@gobing-ai/knowledge-kit 0.0.34 → 0.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +1 -1
  3. package/package.json +1 -1
  4. package/plugins/generations/daily-article-gen/dist/index.js +17 -3
  5. package/plugins/generations/daily-article-gen/src/index.ts +32 -3
  6. package/plugins/generations/dailynews-gen/dist/index.js +2 -1
  7. package/plugins/generations/dailynews-gen/src/script-builder.ts +6 -1
  8. package/plugins/generations/gemini-tts/dist/index.js +4 -3
  9. package/plugins/generations/omni-voice-gen/plugin.json +1 -1
  10. package/plugins/generations/voice-gen/dist/index.js +4 -3
  11. package/plugins/generations/voice-gen/src/index.ts +9 -3
  12. package/plugins/kk/plugin.json +1 -1
  13. package/plugins/kk/scripts/daily-stages.ts +296 -39
  14. package/plugins/kk/scripts/kk-workflow-stages.ts +40 -0
  15. package/plugins/kk/skills/content-safety/references/platforms/wechatmp.md +2 -2
  16. package/plugins/kk/skills/content-safety/references/platforms/xiaoyuzhou.md +4 -2
  17. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +32 -7
  18. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +7 -4
  19. package/plugins/kk/workflows/kk-daily-safety.yaml +1 -1
  20. package/plugins/publishings/infoq-pub/dist/profile-check.js +30336 -0
  21. package/plugins/publishings/infoq-pub/plugin.json +1 -1
  22. package/plugins/publishings/juejin-pub/dist/profile-check.js +30322 -0
  23. package/plugins/publishings/juejin-pub/plugin.json +1 -1
  24. package/plugins/publishings/substack-pub/dist/profile-check.js +30392 -0
  25. package/plugins/publishings/substack-pub/plugin.json +1 -1
  26. package/plugins/publishings/wechatmp-pub/dist/index.js +307 -132
  27. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +30271 -0
  28. package/plugins/publishings/wechatmp-pub/plugin.json +1 -1
  29. package/plugins/publishings/wechatmp-pub/src/enrich.ts +106 -145
  30. package/plugins/publishings/wechatmp-pub/src/flow.ts +283 -42
  31. package/plugins/publishings/wechatmp-pub/src/page.ts +20 -0
  32. package/plugins/publishings/x-pub/dist/profile-check.js +30328 -0
  33. package/plugins/publishings/x-pub/plugin.json +1 -1
  34. package/plugins/publishings/xhs-pub/dist/profile-check.js +30538 -0
  35. package/plugins/publishings/xhs-pub/plugin.json +1 -1
  36. package/plugins/generations/omni-voice-gen/dist/omni-voice-gen-prr8skpb. +0 -2
  37. package/plugins/generations/omni-voice-gen/dist/omni-voice-gen.js +0 -6
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "plugins": [
8
8
  {
9
- "version": "0.0.34",
9
+ "version": "0.0.35",
10
10
  "name": "kk",
11
11
  "source": "./plugins/kk",
12
12
  "description": "knowledge-kit — a set of toolkit for knowledge management"
package/dist/index.js CHANGED
@@ -43509,7 +43509,7 @@ function probeVerdict(exitCode, stdout) {
43509
43509
  }
43510
43510
  const fields = typeof parsed === "object" && parsed !== null ? parsed : undefined;
43511
43511
  const status = typeof fields?.status === "string" ? fields.status : undefined;
43512
- if ((status === "verified" || status === "refused") && (exitCode === 0 || exitCode === 1)) {
43512
+ if (status === "verified" && exitCode === 0 || status === "refused" && exitCode === 1) {
43513
43513
  return {
43514
43514
  status,
43515
43515
  ...typeof fields?.profileDir === "string" ? { profileDir: fields.profileDir } : {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/knowledge-kit",
3
- "version": "0.0.34",
3
+ "version": "0.0.35",
4
4
  "description": "An ingest → create → publish content pipeline CLI (Bun).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22062,6 +22062,10 @@ function tagsFor(docs) {
22062
22062
  var LEADING_H1_RE = /^#(?!#)\s*[^\n]+\n+/.source;
22063
22063
  var NOTE_EXCLUDED_RE = /\u672A\u5199\u5165[\uFF1A:](.*)/g;
22064
22064
  var HEX_ID_RE = /(?:[a-zA-Z0-9_-]+:)?[0-9a-f]{16,64}/g;
22065
+ var BOUNDARY_NOTE_RE = /^>[^\n]*(?:\u9009\u9898\u8FB9\u754C|\u672A\u5199\u5165)[^\n]*\n?/gm;
22066
+ function stripBoundaryNote(body) {
22067
+ return body.replace(BOUNDARY_NOTE_RE, "");
22068
+ }
22065
22069
  function excludedIdsFromNote(body) {
22066
22070
  const ids = new Set;
22067
22071
  if (!body)
@@ -22080,18 +22084,27 @@ function docsToContent(docs, date6, bodyOverride) {
22080
22084
  const title = episodeTitle(date6);
22081
22085
  let body = renderDailyArticle(docs, date6);
22082
22086
  if (bodyOverride) {
22083
- const stripped = stripScaffold(bodyOverride.trim()).trim().replace(new RegExp(LEADING_H1_RE), "");
22087
+ const cleaned = stripBoundaryNote(stripScaffold(bodyOverride.trim()).trim().replace(new RegExp(LEADING_H1_RE), "")).trim();
22084
22088
  body = `# ${title}
22085
22089
 
22086
- ${stripped}
22090
+ ${cleaned}
22087
22091
  `;
22092
+ } else {
22093
+ body = stripBoundaryNote(body);
22088
22094
  }
22095
+ const excludedIds = [...excludedIdsFromNote(bodyOverride)];
22089
22096
  return ContentSchema.parse({
22090
22097
  title,
22091
22098
  body,
22092
22099
  format: "markdown",
22093
22100
  references: docs.filter((d) => d.sourceUri).map((d) => ({ url: d.sourceUri, title: d.title, cite: d.id })),
22094
- metadata: { generator: "kk:daily-article", date: date6, doc_count: docs.length, tags: tagsFor(docs) }
22101
+ metadata: {
22102
+ generator: "kk:daily-article",
22103
+ date: date6,
22104
+ doc_count: docs.length,
22105
+ tags: tagsFor(docs),
22106
+ ...excludedIds.length > 0 ? { excludedIds } : {}
22107
+ }
22095
22108
  });
22096
22109
  }
22097
22110
  async function processGeneratorIO(inputPath, outputPath) {
@@ -22140,6 +22153,7 @@ if (import.meta.main) {
22140
22153
  }
22141
22154
  export {
22142
22155
  stripScaffold,
22156
+ stripBoundaryNote,
22143
22157
  renderDailyArticle,
22144
22158
  processGeneratorIO,
22145
22159
  main2 as main,
@@ -141,6 +141,20 @@ const LEADING_H1_RE = /^#(?!#)\s*[^\n]+\n+/.source;
141
141
  const NOTE_EXCLUDED_RE = /未写入[::](.*)/g;
142
142
  const HEX_ID_RE = /(?:[a-zA-Z0-9_-]+:)?[0-9a-f]{16,64}/g;
143
143
 
144
+ /**
145
+ * The 选题边界 note is pipeline bookkeeping: it names dropped doc ids and the rule that dropped them.
146
+ * It is the machine-readable exclusion signal (`excludedIdsFromNote`), so it stays in the agent's body
147
+ * file — but it must never reach a published body, where it printed an internal doc id and pipeline
148
+ * vocabulary to readers (operator report 2026-10-10). Matches only blockquote lines, which the article
149
+ * body reserves for exactly this note, so ordinary prose is untouched.
150
+ */
151
+ const BOUNDARY_NOTE_RE = /^>[^\n]*(?:选题边界|未写入)[^\n]*\n?/gm;
152
+
153
+ /** Strip the internal boundary note from a body destined for a customer-visible surface. */
154
+ export function stripBoundaryNote(body: string): string {
155
+ return body.replace(BOUNDARY_NOTE_RE, '');
156
+ }
157
+
144
158
  /** Parse the boundary-note ids out of an article body so references/metadata never cite excluded docs (judge finding 2026-10-02). */
145
159
  export function excludedIdsFromNote(body: string | undefined): Set<string> {
146
160
  const ids = new Set<string>();
@@ -162,15 +176,30 @@ export function docsToContent(docs: Doc[], date: string, bodyOverride?: string):
162
176
  if (bodyOverride) {
163
177
  // The canonical episode title is computed from the lead item; a leading H1 the agent may
164
178
  // still have written is replaced (not duplicated) so body and metadata agree on every path.
165
- const stripped = stripScaffold(bodyOverride.trim()).trim().replace(new RegExp(LEADING_H1_RE), '');
166
- body = `# ${title}\n\n${stripped}\n`;
179
+ // The boundary note is stripped here, not dropped upstream: the agent's body file keeps it as
180
+ // the exclusion signal, this Content is what readers see.
181
+ const cleaned = stripBoundaryNote(
182
+ stripScaffold(bodyOverride.trim()).trim().replace(new RegExp(LEADING_H1_RE), ''),
183
+ ).trim();
184
+ body = `# ${title}\n\n${cleaned}\n`;
185
+ } else {
186
+ body = stripBoundaryNote(body);
167
187
  }
188
+ // The ids the note named become metadata, so consumers that must filter excluded docs (show-notes
189
+ // plan copies) read a field instead of re-parsing a note that no longer exists.
190
+ const excludedIds = [...excludedIdsFromNote(bodyOverride)];
168
191
  return ContentSchema.parse({
169
192
  title,
170
193
  body,
171
194
  format: 'markdown',
172
195
  references: docs.filter((d) => d.sourceUri).map((d) => ({ url: d.sourceUri, title: d.title, cite: d.id })),
173
- metadata: { generator: 'kk:daily-article', date, doc_count: docs.length, tags: tagsFor(docs) },
196
+ metadata: {
197
+ generator: 'kk:daily-article',
198
+ date,
199
+ doc_count: docs.length,
200
+ tags: tagsFor(docs),
201
+ ...(excludedIds.length > 0 ? { excludedIds } : {}),
202
+ },
174
203
  });
175
204
  }
176
205
 
@@ -22116,6 +22116,7 @@ function buildNewsVoiceScript(docs, options) {
22116
22116
  const defaultEffectPreset = options?.effectPreset;
22117
22117
  const broadcastDate = options?.date ?? new Date;
22118
22118
  const dateStr = formatBroadcastDate(broadcastDate, language);
22119
+ const episodeDate = `${broadcastDate.getFullYear()}-${String(broadcastDate.getMonth() + 1).padStart(2, "0")}-${String(broadcastDate.getDate()).padStart(2, "0")}`;
22119
22120
  const showName = "\u54B8\u8BDD\u54B8\u8BF4\u4E4B\u6BCF\u65E5AI\u4E71\u7096";
22120
22121
  let hostName = options?.speakers?.host?.name || voiceProfile.replace(/[-_]?(news|voice|bot|profile)$/i, "") || voiceProfile;
22121
22122
  if (hostName.length > 0) {
@@ -22157,7 +22158,7 @@ function buildNewsVoiceScript(docs, options) {
22157
22158
  segments: segments2
22158
22159
  });
22159
22160
  }
22160
- const title = options?.title || (isZh ? `\u6BCF\u65E5AI\u4E71\u7096\uFF08\u5171${docs.length}\u6761\uFF09` : `\u6BCF\u65E5AI\u4E71\u7096 (${docs.length} stories)`);
22161
+ const title = options?.title || `\u6BCF\u65E5AI\u4E71\u7096 \xB7 ${episodeDate}`;
22161
22162
  const segments = [];
22162
22163
  const scriptPlan = isZh ? options?.scriptPlan : undefined;
22163
22164
  const planItems = new Map((scriptPlan?.items ?? []).map((item) => [item.id, item]));
@@ -313,6 +313,11 @@ export function buildNewsVoiceScript(docs: Doc[], options?: ScriptBuilderOptions
313
313
  const defaultEffectPreset = options?.effectPreset;
314
314
  const broadcastDate = options?.date ?? new Date();
315
315
  const dateStr = formatBroadcastDate(broadcastDate, language);
316
+ // Canonical episode identifier: date-only, matching daily-article-gen's `episodeTitle`. One
317
+ // title has to cover the post body, ID3, RSS and the show notes, so the voicescript default
318
+ // must not invent its own form (it used to be `共N条`, which disagreed with the article title
319
+ // on every path where the workflow passes no explicit title).
320
+ const episodeDate = `${broadcastDate.getFullYear()}-${String(broadcastDate.getMonth() + 1).padStart(2, '0')}-${String(broadcastDate.getDate()).padStart(2, '0')}`;
316
321
  // Column of 咸话咸说. The show is not the speaker; the host is.
317
322
  const showName = '咸话咸说之每日AI乱炖';
318
323
  let hostName =
@@ -365,7 +370,7 @@ export function buildNewsVoiceScript(docs: Doc[], options?: ScriptBuilderOptions
365
370
  });
366
371
  }
367
372
 
368
- const title = options?.title || (isZh ? `每日AI乱炖(共${docs.length}条)` : `每日AI乱炖 (${docs.length} stories)`);
373
+ const title = options?.title || `每日AI乱炖 · ${episodeDate}`;
369
374
  const segments: VoiceScriptSegment[] = [];
370
375
 
371
376
  // F613: the plan owns the words on the zh path; en keeps templates (design: substitution is
@@ -23041,7 +23041,8 @@ async function processGeneratorIO(inputPath, outputPath, clientOverride, transco
23041
23041
  let loudnessDip = false;
23042
23042
  let lastError;
23043
23043
  let attemptBody = generateBody;
23044
- for (let verifyAttempt = 0;verifyAttempt <= 2 && wavBytes === undefined; verifyAttempt++) {
23044
+ const maxVerifyAttempts = Number.parseInt(process.env.VOICEBOX_MAX_VERIFY_ATTEMPTS ?? "2", 10) || 0;
23045
+ for (let verifyAttempt = 0;verifyAttempt <= maxVerifyAttempts && wavBytes === undefined; verifyAttempt++) {
23045
23046
  if (verifyAttempt > 0) {
23046
23047
  verifyRetries = verifyAttempt;
23047
23048
  const chunkChars = Math.max(VOICEBOX_CHUNK_CHARS_MIN, Math.floor(attemptBody.max_chunk_chars / 2));
@@ -23079,7 +23080,7 @@ async function processGeneratorIO(inputPath, outputPath, clientOverride, transco
23079
23080
  try {
23080
23081
  const trans = await client.transcribe(attemptBytes, language);
23081
23082
  const fidelity = transcriptionFidelity(segment.text, trans.text);
23082
- if (fidelity < TRANSCRIPTION_FIDELITY_FLOOR && verifyAttempt < 2) {
23083
+ if (fidelity < TRANSCRIPTION_FIDELITY_FLOOR && verifyAttempt < maxVerifyAttempts) {
23083
23084
  echoError(`voice-gen: transcription fidelity ${fidelity.toFixed(2)} below ${TRANSCRIPTION_FIDELITY_FLOOR} (looped/garbage audio): "${trans.text.slice(0, 80)}..."`);
23084
23085
  continue;
23085
23086
  }
@@ -23088,7 +23089,7 @@ async function processGeneratorIO(inputPath, outputPath, clientOverride, transco
23088
23089
  }
23089
23090
  const dip = detectLoudnessDip(attemptBytes);
23090
23091
  loudnessDip = dip.dip;
23091
- if (dip.dip && verifyAttempt < 2) {
23092
+ if (dip.dip && verifyAttempt < maxVerifyAttempts) {
23092
23093
  echoError(`voice-gen: loudness dip detected at ${dip.dipAtSec ?? "?"}s \u2014 regenerating segment`);
23093
23094
  continue;
23094
23095
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omni-voice-gen",
3
3
  "kind": "generator",
4
- "entry": "./dist/index.js",
4
+ "entry": "./bin/omni-voice-gen",
5
5
  "version": "1.0.0"
6
6
  }
@@ -23038,7 +23038,8 @@ async function processGeneratorIO(inputPath, outputPath, clientOverride, transco
23038
23038
  let loudnessDip = false;
23039
23039
  let lastError;
23040
23040
  let attemptBody = generateBody;
23041
- for (let verifyAttempt = 0;verifyAttempt <= 2 && wavBytes === undefined; verifyAttempt++) {
23041
+ const maxVerifyAttempts = Number.parseInt(process.env.VOICEBOX_MAX_VERIFY_ATTEMPTS ?? "2", 10) || 0;
23042
+ for (let verifyAttempt = 0;verifyAttempt <= maxVerifyAttempts && wavBytes === undefined; verifyAttempt++) {
23042
23043
  if (verifyAttempt > 0) {
23043
23044
  verifyRetries = verifyAttempt;
23044
23045
  const chunkChars = Math.max(VOICEBOX_CHUNK_CHARS_MIN, Math.floor(attemptBody.max_chunk_chars / 2));
@@ -23076,7 +23077,7 @@ async function processGeneratorIO(inputPath, outputPath, clientOverride, transco
23076
23077
  try {
23077
23078
  const trans = await client.transcribe(attemptBytes, language);
23078
23079
  const fidelity = transcriptionFidelity(segment.text, trans.text);
23079
- if (fidelity < TRANSCRIPTION_FIDELITY_FLOOR && verifyAttempt < 2) {
23080
+ if (fidelity < TRANSCRIPTION_FIDELITY_FLOOR && verifyAttempt < maxVerifyAttempts) {
23080
23081
  echoError(`voice-gen: transcription fidelity ${fidelity.toFixed(2)} below ${TRANSCRIPTION_FIDELITY_FLOOR} (looped/garbage audio): "${trans.text.slice(0, 80)}..."`);
23081
23082
  continue;
23082
23083
  }
@@ -23085,7 +23086,7 @@ async function processGeneratorIO(inputPath, outputPath, clientOverride, transco
23085
23086
  }
23086
23087
  const dip = detectLoudnessDip(attemptBytes);
23087
23088
  loudnessDip = dip.dip;
23088
- if (dip.dip && verifyAttempt < 2) {
23089
+ if (dip.dip && verifyAttempt < maxVerifyAttempts) {
23089
23090
  echoError(`voice-gen: loudness dip detected at ${dip.dipAtSec ?? "?"}s \u2014 regenerating segment`);
23090
23091
  continue;
23091
23092
  }
@@ -212,7 +212,13 @@ export async function processGeneratorIO(
212
212
  let loudnessDip = false;
213
213
  let lastError: unknown;
214
214
  let attemptBody = generateBody;
215
- for (let verifyAttempt = 0; verifyAttempt <= 2 && wavBytes === undefined; verifyAttempt++) {
215
+ // Verify-retry budget. Hosted TTS is billed per request against a daily quota
216
+ // (Gemini Tier 1: 100/day), and each retry is a full re-synthesis — a 41-segment
217
+ // episode with the default 2 retries asks for up to 123 requests and dies partway
218
+ // through the render with no audio (daily-generate failures of 2026-10-06/07/09/10).
219
+ // Lower it for a quota-bound driver; 0 keeps the dip/fidelity result for QC instead.
220
+ const maxVerifyAttempts = Number.parseInt(process.env.VOICEBOX_MAX_VERIFY_ATTEMPTS ?? '2', 10) || 0;
221
+ for (let verifyAttempt = 0; verifyAttempt <= maxVerifyAttempts && wavBytes === undefined; verifyAttempt++) {
216
222
  if (verifyAttempt > 0) {
217
223
  verifyRetries = verifyAttempt;
218
224
  const chunkChars = Math.max(VOICEBOX_CHUNK_CHARS_MIN, Math.floor(attemptBody.max_chunk_chars / 2));
@@ -262,7 +268,7 @@ export async function processGeneratorIO(
262
268
  try {
263
269
  const trans = await client.transcribe(attemptBytes, language);
264
270
  const fidelity = transcriptionFidelity(segment.text, trans.text);
265
- if (fidelity < TRANSCRIPTION_FIDELITY_FLOOR && verifyAttempt < 2) {
271
+ if (fidelity < TRANSCRIPTION_FIDELITY_FLOOR && verifyAttempt < maxVerifyAttempts) {
266
272
  echoError(
267
273
  `voice-gen: transcription fidelity ${fidelity.toFixed(2)} below ${TRANSCRIPTION_FIDELITY_FLOOR} (looped/garbage audio): "${trans.text.slice(0, 80)}..."`,
268
274
  );
@@ -278,7 +284,7 @@ export async function processGeneratorIO(
278
284
  // (dogfood 2026-09-08 A/B: same text regenerated clean, so retry usually clears).
279
285
  const dip = detectLoudnessDip(attemptBytes);
280
286
  loudnessDip = dip.dip;
281
- if (dip.dip && verifyAttempt < 2) {
287
+ if (dip.dip && verifyAttempt < maxVerifyAttempts) {
282
288
  echoError(`voice-gen: loudness dip detected at ${dip.dipAtSec ?? '?'}s — regenerating segment`);
283
289
  continue;
284
290
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "kk",
3
- "version": "0.0.34",
3
+ "version": "0.0.35",
4
4
  "description": "knowledge-kit Claude Code plugin: skills, commands, subagents, hooks, and rules"
5
5
  }