Одобренная статья или расшифровка может стать основой нескольких черновиков, но каждый должен явно вести к первоисточнику. В руководстве объединяются четыре отдельных проектных навыка Claude Code: они составляют опись текста, одобренного владельцем, пишут публикацию LinkedIn, раздел рассылки и рекламу в отдельных каталогах, затем локальная сборка Python проверяет файловые контракты. Результат — черновики для назначенного человека-рецензента, а не разрешение публиковать или отправлять их.
BIG CHANGE проверила навыки, контракты и код сборки ниже. Независимый рецензент выполнил 16 локальных структурных проверок. Мы не устанавливали и не запускали эти навыки в Claude Code, не оплачивали запуск модели, не создавали фактический текст для канала и не испытывали реальную кампанию. Далее описан порядок действий, который читатель должен проверить на собственном одобренном источнике.
Главное изменение
- Что изменилось: У четырёх собственных навыков теперь явные контракты источника, описи и результатов каналов, за которыми следует детерминированная сборка.
- Почему это важно: Каждый черновик можно связать с фрагментами источника, а отсутствующий канал остаётся видимым как заблокированное место.
- На что обратить внимание: Проверки файлов не доказывают истинность, право на адаптацию или редакционное одобрение. Каждый готовый черновик должен проверить человек.
1. Подтвердите доступ, права и источник
Используйте подконтрольный вам репозиторий Claude Code и учётную запись, которой разрешена обработка источника. Перед конфиденциальными материалами проверьте доступные модели, использование и стоимость, политику данных и разрешения инструментов. Команда Claude Code /usage может показать расход текущего сеанса; оплата зависит от учётной записи и тарифа. Стоимость и экономия этого процесса не измерялись. Источником должна быть настоящая одобренная статья UTF-8 или проверенная расшифровка с указанными владельцем, версией и одобрением. Сначала согласуйте с владельцем точность расшифровки, права для каждого канала, конфиденциальность и любое предложение. Ссылка на видео или пустой шаблон ниже не отвечают этому требованию.
Четыре эталонных навыка используют документированное расположение Claude Code .claude/skills/<name>/SKILL.md, вызов slash-командой и подстановку $ARGUMENTS. Настройка context: fork даёт каждому вызову отдельный контекст без истории разговора. Навыки черновиков используют background: true, для чего нужен Claude Code v2.1.218 или новее. Используется универсальный агент; возможность записи файлов и фактические права зависят от пользовательских настроек Claude Code. Текст навыка не выдаёт разрешений и не может принудительно ограничить работу только чтением. Фоновая работа может перекрываться, если среда разрешает, но параллельность не гарантируется. Перед выбором имён проверьте существующие одноимённые навыки и обнаружение команд, затем убедитесь, что команда указывает на установленный файл проекта. См. руководство по навыкам и руководство по подагентам.
2. Установите четыре файла и контракты
Создайте в проекте точные пути из списка ниже. Целиком скопируйте каждый блок эталонных файлов без изменения содержимого. Пять файлов в .claude/repurpose-contracts/ задают формы входных данных, описи, блокировки и результата канала, а также план зависимостей. Поместите join.py в корень проекта. Два шаблона должны находиться в repurpose-inputs/; до любого запуска замените заглушки настоящими одобренными данными. Дерево вывода показывает только структуру. Ниже приведены полные файлы для копирования.
Файл | Назначение |
|---|---|
| Проверяет одобрения и описывает каждую строку источника перед написанием. |
| Отвечает только за черновик и результат LinkedIn. |
| Отвечает только за черновик и результат рассылки. |
| Отвечает только за рекламный черновик и результат либо явную блокировку предложения. |
| Определяет точные поля, связи хешей и порядок этапов. |
| Проверяет структуру и происхождение, создаёт карту утверждений и передачу на рецензирование. |
| Пустые начальные формы, никогда не доказательства выполнения. |
Ограничения требуют явных прав на адаптацию для LinkedIn, рассылки и рекламы, даже если реклама будет заблокирована без одобренного предложения. forbiddenClaims может быть пустым списком, если у владельца нет иных запретов. Рекламе нужны одобренное предложение, адрес HTTP(S) и призыв к действию; отсутствие любого значения блокирует ветку. Укажите настоящего рецензента по имени. Не меняйте одобренный источник и ограничения во время запуска.
3. Проверьте каждую зависимость, затем запустите опись
Сначала отдельно проверьте каждый навык на небольшом настоящем одобренном источнике. Сопоставьте реальные файлы и хеши с контрактами. BIG CHANGE не проводила эту проверку в Claude Code. Для полного запуска создайте новые абсолютные каталоги входов, результатов и сборки. В репозитории вызовите команду описи с тремя абсолютными путями:
/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.
/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; сначала проверьте локальную установку.
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
---
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=pendingИсточники и дополнительные материалы
- Руководство по навыкам Claude Code — проектные пути, frontmatter, вызовы, подстановка аргументов, отдельный контекст и фоновое выполнение. Проверено 19 октября 2026 года; единая версия для всего рецепта не указана.
- Руководство по подагентам Claude Code — универсальный агент, контекст и разрешения. Проверено 19 октября 2026 года.
- Стоимость Claude Code, использование данных и разрешения — расход по учётной записи, обработка данных и управление инструментами. Проверено 19 октября 2026 года; расходы процесса не измерялись.
- Независимо проверенный локальный пакет содержит четыре навыка, пять контрактов JSON, два входных шаблона, дерево вывода и скрипт сборки. 16 проверок использовали только временные структурные тестовые данные; сгенерированного текста и одобрения фактов не было.
- Связанное руководство BIG CHANGE: параллельная проверка документов описывает другой процесс Claude для проверки документов, а не локальной адаптации источника для трёх каналов в Code.



