@dadado/agent-kit-cli 4.8.0 → 4.8.3
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/dashboard/dashboard-data.mjs +813 -0
- package/dashboard/dashboard.html +6955 -0
- package/dashboard/lib/guards.mjs +935 -0
- package/dashboard/lib/live-refresh.mjs +147 -0
- package/dashboard/lib/semantic-model.d.mts +34 -0
- package/dashboard/lib/semantic-model.mjs +3596 -0
- package/dashboard/lib/triage-heading.mjs +20 -0
- package/dashboard/logo-cursor.svg +10 -0
- package/dashboard/logo.svg +1 -0
- package/dashboard/serve.mjs +550 -0
- package/dashboard/start-broadcast.mjs +231 -0
- package/dashboard/start.mjs +286 -0
- package/dist/index.js +1436 -171
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { defineCommand as
|
|
4
|
+
import { defineCommand as defineCommand18, runMain } from "citty";
|
|
5
5
|
|
|
6
6
|
// src/commands/add.ts
|
|
7
7
|
import { defineCommand } from "citty";
|
|
@@ -1093,18 +1093,34 @@ var L0_ARTIFACTS = [
|
|
|
1093
1093
|
source: ".cursor/hooks/pre-commit/check-secrets.sh",
|
|
1094
1094
|
target: ".cursor/hooks/pre-commit/check-secrets.sh"
|
|
1095
1095
|
},
|
|
1096
|
-
// Native Cursor agent hooks (
|
|
1096
|
+
// Native Cursor agent hooks (thin adapters -> agent-kit CLI)
|
|
1097
1097
|
{
|
|
1098
1098
|
source: ".cursor/hooks.json",
|
|
1099
1099
|
target: ".cursor/hooks.json"
|
|
1100
1100
|
},
|
|
1101
1101
|
{
|
|
1102
|
-
source: ".cursor/hooks/agent/
|
|
1103
|
-
target: ".cursor/hooks/agent/
|
|
1102
|
+
source: ".cursor/hooks/agent/resolve-agent-kit.sh",
|
|
1103
|
+
target: ".cursor/hooks/agent/resolve-agent-kit.sh"
|
|
1104
1104
|
},
|
|
1105
1105
|
{
|
|
1106
|
-
source: ".cursor/hooks/agent/
|
|
1107
|
-
target: ".cursor/hooks/agent/
|
|
1106
|
+
source: ".cursor/hooks/agent/session-start.sh",
|
|
1107
|
+
target: ".cursor/hooks/agent/session-start.sh"
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
source: ".cursor/hooks/agent/pre-compact.sh",
|
|
1111
|
+
target: ".cursor/hooks/agent/pre-compact.sh"
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
source: ".cursor/hooks/agent/guard-shell.sh",
|
|
1115
|
+
target: ".cursor/hooks/agent/guard-shell.sh"
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
source: ".cursor/hooks/agent/after-edit-schema.sh",
|
|
1119
|
+
target: ".cursor/hooks/agent/after-edit-schema.sh"
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
source: ".cursor/hooks/agent/secrets-prompt.sh",
|
|
1123
|
+
target: ".cursor/hooks/agent/secrets-prompt.sh"
|
|
1108
1124
|
},
|
|
1109
1125
|
// Git spine docs at project root (not a nested agent-kit/ copy)
|
|
1110
1126
|
{
|
|
@@ -1397,11 +1413,33 @@ import path10 from "path";
|
|
|
1397
1413
|
import { defineCommand as defineCommand4 } from "citty";
|
|
1398
1414
|
|
|
1399
1415
|
// src/commands/dashboard.ts
|
|
1400
|
-
import { spawn } from "child_process";
|
|
1416
|
+
import { execFileSync, spawn } from "child_process";
|
|
1401
1417
|
import { access as access2 } from "fs/promises";
|
|
1402
1418
|
import path9 from "path";
|
|
1419
|
+
import { fileURLToPath } from "url";
|
|
1403
1420
|
import { defineCommand as defineCommand3 } from "citty";
|
|
1404
|
-
|
|
1421
|
+
function bundledDashboardCandidates(filename, moduleUrl = import.meta.url) {
|
|
1422
|
+
const here = path9.dirname(fileURLToPath(moduleUrl));
|
|
1423
|
+
return [
|
|
1424
|
+
// Published / built: <pkg>/dist/index.js -> <pkg>/dashboard/
|
|
1425
|
+
path9.join(here, "..", "dashboard", filename),
|
|
1426
|
+
// Dev source: packages/cli/src/commands/*.ts -> packages/cli/dashboard/ (sync copy)
|
|
1427
|
+
path9.join(here, "..", "..", "dashboard", filename),
|
|
1428
|
+
// From src/commands: packages/dashboard/ (unused layout); from dist: two levels above package
|
|
1429
|
+
path9.join(here, "..", "..", "..", "dashboard", filename)
|
|
1430
|
+
];
|
|
1431
|
+
}
|
|
1432
|
+
async function firstExisting(candidates) {
|
|
1433
|
+
for (const candidate2 of candidates) {
|
|
1434
|
+
try {
|
|
1435
|
+
await access2(candidate2);
|
|
1436
|
+
return path9.resolve(candidate2);
|
|
1437
|
+
} catch {
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return null;
|
|
1441
|
+
}
|
|
1442
|
+
async function findDashboardStart(cwd, env = process.env, options = {}) {
|
|
1405
1443
|
let dir = path9.resolve(cwd);
|
|
1406
1444
|
for (; ; ) {
|
|
1407
1445
|
const candidate2 = path9.join(dir, "dashboard", "start.mjs");
|
|
@@ -1411,9 +1449,40 @@ async function findDashboardStart(cwd) {
|
|
|
1411
1449
|
} catch {
|
|
1412
1450
|
}
|
|
1413
1451
|
const parent = path9.dirname(dir);
|
|
1414
|
-
if (parent === dir)
|
|
1452
|
+
if (parent === dir) break;
|
|
1415
1453
|
dir = parent;
|
|
1416
1454
|
}
|
|
1455
|
+
for (const key of ["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]) {
|
|
1456
|
+
const base = env[key];
|
|
1457
|
+
if (!base || typeof base !== "string" || !base.trim()) continue;
|
|
1458
|
+
const candidate2 = path9.join(path9.resolve(base.trim()), "dashboard", "start.mjs");
|
|
1459
|
+
try {
|
|
1460
|
+
await access2(candidate2);
|
|
1461
|
+
return candidate2;
|
|
1462
|
+
} catch {
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
const sibling = path9.join(path9.resolve(cwd), "..", "agent-kit", "dashboard", "start.mjs");
|
|
1466
|
+
try {
|
|
1467
|
+
await access2(sibling);
|
|
1468
|
+
return path9.resolve(sibling);
|
|
1469
|
+
} catch {
|
|
1470
|
+
}
|
|
1471
|
+
return firstExisting(
|
|
1472
|
+
bundledDashboardCandidates("start.mjs", options.moduleUrl ?? import.meta.url)
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
function resolveDashboardSnapshotRoot(cwd) {
|
|
1476
|
+
const abs = path9.resolve(cwd);
|
|
1477
|
+
try {
|
|
1478
|
+
const top = execFileSync("git", ["-C", abs, "rev-parse", "--show-toplevel"], {
|
|
1479
|
+
encoding: "utf8",
|
|
1480
|
+
timeout: 5e3
|
|
1481
|
+
}).trim();
|
|
1482
|
+
if (top) return path9.resolve(top);
|
|
1483
|
+
} catch {
|
|
1484
|
+
}
|
|
1485
|
+
return abs;
|
|
1417
1486
|
}
|
|
1418
1487
|
function runStartScript(startPath, env) {
|
|
1419
1488
|
return new Promise((resolve, reject) => {
|
|
@@ -1429,13 +1498,13 @@ function runStartScript(startPath, env) {
|
|
|
1429
1498
|
var dashboardCommand = defineCommand3({
|
|
1430
1499
|
meta: {
|
|
1431
1500
|
name: "dashboard",
|
|
1432
|
-
description: "Start Mission Control
|
|
1501
|
+
description: "Start Mission Control for this workspace (stable per-root port) and open the panel URL."
|
|
1433
1502
|
},
|
|
1434
1503
|
args: {
|
|
1435
1504
|
cwd: {
|
|
1436
1505
|
type: "string",
|
|
1437
1506
|
default: process.cwd(),
|
|
1438
|
-
description: "
|
|
1507
|
+
description: "Workspace to snapshot (git root preferred); also searched upward for dashboard/start.mjs"
|
|
1439
1508
|
},
|
|
1440
1509
|
"no-open": {
|
|
1441
1510
|
type: "boolean",
|
|
@@ -1444,15 +1513,17 @@ var dashboardCommand = defineCommand3({
|
|
|
1444
1513
|
}
|
|
1445
1514
|
},
|
|
1446
1515
|
async run({ args }) {
|
|
1516
|
+
const snapshotRoot = resolveDashboardSnapshotRoot(args.cwd);
|
|
1447
1517
|
const startPath = await findDashboardStart(args.cwd);
|
|
1448
1518
|
if (!startPath) {
|
|
1449
1519
|
logger.error(
|
|
1450
|
-
"No dashboard/start.mjs found.
|
|
1520
|
+
"No dashboard/start.mjs found. After a CLI publish that ships dashboard/ (Path C), reinstall @dadado/agent-kit-cli. Or set MISSION_CONTROL_KIT_ROOT / AGENT_KIT_HOME to an agent-kit checkout, place a sibling ../agent-kit tree, or run from that kit tree."
|
|
1451
1521
|
);
|
|
1452
1522
|
process.exitCode = 1;
|
|
1453
1523
|
return;
|
|
1454
1524
|
}
|
|
1455
1525
|
const env = { ...process.env };
|
|
1526
|
+
env.MISSION_CONTROL_REPO_ROOT = snapshotRoot;
|
|
1456
1527
|
if (args["no-open"]) env.MISSION_CONTROL_NO_OPEN = "1";
|
|
1457
1528
|
const code = await runStartScript(startPath, env);
|
|
1458
1529
|
if (code !== 0) process.exitCode = code;
|
|
@@ -1460,7 +1531,7 @@ var dashboardCommand = defineCommand3({
|
|
|
1460
1531
|
});
|
|
1461
1532
|
|
|
1462
1533
|
// src/commands/dashboard-broadcast.ts
|
|
1463
|
-
async function findDashboardBroadcastStart(cwd) {
|
|
1534
|
+
async function findDashboardBroadcastStart(cwd, env = process.env, options = {}) {
|
|
1464
1535
|
let dir = path10.resolve(cwd);
|
|
1465
1536
|
for (; ; ) {
|
|
1466
1537
|
const candidate2 = path10.join(dir, "dashboard", "start-broadcast.mjs");
|
|
@@ -1470,9 +1541,42 @@ async function findDashboardBroadcastStart(cwd) {
|
|
|
1470
1541
|
} catch {
|
|
1471
1542
|
}
|
|
1472
1543
|
const parent = path10.dirname(dir);
|
|
1473
|
-
if (parent === dir)
|
|
1544
|
+
if (parent === dir) break;
|
|
1474
1545
|
dir = parent;
|
|
1475
1546
|
}
|
|
1547
|
+
for (const key of ["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]) {
|
|
1548
|
+
const base = env[key];
|
|
1549
|
+
if (!base || typeof base !== "string" || !base.trim()) continue;
|
|
1550
|
+
const candidate2 = path10.join(path10.resolve(base.trim()), "dashboard", "start-broadcast.mjs");
|
|
1551
|
+
try {
|
|
1552
|
+
await access3(candidate2);
|
|
1553
|
+
return candidate2;
|
|
1554
|
+
} catch {
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
const sibling = path10.join(
|
|
1558
|
+
path10.resolve(cwd),
|
|
1559
|
+
"..",
|
|
1560
|
+
"agent-kit",
|
|
1561
|
+
"dashboard",
|
|
1562
|
+
"start-broadcast.mjs"
|
|
1563
|
+
);
|
|
1564
|
+
try {
|
|
1565
|
+
await access3(sibling);
|
|
1566
|
+
return path10.resolve(sibling);
|
|
1567
|
+
} catch {
|
|
1568
|
+
}
|
|
1569
|
+
for (const candidate2 of bundledDashboardCandidates(
|
|
1570
|
+
"start-broadcast.mjs",
|
|
1571
|
+
options.moduleUrl ?? import.meta.url
|
|
1572
|
+
)) {
|
|
1573
|
+
try {
|
|
1574
|
+
await access3(candidate2);
|
|
1575
|
+
return path10.resolve(candidate2);
|
|
1576
|
+
} catch {
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
return null;
|
|
1476
1580
|
}
|
|
1477
1581
|
function runStartScript2(startPath, env) {
|
|
1478
1582
|
return new Promise((resolve, reject) => {
|
|
@@ -1507,7 +1611,7 @@ var dashboardBroadcastCommand = defineCommand4({
|
|
|
1507
1611
|
if (!startPath) {
|
|
1508
1612
|
const loopback = await findDashboardStart(args.cwd);
|
|
1509
1613
|
logger.error(
|
|
1510
|
-
loopback ? "No dashboard/start-broadcast.mjs found beside dashboard/start.mjs. Update the agent-kit tree
|
|
1614
|
+
loopback ? "No dashboard/start-broadcast.mjs found beside dashboard/start.mjs. Update the agent-kit tree or reinstall a CLI that ships dashboard/, then retry." : "No dashboard/start-broadcast.mjs found. After a CLI publish that ships dashboard/ (Path C), reinstall @dadado/agent-kit-cli. Or run from an agent-kit tree that includes dashboard/."
|
|
1511
1615
|
);
|
|
1512
1616
|
process.exitCode = 1;
|
|
1513
1617
|
return;
|
|
@@ -1659,9 +1763,152 @@ var diffCommand = defineCommand5({
|
|
|
1659
1763
|
});
|
|
1660
1764
|
|
|
1661
1765
|
// src/commands/doctor.ts
|
|
1662
|
-
import
|
|
1766
|
+
import path22 from "path";
|
|
1663
1767
|
import { defineCommand as defineCommand6 } from "citty";
|
|
1664
1768
|
|
|
1769
|
+
// src/invariants/hooks-health.ts
|
|
1770
|
+
import { execFile as execFile2 } from "child_process";
|
|
1771
|
+
import { constants as constants2, access as access4, readFile as readFile6, stat } from "fs/promises";
|
|
1772
|
+
import path12 from "path";
|
|
1773
|
+
import { promisify as promisify2 } from "util";
|
|
1774
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
1775
|
+
var EXPECTED_EVENTS = [
|
|
1776
|
+
"sessionStart",
|
|
1777
|
+
"preCompact",
|
|
1778
|
+
"beforeShellExecution",
|
|
1779
|
+
"afterFileEdit",
|
|
1780
|
+
"beforeSubmitPrompt"
|
|
1781
|
+
];
|
|
1782
|
+
async function exists(p) {
|
|
1783
|
+
try {
|
|
1784
|
+
await access4(p);
|
|
1785
|
+
return true;
|
|
1786
|
+
} catch {
|
|
1787
|
+
return false;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
async function isExecutable(p) {
|
|
1791
|
+
try {
|
|
1792
|
+
await access4(p, constants2.X_OK);
|
|
1793
|
+
return true;
|
|
1794
|
+
} catch {
|
|
1795
|
+
return false;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
async function resolveAgentKitCli(rootDir) {
|
|
1799
|
+
const root = path12.resolve(rootDir);
|
|
1800
|
+
const candidates = [
|
|
1801
|
+
path12.join(root, "node_modules", ".bin", "agent-kit"),
|
|
1802
|
+
path12.join(root, "packages", "cli", "dist", "index.js")
|
|
1803
|
+
];
|
|
1804
|
+
for (const c of candidates) {
|
|
1805
|
+
if (await exists(c)) return c;
|
|
1806
|
+
}
|
|
1807
|
+
try {
|
|
1808
|
+
const { stdout } = await execFileAsync2("which", ["agent-kit"], { encoding: "utf8" });
|
|
1809
|
+
const hit = stdout.trim().split("\n")[0]?.trim();
|
|
1810
|
+
if (hit) return hit;
|
|
1811
|
+
} catch {
|
|
1812
|
+
}
|
|
1813
|
+
return null;
|
|
1814
|
+
}
|
|
1815
|
+
function commandLooksLikeAdapter(command) {
|
|
1816
|
+
const trimmed = command.trim();
|
|
1817
|
+
if (!trimmed) return null;
|
|
1818
|
+
const m = trimmed.match(/(\.cursor\/hooks\/agent\/[A-Za-z0-9._-]+\.sh)\b/);
|
|
1819
|
+
return m?.[1] ?? null;
|
|
1820
|
+
}
|
|
1821
|
+
async function assessHooksHealth(rootDir) {
|
|
1822
|
+
const root = path12.resolve(rootDir);
|
|
1823
|
+
const hooksJsonPath = ".cursor/hooks.json";
|
|
1824
|
+
const hooksJsonAbs = path12.join(root, hooksJsonPath);
|
|
1825
|
+
const reasons = [];
|
|
1826
|
+
const wiredEvents = [];
|
|
1827
|
+
if (!await exists(hooksJsonAbs)) {
|
|
1828
|
+
return {
|
|
1829
|
+
status: "missing",
|
|
1830
|
+
reasons: ["`.cursor/hooks.json` not found"],
|
|
1831
|
+
hooksJsonPath,
|
|
1832
|
+
expectedEvents: [...EXPECTED_EVENTS],
|
|
1833
|
+
wiredEvents
|
|
1834
|
+
};
|
|
1835
|
+
}
|
|
1836
|
+
let parsed;
|
|
1837
|
+
try {
|
|
1838
|
+
parsed = JSON.parse(await readFile6(hooksJsonAbs, "utf8"));
|
|
1839
|
+
} catch {
|
|
1840
|
+
return {
|
|
1841
|
+
status: "degraded",
|
|
1842
|
+
reasons: ["`.cursor/hooks.json` is not valid JSON"],
|
|
1843
|
+
hooksJsonPath,
|
|
1844
|
+
expectedEvents: [...EXPECTED_EVENTS],
|
|
1845
|
+
wiredEvents
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
const hooks = parsed.hooks ?? {};
|
|
1849
|
+
const adapterRels = /* @__PURE__ */ new Set();
|
|
1850
|
+
for (const event of EXPECTED_EVENTS) {
|
|
1851
|
+
const list = hooks[event];
|
|
1852
|
+
if (Array.isArray(list) && list.length > 0) {
|
|
1853
|
+
wiredEvents.push(event);
|
|
1854
|
+
for (const entry of list) {
|
|
1855
|
+
if (!entry || typeof entry !== "object") continue;
|
|
1856
|
+
const command = String(entry.command ?? "");
|
|
1857
|
+
if (command.endsWith(".py") || command.includes("python")) {
|
|
1858
|
+
reasons.push(`${event} still points at a Python script (${command})`);
|
|
1859
|
+
}
|
|
1860
|
+
const rel = commandLooksLikeAdapter(command);
|
|
1861
|
+
if (rel) adapterRels.add(rel);
|
|
1862
|
+
}
|
|
1863
|
+
} else {
|
|
1864
|
+
reasons.push(`missing hook event: ${event}`);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
const resolveLib = path12.join(root, ".cursor", "hooks", "agent", "resolve-agent-kit.sh");
|
|
1868
|
+
if (!await exists(resolveLib)) {
|
|
1869
|
+
reasons.push("missing `.cursor/hooks/agent/resolve-agent-kit.sh` (thin adapter resolver)");
|
|
1870
|
+
} else if (!await isExecutable(resolveLib)) {
|
|
1871
|
+
reasons.push("`.cursor/hooks/agent/resolve-agent-kit.sh` is not executable (chmod +x)");
|
|
1872
|
+
}
|
|
1873
|
+
for (const rel of adapterRels) {
|
|
1874
|
+
const abs = path12.join(root, rel);
|
|
1875
|
+
if (!await exists(abs)) {
|
|
1876
|
+
reasons.push(`missing adapter script: \`${rel}\``);
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
try {
|
|
1880
|
+
const st = await stat(abs);
|
|
1881
|
+
if (!st.isFile()) {
|
|
1882
|
+
reasons.push(`adapter path is not a file: \`${rel}\``);
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
} catch {
|
|
1886
|
+
reasons.push(`unreadable adapter script: \`${rel}\``);
|
|
1887
|
+
continue;
|
|
1888
|
+
}
|
|
1889
|
+
if (!await isExecutable(abs)) {
|
|
1890
|
+
reasons.push(`adapter not executable: \`${rel}\` (chmod +x)`);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
const cli = await resolveAgentKitCli(root);
|
|
1894
|
+
if (!cli) {
|
|
1895
|
+
reasons.push(
|
|
1896
|
+
"agent-kit CLI not resolvable (PATH, node_modules/.bin/agent-kit, or packages/cli/dist)"
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
if (Array.isArray(hooks.stop) && hooks.stop.length > 0) {
|
|
1900
|
+
reasons.push("`stop` hook is registered (forbidden; remove it)");
|
|
1901
|
+
}
|
|
1902
|
+
const status = reasons.length === 0 && wiredEvents.length === EXPECTED_EVENTS.length ? "active" : "degraded";
|
|
1903
|
+
return {
|
|
1904
|
+
status,
|
|
1905
|
+
reasons,
|
|
1906
|
+
hooksJsonPath,
|
|
1907
|
+
expectedEvents: [...EXPECTED_EVENTS],
|
|
1908
|
+
wiredEvents
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1665
1912
|
// src/scanner/readiness.ts
|
|
1666
1913
|
import { createHash as createHash2 } from "crypto";
|
|
1667
1914
|
function action(id, status, recommendation, owner) {
|
|
@@ -1909,12 +2156,12 @@ function createReadinessReport(scan, options) {
|
|
|
1909
2156
|
}
|
|
1910
2157
|
|
|
1911
2158
|
// src/scanner/safe-fixes.ts
|
|
1912
|
-
import { readFile as
|
|
1913
|
-
import
|
|
2159
|
+
import { readFile as readFile9, writeFile as writeFile3 } from "fs/promises";
|
|
2160
|
+
import path20 from "path";
|
|
1914
2161
|
|
|
1915
2162
|
// src/scanner/detect-repository.ts
|
|
1916
|
-
import { readFile as
|
|
1917
|
-
import
|
|
2163
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
2164
|
+
import path13 from "path";
|
|
1918
2165
|
var CONTEXT_PATHS = [
|
|
1919
2166
|
["README.md", "README"],
|
|
1920
2167
|
["README", "README"],
|
|
@@ -1933,7 +2180,7 @@ var CONTEXT_PATHS = [
|
|
|
1933
2180
|
async function existingEvidence(rootDir, candidates) {
|
|
1934
2181
|
const evidence = await Promise.all(
|
|
1935
2182
|
candidates.map(
|
|
1936
|
-
async ([relativePath, label]) => await fileExists(
|
|
2183
|
+
async ([relativePath, label]) => await fileExists(path13.join(rootDir, relativePath)) ? { source: "file", value: `${relativePath}:${label}` } : void 0
|
|
1937
2184
|
)
|
|
1938
2185
|
);
|
|
1939
2186
|
return evidence.flatMap((item) => item ? [item] : []);
|
|
@@ -1954,7 +2201,7 @@ async function detectContext(rootDir) {
|
|
|
1954
2201
|
async function detectPurpose(rootDir, stack) {
|
|
1955
2202
|
const entries = await listDirectory(rootDir);
|
|
1956
2203
|
const lowerEntries = entries.map((entry) => entry.toLowerCase());
|
|
1957
|
-
const packageJson = await readJson(
|
|
2204
|
+
const packageJson = await readJson(path13.join(rootDir, "package.json"));
|
|
1958
2205
|
const categories = [];
|
|
1959
2206
|
const evidence = [];
|
|
1960
2207
|
const add = (category, value2) => {
|
|
@@ -1994,16 +2241,16 @@ async function detectPurpose(rootDir, stack) {
|
|
|
1994
2241
|
}
|
|
1995
2242
|
async function detectAgentKit(rootDir) {
|
|
1996
2243
|
const manifestRelativePath = ".cursor/agent-kit.json";
|
|
1997
|
-
const manifestPath =
|
|
2244
|
+
const manifestPath = path13.join(rootDir, manifestRelativePath);
|
|
1998
2245
|
const installed = await fileExists(manifestPath);
|
|
1999
2246
|
const manifest = installed ? await readJson(manifestPath) : null;
|
|
2000
2247
|
return {
|
|
2001
2248
|
installed,
|
|
2002
2249
|
manifestPath: installed ? manifestRelativePath : void 0,
|
|
2003
2250
|
version: manifest?.version,
|
|
2004
|
-
hasPlans: await fileExists(
|
|
2005
|
-
hasHandoff: await fileExists(
|
|
2006
|
-
hasMemory: await fileExists(
|
|
2251
|
+
hasPlans: await fileExists(path13.join(rootDir, ".cursor/plans")),
|
|
2252
|
+
hasHandoff: await fileExists(path13.join(rootDir, ".cursor/HANDOFF.md")),
|
|
2253
|
+
hasMemory: await fileExists(path13.join(rootDir, ".cursor/memory"))
|
|
2007
2254
|
};
|
|
2008
2255
|
}
|
|
2009
2256
|
var REQUIRED_SECRET_PATTERNS = [
|
|
@@ -2017,9 +2264,9 @@ var REQUIRED_SECRET_PATTERNS = [
|
|
|
2017
2264
|
"*service-account*.json"
|
|
2018
2265
|
];
|
|
2019
2266
|
async function detectSafety(rootDir, trackedFiles) {
|
|
2020
|
-
const gitignorePath =
|
|
2267
|
+
const gitignorePath = path13.join(rootDir, ".gitignore");
|
|
2021
2268
|
const hasGitignore = await fileExists(gitignorePath);
|
|
2022
|
-
const gitignore = hasGitignore ? await
|
|
2269
|
+
const gitignore = hasGitignore ? await readFile7(gitignorePath, "utf8") : "";
|
|
2023
2270
|
const lines = gitignore.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
2024
2271
|
const ignoredSecretPatterns = REQUIRED_SECRET_PATTERNS.filter(
|
|
2025
2272
|
(pattern) => lines.includes(pattern)
|
|
@@ -2028,7 +2275,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
2028
2275
|
(file) => /(^|\/)(\.env(\..+)?|.*\.(key|pem|p12|pfx)|.*credentials.*\.json)$/i.test(file)
|
|
2029
2276
|
);
|
|
2030
2277
|
const hookPaths = [".husky", ".git/hooks/pre-commit", "git-hooks/pre-commit"];
|
|
2031
|
-
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(
|
|
2278
|
+
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(path13.join(rootDir, item))))).some(Boolean);
|
|
2032
2279
|
const guardCandidates = [
|
|
2033
2280
|
".husky/pre-commit",
|
|
2034
2281
|
".husky/pre-push",
|
|
@@ -2037,7 +2284,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
2037
2284
|
];
|
|
2038
2285
|
const guardContents = await Promise.all(
|
|
2039
2286
|
guardCandidates.map(
|
|
2040
|
-
async (item) => await fileExists(
|
|
2287
|
+
async (item) => await fileExists(path13.join(rootDir, item)) ? readFile7(path13.join(rootDir, item), "utf8") : ""
|
|
2041
2288
|
)
|
|
2042
2289
|
);
|
|
2043
2290
|
return {
|
|
@@ -2055,13 +2302,13 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
2055
2302
|
}
|
|
2056
2303
|
|
|
2057
2304
|
// src/scanner/scan.ts
|
|
2058
|
-
import
|
|
2305
|
+
import path19 from "path";
|
|
2059
2306
|
|
|
2060
2307
|
// src/scanner/detect-git.ts
|
|
2061
|
-
import { execFile as
|
|
2062
|
-
import
|
|
2063
|
-
import { promisify as
|
|
2064
|
-
var exec =
|
|
2308
|
+
import { execFile as execFile3 } from "child_process";
|
|
2309
|
+
import path14 from "path";
|
|
2310
|
+
import { promisify as promisify3 } from "util";
|
|
2311
|
+
var exec = promisify3(execFile3);
|
|
2065
2312
|
function remoteHostname(remoteUrl) {
|
|
2066
2313
|
const scpMatch = remoteUrl.match(/^[^@]+@([^:]+):/);
|
|
2067
2314
|
if (scpMatch?.[1]) return scpMatch[1].toLowerCase();
|
|
@@ -2083,7 +2330,7 @@ function sanitizeRemoteUrl(remoteUrl) {
|
|
|
2083
2330
|
}
|
|
2084
2331
|
async function detectProvider(rootDir, remoteUrl) {
|
|
2085
2332
|
const configuration = await readJson(
|
|
2086
|
-
|
|
2333
|
+
path14.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
2087
2334
|
);
|
|
2088
2335
|
const configuredProvider = configuration?.git?.provider;
|
|
2089
2336
|
if (configuredProvider) {
|
|
@@ -2140,7 +2387,7 @@ async function detectProvider(rootDir, remoteUrl) {
|
|
|
2140
2387
|
evidence: remoteEvidence
|
|
2141
2388
|
};
|
|
2142
2389
|
}
|
|
2143
|
-
if (await fileExists(
|
|
2390
|
+
if (await fileExists(path14.join(rootDir, ".gitlab-ci.yml"))) {
|
|
2144
2391
|
return {
|
|
2145
2392
|
provider: "gitlab",
|
|
2146
2393
|
providerKind: "gitlab-self-hosted",
|
|
@@ -2229,11 +2476,11 @@ async function detectGit(rootDir) {
|
|
|
2229
2476
|
}
|
|
2230
2477
|
|
|
2231
2478
|
// src/scanner/detect-ide.ts
|
|
2232
|
-
import
|
|
2479
|
+
import path15 from "path";
|
|
2233
2480
|
async function detectIde(rootDir) {
|
|
2234
|
-
const hasCursor = await fileExists(
|
|
2235
|
-
const hasVSCode = await fileExists(
|
|
2236
|
-
const hasWindsurf = await fileExists(
|
|
2481
|
+
const hasCursor = await fileExists(path15.join(rootDir, ".cursor"));
|
|
2482
|
+
const hasVSCode = await fileExists(path15.join(rootDir, ".vscode"));
|
|
2483
|
+
const hasWindsurf = await fileExists(path15.join(rootDir, ".windsurfrules"));
|
|
2237
2484
|
if (hasCursor) return { ide: "cursor", plan: "cursor-pro" };
|
|
2238
2485
|
if (hasVSCode) return { ide: "vscode", plan: "vscode-pro" };
|
|
2239
2486
|
if (hasWindsurf) return { ide: "windsurf", plan: "windsurf" };
|
|
@@ -2241,7 +2488,7 @@ async function detectIde(rootDir) {
|
|
|
2241
2488
|
}
|
|
2242
2489
|
|
|
2243
2490
|
// src/scanner/detect-infra.ts
|
|
2244
|
-
import
|
|
2491
|
+
import path16 from "path";
|
|
2245
2492
|
|
|
2246
2493
|
// src/types.ts
|
|
2247
2494
|
var CI_PLATFORM_FILES = {
|
|
@@ -2270,12 +2517,12 @@ var PM_TOOL_LABELS = {
|
|
|
2270
2517
|
|
|
2271
2518
|
// src/scanner/detect-infra.ts
|
|
2272
2519
|
async function detectInfra(rootDir) {
|
|
2273
|
-
const docker = await fileExists(
|
|
2274
|
-
const kubernetes = await fileExists(
|
|
2520
|
+
const docker = await fileExists(path16.join(rootDir, "Dockerfile")) || await fileExists(path16.join(rootDir, "docker-compose.yml")) || await fileExists(path16.join(rootDir, "docker-compose.yaml"));
|
|
2521
|
+
const kubernetes = await fileExists(path16.join(rootDir, "k8s")) || await fileExists(path16.join(rootDir, "kubernetes"));
|
|
2275
2522
|
let ci = "none";
|
|
2276
2523
|
const ciFiles = [];
|
|
2277
2524
|
for (const [platform, filePath] of Object.entries(CI_PLATFORM_FILES)) {
|
|
2278
|
-
if (await fileExists(
|
|
2525
|
+
if (await fileExists(path16.join(rootDir, filePath))) {
|
|
2279
2526
|
if (ci === "none") ci = platform;
|
|
2280
2527
|
ciFiles.push(filePath);
|
|
2281
2528
|
}
|
|
@@ -2300,30 +2547,30 @@ async function detectInfra(rootDir) {
|
|
|
2300
2547
|
];
|
|
2301
2548
|
const infrastructureFiles = (await Promise.all(
|
|
2302
2549
|
infrastructureCandidates.map(
|
|
2303
|
-
async (file) => await fileExists(
|
|
2550
|
+
async (file) => await fileExists(path16.join(rootDir, file)) ? file : void 0
|
|
2304
2551
|
)
|
|
2305
2552
|
)).filter((file) => file !== void 0);
|
|
2306
2553
|
const deploymentFiles = (await Promise.all(
|
|
2307
2554
|
deploymentCandidates.map(
|
|
2308
|
-
async (file) => await fileExists(
|
|
2555
|
+
async (file) => await fileExists(path16.join(rootDir, file)) ? file : void 0
|
|
2309
2556
|
)
|
|
2310
2557
|
)).filter((file) => file !== void 0);
|
|
2311
2558
|
return { docker, kubernetes, ci, ciFiles, infrastructureFiles, deploymentFiles };
|
|
2312
2559
|
}
|
|
2313
2560
|
|
|
2314
2561
|
// src/scanner/detect-services.ts
|
|
2315
|
-
import { readFile as
|
|
2316
|
-
import
|
|
2562
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2563
|
+
import path17 from "path";
|
|
2317
2564
|
async function detectProjectManagement(rootDir) {
|
|
2318
2565
|
const tools = [];
|
|
2319
2566
|
const mcpConfigPaths = [
|
|
2320
|
-
|
|
2321
|
-
|
|
2567
|
+
path17.join(rootDir, ".cursor", "mcp.json"),
|
|
2568
|
+
path17.join(rootDir, "mcp.json")
|
|
2322
2569
|
];
|
|
2323
2570
|
for (const configPath of mcpConfigPaths) {
|
|
2324
2571
|
if (!await fileExists(configPath)) continue;
|
|
2325
2572
|
try {
|
|
2326
|
-
const raw = await
|
|
2573
|
+
const raw = await readFile8(configPath, "utf8");
|
|
2327
2574
|
const lower = raw.toLowerCase();
|
|
2328
2575
|
if (lower.includes("clickup")) tools.push("clickup");
|
|
2329
2576
|
if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
|
|
@@ -2334,20 +2581,20 @@ async function detectProjectManagement(rootDir) {
|
|
|
2334
2581
|
} catch {
|
|
2335
2582
|
}
|
|
2336
2583
|
}
|
|
2337
|
-
if (await fileExists(
|
|
2584
|
+
if (await fileExists(path17.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
|
|
2338
2585
|
tools.push("github-issues");
|
|
2339
2586
|
}
|
|
2340
|
-
if (await fileExists(
|
|
2587
|
+
if (await fileExists(path17.join(rootDir, ".github", "projects"))) {
|
|
2341
2588
|
tools.push("github-projects");
|
|
2342
2589
|
}
|
|
2343
2590
|
return [...new Set(tools)];
|
|
2344
2591
|
}
|
|
2345
2592
|
async function detectServices(rootDir) {
|
|
2346
|
-
const hasPrisma = await fileExists(
|
|
2347
|
-
const hasSequelize = await fileExists(
|
|
2348
|
-
const hasDrizzle = await fileExists(
|
|
2349
|
-
const hasKnex = await fileExists(
|
|
2350
|
-
const hasTypeorm = await fileExists(
|
|
2593
|
+
const hasPrisma = await fileExists(path17.join(rootDir, "prisma/schema.prisma"));
|
|
2594
|
+
const hasSequelize = await fileExists(path17.join(rootDir, "sequelize"));
|
|
2595
|
+
const hasDrizzle = await fileExists(path17.join(rootDir, "drizzle.config.ts"));
|
|
2596
|
+
const hasKnex = await fileExists(path17.join(rootDir, "knexfile.ts"));
|
|
2597
|
+
const hasTypeorm = await fileExists(path17.join(rootDir, "ormconfig.json"));
|
|
2351
2598
|
const database = hasPrisma || hasSequelize || hasDrizzle || hasKnex || hasTypeorm ? "postgresql" : void 0;
|
|
2352
2599
|
const orm = hasPrisma ? "prisma" : hasDrizzle ? "drizzle" : hasSequelize ? "sequelize" : hasKnex ? "knex" : hasTypeorm ? "typeorm" : void 0;
|
|
2353
2600
|
const projectManagement = await detectProjectManagement(rootDir);
|
|
@@ -2359,7 +2606,7 @@ async function detectServices(rootDir) {
|
|
|
2359
2606
|
}
|
|
2360
2607
|
|
|
2361
2608
|
// src/scanner/detect-stack.ts
|
|
2362
|
-
import
|
|
2609
|
+
import path18 from "path";
|
|
2363
2610
|
var PROJECT_MARKERS = [
|
|
2364
2611
|
"package.json",
|
|
2365
2612
|
"requirements.txt",
|
|
@@ -2388,7 +2635,7 @@ async function detectPackageManager(rootDir, packageJson) {
|
|
|
2388
2635
|
};
|
|
2389
2636
|
}
|
|
2390
2637
|
for (const [lockfile, packageManager] of LOCKFILES) {
|
|
2391
|
-
if (await fileExists(
|
|
2638
|
+
if (await fileExists(path18.join(rootDir, lockfile))) {
|
|
2392
2639
|
return {
|
|
2393
2640
|
packageManager,
|
|
2394
2641
|
evidence: [{ source: "file", value: lockfile }]
|
|
@@ -2405,27 +2652,27 @@ function commandsForScripts(scripts, packageManager) {
|
|
|
2405
2652
|
return { testCommands, validationCommands };
|
|
2406
2653
|
}
|
|
2407
2654
|
async function detectStack(rootDir) {
|
|
2408
|
-
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(
|
|
2409
|
-
const hasPackageJson = await fileExists(
|
|
2655
|
+
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(path18.join(rootDir, item))))).some(Boolean);
|
|
2656
|
+
const hasPackageJson = await fileExists(path18.join(rootDir, "package.json"));
|
|
2410
2657
|
if (hasPackageJson) {
|
|
2411
|
-
const packageJson = await readJson(
|
|
2658
|
+
const packageJson = await readJson(path18.join(rootDir, "package.json")) ?? {};
|
|
2412
2659
|
const scripts = packageJson.scripts ?? {};
|
|
2413
2660
|
const packageManager = await detectPackageManager(rootDir, packageJson);
|
|
2414
2661
|
const commands = commandsForScripts(scripts, packageManager.packageManager);
|
|
2415
|
-
const hasNextConfig = await fileExists(
|
|
2416
|
-
const hasNestConfig = await fileExists(
|
|
2662
|
+
const hasNextConfig = await fileExists(path18.join(rootDir, "next.config.js")) || await fileExists(path18.join(rootDir, "next.config.mjs")) || await fileExists(path18.join(rootDir, "next.config.ts"));
|
|
2663
|
+
const hasNestConfig = await fileExists(path18.join(rootDir, "nest-cli.json"));
|
|
2417
2664
|
return {
|
|
2418
2665
|
language: "node",
|
|
2419
2666
|
framework: hasNextConfig ? "nextjs" : hasNestConfig ? "nestjs" : "node",
|
|
2420
2667
|
packageManager: packageManager.packageManager,
|
|
2421
2668
|
packageManagerEvidence: packageManager.evidence,
|
|
2422
2669
|
scripts,
|
|
2423
|
-
workspaces: packageJson.workspaces !== void 0 || await fileExists(
|
|
2670
|
+
workspaces: packageJson.workspaces !== void 0 || await fileExists(path18.join(rootDir, "pnpm-workspace.yaml")),
|
|
2424
2671
|
...commands,
|
|
2425
2672
|
hasProjectFiles: hasAnyProjectMarker
|
|
2426
2673
|
};
|
|
2427
2674
|
}
|
|
2428
|
-
if (await fileExists(
|
|
2675
|
+
if (await fileExists(path18.join(rootDir, "pyproject.toml"))) {
|
|
2429
2676
|
return {
|
|
2430
2677
|
language: "python",
|
|
2431
2678
|
framework: "python",
|
|
@@ -2435,7 +2682,7 @@ async function detectStack(rootDir) {
|
|
|
2435
2682
|
hasProjectFiles: hasAnyProjectMarker
|
|
2436
2683
|
};
|
|
2437
2684
|
}
|
|
2438
|
-
if (await fileExists(
|
|
2685
|
+
if (await fileExists(path18.join(rootDir, "go.mod"))) {
|
|
2439
2686
|
return {
|
|
2440
2687
|
language: "go",
|
|
2441
2688
|
framework: "go",
|
|
@@ -2445,7 +2692,7 @@ async function detectStack(rootDir) {
|
|
|
2445
2692
|
hasProjectFiles: hasAnyProjectMarker
|
|
2446
2693
|
};
|
|
2447
2694
|
}
|
|
2448
|
-
if (await fileExists(
|
|
2695
|
+
if (await fileExists(path18.join(rootDir, "Cargo.toml"))) {
|
|
2449
2696
|
return {
|
|
2450
2697
|
language: "rust",
|
|
2451
2698
|
framework: "rust",
|
|
@@ -2455,7 +2702,7 @@ async function detectStack(rootDir) {
|
|
|
2455
2702
|
hasProjectFiles: hasAnyProjectMarker
|
|
2456
2703
|
};
|
|
2457
2704
|
}
|
|
2458
|
-
if (await fileExists(
|
|
2705
|
+
if (await fileExists(path18.join(rootDir, "composer.json"))) {
|
|
2459
2706
|
return {
|
|
2460
2707
|
language: "php",
|
|
2461
2708
|
framework: "php",
|
|
@@ -2488,7 +2735,7 @@ function isGreenfieldByEntries(entries) {
|
|
|
2488
2735
|
return meaningful.length === 0;
|
|
2489
2736
|
}
|
|
2490
2737
|
async function runScanner(rootDir) {
|
|
2491
|
-
const normalizedRoot =
|
|
2738
|
+
const normalizedRoot = path19.resolve(rootDir);
|
|
2492
2739
|
const entries = await listDirectory(normalizedRoot);
|
|
2493
2740
|
const stack = await detectStack(normalizedRoot);
|
|
2494
2741
|
const purpose = await detectPurpose(normalizedRoot, stack);
|
|
@@ -2692,21 +2939,21 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2692
2939
|
});
|
|
2693
2940
|
const changes = [];
|
|
2694
2941
|
for (const relativePath of ESSENTIAL_DIRECTORIES) {
|
|
2695
|
-
const absolutePath =
|
|
2696
|
-
const
|
|
2697
|
-
if (!
|
|
2942
|
+
const absolutePath = path20.join(beforeScan.rootDir, relativePath);
|
|
2943
|
+
const exists2 = await fileExists(absolutePath);
|
|
2944
|
+
if (!exists2 && !dryRun) await ensureDir(absolutePath);
|
|
2698
2945
|
recordChange(
|
|
2699
2946
|
changes,
|
|
2700
2947
|
"ensure-agent-kit-directory",
|
|
2701
2948
|
relativePath,
|
|
2702
|
-
!
|
|
2949
|
+
!exists2,
|
|
2703
2950
|
dryRun,
|
|
2704
|
-
relativeEvidence(relativePath,
|
|
2951
|
+
relativeEvidence(relativePath, exists2 ? "already exists" : "missing directory")
|
|
2705
2952
|
);
|
|
2706
2953
|
}
|
|
2707
2954
|
const gitignoreRelativePath = ".gitignore";
|
|
2708
|
-
const gitignorePath =
|
|
2709
|
-
const existingGitignore = await fileExists(gitignorePath) ? await
|
|
2955
|
+
const gitignorePath = path20.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
2956
|
+
const existingGitignore = await fileExists(gitignorePath) ? await readFile9(gitignorePath, "utf8") : "";
|
|
2710
2957
|
const mergedGitignore = mergeSecretIgnores(existingGitignore);
|
|
2711
2958
|
const gitignoreChanged = mergedGitignore !== existingGitignore;
|
|
2712
2959
|
if (gitignoreChanged && !dryRun) await writeFile3(gitignorePath, mergedGitignore, "utf8");
|
|
@@ -2721,7 +2968,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2721
2968
|
gitignoreChanged ? "required secret patterns are missing" : "required patterns are present"
|
|
2722
2969
|
)
|
|
2723
2970
|
);
|
|
2724
|
-
const profilePath =
|
|
2971
|
+
const profilePath = path20.join(beforeScan.rootDir, PROFILE_RELATIVE_PATH);
|
|
2725
2972
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
2726
2973
|
const desiredProfile = createProfile(beforeScan, before, generatedAt);
|
|
2727
2974
|
const mergedProfile = mergeMissing(existingProfile, desiredProfile);
|
|
@@ -2743,7 +2990,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2743
2990
|
generatorVersion: options.generatorVersion,
|
|
2744
2991
|
generatedAt
|
|
2745
2992
|
});
|
|
2746
|
-
const contextConfigPath =
|
|
2993
|
+
const contextConfigPath = path20.join(beforeScan.rootDir, CONTEXT_CONFIG_RELATIVE_PATH);
|
|
2747
2994
|
const existingContextConfig = await readJson(contextConfigPath) ?? {};
|
|
2748
2995
|
const onboarding = reconcileOnboardingState(evidenceReport, existingContextConfig, generatedAt);
|
|
2749
2996
|
const defaults = preferenceDefaults(onboarding, existingContextConfig.onboarded);
|
|
@@ -2772,24 +3019,25 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2772
3019
|
}
|
|
2773
3020
|
|
|
2774
3021
|
// src/scanner/snapshot.ts
|
|
2775
|
-
import
|
|
3022
|
+
import path21 from "path";
|
|
2776
3023
|
var READINESS_SNAPSHOT_RELATIVE_PATH = ".cursor/context/readiness.json";
|
|
2777
3024
|
async function writeReadinessSnapshot(rootDir, report) {
|
|
2778
|
-
const snapshotPath =
|
|
3025
|
+
const snapshotPath = path21.join(rootDir, READINESS_SNAPSHOT_RELATIVE_PATH);
|
|
2779
3026
|
await writeJson(snapshotPath, report);
|
|
2780
3027
|
return snapshotPath;
|
|
2781
3028
|
}
|
|
2782
3029
|
|
|
2783
3030
|
// src/commands/doctor.ts
|
|
2784
3031
|
async function runDoctor(cwd, options = {}) {
|
|
2785
|
-
const rootDir =
|
|
3032
|
+
const rootDir = path22.resolve(cwd);
|
|
3033
|
+
const hooks = await assessHooksHealth(rootDir);
|
|
2786
3034
|
if (options.fixSafe) {
|
|
2787
3035
|
const execution = await executeSafeReadinessFixes(rootDir, {
|
|
2788
3036
|
generatorVersion: KIT_VERSION,
|
|
2789
3037
|
generatedAt: options.generatedAt
|
|
2790
3038
|
});
|
|
2791
3039
|
await writeReadinessSnapshot(rootDir, execution.after);
|
|
2792
|
-
return { report: execution.after, safeChanges: execution.changes };
|
|
3040
|
+
return { report: execution.after, safeChanges: execution.changes, hooks };
|
|
2793
3041
|
}
|
|
2794
3042
|
const scan = await runScanner(rootDir);
|
|
2795
3043
|
const report = createReadinessReport(scan, {
|
|
@@ -2797,7 +3045,7 @@ async function runDoctor(cwd, options = {}) {
|
|
|
2797
3045
|
generatedAt: options.generatedAt
|
|
2798
3046
|
});
|
|
2799
3047
|
await writeReadinessSnapshot(rootDir, report);
|
|
2800
|
-
return { report, safeChanges: [] };
|
|
3048
|
+
return { report, safeChanges: [], hooks };
|
|
2801
3049
|
}
|
|
2802
3050
|
function printDoctorSummary(result) {
|
|
2803
3051
|
const { summary, pendingActions } = result.report;
|
|
@@ -2809,6 +3057,12 @@ function printDoctorSummary(result) {
|
|
|
2809
3057
|
);
|
|
2810
3058
|
console.log(` safe fixes applied: ${fixed}`);
|
|
2811
3059
|
console.log(` pending actions: ${pendingActions.length}`);
|
|
3060
|
+
console.log(`hooks: ${result.hooks.status}`);
|
|
3061
|
+
if (result.hooks.reasons.length > 0) {
|
|
3062
|
+
for (const reason of result.hooks.reasons.slice(0, 5)) {
|
|
3063
|
+
console.log(` - ${reason}`);
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
2812
3066
|
console.log(
|
|
2813
3067
|
nextAction ? `Next: ${nextAction.recommendation}` : "Next: repository readiness checks are complete"
|
|
2814
3068
|
);
|
|
@@ -2844,11 +3098,293 @@ var doctorCommand = defineCommand6({
|
|
|
2844
3098
|
}
|
|
2845
3099
|
});
|
|
2846
3100
|
|
|
3101
|
+
// src/commands/guard.ts
|
|
3102
|
+
import { execFile as execFile4 } from "child_process";
|
|
3103
|
+
import { promisify as promisify4 } from "util";
|
|
3104
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
3105
|
+
|
|
3106
|
+
// src/hooks/read-stdin-json.ts
|
|
3107
|
+
async function readStdinJson() {
|
|
3108
|
+
const chunks = [];
|
|
3109
|
+
for await (const chunk of process.stdin) {
|
|
3110
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
3111
|
+
}
|
|
3112
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
3113
|
+
if (!raw) return {};
|
|
3114
|
+
try {
|
|
3115
|
+
return JSON.parse(raw);
|
|
3116
|
+
} catch {
|
|
3117
|
+
return {};
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
// src/invariants/secrets-scan.ts
|
|
3122
|
+
var CITE = "agent-kit guard prompt (docs/cursor-native-audit.md)";
|
|
3123
|
+
var SECRET_PATTERNS2 = [
|
|
3124
|
+
{
|
|
3125
|
+
id: "json-secret-kv",
|
|
3126
|
+
re: /"(password|apiKey|api_key|secret|token|auth)"\s*:\s*"[^"]{12,}"/i
|
|
3127
|
+
},
|
|
3128
|
+
{
|
|
3129
|
+
id: "env-assignment",
|
|
3130
|
+
re: /\b(?:API_KEY|SECRET|PASSWORD|TOKEN|ACCESS_KEY|PRIVATE_KEY)\s*=\s*['"]?[^\s'"]{12,}/i
|
|
3131
|
+
},
|
|
3132
|
+
{
|
|
3133
|
+
id: "aws-access-key",
|
|
3134
|
+
re: /\bAKIA[0-9A-Z]{16}\b/
|
|
3135
|
+
},
|
|
3136
|
+
{
|
|
3137
|
+
id: "github-pat",
|
|
3138
|
+
re: /\bghp_[A-Za-z0-9_]{36,}\b/
|
|
3139
|
+
},
|
|
3140
|
+
{
|
|
3141
|
+
id: "openai-sk",
|
|
3142
|
+
re: /\bsk-[A-Za-z0-9]{20,}\b/
|
|
3143
|
+
}
|
|
3144
|
+
];
|
|
3145
|
+
function maskSecretExcerpt(raw) {
|
|
3146
|
+
return raw.replace(/\b(ghp_|sk-|AKIA)([A-Za-z0-9_]{4,})/g, (_m, p1, p2) => {
|
|
3147
|
+
return `${p1}${"*".repeat(Math.min(8, p2.length))}`;
|
|
3148
|
+
}).replace(
|
|
3149
|
+
/(=\s*['"]?)([^\s'"]{4,})/g,
|
|
3150
|
+
(_m, p1, p2) => `${p1}${"*".repeat(Math.min(8, p2.length))}`
|
|
3151
|
+
).replace(
|
|
3152
|
+
/("(?:password|apiKey|api_key|secret|token|auth)"\s*:\s*")([^"]{4,})(")/gi,
|
|
3153
|
+
(_m, p1, p2, p3) => `${p1}${"*".repeat(Math.min(8, p2.length))}${p3}`
|
|
3154
|
+
);
|
|
3155
|
+
}
|
|
3156
|
+
function excerptAround(text, index, len) {
|
|
3157
|
+
const start = Math.max(0, index - 8);
|
|
3158
|
+
const end = Math.min(text.length, index + len + 8);
|
|
3159
|
+
return maskSecretExcerpt(text.slice(start, end).replace(/\s+/g, " "));
|
|
3160
|
+
}
|
|
3161
|
+
function scanTextForSecrets(text) {
|
|
3162
|
+
if (!text) return [];
|
|
3163
|
+
const hits = [];
|
|
3164
|
+
for (const { id, re } of SECRET_PATTERNS2) {
|
|
3165
|
+
const flags = re.flags.includes("g") ? re.flags : `${re.flags}g`;
|
|
3166
|
+
const global = new RegExp(re.source, flags);
|
|
3167
|
+
let match = global.exec(text);
|
|
3168
|
+
while (match) {
|
|
3169
|
+
hits.push({
|
|
3170
|
+
patternId: id,
|
|
3171
|
+
excerpt: excerptAround(text, match.index, match[0].length)
|
|
3172
|
+
});
|
|
3173
|
+
if (!global.global) break;
|
|
3174
|
+
match = global.exec(text);
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
return hits;
|
|
3178
|
+
}
|
|
3179
|
+
function secretsAdviseMessage(hits) {
|
|
3180
|
+
const ids = [...new Set(hits.map((h) => h.patternId))].join(", ");
|
|
3181
|
+
return `Possible secret pattern(s) in prompt (${ids}). Cite: ${CITE}. Remove live credentials before submitting; use env vars or a secrets store.`;
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
// src/invariants/shell-guard.ts
|
|
3185
|
+
var CITE2 = "agent-kit guard shell (ADR 2026-07-29_cli-invariants-thin-hook-adapters)";
|
|
3186
|
+
var PROTECTED_BRANCH_RE = /^(?:main|master|prod)$/;
|
|
3187
|
+
function normalizeShellCommand(command) {
|
|
3188
|
+
return command.replace(/\s+/g, " ").trim();
|
|
3189
|
+
}
|
|
3190
|
+
function shellInvocationHeads(command) {
|
|
3191
|
+
const normalized = normalizeShellCommand(command);
|
|
3192
|
+
if (!normalized) return [];
|
|
3193
|
+
return normalized.split(/(?:&&|\|\||[;|])/).map((part) => part.trim().replace(/^(?:\w+=\S+\s+)*/, "")).filter(Boolean);
|
|
3194
|
+
}
|
|
3195
|
+
function anyHeadMatches(command, re) {
|
|
3196
|
+
return shellInvocationHeads(command).some((head) => re.test(head));
|
|
3197
|
+
}
|
|
3198
|
+
function isProtectedBranch(name) {
|
|
3199
|
+
return typeof name === "string" && PROTECTED_BRANCH_RE.test(name.trim());
|
|
3200
|
+
}
|
|
3201
|
+
function normalizePushRefspecToken(token) {
|
|
3202
|
+
let t = token.trim();
|
|
3203
|
+
if (t.startsWith("'") && t.endsWith("'") && t.length >= 2 || t.startsWith('"') && t.endsWith('"') && t.length >= 2) {
|
|
3204
|
+
t = t.slice(1, -1).trim();
|
|
3205
|
+
}
|
|
3206
|
+
if (t.startsWith("+")) t = t.slice(1);
|
|
3207
|
+
if (t.startsWith("refs/heads/")) t = t.slice("refs/heads/".length);
|
|
3208
|
+
if (t.startsWith("origin/")) t = t.slice("origin/".length);
|
|
3209
|
+
return t;
|
|
3210
|
+
}
|
|
3211
|
+
function pushHeadHasProtectedDest(head) {
|
|
3212
|
+
if (/HEAD:(?:refs\/heads\/)?(?:main|master|prod)\b/.test(head)) return true;
|
|
3213
|
+
if (/(?:^|\s)-(?:u|--set-upstream)\s+\S+\s+(?:main|master|prod)(?:\s|$)/.test(head)) {
|
|
3214
|
+
return true;
|
|
3215
|
+
}
|
|
3216
|
+
const after = head.replace(/^(?:[\w./-]+\/)?git\s+push\b/, "");
|
|
3217
|
+
for (const raw of after.split(/\s+/).filter(Boolean)) {
|
|
3218
|
+
if (raw.startsWith("-")) continue;
|
|
3219
|
+
const dest = raw.includes(":") ? raw.slice(raw.lastIndexOf(":") + 1) : raw;
|
|
3220
|
+
if (PROTECTED_BRANCH_RE.test(normalizePushRefspecToken(dest))) return true;
|
|
3221
|
+
}
|
|
3222
|
+
return false;
|
|
3223
|
+
}
|
|
3224
|
+
function isBareOrHeadPushToCurrent(head) {
|
|
3225
|
+
if (!/^(?:[\w./-]+\/)?git\s+push\b/.test(head)) return false;
|
|
3226
|
+
if (pushHeadHasProtectedDest(head)) {
|
|
3227
|
+
return false;
|
|
3228
|
+
}
|
|
3229
|
+
if (/(?:^|\s)\+?(?:refs\/heads\/)?(?:origin\/)?(?:staging|develop|homologacao)(?:\s|$|:)/.test(
|
|
3230
|
+
head
|
|
3231
|
+
) || /HEAD:(?:refs\/heads\/)?(?!main|master|prod)[A-Za-z0-9._/-]+/.test(head)) {
|
|
3232
|
+
return false;
|
|
3233
|
+
}
|
|
3234
|
+
const after = head.replace(/^(?:[\w./-]+\/)?git\s+push\b/, "").trim();
|
|
3235
|
+
const withoutFlags = after.replace(/(?:^|\s)(?:--force|-f|-u|--set-upstream|--tags|--all|--prune)(?=\s|$)/g, " ").replace(/(?:^|\s)--\w[\w-]*(?:=\S+)?/g, " ").replace(/\s+/g, " ").trim();
|
|
3236
|
+
if (!withoutFlags) return true;
|
|
3237
|
+
const tokens = withoutFlags.split(/\s+/);
|
|
3238
|
+
if (tokens.length === 1) return true;
|
|
3239
|
+
if (tokens.length >= 2 && tokens[1] === "HEAD") return true;
|
|
3240
|
+
if (/\bHEAD\b/.test(withoutFlags) && !/HEAD:/.test(withoutFlags)) return true;
|
|
3241
|
+
return false;
|
|
3242
|
+
}
|
|
3243
|
+
var SHELL_DENY_RULES = [
|
|
3244
|
+
{
|
|
3245
|
+
id: "git-checkout-path",
|
|
3246
|
+
description: "git checkout -- / HEAD -- / . discards working-tree edits",
|
|
3247
|
+
test: (cmd) => shellInvocationHeads(cmd).some((head) => {
|
|
3248
|
+
if (!/^(?:[\w./-]+\/)?git\s+checkout\b/.test(head)) return false;
|
|
3249
|
+
if (/\s--(?:\s|$)/.test(head)) return true;
|
|
3250
|
+
if (/\scheckout\s+\.(?:\s|$)/.test(head)) return true;
|
|
3251
|
+
return false;
|
|
3252
|
+
})
|
|
3253
|
+
},
|
|
3254
|
+
{
|
|
3255
|
+
id: "git-restore",
|
|
3256
|
+
description: "git restore discards working-tree edits",
|
|
3257
|
+
test: (cmd) => anyHeadMatches(cmd, /^(?:[\w./-]+\/)?git\s+restore\b/)
|
|
3258
|
+
},
|
|
3259
|
+
{
|
|
3260
|
+
id: "git-reset-hard",
|
|
3261
|
+
description: "git reset --hard destroys uncommitted work",
|
|
3262
|
+
test: (cmd) => anyHeadMatches(cmd, /^(?:[\w./-]+\/)?git\s+reset\b.*--hard\b/)
|
|
3263
|
+
},
|
|
3264
|
+
{
|
|
3265
|
+
id: "git-clean-fd",
|
|
3266
|
+
description: "git clean -fd removes untracked files",
|
|
3267
|
+
test: (cmd) => shellInvocationHeads(cmd).some(
|
|
3268
|
+
(head) => /^(?:[\w./-]+\/)?git\s+clean\b/.test(head) && /(?:^|\s)-(?:[a-z]*f[a-z]*d|[a-z]*d[a-z]*f)(?:\s|$)/.test(head)
|
|
3269
|
+
)
|
|
3270
|
+
},
|
|
3271
|
+
{
|
|
3272
|
+
id: "git-push-main",
|
|
3273
|
+
description: "direct push to main/master/prod bypasses staging",
|
|
3274
|
+
test: (cmd, opts) => shellInvocationHeads(cmd).some((head) => {
|
|
3275
|
+
if (!/^(?:[\w./-]+\/)?git\s+push\b/.test(head)) return false;
|
|
3276
|
+
if (pushHeadHasProtectedDest(head)) {
|
|
3277
|
+
return true;
|
|
3278
|
+
}
|
|
3279
|
+
if (isProtectedBranch(opts?.currentBranch) && isBareOrHeadPushToCurrent(head)) {
|
|
3280
|
+
return true;
|
|
3281
|
+
}
|
|
3282
|
+
return false;
|
|
3283
|
+
})
|
|
3284
|
+
}
|
|
3285
|
+
];
|
|
3286
|
+
function evaluateShellCommand(command, opts = {}) {
|
|
3287
|
+
const normalized = normalizeShellCommand(command);
|
|
3288
|
+
if (!normalized) {
|
|
3289
|
+
return { permission: "allow" };
|
|
3290
|
+
}
|
|
3291
|
+
for (const rule of SHELL_DENY_RULES) {
|
|
3292
|
+
if (rule.test(normalized, opts)) {
|
|
3293
|
+
const agent_message = `Denied by ${CITE2}: ${rule.description} (rule \`${rule.id}\`). Use /git-staging; never discard human hunks or push protected branches from the agent.`;
|
|
3294
|
+
return {
|
|
3295
|
+
permission: "deny",
|
|
3296
|
+
rule: rule.id,
|
|
3297
|
+
agent_message,
|
|
3298
|
+
user_message: agent_message
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
return { permission: "allow" };
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
// src/commands/guard.ts
|
|
3306
|
+
var execFileAsync3 = promisify4(execFile4);
|
|
3307
|
+
async function detectCurrentBranch() {
|
|
3308
|
+
try {
|
|
3309
|
+
const { stdout } = await execFileAsync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
3310
|
+
encoding: "utf8"
|
|
3311
|
+
});
|
|
3312
|
+
const branch = stdout.trim();
|
|
3313
|
+
return branch && branch !== "HEAD" ? branch : void 0;
|
|
3314
|
+
} catch {
|
|
3315
|
+
return void 0;
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
var guardCommand = defineCommand7({
|
|
3319
|
+
meta: {
|
|
3320
|
+
name: "guard",
|
|
3321
|
+
description: "Mechanizable deny/annotate guards (shell, prompt). Hooks are thin adapters."
|
|
3322
|
+
},
|
|
3323
|
+
subCommands: {
|
|
3324
|
+
shell: defineCommand7({
|
|
3325
|
+
meta: {
|
|
3326
|
+
name: "shell",
|
|
3327
|
+
description: "Evaluate a shell command against the destructive deny-list"
|
|
3328
|
+
},
|
|
3329
|
+
args: {
|
|
3330
|
+
json: {
|
|
3331
|
+
type: "boolean",
|
|
3332
|
+
default: true,
|
|
3333
|
+
description: "Print Cursor beforeShellExecution JSON (default)"
|
|
3334
|
+
},
|
|
3335
|
+
command: {
|
|
3336
|
+
type: "string",
|
|
3337
|
+
description: "Command string (otherwise read from stdin JSON.command)"
|
|
3338
|
+
}
|
|
3339
|
+
},
|
|
3340
|
+
async run({ args }) {
|
|
3341
|
+
let command = typeof args.command === "string" ? args.command : "";
|
|
3342
|
+
if (!command) {
|
|
3343
|
+
const payload = await readStdinJson();
|
|
3344
|
+
command = typeof payload.command === "string" ? payload.command : "";
|
|
3345
|
+
}
|
|
3346
|
+
const currentBranch = await detectCurrentBranch();
|
|
3347
|
+
const result = evaluateShellCommand(command, { currentBranch });
|
|
3348
|
+
console.log(JSON.stringify(result));
|
|
3349
|
+
}
|
|
3350
|
+
}),
|
|
3351
|
+
prompt: defineCommand7({
|
|
3352
|
+
meta: {
|
|
3353
|
+
name: "prompt",
|
|
3354
|
+
description: "Scan prompt text for secret patterns (advisory; fail-open at hook)"
|
|
3355
|
+
},
|
|
3356
|
+
args: {
|
|
3357
|
+
json: {
|
|
3358
|
+
type: "boolean",
|
|
3359
|
+
default: true
|
|
3360
|
+
}
|
|
3361
|
+
},
|
|
3362
|
+
async run() {
|
|
3363
|
+
const payload = await readStdinJson();
|
|
3364
|
+
const text = typeof payload.prompt === "string" && payload.prompt || typeof payload.text === "string" && payload.text || "";
|
|
3365
|
+
const hits = scanTextForSecrets(text);
|
|
3366
|
+
if (hits.length === 0) {
|
|
3367
|
+
console.log(JSON.stringify({ continue: true, hits: [] }));
|
|
3368
|
+
return;
|
|
3369
|
+
}
|
|
3370
|
+
console.log(
|
|
3371
|
+
JSON.stringify({
|
|
3372
|
+
continue: true,
|
|
3373
|
+
user_message: secretsAdviseMessage(hits),
|
|
3374
|
+
agent_message: secretsAdviseMessage(hits),
|
|
3375
|
+
hits: hits.map((h) => ({ patternId: h.patternId }))
|
|
3376
|
+
})
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
})
|
|
3380
|
+
}
|
|
3381
|
+
});
|
|
3382
|
+
|
|
2847
3383
|
// src/commands/handoff.ts
|
|
2848
3384
|
import { spawn as spawn3 } from "child_process";
|
|
2849
|
-
import { readFile as
|
|
2850
|
-
import
|
|
2851
|
-
import { defineCommand as
|
|
3385
|
+
import { readFile as readFile10, readdir as readdir2, writeFile as writeFile4 } from "fs/promises";
|
|
3386
|
+
import path23 from "path";
|
|
3387
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
2852
3388
|
function parsePlanFrontmatter(raw) {
|
|
2853
3389
|
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
2854
3390
|
if (!match?.[1]) return null;
|
|
@@ -2867,19 +3403,19 @@ async function findActivePlan(plansDir) {
|
|
|
2867
3403
|
if (!await fileExists(plansDir)) return null;
|
|
2868
3404
|
const files = (await readdir2(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
2869
3405
|
for (const file of files) {
|
|
2870
|
-
const raw = await
|
|
3406
|
+
const raw = await readFile10(path23.join(plansDir, file), "utf8");
|
|
2871
3407
|
const fm = parsePlanFrontmatter(raw);
|
|
2872
3408
|
if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
|
|
2873
3409
|
return { file, raw };
|
|
2874
3410
|
}
|
|
2875
3411
|
}
|
|
2876
|
-
return files[0] ? { file: files[0], raw: await
|
|
3412
|
+
return files[0] ? { file: files[0], raw: await readFile10(path23.join(plansDir, files[0]), "utf8") } : null;
|
|
2877
3413
|
}
|
|
2878
3414
|
function now() {
|
|
2879
3415
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
|
|
2880
3416
|
}
|
|
2881
3417
|
async function loadProfile(rootDir) {
|
|
2882
|
-
const configPath =
|
|
3418
|
+
const configPath = path23.join(rootDir, ".cursor", "agent-kit.config.json");
|
|
2883
3419
|
try {
|
|
2884
3420
|
return await readJson(configPath);
|
|
2885
3421
|
} catch {
|
|
@@ -2965,7 +3501,7 @@ function runCursorHandoff(scriptPath, cwd) {
|
|
|
2965
3501
|
child.on("close", (code) => resolve(code ?? 1));
|
|
2966
3502
|
});
|
|
2967
3503
|
}
|
|
2968
|
-
var handoffCommand =
|
|
3504
|
+
var handoffCommand = defineCommand8({
|
|
2969
3505
|
meta: {
|
|
2970
3506
|
name: "handoff",
|
|
2971
3507
|
description: "Write .cursor/HANDOFF.md from the active Cursor plan, or run ./cursor-handoff handoff when no plan exists."
|
|
@@ -2979,13 +3515,13 @@ var handoffCommand = defineCommand7({
|
|
|
2979
3515
|
},
|
|
2980
3516
|
async run({ args }) {
|
|
2981
3517
|
const profile = await loadProfile(args.cwd);
|
|
2982
|
-
const plansDir =
|
|
2983
|
-
const handoffPath =
|
|
3518
|
+
const plansDir = path23.join(args.cwd, ".cursor", "plans");
|
|
3519
|
+
const handoffPath = path23.join(args.cwd, ".cursor", "HANDOFF.md");
|
|
2984
3520
|
const plan = await findActivePlan(plansDir);
|
|
2985
3521
|
if (plan) {
|
|
2986
3522
|
const fm = parsePlanFrontmatter(plan.raw);
|
|
2987
3523
|
if (fm) {
|
|
2988
|
-
await ensureDir(
|
|
3524
|
+
await ensureDir(path23.join(args.cwd, ".cursor"));
|
|
2989
3525
|
const content = buildHandoff(plan.file, fm, profile);
|
|
2990
3526
|
await writeFile4(handoffPath, content, "utf8");
|
|
2991
3527
|
logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
|
|
@@ -3005,7 +3541,7 @@ var handoffCommand = defineCommand7({
|
|
|
3005
3541
|
}
|
|
3006
3542
|
logger.warn(`Plan ${plan.file} without valid frontmatter; trying legacy flow.`);
|
|
3007
3543
|
}
|
|
3008
|
-
const scriptPath =
|
|
3544
|
+
const scriptPath = path23.join(args.cwd, "cursor-handoff");
|
|
3009
3545
|
if (!await fileExists(scriptPath)) {
|
|
3010
3546
|
printV3Guidance();
|
|
3011
3547
|
return;
|
|
@@ -3029,17 +3565,388 @@ var handoffCommand = defineCommand7({
|
|
|
3029
3565
|
}
|
|
3030
3566
|
});
|
|
3031
3567
|
|
|
3568
|
+
// src/commands/hook.ts
|
|
3569
|
+
import path25 from "path";
|
|
3570
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
3571
|
+
|
|
3572
|
+
// src/hooks/pre-compact.ts
|
|
3573
|
+
function buildPreCompactUserMessage(payload = {}) {
|
|
3574
|
+
const pct = payload.context_usage_percent;
|
|
3575
|
+
const trigger = payload.trigger || "auto";
|
|
3576
|
+
const pctTxt = pct !== void 0 && pct !== null ? `~${pct}%` : "high";
|
|
3577
|
+
const msg = `Context compacting (${trigger}, usage ${pctTxt}). Update \`.cursor/HANDOFF.md\` and open a new chat with \`/continue-plan\` so the next agent starts fresh.`;
|
|
3578
|
+
return { user_message: msg };
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
// src/hooks/session-start.ts
|
|
3582
|
+
import { spawn as spawn4 } from "child_process";
|
|
3583
|
+
import { access as access5, readFile as readFile11 } from "fs/promises";
|
|
3584
|
+
import path24 from "path";
|
|
3585
|
+
|
|
3586
|
+
// src/invariants/handoff-schema.ts
|
|
3587
|
+
var MACHINE_LIST_CHECKS = [
|
|
3588
|
+
{
|
|
3589
|
+
heading: "Backlog plans",
|
|
3590
|
+
fieldLabels: ["Backlog plans", "Backlog"],
|
|
3591
|
+
code: "heading-without-field-backlog"
|
|
3592
|
+
},
|
|
3593
|
+
{
|
|
3594
|
+
heading: "Parked plans",
|
|
3595
|
+
fieldLabels: ["Parked plans"],
|
|
3596
|
+
code: "heading-without-field-parked"
|
|
3597
|
+
},
|
|
3598
|
+
{
|
|
3599
|
+
heading: "Run queue",
|
|
3600
|
+
fieldLabels: ["Run queue"],
|
|
3601
|
+
code: "heading-without-field-run-queue"
|
|
3602
|
+
}
|
|
3603
|
+
];
|
|
3604
|
+
var CITE3 = "agent-kit validate handoff (see .cursor/context/templates/handoff.md)";
|
|
3605
|
+
function validateHandoffText(text) {
|
|
3606
|
+
if (!text.trim()) return [];
|
|
3607
|
+
const warnings = [];
|
|
3608
|
+
for (const check2 of MACHINE_LIST_CHECKS) {
|
|
3609
|
+
const headingRe = new RegExp(`^##\\s+${check2.heading}\\s*$`, "m");
|
|
3610
|
+
if (!headingRe.test(text)) continue;
|
|
3611
|
+
const hasField = check2.fieldLabels.some(
|
|
3612
|
+
(label) => new RegExp(`^- \\*\\*${label}:\\*\\*`, "m").test(text)
|
|
3613
|
+
);
|
|
3614
|
+
if (!hasField) {
|
|
3615
|
+
warnings.push({
|
|
3616
|
+
code: check2.code,
|
|
3617
|
+
message: `\`## ${check2.heading}\` found without \`- **${check2.fieldLabels[0]}:**\` (Mission Control will miss this list; rewrite as a field bullet).`,
|
|
3618
|
+
cite: CITE3
|
|
3619
|
+
});
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
return warnings;
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
// src/hooks/hard-rules.ts
|
|
3626
|
+
var HARD_RULES = `# Agent Kit session hard rules (manual mode default)
|
|
3627
|
+
|
|
3628
|
+
1. **One phase per chat.** Finish the current phase (or one to-do if the phase is huge), update \`.cursor/HANDOFF.md\`, then STOP and ask the user before starting the next phase.
|
|
3629
|
+
2. **Do not burn the window.** Never run an entire multi-phase plan in one conversation unless the user explicitly ran \`/run-plan\` (or a deprecated alias \`/run-plan-loop\` / \`/run-plan-orchestrated\`).
|
|
3630
|
+
3. **Context questions are not optional.** If the user asks about context / contexto / window size, run the context-guardian protocol: warn, offer handoff, do NOT dismiss with "it's fine" and keep coding.
|
|
3631
|
+
4. **Read HANDOFF first** when resuming. Do not restart the plan from scratch.
|
|
3632
|
+
5. **Git:** suggest \`/git-staging\` after a phase with a diff; never \`/git-prod\` without explicit confirmation.
|
|
3633
|
+
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.
|
|
3634
|
+
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) \u2014 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/rules/hitl-ask-questions.mdc\`. Fallback: one numbered list per message.
|
|
3635
|
+
8. **\`/continue-plan\` waits for yes.** Summarize next \`[to-do-id]\`, then stop until the user confirms before editing.
|
|
3636
|
+
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.
|
|
3637
|
+
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.
|
|
3638
|
+
11. **HANDOFF machine fields are bullet fields, not \`##\` headings.** Mission Control parses \`- **Plan:**\`, \`- **Backlog plans:**\`, \`- **Parked plans:**\`, \`- **Run queue:**\` (etc.). Canonical Plan: \`- **Plan:** \\\`name.plan.md\\\`\` or \`none\`. Nested backlog/parked rows: \`- \\\`other.plan.md\\\`\`. Never invent \`## Backlog plans\` / \`## Parked plans\` / \`## Run queue\` headings in place of those fields (Checklist / Current mission go empty or idle).`;
|
|
3639
|
+
var DOGFOOD_INBOX_HINT = `## Dogfood inbox
|
|
3640
|
+
|
|
3641
|
+
Unprocessed files are listed under \`dogfood/README.md\` (### Unprocessed Files). Follow the ingest ritual there (detect \u2192 analyze \u2192 memory WRITE \u2192 triage). Do not auto-start analysis unless the user asks.`;
|
|
3642
|
+
var UPDATE_CHECK_NUDGE = `## Agent Kit update available
|
|
3643
|
+
|
|
3644
|
+
Installed **v{installed}**; latest public **v{latest}**.
|
|
3645
|
+
|
|
3646
|
+
This is an advisory only (no files were changed). To apply, run \`/update\` and confirm via Ask questions. Bare \`agent-kit update\` is an explicit operator invoke, not a background job.`;
|
|
3647
|
+
|
|
3648
|
+
// src/hooks/session-start.ts
|
|
3649
|
+
var NONE_PLACEHOLDERS = /* @__PURE__ */ new Set(["none", "n/a", "empty", "nil"]);
|
|
3650
|
+
async function readTextLimited(filePath, limit = 60) {
|
|
3651
|
+
try {
|
|
3652
|
+
const lines = (await readFile11(filePath, "utf8")).split(/\r?\n/);
|
|
3653
|
+
return lines.slice(0, limit).join("\n").trim();
|
|
3654
|
+
} catch {
|
|
3655
|
+
return "";
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
async function readFull(filePath) {
|
|
3659
|
+
try {
|
|
3660
|
+
return await readFile11(filePath, "utf8");
|
|
3661
|
+
} catch {
|
|
3662
|
+
return "";
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
async function fileExists2(p) {
|
|
3666
|
+
try {
|
|
3667
|
+
await access5(p);
|
|
3668
|
+
return true;
|
|
3669
|
+
} catch {
|
|
3670
|
+
return false;
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
function parseUnprocessedDogfoodItems(readmeText) {
|
|
3674
|
+
const items = [];
|
|
3675
|
+
let inSection = false;
|
|
3676
|
+
for (const line of readmeText.split(/\r?\n/)) {
|
|
3677
|
+
if (line.startsWith("### Unprocessed Files")) {
|
|
3678
|
+
inSection = true;
|
|
3679
|
+
continue;
|
|
3680
|
+
}
|
|
3681
|
+
if (!inSection) continue;
|
|
3682
|
+
if (line.startsWith("### ")) break;
|
|
3683
|
+
const stripped = line.trim();
|
|
3684
|
+
if (!stripped.startsWith("- ")) continue;
|
|
3685
|
+
const body = stripped.slice(2).trim();
|
|
3686
|
+
const normalized = body.toLowerCase().replace(/[*_]/g, "").trim();
|
|
3687
|
+
if (!normalized || NONE_PLACEHOLDERS.has(normalized)) continue;
|
|
3688
|
+
items.push(body);
|
|
3689
|
+
}
|
|
3690
|
+
return items;
|
|
3691
|
+
}
|
|
3692
|
+
async function l0Present(root) {
|
|
3693
|
+
const cursor = path24.join(root, ".cursor");
|
|
3694
|
+
return await fileExists2(path24.join(cursor, "agent-kit.json")) || await fileExists2(path24.join(cursor, "commands", "agent-kit-onboard.md")) || await fileExists2(path24.join(cursor, "commands", "start-project.md"));
|
|
3695
|
+
}
|
|
3696
|
+
function checkLabelAndRecommendation(check2) {
|
|
3697
|
+
const checkId = check2.id;
|
|
3698
|
+
if (typeof checkId !== "string" || !checkId) return null;
|
|
3699
|
+
const actions = check2.actions;
|
|
3700
|
+
if (Array.isArray(actions)) {
|
|
3701
|
+
for (const action2 of actions) {
|
|
3702
|
+
if (!action2 || typeof action2 !== "object") continue;
|
|
3703
|
+
const a = action2;
|
|
3704
|
+
if (typeof a.id === "string" && typeof a.recommendation === "string") {
|
|
3705
|
+
return [a.id, a.recommendation];
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
const title = check2.title;
|
|
3710
|
+
if (typeof title === "string" && title) return [checkId, title];
|
|
3711
|
+
return [checkId, "Resolve this readiness check"];
|
|
3712
|
+
}
|
|
3713
|
+
function unresolvedReadinessChecks(data) {
|
|
3714
|
+
const essential = [];
|
|
3715
|
+
const nonessential = [];
|
|
3716
|
+
const pillars = data.pillars;
|
|
3717
|
+
if (!Array.isArray(pillars)) return { essential, nonessential };
|
|
3718
|
+
for (const pillar2 of pillars) {
|
|
3719
|
+
if (!pillar2 || typeof pillar2 !== "object") continue;
|
|
3720
|
+
const checks = pillar2.checks;
|
|
3721
|
+
if (!Array.isArray(checks)) continue;
|
|
3722
|
+
for (const check2 of checks) {
|
|
3723
|
+
if (!check2 || typeof check2 !== "object") continue;
|
|
3724
|
+
const c = check2;
|
|
3725
|
+
if (c.status === "ready") continue;
|
|
3726
|
+
if (c.essential === true) essential.push(c);
|
|
3727
|
+
else nonessential.push(c);
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
return { essential, nonessential };
|
|
3731
|
+
}
|
|
3732
|
+
async function readinessSection(root) {
|
|
3733
|
+
const snapshotPath = path24.join(root, ".cursor", "context", "readiness.json");
|
|
3734
|
+
let data;
|
|
3735
|
+
try {
|
|
3736
|
+
data = JSON.parse(await readFile11(snapshotPath, "utf8"));
|
|
3737
|
+
} catch {
|
|
3738
|
+
return null;
|
|
3739
|
+
}
|
|
3740
|
+
const { essential, nonessential } = unresolvedReadinessChecks(data);
|
|
3741
|
+
if (essential[0]) {
|
|
3742
|
+
const labeled = checkLabelAndRecommendation(essential[0]);
|
|
3743
|
+
if (!labeled) return null;
|
|
3744
|
+
const [actionId, recommendation] = labeled;
|
|
3745
|
+
return `## Repository readiness
|
|
3746
|
+
|
|
3747
|
+
Unresolved essential check: \`${actionId}\`. ${recommendation} Run \`/agent-kit-onboard\` before \`/start-project\`. An active plan or HANDOFF remains the current work and is not replaced.`;
|
|
3748
|
+
}
|
|
3749
|
+
if (nonessential[0]) {
|
|
3750
|
+
const labeled = checkLabelAndRecommendation(nonessential[0]);
|
|
3751
|
+
if (!labeled) return null;
|
|
3752
|
+
const [actionId, recommendation] = labeled;
|
|
3753
|
+
return `## Repository readiness
|
|
3754
|
+
|
|
3755
|
+
Optional readiness item: \`${actionId}\`. ${recommendation} This does not block \`/start-project\` or active plan work. Resume later with \`/agent-kit-onboard\` if useful.`;
|
|
3756
|
+
}
|
|
3757
|
+
const actions = data.pendingActions;
|
|
3758
|
+
if (!Array.isArray(actions) || !actions[0] || typeof actions[0] !== "object") return null;
|
|
3759
|
+
const first = actions[0];
|
|
3760
|
+
if (typeof first.id !== "string" || typeof first.recommendation !== "string") return null;
|
|
3761
|
+
return `## Repository readiness
|
|
3762
|
+
|
|
3763
|
+
Optional readiness item: \`${first.id}\`. ${first.recommendation} This does not block \`/start-project\` or active plan work. Resume later with \`/agent-kit-onboard\` if useful.`;
|
|
3764
|
+
}
|
|
3765
|
+
async function dogfoodInboxSection(root) {
|
|
3766
|
+
const dogfoodDir = path24.join(root, "dogfood");
|
|
3767
|
+
if (!await fileExists2(dogfoodDir)) return null;
|
|
3768
|
+
const readme = path24.join(dogfoodDir, "README.md");
|
|
3769
|
+
if (!await fileExists2(readme)) return null;
|
|
3770
|
+
try {
|
|
3771
|
+
const text = await readFile11(readme, "utf8");
|
|
3772
|
+
if (!parseUnprocessedDogfoodItems(text).length) return null;
|
|
3773
|
+
return DOGFOOD_INBOX_HINT;
|
|
3774
|
+
} catch {
|
|
3775
|
+
return null;
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
async function loadUpdateCheckPrefs(root) {
|
|
3779
|
+
try {
|
|
3780
|
+
const data = JSON.parse(
|
|
3781
|
+
await readFile11(path24.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
3782
|
+
);
|
|
3783
|
+
const uc = data.updateCheck;
|
|
3784
|
+
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
3785
|
+
return null;
|
|
3786
|
+
}
|
|
3787
|
+
return uc;
|
|
3788
|
+
} catch {
|
|
3789
|
+
return null;
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
function runUpdateCheckJson(root) {
|
|
3793
|
+
return new Promise((resolve) => {
|
|
3794
|
+
const child = spawn4(
|
|
3795
|
+
process.execPath,
|
|
3796
|
+
[
|
|
3797
|
+
process.argv[1] ?? "",
|
|
3798
|
+
"update",
|
|
3799
|
+
"--check",
|
|
3800
|
+
"--json",
|
|
3801
|
+
"--respect-prefs",
|
|
3802
|
+
"--stamp",
|
|
3803
|
+
"--cwd",
|
|
3804
|
+
root
|
|
3805
|
+
],
|
|
3806
|
+
{ stdio: ["ignore", "pipe", "ignore"], timeout: 12e3 }
|
|
3807
|
+
);
|
|
3808
|
+
let out = "";
|
|
3809
|
+
child.stdout?.on("data", (chunk) => {
|
|
3810
|
+
out += chunk.toString("utf8");
|
|
3811
|
+
});
|
|
3812
|
+
child.on("error", () => resolve(null));
|
|
3813
|
+
child.on("close", () => {
|
|
3814
|
+
try {
|
|
3815
|
+
const parsed = JSON.parse(out.trim());
|
|
3816
|
+
resolve(parsed && typeof parsed === "object" ? parsed : null);
|
|
3817
|
+
} catch {
|
|
3818
|
+
resolve(null);
|
|
3819
|
+
}
|
|
3820
|
+
});
|
|
3821
|
+
});
|
|
3822
|
+
}
|
|
3823
|
+
async function updateCheckSection(root) {
|
|
3824
|
+
if (await loadUpdateCheckPrefs(root) === null) return null;
|
|
3825
|
+
const result = await new Promise((resolve) => {
|
|
3826
|
+
const child = spawn4(
|
|
3827
|
+
"agent-kit",
|
|
3828
|
+
["update", "--check", "--json", "--respect-prefs", "--stamp", "--cwd", root],
|
|
3829
|
+
{ stdio: ["ignore", "pipe", "ignore"], timeout: 12e3, shell: false }
|
|
3830
|
+
);
|
|
3831
|
+
let out = "";
|
|
3832
|
+
child.stdout?.on("data", (chunk) => {
|
|
3833
|
+
out += chunk.toString("utf8");
|
|
3834
|
+
});
|
|
3835
|
+
child.on("error", () => {
|
|
3836
|
+
void runUpdateCheckJson(root).then(resolve);
|
|
3837
|
+
});
|
|
3838
|
+
child.on("close", (code) => {
|
|
3839
|
+
if (code !== 0 && !out.trim()) {
|
|
3840
|
+
void runUpdateCheckJson(root).then(resolve);
|
|
3841
|
+
return;
|
|
3842
|
+
}
|
|
3843
|
+
try {
|
|
3844
|
+
resolve(JSON.parse(out.trim()));
|
|
3845
|
+
} catch {
|
|
3846
|
+
void runUpdateCheckJson(root).then(resolve);
|
|
3847
|
+
}
|
|
3848
|
+
});
|
|
3849
|
+
});
|
|
3850
|
+
if (!result || result.status !== "update-available") return null;
|
|
3851
|
+
if (result.applyRecommended === true) return null;
|
|
3852
|
+
const installed = String(result.installedVersion ?? "?");
|
|
3853
|
+
const latest = String(result.latestVersion ?? "?");
|
|
3854
|
+
return UPDATE_CHECK_NUDGE.replace("{installed}", installed).replace("{latest}", latest);
|
|
3855
|
+
}
|
|
3856
|
+
async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
3857
|
+
const root = path24.resolve(rootDir);
|
|
3858
|
+
const handoffPath = path24.join(root, ".cursor", "HANDOFF.md");
|
|
3859
|
+
const handoffFull = await readFull(handoffPath);
|
|
3860
|
+
const handoff = await readTextLimited(handoffPath);
|
|
3861
|
+
const parts = [HARD_RULES];
|
|
3862
|
+
if (await l0Present(root)) {
|
|
3863
|
+
const readiness = await readinessSection(root);
|
|
3864
|
+
if (readiness) parts.push(readiness);
|
|
3865
|
+
}
|
|
3866
|
+
const dogfood = await dogfoodInboxSection(root);
|
|
3867
|
+
if (dogfood) parts.push(dogfood);
|
|
3868
|
+
const updateNudge = await updateCheckSection(root);
|
|
3869
|
+
if (updateNudge) parts.push(updateNudge);
|
|
3870
|
+
const formatWarnings = validateHandoffText(handoffFull);
|
|
3871
|
+
if (formatWarnings.length) {
|
|
3872
|
+
const bullet = formatWarnings.map((w) => `- ${w.message}`).join("\n");
|
|
3873
|
+
parts.push(
|
|
3874
|
+
`## HANDOFF format warning (Mission Control)
|
|
3875
|
+
|
|
3876
|
+
${bullet}
|
|
3877
|
+
|
|
3878
|
+
Rewrite machine lists as \`- **Field:**\` bullets before trusting Checklist / Current mission.`
|
|
3879
|
+
);
|
|
3880
|
+
}
|
|
3881
|
+
if (handoff) {
|
|
3882
|
+
parts.push(`## Current HANDOFF.md (excerpt)
|
|
3883
|
+
|
|
3884
|
+
${handoff}`);
|
|
3885
|
+
} else {
|
|
3886
|
+
parts.push(
|
|
3887
|
+
"## HANDOFF.md\n\nNo handoff file yet. If starting work, create a plan with to-dos first (`/start-project`)."
|
|
3888
|
+
);
|
|
3889
|
+
}
|
|
3890
|
+
return { additional_context: parts.join("\n\n") };
|
|
3891
|
+
}
|
|
3892
|
+
function resolveSessionRoot(payload, cwd = process.cwd()) {
|
|
3893
|
+
const roots = payload.workspace_roots;
|
|
3894
|
+
if (Array.isArray(roots) && typeof roots[0] === "string" && roots[0]) {
|
|
3895
|
+
return roots[0];
|
|
3896
|
+
}
|
|
3897
|
+
return cwd;
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
// src/commands/hook.ts
|
|
3901
|
+
var hookCommand = defineCommand9({
|
|
3902
|
+
meta: {
|
|
3903
|
+
name: "hook",
|
|
3904
|
+
description: "Cursor hook adapters (session-start, pre-compact). CLI is SoT; thin hooks shell out here."
|
|
3905
|
+
},
|
|
3906
|
+
subCommands: {
|
|
3907
|
+
"session-start": defineCommand9({
|
|
3908
|
+
meta: {
|
|
3909
|
+
name: "session-start",
|
|
3910
|
+
description: "Emit sessionStart additional_context JSON (stdin: Cursor payload)"
|
|
3911
|
+
},
|
|
3912
|
+
args: {
|
|
3913
|
+
cwd: {
|
|
3914
|
+
type: "string",
|
|
3915
|
+
default: process.cwd()
|
|
3916
|
+
}
|
|
3917
|
+
},
|
|
3918
|
+
async run({ args }) {
|
|
3919
|
+
const payload = await readStdinJson();
|
|
3920
|
+
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
3921
|
+
const root = resolveSessionRoot(payload, path25.resolve(cwd));
|
|
3922
|
+
const out = await buildSessionStartAdditionalContext(root, payload);
|
|
3923
|
+
console.log(JSON.stringify(out));
|
|
3924
|
+
}
|
|
3925
|
+
}),
|
|
3926
|
+
"pre-compact": defineCommand9({
|
|
3927
|
+
meta: {
|
|
3928
|
+
name: "pre-compact",
|
|
3929
|
+
description: "Emit preCompact user_message JSON (stdin: Cursor payload)"
|
|
3930
|
+
},
|
|
3931
|
+
async run() {
|
|
3932
|
+
const payload = await readStdinJson();
|
|
3933
|
+
console.log(JSON.stringify(buildPreCompactUserMessage(payload)));
|
|
3934
|
+
}
|
|
3935
|
+
})
|
|
3936
|
+
}
|
|
3937
|
+
});
|
|
3938
|
+
|
|
3032
3939
|
// src/commands/init.ts
|
|
3033
3940
|
import { intro, outro } from "@clack/prompts";
|
|
3034
|
-
import { defineCommand as
|
|
3941
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
3035
3942
|
|
|
3036
3943
|
// src/commands/install.ts
|
|
3037
|
-
import
|
|
3038
|
-
import { defineCommand as
|
|
3944
|
+
import path28 from "path";
|
|
3945
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
3039
3946
|
|
|
3040
3947
|
// src/generator/personalization.ts
|
|
3041
|
-
import { readFile as
|
|
3042
|
-
import
|
|
3948
|
+
import { readFile as readFile12, writeFile as writeFile5 } from "fs/promises";
|
|
3949
|
+
import path26 from "path";
|
|
3043
3950
|
var PERSONALIZATION_CONTRACT_VERSION = 1;
|
|
3044
3951
|
var CONTEXT_PATH = ".cursor/project-context.md";
|
|
3045
3952
|
var AGENTS_PATH = "AGENTS.md";
|
|
@@ -3190,7 +4097,7 @@ function renderProjectContext(profile) {
|
|
|
3190
4097
|
`;
|
|
3191
4098
|
}
|
|
3192
4099
|
async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
3193
|
-
const target =
|
|
4100
|
+
const target = path26.join(rootDir, relativePath);
|
|
3194
4101
|
if (await fileExists(target)) {
|
|
3195
4102
|
return {
|
|
3196
4103
|
kind: "file",
|
|
@@ -3200,7 +4107,7 @@ async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
|
3200
4107
|
evidence
|
|
3201
4108
|
};
|
|
3202
4109
|
}
|
|
3203
|
-
await ensureDir(
|
|
4110
|
+
await ensureDir(path26.dirname(target));
|
|
3204
4111
|
await writeFile5(target, content, "utf8");
|
|
3205
4112
|
return {
|
|
3206
4113
|
kind: "file",
|
|
@@ -3217,7 +4124,7 @@ async function packTargets(registryRoot, packId) {
|
|
|
3217
4124
|
async function existingTargets(projectRoot, targets) {
|
|
3218
4125
|
const checks = await Promise.all(
|
|
3219
4126
|
targets.map(
|
|
3220
|
-
async (target) => await fileExists(
|
|
4127
|
+
async (target) => await fileExists(path26.join(projectRoot, target)) ? target : null
|
|
3221
4128
|
)
|
|
3222
4129
|
);
|
|
3223
4130
|
return checks.filter((target) => target !== null);
|
|
@@ -3239,14 +4146,14 @@ async function applyPersonalization(input) {
|
|
|
3239
4146
|
componentResults.push({ ...item, status: "unavailable" });
|
|
3240
4147
|
continue;
|
|
3241
4148
|
}
|
|
3242
|
-
const target =
|
|
4149
|
+
const target = path26.posix.join(
|
|
3243
4150
|
".cursor",
|
|
3244
4151
|
"skills",
|
|
3245
4152
|
skill.path.includes("/core/") ? "core" : "community",
|
|
3246
4153
|
skill.id,
|
|
3247
4154
|
"SKILL.md"
|
|
3248
4155
|
);
|
|
3249
|
-
if (await fileExists(
|
|
4156
|
+
if (await fileExists(path26.join(input.rootDir, target))) {
|
|
3250
4157
|
componentResults.push({ ...item, status: "skipped-customized", path: target });
|
|
3251
4158
|
protectedPaths.add(target);
|
|
3252
4159
|
continue;
|
|
@@ -3301,7 +4208,7 @@ async function applyPersonalization(input) {
|
|
|
3301
4208
|
items: [...fileResults, ...componentResults],
|
|
3302
4209
|
protectedPaths: [...protectedPaths].sort()
|
|
3303
4210
|
};
|
|
3304
|
-
await writeJson(
|
|
4211
|
+
await writeJson(path26.join(input.rootDir, RESULT_PATH), result);
|
|
3305
4212
|
return {
|
|
3306
4213
|
result,
|
|
3307
4214
|
manifest: {
|
|
@@ -3319,26 +4226,26 @@ async function applyPersonalization(input) {
|
|
|
3319
4226
|
};
|
|
3320
4227
|
}
|
|
3321
4228
|
async function readRepositoryProfile(rootDir) {
|
|
3322
|
-
const target =
|
|
4229
|
+
const target = path26.join(rootDir, ".cursor/agent-kit.config.json");
|
|
3323
4230
|
if (!await fileExists(target)) return null;
|
|
3324
|
-
return JSON.parse(await
|
|
4231
|
+
return JSON.parse(await readFile12(target, "utf8"));
|
|
3325
4232
|
}
|
|
3326
4233
|
|
|
3327
4234
|
// src/lifecycle/onboard-migration.ts
|
|
3328
4235
|
import { createHash as createHash3 } from "crypto";
|
|
3329
|
-
import { readFile as
|
|
3330
|
-
import
|
|
4236
|
+
import { readFile as readFile13, unlink } from "fs/promises";
|
|
4237
|
+
import path27 from "path";
|
|
3331
4238
|
var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
|
|
3332
4239
|
var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
|
|
3333
4240
|
var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
|
|
3334
4241
|
"b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
|
|
3335
4242
|
]);
|
|
3336
4243
|
async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
|
|
3337
|
-
const legacyPath =
|
|
4244
|
+
const legacyPath = path27.join(projectRoot, LEGACY_ONBOARD_PATH);
|
|
3338
4245
|
if (!await fileExists(legacyPath)) return "absent";
|
|
3339
|
-
const namespacedPath =
|
|
4246
|
+
const namespacedPath = path27.join(projectRoot, NAMESPACED_ONBOARD_PATH);
|
|
3340
4247
|
if (!await fileExists(namespacedPath)) return "preserved-customized";
|
|
3341
|
-
const content = await
|
|
4248
|
+
const content = await readFile13(legacyPath);
|
|
3342
4249
|
const hash = createHash3("sha256").update(content).digest("hex");
|
|
3343
4250
|
if (!managedHashes.has(hash)) return "preserved-customized";
|
|
3344
4251
|
await unlink(legacyPath);
|
|
@@ -3407,7 +4314,7 @@ function printReadinessNarrative(result) {
|
|
|
3407
4314
|
);
|
|
3408
4315
|
}
|
|
3409
4316
|
async function performInstall(options) {
|
|
3410
|
-
const projectRoot =
|
|
4317
|
+
const projectRoot = path28.resolve(options.cwd);
|
|
3411
4318
|
const packs = parsePackList(options.pack);
|
|
3412
4319
|
const existing = await loadAgentKitManifest(projectRoot);
|
|
3413
4320
|
const registry = await resolveRegistryFromCli({
|
|
@@ -3461,7 +4368,7 @@ async function performInstall(options) {
|
|
|
3461
4368
|
safeChanges: readinessExecution.changes
|
|
3462
4369
|
};
|
|
3463
4370
|
}
|
|
3464
|
-
var installCommand =
|
|
4371
|
+
var installCommand = defineCommand10({
|
|
3465
4372
|
meta: {
|
|
3466
4373
|
name: "install",
|
|
3467
4374
|
description: "Bootstrap L0 (+ optional packs) from the registry and write agent-kit.json."
|
|
@@ -3483,7 +4390,7 @@ var installCommand = defineCommand8({
|
|
|
3483
4390
|
...REGISTRY_CLI_ARGS
|
|
3484
4391
|
},
|
|
3485
4392
|
async run({ args }) {
|
|
3486
|
-
const projectRoot =
|
|
4393
|
+
const projectRoot = path28.resolve(args.cwd);
|
|
3487
4394
|
logger.info(`Installing into: ${projectRoot}`);
|
|
3488
4395
|
const packs = parsePackList(args.pack);
|
|
3489
4396
|
for (const id of packs) {
|
|
@@ -3511,7 +4418,7 @@ var installCommand = defineCommand8({
|
|
|
3511
4418
|
async function runInitCompatibility(cwd, installer = performInstall) {
|
|
3512
4419
|
return installer({ cwd });
|
|
3513
4420
|
}
|
|
3514
|
-
var initCommand =
|
|
4421
|
+
var initCommand = defineCommand11({
|
|
3515
4422
|
meta: {
|
|
3516
4423
|
name: "init",
|
|
3517
4424
|
description: "Guided compatibility entry point for install and repository readiness."
|
|
@@ -3535,16 +4442,190 @@ var initCommand = defineCommand9({
|
|
|
3535
4442
|
}
|
|
3536
4443
|
});
|
|
3537
4444
|
|
|
3538
|
-
// src/commands/
|
|
4445
|
+
// src/commands/monitors.ts
|
|
3539
4446
|
import path30 from "path";
|
|
3540
|
-
import { defineCommand as
|
|
4447
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
4448
|
+
|
|
4449
|
+
// src/invariants/monitors-untriaged.ts
|
|
4450
|
+
import { execFile as execFile5 } from "child_process";
|
|
4451
|
+
import { readFile as readFile14, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
4452
|
+
import path29 from "path";
|
|
4453
|
+
import { promisify as promisify5 } from "util";
|
|
4454
|
+
|
|
4455
|
+
// src/invariants/triage-heading.ts
|
|
4456
|
+
var TRIAGE_HEADING_RE = /^#{2,6}\s+(?:Triage note|Follow-?up plan|Residuals plan)\b/im;
|
|
4457
|
+
|
|
4458
|
+
// src/invariants/monitors-untriaged.ts
|
|
4459
|
+
var execFileAsync4 = promisify5(execFile5);
|
|
4460
|
+
var CITE4 = "agent-kit monitors --untriaged (ADR 2026-07-27_plan-review-triage-untriaged-not-mtime; never newest-mtime-wins)";
|
|
4461
|
+
function hasOpenGaps(content) {
|
|
4462
|
+
if (/###\s+Still open[^\n]*\n+(?:\s*\n)*(?:None\.|none\.|\*None\*)/i.test(content)) {
|
|
4463
|
+
return false;
|
|
4464
|
+
}
|
|
4465
|
+
if (/###\s+Still open/i.test(content) && !/###\s+Still open[^\n]*\n+(?:\s*\n)*(?:None\.|none\.)/i.test(content)) {
|
|
4466
|
+
const m = content.match(/###\s+Still open[^\n]*\n([\s\S]*?)(?=\n### |\n## |$)/i);
|
|
4467
|
+
if (m?.[1]?.trim() && !/^(none\.?|\*none\*)$/i.test(m[1].trim())) {
|
|
4468
|
+
return true;
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
if (/^#{2,6}\s+.*\bResidual items?\b/im.test(content)) return true;
|
|
4472
|
+
return false;
|
|
4473
|
+
}
|
|
4474
|
+
async function listMonitorFiles(memoryDir) {
|
|
4475
|
+
try {
|
|
4476
|
+
const names = await readdir3(memoryDir);
|
|
4477
|
+
return names.filter((n) => n.startsWith("plan-monitor-") && n.endsWith(".md")).sort();
|
|
4478
|
+
} catch {
|
|
4479
|
+
return [];
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
async function gitFreshMonitorNames(rootDir) {
|
|
4483
|
+
const names = /* @__PURE__ */ new Set();
|
|
4484
|
+
try {
|
|
4485
|
+
const { stdout } = await execFileAsync4(
|
|
4486
|
+
"git",
|
|
4487
|
+
["status", "--porcelain", "--", ".cursor/memory"],
|
|
4488
|
+
{ cwd: rootDir, maxBuffer: 2 * 1024 * 1024 }
|
|
4489
|
+
);
|
|
4490
|
+
for (const line of stdout.split("\n")) {
|
|
4491
|
+
if (!line.trim()) continue;
|
|
4492
|
+
const file = line.slice(3).trim().replace(/^.* -> /, "");
|
|
4493
|
+
const base = path29.basename(file);
|
|
4494
|
+
if (base.startsWith("plan-monitor-") && base.endsWith(".md")) {
|
|
4495
|
+
names.add(base);
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
} catch {
|
|
4499
|
+
}
|
|
4500
|
+
return names;
|
|
4501
|
+
}
|
|
4502
|
+
function extractHandoffPlanSlugs(handoff) {
|
|
4503
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
4504
|
+
for (const m of handoff.matchAll(/`([a-z0-9][a-z0-9._-]*)\.plan\.md`/gi)) {
|
|
4505
|
+
if (m[1]) slugs.add(m[1].toLowerCase());
|
|
4506
|
+
}
|
|
4507
|
+
for (const m of handoff.matchAll(/plan-monitor-([a-z0-9][a-z0-9._-]*)\.md/gi)) {
|
|
4508
|
+
if (m[1]) slugs.add(m[1].toLowerCase());
|
|
4509
|
+
}
|
|
4510
|
+
return slugs;
|
|
4511
|
+
}
|
|
4512
|
+
function monitorSlugFromName(fileName) {
|
|
4513
|
+
return fileName.replace(/^plan-monitor-/, "").replace(/\.md$/, "").toLowerCase();
|
|
4514
|
+
}
|
|
4515
|
+
async function selectUntriagedMonitors(rootDir) {
|
|
4516
|
+
const root = path29.resolve(rootDir);
|
|
4517
|
+
const memoryDir = path29.join(root, ".cursor", "memory");
|
|
4518
|
+
const allNames = await listMonitorFiles(memoryDir);
|
|
4519
|
+
const selectionOrder = ["git-fresh", "handoff-aligned", "untriaged-scan"];
|
|
4520
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4521
|
+
for (const name of allNames) {
|
|
4522
|
+
const abs = path29.join(memoryDir, name);
|
|
4523
|
+
try {
|
|
4524
|
+
const [content, st] = await Promise.all([readFile14(abs, "utf8"), stat2(abs)]);
|
|
4525
|
+
byName.set(name, { content, mtimeMs: st.mtimeMs });
|
|
4526
|
+
} catch {
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
const untriaged = (name) => {
|
|
4530
|
+
const row = byName.get(name);
|
|
4531
|
+
return !!row && !TRIAGE_HEADING_RE.test(row.content);
|
|
4532
|
+
};
|
|
4533
|
+
const gitFresh = await gitFreshMonitorNames(root);
|
|
4534
|
+
const gitFreshSet = [...gitFresh].filter(untriaged).sort();
|
|
4535
|
+
let handoff = "";
|
|
4536
|
+
try {
|
|
4537
|
+
handoff = await readFile14(path29.join(root, ".cursor", "HANDOFF.md"), "utf8");
|
|
4538
|
+
} catch {
|
|
4539
|
+
handoff = "";
|
|
4540
|
+
}
|
|
4541
|
+
const handoffSlugs = extractHandoffPlanSlugs(handoff);
|
|
4542
|
+
const handoffAligned = allNames.filter((n) => untriaged(n) && handoffSlugs.has(monitorSlugFromName(n))).sort();
|
|
4543
|
+
const scanAll = allNames.filter(untriaged);
|
|
4544
|
+
let chosen;
|
|
4545
|
+
if (gitFreshSet.length > 0) {
|
|
4546
|
+
chosen = { names: gitFreshSet, bucket: "git-fresh" };
|
|
4547
|
+
} else if (handoffAligned.length > 0) {
|
|
4548
|
+
chosen = { names: handoffAligned, bucket: "handoff-aligned" };
|
|
4549
|
+
} else {
|
|
4550
|
+
chosen = { names: scanAll, bucket: "untriaged-scan" };
|
|
4551
|
+
}
|
|
4552
|
+
const entries = [];
|
|
4553
|
+
for (const name of chosen.names) {
|
|
4554
|
+
const row = byName.get(name);
|
|
4555
|
+
if (!row) continue;
|
|
4556
|
+
entries.push({
|
|
4557
|
+
path: path29.join(memoryDir, name),
|
|
4558
|
+
relativePath: path29.relative(root, path29.join(memoryDir, name)).split(path29.sep).join("/"),
|
|
4559
|
+
mtimeMs: row.mtimeMs,
|
|
4560
|
+
hasTriageHeading: false,
|
|
4561
|
+
hasOpenGaps: hasOpenGaps(row.content),
|
|
4562
|
+
selectionBucket: chosen.bucket
|
|
4563
|
+
});
|
|
4564
|
+
}
|
|
4565
|
+
entries.sort((a, b) => {
|
|
4566
|
+
if (a.hasOpenGaps !== b.hasOpenGaps) return a.hasOpenGaps ? -1 : 1;
|
|
4567
|
+
return b.mtimeMs - a.mtimeMs;
|
|
4568
|
+
});
|
|
4569
|
+
return {
|
|
4570
|
+
selectionOrder: [...selectionOrder],
|
|
4571
|
+
monitors: entries,
|
|
4572
|
+
cite: CITE4
|
|
4573
|
+
};
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4576
|
+
// src/commands/monitors.ts
|
|
4577
|
+
var monitorsCommand = defineCommand12({
|
|
4578
|
+
meta: {
|
|
4579
|
+
name: "monitors",
|
|
4580
|
+
description: "Plan-monitor selection helpers (untriaged SoT for /plan-review-triage)"
|
|
4581
|
+
},
|
|
4582
|
+
args: {
|
|
4583
|
+
cwd: {
|
|
4584
|
+
type: "string",
|
|
4585
|
+
default: process.cwd()
|
|
4586
|
+
},
|
|
4587
|
+
untriaged: {
|
|
4588
|
+
type: "boolean",
|
|
4589
|
+
default: false,
|
|
4590
|
+
description: "Select untriaged monitors (never newest-mtime-wins alone)"
|
|
4591
|
+
},
|
|
4592
|
+
json: {
|
|
4593
|
+
type: "boolean",
|
|
4594
|
+
default: false,
|
|
4595
|
+
description: "Machine-readable JSON"
|
|
4596
|
+
}
|
|
4597
|
+
},
|
|
4598
|
+
async run({ args }) {
|
|
4599
|
+
if (!args.untriaged) {
|
|
4600
|
+
console.error("Usage: agent-kit monitors --untriaged [--json] [--cwd <dir>]");
|
|
4601
|
+
process.exitCode = 2;
|
|
4602
|
+
return;
|
|
4603
|
+
}
|
|
4604
|
+
const result = await selectUntriagedMonitors(path30.resolve(args.cwd));
|
|
4605
|
+
if (args.json) {
|
|
4606
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4607
|
+
return;
|
|
4608
|
+
}
|
|
4609
|
+
if (result.monitors.length === 0) {
|
|
4610
|
+
console.log("No untriaged plan-monitor files.");
|
|
4611
|
+
return;
|
|
4612
|
+
}
|
|
4613
|
+
for (const m of result.monitors) {
|
|
4614
|
+
console.log(m.relativePath);
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
});
|
|
4618
|
+
|
|
4619
|
+
// src/commands/run-plan.ts
|
|
4620
|
+
import path35 from "path";
|
|
4621
|
+
import { defineCommand as defineCommand13 } from "citty";
|
|
3541
4622
|
|
|
3542
4623
|
// src/plan-loop/backends.ts
|
|
3543
|
-
import { execFileSync, spawn as
|
|
4624
|
+
import { execFileSync as execFileSync2, spawn as spawn5 } from "child_process";
|
|
3544
4625
|
import { createWriteStream } from "fs";
|
|
3545
4626
|
async function which(bin) {
|
|
3546
4627
|
try {
|
|
3547
|
-
const out =
|
|
4628
|
+
const out = execFileSync2("which", [bin], { encoding: "utf8" }).trim();
|
|
3548
4629
|
return out || null;
|
|
3549
4630
|
} catch {
|
|
3550
4631
|
return null;
|
|
@@ -3553,7 +4634,7 @@ async function which(bin) {
|
|
|
3553
4634
|
function spawnLogged(command, args, logPath) {
|
|
3554
4635
|
return new Promise((resolve, reject) => {
|
|
3555
4636
|
const out = createWriteStream(logPath, { flags: "w" });
|
|
3556
|
-
const child =
|
|
4637
|
+
const child = spawn5(command, args, {
|
|
3557
4638
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3558
4639
|
});
|
|
3559
4640
|
const onData = (chunk) => {
|
|
@@ -3621,14 +4702,14 @@ function listBackendIds() {
|
|
|
3621
4702
|
}
|
|
3622
4703
|
|
|
3623
4704
|
// src/plan-loop/run-loop.ts
|
|
3624
|
-
import { mkdir as mkdir4, readFile as
|
|
3625
|
-
import
|
|
4705
|
+
import { mkdir as mkdir4, readFile as readFile17, rm, unlink as unlink2 } from "fs/promises";
|
|
4706
|
+
import path34 from "path";
|
|
3626
4707
|
|
|
3627
4708
|
// src/plan-loop/external-review.ts
|
|
3628
|
-
import { spawn as
|
|
3629
|
-
import
|
|
3630
|
-
var CANONICAL_REL =
|
|
3631
|
-
var FALLBACK_REL =
|
|
4709
|
+
import { spawn as spawn6 } from "child_process";
|
|
4710
|
+
import path31 from "path";
|
|
4711
|
+
var CANONICAL_REL = path31.join(".cursor", "scripts", "plan-external-review.sh");
|
|
4712
|
+
var FALLBACK_REL = path31.join("scripts", "plan-external-review.sh");
|
|
3632
4713
|
function isPlanExhaustedReason(reason) {
|
|
3633
4714
|
const r = reason.trim().toLowerCase();
|
|
3634
4715
|
if (!r) return false;
|
|
@@ -3646,12 +4727,12 @@ function shouldArmExternalPlanReview(input) {
|
|
|
3646
4727
|
return false;
|
|
3647
4728
|
}
|
|
3648
4729
|
async function armExternalPlanReview(root, options = {}) {
|
|
3649
|
-
const spawnFn = options.spawnFn ??
|
|
4730
|
+
const spawnFn = options.spawnFn ?? spawn6;
|
|
3650
4731
|
const existsFn = options.existsFn ?? fileExists;
|
|
3651
4732
|
const log = options.log ?? ((line) => console.log(line));
|
|
3652
4733
|
const force = options.force === true;
|
|
3653
|
-
const canonicalPath =
|
|
3654
|
-
const fallbackPath =
|
|
4734
|
+
const canonicalPath = path31.join(root, CANONICAL_REL);
|
|
4735
|
+
const fallbackPath = path31.join(root, FALLBACK_REL);
|
|
3655
4736
|
let scriptPath = null;
|
|
3656
4737
|
let scriptRel = CANONICAL_REL;
|
|
3657
4738
|
if (await existsFn(canonicalPath)) {
|
|
@@ -3704,7 +4785,7 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
3704
4785
|
}
|
|
3705
4786
|
|
|
3706
4787
|
// src/plan-loop/persona-banners.ts
|
|
3707
|
-
import
|
|
4788
|
+
import path32 from "path";
|
|
3708
4789
|
import {
|
|
3709
4790
|
blue,
|
|
3710
4791
|
cyan as cyan2,
|
|
@@ -3740,7 +4821,7 @@ function resolveColor(name, fallback) {
|
|
|
3740
4821
|
async function resolveCliPersonaId(root) {
|
|
3741
4822
|
try {
|
|
3742
4823
|
const cfg = await readJson(
|
|
3743
|
-
|
|
4824
|
+
path32.join(root, ".cursor", "context", "config.json")
|
|
3744
4825
|
);
|
|
3745
4826
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
3746
4827
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -3751,7 +4832,7 @@ async function resolveCliPersonaId(root) {
|
|
|
3751
4832
|
}
|
|
3752
4833
|
async function loadPersonaPack(root, personaId) {
|
|
3753
4834
|
try {
|
|
3754
|
-
const personaPath =
|
|
4835
|
+
const personaPath = path32.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
3755
4836
|
const pack = await readJson(personaPath);
|
|
3756
4837
|
if (!pack || typeof pack.id !== "string") return null;
|
|
3757
4838
|
return pack;
|
|
@@ -3800,8 +4881,8 @@ function createPersonaBannerPrinter(persona) {
|
|
|
3800
4881
|
}
|
|
3801
4882
|
|
|
3802
4883
|
// src/plan-loop/plan-state.ts
|
|
3803
|
-
import { readFile as
|
|
3804
|
-
import
|
|
4884
|
+
import { readFile as readFile15, readdir as readdir4 } from "fs/promises";
|
|
4885
|
+
import path33 from "path";
|
|
3805
4886
|
function countPendingTodos(raw) {
|
|
3806
4887
|
const lines = raw.split(/\r?\n/);
|
|
3807
4888
|
let inFront = 0;
|
|
@@ -3828,15 +4909,15 @@ function countPendingTodos(raw) {
|
|
|
3828
4909
|
}
|
|
3829
4910
|
async function findActivePlanFile(plansDir) {
|
|
3830
4911
|
if (!await fileExists(plansDir)) return null;
|
|
3831
|
-
const files = (await
|
|
3832
|
-
return files[0] ?
|
|
4912
|
+
const files = (await readdir4(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
4913
|
+
return files[0] ? path33.join(plansDir, files[0]) : null;
|
|
3833
4914
|
}
|
|
3834
4915
|
async function readPlan(planPath) {
|
|
3835
|
-
return
|
|
4916
|
+
return readFile15(planPath, "utf8");
|
|
3836
4917
|
}
|
|
3837
4918
|
|
|
3838
4919
|
// src/plan-loop/sentinel.ts
|
|
3839
|
-
import { readFile as
|
|
4920
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
3840
4921
|
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—\-].*)?)/i;
|
|
3841
4922
|
function takeFromText(text) {
|
|
3842
4923
|
if (!text) return null;
|
|
@@ -3883,7 +4964,7 @@ function parseSentinelFromLog(content) {
|
|
|
3883
4964
|
}
|
|
3884
4965
|
async function parseSentinelFromLogFile(logPath) {
|
|
3885
4966
|
try {
|
|
3886
|
-
const content = await
|
|
4967
|
+
const content = await readFile16(logPath, "utf8");
|
|
3887
4968
|
return parseSentinelFromLog(content);
|
|
3888
4969
|
} catch {
|
|
3889
4970
|
return { kind: "missing" };
|
|
@@ -3906,9 +4987,9 @@ function sleep(ms) {
|
|
|
3906
4987
|
return new Promise((r) => setTimeout(r, ms));
|
|
3907
4988
|
}
|
|
3908
4989
|
async function runPlanLoop(opts) {
|
|
3909
|
-
const plansDir =
|
|
3910
|
-
const stopFile =
|
|
3911
|
-
const logDir =
|
|
4990
|
+
const plansDir = path34.join(opts.root, ".cursor", "plans");
|
|
4991
|
+
const stopFile = path34.join(opts.root, ".cursor", "loop.stop");
|
|
4992
|
+
const logDir = path34.join(opts.root, ".cursor", "loop-logs");
|
|
3912
4993
|
const planPath = await findActivePlanFile(plansDir);
|
|
3913
4994
|
if (!planPath) {
|
|
3914
4995
|
logger.error("No active plan in .cursor/plans/");
|
|
@@ -3929,7 +5010,7 @@ async function runPlanLoop(opts) {
|
|
|
3929
5010
|
try {
|
|
3930
5011
|
const persona = await loadCliRunPlanPersona(opts.root);
|
|
3931
5012
|
const banners = createPersonaBannerPrinter(persona);
|
|
3932
|
-
console.log(`Active plan: ${
|
|
5013
|
+
console.log(`Active plan: ${path34.basename(planPath)}`);
|
|
3933
5014
|
console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
|
|
3934
5015
|
console.log(`Backend: ${opts.backend.id}`);
|
|
3935
5016
|
if (persona) {
|
|
@@ -3972,8 +5053,8 @@ async function runPlanLoop(opts) {
|
|
|
3972
5053
|
planExhausted = true;
|
|
3973
5054
|
break;
|
|
3974
5055
|
}
|
|
3975
|
-
const logPath =
|
|
3976
|
-
const relLog =
|
|
5056
|
+
const logPath = path34.join(logDir, `tick-${stamp()}.log`);
|
|
5057
|
+
const relLog = path34.relative(opts.root, logPath);
|
|
3977
5058
|
console.log("");
|
|
3978
5059
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
3979
5060
|
if (banners) banners.tickStart(tickLine);
|
|
@@ -3992,7 +5073,7 @@ async function runPlanLoop(opts) {
|
|
|
3992
5073
|
return 1;
|
|
3993
5074
|
}
|
|
3994
5075
|
try {
|
|
3995
|
-
const logText = await
|
|
5076
|
+
const logText = await readFile17(logPath, "utf8");
|
|
3996
5077
|
if (logText.includes("Too many MCP tools")) {
|
|
3997
5078
|
const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
|
|
3998
5079
|
if (banners) banners.stop(msg);
|
|
@@ -4049,7 +5130,7 @@ async function runPlanLoop(opts) {
|
|
|
4049
5130
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
4050
5131
|
if (banners) banners.phaseComplete(finishDetail);
|
|
4051
5132
|
console.log(
|
|
4052
|
-
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${
|
|
5133
|
+
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path34.relative(opts.root, logDir)}/`
|
|
4053
5134
|
);
|
|
4054
5135
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
4055
5136
|
await armExternalPlanReview(opts.root);
|
|
@@ -4061,7 +5142,7 @@ async function runPlanLoop(opts) {
|
|
|
4061
5142
|
}
|
|
4062
5143
|
|
|
4063
5144
|
// src/commands/run-plan.ts
|
|
4064
|
-
var runPlanCommand =
|
|
5145
|
+
var runPlanCommand = defineCommand13({
|
|
4065
5146
|
meta: {
|
|
4066
5147
|
name: "run-plan",
|
|
4067
5148
|
description: "Headless continuous plan runner: one fresh agent per tick (LOOP_TICK_RESULT contract). Never git-prod."
|
|
@@ -4120,7 +5201,7 @@ var runPlanCommand = defineCommand10({
|
|
|
4120
5201
|
return;
|
|
4121
5202
|
}
|
|
4122
5203
|
const code = await runPlanLoop({
|
|
4123
|
-
root:
|
|
5204
|
+
root: path35.resolve(args.cwd),
|
|
4124
5205
|
maxTicks,
|
|
4125
5206
|
sleepSeconds,
|
|
4126
5207
|
model: args.model ? String(args.model) : void 0,
|
|
@@ -4132,8 +5213,8 @@ var runPlanCommand = defineCommand10({
|
|
|
4132
5213
|
});
|
|
4133
5214
|
|
|
4134
5215
|
// src/commands/scan.ts
|
|
4135
|
-
import { defineCommand as
|
|
4136
|
-
var scanCommand =
|
|
5216
|
+
import { defineCommand as defineCommand14 } from "citty";
|
|
5217
|
+
var scanCommand = defineCommand14({
|
|
4137
5218
|
meta: {
|
|
4138
5219
|
name: "scan",
|
|
4139
5220
|
description: "Scan the current repository and print detected profile."
|
|
@@ -4154,8 +5235,8 @@ var scanCommand = defineCommand11({
|
|
|
4154
5235
|
});
|
|
4155
5236
|
|
|
4156
5237
|
// src/commands/status.ts
|
|
4157
|
-
import
|
|
4158
|
-
import { defineCommand as
|
|
5238
|
+
import path36 from "path";
|
|
5239
|
+
import { defineCommand as defineCommand15 } from "citty";
|
|
4159
5240
|
function profileStatus(profile) {
|
|
4160
5241
|
if (!profile) return { origin: "none", evidence: [], profile: null };
|
|
4161
5242
|
if ("detection" in profile && profile.detection && typeof profile.detection === "object") {
|
|
@@ -4168,7 +5249,7 @@ function profileStatus(profile) {
|
|
|
4168
5249
|
}
|
|
4169
5250
|
return { origin: "legacy-wizard", evidence: [], profile };
|
|
4170
5251
|
}
|
|
4171
|
-
var statusCommand =
|
|
5252
|
+
var statusCommand = defineCommand15({
|
|
4172
5253
|
meta: {
|
|
4173
5254
|
name: "status",
|
|
4174
5255
|
description: "Show Agent Kit distribution status (manifest + optional wizard profile)."
|
|
@@ -4185,11 +5266,11 @@ var statusCommand = defineCommand12({
|
|
|
4185
5266
|
}
|
|
4186
5267
|
},
|
|
4187
5268
|
async run({ args }) {
|
|
4188
|
-
const rootDir =
|
|
5269
|
+
const rootDir = path36.resolve(args.cwd);
|
|
4189
5270
|
const [manifest, rawProfile, scan] = await Promise.all([
|
|
4190
5271
|
loadAgentKitManifest(rootDir),
|
|
4191
5272
|
readJson(
|
|
4192
|
-
|
|
5273
|
+
path36.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
4193
5274
|
),
|
|
4194
5275
|
runScanner(rootDir)
|
|
4195
5276
|
]);
|
|
@@ -4244,13 +5325,13 @@ var statusCommand = defineCommand12({
|
|
|
4244
5325
|
});
|
|
4245
5326
|
|
|
4246
5327
|
// src/commands/update.ts
|
|
4247
|
-
import { defineCommand as
|
|
5328
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
4248
5329
|
|
|
4249
5330
|
// src/lifecycle/check-updates.ts
|
|
4250
|
-
import { execFile as
|
|
4251
|
-
import
|
|
4252
|
-
import { promisify as
|
|
4253
|
-
var
|
|
5331
|
+
import { execFile as execFile6 } from "child_process";
|
|
5332
|
+
import path37 from "path";
|
|
5333
|
+
import { promisify as promisify6 } from "util";
|
|
5334
|
+
var execFileAsync5 = promisify6(execFile6);
|
|
4254
5335
|
var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i;
|
|
4255
5336
|
var FACTORY_URL_MARKERS = ["agent-kit-dev"];
|
|
4256
5337
|
var FACTORY_REFS = /* @__PURE__ */ new Set(["staging", "homologacao", "develop", "dev"]);
|
|
@@ -4309,7 +5390,7 @@ function pickLatestSemverTag(lsRemoteStdout) {
|
|
|
4309
5390
|
}
|
|
4310
5391
|
async function fetchLatestPublicVersion(registryUrl = DEFAULT_REGISTRY_URL) {
|
|
4311
5392
|
assertSafeRegistrySource(registryUrl, "main");
|
|
4312
|
-
const { stdout } = await
|
|
5393
|
+
const { stdout } = await execFileAsync5("git", ["ls-remote", "--tags", "--", registryUrl], {
|
|
4313
5394
|
env: gitEnv2(),
|
|
4314
5395
|
timeout: 2e4
|
|
4315
5396
|
});
|
|
@@ -4339,11 +5420,11 @@ function intervalElapsed(lastCheckedAt, intervalDays) {
|
|
|
4339
5420
|
return Date.now() - last >= ms;
|
|
4340
5421
|
}
|
|
4341
5422
|
async function loadContextConfig(cwd) {
|
|
4342
|
-
const configPath =
|
|
5423
|
+
const configPath = path37.join(cwd, ".cursor", "context", "config.json");
|
|
4343
5424
|
return readJson(configPath);
|
|
4344
5425
|
}
|
|
4345
5426
|
async function stampLastCheckedAt(cwd) {
|
|
4346
|
-
const configPath =
|
|
5427
|
+
const configPath = path37.join(cwd, ".cursor", "context", "config.json");
|
|
4347
5428
|
const existing = await loadContextConfig(cwd) ?? {};
|
|
4348
5429
|
const prev = existing.updateCheck && typeof existing.updateCheck === "object" ? { ...existing.updateCheck } : {};
|
|
4349
5430
|
existing.updateCheck = {
|
|
@@ -4484,7 +5565,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
4484
5565
|
}
|
|
4485
5566
|
|
|
4486
5567
|
// src/commands/update.ts
|
|
4487
|
-
var updateCommand =
|
|
5568
|
+
var updateCommand = defineCommand16({
|
|
4488
5569
|
meta: {
|
|
4489
5570
|
name: "update",
|
|
4490
5571
|
description: "Re-apply L0/packs/skills from the registry; never overwrites L3 protected paths. Use --check for notify-only."
|
|
@@ -4566,8 +5647,188 @@ var updateCommand = defineCommand13({
|
|
|
4566
5647
|
}
|
|
4567
5648
|
});
|
|
4568
5649
|
|
|
5650
|
+
// src/commands/validate.ts
|
|
5651
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
5652
|
+
import path38 from "path";
|
|
5653
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
5654
|
+
|
|
5655
|
+
// src/invariants/plan-schema.ts
|
|
5656
|
+
var CITE5 = "agent-kit validate plan (.cursor/context/templates/plan.md)";
|
|
5657
|
+
function validatePlanFrontmatterText(text) {
|
|
5658
|
+
const warnings = [];
|
|
5659
|
+
const match = text.match(/^---\n([\s\S]*?)\n---/);
|
|
5660
|
+
if (!match?.[1]) {
|
|
5661
|
+
warnings.push({
|
|
5662
|
+
code: "missing-frontmatter",
|
|
5663
|
+
message: "Plan file has no YAML frontmatter block.",
|
|
5664
|
+
cite: CITE5
|
|
5665
|
+
});
|
|
5666
|
+
return warnings;
|
|
5667
|
+
}
|
|
5668
|
+
const block = match[1];
|
|
5669
|
+
if (!/^todos:\s*$/m.test(block) && !/^todos:\s*\[/m.test(block)) {
|
|
5670
|
+
if (!/^todos:/m.test(block)) {
|
|
5671
|
+
warnings.push({
|
|
5672
|
+
code: "missing-todos",
|
|
5673
|
+
message: "Plan frontmatter has no `todos:` key.",
|
|
5674
|
+
cite: CITE5
|
|
5675
|
+
});
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
if (!/^name:\s*\S+/m.test(block)) {
|
|
5679
|
+
warnings.push({
|
|
5680
|
+
code: "missing-name",
|
|
5681
|
+
message: "Plan frontmatter has no `name:` key.",
|
|
5682
|
+
cite: CITE5
|
|
5683
|
+
});
|
|
5684
|
+
}
|
|
5685
|
+
const hasTodoItem = /^- id:\s*\S+/m.test(block);
|
|
5686
|
+
if (/^todos:/m.test(block) && !hasTodoItem && !/^todos:\s*\[\s*\]/m.test(block)) {
|
|
5687
|
+
warnings.push({
|
|
5688
|
+
code: "empty-todos",
|
|
5689
|
+
message: "Plan frontmatter `todos:` has no `- id:` items.",
|
|
5690
|
+
cite: CITE5
|
|
5691
|
+
});
|
|
5692
|
+
}
|
|
5693
|
+
return warnings;
|
|
5694
|
+
}
|
|
5695
|
+
|
|
5696
|
+
// src/commands/validate.ts
|
|
5697
|
+
async function resolveEditedPath(cwd, explicit) {
|
|
5698
|
+
if (explicit) {
|
|
5699
|
+
const filePath2 = path38.resolve(cwd, explicit);
|
|
5700
|
+
try {
|
|
5701
|
+
return { filePath: filePath2, content: await readFile18(filePath2, "utf8") };
|
|
5702
|
+
} catch {
|
|
5703
|
+
return null;
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
const payload = await readStdinJson();
|
|
5707
|
+
const rel = typeof payload.file_path === "string" && payload.file_path || typeof payload.path === "string" && payload.path || typeof payload.file === "string" && payload.file || "";
|
|
5708
|
+
if (!rel) return null;
|
|
5709
|
+
const filePath = path38.isAbsolute(rel) ? rel : path38.resolve(cwd, rel);
|
|
5710
|
+
try {
|
|
5711
|
+
return { filePath, content: await readFile18(filePath, "utf8") };
|
|
5712
|
+
} catch {
|
|
5713
|
+
return null;
|
|
5714
|
+
}
|
|
5715
|
+
}
|
|
5716
|
+
function isHandoffPath(filePath) {
|
|
5717
|
+
return filePath.replace(/\\/g, "/").endsWith(".cursor/HANDOFF.md");
|
|
5718
|
+
}
|
|
5719
|
+
function isPlanPath(filePath) {
|
|
5720
|
+
const norm = filePath.replace(/\\/g, "/");
|
|
5721
|
+
return norm.includes("/.cursor/plans/") && norm.endsWith(".plan.md");
|
|
5722
|
+
}
|
|
5723
|
+
var validateCommand = defineCommand17({
|
|
5724
|
+
meta: {
|
|
5725
|
+
name: "validate",
|
|
5726
|
+
description: "Advisory validators for HANDOFF / plan frontmatter (afterFileEdit adapter)"
|
|
5727
|
+
},
|
|
5728
|
+
subCommands: {
|
|
5729
|
+
handoff: defineCommand17({
|
|
5730
|
+
meta: { name: "handoff", description: "Validate HANDOFF machine fields" },
|
|
5731
|
+
args: {
|
|
5732
|
+
cwd: { type: "string", default: process.cwd() },
|
|
5733
|
+
file: { type: "string", description: "Path to HANDOFF.md" },
|
|
5734
|
+
json: { type: "boolean", default: true }
|
|
5735
|
+
},
|
|
5736
|
+
async run({ args }) {
|
|
5737
|
+
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
5738
|
+
const fileArg = typeof args.file === "string" ? args.file : void 0;
|
|
5739
|
+
const filePath = fileArg ? path38.resolve(cwd, fileArg) : path38.join(path38.resolve(cwd), ".cursor", "HANDOFF.md");
|
|
5740
|
+
let content = "";
|
|
5741
|
+
try {
|
|
5742
|
+
content = await readFile18(filePath, "utf8");
|
|
5743
|
+
} catch {
|
|
5744
|
+
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
5745
|
+
return;
|
|
5746
|
+
}
|
|
5747
|
+
const warnings = validateHandoffText(content);
|
|
5748
|
+
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
5749
|
+
}
|
|
5750
|
+
}),
|
|
5751
|
+
plan: defineCommand17({
|
|
5752
|
+
meta: { name: "plan", description: "Validate plan frontmatter" },
|
|
5753
|
+
args: {
|
|
5754
|
+
cwd: { type: "string", default: process.cwd() },
|
|
5755
|
+
file: { type: "string", description: "Path to *.plan.md" },
|
|
5756
|
+
json: { type: "boolean", default: true }
|
|
5757
|
+
},
|
|
5758
|
+
async run({ args }) {
|
|
5759
|
+
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
5760
|
+
const fileArg = typeof args.file === "string" ? args.file : void 0;
|
|
5761
|
+
if (!fileArg) {
|
|
5762
|
+
console.log(JSON.stringify({ ok: false, warnings: [{ message: "file required" }] }));
|
|
5763
|
+
process.exitCode = 2;
|
|
5764
|
+
return;
|
|
5765
|
+
}
|
|
5766
|
+
const filePath = path38.resolve(cwd, fileArg);
|
|
5767
|
+
let content = "";
|
|
5768
|
+
try {
|
|
5769
|
+
content = await readFile18(filePath, "utf8");
|
|
5770
|
+
} catch {
|
|
5771
|
+
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
5772
|
+
return;
|
|
5773
|
+
}
|
|
5774
|
+
const warnings = validatePlanFrontmatterText(content);
|
|
5775
|
+
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
5776
|
+
}
|
|
5777
|
+
}),
|
|
5778
|
+
"after-edit": defineCommand17({
|
|
5779
|
+
meta: {
|
|
5780
|
+
name: "after-edit",
|
|
5781
|
+
description: "Advisory afterFileEdit: annotate HANDOFF/plan issues (never block)"
|
|
5782
|
+
},
|
|
5783
|
+
args: {
|
|
5784
|
+
cwd: { type: "string", default: process.cwd() }
|
|
5785
|
+
},
|
|
5786
|
+
async run({ args }) {
|
|
5787
|
+
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
5788
|
+
const resolved = await resolveEditedPath(path38.resolve(cwd));
|
|
5789
|
+
if (!resolved) {
|
|
5790
|
+
console.log(JSON.stringify({}));
|
|
5791
|
+
return;
|
|
5792
|
+
}
|
|
5793
|
+
const { filePath, content } = resolved;
|
|
5794
|
+
if (isHandoffPath(filePath)) {
|
|
5795
|
+
const warnings = validateHandoffText(content);
|
|
5796
|
+
if (!warnings.length) {
|
|
5797
|
+
console.log(JSON.stringify({}));
|
|
5798
|
+
return;
|
|
5799
|
+
}
|
|
5800
|
+
const msg = warnings.map((w) => w.message).join(" ");
|
|
5801
|
+
console.log(
|
|
5802
|
+
JSON.stringify({
|
|
5803
|
+
user_message: msg,
|
|
5804
|
+
agent_message: `${msg} Cite: agent-kit validate handoff.`
|
|
5805
|
+
})
|
|
5806
|
+
);
|
|
5807
|
+
return;
|
|
5808
|
+
}
|
|
5809
|
+
if (isPlanPath(filePath)) {
|
|
5810
|
+
const warnings = validatePlanFrontmatterText(content);
|
|
5811
|
+
if (!warnings.length) {
|
|
5812
|
+
console.log(JSON.stringify({}));
|
|
5813
|
+
return;
|
|
5814
|
+
}
|
|
5815
|
+
const msg = warnings.map((w) => w.message).join(" ");
|
|
5816
|
+
console.log(
|
|
5817
|
+
JSON.stringify({
|
|
5818
|
+
user_message: msg,
|
|
5819
|
+
agent_message: `${msg} Cite: agent-kit validate plan.`
|
|
5820
|
+
})
|
|
5821
|
+
);
|
|
5822
|
+
return;
|
|
5823
|
+
}
|
|
5824
|
+
console.log(JSON.stringify({}));
|
|
5825
|
+
}
|
|
5826
|
+
})
|
|
5827
|
+
}
|
|
5828
|
+
});
|
|
5829
|
+
|
|
4569
5830
|
// src/index.ts
|
|
4570
|
-
var main =
|
|
5831
|
+
var main = defineCommand18({
|
|
4571
5832
|
meta: {
|
|
4572
5833
|
name: "agent-kit",
|
|
4573
5834
|
description: "HITL framework for AI-assisted IDEs"
|
|
@@ -4585,7 +5846,11 @@ var main = defineCommand14({
|
|
|
4585
5846
|
handoff: handoffCommand,
|
|
4586
5847
|
"run-plan": runPlanCommand,
|
|
4587
5848
|
dashboard: dashboardCommand,
|
|
4588
|
-
"dashboard-broadcast": dashboardBroadcastCommand
|
|
5849
|
+
"dashboard-broadcast": dashboardBroadcastCommand,
|
|
5850
|
+
hook: hookCommand,
|
|
5851
|
+
guard: guardCommand,
|
|
5852
|
+
monitors: monitorsCommand,
|
|
5853
|
+
validate: validateCommand
|
|
4589
5854
|
}
|
|
4590
5855
|
});
|
|
4591
5856
|
runMain(main);
|