Odobreni članak ili transkript može biti osnova za više nacrta, ali svaki mora jasno voditi do izvora. Ovaj vodič povezuje četiri zasebne projektne veštine Claude Code za popis teksta koji je vlasnik odobrio, izradu LinkedIn objave, odeljka biltena i oglasa u zasebnim direktorijumima, pa proveru ugovora datoteka lokalnim Python spajanjem. Rezultat su nacrti za imenovanog ljudskog recenzenta, a ne dozvola za objavljivanje ili slanje.
BIG CHANGE je pregledao sledeće veštine, ugovore i kod za spajanje. Nezavisni recenzent izvršio je 16 lokalnih strukturnih provera. Mi nismo instalirali niti pokrenuli veštine u Claude Code, platili izvršavanje modela, napravili stvarni nacrt za kanal niti testirali stvarnu kampanju. Sledeći koraci su postupak koji čitalac treba da isproba sa svojim odobrenim izvorom.
Velika promena
- Šta se promenilo: Četiri sopstvene veštine imaju izričite ugovore za izvor, popis i rezultate kanala, a zatim sledi determinističko spajanje.
- Zašto je važno: Svaki nacrt može da se poveže sa pasusima izvora, dok nedostajući kanal ostaje vidljiv kao blokirano mesto.
- Na šta paziti: Provere datoteka ne potvrđuju istinitost, prava prerade ili uredničko odobrenje; čovek mora da pregleda svaki spreman nacrt.
1. Potvrdite pristup, prava i izvor
Koristite Claude Code repozitorijum koji kontrolišete i nalog ovlašćen da obrađuje izvor. Pre poverljivog materijala proverite dostupne modele, potrošnju i troškove, politiku podataka i dozvole alata. Claude Code komanda /usage može prikazati potrošnju tekuće sesije; naplata zavisi od naloga i paketa. Troškovi ni uštede ovog toka nisu mereni. Izvor mora biti stvarni odobreni UTF-8 članak ili proveren transkript, uz zabeležene vlasnika, verziju i odobrenje. Najpre sa vlasnikom razrešite tačnost transkripta, prava za svaki kanal, privatnost i svaku ponudu. Video URL ili prazan obrazac ispod ne ispunjavaju ovaj uslov.
Četiri referentne veštine koriste dokumentovanu Claude Code lokaciju .claude/skills/<name>/SKILL.md, pozivanje komandom sa kosom crtom i zamenu $ARGUMENTS. Podešavanje context: fork svakom pozivu daje sopstveni kontekst bez istorije razgovora. Veštine za nacrte koriste background: true, za šta je potreban Claude Code v2.1.218 ili noviji. Koriste agenta opšte namene; mogućnost pisanja datoteka i stvarne dozvole slede korisnikovo podešavanje Claude Code. Tekst veštine ne daje dozvole niti može nametnuti granicu samo za čitanje. Pozadinski rad može da se preklapa ako domaćin dozvoli, ali paralelno izvršavanje nije garantovano. Pre izbora naziva proverite postojeće istoimene veštine i otkrivanje komandi, pa potvrdite da komanda vodi do instalirane projektne datoteke. Pogledajte priručnik za veštine i priručnik za podagente.
2. Instalirajte četiri datoteke i ugovore
U projektu napravite tačne putanje iz spiska. Kopirajte svaki ceo blok referentnih datoteka bez promene sadržaja. Pet datoteka u .claude/repurpose-contracts/ definišu oblike ulaza, popisa, blokiranog stanja i rezultata kanala, kao i plan zavisnosti. Stavite join.py u koren projekta. Dva obrasca idu u repurpose-inputs/; pre pokretanja zamenite njihove privremene vrednosti stvarnim odobrenim sadržajem. Stablo izlaza je samo strukturni vodič. Ispod su cele datoteke za kopiranje.
Datoteka | Namena |
|---|---|
| Proverava odobrenja i popisuje svaki red izvora pre pisanja. |
| Odgovara samo za LinkedIn nacrt i rezultat. |
| Odgovara samo za nacrt i rezultat biltena. |
| Odgovara samo za nacrt i rezultat oglasa ili izričit razlog blokirane ponude. |
| Definiše tačna polja, veze heševa i redosled faza. |
| Proverava strukturu i poreklo, pa pravi mapu tvrdnji i predaju za pregled. |
| Prazni početni obrasci, nikad dokaz izvršavanja. |
Ograničenja zahtevaju izričita prava prerade za LinkedIn, bilten i oglas, čak i kada je oglas blokiran jer nema odobrene ponude. forbiddenClaims može biti prazna lista ako vlasnik nema dodatnih zabrana. Oglas zahteva odobrenu ponudu, HTTP(S) odredište i poziv na akciju; odsustvo bilo čega blokira taj ogranak. Zabeležite stvarnog imenovanog recenzenta. Ne menjajte odobreni izvor ni ograničenja tokom izvršavanja.
3. Testirajte svaku vezu, pa pokrenite popis
Najpre testirajte svaku veštinu zasebno na ograničenom stvarnom odobrenom izvoru. Uporedite stvarne datoteke i heševe sa ugovorima. BIG CHANGE nije izvršio taj Claude Code test. Za ceo tok napravite nove apsolutne direktorijume za ulaz, rezultate i spajanje. U repozitorijumu pozovite komandu za popis sa tri apsolutne putanje:
/source-inventory <absolute-path-to-approved-source.md> <absolute-path-to-constraints.json> <absolute-path-to-new-run-directory>Svaki argument u uglastim zagradama oblika <…> zamenite stvarnom putanjom. Veština treba da napiše inventory.json sa doslovnim, uređenim opsezima odlomaka koji pokrivaju svaki red, ili inventory-blocked.json sa tačnim nedostajućim odobrenjem ili pravom. Ne počinjite nacrt ako je popis blokiran. Uporedite zabeležene SHA-256 vrednosti sa obe ulazne datoteke i pregledajte granice odlomaka, uključujući prazne redove.
4. Pišite u tri izolovana kanala
Kada inventory.json postoji, a oba ulaza i dalje imaju zabeležene heševe, pozovite tri zasebne veštine. Za prvi argument koristite isti apsolutni direktorijum izvršavanja, a za drugi tri različita izlazna direktorijuma. Svaka veština čita odobreni izvor, ograničenja i popis, nikad susedni nacrt. Mogu se nezavisno pokrenuti posle popisa, u granicama domaćina i Claude Code dozvola.
/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>/adSvaki kanal odgovara za result.json i, samo kada je spreman, draft.md. Rezultat identifikuje kanal i SHA-256 izvora, ograničenja i popisa, pa vezuje bitne tvrdnje ili citate za ID odlomaka i odgovarajuće ključeve ograničenja. ready znači spremno za proveru činjenica. Nedostajuća odobrena ponuda, odredište ili poziv na akciju moraju dati blokiran rezultat bez nacrta oglasa. Pozadinski poziv ili ispravan JSON sami ne dokazuju koristan ili tačan nacrt.
5. Spojite rezultate i donesite ljudsku odluku
Kada se sve tri faze završe ili definitivno otkažu, pokrenite priloženu Python datoteku sa direktorijumom izvršavanja i novim direktorijumom za spajanje. Koristi se Python standardna biblioteka; pre poziva proverite lokalnu instalaciju.
python join.py <absolute-run-directory> <absolute-new-join-directory>Spajanje proverava heševe izvora i ograničenja, pokrivenost svih redova popisom, njegov heš u svakom kanalu, identitet faze, pokazivače tvrdnji, vlasništvo izlaza i preduslove oglasa. Piše claim-map.md, review.md i summary.json. Nedostajuće ili neispravne faze prikazuje kao blokirane, dok ispravni susedni nacrti ostaju vidljivi. Globalno nepoklapanje izvora zaustavlja spajanje. Ono ne potvrđuje istinitost rečenice, pravičnost citata, pravni osnov tvrdnje ili vlasnikovo odobrenje objavljivanja. review.md počinje odlukama PENDING.
Imenovani recenzent treba da otvori svaki spreman draft.md, uporedi sve bitne izjave i citate sa navedenim odlomkom i ograničenjima, proveri dvosmislenost, glas kanala, prava prerade, odredište i ponudu oglasa, pa zabeleži prihvati, izmeni ili odbij za svaki kanal izvan generisanih polja na čekanju. Objavljivanje, slanje, zakazivanje i plasiranje oglasa zahtevaju zasebno ljudsko ovlašćenje i provere platforme.
6. Oporavite neuspešan ogranak bez gubitka dobrog rada
Ako odobrenje ili prava nisu jasni, rešite tačan nedostatak sa vlasnikom i počnite novo izvršavanje sa ispravljenim ulazima. Svaka promena heša izvora ili ograničenja zahteva nov potpun popis. Ako jedan kanal otkaže, a drugi su ispravni, najpre pregledajte njegove stvarne datoteke i grešku. Kopirajte neizmenjene bajtove popisa i proverene susedne fascikle u nov direktorijum, izvršite tamo samo neuspešnu fazu i spojite u još jedan nov direktorijum. Kopirani rezultati i dalje moraju odgovarati hešu popisa. Ako je ishod procesa neizvestan, pročitajte rezultat i heševe pre odluke o ponavljanju. Sačuvajte prethodna izvršavanja i izmene recenzenta; nikad ih ne prepisujte ponavljanjem. Ispravite nedostajući ili nepodržani pokazivač tvrdnje u odgovornoj fazi, pa ponovite determinističko spajanje u nov direktorijum.
Ovo je ručno pozvana kompozicija koju vodi model. Nema kodom nametnute raspodele, automatskih ponavljanja, fiksne konkurentnosti ni oporavka spoljnih efekata. Počnite od već odobrenog teksta; postupak ne pribavlja niti proverava transkript i ne pravi video, audio ili vizuelne materijale kampanje.
Potpune referentne datoteke
Čuvajte putanje i sadržaj zajedno. Blokovi koda ispod su potpune kopije nezavisno pregledanog paketa, referentne datoteke, a ne zapisi Claude izvršavanja.
.claude/skills/source-inventory/SKILL.md
---
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
---
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
---
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
---
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
{
"$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
{
"$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
{
"$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
{
"$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
{
"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
"""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
# 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
{
"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
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=pendingIzvori i dodatna literatura
- Priručnik za Claude Code veštine — projektne putanje, frontmatter, pozivanje, zamena argumenata, odvojeni kontekst i pozadinski rad. Provereno 19. oktobra 2026; priručnik ne navodi jednu verziju za ceo postupak.
- Priručnik za Claude Code podagente — agent opšte namene, kontekst i dozvole. Provereno 19. oktobra 2026.
- Claude Code troškovi, korišćenje podataka i dozvole — potrošnja prema nalogu, obrada podataka i kontrola alata. Provereno 19. oktobra 2026; troškovi toka nisu mereni.
- Nezavisno pregledani lokalni paket obezbeđuje četiri veštine, pet JSON ugovora, dva ulazna obrasca, stablo izlaza i skriptu za spajanje. Njegovih 16 provera koristilo je samo privremene strukturne primere; nije nastala generisana proza niti odobrenje činjenica.
- Povezani BIG CHANGE vodič: paralelan pregled dokumenata obrađuje drugačiji Claude tok zasnovan na pregledu dokumenata, a ne lokalnu preradu izvora za tri kanala u Code.



