@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.
- package/dist/index.js +102 -50
- package/package.json +4 -4
- package/plugins/generations/dailynews-gen/package.json +17 -0
- package/plugins/generations/dailynews-gen/plugin.json +7 -0
- package/plugins/generations/dailynews-gen/src/index.ts +111 -0
- package/plugins/generations/dailynews-gen/src/script-builder.ts +430 -0
- package/plugins/generations/dailynews-gen/tsconfig.json +4 -0
- package/plugins/generations/voice-gen/src/concat.ts +9 -7
- package/plugins/generations/voice-gen/src/index.ts +65 -11
- package/plugins/generations/voice-gen/src/mp3.ts +65 -0
- package/plugins/generations/voice-gen/src/qc.ts +157 -0
- package/plugins/generations/voice-gen/src/voicebox-client.ts +40 -2
- package/plugins/generations/voice-gen/src/voicescript.ts +149 -12
- package/plugins/ingestions/aihot-ingest/package.json +17 -0
- package/plugins/ingestions/aihot-ingest/plugin.json +7 -0
- package/plugins/ingestions/aihot-ingest/src/client.ts +185 -0
- package/plugins/ingestions/aihot-ingest/src/index.ts +137 -0
- package/plugins/ingestions/aihot-ingest/src/mapper.ts +42 -0
- package/plugins/ingestions/aihot-ingest/tsconfig.json +4 -0
- package/plugins/ingestions/web-search/src/index.ts +2 -1
- package/plugins/kk/commands/workflow-run.md +70 -48
- package/plugins/kk/skills/audio-authoring/SKILL.md +185 -0
- package/plugins/kk/skills/audio-authoring/templates/voicescript.solo.yaml +25 -0
- package/plugins/kk/workflows/judge-gated-publish-example.yaml +6 -7
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +158 -0
- package/plugins/kk/workflows/kk-ingest-generate-publish.yaml +13 -4
- package/plugins/kk/workflows/kk-itc.yaml +14 -18
- package/plugins/kk/workflows/kk-solo-podcast.yaml +135 -104
- package/plugins/kk/workflows/kk-storm-research.yaml +26 -5
- package/plugins/kk/workflows/wrap-voicescript-doc.ts +40 -0
- 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:
|
|
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
|
|
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.
|
|
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
|
-
|
|
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: "
|
|
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:
|
|
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:
|
|
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:
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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" &&
|
|
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" &&
|
|
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.
|
|
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.
|
|
281
|
+
command: 'v="$(jq -r .verdict ${vars.work_dir}/.itc-verdict.json 2>/dev/null)"; test -n "$v" -a "$v" != PASS'
|