@dadado/agent-kit-cli 5.9.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/dist/index.js +2750 -250
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -274,7 +274,73 @@ var KNOWN_SHIPPED_OVERLAY_HASHES = /* @__PURE__ */ new Set([
|
|
|
274
274
|
"481a9994ccecaa611173f9ef91a1dbe7458d89758c6a6b5e7c3bbbbf460888ce",
|
|
275
275
|
"691d91fd1e9032b6f9315f29348d4473201917da257258afd74eb954298d8d71",
|
|
276
276
|
"ab0808b7dbc333e523b87b126bb655353cde2574db30d849cc9c9037843bef8c",
|
|
277
|
-
"85514e26af694bbe38e7ddf625710a32719ddf6afc173843c9cdfbffdad8e466"
|
|
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"
|
|
278
344
|
]);
|
|
279
345
|
|
|
280
346
|
// src/lifecycle/paths.ts
|
|
@@ -1521,6 +1587,54 @@ var L0_ARTIFACTS = [
|
|
|
1521
1587
|
source: ".cursor/rules/hitl-ask-questions.mdc",
|
|
1522
1588
|
target: ".cursor/rules/hitl-ask-questions.mdc"
|
|
1523
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
|
+
},
|
|
1524
1638
|
{
|
|
1525
1639
|
source: "registry/rules/git-secrets-safety.mdc",
|
|
1526
1640
|
target: ".cursor/rules/git-secrets-safety.mdc"
|
|
@@ -1590,11 +1704,8 @@ var L0_ARTIFACTS = [
|
|
|
1590
1704
|
target: ".cursor/commands/git-staging.md"
|
|
1591
1705
|
},
|
|
1592
1706
|
{ source: ".cursor/commands/git-prod.md", target: ".cursor/commands/git-prod.md" },
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
target: ".cursor/commands/kit-staging.md"
|
|
1596
|
-
},
|
|
1597
|
-
{ 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).
|
|
1598
1709
|
{
|
|
1599
1710
|
source: ".cursor/commands/plan-external-review.md",
|
|
1600
1711
|
target: ".cursor/commands/plan-external-review.md"
|
|
@@ -1618,6 +1729,10 @@ var L0_ARTIFACTS = [
|
|
|
1618
1729
|
source: ".cursor/commands/cursor-update-awareness.md",
|
|
1619
1730
|
target: ".cursor/commands/cursor-update-awareness.md"
|
|
1620
1731
|
},
|
|
1732
|
+
{
|
|
1733
|
+
source: ".cursor/commands/qa.md",
|
|
1734
|
+
target: ".cursor/commands/qa.md"
|
|
1735
|
+
},
|
|
1621
1736
|
{
|
|
1622
1737
|
source: ".cursor/commands/update.md",
|
|
1623
1738
|
target: ".cursor/commands/update.md"
|
|
@@ -2168,6 +2283,94 @@ function parseFeatureMapNames(markdown) {
|
|
|
2168
2283
|
}
|
|
2169
2284
|
return names;
|
|
2170
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
|
+
}
|
|
2171
2374
|
async function defaultFetchText(url) {
|
|
2172
2375
|
if (!url.startsWith("https://")) {
|
|
2173
2376
|
throw new Error(`Refusing non-HTTPS changelog URL: ${url}`);
|
|
@@ -2266,6 +2469,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2266
2469
|
const inventoryRefreshed = parseInventoryRefreshed(inventoryMd);
|
|
2267
2470
|
const openActionIds = parseOpenActionIds(inventoryMd);
|
|
2268
2471
|
const gaps = [];
|
|
2472
|
+
let featuresMd = "";
|
|
2269
2473
|
for (const id of openActionIds) {
|
|
2270
2474
|
gaps.push({
|
|
2271
2475
|
id: `open-action-${id}`,
|
|
@@ -2291,7 +2495,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2291
2495
|
}
|
|
2292
2496
|
}
|
|
2293
2497
|
try {
|
|
2294
|
-
|
|
2498
|
+
featuresMd = await readFile7(featuresPath, "utf8");
|
|
2295
2499
|
if (parseFeatureMapNames(featuresMd).length === 0) {
|
|
2296
2500
|
gaps.push({
|
|
2297
2501
|
id: "features-map-empty",
|
|
@@ -2315,6 +2519,23 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2315
2519
|
try {
|
|
2316
2520
|
const body = options.changelogBody ?? await (options.fetchText ?? defaultFetchText)(changelogUrl);
|
|
2317
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
|
+
}
|
|
2318
2539
|
if (latestCursorVersion && prefs.lastSeenCursorVersion && compareCursorVersion(latestCursorVersion, prefs.lastSeenCursorVersion) > 0) {
|
|
2319
2540
|
gaps.push({
|
|
2320
2541
|
id: "changelog-ahead",
|
|
@@ -2534,13 +2755,13 @@ function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
|
|
|
2534
2755
|
}
|
|
2535
2756
|
|
|
2536
2757
|
// src/commands/dashboard.ts
|
|
2537
|
-
function readPreferredBrowserFromWorkspace(cwd,
|
|
2758
|
+
function readPreferredBrowserFromWorkspace(cwd, readFile31 = readFileSync2) {
|
|
2538
2759
|
const resolved = resolveContextConfigPath(path11.resolve(cwd), {
|
|
2539
2760
|
existsSync,
|
|
2540
2761
|
realpathSync
|
|
2541
2762
|
});
|
|
2542
2763
|
if (!resolved.ok) return null;
|
|
2543
|
-
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync:
|
|
2764
|
+
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync: readFile31 });
|
|
2544
2765
|
return normalizePreferredBrowser(value);
|
|
2545
2766
|
}
|
|
2546
2767
|
function applyDashboardOpenEnv(env, opts) {
|
|
@@ -4016,21 +4237,30 @@ function buildPillars(scan) {
|
|
|
4016
4237
|
)
|
|
4017
4238
|
]),
|
|
4018
4239
|
pillar("quality-ci", [
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
"
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
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
|
+
})()
|
|
4034
4264
|
]),
|
|
4035
4265
|
pillar("deploy-infrastructure", [
|
|
4036
4266
|
check(
|
|
@@ -4104,7 +4334,7 @@ function createReadinessReport(scan, options) {
|
|
|
4104
4334
|
}
|
|
4105
4335
|
|
|
4106
4336
|
// src/scanner/safe-fixes.ts
|
|
4107
|
-
import { readFile as
|
|
4337
|
+
import { readFile as readFile15, writeFile as writeFile5 } from "fs/promises";
|
|
4108
4338
|
import path24 from "path";
|
|
4109
4339
|
|
|
4110
4340
|
// src/scanner/detect-repository.ts
|
|
@@ -4477,6 +4707,7 @@ async function detectIde(rootDir) {
|
|
|
4477
4707
|
}
|
|
4478
4708
|
|
|
4479
4709
|
// src/scanner/detect-infra.ts
|
|
4710
|
+
import { readFile as readFile12, readdir as readdir5 } from "fs/promises";
|
|
4480
4711
|
import path20 from "path";
|
|
4481
4712
|
|
|
4482
4713
|
// src/types.ts
|
|
@@ -4532,6 +4763,51 @@ var PM_TOOL_LABELS = {
|
|
|
4532
4763
|
};
|
|
4533
4764
|
|
|
4534
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
|
+
}
|
|
4535
4811
|
async function detectInfra(rootDir) {
|
|
4536
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"));
|
|
4537
4813
|
const kubernetes = await fileExists(path20.join(rootDir, "k8s")) || await fileExists(path20.join(rootDir, "kubernetes"));
|
|
@@ -4571,11 +4847,12 @@ async function detectInfra(rootDir) {
|
|
|
4571
4847
|
async (file) => await fileExists(path20.join(rootDir, file)) ? file : void 0
|
|
4572
4848
|
)
|
|
4573
4849
|
)).filter((file) => file !== void 0);
|
|
4574
|
-
|
|
4850
|
+
const ciRunCommands = ci === "github-actions" ? await detectCiRunCommands(rootDir) : [];
|
|
4851
|
+
return { docker, kubernetes, ci, ciFiles, infrastructureFiles, deploymentFiles, ciRunCommands };
|
|
4575
4852
|
}
|
|
4576
4853
|
|
|
4577
4854
|
// src/scanner/detect-services.ts
|
|
4578
|
-
import { readFile as
|
|
4855
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
4579
4856
|
import path21 from "path";
|
|
4580
4857
|
async function detectProjectManagement(rootDir) {
|
|
4581
4858
|
const tools = [];
|
|
@@ -4586,7 +4863,7 @@ async function detectProjectManagement(rootDir) {
|
|
|
4586
4863
|
for (const configPath of mcpConfigPaths) {
|
|
4587
4864
|
if (!await fileExists(configPath)) continue;
|
|
4588
4865
|
try {
|
|
4589
|
-
const raw = await
|
|
4866
|
+
const raw = await readFile13(configPath, "utf8");
|
|
4590
4867
|
const lower = raw.toLowerCase();
|
|
4591
4868
|
if (lower.includes("clickup")) tools.push("clickup");
|
|
4592
4869
|
if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
|
|
@@ -4622,6 +4899,7 @@ async function detectServices(rootDir) {
|
|
|
4622
4899
|
}
|
|
4623
4900
|
|
|
4624
4901
|
// src/scanner/detect-stack.ts
|
|
4902
|
+
import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
|
|
4625
4903
|
import path22 from "path";
|
|
4626
4904
|
var PROJECT_MARKERS = [
|
|
4627
4905
|
"package.json",
|
|
@@ -4633,8 +4911,152 @@ var PROJECT_MARKERS = [
|
|
|
4633
4911
|
"build.gradle",
|
|
4634
4912
|
"composer.json",
|
|
4635
4913
|
"Cargo.toml",
|
|
4636
|
-
"pnpm-workspace.yaml"
|
|
4914
|
+
"pnpm-workspace.yaml",
|
|
4915
|
+
"pubspec.yaml"
|
|
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 ."
|
|
4637
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
|
+
}
|
|
4638
5060
|
var LOCKFILES = [
|
|
4639
5061
|
["pnpm-lock.yaml", "pnpm"],
|
|
4640
5062
|
["yarn.lock", "yarn"],
|
|
@@ -4668,7 +5090,15 @@ function commandsForScripts(scripts, packageManager) {
|
|
|
4668
5090
|
return { testCommands, validationCommands };
|
|
4669
5091
|
}
|
|
4670
5092
|
async function detectStack(rootDir) {
|
|
5093
|
+
const confirmed = await readConfirmedStack(rootDir);
|
|
5094
|
+
if (confirmed) return confirmed;
|
|
4671
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
|
+
}
|
|
4672
5102
|
const hasPackageJson = await fileExists(path22.join(rootDir, "package.json"));
|
|
4673
5103
|
if (hasPackageJson) {
|
|
4674
5104
|
const packageJson = await readJson(path22.join(rootDir, "package.json")) ?? {};
|
|
@@ -4728,6 +5158,17 @@ async function detectStack(rootDir) {
|
|
|
4728
5158
|
hasProjectFiles: hasAnyProjectMarker
|
|
4729
5159
|
};
|
|
4730
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
|
+
}
|
|
4731
5172
|
return {
|
|
4732
5173
|
language: "unknown",
|
|
4733
5174
|
workspaces: false,
|
|
@@ -4782,7 +5223,8 @@ async function runScanner(rootDir) {
|
|
|
4782
5223
|
testCommands: stack.testCommands,
|
|
4783
5224
|
validationCommands: stack.validationCommands,
|
|
4784
5225
|
ci: infra.ci,
|
|
4785
|
-
hasTests: stack.testCommands.length > 0
|
|
5226
|
+
hasTests: stack.testCommands.length > 0,
|
|
5227
|
+
ciRunCommands: infra.ciRunCommands
|
|
4786
5228
|
}
|
|
4787
5229
|
};
|
|
4788
5230
|
}
|
|
@@ -4855,9 +5297,12 @@ function createOnboardingState(report, generatedAt, deferredItems = report.defer
|
|
|
4855
5297
|
const validDeferredCheckIds = new Set(
|
|
4856
5298
|
deferredItems.filter((item) => item.reason.trim().length > 0).map((item) => item.checkId)
|
|
4857
5299
|
);
|
|
4858
|
-
const unresolvedEssential = report.pillars.flatMap((pillar2) => pillar2.checks).some(
|
|
4859
|
-
(check2
|
|
4860
|
-
|
|
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
|
+
});
|
|
4861
5306
|
return {
|
|
4862
5307
|
contractVersion: 1,
|
|
4863
5308
|
status: unresolvedEssential ? "in_progress" : "completed",
|
|
@@ -5008,7 +5453,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5008
5453
|
}
|
|
5009
5454
|
const gitignoreRelativePath = ".gitignore";
|
|
5010
5455
|
const gitignorePath = path24.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
5011
|
-
const existingGitignore = await fileExists(gitignorePath) ? await
|
|
5456
|
+
const existingGitignore = await fileExists(gitignorePath) ? await readFile15(gitignorePath, "utf8") : "";
|
|
5012
5457
|
const gitignoreAfterSecrets = mergeSecretIgnores(existingGitignore);
|
|
5013
5458
|
const secretsChanged = gitignoreAfterSecrets !== existingGitignore;
|
|
5014
5459
|
const mergedGitignore = mergeKitOwnedIgnores(gitignoreAfterSecrets);
|
|
@@ -5123,11 +5568,12 @@ var KIT_TIPS = [
|
|
|
5123
5568
|
"agent-kit --help groups SETUP, MISSION, DASHBOARD, INTEGRITY."
|
|
5124
5569
|
];
|
|
5125
5570
|
function shouldUseWelcomeColor(opts = {}) {
|
|
5571
|
+
const env = opts.env ?? process.env;
|
|
5126
5572
|
if (opts.color === false) return false;
|
|
5127
|
-
if (
|
|
5128
|
-
if (
|
|
5129
|
-
if (
|
|
5130
|
-
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;
|
|
5131
5577
|
if (opts.color === true) return true;
|
|
5132
5578
|
const tty = opts.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
|
|
5133
5579
|
return tty;
|
|
@@ -5280,6 +5726,19 @@ async function withCliProgress(label, fn, opts) {
|
|
|
5280
5726
|
}
|
|
5281
5727
|
|
|
5282
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
|
+
}
|
|
5283
5742
|
async function runDoctor(cwd, options = {}) {
|
|
5284
5743
|
const rootDir = path26.resolve(cwd);
|
|
5285
5744
|
const hooks = await assessHooksHealth(rootDir);
|
|
@@ -5290,7 +5749,13 @@ async function runDoctor(cwd, options = {}) {
|
|
|
5290
5749
|
generatedAt: options.generatedAt
|
|
5291
5750
|
});
|
|
5292
5751
|
await writeReadinessSnapshot(rootDir, execution.after);
|
|
5293
|
-
return {
|
|
5752
|
+
return {
|
|
5753
|
+
report: execution.after,
|
|
5754
|
+
safeChanges: execution.changes,
|
|
5755
|
+
hooks,
|
|
5756
|
+
env,
|
|
5757
|
+
deferredEssentials: await readDeferredEssentials(rootDir, execution.after)
|
|
5758
|
+
};
|
|
5294
5759
|
}
|
|
5295
5760
|
if (options.refreshProfile) {
|
|
5296
5761
|
const refresh = await refreshRepositoryProfile(rootDir, {
|
|
@@ -5303,7 +5768,14 @@ async function runDoctor(cwd, options = {}) {
|
|
|
5303
5768
|
generatedAt: options.generatedAt
|
|
5304
5769
|
});
|
|
5305
5770
|
await writeReadinessSnapshot(rootDir, report2);
|
|
5306
|
-
return {
|
|
5771
|
+
return {
|
|
5772
|
+
report: report2,
|
|
5773
|
+
safeChanges: [],
|
|
5774
|
+
hooks,
|
|
5775
|
+
env,
|
|
5776
|
+
profileRefreshed: refresh.changed,
|
|
5777
|
+
deferredEssentials: await readDeferredEssentials(rootDir, report2)
|
|
5778
|
+
};
|
|
5307
5779
|
}
|
|
5308
5780
|
const scan = await runScanner(rootDir);
|
|
5309
5781
|
const report = createReadinessReport(scan, {
|
|
@@ -5311,7 +5783,13 @@ async function runDoctor(cwd, options = {}) {
|
|
|
5311
5783
|
generatedAt: options.generatedAt
|
|
5312
5784
|
});
|
|
5313
5785
|
await writeReadinessSnapshot(rootDir, report);
|
|
5314
|
-
return {
|
|
5786
|
+
return {
|
|
5787
|
+
report,
|
|
5788
|
+
safeChanges: [],
|
|
5789
|
+
hooks,
|
|
5790
|
+
env,
|
|
5791
|
+
deferredEssentials: await readDeferredEssentials(rootDir, report)
|
|
5792
|
+
};
|
|
5315
5793
|
}
|
|
5316
5794
|
function printDoctorSummary(result) {
|
|
5317
5795
|
const { summary, pendingActions } = result.report;
|
|
@@ -5328,6 +5806,9 @@ function printDoctorSummary(result) {
|
|
|
5328
5806
|
` profile refreshed: ${result.profileRefreshed ? "yes (facts changed)" : "no (already current)"}`
|
|
5329
5807
|
);
|
|
5330
5808
|
}
|
|
5809
|
+
for (const item of result.deferredEssentials) {
|
|
5810
|
+
console.log(` Deferred essential: ${item.checkId} \u2014 ${item.reason}`);
|
|
5811
|
+
}
|
|
5331
5812
|
console.log(`hooks: ${result.hooks.status}`);
|
|
5332
5813
|
if (result.hooks.reasons.length > 0) {
|
|
5333
5814
|
for (const reason of result.hooks.reasons.slice(0, 5)) {
|
|
@@ -5787,9 +6268,187 @@ var guardCommand = defineCommand8({
|
|
|
5787
6268
|
|
|
5788
6269
|
// src/commands/handoff.ts
|
|
5789
6270
|
import { spawn as spawn4 } from "child_process";
|
|
5790
|
-
import { readFile as
|
|
6271
|
+
import { appendFile, readFile as readFile16, readdir as readdir7, writeFile as writeFile6 } from "fs/promises";
|
|
5791
6272
|
import path27 from "path";
|
|
5792
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
|
+
}
|
|
5793
6452
|
function parsePlanFrontmatter(raw) {
|
|
5794
6453
|
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
5795
6454
|
if (!match?.[1]) return null;
|
|
@@ -5806,15 +6465,15 @@ function parsePlanFrontmatter(raw) {
|
|
|
5806
6465
|
}
|
|
5807
6466
|
async function findActivePlan(plansDir) {
|
|
5808
6467
|
if (!await fileExists(plansDir)) return null;
|
|
5809
|
-
const files = (await
|
|
6468
|
+
const files = (await readdir7(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
5810
6469
|
for (const file of files) {
|
|
5811
|
-
const raw = await
|
|
6470
|
+
const raw = await readFile16(path27.join(plansDir, file), "utf8");
|
|
5812
6471
|
const fm = parsePlanFrontmatter(raw);
|
|
5813
6472
|
if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
|
|
5814
6473
|
return { file, raw };
|
|
5815
6474
|
}
|
|
5816
6475
|
}
|
|
5817
|
-
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;
|
|
5818
6477
|
}
|
|
5819
6478
|
function now() {
|
|
5820
6479
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
|
|
@@ -5916,9 +6575,24 @@ var handoffCommand = defineCommand9({
|
|
|
5916
6575
|
type: "string",
|
|
5917
6576
|
description: "Project root directory",
|
|
5918
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}).`
|
|
5919
6587
|
}
|
|
5920
6588
|
},
|
|
5921
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
|
+
}
|
|
5922
6596
|
const profile = await loadProfile(args.cwd);
|
|
5923
6597
|
const plansDir = path27.join(args.cwd, ".cursor", "plans");
|
|
5924
6598
|
const handoffPath = path27.join(args.cwd, ".cursor", "HANDOFF.md");
|
|
@@ -5997,7 +6671,7 @@ function buildPreCompactUserMessage(payload = {}) {
|
|
|
5997
6671
|
|
|
5998
6672
|
// src/hooks/session-start.ts
|
|
5999
6673
|
import { spawn as spawn5 } from "child_process";
|
|
6000
|
-
import { access as access7, readFile as
|
|
6674
|
+
import { access as access7, readFile as readFile18, stat as stat4 } from "fs/promises";
|
|
6001
6675
|
import path29 from "path";
|
|
6002
6676
|
|
|
6003
6677
|
// src/invariants/handoff-schema.ts
|
|
@@ -6040,7 +6714,7 @@ function validateHandoffText(text) {
|
|
|
6040
6714
|
}
|
|
6041
6715
|
|
|
6042
6716
|
// src/plan-index/plan-index.ts
|
|
6043
|
-
import { readFile as
|
|
6717
|
+
import { readFile as readFile17, writeFile as writeFile7 } from "fs/promises";
|
|
6044
6718
|
import path28 from "path";
|
|
6045
6719
|
var PLAN_INDEX_REL = ".cursor/context/plan-index.json";
|
|
6046
6720
|
var NONE = /^(none|n\/a|empty|nil)$/i;
|
|
@@ -6049,7 +6723,7 @@ function defaultPlanIndexIo() {
|
|
|
6049
6723
|
return {
|
|
6050
6724
|
async readFile(absPath) {
|
|
6051
6725
|
try {
|
|
6052
|
-
return await
|
|
6726
|
+
return await readFile17(absPath, "utf8");
|
|
6053
6727
|
} catch {
|
|
6054
6728
|
return null;
|
|
6055
6729
|
}
|
|
@@ -6220,6 +6894,137 @@ function extractPlanBasenames(raw) {
|
|
|
6220
6894
|
return ids;
|
|
6221
6895
|
}
|
|
6222
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
|
+
|
|
6223
7028
|
// src/hooks/hard-rules.ts
|
|
6224
7029
|
var HARD_RULES = `# Agent Kit session hard rules (manual mode default)
|
|
6225
7030
|
|
|
@@ -6229,7 +7034,7 @@ var HARD_RULES = `# Agent Kit session hard rules (manual mode default)
|
|
|
6229
7034
|
4. **Read HANDOFF first** when resuming. Do not restart the plan from scratch.
|
|
6230
7035
|
5. **Git:** suggest \`/git-staging\` after a phase with a diff; never \`/git-prod\` without explicit confirmation.
|
|
6231
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.
|
|
6232
|
-
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.
|
|
6233
7038
|
8. **\`/continue-plan\` waits for yes.** Summarize next \`[to-do-id]\`, then stop until the user confirms before editing.
|
|
6234
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.
|
|
6235
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.
|
|
@@ -6252,17 +7057,9 @@ Run \`/cursor-update-awareness\` (or \`agent-kit cursor-awareness --check\`) and
|
|
|
6252
7057
|
// src/hooks/session-start.ts
|
|
6253
7058
|
var NONE_PLACEHOLDERS = /* @__PURE__ */ new Set(["none", "n/a", "empty", "nil"]);
|
|
6254
7059
|
var CURSOR_AWARENESS_SPAWN_TIMEOUT_MS = CHANGELOG_FETCH_TIMEOUT_MS + 3e3;
|
|
6255
|
-
async function readTextLimited(filePath, limit = 60) {
|
|
6256
|
-
try {
|
|
6257
|
-
const lines = (await readFile16(filePath, "utf8")).split(/\r?\n/);
|
|
6258
|
-
return lines.slice(0, limit).join("\n").trim();
|
|
6259
|
-
} catch {
|
|
6260
|
-
return "";
|
|
6261
|
-
}
|
|
6262
|
-
}
|
|
6263
7060
|
async function readFull(filePath) {
|
|
6264
7061
|
try {
|
|
6265
|
-
return await
|
|
7062
|
+
return await readFile18(filePath, "utf8");
|
|
6266
7063
|
} catch {
|
|
6267
7064
|
return "";
|
|
6268
7065
|
}
|
|
@@ -6379,7 +7176,7 @@ async function readinessSection(root) {
|
|
|
6379
7176
|
const snapshotPath = path29.join(root, ".cursor", "context", "readiness.json");
|
|
6380
7177
|
let data;
|
|
6381
7178
|
try {
|
|
6382
|
-
data = JSON.parse(await
|
|
7179
|
+
data = JSON.parse(await readFile18(snapshotPath, "utf8"));
|
|
6383
7180
|
} catch {
|
|
6384
7181
|
return null;
|
|
6385
7182
|
}
|
|
@@ -6416,7 +7213,7 @@ async function dogfoodInboxSection(root) {
|
|
|
6416
7213
|
for (const readme of candidateReadmes) {
|
|
6417
7214
|
if (!await fileExists2(readme)) continue;
|
|
6418
7215
|
try {
|
|
6419
|
-
const text = await
|
|
7216
|
+
const text = await readFile18(readme, "utf8");
|
|
6420
7217
|
if (parseUnprocessedDogfoodItems(text).length) return DOGFOOD_INBOX_HINT;
|
|
6421
7218
|
} catch {
|
|
6422
7219
|
}
|
|
@@ -6426,7 +7223,7 @@ async function dogfoodInboxSection(root) {
|
|
|
6426
7223
|
async function loadUpdateCheckPrefs(root) {
|
|
6427
7224
|
try {
|
|
6428
7225
|
const data = JSON.parse(
|
|
6429
|
-
await
|
|
7226
|
+
await readFile18(path29.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
6430
7227
|
);
|
|
6431
7228
|
const uc = data.updateCheck;
|
|
6432
7229
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -6504,7 +7301,7 @@ async function updateCheckSection(root) {
|
|
|
6504
7301
|
async function loadCursorUpdateCheckPrefs(root) {
|
|
6505
7302
|
try {
|
|
6506
7303
|
const data = JSON.parse(
|
|
6507
|
-
await
|
|
7304
|
+
await readFile18(path29.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
6508
7305
|
);
|
|
6509
7306
|
const uc = data.cursorUpdateCheck;
|
|
6510
7307
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -6699,7 +7496,7 @@ async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
|
6699
7496
|
const root = path29.resolve(rootDir);
|
|
6700
7497
|
const handoffPath = path29.join(root, ".cursor", "HANDOFF.md");
|
|
6701
7498
|
const handoffFull = await readFull(handoffPath);
|
|
6702
|
-
const handoff =
|
|
7499
|
+
const handoff = capHandoffExcerpt(handoffFull);
|
|
6703
7500
|
const parts = [HARD_RULES];
|
|
6704
7501
|
if (await l0Present(root)) {
|
|
6705
7502
|
const readiness = await readinessSection(root);
|
|
@@ -6724,10 +7521,14 @@ ${bullet}
|
|
|
6724
7521
|
Rewrite machine lists as \`- **Field:**\` bullets before trusting Checklist / Current mission.`
|
|
6725
7522
|
);
|
|
6726
7523
|
}
|
|
6727
|
-
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._` : "";
|
|
6728
7529
|
parts.push(`## Current HANDOFF.md (excerpt)
|
|
6729
7530
|
|
|
6730
|
-
${handoff}`);
|
|
7531
|
+
${handoff.text}${note}`);
|
|
6731
7532
|
} else {
|
|
6732
7533
|
parts.push(
|
|
6733
7534
|
"## HANDOFF.md\n\nNo handoff file yet. If starting work, create a plan with to-dos first (`/start-project`)."
|
|
@@ -6816,7 +7617,7 @@ import { spawn as spawn6 } from "child_process";
|
|
|
6816
7617
|
import path32 from "path";
|
|
6817
7618
|
|
|
6818
7619
|
// src/utils/terminal.ts
|
|
6819
|
-
import { readdir as
|
|
7620
|
+
import { readdir as readdir8 } from "fs/promises";
|
|
6820
7621
|
import { homedir as homedir3 } from "os";
|
|
6821
7622
|
import path31 from "path";
|
|
6822
7623
|
import { confirm, isCancel } from "@clack/prompts";
|
|
@@ -6830,7 +7631,7 @@ var NESTED_REPO_SCAN_LIMIT = 200;
|
|
|
6830
7631
|
async function findNestedRepoChildren(resolved) {
|
|
6831
7632
|
let entries;
|
|
6832
7633
|
try {
|
|
6833
|
-
entries = await
|
|
7634
|
+
entries = await readdir8(resolved, { withFileTypes: true });
|
|
6834
7635
|
} catch {
|
|
6835
7636
|
return [];
|
|
6836
7637
|
}
|
|
@@ -7110,11 +7911,11 @@ import { defineCommand as defineCommand11 } from "citty";
|
|
|
7110
7911
|
import { bold, cyan as cyan3, green as green2, options as koloristOptions2 } from "kolorist";
|
|
7111
7912
|
|
|
7112
7913
|
// src/generator/personalization.ts
|
|
7113
|
-
import { readFile as
|
|
7914
|
+
import { readFile as readFile21, writeFile as writeFile12 } from "fs/promises";
|
|
7114
7915
|
import path37 from "path";
|
|
7115
7916
|
|
|
7116
7917
|
// src/generator/claude-command-adapters.ts
|
|
7117
|
-
import { readFile as
|
|
7918
|
+
import { readFile as readFile19, readdir as readdir9, writeFile as writeFile8 } from "fs/promises";
|
|
7118
7919
|
import path33 from "path";
|
|
7119
7920
|
var CURSOR_COMMANDS_DIR_REL = ".cursor/commands";
|
|
7120
7921
|
var CLAUDE_COMMANDS_DIR_REL = ".claude/commands";
|
|
@@ -7137,7 +7938,7 @@ description: ${command.description}
|
|
|
7137
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.
|
|
7138
7939
|
|
|
7139
7940
|
Adapter rules (Claude Code CLI):
|
|
7140
|
-
- 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.
|
|
7141
7942
|
- Skip or cancel means stop.
|
|
7142
7943
|
- Never \`/git-prod\` without an explicit operator yes.
|
|
7143
7944
|
- Do not clone Cursor hooks or invent behavior beyond the SoT file.
|
|
@@ -7147,7 +7948,7 @@ async function discoverInstalledCommands(rootDir) {
|
|
|
7147
7948
|
const dir = path33.join(rootDir, CURSOR_COMMANDS_DIR_REL);
|
|
7148
7949
|
let entries;
|
|
7149
7950
|
try {
|
|
7150
|
-
entries = (await
|
|
7951
|
+
entries = (await readdir9(dir)).filter((f) => f.endsWith(".md"));
|
|
7151
7952
|
} catch {
|
|
7152
7953
|
return [];
|
|
7153
7954
|
}
|
|
@@ -7156,7 +7957,7 @@ async function discoverInstalledCommands(rootDir) {
|
|
|
7156
7957
|
const name = file.slice(0, -3);
|
|
7157
7958
|
if (RESERVED_ADAPTER_NAMES.has(name)) continue;
|
|
7158
7959
|
try {
|
|
7159
|
-
const raw = await
|
|
7960
|
+
const raw = await readFile19(path33.join(dir, file), "utf8");
|
|
7160
7961
|
const parsed = parseCommandFrontmatter(name, raw);
|
|
7161
7962
|
if (parsed) commands.push(parsed);
|
|
7162
7963
|
} catch {
|
|
@@ -7182,7 +7983,7 @@ async function generateClaudeCommandAdapters(rootDir) {
|
|
|
7182
7983
|
results.push({ relativePath: relPath, status: "applied" });
|
|
7183
7984
|
continue;
|
|
7184
7985
|
}
|
|
7185
|
-
const localContent = await
|
|
7986
|
+
const localContent = await readFile19(abs, "utf8");
|
|
7186
7987
|
if (localContent === rendered) {
|
|
7187
7988
|
if (ledger.hashes[relPath] !== contentHash(rendered)) {
|
|
7188
7989
|
ledger.hashes[relPath] = contentHash(rendered);
|
|
@@ -7278,7 +8079,7 @@ async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
|
7278
8079
|
}
|
|
7279
8080
|
|
|
7280
8081
|
// src/generator/claude-session-start-hook.ts
|
|
7281
|
-
import { readFile as
|
|
8082
|
+
import { readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
|
|
7282
8083
|
import path35 from "path";
|
|
7283
8084
|
var CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
7284
8085
|
var RESOLVE_AGENT_KIT_REL = ".cursor/hooks/agent/resolve-agent-kit.sh";
|
|
@@ -7357,7 +8158,7 @@ async function writeClaudeSessionStartHook(rootDir) {
|
|
|
7357
8158
|
const abs = path35.join(rootDir, CLAUDE_SETTINGS_REL);
|
|
7358
8159
|
let existing = null;
|
|
7359
8160
|
try {
|
|
7360
|
-
existing = await
|
|
8161
|
+
existing = await readFile20(abs, "utf8");
|
|
7361
8162
|
} catch {
|
|
7362
8163
|
existing = null;
|
|
7363
8164
|
}
|
|
@@ -7881,12 +8682,12 @@ async function applyPersonalization(input) {
|
|
|
7881
8682
|
async function readRepositoryProfile(rootDir) {
|
|
7882
8683
|
const target = path37.join(rootDir, ".cursor/agent-kit.config.json");
|
|
7883
8684
|
if (!await fileExists(target)) return null;
|
|
7884
|
-
return JSON.parse(await
|
|
8685
|
+
return JSON.parse(await readFile21(target, "utf8"));
|
|
7885
8686
|
}
|
|
7886
8687
|
|
|
7887
8688
|
// src/lifecycle/onboard-migration.ts
|
|
7888
8689
|
import { createHash as createHash4 } from "crypto";
|
|
7889
|
-
import { readFile as
|
|
8690
|
+
import { readFile as readFile22, unlink } from "fs/promises";
|
|
7890
8691
|
import path38 from "path";
|
|
7891
8692
|
var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
|
|
7892
8693
|
var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
|
|
@@ -7898,7 +8699,7 @@ async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_
|
|
|
7898
8699
|
if (!await fileExists(legacyPath)) return "absent";
|
|
7899
8700
|
const namespacedPath = path38.join(projectRoot, NAMESPACED_ONBOARD_PATH);
|
|
7900
8701
|
if (!await fileExists(namespacedPath)) return "preserved-customized";
|
|
7901
|
-
const content = await
|
|
8702
|
+
const content = await readFile22(legacyPath);
|
|
7902
8703
|
const hash = createHash4("sha256").update(content).digest("hex");
|
|
7903
8704
|
if (!managedHashes.has(hash)) return "preserved-customized";
|
|
7904
8705
|
await unlink(legacyPath);
|
|
@@ -8433,32 +9234,354 @@ function shouldLiveRefresh(opts) {
|
|
|
8433
9234
|
const tty = opts.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
|
|
8434
9235
|
return tty;
|
|
8435
9236
|
}
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
"
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
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
|
+
}
|
|
9559
|
+
|
|
9560
|
+
// src/mission-control/snapshot.ts
|
|
9561
|
+
import { execFile as execFile6 } from "child_process";
|
|
9562
|
+
import { access as access8 } from "fs/promises";
|
|
9563
|
+
import path40 from "path";
|
|
9564
|
+
|
|
9565
|
+
// ../../dashboard/lib/live-refresh.mjs
|
|
9566
|
+
import { join, resolve as resolve2 } from "path";
|
|
9567
|
+
var PERIODIC_REFRESH_MS = 15e3;
|
|
9568
|
+
var SNAPSHOT_REPO_SOURCE_RELS = Object.freeze([
|
|
9569
|
+
".cursor/plans",
|
|
9570
|
+
".cursor/HANDOFF.md",
|
|
9571
|
+
".cursor/agents",
|
|
9572
|
+
".cursor/commands",
|
|
9573
|
+
".cursor/memory",
|
|
9574
|
+
".cursor/context/config.json",
|
|
9575
|
+
".cursor/context/current",
|
|
9576
|
+
".cursor/context/readiness.json",
|
|
9577
|
+
".cursor/context/field-report-dismissals.json",
|
|
9578
|
+
".cursor/context/mission-timing.json",
|
|
9579
|
+
".cursor/context/flight-log.json",
|
|
9580
|
+
".cursor/context/field-report-cadence.json",
|
|
9581
|
+
".cursor/skills",
|
|
9582
|
+
"package.json"
|
|
9583
|
+
]);
|
|
9584
|
+
|
|
8462
9585
|
// src/mission-control/snapshot.ts
|
|
8463
9586
|
var DEFAULT_DATA_TIMEOUT_MS = 6e4;
|
|
8464
9587
|
function dataScriptTimeoutMs(env = process.env) {
|
|
@@ -8636,11 +9759,14 @@ function buildMcTuiView(snapshot, error = null) {
|
|
|
8636
9759
|
var CLEAR_HOME = "\x1B[2J\x1B[H";
|
|
8637
9760
|
var HIDE_CURSOR = "\x1B[?25l";
|
|
8638
9761
|
var SHOW_CURSOR = "\x1B[?25h";
|
|
8639
|
-
function
|
|
9762
|
+
function quitKeyOf(chunk) {
|
|
8640
9763
|
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
8641
|
-
|
|
9764
|
+
if (text === "q" || text === "Q") return "q";
|
|
9765
|
+
if (text === "") return "Ctrl-C";
|
|
9766
|
+
return null;
|
|
8642
9767
|
}
|
|
8643
9768
|
async function runMcTuiLoop(opts) {
|
|
9769
|
+
const render = opts.render ?? ((view, renderOpts) => renderMcTui(view, renderOpts));
|
|
8644
9770
|
const live = shouldLiveRefresh({
|
|
8645
9771
|
once: opts.once,
|
|
8646
9772
|
stdoutIsTTY: opts.stdoutIsTTY,
|
|
@@ -8648,12 +9774,14 @@ async function runMcTuiLoop(opts) {
|
|
|
8648
9774
|
});
|
|
8649
9775
|
let frameIndex = 0;
|
|
8650
9776
|
let inFlight = false;
|
|
8651
|
-
|
|
8652
|
-
|
|
9777
|
+
let stopped = false;
|
|
9778
|
+
const paint3 = async (clear) => {
|
|
9779
|
+
if (inFlight || stopped) return;
|
|
8653
9780
|
inFlight = true;
|
|
8654
9781
|
try {
|
|
8655
9782
|
const view = await opts.loadView();
|
|
8656
|
-
|
|
9783
|
+
if (stopped) return;
|
|
9784
|
+
const frame = render(view, {
|
|
8657
9785
|
...opts.renderOpts,
|
|
8658
9786
|
stdoutIsTTY: opts.stdoutIsTTY,
|
|
8659
9787
|
frameIndex
|
|
@@ -8663,6 +9791,7 @@ async function runMcTuiLoop(opts) {
|
|
|
8663
9791
|
` : `${frame}
|
|
8664
9792
|
`);
|
|
8665
9793
|
} catch (err) {
|
|
9794
|
+
if (stopped) return;
|
|
8666
9795
|
const message = err instanceof Error ? err.message : String(err);
|
|
8667
9796
|
const frame = renderMcTui(buildMcTuiView(null, message), {
|
|
8668
9797
|
...opts.renderOpts,
|
|
@@ -8677,7 +9806,7 @@ async function runMcTuiLoop(opts) {
|
|
|
8677
9806
|
inFlight = false;
|
|
8678
9807
|
}
|
|
8679
9808
|
};
|
|
8680
|
-
await
|
|
9809
|
+
await paint3(false);
|
|
8681
9810
|
if (!live) return null;
|
|
8682
9811
|
opts.hooks.write(HIDE_CURSOR);
|
|
8683
9812
|
const intervalMs = opts.intervalMs ?? PERIODIC_REFRESH_MS;
|
|
@@ -8686,7 +9815,7 @@ async function runMcTuiLoop(opts) {
|
|
|
8686
9815
|
let timer;
|
|
8687
9816
|
try {
|
|
8688
9817
|
timer = setInt(() => {
|
|
8689
|
-
void
|
|
9818
|
+
void paint3(true);
|
|
8690
9819
|
}, intervalMs);
|
|
8691
9820
|
} catch (err) {
|
|
8692
9821
|
opts.hooks.write(SHOW_CURSOR);
|
|
@@ -8695,7 +9824,6 @@ async function runMcTuiLoop(opts) {
|
|
|
8695
9824
|
const stdin = opts.hooks.stdin;
|
|
8696
9825
|
const attachQuit = Boolean(stdin?.isTTY);
|
|
8697
9826
|
let quitListener;
|
|
8698
|
-
let stopped = false;
|
|
8699
9827
|
const restoreStdin = () => {
|
|
8700
9828
|
if (!stdin || !attachQuit) return;
|
|
8701
9829
|
if (quitListener) {
|
|
@@ -8719,14 +9847,24 @@ async function runMcTuiLoop(opts) {
|
|
|
8719
9847
|
stdin.setRawMode?.(true);
|
|
8720
9848
|
stdin.resume?.();
|
|
8721
9849
|
quitListener = (chunk) => {
|
|
8722
|
-
if (
|
|
9850
|
+
if (stopped) return;
|
|
9851
|
+
if (opts.hooks.onKey?.(chunk)) return;
|
|
9852
|
+
const key = quitKeyOf(chunk);
|
|
9853
|
+
if (!key) return;
|
|
8723
9854
|
stop();
|
|
9855
|
+
if (opts.hooks.onQuit) {
|
|
9856
|
+
opts.hooks.onQuit(key);
|
|
9857
|
+
return;
|
|
9858
|
+
}
|
|
8724
9859
|
const exitFn = opts.hooks.exit ?? ((code) => process.exit(code));
|
|
8725
9860
|
exitFn(0);
|
|
8726
9861
|
};
|
|
8727
9862
|
stdin.on("data", quitListener);
|
|
8728
9863
|
}
|
|
8729
|
-
return {
|
|
9864
|
+
return {
|
|
9865
|
+
stop,
|
|
9866
|
+
repaint: () => stopped ? Promise.resolve() : paint3(true)
|
|
9867
|
+
};
|
|
8730
9868
|
}
|
|
8731
9869
|
|
|
8732
9870
|
// src/commands/mission-control.ts
|
|
@@ -8807,7 +9945,7 @@ import { defineCommand as defineCommand14 } from "citty";
|
|
|
8807
9945
|
|
|
8808
9946
|
// src/invariants/monitors-untriaged.ts
|
|
8809
9947
|
import { execFile as execFile7 } from "child_process";
|
|
8810
|
-
import { readFile as
|
|
9948
|
+
import { readFile as readFile23, readdir as readdir10, stat as stat5 } from "fs/promises";
|
|
8811
9949
|
import path41 from "path";
|
|
8812
9950
|
import { promisify as promisify6 } from "util";
|
|
8813
9951
|
|
|
@@ -8832,7 +9970,7 @@ function hasOpenGaps(content) {
|
|
|
8832
9970
|
}
|
|
8833
9971
|
async function listMonitorFiles(memoryDir) {
|
|
8834
9972
|
try {
|
|
8835
|
-
const names = await
|
|
9973
|
+
const names = await readdir10(memoryDir);
|
|
8836
9974
|
return names.filter((n) => n.startsWith("plan-monitor-") && n.endsWith(".md")).sort();
|
|
8837
9975
|
} catch {
|
|
8838
9976
|
return [];
|
|
@@ -8880,7 +10018,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
8880
10018
|
for (const name of allNames) {
|
|
8881
10019
|
const abs = path41.join(memoryDir, name);
|
|
8882
10020
|
try {
|
|
8883
|
-
const [content, st] = await Promise.all([
|
|
10021
|
+
const [content, st] = await Promise.all([readFile23(abs, "utf8"), stat5(abs)]);
|
|
8884
10022
|
byName.set(name, { content, mtimeMs: st.mtimeMs });
|
|
8885
10023
|
} catch {
|
|
8886
10024
|
}
|
|
@@ -8893,7 +10031,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
8893
10031
|
const gitFreshSet = [...gitFresh].filter(untriaged).sort();
|
|
8894
10032
|
let handoff = "";
|
|
8895
10033
|
try {
|
|
8896
|
-
handoff = await
|
|
10034
|
+
handoff = await readFile23(path41.join(root, ".cursor", "HANDOFF.md"), "utf8");
|
|
8897
10035
|
} catch {
|
|
8898
10036
|
handoff = "";
|
|
8899
10037
|
}
|
|
@@ -9011,7 +10149,7 @@ var planIndexCommand = defineCommand15({
|
|
|
9011
10149
|
});
|
|
9012
10150
|
|
|
9013
10151
|
// src/commands/run-plan.ts
|
|
9014
|
-
import
|
|
10152
|
+
import path50 from "path";
|
|
9015
10153
|
import { defineCommand as defineCommand16 } from "citty";
|
|
9016
10154
|
|
|
9017
10155
|
// src/plan-loop/backends.ts
|
|
@@ -9019,7 +10157,677 @@ import { execFileSync as execFileSync2, spawn as spawn7 } from "child_process";
|
|
|
9019
10157
|
import { createWriteStream } from "fs";
|
|
9020
10158
|
import { access as access9 } from "fs/promises";
|
|
9021
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";
|
|
9022
10164
|
import { StringDecoder } from "string_decoder";
|
|
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
|
|
10176
|
+
];
|
|
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;
|
|
10190
|
+
}
|
|
10191
|
+
return last;
|
|
10192
|
+
}
|
|
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;
|
|
10400
|
+
pending = "";
|
|
10401
|
+
assistantTexts = [];
|
|
10402
|
+
decoder = new StringDecoder("utf8");
|
|
10403
|
+
feed(chunk) {
|
|
10404
|
+
const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
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;
|
|
10424
|
+
}
|
|
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);
|
|
10435
|
+
}
|
|
10436
|
+
}
|
|
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
|
+
});
|
|
10449
|
+
}
|
|
10450
|
+
}
|
|
10451
|
+
};
|
|
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);
|
|
10460
|
+
}
|
|
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
|
+
}
|
|
9023
10831
|
var CLAUDE_ENV_PASSTHROUGH = [
|
|
9024
10832
|
"ANTHROPIC_BASE_URL",
|
|
9025
10833
|
"ANTHROPIC_AUTH_TOKEN",
|
|
@@ -9036,7 +10844,7 @@ function redactSecrets(text, secrets) {
|
|
|
9036
10844
|
var REDACT_HOLD_MAX_CHARS = 64 * 1024;
|
|
9037
10845
|
var RedactingStreamBuffer = class {
|
|
9038
10846
|
pending = "";
|
|
9039
|
-
decoder = new
|
|
10847
|
+
decoder = new StringDecoder3("utf8");
|
|
9040
10848
|
secrets;
|
|
9041
10849
|
keep;
|
|
9042
10850
|
constructor(secrets) {
|
|
@@ -9177,6 +10985,8 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
9177
10985
|
const spawnFn = options.spawnFn ?? spawn7;
|
|
9178
10986
|
const redact = options.redact ?? [];
|
|
9179
10987
|
const redactedError = (err) => new Error(redactSecrets(String(err), redact));
|
|
10988
|
+
const hitl = options.hitl;
|
|
10989
|
+
const stdinTransport = hitl?.transport.kind === "stdin-stream-json";
|
|
9180
10990
|
return new Promise((resolve3, reject) => {
|
|
9181
10991
|
let out;
|
|
9182
10992
|
let child;
|
|
@@ -9188,7 +10998,7 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
9188
10998
|
reject(redactedError(err));
|
|
9189
10999
|
});
|
|
9190
11000
|
child = spawnFn(command, args, {
|
|
9191
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
11001
|
+
stdio: [stdinTransport ? "pipe" : "ignore", "pipe", "pipe"],
|
|
9192
11002
|
...options.cwd ? { cwd: options.cwd } : {},
|
|
9193
11003
|
...options.env ? { env: options.env } : {}
|
|
9194
11004
|
});
|
|
@@ -9196,11 +11006,33 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
9196
11006
|
reject(redactedError(err));
|
|
9197
11007
|
return;
|
|
9198
11008
|
}
|
|
11009
|
+
const render = options.render ?? createStreamRenderer();
|
|
11010
|
+
const relay = hitl ? createRelay(hitl, child, redactedError) : null;
|
|
9199
11011
|
const emit = (text) => {
|
|
9200
11012
|
if (text.length === 0) return;
|
|
9201
|
-
process.stdout.write(text);
|
|
9202
11013
|
out.write(text);
|
|
11014
|
+
render.feed(text);
|
|
11015
|
+
relay?.feed(text);
|
|
9203
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
|
+
});
|
|
9204
11036
|
const buffers = [];
|
|
9205
11037
|
const tee = (stream) => {
|
|
9206
11038
|
if (!stream) return;
|
|
@@ -9220,14 +11052,138 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
9220
11052
|
});
|
|
9221
11053
|
child.on("close", (code) => {
|
|
9222
11054
|
for (const buffer of buffers) emit(buffer.flush());
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
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();
|
|
9228
11066
|
});
|
|
9229
11067
|
});
|
|
9230
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
|
+
}
|
|
9231
11187
|
var cursorAgentBackend = {
|
|
9232
11188
|
id: "cursor-agent",
|
|
9233
11189
|
async resolve() {
|
|
@@ -9252,7 +11208,11 @@ var cursorAgentBackend = {
|
|
|
9252
11208
|
return spawnLogged("cursor-agent", args, opts.logPath, {
|
|
9253
11209
|
spawnFn: opts.spawnFn,
|
|
9254
11210
|
env,
|
|
9255
|
-
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"
|
|
9256
11216
|
});
|
|
9257
11217
|
}
|
|
9258
11218
|
};
|
|
@@ -9303,7 +11263,7 @@ function checkClaudeVersion(bin, versionFn = defaultVersionFn) {
|
|
|
9303
11263
|
}
|
|
9304
11264
|
return { ok: true, version };
|
|
9305
11265
|
}
|
|
9306
|
-
function claudeHeadlessArgs(opts) {
|
|
11266
|
+
function claudeHeadlessArgs(opts = {}) {
|
|
9307
11267
|
const args = [
|
|
9308
11268
|
"-p",
|
|
9309
11269
|
"--output-format",
|
|
@@ -9311,7 +11271,10 @@ function claudeHeadlessArgs(opts) {
|
|
|
9311
11271
|
"--verbose",
|
|
9312
11272
|
"--dangerously-skip-permissions",
|
|
9313
11273
|
"--permission-prompts",
|
|
9314
|
-
"none"
|
|
11274
|
+
"none",
|
|
11275
|
+
"--input-format",
|
|
11276
|
+
"stream-json",
|
|
11277
|
+
"--replay-user-messages"
|
|
9315
11278
|
];
|
|
9316
11279
|
if (opts.maxTurns !== void 0) {
|
|
9317
11280
|
args.push("--max-turns", String(opts.maxTurns));
|
|
@@ -9322,7 +11285,6 @@ function claudeHeadlessArgs(opts) {
|
|
|
9322
11285
|
if (opts.model) {
|
|
9323
11286
|
args.push("--model", opts.model);
|
|
9324
11287
|
}
|
|
9325
|
-
args.push(opts.prompt);
|
|
9326
11288
|
return args;
|
|
9327
11289
|
}
|
|
9328
11290
|
async function missingClaudeAdapter(workspace, prompt) {
|
|
@@ -9367,15 +11329,22 @@ var claudeBackend = {
|
|
|
9367
11329
|
(key) => `${key}=${env[key] ? "set" : "unset"}`
|
|
9368
11330
|
).join(" ");
|
|
9369
11331
|
log(
|
|
9370
|
-
`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}`
|
|
9371
11333
|
);
|
|
9372
|
-
const args = claudeHeadlessArgs({
|
|
11334
|
+
const args = claudeHeadlessArgs({ model: opts.model, ...caps });
|
|
9373
11335
|
try {
|
|
9374
11336
|
return await spawnLogged("claude", args, opts.logPath, {
|
|
9375
11337
|
cwd: opts.workspace,
|
|
9376
11338
|
env,
|
|
9377
11339
|
spawnFn: opts.spawnFn,
|
|
9378
|
-
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"
|
|
9379
11348
|
});
|
|
9380
11349
|
} catch (err) {
|
|
9381
11350
|
throw new Error(`claude backend failed to start: ${redactSecrets(String(err), redact)}`);
|
|
@@ -9445,14 +11414,464 @@ async function detectAgentBackend(requested, whichFn = whichBinary) {
|
|
|
9445
11414
|
}
|
|
9446
11415
|
|
|
9447
11416
|
// src/plan-loop/run-loop.ts
|
|
9448
|
-
import { mkdir as mkdir6, readFile as
|
|
9449
|
-
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
|
+
}
|
|
9450
11869
|
|
|
9451
11870
|
// src/plan-loop/external-review.ts
|
|
9452
11871
|
import { spawn as spawn8 } from "child_process";
|
|
9453
|
-
import
|
|
9454
|
-
var CANONICAL_REL =
|
|
9455
|
-
var FALLBACK_REL =
|
|
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");
|
|
9456
11875
|
function isPlanExhaustedReason(reason) {
|
|
9457
11876
|
const r = reason.trim().toLowerCase();
|
|
9458
11877
|
if (!r) return false;
|
|
@@ -9474,8 +11893,8 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
9474
11893
|
const existsFn = options.existsFn ?? fileExists;
|
|
9475
11894
|
const log = options.log ?? ((line) => console.log(line));
|
|
9476
11895
|
const force = options.force === true;
|
|
9477
|
-
const canonicalPath =
|
|
9478
|
-
const fallbackPath =
|
|
11896
|
+
const canonicalPath = path47.join(root, CANONICAL_REL);
|
|
11897
|
+
const fallbackPath = path47.join(root, FALLBACK_REL);
|
|
9479
11898
|
let scriptPath = null;
|
|
9480
11899
|
let scriptRel = CANONICAL_REL;
|
|
9481
11900
|
if (await existsFn(canonicalPath)) {
|
|
@@ -9528,7 +11947,7 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
9528
11947
|
}
|
|
9529
11948
|
|
|
9530
11949
|
// src/plan-loop/persona-banners.ts
|
|
9531
|
-
import
|
|
11950
|
+
import path48 from "path";
|
|
9532
11951
|
import {
|
|
9533
11952
|
blue,
|
|
9534
11953
|
cyan as cyan4,
|
|
@@ -9564,7 +11983,7 @@ function resolveColor(name, fallback) {
|
|
|
9564
11983
|
async function resolveCliPersonaId(root) {
|
|
9565
11984
|
try {
|
|
9566
11985
|
const cfg = await readJson(
|
|
9567
|
-
|
|
11986
|
+
path48.join(root, ".cursor", "context", "config.json")
|
|
9568
11987
|
);
|
|
9569
11988
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
9570
11989
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -9575,7 +11994,7 @@ async function resolveCliPersonaId(root) {
|
|
|
9575
11994
|
}
|
|
9576
11995
|
async function loadPersonaPack(root, personaId) {
|
|
9577
11996
|
try {
|
|
9578
|
-
const personaPath =
|
|
11997
|
+
const personaPath = path48.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
9579
11998
|
const pack = await readJson(personaPath);
|
|
9580
11999
|
if (!pack || typeof pack.id !== "string") return null;
|
|
9581
12000
|
return pack;
|
|
@@ -9623,44 +12042,8 @@ function createPersonaBannerPrinter(persona) {
|
|
|
9623
12042
|
};
|
|
9624
12043
|
}
|
|
9625
12044
|
|
|
9626
|
-
// src/plan-loop/plan-state.ts
|
|
9627
|
-
import { readFile as readFile22, readdir as readdir9 } from "fs/promises";
|
|
9628
|
-
import path47 from "path";
|
|
9629
|
-
function countPendingTodos(raw) {
|
|
9630
|
-
const lines = raw.split(/\r?\n/);
|
|
9631
|
-
let inFront = 0;
|
|
9632
|
-
let cont = false;
|
|
9633
|
-
let n = 0;
|
|
9634
|
-
for (const line of lines) {
|
|
9635
|
-
if (line === "---") {
|
|
9636
|
-
inFront += 1;
|
|
9637
|
-
continue;
|
|
9638
|
-
}
|
|
9639
|
-
if (inFront !== 1) continue;
|
|
9640
|
-
const idMatch = line.match(/^- id:\s*(\S+)/);
|
|
9641
|
-
if (idMatch?.[1]) {
|
|
9642
|
-
cont = idMatch[1] === "dogfood-poc";
|
|
9643
|
-
}
|
|
9644
|
-
if (/content:.*[Cc]ont[ií]nuo/.test(line)) {
|
|
9645
|
-
cont = true;
|
|
9646
|
-
}
|
|
9647
|
-
if (/status:\s*(pending|in_progress)\b/.test(line) && !cont) {
|
|
9648
|
-
n += 1;
|
|
9649
|
-
}
|
|
9650
|
-
}
|
|
9651
|
-
return n;
|
|
9652
|
-
}
|
|
9653
|
-
async function findActivePlanFile(plansDir) {
|
|
9654
|
-
if (!await fileExists(plansDir)) return null;
|
|
9655
|
-
const files = (await readdir9(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
9656
|
-
return files[0] ? path47.join(plansDir, files[0]) : null;
|
|
9657
|
-
}
|
|
9658
|
-
async function readPlan(planPath) {
|
|
9659
|
-
return readFile22(planPath, "utf8");
|
|
9660
|
-
}
|
|
9661
|
-
|
|
9662
12045
|
// src/plan-loop/sentinel.ts
|
|
9663
|
-
import { readFile as
|
|
12046
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
9664
12047
|
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—\-].*)?)/i;
|
|
9665
12048
|
function takeFromText(text) {
|
|
9666
12049
|
if (!text) return null;
|
|
@@ -9726,14 +12109,14 @@ function parseTickResultStatus(content) {
|
|
|
9726
12109
|
}
|
|
9727
12110
|
async function parseTickResultStatusFromLogFile(logPath) {
|
|
9728
12111
|
try {
|
|
9729
|
-
return parseTickResultStatus(await
|
|
12112
|
+
return parseTickResultStatus(await readFile26(logPath, "utf8"));
|
|
9730
12113
|
} catch {
|
|
9731
12114
|
return null;
|
|
9732
12115
|
}
|
|
9733
12116
|
}
|
|
9734
12117
|
async function parseSentinelFromLogFile(logPath) {
|
|
9735
12118
|
try {
|
|
9736
|
-
const content = await
|
|
12119
|
+
const content = await readFile26(logPath, "utf8");
|
|
9737
12120
|
return parseSentinelFromLog(content);
|
|
9738
12121
|
} catch {
|
|
9739
12122
|
return { kind: "missing" };
|
|
@@ -9747,6 +12130,40 @@ function formatSentinelLine(sentinel) {
|
|
|
9747
12130
|
|
|
9748
12131
|
// src/plan-loop/run-loop.ts
|
|
9749
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
|
+
}
|
|
9750
12167
|
function stamp() {
|
|
9751
12168
|
const d = /* @__PURE__ */ new Date();
|
|
9752
12169
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
@@ -9756,9 +12173,9 @@ function sleep(ms) {
|
|
|
9756
12173
|
return new Promise((r) => setTimeout(r, ms));
|
|
9757
12174
|
}
|
|
9758
12175
|
async function runPlanLoop(opts) {
|
|
9759
|
-
const plansDir =
|
|
9760
|
-
const stopFile =
|
|
9761
|
-
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");
|
|
9762
12179
|
const planPath = await findActivePlanFile(plansDir);
|
|
9763
12180
|
if (!planPath) {
|
|
9764
12181
|
logger.error("No active plan in .cursor/plans/");
|
|
@@ -9779,7 +12196,7 @@ async function runPlanLoop(opts) {
|
|
|
9779
12196
|
try {
|
|
9780
12197
|
const persona = await loadCliRunPlanPersona(opts.root);
|
|
9781
12198
|
const banners = createPersonaBannerPrinter(persona);
|
|
9782
|
-
console.log(`Active plan: ${
|
|
12199
|
+
console.log(`Active plan: ${path49.basename(planPath)}`);
|
|
9783
12200
|
console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
|
|
9784
12201
|
console.log(`Backend: ${opts.backend.id}`);
|
|
9785
12202
|
if (persona) {
|
|
@@ -9798,6 +12215,7 @@ async function runPlanLoop(opts) {
|
|
|
9798
12215
|
let tick = 0;
|
|
9799
12216
|
let stopReason;
|
|
9800
12217
|
let planExhausted = false;
|
|
12218
|
+
let exitCode = 0;
|
|
9801
12219
|
while (true) {
|
|
9802
12220
|
tick += 1;
|
|
9803
12221
|
if (tick > opts.maxTicks) {
|
|
@@ -9822,30 +12240,47 @@ async function runPlanLoop(opts) {
|
|
|
9822
12240
|
planExhausted = true;
|
|
9823
12241
|
break;
|
|
9824
12242
|
}
|
|
9825
|
-
const logPath =
|
|
9826
|
-
const relLog =
|
|
12243
|
+
const logPath = path49.join(logDir, `tick-${stamp()}.log`);
|
|
12244
|
+
const relLog = path49.relative(opts.root, logPath);
|
|
9827
12245
|
console.log("");
|
|
9828
12246
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
9829
12247
|
if (banners) banners.tickStart(tickLine);
|
|
9830
12248
|
else console.log(tickLine);
|
|
9831
12249
|
let agentExit = 0;
|
|
12250
|
+
let hitlStop;
|
|
9832
12251
|
try {
|
|
9833
|
-
const result = await
|
|
12252
|
+
const result = await runTickWithSpinner(
|
|
9834
12253
|
`tick ${tick}`,
|
|
9835
|
-
|
|
12254
|
+
opts.backend,
|
|
12255
|
+
{
|
|
9836
12256
|
workspace: opts.root,
|
|
9837
12257
|
prompt: TICK_PROMPT,
|
|
9838
12258
|
model: opts.model,
|
|
9839
|
-
logPath
|
|
9840
|
-
|
|
12259
|
+
logPath,
|
|
12260
|
+
hitl: { policy: opts.noHitl ? "off" : "prompt" }
|
|
12261
|
+
},
|
|
12262
|
+
opts.plain === true
|
|
9841
12263
|
);
|
|
9842
12264
|
agentExit = result.exitCode;
|
|
12265
|
+
for (const line of formatHitlSummary(result.hitl)) console.log(line);
|
|
12266
|
+
hitlStop = result.hitl?.stop;
|
|
9843
12267
|
} catch (err) {
|
|
9844
12268
|
logger.error(String(err));
|
|
9845
12269
|
return 1;
|
|
9846
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
|
+
}
|
|
9847
12282
|
try {
|
|
9848
|
-
const logText = await
|
|
12283
|
+
const logText = await readFile27(logPath, "utf8");
|
|
9849
12284
|
if (logText.includes("Too many MCP tools")) {
|
|
9850
12285
|
const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
|
|
9851
12286
|
if (banners) banners.stop(msg);
|
|
@@ -9914,12 +12349,12 @@ async function runPlanLoop(opts) {
|
|
|
9914
12349
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
9915
12350
|
if (banners) banners.phaseComplete(finishDetail);
|
|
9916
12351
|
console.log(
|
|
9917
|
-
`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)}/`
|
|
9918
12353
|
);
|
|
9919
12354
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
9920
12355
|
await armExternalPlanReview(opts.root);
|
|
9921
12356
|
}
|
|
9922
|
-
return
|
|
12357
|
+
return exitCode;
|
|
9923
12358
|
} finally {
|
|
9924
12359
|
process.off("SIGINT", onSigInt);
|
|
9925
12360
|
}
|
|
@@ -9961,6 +12396,16 @@ var runPlanCommand = defineCommand16({
|
|
|
9961
12396
|
type: "boolean",
|
|
9962
12397
|
description: "Print the tick prompt and exit without starting an agent",
|
|
9963
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
|
|
9964
12409
|
}
|
|
9965
12410
|
},
|
|
9966
12411
|
async run({ args }) {
|
|
@@ -9996,12 +12441,14 @@ var runPlanCommand = defineCommand16({
|
|
|
9996
12441
|
return;
|
|
9997
12442
|
}
|
|
9998
12443
|
const code = await runPlanLoop({
|
|
9999
|
-
root:
|
|
12444
|
+
root: path50.resolve(args.cwd),
|
|
10000
12445
|
maxTicks,
|
|
10001
12446
|
sleepSeconds,
|
|
10002
12447
|
model: args.model ? String(args.model) : void 0,
|
|
10003
12448
|
dryRun: Boolean(args["dry-run"]),
|
|
10004
|
-
backend
|
|
12449
|
+
backend,
|
|
12450
|
+
noHitl: Boolean(args["no-hitl"]),
|
|
12451
|
+
plain: Boolean(args.plain)
|
|
10005
12452
|
});
|
|
10006
12453
|
process.exitCode = code;
|
|
10007
12454
|
}
|
|
@@ -10009,12 +12456,12 @@ var runPlanCommand = defineCommand16({
|
|
|
10009
12456
|
|
|
10010
12457
|
// src/commands/run.ts
|
|
10011
12458
|
import { homedir as homedir4 } from "os";
|
|
10012
|
-
import
|
|
12459
|
+
import path52 from "path";
|
|
10013
12460
|
import { defineCommand as defineCommand17 } from "citty";
|
|
10014
12461
|
|
|
10015
12462
|
// src/plan-loop/dispatch.ts
|
|
10016
|
-
import { mkdir as mkdir7, readFile as
|
|
10017
|
-
import
|
|
12463
|
+
import { mkdir as mkdir7, readFile as readFile28 } from "fs/promises";
|
|
12464
|
+
import path51 from "path";
|
|
10018
12465
|
var RUN_CATALOG = [
|
|
10019
12466
|
"run-plan",
|
|
10020
12467
|
"run-plan-all",
|
|
@@ -10058,11 +12505,11 @@ function rewriteRootArgvToRun(argv) {
|
|
|
10058
12505
|
return next;
|
|
10059
12506
|
}
|
|
10060
12507
|
function commandMarkdownPath(root, name) {
|
|
10061
|
-
return
|
|
12508
|
+
return path51.join(root, ".cursor", "commands", `${normalizeSlashName(name)}.md`);
|
|
10062
12509
|
}
|
|
10063
12510
|
function buildDispatchPrompt(commandBody) {
|
|
10064
12511
|
return [
|
|
10065
|
-
"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.",
|
|
10066
12513
|
"",
|
|
10067
12514
|
commandBody.trimEnd(),
|
|
10068
12515
|
""
|
|
@@ -10083,7 +12530,7 @@ async function readCommandFile(root, name) {
|
|
|
10083
12530
|
message: `Command file not found: ${filePath}. Install or refresh Agent Kit L0 so .cursor/commands/${normalizeSlashName(name)}.md exists.`
|
|
10084
12531
|
};
|
|
10085
12532
|
}
|
|
10086
|
-
const body = await
|
|
12533
|
+
const body = await readFile28(filePath, "utf8");
|
|
10087
12534
|
return { ok: true, path: filePath, body };
|
|
10088
12535
|
}
|
|
10089
12536
|
function stamp2() {
|
|
@@ -10106,7 +12553,7 @@ function cursorAgentDispatchArgs(opts) {
|
|
|
10106
12553
|
args.push(opts.prompt);
|
|
10107
12554
|
return args;
|
|
10108
12555
|
}
|
|
10109
|
-
function claudeDispatchArgs(opts) {
|
|
12556
|
+
function claudeDispatchArgs(opts = {}) {
|
|
10110
12557
|
return claudeHeadlessArgs(opts);
|
|
10111
12558
|
}
|
|
10112
12559
|
async function runHeadlessDispatch(opts) {
|
|
@@ -10123,9 +12570,21 @@ async function runHeadlessDispatch(opts) {
|
|
|
10123
12570
|
try {
|
|
10124
12571
|
return await spawnLogged(
|
|
10125
12572
|
opts.bin,
|
|
10126
|
-
claudeDispatchArgs({
|
|
12573
|
+
claudeDispatchArgs({ model: opts.model, ...caps }),
|
|
10127
12574
|
opts.logPath,
|
|
10128
|
-
{
|
|
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
|
+
}
|
|
10129
12588
|
);
|
|
10130
12589
|
} catch (err) {
|
|
10131
12590
|
throw new Error(`claude dispatch failed to start: ${redactSecrets(String(err), redact)}`);
|
|
@@ -10138,24 +12597,28 @@ async function runHeadlessDispatch(opts) {
|
|
|
10138
12597
|
});
|
|
10139
12598
|
return spawnLogged(opts.bin, args, opts.logPath, {
|
|
10140
12599
|
spawnFn: opts.spawnFn,
|
|
10141
|
-
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"
|
|
10142
12605
|
});
|
|
10143
12606
|
}
|
|
10144
12607
|
async function ensureDispatchLogPath(root) {
|
|
10145
|
-
const logDir =
|
|
12608
|
+
const logDir = path51.join(root, ".cursor", "loop-logs");
|
|
10146
12609
|
await mkdir7(logDir, { recursive: true });
|
|
10147
|
-
return
|
|
12610
|
+
return path51.join(logDir, `run-${stamp2()}.log`);
|
|
10148
12611
|
}
|
|
10149
12612
|
|
|
10150
12613
|
// src/commands/run.ts
|
|
10151
12614
|
function displayPath(target, home = homedir4()) {
|
|
10152
|
-
const resolvedHome =
|
|
12615
|
+
const resolvedHome = path52.resolve(home);
|
|
10153
12616
|
if (target === resolvedHome) return "~";
|
|
10154
|
-
const prefix = resolvedHome.endsWith(
|
|
10155
|
-
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;
|
|
10156
12619
|
}
|
|
10157
12620
|
async function executeRun(input) {
|
|
10158
|
-
const root =
|
|
12621
|
+
const root = path52.resolve(input.cwd);
|
|
10159
12622
|
const slash = normalizeSlashName(input.slash);
|
|
10160
12623
|
if (!slash) {
|
|
10161
12624
|
return { exitCode: 1, error: "Provide a slash: agent-kit run <slash>" };
|
|
@@ -10191,7 +12654,9 @@ async function executeRun(input) {
|
|
|
10191
12654
|
sleepSeconds: input.sleepSeconds,
|
|
10192
12655
|
model: input.model,
|
|
10193
12656
|
dryRun: false,
|
|
10194
|
-
backend
|
|
12657
|
+
backend,
|
|
12658
|
+
noHitl: input.noHitl === true,
|
|
12659
|
+
plain: input.plain === true
|
|
10195
12660
|
});
|
|
10196
12661
|
return { exitCode: code };
|
|
10197
12662
|
}
|
|
@@ -10204,7 +12669,7 @@ async function executeRun(input) {
|
|
|
10204
12669
|
const stdout = [
|
|
10205
12670
|
"--dry-run: no agent will be started.",
|
|
10206
12671
|
`Slash: ${slash}`,
|
|
10207
|
-
`Command file: ${
|
|
12672
|
+
`Command file: ${path52.relative(root, file.path)}`,
|
|
10208
12673
|
`Backend: ${detected.id} (${displayPath(detected.bin)})`,
|
|
10209
12674
|
"Prompt:",
|
|
10210
12675
|
prompt
|
|
@@ -10213,18 +12678,32 @@ async function executeRun(input) {
|
|
|
10213
12678
|
}
|
|
10214
12679
|
const logPath = await ensureDispatchLogPath(root);
|
|
10215
12680
|
console.log(`Dispatch: ${slash} via ${detected.id}`);
|
|
10216
|
-
console.log(`Command file: ${
|
|
10217
|
-
console.log(`Log: ${
|
|
12681
|
+
console.log(`Command file: ${path52.relative(root, file.path)}`);
|
|
12682
|
+
console.log(`Log: ${path52.relative(root, logPath)}`);
|
|
10218
12683
|
try {
|
|
10219
|
-
const
|
|
12684
|
+
const dispatch = {
|
|
10220
12685
|
backendId: detected.id,
|
|
10221
12686
|
bin: detected.bin,
|
|
10222
12687
|
workspace: root,
|
|
10223
12688
|
prompt,
|
|
10224
12689
|
model: input.model,
|
|
10225
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" }
|
|
10226
12704
|
});
|
|
10227
|
-
|
|
12705
|
+
for (const line of formatHitlSummary(result.hitl)) console.log(line);
|
|
12706
|
+
return { exitCode: hitlExitCode(result.exitCode, result.hitl) };
|
|
10228
12707
|
} catch (err) {
|
|
10229
12708
|
return { exitCode: 1, error: String(err) };
|
|
10230
12709
|
}
|
|
@@ -10259,6 +12738,16 @@ var runDispatchArgs = {
|
|
|
10259
12738
|
type: "string",
|
|
10260
12739
|
description: "For run-plan alias only: seconds between ticks (default: 5)",
|
|
10261
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
|
|
10262
12751
|
}
|
|
10263
12752
|
};
|
|
10264
12753
|
async function runSlashCli(slash, args) {
|
|
@@ -10281,7 +12770,9 @@ async function runSlashCli(slash, args) {
|
|
|
10281
12770
|
model: args.model ? String(args.model) : void 0,
|
|
10282
12771
|
dryRun: Boolean(args["dry-run"]),
|
|
10283
12772
|
maxTicks,
|
|
10284
|
-
sleepSeconds
|
|
12773
|
+
sleepSeconds,
|
|
12774
|
+
noHitl: Boolean(args["no-hitl"]),
|
|
12775
|
+
plain: Boolean(args.plain)
|
|
10285
12776
|
});
|
|
10286
12777
|
if (result.stdout) {
|
|
10287
12778
|
console.log(result.stdout);
|
|
@@ -10294,7 +12785,7 @@ async function runSlashCli(slash, args) {
|
|
|
10294
12785
|
var runCommand = defineCommand17({
|
|
10295
12786
|
meta: {
|
|
10296
12787
|
name: "run",
|
|
10297
|
-
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."
|
|
10298
12789
|
},
|
|
10299
12790
|
args: {
|
|
10300
12791
|
slash: {
|
|
@@ -10311,7 +12802,7 @@ var runCommand = defineCommand17({
|
|
|
10311
12802
|
var runPlanAllCommand = defineCommand17({
|
|
10312
12803
|
meta: {
|
|
10313
12804
|
name: "run-plan-all",
|
|
10314
|
-
description: "Headless /run-plan-all queue from the L0 file. Numbered-list HITL. Never git-prod."
|
|
12805
|
+
description: "Headless /run-plan-all queue from the L0 file. Numbered-list HITL answered from the terminal (claude backend). Never git-prod."
|
|
10315
12806
|
},
|
|
10316
12807
|
args: runDispatchArgs,
|
|
10317
12808
|
async run({ args }) {
|
|
@@ -10343,9 +12834,9 @@ var scanCommand = defineCommand18({
|
|
|
10343
12834
|
|
|
10344
12835
|
// src/commands/setup-global.ts
|
|
10345
12836
|
import { spawn as spawn9 } from "child_process";
|
|
10346
|
-
import { appendFile, mkdir as mkdir8, readFile as
|
|
12837
|
+
import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile29, writeFile as writeFile13 } from "fs/promises";
|
|
10347
12838
|
import { homedir as homedir5 } from "os";
|
|
10348
|
-
import
|
|
12839
|
+
import path53 from "path";
|
|
10349
12840
|
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
10350
12841
|
import { defineCommand as defineCommand19 } from "citty";
|
|
10351
12842
|
import { cyan as cyan5, green as green4, yellow as yellow3 } from "kolorist";
|
|
@@ -10354,9 +12845,9 @@ var SETUP_GLOBAL_MARKER = "# agent-kit setup-global";
|
|
|
10354
12845
|
function planSetupGlobalSteps(env, options = {}) {
|
|
10355
12846
|
const homeDir = options.homeDir ?? homedir5();
|
|
10356
12847
|
const packageSpec = options.packageSpec ?? pinnedCliSpec();
|
|
10357
|
-
const npmGlobalDir =
|
|
10358
|
-
const npmGlobalBin =
|
|
10359
|
-
const npmrcPath =
|
|
12848
|
+
const npmGlobalDir = path53.join(homeDir, NPM_GLOBAL_DIR_NAME);
|
|
12849
|
+
const npmGlobalBin = path53.join(npmGlobalDir, "bin");
|
|
12850
|
+
const npmrcPath = path53.join(homeDir, ".npmrc");
|
|
10360
12851
|
const npmrcPrefixValue = `~/${NPM_GLOBAL_DIR_NAME}`;
|
|
10361
12852
|
const pathExportLine = `export PATH="${npmGlobalBin}:$PATH"`;
|
|
10362
12853
|
const shellProfile = env.shellProfile;
|
|
@@ -10424,9 +12915,9 @@ var defaultFsImpl = {
|
|
|
10424
12915
|
mkdir: async (dir) => {
|
|
10425
12916
|
await mkdir8(dir, { recursive: true });
|
|
10426
12917
|
},
|
|
10427
|
-
readFile: (filePath) =>
|
|
12918
|
+
readFile: (filePath) => readFile29(filePath, "utf8"),
|
|
10428
12919
|
writeFile: (filePath, content) => writeFile13(filePath, content, "utf8"),
|
|
10429
|
-
appendFile: (filePath, content) =>
|
|
12920
|
+
appendFile: (filePath, content) => appendFile2(filePath, content, "utf8")
|
|
10430
12921
|
};
|
|
10431
12922
|
async function safeReadFile(fs, filePath) {
|
|
10432
12923
|
try {
|
|
@@ -10655,7 +13146,7 @@ var setupGlobalCommand = defineCommand19({
|
|
|
10655
13146
|
});
|
|
10656
13147
|
|
|
10657
13148
|
// src/commands/status.ts
|
|
10658
|
-
import
|
|
13149
|
+
import path54 from "path";
|
|
10659
13150
|
import { defineCommand as defineCommand20 } from "citty";
|
|
10660
13151
|
function profileStatus(profile) {
|
|
10661
13152
|
if (!profile) return { origin: "none", evidence: [], profile: null };
|
|
@@ -10686,11 +13177,11 @@ var statusCommand = defineCommand20({
|
|
|
10686
13177
|
}
|
|
10687
13178
|
},
|
|
10688
13179
|
async run({ args }) {
|
|
10689
|
-
const rootDir =
|
|
13180
|
+
const rootDir = path54.resolve(args.cwd);
|
|
10690
13181
|
const [manifest, rawProfile, scan, env] = await Promise.all([
|
|
10691
13182
|
loadAgentKitManifest(rootDir),
|
|
10692
13183
|
readJson(
|
|
10693
|
-
|
|
13184
|
+
path54.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
10694
13185
|
),
|
|
10695
13186
|
runScanner(rootDir),
|
|
10696
13187
|
assessEnvironment()
|
|
@@ -10935,8 +13426,8 @@ ${err.recovery}
|
|
|
10935
13426
|
});
|
|
10936
13427
|
|
|
10937
13428
|
// src/commands/validate.ts
|
|
10938
|
-
import { readFile as
|
|
10939
|
-
import
|
|
13429
|
+
import { readFile as readFile30 } from "fs/promises";
|
|
13430
|
+
import path55 from "path";
|
|
10940
13431
|
import { defineCommand as defineCommand22 } from "citty";
|
|
10941
13432
|
|
|
10942
13433
|
// src/invariants/plan-schema.ts
|
|
@@ -10969,7 +13460,7 @@ function validatePlanFrontmatterText(text) {
|
|
|
10969
13460
|
cite: CITE5
|
|
10970
13461
|
});
|
|
10971
13462
|
}
|
|
10972
|
-
const hasTodoItem =
|
|
13463
|
+
const hasTodoItem = /^\s*- id:\s*\S+/m.test(block);
|
|
10973
13464
|
if (/^todos:/m.test(block) && !hasTodoItem && !/^todos:\s*\[\s*\]/m.test(block)) {
|
|
10974
13465
|
warnings.push({
|
|
10975
13466
|
code: "empty-todos",
|
|
@@ -10983,9 +13474,9 @@ function validatePlanFrontmatterText(text) {
|
|
|
10983
13474
|
// src/commands/validate.ts
|
|
10984
13475
|
async function resolveEditedPath(cwd, explicit) {
|
|
10985
13476
|
if (explicit) {
|
|
10986
|
-
const filePath2 =
|
|
13477
|
+
const filePath2 = path55.resolve(cwd, explicit);
|
|
10987
13478
|
try {
|
|
10988
|
-
return { filePath: filePath2, content: await
|
|
13479
|
+
return { filePath: filePath2, content: await readFile30(filePath2, "utf8") };
|
|
10989
13480
|
} catch {
|
|
10990
13481
|
return null;
|
|
10991
13482
|
}
|
|
@@ -10993,9 +13484,9 @@ async function resolveEditedPath(cwd, explicit) {
|
|
|
10993
13484
|
const payload = await readStdinJson();
|
|
10994
13485
|
const rel = typeof payload.file_path === "string" && payload.file_path || typeof payload.path === "string" && payload.path || typeof payload.file === "string" && payload.file || "";
|
|
10995
13486
|
if (!rel) return null;
|
|
10996
|
-
const filePath =
|
|
13487
|
+
const filePath = path55.isAbsolute(rel) ? rel : path55.resolve(cwd, rel);
|
|
10997
13488
|
try {
|
|
10998
|
-
return { filePath, content: await
|
|
13489
|
+
return { filePath, content: await readFile30(filePath, "utf8") };
|
|
10999
13490
|
} catch {
|
|
11000
13491
|
return null;
|
|
11001
13492
|
}
|
|
@@ -11023,15 +13514,21 @@ var validateCommand = defineCommand22({
|
|
|
11023
13514
|
async run({ args }) {
|
|
11024
13515
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
11025
13516
|
const fileArg = typeof args.file === "string" ? args.file : void 0;
|
|
11026
|
-
const filePath = fileArg ?
|
|
13517
|
+
const filePath = fileArg ? path55.resolve(cwd, fileArg) : path55.join(path55.resolve(cwd), ".cursor", "HANDOFF.md");
|
|
11027
13518
|
let content = "";
|
|
11028
13519
|
try {
|
|
11029
|
-
content = await
|
|
13520
|
+
content = await readFile30(filePath, "utf8");
|
|
11030
13521
|
} catch {
|
|
11031
13522
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
11032
13523
|
return;
|
|
11033
13524
|
}
|
|
11034
|
-
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
|
+
];
|
|
11035
13532
|
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
11036
13533
|
}
|
|
11037
13534
|
}),
|
|
@@ -11050,10 +13547,10 @@ var validateCommand = defineCommand22({
|
|
|
11050
13547
|
process.exitCode = 2;
|
|
11051
13548
|
return;
|
|
11052
13549
|
}
|
|
11053
|
-
const filePath =
|
|
13550
|
+
const filePath = path55.resolve(cwd, fileArg);
|
|
11054
13551
|
let content = "";
|
|
11055
13552
|
try {
|
|
11056
|
-
content = await
|
|
13553
|
+
content = await readFile30(filePath, "utf8");
|
|
11057
13554
|
} catch {
|
|
11058
13555
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
11059
13556
|
return;
|
|
@@ -11072,19 +13569,22 @@ var validateCommand = defineCommand22({
|
|
|
11072
13569
|
},
|
|
11073
13570
|
async run({ args }) {
|
|
11074
13571
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
11075
|
-
const resolved = await resolveEditedPath(
|
|
13572
|
+
const resolved = await resolveEditedPath(path55.resolve(cwd));
|
|
11076
13573
|
if (!resolved) {
|
|
11077
13574
|
console.log(JSON.stringify({}));
|
|
11078
13575
|
return;
|
|
11079
13576
|
}
|
|
11080
13577
|
const { filePath, content } = resolved;
|
|
11081
13578
|
if (isHandoffPath(filePath)) {
|
|
11082
|
-
const warnings =
|
|
13579
|
+
const warnings = [
|
|
13580
|
+
...validateHandoffText(content).map((w) => w.message),
|
|
13581
|
+
...checkHandoffGrowth(content).map((w) => w.message)
|
|
13582
|
+
];
|
|
11083
13583
|
if (!warnings.length) {
|
|
11084
13584
|
console.log(JSON.stringify({}));
|
|
11085
13585
|
return;
|
|
11086
13586
|
}
|
|
11087
|
-
const msg = warnings.
|
|
13587
|
+
const msg = warnings.join(" ");
|
|
11088
13588
|
console.log(
|
|
11089
13589
|
JSON.stringify({
|
|
11090
13590
|
user_message: msg,
|
|
@@ -11118,7 +13618,7 @@ var validateCommand = defineCommand22({
|
|
|
11118
13618
|
import { bold as bold2, gray as gray4, underline } from "kolorist";
|
|
11119
13619
|
|
|
11120
13620
|
// src/welcome/screen.ts
|
|
11121
|
-
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";
|
|
11122
13622
|
var HELMET_ASCII = [
|
|
11123
13623
|
" ____",
|
|
11124
13624
|
" .-' '-.",
|
|
@@ -11139,7 +13639,7 @@ function hexToRgb(hex) {
|
|
|
11139
13639
|
}
|
|
11140
13640
|
function outlineAnsi(line) {
|
|
11141
13641
|
const [r, g, b] = hexToRgb(HELMET_OUTLINE);
|
|
11142
|
-
return
|
|
13642
|
+
return trueColor3(r, g, b)(line);
|
|
11143
13643
|
}
|
|
11144
13644
|
function hasCliSubcommand(rawArgs) {
|
|
11145
13645
|
return Boolean(rawArgs?.some((arg) => !arg.startsWith("-")));
|