@gobing-ai/knowledge-kit 0.0.17 → 0.0.19

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 (76) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +74 -0
  3. package/dist/index.js +108 -107
  4. package/package.json +19 -1
  5. package/plugins/generations/content-gen/dist/index.js +42 -94
  6. package/plugins/generations/core-facts-gen/dist/index.js +51 -100
  7. package/plugins/generations/daily-article-gen/dist/index.js +57 -99
  8. package/plugins/generations/daily-article-gen/src/index.ts +12 -4
  9. package/plugins/generations/dailynews-gen/dist/index.js +57 -102
  10. package/plugins/generations/dailynews-gen/src/script-builder.ts +9 -1
  11. package/plugins/generations/episode-plan-gen/dist/index.js +120 -113
  12. package/plugins/generations/episode-plan-gen/src/index.ts +102 -22
  13. package/plugins/generations/image-gen/dist/index.js +63 -115
  14. package/plugins/generations/news-report-gen/dist/index.js +117 -99
  15. package/plugins/generations/news-report-gen/src/index.ts +117 -1
  16. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +1 -1
  17. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +8 -0
  18. package/plugins/generations/voice-gen/dist/index.js +161 -120
  19. package/plugins/generations/voice-gen/src/concat.ts +34 -0
  20. package/plugins/generations/voice-gen/src/index.ts +14 -4
  21. package/plugins/generations/voice-gen/src/voicebox-server.ts +91 -0
  22. package/plugins/generations/voice-gen/src/voicescript.ts +8 -0
  23. package/plugins/ingestions/aihot-ingest/dist/index.js +47 -97
  24. package/plugins/ingestions/horizon-ingest/dist/index.js +47 -96
  25. package/plugins/ingestions/karakeep-local/dist/index.js +217 -266
  26. package/plugins/ingestions/last30days-ingest/dist/index.js +42 -91
  27. package/plugins/ingestions/web-search/dist/index.js +68 -117
  28. package/plugins/kk/commands/workflow-run.md +10 -6
  29. package/plugins/kk/plugin.json +1 -1
  30. package/plugins/kk/scripts/kk-workflow-stages.ts +376 -70
  31. package/plugins/kk/skills/publish/SKILL.md +80 -0
  32. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +136 -224
  33. package/plugins/kk/workflows/leftover-publish-fanout-example.yaml +53 -0
  34. package/plugins/publishings/emdash-pub/dist/index.js +42 -91
  35. package/plugins/publishings/infoq-pub/dist/index.js +21994 -0
  36. package/plugins/publishings/infoq-pub/package.json +17 -0
  37. package/plugins/publishings/infoq-pub/plugin.json +7 -0
  38. package/plugins/publishings/infoq-pub/src/index.ts +123 -0
  39. package/plugins/publishings/infoq-pub/tsconfig.json +4 -0
  40. package/plugins/publishings/juejin-pub/dist/index.js +21994 -0
  41. package/plugins/publishings/juejin-pub/package.json +17 -0
  42. package/plugins/publishings/juejin-pub/plugin.json +7 -0
  43. package/plugins/publishings/juejin-pub/src/index.ts +123 -0
  44. package/plugins/publishings/juejin-pub/tsconfig.json +4 -0
  45. package/plugins/publishings/medium-pub/dist/index.js +22041 -0
  46. package/plugins/publishings/medium-pub/package.json +17 -0
  47. package/plugins/publishings/medium-pub/plugin.json +7 -0
  48. package/plugins/publishings/medium-pub/src/index.ts +215 -0
  49. package/plugins/publishings/medium-pub/tsconfig.json +4 -0
  50. package/plugins/publishings/podcast-pub/dist/index.js +183 -123
  51. package/plugins/publishings/podcast-pub/src/index.ts +47 -3
  52. package/plugins/publishings/podcast-pub/src/show-notes.ts +153 -34
  53. package/plugins/publishings/qiita-pub/dist/index.js +42 -91
  54. package/plugins/publishings/substack-pub/dist/index.js +21994 -0
  55. package/plugins/publishings/substack-pub/package.json +17 -0
  56. package/plugins/publishings/substack-pub/plugin.json +7 -0
  57. package/plugins/publishings/substack-pub/src/index.ts +123 -0
  58. package/plugins/publishings/substack-pub/tsconfig.json +4 -0
  59. package/plugins/publishings/surfdash-pub/dist/index.js +172 -103
  60. package/plugins/publishings/surfdash-pub/src/index.ts +32 -2
  61. package/plugins/publishings/wechatmp-pub/dist/index.js +22119 -0
  62. package/plugins/publishings/wechatmp-pub/package.json +17 -0
  63. package/plugins/publishings/wechatmp-pub/plugin.json +7 -0
  64. package/plugins/publishings/wechatmp-pub/src/index.ts +304 -0
  65. package/plugins/publishings/wechatmp-pub/tsconfig.json +4 -0
  66. package/plugins/publishings/x-pub/dist/index.js +22110 -0
  67. package/plugins/publishings/x-pub/package.json +17 -0
  68. package/plugins/publishings/x-pub/plugin.json +7 -0
  69. package/plugins/publishings/x-pub/src/index.ts +222 -0
  70. package/plugins/publishings/x-pub/tsconfig.json +4 -0
  71. package/plugins/publishings/xhs-pub/dist/index.js +21994 -0
  72. package/plugins/publishings/xhs-pub/package.json +17 -0
  73. package/plugins/publishings/xhs-pub/plugin.json +7 -0
  74. package/plugins/publishings/xhs-pub/src/index.ts +123 -0
  75. package/plugins/publishings/xhs-pub/tsconfig.json +4 -0
  76. package/plugins/publishings/zenn-pub/dist/index.js +42 -91
@@ -14642,72 +14642,6 @@ var init_zod = __esm(() => {
14642
14642
  init_external();
14643
14643
  });
14644
14644
 
14645
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/errors.js
14646
- var ErrorCode;
14647
- var init_errors3 = __esm(() => {
14648
- ErrorCode = {
14649
- NotFound: "NOT_FOUND",
14650
- Validation: "VALIDATION",
14651
- Conflict: "CONFLICT",
14652
- Internal: "INTERNAL"
14653
- };
14654
- });
14655
-
14656
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/api-response.js
14657
- var API_ERROR_CODES, ERROR_CODE_TO_HTTP, CLIENT_SAFE_CODES;
14658
- var init_api_response = __esm(() => {
14659
- init_errors3();
14660
- API_ERROR_CODES = {
14661
- SUCCESS: 0,
14662
- NOT_FOUND: 404,
14663
- VALIDATION_ERROR: 422,
14664
- BAD_REQUEST: 400,
14665
- UNAUTHORIZED: 401,
14666
- FORBIDDEN: 403,
14667
- CONFLICT: 409,
14668
- INTERNAL_ERROR: 500
14669
- };
14670
- ERROR_CODE_TO_HTTP = {
14671
- [ErrorCode.NotFound]: API_ERROR_CODES.NOT_FOUND,
14672
- [ErrorCode.Validation]: API_ERROR_CODES.VALIDATION_ERROR,
14673
- [ErrorCode.Conflict]: API_ERROR_CODES.CONFLICT,
14674
- [ErrorCode.Internal]: API_ERROR_CODES.INTERNAL_ERROR
14675
- };
14676
- CLIENT_SAFE_CODES = new Set([ErrorCode.NotFound, ErrorCode.Validation, ErrorCode.Conflict]);
14677
- });
14678
-
14679
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/cursor.js
14680
- var init_cursor = () => {};
14681
-
14682
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/event-severity.js
14683
- var init_event_severity = () => {};
14684
-
14685
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/output.js
14686
- function processStream(name) {
14687
- const proc = globalThis.process;
14688
- const stream = proc?.[name];
14689
- if (stream === undefined) {
14690
- throw new Error(`No ${name} target available: set one via setDefaultOutputTargets or pass an explicit target`);
14691
- }
14692
- return stream;
14693
- }
14694
- function writeLine(message, target) {
14695
- target.write(`${message}
14696
- `);
14697
- }
14698
- function echoError(message, target = defaultStderrTarget ?? processStream("stderr")) {
14699
- writeLine(message, target);
14700
- }
14701
- var defaultStderrTarget;
14702
-
14703
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/index.js
14704
- var init_dist = __esm(() => {
14705
- init_api_response();
14706
- init_cursor();
14707
- init_errors3();
14708
- init_event_severity();
14709
- });
14710
-
14711
14645
  // ../../node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
14712
14646
  var require_identity = __commonJS((exports) => {
14713
14647
  var ALIAS = Symbol.for("yaml.alias");
@@ -17612,15 +17546,15 @@ var require_timestamp = __commonJS((exports) => {
17612
17546
  throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
17613
17547
  const [, year, month, day, hour, minute, second] = match.map(Number);
17614
17548
  const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0;
17615
- let date6 = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
17549
+ let date5 = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
17616
17550
  const tz = match[8];
17617
17551
  if (tz && tz !== "Z") {
17618
17552
  let d = parseSexagesimal(tz, false);
17619
17553
  if (Math.abs(d) < 30)
17620
17554
  d *= 60;
17621
- date6 -= 60000 * d;
17555
+ date5 -= 60000 * d;
17622
17556
  }
17623
- return new Date(date6);
17557
+ return new Date(date5);
17624
17558
  },
17625
17559
  stringify: ({ value }) => value?.toISOString().replace(/(T00:00:00)?\.000Z$/, "") ?? ""
17626
17560
  };
@@ -19480,7 +19414,7 @@ var require_composer = __commonJS((exports) => {
19480
19414
  var node_process = __require("process");
19481
19415
  var directives = require_directives();
19482
19416
  var Document = require_Document();
19483
- var errors4 = require_errors();
19417
+ var errors3 = require_errors();
19484
19418
  var identity = require_identity();
19485
19419
  var composeDoc = require_compose_doc();
19486
19420
  var resolveEnd = require_resolve_end();
@@ -19531,9 +19465,9 @@ var require_composer = __commonJS((exports) => {
19531
19465
  this.onError = (source, code, message, warning) => {
19532
19466
  const pos = getErrorPos(source);
19533
19467
  if (warning)
19534
- this.warnings.push(new errors4.YAMLWarning(pos, code, message));
19468
+ this.warnings.push(new errors3.YAMLWarning(pos, code, message));
19535
19469
  else
19536
- this.errors.push(new errors4.YAMLParseError(pos, code, message));
19470
+ this.errors.push(new errors3.YAMLParseError(pos, code, message));
19537
19471
  };
19538
19472
  this.directives = new directives.Directives({ version: options.version || "1.2" });
19539
19473
  this.options = options;
@@ -19619,7 +19553,7 @@ ${cb}` : comment;
19619
19553
  break;
19620
19554
  case "error": {
19621
19555
  const msg = token.source ? `${token.message}: ${JSON.stringify(token.source)}` : token.message;
19622
- const error51 = new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
19556
+ const error51 = new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
19623
19557
  if (this.atDirectives || !this.doc)
19624
19558
  this.errors.push(error51);
19625
19559
  else
@@ -19629,7 +19563,7 @@ ${cb}` : comment;
19629
19563
  case "doc-end": {
19630
19564
  if (!this.doc) {
19631
19565
  const msg = "Unexpected doc-end without preceding document";
19632
- this.errors.push(new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
19566
+ this.errors.push(new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
19633
19567
  break;
19634
19568
  }
19635
19569
  this.doc.directives.docEnd = true;
@@ -19644,7 +19578,7 @@ ${end.comment}` : end.comment;
19644
19578
  break;
19645
19579
  }
19646
19580
  default:
19647
- this.errors.push(new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
19581
+ this.errors.push(new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
19648
19582
  }
19649
19583
  }
19650
19584
  *end(forceDoc = false, endOffset = -1) {
@@ -19670,7 +19604,7 @@ ${end.comment}` : end.comment;
19670
19604
  var require_cst_scalar = __commonJS((exports) => {
19671
19605
  var resolveBlockScalar = require_resolve_block_scalar();
19672
19606
  var resolveFlowScalar = require_resolve_flow_scalar();
19673
- var errors4 = require_errors();
19607
+ var errors3 = require_errors();
19674
19608
  var stringifyString = require_stringifyString();
19675
19609
  function resolveAsScalar(token, strict = true, onError) {
19676
19610
  if (token) {
@@ -19679,7 +19613,7 @@ var require_cst_scalar = __commonJS((exports) => {
19679
19613
  if (onError)
19680
19614
  onError(offset, code, message);
19681
19615
  else
19682
- throw new errors4.YAMLParseError([offset, offset + 1], code, message);
19616
+ throw new errors3.YAMLParseError([offset, offset + 1], code, message);
19683
19617
  };
19684
19618
  switch (token.type) {
19685
19619
  case "scalar":
@@ -21559,7 +21493,7 @@ var require_parser = __commonJS((exports) => {
21559
21493
  var require_public_api = __commonJS((exports) => {
21560
21494
  var composer = require_composer();
21561
21495
  var Document = require_Document();
21562
- var errors4 = require_errors();
21496
+ var errors3 = require_errors();
21563
21497
  var log = require_log();
21564
21498
  var identity = require_identity();
21565
21499
  var lineCounter = require_line_counter();
@@ -21576,8 +21510,8 @@ var require_public_api = __commonJS((exports) => {
21576
21510
  const docs = Array.from(composer$1.compose(parser$1.parse(source)));
21577
21511
  if (prettyErrors && lineCounter2)
21578
21512
  for (const doc2 of docs) {
21579
- doc2.errors.forEach(errors4.prettifyError(source, lineCounter2));
21580
- doc2.warnings.forEach(errors4.prettifyError(source, lineCounter2));
21513
+ doc2.errors.forEach(errors3.prettifyError(source, lineCounter2));
21514
+ doc2.warnings.forEach(errors3.prettifyError(source, lineCounter2));
21581
21515
  }
21582
21516
  if (docs.length > 0)
21583
21517
  return docs;
@@ -21592,13 +21526,13 @@ var require_public_api = __commonJS((exports) => {
21592
21526
  if (!doc2)
21593
21527
  doc2 = _doc;
21594
21528
  else if (doc2.options.logLevel !== "silent") {
21595
- doc2.errors.push(new errors4.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
21529
+ doc2.errors.push(new errors3.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
21596
21530
  break;
21597
21531
  }
21598
21532
  }
21599
21533
  if (prettyErrors && lineCounter2) {
21600
- doc2.errors.forEach(errors4.prettifyError(source, lineCounter2));
21601
- doc2.warnings.forEach(errors4.prettifyError(source, lineCounter2));
21534
+ doc2.errors.forEach(errors3.prettifyError(source, lineCounter2));
21535
+ doc2.warnings.forEach(errors3.prettifyError(source, lineCounter2));
21602
21536
  }
21603
21537
  return doc2;
21604
21538
  }
@@ -21650,12 +21584,12 @@ var require_public_api = __commonJS((exports) => {
21650
21584
  });
21651
21585
 
21652
21586
  // ../../node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/index.js
21653
- var composer, Document, Schema, errors4, Alias, identity, Pair, Scalar, YAMLMap, YAMLSeq, cst, lexer, lineCounter, parser, publicApi, visit, $Composer, $Document, $Schema, $YAMLError, $YAMLParseError, $YAMLWarning, $Alias, $isAlias, $isCollection, $isDocument, $isMap, $isNode, $isPair, $isScalar, $isSeq, $Pair, $Scalar, $YAMLMap, $YAMLSeq, $Lexer, $LineCounter, $Parser, $parse, $parseAllDocuments, $parseDocument, $stringify, $visit, $visitAsync;
21654
- var init_dist2 = __esm(() => {
21587
+ var composer, Document, Schema, errors3, Alias, identity, Pair, Scalar, YAMLMap, YAMLSeq, cst, lexer, lineCounter, parser, publicApi, visit, $Composer, $Document, $Schema, $YAMLError, $YAMLParseError, $YAMLWarning, $Alias, $isAlias, $isCollection, $isDocument, $isMap, $isNode, $isPair, $isScalar, $isSeq, $Pair, $Scalar, $YAMLMap, $YAMLSeq, $Lexer, $LineCounter, $Parser, $parse, $parseAllDocuments, $parseDocument, $stringify, $visit, $visitAsync;
21588
+ var init_dist = __esm(() => {
21655
21589
  composer = require_composer();
21656
21590
  Document = require_Document();
21657
21591
  Schema = require_Schema();
21658
- errors4 = require_errors();
21592
+ errors3 = require_errors();
21659
21593
  Alias = require_Alias();
21660
21594
  identity = require_identity();
21661
21595
  Pair = require_Pair();
@@ -21671,9 +21605,9 @@ var init_dist2 = __esm(() => {
21671
21605
  $Composer = composer.Composer;
21672
21606
  $Document = Document.Document;
21673
21607
  $Schema = Schema.Schema;
21674
- $YAMLError = errors4.YAMLError;
21675
- $YAMLParseError = errors4.YAMLParseError;
21676
- $YAMLWarning = errors4.YAMLWarning;
21608
+ $YAMLError = errors3.YAMLError;
21609
+ $YAMLParseError = errors3.YAMLParseError;
21610
+ $YAMLWarning = errors3.YAMLWarning;
21677
21611
  $Alias = Alias.Alias;
21678
21612
  $isAlias = identity.isAlias;
21679
21613
  $isCollection = identity.isCollection;
@@ -21701,7 +21635,7 @@ var init_dist2 = __esm(() => {
21701
21635
  // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/config.js
21702
21636
  var configSchema;
21703
21637
  var init_config = __esm(() => {
21704
- init_dist2();
21638
+ init_dist();
21705
21639
  init_zod();
21706
21640
  configSchema = exports_external.object({
21707
21641
  app: exports_external.object({
@@ -21952,10 +21886,27 @@ function getProcessPid() {
21952
21886
  function uniqueToken() {
21953
21887
  return `${Date.now()}.${crypto.randomUUID()}`;
21954
21888
  }
21889
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.67/node_modules/@gobing-ai/ts-utils/dist/output.js
21890
+ var defaultStderrTarget;
21891
+ function processStream(name) {
21892
+ const proc = globalThis.process;
21893
+ const stream = proc?.[name];
21894
+ if (stream === undefined) {
21895
+ throw new Error(`No ${name} target available: set one via setDefaultOutputTargets or pass an explicit target`);
21896
+ }
21897
+ return stream;
21898
+ }
21899
+ function writeLine(message, target) {
21900
+ target.write(`${message}
21901
+ `);
21902
+ }
21903
+ function echoError(message, target = defaultStderrTarget ?? processStream("stderr")) {
21904
+ writeLine(message, target);
21905
+ }
21906
+
21955
21907
  // ../../packages/kk-core/src/render-md.ts
21956
21908
  if (false) {}
21957
21909
  // ../../plugins/generations/news-report-gen/src/index.ts
21958
- init_dist();
21959
21910
  var QC_DIMENSIONS = ["quality", "importance", "urgency", "impact"];
21960
21911
  var QC_MIN_ENVS = {
21961
21912
  quality: "QC_MIN_QUALITY",
@@ -22094,8 +22045,8 @@ function durationMs(entry) {
22094
22045
  return null;
22095
22046
  return Math.max(0, end - start);
22096
22047
  }
22097
- function renderReportMarkdown(docs, date6, options = {}) {
22098
- const lines = [`# \u6BCF\u65E5 AI \u8BED\u97F3\u8FD0\u884C\u62A5\u544A \u2014 ${date6}`, ""];
22048
+ function renderReportMarkdown(docs, date5, options = {}) {
22049
+ const lines = [`# \u6BCF\u65E5 AI \u8BED\u97F3\u8FD0\u884C\u62A5\u544A \u2014 ${date5}`, ""];
22099
22050
  lines.push("## \u9009\u7A3F\u8BC4\u5206\uFF08\u4FDD\u7559\u5019\u9009\uFF09", "");
22100
22051
  if (docs.length === 0) {
22101
22052
  lines.push("\u672C\u671F\u65E0\u4FDD\u7559\u5019\u9009\u3002", "");
@@ -22132,6 +22083,30 @@ function renderReportMarkdown(docs, date6, options = {}) {
22132
22083
  }
22133
22084
  lines.push(`| category allowlist | \u2014 | ${counts.category} |`);
22134
22085
  lines.push("");
22086
+ const blended = options.blended ?? null;
22087
+ if (blended !== null && blended.length > 0) {
22088
+ const comp = corpusComposition(blended);
22089
+ lines.push(`## \u8BED\u6599\u6784\u6210\uFF08fan-in \u8F93\u5165\uFF0C\u5171 ${comp.total} \u6761\uFF09`, "");
22090
+ lines.push("| \u6765\u6E90 | \u6761\u6570 |", "| --- | --- |");
22091
+ for (const g of comp.sources)
22092
+ lines.push(`| ${g.key} | ${g.count} |`);
22093
+ lines.push("");
22094
+ lines.push("| \u7C7B\u522B | \u6761\u6570 |", "| --- | --- |");
22095
+ for (const g of comp.categories)
22096
+ lines.push(`| ${g.key} | ${g.count} |`);
22097
+ lines.push("");
22098
+ }
22099
+ const planAudit = options.planAudit ?? null;
22100
+ if (planAudit !== null && planAudit.length > 0) {
22101
+ lines.push(`## \u9009\u7A3F\u6F0F\u6597\uFF08\u5168\u91CF\u5BA1\u8BA1\uFF0C\u5171 ${planAudit.length} \u6761\uFF09`, "");
22102
+ lines.push("| \u6765\u6E90 | \u6807\u9898 | \u7C7B\u522B | q | i | u | im | score | \u53BB\u5411 |", "| --- | --- | --- | --- | --- | --- | --- | --- | --- |");
22103
+ for (const row of planAudit) {
22104
+ const s = row.scores ?? {};
22105
+ const title = (row.title || row.id).replace(/\|/g, "\\|").slice(0, 60);
22106
+ lines.push(`| ${row.source} | ${title} | ${row.category} | ${s.quality ?? "\u2014"} | ${s.importance ?? "\u2014"} | ${s.urgency ?? "\u2014"} | ${s.impact ?? "\u2014"} | ${row.planScore ?? "\u2014"} | ${row.disposition} |`);
22107
+ }
22108
+ lines.push("");
22109
+ }
22135
22110
  const qc = options.qc ?? null;
22136
22111
  if (qc !== null) {
22137
22112
  lines.push("## \u97F3\u9891 QC", "");
@@ -22176,12 +22151,50 @@ async function readRejectedAudit(path2) {
22176
22151
  return [];
22177
22152
  }
22178
22153
  }
22179
- function docsToContent(docs, date6, options) {
22154
+ async function readPlanAudit(path2) {
22155
+ if (!path2 || path2.trim() === "")
22156
+ return null;
22157
+ try {
22158
+ const parsed = JSON.parse(await Bun.file(path2).text());
22159
+ const rows = parsed?.rows;
22160
+ return Array.isArray(rows) ? rows : null;
22161
+ } catch {
22162
+ return null;
22163
+ }
22164
+ }
22165
+ async function readBlendedCorpus(path2) {
22166
+ if (!path2 || path2.trim() === "")
22167
+ return null;
22168
+ try {
22169
+ const parsed = JSON.parse(await Bun.file(path2).text());
22170
+ const docs = Array.isArray(parsed) ? parsed : parsed?.docs;
22171
+ return Array.isArray(docs) ? docs : null;
22172
+ } catch {
22173
+ return null;
22174
+ }
22175
+ }
22176
+ function corpusComposition(docs) {
22177
+ const tally = (pick2) => {
22178
+ const counts = new Map;
22179
+ for (const d of docs) {
22180
+ const raw = pick2(d);
22181
+ const key = typeof raw === "string" && raw.trim() !== "" ? raw : "\uFF08\u65E0\uFF09";
22182
+ counts.set(key, (counts.get(key) ?? 0) + 1);
22183
+ }
22184
+ return [...counts.entries()].map(([key, count]) => ({ key, count })).sort((a, b) => b.count - a.count || (a.key < b.key ? -1 : 1));
22185
+ };
22186
+ return {
22187
+ total: docs.length,
22188
+ sources: tally((d) => d.metadata?.source ?? d.source),
22189
+ categories: tally((d) => d.metadata?.category ?? d.category)
22190
+ };
22191
+ }
22192
+ function docsToContent(docs, date5, options) {
22180
22193
  return ContentSchema.parse({
22181
- title: `\u6BCF\u65E5 AI \u8BED\u97F3\u8FD0\u884C\u62A5\u544A \u2014 ${date6}`,
22182
- body: renderReportMarkdown(docs, date6, options),
22194
+ title: `\u6BCF\u65E5 AI \u8BED\u97F3\u8FD0\u884C\u62A5\u544A \u2014 ${date5}`,
22195
+ body: renderReportMarkdown(docs, date5, options),
22183
22196
  format: "markdown",
22184
- metadata: { generator: "kk:news-report", date: date6, doc_count: docs.length }
22197
+ metadata: { generator: "kk:news-report", date: date5, doc_count: docs.length }
22185
22198
  });
22186
22199
  }
22187
22200
  function dateFromEnv() {
@@ -22193,7 +22206,9 @@ async function processGeneratorIO(inputPath, outputPath) {
22193
22206
  const rejected = await readRejectedAudit(process.env.NEWS_REPORT_REJECTED_FILE);
22194
22207
  const timing = await readTiming(process.env.NEWS_REPORT_TIMING_FILE);
22195
22208
  const qc = await readQcSummary(process.env.NEWS_REPORT_QC_FILE);
22196
- const content = docsToContent(docs, dateFromEnv(), { rejected, timing, qc });
22209
+ const planAudit = await readPlanAudit(process.env.NEWS_REPORT_PLAN_AUDIT_FILE);
22210
+ const blended = await readBlendedCorpus(process.env.NEWS_REPORT_BLENDED_FILE);
22211
+ const content = docsToContent(docs, dateFromEnv(), { rejected, timing, qc, planAudit, blended });
22197
22212
  const fs = createNodeFileSystem();
22198
22213
  const outDir = dirname2(outputPath);
22199
22214
  if (outDir && outDir !== ".")
@@ -22232,8 +22247,11 @@ export {
22232
22247
  readTiming,
22233
22248
  readRejectedAudit,
22234
22249
  readQcSummary,
22250
+ readPlanAudit,
22251
+ readBlendedCorpus,
22235
22252
  processGeneratorIO,
22236
22253
  minimumsFromEnv,
22237
22254
  main2 as main,
22238
- docsToContent
22255
+ docsToContent,
22256
+ corpusComposition
22239
22257
  };
@@ -18,6 +18,9 @@ import { echoError } from '@gobing-ai/ts-utils';
18
18
  * `metadata.scores` (quality/importance/urgency/impact 0-5).
19
19
  * - `NEWS_REPORT_REJECTED_FILE` — the filter's rejected audit path
20
20
  * (`2-plan/candidates.rejected.json`, RejectedDoc[] of {doc, reasons})
21
+ * - `NEWS_REPORT_BLENDED_FILE` — the collect stage's blended fan-in output
22
+ * (`1-ingest/<date>_02_collect_blended.json`); absent or unreadable -> the
23
+ * 语料构成 section is omitted, never throw.
21
24
  * - `NEWS_REPORT_QC_FILE` — the generate stage's QC audit path
22
25
  * (`3-audio/<date>_11_generate_content.json`); absent -> the 音频 QC block is omitted;
23
26
  * absent or unreadable -> reject counts render as 0, never throw.
@@ -231,6 +234,8 @@ export function renderReportMarkdown(
231
234
  rejected?: { doc: Doc; reasons: string[] }[];
232
235
  timing?: TimingEntry[];
233
236
  qc?: QcSummary | null;
237
+ planAudit?: PlanAuditRow[] | null;
238
+ blended?: BlendedDoc[] | null;
234
239
  } = {},
235
240
  ): string {
236
241
  const lines: string[] = [`# 每日 AI 语音运行报告 — ${date}`, ''];
@@ -272,6 +277,40 @@ export function renderReportMarkdown(
272
277
  lines.push(`| category allowlist | — | ${counts.category} |`);
273
278
  lines.push('');
274
279
 
280
+ const blended = options.blended ?? null;
281
+ if (blended !== null && blended.length > 0) {
282
+ // 20260917 funnel: selection can only be judged against what arrived — the blended
283
+ // input's source/category composition exposes the uncategorized mass that plan
284
+ // under-ranks (e.g. 47 of 52 docs with no category -> 2 selected).
285
+ const comp = corpusComposition(blended);
286
+ lines.push(`## 语料构成(fan-in 输入,共 ${comp.total} 条)`, '');
287
+ lines.push('| 来源 | 条数 |', '| --- | --- |');
288
+ for (const g of comp.sources) lines.push(`| ${g.key} | ${g.count} |`);
289
+ lines.push('');
290
+ lines.push('| 类别 | 条数 |', '| --- | --- |');
291
+ for (const g of comp.categories) lines.push(`| ${g.key} | ${g.count} |`);
292
+ lines.push('');
293
+ }
294
+
295
+ const planAudit = options.planAudit ?? null;
296
+ if (planAudit !== null && planAudit.length > 0) {
297
+ // 20260917 funnel: the report only showed the 2 survivors — every fetched title, its
298
+ // scores and its elimination reason now render here (selection is auditable end to end).
299
+ lines.push(`## 选稿漏斗(全量审计,共 ${planAudit.length} 条)`, '');
300
+ lines.push(
301
+ '| 来源 | 标题 | 类别 | q | i | u | im | score | 去向 |',
302
+ '| --- | --- | --- | --- | --- | --- | --- | --- | --- |',
303
+ );
304
+ for (const row of planAudit) {
305
+ const s = row.scores ?? {};
306
+ const title = (row.title || row.id).replace(/\|/g, '\\|').slice(0, 60);
307
+ lines.push(
308
+ `| ${row.source} | ${title} | ${row.category} | ${s.quality ?? '—'} | ${s.importance ?? '—'} | ${s.urgency ?? '—'} | ${s.impact ?? '—'} | ${row.planScore ?? '—'} | ${row.disposition} |`,
309
+ );
310
+ }
311
+ lines.push('');
312
+ }
313
+
275
314
  const qc = options.qc ?? null;
276
315
  if (qc !== null) {
277
316
  // 0139 R5b: the audio QC verdict is otherwise invisible in the report — this run published
@@ -322,6 +361,79 @@ export async function readRejectedAudit(path: string | undefined): Promise<Rejec
322
361
  }
323
362
  }
324
363
 
364
+ /** One row of episode-plan-gen's `<out>.audit.json` selection trail (plan mode). */
365
+ export interface PlanAuditRow {
366
+ id: string;
367
+ source: string;
368
+ title: string;
369
+ category: string;
370
+ scores: Record<string, number>;
371
+ planScore: number | null;
372
+ disposition: 'gate' | 'url-merge' | 'title-merge' | 'cap' | 'topN' | 'selected';
373
+ }
374
+
375
+ /** Read the plan-mode audit envelope `{ total, rows }`; absent/unreadable/malformed -> null (never throw). */
376
+ export async function readPlanAudit(path: string | undefined): Promise<PlanAuditRow[] | null> {
377
+ if (!path || path.trim() === '') return null;
378
+ try {
379
+ const parsed: unknown = JSON.parse(await Bun.file(path).text());
380
+ const rows = (parsed as { rows?: unknown } | null)?.rows;
381
+ return Array.isArray(rows) ? (rows as PlanAuditRow[]) : null;
382
+ } catch {
383
+ return null;
384
+ }
385
+ }
386
+
387
+ /** Minimal shape the 语料构成 section needs from a blended fan-in doc (tolerant on purpose). */
388
+ export interface BlendedDoc {
389
+ title?: unknown;
390
+ id?: unknown;
391
+ source?: unknown;
392
+ category?: unknown;
393
+ metadata?: { source?: unknown; category?: unknown };
394
+ }
395
+
396
+ export interface CorpusCount {
397
+ key: string;
398
+ count: number;
399
+ }
400
+
401
+ /** Read the blended fan-in input; absent/unreadable/malformed/not-an-array -> null (never throw). */
402
+ export async function readBlendedCorpus(path: string | undefined): Promise<BlendedDoc[] | null> {
403
+ if (!path || path.trim() === '') return null;
404
+ try {
405
+ const parsed: unknown = JSON.parse(await Bun.file(path).text());
406
+ const docs = Array.isArray(parsed) ? parsed : (parsed as { docs?: unknown } | null)?.docs;
407
+ return Array.isArray(docs) ? (docs as BlendedDoc[]) : null;
408
+ } catch {
409
+ return null;
410
+ }
411
+ }
412
+
413
+ /** Tally blended docs by source and by category (missing fields bucket as (无)), most frequent first. */
414
+ export function corpusComposition(docs: BlendedDoc[]): {
415
+ total: number;
416
+ sources: CorpusCount[];
417
+ categories: CorpusCount[];
418
+ } {
419
+ const tally = (pick: (d: BlendedDoc) => unknown): CorpusCount[] => {
420
+ const counts = new Map<string, number>();
421
+ for (const d of docs) {
422
+ const raw = pick(d);
423
+ const key = typeof raw === 'string' && raw.trim() !== '' ? raw : '(无)';
424
+ counts.set(key, (counts.get(key) ?? 0) + 1);
425
+ }
426
+ return [...counts.entries()]
427
+ .map(([key, count]) => ({ key, count }))
428
+ .sort((a, b) => b.count - a.count || (a.key < b.key ? -1 : 1));
429
+ };
430
+ return {
431
+ total: docs.length,
432
+ sources: tally((d) => d.metadata?.source ?? d.source),
433
+ categories: tally((d) => d.metadata?.category ?? d.category),
434
+ };
435
+ }
436
+
325
437
  /** Build the report Content from a kept-candidate Doc[]. */
326
438
  export function docsToContent(
327
439
  docs: Doc[],
@@ -330,6 +442,8 @@ export function docsToContent(
330
442
  rejected?: { doc: Doc; reasons: string[] }[];
331
443
  timing?: TimingEntry[];
332
444
  qc?: QcSummary | null;
445
+ planAudit?: PlanAuditRow[] | null;
446
+ blended?: BlendedDoc[] | null;
333
447
  },
334
448
  ): Content {
335
449
  return ContentSchema.parse({
@@ -350,7 +464,9 @@ export async function processGeneratorIO(inputPath: string, outputPath: string):
350
464
  const rejected = await readRejectedAudit(process.env.NEWS_REPORT_REJECTED_FILE);
351
465
  const timing = await readTiming(process.env.NEWS_REPORT_TIMING_FILE);
352
466
  const qc = await readQcSummary(process.env.NEWS_REPORT_QC_FILE);
353
- const content = docsToContent(docs, dateFromEnv(), { rejected, timing, qc });
467
+ const planAudit = await readPlanAudit(process.env.NEWS_REPORT_PLAN_AUDIT_FILE);
468
+ const blended = await readBlendedCorpus(process.env.NEWS_REPORT_BLENDED_FILE);
469
+ const content = docsToContent(docs, dateFromEnv(), { rejected, timing, qc, planAudit, blended });
354
470
  const fs = createNodeFileSystem();
355
471
  const outDir = dirname(outputPath);
356
472
  if (outDir && outDir !== '.') await fs.ensureDir(outDir);
@@ -210,7 +210,7 @@ def _render(
210
210
  "profile": resolve_profile(profile, registry),
211
211
  "instruct": segment_instruct,
212
212
  "seed": None if segment.seed is None else int(segment.seed),
213
- "speed": None,
213
+ "speed": None if segment.speed is None else float(segment.speed),
214
214
  }
215
215
  wav, duration, retries, transcription, dip = _render_verified(backend, spec)
216
216
 
@@ -70,6 +70,7 @@ class VoiceSegment:
70
70
  engine: Any = None
71
71
  language: Any = None
72
72
  gap_ms: Any = None
73
+ speed: Any = None
73
74
  max_chunk_chars: Any = None
74
75
  crossfade_ms: Any = None
75
76
  personality: Any = None
@@ -334,6 +335,13 @@ def validate_voice_script(script: VoiceScript, sink: WarnSink) -> None:
334
335
  ):
335
336
  raise ValueError("gap_ms must be non-negative")
336
337
 
338
+ if segment.speed is not None and (
339
+ isinstance(segment.speed, bool)
340
+ or not isinstance(segment.speed, (int, float))
341
+ or not 0.5 <= float(segment.speed) <= 2.0
342
+ ):
343
+ raise ValueError("speed must be between 0.5 and 2.0")
344
+
337
345
  if segment.seed is not None and (not _is_int(segment.seed) or segment.seed < 0):
338
346
  raise ValueError("seed must be a non-negative integer")
339
347