@gobing-ai/knowledge-kit 0.0.29 → 0.0.31
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 +114 -38
- package/package.json +1 -1
- package/plugins/generations/daily-article-gen/dist/index.js +24 -7
- package/plugins/generations/daily-article-gen/src/index.ts +36 -12
- package/plugins/generations/dailynews-gen/dist/index.js +20 -3
- package/plugins/generations/dailynews-gen/src/index.ts +7 -0
- package/plugins/generations/dailynews-gen/src/script-builder.ts +4 -2
- package/plugins/generations/dailynews-gen/src/script-plan.ts +21 -3
- package/plugins/generations/episode-plan-gen/dist/index.js +66 -5
- package/plugins/generations/episode-plan-gen/src/index.ts +103 -4
- package/plugins/generations/image-gen/dist/index.js +122 -20
- package/plugins/generations/image-gen/src/bytes.ts +106 -18
- package/plugins/generations/image-gen/src/index.ts +46 -6
- package/plugins/generations/image-gen/src/job.ts +6 -0
- package/plugins/generations/image-gen/src/providers/minimax.ts +12 -4
- package/plugins/generations/image-gen/src/providers/types.ts +12 -1
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/daily-voice-style.zh.md +5 -5
- package/plugins/kk/scripts/itc-stages.ts +21 -3
- package/plugins/kk/scripts/kk-workflow-stages.ts +199 -7
- package/plugins/kk/scripts/topic-boundary.json +3 -12
- package/plugins/kk/skills/content-judge/SKILL.md +32 -0
- package/plugins/kk/skills/content-judge/references/workflow-integration.md +10 -2
- package/plugins/kk/skills/publish/SKILL.md +1 -1
- package/plugins/kk/workflows/judge-gated-publish-example.yaml +18 -10
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +39 -14
- package/plugins/kk/workflows/kk-itc.yaml +21 -8
- package/plugins/publishings/xhs-pub/dist/index.js +1 -1
- package/plugins/publishings/xhs-pub/src/automation-guard.ts +2 -2
- package/plugins/publishings/xhs-pub/src/index.ts +1 -1
- package/plugins/publishings/xhs-pub/src/profile-check.ts +2 -2
- package/plugins/sp/lib/env.ts +0 -46
- package/plugins/sp/lib/inline-run.generated.mjs +0 -1489
- package/plugins/sp/scripts/batch-preflight.mjs +0 -346
- package/plugins/sp/scripts/batch-preflight.ts +0 -459
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +0 -615
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +0 -846
- package/plugins/sp/scripts/daily-summary/logger.ts +0 -28
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +0 -223
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +0 -367
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +0 -132
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +0 -169
- package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -171
- package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
- package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -285
- package/plugins/sp/scripts/feature-sync-bounded.ts +0 -478
- package/plugins/sp/scripts/feature-verification-steps.ts +0 -275
- package/plugins/sp/scripts/history-anatomy-cache.mjs +0 -902
- package/plugins/sp/scripts/history-anatomy-cache.ts +0 -1028
- package/plugins/sp/scripts/idea-handoff.mjs +0 -22
- package/plugins/sp/scripts/idea-handoff.ts +0 -44
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -185
- package/plugins/sp/scripts/inline-run-setup.ts +0 -722
- package/plugins/sp/scripts/pr-reviewing.mjs +0 -769
- package/plugins/sp/scripts/pr-reviewing.ts +0 -925
- package/plugins/sp/scripts/quality-gate.mjs +0 -179
- package/plugins/sp/scripts/quality-gate.ts +0 -217
- package/plugins/sp/scripts/script-contract-check.ts +0 -319
- package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
- package/plugins/sp/scripts/surface-drift-inventory.ts +0 -929
- package/plugins/sp/scripts/task-evidence-precheck.ts +0 -181
- package/plugins/sp/scripts/task-size-precheck.ts +0 -175
- package/plugins/sp/scripts/transition-shim-check.ts +0 -238
- package/plugins/sp/scripts/validate-commands.ts +0 -689
- package/plugins/sp/scripts/validate-flag-contracts.ts +0 -878
- package/plugins/sp/scripts/verify-answer-lint.ts +0 -530
- package/plugins/sp/scripts/workflow-step-profile.mjs +0 -316
- package/plugins/sp/scripts/workflow-step-profile.ts +0 -456
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +0 -190
- package/plugins/sp/scripts/wrapup-drift-probe.ts +0 -258
- package/plugins/sp/scripts/wrapup-steps.mjs +0 -444
- package/plugins/sp/scripts/wrapup-steps.ts +0 -552
package/dist/index.js
CHANGED
|
@@ -26530,6 +26530,11 @@ function killSubtree(pid) {
|
|
|
26530
26530
|
} catch {}
|
|
26531
26531
|
}
|
|
26532
26532
|
async function killProcessTree(pid) {
|
|
26533
|
+
if (process.platform !== "win32") {
|
|
26534
|
+
try {
|
|
26535
|
+
process.kill(-pid, "SIGKILL");
|
|
26536
|
+
} catch {}
|
|
26537
|
+
}
|
|
26533
26538
|
const sweep = () => {
|
|
26534
26539
|
for (const child of listChildren(pid)) {
|
|
26535
26540
|
killSubtree(child);
|
|
@@ -26549,7 +26554,8 @@ async function spawnPluginEntryAsync(opts) {
|
|
|
26549
26554
|
cwd: opts.cwd,
|
|
26550
26555
|
env: pluginEntryEnv(opts),
|
|
26551
26556
|
stdout: "pipe",
|
|
26552
|
-
stderr: "pipe"
|
|
26557
|
+
stderr: "pipe",
|
|
26558
|
+
detached: process.platform !== "win32"
|
|
26553
26559
|
});
|
|
26554
26560
|
if (opts.timeoutMs !== undefined) {
|
|
26555
26561
|
timer = setTimeout(() => {
|
|
@@ -27021,41 +27027,70 @@ function aggregate(entries) {
|
|
|
27021
27027
|
}
|
|
27022
27028
|
return { ok: summary.failed === 0, summary, targets: entries };
|
|
27023
27029
|
}
|
|
27024
|
-
async function
|
|
27025
|
-
const
|
|
27026
|
-
|
|
27027
|
-
const
|
|
27028
|
-
|
|
27029
|
-
|
|
27030
|
-
|
|
27031
|
-
|
|
27032
|
-
|
|
27033
|
-
|
|
27034
|
-
|
|
27035
|
-
|
|
27036
|
-
|
|
27037
|
-
|
|
27038
|
-
|
|
27039
|
-
|
|
27040
|
-
|
|
27041
|
-
|
|
27042
|
-
|
|
27043
|
-
|
|
27044
|
-
|
|
27045
|
-
|
|
27046
|
-
|
|
27047
|
-
|
|
27048
|
-
|
|
27049
|
-
|
|
27050
|
-
|
|
27051
|
-
|
|
27052
|
-
error: err instanceof Error ? err.message : String(err),
|
|
27053
|
-
durationMs: Date.now() - started
|
|
27054
|
-
};
|
|
27055
|
-
}
|
|
27056
|
-
entries.push(entry);
|
|
27030
|
+
async function dispatchOne(target, dispatch, timeoutMs) {
|
|
27031
|
+
const started = Date.now();
|
|
27032
|
+
try {
|
|
27033
|
+
const result = timeoutMs === undefined ? await dispatch(target) : await withTimeout(dispatch(target), timeoutMs, target);
|
|
27034
|
+
const durationMs = Date.now() - started;
|
|
27035
|
+
return result.ok ? {
|
|
27036
|
+
target,
|
|
27037
|
+
ok: true,
|
|
27038
|
+
status: "published",
|
|
27039
|
+
...result.url !== undefined ? { url: result.url } : {},
|
|
27040
|
+
...result.id !== undefined ? { id: result.id } : {},
|
|
27041
|
+
...result.metadata !== undefined ? { metadata: result.metadata } : {},
|
|
27042
|
+
durationMs
|
|
27043
|
+
} : {
|
|
27044
|
+
target,
|
|
27045
|
+
ok: false,
|
|
27046
|
+
status: "failed",
|
|
27047
|
+
error: result.error ?? result.message ?? "publish failed",
|
|
27048
|
+
durationMs
|
|
27049
|
+
};
|
|
27050
|
+
} catch (err) {
|
|
27051
|
+
return {
|
|
27052
|
+
target,
|
|
27053
|
+
ok: false,
|
|
27054
|
+
status: "failed",
|
|
27055
|
+
error: err instanceof Error ? err.message : String(err),
|
|
27056
|
+
durationMs: Date.now() - started
|
|
27057
|
+
};
|
|
27057
27058
|
}
|
|
27058
|
-
|
|
27059
|
+
}
|
|
27060
|
+
function withTimeout(promise2, timeoutMs, target) {
|
|
27061
|
+
let timer;
|
|
27062
|
+
const budget = new Promise((_resolve, reject) => {
|
|
27063
|
+
timer = setTimeout(() => reject(new Error(`target '${target}' exceeded its ${timeoutMs}ms budget`)), timeoutMs);
|
|
27064
|
+
});
|
|
27065
|
+
return Promise.race([promise2, budget]).finally(() => clearTimeout(timer));
|
|
27066
|
+
}
|
|
27067
|
+
async function fanout(content, targets, dispatch, options = {}) {
|
|
27068
|
+
const outcomes = new Map;
|
|
27069
|
+
const runOne = async (target) => {
|
|
27070
|
+
outcomes.set(target, await dispatchOne(target, dispatch, options.timeoutMs));
|
|
27071
|
+
};
|
|
27072
|
+
if (options.mode === "serial") {
|
|
27073
|
+
for (const target of targets)
|
|
27074
|
+
await runOne(target);
|
|
27075
|
+
} else {
|
|
27076
|
+
const serial = new Set(options.serialTargets ?? []);
|
|
27077
|
+
const parallel = targets.filter((target) => !serial.has(target));
|
|
27078
|
+
const solo = targets.filter((target) => serial.has(target));
|
|
27079
|
+
const jobs = options.jobs !== undefined && options.jobs > 0 ? options.jobs : parallel.length;
|
|
27080
|
+
let cursor2 = 0;
|
|
27081
|
+
const worker = async () => {
|
|
27082
|
+
while (cursor2 < parallel.length) {
|
|
27083
|
+
const target = parallel[cursor2++];
|
|
27084
|
+
if (target === undefined)
|
|
27085
|
+
return;
|
|
27086
|
+
await runOne(target);
|
|
27087
|
+
}
|
|
27088
|
+
};
|
|
27089
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(jobs, parallel.length)) }, worker));
|
|
27090
|
+
for (const target of solo)
|
|
27091
|
+
await runOne(target);
|
|
27092
|
+
}
|
|
27093
|
+
return aggregate(targets.map((target) => outcomes.get(target)).filter((entry) => entry !== undefined));
|
|
27059
27094
|
}
|
|
27060
27095
|
async function readContent(fs2, path3) {
|
|
27061
27096
|
if (!await fs2.exists(path3)) {
|
|
@@ -27139,10 +27174,14 @@ async function readAggregateResult(fs2, path3) {
|
|
|
27139
27174
|
function parseArgs2(argv) {
|
|
27140
27175
|
const targets = [];
|
|
27141
27176
|
const pluginsPath = [];
|
|
27177
|
+
const serialTargets = [];
|
|
27142
27178
|
let contentPath;
|
|
27143
27179
|
let outPath;
|
|
27144
27180
|
let retryFromPath;
|
|
27145
27181
|
let pluginsRoot;
|
|
27182
|
+
let serial = false;
|
|
27183
|
+
let jobs;
|
|
27184
|
+
let targetTimeoutMs;
|
|
27146
27185
|
for (let i = 0;i < argv.length; i++) {
|
|
27147
27186
|
const arg = argv[i];
|
|
27148
27187
|
if (arg === "--content") {
|
|
@@ -27193,6 +27232,34 @@ function parseArgs2(argv) {
|
|
|
27193
27232
|
throw new Error("--plugins-path requires a directory path");
|
|
27194
27233
|
}
|
|
27195
27234
|
pluginsPath.push(value);
|
|
27235
|
+
} else if (arg === "--serial") {
|
|
27236
|
+
serial = true;
|
|
27237
|
+
} else if (arg === "--jobs") {
|
|
27238
|
+
const value = argv[++i];
|
|
27239
|
+
if (value === undefined) {
|
|
27240
|
+
throw new Error("--jobs requires a positive integer");
|
|
27241
|
+
}
|
|
27242
|
+
const parsed = Number(value);
|
|
27243
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
27244
|
+
throw new Error(`--jobs requires a positive integer, got '${value}'`);
|
|
27245
|
+
}
|
|
27246
|
+
jobs = parsed;
|
|
27247
|
+
} else if (arg === "--serial-target") {
|
|
27248
|
+
const value = argv[++i];
|
|
27249
|
+
if (value === undefined) {
|
|
27250
|
+
throw new Error("--serial-target requires a plugin name");
|
|
27251
|
+
}
|
|
27252
|
+
serialTargets.push(value);
|
|
27253
|
+
} else if (arg === "--target-timeout") {
|
|
27254
|
+
const value = argv[++i];
|
|
27255
|
+
if (value === undefined) {
|
|
27256
|
+
throw new Error("--target-timeout requires milliseconds");
|
|
27257
|
+
}
|
|
27258
|
+
const parsed = Number(value);
|
|
27259
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
27260
|
+
throw new Error(`--target-timeout requires a positive integer in milliseconds, got '${value}'`);
|
|
27261
|
+
}
|
|
27262
|
+
targetTimeoutMs = parsed;
|
|
27196
27263
|
} else {
|
|
27197
27264
|
throw new Error(`Unknown argument: ${arg}`);
|
|
27198
27265
|
}
|
|
@@ -27212,7 +27279,11 @@ function parseArgs2(argv) {
|
|
|
27212
27279
|
outPath,
|
|
27213
27280
|
...retryFromPath !== undefined ? { retryFromPath } : {},
|
|
27214
27281
|
...pluginsRoot !== undefined ? { pluginsRoot } : {},
|
|
27215
|
-
...pluginsPath.length > 0 ? { pluginsPath } : {}
|
|
27282
|
+
...pluginsPath.length > 0 ? { pluginsPath } : {},
|
|
27283
|
+
...serial ? { serial } : {},
|
|
27284
|
+
...jobs !== undefined ? { jobs } : {},
|
|
27285
|
+
...serialTargets.length > 0 ? { serialTargets } : {},
|
|
27286
|
+
...targetTimeoutMs !== undefined ? { targetTimeoutMs } : {}
|
|
27216
27287
|
};
|
|
27217
27288
|
}
|
|
27218
27289
|
async function pluginDispatch(target, contentPath, discovery) {
|
|
@@ -27315,7 +27386,12 @@ async function runFanout(argv, deps = {}) {
|
|
|
27315
27386
|
if (deps.dispatch === undefined && !validateTargets(targets, discovery)) {
|
|
27316
27387
|
return EXIT_INPUT2;
|
|
27317
27388
|
}
|
|
27318
|
-
const fresh = await fanout(content, targets, dispatch
|
|
27389
|
+
const fresh = await fanout(content, targets, dispatch, {
|
|
27390
|
+
...opts.serial === true ? { mode: "serial" } : {},
|
|
27391
|
+
...opts.jobs !== undefined ? { jobs: opts.jobs } : {},
|
|
27392
|
+
...opts.serialTargets !== undefined ? { serialTargets: opts.serialTargets } : {},
|
|
27393
|
+
...opts.targetTimeoutMs !== undefined ? { timeoutMs: opts.targetTimeoutMs } : {}
|
|
27394
|
+
});
|
|
27319
27395
|
const result = prior !== undefined ? aggregate([...skipped, ...retainedFailed, ...fresh.targets]) : fresh;
|
|
27320
27396
|
try {
|
|
27321
27397
|
await fs2.writeFile(resolve4(opts.outPath), `${JSON.stringify(result, null, 2)}
|
|
@@ -27399,7 +27475,7 @@ function withFaninOptions(cmd) {
|
|
|
27399
27475
|
return cmd.option("--in <path>", "Input Doc[] JSON file (repeatable)", collectRepeatable, []).option("--source <plugin>:<file>", "Ingestion source to spawn concurrently: plugin name and its input file (repeatable)", collectRepeatable, []).option("--source-timeout <ms>", "Per-source timeout in milliseconds (default 1800000)").option("--source-timeout-override <plugin>:<ms>", "Per-source timeout override for one named --source plugin, e.g. horizon-ingest:2700000 (repeatable; wins over --source-timeout/default)", collectRepeatable, []).requiredOption("--out <path>", "Output merged Doc[] JSON file path").option("--plugins-root <dir>", "Exclusive root containing plugins (isolation/tests only)").option("--plugins-path <dir>", "Extra plugin root to union with defaults (repeatable)", collectRepeatable, []);
|
|
27400
27476
|
}
|
|
27401
27477
|
function withFanoutOptions(cmd) {
|
|
27402
|
-
return cmd.requiredOption("--content <path>", "Source ContentSchema JSON file path").option("--target <plugin>", "Target publish plugin name (repeatable)", collectRepeatable, []).requiredOption("--out <path>", "Aggregate Result JSON file path").option("--retry-from <path>", "Prior Result JSON file to retry only previously failed targets").option("--plugins-root <dir>", "Exclusive root containing plugins (isolation/tests only)").option("--plugins-path <dir>", "Extra plugin root to union with defaults (repeatable)", collectRepeatable, []);
|
|
27478
|
+
return cmd.requiredOption("--content <path>", "Source ContentSchema JSON file path").option("--target <plugin>", "Target publish plugin name (repeatable)", collectRepeatable, []).requiredOption("--out <path>", "Aggregate Result JSON file path").option("--retry-from <path>", "Prior Result JSON file to retry only previously failed targets").option("--serial", "Dispatch the parallel batch one target at a time (pre-2026-10-03 behaviour)").option("--jobs <n>", "Concurrent-start bound for the parallel batch (default: every target at once)").option("--serial-target <plugin>", "Target kept out of the parallel batch and run alone afterwards, e.g. a headed browser channel or a shared checkout (repeatable)", collectRepeatable, []).option("--target-timeout <ms>", "Per-target wall-clock budget; an overrun is recorded failed and siblings continue").option("--plugins-root <dir>", "Exclusive root containing plugins (isolation/tests only)").option("--plugins-path <dir>", "Extra plugin root to union with defaults (repeatable)", collectRepeatable, []);
|
|
27403
27479
|
}
|
|
27404
27480
|
function registerExecutorCommand(program2) {
|
|
27405
27481
|
const executor = program2.command("executor").description("Run knowledge-kit plugin steps (invoke, fan-in, fan-out)");
|
package/package.json
CHANGED
|
@@ -22035,17 +22035,15 @@ ${metaLine ? `${metaLine}
|
|
|
22035
22035
|
|
|
22036
22036
|
` : ""}${stripScaffold(doc2.body ?? "").trim()}`;
|
|
22037
22037
|
}
|
|
22038
|
-
function episodeTitle(
|
|
22039
|
-
|
|
22040
|
-
const theme = lead !== "" ? lead : "\u5DE5\u5177\u3001\u8BBA\u6587\u4E0E\u5B9E\u8DF5";
|
|
22041
|
-
return `AI \u4E71\u7096\uFF5C${theme} \xB7 ${date6}`;
|
|
22038
|
+
function episodeTitle(date6) {
|
|
22039
|
+
return `AI \u4E71\u7096 \xB7 ${date6}`;
|
|
22042
22040
|
}
|
|
22043
22041
|
function renderDailyArticle(docs, date6) {
|
|
22044
22042
|
const items = docs.map(renderDoc).join(`
|
|
22045
22043
|
|
|
22046
22044
|
`);
|
|
22047
22045
|
const intro = docs.length > 0 ? `\u300CAI \u4E71\u7096\u300D\u662F\u300C\u54B8\u8BDD\u54B8\u8BF4\u300D\u7684 AI \u6280\u672F\u5206\u4EAB\u680F\u76EE\uFF1A\u5DE5\u5177\u3001\u8BBA\u6587\u4E0E\u5B9E\u8DF5\u968F\u804A\u3002\u672C\u671F\u5171 ${docs.length} \u6761\u5185\u5BB9\uFF0C\u5747\u9644\u53EF\u70B9\u51FB\u6765\u6E90\u94FE\u63A5\u3002` : "\u300CAI \u4E71\u7096\u300D\u662F\u300C\u54B8\u8BDD\u54B8\u8BF4\u300D\u7684 AI \u6280\u672F\u5206\u4EAB\u680F\u76EE\uFF1A\u5DE5\u5177\u3001\u8BBA\u6587\u4E0E\u5B9E\u8DF5\u968F\u804A\u3002\u672C\u671F\u6CA1\u6709\u8DB3\u591F\u624E\u5B9E\u7684\u53EF\u804A\u5185\u5BB9\u3002";
|
|
22048
|
-
return `# ${episodeTitle(
|
|
22046
|
+
return `# ${episodeTitle(date6)}
|
|
22049
22047
|
|
|
22050
22048
|
${intro}
|
|
22051
22049
|
|
|
@@ -22061,8 +22059,24 @@ function tagsFor(docs) {
|
|
|
22061
22059
|
return [AI_NEWS_TAG, ...existing.filter((t) => t !== AI_NEWS_TAG)];
|
|
22062
22060
|
}
|
|
22063
22061
|
var LEADING_H1_RE = /^#(?!#)\s*[^\n]+\n+/.source;
|
|
22062
|
+
var NOTE_EXCLUDED_RE = /\u672A\u5199\u5165[\uFF1A:](.*)/g;
|
|
22063
|
+
var HEX_ID_RE = /(?:[a-zA-Z0-9_-]+:)?[0-9a-f]{16}/g;
|
|
22064
|
+
function excludedIdsFromNote(body) {
|
|
22065
|
+
const ids = new Set;
|
|
22066
|
+
if (!body)
|
|
22067
|
+
return ids;
|
|
22068
|
+
for (const note of body.matchAll(NOTE_EXCLUDED_RE)) {
|
|
22069
|
+
for (const id of (note[1] ?? "").matchAll(HEX_ID_RE)) {
|
|
22070
|
+
ids.add(id[0]);
|
|
22071
|
+
const colon = id[0].indexOf(":");
|
|
22072
|
+
if (colon !== -1)
|
|
22073
|
+
ids.add(id[0].slice(colon + 1));
|
|
22074
|
+
}
|
|
22075
|
+
}
|
|
22076
|
+
return ids;
|
|
22077
|
+
}
|
|
22064
22078
|
function docsToContent(docs, date6, bodyOverride) {
|
|
22065
|
-
const title = episodeTitle(
|
|
22079
|
+
const title = episodeTitle(date6);
|
|
22066
22080
|
let body = renderDailyArticle(docs, date6);
|
|
22067
22081
|
if (bodyOverride) {
|
|
22068
22082
|
const stripped = stripScaffold(bodyOverride.trim()).trim().replace(new RegExp(LEADING_H1_RE), "");
|
|
@@ -22096,7 +22110,9 @@ async function processGeneratorIO(inputPath, outputPath) {
|
|
|
22096
22110
|
if (bodyOverride.trim() === "")
|
|
22097
22111
|
bodyOverride = undefined;
|
|
22098
22112
|
}
|
|
22099
|
-
const
|
|
22113
|
+
const excluded = excludedIdsFromNote(bodyOverride);
|
|
22114
|
+
const kept = excluded.size === 0 ? docs : docs.filter((d) => !excluded.has(d.id) && !(d.id.includes(":") && excluded.has(d.id.slice(d.id.indexOf(":") + 1))));
|
|
22115
|
+
const content = docsToContent(kept, date6, bodyOverride);
|
|
22100
22116
|
const fs = createNodeFileSystem();
|
|
22101
22117
|
const outDir = dirname2(outputPath);
|
|
22102
22118
|
if (outDir && outDir !== ".")
|
|
@@ -22132,6 +22148,7 @@ export {
|
|
|
22132
22148
|
renderDailyArticle,
|
|
22133
22149
|
processGeneratorIO,
|
|
22134
22150
|
main2 as main,
|
|
22151
|
+
excludedIdsFromNote,
|
|
22135
22152
|
episodeTitle,
|
|
22136
22153
|
docsToContent
|
|
22137
22154
|
};
|
|
@@ -103,16 +103,14 @@ function renderDoc(doc: Doc, index: number): string {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
|
-
* Episode title for the 「AI 乱炖」 column
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* agent-written and template article paths, ID3, RSS and show notes all
|
|
110
|
-
*
|
|
106
|
+
* Episode title for the 「AI 乱炖」 column: fixed `AI 乱炖 · YYYY-MM-DD` (2026-10-02 operator
|
|
107
|
+
* simplification, superseding the 0189 R1/R4 `AI 乱炖|<lead item> · date` pattern). The date
|
|
108
|
+
* alone identifies the episode; the day's stories live in the show notes, cards and post body.
|
|
109
|
+
* Pure function so the agent-written and template article paths, ID3, RSS and show notes all
|
|
110
|
+
* derive one identical title.
|
|
111
111
|
*/
|
|
112
|
-
export function episodeTitle(
|
|
113
|
-
|
|
114
|
-
const theme = lead !== '' ? lead : '工具、论文与实践';
|
|
115
|
-
return `AI 乱炖|${theme} · ${date}`;
|
|
112
|
+
export function episodeTitle(date: string): string {
|
|
113
|
+
return `AI 乱炖 · ${date}`;
|
|
116
114
|
}
|
|
117
115
|
|
|
118
116
|
/** Render the article markdown for a merged Doc[] (pure — exported for tests). */
|
|
@@ -122,7 +120,7 @@ export function renderDailyArticle(docs: Doc[], date: string): string {
|
|
|
122
120
|
docs.length > 0
|
|
123
121
|
? `「AI 乱炖」是「咸话咸说」的 AI 技术分享栏目:工具、论文与实践随聊。本期共 ${docs.length} 条内容,均附可点击来源链接。`
|
|
124
122
|
: '「AI 乱炖」是「咸话咸说」的 AI 技术分享栏目:工具、论文与实践随聊。本期没有足够扎实的可聊内容。';
|
|
125
|
-
return `# ${episodeTitle(
|
|
123
|
+
return `# ${episodeTitle(date)}\n\n${intro}\n\n${items}\n`;
|
|
126
124
|
}
|
|
127
125
|
|
|
128
126
|
/** Single-source daily tag: stamped on every Content this generator emits (task 0119). */
|
|
@@ -140,9 +138,27 @@ function tagsFor(docs: Doc[]): string[] {
|
|
|
140
138
|
/** A leading H1 line — agent bodies used to carry their own title; the canonical title is computed (task 0189 R4). */
|
|
141
139
|
const LEADING_H1_RE = /^#(?!#)\s*[^\n]+\n+/.source;
|
|
142
140
|
|
|
141
|
+
/** ids the article-write note marks as dropped on the topic boundary, e.g. 「…未写入:<id>(原因)、<id>(原因)」. */
|
|
142
|
+
const NOTE_EXCLUDED_RE = /未写入[::](.*)/g;
|
|
143
|
+
const HEX_ID_RE = /(?:[a-zA-Z0-9_-]+:)?[0-9a-f]{16}/g;
|
|
144
|
+
|
|
145
|
+
/** Parse the boundary-note ids out of an article body so references/metadata never cite excluded docs (judge finding 2026-10-02). */
|
|
146
|
+
export function excludedIdsFromNote(body: string | undefined): Set<string> {
|
|
147
|
+
const ids = new Set<string>();
|
|
148
|
+
if (!body) return ids;
|
|
149
|
+
for (const note of body.matchAll(NOTE_EXCLUDED_RE)) {
|
|
150
|
+
for (const id of (note[1] ?? '').matchAll(HEX_ID_RE)) {
|
|
151
|
+
ids.add(id[0]);
|
|
152
|
+
const colon = id[0].indexOf(':');
|
|
153
|
+
if (colon !== -1) ids.add(id[0].slice(colon + 1));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return ids;
|
|
157
|
+
}
|
|
158
|
+
|
|
143
159
|
/** Build the article Content from a merged Doc[]; `bodyOverride` replaces the mechanical render (agent-written prose), still scaffold-stripped as a safety net. */
|
|
144
160
|
export function docsToContent(docs: Doc[], date: string, bodyOverride?: string): Content {
|
|
145
|
-
const title = episodeTitle(
|
|
161
|
+
const title = episodeTitle(date);
|
|
146
162
|
let body = renderDailyArticle(docs, date);
|
|
147
163
|
if (bodyOverride) {
|
|
148
164
|
// The canonical episode title is computed from the lead item; a leading H1 the agent may
|
|
@@ -174,7 +190,15 @@ export async function processGeneratorIO(inputPath: string, outputPath: string):
|
|
|
174
190
|
bodyOverride = await Bun.file(bodyFile).text();
|
|
175
191
|
if (bodyOverride.trim() === '') bodyOverride = undefined;
|
|
176
192
|
}
|
|
177
|
-
const
|
|
193
|
+
const excluded = excludedIdsFromNote(bodyOverride);
|
|
194
|
+
const kept =
|
|
195
|
+
excluded.size === 0
|
|
196
|
+
? docs
|
|
197
|
+
: docs.filter(
|
|
198
|
+
(d) =>
|
|
199
|
+
!excluded.has(d.id) && !(d.id.includes(':') && excluded.has(d.id.slice(d.id.indexOf(':') + 1))),
|
|
200
|
+
);
|
|
201
|
+
const content = docsToContent(kept, date, bodyOverride);
|
|
178
202
|
const fs = createNodeFileSystem();
|
|
179
203
|
const outDir = dirname(outputPath);
|
|
180
204
|
if (outDir && outDir !== '.') await fs.ensureDir(outDir);
|
|
@@ -21994,7 +21994,7 @@ function normalizeBroadcastText(raw, isZh) {
|
|
|
21994
21994
|
text = text.replace(/\s+/g, " ");
|
|
21995
21995
|
if (isZh) {
|
|
21996
21996
|
text = text.replace(/[''\u2018\u2019]/g, "");
|
|
21997
|
-
text = text.replace(/[:\uFF1A]/g, "\uFF0C");
|
|
21997
|
+
text = text.replace(/[:\uFF1A](?!\/\/)/g, "\uFF0C");
|
|
21998
21998
|
text = text.replace(/[;\uFF1B]/g, "\u3002");
|
|
21999
21999
|
text = text.replace(/--+/g, "\uFF0C");
|
|
22000
22000
|
text = text.replace(/\.{2,}/g, "\u3002");
|
|
@@ -22344,11 +22344,16 @@ var ScriptPlanItemSchema = exports_external.object({
|
|
|
22344
22344
|
lead: exports_external.string().min(1),
|
|
22345
22345
|
insight: exports_external.string().min(1)
|
|
22346
22346
|
});
|
|
22347
|
+
var ScriptPlanExclusionSchema = exports_external.object({
|
|
22348
|
+
id: exports_external.string().min(1),
|
|
22349
|
+
reason: exports_external.string().min(1)
|
|
22350
|
+
});
|
|
22347
22351
|
var ScriptPlanSchema = exports_external.object({
|
|
22348
22352
|
version: exports_external.literal(1),
|
|
22349
22353
|
intro: exports_external.string().min(1),
|
|
22350
22354
|
outro: exports_external.string().min(1),
|
|
22351
|
-
items: exports_external.array(ScriptPlanItemSchema)
|
|
22355
|
+
items: exports_external.array(ScriptPlanItemSchema),
|
|
22356
|
+
excluded: exports_external.array(ScriptPlanExclusionSchema).optional()
|
|
22352
22357
|
});
|
|
22353
22358
|
var StyleContractSchema = exports_external.object({
|
|
22354
22359
|
caps: exports_external.object({
|
|
@@ -22386,8 +22391,15 @@ function validateScriptPlan(plan, docs, style) {
|
|
|
22386
22391
|
errors5.push({ itemId: item.id, rule: "unknown-id" });
|
|
22387
22392
|
}
|
|
22388
22393
|
const plannedIds = new Set(valid.items.map((item) => item.id));
|
|
22394
|
+
const excludedIds = new Set((valid.excluded ?? []).map((ex) => ex.id));
|
|
22395
|
+
for (const ex of valid.excluded ?? []) {
|
|
22396
|
+
if (!docIds.has(ex.id))
|
|
22397
|
+
errors5.push({ itemId: ex.id, rule: "unknown-id" });
|
|
22398
|
+
if (plannedIds.has(ex.id))
|
|
22399
|
+
errors5.push({ itemId: ex.id, rule: "conflict-id" });
|
|
22400
|
+
}
|
|
22389
22401
|
for (const doc2 of docs) {
|
|
22390
|
-
if (!plannedIds.has(doc2.id))
|
|
22402
|
+
if (!plannedIds.has(doc2.id) && !excludedIds.has(doc2.id))
|
|
22391
22403
|
errors5.push({ itemId: doc2.id, rule: "missing-item" });
|
|
22392
22404
|
}
|
|
22393
22405
|
if (valid.intro.length > style.caps.intro)
|
|
@@ -22493,6 +22505,11 @@ async function processGeneratorIO(inputPath, outputPath, options) {
|
|
|
22493
22505
|
}
|
|
22494
22506
|
const isZh = (options?.language || "zh").toLowerCase() === "zh";
|
|
22495
22507
|
const planOutcome = isZh ? await resolveScriptPlan(fs, docs) : { scriptSource: "template", scriptPlanErrors: [] };
|
|
22508
|
+
const excludedIds = new Set(planOutcome.scriptPlan?.excluded?.map((ex) => ex.id) ?? []);
|
|
22509
|
+
if (excludedIds.size > 0) {
|
|
22510
|
+
echoError(`dailynews-gen: topic boundary excludes ${excludedIds.size} doc(s): ${[...excludedIds].join(", ")}`);
|
|
22511
|
+
docs = docs.filter((doc2) => !excludedIds.has(doc2.id));
|
|
22512
|
+
}
|
|
22496
22513
|
const date6 = options?.date ?? broadcastDateFromEnv(process.env.DAILYNEWS_DATE);
|
|
22497
22514
|
const script = buildNewsVoiceScript(docs, { ...options, date: date6, scriptPlan: planOutcome.scriptPlan });
|
|
22498
22515
|
const yamlBody = formatVoiceScriptToYaml(script);
|
|
@@ -127,6 +127,13 @@ export async function processGeneratorIO(
|
|
|
127
127
|
const planOutcome = isZh
|
|
128
128
|
? await resolveScriptPlan(fs, docs)
|
|
129
129
|
: { scriptSource: 'template' as const, scriptPlanErrors: [] as ScriptPlanError[] };
|
|
130
|
+
// Topic boundary (task 0189): docs the plan declares excluded are not spoken, not counted,
|
|
131
|
+
// and not referenced — title, storyCount, and references must match what is actually said.
|
|
132
|
+
const excludedIds = new Set(planOutcome.scriptPlan?.excluded?.map((ex) => ex.id) ?? []);
|
|
133
|
+
if (excludedIds.size > 0) {
|
|
134
|
+
echoError(`dailynews-gen: topic boundary excludes ${excludedIds.size} doc(s): ${[...excludedIds].join(', ')}`);
|
|
135
|
+
docs = docs.filter((doc) => !excludedIds.has(doc.id));
|
|
136
|
+
}
|
|
130
137
|
// Explicit option wins; else DAILYNEWS_DATE (workflow backfill/retry); else the builder's now.
|
|
131
138
|
const date = options?.date ?? broadcastDateFromEnv(process.env.DAILYNEWS_DATE);
|
|
132
139
|
const script = buildNewsVoiceScript(docs, { ...options, date, scriptPlan: planOutcome.scriptPlan });
|
|
@@ -119,8 +119,10 @@ export function normalizeBroadcastText(raw: string, isZh: boolean): string {
|
|
|
119
119
|
|
|
120
120
|
if (isZh) {
|
|
121
121
|
text = text.replace(/[''‘’]/g, '');
|
|
122
|
-
// Replace English punctuation with Chinese pauses
|
|
123
|
-
|
|
122
|
+
// Replace English punctuation with Chinese pauses. A URL scheme's colon (`https://`,
|
|
123
|
+
// `wss://`) is the exception: spoken as `,//` the URL is mangled, and the content gate
|
|
124
|
+
// (`validate-voicescript.ts` CONTENT_URL_MANGLE_RE) rejects the whole run over it.
|
|
125
|
+
text = text.replace(/[::](?!\/\/)/g, ',');
|
|
124
126
|
text = text.replace(/[;;]/g, '。');
|
|
125
127
|
text = text.replace(/--+/g, ',');
|
|
126
128
|
text = text.replace(/\.{2,}/g, '。');
|
|
@@ -12,6 +12,12 @@ export interface ScriptPlan {
|
|
|
12
12
|
intro: string;
|
|
13
13
|
outro: string;
|
|
14
14
|
items: { id: string; lead: string; insight: string }[];
|
|
15
|
+
/**
|
|
16
|
+
* Docs deliberately not spoken (topic-boundary exclusions, task 0189). An id here is
|
|
17
|
+
* exempt from `missing-item`; listing an id in both `items` and `excluded` is a
|
|
18
|
+
* `conflict-id` error, and an unknown id is `unknown-id` like any plan item.
|
|
19
|
+
*/
|
|
20
|
+
excluded?: { id: string; reason: string }[];
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
/**
|
|
@@ -41,11 +47,17 @@ const ScriptPlanItemSchema = z.object({
|
|
|
41
47
|
insight: z.string().min(1),
|
|
42
48
|
});
|
|
43
49
|
|
|
50
|
+
const ScriptPlanExclusionSchema = z.object({
|
|
51
|
+
id: z.string().min(1),
|
|
52
|
+
reason: z.string().min(1),
|
|
53
|
+
});
|
|
54
|
+
|
|
44
55
|
const ScriptPlanSchema = z.object({
|
|
45
56
|
version: z.literal(1),
|
|
46
57
|
intro: z.string().min(1),
|
|
47
58
|
outro: z.string().min(1),
|
|
48
59
|
items: z.array(ScriptPlanItemSchema),
|
|
60
|
+
excluded: z.array(ScriptPlanExclusionSchema).optional(),
|
|
49
61
|
});
|
|
50
62
|
|
|
51
63
|
const StyleContractSchema = z.object({
|
|
@@ -86,8 +98,9 @@ export function parseStyleContract(markdown: string): StyleContract {
|
|
|
86
98
|
* one warn line names every violation, so a rejected plan is diagnosable in one read:
|
|
87
99
|
*
|
|
88
100
|
* - `schema`: wrong shape or version (`itemId: episode`)
|
|
89
|
-
* - `unknown-id`: a plan item id that is not a doc
|
|
90
|
-
* - `missing-item`: a doc with no plan item
|
|
101
|
+
* - `unknown-id`: a plan item (or exclusion) id that is not a doc
|
|
102
|
+
* - `missing-item`: a doc with no plan item and no exclusion
|
|
103
|
+
* - `conflict-id`: an id listed in both `items` and `excluded`
|
|
91
104
|
* - `cap:<field>`: a field over its cap (`intro`/`outro` report `itemId: episode`)
|
|
92
105
|
* - `banned:<phrase>`: a field containing a banned phrase
|
|
93
106
|
* - `duplicate-insight`: the same insight text on two items in one episode
|
|
@@ -107,8 +120,13 @@ export function validateScriptPlan(plan: unknown, docs: Doc[], style: StyleContr
|
|
|
107
120
|
if (!docIds.has(item.id)) errors.push({ itemId: item.id, rule: 'unknown-id' });
|
|
108
121
|
}
|
|
109
122
|
const plannedIds = new Set(valid.items.map((item) => item.id));
|
|
123
|
+
const excludedIds = new Set((valid.excluded ?? []).map((ex) => ex.id));
|
|
124
|
+
for (const ex of valid.excluded ?? []) {
|
|
125
|
+
if (!docIds.has(ex.id)) errors.push({ itemId: ex.id, rule: 'unknown-id' });
|
|
126
|
+
if (plannedIds.has(ex.id)) errors.push({ itemId: ex.id, rule: 'conflict-id' });
|
|
127
|
+
}
|
|
110
128
|
for (const doc of docs) {
|
|
111
|
-
if (!plannedIds.has(doc.id)) errors.push({ itemId: doc.id, rule: 'missing-item' });
|
|
129
|
+
if (!plannedIds.has(doc.id) && !excludedIds.has(doc.id)) errors.push({ itemId: doc.id, rule: 'missing-item' });
|
|
112
130
|
}
|
|
113
131
|
|
|
114
132
|
if (valid.intro.length > style.caps.intro) errors.push({ itemId: 'episode', rule: 'cap:intro' });
|
|
@@ -22118,8 +22118,18 @@ function passesGates(doc2, planDate) {
|
|
|
22118
22118
|
return true;
|
|
22119
22119
|
return published >= Date.parse(planDate) - FRESHNESS_DAYS * 86400000;
|
|
22120
22120
|
}
|
|
22121
|
+
var SCAFFOLD_HEADING_RE = /^\s*\*\*\s*[\u300C\u300E][^\u300D\u300F\n]{1,80}[\u300D\u300F]\s*\*\*\s*[\uFF1A:]?(?:\s+|$)/;
|
|
22121
22122
|
function cleanBody(body) {
|
|
22122
|
-
|
|
22123
|
+
const kept = [];
|
|
22124
|
+
for (const line of body.split(`
|
|
22125
|
+
`)) {
|
|
22126
|
+
const stripped = line.replace(SCAFFOLD_HEADING_RE, "");
|
|
22127
|
+
if (stripped.trim() === "" && line.trim() !== "")
|
|
22128
|
+
continue;
|
|
22129
|
+
kept.push(stripped);
|
|
22130
|
+
}
|
|
22131
|
+
return kept.join(`
|
|
22132
|
+
`).replace(/\u2B50\uFE0F\s*[\d.]+\s*\/\s*10/g, "").replace(/^#{1,6}\s*\[[^\]]*\]\([^)]*\)\s*$/gm, "").replace(/\n{3,}/g, `
|
|
22123
22133
|
|
|
22124
22134
|
`).trim();
|
|
22125
22135
|
}
|
|
@@ -22430,7 +22440,8 @@ var DEFAULT_FILTER_CONFIG = {
|
|
|
22430
22440
|
minQuality: 2,
|
|
22431
22441
|
minImportance: 3,
|
|
22432
22442
|
minUrgency: 2,
|
|
22433
|
-
minImpact: 1
|
|
22443
|
+
minImpact: 1,
|
|
22444
|
+
minEpisodeItems: 8
|
|
22434
22445
|
};
|
|
22435
22446
|
function parseFilterThreshold(raw, name, fallback) {
|
|
22436
22447
|
if (raw === undefined || raw.trim() === "")
|
|
@@ -22443,6 +22454,17 @@ function parseFilterThreshold(raw, name, fallback) {
|
|
|
22443
22454
|
throw new Error(`${name}=${parsed} is outside 0\u20135 (fail loud, no clamping)`);
|
|
22444
22455
|
return parsed;
|
|
22445
22456
|
}
|
|
22457
|
+
function parseFloorCount(raw, name, fallback) {
|
|
22458
|
+
if (raw === undefined || raw.trim() === "")
|
|
22459
|
+
return fallback;
|
|
22460
|
+
const value = raw.trim();
|
|
22461
|
+
if (!/^\d+$/.test(value))
|
|
22462
|
+
throw new Error(`${name}='${raw}' is not a positive integer (fail loud, no clamping)`);
|
|
22463
|
+
const parsed = Number.parseInt(value, 10);
|
|
22464
|
+
if (parsed < 1)
|
|
22465
|
+
throw new Error(`${name}=${parsed} must be >= 1 (a 0 floor disables the gate \u2014 drop the env instead)`);
|
|
22466
|
+
return parsed;
|
|
22467
|
+
}
|
|
22446
22468
|
function parseFilterCategories(raw) {
|
|
22447
22469
|
if (raw === undefined || raw.trim() === "")
|
|
22448
22470
|
return [...DEFAULT_FILTER_CONFIG.categories];
|
|
@@ -22460,7 +22482,8 @@ function filterConfigFromEnv(env2 = process.env) {
|
|
|
22460
22482
|
minQuality: parseFilterThreshold(env2.QC_MIN_QUALITY, "QC_MIN_QUALITY", DEFAULT_FILTER_CONFIG.minQuality),
|
|
22461
22483
|
minImportance: parseFilterThreshold(env2.QC_MIN_IMPORTANCE, "QC_MIN_IMPORTANCE", DEFAULT_FILTER_CONFIG.minImportance),
|
|
22462
22484
|
minUrgency: parseFilterThreshold(env2.QC_MIN_URGENCY, "QC_MIN_URGENCY", DEFAULT_FILTER_CONFIG.minUrgency),
|
|
22463
|
-
minImpact: parseFilterThreshold(env2.QC_MIN_IMPACT, "QC_MIN_IMPACT", DEFAULT_FILTER_CONFIG.minImpact)
|
|
22485
|
+
minImpact: parseFilterThreshold(env2.QC_MIN_IMPACT, "QC_MIN_IMPACT", DEFAULT_FILTER_CONFIG.minImpact),
|
|
22486
|
+
minEpisodeItems: parseFloorCount(env2.QC_MIN_EPISODE_ITEMS, "QC_MIN_EPISODE_ITEMS", DEFAULT_FILTER_CONFIG.minEpisodeItems)
|
|
22464
22487
|
};
|
|
22465
22488
|
}
|
|
22466
22489
|
function requireAnnotation(doc2) {
|
|
@@ -22505,6 +22528,39 @@ function filterAnnotated(docs, config3) {
|
|
|
22505
22528
|
}
|
|
22506
22529
|
return { kept, rejected };
|
|
22507
22530
|
}
|
|
22531
|
+
function importanceOf(doc2) {
|
|
22532
|
+
const scores = asRecord(asRecord(doc2.metadata).scores);
|
|
22533
|
+
return typeof scores.importance === "number" ? scores.importance : 0;
|
|
22534
|
+
}
|
|
22535
|
+
function planScoreOf(doc2) {
|
|
22536
|
+
const value = asRecord(doc2.metadata).planScore;
|
|
22537
|
+
return typeof value === "number" ? value : 0;
|
|
22538
|
+
}
|
|
22539
|
+
function backfillToFloor(kept, rejected, config3) {
|
|
22540
|
+
if (kept.length >= config3.minEpisodeItems)
|
|
22541
|
+
return { kept, rejected, admitted: [] };
|
|
22542
|
+
const eligible = rejected.map((entry, index) => ({ entry, index })).filter(({ entry }) => entry.reasons.length > 0 && entry.reasons.every((r) => r.startsWith("importance:")));
|
|
22543
|
+
eligible.sort((a, b) => {
|
|
22544
|
+
const byImportance = importanceOf(b.entry.doc) - importanceOf(a.entry.doc);
|
|
22545
|
+
if (byImportance !== 0)
|
|
22546
|
+
return byImportance;
|
|
22547
|
+
const byPlanScore = planScoreOf(b.entry.doc) - planScoreOf(a.entry.doc);
|
|
22548
|
+
if (byPlanScore !== 0)
|
|
22549
|
+
return byPlanScore;
|
|
22550
|
+
return a.index - b.index;
|
|
22551
|
+
});
|
|
22552
|
+
const taken = eligible.slice(0, config3.minEpisodeItems - kept.length);
|
|
22553
|
+
const takenIds = new Set(taken.map(({ entry }) => entry.doc.id));
|
|
22554
|
+
const admitted = taken.map(({ entry }) => ({
|
|
22555
|
+
...entry.doc,
|
|
22556
|
+
metadata: { ...asRecord(entry.doc.metadata), backfilled: true }
|
|
22557
|
+
}));
|
|
22558
|
+
return {
|
|
22559
|
+
kept: [...kept, ...admitted],
|
|
22560
|
+
rejected: rejected.filter((entry) => !takenIds.has(entry.doc.id)),
|
|
22561
|
+
admitted
|
|
22562
|
+
};
|
|
22563
|
+
}
|
|
22508
22564
|
function rejectedOutPath(outputPath) {
|
|
22509
22565
|
return outputPath.endsWith(".json") ? `${outputPath.slice(0, -".json".length)}.rejected.json` : `${outputPath}.rejected.json`;
|
|
22510
22566
|
}
|
|
@@ -22530,9 +22586,13 @@ async function processFilterIO(inputPath, outputPath) {
|
|
|
22530
22586
|
const config3 = filterConfigFromEnv();
|
|
22531
22587
|
const docs = DocListSchema.parse(await readJsonFile(inputPath));
|
|
22532
22588
|
const { kept, rejected } = filterAnnotated(docs, config3);
|
|
22533
|
-
const
|
|
22589
|
+
const filled = backfillToFloor(kept, rejected, config3);
|
|
22590
|
+
if (filled.admitted.length > 0) {
|
|
22591
|
+
echoError(`episode floor backfill: admitted ${filled.admitted.length} importance-only reject(s) to reach ${config3.minEpisodeItems} items (${filled.admitted.map((d) => d.id).join(", ")})`);
|
|
22592
|
+
}
|
|
22593
|
+
const content = planContent(filled.kept, resolvePlanDate());
|
|
22534
22594
|
await atomicWrite(outputPath, content);
|
|
22535
|
-
await atomicWrite(rejectedOutPath(outputPath), { rejected, config: config3, total: docs.length });
|
|
22595
|
+
await atomicWrite(rejectedOutPath(outputPath), { rejected: filled.rejected, config: config3, total: docs.length });
|
|
22536
22596
|
return content;
|
|
22537
22597
|
}
|
|
22538
22598
|
async function main2() {
|
|
@@ -22581,5 +22641,6 @@ export {
|
|
|
22581
22641
|
docsToContent,
|
|
22582
22642
|
cleanBody,
|
|
22583
22643
|
classifyCategory,
|
|
22644
|
+
backfillToFloor,
|
|
22584
22645
|
DEFAULT_FILTER_CONFIG
|
|
22585
22646
|
};
|