@gobing-ai/knowledge-kit 0.0.36 → 0.1.0

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 (159) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/board/collect/index.css +654 -16
  3. package/dist/board/collect/index.js +191 -64
  4. package/dist/board/daily-voice/index.css +679 -12
  5. package/dist/board/daily-voice/index.js +185 -66
  6. package/dist/board/itc/index.css +679 -13
  7. package/dist/board/itc/index.js +188 -66
  8. package/dist/index.js +2878 -152
  9. package/package.json +3 -2
  10. package/plugins/generations/content-gen/dist/index.js +158 -12
  11. package/plugins/generations/core-facts-gen/dist/index.js +158 -12
  12. package/plugins/generations/daily-article-gen/dist/index.js +158 -12
  13. package/plugins/generations/dailynews-gen/dist/index.js +180 -12
  14. package/plugins/generations/dailynews-gen/src/script-plan.ts +32 -0
  15. package/plugins/generations/episode-plan-gen/dist/index.js +158 -12
  16. package/plugins/generations/gemini-tts/README.md +4 -3
  17. package/plugins/generations/gemini-tts/dist/index.js +158 -12
  18. package/plugins/generations/image-gen/dist/index.js +158 -12
  19. package/plugins/generations/news-report-gen/dist/index.js +158 -12
  20. package/plugins/generations/omni-voice-gen/README.md +15 -4
  21. package/plugins/generations/omni-voice-gen/profiles.json +1 -2
  22. package/plugins/generations/voice-gen/dist/index.js +158 -12
  23. package/plugins/generations/voxcpm-tts/README.md +21 -1
  24. package/plugins/generations/voxcpm-tts/dist/index.js +173 -13
  25. package/plugins/generations/voxcpm-tts/scripts/audit-speaker-consistency.py +109 -0
  26. package/plugins/generations/voxcpm-tts/src/voxcpm-client.ts +28 -1
  27. package/plugins/ingestions/aihot-ingest/dist/index.js +158 -12
  28. package/plugins/ingestions/horizon-ingest/dist/index.js +158 -12
  29. package/plugins/ingestions/last30days-ingest/dist/index.js +158 -12
  30. package/plugins/ingestions/web-search/dist/index.js +158 -12
  31. package/plugins/kk/commands/workflow-run.md +56 -18
  32. package/plugins/kk/decisions/kk-itc.yaml +46 -0
  33. package/plugins/kk/decisions/kk-seo-keyword.yaml +57 -0
  34. package/plugins/kk/decisions/kk-seo-page.yaml +230 -0
  35. package/plugins/kk/decisions/kk-seo-pair.yaml +21 -0
  36. package/plugins/kk/decisions/kk-seo-site.yaml +90 -0
  37. package/plugins/kk/plugin.json +1 -1
  38. package/plugins/kk/publish-presets/chinese-article.json +14 -0
  39. package/plugins/kk/publish-presets/technical-article.json +14 -0
  40. package/plugins/kk/references/smart-seo-attribution.md +85 -0
  41. package/plugins/kk/scripts/daily-stages.ts +42 -2
  42. package/plugins/kk/scripts/daily-voice-style.zh.md +1 -1
  43. package/plugins/kk/scripts/itc-stages.ts +25 -118
  44. package/plugins/kk/scripts/kk-workflow-stages.ts +38 -1
  45. package/plugins/kk/scripts/seo-decisions.ts +477 -0
  46. package/plugins/kk/scripts/seo-enrich.ts +728 -0
  47. package/plugins/kk/scripts/seo-facts.ts +610 -0
  48. package/plugins/kk/scripts/seo-fetch.ts +766 -0
  49. package/plugins/kk/scripts/seo-report.ts +460 -0
  50. package/plugins/kk/scripts/seo-rules.ts +1882 -0
  51. package/plugins/kk/scripts/seo-stages.ts +1044 -0
  52. package/plugins/kk/skills/article-adapt/SKILL.md +2 -2
  53. package/plugins/kk/skills/smart-seo/SKILL.md +183 -0
  54. package/plugins/kk/skills/smart-seo/references/method.md +126 -0
  55. package/plugins/kk/skills/smart-seo/references/narrative.md +125 -0
  56. package/plugins/kk/skills/smart-seo/references/operations.md +202 -0
  57. package/plugins/kk/skills/smart-seo/references/seo-geo-sources.md +90 -0
  58. package/plugins/kk/workflows/feature-verification.yaml +28 -24
  59. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +86 -18
  60. package/plugins/kk/workflows/kk-itc-author.yaml +406 -0
  61. package/plugins/kk/workflows/kk-itc.yaml +208 -682
  62. package/plugins/kk/workflows/kk-smart-seo.yaml +251 -0
  63. package/plugins/publishings/bilibili-pub/dist/index.js +21782 -0
  64. package/plugins/publishings/bilibili-pub/dist/profile-check.js +20410 -0
  65. package/plugins/publishings/bilibili-pub/package.json +20 -0
  66. package/plugins/publishings/bilibili-pub/plugin.json +12 -0
  67. package/plugins/publishings/bilibili-pub/src/flow.ts +1296 -0
  68. package/plugins/publishings/bilibili-pub/src/index.ts +139 -0
  69. package/plugins/publishings/bilibili-pub/src/profile-check.ts +19 -0
  70. package/plugins/publishings/bilibili-pub/src/selectors.ts +222 -0
  71. package/plugins/publishings/bilibili-pub/tsconfig.json +4 -0
  72. package/plugins/publishings/cnblogs-pub/dist/index.js +21383 -0
  73. package/plugins/publishings/cnblogs-pub/dist/profile-check.js +20412 -0
  74. package/plugins/publishings/cnblogs-pub/package.json +19 -0
  75. package/plugins/publishings/cnblogs-pub/plugin.json +12 -0
  76. package/plugins/publishings/cnblogs-pub/src/flow.ts +708 -0
  77. package/plugins/publishings/cnblogs-pub/src/index.ts +172 -0
  78. package/plugins/publishings/cnblogs-pub/src/profile-check.ts +24 -0
  79. package/plugins/publishings/cnblogs-pub/src/selectors.ts +104 -0
  80. package/plugins/publishings/cnblogs-pub/tsconfig.json +4 -0
  81. package/plugins/publishings/csdn-pub/dist/index.js +21552 -0
  82. package/plugins/publishings/csdn-pub/dist/profile-check.js +20963 -0
  83. package/plugins/publishings/csdn-pub/package.json +19 -0
  84. package/plugins/publishings/csdn-pub/plugin.json +12 -0
  85. package/plugins/publishings/csdn-pub/src/index.ts +1178 -0
  86. package/plugins/publishings/csdn-pub/src/profile-check.ts +23 -0
  87. package/plugins/publishings/csdn-pub/tsconfig.json +4 -0
  88. package/plugins/publishings/emdash-pub/dist/index.js +158 -12
  89. package/plugins/publishings/emdash-pub/plugin.json +22 -1
  90. package/plugins/publishings/infoq-pub/dist/index.js +160 -12
  91. package/plugins/publishings/infoq-pub/dist/profile-check.js +160 -12
  92. package/plugins/publishings/infoq-pub/plugin.json +21 -0
  93. package/plugins/publishings/jike-pub/dist/index.js +21379 -0
  94. package/plugins/publishings/jike-pub/dist/profile-check.js +20951 -0
  95. package/plugins/publishings/jike-pub/package.json +19 -0
  96. package/plugins/publishings/jike-pub/plugin.json +12 -0
  97. package/plugins/publishings/jike-pub/src/index.ts +800 -0
  98. package/plugins/publishings/jike-pub/src/profile-check.ts +21 -0
  99. package/plugins/publishings/jike-pub/tsconfig.json +4 -0
  100. package/plugins/publishings/juejin-pub/dist/index.js +160 -12
  101. package/plugins/publishings/juejin-pub/dist/profile-check.js +160 -12
  102. package/plugins/publishings/juejin-pub/plugin.json +21 -0
  103. package/plugins/publishings/medium-pub/dist/index.js +158 -12
  104. package/plugins/publishings/medium-pub/plugin.json +22 -1
  105. package/plugins/publishings/podcast-pub/dist/index.js +158 -12
  106. package/plugins/publishings/qiita-pub/dist/index.js +158 -12
  107. package/plugins/publishings/qiita-pub/plugin.json +24 -1
  108. package/plugins/publishings/shipinhao-pub/dist/index.js +21715 -0
  109. package/plugins/publishings/shipinhao-pub/dist/profile-check.js +20431 -0
  110. package/plugins/publishings/shipinhao-pub/package.json +19 -0
  111. package/plugins/publishings/shipinhao-pub/plugin.json +12 -0
  112. package/plugins/publishings/shipinhao-pub/src/flow.ts +1323 -0
  113. package/plugins/publishings/shipinhao-pub/src/index.ts +173 -0
  114. package/plugins/publishings/shipinhao-pub/src/profile-check.ts +25 -0
  115. package/plugins/publishings/shipinhao-pub/src/selectors.ts +241 -0
  116. package/plugins/publishings/shipinhao-pub/tsconfig.json +4 -0
  117. package/plugins/publishings/sspai-pub/dist/index.js +21371 -0
  118. package/plugins/publishings/sspai-pub/dist/profile-check.js +20414 -0
  119. package/plugins/publishings/sspai-pub/package.json +19 -0
  120. package/plugins/publishings/sspai-pub/plugin.json +12 -0
  121. package/plugins/publishings/sspai-pub/src/flow.ts +624 -0
  122. package/plugins/publishings/sspai-pub/src/index.ts +167 -0
  123. package/plugins/publishings/sspai-pub/src/profile-check.ts +25 -0
  124. package/plugins/publishings/sspai-pub/src/selectors.ts +122 -0
  125. package/plugins/publishings/sspai-pub/tsconfig.json +4 -0
  126. package/plugins/publishings/substack-pub/dist/index.js +160 -12
  127. package/plugins/publishings/substack-pub/dist/profile-check.js +160 -12
  128. package/plugins/publishings/substack-pub/plugin.json +21 -0
  129. package/plugins/publishings/surfdash-pub/dist/index.js +158 -12
  130. package/plugins/publishings/surfdash-pub/plugin.json +22 -1
  131. package/plugins/publishings/threads-pub/dist/index.js +158 -12
  132. package/plugins/publishings/wechatmp-pub/dist/index.js +1669 -502
  133. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +204 -21
  134. package/plugins/publishings/wechatmp-pub/plugin.json +32 -0
  135. package/plugins/publishings/wechatmp-pub/src/cgi-draft.ts +540 -0
  136. package/plugins/publishings/wechatmp-pub/src/cgi-media.ts +380 -0
  137. package/plugins/publishings/wechatmp-pub/src/cgi.ts +485 -0
  138. package/plugins/publishings/wechatmp-pub/src/flow.ts +104 -0
  139. package/plugins/publishings/weibo-pub/dist/index.js +160 -12
  140. package/plugins/publishings/weibo-pub/dist/profile-check.js +160 -12
  141. package/plugins/publishings/weibo-pub/plugin.json +35 -0
  142. package/plugins/publishings/x-pub/dist/index.js +160 -12
  143. package/plugins/publishings/x-pub/dist/profile-check.js +160 -12
  144. package/plugins/publishings/x-pub/plugin.json +40 -0
  145. package/plugins/publishings/xhs-pub/dist/index.js +160 -12
  146. package/plugins/publishings/xhs-pub/dist/profile-check.js +160 -12
  147. package/plugins/publishings/xhs-pub/plugin.json +37 -0
  148. package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +21491 -0
  149. package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +20417 -0
  150. package/plugins/publishings/xiaoyuzhou-pub/package.json +19 -0
  151. package/plugins/publishings/xiaoyuzhou-pub/plugin.json +12 -0
  152. package/plugins/publishings/xiaoyuzhou-pub/src/flow.ts +826 -0
  153. package/plugins/publishings/xiaoyuzhou-pub/src/index.ts +174 -0
  154. package/plugins/publishings/xiaoyuzhou-pub/src/profile-check.ts +26 -0
  155. package/plugins/publishings/xiaoyuzhou-pub/src/selectors.ts +125 -0
  156. package/plugins/publishings/xiaoyuzhou-pub/tsconfig.json +4 -0
  157. package/plugins/publishings/zenn-pub/dist/index.js +158 -12
  158. package/plugins/publishings/zenn-pub/plugin.json +22 -1
  159. package/plugins/generations/omni-voice-gen/voices/robin-news.wav +0 -0
package/dist/index.js CHANGED
@@ -31845,6 +31845,7 @@ var init_types = () => {};
31845
31845
  // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.28+531271cb50a1e275/node_modules/@gobing-ai/ts-runtime/dist/index.js
31846
31846
  var init_dist3 = __esm(() => {
31847
31847
  init_file_system_node();
31848
+ init_process_executor();
31848
31849
  init_runtime_node_bun();
31849
31850
  init_process_executor();
31850
31851
  init_config();
@@ -37325,7 +37326,7 @@ ${section}
37325
37326
  `;
37326
37327
  }
37327
37328
  function selectMissingRuns(entries, markdown) {
37328
- return entries.filter((entry) => entry.status !== "running" && !ledgerHasRun(markdown, entry.runId)).sort((left, right) => (left.startedAt ?? "").localeCompare(right.startedAt ?? "")).map((entry) => entry.runId);
37329
+ return entries.filter((entry) => entry.status !== "running" && entry.isDryRun !== true && !ledgerHasRun(markdown, entry.runId)).sort((left, right) => (left.startedAt ?? "").localeCompare(right.startedAt ?? "")).map((entry) => entry.runId);
37329
37330
  }
37330
37331
  function rowCells(row) {
37331
37332
  const cells = row.replaceAll("\\|", ESCAPED_PIPE_SENTINEL).split("|");
@@ -38535,6 +38536,195 @@ var init_profile_dir2 = __esm(() => {
38535
38536
  init_dist6();
38536
38537
  });
38537
38538
 
38539
+ // ../../packages/publish-harness/src/browser-anti-detection.ts
38540
+ function resolveOptions(options) {
38541
+ const shieldProfile = options.shieldProfile === true;
38542
+ const suppressFileChooser = options.suppressFileChooser === true;
38543
+ if (!shieldProfile && !suppressFileChooser)
38544
+ return;
38545
+ const raw = options.allowedOrigins;
38546
+ if (raw === undefined || raw.length === 0) {
38547
+ throw new Error("kk anti-detection: enabled flags require a nonempty allowedOrigins list");
38548
+ }
38549
+ const allowedOrigins = raw.map((entry) => {
38550
+ let url;
38551
+ try {
38552
+ url = new URL(entry);
38553
+ } catch {
38554
+ throw new Error(`kk anti-detection: allowedOrigins entry is not a valid URL: '${entry}'`);
38555
+ }
38556
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "" || url.origin === "null") {
38557
+ throw new Error(`kk anti-detection: allowedOrigins entry must be a bare http(s) origin: '${entry}'`);
38558
+ }
38559
+ return url.origin;
38560
+ });
38561
+ return { allowedOrigins, shieldProfile, suppressFileChooser };
38562
+ }
38563
+ function installerScript(config) {
38564
+ const payload = JSON.stringify(config);
38565
+ return `(() => {
38566
+ const cfg = ${payload};
38567
+ const allowed = new Set(cfg.allowedOrigins);
38568
+ if (!allowed.has(location.origin)) return;
38569
+ const existing = window['${INSTALL_KEY}'];
38570
+ const signature = JSON.stringify([cfg.allowedOrigins.slice().sort(), cfg.shieldProfile, cfg.suppressFileChooser]);
38571
+ if (existing) {
38572
+ if (existing.signature === signature) return;
38573
+ throw new Error('kk anti-detection: already installed with a different configuration on this document');
38574
+ }
38575
+ const originals = {};
38576
+ const SHIELD_PATH = ${JSON.stringify(SHIELD_PATH)};
38577
+ const nativeLike = (wrapper) => {
38578
+ wrapper.toString = function () { return 'function () { [native code] }'; };
38579
+ return wrapper;
38580
+ };
38581
+ const resolveUrl = (input) => {
38582
+ try {
38583
+ if (typeof Request === 'function' && input instanceof Request) return new URL(input.url);
38584
+ return new URL(String(input), document.baseURI);
38585
+ } catch {
38586
+ return null;
38587
+ }
38588
+ };
38589
+ const isShield = (url) => url !== null && url.origin === location.origin && allowed.has(url.origin) && url.pathname === SHIELD_PATH;
38590
+ const receipt = () => JSON.stringify({ code: 0, success: true, msg: 'ok', data: { st: Date.now() } });
38591
+ if (cfg.shieldProfile) {
38592
+ const originalFetch = window.fetch;
38593
+ originals.fetch = originalFetch;
38594
+ window.fetch = nativeLike(function (input) {
38595
+ if (isShield(resolveUrl(input))) {
38596
+ return Promise.resolve(new Response(receipt(), { status: 200, headers: { 'content-type': 'application/json' } }));
38597
+ }
38598
+ return originalFetch.apply(this, arguments);
38599
+ });
38600
+ const originalSendBeacon = navigator.sendBeacon.bind(navigator);
38601
+ originals.sendBeacon = originalSendBeacon;
38602
+ navigator.sendBeacon = nativeLike(function (url) {
38603
+ if (isShield(resolveUrl(url))) return true;
38604
+ return originalSendBeacon.apply(navigator, arguments);
38605
+ });
38606
+ const XHR = window.XMLHttpRequest;
38607
+ const originalOpen = XHR.prototype.open;
38608
+ const originalSend = XHR.prototype.send;
38609
+ const originalAbort = XHR.prototype.abort;
38610
+ originals.xhr = { XHR, open: originalOpen, send: originalSend, abort: originalAbort };
38611
+ XHR.prototype.open = nativeLike(function (method, url) {
38612
+ this['__kkShield'] = isShield(resolveUrl(url))
38613
+ ? { async: arguments.length < 3 || arguments[2] !== false, aborted: false, delivered: false }
38614
+ : null;
38615
+ return originalOpen.apply(this, arguments);
38616
+ });
38617
+ XHR.prototype.send = nativeLike(function () {
38618
+ const hit = this['__kkShield'];
38619
+ if (!hit) return originalSend.apply(this, arguments);
38620
+ if (!hit.async) throw new Error('kk anti-detection: synchronous XHR to the shield path is not supported');
38621
+ const self = this;
38622
+ const define = (name, value) => {
38623
+ try {
38624
+ Object.defineProperty(self, name, { value, configurable: true });
38625
+ } catch {}
38626
+ };
38627
+ setTimeout(() => {
38628
+ if (hit.aborted || hit.delivered) return;
38629
+ hit.delivered = true;
38630
+ const body = receipt();
38631
+ define('readyState', 4);
38632
+ define('status', 200);
38633
+ define('statusText', 'OK');
38634
+ define('responseText', body);
38635
+ if (self.responseType === 'json') define('response', JSON.parse(body));
38636
+ else define('response', body);
38637
+ self.dispatchEvent(new Event('readystatechange'));
38638
+ self.dispatchEvent(new Event('load'));
38639
+ self.dispatchEvent(new Event('loadend'));
38640
+ }, 0);
38641
+ });
38642
+ XHR.prototype.abort = nativeLike(function () {
38643
+ if (this['__kkShield']) this['__kkShield'].aborted = true;
38644
+ return originalAbort.apply(this, arguments);
38645
+ });
38646
+ }
38647
+ if (cfg.suppressFileChooser) {
38648
+ const originalClick = HTMLInputElement.prototype.click;
38649
+ originals.inputClick = originalClick;
38650
+ HTMLInputElement.prototype.click = nativeLike(function () {
38651
+ const install = window['${INSTALL_KEY}'];
38652
+ const batch = install ? install.pending : null;
38653
+ if (!(this instanceof HTMLInputElement) || this.type !== 'file' || !Array.isArray(batch)) {
38654
+ return originalClick.apply(this, arguments);
38655
+ }
38656
+ // Enforce the multiple constraint BEFORE consumption: a single-file input keeps native
38657
+ // behavior and the queue stays available for a multiple-capable input.
38658
+ if (batch.length > 1 && !this.multiple) return originalClick.apply(this, arguments);
38659
+ const transfer = new DataTransfer();
38660
+ for (const file of batch) transfer.items.add(file);
38661
+ // Assignment first: a failure here preserves the queue and surfaces the real error.
38662
+ this.files = transfer.files;
38663
+ install.pending = null;
38664
+ this.dispatchEvent(new Event('input', { bubbles: true }));
38665
+ this.dispatchEvent(new Event('change', { bubbles: true }));
38666
+ });
38667
+ }
38668
+ window['${INSTALL_KEY}'] = { signature, originals, chooser: cfg.suppressFileChooser };
38669
+ })()`;
38670
+ }
38671
+ async function injectBrowserAntiDetection(page, options = {}) {
38672
+ const config = resolveOptions(options);
38673
+ if (config === undefined)
38674
+ return;
38675
+ await page.evaluate(installerScript(config));
38676
+ await page.addInitScript(installerScript(config));
38677
+ }
38678
+ function validatePendingFiles(files) {
38679
+ if (files.length === 0)
38680
+ throw new Error("kk file queue: files must be a nonempty batch");
38681
+ if (files.length > MAX_QUEUED_FILES) {
38682
+ throw new Error(`kk file queue: at most ${MAX_QUEUED_FILES} files per batch, got ${files.length}`);
38683
+ }
38684
+ let totalBytes = 0;
38685
+ for (const file of files) {
38686
+ if (typeof file?.name !== "string" || file.name.trim() === "") {
38687
+ throw new Error("kk file queue: every file needs a nonempty name");
38688
+ }
38689
+ if (typeof file?.mimeType !== "string" || !/^[\w.+-]+\/[\w.+-]+$/.test(file.mimeType)) {
38690
+ throw new Error(`kk file queue: file '${file.name}' needs a type/subtype MIME string`);
38691
+ }
38692
+ if (typeof file?.base64 !== "string" || file.base64.length === 0) {
38693
+ throw new Error(`kk file queue: file '${file.name}' needs nonempty base64 bytes`);
38694
+ }
38695
+ const bytes = Buffer.from(file.base64, "base64");
38696
+ if (bytes.toString("base64") !== file.base64) {
38697
+ throw new Error(`kk file queue: file '${file.name}' base64 is not valid standard base64`);
38698
+ }
38699
+ totalBytes += bytes.length;
38700
+ }
38701
+ if (totalBytes > MAX_QUEUED_BYTES) {
38702
+ throw new Error(`kk file queue: decoded batch is ${totalBytes} bytes, above the ${MAX_QUEUED_BYTES}-byte limit`);
38703
+ }
38704
+ return totalBytes;
38705
+ }
38706
+ function queueScript(files) {
38707
+ const payload = JSON.stringify(files);
38708
+ return `(() => {
38709
+ const install = window['${INSTALL_KEY}'];
38710
+ if (!install || !install.chooser) {
38711
+ throw new Error('kk file queue: no chooser hook is installed on this document \u2014 call injectBrowserAntiDetection with suppressFileChooser first');
38712
+ }
38713
+ install.pending = ${payload}.map((file) => {
38714
+ const bytes = Uint8Array.from(atob(file.base64), (char) => char.charCodeAt(0));
38715
+ return new File([bytes], file.name, { type: file.mimeType });
38716
+ });
38717
+ })();`;
38718
+ }
38719
+ async function queueBrowserFiles(page, files) {
38720
+ validatePendingFiles(files);
38721
+ await page.evaluate(queueScript(files));
38722
+ }
38723
+ var SHIELD_PATH = "/shield/webprofile", INSTALL_KEY = "__kkAntiDetection", MAX_QUEUED_FILES = 20, MAX_QUEUED_BYTES;
38724
+ var init_browser_anti_detection = __esm(() => {
38725
+ MAX_QUEUED_BYTES = 8 * 1024 * 1024;
38726
+ });
38727
+
38538
38728
  // ../../packages/publish-harness/src/browser.ts
38539
38729
  var exports_browser = {};
38540
38730
  __export(exports_browser, {
@@ -38560,6 +38750,7 @@ __export(exports_browser, {
38560
38750
  fillCodeMirror: () => fillCodeMirror,
38561
38751
  findLeftoverTransaction: () => findLeftoverTransaction,
38562
38752
  findShadowControl: () => findShadowControl,
38753
+ injectBrowserAntiDetection: () => injectBrowserAntiDetection,
38563
38754
  isSafeBotName: () => isSafeBotName,
38564
38755
  kkBrowserLauncher: () => kkBrowserLauncher,
38565
38756
  listBots: () => listBots,
@@ -38571,6 +38762,7 @@ __export(exports_browser, {
38571
38762
  openChannelSession: () => openChannelSession,
38572
38763
  parseBotMetadata: () => parseBotMetadata,
38573
38764
  pasteHtml: () => pasteHtml,
38765
+ queueBrowserFiles: () => queueBrowserFiles,
38574
38766
  readBotMetadata: () => readBotMetadata,
38575
38767
  readBotSavedMetadata: () => readBotSavedMetadata,
38576
38768
  readScopedSignal: () => readScopedSignal,
@@ -39255,6 +39447,7 @@ var init_browser = __esm(() => {
39255
39447
  init_src();
39256
39448
  init_profile_dir2();
39257
39449
  init_profile_lease();
39450
+ init_browser_anti_detection();
39258
39451
  init_bot();
39259
39452
  init_bot_transaction();
39260
39453
  init_profile_lease();
@@ -41446,6 +41639,83 @@ import { existsSync as existsSync3, readFileSync as readFileSync5, statSync as s
41446
41639
  import { homedir as homedir2 } from "os";
41447
41640
  import { isAbsolute, join as join2, resolve as resolve2 } from "path";
41448
41641
 
41642
+ // ../../packages/kk-core/src/content-limits.ts
41643
+ var PLATFORM_CONTENT_LIMITS = Object.freeze({
41644
+ schemaVersion: 5,
41645
+ tagLimits: Object.freeze({
41646
+ default: 7,
41647
+ imageText: Object.freeze({
41648
+ instagram: 5,
41649
+ douyin: 5,
41650
+ kuaishou: 4,
41651
+ weixin: 1,
41652
+ xiaohongshu: 10
41653
+ }),
41654
+ shortVideo: Object.freeze({
41655
+ douyin: 5,
41656
+ kuaishou: 4,
41657
+ xiaohongshu: 10,
41658
+ bilibili: 10,
41659
+ zhihu: 5,
41660
+ tiktok: 20,
41661
+ youtube: 60
41662
+ })
41663
+ }),
41664
+ titleCapabilities: Object.freeze({
41665
+ dynamic: Object.freeze({
41666
+ bilibili: Object.freeze({ max: 20, unit: "utf16_code_units" }),
41667
+ zhihu: Object.freeze({ max: 50, unit: "utf16_code_units" }),
41668
+ douban: Object.freeze({ max: 70, unit: "utf16_code_units" }),
41669
+ x: null,
41670
+ threads: null,
41671
+ weibo: null,
41672
+ jike: null,
41673
+ zsxq: null,
41674
+ toutiao: null,
41675
+ juejin: null
41676
+ }),
41677
+ imageText: Object.freeze({
41678
+ xiaohongshu: Object.freeze({ max: 20, unit: "xiaohongshu_title_weighted" }),
41679
+ weixin: Object.freeze({ max: 20, unit: "utf16_code_units" }),
41680
+ douyin: Object.freeze({ max: 20, unit: "utf16_code_units" }),
41681
+ shipinhao: Object.freeze({ max: 22, unit: "utf16_code_units" }),
41682
+ reddit: Object.freeze({ max: 300, unit: "utf16_code_units" })
41683
+ }),
41684
+ article: Object.freeze({
41685
+ juejin: Object.freeze({ max: 100, unit: "utf16_code_units" }),
41686
+ csdn: Object.freeze({ max: 100, unit: "utf16_code_units" }),
41687
+ cnblogs: Object.freeze({ max: 128, unit: "utf16_code_units" }),
41688
+ weibo: Object.freeze({ max: 30, unit: "utf16_code_units" }),
41689
+ sspai: Object.freeze({ max: 80, unit: "utf16_code_units" })
41690
+ }),
41691
+ shortVideo: Object.freeze({
41692
+ shipinhao: Object.freeze({ max: 16, unit: "utf16_code_units" }),
41693
+ douyin: Object.freeze({ max: 30, unit: "utf16_code_units" }),
41694
+ bilibili: Object.freeze({ max: 80, unit: "utf16_code_units" })
41695
+ })
41696
+ }),
41697
+ bodyCapabilities: Object.freeze({
41698
+ dynamic: Object.freeze({
41699
+ threads: Object.freeze({ max: 500, unit: "unicode_code_points", behavior: "split_thread" }),
41700
+ x: Object.freeze({ max: 280, unit: "weighted", behavior: "split_thread" }),
41701
+ weibo: Object.freeze({ max: 2000, unit: "unicode_code_points" }),
41702
+ jike: Object.freeze({ max: 1000, unit: "unicode_code_points" })
41703
+ }),
41704
+ imageText: Object.freeze({
41705
+ xiaohongshu: Object.freeze({ max: 1000, unit: "unicode_code_points" }),
41706
+ douyin: Object.freeze({ max: 1000, unit: "unicode_code_points" })
41707
+ }),
41708
+ article: Object.freeze({
41709
+ wechatmp: Object.freeze({ max: 50000, unit: "unicode_code_points" }),
41710
+ juejin: Object.freeze({ max: 50000, unit: "unicode_code_points" })
41711
+ }),
41712
+ shortVideo: Object.freeze({
41713
+ douyin: Object.freeze({ max: 1000, unit: "unicode_code_points" }),
41714
+ kuaishou: Object.freeze({ max: 1000, unit: "unicode_code_points" }),
41715
+ shipinhao: Object.freeze({ max: 1000, unit: "unicode_code_points" })
41716
+ })
41717
+ })
41718
+ });
41449
41719
  // ../../packages/kk-core/src/kinds.ts
41450
41720
  init_zod();
41451
41721
 
@@ -41567,6 +41837,67 @@ var BrowserSessionDescriptorSchema = object2({
41567
41837
  }
41568
41838
  }).optional()
41569
41839
  }).strict();
41840
+ var ContentCountUnitSchema = _enum2([
41841
+ "utf16_code_units",
41842
+ "unicode_code_points",
41843
+ "weighted",
41844
+ "xiaohongshu_title_weighted"
41845
+ ]);
41846
+ var LimitEntrySchema = object2({
41847
+ max: number2().int().positive(),
41848
+ unit: ContentCountUnitSchema
41849
+ }).strict();
41850
+ var PublishingProfileLimitsSchema = object2({
41851
+ title: LimitEntrySchema.optional(),
41852
+ body: LimitEntrySchema.optional()
41853
+ }).strict();
41854
+ var PublishingProfileRequiresSchema = object2({
41855
+ cover: boolean2().optional(),
41856
+ orderedImages: boolean2().optional()
41857
+ }).strict();
41858
+ var PublishingProfileModeBindingSchema = object2({
41859
+ metadata: record(string2(), unknown())
41860
+ }).strict();
41861
+ var PublishingProfileModesSchema = object2({
41862
+ draft: PublishingProfileModeBindingSchema.optional(),
41863
+ live: PublishingProfileModeBindingSchema.optional()
41864
+ }).strict().superRefine((modes, ctx) => {
41865
+ if (modes.draft === undefined && modes.live === undefined) {
41866
+ ctx.addIssue({
41867
+ code: "custom",
41868
+ message: 'modes must define at least one of "draft" or "live"'
41869
+ });
41870
+ }
41871
+ });
41872
+ var PublishingProfileSchema = object2({
41873
+ format: string2().min(1),
41874
+ locale: string2().regex(/^(source|[a-z]{2,3}(-[A-Za-z0-9]+)*)$/, {
41875
+ message: 'locale must be "source" or a valid language tag'
41876
+ }),
41877
+ limits: PublishingProfileLimitsSchema.optional(),
41878
+ requires: PublishingProfileRequiresSchema.optional(),
41879
+ modes: PublishingProfileModesSchema
41880
+ }).strict();
41881
+ var PublishingCapabilitiesSchema = object2({
41882
+ defaultProfile: string2().min(1),
41883
+ profiles: record(string2().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/), PublishingProfileSchema)
41884
+ }).strict().superRefine((data, ctx) => {
41885
+ const keys = Object.keys(data.profiles);
41886
+ if (keys.length === 0) {
41887
+ ctx.addIssue({
41888
+ code: "custom",
41889
+ path: ["profiles"],
41890
+ message: "profiles record must be nonempty"
41891
+ });
41892
+ }
41893
+ if (!(data.defaultProfile in data.profiles)) {
41894
+ ctx.addIssue({
41895
+ code: "custom",
41896
+ path: ["defaultProfile"],
41897
+ message: `defaultProfile "${data.defaultProfile}" must reference a declared profile`
41898
+ });
41899
+ }
41900
+ });
41570
41901
  var PluginManifestSchemaBase = looseObject({
41571
41902
  name: string2().min(1),
41572
41903
  kind: PluginKindSchema,
@@ -41575,23 +41906,31 @@ var PluginManifestSchemaBase = looseObject({
41575
41906
  version: string2().optional(),
41576
41907
  description: string2().optional(),
41577
41908
  operations: record(string2(), PluginOperationSchema).optional(),
41578
- browserSession: BrowserSessionDescriptorSchema.optional()
41909
+ browserSession: BrowserSessionDescriptorSchema.optional(),
41910
+ publishing: PublishingCapabilitiesSchema.optional()
41579
41911
  });
41580
41912
  var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx) => {
41581
- if (manifest.browserSession === undefined)
41582
- return;
41583
- if (manifest.kind !== "publish") {
41584
- ctx.addIssue({
41585
- code: "custom",
41586
- path: ["browserSession"],
41587
- message: `browserSession requires kind "publish", not "${manifest.kind}"`
41588
- });
41913
+ if (manifest.browserSession !== undefined) {
41914
+ if (manifest.kind !== "publish") {
41915
+ ctx.addIssue({
41916
+ code: "custom",
41917
+ path: ["browserSession"],
41918
+ message: `browserSession requires kind "publish", not "${manifest.kind}"`
41919
+ });
41920
+ }
41921
+ if (manifest.check === undefined) {
41922
+ ctx.addIssue({
41923
+ code: "custom",
41924
+ path: ["browserSession"],
41925
+ message: "browserSession requires a check probe entry that owns the session routines"
41926
+ });
41927
+ }
41589
41928
  }
41590
- if (manifest.check === undefined) {
41929
+ if (manifest.publishing !== undefined && manifest.kind !== "publish") {
41591
41930
  ctx.addIssue({
41592
41931
  code: "custom",
41593
- path: ["browserSession"],
41594
- message: "browserSession requires a check probe entry that owns the session routines"
41932
+ path: ["publishing"],
41933
+ message: `publishing requires kind "publish", not "${manifest.kind}"`
41595
41934
  });
41596
41935
  }
41597
41936
  });
@@ -41834,7 +42173,8 @@ function consumeManifest(dirPath, expectedKind, origin, plugins, diagnostics, se
41834
42173
  version: manifest.version,
41835
42174
  description: manifest.description,
41836
42175
  ...manifest.operations !== undefined ? { operations: manifest.operations } : {},
41837
- ...manifest.browserSession !== undefined ? { browserSession: manifest.browserSession } : {}
42176
+ ...manifest.browserSession !== undefined ? { browserSession: manifest.browserSession } : {},
42177
+ ...manifest.publishing !== undefined ? { publishing: manifest.publishing } : {}
41838
42178
  });
41839
42179
  }
41840
42180
  function listSortedDir(absDir) {
@@ -42257,7 +42597,7 @@ var WORKFLOW_SETTINGS = {
42257
42597
  "kk-daily-ai-voice2": {
42258
42598
  window: { kind: "duration" },
42259
42599
  language: { kind: "choice", values: ["en", "zh", "ja"] },
42260
- vdriver: { kind: "choice", values: ["voicebox", "ominivoice", "gemini"] },
42600
+ vdriver: { kind: "choice", values: ["voicebox", "ominivoice", "gemini", "voxcpm"] },
42261
42601
  skip_review: { kind: "boolean" },
42262
42602
  transcode_mp3: { kind: "boolean" },
42263
42603
  cover_enabled: { kind: "boolean" },
@@ -42290,7 +42630,13 @@ var WORKFLOW_SETTINGS = {
42290
42630
  cover_enabled: { kind: "boolean" },
42291
42631
  inline_images: { kind: "boolean" },
42292
42632
  max_revisions: { kind: "integer" },
42293
- source_locale: { kind: "choice", values: ["en", "zh", "ja"] }
42633
+ source_locale: { kind: "choice", values: ["en", "zh", "ja"] },
42634
+ writer: { kind: "choice", values: ["itc-generating", "topic"] },
42635
+ playbook: { kind: "choice", values: ["generic", "english", "wechat"] },
42636
+ publish_preset: { kind: "string" },
42637
+ topic_profile: { kind: "string" },
42638
+ publish_targets: { kind: "string" },
42639
+ seo: { kind: "choice", values: ["off", "handoff"] }
42294
42640
  }
42295
42641
  };
42296
42642
  var CONFIDENCE_LEVELS = ["HIGH", "MEDIUM", "LOW"];
@@ -43374,6 +43720,15 @@ function revisionUsage(progress, settings) {
43374
43720
  }
43375
43721
  return { revise, autoRevise, total: revise + autoRevise, budget: revisionBudget(settings), recorded: true };
43376
43722
  }
43723
+ function revisionUsageFromChild(parentProgress, childEvidence, childProgress, settings) {
43724
+ const parentStates = asRecord2(parentProgress)?.states;
43725
+ if (Array.isArray(parentStates))
43726
+ return revisionUsage(parentProgress, settings);
43727
+ if (childEvidence.childRunId === null) {
43728
+ return { revise: null, autoRevise: null, total: null, budget: revisionBudget(settings), recorded: false };
43729
+ }
43730
+ return revisionUsage(childProgress, settings);
43731
+ }
43377
43732
  function resultRows(parsed) {
43378
43733
  if (parsed === null)
43379
43734
  return [];
@@ -43516,11 +43871,17 @@ function projectItcResults(input) {
43516
43871
  selectedOutline: preview("outline-approved"),
43517
43872
  draft: preview("draft"),
43518
43873
  judge,
43519
- revisions: revisionUsage(input.progress, record.settings),
43874
+ revisions: input.child === undefined ? revisionUsage(input.progress, record.settings) : revisionUsageFromChild(input.progress, input.child.evidence, input.child.progress, record.settings),
43520
43875
  targets,
43521
43876
  report,
43522
43877
  counts
43523
43878
  };
43879
+ if (input.child !== undefined && input.child.evidence.reason !== null) {
43880
+ diagnostics.push({
43881
+ code: "board.itc.child-unverified",
43882
+ message: `${input.child.evidence.reason}; revision counts and child progress are unavailable.`
43883
+ });
43884
+ }
43524
43885
  if (resultSpecs.length === 0 && reportRecord === null) {
43525
43886
  diagnostics.push({
43526
43887
  code: "board.publish.unavailable",
@@ -45790,7 +46151,7 @@ var ITEM_DECISION_IDS = [
45790
46151
  ];
45791
46152
  var FIRST_SEEN_AT = "first_seen_at";
45792
46153
  var DEFAULT_EXTRACT_LIMIT = 20;
45793
- var PROFILE_KEYS = new Set(["topic", "where", "order", "limit"]);
46154
+ var PROFILE_KEYS = new Set(["topic", "where", "order", "limit", "publishPreset"]);
45794
46155
 
45795
46156
  class ProfileInvalidError extends Error {
45796
46157
  constructor(message) {
@@ -45884,7 +46245,14 @@ function parseProfile(yaml) {
45884
46245
  }
45885
46246
  limit = document2.limit;
45886
46247
  }
45887
- return { topic: topic.trim(), where, order, limit };
46248
+ let publishPreset;
46249
+ if (document2.publishPreset !== undefined) {
46250
+ if (typeof document2.publishPreset !== "string" || !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(document2.publishPreset.trim())) {
46251
+ throw new ProfileInvalidError(`publishPreset: expected a lowercase hyphenated slug, got ${JSON.stringify(document2.publishPreset)}`);
46252
+ }
46253
+ publishPreset = document2.publishPreset.trim();
46254
+ }
46255
+ return { topic: topic.trim(), where, order, limit, ...publishPreset !== undefined ? { publishPreset } : {} };
45888
46256
  }
45889
46257
  function compileProfile(profile, options) {
45890
46258
  return {
@@ -45902,7 +46270,7 @@ init_output2();
45902
46270
 
45903
46271
  // src/stage.ts
45904
46272
  init_dist();
45905
- import { spawnSync as spawnSync3 } from "child_process";
46273
+ import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
45906
46274
  import { realpathSync as realpathSync4, statSync as statSync6 } from "fs";
45907
46275
  import { dirname as dirname6, join as join5, resolve as resolve6 } from "path";
45908
46276
  var STAGES_REL = "plugins/kk/scripts/kk-workflow-stages.ts";
@@ -45964,6 +46332,64 @@ function forwardChildOutput(chunk, write) {
45964
46332
  write(text.endsWith(`
45965
46333
  `) ? text.slice(0, -1) : text);
45966
46334
  }
46335
+ var FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
46336
+ var CANCEL_ESCALATION_MS = 5000;
46337
+ var SIGNAL_EXIT_CODES = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 };
46338
+ function runStageCancellable(args, execPath = process.execPath, argv1) {
46339
+ const script = resolveStagesScript(argv1);
46340
+ return new Promise((resolvePromise, rejectPromise) => {
46341
+ const child = spawn2(execPath, [script, ...args], {
46342
+ stdio: ["ignore", "pipe", "pipe"],
46343
+ env: process.env,
46344
+ detached: true
46345
+ });
46346
+ const stdoutChunks = [];
46347
+ const stderrChunks = [];
46348
+ child.stdout?.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk)));
46349
+ child.stderr?.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk)));
46350
+ let escalation;
46351
+ const forward = (signal) => {
46352
+ const pid = child.pid;
46353
+ if (pid === undefined)
46354
+ return;
46355
+ try {
46356
+ process.kill(-pid, signal);
46357
+ } catch {
46358
+ try {
46359
+ child.kill(signal);
46360
+ } catch {}
46361
+ }
46362
+ escalation ??= setTimeout(() => {
46363
+ try {
46364
+ process.kill(-pid, "SIGKILL");
46365
+ } catch {}
46366
+ }, CANCEL_ESCALATION_MS);
46367
+ };
46368
+ const handlers = new Map;
46369
+ for (const signal of FORWARDED_SIGNALS) {
46370
+ const handler = () => forward(signal);
46371
+ handlers.set(signal, handler);
46372
+ process.on(signal, handler);
46373
+ }
46374
+ const disarm = () => {
46375
+ for (const [signal, handler] of handlers)
46376
+ process.removeListener(signal, handler);
46377
+ handlers.clear();
46378
+ if (escalation !== undefined)
46379
+ clearTimeout(escalation);
46380
+ };
46381
+ child.on("error", (err) => {
46382
+ disarm();
46383
+ rejectPromise(new Error(`failed to spawn stage runner at ${script}: ${err.message}`));
46384
+ });
46385
+ child.on("close", (code, signal) => {
46386
+ disarm();
46387
+ forwardChildOutput(Buffer.concat(stdoutChunks), echo);
46388
+ forwardChildOutput(Buffer.concat(stderrChunks), echoError);
46389
+ resolvePromise(code ?? (signal === null ? 1 : SIGNAL_EXIT_CODES[signal] ?? 1));
46390
+ });
46391
+ });
46392
+ }
45967
46393
 
45968
46394
  // src/commands/daily.ts
45969
46395
  var parseJson3 = JSON.parse;
@@ -46867,6 +47293,7 @@ async function dispatchOne(target, dispatch, timeoutMs) {
46867
47293
  ok: false,
46868
47294
  status: "failed",
46869
47295
  error: result.error ?? result.message ?? "publish failed",
47296
+ ...result.metadata !== undefined ? { metadata: result.metadata } : {},
46870
47297
  durationMs
46871
47298
  };
46872
47299
  } catch (err) {
@@ -47322,9 +47749,9 @@ function registerExecutorCommand(program) {
47322
47749
  withRunOptions(executor.command("run").description("Invoke a plugin with input/output files and contract validation").argument("<plugin>", "Name of the plugin to invoke")).action(runInvokeAction);
47323
47750
  withFaninOptions(executor.command("fan-in").alias("fanin").description("Merge N Doc[] JSON inputs into one deduplicated Doc[] output")).action(runFaninAction);
47324
47751
  withFanoutOptions(executor.command("fan-out").alias("fanout").description("Publish one Content JSON to N targets, aggregating per-target results")).action(runFanoutAction);
47325
- withRunOptions(program.command("invoke", { hidden: true }).description("Alias for `executor run` (deprecated)").argument("<plugin>", "Name of the plugin to invoke")).action(runInvokeAction);
47326
- withFaninOptions(program.command("fanin", { hidden: true }).description("Alias for `executor fan-in` (deprecated)")).action(runFaninAction);
47327
- withFanoutOptions(program.command("fanout", { hidden: true }).description("Alias for `executor fan-out` (deprecated)")).action(runFanoutAction);
47752
+ withRunOptions(program.command("invoke", { hidden: true }).description("[deprecated] Alias for `executor run`").argument("<plugin>", "Name of the plugin to invoke")).action(runInvokeAction);
47753
+ withFaninOptions(program.command("fanin", { hidden: true }).description("[deprecated] Alias for `executor fan-in`")).action(runFaninAction);
47754
+ withFanoutOptions(program.command("fanout", { hidden: true }).description("[deprecated] Alias for `executor fan-out`")).action(runFanoutAction);
47328
47755
  }
47329
47756
 
47330
47757
  // src/cli.ts
@@ -47381,9 +47808,10 @@ async function lockReleaseAction(options) {
47381
47808
  }
47382
47809
  }
47383
47810
  function registerLockCommand(parent, hidden = false) {
47384
- const lock = parent.command("lock", { hidden }).description("Single-flight locks over the store");
47385
- lock.command("acquire").description("Take the named lock for this run; prints {acquired} or {skipped, holder} (exit 0 both ways)").requiredOption("--name <lock>", "Lock name (e.g. collect)").requiredOption("--run <id>", "Owning run id (re-entrant for the same id)").option("--ttl <ms>", "Claim lifetime for a dead holder, in milliseconds", String(DEFAULT_LOCK_TTL_MS)).option("--db <path>", "Store database path (default: kk config store.db_path, else .works/kk.db)").action(lockAcquireAction);
47386
- lock.command("release").description("Release the named lock iff this run still owns it; prints {released}").requiredOption("--name <lock>", "Lock name (e.g. collect)").requiredOption("--run <id>", "Owning run id").option("--db <path>", "Store database path (default: kk config store.db_path, else .works/kk.db)").action(lockReleaseAction);
47811
+ const legacy = hidden ? "[deprecated] " : "";
47812
+ const lock = parent.command("lock", { hidden }).description(`${legacy}Single-flight locks over the store`);
47813
+ lock.command("acquire").description(`${legacy}Take the named lock for this run; prints {acquired} or {skipped, holder} (exit 0 both ways)`).requiredOption("--name <lock>", "Lock name (e.g. collect)").requiredOption("--run <id>", "Owning run id (re-entrant for the same id)").option("--ttl <ms>", "Claim lifetime for a dead holder, in milliseconds", String(DEFAULT_LOCK_TTL_MS)).option("--db <path>", "Store database path (default: kk config store.db_path, else .works/kk.db)").action(lockAcquireAction);
47814
+ lock.command("release").description(`${legacy}Release the named lock iff this run still owns it; prints {released}`).requiredOption("--name <lock>", "Lock name (e.g. collect)").requiredOption("--run <id>", "Owning run id").option("--db <path>", "Store database path (default: kk config store.db_path, else .works/kk.db)").action(lockReleaseAction);
47387
47815
  }
47388
47816
 
47389
47817
  // src/commands/plugin.ts
@@ -47590,12 +48018,12 @@ function registerPluginCommand(program) {
47590
48018
  registerStatusVerb(plugin, "status", "List discovered plugins grouped by kind with a per-plugin health mark", false);
47591
48019
  plugin.command("check [names...]").description("Run each selected plugin\u2019s readiness probe; exits nonzero on refused or errored plugins").option("--kind <kind>", "Filter plugins by kind (ingestion, generator, publish)").option("--json", "Output machine-readable verdict rows").option("--headed", "Run probes with a visible browser window").option("--timeout <ms>", "Per-plugin probe timeout in milliseconds").option("--plugins-root <dir>", "Exclusive root containing plugins (isolation/tests only)").option("--plugins-path <dir>", "Extra plugin root to union with defaults (repeatable)", collectPath, []).action(checkAction);
47592
48020
  plugin.command("login <name>").description("Run the plugin\u2019s interactive login probe with inherited stdio").option("--plugins-root <dir>", "Exclusive root containing plugins (isolation/tests only)").option("--plugins-path <dir>", "Extra plugin root to union with defaults (repeatable)", collectPath, []).action(loginAction);
47593
- registerStatusVerb(plugin, "list", "Alias for `plugin status` (deprecated)", true);
47594
- registerStatusVerb(plugin, "doctor", "Alias for `plugin status` (deprecated)", true);
47595
- const plugins = program.command("plugins", { hidden: true }).description("Alias for `plugin status` (deprecated)");
47596
- registerStatusVerb(plugins, "status", "Alias for `plugin status` (deprecated)", true);
47597
- registerStatusVerb(plugins, "list", "Alias for `plugin status` (deprecated)", true);
47598
- registerStatusVerb(plugins, "doctor", "Alias for `plugin status` (deprecated)", true);
48021
+ registerStatusVerb(plugin, "list", "[deprecated] Alias for `plugin status`", true);
48022
+ registerStatusVerb(plugin, "doctor", "[deprecated] Alias for `plugin status`", true);
48023
+ const plugins = program.command("plugins", { hidden: true }).description("[deprecated] Alias for `plugin status`");
48024
+ registerStatusVerb(plugins, "status", "[deprecated] Alias for `plugin status`", true);
48025
+ registerStatusVerb(plugins, "list", "[deprecated] Alias for `plugin status`", true);
48026
+ registerStatusVerb(plugins, "doctor", "[deprecated] Alias for `plugin status`", true);
47599
48027
  }
47600
48028
 
47601
48029
  // src/commands/schedule.ts
@@ -47655,123 +48083,2421 @@ function registerScheduleCommand(program) {
47655
48083
 
47656
48084
  // src/commands/stage.ts
47657
48085
  init_commander();
47658
- var ROOT_GLOBALS = new Set(["-v", "--verbose"]);
47659
- function normalizeStageArgs(argv) {
47660
- let i = 2;
47661
- while (i < argv.length && ROOT_GLOBALS.has(argv[i])) {
47662
- i += 1;
47663
- }
47664
- const next = argv[i + 1];
47665
- if (argv[i] !== "stage" || next === undefined || next === "run" || next === "--help" || next === "-h") {
47666
- return [...argv];
48086
+
48087
+ // src/itc-stage.ts
48088
+ import { createHash as createHash7 } from "crypto";
48089
+ import { join as join19 } from "path";
48090
+ init_dist3();
48091
+ init_dist();
48092
+
48093
+ // src/itc-config.ts
48094
+ init_dist3();
48095
+ import { createHash as createHash2 } from "crypto";
48096
+ import { dirname as dirname10, isAbsolute as isAbsolute8, join as join15, resolve as resolve18 } from "path";
48097
+
48098
+ class ItcConfigInvalidError extends Error {
48099
+ constructor(message) {
48100
+ super(message);
48101
+ this.name = "ItcConfigInvalidError";
47667
48102
  }
47668
- return [...argv.slice(0, i + 1), "run", ...argv.slice(i + 1)];
47669
- }
47670
- function registerStageCommand(program) {
47671
- program.command("stage").description("Run a kk-workflow-stages.ts stage (workflow orchestration steps)").command("run").description("Run one stage by name with positional pass-through").argument("<args...>", "Stage name followed by its positional arguments").allowUnknownOption(true).action((args) => {
47672
- const code = runStage(args);
47673
- if (code !== 0) {
47674
- throw new CommanderError(code, "kk.stage.failed", `stage failed with exit code ${code}`);
47675
- }
47676
- });
47677
48103
  }
47678
48104
 
47679
- // src/commands/store.ts
47680
- import { dirname as dirname10, join as join15, resolve as resolve18 } from "path";
47681
- init_dist3();
47682
- init_commander();
47683
- init_output2();
47684
- var HEURISTIC_MAKER = "heuristic";
47685
- var LABEL_PLUGIN = "episode-plan-gen";
47686
- var LABEL_SCRATCH_DIR = "label";
47687
- var PROFILE_DIR_REL = "plugins/kk/decisions/extract";
47688
- var CHECKOUT_ROOT3 = resolve18(import.meta.dir, "../../../..");
47689
- var CATALOG_PATH_REL = "plugins/kk/decisions/kk-items.yaml";
47690
- var TOPIC_SLUG = /^[a-z0-9][a-z0-9-]*$/;
47691
- var WINDOW_UNITS = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
47692
- var DEFAULT_EXTRACT_WINDOW = "24h";
47693
- var CATEGORY_DECISION_ID = "kk-item-category";
47694
- var CATEGORY_LABELS = ["ai-news", "ai-technical", "ai-research", "ai-industry", "non-ai"];
47695
- var SCORE_DIMENSIONS = ["quality", "importance", "urgency", "impact"];
47696
- var AUDIENCE_LABELS = ["general", "practitioner", "researcher"];
47697
- var LABEL_DECISIONS = [
47698
- { id: CATEGORY_DECISION_ID, kind: "choice", labels: CATEGORY_LABELS },
47699
- { id: "kk-item-quality", kind: "score", levels: 6 },
47700
- { id: "kk-item-importance", kind: "score", levels: 6 },
47701
- { id: "kk-item-urgency", kind: "score", levels: 6 },
47702
- { id: "kk-item-impact", kind: "score", levels: 6 },
47703
- { id: CHINA_POLITICS_DECISION_ID, kind: "score", levels: 4 },
47704
- { id: "kk-item-sensitivity", kind: "score", levels: 4 },
47705
- { id: "kk-item-audience", kind: "choice", labels: AUDIENCE_LABELS }
47706
- ];
47707
- var HEURISTIC_DECISION_IDS = [
47708
- CATEGORY_DECISION_ID,
47709
- ...SCORE_DIMENSIONS.map((dimension) => `kk-item-${dimension}`)
47710
- ];
47711
- var KEYWORD_MAKER = "keyword";
47712
- var CHINA_POLITICS_BODY_HEAD = 200;
47713
- function failLoud5(code, error) {
47714
- const cause = error instanceof Error ? error.message : String(error);
47715
- const message = `${code}: ${cause}`;
47716
- consoleOutput.error(message);
47717
- throw new CommanderError(1, code, message);
48105
+ class ItcLaunchInvalidError extends Error {
48106
+ constructor(message) {
48107
+ super(message);
48108
+ this.name = "ItcLaunchInvalidError";
48109
+ }
48110
+ }
48111
+ var COMPILED_DEFAULTS = {
48112
+ writer: "itc-generating",
48113
+ playbook: "generic",
48114
+ sourceLocale: "en",
48115
+ research: false,
48116
+ judge: false,
48117
+ unslop: false,
48118
+ auto: false,
48119
+ maxRevisions: 2,
48120
+ coverEnabled: false,
48121
+ inlineImages: false,
48122
+ publishTargets: "",
48123
+ publishLive: false,
48124
+ seo: "off",
48125
+ dated: false
48126
+ };
48127
+ var WRITERS = new Set(["itc-generating", "topic"]);
48128
+ var PLAYBOOKS = new Set(["generic", "english", "wechat"]);
48129
+ var LOCALES = new Set(["en", "zh", "ja"]);
48130
+ var SEO_MODES = new Set(["off", "handoff"]);
48131
+ var ITC_KEYS = new Set([
48132
+ "writer",
48133
+ "playbook",
48134
+ "source_locale",
48135
+ "research",
48136
+ "judge",
48137
+ "unslop",
48138
+ "auto",
48139
+ "max_revisions",
48140
+ "topic_profile",
48141
+ "publish_preset",
48142
+ "workspace",
48143
+ "seo"
48144
+ ]);
48145
+ var SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
48146
+ function isSafeSlug(value) {
48147
+ return SLUG.test(value);
47718
48148
  }
47719
- async function migrateAction(options) {
48149
+ function slugifyTopicName(name) {
48150
+ if (name.includes("\x00"))
48151
+ return null;
48152
+ const normalized = name.normalize("NFC").toLowerCase();
48153
+ const unsafe = new RegExp(`[/\\\\${String.fromCharCode(0)}-${String.fromCharCode(31)}${String.fromCharCode(127)}]`);
48154
+ if (unsafe.test(normalized))
48155
+ return null;
48156
+ const slug = normalized.replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "");
48157
+ if (slug === "" || slug === "." || slug === "..")
48158
+ return null;
48159
+ return slug;
48160
+ }
48161
+ function expandHome3(value, homeDir) {
48162
+ if (value === "~")
48163
+ return homeDir;
48164
+ if (value.startsWith("~/"))
48165
+ return join15(homeDir, value.slice(2));
48166
+ return value;
48167
+ }
48168
+ function canonicalJson(value) {
48169
+ if (value === null || typeof value !== "object")
48170
+ return JSON.stringify(value ?? null);
48171
+ if (Array.isArray(value))
48172
+ return `[${value.map(canonicalJson).join(",")}]`;
48173
+ const entries = Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
48174
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
48175
+ }
48176
+ function digestItcOptions(options) {
48177
+ return createHash2("sha256").update(canonicalJson(options)).digest("hex");
48178
+ }
48179
+ function resolveItcConfigPath(env, homeDir) {
48180
+ const explicit = env.KK_CONFIG;
48181
+ if (explicit !== undefined && explicit.trim() !== "")
48182
+ return expandHome3(explicit.trim(), homeDir);
48183
+ return join15(homeDir, ".config", "kk", "config.yaml");
48184
+ }
48185
+ async function readItcConfigBlock(configPath, fs = createNodeFileSystem()) {
48186
+ if (!await fs.exists(configPath))
48187
+ return {};
48188
+ let text;
47720
48189
  try {
47721
- const dbPath = await resolveStorePath(resolveKkConfigPath(), options.db);
47722
- const store = await openStore({ path: dbPath });
47723
- const applied = store.appliedMigrations;
47724
- await store.close();
47725
- consoleOutput.write(JSON.stringify({ db: dbPath, applied }));
47726
- } catch (error) {
47727
- failLoud5("kk.store.migrate.failed", error);
48190
+ text = await fs.readFile(configPath);
48191
+ } catch {
48192
+ throw new ItcConfigInvalidError(`config file is unreadable: ${configPath}`);
47728
48193
  }
47729
- }
47730
- async function readDocList2(fs, path, label) {
47731
- if (!await fs.exists(path)) {
47732
- throw new Error(`input file not found: ${path}`);
48194
+ let parsed;
48195
+ try {
48196
+ parsed = Bun.YAML.parse(text);
48197
+ } catch {
48198
+ throw new ItcConfigInvalidError(`config file is not valid YAML: ${configPath}`);
47733
48199
  }
47734
- return readPluginJson(fs, path, DocListSchema, label, "is not a Doc[]");
48200
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
48201
+ throw new ItcConfigInvalidError(`config file has no top-level mapping: ${configPath}`);
48202
+ }
48203
+ const root = parsed;
48204
+ for (const key of ["works_dir", "workflows_dir"]) {
48205
+ if (root[key] !== undefined && (typeof root[key] !== "string" || root[key].trim() === "")) {
48206
+ throw new ItcConfigInvalidError(`config key '${key}' must be a nonempty path string`);
48207
+ }
48208
+ }
48209
+ const workflows = root.workflows;
48210
+ if (workflows === undefined)
48211
+ return {};
48212
+ if (workflows === null || typeof workflows !== "object" || Array.isArray(workflows)) {
48213
+ throw new ItcConfigInvalidError("config key 'workflows' must be a mapping");
48214
+ }
48215
+ const block = workflows["kk-itc"];
48216
+ if (block === undefined)
48217
+ return {};
48218
+ if (block === null || typeof block !== "object" || Array.isArray(block)) {
48219
+ throw new ItcConfigInvalidError("config key 'workflows.kk-itc' must be a mapping");
48220
+ }
48221
+ return validateItcBlock(block);
47735
48222
  }
47736
- async function invalidatedIds(items, docs) {
47737
- const byId = new Map;
47738
- for (const doc of docs) {
47739
- byId.set(itemId(doc), doc);
48223
+ function validateItcBlock(raw) {
48224
+ for (const key of Object.keys(raw)) {
48225
+ if (!ITC_KEYS.has(key)) {
48226
+ throw new ItcConfigInvalidError(`config key 'workflows.kk-itc.${key}' is not a declared ITC setting (allowed: ${[...ITC_KEYS].join(", ")})`);
48227
+ }
47740
48228
  }
47741
- const invalidated = [];
47742
- for (const [id, doc] of byId) {
47743
- const stored = await items.findById(id);
47744
- if (stored === undefined) {
47745
- continue;
48229
+ const out = {};
48230
+ const enumAt = (key, allowed) => {
48231
+ const value = raw[key];
48232
+ if (value === undefined)
48233
+ return;
48234
+ if (typeof value !== "string" || !allowed.has(value)) {
48235
+ throw new ItcConfigInvalidError(`config key 'workflows.kk-itc.${key}' must be one of ${[...allowed].join("|")}`);
47746
48236
  }
47747
- if (stored.title !== (doc.title ?? "") || stored.body !== doc.body) {
47748
- invalidated.push(id);
48237
+ return value;
48238
+ };
48239
+ const boolAt = (key) => {
48240
+ const value = raw[key];
48241
+ if (value === undefined)
48242
+ return;
48243
+ if (typeof value !== "boolean") {
48244
+ throw new ItcConfigInvalidError(`config key 'workflows.kk-itc.${key}' must be a boolean`);
48245
+ }
48246
+ return value;
48247
+ };
48248
+ const slugAt = (key) => {
48249
+ const value = raw[key];
48250
+ if (value === undefined)
48251
+ return;
48252
+ if (typeof value !== "string" || !isSafeSlug(value)) {
48253
+ throw new ItcConfigInvalidError(`config key 'workflows.kk-itc.${key}' must be a lowercase hyphenated slug`);
47749
48254
  }
48255
+ return value;
48256
+ };
48257
+ const writer = enumAt("writer", WRITERS);
48258
+ if (writer !== undefined)
48259
+ out.writer = writer;
48260
+ const playbook = enumAt("playbook", PLAYBOOKS);
48261
+ if (playbook !== undefined)
48262
+ out.playbook = playbook;
48263
+ const sourceLocale = enumAt("source_locale", LOCALES);
48264
+ if (sourceLocale !== undefined)
48265
+ out.source_locale = sourceLocale;
48266
+ for (const key of ["research", "judge", "unslop", "auto"]) {
48267
+ const value = boolAt(key);
48268
+ if (value !== undefined)
48269
+ out[key] = value;
47750
48270
  }
47751
- return invalidated;
47752
- }
47753
- function sourceCountsOf2(docs) {
47754
- const counts = {};
47755
- for (const doc of docs) {
47756
- const sources = doc.metadata?.sources;
47757
- if (!Array.isArray(sources))
47758
- continue;
47759
- for (const source of sources) {
47760
- const file = source?.file;
47761
- if (typeof file === "string" && file !== "")
47762
- counts[file] = (counts[file] ?? 0) + 1;
48271
+ const maxRevisions = raw.max_revisions;
48272
+ if (maxRevisions !== undefined) {
48273
+ if (typeof maxRevisions !== "number" || !Number.isInteger(maxRevisions) || maxRevisions < 0 || maxRevisions > 10) {
48274
+ throw new ItcConfigInvalidError("config key 'workflows.kk-itc.max_revisions' must be an integer from 0 to 10");
47763
48275
  }
48276
+ out.max_revisions = maxRevisions;
47764
48277
  }
47765
- return counts;
47766
- }
47767
- async function writeNewIds(fs, path, ids) {
47768
- await fs.ensureDir(dirname10(path));
47769
- await fs.writeFile(path, `${JSON.stringify([...new Set(ids)], null, 2)}
47770
- `);
47771
- }
47772
- async function ingestAction(options) {
47773
- try {
47774
- const fs = createNodeFileSystem();
48278
+ const topicProfile = slugAt("topic_profile");
48279
+ if (topicProfile !== undefined)
48280
+ out.topic_profile = topicProfile;
48281
+ const publishPreset = slugAt("publish_preset");
48282
+ if (publishPreset !== undefined)
48283
+ out.publish_preset = publishPreset;
48284
+ const workspace = raw.workspace;
48285
+ if (workspace !== undefined) {
48286
+ if (workspace === null || typeof workspace !== "object" || Array.isArray(workspace)) {
48287
+ throw new ItcConfigInvalidError("config key 'workflows.kk-itc.workspace' must be a mapping");
48288
+ }
48289
+ const ws = workspace;
48290
+ for (const key of Object.keys(ws)) {
48291
+ if (key !== "dated") {
48292
+ throw new ItcConfigInvalidError(`config key 'workflows.kk-itc.workspace.${key}' is not declared`);
48293
+ }
48294
+ }
48295
+ if (ws.dated !== undefined && typeof ws.dated !== "boolean") {
48296
+ throw new ItcConfigInvalidError("config key 'workflows.kk-itc.workspace.dated' must be a boolean");
48297
+ }
48298
+ out.workspace = ws.dated === undefined ? {} : { dated: ws.dated };
48299
+ }
48300
+ const seo = raw.seo;
48301
+ if (seo !== undefined) {
48302
+ if (seo === null || typeof seo !== "object" || Array.isArray(seo)) {
48303
+ throw new ItcConfigInvalidError("config key 'workflows.kk-itc.seo' must be a mapping");
48304
+ }
48305
+ const seoRecord = seo;
48306
+ for (const key of Object.keys(seoRecord)) {
48307
+ if (key !== "mode") {
48308
+ throw new ItcConfigInvalidError(`config key 'workflows.kk-itc.seo.${key}' is not declared`);
48309
+ }
48310
+ }
48311
+ const mode = seoRecord.mode;
48312
+ if (mode !== undefined) {
48313
+ if (typeof mode !== "string" || !SEO_MODES.has(mode)) {
48314
+ throw new ItcConfigInvalidError("config key 'workflows.kk-itc.seo.mode' must be one of off|handoff");
48315
+ }
48316
+ out.seo = { mode };
48317
+ } else {
48318
+ out.seo = {};
48319
+ }
48320
+ }
48321
+ return out;
48322
+ }
48323
+ async function readWorksRoot(configPath, env, homeDir, fs) {
48324
+ const fromEnv = env.KK_WORKS_DIR;
48325
+ if (fromEnv !== undefined && fromEnv.trim() !== "") {
48326
+ const expanded = expandHome3(fromEnv.trim(), homeDir);
48327
+ return isAbsolute8(expanded) ? expanded : resolve18(process.cwd(), expanded);
48328
+ }
48329
+ if (!await fs.exists(configPath))
48330
+ return join15(homeDir, ".config", "kk", "works");
48331
+ try {
48332
+ const parsed = Bun.YAML.parse(await fs.readFile(configPath));
48333
+ const raw = parsed?.works_dir;
48334
+ if (typeof raw === "string" && raw.trim() !== "") {
48335
+ const expanded = expandHome3(raw.trim(), homeDir);
48336
+ return isAbsolute8(expanded) ? expanded : resolve18(dirname10(configPath), expanded);
48337
+ }
48338
+ } catch {}
48339
+ return join15(homeDir, ".config", "kk", "works");
48340
+ }
48341
+ function parseRunDate(raw) {
48342
+ if (!/^\d{8}$/.test(raw))
48343
+ return null;
48344
+ const year = Number(raw.slice(0, 4));
48345
+ const month = Number(raw.slice(4, 6));
48346
+ const day = Number(raw.slice(6, 8));
48347
+ const date = new Date(Date.UTC(year, month - 1, day));
48348
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day)
48349
+ return null;
48350
+ return raw;
48351
+ }
48352
+ function resolveSource(input) {
48353
+ const topic = input.topic !== undefined && input.topic !== "" ? input.topic : undefined;
48354
+ const inputFile = input.inputFile !== undefined && input.inputFile !== "" ? input.inputFile : undefined;
48355
+ const fromCollect = input.fromCollect !== undefined && input.fromCollect !== "" ? input.fromCollect : undefined;
48356
+ const set = [topic, inputFile, fromCollect].filter((v) => v !== undefined);
48357
+ if (set.length === 0) {
48358
+ throw new ItcLaunchInvalidError("exactly one of --topic, --input-file or --from-collect is required");
48359
+ }
48360
+ if (set.length > 1) {
48361
+ throw new ItcLaunchInvalidError("exactly one of --topic, --input-file or --from-collect is required (two given)");
48362
+ }
48363
+ if (topic !== undefined)
48364
+ return { sourceKind: "topic", sourceValue: topic };
48365
+ if (inputFile !== undefined)
48366
+ return { sourceKind: "input_file", sourceValue: inputFile };
48367
+ return { sourceKind: "from_collect", sourceValue: fromCollect };
48368
+ }
48369
+ function pick2(...values) {
48370
+ for (const value of values)
48371
+ if (value !== undefined)
48372
+ return value;
48373
+ return;
48374
+ }
48375
+ async function resolveItcOptions(input, context = {}, fs = createNodeFileSystem()) {
48376
+ const env = context.env ?? process.env;
48377
+ const homeDir = context.homeDir ?? env.HOME ?? "~";
48378
+ const cwd = context.cwd ?? process.cwd();
48379
+ const now = context.now ?? (() => new Date);
48380
+ const configPath = resolveItcConfigPath(env, homeDir);
48381
+ const block = await readItcConfigBlock(configPath, fs);
48382
+ const worksRoot = await readWorksRoot(configPath, env, homeDir, fs);
48383
+ const source = resolveSource(input);
48384
+ const autoFromFlag = input.auto;
48385
+ const autoFromConfig = block.auto;
48386
+ const autoEffective = pick2(autoFromFlag, autoFromConfig) ?? COMPILED_DEFAULTS.auto;
48387
+ const autoIsExplicitLaunch = autoFromFlag !== undefined;
48388
+ const impliedWhenAuto = (value, configured) => {
48389
+ if (value !== undefined)
48390
+ return value;
48391
+ if (autoIsExplicitLaunch && autoEffective)
48392
+ return true;
48393
+ if (configured !== undefined)
48394
+ return configured;
48395
+ if (autoEffective)
48396
+ return true;
48397
+ return;
48398
+ };
48399
+ const playbook = pick2(input.playbook, block.playbook) ?? COMPILED_DEFAULTS.playbook;
48400
+ if (!PLAYBOOKS.has(playbook)) {
48401
+ throw new ItcLaunchInvalidError(`--playbook must be one of ${[...PLAYBOOKS].join("|")}`);
48402
+ }
48403
+ const writer = pick2(input.writer, block.writer) ?? COMPILED_DEFAULTS.writer;
48404
+ if (!WRITERS.has(writer)) {
48405
+ throw new ItcLaunchInvalidError(`--writer must be one of ${[...WRITERS].join("|")}`);
48406
+ }
48407
+ const explicitLocale = pick2(input.sourceLocale, block.source_locale);
48408
+ const sourceLocale = explicitLocale ?? (playbook === "wechat" ? "zh" : COMPILED_DEFAULTS.sourceLocale);
48409
+ if (!LOCALES.has(sourceLocale)) {
48410
+ throw new ItcLaunchInvalidError(`--source-locale must be one of ${[...LOCALES].join("|")}`);
48411
+ }
48412
+ const maxRevisions = pick2(input.maxRevisions, block.max_revisions) ?? COMPILED_DEFAULTS.maxRevisions;
48413
+ if (!Number.isInteger(maxRevisions) || maxRevisions < 0 || maxRevisions > 10) {
48414
+ throw new ItcLaunchInvalidError("--max-revisions must be an integer from 0 to 10");
48415
+ }
48416
+ const seo = pick2(input.seo, block.seo?.mode) ?? COMPILED_DEFAULTS.seo;
48417
+ if (!SEO_MODES.has(seo)) {
48418
+ throw new ItcLaunchInvalidError("--seo must be one of off|handoff");
48419
+ }
48420
+ const publishPreset = pick2(input.publishPreset, block.publish_preset);
48421
+ if (publishPreset !== undefined && !isSafeSlug(publishPreset)) {
48422
+ throw new ItcLaunchInvalidError("--publish-preset must be a lowercase hyphenated slug");
48423
+ }
48424
+ const topicProfile = pick2(input.topicProfile, block.topic_profile);
48425
+ if (topicProfile !== undefined && !isSafeSlug(topicProfile)) {
48426
+ throw new ItcLaunchInvalidError("--topic-profile must be a lowercase hyphenated slug");
48427
+ }
48428
+ const runDateInput = input.runDate;
48429
+ let runDate = null;
48430
+ if (runDateInput !== undefined && runDateInput !== "") {
48431
+ runDate = parseRunDate(runDateInput);
48432
+ if (runDate === null) {
48433
+ throw new ItcLaunchInvalidError("--run-date must be a real calendar date as YYYYMMDD");
48434
+ }
48435
+ }
48436
+ const force = input.force ?? false;
48437
+ const frozen = now();
48438
+ if (runDate === null && block.workspace?.dated) {
48439
+ runDate = `${frozen.getFullYear()}${String(frozen.getMonth() + 1).padStart(2, "0")}${String(frozen.getDate()).padStart(2, "0")}`;
48440
+ }
48441
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
48442
+ const intake = source.sourceKind === "from_collect";
48443
+ const draft = {
48444
+ schemaVersion: 1,
48445
+ status: intake ? "intake" : "prepared",
48446
+ parentRunId: input.parentRunId ?? "",
48447
+ launchProjectRoot: resolve18(input.projectRoot ?? cwd),
48448
+ worksRoot,
48449
+ workDir: null,
48450
+ topicName: intake ? null : source.sourceValue,
48451
+ displayTitle: null,
48452
+ writer,
48453
+ playbook,
48454
+ sourceLocale,
48455
+ research: impliedWhenAuto(input.research, block.research) ?? COMPILED_DEFAULTS.research,
48456
+ judge: impliedWhenAuto(input.judge, block.judge) ?? COMPILED_DEFAULTS.judge,
48457
+ unslop: impliedWhenAuto(input.unslop, block.unslop) ?? COMPILED_DEFAULTS.unslop,
48458
+ auto: autoEffective,
48459
+ maxRevisions,
48460
+ coverEnabled: input.coverEnabled ?? COMPILED_DEFAULTS.coverEnabled,
48461
+ inlineImages: input.inlineImages ?? COMPILED_DEFAULTS.inlineImages,
48462
+ publishTargets: input.publishTargets ?? COMPILED_DEFAULTS.publishTargets,
48463
+ publishLive: input.publishLive ?? COMPILED_DEFAULTS.publishLive,
48464
+ publishPreset: publishPreset ?? null,
48465
+ publishPresetSource: input.publishPreset !== undefined ? "launch" : publishPreset !== undefined ? "config" : null,
48466
+ topicProfile: topicProfile ?? null,
48467
+ seo,
48468
+ sourceKind: source.sourceKind,
48469
+ sourceValue: source.sourceValue,
48470
+ refreshCollect: input.refreshCollect ?? false,
48471
+ dated: runDate !== null,
48472
+ force,
48473
+ timezone,
48474
+ runDate
48475
+ };
48476
+ const expandedInput = {
48477
+ ...input,
48478
+ ...input.dir !== undefined ? { dir: expandHome3(input.dir, homeDir) } : {},
48479
+ ...input.workDir !== undefined ? { workDir: expandHome3(input.workDir, homeDir) } : {}
48480
+ };
48481
+ const withWorkspace = intake ? draft : applyWorkspace(draft, expandedInput);
48482
+ return {
48483
+ ...withWorkspace,
48484
+ frozenAt: frozen.toISOString(),
48485
+ optionsDigest: digestItcOptions(withWorkspace)
48486
+ };
48487
+ }
48488
+ function workspaceNameFor(sourceKind, value) {
48489
+ if (sourceKind === "topic")
48490
+ return value;
48491
+ const base = value.split(/[/\\]/).pop() ?? "";
48492
+ const stem = base.replace(/\.[^.]*$/, "");
48493
+ return stem === "" ? "itc-run" : stem;
48494
+ }
48495
+ function applyWorkspace(draft, input) {
48496
+ const workspace = resolveItcWorkspace(draft, workspaceNameFor(draft.sourceKind, draft.sourceValue), input);
48497
+ return { ...draft, status: "prepared", workDir: workspace.workDir, topicName: workspace.topicName };
48498
+ }
48499
+ function finalizeItcCollectedOptions(options, chosen, input = {}) {
48500
+ const { optionsDigest: _intakeDigest, frozenAt, ...draft } = options;
48501
+ const workspace = resolveItcWorkspace(draft, chosen.topicName, input);
48502
+ const prepared = {
48503
+ ...draft,
48504
+ status: "prepared",
48505
+ workDir: workspace.workDir,
48506
+ topicName: workspace.topicName,
48507
+ displayTitle: chosen.displayTitle,
48508
+ topicProfile: draft.topicProfile ?? chosen.topicProfile
48509
+ };
48510
+ return { ...prepared, frozenAt, optionsDigest: digestItcOptions(prepared) };
48511
+ }
48512
+ function resolveItcWorkspace(options, topic, input = {}) {
48513
+ const slug = slugifyTopicName(topic);
48514
+ if (slug === null) {
48515
+ throw new ItcLaunchInvalidError(`topic name '${topic}' cannot form a safe workspace path; pass an explicit --dir or a lowercase slug topic`);
48516
+ }
48517
+ const override = input.dir ?? input.workDir;
48518
+ if (override !== undefined && override.trim() !== "") {
48519
+ const expanded = override.trim().replace(/^~(?=\/|$)/, "");
48520
+ const base = expanded === override.trim() ? override.trim() : expanded;
48521
+ return {
48522
+ workDir: isAbsolute8(base) ? base : resolve18(options.launchProjectRoot, base),
48523
+ topicName: slug,
48524
+ dated: input.runDate !== undefined
48525
+ };
48526
+ }
48527
+ const leaf = options.runDate !== null ? join15(slug, options.runDate) : slug;
48528
+ return { workDir: join15(options.worksRoot, "kk-itc", leaf), topicName: slug, dated: options.runDate !== null };
48529
+ }
48530
+ async function writeItcOptionsSnapshot(workDir, options, fs = createNodeFileSystem()) {
48531
+ const path = join15(workDir, "itc-options.json");
48532
+ await fs.ensureDir(workDir);
48533
+ await fs.writeFile(path, `${JSON.stringify(options, null, 2)}
48534
+ `);
48535
+ return path;
48536
+ }
48537
+ async function verifyItcOptionsSnapshot(workDir, options, fs = createNodeFileSystem()) {
48538
+ const path = join15(workDir, "itc-options.json");
48539
+ if (!await fs.exists(path))
48540
+ return { ok: true };
48541
+ let saved;
48542
+ try {
48543
+ saved = JSON.parse(await fs.readFile(path));
48544
+ } catch {
48545
+ return { ok: false, reason: `existing snapshot is unreadable or invalid JSON: ${path}` };
48546
+ }
48547
+ const frozenKeys = [
48548
+ "schemaVersion",
48549
+ "worksRoot",
48550
+ "workDir",
48551
+ "topicName",
48552
+ "sourceLocale",
48553
+ "publishPreset",
48554
+ "publishTargets",
48555
+ "publishLive",
48556
+ "seo",
48557
+ "maxRevisions",
48558
+ "optionsDigest"
48559
+ ];
48560
+ for (const key of frozenKeys) {
48561
+ if (saved[key] !== options[key]) {
48562
+ return {
48563
+ ok: false,
48564
+ reason: `resume refused: frozen option '${key}' changed (${String(saved[key])} -> ${String(options[key])})`
48565
+ };
48566
+ }
48567
+ }
48568
+ return { ok: true };
48569
+ }
48570
+
48571
+ // src/itc-decision.ts
48572
+ init_dist3();
48573
+ init_zod();
48574
+ import { createHash as createHash3 } from "crypto";
48575
+ import { dirname as dirname11 } from "path";
48576
+ var ItcDecisionCandidateSchema = object2({
48577
+ id: string2().min(1),
48578
+ title: string2().optional(),
48579
+ description: string2().optional(),
48580
+ metadata: record(string2(), unknown()).optional()
48581
+ }).strict();
48582
+ var ItcDecisionRequestSchema = object2({
48583
+ schemaVersion: literal(1).default(1),
48584
+ parentRunId: string2().min(1),
48585
+ decisionId: _enum2(["kk-itc-topic-choice", "kk-itc-outline-choice", "kk-itc-preset-choice"]),
48586
+ candidateDigest: string2().min(1).optional(),
48587
+ candidates: array(ItcDecisionCandidateSchema).min(1),
48588
+ evidenceFiles: array(string2()).default([]),
48589
+ outPath: string2().min(1),
48590
+ maker: string2().optional(),
48591
+ explicitChoice: string2().optional()
48592
+ }).strict();
48593
+ async function compactEvidenceFile(fs, filePath) {
48594
+ if (!await fs.exists(filePath))
48595
+ return;
48596
+ try {
48597
+ const text = await fs.readFile(filePath);
48598
+ const originalLength = text.length;
48599
+ const truncated = originalLength > 1800;
48600
+ const content = text.slice(0, 1800);
48601
+ return {
48602
+ file: filePath,
48603
+ originalLength,
48604
+ retainedLength: content.length,
48605
+ truncated,
48606
+ content
48607
+ };
48608
+ } catch {
48609
+ return;
48610
+ }
48611
+ }
48612
+ async function itcDecision(request, fs = createNodeFileSystem(), executor = new NodeProcessExecutor) {
48613
+ const fallback = request.decisionId === "kk-itc-preset-choice" ? "none" : "defer";
48614
+ const candidateDigest = request.candidateDigest ?? createHash3("sha256").update(JSON.stringify(request.candidates)).digest("hex");
48615
+ const validCandidateIds = new Set(request.candidates.map((c) => c.id));
48616
+ const allowedValues = new Set;
48617
+ if (request.decisionId === "kk-itc-preset-choice") {
48618
+ allowedValues.add("suggested");
48619
+ allowedValues.add("none");
48620
+ } else {
48621
+ for (const id of validCandidateIds) {
48622
+ allowedValues.add(id);
48623
+ }
48624
+ allowedValues.add("defer");
48625
+ }
48626
+ await fs.ensureDir(dirname11(request.outPath));
48627
+ if (request.explicitChoice !== undefined) {
48628
+ const choice = request.explicitChoice.trim();
48629
+ const isValid = allowedValues.has(choice);
48630
+ const selectedValue = isValid ? choice : fallback;
48631
+ const result = {
48632
+ schemaVersion: 1,
48633
+ parentRunId: request.parentRunId,
48634
+ decisionId: request.decisionId,
48635
+ candidateDigest,
48636
+ value: selectedValue,
48637
+ source: isValid ? "explicit" : "default",
48638
+ reason: isValid ? "explicit operator choice" : `explicit choice '${choice}' was not allowed; fell back to '${fallback}'`,
48639
+ maker: "operator",
48640
+ selected: selectedValue !== fallback
48641
+ };
48642
+ await fs.writeFile(request.outPath, `${JSON.stringify(result, null, 2)}
48643
+ `);
48644
+ return result;
48645
+ }
48646
+ const tmpEvidenceFiles = [];
48647
+ const evidenceArgs = [];
48648
+ const evidenceFiles = request.evidenceFiles ?? [];
48649
+ for (const [index, ef] of evidenceFiles.entries()) {
48650
+ const compacted = await compactEvidenceFile(fs, ef);
48651
+ if (compacted) {
48652
+ const tmpPath = `${request.outPath}.evidence-${index}.json`;
48653
+ await fs.writeFile(tmpPath, JSON.stringify(compacted));
48654
+ tmpEvidenceFiles.push(tmpPath);
48655
+ evidenceArgs.push("--evidence", tmpPath);
48656
+ }
48657
+ }
48658
+ let paramKey = "candidates";
48659
+ if (request.decisionId === "kk-itc-outline-choice")
48660
+ paramKey = "options";
48661
+ if (request.decisionId === "kk-itc-preset-choice")
48662
+ paramKey = "suggestion";
48663
+ const paramValue = request.candidates.map((c) => `${c.id}: ${c.title ?? c.description ?? c.id}`).join("; ");
48664
+ const spurBin = process.env.SPUR_BIN || "spur";
48665
+ const argv = [
48666
+ "decision",
48667
+ "run",
48668
+ request.decisionId,
48669
+ "--param",
48670
+ `${paramKey}=${paramValue}`,
48671
+ ...evidenceArgs,
48672
+ ...request.maker ? ["--maker", request.maker] : [],
48673
+ "--json"
48674
+ ];
48675
+ let resultValue = fallback;
48676
+ let resultSource = "default";
48677
+ let resultReason = "decision run invocation failed";
48678
+ let resultMaker = "catalog-fallback";
48679
+ let resultConfidence = null;
48680
+ try {
48681
+ const res = await executor.run({
48682
+ command: spurBin,
48683
+ args: argv,
48684
+ env: process.env,
48685
+ timeout: 30000
48686
+ });
48687
+ const stdout = typeof res.stdout === "string" ? res.stdout : "";
48688
+ const stderr = typeof res.stderr === "string" ? res.stderr : "";
48689
+ if (res.exitCode === 0 && stdout !== "") {
48690
+ try {
48691
+ const parsed = JSON.parse(stdout);
48692
+ const val = typeof parsed.value === "string" ? parsed.value.trim() : String(parsed.value ?? "");
48693
+ if (allowedValues.has(val)) {
48694
+ resultValue = val;
48695
+ resultSource = parsed.source === "default" ? "default" : "model";
48696
+ resultReason = typeof parsed.reason === "string" ? parsed.reason : "model decision";
48697
+ resultMaker = typeof parsed.maker === "string" ? parsed.maker : "model";
48698
+ resultConfidence = typeof parsed.confidence === "number" ? parsed.confidence : null;
48699
+ } else {
48700
+ resultValue = fallback;
48701
+ resultSource = "default";
48702
+ resultReason = `model output "${val}" is not among valid candidates [${[...validCandidateIds].join(", ")}]`;
48703
+ resultMaker = typeof parsed.maker === "string" ? parsed.maker : "catalog-fallback";
48704
+ }
48705
+ } catch (jsonErr) {
48706
+ resultReason = `failed to parse decision json: ${jsonErr instanceof Error ? jsonErr.message : String(jsonErr)}`;
48707
+ }
48708
+ } else {
48709
+ resultReason = `spur decision run exited with code ${res.exitCode ?? 1}: ${stderr.trim() || "unknown error"}`;
48710
+ }
48711
+ } catch (spawnErr) {
48712
+ resultReason = `failed to spawn spur decision run: ${spawnErr instanceof Error ? spawnErr.message : String(spawnErr)}`;
48713
+ } finally {
48714
+ for (const tf of tmpEvidenceFiles) {
48715
+ try {
48716
+ await fs.deleteFile(tf);
48717
+ } catch {}
48718
+ }
48719
+ }
48720
+ const decisionResult = {
48721
+ schemaVersion: 1,
48722
+ parentRunId: request.parentRunId,
48723
+ decisionId: request.decisionId,
48724
+ candidateDigest,
48725
+ value: resultValue,
48726
+ source: resultSource,
48727
+ reason: resultReason,
48728
+ maker: resultMaker,
48729
+ selected: resultValue !== fallback,
48730
+ ...resultConfidence !== null ? { confidence: resultConfidence } : {}
48731
+ };
48732
+ await fs.writeFile(request.outPath, `${JSON.stringify(decisionResult, null, 2)}
48733
+ `);
48734
+ return decisionResult;
48735
+ }
48736
+
48737
+ // src/itc-plan.ts
48738
+ import { createHash as createHash4 } from "crypto";
48739
+ import { join as join16, resolve as resolve19 } from "path";
48740
+ init_dist3();
48741
+ init_zod();
48742
+ var ItcTargetsOriginSchema = _enum2([
48743
+ "explicit-targets",
48744
+ "explicit-preset",
48745
+ "workflow-config",
48746
+ "topic-profile",
48747
+ "none"
48748
+ ]);
48749
+ var ItcPlanTargetRequestSchema = object2({
48750
+ plugin: string2().min(1),
48751
+ profile: string2().min(1).optional()
48752
+ }).strict();
48753
+ var PublishPresetTargetSchema = object2({
48754
+ plugin: string2().min(1),
48755
+ profile: string2().min(1).optional()
48756
+ }).strict();
48757
+ var PublishPresetSchema = object2({
48758
+ schemaVersion: literal(1).default(1),
48759
+ name: string2().min(1),
48760
+ targets: array(PublishPresetTargetSchema).min(1)
48761
+ }).strict();
48762
+ var ItcPlanRequestSchema = object2({
48763
+ schemaVersion: literal(1).optional(),
48764
+ workDir: string2().min(1),
48765
+ sourceLocale: string2().min(1),
48766
+ live: boolean2(),
48767
+ targets: array(ItcPlanTargetRequestSchema),
48768
+ origin: ItcTargetsOriginSchema.optional(),
48769
+ presetName: string2().optional(),
48770
+ presetDigest: string2().optional(),
48771
+ sourceContentDigest: string2().optional(),
48772
+ configDigest: string2().optional()
48773
+ });
48774
+ var SLUG_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
48775
+ async function resolveDefaultPackageRoot(fs) {
48776
+ let dir = resolve19(process.cwd());
48777
+ for (;; ) {
48778
+ if (await fs.exists(join16(dir, "plugins/kk/publish-presets")))
48779
+ return dir;
48780
+ const parent = resolve19(dir, "..");
48781
+ if (parent === dir)
48782
+ return process.cwd();
48783
+ dir = parent;
48784
+ }
48785
+ }
48786
+ function parseTargetTokens(raw) {
48787
+ const tokens = raw.split(",").map((t) => t.trim()).filter(Boolean);
48788
+ if (tokens.length === 0) {
48789
+ throw new Error("explicit targets input is empty");
48790
+ }
48791
+ return tokens.map((token) => {
48792
+ const colonIdx = token.indexOf(":");
48793
+ if (colonIdx !== -1) {
48794
+ return {
48795
+ plugin: token.slice(0, colonIdx),
48796
+ profile: token.slice(colonIdx + 1)
48797
+ };
48798
+ }
48799
+ return { plugin: token };
48800
+ });
48801
+ }
48802
+ function normalizeTargetList(targets) {
48803
+ if (targets.length === 0) {
48804
+ throw new Error("explicit targets input is empty");
48805
+ }
48806
+ const pluginProfiles = new Map;
48807
+ const unique = [];
48808
+ for (const t of targets) {
48809
+ if (pluginProfiles.has(t.plugin)) {
48810
+ const existing = pluginProfiles.get(t.plugin);
48811
+ if (existing !== t.profile) {
48812
+ throw new Error(`conflicting profiles for plugin ${t.plugin}: "${existing ?? ""}" vs "${t.profile ?? ""}"`);
48813
+ }
48814
+ continue;
48815
+ }
48816
+ pluginProfiles.set(t.plugin, t.profile);
48817
+ unique.push({ plugin: t.plugin, ...t.profile ? { profile: t.profile } : {} });
48818
+ }
48819
+ return unique;
48820
+ }
48821
+ async function resolvePresetFromRoots(fs, slug, projectRoot, packageRoot) {
48822
+ if (!SLUG_REGEX.test(slug)) {
48823
+ throw new Error(`invalid publish preset slug '${slug}': must be lowercase alphanumeric with hyphens`);
48824
+ }
48825
+ const projectDir = resolve19(projectRoot, ".kk/publish-presets");
48826
+ const projectPath = resolve19(projectDir, `${slug}.json`);
48827
+ if (await fs.exists(projectPath)) {
48828
+ return readPresetFile(fs, projectPath, projectDir, slug);
48829
+ }
48830
+ const pkgDir = resolve19(packageRoot, "plugins/kk/publish-presets");
48831
+ const pkgPath = resolve19(pkgDir, `${slug}.json`);
48832
+ if (await fs.exists(pkgPath)) {
48833
+ return readPresetFile(fs, pkgPath, pkgDir, slug);
48834
+ }
48835
+ throw new Error(`publish preset '${slug}' not found in ${projectPath} or ${pkgPath}`);
48836
+ }
48837
+ async function readPresetFile(fs, path, root, slug) {
48838
+ if (!path.startsWith(root)) {
48839
+ throw new Error(`path traversal rejected for preset '${slug}'`);
48840
+ }
48841
+ let text;
48842
+ try {
48843
+ text = await fs.readFile(path);
48844
+ } catch (err) {
48845
+ throw new Error(`malformed publish preset at ${path}: ${err instanceof Error ? err.message : String(err)}`);
48846
+ }
48847
+ let parsed;
48848
+ try {
48849
+ parsed = PublishPresetSchema.safeParse(JSON.parse(text));
48850
+ } catch (err) {
48851
+ throw new Error(`malformed publish preset at ${path}: ${err instanceof Error ? err.message : String(err)}`);
48852
+ }
48853
+ if (!parsed.success) {
48854
+ throw new Error(`malformed publish preset at ${path}: ${parsed.error.message}`);
48855
+ }
48856
+ return { preset: parsed.data, digest: createHash4("sha256").update(text).digest("hex") };
48857
+ }
48858
+ async function resolveItcTargets(input, fs = createNodeFileSystem()) {
48859
+ const projectRoot = input.projectRoot ? resolve19(input.projectRoot) : process.cwd();
48860
+ const packageRoot = input.packageRoot ? resolve19(input.packageRoot) : await resolveDefaultPackageRoot(fs);
48861
+ if (input.explicitTargets !== undefined) {
48862
+ if (typeof input.explicitTargets === "string") {
48863
+ if (input.explicitTargets.trim() === "") {
48864
+ throw new Error("explicit targets input is empty");
48865
+ }
48866
+ const parsed = parseTargetTokens(input.explicitTargets);
48867
+ return {
48868
+ targets: normalizeTargetList(parsed),
48869
+ origin: "explicit-targets"
48870
+ };
48871
+ }
48872
+ if (Array.isArray(input.explicitTargets)) {
48873
+ if (input.explicitTargets.length === 0) {
48874
+ throw new Error("explicit targets input is empty");
48875
+ }
48876
+ return {
48877
+ targets: normalizeTargetList(input.explicitTargets),
48878
+ origin: "explicit-targets"
48879
+ };
48880
+ }
48881
+ }
48882
+ if (input.explicitPreset !== undefined) {
48883
+ if (input.explicitPreset.trim() === "") {
48884
+ throw new Error("explicit publish preset input is empty");
48885
+ }
48886
+ const { preset, digest } = await resolvePresetFromRoots(fs, input.explicitPreset.trim(), projectRoot, packageRoot);
48887
+ return {
48888
+ targets: normalizeTargetList(preset.targets),
48889
+ origin: "explicit-preset",
48890
+ presetName: preset.name,
48891
+ presetDigest: digest
48892
+ };
48893
+ }
48894
+ if (input.workflowConfigPreset !== undefined) {
48895
+ if (input.workflowConfigPreset.trim() === "") {
48896
+ throw new Error("workflow config publish preset input is empty");
48897
+ }
48898
+ const { preset, digest } = await resolvePresetFromRoots(fs, input.workflowConfigPreset.trim(), projectRoot, packageRoot);
48899
+ return {
48900
+ targets: normalizeTargetList(preset.targets),
48901
+ origin: "workflow-config",
48902
+ presetName: preset.name,
48903
+ presetDigest: digest
48904
+ };
48905
+ }
48906
+ if (input.topicProfile !== undefined) {
48907
+ const topicSlug = input.topicProfile.trim();
48908
+ if (!SLUG_REGEX.test(topicSlug)) {
48909
+ throw new Error("topicProfile must be a lowercase hyphenated slug");
48910
+ }
48911
+ const profilePath = input.topicProfilePath !== undefined && await fs.exists(input.topicProfilePath) ? input.topicProfilePath : resolve19(packageRoot, "plugins/kk/decisions/extract", `${topicSlug}.yaml`);
48912
+ if (await fs.exists(profilePath)) {
48913
+ const profile = parseProfile(await fs.readFile(profilePath));
48914
+ if (profile.publishPreset) {
48915
+ const { preset, digest } = await resolvePresetFromRoots(fs, profile.publishPreset, projectRoot, packageRoot);
48916
+ return {
48917
+ targets: normalizeTargetList(preset.targets),
48918
+ origin: "topic-profile",
48919
+ presetName: preset.name,
48920
+ presetDigest: digest
48921
+ };
48922
+ }
48923
+ }
48924
+ }
48925
+ return {
48926
+ targets: [],
48927
+ origin: "none"
48928
+ };
48929
+ }
48930
+ async function hasCoverImage(fs, workDir) {
48931
+ const dir = join16(workDir, "4-illustrations");
48932
+ if (!await fs.exists(dir))
48933
+ return false;
48934
+ try {
48935
+ const files = await fs.readDir(dir);
48936
+ return files.some((f) => f.startsWith("cover."));
48937
+ } catch {
48938
+ return false;
48939
+ }
48940
+ }
48941
+ async function hasOrderedImages(fs, workDir) {
48942
+ const path = join16(workDir, "4-publish", "content.json");
48943
+ if (!await fs.exists(path))
48944
+ return false;
48945
+ try {
48946
+ const data = JSON.parse(await fs.readFile(path));
48947
+ const images = data.metadata?.images;
48948
+ return Array.isArray(images) && images.length > 0 && (await Promise.all(images.map(async (img) => typeof img === "string" && img.trim().length > 0 && await fs.exists(resolve19(workDir, img))))).every(Boolean);
48949
+ } catch {
48950
+ return false;
48951
+ }
48952
+ }
48953
+ async function buildItcPublishPlan(request, plugins, fs = createNodeFileSystem()) {
48954
+ const pluginsByName = new Map(plugins.map((p) => [p.name, p]));
48955
+ const hasCover = await hasCoverImage(fs, request.workDir);
48956
+ const pluginProfiles = new Map;
48957
+ const uniqueTargets = [];
48958
+ for (const t of request.targets) {
48959
+ const plugin = pluginsByName.get(t.plugin);
48960
+ if (plugin?.kind !== "publish" || !plugin.publishing) {
48961
+ throw new Error(`unsupported publish target: ${t.plugin} (unknown or not an article target)`);
48962
+ }
48963
+ const profileName = t.profile ?? plugin.publishing.defaultProfile;
48964
+ const existingProfile = pluginProfiles.get(t.plugin);
48965
+ if (existingProfile !== undefined) {
48966
+ if (existingProfile !== profileName) {
48967
+ throw new Error(`conflicting profiles for plugin ${t.plugin}: "${existingProfile}" vs "${profileName}"`);
48968
+ }
48969
+ continue;
48970
+ }
48971
+ pluginProfiles.set(t.plugin, profileName);
48972
+ uniqueTargets.push({ plugin: t.plugin, profile: profileName });
48973
+ }
48974
+ const variants = {};
48975
+ const fanoutMap = new Map;
48976
+ const browser = [];
48977
+ const targetDetails = [];
48978
+ for (const targetReq of uniqueTargets) {
48979
+ const plugin = pluginsByName.get(targetReq.plugin);
48980
+ if (!plugin?.publishing) {
48981
+ continue;
48982
+ }
48983
+ const profileName = targetReq.profile ?? plugin.publishing.defaultProfile;
48984
+ const profile = plugin.publishing.profiles[profileName];
48985
+ if (!profile) {
48986
+ throw new Error(`target ${targetReq.plugin} has no profile "${profileName}"`);
48987
+ }
48988
+ const modeKey = request.live ? "live" : "draft";
48989
+ const modeBinding = profile.modes[modeKey];
48990
+ if (!modeBinding) {
48991
+ if (request.live) {
48992
+ throw new Error(`target ${targetReq.plugin} refuses a live publish`);
48993
+ }
48994
+ throw new Error(`target ${targetReq.plugin} has no draft mode; it needs publish_live=true`);
48995
+ }
48996
+ if (profile.requires?.cover && !hasCover) {
48997
+ throw new Error(`target ${targetReq.plugin} needs a cover image at 4-illustrations/cover.* in ${request.workDir}`);
48998
+ }
48999
+ if (profile.requires?.orderedImages && !await hasOrderedImages(fs, request.workDir)) {
49000
+ throw new Error(`target ${targetReq.plugin} requires ordered metadata.images with real paths; a hero cover alone does not satisfy that requirement`);
49001
+ }
49002
+ const locale = profile.locale === "source" ? request.sourceLocale : profile.locale;
49003
+ const format = profile.format;
49004
+ const variant = `${locale}-${format}`;
49005
+ const sharesSourceArticle = variant === `${request.sourceLocale}-article`;
49006
+ const file = sharesSourceArticle ? "content.json" : `content.${variant}.json`;
49007
+ const adapt = !sharesSourceArticle;
49008
+ const switches = { ...modeBinding.metadata };
49009
+ if (!variants[variant]) {
49010
+ variants[variant] = {
49011
+ file,
49012
+ adapt,
49013
+ limits: profile.limits ?? (sharesSourceArticle ? undefined : {}),
49014
+ switches: {}
49015
+ };
49016
+ }
49017
+ Object.assign(variants[variant].switches, switches);
49018
+ const isBrowser = plugin.browserSession !== undefined;
49019
+ if (isBrowser) {
49020
+ browser.push({ target: targetReq.plugin, variant });
49021
+ } else {
49022
+ const group = fanoutMap.get(variant);
49023
+ if (group) {
49024
+ if (!group.includes(targetReq.plugin))
49025
+ group.push(targetReq.plugin);
49026
+ } else {
49027
+ fanoutMap.set(variant, [targetReq.plugin]);
49028
+ }
49029
+ }
49030
+ targetDetails.push({
49031
+ target: targetReq.plugin,
49032
+ plugin: targetReq.plugin,
49033
+ profile: profileName,
49034
+ variant,
49035
+ transport: isBrowser ? "browser" : "fanout",
49036
+ mode: modeKey,
49037
+ metadata: switches
49038
+ });
49039
+ }
49040
+ const fanout = [...fanoutMap.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([variant, targets]) => ({ variant, targets }));
49041
+ return {
49042
+ ...request.schemaVersion !== undefined ? { schemaVersion: 1, targets: targetDetails } : {},
49043
+ ...request.origin !== undefined ? { origin: request.origin } : {},
49044
+ ...request.presetName !== undefined ? { presetName: request.presetName } : {},
49045
+ ...request.presetDigest !== undefined ? { presetDigest: request.presetDigest } : {},
49046
+ live: request.live,
49047
+ sourceLocale: request.sourceLocale,
49048
+ variants,
49049
+ fanout,
49050
+ browser
49051
+ };
49052
+ }
49053
+
49054
+ // src/itc-publish.ts
49055
+ import { createHash as createHash6 } from "crypto";
49056
+ import { join as join18 } from "path";
49057
+ init_dist3();
49058
+ init_dist();
49059
+ init_zod();
49060
+
49061
+ // src/itc-topic.ts
49062
+ import { createHash as createHash5 } from "crypto";
49063
+ import { join as join17, resolve as resolve20 } from "path";
49064
+ init_dist3();
49065
+ init_dist();
49066
+ init_zod();
49067
+ var WINDOW_UNITS = {
49068
+ s: 1000,
49069
+ m: 60 * 1000,
49070
+ h: 60 * 60 * 1000,
49071
+ d: 24 * 60 * 60 * 1000
49072
+ };
49073
+ function parseWindow(raw) {
49074
+ const str = raw ? raw.trim() : "24h";
49075
+ const match = /^(\d+)([smhd])$/.exec(str);
49076
+ const amount = match?.[1];
49077
+ const unit = match?.[2];
49078
+ if (amount === undefined || unit === undefined) {
49079
+ throw new Error(`--window must be '<n>s|m|h|d' (e.g. 24h), got '${raw ?? ""}'`);
49080
+ }
49081
+ const milliseconds = Number(amount) * (WINDOW_UNITS[unit] ?? 0);
49082
+ if (milliseconds <= 0) {
49083
+ throw new Error(`--window must be greater than zero, got '${raw ?? ""}'`);
49084
+ }
49085
+ return milliseconds;
49086
+ }
49087
+ function toPoolDoc(item) {
49088
+ return {
49089
+ id: item.id,
49090
+ title: item.title,
49091
+ body: item.body,
49092
+ ...item.sourceUri === null ? {} : { sourceUri: item.sourceUri },
49093
+ ...item.mediaType === null ? {} : { mediaType: item.mediaType },
49094
+ metadata: { ...item.metadata, labels: item.labels }
49095
+ };
49096
+ }
49097
+ function resolveKkConfigPath2() {
49098
+ return process.env.KK_CONFIG || resolve20(process.env.HOME || "~", ".config/kk/config.yaml");
49099
+ }
49100
+ async function resolveTopicProfilePath(fs, topic, customPath) {
49101
+ if (customPath !== undefined && await fs.exists(customPath)) {
49102
+ return customPath;
49103
+ }
49104
+ const rel = join17("plugins/kk/decisions/extract", `${topic}.yaml`);
49105
+ let dir = resolve20(process.cwd());
49106
+ for (;; ) {
49107
+ const candidate = join17(dir, rel);
49108
+ if (await fs.exists(candidate))
49109
+ return candidate;
49110
+ const parent = resolve20(dir, "..");
49111
+ if (parent === dir)
49112
+ break;
49113
+ dir = parent;
49114
+ }
49115
+ throw new Error(`no profile for topic '${topic}': expected ${rel} under the cwd or one of its ancestors`);
49116
+ }
49117
+ var ItcTopicExtractRequestSchema = object2({
49118
+ schemaVersion: literal(1).default(1),
49119
+ parentRunId: string2().min(1),
49120
+ intakeDir: string2().min(1),
49121
+ topicProfile: string2().min(1),
49122
+ window: string2().optional().default("24h"),
49123
+ limit: number2().int().positive().optional().default(20),
49124
+ dbPath: string2().optional(),
49125
+ profilePath: string2().optional(),
49126
+ refreshRun: object2({
49127
+ runId: string2().min(1),
49128
+ status: _enum2(["succeeded", "failed", "locked", "skipped"]),
49129
+ outputDocsPath: string2().optional()
49130
+ }).optional()
49131
+ });
49132
+ async function itcTopicExtract(request, fs = createNodeFileSystem()) {
49133
+ request = ItcTopicExtractRequestSchema.parse(request);
49134
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(request.topicProfile))
49135
+ throw new Error("invalid topic profile slug");
49136
+ if (request.refreshRun?.status === "failed")
49137
+ throw new Error("requested collection refresh failed (failed)");
49138
+ const profilePath = await resolveTopicProfilePath(fs, request.topicProfile, request.profilePath);
49139
+ const profileText = await fs.readFile(profilePath);
49140
+ const profile = parseProfile(profileText);
49141
+ const query = compileProfile(profile, {
49142
+ windowMs: parseWindow(request.window),
49143
+ now: Date.now(),
49144
+ limit: request.limit,
49145
+ excludeTopic: request.topicProfile
49146
+ });
49147
+ const storePath = await resolveStorePath(resolveKkConfigPath2(), request.dbPath);
49148
+ const store = await openStore({ path: storePath });
49149
+ let docs;
49150
+ try {
49151
+ let policy = {
49152
+ priority: DEFAULT_LABEL_PRIORITY,
49153
+ decisionPriority: {},
49154
+ minConfidence: DEFAULT_MIN_CONFIDENCE
49155
+ };
49156
+ try {
49157
+ policy = (await readStoreConfig(resolveKkConfigPath2())).labels;
49158
+ } catch {}
49159
+ const rawDocs = await store.items.selectByProfile(query, policy);
49160
+ docs = array(DocSchema).parse(rawDocs.map(toPoolDoc));
49161
+ } finally {
49162
+ await store.close();
49163
+ }
49164
+ if (docs.length === 0) {
49165
+ if (request.refreshRun && request.refreshRun.status !== "succeeded") {
49166
+ throw new Error(`requested collection refresh failed (${request.refreshRun.status}) and existing store pool is empty for profile '${request.topicProfile}'`);
49167
+ }
49168
+ throw new Error(`no usable topic; collection store pool is empty for profile '${request.topicProfile}'`);
49169
+ }
49170
+ await fs.ensureDir(request.intakeDir);
49171
+ const poolDigest = createHash5("sha256").update(JSON.stringify(docs)).digest("hex");
49172
+ const manifest = {
49173
+ schemaVersion: 1,
49174
+ parentRunId: request.parentRunId,
49175
+ topicProfile: request.topicProfile,
49176
+ window: request.window,
49177
+ limit: request.limit,
49178
+ extractedAt: new Date().toISOString(),
49179
+ poolDigest,
49180
+ ...request.refreshRun?.runId ? { collectRunId: request.refreshRun.runId } : {}
49181
+ };
49182
+ await fs.writeFile(join17(request.intakeDir, "topic-pool.json"), `${JSON.stringify(docs, null, 2)}
49183
+ `);
49184
+ await fs.writeFile(join17(request.intakeDir, "topic-pool-manifest.json"), `${JSON.stringify(manifest, null, 2)}
49185
+ `);
49186
+ return { count: docs.length, poolDigest };
49187
+ }
49188
+ var ItcTopicCandidateSchema = object2({
49189
+ id: _enum2(["a", "b", "c"]),
49190
+ title: string2().min(1).max(200),
49191
+ reason: string2().min(1).max(1000),
49192
+ itemIds: array(string2().min(1)).min(1),
49193
+ sourceUrls: array(string2().url()).min(1)
49194
+ });
49195
+ var ItcTopicCandidatesArtifactSchema = object2({
49196
+ schemaVersion: literal(1).default(1),
49197
+ parentRunId: string2().min(1),
49198
+ poolDigest: string2().min(1),
49199
+ candidates: array(ItcTopicCandidateSchema).min(1).max(3)
49200
+ });
49201
+ var ItcTopicValidateRequestSchema = object2({
49202
+ schemaVersion: literal(1).default(1),
49203
+ intakeDir: string2().min(1),
49204
+ parentRunId: string2().min(1)
49205
+ });
49206
+ async function itcTopicValidate(request, fs = createNodeFileSystem()) {
49207
+ const manifestPath = join17(request.intakeDir, "topic-pool-manifest.json");
49208
+ const poolPath = join17(request.intakeDir, "topic-pool.json");
49209
+ const candidatesPath = join17(request.intakeDir, "topic-candidates.json");
49210
+ if (!await fs.exists(manifestPath) || !await fs.exists(poolPath)) {
49211
+ throw new Error(`topic pool artifacts not found in ${request.intakeDir}`);
49212
+ }
49213
+ if (!await fs.exists(candidatesPath)) {
49214
+ throw new Error(`topic-candidates.json not found in ${request.intakeDir}`);
49215
+ }
49216
+ const manifest = JSON.parse(await fs.readFile(manifestPath));
49217
+ const poolDocs = array(DocSchema).parse(JSON.parse(await fs.readFile(poolPath)));
49218
+ const rawCandidates = JSON.parse(await fs.readFile(candidatesPath));
49219
+ const parsed = ItcTopicCandidatesArtifactSchema.safeParse(rawCandidates);
49220
+ if (!parsed.success) {
49221
+ throw new Error(`invalid topic candidates artifact: ${parsed.error.message}`);
49222
+ }
49223
+ const artifact = parsed.data;
49224
+ if (artifact.parentRunId !== request.parentRunId || artifact.parentRunId !== manifest.parentRunId) {
49225
+ throw new Error(`parentRunId mismatch: request ${request.parentRunId} vs candidates ${artifact.parentRunId} vs manifest ${manifest.parentRunId}`);
49226
+ }
49227
+ const currentDigest = createHash5("sha256").update(JSON.stringify(poolDocs)).digest("hex");
49228
+ if (artifact.poolDigest !== manifest.poolDigest || currentDigest !== manifest.poolDigest) {
49229
+ throw new Error(`stale candidates: poolDigest mismatch (candidates ${artifact.poolDigest} vs pool ${manifest.poolDigest})`);
49230
+ }
49231
+ const candidateIds = new Set;
49232
+ for (const c of artifact.candidates) {
49233
+ if (candidateIds.has(c.id)) {
49234
+ throw new Error(`duplicate candidate id '${c.id}'`);
49235
+ }
49236
+ candidateIds.add(c.id);
49237
+ }
49238
+ const poolDocMap = new Map(poolDocs.map((d) => [d.id, d]));
49239
+ for (const c of artifact.candidates) {
49240
+ for (const itemId of c.itemIds) {
49241
+ const doc = poolDocMap.get(itemId);
49242
+ if (!doc) {
49243
+ throw new Error(`candidate '${c.id}' cites unknown item ID '${itemId}' not present in topic-pool.json`);
49244
+ }
49245
+ }
49246
+ for (const url of c.sourceUrls) {
49247
+ const matchingDoc = c.itemIds.map((id) => poolDocMap.get(id)).find((doc) => doc?.sourceUri === url);
49248
+ if (!matchingDoc) {
49249
+ throw new Error(`candidate '${c.id}' sourceUrl '${url}' does not match any referenced item sourceUri in topic-pool.json`);
49250
+ }
49251
+ }
49252
+ }
49253
+ await fs.writeFile(join17(request.intakeDir, ".topic-candidates-valid"), `${new Date().toISOString()}
49254
+ `);
49255
+ return { valid: true };
49256
+ }
49257
+ var ItcTopicSelectRequestSchema = object2({
49258
+ schemaVersion: literal(1).default(1),
49259
+ intakeDir: string2().min(1),
49260
+ parentRunId: string2().min(1),
49261
+ selectedCandidateId: _enum2(["a", "b", "c"]),
49262
+ dbPath: string2().optional()
49263
+ });
49264
+ async function itcTopicSelect(request, fs = createNodeFileSystem()) {
49265
+ await itcTopicValidate(request, fs);
49266
+ const manifestPath = join17(request.intakeDir, "topic-pool-manifest.json");
49267
+ const poolPath = join17(request.intakeDir, "topic-pool.json");
49268
+ const candidatesPath = join17(request.intakeDir, "topic-candidates.json");
49269
+ if (!await fs.exists(manifestPath) || !await fs.exists(poolPath) || !await fs.exists(candidatesPath)) {
49270
+ throw new Error(`intake artifacts missing in ${request.intakeDir}`);
49271
+ }
49272
+ const manifest = JSON.parse(await fs.readFile(manifestPath));
49273
+ const poolDocs = JSON.parse(await fs.readFile(poolPath));
49274
+ const candidatesArtifact = JSON.parse(await fs.readFile(candidatesPath));
49275
+ const candidate = candidatesArtifact.candidates.find((c) => c.id === request.selectedCandidateId);
49276
+ if (!candidate) {
49277
+ throw new Error(`selected candidate '${request.selectedCandidateId}' not found in candidates list: [${candidatesArtifact.candidates.map((c) => c.id).join(", ")}]`);
49278
+ }
49279
+ const selectedDocSet = new Set(candidate.itemIds);
49280
+ const selectedDocs = poolDocs.filter((d) => selectedDocSet.has(d.id));
49281
+ const selectedDocsPath = join17(request.intakeDir, "selected-docs.json");
49282
+ await fs.writeFile(selectedDocsPath, `${JSON.stringify(selectedDocs, null, 2)}
49283
+ `);
49284
+ const chosenSource = {
49285
+ schemaVersion: 1,
49286
+ parentRunId: request.parentRunId,
49287
+ topicProfile: manifest.topicProfile,
49288
+ candidateId: candidate.id,
49289
+ poolDigest: manifest.poolDigest,
49290
+ topicName: candidate.title,
49291
+ displayTitle: candidate.title,
49292
+ itemIds: candidate.itemIds,
49293
+ selectedDocsPath
49294
+ };
49295
+ await fs.writeFile(join17(request.intakeDir, "chosen-source.json"), `${JSON.stringify(chosenSource, null, 2)}
49296
+ `);
49297
+ const storePath = await resolveStorePath(resolveKkConfigPath2(), request.dbPath);
49298
+ const store = await openStore({ path: storePath });
49299
+ try {
49300
+ const now = Date.now();
49301
+ const rows = [];
49302
+ for (const [index, itemId] of candidate.itemIds.entries()) {
49303
+ const item = await store.items.findById(itemId);
49304
+ if (!item) {
49305
+ throw new Error(`item '${itemId}' not found in store`);
49306
+ }
49307
+ rows.push({
49308
+ topic: manifest.topicProfile,
49309
+ itemId: item.id,
49310
+ runId: request.parentRunId,
49311
+ storyId: item.storyId ?? item.id,
49312
+ workflow: "kk-itc",
49313
+ rank: index + 1
49314
+ });
49315
+ }
49316
+ await store.selections.insertMany(rows, now);
49317
+ } finally {
49318
+ await store.close();
49319
+ }
49320
+ return { selectedCandidateId: candidate.id, itemIds: candidate.itemIds };
49321
+ }
49322
+ var ItcSelectionSettleRequestSchema = object2({
49323
+ schemaVersion: literal(1).default(1),
49324
+ intakeDir: string2().min(1),
49325
+ parentRunId: string2().min(1),
49326
+ topicProfile: string2().min(1),
49327
+ outcome: _enum2(["published", "uncertain", "author_only", "draft_only", "failed"]),
49328
+ dbPath: string2().optional()
49329
+ });
49330
+ async function itcSelectionSettle(request, fs = createNodeFileSystem()) {
49331
+ const storePath = await resolveStorePath(resolveKkConfigPath2(), request.dbPath);
49332
+ if (request.outcome === "published") {
49333
+ let itemIds;
49334
+ const chosenSourcePath = join17(request.intakeDir, "chosen-source.json");
49335
+ if (await fs.exists(chosenSourcePath)) {
49336
+ try {
49337
+ const chosen = JSON.parse(await fs.readFile(chosenSourcePath));
49338
+ if (Array.isArray(chosen.itemIds)) {
49339
+ itemIds = chosen.itemIds;
49340
+ }
49341
+ } catch {
49342
+ itemIds = undefined;
49343
+ }
49344
+ }
49345
+ const store = await openStore({ path: storePath });
49346
+ try {
49347
+ const count = await store.selections.updateStatus(request.parentRunId, request.topicProfile, "published", itemIds);
49348
+ return { settled: count, action: "marked_published" };
49349
+ } finally {
49350
+ await store.close();
49351
+ }
49352
+ }
49353
+ if (request.outcome === "uncertain") {
49354
+ const reconcileInfo = {
49355
+ schemaVersion: 1,
49356
+ parentRunId: request.parentRunId,
49357
+ topicProfile: request.topicProfile,
49358
+ settledAt: new Date().toISOString(),
49359
+ status: "retained_for_reconciliation",
49360
+ reason: "unresolved possible remote effects",
49361
+ guidance: "reconcile status manually via kk store mark-published or kk store release"
49362
+ };
49363
+ await fs.ensureDir(request.intakeDir);
49364
+ await fs.writeFile(join17(request.intakeDir, ".selection-reconcile.json"), `${JSON.stringify(reconcileInfo, null, 2)}
49365
+ `);
49366
+ return { settled: "retained", action: "reconcile_retained" };
49367
+ }
49368
+ const store = await openStore({ path: storePath });
49369
+ try {
49370
+ const count = await store.selections.updateStatus(request.parentRunId, request.topicProfile, "dropped");
49371
+ return { settled: count, action: "released" };
49372
+ } finally {
49373
+ await store.close();
49374
+ }
49375
+ }
49376
+ async function runItcTopicStage(stageName, args, fs = createNodeFileSystem()) {
49377
+ const requestIdx = args.indexOf("--request");
49378
+ const requestFile = requestIdx !== -1 ? args[requestIdx + 1] : undefined;
49379
+ if (requestFile === undefined || requestFile === "") {
49380
+ echoError(`${stageName}: --request <JSON-file> is required`);
49381
+ return 1;
49382
+ }
49383
+ if (!await fs.exists(requestFile)) {
49384
+ echoError(`${stageName}: request file not found: ${requestFile}`);
49385
+ return 1;
49386
+ }
49387
+ let raw;
49388
+ try {
49389
+ raw = JSON.parse(await fs.readFile(requestFile));
49390
+ } catch (err) {
49391
+ echoError(`${stageName}: invalid JSON in request file: ${err instanceof Error ? err.message : String(err)}`);
49392
+ return 1;
49393
+ }
49394
+ try {
49395
+ if (stageName === "itc-topic-extract") {
49396
+ const parsed = ItcTopicExtractRequestSchema.parse(raw);
49397
+ const res = await itcTopicExtract(parsed, fs);
49398
+ echo(JSON.stringify(res));
49399
+ return 0;
49400
+ }
49401
+ if (stageName === "itc-topic-validate") {
49402
+ const parsed = ItcTopicValidateRequestSchema.parse(raw);
49403
+ const res = await itcTopicValidate(parsed, fs);
49404
+ echo(JSON.stringify(res));
49405
+ return 0;
49406
+ }
49407
+ if (stageName === "itc-topic-select") {
49408
+ const parsed = ItcTopicSelectRequestSchema.parse(raw);
49409
+ const res = await itcTopicSelect(parsed, fs);
49410
+ echo(JSON.stringify(res));
49411
+ return 0;
49412
+ }
49413
+ if (stageName === "itc-selection-settle") {
49414
+ const parsed = ItcSelectionSettleRequestSchema.parse(raw);
49415
+ const res = await itcSelectionSettle(parsed, fs);
49416
+ echo(JSON.stringify(res));
49417
+ return 0;
49418
+ }
49419
+ } catch (err) {
49420
+ echoError(`${stageName}: ${err instanceof Error ? err.message : String(err)}`);
49421
+ return 1;
49422
+ }
49423
+ return;
49424
+ }
49425
+
49426
+ // src/itc-publish.ts
49427
+ var ItcDeliveryMetadataSchema = object2({
49428
+ schemaVersion: literal(1).default(1),
49429
+ effect: _enum2(["none", "confirmed", "unknown"]),
49430
+ publicUrlVerified: boolean2().optional()
49431
+ }).strict();
49432
+ var ItcReceiptSchema = object2({
49433
+ schemaVersion: literal(1).default(1),
49434
+ parentRunId: string2().min(1),
49435
+ planDigest: string2().min(1),
49436
+ payloadDigest: string2().min(1),
49437
+ plugin: string2().min(1),
49438
+ profile: string2().min(1),
49439
+ mode: _enum2(["draft", "live"]),
49440
+ attemptId: string2().min(1),
49441
+ status: _enum2(["succeeded", "failed", "uncertain"]),
49442
+ effect: _enum2(["none", "confirmed", "unknown"]),
49443
+ resultDigest: string2().optional(),
49444
+ aggregatePath: string2().optional(),
49445
+ publicUrlVerified: boolean2().optional(),
49446
+ url: string2().optional(),
49447
+ durationMs: number2().optional()
49448
+ }).strict();
49449
+ async function findCover(fs, workDir) {
49450
+ const dir = join18(workDir, "4-illustrations");
49451
+ if (!await fs.exists(dir))
49452
+ return;
49453
+ const cover = (await fs.readDir(dir)).find((file) => file.startsWith("cover."));
49454
+ return cover ? join18("4-illustrations", cover) : undefined;
49455
+ }
49456
+ async function readFrontmatter(fs, path) {
49457
+ if (!await fs.exists(path))
49458
+ return {};
49459
+ const text = await fs.readFile(path);
49460
+ if (!text.startsWith("---"))
49461
+ return {};
49462
+ const end = text.indexOf(`
49463
+ ---`, 3);
49464
+ if (end < 0)
49465
+ return {};
49466
+ const out = {};
49467
+ for (const line of text.slice(4, end).split(`
49468
+ `)) {
49469
+ const m = /^([\w-]+):\s*(.+)$/.exec(line);
49470
+ if (m?.[1] && m[2])
49471
+ out[m[1]] = m[2].trim().replace(/^['"]|['"]$/g, "");
49472
+ }
49473
+ return out;
49474
+ }
49475
+ function splitList(value) {
49476
+ return value.replace(/^\[|\]$/g, "").split(",").map((entry) => entry.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
49477
+ }
49478
+ async function itcMaterializePlan(args, fs = createNodeFileSystem()) {
49479
+ const [workDir = "", ...positional] = args;
49480
+ if (!workDir) {
49481
+ echoError("usage: itc-materialize-plan <work_dir> [targets] [source_locale] [publish_live] [preset]");
49482
+ return 1;
49483
+ }
49484
+ const snapshot = await readOptionsSnapshot(fs, workDir);
49485
+ const [targetsArg = "", sourceLocale = "", liveArg = "", presetArg = ""] = snapshot ? [snapshot.publishTargets, snapshot.sourceLocale, String(snapshot.publishLive), ""] : positional;
49486
+ const live = liveArg === "true";
49487
+ const draftPath = join18(workDir, "3-draft-draft-article.md");
49488
+ if (!await fs.exists(draftPath)) {
49489
+ echoError(`final draft not found: ${draftPath}`);
49490
+ return 1;
49491
+ }
49492
+ const lines = (await fs.readFile(draftPath)).split(`
49493
+ `);
49494
+ const h1 = lines.findIndex((line) => /^#\s+\S/.test(line));
49495
+ if (h1 < 0) {
49496
+ echoError(`no H1 title in ${draftPath}`);
49497
+ return 1;
49498
+ }
49499
+ const title = lines[h1]?.replace(/^#\s+/, "").trim() ?? "";
49500
+ const body = [...lines.slice(0, h1), ...lines.slice(h1 + 1)].join(`
49501
+ `).trim();
49502
+ if (!body) {
49503
+ echoError(`empty body in ${draftPath}`);
49504
+ return 1;
49505
+ }
49506
+ const content = { title, format: "markdown", body };
49507
+ const outlinePath = join18(workDir, "2-outline", "outline-approved.md");
49508
+ if (await fs.exists(outlinePath))
49509
+ content.outline = (await fs.readFile(outlinePath)).trim();
49510
+ const docsPath = join18(workDir, "docs.json");
49511
+ if (await fs.exists(docsPath)) {
49512
+ try {
49513
+ const docs = JSON.parse(await fs.readFile(docsPath));
49514
+ if (Array.isArray(docs)) {
49515
+ content.references = docs.map((doc) => ({
49516
+ ...typeof doc.sourceUri === "string" ? { url: doc.sourceUri } : {},
49517
+ ...typeof doc.title === "string" ? { title: doc.title } : {},
49518
+ ...typeof doc.id === "string" ? { cite: doc.id } : {}
49519
+ }));
49520
+ }
49521
+ } catch {}
49522
+ }
49523
+ const metadata = { published: live };
49524
+ const cover = await findCover(fs, workDir);
49525
+ if (cover)
49526
+ metadata.coverImage = cover;
49527
+ const brief = await readFrontmatter(fs, join18(workDir, "brief.md"));
49528
+ if (brief.tags)
49529
+ metadata.tags = splitList(brief.tags);
49530
+ if (brief.keywords)
49531
+ metadata.keywords = splitList(brief.keywords);
49532
+ if (brief.description)
49533
+ metadata.description = brief.description;
49534
+ content.metadata = metadata;
49535
+ const publishDir = join18(workDir, "4-publish");
49536
+ await fs.ensureDir(publishDir);
49537
+ await fs.writeFile(join18(publishDir, "content.json"), `${JSON.stringify(content, null, 2)}
49538
+ `);
49539
+ const fromLaunch = snapshot?.publishPresetSource === "launch" ? snapshot.publishPreset : null;
49540
+ const fromConfig = snapshot?.publishPresetSource === "config" ? snapshot.publishPreset : null;
49541
+ let resolvedTargets;
49542
+ try {
49543
+ resolvedTargets = await resolveItcTargets({
49544
+ explicitTargets: targetsArg ? targetsArg : undefined,
49545
+ explicitPreset: presetArg ? presetArg : fromLaunch ?? undefined,
49546
+ workflowConfigPreset: fromConfig ?? undefined,
49547
+ topicProfile: snapshot?.topicProfile ?? undefined,
49548
+ projectRoot: snapshot?.launchProjectRoot
49549
+ }, fs);
49550
+ } catch (err) {
49551
+ echoError(`itc-materialize-plan: ${err instanceof Error ? err.message : String(err)}`);
49552
+ return 1;
49553
+ }
49554
+ if (resolvedTargets.origin === "topic-profile" && snapshot) {
49555
+ const decision = await itcDecision({
49556
+ parentRunId: snapshot.parentRunId,
49557
+ decisionId: "kk-itc-preset-choice",
49558
+ candidates: [
49559
+ {
49560
+ id: "suggested",
49561
+ title: `${resolvedTargets.presetName}: ${resolvedTargets.targets.map((t) => t.plugin).join(", ")}`
49562
+ }
49563
+ ],
49564
+ outPath: join18(workDir, "4-publish", "preset-decision.json")
49565
+ }, fs);
49566
+ if (decision.value !== "suggested")
49567
+ resolvedTargets = { targets: [], origin: "none" };
49568
+ }
49569
+ if (resolvedTargets.targets.length === 0) {
49570
+ const emptyPlan = {
49571
+ schemaVersion: 1,
49572
+ live,
49573
+ sourceLocale: sourceLocale || "en",
49574
+ origin: resolvedTargets.origin,
49575
+ variants: {},
49576
+ fanout: [],
49577
+ browser: [],
49578
+ targets: []
49579
+ };
49580
+ await fs.writeFile(join18(publishDir, "publish-plan.json"), `${JSON.stringify(emptyPlan, null, 2)}
49581
+ `);
49582
+ return 0;
49583
+ }
49584
+ const { plugins } = discoverAllPlugins();
49585
+ const req = {
49586
+ schemaVersion: 1,
49587
+ workDir,
49588
+ sourceLocale: sourceLocale || "en",
49589
+ live,
49590
+ targets: resolvedTargets.targets,
49591
+ origin: resolvedTargets.origin,
49592
+ presetName: resolvedTargets.presetName,
49593
+ presetDigest: resolvedTargets.presetDigest
49594
+ };
49595
+ try {
49596
+ const plan = await buildItcPublishPlan(req, plugins, fs);
49597
+ const planPath = join18(publishDir, "publish-plan.json");
49598
+ await fs.writeFile(planPath, `${JSON.stringify(plan, null, 2)}
49599
+ `);
49600
+ await prepareTargetPayloads(fs, workDir, plan, plugins);
49601
+ return 0;
49602
+ } catch (err) {
49603
+ echoError(`itc-materialize-plan: ${err instanceof Error ? err.message : String(err)}`);
49604
+ return 1;
49605
+ }
49606
+ }
49607
+ async function readOptionsSnapshot(fs, workDir) {
49608
+ const path = join18(workDir, "itc-options.json");
49609
+ if (!await fs.exists(path))
49610
+ return;
49611
+ return JSON.parse(await fs.readFile(path));
49612
+ }
49613
+ async function prepareTargetPayloads(fs, workDir, plan, plugins) {
49614
+ const publishDir = join18(workDir, "4-publish");
49615
+ if (!plan.targets || plan.targets.length === 0)
49616
+ return;
49617
+ const planned = new Set(plan.targets.map((t) => t.plugin));
49618
+ const modeKeys = new Set(["operation", "published", "publishStatus", "private", "mode"]);
49619
+ for (const plugin of plugins) {
49620
+ if (!planned.has(plugin.name))
49621
+ continue;
49622
+ for (const profile of Object.values(plugin.publishing?.profiles ?? {})) {
49623
+ for (const binding of Object.values(profile.modes)) {
49624
+ for (const key of Object.keys(binding?.metadata ?? {}))
49625
+ modeKeys.add(key);
49626
+ }
49627
+ }
49628
+ }
49629
+ for (const target of plan.targets) {
49630
+ const variantEntry = plan.variants[target.variant];
49631
+ const baseFile = variantEntry?.file ?? "content.json";
49632
+ let basePath = join18(publishDir, baseFile);
49633
+ if (!await fs.exists(basePath)) {
49634
+ basePath = join18(publishDir, "content.json");
49635
+ }
49636
+ if (!await fs.exists(basePath))
49637
+ continue;
49638
+ const baseContent = JSON.parse(await fs.readFile(basePath));
49639
+ const cleanedMetadata = { ...baseContent.metadata ?? {} };
49640
+ for (const k of modeKeys) {
49641
+ delete cleanedMetadata[k];
49642
+ }
49643
+ const isolatedContent = {
49644
+ ...baseContent,
49645
+ metadata: {
49646
+ ...cleanedMetadata,
49647
+ ...target.metadata
49648
+ }
49649
+ };
49650
+ const targetFileName = `content.${target.plugin}.json`;
49651
+ const targetFilePath = join18(publishDir, targetFileName);
49652
+ const contentStr = `${JSON.stringify(isolatedContent, null, 2)}
49653
+ `;
49654
+ await fs.writeFile(targetFilePath, contentStr);
49655
+ const payloadDigest = createHash6("sha256").update(contentStr).digest("hex");
49656
+ target.payloadPath = `4-publish/${targetFileName}`;
49657
+ target.payloadDigest = payloadDigest;
49658
+ }
49659
+ await fs.writeFile(join18(publishDir, "publish-plan.json"), `${JSON.stringify(plan, null, 2)}
49660
+ `);
49661
+ }
49662
+ async function itcCheckTargets(args, fs = createNodeFileSystem(), executor = new NodeProcessExecutor) {
49663
+ const [workDir = ""] = args;
49664
+ if (!workDir) {
49665
+ echoError("usage: itc-check-targets <work_dir>");
49666
+ return 1;
49667
+ }
49668
+ const planPath = join18(workDir, "4-publish/publish-plan.json");
49669
+ if (!await fs.exists(planPath))
49670
+ return 0;
49671
+ const plan = JSON.parse(await fs.readFile(planPath));
49672
+ if (!plan.targets || plan.targets.length === 0)
49673
+ return 0;
49674
+ const { plugins } = discoverAllPlugins();
49675
+ const byName = new Map(plugins.map((p) => [p.name, p]));
49676
+ for (const target of plan.targets) {
49677
+ const plugin = byName.get(target.plugin);
49678
+ if (plugin?.check) {
49679
+ const probe = await executor.run({
49680
+ command: process.execPath,
49681
+ args: [plugin.check, "check"],
49682
+ env: process.env,
49683
+ timeout: 60000
49684
+ });
49685
+ if (probe.exitCode !== 0) {
49686
+ const botName = plugin.browserSession?.profileName ?? plugin.name;
49687
+ echoError(`channel probe for '${target.plugin}' refused: run \`kk bot open/login ${botName}\` to authenticate`);
49688
+ return 1;
49689
+ }
49690
+ }
49691
+ }
49692
+ return 0;
49693
+ }
49694
+ async function itcSendTargets(args, fs = createNodeFileSystem(), dispatch = runFanout) {
49695
+ const [workDir = ""] = args;
49696
+ const isRetry = args.includes("--retry");
49697
+ if (!workDir) {
49698
+ echoError("usage: itc-send-targets <work_dir> [--retry]");
49699
+ return 1;
49700
+ }
49701
+ const publishDir = join18(workDir, "4-publish");
49702
+ const planPath = join18(publishDir, "publish-plan.json");
49703
+ if (!await fs.exists(planPath))
49704
+ return 0;
49705
+ const plan = JSON.parse(await fs.readFile(planPath));
49706
+ if (!plan.targets || plan.targets.length === 0)
49707
+ return 0;
49708
+ const parentRunId = (await readOptionsSnapshot(fs, workDir))?.parentRunId;
49709
+ if (!parentRunId) {
49710
+ echoError(`itc-send-targets: ${join18(workDir, "itc-options.json")} has no parentRunId`);
49711
+ return 1;
49712
+ }
49713
+ const receiptsDir = join18(publishDir, "receipts");
49714
+ await fs.ensureDir(receiptsDir);
49715
+ const planRaw = await fs.readFile(planPath);
49716
+ const planDigest = createHash6("sha256").update(planRaw).digest("hex");
49717
+ for (const target of plan.targets) {
49718
+ const targetReceiptPath = join18(receiptsDir, `${target.plugin}.json`);
49719
+ if (isRetry) {
49720
+ if (!await fs.exists(targetReceiptPath))
49721
+ continue;
49722
+ try {
49723
+ const priorReceipt = JSON.parse(await fs.readFile(targetReceiptPath));
49724
+ if (priorReceipt.status === "succeeded" || priorReceipt.effect !== "none") {
49725
+ continue;
49726
+ }
49727
+ } catch {
49728
+ continue;
49729
+ }
49730
+ } else if (await fs.exists(targetReceiptPath)) {
49731
+ try {
49732
+ const prior = JSON.parse(await fs.readFile(targetReceiptPath));
49733
+ if (prior.status === "succeeded")
49734
+ continue;
49735
+ } catch {}
49736
+ }
49737
+ const payloadRel = target.payloadPath ?? `4-publish/content.${target.plugin}.json`;
49738
+ const payloadPath = join18(workDir, payloadRel);
49739
+ const payloadRaw = await fs.exists(payloadPath) ? await fs.readFile(payloadPath) : "{}";
49740
+ const payloadDigest = createHash6("sha256").update(payloadRaw).digest("hex");
49741
+ const outPath = join18(publishDir, `fanout-result.${target.plugin}${isRetry ? ".retry" : ""}.json`);
49742
+ const fanoutArgs = [
49743
+ "--content",
49744
+ payloadPath,
49745
+ "--target",
49746
+ target.plugin,
49747
+ "--out",
49748
+ outPath,
49749
+ ...target.transport === "browser" ? ["--serial-target", target.plugin] : []
49750
+ ];
49751
+ let fanoutExit = 0;
49752
+ if (!await fs.exists(outPath)) {
49753
+ try {
49754
+ fanoutExit = await dispatch(fanoutArgs);
49755
+ } catch {
49756
+ fanoutExit = 1;
49757
+ }
49758
+ }
49759
+ let outcome = {};
49760
+ if (await fs.exists(outPath)) {
49761
+ try {
49762
+ const agg = JSON.parse(await fs.readFile(outPath));
49763
+ const found = (agg.targets ?? []).find((t) => t.target === target.plugin);
49764
+ if (found)
49765
+ outcome = found;
49766
+ } catch {
49767
+ outcome = { ok: false, status: "failed", error: "failed to parse fanout result" };
49768
+ }
49769
+ }
49770
+ const isSuccess = fanoutExit === 0 && outcome.ok === true && outcome.status === "published";
49771
+ const itcDelivery = outcome.metadata?.itcDelivery;
49772
+ let effect = "unknown";
49773
+ let publicUrlVerified = false;
49774
+ if (isSuccess) {
49775
+ effect = "confirmed";
49776
+ if (target.mode === "live" && outcome.url) {
49777
+ publicUrlVerified = itcDelivery?.publicUrlVerified === true;
49778
+ }
49779
+ } else if (itcDelivery?.effect === "none") {
49780
+ effect = "none";
49781
+ }
49782
+ const receipt = {
49783
+ schemaVersion: 1,
49784
+ parentRunId,
49785
+ planDigest,
49786
+ payloadDigest,
49787
+ plugin: target.plugin,
49788
+ profile: target.profile,
49789
+ mode: target.mode,
49790
+ attemptId: isRetry ? "retry-1" : "attempt-1",
49791
+ status: isSuccess ? "succeeded" : effect === "unknown" ? "uncertain" : "failed",
49792
+ effect,
49793
+ publicUrlVerified,
49794
+ ...outcome.url ? { url: outcome.url } : {},
49795
+ ...outcome.durationMs !== undefined ? { durationMs: outcome.durationMs } : {},
49796
+ aggregatePath: outPath
49797
+ };
49798
+ await fs.writeFile(targetReceiptPath, `${JSON.stringify(receipt, null, 2)}
49799
+ `);
49800
+ }
49801
+ return 0;
49802
+ }
49803
+ async function itcReconcileReceipts(args, fs = createNodeFileSystem()) {
49804
+ const [workDir = ""] = args;
49805
+ if (!workDir) {
49806
+ echoError("usage: itc-reconcile-receipts <work_dir>");
49807
+ return 1;
49808
+ }
49809
+ const publishDir = join18(workDir, "4-publish");
49810
+ const planPath = join18(publishDir, "publish-plan.json");
49811
+ if (!await fs.exists(planPath))
49812
+ return 0;
49813
+ const plan = JSON.parse(await fs.readFile(planPath));
49814
+ const chosenPath = join18(workDir, "chosen-source.json");
49815
+ const chosen = await fs.exists(chosenPath) ? JSON.parse(await fs.readFile(chosenPath)) : undefined;
49816
+ const settle = async (outcome) => {
49817
+ if (chosen === undefined)
49818
+ return;
49819
+ await itcSelectionSettle({
49820
+ schemaVersion: 1,
49821
+ intakeDir: workDir,
49822
+ parentRunId: chosen.parentRunId,
49823
+ topicProfile: chosen.topicProfile,
49824
+ outcome
49825
+ }, fs);
49826
+ };
49827
+ if (!plan.targets || plan.targets.length === 0) {
49828
+ await settle("author_only");
49829
+ return 0;
49830
+ }
49831
+ const receiptsDir = join18(publishDir, "receipts");
49832
+ const receipts = [];
49833
+ for (const target of plan.targets) {
49834
+ const rPath = join18(receiptsDir, `${target.plugin}.json`);
49835
+ if (!await fs.exists(rPath)) {
49836
+ echoError(`itc-reconcile-receipts: missing receipt for target '${target.plugin}'`);
49837
+ return 1;
49838
+ }
49839
+ try {
49840
+ const r = JSON.parse(await fs.readFile(rPath));
49841
+ receipts.push(r);
49842
+ } catch {
49843
+ echoError(`itc-reconcile-receipts: corrupt receipt for target '${target.plugin}'`);
49844
+ return 1;
49845
+ }
49846
+ }
49847
+ const liveSuccess = receipts.some((r) => r.mode === "live" && r.status === "succeeded");
49848
+ const hasUncertain = receipts.some((r) => r.effect === "unknown" || r.status === "uncertain");
49849
+ const allSucceeded = receipts.every((r) => r.status === "succeeded");
49850
+ await settle(liveSuccess ? "published" : hasUncertain ? "uncertain" : allSucceeded ? "draft_only" : "failed");
49851
+ return 0;
49852
+ }
49853
+ async function itcExportSeo(args, fs = createNodeFileSystem()) {
49854
+ const [workDir = "", mode = "handoff"] = args;
49855
+ if (!workDir) {
49856
+ echoError("usage: itc-export-seo <work_dir> [mode]");
49857
+ return 1;
49858
+ }
49859
+ const seoDir = join18(workDir, "seo");
49860
+ await fs.ensureDir(seoDir);
49861
+ if (mode === "off") {
49862
+ await fs.writeFile(join18(seoDir, "prepublish-request.json"), `${JSON.stringify({ schemaVersion: 1, mode: "off", status: "not-assessed" }, null, 2)}
49863
+ `);
49864
+ await fs.writeFile(join18(seoDir, "published-pages.json"), `${JSON.stringify({ schemaVersion: 1, mode: "off", pages: [], status: "not-assessed" }, null, 2)}
49865
+ `);
49866
+ return 0;
49867
+ }
49868
+ const prepublish = {
49869
+ schemaVersion: 1,
49870
+ mode: "handoff",
49871
+ status: "ready",
49872
+ draftFile: "3-draft-draft-article.md"
49873
+ };
49874
+ await fs.writeFile(join18(seoDir, "prepublish-request.json"), `${JSON.stringify(prepublish, null, 2)}
49875
+ `);
49876
+ const receiptsDir = join18(workDir, "4-publish/receipts");
49877
+ const verifiedPages = [];
49878
+ if (await fs.exists(receiptsDir)) {
49879
+ for (const file of (await fs.readDir(receiptsDir)).filter((f) => f.endsWith(".json"))) {
49880
+ try {
49881
+ const r = JSON.parse(await fs.readFile(join18(receiptsDir, file)));
49882
+ if (r.mode === "live" && r.effect === "confirmed" && r.publicUrlVerified === true && r.url) {
49883
+ verifiedPages.push({ plugin: r.plugin, url: r.url });
49884
+ }
49885
+ } catch {}
49886
+ }
49887
+ }
49888
+ const publishedPages = {
49889
+ schemaVersion: 1,
49890
+ mode: "handoff",
49891
+ status: verifiedPages.length > 0 ? "assessed" : "not-assessed",
49892
+ pages: verifiedPages,
49893
+ ...verifiedPages.length === 0 ? { reason: "no verified public live URLs available" } : {}
49894
+ };
49895
+ await fs.writeFile(join18(seoDir, "published-pages.json"), `${JSON.stringify(publishedPages, null, 2)}
49896
+ `);
49897
+ return 0;
49898
+ }
49899
+ async function itcReportSummary(args, fs = createNodeFileSystem()) {
49900
+ const [workDir = "", maxRevisions = "2"] = args;
49901
+ if (!workDir) {
49902
+ echoError("usage: itc-report-summary <work_dir> [max_revisions]");
49903
+ return 1;
49904
+ }
49905
+ const reportDir = join18(workDir, "5-report");
49906
+ await fs.ensureDir(reportDir);
49907
+ let verdict = "PASS";
49908
+ let outcome = "done";
49909
+ const verdictPath = join18(workDir, ".itc-verdict.json");
49910
+ if (await fs.exists(verdictPath)) {
49911
+ try {
49912
+ const v = JSON.parse(await fs.readFile(verdictPath));
49913
+ verdict = v.verdict ?? "PASS";
49914
+ if (verdict !== "PASS")
49915
+ outcome = "failed";
49916
+ } catch {
49917
+ outcome = "failed";
49918
+ }
49919
+ }
49920
+ let revisions = 0;
49921
+ const revPath = join18(workDir, ".auto-revise-count");
49922
+ if (await fs.exists(revPath)) {
49923
+ try {
49924
+ revisions = Number((await fs.readFile(revPath)).trim()) || 0;
49925
+ } catch {
49926
+ revisions = 0;
49927
+ }
49928
+ }
49929
+ const receiptsDir = join18(workDir, "4-publish/receipts");
49930
+ const receipts = [];
49931
+ if (await fs.exists(receiptsDir)) {
49932
+ for (const file of (await fs.readDir(receiptsDir)).filter((f) => f.endsWith(".json"))) {
49933
+ try {
49934
+ const r = JSON.parse(await fs.readFile(join18(receiptsDir, file)));
49935
+ receipts.push(r);
49936
+ } catch {}
49937
+ }
49938
+ }
49939
+ if (receipts.some((r) => r.status !== "succeeded")) {
49940
+ outcome = "failed";
49941
+ }
49942
+ const planPath = join18(workDir, "4-publish/publish-plan.json");
49943
+ let targetContract;
49944
+ if (await fs.exists(planPath)) {
49945
+ const planRaw = await fs.readFile(planPath);
49946
+ const planDigest = createHash6("sha256").update(planRaw).digest("hex");
49947
+ const expected = (JSON.parse(planRaw).targets ?? []).map((t) => t.plugin).sort();
49948
+ const observed = receipts.map((r) => r.plugin).sort();
49949
+ const stale = receipts.filter((r) => r.planDigest !== planDigest).map((r) => r.plugin);
49950
+ if (expected.join(`
49951
+ `) !== observed.join(`
49952
+ `) || stale.length > 0) {
49953
+ targetContract = { expected, observed, stale };
49954
+ outcome = "failed";
49955
+ }
49956
+ }
49957
+ const reportJson = {
49958
+ outcome,
49959
+ verdict,
49960
+ revisions,
49961
+ receipts,
49962
+ ...targetContract ? { targetContractFailure: targetContract } : {},
49963
+ maxRevisions: Number(maxRevisions) || 2
49964
+ };
49965
+ await fs.writeFile(join18(reportDir, "report.json"), `${JSON.stringify(reportJson, null, 2)}
49966
+ `);
49967
+ const md = [
49968
+ "# ITC Publication Run Report",
49969
+ "",
49970
+ `- Outcome: ${outcome}`,
49971
+ `- Verdict: ${verdict}`,
49972
+ `- Revisions: ${revisions}`,
49973
+ `- Target Receipts: ${receipts.length}`,
49974
+ ...targetContract ? [
49975
+ `- Target contract failure: expected [${targetContract.expected.join(", ")}], observed [${targetContract.observed.join(", ")}], stale [${targetContract.stale.join(", ")}]`
49976
+ ] : [],
49977
+ "",
49978
+ "## Targets",
49979
+ ...receipts.map((r) => `- ${r.plugin} (${r.profile}): status=${r.status}, effect=${r.effect}`)
49980
+ ].join(`
49981
+ `);
49982
+ await fs.writeFile(join18(reportDir, "report.md"), `${md}
49983
+ `);
49984
+ return 0;
49985
+ }
49986
+ async function itcAuthorVerdict(args, fs = createNodeFileSystem()) {
49987
+ const [workDir = ""] = args;
49988
+ if (!workDir) {
49989
+ echoError("usage: itc-author-verdict <work_dir>");
49990
+ return 1;
49991
+ }
49992
+ const auto = process.env.KK_ITC_AUTO === "true";
49993
+ const maxRevisions = Number(process.env.KK_ITC_MAX_REVISIONS ?? "2") || 2;
49994
+ const readCount = async (name) => {
49995
+ const p = join18(workDir, name);
49996
+ if (!await fs.exists(p))
49997
+ return 0;
49998
+ const n = Number((await fs.readFile(p)).trim());
49999
+ return Number.isFinite(n) ? n : 0;
50000
+ };
50001
+ const verdictPath = join18(workDir, ".itc-verdict.json");
50002
+ let verdict = "";
50003
+ let confidence = "";
50004
+ if (await fs.exists(verdictPath)) {
50005
+ try {
50006
+ const v = JSON.parse(await fs.readFile(verdictPath));
50007
+ verdict = typeof v.verdict === "string" ? v.verdict : "";
50008
+ confidence = typeof v.confidence === "string" ? v.confidence : "";
50009
+ } catch {
50010
+ verdict = "malformed";
50011
+ }
50012
+ }
50013
+ let disposition;
50014
+ if (verdict === "") {
50015
+ disposition = "finish";
50016
+ } else if (verdict === "PASS" && ["HIGH", "MEDIUM", "LOW"].includes(confidence)) {
50017
+ disposition = "finish";
50018
+ } else if (verdict === "NEEDS_REVISION") {
50019
+ if (auto) {
50020
+ disposition = await readCount(".auto-revise-count") < maxRevisions ? "autorevise" : "fail";
50021
+ } else {
50022
+ disposition = await readCount(".revise-count") < 3 ? "revise" : "fail";
50023
+ }
50024
+ } else {
50025
+ disposition = "fail";
50026
+ }
50027
+ await fs.ensureDir(join18(workDir, ".itc"));
50028
+ await fs.writeFile(join18(workDir, ".itc/disposition.txt"), `${disposition}
50029
+ `);
50030
+ return 0;
50031
+ }
50032
+
50033
+ // src/itc-stage.ts
50034
+ async function runItcStage(args, fs = createNodeFileSystem()) {
50035
+ if (args.length === 0)
50036
+ return;
50037
+ const stageName = args[0];
50038
+ if (stageName === "itc-publish-plan") {
50039
+ return runItcPublishPlan(args.slice(1), fs);
50040
+ }
50041
+ if (stageName === "itc-materialize-plan") {
50042
+ return itcMaterializePlan(args.slice(1), fs);
50043
+ }
50044
+ if (stageName === "itc-check-targets") {
50045
+ return itcCheckTargets(args.slice(1), fs);
50046
+ }
50047
+ if (stageName === "itc-send-targets") {
50048
+ return itcSendTargets(args.slice(1), fs);
50049
+ }
50050
+ if (stageName === "itc-reconcile-receipts") {
50051
+ return itcReconcileReceipts(args.slice(1), fs);
50052
+ }
50053
+ if (stageName === "itc-export-seo") {
50054
+ return itcExportSeo(args.slice(1), fs);
50055
+ }
50056
+ if (stageName === "itc-report-summary") {
50057
+ return itcReportSummary(args.slice(1), fs);
50058
+ }
50059
+ if (stageName === "itc-prepare") {
50060
+ return runItcPrepare(args.slice(1), fs);
50061
+ }
50062
+ if (stageName === "itc-author-verdict") {
50063
+ return itcAuthorVerdict(args.slice(1), fs);
50064
+ }
50065
+ if (stageName === "itc-decision") {
50066
+ return runItcDecision(args.slice(1), fs);
50067
+ }
50068
+ if (stageName === "itc-author-complete") {
50069
+ return runItcAuthorComplete(args.slice(1), fs);
50070
+ }
50071
+ if (stageName === "itc-topic-extract" || stageName === "itc-topic-validate" || stageName === "itc-topic-select" || stageName === "itc-selection-settle") {
50072
+ return runItcTopicStage(stageName, args.slice(1), fs);
50073
+ }
50074
+ return;
50075
+ }
50076
+ async function runItcPublishPlan(args, fs) {
50077
+ let request;
50078
+ const requestIdx = args.indexOf("--request");
50079
+ if (requestIdx !== -1) {
50080
+ const filePath = args[requestIdx + 1];
50081
+ if (filePath === undefined || filePath === "" || !await fs.exists(filePath)) {
50082
+ echoError(`itc-publish-plan: request file not found: ${filePath ?? ""}`);
50083
+ return 1;
50084
+ }
50085
+ try {
50086
+ const raw = JSON.parse(await fs.readFile(filePath));
50087
+ const parsed = ItcPlanRequestSchema.safeParse(raw);
50088
+ if (!parsed.success) {
50089
+ echoError(`itc-publish-plan: invalid request schema: ${parsed.error.message}`);
50090
+ return 1;
50091
+ }
50092
+ request = parsed.data;
50093
+ } catch (err) {
50094
+ echoError(`itc-publish-plan: failed to read request file: ${err instanceof Error ? err.message : String(err)}`);
50095
+ return 1;
50096
+ }
50097
+ } else {
50098
+ const [workDir = "", targetsArg = "", sourceLocale = "", liveArg = ""] = args;
50099
+ if (!workDir || !targetsArg || !sourceLocale) {
50100
+ echoError("usage: itc-publish-plan <work_dir> <targets> <source_locale> <publish_live>");
50101
+ return 1;
50102
+ }
50103
+ const live = liveArg === "true";
50104
+ const targetTokens = targetsArg.split(",").map((t) => t.trim()).filter(Boolean);
50105
+ const targets = targetTokens.map((token) => {
50106
+ const colonIdx = token.indexOf(":");
50107
+ if (colonIdx !== -1) {
50108
+ return {
50109
+ plugin: token.slice(0, colonIdx),
50110
+ profile: token.slice(colonIdx + 1)
50111
+ };
50112
+ }
50113
+ return { plugin: token };
50114
+ });
50115
+ request = {
50116
+ workDir,
50117
+ sourceLocale,
50118
+ live,
50119
+ targets
50120
+ };
50121
+ }
50122
+ try {
50123
+ const { plugins } = discoverAllPlugins();
50124
+ const plan = await buildItcPublishPlan(request, plugins, fs);
50125
+ const outDir = join19(request.workDir, "4-publish");
50126
+ await fs.ensureDir(outDir);
50127
+ await fs.writeFile(join19(outDir, "publish-plan.json"), `${JSON.stringify(plan, null, 4)}
50128
+ `);
50129
+ return 0;
50130
+ } catch (err) {
50131
+ const msg = err instanceof Error ? err.message : String(err);
50132
+ echoError(msg);
50133
+ return 1;
50134
+ }
50135
+ }
50136
+ async function runItcDecision(args, fs) {
50137
+ if (args[0] === "--outline" || args[0] === "--topic")
50138
+ return runSlotDecision(args, fs);
50139
+ const requestIdx = args.indexOf("--request");
50140
+ const requestFile = requestIdx !== -1 ? args[requestIdx + 1] : undefined;
50141
+ if (!requestFile) {
50142
+ echoError("itc-decision: --request <JSON-file> is required");
50143
+ return 1;
50144
+ }
50145
+ if (!await fs.exists(requestFile)) {
50146
+ echoError(`itc-decision: request file not found: ${requestFile}`);
50147
+ return 1;
50148
+ }
50149
+ try {
50150
+ const raw = JSON.parse(await fs.readFile(requestFile));
50151
+ const parsed = ItcDecisionRequestSchema.parse(raw);
50152
+ await itcDecision(parsed, fs);
50153
+ return 0;
50154
+ } catch (err) {
50155
+ echoError(`itc-decision: ${err instanceof Error ? err.message : String(err)}`);
50156
+ return 1;
50157
+ }
50158
+ }
50159
+ async function runSlotDecision(args, fs) {
50160
+ const [mode, dir = "", parentRunId = ""] = args;
50161
+ if (!dir || !parentRunId) {
50162
+ echoError(`itc-decision: usage: itc-decision ${mode} <dir> <parentRunId>`);
50163
+ return 1;
50164
+ }
50165
+ try {
50166
+ if (mode === "--topic") {
50167
+ const candidatesPath = join19(dir, "topic-candidates.json");
50168
+ const artifact = JSON.parse(await fs.readFile(candidatesPath));
50169
+ const result = await itcDecision({
50170
+ parentRunId,
50171
+ decisionId: "kk-itc-topic-choice",
50172
+ candidates: artifact.candidates.map((c) => ({ id: c.id, title: c.title, description: c.reason })),
50173
+ evidenceFiles: [candidatesPath],
50174
+ outPath: join19(dir, "topic-decision.json")
50175
+ }, fs);
50176
+ echo(result.value);
50177
+ return 0;
50178
+ }
50179
+ const outlineDir = join19(dir, "2-outline");
50180
+ const candidates = [];
50181
+ const evidenceFiles = [];
50182
+ for (const id of ["a", "b", "c"]) {
50183
+ const file = join19(outlineDir, `outline-option-${id}.md`);
50184
+ if (!await fs.exists(file))
50185
+ continue;
50186
+ const heading = (await fs.readFile(file)).split(`
50187
+ `).find((line) => line.trim() !== "") ?? id;
50188
+ candidates.push({ id, title: heading.replace(/^#+\s*/, "").trim() });
50189
+ evidenceFiles.push(file);
50190
+ }
50191
+ if (candidates.length === 0)
50192
+ throw new Error(`no outline options in ${outlineDir}`);
50193
+ evidenceFiles.push(join19(outlineDir, "outline-recommendation.json"));
50194
+ const result = await itcDecision({
50195
+ parentRunId,
50196
+ decisionId: "kk-itc-outline-choice",
50197
+ candidates,
50198
+ evidenceFiles,
50199
+ outPath: join19(outlineDir, "outline-decision.json")
50200
+ }, fs);
50201
+ const pick = result.value === "defer" ? "" : result.value;
50202
+ if (pick === "")
50203
+ echoError(`itc-decision: outline choice deferred to the operator: ${result.reason}`);
50204
+ await fs.writeFile(join19(outlineDir, ".auto-pick"), pick);
50205
+ return 0;
50206
+ } catch (err) {
50207
+ echoError(`itc-decision: ${err instanceof Error ? err.message : String(err)}`);
50208
+ return 1;
50209
+ }
50210
+ }
50211
+ async function runItcAuthorComplete(args, fs) {
50212
+ const [workDir = "", parentRunId = "", reviewModeArg = ""] = args;
50213
+ if (!workDir || !parentRunId) {
50214
+ echoError("usage: itc-author-complete <work_dir> <parentRunId> [reviewMode]");
50215
+ return 1;
50216
+ }
50217
+ const draftPath = join19(workDir, "3-draft-draft-article.md");
50218
+ if (!await fs.exists(draftPath)) {
50219
+ echoError(`itc-author-complete: draft not found: ${draftPath}`);
50220
+ return 1;
50221
+ }
50222
+ const draftText = await fs.readFile(draftPath);
50223
+ if (draftText.trim().length === 0) {
50224
+ echoError(`itc-author-complete: empty draft in ${draftPath}`);
50225
+ return 1;
50226
+ }
50227
+ const draftDigest = createHash7("sha256").update(draftText).digest("hex");
50228
+ const outlinePath = join19(workDir, "2-outline/outline-approved.md");
50229
+ if (!await fs.exists(outlinePath)) {
50230
+ echoError(`itc-author-complete: approved outline not found: ${outlinePath}`);
50231
+ return 1;
50232
+ }
50233
+ const outlineText = await fs.readFile(outlinePath);
50234
+ if (outlineText.trim().length === 0) {
50235
+ echoError(`itc-author-complete: empty outline in ${outlinePath}`);
50236
+ return 1;
50237
+ }
50238
+ const outlineDigest = createHash7("sha256").update(outlineText).digest("hex");
50239
+ const verdictPath = join19(workDir, ".itc-verdict.json");
50240
+ let judgeDigest = null;
50241
+ let reviewMode = reviewModeArg === "manual" ? "manual" : "judged";
50242
+ if (await fs.exists(verdictPath)) {
50243
+ try {
50244
+ const verdictRaw = await fs.readFile(verdictPath);
50245
+ const verdictData = JSON.parse(verdictRaw);
50246
+ if (verdictData.verdict !== "PASS") {
50247
+ echoError(`itc-author-complete: judge verdict is not PASS: ${String(verdictData.verdict)}`);
50248
+ return 1;
50249
+ }
50250
+ const conf = String(verdictData.confidence ?? "");
50251
+ if (!["HIGH", "MEDIUM", "LOW"].includes(conf)) {
50252
+ echoError(`itc-author-complete: judge confidence must be HIGH, MEDIUM, or LOW, got '${conf}'`);
50253
+ return 1;
50254
+ }
50255
+ judgeDigest = createHash7("sha256").update(verdictRaw).digest("hex");
50256
+ reviewMode = "judged";
50257
+ } catch (err) {
50258
+ echoError(`itc-author-complete: invalid verdict JSON: ${err instanceof Error ? err.message : String(err)}`);
50259
+ return 1;
50260
+ }
50261
+ } else {
50262
+ reviewMode = "manual";
50263
+ }
50264
+ const runPath = join19(workDir, ".itc/author-run.json");
50265
+ let childRunId = `${parentRunId}-author`;
50266
+ if (await fs.exists(runPath)) {
50267
+ try {
50268
+ const runMeta = JSON.parse(await fs.readFile(runPath));
50269
+ if (runMeta.childRunId)
50270
+ childRunId = runMeta.childRunId;
50271
+ } catch {}
50272
+ }
50273
+ const completeReceipt = {
50274
+ schemaVersion: 1,
50275
+ parentRunId,
50276
+ childRunId,
50277
+ draftDigest,
50278
+ outlineDigest,
50279
+ judgeDigest,
50280
+ reviewMode,
50281
+ status: "complete"
50282
+ };
50283
+ const itcDir = join19(workDir, ".itc");
50284
+ await fs.ensureDir(itcDir);
50285
+ await fs.writeFile(join19(itcDir, "author-complete.json"), `${JSON.stringify(completeReceipt, null, 2)}
50286
+ `);
50287
+ return 0;
50288
+ }
50289
+ async function runItcPrepare(args, fs) {
50290
+ const requestIdx = args.indexOf("--request");
50291
+ const requestFile = requestIdx !== -1 ? args[requestIdx + 1] : undefined;
50292
+ if (requestFile === undefined || requestFile === "") {
50293
+ echoError("itc-prepare: --request <launch.json> is required");
50294
+ return 1;
50295
+ }
50296
+ if (!await fs.exists(requestFile)) {
50297
+ echoError(`itc-prepare: request file not found: ${requestFile}`);
50298
+ return 1;
50299
+ }
50300
+ let launch;
50301
+ try {
50302
+ launch = JSON.parse(await fs.readFile(requestFile));
50303
+ } catch (err) {
50304
+ echoError(`itc-prepare: invalid JSON in ${requestFile}: ${err instanceof Error ? err.message : String(err)}`);
50305
+ return 1;
50306
+ }
50307
+ let options;
50308
+ try {
50309
+ options = await resolveItcOptions(launch, {
50310
+ ...process.env.KK_CONFIG ? { env: process.env } : {}
50311
+ }, fs);
50312
+ } catch (err) {
50313
+ echoError(`itc-prepare: ${err instanceof Error ? err.message : String(err)}`);
50314
+ return 1;
50315
+ }
50316
+ const chosenIdx = args.indexOf("--chosen");
50317
+ const intakeDir = chosenIdx !== -1 ? args[chosenIdx + 1] : undefined;
50318
+ if (intakeDir !== undefined) {
50319
+ if (options.status !== "intake") {
50320
+ echoError("itc-prepare: --chosen applies only to a --from-collect launch");
50321
+ return 1;
50322
+ }
50323
+ const chosenPath = join19(intakeDir, "chosen-source.json");
50324
+ if (!await fs.exists(chosenPath)) {
50325
+ echoError(`itc-prepare: chosen source not found: ${chosenPath}`);
50326
+ return 1;
50327
+ }
50328
+ let chosen;
50329
+ try {
50330
+ chosen = JSON.parse(await fs.readFile(chosenPath));
50331
+ if (chosen.parentRunId !== options.parentRunId) {
50332
+ throw new Error(`chosen source belongs to run '${chosen.parentRunId}', not '${options.parentRunId}'`);
50333
+ }
50334
+ options = finalizeItcCollectedOptions(options, chosen, launch);
50335
+ } catch (err) {
50336
+ echoError(`itc-prepare: ${err instanceof Error ? err.message : String(err)}`);
50337
+ return 1;
50338
+ }
50339
+ }
50340
+ const workDir = options.workDir;
50341
+ if (workDir === null) {
50342
+ echo(JSON.stringify(options));
50343
+ return 0;
50344
+ }
50345
+ const reuseCheck = await verifyItcOptionsSnapshot(workDir, options, fs);
50346
+ if (reuseCheck.ok === false && !options.force) {
50347
+ echoError(`itc-prepare: ${reuseCheck.reason}`);
50348
+ return 1;
50349
+ }
50350
+ try {
50351
+ await writeItcOptionsSnapshot(workDir, options, fs);
50352
+ if (intakeDir !== undefined) {
50353
+ const copies = [
50354
+ ["chosen-source.json", "chosen-source.json"],
50355
+ ["selected-docs.json", "docs.json"],
50356
+ ["topic-pool-manifest.json", "topic-pool-manifest.json"]
50357
+ ];
50358
+ for (const [from, to] of copies) {
50359
+ const src = join19(intakeDir, from);
50360
+ if (await fs.exists(src))
50361
+ await fs.writeFile(join19(workDir, to), await fs.readFile(src));
50362
+ }
50363
+ }
50364
+ } catch (err) {
50365
+ echoError(`itc-prepare: cannot write the workspace at ${workDir}: ${err instanceof Error ? err.message : String(err)}`);
50366
+ return 1;
50367
+ }
50368
+ echo(JSON.stringify(options));
50369
+ return 0;
50370
+ }
50371
+
50372
+ // src/commands/stage.ts
50373
+ var ROOT_GLOBALS = new Set(["-v", "--verbose"]);
50374
+ function normalizeStageArgs(argv) {
50375
+ let i = 2;
50376
+ while (i < argv.length && ROOT_GLOBALS.has(argv[i])) {
50377
+ i += 1;
50378
+ }
50379
+ const next = argv[i + 1];
50380
+ if (argv[i] !== "stage" || next === undefined || next === "run" || next === "--help" || next === "-h") {
50381
+ return [...argv];
50382
+ }
50383
+ return [...argv.slice(0, i + 1), "run", ...argv.slice(i + 1)];
50384
+ }
50385
+ function registerStageCommand(program) {
50386
+ program.command("stage").description("Run a kk-workflow-stages.ts stage (workflow orchestration steps)").command("run").description("Run one stage by name with positional pass-through").argument("<args...>", "Stage name followed by its positional arguments").allowUnknownOption(true).action((args) => {
50387
+ if (args[0]?.startsWith("itc-")) {
50388
+ return (async () => {
50389
+ const itcCode = await runItcStage(args);
50390
+ const code = itcCode !== undefined ? itcCode : await runStageCancellable(args);
50391
+ if (code !== 0) {
50392
+ throw new CommanderError(code, "kk.stage.failed", `stage failed with exit code ${code}`);
50393
+ }
50394
+ })();
50395
+ }
50396
+ return (async () => {
50397
+ const code = await runStageCancellable(args);
50398
+ if (code !== 0) {
50399
+ throw new CommanderError(code, "kk.stage.failed", `stage failed with exit code ${code}`);
50400
+ }
50401
+ })();
50402
+ });
50403
+ }
50404
+
50405
+ // src/commands/store.ts
50406
+ import { dirname as dirname12, join as join20, resolve as resolve21 } from "path";
50407
+ init_dist3();
50408
+ init_commander();
50409
+ init_output2();
50410
+ var HEURISTIC_MAKER = "heuristic";
50411
+ var LABEL_PLUGIN = "episode-plan-gen";
50412
+ var LABEL_SCRATCH_DIR = "label";
50413
+ var PROFILE_DIR_REL = "plugins/kk/decisions/extract";
50414
+ var CHECKOUT_ROOT3 = resolve21(import.meta.dir, "../../../..");
50415
+ var CATALOG_PATH_REL = "plugins/kk/decisions/kk-items.yaml";
50416
+ var TOPIC_SLUG = /^[a-z0-9][a-z0-9-]*$/;
50417
+ var WINDOW_UNITS2 = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
50418
+ var DEFAULT_EXTRACT_WINDOW = "24h";
50419
+ var CATEGORY_DECISION_ID = "kk-item-category";
50420
+ var CATEGORY_LABELS = ["ai-news", "ai-technical", "ai-research", "ai-industry", "non-ai"];
50421
+ var SCORE_DIMENSIONS = ["quality", "importance", "urgency", "impact"];
50422
+ var AUDIENCE_LABELS = ["general", "practitioner", "researcher"];
50423
+ var LABEL_DECISIONS = [
50424
+ { id: CATEGORY_DECISION_ID, kind: "choice", labels: CATEGORY_LABELS },
50425
+ { id: "kk-item-quality", kind: "score", levels: 6 },
50426
+ { id: "kk-item-importance", kind: "score", levels: 6 },
50427
+ { id: "kk-item-urgency", kind: "score", levels: 6 },
50428
+ { id: "kk-item-impact", kind: "score", levels: 6 },
50429
+ { id: CHINA_POLITICS_DECISION_ID, kind: "score", levels: 4 },
50430
+ { id: "kk-item-sensitivity", kind: "score", levels: 4 },
50431
+ { id: "kk-item-audience", kind: "choice", labels: AUDIENCE_LABELS }
50432
+ ];
50433
+ var HEURISTIC_DECISION_IDS = [
50434
+ CATEGORY_DECISION_ID,
50435
+ ...SCORE_DIMENSIONS.map((dimension) => `kk-item-${dimension}`)
50436
+ ];
50437
+ var KEYWORD_MAKER = "keyword";
50438
+ var CHINA_POLITICS_BODY_HEAD = 200;
50439
+ function failLoud5(code, error) {
50440
+ const cause = error instanceof Error ? error.message : String(error);
50441
+ const message = `${code}: ${cause}`;
50442
+ consoleOutput.error(message);
50443
+ throw new CommanderError(1, code, message);
50444
+ }
50445
+ async function migrateAction(options) {
50446
+ try {
50447
+ const dbPath = await resolveStorePath(resolveKkConfigPath(), options.db);
50448
+ const store = await openStore({ path: dbPath });
50449
+ const applied = store.appliedMigrations;
50450
+ await store.close();
50451
+ consoleOutput.write(JSON.stringify({ db: dbPath, applied }));
50452
+ } catch (error) {
50453
+ failLoud5("kk.store.migrate.failed", error);
50454
+ }
50455
+ }
50456
+ async function readDocList2(fs, path, label) {
50457
+ if (!await fs.exists(path)) {
50458
+ throw new Error(`input file not found: ${path}`);
50459
+ }
50460
+ return readPluginJson(fs, path, DocListSchema, label, "is not a Doc[]");
50461
+ }
50462
+ async function invalidatedIds(items, docs) {
50463
+ const byId = new Map;
50464
+ for (const doc of docs) {
50465
+ byId.set(itemId(doc), doc);
50466
+ }
50467
+ const invalidated = [];
50468
+ for (const [id, doc] of byId) {
50469
+ const stored = await items.findById(id);
50470
+ if (stored === undefined) {
50471
+ continue;
50472
+ }
50473
+ if (stored.title !== (doc.title ?? "") || stored.body !== doc.body) {
50474
+ invalidated.push(id);
50475
+ }
50476
+ }
50477
+ return invalidated;
50478
+ }
50479
+ function sourceCountsOf2(docs) {
50480
+ const counts = {};
50481
+ for (const doc of docs) {
50482
+ const sources = doc.metadata?.sources;
50483
+ if (!Array.isArray(sources))
50484
+ continue;
50485
+ for (const source of sources) {
50486
+ const file = source?.file;
50487
+ if (typeof file === "string" && file !== "")
50488
+ counts[file] = (counts[file] ?? 0) + 1;
50489
+ }
50490
+ }
50491
+ return counts;
50492
+ }
50493
+ async function writeNewIds(fs, path, ids) {
50494
+ await fs.ensureDir(dirname12(path));
50495
+ await fs.writeFile(path, `${JSON.stringify([...new Set(ids)], null, 2)}
50496
+ `);
50497
+ }
50498
+ async function ingestAction(options) {
50499
+ try {
50500
+ const fs = createNodeFileSystem();
47775
50501
  const docs = await readDocList2(fs, options.in, `Store ingest input '${options.in}'`);
47776
50502
  const dbPath = await resolveStorePath(resolveKkConfigPath(), options.db);
47777
50503
  const story = (await readStoreConfig(resolveKkConfigPath())).story;
@@ -47886,8 +50612,8 @@ async function readAnnotations(fs, outputPath) {
47886
50612
  async function heuristicAnnotations(fs, scratch, runId, items) {
47887
50613
  if (items.length === 0)
47888
50614
  return new Map;
47889
- const inputPath = join15(scratch, "label-input.json");
47890
- const outputPath = join15(scratch, "label-out.json");
50615
+ const inputPath = join20(scratch, "label-input.json");
50616
+ const outputPath = join20(scratch, "label-out.json");
47891
50617
  await fs.ensureDir(scratch);
47892
50618
  const docs = items.map((item) => ({
47893
50619
  id: item.id,
@@ -48056,7 +50782,7 @@ function keywordRow(item, term, runId, catalogVersion) {
48056
50782
  }
48057
50783
  async function labelItem(fs, scratch, item, annotation, mode, maker, runId, catalogVersion, chinaPoliticsTerms) {
48058
50784
  const routed = mode === "decision" || annotation?.needsModel === true;
48059
- const evidencePath = join15(scratch, `evidence-${item.id}.md`);
50785
+ const evidencePath = join20(scratch, `evidence-${item.id}.md`);
48060
50786
  await fs.writeFile(evidencePath, item.body);
48061
50787
  let consults = 0;
48062
50788
  let consultMs = 0;
@@ -48090,7 +50816,7 @@ async function labelItem(fs, scratch, item, annotation, mode, maker, runId, cata
48090
50816
  return { rows, consults, consultMs, refused };
48091
50817
  }
48092
50818
  async function replayItem(fs, scratch, item, decisionIds, maker, runId, catalogVersion) {
48093
- const evidencePath = join15(scratch, `evidence-${item.id}.md`);
50819
+ const evidencePath = join20(scratch, `evidence-${item.id}.md`);
48094
50820
  await fs.writeFile(evidencePath, item.body);
48095
50821
  const rows = [];
48096
50822
  let consults = 0;
@@ -48118,7 +50844,7 @@ function parseReplaySelector(raw) {
48118
50844
  throw new Error(`--replay must be 'topic:<profile>', 'run:<collect-run-id>' or 'since:<n><s|m|h|d>', got '${raw}'`);
48119
50845
  }
48120
50846
  if (kind === "since")
48121
- parseWindow(value);
50847
+ parseWindow2(value);
48122
50848
  return { kind, value };
48123
50849
  }
48124
50850
  function parseDecisions(raw) {
@@ -48137,7 +50863,7 @@ function parseDecisions(raw) {
48137
50863
  return ids;
48138
50864
  }
48139
50865
  async function readCatalogVersion(fs) {
48140
- const candidates = [resolve18(process.cwd(), CATALOG_PATH_REL), resolve18(CHECKOUT_ROOT3, CATALOG_PATH_REL)];
50866
+ const candidates = [resolve21(process.cwd(), CATALOG_PATH_REL), resolve21(CHECKOUT_ROOT3, CATALOG_PATH_REL)];
48141
50867
  for (const path of candidates) {
48142
50868
  try {
48143
50869
  if (!await fs.exists(path))
@@ -48215,13 +50941,13 @@ async function replayItemIds(fs, store, selector, window, policy) {
48215
50941
  if (selector.kind === "run")
48216
50942
  return store.items.idsByRun(selector.value);
48217
50943
  if (selector.kind === "since")
48218
- return store.items.idsSince(Date.now() - parseWindow(selector.value));
50944
+ return store.items.idsSince(Date.now() - parseWindow2(selector.value));
48219
50945
  const profilePath = await resolveProfilePath(fs, selector.value);
48220
50946
  const profile = parseProfile(await fs.readFile(profilePath));
48221
50947
  if (profile.topic !== selector.value) {
48222
50948
  throw new ProfileInvalidError(`profile '${profilePath}' declares topic '${profile.topic}', not '${selector.value}'`);
48223
50949
  }
48224
- const query = compileProfile(profile, { windowMs: parseWindow(window), now: Date.now() });
50950
+ const query = compileProfile(profile, { windowMs: parseWindow2(window), now: Date.now() });
48225
50951
  return (await store.items.selectByProfile(query, policy)).map((doc) => doc.id);
48226
50952
  }
48227
50953
  async function replaySelected(fs, store, selector, decisions, window, maker, catalogVersion, scratch, policy) {
@@ -48265,7 +50991,7 @@ async function labelAction(options) {
48265
50991
  const mode = parseLabelMode(options.mode);
48266
50992
  const configPath = resolveKkConfigPath();
48267
50993
  const dbPath = await resolveStorePath(configPath, options.db);
48268
- const scratch = join15(dirname10(plan.kind === "new-ids" ? plan.newIds : dbPath), LABEL_SCRATCH_DIR);
50994
+ const scratch = join20(dirname12(plan.kind === "new-ids" ? plan.newIds : dbPath), LABEL_SCRATCH_DIR);
48269
50995
  const store = await openStore({ path: dbPath });
48270
50996
  const catalogVersion = await readCatalogVersion(fs);
48271
50997
  const labels = (await readStoreConfig(configPath)).labels;
@@ -48283,14 +51009,14 @@ async function labelAction(options) {
48283
51009
  failLoud5("kk.store.label.failed", error);
48284
51010
  }
48285
51011
  }
48286
- function parseWindow(raw) {
51012
+ function parseWindow2(raw) {
48287
51013
  const match = /^(\d+)([smhd])$/.exec(raw.trim());
48288
51014
  const amount = match?.[1];
48289
51015
  const unit = match?.[2];
48290
51016
  if (amount === undefined || unit === undefined) {
48291
51017
  throw new Error(`--window must be '<n>s|m|h|d' (e.g. 24h), got '${raw}'`);
48292
51018
  }
48293
- const milliseconds = Number(amount) * (WINDOW_UNITS[unit] ?? 0);
51019
+ const milliseconds = Number(amount) * (WINDOW_UNITS2[unit] ?? 0);
48294
51020
  if (milliseconds <= 0) {
48295
51021
  throw new Error(`--window must be greater than zero, got '${raw}'`);
48296
51022
  }
@@ -48308,8 +51034,8 @@ async function resolveProfilePath(fs, topic) {
48308
51034
  throw new Error(`--topic must be a lowercase slug (e.g. ai-news), got '${topic}'`);
48309
51035
  }
48310
51036
  const candidates = [
48311
- resolve18(process.cwd(), PROFILE_DIR_REL, `${topic}.yaml`),
48312
- resolve18(CHECKOUT_ROOT3, PROFILE_DIR_REL, `${topic}.yaml`)
51037
+ resolve21(process.cwd(), PROFILE_DIR_REL, `${topic}.yaml`),
51038
+ resolve21(CHECKOUT_ROOT3, PROFILE_DIR_REL, `${topic}.yaml`)
48313
51039
  ];
48314
51040
  for (const path of candidates) {
48315
51041
  if (await fs.exists(path))
@@ -48317,7 +51043,7 @@ async function resolveProfilePath(fs, topic) {
48317
51043
  }
48318
51044
  throw new Error(`no profile for topic '${topic}': expected one of ${candidates.join(" or ")}`);
48319
51045
  }
48320
- function toPoolDoc(item) {
51046
+ function toPoolDoc2(item) {
48321
51047
  return {
48322
51048
  id: item.id,
48323
51049
  title: item.title,
@@ -48336,7 +51062,7 @@ async function extractAction(options) {
48336
51062
  throw new ProfileInvalidError(`profile '${profilePath}' declares topic '${profile.topic}', not '${options.topic}'`);
48337
51063
  }
48338
51064
  const query = compileProfile(profile, {
48339
- windowMs: parseWindow(options.window),
51065
+ windowMs: parseWindow2(options.window),
48340
51066
  now: Date.now(),
48341
51067
  ...options.limit === undefined ? {} : { limit: parseLimit(options.limit) },
48342
51068
  ...options.includeSelected === true ? {} : { excludeTopic: options.topic }
@@ -48345,8 +51071,8 @@ async function extractAction(options) {
48345
51071
  const store = await openStore({ path: dbPath });
48346
51072
  try {
48347
51073
  const policy = (await readStoreConfig(resolveKkConfigPath())).labels;
48348
- const docs = (await store.items.selectByProfile(query, policy)).map(toPoolDoc);
48349
- await fs.ensureDir(dirname10(options.out));
51074
+ const docs = (await store.items.selectByProfile(query, policy)).map(toPoolDoc2);
51075
+ await fs.ensureDir(dirname12(options.out));
48350
51076
  await fs.writeFile(options.out, `${JSON.stringify(docs, null, 2)}
48351
51077
  `);
48352
51078
  consoleOutput.write(JSON.stringify({ count: docs.length }));