@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,285 @@
1
+ ---
2
+ "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
3
+ name: kk-itc
4
+ kind: state-machine
5
+ description: >-
6
+ IT content authoring pipeline using topic/itc-generating, Spur HITL gates, and optional judging
7
+ initialState: prepare
8
+ terminalStates:
9
+ - done
10
+ - failed
11
+ failureStates:
12
+ - failed
13
+
14
+ vars:
15
+ topic: ""
16
+ input_file: ""
17
+ work_dir: ""
18
+ writer: "itc-generating"
19
+ playbook: "generic"
20
+ research: "false"
21
+ judge: "false"
22
+ outline: ""
23
+ force: "false"
24
+ rubric: "tech-accuracy"
25
+ verdictFile: ".spur/run/${vars.__runId}-itc-verdict.json"
26
+ agent: ""
27
+ __runId: "itc"
28
+ __hitlAnswer: ""
29
+ __hitlInput: ""
30
+
31
+ states:
32
+ - id: prepare
33
+ description: "mkdir work_dir, 2-outline, 3-draft; validate XOR topic/input_file; write brief.md"
34
+ onEnter:
35
+ - kind: shell
36
+ options:
37
+ command: >-
38
+ set -e;
39
+ if [ -z "${vars.work_dir}" ]; then
40
+ echo "missing work_dir" >&2; exit 1;
41
+ fi;
42
+ mkdir -p "${vars.work_dir}" "${vars.work_dir}/2-outline" "${vars.work_dir}/3-draft";
43
+ if [ -n "${vars.topic}" ] && [ -n "${vars.input_file}" ]; then
44
+ echo "XOR violation: both topic and input_file set" >&2; exit 1;
45
+ fi;
46
+ if [ -z "${vars.topic}" ] && [ -z "${vars.input_file}" ]; then
47
+ echo "XOR violation: neither topic nor input_file set" >&2; exit 1;
48
+ fi;
49
+ if [ -f "${vars.work_dir}/brief.md" ] && [ "${vars.force}" != "true" ]; then
50
+ echo "initialization target already exists at ${vars.work_dir}/brief.md; pass force=true to replace" >&2; exit 1;
51
+ fi;
52
+ if [ -n "${vars.topic}" ]; then
53
+ printf '%s\n' "${vars.topic}" > "${vars.work_dir}/brief.md";
54
+ else
55
+ if [ ! -f "${vars.input_file}" ]; then
56
+ echo "input file not found: ${vars.input_file}" >&2; exit 1;
57
+ fi;
58
+ cp "${vars.input_file}" "${vars.work_dir}/brief.md";
59
+ fi;
60
+
61
+ - id: research
62
+ description: "Conduct preliminary topic research via storm-research (agent.run)"
63
+ onEnter:
64
+ - kind: agent.run
65
+ options:
66
+ role: scribe
67
+ agent: ${vars.agent}
68
+ input: |
69
+ Load plugins/kk/skills/storm-research/SKILL.md.
70
+ Run storm-research on the topic or input file specified in the brief at ${vars.work_dir}/brief.md.
71
+
72
+ - id: outline-write
73
+ description: "Generate outline options a, b (and optional c) via writer skill (agent.run)"
74
+ onEnter:
75
+ - kind: agent.run
76
+ options:
77
+ role: scribe
78
+ agent: ${vars.agent}
79
+ input: |
80
+ Load plugins/kk/skills/${vars.writer}/SKILL.md.
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
+ Do not wait for operator selection; write the option files.
83
+ expectFile: ${vars.work_dir}/2-outline/outline-option-a.md
84
+ - kind: shell
85
+ options:
86
+ command: >-
87
+ test -f "${vars.work_dir}/2-outline/outline-option-b.md" || { echo "missing required outline-option-b.md" >&2; exit 1; }
88
+
89
+ - id: outline-select
90
+ description: "Operator selects preferred outline option"
91
+ pause: true
92
+ onEnter:
93
+ - kind: hitl.select
94
+ options:
95
+ prompt: "Select an approved outline option from ${vars.work_dir}/2-outline/ (outline-option-a.md, outline-option-b.md, or outline-option-c.md):"
96
+ options:
97
+ - a
98
+ - b
99
+ - c
100
+ var: __hitlAnswer
101
+
102
+ - id: outline-commit
103
+ description: "Copy selected outline to outline-approved.md"
104
+ onEnter:
105
+ - kind: shell
106
+ options:
107
+ command: >-
108
+ sel="${vars.outline}";
109
+ if [ -z "$sel" ]; then sel="${vars.__hitlAnswer}"; fi;
110
+ if [ -z "$sel" ]; then sel="a"; fi;
111
+ src="${vars.work_dir}/2-outline/outline-option-$sel.md";
112
+ if [ ! -f "$src" ]; then
113
+ echo "selected outline file not found: $src" >&2; exit 1;
114
+ fi;
115
+ cp "$src" "${vars.work_dir}/2-outline/outline-approved.md";
116
+
117
+ - id: draft-write
118
+ description: "Author full technical draft from approved outline (agent.run)"
119
+ onEnter:
120
+ - kind: agent.run
121
+ options:
122
+ role: scribe
123
+ agent: ${vars.agent}
124
+ input: |
125
+ Load plugins/kk/skills/${vars.writer}/SKILL.md.
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
+ Revision feedback: ${vars.__hitlInput}
128
+ expectFile: ${vars.work_dir}/3-draft/draft-article.md
129
+
130
+ - id: draft-review
131
+ description: "Operator reviews and approves or requests revision on draft-article.md"
132
+ pause: true
133
+ onEnter:
134
+ - kind: hitl.confirm
135
+ options:
136
+ prompt: "Review draft at ${vars.work_dir}/3-draft/draft-article.md. Approve?"
137
+ var: __hitlAnswer
138
+
139
+ - id: draft-revise
140
+ description: "Collect revision feedback from operator (bounded by 3 rounds)"
141
+ pause: true
142
+ onEnter:
143
+ - kind: shell
144
+ options:
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";
152
+ - kind: hitl.input
153
+ options:
154
+ prompt: "Provide specific revision feedback for ${vars.work_dir}/3-draft/draft-article.md:"
155
+ var: __hitlInput
156
+
157
+ - id: judge
158
+ description: "Evaluate technical draft with content-judge (agent.run)"
159
+ onEnter:
160
+ - kind: agent.run
161
+ options:
162
+ role: reviewer
163
+ agent: ${vars.agent}
164
+ input: |
165
+ Load plugins/kk/skills/content-judge/SKILL.md and its references/rubrics.md.
166
+ 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.
168
+ Print the verdict path as the last line of stdout.
169
+ expectFile: ${vars.verdictFile}
170
+
171
+ - id: done
172
+ description: "Terminal — IT content draft generated and approved successfully"
173
+ - id: failed
174
+ description: "Terminal — IT content workflow failed or rejected"
175
+
176
+ transitions:
177
+ - from: prepare
178
+ to: research
179
+ description: "Research enabled -> proceed to storm-research"
180
+ guard:
181
+ kind: shell
182
+ options:
183
+ command: 'test "${vars.research}" = "true"'
184
+
185
+ - from: prepare
186
+ to: outline-write
187
+ description: "Research disabled -> proceed directly to outline generation"
188
+ guard:
189
+ kind: shell
190
+ options:
191
+ command: 'test "${vars.research}" != "true"'
192
+
193
+ - from: research
194
+ to: outline-write
195
+ description: "Research completed -> proceed to outline generation"
196
+ guard:
197
+ kind: always
198
+
199
+ - from: outline-write
200
+ to: outline-select
201
+ description: "Outline not pre-selected -> pause for operator selection"
202
+ guard:
203
+ kind: shell
204
+ options:
205
+ command: 'test -z "${vars.outline}"'
206
+
207
+ - from: outline-write
208
+ to: outline-commit
209
+ description: "Outline pre-selected -> skip interactive selection"
210
+ guard:
211
+ kind: shell
212
+ options:
213
+ command: 'test -n "${vars.outline}"'
214
+
215
+ - from: outline-select
216
+ to: outline-commit
217
+ description: "Outline selected -> commit approved outline"
218
+ guard:
219
+ kind: always
220
+
221
+ - from: outline-commit
222
+ to: draft-write
223
+ description: "Approved outline committed -> write draft"
224
+ guard:
225
+ kind: always
226
+
227
+ - from: draft-write
228
+ to: draft-review
229
+ description: "Draft written -> pause for operator review"
230
+ guard:
231
+ kind: always
232
+
233
+ - from: draft-review
234
+ to: judge
235
+ description: "Draft approved and judge enabled -> evaluate quality"
236
+ guard:
237
+ kind: shell
238
+ options:
239
+ command: '{ test -z "${vars.__hitlAnswer}" || test "${vars.__hitlAnswer}" = "yes"; } && test "${vars.judge}" = "true"'
240
+
241
+ - from: draft-review
242
+ to: done
243
+ description: "Draft approved and judge disabled -> complete workflow"
244
+ guard:
245
+ kind: shell
246
+ options:
247
+ command: '{ test -z "${vars.__hitlAnswer}" || test "${vars.__hitlAnswer}" = "yes"; } && test "${vars.judge}" != "true"'
248
+
249
+ - from: draft-review
250
+ to: draft-revise
251
+ description: "Draft rejected with revision budget remaining -> collect feedback"
252
+ guard:
253
+ kind: shell
254
+ 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; }'
256
+
257
+ - from: draft-review
258
+ to: failed
259
+ description: "Draft rejected with revision budget exhausted -> fail"
260
+ guard:
261
+ kind: shell
262
+ 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; }'
264
+
265
+ - from: draft-revise
266
+ to: draft-write
267
+ description: "Feedback collected -> rewrite draft"
268
+ guard:
269
+ kind: always
270
+
271
+ - from: judge
272
+ to: done
273
+ description: "Judge verdict is PASS (or empty in dry-run) -> complete workflow"
274
+ guard:
275
+ kind: shell
276
+ options:
277
+ command: 'v="$(jq -r .verdict ${vars.verdictFile} 2>/dev/null)"; test -z "$v" -o "$v" = PASS'
278
+
279
+ - from: judge
280
+ to: failed
281
+ description: "Judge verdict is not PASS -> fail"
282
+ guard:
283
+ kind: shell
284
+ options:
285
+ command: 'v="$(jq -r .verdict ${vars.verdictFile} 2>/dev/null)"; test -n "$v" -a "$v" != PASS'
@@ -0,0 +1,374 @@
1
+ ---
2
+ "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
3
+ name: kk-solo-podcast
4
+ kind: state-machine
5
+ description: >-
6
+ Solo podcast authoring pipeline using VoiceScript authoring, Spur HITL gates, and voice-gen audio generation
7
+ initialState: prepare
8
+ terminalStates:
9
+ - done
10
+ - failed
11
+ failureStates:
12
+ - failed
13
+
14
+ vars:
15
+ topic: ""
16
+ input_file: ""
17
+ work_dir: ""
18
+ force: "false"
19
+ outline: ""
20
+ script_approved: ""
21
+ target_duration_min: "8"
22
+ words_per_min: "150"
23
+ cjk_chars_per_min: "220"
24
+ voice_profile: ""
25
+ language: "en"
26
+ plugins_path: ""
27
+ validate_script: ""
28
+ agent: ""
29
+ __runId: "solo"
30
+ __hitlAnswer: ""
31
+ __hitlInput: ""
32
+
33
+ states:
34
+ - id: prepare
35
+ description: "mkdir work_dir and stage subdirectories; validate XOR topic/input_file and voice_profile; write brief.md"
36
+ onEnter:
37
+ - kind: shell
38
+ options:
39
+ command: >-
40
+ set -e;
41
+ if [ -z "${vars.work_dir}" ]; then
42
+ echo "missing work_dir" >&2; exit 1;
43
+ fi;
44
+ mkdir -p "${vars.work_dir}" "${vars.work_dir}/1-briefing" "${vars.work_dir}/2-outline" "${vars.work_dir}/3-script" "${vars.work_dir}/4-audio";
45
+ if [ -n "${vars.topic}" ] && [ -n "${vars.input_file}" ]; then
46
+ echo "XOR violation: both topic and input_file set" >&2; exit 1;
47
+ fi;
48
+ if [ -z "${vars.topic}" ] && [ -z "${vars.input_file}" ]; then
49
+ echo "XOR violation: neither topic nor input_file set" >&2; exit 1;
50
+ fi;
51
+ if [ -z "${vars.voice_profile}" ] && [ -z "$VOICEBOX_DEFAULT_PROFILE" ]; then
52
+ echo "missing voice_profile (set vars.voice_profile or VOICEBOX_DEFAULT_PROFILE)" >&2; exit 1;
53
+ fi;
54
+ if [ -f "${vars.work_dir}/brief.md" ] && [ "${vars.force}" != "true" ]; then
55
+ echo "initialization target already exists at ${vars.work_dir}/brief.md; pass force=true to replace" >&2; exit 1;
56
+ fi;
57
+ if [ -n "${vars.topic}" ]; then
58
+ printf '%s\n' "${vars.topic}" > "${vars.work_dir}/brief.md";
59
+ else
60
+ if [ ! -f "${vars.input_file}" ]; then
61
+ echo "input file not found: ${vars.input_file}" >&2; exit 1;
62
+ fi;
63
+ cp "${vars.input_file}" "${vars.work_dir}/brief.md";
64
+ fi;
65
+
66
+ - id: understand
67
+ description: "Analyze brief and generate podcast briefing (agent.run)"
68
+ onEnter:
69
+ - kind: agent.run
70
+ options:
71
+ role: scribe
72
+ agent: ${vars.agent}
73
+ input: |
74
+ Read the source brief at ${vars.work_dir}/brief.md.
75
+ Write a concise podcast briefing at ${vars.work_dir}/1-briefing/briefing.md.
76
+ Identify core thesis, key listener takeaways, audience profile, target spoken duration (${vars.target_duration_min} minutes), tone, and what material from the brief to emphasize or cut.
77
+ expectFile: ${vars.work_dir}/1-briefing/briefing.md
78
+
79
+ - id: outline-write
80
+ description: "Generate spoken podcast outline options (agent.run)"
81
+ onEnter:
82
+ - kind: agent.run
83
+ options:
84
+ role: scribe
85
+ agent: ${vars.agent}
86
+ input: |
87
+ Read the briefing at ${vars.work_dir}/1-briefing/briefing.md and brief at ${vars.work_dir}/brief.md.
88
+ Generate spoken podcast outline options in ${vars.work_dir}/2-outline/:
89
+ - outline-option-a.md (e.g. narrative / storytelling arc)
90
+ - outline-option-b.md (e.g. analytical / explainer breakdown)
91
+ - optional outline-option-c.md
92
+ Target total episode spoken duration is ${vars.target_duration_min} minutes.
93
+ Do not wait for operator selection; write the option files.
94
+ expectFile: ${vars.work_dir}/2-outline/outline-option-a.md
95
+ - kind: shell
96
+ options:
97
+ command: >-
98
+ test -f "${vars.work_dir}/2-outline/outline-option-b.md" || { echo "missing required outline-option-b.md" >&2; exit 1; }
99
+
100
+ - id: outline-select
101
+ description: "Operator selects preferred outline option"
102
+ pause: true
103
+ onEnter:
104
+ - kind: hitl.select
105
+ options:
106
+ prompt: "Select an approved outline option from ${vars.work_dir}/2-outline/ (outline-option-a.md, outline-option-b.md, or outline-option-c.md):"
107
+ options:
108
+ - a
109
+ - b
110
+ - c
111
+ var: __hitlAnswer
112
+
113
+ - id: outline-commit
114
+ description: "Copy selected outline to outline-approved.md"
115
+ onEnter:
116
+ - kind: shell
117
+ options:
118
+ command: >-
119
+ sel="${vars.outline}";
120
+ if [ -z "$sel" ]; then sel="${vars.__hitlAnswer}"; fi;
121
+ if [ -z "$sel" ]; then sel="a"; fi;
122
+ src="${vars.work_dir}/2-outline/outline-option-$sel.md";
123
+ if [ ! -f "$src" ]; then
124
+ echo "selected outline file not found: $src" >&2; exit 1;
125
+ fi;
126
+ cp "$src" "${vars.work_dir}/2-outline/outline-approved.md";
127
+
128
+ - id: script-write
129
+ description: "Author complete VoiceScript YAML from approved outline (agent.run)"
130
+ onEnter:
131
+ - kind: agent.run
132
+ options:
133
+ role: scribe
134
+ agent: ${vars.agent}
135
+ input: |
136
+ Read the approved outline at ${vars.work_dir}/2-outline/outline-approved.md and briefing at ${vars.work_dir}/1-briefing/briefing.md.
137
+ Target duration is ${vars.target_duration_min} minutes.
138
+ Spoken budget: if language (${vars.language}) is zh, ja, or ko, target characters = ${vars.target_duration_min} * ${vars.cjk_chars_per_min}; otherwise target words = ${vars.target_duration_min} * ${vars.words_per_min}. Land inside ±20%, spoken prose, one speaker.
139
+ Author the complete solo-podcast VoiceScript YAML at ${vars.work_dir}/3-script/voicescript.yaml.
140
+ Emit ONLY valid YAML (no markdown fences, no repo/docs paths). Contract:
141
+ title: string
142
+ language: "${vars.language}"
143
+ default_profile: "${vars.voice_profile}"
144
+ default_engine: qwen|qwen_custom_voice|luxtts|chatterbox|chatterbox_turbo|tada|kokoro # optional
145
+ segments:
146
+ - text: "spoken sentence" # required, 1..50000 characters
147
+ gap_ms: 400 # optional, >= 0, silence before next clip
148
+ instruct: "warm" # optional, <= 500 characters
149
+ Do not invent fields. Revision feedback: ${vars.__hitlInput}
150
+ expectFile: ${vars.work_dir}/3-script/voicescript.yaml
151
+
152
+ - id: script-validate
153
+ description: "Validate authored VoiceScript YAML structure and constraints"
154
+ onEnter:
155
+ - kind: shell
156
+ options:
157
+ command: >-
158
+ set -e;
159
+ vs="${vars.validate_script}";
160
+ if [ -z "$vs" ]; then
161
+ if [ -n "$KK_WORKFLOWS_DIR" ]; then vs="$KK_WORKFLOWS_DIR/validate-voicescript.ts";
162
+ else vs="$HOME/.config/kk/workflows/validate-voicescript.ts"; fi;
163
+ fi;
164
+ if [ ! -f "$vs" ]; then
165
+ echo "validate_script not found: $vs (install with /workflow-run kk-solo-podcast into $HOME/.config/kk/workflows)" >&2;
166
+ exit 1;
167
+ fi;
168
+ bun "$vs" --in "${vars.work_dir}/3-script/voicescript.yaml"
169
+
170
+ - id: script-review
171
+ description: "Operator reviews and approves VoiceScript before live TTS generation"
172
+ pause: true
173
+ onEnter:
174
+ - kind: hitl.confirm
175
+ options:
176
+ prompt: "Review VoiceScript at ${vars.work_dir}/3-script/voicescript.yaml. Approve for voice generation?"
177
+ var: __hitlAnswer
178
+
179
+ - id: script-revise
180
+ description: "Collect revision feedback from operator (bounded by 3 rounds)"
181
+ pause: true
182
+ onEnter:
183
+ - kind: shell
184
+ options:
185
+ command: >-
186
+ mkdir -p .spur/run;
187
+ run_id="${vars.__runId}";
188
+ if [ -z "$run_id" ]; then run_id="solo"; fi;
189
+ count_file=".spur/run/$run_id-solo-revise-count";
190
+ curr=$(cat "$count_file" 2>/dev/null || echo 0);
191
+ echo $((curr + 1)) > "$count_file";
192
+ - kind: hitl.input
193
+ options:
194
+ prompt: "Provide specific revision feedback for ${vars.work_dir}/3-script/voicescript.yaml:"
195
+ var: __hitlInput
196
+
197
+ - id: wrap-docs
198
+ description: "Wrap VoiceScript into Doc[] JSON for voice-gen generator plugin"
199
+ onEnter:
200
+ - kind: shell
201
+ options:
202
+ command: >-
203
+ bun -e '
204
+ import { readFileSync, writeFileSync } from "node:fs";
205
+ import { createHash } from "node:crypto";
206
+ import { resolve } from "node:path";
207
+ const scriptPath = resolve(process.argv[1]);
208
+ const outPath = resolve(process.argv[2]);
209
+ const voiceProfile = process.argv[3] || process.env.VOICEBOX_DEFAULT_PROFILE || "";
210
+ const body = readFileSync(scriptPath, "utf-8");
211
+ const sourceUri = "file://" + scriptPath;
212
+ const id = createHash("sha256").update(sourceUri).digest("hex").slice(0, 16);
213
+ const doc = {
214
+ id,
215
+ title: "Podcast VoiceScript",
216
+ sourceUri,
217
+ body,
218
+ mediaType: "application/yaml",
219
+ metadata: voiceProfile ? { voiceProfile } : {},
220
+ };
221
+ writeFileSync(outPath, JSON.stringify([doc], null, 2));
222
+ ' "${vars.work_dir}/3-script/voicescript.yaml" "${vars.work_dir}/4-audio/docs.json" "${vars.voice_profile}"
223
+
224
+ - id: generate
225
+ description: "Invoke voice-gen generator plugin to generate audio"
226
+ onEnter:
227
+ - kind: shell
228
+ options:
229
+ command: >-
230
+ set -e;
231
+ extra="";
232
+ if [ -n "${vars.plugins_path}" ]; then extra="--plugins-path ${vars.plugins_path}"; fi;
233
+ kk executor run voice-gen --in "${vars.work_dir}/4-audio/docs.json" --out "${vars.work_dir}/4-audio/content.json" $extra
234
+
235
+ - id: duration-check
236
+ description: "Validate generated audio duration against target budget"
237
+
238
+ - id: done
239
+ description: "Terminal — Solo podcast VoiceScript and audio generated successfully"
240
+ - id: failed
241
+ description: "Terminal — Solo podcast workflow failed or rejected"
242
+
243
+ transitions:
244
+ - from: prepare
245
+ to: understand
246
+ description: "Prepare completed -> begin briefing analysis"
247
+ guard:
248
+ kind: always
249
+
250
+ - from: understand
251
+ to: outline-write
252
+ description: "Briefing completed -> generate outline options"
253
+ guard:
254
+ kind: always
255
+
256
+ - from: outline-write
257
+ to: outline-select
258
+ description: "Outline not pre-selected -> pause for operator selection"
259
+ guard:
260
+ kind: shell
261
+ options:
262
+ command: 'test -z "${vars.outline}"'
263
+
264
+ - from: outline-write
265
+ to: outline-commit
266
+ description: "Outline pre-selected -> skip interactive selection"
267
+ guard:
268
+ kind: shell
269
+ options:
270
+ command: 'test -n "${vars.outline}"'
271
+
272
+ - from: outline-select
273
+ to: outline-commit
274
+ description: "Outline selected -> commit approved outline"
275
+ guard:
276
+ kind: always
277
+
278
+ - from: outline-commit
279
+ to: script-write
280
+ description: "Approved outline committed -> write VoiceScript"
281
+ guard:
282
+ kind: always
283
+
284
+ - from: script-write
285
+ to: script-validate
286
+ description: "VoiceScript written -> validate structure and caps"
287
+ guard:
288
+ kind: always
289
+
290
+ - from: script-validate
291
+ to: wrap-docs
292
+ description: "script_approved pre-set -> skip script review (CI/dry-run)"
293
+ guard:
294
+ kind: shell
295
+ options:
296
+ command: 'test "${vars.script_approved}" = "true"'
297
+ - from: script-validate
298
+ to: script-review
299
+ description: "VoiceScript validation passed -> pause for operator review"
300
+ guard:
301
+ kind: always
302
+
303
+ - from: script-review
304
+ to: wrap-docs
305
+ description: "VoiceScript approved -> wrap into Doc[] for generation"
306
+ guard:
307
+ kind: shell
308
+ options:
309
+ command: '{ test -z "${vars.__hitlAnswer}" || test "${vars.__hitlAnswer}" = "yes"; }'
310
+
311
+ - from: script-review
312
+ to: script-revise
313
+ description: "VoiceScript rejected with revision budget remaining -> collect feedback"
314
+ guard:
315
+ kind: shell
316
+ options:
317
+ command: 'test "${vars.__hitlAnswer}" = "no" && { run_id="${vars.__runId}"; if [ -z "$run_id" ]; then run_id="solo"; fi; test "$(cat .spur/run/$run_id-solo-revise-count 2>/dev/null || echo 0)" -lt 3; }'
318
+
319
+ - from: script-review
320
+ to: failed
321
+ description: "VoiceScript rejected with revision budget exhausted -> fail"
322
+ guard:
323
+ kind: shell
324
+ options:
325
+ command: 'test "${vars.__hitlAnswer}" = "no" && { run_id="${vars.__runId}"; if [ -z "$run_id" ]; then run_id="solo"; fi; test "$(cat .spur/run/$run_id-solo-revise-count 2>/dev/null || echo 0)" -ge 3; }'
326
+
327
+ - from: script-revise
328
+ to: script-write
329
+ description: "Feedback collected -> rewrite VoiceScript"
330
+ guard:
331
+ kind: always
332
+
333
+ - from: wrap-docs
334
+ to: generate
335
+ description: "Doc[] wrapped -> invoke voice-gen"
336
+ guard:
337
+ kind: always
338
+
339
+ - from: generate
340
+ to: duration-check
341
+ description: "Voice generation finished -> verify duration"
342
+ guard:
343
+ kind: always
344
+
345
+ - from: duration-check
346
+ to: done
347
+ description: "Audio duration within [0.5x, 2.0x] band or missing content.json in dry-run -> complete"
348
+ guard:
349
+ kind: shell
350
+ options:
351
+ command: >-
352
+ test ! -f "${vars.work_dir}/4-audio/content.json" || {
353
+ dur=$(jq -r '.metadata.duration // empty' "${vars.work_dir}/4-audio/content.json" 2>/dev/null);
354
+ test -n "$dur" && target_s=$((${vars.target_duration_min} * 60)) && min_s=$(awk -v t="$target_s" 'BEGIN {print t * 0.5}') && max_s=$(awk -v t="$target_s" 'BEGIN {print t * 2.0}') && awk -v d="$dur" -v min="$min_s" -v max="$max_s" 'BEGIN { if (d >= min && d <= max) exit 0; else exit 1 }';
355
+ }
356
+
357
+ - from: duration-check
358
+ to: failed
359
+ description: "Audio duration missing or outside [0.5x, 2.0x] target range -> fail"
360
+ guard:
361
+ kind: shell
362
+ options:
363
+ command: >-
364
+ test -f "${vars.work_dir}/4-audio/content.json" && {
365
+ dur=$(jq -r '.metadata.duration // empty' "${vars.work_dir}/4-audio/content.json" 2>/dev/null);
366
+ if [ -z "$dur" ]; then
367
+ echo "duration-check failed: metadata.duration missing or not a number" >&2;
368
+ exit 0;
369
+ fi;
370
+ target_s=$((${vars.target_duration_min} * 60));
371
+ min_s=$(awk -v t="$target_s" 'BEGIN {print t * 0.5}');
372
+ max_s=$(awk -v t="$target_s" 'BEGIN {print t * 2.0}');
373
+ awk -v d="$dur" -v min="$min_s" -v max="$max_s" -v ts="$target_s" 'BEGIN { if (d < min || d > max) { printf "duration-check failed: duration %ss outside 0.5x–2x of %ss\n", d, ts > "/dev/stderr"; exit 0 } else exit 1 }';
374
+ }