@gobing-ai/knowledge-kit 0.0.16 → 0.0.18
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/README.md +74 -0
- package/dist/index.js +46 -100
- package/package.json +19 -1
- package/plugins/generations/content-gen/dist/index.js +42 -94
- package/plugins/generations/core-facts-gen/dist/index.js +51 -100
- package/plugins/generations/daily-article-gen/dist/index.js +98 -100
- package/plugins/generations/daily-article-gen/src/index.ts +80 -6
- package/plugins/generations/dailynews-gen/dist/index.js +144 -130
- package/plugins/generations/dailynews-gen/src/script-builder.ts +141 -33
- package/plugins/generations/episode-plan-gen/dist/index.js +124 -115
- package/plugins/generations/episode-plan-gen/src/index.ts +111 -24
- package/plugins/generations/image-gen/dist/index.js +63 -115
- package/plugins/generations/news-report-gen/dist/index.js +164 -100
- package/plugins/generations/news-report-gen/src/index.ts +199 -7
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +17 -1
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/qc.py +4 -22
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +8 -0
- package/plugins/generations/voice-gen/dist/index.js +136 -117
- package/plugins/generations/voice-gen/src/concat.ts +34 -0
- package/plugins/generations/voice-gen/src/index.ts +6 -4
- package/plugins/generations/voice-gen/src/voicescript.ts +61 -0
- package/plugins/ingestions/aihot-ingest/dist/index.js +47 -97
- package/plugins/ingestions/horizon-ingest/dist/index.js +47 -96
- package/plugins/ingestions/karakeep-local/dist/index.js +217 -266
- package/plugins/ingestions/last30days-ingest/dist/index.js +42 -91
- package/plugins/ingestions/web-search/dist/index.js +68 -117
- package/plugins/kk/commands/workflow-run.md +25 -2
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/kk-workflow-stages.ts +522 -163
- package/plugins/kk/scripts/validate-voicescript.ts +55 -0
- package/plugins/kk/skills/publish/SKILL.md +80 -0
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +125 -120
- package/plugins/kk/workflows/leftover-publish-fanout-example.yaml +53 -0
- package/plugins/publishings/emdash-pub/dist/index.js +42 -91
- package/plugins/publishings/infoq-pub/dist/index.js +21994 -0
- package/plugins/publishings/infoq-pub/package.json +17 -0
- package/plugins/publishings/infoq-pub/plugin.json +7 -0
- package/plugins/publishings/infoq-pub/src/index.ts +123 -0
- package/plugins/publishings/infoq-pub/tsconfig.json +4 -0
- package/plugins/publishings/juejin-pub/dist/index.js +21994 -0
- package/plugins/publishings/juejin-pub/package.json +17 -0
- package/plugins/publishings/juejin-pub/plugin.json +7 -0
- package/plugins/publishings/juejin-pub/src/index.ts +123 -0
- package/plugins/publishings/juejin-pub/tsconfig.json +4 -0
- package/plugins/publishings/medium-pub/dist/index.js +22041 -0
- package/plugins/publishings/medium-pub/package.json +17 -0
- package/plugins/publishings/medium-pub/plugin.json +7 -0
- package/plugins/publishings/medium-pub/src/index.ts +215 -0
- package/plugins/publishings/medium-pub/tsconfig.json +4 -0
- package/plugins/publishings/podcast-pub/dist/index.js +183 -123
- package/plugins/publishings/podcast-pub/src/index.ts +47 -3
- package/plugins/publishings/podcast-pub/src/show-notes.ts +153 -34
- package/plugins/publishings/qiita-pub/dist/index.js +42 -91
- package/plugins/publishings/substack-pub/dist/index.js +21994 -0
- package/plugins/publishings/substack-pub/package.json +17 -0
- package/plugins/publishings/substack-pub/plugin.json +7 -0
- package/plugins/publishings/substack-pub/src/index.ts +123 -0
- package/plugins/publishings/substack-pub/tsconfig.json +4 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +172 -103
- package/plugins/publishings/surfdash-pub/src/index.ts +32 -2
- package/plugins/publishings/wechatmp-pub/dist/index.js +22119 -0
- package/plugins/publishings/wechatmp-pub/package.json +17 -0
- package/plugins/publishings/wechatmp-pub/plugin.json +7 -0
- package/plugins/publishings/wechatmp-pub/src/index.ts +304 -0
- package/plugins/publishings/wechatmp-pub/tsconfig.json +4 -0
- package/plugins/publishings/x-pub/dist/index.js +22110 -0
- package/plugins/publishings/x-pub/package.json +17 -0
- package/plugins/publishings/x-pub/plugin.json +7 -0
- package/plugins/publishings/x-pub/src/index.ts +222 -0
- package/plugins/publishings/x-pub/tsconfig.json +4 -0
- package/plugins/publishings/xhs-pub/dist/index.js +21994 -0
- package/plugins/publishings/xhs-pub/package.json +17 -0
- package/plugins/publishings/xhs-pub/plugin.json +7 -0
- package/plugins/publishings/xhs-pub/src/index.ts +123 -0
- package/plugins/publishings/xhs-pub/tsconfig.json +4 -0
- package/plugins/publishings/zenn-pub/dist/index.js +42 -91
|
@@ -118,6 +118,59 @@ function assertCrossfade(value: number | undefined): void {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// ─── Content checks (task 0139 R3) ───────────────────────────────────────────
|
|
122
|
+
// Deterministic spoken-copy gates. The workflow wires this validator at the end of the script
|
|
123
|
+
// stage's fresh write and again in wrap-docs (which covers a HITL-edited YAML and the script cache
|
|
124
|
+
// path), so a voicescript that reaches audio carries no title echo, no duplicated segment, no
|
|
125
|
+
// research scaffolding and no colon-mangled URL. Every failure names the segment index and reason.
|
|
126
|
+
|
|
127
|
+
/** Punctuation/space the translator drifts between a title and its body restatement. */
|
|
128
|
+
const CONTENT_ECHO_NOISE_RE = /[,。、::;;!!??…\s「」『』【】]/g;
|
|
129
|
+
/** Research scaffold labels the plan-translate stage must not emit as spoken copy. */
|
|
130
|
+
const CONTENT_SCAFFOLD_LABEL_RE =
|
|
131
|
+
/^(?:\*\*)?[「『]?(?:背景|潜在影响|可做角度|为什么现在值得注意|社区讨论)[」』]?(?:\*\*)?(?:[::,,]|\s|$)/;
|
|
132
|
+
/** `标签,` metadata tails — the plan item's tag line spoken aloud. */
|
|
133
|
+
const CONTENT_TAG_TAIL_RE = /标签[::,,]/;
|
|
134
|
+
/** `wss,//` / `https。//` — the translator replacing a URL colon with a Chinese comma. */
|
|
135
|
+
const CONTENT_URL_MANGLE_RE = /[,。]\/\//;
|
|
136
|
+
|
|
137
|
+
function assertSegmentContent(script: VoiceScript): void {
|
|
138
|
+
const seen = new Map<string, number>();
|
|
139
|
+
for (let idx = 0; idx < script.segments.length; idx++) {
|
|
140
|
+
const text = (script.segments[idx]?.text ?? '').trim();
|
|
141
|
+
if (text === '') continue;
|
|
142
|
+
|
|
143
|
+
const previous = seen.get(text);
|
|
144
|
+
if (previous !== undefined) {
|
|
145
|
+
throw new Error(`content check failed: segment ${idx} duplicates segment ${previous} verbatim`);
|
|
146
|
+
}
|
|
147
|
+
seen.set(text, idx);
|
|
148
|
+
|
|
149
|
+
// Title echo: the 【lead】 content reappearing immediately after the lead, punctuation-drift
|
|
150
|
+
// tolerant — the same comparison `stripLeadingTitleEcho` uses on the producer side.
|
|
151
|
+
const lead = text.match(/^[^【]*【([^】]{4,80})】[,。、\s]*/);
|
|
152
|
+
if (lead !== null) {
|
|
153
|
+
const bracketed = (lead[1] ?? '').replace(CONTENT_ECHO_NOISE_RE, '');
|
|
154
|
+
const rest = text.slice(lead[0].length).replace(CONTENT_ECHO_NOISE_RE, '');
|
|
155
|
+
if (bracketed.length >= 4 && rest.startsWith(bracketed)) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`content check failed: segment ${idx} repeats its 【${lead[1]}】 title in the body text`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (CONTENT_SCAFFOLD_LABEL_RE.test(text)) {
|
|
163
|
+
throw new Error(`content check failed: segment ${idx} opens with a research scaffold label`);
|
|
164
|
+
}
|
|
165
|
+
if (CONTENT_TAG_TAIL_RE.test(text)) {
|
|
166
|
+
throw new Error(`content check failed: segment ${idx} carries a 标签 metadata tail`);
|
|
167
|
+
}
|
|
168
|
+
if (CONTENT_URL_MANGLE_RE.test(text)) {
|
|
169
|
+
throw new Error(`content check failed: segment ${idx} has a colon-mangled URL`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
121
174
|
function validateVoiceScript(script: VoiceScript): void {
|
|
122
175
|
if (!script || typeof script !== 'object') {
|
|
123
176
|
throw new Error('VoiceScript must be an object');
|
|
@@ -173,6 +226,8 @@ function validateVoiceScript(script: VoiceScript): void {
|
|
|
173
226
|
assertEngine(segment.engine, `Segment at index ${idx}`);
|
|
174
227
|
assertLanguage(segment.language, `Segment at index ${idx}`);
|
|
175
228
|
}
|
|
229
|
+
|
|
230
|
+
assertSegmentContent(script);
|
|
176
231
|
}
|
|
177
232
|
|
|
178
233
|
function main(): void {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publish
|
|
3
|
+
description: >-
|
|
4
|
+
Thin operator surface for leftover publish channels — "publish this draft to
|
|
5
|
+
Medium", "fan out to Qiita and Medium", "prepare a Juejin draft". Shells
|
|
6
|
+
`kk executor run` / `kk fanout` for file-cli and script channels; sequences
|
|
7
|
+
agent-default CMS channels as workflow `agent.run` steps. Does not own
|
|
8
|
+
transport.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# publish — leftover channel operator surface
|
|
12
|
+
|
|
13
|
+
## Purpose
|
|
14
|
+
|
|
15
|
+
Drive harvested `kind: publish` plugins after a draft exists. **The plugin owns
|
|
16
|
+
transport and Result.** This skill selects the channel, confirms draft vs live,
|
|
17
|
+
and shells `kk` — it does not call an LLM from inside a plugin process and does
|
|
18
|
+
not add a `kk publish` noun.
|
|
19
|
+
|
|
20
|
+
## Channels
|
|
21
|
+
|
|
22
|
+
| Channel | Plugin | Transport | How this skill invokes it |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| medium | `medium-pub` | `file-cli` (token API) | `kk executor run medium-pub --in <content.json> --out <result.json>` |
|
|
25
|
+
| wechatmp | `wechatmp-pub` | `file-cli` (`draft/add`) | `kk executor run wechatmp-pub` (default operation `draft`) |
|
|
26
|
+
| x post | `x-pub` | `file-cli` (`POST /2/tweets`) | `kk executor run x-pub` (`options.operation` omitted / `post`) |
|
|
27
|
+
| x article | `x-pub` | `browser` / `agent` | workflow `agent.run` — not `kk fanout` |
|
|
28
|
+
| juejin | `juejin-pub` | `browser` / `agent` | workflow `agent.run` |
|
|
29
|
+
| infoq | `infoq-pub` | `browser` / `agent` | workflow `agent.run` |
|
|
30
|
+
| substack | `substack-pub` | `browser` / `agent` | workflow `agent.run` |
|
|
31
|
+
| xhs | `xhs-pub` | `browser` / `agent` | workflow `agent.run` |
|
|
32
|
+
|
|
33
|
+
Already-harvested file-cli channels (`qiita-pub`, `zenn-pub`, `surfdash-pub`,
|
|
34
|
+
`emdash-pub`, `podcast-pub`) compose the same way.
|
|
35
|
+
|
|
36
|
+
## Do not
|
|
37
|
+
|
|
38
|
+
- Embed Playwright selectors or reimplement a transport here.
|
|
39
|
+
- Spawn an LLM from `kk fanout` (ADR-007 R4). Agent-default channels are
|
|
40
|
+
sequential workflow `agent.run` steps — see
|
|
41
|
+
`plugins/kk/workflows/leftover-publish-fanout-example.yaml`.
|
|
42
|
+
- Add a first-layer `kk publish` command.
|
|
43
|
+
- Put secrets in `--in` JSON or `plugin.json`. Env only.
|
|
44
|
+
|
|
45
|
+
## Confirmation
|
|
46
|
+
|
|
47
|
+
Default Result is **draft / unpublished**. Live publish (`public`, WeChat
|
|
48
|
+
`freepublish/submit`, CMS submit) requires an explicit operator confirmation
|
|
49
|
+
in this skill before the plugin is invoked with a live status. Under
|
|
50
|
+
`KK_NONINTERACTIVE=1`, proceed only when the `--in` payload already carries
|
|
51
|
+
the confirmed live status; otherwise fail naming the switch.
|
|
52
|
+
|
|
53
|
+
## Invocation
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
<channel> [--in <content.json>] [--out <result.json>] [--live]
|
|
57
|
+
[--fanout <channel>,<channel>]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
1. Resolve `--in` Content (required).
|
|
61
|
+
2. If `--fanout` lists two or more **file-cli or script** channels: run
|
|
62
|
+
`kk fanout --in <content.json> --targets <list> --out <aggregate.json>`.
|
|
63
|
+
Each target contributes one `targets[]` Result entry.
|
|
64
|
+
3. If the channel's default driver is `agent`: do **not** call `kk fanout`.
|
|
65
|
+
Tell the operator (or the workflow) to run the page against
|
|
66
|
+
`~/.config/kk/browser-profiles/<channel>` then `kk executor run <plugin>`
|
|
67
|
+
so the plugin writes Result. The example workflow sequences that as
|
|
68
|
+
`agent.run`.
|
|
69
|
+
4. Otherwise: `kk executor run <plugin> --in … --out …`.
|
|
70
|
+
5. Report `--out` `ok`, `target`, `id`/`url`, and `error` verbatim on failure.
|
|
71
|
+
|
|
72
|
+
## Env (plugin-owned; never echoed)
|
|
73
|
+
|
|
74
|
+
| Plugin | Required |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| medium-pub | `MEDIUM_INTEGRATION_TOKEN` |
|
|
77
|
+
| wechatmp-pub | `WECHAT_APP_ID`, `WECHAT_APP_SECRET` |
|
|
78
|
+
| x-pub `post` | `X_API_KEY`, `X_API_SECRET`, `X_ACCESS_TOKEN`, `X_ACCESS_TOKEN_SECRET` |
|
|
79
|
+
|
|
80
|
+
Missing env → plugin exit 1, `Result.ok false`, error names the variable.
|
|
@@ -19,7 +19,12 @@ vars:
|
|
|
19
19
|
limit: "10"
|
|
20
20
|
language: "zh"
|
|
21
21
|
voice_profile: "robin-news"
|
|
22
|
-
|
|
22
|
+
# 20260917: voice driver switch — ominivoice (default, omni-voice-gen) | voicebox (voice-gen).
|
|
23
|
+
vdriver: "ominivoice"
|
|
24
|
+
# 20260917 review #7: 8s intro sting prepended to every daily output; empty = skip.
|
|
25
|
+
intro_music: ".works/kk-daily-ai-voice/AI每日资讯_8秒播客片头.mp3"
|
|
26
|
+
# 20260917 review #4/#6: surfdash checkout the patch stage commits to.
|
|
27
|
+
surfdash_root: "/Users/robin/xprojects/surfdash"
|
|
23
28
|
title: ""
|
|
24
29
|
cursor: ""
|
|
25
30
|
state_file: ""
|
|
@@ -28,6 +33,7 @@ vars:
|
|
|
28
33
|
cover_enabled: "true"
|
|
29
34
|
plan_max_items: "20"
|
|
30
35
|
plan_translate_enabled: "true"
|
|
36
|
+
article_write_enabled: "true"
|
|
31
37
|
plugins_path: ""
|
|
32
38
|
last30days_topic: "AI news"
|
|
33
39
|
horizon_hours: "24"
|
|
@@ -35,10 +41,12 @@ vars:
|
|
|
35
41
|
last30days_enabled: "false"
|
|
36
42
|
run_date: ""
|
|
37
43
|
agent: "inline"
|
|
38
|
-
surfdash_post_path: ""
|
|
39
44
|
surfdash_en_post_path: ""
|
|
40
45
|
surfdash_ja_post_path: ""
|
|
41
|
-
|
|
46
|
+
# 20260917 funnel: allowlist widened to the whole AI taxonomy in lockstep with
|
|
47
|
+
# episode-plan-gen's DEFAULT_FILTER_CONFIG — genuine technical/research stories died on
|
|
48
|
+
# category:not-in-allowlist. `non-ai` stays excluded (that is the point of the gate).
|
|
49
|
+
qc_categories: "ai-news,ai-technical,ai-research,ai-industry"
|
|
42
50
|
# 0098 dogfood calibration (r7, owner-approved 0099): quality 3→2, impact 2→1 — the shipped
|
|
43
51
|
# defaults rejected 20/20 live candidates (strongest ai-news items died solely on impact:1<2).
|
|
44
52
|
# These pins must move in lockstep with episode-plan-gen's DEFAULT_FILTER_CONFIG (env wins).
|
|
@@ -121,15 +129,33 @@ states:
|
|
|
121
129
|
options:
|
|
122
130
|
role: scribe
|
|
123
131
|
agent: ${vars.agent}
|
|
132
|
+
# ADR-115 error tier: keep this prompt <= 1000 chars (0133 R3; 0138 R4a adds the copy discipline).
|
|
124
133
|
input: |
|
|
125
|
-
Read ${vars.work_dir}/2-plan/${vars.run_date}_04_quality-control-content_candidates.json —
|
|
126
|
-
For
|
|
127
|
-
Titles
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
Read ${vars.work_dir}/2-plan/${vars.run_date}_04_quality-control-content_candidates.json — metadata.docs (Doc[]) is the plan; don't edit it.
|
|
135
|
+
For items with needsTranslation, translate "title", "body", and "metadata.brief" (when present) into natural simplified Chinese.
|
|
136
|
+
Titles read as a news event (subject + action + object) — e.g. "OpenAI 发布 Sora 应用,集成 Sora 2 模型". Keep product/model/company names verbatim, preserve body URLs, use 「」 not ASCII double quotes.
|
|
137
|
+
Copy discipline: reader-facing news copy — no scaffold labels (「可做角度」「为什么现在值得注意」「社区讨论」「背景」「潜在影响」「影响」), no duplicated labels or hedging (「读者宜另行核实」「仍待验证」) — merge their substance into prose or drop.
|
|
138
|
+
Keep other fields (id, sourceUri, mediaType, metadata), technical terms as-is; no trailing commas or comments.
|
|
139
|
+
Output strictly valid JSON — a same-length, same-order array with the same ids — to ${vars.work_dir}/2-plan/${vars.run_date}_03_plan_plan.json.
|
|
130
140
|
expectFile: ${vars.work_dir}/2-plan/${vars.run_date}_03_plan_plan.json
|
|
131
141
|
allowAppend: false
|
|
132
142
|
|
|
143
|
+
- id: article-write
|
|
144
|
+
description: "Write the morning-news prose article from the translated plan via agent.run (3-format split: surfdash gets prose, not a news list)"
|
|
145
|
+
onEnter:
|
|
146
|
+
- kind: agent.run
|
|
147
|
+
options:
|
|
148
|
+
role: scribe
|
|
149
|
+
agent: ${vars.agent}
|
|
150
|
+
# ADR-115 error tier: keep this prompt <= 1000 chars (0133 R3).
|
|
151
|
+
input: |
|
|
152
|
+
Read ${vars.work_dir}/2-plan/${vars.run_date}_03_plan_plan.json — metadata.docs (Doc[]) is the translated plan; don't edit it.
|
|
153
|
+
Write a morning-news prose article in natural simplified Chinese: title line "# 每日 AI 资讯 — ${vars.run_date}", an intro paragraph (2–3 sentences), then one section per item (## numbered title, 2–4 sentences of flowing prose, source link [来源:<label>](<sourceUri>) inline), a closing paragraph tying items together.
|
|
154
|
+
Narrative register — smooth transitions between items, no list-like bulleting, no scaffold labels (「可做角度」「背景」「潜在影响」), no hedging (「读者宜另行核实」「仍待验证」). Keep facts/URLs from the plan verbatim; product/model names as-is; 「」 quotes.
|
|
155
|
+
Output the markdown to ${vars.work_dir}/2-article/${vars.run_date}_06_article-write_body.md.
|
|
156
|
+
expectFile: ${vars.work_dir}/2-article/${vars.run_date}_06_article-write_body.md
|
|
157
|
+
allowAppend: false
|
|
158
|
+
|
|
133
159
|
- id: article
|
|
134
160
|
description: "Generate the dated daily article from the episode plan (daily-article-gen template)"
|
|
135
161
|
onEnter:
|
|
@@ -203,7 +229,20 @@ states:
|
|
|
203
229
|
bun "$st" daily-wrap-docs "${vars.work_dir}" "${vars.run_date}" "${vars.voice_profile}"
|
|
204
230
|
|
|
205
231
|
- id: generate
|
|
206
|
-
description: "Invoke voice
|
|
232
|
+
description: "Invoke voice driver plugin (vdriver var: ominivoice -> omni-voice-gen, voicebox -> voice-gen) to generate audio"
|
|
233
|
+
onEnter:
|
|
234
|
+
- kind: shell
|
|
235
|
+
options:
|
|
236
|
+
command: >-
|
|
237
|
+
kbin="$(command -v kk 2>/dev/null)";
|
|
238
|
+
pkg="$(dirname "$(dirname "$(realpath "$kbin" 2>/dev/null)")")";
|
|
239
|
+
st="$pkg/plugins/kk/scripts/kk-workflow-stages.ts";
|
|
240
|
+
[ -f "$st" ] || st="plugins/kk/scripts/kk-workflow-stages.ts";
|
|
241
|
+
[ -f "$st" ] || echo "kk-workflow-stages.ts not found at $st — install or refresh the kk package (bun link in a checkout)" >&2;
|
|
242
|
+
OMNIVOICE_SPEED=1.06 bun "$st" daily-generate "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}" "${vars.transcode_mp3}" "${vars.vdriver}"
|
|
243
|
+
|
|
244
|
+
- id: intro-music
|
|
245
|
+
description: "Prepend the 8s intro sting (intro_music var; empty = skip) to the generated wav, update duration/audioPath"
|
|
207
246
|
onEnter:
|
|
208
247
|
- kind: shell
|
|
209
248
|
options:
|
|
@@ -213,7 +252,7 @@ states:
|
|
|
213
252
|
st="$pkg/plugins/kk/scripts/kk-workflow-stages.ts";
|
|
214
253
|
[ -f "$st" ] || st="plugins/kk/scripts/kk-workflow-stages.ts";
|
|
215
254
|
[ -f "$st" ] || echo "kk-workflow-stages.ts not found at $st — install or refresh the kk package (bun link in a checkout)" >&2;
|
|
216
|
-
bun "$st" daily-
|
|
255
|
+
bun "$st" daily-intro "${vars.work_dir}" "${vars.run_date}" "${vars.intro_music}"
|
|
217
256
|
|
|
218
257
|
- id: quality-control
|
|
219
258
|
description: "Verify audio quality, speech rate, and silence/repetition QC metrics"
|
|
@@ -293,8 +332,8 @@ states:
|
|
|
293
332
|
[ -f "$st" ] || echo "kk-workflow-stages.ts not found at $st — install or refresh the kk package (bun link in a checkout)" >&2;
|
|
294
333
|
bun "$st" daily-publish-classify "${vars.work_dir}" "${vars.run_date}"
|
|
295
334
|
|
|
296
|
-
- id:
|
|
297
|
-
description: "
|
|
335
|
+
- id: patch-surfdash
|
|
336
|
+
description: "20260917 #4/#6 — patch published surfdash posts: podcast cover URL into image/og_image frontmatter + podcast cross-link (zh/en/ja), commit+push"
|
|
298
337
|
onEnter:
|
|
299
338
|
- kind: shell
|
|
300
339
|
options:
|
|
@@ -304,10 +343,10 @@ states:
|
|
|
304
343
|
st="$pkg/plugins/kk/scripts/kk-workflow-stages.ts";
|
|
305
344
|
[ -f "$st" ] || st="plugins/kk/scripts/kk-workflow-stages.ts";
|
|
306
345
|
[ -f "$st" ] || echo "kk-workflow-stages.ts not found at $st — install or refresh the kk package (bun link in a checkout)" >&2;
|
|
307
|
-
bun "$st" daily-
|
|
346
|
+
bun "$st" daily-patch-surfdash "${vars.work_dir}" "${vars.run_date}" "${vars.surfdash_root}"
|
|
308
347
|
|
|
309
|
-
- id:
|
|
310
|
-
description: "
|
|
348
|
+
- id: publish-partial
|
|
349
|
+
description: "Partial publish — at least one target published, at least one failed; echo warning, write a .spur/run marker, preserve result.json for --retry-from, continue to done (task 0118: surfdash failure = no-URL fallback notes; podcast failure = its own partial)"
|
|
311
350
|
onEnter:
|
|
312
351
|
- kind: shell
|
|
313
352
|
options:
|
|
@@ -317,10 +356,10 @@ states:
|
|
|
317
356
|
st="$pkg/plugins/kk/scripts/kk-workflow-stages.ts";
|
|
318
357
|
[ -f "$st" ] || st="plugins/kk/scripts/kk-workflow-stages.ts";
|
|
319
358
|
[ -f "$st" ] || echo "kk-workflow-stages.ts not found at $st — install or refresh the kk package (bun link in a checkout)" >&2;
|
|
320
|
-
bun "$st" daily-
|
|
359
|
+
bun "$st" daily-publish-partial "${vars.work_dir}" "${vars.run_date}"
|
|
321
360
|
|
|
322
|
-
- id:
|
|
323
|
-
description: "
|
|
361
|
+
- id: run-report
|
|
362
|
+
description: "Assemble the per-run report (scores+aggregates+rejected counts+step timing) from existing artifacts — news-report-gen; output-only, never throws; 0117"
|
|
324
363
|
onEnter:
|
|
325
364
|
- kind: shell
|
|
326
365
|
options:
|
|
@@ -330,32 +369,10 @@ states:
|
|
|
330
369
|
st="$pkg/plugins/kk/scripts/kk-workflow-stages.ts";
|
|
331
370
|
[ -f "$st" ] || st="plugins/kk/scripts/kk-workflow-stages.ts";
|
|
332
371
|
[ -f "$st" ] || echo "kk-workflow-stages.ts not found at $st — install or refresh the kk package (bun link in a checkout)" >&2;
|
|
333
|
-
bun "$st" daily-
|
|
334
|
-
- kind: file.read.into-var
|
|
335
|
-
options:
|
|
336
|
-
path: ${vars.work_dir}/3-publish/${vars.run_date}_20_translate-en_zh-post-path.txt
|
|
337
|
-
var: surfdash_post_path
|
|
338
|
-
- kind: file.read.into-var
|
|
339
|
-
options:
|
|
340
|
-
path: ${vars.work_dir}/3-publish/${vars.run_date}_20_translate-en_en-post-path.txt
|
|
341
|
-
var: surfdash_en_post_path
|
|
372
|
+
bun "$st" daily-run-report "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}" "${vars.qc_min_quality}" "${vars.qc_min_importance}" "${vars.qc_min_urgency}" "${vars.qc_min_impact}"
|
|
342
373
|
|
|
343
|
-
- id: translate
|
|
344
|
-
description: "
|
|
345
|
-
onEnter:
|
|
346
|
-
- kind: agent.run
|
|
347
|
-
options:
|
|
348
|
-
role: scribe
|
|
349
|
-
agent: ${vars.agent}
|
|
350
|
-
input: |
|
|
351
|
-
Translate the English draft post at ${vars.surfdash_en_post_path} from Chinese to English.
|
|
352
|
-
Edit the file in place: translate the title in frontmatter and the whole markdown body to natural English. Keep frontmatter keys, dates, URLs, code, and the assets/ image reference untouched. Keep the draft: true flag.
|
|
353
|
-
Read the surfdash translator glossary if present at the surfdash checkout root under .agents/skills/surfdash-translator/glossary.md and apply its terminology.
|
|
354
|
-
Do not wait for operator confirmation. The file must end fully in English.
|
|
355
|
-
expectFile: ${vars.surfdash_en_post_path}
|
|
356
|
-
|
|
357
|
-
- id: translate-ja
|
|
358
|
-
description: "Scaffold ja draft from the zh article via surfdash-pub scaffold-locale (sed-derived path as cache pre-check; plugin's draftPath is authoritative once scaffolded)"
|
|
374
|
+
- id: translate
|
|
375
|
+
description: "Locales — surfdash-pub scaffolds en/ja drafts (deterministic, plugin-owned), agent translates both, sync into the surfdash CMS"
|
|
359
376
|
onEnter:
|
|
360
377
|
- kind: shell
|
|
361
378
|
options:
|
|
@@ -365,29 +382,26 @@ states:
|
|
|
365
382
|
st="$pkg/plugins/kk/scripts/kk-workflow-stages.ts";
|
|
366
383
|
[ -f "$st" ] || st="plugins/kk/scripts/kk-workflow-stages.ts";
|
|
367
384
|
[ -f "$st" ] || echo "kk-workflow-stages.ts not found at $st — install or refresh the kk package (bun link in a checkout)" >&2;
|
|
368
|
-
bun "$st" daily-translate-
|
|
385
|
+
bun "$st" daily-translate-localize "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}"
|
|
369
386
|
- kind: file.read.into-var
|
|
370
387
|
options:
|
|
371
|
-
path: ${vars.work_dir}/3-publish/${vars.run_date}
|
|
388
|
+
path: ${vars.work_dir}/3-publish/${vars.run_date}_20_translate-localize_en-post-path.txt
|
|
389
|
+
var: surfdash_en_post_path
|
|
390
|
+
- kind: file.read.into-var
|
|
391
|
+
options:
|
|
392
|
+
path: ${vars.work_dir}/3-publish/${vars.run_date}_20_translate-localize_ja-post-path.txt
|
|
372
393
|
var: surfdash_ja_post_path
|
|
373
|
-
|
|
374
|
-
- id: translate-ja-agent
|
|
375
|
-
description: "Translate the ja draft in place via agent.run"
|
|
376
|
-
onEnter:
|
|
377
394
|
- kind: agent.run
|
|
378
395
|
options:
|
|
379
396
|
role: scribe
|
|
380
397
|
agent: ${vars.agent}
|
|
381
398
|
input: |
|
|
382
|
-
Translate
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
- id: translate-sync
|
|
389
|
-
description: "Sync the zh post and its en/ja locale variants into the surfdash CMS (surfdash-pub sync operation)"
|
|
390
|
-
onEnter:
|
|
399
|
+
Translate en/ja draft posts of the zh article — two independent files:
|
|
400
|
+
English draft: ${vars.surfdash_en_post_path}
|
|
401
|
+
Japanese draft: ${vars.surfdash_ja_post_path}
|
|
402
|
+
For each: if its path above is empty, skip that locale. Otherwise edit the file in place — translate the frontmatter title and the whole markdown body to natural English/Japanese respectively. Keep frontmatter keys, dates, URLs, code, and the assets/ image reference untouched. Set draft: false (the translated post publishes to the site) and remove any canonical: frontmatter line pointing at a different locale's URL.
|
|
403
|
+
Keep frontmatter valid YAML: double-quote any value containing ': '. Read the surfdash translator glossary if present at the surfdash checkout root under .agents/skills/surfdash-translator/glossary.md and apply its terminology.
|
|
404
|
+
Do not wait for operator confirmation. Each edited file must end fully in its target language.
|
|
391
405
|
- kind: shell
|
|
392
406
|
options:
|
|
393
407
|
command: >-
|
|
@@ -396,7 +410,7 @@ states:
|
|
|
396
410
|
st="$pkg/plugins/kk/scripts/kk-workflow-stages.ts";
|
|
397
411
|
[ -f "$st" ] || st="plugins/kk/scripts/kk-workflow-stages.ts";
|
|
398
412
|
[ -f "$st" ] || echo "kk-workflow-stages.ts not found at $st — install or refresh the kk package (bun link in a checkout)" >&2;
|
|
399
|
-
bun "$st" daily-translate-sync "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}" "${vars.
|
|
413
|
+
bun "$st" daily-translate-sync "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}" "${vars.work_dir}/3-publish/${vars.run_date}_20_translate-localize_zh-post-path.txt"
|
|
400
414
|
|
|
401
415
|
- id: done
|
|
402
416
|
description: "Terminal — Daily AI news pipeline completed (collect → QC'd plan → article/cover/script/voice → publish/locales)"
|
|
@@ -442,17 +456,41 @@ transitions:
|
|
|
442
456
|
options:
|
|
443
457
|
command: 'test "${vars.plan_translate_enabled}" = "true"'
|
|
444
458
|
|
|
459
|
+
- from: quality-control-content
|
|
460
|
+
to: article-write
|
|
461
|
+
description: "Candidates passed QC, plan-translate disabled, prose enabled -> straight to article-write"
|
|
462
|
+
guard:
|
|
463
|
+
kind: shell
|
|
464
|
+
options:
|
|
465
|
+
command: 'test "${vars.plan_translate_enabled}" != "true" && test "${vars.article_write_enabled}" = "true"'
|
|
466
|
+
|
|
445
467
|
- from: quality-control-content
|
|
446
468
|
to: article
|
|
447
|
-
description: "
|
|
469
|
+
description: "Prose disabled -> package the template article directly"
|
|
470
|
+
guard:
|
|
471
|
+
kind: shell
|
|
472
|
+
options:
|
|
473
|
+
command: 'test "${vars.article_write_enabled}" != "true"'
|
|
474
|
+
|
|
475
|
+
- from: plan-translate
|
|
476
|
+
to: article-write
|
|
477
|
+
description: "Plan translated and prose enabled -> write prose article from plan"
|
|
448
478
|
guard:
|
|
449
479
|
kind: shell
|
|
450
480
|
options:
|
|
451
|
-
command: 'test "${vars.
|
|
481
|
+
command: 'test "${vars.article_write_enabled}" = "true"'
|
|
452
482
|
|
|
453
483
|
- from: plan-translate
|
|
454
484
|
to: article
|
|
455
|
-
description: "Plan translated ->
|
|
485
|
+
description: "Plan translated, prose disabled -> package the template article"
|
|
486
|
+
guard:
|
|
487
|
+
kind: shell
|
|
488
|
+
options:
|
|
489
|
+
command: 'test "${vars.article_write_enabled}" != "true"'
|
|
490
|
+
|
|
491
|
+
- from: article-write
|
|
492
|
+
to: article
|
|
493
|
+
description: "Prose body written -> package article content"
|
|
456
494
|
guard:
|
|
457
495
|
kind: always
|
|
458
496
|
|
|
@@ -518,27 +556,30 @@ transitions:
|
|
|
518
556
|
guard:
|
|
519
557
|
kind: always
|
|
520
558
|
|
|
559
|
+
# 0140: intentionally NO `qc.passed` conjunct on these two edges (operator decision, ratified
|
|
560
|
+
# 2026-09-17, see the 0140 Q&A). The gate that used to sit here read
|
|
561
|
+
# `jq -r '.metadata.qc.passed // true' <generate content>` — and jq's `//` treats `false` as empty,
|
|
562
|
+
# so it printed "true" for a failing audit and the conjunct passed for every input. It never gated
|
|
563
|
+
# (trace `dogfood-daily-031816` fired quality-control → publish-prep with `passed:false` on disk),
|
|
564
|
+
# so removing it is behaviour-neutral. Policy: degradation-class audits (loudness dips,
|
|
565
|
+
# transcription fidelity) publish warn-only and surface in the run report's ## 音频 QC block;
|
|
566
|
+
# the credibility-damaging class (`repetition_detected`) is hard-failed upstream by 0139 R5a.
|
|
567
|
+
# Do not restore a `qc.passed` guard here without also fixing the `// false`-swallowing expression.
|
|
521
568
|
- from: quality-control
|
|
522
569
|
to: publish-prep
|
|
523
|
-
description: "publish enabled
|
|
570
|
+
description: "publish enabled -> merge article + audio for the publish seam"
|
|
524
571
|
guard:
|
|
525
572
|
kind: shell
|
|
526
573
|
options:
|
|
527
|
-
command: 'test "${vars.publish_enabled}" = "true"
|
|
574
|
+
command: 'test "${vars.publish_enabled}" = "true"'
|
|
528
575
|
|
|
529
576
|
- from: quality-control
|
|
530
577
|
to: done
|
|
531
|
-
description: "publish disabled
|
|
578
|
+
description: "publish disabled -> audio episode complete (no publish)"
|
|
532
579
|
guard:
|
|
533
580
|
kind: shell
|
|
534
581
|
options:
|
|
535
|
-
command: 'test "${vars.publish_enabled}" != "true"
|
|
536
|
-
|
|
537
|
-
- from: quality-control
|
|
538
|
-
to: failed
|
|
539
|
-
description: "Audio QC failed -> pipeline failed"
|
|
540
|
-
guard:
|
|
541
|
-
kind: always
|
|
582
|
+
command: 'test "${vars.publish_enabled}" != "true"'
|
|
542
583
|
|
|
543
584
|
- from: publish-prep
|
|
544
585
|
to: publish-surfdash
|
|
@@ -565,12 +606,16 @@ transitions:
|
|
|
565
606
|
kind: always
|
|
566
607
|
|
|
567
608
|
- from: publish
|
|
609
|
+
to: patch-surfdash
|
|
610
|
+
description: "Publish classified -> patch surfdash posts with cover + podcast cross-link"
|
|
611
|
+
guard:
|
|
612
|
+
kind: always
|
|
613
|
+
|
|
614
|
+
- from: patch-surfdash
|
|
568
615
|
to: run-report
|
|
569
|
-
description: "
|
|
616
|
+
description: "Posts patched -> assemble run report"
|
|
570
617
|
guard:
|
|
571
|
-
kind:
|
|
572
|
-
options:
|
|
573
|
-
command: 'test "$(cat "${vars.work_dir}/3-publish/${vars.run_date}_17_publish_publish-status.txt")" = "full"'
|
|
618
|
+
kind: always
|
|
574
619
|
|
|
575
620
|
- from: publish
|
|
576
621
|
to: publish-partial
|
|
@@ -595,8 +640,8 @@ transitions:
|
|
|
595
640
|
kind: always
|
|
596
641
|
|
|
597
642
|
- from: run-report
|
|
598
|
-
to: translate
|
|
599
|
-
description: "Run report assembled after full publish -> scaffold + translate en
|
|
643
|
+
to: translate
|
|
644
|
+
description: "Run report assembled after full publish -> scaffold + translate en/ja locales + sync"
|
|
600
645
|
guard:
|
|
601
646
|
kind: shell
|
|
602
647
|
options:
|
|
@@ -610,48 +655,8 @@ transitions:
|
|
|
610
655
|
options:
|
|
611
656
|
command: 'test "$(cat "${vars.work_dir}/3-publish/${vars.run_date}_17_publish_publish-status.txt")" = "partial"'
|
|
612
657
|
|
|
613
|
-
- from: translate
|
|
614
|
-
to: translate-en-agent
|
|
615
|
-
description: "zh post published and en draft scaffolded -> agent translates en draft in place"
|
|
616
|
-
guard:
|
|
617
|
-
kind: shell
|
|
618
|
-
options:
|
|
619
|
-
command: 'test -n "${vars.surfdash_post_path}" && test -f "${vars.surfdash_en_post_path}"'
|
|
620
|
-
|
|
621
|
-
- from: translate-en
|
|
622
|
-
to: translate-ja
|
|
623
|
-
description: "no zh post path -> skip en locale"
|
|
624
|
-
guard:
|
|
625
|
-
kind: always
|
|
626
|
-
|
|
627
|
-
- from: translate-en-agent
|
|
628
|
-
to: translate-ja
|
|
629
|
-
description: "en draft translated -> scaffold ja locale"
|
|
630
|
-
guard:
|
|
631
|
-
kind: always
|
|
632
|
-
|
|
633
|
-
- from: translate-ja
|
|
634
|
-
to: translate-ja-agent
|
|
635
|
-
description: "zh post published and ja draft scaffolded -> agent translates ja draft in place"
|
|
636
|
-
guard:
|
|
637
|
-
kind: shell
|
|
638
|
-
options:
|
|
639
|
-
command: 'test -n "${vars.surfdash_post_path}" && test -f "${vars.surfdash_ja_post_path}"'
|
|
640
|
-
|
|
641
|
-
- from: translate-ja
|
|
642
|
-
to: translate-sync
|
|
643
|
-
description: "no zh post path -> skip ja locale"
|
|
644
|
-
guard:
|
|
645
|
-
kind: always
|
|
646
|
-
|
|
647
|
-
- from: translate-ja-agent
|
|
648
|
-
to: translate-sync
|
|
649
|
-
description: "ja draft translated -> sync all locales into the CMS"
|
|
650
|
-
guard:
|
|
651
|
-
kind: always
|
|
652
|
-
|
|
653
|
-
- from: translate-sync
|
|
658
|
+
- from: translate
|
|
654
659
|
to: done
|
|
655
|
-
description: "zh post + locales synced into surfdash CMS -> episode complete"
|
|
660
|
+
description: "zh post + en/ja locales scaffolded, translated, and synced into the surfdash CMS -> episode complete"
|
|
656
661
|
guard:
|
|
657
662
|
kind: always
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
2
|
+
kind: state-machine
|
|
3
|
+
name: leftover-publish-fanout-example
|
|
4
|
+
description: >-
|
|
5
|
+
Example: fan out two leftover file-cli channels (medium-pub, wechatmp-pub),
|
|
6
|
+
then sequence one agent-default CMS channel (juejin-pub) as agent.run.
|
|
7
|
+
kk fanout does not spawn an LLM.
|
|
8
|
+
initialState: fanout-file-cli
|
|
9
|
+
terminalStates:
|
|
10
|
+
- done
|
|
11
|
+
- failed
|
|
12
|
+
failureStates:
|
|
13
|
+
- failed
|
|
14
|
+
vars:
|
|
15
|
+
contentFile: ".spur/run/content.json"
|
|
16
|
+
fanoutFile: ".spur/run/fanout-result.json"
|
|
17
|
+
juejinFile: ".spur/run/juejin-result.json"
|
|
18
|
+
spurBin: "spur"
|
|
19
|
+
states:
|
|
20
|
+
- id: fanout-file-cli
|
|
21
|
+
description: "kk fanout over leftover file-cli channels (medium + wechatmp draft)"
|
|
22
|
+
onEnter:
|
|
23
|
+
- kind: shell
|
|
24
|
+
options:
|
|
25
|
+
command: >-
|
|
26
|
+
kk fanout --in ${vars.contentFile}
|
|
27
|
+
--targets medium-pub,wechatmp-pub
|
|
28
|
+
--out ${vars.fanoutFile}
|
|
29
|
+
- id: agent-juejin
|
|
30
|
+
description: "Agent-default CMS channel — workflow agent.run, not kk fanout"
|
|
31
|
+
onEnter:
|
|
32
|
+
- kind: agent.run
|
|
33
|
+
options:
|
|
34
|
+
role: coder
|
|
35
|
+
input: |
|
|
36
|
+
Invoke the kk:publish skill for juejin.
|
|
37
|
+
Drive ~/.config/kk/browser-profiles/juejin as driver=agent.
|
|
38
|
+
Then `kk executor run juejin-pub --in ${vars.contentFile} --out ${vars.juejinFile}`.
|
|
39
|
+
Do not embed an LLM in the plugin process. Do not call kk fanout for this channel.
|
|
40
|
+
expectFile: ${vars.juejinFile}
|
|
41
|
+
- id: done
|
|
42
|
+
description: "Fan-out aggregate plus agent CMS Result written"
|
|
43
|
+
- id: failed
|
|
44
|
+
description: "A file-cli target or the agent CMS step failed loud"
|
|
45
|
+
transitions:
|
|
46
|
+
- from: fanout-file-cli
|
|
47
|
+
to: agent-juejin
|
|
48
|
+
guard:
|
|
49
|
+
kind: always
|
|
50
|
+
- from: agent-juejin
|
|
51
|
+
to: done
|
|
52
|
+
guard:
|
|
53
|
+
kind: always
|