AI-translated from English; not yet reviewed by a fluent editor.
# Повежите Claude Code вештине за три проверена нацрта из једног одобреног извора
> Помоћу четири одвојене Claude Code вештине попишите одобрени текст, напишите LinkedIn објаву, део билтена и оглас, па проверите хешеве датотека и тврдње пре људског прегледа.
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.
Одобрени чланак или транскрипт може бити основа за више нацрта, али сваки мора јасно водити до извора. Овај водич повезује четири засебне [пројектне вештине Claude Code](https://code.claude.com/docs/en/skills) за попис текста који је власник одобрио, израду LinkedIn објаве, одељка билтена и огласа у засебним директоријумима, па проверу уговора датотека локалним Python спајањем. Резултат су нацрти за именованог људског рецензента, а не дозвола за објављивање или слање.
BIG CHANGE је прегледао следеће вештине, уговоре и код за спајање. Независни рецензент извршио је 16 локалних структурних провера. Ми **нисмо** инсталирали нити покренули вештине у Claude Code, платили извршавање модела, направили стварни нацрт за канал нити тестирали стварну кампању. Следећи кораци су поступак који читалац треба да испроба са својим одобреним извором.
## Велика промена
- **Шта се променило:** Четири сопствене вештине имају изричите уговоре за извор, попис и резултате канала, а затим следи детерминистичко спајање.
- **Зашто је важно:** Сваки нацрт може да се повеже са пасусима извора, док недостајући канал остаје видљив као блокирано место.
- **На шта пазити:** Провере датотека не потврђују истинитост, права прераде или уредничко одобрење; човек мора да прегледа сваки спреман нацрт.
## 1. Потврдите приступ, права и извор
Користите Claude Code репозиторијум који контролишете и налог овлашћен да обрађује извор. Пре поверљивог материјала проверите доступне моделе, [потрошњу и трошкове](https://code.claude.com/docs/en/costs), [политику података](https://code.claude.com/docs/en/data-usage) и [дозволе алата](https://code.claude.com/docs/en/permissions). Claude Code команда `/usage` може приказати потрошњу текуће сесије; наплата зависи од налога и пакета. Трошкови ни уштеде овог тока нису мерени. Извор мора бити стварни одобрени UTF-8 чланак или проверен транскрипт, уз забележене власника, верзију и одобрење. Најпре са власником разрешите тачност транскрипта, права за сваки канал, приватност и сваку понуду. Видео URL или празан образац испод не испуњавају овај услов.
Четири референтне вештине користе документовану Claude Code локацију `.claude/skills/<name>/SKILL.md`, позивање командом са косом цртом и замену `$ARGUMENTS`. Подешавање `context: fork` сваком позиву даје сопствени контекст без историје разговора. Вештине за нацрте користе `background: true`, за шта је потребан Claude Code **v2.1.218 или новији**. Користе агента опште намене; могућност писања датотека и стварне дозволе следе корисниково подешавање Claude Code. Текст вештине не даје дозволе нити може наметнути границу само за читање. Позадински рад може да се преклапа ако домаћин дозволи, али паралелно извршавање није гарантовано. Пре избора назива проверите постојеће истоимене вештине и откривање команди, па потврдите да команда води до инсталиране пројектне датотеке. Погледајте [приручник за вештине](https://code.claude.com/docs/en/skills) и [приручник за подагенте](https://code.claude.com/docs/en/sub-agents).
## 2. Инсталирајте четири датотеке и уговоре
У пројекту направите тачне путање из списка. Копирајте сваки цео блок референтних датотека без промене садржаја. Пет датотека у `.claude/repurpose-contracts/` дефинишу облике улаза, пописа, блокираног стања и резултата канала, као и план зависности. Ставите `join.py` у корен пројекта. Два обрасца иду у `repurpose-inputs/`; пре покретања замените њихове привремене вредности стварним одобреним садржајем. Стабло излаза је само структурни водич. Испод су целе датотеке за копирање.
| Датотека | Намена |
| --- | --- |
| `.claude/skills/source-inventory/SKILL.md` | Проверава одобрења и пописује сваки ред извора пре писања. |
| `.claude/skills/draft-linkedin/SKILL.md` | Одговара само за LinkedIn нацрт и резултат. |
| `.claude/skills/draft-newsletter/SKILL.md` | Одговара само за нацрт и резултат билтена. |
| `.claude/skills/draft-ad/SKILL.md` | Одговара само за нацрт и резултат огласа или изричит разлог блокиране понуде. |
| `.claude/repurpose-contracts/*.json` | Дефинише тачна поља, везе хешева и редослед фаза. |
| `join.py` | Проверава структуру и порекло, па прави мапу тврдњи и предају за преглед. |
| `repurpose-inputs/approved-source.template.md`, `repurpose-inputs/constraints.template.json` | Празни почетни обрасци, никад доказ извршавања. |
Ограничења захтевају изричита права прераде за LinkedIn, билтен и оглас, чак и када је оглас блокиран јер нема одобрене понуде. `forbiddenClaims` може бити празна листа ако власник нема додатних забрана. Оглас захтева одобрену понуду, HTTP(S) одредиште и позив на акцију; одсуство било чега блокира тај огранак. Забележите стварног именованог рецензента. Не мењајте одобрени извор ни ограничења током извршавања.
## 3. Тестирајте сваку везу, па покрените попис
Најпре тестирајте сваку вештину засебно на ограниченом стварном одобреном извору. Упоредите стварне датотеке и хешеве са уговорима. BIG CHANGE није извршио тај Claude Code тест. За цео ток направите нове апсолутне директоријуме за улаз, резултате и спајање. У репозиторијуму позовите команду за попис са три апсолутне путање:
```prompt
/source-inventory <absolute-path-to-approved-source.md> <absolute-path-to-constraints.json> <absolute-path-to-new-run-directory>
```
Сваки аргумент у угластим заградама облика <…> замените стварном путањом. Вештина треба да напише `inventory.json` са дословним, уређеним опсезима одломака који покривају сваки ред, или `inventory-blocked.json` са тачним недостајућим одобрењем или правом. Не почињите нацрт ако је попис блокиран. Упоредите забележене SHA-256 вредности са обе улазне датотеке и прегледајте границе одломака, укључујући празне редове.
## 4. Пишите у три изолована канала
Када `inventory.json` постоји, а оба улаза и даље имају забележене хешеве, позовите три засебне вештине. За први аргумент користите исти апсолутни директоријум извршавања, а за други три различита излазна директоријума. Свака вештина чита одобрени извор, ограничења и попис, никад суседни нацрт. Могу се независно покренути после пописа, у границама домаћина и Claude Code дозвола.
```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
```
Сваки канал одговара за `result.json` и, само када је спреман, `draft.md`. Резултат идентификује канал и SHA-256 извора, ограничења и пописа, па везује битне тврдње или цитате за ID одломака и одговарајуће кључеве ограничења. `ready` значи спремно за проверу чињеница. Недостајућа одобрена понуда, одредиште или позив на акцију морају дати блокиран резултат без нацрта огласа. Позадински позив или исправан JSON сами не доказују користан или тачан нацрт.
## 5. Спојите резултате и донесите људску одлуку
Када се све три фазе заврше или дефинитивно откажу, покрените приложену Python датотеку са директоријумом извршавања и **новим** директоријумом за спајање. Користи се Python стандардна библиотека; пре позива проверите локалну инсталацију.
```bash
python join.py <absolute-run-directory> <absolute-new-join-directory>
```
Спајање проверава хешеве извора и ограничења, покривеност свих редова пописом, његов хеш у сваком каналу, идентитет фазе, показиваче тврдњи, власништво излаза и предуслове огласа. Пише `claim-map.md`, `review.md` и `summary.json`. Недостајуће или неисправне фазе приказује као блокиране, док исправни суседни нацрти остају видљиви. Глобално непоклапање извора зауставља спајање. Оно не потврђује истинитост реченице, правичност цитата, правни основ тврдње или власниково одобрење објављивања. `review.md` почиње одлукама `PENDING`.
Именовани рецензент треба да отвори сваки спреман `draft.md`, упореди све битне изјаве и цитате са наведеним одломком и ограничењима, провери двосмисленост, глас канала, права прераде, одредиште и понуду огласа, па забележи **прихвати, измени или одбиј** за сваки канал изван генерисаних поља на чекању. Објављивање, слање, заказивање и пласирање огласа захтевају засебно људско овлашћење и провере платформе.
## 6. Опоравите неуспешан огранак без губитка доброг рада
Ако одобрење или права нису јасни, решите тачан недостатак са власником и почните ново извршавање са исправљеним улазима. Свака промена хеша извора или ограничења захтева нов потпун попис. Ако један канал откаже, а други су исправни, најпре прегледајте његове стварне датотеке и грешку. Копирајте неизмењене бајтове пописа и проверене суседне фасцикле у нов директоријум, извршите тамо само неуспешну фазу и спојите у још један нов директоријум. Копирани резултати и даље морају одговарати хешу пописа. Ако је исход процеса неизвестан, прочитајте резултат и хешеве пре одлуке о понављању. Сачувајте претходна извршавања и измене рецензента; никад их не преписујте понављањем. Исправите недостајући или неподржани показивач тврдње у одговорној фази, па поновите детерминистичко спајање у нов директоријум.
Ово је ручно позвана композиција коју води модел. Нема кодом наметнуте расподеле, аутоматских понављања, фиксне конкурентности ни опоравка спољних ефеката. Почните од већ одобреног текста; поступак не прибавља нити проверава транскрипт и не прави видео, аудио или визуелне материјале кампање.
## Потпуне референтне датотеке
Чувајте путање и садржај заједно. Блокови кода испод су потпуне копије независно прегледаног пакета, референтне датотеке, а не записи Claude извршавања.
### `.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
```
## Извори и додатна литература
- [Приручник за Claude Code вештине](https://code.claude.com/docs/en/skills) — пројектне путање, frontmatter, позивање, замена аргумената, одвојени контекст и позадински рад. Проверено 19. октобра 2026; приручник не наводи једну верзију за цео поступак.
- [Приручник за Claude Code подагенте](https://code.claude.com/docs/en/sub-agents) — агент опште намене, контекст и дозволе. Проверено 19. октобра 2026.
- [Claude Code трошкови](https://code.claude.com/docs/en/costs), [коришћење података](https://code.claude.com/docs/en/data-usage) и [дозволе](https://code.claude.com/docs/en/permissions) — потрошња према налогу, обрада података и контрола алата. Проверено 19. октобра 2026; трошкови тока нису мерени.
- Независно прегледани локални пакет обезбеђује четири вештине, пет JSON уговора, два улазна обрасца, стабло излаза и скрипту за спајање. Његових 16 провера користило је само привремене структурне примере; није настала генерисана проза нити одобрење чињеница.
- [Повезани BIG CHANGE водич: паралелан преглед докумената](https://bigchange.ai/blog/claude-managed-agents-parallel-document-review-guide) обрађује другачији Claude ток заснован на прегледу докумената, а не локалну прераду извора за три канала у Code.
## Sources
- [Приручник за Claude Code вештине](https://code.claude.com/docs/en/skills) — Пројектна путања, позивање, одвојени контекст и позадински рад; HTTP 200 и хеш проверени 2026-10-19.
- [Приручник за Claude Code подагенте](https://code.claude.com/docs/en/sub-agents) — Агент опште намене и дозволе; HTTP 200 и хеш проверени 2026-10-19.
- [Claude Code трошкови](https://code.claude.com/docs/en/costs) — Потрошња према налогу и приказ потрошње сесије; проверено 2026-10-19.
- [Claude Code коришћење података](https://code.claude.com/docs/en/data-usage) — Политика података налога и организације; проверено 2026-10-19.
- [Claude Code дозволе](https://code.claude.com/docs/en/permissions) — Стварни приступ алатима подешава се изван текста вештине; проверено 2026-10-19.
Билтен BIG CHANGE
Шира слика. Вашим темпом.
Недавне приче о вештачкој интелигенцији и роботици, промене вредне праћења и практичне идеје за примену. Изаберите дневни преглед, недељни сажетак или месечну перспективу.
Next scheduled send (UTC): . Your first edition arrives at the next scheduled send after you confirm.
Ваша приватност, ваш избор.
Неопходно складиште доприноси безбедности сајта и памти ваше изборе. Опционални Google Analytics остаје искључен док га не дозволите. Све чланке можете читати само уз неопходно складиштење података. Детаљи о приватности