@gobing-ai/knowledge-kit 0.0.13 → 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 +20 -0
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +20 -0
- package/plugins/generations/core-facts-gen/dist/index.js +20 -0
- package/plugins/generations/daily-article-gen/dist/index.js +29 -1
- package/plugins/generations/daily-article-gen/src/index.ts +13 -1
- package/plugins/generations/dailynews-gen/dist/index.js +20 -0
- package/plugins/generations/episode-plan-gen/dist/index.js +24 -0
- package/plugins/generations/episode-plan-gen/src/index.ts +11 -0
- package/plugins/generations/image-gen/dist/index.js +2296 -53
- 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 +1 -1
- package/plugins/generations/image-gen/src/providers/types.ts +52 -51
- package/plugins/generations/image-gen/src/providers/zai.ts +237 -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 +30 -2
- 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 +20 -0
- package/plugins/ingestions/horizon-ingest/dist/index.js +20 -0
- package/plugins/ingestions/last30days-ingest/dist/index.js +20 -0
- package/plugins/ingestions/web-search/dist/index.js +20 -0
- package/plugins/kk/commands/image-extract.md +40 -0
- package/plugins/kk/commands/image-generate.md +18 -1
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/skills/image-authoring/SKILL.md +10 -3
- package/plugins/kk/skills/image-authoring/references/format-drafting.md +57 -0
- package/plugins/kk/skills/image-authoring/references/style-extraction.md +14 -9
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +130 -30
- package/plugins/publishings/emdash-pub/dist/index.js +20 -0
- package/plugins/publishings/podcast-pub/dist/index.js +57 -3
- 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 +20 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +96 -6
- package/plugins/publishings/surfdash-pub/src/index.ts +109 -9
- package/plugins/publishings/zenn-pub/dist/index.js +20 -0
- 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
|
@@ -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"
|
|
@@ -21848,6 +21848,26 @@ var ResultSchema = exports_external.object({
|
|
|
21848
21848
|
metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
21849
21849
|
});
|
|
21850
21850
|
var DocListSchema = exports_external.array(DocSchema);
|
|
21851
|
+
var omniVoiceGenContentSchema = ContentSchema.extend({
|
|
21852
|
+
format: exports_external.literal("audio"),
|
|
21853
|
+
metadata: exports_external.looseObject({
|
|
21854
|
+
generator: exports_external.literal("kk:omni-voice-gen"),
|
|
21855
|
+
audioPath: exports_external.string(),
|
|
21856
|
+
duration: exports_external.number(),
|
|
21857
|
+
segments: exports_external.array(exports_external.looseObject({
|
|
21858
|
+
generationId: exports_external.string(),
|
|
21859
|
+
profile: exports_external.string(),
|
|
21860
|
+
speaker: exports_external.string().optional(),
|
|
21861
|
+
emotion: exports_external.string().optional(),
|
|
21862
|
+
duration: exports_external.number(),
|
|
21863
|
+
verifyRetries: exports_external.number(),
|
|
21864
|
+
transcription: exports_external.string().optional(),
|
|
21865
|
+
loudnessDip: exports_external.boolean().optional()
|
|
21866
|
+
})),
|
|
21867
|
+
mp3Path: exports_external.string().optional(),
|
|
21868
|
+
qc: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
21869
|
+
})
|
|
21870
|
+
});
|
|
21851
21871
|
|
|
21852
21872
|
// ../../packages/kk-core/src/kinds.ts
|
|
21853
21873
|
var PLUGIN_KINDS = ["ingestion", "generator", "publish"];
|
|
@@ -21849,6 +21849,26 @@ var ResultSchema = exports_external.object({
|
|
|
21849
21849
|
metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
21850
21850
|
});
|
|
21851
21851
|
var DocListSchema = exports_external.array(DocSchema);
|
|
21852
|
+
var omniVoiceGenContentSchema = ContentSchema.extend({
|
|
21853
|
+
format: exports_external.literal("audio"),
|
|
21854
|
+
metadata: exports_external.looseObject({
|
|
21855
|
+
generator: exports_external.literal("kk:omni-voice-gen"),
|
|
21856
|
+
audioPath: exports_external.string(),
|
|
21857
|
+
duration: exports_external.number(),
|
|
21858
|
+
segments: exports_external.array(exports_external.looseObject({
|
|
21859
|
+
generationId: exports_external.string(),
|
|
21860
|
+
profile: exports_external.string(),
|
|
21861
|
+
speaker: exports_external.string().optional(),
|
|
21862
|
+
emotion: exports_external.string().optional(),
|
|
21863
|
+
duration: exports_external.number(),
|
|
21864
|
+
verifyRetries: exports_external.number(),
|
|
21865
|
+
transcription: exports_external.string().optional(),
|
|
21866
|
+
loudnessDip: exports_external.boolean().optional()
|
|
21867
|
+
})),
|
|
21868
|
+
mp3Path: exports_external.string().optional(),
|
|
21869
|
+
qc: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
21870
|
+
})
|
|
21871
|
+
});
|
|
21852
21872
|
|
|
21853
21873
|
// ../../packages/kk-core/src/kinds.ts
|
|
21854
21874
|
var PLUGIN_KINDS = ["ingestion", "generator", "publish"];
|
|
@@ -21962,7 +21982,12 @@ function renderShowNotesMarkdown(docs, opts) {
|
|
|
21962
21982
|
const s = t.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/[\s_]+/g, "-");
|
|
21963
21983
|
return s.length > 0 ? s : "post";
|
|
21964
21984
|
};
|
|
21965
|
-
const
|
|
21985
|
+
const baseUrl = opts.articleBaseUrl ?? "https://surfing.salty.vip";
|
|
21986
|
+
const segments = opts.articlePostPath?.split(/[\\/]/).filter((s) => s.length > 0) ?? [];
|
|
21987
|
+
const postSlug = segments.length >= 2 ? segments[segments.length - 2] ?? "" : "";
|
|
21988
|
+
const postLocale = segments.find((s) => ["zh", "en", "ja"].includes(s)) ?? "zh";
|
|
21989
|
+
const articleUrl = postSlug.length > 0 ? `${baseUrl}/${postLocale}/posts/${postSlug}` : `${baseUrl}/zh/posts/${opts.runDate}-${slugify2(opts.title)}`;
|
|
21990
|
+
const briefMode = opts.briefMode === true && postSlug.length > 0;
|
|
21966
21991
|
const sourceLabel = (d) => {
|
|
21967
21992
|
const m = d.metadata ?? {};
|
|
21968
21993
|
if (typeof m.sourceName === "string" && m.sourceName.trim())
|
|
@@ -21973,11 +21998,31 @@ function renderShowNotesMarkdown(docs, opts) {
|
|
|
21973
21998
|
return "\u6765\u6E90";
|
|
21974
21999
|
}
|
|
21975
22000
|
};
|
|
22001
|
+
const itemBrief = (d) => {
|
|
22002
|
+
const meta3 = d.metadata ?? {};
|
|
22003
|
+
if (typeof meta3.brief === "string" && meta3.brief.trim().length > 0)
|
|
22004
|
+
return meta3.brief.trim();
|
|
22005
|
+
return (d.body ?? "").trim().split(/(?<=[\u3002\uFF01\uFF1F])\s*/).slice(0, 2).join("");
|
|
22006
|
+
};
|
|
22007
|
+
const itemTimestamp = (d) => {
|
|
22008
|
+
const meta3 = d.metadata ?? {};
|
|
22009
|
+
const raw = meta3.publishAt;
|
|
22010
|
+
const t = typeof raw === "string" ? Date.parse(raw) : typeof raw === "number" ? raw : Number.NaN;
|
|
22011
|
+
if (!Number.isFinite(t))
|
|
22012
|
+
return "--:--";
|
|
22013
|
+
const at = new Date(t);
|
|
22014
|
+
return `${String(at.getHours()).padStart(2, "0")}:${String(at.getMinutes()).padStart(2, "0")}`;
|
|
22015
|
+
};
|
|
21976
22016
|
const lines = [`# ${opts.title}`, ""];
|
|
21977
22017
|
docs.forEach((d, i) => {
|
|
21978
22018
|
const body = (d.body ?? "").trim();
|
|
21979
22019
|
const sentences = body.split(/(?<=[\u3002\uFF01\uFF1F])\s*/).slice(0, 2).join("");
|
|
21980
|
-
lines.push(`## ${i + 1}. ${d.title ?? `\u6761\u76EE ${i + 1}`}`, ""
|
|
22020
|
+
lines.push(`## ${i + 1}. ${d.title ?? `\u6761\u76EE ${i + 1}`}`, "");
|
|
22021
|
+
if (briefMode) {
|
|
22022
|
+
lines.push(`${itemTimestamp(d)} \xB7 ${itemBrief(d)}`, "", `[\u6765\u6E90\uFF1A${sourceLabel(d)}](${d.sourceUri ?? ""}) \xB7 [\u6587\u7AE0](${articleUrl})`, "");
|
|
22023
|
+
} else {
|
|
22024
|
+
lines.push(sentences, "", `[\u6765\u6E90\uFF1A${sourceLabel(d)}](${d.sourceUri ?? ""})`, "");
|
|
22025
|
+
}
|
|
21981
22026
|
});
|
|
21982
22027
|
lines.push(`\u8BE6\u60C5\u89C1\u672C\u671F\u5B8C\u6574\u6587\u7AE0\uFF1A[ ${opts.title} ](${articleUrl})`, "");
|
|
21983
22028
|
return lines.join(`
|
|
@@ -22262,8 +22307,17 @@ function resolveShowNotesRenderOptions(options) {
|
|
|
22262
22307
|
return { error: 'options.runDate (or PODCAST_PUB_RUN_DATE) is required for the "show-notes" operation' };
|
|
22263
22308
|
}
|
|
22264
22309
|
const articleBaseUrl = resolveOptionString(options.articleBaseUrl, "PODCAST_PUB_ARTICLE_BASE_URL");
|
|
22310
|
+
const briefModeRaw = options.briefMode ?? process.env.PODCAST_PUB_BRIEF_MODE;
|
|
22311
|
+
const briefMode = briefModeRaw === true || briefModeRaw === "true" || briefModeRaw === "1";
|
|
22312
|
+
const articlePostPath = resolveOptionString(options.articlePostPath, "PODCAST_PUB_ARTICLE_POST_PATH");
|
|
22265
22313
|
return {
|
|
22266
|
-
renderOptions: {
|
|
22314
|
+
renderOptions: {
|
|
22315
|
+
title,
|
|
22316
|
+
runDate,
|
|
22317
|
+
...articleBaseUrl !== undefined ? { articleBaseUrl } : {},
|
|
22318
|
+
...briefMode ? { briefMode: true } : {},
|
|
22319
|
+
...articlePostPath !== undefined ? { articlePostPath } : {}
|
|
22320
|
+
}
|
|
22267
22321
|
};
|
|
22268
22322
|
}
|
|
22269
22323
|
function renderShowNotesFromOptions(options) {
|
|
@@ -28,6 +28,10 @@ interface PodcastPubOptions {
|
|
|
28
28
|
title?: unknown;
|
|
29
29
|
runDate?: unknown;
|
|
30
30
|
articleBaseUrl?: unknown;
|
|
31
|
+
/** Brief/timeline render flag (task 0118). */
|
|
32
|
+
briefMode?: unknown;
|
|
33
|
+
/** Surfdash publish result `metadata.postPath` (briefMode URL source). */
|
|
34
|
+
articlePostPath?: unknown;
|
|
31
35
|
/** Episode-assembly options for the `publish` op (absorb publish-prep's merge; additive). */
|
|
32
36
|
audioFile?: unknown;
|
|
33
37
|
coverPath?: unknown;
|
|
@@ -76,7 +80,10 @@ function resolveItemId(options: PodcastPubOptions): { itemId: string } | { error
|
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
/** Options the `show-notes` operation reads on the payload (a subset of {@link PodcastPubOptions}). */
|
|
79
|
-
type ShowNotesOpOptions = Pick<
|
|
83
|
+
type ShowNotesOpOptions = Pick<
|
|
84
|
+
PodcastPubOptions,
|
|
85
|
+
'docs' | 'title' | 'runDate' | 'articleBaseUrl' | 'briefMode' | 'articlePostPath'
|
|
86
|
+
>;
|
|
80
87
|
|
|
81
88
|
/**
|
|
82
89
|
* Option-then-env resolution for the show-notes render options: `kk executor run`
|
|
@@ -103,8 +110,17 @@ function resolveShowNotesRenderOptions(
|
|
|
103
110
|
return { error: 'options.runDate (or PODCAST_PUB_RUN_DATE) is required for the "show-notes" operation' };
|
|
104
111
|
}
|
|
105
112
|
const articleBaseUrl = resolveOptionString(options.articleBaseUrl, 'PODCAST_PUB_ARTICLE_BASE_URL');
|
|
113
|
+
const briefModeRaw = options.briefMode ?? process.env.PODCAST_PUB_BRIEF_MODE;
|
|
114
|
+
const briefMode = briefModeRaw === true || briefModeRaw === 'true' || briefModeRaw === '1';
|
|
115
|
+
const articlePostPath = resolveOptionString(options.articlePostPath, 'PODCAST_PUB_ARTICLE_POST_PATH');
|
|
106
116
|
return {
|
|
107
|
-
renderOptions: {
|
|
117
|
+
renderOptions: {
|
|
118
|
+
title,
|
|
119
|
+
runDate,
|
|
120
|
+
...(articleBaseUrl !== undefined ? { articleBaseUrl } : {}),
|
|
121
|
+
...(briefMode ? { briefMode: true } : {}),
|
|
122
|
+
...(articlePostPath !== undefined ? { articlePostPath } : {}),
|
|
123
|
+
},
|
|
108
124
|
};
|
|
109
125
|
}
|
|
110
126
|
|
|
@@ -82,6 +82,20 @@ export interface ShowNotesRenderOptions {
|
|
|
82
82
|
runDate: string;
|
|
83
83
|
/** Article base URL; defaults to the production site (surfing.salty.vip). */
|
|
84
84
|
articleBaseUrl?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Brief/timeline mode (task 0118): per-item 1–2-sentence briefing + `HH:MM`
|
|
87
|
+
* timestamp + per-item article link. When no surfdash article path is
|
|
88
|
+
* available the render degrades to the legacy full-body layout — never
|
|
89
|
+
* throws, never invents a URL.
|
|
90
|
+
*/
|
|
91
|
+
briefMode?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Absolute surfdash `metadata.postPath` from the publish result
|
|
94
|
+
* (`content/posts/articles/<locale>/<slug>/index.md`); the article URL is
|
|
95
|
+
* derived O(1) from its slug+locale — authoritative over the title/runDate
|
|
96
|
+
* slug construction, which stays the fallback.
|
|
97
|
+
*/
|
|
98
|
+
articlePostPath?: string;
|
|
85
99
|
}
|
|
86
100
|
|
|
87
101
|
/**
|
|
@@ -101,7 +115,18 @@ export function renderShowNotesMarkdown(docs: Doc[], opts: ShowNotesRenderOption
|
|
|
101
115
|
.replace(/[\s_]+/g, '-');
|
|
102
116
|
return s.length > 0 ? s : 'post';
|
|
103
117
|
};
|
|
104
|
-
const
|
|
118
|
+
const baseUrl = opts.articleBaseUrl ?? 'https://surfing.salty.vip';
|
|
119
|
+
// O(1) postPath derivation (task 0118): the published slug + locale come from
|
|
120
|
+
// the real path, not from re-slugging the title. Falls back to the legacy
|
|
121
|
+
// title/runDate construction when no postPath exists (surfdash failed).
|
|
122
|
+
const segments = opts.articlePostPath?.split(/[\\/]/).filter((s) => s.length > 0) ?? [];
|
|
123
|
+
const postSlug = segments.length >= 2 ? (segments[segments.length - 2] ?? '') : '';
|
|
124
|
+
const postLocale = segments.find((s) => ['zh', 'en', 'ja'].includes(s)) ?? 'zh';
|
|
125
|
+
const articleUrl =
|
|
126
|
+
postSlug.length > 0
|
|
127
|
+
? `${baseUrl}/${postLocale}/posts/${postSlug}`
|
|
128
|
+
: `${baseUrl}/zh/posts/${opts.runDate}-${slugify(opts.title)}`;
|
|
129
|
+
const briefMode = opts.briefMode === true && postSlug.length > 0;
|
|
105
130
|
const sourceLabel = (d: Doc): string => {
|
|
106
131
|
const m = d.metadata ?? {};
|
|
107
132
|
if (typeof m.sourceName === 'string' && m.sourceName.trim()) return m.sourceName.trim();
|
|
@@ -111,6 +136,23 @@ export function renderShowNotesMarkdown(docs: Doc[], opts: ShowNotesRenderOption
|
|
|
111
136
|
return '来源';
|
|
112
137
|
}
|
|
113
138
|
};
|
|
139
|
+
const itemBrief = (d: Doc): string => {
|
|
140
|
+
const meta = d.metadata ?? {};
|
|
141
|
+
if (typeof meta.brief === 'string' && meta.brief.trim().length > 0) return meta.brief.trim();
|
|
142
|
+
return (d.body ?? '')
|
|
143
|
+
.trim()
|
|
144
|
+
.split(/(?<=[。!?])\s*/)
|
|
145
|
+
.slice(0, 2)
|
|
146
|
+
.join('');
|
|
147
|
+
};
|
|
148
|
+
const itemTimestamp = (d: Doc): string => {
|
|
149
|
+
const meta = d.metadata ?? {};
|
|
150
|
+
const raw = meta.publishAt;
|
|
151
|
+
const t = typeof raw === 'string' ? Date.parse(raw) : typeof raw === 'number' ? raw : Number.NaN;
|
|
152
|
+
if (!Number.isFinite(t)) return '--:--';
|
|
153
|
+
const at = new Date(t);
|
|
154
|
+
return `${String(at.getHours()).padStart(2, '0')}:${String(at.getMinutes()).padStart(2, '0')}`;
|
|
155
|
+
};
|
|
114
156
|
const lines: string[] = [`# ${opts.title}`, ''];
|
|
115
157
|
docs.forEach((d, i) => {
|
|
116
158
|
const body = (d.body ?? '').trim();
|
|
@@ -118,14 +160,19 @@ export function renderShowNotesMarkdown(docs: Doc[], opts: ShowNotesRenderOption
|
|
|
118
160
|
.split(/(?<=[。!?])\s*/)
|
|
119
161
|
.slice(0, 2)
|
|
120
162
|
.join('');
|
|
121
|
-
lines.push(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
163
|
+
lines.push(`## ${i + 1}. ${d.title ?? `条目 ${i + 1}`}`, '');
|
|
164
|
+
if (briefMode) {
|
|
165
|
+
// Timeline entry: HH:MM + 1–2-sentence briefing; the trailing link pair
|
|
166
|
+
// ends with the surfdash article URL (task 0118 R2.1–R2.4).
|
|
167
|
+
lines.push(
|
|
168
|
+
`${itemTimestamp(d)} · ${itemBrief(d)}`,
|
|
169
|
+
'',
|
|
170
|
+
`[来源:${sourceLabel(d)}](${d.sourceUri ?? ''}) · [文章](${articleUrl})`,
|
|
171
|
+
'',
|
|
172
|
+
);
|
|
173
|
+
} else {
|
|
174
|
+
lines.push(sentences, '', `[来源:${sourceLabel(d)}](${d.sourceUri ?? ''})`, '');
|
|
175
|
+
}
|
|
129
176
|
});
|
|
130
177
|
lines.push(`详情见本期完整文章:[ ${opts.title} ](${articleUrl})`, '');
|
|
131
178
|
return lines.join('\n');
|