@gobing-ai/knowledge-kit 0.0.19 → 0.0.22
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 +161 -142
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +20 -3
- package/plugins/generations/core-facts-gen/dist/index.js +20 -3
- package/plugins/generations/daily-article-gen/dist/index.js +20 -3
- package/plugins/generations/dailynews-gen/dist/index.js +29 -4
- package/plugins/generations/dailynews-gen/src/script-builder.ts +19 -1
- package/plugins/generations/episode-plan-gen/dist/index.js +20 -3
- package/plugins/generations/image-gen/dist/index.js +147 -69
- package/plugins/generations/image-gen/src/presets.ts +2 -2
- package/plugins/generations/image-gen/src/providers/agnes.ts +6 -2
- package/plugins/generations/image-gen/src/providers/azure.ts +6 -2
- package/plugins/generations/image-gen/src/providers/dashscope.ts +10 -3
- package/plugins/generations/image-gen/src/providers/google.ts +70 -29
- package/plugins/generations/image-gen/src/providers/huggingface.ts +3 -0
- package/plugins/generations/image-gen/src/providers/jimeng.ts +9 -2
- package/plugins/generations/image-gen/src/providers/minimax.ts +10 -2
- package/plugins/generations/image-gen/src/providers/openai.ts +9 -3
- package/plugins/generations/image-gen/src/providers/openrouter.ts +16 -4
- package/plugins/generations/image-gen/src/providers/replicate.ts +13 -3
- package/plugins/generations/image-gen/src/providers/seedream.ts +4 -1
- package/plugins/generations/image-gen/src/providers/timeouts.ts +14 -0
- package/plugins/generations/image-gen/src/providers/types.ts +10 -0
- package/plugins/generations/image-gen/src/providers/zai.ts +4 -1
- package/plugins/generations/news-report-gen/dist/index.js +20 -3
- 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 +36 -10
- package/plugins/generations/voice-gen/src/voicebox-client.ts +18 -4
- package/plugins/generations/voice-gen/src/voicebox-server.ts +2 -1
- package/plugins/ingestions/aihot-ingest/dist/index.js +26 -6
- package/plugins/ingestions/aihot-ingest/plugin.json +1 -1
- package/plugins/ingestions/aihot-ingest/src/client.ts +5 -0
- package/plugins/ingestions/aihot-ingest/src/rss.ts +12 -2
- package/plugins/ingestions/horizon-ingest/dist/index.js +20 -3
- package/plugins/ingestions/last30days-ingest/dist/index.js +20 -3
- package/plugins/ingestions/web-search/dist/index.js +22 -3
- package/plugins/ingestions/web-search/src/index.ts +4 -0
- package/plugins/kk/commands/workflow-run.md +20 -6
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/itc-stages.ts +563 -0
- package/plugins/kk/scripts/kk-workflow-stages.ts +125 -18
- package/plugins/kk/scripts/render-md.ts +7 -1
- package/plugins/kk/skills/article-adapt/SKILL.md +85 -0
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +26 -16
- package/plugins/kk/workflows/kk-itc.yaml +461 -12
- package/plugins/publishings/emdash-pub/dist/index.js +76 -19
- package/plugins/publishings/emdash-pub/src/index.ts +4 -0
- package/plugins/publishings/infoq-pub/dist/index.js +74 -21
- package/plugins/publishings/juejin-pub/dist/index.js +74 -21
- package/plugins/publishings/medium-pub/dist/index.js +77 -19
- package/plugins/publishings/medium-pub/src/index.ts +5 -0
- package/plugins/publishings/podcast-pub/dist/index.js +130 -34
- package/plugins/publishings/podcast-pub/src/index.ts +20 -5
- package/plugins/publishings/podcast-pub/src/map.ts +11 -0
- package/plugins/publishings/podcast-pub/src/show-notes.ts +66 -13
- package/plugins/publishings/qiita-pub/dist/index.js +75 -18
- package/plugins/publishings/qiita-pub/src/index.ts +4 -0
- package/plugins/publishings/substack-pub/dist/index.js +74 -21
- package/plugins/publishings/surfdash-pub/dist/index.js +99 -62
- package/plugins/publishings/surfdash-pub/src/index.ts +60 -19
- package/plugins/publishings/wechatmp-pub/dist/index.js +79 -22
- package/plugins/publishings/wechatmp-pub/src/index.ts +12 -3
- package/plugins/publishings/x-pub/dist/index.js +56 -37
- package/plugins/publishings/x-pub/src/index.ts +4 -0
- package/plugins/publishings/xhs-pub/dist/index.js +74 -21
- package/plugins/publishings/zenn-pub/dist/index.js +103 -25
- package/plugins/publishings/zenn-pub/src/index.ts +43 -9
|
@@ -12,13 +12,21 @@
|
|
|
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
|
|
@@ -46,7 +54,7 @@ import {
|
|
|
46
54
|
writeFileSync,
|
|
47
55
|
} from 'node:fs';
|
|
48
56
|
import { homedir } from 'node:os';
|
|
49
|
-
import { basename, dirname, join, resolve } from 'node:path';
|
|
57
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
50
58
|
|
|
51
59
|
const SCRIPTS_DIR = import.meta.dir;
|
|
52
60
|
const VALIDATE_SIDECAR = 'validate-voicescript.ts';
|
|
@@ -63,6 +71,21 @@ function fail(message: string): never {
|
|
|
63
71
|
process.exit(1);
|
|
64
72
|
}
|
|
65
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
|
+
|
|
66
89
|
/** coreutils-style reason for the errno codes the staged file operations can hit. */
|
|
67
90
|
const ERRNO_REASONS: Record<string, string> = {
|
|
68
91
|
EACCES: 'Permission denied',
|
|
@@ -904,7 +927,14 @@ function dailyScript(args: string[]): void {
|
|
|
904
927
|
sted('write', yamlOut, () => writeRaw(yamlOut, body));
|
|
905
928
|
// R3 (task 0139): fresh writes are validated here; the script cache path is covered by
|
|
906
929
|
// wrap-docs, which runs on every path that reaches audio.
|
|
907
|
-
|
|
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
|
+
}
|
|
908
938
|
});
|
|
909
939
|
}
|
|
910
940
|
|
|
@@ -1306,6 +1336,17 @@ function dailyPatchSurfdash(args: string[]): void {
|
|
|
1306
1336
|
const postPath = (sd.metadata as Record<string, unknown> | undefined)?.postPath;
|
|
1307
1337
|
if (typeof postPath !== 'string' || postPath === '')
|
|
1308
1338
|
failWith(1, 'patch-surfdash: surfdash result carries no postPath');
|
|
1339
|
+
// Refuse before anything is written: surfdash's deploy.yml publishes from main, so a commit
|
|
1340
|
+
// on any other branch is invisible to production. Placed here (after the input checks) so a
|
|
1341
|
+
// refusal leaves the working tree untouched.
|
|
1342
|
+
const head = spawnSync('git', ['-C', surfdashRoot, 'rev-parse', '--abbrev-ref', 'HEAD'])
|
|
1343
|
+
.stdout?.toString()
|
|
1344
|
+
.trim();
|
|
1345
|
+
if (head !== 'main')
|
|
1346
|
+
failWith(1, `patch-surfdash: refusing to commit on '${head}' — surfdash deploys from main`);
|
|
1347
|
+
// Paths this run actually wrote, relative to surfdashRoot. `git add`/`commit` take these as a
|
|
1348
|
+
// pathspec so the operator's unrelated staged work is never swept into the commit.
|
|
1349
|
+
const written: string[] = [];
|
|
1309
1350
|
const slugDir = dirname(postPath);
|
|
1310
1351
|
const showNotesUrl = typeof pod.url === 'string' ? pod.url : '';
|
|
1311
1352
|
const coverUrl = ((pod.metadata as Record<string, unknown> | undefined)?.imageUrl as string | undefined) ?? '';
|
|
@@ -1318,9 +1359,12 @@ function dailyPatchSurfdash(args: string[]): void {
|
|
|
1318
1359
|
};
|
|
1319
1360
|
let changed = 0;
|
|
1320
1361
|
for (const [locale, dir] of [
|
|
1362
|
+
// zh posts live under articles/zh/<slug>; en/ja under articles/<locale>/<slug> —
|
|
1363
|
+
// two dirnames up, not one (20260919: `articles/zh/en/...` never existed, so en/ja
|
|
1364
|
+
// were silently skipped by the not-published branch).
|
|
1321
1365
|
['zh', slugDir],
|
|
1322
|
-
['en', join(dirname(slugDir), 'en', basename(slugDir))],
|
|
1323
|
-
['ja', join(dirname(slugDir), 'ja', basename(slugDir))],
|
|
1366
|
+
['en', join(dirname(dirname(slugDir)), 'en', basename(slugDir))],
|
|
1367
|
+
['ja', join(dirname(dirname(slugDir)), 'ja', basename(slugDir))],
|
|
1324
1368
|
] as const) {
|
|
1325
1369
|
const indexMd = join(dir, 'index.md');
|
|
1326
1370
|
if (!existsSync(indexMd)) {
|
|
@@ -1330,17 +1374,38 @@ function dailyPatchSurfdash(args: string[]): void {
|
|
|
1330
1374
|
let md = readFileSync(indexMd, 'utf-8');
|
|
1331
1375
|
const fmEnd = md.indexOf('---', 3); // frontmatter is `---\n ... \n---\n`
|
|
1332
1376
|
if (fmEnd < 0) failWith(1, `patch-surfdash: no frontmatter in ${indexMd}`);
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1377
|
+
// Upsert a single-line `key: 'value'`; the regex consumes folded-scalar continuation
|
|
1378
|
+
// lines (`og_image: >-\n https://...`) so multi-line publish output is replaced whole.
|
|
1379
|
+
const upsertKey = (src: string, key: string, value: string): string => {
|
|
1380
|
+
// YAML single-quoted scalars escape `'` as `''`; the replacement is a function so
|
|
1381
|
+
// `$&`/`$1`/`$$` in the value are never expanded. Scope the match to the frontmatter
|
|
1382
|
+
// (recomputed per call, since each upsert shifts offsets) so a body line starting with
|
|
1383
|
+
// `image:` is not rewritten.
|
|
1384
|
+
const end = src.indexOf('\n---', 3);
|
|
1385
|
+
const fm = src.slice(0, end);
|
|
1386
|
+
const body = src.slice(end);
|
|
1387
|
+
const line = `${key}: '${value.replaceAll("'", "''")}'`;
|
|
1388
|
+
const re = new RegExp(`^${key}:.*(?:\\n[ \\t]+.*)*`, 'm');
|
|
1389
|
+
const next = re.test(fm)
|
|
1390
|
+
? fm.replace(re, () => line)
|
|
1391
|
+
: fm.replace(/^publishDate: .*$/m, (m0) => `${m0}\n${line}`);
|
|
1392
|
+
return next + body;
|
|
1393
|
+
};
|
|
1394
|
+
if (coverUrl !== '') {
|
|
1395
|
+
// 20260919 prod feedback: the podcast-hosted cover URL wins even when surfdash-pub
|
|
1396
|
+
// already wrote `image:` — its resolved og_image points at /content-posts/<slug>/
|
|
1397
|
+
// assets/cover.png, which the deployed worker does not serve (404).
|
|
1398
|
+
md = upsertKey(md, 'image', coverUrl);
|
|
1399
|
+
md = upsertKey(md, 'og_image', coverUrl);
|
|
1400
|
+
} else if (!/^image:/m.test(md.slice(0, fmEnd))) {
|
|
1401
|
+
// Fallback for runs without a podcast cover: copy the local cover instead.
|
|
1402
|
+
if (existsSync(localCover)) {
|
|
1337
1403
|
const assets = join(dir, 'assets');
|
|
1338
1404
|
mkdirSync(assets, { recursive: true });
|
|
1339
1405
|
copyFileSync(localCover, join(assets, 'cover.png'));
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
md = md.replace(/^publishDate: .*$/m, (m0) => `${m0}\nimage: '${image}'\nog_image: '${image}'`);
|
|
1406
|
+
written.push(relative(surfdashRoot, join(assets, 'cover.png')));
|
|
1407
|
+
md = upsertKey(md, 'image', './assets/cover.png');
|
|
1408
|
+
md = upsertKey(md, 'og_image', './assets/cover.png');
|
|
1344
1409
|
}
|
|
1345
1410
|
}
|
|
1346
1411
|
if (showNotesUrl !== '' && !md.includes(showNotesUrl)) {
|
|
@@ -1348,6 +1413,7 @@ function dailyPatchSurfdash(args: string[]): void {
|
|
|
1348
1413
|
}
|
|
1349
1414
|
if (md !== readFileSync(indexMd, 'utf-8')) {
|
|
1350
1415
|
sted('write', indexMd, () => writeFileSync(indexMd, md));
|
|
1416
|
+
written.push(relative(surfdashRoot, indexMd));
|
|
1351
1417
|
changed++;
|
|
1352
1418
|
}
|
|
1353
1419
|
}
|
|
@@ -1359,9 +1425,15 @@ function dailyPatchSurfdash(args: string[]): void {
|
|
|
1359
1425
|
const r = spawnSync('git', ['-C', surfdashRoot, ...a]);
|
|
1360
1426
|
if (r.status !== 0) failWith(1, `patch-surfdash: git ${a[0]} failed: ${r.stderr?.toString().trim()}`);
|
|
1361
1427
|
};
|
|
1362
|
-
git('add', '
|
|
1363
|
-
git(
|
|
1364
|
-
|
|
1428
|
+
git('add', '--', ...written);
|
|
1429
|
+
git(
|
|
1430
|
+
'commit',
|
|
1431
|
+
'-m',
|
|
1432
|
+
`feat: cover + podcast cross-link for ${basename(slugDir)} (kk daily patch)`,
|
|
1433
|
+
'--',
|
|
1434
|
+
...written,
|
|
1435
|
+
);
|
|
1436
|
+
if (process.env.SURFDASH_AUTO_PUSH !== '0') git('push', 'origin', 'main');
|
|
1365
1437
|
console.log(
|
|
1366
1438
|
`patch-surfdash: patched ${changed} post(s), committed${process.env.SURFDASH_AUTO_PUSH !== '0' ? ' + pushed' : ' (push skipped: SURFDASH_AUTO_PUSH=0)'}`,
|
|
1367
1439
|
);
|
|
@@ -1626,8 +1698,43 @@ if (import.meta.main)
|
|
|
1626
1698
|
case 'daily-translate-sync':
|
|
1627
1699
|
dailyTranslateSync(rest);
|
|
1628
1700
|
break;
|
|
1701
|
+
case 'itc-outline-pick': {
|
|
1702
|
+
const { itcOutlinePick } = await loadItcStages();
|
|
1703
|
+
itcOutlinePick(rest);
|
|
1704
|
+
break;
|
|
1705
|
+
}
|
|
1706
|
+
case 'itc-report': {
|
|
1707
|
+
const { itcReport } = await loadItcStages();
|
|
1708
|
+
itcReport(rest);
|
|
1709
|
+
break;
|
|
1710
|
+
}
|
|
1711
|
+
case 'itc-materialize': {
|
|
1712
|
+
const { itcMaterialize } = await loadItcStages();
|
|
1713
|
+
itcMaterialize(rest);
|
|
1714
|
+
break;
|
|
1715
|
+
}
|
|
1716
|
+
case 'itc-publish-plan': {
|
|
1717
|
+
const { itcPublishPlan } = await loadItcStages();
|
|
1718
|
+
itcPublishPlan(rest);
|
|
1719
|
+
break;
|
|
1720
|
+
}
|
|
1721
|
+
case 'itc-publish-fanout': {
|
|
1722
|
+
const { itcPublishFanout } = await loadItcStages();
|
|
1723
|
+
itcPublishFanout(rest);
|
|
1724
|
+
break;
|
|
1725
|
+
}
|
|
1726
|
+
case 'itc-publish-next': {
|
|
1727
|
+
const { itcPublishNext } = await loadItcStages();
|
|
1728
|
+
itcPublishNext(rest);
|
|
1729
|
+
break;
|
|
1730
|
+
}
|
|
1731
|
+
case 'itc-adapt-verify': {
|
|
1732
|
+
const { itcAdaptVerify } = await loadItcStages();
|
|
1733
|
+
itcAdaptVerify(rest);
|
|
1734
|
+
break;
|
|
1735
|
+
}
|
|
1629
1736
|
default:
|
|
1630
1737
|
fail(
|
|
1631
|
-
`unknown stage: ${stage ?? '(none)'} (expected prepare-itc|prepare-solo|prepare-storm|validate|wrap|duration|render-storm|daily-*)`,
|
|
1738
|
+
`unknown stage: ${stage ?? '(none)'} (expected prepare-itc|prepare-solo|prepare-storm|validate|wrap|duration|render-storm|itc-*|daily-*)`,
|
|
1632
1739
|
);
|
|
1633
1740
|
}
|
|
@@ -101,6 +101,12 @@ function renderReferences(references: ContentInput['references']): string {
|
|
|
101
101
|
return `\n\n## References\n\n${lines.join('\n')}`;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/** Escape `\\`, `[`, `]` so link text cannot terminate the `[title]` form early (0155 R4). */
|
|
105
|
+
const escapeLinkText = (s: string): string => s.replace(/[\\[\]]/g, '\\$&');
|
|
106
|
+
/** Percent-encode characters that would break the inline link destination `(url)` (0155 R4). */
|
|
107
|
+
const escapeLinkUrl = (s: string): string =>
|
|
108
|
+
s.replace(/[ ()<>]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')}`);
|
|
109
|
+
|
|
104
110
|
function renderReferenceLabel(ref: Ref): string {
|
|
105
111
|
const { url, title, cite } = ref;
|
|
106
112
|
const titleT = title?.trim() ?? '';
|
|
@@ -108,7 +114,7 @@ function renderReferenceLabel(ref: Ref): string {
|
|
|
108
114
|
const citeT = cite?.trim() ?? '';
|
|
109
115
|
let label: string;
|
|
110
116
|
if (titleT !== '' && urlT !== '') {
|
|
111
|
-
label = `[${titleT}](${urlT})`;
|
|
117
|
+
label = `[${escapeLinkText(titleT)}](${escapeLinkUrl(urlT)})`;
|
|
112
118
|
} else if (titleT !== '') {
|
|
113
119
|
label = titleT;
|
|
114
120
|
} else if (urlT !== '') {
|
|
@@ -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.
|
|
@@ -19,8 +19,10 @@ vars:
|
|
|
19
19
|
limit: "10"
|
|
20
20
|
language: "zh"
|
|
21
21
|
voice_profile: "robin-news"
|
|
22
|
-
# 20260917: voice driver switch —
|
|
23
|
-
|
|
22
|
+
# 20260917: voice driver switch — voicebox (default, voice-gen) | ominivoice (omni-voice-gen).
|
|
23
|
+
# 20260920: default flipped to voicebox — operator A/B at matched speech rate (~5.2 chars/s)
|
|
24
|
+
# judged voicebox clearly better (ominivoice carries ~1min dead air over 16min + ASR-garble class).
|
|
25
|
+
vdriver: "voicebox"
|
|
24
26
|
# 20260917 review #7: 8s intro sting prepended to every daily output; empty = skip.
|
|
25
27
|
intro_music: ".works/kk-daily-ai-voice/AI每日资讯_8秒播客片头.mp3"
|
|
26
28
|
# 20260917 review #4/#6: surfdash checkout the patch stage commits to.
|
|
@@ -463,8 +465,14 @@ transitions:
|
|
|
463
465
|
kind: always
|
|
464
466
|
|
|
465
467
|
- from: generate
|
|
468
|
+
to: intro-music
|
|
469
|
+
description: "Audio generated -> prepend intro sting"
|
|
470
|
+
guard:
|
|
471
|
+
kind: always
|
|
472
|
+
|
|
473
|
+
- from: intro-music
|
|
466
474
|
to: quality-control
|
|
467
|
-
description: "
|
|
475
|
+
description: "Intro merged -> run quality control validation"
|
|
468
476
|
guard:
|
|
469
477
|
kind: always
|
|
470
478
|
|
|
@@ -518,10 +526,12 @@ transitions:
|
|
|
518
526
|
kind: always
|
|
519
527
|
|
|
520
528
|
- from: publish
|
|
521
|
-
to:
|
|
522
|
-
description: "Publish classified -> patch
|
|
529
|
+
to: translate
|
|
530
|
+
description: "Publish classified -> full publish continues to en/ja translation; guard is full-only (partial exits via publish-partial); patch-surfdash runs after translate so all three locales get cover + podcast cross-link patching"
|
|
523
531
|
guard:
|
|
524
|
-
kind:
|
|
532
|
+
kind: shell
|
|
533
|
+
options:
|
|
534
|
+
command: 'test "${vars.publish_status}" = "full"'
|
|
525
535
|
|
|
526
536
|
- from: patch-surfdash
|
|
527
537
|
to: run-report
|
|
@@ -551,24 +561,24 @@ transitions:
|
|
|
551
561
|
guard:
|
|
552
562
|
kind: always
|
|
553
563
|
|
|
554
|
-
- from:
|
|
555
|
-
to:
|
|
556
|
-
description: "
|
|
564
|
+
- from: translate
|
|
565
|
+
to: patch-surfdash
|
|
566
|
+
description: "zh post + en/ja locales scaffolded, translated, and synced -> patch all published posts with cover + podcast cross-link"
|
|
557
567
|
guard:
|
|
558
|
-
kind:
|
|
559
|
-
options:
|
|
560
|
-
command: 'test "${vars.publish_status}" = "full"'
|
|
568
|
+
kind: always
|
|
561
569
|
|
|
562
570
|
- from: run-report
|
|
563
571
|
to: done
|
|
564
|
-
description: "Run report assembled after
|
|
572
|
+
description: "Run report assembled after non-full publish (partial) -> done; retry failed targets later via kk executor fan-out --retry-from"
|
|
565
573
|
guard:
|
|
566
574
|
kind: shell
|
|
567
575
|
options:
|
|
568
576
|
command: 'test "${vars.publish_status}" = "partial"'
|
|
569
577
|
|
|
570
|
-
- from:
|
|
578
|
+
- from: run-report
|
|
571
579
|
to: done
|
|
572
|
-
description: "
|
|
580
|
+
description: "Run report assembled after full publish -> episode complete (en/ja already translated and patched)"
|
|
573
581
|
guard:
|
|
574
|
-
kind:
|
|
582
|
+
kind: shell
|
|
583
|
+
options:
|
|
584
|
+
command: 'test "${vars.publish_status}" = "full"'
|