@gobing-ai/knowledge-kit 0.0.37 → 0.1.0

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 (157) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/board/collect/index.css +654 -16
  3. package/dist/board/collect/index.js +191 -64
  4. package/dist/board/daily-voice/index.css +679 -12
  5. package/dist/board/daily-voice/index.js +185 -66
  6. package/dist/board/itc/index.css +679 -13
  7. package/dist/board/itc/index.js +188 -66
  8. package/dist/index.js +2864 -139
  9. package/package.json +3 -2
  10. package/plugins/generations/content-gen/dist/index.js +158 -12
  11. package/plugins/generations/core-facts-gen/dist/index.js +158 -12
  12. package/plugins/generations/daily-article-gen/dist/index.js +158 -12
  13. package/plugins/generations/dailynews-gen/dist/index.js +158 -12
  14. package/plugins/generations/episode-plan-gen/dist/index.js +158 -12
  15. package/plugins/generations/gemini-tts/README.md +4 -3
  16. package/plugins/generations/gemini-tts/dist/index.js +158 -12
  17. package/plugins/generations/image-gen/dist/index.js +158 -12
  18. package/plugins/generations/news-report-gen/dist/index.js +158 -12
  19. package/plugins/generations/omni-voice-gen/README.md +15 -4
  20. package/plugins/generations/omni-voice-gen/profiles.json +1 -2
  21. package/plugins/generations/voice-gen/dist/index.js +158 -12
  22. package/plugins/generations/voxcpm-tts/README.md +21 -1
  23. package/plugins/generations/voxcpm-tts/dist/index.js +173 -13
  24. package/plugins/generations/voxcpm-tts/scripts/audit-speaker-consistency.py +109 -0
  25. package/plugins/generations/voxcpm-tts/src/voxcpm-client.ts +28 -1
  26. package/plugins/ingestions/aihot-ingest/dist/index.js +158 -12
  27. package/plugins/ingestions/horizon-ingest/dist/index.js +158 -12
  28. package/plugins/ingestions/last30days-ingest/dist/index.js +158 -12
  29. package/plugins/ingestions/web-search/dist/index.js +158 -12
  30. package/plugins/kk/commands/workflow-run.md +56 -18
  31. package/plugins/kk/decisions/kk-itc.yaml +46 -0
  32. package/plugins/kk/decisions/kk-seo-keyword.yaml +57 -0
  33. package/plugins/kk/decisions/kk-seo-page.yaml +230 -0
  34. package/plugins/kk/decisions/kk-seo-pair.yaml +21 -0
  35. package/plugins/kk/decisions/kk-seo-site.yaml +90 -0
  36. package/plugins/kk/plugin.json +1 -1
  37. package/plugins/kk/publish-presets/chinese-article.json +14 -0
  38. package/plugins/kk/publish-presets/technical-article.json +14 -0
  39. package/plugins/kk/references/smart-seo-attribution.md +85 -0
  40. package/plugins/kk/scripts/daily-stages.ts +42 -2
  41. package/plugins/kk/scripts/itc-stages.ts +25 -118
  42. package/plugins/kk/scripts/kk-workflow-stages.ts +38 -1
  43. package/plugins/kk/scripts/seo-decisions.ts +477 -0
  44. package/plugins/kk/scripts/seo-enrich.ts +728 -0
  45. package/plugins/kk/scripts/seo-facts.ts +610 -0
  46. package/plugins/kk/scripts/seo-fetch.ts +766 -0
  47. package/plugins/kk/scripts/seo-report.ts +460 -0
  48. package/plugins/kk/scripts/seo-rules.ts +1882 -0
  49. package/plugins/kk/scripts/seo-stages.ts +1044 -0
  50. package/plugins/kk/skills/article-adapt/SKILL.md +2 -2
  51. package/plugins/kk/skills/smart-seo/SKILL.md +183 -0
  52. package/plugins/kk/skills/smart-seo/references/method.md +126 -0
  53. package/plugins/kk/skills/smart-seo/references/narrative.md +125 -0
  54. package/plugins/kk/skills/smart-seo/references/operations.md +202 -0
  55. package/plugins/kk/skills/smart-seo/references/seo-geo-sources.md +90 -0
  56. package/plugins/kk/workflows/feature-verification.yaml +28 -24
  57. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +85 -17
  58. package/plugins/kk/workflows/kk-itc-author.yaml +406 -0
  59. package/plugins/kk/workflows/kk-itc.yaml +208 -682
  60. package/plugins/kk/workflows/kk-smart-seo.yaml +251 -0
  61. package/plugins/publishings/bilibili-pub/dist/index.js +21782 -0
  62. package/plugins/publishings/bilibili-pub/dist/profile-check.js +20410 -0
  63. package/plugins/publishings/bilibili-pub/package.json +20 -0
  64. package/plugins/publishings/bilibili-pub/plugin.json +12 -0
  65. package/plugins/publishings/bilibili-pub/src/flow.ts +1296 -0
  66. package/plugins/publishings/bilibili-pub/src/index.ts +139 -0
  67. package/plugins/publishings/bilibili-pub/src/profile-check.ts +19 -0
  68. package/plugins/publishings/bilibili-pub/src/selectors.ts +222 -0
  69. package/plugins/publishings/bilibili-pub/tsconfig.json +4 -0
  70. package/plugins/publishings/cnblogs-pub/dist/index.js +21383 -0
  71. package/plugins/publishings/cnblogs-pub/dist/profile-check.js +20412 -0
  72. package/plugins/publishings/cnblogs-pub/package.json +19 -0
  73. package/plugins/publishings/cnblogs-pub/plugin.json +12 -0
  74. package/plugins/publishings/cnblogs-pub/src/flow.ts +708 -0
  75. package/plugins/publishings/cnblogs-pub/src/index.ts +172 -0
  76. package/plugins/publishings/cnblogs-pub/src/profile-check.ts +24 -0
  77. package/plugins/publishings/cnblogs-pub/src/selectors.ts +104 -0
  78. package/plugins/publishings/cnblogs-pub/tsconfig.json +4 -0
  79. package/plugins/publishings/csdn-pub/dist/index.js +21552 -0
  80. package/plugins/publishings/csdn-pub/dist/profile-check.js +20963 -0
  81. package/plugins/publishings/csdn-pub/package.json +19 -0
  82. package/plugins/publishings/csdn-pub/plugin.json +12 -0
  83. package/plugins/publishings/csdn-pub/src/index.ts +1178 -0
  84. package/plugins/publishings/csdn-pub/src/profile-check.ts +23 -0
  85. package/plugins/publishings/csdn-pub/tsconfig.json +4 -0
  86. package/plugins/publishings/emdash-pub/dist/index.js +158 -12
  87. package/plugins/publishings/emdash-pub/plugin.json +22 -1
  88. package/plugins/publishings/infoq-pub/dist/index.js +160 -12
  89. package/plugins/publishings/infoq-pub/dist/profile-check.js +160 -12
  90. package/plugins/publishings/infoq-pub/plugin.json +21 -0
  91. package/plugins/publishings/jike-pub/dist/index.js +21379 -0
  92. package/plugins/publishings/jike-pub/dist/profile-check.js +20951 -0
  93. package/plugins/publishings/jike-pub/package.json +19 -0
  94. package/plugins/publishings/jike-pub/plugin.json +12 -0
  95. package/plugins/publishings/jike-pub/src/index.ts +800 -0
  96. package/plugins/publishings/jike-pub/src/profile-check.ts +21 -0
  97. package/plugins/publishings/jike-pub/tsconfig.json +4 -0
  98. package/plugins/publishings/juejin-pub/dist/index.js +160 -12
  99. package/plugins/publishings/juejin-pub/dist/profile-check.js +160 -12
  100. package/plugins/publishings/juejin-pub/plugin.json +21 -0
  101. package/plugins/publishings/medium-pub/dist/index.js +158 -12
  102. package/plugins/publishings/medium-pub/plugin.json +22 -1
  103. package/plugins/publishings/podcast-pub/dist/index.js +158 -12
  104. package/plugins/publishings/qiita-pub/dist/index.js +158 -12
  105. package/plugins/publishings/qiita-pub/plugin.json +24 -1
  106. package/plugins/publishings/shipinhao-pub/dist/index.js +21715 -0
  107. package/plugins/publishings/shipinhao-pub/dist/profile-check.js +20431 -0
  108. package/plugins/publishings/shipinhao-pub/package.json +19 -0
  109. package/plugins/publishings/shipinhao-pub/plugin.json +12 -0
  110. package/plugins/publishings/shipinhao-pub/src/flow.ts +1323 -0
  111. package/plugins/publishings/shipinhao-pub/src/index.ts +173 -0
  112. package/plugins/publishings/shipinhao-pub/src/profile-check.ts +25 -0
  113. package/plugins/publishings/shipinhao-pub/src/selectors.ts +241 -0
  114. package/plugins/publishings/shipinhao-pub/tsconfig.json +4 -0
  115. package/plugins/publishings/sspai-pub/dist/index.js +21371 -0
  116. package/plugins/publishings/sspai-pub/dist/profile-check.js +20414 -0
  117. package/plugins/publishings/sspai-pub/package.json +19 -0
  118. package/plugins/publishings/sspai-pub/plugin.json +12 -0
  119. package/plugins/publishings/sspai-pub/src/flow.ts +624 -0
  120. package/plugins/publishings/sspai-pub/src/index.ts +167 -0
  121. package/plugins/publishings/sspai-pub/src/profile-check.ts +25 -0
  122. package/plugins/publishings/sspai-pub/src/selectors.ts +122 -0
  123. package/plugins/publishings/sspai-pub/tsconfig.json +4 -0
  124. package/plugins/publishings/substack-pub/dist/index.js +160 -12
  125. package/plugins/publishings/substack-pub/dist/profile-check.js +160 -12
  126. package/plugins/publishings/substack-pub/plugin.json +21 -0
  127. package/plugins/publishings/surfdash-pub/dist/index.js +158 -12
  128. package/plugins/publishings/surfdash-pub/plugin.json +22 -1
  129. package/plugins/publishings/threads-pub/dist/index.js +158 -12
  130. package/plugins/publishings/wechatmp-pub/dist/index.js +1669 -502
  131. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +204 -21
  132. package/plugins/publishings/wechatmp-pub/plugin.json +32 -0
  133. package/plugins/publishings/wechatmp-pub/src/cgi-draft.ts +540 -0
  134. package/plugins/publishings/wechatmp-pub/src/cgi-media.ts +380 -0
  135. package/plugins/publishings/wechatmp-pub/src/cgi.ts +485 -0
  136. package/plugins/publishings/wechatmp-pub/src/flow.ts +104 -0
  137. package/plugins/publishings/weibo-pub/dist/index.js +160 -12
  138. package/plugins/publishings/weibo-pub/dist/profile-check.js +160 -12
  139. package/plugins/publishings/weibo-pub/plugin.json +35 -0
  140. package/plugins/publishings/x-pub/dist/index.js +160 -12
  141. package/plugins/publishings/x-pub/dist/profile-check.js +160 -12
  142. package/plugins/publishings/x-pub/plugin.json +40 -0
  143. package/plugins/publishings/xhs-pub/dist/index.js +160 -12
  144. package/plugins/publishings/xhs-pub/dist/profile-check.js +160 -12
  145. package/plugins/publishings/xhs-pub/plugin.json +37 -0
  146. package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +21491 -0
  147. package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +20417 -0
  148. package/plugins/publishings/xiaoyuzhou-pub/package.json +19 -0
  149. package/plugins/publishings/xiaoyuzhou-pub/plugin.json +12 -0
  150. package/plugins/publishings/xiaoyuzhou-pub/src/flow.ts +826 -0
  151. package/plugins/publishings/xiaoyuzhou-pub/src/index.ts +174 -0
  152. package/plugins/publishings/xiaoyuzhou-pub/src/profile-check.ts +26 -0
  153. package/plugins/publishings/xiaoyuzhou-pub/src/selectors.ts +125 -0
  154. package/plugins/publishings/xiaoyuzhou-pub/tsconfig.json +4 -0
  155. package/plugins/publishings/zenn-pub/dist/index.js +158 -12
  156. package/plugins/publishings/zenn-pub/plugin.json +22 -1
  157. package/plugins/generations/omni-voice-gen/voices/robin-news.wav +0 -0
@@ -0,0 +1,90 @@
1
+ # Smart SEO — sources, crawler policy and GEO limits
2
+
3
+ Every rule row carries the primary `sourceUrl` and the `sourceCheckedAt` date that justified it, and
4
+ the report header prints the method version's checked-at date. This reference is the refresh
5
+ procedure and the per-source limits — not a license to restate a source as a measurement.
6
+
7
+ ## 1. Primary sources (checked 2026-10-14)
8
+
9
+ | Source | What it owns | Limit / unknown |
10
+ | --- | --- | --- |
11
+ | [Google AI optimization guide](https://developers.google.com/search/docs/fundamentals/ai-optimization-guide) | Original, useful content plus technical access are what matter for Google AI surfaces. Special AI files, rigid chunking and schema are not eligibility requirements. | It promises no citation, ranking or inclusion. |
12
+ | [Google AI features](https://developers.google.com/search/docs/appearance/ai-features) | Snippet eligibility and indexing are prerequisites; crawl observations cannot verify inclusion. | Reporting surface is described inconsistently across Google pages and depends on the account; never freeze an availability claim. |
13
+ | [Google common crawlers](https://developers.google.com/crawling/docs/crawlers-fetchers/google-common-crawlers) | `Google-Extended` controls separate uses and does not control Search inclusion. | Robots groups express the site owner's **declared** policy only. |
14
+ | [OpenAI crawler documentation](https://developers.openai.com/api/docs/bots) | `OAI-SearchBot` is for search, `GPTBot` for training, `ChatGPT-User` is user-triggered with different semantics. | Blocking training is not a search defect; no OpenAI behavior is observable from the audit. |
15
+ | [Google Search updates](https://developers.google.com/search/updates) | Dated change log; FAQ rich results were fully retired on 2026-05-07 (eligibility had already been restricted to authoritative government/health sites in Aug/Sep 2023). | Any removal date must be re-read from this page, not from memory. |
16
+ | [PSI API notice](https://developers.google.com/speed/docs/insights/v5/get-started) | PSI setup and the announced CrUX removal from PSI. | The removal is announced, not completed — do not report it as done. |
17
+ | [Web Vitals](https://web.dev/articles/vitals) | Field p75 LCP ≤ 2.5 s, INP ≤ 200 ms, CLS ≤ 0.1 defines a Core Web Vitals pass. | Lab TBT cannot substitute for INP; a missing field sample is `unknown`, never zero. |
18
+
19
+ Supporting rule-level sources (RFC 9309 for robots matching, Google canonicalization and robots-meta
20
+ docs, sitemap docs) are recorded per row in `plugins/kk/scripts/seo-rules.ts` (`sourceUrl`).
21
+
22
+ ## 2. Crawler policy — keep the three purposes separate
23
+
24
+ The audit reads the site's declared crawler policy and the audit's own requests. It cannot observe
25
+ what any other crawler did. When you explain crawler findings, name the purpose explicitly:
26
+
27
+ | Purpose | Typical user agents (as documented by their owners) | What a block means |
28
+ | --- | --- | --- |
29
+ | **Search / answer-surface indexing** | `Googlebot`, `Bingbot`, `OAI-SearchBot` | Affects eligibility for search and AI answer surfaces that use a search index. Operator intent required: a deliberate restriction is a decision, not a defect. |
30
+ | **Model training corpus** | `GPTBot`, `Google-Extended`, `ClaudeBot`, `CCBot` | A policy choice about training data. Blocking training is **not** a search defect, and allowing it is not a ranking or citation strategy. |
31
+ | **User-triggered fetch** | `ChatGPT-User` and similar on-demand fetchers | Different robots semantics: the fetch serves a user's explicit request. A block limits that user's session, not search eligibility. |
32
+
33
+ Rules the audit uses here: `robots_blocks_site` (adapted, critical), `ai_bots_blocked` and
34
+ `llms_txt_missing` (informational), `js_dependent` (informational). Report the observed directive,
35
+ the path it applies to and the operator-intent caveat — never "AI crawlers cannot see your site".
36
+
37
+ Also state what the audit itself did: its own crawl honors robots.txt, the declared `Crawl-delay`
38
+ and one shared page/request/byte/deadline budget, and it sends no credentials or cookies.
39
+
40
+ ## 3. Source refresh procedure
41
+
42
+ Do this before changing a rule, re-publishing a rubric statement, or answering a question whose
43
+ answer depends on current engine behavior:
44
+
45
+ 1. Open the rule's `sourceUrl` from the failing row (or the table above) and re-read the relevant
46
+ section. Do not refresh from a summary, a vendor README or a search-result snippet.
47
+ 2. Record today's date as the new `sourceCheckedAt` for that rule and update `sourceUrl` if the
48
+ owner moved the page. Never silently keep an old checked-at date.
49
+ 3. If the source contradicts the rule's current rationale, treat that as a **rule defect**: the
50
+ rationale, disposition and severity all change together, and the change is a method-version
51
+ change — not an in-run edit of a saved audit.
52
+ 4. If two sources disagree, keep the more conservative reading, report the uncertainty, and say
53
+ which pages disagree. Vendor self-reported model agreement is not human calibration.
54
+ 5. Never re-state an announced-but-incomplete change as shipped (for example the PSI/CrUX notice).
55
+
56
+ Audits stay reproducible: a saved `audit.json` keeps the `sourceCheckedAt` it was produced with,
57
+ and offline rescoring reuses the saved check rows rather than re-reading live sources.
58
+
59
+ ## 4. GEO (generative-engine) diagnostics and their limits
60
+
61
+ GEO work in this method is **editorial diagnostics with explicit uncertainty**: intent
62
+ satisfaction, first-hand specificity, verifiable provenance, entity consistency and self-contained
63
+ answer-first facts. They are model judgments (`source: model`) with a maker and confidence, or
64
+ heuristic content checks — never measurements of an answer engine's behavior. The two `geo`
65
+ category rules and the page-level semantic decisions feed the action list exactly like other rows.
66
+
67
+ What this means in practice:
68
+
69
+ - Never state or imply that a change will produce citations, mentions, rankings or traffic.
70
+ - No synthetic visibility percentage is generated from a quality score. A visibility number exists
71
+ only when the operator supplied an imported snapshot (§5 of `operations.md`), and it is reported
72
+ with its source, window, platform and denominator.
73
+ - Locale-specific query and citation samples are dated snapshots, not live data. A missing
74
+ platform, denominator or sample count makes the row unusable — drop it rather than guess.
75
+ - "Answer-first" (heading fit, self-contained answer, next step) is an editorial rubric applied to
76
+ the observed page text. It is not a snippet or citation predictor.
77
+ - Where the evidence does not exist (a specific engine's answer for a specific query, an actual
78
+ citation rate, a training-corpus inclusion), say that it is unknown and name what would be needed
79
+ to know it. Live provider integration, rank tracking and Search Console OAuth are out of scope in
80
+ this version.
81
+
82
+ ## 5. Vendors and third-party code
83
+
84
+ `vendors/` is reference-only: it is excluded from runtime and install resolution, and nothing may
85
+ be read or copied out of it into a shipped file. The audit is native Bun/TypeScript — no Python
86
+ path, no browser/rendering runtime, no bundled font assets. Vendor rule identities are retained for
87
+ traceability, but their behavior reflects the dispositions in `plugins/kk/references/smart-seo-attribution.md`
88
+ (shipped beside this skill), not the vendor's weighted grades or relative impact scale. The full vendor
89
+ review lives in the source repository at `docs/plans/2026-10-14-smart-seo-vendor-review.md`; it is
90
+ design-time context and is not installed, so never resolve a runtime path through it.
@@ -10,8 +10,20 @@
10
10
  #
11
11
  # Run: spur workflow run feature-verification.yaml --vars '{"featureId":"D63"}'
12
12
  #
13
- # Shape: verify -> done | failed. The onEnter step runs the standard script
14
- # `feature-verification-steps.ts` (no public CLI verb, D63 task 0915): it
13
+ # No project-side override is required (task 1185, A33). Since the pass moved into the
14
+ # application (1176 / efd78f058) the bundled definition owns it end to end, so a
15
+ # project-layer copy of this file (plus a rooted port under `.spur/scripts/`) is a
16
+ # leftover — the port was written against an entry export contract that has since
17
+ # changed. Remove the override so this definition applies, or migrate the port to the
18
+ # current app-owned operation `runFeatureVerification` (the generated inline bundle's
19
+ # export). If a stale consumer is still reached, feature-lifecycle's `verifying` entry
20
+ # names it: the override path, the consumer script, the export it requires that the
21
+ # shipped entry no longer provides, and the entry path. Reinstalling or upgrading the
22
+ # plugin cannot supply a retired export — only the two remedies above can.
23
+ #
24
+ # Shape: verify -> done | failed. The onEnter step runs `spur feature verify`
25
+ # (D63 task 0915; script-absorption PR-5 retired the feature-verification-steps
26
+ # plugin script): it
15
27
  # resolves this very definition for the verifier identity (name, source path,
16
28
  # layer, definition digest), records the RUNNING receipt (run-scoped +
17
29
  # feature-latest copies), registers the run-scoped receipt as a run artifact,
@@ -40,36 +52,28 @@ vars:
40
52
  featureId: "X"
41
53
  spurBin: "spur"
42
54
  verificationCmd: "bun run spur-check"
55
+ # Operator-declared confidence level; empty derives it from the pass (HIGH when the selected
56
+ # definition's own command exited 0 with an unchanged proof-input digest, MEDIUM under a caller
57
+ # `--cmd` override, LOW otherwise). The completion boundary refuses a PASS carrying no level or
58
+ # LOW unless the receipt records the acknowledgement below.
59
+ confidence: ""
60
+ # Operator acknowledgement of a LOW level. It rides the receipt, so the transition reads it from
61
+ # the evidence rather than from whatever flags a later command happens to pass.
62
+ ackLowConfidence: "false"
43
63
  __runId: ""
44
64
 
45
65
  states:
46
66
  - id: verify
47
67
  description: >
48
- Resolve this run's installed sp script root via Superskill, then run the
49
- pinned installed verifier with Bun against the repo-wide check set. The
50
- verifier records the bound evidence receipt (verifier identity,
51
- checked-input digest, run identity) and the coarse status file. Both
52
- actions fail closed — a locator/script failure, an unresolved or
53
- non-installed root, an absent verifier, or a failing check yields a
54
- non-PASS status — and always exit 0, so the transition guard routes
55
- missing/corrupt/FAIL to `failed`.
68
+ Run the repo-wide check set via `spur feature verify`, which
69
+ records the bound evidence receipt (verifier identity, checked-input
70
+ digest, run identity) and the coarse status file, always exit 0 so the
71
+ transition guard routes missing/corrupt/FAIL to `failed`.
56
72
  onEnter:
57
73
  - kind: shell
58
74
  options:
59
75
  command: >-
60
- S="$(superskill script path sp script-root.mjs 2>/dev/null)" || S="";
61
- if [ -n "$S" ] && [ -f "$S" ]; then node "$S" --run-id "$__runId" --spur-bin "$spurBin";
62
- else echo "script-root failed closed — run 'superskill install sp --marketplace gobing-ai/spur'" >&2;
63
- fi;
64
- exit 0
65
- - kind: shell
66
- options:
67
- command: >-
68
- mkdir -p .spur/run;
69
- DIR="$(jq -r 'select(.mode == "installed" and (.dir | type == "string") and .dir != "") | .dir' ".spur/run/$__runId-script-root.json" 2>/dev/null)" || DIR="";
70
- [ -n "$DIR" ] && [ -f "$DIR/feature-verification-steps.mjs" ] && S="$DIR/feature-verification-steps.mjs";
71
- [ -f "$S" ] && bun "$S" verify --feature-id "$featureId" --run-id "$__runId" --spur-bin "$spurBin" || printf 'FAIL feature-verification failed closed — unresolved root or verifier failure\n' > ".spur/run/$__runId-feature-verification.status";
72
- exit 0
76
+ mkdir -p .spur/run; ACK=""; [ "$ackLowConfidence" = "true" ] && ACK="--ack-low-confidence"; $spurBin feature verify "$featureId" --run-id "$__runId" --confidence "$confidence" $ACK --no-logo || printf 'FAIL feature-verification failed closed — spur feature verify exited nonzero\n' > ".spur/run/$__runId-feature-verification.status"; exit 0
73
77
 
74
78
  - id: done
75
79
  description: Terminal — repo-wide checks passed and the receipt is bound.
@@ -80,7 +84,7 @@ states:
80
84
  transitions:
81
85
  - from: verify
82
86
  to: done
83
- description: Repo-wide checks passed — the script completed the receipt with PASS.
87
+ description: Repo-wide checks passed — `spur feature verify` completed the receipt with PASS.
84
88
  guard:
85
89
  kind: shell
86
90
  options:
@@ -22,10 +22,13 @@ vars:
22
22
  window: "24h"
23
23
  language: "zh"
24
24
  voice_profile: "robin-news"
25
- # Voice driver switch — voicebox (voice-gen) | ominivoice (omni-voice-gen) | gemini (gemini-tts)
26
- # | voxcpm (voxcpm-tts, opt-in; ADR-036; provisional 5.6 字/秒 estimate). Same default as
27
- # kk-daily-ai-voice: gemini-tts (20260929 operator override, verified live).
28
- vdriver: "gemini"
25
+ # Voice driver switch — voxcpm (voxcpm-tts) | gemini (gemini-tts) | voicebox (voice-gen)
26
+ # | ominivoice (omni-voice-gen). The generate tier below mirrors the fallback order, and the
27
+ # fit/duration gates take their spoken-char rate from this var — switching drivers here is enough.
28
+ # 2026-10-15 production test (operator decision): voxcpm first, then gemini, then omni. voxcpm
29
+ # rendered that episode in 21.8 min at a measured 5.19 字/秒 (fitted at 5.2); if the audio verdict
30
+ # is good this is the standing order, otherwise revert this default to "gemini".
31
+ vdriver: "voxcpm"
29
32
  intro_music: ".works/kk-daily-ai-voice/AI每日资讯_8秒播客片头.mp3"
30
33
  surfdash_root: "/Users/robin/xprojects/surfdash"
31
34
  title: ""
@@ -258,7 +261,7 @@ nodes:
258
261
  command: >-
259
262
  kk store pool-count "${vars.work_dir}/21-plan/${vars.run_date}_04_quality-control-content_candidates.json" --min 1
260
263
  && test "${vars.plan_translate_enabled}" = "true"
261
- && ! test -s "${vars.work_dir}/21-plan/${vars.run_date}_03_plan_plan.json"
264
+ && ! jq -e --argjson n "$(jq '.metadata.docs|length' "${vars.work_dir}/21-plan/${vars.run_date}_04_quality-control-content_candidates.json")" 'type=="array" and length==$n and all(.[];type=="object")' "${vars.work_dir}/21-plan/${vars.run_date}_03_plan_plan.json"
262
265
  action:
263
266
  kind: agent.run
264
267
  # 0238 R2: a stochastic executor no-op must not end the run here — the retry sibling decides.
@@ -268,21 +271,28 @@ nodes:
268
271
  agent: ${vars.agent}
269
272
  # ADR-115 error tier: keep this prompt <= 1000 chars (0133 R3; 0138 R4a copy discipline).
270
273
  # The anchor is shared with plan-translate-retry so the two attempts cannot drift apart.
274
+ # 20261015 forensics: headless agy's terminal sandbox denies workspace writes for EVERY
275
+ # shell command (command() rules are promptless-but-sandboxed; unsandboxed() is dead), so
276
+ # the producer must deliver via the file-write tool; and the condition gate checks JSON
277
+ # validity (array, length == candidates docs, all objects) because a pi token-cap staged
278
+ # write once flowed 4/12 items downstream. Keep both when editing prompt or condition.
271
279
  input: &plan-translate-input |
272
- Read ${vars.work_dir}/21-plan/${vars.run_date}_04_quality-control-content_candidates.json — metadata.docs (Doc[]) is the plan; don't edit it.
273
- For needsTranslation items, adapt "title", "body" and "metadata.brief" into spoken simplified Chinese — full coverage, not a summary: keep every fact, number, name, quote and hedge (口播全长改编,不摘要不省略), body ≈0.6–0.7× the source.
274
- Titles read as a news event; keep product/model names verbatim, preserve body URLs, use 「」 not ASCII double quotes.
275
- Copy discipline: reader-facing news copy — no scaffold labels (「可做角度」「为什么现在值得注意」「社区讨论」「背景」「潜在影响」「影响」), no duplicated labels or hedging (「读者宜另行核实」「仍待验证」) — merge their substance into prose or drop.
276
- Keep other fields (id, sourceUri, mediaType, metadata) as-is; no comments or trailing commas.
277
- Output strictly valid JSON — a same-length, same-order array — to ${vars.work_dir}/21-plan/${vars.run_date}_03_plan_plan.json.
280
+ Read ${vars.work_dir}/21-plan/${vars.run_date}_04_quality-control-content_candidates.json; metadata.docs (Doc[]) is the plan.
281
+ For needsTranslation items adapt "title", "body" and "metadata.brief" into spoken simplified Chinese — keep every fact, number, name, quote, hedge (口播全长改编,不摘要不省略), body ≈0.6–0.7× source.
282
+ Titles read as news; keep product/model names and body URLs verbatim, use 「」 not ASCII quotes.
283
+ Copy discipline: reader-facing news copy — no scaffold labels (「可做角度」「为什么现在值得注意」「社区讨论」「背景」「潜在影响」「影响」), no hedging labels (「读者宜另行核实」「仍待验证」) — merge or drop.
284
+ Keep id/sourceUri/mediaType/metadata as-is; no comments/trailing commas.
285
+ Deliver with your file-write tool (headless agy sandbox blocks shell writes); the file must end as ONE valid JSON array — no split markers/placeholders.
286
+ Output the JSON array — same length and order — to ${vars.work_dir}/21-plan/${vars.run_date}_03_plan_plan.json.
278
287
  expectFile: ${vars.work_dir}/21-plan/${vars.run_date}_03_plan_plan.json
279
288
  allowAppend: false
280
289
 
281
290
  - id: plan-translate-retry
282
291
  description: >-
283
- Bounded recovery for plan-translate (0238 R2): runs only when the primary settled without its
284
- expectFile, retries the same input on a different tier, and carries NO onError — a second miss
285
- fails the run loudly instead of letting an untranslated plan flow downstream.
292
+ Bounded recovery for plan-translate (0238 R2): runs only when the primary settled without a
293
+ VALID expectFile (missing or contract-violating JSON), retries the same input on a different
294
+ tier, and carries NO onError — a second miss fails the run loudly instead of letting an
295
+ untranslated or truncated plan flow downstream.
286
296
  dependsOn: [plan-translate]
287
297
  condition:
288
298
  kind: shell
@@ -290,7 +300,7 @@ nodes:
290
300
  command: >-
291
301
  kk store pool-count "${vars.work_dir}/21-plan/${vars.run_date}_04_quality-control-content_candidates.json" --min 1
292
302
  && test "${vars.plan_translate_enabled}" = "true"
293
- && ! test -s "${vars.work_dir}/21-plan/${vars.run_date}_03_plan_plan.json"
303
+ && ! jq -e --argjson n "$(jq '.metadata.docs|length' "${vars.work_dir}/21-plan/${vars.run_date}_04_quality-control-content_candidates.json")" 'type=="array" and length==$n and all(.[];type=="object")' "${vars.work_dir}/21-plan/${vars.run_date}_03_plan_plan.json"
294
304
  action:
295
305
  kind: agent.run
296
306
  options:
@@ -535,7 +545,7 @@ nodes:
535
545
  kk stage daily-wrap-docs "${vars.work_dir}" "${vars.run_date}" "${vars.voice_profile}"
536
546
 
537
547
  - id: generate
538
- description: "Invoke the voice driver plugin (vdriver: gemini -> gemini-tts, voxcpm -> voxcpm-tts) to generate the audio"
548
+ description: "Voice tier, primary driver (vars.vdriver — voxcpm while the 2026-10-15 production test runs). onError: continue — the fallback siblings below are the loud chain (same pattern as 0238 R2)."
539
549
  # wrap-docs is a data dependency, not a sibling: daily-generate reads its _10_wrap-docs_docs.json
540
550
  # as the TTS input. Without this edge the two nodes race in the same wave (0212 dogfood finding).
541
551
  dependsOn: [script, safety, review, wrap-docs]
@@ -547,18 +557,76 @@ nodes:
547
557
  action:
548
558
  kind: shell
549
559
  options:
560
+ onError: continue
550
561
  command: >-
551
562
  OMNIVOICE_SPEED=1.06 kk stage daily-generate "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}" "${vars.transcode_mp3}" "${vars.vdriver}" "1" "${vars.episode_max_minutes}"
552
563
 
564
+ # Driver fallback chain (2026-10-15, operator decision): voxcpm -> gemini -> omni. Each sibling
565
+ # runs the same stage with a pinned driver; its condition re-checks the pool AND that NO upstream
566
+ # driver left a valid, passing whole-document Content (the stage stamps metadata.generator with
567
+ # its plugin id and the cache ignores foreign-driver sections, so a failed attempt can neither be
568
+ # relabeled nor mix voices). Non-final siblings continue on error; the last one is loud.
569
+ - id: generate-fallback
570
+ description: "Voice tier fallback 1: gemini (gemini-tts) when the primary driver left no valid Content"
571
+ dependsOn: [generate]
572
+ condition:
573
+ kind: shell
574
+ options:
575
+ command: >-
576
+ kk store pool-count "${vars.work_dir}/21-plan/${vars.run_date}_04_quality-control-content_candidates.json" --min 1
577
+ && ! jq -e '(.metadata.generator == "kk:voxcpm-tts" or .metadata.generator == "kk:gemini-tts") and .metadata.qc.passed == true' "${vars.work_dir}/30-audio/${vars.run_date}_11_generate_content.json"
578
+ action:
579
+ kind: shell
580
+ options:
581
+ onError: continue
582
+ command: >-
583
+ kk stage daily-generate "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}" "${vars.transcode_mp3}" "gemini" "1" "${vars.episode_max_minutes}"
584
+
585
+ - id: generate-fallback2
586
+ description: "Voice tier fallback 2 (last, loud): omni-voice-gen when neither voxcpm nor gemini delivered"
587
+ dependsOn: [generate-fallback]
588
+ condition:
589
+ kind: shell
590
+ options:
591
+ command: >-
592
+ kk store pool-count "${vars.work_dir}/21-plan/${vars.run_date}_04_quality-control-content_candidates.json" --min 1
593
+ && ! jq -e '(.metadata.generator == "kk:voxcpm-tts" or .metadata.generator == "kk:gemini-tts" or .metadata.generator == "kk:omni-voice-gen") and .metadata.qc.passed == true' "${vars.work_dir}/30-audio/${vars.run_date}_11_generate_content.json"
594
+ action:
595
+ kind: shell
596
+ options:
597
+ command: >-
598
+ OMNIVOICE_SPEED=1.06 kk stage daily-generate "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}" "${vars.transcode_mp3}" "ominivoice" "1" "${vars.episode_max_minutes}"
599
+
553
600
  - id: intro
554
601
  description: "Prepend the 8s intro sting (intro_music var; empty = skip), update duration/audioPath"
555
- dependsOn: [generate]
602
+ dependsOn: [generate, generate-fallback, generate-fallback2]
556
603
  action:
557
604
  kind: shell
558
605
  options:
559
606
  command: >-
560
607
  kk stage daily-intro "${vars.work_dir}" "${vars.run_date}" "${vars.intro_music}"
561
608
 
609
+ # ---- advisory voice audit (2026-10-16): never fails the run ----
610
+ # The 20261015 episode shipped 59 different speakers because an instruct-mode profile pins none,
611
+ # and nothing but a human listening caught it. This grades the final render by per-segment median
612
+ # F0 (a speaker change swings it tens of Hz) and prints the table plus a PASS/FAIL line. It is
613
+ # deliberately advisory: `|| true` keeps its non-zero exit out of the run, and onError covers a
614
+ # missing venv (the check needs librosa from the voxcpm plugin's environment).
615
+ - id: voice-audit
616
+ description: "Advisory speaker-consistency audit of the joined episode (warn-only, never fails the run)"
617
+ dependsOn: [intro]
618
+ condition:
619
+ kind: shell
620
+ options:
621
+ command: >-
622
+ test -f "${vars.work_dir}/30-audio/${vars.run_date}_11_generate_content.json"
623
+ action:
624
+ kind: shell
625
+ options:
626
+ command: >-
627
+ kk stage daily-voice-audit "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}"
628
+ onError: continue
629
+
562
630
  - id: duration
563
631
  description: "Measured duration gate: under episode_min_minutes the run refuses and closes as no-content (refusal artifact + emptied candidates, exit 0 — 0266); above episode_max_minutes it still fails loud. Duration is authoritative."
564
632
  dependsOn: [intro]