@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/board/collect/index.css +654 -16
- package/dist/board/collect/index.js +191 -64
- package/dist/board/daily-voice/index.css +679 -12
- package/dist/board/daily-voice/index.js +185 -66
- package/dist/board/itc/index.css +679 -13
- package/dist/board/itc/index.js +188 -66
- package/dist/index.js +2878 -152
- package/package.json +3 -2
- package/plugins/generations/content-gen/dist/index.js +158 -12
- package/plugins/generations/core-facts-gen/dist/index.js +158 -12
- package/plugins/generations/daily-article-gen/dist/index.js +158 -12
- package/plugins/generations/dailynews-gen/dist/index.js +180 -12
- package/plugins/generations/dailynews-gen/src/script-plan.ts +32 -0
- package/plugins/generations/episode-plan-gen/dist/index.js +158 -12
- package/plugins/generations/gemini-tts/README.md +4 -3
- package/plugins/generations/gemini-tts/dist/index.js +158 -12
- package/plugins/generations/image-gen/dist/index.js +158 -12
- package/plugins/generations/news-report-gen/dist/index.js +158 -12
- package/plugins/generations/omni-voice-gen/README.md +15 -4
- package/plugins/generations/omni-voice-gen/profiles.json +1 -2
- package/plugins/generations/voice-gen/dist/index.js +158 -12
- package/plugins/generations/voxcpm-tts/README.md +21 -1
- package/plugins/generations/voxcpm-tts/dist/index.js +173 -13
- package/plugins/generations/voxcpm-tts/scripts/audit-speaker-consistency.py +109 -0
- package/plugins/generations/voxcpm-tts/src/voxcpm-client.ts +28 -1
- package/plugins/ingestions/aihot-ingest/dist/index.js +158 -12
- package/plugins/ingestions/horizon-ingest/dist/index.js +158 -12
- package/plugins/ingestions/last30days-ingest/dist/index.js +158 -12
- package/plugins/ingestions/web-search/dist/index.js +158 -12
- package/plugins/kk/commands/workflow-run.md +56 -18
- package/plugins/kk/decisions/kk-itc.yaml +46 -0
- package/plugins/kk/decisions/kk-seo-keyword.yaml +57 -0
- package/plugins/kk/decisions/kk-seo-page.yaml +230 -0
- package/plugins/kk/decisions/kk-seo-pair.yaml +21 -0
- package/plugins/kk/decisions/kk-seo-site.yaml +90 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/publish-presets/chinese-article.json +14 -0
- package/plugins/kk/publish-presets/technical-article.json +14 -0
- package/plugins/kk/references/smart-seo-attribution.md +85 -0
- package/plugins/kk/scripts/daily-stages.ts +42 -2
- package/plugins/kk/scripts/daily-voice-style.zh.md +1 -1
- package/plugins/kk/scripts/itc-stages.ts +25 -118
- package/plugins/kk/scripts/kk-workflow-stages.ts +38 -1
- package/plugins/kk/scripts/seo-decisions.ts +477 -0
- package/plugins/kk/scripts/seo-enrich.ts +728 -0
- package/plugins/kk/scripts/seo-facts.ts +610 -0
- package/plugins/kk/scripts/seo-fetch.ts +766 -0
- package/plugins/kk/scripts/seo-report.ts +460 -0
- package/plugins/kk/scripts/seo-rules.ts +1882 -0
- package/plugins/kk/scripts/seo-stages.ts +1044 -0
- package/plugins/kk/skills/article-adapt/SKILL.md +2 -2
- package/plugins/kk/skills/smart-seo/SKILL.md +183 -0
- package/plugins/kk/skills/smart-seo/references/method.md +126 -0
- package/plugins/kk/skills/smart-seo/references/narrative.md +125 -0
- package/plugins/kk/skills/smart-seo/references/operations.md +202 -0
- package/plugins/kk/skills/smart-seo/references/seo-geo-sources.md +90 -0
- package/plugins/kk/workflows/feature-verification.yaml +28 -24
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +86 -18
- package/plugins/kk/workflows/kk-itc-author.yaml +406 -0
- package/plugins/kk/workflows/kk-itc.yaml +208 -682
- package/plugins/kk/workflows/kk-smart-seo.yaml +251 -0
- package/plugins/publishings/bilibili-pub/dist/index.js +21782 -0
- package/plugins/publishings/bilibili-pub/dist/profile-check.js +20410 -0
- package/plugins/publishings/bilibili-pub/package.json +20 -0
- package/plugins/publishings/bilibili-pub/plugin.json +12 -0
- package/plugins/publishings/bilibili-pub/src/flow.ts +1296 -0
- package/plugins/publishings/bilibili-pub/src/index.ts +139 -0
- package/plugins/publishings/bilibili-pub/src/profile-check.ts +19 -0
- package/plugins/publishings/bilibili-pub/src/selectors.ts +222 -0
- package/plugins/publishings/bilibili-pub/tsconfig.json +4 -0
- package/plugins/publishings/cnblogs-pub/dist/index.js +21383 -0
- package/plugins/publishings/cnblogs-pub/dist/profile-check.js +20412 -0
- package/plugins/publishings/cnblogs-pub/package.json +19 -0
- package/plugins/publishings/cnblogs-pub/plugin.json +12 -0
- package/plugins/publishings/cnblogs-pub/src/flow.ts +708 -0
- package/plugins/publishings/cnblogs-pub/src/index.ts +172 -0
- package/plugins/publishings/cnblogs-pub/src/profile-check.ts +24 -0
- package/plugins/publishings/cnblogs-pub/src/selectors.ts +104 -0
- package/plugins/publishings/cnblogs-pub/tsconfig.json +4 -0
- package/plugins/publishings/csdn-pub/dist/index.js +21552 -0
- package/plugins/publishings/csdn-pub/dist/profile-check.js +20963 -0
- package/plugins/publishings/csdn-pub/package.json +19 -0
- package/plugins/publishings/csdn-pub/plugin.json +12 -0
- package/plugins/publishings/csdn-pub/src/index.ts +1178 -0
- package/plugins/publishings/csdn-pub/src/profile-check.ts +23 -0
- package/plugins/publishings/csdn-pub/tsconfig.json +4 -0
- package/plugins/publishings/emdash-pub/dist/index.js +158 -12
- package/plugins/publishings/emdash-pub/plugin.json +22 -1
- package/plugins/publishings/infoq-pub/dist/index.js +160 -12
- package/plugins/publishings/infoq-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/infoq-pub/plugin.json +21 -0
- package/plugins/publishings/jike-pub/dist/index.js +21379 -0
- package/plugins/publishings/jike-pub/dist/profile-check.js +20951 -0
- package/plugins/publishings/jike-pub/package.json +19 -0
- package/plugins/publishings/jike-pub/plugin.json +12 -0
- package/plugins/publishings/jike-pub/src/index.ts +800 -0
- package/plugins/publishings/jike-pub/src/profile-check.ts +21 -0
- package/plugins/publishings/jike-pub/tsconfig.json +4 -0
- package/plugins/publishings/juejin-pub/dist/index.js +160 -12
- package/plugins/publishings/juejin-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/juejin-pub/plugin.json +21 -0
- package/plugins/publishings/medium-pub/dist/index.js +158 -12
- package/plugins/publishings/medium-pub/plugin.json +22 -1
- package/plugins/publishings/podcast-pub/dist/index.js +158 -12
- package/plugins/publishings/qiita-pub/dist/index.js +158 -12
- package/plugins/publishings/qiita-pub/plugin.json +24 -1
- package/plugins/publishings/shipinhao-pub/dist/index.js +21715 -0
- package/plugins/publishings/shipinhao-pub/dist/profile-check.js +20431 -0
- package/plugins/publishings/shipinhao-pub/package.json +19 -0
- package/plugins/publishings/shipinhao-pub/plugin.json +12 -0
- package/plugins/publishings/shipinhao-pub/src/flow.ts +1323 -0
- package/plugins/publishings/shipinhao-pub/src/index.ts +173 -0
- package/plugins/publishings/shipinhao-pub/src/profile-check.ts +25 -0
- package/plugins/publishings/shipinhao-pub/src/selectors.ts +241 -0
- package/plugins/publishings/shipinhao-pub/tsconfig.json +4 -0
- package/plugins/publishings/sspai-pub/dist/index.js +21371 -0
- package/plugins/publishings/sspai-pub/dist/profile-check.js +20414 -0
- package/plugins/publishings/sspai-pub/package.json +19 -0
- package/plugins/publishings/sspai-pub/plugin.json +12 -0
- package/plugins/publishings/sspai-pub/src/flow.ts +624 -0
- package/plugins/publishings/sspai-pub/src/index.ts +167 -0
- package/plugins/publishings/sspai-pub/src/profile-check.ts +25 -0
- package/plugins/publishings/sspai-pub/src/selectors.ts +122 -0
- package/plugins/publishings/sspai-pub/tsconfig.json +4 -0
- package/plugins/publishings/substack-pub/dist/index.js +160 -12
- package/plugins/publishings/substack-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/substack-pub/plugin.json +21 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +158 -12
- package/plugins/publishings/surfdash-pub/plugin.json +22 -1
- package/plugins/publishings/threads-pub/dist/index.js +158 -12
- package/plugins/publishings/wechatmp-pub/dist/index.js +1669 -502
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +204 -21
- package/plugins/publishings/wechatmp-pub/plugin.json +32 -0
- package/plugins/publishings/wechatmp-pub/src/cgi-draft.ts +540 -0
- package/plugins/publishings/wechatmp-pub/src/cgi-media.ts +380 -0
- package/plugins/publishings/wechatmp-pub/src/cgi.ts +485 -0
- package/plugins/publishings/wechatmp-pub/src/flow.ts +104 -0
- package/plugins/publishings/weibo-pub/dist/index.js +160 -12
- package/plugins/publishings/weibo-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/weibo-pub/plugin.json +35 -0
- package/plugins/publishings/x-pub/dist/index.js +160 -12
- package/plugins/publishings/x-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/x-pub/plugin.json +40 -0
- package/plugins/publishings/xhs-pub/dist/index.js +160 -12
- package/plugins/publishings/xhs-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/xhs-pub/plugin.json +37 -0
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +21491 -0
- package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +20417 -0
- package/plugins/publishings/xiaoyuzhou-pub/package.json +19 -0
- package/plugins/publishings/xiaoyuzhou-pub/plugin.json +12 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/flow.ts +826 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/index.ts +174 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/profile-check.ts +26 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/selectors.ts +125 -0
- package/plugins/publishings/xiaoyuzhou-pub/tsconfig.json +4 -0
- package/plugins/publishings/zenn-pub/dist/index.js +158 -12
- package/plugins/publishings/zenn-pub/plugin.json +22 -1
- 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
|
|
41582
|
-
|
|
41583
|
-
|
|
41584
|
-
|
|
41585
|
-
|
|
41586
|
-
|
|
41587
|
-
|
|
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.
|
|
41929
|
+
if (manifest.publishing !== undefined && manifest.kind !== "publish") {
|
|
41591
41930
|
ctx.addIssue({
|
|
41592
41931
|
code: "custom",
|
|
41593
|
-
path: ["
|
|
41594
|
-
message:
|
|
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
|
-
|
|
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`
|
|
47326
|
-
withFaninOptions(program.command("fanin", { hidden: true }).description("Alias for `executor fan-in`
|
|
47327
|
-
withFanoutOptions(program.command("fanout", { hidden: true }).description("Alias for `executor fan-out`
|
|
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
|
|
47385
|
-
|
|
47386
|
-
lock.command("
|
|
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`
|
|
47594
|
-
registerStatusVerb(plugin, "doctor", "Alias for `plugin status`
|
|
47595
|
-
const plugins = program.command("plugins", { hidden: true }).description("Alias for `plugin status`
|
|
47596
|
-
registerStatusVerb(plugins, "status", "Alias for `plugin status`
|
|
47597
|
-
registerStatusVerb(plugins, "list", "Alias for `plugin status`
|
|
47598
|
-
registerStatusVerb(plugins, "doctor", "Alias for `plugin status`
|
|
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
|
-
|
|
47659
|
-
|
|
47660
|
-
|
|
47661
|
-
|
|
47662
|
-
|
|
47663
|
-
|
|
47664
|
-
|
|
47665
|
-
|
|
47666
|
-
|
|
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
|
-
|
|
47680
|
-
|
|
47681
|
-
|
|
47682
|
-
|
|
47683
|
-
|
|
47684
|
-
|
|
47685
|
-
var
|
|
47686
|
-
|
|
47687
|
-
|
|
47688
|
-
|
|
47689
|
-
|
|
47690
|
-
|
|
47691
|
-
|
|
47692
|
-
|
|
47693
|
-
|
|
47694
|
-
|
|
47695
|
-
|
|
47696
|
-
|
|
47697
|
-
|
|
47698
|
-
|
|
47699
|
-
|
|
47700
|
-
|
|
47701
|
-
|
|
47702
|
-
|
|
47703
|
-
|
|
47704
|
-
|
|
47705
|
-
|
|
47706
|
-
|
|
47707
|
-
|
|
47708
|
-
|
|
47709
|
-
|
|
47710
|
-
|
|
47711
|
-
|
|
47712
|
-
|
|
47713
|
-
|
|
47714
|
-
|
|
47715
|
-
|
|
47716
|
-
|
|
47717
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47722
|
-
|
|
47723
|
-
|
|
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
|
-
|
|
47731
|
-
|
|
47732
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47737
|
-
const
|
|
47738
|
-
|
|
47739
|
-
|
|
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
|
|
47742
|
-
|
|
47743
|
-
const
|
|
47744
|
-
if (
|
|
47745
|
-
|
|
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
|
-
|
|
47748
|
-
|
|
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
|
-
|
|
47752
|
-
|
|
47753
|
-
|
|
47754
|
-
|
|
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
|
-
|
|
47766
|
-
|
|
47767
|
-
|
|
47768
|
-
|
|
47769
|
-
|
|
47770
|
-
|
|
47771
|
-
|
|
47772
|
-
|
|
47773
|
-
|
|
47774
|
-
|
|
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 =
|
|
47890
|
-
const outputPath =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 = [
|
|
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() -
|
|
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:
|
|
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 =
|
|
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
|
|
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) * (
|
|
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
|
-
|
|
48312
|
-
|
|
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
|
|
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:
|
|
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(
|
|
48349
|
-
await fs.ensureDir(
|
|
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 }));
|