@gobing-ai/knowledge-kit 0.0.7 → 0.0.9

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 (61) hide show
  1. package/dist/index.js +21604 -11844
  2. package/package.json +4 -1
  3. package/plugins/generations/content-gen/package.json +2 -1
  4. package/plugins/generations/content-gen/src/index.ts +9 -8
  5. package/plugins/generations/content-gen/src/storm.ts +12 -4
  6. package/plugins/generations/dailynews-gen/package.json +17 -0
  7. package/plugins/generations/dailynews-gen/plugin.json +7 -0
  8. package/plugins/generations/dailynews-gen/src/index.ts +111 -0
  9. package/plugins/generations/dailynews-gen/src/script-builder.ts +121 -0
  10. package/plugins/generations/dailynews-gen/tsconfig.json +4 -0
  11. package/plugins/generations/voice-gen/package.json +17 -0
  12. package/plugins/generations/voice-gen/plugin.json +6 -0
  13. package/plugins/generations/voice-gen/src/concat.ts +218 -0
  14. package/plugins/generations/voice-gen/src/index.ts +228 -0
  15. package/plugins/generations/voice-gen/src/mp3.ts +65 -0
  16. package/plugins/generations/voice-gen/src/voicebox-client.ts +223 -0
  17. package/plugins/generations/voice-gen/src/voicescript.ts +365 -0
  18. package/plugins/generations/voice-gen/tsconfig.json +8 -0
  19. package/plugins/ingestions/aihot-ingest/package.json +17 -0
  20. package/plugins/ingestions/aihot-ingest/plugin.json +7 -0
  21. package/plugins/ingestions/aihot-ingest/src/client.ts +185 -0
  22. package/plugins/ingestions/aihot-ingest/src/index.ts +137 -0
  23. package/plugins/ingestions/aihot-ingest/src/mapper.ts +42 -0
  24. package/plugins/ingestions/aihot-ingest/tsconfig.json +4 -0
  25. package/plugins/ingestions/karakeep-local/package.json +17 -0
  26. package/plugins/ingestions/karakeep-local/src/index.ts +31 -26
  27. package/plugins/ingestions/karakeep-local/tsconfig.json +4 -0
  28. package/plugins/ingestions/web-search/package.json +4 -1
  29. package/plugins/ingestions/web-search/src/index.ts +139 -16
  30. package/plugins/kk/README.md +9 -3
  31. package/plugins/kk/commands/workflow-run.md +100 -28
  32. package/plugins/kk/config.example.yaml +34 -0
  33. package/plugins/kk/scripts/render-md.ts +8 -3
  34. package/plugins/kk/skills/{judge → content-judge}/SKILL.md +13 -14
  35. package/plugins/kk/skills/{judge → content-judge}/references/workflow-integration.md +13 -11
  36. package/plugins/kk/skills/itc-generating/SKILL.md +147 -0
  37. package/plugins/kk/skills/itc-generating/references/generic-craft.md +80 -0
  38. package/plugins/kk/skills/itc-generating/references/platform-english.md +72 -0
  39. package/plugins/kk/skills/itc-generating/references/platform-wechat.md +60 -0
  40. package/plugins/kk/skills/itc-generating/references/skill-authoring.md +62 -0
  41. package/plugins/kk/skills/storm-research/SKILL.md +10 -3
  42. package/plugins/kk/workflows/judge-gated-publish-example.yaml +101 -0
  43. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +144 -0
  44. package/plugins/kk/workflows/kk-ingest-generate-publish.yaml +72 -0
  45. package/plugins/kk/workflows/kk-itc.yaml +285 -0
  46. package/plugins/kk/workflows/kk-solo-podcast.yaml +374 -0
  47. package/plugins/kk/workflows/validate-voicescript.ts +226 -0
  48. package/plugins/publishings/emdash-pub/package.json +17 -0
  49. package/plugins/publishings/emdash-pub/plugin.json +7 -0
  50. package/plugins/publishings/emdash-pub/src/index.ts +450 -0
  51. package/plugins/publishings/emdash-pub/tsconfig.json +4 -0
  52. package/plugins/publishings/qiita-pub/package.json +2 -1
  53. package/plugins/publishings/qiita-pub/src/index.ts +9 -9
  54. package/plugins/publishings/surfdash-pub/package.json +2 -1
  55. package/plugins/publishings/surfdash-pub/src/index.ts +17 -12
  56. package/plugins/publishings/zenn-pub/package.json +2 -1
  57. package/plugins/publishings/zenn-pub/src/index.ts +11 -11
  58. package/plugins/kk/agents/judge-compliance.md +0 -37
  59. package/plugins/kk/agents/judge-tech.md +0 -35
  60. package/plugins/kk/agents/judge-tone.md +0 -37
  61. /package/plugins/kk/skills/{judge → content-judge}/references/rubrics.md +0 -0
@@ -0,0 +1,72 @@
1
+ # English-platform playbook (X / Medium / Substack)
2
+
3
+ Distilled from the STORM study "How to Write and Distribute IT Technical Documents on English
4
+ Platforms" (works dir `how-to-write-it-technical-documents-for-english-a6a76477`, provenance only).
5
+ Load this reference when `--playbook english`; it shapes platform choice, format, and distribution
6
+ for the draft — never reimplements publishing.
7
+
8
+ ## Choose the platform before you write
9
+
10
+ - The writer's-first-question fallacy: pick a platform *after* deciding what you actually want as
11
+ a writer. Technical content is expensive to produce; making it for the wrong audience on the
12
+ wrong surface wastes your scarcest resource.
13
+ - Compare platforms on three axes:
14
+ - **Growth/discoverability** — Medium: internal distribution, but the algorithm decides reach.
15
+ X: reach with high volatility. Substack: slow to start, compounds (Notes, recommendations,
16
+ direct email) into an asset you keep.
17
+ - **Audience/culture** — X is fast and combative; Medium skews essay readers; Substack readers
18
+ opt in deliberately (roughly 85% email / 10% app on a typical post).
19
+ - **Business model** — Medium pays per member read; X monetization is thin for most; Substack is
20
+ free, you own your subscriber list, paid subs optional (10% cut only when someone pays).
21
+ - For IT writers the email list is the difference between building equity and renting attention.
22
+ Cross-posting to Medium is a fine low-effort hedge — just understand its mechanics first.
23
+
24
+ ## Launch and grow (Substack)
25
+
26
+ - Substack is three layers: email newsletter, publication (archive/about/navigation), and
27
+ social/discovery (Notes, recommendations, search). Most writers only use layer one — that is why
28
+ they struggle to grow.
29
+ - Setup order: name the publication (clear topic promise or memorable brand — not your personal
30
+ name unless known); write an attention-grabbing bio (what you write, who it's for, credentials);
31
+ import any existing subscriber list; publish a first post before promoting.
32
+ - Cadence: a sustainable frequency — one good post per week beats one "perfect" post per month;
33
+ shipping gets you feedback to iterate. Open each issue with a clear thesis and keep it readable
34
+ in an email client.
35
+ - **Growth levers combine**: from zero, Notes daily + connecting with writers gets the first
36
+ 50–100 subs; 100–500 layers in guest posts, collaborations, and recommendations (one publication
37
+ gained ~7,000 subs through recommendations alone).
38
+ - **Notes** is the "new Twitter for writers": short valuable insights, questions, daily presence,
39
+ genuine engagement (comments, restacks). Not random hot takes or growth-hacking. A 3-2-1 weekly
40
+ rhythm: three value notes, two engagement posts, one soft promotion.
41
+ - **Own the channel, rent the algorithm**: the email list is the asset that survives platform
42
+ changes; Notes/X/LinkedIn are top-of-funnel discovery — valuable, volatile, never the foundation.
43
+
44
+ ## Position and voice
45
+
46
+ - **Author-first** (the newsletter builds an audience for *you*; topics range widely) vs
47
+ **topic-first** (the publication is the draw; the niche is the promise — "production Kubernetes
48
+ war stories"). IT content tends to capture niche intent faster topic-first; author-first builds a
49
+ brand that travels.
50
+ - **Voice is the durable moat**: people want the person behind the publication. When automation
51
+ repurposes content, preserve conversational voice and framework thinking — never "AI rewrites my
52
+ stuff," which yields generic output.
53
+
54
+ ## Repurposing (one long-form → many assets)
55
+
56
+ - One newsletter becomes 5 LinkedIn posts + 10 Substack Notes + 3 X threads via a webhook →
57
+ extract → generate → deliver pipeline (Make.com + Firecrawl + OpenAI + Google Docs), cutting
58
+ hours of manual reformatting to ~30 minutes of polish.
59
+ - **Adapt per platform, never copy-paste**: LinkedIn wants declarative hooks + strategic line
60
+ breaks; Substack Notes wants ~200-character insights with bold + mic-drop endings; X threads
61
+ need conversational hooks with curiosity gaps between tweets.
62
+ - Dense IT material (architecture tradeoffs, debugging walkthroughs, benchmarks) must be re-cut
63
+ per surface. Long-form Substack/Medium carries the full technical depth and is the canonical
64
+ source everything links back to.
65
+ - **The 10–20% human polish pass is mandatory and non-delegable** — for technical material this
66
+ is where accuracy lives: verify every code snippet, command, flag, and version-specific claim.
67
+
68
+ ## Applying this in `create`
69
+
70
+ Load this reference when `--playbook english`. It informs the draft's framing (audience, voice,
71
+ platform-native format) and the distribution plan, but the deliverable stays `topic`'s
72
+ `3-draft/draft-article.md` — this playbook never publishes and never writes a parallel artifact.
@@ -0,0 +1,60 @@
1
+ # WeChat Official Account playbook (specific-platform study)
2
+
3
+ Distilled from the STORM study "How to Write and Publish Technical Documents on WeChat Official
4
+ Accounts" (works dir `how-to-write-it-technical-documents-for-specific-d158f7c2`, provenance only).
5
+ Load this reference when `--playbook wechat`; it shapes the draft for WeChat's reading context and
6
+ compliance constraints — never reimplements registration, publishing, or the API.
7
+
8
+ ## Why WeChat
9
+
10
+ - For Chinese academics, engineers, and technical decision-makers, WeChat Official Accounts
11
+ (微信公众号) are the primary delivery channel — researchers check it almost every waking hour.
12
+ - Authority bar: an expert should be able to name at least five public accounts on the topic they
13
+ follow. If your account would not make that list, you are not reaching the expert audience.
14
+
15
+ ## Account type and constraints (the drafting context)
16
+
17
+ - **Subscription account (订阅号)**: up to 1 push/day, but folded into a "Subscriptions" folder —
18
+ best for high-cadence technical publishing. **Service account (服务号)**: 4 messages/month but
19
+ appears in the main chat list with push — best for low-cadence, high-importance announcements.
20
+ - **Enterprise accounts (企业号)** are for internal newsletters (mutual approval, no public reach).
21
+ - **Hard limits that shape content**: each message carries up to 8 articles; **published articles
22
+ cannot be deleted** — only modified with a correction notice. Errors are permanent, so
23
+ review-before-publish is mandatory.
24
+ - Registration is a Chinese-identity-bound process (QR-scan binding, Chinese ID/phone for admins,
25
+ ICP / Mini Program filings for hosted infrastructure). Foreign entities must work through a
26
+ Chinese partner. API access needs AppID/AppSecret (QR-verified) and an IP whitelist.
27
+
28
+ ## Authoring for the WeChat reader
29
+
30
+ - **Markdown-first pipeline**: WeChat's rich-text editor cannot interpret Markdown. Write in
31
+ Markdown, convert to WeChat-compatible rich text (MD2WeChat / `lyricat/wechat-format`, or the
32
+ Obsidian `wechat-public-platform` plugin for one-command draft upload), then paste/upload.
33
+ Converters turn external links into footnotes (external links are not allowed in articles).
34
+ - **Format for mobile consumption** (articles are read on phones — commuting, mid-meeting):
35
+ short paragraphs, images/GIFs/audio/video (text-only underperforms), text-light images, abstract
36
+ (摘要) under 120 characters, 900×500px cover, Simplified Chinese, short self-explanatory titles
37
+ (truncated after ~26 Chinese characters), h2–h4 headings (leave h1 for larger embedding), 15px
38
+ body text, PingFang on iOS.
39
+ - **Language**: Chinese-language articles outperform English even for Chinese academics who read
40
+ English; some international accounts publish multilingual content.
41
+ - **What engages**: novelty (new findings/new resources), originality and authenticity, and
42
+ structural rigor — this audience is trained to be critical of a WeChat post.
43
+
44
+ ## Editorial standards for authority
45
+
46
+ - **Bilingual/translated material**: the CAICT model — long-form Chinese analyses of international
47
+ technical reports, crediting Chinese-institution contributors — is the reference pattern.
48
+ - **Worth a "top five" slot**: recurring high-signal formats (standards announcements, seminar
49
+ notices, joint research, translated analyses) plus originality, authenticity, and rigor.
50
+ - **Review-before-publish checklist** (forced by the no-deletion rule): preview to your own WeChat
51
+ first; verify title length, abstract, cover, and footnote-converted links; confirm originality
52
+ declaration, tags, and comment settings manually in the draft box; route the broadcast through
53
+ the admin QR-code verification step.
54
+
55
+ ## Applying this in `create`
56
+
57
+ Load this reference when `--playbook wechat`. It informs the draft's structure (mobile-first
58
+ formatting, Chinese-language expectations, title/abstract limits, review rigor) but the deliverable
59
+ stays `topic`'s `3-draft/draft-article.md` — this playbook never registers an account, never
60
+ publishes, and never touches the WeChat API.
@@ -0,0 +1,62 @@
1
+ # Skill-authoring self-discipline (why this skill stays lean)
2
+
3
+ Distilled from the STORM study "How to Write the Best Technical Documentation with Agent Skills"
4
+ (works dir `how-to-write-the-best-technical-document-with-ex-2c52b35c`, provenance only). This
5
+ reference is the meta-discipline for *this skill*: it exists so `itc-generating` stays a thin
6
+ method+orchestration layer instead of re-vendoring craft or reimplementing sibling skills.
7
+
8
+ ## The SKILL.md contract (hard limits)
9
+
10
+ - **Frontmatter**: `name` ≤64 chars, lowercase letters/numbers/hyphens only, no XML tags or
11
+ reserved words, and must exactly match the parent directory name. `description` is non-empty,
12
+ ≤1,024 chars, no XML tags.
13
+ - **Body budget**: keep SKILL.md under ~500 lines / ~5,000 tokens — just the core instructions the
14
+ agent needs every run; split the rest into `references/`.
15
+ - **Context economics**: only `name` + `description` are pre-loaded; the rest enters context only
16
+ when needed. The description is the *only* routing signal — write it as a third-person capability
17
+ statement with explicit negative triggers so near-miss tasks are excluded.
18
+ - **Folder grammar**: SKILL.md + `references/` + `scripts/` + `assets/`, kept **exactly one level
19
+ deep** (`references/schema.md`, not `references/db/v1/schema.md`). Avoid nested reference chains —
20
+ when references point at references, the agent previews instead of reading fully.
21
+
22
+ ## Progressive disclosure (the load-on-demand rule)
23
+
24
+ - Keep the always-loaded core minimal; push detail into `references/` with **just-in-time
25
+ pointers**: "Read `references/api-errors.md` if the API returns a non-200" beats "see
26
+ references/ for details".
27
+ - Three patterns: high-level guide + references; domain-specific organization; conditional details
28
+ (branch to a section only when a condition holds).
29
+ - Prefer **deterministic scripts** for fragile/repetitive operations (link checks, format
30
+ validation) — not for judgment-heavy authoring. Design their failure modes deliberately.
31
+
32
+ ## Instruction design
33
+
34
+ - **Degree of freedom**: prescriptive exact-sequence instructions for fragile operations; goal-plus-
35
+ philosophy for judgment-heavy work (authoring). Vague skills fail — "handle errors appropriately"
36
+ carries no information.
37
+ - **Defaults, not menus**: pick one default and a one-line escape hatch, rather than listing
38
+ options and hoping the agent chooses well.
39
+ - **Add what the agent lacks; omit what it knows**: the filter is "would the agent get this wrong
40
+ without this instruction?" If no, cut it.
41
+ - Highest-value blocks: gotchas sections (environment facts that defy assumptions), output-format
42
+ templates, checklists and self-check loops.
43
+
44
+ ## Evaluation and review (how to keep improving the skill)
45
+
46
+ - Four validation passes: discovery (description in isolation — does it route correctly?),
47
+ logic (deterministic steps, no hallucinated gaps), adversarial edge-case QA ("try to break this
48
+ skill"), architecture refinement (enforce progressive disclosure).
49
+ - **Execute-then-refine** beats endless pre-polish: run the skill on real tasks and iterate;
50
+ read execution traces, not just outputs (instructions retried many times, instructions followed
51
+ that don't apply, too many options without a default).
52
+ - Add a **self-reflection step**: after a run, examine the friction log and improve the skill so
53
+ the next run does not struggle the same way.
54
+
55
+ ## How this skill applies it
56
+
57
+ `itc-generating` is one-level-deep by construction: SKILL.md points at the four `references/*.md`
58
+ files below it and loads them on demand (generic-craft always; playbook only when `--playbook` is
59
+ not `generic`). It reuses `topic`'s artifact contract rather than inventing a parallel workspace
60
+ (D5), delegates research/judging to `storm-research`/`content-judge` rather than reimplementing them (D6),
61
+ and stays under the body budget because the four STORM reports live in the works dirs as
62
+ provenance — never vendored here (D4).
@@ -35,7 +35,7 @@ Use when:
35
35
  Do **not** use for:
36
36
 
37
37
  - Authoring an article from a topic with no research need — that is `topic`.
38
- - Quality gating / judging generated content — that is `judge`.
38
+ - Quality gating / judging generated content — that is `content-judge`.
39
39
  - Publishing, platform adaptation, or illustration — out of C4 scope.
40
40
  - Anything requiring a raw `kk executor run` chain when the workflow already composes it.
41
41
 
@@ -93,7 +93,7 @@ skill is also callable directly as `Skill(skill="storm-research", args="<topic|-
93
93
  works dir (re-running a topic is legal — no `--force` needed).
94
94
  - If `raw` derives empty (no H1 and an empty basename, e.g. a file named `.md`) → fail loud.
95
95
 
96
- ## Works layout (0056, frozen)
96
+ ## Works layout (0056, frozen; ADR-013 adds `references/`)
97
97
 
98
98
  Per-topic workspace at `$works_dir/<topic-id>/`:
99
99
 
@@ -105,6 +105,7 @@ Per-topic workspace at `$works_dir/<topic-id>/`:
105
105
  | `docs.json` | ingest | `web-search` output `Doc[]` |
106
106
  | `content.json` | generate | STORM `Content` (machine contract) |
107
107
  | `content.md` | render | markdown sidecar report |
108
+ | `references/<doc-id>.md` | ingest (live path only) | normalized full content per source (`/v2/scrape` → markitdown); file name = Doc id; audit trail and the citation-resolution target for the report tail (ADR-013) |
108
109
 
109
110
  Contrast: `topic`'s authoring workspace is `./<kebab-case(topic)>` with `brief.md` /
110
111
  `2-outline/` / `3-draft/` and explicitly excludes research. C4 works live under `$works_dir`,
@@ -130,9 +131,10 @@ outside any repo.
130
131
  | `--in` file unreadable (missing / I/O) | exit 1, stderr names the path | fix the path / permissions |
131
132
  | Derived query / topic-id empty (no H1 and empty basename) | exit 1, stderr shows the derived value | add an H1 or give the file a real name |
132
133
  | Dest `$workflows_dir/<name>.yaml` exists with different bytes | warning, **no overwrite**; run proceeds with the user's copy | re-run with `--force` to replace from the install source |
133
- | Workflow YAML cannot be resolved from any install source | exit 1, stderr lists the roots searched | reinstall the package / set `KK_WORKFLOWS_SOURCE` |
134
+ | Workflow YAML cannot be resolved from any install source | exit 1, stderr lists the roots searched (`KK_WORKFLOWS_SOURCE`, `plugins/kk/workflows`) | reinstall the package / set `KK_WORKFLOWS_SOURCE` |
134
135
  | `KK_CONFIG` set but unreadable, or config unparseable / invalid types | exit 1, stderr names the key + expected vs actual type | fix the file, or delete it (recreated with defaults next run) |
135
136
  | Missing config at `KK_CONFIG` | **not a failure** — create-on-first-run with compiled defaults; an existing file is never overwritten | — |
137
+ | `markitdown` missing from `PATH` (live mode) | ingest exit 1, stderr `web-search failed: markitdown is not on PATH`; probed before any network call, so no `docs.json` write | install markitdown / put it on `PATH`, re-run ingest |
136
138
 
137
139
  Every failure names the exact path and the exact next command. Never silently skip, never
138
140
  auto-replace, never hash topic-id in the YAML.
@@ -154,6 +156,10 @@ auto-replace, never hash topic-id in the YAML.
154
156
  6. **Prefer primary sources in the markdown input file.** When a source file is supplied via
155
157
  `--in`, favor the primary material in it (first-party specs, owned notes, authoritative
156
158
  extracts) as the spine of the report; web results fill gaps, not the reverse.
159
+ 7. **Digest, don't index.** References are evidence, not the deliverable: the report synthesizes
160
+ findings across the supplied documents, compares them, and states conclusions — it never
161
+ lists sources one per line. The render drops the `## Outline` replay (ADR-013 D7); the
162
+ references tail plus `references/<doc-id>.md` files are the audit trail behind each cite id.
157
163
 
158
164
  ## Invariants
159
165
 
@@ -164,3 +170,4 @@ auto-replace, never hash topic-id in the YAML.
164
170
  `kk` CLI verb.
165
171
  - `FIRECRAWL_API_KEY` stays env-only.
166
172
  - Works layout file names are frozen (0056) — do not rename or add sidecars in this skill.
173
+ ADR-013 exception: the ingest live path writes `references/<doc-id>.md` per cached source.
@@ -0,0 +1,101 @@
1
+ "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
2
+ kind: state-machine
3
+ name: judge-gated-publish
4
+ description: "Example workflow where an LLM-as-judge agent.run step gates a publish transition"
5
+ initialState: generate
6
+ terminalStates:
7
+ - done
8
+ - failed
9
+ failureStates:
10
+ - failed
11
+ vars:
12
+ contentFile: ".spur/run/content.json"
13
+ verdictFile: ".spur/run/verdict.json"
14
+ rubric: "general"
15
+ maxRetries: "2"
16
+ # attempt is mutated by the revise loop; initialize to 0.
17
+ attempt: "0"
18
+ spurBin: "spur"
19
+ states:
20
+ - id: generate
21
+ description: "Generate content from brief or docs (kk invoke <generator>)"
22
+ onEnter:
23
+ - kind: shell
24
+ options:
25
+ # Seed the verdict so the judge gate guards can be evaluated in --dry-run
26
+ # (dry-run skips actions but still evaluates shell guards). A real run's
27
+ # judge step overwrites this file before the gate is read.
28
+ command: "echo '{\"verdict\":\"PASS\",\"score\":0.0}' > ${vars.verdictFile} && echo 'generating content...'"
29
+ - id: judge
30
+ description: "Evaluate generated content via the judge skill (agent.run)"
31
+ onEnter:
32
+ # `agent.run` is registered by the kk workflow host: it spawns an agent that loads the
33
+ # content-judge skill, evaluates the content against ${vars.rubric}, and writes the verdict
34
+ # JSON to ${vars.verdictFile}. Under `spur workflow run` (dry-run), this action is
35
+ # skipped — the seeded verdict above keeps the gate evaluable. See
36
+ # plugins/kk/skills/content-judge/SKILL.md and references/workflow-integration.md.
37
+ - kind: agent.run
38
+ options:
39
+ role: reviewer
40
+ input: |
41
+ Evaluate the content at ${vars.contentFile} against the ${vars.rubric} rubric.
42
+ Load plugins/kk/skills/content-judge/SKILL.md and its references/rubrics.md.
43
+ Write the verdict JSON to ${vars.verdictFile} per the contract in SKILL.md.
44
+ Print the verdict path as the last line of stdout.
45
+ expectFile: ${vars.verdictFile}
46
+ - id: publish
47
+ description: "Publish content after clearing judge gate (kk fanout)"
48
+ onEnter:
49
+ - kind: shell
50
+ options:
51
+ command: "echo 'publishing content...'"
52
+ - id: done
53
+ description: "Publish completed successfully"
54
+ - id: failed
55
+ description: "Judge rejected content (FAIL) or retries exhausted (NEEDS_REVISION)"
56
+
57
+ transitions:
58
+ - from: generate
59
+ to: judge
60
+ description: "Content generated -> proceed to evaluation"
61
+ guard:
62
+ kind: always
63
+
64
+ # PASS -> publish.
65
+ - from: judge
66
+ to: publish
67
+ description: "Judge verdict is PASS (or file absent under --dry-run) -> proceed to publish"
68
+ guard:
69
+ kind: shell
70
+ options:
71
+ # Fail-closed on an explicit non-PASS verdict; fail-open only when the file is
72
+ # absent, which happens under --dry-run (actions are skipped, so the judge
73
+ # never writes its verdict). A real run always has the file.
74
+ command: 'v="$(jq -r .verdict ${vars.verdictFile} 2>/dev/null)"; test -z "$v" -o "$v" = PASS'
75
+
76
+ # NEEDS_REVISION -> back to generate with a bounded retry budget.
77
+ - from: judge
78
+ to: generate
79
+ description: "Judge verdict is NEEDS_REVISION and retry budget remains -> regenerate with feedback"
80
+ guard:
81
+ kind: shell
82
+ options:
83
+ # Allow the revise loop only while the attempt counter is below maxRetries.
84
+ # The shell kind cannot mutate vars, so the host (or a sidecar note action) is
85
+ # responsible for incrementing attempt on each loop in a real run.
86
+ command: 'v="$(jq -r .verdict ${vars.verdictFile} 2>/dev/null)"; test "$v" = NEEDS_REVISION -a "$attempt" -lt "$maxRetries"'
87
+
88
+ # FAIL, or NEEDS_REVISION with no retries left -> failed.
89
+ - from: judge
90
+ to: failed
91
+ description: "Judge verdict is FAIL, or NEEDS_REVISION with retries exhausted -> block publish"
92
+ guard:
93
+ kind: shell
94
+ options:
95
+ command: 'v="$(jq -r .verdict ${vars.verdictFile} 2>/dev/null)"; test -n "$v" && { test "$v" = FAIL || test "$v" = NEEDS_REVISION -a "$attempt" -ge "$maxRetries"; }'
96
+
97
+ - from: publish
98
+ to: done
99
+ description: "Publish done"
100
+ guard:
101
+ kind: always
@@ -0,0 +1,144 @@
1
+ ---
2
+ "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
3
+ name: kk-daily-ai-voice
4
+ kind: state-machine
5
+ description: >-
6
+ Daily AI news broadcast pipeline orchestrating aihot-ingest, dailynews-gen, and voice-gen
7
+ initialState: prepare
8
+ terminalStates:
9
+ - done
10
+ - failed
11
+ failureStates:
12
+ - failed
13
+
14
+ vars:
15
+ work_dir: ""
16
+ limit: "10"
17
+ language: "zh"
18
+ voice_profile: "Robin"
19
+ title: ""
20
+ cursor: ""
21
+ state_file: ""
22
+ skip_review: "false"
23
+ transcode_mp3: "false"
24
+ plugins_path: ""
25
+ __hitlAnswer: ""
26
+
27
+ states:
28
+ - id: prepare
29
+ description: "Create workspace subdirectories and validate parameters"
30
+ onEnter:
31
+ - kind: shell
32
+ options:
33
+ command: >-
34
+ test -n "${vars.work_dir}" || { echo "missing required variable: work_dir" >&2 && exit 1; };
35
+ mkdir -p "${vars.work_dir}" "${vars.work_dir}/1-ingest" "${vars.work_dir}/2-script" "${vars.work_dir}/3-audio";
36
+ bun -e 'await Bun.write(process.argv[1], JSON.stringify({ limit: Number(process.argv[2]), cursor: process.argv[3] || undefined, stateFile: process.argv[4] || undefined }, null, 2))' "${vars.work_dir}/1-ingest/source.json" "${vars.limit}" "${vars.cursor}" "${vars.state_file}"
37
+
38
+ - id: fetch
39
+ description: "Ingest latest curated AI news from aihot.virxact.com"
40
+ onEnter:
41
+ - kind: shell
42
+ options:
43
+ command: >-
44
+ set --;
45
+ if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
46
+ kk executor run aihot-ingest --in "${vars.work_dir}/1-ingest/source.json" --out "${vars.work_dir}/1-ingest/docs.json" "$@"
47
+
48
+ - id: script
49
+ description: "Compile news Doc[] into structured broadcast VoiceScript YAML Content"
50
+ onEnter:
51
+ - kind: shell
52
+ options:
53
+ command: >-
54
+ set --;
55
+ if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
56
+ DAILYNEWS_LANGUAGE="${vars.language}" DAILYNEWS_TITLE="${vars.title}" VOICEBOX_DEFAULT_PROFILE="${vars.voice_profile}" kk executor run dailynews-gen --in "${vars.work_dir}/1-ingest/docs.json" --out "${vars.work_dir}/2-script/content.json" "$@" &&
57
+ bun -e 'await Bun.write(process.argv[2], (await Bun.file(process.argv[1]).json()).body)' "${vars.work_dir}/2-script/content.json" "${vars.work_dir}/2-script/voicescript.yaml"
58
+
59
+ - id: review
60
+ description: "Operator reviews the generated news script before speech synthesis"
61
+ pause: true
62
+ onEnter:
63
+ - kind: hitl.confirm
64
+ options:
65
+ prompt: "Review daily AI news VoiceScript at ${vars.work_dir}/2-script/voicescript.yaml. Proceed to voice generation?"
66
+ var: __hitlAnswer
67
+
68
+ - id: wrap-docs
69
+ description: "Wrap VoiceScript into Doc[] input for voice-gen"
70
+ onEnter:
71
+ - kind: shell
72
+ options:
73
+ command: >-
74
+ bun -e 'const scriptUrl = new URL(process.argv[1], "file://" + process.cwd() + "/"); const scriptPath = decodeURIComponent(scriptUrl.pathname); const doc = { id: new Bun.CryptoHasher("sha256").update(scriptUrl.href).digest("hex").slice(0, 16), title: "Daily AI News VoiceScript", sourceUri: scriptUrl.href, body: await Bun.file(scriptPath).text(), mediaType: "application/yaml", metadata: { voiceProfile: process.argv[3] || process.env.VOICEBOX_DEFAULT_PROFILE || "Robin" } }; await Bun.write(process.argv[2], JSON.stringify([doc], null, 2))' "${vars.work_dir}/2-script/voicescript.yaml" "${vars.work_dir}/3-audio/docs.json" "${vars.voice_profile}"
75
+
76
+ - id: generate
77
+ description: "Invoke voice-gen generator plugin to generate audio"
78
+ onEnter:
79
+ - kind: shell
80
+ options:
81
+ command: >-
82
+ set --;
83
+ if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
84
+ VOICE_GEN_MP3="${vars.transcode_mp3}" kk executor run voice-gen --in "${vars.work_dir}/3-audio/docs.json" --out "${vars.work_dir}/3-audio/content.json" "$@"
85
+
86
+ - id: done
87
+ description: "Terminal — Daily AI news voice broadcast generated successfully"
88
+ - id: failed
89
+ description: "Terminal — Pipeline failed"
90
+
91
+ transitions:
92
+ - from: prepare
93
+ to: fetch
94
+ description: "Workspace prepared -> fetch news items"
95
+ guard:
96
+ kind: always
97
+
98
+ - from: fetch
99
+ to: script
100
+ description: "News ingested -> build broadcast script"
101
+ guard:
102
+ kind: always
103
+
104
+ - from: script
105
+ to: wrap-docs
106
+ description: "skip_review enabled -> bypass HITL review"
107
+ guard:
108
+ kind: shell
109
+ options:
110
+ command: 'test "${vars.skip_review}" = "true"'
111
+
112
+ - from: script
113
+ to: review
114
+ description: "skip_review disabled -> pause for operator review"
115
+ guard:
116
+ kind: always
117
+
118
+ - from: review
119
+ to: wrap-docs
120
+ description: "VoiceScript approved -> wrap for audio generation"
121
+ guard:
122
+ kind: shell
123
+ options:
124
+ command: 'test "${vars.__hitlAnswer}" = "yes"'
125
+
126
+ - from: review
127
+ to: failed
128
+ description: "VoiceScript rejected -> abort"
129
+ guard:
130
+ kind: shell
131
+ options:
132
+ command: 'test "${vars.__hitlAnswer}" = "no"'
133
+
134
+ - from: wrap-docs
135
+ to: generate
136
+ description: "Doc[] wrapped -> synthesize audio"
137
+ guard:
138
+ kind: always
139
+
140
+ - from: generate
141
+ to: done
142
+ description: "Audio generated successfully -> complete"
143
+ guard:
144
+ kind: always
@@ -0,0 +1,72 @@
1
+ # Linear state-machine workflow template for knowledge-kit plugin platform:
2
+ # ingest -> generate -> publish -> done.
3
+ "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
4
+ name: kk-ingest-generate-publish
5
+ kind: state-machine
6
+ description: Chained ingestion, generator, and publish plugins via kk executor run
7
+ initialState: ingest
8
+ terminalStates:
9
+ - done
10
+ - failed
11
+ vars:
12
+ ingestion_plugin: "karakeep-local"
13
+ generator_plugin: "content-gen"
14
+ publish_plugin: "surfdash-pub"
15
+ source: "./source"
16
+ work_dir: "./work"
17
+ plugins_path: "./plugins"
18
+
19
+ states:
20
+ - id: ingest
21
+ description: Run ingestion plugin to produce document list (docs.json)
22
+ onEnter:
23
+ - kind: shell
24
+ options:
25
+ command: kk executor run ${vars.ingestion_plugin} --in ${vars.source} --out ${vars.work_dir}/docs.json --plugins-path ${vars.plugins_path}
26
+
27
+ - id: generate
28
+ description: Run content generator plugin to produce markdown content (content.json)
29
+ onEnter:
30
+ - kind: shell
31
+ options:
32
+ command: kk executor run ${vars.generator_plugin} --in ${vars.work_dir}/docs.json --out ${vars.work_dir}/content.json --plugins-path ${vars.plugins_path}
33
+
34
+ - id: publish
35
+ description: Run publish plugin to deploy content and receive result (result.json)
36
+ onEnter:
37
+ - kind: shell
38
+ options:
39
+ command: kk executor run ${vars.publish_plugin} --in ${vars.work_dir}/content.json --out ${vars.work_dir}/result.json --plugins-path ${vars.plugins_path}
40
+
41
+ - id: done
42
+ description: Terminal — all three plugin steps succeeded
43
+ - id: failed
44
+ description: Terminal — one of the plugin run steps failed
45
+
46
+ transitions:
47
+ - from: ingest
48
+ to: generate
49
+ description: Ingest succeeded — proceed to content generation
50
+ guard:
51
+ kind: action-ok
52
+ - from: ingest
53
+ to: failed
54
+ description: Ingest failed — terminate workflow
55
+
56
+ - from: generate
57
+ to: publish
58
+ description: Content generation succeeded — proceed to publish
59
+ guard:
60
+ kind: action-ok
61
+ - from: generate
62
+ to: failed
63
+ description: Generator failed — terminate workflow
64
+
65
+ - from: publish
66
+ to: done
67
+ description: Publish succeeded — workflow complete
68
+ guard:
69
+ kind: action-ok
70
+ - from: publish
71
+ to: failed
72
+ description: Publish failed — terminate workflow