@dadado/agent-kit-cli 5.8.0 → 5.10.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/README.md +2 -1
- package/dist/index.js +3199 -423
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -273,7 +273,74 @@ var KNOWN_SHIPPED_OVERLAY_HASHES = /* @__PURE__ */ new Set([
|
|
|
273
273
|
"3a88df22375f90a3a6743f50d9a01129e0373f4ca8dff03fb744c1de3c149409",
|
|
274
274
|
"481a9994ccecaa611173f9ef91a1dbe7458d89758c6a6b5e7c3bbbbf460888ce",
|
|
275
275
|
"691d91fd1e9032b6f9315f29348d4473201917da257258afd74eb954298d8d71",
|
|
276
|
-
"ab0808b7dbc333e523b87b126bb655353cde2574db30d849cc9c9037843bef8c"
|
|
276
|
+
"ab0808b7dbc333e523b87b126bb655353cde2574db30d849cc9c9037843bef8c",
|
|
277
|
+
"85514e26af694bbe38e7ddf625710a32719ddf6afc173843c9cdfbffdad8e466",
|
|
278
|
+
"35b6c0cde7538fb1837d54ba841668e82ac7b9e3ef274adcea3d7f34ba4bfb64",
|
|
279
|
+
"4230e45f2ccd89cf8e25c56d21dfbfbe134613c686ea361243f94c49cf6802af",
|
|
280
|
+
"b4fbb2c15a1330ba36351a90490696fd558a347d25ac14af768017d6155335c0",
|
|
281
|
+
"c37ee51662b6eca65224d737911a4bb209a3742694b37a4d00ed29abc4e0b253",
|
|
282
|
+
"f46a0e36c1ee887ad7e62fa00a8facefe90605827de7bf2e6f90655d6660ed0e",
|
|
283
|
+
"40edd2d21be802af95501cf3492665ee48423439cfc64c14f17ed1e7eb8bdfdf",
|
|
284
|
+
"08c15346f42360bd7a4b4e803ca88e5f1727d9db106e7dc4d96523ee7aba129a",
|
|
285
|
+
"0cc36b633ca1a18fa472ed8040a5b927b4c918318dbd17f6163b706cd7a8acbe",
|
|
286
|
+
"0fdc92828174abba815db3799a274aca70cccacca573d949e412d4622928ec50",
|
|
287
|
+
"33fc7d6bc49cea32bd737e46aaf63ad2bedbc72a52615fc2b881e6ddbb34587a",
|
|
288
|
+
"45c475337798bdbf5bd3a42c1f085278d1563a9e76c1295961b1ce66a89b4820",
|
|
289
|
+
"484df32ebf132fb121ab1e31b0e64345058c9c82b2a39498cc06d1794c270f23",
|
|
290
|
+
"60aa86ca3f2873908e57c2830675eec393c114cd711b3b162d02be355792b697",
|
|
291
|
+
"85c307d01e36dc62d4a341c1932b0a7fae3663f28ec0e32ad5cda30f35ffca86",
|
|
292
|
+
"9133d73c070243947b3bf9ffec78d21d66a5cb7488c11a4bf731b3c9c3529b3c",
|
|
293
|
+
"c082e903d9569b297c93a41ea561718a5258dfefd6a0f0457402f565caa1bbd0",
|
|
294
|
+
"c10bcbc952e58e8f89818c5119cde7b54bab5b74c5b194b5d26b2b125ec1036a",
|
|
295
|
+
"ed68d0fe3dca46c159402a1ae62148d2482352718be792ff65fd38efae706bef",
|
|
296
|
+
"8356eda7f3597c3d3726b32f87f6f4da87999be93760e738748138acc2a3d73e",
|
|
297
|
+
"90a8e3a67e41c33b93da824290148e4af84d3d49848727fa88b34d8df9bad29a",
|
|
298
|
+
"ec51cebd225fc861072b62e4ee0292fd4ba01ba0817770e7a379ad41d7bd850a",
|
|
299
|
+
"3edb378d29c89081c9ad6439b077aa928a21aed864aed5ef02e819b75ac33d7a",
|
|
300
|
+
"634f1d9ac6f632074054c5b029567303d783f1fc01e62be588668d20044fadcc",
|
|
301
|
+
"bc5c76a5b63fad600c94786300284987f54d237e3f7d38bb8b520906a9685ba2",
|
|
302
|
+
"efcbbf40f1e616e2e7ae513765463806b192bd5e6b8acd3be832bf590c14a542",
|
|
303
|
+
"2ef2688b31b86a4bbf358f3662495a65314b2edf2b7b2215d71e5be9cff90d22",
|
|
304
|
+
"a9cad5b77ae12cfa907a4e460d39be75c8f864a27e7f85394bef67120a6fba45",
|
|
305
|
+
"c35c76052f76cb45a970fcb825d5ddc49f7e371441f0953735ffb9d072da2680",
|
|
306
|
+
"611279d799db865e5329ed8723d73ebd2d839c273b52aef12803b4b11a75a75a",
|
|
307
|
+
"28840c700bdf8968b6f02c6bbb9cf965643f30f0911b2622a9c01dfae233d8fb",
|
|
308
|
+
"947f39141a3f830cd29145a74058d48c2feb8fbcd958ea7f90522e46fb0c25c5",
|
|
309
|
+
"c8535a73997daf1800394626d0e4845c923544040bae85a724ca427f56ba9812",
|
|
310
|
+
"9486e4ae754782114b1ec7d22baf661a8f33413d01fcf3a0759f97f1eedd84c1",
|
|
311
|
+
"8453f7d9d818b101d0d7b0cf5d0985e57174285f4cb7899517d5af52bc6490d5",
|
|
312
|
+
"9a877468512ffa14e77940757b3532c20f6fda7878419c9986f6d9e03ca31081",
|
|
313
|
+
"db310ca8f9841b5937f475ab042d768f84bc00a29282fbcce159fc9bf7646921",
|
|
314
|
+
"052217cbd533ef7214bb55f9e7a7d8cb979f0a4b32e42f8b98b57ebd86935d25",
|
|
315
|
+
"0aab08e34dd433fd38fa3f4d6729d1a7a2f626387fdb2b9bc8a48bd0f84be2f3",
|
|
316
|
+
"4bdbc2837b195c529d83302bd3c830d7d669879757ac3f92ec1d1ac703b21522",
|
|
317
|
+
"744c0d44e26b8a8fb7272b07eff884da583a8b73a50f7a1e4a9be55ecb40b39e",
|
|
318
|
+
"7b791a2ec4ff31bb6f19d60bc19152fb1ee5139b56254b9a3ba783a7f1bf9016",
|
|
319
|
+
"7e5a4a5f88ee48f17fe40e66772da4816480b1fba5fff5a58026d05753614393",
|
|
320
|
+
"c0b51e50d6117ce597c096386a1928f803e3abf1a65f6948ff7b00583651db5e",
|
|
321
|
+
"d1c8ea35ae9bdb5dad47c3ab80d73d5a327a0379216bead38eda18c0f2836fe9",
|
|
322
|
+
"e299dd585ed6c37292758d2821663aa46a80d6c7964f67b3d1d3279cf746e764",
|
|
323
|
+
"51a5608322e14694fafad15cbb710663800dc315d4deb12516c1591ac94e3ed7",
|
|
324
|
+
"78d553f98cc172dd6f0f3b2e01056813a32a894f6bdca64e665265df84a8eb0b",
|
|
325
|
+
"90c466fbe5bf7e6f3936b8a46407fbd5d0d542582c39b697ecbb5ada1be482ce",
|
|
326
|
+
"d2f539aa6340b1e41f6f47d179a2c17666415b82e7bf02be9416061855b5534c",
|
|
327
|
+
"05034e22b01b830b1fe12df4834e6c57eb0b152539e469d8d4e393b2b61dd76d",
|
|
328
|
+
"3268c3549d9634aefe29e399961a3be0ebb6e205d454609411ea877da4552d51",
|
|
329
|
+
"5c583058ae2d35e9a650a7a0d2ee304be8e71e361b51ea6903b2cd6ac14fd6a1",
|
|
330
|
+
"7c3cb1f7f449b6a459532d1dc315d574b7148f0d373f3e3271a52ad0151e0eb4",
|
|
331
|
+
"1163b51a64118363c90db5f0b8f7280fcea335631768f83e64fdf1948b41df89",
|
|
332
|
+
"a1590ecae718e7c43f356ddfc76c983f499725970a1953bf6d4d2d4ebf8fa1dc",
|
|
333
|
+
"2b69c26c0dacd5596906178138f85b2b14076552e1995817ea64bf99f48e0ed2",
|
|
334
|
+
"6e3e62f4d47d66cc55f659cf37b7c1ff4f9b43581b9470e740d60768217c1a23",
|
|
335
|
+
"60a3ef8eddf0f262fce40a32b715ee4c2e68d52e18f74d790e68a0f54c8657f1",
|
|
336
|
+
"dfd40e3c2c62a2810030e21a78f71f2646eab19b3658e326ecb5cd8def258aad",
|
|
337
|
+
"ee5572cfba7b96285c433ea2f6e469a205c783fba216af6cf4aafefdbf33ff89",
|
|
338
|
+
"85cd5f00c51efcc263b45fe91d1e10278a866d9266392e0ed480cd8ef5da6cc1",
|
|
339
|
+
"8ea5a65412bf4b95fdbb57fcce7c1a046848f161cf8235a3d9b7e752068775a8",
|
|
340
|
+
"974b143673aa6bd202205264d82f30898777e5694d4269449f564fd536f402d7",
|
|
341
|
+
"2317949f51d8a9d31b700c3af60b6fd3bccff1a8520af7707d7909f42bc5cff2",
|
|
342
|
+
"18426bc3bc8fdf0ab7577d92e31310faf3b83001f8e6e6fe5ef1a1043230e178",
|
|
343
|
+
"24cc918e7ffa54df7f6007989fe62b83af1035c9bf6071cac8c4f848e28b04ca"
|
|
277
344
|
]);
|
|
278
345
|
|
|
279
346
|
// src/lifecycle/paths.ts
|
|
@@ -942,6 +1009,9 @@ function loadPackageVersion() {
|
|
|
942
1009
|
}
|
|
943
1010
|
var KIT_VERSION = loadPackageVersion();
|
|
944
1011
|
var KIT_PACKAGE_SPEC = "@dadado/agent-kit-cli";
|
|
1012
|
+
function pinnedCliSpec(version = KIT_VERSION) {
|
|
1013
|
+
return `${KIT_PACKAGE_SPEC}@${version}`;
|
|
1014
|
+
}
|
|
945
1015
|
|
|
946
1016
|
// src/manifest/index.ts
|
|
947
1017
|
import path6 from "path";
|
|
@@ -1517,6 +1587,54 @@ var L0_ARTIFACTS = [
|
|
|
1517
1587
|
source: ".cursor/rules/hitl-ask-questions.mdc",
|
|
1518
1588
|
target: ".cursor/rules/hitl-ask-questions.mdc"
|
|
1519
1589
|
},
|
|
1590
|
+
{
|
|
1591
|
+
source: ".cursor/skills/core/hitl-gates/SKILL.md",
|
|
1592
|
+
target: ".cursor/skills/core/hitl-gates/SKILL.md"
|
|
1593
|
+
},
|
|
1594
|
+
{
|
|
1595
|
+
source: ".cursor/skills/core/hitl-gates/procedures.md",
|
|
1596
|
+
target: ".cursor/skills/core/hitl-gates/procedures.md"
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
source: ".cursor/skills/core/hitl-gates/run-plan-tick-contract.md",
|
|
1600
|
+
target: ".cursor/skills/core/hitl-gates/run-plan-tick-contract.md"
|
|
1601
|
+
},
|
|
1602
|
+
{
|
|
1603
|
+
source: ".cursor/skills/core/hitl-gates/start-project-intake.md",
|
|
1604
|
+
target: ".cursor/skills/core/hitl-gates/start-project-intake.md"
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
source: ".cursor/skills/core/hitl-gates/run-plan-all-queue.md",
|
|
1608
|
+
target: ".cursor/skills/core/hitl-gates/run-plan-all-queue.md"
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
source: ".cursor/skills/core/qa/SKILL.md",
|
|
1612
|
+
target: ".cursor/skills/core/qa/SKILL.md"
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
source: ".cursor/skills/core/dashboard-broadcast/SKILL.md",
|
|
1616
|
+
target: ".cursor/skills/core/dashboard-broadcast/SKILL.md"
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
source: ".cursor/skills/core/plan-review-triage/procedure.md",
|
|
1620
|
+
target: ".cursor/skills/core/plan-review-triage/procedure.md"
|
|
1621
|
+
},
|
|
1622
|
+
{
|
|
1623
|
+
source: ".cursor/skills/core/plan-external-review/procedure.md",
|
|
1624
|
+
target: ".cursor/skills/core/plan-external-review/procedure.md"
|
|
1625
|
+
},
|
|
1626
|
+
{
|
|
1627
|
+
source: ".cursor/skills/core/agent-kit-onboard/procedure.md",
|
|
1628
|
+
target: ".cursor/skills/core/agent-kit-onboard/procedure.md"
|
|
1629
|
+
},
|
|
1630
|
+
{
|
|
1631
|
+
source: ".cursor/skills/core/field-report-resolve/procedure.md",
|
|
1632
|
+
target: ".cursor/skills/core/field-report-resolve/procedure.md"
|
|
1633
|
+
},
|
|
1634
|
+
{
|
|
1635
|
+
source: ".cursor/skills/core/backlog-add/procedure.md",
|
|
1636
|
+
target: ".cursor/skills/core/backlog-add/procedure.md"
|
|
1637
|
+
},
|
|
1520
1638
|
{
|
|
1521
1639
|
source: "registry/rules/git-secrets-safety.mdc",
|
|
1522
1640
|
target: ".cursor/rules/git-secrets-safety.mdc"
|
|
@@ -1586,11 +1704,8 @@ var L0_ARTIFACTS = [
|
|
|
1586
1704
|
target: ".cursor/commands/git-staging.md"
|
|
1587
1705
|
},
|
|
1588
1706
|
{ source: ".cursor/commands/git-prod.md", target: ".cursor/commands/git-prod.md" },
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
target: ".cursor/commands/kit-staging.md"
|
|
1592
|
-
},
|
|
1593
|
-
{ source: ".cursor/commands/kit-prod.md", target: ".cursor/commands/kit-prod.md" },
|
|
1707
|
+
// Factory landing wraps: keep `.cursor/commands/kit-staging.md` and
|
|
1708
|
+
// `kit-prod.md` on disk. They are not consumer L0 (surface diet).
|
|
1594
1709
|
{
|
|
1595
1710
|
source: ".cursor/commands/plan-external-review.md",
|
|
1596
1711
|
target: ".cursor/commands/plan-external-review.md"
|
|
@@ -1614,6 +1729,10 @@ var L0_ARTIFACTS = [
|
|
|
1614
1729
|
source: ".cursor/commands/cursor-update-awareness.md",
|
|
1615
1730
|
target: ".cursor/commands/cursor-update-awareness.md"
|
|
1616
1731
|
},
|
|
1732
|
+
{
|
|
1733
|
+
source: ".cursor/commands/qa.md",
|
|
1734
|
+
target: ".cursor/commands/qa.md"
|
|
1735
|
+
},
|
|
1617
1736
|
{
|
|
1618
1737
|
source: ".cursor/commands/update.md",
|
|
1619
1738
|
target: ".cursor/commands/update.md"
|
|
@@ -2164,6 +2283,94 @@ function parseFeatureMapNames(markdown) {
|
|
|
2164
2283
|
}
|
|
2165
2284
|
return names;
|
|
2166
2285
|
}
|
|
2286
|
+
var HEADING_NOISE = /* @__PURE__ */ new Set([
|
|
2287
|
+
"changelog",
|
|
2288
|
+
"improvements",
|
|
2289
|
+
"bug fixes",
|
|
2290
|
+
"get started",
|
|
2291
|
+
"settings",
|
|
2292
|
+
"powered by",
|
|
2293
|
+
"powered by cloud agents",
|
|
2294
|
+
"shared context",
|
|
2295
|
+
"desktop improvements",
|
|
2296
|
+
"desktop bug fixes",
|
|
2297
|
+
"agents window improvements",
|
|
2298
|
+
"agents window bug fixes",
|
|
2299
|
+
"what's new in cursor",
|
|
2300
|
+
"latest updates",
|
|
2301
|
+
"release notes"
|
|
2302
|
+
]);
|
|
2303
|
+
var MAX_FEATURE_KEYWORD_GAPS = 12;
|
|
2304
|
+
function visibleTextFromHtml(html) {
|
|
2305
|
+
return html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<svg[\s\S]*?<\/svg>/gi, " ").replace(/<[^>]+>/g, " ").replace(/ /gi, " ").replace(/&/gi, "&").replace(/&#?\w+;/g, " ").replace(/\s+/g, " ").trim();
|
|
2306
|
+
}
|
|
2307
|
+
function normalizeFeatureKeyword(raw) {
|
|
2308
|
+
let value = raw.replace(/^#+\s*/, "").replace(/\s+/g, " ").trim();
|
|
2309
|
+
value = value.replace(/[.,:;]+$/g, "");
|
|
2310
|
+
value = value.replace(/\s+(improvements|bug fixes)$/i, "").trim();
|
|
2311
|
+
if (!value) return null;
|
|
2312
|
+
if (value.startsWith("/")) {
|
|
2313
|
+
const cmd = value.match(/^\/[a-z][a-z0-9-]{1,32}$/i);
|
|
2314
|
+
return cmd?.[0] ? cmd[0].toLowerCase() : null;
|
|
2315
|
+
}
|
|
2316
|
+
const lower = value.toLowerCase();
|
|
2317
|
+
if (HEADING_NOISE.has(lower)) return null;
|
|
2318
|
+
if (/^\d+(\.\d+){1,2}$/.test(value)) return null;
|
|
2319
|
+
if (value.length < 4 || value.length > 80) return null;
|
|
2320
|
+
return value;
|
|
2321
|
+
}
|
|
2322
|
+
function slugifyFeatureKeyword(name) {
|
|
2323
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
2324
|
+
}
|
|
2325
|
+
function extractChangelogFeatureKeywords(changelogText) {
|
|
2326
|
+
const text = String(changelogText ?? "");
|
|
2327
|
+
const buckets = {
|
|
2328
|
+
slash: [],
|
|
2329
|
+
h1: [],
|
|
2330
|
+
h2: [],
|
|
2331
|
+
h3: []
|
|
2332
|
+
};
|
|
2333
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2334
|
+
const push = (raw, bucket) => {
|
|
2335
|
+
const keyword = normalizeFeatureKeyword(raw);
|
|
2336
|
+
if (!keyword) return;
|
|
2337
|
+
const key = keyword.toLowerCase();
|
|
2338
|
+
if (seen.has(key)) return;
|
|
2339
|
+
seen.add(key);
|
|
2340
|
+
buckets[bucket].push(keyword);
|
|
2341
|
+
};
|
|
2342
|
+
for (const match of text.matchAll(/<h1[^>]*>([\s\S]*?)<\/h1>/gi)) {
|
|
2343
|
+
push(visibleTextFromHtml(match[1] ?? ""), "h1");
|
|
2344
|
+
}
|
|
2345
|
+
for (const match of text.matchAll(/<h2[^>]*>([\s\S]*?)<\/h2>/gi)) {
|
|
2346
|
+
push(visibleTextFromHtml(match[1] ?? ""), "h2");
|
|
2347
|
+
}
|
|
2348
|
+
for (const match of text.matchAll(/<h3[^>]*>([\s\S]*?)<\/h3>/gi)) {
|
|
2349
|
+
push(visibleTextFromHtml(match[1] ?? ""), "h3");
|
|
2350
|
+
}
|
|
2351
|
+
for (const match of text.matchAll(/^#\s+(.+)$/gm)) {
|
|
2352
|
+
push(match[1] ?? "", "h1");
|
|
2353
|
+
}
|
|
2354
|
+
for (const match of text.matchAll(/^##\s+(.+)$/gm)) {
|
|
2355
|
+
push(match[1] ?? "", "h2");
|
|
2356
|
+
}
|
|
2357
|
+
for (const match of text.matchAll(/^###\s+(.+)$/gm)) {
|
|
2358
|
+
push(match[1] ?? "", "h3");
|
|
2359
|
+
}
|
|
2360
|
+
for (const match of text.matchAll(/(^|[\s>`(])(\/[a-z][a-z0-9-]{1,32})(?![/\w-])/gi)) {
|
|
2361
|
+
push(match[2] ?? "", "slash");
|
|
2362
|
+
}
|
|
2363
|
+
return [...buckets.slash, ...buckets.h1, ...buckets.h2, ...buckets.h3];
|
|
2364
|
+
}
|
|
2365
|
+
function findMissingInventoryKeywords(keywords, haystack) {
|
|
2366
|
+
const lower = String(haystack ?? "").toLowerCase();
|
|
2367
|
+
return keywords.filter((keyword) => {
|
|
2368
|
+
const needle = keyword.toLowerCase();
|
|
2369
|
+
if (lower.includes(needle)) return false;
|
|
2370
|
+
if (needle.startsWith("/") && lower.includes(needle.slice(1))) return false;
|
|
2371
|
+
return true;
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2167
2374
|
async function defaultFetchText(url) {
|
|
2168
2375
|
if (!url.startsWith("https://")) {
|
|
2169
2376
|
throw new Error(`Refusing non-HTTPS changelog URL: ${url}`);
|
|
@@ -2262,6 +2469,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2262
2469
|
const inventoryRefreshed = parseInventoryRefreshed(inventoryMd);
|
|
2263
2470
|
const openActionIds = parseOpenActionIds(inventoryMd);
|
|
2264
2471
|
const gaps = [];
|
|
2472
|
+
let featuresMd = "";
|
|
2265
2473
|
for (const id of openActionIds) {
|
|
2266
2474
|
gaps.push({
|
|
2267
2475
|
id: `open-action-${id}`,
|
|
@@ -2287,7 +2495,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2287
2495
|
}
|
|
2288
2496
|
}
|
|
2289
2497
|
try {
|
|
2290
|
-
|
|
2498
|
+
featuresMd = await readFile7(featuresPath, "utf8");
|
|
2291
2499
|
if (parseFeatureMapNames(featuresMd).length === 0) {
|
|
2292
2500
|
gaps.push({
|
|
2293
2501
|
id: "features-map-empty",
|
|
@@ -2311,6 +2519,23 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2311
2519
|
try {
|
|
2312
2520
|
const body = options.changelogBody ?? await (options.fetchText ?? defaultFetchText)(changelogUrl);
|
|
2313
2521
|
latestCursorVersion = extractLatestCursorVersion(body);
|
|
2522
|
+
const haystack = `${inventoryMd}
|
|
2523
|
+
${featuresMd}`;
|
|
2524
|
+
const missingKeywords = findMissingInventoryKeywords(
|
|
2525
|
+
extractChangelogFeatureKeywords(body),
|
|
2526
|
+
haystack
|
|
2527
|
+
);
|
|
2528
|
+
for (const keyword of missingKeywords.slice(0, MAX_FEATURE_KEYWORD_GAPS)) {
|
|
2529
|
+
const slug = slugifyFeatureKeyword(keyword);
|
|
2530
|
+
if (!slug) continue;
|
|
2531
|
+
gaps.push({
|
|
2532
|
+
id: `feature-keyword-miss:${slug}`,
|
|
2533
|
+
severity: "advisory",
|
|
2534
|
+
path: FEATURES_REL,
|
|
2535
|
+
evidence: `Changelog names "${keyword}" which is not listed in native-audit or cursor-3-features`,
|
|
2536
|
+
suggestedRoute: "backlog-add"
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2314
2539
|
if (latestCursorVersion && prefs.lastSeenCursorVersion && compareCursorVersion(latestCursorVersion, prefs.lastSeenCursorVersion) > 0) {
|
|
2315
2540
|
gaps.push({
|
|
2316
2541
|
id: "changelog-ahead",
|
|
@@ -2530,13 +2755,13 @@ function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
|
|
|
2530
2755
|
}
|
|
2531
2756
|
|
|
2532
2757
|
// src/commands/dashboard.ts
|
|
2533
|
-
function readPreferredBrowserFromWorkspace(cwd,
|
|
2758
|
+
function readPreferredBrowserFromWorkspace(cwd, readFile31 = readFileSync2) {
|
|
2534
2759
|
const resolved = resolveContextConfigPath(path11.resolve(cwd), {
|
|
2535
2760
|
existsSync,
|
|
2536
2761
|
realpathSync
|
|
2537
2762
|
});
|
|
2538
2763
|
if (!resolved.ok) return null;
|
|
2539
|
-
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync:
|
|
2764
|
+
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync: readFile31 });
|
|
2540
2765
|
return normalizePreferredBrowser(value);
|
|
2541
2766
|
}
|
|
2542
2767
|
function applyDashboardOpenEnv(env, opts) {
|
|
@@ -3659,21 +3884,81 @@ import { constants as constants3, access as access6, readFile as readFile10, sta
|
|
|
3659
3884
|
import { homedir as homedir2 } from "os";
|
|
3660
3885
|
import path16 from "path";
|
|
3661
3886
|
var MIN_NODE_MAJOR = 20;
|
|
3662
|
-
|
|
3887
|
+
var CLI_PACKAGE_JSON_REL = path16.join(
|
|
3888
|
+
"lib",
|
|
3889
|
+
"node_modules",
|
|
3890
|
+
"@dadado",
|
|
3891
|
+
"agent-kit-cli",
|
|
3892
|
+
"package.json"
|
|
3893
|
+
);
|
|
3894
|
+
var DIST_INDEX_RE = /(?:\$\{basedir\}|\$basedir)?[^'"\s;]*dist\/index\.js/g;
|
|
3895
|
+
function extractDistIndexFromBinScript(contents) {
|
|
3896
|
+
const found = [];
|
|
3897
|
+
DIST_INDEX_RE.lastIndex = 0;
|
|
3898
|
+
for (const match of contents.matchAll(DIST_INDEX_RE)) {
|
|
3899
|
+
const raw = match[0]?.trim();
|
|
3900
|
+
if (raw) found.push(raw.replace(/\\/g, "/"));
|
|
3901
|
+
}
|
|
3902
|
+
return found;
|
|
3903
|
+
}
|
|
3904
|
+
function expandBinDirVars(raw, binDir) {
|
|
3905
|
+
return raw.replace(/\$\{basedir\}/g, binDir).replace(/\$basedir/g, binDir);
|
|
3906
|
+
}
|
|
3907
|
+
function packageJsonFromDistIndex(distIndex) {
|
|
3908
|
+
return path16.join(path16.dirname(distIndex), "..", "package.json");
|
|
3909
|
+
}
|
|
3910
|
+
function parsePackageVersion(contents) {
|
|
3911
|
+
try {
|
|
3912
|
+
const parsed = JSON.parse(contents);
|
|
3913
|
+
return typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : null;
|
|
3914
|
+
} catch {
|
|
3915
|
+
return null;
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
async function readBinVersion(binPath, options = {}) {
|
|
3919
|
+
const readFileImpl = options.readFileImpl ?? ((filePath) => readFile10(filePath, "utf8"));
|
|
3920
|
+
const binDir = path16.dirname(binPath);
|
|
3921
|
+
const candidates = [
|
|
3922
|
+
path16.join(binDir, "..", CLI_PACKAGE_JSON_REL),
|
|
3923
|
+
path16.join(binDir, "..", "node_modules", "@dadado", "agent-kit-cli", "package.json")
|
|
3924
|
+
];
|
|
3925
|
+
try {
|
|
3926
|
+
const contents = await readFileImpl(binPath);
|
|
3927
|
+
for (const raw of extractDistIndexFromBinScript(contents)) {
|
|
3928
|
+
const distIndex = path16.resolve(binDir, expandBinDirVars(raw, binDir));
|
|
3929
|
+
candidates.push(packageJsonFromDistIndex(distIndex));
|
|
3930
|
+
}
|
|
3931
|
+
} catch {
|
|
3932
|
+
}
|
|
3933
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3934
|
+
for (const candidate2 of candidates) {
|
|
3935
|
+
const resolved = path16.resolve(candidate2);
|
|
3936
|
+
if (seen.has(resolved)) continue;
|
|
3937
|
+
seen.add(resolved);
|
|
3938
|
+
try {
|
|
3939
|
+
const version = parsePackageVersion(await readFileImpl(resolved));
|
|
3940
|
+
if (version) return version;
|
|
3941
|
+
} catch {
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
return null;
|
|
3945
|
+
}
|
|
3946
|
+
async function resolveBinOnPath(binName, env, platform) {
|
|
3663
3947
|
const pathVar = env.PATH ?? env.Path ?? "";
|
|
3664
|
-
if (!pathVar) return
|
|
3948
|
+
if (!pathVar) return null;
|
|
3665
3949
|
const dirs = pathVar.split(path16.delimiter).filter(Boolean);
|
|
3666
3950
|
const candidates = platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.exe`, `${binName}.bat`] : [binName];
|
|
3667
3951
|
for (const dir of dirs) {
|
|
3668
3952
|
for (const candidate2 of candidates) {
|
|
3953
|
+
const abs = path16.join(dir, candidate2);
|
|
3669
3954
|
try {
|
|
3670
|
-
await access6(
|
|
3671
|
-
return
|
|
3955
|
+
await access6(abs, platform === "win32" ? void 0 : constants3.X_OK);
|
|
3956
|
+
return abs;
|
|
3672
3957
|
} catch {
|
|
3673
3958
|
}
|
|
3674
3959
|
}
|
|
3675
3960
|
}
|
|
3676
|
-
return
|
|
3961
|
+
return null;
|
|
3677
3962
|
}
|
|
3678
3963
|
function isNodeVersionOk(nodeVersion, minMajor = MIN_NODE_MAJOR) {
|
|
3679
3964
|
const match = /^v?(\d+)/.exec(nodeVersion);
|
|
@@ -3769,8 +4054,8 @@ async function assessEnvironment(options = {}) {
|
|
|
3769
4054
|
const nodeVersion = options.nodeVersion ?? process.version;
|
|
3770
4055
|
const homeDir = options.homeDir ?? homedir2();
|
|
3771
4056
|
const binName = options.binName ?? "agent-kit";
|
|
3772
|
-
const [
|
|
3773
|
-
|
|
4057
|
+
const [binPath, npmPrefix] = await Promise.all([
|
|
4058
|
+
resolveBinOnPath(binName, env, platform).catch(() => null),
|
|
3774
4059
|
checkNpmPrefixWritable(options).catch(
|
|
3775
4060
|
() => ({
|
|
3776
4061
|
prefix: null,
|
|
@@ -3779,8 +4064,11 @@ async function assessEnvironment(options = {}) {
|
|
|
3779
4064
|
})
|
|
3780
4065
|
)
|
|
3781
4066
|
]);
|
|
4067
|
+
const binVersion = binPath ? await readBinVersion(binPath, options).catch(() => null) : null;
|
|
3782
4068
|
return {
|
|
3783
|
-
binOnPath,
|
|
4069
|
+
binOnPath: binPath != null,
|
|
4070
|
+
binPath,
|
|
4071
|
+
binVersion,
|
|
3784
4072
|
npmPrefixWritable: npmPrefix.writable,
|
|
3785
4073
|
npmPrefix,
|
|
3786
4074
|
nodeVersionOk: isNodeVersionOk(nodeVersion),
|
|
@@ -3949,21 +4237,30 @@ function buildPillars(scan) {
|
|
|
3949
4237
|
)
|
|
3950
4238
|
]),
|
|
3951
4239
|
pillar("quality-ci", [
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
"
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
4240
|
+
(() => {
|
|
4241
|
+
const hasValidation = scan.quality.hasTests || scan.quality.validationCommands.length > 0 || scan.quality.ciRunCommands.length > 0;
|
|
4242
|
+
return check(
|
|
4243
|
+
"quality.validation",
|
|
4244
|
+
"Tests and validation",
|
|
4245
|
+
hasValidation ? "ready" : "manual",
|
|
4246
|
+
false,
|
|
4247
|
+
[
|
|
4248
|
+
...scan.infra.ciFiles.map((value) => ({ source: "file", value })),
|
|
4249
|
+
...scan.quality.ciRunCommands.map((value) => ({
|
|
4250
|
+
source: "file",
|
|
4251
|
+
value: `ci:${value}`
|
|
4252
|
+
}))
|
|
4253
|
+
],
|
|
4254
|
+
hasValidation ? [] : [
|
|
4255
|
+
action(
|
|
4256
|
+
"document-validation",
|
|
4257
|
+
"manual",
|
|
4258
|
+
"Document a repeatable repository validation command",
|
|
4259
|
+
"user"
|
|
4260
|
+
)
|
|
4261
|
+
]
|
|
4262
|
+
);
|
|
4263
|
+
})()
|
|
3967
4264
|
]),
|
|
3968
4265
|
pillar("deploy-infrastructure", [
|
|
3969
4266
|
check(
|
|
@@ -4037,7 +4334,7 @@ function createReadinessReport(scan, options) {
|
|
|
4037
4334
|
}
|
|
4038
4335
|
|
|
4039
4336
|
// src/scanner/safe-fixes.ts
|
|
4040
|
-
import { readFile as
|
|
4337
|
+
import { readFile as readFile15, writeFile as writeFile5 } from "fs/promises";
|
|
4041
4338
|
import path24 from "path";
|
|
4042
4339
|
|
|
4043
4340
|
// src/scanner/detect-repository.ts
|
|
@@ -4410,6 +4707,7 @@ async function detectIde(rootDir) {
|
|
|
4410
4707
|
}
|
|
4411
4708
|
|
|
4412
4709
|
// src/scanner/detect-infra.ts
|
|
4710
|
+
import { readFile as readFile12, readdir as readdir5 } from "fs/promises";
|
|
4413
4711
|
import path20 from "path";
|
|
4414
4712
|
|
|
4415
4713
|
// src/types.ts
|
|
@@ -4465,6 +4763,51 @@ var PM_TOOL_LABELS = {
|
|
|
4465
4763
|
};
|
|
4466
4764
|
|
|
4467
4765
|
// src/scanner/detect-infra.ts
|
|
4766
|
+
function extractRunCommands(yaml) {
|
|
4767
|
+
const lines = yaml.split(/\r?\n/);
|
|
4768
|
+
const commands = [];
|
|
4769
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4770
|
+
const line = lines[i] ?? "";
|
|
4771
|
+
const match = /^(\s*)(?:-\s+)?run:\s*(.*)$/.exec(line);
|
|
4772
|
+
if (!match) continue;
|
|
4773
|
+
const [, indent = "", inline = ""] = match;
|
|
4774
|
+
const trimmedInline = inline.trim();
|
|
4775
|
+
if (trimmedInline && !/^[|>][+-]?$/.test(trimmedInline)) {
|
|
4776
|
+
commands.push(trimmedInline.replace(/^['"]|['"]$/g, ""));
|
|
4777
|
+
continue;
|
|
4778
|
+
}
|
|
4779
|
+
const blockIndent = indent.length;
|
|
4780
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
4781
|
+
const blockLine = lines[j] ?? "";
|
|
4782
|
+
if (!blockLine.trim()) continue;
|
|
4783
|
+
const lineIndent = blockLine.length - blockLine.trimStart().length;
|
|
4784
|
+
if (lineIndent <= blockIndent) break;
|
|
4785
|
+
commands.push(blockLine.trim());
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
return Array.from(new Set(commands));
|
|
4789
|
+
}
|
|
4790
|
+
async function detectCiRunCommands(rootDir) {
|
|
4791
|
+
const workflowsDir = path20.join(rootDir, ".github", "workflows");
|
|
4792
|
+
if (!await fileExists(workflowsDir)) return [];
|
|
4793
|
+
let entries;
|
|
4794
|
+
try {
|
|
4795
|
+
entries = await readdir5(workflowsDir);
|
|
4796
|
+
} catch {
|
|
4797
|
+
return [];
|
|
4798
|
+
}
|
|
4799
|
+
const workflowFiles = entries.filter((name) => /\.ya?ml$/.test(name));
|
|
4800
|
+
const perFile = await Promise.all(
|
|
4801
|
+
workflowFiles.map(async (name) => {
|
|
4802
|
+
try {
|
|
4803
|
+
return extractRunCommands(await readFile12(path20.join(workflowsDir, name), "utf8"));
|
|
4804
|
+
} catch {
|
|
4805
|
+
return [];
|
|
4806
|
+
}
|
|
4807
|
+
})
|
|
4808
|
+
);
|
|
4809
|
+
return Array.from(new Set(perFile.flat()));
|
|
4810
|
+
}
|
|
4468
4811
|
async function detectInfra(rootDir) {
|
|
4469
4812
|
const docker = await fileExists(path20.join(rootDir, "Dockerfile")) || await fileExists(path20.join(rootDir, "docker-compose.yml")) || await fileExists(path20.join(rootDir, "docker-compose.yaml"));
|
|
4470
4813
|
const kubernetes = await fileExists(path20.join(rootDir, "k8s")) || await fileExists(path20.join(rootDir, "kubernetes"));
|
|
@@ -4504,11 +4847,12 @@ async function detectInfra(rootDir) {
|
|
|
4504
4847
|
async (file) => await fileExists(path20.join(rootDir, file)) ? file : void 0
|
|
4505
4848
|
)
|
|
4506
4849
|
)).filter((file) => file !== void 0);
|
|
4507
|
-
|
|
4850
|
+
const ciRunCommands = ci === "github-actions" ? await detectCiRunCommands(rootDir) : [];
|
|
4851
|
+
return { docker, kubernetes, ci, ciFiles, infrastructureFiles, deploymentFiles, ciRunCommands };
|
|
4508
4852
|
}
|
|
4509
4853
|
|
|
4510
4854
|
// src/scanner/detect-services.ts
|
|
4511
|
-
import { readFile as
|
|
4855
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
4512
4856
|
import path21 from "path";
|
|
4513
4857
|
async function detectProjectManagement(rootDir) {
|
|
4514
4858
|
const tools = [];
|
|
@@ -4519,7 +4863,7 @@ async function detectProjectManagement(rootDir) {
|
|
|
4519
4863
|
for (const configPath of mcpConfigPaths) {
|
|
4520
4864
|
if (!await fileExists(configPath)) continue;
|
|
4521
4865
|
try {
|
|
4522
|
-
const raw = await
|
|
4866
|
+
const raw = await readFile13(configPath, "utf8");
|
|
4523
4867
|
const lower = raw.toLowerCase();
|
|
4524
4868
|
if (lower.includes("clickup")) tools.push("clickup");
|
|
4525
4869
|
if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
|
|
@@ -4555,6 +4899,7 @@ async function detectServices(rootDir) {
|
|
|
4555
4899
|
}
|
|
4556
4900
|
|
|
4557
4901
|
// src/scanner/detect-stack.ts
|
|
4902
|
+
import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
|
|
4558
4903
|
import path22 from "path";
|
|
4559
4904
|
var PROJECT_MARKERS = [
|
|
4560
4905
|
"package.json",
|
|
@@ -4566,8 +4911,152 @@ var PROJECT_MARKERS = [
|
|
|
4566
4911
|
"build.gradle",
|
|
4567
4912
|
"composer.json",
|
|
4568
4913
|
"Cargo.toml",
|
|
4569
|
-
"pnpm-workspace.yaml"
|
|
4914
|
+
"pnpm-workspace.yaml",
|
|
4915
|
+
"pubspec.yaml"
|
|
4570
4916
|
];
|
|
4917
|
+
var SUBDIR_SCAN_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "build", ".dart_tool"]);
|
|
4918
|
+
async function scanSubdirectoryMarkers(rootDir) {
|
|
4919
|
+
let entries;
|
|
4920
|
+
try {
|
|
4921
|
+
entries = await readdir6(rootDir, { withFileTypes: true });
|
|
4922
|
+
} catch {
|
|
4923
|
+
return [];
|
|
4924
|
+
}
|
|
4925
|
+
const dirs = entries.filter((entry) => entry.isDirectory() && !SUBDIR_SCAN_SKIP.has(entry.name)).map((entry) => entry.name);
|
|
4926
|
+
const candidates = dirs.flatMap((dir) => PROJECT_MARKERS.map((marker) => ({ dir, marker })));
|
|
4927
|
+
const found = await Promise.all(
|
|
4928
|
+
candidates.map(async (candidate2) => {
|
|
4929
|
+
const exists2 = await fileExists(path22.join(rootDir, candidate2.dir, candidate2.marker));
|
|
4930
|
+
return exists2 ? candidate2 : null;
|
|
4931
|
+
})
|
|
4932
|
+
);
|
|
4933
|
+
return found.filter((hit) => hit !== null);
|
|
4934
|
+
}
|
|
4935
|
+
function isFlutterPubspec(content) {
|
|
4936
|
+
return /^\s*flutter:\s*$/m.test(content) || /sdk:\s*flutter\b/.test(content);
|
|
4937
|
+
}
|
|
4938
|
+
var DART_VALIDATION_COMMANDS = [
|
|
4939
|
+
"dart analyze --fatal-infos",
|
|
4940
|
+
"dart format --output=none --set-exit-if-changed ."
|
|
4941
|
+
];
|
|
4942
|
+
async function detectDartFlavor(pubspecDir) {
|
|
4943
|
+
let content = "";
|
|
4944
|
+
try {
|
|
4945
|
+
content = await readFile14(path22.join(pubspecDir, "pubspec.yaml"), "utf8");
|
|
4946
|
+
} catch {
|
|
4947
|
+
}
|
|
4948
|
+
return { isFlutter: isFlutterPubspec(content) };
|
|
4949
|
+
}
|
|
4950
|
+
async function detectStackFromSubdirHits(rootDir, hits) {
|
|
4951
|
+
const packageManagerEvidence = hits.map((hit) => ({
|
|
4952
|
+
source: "file",
|
|
4953
|
+
value: `${hit.dir}/${hit.marker}`
|
|
4954
|
+
}));
|
|
4955
|
+
const dartHits = hits.filter((hit) => hit.marker === "pubspec.yaml");
|
|
4956
|
+
if (dartHits.length > 0) {
|
|
4957
|
+
const flavors = await Promise.all(
|
|
4958
|
+
dartHits.map((hit) => detectDartFlavor(path22.join(rootDir, hit.dir)))
|
|
4959
|
+
);
|
|
4960
|
+
const testCommands = Array.from(
|
|
4961
|
+
new Set(flavors.map((flavor) => flavor.isFlutter ? "flutter test" : "dart test"))
|
|
4962
|
+
);
|
|
4963
|
+
return {
|
|
4964
|
+
language: "dart",
|
|
4965
|
+
framework: flavors.some((flavor) => flavor.isFlutter) ? "flutter" : "dart",
|
|
4966
|
+
workspaces: true,
|
|
4967
|
+
testCommands,
|
|
4968
|
+
validationCommands: DART_VALIDATION_COMMANDS,
|
|
4969
|
+
hasProjectFiles: true,
|
|
4970
|
+
packageManagerEvidence
|
|
4971
|
+
};
|
|
4972
|
+
}
|
|
4973
|
+
const nodeHit = hits.find((hit) => hit.marker === "package.json");
|
|
4974
|
+
if (nodeHit) {
|
|
4975
|
+
const subRoot = path22.join(rootDir, nodeHit.dir);
|
|
4976
|
+
const packageJson = await readJson(path22.join(subRoot, "package.json")) ?? {};
|
|
4977
|
+
const scripts = packageJson.scripts ?? {};
|
|
4978
|
+
const packageManager = await detectPackageManager(subRoot, packageJson);
|
|
4979
|
+
const commands = commandsForScripts(scripts, packageManager.packageManager);
|
|
4980
|
+
return {
|
|
4981
|
+
language: "node",
|
|
4982
|
+
framework: "node",
|
|
4983
|
+
packageManager: packageManager.packageManager,
|
|
4984
|
+
packageManagerEvidence: [...packageManagerEvidence, ...packageManager.evidence],
|
|
4985
|
+
scripts,
|
|
4986
|
+
workspaces: true,
|
|
4987
|
+
...commands,
|
|
4988
|
+
hasProjectFiles: true
|
|
4989
|
+
};
|
|
4990
|
+
}
|
|
4991
|
+
const languageByMarker = [
|
|
4992
|
+
{ marker: "pyproject.toml", language: "python", testCommands: [], validationCommands: [] },
|
|
4993
|
+
{ marker: "requirements.txt", language: "python", testCommands: [], validationCommands: [] },
|
|
4994
|
+
{ marker: "go.mod", language: "go", testCommands: ["go test ./..."], validationCommands: [] },
|
|
4995
|
+
{
|
|
4996
|
+
marker: "Cargo.toml",
|
|
4997
|
+
language: "rust",
|
|
4998
|
+
testCommands: ["cargo test"],
|
|
4999
|
+
validationCommands: ["cargo check"]
|
|
5000
|
+
},
|
|
5001
|
+
{ marker: "composer.json", language: "php", testCommands: [], validationCommands: [] }
|
|
5002
|
+
];
|
|
5003
|
+
for (const candidate2 of languageByMarker) {
|
|
5004
|
+
if (hits.some((hit) => hit.marker === candidate2.marker)) {
|
|
5005
|
+
return {
|
|
5006
|
+
language: candidate2.language,
|
|
5007
|
+
framework: candidate2.language,
|
|
5008
|
+
workspaces: true,
|
|
5009
|
+
testCommands: candidate2.testCommands,
|
|
5010
|
+
validationCommands: candidate2.validationCommands,
|
|
5011
|
+
hasProjectFiles: true,
|
|
5012
|
+
packageManagerEvidence
|
|
5013
|
+
};
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
return {
|
|
5017
|
+
language: "unknown",
|
|
5018
|
+
workspaces: true,
|
|
5019
|
+
testCommands: [],
|
|
5020
|
+
validationCommands: [],
|
|
5021
|
+
hasProjectFiles: true,
|
|
5022
|
+
packageManagerEvidence
|
|
5023
|
+
};
|
|
5024
|
+
}
|
|
5025
|
+
var KNOWN_PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
5026
|
+
function isDetectionEvidenceArray(value) {
|
|
5027
|
+
return Array.isArray(value) && value.every(
|
|
5028
|
+
(item) => item && typeof item === "object" && typeof item.value === "string" && typeof item.source === "string"
|
|
5029
|
+
);
|
|
5030
|
+
}
|
|
5031
|
+
async function readConfirmedStack(rootDir) {
|
|
5032
|
+
const configuration = await readJson(
|
|
5033
|
+
path22.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
5034
|
+
);
|
|
5035
|
+
const configured = configuration?.stack;
|
|
5036
|
+
const language = configured?.language;
|
|
5037
|
+
if (!configured || typeof language !== "string" || !language || language === "unknown") {
|
|
5038
|
+
return void 0;
|
|
5039
|
+
}
|
|
5040
|
+
const testCommands = Array.isArray(configured.testCommands) ? configured.testCommands.filter((item) => typeof item === "string") : [];
|
|
5041
|
+
const validationCommands = Array.isArray(configured.validationCommands) ? configured.validationCommands.filter((item) => typeof item === "string") : [];
|
|
5042
|
+
if (testCommands.length === 0 && validationCommands.length === 0) return void 0;
|
|
5043
|
+
const packageManager = typeof configured.packageManager === "string" && KNOWN_PACKAGE_MANAGERS.includes(configured.packageManager) ? configured.packageManager : void 0;
|
|
5044
|
+
const scripts = configured.scripts && typeof configured.scripts === "object" && !Array.isArray(configured.scripts) ? configured.scripts : void 0;
|
|
5045
|
+
const packageManagerEvidence = isDetectionEvidenceArray(
|
|
5046
|
+
configured.packageManagerEvidence
|
|
5047
|
+
) ? configured.packageManagerEvidence : [{ source: "configuration", value: ".cursor/agent-kit.config.json#stack.language" }];
|
|
5048
|
+
return {
|
|
5049
|
+
language,
|
|
5050
|
+
framework: typeof configured.framework === "string" ? configured.framework : void 0,
|
|
5051
|
+
packageManager,
|
|
5052
|
+
scripts,
|
|
5053
|
+
workspaces: configured.workspaces === true,
|
|
5054
|
+
testCommands,
|
|
5055
|
+
validationCommands,
|
|
5056
|
+
hasProjectFiles: true,
|
|
5057
|
+
packageManagerEvidence
|
|
5058
|
+
};
|
|
5059
|
+
}
|
|
4571
5060
|
var LOCKFILES = [
|
|
4572
5061
|
["pnpm-lock.yaml", "pnpm"],
|
|
4573
5062
|
["yarn.lock", "yarn"],
|
|
@@ -4601,7 +5090,15 @@ function commandsForScripts(scripts, packageManager) {
|
|
|
4601
5090
|
return { testCommands, validationCommands };
|
|
4602
5091
|
}
|
|
4603
5092
|
async function detectStack(rootDir) {
|
|
5093
|
+
const confirmed = await readConfirmedStack(rootDir);
|
|
5094
|
+
if (confirmed) return confirmed;
|
|
4604
5095
|
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(path22.join(rootDir, item))))).some(Boolean);
|
|
5096
|
+
if (!hasAnyProjectMarker) {
|
|
5097
|
+
const subdirHits = await scanSubdirectoryMarkers(rootDir);
|
|
5098
|
+
if (subdirHits.length > 0) {
|
|
5099
|
+
return detectStackFromSubdirHits(rootDir, subdirHits);
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
4605
5102
|
const hasPackageJson = await fileExists(path22.join(rootDir, "package.json"));
|
|
4606
5103
|
if (hasPackageJson) {
|
|
4607
5104
|
const packageJson = await readJson(path22.join(rootDir, "package.json")) ?? {};
|
|
@@ -4661,6 +5158,17 @@ async function detectStack(rootDir) {
|
|
|
4661
5158
|
hasProjectFiles: hasAnyProjectMarker
|
|
4662
5159
|
};
|
|
4663
5160
|
}
|
|
5161
|
+
if (await fileExists(path22.join(rootDir, "pubspec.yaml"))) {
|
|
5162
|
+
const { isFlutter } = await detectDartFlavor(rootDir);
|
|
5163
|
+
return {
|
|
5164
|
+
language: "dart",
|
|
5165
|
+
framework: isFlutter ? "flutter" : "dart",
|
|
5166
|
+
workspaces: false,
|
|
5167
|
+
testCommands: [isFlutter ? "flutter test" : "dart test"],
|
|
5168
|
+
validationCommands: DART_VALIDATION_COMMANDS,
|
|
5169
|
+
hasProjectFiles: hasAnyProjectMarker
|
|
5170
|
+
};
|
|
5171
|
+
}
|
|
4664
5172
|
return {
|
|
4665
5173
|
language: "unknown",
|
|
4666
5174
|
workspaces: false,
|
|
@@ -4715,7 +5223,8 @@ async function runScanner(rootDir) {
|
|
|
4715
5223
|
testCommands: stack.testCommands,
|
|
4716
5224
|
validationCommands: stack.validationCommands,
|
|
4717
5225
|
ci: infra.ci,
|
|
4718
|
-
hasTests: stack.testCommands.length > 0
|
|
5226
|
+
hasTests: stack.testCommands.length > 0,
|
|
5227
|
+
ciRunCommands: infra.ciRunCommands
|
|
4719
5228
|
}
|
|
4720
5229
|
};
|
|
4721
5230
|
}
|
|
@@ -4788,9 +5297,12 @@ function createOnboardingState(report, generatedAt, deferredItems = report.defer
|
|
|
4788
5297
|
const validDeferredCheckIds = new Set(
|
|
4789
5298
|
deferredItems.filter((item) => item.reason.trim().length > 0).map((item) => item.checkId)
|
|
4790
5299
|
);
|
|
4791
|
-
const unresolvedEssential = report.pillars.flatMap((pillar2) => pillar2.checks).some(
|
|
4792
|
-
(check2
|
|
4793
|
-
|
|
5300
|
+
const unresolvedEssential = report.pillars.flatMap((pillar2) => pillar2.checks).some((check2) => {
|
|
5301
|
+
if (!check2.essential || check2.status === "ready") return false;
|
|
5302
|
+
if (check2.status === "blocked") return true;
|
|
5303
|
+
if (!validDeferredCheckIds.has(check2.id)) return true;
|
|
5304
|
+
return check2.actions.length > 0;
|
|
5305
|
+
});
|
|
4794
5306
|
return {
|
|
4795
5307
|
contractVersion: 1,
|
|
4796
5308
|
status: unresolvedEssential ? "in_progress" : "completed",
|
|
@@ -4941,7 +5453,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4941
5453
|
}
|
|
4942
5454
|
const gitignoreRelativePath = ".gitignore";
|
|
4943
5455
|
const gitignorePath = path24.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
4944
|
-
const existingGitignore = await fileExists(gitignorePath) ? await
|
|
5456
|
+
const existingGitignore = await fileExists(gitignorePath) ? await readFile15(gitignorePath, "utf8") : "";
|
|
4945
5457
|
const gitignoreAfterSecrets = mergeSecretIgnores(existingGitignore);
|
|
4946
5458
|
const secretsChanged = gitignoreAfterSecrets !== existingGitignore;
|
|
4947
5459
|
const mergedGitignore = mergeKitOwnedIgnores(gitignoreAfterSecrets);
|
|
@@ -5056,11 +5568,12 @@ var KIT_TIPS = [
|
|
|
5056
5568
|
"agent-kit --help groups SETUP, MISSION, DASHBOARD, INTEGRITY."
|
|
5057
5569
|
];
|
|
5058
5570
|
function shouldUseWelcomeColor(opts = {}) {
|
|
5571
|
+
const env = opts.env ?? process.env;
|
|
5059
5572
|
if (opts.color === false) return false;
|
|
5060
|
-
if (
|
|
5061
|
-
if (
|
|
5062
|
-
if (
|
|
5063
|
-
if (
|
|
5573
|
+
if (env.NO_COLOR) return false;
|
|
5574
|
+
if (env.NODE_DISABLE_COLORS) return false;
|
|
5575
|
+
if (env.FORCE_COLOR === "0") return false;
|
|
5576
|
+
if (env.CI != null && env.CI !== "") return false;
|
|
5064
5577
|
if (opts.color === true) return true;
|
|
5065
5578
|
const tty = opts.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
|
|
5066
5579
|
return tty;
|
|
@@ -5213,6 +5726,19 @@ async function withCliProgress(label, fn, opts) {
|
|
|
5213
5726
|
}
|
|
5214
5727
|
|
|
5215
5728
|
// src/commands/doctor.ts
|
|
5729
|
+
async function readDeferredEssentials(rootDir, report) {
|
|
5730
|
+
const config = await readJson(
|
|
5731
|
+
path26.join(rootDir, ".cursor", "context", "config.json")
|
|
5732
|
+
);
|
|
5733
|
+
const deferredItems = config?.onboarding?.deferredItems;
|
|
5734
|
+
if (!Array.isArray(deferredItems)) return [];
|
|
5735
|
+
const essentialIds = new Set(
|
|
5736
|
+
report.pillars.flatMap((pillar2) => pillar2.checks).filter((check2) => check2.essential).map((check2) => check2.id)
|
|
5737
|
+
);
|
|
5738
|
+
return deferredItems.filter(
|
|
5739
|
+
(item) => typeof item?.checkId === "string" && typeof item?.reason === "string" && essentialIds.has(item.checkId)
|
|
5740
|
+
).map((item) => ({ checkId: item.checkId, reason: item.reason }));
|
|
5741
|
+
}
|
|
5216
5742
|
async function runDoctor(cwd, options = {}) {
|
|
5217
5743
|
const rootDir = path26.resolve(cwd);
|
|
5218
5744
|
const hooks = await assessHooksHealth(rootDir);
|
|
@@ -5223,7 +5749,13 @@ async function runDoctor(cwd, options = {}) {
|
|
|
5223
5749
|
generatedAt: options.generatedAt
|
|
5224
5750
|
});
|
|
5225
5751
|
await writeReadinessSnapshot(rootDir, execution.after);
|
|
5226
|
-
return {
|
|
5752
|
+
return {
|
|
5753
|
+
report: execution.after,
|
|
5754
|
+
safeChanges: execution.changes,
|
|
5755
|
+
hooks,
|
|
5756
|
+
env,
|
|
5757
|
+
deferredEssentials: await readDeferredEssentials(rootDir, execution.after)
|
|
5758
|
+
};
|
|
5227
5759
|
}
|
|
5228
5760
|
if (options.refreshProfile) {
|
|
5229
5761
|
const refresh = await refreshRepositoryProfile(rootDir, {
|
|
@@ -5236,7 +5768,14 @@ async function runDoctor(cwd, options = {}) {
|
|
|
5236
5768
|
generatedAt: options.generatedAt
|
|
5237
5769
|
});
|
|
5238
5770
|
await writeReadinessSnapshot(rootDir, report2);
|
|
5239
|
-
return {
|
|
5771
|
+
return {
|
|
5772
|
+
report: report2,
|
|
5773
|
+
safeChanges: [],
|
|
5774
|
+
hooks,
|
|
5775
|
+
env,
|
|
5776
|
+
profileRefreshed: refresh.changed,
|
|
5777
|
+
deferredEssentials: await readDeferredEssentials(rootDir, report2)
|
|
5778
|
+
};
|
|
5240
5779
|
}
|
|
5241
5780
|
const scan = await runScanner(rootDir);
|
|
5242
5781
|
const report = createReadinessReport(scan, {
|
|
@@ -5244,7 +5783,13 @@ async function runDoctor(cwd, options = {}) {
|
|
|
5244
5783
|
generatedAt: options.generatedAt
|
|
5245
5784
|
});
|
|
5246
5785
|
await writeReadinessSnapshot(rootDir, report);
|
|
5247
|
-
return {
|
|
5786
|
+
return {
|
|
5787
|
+
report,
|
|
5788
|
+
safeChanges: [],
|
|
5789
|
+
hooks,
|
|
5790
|
+
env,
|
|
5791
|
+
deferredEssentials: await readDeferredEssentials(rootDir, report)
|
|
5792
|
+
};
|
|
5248
5793
|
}
|
|
5249
5794
|
function printDoctorSummary(result) {
|
|
5250
5795
|
const { summary, pendingActions } = result.report;
|
|
@@ -5261,6 +5806,9 @@ function printDoctorSummary(result) {
|
|
|
5261
5806
|
` profile refreshed: ${result.profileRefreshed ? "yes (facts changed)" : "no (already current)"}`
|
|
5262
5807
|
);
|
|
5263
5808
|
}
|
|
5809
|
+
for (const item of result.deferredEssentials) {
|
|
5810
|
+
console.log(` Deferred essential: ${item.checkId} \u2014 ${item.reason}`);
|
|
5811
|
+
}
|
|
5264
5812
|
console.log(`hooks: ${result.hooks.status}`);
|
|
5265
5813
|
if (result.hooks.reasons.length > 0) {
|
|
5266
5814
|
for (const reason of result.hooks.reasons.slice(0, 5)) {
|
|
@@ -5274,7 +5822,8 @@ function printDoctorSummary(result) {
|
|
|
5274
5822
|
}
|
|
5275
5823
|
}
|
|
5276
5824
|
console.log("environment:");
|
|
5277
|
-
|
|
5825
|
+
const pathLabel = result.env.binOnPath ? `ok (v${result.env.binVersion ?? "unknown"} at ${result.env.binPath})` : "MISSING";
|
|
5826
|
+
console.log(` - bin on PATH (agent-kit): ${pathLabel}`);
|
|
5278
5827
|
console.log(
|
|
5279
5828
|
` - npm prefix writable: ${result.env.npmPrefixWritable ? "ok" : "BLOCKED"}${result.env.npmPrefix.prefix ? ` (${result.env.npmPrefix.prefix})` : ""}`
|
|
5280
5829
|
);
|
|
@@ -5719,9 +6268,187 @@ var guardCommand = defineCommand8({
|
|
|
5719
6268
|
|
|
5720
6269
|
// src/commands/handoff.ts
|
|
5721
6270
|
import { spawn as spawn4 } from "child_process";
|
|
5722
|
-
import { readFile as
|
|
6271
|
+
import { appendFile, readFile as readFile16, readdir as readdir7, writeFile as writeFile6 } from "fs/promises";
|
|
5723
6272
|
import path27 from "path";
|
|
5724
6273
|
import { defineCommand as defineCommand9 } from "citty";
|
|
6274
|
+
|
|
6275
|
+
// src/invariants/handoff-prune.ts
|
|
6276
|
+
var HANDOFF_MACHINE_FIELD_LABELS = [
|
|
6277
|
+
"Plan",
|
|
6278
|
+
"Last updated",
|
|
6279
|
+
"Mode",
|
|
6280
|
+
"Phase completed",
|
|
6281
|
+
"Next phase",
|
|
6282
|
+
"Completed to-dos",
|
|
6283
|
+
"Next to-dos",
|
|
6284
|
+
"Parked plans",
|
|
6285
|
+
"Backlog plans",
|
|
6286
|
+
"Run queue",
|
|
6287
|
+
"Queue cursor",
|
|
6288
|
+
"Queue status",
|
|
6289
|
+
"Queue outcomes",
|
|
6290
|
+
"Gaps",
|
|
6291
|
+
"Instruction for the next agent"
|
|
6292
|
+
];
|
|
6293
|
+
var HANDOFF_FIXED_SECTION_HEADING_PREFIXES = [
|
|
6294
|
+
"## Work Status",
|
|
6295
|
+
"## Session Context",
|
|
6296
|
+
"## Files Touched",
|
|
6297
|
+
"## Decisions Made",
|
|
6298
|
+
"## Issues Found",
|
|
6299
|
+
"## Run queue (multi-plan only)"
|
|
6300
|
+
];
|
|
6301
|
+
var HANDOFF_MAX_LINES = 200;
|
|
6302
|
+
var HANDOFF_MAX_LINE_LENGTH = 2e3;
|
|
6303
|
+
var MACHINE_FIELD_RE = new RegExp(
|
|
6304
|
+
`^- \\*\\*(${HANDOFF_MACHINE_FIELD_LABELS.map((l) => l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")}):\\*\\*`
|
|
6305
|
+
);
|
|
6306
|
+
function isChunkStart(line) {
|
|
6307
|
+
return /^- \*\*/.test(line) || /^## /.test(line);
|
|
6308
|
+
}
|
|
6309
|
+
function isFixedSectionHeading(line) {
|
|
6310
|
+
return HANDOFF_FIXED_SECTION_HEADING_PREFIXES.some((p) => line.startsWith(p));
|
|
6311
|
+
}
|
|
6312
|
+
function splitIntoChunks(lines) {
|
|
6313
|
+
const firstStart = lines.findIndex(isChunkStart);
|
|
6314
|
+
if (firstStart === -1) return { preamble: lines, chunks: [] };
|
|
6315
|
+
const preamble = lines.slice(0, firstStart);
|
|
6316
|
+
const chunks = [];
|
|
6317
|
+
let current = [lines[firstStart]];
|
|
6318
|
+
for (const line of lines.slice(firstStart + 1)) {
|
|
6319
|
+
if (isChunkStart(line)) {
|
|
6320
|
+
chunks.push({ lines: current, isMachineField: MACHINE_FIELD_RE.test(current[0] ?? "") });
|
|
6321
|
+
current = [line];
|
|
6322
|
+
} else {
|
|
6323
|
+
current.push(line);
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
chunks.push({ lines: current, isMachineField: MACHINE_FIELD_RE.test(current[0] ?? "") });
|
|
6327
|
+
return { preamble, chunks };
|
|
6328
|
+
}
|
|
6329
|
+
function pruneHandoffText(text, keep) {
|
|
6330
|
+
if (!text.trim()) {
|
|
6331
|
+
return { ok: false, reason: "empty file" };
|
|
6332
|
+
}
|
|
6333
|
+
const lines = text.split("\n");
|
|
6334
|
+
const titleIdx = lines.findIndex((l) => /^# /.test(l));
|
|
6335
|
+
if (titleIdx === -1) {
|
|
6336
|
+
return { ok: false, reason: "no H1 title line found" };
|
|
6337
|
+
}
|
|
6338
|
+
let fixedStartIdx = lines.findIndex((l) => isFixedSectionHeading(l));
|
|
6339
|
+
if (fixedStartIdx === -1) fixedStartIdx = lines.length;
|
|
6340
|
+
const head = lines.slice(0, titleIdx + 1);
|
|
6341
|
+
const narrativeLines = lines.slice(titleIdx + 1, fixedStartIdx);
|
|
6342
|
+
const trailingLines = lines.slice(fixedStartIdx);
|
|
6343
|
+
const { preamble, chunks } = splitIntoChunks(narrativeLines);
|
|
6344
|
+
const machineFieldCount = chunks.filter((c) => c.isMachineField).length;
|
|
6345
|
+
if (machineFieldCount === 0) {
|
|
6346
|
+
return { ok: false, reason: "no machine-field bullets found; refusing to prune" };
|
|
6347
|
+
}
|
|
6348
|
+
if (fixedStartIdx === lines.length) {
|
|
6349
|
+
return { ok: false, reason: "no recognized fixed-section heading found; refusing to prune" };
|
|
6350
|
+
}
|
|
6351
|
+
const narrativeChunks = chunks.filter((c) => !c.isMachineField);
|
|
6352
|
+
if (narrativeChunks.length <= keep) {
|
|
6353
|
+
return { ok: true, text, archived: [], keptCount: narrativeChunks.length };
|
|
6354
|
+
}
|
|
6355
|
+
const keptNarrativeIdx = /* @__PURE__ */ new Set();
|
|
6356
|
+
let seen = 0;
|
|
6357
|
+
const chunkIsNarrative = chunks.map((c) => !c.isMachineField);
|
|
6358
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
6359
|
+
if (chunkIsNarrative[i]) {
|
|
6360
|
+
if (seen < keep) keptNarrativeIdx.add(i);
|
|
6361
|
+
seen++;
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
const keptChunks = [];
|
|
6365
|
+
const archivedChunks = [];
|
|
6366
|
+
chunks.forEach((c, i) => {
|
|
6367
|
+
if (c.isMachineField || keptNarrativeIdx.has(i)) keptChunks.push(c);
|
|
6368
|
+
else archivedChunks.push(c);
|
|
6369
|
+
});
|
|
6370
|
+
const newNarrativeLines = [...preamble, ...keptChunks.flatMap((c) => c.lines)];
|
|
6371
|
+
const newLines = [...head, ...newNarrativeLines, ...trailingLines];
|
|
6372
|
+
return {
|
|
6373
|
+
ok: true,
|
|
6374
|
+
text: newLines.join("\n"),
|
|
6375
|
+
archived: archivedChunks.map((c) => c.lines.join("\n")),
|
|
6376
|
+
keptCount: keptNarrativeIdx.size
|
|
6377
|
+
};
|
|
6378
|
+
}
|
|
6379
|
+
function checkHandoffGrowth(text) {
|
|
6380
|
+
if (!text.trim()) return [];
|
|
6381
|
+
const lines = text.split("\n");
|
|
6382
|
+
const warnings = [];
|
|
6383
|
+
if (lines.length > HANDOFF_MAX_LINES) {
|
|
6384
|
+
warnings.push({
|
|
6385
|
+
code: "file-over-limit",
|
|
6386
|
+
message: `HANDOFF.md is ${lines.length} lines, over the ${HANDOFF_MAX_LINES}-line growth-contract limit. Run \`agent-kit handoff --prune\` to archive older narrative entries.`
|
|
6387
|
+
});
|
|
6388
|
+
}
|
|
6389
|
+
let firstOffendingLine = -1;
|
|
6390
|
+
let overCount = 0;
|
|
6391
|
+
lines.forEach((l, i) => {
|
|
6392
|
+
if (l.length > HANDOFF_MAX_LINE_LENGTH) {
|
|
6393
|
+
overCount++;
|
|
6394
|
+
if (firstOffendingLine === -1) firstOffendingLine = i + 1;
|
|
6395
|
+
}
|
|
6396
|
+
});
|
|
6397
|
+
if (overCount > 0) {
|
|
6398
|
+
warnings.push({
|
|
6399
|
+
code: "line-over-limit",
|
|
6400
|
+
message: `HANDOFF.md has ${overCount} line(s) over the ${HANDOFF_MAX_LINE_LENGTH}-char growth-contract limit (first at line ${firstOffendingLine}). Wrap long paragraphs; do not widen the limit.`
|
|
6401
|
+
});
|
|
6402
|
+
}
|
|
6403
|
+
return warnings;
|
|
6404
|
+
}
|
|
6405
|
+
|
|
6406
|
+
// src/commands/handoff.ts
|
|
6407
|
+
var HANDOFF_PRUNE_DEFAULT_KEEP = 5;
|
|
6408
|
+
function pruneArchiveTimestamp(now2 = /* @__PURE__ */ new Date()) {
|
|
6409
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
6410
|
+
return `${now2.getFullYear()}-${pad(now2.getMonth() + 1)}-${pad(now2.getDate())}-${pad(now2.getHours())}${pad(now2.getMinutes())}`;
|
|
6411
|
+
}
|
|
6412
|
+
async function runPrune(cwd, keep) {
|
|
6413
|
+
const handoffPath = path27.join(cwd, ".cursor", "HANDOFF.md");
|
|
6414
|
+
if (!await fileExists(handoffPath)) {
|
|
6415
|
+
logger.warn("No .cursor/HANDOFF.md to prune.");
|
|
6416
|
+
return;
|
|
6417
|
+
}
|
|
6418
|
+
const original = await readFile16(handoffPath, "utf8");
|
|
6419
|
+
const result = pruneHandoffText(original, keep);
|
|
6420
|
+
if (!result.ok) {
|
|
6421
|
+
logger.warn(`Refusing to prune: ${result.reason}`);
|
|
6422
|
+
process.exitCode = 1;
|
|
6423
|
+
return;
|
|
6424
|
+
}
|
|
6425
|
+
if (!result.archived || result.archived.length === 0) {
|
|
6426
|
+
logger.info(
|
|
6427
|
+
`HANDOFF.md already at or under ${keep} narrative ${keep === 1 ? "entry" : "entries"}; nothing to prune.`
|
|
6428
|
+
);
|
|
6429
|
+
return;
|
|
6430
|
+
}
|
|
6431
|
+
const archiveDir = path27.join(cwd, ".cursor", "context", "archive");
|
|
6432
|
+
await ensureDir(archiveDir);
|
|
6433
|
+
const archivePath = path27.join(archiveDir, `handoff-${pruneArchiveTimestamp()}.md`);
|
|
6434
|
+
const header = [
|
|
6435
|
+
`<!-- Pruned from .cursor/HANDOFF.md at ${(/* @__PURE__ */ new Date()).toISOString()} -->`,
|
|
6436
|
+
`<!-- ${result.archived.length} narrative ${result.archived.length === 1 ? "entry" : "entries"} archived, keep=${keep} -->`,
|
|
6437
|
+
""
|
|
6438
|
+
].join("\n");
|
|
6439
|
+
const body = `${result.archived.join("\n\n")}
|
|
6440
|
+
`;
|
|
6441
|
+
if (await fileExists(archivePath)) {
|
|
6442
|
+
await appendFile(archivePath, `
|
|
6443
|
+
${header}${body}`, "utf8");
|
|
6444
|
+
} else {
|
|
6445
|
+
await writeFile6(archivePath, `${header}${body}`, "utf8");
|
|
6446
|
+
}
|
|
6447
|
+
await writeFile6(handoffPath, result.text ?? original, "utf8");
|
|
6448
|
+
logger.success(
|
|
6449
|
+
`HANDOFF.md pruned: kept ${result.keptCount} narrative ${result.keptCount === 1 ? "entry" : "entries"}, archived ${result.archived.length} to ${path27.relative(cwd, archivePath)}.`
|
|
6450
|
+
);
|
|
6451
|
+
}
|
|
5725
6452
|
function parsePlanFrontmatter(raw) {
|
|
5726
6453
|
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
5727
6454
|
if (!match?.[1]) return null;
|
|
@@ -5738,15 +6465,15 @@ function parsePlanFrontmatter(raw) {
|
|
|
5738
6465
|
}
|
|
5739
6466
|
async function findActivePlan(plansDir) {
|
|
5740
6467
|
if (!await fileExists(plansDir)) return null;
|
|
5741
|
-
const files = (await
|
|
6468
|
+
const files = (await readdir7(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
5742
6469
|
for (const file of files) {
|
|
5743
|
-
const raw = await
|
|
6470
|
+
const raw = await readFile16(path27.join(plansDir, file), "utf8");
|
|
5744
6471
|
const fm = parsePlanFrontmatter(raw);
|
|
5745
6472
|
if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
|
|
5746
6473
|
return { file, raw };
|
|
5747
6474
|
}
|
|
5748
6475
|
}
|
|
5749
|
-
return files[0] ? { file: files[0], raw: await
|
|
6476
|
+
return files[0] ? { file: files[0], raw: await readFile16(path27.join(plansDir, files[0]), "utf8") } : null;
|
|
5750
6477
|
}
|
|
5751
6478
|
function now() {
|
|
5752
6479
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
|
|
@@ -5848,9 +6575,24 @@ var handoffCommand = defineCommand9({
|
|
|
5848
6575
|
type: "string",
|
|
5849
6576
|
description: "Project root directory",
|
|
5850
6577
|
default: process.cwd()
|
|
6578
|
+
},
|
|
6579
|
+
prune: {
|
|
6580
|
+
type: "boolean",
|
|
6581
|
+
description: "Prune .cursor/HANDOFF.md in place instead of regenerating it: keep the most recent narrative entries, archive the rest to .cursor/context/archive/. Never auto-triggered.",
|
|
6582
|
+
default: false
|
|
6583
|
+
},
|
|
6584
|
+
keep: {
|
|
6585
|
+
type: "string",
|
|
6586
|
+
description: `With --prune, how many recent narrative entries to keep in place (default ${HANDOFF_PRUNE_DEFAULT_KEEP}).`
|
|
5851
6587
|
}
|
|
5852
6588
|
},
|
|
5853
6589
|
async run({ args }) {
|
|
6590
|
+
if (args.prune) {
|
|
6591
|
+
const keepArg = typeof args.keep === "string" ? Number.parseInt(args.keep, 10) : void 0;
|
|
6592
|
+
const keep = keepArg !== void 0 && Number.isFinite(keepArg) && keepArg >= 0 ? keepArg : HANDOFF_PRUNE_DEFAULT_KEEP;
|
|
6593
|
+
await runPrune(args.cwd, keep);
|
|
6594
|
+
return;
|
|
6595
|
+
}
|
|
5854
6596
|
const profile = await loadProfile(args.cwd);
|
|
5855
6597
|
const plansDir = path27.join(args.cwd, ".cursor", "plans");
|
|
5856
6598
|
const handoffPath = path27.join(args.cwd, ".cursor", "HANDOFF.md");
|
|
@@ -5929,7 +6671,7 @@ function buildPreCompactUserMessage(payload = {}) {
|
|
|
5929
6671
|
|
|
5930
6672
|
// src/hooks/session-start.ts
|
|
5931
6673
|
import { spawn as spawn5 } from "child_process";
|
|
5932
|
-
import { access as access7, readFile as
|
|
6674
|
+
import { access as access7, readFile as readFile18, stat as stat4 } from "fs/promises";
|
|
5933
6675
|
import path29 from "path";
|
|
5934
6676
|
|
|
5935
6677
|
// src/invariants/handoff-schema.ts
|
|
@@ -5972,7 +6714,7 @@ function validateHandoffText(text) {
|
|
|
5972
6714
|
}
|
|
5973
6715
|
|
|
5974
6716
|
// src/plan-index/plan-index.ts
|
|
5975
|
-
import { readFile as
|
|
6717
|
+
import { readFile as readFile17, writeFile as writeFile7 } from "fs/promises";
|
|
5976
6718
|
import path28 from "path";
|
|
5977
6719
|
var PLAN_INDEX_REL = ".cursor/context/plan-index.json";
|
|
5978
6720
|
var NONE = /^(none|n\/a|empty|nil)$/i;
|
|
@@ -5981,7 +6723,7 @@ function defaultPlanIndexIo() {
|
|
|
5981
6723
|
return {
|
|
5982
6724
|
async readFile(absPath) {
|
|
5983
6725
|
try {
|
|
5984
|
-
return await
|
|
6726
|
+
return await readFile17(absPath, "utf8");
|
|
5985
6727
|
} catch {
|
|
5986
6728
|
return null;
|
|
5987
6729
|
}
|
|
@@ -6152,6 +6894,137 @@ function extractPlanBasenames(raw) {
|
|
|
6152
6894
|
return ids;
|
|
6153
6895
|
}
|
|
6154
6896
|
|
|
6897
|
+
// src/hooks/handoff-excerpt.ts
|
|
6898
|
+
var HANDOFF_EXCERPT_MAX_LINES = 60;
|
|
6899
|
+
var HANDOFF_EXCERPT_MAX_BYTES = 12 * 1024;
|
|
6900
|
+
var HANDOFF_PROSE_LINE_MIN_BYTES = 160;
|
|
6901
|
+
var HANDOFF_MACHINE_FIELDS = /* @__PURE__ */ new Set([
|
|
6902
|
+
"Plan",
|
|
6903
|
+
"Last updated",
|
|
6904
|
+
"Mode",
|
|
6905
|
+
"In progress",
|
|
6906
|
+
"Next",
|
|
6907
|
+
"Next phase",
|
|
6908
|
+
"Next to-dos",
|
|
6909
|
+
"Backlog",
|
|
6910
|
+
"Backlog plans",
|
|
6911
|
+
"Parked plans",
|
|
6912
|
+
"Run queue",
|
|
6913
|
+
"Queue cursor",
|
|
6914
|
+
"Queue status"
|
|
6915
|
+
]);
|
|
6916
|
+
var FIELD_BULLET = /^- \*\*([^*\n]+?):\*\*/;
|
|
6917
|
+
function byteLength(s) {
|
|
6918
|
+
return Buffer.byteLength(s, "utf8");
|
|
6919
|
+
}
|
|
6920
|
+
function classify(lines) {
|
|
6921
|
+
const entries = [];
|
|
6922
|
+
let lastBullet = "structural";
|
|
6923
|
+
for (const text of lines) {
|
|
6924
|
+
let kind;
|
|
6925
|
+
if (!text.trim() || /^#{1,6}\s/.test(text)) {
|
|
6926
|
+
kind = "structural";
|
|
6927
|
+
} else if (/^\s+\S/.test(text)) {
|
|
6928
|
+
kind = lastBullet;
|
|
6929
|
+
} else {
|
|
6930
|
+
const m = FIELD_BULLET.exec(text);
|
|
6931
|
+
kind = m?.[1] && HANDOFF_MACHINE_FIELDS.has(m[1].trim()) ? "machine" : "prose";
|
|
6932
|
+
lastBullet = kind;
|
|
6933
|
+
}
|
|
6934
|
+
entries.push({ text, kind, bytes: byteLength(text) });
|
|
6935
|
+
}
|
|
6936
|
+
return entries;
|
|
6937
|
+
}
|
|
6938
|
+
function truncateToBytes(text, maxBytes) {
|
|
6939
|
+
let used = 0;
|
|
6940
|
+
let kept = "";
|
|
6941
|
+
let keptChars = 0;
|
|
6942
|
+
const total = Array.from(text).length;
|
|
6943
|
+
for (const ch of text) {
|
|
6944
|
+
const b = byteLength(ch);
|
|
6945
|
+
if (used + b > maxBytes) break;
|
|
6946
|
+
used += b;
|
|
6947
|
+
kept += ch;
|
|
6948
|
+
keptChars += 1;
|
|
6949
|
+
}
|
|
6950
|
+
return { kept: kept.trimEnd(), removedChars: total - keptChars };
|
|
6951
|
+
}
|
|
6952
|
+
var MARKER_RESERVE_BYTES = 32;
|
|
6953
|
+
function truncationMarker(removedChars) {
|
|
6954
|
+
return ` \u2026[truncated ${removedChars} chars]`;
|
|
6955
|
+
}
|
|
6956
|
+
function droppedMarker(dropped) {
|
|
6957
|
+
return `\u2026[${dropped} prose ${dropped === 1 ? "line" : "lines"} omitted by the excerpt byte cap; read .cursor/HANDOFF.md]`;
|
|
6958
|
+
}
|
|
6959
|
+
function proseShare(proseBytes, budget, floor) {
|
|
6960
|
+
const n = proseBytes.length;
|
|
6961
|
+
if (n === 0) return budget;
|
|
6962
|
+
let share = Math.floor(budget / n);
|
|
6963
|
+
for (let i = 0; i < 8; i++) {
|
|
6964
|
+
const longer = proseBytes.filter((b) => b > share);
|
|
6965
|
+
if (longer.length === 0) break;
|
|
6966
|
+
const used = proseBytes.reduce((sum, b) => sum + Math.min(b, share), 0);
|
|
6967
|
+
const slack = budget - used;
|
|
6968
|
+
const step = Math.floor(slack / longer.length);
|
|
6969
|
+
if (step <= 0) break;
|
|
6970
|
+
share += step;
|
|
6971
|
+
}
|
|
6972
|
+
return Math.max(floor, share);
|
|
6973
|
+
}
|
|
6974
|
+
function capHandoffExcerpt(content, options = {}) {
|
|
6975
|
+
const maxLines = options.maxLines ?? HANDOFF_EXCERPT_MAX_LINES;
|
|
6976
|
+
const maxBytes = options.maxBytes ?? HANDOFF_EXCERPT_MAX_BYTES;
|
|
6977
|
+
const floor = options.proseLineMinBytes ?? HANDOFF_PROSE_LINE_MIN_BYTES;
|
|
6978
|
+
const lines = content.split(/\r?\n/).slice(0, maxLines);
|
|
6979
|
+
const entries = classify(lines);
|
|
6980
|
+
const join2 = (items) => items.map((e) => e.text).join("\n").trim();
|
|
6981
|
+
const untouched = join2(entries);
|
|
6982
|
+
const untouchedBytes = byteLength(untouched);
|
|
6983
|
+
if (untouchedBytes <= maxBytes) {
|
|
6984
|
+
return {
|
|
6985
|
+
text: untouched,
|
|
6986
|
+
bytes: untouchedBytes,
|
|
6987
|
+
lines: lines.length,
|
|
6988
|
+
truncatedLines: 0,
|
|
6989
|
+
droppedLines: 0
|
|
6990
|
+
};
|
|
6991
|
+
}
|
|
6992
|
+
const fixedBytes = entries.reduce((sum, e) => sum + (e.kind === "prose" ? 0 : e.bytes) + 1, 0);
|
|
6993
|
+
const proseBudget = Math.max(0, maxBytes - fixedBytes);
|
|
6994
|
+
const share = proseShare(
|
|
6995
|
+
entries.filter((e) => e.kind === "prose").map((e) => e.bytes),
|
|
6996
|
+
proseBudget,
|
|
6997
|
+
floor
|
|
6998
|
+
);
|
|
6999
|
+
let truncatedLines = 0;
|
|
7000
|
+
const shaped = entries.map((e) => {
|
|
7001
|
+
if (e.kind !== "prose" || e.bytes <= share) return e;
|
|
7002
|
+
const { kept, removedChars } = truncateToBytes(e.text, share - MARKER_RESERVE_BYTES);
|
|
7003
|
+
const text2 = kept + truncationMarker(removedChars);
|
|
7004
|
+
truncatedLines += 1;
|
|
7005
|
+
return { text: text2, kind: e.kind, bytes: byteLength(text2) };
|
|
7006
|
+
});
|
|
7007
|
+
let total = shaped.reduce((sum, e) => sum + e.bytes + 1, 0) - 1;
|
|
7008
|
+
let droppedLines = 0;
|
|
7009
|
+
if (total > maxBytes) {
|
|
7010
|
+
const keep = new Set(shaped.map((_, i) => i));
|
|
7011
|
+
const noteBytes = byteLength(droppedMarker(99)) + 1;
|
|
7012
|
+
for (let i = shaped.length - 1; i >= 0 && total + noteBytes > maxBytes; i--) {
|
|
7013
|
+
const e = shaped[i];
|
|
7014
|
+
if (!e || e.kind !== "prose") continue;
|
|
7015
|
+
keep.delete(i);
|
|
7016
|
+
total -= e.bytes + 1;
|
|
7017
|
+
droppedLines += 1;
|
|
7018
|
+
}
|
|
7019
|
+
const remaining = shaped.filter((_, i) => keep.has(i));
|
|
7020
|
+
remaining.push({ text: droppedMarker(droppedLines), kind: "structural", bytes: 0 });
|
|
7021
|
+
const text2 = join2(remaining);
|
|
7022
|
+
return { text: text2, bytes: byteLength(text2), lines: lines.length, truncatedLines, droppedLines };
|
|
7023
|
+
}
|
|
7024
|
+
const text = join2(shaped);
|
|
7025
|
+
return { text, bytes: byteLength(text), lines: lines.length, truncatedLines, droppedLines };
|
|
7026
|
+
}
|
|
7027
|
+
|
|
6155
7028
|
// src/hooks/hard-rules.ts
|
|
6156
7029
|
var HARD_RULES = `# Agent Kit session hard rules (manual mode default)
|
|
6157
7030
|
|
|
@@ -6161,7 +7034,7 @@ var HARD_RULES = `# Agent Kit session hard rules (manual mode default)
|
|
|
6161
7034
|
4. **Read HANDOFF first** when resuming. Do not restart the plan from scratch.
|
|
6162
7035
|
5. **Git:** suggest \`/git-staging\` after a phase with a diff; never \`/git-prod\` without explicit confirmation.
|
|
6163
7036
|
6. **HITL slash commands win.** When waiting for confirmation on \`/git-staging\` or \`/git-prod\` (or similar), do not divert to continue-plan / phase-boundary chatter; stay on that routine until the user answers.
|
|
6164
|
-
7. **\`/start-project\` is plan bootstrap, not execute.** Broad Intake Review first, then two gates: (A) single composite question (with active plan: backlog+write / park+write / modify / cancel; without: write / modify / cancel)
|
|
7037
|
+
7. **\`/start-project\` is plan bootstrap, not execute.** Broad Intake Review first, then two gates: (A) single composite question (with active plan: backlog+write / park+write / modify / cancel; without: write / write+backlog (\`Write plan and add to backlog\`) / modify / cancel): approve/write the plan file only, (B) approve the first unit. Goal text in the same message is NOT permission to edit product files. Never "create plan and start Phase 1" in one turn. If HANDOFF already has an active plan, disposition is merged into Gate A composite options; never park silently. Gates use Ask questions per \`.cursor/skills/core/hitl-gates/SKILL.md\` and \`.cursor/rules/hitl-ask-questions.mdc\`. Fallback: numbered list is path 1.
|
|
6165
7038
|
8. **\`/continue-plan\` waits for yes.** Summarize next \`[to-do-id]\`, then stop until the user confirms before editing.
|
|
6166
7039
|
9. **\`/run-plan-all\` is a pure orchestrator.** After the confirm queue Ask, dispatch one Task subagent per plan (run the \`/run-plan\` tick contract inside it); never implement to-dos, run tests, or write changelogs in the orchestrator window.
|
|
6167
7040
|
10. **Backlog CRUD never activates.** \`/backlog-add\` enqueues (Broad Intake + write Ask + plan file + HANDOFF Backlog) without park, activate, or Gate B. \`/backlog-edit\` / \`/backlog-delete\` / \`/backlog-cancel\` require Ask confirm before mutate; delete archives from Backlog, cancel is soft in place. No Field Report cards for routine backlog CRUD.
|
|
@@ -6184,17 +7057,9 @@ Run \`/cursor-update-awareness\` (or \`agent-kit cursor-awareness --check\`) and
|
|
|
6184
7057
|
// src/hooks/session-start.ts
|
|
6185
7058
|
var NONE_PLACEHOLDERS = /* @__PURE__ */ new Set(["none", "n/a", "empty", "nil"]);
|
|
6186
7059
|
var CURSOR_AWARENESS_SPAWN_TIMEOUT_MS = CHANGELOG_FETCH_TIMEOUT_MS + 3e3;
|
|
6187
|
-
async function readTextLimited(filePath, limit = 60) {
|
|
6188
|
-
try {
|
|
6189
|
-
const lines = (await readFile16(filePath, "utf8")).split(/\r?\n/);
|
|
6190
|
-
return lines.slice(0, limit).join("\n").trim();
|
|
6191
|
-
} catch {
|
|
6192
|
-
return "";
|
|
6193
|
-
}
|
|
6194
|
-
}
|
|
6195
7060
|
async function readFull(filePath) {
|
|
6196
7061
|
try {
|
|
6197
|
-
return await
|
|
7062
|
+
return await readFile18(filePath, "utf8");
|
|
6198
7063
|
} catch {
|
|
6199
7064
|
return "";
|
|
6200
7065
|
}
|
|
@@ -6311,7 +7176,7 @@ async function readinessSection(root) {
|
|
|
6311
7176
|
const snapshotPath = path29.join(root, ".cursor", "context", "readiness.json");
|
|
6312
7177
|
let data;
|
|
6313
7178
|
try {
|
|
6314
|
-
data = JSON.parse(await
|
|
7179
|
+
data = JSON.parse(await readFile18(snapshotPath, "utf8"));
|
|
6315
7180
|
} catch {
|
|
6316
7181
|
return null;
|
|
6317
7182
|
}
|
|
@@ -6348,7 +7213,7 @@ async function dogfoodInboxSection(root) {
|
|
|
6348
7213
|
for (const readme of candidateReadmes) {
|
|
6349
7214
|
if (!await fileExists2(readme)) continue;
|
|
6350
7215
|
try {
|
|
6351
|
-
const text = await
|
|
7216
|
+
const text = await readFile18(readme, "utf8");
|
|
6352
7217
|
if (parseUnprocessedDogfoodItems(text).length) return DOGFOOD_INBOX_HINT;
|
|
6353
7218
|
} catch {
|
|
6354
7219
|
}
|
|
@@ -6358,7 +7223,7 @@ async function dogfoodInboxSection(root) {
|
|
|
6358
7223
|
async function loadUpdateCheckPrefs(root) {
|
|
6359
7224
|
try {
|
|
6360
7225
|
const data = JSON.parse(
|
|
6361
|
-
await
|
|
7226
|
+
await readFile18(path29.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
6362
7227
|
);
|
|
6363
7228
|
const uc = data.updateCheck;
|
|
6364
7229
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -6436,7 +7301,7 @@ async function updateCheckSection(root) {
|
|
|
6436
7301
|
async function loadCursorUpdateCheckPrefs(root) {
|
|
6437
7302
|
try {
|
|
6438
7303
|
const data = JSON.parse(
|
|
6439
|
-
await
|
|
7304
|
+
await readFile18(path29.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
6440
7305
|
);
|
|
6441
7306
|
const uc = data.cursorUpdateCheck;
|
|
6442
7307
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -6631,7 +7496,7 @@ async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
|
6631
7496
|
const root = path29.resolve(rootDir);
|
|
6632
7497
|
const handoffPath = path29.join(root, ".cursor", "HANDOFF.md");
|
|
6633
7498
|
const handoffFull = await readFull(handoffPath);
|
|
6634
|
-
const handoff =
|
|
7499
|
+
const handoff = capHandoffExcerpt(handoffFull);
|
|
6635
7500
|
const parts = [HARD_RULES];
|
|
6636
7501
|
if (await l0Present(root)) {
|
|
6637
7502
|
const readiness = await readinessSection(root);
|
|
@@ -6656,10 +7521,14 @@ ${bullet}
|
|
|
6656
7521
|
Rewrite machine lists as \`- **Field:**\` bullets before trusting Checklist / Current mission.`
|
|
6657
7522
|
);
|
|
6658
7523
|
}
|
|
6659
|
-
if (handoff) {
|
|
7524
|
+
if (handoff.text) {
|
|
7525
|
+
const capped = handoff.truncatedLines > 0 || handoff.droppedLines > 0;
|
|
7526
|
+
const note = capped ? `
|
|
7527
|
+
|
|
7528
|
+
_Excerpt capped at ${Math.round(HANDOFF_EXCERPT_MAX_BYTES / 1024)} KB (${handoff.truncatedLines} lines truncated, ${handoff.droppedLines} dropped); read \`.cursor/HANDOFF.md\` for the full entries._` : "";
|
|
6660
7529
|
parts.push(`## Current HANDOFF.md (excerpt)
|
|
6661
7530
|
|
|
6662
|
-
${handoff}`);
|
|
7531
|
+
${handoff.text}${note}`);
|
|
6663
7532
|
} else {
|
|
6664
7533
|
parts.push(
|
|
6665
7534
|
"## HANDOFF.md\n\nNo handoff file yet. If starting work, create a plan with to-dos first (`/start-project`)."
|
|
@@ -6743,8 +7612,12 @@ var hookCommand = defineCommand10({
|
|
|
6743
7612
|
import { intro, outro } from "@clack/prompts";
|
|
6744
7613
|
import { defineCommand as defineCommand12 } from "citty";
|
|
6745
7614
|
|
|
6746
|
-
// src/
|
|
6747
|
-
import {
|
|
7615
|
+
// src/lifecycle/path-cli.ts
|
|
7616
|
+
import { spawn as spawn6 } from "child_process";
|
|
7617
|
+
import path32 from "path";
|
|
7618
|
+
|
|
7619
|
+
// src/utils/terminal.ts
|
|
7620
|
+
import { readdir as readdir8 } from "fs/promises";
|
|
6748
7621
|
import { homedir as homedir3 } from "os";
|
|
6749
7622
|
import path31 from "path";
|
|
6750
7623
|
import { confirm, isCancel } from "@clack/prompts";
|
|
@@ -6758,7 +7631,7 @@ var NESTED_REPO_SCAN_LIMIT = 200;
|
|
|
6758
7631
|
async function findNestedRepoChildren(resolved) {
|
|
6759
7632
|
let entries;
|
|
6760
7633
|
try {
|
|
6761
|
-
entries = await
|
|
7634
|
+
entries = await readdir8(resolved, { withFileTypes: true });
|
|
6762
7635
|
} catch {
|
|
6763
7636
|
return [];
|
|
6764
7637
|
}
|
|
@@ -6928,18 +7801,122 @@ function classifyInstallError(err) {
|
|
|
6928
7801
|
};
|
|
6929
7802
|
}
|
|
6930
7803
|
|
|
7804
|
+
// src/lifecycle/path-cli.ts
|
|
7805
|
+
function npxPinned(runtimeVersion, subcommand) {
|
|
7806
|
+
return `npx -y ${pinnedCliSpec(runtimeVersion)} ${subcommand}`;
|
|
7807
|
+
}
|
|
7808
|
+
function isBinUnderPrefix(binPath, prefix) {
|
|
7809
|
+
const bin = path32.resolve(binPath);
|
|
7810
|
+
const root = path32.resolve(prefix);
|
|
7811
|
+
return bin === root || bin.startsWith(`${root}${path32.sep}`);
|
|
7812
|
+
}
|
|
7813
|
+
function pathCliStatus(env, runtimeVersion) {
|
|
7814
|
+
if (!env.binOnPath || !env.binPath) return "missing";
|
|
7815
|
+
const running = normalizeSemver(runtimeVersion);
|
|
7816
|
+
const pathVer = env.binVersion ? normalizeSemver(env.binVersion) : null;
|
|
7817
|
+
if (!pathVer || !running) return "unknown";
|
|
7818
|
+
try {
|
|
7819
|
+
return compareSemver(pathVer, running) >= 0 ? "current" : "behind";
|
|
7820
|
+
} catch {
|
|
7821
|
+
return "unknown";
|
|
7822
|
+
}
|
|
7823
|
+
}
|
|
7824
|
+
function driftLines(env, runtimeVersion) {
|
|
7825
|
+
const pathVer = env.binVersion ?? "unknown";
|
|
7826
|
+
const where = env.binPath ?? "(unresolved)";
|
|
7827
|
+
return [
|
|
7828
|
+
`This CLI is v${runtimeVersion}. PATH \`agent-kit\` is v${pathVer} at:`,
|
|
7829
|
+
` ${where}`,
|
|
7830
|
+
"Do not run bare `agent-kit update` or `init` from that binary. It re-stamps the old version.",
|
|
7831
|
+
"Keep using the pinned npx form until PATH matches:",
|
|
7832
|
+
` ${npxPinned(runtimeVersion, "update")}`,
|
|
7833
|
+
` ${npxPinned(runtimeVersion, "status")}`
|
|
7834
|
+
];
|
|
7835
|
+
}
|
|
7836
|
+
function isShadow(env) {
|
|
7837
|
+
if (!env.binPath || !env.npmPrefix.prefix) return false;
|
|
7838
|
+
return !isBinUnderPrefix(env.binPath, env.npmPrefix.prefix);
|
|
7839
|
+
}
|
|
7840
|
+
async function spawnNpmGlobalInstall(spec) {
|
|
7841
|
+
return new Promise((resolve3) => {
|
|
7842
|
+
const child = spawn6("npm", ["i", "-g", spec], { stdio: "inherit" });
|
|
7843
|
+
child.on("error", (error) => resolve3({ ok: false, error }));
|
|
7844
|
+
child.on("close", (code) => {
|
|
7845
|
+
if (code === 0) resolve3({ ok: true });
|
|
7846
|
+
else resolve3({ ok: false, error: new Error(`npm exited with code ${code ?? "unknown"}`) });
|
|
7847
|
+
});
|
|
7848
|
+
});
|
|
7849
|
+
}
|
|
7850
|
+
async function syncPathCliToRuntime(options = {}) {
|
|
7851
|
+
const runtimeVersion = options.runtimeVersion ?? KIT_VERSION;
|
|
7852
|
+
const assess = options.assessEnvironmentImpl ?? assessEnvironment;
|
|
7853
|
+
let env = options.env ?? await assess();
|
|
7854
|
+
const autoInstall = options.autoInstall ?? !isNonInteractive();
|
|
7855
|
+
const lines = [];
|
|
7856
|
+
let status = pathCliStatus(env, runtimeVersion);
|
|
7857
|
+
if (status === "current") {
|
|
7858
|
+
return { status, env, upgraded: false, lines };
|
|
7859
|
+
}
|
|
7860
|
+
if (status === "behind" || status === "unknown") {
|
|
7861
|
+
lines.push(...driftLines(env, runtimeVersion));
|
|
7862
|
+
if (isShadow(env)) {
|
|
7863
|
+
lines.push(
|
|
7864
|
+
"That PATH hit is not npm's global bin, so `npm i -g` may not replace it.",
|
|
7865
|
+
"Run `hash -r` and `which -a agent-kit`. Put npm's global bin first, or remove the stale shim."
|
|
7866
|
+
);
|
|
7867
|
+
}
|
|
7868
|
+
}
|
|
7869
|
+
if (!autoInstall) {
|
|
7870
|
+
return { status, env, upgraded: false, lines };
|
|
7871
|
+
}
|
|
7872
|
+
if (status === "missing") {
|
|
7873
|
+
lines.push("No bare `agent-kit` on PATH yet (npx is ephemeral).");
|
|
7874
|
+
}
|
|
7875
|
+
if (!env.npmPrefixWritable) {
|
|
7876
|
+
lines.push(
|
|
7877
|
+
"npm's global prefix is not writable, so this process cannot upgrade PATH.",
|
|
7878
|
+
`Run ${npxPinned(runtimeVersion, "setup-global")} (or fix prefix permissions).`
|
|
7879
|
+
);
|
|
7880
|
+
return { status, env, upgraded: false, lines };
|
|
7881
|
+
}
|
|
7882
|
+
const spec = pinnedCliSpec(runtimeVersion);
|
|
7883
|
+
const install = options.npmInstallImpl ?? spawnNpmGlobalInstall;
|
|
7884
|
+
lines.push(`Installing ${spec} into the writable npm prefix...`);
|
|
7885
|
+
const outcome = await install(spec);
|
|
7886
|
+
if (!outcome.ok) {
|
|
7887
|
+
const detail = outcome.error instanceof Error ? outcome.error.message : "npm i -g failed";
|
|
7888
|
+
lines.push(`Global install failed: ${detail}`);
|
|
7889
|
+
return { status, env, upgraded: false, lines };
|
|
7890
|
+
}
|
|
7891
|
+
env = await assess();
|
|
7892
|
+
status = pathCliStatus(env, runtimeVersion);
|
|
7893
|
+
if (status === "current") {
|
|
7894
|
+
lines.push(
|
|
7895
|
+
`PATH \`agent-kit\` is now v${env.binVersion ?? runtimeVersion}.`,
|
|
7896
|
+
"If this shell still shows the old version, run `hash -r` or open a new terminal."
|
|
7897
|
+
);
|
|
7898
|
+
return { status, env, upgraded: true, lines };
|
|
7899
|
+
}
|
|
7900
|
+
lines.push(
|
|
7901
|
+
`Installed ${spec}, but PATH still resolves to v${env.binVersion ?? "unknown"} at:`,
|
|
7902
|
+
` ${env.binPath ?? "(unresolved)"}`,
|
|
7903
|
+
"Run `hash -r` and `which -a agent-kit`. Until PATH matches, keep using the npx pin above."
|
|
7904
|
+
);
|
|
7905
|
+
return { status, env, upgraded: true, lines };
|
|
7906
|
+
}
|
|
7907
|
+
|
|
6931
7908
|
// src/commands/install.ts
|
|
6932
|
-
import
|
|
7909
|
+
import path39 from "path";
|
|
6933
7910
|
import { defineCommand as defineCommand11 } from "citty";
|
|
6934
7911
|
import { bold, cyan as cyan3, green as green2, options as koloristOptions2 } from "kolorist";
|
|
6935
7912
|
|
|
6936
7913
|
// src/generator/personalization.ts
|
|
6937
|
-
import { readFile as
|
|
6938
|
-
import
|
|
7914
|
+
import { readFile as readFile21, writeFile as writeFile12 } from "fs/promises";
|
|
7915
|
+
import path37 from "path";
|
|
6939
7916
|
|
|
6940
7917
|
// src/generator/claude-command-adapters.ts
|
|
6941
|
-
import { readFile as
|
|
6942
|
-
import
|
|
7918
|
+
import { readFile as readFile19, readdir as readdir9, writeFile as writeFile8 } from "fs/promises";
|
|
7919
|
+
import path33 from "path";
|
|
6943
7920
|
var CURSOR_COMMANDS_DIR_REL = ".cursor/commands";
|
|
6944
7921
|
var CLAUDE_COMMANDS_DIR_REL = ".claude/commands";
|
|
6945
7922
|
var RESERVED_ADAPTER_NAMES = /* @__PURE__ */ new Set(["agent-kit"]);
|
|
@@ -6961,17 +7938,17 @@ description: ${command.description}
|
|
|
6961
7938
|
Read \`.cursor/commands/${command.name}.md\` now and follow that contract exactly \u2014 it is the source of truth for /${command.name}; this file is only a thin adapter for Claude Code.
|
|
6962
7939
|
|
|
6963
7940
|
Adapter rules (Claude Code CLI):
|
|
6964
|
-
- Cursor "Ask questions" is unavailable here: use AskUserQuestion when possible, else
|
|
7941
|
+
- Cursor "Ask questions" is unavailable here: use AskUserQuestion when possible, else print one line \`HITL_GATE: <ask-id> | <label 1> | <label 2> | ...\` immediately followed by the same labels as one numbered list, one list per message, and WAIT for the answer (ask-id and labels: \`.cursor/skills/core/hitl-gates/SKILL.md\`). A headless reply arrives as \`HITL_REPLY: <ask-id> | operator reply <n> | <label>\`; cite that line as the Ask provenance.
|
|
6965
7942
|
- Skip or cancel means stop.
|
|
6966
7943
|
- Never \`/git-prod\` without an explicit operator yes.
|
|
6967
7944
|
- Do not clone Cursor hooks or invent behavior beyond the SoT file.
|
|
6968
7945
|
`;
|
|
6969
7946
|
}
|
|
6970
7947
|
async function discoverInstalledCommands(rootDir) {
|
|
6971
|
-
const dir =
|
|
7948
|
+
const dir = path33.join(rootDir, CURSOR_COMMANDS_DIR_REL);
|
|
6972
7949
|
let entries;
|
|
6973
7950
|
try {
|
|
6974
|
-
entries = (await
|
|
7951
|
+
entries = (await readdir9(dir)).filter((f) => f.endsWith(".md"));
|
|
6975
7952
|
} catch {
|
|
6976
7953
|
return [];
|
|
6977
7954
|
}
|
|
@@ -6980,7 +7957,7 @@ async function discoverInstalledCommands(rootDir) {
|
|
|
6980
7957
|
const name = file.slice(0, -3);
|
|
6981
7958
|
if (RESERVED_ADAPTER_NAMES.has(name)) continue;
|
|
6982
7959
|
try {
|
|
6983
|
-
const raw = await
|
|
7960
|
+
const raw = await readFile19(path33.join(dir, file), "utf8");
|
|
6984
7961
|
const parsed = parseCommandFrontmatter(name, raw);
|
|
6985
7962
|
if (parsed) commands.push(parsed);
|
|
6986
7963
|
} catch {
|
|
@@ -6995,18 +7972,18 @@ async function generateClaudeCommandAdapters(rootDir) {
|
|
|
6995
7972
|
const results = [];
|
|
6996
7973
|
let ledgerDirty = false;
|
|
6997
7974
|
for (const command of commands) {
|
|
6998
|
-
const relPath =
|
|
7975
|
+
const relPath = path33.posix.join(CLAUDE_COMMANDS_DIR_REL, `${command.name}.md`);
|
|
6999
7976
|
const rendered = renderClaudeCommandAdapter(command);
|
|
7000
|
-
const abs =
|
|
7977
|
+
const abs = path33.join(rootDir, relPath);
|
|
7001
7978
|
if (!await fileExists(abs)) {
|
|
7002
|
-
await ensureDir(
|
|
7979
|
+
await ensureDir(path33.dirname(abs));
|
|
7003
7980
|
await writeFile8(abs, rendered, "utf8");
|
|
7004
7981
|
ledger.hashes[relPath] = contentHash(rendered);
|
|
7005
7982
|
ledgerDirty = true;
|
|
7006
7983
|
results.push({ relativePath: relPath, status: "applied" });
|
|
7007
7984
|
continue;
|
|
7008
7985
|
}
|
|
7009
|
-
const localContent = await
|
|
7986
|
+
const localContent = await readFile19(abs, "utf8");
|
|
7010
7987
|
if (localContent === rendered) {
|
|
7011
7988
|
if (ledger.hashes[relPath] !== contentHash(rendered)) {
|
|
7012
7989
|
ledger.hashes[relPath] = contentHash(rendered);
|
|
@@ -7030,7 +8007,7 @@ async function generateClaudeCommandAdapters(rootDir) {
|
|
|
7030
8007
|
|
|
7031
8008
|
// src/generator/claude-kit-load.ts
|
|
7032
8009
|
import { writeFile as writeFile9 } from "fs/promises";
|
|
7033
|
-
import
|
|
8010
|
+
import path34 from "path";
|
|
7034
8011
|
var CLAUDE_MD_REL = "CLAUDE.md";
|
|
7035
8012
|
var AGENT_KIT_COMMAND_REL = ".claude/commands/agent-kit.md";
|
|
7036
8013
|
function renderClaudeMd() {
|
|
@@ -7086,11 +8063,11 @@ Non-goals: not audits / \`/plan-external-review\`, not a second tick dialect (\`
|
|
|
7086
8063
|
`;
|
|
7087
8064
|
}
|
|
7088
8065
|
async function writeUnlessExists(rootDir, relativePath, content) {
|
|
7089
|
-
const target =
|
|
8066
|
+
const target = path34.join(rootDir, relativePath);
|
|
7090
8067
|
if (await fileExists(target)) {
|
|
7091
8068
|
return { relativePath, status: "skipped-customized" };
|
|
7092
8069
|
}
|
|
7093
|
-
await ensureDir(
|
|
8070
|
+
await ensureDir(path34.dirname(target));
|
|
7094
8071
|
await writeFile9(target, content, "utf8");
|
|
7095
8072
|
return { relativePath, status: "applied" };
|
|
7096
8073
|
}
|
|
@@ -7102,8 +8079,8 @@ async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
|
7102
8079
|
}
|
|
7103
8080
|
|
|
7104
8081
|
// src/generator/claude-session-start-hook.ts
|
|
7105
|
-
import { readFile as
|
|
7106
|
-
import
|
|
8082
|
+
import { readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
|
|
8083
|
+
import path35 from "path";
|
|
7107
8084
|
var CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
7108
8085
|
var RESOLVE_AGENT_KIT_REL = ".cursor/hooks/agent/resolve-agent-kit.sh";
|
|
7109
8086
|
var SESSION_START_HOOK_MARKER = "hook session-start --format claude";
|
|
@@ -7178,10 +8155,10 @@ function instructionsBlock(entry) {
|
|
|
7178
8155
|
].join("\n");
|
|
7179
8156
|
}
|
|
7180
8157
|
async function writeClaudeSessionStartHook(rootDir) {
|
|
7181
|
-
const abs =
|
|
8158
|
+
const abs = path35.join(rootDir, CLAUDE_SETTINGS_REL);
|
|
7182
8159
|
let existing = null;
|
|
7183
8160
|
try {
|
|
7184
|
-
existing = await
|
|
8161
|
+
existing = await readFile20(abs, "utf8");
|
|
7185
8162
|
} catch {
|
|
7186
8163
|
existing = null;
|
|
7187
8164
|
}
|
|
@@ -7196,14 +8173,14 @@ async function writeClaudeSessionStartHook(rootDir) {
|
|
|
7196
8173
|
if (merged.status === "unchanged") {
|
|
7197
8174
|
return { relativePath: CLAUDE_SETTINGS_REL, status: "unchanged" };
|
|
7198
8175
|
}
|
|
7199
|
-
await ensureDir(
|
|
8176
|
+
await ensureDir(path35.dirname(abs));
|
|
7200
8177
|
await writeFile10(abs, merged.content, "utf8");
|
|
7201
8178
|
return { relativePath: CLAUDE_SETTINGS_REL, status: merged.status };
|
|
7202
8179
|
}
|
|
7203
8180
|
|
|
7204
8181
|
// src/generator/vscode.ts
|
|
7205
8182
|
import { writeFile as writeFile11 } from "fs/promises";
|
|
7206
|
-
import
|
|
8183
|
+
import path36 from "path";
|
|
7207
8184
|
|
|
7208
8185
|
// src/generator/platform.ts
|
|
7209
8186
|
function gitProviderLabel(profile) {
|
|
@@ -7227,10 +8204,10 @@ function prTerminology(profile) {
|
|
|
7227
8204
|
// src/generator/vscode.ts
|
|
7228
8205
|
async function generateVSCodeArtifacts(profile) {
|
|
7229
8206
|
const results = [];
|
|
7230
|
-
const vscodeDir =
|
|
7231
|
-
const githubDir =
|
|
8207
|
+
const vscodeDir = path36.join(profile.rootDir, ".vscode");
|
|
8208
|
+
const githubDir = path36.join(profile.rootDir, ".github");
|
|
7232
8209
|
await Promise.all([ensureDir(vscodeDir), ensureDir(githubDir)]);
|
|
7233
|
-
const settingsPath =
|
|
8210
|
+
const settingsPath = path36.join(vscodeDir, "settings.json");
|
|
7234
8211
|
if (await fileExists(settingsPath)) {
|
|
7235
8212
|
results.push({ relativePath: ".vscode/settings.json", status: "skipped-customized" });
|
|
7236
8213
|
} else {
|
|
@@ -7254,7 +8231,7 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
7254
8231
|
}
|
|
7255
8232
|
const provider = gitProviderLabel(profile);
|
|
7256
8233
|
const prTerm = prTerminology(profile);
|
|
7257
|
-
const copilotPath =
|
|
8234
|
+
const copilotPath = path36.join(githubDir, "copilot-instructions.md");
|
|
7258
8235
|
if (await fileExists(copilotPath)) {
|
|
7259
8236
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "skipped-customized" });
|
|
7260
8237
|
} else {
|
|
@@ -7272,7 +8249,7 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
7272
8249
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "applied" });
|
|
7273
8250
|
}
|
|
7274
8251
|
if (profile.ide.plan === "vscode-pro") {
|
|
7275
|
-
const securityPath =
|
|
8252
|
+
const securityPath = path36.join(vscodeDir, "security-review.agent.md");
|
|
7276
8253
|
if (await fileExists(securityPath)) {
|
|
7277
8254
|
results.push({
|
|
7278
8255
|
relativePath: ".vscode/security-review.agent.md",
|
|
@@ -7484,7 +8461,7 @@ function renderProjectContext(profile, skillItems = []) {
|
|
|
7484
8461
|
`;
|
|
7485
8462
|
}
|
|
7486
8463
|
async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
7487
|
-
const target =
|
|
8464
|
+
const target = path37.join(rootDir, relativePath);
|
|
7488
8465
|
if (await fileExists(target)) {
|
|
7489
8466
|
return {
|
|
7490
8467
|
kind: "file",
|
|
@@ -7494,7 +8471,7 @@ async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
|
7494
8471
|
evidence
|
|
7495
8472
|
};
|
|
7496
8473
|
}
|
|
7497
|
-
await ensureDir(
|
|
8474
|
+
await ensureDir(path37.dirname(target));
|
|
7498
8475
|
await writeFile12(target, content, "utf8");
|
|
7499
8476
|
return {
|
|
7500
8477
|
kind: "file",
|
|
@@ -7511,7 +8488,7 @@ async function packTargets(registryRoot, packId) {
|
|
|
7511
8488
|
async function existingTargets(projectRoot, targets) {
|
|
7512
8489
|
const checks = await Promise.all(
|
|
7513
8490
|
targets.map(
|
|
7514
|
-
async (target) => await fileExists(
|
|
8491
|
+
async (target) => await fileExists(path37.join(projectRoot, target)) ? target : null
|
|
7515
8492
|
)
|
|
7516
8493
|
);
|
|
7517
8494
|
return checks.filter((target) => target !== null);
|
|
@@ -7533,14 +8510,14 @@ async function applyPersonalization(input) {
|
|
|
7533
8510
|
componentResults.push({ ...item, status: "unavailable" });
|
|
7534
8511
|
continue;
|
|
7535
8512
|
}
|
|
7536
|
-
const target =
|
|
8513
|
+
const target = path37.posix.join(
|
|
7537
8514
|
".cursor",
|
|
7538
8515
|
"skills",
|
|
7539
8516
|
skill.path.includes("/core/") ? "core" : "community",
|
|
7540
8517
|
skill.id,
|
|
7541
8518
|
"SKILL.md"
|
|
7542
8519
|
);
|
|
7543
|
-
if (await fileExists(
|
|
8520
|
+
if (await fileExists(path37.join(input.rootDir, target))) {
|
|
7544
8521
|
componentResults.push({ ...item, status: "skipped-customized", path: target });
|
|
7545
8522
|
protectedPaths.add(target);
|
|
7546
8523
|
continue;
|
|
@@ -7685,7 +8662,7 @@ async function applyPersonalization(input) {
|
|
|
7685
8662
|
protectedPaths: [...protectedPaths].sort(),
|
|
7686
8663
|
...claudeSessionStartInstructions ? { claudeSessionStartInstructions } : {}
|
|
7687
8664
|
};
|
|
7688
|
-
await writeJson(
|
|
8665
|
+
await writeJson(path37.join(input.rootDir, RESULT_PATH), result);
|
|
7689
8666
|
return {
|
|
7690
8667
|
result,
|
|
7691
8668
|
manifest: {
|
|
@@ -7703,26 +8680,26 @@ async function applyPersonalization(input) {
|
|
|
7703
8680
|
};
|
|
7704
8681
|
}
|
|
7705
8682
|
async function readRepositoryProfile(rootDir) {
|
|
7706
|
-
const target =
|
|
8683
|
+
const target = path37.join(rootDir, ".cursor/agent-kit.config.json");
|
|
7707
8684
|
if (!await fileExists(target)) return null;
|
|
7708
|
-
return JSON.parse(await
|
|
8685
|
+
return JSON.parse(await readFile21(target, "utf8"));
|
|
7709
8686
|
}
|
|
7710
8687
|
|
|
7711
8688
|
// src/lifecycle/onboard-migration.ts
|
|
7712
8689
|
import { createHash as createHash4 } from "crypto";
|
|
7713
|
-
import { readFile as
|
|
7714
|
-
import
|
|
8690
|
+
import { readFile as readFile22, unlink } from "fs/promises";
|
|
8691
|
+
import path38 from "path";
|
|
7715
8692
|
var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
|
|
7716
8693
|
var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
|
|
7717
8694
|
var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
|
|
7718
8695
|
"b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
|
|
7719
8696
|
]);
|
|
7720
8697
|
async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
|
|
7721
|
-
const legacyPath =
|
|
8698
|
+
const legacyPath = path38.join(projectRoot, LEGACY_ONBOARD_PATH);
|
|
7722
8699
|
if (!await fileExists(legacyPath)) return "absent";
|
|
7723
|
-
const namespacedPath =
|
|
8700
|
+
const namespacedPath = path38.join(projectRoot, NAMESPACED_ONBOARD_PATH);
|
|
7724
8701
|
if (!await fileExists(namespacedPath)) return "preserved-customized";
|
|
7725
|
-
const content = await
|
|
8702
|
+
const content = await readFile22(legacyPath);
|
|
7726
8703
|
const hash = createHash4("sha256").update(content).digest("hex");
|
|
7727
8704
|
if (!managedHashes.has(hash)) return "preserved-customized";
|
|
7728
8705
|
await unlink(legacyPath);
|
|
@@ -7810,8 +8787,15 @@ function paint(fn, text) {
|
|
|
7810
8787
|
function printInstallEpilogue(env, options = {}) {
|
|
7811
8788
|
const print = options.print ?? ((line) => console.log(line));
|
|
7812
8789
|
const color = options.color ?? shouldUseWelcomeColor();
|
|
7813
|
-
|
|
7814
|
-
|
|
8790
|
+
const runtimeVersion = options.runtimeVersion ?? KIT_VERSION;
|
|
8791
|
+
const status = pathCliStatus(env, runtimeVersion);
|
|
8792
|
+
if (status === "current") {
|
|
8793
|
+
const line = `PATH \`agent-kit\` is v${env.binVersion ?? runtimeVersion}. Run it directly, e.g. \`agent-kit doctor\`.`;
|
|
8794
|
+
print(color ? paint(green2, line) : line);
|
|
8795
|
+
return;
|
|
8796
|
+
}
|
|
8797
|
+
if (status === "behind" || status === "unknown") {
|
|
8798
|
+
const line = `Keep using ${npxPinned(runtimeVersion, "<subcommand>")} until PATH is v${runtimeVersion}.`;
|
|
7815
8799
|
print(color ? paint(green2, line) : line);
|
|
7816
8800
|
return;
|
|
7817
8801
|
}
|
|
@@ -7821,11 +8805,11 @@ function printInstallEpilogue(env, options = {}) {
|
|
|
7821
8805
|
'If you try `agent-kit <subcommand>` next, you will see "command not',
|
|
7822
8806
|
'found". Pick one:',
|
|
7823
8807
|
"",
|
|
7824
|
-
" 1. Keep using npx
|
|
7825
|
-
|
|
8808
|
+
" 1. Keep using npx (works right now, no action needed)",
|
|
8809
|
+
` ${npxPinned(runtimeVersion, "<subcommand>")}`,
|
|
7826
8810
|
"",
|
|
7827
8811
|
" 2. Put a bare `agent-kit` on PATH",
|
|
7828
|
-
|
|
8812
|
+
` ${npxPinned(runtimeVersion, "setup-global")}`,
|
|
7829
8813
|
" (fixes a root-owned npm prefix if that's the blocker, or just installs)",
|
|
7830
8814
|
"",
|
|
7831
8815
|
" 3. Manual steps",
|
|
@@ -7833,7 +8817,7 @@ function printInstallEpilogue(env, options = {}) {
|
|
|
7833
8817
|
" mkdir -p ~/.npm-global",
|
|
7834
8818
|
' npm config set prefix "~/.npm-global"',
|
|
7835
8819
|
' export PATH="~/.npm-global/bin:$PATH"',
|
|
7836
|
-
|
|
8820
|
+
` npm i -g ${pinnedCliSpec(runtimeVersion)}`
|
|
7837
8821
|
];
|
|
7838
8822
|
print(color ? paint(cyan3, divider) : divider);
|
|
7839
8823
|
const heading = "Heads up: a bare `agent-kit` command won't work yet";
|
|
@@ -7844,10 +8828,12 @@ function printInstallEpilogue(env, options = {}) {
|
|
|
7844
8828
|
async function printPostInstallSummary(result) {
|
|
7845
8829
|
printReadinessNarrative(result);
|
|
7846
8830
|
const env = await assessEnvironment();
|
|
7847
|
-
|
|
8831
|
+
const sync = await syncPathCliToRuntime({ runtimeVersion: KIT_VERSION, env });
|
|
8832
|
+
for (const line of sync.lines) console.log(line);
|
|
8833
|
+
printInstallEpilogue(sync.env, { runtimeVersion: KIT_VERSION });
|
|
7848
8834
|
}
|
|
7849
8835
|
async function performInstall(options) {
|
|
7850
|
-
const projectRoot =
|
|
8836
|
+
const projectRoot = path39.resolve(options.cwd);
|
|
7851
8837
|
const packs = parsePackList(options.pack);
|
|
7852
8838
|
const existing = await loadAgentKitManifest(projectRoot);
|
|
7853
8839
|
const registry = await resolveRegistryFromCli({
|
|
@@ -7970,6 +8956,7 @@ ${err.recovery}
|
|
|
7970
8956
|
throw err;
|
|
7971
8957
|
}
|
|
7972
8958
|
logger.info(`Installing into: ${projectRoot}`);
|
|
8959
|
+
logger.info(`CLI v${KIT_VERSION}`);
|
|
7973
8960
|
await warnIfRunningCliBehindNpm(projectRoot, { warn: (message) => logger.warn(message) });
|
|
7974
8961
|
const packs = parsePackList(args.pack);
|
|
7975
8962
|
for (const id of packs) {
|
|
@@ -8072,12 +9059,13 @@ ${err.recovery}
|
|
|
8072
9059
|
logger.success(`L0 and readiness prepared in ${result.projectRoot}`);
|
|
8073
9060
|
const nextStep = nextStepAfterInstall(pending);
|
|
8074
9061
|
const env = await assessEnvironment();
|
|
9062
|
+
const sync = await syncPathCliToRuntime({ runtimeVersion: KIT_VERSION, env });
|
|
9063
|
+
for (const line of sync.lines) console.log(line);
|
|
9064
|
+
printInstallEpilogue(sync.env, { runtimeVersion: KIT_VERSION });
|
|
8075
9065
|
if (!nonInteractive) {
|
|
8076
|
-
printInstallEpilogue(env);
|
|
8077
9066
|
outro(nextStep);
|
|
8078
9067
|
} else {
|
|
8079
9068
|
logger.info(nextStep);
|
|
8080
|
-
printInstallEpilogue(env);
|
|
8081
9069
|
}
|
|
8082
9070
|
} catch (err) {
|
|
8083
9071
|
const hint = classifyInstallError(err);
|
|
@@ -8246,11 +9234,333 @@ function shouldLiveRefresh(opts) {
|
|
|
8246
9234
|
const tty = opts.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
|
|
8247
9235
|
return tty;
|
|
8248
9236
|
}
|
|
9237
|
+
function renderProgressBar(opts) {
|
|
9238
|
+
const width = Math.max(4, Math.floor(opts.width ?? 20));
|
|
9239
|
+
const total = Math.max(0, Math.floor(opts.total));
|
|
9240
|
+
const done = Math.min(total, Math.max(0, Math.floor(opts.done)));
|
|
9241
|
+
const filled = total === 0 ? 0 : Math.round(done / total * width);
|
|
9242
|
+
const color = opts.color ?? false;
|
|
9243
|
+
const bar = `${hexPaint(HELMET_ACCENT, "\u2588".repeat(filled), color)}${hexPaint(
|
|
9244
|
+
LABEL_MUTED,
|
|
9245
|
+
"\u2591".repeat(width - filled),
|
|
9246
|
+
color
|
|
9247
|
+
)}`;
|
|
9248
|
+
return `${hexPaint(HELMET_OUTLINE, "[", color)}${bar}${hexPaint(HELMET_OUTLINE, "]", color)} ${hexPaint(
|
|
9249
|
+
HELMET_OUTLINE,
|
|
9250
|
+
`${done}/${total}`,
|
|
9251
|
+
color
|
|
9252
|
+
)}`;
|
|
9253
|
+
}
|
|
9254
|
+
var ScrollRegion = class {
|
|
9255
|
+
lines = [];
|
|
9256
|
+
offset = 0;
|
|
9257
|
+
capacity;
|
|
9258
|
+
constructor(capacity = 500) {
|
|
9259
|
+
this.capacity = Math.max(1, Math.floor(capacity));
|
|
9260
|
+
}
|
|
9261
|
+
get length() {
|
|
9262
|
+
return this.lines.length;
|
|
9263
|
+
}
|
|
9264
|
+
get scrolledUp() {
|
|
9265
|
+
return this.offset;
|
|
9266
|
+
}
|
|
9267
|
+
push(line) {
|
|
9268
|
+
this.lines.push(line);
|
|
9269
|
+
if (this.lines.length > this.capacity) {
|
|
9270
|
+
this.lines.splice(0, this.lines.length - this.capacity);
|
|
9271
|
+
}
|
|
9272
|
+
if (this.offset > 0) this.offset = Math.min(this.offset, Math.max(0, this.lines.length - 1));
|
|
9273
|
+
}
|
|
9274
|
+
scrollUp(n = 1) {
|
|
9275
|
+
this.offset = Math.min(Math.max(0, this.lines.length - 1), this.offset + Math.max(0, n));
|
|
9276
|
+
}
|
|
9277
|
+
scrollDown(n = 1) {
|
|
9278
|
+
this.offset = Math.max(0, this.offset - Math.max(0, n));
|
|
9279
|
+
}
|
|
9280
|
+
scrollToBottom() {
|
|
9281
|
+
this.offset = 0;
|
|
9282
|
+
}
|
|
9283
|
+
/** The last `height` lines (minus the scroll offset). */
|
|
9284
|
+
viewport(height) {
|
|
9285
|
+
const h = Math.max(0, Math.floor(height));
|
|
9286
|
+
const end = Math.max(0, this.lines.length - this.offset);
|
|
9287
|
+
const start = Math.max(0, end - h);
|
|
9288
|
+
return { lines: this.lines.slice(start, end), above: start, below: this.lines.length - end };
|
|
9289
|
+
}
|
|
9290
|
+
all() {
|
|
9291
|
+
return this.lines;
|
|
9292
|
+
}
|
|
9293
|
+
};
|
|
9294
|
+
var KEY_LEFT = `${ESC}[D`;
|
|
9295
|
+
var KEY_RIGHT = `${ESC}[C`;
|
|
9296
|
+
var KEY_HOME_SET = /* @__PURE__ */ new Set([`${ESC}[H`, `${ESC}[1~`, `${ESC}OH`]);
|
|
9297
|
+
var KEY_END_SET = /* @__PURE__ */ new Set([`${ESC}[F`, `${ESC}[4~`, `${ESC}OF`]);
|
|
9298
|
+
var KEY_DELETE = `${ESC}[3~`;
|
|
9299
|
+
function splitKeys(chunk) {
|
|
9300
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
9301
|
+
const keys = [];
|
|
9302
|
+
let i = 0;
|
|
9303
|
+
while (i < text.length) {
|
|
9304
|
+
const ch = text[i] ?? "";
|
|
9305
|
+
if (ch === ESC && i + 1 < text.length) {
|
|
9306
|
+
const next = text[i + 1];
|
|
9307
|
+
if (next === "[" || next === "O") {
|
|
9308
|
+
let j = i + 2;
|
|
9309
|
+
while (j < text.length && !/[A-Za-z~]/.test(text[j] ?? "")) j += 1;
|
|
9310
|
+
keys.push(text.slice(i, Math.min(text.length, j + 1)));
|
|
9311
|
+
i = j + 1;
|
|
9312
|
+
continue;
|
|
9313
|
+
}
|
|
9314
|
+
}
|
|
9315
|
+
keys.push(ch);
|
|
9316
|
+
i += 1;
|
|
9317
|
+
}
|
|
9318
|
+
return keys;
|
|
9319
|
+
}
|
|
9320
|
+
var InputLine = class {
|
|
9321
|
+
buffer = "";
|
|
9322
|
+
cursorPos = 0;
|
|
9323
|
+
get value() {
|
|
9324
|
+
return this.buffer;
|
|
9325
|
+
}
|
|
9326
|
+
get cursor() {
|
|
9327
|
+
return this.cursorPos;
|
|
9328
|
+
}
|
|
9329
|
+
reset() {
|
|
9330
|
+
this.buffer = "";
|
|
9331
|
+
this.cursorPos = 0;
|
|
9332
|
+
}
|
|
9333
|
+
handleKey(chunk) {
|
|
9334
|
+
let result = "ignored";
|
|
9335
|
+
for (const key of splitKeys(chunk)) {
|
|
9336
|
+
const r = this.handleOne(key);
|
|
9337
|
+
if (r === "submit" || r === "sigint" || r === "eof") return r;
|
|
9338
|
+
if (r === "edit") result = "edit";
|
|
9339
|
+
}
|
|
9340
|
+
return result;
|
|
9341
|
+
}
|
|
9342
|
+
handleOne(key) {
|
|
9343
|
+
if (key === "\r" || key === "\n") return "submit";
|
|
9344
|
+
if (key === "") return "sigint";
|
|
9345
|
+
if (key === "") return this.buffer.length === 0 ? "eof" : "ignored";
|
|
9346
|
+
if (key === "\x7F" || key === "\b") {
|
|
9347
|
+
if (this.cursorPos === 0) return "ignored";
|
|
9348
|
+
this.buffer = this.buffer.slice(0, this.cursorPos - 1) + this.buffer.slice(this.cursorPos);
|
|
9349
|
+
this.cursorPos -= 1;
|
|
9350
|
+
return "edit";
|
|
9351
|
+
}
|
|
9352
|
+
if (key === KEY_DELETE) {
|
|
9353
|
+
if (this.cursorPos >= this.buffer.length) return "ignored";
|
|
9354
|
+
this.buffer = this.buffer.slice(0, this.cursorPos) + this.buffer.slice(this.cursorPos + 1);
|
|
9355
|
+
return "edit";
|
|
9356
|
+
}
|
|
9357
|
+
if (key === KEY_LEFT) {
|
|
9358
|
+
if (this.cursorPos === 0) return "ignored";
|
|
9359
|
+
this.cursorPos -= 1;
|
|
9360
|
+
return "edit";
|
|
9361
|
+
}
|
|
9362
|
+
if (key === KEY_RIGHT) {
|
|
9363
|
+
if (this.cursorPos >= this.buffer.length) return "ignored";
|
|
9364
|
+
this.cursorPos += 1;
|
|
9365
|
+
return "edit";
|
|
9366
|
+
}
|
|
9367
|
+
if (KEY_HOME_SET.has(key) || key === "") {
|
|
9368
|
+
this.cursorPos = 0;
|
|
9369
|
+
return "edit";
|
|
9370
|
+
}
|
|
9371
|
+
if (KEY_END_SET.has(key) || key === "") {
|
|
9372
|
+
this.cursorPos = this.buffer.length;
|
|
9373
|
+
return "edit";
|
|
9374
|
+
}
|
|
9375
|
+
if (key === "") {
|
|
9376
|
+
if (this.buffer.length === 0) return "ignored";
|
|
9377
|
+
this.reset();
|
|
9378
|
+
return "edit";
|
|
9379
|
+
}
|
|
9380
|
+
if (key === "") {
|
|
9381
|
+
if (this.cursorPos === 0) return "ignored";
|
|
9382
|
+
const head = this.buffer.slice(0, this.cursorPos).replace(/\S*\s*$/, "");
|
|
9383
|
+
this.buffer = head + this.buffer.slice(this.cursorPos);
|
|
9384
|
+
this.cursorPos = head.length;
|
|
9385
|
+
return "edit";
|
|
9386
|
+
}
|
|
9387
|
+
if (key.startsWith(ESC)) return "ignored";
|
|
9388
|
+
const code = key.codePointAt(0) ?? 0;
|
|
9389
|
+
if (code < 32 || code === 127) return "ignored";
|
|
9390
|
+
this.buffer = this.buffer.slice(0, this.cursorPos) + key + this.buffer.slice(this.cursorPos);
|
|
9391
|
+
this.cursorPos += key.length;
|
|
9392
|
+
return "edit";
|
|
9393
|
+
}
|
|
9394
|
+
/** `> text` with the cursor cell marked by reverse video (plain: `_` at the end). */
|
|
9395
|
+
render(width, color, prompt = "> ") {
|
|
9396
|
+
const max = Math.max(4, width - prompt.length - 1);
|
|
9397
|
+
let start = 0;
|
|
9398
|
+
if (this.cursorPos >= max) start = this.cursorPos - max + 1;
|
|
9399
|
+
const visible = this.buffer.slice(start, start + max);
|
|
9400
|
+
const at = this.cursorPos - start;
|
|
9401
|
+
const before = visible.slice(0, at);
|
|
9402
|
+
const cell = visible.slice(at, at + 1) || " ";
|
|
9403
|
+
const after = visible.slice(at + 1);
|
|
9404
|
+
const painted = color ? `${hexPaint(HELMET_OUTLINE, before, true)}${ESC}[7m${cell}${ESC}[0m${hexPaint(HELMET_OUTLINE, after, true)}` : `${before}${cell === " " && after === "" ? "_" : cell}${after}`;
|
|
9405
|
+
return `${hexPaint(HELMET_ACCENT, prompt, color)}${painted}`;
|
|
9406
|
+
}
|
|
9407
|
+
};
|
|
9408
|
+
var LIVE_MIN_LOG_ROWS = 3;
|
|
9409
|
+
var LIVE_MAX_LOG_ROWS = 40;
|
|
9410
|
+
var LIVE_CHECKLIST_CAP = 4;
|
|
9411
|
+
function formatElapsed(ms) {
|
|
9412
|
+
const s = Math.max(0, Math.floor(ms / 1e3));
|
|
9413
|
+
const m = Math.floor(s / 60);
|
|
9414
|
+
const h = Math.floor(m / 60);
|
|
9415
|
+
if (h > 0) return `${h}h ${m % 60}m`;
|
|
9416
|
+
if (m > 0) return `${m}m ${s % 60}s`;
|
|
9417
|
+
return `${s}s`;
|
|
9418
|
+
}
|
|
9419
|
+
function liveLineHex(kind) {
|
|
9420
|
+
switch (kind) {
|
|
9421
|
+
case "tool":
|
|
9422
|
+
return HELMET_ACCENT;
|
|
9423
|
+
case "result":
|
|
9424
|
+
return STATUS_OK;
|
|
9425
|
+
case "error":
|
|
9426
|
+
return STATUS_ERR;
|
|
9427
|
+
case "rate_limit":
|
|
9428
|
+
return STATUS_WARN;
|
|
9429
|
+
case "hitl":
|
|
9430
|
+
return HELMET_ACCENT;
|
|
9431
|
+
case "hook":
|
|
9432
|
+
case "prompt":
|
|
9433
|
+
case "note":
|
|
9434
|
+
return LABEL_MUTED;
|
|
9435
|
+
default:
|
|
9436
|
+
return HELMET_OUTLINE;
|
|
9437
|
+
}
|
|
9438
|
+
}
|
|
9439
|
+
function renderMcLive(view, opts = {}) {
|
|
9440
|
+
const color = shouldUseWelcomeColor(opts);
|
|
9441
|
+
const motion = shouldUseVisualMotion(opts);
|
|
9442
|
+
const columns = Math.max(
|
|
9443
|
+
MIN_COLUMNS,
|
|
9444
|
+
Math.floor(opts.columns ?? process.stdout.columns ?? DEFAULT_COLUMNS)
|
|
9445
|
+
);
|
|
9446
|
+
const rows = Math.max(12, Math.floor(opts.rows ?? process.stdout.rows ?? 24));
|
|
9447
|
+
const inner = columns - 2 - INNER_PAD;
|
|
9448
|
+
const now2 = opts.now ?? Date.now();
|
|
9449
|
+
const running = view.crew.phase === "running" || view.crew.phase === "gate";
|
|
9450
|
+
const mark = motion && running ? spinnerFrame(opts.frameIndex ?? 0) : SPACE_MARKS.tick;
|
|
9451
|
+
const header = hexPaint(
|
|
9452
|
+
HELMET_ACCENT,
|
|
9453
|
+
`${mark} Mission Control ${SPACE_MARKS.star} live run ${SPACE_MARKS.tick} ${view.crew.backend ?? "agent"} ${SPACE_MARKS.tick} ${view.crew.phase}`,
|
|
9454
|
+
color
|
|
9455
|
+
);
|
|
9456
|
+
const barWidth = Math.max(8, Math.min(24, Math.floor(inner / 3)));
|
|
9457
|
+
const missionBody = [];
|
|
9458
|
+
if (view.error) {
|
|
9459
|
+
missionBody.push(
|
|
9460
|
+
hexPaint(STATUS_ERR, wrapNarrow(view.error, inner).split("\n")[0] ?? "", color)
|
|
9461
|
+
);
|
|
9462
|
+
} else {
|
|
9463
|
+
const m = view.mission;
|
|
9464
|
+
missionBody.push(
|
|
9465
|
+
`${labeled("plan ", lineOrQuiet(m.planFile, "none"), color)}${m.mode ? hexPaint(LABEL_MUTED, ` \xB7 ${m.mode}`, color) : ""}`
|
|
9466
|
+
);
|
|
9467
|
+
const todoTail = m.currentTodo ? ` now ${m.currentTodo}` : "";
|
|
9468
|
+
missionBody.push(
|
|
9469
|
+
`${hexPaint(LABEL_MUTED, "to-dos ", color)}${renderProgressBar({
|
|
9470
|
+
done: m.todos.done,
|
|
9471
|
+
total: m.todos.total,
|
|
9472
|
+
width: barWidth,
|
|
9473
|
+
color
|
|
9474
|
+
})}${hexPaint(HELMET_OUTLINE, todoTail, color)}`
|
|
9475
|
+
);
|
|
9476
|
+
if (m.queue) {
|
|
9477
|
+
const tail = m.queue.status ? ` ${m.queue.status}` : "";
|
|
9478
|
+
missionBody.push(
|
|
9479
|
+
`${hexPaint(LABEL_MUTED, "plans ", color)}${renderProgressBar({
|
|
9480
|
+
done: m.queue.done,
|
|
9481
|
+
total: m.queue.total,
|
|
9482
|
+
width: barWidth,
|
|
9483
|
+
color
|
|
9484
|
+
})}${hexPaint(HELMET_OUTLINE, tail, color)}`
|
|
9485
|
+
);
|
|
9486
|
+
}
|
|
9487
|
+
if (m.nextTodo) missionBody.push(labeled("next ", m.nextTodo, color));
|
|
9488
|
+
}
|
|
9489
|
+
if (view.gate) {
|
|
9490
|
+
const g = view.gate;
|
|
9491
|
+
const head = g.detection === "fallback" ? `${g.askId} (prose fallback)` : g.askId;
|
|
9492
|
+
missionBody.push(
|
|
9493
|
+
`${hexPaint(LABEL_MUTED, "gate ", color)}${hexPaint(STATUS_WARN, head, color)}${hexPaint(
|
|
9494
|
+
LABEL_MUTED,
|
|
9495
|
+
" \xB7 number or label replies; empty, skip, cancel or Ctrl-C stops",
|
|
9496
|
+
color
|
|
9497
|
+
)}`
|
|
9498
|
+
);
|
|
9499
|
+
g.labels.forEach((label, i) => {
|
|
9500
|
+
missionBody.push(hexPaint(HELMET_OUTLINE, ` ${i + 1}. ${label}`, color));
|
|
9501
|
+
});
|
|
9502
|
+
missionBody.push(` ${view.gate.input.render(inner - 9, color)}`);
|
|
9503
|
+
}
|
|
9504
|
+
const checklistBody = view.checklist.length === 0 ? [muted("no queue", color)] : view.checklist.slice(0, LIVE_CHECKLIST_CAP).map((row) => {
|
|
9505
|
+
const markOf = row.state === "done" ? SPACE_MARKS.star : row.state === "current" ? "\u25B6" : SPACE_MARKS.tick;
|
|
9506
|
+
const hex = row.state === "done" ? STATUS_OK : row.state === "current" ? HELMET_ACCENT : LABEL_MUTED;
|
|
9507
|
+
return hexPaint(hex, `${markOf} ${row.label}`, color);
|
|
9508
|
+
});
|
|
9509
|
+
const c = view.crew;
|
|
9510
|
+
const crewLine1 = [
|
|
9511
|
+
c.backend ?? "agent",
|
|
9512
|
+
c.pid !== void 0 ? `pid ${c.pid}` : "no pid",
|
|
9513
|
+
c.phase,
|
|
9514
|
+
formatElapsed(now2 - c.startedAt),
|
|
9515
|
+
c.costUsd !== void 0 ? `$${c.costUsd.toFixed(c.costUsd < 0.1 ? 4 : 2)}` : null,
|
|
9516
|
+
c.turns !== void 0 ? `${c.turns} turn${c.turns === 1 ? "" : "s"}` : null,
|
|
9517
|
+
`${c.toolCalls} tool call${c.toolCalls === 1 ? "" : "s"}`
|
|
9518
|
+
].filter((part) => Boolean(part)).join(` ${SPACE_MARKS.tick} `);
|
|
9519
|
+
const crewBody = [hexPaint(HELMET_OUTLINE, crewLine1, color)];
|
|
9520
|
+
if (c.currentTool) {
|
|
9521
|
+
crewBody.push(
|
|
9522
|
+
`${hexPaint(LABEL_MUTED, "tool ", color)}${hexPaint(HELMET_ACCENT, `${c.currentTool.name} ${SPACE_MARKS.tick} ${c.currentTool.summary}`, color)}`
|
|
9523
|
+
);
|
|
9524
|
+
} else if (c.lastResult) {
|
|
9525
|
+
crewBody.push(
|
|
9526
|
+
`${hexPaint(LABEL_MUTED, "last ", color)}${hexPaint(c.lastResult.ok ? STATUS_OK : STATUS_ERR, `result ${c.lastResult.status}`, color)}`
|
|
9527
|
+
);
|
|
9528
|
+
}
|
|
9529
|
+
if (c.rateLimit) {
|
|
9530
|
+
crewBody.push(
|
|
9531
|
+
`${hexPaint(LABEL_MUTED, "limit ", color)}${hexPaint(c.rateLimit.severe ? STATUS_ERR : STATUS_WARN, c.rateLimit.line, color)}`
|
|
9532
|
+
);
|
|
9533
|
+
}
|
|
9534
|
+
if (c.logPath) crewBody.push(labeled("log ", c.logPath, color, LABEL_MUTED));
|
|
9535
|
+
let checklist = checklistBody;
|
|
9536
|
+
let crew = crewBody;
|
|
9537
|
+
const fixed = () => 1 + (missionBody.length + 2) + 2 + (checklist ? checklist.length + 2 : 0) + (crew.length + 2) + 1;
|
|
9538
|
+
if (fixed() + LIVE_MIN_LOG_ROWS > rows) checklist = null;
|
|
9539
|
+
if (fixed() + LIVE_MIN_LOG_ROWS > rows) crew = crew.slice(0, 1);
|
|
9540
|
+
const logRows = Math.max(1, Math.min(LIVE_MAX_LOG_ROWS, rows - fixed()));
|
|
9541
|
+
const viewport = view.flightLog.viewport(logRows);
|
|
9542
|
+
const flightBody = viewport.lines.map(
|
|
9543
|
+
(line) => hexPaint(liveLineHex(line.kind), line.text, color)
|
|
9544
|
+
);
|
|
9545
|
+
if (flightBody.length === 0) flightBody.push(muted("waiting for the agent stream", color));
|
|
9546
|
+
const flightTitle = viewport.above > 0 || viewport.below > 0 ? `Flight Log ${SPACE_MARKS.tick} ${viewport.above} above${viewport.below > 0 ? ` ${SPACE_MARKS.tick} ${viewport.below} below` : ""}` : "Flight Log";
|
|
9547
|
+
const panels = [
|
|
9548
|
+
...box("Mission", missionBody, columns, color),
|
|
9549
|
+
...box(flightTitle, flightBody, columns, color),
|
|
9550
|
+
...checklist ? box("Checklist", checklist, columns, color) : [],
|
|
9551
|
+
...box("Crew Monitor", crew, columns, color)
|
|
9552
|
+
];
|
|
9553
|
+
const footer = muted(
|
|
9554
|
+
view.gate ? "Third surface \xB7 live feed \xB7 Enter sends the reply \xB7 Ctrl-C stops the run" : "Third surface \xB7 live feed \xB7 q / Ctrl-C stops the run \xB7 log keeps the full NDJSON",
|
|
9555
|
+
color
|
|
9556
|
+
);
|
|
9557
|
+
return [header, ...panels, footer].join("\n");
|
|
9558
|
+
}
|
|
8249
9559
|
|
|
8250
9560
|
// src/mission-control/snapshot.ts
|
|
8251
9561
|
import { execFile as execFile6 } from "child_process";
|
|
8252
9562
|
import { access as access8 } from "fs/promises";
|
|
8253
|
-
import
|
|
9563
|
+
import path40 from "path";
|
|
8254
9564
|
|
|
8255
9565
|
// ../../dashboard/lib/live-refresh.mjs
|
|
8256
9566
|
import { join, resolve as resolve2 } from "path";
|
|
@@ -8283,7 +9593,7 @@ async function firstExisting2(candidates) {
|
|
|
8283
9593
|
for (const candidate2 of candidates) {
|
|
8284
9594
|
try {
|
|
8285
9595
|
await access8(candidate2);
|
|
8286
|
-
return
|
|
9596
|
+
return path40.resolve(candidate2);
|
|
8287
9597
|
} catch {
|
|
8288
9598
|
}
|
|
8289
9599
|
}
|
|
@@ -8292,7 +9602,7 @@ async function firstExisting2(candidates) {
|
|
|
8292
9602
|
async function findDashboardDataScript(cwd, env = process.env, options = {}) {
|
|
8293
9603
|
const startPath = await findDashboardStart(cwd, env, options);
|
|
8294
9604
|
if (startPath) {
|
|
8295
|
-
const sibling =
|
|
9605
|
+
const sibling = path40.join(path40.dirname(startPath), "dashboard-data.mjs");
|
|
8296
9606
|
try {
|
|
8297
9607
|
await access8(sibling);
|
|
8298
9608
|
return sibling;
|
|
@@ -8307,7 +9617,7 @@ async function loadDashboardSnapshot(opts) {
|
|
|
8307
9617
|
const env = opts.env ?? process.env;
|
|
8308
9618
|
const timeout = dataScriptTimeoutMs(env);
|
|
8309
9619
|
const run = opts.execFileFn ?? execFile6;
|
|
8310
|
-
const snapshotRoot =
|
|
9620
|
+
const snapshotRoot = path40.resolve(opts.snapshotRoot);
|
|
8311
9621
|
return new Promise((resolve3) => {
|
|
8312
9622
|
run(
|
|
8313
9623
|
process.execPath,
|
|
@@ -8449,11 +9759,14 @@ function buildMcTuiView(snapshot, error = null) {
|
|
|
8449
9759
|
var CLEAR_HOME = "\x1B[2J\x1B[H";
|
|
8450
9760
|
var HIDE_CURSOR = "\x1B[?25l";
|
|
8451
9761
|
var SHOW_CURSOR = "\x1B[?25h";
|
|
8452
|
-
function
|
|
9762
|
+
function quitKeyOf(chunk) {
|
|
8453
9763
|
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
8454
|
-
|
|
9764
|
+
if (text === "q" || text === "Q") return "q";
|
|
9765
|
+
if (text === "") return "Ctrl-C";
|
|
9766
|
+
return null;
|
|
8455
9767
|
}
|
|
8456
9768
|
async function runMcTuiLoop(opts) {
|
|
9769
|
+
const render = opts.render ?? ((view, renderOpts) => renderMcTui(view, renderOpts));
|
|
8457
9770
|
const live = shouldLiveRefresh({
|
|
8458
9771
|
once: opts.once,
|
|
8459
9772
|
stdoutIsTTY: opts.stdoutIsTTY,
|
|
@@ -8461,12 +9774,14 @@ async function runMcTuiLoop(opts) {
|
|
|
8461
9774
|
});
|
|
8462
9775
|
let frameIndex = 0;
|
|
8463
9776
|
let inFlight = false;
|
|
8464
|
-
|
|
8465
|
-
|
|
9777
|
+
let stopped = false;
|
|
9778
|
+
const paint3 = async (clear) => {
|
|
9779
|
+
if (inFlight || stopped) return;
|
|
8466
9780
|
inFlight = true;
|
|
8467
9781
|
try {
|
|
8468
9782
|
const view = await opts.loadView();
|
|
8469
|
-
|
|
9783
|
+
if (stopped) return;
|
|
9784
|
+
const frame = render(view, {
|
|
8470
9785
|
...opts.renderOpts,
|
|
8471
9786
|
stdoutIsTTY: opts.stdoutIsTTY,
|
|
8472
9787
|
frameIndex
|
|
@@ -8476,6 +9791,7 @@ async function runMcTuiLoop(opts) {
|
|
|
8476
9791
|
` : `${frame}
|
|
8477
9792
|
`);
|
|
8478
9793
|
} catch (err) {
|
|
9794
|
+
if (stopped) return;
|
|
8479
9795
|
const message = err instanceof Error ? err.message : String(err);
|
|
8480
9796
|
const frame = renderMcTui(buildMcTuiView(null, message), {
|
|
8481
9797
|
...opts.renderOpts,
|
|
@@ -8490,7 +9806,7 @@ async function runMcTuiLoop(opts) {
|
|
|
8490
9806
|
inFlight = false;
|
|
8491
9807
|
}
|
|
8492
9808
|
};
|
|
8493
|
-
await
|
|
9809
|
+
await paint3(false);
|
|
8494
9810
|
if (!live) return null;
|
|
8495
9811
|
opts.hooks.write(HIDE_CURSOR);
|
|
8496
9812
|
const intervalMs = opts.intervalMs ?? PERIODIC_REFRESH_MS;
|
|
@@ -8499,7 +9815,7 @@ async function runMcTuiLoop(opts) {
|
|
|
8499
9815
|
let timer;
|
|
8500
9816
|
try {
|
|
8501
9817
|
timer = setInt(() => {
|
|
8502
|
-
void
|
|
9818
|
+
void paint3(true);
|
|
8503
9819
|
}, intervalMs);
|
|
8504
9820
|
} catch (err) {
|
|
8505
9821
|
opts.hooks.write(SHOW_CURSOR);
|
|
@@ -8508,7 +9824,6 @@ async function runMcTuiLoop(opts) {
|
|
|
8508
9824
|
const stdin = opts.hooks.stdin;
|
|
8509
9825
|
const attachQuit = Boolean(stdin?.isTTY);
|
|
8510
9826
|
let quitListener;
|
|
8511
|
-
let stopped = false;
|
|
8512
9827
|
const restoreStdin = () => {
|
|
8513
9828
|
if (!stdin || !attachQuit) return;
|
|
8514
9829
|
if (quitListener) {
|
|
@@ -8532,14 +9847,24 @@ async function runMcTuiLoop(opts) {
|
|
|
8532
9847
|
stdin.setRawMode?.(true);
|
|
8533
9848
|
stdin.resume?.();
|
|
8534
9849
|
quitListener = (chunk) => {
|
|
8535
|
-
if (
|
|
9850
|
+
if (stopped) return;
|
|
9851
|
+
if (opts.hooks.onKey?.(chunk)) return;
|
|
9852
|
+
const key = quitKeyOf(chunk);
|
|
9853
|
+
if (!key) return;
|
|
8536
9854
|
stop();
|
|
9855
|
+
if (opts.hooks.onQuit) {
|
|
9856
|
+
opts.hooks.onQuit(key);
|
|
9857
|
+
return;
|
|
9858
|
+
}
|
|
8537
9859
|
const exitFn = opts.hooks.exit ?? ((code) => process.exit(code));
|
|
8538
9860
|
exitFn(0);
|
|
8539
9861
|
};
|
|
8540
9862
|
stdin.on("data", quitListener);
|
|
8541
9863
|
}
|
|
8542
|
-
return {
|
|
9864
|
+
return {
|
|
9865
|
+
stop,
|
|
9866
|
+
repaint: () => stopped ? Promise.resolve() : paint3(true)
|
|
9867
|
+
};
|
|
8543
9868
|
}
|
|
8544
9869
|
|
|
8545
9870
|
// src/commands/mission-control.ts
|
|
@@ -8615,13 +9940,13 @@ var missionControlCommand = defineCommand13({
|
|
|
8615
9940
|
});
|
|
8616
9941
|
|
|
8617
9942
|
// src/commands/monitors.ts
|
|
8618
|
-
import
|
|
9943
|
+
import path42 from "path";
|
|
8619
9944
|
import { defineCommand as defineCommand14 } from "citty";
|
|
8620
9945
|
|
|
8621
9946
|
// src/invariants/monitors-untriaged.ts
|
|
8622
9947
|
import { execFile as execFile7 } from "child_process";
|
|
8623
|
-
import { readFile as
|
|
8624
|
-
import
|
|
9948
|
+
import { readFile as readFile23, readdir as readdir10, stat as stat5 } from "fs/promises";
|
|
9949
|
+
import path41 from "path";
|
|
8625
9950
|
import { promisify as promisify6 } from "util";
|
|
8626
9951
|
|
|
8627
9952
|
// src/invariants/triage-heading.ts
|
|
@@ -8645,7 +9970,7 @@ function hasOpenGaps(content) {
|
|
|
8645
9970
|
}
|
|
8646
9971
|
async function listMonitorFiles(memoryDir) {
|
|
8647
9972
|
try {
|
|
8648
|
-
const names = await
|
|
9973
|
+
const names = await readdir10(memoryDir);
|
|
8649
9974
|
return names.filter((n) => n.startsWith("plan-monitor-") && n.endsWith(".md")).sort();
|
|
8650
9975
|
} catch {
|
|
8651
9976
|
return [];
|
|
@@ -8662,7 +9987,7 @@ async function gitFreshMonitorNames(rootDir) {
|
|
|
8662
9987
|
for (const line of stdout.split("\n")) {
|
|
8663
9988
|
if (!line.trim()) continue;
|
|
8664
9989
|
const file = line.slice(3).trim().replace(/^.* -> /, "");
|
|
8665
|
-
const base =
|
|
9990
|
+
const base = path41.basename(file);
|
|
8666
9991
|
if (base.startsWith("plan-monitor-") && base.endsWith(".md")) {
|
|
8667
9992
|
names.add(base);
|
|
8668
9993
|
}
|
|
@@ -8685,15 +10010,15 @@ function monitorSlugFromName(fileName) {
|
|
|
8685
10010
|
return fileName.replace(/^plan-monitor-/, "").replace(/\.md$/, "").toLowerCase();
|
|
8686
10011
|
}
|
|
8687
10012
|
async function selectUntriagedMonitors(rootDir) {
|
|
8688
|
-
const root =
|
|
8689
|
-
const memoryDir =
|
|
10013
|
+
const root = path41.resolve(rootDir);
|
|
10014
|
+
const memoryDir = path41.join(root, ".cursor", "memory");
|
|
8690
10015
|
const allNames = await listMonitorFiles(memoryDir);
|
|
8691
10016
|
const selectionOrder = ["git-fresh", "handoff-aligned", "untriaged-scan"];
|
|
8692
10017
|
const byName = /* @__PURE__ */ new Map();
|
|
8693
10018
|
for (const name of allNames) {
|
|
8694
|
-
const abs =
|
|
10019
|
+
const abs = path41.join(memoryDir, name);
|
|
8695
10020
|
try {
|
|
8696
|
-
const [content, st] = await Promise.all([
|
|
10021
|
+
const [content, st] = await Promise.all([readFile23(abs, "utf8"), stat5(abs)]);
|
|
8697
10022
|
byName.set(name, { content, mtimeMs: st.mtimeMs });
|
|
8698
10023
|
} catch {
|
|
8699
10024
|
}
|
|
@@ -8706,7 +10031,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
8706
10031
|
const gitFreshSet = [...gitFresh].filter(untriaged).sort();
|
|
8707
10032
|
let handoff = "";
|
|
8708
10033
|
try {
|
|
8709
|
-
handoff = await
|
|
10034
|
+
handoff = await readFile23(path41.join(root, ".cursor", "HANDOFF.md"), "utf8");
|
|
8710
10035
|
} catch {
|
|
8711
10036
|
handoff = "";
|
|
8712
10037
|
}
|
|
@@ -8726,8 +10051,8 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
8726
10051
|
const row = byName.get(name);
|
|
8727
10052
|
if (!row) continue;
|
|
8728
10053
|
entries.push({
|
|
8729
|
-
path:
|
|
8730
|
-
relativePath:
|
|
10054
|
+
path: path41.join(memoryDir, name),
|
|
10055
|
+
relativePath: path41.relative(root, path41.join(memoryDir, name)).split(path41.sep).join("/"),
|
|
8731
10056
|
mtimeMs: row.mtimeMs,
|
|
8732
10057
|
hasTriageHeading: false,
|
|
8733
10058
|
hasOpenGaps: hasOpenGaps(row.content),
|
|
@@ -8773,7 +10098,7 @@ var monitorsCommand = defineCommand14({
|
|
|
8773
10098
|
process.exitCode = 2;
|
|
8774
10099
|
return;
|
|
8775
10100
|
}
|
|
8776
|
-
const result = await selectUntriagedMonitors(
|
|
10101
|
+
const result = await selectUntriagedMonitors(path42.resolve(args.cwd));
|
|
8777
10102
|
if (args.json) {
|
|
8778
10103
|
console.log(JSON.stringify(result, null, 2));
|
|
8779
10104
|
return;
|
|
@@ -8789,7 +10114,7 @@ var monitorsCommand = defineCommand14({
|
|
|
8789
10114
|
});
|
|
8790
10115
|
|
|
8791
10116
|
// src/commands/plan-index.ts
|
|
8792
|
-
import
|
|
10117
|
+
import path43 from "path";
|
|
8793
10118
|
import { defineCommand as defineCommand15 } from "citty";
|
|
8794
10119
|
var planIndexCommand = defineCommand15({
|
|
8795
10120
|
meta: {
|
|
@@ -8808,7 +10133,7 @@ var planIndexCommand = defineCommand15({
|
|
|
8808
10133
|
}
|
|
8809
10134
|
},
|
|
8810
10135
|
async run({ args }) {
|
|
8811
|
-
const root =
|
|
10136
|
+
const root = path43.resolve(args.cwd);
|
|
8812
10137
|
const index = await writePlanIndex(root);
|
|
8813
10138
|
if (args.json) {
|
|
8814
10139
|
console.log(JSON.stringify(index, null, 2));
|
|
@@ -8824,90 +10149,760 @@ var planIndexCommand = defineCommand15({
|
|
|
8824
10149
|
});
|
|
8825
10150
|
|
|
8826
10151
|
// src/commands/run-plan.ts
|
|
8827
|
-
import
|
|
10152
|
+
import path50 from "path";
|
|
8828
10153
|
import { defineCommand as defineCommand16 } from "citty";
|
|
8829
10154
|
|
|
8830
10155
|
// src/plan-loop/backends.ts
|
|
8831
|
-
import { execFileSync as execFileSync2, spawn as
|
|
10156
|
+
import { execFileSync as execFileSync2, spawn as spawn7 } from "child_process";
|
|
8832
10157
|
import { createWriteStream } from "fs";
|
|
8833
10158
|
import { access as access9 } from "fs/promises";
|
|
8834
|
-
import
|
|
10159
|
+
import path44 from "path";
|
|
10160
|
+
import { StringDecoder as StringDecoder3 } from "string_decoder";
|
|
10161
|
+
|
|
10162
|
+
// src/plan-loop/hitl-relay.ts
|
|
10163
|
+
import { createInterface } from "readline";
|
|
8835
10164
|
import { StringDecoder } from "string_decoder";
|
|
8836
|
-
var
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
10165
|
+
var HITL_GATE_LINE_RE = /^HITL_GATE:\s*([a-z0-9-]+)\s*\|\s*(.+)$/;
|
|
10166
|
+
var HITL_REPLY_PREFIX = "HITL_REPLY:";
|
|
10167
|
+
var RESERVED_GATE_IDS = ["git-prod", "kit-prod"];
|
|
10168
|
+
var FALLBACK_ASK_ID = "prose-fallback";
|
|
10169
|
+
var HITL_UNANSWERED_EXIT_CODE = 4;
|
|
10170
|
+
var HITL_SIGINT_EXIT_CODE = 130;
|
|
10171
|
+
var NUMBERED_LINE_RE = /^\s*(\d+)\.\s+(.+?)\s*$/;
|
|
10172
|
+
var FALLBACK_WORDING_RES = [
|
|
10173
|
+
/reply with (?:the |a )?number/i,
|
|
10174
|
+
/ask questions is not available/i,
|
|
10175
|
+
/skip or cancel means stop/i
|
|
8840
10176
|
];
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
const
|
|
8844
|
-
|
|
8845
|
-
|
|
10177
|
+
var TICK_SENTINEL_RE = /LOOP_TICK_RESULT:/;
|
|
10178
|
+
function parseHitlGateLine(line) {
|
|
10179
|
+
const m = HITL_GATE_LINE_RE.exec(line.trim());
|
|
10180
|
+
if (!m?.[1] || !m[2]) return null;
|
|
10181
|
+
const labels = m[2].split("|").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
10182
|
+
if (labels.length === 0) return null;
|
|
10183
|
+
return { askId: m[1], labels, detection: "sentinel" };
|
|
10184
|
+
}
|
|
10185
|
+
function findHitlGateInText(text) {
|
|
10186
|
+
let last = null;
|
|
10187
|
+
for (const line of text.split(/\r?\n/)) {
|
|
10188
|
+
const gate = parseHitlGateLine(line);
|
|
10189
|
+
if (gate) last = gate;
|
|
8846
10190
|
}
|
|
8847
|
-
return
|
|
10191
|
+
return last;
|
|
8848
10192
|
}
|
|
8849
|
-
|
|
8850
|
-
|
|
10193
|
+
function cleanFallbackLabel(raw) {
|
|
10194
|
+
let label = raw.trim();
|
|
10195
|
+
const bold3 = /^\*\*(.+?)\*\*/.exec(label);
|
|
10196
|
+
if (bold3?.[1]) label = bold3[1];
|
|
10197
|
+
else label = label.replace(/\*\*/g, "").replace(/^`(.+)`$/, "$1");
|
|
10198
|
+
label = label.split(/\s+[—–]\s+/)[0] ?? label;
|
|
10199
|
+
return label.replace(/[.:]+$/, "").trim();
|
|
10200
|
+
}
|
|
10201
|
+
function detectFallbackGate(text) {
|
|
10202
|
+
if (!FALLBACK_WORDING_RES.some((re) => re.test(text))) return null;
|
|
10203
|
+
const labels = [];
|
|
10204
|
+
let expected = 1;
|
|
10205
|
+
for (const line of text.split(/\r?\n/)) {
|
|
10206
|
+
const m = NUMBERED_LINE_RE.exec(line);
|
|
10207
|
+
if (!m?.[1] || !m[2]) continue;
|
|
10208
|
+
const n = Number(m[1]);
|
|
10209
|
+
if (n === 1) {
|
|
10210
|
+
labels.length = 0;
|
|
10211
|
+
expected = 1;
|
|
10212
|
+
}
|
|
10213
|
+
if (n !== expected) continue;
|
|
10214
|
+
labels.push(cleanFallbackLabel(m[2]));
|
|
10215
|
+
expected += 1;
|
|
10216
|
+
}
|
|
10217
|
+
if (labels.length < 2) return null;
|
|
10218
|
+
return { askId: FALLBACK_ASK_ID, labels, detection: "fallback" };
|
|
10219
|
+
}
|
|
10220
|
+
function detectHitlGate(resultText, assistantTexts) {
|
|
10221
|
+
if (TICK_SENTINEL_RE.test(resultText)) return null;
|
|
10222
|
+
const fromResult = findHitlGateInText(resultText);
|
|
10223
|
+
if (fromResult) return fromResult;
|
|
10224
|
+
for (let i = assistantTexts.length - 1; i >= 0; i -= 1) {
|
|
10225
|
+
const text = assistantTexts[i] ?? "";
|
|
10226
|
+
if (TICK_SENTINEL_RE.test(text)) return null;
|
|
10227
|
+
const gate = findHitlGateInText(text);
|
|
10228
|
+
if (gate) return gate;
|
|
10229
|
+
}
|
|
10230
|
+
const fallback = detectFallbackGate(resultText);
|
|
10231
|
+
if (fallback) return fallback;
|
|
10232
|
+
for (let i = assistantTexts.length - 1; i >= 0; i -= 1) {
|
|
10233
|
+
const gate = detectFallbackGate(assistantTexts[i] ?? "");
|
|
10234
|
+
if (gate) return gate;
|
|
10235
|
+
}
|
|
10236
|
+
return null;
|
|
10237
|
+
}
|
|
10238
|
+
function isReservedGateId(askId) {
|
|
10239
|
+
return RESERVED_GATE_IDS.includes(askId);
|
|
10240
|
+
}
|
|
10241
|
+
var STOP_WORDS = /* @__PURE__ */ new Set(["skip", "cancel"]);
|
|
10242
|
+
function resolveOperatorAnswer(input, labels) {
|
|
10243
|
+
const text = input.trim();
|
|
10244
|
+
if (!text) return { kind: "stop", cause: "empty" };
|
|
10245
|
+
const exact = labels.indexOf(text);
|
|
10246
|
+
if (exact !== -1) return { kind: "reply", n: exact + 1, label: labels[exact] ?? text };
|
|
10247
|
+
if (/^\d+$/.test(text)) {
|
|
10248
|
+
const n = Number(text);
|
|
10249
|
+
const label = labels[n - 1];
|
|
10250
|
+
if (n >= 1 && label !== void 0) return { kind: "reply", n, label };
|
|
10251
|
+
}
|
|
10252
|
+
const lower = text.toLowerCase();
|
|
10253
|
+
if (STOP_WORDS.has(lower)) return { kind: "stop", cause: lower };
|
|
10254
|
+
return { kind: "other", text };
|
|
10255
|
+
}
|
|
10256
|
+
function formatHitlReply(askId, answer) {
|
|
10257
|
+
if (answer.kind === "reply") {
|
|
10258
|
+
return `${HITL_REPLY_PREFIX} ${askId} | operator reply ${answer.n} | ${answer.label}`;
|
|
10259
|
+
}
|
|
10260
|
+
if (answer.kind === "other") {
|
|
10261
|
+
return `${HITL_REPLY_PREFIX} ${askId} | operator reply other | ${answer.text}`;
|
|
10262
|
+
}
|
|
10263
|
+
throw new Error("a stop answer has no reply stamp");
|
|
10264
|
+
}
|
|
10265
|
+
function createTerminalAnswerReader(opts = {}) {
|
|
10266
|
+
return (_gate, readOpts) => new Promise((resolve3) => {
|
|
10267
|
+
const input = opts.input ?? process.stdin;
|
|
10268
|
+
const output = opts.output ?? process.stdout;
|
|
10269
|
+
const terminal = opts.terminal ?? Boolean(input.isTTY);
|
|
10270
|
+
const rl = createInterface({ input, output, terminal });
|
|
10271
|
+
let settled = false;
|
|
10272
|
+
const finish = (answer) => {
|
|
10273
|
+
if (settled) return;
|
|
10274
|
+
settled = true;
|
|
10275
|
+
readOpts.signal?.removeEventListener("abort", onAbort);
|
|
10276
|
+
rl.close();
|
|
10277
|
+
if (typeof input.pause === "function") {
|
|
10278
|
+
input.pause();
|
|
10279
|
+
}
|
|
10280
|
+
resolve3(answer);
|
|
10281
|
+
};
|
|
10282
|
+
const onAbort = () => finish({ kind: "eof" });
|
|
10283
|
+
if (readOpts.signal?.aborted) {
|
|
10284
|
+
onAbort();
|
|
10285
|
+
return;
|
|
10286
|
+
}
|
|
10287
|
+
readOpts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10288
|
+
rl.on("SIGINT", () => finish({ kind: "sigint" }));
|
|
10289
|
+
rl.on("close", () => finish({ kind: "eof" }));
|
|
10290
|
+
rl.question("> ", (line) => finish({ kind: "line", text: line }));
|
|
10291
|
+
});
|
|
10292
|
+
}
|
|
10293
|
+
function emptyHitlRecord() {
|
|
10294
|
+
return { replies: [], fallbackDetections: 0 };
|
|
10295
|
+
}
|
|
10296
|
+
function operatorStop(askId, cause) {
|
|
10297
|
+
return {
|
|
10298
|
+
askId,
|
|
10299
|
+
cause,
|
|
10300
|
+
message: `stopped-by-operator: gate ${askId}, no reply (${cause})`,
|
|
10301
|
+
exitCode: cause === "SIGINT" ? HITL_SIGINT_EXIT_CODE : HITL_UNANSWERED_EXIT_CODE
|
|
10302
|
+
};
|
|
10303
|
+
}
|
|
10304
|
+
function operatorQuitStop(key) {
|
|
10305
|
+
return {
|
|
10306
|
+
askId: "none",
|
|
10307
|
+
cause: "quit",
|
|
10308
|
+
message: `stopped-by-operator: quit (${key}), no gate open`,
|
|
10309
|
+
exitCode: HITL_SIGINT_EXIT_CODE
|
|
10310
|
+
};
|
|
10311
|
+
}
|
|
10312
|
+
function unansweredStop(askId, cause) {
|
|
10313
|
+
return {
|
|
10314
|
+
askId,
|
|
10315
|
+
cause,
|
|
10316
|
+
message: `stopped: unanswered gate (${cause})`,
|
|
10317
|
+
exitCode: HITL_UNANSWERED_EXIT_CODE
|
|
10318
|
+
};
|
|
10319
|
+
}
|
|
10320
|
+
function noRelayStop(askId, backend) {
|
|
10321
|
+
return {
|
|
10322
|
+
askId,
|
|
10323
|
+
cause: "no relay",
|
|
10324
|
+
message: `stopped: unanswered gate (backend ${backend} has no relay)`,
|
|
10325
|
+
exitCode: HITL_UNANSWERED_EXIT_CODE
|
|
10326
|
+
};
|
|
10327
|
+
}
|
|
10328
|
+
function reservedStop(askId) {
|
|
10329
|
+
return {
|
|
10330
|
+
askId,
|
|
10331
|
+
cause: "reserved",
|
|
10332
|
+
message: `stopped: gate ${askId} is operator-gated; run the /${askId} slash yourself (never headless)`,
|
|
10333
|
+
exitCode: HITL_UNANSWERED_EXIT_CODE
|
|
10334
|
+
};
|
|
10335
|
+
}
|
|
10336
|
+
function formatGatePrompt(gate) {
|
|
10337
|
+
const head = gate.detection === "fallback" ? `HITL gate (prose fallback, ask-id ${gate.askId})` : `HITL gate ${gate.askId}`;
|
|
10338
|
+
const lines = [
|
|
10339
|
+
"",
|
|
10340
|
+
`${head}: reply with the number or the label; anything else is sent as "other"; empty, skip, cancel or Ctrl-C stops the run.`,
|
|
10341
|
+
...gate.labels.map((label, i) => ` ${i + 1}. ${label}`)
|
|
10342
|
+
];
|
|
10343
|
+
return `${lines.join("\n")}
|
|
10344
|
+
`;
|
|
10345
|
+
}
|
|
10346
|
+
async function askOperator(gate, opts) {
|
|
10347
|
+
if (isReservedGateId(gate.askId)) return { kind: "stop", stop: reservedStop(gate.askId) };
|
|
10348
|
+
if (opts.policy === "off") return { kind: "stop", stop: unansweredStop(gate.askId, "--no-hitl") };
|
|
10349
|
+
if (!opts.isTTY) return { kind: "stop", stop: unansweredStop(gate.askId, "no TTY") };
|
|
10350
|
+
opts.onPromptStart?.();
|
|
10351
|
+
let raw;
|
|
10352
|
+
try {
|
|
10353
|
+
opts.write(formatGatePrompt(gate));
|
|
10354
|
+
raw = await opts.readAnswer(gate, { signal: opts.signal });
|
|
10355
|
+
} finally {
|
|
10356
|
+
opts.onPromptEnd?.();
|
|
10357
|
+
}
|
|
10358
|
+
if (opts.signal?.aborted) {
|
|
10359
|
+
return { kind: "stop", stop: unansweredStop(gate.askId, "child exited") };
|
|
10360
|
+
}
|
|
10361
|
+
if (raw.kind === "eof") return { kind: "stop", stop: operatorStop(gate.askId, "EOF") };
|
|
10362
|
+
if (raw.kind === "sigint") return { kind: "stop", stop: operatorStop(gate.askId, "SIGINT") };
|
|
10363
|
+
const answer = resolveOperatorAnswer(raw.text, gate.labels);
|
|
10364
|
+
if (answer.kind === "stop") return { kind: "stop", stop: operatorStop(gate.askId, answer.cause) };
|
|
10365
|
+
const line = formatHitlReply(gate.askId, answer);
|
|
10366
|
+
const stamp3 = {
|
|
10367
|
+
askId: gate.askId,
|
|
10368
|
+
reply: answer.kind === "reply" ? `operator reply ${answer.n}` : "operator reply other",
|
|
10369
|
+
label: answer.kind === "reply" ? answer.label : answer.text,
|
|
10370
|
+
line,
|
|
10371
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10372
|
+
detection: gate.detection
|
|
10373
|
+
};
|
|
10374
|
+
return { kind: "reply", stamp: stamp3 };
|
|
10375
|
+
}
|
|
10376
|
+
function formatHitlSummary(record) {
|
|
10377
|
+
if (!record) return [];
|
|
10378
|
+
const lines = record.replies.map(
|
|
10379
|
+
(s) => `HITL ${s.askId}: ${s.reply} (${s.label}) ${s.at}${s.detection === "fallback" ? " [fallback]" : ""}`
|
|
10380
|
+
);
|
|
10381
|
+
if (record.fallbackDetections > 0) {
|
|
10382
|
+
lines.push(`hitl-detect: prose-fallback x${record.fallbackDetections}`);
|
|
10383
|
+
}
|
|
10384
|
+
if (record.stop) lines.push(record.stop.message);
|
|
10385
|
+
return lines;
|
|
10386
|
+
}
|
|
10387
|
+
function hitlExitCode(childExitCode, record) {
|
|
10388
|
+
if (record?.stop) return record.stop.exitCode;
|
|
10389
|
+
return childExitCode;
|
|
10390
|
+
}
|
|
10391
|
+
function userEventLine(text) {
|
|
10392
|
+
return `${JSON.stringify({ type: "user", message: { role: "user", content: text } })}
|
|
10393
|
+
`;
|
|
10394
|
+
}
|
|
10395
|
+
var TurnWatcher = class {
|
|
10396
|
+
constructor(onResult) {
|
|
10397
|
+
this.onResult = onResult;
|
|
10398
|
+
}
|
|
10399
|
+
onResult;
|
|
8851
10400
|
pending = "";
|
|
10401
|
+
assistantTexts = [];
|
|
8852
10402
|
decoder = new StringDecoder("utf8");
|
|
8853
|
-
|
|
8854
|
-
keep;
|
|
8855
|
-
constructor(secrets) {
|
|
8856
|
-
this.secrets = secrets.filter((s) => s.value.length > 0);
|
|
8857
|
-
this.keep = this.secrets.length > 0 ? Math.max(...this.secrets.map((s) => s.value.length)) - 1 : 0;
|
|
8858
|
-
}
|
|
8859
|
-
/** Append a chunk; returns the redacted text that is safe to emit now. */
|
|
8860
|
-
push(chunk) {
|
|
10403
|
+
feed(chunk) {
|
|
8861
10404
|
const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
8862
|
-
if (
|
|
8863
|
-
this.pending
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
10405
|
+
if (!text) return;
|
|
10406
|
+
const combined = this.pending + text;
|
|
10407
|
+
const lines = combined.split("\n");
|
|
10408
|
+
this.pending = lines.pop() ?? "";
|
|
10409
|
+
for (const line of lines) this.take(line);
|
|
10410
|
+
}
|
|
10411
|
+
end() {
|
|
10412
|
+
const rest = this.pending + this.decoder.end();
|
|
10413
|
+
this.pending = "";
|
|
10414
|
+
if (rest.trim()) this.take(rest);
|
|
10415
|
+
}
|
|
10416
|
+
take(line) {
|
|
10417
|
+
const trimmed = line.trim();
|
|
10418
|
+
if (!trimmed.startsWith("{")) return;
|
|
10419
|
+
let event;
|
|
10420
|
+
try {
|
|
10421
|
+
event = JSON.parse(trimmed);
|
|
10422
|
+
} catch {
|
|
10423
|
+
return;
|
|
8868
10424
|
}
|
|
8869
|
-
if (
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
break;
|
|
10425
|
+
if (typeof event !== "object" || event === null) return;
|
|
10426
|
+
const ev = event;
|
|
10427
|
+
if (ev.type === "assistant") {
|
|
10428
|
+
const content = ev.message?.content;
|
|
10429
|
+
if (typeof content === "string") {
|
|
10430
|
+
if (content) this.assistantTexts.push(content);
|
|
10431
|
+
} else if (Array.isArray(content)) {
|
|
10432
|
+
for (const part of content) {
|
|
10433
|
+
if (part && typeof part === "object" && part.type === "text" && typeof part.text === "string") {
|
|
10434
|
+
this.assistantTexts.push(part.text);
|
|
8880
10435
|
}
|
|
8881
|
-
idx = this.pending.indexOf(s.value, idx + 1);
|
|
8882
10436
|
}
|
|
8883
|
-
if (moved) break;
|
|
8884
10437
|
}
|
|
10438
|
+
return;
|
|
10439
|
+
}
|
|
10440
|
+
if (ev.type === "result") {
|
|
10441
|
+
const texts = this.assistantTexts;
|
|
10442
|
+
this.assistantTexts = [];
|
|
10443
|
+
this.onResult({
|
|
10444
|
+
resultText: typeof ev.result === "string" ? ev.result : "",
|
|
10445
|
+
assistantTexts: texts,
|
|
10446
|
+
isError: ev.is_error === true,
|
|
10447
|
+
subtype: typeof ev.subtype === "string" ? ev.subtype : null
|
|
10448
|
+
});
|
|
8885
10449
|
}
|
|
8886
|
-
if (cut <= 0) return "";
|
|
8887
|
-
const out = redactSecrets(this.pending.slice(0, cut), this.secrets);
|
|
8888
|
-
this.pending = this.pending.slice(cut);
|
|
8889
|
-
return out;
|
|
8890
|
-
}
|
|
8891
|
-
/** Emit whatever is still held back (call once, on stream end). */
|
|
8892
|
-
flush() {
|
|
8893
|
-
const rest = this.pending + this.decoder.end();
|
|
8894
|
-
this.pending = "";
|
|
8895
|
-
return this.secrets.length === 0 ? rest : redactSecrets(rest, this.secrets);
|
|
8896
10450
|
}
|
|
8897
10451
|
};
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
return
|
|
10452
|
+
|
|
10453
|
+
// src/plan-loop/stream-render.ts
|
|
10454
|
+
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
10455
|
+
import { trueColor as trueColor2 } from "kolorist";
|
|
10456
|
+
|
|
10457
|
+
// src/plan-loop/stream-events.ts
|
|
10458
|
+
function isRecord(value) {
|
|
10459
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8906
10460
|
}
|
|
8907
|
-
|
|
8908
|
-
"
|
|
8909
|
-
|
|
8910
|
-
|
|
10461
|
+
function asString2(value) {
|
|
10462
|
+
return typeof value === "string" ? value : null;
|
|
10463
|
+
}
|
|
10464
|
+
function asNumber(value) {
|
|
10465
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
10466
|
+
}
|
|
10467
|
+
function oneLine(text) {
|
|
10468
|
+
return text.replace(/\s+/g, " ").trim();
|
|
10469
|
+
}
|
|
10470
|
+
var SUMMARY_KEYS = [
|
|
10471
|
+
"command",
|
|
10472
|
+
"file_path",
|
|
10473
|
+
"path",
|
|
10474
|
+
"pattern",
|
|
10475
|
+
"query",
|
|
10476
|
+
"url",
|
|
10477
|
+
"description",
|
|
10478
|
+
"prompt",
|
|
10479
|
+
"content"
|
|
10480
|
+
];
|
|
10481
|
+
function summarizeToolInput(input) {
|
|
10482
|
+
if (typeof input === "string") return oneLine(input);
|
|
10483
|
+
if (!isRecord(input)) return input === void 0 ? "" : oneLine(JSON.stringify(input));
|
|
10484
|
+
for (const key of SUMMARY_KEYS) {
|
|
10485
|
+
const value = input[key];
|
|
10486
|
+
if (typeof value === "string" && value.trim()) return oneLine(value);
|
|
10487
|
+
}
|
|
10488
|
+
const keys = Object.keys(input);
|
|
10489
|
+
if (keys.length === 0) return "";
|
|
10490
|
+
return oneLine(JSON.stringify(input));
|
|
10491
|
+
}
|
|
10492
|
+
function firstLineOfContent(content) {
|
|
10493
|
+
if (typeof content === "string") return firstNonEmptyLine(content);
|
|
10494
|
+
if (Array.isArray(content)) {
|
|
10495
|
+
for (const part of content) {
|
|
10496
|
+
if (isRecord(part) && typeof part.text === "string") {
|
|
10497
|
+
const line = firstNonEmptyLine(part.text);
|
|
10498
|
+
if (line) return line;
|
|
10499
|
+
}
|
|
10500
|
+
}
|
|
10501
|
+
return "";
|
|
10502
|
+
}
|
|
10503
|
+
if (isRecord(content)) return oneLine(JSON.stringify(content));
|
|
10504
|
+
return "";
|
|
10505
|
+
}
|
|
10506
|
+
function firstNonEmptyLine(text) {
|
|
10507
|
+
for (const line of text.split(/\r?\n/)) {
|
|
10508
|
+
const trimmed = line.trim();
|
|
10509
|
+
if (trimmed) return trimmed;
|
|
10510
|
+
}
|
|
10511
|
+
return "";
|
|
10512
|
+
}
|
|
10513
|
+
function parseAssistant(event) {
|
|
10514
|
+
const message = event.message;
|
|
10515
|
+
const content = isRecord(message) ? message.content : void 0;
|
|
10516
|
+
if (typeof content === "string") return content ? [{ kind: "text", text: content }] : [];
|
|
10517
|
+
if (!Array.isArray(content)) return [{ kind: "silent" }];
|
|
10518
|
+
const out = [];
|
|
10519
|
+
for (const part of content) {
|
|
10520
|
+
if (!isRecord(part)) continue;
|
|
10521
|
+
const type = asString2(part.type);
|
|
10522
|
+
if (type === "text" && typeof part.text === "string") {
|
|
10523
|
+
if (part.text) out.push({ kind: "text", text: part.text });
|
|
10524
|
+
} else if (type === "tool_use" || type === "server_tool_use") {
|
|
10525
|
+
out.push({
|
|
10526
|
+
kind: "tool_use",
|
|
10527
|
+
name: asString2(part.name) ?? "tool",
|
|
10528
|
+
summary: summarizeToolInput(part.input)
|
|
10529
|
+
});
|
|
10530
|
+
}
|
|
10531
|
+
}
|
|
10532
|
+
return out.length > 0 ? out : [{ kind: "silent" }];
|
|
10533
|
+
}
|
|
10534
|
+
var HITL_REPLY_PREFIX2 = "HITL_REPLY:";
|
|
10535
|
+
function parseUser(event) {
|
|
10536
|
+
const message = event.message;
|
|
10537
|
+
const content = isRecord(message) ? message.content : void 0;
|
|
10538
|
+
if (typeof content === "string") {
|
|
10539
|
+
if (event.isReplay === true && content.startsWith(HITL_REPLY_PREFIX2)) {
|
|
10540
|
+
return [{ kind: "hitl_reply", line: oneLine(content) }];
|
|
10541
|
+
}
|
|
10542
|
+
return [{ kind: "prompt", chars: content.length }];
|
|
10543
|
+
}
|
|
10544
|
+
if (!Array.isArray(content)) return [{ kind: "silent" }];
|
|
10545
|
+
const out = [];
|
|
10546
|
+
let promptChars = 0;
|
|
10547
|
+
for (const part of content) {
|
|
10548
|
+
if (!isRecord(part)) continue;
|
|
10549
|
+
const type = asString2(part.type);
|
|
10550
|
+
if (type === "tool_result") {
|
|
10551
|
+
out.push({
|
|
10552
|
+
kind: "tool_result",
|
|
10553
|
+
ok: part.is_error !== true,
|
|
10554
|
+
line: firstLineOfContent(part.content)
|
|
10555
|
+
});
|
|
10556
|
+
} else if (type === "text" && typeof part.text === "string") {
|
|
10557
|
+
if (event.isReplay === true && part.text.startsWith(HITL_REPLY_PREFIX2)) {
|
|
10558
|
+
out.push({ kind: "hitl_reply", line: oneLine(part.text) });
|
|
10559
|
+
continue;
|
|
10560
|
+
}
|
|
10561
|
+
promptChars += part.text.length;
|
|
10562
|
+
}
|
|
10563
|
+
}
|
|
10564
|
+
if (promptChars > 0) out.push({ kind: "prompt", chars: promptChars });
|
|
10565
|
+
return out.length > 0 ? out : [{ kind: "silent" }];
|
|
10566
|
+
}
|
|
10567
|
+
function parseResult(event) {
|
|
10568
|
+
const isError = event.is_error === true;
|
|
10569
|
+
const subtype = asString2(event.subtype);
|
|
10570
|
+
return {
|
|
10571
|
+
kind: "result",
|
|
10572
|
+
ok: !isError,
|
|
10573
|
+
status: subtype ?? (isError ? "error" : "success"),
|
|
10574
|
+
costUsd: asNumber(event.total_cost_usd),
|
|
10575
|
+
durationMs: asNumber(event.duration_ms),
|
|
10576
|
+
apiMs: asNumber(event.duration_api_ms),
|
|
10577
|
+
turns: asNumber(event.num_turns)
|
|
10578
|
+
};
|
|
10579
|
+
}
|
|
10580
|
+
function parseSystem(event) {
|
|
10581
|
+
const subtype = asString2(event.subtype) ?? "";
|
|
10582
|
+
if (!subtype.startsWith("hook_")) return { kind: "silent" };
|
|
10583
|
+
const phase = subtype.slice("hook_".length);
|
|
10584
|
+
const name = asString2(event.hook_name) ?? asString2(event.hook_event) ?? "hook";
|
|
10585
|
+
const details = [];
|
|
10586
|
+
const outcome = asString2(event.outcome);
|
|
10587
|
+
if (outcome) details.push(outcome);
|
|
10588
|
+
const exitCode = asNumber(event.exit_code);
|
|
10589
|
+
if (exitCode !== void 0) details.push(`exit ${exitCode}`);
|
|
10590
|
+
return { kind: "hook", name, phase, detail: details.join(", ") };
|
|
10591
|
+
}
|
|
10592
|
+
function percent(value) {
|
|
10593
|
+
const n = asNumber(value);
|
|
10594
|
+
return n === void 0 ? null : `${Math.round(n * 100)}%`;
|
|
10595
|
+
}
|
|
10596
|
+
function parseRateLimit(event) {
|
|
10597
|
+
const info = isRecord(event.rate_limit_info) ? event.rate_limit_info : event;
|
|
10598
|
+
const status = asString2(info.status) ?? "unknown";
|
|
10599
|
+
const type = asString2(info.rateLimitType);
|
|
10600
|
+
const parts = [];
|
|
10601
|
+
if (type) parts.push(type);
|
|
10602
|
+
parts.push(status);
|
|
10603
|
+
const windows = isRecord(info.unifiedWindows) ? info.unifiedWindows : null;
|
|
10604
|
+
if (windows) {
|
|
10605
|
+
for (const [name, window] of Object.entries(windows)) {
|
|
10606
|
+
if (!isRecord(window)) continue;
|
|
10607
|
+
const used = percent(window.utilization);
|
|
10608
|
+
if (used) parts.push(`${name} ${used}`);
|
|
10609
|
+
}
|
|
10610
|
+
}
|
|
10611
|
+
return { kind: "rate_limit", line: parts.join(" \xB7 "), severe: status !== "allowed" };
|
|
10612
|
+
}
|
|
10613
|
+
function parseCursorToolCall(event) {
|
|
10614
|
+
const call = isRecord(event.tool_call) ? event.tool_call : null;
|
|
10615
|
+
const entry = call ? Object.entries(call).find(([, v]) => isRecord(v)) : void 0;
|
|
10616
|
+
const name = entry ? entry[0].replace(/ToolCall$/, "") : "tool";
|
|
10617
|
+
const body = entry && isRecord(entry[1]) ? entry[1] : {};
|
|
10618
|
+
const subtype = asString2(event.subtype) ?? "";
|
|
10619
|
+
if (subtype === "started") {
|
|
10620
|
+
return { kind: "tool_use", name, summary: summarizeToolInput(body.args) };
|
|
10621
|
+
}
|
|
10622
|
+
if (subtype === "completed") {
|
|
10623
|
+
const result = isRecord(body.result) ? body.result : null;
|
|
10624
|
+
const failed = result !== null && ("error" in result || "failure" in result || "rejected" in result);
|
|
10625
|
+
let line = "";
|
|
10626
|
+
if (result) {
|
|
10627
|
+
const payload = failed ? result.error ?? result.failure ?? result.rejected : result.success;
|
|
10628
|
+
line = firstLineOfContent(
|
|
10629
|
+
typeof payload === "string" ? payload : payload === void 0 ? result : payload
|
|
10630
|
+
);
|
|
10631
|
+
}
|
|
10632
|
+
return { kind: "tool_result", ok: !failed, line };
|
|
10633
|
+
}
|
|
10634
|
+
return { kind: "silent" };
|
|
10635
|
+
}
|
|
10636
|
+
function parseStreamLine(line) {
|
|
10637
|
+
const trimmed = line.trim();
|
|
10638
|
+
if (!trimmed || !trimmed.startsWith("{")) return [{ kind: "unparsed" }];
|
|
10639
|
+
let event;
|
|
10640
|
+
try {
|
|
10641
|
+
event = JSON.parse(trimmed);
|
|
10642
|
+
} catch {
|
|
10643
|
+
return [{ kind: "unparsed" }];
|
|
10644
|
+
}
|
|
10645
|
+
if (!isRecord(event)) return [{ kind: "unparsed" }];
|
|
10646
|
+
switch (asString2(event.type)) {
|
|
10647
|
+
case "assistant":
|
|
10648
|
+
return parseAssistant(event);
|
|
10649
|
+
case "user":
|
|
10650
|
+
return parseUser(event);
|
|
10651
|
+
case "result":
|
|
10652
|
+
return [parseResult(event)];
|
|
10653
|
+
case "system":
|
|
10654
|
+
return [parseSystem(event)];
|
|
10655
|
+
case "rate_limit_event":
|
|
10656
|
+
return [parseRateLimit(event)];
|
|
10657
|
+
case "tool_call":
|
|
10658
|
+
return [parseCursorToolCall(event)];
|
|
10659
|
+
default:
|
|
10660
|
+
return [{ kind: "silent" }];
|
|
10661
|
+
}
|
|
10662
|
+
}
|
|
10663
|
+
|
|
10664
|
+
// src/plan-loop/stream-render.ts
|
|
10665
|
+
var PENDING_MAX_CHARS = 4 * 1024 * 1024;
|
|
10666
|
+
var MIN_COLUMNS2 = 40;
|
|
10667
|
+
function paint2(hex, text, color) {
|
|
10668
|
+
if (!color) return text;
|
|
10669
|
+
return withKoloristColor(
|
|
10670
|
+
() => trueColor2(
|
|
10671
|
+
Number.parseInt(hex.slice(1, 3), 16),
|
|
10672
|
+
Number.parseInt(hex.slice(3, 5), 16),
|
|
10673
|
+
Number.parseInt(hex.slice(5, 7), 16)
|
|
10674
|
+
)(text)
|
|
10675
|
+
);
|
|
10676
|
+
}
|
|
10677
|
+
function truncate(text, width) {
|
|
10678
|
+
if (text.length <= width) return text;
|
|
10679
|
+
return `${text.slice(0, Math.max(1, width - 1))}\u2026`;
|
|
10680
|
+
}
|
|
10681
|
+
function formatDuration(ms) {
|
|
10682
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
10683
|
+
const seconds = ms / 1e3;
|
|
10684
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
10685
|
+
const minutes = Math.floor(seconds / 60);
|
|
10686
|
+
const rest = Math.round(seconds - minutes * 60);
|
|
10687
|
+
return `${minutes}m ${rest}s`;
|
|
10688
|
+
}
|
|
10689
|
+
function formatCost(usd) {
|
|
10690
|
+
return `$${usd.toFixed(usd < 0.1 ? 4 : 2)}`;
|
|
10691
|
+
}
|
|
10692
|
+
function renderStreamEvent(event, opts) {
|
|
10693
|
+
const width = Math.max(MIN_COLUMNS2, opts.columns);
|
|
10694
|
+
const color = opts.color;
|
|
10695
|
+
switch (event.kind) {
|
|
10696
|
+
case "text":
|
|
10697
|
+
return event.text;
|
|
10698
|
+
case "tool_use": {
|
|
10699
|
+
const head = `${SPACE_MARKS.diamond} ${event.name}`;
|
|
10700
|
+
const summary = truncate(event.summary, width - head.length - 3);
|
|
10701
|
+
return paint2(HELMET_ACCENT, summary ? `${head} ${SPACE_MARKS.tick} ${summary}` : head, color);
|
|
10702
|
+
}
|
|
10703
|
+
case "tool_result": {
|
|
10704
|
+
const label = event.ok ? "ok" : "error";
|
|
10705
|
+
const head = ` ${SPACE_MARKS.tick} ${label}`;
|
|
10706
|
+
const line = truncate(oneLine(event.line), width - head.length - 3);
|
|
10707
|
+
const text = line ? `${head} ${SPACE_MARKS.tick} ${line}` : head;
|
|
10708
|
+
return paint2(event.ok ? LABEL_MUTED : STATUS_ERR, text, color);
|
|
10709
|
+
}
|
|
10710
|
+
case "result": {
|
|
10711
|
+
const parts = [`${SPACE_MARKS.star} result ${event.status}`];
|
|
10712
|
+
if (event.costUsd !== void 0) parts.push(formatCost(event.costUsd));
|
|
10713
|
+
if (event.durationMs !== void 0) parts.push(formatDuration(event.durationMs));
|
|
10714
|
+
else if (event.apiMs !== void 0) parts.push(`${formatDuration(event.apiMs)} api`);
|
|
10715
|
+
if (event.turns !== void 0) {
|
|
10716
|
+
parts.push(`${event.turns} turn${event.turns === 1 ? "" : "s"}`);
|
|
10717
|
+
}
|
|
10718
|
+
return paint2(event.ok ? STATUS_OK : STATUS_ERR, parts.join(` ${SPACE_MARKS.tick} `), color);
|
|
10719
|
+
}
|
|
10720
|
+
case "hook": {
|
|
10721
|
+
const detail = event.detail ? ` (${event.detail})` : "";
|
|
10722
|
+
return paint2(
|
|
10723
|
+
LABEL_MUTED,
|
|
10724
|
+
truncate(`${SPACE_MARKS.tick} hook ${event.name} ${event.phase}${detail}`, width),
|
|
10725
|
+
color
|
|
10726
|
+
);
|
|
10727
|
+
}
|
|
10728
|
+
case "rate_limit":
|
|
10729
|
+
return paint2(
|
|
10730
|
+
event.severe ? STATUS_ERR : STATUS_WARN,
|
|
10731
|
+
truncate(`! rate limit ${SPACE_MARKS.tick} ${event.line}`, width),
|
|
10732
|
+
color
|
|
10733
|
+
);
|
|
10734
|
+
case "prompt":
|
|
10735
|
+
return paint2(
|
|
10736
|
+
LABEL_MUTED,
|
|
10737
|
+
`${SPACE_MARKS.tick} prompt sent ${SPACE_MARKS.tick} ${event.chars} chars`,
|
|
10738
|
+
color
|
|
10739
|
+
);
|
|
10740
|
+
case "hitl_reply":
|
|
10741
|
+
return paint2(HELMET_ACCENT, truncate(`\u21A9 ${event.line}`, width), color);
|
|
10742
|
+
default:
|
|
10743
|
+
return null;
|
|
10744
|
+
}
|
|
10745
|
+
}
|
|
10746
|
+
var StreamRenderer = class {
|
|
10747
|
+
pending = "";
|
|
10748
|
+
dropped = false;
|
|
10749
|
+
textOpen = false;
|
|
10750
|
+
decoder = new StringDecoder2("utf8");
|
|
10751
|
+
write;
|
|
10752
|
+
color;
|
|
10753
|
+
columns;
|
|
10754
|
+
constructor(opts = {}) {
|
|
10755
|
+
this.write = opts.write ?? ((text) => process.stdout.write(text));
|
|
10756
|
+
this.color = shouldUseWelcomeColor({
|
|
10757
|
+
stdoutIsTTY: opts.isTTY ?? Boolean(process.stdout.isTTY),
|
|
10758
|
+
env: opts.env
|
|
10759
|
+
});
|
|
10760
|
+
this.columns = opts.columns ?? process.stdout.columns ?? 80;
|
|
10761
|
+
}
|
|
10762
|
+
feed(chunk) {
|
|
10763
|
+
try {
|
|
10764
|
+
const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
10765
|
+
if (!text) return;
|
|
10766
|
+
const combined = this.pending + text;
|
|
10767
|
+
const lines = combined.split("\n");
|
|
10768
|
+
this.pending = lines.pop() ?? "";
|
|
10769
|
+
for (const line of lines) {
|
|
10770
|
+
if (this.dropped) {
|
|
10771
|
+
this.dropped = false;
|
|
10772
|
+
continue;
|
|
10773
|
+
}
|
|
10774
|
+
this.renderLine(line);
|
|
10775
|
+
}
|
|
10776
|
+
if (this.pending.length > PENDING_MAX_CHARS) {
|
|
10777
|
+
this.pending = "";
|
|
10778
|
+
this.dropped = true;
|
|
10779
|
+
}
|
|
10780
|
+
} catch {
|
|
10781
|
+
}
|
|
10782
|
+
}
|
|
10783
|
+
end() {
|
|
10784
|
+
try {
|
|
10785
|
+
const rest = this.pending + this.decoder.end();
|
|
10786
|
+
this.pending = "";
|
|
10787
|
+
if (rest && !this.dropped) this.renderLine(rest);
|
|
10788
|
+
this.dropped = false;
|
|
10789
|
+
this.closeText();
|
|
10790
|
+
} catch {
|
|
10791
|
+
}
|
|
10792
|
+
}
|
|
10793
|
+
renderLine(line) {
|
|
10794
|
+
for (const event of parseStreamLine(line)) {
|
|
10795
|
+
const rendered = renderStreamEvent(event, { color: this.color, columns: this.columns });
|
|
10796
|
+
if (rendered === null) continue;
|
|
10797
|
+
if (event.kind === "text") {
|
|
10798
|
+
this.write(rendered);
|
|
10799
|
+
this.textOpen = !rendered.endsWith("\n");
|
|
10800
|
+
continue;
|
|
10801
|
+
}
|
|
10802
|
+
this.closeText();
|
|
10803
|
+
this.write(`${rendered}
|
|
10804
|
+
`);
|
|
10805
|
+
}
|
|
10806
|
+
}
|
|
10807
|
+
closeText() {
|
|
10808
|
+
if (!this.textOpen) return;
|
|
10809
|
+
this.textOpen = false;
|
|
10810
|
+
this.write("\n");
|
|
10811
|
+
}
|
|
10812
|
+
};
|
|
10813
|
+
function createStreamRenderer(opts = {}) {
|
|
10814
|
+
return new StreamRenderer(opts);
|
|
10815
|
+
}
|
|
10816
|
+
|
|
10817
|
+
// src/plan-loop/backends.ts
|
|
10818
|
+
var HITL_KILL_GRACE_MS = 5e3;
|
|
10819
|
+
function resolveHitlRunOptions(hitl, transport) {
|
|
10820
|
+
return {
|
|
10821
|
+
transport,
|
|
10822
|
+
policy: hitl?.policy ?? "prompt",
|
|
10823
|
+
isTTY: hitl?.isTTY ?? Boolean(process.stdin.isTTY),
|
|
10824
|
+
readAnswer: hitl?.readAnswer ?? createTerminalAnswerReader(),
|
|
10825
|
+
write: hitl?.write ?? ((text) => process.stdout.write(text)),
|
|
10826
|
+
onPromptStart: hitl?.onPromptStart,
|
|
10827
|
+
onPromptEnd: hitl?.onPromptEnd,
|
|
10828
|
+
killAfterMs: hitl?.killAfterMs
|
|
10829
|
+
};
|
|
10830
|
+
}
|
|
10831
|
+
var CLAUDE_ENV_PASSTHROUGH = [
|
|
10832
|
+
"ANTHROPIC_BASE_URL",
|
|
10833
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
10834
|
+
"ANTHROPIC_API_KEY"
|
|
10835
|
+
];
|
|
10836
|
+
function redactSecrets(text, secrets) {
|
|
10837
|
+
let out = text;
|
|
10838
|
+
const ordered = secrets.filter((s) => s.value.length > 0).sort((a, b) => b.value.length - a.value.length);
|
|
10839
|
+
for (const s of ordered) {
|
|
10840
|
+
out = out.split(s.value).join(`[${s.label}]`);
|
|
10841
|
+
}
|
|
10842
|
+
return out;
|
|
10843
|
+
}
|
|
10844
|
+
var REDACT_HOLD_MAX_CHARS = 64 * 1024;
|
|
10845
|
+
var RedactingStreamBuffer = class {
|
|
10846
|
+
pending = "";
|
|
10847
|
+
decoder = new StringDecoder3("utf8");
|
|
10848
|
+
secrets;
|
|
10849
|
+
keep;
|
|
10850
|
+
constructor(secrets) {
|
|
10851
|
+
this.secrets = secrets.filter((s) => s.value.length > 0);
|
|
10852
|
+
this.keep = this.secrets.length > 0 ? Math.max(...this.secrets.map((s) => s.value.length)) - 1 : 0;
|
|
10853
|
+
}
|
|
10854
|
+
/** Append a chunk; returns the redacted text that is safe to emit now. */
|
|
10855
|
+
push(chunk) {
|
|
10856
|
+
const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
10857
|
+
if (this.secrets.length === 0) return text;
|
|
10858
|
+
this.pending += text;
|
|
10859
|
+
let cut = this.pending.lastIndexOf("\n") + 1;
|
|
10860
|
+
if (cut === 0) {
|
|
10861
|
+
if (this.pending.length < REDACT_HOLD_MAX_CHARS) return "";
|
|
10862
|
+
cut = this.pending.length - this.keep;
|
|
10863
|
+
}
|
|
10864
|
+
if (cut <= 0) return "";
|
|
10865
|
+
let moved = true;
|
|
10866
|
+
while (moved) {
|
|
10867
|
+
moved = false;
|
|
10868
|
+
for (const s of this.secrets) {
|
|
10869
|
+
let idx = this.pending.indexOf(s.value, Math.max(0, cut - s.value.length + 1));
|
|
10870
|
+
while (idx !== -1 && idx < cut) {
|
|
10871
|
+
if (idx + s.value.length > cut) {
|
|
10872
|
+
cut = idx;
|
|
10873
|
+
moved = true;
|
|
10874
|
+
break;
|
|
10875
|
+
}
|
|
10876
|
+
idx = this.pending.indexOf(s.value, idx + 1);
|
|
10877
|
+
}
|
|
10878
|
+
if (moved) break;
|
|
10879
|
+
}
|
|
10880
|
+
}
|
|
10881
|
+
if (cut <= 0) return "";
|
|
10882
|
+
const out = redactSecrets(this.pending.slice(0, cut), this.secrets);
|
|
10883
|
+
this.pending = this.pending.slice(cut);
|
|
10884
|
+
return out;
|
|
10885
|
+
}
|
|
10886
|
+
/** Emit whatever is still held back (call once, on stream end). */
|
|
10887
|
+
flush() {
|
|
10888
|
+
const rest = this.pending + this.decoder.end();
|
|
10889
|
+
this.pending = "";
|
|
10890
|
+
return this.secrets.length === 0 ? rest : redactSecrets(rest, this.secrets);
|
|
10891
|
+
}
|
|
10892
|
+
};
|
|
10893
|
+
function mergeChildEnv(overrides) {
|
|
10894
|
+
const merged = { ...process.env };
|
|
10895
|
+
if (!overrides) return merged;
|
|
10896
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
10897
|
+
if (value === void 0) delete merged[key];
|
|
10898
|
+
else merged[key] = value;
|
|
10899
|
+
}
|
|
10900
|
+
return merged;
|
|
10901
|
+
}
|
|
10902
|
+
var CLAUDE_NESTED_SESSION_KEYS = [
|
|
10903
|
+
"CLAUDECODE",
|
|
10904
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
10905
|
+
"CLAUDE_CODE_CHILD_SESSION",
|
|
8911
10906
|
"CLAUDE_CODE_BRIDGE_SESSION_ID"
|
|
8912
10907
|
];
|
|
8913
10908
|
function claudeChildEnv(overrides) {
|
|
@@ -8987,9 +10982,11 @@ async function whichBinary(bin) {
|
|
|
8987
10982
|
}
|
|
8988
10983
|
}
|
|
8989
10984
|
function spawnLogged(command, args, logPath, options = {}) {
|
|
8990
|
-
const spawnFn = options.spawnFn ??
|
|
10985
|
+
const spawnFn = options.spawnFn ?? spawn7;
|
|
8991
10986
|
const redact = options.redact ?? [];
|
|
8992
10987
|
const redactedError = (err) => new Error(redactSecrets(String(err), redact));
|
|
10988
|
+
const hitl = options.hitl;
|
|
10989
|
+
const stdinTransport = hitl?.transport.kind === "stdin-stream-json";
|
|
8993
10990
|
return new Promise((resolve3, reject) => {
|
|
8994
10991
|
let out;
|
|
8995
10992
|
let child;
|
|
@@ -9001,7 +10998,7 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
9001
10998
|
reject(redactedError(err));
|
|
9002
10999
|
});
|
|
9003
11000
|
child = spawnFn(command, args, {
|
|
9004
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
11001
|
+
stdio: [stdinTransport ? "pipe" : "ignore", "pipe", "pipe"],
|
|
9005
11002
|
...options.cwd ? { cwd: options.cwd } : {},
|
|
9006
11003
|
...options.env ? { env: options.env } : {}
|
|
9007
11004
|
});
|
|
@@ -9009,11 +11006,33 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
9009
11006
|
reject(redactedError(err));
|
|
9010
11007
|
return;
|
|
9011
11008
|
}
|
|
11009
|
+
const render = options.render ?? createStreamRenderer();
|
|
11010
|
+
const relay = hitl ? createRelay(hitl, child, redactedError) : null;
|
|
9012
11011
|
const emit = (text) => {
|
|
9013
11012
|
if (text.length === 0) return;
|
|
9014
|
-
process.stdout.write(text);
|
|
9015
11013
|
out.write(text);
|
|
11014
|
+
render.feed(text);
|
|
11015
|
+
relay?.feed(text);
|
|
9016
11016
|
};
|
|
11017
|
+
if (relay?.startError) {
|
|
11018
|
+
out.end();
|
|
11019
|
+
reject(relay.startError);
|
|
11020
|
+
return;
|
|
11021
|
+
}
|
|
11022
|
+
options.onSpawn?.({
|
|
11023
|
+
backend: options.backendId ?? "claude",
|
|
11024
|
+
pid: child.pid,
|
|
11025
|
+
stop: (stop) => {
|
|
11026
|
+
if (relay) {
|
|
11027
|
+
relay.stop(stop);
|
|
11028
|
+
return;
|
|
11029
|
+
}
|
|
11030
|
+
try {
|
|
11031
|
+
child.kill("SIGTERM");
|
|
11032
|
+
} catch {
|
|
11033
|
+
}
|
|
11034
|
+
}
|
|
11035
|
+
});
|
|
9017
11036
|
const buffers = [];
|
|
9018
11037
|
const tee = (stream) => {
|
|
9019
11038
|
if (!stream) return;
|
|
@@ -9033,14 +11052,138 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
9033
11052
|
});
|
|
9034
11053
|
child.on("close", (code) => {
|
|
9035
11054
|
for (const buffer of buffers) emit(buffer.flush());
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
11055
|
+
render.end();
|
|
11056
|
+
const finish = () => {
|
|
11057
|
+
out.end((err) => {
|
|
11058
|
+
const failure = err ?? logError;
|
|
11059
|
+
if (failure) reject(redactedError(failure));
|
|
11060
|
+
else if (relay) resolve3({ exitCode: code ?? 1, hitl: relay.record });
|
|
11061
|
+
else resolve3({ exitCode: code ?? 1 });
|
|
11062
|
+
});
|
|
11063
|
+
};
|
|
11064
|
+
if (relay) relay.closed().then(finish, finish);
|
|
11065
|
+
else finish();
|
|
9041
11066
|
});
|
|
9042
11067
|
});
|
|
9043
11068
|
}
|
|
11069
|
+
function createRelay(hitl, child, redactedError) {
|
|
11070
|
+
const record = emptyHitlRecord();
|
|
11071
|
+
const stdinTransport = hitl.transport.kind === "stdin-stream-json";
|
|
11072
|
+
const abort = new AbortController();
|
|
11073
|
+
const grace = hitl.killAfterMs ?? HITL_KILL_GRACE_MS;
|
|
11074
|
+
let startError = null;
|
|
11075
|
+
let closed = false;
|
|
11076
|
+
let stdinEnded = false;
|
|
11077
|
+
let pendingAsk = null;
|
|
11078
|
+
const timers = [];
|
|
11079
|
+
let resolveClosed = () => {
|
|
11080
|
+
};
|
|
11081
|
+
const closedPromise = new Promise((resolve3) => {
|
|
11082
|
+
resolveClosed = resolve3;
|
|
11083
|
+
});
|
|
11084
|
+
const stdin = child.stdin;
|
|
11085
|
+
if (hitl.transport.kind === "stdin-stream-json") {
|
|
11086
|
+
if (!stdin) {
|
|
11087
|
+
startError = new Error("claude child has no writable stdin (stream-json transport)");
|
|
11088
|
+
} else {
|
|
11089
|
+
stdin.on("error", () => {
|
|
11090
|
+
});
|
|
11091
|
+
try {
|
|
11092
|
+
stdin.write(userEventLine(hitl.transport.firstPrompt));
|
|
11093
|
+
} catch (err) {
|
|
11094
|
+
startError = redactedError(err);
|
|
11095
|
+
}
|
|
11096
|
+
}
|
|
11097
|
+
}
|
|
11098
|
+
const endStdin = () => {
|
|
11099
|
+
if (!stdinTransport || stdinEnded) return;
|
|
11100
|
+
stdinEnded = true;
|
|
11101
|
+
try {
|
|
11102
|
+
stdin?.end();
|
|
11103
|
+
} catch {
|
|
11104
|
+
}
|
|
11105
|
+
};
|
|
11106
|
+
const stopChild = () => {
|
|
11107
|
+
endStdin();
|
|
11108
|
+
const term = setTimeout(() => {
|
|
11109
|
+
if (closed) return;
|
|
11110
|
+
child.kill("SIGTERM");
|
|
11111
|
+
const kill = setTimeout(() => {
|
|
11112
|
+
if (!closed) child.kill("SIGKILL");
|
|
11113
|
+
}, grace);
|
|
11114
|
+
kill.unref?.();
|
|
11115
|
+
timers.push(kill);
|
|
11116
|
+
}, grace);
|
|
11117
|
+
term.unref?.();
|
|
11118
|
+
timers.push(term);
|
|
11119
|
+
};
|
|
11120
|
+
const watcher = new TurnWatcher(({ resultText, assistantTexts }) => {
|
|
11121
|
+
if (record.stop || closed) return;
|
|
11122
|
+
const gate = detectHitlGate(resultText, assistantTexts);
|
|
11123
|
+
if (!gate) {
|
|
11124
|
+
endStdin();
|
|
11125
|
+
return;
|
|
11126
|
+
}
|
|
11127
|
+
if (gate.detection === "fallback") record.fallbackDetections += 1;
|
|
11128
|
+
if (hitl.transport.kind === "none") {
|
|
11129
|
+
record.stop = noRelayStop(gate.askId, hitl.transport.backend);
|
|
11130
|
+
return;
|
|
11131
|
+
}
|
|
11132
|
+
const ask = askOperator(gate, {
|
|
11133
|
+
isTTY: hitl.isTTY,
|
|
11134
|
+
policy: hitl.policy,
|
|
11135
|
+
readAnswer: hitl.readAnswer,
|
|
11136
|
+
write: hitl.write,
|
|
11137
|
+
signal: abort.signal,
|
|
11138
|
+
onPromptStart: hitl.onPromptStart,
|
|
11139
|
+
onPromptEnd: hitl.onPromptEnd
|
|
11140
|
+
}).then(
|
|
11141
|
+
(outcome) => {
|
|
11142
|
+
if (record.stop) return;
|
|
11143
|
+
if (outcome.kind === "stop") {
|
|
11144
|
+
record.stop = outcome.stop;
|
|
11145
|
+
stopChild();
|
|
11146
|
+
return;
|
|
11147
|
+
}
|
|
11148
|
+
if (closed || !stdin) {
|
|
11149
|
+
record.stop = unansweredStop(gate.askId, "child exited");
|
|
11150
|
+
return;
|
|
11151
|
+
}
|
|
11152
|
+
record.replies.push(outcome.stamp);
|
|
11153
|
+
hitl.write(`\u2192 ${outcome.stamp.line}
|
|
11154
|
+
`);
|
|
11155
|
+
stdin.write(userEventLine(outcome.stamp.line));
|
|
11156
|
+
},
|
|
11157
|
+
(err) => {
|
|
11158
|
+
record.stop = unansweredStop(gate.askId, "child exited");
|
|
11159
|
+
hitl.write(`hitl relay: ${redactedError(err).message}
|
|
11160
|
+
`);
|
|
11161
|
+
stopChild();
|
|
11162
|
+
}
|
|
11163
|
+
);
|
|
11164
|
+
pendingAsk = ask;
|
|
11165
|
+
});
|
|
11166
|
+
child.on("close", () => {
|
|
11167
|
+
closed = true;
|
|
11168
|
+
for (const t of timers) clearTimeout(t);
|
|
11169
|
+
abort.abort();
|
|
11170
|
+
watcher.end();
|
|
11171
|
+
const settle = () => resolveClosed();
|
|
11172
|
+
if (pendingAsk) pendingAsk.then(settle, settle);
|
|
11173
|
+
else settle();
|
|
11174
|
+
});
|
|
11175
|
+
return {
|
|
11176
|
+
startError,
|
|
11177
|
+
record,
|
|
11178
|
+
feed: (text) => watcher.feed(text),
|
|
11179
|
+
closed: () => closedPromise,
|
|
11180
|
+
stop: (stop) => {
|
|
11181
|
+
if (closed || record.stop) return;
|
|
11182
|
+
record.stop = stop;
|
|
11183
|
+
stopChild();
|
|
11184
|
+
}
|
|
11185
|
+
};
|
|
11186
|
+
}
|
|
9044
11187
|
var cursorAgentBackend = {
|
|
9045
11188
|
id: "cursor-agent",
|
|
9046
11189
|
async resolve() {
|
|
@@ -9065,7 +11208,11 @@ var cursorAgentBackend = {
|
|
|
9065
11208
|
return spawnLogged("cursor-agent", args, opts.logPath, {
|
|
9066
11209
|
spawnFn: opts.spawnFn,
|
|
9067
11210
|
env,
|
|
9068
|
-
redact: claudeRedactions(env ?? process.env)
|
|
11211
|
+
redact: claudeRedactions(env ?? process.env),
|
|
11212
|
+
hitl: resolveHitlRunOptions(opts.hitl, { kind: "none", backend: "cursor-agent" }),
|
|
11213
|
+
render: opts.render,
|
|
11214
|
+
onSpawn: opts.onSpawn,
|
|
11215
|
+
backendId: "cursor-agent"
|
|
9069
11216
|
});
|
|
9070
11217
|
}
|
|
9071
11218
|
};
|
|
@@ -9116,7 +11263,7 @@ function checkClaudeVersion(bin, versionFn = defaultVersionFn) {
|
|
|
9116
11263
|
}
|
|
9117
11264
|
return { ok: true, version };
|
|
9118
11265
|
}
|
|
9119
|
-
function claudeHeadlessArgs(opts) {
|
|
11266
|
+
function claudeHeadlessArgs(opts = {}) {
|
|
9120
11267
|
const args = [
|
|
9121
11268
|
"-p",
|
|
9122
11269
|
"--output-format",
|
|
@@ -9124,7 +11271,10 @@ function claudeHeadlessArgs(opts) {
|
|
|
9124
11271
|
"--verbose",
|
|
9125
11272
|
"--dangerously-skip-permissions",
|
|
9126
11273
|
"--permission-prompts",
|
|
9127
|
-
"none"
|
|
11274
|
+
"none",
|
|
11275
|
+
"--input-format",
|
|
11276
|
+
"stream-json",
|
|
11277
|
+
"--replay-user-messages"
|
|
9128
11278
|
];
|
|
9129
11279
|
if (opts.maxTurns !== void 0) {
|
|
9130
11280
|
args.push("--max-turns", String(opts.maxTurns));
|
|
@@ -9135,15 +11285,14 @@ function claudeHeadlessArgs(opts) {
|
|
|
9135
11285
|
if (opts.model) {
|
|
9136
11286
|
args.push("--model", opts.model);
|
|
9137
11287
|
}
|
|
9138
|
-
args.push(opts.prompt);
|
|
9139
11288
|
return args;
|
|
9140
11289
|
}
|
|
9141
11290
|
async function missingClaudeAdapter(workspace, prompt) {
|
|
9142
11291
|
const m = /^\/([A-Za-z0-9][A-Za-z0-9_-]*)/.exec(prompt.trimStart());
|
|
9143
11292
|
if (!m?.[1]) return null;
|
|
9144
|
-
const rel =
|
|
11293
|
+
const rel = path44.join(".claude", "commands", `${m[1]}.md`);
|
|
9145
11294
|
try {
|
|
9146
|
-
await access9(
|
|
11295
|
+
await access9(path44.join(workspace, rel));
|
|
9147
11296
|
return null;
|
|
9148
11297
|
} catch {
|
|
9149
11298
|
return rel;
|
|
@@ -9180,15 +11329,22 @@ var claudeBackend = {
|
|
|
9180
11329
|
(key) => `${key}=${env[key] ? "set" : "unset"}`
|
|
9181
11330
|
).join(" ");
|
|
9182
11331
|
log(
|
|
9183
|
-
`claude tick: claude -p (bypass permissions, stream-json${capText ? `, ${capText}` : ""}). env: ${presence}`
|
|
11332
|
+
`claude tick: claude -p (bypass permissions, stream-json, stdin relay${capText ? `, ${capText}` : ""}). env: ${presence}`
|
|
9184
11333
|
);
|
|
9185
|
-
const args = claudeHeadlessArgs({
|
|
11334
|
+
const args = claudeHeadlessArgs({ model: opts.model, ...caps });
|
|
9186
11335
|
try {
|
|
9187
11336
|
return await spawnLogged("claude", args, opts.logPath, {
|
|
9188
11337
|
cwd: opts.workspace,
|
|
9189
11338
|
env,
|
|
9190
11339
|
spawnFn: opts.spawnFn,
|
|
9191
|
-
redact
|
|
11340
|
+
redact,
|
|
11341
|
+
hitl: resolveHitlRunOptions(opts.hitl, {
|
|
11342
|
+
kind: "stdin-stream-json",
|
|
11343
|
+
firstPrompt: opts.prompt
|
|
11344
|
+
}),
|
|
11345
|
+
render: opts.render,
|
|
11346
|
+
onSpawn: opts.onSpawn,
|
|
11347
|
+
backendId: "claude"
|
|
9192
11348
|
});
|
|
9193
11349
|
} catch (err) {
|
|
9194
11350
|
throw new Error(`claude backend failed to start: ${redactSecrets(String(err), redact)}`);
|
|
@@ -9258,14 +11414,464 @@ async function detectAgentBackend(requested, whichFn = whichBinary) {
|
|
|
9258
11414
|
}
|
|
9259
11415
|
|
|
9260
11416
|
// src/plan-loop/run-loop.ts
|
|
9261
|
-
import { mkdir as mkdir6, readFile as
|
|
9262
|
-
import
|
|
11417
|
+
import { mkdir as mkdir6, readFile as readFile27, rm as rm2, unlink as unlink2 } from "fs/promises";
|
|
11418
|
+
import path49 from "path";
|
|
11419
|
+
|
|
11420
|
+
// src/mission-control/live-run.ts
|
|
11421
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
11422
|
+
import path46 from "path";
|
|
11423
|
+
|
|
11424
|
+
// src/plan-loop/plan-state.ts
|
|
11425
|
+
import { readFile as readFile24, readdir as readdir11 } from "fs/promises";
|
|
11426
|
+
import path45 from "path";
|
|
11427
|
+
function countPendingTodos(raw) {
|
|
11428
|
+
const lines = raw.split(/\r?\n/);
|
|
11429
|
+
let inFront = 0;
|
|
11430
|
+
let cont = false;
|
|
11431
|
+
let n = 0;
|
|
11432
|
+
for (const line of lines) {
|
|
11433
|
+
if (line === "---") {
|
|
11434
|
+
inFront += 1;
|
|
11435
|
+
continue;
|
|
11436
|
+
}
|
|
11437
|
+
if (inFront !== 1) continue;
|
|
11438
|
+
const idMatch = line.match(/^- id:\s*(\S+)/);
|
|
11439
|
+
if (idMatch?.[1]) {
|
|
11440
|
+
cont = idMatch[1] === "dogfood-poc";
|
|
11441
|
+
}
|
|
11442
|
+
if (/content:.*[Cc]ont[ií]nuo/.test(line)) {
|
|
11443
|
+
cont = true;
|
|
11444
|
+
}
|
|
11445
|
+
if (/status:\s*(pending|in_progress)\b/.test(line) && !cont) {
|
|
11446
|
+
n += 1;
|
|
11447
|
+
}
|
|
11448
|
+
}
|
|
11449
|
+
return n;
|
|
11450
|
+
}
|
|
11451
|
+
async function findActivePlanFile(plansDir) {
|
|
11452
|
+
if (!await fileExists(plansDir)) return null;
|
|
11453
|
+
const files = (await readdir11(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
11454
|
+
return files[0] ? path45.join(plansDir, files[0]) : null;
|
|
11455
|
+
}
|
|
11456
|
+
async function readPlan(planPath) {
|
|
11457
|
+
return readFile24(planPath, "utf8");
|
|
11458
|
+
}
|
|
11459
|
+
|
|
11460
|
+
// src/mission-control/live-feed.ts
|
|
11461
|
+
import { StringDecoder as StringDecoder4 } from "string_decoder";
|
|
11462
|
+
var LIVE_LOG_CAPACITY = 500;
|
|
11463
|
+
var PENDING_MAX_CHARS2 = 4 * 1024 * 1024;
|
|
11464
|
+
var LiveRunFeed = class {
|
|
11465
|
+
log;
|
|
11466
|
+
crew;
|
|
11467
|
+
gate = null;
|
|
11468
|
+
hitlReplies = 0;
|
|
11469
|
+
pending = "";
|
|
11470
|
+
dropped = false;
|
|
11471
|
+
decoder = new StringDecoder4("utf8");
|
|
11472
|
+
columns;
|
|
11473
|
+
version = 0;
|
|
11474
|
+
constructor(opts = {}) {
|
|
11475
|
+
this.columns = Math.max(40, opts.columns ?? 80);
|
|
11476
|
+
this.log = new ScrollRegion(opts.capacity ?? LIVE_LOG_CAPACITY);
|
|
11477
|
+
this.crew = {
|
|
11478
|
+
backend: opts.backend ?? null,
|
|
11479
|
+
pid: void 0,
|
|
11480
|
+
phase: "starting",
|
|
11481
|
+
startedAt: (opts.now ?? Date.now)(),
|
|
11482
|
+
toolCalls: 0,
|
|
11483
|
+
currentTool: null,
|
|
11484
|
+
lastResult: null,
|
|
11485
|
+
rateLimit: null,
|
|
11486
|
+
logPath: opts.logPath ?? null
|
|
11487
|
+
};
|
|
11488
|
+
}
|
|
11489
|
+
/** Bumps on every change; a paint loop can skip a frame when nothing moved. */
|
|
11490
|
+
get revision() {
|
|
11491
|
+
return this.version;
|
|
11492
|
+
}
|
|
11493
|
+
setProcess(info) {
|
|
11494
|
+
this.crew.backend = info.backend;
|
|
11495
|
+
this.crew.pid = info.pid;
|
|
11496
|
+
if (this.crew.phase === "starting") this.crew.phase = "running";
|
|
11497
|
+
this.version += 1;
|
|
11498
|
+
}
|
|
11499
|
+
setPhase(phase) {
|
|
11500
|
+
this.crew.phase = phase;
|
|
11501
|
+
this.version += 1;
|
|
11502
|
+
}
|
|
11503
|
+
openGate(gate) {
|
|
11504
|
+
this.gate = gate;
|
|
11505
|
+
this.crew.phase = "gate";
|
|
11506
|
+
this.version += 1;
|
|
11507
|
+
}
|
|
11508
|
+
closeGate() {
|
|
11509
|
+
this.gate = null;
|
|
11510
|
+
if (this.crew.phase === "gate") this.crew.phase = "running";
|
|
11511
|
+
this.version += 1;
|
|
11512
|
+
}
|
|
11513
|
+
/** Append a status line the run lane produced itself (never from the stream). */
|
|
11514
|
+
note(text, kind = "note") {
|
|
11515
|
+
for (const line of text.split(/\r?\n/)) {
|
|
11516
|
+
const trimmed = line.trimEnd();
|
|
11517
|
+
if (trimmed) this.log.push({ kind, text: trimmed });
|
|
11518
|
+
}
|
|
11519
|
+
this.version += 1;
|
|
11520
|
+
}
|
|
11521
|
+
feed(chunk) {
|
|
11522
|
+
try {
|
|
11523
|
+
const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
11524
|
+
if (!text) return;
|
|
11525
|
+
const combined = this.pending + text;
|
|
11526
|
+
const lines = combined.split("\n");
|
|
11527
|
+
this.pending = lines.pop() ?? "";
|
|
11528
|
+
for (const line of lines) {
|
|
11529
|
+
if (this.dropped) {
|
|
11530
|
+
this.dropped = false;
|
|
11531
|
+
continue;
|
|
11532
|
+
}
|
|
11533
|
+
this.takeLine(line);
|
|
11534
|
+
}
|
|
11535
|
+
if (this.pending.length > PENDING_MAX_CHARS2) {
|
|
11536
|
+
this.pending = "";
|
|
11537
|
+
this.dropped = true;
|
|
11538
|
+
}
|
|
11539
|
+
} catch {
|
|
11540
|
+
}
|
|
11541
|
+
}
|
|
11542
|
+
end() {
|
|
11543
|
+
try {
|
|
11544
|
+
const rest = this.pending + this.decoder.end();
|
|
11545
|
+
this.pending = "";
|
|
11546
|
+
if (rest && !this.dropped) this.takeLine(rest);
|
|
11547
|
+
this.dropped = false;
|
|
11548
|
+
this.crew.currentTool = null;
|
|
11549
|
+
if (this.crew.phase !== "done") this.crew.phase = "done";
|
|
11550
|
+
this.version += 1;
|
|
11551
|
+
} catch {
|
|
11552
|
+
}
|
|
11553
|
+
}
|
|
11554
|
+
takeLine(line) {
|
|
11555
|
+
for (const event of parseStreamLine(line)) {
|
|
11556
|
+
const rendered = renderStreamEvent(event, { color: false, columns: this.columns });
|
|
11557
|
+
if (rendered === null) continue;
|
|
11558
|
+
switch (event.kind) {
|
|
11559
|
+
case "text":
|
|
11560
|
+
this.note(rendered, "text");
|
|
11561
|
+
break;
|
|
11562
|
+
case "tool_use":
|
|
11563
|
+
this.crew.toolCalls += 1;
|
|
11564
|
+
this.crew.currentTool = { name: event.name, summary: event.summary };
|
|
11565
|
+
this.note(rendered, "tool");
|
|
11566
|
+
break;
|
|
11567
|
+
case "tool_result":
|
|
11568
|
+
this.crew.currentTool = null;
|
|
11569
|
+
this.note(rendered, event.ok ? "tool" : "error");
|
|
11570
|
+
break;
|
|
11571
|
+
case "result":
|
|
11572
|
+
this.crew.currentTool = null;
|
|
11573
|
+
this.crew.lastResult = { ok: event.ok, status: event.status };
|
|
11574
|
+
if (event.costUsd !== void 0) this.crew.costUsd = event.costUsd;
|
|
11575
|
+
if (event.turns !== void 0) this.crew.turns = event.turns;
|
|
11576
|
+
this.note(rendered, event.ok ? "result" : "error");
|
|
11577
|
+
break;
|
|
11578
|
+
case "hook":
|
|
11579
|
+
this.note(rendered, "hook");
|
|
11580
|
+
break;
|
|
11581
|
+
case "rate_limit":
|
|
11582
|
+
this.crew.rateLimit = { line: event.line, severe: event.severe };
|
|
11583
|
+
this.note(rendered, "rate_limit");
|
|
11584
|
+
break;
|
|
11585
|
+
case "prompt":
|
|
11586
|
+
this.note(rendered, "prompt");
|
|
11587
|
+
break;
|
|
11588
|
+
case "hitl_reply":
|
|
11589
|
+
this.hitlReplies += 1;
|
|
11590
|
+
this.note(rendered, "hitl");
|
|
11591
|
+
break;
|
|
11592
|
+
default:
|
|
11593
|
+
break;
|
|
11594
|
+
}
|
|
11595
|
+
}
|
|
11596
|
+
}
|
|
11597
|
+
};
|
|
11598
|
+
function emptyLiveMission() {
|
|
11599
|
+
return {
|
|
11600
|
+
planFile: null,
|
|
11601
|
+
mode: null,
|
|
11602
|
+
todos: { done: 0, total: 0 },
|
|
11603
|
+
currentTodo: null,
|
|
11604
|
+
nextTodo: null,
|
|
11605
|
+
queue: null
|
|
11606
|
+
};
|
|
11607
|
+
}
|
|
11608
|
+
|
|
11609
|
+
// src/mission-control/live-run.ts
|
|
11610
|
+
var LIVE_FRAME_MS = 120;
|
|
11611
|
+
var LIVE_PROGRESS_TTL_MS = 2e3;
|
|
11612
|
+
function shouldUseLiveTui(opts = {}) {
|
|
11613
|
+
if (opts.plain) return false;
|
|
11614
|
+
const env = opts.env ?? process.env;
|
|
11615
|
+
const stdoutIsTTY = opts.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
|
|
11616
|
+
const stdinIsTTY = opts.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
11617
|
+
if (!stdoutIsTTY || !stdinIsTTY) return false;
|
|
11618
|
+
return shouldUseWelcomeColor({ stdoutIsTTY, env });
|
|
11619
|
+
}
|
|
11620
|
+
function defaultMissionIo() {
|
|
11621
|
+
return {
|
|
11622
|
+
readFile: async (file) => {
|
|
11623
|
+
try {
|
|
11624
|
+
return await readFile25(file, "utf8");
|
|
11625
|
+
} catch {
|
|
11626
|
+
return null;
|
|
11627
|
+
}
|
|
11628
|
+
},
|
|
11629
|
+
findActivePlan: findActivePlanFile
|
|
11630
|
+
};
|
|
11631
|
+
}
|
|
11632
|
+
var QUEUE_CURSOR_RE = /^- \*\*Queue cursor:\*\*\s*(.+)$/m;
|
|
11633
|
+
var QUEUE_STATUS_RE = /^- \*\*Queue status:\*\*\s*(.+)$/m;
|
|
11634
|
+
var MODE_RE = /^- \*\*Mode:\*\*\s*(.+)$/m;
|
|
11635
|
+
var QUEUE_DONE_STATUSES = /* @__PURE__ */ new Set(["exhausted", "completed", "done"]);
|
|
11636
|
+
function firstWord(raw) {
|
|
11637
|
+
const word = raw?.trim().split(/\s+/)[0]?.replace(/[`*]/g, "");
|
|
11638
|
+
return word ? word : null;
|
|
11639
|
+
}
|
|
11640
|
+
async function readLiveMission(root, io = defaultMissionIo()) {
|
|
11641
|
+
const mission = emptyLiveMission();
|
|
11642
|
+
const handoff = await io.readFile(path46.join(root, ".cursor", "HANDOFF.md")) ?? "";
|
|
11643
|
+
const named = extractHandoffNamedPlans(handoff);
|
|
11644
|
+
mission.mode = firstWord(MODE_RE.exec(handoff)?.[1]);
|
|
11645
|
+
let cursorIndex = null;
|
|
11646
|
+
let cursorPlan = null;
|
|
11647
|
+
const cursorRaw = QUEUE_CURSOR_RE.exec(handoff)?.[1]?.trim() ?? "";
|
|
11648
|
+
const indexMatch = /^(\d+)\b/.exec(cursorRaw);
|
|
11649
|
+
if (indexMatch?.[1]) cursorIndex = Number(indexMatch[1]);
|
|
11650
|
+
const currentMatch = /current:\s*`?([^`()]+?)`?\s*\)/i.exec(cursorRaw);
|
|
11651
|
+
if (currentMatch?.[1]) {
|
|
11652
|
+
const base = currentMatch[1].trim().split("/").pop() ?? "";
|
|
11653
|
+
if (/\.plan\.md$/i.test(base)) cursorPlan = base;
|
|
11654
|
+
}
|
|
11655
|
+
const queueStatus = firstWord(QUEUE_STATUS_RE.exec(handoff)?.[1]);
|
|
11656
|
+
const queue = named.runQueue;
|
|
11657
|
+
const finished = queueStatus !== null && QUEUE_DONE_STATUSES.has(queueStatus.toLowerCase());
|
|
11658
|
+
let checklist = [];
|
|
11659
|
+
if (queue.length > 0) {
|
|
11660
|
+
let cursor = null;
|
|
11661
|
+
if (cursorIndex !== null && cursorIndex >= 0 && cursorIndex < queue.length) {
|
|
11662
|
+
cursor = cursorIndex;
|
|
11663
|
+
} else if (cursorPlan && queue.includes(cursorPlan)) {
|
|
11664
|
+
cursor = queue.indexOf(cursorPlan);
|
|
11665
|
+
}
|
|
11666
|
+
const done = finished ? queue.length : cursor ?? 0;
|
|
11667
|
+
mission.queue = {
|
|
11668
|
+
done,
|
|
11669
|
+
total: queue.length,
|
|
11670
|
+
current: cursor !== null ? queue[cursor] ?? null : cursorPlan,
|
|
11671
|
+
status: queueStatus
|
|
11672
|
+
};
|
|
11673
|
+
checklist = queue.map((file, i) => ({
|
|
11674
|
+
label: file,
|
|
11675
|
+
state: finished || cursor !== null && i < cursor ? "done" : i === cursor ? "current" : "pending"
|
|
11676
|
+
}));
|
|
11677
|
+
}
|
|
11678
|
+
const plansDir = path46.join(root, ".cursor", "plans");
|
|
11679
|
+
const planBase = (mission.queue && !finished ? mission.queue.current : null) ?? named.active;
|
|
11680
|
+
const planPath = planBase ? path46.join(plansDir, planBase) : await io.findActivePlan(plansDir);
|
|
11681
|
+
if (planPath) {
|
|
11682
|
+
const raw = await io.readFile(planPath);
|
|
11683
|
+
if (raw !== null) {
|
|
11684
|
+
mission.planFile = path46.basename(planPath);
|
|
11685
|
+
const { todos } = parsePlanFrontmatter2(raw);
|
|
11686
|
+
const open = todos.filter((t) => t.status !== "cancelled");
|
|
11687
|
+
mission.todos = {
|
|
11688
|
+
done: open.filter((t) => t.status === "completed").length,
|
|
11689
|
+
total: open.length
|
|
11690
|
+
};
|
|
11691
|
+
mission.currentTodo = todos.find((t) => t.status === "in_progress")?.id ?? null;
|
|
11692
|
+
mission.nextTodo = todos.find((t) => t.status === "pending")?.id ?? null;
|
|
11693
|
+
if (checklist.length === 0) {
|
|
11694
|
+
checklist = open.map((t) => ({
|
|
11695
|
+
label: t.id,
|
|
11696
|
+
state: t.status === "completed" ? "done" : t.status === "in_progress" ? "current" : "pending"
|
|
11697
|
+
}));
|
|
11698
|
+
const current = checklist.findIndex((row) => row.state === "current");
|
|
11699
|
+
if (current > 1) checklist = checklist.slice(current - 1);
|
|
11700
|
+
}
|
|
11701
|
+
}
|
|
11702
|
+
}
|
|
11703
|
+
return { mission, checklist };
|
|
11704
|
+
}
|
|
11705
|
+
function createLiveMissionLoader(root, opts = {}) {
|
|
11706
|
+
const ttl = opts.ttlMs ?? LIVE_PROGRESS_TTL_MS;
|
|
11707
|
+
const now2 = opts.now ?? Date.now;
|
|
11708
|
+
let cached = null;
|
|
11709
|
+
let readAt = Number.NEGATIVE_INFINITY;
|
|
11710
|
+
let inFlight = null;
|
|
11711
|
+
return async () => {
|
|
11712
|
+
if (cached && now2() - readAt < ttl) return cached;
|
|
11713
|
+
if (inFlight) return inFlight;
|
|
11714
|
+
inFlight = readLiveMission(root, opts.io).then((state) => {
|
|
11715
|
+
cached = state;
|
|
11716
|
+
readAt = now2();
|
|
11717
|
+
return state;
|
|
11718
|
+
}).finally(() => {
|
|
11719
|
+
inFlight = null;
|
|
11720
|
+
});
|
|
11721
|
+
return cached ?? inFlight;
|
|
11722
|
+
};
|
|
11723
|
+
}
|
|
11724
|
+
async function withLiveTui(opts, run) {
|
|
11725
|
+
const write = opts.write ?? ((chunk) => process.stdout.write(chunk));
|
|
11726
|
+
const stdin = opts.stdin ?? process.stdin;
|
|
11727
|
+
const env = opts.env ?? process.env;
|
|
11728
|
+
const now2 = opts.now ?? Date.now;
|
|
11729
|
+
const columns = opts.columns ?? process.stdout.columns ?? 80;
|
|
11730
|
+
const rows = opts.rows ?? process.stdout.rows ?? 24;
|
|
11731
|
+
const feed = new LiveRunFeed({
|
|
11732
|
+
columns,
|
|
11733
|
+
now: now2,
|
|
11734
|
+
backend: opts.backend,
|
|
11735
|
+
logPath: path46.relative(opts.root, opts.logPath) || opts.logPath
|
|
11736
|
+
});
|
|
11737
|
+
opts.onFeed?.(feed);
|
|
11738
|
+
const missionLoader = opts.missionLoader ?? createLiveMissionLoader(opts.root, { now: now2 });
|
|
11739
|
+
const input = new InputLine();
|
|
11740
|
+
let pending = null;
|
|
11741
|
+
let spawnInfo = null;
|
|
11742
|
+
let quitRequested = null;
|
|
11743
|
+
let handle = null;
|
|
11744
|
+
const repaint = () => {
|
|
11745
|
+
void handle?.repaint();
|
|
11746
|
+
};
|
|
11747
|
+
const settleGate = (answer) => {
|
|
11748
|
+
const open = pending;
|
|
11749
|
+
if (!open) return;
|
|
11750
|
+
pending = null;
|
|
11751
|
+
feed.closeGate();
|
|
11752
|
+
open.settle(answer);
|
|
11753
|
+
};
|
|
11754
|
+
const readAnswer = (gate, readOpts) => new Promise((resolve3) => {
|
|
11755
|
+
const onAbort = () => settleGate({ kind: "eof" });
|
|
11756
|
+
if (readOpts.signal?.aborted) {
|
|
11757
|
+
resolve3({ kind: "eof" });
|
|
11758
|
+
return;
|
|
11759
|
+
}
|
|
11760
|
+
input.reset();
|
|
11761
|
+
pending = {
|
|
11762
|
+
gate,
|
|
11763
|
+
settle: (answer) => {
|
|
11764
|
+
readOpts.signal?.removeEventListener("abort", onAbort);
|
|
11765
|
+
resolve3(answer);
|
|
11766
|
+
}
|
|
11767
|
+
};
|
|
11768
|
+
readOpts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
11769
|
+
feed.openGate(gate);
|
|
11770
|
+
repaint();
|
|
11771
|
+
});
|
|
11772
|
+
const stopRun = (stop) => {
|
|
11773
|
+
if (pending) {
|
|
11774
|
+
settleGate({ kind: "sigint" });
|
|
11775
|
+
return;
|
|
11776
|
+
}
|
|
11777
|
+
if (quitRequested) return;
|
|
11778
|
+
quitRequested = stop;
|
|
11779
|
+
feed.setPhase("stopping");
|
|
11780
|
+
feed.note(stop.message);
|
|
11781
|
+
spawnInfo?.stop(stop);
|
|
11782
|
+
};
|
|
11783
|
+
const hooks = {
|
|
11784
|
+
write,
|
|
11785
|
+
stdin,
|
|
11786
|
+
now: now2,
|
|
11787
|
+
setIntervalFn: opts.setIntervalFn,
|
|
11788
|
+
clearIntervalFn: opts.clearIntervalFn,
|
|
11789
|
+
onKey: (chunk) => {
|
|
11790
|
+
if (!pending) return false;
|
|
11791
|
+
const result = input.handleKey(chunk);
|
|
11792
|
+
if (result === "submit") settleGate({ kind: "line", text: input.value });
|
|
11793
|
+
else if (result === "sigint") settleGate({ kind: "sigint" });
|
|
11794
|
+
else if (result === "eof") settleGate({ kind: "eof" });
|
|
11795
|
+
else if (result === "edit") repaint();
|
|
11796
|
+
return true;
|
|
11797
|
+
},
|
|
11798
|
+
onQuit: (key) => {
|
|
11799
|
+
write("\n");
|
|
11800
|
+
stopRun(operatorQuitStop(key));
|
|
11801
|
+
}
|
|
11802
|
+
};
|
|
11803
|
+
const loadView = async () => {
|
|
11804
|
+
const state = await missionLoader();
|
|
11805
|
+
return {
|
|
11806
|
+
mission: state.mission,
|
|
11807
|
+
checklist: state.checklist,
|
|
11808
|
+
gate: pending ? {
|
|
11809
|
+
askId: pending.gate.askId,
|
|
11810
|
+
labels: pending.gate.labels,
|
|
11811
|
+
detection: pending.gate.detection,
|
|
11812
|
+
input
|
|
11813
|
+
} : null,
|
|
11814
|
+
flightLog: feed.log,
|
|
11815
|
+
crew: feed.crew,
|
|
11816
|
+
error: null
|
|
11817
|
+
};
|
|
11818
|
+
};
|
|
11819
|
+
const seams = {
|
|
11820
|
+
render: feed,
|
|
11821
|
+
log: (line) => feed.note(line),
|
|
11822
|
+
onSpawn: (info) => {
|
|
11823
|
+
spawnInfo = info;
|
|
11824
|
+
feed.setProcess({ backend: info.backend, pid: info.pid });
|
|
11825
|
+
if (quitRequested) info.stop(quitRequested);
|
|
11826
|
+
},
|
|
11827
|
+
hitl: {
|
|
11828
|
+
policy: opts.noHitl ? "off" : "prompt",
|
|
11829
|
+
isTTY: true,
|
|
11830
|
+
readAnswer,
|
|
11831
|
+
write: (text) => feed.note(text, "hitl")
|
|
11832
|
+
}
|
|
11833
|
+
};
|
|
11834
|
+
const onSignal = (signal) => {
|
|
11835
|
+
handle?.stop();
|
|
11836
|
+
stopRun(operatorQuitStop(signal));
|
|
11837
|
+
};
|
|
11838
|
+
const onSigint = () => onSignal("SIGINT");
|
|
11839
|
+
const onSigterm = () => onSignal("SIGTERM");
|
|
11840
|
+
const signals = opts.signals ?? true;
|
|
11841
|
+
if (signals) {
|
|
11842
|
+
process.prependListener("SIGINT", onSigint);
|
|
11843
|
+
process.prependListener("SIGTERM", onSigterm);
|
|
11844
|
+
}
|
|
11845
|
+
try {
|
|
11846
|
+
handle = await runMcTuiLoop({
|
|
11847
|
+
loadView,
|
|
11848
|
+
render: (view, renderOpts) => renderMcLive(view, { ...renderOpts, columns, rows, now: now2() }),
|
|
11849
|
+
stdoutIsTTY: true,
|
|
11850
|
+
env,
|
|
11851
|
+
intervalMs: opts.intervalMs ?? LIVE_FRAME_MS,
|
|
11852
|
+
renderOpts: { stdoutIsTTY: true, env, columns },
|
|
11853
|
+
hooks
|
|
11854
|
+
});
|
|
11855
|
+
const result = await run(seams);
|
|
11856
|
+
feed.setPhase("done");
|
|
11857
|
+
if (handle) await handle.repaint();
|
|
11858
|
+
return result;
|
|
11859
|
+
} finally {
|
|
11860
|
+
if (pending) settleGate({ kind: "eof" });
|
|
11861
|
+
handle?.stop();
|
|
11862
|
+
if (signals) {
|
|
11863
|
+
process.off("SIGINT", onSigint);
|
|
11864
|
+
process.off("SIGTERM", onSigterm);
|
|
11865
|
+
}
|
|
11866
|
+
write("\n");
|
|
11867
|
+
}
|
|
11868
|
+
}
|
|
9263
11869
|
|
|
9264
11870
|
// src/plan-loop/external-review.ts
|
|
9265
|
-
import { spawn as
|
|
9266
|
-
import
|
|
9267
|
-
var CANONICAL_REL =
|
|
9268
|
-
var FALLBACK_REL =
|
|
11871
|
+
import { spawn as spawn8 } from "child_process";
|
|
11872
|
+
import path47 from "path";
|
|
11873
|
+
var CANONICAL_REL = path47.join(".cursor", "scripts", "plan-external-review.sh");
|
|
11874
|
+
var FALLBACK_REL = path47.join("scripts", "plan-external-review.sh");
|
|
9269
11875
|
function isPlanExhaustedReason(reason) {
|
|
9270
11876
|
const r = reason.trim().toLowerCase();
|
|
9271
11877
|
if (!r) return false;
|
|
@@ -9283,12 +11889,12 @@ function shouldArmExternalPlanReview(input) {
|
|
|
9283
11889
|
return false;
|
|
9284
11890
|
}
|
|
9285
11891
|
async function armExternalPlanReview(root, options = {}) {
|
|
9286
|
-
const spawnFn = options.spawnFn ??
|
|
11892
|
+
const spawnFn = options.spawnFn ?? spawn8;
|
|
9287
11893
|
const existsFn = options.existsFn ?? fileExists;
|
|
9288
11894
|
const log = options.log ?? ((line) => console.log(line));
|
|
9289
11895
|
const force = options.force === true;
|
|
9290
|
-
const canonicalPath =
|
|
9291
|
-
const fallbackPath =
|
|
11896
|
+
const canonicalPath = path47.join(root, CANONICAL_REL);
|
|
11897
|
+
const fallbackPath = path47.join(root, FALLBACK_REL);
|
|
9292
11898
|
let scriptPath = null;
|
|
9293
11899
|
let scriptRel = CANONICAL_REL;
|
|
9294
11900
|
if (await existsFn(canonicalPath)) {
|
|
@@ -9341,7 +11947,7 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
9341
11947
|
}
|
|
9342
11948
|
|
|
9343
11949
|
// src/plan-loop/persona-banners.ts
|
|
9344
|
-
import
|
|
11950
|
+
import path48 from "path";
|
|
9345
11951
|
import {
|
|
9346
11952
|
blue,
|
|
9347
11953
|
cyan as cyan4,
|
|
@@ -9377,7 +11983,7 @@ function resolveColor(name, fallback) {
|
|
|
9377
11983
|
async function resolveCliPersonaId(root) {
|
|
9378
11984
|
try {
|
|
9379
11985
|
const cfg = await readJson(
|
|
9380
|
-
|
|
11986
|
+
path48.join(root, ".cursor", "context", "config.json")
|
|
9381
11987
|
);
|
|
9382
11988
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
9383
11989
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -9388,7 +11994,7 @@ async function resolveCliPersonaId(root) {
|
|
|
9388
11994
|
}
|
|
9389
11995
|
async function loadPersonaPack(root, personaId) {
|
|
9390
11996
|
try {
|
|
9391
|
-
const personaPath =
|
|
11997
|
+
const personaPath = path48.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
9392
11998
|
const pack = await readJson(personaPath);
|
|
9393
11999
|
if (!pack || typeof pack.id !== "string") return null;
|
|
9394
12000
|
return pack;
|
|
@@ -9436,44 +12042,8 @@ function createPersonaBannerPrinter(persona) {
|
|
|
9436
12042
|
};
|
|
9437
12043
|
}
|
|
9438
12044
|
|
|
9439
|
-
// src/plan-loop/plan-state.ts
|
|
9440
|
-
import { readFile as readFile22, readdir as readdir9 } from "fs/promises";
|
|
9441
|
-
import path46 from "path";
|
|
9442
|
-
function countPendingTodos(raw) {
|
|
9443
|
-
const lines = raw.split(/\r?\n/);
|
|
9444
|
-
let inFront = 0;
|
|
9445
|
-
let cont = false;
|
|
9446
|
-
let n = 0;
|
|
9447
|
-
for (const line of lines) {
|
|
9448
|
-
if (line === "---") {
|
|
9449
|
-
inFront += 1;
|
|
9450
|
-
continue;
|
|
9451
|
-
}
|
|
9452
|
-
if (inFront !== 1) continue;
|
|
9453
|
-
const idMatch = line.match(/^- id:\s*(\S+)/);
|
|
9454
|
-
if (idMatch?.[1]) {
|
|
9455
|
-
cont = idMatch[1] === "dogfood-poc";
|
|
9456
|
-
}
|
|
9457
|
-
if (/content:.*[Cc]ont[ií]nuo/.test(line)) {
|
|
9458
|
-
cont = true;
|
|
9459
|
-
}
|
|
9460
|
-
if (/status:\s*(pending|in_progress)\b/.test(line) && !cont) {
|
|
9461
|
-
n += 1;
|
|
9462
|
-
}
|
|
9463
|
-
}
|
|
9464
|
-
return n;
|
|
9465
|
-
}
|
|
9466
|
-
async function findActivePlanFile(plansDir) {
|
|
9467
|
-
if (!await fileExists(plansDir)) return null;
|
|
9468
|
-
const files = (await readdir9(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
9469
|
-
return files[0] ? path46.join(plansDir, files[0]) : null;
|
|
9470
|
-
}
|
|
9471
|
-
async function readPlan(planPath) {
|
|
9472
|
-
return readFile22(planPath, "utf8");
|
|
9473
|
-
}
|
|
9474
|
-
|
|
9475
12045
|
// src/plan-loop/sentinel.ts
|
|
9476
|
-
import { readFile as
|
|
12046
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
9477
12047
|
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—\-].*)?)/i;
|
|
9478
12048
|
function takeFromText(text) {
|
|
9479
12049
|
if (!text) return null;
|
|
@@ -9539,14 +12109,14 @@ function parseTickResultStatus(content) {
|
|
|
9539
12109
|
}
|
|
9540
12110
|
async function parseTickResultStatusFromLogFile(logPath) {
|
|
9541
12111
|
try {
|
|
9542
|
-
return parseTickResultStatus(await
|
|
12112
|
+
return parseTickResultStatus(await readFile26(logPath, "utf8"));
|
|
9543
12113
|
} catch {
|
|
9544
12114
|
return null;
|
|
9545
12115
|
}
|
|
9546
12116
|
}
|
|
9547
12117
|
async function parseSentinelFromLogFile(logPath) {
|
|
9548
12118
|
try {
|
|
9549
|
-
const content = await
|
|
12119
|
+
const content = await readFile26(logPath, "utf8");
|
|
9550
12120
|
return parseSentinelFromLog(content);
|
|
9551
12121
|
} catch {
|
|
9552
12122
|
return { kind: "missing" };
|
|
@@ -9560,6 +12130,40 @@ function formatSentinelLine(sentinel) {
|
|
|
9560
12130
|
|
|
9561
12131
|
// src/plan-loop/run-loop.ts
|
|
9562
12132
|
var TICK_PROMPT = '/run-plan - single tick from the headless runner (agent-kit run-plan). Read .cursor/HANDOFF.md and the active plan in .cursor/plans/. Mark the next to-do as in_progress in the frontmatter, execute ONLY that to-do, mark completed and update HANDOFF. If there is a commitable diff: run /git-staging without asking for confirmation. NEVER /git-prod. Do NOT re-arm an internal Loop skill - the external runner starts the next agent. End the response with exactly one line: "LOOP_TICK_RESULT: continue" if implementable to-dos remain, or "LOOP_TICK_RESULT: stop - <reason>" (plan exhausted, external blocker, or human decision needed).';
|
|
12133
|
+
async function runTickWithSpinner(label, backend, opts, plain) {
|
|
12134
|
+
if (shouldUseLiveTui({ plain })) {
|
|
12135
|
+
return withLiveTui(
|
|
12136
|
+
{
|
|
12137
|
+
root: opts.workspace,
|
|
12138
|
+
backend: backend.id,
|
|
12139
|
+
logPath: opts.logPath,
|
|
12140
|
+
noHitl: opts.hitl?.policy === "off"
|
|
12141
|
+
},
|
|
12142
|
+
(seams) => backend.run({
|
|
12143
|
+
...opts,
|
|
12144
|
+
hitl: seams.hitl,
|
|
12145
|
+
render: seams.render,
|
|
12146
|
+
onSpawn: seams.onSpawn,
|
|
12147
|
+
log: seams.log
|
|
12148
|
+
})
|
|
12149
|
+
);
|
|
12150
|
+
}
|
|
12151
|
+
if (!shouldUseVisualMotion()) return backend.run(opts);
|
|
12152
|
+
const spinner = new TtySpinner({ motion: true });
|
|
12153
|
+
spinner.start(label);
|
|
12154
|
+
try {
|
|
12155
|
+
return await backend.run({
|
|
12156
|
+
...opts,
|
|
12157
|
+
hitl: {
|
|
12158
|
+
...opts.hitl,
|
|
12159
|
+
onPromptStart: () => spinner.stop(),
|
|
12160
|
+
onPromptEnd: () => spinner.start(label)
|
|
12161
|
+
}
|
|
12162
|
+
});
|
|
12163
|
+
} finally {
|
|
12164
|
+
spinner.stop();
|
|
12165
|
+
}
|
|
12166
|
+
}
|
|
9563
12167
|
function stamp() {
|
|
9564
12168
|
const d = /* @__PURE__ */ new Date();
|
|
9565
12169
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
@@ -9569,9 +12173,9 @@ function sleep(ms) {
|
|
|
9569
12173
|
return new Promise((r) => setTimeout(r, ms));
|
|
9570
12174
|
}
|
|
9571
12175
|
async function runPlanLoop(opts) {
|
|
9572
|
-
const plansDir =
|
|
9573
|
-
const stopFile =
|
|
9574
|
-
const logDir =
|
|
12176
|
+
const plansDir = path49.join(opts.root, ".cursor", "plans");
|
|
12177
|
+
const stopFile = path49.join(opts.root, ".cursor", "loop.stop");
|
|
12178
|
+
const logDir = path49.join(opts.root, ".cursor", "loop-logs");
|
|
9575
12179
|
const planPath = await findActivePlanFile(plansDir);
|
|
9576
12180
|
if (!planPath) {
|
|
9577
12181
|
logger.error("No active plan in .cursor/plans/");
|
|
@@ -9592,7 +12196,7 @@ async function runPlanLoop(opts) {
|
|
|
9592
12196
|
try {
|
|
9593
12197
|
const persona = await loadCliRunPlanPersona(opts.root);
|
|
9594
12198
|
const banners = createPersonaBannerPrinter(persona);
|
|
9595
|
-
console.log(`Active plan: ${
|
|
12199
|
+
console.log(`Active plan: ${path49.basename(planPath)}`);
|
|
9596
12200
|
console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
|
|
9597
12201
|
console.log(`Backend: ${opts.backend.id}`);
|
|
9598
12202
|
if (persona) {
|
|
@@ -9611,6 +12215,7 @@ async function runPlanLoop(opts) {
|
|
|
9611
12215
|
let tick = 0;
|
|
9612
12216
|
let stopReason;
|
|
9613
12217
|
let planExhausted = false;
|
|
12218
|
+
let exitCode = 0;
|
|
9614
12219
|
while (true) {
|
|
9615
12220
|
tick += 1;
|
|
9616
12221
|
if (tick > opts.maxTicks) {
|
|
@@ -9635,30 +12240,47 @@ async function runPlanLoop(opts) {
|
|
|
9635
12240
|
planExhausted = true;
|
|
9636
12241
|
break;
|
|
9637
12242
|
}
|
|
9638
|
-
const logPath =
|
|
9639
|
-
const relLog =
|
|
12243
|
+
const logPath = path49.join(logDir, `tick-${stamp()}.log`);
|
|
12244
|
+
const relLog = path49.relative(opts.root, logPath);
|
|
9640
12245
|
console.log("");
|
|
9641
12246
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
9642
12247
|
if (banners) banners.tickStart(tickLine);
|
|
9643
12248
|
else console.log(tickLine);
|
|
9644
12249
|
let agentExit = 0;
|
|
12250
|
+
let hitlStop;
|
|
9645
12251
|
try {
|
|
9646
|
-
const result = await
|
|
12252
|
+
const result = await runTickWithSpinner(
|
|
9647
12253
|
`tick ${tick}`,
|
|
9648
|
-
|
|
12254
|
+
opts.backend,
|
|
12255
|
+
{
|
|
9649
12256
|
workspace: opts.root,
|
|
9650
12257
|
prompt: TICK_PROMPT,
|
|
9651
12258
|
model: opts.model,
|
|
9652
|
-
logPath
|
|
9653
|
-
|
|
12259
|
+
logPath,
|
|
12260
|
+
hitl: { policy: opts.noHitl ? "off" : "prompt" }
|
|
12261
|
+
},
|
|
12262
|
+
opts.plain === true
|
|
9654
12263
|
);
|
|
9655
12264
|
agentExit = result.exitCode;
|
|
12265
|
+
for (const line of formatHitlSummary(result.hitl)) console.log(line);
|
|
12266
|
+
hitlStop = result.hitl?.stop;
|
|
9656
12267
|
} catch (err) {
|
|
9657
12268
|
logger.error(String(err));
|
|
9658
12269
|
return 1;
|
|
9659
12270
|
}
|
|
12271
|
+
if (hitlStop) {
|
|
12272
|
+
const msg = `${opts.backend.id} tick ended at a HITL gate without a reply - stopping. See log: ${relLog}`;
|
|
12273
|
+
if (banners) {
|
|
12274
|
+
banners.tickEnd("hitl stop");
|
|
12275
|
+
banners.stop(msg);
|
|
12276
|
+
} else {
|
|
12277
|
+
console.log(msg);
|
|
12278
|
+
}
|
|
12279
|
+
exitCode = hitlStop.exitCode;
|
|
12280
|
+
break;
|
|
12281
|
+
}
|
|
9660
12282
|
try {
|
|
9661
|
-
const logText = await
|
|
12283
|
+
const logText = await readFile27(logPath, "utf8");
|
|
9662
12284
|
if (logText.includes("Too many MCP tools")) {
|
|
9663
12285
|
const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
|
|
9664
12286
|
if (banners) banners.stop(msg);
|
|
@@ -9727,12 +12349,12 @@ async function runPlanLoop(opts) {
|
|
|
9727
12349
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
9728
12350
|
if (banners) banners.phaseComplete(finishDetail);
|
|
9729
12351
|
console.log(
|
|
9730
|
-
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${
|
|
12352
|
+
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path49.relative(opts.root, logDir)}/`
|
|
9731
12353
|
);
|
|
9732
12354
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
9733
12355
|
await armExternalPlanReview(opts.root);
|
|
9734
12356
|
}
|
|
9735
|
-
return
|
|
12357
|
+
return exitCode;
|
|
9736
12358
|
} finally {
|
|
9737
12359
|
process.off("SIGINT", onSigInt);
|
|
9738
12360
|
}
|
|
@@ -9774,6 +12396,16 @@ var runPlanCommand = defineCommand16({
|
|
|
9774
12396
|
type: "boolean",
|
|
9775
12397
|
description: "Print the tick prompt and exit without starting an agent",
|
|
9776
12398
|
default: false
|
|
12399
|
+
},
|
|
12400
|
+
"no-hitl": {
|
|
12401
|
+
type: "boolean",
|
|
12402
|
+
description: `CI: never prompt; a HITL gate stops the run with exit ${HITL_UNANSWERED_EXIT_CODE} (no default answer)`,
|
|
12403
|
+
default: false
|
|
12404
|
+
},
|
|
12405
|
+
plain: {
|
|
12406
|
+
type: "boolean",
|
|
12407
|
+
description: "Status lines instead of the Mission Control live view (the default on a TTY; non-TTY, NO_COLOR and CI are always plain)",
|
|
12408
|
+
default: false
|
|
9777
12409
|
}
|
|
9778
12410
|
},
|
|
9779
12411
|
async run({ args }) {
|
|
@@ -9809,12 +12441,14 @@ var runPlanCommand = defineCommand16({
|
|
|
9809
12441
|
return;
|
|
9810
12442
|
}
|
|
9811
12443
|
const code = await runPlanLoop({
|
|
9812
|
-
root:
|
|
12444
|
+
root: path50.resolve(args.cwd),
|
|
9813
12445
|
maxTicks,
|
|
9814
12446
|
sleepSeconds,
|
|
9815
12447
|
model: args.model ? String(args.model) : void 0,
|
|
9816
12448
|
dryRun: Boolean(args["dry-run"]),
|
|
9817
|
-
backend
|
|
12449
|
+
backend,
|
|
12450
|
+
noHitl: Boolean(args["no-hitl"]),
|
|
12451
|
+
plain: Boolean(args.plain)
|
|
9818
12452
|
});
|
|
9819
12453
|
process.exitCode = code;
|
|
9820
12454
|
}
|
|
@@ -9822,12 +12456,12 @@ var runPlanCommand = defineCommand16({
|
|
|
9822
12456
|
|
|
9823
12457
|
// src/commands/run.ts
|
|
9824
12458
|
import { homedir as homedir4 } from "os";
|
|
9825
|
-
import
|
|
12459
|
+
import path52 from "path";
|
|
9826
12460
|
import { defineCommand as defineCommand17 } from "citty";
|
|
9827
12461
|
|
|
9828
12462
|
// src/plan-loop/dispatch.ts
|
|
9829
|
-
import { mkdir as mkdir7, readFile as
|
|
9830
|
-
import
|
|
12463
|
+
import { mkdir as mkdir7, readFile as readFile28 } from "fs/promises";
|
|
12464
|
+
import path51 from "path";
|
|
9831
12465
|
var RUN_CATALOG = [
|
|
9832
12466
|
"run-plan",
|
|
9833
12467
|
"run-plan-all",
|
|
@@ -9840,6 +12474,7 @@ var RUN_CATALOG = [
|
|
|
9840
12474
|
"git-staging",
|
|
9841
12475
|
"kit-staging"
|
|
9842
12476
|
];
|
|
12477
|
+
var FIRST_CLASS_SLASH_COMMANDS = ["run-plan", "run-plan-all"];
|
|
9843
12478
|
var RUN_PROMOTE_BLOCKED = ["git-prod", "kit-prod"];
|
|
9844
12479
|
function normalizeSlashName(raw) {
|
|
9845
12480
|
return raw.replace(/^\//, "").replace(/\.md$/i, "").trim();
|
|
@@ -9851,12 +12486,30 @@ function classifySlash(name) {
|
|
|
9851
12486
|
if (RUN_CATALOG.includes(n)) return "catalog";
|
|
9852
12487
|
return "unknown";
|
|
9853
12488
|
}
|
|
12489
|
+
function rewriteRootArgvToRun(argv) {
|
|
12490
|
+
const idx = argv.findIndex((arg) => arg.length > 0 && !arg.startsWith("-"));
|
|
12491
|
+
if (idx < 0) return argv;
|
|
12492
|
+
const token = argv[idx];
|
|
12493
|
+
if (!token || token === "run") return argv;
|
|
12494
|
+
const kind = classifySlash(token);
|
|
12495
|
+
if (kind === "unknown") return argv;
|
|
12496
|
+
const slash = normalizeSlashName(token);
|
|
12497
|
+
if (FIRST_CLASS_SLASH_COMMANDS.includes(slash)) {
|
|
12498
|
+
if (token === slash) return argv;
|
|
12499
|
+
const next2 = [...argv];
|
|
12500
|
+
next2[idx] = slash;
|
|
12501
|
+
return next2;
|
|
12502
|
+
}
|
|
12503
|
+
const next = [...argv];
|
|
12504
|
+
next.splice(idx, 1, "run", slash);
|
|
12505
|
+
return next;
|
|
12506
|
+
}
|
|
9854
12507
|
function commandMarkdownPath(root, name) {
|
|
9855
|
-
return
|
|
12508
|
+
return path51.join(root, ".cursor", "commands", `${normalizeSlashName(name)}.md`);
|
|
9856
12509
|
}
|
|
9857
12510
|
function buildDispatchPrompt(commandBody) {
|
|
9858
12511
|
return [
|
|
9859
|
-
"Follow the Agent Kit command below. This session is headless: use numbered-list HITL (Ask questions is Cursor-only). Never /git-prod. No CLI --force promote.",
|
|
12512
|
+
"Follow the Agent Kit command below. This session is headless: use numbered-list HITL (Ask questions is Cursor-only). Before each numbered list print one line `HITL_GATE: <ask-id> | <label 1> | <label 2> | ...` (ask-id and labels from the hitl-gates skill), then wait; the operator's answer arrives as `HITL_REPLY: <ask-id> | operator reply <n> | <label>`. Never /git-prod. No CLI --force promote.",
|
|
9860
12513
|
"",
|
|
9861
12514
|
commandBody.trimEnd(),
|
|
9862
12515
|
""
|
|
@@ -9877,7 +12530,7 @@ async function readCommandFile(root, name) {
|
|
|
9877
12530
|
message: `Command file not found: ${filePath}. Install or refresh Agent Kit L0 so .cursor/commands/${normalizeSlashName(name)}.md exists.`
|
|
9878
12531
|
};
|
|
9879
12532
|
}
|
|
9880
|
-
const body = await
|
|
12533
|
+
const body = await readFile28(filePath, "utf8");
|
|
9881
12534
|
return { ok: true, path: filePath, body };
|
|
9882
12535
|
}
|
|
9883
12536
|
function stamp2() {
|
|
@@ -9900,7 +12553,7 @@ function cursorAgentDispatchArgs(opts) {
|
|
|
9900
12553
|
args.push(opts.prompt);
|
|
9901
12554
|
return args;
|
|
9902
12555
|
}
|
|
9903
|
-
function claudeDispatchArgs(opts) {
|
|
12556
|
+
function claudeDispatchArgs(opts = {}) {
|
|
9904
12557
|
return claudeHeadlessArgs(opts);
|
|
9905
12558
|
}
|
|
9906
12559
|
async function runHeadlessDispatch(opts) {
|
|
@@ -9917,9 +12570,21 @@ async function runHeadlessDispatch(opts) {
|
|
|
9917
12570
|
try {
|
|
9918
12571
|
return await spawnLogged(
|
|
9919
12572
|
opts.bin,
|
|
9920
|
-
claudeDispatchArgs({
|
|
12573
|
+
claudeDispatchArgs({ model: opts.model, ...caps }),
|
|
9921
12574
|
opts.logPath,
|
|
9922
|
-
{
|
|
12575
|
+
{
|
|
12576
|
+
cwd: opts.workspace,
|
|
12577
|
+
env,
|
|
12578
|
+
redact,
|
|
12579
|
+
spawnFn: opts.spawnFn,
|
|
12580
|
+
hitl: resolveHitlRunOptions(opts.hitl, {
|
|
12581
|
+
kind: "stdin-stream-json",
|
|
12582
|
+
firstPrompt: opts.prompt
|
|
12583
|
+
}),
|
|
12584
|
+
render: opts.render,
|
|
12585
|
+
onSpawn: opts.onSpawn,
|
|
12586
|
+
backendId: "claude"
|
|
12587
|
+
}
|
|
9923
12588
|
);
|
|
9924
12589
|
} catch (err) {
|
|
9925
12590
|
throw new Error(`claude dispatch failed to start: ${redactSecrets(String(err), redact)}`);
|
|
@@ -9932,24 +12597,28 @@ async function runHeadlessDispatch(opts) {
|
|
|
9932
12597
|
});
|
|
9933
12598
|
return spawnLogged(opts.bin, args, opts.logPath, {
|
|
9934
12599
|
spawnFn: opts.spawnFn,
|
|
9935
|
-
redact: claudeRedactions(process.env)
|
|
12600
|
+
redact: claudeRedactions(process.env),
|
|
12601
|
+
hitl: resolveHitlRunOptions(opts.hitl, { kind: "none", backend: "cursor-agent" }),
|
|
12602
|
+
render: opts.render,
|
|
12603
|
+
onSpawn: opts.onSpawn,
|
|
12604
|
+
backendId: "cursor-agent"
|
|
9936
12605
|
});
|
|
9937
12606
|
}
|
|
9938
12607
|
async function ensureDispatchLogPath(root) {
|
|
9939
|
-
const logDir =
|
|
12608
|
+
const logDir = path51.join(root, ".cursor", "loop-logs");
|
|
9940
12609
|
await mkdir7(logDir, { recursive: true });
|
|
9941
|
-
return
|
|
12610
|
+
return path51.join(logDir, `run-${stamp2()}.log`);
|
|
9942
12611
|
}
|
|
9943
12612
|
|
|
9944
12613
|
// src/commands/run.ts
|
|
9945
12614
|
function displayPath(target, home = homedir4()) {
|
|
9946
|
-
const resolvedHome =
|
|
12615
|
+
const resolvedHome = path52.resolve(home);
|
|
9947
12616
|
if (target === resolvedHome) return "~";
|
|
9948
|
-
const prefix = resolvedHome.endsWith(
|
|
9949
|
-
return target.startsWith(prefix) ? `~${
|
|
12617
|
+
const prefix = resolvedHome.endsWith(path52.sep) ? resolvedHome : `${resolvedHome}${path52.sep}`;
|
|
12618
|
+
return target.startsWith(prefix) ? `~${path52.sep}${target.slice(prefix.length)}` : target;
|
|
9950
12619
|
}
|
|
9951
12620
|
async function executeRun(input) {
|
|
9952
|
-
const root =
|
|
12621
|
+
const root = path52.resolve(input.cwd);
|
|
9953
12622
|
const slash = normalizeSlashName(input.slash);
|
|
9954
12623
|
if (!slash) {
|
|
9955
12624
|
return { exitCode: 1, error: "Provide a slash: agent-kit run <slash>" };
|
|
@@ -9985,7 +12654,9 @@ async function executeRun(input) {
|
|
|
9985
12654
|
sleepSeconds: input.sleepSeconds,
|
|
9986
12655
|
model: input.model,
|
|
9987
12656
|
dryRun: false,
|
|
9988
|
-
backend
|
|
12657
|
+
backend,
|
|
12658
|
+
noHitl: input.noHitl === true,
|
|
12659
|
+
plain: input.plain === true
|
|
9989
12660
|
});
|
|
9990
12661
|
return { exitCode: code };
|
|
9991
12662
|
}
|
|
@@ -9998,7 +12669,7 @@ async function executeRun(input) {
|
|
|
9998
12669
|
const stdout = [
|
|
9999
12670
|
"--dry-run: no agent will be started.",
|
|
10000
12671
|
`Slash: ${slash}`,
|
|
10001
|
-
`Command file: ${
|
|
12672
|
+
`Command file: ${path52.relative(root, file.path)}`,
|
|
10002
12673
|
`Backend: ${detected.id} (${displayPath(detected.bin)})`,
|
|
10003
12674
|
"Prompt:",
|
|
10004
12675
|
prompt
|
|
@@ -10007,93 +12678,135 @@ async function executeRun(input) {
|
|
|
10007
12678
|
}
|
|
10008
12679
|
const logPath = await ensureDispatchLogPath(root);
|
|
10009
12680
|
console.log(`Dispatch: ${slash} via ${detected.id}`);
|
|
10010
|
-
console.log(`Command file: ${
|
|
10011
|
-
console.log(`Log: ${
|
|
12681
|
+
console.log(`Command file: ${path52.relative(root, file.path)}`);
|
|
12682
|
+
console.log(`Log: ${path52.relative(root, logPath)}`);
|
|
10012
12683
|
try {
|
|
10013
|
-
const
|
|
12684
|
+
const dispatch = {
|
|
10014
12685
|
backendId: detected.id,
|
|
10015
12686
|
bin: detected.bin,
|
|
10016
12687
|
workspace: root,
|
|
10017
12688
|
prompt,
|
|
10018
12689
|
model: input.model,
|
|
10019
12690
|
logPath
|
|
12691
|
+
};
|
|
12692
|
+
const result = shouldUseLiveTui({ plain: input.plain }) ? await withLiveTui(
|
|
12693
|
+
{ root, backend: detected.id, logPath, noHitl: input.noHitl === true },
|
|
12694
|
+
(seams) => runHeadlessDispatch({
|
|
12695
|
+
...dispatch,
|
|
12696
|
+
hitl: seams.hitl,
|
|
12697
|
+
render: seams.render,
|
|
12698
|
+
onSpawn: seams.onSpawn,
|
|
12699
|
+
log: seams.log
|
|
12700
|
+
})
|
|
12701
|
+
) : await runHeadlessDispatch({
|
|
12702
|
+
...dispatch,
|
|
12703
|
+
hitl: { policy: input.noHitl ? "off" : "prompt" }
|
|
10020
12704
|
});
|
|
10021
|
-
|
|
12705
|
+
for (const line of formatHitlSummary(result.hitl)) console.log(line);
|
|
12706
|
+
return { exitCode: hitlExitCode(result.exitCode, result.hitl) };
|
|
10022
12707
|
} catch (err) {
|
|
10023
12708
|
return { exitCode: 1, error: String(err) };
|
|
10024
12709
|
}
|
|
10025
12710
|
}
|
|
12711
|
+
var runDispatchArgs = {
|
|
12712
|
+
cwd: {
|
|
12713
|
+
type: "string",
|
|
12714
|
+
description: "Project root (default: current directory)",
|
|
12715
|
+
default: process.cwd()
|
|
12716
|
+
},
|
|
12717
|
+
backend: {
|
|
12718
|
+
type: "string",
|
|
12719
|
+
description: `Agent CLI (${listDetectBackendIds().join(" | ")}; default: auto)`,
|
|
12720
|
+
default: "auto"
|
|
12721
|
+
},
|
|
12722
|
+
model: {
|
|
12723
|
+
type: "string",
|
|
12724
|
+
description: "Optional model id passed to the agent CLI",
|
|
12725
|
+
default: ""
|
|
12726
|
+
},
|
|
12727
|
+
"dry-run": {
|
|
12728
|
+
type: "boolean",
|
|
12729
|
+
description: "Print the resolved backend and prompt, then exit without starting an agent",
|
|
12730
|
+
default: false
|
|
12731
|
+
},
|
|
12732
|
+
"max-ticks": {
|
|
12733
|
+
type: "string",
|
|
12734
|
+
description: "For run-plan alias only: maximum ticks (default: 10)",
|
|
12735
|
+
default: "10"
|
|
12736
|
+
},
|
|
12737
|
+
sleep: {
|
|
12738
|
+
type: "string",
|
|
12739
|
+
description: "For run-plan alias only: seconds between ticks (default: 5)",
|
|
12740
|
+
default: "5"
|
|
12741
|
+
},
|
|
12742
|
+
"no-hitl": {
|
|
12743
|
+
type: "boolean",
|
|
12744
|
+
description: `CI: never prompt; a HITL gate stops the run with exit ${HITL_UNANSWERED_EXIT_CODE} (no default answer)`,
|
|
12745
|
+
default: false
|
|
12746
|
+
},
|
|
12747
|
+
plain: {
|
|
12748
|
+
type: "boolean",
|
|
12749
|
+
description: "Status lines instead of the Mission Control live view (the default on a TTY; non-TTY, NO_COLOR and CI are always plain)",
|
|
12750
|
+
default: false
|
|
12751
|
+
}
|
|
12752
|
+
};
|
|
12753
|
+
async function runSlashCli(slash, args) {
|
|
12754
|
+
const maxTicks = Number.parseInt(String(args["max-ticks"]), 10);
|
|
12755
|
+
const sleepSeconds = Number.parseFloat(String(args.sleep));
|
|
12756
|
+
if (!Number.isFinite(maxTicks) || maxTicks < 1) {
|
|
12757
|
+
logger.error("--max-ticks must be a positive integer");
|
|
12758
|
+
process.exitCode = 1;
|
|
12759
|
+
return;
|
|
12760
|
+
}
|
|
12761
|
+
if (!Number.isFinite(sleepSeconds) || sleepSeconds < 0) {
|
|
12762
|
+
logger.error("--sleep must be a non-negative number");
|
|
12763
|
+
process.exitCode = 1;
|
|
12764
|
+
return;
|
|
12765
|
+
}
|
|
12766
|
+
const result = await executeRun({
|
|
12767
|
+
slash,
|
|
12768
|
+
cwd: String(args.cwd),
|
|
12769
|
+
backend: String(args.backend),
|
|
12770
|
+
model: args.model ? String(args.model) : void 0,
|
|
12771
|
+
dryRun: Boolean(args["dry-run"]),
|
|
12772
|
+
maxTicks,
|
|
12773
|
+
sleepSeconds,
|
|
12774
|
+
noHitl: Boolean(args["no-hitl"]),
|
|
12775
|
+
plain: Boolean(args.plain)
|
|
12776
|
+
});
|
|
12777
|
+
if (result.stdout) {
|
|
12778
|
+
console.log(result.stdout);
|
|
12779
|
+
}
|
|
12780
|
+
if (result.error) {
|
|
12781
|
+
logger.error(result.error);
|
|
12782
|
+
}
|
|
12783
|
+
process.exitCode = result.exitCode;
|
|
12784
|
+
}
|
|
10026
12785
|
var runCommand = defineCommand17({
|
|
10027
12786
|
meta: {
|
|
10028
12787
|
name: "run",
|
|
10029
|
-
description: "Start one headless session from an L0 slash file. Numbered-list HITL. Never git-prod."
|
|
12788
|
+
description: "Start one headless session from an L0 slash file. Numbered-list HITL answered from the terminal (claude backend). Never git-prod."
|
|
10030
12789
|
},
|
|
10031
12790
|
args: {
|
|
10032
12791
|
slash: {
|
|
10033
12792
|
type: "positional",
|
|
10034
|
-
description: "Slash name (e.g.
|
|
12793
|
+
description: "Slash name (e.g. run-plan-all, backlog-add, continue-plan)",
|
|
10035
12794
|
required: true
|
|
10036
12795
|
},
|
|
10037
|
-
|
|
10038
|
-
type: "string",
|
|
10039
|
-
description: "Project root (default: current directory)",
|
|
10040
|
-
default: process.cwd()
|
|
10041
|
-
},
|
|
10042
|
-
backend: {
|
|
10043
|
-
type: "string",
|
|
10044
|
-
description: `Agent CLI (${listDetectBackendIds().join(" | ")}; default: auto)`,
|
|
10045
|
-
default: "auto"
|
|
10046
|
-
},
|
|
10047
|
-
model: {
|
|
10048
|
-
type: "string",
|
|
10049
|
-
description: "Optional model id passed to the agent CLI",
|
|
10050
|
-
default: ""
|
|
10051
|
-
},
|
|
10052
|
-
"dry-run": {
|
|
10053
|
-
type: "boolean",
|
|
10054
|
-
description: "Print the resolved backend and prompt, then exit without starting an agent",
|
|
10055
|
-
default: false
|
|
10056
|
-
},
|
|
10057
|
-
"max-ticks": {
|
|
10058
|
-
type: "string",
|
|
10059
|
-
description: "For run-plan alias only: maximum ticks (default: 10)",
|
|
10060
|
-
default: "10"
|
|
10061
|
-
},
|
|
10062
|
-
sleep: {
|
|
10063
|
-
type: "string",
|
|
10064
|
-
description: "For run-plan alias only: seconds between ticks (default: 5)",
|
|
10065
|
-
default: "5"
|
|
10066
|
-
}
|
|
12796
|
+
...runDispatchArgs
|
|
10067
12797
|
},
|
|
10068
12798
|
async run({ args }) {
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
}
|
|
10081
|
-
const result = await executeRun({
|
|
10082
|
-
slash: String(args.slash ?? ""),
|
|
10083
|
-
cwd: String(args.cwd),
|
|
10084
|
-
backend: String(args.backend),
|
|
10085
|
-
model: args.model ? String(args.model) : void 0,
|
|
10086
|
-
dryRun: Boolean(args["dry-run"]),
|
|
10087
|
-
maxTicks,
|
|
10088
|
-
sleepSeconds
|
|
10089
|
-
});
|
|
10090
|
-
if (result.stdout) {
|
|
10091
|
-
console.log(result.stdout);
|
|
10092
|
-
}
|
|
10093
|
-
if (result.error) {
|
|
10094
|
-
logger.error(result.error);
|
|
10095
|
-
}
|
|
10096
|
-
process.exitCode = result.exitCode;
|
|
12799
|
+
await runSlashCli(String(args.slash ?? ""), args);
|
|
12800
|
+
}
|
|
12801
|
+
});
|
|
12802
|
+
var runPlanAllCommand = defineCommand17({
|
|
12803
|
+
meta: {
|
|
12804
|
+
name: "run-plan-all",
|
|
12805
|
+
description: "Headless /run-plan-all queue from the L0 file. Numbered-list HITL answered from the terminal (claude backend). Never git-prod."
|
|
12806
|
+
},
|
|
12807
|
+
args: runDispatchArgs,
|
|
12808
|
+
async run({ args }) {
|
|
12809
|
+
await runSlashCli("run-plan-all", args);
|
|
10097
12810
|
}
|
|
10098
12811
|
});
|
|
10099
12812
|
|
|
@@ -10120,22 +12833,21 @@ var scanCommand = defineCommand18({
|
|
|
10120
12833
|
});
|
|
10121
12834
|
|
|
10122
12835
|
// src/commands/setup-global.ts
|
|
10123
|
-
import { spawn as
|
|
10124
|
-
import { appendFile, mkdir as mkdir8, readFile as
|
|
12836
|
+
import { spawn as spawn9 } from "child_process";
|
|
12837
|
+
import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile29, writeFile as writeFile13 } from "fs/promises";
|
|
10125
12838
|
import { homedir as homedir5 } from "os";
|
|
10126
|
-
import
|
|
12839
|
+
import path53 from "path";
|
|
10127
12840
|
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
10128
12841
|
import { defineCommand as defineCommand19 } from "citty";
|
|
10129
12842
|
import { cyan as cyan5, green as green4, yellow as yellow3 } from "kolorist";
|
|
10130
12843
|
var NPM_GLOBAL_DIR_NAME = ".npm-global";
|
|
10131
12844
|
var SETUP_GLOBAL_MARKER = "# agent-kit setup-global";
|
|
10132
|
-
var DEFAULT_PACKAGE_SPEC = "@dadado/agent-kit-cli";
|
|
10133
12845
|
function planSetupGlobalSteps(env, options = {}) {
|
|
10134
12846
|
const homeDir = options.homeDir ?? homedir5();
|
|
10135
|
-
const packageSpec = options.packageSpec ??
|
|
10136
|
-
const npmGlobalDir =
|
|
10137
|
-
const npmGlobalBin =
|
|
10138
|
-
const npmrcPath =
|
|
12847
|
+
const packageSpec = options.packageSpec ?? pinnedCliSpec();
|
|
12848
|
+
const npmGlobalDir = path53.join(homeDir, NPM_GLOBAL_DIR_NAME);
|
|
12849
|
+
const npmGlobalBin = path53.join(npmGlobalDir, "bin");
|
|
12850
|
+
const npmrcPath = path53.join(homeDir, ".npmrc");
|
|
10139
12851
|
const npmrcPrefixValue = `~/${NPM_GLOBAL_DIR_NAME}`;
|
|
10140
12852
|
const pathExportLine = `export PATH="${npmGlobalBin}:$PATH"`;
|
|
10141
12853
|
const shellProfile = env.shellProfile;
|
|
@@ -10203,9 +12915,9 @@ var defaultFsImpl = {
|
|
|
10203
12915
|
mkdir: async (dir) => {
|
|
10204
12916
|
await mkdir8(dir, { recursive: true });
|
|
10205
12917
|
},
|
|
10206
|
-
readFile: (filePath) =>
|
|
12918
|
+
readFile: (filePath) => readFile29(filePath, "utf8"),
|
|
10207
12919
|
writeFile: (filePath, content) => writeFile13(filePath, content, "utf8"),
|
|
10208
|
-
appendFile: (filePath, content) =>
|
|
12920
|
+
appendFile: (filePath, content) => appendFile2(filePath, content, "utf8")
|
|
10209
12921
|
};
|
|
10210
12922
|
async function safeReadFile(fs, filePath) {
|
|
10211
12923
|
try {
|
|
@@ -10215,7 +12927,7 @@ async function safeReadFile(fs, filePath) {
|
|
|
10215
12927
|
}
|
|
10216
12928
|
}
|
|
10217
12929
|
var defaultNpmInstallImpl = (packageSpec) => new Promise((resolve3) => {
|
|
10218
|
-
const child =
|
|
12930
|
+
const child = spawn9("npm", ["i", "-g", packageSpec], { stdio: "inherit" });
|
|
10219
12931
|
child.on("error", (error) => resolve3({ ok: false, error }));
|
|
10220
12932
|
child.on("close", (code) => {
|
|
10221
12933
|
if (code === 0) resolve3({ ok: true });
|
|
@@ -10266,11 +12978,48 @@ async function runSetupGlobal(options = {}) {
|
|
|
10266
12978
|
const homeDir = options.homeDir ?? homedir5();
|
|
10267
12979
|
const env = await assessEnvironmentImpl(options);
|
|
10268
12980
|
const plan = planSetupGlobalSteps(env, { homeDir, packageSpec: options.packageSpec });
|
|
10269
|
-
if (plan.alreadyWritable) {
|
|
12981
|
+
if (plan.alreadyWritable && pathCliStatus(env, KIT_VERSION) === "current") {
|
|
10270
12982
|
printHeader(env, print);
|
|
10271
|
-
print(green4("npm's global prefix is
|
|
12983
|
+
print(green4("npm's global prefix is writable and PATH `agent-kit` matches this CLI."));
|
|
10272
12984
|
return { exitCode: 0, mutated: false, outcome: "already-ok", env, plan };
|
|
10273
12985
|
}
|
|
12986
|
+
if (plan.alreadyWritable) {
|
|
12987
|
+
printHeader(env, print);
|
|
12988
|
+
print(
|
|
12989
|
+
`PATH \`agent-kit\` is ${env.binVersion ? `v${env.binVersion}` : "missing or unreadable"}; this CLI is v${KIT_VERSION}.`
|
|
12990
|
+
);
|
|
12991
|
+
print(`Prefix is writable. Next step is only: npm i -g ${plan.packageSpec}`);
|
|
12992
|
+
if (options.dryRun) {
|
|
12993
|
+
print("Dry run: no changes.");
|
|
12994
|
+
print(` npm i -g ${plan.packageSpec}`);
|
|
12995
|
+
return { exitCode: 0, mutated: false, outcome: "dry-run", env, plan };
|
|
12996
|
+
}
|
|
12997
|
+
const nonInteractiveWritable = options.nonInteractive ?? isNonInteractive();
|
|
12998
|
+
if (nonInteractiveWritable) {
|
|
12999
|
+
print("No changes made. Run this yourself:");
|
|
13000
|
+
print(` npm i -g ${plan.packageSpec}`);
|
|
13001
|
+
print(" hash -r");
|
|
13002
|
+
print(" agent-kit --version");
|
|
13003
|
+
return { exitCode: 0, mutated: false, outcome: "manual-instructions", env, plan };
|
|
13004
|
+
}
|
|
13005
|
+
const confirmStep2 = options.confirmImpl ?? defaultConfirmImpl;
|
|
13006
|
+
const npmInstall2 = options.npmInstallImpl ?? defaultNpmInstallImpl;
|
|
13007
|
+
const proceedInstall2 = await confirmStep2(`Run: npm i -g ${plan.packageSpec}?`);
|
|
13008
|
+
if (!proceedInstall2) {
|
|
13009
|
+
print(yellow3("Cancelled: no changes made."));
|
|
13010
|
+
return { exitCode: 1, mutated: false, outcome: "cancelled", env, plan };
|
|
13011
|
+
}
|
|
13012
|
+
const installResult2 = await npmInstall2(plan.packageSpec);
|
|
13013
|
+
if (!installResult2.ok) {
|
|
13014
|
+
const hint = classifyInstallError(installResult2.error);
|
|
13015
|
+
print(` npm install failed: ${hint.message}`);
|
|
13016
|
+
print(hint.recovery);
|
|
13017
|
+
return { exitCode: 1, mutated: false, outcome: "error", env, plan };
|
|
13018
|
+
}
|
|
13019
|
+
print(green4(` done: ${plan.packageSpec} installed globally.`));
|
|
13020
|
+
print(" If this shell still shows the old version: hash -r (or open a new terminal).");
|
|
13021
|
+
return { exitCode: 0, mutated: true, outcome: "completed", env, plan };
|
|
13022
|
+
}
|
|
10274
13023
|
if (options.dryRun) {
|
|
10275
13024
|
printHeader(env, print);
|
|
10276
13025
|
print("Dry run \u2014 no changes will be made. Steps that would run:");
|
|
@@ -10397,7 +13146,7 @@ var setupGlobalCommand = defineCommand19({
|
|
|
10397
13146
|
});
|
|
10398
13147
|
|
|
10399
13148
|
// src/commands/status.ts
|
|
10400
|
-
import
|
|
13149
|
+
import path54 from "path";
|
|
10401
13150
|
import { defineCommand as defineCommand20 } from "citty";
|
|
10402
13151
|
function profileStatus(profile) {
|
|
10403
13152
|
if (!profile) return { origin: "none", evidence: [], profile: null };
|
|
@@ -10428,13 +13177,14 @@ var statusCommand = defineCommand20({
|
|
|
10428
13177
|
}
|
|
10429
13178
|
},
|
|
10430
13179
|
async run({ args }) {
|
|
10431
|
-
const rootDir =
|
|
10432
|
-
const [manifest, rawProfile, scan] = await Promise.all([
|
|
13180
|
+
const rootDir = path54.resolve(args.cwd);
|
|
13181
|
+
const [manifest, rawProfile, scan, env] = await Promise.all([
|
|
10433
13182
|
loadAgentKitManifest(rootDir),
|
|
10434
13183
|
readJson(
|
|
10435
|
-
|
|
13184
|
+
path54.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
10436
13185
|
),
|
|
10437
|
-
runScanner(rootDir)
|
|
13186
|
+
runScanner(rootDir),
|
|
13187
|
+
assessEnvironment()
|
|
10438
13188
|
]);
|
|
10439
13189
|
const readiness = createReadinessReport(scan, { generatorVersion: KIT_VERSION });
|
|
10440
13190
|
const profile = profileStatus(rawProfile);
|
|
@@ -10470,6 +13220,17 @@ var statusCommand = defineCommand20({
|
|
|
10470
13220
|
` registry: ${manifest.registry?.url ?? "(default)"} @ ${manifest.registry?.ref ?? "(default)"}`
|
|
10471
13221
|
);
|
|
10472
13222
|
if (manifest.installedAt) console.log(` installed at: ${manifest.installedAt}`);
|
|
13223
|
+
if (manifest.version !== KIT_VERSION) {
|
|
13224
|
+
console.log(
|
|
13225
|
+
` overlay: this CLI is v${KIT_VERSION}; apply with the same binary, not a stale PATH hit:`
|
|
13226
|
+
);
|
|
13227
|
+
console.log(` ${npxPinned(KIT_VERSION, "update")}`);
|
|
13228
|
+
}
|
|
13229
|
+
const pathStatus = pathCliStatus(env, KIT_VERSION);
|
|
13230
|
+
if (pathStatus === "behind" || pathStatus === "unknown") {
|
|
13231
|
+
console.log(` PATH bin: v${env.binVersion ?? "unknown"} at ${env.binPath}`);
|
|
13232
|
+
console.log(" bare `agent-kit update` will re-stamp that older version");
|
|
13233
|
+
}
|
|
10473
13234
|
}
|
|
10474
13235
|
console.log("Repository readiness");
|
|
10475
13236
|
console.log(
|
|
@@ -10656,6 +13417,8 @@ ${err.recovery}
|
|
|
10656
13417
|
logApplyStats(stats);
|
|
10657
13418
|
const transition = next.version === existing.version ? `unchanged at v${next.version}` : `v${existing.version} \u2192 v${next.version}`;
|
|
10658
13419
|
logger.success(`Update complete: ${transition} (L3 protected paths left untouched).`);
|
|
13420
|
+
const sync = await syncPathCliToRuntime({ runtimeVersion: KIT_VERSION });
|
|
13421
|
+
for (const line of sync.lines) console.log(line);
|
|
10659
13422
|
} finally {
|
|
10660
13423
|
await registry.unlock?.();
|
|
10661
13424
|
}
|
|
@@ -10663,8 +13426,8 @@ ${err.recovery}
|
|
|
10663
13426
|
});
|
|
10664
13427
|
|
|
10665
13428
|
// src/commands/validate.ts
|
|
10666
|
-
import { readFile as
|
|
10667
|
-
import
|
|
13429
|
+
import { readFile as readFile30 } from "fs/promises";
|
|
13430
|
+
import path55 from "path";
|
|
10668
13431
|
import { defineCommand as defineCommand22 } from "citty";
|
|
10669
13432
|
|
|
10670
13433
|
// src/invariants/plan-schema.ts
|
|
@@ -10697,7 +13460,7 @@ function validatePlanFrontmatterText(text) {
|
|
|
10697
13460
|
cite: CITE5
|
|
10698
13461
|
});
|
|
10699
13462
|
}
|
|
10700
|
-
const hasTodoItem =
|
|
13463
|
+
const hasTodoItem = /^\s*- id:\s*\S+/m.test(block);
|
|
10701
13464
|
if (/^todos:/m.test(block) && !hasTodoItem && !/^todos:\s*\[\s*\]/m.test(block)) {
|
|
10702
13465
|
warnings.push({
|
|
10703
13466
|
code: "empty-todos",
|
|
@@ -10711,9 +13474,9 @@ function validatePlanFrontmatterText(text) {
|
|
|
10711
13474
|
// src/commands/validate.ts
|
|
10712
13475
|
async function resolveEditedPath(cwd, explicit) {
|
|
10713
13476
|
if (explicit) {
|
|
10714
|
-
const filePath2 =
|
|
13477
|
+
const filePath2 = path55.resolve(cwd, explicit);
|
|
10715
13478
|
try {
|
|
10716
|
-
return { filePath: filePath2, content: await
|
|
13479
|
+
return { filePath: filePath2, content: await readFile30(filePath2, "utf8") };
|
|
10717
13480
|
} catch {
|
|
10718
13481
|
return null;
|
|
10719
13482
|
}
|
|
@@ -10721,9 +13484,9 @@ async function resolveEditedPath(cwd, explicit) {
|
|
|
10721
13484
|
const payload = await readStdinJson();
|
|
10722
13485
|
const rel = typeof payload.file_path === "string" && payload.file_path || typeof payload.path === "string" && payload.path || typeof payload.file === "string" && payload.file || "";
|
|
10723
13486
|
if (!rel) return null;
|
|
10724
|
-
const filePath =
|
|
13487
|
+
const filePath = path55.isAbsolute(rel) ? rel : path55.resolve(cwd, rel);
|
|
10725
13488
|
try {
|
|
10726
|
-
return { filePath, content: await
|
|
13489
|
+
return { filePath, content: await readFile30(filePath, "utf8") };
|
|
10727
13490
|
} catch {
|
|
10728
13491
|
return null;
|
|
10729
13492
|
}
|
|
@@ -10751,15 +13514,21 @@ var validateCommand = defineCommand22({
|
|
|
10751
13514
|
async run({ args }) {
|
|
10752
13515
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
10753
13516
|
const fileArg = typeof args.file === "string" ? args.file : void 0;
|
|
10754
|
-
const filePath = fileArg ?
|
|
13517
|
+
const filePath = fileArg ? path55.resolve(cwd, fileArg) : path55.join(path55.resolve(cwd), ".cursor", "HANDOFF.md");
|
|
10755
13518
|
let content = "";
|
|
10756
13519
|
try {
|
|
10757
|
-
content = await
|
|
13520
|
+
content = await readFile30(filePath, "utf8");
|
|
10758
13521
|
} catch {
|
|
10759
13522
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
10760
13523
|
return;
|
|
10761
13524
|
}
|
|
10762
|
-
const warnings =
|
|
13525
|
+
const warnings = [
|
|
13526
|
+
...validateHandoffText(content),
|
|
13527
|
+
...checkHandoffGrowth(content).map((w) => ({
|
|
13528
|
+
...w,
|
|
13529
|
+
cite: "agent-kit handoff --prune (see .cursor/context/templates/handoff.md)"
|
|
13530
|
+
}))
|
|
13531
|
+
];
|
|
10763
13532
|
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
10764
13533
|
}
|
|
10765
13534
|
}),
|
|
@@ -10778,10 +13547,10 @@ var validateCommand = defineCommand22({
|
|
|
10778
13547
|
process.exitCode = 2;
|
|
10779
13548
|
return;
|
|
10780
13549
|
}
|
|
10781
|
-
const filePath =
|
|
13550
|
+
const filePath = path55.resolve(cwd, fileArg);
|
|
10782
13551
|
let content = "";
|
|
10783
13552
|
try {
|
|
10784
|
-
content = await
|
|
13553
|
+
content = await readFile30(filePath, "utf8");
|
|
10785
13554
|
} catch {
|
|
10786
13555
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
10787
13556
|
return;
|
|
@@ -10800,19 +13569,22 @@ var validateCommand = defineCommand22({
|
|
|
10800
13569
|
},
|
|
10801
13570
|
async run({ args }) {
|
|
10802
13571
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
10803
|
-
const resolved = await resolveEditedPath(
|
|
13572
|
+
const resolved = await resolveEditedPath(path55.resolve(cwd));
|
|
10804
13573
|
if (!resolved) {
|
|
10805
13574
|
console.log(JSON.stringify({}));
|
|
10806
13575
|
return;
|
|
10807
13576
|
}
|
|
10808
13577
|
const { filePath, content } = resolved;
|
|
10809
13578
|
if (isHandoffPath(filePath)) {
|
|
10810
|
-
const warnings =
|
|
13579
|
+
const warnings = [
|
|
13580
|
+
...validateHandoffText(content).map((w) => w.message),
|
|
13581
|
+
...checkHandoffGrowth(content).map((w) => w.message)
|
|
13582
|
+
];
|
|
10811
13583
|
if (!warnings.length) {
|
|
10812
13584
|
console.log(JSON.stringify({}));
|
|
10813
13585
|
return;
|
|
10814
13586
|
}
|
|
10815
|
-
const msg = warnings.
|
|
13587
|
+
const msg = warnings.join(" ");
|
|
10816
13588
|
console.log(
|
|
10817
13589
|
JSON.stringify({
|
|
10818
13590
|
user_message: msg,
|
|
@@ -10846,7 +13618,7 @@ var validateCommand = defineCommand22({
|
|
|
10846
13618
|
import { bold as bold2, gray as gray4, underline } from "kolorist";
|
|
10847
13619
|
|
|
10848
13620
|
// src/welcome/screen.ts
|
|
10849
|
-
import { blue as blue2, cyan as cyan6, gray as gray3, lightCyan, trueColor as
|
|
13621
|
+
import { blue as blue2, cyan as cyan6, gray as gray3, lightCyan, trueColor as trueColor3, white as white2 } from "kolorist";
|
|
10850
13622
|
var HELMET_ASCII = [
|
|
10851
13623
|
" ____",
|
|
10852
13624
|
" .-' '-.",
|
|
@@ -10867,7 +13639,7 @@ function hexToRgb(hex) {
|
|
|
10867
13639
|
}
|
|
10868
13640
|
function outlineAnsi(line) {
|
|
10869
13641
|
const [r, g, b] = hexToRgb(HELMET_OUTLINE);
|
|
10870
|
-
return
|
|
13642
|
+
return trueColor3(r, g, b)(line);
|
|
10871
13643
|
}
|
|
10872
13644
|
function hasCliSubcommand(rawArgs) {
|
|
10873
13645
|
return Boolean(rawArgs?.some((arg) => !arg.startsWith("-")));
|
|
@@ -10913,7 +13685,7 @@ function renderWelcomeScreen(opts = {}) {
|
|
|
10913
13685
|
})(),
|
|
10914
13686
|
"",
|
|
10915
13687
|
muted2(
|
|
10916
|
-
"
|
|
13688
|
+
"Terminal: agent-kit run-plan-all (same as agent-kit run run-plan-all). Cursor chat: /run-plan-all."
|
|
10917
13689
|
),
|
|
10918
13690
|
muted2(
|
|
10919
13691
|
`${shouldUseVisualMotion(opts) ? SPACE_MARKS.star : SPACE_MARKS.tick} ${tipAt(
|
|
@@ -10938,7 +13710,7 @@ var CLI_HELP_GROUPS = [
|
|
|
10938
13710
|
{
|
|
10939
13711
|
id: "mission",
|
|
10940
13712
|
title: "MISSION",
|
|
10941
|
-
commands: ["handoff", "plan-index", "run", "run-plan"]
|
|
13713
|
+
commands: ["handoff", "plan-index", "run", "run-plan", "run-plan-all"]
|
|
10942
13714
|
},
|
|
10943
13715
|
{
|
|
10944
13716
|
id: "dashboard",
|
|
@@ -11004,7 +13776,7 @@ async function renderGroupedRootHelp(cmd) {
|
|
|
11004
13776
|
lines.push(
|
|
11005
13777
|
g(`Use \`${name} <command> --help\` for more information about a command.`),
|
|
11006
13778
|
g(
|
|
11007
|
-
"Ask questions is Cursor-only. Use `agent-kit run <slash>` for headless numbered-list HITL. `/git-prod` stays operator-gated."
|
|
13779
|
+
"Ask questions is Cursor-only. Use `agent-kit run-plan-all` or `agent-kit run <slash>` for headless numbered-list HITL. `/git-prod` stays operator-gated."
|
|
11008
13780
|
),
|
|
11009
13781
|
g(`${tipMark} ${tip}`),
|
|
11010
13782
|
""
|
|
@@ -11013,6 +13785,9 @@ async function renderGroupedRootHelp(cmd) {
|
|
|
11013
13785
|
}
|
|
11014
13786
|
|
|
11015
13787
|
// src/index.ts
|
|
13788
|
+
var rewrittenArgv = rewriteRootArgvToRun(process.argv.slice(2));
|
|
13789
|
+
process.argv.length = 2;
|
|
13790
|
+
process.argv.push(...rewrittenArgv);
|
|
11016
13791
|
var main = defineCommand23({
|
|
11017
13792
|
meta: {
|
|
11018
13793
|
name: "agent-kit",
|
|
@@ -11035,6 +13810,7 @@ var main = defineCommand23({
|
|
|
11035
13810
|
"plan-index": planIndexCommand,
|
|
11036
13811
|
run: runCommand,
|
|
11037
13812
|
"run-plan": runPlanCommand,
|
|
13813
|
+
"run-plan-all": runPlanAllCommand,
|
|
11038
13814
|
dashboard: dashboardCommand,
|
|
11039
13815
|
"dashboard-broadcast": dashboardBroadcastCommand,
|
|
11040
13816
|
"mission-control": missionControlCommand,
|