@gobing-ai/knowledge-kit 0.1.4 → 0.1.5
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 +475 -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 +12 -12
- 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/dist/index.js +9 -9
- 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/daily-stages.ts +117 -0
- package/plugins/kk/scripts/kk-workflow-stages.ts +10 -0
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +6 -3
- 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 +9 -9
- 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
|
@@ -4827,7 +4827,7 @@ var init_zod = __esm(() => {
|
|
|
4827
4827
|
init_external();
|
|
4828
4828
|
});
|
|
4829
4829
|
|
|
4830
|
-
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.
|
|
4830
|
+
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.32/node_modules/@gobing-ai/ts-utils/dist/errors.js
|
|
4831
4831
|
var ErrorCode;
|
|
4832
4832
|
var init_errors3 = __esm(() => {
|
|
4833
4833
|
ErrorCode = {
|
|
@@ -4838,7 +4838,7 @@ var init_errors3 = __esm(() => {
|
|
|
4838
4838
|
};
|
|
4839
4839
|
});
|
|
4840
4840
|
|
|
4841
|
-
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.
|
|
4841
|
+
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.32/node_modules/@gobing-ai/ts-utils/dist/api-response.js
|
|
4842
4842
|
var API_ERROR_CODES, ERROR_CODE_TO_HTTP, CLIENT_SAFE_CODES;
|
|
4843
4843
|
var init_api_response = __esm(() => {
|
|
4844
4844
|
init_errors3();
|
|
@@ -4861,13 +4861,13 @@ var init_api_response = __esm(() => {
|
|
|
4861
4861
|
CLIENT_SAFE_CODES = new Set([ErrorCode.NotFound, ErrorCode.Validation, ErrorCode.Conflict]);
|
|
4862
4862
|
});
|
|
4863
4863
|
|
|
4864
|
-
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.
|
|
4864
|
+
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.32/node_modules/@gobing-ai/ts-utils/dist/cursor.js
|
|
4865
4865
|
var init_cursor = () => {};
|
|
4866
4866
|
|
|
4867
|
-
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.
|
|
4867
|
+
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.32/node_modules/@gobing-ai/ts-utils/dist/event-severity.js
|
|
4868
4868
|
var init_event_severity = () => {};
|
|
4869
4869
|
|
|
4870
|
-
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.
|
|
4870
|
+
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.32/node_modules/@gobing-ai/ts-utils/dist/output.js
|
|
4871
4871
|
function processStream(name) {
|
|
4872
4872
|
const proc = globalThis.process;
|
|
4873
4873
|
const stream = proc?.[name];
|
|
@@ -4885,7 +4885,7 @@ function echoError(message, target = defaultStderrTarget ?? processStream("stder
|
|
|
4885
4885
|
}
|
|
4886
4886
|
var defaultStderrTarget;
|
|
4887
4887
|
|
|
4888
|
-
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.
|
|
4888
|
+
// ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.32/node_modules/@gobing-ai/ts-utils/dist/index.js
|
|
4889
4889
|
var init_dist = __esm(() => {
|
|
4890
4890
|
init_api_response();
|
|
4891
4891
|
init_cursor();
|
|
@@ -11883,7 +11883,7 @@ var init_dist2 = __esm(() => {
|
|
|
11883
11883
|
$visitAsync = visit.visitAsync;
|
|
11884
11884
|
});
|
|
11885
11885
|
|
|
11886
|
-
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.
|
|
11886
|
+
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.32+180a6e424da77181/node_modules/@gobing-ai/ts-runtime/dist/config.js
|
|
11887
11887
|
var configSchema;
|
|
11888
11888
|
var init_config = __esm(() => {
|
|
11889
11889
|
init_dist2();
|
|
@@ -11907,7 +11907,7 @@ var init_config = __esm(() => {
|
|
|
11907
11907
|
});
|
|
11908
11908
|
});
|
|
11909
11909
|
|
|
11910
|
-
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.
|
|
11910
|
+
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.32+180a6e424da77181/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
|
|
11911
11911
|
import { appendFileSync, cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
|
|
11912
11912
|
import { dirname, resolve as resolvePath } from "path";
|
|
11913
11913
|
function createNodeFileSystem(root, paths) {
|
|
@@ -12269,7 +12269,7 @@ var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx)
|
|
|
12269
12269
|
});
|
|
12270
12270
|
}
|
|
12271
12271
|
});
|
|
12272
|
-
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.
|
|
12272
|
+
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.32+180a6e424da77181/node_modules/@gobing-ai/ts-runtime/dist/index.js
|
|
12273
12273
|
init_file_system_node();
|
|
12274
12274
|
init_config();
|
|
12275
12275
|
|
|
@@ -14400,7 +14400,7 @@ var ConfigError = class extends Error {
|
|
|
14400
14400
|
}
|
|
14401
14401
|
};
|
|
14402
14402
|
|
|
14403
|
-
// ../../node_modules/.bun/@gobing-ai+ts-infra@0.5.
|
|
14403
|
+
// ../../node_modules/.bun/@gobing-ai+ts-infra@0.5.32+7037256b2d91fd75/node_modules/@gobing-ai/ts-infra/dist/logger.js
|
|
14404
14404
|
var ROOT_CATEGORY = "app";
|
|
14405
14405
|
function toLogTapeLevel(level) {
|
|
14406
14406
|
return level === "warn" ? "warning" : level;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Solo-podcast outline binding (task 0317 R7).
|
|
2
|
+
#
|
|
3
|
+
# The outline gate in `kk-solo-podcast` (`outline-select`) always paused. Under an auto profile the
|
|
4
|
+
# pipeline now asks this catalog decision over the written option files first and only pauses when
|
|
5
|
+
# the decision cannot answer confidently — the same pattern `kk-itc` uses for its outline slot
|
|
6
|
+
# (`plugins/kk/workflows/kk-itc-author.yaml` → `outline-autopick` → `itc-decision`).
|
|
7
|
+
#
|
|
8
|
+
# The fallback is a declared label because the catalog format requires one, but the workflow never
|
|
9
|
+
# treats a fallback as a pick: its projection reads `degraded` and defers to the operator gate when
|
|
10
|
+
# the answer did not come from a maker.
|
|
11
|
+
#
|
|
12
|
+
# Resolution: registered through `decisions.paths` in `.spur/config.yaml`
|
|
13
|
+
# (project-relative `plugins/kk/decisions`).
|
|
14
|
+
$schema: "@gobing-ai/spur/schemas/decision-catalog.schema.json"
|
|
15
|
+
version: 1
|
|
16
|
+
|
|
17
|
+
defaults:
|
|
18
|
+
# A low-confidence answer still names a slot; the workflow's own `degraded` check is what decides
|
|
19
|
+
# whether the answer is usable, so folding it into the fallback would lose the distinction.
|
|
20
|
+
minConfidence: 0
|
|
21
|
+
|
|
22
|
+
decisions:
|
|
23
|
+
kk-podcast-outline:
|
|
24
|
+
type: choice
|
|
25
|
+
description: >-
|
|
26
|
+
Which written outline option should this episode follow, given the briefing and the
|
|
27
|
+
option files as evidence?
|
|
28
|
+
parameters:
|
|
29
|
+
briefing:
|
|
30
|
+
type: string
|
|
31
|
+
description: >-
|
|
32
|
+
The briefing summary the options were written from (thesis, listener takeaways,
|
|
33
|
+
audience, target spoken duration, tone).
|
|
34
|
+
criteria:
|
|
35
|
+
a: >-
|
|
36
|
+
The narrative / storytelling arc — the option that leads with a story or a single
|
|
37
|
+
thread and earns its conclusion.
|
|
38
|
+
b: >-
|
|
39
|
+
The analytical / explainer breakdown — the option that decomposes the topic into
|
|
40
|
+
ordered mechanics a listener can follow.
|
|
41
|
+
c: >-
|
|
42
|
+
The third written alternative, when the option files include one; never chosen when
|
|
43
|
+
no `outline-option-c.md` exists.
|
|
44
|
+
fallback: a
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Publish-verdict decision for the daily content-safety gate (task 0317 R6).
|
|
2
|
+
#
|
|
3
|
+
# `kk-daily-safety` used to read the judge's self-reported `confidence` field in a shell `jq`
|
|
4
|
+
# filter on the accept edge. The verdict is now a catalog decision over the SAME artifacts as
|
|
5
|
+
# evidence, so the outcome is calibrated, ledgered (`decision_logs`) and carries a declared
|
|
6
|
+
# fallback. The fallback is `escalate`, never `pass`: a missing, unreadable or garbled report has
|
|
7
|
+
# to land on a recorded human decision, not on a silent publish.
|
|
8
|
+
#
|
|
9
|
+
# Resolution: registered through `decisions.paths` in `.spur/config.yaml`
|
|
10
|
+
# (project-relative `plugins/kk/decisions`). No maker is named here on purpose — the project
|
|
11
|
+
# (`decisions.makers` / `decisions.maker`) decides which backend answers, and spur degrades to this
|
|
12
|
+
# catalog's fallback when no maker is configured or available.
|
|
13
|
+
$schema: "@gobing-ai/spur/schemas/decision-catalog.schema.json"
|
|
14
|
+
version: 1
|
|
15
|
+
|
|
16
|
+
defaults:
|
|
17
|
+
# The gate's own review already screened the drafts; this decision only files the publish
|
|
18
|
+
# verdict, so a low-confidence model answer keeps its own value (an explicit pass/escalate/
|
|
19
|
+
# exclude) instead of being folded into the fallback.
|
|
20
|
+
minConfidence: 0
|
|
21
|
+
|
|
22
|
+
decisions:
|
|
23
|
+
kk-safety-verdict:
|
|
24
|
+
type: choice
|
|
25
|
+
description: >-
|
|
26
|
+
Final publish verdict for the reviewed article + VoiceScript pair, from the
|
|
27
|
+
content-safety report and the content-judge verdict as evidence.
|
|
28
|
+
parameters:
|
|
29
|
+
report:
|
|
30
|
+
type: string
|
|
31
|
+
description: >-
|
|
32
|
+
Summary of the safety report and the judge verdict the gate consumed (per-platform
|
|
33
|
+
decisions, judge findings, and the gate's own pass/escalate/exclude/revise/block
|
|
34
|
+
classification).
|
|
35
|
+
criteria:
|
|
36
|
+
pass: >-
|
|
37
|
+
Every platform decision is OK and the judge verdict is PASS on unchanged drafts — the
|
|
38
|
+
pair is publishable as it stands.
|
|
39
|
+
escalate: >-
|
|
40
|
+
Admitted only through a recorded human decision, or the evidence is missing, garbled or
|
|
41
|
+
contradicts itself. The safe landing for anything the evidence cannot settle.
|
|
42
|
+
exclude: >-
|
|
43
|
+
Publishable but not on every target — admission-only findings (per-platform rules,
|
|
44
|
+
formal fits) mean named platforms are dropped while the remaining targets publish at
|
|
45
|
+
recorded risk.
|
|
46
|
+
fallback: escalate
|
package/plugins/kk/plugin.json
CHANGED
|
@@ -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,49 @@ export function dailySafetyGate(args: string[]): void {
|
|
|
1907
1981
|
});
|
|
1908
1982
|
}
|
|
1909
1983
|
|
|
1984
|
+
/**
|
|
1985
|
+
* Task 0317 R6 — fold the gate's own classification, its stamp and the `kk-safety-verdict` catalog
|
|
1986
|
+
* decision into ONE accept/refuse token the `judge -> clean` guard reads as a single predicate
|
|
1987
|
+
* (ADR-115 caps that guard at 5 logical commands, so the logic lives here).
|
|
1988
|
+
*
|
|
1989
|
+
* The gate stays authoritative on `block`/`revise`; the decision can only refuse an outcome the gate
|
|
1990
|
+
* already admitted. The decision replaced the judge's self-reported `confidence` field, which the
|
|
1991
|
+
* accept guard used to re-parse in shell: its fallback is `escalate`, so missing or garbled evidence
|
|
1992
|
+
* lands on a recorded human decision instead of a silent publish.
|
|
1993
|
+
*
|
|
1994
|
+
* Writes `24-script/<date>_09_safety-verdict.status` = `accept`, or `refuse` after naming the
|
|
1995
|
+
* offending value on stderr. Never exits non-zero: routing belongs to the transitions.
|
|
1996
|
+
*/
|
|
1997
|
+
export function dailySafetyVerdict(args: string[]): void {
|
|
1998
|
+
const [workDir = '', runDate = ''] = args;
|
|
1999
|
+
const statusFile = safetyPath(workDir, runDate, 'safety-judge_status.txt');
|
|
2000
|
+
const stampFile = safetyPath(workDir, runDate, 'safety-judge_stamp.json');
|
|
2001
|
+
const decisionFile = safetyPath(workDir, runDate, 'safety-verdict.decision');
|
|
2002
|
+
const token = safetyPath(workDir, runDate, 'safety-verdict.status');
|
|
2003
|
+
const ADMITTED = new Set(['pass', 'escalate', 'exclude']);
|
|
2004
|
+
timed(workDir, 'safety-verdict-check', () => {
|
|
2005
|
+
const status = isFile(statusFile) ? readFileSync(statusFile, 'utf-8').trim() : '';
|
|
2006
|
+
const stamp = isFile(stampFile) ? statSync(stampFile).size : 0;
|
|
2007
|
+
// A missing/unreadable decision row is a refusal, never an approval: `onError: continue` on
|
|
2008
|
+
// the decide action is what can leave it absent.
|
|
2009
|
+
let verdict = '';
|
|
2010
|
+
if (isFile(decisionFile)) {
|
|
2011
|
+
const row = JSON.parse(readFileSync(decisionFile, 'utf-8')) as { value?: unknown };
|
|
2012
|
+
verdict = String(row.value ?? '');
|
|
2013
|
+
}
|
|
2014
|
+
let refusal = '';
|
|
2015
|
+
if (!ADMITTED.has(status)) {
|
|
2016
|
+
refusal = `kk-daily-safety.judge.verdict-refused: gate status [${status}] is not an admitted outcome, so these drafts must not publish`;
|
|
2017
|
+
} else if (stamp === 0) {
|
|
2018
|
+
refusal = `kk-daily-safety.judge.verdict-refused: gate stamp missing for status ${status}, so these drafts must not publish`;
|
|
2019
|
+
} else if (!ADMITTED.has(verdict)) {
|
|
2020
|
+
refusal = `kk-daily-safety.judge.decision-refused: kk-safety-verdict [${verdict}] must admit pass|escalate|exclude, so these drafts must not publish`;
|
|
2021
|
+
}
|
|
2022
|
+
if (refusal !== '') console.error(refusal);
|
|
2023
|
+
sted('write', token, () => writeRaw(token, refusal === '' ? 'accept' : 'refuse'));
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
|
|
1910
2027
|
/**
|
|
1911
2028
|
* Post-remediation bookkeeping for the `safety-revise` state: the agent edited the drafts, this
|
|
1912
2029
|
* stage checks they are still the artifacts the rest of the pipeline consumes and spends one unit
|
|
@@ -1593,6 +1593,11 @@ if (import.meta.main)
|
|
|
1593
1593
|
case 'collect-prepare':
|
|
1594
1594
|
collectPrepare(rest);
|
|
1595
1595
|
break;
|
|
1596
|
+
case 'daily-extract': {
|
|
1597
|
+
const { dailyExtract } = await loadDailyStages();
|
|
1598
|
+
dailyExtract(rest);
|
|
1599
|
+
break;
|
|
1600
|
+
}
|
|
1596
1601
|
case 'daily-plan': {
|
|
1597
1602
|
const { dailyPlan } = await loadDailyStages();
|
|
1598
1603
|
dailyPlan(rest);
|
|
@@ -1643,6 +1648,11 @@ if (import.meta.main)
|
|
|
1643
1648
|
dailySafetyGate(rest);
|
|
1644
1649
|
break;
|
|
1645
1650
|
}
|
|
1651
|
+
case 'daily-safety-verdict': {
|
|
1652
|
+
const { dailySafetyVerdict } = await loadDailyStages();
|
|
1653
|
+
dailySafetyVerdict(rest);
|
|
1654
|
+
break;
|
|
1655
|
+
}
|
|
1646
1656
|
case 'daily-safety-revise': {
|
|
1647
1657
|
const { dailySafetyRevise } = await loadDailyStages();
|
|
1648
1658
|
dailySafetyRevise(rest);
|
|
@@ -181,14 +181,17 @@ nodes:
|
|
|
181
181
|
kk-collect wrote, minus every story already selected or published for the ai-news topic (the
|
|
182
182
|
store's `selections` ledger, written by the `select` node below). Writes
|
|
183
183
|
<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.
|
|
184
|
+
pool writes [] and exits 0 — the run's no-content path, not a failure. The `daily-extract`
|
|
185
|
+
stage widens the window (×2, ×3, capped at 72h) only when the configured window's pool cannot
|
|
186
|
+
reach `episode_min_minutes` (2026-10-19): a published episode consumes more stories than a day
|
|
187
|
+
of collection supplies, so the tight window alone closed 2026-10-18/19 as no-content while
|
|
188
|
+
older unselected items sat in the store.
|
|
185
189
|
dependsOn: [read-run-date]
|
|
186
190
|
action:
|
|
187
191
|
kind: shell
|
|
188
192
|
options:
|
|
189
193
|
command: >-
|
|
190
|
-
kk
|
|
191
|
-
--out "${vars.work_dir}/10-collect/pool.json"
|
|
194
|
+
kk stage daily-extract "${vars.work_dir}" ai-news "${vars.window}" "${vars.episode_min_minutes}" "${vars.vdriver}"
|
|
192
195
|
|
|
193
196
|
- id: plan
|
|
194
197
|
description: >-
|
|
@@ -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:
|