@gobing-ai/knowledge-kit 0.1.4 → 0.1.6
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/board/collect/index.css +447 -0
- package/dist/board/collect/index.js +2505 -453
- package/dist/board/daily-voice/index.css +447 -0
- package/dist/board/daily-voice/index.js +415 -8
- package/dist/board/itc/index.css +447 -0
- package/dist/board/itc/index.js +415 -8
- package/dist/index.js +494 -136
- package/package.json +6 -5
- package/plugins/generations/content-gen/dist/index.js +9 -9
- package/plugins/generations/core-facts-gen/dist/index.js +12 -12
- package/plugins/generations/daily-article-gen/dist/index.js +15 -13
- package/plugins/generations/daily-article-gen/src/index.ts +5 -3
- package/plugins/generations/dailynews-gen/dist/index.js +9 -9
- package/plugins/generations/episode-plan-gen/dist/index.js +12 -12
- package/plugins/generations/gemini-tts/dist/index.js +9 -9
- package/plugins/generations/image-gen/dist/index.js +28 -12
- package/plugins/generations/image-gen/src/compose/card.ts +19 -3
- package/plugins/generations/news-report-gen/dist/index.js +12 -12
- package/plugins/generations/voice-gen/dist/index.js +9 -9
- package/plugins/generations/voxcpm-tts/README.md +31 -0
- package/plugins/generations/voxcpm-tts/dist/index.js +9 -9
- package/plugins/generations/voxcpm-tts/scripts/probe-long-render.py +134 -0
- package/plugins/ingestions/aihot-ingest/dist/index.js +12 -12
- package/plugins/ingestions/horizon-ingest/dist/index.js +12 -12
- package/plugins/ingestions/karakeep-local/dist/index.js +13 -13
- package/plugins/ingestions/last30days-ingest/dist/index.js +12 -12
- package/plugins/ingestions/web-search/dist/index.js +10 -10
- package/plugins/kk/decisions/kk-podcast.yaml +44 -0
- package/plugins/kk/decisions/kk-safety.yaml +46 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/audio-quality.ts +635 -0
- package/plugins/kk/scripts/daily-stages.ts +163 -0
- package/plugins/kk/scripts/kk-workflow-stages.ts +34 -0
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +25 -9
- package/plugins/kk/workflows/kk-daily-safety.yaml +37 -10
- package/plugins/kk/workflows/kk-solo-podcast.yaml +65 -1
- package/plugins/publishings/bilibili-pub/dist/index.js +22 -22
- package/plugins/publishings/bilibili-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/cnblogs-pub/dist/index.js +22 -22
- package/plugins/publishings/cnblogs-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/csdn-pub/dist/index.js +22 -22
- package/plugins/publishings/csdn-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/emdash-pub/dist/index.js +9 -9
- package/plugins/publishings/infoq-pub/dist/index.js +22 -22
- package/plugins/publishings/infoq-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/jike-pub/dist/index.js +22 -22
- package/plugins/publishings/jike-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/juejin-pub/dist/index.js +22 -22
- package/plugins/publishings/juejin-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/medium-pub/dist/index.js +9 -9
- package/plugins/publishings/podcast-pub/dist/index.js +9 -9
- package/plugins/publishings/qiita-pub/dist/index.js +9 -9
- package/plugins/publishings/shipinhao-pub/dist/index.js +22 -22
- package/plugins/publishings/shipinhao-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/sspai-pub/dist/index.js +22 -22
- package/plugins/publishings/sspai-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/substack-pub/dist/index.js +22 -22
- package/plugins/publishings/substack-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/surfdash-pub/dist/index.js +39 -12
- package/plugins/publishings/surfdash-pub/src/index.ts +33 -6
- package/plugins/publishings/surfdash-pub/src/patch.ts +4 -0
- package/plugins/publishings/threads-pub/dist/index.js +9 -9
- package/plugins/publishings/wechatmp-pub/dist/index.js +22 -22
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/weibo-pub/dist/index.js +22 -22
- package/plugins/publishings/weibo-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/x-pub/dist/index.js +22 -22
- package/plugins/publishings/x-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/xhs-pub/dist/index.js +22 -22
- package/plugins/publishings/xhs-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +22 -22
- package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +22 -22
- package/plugins/publishings/zenn-pub/dist/index.js +9 -9
- package/plugins/kk/workflows/feature-verification.yaml +0 -89
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
readFileSync,
|
|
61
61
|
renameSync,
|
|
62
62
|
rmSync,
|
|
63
|
+
statSync,
|
|
63
64
|
unlinkSync,
|
|
64
65
|
writeFileSync,
|
|
65
66
|
} from 'node:fs';
|
|
@@ -284,6 +285,79 @@ function applyFloorGate(
|
|
|
284
285
|
);
|
|
285
286
|
}
|
|
286
287
|
|
|
288
|
+
/** Widest window the extract ladder may reach; a "daily" show must not import a week-old story. */
|
|
289
|
+
const LADDER_MAX_MS = 72 * 3_600_000;
|
|
290
|
+
|
|
291
|
+
const WINDOW_UNIT_MS: Record<string, number> = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };
|
|
292
|
+
|
|
293
|
+
/** `'24h'` → milliseconds, mirroring `kk store extract --window`'s grammar. null when unparsable. */
|
|
294
|
+
function parseWindowMs(window: string): number | null {
|
|
295
|
+
const match = /^(\d+)([smhd])$/.exec(window.trim());
|
|
296
|
+
if (match === null) return null;
|
|
297
|
+
const unit = WINDOW_UNIT_MS[match[2] as string];
|
|
298
|
+
const ms = Number(match[1]) * (unit ?? 0);
|
|
299
|
+
return ms > 0 ? ms : null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Milliseconds → the hour form the store CLI takes. Whole hours only: every ladder step is ×2/×3. */
|
|
303
|
+
function formatWindowMs(ms: number): string {
|
|
304
|
+
return `${Math.max(1, Math.round(ms / 3_600_000))}h`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Pool extraction with a conditional window ladder (2026-10-19 operator decision, superseding the
|
|
309
|
+
* 0266 D1 blanket rejection). `kk store extract` reads one first_seen window and `applyFloorGate`
|
|
310
|
+
* then refuses a pool that cannot reach `episode_min_minutes`. Together they ate whole days: an
|
|
311
|
+
* episode consumes 6-12 stories while a day of collection adds ~3-13 qualifying ones, so the 04:30
|
|
312
|
+
* runs of 2026-10-18 and 2026-10-19 saw 2- and 3-item pools and closed as no-content while older
|
|
313
|
+
* unselected items sat in the store. The ladder keeps the configured window as the first choice and
|
|
314
|
+
* widens (×2, ×3, capped at 72h) ONLY when that pool's estimate misses the floor — a rich day is
|
|
315
|
+
* extracted exactly once, and a thin one is filled from the backlog instead of dropped.
|
|
316
|
+
*
|
|
317
|
+
* The store's `selections` ledger still excludes every story a previous run took, so a widened
|
|
318
|
+
* window can only add items no episode has used. The estimate here runs on the raw pool (before QC
|
|
319
|
+
* and dedup), so it is an upper bound: the ladder can stop one step early, never one late.
|
|
320
|
+
*/
|
|
321
|
+
export function dailyExtract(args: string[]): void {
|
|
322
|
+
const [workDir = '', topic = '', window = '', minMinutes = '', vdriver = ''] = args;
|
|
323
|
+
if (!workDir) fail('missing work_dir');
|
|
324
|
+
if (!topic) fail('missing topic');
|
|
325
|
+
const poolPath = join(workDir, '10-collect', 'pool.json');
|
|
326
|
+
const base = parseWindowMs(window === '' ? '24h' : window);
|
|
327
|
+
if (base === null) fail(`extract: invalid window: ${window || '(empty)'}`);
|
|
328
|
+
const floor = Number(minMinutes);
|
|
329
|
+
const gated = minMinutes !== '' && Number.isFinite(floor) && floor > 0;
|
|
330
|
+
const windows: number[] = [base];
|
|
331
|
+
for (const factor of gated ? [2, 3] : []) {
|
|
332
|
+
const ms = base * factor;
|
|
333
|
+
if (ms > LADDER_MAX_MS) break;
|
|
334
|
+
windows.push(ms);
|
|
335
|
+
}
|
|
336
|
+
timed(workDir, 'extract', () => {
|
|
337
|
+
mkdirSync(join(workDir, '10-collect'), { recursive: true });
|
|
338
|
+
let chosen: { window: string; docs: unknown[] } | null = null;
|
|
339
|
+
for (const ms of windows) {
|
|
340
|
+
const label = formatWindowMs(ms);
|
|
341
|
+
const attempt = join(workDir, '10-collect', `.pool-${label}.json`);
|
|
342
|
+
kk(['store', 'extract', '--topic', topic, '--window', label, '--out', attempt]);
|
|
343
|
+
const parsed = readJson(attempt);
|
|
344
|
+
const docs = Array.isArray(parsed) ? (parsed as unknown[]) : [];
|
|
345
|
+
const estimate = estimateSpokenMinutes(docs, vdriver);
|
|
346
|
+
chosen = { window: label, docs };
|
|
347
|
+
const verdict = gated
|
|
348
|
+
? `estimated ${estimate.minutes} min — ${estimate.seconds >= floor * 60 ? 'clears' : 'below'} the ${floor} min floor`
|
|
349
|
+
: 'floor gate off';
|
|
350
|
+
console.error(`extract: ${label} pool ${docs.length} item(s), ${verdict}`);
|
|
351
|
+
if (!gated || estimate.seconds >= floor * 60) break;
|
|
352
|
+
rmSync(attempt, { force: true });
|
|
353
|
+
}
|
|
354
|
+
const picked = chosen ?? { window: formatWindowMs(base), docs: [] as unknown[] };
|
|
355
|
+
sted('write', poolPath, () => writeJsonText(poolPath, picked.docs));
|
|
356
|
+
for (const ms of windows)
|
|
357
|
+
rmSync(join(workDir, '10-collect', `.pool-${formatWindowMs(ms)}.json`), { force: true });
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
287
361
|
export function dailyArticle(args: string[]): void {
|
|
288
362
|
const [workDir = '', runDate = '', pluginsPath = ''] = args;
|
|
289
363
|
const articleMd = join(workDir, '22-article', `${runDate}_06_article_article.md`);
|
|
@@ -1907,6 +1981,95 @@ export function dailySafetyGate(args: string[]): void {
|
|
|
1907
1981
|
});
|
|
1908
1982
|
}
|
|
1909
1983
|
|
|
1984
|
+
/**
|
|
1985
|
+
* `daily-preflight <workflow.yaml>…` — assert the stage runner that `kk stage` just loaded
|
|
1986
|
+
* advertises every `kk stage <name>` these workflow definitions invoke.
|
|
1987
|
+
*
|
|
1988
|
+
* WHY (2026-10-20): the scheduler runs the CHECKOUT's workflow YAML while `kk stage` resolves the
|
|
1989
|
+
* runner from the INSTALLED package (`apps/cli/src/stage.ts`). A commit that adds a stage therefore
|
|
1990
|
+
* outruns the global install silently, and the mismatch only surfaces where the new stage is first
|
|
1991
|
+
* reached. That day's 04:30 run died 29 minutes in at `safety` with `unknown stage:
|
|
1992
|
+
* daily-safety-verdict` (present in the checkout, absent from the installed 0.1.4) and published
|
|
1993
|
+
* nothing. This runs as the first command of the run's `prepare` node, so the same skew costs one
|
|
1994
|
+
* cheap step and names both the missing stages and the refresh command.
|
|
1995
|
+
*
|
|
1996
|
+
* The known-name set is the dispatch switch of the sibling `kk-workflow-stages.ts` — the file beside
|
|
1997
|
+
* this one, i.e. the copy actually in force, never a checkout-vs-install guess.
|
|
1998
|
+
*/
|
|
1999
|
+
export function dailyPreflight(args: string[]): void {
|
|
2000
|
+
const files = args.filter((arg) => arg !== '');
|
|
2001
|
+
timed('', 'preflight', () => {
|
|
2002
|
+
if (files.length === 0) failWith(2, 'daily-preflight: pass at least one workflow YAML path');
|
|
2003
|
+
const runner = join(import.meta.dir, 'kk-workflow-stages.ts');
|
|
2004
|
+
if (!isFile(runner)) failWith(1, `daily-preflight.runner-missing: ${runner} is not beside this stage body`);
|
|
2005
|
+
const known = new Set(
|
|
2006
|
+
[...readFileSync(runner, 'utf-8').matchAll(/^\s*case '([a-z][a-z0-9-]*)':/gm)].map((match) =>
|
|
2007
|
+
String(match[1]),
|
|
2008
|
+
),
|
|
2009
|
+
);
|
|
2010
|
+
const missing: string[] = [];
|
|
2011
|
+
for (const file of files) {
|
|
2012
|
+
if (!isFile(file)) failWith(1, `daily-preflight.workflow-missing: ${file}`);
|
|
2013
|
+
for (const match of readFileSync(file, 'utf-8').matchAll(/\bkk stage ([a-z][a-z0-9-]*)/g)) {
|
|
2014
|
+
const name = String(match[1]);
|
|
2015
|
+
if (!known.has(name)) missing.push(`${name} (needed by ${basename(file)})`);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
if (missing.length > 0) {
|
|
2019
|
+
failWith(
|
|
2020
|
+
1,
|
|
2021
|
+
`daily-preflight.runner-stale: the installed stage runner ${runner} does not know ${missing.join(', ')} — refresh it (install the released kk package, or rsync plugins/kk/scripts/ per AGENTS.md) before the scheduled run`,
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
console.log(
|
|
2025
|
+
`preflight: stage runner advertises every stage ${files.map((file) => basename(file)).join(' + ')} invokes`,
|
|
2026
|
+
);
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
/**
|
|
2031
|
+
* Task 0317 R6 — fold the gate's own classification, its stamp and the `kk-safety-verdict` catalog
|
|
2032
|
+
* decision into ONE accept/refuse token the `judge -> clean` guard reads as a single predicate
|
|
2033
|
+
* (ADR-115 caps that guard at 5 logical commands, so the logic lives here).
|
|
2034
|
+
*
|
|
2035
|
+
* The gate stays authoritative on `block`/`revise`; the decision can only refuse an outcome the gate
|
|
2036
|
+
* already admitted. The decision replaced the judge's self-reported `confidence` field, which the
|
|
2037
|
+
* accept guard used to re-parse in shell: its fallback is `escalate`, so missing or garbled evidence
|
|
2038
|
+
* lands on a recorded human decision instead of a silent publish.
|
|
2039
|
+
*
|
|
2040
|
+
* Writes `24-script/<date>_09_safety-verdict.status` = `accept`, or `refuse` after naming the
|
|
2041
|
+
* offending value on stderr. Never exits non-zero: routing belongs to the transitions.
|
|
2042
|
+
*/
|
|
2043
|
+
export function dailySafetyVerdict(args: string[]): void {
|
|
2044
|
+
const [workDir = '', runDate = ''] = args;
|
|
2045
|
+
const statusFile = safetyPath(workDir, runDate, 'safety-judge_status.txt');
|
|
2046
|
+
const stampFile = safetyPath(workDir, runDate, 'safety-judge_stamp.json');
|
|
2047
|
+
const decisionFile = safetyPath(workDir, runDate, 'safety-verdict.decision');
|
|
2048
|
+
const token = safetyPath(workDir, runDate, 'safety-verdict.status');
|
|
2049
|
+
const ADMITTED = new Set(['pass', 'escalate', 'exclude']);
|
|
2050
|
+
timed(workDir, 'safety-verdict-check', () => {
|
|
2051
|
+
const status = isFile(statusFile) ? readFileSync(statusFile, 'utf-8').trim() : '';
|
|
2052
|
+
const stamp = isFile(stampFile) ? statSync(stampFile).size : 0;
|
|
2053
|
+
// A missing/unreadable decision row is a refusal, never an approval: `onError: continue` on
|
|
2054
|
+
// the decide action is what can leave it absent.
|
|
2055
|
+
let verdict = '';
|
|
2056
|
+
if (isFile(decisionFile)) {
|
|
2057
|
+
const row = JSON.parse(readFileSync(decisionFile, 'utf-8')) as { value?: unknown };
|
|
2058
|
+
verdict = String(row.value ?? '');
|
|
2059
|
+
}
|
|
2060
|
+
let refusal = '';
|
|
2061
|
+
if (!ADMITTED.has(status)) {
|
|
2062
|
+
refusal = `kk-daily-safety.judge.verdict-refused: gate status [${status}] is not an admitted outcome, so these drafts must not publish`;
|
|
2063
|
+
} else if (stamp === 0) {
|
|
2064
|
+
refusal = `kk-daily-safety.judge.verdict-refused: gate stamp missing for status ${status}, so these drafts must not publish`;
|
|
2065
|
+
} else if (!ADMITTED.has(verdict)) {
|
|
2066
|
+
refusal = `kk-daily-safety.judge.decision-refused: kk-safety-verdict [${verdict}] must admit pass|escalate|exclude, so these drafts must not publish`;
|
|
2067
|
+
}
|
|
2068
|
+
if (refusal !== '') console.error(refusal);
|
|
2069
|
+
sted('write', token, () => writeRaw(token, refusal === '' ? 'accept' : 'refuse'));
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
|
|
1910
2073
|
/**
|
|
1911
2074
|
* Post-remediation bookkeeping for the `safety-revise` state: the agent edited the drafts, this
|
|
1912
2075
|
* stage checks they are still the artifacts the rest of the pipeline consumes and spends one unit
|
|
@@ -159,6 +159,20 @@ async function loadDailyStages(): Promise<typeof import('./daily-stages.ts')> {
|
|
|
159
159
|
);
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Load the audio-quality diagnostic lazily: it shells out to ffprobe/ffmpeg and is only needed when an
|
|
164
|
+
* operator asks for it, so the runner must not pay for it on every stage (and an install without
|
|
165
|
+
* ffmpeg on PATH still runs every other stage).
|
|
166
|
+
*/
|
|
167
|
+
async function loadAudioQuality(): Promise<typeof import('./audio-quality.ts')> {
|
|
168
|
+
try {
|
|
169
|
+
return await import('./audio-quality.ts');
|
|
170
|
+
} catch {
|
|
171
|
+
return fail(
|
|
172
|
+
'audio-quality.ts not found next to kk-workflow-stages.ts — install or refresh the kk package (bun link in a checkout)',
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
162
176
|
export const ERRNO_REASONS: Record<string, string> = {
|
|
163
177
|
EACCES: 'Permission denied',
|
|
164
178
|
EROFS: 'Read-only file system',
|
|
@@ -1593,6 +1607,11 @@ if (import.meta.main)
|
|
|
1593
1607
|
case 'collect-prepare':
|
|
1594
1608
|
collectPrepare(rest);
|
|
1595
1609
|
break;
|
|
1610
|
+
case 'daily-extract': {
|
|
1611
|
+
const { dailyExtract } = await loadDailyStages();
|
|
1612
|
+
dailyExtract(rest);
|
|
1613
|
+
break;
|
|
1614
|
+
}
|
|
1596
1615
|
case 'daily-plan': {
|
|
1597
1616
|
const { dailyPlan } = await loadDailyStages();
|
|
1598
1617
|
dailyPlan(rest);
|
|
@@ -1633,6 +1652,16 @@ if (import.meta.main)
|
|
|
1633
1652
|
dailyScript(rest);
|
|
1634
1653
|
break;
|
|
1635
1654
|
}
|
|
1655
|
+
case 'audio-quality': {
|
|
1656
|
+
const { runAudioQuality } = await loadAudioQuality();
|
|
1657
|
+
await runAudioQuality(rest);
|
|
1658
|
+
break;
|
|
1659
|
+
}
|
|
1660
|
+
case 'daily-preflight': {
|
|
1661
|
+
const { dailyPreflight } = await loadDailyStages();
|
|
1662
|
+
dailyPreflight(rest);
|
|
1663
|
+
break;
|
|
1664
|
+
}
|
|
1636
1665
|
case 'daily-safety-prep': {
|
|
1637
1666
|
const { dailySafetyPrep } = await loadDailyStages();
|
|
1638
1667
|
dailySafetyPrep(rest);
|
|
@@ -1643,6 +1672,11 @@ if (import.meta.main)
|
|
|
1643
1672
|
dailySafetyGate(rest);
|
|
1644
1673
|
break;
|
|
1645
1674
|
}
|
|
1675
|
+
case 'daily-safety-verdict': {
|
|
1676
|
+
const { dailySafetyVerdict } = await loadDailyStages();
|
|
1677
|
+
dailySafetyVerdict(rest);
|
|
1678
|
+
break;
|
|
1679
|
+
}
|
|
1646
1680
|
case 'daily-safety-revise': {
|
|
1647
1681
|
const { dailySafetyRevise } = await loadDailyStages();
|
|
1648
1682
|
dailySafetyRevise(rest);
|
|
@@ -25,9 +25,15 @@ vars:
|
|
|
25
25
|
# Voice driver switch — voxcpm (voxcpm-tts) | gemini (gemini-tts) | voicebox (voice-gen)
|
|
26
26
|
# | ominivoice (omni-voice-gen). The generate tier below mirrors the fallback order, and the
|
|
27
27
|
# fit/duration gates take their spoken-char rate from this var — switching drivers here is enough.
|
|
28
|
-
# 2026-10-
|
|
29
|
-
#
|
|
30
|
-
#
|
|
28
|
+
# 2026-10-20 (operator decision): voxcpm STAYS the primary. It is audibly echoey - a 40 ms periodic
|
|
29
|
+
# reflection on renders longer than ~30 s plus a pause floor only 16-22 dB below speech, measured by
|
|
30
|
+
# `kk stage audio-quality` on every episode's raw model output since 10-15 (cepstrum 40 ms @ 26-31 dB
|
|
31
|
+
# / pauseFloor -16..-22 dB, versus gemini's 20-25 ms @ 19-20 dB / -34..-37 dB) - and no exposed knob
|
|
32
|
+
# (cfg_value, inference_timesteps, normalize, denoise) or usable checkpoint fixes it (VoxCPM1.5:
|
|
33
|
+
# 80 ms @ 31 dB; VoxCPM-0.5B loses the lattice but measures -15.5 dB at 16 kHz). The operator keeps
|
|
34
|
+
# the local driver for its speed and cost and will monitor it episode by episode until a proper fix
|
|
35
|
+
# exists; flipping this one var to "gemini" is the escape hatch (measured dry: 20-25 ms / -34..-37 dB)
|
|
36
|
+
# at the price of a 1h47m-2h21m remote render.
|
|
31
37
|
vdriver: "voxcpm"
|
|
32
38
|
intro_music: ".works/kk-daily-ai-voice/AI每日资讯_8秒播客片头.mp3"
|
|
33
39
|
surfdash_root: "/Users/robin/xprojects/surfdash"
|
|
@@ -113,11 +119,18 @@ nodes:
|
|
|
113
119
|
the artifact directories and the safety revise budget. `kk ledger sync` records runs that
|
|
114
120
|
already finished without a ledger row (failed ones included) — a fail-fast DAG never reaches
|
|
115
121
|
its own `ledger` node, so the next run's prepare is the backfill (design §7).
|
|
122
|
+
The `daily-preflight` stage runs FIRST: the scheduler executes this checkout's YAML while
|
|
123
|
+
`kk stage` resolves its stage runner from the installed package, so a run whose YAML names a
|
|
124
|
+
stage the installed runner lacks must fail here, naming the stage, instead of 29 minutes later
|
|
125
|
+
inside the nested safety machine (2026-10-20: `unknown stage: daily-safety-verdict`, nothing
|
|
126
|
+
published).
|
|
116
127
|
action:
|
|
117
128
|
kind: shell
|
|
118
129
|
options:
|
|
119
130
|
command: >-
|
|
120
|
-
kk daily
|
|
131
|
+
kk stage daily-preflight plugins/kk/workflows/kk-daily-ai-voice2.yaml
|
|
132
|
+
plugins/kk/workflows/kk-daily-safety.yaml
|
|
133
|
+
&& kk daily prepare --workflow kk-daily-ai-voice2 --run-date "${vars.run_date}" --ledger-sync
|
|
121
134
|
--run "${vars.__runId}" --definition-digest "${vars.__definitionDigest}"
|
|
122
135
|
--setting window="${vars.window}" --setting language="${vars.language}" --setting vdriver="${vars.vdriver}"
|
|
123
136
|
--setting skip_review="${vars.skip_review}" --setting transcode_mp3="${vars.transcode_mp3}"
|
|
@@ -181,14 +194,17 @@ nodes:
|
|
|
181
194
|
kk-collect wrote, minus every story already selected or published for the ai-news topic (the
|
|
182
195
|
store's `selections` ledger, written by the `select` node below). Writes
|
|
183
196
|
<work_dir>/10-collect/pool.json (the fan-in blended.json shape) and prints {count}. An empty
|
|
184
|
-
pool writes [] and exits 0 — the run's no-content path, not a failure.
|
|
197
|
+
pool writes [] and exits 0 — the run's no-content path, not a failure. The `daily-extract`
|
|
198
|
+
stage widens the window (×2, ×3, capped at 72h) only when the configured window's pool cannot
|
|
199
|
+
reach `episode_min_minutes` (2026-10-19): a published episode consumes more stories than a day
|
|
200
|
+
of collection supplies, so the tight window alone closed 2026-10-18/19 as no-content while
|
|
201
|
+
older unselected items sat in the store.
|
|
185
202
|
dependsOn: [read-run-date]
|
|
186
203
|
action:
|
|
187
204
|
kind: shell
|
|
188
205
|
options:
|
|
189
206
|
command: >-
|
|
190
|
-
kk
|
|
191
|
-
--out "${vars.work_dir}/10-collect/pool.json"
|
|
207
|
+
kk stage daily-extract "${vars.work_dir}" ai-news "${vars.window}" "${vars.episode_min_minutes}" "${vars.vdriver}"
|
|
192
208
|
|
|
193
209
|
- id: plan
|
|
194
210
|
description: >-
|
|
@@ -545,7 +561,7 @@ nodes:
|
|
|
545
561
|
kk stage daily-wrap-docs "${vars.work_dir}" "${vars.run_date}" "${vars.voice_profile}"
|
|
546
562
|
|
|
547
563
|
- id: generate
|
|
548
|
-
description: "Voice tier, primary driver (vars.vdriver — voxcpm
|
|
564
|
+
description: "Voice tier, primary driver (vars.vdriver — voxcpm; its 40 ms lattice is known and monitored, see the vdriver comment). onError: continue — the fallback siblings below are the loud chain (same pattern as 0238 R2)."
|
|
549
565
|
# wrap-docs is a data dependency, not a sibling: daily-generate reads its _10_wrap-docs_docs.json
|
|
550
566
|
# as the TTS input. Without this edge the two nodes race in the same wave (0212 dogfood finding).
|
|
551
567
|
dependsOn: [script, safety, review, wrap-docs]
|
|
@@ -567,7 +583,7 @@ nodes:
|
|
|
567
583
|
# its plugin id and the cache ignores foreign-driver sections, so a failed attempt can neither be
|
|
568
584
|
# relabeled nor mix voices). Non-final siblings continue on error; the last one is loud.
|
|
569
585
|
- id: generate-fallback
|
|
570
|
-
description: "Voice tier fallback 1: gemini (gemini-tts) when the primary driver left no valid Content"
|
|
586
|
+
description: "Voice tier fallback 1: gemini (gemini-tts) when the primary driver left no valid Content — the measured-dry escape hatch from voxcpm's lattice"
|
|
571
587
|
dependsOn: [generate]
|
|
572
588
|
condition:
|
|
573
589
|
kind: shell
|
|
@@ -93,6 +93,31 @@ states:
|
|
|
93
93
|
options:
|
|
94
94
|
command: >-
|
|
95
95
|
kk stage daily-safety-gate "${vars.work_dir}" "${vars.run_date}" "${vars.safety_max_revise}"
|
|
96
|
+
# 0317 R6: the publish verdict is a catalog DECISION over the same artifacts the gate read,
|
|
97
|
+
# not the judge's self-reported `confidence` re-parsed in a shell jq filter. `onError:
|
|
98
|
+
# continue` keeps a catalog/maker problem from aborting the machine — the accept guard below
|
|
99
|
+
# then refuses an absent row, which is the fail-closed direction.
|
|
100
|
+
- kind: decide
|
|
101
|
+
onError: continue
|
|
102
|
+
options:
|
|
103
|
+
id: safety-verdict
|
|
104
|
+
decision: kk-safety-verdict
|
|
105
|
+
params:
|
|
106
|
+
report: >-
|
|
107
|
+
Safety report ${vars.work_dir}/24-script/${vars.run_date}_09_safety-review_report.json,
|
|
108
|
+
judge verdict ${vars.work_dir}/24-script/${vars.run_date}_09_safety-judge_verdict.json,
|
|
109
|
+
and the gate's own classification in
|
|
110
|
+
${vars.work_dir}/24-script/${vars.run_date}_09_safety-judge_status.txt.
|
|
111
|
+
evidence:
|
|
112
|
+
- ${vars.work_dir}/24-script/${vars.run_date}_09_safety-review_report.json
|
|
113
|
+
- ${vars.work_dir}/24-script/${vars.run_date}_09_safety-judge_verdict.json
|
|
114
|
+
- ${vars.work_dir}/24-script/${vars.run_date}_09_safety-judge_status.txt
|
|
115
|
+
resultFile: ${vars.work_dir}/24-script/${vars.run_date}_09_safety-verdict.decision
|
|
116
|
+
# Fold gate status + stamp + decision into the single accept token the accept guard reads.
|
|
117
|
+
- kind: shell
|
|
118
|
+
options:
|
|
119
|
+
command: >-
|
|
120
|
+
kk stage daily-safety-verdict "${vars.work_dir}" "${vars.run_date}"
|
|
96
121
|
- kind: file.read.into-var
|
|
97
122
|
options:
|
|
98
123
|
path: ${vars.work_dir}/24-script/${vars.run_date}_09_safety-judge_status.txt
|
|
@@ -139,20 +164,22 @@ transitions:
|
|
|
139
164
|
- from: judge
|
|
140
165
|
to: clean
|
|
141
166
|
description: >-
|
|
142
|
-
An admitted outcome:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
never strand
|
|
167
|
+
An admitted outcome: the gate classified the run pass|escalate|exclude and wrote its stamp,
|
|
168
|
+
AND the `kk-safety-verdict` decision (task 0317 R6) admits the same pair. The decision replaced
|
|
169
|
+
the judge's self-reported `confidence` re-parsed in a shell jq filter: it reads the report, the
|
|
170
|
+
verdict and the gate status as evidence and declares `escalate` as its fallback, so a missing
|
|
171
|
+
or garbled evidence set lands on a recorded human decision instead of a silent publish. The
|
|
172
|
+
gate stays authoritative on `block`/`revise`, which is why the status check is not redundant:
|
|
173
|
+
a decision cannot overrule the gate. An absent or out-of-vocabulary decision row refuses the
|
|
174
|
+
edge (fail-closed), and the unconditional `judge -> block` below always matches, so the loop
|
|
175
|
+
can never strand.
|
|
151
176
|
guard:
|
|
152
177
|
kind: shell
|
|
153
178
|
options:
|
|
179
|
+
# One predicate over one result file (ADR-115): the three tokens are folded by
|
|
180
|
+
# `kk stage daily-safety-verdict`, which names any refusal on stderr.
|
|
154
181
|
command: >-
|
|
155
|
-
|
|
182
|
+
test "$(cat "${vars.work_dir}/24-script/${vars.run_date}_09_safety-verdict.status" 2>/dev/null)" = accept
|
|
156
183
|
|
|
157
184
|
- from: judge
|
|
158
185
|
to: revise
|
|
@@ -18,6 +18,9 @@ vars:
|
|
|
18
18
|
work_dir: ""
|
|
19
19
|
force: "false"
|
|
20
20
|
outline: ""
|
|
21
|
+
# Auto profile (0317 R7): bind the outline from the option evidence instead of pausing, and fall
|
|
22
|
+
# back to the operator gate whenever the decision is not confident.
|
|
23
|
+
auto: "false"
|
|
21
24
|
script_approved: ""
|
|
22
25
|
target_duration_min: "8"
|
|
23
26
|
words_per_min: "150"
|
|
@@ -102,6 +105,43 @@ states:
|
|
|
102
105
|
decision:
|
|
103
106
|
mode: never
|
|
104
107
|
|
|
108
|
+
- id: outline-autopick
|
|
109
|
+
description: "Auto profile — bind the outline from the written options; defer to the operator gate on low confidence (0317 R7)"
|
|
110
|
+
onEnter:
|
|
111
|
+
# ONE evidence file: option c is optional, and a missing `--evidence` path fails the decide action.
|
|
112
|
+
- kind: shell
|
|
113
|
+
options:
|
|
114
|
+
command: >-
|
|
115
|
+
mkdir -p "${vars.work_dir}/2-outline"; ev="${vars.work_dir}/2-outline/.outline-evidence.md"; : > "$ev";
|
|
116
|
+
for s in a b c; do f="${vars.work_dir}/2-outline/outline-option-$s.md"; if [ -f "$f" ]; then printf '## option %s\n%s\n' "$s" "$(cat "$f")" >> "$ev"; fi; done;
|
|
117
|
+
exit 0
|
|
118
|
+
- kind: decide
|
|
119
|
+
onError: continue
|
|
120
|
+
options:
|
|
121
|
+
id: podcast-outline
|
|
122
|
+
decision: kk-podcast-outline
|
|
123
|
+
params:
|
|
124
|
+
briefing: >-
|
|
125
|
+
Episode briefing at ${vars.work_dir}/1-briefing-briefing.md; target spoken duration
|
|
126
|
+
${vars.target_duration_min} minutes; language ${vars.language}.
|
|
127
|
+
evidence:
|
|
128
|
+
- ${vars.work_dir}/2-outline/.outline-evidence.md
|
|
129
|
+
resultFile: ${vars.work_dir}/2-outline/.outline-decision.json
|
|
130
|
+
# Project the row into the ONE token `outline-commit` reads. A degraded (fallback) row is not a
|
|
131
|
+
# pick — the same rule the kk-itc outline slot uses — so the operator gate still decides.
|
|
132
|
+
- kind: shell
|
|
133
|
+
options:
|
|
134
|
+
command: >-
|
|
135
|
+
pick="${vars.work_dir}/2-outline/.auto-pick"; : > "$pick";
|
|
136
|
+
if ! jq -e '.degraded == false' "${vars.work_dir}/2-outline/.outline-decision.json" >/dev/null 2>&1; then exit 0; fi;
|
|
137
|
+
v="$(jq -r '.value // ""' "${vars.work_dir}/2-outline/.outline-decision.json")";
|
|
138
|
+
if [ -f "${vars.work_dir}/2-outline/outline-option-$v.md" ]; then printf '%s\n' "$v" > "$pick"; fi;
|
|
139
|
+
exit 0
|
|
140
|
+
- kind: file.read.into-var
|
|
141
|
+
options:
|
|
142
|
+
path: ${vars.work_dir}/2-outline/.auto-pick
|
|
143
|
+
var: outline
|
|
144
|
+
|
|
105
145
|
- id: outline-commit
|
|
106
146
|
description: "Copy selected outline to outline-approved.md"
|
|
107
147
|
onEnter:
|
|
@@ -239,8 +279,32 @@ transitions:
|
|
|
239
279
|
kind: always
|
|
240
280
|
|
|
241
281
|
- from: outline-write
|
|
282
|
+
to: outline-autopick
|
|
283
|
+
description: "Auto profile, no pre-selected outline -> bind the outline from the option evidence"
|
|
284
|
+
guard:
|
|
285
|
+
kind: shell
|
|
286
|
+
options:
|
|
287
|
+
command: 'test -z "${vars.outline}" && test "${vars.auto}" = "true"'
|
|
288
|
+
|
|
289
|
+
- from: outline-write
|
|
290
|
+
to: outline-select
|
|
291
|
+
description: "Interactive profile, no pre-selected outline -> pause for operator selection"
|
|
292
|
+
guard:
|
|
293
|
+
kind: shell
|
|
294
|
+
options:
|
|
295
|
+
command: 'test -z "${vars.outline}" && test "${vars.auto}" != "true"'
|
|
296
|
+
|
|
297
|
+
- from: outline-autopick
|
|
298
|
+
to: outline-commit
|
|
299
|
+
description: "Decision bound a confident option -> commit it"
|
|
300
|
+
guard:
|
|
301
|
+
kind: shell
|
|
302
|
+
options:
|
|
303
|
+
command: 'test -n "${vars.outline}"'
|
|
304
|
+
|
|
305
|
+
- from: outline-autopick
|
|
242
306
|
to: outline-select
|
|
243
|
-
description: "
|
|
307
|
+
description: "Decision deferred (fallback or unconfident) -> pause for operator selection"
|
|
244
308
|
guard:
|
|
245
309
|
kind: shell
|
|
246
310
|
options:
|