AI-translated from English; not yet reviewed by a fluent editor.
# Claude Code skills kombinieren: drei geprüfte Entwürfe aus einer freigegebenen Quelle
> Mit vier getrennten Claude Code skills eine freigegebene Textquelle erfassen, LinkedIn-, Newsletter- und Anzeigenentwürfe erstellen und vor der menschlichen Prüfung Dateihashes und Aussagen kontrollieren.
By BIG CHANGE Editorial
Published: 2026-10-19T19:41:34.834Z
Updated: 2026-10-19T19:41:34.834Z
Canonical: https://bigchange.ai/blog/claude-code-three-channel-skill-pipeline-guide

AI-generated conceptual illustration by BIG CHANGE; no actual Claude Code interface, model output, reviewed draft or campaign is depicted.
Ein freigegebener Artikel oder ein Transkript kann mehrere Entwürfe liefern. Jeder braucht aber einen klaren Rückweg zur Quelle. Diese Anleitung kombiniert vier getrennte [Claude Code Projekt-skills](https://code.claude.com/docs/en/skills), um eine vom Eigentümer freigegebene Textquelle zu erfassen, einen LinkedIn-Beitrag, Newsletter-Abschnitt und eine Anzeige in getrennten Verzeichnissen zu entwerfen und ihre Dateiverträge mit einem lokalen Python-Zusammenführungsskript zu prüfen. Das Ergebnis sind Entwürfe für eine namentlich bestimmte Prüfperson, keine Erlaubnis zum Veröffentlichen oder Versenden.
BIG CHANGE hat die folgenden skills, Verträge und den Zusammenführungscode geprüft. Eine unabhängige Prüfperson führte 16 lokale Strukturprüfungen aus. Wir haben **nicht** die skills in Claude Code installiert oder ausgeführt, einen Modelllauf bezahlt, einen sachhaltigen Kanalentwurf erzeugt oder eine reale Kampagne getestet. Die folgenden Schritte sind ein Verfahren, das Leser mit ihrer eigenen freigegebenen Quelle testen sollen.
## Die große Veränderung
- **Was sich ändert:** Vier eigene skills erhalten ausdrückliche Verträge für Quelle, Inventar und Kanalergebnis, gefolgt von einer deterministischen Zusammenführung.
- **Warum das wichtig ist:** Jeder Entwurf lässt sich auf Quellpassagen zurückführen; ein fehlender Kanal bleibt als blockierter Platz sichtbar.
- **Worauf zu achten ist:** Dateiprüfungen beweisen weder Wahrheit noch Bearbeitungsrechte oder redaktionelle Freigabe. Ein Mensch muss jeden fertigen Entwurf prüfen.
## 1. Zugriff, Rechte und Quelle bestätigen
Verwenden Sie ein selbst kontrolliertes Claude Code Repository und ein Konto, das die Quelle verarbeiten darf. Prüfen Sie vor vertraulichen Inhalten die Modellverfügbarkeit, [Nutzung und Kosten](https://code.claude.com/docs/en/costs), [Datenrichtlinie](https://code.claude.com/docs/en/data-usage) und [Werkzeugberechtigungen](https://code.claude.com/docs/en/permissions). Der Claude Code Befehl `/usage` kann die Nutzung der aktuellen Sitzung anzeigen; die Abrechnung hängt von Konto und Tarif ab. Kosten oder Einsparungen dieser Pipeline wurden nicht gemessen. Die Quelle muss ein tatsächlich freigegebener UTF-8-Artikel oder ein geprüftes Transkript sein; Eigentümer, Version und Freigabe sind zu dokumentieren. Klären Sie Transkriptgenauigkeit, Rechte je Kanal, Datenschutz und jedes Angebot vorher mit dem Eigentümer. Eine Video-URL oder die leere Vorlage unten erfüllt diese Anforderung nicht.
Die vier Referenz-skills verwenden den dokumentierten Claude Code Speicherort `.claude/skills/<name>/SKILL.md`, Aufrufe per Slash-Befehl und die Ersetzung `$ARGUMENTS`. Ihre Einstellung `context: fork` gibt jedem Aufruf einen eigenen Kontext ohne Gesprächsverlauf. Die Entwurfs-skills verwenden `background: true`; dafür ist Claude Code **v2.1.218 oder neuer** erforderlich. Sie verwenden den allgemeinen Agenten, dessen Schreibfähigkeit und wirksame Berechtigungen der Claude Code Konfiguration des Nutzers folgen. Skill-Text erteilt keine Rechte und erzwingt keine Nur-Lese-Grenze. Hintergrundarbeiten können sich überschneiden, wenn der Host es erlaubt; parallele Ausführung ist nicht garantiert. Prüfen Sie vorhandene gleichnamige skills und die Befehlserkennung vorab und stellen Sie sicher, dass der Befehl auf die installierte Projektdatei verweist. Siehe [skills-Handbuch](https://code.claude.com/docs/en/skills) und [subagents-Handbuch](https://code.claude.com/docs/en/sub-agents).
## 2. Die vier Dateien und Verträge installieren
Legen Sie im Projekt die exakten Pfade der folgenden Dateiliste an. Kopieren Sie jeden vollständigen Block aus den Referenzdateien unverändert. Die fünf Dateien unter `.claude/repurpose-contracts/` definieren Eingabe-, Inventar-, Blockierungs- und Kanalergebnisformate sowie den Abhängigkeitsplan. Legen Sie `join.py` im Projektstamm ab. Die beiden Vorlagen gehören unter `repurpose-inputs/`; ersetzen Sie ihre Platzhalter vor jedem Lauf durch echte freigegebene Inhalte. Der Ausgabebaum dient nur der Strukturübersicht. Die vollständigen Dateien stehen unten zum Kopieren.
| Datei | Zweck |
| --- | --- |
| `.claude/skills/source-inventory/SKILL.md` | Prüft Freigaben und erfasst vor dem Entwurf jede Quellzeile. |
| `.claude/skills/draft-linkedin/SKILL.md` | Verwaltet nur LinkedIn-Entwurf und -Ergebnis. |
| `.claude/skills/draft-newsletter/SKILL.md` | Verwaltet nur Newsletter-Entwurf und -Ergebnis. |
| `.claude/skills/draft-ad/SKILL.md` | Verwaltet nur Anzeigenentwurf und -Ergebnis oder einen ausdrücklichen Angebotsblocker. |
| `.claude/repurpose-contracts/*.json` | Definiert exakte Felder, Hash-Verbindungen und Stufenfolge. |
| `join.py` | Prüft Struktur und Herkunft und erstellt Aussagezuordnung und Prüfungsübergabe. |
| `repurpose-inputs/approved-source.template.md`, `repurpose-inputs/constraints.template.json` | Leere Startformulare, niemals Ausführungsnachweise. |
Die Vorgaben verlangen ausdrückliche Bearbeitungsrechte für LinkedIn, Newsletter und Anzeige, auch wenn der Anzeigenzweig wegen fehlenden freigegebenen Angebots blockiert wird. `forbiddenClaims` darf eine leere Liste sein, wenn der Eigentümer keine weiteren Verbote hat. Eine Anzeige braucht ein freigegebenes Angebot, ein HTTP(S)-Ziel und einen Handlungsaufruf; jeder fehlende Wert blockiert diesen Zweig. Benennen Sie eine echte Prüfperson. Quelle und Vorgaben bleiben während eines Laufs unverändert.
## 3. Jede Verbindung testen, dann das Inventar ausführen
Testen Sie jeden skill zuerst einzeln an einer begrenzten echten freigegebenen Quelle. Vergleichen Sie tatsächliche Dateien und Hashes mit den Verträgen. BIG CHANGE hat diesen Claude Code Test nicht ausgeführt. Legen Sie für einen Gesamtlauf neue absolute Verzeichnisse für Eingaben, Laufergebnisse und Zusammenführung an. Rufen Sie im Repository den Inventarbefehl mit drei absoluten Pfaden auf:
```prompt
/source-inventory <absolute-path-to-approved-source.md> <absolute-path-to-constraints.json> <absolute-path-to-new-run-directory>
```
Ersetzen Sie jedes Argument in spitzen Klammern durch einen echten Pfad. Der skill sollte `inventory.json` mit wortgetreuen geordneten Passagenbereichen für sämtliche Zeilen schreiben oder `inventory-blocked.json` mit der genau fehlenden Freigabe beziehungsweise dem Recht. Beginnen Sie keinen Entwurf, wenn das Inventar blockiert ist. Vergleichen Sie die aufgezeichneten SHA-256-Werte mit beiden Eingabedateien und kontrollieren Sie die Passagengrenzen einschließlich Leerzeilen.
## 4. In drei getrennten Kanälen entwerfen
Sobald `inventory.json` existiert und beide Eingaben weiterhin die dokumentierten Hashes haben, rufen Sie die drei getrennten skills auf. Verwenden Sie dasselbe absolute Laufverzeichnis als erstes Argument und drei verschiedene Ausgabeverzeichnisse als zweites. Jeder skill liest freigegebene Quelle, Vorgaben und Inventar, keinen Geschwisterentwurf. Nach dem Inventar können sie unabhängig starten, soweit Host und Berechtigungen dies erlauben.
```prompt
/draft-linkedin <absolute-run-directory> <absolute-run-directory>/linkedin
/draft-newsletter <absolute-run-directory> <absolute-run-directory>/newsletter
/draft-ad <absolute-run-directory> <absolute-run-directory>/ad
```
Jeder Kanal verwaltet `result.json` und nur im bereiten Zustand `draft.md`. Sein Ergebnis nennt Kanal und SHA-256 von Quelle, Vorgaben und Inventar und ordnet wesentliche Aussagen oder Zitate Passagen-IDs und passenden Vorgabenschlüsseln zu. `ready` bedeutet bereit zur Faktenprüfung. Fehlen freigegebenes Anzeigenangebot, Ziel oder Handlungsaufruf, muss ein blockiertes Ergebnis ohne Anzeigenentwurf entstehen. Ein Hintergrundaufruf oder gültiges JSON allein beweist keinen nützlichen oder korrekten Entwurf.
## 5. Ergebnisse zusammenführen und menschlich entscheiden
Wenn alle drei Stufen zurückgekehrt oder endgültig fehlgeschlagen sind, führen Sie die mitgelieferte Python-Datei mit dem Laufverzeichnis und einem **neuen** Zusammenführungsverzeichnis aus. Das Skript nutzt die Python-Standardbibliothek; prüfen Sie vorher Ihre lokale Installation.
```bash
python join.py <absolute-run-directory> <absolute-new-join-directory>
```
Die Zusammenführung prüft Quell- und Vorgabenhashes, vollständige Zeilenabdeckung, Inventarhash in jedem Kanal, Stufenidentität, Aussageverweise, Ausgabezuständigkeit und Anzeigenvoraussetzungen. Sie schreibt `claim-map.md`, `review.md` und `summary.json`. Fehlende oder ungültige Stufen werden blockiert angezeigt, gültige Geschwisterentwürfe bleiben sichtbar. Eine globale Quellenabweichung stoppt die Zusammenführung. Sie prüft nicht, ob ein Satz wahr, ein Zitat fair, eine Aussage rechtlich ausreichend belegt oder die Veröffentlichung genehmigt ist. `review.md` beginnt mit Entscheidungen im Zustand `PENDING`.
Die benannte Prüfperson öffnet jeden bereiten `draft.md`, vergleicht alle wesentlichen Aussagen und Zitate mit referenzierten Passagen und Vorgaben, prüft Mehrdeutigkeit, Kanalton, Bearbeitungsrechte, Anzeigenziel und Angebot und dokumentiert **annehmen, überarbeiten oder ablehnen** je Kanal außerhalb der erzeugten ausstehenden Felder. Veröffentlichen, Versenden, Terminieren und Anzeigenplatzierung benötigen eigene menschliche Autorisierung und Plattformprüfungen.
## 6. Fehlgeschlagenen Zweig retten, gültige Arbeit erhalten
Sind Quellenfreigabe oder Rechte unklar, klären Sie die genaue Lücke mit dem Eigentümer und starten Sie einen neuen Lauf mit korrigierten Eingaben. Jede Änderung des Quell- oder Vorgabenhashs erfordert ein vollständig neues Inventar. Scheitert nur ein Kanal, prüfen Sie zuerst seine tatsächlichen Dateien und Fehler. Kopieren Sie die unveränderten Inventarbytes und geprüften Geschwisterordner in ein neues Laufverzeichnis, führen Sie dort nur die gescheiterte Stufe aus und führen Sie in einem weiteren neuen Verzeichnis zusammen. Kopierte Ergebnisse müssen weiterhin zum Inventarhash passen. Bei unsicherem Prozessausgang lesen Sie Ergebnis und Hashes vor einer Wiederholung. Erhalten Sie alte Läufe und menschliche Änderungen; überschreiben Sie sie nie. Reparieren Sie fehlende oder nicht unterstützte Aussageverweise in der zuständigen Stufe und wiederholen Sie die deterministische Zusammenführung in einem neuen Verzeichnis.
Dies ist manuell aufgerufene, modellgesteuerte Komposition. Sie bietet keine codeerzwungene Verteilung, automatischen Wiederholungen, feste Parallelität oder Wiederherstellung externer Nebenwirkungen. Beginnen Sie mit einer freigegebenen Textquelle; das Verfahren beschafft oder prüft kein Transkript und erzeugt keine Video-, Audio- oder Bildkampagneninhalte.
## Vollständige Referenzdateien
Bewahren Sie Pfade und Inhalte zusammen auf. Die folgenden Codeblöcke sind vollständige Kopien des unabhängig geprüften Pakets, Referenzdateien und keine Aufzeichnungen eines Claude-Laufs.
### `.claude/skills/source-inventory/SKILL.md`
```markdown
---
name: source-inventory
description: Build a bounded passage inventory from one approved owned source for a three-format draft pipeline.
disable-model-invocation: true
context: fork
agent: general-purpose
background: false
---
Input arguments: `$ARGUMENTS[0]` is the approved UTF-8 source Markdown path, `$ARGUMENTS[1]` is the constraints JSON path, and `$ARGUMENTS[2]` is a new run directory. Read only those inputs and the run directory contract. Do not infer inputs from conversation history.
1. Verify both files exist. Read `constraints.json` against `.claude/repurpose-contracts/constraints.schema.json`; require `sourceOwner`, `sourceVersion`, `approvedAt`, `adaptationRights`, `audience`, `permittedChannels`, `reviewer`, `forbiddenClaims`, and `offer`. `adaptationRights` must explicitly mark all three channels `true`; a template or unapproved value does not count. `offer` may be null; that blocks ad copy requiring an offer or destination. If any approval or right is unclear, create only `inventory-blocked.json` under `.claude/repurpose-contracts/inventory-blocked.schema.json` explaining the gap and stop.
2. Record SHA-256 hashes of the exact two input files. Divide the entire approved source into numbered, contiguous, verbatim line ranges with no omitted lines. Give each range a stable `P001`-style ID, start/end line and exact text, including blank lines where they occur in a range. Do not add outside facts, inferred transcript words, or quotations not present in the source.
3. Write `inventory.json` in the run directory according to `.claude/repurpose-contracts/inventory.schema.json` in the reader project. Record the source and constraints paths/hashes, source owner/version/approval, passages and any ambiguous passages requiring human review. Do not edit either input.
4. Return the inventory path and blockers. This stage never drafts, publishes, fetches video, contacts services or approves source rights on behalf of the owner.
```
### `.claude/skills/draft-linkedin/SKILL.md`
```markdown
---
name: draft-linkedin
description: Draft one review-only LinkedIn post from an approved source inventory.
disable-model-invocation: true
context: fork
agent: general-purpose
background: true
---
Inputs: `$ARGUMENTS[0]` is the run directory containing `inventory.json`; `$ARGUMENTS[1]` is the dedicated `linkedin` output directory. Read the source and constraints paths recorded in the inventory and verify their hashes still match. Do not rely on prior conversation or sibling-stage results.
Write only under the dedicated output directory. If required source, permission, audience or voice information is missing or conflicting, write `result.json` with `status: blocked` and exact blockers; do not invent it. Otherwise create `draft.md`: one self-contained LinkedIn post with a factual opening and concrete takeaway, no invented statistics, quotes, benefits, offers or links. Write `result.json` according to `.claude/repurpose-contracts/stage-result.schema.json` in the reader project, with `channel: linkedin`, input hashes, inventory hash, draft path, and every material claim or quote paired with exact passage IDs and any applicable constraints keys. Mark uncertainty in the draft and result. `status: ready` means ready for human fact review, not approved or published. Return the two file paths. Never post or schedule.
```
### `.claude/skills/draft-newsletter/SKILL.md`
```markdown
---
name: draft-newsletter
description: Draft one review-only newsletter section from an approved source inventory.
disable-model-invocation: true
context: fork
agent: general-purpose
background: true
---
Inputs: `$ARGUMENTS[0]` is the run directory containing `inventory.json`; `$ARGUMENTS[1]` is the dedicated `newsletter` output directory. Read the source and constraints paths recorded in the inventory and verify their hashes still match. Do not rely on prior conversation or sibling-stage results.
Write only under the dedicated output directory. If required source, permission, audience or voice information is missing or conflicting, write `result.json` with `status: blocked` and exact blockers. Otherwise create `draft.md`: a descriptive heading, short summary and evidence-linked detail suited to the approved newsletter audience. Add no invented statistics, quotes, benefits or outside news. Write `result.json` according to `.claude/repurpose-contracts/stage-result.schema.json` in the reader project, with `channel: newsletter`, input hashes, inventory hash, draft path, and every material claim or quote paired with exact passage IDs and applicable constraints keys. Mark uncertainty in the draft and result. `status: ready` means ready for human fact review, not approved or sent. Return the two file paths. Never send or schedule.
```
### `.claude/skills/draft-ad/SKILL.md`
```markdown
---
name: draft-ad
description: Draft review-only ad copy from an approved source inventory and approved offer.
disable-model-invocation: true
context: fork
agent: general-purpose
background: true
---
Inputs: `$ARGUMENTS[0]` is the run directory containing `inventory.json`; `$ARGUMENTS[1]` is the dedicated `ad` output directory. Read the source and constraints paths recorded in the inventory and verify their hashes still match. Do not rely on prior conversation or sibling-stage results.
Write only under the dedicated output directory. Require an approved offer, destination URL and call to action in constraints, plus adaptation rights for advertising. If any are absent, write `result.json` with `status: blocked`, the missing exact fields and no draft. Otherwise create `draft.md` with one headline, one body and the approved call to action. No invented metrics, testimonials, urgency, claims, price or performance. Write `result.json` according to `.claude/repurpose-contracts/stage-result.schema.json` in the reader project, with `channel: ad`, input hashes, inventory hash, draft path, and every material claim or quote paired with exact passage IDs and applicable constraints keys. Mark uncertainty in the draft and result. `status: ready` means ready for human fact/legal review, not approved or placed. Return the two file paths. Never upload, buy or schedule ads.
```
### `.claude/repurpose-contracts/constraints.schema.json`
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Reader-approved source constraints",
"type": "object",
"required": ["sourceOwner", "sourceVersion", "approvedAt", "adaptationRights", "audience", "permittedChannels", "reviewer", "forbiddenClaims", "voiceRules", "offer"],
"properties": {
"sourceOwner": {"type": "string", "minLength": 1},
"sourceVersion": {"type": "string", "minLength": 1},
"approvedAt": {"type": "string", "minLength": 1},
"adaptationRights": {"type": "object", "required": ["linkedin", "newsletter", "ad"], "properties": {"linkedin": {"type": "boolean"}, "newsletter": {"type": "boolean"}, "ad": {"type": "boolean"}}},
"audience": {"type": "string", "minLength": 1},
"permittedChannels": {"type": "array", "uniqueItems": true, "items": {"enum": ["linkedin", "newsletter", "ad"]}},
"reviewer": {"type": "string", "minLength": 1},
"forbiddenClaims": {"type": "array", "items": {"type": "string"}},
"voiceRules": {"type": "string", "minLength": 1},
"offer": {"type": ["object", "null"], "required": ["approvedOffer", "destinationUrl", "callToAction"], "properties": {"approvedOffer": {"type": "string", "minLength": 1}, "destinationUrl": {"type": "string", "format": "uri"}, "callToAction": {"type": "string", "minLength": 1}}}
}
}
```
### `.claude/repurpose-contracts/inventory.schema.json`
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Approved source inventory",
"type": "object",
"additionalProperties": false,
"required": ["sourcePath", "sourceSha256", "constraintsPath", "constraintsSha256", "sourceOwner", "sourceVersion", "approvedAt", "passages", "ambiguities"],
"properties": {
"sourcePath": {"type": "string", "minLength": 1},
"sourceSha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
"constraintsPath": {"type": "string", "minLength": 1},
"constraintsSha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
"sourceOwner": {"type": "string", "minLength": 1},
"sourceVersion": {"type": "string", "minLength": 1},
"approvedAt": {"type": "string", "minLength": 1},
"passages": {"type": "array", "minItems": 1, "items": {"type": "object", "additionalProperties": false, "required": ["id", "startLine", "endLine", "text"], "properties": {"id": {"type": "string", "pattern": "^P[0-9]{3,}$"}, "startLine": {"type": "integer", "minimum": 1}, "endLine": {"type": "integer", "minimum": 1}, "text": {"type": "string"}}}},
"ambiguities": {"type": "array", "items": {"type": "string"}}
}
}
```
### `.claude/repurpose-contracts/inventory-blocked.schema.json`
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Preflight block before drafting",
"type": "object",
"additionalProperties": false,
"required": ["status", "blockers"],
"properties": {
"status": {"const": "blocked"},
"blockers": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}
}
}
```
### `.claude/repurpose-contracts/stage-result.schema.json`
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "One isolated channel result",
"type": "object",
"additionalProperties": false,
"required": ["channel", "status", "sourceSha256", "constraintsSha256", "inventorySha256", "draftFile", "claims", "blockers"],
"properties": {
"channel": {"enum": ["linkedin", "newsletter", "ad"]},
"status": {"enum": ["ready", "blocked"]},
"sourceSha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
"constraintsSha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
"inventorySha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
"draftFile": {"type": ["string", "null"]},
"claims": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["text", "passageIds", "constraintKeys", "status"], "properties": {"text": {"type": "string", "minLength": 1}, "passageIds": {"type": "array", "items": {"type": "string", "pattern": "^P[0-9]{3,}$"}}, "constraintKeys": {"type": "array", "items": {"type": "string"}}, "status": {"enum": ["supported", "needs-human-check", "unsupported"]}}}},
"blockers": {"type": "array", "items": {"type": "string"}}
}
}
```
### `.claude/repurpose-contracts/pipeline-plan.json`
```json
{
"purpose": "Compose reader-owned skills into one source-to-three-format review run",
"execution": "manual Claude Code skill invocation plus deterministic local join",
"sourceStage": {
"skill": "source-inventory",
"inputs": ["approved-source.md", "constraints.json"],
"success": "inventory.json with source and constraints hashes and complete numbered line coverage",
"failure": "inventory-blocked.json; stop all channel stages"
},
"parallelAfterInventory": [
{"skill": "draft-linkedin", "reads": ["approved-source.md", "constraints.json", "inventory.json"], "writesOnly": "linkedin/", "contract": "stage-result.schema.json"},
{"skill": "draft-newsletter", "reads": ["approved-source.md", "constraints.json", "inventory.json"], "writesOnly": "newsletter/", "contract": "stage-result.schema.json"},
{"skill": "draft-ad", "reads": ["approved-source.md", "constraints.json", "inventory.json"], "writesOnly": "ad/", "contract": "stage-result.schema.json"}
],
"join": {
"file": "join.py",
"dependsOn": ["all three stage results or recorded missing/failed slots"],
"writesOnly": "new join directory",
"outputs": ["claim-map.md", "review.md", "summary.json"],
"humanApproval": "pending"
}
}
```
### `join.py`
```python
"""Deterministic join/check for the proposed local, review-only pipeline.
This does not call a model, generate prose, or approve factual support. Run only
on reader-owned paths after separately invoked stage skills have returned.
"""
import hashlib
import json
import sys
from pathlib import Path
from urllib.parse import urlparse
CHANNELS = ("linkedin", "newsletter", "ad")
HEX = set("0123456789abcdef")
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def valid_hash(value):
return isinstance(value, str) and len(value) == 64 and set(value) <= HEX
def read_json(path):
return json.loads(path.read_text(encoding="utf-8"))
def string_list(value):
return isinstance(value, list) and all(isinstance(v, str) and v.strip() for v in value)
def approved_offer(value):
return (isinstance(value, dict) and
all(isinstance(value.get(k), str) and value[k].strip()
for k in ("approvedOffer", "destinationUrl", "callToAction")) and
urlparse(value["destinationUrl"]).scheme in ("http", "https") and
bool(urlparse(value["destinationUrl"]).netloc))
def check_inventory(run):
inv_path = run / "inventory.json"
inv = read_json(inv_path)
expected = {"sourcePath", "sourceSha256", "constraintsPath", "constraintsSha256",
"sourceOwner", "sourceVersion", "approvedAt", "passages", "ambiguities"}
if set(inv) != expected:
raise ValueError("inventory keys differ from contract")
source, constraints = Path(inv["sourcePath"]), Path(inv["constraintsPath"])
if not source.is_file() or not constraints.is_file():
raise ValueError("approved source or constraints missing")
if digest(source) != inv["sourceSha256"] or digest(constraints) != inv["constraintsSha256"]:
raise ValueError("approved input changed since inventory")
rules = read_json(constraints)
required = ("sourceOwner", "sourceVersion", "approvedAt", "adaptationRights",
"audience", "permittedChannels", "reviewer", "forbiddenClaims", "voiceRules", "offer")
if not isinstance(rules, dict) or any(k not in rules for k in required):
raise ValueError("constraints approval/rights fields incomplete")
text_fields = ("sourceOwner", "sourceVersion", "approvedAt", "audience", "reviewer", "voiceRules")
if any(not isinstance(rules[k], str) or not rules[k].strip() for k in text_fields):
raise ValueError("constraints text fields incomplete")
if not string_list(rules["forbiddenClaims"]) or not string_list(rules["permittedChannels"]):
raise ValueError("constraints lists malformed")
if any("REPLACE_WITH" in rules[k] for k in text_fields):
raise ValueError("constraints template was not completed")
if not isinstance(rules["adaptationRights"], dict) or any(rules["adaptationRights"].get(c) is not True for c in CHANNELS):
raise ValueError("all three adaptation rights must be explicit")
if not set(CHANNELS).issubset(set(rules["permittedChannels"])):
raise ValueError("three-channel rights not explicit")
if any(rules[k] != inv[k] for k in ("sourceOwner", "sourceVersion", "approvedAt")):
raise ValueError("source identity differs from constraints")
lines = source.read_text(encoding="utf-8").splitlines()
if not lines:
raise ValueError("source is empty")
next_line, ids = 1, set()
if not isinstance(inv["passages"], list) or not string_list(inv["ambiguities"]):
raise ValueError("inventory passages or ambiguities malformed")
for n, p in enumerate(inv["passages"], 1):
if set(p) != {"id", "startLine", "endLine", "text"}:
raise ValueError("passage keys differ from contract")
if p["id"] != f"P{n:03d}" or p["id"] in ids:
raise ValueError("passage IDs must be ordered and unique")
ids.add(p["id"])
if (type(p["startLine"]) is not int or type(p["endLine"]) is not int or
p["startLine"] != next_line or p["endLine"] < next_line or p["endLine"] > len(lines)):
raise ValueError("source line range gap or overlap")
actual = "\n".join(lines[p["startLine"] - 1:p["endLine"]])
if p["text"] != actual:
raise ValueError("passage text differs from approved source")
next_line = p["endLine"] + 1
if next_line != len(lines) + 1:
raise ValueError("inventory omits source lines")
return inv_path, inv, rules, ids
def check_stage(run, channel, inv, rules, ids, inv_hash):
folder = run / channel
path = folder / "result.json"
if not path.is_file():
return {"channel": channel, "status": "blocked", "blockers": ["result.json missing"], "claims": []}
try:
result = read_json(path)
required = {"channel", "status", "sourceSha256", "constraintsSha256",
"inventorySha256", "draftFile", "claims", "blockers"}
if set(result) != required or result["channel"] != channel:
raise ValueError("result keys or channel differ from contract")
if result["status"] not in ("ready", "blocked"):
raise ValueError("invalid result status")
if (result["sourceSha256"] != inv["sourceSha256"] or
result["constraintsSha256"] != inv["constraintsSha256"] or
result["inventorySha256"] != inv_hash):
raise ValueError("result belongs to another source/inventory")
if not isinstance(result["claims"], list) or not string_list(result["blockers"]):
raise ValueError("claims/blockers must be arrays")
if result["status"] == "ready":
if channel == "ad" and not approved_offer(rules.get("offer")):
raise ValueError("ad requires approved offer, HTTP(S) destination and call to action")
if result["draftFile"] != "draft.md" or not result["claims"] or result["blockers"]:
raise ValueError("ready result lacks draft or claim list")
draft = folder / "draft.md"
if not draft.is_file() or not draft.read_text(encoding="utf-8").strip():
raise ValueError("ready draft missing or empty")
elif not result["blockers"] or result["draftFile"] is not None:
raise ValueError("blocked result needs reasons and no draft")
for claim in result["claims"]:
if set(claim) != {"text", "passageIds", "constraintKeys", "status"}:
raise ValueError("claim keys differ from contract")
if not isinstance(claim["text"], str) or not claim["text"].strip():
raise ValueError("empty claim")
if claim["status"] not in ("supported", "needs-human-check", "unsupported"):
raise ValueError("invalid claim status")
if not string_list(claim["passageIds"]) or not string_list(claim["constraintKeys"]):
raise ValueError("claim evidence pointers must be string arrays")
if not set(claim["passageIds"]).issubset(ids):
raise ValueError("unknown source passage ID")
if not claim["passageIds"] and not claim["constraintKeys"]:
raise ValueError("claim has no evidence pointer")
if not set(claim["constraintKeys"]).issubset(rules):
raise ValueError("unknown constraints key")
if claim["status"] == "unsupported" and result["status"] == "ready":
raise ValueError("unsupported claim in ready draft")
return result
except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError) as exc:
return {"channel": channel, "status": "blocked", "blockers": [f"invalid result: {exc}"], "claims": []}
def main():
if len(sys.argv) != 3:
raise SystemExit("Usage: python join.py RUN_DIRECTORY NEW_JOIN_DIRECTORY")
run, target = Path(sys.argv[1]).resolve(), Path(sys.argv[2]).resolve()
if target.exists():
raise SystemExit("join output already exists; use a new directory to preserve prior review")
inv_path, inv, rules, ids = check_inventory(run)
inv_hash = digest(inv_path)
results = [check_stage(run, channel, inv, rules, ids, inv_hash) for channel in CHANNELS]
target.mkdir(parents=True, exist_ok=False)
map_lines = ["# Claim map for human review", "", f"Source SHA-256: {inv['sourceSha256']}",
f"Constraints SHA-256: {inv['constraintsSha256']}", f"Inventory SHA-256: {inv_hash}", ""]
review_lines = ["# Review handoff", "", f"Reviewer: {rules['reviewer']}",
"No draft is approved, sent or published by this join.", ""]
for result in results:
channel = result["channel"]
map_lines += [f"## {channel}", ""]
review_lines += [f"## {channel}: {result['status']}", ""]
for blocker in result["blockers"]:
review_lines.append(f"- Blocker: {blocker}")
for claim in result["claims"]:
refs = ", ".join(claim["passageIds"] + claim["constraintKeys"])
map_lines.append(f"- {claim['status']}: {claim['text']} — {refs}")
map_lines.append("")
review_lines += ["- Human must compare every claim and quote with the approved inputs.",
"- Human must check rights, voice, audience and any offer/destination.",
"- Human approval decision: PENDING", ""]
(target / "claim-map.md").write_text("\n".join(map_lines), encoding="utf-8")
(target / "review.md").write_text("\n".join(review_lines), encoding="utf-8")
summary = {"sourceSha256": inv["sourceSha256"], "constraintsSha256": inv["constraintsSha256"],
"inventorySha256": inv_hash, "channels": {r["channel"]: r["status"] for r in results},
"humanApproval": "pending"}
(target / "summary.json").write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8")
print(json.dumps(summary))
if __name__ == "__main__":
main()
```
### `repurpose-inputs/approved-source.template.md`
```markdown
# Replace with the owner's approved written source
This template is deliberately empty of publishable facts. Supply the exact approved transcript or article, retain line breaks for passage IDs, and record its owner and version in constraints.json. Do not invoke the pipeline on this template.
```
### `repurpose-inputs/constraints.template.json`
```json
{
"sourceOwner": "REPLACE_WITH_ACTUAL_OWNER",
"sourceVersion": "REPLACE_WITH_ACTUAL_VERSION",
"approvedAt": "REPLACE_WITH_ACTUAL_APPROVAL_DATE",
"adaptationRights": {"linkedin": false, "newsletter": false, "ad": false},
"audience": "REPLACE_WITH_APPROVED_AUDIENCE",
"permittedChannels": ["linkedin", "newsletter", "ad"],
"reviewer": "REPLACE_WITH_ACTUAL_HUMAN_REVIEWER",
"forbiddenClaims": [],
"voiceRules": "REPLACE_WITH_APPROVED_VOICE_RULES",
"offer": null
}
```
### `expected-output-tree.txt`
```markdown
STRUCTURE ONLY. No Claude run or model output exists in this packet.
<new-run>/
inventory.json from source-inventory skill, or inventory-blocked.json
linkedin/result.json ready or blocked; draft.md only when ready
newsletter/result.json ready or blocked; draft.md only when ready
ad/result.json ready or blocked; draft.md only when ready
<new-join>/
claim-map.md generated by join.py from stage claims
review.md all three statuses and PENDING human decisions
summary.json hashes, statuses and humanApproval=pending
```
## Quellen und weiterführende Informationen
- [Claude Code skills-Handbuch](https://code.claude.com/docs/en/skills) — Projektpfade, frontmatter, Aufruf, Argumentersetzung, separater Kontext und Hintergrundverhalten. Geprüft am 19. Oktober 2026; keine einheitliche Version für dieses Rezept angegeben.
- [Claude Code subagents-Handbuch](https://code.claude.com/docs/en/sub-agents) — allgemeiner Agent, Kontext und Berechtigungen. Geprüft am 19. Oktober 2026.
- [Claude Code Kosten](https://code.claude.com/docs/en/costs), [Datennutzung](https://code.claude.com/docs/en/data-usage) und [Berechtigungen](https://code.claude.com/docs/en/permissions) — kontoabhängige Nutzung, Datenverarbeitung und Werkzeugkontrollen. Geprüft am 19. Oktober 2026; keine Pipelineausgaben gemessen.
- Das unabhängig geprüfte lokale Paket liefert vier skills, fünf JSON-Verträge, zwei Eingabevorlagen, Ausgabebaum und Zusammenführungsskript. Die 16 Prüfungen nutzten nur temporäre Strukturtestdaten; weder Textgenerierung noch Faktenfreigabe erfolgten.
- [Verwandte BIG CHANGE Anleitung: parallele Dokumentprüfung](https://bigchange.ai/blog/claude-managed-agents-parallel-document-review-guide) behandelt einen anderen Claude-Ablauf für Dokumentprüfung statt Code-lokaler Quellenanpassung für drei Kanäle.
## Sources
- [Claude Code skills-Handbuch](https://code.claude.com/docs/en/skills) — Projektpfad, Aufruf, separater Kontext und Hintergrundverhalten; HTTP 200 und Hash am 2026-10-19 geprüft.
- [Claude Code subagents-Handbuch](https://code.claude.com/docs/en/sub-agents) — Allgemeiner Agent und Berechtigungen; HTTP 200 und Hash am 2026-10-19 geprüft.
- [Claude Code Kosten](https://code.claude.com/docs/en/costs) — Kontoabhängige Nutzung und Sitzungsverbrauchsanzeige; geprüft am 2026-10-19.
- [Claude Code Datennutzung](https://code.claude.com/docs/en/data-usage) — Datenrichtlinie für Konto und Organisation; geprüft am 2026-10-19.
- [Claude Code Berechtigungen](https://code.claude.com/docs/en/permissions) — Tatsächlicher Werkzeugzugriff wird außerhalb des skill-Texts konfiguriert; geprüft am 2026-10-19.
Der Newsletter von BIG CHANGE
Das große Ganze. In Ihrem Tempo.
Aktuelle Storys über KI und Robotik, beobachtenswerte Veränderungen und praktische Ideen zur Anwendung. Wählen Sie ein tägliches Briefing, eine wöchentliche Zusammenfassung oder eine monatliche Perspektive.
Next scheduled send (UTC): . Your first edition arrives at the next scheduled send after you confirm.
Ihr Datenschutz, Ihre Entscheidung.
Notwendiger Speicher schützt die Website und merkt sich Ihre Einstellungen. Optionales Google Analytics bleibt deaktiviert, bis Sie es erlauben. Sie können alle Geschichten auch nur mit notwendigem Speicher lesen. Datenschutzdetails