@gobing-ai/knowledge-kit 0.0.23 → 0.0.25

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.
Files changed (67) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +137 -59
  3. package/package.json +4 -4
  4. package/plugins/generations/content-gen/dist/index.js +97 -45
  5. package/plugins/generations/core-facts-gen/dist/index.js +106 -57
  6. package/plugins/generations/daily-article-gen/dist/index.js +105 -56
  7. package/plugins/generations/dailynews-gen/dist/index.js +235 -57
  8. package/plugins/generations/dailynews-gen/src/index.ts +67 -2
  9. package/plugins/generations/dailynews-gen/src/script-builder.ts +36 -15
  10. package/plugins/generations/dailynews-gen/src/script-plan.ts +142 -0
  11. package/plugins/generations/episode-plan-gen/dist/index.js +115 -66
  12. package/plugins/generations/gemini-tts/README.md +24 -0
  13. package/plugins/generations/gemini-tts/dist/index.js +326 -260
  14. package/plugins/generations/gemini-tts/src/gemini-client.ts +41 -11
  15. package/plugins/generations/image-gen/dist/index.js +338 -117
  16. package/plugins/generations/image-gen/presets/formats/daily-news-cover.yaml +19 -0
  17. package/plugins/generations/image-gen/presets/formats/xhs-card.yaml +55 -0
  18. package/plugins/generations/image-gen/presets/styles/daily-news.yaml +9 -0
  19. package/plugins/generations/image-gen/presets/styles/xhs-knowledge.yaml +7 -0
  20. package/plugins/generations/image-gen/src/index.ts +148 -14
  21. package/plugins/generations/image-gen/src/job.ts +30 -0
  22. package/plugins/generations/image-gen/src/providers/errors.ts +91 -0
  23. package/plugins/generations/image-gen/src/providers/types.ts +79 -0
  24. package/plugins/generations/news-report-gen/dist/index.js +136 -55
  25. package/plugins/generations/news-report-gen/src/index.ts +57 -1
  26. package/plugins/generations/voice-gen/dist/index.js +119 -67
  27. package/plugins/ingestions/aihot-ingest/dist/index.js +103 -53
  28. package/plugins/ingestions/horizon-ingest/dist/index.js +102 -53
  29. package/plugins/ingestions/karakeep-local/dist/index.js +273 -224
  30. package/plugins/ingestions/last30days-ingest/dist/index.js +97 -48
  31. package/plugins/ingestions/web-search/dist/index.js +121 -72
  32. package/plugins/kk/config.example.yaml +13 -0
  33. package/plugins/kk/plugin.json +1 -1
  34. package/plugins/kk/scripts/daily-voice-style.zh.md +52 -0
  35. package/plugins/kk/scripts/kk-workflow-stages.ts +433 -16
  36. package/plugins/kk/skills/publish/SKILL.md +24 -1
  37. package/plugins/kk/skills/xhs-images/SKILL.md +89 -0
  38. package/plugins/kk/skills/xhs-images/references/card-layouts.md +35 -0
  39. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +162 -11
  40. package/plugins/publishings/emdash-pub/dist/index.js +94 -45
  41. package/plugins/publishings/infoq-pub/dist/index.js +94 -45
  42. package/plugins/publishings/juejin-pub/dist/index.js +94 -45
  43. package/plugins/publishings/medium-pub/dist/index.js +94 -45
  44. package/plugins/publishings/podcast-pub/dist/index.js +115 -55
  45. package/plugins/publishings/podcast-pub/src/map.ts +30 -10
  46. package/plugins/publishings/podcast-pub/src/show-notes.ts +6 -1
  47. package/plugins/publishings/qiita-pub/dist/index.js +94 -45
  48. package/plugins/publishings/substack-pub/dist/index.js +94 -45
  49. package/plugins/publishings/surfdash-pub/dist/index.js +99 -63
  50. package/plugins/publishings/surfdash-pub/src/index.ts +10 -1
  51. package/plugins/publishings/wechatmp-pub/dist/index.js +94 -45
  52. package/plugins/publishings/x-pub/dist/index.js +94 -45
  53. package/plugins/publishings/xhs-pub/dist/index.js +8908 -229
  54. package/plugins/publishings/xhs-pub/package.json +1 -0
  55. package/plugins/publishings/xhs-pub/plugin.json +1 -1
  56. package/plugins/publishings/xhs-pub/src/automation-guard.ts +128 -0
  57. package/plugins/publishings/xhs-pub/src/index.ts +753 -24
  58. package/plugins/publishings/xhs-pub/src/profile-check.ts +133 -0
  59. package/plugins/publishings/zenn-pub/dist/index.js +94 -45
  60. package/plugins/sp/lib/env.ts +46 -0
  61. package/plugins/sp/lib/inline-run.generated.mjs +1489 -0
  62. package/plugins/sp/scripts/feature-verification-steps.ts +275 -0
  63. package/plugins/sp/scripts/inline-run-setup.ts +571 -47
  64. package/plugins/sp/scripts/wrapup-drift-probe.mjs +190 -0
  65. package/plugins/sp/scripts/wrapup-drift-probe.ts +258 -0
  66. package/plugins/sp/scripts/wrapup-steps.mjs +77 -6
  67. package/plugins/sp/scripts/wrapup-steps.ts +91 -5
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: xhs-images
3
+ description: >-
4
+ Use the xhs-images skill to turn source content into an ordered Xiaohongshu
5
+ card series — "make XHS cards from this article", "generate a card series",
6
+ "turn these notes into 小红书图文卡片". Plans N ordered cards (cover, content,
7
+ closing) grounded in the source, renders them through the image-gen executor
8
+ with the xhs-card/xhs-knowledge presets, packages a publishable Content, and
9
+ hands off to kk:publish channel xhs. Never publishes directly.
10
+ ---
11
+
12
+ # xhs-images — source-grounded XHS card series (plan → render → package → handoff)
13
+
14
+ Adapted from `baoyu-xhs-images` (MIT © Jim Liu) — layout, typography, and
15
+ consistency guidance only. Not imported: backend selection, batch/parallel
16
+ generation, `EXTEND.md` chain. Layout catalog:
17
+ `references/card-layouts.md`. Presets: `xhs-card` (format) +
18
+ `xhs-knowledge` (style). Rendering always goes through
19
+ `kk executor run image-gen` — this skill holds no transport.
20
+
21
+ ## Inputs
22
+
23
+ | Input | Required | Notes |
24
+ | ----- | -------- | ----- |
25
+ | source file (md/txt) | yes | read-only; never written or modified |
26
+ | `--out <dir>` | yes | fresh dir = new plan; existing dir = resume |
27
+ | `--count <n>` | no | 1–10; default: cover + one card per major point, capped at 10 |
28
+ | `--style <name>` | no | default `xhs-knowledge` |
29
+ | `--live` | no | passed through to kk:publish as the operator's live request |
30
+
31
+ `--count` above `min(10, image.max_images)` is refused, naming
32
+ `KK_IMAGE_MAX_IMAGES` / `image.max_images`.
33
+
34
+ ## Procedure
35
+
36
+ 1. **Plan** — read the source; produce ordered cards: card 01 `role: cover`,
37
+ then `content` cards in source order, then an optional `closing` card. Each
38
+ card records `index`, `role`, `sourcePoint` (verbatim heading or sentence),
39
+ `copy` (short Chinese on-card text), `layout` (from the reference catalog),
40
+ `subject` (visual scene). Write `<out>/cards.json` with
41
+ `{ source, cards: [{ index, role, sourcePoint, copy, layout, promptPath,
42
+ imagePath, status: 'pending' }] }`.
43
+ 2. **Envelopes** — for each card write `<out>/cards/NN.in.json`: the `image-gen`
44
+ envelope with `job: free`, `format: xhs-card`, `style`, `vars` (role,
45
+ layout, headline, points, subject, language 简体中文, index), `provider`,
46
+ `model`, `maxImages: 1`, `outputStem: card-NN`. For NN ≥ 02 add
47
+ `referenceImages: [<cover path>]` **only when** `kk:image-authoring`
48
+ `references/providers.md` marks the provider/model reference-capable.
49
+ 3. **Dry-run + one confirmation** — run each envelope with `KK_IMAGE_DRY_RUN=1`;
50
+ save the emitted `Content.body` as `<out>/cards/NN.prompt.txt` (the exact
51
+ composed prompt; it must equal `ImageArtifact.prompt` after render). Resolve
52
+ provider/model via `kk:image-authoring`'s preference chain (arguments >
53
+ env > `$KK_CONFIG` > defaults). Show **one** spend preview for the series
54
+ (provider, model, style, N, cover prompt, per-card copy list, plus the
55
+ consistency path: cover-reference chain when the provider is
56
+ reference-capable per `kk:image-authoring` `references/providers.md`, or an
57
+ explicit weaker-consistency disclosure when it is not) and follow the
58
+ `kk:image-authoring` Confirmation policy once before any paid render.
59
+ 4. **Render sequentially** — card 01, then 02..N, each via
60
+ `kk executor run image-gen --in cards/NN.in.json --out cards/NN.json`.
61
+ On the first failure: stop spending, mark the card `failed`, later cards
62
+ stay `pending`, report the retry.
63
+ 5. **Package** — write `<out>/content.json`: `title` (XHS title, ≤ 20 chars),
64
+ `body` (Chinese note + hashtags, ≤ 1000 chars), `format: 'text'`,
65
+ `metadata.images` = each card's `cards/NN.json` `metadata.images[0]` in
66
+ index order, `metadata.publishStatus: 'draft'`. Packaging refuses while any
67
+ card is not `rendered` or its prompt/image file is missing.
68
+ 6. **Handoff** — never spawn `xhs-pub` directly. Hand `content.json` to
69
+ `kk:publish` channel `xhs`; pass `--live` through as the operator's live
70
+ request.
71
+
72
+ ## Resume
73
+
74
+ Re-running with the same `--out` reuses the existing `cards.json` and
75
+ `cards/NN.in.json` (no replanning) and renders only cards without an existing
76
+ image file — check existence via the artifact path recorded in the prior run's
77
+ `cards/NN.json` (or a `cards/card-NN-*` glob), since the provider decides the
78
+ extension. Replanning requires a new `--out`.
79
+
80
+ ## Checks and limits
81
+
82
+ - N ≤ 10 and ≤ `image.max_images`; refusal names the cap env/key.
83
+ - Source file is never written.
84
+ - One spend confirmation per series; renders strictly sequential.
85
+ - Card text stays minimal (headline ≤ 12 chars, 2–4 short points) — the model
86
+ renders text, so fewer words means fewer rendering defects.
87
+ - Consistency: cover prompt fixes palette/typography wording; per-card prompts
88
+ reuse it verbatim and vary only layout/points/subject. When the provider is
89
+ reference-capable, cards ≥ 02 reference the rendered cover.
@@ -0,0 +1,35 @@
1
+ # XHS card layouts — adapted from `baoyu-xhs-images`
2
+
3
+ Source: `vendors/baoyu-skills/skills/baoyu-xhs-images/SKILL.md` (MIT © Jim Liu).
4
+ Adapted: layout names, typography rules, consistency guidance. Not imported:
5
+ backend selection, batch/parallel generation, `EXTEND.md` chain.
6
+
7
+ ## Layout catalog
8
+
9
+ | Layout | Use when | On-card structure |
10
+ | ------ | -------- | ----------------- |
11
+ | `cover` | Card 01 | Big headline (≤ 12 chars), one-line hook, small series label |
12
+ | `definition` | One concept to explain | Term + one-sentence definition + 2 short points |
13
+ | `list` | 3–4 parallel items | Numbered short lines, uniform phrasing |
14
+ | `comparison` | Two options | Two columns, 2–3 contrast points each |
15
+ | `steps` | Ordered process | 3–4 numbered steps, verb-first |
16
+ | `quote` | Key sentence from source | Large quote text + attribution line |
17
+ | `summary` | Closing card | 3 takeaways + one CTA line |
18
+
19
+ ## Typography rules
20
+
21
+ - Headline: extra-large bold; ≤ 12 Chinese characters.
22
+ - Points: short lines, 2–4 per card, generous line spacing.
23
+ - Keep total on-card text minimal — the model renders text, so fewer words
24
+ means fewer rendering defects.
25
+ - Language default: 简体中文 (`xhs-card` format `language` var).
26
+
27
+ ## Consistency rules
28
+
29
+ - Compose the cover prompt first; fix palette/typography wording there.
30
+ - Per-card prompts reuse that wording verbatim; vary only
31
+ layout/points/subject (via the `xhs-card` template variables).
32
+ - When the provider/model is reference-capable per `kk:image-authoring`
33
+ `references/providers.md`, cards ≥ 02 set `referenceImages: [<cover path>]`.
34
+ - Renders are strictly sequential (card 01 → N) so the cover reference exists
35
+ before dependents render.
@@ -5,7 +5,8 @@ kind: state-machine
5
5
  description: >-
6
6
  Daily AI news end-to-end pipeline (F61 destination): collect (fan-in --source: aihot + last30days
7
7
  + Horizon) → core facts → plan → content QC (filter, done-no-content terminal) → article + cover
8
- + script + review + voice → publish (surfdash + podcast, full|partial|none routing) → locales
8
+ + script + review + voice → publish (surfdash + podcast, full|partial|none routing) → xhs draft
9
+ (independent, non-blocking target) → locales
9
10
  initialState: prepare
10
11
  terminalStates:
11
12
  - done
@@ -31,11 +32,45 @@ vars:
31
32
  cursor: ""
32
33
  state_file: ""
33
34
  skip_review: "false"
34
- transcode_mp3: "false"
35
+ # 0171 R1: podcast-pub publishes metadata.mp3Path, so "false" fell back to the WAV render — an
36
+ # enclosure Apple Podcasts rejects and no ID3 tag can ride.
37
+ transcode_mp3: "true"
38
+ # 0171 R3/R4: ID3 artist/album stamped onto the published MP3 (title comes from the article,
39
+ # date from run_date); an empty var omits that tag.
40
+ podcast_artist: "Robin Min"
41
+ podcast_album: "咸话咸说"
35
42
  cover_enabled: "true"
43
+ # F614: the daily cover pins provider+model in vars — each provider has its own look, so the
44
+ # pin is part of the style contract; it stays a var because the right value depends on which
45
+ # credentials the machine has (2026-09-24 run: minimax/image-01).
46
+ cover_provider: "minimax"
47
+ cover_model: "image-01"
48
+ # D6 0166: runtime-fallback opt-in for the cover render — "" (default) keeps strict fail-fast,
49
+ # "true" retries down AUTO_ORDER, anything else is a comma-separated provider list. The empty
50
+ # default keeps F614's pinned look the default; availability trades against it only on opt-in.
51
+ cover_fallback: ""
52
+ # E8: XHS image-card target. Cards are planned from the lead plan item (the same source the
53
+ # cover uses) and rendered through the pinned xhs-card/xhs-knowledge presets. `xhs_live` stays a
54
+ # separate launch-time switch (ADR-022), so an unattended run drafts and never posts live.
55
+ # Default OFF (2026-09-28): XHS warned the operator about automated login/publish activity.
56
+ # `xhs-pub` always launches a headed browser and drives the creator console, and an unattended
57
+ # daily job repeating that is precisely the pattern the platform flags — so the target is
58
+ # operator-opt-in until a reviewed automation policy exists. Do NOT flip this on for a scheduled
59
+ # run; publish XHS deliberately, by hand, and rarely.
60
+ xhs_enabled: "false"
61
+ xhs_cards: "4"
62
+ xhs_style: "xhs-knowledge"
63
+ # Same credential surface as the cover pin: the provider/model must be one this machine can
64
+ # actually reach, and each provider renders its own look.
65
+ xhs_provider: "minimax"
66
+ xhs_model: "image-01"
67
+ xhs_live: "false"
36
68
  plan_max_items: "20"
37
69
  plan_translate_enabled: "true"
38
70
  article_write_enabled: "true"
71
+ # F613 R3: script-write authors the per-item plan the zh builder consumes; "false" routes
72
+ # article/cover straight to script (template build, no plan file is written).
73
+ script_write_enabled: "true"
39
74
  plugins_path: ""
40
75
  # Projected by the publish state from 3-publish/<run_date>_17_publish_publish-status.txt;
41
76
  # guards test this var instead of cat-ing the file (state at decision time).
@@ -160,24 +195,60 @@ states:
160
195
  kk stage daily-article "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}"
161
196
 
162
197
  - id: cover
163
- description: "Generate the article cover image via the kk:image-authoring skill (agent.run)"
198
+ description: "Render the daily cover from the lead plan item through the pinned daily-news presets (brief agent.run -> pinned stage render -> normalize, F614)"
164
199
  onEnter:
165
200
  - kind: agent.run
201
+ # A bad or failed brief must never halt the run: the stage falls back to the format's
202
+ # default subject (F614 R4) and keeps the same pinned format/style/provider/model.
203
+ onError: continue
166
204
  options:
167
205
  role: scribe
168
206
  agent: ${vars.agent}
207
+ # ADR-115 error tier: keep this prompt <= 1000 chars.
169
208
  input: |
170
- Invoke the kk:image-authoring skill (generate operation).
171
- Non-interactive step: run every image command with KK_NONINTERACTIVE=1 and KK_IMAGE_AUTO_CONFIRM=1 — never prompt (cover_enabled is the spend approval).
172
- Command: --format cover --content ${vars.work_dir}/2-article/${vars.run_date}_06_article_article.md --out ${vars.work_dir}/2-cover
173
- Template variables (title, subtitle, topics) come from the article as the skill owns; do not pass --style.
174
- The plugin writes ${vars.work_dir}/2-cover/content.json plus ${vars.work_dir}/2-cover/content-cover-01.<ext> (ext follows the elected provider: minimax → .jpg, google/ark → .png). On failure report the error verbatim and stop — no procedural fallback.
175
- expectFile: ${vars.work_dir}/2-cover/content-cover-01.jpg
209
+ Read ${vars.work_dir}/2-plan/${vars.run_date}_03_plan_plan.json — a JSON array of news docs; don't edit it.
210
+ Take item 1 only (its "title" and "metadata.brief") — the episode's lead story.
211
+ Write one concrete visual scene in English, ≤240 characters, that a viewer would connect to that story: objects, settings, symbols, metaphor.
212
+ No text, letters, numbers, logos, brand marks, flags, or recognizable real people. No style, color, lighting, or composition words — the preset owns those.
213
+ Output strictly valid JSON {"subject": "..."} to ${vars.work_dir}/2-cover/${vars.run_date}_07_cover-brief.json.
214
+ expectFile: ${vars.work_dir}/2-cover/${vars.run_date}_07_cover-brief.json
215
+ allowAppend: false
216
+ - kind: shell
217
+ options:
218
+ command: >-
219
+ kk stage daily-cover "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}" "${vars.cover_provider}" "${vars.cover_model}" "${vars.cover_fallback}"
176
220
  - kind: shell
177
221
  options:
178
222
  command: >-
179
223
  kk stage daily-cover-normalize "${vars.work_dir}" "${vars.run_date}"
180
224
 
225
+ - id: script-write
226
+ description: "Author the per-item script plan (intro/outro + lead/insight per doc id) from the show-notes plan source via agent.run; onError: continue — a failed plan falls back to templates (F613)"
227
+ onEnter:
228
+ - kind: shell
229
+ options:
230
+ # Spur has no workflow-dir var, so the prompt cannot name the shipped sidecar: this
231
+ # stage snapshots it into the work dir (idempotent; a missing sidecar is a broken
232
+ # install and fails loud). The snapshot also freezes the contract the plan was
233
+ # written against, per run, for audit.
234
+ command: >-
235
+ kk stage daily-script-style "${vars.work_dir}" "${vars.run_date}"
236
+ - kind: agent.run
237
+ # A rejected or failed plan must never halt the unattended daily run: the builder
238
+ # decides plan-vs-template from the file itself and falls back to templates.
239
+ onError: continue
240
+ options:
241
+ role: scribe
242
+ agent: ${vars.agent}
243
+ # ADR-115 error tier: keep this prompt <= 1000 chars.
244
+ input: |
245
+ Read ${vars.work_dir}/2-plan/${vars.run_date}_03_plan_plan.json — a JSON array of docs; use each doc's `id`; don't edit it. Also read the style contract ${vars.work_dir}/2-script/${vars.run_date}_08_script-write_style.md and obey its char caps and banned phrases.
246
+ For each doc write a `lead` (a spoken bridge into the title, not containing it) and an `insight` (why it matters, grounded only in that doc's facts).
247
+ Write an `intro` hooking today's actual themes and an `outro`; no greeting or sign-off — the builder adds both.
248
+ Output only the JSON object {"version":1,"intro":"…","outro":"…","items":[{"id":"…","lead":"…","insight":"…"}]} with one item per doc id to ${vars.work_dir}/2-script/${vars.run_date}_08_script-write_plan.json.
249
+ expectFile: ${vars.work_dir}/2-script/${vars.run_date}_08_script-write_plan.json
250
+ allowAppend: false
251
+
181
252
  - id: script
182
253
  description: "Compile the episode plan Doc[] into structured broadcast VoiceScript YAML Content"
183
254
  onEnter:
@@ -228,12 +299,12 @@ states:
228
299
  kk stage daily-quality-report "${vars.work_dir}" "${vars.run_date}"
229
300
 
230
301
  - id: publish-prep
231
- description: "Merge article Content with audio (podcast assembly facts ride the content options channel for podcast-pub's absorbed assembly; runDate kept for surfdash slug)"
302
+ description: "Merge article Content with the MP3 (rejects a non-.mp3 enclosure) and stamp its ID3 tags (podcast assembly facts ride the content options channel for podcast-pub's absorbed assembly; runDate kept for surfdash slug)"
232
303
  onEnter:
233
304
  - kind: shell
234
305
  options:
235
306
  command: >-
236
- kk stage daily-publish-prep "${vars.work_dir}" "${vars.run_date}"
307
+ kk stage daily-publish-prep "${vars.work_dir}" "${vars.run_date}" "${vars.podcast_artist}" "${vars.podcast_album}"
237
308
 
238
309
  - id: publish-surfdash
239
310
  description: "Publish the merged Content to surfdash (single target); result lands in result.json — surfdash publishes first so show-notes can link the article (task 0118 seam reorder)"
@@ -259,6 +330,41 @@ states:
259
330
  command: >-
260
331
  kk stage daily-publish-podcast "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}"
261
332
 
333
+ - id: xhs-write
334
+ description: "Plan the XHS card series from the lead plan item via agent.run (title + note + N ordered cards), then render it through the pinned xhs-card/xhs-knowledge presets (E8); onError: continue — a failed plan or render skips the XHS target, never the run"
335
+ onEnter:
336
+ - kind: agent.run
337
+ # XHS is an independent, non-blocking target: a bad or failed plan must never halt the run.
338
+ # The render stage still fails loud; the publish state then skips naming the missing series.
339
+ onError: continue
340
+ options:
341
+ role: scribe
342
+ agent: ${vars.agent}
343
+ # ADR-115 error tier: keep this prompt <= 1000 chars.
344
+ input: |
345
+ Read ${vars.work_dir}/2-plan/${vars.run_date}_03_plan_plan.json — a JSON array of news docs; don't edit it.
346
+ Take item 1 only (its "title" and "metadata.brief") — the lead story.
347
+ Write a Xiaohongshu note: "title" (≤20 chars) and "body" (Chinese note ≤1000 chars, ending with 3-5 hashtags).
348
+ Write exactly ${vars.xhs_cards} cards: card 1 role "cover", the rest "content", except a final "closing". Each card carries "role", "copy" (Chinese on-card headline ≤12 chars), "points" (2-4 short Chinese bullets), "layout", "subject" (one concrete English visual scene), "sourcePoint" (the lead-story fact that card uses).
349
+ Ground every "copy", "points" and "sourcePoint" in that story alone — no invented facts. No text, letters, numbers, logos or real people in "subject".
350
+ Output strictly valid JSON {"title": "...", "body": "...", "cards": [...]} to ${vars.work_dir}/2-xhs/${vars.run_date}_18_xhs_plan.json.
351
+ expectFile: ${vars.work_dir}/2-xhs/${vars.run_date}_18_xhs_plan.json
352
+ allowAppend: false
353
+ - kind: shell
354
+ onError: continue
355
+ options:
356
+ command: >-
357
+ kk stage daily-xhs-render "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}" "${vars.xhs_cards}" "${vars.xhs_style}" "${vars.xhs_provider}" "${vars.xhs_model}"
358
+
359
+ - id: xhs-publish
360
+ description: "Publish the packaged card series to XHS — draft, or live only when xhs_live=true (ADR-022); independent and non-blocking: its outcome never enters publish_status. xhs-pub itself enforces a headless session preflight and a 24h publish/check cap (ADR-025), so a sessionless or capped run fails soft with a named reason and the run continues"
361
+ onEnter:
362
+ - kind: shell
363
+ onError: continue
364
+ options:
365
+ command: >-
366
+ kk stage daily-publish-xhs "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}" "${vars.xhs_live}"
367
+
262
368
  - id: publish
263
369
  description: "Classify the sequenced surfdash → podcast outcomes full|partial|none into publish-status.txt (exit 0 in all three — routing is transition-owned; task 0118: per-target classification over the merged result.json)"
264
370
  onEnter:
@@ -416,18 +522,43 @@ transitions:
416
522
  options:
417
523
  command: 'test "${vars.cover_enabled}" = "true" && test ! -f "${vars.work_dir}/2-cover/${vars.run_date}_07_cover_cover.png"'
418
524
 
525
+ # F613 R3: first match wins — the cover edge stays above script-write so the cover still
526
+ # generates before the plan is authored; script-write sits above the always `-> script` edges
527
+ # and skips itself when disabled, non-zh, or the plan/YAML already exists (resume cost).
528
+ - from: article
529
+ to: script-write
530
+ description: "script_write_enabled, zh, and neither today's plan nor VoiceScript exists -> author the script plan"
531
+ guard:
532
+ kind: shell
533
+ options:
534
+ command: 'test "${vars.script_write_enabled}" = "true" && test "${vars.language}" = "zh" && test ! -f "${vars.work_dir}/2-script/${vars.run_date}_08_script-write_plan.json" && test ! -f "${vars.work_dir}/2-script/${vars.run_date}_08_script_voicescript.yaml"'
535
+
419
536
  - from: article
420
537
  to: script
421
538
  description: "cover disabled or today's cover cached -> skip to script"
422
539
  guard:
423
540
  kind: always
424
541
 
542
+ - from: cover
543
+ to: script-write
544
+ description: "script_write_enabled, zh, and neither today's plan nor VoiceScript exists -> author the script plan"
545
+ guard:
546
+ kind: shell
547
+ options:
548
+ command: 'test "${vars.script_write_enabled}" = "true" && test "${vars.language}" = "zh" && test ! -f "${vars.work_dir}/2-script/${vars.run_date}_08_script-write_plan.json" && test ! -f "${vars.work_dir}/2-script/${vars.run_date}_08_script_voicescript.yaml"'
549
+
425
550
  - from: cover
426
551
  to: script
427
552
  description: "Cover generated -> build broadcast script"
428
553
  guard:
429
554
  kind: always
430
555
 
556
+ - from: script-write
557
+ to: script
558
+ description: "Script plan authored (ok or failed: onError continue) -> build the broadcast script"
559
+ guard:
560
+ kind: always
561
+
431
562
  - from: script
432
563
  to: wrap-docs
433
564
  description: "skip_review enabled -> bypass HITL review"
@@ -519,6 +650,26 @@ transitions:
519
650
  guard:
520
651
  kind: always
521
652
 
653
+ - from: publish-podcast
654
+ to: xhs-write
655
+ description: "xhs_enabled -> plan and render the XHS card series from the lead plan item; first-match-wins, so this sits above the classification edge (XHS is independent and never gates publish_status)"
656
+ guard:
657
+ kind: shell
658
+ options:
659
+ command: 'test "${vars.xhs_enabled}" = "true"'
660
+
661
+ - from: xhs-write
662
+ to: xhs-publish
663
+ description: "Card series planned and rendered (or failed: onError continue) -> publish the XHS draft"
664
+ guard:
665
+ kind: always
666
+
667
+ - from: xhs-publish
668
+ to: publish
669
+ description: "XHS dispatched (or skipped: onError continue) -> classify the surfdash/podcast outcomes; XHS contributes no target to the classification"
670
+ guard:
671
+ kind: always
672
+
522
673
  - from: publish-podcast
523
674
  to: publish
524
675
  description: "Podcast dispatched -> classify the per-target outcomes full|partial|none"
@@ -14642,6 +14642,72 @@ var init_zod = __esm(() => {
14642
14642
  init_external();
14643
14643
  });
14644
14644
 
14645
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/errors.js
14646
+ var ErrorCode;
14647
+ var init_errors3 = __esm(() => {
14648
+ ErrorCode = {
14649
+ NotFound: "NOT_FOUND",
14650
+ Validation: "VALIDATION",
14651
+ Conflict: "CONFLICT",
14652
+ Internal: "INTERNAL"
14653
+ };
14654
+ });
14655
+
14656
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/api-response.js
14657
+ var API_ERROR_CODES, ERROR_CODE_TO_HTTP, CLIENT_SAFE_CODES;
14658
+ var init_api_response = __esm(() => {
14659
+ init_errors3();
14660
+ API_ERROR_CODES = {
14661
+ SUCCESS: 0,
14662
+ NOT_FOUND: 404,
14663
+ VALIDATION_ERROR: 422,
14664
+ BAD_REQUEST: 400,
14665
+ UNAUTHORIZED: 401,
14666
+ FORBIDDEN: 403,
14667
+ CONFLICT: 409,
14668
+ INTERNAL_ERROR: 500
14669
+ };
14670
+ ERROR_CODE_TO_HTTP = {
14671
+ [ErrorCode.NotFound]: API_ERROR_CODES.NOT_FOUND,
14672
+ [ErrorCode.Validation]: API_ERROR_CODES.VALIDATION_ERROR,
14673
+ [ErrorCode.Conflict]: API_ERROR_CODES.CONFLICT,
14674
+ [ErrorCode.Internal]: API_ERROR_CODES.INTERNAL_ERROR
14675
+ };
14676
+ CLIENT_SAFE_CODES = new Set([ErrorCode.NotFound, ErrorCode.Validation, ErrorCode.Conflict]);
14677
+ });
14678
+
14679
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/cursor.js
14680
+ var init_cursor = () => {};
14681
+
14682
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/event-severity.js
14683
+ var init_event_severity = () => {};
14684
+
14685
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/output.js
14686
+ function processStream(name) {
14687
+ const proc = globalThis.process;
14688
+ const stream = proc?.[name];
14689
+ if (stream === undefined) {
14690
+ throw new Error(`No ${name} target available: set one via setDefaultOutputTargets or pass an explicit target`);
14691
+ }
14692
+ return stream;
14693
+ }
14694
+ function writeLine(message, target) {
14695
+ target.write(`${message}
14696
+ `);
14697
+ }
14698
+ function echoError(message, target = defaultStderrTarget ?? processStream("stderr")) {
14699
+ writeLine(message, target);
14700
+ }
14701
+ var defaultStderrTarget;
14702
+
14703
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/index.js
14704
+ var init_dist = __esm(() => {
14705
+ init_api_response();
14706
+ init_cursor();
14707
+ init_errors3();
14708
+ init_event_severity();
14709
+ });
14710
+
14645
14711
  // ../../node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
14646
14712
  var require_identity = __commonJS((exports) => {
14647
14713
  var ALIAS = Symbol.for("yaml.alias");
@@ -17546,15 +17612,15 @@ var require_timestamp = __commonJS((exports) => {
17546
17612
  throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
17547
17613
  const [, year, month, day, hour, minute, second] = match.map(Number);
17548
17614
  const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0;
17549
- let date5 = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
17615
+ let date6 = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
17550
17616
  const tz = match[8];
17551
17617
  if (tz && tz !== "Z") {
17552
17618
  let d = parseSexagesimal(tz, false);
17553
17619
  if (Math.abs(d) < 30)
17554
17620
  d *= 60;
17555
- date5 -= 60000 * d;
17621
+ date6 -= 60000 * d;
17556
17622
  }
17557
- return new Date(date5);
17623
+ return new Date(date6);
17558
17624
  },
17559
17625
  stringify: ({ value }) => value?.toISOString().replace(/(T00:00:00)?\.000Z$/, "") ?? ""
17560
17626
  };
@@ -19414,7 +19480,7 @@ var require_composer = __commonJS((exports) => {
19414
19480
  var node_process = __require("process");
19415
19481
  var directives = require_directives();
19416
19482
  var Document = require_Document();
19417
- var errors3 = require_errors();
19483
+ var errors4 = require_errors();
19418
19484
  var identity = require_identity();
19419
19485
  var composeDoc = require_compose_doc();
19420
19486
  var resolveEnd = require_resolve_end();
@@ -19465,9 +19531,9 @@ var require_composer = __commonJS((exports) => {
19465
19531
  this.onError = (source, code, message, warning) => {
19466
19532
  const pos = getErrorPos(source);
19467
19533
  if (warning)
19468
- this.warnings.push(new errors3.YAMLWarning(pos, code, message));
19534
+ this.warnings.push(new errors4.YAMLWarning(pos, code, message));
19469
19535
  else
19470
- this.errors.push(new errors3.YAMLParseError(pos, code, message));
19536
+ this.errors.push(new errors4.YAMLParseError(pos, code, message));
19471
19537
  };
19472
19538
  this.directives = new directives.Directives({ version: options.version || "1.2" });
19473
19539
  this.options = options;
@@ -19553,7 +19619,7 @@ ${cb}` : comment;
19553
19619
  break;
19554
19620
  case "error": {
19555
19621
  const msg = token.source ? `${token.message}: ${JSON.stringify(token.source)}` : token.message;
19556
- const error51 = new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
19622
+ const error51 = new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
19557
19623
  if (this.atDirectives || !this.doc)
19558
19624
  this.errors.push(error51);
19559
19625
  else
@@ -19563,7 +19629,7 @@ ${cb}` : comment;
19563
19629
  case "doc-end": {
19564
19630
  if (!this.doc) {
19565
19631
  const msg = "Unexpected doc-end without preceding document";
19566
- this.errors.push(new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
19632
+ this.errors.push(new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
19567
19633
  break;
19568
19634
  }
19569
19635
  this.doc.directives.docEnd = true;
@@ -19578,7 +19644,7 @@ ${end.comment}` : end.comment;
19578
19644
  break;
19579
19645
  }
19580
19646
  default:
19581
- this.errors.push(new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
19647
+ this.errors.push(new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
19582
19648
  }
19583
19649
  }
19584
19650
  *end(forceDoc = false, endOffset = -1) {
@@ -19604,7 +19670,7 @@ ${end.comment}` : end.comment;
19604
19670
  var require_cst_scalar = __commonJS((exports) => {
19605
19671
  var resolveBlockScalar = require_resolve_block_scalar();
19606
19672
  var resolveFlowScalar = require_resolve_flow_scalar();
19607
- var errors3 = require_errors();
19673
+ var errors4 = require_errors();
19608
19674
  var stringifyString = require_stringifyString();
19609
19675
  function resolveAsScalar(token, strict = true, onError) {
19610
19676
  if (token) {
@@ -19613,7 +19679,7 @@ var require_cst_scalar = __commonJS((exports) => {
19613
19679
  if (onError)
19614
19680
  onError(offset, code, message);
19615
19681
  else
19616
- throw new errors3.YAMLParseError([offset, offset + 1], code, message);
19682
+ throw new errors4.YAMLParseError([offset, offset + 1], code, message);
19617
19683
  };
19618
19684
  switch (token.type) {
19619
19685
  case "scalar":
@@ -21493,7 +21559,7 @@ var require_parser = __commonJS((exports) => {
21493
21559
  var require_public_api = __commonJS((exports) => {
21494
21560
  var composer = require_composer();
21495
21561
  var Document = require_Document();
21496
- var errors3 = require_errors();
21562
+ var errors4 = require_errors();
21497
21563
  var log = require_log();
21498
21564
  var identity = require_identity();
21499
21565
  var lineCounter = require_line_counter();
@@ -21510,8 +21576,8 @@ var require_public_api = __commonJS((exports) => {
21510
21576
  const docs = Array.from(composer$1.compose(parser$1.parse(source)));
21511
21577
  if (prettyErrors && lineCounter2)
21512
21578
  for (const doc2 of docs) {
21513
- doc2.errors.forEach(errors3.prettifyError(source, lineCounter2));
21514
- doc2.warnings.forEach(errors3.prettifyError(source, lineCounter2));
21579
+ doc2.errors.forEach(errors4.prettifyError(source, lineCounter2));
21580
+ doc2.warnings.forEach(errors4.prettifyError(source, lineCounter2));
21515
21581
  }
21516
21582
  if (docs.length > 0)
21517
21583
  return docs;
@@ -21526,13 +21592,13 @@ var require_public_api = __commonJS((exports) => {
21526
21592
  if (!doc2)
21527
21593
  doc2 = _doc;
21528
21594
  else if (doc2.options.logLevel !== "silent") {
21529
- doc2.errors.push(new errors3.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
21595
+ doc2.errors.push(new errors4.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
21530
21596
  break;
21531
21597
  }
21532
21598
  }
21533
21599
  if (prettyErrors && lineCounter2) {
21534
- doc2.errors.forEach(errors3.prettifyError(source, lineCounter2));
21535
- doc2.warnings.forEach(errors3.prettifyError(source, lineCounter2));
21600
+ doc2.errors.forEach(errors4.prettifyError(source, lineCounter2));
21601
+ doc2.warnings.forEach(errors4.prettifyError(source, lineCounter2));
21536
21602
  }
21537
21603
  return doc2;
21538
21604
  }
@@ -21584,12 +21650,12 @@ var require_public_api = __commonJS((exports) => {
21584
21650
  });
21585
21651
 
21586
21652
  // ../../node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/index.js
21587
- var composer, Document, Schema, errors3, Alias, identity, Pair, Scalar, YAMLMap, YAMLSeq, cst, lexer, lineCounter, parser, publicApi, visit, $Composer, $Document, $Schema, $YAMLError, $YAMLParseError, $YAMLWarning, $Alias, $isAlias, $isCollection, $isDocument, $isMap, $isNode, $isPair, $isScalar, $isSeq, $Pair, $Scalar, $YAMLMap, $YAMLSeq, $Lexer, $LineCounter, $Parser, $parse, $parseAllDocuments, $parseDocument, $stringify, $visit, $visitAsync;
21588
- var init_dist = __esm(() => {
21653
+ var composer, Document, Schema, errors4, Alias, identity, Pair, Scalar, YAMLMap, YAMLSeq, cst, lexer, lineCounter, parser, publicApi, visit, $Composer, $Document, $Schema, $YAMLError, $YAMLParseError, $YAMLWarning, $Alias, $isAlias, $isCollection, $isDocument, $isMap, $isNode, $isPair, $isScalar, $isSeq, $Pair, $Scalar, $YAMLMap, $YAMLSeq, $Lexer, $LineCounter, $Parser, $parse, $parseAllDocuments, $parseDocument, $stringify, $visit, $visitAsync;
21654
+ var init_dist2 = __esm(() => {
21589
21655
  composer = require_composer();
21590
21656
  Document = require_Document();
21591
21657
  Schema = require_Schema();
21592
- errors3 = require_errors();
21658
+ errors4 = require_errors();
21593
21659
  Alias = require_Alias();
21594
21660
  identity = require_identity();
21595
21661
  Pair = require_Pair();
@@ -21605,9 +21671,9 @@ var init_dist = __esm(() => {
21605
21671
  $Composer = composer.Composer;
21606
21672
  $Document = Document.Document;
21607
21673
  $Schema = Schema.Schema;
21608
- $YAMLError = errors3.YAMLError;
21609
- $YAMLParseError = errors3.YAMLParseError;
21610
- $YAMLWarning = errors3.YAMLWarning;
21674
+ $YAMLError = errors4.YAMLError;
21675
+ $YAMLParseError = errors4.YAMLParseError;
21676
+ $YAMLWarning = errors4.YAMLWarning;
21611
21677
  $Alias = Alias.Alias;
21612
21678
  $isAlias = identity.isAlias;
21613
21679
  $isCollection = identity.isCollection;
@@ -21632,10 +21698,10 @@ var init_dist = __esm(() => {
21632
21698
  $visitAsync = visit.visitAsync;
21633
21699
  });
21634
21700
 
21635
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/config.js
21701
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/config.js
21636
21702
  var configSchema;
21637
21703
  var init_config = __esm(() => {
21638
- init_dist();
21704
+ init_dist2();
21639
21705
  init_zod();
21640
21706
  configSchema = exports_external.object({
21641
21707
  app: exports_external.object({
@@ -21656,7 +21722,7 @@ var init_config = __esm(() => {
21656
21722
  });
21657
21723
  });
21658
21724
 
21659
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
21725
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
21660
21726
  import { appendFileSync, cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
21661
21727
  import { dirname, resolve as resolvePath } from "path";
21662
21728
  function createNodeFileSystem(root, paths) {
@@ -21834,28 +21900,10 @@ var PluginManifestSchema = exports_external.looseObject({
21834
21900
  description: exports_external.string().optional(),
21835
21901
  operations: exports_external.record(exports_external.string(), PluginOperationSchema).optional()
21836
21902
  });
21837
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/index.js
21903
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/index.js
21838
21904
  init_file_system_node();
21839
21905
  init_config();
21840
21906
 
21841
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.67/node_modules/@gobing-ai/ts-utils/dist/output.js
21842
- var defaultStderrTarget;
21843
- function processStream(name) {
21844
- const proc = globalThis.process;
21845
- const stream = proc?.[name];
21846
- if (stream === undefined) {
21847
- throw new Error(`No ${name} target available: set one via setDefaultOutputTargets or pass an explicit target`);
21848
- }
21849
- return stream;
21850
- }
21851
- function writeLine(message, target) {
21852
- target.write(`${message}
21853
- `);
21854
- }
21855
- function echoError(message, target = defaultStderrTarget ?? processStream("stderr")) {
21856
- writeLine(message, target);
21857
- }
21858
-
21859
21907
  // ../../packages/kk-core/src/render-md.ts
21860
21908
  if (false) {}
21861
21909
  // ../../packages/publish-harness/src/index.ts
@@ -21933,6 +21981,7 @@ class FakeBrowserTransport {
21933
21981
  }
21934
21982
 
21935
21983
  // ../../plugins/publishings/emdash-pub/src/index.ts
21984
+ init_dist();
21936
21985
  var REQUEST_TIMEOUT_MS = 120000;
21937
21986
  function slugify2(str) {
21938
21987
  return str.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/[\s_]+/g, "-");