@gobing-ai/knowledge-kit 0.0.25 → 0.0.27
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/package.json +1 -1
- package/plugins/generations/gemini-tts/dist/index.js +3 -3
- package/plugins/generations/news-report-gen/src/index.ts +5 -5
- package/plugins/generations/voice-gen/dist/index.js +3 -3
- package/plugins/generations/voice-gen/src/mp3.ts +4 -3
- package/plugins/generations/voice-gen/src/voicescript.ts +5 -2
- package/plugins/kk/commands/workflow-run.md +4 -4
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/kk-workflow-stages.ts +146 -99
- package/plugins/kk/scripts/validate-voicescript.ts +5 -2
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +73 -39
- package/plugins/publishings/wechatmp-pub/dist/index.js +8449 -307
- package/plugins/publishings/wechatmp-pub/package.json +3 -1
- package/plugins/publishings/wechatmp-pub/plugin.json +1 -1
- package/plugins/publishings/wechatmp-pub/src/index.ts +212 -167
- package/plugins/publishings/wechatmp-pub/src/profile-check.ts +34 -0
- package/plugins/publishings/xhs-pub/package.json +2 -1
|
@@ -438,15 +438,15 @@ function parseStage(
|
|
|
438
438
|
// deliberately stay in the YAML.
|
|
439
439
|
|
|
440
440
|
const DAILY_DIRS = [
|
|
441
|
-
'
|
|
442
|
-
'
|
|
443
|
-
'
|
|
444
|
-
'
|
|
445
|
-
'
|
|
446
|
-
'
|
|
447
|
-
'
|
|
448
|
-
'
|
|
449
|
-
'
|
|
441
|
+
'10-ingest',
|
|
442
|
+
'20-facts',
|
|
443
|
+
'21-plan',
|
|
444
|
+
'22-article',
|
|
445
|
+
'23-cover',
|
|
446
|
+
'25-xhs',
|
|
447
|
+
'24-script',
|
|
448
|
+
'30-audio',
|
|
449
|
+
'31-publish',
|
|
450
450
|
] as const;
|
|
451
451
|
|
|
452
452
|
/** `date -u +%FT%TZ` — the timestamp grammar the step-timing lines have always used. */
|
|
@@ -619,23 +619,23 @@ function dailyPrepare(args: string[]): void {
|
|
|
619
619
|
mkdirSync(anchorDir, { recursive: true });
|
|
620
620
|
writeFileSync(join(anchorDir, 'work-dir.txt'), `${workDir}\n`);
|
|
621
621
|
});
|
|
622
|
-
sted('write', join(workDir, '
|
|
623
|
-
writeJsonText(join(workDir, '
|
|
622
|
+
sted('write', join(workDir, '10-ingest/source.json'), () =>
|
|
623
|
+
writeJsonText(join(workDir, '10-ingest/source.json'), {
|
|
624
624
|
limit: Number(limit),
|
|
625
625
|
cursor: cursor === '' ? undefined : cursor,
|
|
626
626
|
stateFile: stateFile === '' ? undefined : stateFile,
|
|
627
627
|
}),
|
|
628
628
|
);
|
|
629
629
|
if (last30daysEnabled !== 'true') {
|
|
630
|
-
sted('write', join(workDir, '
|
|
631
|
-
writeFileSync(join(workDir, '
|
|
630
|
+
sted('write', join(workDir, '10-ingest/last30days-docs.json'), () =>
|
|
631
|
+
writeFileSync(join(workDir, '10-ingest/last30days-docs.json'), '[]'),
|
|
632
632
|
);
|
|
633
633
|
}
|
|
634
|
-
sted('write', join(workDir, '
|
|
635
|
-
writeJsonText(join(workDir, '
|
|
634
|
+
sted('write', join(workDir, '10-ingest/last30days.json'), () =>
|
|
635
|
+
writeJsonText(join(workDir, '10-ingest/last30days.json'), { topic }),
|
|
636
636
|
);
|
|
637
|
-
sted('write', join(workDir, '
|
|
638
|
-
writeJsonText(join(workDir, '
|
|
637
|
+
sted('write', join(workDir, '10-ingest/horizon.json'), () =>
|
|
638
|
+
writeJsonText(join(workDir, '10-ingest/horizon.json'), { hours: Number(horizonHours) }),
|
|
639
639
|
);
|
|
640
640
|
});
|
|
641
641
|
}
|
|
@@ -679,9 +679,9 @@ function resolveWorksDir(): string {
|
|
|
679
679
|
|
|
680
680
|
function dailyCollectFacts(args: string[]): void {
|
|
681
681
|
const [workDir = '', runDate = '', pluginsPath = ''] = args;
|
|
682
|
-
const content = join(workDir, '
|
|
683
|
-
const facts = join(workDir, '
|
|
684
|
-
const blended = join(workDir, '
|
|
682
|
+
const content = join(workDir, '20-facts', `${runDate}_02_collect_content.json`);
|
|
683
|
+
const facts = join(workDir, '20-facts', `${runDate}_02_collect_core-facts.md`);
|
|
684
|
+
const blended = join(workDir, '10-ingest', `${runDate}_02_collect_blended.json`);
|
|
685
685
|
timed(workDir, 'collect', () => {
|
|
686
686
|
if (isFile(content) && isFile(facts)) {
|
|
687
687
|
console.log(`core facts cached: ${facts}`);
|
|
@@ -696,9 +696,9 @@ function dailyCollectFacts(args: string[]): void {
|
|
|
696
696
|
|
|
697
697
|
function dailyPlan(args: string[]): void {
|
|
698
698
|
const [workDir = '', runDate = '', pluginsPath = '', planMaxItems = ''] = args;
|
|
699
|
-
const planContent = join(workDir, '
|
|
700
|
-
const planDocs = join(workDir, '
|
|
701
|
-
const blended = join(workDir, '
|
|
699
|
+
const planContent = join(workDir, '21-plan', `${runDate}_03_plan_content.json`);
|
|
700
|
+
const planDocs = join(workDir, '21-plan', `${runDate}_03_plan_plan.json`);
|
|
701
|
+
const blended = join(workDir, '10-ingest', `${runDate}_02_collect_blended.json`);
|
|
702
702
|
timed(workDir, 'plan', () => {
|
|
703
703
|
if (isFile(planDocs)) {
|
|
704
704
|
console.log(`plan cached: ${planDocs}`);
|
|
@@ -728,9 +728,9 @@ function dailyQcContent(args: string[]): void {
|
|
|
728
728
|
minUrgency = '',
|
|
729
729
|
minImpact = '',
|
|
730
730
|
] = args;
|
|
731
|
-
const candidates = join(workDir, '
|
|
732
|
-
const annotated = join(workDir, '
|
|
733
|
-
const planContent = join(workDir, '
|
|
731
|
+
const candidates = join(workDir, '21-plan', `${runDate}_04_quality-control-content_candidates.json`);
|
|
732
|
+
const annotated = join(workDir, '21-plan', `${runDate}_04_quality-control-content_annotated.json`);
|
|
733
|
+
const planContent = join(workDir, '21-plan', `${runDate}_03_plan_content.json`);
|
|
734
734
|
timed(workDir, 'quality-control-content', () => {
|
|
735
735
|
if (isFile(candidates)) {
|
|
736
736
|
console.log(`candidates cached: ${candidates}`);
|
|
@@ -830,10 +830,10 @@ function assertScriptContent(scriptPath: string): void {
|
|
|
830
830
|
|
|
831
831
|
function dailyArticle(args: string[]): void {
|
|
832
832
|
const [workDir = '', runDate = '', pluginsPath = ''] = args;
|
|
833
|
-
const articleMd = join(workDir, '
|
|
834
|
-
const articleContent = join(workDir, '
|
|
835
|
-
const planIn = join(workDir, '
|
|
836
|
-
const candidates = join(workDir, '
|
|
833
|
+
const articleMd = join(workDir, '22-article', `${runDate}_06_article_article.md`);
|
|
834
|
+
const articleContent = join(workDir, '22-article', `${runDate}_06_article_content.json`);
|
|
835
|
+
const planIn = join(workDir, '21-plan', `${runDate}_03_plan_plan.json`);
|
|
836
|
+
const candidates = join(workDir, '21-plan', `${runDate}_04_quality-control-content_candidates.json`);
|
|
837
837
|
timed(workDir, 'article', () => {
|
|
838
838
|
let planUsable = true;
|
|
839
839
|
const raw = isFile(planIn) ? readFileSync(planIn, 'utf-8') : '';
|
|
@@ -870,7 +870,7 @@ function dailyArticle(args: string[]): void {
|
|
|
870
870
|
// Agent-written prose body (workflow `article-write` state) replaces the mechanical
|
|
871
871
|
// per-item render when present — the 3-format split: surfdash gets an article,
|
|
872
872
|
// not a news list (2026-09-16 feedback).
|
|
873
|
-
const proseBody = join(workDir, '
|
|
873
|
+
const proseBody = join(workDir, '22-article', `${runDate}_06_article-write_body.md`);
|
|
874
874
|
kk(
|
|
875
875
|
[
|
|
876
876
|
'executor',
|
|
@@ -928,9 +928,9 @@ function coverBrief(briefPath: string): { reason: string | null; subject: string
|
|
|
928
928
|
*/
|
|
929
929
|
function dailyCover(args: string[]): void {
|
|
930
930
|
const [workDir = '', runDate = '', pluginsPath = '', provider = '', model = '', fallback = ''] = args;
|
|
931
|
-
const briefPath = join(workDir, '
|
|
932
|
-
const envPath = join(workDir, '
|
|
933
|
-
const contentOut = join(workDir, '
|
|
931
|
+
const briefPath = join(workDir, '23-cover', `${runDate}_07_cover-brief.json`);
|
|
932
|
+
const envPath = join(workDir, '23-cover', `${runDate}_07_cover_image-gen-in.json`);
|
|
933
|
+
const contentOut = join(workDir, '23-cover', 'content.json');
|
|
934
934
|
timed(workDir, 'cover', () => {
|
|
935
935
|
const brief = coverBrief(briefPath);
|
|
936
936
|
const vars: Record<string, string> = {};
|
|
@@ -987,20 +987,20 @@ function coverNormalizeBody(workDir: string, runDate: string): void {
|
|
|
987
987
|
// F614 R5: the artifact name follows the format preset (`<stem>-<format>-NN.<ext>`,
|
|
988
988
|
// paths.ts), so normalize reads the path the plugin itself recorded in the manifest instead
|
|
989
989
|
// of a hard-coded name. The legacy content-cover-01.{png,jpg} paths stay as stale cleanup.
|
|
990
|
-
const legacyPng = join(workDir, '
|
|
991
|
-
const legacyJpg = join(workDir, '
|
|
990
|
+
const legacyPng = join(workDir, '23-cover', 'content-cover-01.png');
|
|
991
|
+
const legacyJpg = join(workDir, '23-cover', 'content-cover-01.jpg');
|
|
992
992
|
// Must match the name dailyPublishPrep and the article->cover workflow guard read:
|
|
993
993
|
// `${runDate}_07_cover_cover.png` (a plain `cover-${runDate}.png` left options.coverPath
|
|
994
994
|
// undefined and silently dropped the cover from both podcast and surfdash — 2026-09-16 run).
|
|
995
|
-
const out = join(workDir, '
|
|
996
|
-
const images = metadataOf(readJson(join(workDir, '
|
|
995
|
+
const out = join(workDir, '23-cover', `${runDate}_07_cover_cover.png`);
|
|
996
|
+
const images = metadataOf(readJson(join(workDir, '23-cover', 'content.json'))).images;
|
|
997
997
|
const first = Array.isArray(images) ? images[0] : undefined;
|
|
998
998
|
const recorded =
|
|
999
999
|
first !== null && typeof first === 'object' && typeof (first as Record<string, unknown>).path === 'string'
|
|
1000
1000
|
? ((first as Record<string, unknown>).path as string)
|
|
1001
1001
|
: '';
|
|
1002
1002
|
if (recorded === '' || !isFile(recorded)) {
|
|
1003
|
-
failWith(1, 'cover normalize: no image artifact in
|
|
1003
|
+
failWith(1, 'cover normalize: no image artifact in 23-cover/content.json');
|
|
1004
1004
|
}
|
|
1005
1005
|
const source = recorded;
|
|
1006
1006
|
if (source.endsWith('.jpg') || source.endsWith('.jpeg')) {
|
|
@@ -1096,10 +1096,10 @@ function xhsPlan(planPath: string, count: number): { title: string; body: string
|
|
|
1096
1096
|
function dailyXhsRender(args: string[]): void {
|
|
1097
1097
|
const [workDir = '', runDate = '', pluginsPath = '', countArg = '', style = '', provider = '', model = ''] = args;
|
|
1098
1098
|
const count = Number.parseInt(countArg, 10);
|
|
1099
|
-
const planPath = join(workDir, '
|
|
1100
|
-
const cardsDir = join(workDir, '
|
|
1101
|
-
const manifestPath = join(workDir, '
|
|
1102
|
-
const contentPath = join(workDir, '
|
|
1099
|
+
const planPath = join(workDir, '25-xhs', `${runDate}_18_xhs_plan.json`);
|
|
1100
|
+
const cardsDir = join(workDir, '25-xhs', 'cards');
|
|
1101
|
+
const manifestPath = join(workDir, '25-xhs', 'cards.json');
|
|
1102
|
+
const contentPath = join(workDir, '25-xhs', 'content.json');
|
|
1103
1103
|
timed(workDir, 'xhs-render', () => {
|
|
1104
1104
|
if (!Number.isInteger(count) || count < 1 || count > XHS_MAX_CARDS) {
|
|
1105
1105
|
failWith(1, `xhs render: xhs_cards must be 1..${XHS_MAX_CARDS}, got '${countArg}'`);
|
|
@@ -1176,8 +1176,8 @@ function dailyXhsRender(args: string[]): void {
|
|
|
1176
1176
|
});
|
|
1177
1177
|
}
|
|
1178
1178
|
|
|
1179
|
-
/** A single-target
|
|
1180
|
-
function
|
|
1179
|
+
/** A single-target publish Result, not the fan-out aggregate: `ok` plus `metadata.publishStatus`. */
|
|
1180
|
+
function resultPublishStatus(path: string): string {
|
|
1181
1181
|
const parsed = readJson(path);
|
|
1182
1182
|
if (parsed?.ok !== true) return '';
|
|
1183
1183
|
const status = metadataOf(parsed).publishStatus;
|
|
@@ -1193,14 +1193,14 @@ function xhsStatus(path: string): string {
|
|
|
1193
1193
|
*/
|
|
1194
1194
|
function dailyPublishXhs(args: string[]): void {
|
|
1195
1195
|
const [workDir = '', runDate = '', pluginsPath = '', live = 'false'] = args;
|
|
1196
|
-
const content = join(workDir, '
|
|
1197
|
-
const input = join(workDir, '
|
|
1198
|
-
const result = join(workDir, '
|
|
1196
|
+
const content = join(workDir, '25-xhs', 'content.json');
|
|
1197
|
+
const input = join(workDir, '31-publish', `${runDate}_19_publish-xhs_content.json`);
|
|
1198
|
+
const result = join(workDir, '31-publish', `${runDate}_19_publish-xhs_result.json`);
|
|
1199
1199
|
const want = live === 'true' ? 'public' : 'draft';
|
|
1200
1200
|
timed(workDir, 'publish-xhs', () => {
|
|
1201
1201
|
// Cache on the *requested* status: a cached draft must not silently satisfy a later
|
|
1202
1202
|
// xhs_live=true re-run (that would report a live post that never happened).
|
|
1203
|
-
if (
|
|
1203
|
+
if (resultPublishStatus(result) === want) {
|
|
1204
1204
|
console.log(`publish-xhs cached: ${want} already recorded in ${result}`);
|
|
1205
1205
|
return;
|
|
1206
1206
|
}
|
|
@@ -1226,6 +1226,50 @@ function dailyPublishXhs(args: string[]): void {
|
|
|
1226
1226
|
});
|
|
1227
1227
|
}
|
|
1228
1228
|
|
|
1229
|
+
/**
|
|
1230
|
+
* WeChat MP: drive the console via `wechatmp-pub`'s browser transport and save the article to the
|
|
1231
|
+
* draft box (草稿箱). Draft by default; the 发表 flow runs only on an explicit launch-time
|
|
1232
|
+
* `wechat_live=true` (ADR-022 rail, same as xhs). Independent and non-blocking like XHS: the
|
|
1233
|
+
* outcome never enters `publish_status` (surfdash + podcast remain the publication contract).
|
|
1234
|
+
*/
|
|
1235
|
+
function dailyPublishWechat(args: string[]): void {
|
|
1236
|
+
const [workDir = '', runDate = '', pluginsPath = '', live = 'false'] = args;
|
|
1237
|
+
const article = join(workDir, '22-article', `${runDate}_06_article_content.json`);
|
|
1238
|
+
const input = join(workDir, '31-publish', `${runDate}_20_publish-wechat_content.json`);
|
|
1239
|
+
const result = join(workDir, '31-publish', `${runDate}_20_publish-wechat_result.json`);
|
|
1240
|
+
const want = live === 'true' ? 'public' : 'draft';
|
|
1241
|
+
timed(workDir, 'publish-wechat', () => {
|
|
1242
|
+
// Cache on the *requested* status: a cached draft must not silently satisfy a later
|
|
1243
|
+
// wechat_live=true re-run (that would report a live post that never happened).
|
|
1244
|
+
if (resultPublishStatus(result) === want) {
|
|
1245
|
+
console.log(`publish-wechat cached: ${want} already recorded in ${result}`);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
if (!isFile(article)) {
|
|
1249
|
+
console.log(`publish-wechat: no article at ${article} — skip`);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
const packaged = requireJson(article, 'publish-wechat');
|
|
1253
|
+
sted('write', input, () =>
|
|
1254
|
+
writeJsonText(input, { ...packaged, metadata: { ...metadataOf(packaged), publishStatus: want } }),
|
|
1255
|
+
);
|
|
1256
|
+
// `soft` keeps the WeChat target non-blocking: the plugin still writes its own failed Result
|
|
1257
|
+
// and prints the failing step (session preflight, editor paste, save control), which is what
|
|
1258
|
+
// the operator needs to re-run — never a silent skip.
|
|
1259
|
+
const status = kk(
|
|
1260
|
+
['executor', 'run', 'wechatmp-pub', '--in', input, '--out', result, ...pluginsArgs(pluginsPath)],
|
|
1261
|
+
{
|
|
1262
|
+
soft: true,
|
|
1263
|
+
},
|
|
1264
|
+
);
|
|
1265
|
+
console.log(
|
|
1266
|
+
status === 0
|
|
1267
|
+
? `publish-wechat: ${want} dispatched; outcome recorded in ${result}`
|
|
1268
|
+
: `WARNING: publish-wechat — wechatmp-pub exited ${status}; WeChat target skipped (run continues, see ${result})`,
|
|
1269
|
+
);
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1229
1273
|
/**
|
|
1230
1274
|
* F613 R1: snapshot the shipped style contract into the run's work dir. Spur workflows have no
|
|
1231
1275
|
* workflow-dir var, so the script-write prompt reads this copy — and the snapshot freezes the
|
|
@@ -1234,13 +1278,13 @@ function dailyPublishXhs(args: string[]): void {
|
|
|
1234
1278
|
*/
|
|
1235
1279
|
function dailyScriptStyle(args: string[]): void {
|
|
1236
1280
|
const [workDir = '', runDate = ''] = args;
|
|
1237
|
-
const snapshot = join(workDir, '
|
|
1281
|
+
const snapshot = join(workDir, '24-script', `${runDate}_08_script-write_style.md`);
|
|
1238
1282
|
if (isFile(snapshot)) {
|
|
1239
1283
|
console.log(`script style cached: ${snapshot}`);
|
|
1240
1284
|
return;
|
|
1241
1285
|
}
|
|
1242
1286
|
const sidecar = requireSidecar('', STYLE_SIDECAR, 'daily-voice-style');
|
|
1243
|
-
const scriptDir = join(workDir, '
|
|
1287
|
+
const scriptDir = join(workDir, '24-script');
|
|
1244
1288
|
sted('mkdir', scriptDir, () => mkdirSync(scriptDir, { recursive: true }));
|
|
1245
1289
|
sted('cp', snapshot, () => copyFileSync(sidecar, snapshot));
|
|
1246
1290
|
console.log(`script style snapshot: ${snapshot}`);
|
|
@@ -1248,9 +1292,9 @@ function dailyScriptStyle(args: string[]): void {
|
|
|
1248
1292
|
|
|
1249
1293
|
function dailyScript(args: string[]): void {
|
|
1250
1294
|
const [workDir = '', runDate = '', pluginsPath = '', language = '', title = '', voiceProfile = ''] = args;
|
|
1251
|
-
const yamlOut = join(workDir, '
|
|
1252
|
-
const contentOut = join(workDir, '
|
|
1253
|
-
const planIn = join(workDir, '
|
|
1295
|
+
const yamlOut = join(workDir, '24-script', `${runDate}_08_script_voicescript.yaml`);
|
|
1296
|
+
const contentOut = join(workDir, '24-script', `${runDate}_08_script_content.json`);
|
|
1297
|
+
const planIn = join(workDir, '21-plan', `${runDate}_03_plan_plan.json`);
|
|
1254
1298
|
timed(workDir, 'script', () => {
|
|
1255
1299
|
if (isFile(yamlOut)) {
|
|
1256
1300
|
console.log(`voicescript capped by plan: ${yamlOut}`);
|
|
@@ -1264,7 +1308,7 @@ function dailyScript(args: string[]): void {
|
|
|
1264
1308
|
// F613 R4: always passed as absolute paths — spawnPluginEntry runs plugins with
|
|
1265
1309
|
// cwd = the plugin package dir, so a relative workDir would silently miss the
|
|
1266
1310
|
// plan; a plan file absent at build time reports missing-plan (template fallback).
|
|
1267
|
-
DAILYNEWS_SCRIPT_PLAN: resolve(join(workDir, '
|
|
1311
|
+
DAILYNEWS_SCRIPT_PLAN: resolve(join(workDir, '24-script', `${runDate}_08_script-write_plan.json`)),
|
|
1268
1312
|
DAILYNEWS_STYLE_FILE: resolve(resolveSidecar('', STYLE_SIDECAR)),
|
|
1269
1313
|
},
|
|
1270
1314
|
});
|
|
@@ -1286,8 +1330,8 @@ function dailyScript(args: string[]): void {
|
|
|
1286
1330
|
|
|
1287
1331
|
function dailyWrapDocs(args: string[]): void {
|
|
1288
1332
|
const [workDir = '', runDate = '', voiceProfile = ''] = args;
|
|
1289
|
-
const scriptPath = join(workDir, '
|
|
1290
|
-
const docsPath = join(workDir, '
|
|
1333
|
+
const scriptPath = join(workDir, '24-script', `${runDate}_08_script_voicescript.yaml`);
|
|
1334
|
+
const docsPath = join(workDir, '30-audio', `${runDate}_10_wrap-docs_docs.json`);
|
|
1291
1335
|
timed(workDir, 'wrap-docs', () => {
|
|
1292
1336
|
// R3 (task 0139): the second validator placement — covers a HITL-edited YAML and the
|
|
1293
1337
|
// script-stage cache path, neither of which re-enters dailyScript's fresh-write branch.
|
|
@@ -1323,8 +1367,8 @@ function dailyGenerate(args: string[]): void {
|
|
|
1323
1367
|
`generate: unknown vdriver '${vdriver}' (expected one of: ${Object.keys(VOICE_DRIVERS).join(', ')})`,
|
|
1324
1368
|
);
|
|
1325
1369
|
}
|
|
1326
|
-
const docs = join(workDir, '
|
|
1327
|
-
const content = join(workDir, '
|
|
1370
|
+
const docs = join(workDir, '30-audio', `${runDate}_10_wrap-docs_docs.json`);
|
|
1371
|
+
const content = join(workDir, '30-audio', `${runDate}_11_generate_content.json`);
|
|
1328
1372
|
timed(workDir, 'generate', () => {
|
|
1329
1373
|
if (isFile(content) && metadataOf(readJson(content)).qc !== undefined) {
|
|
1330
1374
|
const qc = metadataOf(readJson(content)).qc as Record<string, unknown> | undefined;
|
|
@@ -1350,7 +1394,7 @@ function dailyIntro(args: string[]): void {
|
|
|
1350
1394
|
console.log('intro-music: intro_music var empty — skip');
|
|
1351
1395
|
return;
|
|
1352
1396
|
}
|
|
1353
|
-
const contentPath = join(workDir, '
|
|
1397
|
+
const contentPath = join(workDir, '30-audio', `${runDate}_11_generate_content.json`);
|
|
1354
1398
|
timed(workDir, 'intro-music', () => {
|
|
1355
1399
|
const content = requireJson(contentPath, 'intro-music');
|
|
1356
1400
|
const meta = metadataOf(content);
|
|
@@ -1453,7 +1497,7 @@ function dailyIntro(args: string[]): void {
|
|
|
1453
1497
|
|
|
1454
1498
|
function dailyQualityReport(args: string[]): void {
|
|
1455
1499
|
const [workDir = '', runDate = ''] = args;
|
|
1456
|
-
const content = join(workDir, '
|
|
1500
|
+
const content = join(workDir, '30-audio', `${runDate}_11_generate_content.json`);
|
|
1457
1501
|
timed(workDir, 'quality-control', () => {
|
|
1458
1502
|
const qc = metadataOf(readJson(content)).qc as Record<string, unknown> | undefined;
|
|
1459
1503
|
console.log('=== Voice Quality Control Report ===');
|
|
@@ -1511,10 +1555,10 @@ function stampId3Tags(audioFile: string, title: string, runDate: string, artist:
|
|
|
1511
1555
|
|
|
1512
1556
|
function dailyPublishPrep(args: string[]): void {
|
|
1513
1557
|
const [workDir = '', runDate = '', artist = '', album = ''] = args;
|
|
1514
|
-
const articlePath = join(workDir, '
|
|
1515
|
-
const audioPath = join(workDir, '
|
|
1516
|
-
const prepPath = join(workDir, '
|
|
1517
|
-
const coverPath = join(workDir, '
|
|
1558
|
+
const articlePath = join(workDir, '22-article', `${runDate}_06_article_content.json`);
|
|
1559
|
+
const audioPath = join(workDir, '30-audio', `${runDate}_11_generate_content.json`);
|
|
1560
|
+
const prepPath = join(workDir, '31-publish', `${runDate}_13_publish-prep_content.json`);
|
|
1561
|
+
const coverPath = join(workDir, '23-cover', `${runDate}_07_cover_cover.png`);
|
|
1518
1562
|
timed(workDir, 'publish-prep', () => {
|
|
1519
1563
|
const article = requireJson(articlePath, 'publish-prep');
|
|
1520
1564
|
const audioMeta = metadataOf(readJson(audioPath));
|
|
@@ -1553,8 +1597,8 @@ function dailyPublishPrep(args: string[]): void {
|
|
|
1553
1597
|
|
|
1554
1598
|
function dailyPublishSurfdash(args: string[]): void {
|
|
1555
1599
|
const [workDir = '', runDate = '', pluginsPath = ''] = args;
|
|
1556
|
-
const prep = join(workDir, '
|
|
1557
|
-
const result = join(workDir, '
|
|
1600
|
+
const prep = join(workDir, '31-publish', `${runDate}_13_publish-prep_content.json`);
|
|
1601
|
+
const result = join(workDir, '31-publish', `${runDate}_14_publish-surfdash_result.json`);
|
|
1558
1602
|
timed(workDir, 'publish-surfdash', () => {
|
|
1559
1603
|
if (targetPublished(result, 'surfdash-pub')) {
|
|
1560
1604
|
console.log(`publish-surfdash cached: surfdash target ok in ${result}`);
|
|
@@ -1583,16 +1627,16 @@ function dailyPublishSurfdash(args: string[]): void {
|
|
|
1583
1627
|
|
|
1584
1628
|
function dailyShowNotes(args: string[]): void {
|
|
1585
1629
|
const [workDir = '', runDate = '', pluginsPath = '', title = ''] = args;
|
|
1586
|
-
const article = join(workDir, '
|
|
1630
|
+
const article = join(workDir, '22-article', `${runDate}_06_article_content.json`);
|
|
1587
1631
|
// The preceding `publish-surfdash` state writes this artifact; `_17_publish_result.json` does
|
|
1588
1632
|
// not exist yet at `show-notes` time (the later `publish` state merges it), so reading it left
|
|
1589
1633
|
// PODCAST_PUB_ARTICLE_POST_PATH permanently empty (task 0137).
|
|
1590
|
-
const result = join(workDir, '
|
|
1591
|
-
const out = join(workDir, '
|
|
1592
|
-
const plan = join(workDir, '
|
|
1634
|
+
const result = join(workDir, '31-publish', `${runDate}_14_publish-surfdash_result.json`);
|
|
1635
|
+
const out = join(workDir, '31-publish', `${runDate}_15_show-notes_show-notes.md`);
|
|
1636
|
+
const plan = join(workDir, '21-plan', `${runDate}_03_plan_plan.json`);
|
|
1593
1637
|
// Generated-episode content carries `metadata.segments[]` — the source of real
|
|
1594
1638
|
// audio offsets for the show-notes timeline (jump-to-item timestamps).
|
|
1595
|
-
const audioContent = join(workDir, '
|
|
1639
|
+
const audioContent = join(workDir, '30-audio', `${runDate}_11_generate_content.json`);
|
|
1596
1640
|
timed(workDir, 'show-notes', () => {
|
|
1597
1641
|
const effective = readJson(article)?.title;
|
|
1598
1642
|
const titleArg = title !== '' ? title : typeof effective === 'string' ? effective : '';
|
|
@@ -1625,9 +1669,9 @@ function firstPostPath(parsed: Record<string, unknown> | null, target: string):
|
|
|
1625
1669
|
|
|
1626
1670
|
function dailyPublishPodcast(args: string[]): void {
|
|
1627
1671
|
const [workDir = '', runDate = '', pluginsPath = ''] = args;
|
|
1628
|
-
const prep = join(workDir, '
|
|
1629
|
-
const notes = join(workDir, '
|
|
1630
|
-
const result = join(workDir, '
|
|
1672
|
+
const prep = join(workDir, '31-publish', `${runDate}_13_publish-prep_content.json`);
|
|
1673
|
+
const notes = join(workDir, '31-publish', `${runDate}_15_show-notes_show-notes.md`);
|
|
1674
|
+
const result = join(workDir, '31-publish', `${runDate}_16_publish-podcast_result.json`);
|
|
1631
1675
|
timed(workDir, 'publish-podcast', () => {
|
|
1632
1676
|
if (targetPublished(result, 'podcast-pub')) {
|
|
1633
1677
|
console.log(`publish-podcast cached: podcast target ok in ${result}`);
|
|
@@ -1667,10 +1711,10 @@ function dailyPublishPodcast(args: string[]): void {
|
|
|
1667
1711
|
|
|
1668
1712
|
function dailyPublishClassify(args: string[]): void {
|
|
1669
1713
|
const [workDir = '', runDate = ''] = args;
|
|
1670
|
-
const result = join(workDir, '
|
|
1671
|
-
const statusFile = join(workDir, '
|
|
1672
|
-
const surfdash = join(workDir, '
|
|
1673
|
-
const podcast = join(workDir, '
|
|
1714
|
+
const result = join(workDir, '31-publish', `${runDate}_17_publish_result.json`);
|
|
1715
|
+
const statusFile = join(workDir, '31-publish', `${runDate}_17_publish_publish-status.txt`);
|
|
1716
|
+
const surfdash = join(workDir, '31-publish', `${runDate}_14_publish-surfdash_result.json`);
|
|
1717
|
+
const podcast = join(workDir, '31-publish', `${runDate}_16_publish-podcast_result.json`);
|
|
1674
1718
|
timed(workDir, 'publish', () => {
|
|
1675
1719
|
// Both per-target files must exist: the pre-0133 `jq -s … || echo '[]'` chain could not read
|
|
1676
1720
|
// a missing one (jq exits 2 after printing the slurped partial), which drove status to `none`.
|
|
@@ -1696,7 +1740,7 @@ function targetsOf(path: string): Record<string, unknown>[] {
|
|
|
1696
1740
|
|
|
1697
1741
|
function dailyPublishPartial(args: string[]): void {
|
|
1698
1742
|
const [workDir = '', runDate = ''] = args;
|
|
1699
|
-
const result = join(workDir, '
|
|
1743
|
+
const result = join(workDir, '31-publish', `${runDate}_17_publish_result.json`);
|
|
1700
1744
|
timed(workDir, 'publish-partial', () => {
|
|
1701
1745
|
const failed = targetsOf(result)
|
|
1702
1746
|
.filter((entry) => entry.ok !== true || entry.status !== 'published')
|
|
@@ -1727,8 +1771,8 @@ function dailyPatchSurfdash(args: string[]): void {
|
|
|
1727
1771
|
return;
|
|
1728
1772
|
}
|
|
1729
1773
|
timed(workDir, 'patch-surfdash', () => {
|
|
1730
|
-
const sdResult = join(workDir, '
|
|
1731
|
-
const podcastResult = join(workDir, '
|
|
1774
|
+
const sdResult = join(workDir, '31-publish', `${runDate}_14_publish-surfdash_result.json`);
|
|
1775
|
+
const podcastResult = join(workDir, '31-publish', `${runDate}_16_publish-podcast_result.json`);
|
|
1732
1776
|
if (!isFile(sdResult) || !isFile(podcastResult)) {
|
|
1733
1777
|
failWith(1, 'patch-surfdash: missing surfdash/podcast publish results — run the publish states first');
|
|
1734
1778
|
}
|
|
@@ -1755,7 +1799,7 @@ function dailyPatchSurfdash(args: string[]): void {
|
|
|
1755
1799
|
const slugDir = dirname(postPath);
|
|
1756
1800
|
const showNotesUrl = typeof pod.url === 'string' ? pod.url : '';
|
|
1757
1801
|
const coverUrl = ((pod.metadata as Record<string, unknown> | undefined)?.imageUrl as string | undefined) ?? '';
|
|
1758
|
-
const localCover = join(workDir, '
|
|
1802
|
+
const localCover = join(workDir, '23-cover', `${runDate}_07_cover_cover.png`);
|
|
1759
1803
|
const dateIso = `${runDate.slice(0, 4)}-${runDate.slice(4, 6)}-${runDate.slice(6, 8)}`;
|
|
1760
1804
|
const crossLink: Record<string, string> = {
|
|
1761
1805
|
zh: `🎧 本期已同步制作为播客节目,[点击收听《每日 AI 资讯 · ${dateIso}》](${showNotesUrl})。`,
|
|
@@ -1857,8 +1901,8 @@ function dailyRunReport(args: string[]): void {
|
|
|
1857
1901
|
] = args;
|
|
1858
1902
|
const reportMd = join(workDir, `${runDate}_19_run-report_report.md`);
|
|
1859
1903
|
const reportJson = join(workDir, `${runDate}_19_run-report_report.json`);
|
|
1860
|
-
const cands = join(workDir, '
|
|
1861
|
-
const candidates = join(workDir, '
|
|
1904
|
+
const cands = join(workDir, '21-plan', `${runDate}_19_run-report_episode-plan.json`);
|
|
1905
|
+
const candidates = join(workDir, '21-plan', `${runDate}_04_quality-control-content_candidates.json`);
|
|
1862
1906
|
timed(workDir, 'run-report', () => {
|
|
1863
1907
|
if (isFile(reportMd)) {
|
|
1864
1908
|
console.log(`run report cached: ${reportMd}`);
|
|
@@ -1880,20 +1924,20 @@ function dailyRunReport(args: string[]): void {
|
|
|
1880
1924
|
QC_MIN_IMPACT: minImpact,
|
|
1881
1925
|
NEWS_REPORT_REJECTED_FILE: join(
|
|
1882
1926
|
absWorkDir,
|
|
1883
|
-
'
|
|
1927
|
+
'21-plan',
|
|
1884
1928
|
`${runDate}_04_quality-control-content_candidates.rejected.json`,
|
|
1885
1929
|
),
|
|
1886
1930
|
NEWS_REPORT_TIMING_FILE: join(absWorkDir, 'step-timing.json'),
|
|
1887
1931
|
// 0139 R5b: the generate stage's QC audit, read best-effort (absent -> section omitted).
|
|
1888
|
-
NEWS_REPORT_QC_FILE: join(absWorkDir, '
|
|
1932
|
+
NEWS_REPORT_QC_FILE: join(absWorkDir, '30-audio', `${runDate}_11_generate_content.json`),
|
|
1889
1933
|
// 20260917 funnel: the plan stage's full selection trail (every fetched title +
|
|
1890
1934
|
// scores + disposition), rendered as the 选稿漏斗 section (absent -> omitted).
|
|
1891
|
-
NEWS_REPORT_PLAN_AUDIT_FILE: join(absWorkDir, '
|
|
1935
|
+
NEWS_REPORT_PLAN_AUDIT_FILE: join(absWorkDir, '21-plan', `${runDate}_03_plan_plan.json.audit.json`),
|
|
1892
1936
|
// 20260917 funnel: the blended fan-in input, rendered as the 语料构成
|
|
1893
1937
|
// source/category composition (absent -> omitted).
|
|
1894
|
-
NEWS_REPORT_BLENDED_FILE: join(absWorkDir, '
|
|
1938
|
+
NEWS_REPORT_BLENDED_FILE: join(absWorkDir, '10-ingest', `${runDate}_02_collect_blended.json`),
|
|
1895
1939
|
// F613 R5: the script stage's Content, read best-effort (absent -> 脚本来源 omitted).
|
|
1896
|
-
NEWS_REPORT_SCRIPT_FILE: join(absWorkDir, '
|
|
1940
|
+
NEWS_REPORT_SCRIPT_FILE: join(absWorkDir, '24-script', `${runDate}_08_script_content.json`),
|
|
1897
1941
|
NEWS_REPORT_DATE: runDate,
|
|
1898
1942
|
},
|
|
1899
1943
|
});
|
|
@@ -1914,8 +1958,8 @@ function dailyRunReport(args: string[]): void {
|
|
|
1914
1958
|
*/
|
|
1915
1959
|
function dailyTranslateLocalize(args: string[]): void {
|
|
1916
1960
|
const [workDir = '', runDate = '', pluginsPath = ''] = args;
|
|
1917
|
-
const result = join(workDir, '
|
|
1918
|
-
const zhFile = join(workDir, '
|
|
1961
|
+
const result = join(workDir, '31-publish', `${runDate}_17_publish_result.json`);
|
|
1962
|
+
const zhFile = join(workDir, '31-publish', `${runDate}_20_translate-localize_zh-post-path.txt`);
|
|
1919
1963
|
timed(workDir, 'translate-localize', () => {
|
|
1920
1964
|
const postPath = firstPostPath(readJson(result), 'surfdash-pub');
|
|
1921
1965
|
const zh = postPath.endsWith('index.md') ? postPath : '';
|
|
@@ -1924,10 +1968,10 @@ function dailyTranslateLocalize(args: string[]): void {
|
|
|
1924
1968
|
for (const target of ['en', 'ja'] as const) {
|
|
1925
1969
|
sted(
|
|
1926
1970
|
'write',
|
|
1927
|
-
join(workDir, '
|
|
1971
|
+
join(workDir, '31-publish', `${runDate}_20_translate-localize_${target}-post-path.txt`),
|
|
1928
1972
|
() =>
|
|
1929
1973
|
writeRaw(
|
|
1930
|
-
join(workDir, '
|
|
1974
|
+
join(workDir, '31-publish', `${runDate}_20_translate-localize_${target}-post-path.txt`),
|
|
1931
1975
|
'',
|
|
1932
1976
|
),
|
|
1933
1977
|
);
|
|
@@ -1936,17 +1980,17 @@ function dailyTranslateLocalize(args: string[]): void {
|
|
|
1936
1980
|
return;
|
|
1937
1981
|
}
|
|
1938
1982
|
for (const target of ['en', 'ja'] as const) {
|
|
1939
|
-
const markerFile = join(workDir, '
|
|
1983
|
+
const markerFile = join(workDir, '31-publish', `${runDate}_20_translate-localize_${target}-post-path.txt`);
|
|
1940
1984
|
const existing = zh.replace('/zh/', `/${target}/`);
|
|
1941
1985
|
if (isFile(existing)) {
|
|
1942
1986
|
sted('write', markerFile, () => writeRaw(markerFile, existing));
|
|
1943
1987
|
console.log(`${target} draft cached: ${existing}`);
|
|
1944
1988
|
continue;
|
|
1945
1989
|
}
|
|
1946
|
-
const scaffold = join(workDir, '
|
|
1990
|
+
const scaffold = join(workDir, '31-publish', `${runDate}_20_translate-localize_scaffold-${target}.json`);
|
|
1947
1991
|
const scaffoldResult = join(
|
|
1948
1992
|
workDir,
|
|
1949
|
-
'
|
|
1993
|
+
'31-publish',
|
|
1950
1994
|
`${runDate}_20_translate-localize_scaffold-${target}-result.json`,
|
|
1951
1995
|
);
|
|
1952
1996
|
sted('write', scaffold, () =>
|
|
@@ -1970,8 +2014,8 @@ function dailyTranslateLocalize(args: string[]): void {
|
|
|
1970
2014
|
|
|
1971
2015
|
function dailyTranslateSync(args: string[]): void {
|
|
1972
2016
|
const [workDir = '', runDate = '', pluginsPath = '', zhPathFile = ''] = args;
|
|
1973
|
-
const sync = join(workDir, '
|
|
1974
|
-
const syncResult = join(workDir, '
|
|
2017
|
+
const sync = join(workDir, '31-publish', `${runDate}_24_translate-sync_sync.json`);
|
|
2018
|
+
const syncResult = join(workDir, '31-publish', `${runDate}_24_translate-sync_sync-result.json`);
|
|
1975
2019
|
timed(workDir, 'translate-sync', () => {
|
|
1976
2020
|
// Reads the marker file itself — the collapsed `translate` state has no
|
|
1977
2021
|
// cross-state var to hand the path through.
|
|
@@ -2101,6 +2145,9 @@ if (import.meta.main)
|
|
|
2101
2145
|
case 'daily-publish-xhs':
|
|
2102
2146
|
dailyPublishXhs(rest);
|
|
2103
2147
|
break;
|
|
2148
|
+
case 'daily-publish-wechat':
|
|
2149
|
+
dailyPublishWechat(rest);
|
|
2150
|
+
break;
|
|
2104
2151
|
case 'daily-publish-classify':
|
|
2105
2152
|
dailyPublishClassify(rest);
|
|
2106
2153
|
break;
|
|
@@ -129,8 +129,11 @@ const CONTENT_ECHO_NOISE_RE = /[,。、::;;!!??…\s「」『』【】
|
|
|
129
129
|
/** Research scaffold labels the plan-translate stage must not emit as spoken copy. */
|
|
130
130
|
const CONTENT_SCAFFOLD_LABEL_RE =
|
|
131
131
|
/^(?:\*\*)?[「『]?(?:背景|潜在影响|可做角度|为什么现在值得注意|社区讨论)[」』]?(?:\*\*)?(?:[::,,]|\s|$)/;
|
|
132
|
-
/**
|
|
133
|
-
|
|
132
|
+
/** `标签:…` / `标签,ai、google` metadata tails — the plan item's tag line spoken aloud.
|
|
133
|
+
* Terminal-anchored: the tail runs to the end of the segment with no sentence-ending
|
|
134
|
+
* punctuation. Prose like 「一个推了两年的硬件标签,突然从新品上消失了。」 continues past the
|
|
135
|
+
* comma to a 。 and must not trip the gate (false positive of the 2026-09-28 daily run). */
|
|
136
|
+
const CONTENT_TAG_TAIL_RE = /标签[::,,][^\n。!?…]*$/;
|
|
134
137
|
/** `wss,//` / `https。//` — the translator replacing a URL colon with a Chinese comma. */
|
|
135
138
|
const CONTENT_URL_MANGLE_RE = /[,。]\/\//;
|
|
136
139
|
|