@gobing-ai/knowledge-kit 0.0.8 → 0.0.10

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 (31) hide show
  1. package/dist/index.js +102 -50
  2. package/package.json +4 -4
  3. package/plugins/generations/dailynews-gen/package.json +17 -0
  4. package/plugins/generations/dailynews-gen/plugin.json +7 -0
  5. package/plugins/generations/dailynews-gen/src/index.ts +111 -0
  6. package/plugins/generations/dailynews-gen/src/script-builder.ts +430 -0
  7. package/plugins/generations/dailynews-gen/tsconfig.json +4 -0
  8. package/plugins/generations/voice-gen/src/concat.ts +9 -7
  9. package/plugins/generations/voice-gen/src/index.ts +65 -11
  10. package/plugins/generations/voice-gen/src/mp3.ts +65 -0
  11. package/plugins/generations/voice-gen/src/qc.ts +157 -0
  12. package/plugins/generations/voice-gen/src/voicebox-client.ts +40 -2
  13. package/plugins/generations/voice-gen/src/voicescript.ts +149 -12
  14. package/plugins/ingestions/aihot-ingest/package.json +17 -0
  15. package/plugins/ingestions/aihot-ingest/plugin.json +7 -0
  16. package/plugins/ingestions/aihot-ingest/src/client.ts +185 -0
  17. package/plugins/ingestions/aihot-ingest/src/index.ts +137 -0
  18. package/plugins/ingestions/aihot-ingest/src/mapper.ts +42 -0
  19. package/plugins/ingestions/aihot-ingest/tsconfig.json +4 -0
  20. package/plugins/ingestions/web-search/src/index.ts +2 -1
  21. package/plugins/kk/commands/workflow-run.md +70 -48
  22. package/plugins/kk/skills/audio-authoring/SKILL.md +185 -0
  23. package/plugins/kk/skills/audio-authoring/templates/voicescript.solo.yaml +25 -0
  24. package/plugins/kk/workflows/judge-gated-publish-example.yaml +6 -7
  25. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +158 -0
  26. package/plugins/kk/workflows/kk-ingest-generate-publish.yaml +13 -4
  27. package/plugins/kk/workflows/kk-itc.yaml +14 -18
  28. package/plugins/kk/workflows/kk-solo-podcast.yaml +135 -104
  29. package/plugins/kk/workflows/kk-storm-research.yaml +26 -5
  30. package/plugins/kk/workflows/wrap-voicescript-doc.ts +40 -0
  31. package/plugins/publishings/surfdash-pub/src/index.ts +1 -1
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: audio-authoring
3
+ description: >-
4
+ Use the audio-authoring skill to author spoken-audio scripts from a brief —
5
+ "create an audio script", "write a podcast script", "generate a briefing",
6
+ "outline options for audio", "author a VoiceScript", "revise the script".
7
+ Runs briefing → outline → script with explicit operator gates; solo mode
8
+ ships first, dialogue/interview/narration are named extension points.
9
+ ---
10
+
11
+ # audio-authoring — spoken-audio authoring (create | briefing | outline | script)
12
+
13
+ ## Purpose
14
+
15
+ Turn a Markdown brief into an **approved VoiceScript** for spoken audio in a small
16
+ file-backed workspace. One fat skill with four operations — `create`, `briefing`,
17
+ `outline`, `script` — sharing one artifact contract, one VoiceScript contract, one
18
+ spoken-budget rule, resume behavior, and recovery guidance. This is the authoring
19
+ layer extracted from `kk-solo-podcast`; synthesis (TTS) and deterministic formats
20
+ are deliberately **out of scope**.
21
+
22
+ ## When to use
23
+
24
+ - A user asks to author spoken-audio content from a brief or topic ("create a podcast
25
+ script about X", "write the briefing for a 10-minute episode").
26
+ - A workflow `agent.run` step needs one authoring stage independently (briefing only,
27
+ outline options only, script from an approved outline).
28
+ - An operator resumes a half-finished audio workspace and expects existing approved
29
+ artifacts to be reused, not overwritten.
30
+
31
+ Do **not** use this skill for: deterministic broadcast-news scripting (that is the
32
+ `kk:dailynews-gen` generator plugin), text-to-speech synthesis itself (that is
33
+ `kk:voice-gen` — this skill's output is its input), or multi-speaker authoring
34
+ beyond the extension-point notes below.
35
+
36
+ ## Invocation contract
37
+
38
+ `$ARGUMENTS` (or the Skill call args) parse as:
39
+
40
+ ```
41
+ [operation] [--mode solo] --dir <work_dir> [--duration <min>] [--language <code>] [--profile <voice>] [--outline a|b|c] [--revise <feedback>] [--force]
42
+ ```
43
+
44
+ | Token | Meaning | Default |
45
+ | --- | --- | --- |
46
+ | `operation` | `create` (default) \| `briefing` \| `outline` \| `script` | `create` |
47
+ | `--mode <name>` | authoring mode | `solo` (only implemented mode; others fail loud) |
48
+ | `--dir <work_dir>` | workspace directory | required — an empty dir fails loud |
49
+ | `--duration <min>` | target spoken duration in minutes | `8` |
50
+ | `--language <code>` | spoken language code (enum below) | `en` |
51
+ | `--profile <voice>` | voice profile for the wrap step — **not** written into the VoiceScript | none |
52
+ | `--outline <a\|b\|c>` | explicit outline selection | operator must select when options are shown |
53
+ | `--revise <feedback>` | revision feedback for the script review gate | none (approve path) |
54
+ | `--force` | explicit operator approval to replace an existing artifact | off — never overwrite without it |
55
+
56
+ Operations are case-insensitive. An unknown operation fails loud and lists the four
57
+ valid operations. Flags may appear in any order (topic convention).
58
+
59
+ ## Artifact contract
60
+
61
+ All paths are relative to the workspace `--dir`. Every operation uses these exact
62
+ paths — the contract is the same whether the operation runs inside `create` or
63
+ directly.
64
+
65
+ | Artifact | Path | Written by |
66
+ | --- | --- | --- |
67
+ | Brief (input) | `brief.md` | supplied before invocation — topic phrase or Markdown brief |
68
+ | Briefing | `1-briefing/briefing.md` | `briefing` — thesis, takeaways, audience, duration, tone |
69
+ | Outline options | `2-outline/outline-option-a.md`, `outline-option-b.md`, `outline-option-c.md` | `outline` — always two or three (a and b required; c optional) |
70
+ | Approved outline | `2-outline/outline-approved.md` | `outline` — after explicit operator selection |
71
+ | VoiceScript | `3-script/voicescript.yaml` | `script` — from the approved outline only |
72
+
73
+ Each operation creates the directory it writes into (`1-briefing/`, `2-outline/`,
74
+ `3-script/`) when absent. No other files are created — no metadata, no JSON
75
+ manifests, no templates, no placeholders.
76
+
77
+ ## Modes
78
+
79
+ - **solo** (default; only implemented mode) — one voice, no `speakers` map, no
80
+ per-segment `profile` (the `--profile` value lands in the Doc[] wrap performed by
81
+ the workflow, never in the VoiceScript).
82
+ - **dialogue** (extension point) — two or more named speakers via the `speakers` map
83
+ and per-segment `speaker`; turns alternate, distinct voices.
84
+ - **interview** (extension point) — host + guest via `speakers`; host drives
85
+ questions, guest answers at length.
86
+ - **narration** (extension point) — single voice over long-form material; like solo
87
+ but paces for continuous narration rather than episode structure.
88
+
89
+ Requesting an unimplemented mode fails loud naming the mode and stating that only
90
+ `solo` is implemented. No multi-speaker authoring guidance beyond these notes.
91
+
92
+ ## Operations
93
+
94
+ ### create (default)
95
+
96
+ Sequence: `briefing` → `outline` (selection gate) → `script` (review gate), resuming
97
+ from any existing approved artifact:
98
+
99
+ 1. **Resolve the brief** — require `brief.md` under `--dir`; run the `briefing`
100
+ semantics.
101
+ 2. **Outline** — if `2-outline/outline-approved.md` is missing, run the `outline`
102
+ semantics (write options, selection gate). If it exists, reuse it — do not
103
+ regenerate.
104
+ 3. **Script** — if `3-script/voicescript.yaml` is missing, run the `script` semantics
105
+ (write + review gate). If it exists and is approved, report it as final — do not
106
+ rewrite.
107
+ 4. **Report** the final artifact paths and stop. Never re-run a completed step; never
108
+ overwrite an existing approved artifact without `--force`.
109
+
110
+ ### briefing
111
+
112
+ 1. **Prerequisite** (fail loud): `brief.md` missing → stop with recovery ("supply a
113
+ brief at `<dir>/brief.md` first"). `1-briefing/briefing.md` exists without
114
+ `--force` → stop before replacing.
115
+ 2. **Read** `brief.md` and write `1-briefing/briefing.md`: core thesis, key listener
116
+ takeaways, audience profile, target spoken duration (`--duration` minutes), tone,
117
+ and what material from the brief to emphasize or cut.
118
+
119
+ ### outline
120
+
121
+ 1. **Prerequisite** (fail loud): `1-briefing/briefing.md` missing → stop with
122
+ recovery ("run `briefing` or `create` first").
123
+ 2. **Resume**: `2-outline/outline-approved.md` exists → reuse it; without `--force`,
124
+ stop before replacing.
125
+ 3. **Write options** — read the briefing and write two or three genuinely distinct
126
+ outline options to `2-outline/outline-option-{a,b,c}.md` (a and b required; c
127
+ optional). Each option targets the total spoken duration from `--duration`.
128
+ 4. **Selection gate**: present the options with their paths and **wait for explicit
129
+ operator selection** — `--outline <a|b|c>` or an interactive answer. Do not
130
+ proceed without it. Copy the selected option **verbatim** to
131
+ `2-outline/outline-approved.md`.
132
+
133
+ ### script
134
+
135
+ 1. **Prerequisite** (fail loud): `2-outline/outline-approved.md` missing → stop with
136
+ recovery ("run `outline` or `create` first — never script before outline
137
+ approval").
138
+ 2. **Resume**: `3-script/voicescript.yaml` exists → without `--force` or `--revise`,
139
+ stop before replacing ("script exists at `<path>`; to revise pass `--revise
140
+ <feedback>`, to replace pass `--force`").
141
+ 3. **Write** `3-script/voicescript.yaml` from the approved outline only, obeying the
142
+ VoiceScript contract and the spoken-budget rule below. Solo mode shape: one
143
+ speaker, **no** `speakers` map, per-segment `profile` omitted. Emit only valid
144
+ YAML — no markdown fences, no repo or docs paths in the file body.
145
+ 4. **Review gate**: present the script; offer **approve** or **revision**. Bounded
146
+ revision: at most **3 revision rounds**; each round takes `--revise <feedback>`,
147
+ rewrites the script from the approved outline plus the feedback, and re-presents
148
+ it. After 3 rounds, stop and require an explicit operator decision — never loop
149
+ past the bound.
150
+ 5. **Executable check**: when the `validate-voicescript.ts` checker is discoverable
151
+ (`KK_WORKFLOWS_DIR` or `$HOME/.config/kk/workflows/`), run it against the written
152
+ file; it is the sole executable authority — the embedded contract below is the
153
+ authoring-time distillation of it.
154
+
155
+ ## VoiceScript contract
156
+
157
+ The authoring template lives at **`templates/voicescript.solo.yaml`** (relative to
158
+ this skill file) — read it first when authoring `3-script/voicescript.yaml`; it is a
159
+ valid-YAML skeleton with per-field constraint comments. The executable authority is
160
+ `plugins/kk/workflows/validate-voicescript.ts` (canonical source
161
+ `plugins/generations/voice-gen/src/voicescript.ts`); the template is its
162
+ authoring-time distillation. No other fields are valid authoring targets — do not
163
+ invent fields (no `default_profile`, no required `title`).
164
+
165
+ Field summary (the template carries the same information as inline comments):
166
+
167
+ | Field | Where | Constraint |
168
+ | --- | --- | --- |
169
+ | `language` | root | 23-language enum (see template) |
170
+ | `default_engine` | root | engine enum (7 engines, see template) |
171
+ | `speakers` | root | multi-speaker modes only; each entry requires `profile`; omit in solo |
172
+ | `segments` | root | required, non-empty |
173
+ | `text` | segment | required, 1..50000 chars |
174
+ | `instruct` | speaker/segment | <= 500 chars |
175
+ | `gap_ms` | segment | >= 0 |
176
+ | `max_chunk_chars` | segment | 100..5000 |
177
+ | `crossfade_ms` | segment | 0..500 |
178
+ | `speaker` | segment | must name a key of the `speakers` map |
179
+
180
+ ## Spoken-budget rule
181
+
182
+ Target spoken volume derives from `--duration` and `--language`: for `zh`, `ja`, or
183
+ `ko`, target **characters = duration × 220**; for every other language, target
184
+ **words = duration × 150**. Land within **±20%** of the target. Apply the rule when
185
+ outlining (each option) and when scripting (total across segments).
@@ -0,0 +1,25 @@
1
+ # Solo-podcast VoiceScript starter template.
2
+ # Fill the placeholders; delete comments before authoring is considered final.
3
+ # Executable contract: validate-voicescript.ts (canonical: voice-gen/src/voicescript.ts).
4
+ # No other fields are valid — do not invent fields (no `default_profile`, no `title`).
5
+
6
+ language: en # zh|en|ja|ko|de|fr|ru|pt|es|it|he|ar|da|el|fi|hi|ms|nl|no|pl|sv|sw|tr
7
+ # default_engine: qwen # optional; qwen|qwen_custom_voice|luxtts|chatterbox|chatterbox_turbo|tada|kokoro
8
+
9
+ # Multi-speaker modes only (dialogue/interview) — omit entirely in solo mode:
10
+ # speakers:
11
+ # <name>:
12
+ # profile: <voice-profile> # required per speaker
13
+ # engine: qwen # optional
14
+ # language: en # optional
15
+ # instruct: warm # optional, <= 500 chars
16
+
17
+ segments: # required, non-empty
18
+ - text: "Replace with the spoken sentence." # required, 1..50000 characters
19
+ gap_ms: 400 # optional, >= 0 — silence before next clip
20
+ # instruct: "warm" # optional, <= 500 characters
21
+ # engine: qwen # optional, per-segment override
22
+ # language: en # optional, per-segment override
23
+ # max_chunk_chars: 800 # optional, 100..5000
24
+ # crossfade_ms: 50 # optional, 0..500
25
+ # speaker: <name> # multi-speaker only; must key the speakers map
@@ -25,22 +25,21 @@ states:
25
25
  # Seed the verdict so the judge gate guards can be evaluated in --dry-run
26
26
  # (dry-run skips actions but still evaluates shell guards). A real run's
27
27
  # judge step overwrites this file before the gate is read.
28
- command: "echo '{\"verdict\":\"PASS\",\"score\":0.0}' > ${vars.verdictFile} && echo 'generating content...'"
28
+ command: 'echo ''{"verdict":"PASS","score":0.0}'' > ${vars.verdictFile} && echo ''generating content...'''
29
29
  - id: judge
30
30
  description: "Evaluate generated content via the judge skill (agent.run)"
31
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
32
+ # `agent.run` is registered by the kk workflow host: it spawns an agent that invokes the
33
+ # kk:content-judge skill, evaluates the content against ${vars.rubric}, and writes the verdict
34
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.
35
+ # skipped — the seeded verdict above keeps the gate evaluable.
37
36
  - kind: agent.run
38
37
  options:
39
38
  role: reviewer
40
39
  input: |
40
+ Invoke the kk:content-judge skill.
41
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.
42
+ Write the verdict JSON to ${vars.verdictFile} per the skill's verdict contract.
44
43
  Print the verdict path as the last line of stdout.
45
44
  expectFile: ${vars.verdictFile}
46
45
  - id: publish
@@ -0,0 +1,158 @@
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: quality-control
87
+ description: "Verify audio quality, speech rate, and silence/repetition QC metrics"
88
+ onEnter:
89
+ - kind: shell
90
+ options:
91
+ command: >-
92
+ bun -e 'const content = JSON.parse(await Bun.file(process.argv[1]).text()); const qc = content.metadata?.qc; console.log("=== Voice Quality Control Report ==="); console.log("Overall Score: " + (qc ? qc.overallScore : 100) + "/100 | Passed: " + (qc ? qc.passed : true)); if (qc && qc.criticalIssues && qc.criticalIssues.length) { console.warn("QC Issues:\n" + qc.criticalIssues.map(function(i) { return " - " + i; }).join("\n")); } else { console.log("All audio segments passed quality control checks cleanly."); }' "${vars.work_dir}/3-audio/content.json"
93
+
94
+ - id: done
95
+ description: "Terminal — Daily AI news voice broadcast generated successfully"
96
+ - id: failed
97
+ description: "Terminal — Pipeline failed"
98
+
99
+ transitions:
100
+ - from: prepare
101
+ to: fetch
102
+ description: "Workspace prepared -> fetch news items"
103
+ guard:
104
+ kind: always
105
+
106
+ - from: fetch
107
+ to: script
108
+ description: "News ingested -> build broadcast script"
109
+ guard:
110
+ kind: always
111
+
112
+ - from: script
113
+ to: wrap-docs
114
+ description: "skip_review enabled -> bypass HITL review"
115
+ guard:
116
+ kind: shell
117
+ options:
118
+ command: 'test "${vars.skip_review}" = "true"'
119
+
120
+ - from: script
121
+ to: review
122
+ description: "skip_review disabled -> pause for operator review"
123
+ guard:
124
+ kind: always
125
+
126
+ - from: review
127
+ to: wrap-docs
128
+ description: "VoiceScript approved -> wrap for audio generation"
129
+ guard:
130
+ kind: shell
131
+ options:
132
+ command: 'test "${vars.__hitlAnswer}" = "yes"'
133
+
134
+ - from: review
135
+ to: failed
136
+ description: "VoiceScript rejected -> abort"
137
+ guard:
138
+ kind: shell
139
+ options:
140
+ command: 'test "${vars.__hitlAnswer}" = "no"'
141
+
142
+ - from: wrap-docs
143
+ to: generate
144
+ description: "Doc[] wrapped -> synthesize audio"
145
+ guard:
146
+ kind: always
147
+
148
+ - from: generate
149
+ to: quality-control
150
+ description: "Audio generated -> run quality control validation"
151
+ guard:
152
+ kind: always
153
+
154
+ - from: quality-control
155
+ to: done
156
+ description: "Quality control checks complete -> done"
157
+ guard:
158
+ kind: always
@@ -14,7 +14,7 @@ vars:
14
14
  publish_plugin: "surfdash-pub"
15
15
  source: "./source"
16
16
  work_dir: "./work"
17
- plugins_path: "./plugins"
17
+ plugins_path: "" # empty -> ADR-012 default discovery (package-shipped built-ins); set for checkout dev
18
18
 
19
19
  states:
20
20
  - id: ingest
@@ -22,21 +22,30 @@ states:
22
22
  onEnter:
23
23
  - kind: shell
24
24
  options:
25
- command: kk executor run ${vars.ingestion_plugin} --in ${vars.source} --out ${vars.work_dir}/docs.json --plugins-path ${vars.plugins_path}
25
+ command: >-
26
+ set --;
27
+ if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
28
+ kk executor run ${vars.ingestion_plugin} --in ${vars.source} --out ${vars.work_dir}/docs.json "$@"
26
29
 
27
30
  - id: generate
28
31
  description: Run content generator plugin to produce markdown content (content.json)
29
32
  onEnter:
30
33
  - kind: shell
31
34
  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}
35
+ command: >-
36
+ set --;
37
+ if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
38
+ kk executor run ${vars.generator_plugin} --in ${vars.work_dir}/docs.json --out ${vars.work_dir}/content.json "$@"
33
39
 
34
40
  - id: publish
35
41
  description: Run publish plugin to deploy content and receive result (result.json)
36
42
  onEnter:
37
43
  - kind: shell
38
44
  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}
45
+ command: >-
46
+ set --;
47
+ if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
48
+ kk executor run ${vars.publish_plugin} --in ${vars.work_dir}/content.json --out ${vars.work_dir}/result.json "$@"
40
49
 
41
50
  - id: done
42
51
  description: Terminal — all three plugin steps succeeded
@@ -22,9 +22,8 @@ vars:
22
22
  outline: ""
23
23
  force: "false"
24
24
  rubric: "tech-accuracy"
25
- verdictFile: ".spur/run/${vars.__runId}-itc-verdict.json"
25
+ verdictFile: "" # empty -> ${vars.work_dir}/.itc-verdict.json (set by prepare)
26
26
  agent: ""
27
- __runId: "itc"
28
27
  __hitlAnswer: ""
29
28
  __hitlInput: ""
30
29
 
@@ -40,6 +39,7 @@ states:
40
39
  echo "missing work_dir" >&2; exit 1;
41
40
  fi;
42
41
  mkdir -p "${vars.work_dir}" "${vars.work_dir}/2-outline" "${vars.work_dir}/3-draft";
42
+ echo 0 > "${vars.work_dir}/.revise-count";
43
43
  if [ -n "${vars.topic}" ] && [ -n "${vars.input_file}" ]; then
44
44
  echo "XOR violation: both topic and input_file set" >&2; exit 1;
45
45
  fi;
@@ -66,7 +66,7 @@ states:
66
66
  role: scribe
67
67
  agent: ${vars.agent}
68
68
  input: |
69
- Load plugins/kk/skills/storm-research/SKILL.md.
69
+ Invoke the kk:storm-research skill.
70
70
  Run storm-research on the topic or input file specified in the brief at ${vars.work_dir}/brief.md.
71
71
 
72
72
  - id: outline-write
@@ -77,7 +77,7 @@ states:
77
77
  role: scribe
78
78
  agent: ${vars.agent}
79
79
  input: |
80
- Load plugins/kk/skills/${vars.writer}/SKILL.md.
80
+ Invoke the kk:${vars.writer} skill.
81
81
  Generate outline options (outline-option-a.md, outline-option-b.md, and optionally outline-option-c.md) in ${vars.work_dir}/2-outline/ from the brief at ${vars.work_dir}/brief.md using playbook ${vars.playbook}.
82
82
  Do not wait for operator selection; write the option files.
83
83
  expectFile: ${vars.work_dir}/2-outline/outline-option-a.md
@@ -122,7 +122,7 @@ states:
122
122
  role: scribe
123
123
  agent: ${vars.agent}
124
124
  input: |
125
- Load plugins/kk/skills/${vars.writer}/SKILL.md.
125
+ Invoke the kk:${vars.writer} skill.
126
126
  Author the technical draft at ${vars.work_dir}/3-draft/draft-article.md based on approved outline ${vars.work_dir}/2-outline/outline-approved.md and brief ${vars.work_dir}/brief.md using playbook ${vars.playbook}.
127
127
  Revision feedback: ${vars.__hitlInput}
128
128
  expectFile: ${vars.work_dir}/3-draft/draft-article.md
@@ -143,12 +143,8 @@ states:
143
143
  - kind: shell
144
144
  options:
145
145
  command: >-
146
- mkdir -p .spur/run;
147
- run_id="${vars.__runId}";
148
- if [ -z "$run_id" ]; then run_id="itc"; fi;
149
- count_file=".spur/run/$run_id-itc-revise-count";
150
- curr=$(cat "$count_file" 2>/dev/null || echo 0);
151
- echo $((curr + 1)) > "$count_file";
146
+ n=$(cat "${vars.work_dir}/.revise-count" 2>/dev/null || echo 0);
147
+ echo $((n + 1)) > "${vars.work_dir}/.revise-count";
152
148
  - kind: hitl.input
153
149
  options:
154
150
  prompt: "Provide specific revision feedback for ${vars.work_dir}/3-draft/draft-article.md:"
@@ -162,11 +158,11 @@ states:
162
158
  role: reviewer
163
159
  agent: ${vars.agent}
164
160
  input: |
165
- Load plugins/kk/skills/content-judge/SKILL.md and its references/rubrics.md.
161
+ Invoke the kk:content-judge skill.
166
162
  Evaluate the draft at ${vars.work_dir}/3-draft/draft-article.md against the ${vars.rubric} rubric.
167
- Write the verdict JSON to ${vars.verdictFile} per the contract in SKILL.md.
163
+ Write the verdict JSON to ${vars.work_dir}/.itc-verdict.json per the skill's verdict contract.
168
164
  Print the verdict path as the last line of stdout.
169
- expectFile: ${vars.verdictFile}
165
+ expectFile: ${vars.work_dir}/.itc-verdict.json
170
166
 
171
167
  - id: done
172
168
  description: "Terminal — IT content draft generated and approved successfully"
@@ -252,7 +248,7 @@ transitions:
252
248
  guard:
253
249
  kind: shell
254
250
  options:
255
- command: 'test "${vars.__hitlAnswer}" = "no" && { run_id="${vars.__runId}"; if [ -z "$run_id" ]; then run_id="itc"; fi; test "$(cat .spur/run/$run_id-itc-revise-count 2>/dev/null || echo 0)" -lt 3; }'
251
+ command: 'test "${vars.__hitlAnswer}" = "no" && test "$(cat "${vars.work_dir}/.revise-count" 2>/dev/null || echo 0)" -lt 3'
256
252
 
257
253
  - from: draft-review
258
254
  to: failed
@@ -260,7 +256,7 @@ transitions:
260
256
  guard:
261
257
  kind: shell
262
258
  options:
263
- command: 'test "${vars.__hitlAnswer}" = "no" && { run_id="${vars.__runId}"; if [ -z "$run_id" ]; then run_id="itc"; fi; test "$(cat .spur/run/$run_id-itc-revise-count 2>/dev/null || echo 0)" -ge 3; }'
259
+ command: 'test "${vars.__hitlAnswer}" = "no" && test "$(cat "${vars.work_dir}/.revise-count" 2>/dev/null || echo 0)" -ge 3'
264
260
 
265
261
  - from: draft-revise
266
262
  to: draft-write
@@ -274,7 +270,7 @@ transitions:
274
270
  guard:
275
271
  kind: shell
276
272
  options:
277
- command: 'v="$(jq -r .verdict ${vars.verdictFile} 2>/dev/null)"; test -z "$v" -o "$v" = PASS'
273
+ command: 'v="$(jq -r .verdict ${vars.work_dir}/.itc-verdict.json 2>/dev/null)"; test -z "$v" -o "$v" = PASS'
278
274
 
279
275
  - from: judge
280
276
  to: failed
@@ -282,4 +278,4 @@ transitions:
282
278
  guard:
283
279
  kind: shell
284
280
  options:
285
- command: 'v="$(jq -r .verdict ${vars.verdictFile} 2>/dev/null)"; test -n "$v" -a "$v" != PASS'
281
+ command: 'v="$(jq -r .verdict ${vars.work_dir}/.itc-verdict.json 2>/dev/null)"; test -n "$v" -a "$v" != PASS'