@gobing-ai/knowledge-kit 0.1.14 → 0.1.15
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 +796 -176
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +2 -2
- package/plugins/generations/core-facts-gen/dist/index.js +2 -2
- package/plugins/generations/daily-article-gen/dist/index.js +2 -2
- package/plugins/generations/dailynews-gen/dist/index.js +72 -9
- package/plugins/generations/dailynews-gen/src/index.ts +23 -1
- package/plugins/generations/dailynews-gen/src/script-plan.ts +103 -5
- package/plugins/generations/episode-plan-gen/dist/index.js +2 -2
- package/plugins/generations/gemini-tts/dist/index.js +2 -2
- package/plugins/generations/image-gen/dist/index.js +2 -2
- package/plugins/generations/news-report-gen/dist/index.js +2 -2
- package/plugins/generations/voice-gen/dist/index.js +2 -2
- package/plugins/generations/voxcpm-tts/dist/index.js +2 -2
- package/plugins/ingestions/aihot-ingest/dist/index.js +2 -2
- package/plugins/ingestions/horizon-ingest/dist/index.js +2 -2
- package/plugins/ingestions/last30days-ingest/dist/index.js +2 -2
- package/plugins/ingestions/web-search/dist/index.js +2 -2
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/daily-scheduled-run.ts +4 -0
- package/plugins/kk/scripts/daily-stages.ts +476 -12
- package/plugins/kk/scripts/kk-workflow-stages.ts +20 -4
- package/plugins/kk/skills/content-judge/SKILL.md +5 -4
- package/plugins/kk/skills/content-judge/references/rubrics.md +14 -0
- package/plugins/kk/skills/news-editorial/SKILL.md +122 -0
- package/plugins/kk/skills/news-editorial/references/selection.md +47 -0
- package/plugins/kk/skills/news-editorial/references/style-contract.zh.md +99 -0
- package/plugins/kk/skills/publish/SKILL.md +3 -3
- package/plugins/kk/skills/web-automation/SKILL.md +229 -77
- package/plugins/kk/skills/web-automation/references/browser-launch.md +127 -36
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +4 -6
- package/plugins/kk/workflows/kk-daily-ai-voice3.yaml +183 -222
- package/plugins/kk/workflows/kk-daily-publish3.yaml +16 -8
- package/plugins/publishings/bilibili-pub/dist/index.js +5 -3
- package/plugins/publishings/bilibili-pub/dist/profile-check.js +3 -1
- package/plugins/publishings/cnblogs-pub/dist/index.js +5 -3
- package/plugins/publishings/cnblogs-pub/dist/profile-check.js +3 -1
- package/plugins/publishings/csdn-pub/dist/index.js +5 -3
- package/plugins/publishings/csdn-pub/dist/profile-check.js +5 -3
- package/plugins/publishings/emdash-pub/dist/index.js +2 -2
- package/plugins/publishings/infoq-pub/dist/index.js +5 -3
- package/plugins/publishings/infoq-pub/dist/profile-check.js +5 -3
- package/plugins/publishings/jike-pub/dist/index.js +5 -3
- package/plugins/publishings/jike-pub/dist/profile-check.js +5 -3
- package/plugins/publishings/juejin-pub/dist/index.js +5 -3
- package/plugins/publishings/juejin-pub/dist/profile-check.js +5 -3
- package/plugins/publishings/linkedin-pub/dist/index.js +5 -3
- package/plugins/publishings/linkedin-pub/dist/profile-check.js +5 -3
- package/plugins/publishings/medium-pub/dist/index.js +2 -2
- package/plugins/publishings/podcast-pub/dist/index.js +147 -4
- package/plugins/publishings/podcast-pub/src/index.ts +71 -2
- package/plugins/publishings/podcast-pub/src/map.ts +3 -0
- package/plugins/publishings/podcast-pub/src/transcript.ts +195 -0
- package/plugins/publishings/qiita-pub/dist/index.js +2 -2
- package/plugins/publishings/shipinhao-pub/dist/index.js +5 -3
- package/plugins/publishings/shipinhao-pub/dist/profile-check.js +3 -1
- package/plugins/publishings/sspai-pub/dist/index.js +5 -3
- package/plugins/publishings/sspai-pub/dist/profile-check.js +3 -1
- package/plugins/publishings/substack-pub/dist/index.js +5 -3
- package/plugins/publishings/substack-pub/dist/profile-check.js +5 -3
- package/plugins/publishings/surfdash-pub/dist/index.js +2 -2
- package/plugins/publishings/threads-pub/dist/index.js +2 -2
- package/plugins/publishings/wechatmp-pub/dist/index.js +52 -33
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +5 -3
- package/plugins/publishings/wechatmp-pub/src/flow.ts +70 -33
- package/plugins/publishings/weibo-pub/dist/index.js +5 -3
- package/plugins/publishings/weibo-pub/dist/profile-check.js +5 -3
- package/plugins/publishings/x-pub/dist/index.js +5 -3
- package/plugins/publishings/x-pub/dist/profile-check.js +5 -3
- package/plugins/publishings/xhs-pub/dist/index.js +5 -3
- package/plugins/publishings/xhs-pub/dist/profile-check.js +5 -3
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +5 -3
- package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +3 -1
- package/plugins/publishings/zenn-pub/dist/index.js +2 -2
- package/plugins/kk/scripts/daily-voice-style.zh.md +0 -70
|
@@ -55,6 +55,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
55
55
|
import { createHash } from 'node:crypto';
|
|
56
56
|
import {
|
|
57
57
|
copyFileSync,
|
|
58
|
+
cpSync,
|
|
58
59
|
existsSync,
|
|
59
60
|
mkdirSync,
|
|
60
61
|
readdirSync,
|
|
@@ -75,6 +76,7 @@ import {
|
|
|
75
76
|
coverBrief,
|
|
76
77
|
coverNormalizeBody,
|
|
77
78
|
dedupPlanByTitle,
|
|
79
|
+
EDITORIAL_SKILL_DIR,
|
|
78
80
|
expandHome,
|
|
79
81
|
fail,
|
|
80
82
|
failWith,
|
|
@@ -91,12 +93,10 @@ import {
|
|
|
91
93
|
readJson,
|
|
92
94
|
readTopicBoundary,
|
|
93
95
|
requireJson,
|
|
94
|
-
requireSidecar,
|
|
95
|
-
resolveSidecar,
|
|
96
96
|
resultPublishStatus,
|
|
97
97
|
SAFETY_PLATFORM_DECISIONS,
|
|
98
98
|
SAFETY_REVISE_COUNT,
|
|
99
|
-
|
|
99
|
+
STYLE_CONTRACT,
|
|
100
100
|
StageFailure,
|
|
101
101
|
safetyDigest,
|
|
102
102
|
safetyDraftPaths,
|
|
@@ -511,14 +511,24 @@ export function dailyArticle(args: string[]): void {
|
|
|
511
511
|
// half-ran (or the json was pruned), and returning here starves every downstream stage
|
|
512
512
|
// that reads content.json — publish-prep refuses to merge a draft it cannot read. Requiring
|
|
513
513
|
// both artifacts makes the pair self-healing instead of permanently stuck.
|
|
514
|
-
|
|
514
|
+
// The prose body is the generator's INPUT: when it is newer than content.json the pair
|
|
515
|
+
// is stale (an operator or a retry edited the headings after the render), and serving it
|
|
516
|
+
// ships last round's body to surfdash. Re-derive instead of returning.
|
|
517
|
+
const proseBody = join(workDir, '22-article', `${runDate}_06_article-write_body.md`);
|
|
518
|
+
const proseStale =
|
|
519
|
+
isFile(proseBody) &&
|
|
520
|
+
isFile(articleContent) &&
|
|
521
|
+
statSync(proseBody).mtimeMs > statSync(articleContent).mtimeMs;
|
|
522
|
+
if (isFile(articleMd) && isFile(articleContent) && !proseStale) {
|
|
515
523
|
console.log(`article cached: ${articleMd}`);
|
|
516
524
|
return;
|
|
517
525
|
}
|
|
526
|
+
if (proseStale) {
|
|
527
|
+
console.error(`article: stale pair — ${proseBody} is newer than ${articleContent}, re-deriving`);
|
|
528
|
+
}
|
|
518
529
|
// Agent-written prose body (workflow `article-write` state) replaces the mechanical
|
|
519
530
|
// per-item render when present — the 3-format split: surfdash gets an article,
|
|
520
531
|
// not a news list (2026-09-16 feedback).
|
|
521
|
-
const proseBody = join(workDir, '22-article', `${runDate}_06_article-write_body.md`);
|
|
522
532
|
kk(
|
|
523
533
|
[
|
|
524
534
|
'executor',
|
|
@@ -3999,7 +4009,8 @@ export function dailyScript(args: string[]): void {
|
|
|
3999
4009
|
// cwd = the plugin package dir, so a relative workDir would silently miss the
|
|
4000
4010
|
// plan; a plan file absent at build time reports missing-plan (template fallback).
|
|
4001
4011
|
DAILYNEWS_SCRIPT_PLAN: resolve(join(workDir, '24-script', `${runDate}_08_script-write_plan.json`)),
|
|
4002
|
-
DAILYNEWS_STYLE_FILE: resolve(
|
|
4012
|
+
DAILYNEWS_STYLE_FILE: resolve(STYLE_CONTRACT),
|
|
4013
|
+
DAILYNEWS_CURATE_EXCLUDED: resolve(join(workDir, '21-plan', `${runDate}_03_curate_excluded.json`)),
|
|
4003
4014
|
},
|
|
4004
4015
|
});
|
|
4005
4016
|
const body = readJson(contentOut)?.body;
|
|
@@ -4021,25 +4032,478 @@ export function dailyScript(args: string[]): void {
|
|
|
4021
4032
|
|
|
4022
4033
|
/** Task 0189 R10 artifacts of the content-safety verdict gate (24-script, step 09). */
|
|
4023
4034
|
/**
|
|
4024
|
-
* F613 R1: snapshot the shipped
|
|
4025
|
-
* workflow-dir var
|
|
4026
|
-
*
|
|
4027
|
-
*
|
|
4035
|
+
* F613 R1 / ADR-045: snapshot the shipped kk:news-editorial skill into the run's work dir. Spur
|
|
4036
|
+
* workflows have no workflow-dir var and kk skills are not installed for every executor, so the
|
|
4037
|
+
* writer prompts read this copy by path — and the snapshot freezes the exact policy the run's plan
|
|
4038
|
+
* was written against. `20-editorial/news-editorial/` holds SKILL.md + references; the
|
|
4039
|
+
* `_08_script-write_style.md` copy of the style contract stays the run's audit record.
|
|
4040
|
+
* Idempotent per artifact (an existing snapshot is kept); a missing skill is a broken install.
|
|
4028
4041
|
*/
|
|
4029
4042
|
export function dailyScriptStyle(args: string[]): void {
|
|
4030
4043
|
const [workDir = '', runDate = ''] = args;
|
|
4044
|
+
if (!isFile(STYLE_CONTRACT)) {
|
|
4045
|
+
failWith(
|
|
4046
|
+
1,
|
|
4047
|
+
`news-editorial skill not found — no ${STYLE_CONTRACT}; install or refresh the kk package (bun link in a checkout)`,
|
|
4048
|
+
);
|
|
4049
|
+
}
|
|
4050
|
+
const skillSnapshot = join(workDir, '20-editorial', 'news-editorial');
|
|
4051
|
+
if (isFile(join(skillSnapshot, 'SKILL.md'))) {
|
|
4052
|
+
console.log(`editorial skill cached: ${skillSnapshot}`);
|
|
4053
|
+
} else {
|
|
4054
|
+
sted('cp', skillSnapshot, () => cpSync(EDITORIAL_SKILL_DIR, skillSnapshot, { recursive: true }));
|
|
4055
|
+
console.log(`editorial skill snapshot: ${skillSnapshot}`);
|
|
4056
|
+
}
|
|
4031
4057
|
const snapshot = join(workDir, '24-script', `${runDate}_08_script-write_style.md`);
|
|
4032
4058
|
if (isFile(snapshot)) {
|
|
4033
4059
|
console.log(`script style cached: ${snapshot}`);
|
|
4034
4060
|
return;
|
|
4035
4061
|
}
|
|
4036
|
-
const sidecar = requireSidecar('', STYLE_SIDECAR, 'daily-voice-style');
|
|
4037
4062
|
const scriptDir = join(workDir, '24-script');
|
|
4038
4063
|
sted('mkdir', scriptDir, () => mkdirSync(scriptDir, { recursive: true }));
|
|
4039
|
-
sted('cp', snapshot, () => copyFileSync(
|
|
4064
|
+
sted('cp', snapshot, () => copyFileSync(STYLE_CONTRACT, snapshot));
|
|
4040
4065
|
console.log(`script style snapshot: ${snapshot}`);
|
|
4041
4066
|
}
|
|
4042
4067
|
|
|
4068
|
+
/**
|
|
4069
|
+
* 0345 R4: dry-run the dailynews-gen validator on the authored script plan so a rejected plan gets
|
|
4070
|
+
* one targeted `script-write-fix` pass instead of silently shipping the template fallback. Writes
|
|
4071
|
+
* `24-script/<date>_08_script-write_errors.json` ([{itemId, rule}]) when the plan is absent or
|
|
4072
|
+
* rejected, and removes it when the plan is accepted. The trial build goes to a scratch path so the
|
|
4073
|
+
* `script` stage's isFile cache is never primed by a check.
|
|
4074
|
+
*/
|
|
4075
|
+
export function dailyScriptCheck(args: string[]): void {
|
|
4076
|
+
const [workDir = '', runDate = '', pluginsPath = ''] = args;
|
|
4077
|
+
const planOut = join(workDir, '24-script', `${runDate}_08_script-write_plan.json`);
|
|
4078
|
+
const errorsOut = join(workDir, '24-script', `${runDate}_08_script-write_errors.json`);
|
|
4079
|
+
const writeErrors = (errors: unknown[]) => {
|
|
4080
|
+
sted('write', errorsOut, () => writeJsonText(errorsOut, errors));
|
|
4081
|
+
console.log(`script plan rejected (${errors.length} error(s)): ${errorsOut}`);
|
|
4082
|
+
};
|
|
4083
|
+
if (!isFile(planOut)) {
|
|
4084
|
+
writeErrors([{ itemId: 'episode', rule: 'missing-plan' }]);
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
const trialOut = join(workDir, '24-script', `.${runDate}_08_script-check_content.json`);
|
|
4088
|
+
const planIn = join(workDir, '21-plan', `${runDate}_03_plan_plan.json`);
|
|
4089
|
+
try {
|
|
4090
|
+
kk(['executor', 'run', 'dailynews-gen', '--in', planIn, '--out', trialOut, ...pluginsArgs(pluginsPath)], {
|
|
4091
|
+
env: {
|
|
4092
|
+
DAILYNEWS_LANGUAGE: 'zh',
|
|
4093
|
+
DAILYNEWS_DATE: runDate,
|
|
4094
|
+
DAILYNEWS_SCRIPT_PLAN: resolve(planOut),
|
|
4095
|
+
DAILYNEWS_STYLE_FILE: resolve(STYLE_CONTRACT),
|
|
4096
|
+
DAILYNEWS_CURATE_EXCLUDED: resolve(join(workDir, '21-plan', `${runDate}_03_curate_excluded.json`)),
|
|
4097
|
+
},
|
|
4098
|
+
});
|
|
4099
|
+
const metadata = readJson(trialOut)?.metadata;
|
|
4100
|
+
const source = metadata?.scriptSource;
|
|
4101
|
+
if (source !== 'plan' && source !== 'template') {
|
|
4102
|
+
failWith(1, `script-check: ${trialOut} carries no scriptSource stamp`);
|
|
4103
|
+
}
|
|
4104
|
+
if (source === 'plan') {
|
|
4105
|
+
if (isFile(errorsOut)) unlinkSync(errorsOut);
|
|
4106
|
+
console.log('script plan accepted by the validator');
|
|
4107
|
+
return;
|
|
4108
|
+
}
|
|
4109
|
+
writeErrors(Array.isArray(metadata.scriptPlanErrors) ? metadata.scriptPlanErrors : []);
|
|
4110
|
+
} finally {
|
|
4111
|
+
if (isFile(trialOut)) unlinkSync(trialOut);
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
|
|
4115
|
+
/**
|
|
4116
|
+
* 0345 R8: validate the `curate` agent output (`21-plan/<date>_03_plan_plan.json` +
|
|
4117
|
+
* `21-plan/<date>_03_curate_excluded.json`) against the QC candidates
|
|
4118
|
+
* (`21-plan/<date>_04_quality-control-content_candidates.json`). Unwraps a `Content` envelope and
|
|
4119
|
+
* dedups near-duplicate titles in place before `write` reads the plan. Writes
|
|
4120
|
+
* `21-plan/<date>_03_curate_errors.json` (`[{itemId, rule}]`) on failure and removes it on success.
|
|
4121
|
+
* With `--final`, falls back to the untranslated candidates and `[]` exclusions on failure.
|
|
4122
|
+
*/
|
|
4123
|
+
export function dailyCurateCheck(args: string[]): void {
|
|
4124
|
+
const [workDir = '', runDate = '', flag = ''] = args;
|
|
4125
|
+
if (!workDir) fail('missing work_dir');
|
|
4126
|
+
if (!runDate) fail('missing run_date');
|
|
4127
|
+
const isFinal = flag === '--final';
|
|
4128
|
+
const candidatesFile = join(workDir, '21-plan', `${runDate}_04_quality-control-content_candidates.json`);
|
|
4129
|
+
const planFile = join(workDir, '21-plan', `${runDate}_03_plan_plan.json`);
|
|
4130
|
+
const excludedFile = join(workDir, '21-plan', `${runDate}_03_curate_excluded.json`);
|
|
4131
|
+
const errFile = join(workDir, '21-plan', `${runDate}_03_curate_errors.json`);
|
|
4132
|
+
|
|
4133
|
+
const candRaw = readJson(candidatesFile);
|
|
4134
|
+
const candidateDocs: Record<string, unknown>[] = Array.isArray(candRaw)
|
|
4135
|
+
? (candRaw as Record<string, unknown>[])
|
|
4136
|
+
: Array.isArray(metadataOf(candRaw).docs)
|
|
4137
|
+
? (metadataOf(candRaw).docs as Record<string, unknown>[])
|
|
4138
|
+
: [];
|
|
4139
|
+
const candidateIds = candidateDocs.map((doc) => (typeof doc?.id === 'string' ? doc.id.trim() : '')).filter(Boolean);
|
|
4140
|
+
const candidateIdSet = new Set(candidateIds);
|
|
4141
|
+
|
|
4142
|
+
const handleErrors = (errors: { itemId: string; rule: string }[]): void => {
|
|
4143
|
+
if (isFinal) {
|
|
4144
|
+
const reason = errors.map((e) => `${e.itemId}:${e.rule}`).join(', ');
|
|
4145
|
+
console.error(
|
|
4146
|
+
`curate-check: final check failed (${reason}) — falling back to ${candidateDocs.length} untranslated candidate(s)`,
|
|
4147
|
+
);
|
|
4148
|
+
sted('write', planFile, () => writeJsonText(planFile, candidateDocs));
|
|
4149
|
+
sted('write', excludedFile, () => writeJsonText(excludedFile, []));
|
|
4150
|
+
if (isFile(errFile)) unlinkSync(errFile);
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
4153
|
+
sted('write', errFile, () => writeJsonText(errFile, errors));
|
|
4154
|
+
console.log(`curate-check: rejected (${errors.length} error(s)): ${errFile}`);
|
|
4155
|
+
};
|
|
4156
|
+
|
|
4157
|
+
if (!isFile(planFile)) {
|
|
4158
|
+
handleErrors([{ itemId: 'episode', rule: 'missing-plan' }]);
|
|
4159
|
+
return;
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
let planParsed: unknown;
|
|
4163
|
+
try {
|
|
4164
|
+
planParsed = JSON.parse(readFileSync(planFile, 'utf-8'));
|
|
4165
|
+
} catch {
|
|
4166
|
+
handleErrors([{ itemId: 'episode', rule: 'invalid-json' }]);
|
|
4167
|
+
return;
|
|
4168
|
+
}
|
|
4169
|
+
|
|
4170
|
+
let planDocs: Record<string, unknown>[];
|
|
4171
|
+
if (Array.isArray(planParsed)) {
|
|
4172
|
+
planDocs = planParsed as Record<string, unknown>[];
|
|
4173
|
+
} else if (
|
|
4174
|
+
planParsed !== null &&
|
|
4175
|
+
typeof planParsed === 'object' &&
|
|
4176
|
+
Array.isArray(metadataOf(planParsed as Record<string, unknown>).docs)
|
|
4177
|
+
) {
|
|
4178
|
+
planDocs = metadataOf(planParsed as Record<string, unknown>).docs as Record<string, unknown>[];
|
|
4179
|
+
sted('write', planFile, () => writeJsonText(planFile, planDocs));
|
|
4180
|
+
console.log(`curate-check: unwrapped Content envelope (${planDocs.length} doc(s)) in ${planFile}`);
|
|
4181
|
+
} else {
|
|
4182
|
+
handleErrors([{ itemId: 'episode', rule: 'invalid-json' }]);
|
|
4183
|
+
return;
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
for (const entry of planDocs) {
|
|
4187
|
+
if (entry === null || typeof entry !== 'object' || typeof entry.id !== 'string' || entry.id.trim() === '') {
|
|
4188
|
+
handleErrors([{ itemId: 'episode', rule: 'invalid-json' }]);
|
|
4189
|
+
return;
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
const excludedItems: { id: string; reason: string }[] = [];
|
|
4194
|
+
if (isFile(excludedFile)) {
|
|
4195
|
+
let exParsed: unknown;
|
|
4196
|
+
try {
|
|
4197
|
+
exParsed = JSON.parse(readFileSync(excludedFile, 'utf-8'));
|
|
4198
|
+
} catch {
|
|
4199
|
+
handleErrors([{ itemId: 'episode', rule: 'invalid-json' }]);
|
|
4200
|
+
return;
|
|
4201
|
+
}
|
|
4202
|
+
if (!Array.isArray(exParsed)) {
|
|
4203
|
+
handleErrors([{ itemId: 'episode', rule: 'invalid-json' }]);
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
4206
|
+
for (const entry of exParsed) {
|
|
4207
|
+
if (
|
|
4208
|
+
entry === null ||
|
|
4209
|
+
typeof entry !== 'object' ||
|
|
4210
|
+
typeof (entry as { id?: unknown }).id !== 'string' ||
|
|
4211
|
+
(entry as { id: string }).id.trim() === '' ||
|
|
4212
|
+
typeof (entry as { reason?: unknown }).reason !== 'string' ||
|
|
4213
|
+
(entry as { reason: string }).reason.trim() === ''
|
|
4214
|
+
) {
|
|
4215
|
+
handleErrors([{ itemId: 'episode', rule: 'invalid-json' }]);
|
|
4216
|
+
return;
|
|
4217
|
+
}
|
|
4218
|
+
excludedItems.push({
|
|
4219
|
+
id: (entry as { id: string }).id.trim(),
|
|
4220
|
+
reason: (entry as { reason: string }).reason.trim(),
|
|
4221
|
+
});
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
const errors: { itemId: string; rule: string }[] = [];
|
|
4226
|
+
if (planDocs.length === 0) {
|
|
4227
|
+
errors.push({ itemId: 'episode', rule: 'empty-plan' });
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
const keptIds = new Set<string>();
|
|
4231
|
+
for (const doc of planDocs) {
|
|
4232
|
+
const id = (doc.id as string).trim();
|
|
4233
|
+
keptIds.add(id);
|
|
4234
|
+
if (candidateIdSet.size > 0 && !candidateIdSet.has(id)) {
|
|
4235
|
+
errors.push({ itemId: id, rule: 'unknown-id' });
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
const excludedIds = new Set<string>();
|
|
4240
|
+
for (const ex of excludedItems) {
|
|
4241
|
+
excludedIds.add(ex.id);
|
|
4242
|
+
if (candidateIdSet.size > 0 && !candidateIdSet.has(ex.id)) {
|
|
4243
|
+
errors.push({ itemId: ex.id, rule: 'unknown-id' });
|
|
4244
|
+
}
|
|
4245
|
+
if (keptIds.has(ex.id)) {
|
|
4246
|
+
errors.push({ itemId: ex.id, rule: 'conflict-id' });
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
for (const cid of candidateIds) {
|
|
4251
|
+
if (!keptIds.has(cid) && !excludedIds.has(cid)) {
|
|
4252
|
+
errors.push({ itemId: cid, rule: 'missing-item' });
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
|
|
4256
|
+
if (errors.length > 0) {
|
|
4257
|
+
handleErrors(errors);
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4261
|
+
const { kept, dropped } = dedupPlanByTitle(planDocs);
|
|
4262
|
+
if (dropped.length > 0) {
|
|
4263
|
+
sted('write', planFile, () => writeJsonText(planFile, kept));
|
|
4264
|
+
console.error(`curate-check: dropped ${dropped.length} near-duplicate plan item(s): ${dropped.join(', ')}`);
|
|
4265
|
+
for (const droppedId of dropped) {
|
|
4266
|
+
if (!excludedIds.has(droppedId)) {
|
|
4267
|
+
excludedIds.add(droppedId);
|
|
4268
|
+
excludedItems.push({ id: droppedId, reason: '标题近似重复自动去重' });
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
sted('write', excludedFile, () => writeJsonText(excludedFile, excludedItems));
|
|
4272
|
+
} else if (!isFile(excludedFile)) {
|
|
4273
|
+
sted('write', excludedFile, () => writeJsonText(excludedFile, excludedItems));
|
|
4274
|
+
}
|
|
4275
|
+
|
|
4276
|
+
if (isFile(errFile)) unlinkSync(errFile);
|
|
4277
|
+
console.log(`curate-check: accepted (${kept.length} kept, ${excludedItems.length} excluded)`);
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
function sourceLabelForDoc(doc: Record<string, unknown>, sourceUri: string): string {
|
|
4281
|
+
const meta = metadataOf(doc);
|
|
4282
|
+
if (typeof meta.source === 'string' && meta.source.trim() !== '') {
|
|
4283
|
+
return meta.source.trim();
|
|
4284
|
+
}
|
|
4285
|
+
try {
|
|
4286
|
+
return new URL(sourceUri).hostname.replace(/^www\./, '') || '原文';
|
|
4287
|
+
} catch {
|
|
4288
|
+
return '原文';
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
|
|
4292
|
+
/** Fixed column opening line of the rendered article body: house copy, not editorial judgement (0345). */
|
|
4293
|
+
const EDITORIAL_ARTICLE_OPENING = '这里是由Robin为你带来的咸话咸说之每日AI乱炖。';
|
|
4294
|
+
|
|
4295
|
+
/**
|
|
4296
|
+
* 0345 R10: deterministically render the five sibling editorial artifacts from the master script
|
|
4297
|
+
* plan (`24-script/<date>_08_script-write_plan.json`) and the curated plan
|
|
4298
|
+
* (`21-plan/<date>_03_plan_plan.json`):
|
|
4299
|
+
* 1. `22-article/<date>_06_article-write_body.md`
|
|
4300
|
+
* 2. `23-cover/<date>_07_cover-brief.json`
|
|
4301
|
+
* 3. `25-xhs/<date>_18_xhs_plan.json` (only when `cards_enabled === 'true'`)
|
|
4302
|
+
* 4. `26-social/{x,threads}.md` (`social.short`)
|
|
4303
|
+
* 5. `26-social/{weibo,linkedin}.md` (`social.long`)
|
|
4304
|
+
* Missing master field -> skip that one artifact with a logged reason, write the others.
|
|
4305
|
+
*/
|
|
4306
|
+
export function dailyEditorialRender(args: string[]): void {
|
|
4307
|
+
const [workDir = '', runDate = '', cardsEnabled = 'false', cardsRaw = '6'] = args;
|
|
4308
|
+
if (!workDir) fail('missing work_dir');
|
|
4309
|
+
if (!runDate) fail('missing run_date');
|
|
4310
|
+
const cardsCount = Math.max(2, Number.parseInt(cardsRaw, 10) || 6);
|
|
4311
|
+
const planFile = join(workDir, '21-plan', `${runDate}_03_plan_plan.json`);
|
|
4312
|
+
const masterFile = join(workDir, '24-script', `${runDate}_08_script-write_plan.json`);
|
|
4313
|
+
|
|
4314
|
+
const master = readJson(masterFile);
|
|
4315
|
+
if (master === null) {
|
|
4316
|
+
console.log(`editorial-render: no master script plan at ${masterFile} — skipping all renders`);
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
const planRaw = readJson(planFile);
|
|
4321
|
+
const planDocs: Record<string, unknown>[] = Array.isArray(planRaw)
|
|
4322
|
+
? (planRaw as Record<string, unknown>[])
|
|
4323
|
+
: Array.isArray(metadataOf(planRaw).docs)
|
|
4324
|
+
? (metadataOf(planRaw).docs as Record<string, unknown>[])
|
|
4325
|
+
: [];
|
|
4326
|
+
const docById = new Map<string, Record<string, unknown>>();
|
|
4327
|
+
for (const doc of planDocs) {
|
|
4328
|
+
if (typeof doc?.id === 'string' && doc.id.trim() !== '') {
|
|
4329
|
+
docById.set(doc.id.trim(), doc);
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
|
|
4333
|
+
const rawItems = Array.isArray(master.items) ? (master.items as Record<string, unknown>[]) : [];
|
|
4334
|
+
const itemById = new Map<string, Record<string, unknown>>();
|
|
4335
|
+
for (const item of rawItems) {
|
|
4336
|
+
if (item !== null && typeof item === 'object' && typeof item.id === 'string' && item.id.trim() !== '') {
|
|
4337
|
+
itemById.set(item.id.trim(), item);
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
const orderedIds =
|
|
4341
|
+
planDocs.length > 0
|
|
4342
|
+
? planDocs
|
|
4343
|
+
.map((d) => (typeof d.id === 'string' ? d.id.trim() : ''))
|
|
4344
|
+
.filter((id) => id !== '' && itemById.has(id))
|
|
4345
|
+
: [...itemById.keys()];
|
|
4346
|
+
|
|
4347
|
+
// 1. Article body: 22-article/<date>_06_article-write_body.md
|
|
4348
|
+
const outro = typeof master.outro === 'string' ? master.outro.trim() : '';
|
|
4349
|
+
const articleSections: string[] = [];
|
|
4350
|
+
for (const id of orderedIds) {
|
|
4351
|
+
const doc = docById.get(id);
|
|
4352
|
+
const item = itemById.get(id);
|
|
4353
|
+
const title = typeof doc?.title === 'string' ? doc.title.trim() : '';
|
|
4354
|
+
const body = typeof doc?.body === 'string' ? doc.body.trim() : '';
|
|
4355
|
+
const lead = typeof item?.lead === 'string' ? item.lead.trim() : '';
|
|
4356
|
+
const insight = typeof item?.insight === 'string' ? item.insight.trim() : '';
|
|
4357
|
+
if (!title || !body || !lead || !insight) continue;
|
|
4358
|
+
const sourceUri = typeof doc?.sourceUri === 'string' ? doc.sourceUri.trim() : '';
|
|
4359
|
+
const sourceLine = sourceUri ? `[来源:${sourceLabelForDoc(doc ?? {}, sourceUri)}](${sourceUri})` : '';
|
|
4360
|
+
const parts = [`## ${title}`, lead, body, insight, ...(sourceLine ? [sourceLine] : [])];
|
|
4361
|
+
articleSections.push(parts.join('\n\n'));
|
|
4362
|
+
}
|
|
4363
|
+
if (articleSections.length > 0 && outro !== '') {
|
|
4364
|
+
const articleDir = join(workDir, '22-article');
|
|
4365
|
+
sted('mkdir', articleDir, () => mkdirSync(articleDir, { recursive: true }));
|
|
4366
|
+
const articleOut = join(articleDir, `${runDate}_06_article-write_body.md`);
|
|
4367
|
+
const articleText = `${[EDITORIAL_ARTICLE_OPENING, ...articleSections, outro].join('\n\n')}\n`;
|
|
4368
|
+
sted('write', articleOut, () => writeRaw(articleOut, articleText));
|
|
4369
|
+
console.log(`editorial-render: wrote article body (${articleSections.length} item(s)): ${articleOut}`);
|
|
4370
|
+
} else {
|
|
4371
|
+
console.log('editorial-render: skipping article body (missing items/doc fields or outro)');
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
// 2. Cover brief: 23-cover/<date>_07_cover-brief.json
|
|
4375
|
+
const coverSubject = typeof master.cover === 'string' ? master.cover.trim() : '';
|
|
4376
|
+
if (coverSubject !== '') {
|
|
4377
|
+
const coverDir = join(workDir, '23-cover');
|
|
4378
|
+
sted('mkdir', coverDir, () => mkdirSync(coverDir, { recursive: true }));
|
|
4379
|
+
const coverOut = join(coverDir, `${runDate}_07_cover-brief.json`);
|
|
4380
|
+
sted('write', coverOut, () => writeJsonText(coverOut, { subject: coverSubject }));
|
|
4381
|
+
console.log(`editorial-render: wrote cover brief: ${coverOut}`);
|
|
4382
|
+
} else {
|
|
4383
|
+
console.log('editorial-render: skipping cover brief (missing cover in script plan)');
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
// 3. XHS card plan: 25-xhs/<date>_18_xhs_plan.json (only when cards_enabled === 'true')
|
|
4387
|
+
if (cardsEnabled === 'true') {
|
|
4388
|
+
const cardsMeta =
|
|
4389
|
+
master.cards !== null && typeof master.cards === 'object'
|
|
4390
|
+
? (master.cards as Record<string, unknown>)
|
|
4391
|
+
: null;
|
|
4392
|
+
const cardTitle = typeof cardsMeta?.title === 'string' ? cardsMeta.title.trim() : '';
|
|
4393
|
+
const cardBody = typeof cardsMeta?.body === 'string' ? cardsMeta.body.trim() : '';
|
|
4394
|
+
const contentItems = orderedIds
|
|
4395
|
+
.map((id) => ({ id, item: itemById.get(id), doc: docById.get(id) }))
|
|
4396
|
+
.filter(({ item }) => {
|
|
4397
|
+
const c = item?.card as Record<string, unknown> | undefined;
|
|
4398
|
+
return (
|
|
4399
|
+
c !== null &&
|
|
4400
|
+
typeof c === 'object' &&
|
|
4401
|
+
typeof c.copy === 'string' &&
|
|
4402
|
+
c.copy.trim() !== '' &&
|
|
4403
|
+
Array.isArray(c.points) &&
|
|
4404
|
+
c.points.length >= 1 &&
|
|
4405
|
+
typeof c.subject === 'string' &&
|
|
4406
|
+
c.subject.trim() !== ''
|
|
4407
|
+
);
|
|
4408
|
+
});
|
|
4409
|
+
const maxContent = Math.max(1, cardsCount - 2);
|
|
4410
|
+
const chosenContent = contentItems.slice(0, maxContent);
|
|
4411
|
+
if (cardTitle !== '' && cardBody !== '' && chosenContent.length > 0) {
|
|
4412
|
+
const firstSubject =
|
|
4413
|
+
coverSubject ||
|
|
4414
|
+
((chosenContent[0]?.item?.card as { subject?: string } | undefined)?.subject ?? '').trim() ||
|
|
4415
|
+
'Minimalist geometric light sculpture on a dark studio table';
|
|
4416
|
+
const introText =
|
|
4417
|
+
typeof master.intro === 'string' && master.intro.trim() !== '' ? master.intro.trim() : cardTitle;
|
|
4418
|
+
const coverPoints = chosenContent
|
|
4419
|
+
.slice(0, 4)
|
|
4420
|
+
.map(({ item }) => ((item?.card as { copy?: string } | undefined)?.copy || '').trim())
|
|
4421
|
+
.filter(Boolean);
|
|
4422
|
+
const renderedCards: Record<string, unknown>[] = [
|
|
4423
|
+
{
|
|
4424
|
+
role: 'cover',
|
|
4425
|
+
copy: cardTitle.slice(0, 12),
|
|
4426
|
+
points: coverPoints.length >= 2 ? coverPoints : [cardTitle, introText.slice(0, 36)],
|
|
4427
|
+
layout: 'hero',
|
|
4428
|
+
subject: firstSubject,
|
|
4429
|
+
sourcePoint: introText,
|
|
4430
|
+
},
|
|
4431
|
+
...chosenContent.map(({ id, item, doc }) => {
|
|
4432
|
+
const card = item?.card as { copy: string; points: string[]; subject: string };
|
|
4433
|
+
const lead = typeof item?.lead === 'string' && item.lead.trim() !== '' ? item.lead.trim() : '';
|
|
4434
|
+
const docTitle = typeof doc?.title === 'string' && doc.title.trim() !== '' ? doc.title.trim() : id;
|
|
4435
|
+
return {
|
|
4436
|
+
role: 'content',
|
|
4437
|
+
copy: card.copy.trim(),
|
|
4438
|
+
points: card.points
|
|
4439
|
+
.map((p) => String(p).trim())
|
|
4440
|
+
.filter(Boolean)
|
|
4441
|
+
.slice(0, 4),
|
|
4442
|
+
layout: 'split',
|
|
4443
|
+
subject: card.subject.trim(),
|
|
4444
|
+
sourcePoint: lead || docTitle,
|
|
4445
|
+
itemId: id,
|
|
4446
|
+
};
|
|
4447
|
+
}),
|
|
4448
|
+
];
|
|
4449
|
+
// Pad content cards if chosenContent.length < maxContent so total length equals cardsCount
|
|
4450
|
+
while (renderedCards.length < cardsCount - 1) {
|
|
4451
|
+
const seed = renderedCards[renderedCards.length - 1] as Record<string, unknown>;
|
|
4452
|
+
renderedCards.push({ ...seed });
|
|
4453
|
+
}
|
|
4454
|
+
const closingPoints = [outro || introText, cardTitle].filter(Boolean);
|
|
4455
|
+
renderedCards.push({
|
|
4456
|
+
role: 'closing',
|
|
4457
|
+
copy: '本期小结',
|
|
4458
|
+
points: closingPoints.length >= 2 ? closingPoints : [outro || cardTitle, '感谢关注每日AI乱炖'],
|
|
4459
|
+
layout: 'summary',
|
|
4460
|
+
subject: firstSubject,
|
|
4461
|
+
sourcePoint: outro || introText,
|
|
4462
|
+
});
|
|
4463
|
+
const xhsDir = join(workDir, '25-xhs');
|
|
4464
|
+
sted('mkdir', xhsDir, () => mkdirSync(xhsDir, { recursive: true }));
|
|
4465
|
+
const xhsOut = join(xhsDir, `${runDate}_18_xhs_plan.json`);
|
|
4466
|
+
sted('write', xhsOut, () =>
|
|
4467
|
+
writeJsonText(xhsOut, {
|
|
4468
|
+
title: cardTitle,
|
|
4469
|
+
body: cardBody,
|
|
4470
|
+
cards: renderedCards,
|
|
4471
|
+
}),
|
|
4472
|
+
);
|
|
4473
|
+
console.log(`editorial-render: wrote xhs card plan (${renderedCards.length} card(s)): ${xhsOut}`);
|
|
4474
|
+
} else {
|
|
4475
|
+
console.log('editorial-render: skipping xhs card plan (missing cards.title/body or items[].card)');
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
// 4 & 5. Social posts: 26-social/{x,threads}.md and 26-social/{weibo,linkedin}.md
|
|
4480
|
+
const social =
|
|
4481
|
+
master.social !== null && typeof master.social === 'object' ? (master.social as Record<string, unknown>) : null;
|
|
4482
|
+
const socialShort = typeof social?.short === 'string' ? social.short.trim() : '';
|
|
4483
|
+
const socialDir = join(workDir, '26-social');
|
|
4484
|
+
if (socialShort !== '') {
|
|
4485
|
+
sted('mkdir', socialDir, () => mkdirSync(socialDir, { recursive: true }));
|
|
4486
|
+
for (const name of ['x.md', 'threads.md']) {
|
|
4487
|
+
const out = join(socialDir, name);
|
|
4488
|
+
sted('write', out, () => writeRaw(out, `${socialShort}\n`));
|
|
4489
|
+
}
|
|
4490
|
+
console.log('editorial-render: wrote 26-social/{x,threads}.md');
|
|
4491
|
+
} else {
|
|
4492
|
+
console.log('editorial-render: skipping social short posts (missing social.short in script plan)');
|
|
4493
|
+
}
|
|
4494
|
+
const socialLong = typeof social?.long === 'string' ? social.long.trim() : '';
|
|
4495
|
+
if (socialLong !== '') {
|
|
4496
|
+
sted('mkdir', socialDir, () => mkdirSync(socialDir, { recursive: true }));
|
|
4497
|
+
for (const name of ['weibo.md', 'linkedin.md']) {
|
|
4498
|
+
const out = join(socialDir, name);
|
|
4499
|
+
sted('write', out, () => writeRaw(out, `${socialLong}\n`));
|
|
4500
|
+
}
|
|
4501
|
+
console.log('editorial-render: wrote 26-social/{weibo,linkedin}.md');
|
|
4502
|
+
} else {
|
|
4503
|
+
console.log('editorial-render: skipping social long posts (missing social.long in script plan)');
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
|
|
4043
4507
|
export function dailyShowNotes(args: string[]): void {
|
|
4044
4508
|
const [workDir = '', runDate = '', pluginsPath = '', title = ''] = args;
|
|
4045
4509
|
const article = join(workDir, '22-article', `${runDate}_06_article_content.json`);
|
|
@@ -61,10 +61,11 @@ export const SCRIPTS_DIR = import.meta.dir;
|
|
|
61
61
|
export const VALIDATE_SIDECAR = 'validate-voicescript.ts';
|
|
62
62
|
const WRAP_SIDECAR = 'wrap-voicescript-doc.ts';
|
|
63
63
|
const RENDER_SIDECAR = 'render-md.ts';
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
export const
|
|
64
|
+
// ADR-045: daily-news editorial policy lives in the kk:news-editorial skill, shipped beside this
|
|
65
|
+
// scripts dir in the same package. Its style contract is what the `write` mode reads (via the run
|
|
66
|
+
// snapshot) and dailynews-gen validates against (DAILYNEWS_STYLE_FILE).
|
|
67
|
+
export const EDITORIAL_SKILL_DIR = join(SCRIPTS_DIR, '..', 'skills', 'news-editorial');
|
|
68
|
+
export const STYLE_CONTRACT = join(EDITORIAL_SKILL_DIR, 'references', 'style-contract.zh.md');
|
|
68
69
|
const REVISE_COUNT = '.revise-count';
|
|
69
70
|
/** Bounded draft-remediation counter for the daily safety loop (20261005). */
|
|
70
71
|
export const SAFETY_REVISE_COUNT = '.safety-revise-count';
|
|
@@ -1877,6 +1878,21 @@ if (import.meta.main)
|
|
|
1877
1878
|
dailyScriptStyle(rest);
|
|
1878
1879
|
break;
|
|
1879
1880
|
}
|
|
1881
|
+
case 'daily-script-check': {
|
|
1882
|
+
const { dailyScriptCheck } = await loadDailyStages();
|
|
1883
|
+
dailyScriptCheck(rest);
|
|
1884
|
+
break;
|
|
1885
|
+
}
|
|
1886
|
+
case 'daily-curate-check': {
|
|
1887
|
+
const { dailyCurateCheck } = await loadDailyStages();
|
|
1888
|
+
dailyCurateCheck(rest);
|
|
1889
|
+
break;
|
|
1890
|
+
}
|
|
1891
|
+
case 'daily-editorial-render': {
|
|
1892
|
+
const { dailyEditorialRender } = await loadDailyStages();
|
|
1893
|
+
dailyEditorialRender(rest);
|
|
1894
|
+
break;
|
|
1895
|
+
}
|
|
1880
1896
|
case 'daily-script': {
|
|
1881
1897
|
const { dailyScript } = await loadDailyStages();
|
|
1882
1898
|
dailyScript(rest);
|
|
@@ -21,7 +21,7 @@ LLM-as-judge is **not** a knowledge-kit plugin kind (ADR-007 amdt 2026-08-07). I
|
|
|
21
21
|
|
|
22
22
|
- A spur workflow needs a quality gate between `generate` and `publish`.
|
|
23
23
|
- A user asks to "judge", "evaluate", "quality-check", or "gate" generated content.
|
|
24
|
-
- A workflow or skill names a rubric (`tech-accuracy` / `brand-tone` / `compliance` / `general`).
|
|
24
|
+
- A workflow or skill names a rubric (`tech-accuracy` / `brand-tone` / `compliance` / `news-balance` / `general`).
|
|
25
25
|
|
|
26
26
|
## Verdict output contract
|
|
27
27
|
|
|
@@ -99,7 +99,7 @@ and `escalate` flag; treat `LOW` as a signal to demand human review before publi
|
|
|
99
99
|
1. **Read the Content** from the path given in the evaluation request (the `content.json` written
|
|
100
100
|
by the preceding `kk executor run <generator>` step).
|
|
101
101
|
2. **Select the rubric** — named in the request (`tech-accuracy`, `brand-tone`,
|
|
102
|
-
`compliance`, `general`). Default: `general`.
|
|
102
|
+
`compliance`, `news-balance`, `general`). Default: `general`.
|
|
103
103
|
3. **Evaluate each criterion** in the rubric. For each, produce a `pass` / `fail` / `warn` status
|
|
104
104
|
backed by a specific, quotable evidence reference (quote, section, claim).
|
|
105
105
|
4. **Score** — weighted aggregate per the rubric's scoring rules. `score` is 0.0–1.0.
|
|
@@ -127,7 +127,7 @@ and `escalate` flag; treat `LOW` as a signal to demand human review before publi
|
|
|
127
127
|
|
|
128
128
|
## Rubrics
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
Five built-in rubrics. Full definitions, weights, and thresholds live in
|
|
131
131
|
`references/rubrics.md`. The skill body intentionally stays lean — load the reference when
|
|
132
132
|
evaluating.
|
|
133
133
|
|
|
@@ -137,6 +137,7 @@ evaluating.
|
|
|
137
137
|
| `tech-accuracy` | Technical content | factual correctness vs. sources, no fabricated citations, code correctness |
|
|
138
138
|
| `brand-tone` | Published voice | tone consistency, banned-phrase compliance, audience fit |
|
|
139
139
|
| `compliance` | Sensitive content | no prohibited claims, disclosure presence, jurisdictional rules |
|
|
140
|
+
| `news-balance` | Daily news script plan | news-first, commentary-share, no-restatement |
|
|
140
141
|
|
|
141
142
|
## Prompt template
|
|
142
143
|
|
|
@@ -147,7 +148,7 @@ Use this template when the skill is invoked by an `agent.run` step. The workflow
|
|
|
147
148
|
You are an LLM-as-judge evaluating generated content for the knowledge-kit pipeline.
|
|
148
149
|
|
|
149
150
|
Content to evaluate: {{content_path}}
|
|
150
|
-
Rubric: {{rubric}} # general | tech-accuracy | brand-tone | compliance
|
|
151
|
+
Rubric: {{rubric}} # general | tech-accuracy | brand-tone | compliance | news-balance
|
|
151
152
|
Output verdict to: {{verdict_path}}
|
|
152
153
|
|
|
153
154
|
Load the rubric definition from plugins/kk/skills/content-judge/references/rubrics.md (section: {{rubric}}).
|
|
@@ -100,6 +100,20 @@ Thresholds: `pass_threshold = 0.85`
|
|
|
100
100
|
- `data-handling`: scan for PII that may have been carried through from input Docs (emails,
|
|
101
101
|
phone numbers, national IDs). Leakage is a `fail`.
|
|
102
102
|
|
|
103
|
+
## news-balance
|
|
104
|
+
|
|
105
|
+
For daily news episode script plans (`kk-daily-ai-voice3` advisory `editorial-judge` sensor, task 0345 R11). Evaluates the balance of news reporting, commentary, and technical depth against `kk:news-editorial`.
|
|
106
|
+
|
|
107
|
+
| Criterion | Weight | Must-pass |
|
|
108
|
+
|-----------|--------|-----------|
|
|
109
|
+
| `news-first` — `intro` and every `lead` name concrete actors and actions, not abstract essay themes | 0.30 | yes |
|
|
110
|
+
| `commentary-share` — each `insight` is ≤⅓ of the item's spoken length and contains no generic methodology or shopping-list advice | 0.25 | yes |
|
|
111
|
+
| `no-restatement` — `insight` adds judgement rather than repeating the body's facts or numbers | 0.20 | yes |
|
|
112
|
+
| `tech-depth` — mechanisms are explained in 1–2 sentences without textbook primers | 0.15 | no |
|
|
113
|
+
| `ordering` — items are ordered by news weight (urgency × impact), with the strongest concrete news item leading | 0.10 | no |
|
|
114
|
+
|
|
115
|
+
Thresholds: `pass_threshold = 0.80`
|
|
116
|
+
|
|
103
117
|
## Adding a custom rubric
|
|
104
118
|
|
|
105
119
|
1. Add a section to this file with a unique heading and a criteria table matching the schema
|