@gobing-ai/knowledge-kit 0.0.12 → 0.0.14
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/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +120 -26
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +22187 -0
- package/plugins/generations/content-gen/plugin.json +1 -1
- package/plugins/generations/core-facts-gen/dist/index.js +22068 -0
- package/plugins/generations/core-facts-gen/plugin.json +1 -1
- package/plugins/generations/daily-article-gen/dist/index.js +22050 -0
- package/plugins/generations/daily-article-gen/plugin.json +1 -1
- package/plugins/generations/daily-article-gen/src/index.ts +13 -1
- package/plugins/generations/dailynews-gen/dist/index.js +22344 -0
- package/plugins/generations/dailynews-gen/plugin.json +1 -1
- package/plugins/generations/episode-plan-gen/dist/index.js +22503 -0
- package/plugins/generations/episode-plan-gen/plugin.json +1 -1
- package/plugins/generations/episode-plan-gen/src/index.ts +11 -0
- package/plugins/generations/image-gen/config.example.yaml +75 -0
- package/plugins/generations/image-gen/dist/index.js +24862 -0
- package/plugins/generations/image-gen/package.json +17 -0
- package/plugins/generations/image-gen/plugin.json +7 -0
- package/plugins/generations/image-gen/presets/formats/cover.yaml +57 -0
- package/plugins/generations/image-gen/presets/formats/free.yaml +46 -0
- package/plugins/generations/image-gen/presets/formats/illustration.yaml +48 -0
- package/plugins/generations/image-gen/presets/styles/clean-webapp-ui.yaml +28 -0
- package/plugins/generations/image-gen/presets/styles/cute.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/editorial.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/fresh.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/minimalist.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/photorealistic.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/sketch.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/technical-diagram.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/vibrant.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/warm.yaml +3 -0
- package/plugins/generations/image-gen/src/bytes.ts +19 -0
- package/plugins/generations/image-gen/src/index.ts +319 -0
- package/plugins/generations/image-gen/src/job.ts +143 -0
- package/plugins/generations/image-gen/src/paths.ts +31 -0
- package/plugins/generations/image-gen/src/presets.ts +344 -0
- package/plugins/generations/image-gen/src/providers/agnes.ts +110 -0
- package/plugins/generations/image-gen/src/providers/azure.ts +153 -0
- package/plugins/generations/image-gen/src/providers/codex-cli.ts +170 -0
- package/plugins/generations/image-gen/src/providers/dashscope.ts +485 -0
- package/plugins/generations/image-gen/src/providers/google.ts +268 -0
- package/plugins/generations/image-gen/src/providers/huggingface.ts +59 -0
- package/plugins/generations/image-gen/src/providers/jimeng.ts +259 -0
- package/plugins/generations/image-gen/src/providers/minimax.ts +171 -0
- package/plugins/generations/image-gen/src/providers/openai.ts +319 -0
- package/plugins/generations/image-gen/src/providers/openrouter.ts +257 -0
- package/plugins/generations/image-gen/src/providers/refs.ts +24 -0
- package/plugins/generations/image-gen/src/providers/replicate.ts +279 -0
- package/plugins/generations/image-gen/src/providers/seedream.ts +128 -0
- package/plugins/generations/image-gen/src/providers/types.ts +286 -0
- package/plugins/generations/image-gen/src/providers/zai.ts +237 -0
- package/plugins/generations/image-gen/tsconfig.json +8 -0
- package/plugins/generations/news-report-gen/dist/index.js +22193 -0
- package/plugins/generations/news-report-gen/package.json +17 -0
- package/plugins/generations/news-report-gen/plugin.json +7 -0
- package/plugins/generations/news-report-gen/src/index.ts +308 -0
- package/plugins/generations/news-report-gen/tsconfig.json +4 -0
- package/plugins/generations/omni-voice-gen/Makefile +14 -0
- package/plugins/generations/omni-voice-gen/README.md +112 -0
- package/plugins/generations/omni-voice-gen/bin/omni-voice-gen +2 -0
- package/plugins/generations/omni-voice-gen/dist/omni-voice-gen-prr8skpb. +2 -0
- package/plugins/generations/omni-voice-gen/dist/omni-voice-gen.js +6 -0
- package/plugins/generations/omni-voice-gen/plugin.json +6 -0
- package/plugins/generations/omni-voice-gen/profiles.json +12 -0
- package/plugins/generations/omni-voice-gen/pyproject.toml +25 -0
- package/plugins/generations/omni-voice-gen/scripts/coverage_gate.py +74 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__init__.py +1 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__main__.py +39 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/audio.py +190 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/backend.py +150 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/contract.py +76 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/mp3.py +60 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +289 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/profiles.py +100 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/qc.py +234 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +352 -0
- package/plugins/generations/omni-voice-gen/uv.lock +3510 -0
- package/plugins/generations/voice-gen/dist/index.js +23055 -0
- package/plugins/generations/voice-gen/plugin.json +1 -1
- package/plugins/generations/voice-gen/src/index.ts +16 -1
- package/plugins/generations/voice-gen/src/voicebox-client.ts +3 -1
- package/plugins/ingestions/aihot-ingest/dist/index.js +22378 -0
- package/plugins/ingestions/aihot-ingest/plugin.json +1 -1
- package/plugins/ingestions/horizon-ingest/dist/index.js +22125 -0
- package/plugins/ingestions/horizon-ingest/plugin.json +1 -1
- package/plugins/ingestions/karakeep-local/dist/index.js +24204 -0
- package/plugins/ingestions/karakeep-local/plugin.json +1 -1
- package/plugins/ingestions/last30days-ingest/dist/index.js +22070 -0
- package/plugins/ingestions/last30days-ingest/plugin.json +1 -1
- package/plugins/ingestions/web-search/dist/index.js +24399 -0
- package/plugins/ingestions/web-search/plugin.json +1 -1
- package/plugins/kk/commands/image-extract.md +40 -0
- package/plugins/kk/commands/image-generate.md +32 -0
- package/plugins/kk/config.example.yaml +80 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/skills/image-authoring/SKILL.md +257 -0
- package/plugins/kk/skills/image-authoring/references/format-drafting.md +57 -0
- package/plugins/kk/skills/image-authoring/references/illustration-positions.md +87 -0
- package/plugins/kk/skills/image-authoring/references/migrating-from-wt.md +31 -0
- package/plugins/kk/skills/image-authoring/references/providers.md +52 -0
- package/plugins/kk/skills/image-authoring/references/style-extraction.md +139 -0
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +130 -30
- package/plugins/publishings/emdash-pub/dist/index.js +22263 -0
- package/plugins/publishings/emdash-pub/plugin.json +1 -1
- package/plugins/publishings/podcast-pub/dist/index.js +22650 -0
- package/plugins/publishings/podcast-pub/plugin.json +8 -2
- package/plugins/publishings/podcast-pub/src/index.ts +18 -2
- package/plugins/publishings/podcast-pub/src/show-notes.ts +56 -9
- package/plugins/publishings/qiita-pub/dist/index.js +22101 -0
- package/plugins/publishings/qiita-pub/plugin.json +1 -1
- package/plugins/publishings/surfdash-pub/dist/index.js +22323 -0
- package/plugins/publishings/surfdash-pub/plugin.json +1 -1
- package/plugins/publishings/surfdash-pub/src/index.ts +109 -9
- package/plugins/publishings/zenn-pub/dist/index.js +22142 -0
- package/plugins/publishings/zenn-pub/plugin.json +1 -1
- package/plugins/sp/scripts/batch-preflight.mjs +346 -0
- package/plugins/sp/scripts/batch-preflight.ts +459 -0
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +615 -0
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +846 -0
- package/plugins/sp/scripts/daily-summary/logger.ts +28 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +223 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +367 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +132 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +169 -0
- package/plugins/sp/scripts/feature-dev-precheck.mjs +171 -0
- package/plugins/sp/scripts/feature-dev-precheck.ts +238 -0
- package/plugins/sp/scripts/feature-sync-bounded.mjs +285 -0
- package/plugins/sp/scripts/feature-sync-bounded.ts +478 -0
- package/plugins/sp/scripts/history-anatomy-cache.mjs +902 -0
- package/plugins/sp/scripts/history-anatomy-cache.ts +1028 -0
- package/plugins/sp/scripts/idea-handoff.mjs +22 -0
- package/plugins/sp/scripts/idea-handoff.ts +44 -0
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +185 -0
- package/plugins/sp/scripts/inline-run-setup.ts +198 -0
- package/plugins/sp/scripts/pr-reviewing.mjs +769 -0
- package/plugins/sp/scripts/pr-reviewing.ts +925 -0
- package/plugins/sp/scripts/quality-gate.mjs +179 -0
- package/plugins/sp/scripts/quality-gate.ts +217 -0
- package/plugins/sp/scripts/script-contract-check.ts +319 -0
- package/plugins/sp/scripts/stage-registry-adapter.ts +1533 -0
- package/plugins/sp/scripts/surface-drift-inventory.ts +929 -0
- package/plugins/sp/scripts/task-evidence-precheck.ts +181 -0
- package/plugins/sp/scripts/task-size-precheck.ts +175 -0
- package/plugins/sp/scripts/transition-shim-check.ts +238 -0
- package/plugins/sp/scripts/validate-commands.ts +689 -0
- package/plugins/sp/scripts/validate-flag-contracts.ts +878 -0
- package/plugins/sp/scripts/verify-answer-lint.ts +530 -0
- package/plugins/sp/scripts/workflow-step-profile.mjs +316 -0
- package/plugins/sp/scripts/workflow-step-profile.ts +456 -0
- package/plugins/sp/scripts/wrapup-steps.mjs +373 -0
- package/plugins/sp/scripts/wrapup-steps.ts +466 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Provider facts for image generation
|
|
2
|
+
|
|
3
|
+
The operator-facing digest of 0103's provider inventory. Source of truth:
|
|
4
|
+
`plugins/generations/image-gen/src/providers/types.ts` (the frozen registry) —
|
|
5
|
+
if this file and the registry ever disagree, the registry wins and this file
|
|
6
|
+
gets fixed. Credential *values* live only in
|
|
7
|
+
`plugins.generations.image-gen.env` (`plugins/kk/config.example.yaml`); they
|
|
8
|
+
are read by `invoke.ts` at spawn, never by this skill.
|
|
9
|
+
|
|
10
|
+
## The rules
|
|
11
|
+
|
|
12
|
+
- **Readiness is env presence.** A provider is available when its env var(s)
|
|
13
|
+
are non-empty. A stale key still passes readiness and surfaces as the
|
|
14
|
+
adapter's own auth error at generate time.
|
|
15
|
+
- **Auto-selection** walks `AUTO_ORDER` — google, openai, replicate,
|
|
16
|
+
openrouter, dashscope, seedream, zai, minimax, jimeng, azure, agnes — and
|
|
17
|
+
takes the first available. `IMAGE_GEN_PROVIDER` names a provider explicitly;
|
|
18
|
+
`--provider` outranks it.
|
|
19
|
+
- **`codex-cli` is never auto-selected.** Its readiness (the `codex` CLI on
|
|
20
|
+
PATH with an active login) is not env-expressible; pin it explicitly.
|
|
21
|
+
- **Reference images** ride `--ref` only when `supportsReferenceImages(model)`
|
|
22
|
+
is true for the resolved model — a substring test over the registry's
|
|
23
|
+
`refModels` fragments (no `refModels` = every model; empty list = none).
|
|
24
|
+
When it is false, say so and offer `extract-style`; never drop the
|
|
25
|
+
reference silently.
|
|
26
|
+
|
|
27
|
+
## The inventory
|
|
28
|
+
|
|
29
|
+
| Provider | Env var(s) that enable it | Default model | Reference images |
|
|
30
|
+
| --- | --- | --- | --- |
|
|
31
|
+
| agnes | `AGNES_API_KEY` | `agnes-image-2.1-flash` | yes |
|
|
32
|
+
| azure | `AZURE_OPENAI_API_KEY` (+ `AZURE_OPENAI_BASE_URL`, `AZURE_OPENAI_DEPLOYMENT` to run) | `gpt-image-2` | yes (no `refModels` declared → every model passes `supportsReferenceImages`) |
|
|
33
|
+
| codex-cli | not env-expressible — `codex` CLI + login | `codex-image-gen` | never auto-selected |
|
|
34
|
+
| dashscope | `DASHSCOPE_API_KEY` | `qwen-image-2.0-pro` | only `wan2.7` models |
|
|
35
|
+
| google | `GOOGLE_API_KEY` or `GEMINI_API_KEY` | `gemini-3-pro-image` | the gemini image models (`gemini-3-pro-image`, `*-flash-image`, previews) |
|
|
36
|
+
| jimeng | `JIMENG_ACCESS_KEY_ID` **and** `JIMENG_SECRET_ACCESS_KEY` (both halves) | `jimeng_t2i_v40` | yes |
|
|
37
|
+
| minimax | `MINIMAX_API_KEY` | `image-01` | yes (jpg/png <10MB, subject reference) |
|
|
38
|
+
| openai | `OPENAI_API_KEY` | `gpt-image-2` | `gpt-image` models |
|
|
39
|
+
| openrouter | `OPENROUTER_API_KEY` | `google/gemini-3.1-flash-image` | yes (no `refModels` declared → treated as supporting; real capability depends on the routed model) |
|
|
40
|
+
| replicate | `REPLICATE_API_TOKEN` | `google/nano-banana-2` | yes |
|
|
41
|
+
| seedream | `ARK_API_KEY` (not `SEEDREAM_*`) | `doubao-seedream-5-0-260128` | `doubao-seedream-5-0` / `4-5-` / `4-0-` models |
|
|
42
|
+
| zai | `ZAI_API_KEY` or `BIGMODEL_API_KEY` | `glm-image` | **none** |
|
|
43
|
+
|
|
44
|
+
## Deciding with references in hand
|
|
45
|
+
|
|
46
|
+
1. Resolve the provider per Preference chain (argument → `IMAGE_GEN_PROVIDER`
|
|
47
|
+
→ config → `AUTO_ORDER`).
|
|
48
|
+
2. Check `supportsReferenceImages` for the resolved model. False → the
|
|
49
|
+
pass-through path is unavailable: state it and offer style extraction
|
|
50
|
+
(0105), which works with every provider.
|
|
51
|
+
3. Name the chosen provider and model in the spend preview — the operator
|
|
52
|
+
approves the pair, not just the prompt.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Style extraction from reference images
|
|
2
|
+
|
|
3
|
+
Loaded by the image-authoring skill when the operator hands over one or more
|
|
4
|
+
reference images and wants the look captured as a reusable style preset.
|
|
5
|
+
Extraction is a **skill operation** — the running agent reads the image(s) with
|
|
6
|
+
its own vision and writes a YAML file. No plugin code, no vision API call, no
|
|
7
|
+
provider credential is involved.
|
|
8
|
+
|
|
9
|
+
There are two mechanisms for using a reference image, and this file owns the
|
|
10
|
+
boundary between them:
|
|
11
|
+
|
|
12
|
+
| baoyu usage | kk mechanism | What happens |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| `direct` | pass-through | The file rides `ImageJobSpec.referenceImages` (0104) to a provider whose `supportsReferenceImages(model)` is true (0103). Highest fidelity; produces one image, nothing reusable. |
|
|
15
|
+
| `style` | extraction (this file) | The look is analysed and written as a `styles/<name>.yaml`. Works with every provider; reusable and editable. |
|
|
16
|
+
| `palette` | extraction, partial | Same procedure, but only `palette`, `name` and `description` are filled; `modifiers` stays a one-line summary. |
|
|
17
|
+
|
|
18
|
+
## Where the preset goes
|
|
19
|
+
|
|
20
|
+
Write to the **user root**: `~/.config/kk/image-presets/styles/<name>.yaml`. A cloned
|
|
21
|
+
look is usually the operator's, not the repository's. An operator who wants the
|
|
22
|
+
preset shared with a team writes to the project root
|
|
23
|
+
(`<KNOWLEDGE_KIT_PROJECT_ROOT>/.kk/image-presets/styles/`) instead — it ranks
|
|
24
|
+
higher in the loader chain. No registration step exists or is needed: the
|
|
25
|
+
loader discovers presets by scanning these directories (0102). If the chosen
|
|
26
|
+
name matches a shipped preset, the new file **shadows** it whole-file and the
|
|
27
|
+
loader emits a `shadowed` diagnostic naming both paths — that is the intended
|
|
28
|
+
way to adjust a shipped style, not an accident.
|
|
29
|
+
|
|
30
|
+
## Input rules
|
|
31
|
+
|
|
32
|
+
- **One to five reference images.** Count them before reading anything.
|
|
33
|
+
- **Cap refusal (exact wording):** if the operator supplies more than 5, refuse
|
|
34
|
+
before reading any of them:
|
|
35
|
+
`Refusing extraction: <N> reference images requested; the cap is 5. Split the set into coherent groups, or drop the least representative references and re-run.`
|
|
36
|
+
The cap is a constant, not a design commitment; changing it means changing
|
|
37
|
+
this number and this message only.
|
|
38
|
+
|
|
39
|
+
## Output rules — the schema is frozen
|
|
40
|
+
|
|
41
|
+
Emit **YAML only** — no prose, no code fence. The document fills **exactly**
|
|
42
|
+
the `StylePreset` fields of `plugins/generations/image-gen/src/presets.ts` and
|
|
43
|
+
nothing else. **Inventing a field outside this list is a defect, not an
|
|
44
|
+
enhancement.** Field-by-field:
|
|
45
|
+
|
|
46
|
+
| Field | Required | Rule |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `name` | yes | kebab-case, derived from the look, never from the filename. |
|
|
49
|
+
| `description` | yes | One or two sentences naming the look and where it works. |
|
|
50
|
+
| `modifiers` | yes | A comma-separated prompt fragment in the same voice as the shipped styles under `plugins/generations/image-gen/presets/styles/` — `composePrompt` concatenates it as segment 3 of a comma-joined prompt. Read two shipped styles before writing one; a style in the wrong voice produces a prompt in two voices. |
|
|
51
|
+
| `palette` | no | 3–6 entries of `{role, name, hex}`. `role` from `{primary, secondary, accent, background, text}`. `hex` uppercase `#RRGGBB`, sampled from the image, never invented. (A merged set may legitimately keep fewer than 3 — only colours common to every reference survive; see merge rules.) |
|
|
52
|
+
| `typography` | no | Only when the image evidences it. Omit rather than guess. |
|
|
53
|
+
| `visualElements` | no | Array of strings naming recurring visual devices (borders, banding, grain, iconography). Only what the image shows. |
|
|
54
|
+
| `bestFor` | no | The content types this look suits. Inferred from the image's own subject matter. |
|
|
55
|
+
| `negative` | no | What this look must avoid — inferred from what is conspicuously absent. |
|
|
56
|
+
| `source` | yes for extraction | `kind: extracted`, `refImages` = the absolute input paths **in the order given**, `extractedAt` = ISO-8601 timestamp, `confidence` = the judgement below. |
|
|
57
|
+
|
|
58
|
+
### The `confidence` decision rule (human-judged, not computed)
|
|
59
|
+
|
|
60
|
+
- `high` — a single clean image with an obvious, consistent look.
|
|
61
|
+
- `medium` — mixed or noisy references, or a merged set where the references
|
|
62
|
+
disagree on any of the four fidelity axes.
|
|
63
|
+
- `low` — the look is ambiguous, or the image is photographic content rather
|
|
64
|
+
than a designed style (a photo *of* something is not a style).
|
|
65
|
+
|
|
66
|
+
## Multi-image merge rules
|
|
67
|
+
|
|
68
|
+
With N references (1 ≤ N ≤ 5) the output is **one merged preset**, not N
|
|
69
|
+
presets and not an average:
|
|
70
|
+
|
|
71
|
+
- `palette` — colours **common to the set**. A colour appearing in one image
|
|
72
|
+
only is dropped, not averaged into a muddy midpoint.
|
|
73
|
+
- `modifiers` — traits present in **every** image. Divergent traits are
|
|
74
|
+
dropped, and each discarded trait is **named in the extraction report** so
|
|
75
|
+
the operator sees exactly what was thrown away.
|
|
76
|
+
- `confidence` — capped at `medium` when the set disagrees on any fidelity axis.
|
|
77
|
+
- `source.refImages` — all N absolute paths, in the order given.
|
|
78
|
+
|
|
79
|
+
## What extraction writes
|
|
80
|
+
|
|
81
|
+
**Style by default; format drafting is opt-in.** A `FormatPreset` carries `template`,
|
|
82
|
+
`variables`, `keywords`, `defaultStyle`, `outputFilename` — none of which is
|
|
83
|
+
*recoverable* from a picture, so extraction alone never writes a `formats/*.yaml`.
|
|
84
|
+
What an image *does* yield is arithmetic: the extraction report **states the observed
|
|
85
|
+
pixel dimensions and the nearest named aspect ratio** of each reference, chosen from
|
|
86
|
+
`21:9`, `4:3`, `1:1`, `16:9`, `3:4`, `9:16`.
|
|
87
|
+
|
|
88
|
+
When the operator passes `--emit-format <name> --job <job>` alongside `--extract-style`,
|
|
89
|
+
that arithmetic plus the same visual analysis feeds a **draft** format preset — measured
|
|
90
|
+
fields taken, visual defaults authored, structural fields reused from the shipped
|
|
91
|
+
skeleton, operator fine-tuning expected. That procedure lives in
|
|
92
|
+
[format-drafting.md](format-drafting.md); this file stays style-only.
|
|
93
|
+
|
|
94
|
+
## Pass-through degradation (visible, never silent)
|
|
95
|
+
|
|
96
|
+
When the operator wants `direct` (pass-through) and the selected provider's
|
|
97
|
+
`supportsReferenceImages(model)` returns `false`, the skill must state it in
|
|
98
|
+
these terms and offer the alternative:
|
|
99
|
+
|
|
100
|
+
`Provider <name> cannot accept reference images for model <model> (supportsReferenceImages: false). Pass-through is unavailable; offering style extraction instead.`
|
|
101
|
+
|
|
102
|
+
**Never silently proceed without the reference.** A plausible image generated
|
|
103
|
+
from a dropped reference is the worst outcome available — worse than an error.
|
|
104
|
+
|
|
105
|
+
## Validate after writing
|
|
106
|
+
|
|
107
|
+
After writing the YAML, run the dry run: `KK_IMAGE_DRY_RUN=1` `image-gen`
|
|
108
|
+
(0104) with a job whose `style` is the new preset's name. A malformed or
|
|
109
|
+
unloadable file fails there loudly, naming the file and the schema violation,
|
|
110
|
+
before any provider spend. No validator is written; 0102's loader and the dry
|
|
111
|
+
run already are one.
|
|
112
|
+
|
|
113
|
+
## Fidelity check (v1 method: human eyeball)
|
|
114
|
+
|
|
115
|
+
To answer "did the clone work": extract from reference **I**, regenerate with
|
|
116
|
+
the extracted preset using a prompt describing **I's own subject** (so subject
|
|
117
|
+
variance does not contaminate the comparison), then compare **I** and the
|
|
118
|
+
regeneration on exactly four axes — **palette, rendering technique,
|
|
119
|
+
composition density, typographic treatment**. The v1 comparison method is a
|
|
120
|
+
human eyeballing the pair; no metric is computed. Record the verdict in the
|
|
121
|
+
preset's `source.confidence`; if the fidelity verdict disagrees with the
|
|
122
|
+
extraction-side judgement, take the lower.
|
|
123
|
+
|
|
124
|
+
## Worked output shape
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
name: example-look
|
|
128
|
+
description: Flat colour-band composition with deep teal over warm cream.
|
|
129
|
+
modifiers: flat colour-band composition, deep teal and warm cream palette, hard edges, no gradients, minimal detail
|
|
130
|
+
palette:
|
|
131
|
+
- { role: primary, name: deep teal, hex: '#1D5C63' }
|
|
132
|
+
- { role: background, name: warm cream, hex: '#F5EFE0' }
|
|
133
|
+
source:
|
|
134
|
+
kind: extracted
|
|
135
|
+
refImages:
|
|
136
|
+
- /absolute/path/to/reference.png
|
|
137
|
+
extractedAt: '2026-09-09T18:00:00Z'
|
|
138
|
+
confidence: high
|
|
139
|
+
```
|
|
@@ -19,6 +19,7 @@ vars:
|
|
|
19
19
|
limit: "10"
|
|
20
20
|
language: "zh"
|
|
21
21
|
voice_profile: "robin-news"
|
|
22
|
+
voice_generator: "voice-gen"
|
|
22
23
|
title: ""
|
|
23
24
|
cursor: ""
|
|
24
25
|
state_file: ""
|
|
@@ -54,6 +55,7 @@ states:
|
|
|
54
55
|
- kind: shell
|
|
55
56
|
options:
|
|
56
57
|
command: >-
|
|
58
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"prepare\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
57
59
|
test -n "${vars.work_dir}" || { echo "missing required variable: work_dir" >&2 && exit 1; };
|
|
58
60
|
mkdir -p "${vars.work_dir}/1-ingest" "${vars.work_dir}/2-facts" "${vars.work_dir}/2-plan" "${vars.work_dir}/2-article" "${vars.work_dir}/2-cover" "${vars.work_dir}/2-script" "${vars.work_dir}/3-audio" "${vars.work_dir}/3-publish";
|
|
59
61
|
date +%Y%m%d > "${vars.work_dir}/run-date.txt" &&
|
|
@@ -75,11 +77,13 @@ states:
|
|
|
75
77
|
# fan-in 30m per-source default and making empty episodes stochastic — horizon gets a
|
|
76
78
|
# 45m leash; aihot/last30days keep the default.
|
|
77
79
|
command: >-
|
|
80
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"collect\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
78
81
|
if [ -f "${vars.work_dir}/1-ingest/blended.json" ]; then echo "collect cached: ${vars.work_dir}/1-ingest/blended.json"; exit 0; fi;
|
|
79
82
|
kk executor fan-in --source "aihot-ingest:${vars.work_dir}/1-ingest/source.json" --source "last30days-ingest:${vars.work_dir}/1-ingest/last30days.json" --source "horizon-ingest:${vars.work_dir}/1-ingest/horizon.json" --source-timeout-override "horizon-ingest:2700000" --out "${vars.work_dir}/1-ingest/blended.json"
|
|
80
83
|
- kind: shell
|
|
81
84
|
options:
|
|
82
85
|
command: >-
|
|
86
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"collect\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
83
87
|
set --;
|
|
84
88
|
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
85
89
|
if [ -f "${vars.work_dir}/2-facts/content.json" ] && [ -f "${vars.work_dir}/2-facts/core-facts.md" ]; then echo "core facts cached: ${vars.work_dir}/2-facts/core-facts.md"; exit 0; fi;
|
|
@@ -92,6 +96,7 @@ states:
|
|
|
92
96
|
- kind: shell
|
|
93
97
|
options:
|
|
94
98
|
command: >-
|
|
99
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"plan\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
95
100
|
set --;
|
|
96
101
|
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
97
102
|
PLAN_CONTENT="${vars.work_dir}/2-plan/content.json"; PLAN_DOCS="${vars.work_dir}/2-plan/plan.json";
|
|
@@ -106,6 +111,7 @@ states:
|
|
|
106
111
|
- kind: shell
|
|
107
112
|
options:
|
|
108
113
|
command: >-
|
|
114
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"quality-control-content\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
109
115
|
set --;
|
|
110
116
|
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
111
117
|
if [ -f "${vars.work_dir}/2-plan/candidates.json" ]; then echo "candidates cached: ${vars.work_dir}/2-plan/candidates.json"; exit 0; fi;
|
|
@@ -136,23 +142,30 @@ states:
|
|
|
136
142
|
- kind: shell
|
|
137
143
|
options:
|
|
138
144
|
command: >-
|
|
145
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"article\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
139
146
|
set --;
|
|
140
147
|
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
141
148
|
CONTENT_OUT="${vars.work_dir}/2-article/content-${vars.run_date}.json"; ARTICLE_MD="${vars.work_dir}/2-article/article-${vars.run_date}.md";
|
|
142
149
|
if [ -f "$ARTICLE_MD" ]; then echo "article cached: $ARTICLE_MD"; else PLAN_IN="${vars.work_dir}/2-plan/plan.json"; PLAN_OK=1; bun -e 'try { JSON.parse(await Bun.file(process.argv[1]).text()); process.exit(0); } catch (e) { console.warn("article: plan.json invalid JSON — replacing with untranslated plan (downstream states read this file): " + e.message); const docs = JSON.parse(await Bun.file(process.argv[2]).text()).metadata?.docs; if (!Array.isArray(docs) || docs.length === 0) { console.error("article: candidates.json metadata.docs missing"); process.exit(2); } await Bun.write(process.argv[1], JSON.stringify(docs, null, 2)); process.exit(1); }' "$PLAN_IN" "${vars.work_dir}/2-plan/candidates.json"; case $? in 0|1) PLAN_OK=0;; esac; test "$PLAN_OK" -eq 0 || { echo "article: no usable plan input"; exit 1; }; ARTICLE_DATE="${vars.run_date}" kk executor run daily-article-gen --in "$PLAN_IN" --out "$CONTENT_OUT" "$@" && bun -e 'await Bun.write(process.argv[2], (await Bun.file(process.argv[1]).json()).body)' "$CONTENT_OUT" "$ARTICLE_MD"; fi
|
|
143
150
|
|
|
144
151
|
- id: cover
|
|
145
|
-
description: "Generate the article cover image via the
|
|
152
|
+
description: "Generate the article cover image via the kk:image-authoring skill (agent.run)"
|
|
146
153
|
onEnter:
|
|
147
154
|
- kind: agent.run
|
|
148
155
|
options:
|
|
149
156
|
role: scribe
|
|
150
157
|
agent: ${vars.agent}
|
|
151
158
|
input: |
|
|
152
|
-
Invoke the
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
Invoke the kk:image-authoring skill (generate operation).
|
|
160
|
+
This node is a non-interactive workflow step: run every image command with KK_NONINTERACTIVE=1 and KK_IMAGE_AUTO_CONFIRM=1 in the environment (this workflow approves the image spend via cover_enabled) — never prompt, never open a question.
|
|
161
|
+
Command: --format cover --content ${vars.work_dir}/2-article/article-${vars.run_date}.md --out ${vars.work_dir}/2-cover
|
|
162
|
+
Derive the cover template variables (title, subtitle, topics) from the article as the skill owns; style is the format's default — do not pass --style.
|
|
163
|
+
The plugin writes ${vars.work_dir}/2-cover/content.json plus the artifact ${vars.work_dir}/2-cover/content-cover-01.<ext>, where <ext> follows the elected provider's native output (payload honesty: minimax returns JPEG, google/ark return PNG). If generation fails, report the error verbatim and stop — no procedural fallback.
|
|
164
|
+
Run 20260911 context: only MINIMAX_API_KEY is credentialed (ARK out of token, google key unset), so the elected provider is minimax and the artifact lands as content-cover-01.jpg.
|
|
165
|
+
expectFile: ${vars.work_dir}/2-cover/content-cover-01.jpg
|
|
166
|
+
- kind: shell
|
|
167
|
+
options:
|
|
168
|
+
command: 'cd "${vars.work_dir}/2-cover" && src="content-cover-01.png"; test -f "$src" || src="content-cover-01.jpg"; test -f "$src" || { echo "cover normalize: no content-cover-01.{png,jpg} artifact" >&2; exit 1; }; case "$src" in *.jpg) command -v sips >/dev/null 2>&1 || { echo "cover normalize: sips not found; cannot transcode jpg -> png" >&2; exit 1; }; sips -s format png "$src" --out "cover-${vars.run_date}.png" >/dev/null;; *) mv "$src" "cover-${vars.run_date}.png";; esac; rm -f content-cover-01.jpg content-cover-01.png; echo "cover normalized: cover-${vars.run_date}.png"'
|
|
156
169
|
|
|
157
170
|
- id: script
|
|
158
171
|
description: "Compile the episode plan Doc[] into structured broadcast VoiceScript YAML Content"
|
|
@@ -160,6 +173,7 @@ states:
|
|
|
160
173
|
- kind: shell
|
|
161
174
|
options:
|
|
162
175
|
command: >-
|
|
176
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"script\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
163
177
|
set --;
|
|
164
178
|
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
165
179
|
SCRIPT_CONTENT_OUT="${vars.work_dir}/2-script/content-${vars.run_date}.json"; VOICESCRIPT_YAML="${vars.work_dir}/2-script/voicescript-${vars.run_date}.yaml";
|
|
@@ -180,18 +194,20 @@ states:
|
|
|
180
194
|
- kind: shell
|
|
181
195
|
options:
|
|
182
196
|
command: >-
|
|
197
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"wrap-docs\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
183
198
|
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-news" } }; await Bun.write(process.argv[2], JSON.stringify([doc], null, 2))' "${vars.work_dir}/2-script/voicescript-${vars.run_date}.yaml" "${vars.work_dir}/3-audio/docs.json" "${vars.voice_profile}"
|
|
184
199
|
|
|
185
200
|
- id: generate
|
|
186
|
-
description: "Invoke voice
|
|
201
|
+
description: "Invoke voice generator plugin (voice_generator var) to generate audio"
|
|
187
202
|
onEnter:
|
|
188
203
|
- kind: shell
|
|
189
204
|
options:
|
|
190
205
|
command: >-
|
|
206
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"generate\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
191
207
|
set --;
|
|
192
208
|
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
193
209
|
if [ -f "${vars.work_dir}/3-audio/content.json" ] && jq -e '.metadata.qc.passed == true' "${vars.work_dir}/3-audio/content.json" > /dev/null 2>&1; then echo "audio cached: ${vars.work_dir}/3-audio/content.json (QC passed)"; exit 0; fi;
|
|
194
|
-
VOICE_GEN_MP3="${vars.transcode_mp3}" kk executor run
|
|
210
|
+
VOICE_GEN_MP3="${vars.transcode_mp3}" kk executor run ${vars.voice_generator} --in "${vars.work_dir}/3-audio/docs.json" --out "${vars.work_dir}/3-audio/content.json" "$@"
|
|
195
211
|
|
|
196
212
|
- id: quality-control
|
|
197
213
|
description: "Verify audio quality, speech rate, and silence/repetition QC metrics"
|
|
@@ -199,63 +215,117 @@ states:
|
|
|
199
215
|
- kind: shell
|
|
200
216
|
options:
|
|
201
217
|
command: >-
|
|
218
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"quality-control\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
202
219
|
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"
|
|
203
220
|
|
|
221
|
+
- id: publish-prep
|
|
222
|
+
description: "Merge article Content with audio (podcast assembly facts ride the content options channel for podcast-pub's absorbed assembly; runDate kept for surfdash slug)"
|
|
223
|
+
onEnter:
|
|
224
|
+
- kind: shell
|
|
225
|
+
options:
|
|
226
|
+
command: >-
|
|
227
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"publish-prep\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
228
|
+
bun -e 'const article = JSON.parse(await Bun.file(process.argv[1]).text()); const audio = JSON.parse(await Bun.file(process.argv[2]).text()); const audioMeta = audio.metadata ?? {}; const rawFile = audioMeta.mp3Path ?? audioMeta.audioPath; if (!rawFile) { console.error("publish-prep: voice content carries no mp3Path/audioPath"); process.exit(1); } const { resolve } = await import("node:path"); const { existsSync } = await import("node:fs"); const audioFile = resolve(rawFile); const coverPath = resolve(process.argv[4]); const merged = { ...article, metadata: { ...(article.metadata ?? {}), audioFile, runDate: process.argv[5] }, options: { audioFile, durationSec: audioMeta.duration, coverPath: existsSync(coverPath) ? coverPath : undefined } }; await Bun.write(process.argv[3], JSON.stringify(merged, null, 2))' "${vars.work_dir}/2-article/content-${vars.run_date}.json" "${vars.work_dir}/3-audio/content.json" "${vars.work_dir}/3-publish/content.json" "${vars.work_dir}/2-cover/cover-${vars.run_date}.png" "${vars.run_date}"
|
|
229
|
+
|
|
230
|
+
- id: publish-surfdash
|
|
231
|
+
description: "Publish the merged Content to surfdash (single target); result lands in result.json — surfdash publishes first so show-notes can link the article (task 0118 seam reorder)"
|
|
232
|
+
onEnter:
|
|
233
|
+
- kind: shell
|
|
234
|
+
options:
|
|
235
|
+
command: >-
|
|
236
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"publish-surfdash\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
237
|
+
set --;
|
|
238
|
+
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
239
|
+
RESULT="${vars.work_dir}/3-publish/result-surfdash.json";
|
|
240
|
+
if jq -e '.targets[]? | select(.target == "surfdash-pub" and .ok == true and .status == "published")' "$RESULT" > /dev/null 2>&1; then echo "publish-surfdash cached: surfdash target ok in $RESULT"; exit 0; fi;
|
|
241
|
+
env -u SPUR_WORKFLOW_RUN_ACTIVE kk executor fan-out --content "${vars.work_dir}/3-publish/content.json" --target surfdash-pub --out "$RESULT" "$@" || true;
|
|
242
|
+
echo "publish-surfdash: surfdash target dispatched; classification happens after podcast publish"
|
|
243
|
+
|
|
204
244
|
- id: show-notes
|
|
205
|
-
description: "Render
|
|
245
|
+
description: "Render timeline show notes from the episode plan (podcast-pub show-notes op, briefMode; article URL from the surfdash result postPath — surfdash failure degrades to legacy full-body layout)"
|
|
206
246
|
onEnter:
|
|
207
247
|
- kind: shell
|
|
208
248
|
options:
|
|
209
249
|
command: >-
|
|
250
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"show-notes\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
210
251
|
set --;
|
|
211
252
|
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
212
253
|
TITLE_ARG="${vars.title}";
|
|
213
254
|
EFFECTIVE_TITLE="$(jq -r '.title // ""' "${vars.work_dir}/2-article/content-${vars.run_date}.json")";
|
|
214
255
|
if [ -z "$TITLE_ARG" ]; then TITLE_ARG="$EFFECTIVE_TITLE"; fi;
|
|
215
|
-
|
|
256
|
+
RESULT="${vars.work_dir}/3-publish/result.json";
|
|
257
|
+
POST_PATH="$(jq -r '[(.targets // [])[] | select(.target == "surfdash-pub" and .ok == true) | .metadata.postPath // empty] | first // ""' "$RESULT" 2>/dev/null || true)";
|
|
258
|
+
PODCAST_PUB_TITLE="$TITLE_ARG" PODCAST_PUB_RUN_DATE="${vars.run_date}" PODCAST_PUB_BRIEF_MODE=true PODCAST_PUB_ARTICLE_POST_PATH="$POST_PATH" kk executor run podcast-pub --in "${vars.work_dir}/2-plan/plan.json" --out "${vars.work_dir}/3-publish/show-notes.md" "$@"
|
|
216
259
|
|
|
217
|
-
- id: publish-
|
|
218
|
-
description: "
|
|
260
|
+
- id: publish-podcast
|
|
261
|
+
description: "Publish the episode to podcast-pub with the rendered show notes (metadata.showNotes carries the brief/timeline markdown; map.ts falls back to content.body when absent — task 0118 R3)"
|
|
219
262
|
onEnter:
|
|
220
263
|
- kind: shell
|
|
221
264
|
options:
|
|
222
265
|
command: >-
|
|
223
|
-
|
|
266
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"publish-podcast\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
267
|
+
set --;
|
|
268
|
+
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
269
|
+
RESULT="${vars.work_dir}/3-publish/result-podcast.json";
|
|
270
|
+
if jq -e '.targets[]? | select(.target == "podcast-pub" and .ok == true and .status == "published")' "$RESULT" > /dev/null 2>&1; then echo "publish-podcast cached: podcast target ok in $RESULT"; exit 0; fi;
|
|
271
|
+
bun -e 'const content = JSON.parse(await Bun.file(process.argv[1]).text()); const notes = await Bun.file(process.argv[2]).text(); content.metadata = { ...(content.metadata ?? {}), showNotes: notes }; await Bun.write(process.argv[1], JSON.stringify(content, null, 2))' "${vars.work_dir}/3-publish/content.json" "${vars.work_dir}/3-publish/show-notes.md" &&
|
|
272
|
+
env -u SPUR_WORKFLOW_RUN_ACTIVE kk executor fan-out --content "${vars.work_dir}/3-publish/content.json" --target podcast-pub --out "$RESULT" "$@" || true;
|
|
273
|
+
echo "publish-podcast: podcast target dispatched"
|
|
224
274
|
|
|
225
275
|
- id: publish
|
|
226
|
-
description: "
|
|
276
|
+
description: "Classify the sequenced surfdash → podcast outcomes full|partial|none into publish-status.txt (exit 0 in all three — routing is transition-owned; task 0118: per-target classification over the merged result.json)"
|
|
227
277
|
onEnter:
|
|
228
278
|
- kind: shell
|
|
229
279
|
options:
|
|
230
280
|
command: >-
|
|
231
|
-
|
|
232
|
-
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
281
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"publish\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
233
282
|
RESULT="${vars.work_dir}/3-publish/result.json"; STATUS_FILE="${vars.work_dir}/3-publish/publish-status.txt";
|
|
234
|
-
|
|
235
|
-
OK_COUNT=
|
|
236
|
-
|
|
237
|
-
if [
|
|
283
|
+
TARGETS="$(jq -s '[(.[0].targets // []), (.[1].targets // [])] | add' "${vars.work_dir}/3-publish/result-surfdash.json" "${vars.work_dir}/3-publish/result-podcast.json" 2>/dev/null || echo '[]')";
|
|
284
|
+
OK_COUNT="$(printf '%s' "$TARGETS" | jq '[.[]? | select(.ok == true and .status == "published")] | length')";
|
|
285
|
+
TOTAL="$(printf '%s' "$TARGETS" | jq 'length')";
|
|
286
|
+
if [ "$OK_COUNT" -gt 0 ] && [ "$OK_COUNT" -eq "$TOTAL" ]; then STATUS="full"; elif [ "$OK_COUNT" -gt 0 ]; then STATUS="partial"; else STATUS="none"; fi;
|
|
287
|
+
jq -n --argjson targets "$TARGETS" --argjson ok "$OK_COUNT" --argjson total "$TOTAL" '{ok: ($ok == $total and $total > 0), targets: $targets}' > "$RESULT";
|
|
238
288
|
printf '%s' "$STATUS" > "$STATUS_FILE";
|
|
239
289
|
echo "publish: status=$STATUS ($OK_COUNT/$TOTAL targets published); routing is transition-owned"
|
|
240
290
|
|
|
241
291
|
- id: publish-partial
|
|
242
|
-
description: "Partial publish — at least one target published, at least one failed; echo warning, write a .spur/run marker, preserve result.json for --retry-from, continue to done"
|
|
292
|
+
description: "Partial publish — at least one target published, at least one failed; echo warning, write a .spur/run marker, preserve result.json for --retry-from, continue to done (task 0118: surfdash failure = no-URL fallback notes; podcast failure = its own partial)"
|
|
243
293
|
onEnter:
|
|
244
294
|
- kind: shell
|
|
245
295
|
options:
|
|
246
296
|
command: >-
|
|
297
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"publish-partial\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
247
298
|
FAILED="$(jq -r '[.targets[]? | select(.ok != true or .status != "published") | .target] | join(",")' "${vars.work_dir}/3-publish/result.json")";
|
|
248
299
|
if [ -z "$FAILED" ]; then FAILED="(unparsed)"; fi;
|
|
249
300
|
echo "WARNING: partial publish — failed targets: $FAILED" >&2;
|
|
250
301
|
mkdir -p .spur/run &&
|
|
251
302
|
printf '%s partial publish: failed targets: %s (result.json preserved for kk executor fan-out --retry-from)\n' "${vars.run_date}" "$FAILED" > ".spur/run/publish-partial-${vars.run_date}.txt"
|
|
252
303
|
|
|
304
|
+
- id: run-report
|
|
305
|
+
description: "Assemble the per-run report (scores+aggregates+rejected counts+step timing) from existing artifacts — news-report-gen; output-only, never throws; 0117"
|
|
306
|
+
onEnter:
|
|
307
|
+
- kind: shell
|
|
308
|
+
options:
|
|
309
|
+
command: >-
|
|
310
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"run-report\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
311
|
+
REPORT_MD="${vars.work_dir}/run-report-${vars.run_date}.md";
|
|
312
|
+
if [ -f "$REPORT_MD" ]; then echo "run report cached: $REPORT_MD"; exit 0; fi;
|
|
313
|
+
CANDS="${vars.work_dir}/2-plan/episode-plan.json";
|
|
314
|
+
test -f "${vars.work_dir}/2-plan/candidates.json" || { echo "run-report: no candidates.json (run ended below plan step) — skip"; exit 0; };
|
|
315
|
+
jq '.metadata.docs // []' "${vars.work_dir}/2-plan/candidates.json" > "$CANDS";
|
|
316
|
+
set --;
|
|
317
|
+
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
318
|
+
QC_MIN_QUALITY="${vars.qc_min_quality}" QC_MIN_IMPORTANCE="${vars.qc_min_importance}" QC_MIN_URGENCY="${vars.qc_min_urgency}" QC_MIN_IMPACT="${vars.qc_min_impact}" NEWS_REPORT_REJECTED_FILE="${vars.work_dir}/2-plan/candidates.rejected.json" NEWS_REPORT_TIMING_FILE="${vars.work_dir}/step-timing.json" NEWS_REPORT_DATE="${vars.run_date}" kk executor run news-report-gen --in "$CANDS" --out "${vars.work_dir}/run-report.json" "$@" &&
|
|
319
|
+
bun -e 'await Bun.write(process.argv[2], (await Bun.file(process.argv[1]).json()).body)' "${vars.work_dir}/run-report.json" "$REPORT_MD" &&
|
|
320
|
+
echo "run report written: $REPORT_MD"
|
|
321
|
+
|
|
253
322
|
- id: translate-en
|
|
254
323
|
description: "Scaffold en draft from the zh article via surfdash-pub scaffold-locale (postPath from publish result; done-no-content/none-publish runs carry no zh path and skip)"
|
|
255
324
|
onEnter:
|
|
256
325
|
- kind: shell
|
|
257
326
|
options:
|
|
258
327
|
command: >-
|
|
328
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"translate-en\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
259
329
|
RESULT="${vars.work_dir}/3-publish/result.json";
|
|
260
330
|
ZH="$(jq -r '[(.targets // [])[] | select(.target == "surfdash-pub" and .ok == true) | .metadata.postPath // empty] | first // ""' "$RESULT")";
|
|
261
331
|
case "$ZH" in *index.md) ;; *) ZH="";; esac;
|
|
@@ -298,6 +368,7 @@ states:
|
|
|
298
368
|
- kind: shell
|
|
299
369
|
options:
|
|
300
370
|
command: >-
|
|
371
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"translate-ja\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
301
372
|
ZH="$(cat "${vars.work_dir}/3-publish/zh-post-path.txt" 2>/dev/null || printf '')";
|
|
302
373
|
JA="$(printf '%s' "$ZH" | sed 's|/zh/|/ja/|')";
|
|
303
374
|
printf '%s' "$JA" > "${vars.work_dir}/3-publish/ja-post-path.txt";
|
|
@@ -334,6 +405,7 @@ states:
|
|
|
334
405
|
- kind: shell
|
|
335
406
|
options:
|
|
336
407
|
command: >-
|
|
408
|
+
__STAMP_T0="$(date -u +%FT%TZ)"; trap '[ -n "${vars.work_dir}" ] && printf "{\"step\":\"translate-sync\",\"startedAt\":\"%s\",\"endedAt\":\"%s\"}\n" "$__STAMP_T0" "$(date -u +%FT%TZ)" >> "${vars.work_dir}/step-timing.json"' EXIT;
|
|
337
409
|
test -n "${vars.surfdash_post_path}" || { echo "no zh post path — skip translate-sync"; exit 0; };
|
|
338
410
|
set --;
|
|
339
411
|
if [ -n "${vars.plugins_path}" ]; then set -- --plugins-path "${vars.plugins_path}"; fi;
|
|
@@ -461,8 +533,8 @@ transitions:
|
|
|
461
533
|
kind: always
|
|
462
534
|
|
|
463
535
|
- from: quality-control
|
|
464
|
-
to:
|
|
465
|
-
description: "publish enabled AND QC passed ->
|
|
536
|
+
to: publish-prep
|
|
537
|
+
description: "publish enabled AND QC passed -> merge article + audio for the publish seam"
|
|
466
538
|
guard:
|
|
467
539
|
kind: shell
|
|
468
540
|
options:
|
|
@@ -482,21 +554,33 @@ transitions:
|
|
|
482
554
|
guard:
|
|
483
555
|
kind: always
|
|
484
556
|
|
|
557
|
+
- from: publish-prep
|
|
558
|
+
to: publish-surfdash
|
|
559
|
+
description: "Merged content ready -> surfdash publishes first (task 0118 seam reorder: article URLs must exist before show-notes render)"
|
|
560
|
+
guard:
|
|
561
|
+
kind: always
|
|
562
|
+
|
|
563
|
+
- from: publish-surfdash
|
|
564
|
+
to: show-notes
|
|
565
|
+
description: "Surfdash dispatched (ok or failed — failure degrades to no-URL notes) -> render timeline show notes"
|
|
566
|
+
guard:
|
|
567
|
+
kind: always
|
|
568
|
+
|
|
485
569
|
- from: show-notes
|
|
486
|
-
to: publish-
|
|
487
|
-
description: "Show notes rendered
|
|
570
|
+
to: publish-podcast
|
|
571
|
+
description: "Show notes rendered (briefMode timeline or legacy fallback) -> podcast publish carries them via metadata.showNotes"
|
|
488
572
|
guard:
|
|
489
573
|
kind: always
|
|
490
574
|
|
|
491
|
-
- from: publish-
|
|
575
|
+
- from: publish-podcast
|
|
492
576
|
to: publish
|
|
493
|
-
description: "
|
|
577
|
+
description: "Podcast dispatched -> classify the per-target outcomes full|partial|none"
|
|
494
578
|
guard:
|
|
495
579
|
kind: always
|
|
496
580
|
|
|
497
581
|
- from: publish
|
|
498
|
-
to:
|
|
499
|
-
description: "Publish full (all targets published) ->
|
|
582
|
+
to: run-report
|
|
583
|
+
description: "Publish full (all targets published) -> assemble run report first"
|
|
500
584
|
guard:
|
|
501
585
|
kind: shell
|
|
502
586
|
options:
|
|
@@ -519,11 +603,27 @@ transitions:
|
|
|
519
603
|
command: 'test "$(cat "${vars.work_dir}/3-publish/publish-status.txt")" = "none"'
|
|
520
604
|
|
|
521
605
|
- from: publish-partial
|
|
522
|
-
to:
|
|
523
|
-
description: "Partial publish recorded (warning + .spur/run marker) ->
|
|
606
|
+
to: run-report
|
|
607
|
+
description: "Partial publish recorded (warning + .spur/run marker) -> still assemble the run report"
|
|
524
608
|
guard:
|
|
525
609
|
kind: always
|
|
526
610
|
|
|
611
|
+
- from: run-report
|
|
612
|
+
to: translate-en
|
|
613
|
+
description: "Run report assembled after full publish -> scaffold + translate en locale"
|
|
614
|
+
guard:
|
|
615
|
+
kind: shell
|
|
616
|
+
options:
|
|
617
|
+
command: 'test "$(cat "${vars.work_dir}/3-publish/publish-status.txt")" = "full"'
|
|
618
|
+
|
|
619
|
+
- from: run-report
|
|
620
|
+
to: done
|
|
621
|
+
description: "Run report assembled after partial publish -> done; retry failed targets later via kk executor fan-out --retry-from"
|
|
622
|
+
guard:
|
|
623
|
+
kind: shell
|
|
624
|
+
options:
|
|
625
|
+
command: 'test "$(cat "${vars.work_dir}/3-publish/publish-status.txt")" = "partial"'
|
|
626
|
+
|
|
527
627
|
- from: translate-en
|
|
528
628
|
to: translate-en-agent
|
|
529
629
|
description: "zh post published and en draft scaffolded -> agent translates en draft in place"
|