@gobing-ai/knowledge-kit 0.0.18 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +62 -7
- package/package.json +1 -1
- package/plugins/generations/dailynews-gen/dist/index.js +9 -1
- package/plugins/generations/dailynews-gen/src/script-builder.ts +19 -1
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +13 -5
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/qc.py +23 -15
- package/plugins/generations/voice-gen/dist/index.js +60 -3
- package/plugins/generations/voice-gen/src/index.ts +8 -0
- package/plugins/generations/voice-gen/src/voicebox-server.ts +91 -0
- package/plugins/ingestions/aihot-ingest/dist/index.js +1 -1
- package/plugins/ingestions/aihot-ingest/plugin.json +1 -1
- package/plugins/ingestions/aihot-ingest/src/rss.ts +12 -2
- package/plugins/kk/commands/workflow-run.md +30 -12
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/itc-stages.ts +563 -0
- package/plugins/kk/scripts/kk-workflow-stages.ts +150 -23
- package/plugins/kk/skills/article-adapt/SKILL.md +85 -0
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +67 -145
- package/plugins/kk/workflows/kk-itc.yaml +461 -12
|
@@ -12,22 +12,32 @@
|
|
|
12
12
|
* wrap [<override>] --in <voicescript.yaml> --out <docs.json> [--profile <name>]
|
|
13
13
|
* duration <work_dir> <target_duration_min>
|
|
14
14
|
* render-storm [<override>] --in <content.json> --out <content.md>
|
|
15
|
+
* itc-outline-pick <work_dir> (0149, body in the sibling itc-stages.ts)
|
|
16
|
+
* itc-report <work_dir> [max_revisions] (0149, body in the sibling itc-stages.ts)
|
|
17
|
+
* itc-materialize <work_dir> <publish_live> (0150, sibling)
|
|
18
|
+
* itc-publish-plan <work_dir> <targets> <source_locale> <publish_live> (0150, sibling)
|
|
19
|
+
* itc-publish-fanout <work_dir> [--retry] (0151, sibling)
|
|
20
|
+
* itc-publish-next <work_dir> (0151, sibling)
|
|
15
21
|
*
|
|
16
22
|
* `validate` / `wrap` / `render-storm` resolve the shipped sibling sidecars and delegate to them;
|
|
17
23
|
* nothing about those sidecars changed. The `prepare-*` verbs and `duration` keep the logic that
|
|
18
24
|
* used to live inline in `plugins/kk/workflows/*.yaml`, message-for-message.
|
|
19
25
|
*
|
|
20
|
-
* Node/Bun builtins only — no @gobing-ai/* and no relative imports (the file is
|
|
21
|
-
* installed package or from a checkout).
|
|
26
|
+
* Node/Bun builtins only — no @gobing-ai/* and no cross-directory relative imports (the file is
|
|
27
|
+
* resolved from the installed package or from a checkout). The itc-* stages delegate to their
|
|
28
|
+
* same-directory sibling `itc-stages.ts`, imported lazily so a runner copy without the sibling
|
|
29
|
+
* still loads and fails loud with the remediation path.
|
|
22
30
|
*
|
|
23
31
|
* The YAML prelude resolves this file as `$pkg/plugins/kk/scripts/kk-workflow-stages.ts` where `pkg`
|
|
24
32
|
* comes from `realpath "$(command -v kk)"` (the installed package root), and falls back to the
|
|
25
33
|
* cwd-relative `plugins/kk/scripts/kk-workflow-stages.ts` when that copy is absent — the checkout
|
|
26
|
-
* tier kk-storm-research's render has always relied on, preserved on purpose.
|
|
34
|
+
* tier kk-storm-research's render has always relied on, preserved on purpose. The daily-voice
|
|
35
|
+
* workflow reaches the same script through `kk stage` (apps/cli/src/stage.ts), which applies the
|
|
36
|
+
* same two resolution tiers in TypeScript.
|
|
27
37
|
*
|
|
28
|
-
* SYNC: plugins/kk/workflows/{kk-solo-podcast,kk-itc,kk-storm-research
|
|
29
|
-
*
|
|
30
|
-
* four workflow static tests match them.
|
|
38
|
+
* SYNC: plugins/kk/workflows/{kk-solo-podcast,kk-itc,kk-storm-research}.yaml shell this script
|
|
39
|
+
* (kk-daily-ai-voice goes through `kk stage`). Keep message substrings stable —
|
|
40
|
+
* apps/cli/tests/kk-workflow-stages.test.ts and the four workflow static tests match them.
|
|
31
41
|
*/
|
|
32
42
|
import { spawnSync } from 'node:child_process';
|
|
33
43
|
import { createHash } from 'node:crypto';
|
|
@@ -43,6 +53,7 @@ import {
|
|
|
43
53
|
unlinkSync,
|
|
44
54
|
writeFileSync,
|
|
45
55
|
} from 'node:fs';
|
|
56
|
+
import { homedir } from 'node:os';
|
|
46
57
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
47
58
|
|
|
48
59
|
const SCRIPTS_DIR = import.meta.dir;
|
|
@@ -60,6 +71,21 @@ function fail(message: string): never {
|
|
|
60
71
|
process.exit(1);
|
|
61
72
|
}
|
|
62
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Load the itc-* helper sibling lazily: this runner is resolved (and copied alone, e.g. by
|
|
76
|
+
* apps/cli/tests/kk-workflow-stages.test.ts) from installs where the sibling may be absent, so
|
|
77
|
+
* the itc-* stages fail loud with a remediation path instead of breaking every stage at load.
|
|
78
|
+
*/
|
|
79
|
+
async function loadItcStages(): Promise<typeof import('./itc-stages.ts')> {
|
|
80
|
+
try {
|
|
81
|
+
return await import('./itc-stages.ts');
|
|
82
|
+
} catch {
|
|
83
|
+
return fail(
|
|
84
|
+
'itc-stages.ts not found next to kk-workflow-stages.ts — install or refresh the kk package (bun link in a checkout)',
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
63
89
|
/** coreutils-style reason for the errno codes the staged file operations can hit. */
|
|
64
90
|
const ERRNO_REASONS: Record<string, string> = {
|
|
65
91
|
EACCES: 'Permission denied',
|
|
@@ -534,15 +560,29 @@ function dailyPrepare(args: string[]): void {
|
|
|
534
560
|
last30daysEnabled = '',
|
|
535
561
|
topic = '',
|
|
536
562
|
horizonHours = '',
|
|
563
|
+
runDateArg = '',
|
|
537
564
|
] = args;
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
565
|
+
// Empty work_dir composes the real workspace from the operator's works_dir
|
|
566
|
+
// (workflow-run.md §2 precedence: KK_WORKS_DIR > config works_dir > ~/.config/kk/works):
|
|
567
|
+
// $works_dir/kk-daily-ai-voice/<stamp>
|
|
568
|
+
// The stamp is digits-only (dashes stripped from the caller's run_date, else today) so a
|
|
569
|
+
// resumed/retried run whose persisted run_date var already carries the digits-only
|
|
570
|
+
// run-date.txt value re-derives the same directory.
|
|
571
|
+
const dirDate = runDateArg ? runDateArg.replaceAll('-', '') : formatRunDate();
|
|
572
|
+
const workDir = resolve(workDirArg || join(resolveWorksDir(), 'kk-daily-ai-voice', dirDate));
|
|
573
|
+
timed(workDir, 'prepare', () => {
|
|
541
574
|
for (const dir of DAILY_DIRS)
|
|
542
575
|
sted('mkdir', join(workDir, dir), () => mkdirSync(join(workDir, dir), { recursive: true }));
|
|
543
576
|
sted('write', join(workDir, 'run-date.txt'), () =>
|
|
544
|
-
writeFileSync(join(workDir, 'run-date.txt'), `${
|
|
577
|
+
writeFileSync(join(workDir, 'run-date.txt'), `${dirDate}\n`),
|
|
545
578
|
);
|
|
579
|
+
// Anchor for the workflow: shell actions cannot set vars, so the YAML reads the
|
|
580
|
+
// effective work_dir back into vars.work_dir from this cwd-relative file.
|
|
581
|
+
const anchorDir = join('.spur', 'runs', 'kk-daily-ai-voice');
|
|
582
|
+
sted('write', join(anchorDir, 'work-dir.txt'), () => {
|
|
583
|
+
mkdirSync(anchorDir, { recursive: true });
|
|
584
|
+
writeFileSync(join(anchorDir, 'work-dir.txt'), `${workDir}\n`);
|
|
585
|
+
});
|
|
546
586
|
sted('write', join(workDir, '1-ingest/source.json'), () =>
|
|
547
587
|
writeJsonText(join(workDir, '1-ingest/source.json'), {
|
|
548
588
|
limit: Number(limit),
|
|
@@ -572,6 +612,35 @@ function formatRunDate(): string {
|
|
|
572
612
|
return `${now.getFullYear()}${month}${day}`;
|
|
573
613
|
}
|
|
574
614
|
|
|
615
|
+
/** Expand the `~/` and `$HOME/` prefixes the config examples use. */
|
|
616
|
+
function expandHome(p: string): string {
|
|
617
|
+
if (p.startsWith('~/')) return join(homedir(), p.slice(2));
|
|
618
|
+
if (p.startsWith('$HOME/')) return join(homedir(), p.slice('$HOME/'.length));
|
|
619
|
+
return p;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* works_dir for composed daily workspaces — workflow-run.md §2 precedence:
|
|
624
|
+
* KK_WORKS_DIR env > config works_dir > compiled default ~/.config/kk/works.
|
|
625
|
+
* Unreadable/unparseable config falls through to the default (pluginEnvFromConfig
|
|
626
|
+
* tolerates the same), never fails the run.
|
|
627
|
+
*/
|
|
628
|
+
function resolveWorksDir(): string {
|
|
629
|
+
const fromEnv = process.env.KK_WORKS_DIR;
|
|
630
|
+
if (fromEnv) return fromEnv;
|
|
631
|
+
const configPath = process.env.KK_CONFIG || join(homedir(), '.config/kk/config.yaml');
|
|
632
|
+
if (existsSync(configPath)) {
|
|
633
|
+
try {
|
|
634
|
+
const parsed = Bun.YAML.parse(readFileSync(configPath, 'utf-8')) as Record<string, unknown> | null;
|
|
635
|
+
const worksDir = parsed !== null && typeof parsed === 'object' ? parsed.works_dir : undefined;
|
|
636
|
+
if (typeof worksDir === 'string' && worksDir !== '') return expandHome(worksDir);
|
|
637
|
+
} catch {
|
|
638
|
+
// fall through to the compiled default
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return join(homedir(), '.config/kk/works');
|
|
642
|
+
}
|
|
643
|
+
|
|
575
644
|
function dailyCollectFacts(args: string[]): void {
|
|
576
645
|
const [workDir = '', runDate = '', pluginsPath = ''] = args;
|
|
577
646
|
const content = join(workDir, '2-facts', `${runDate}_02_collect_content.json`);
|
|
@@ -780,7 +849,9 @@ function dailyArticle(args: string[]): void {
|
|
|
780
849
|
{
|
|
781
850
|
env: {
|
|
782
851
|
ARTICLE_DATE: runDate,
|
|
783
|
-
|
|
852
|
+
// Plugin entries run with cwd = plugin dir (invoke.ts), so relative
|
|
853
|
+
// paths in env must be resolved or the plugin sees ENOENT.
|
|
854
|
+
...(isFile(proseBody) ? { ARTICLE_BODY_FILE: resolve(proseBody) } : {}),
|
|
784
855
|
},
|
|
785
856
|
},
|
|
786
857
|
);
|
|
@@ -856,7 +927,14 @@ function dailyScript(args: string[]): void {
|
|
|
856
927
|
sted('write', yamlOut, () => writeRaw(yamlOut, body));
|
|
857
928
|
// R3 (task 0139): fresh writes are validated here; the script cache path is covered by
|
|
858
929
|
// wrap-docs, which runs on every path that reaches audio.
|
|
859
|
-
|
|
930
|
+
try {
|
|
931
|
+
assertScriptContent(yamlOut);
|
|
932
|
+
} catch (err) {
|
|
933
|
+
// A rejected fresh write would otherwise poison the isFile(yamlOut) cache: every
|
|
934
|
+
// resume skips regeneration and re-fails at wrap-docs. Drop it so retry regenerates.
|
|
935
|
+
unlinkSync(yamlOut);
|
|
936
|
+
throw err;
|
|
937
|
+
}
|
|
860
938
|
});
|
|
861
939
|
}
|
|
862
940
|
|
|
@@ -1270,9 +1348,12 @@ function dailyPatchSurfdash(args: string[]): void {
|
|
|
1270
1348
|
};
|
|
1271
1349
|
let changed = 0;
|
|
1272
1350
|
for (const [locale, dir] of [
|
|
1351
|
+
// zh posts live under articles/zh/<slug>; en/ja under articles/<locale>/<slug> —
|
|
1352
|
+
// two dirnames up, not one (20260919: `articles/zh/en/...` never existed, so en/ja
|
|
1353
|
+
// were silently skipped by the not-published branch).
|
|
1273
1354
|
['zh', slugDir],
|
|
1274
|
-
['en', join(dirname(slugDir), 'en', basename(slugDir))],
|
|
1275
|
-
['ja', join(dirname(slugDir), 'ja', basename(slugDir))],
|
|
1355
|
+
['en', join(dirname(dirname(slugDir)), 'en', basename(slugDir))],
|
|
1356
|
+
['ja', join(dirname(dirname(slugDir)), 'ja', basename(slugDir))],
|
|
1276
1357
|
] as const) {
|
|
1277
1358
|
const indexMd = join(dir, 'index.md');
|
|
1278
1359
|
if (!existsSync(indexMd)) {
|
|
@@ -1282,17 +1363,28 @@ function dailyPatchSurfdash(args: string[]): void {
|
|
|
1282
1363
|
let md = readFileSync(indexMd, 'utf-8');
|
|
1283
1364
|
const fmEnd = md.indexOf('---', 3); // frontmatter is `---\n ... \n---\n`
|
|
1284
1365
|
if (fmEnd < 0) failWith(1, `patch-surfdash: no frontmatter in ${indexMd}`);
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1366
|
+
// Upsert a single-line `key: 'value'`; the regex consumes folded-scalar continuation
|
|
1367
|
+
// lines (`og_image: >-\n https://...`) so multi-line publish output is replaced whole.
|
|
1368
|
+
const upsertKey = (src: string, key: string, value: string): string => {
|
|
1369
|
+
const line = `${key}: '${value}'`;
|
|
1370
|
+
const re = new RegExp(`^${key}:.*(?:\\n[ \\t]+.*)*`, 'm');
|
|
1371
|
+
if (re.test(src)) return src.replace(re, line);
|
|
1372
|
+
return src.replace(/^publishDate: .*$/m, (m0) => `${m0}\n${line}`);
|
|
1373
|
+
};
|
|
1374
|
+
if (coverUrl !== '') {
|
|
1375
|
+
// 20260919 prod feedback: the podcast-hosted cover URL wins even when surfdash-pub
|
|
1376
|
+
// already wrote `image:` — its resolved og_image points at /content-posts/<slug>/
|
|
1377
|
+
// assets/cover.png, which the deployed worker does not serve (404).
|
|
1378
|
+
md = upsertKey(md, 'image', coverUrl);
|
|
1379
|
+
md = upsertKey(md, 'og_image', coverUrl);
|
|
1380
|
+
} else if (!/^image:/m.test(md.slice(0, fmEnd))) {
|
|
1381
|
+
// Fallback for runs without a podcast cover: copy the local cover instead.
|
|
1382
|
+
if (existsSync(localCover)) {
|
|
1289
1383
|
const assets = join(dir, 'assets');
|
|
1290
1384
|
mkdirSync(assets, { recursive: true });
|
|
1291
1385
|
copyFileSync(localCover, join(assets, 'cover.png'));
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
if (image !== '') {
|
|
1295
|
-
md = md.replace(/^publishDate: .*$/m, (m0) => `${m0}\nimage: '${image}'\nog_image: '${image}'`);
|
|
1386
|
+
md = upsertKey(md, 'image', './assets/cover.png');
|
|
1387
|
+
md = upsertKey(md, 'og_image', './assets/cover.png');
|
|
1296
1388
|
}
|
|
1297
1389
|
}
|
|
1298
1390
|
if (showNotesUrl !== '' && !md.includes(showNotesUrl)) {
|
|
@@ -1578,8 +1670,43 @@ if (import.meta.main)
|
|
|
1578
1670
|
case 'daily-translate-sync':
|
|
1579
1671
|
dailyTranslateSync(rest);
|
|
1580
1672
|
break;
|
|
1673
|
+
case 'itc-outline-pick': {
|
|
1674
|
+
const { itcOutlinePick } = await loadItcStages();
|
|
1675
|
+
itcOutlinePick(rest);
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
case 'itc-report': {
|
|
1679
|
+
const { itcReport } = await loadItcStages();
|
|
1680
|
+
itcReport(rest);
|
|
1681
|
+
break;
|
|
1682
|
+
}
|
|
1683
|
+
case 'itc-materialize': {
|
|
1684
|
+
const { itcMaterialize } = await loadItcStages();
|
|
1685
|
+
itcMaterialize(rest);
|
|
1686
|
+
break;
|
|
1687
|
+
}
|
|
1688
|
+
case 'itc-publish-plan': {
|
|
1689
|
+
const { itcPublishPlan } = await loadItcStages();
|
|
1690
|
+
itcPublishPlan(rest);
|
|
1691
|
+
break;
|
|
1692
|
+
}
|
|
1693
|
+
case 'itc-publish-fanout': {
|
|
1694
|
+
const { itcPublishFanout } = await loadItcStages();
|
|
1695
|
+
itcPublishFanout(rest);
|
|
1696
|
+
break;
|
|
1697
|
+
}
|
|
1698
|
+
case 'itc-publish-next': {
|
|
1699
|
+
const { itcPublishNext } = await loadItcStages();
|
|
1700
|
+
itcPublishNext(rest);
|
|
1701
|
+
break;
|
|
1702
|
+
}
|
|
1703
|
+
case 'itc-adapt-verify': {
|
|
1704
|
+
const { itcAdaptVerify } = await loadItcStages();
|
|
1705
|
+
itcAdaptVerify(rest);
|
|
1706
|
+
break;
|
|
1707
|
+
}
|
|
1581
1708
|
default:
|
|
1582
1709
|
fail(
|
|
1583
|
-
`unknown stage: ${stage ?? '(none)'} (expected prepare-itc|prepare-solo|prepare-storm|validate|wrap|duration|render-storm|daily-*)`,
|
|
1710
|
+
`unknown stage: ${stage ?? '(none)'} (expected prepare-itc|prepare-solo|prepare-storm|validate|wrap|duration|render-storm|itc-*|daily-*)`,
|
|
1584
1711
|
);
|
|
1585
1712
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: article-adapt
|
|
3
|
+
# Description rules: front-load the leading identity phrase; one trigger per genuine
|
|
4
|
+
# branch (collapse synonym triggers into one); never restate the body's identity line.
|
|
5
|
+
description: This skill should be used when a kk-itc publish plan contains variants with adapt=true and per-variant Content files must be written — "adapt the article", "translate for the target profile", "write content.zh-article.json", or when a spur workflow agent.run step invokes kk:article-adapt. Translates title and body to the variant locale, applies the plan limits, and preserves code blocks, URLs and references; it holds no platform table.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# article-adapt
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
This skill adapts one published article into one Content file per `publish-plan.json` variant with
|
|
12
|
+
`adapt: true`. It is deliberately **thin**: the plan (built from `TARGET_PROFILES` in
|
|
13
|
+
`plugins/kk/scripts/itc-stages.ts`) carries the per-variant locale, format and limits — this skill
|
|
14
|
+
holds **no platform table** and never decides which variants exist.
|
|
15
|
+
|
|
16
|
+
## Inputs
|
|
17
|
+
|
|
18
|
+
- `<work_dir>/4-publish/publish-plan.json` — `{live, sourceLocale, variants, fanout, browser}` where
|
|
19
|
+
each `variants[<locale>-<format>]` entry is
|
|
20
|
+
`{file, adapt, limits?, switches}`.
|
|
21
|
+
- `<work_dir>/4-publish/content.json` — the source article (the `sourceLocale` materialized draft).
|
|
22
|
+
|
|
23
|
+
## Output
|
|
24
|
+
|
|
25
|
+
For every `variants[*]` entry with `adapt: true`, write
|
|
26
|
+
`<work_dir>/4-publish/<entry.file>` (e.g. `content.zh-article.json`) as a Content JSON object:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"title": "<translated title>",
|
|
31
|
+
"format": "<variant format suffix, e.g. article | wechat-article | short-note | short-post>",
|
|
32
|
+
"body": "<translated body>",
|
|
33
|
+
"metadata": { "coverImage": "...", "tags": [], "description": "..." },
|
|
34
|
+
"references": [{ "url": "...", "title": "...", "cite": "id" }]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Procedure
|
|
39
|
+
|
|
40
|
+
1. Read the plan. Collect the variants whose `adapt` is `true`. If there are none, report that and
|
|
41
|
+
stop — do not write any file.
|
|
42
|
+
2. For each such variant, read `content.json` and produce the variant file:
|
|
43
|
+
- **Translate** `title` and `body` into the variant locale (the locale is the part of the variant
|
|
44
|
+
key before the first `-`). Keep every code block, URL and inline link verbatim — never
|
|
45
|
+
translate code, identifiers, commands or URLs.
|
|
46
|
+
- **Carry over** `references[]`, `metadata.coverImage`, `metadata.tags`, `metadata.keywords` and
|
|
47
|
+
`metadata.description` unchanged.
|
|
48
|
+
- **Apply the limits** from the entry's `limits` (e.g. a max character count): trim the body
|
|
49
|
+
within the limit without dropping code blocks or breaking markdown; state in one closing line
|
|
50
|
+
that the full article is available at the source URL when content had to be cut.
|
|
51
|
+
- **Set `format`** to the variant key's format suffix (the part after the first `-`).
|
|
52
|
+
3. Write the file with `metadata` present (at minimum `{}`). A valid Content object always has a
|
|
53
|
+
non-empty `title` and `body`.
|
|
54
|
+
4. Report each written path as the last line of stdout.
|
|
55
|
+
|
|
56
|
+
## Invariants
|
|
57
|
+
|
|
58
|
+
- **Never set draft or live switch keys.** Do not write `metadata.published`,
|
|
59
|
+
`metadata.publishStatus` or `metadata.operation`: `itc-publish-fanout` stamps the plan's
|
|
60
|
+
`switches` into each variant's metadata at send time (V3 — the switch is deterministic code, not
|
|
61
|
+
model output).
|
|
62
|
+
- **No platform knowledge.** If the plan names a variant, adapt it; if a limit is missing, adapt
|
|
63
|
+
without a limit. Never hard-code target names, locales or limits in this skill.
|
|
64
|
+
- **Faithful translation.** Do not add, remove or reorder sections relative to the source body; the
|
|
65
|
+
approved outline's structure is preserved.
|
|
66
|
+
|
|
67
|
+
## Gotchas
|
|
68
|
+
|
|
69
|
+
1. **Read the plan, not the target list** — the plan, not `publish_targets`, is the adaptation
|
|
70
|
+
contract.
|
|
71
|
+
2. **A missing source `content.json` is a blocking error** — the materialize state always writes it
|
|
72
|
+
before publish-plan; if it is absent, fail loudly instead of inventing content.
|
|
73
|
+
3. **One file per adapt variant, exact path** — write `<entry.file>` under `4-publish/`; a
|
|
74
|
+
follow-up `kk stage itc-adapt-verify` step fails the workflow when a variant file is missing or
|
|
75
|
+
lacks `title`/`body`.
|
|
76
|
+
|
|
77
|
+
## Platform Notes
|
|
78
|
+
|
|
79
|
+
### Claude Code
|
|
80
|
+
|
|
81
|
+
Invoked from a spur workflow `agent.run` step; the prompt supplies the work_dir path.
|
|
82
|
+
|
|
83
|
+
### Codex / OpenClaw / OpenCode / Antigravity
|
|
84
|
+
|
|
85
|
+
Run file reads/writes via the available file tools; arguments arrive in the step prompt.
|