@dadado/agent-kit-cli 4.7.2 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +619 -235
- package/package.json +1 -1
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 defineCommand14, runMain } from "citty";
|
|
5
5
|
|
|
6
6
|
// src/commands/add.ts
|
|
7
7
|
import { defineCommand } from "citty";
|
|
@@ -974,6 +974,22 @@ var L0_ARTIFACTS = [
|
|
|
974
974
|
source: ".cursor/commands/start-project.md",
|
|
975
975
|
target: ".cursor/commands/start-project.md"
|
|
976
976
|
},
|
|
977
|
+
{
|
|
978
|
+
source: ".cursor/commands/backlog-add.md",
|
|
979
|
+
target: ".cursor/commands/backlog-add.md"
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
source: ".cursor/commands/backlog-edit.md",
|
|
983
|
+
target: ".cursor/commands/backlog-edit.md"
|
|
984
|
+
},
|
|
985
|
+
{
|
|
986
|
+
source: ".cursor/commands/backlog-delete.md",
|
|
987
|
+
target: ".cursor/commands/backlog-delete.md"
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
source: ".cursor/commands/backlog-cancel.md",
|
|
991
|
+
target: ".cursor/commands/backlog-cancel.md"
|
|
992
|
+
},
|
|
977
993
|
{
|
|
978
994
|
source: ".cursor/commands/agent-kit-onboard.md",
|
|
979
995
|
target: ".cursor/commands/agent-kit-onboard.md"
|
|
@@ -986,6 +1002,14 @@ var L0_ARTIFACTS = [
|
|
|
986
1002
|
source: ".cursor/commands/run-plan.md",
|
|
987
1003
|
target: ".cursor/commands/run-plan.md"
|
|
988
1004
|
},
|
|
1005
|
+
{
|
|
1006
|
+
source: ".cursor/commands/hotfix.md",
|
|
1007
|
+
target: ".cursor/commands/hotfix.md"
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
source: ".cursor/commands/run-plan-all.md",
|
|
1011
|
+
target: ".cursor/commands/run-plan-all.md"
|
|
1012
|
+
},
|
|
989
1013
|
// Deprecated aliases of /run-plan (kept so existing references keep working)
|
|
990
1014
|
{
|
|
991
1015
|
source: ".cursor/commands/run-plan-loop.md",
|
|
@@ -1014,6 +1038,10 @@ var L0_ARTIFACTS = [
|
|
|
1014
1038
|
source: ".cursor/commands/plan-review-triage.md",
|
|
1015
1039
|
target: ".cursor/commands/plan-review-triage.md"
|
|
1016
1040
|
},
|
|
1041
|
+
{
|
|
1042
|
+
source: ".cursor/commands/field-report-resolve.md",
|
|
1043
|
+
target: ".cursor/commands/field-report-resolve.md"
|
|
1044
|
+
},
|
|
1017
1045
|
// Context (templates + example config; private config.json is not L0)
|
|
1018
1046
|
{
|
|
1019
1047
|
source: ".cursor/context/templates/plan-external-review-prompt.md",
|
|
@@ -1047,11 +1075,19 @@ var L0_ARTIFACTS = [
|
|
|
1047
1075
|
source: ".cursor/context/config.example.json",
|
|
1048
1076
|
target: ".cursor/context/config.example.json"
|
|
1049
1077
|
},
|
|
1050
|
-
// Scripts (canonical
|
|
1078
|
+
// Scripts (canonical launchers; consumers never receive repo-root scripts/)
|
|
1051
1079
|
{
|
|
1052
1080
|
source: ".cursor/scripts/plan-external-review.sh",
|
|
1053
1081
|
target: ".cursor/scripts/plan-external-review.sh"
|
|
1054
1082
|
},
|
|
1083
|
+
{
|
|
1084
|
+
source: ".cursor/scripts/field-report-cadence-bump.sh",
|
|
1085
|
+
target: ".cursor/scripts/field-report-cadence-bump.sh"
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
source: ".cursor/scripts/run-plan-all-consolidate.sh",
|
|
1089
|
+
target: ".cursor/scripts/run-plan-all-consolidate.sh"
|
|
1090
|
+
},
|
|
1055
1091
|
// Secrets gate (structural)
|
|
1056
1092
|
{
|
|
1057
1093
|
source: ".cursor/hooks/pre-commit/check-secrets.sh",
|
|
@@ -1354,6 +1390,12 @@ var contributeCommand = defineCommand2({
|
|
|
1354
1390
|
}
|
|
1355
1391
|
});
|
|
1356
1392
|
|
|
1393
|
+
// src/commands/dashboard-broadcast.ts
|
|
1394
|
+
import { spawn as spawn2 } from "child_process";
|
|
1395
|
+
import { access as access3 } from "fs/promises";
|
|
1396
|
+
import path10 from "path";
|
|
1397
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
1398
|
+
|
|
1357
1399
|
// src/commands/dashboard.ts
|
|
1358
1400
|
import { spawn } from "child_process";
|
|
1359
1401
|
import { access as access2 } from "fs/promises";
|
|
@@ -1417,12 +1459,72 @@ var dashboardCommand = defineCommand3({
|
|
|
1417
1459
|
}
|
|
1418
1460
|
});
|
|
1419
1461
|
|
|
1462
|
+
// src/commands/dashboard-broadcast.ts
|
|
1463
|
+
async function findDashboardBroadcastStart(cwd) {
|
|
1464
|
+
let dir = path10.resolve(cwd);
|
|
1465
|
+
for (; ; ) {
|
|
1466
|
+
const candidate2 = path10.join(dir, "dashboard", "start-broadcast.mjs");
|
|
1467
|
+
try {
|
|
1468
|
+
await access3(candidate2);
|
|
1469
|
+
return candidate2;
|
|
1470
|
+
} catch {
|
|
1471
|
+
}
|
|
1472
|
+
const parent = path10.dirname(dir);
|
|
1473
|
+
if (parent === dir) return null;
|
|
1474
|
+
dir = parent;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function runStartScript2(startPath, env) {
|
|
1478
|
+
return new Promise((resolve, reject) => {
|
|
1479
|
+
const child = spawn2(process.execPath, [startPath], {
|
|
1480
|
+
cwd: path10.dirname(path10.dirname(startPath)),
|
|
1481
|
+
env,
|
|
1482
|
+
stdio: "inherit"
|
|
1483
|
+
});
|
|
1484
|
+
child.on("error", reject);
|
|
1485
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
var dashboardBroadcastCommand = defineCommand4({
|
|
1489
|
+
meta: {
|
|
1490
|
+
name: "dashboard-broadcast",
|
|
1491
|
+
description: "Opt-in LAN Mission Control (token-gated). Terminal counterpart to /dashboard-broadcast. Does not change loopback /dashboard."
|
|
1492
|
+
},
|
|
1493
|
+
args: {
|
|
1494
|
+
cwd: {
|
|
1495
|
+
type: "string",
|
|
1496
|
+
default: process.cwd(),
|
|
1497
|
+
description: "Directory to search upward for dashboard/start-broadcast.mjs"
|
|
1498
|
+
},
|
|
1499
|
+
"no-open": {
|
|
1500
|
+
type: "boolean",
|
|
1501
|
+
default: false,
|
|
1502
|
+
description: "Do not open a browser; only ensure the server is up and print LAN URL + token"
|
|
1503
|
+
}
|
|
1504
|
+
},
|
|
1505
|
+
async run({ args }) {
|
|
1506
|
+
const startPath = await findDashboardBroadcastStart(args.cwd);
|
|
1507
|
+
if (!startPath) {
|
|
1508
|
+
const loopback = await findDashboardStart(args.cwd);
|
|
1509
|
+
logger.error(
|
|
1510
|
+
loopback ? "No dashboard/start-broadcast.mjs found beside dashboard/start.mjs. Update the agent-kit tree, then retry." : "No dashboard/start-broadcast.mjs found. Mission Control ships with the agent-kit repo; run from that tree."
|
|
1511
|
+
);
|
|
1512
|
+
process.exitCode = 1;
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
const env = { ...process.env };
|
|
1516
|
+
if (args["no-open"]) env.MISSION_CONTROL_NO_OPEN = "1";
|
|
1517
|
+
const code = await runStartScript2(startPath, env);
|
|
1518
|
+
if (code !== 0) process.exitCode = code;
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1420
1522
|
// src/commands/diff.ts
|
|
1421
|
-
import { defineCommand as
|
|
1523
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
1422
1524
|
|
|
1423
1525
|
// src/lifecycle/diff.ts
|
|
1424
1526
|
import { readFile as readFile5 } from "fs/promises";
|
|
1425
|
-
import
|
|
1527
|
+
import path11 from "path";
|
|
1426
1528
|
async function readIfExists(abs) {
|
|
1427
1529
|
try {
|
|
1428
1530
|
return await readFile5(abs, "utf8");
|
|
@@ -1431,7 +1533,7 @@ async function readIfExists(abs) {
|
|
|
1431
1533
|
}
|
|
1432
1534
|
}
|
|
1433
1535
|
async function comparePair(registryRoot, projectRoot, sourceRel, targetRel, protectedGlobs) {
|
|
1434
|
-
const posixTarget = targetRel.split(
|
|
1536
|
+
const posixTarget = targetRel.split(path11.sep).join("/");
|
|
1435
1537
|
if (isProtectedPath(posixTarget, protectedGlobs)) {
|
|
1436
1538
|
return { path: posixTarget, status: "protected" };
|
|
1437
1539
|
}
|
|
@@ -1461,7 +1563,7 @@ async function diffAgainstRegistry(registryRoot, projectRoot, manifest) {
|
|
|
1461
1563
|
const entries = [];
|
|
1462
1564
|
const seen = /* @__PURE__ */ new Set();
|
|
1463
1565
|
const pushUnique = async (sourceRel, targetRel) => {
|
|
1464
|
-
const key = targetRel.split(
|
|
1566
|
+
const key = targetRel.split(path11.sep).join("/");
|
|
1465
1567
|
if (seen.has(key)) return;
|
|
1466
1568
|
seen.add(key);
|
|
1467
1569
|
entries.push(
|
|
@@ -1488,8 +1590,8 @@ async function diffAgainstRegistry(registryRoot, projectRoot, manifest) {
|
|
|
1488
1590
|
continue;
|
|
1489
1591
|
}
|
|
1490
1592
|
const category = skill.path.includes("/core/") ? "core" : "community";
|
|
1491
|
-
const sourceRel =
|
|
1492
|
-
const targetRel =
|
|
1593
|
+
const sourceRel = path11.posix.join(skill.path, "SKILL.md");
|
|
1594
|
+
const targetRel = path11.posix.join(".cursor", "skills", category, skill.id, "SKILL.md");
|
|
1493
1595
|
await pushUnique(sourceRel, targetRel);
|
|
1494
1596
|
}
|
|
1495
1597
|
}
|
|
@@ -1508,7 +1610,7 @@ function summarizeDiff(entries) {
|
|
|
1508
1610
|
}
|
|
1509
1611
|
|
|
1510
1612
|
// src/commands/diff.ts
|
|
1511
|
-
var diffCommand =
|
|
1613
|
+
var diffCommand = defineCommand5({
|
|
1512
1614
|
meta: {
|
|
1513
1615
|
name: "diff",
|
|
1514
1616
|
description: "Compare installed kit artifacts to the registry (respects L3 protected)."
|
|
@@ -1557,8 +1659,8 @@ var diffCommand = defineCommand4({
|
|
|
1557
1659
|
});
|
|
1558
1660
|
|
|
1559
1661
|
// src/commands/doctor.ts
|
|
1560
|
-
import
|
|
1561
|
-
import { defineCommand as
|
|
1662
|
+
import path21 from "path";
|
|
1663
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
1562
1664
|
|
|
1563
1665
|
// src/scanner/readiness.ts
|
|
1564
1666
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -1808,11 +1910,11 @@ function createReadinessReport(scan, options) {
|
|
|
1808
1910
|
|
|
1809
1911
|
// src/scanner/safe-fixes.ts
|
|
1810
1912
|
import { readFile as readFile8, writeFile as writeFile3 } from "fs/promises";
|
|
1811
|
-
import
|
|
1913
|
+
import path19 from "path";
|
|
1812
1914
|
|
|
1813
1915
|
// src/scanner/detect-repository.ts
|
|
1814
1916
|
import { readFile as readFile6 } from "fs/promises";
|
|
1815
|
-
import
|
|
1917
|
+
import path12 from "path";
|
|
1816
1918
|
var CONTEXT_PATHS = [
|
|
1817
1919
|
["README.md", "README"],
|
|
1818
1920
|
["README", "README"],
|
|
@@ -1831,7 +1933,7 @@ var CONTEXT_PATHS = [
|
|
|
1831
1933
|
async function existingEvidence(rootDir, candidates) {
|
|
1832
1934
|
const evidence = await Promise.all(
|
|
1833
1935
|
candidates.map(
|
|
1834
|
-
async ([relativePath, label]) => await fileExists(
|
|
1936
|
+
async ([relativePath, label]) => await fileExists(path12.join(rootDir, relativePath)) ? { source: "file", value: `${relativePath}:${label}` } : void 0
|
|
1835
1937
|
)
|
|
1836
1938
|
);
|
|
1837
1939
|
return evidence.flatMap((item) => item ? [item] : []);
|
|
@@ -1852,7 +1954,7 @@ async function detectContext(rootDir) {
|
|
|
1852
1954
|
async function detectPurpose(rootDir, stack) {
|
|
1853
1955
|
const entries = await listDirectory(rootDir);
|
|
1854
1956
|
const lowerEntries = entries.map((entry) => entry.toLowerCase());
|
|
1855
|
-
const packageJson = await readJson(
|
|
1957
|
+
const packageJson = await readJson(path12.join(rootDir, "package.json"));
|
|
1856
1958
|
const categories = [];
|
|
1857
1959
|
const evidence = [];
|
|
1858
1960
|
const add = (category, value2) => {
|
|
@@ -1892,16 +1994,16 @@ async function detectPurpose(rootDir, stack) {
|
|
|
1892
1994
|
}
|
|
1893
1995
|
async function detectAgentKit(rootDir) {
|
|
1894
1996
|
const manifestRelativePath = ".cursor/agent-kit.json";
|
|
1895
|
-
const manifestPath =
|
|
1997
|
+
const manifestPath = path12.join(rootDir, manifestRelativePath);
|
|
1896
1998
|
const installed = await fileExists(manifestPath);
|
|
1897
1999
|
const manifest = installed ? await readJson(manifestPath) : null;
|
|
1898
2000
|
return {
|
|
1899
2001
|
installed,
|
|
1900
2002
|
manifestPath: installed ? manifestRelativePath : void 0,
|
|
1901
2003
|
version: manifest?.version,
|
|
1902
|
-
hasPlans: await fileExists(
|
|
1903
|
-
hasHandoff: await fileExists(
|
|
1904
|
-
hasMemory: await fileExists(
|
|
2004
|
+
hasPlans: await fileExists(path12.join(rootDir, ".cursor/plans")),
|
|
2005
|
+
hasHandoff: await fileExists(path12.join(rootDir, ".cursor/HANDOFF.md")),
|
|
2006
|
+
hasMemory: await fileExists(path12.join(rootDir, ".cursor/memory"))
|
|
1905
2007
|
};
|
|
1906
2008
|
}
|
|
1907
2009
|
var REQUIRED_SECRET_PATTERNS = [
|
|
@@ -1915,7 +2017,7 @@ var REQUIRED_SECRET_PATTERNS = [
|
|
|
1915
2017
|
"*service-account*.json"
|
|
1916
2018
|
];
|
|
1917
2019
|
async function detectSafety(rootDir, trackedFiles) {
|
|
1918
|
-
const gitignorePath =
|
|
2020
|
+
const gitignorePath = path12.join(rootDir, ".gitignore");
|
|
1919
2021
|
const hasGitignore = await fileExists(gitignorePath);
|
|
1920
2022
|
const gitignore = hasGitignore ? await readFile6(gitignorePath, "utf8") : "";
|
|
1921
2023
|
const lines = gitignore.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
@@ -1926,7 +2028,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
1926
2028
|
(file) => /(^|\/)(\.env(\..+)?|.*\.(key|pem|p12|pfx)|.*credentials.*\.json)$/i.test(file)
|
|
1927
2029
|
);
|
|
1928
2030
|
const hookPaths = [".husky", ".git/hooks/pre-commit", "git-hooks/pre-commit"];
|
|
1929
|
-
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(
|
|
2031
|
+
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(path12.join(rootDir, item))))).some(Boolean);
|
|
1930
2032
|
const guardCandidates = [
|
|
1931
2033
|
".husky/pre-commit",
|
|
1932
2034
|
".husky/pre-push",
|
|
@@ -1935,7 +2037,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
1935
2037
|
];
|
|
1936
2038
|
const guardContents = await Promise.all(
|
|
1937
2039
|
guardCandidates.map(
|
|
1938
|
-
async (item) => await fileExists(
|
|
2040
|
+
async (item) => await fileExists(path12.join(rootDir, item)) ? readFile6(path12.join(rootDir, item), "utf8") : ""
|
|
1939
2041
|
)
|
|
1940
2042
|
);
|
|
1941
2043
|
return {
|
|
@@ -1953,11 +2055,11 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
1953
2055
|
}
|
|
1954
2056
|
|
|
1955
2057
|
// src/scanner/scan.ts
|
|
1956
|
-
import
|
|
2058
|
+
import path18 from "path";
|
|
1957
2059
|
|
|
1958
2060
|
// src/scanner/detect-git.ts
|
|
1959
2061
|
import { execFile as execFile2 } from "child_process";
|
|
1960
|
-
import
|
|
2062
|
+
import path13 from "path";
|
|
1961
2063
|
import { promisify as promisify2 } from "util";
|
|
1962
2064
|
var exec = promisify2(execFile2);
|
|
1963
2065
|
function remoteHostname(remoteUrl) {
|
|
@@ -1981,7 +2083,7 @@ function sanitizeRemoteUrl(remoteUrl) {
|
|
|
1981
2083
|
}
|
|
1982
2084
|
async function detectProvider(rootDir, remoteUrl) {
|
|
1983
2085
|
const configuration = await readJson(
|
|
1984
|
-
|
|
2086
|
+
path13.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
1985
2087
|
);
|
|
1986
2088
|
const configuredProvider = configuration?.git?.provider;
|
|
1987
2089
|
if (configuredProvider) {
|
|
@@ -2038,7 +2140,7 @@ async function detectProvider(rootDir, remoteUrl) {
|
|
|
2038
2140
|
evidence: remoteEvidence
|
|
2039
2141
|
};
|
|
2040
2142
|
}
|
|
2041
|
-
if (await fileExists(
|
|
2143
|
+
if (await fileExists(path13.join(rootDir, ".gitlab-ci.yml"))) {
|
|
2042
2144
|
return {
|
|
2043
2145
|
provider: "gitlab",
|
|
2044
2146
|
providerKind: "gitlab-self-hosted",
|
|
@@ -2127,11 +2229,11 @@ async function detectGit(rootDir) {
|
|
|
2127
2229
|
}
|
|
2128
2230
|
|
|
2129
2231
|
// src/scanner/detect-ide.ts
|
|
2130
|
-
import
|
|
2232
|
+
import path14 from "path";
|
|
2131
2233
|
async function detectIde(rootDir) {
|
|
2132
|
-
const hasCursor = await fileExists(
|
|
2133
|
-
const hasVSCode = await fileExists(
|
|
2134
|
-
const hasWindsurf = await fileExists(
|
|
2234
|
+
const hasCursor = await fileExists(path14.join(rootDir, ".cursor"));
|
|
2235
|
+
const hasVSCode = await fileExists(path14.join(rootDir, ".vscode"));
|
|
2236
|
+
const hasWindsurf = await fileExists(path14.join(rootDir, ".windsurfrules"));
|
|
2135
2237
|
if (hasCursor) return { ide: "cursor", plan: "cursor-pro" };
|
|
2136
2238
|
if (hasVSCode) return { ide: "vscode", plan: "vscode-pro" };
|
|
2137
2239
|
if (hasWindsurf) return { ide: "windsurf", plan: "windsurf" };
|
|
@@ -2139,7 +2241,7 @@ async function detectIde(rootDir) {
|
|
|
2139
2241
|
}
|
|
2140
2242
|
|
|
2141
2243
|
// src/scanner/detect-infra.ts
|
|
2142
|
-
import
|
|
2244
|
+
import path15 from "path";
|
|
2143
2245
|
|
|
2144
2246
|
// src/types.ts
|
|
2145
2247
|
var CI_PLATFORM_FILES = {
|
|
@@ -2168,12 +2270,12 @@ var PM_TOOL_LABELS = {
|
|
|
2168
2270
|
|
|
2169
2271
|
// src/scanner/detect-infra.ts
|
|
2170
2272
|
async function detectInfra(rootDir) {
|
|
2171
|
-
const docker = await fileExists(
|
|
2172
|
-
const kubernetes = await fileExists(
|
|
2273
|
+
const docker = await fileExists(path15.join(rootDir, "Dockerfile")) || await fileExists(path15.join(rootDir, "docker-compose.yml")) || await fileExists(path15.join(rootDir, "docker-compose.yaml"));
|
|
2274
|
+
const kubernetes = await fileExists(path15.join(rootDir, "k8s")) || await fileExists(path15.join(rootDir, "kubernetes"));
|
|
2173
2275
|
let ci = "none";
|
|
2174
2276
|
const ciFiles = [];
|
|
2175
2277
|
for (const [platform, filePath] of Object.entries(CI_PLATFORM_FILES)) {
|
|
2176
|
-
if (await fileExists(
|
|
2278
|
+
if (await fileExists(path15.join(rootDir, filePath))) {
|
|
2177
2279
|
if (ci === "none") ci = platform;
|
|
2178
2280
|
ciFiles.push(filePath);
|
|
2179
2281
|
}
|
|
@@ -2198,12 +2300,12 @@ async function detectInfra(rootDir) {
|
|
|
2198
2300
|
];
|
|
2199
2301
|
const infrastructureFiles = (await Promise.all(
|
|
2200
2302
|
infrastructureCandidates.map(
|
|
2201
|
-
async (file) => await fileExists(
|
|
2303
|
+
async (file) => await fileExists(path15.join(rootDir, file)) ? file : void 0
|
|
2202
2304
|
)
|
|
2203
2305
|
)).filter((file) => file !== void 0);
|
|
2204
2306
|
const deploymentFiles = (await Promise.all(
|
|
2205
2307
|
deploymentCandidates.map(
|
|
2206
|
-
async (file) => await fileExists(
|
|
2308
|
+
async (file) => await fileExists(path15.join(rootDir, file)) ? file : void 0
|
|
2207
2309
|
)
|
|
2208
2310
|
)).filter((file) => file !== void 0);
|
|
2209
2311
|
return { docker, kubernetes, ci, ciFiles, infrastructureFiles, deploymentFiles };
|
|
@@ -2211,12 +2313,12 @@ async function detectInfra(rootDir) {
|
|
|
2211
2313
|
|
|
2212
2314
|
// src/scanner/detect-services.ts
|
|
2213
2315
|
import { readFile as readFile7 } from "fs/promises";
|
|
2214
|
-
import
|
|
2316
|
+
import path16 from "path";
|
|
2215
2317
|
async function detectProjectManagement(rootDir) {
|
|
2216
2318
|
const tools = [];
|
|
2217
2319
|
const mcpConfigPaths = [
|
|
2218
|
-
|
|
2219
|
-
|
|
2320
|
+
path16.join(rootDir, ".cursor", "mcp.json"),
|
|
2321
|
+
path16.join(rootDir, "mcp.json")
|
|
2220
2322
|
];
|
|
2221
2323
|
for (const configPath of mcpConfigPaths) {
|
|
2222
2324
|
if (!await fileExists(configPath)) continue;
|
|
@@ -2232,20 +2334,20 @@ async function detectProjectManagement(rootDir) {
|
|
|
2232
2334
|
} catch {
|
|
2233
2335
|
}
|
|
2234
2336
|
}
|
|
2235
|
-
if (await fileExists(
|
|
2337
|
+
if (await fileExists(path16.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
|
|
2236
2338
|
tools.push("github-issues");
|
|
2237
2339
|
}
|
|
2238
|
-
if (await fileExists(
|
|
2340
|
+
if (await fileExists(path16.join(rootDir, ".github", "projects"))) {
|
|
2239
2341
|
tools.push("github-projects");
|
|
2240
2342
|
}
|
|
2241
2343
|
return [...new Set(tools)];
|
|
2242
2344
|
}
|
|
2243
2345
|
async function detectServices(rootDir) {
|
|
2244
|
-
const hasPrisma = await fileExists(
|
|
2245
|
-
const hasSequelize = await fileExists(
|
|
2246
|
-
const hasDrizzle = await fileExists(
|
|
2247
|
-
const hasKnex = await fileExists(
|
|
2248
|
-
const hasTypeorm = await fileExists(
|
|
2346
|
+
const hasPrisma = await fileExists(path16.join(rootDir, "prisma/schema.prisma"));
|
|
2347
|
+
const hasSequelize = await fileExists(path16.join(rootDir, "sequelize"));
|
|
2348
|
+
const hasDrizzle = await fileExists(path16.join(rootDir, "drizzle.config.ts"));
|
|
2349
|
+
const hasKnex = await fileExists(path16.join(rootDir, "knexfile.ts"));
|
|
2350
|
+
const hasTypeorm = await fileExists(path16.join(rootDir, "ormconfig.json"));
|
|
2249
2351
|
const database = hasPrisma || hasSequelize || hasDrizzle || hasKnex || hasTypeorm ? "postgresql" : void 0;
|
|
2250
2352
|
const orm = hasPrisma ? "prisma" : hasDrizzle ? "drizzle" : hasSequelize ? "sequelize" : hasKnex ? "knex" : hasTypeorm ? "typeorm" : void 0;
|
|
2251
2353
|
const projectManagement = await detectProjectManagement(rootDir);
|
|
@@ -2257,7 +2359,7 @@ async function detectServices(rootDir) {
|
|
|
2257
2359
|
}
|
|
2258
2360
|
|
|
2259
2361
|
// src/scanner/detect-stack.ts
|
|
2260
|
-
import
|
|
2362
|
+
import path17 from "path";
|
|
2261
2363
|
var PROJECT_MARKERS = [
|
|
2262
2364
|
"package.json",
|
|
2263
2365
|
"requirements.txt",
|
|
@@ -2286,7 +2388,7 @@ async function detectPackageManager(rootDir, packageJson) {
|
|
|
2286
2388
|
};
|
|
2287
2389
|
}
|
|
2288
2390
|
for (const [lockfile, packageManager] of LOCKFILES) {
|
|
2289
|
-
if (await fileExists(
|
|
2391
|
+
if (await fileExists(path17.join(rootDir, lockfile))) {
|
|
2290
2392
|
return {
|
|
2291
2393
|
packageManager,
|
|
2292
2394
|
evidence: [{ source: "file", value: lockfile }]
|
|
@@ -2303,27 +2405,27 @@ function commandsForScripts(scripts, packageManager) {
|
|
|
2303
2405
|
return { testCommands, validationCommands };
|
|
2304
2406
|
}
|
|
2305
2407
|
async function detectStack(rootDir) {
|
|
2306
|
-
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(
|
|
2307
|
-
const hasPackageJson = await fileExists(
|
|
2408
|
+
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(path17.join(rootDir, item))))).some(Boolean);
|
|
2409
|
+
const hasPackageJson = await fileExists(path17.join(rootDir, "package.json"));
|
|
2308
2410
|
if (hasPackageJson) {
|
|
2309
|
-
const packageJson = await readJson(
|
|
2411
|
+
const packageJson = await readJson(path17.join(rootDir, "package.json")) ?? {};
|
|
2310
2412
|
const scripts = packageJson.scripts ?? {};
|
|
2311
2413
|
const packageManager = await detectPackageManager(rootDir, packageJson);
|
|
2312
2414
|
const commands = commandsForScripts(scripts, packageManager.packageManager);
|
|
2313
|
-
const hasNextConfig = await fileExists(
|
|
2314
|
-
const hasNestConfig = await fileExists(
|
|
2415
|
+
const hasNextConfig = await fileExists(path17.join(rootDir, "next.config.js")) || await fileExists(path17.join(rootDir, "next.config.mjs")) || await fileExists(path17.join(rootDir, "next.config.ts"));
|
|
2416
|
+
const hasNestConfig = await fileExists(path17.join(rootDir, "nest-cli.json"));
|
|
2315
2417
|
return {
|
|
2316
2418
|
language: "node",
|
|
2317
2419
|
framework: hasNextConfig ? "nextjs" : hasNestConfig ? "nestjs" : "node",
|
|
2318
2420
|
packageManager: packageManager.packageManager,
|
|
2319
2421
|
packageManagerEvidence: packageManager.evidence,
|
|
2320
2422
|
scripts,
|
|
2321
|
-
workspaces: packageJson.workspaces !== void 0 || await fileExists(
|
|
2423
|
+
workspaces: packageJson.workspaces !== void 0 || await fileExists(path17.join(rootDir, "pnpm-workspace.yaml")),
|
|
2322
2424
|
...commands,
|
|
2323
2425
|
hasProjectFiles: hasAnyProjectMarker
|
|
2324
2426
|
};
|
|
2325
2427
|
}
|
|
2326
|
-
if (await fileExists(
|
|
2428
|
+
if (await fileExists(path17.join(rootDir, "pyproject.toml"))) {
|
|
2327
2429
|
return {
|
|
2328
2430
|
language: "python",
|
|
2329
2431
|
framework: "python",
|
|
@@ -2333,7 +2435,7 @@ async function detectStack(rootDir) {
|
|
|
2333
2435
|
hasProjectFiles: hasAnyProjectMarker
|
|
2334
2436
|
};
|
|
2335
2437
|
}
|
|
2336
|
-
if (await fileExists(
|
|
2438
|
+
if (await fileExists(path17.join(rootDir, "go.mod"))) {
|
|
2337
2439
|
return {
|
|
2338
2440
|
language: "go",
|
|
2339
2441
|
framework: "go",
|
|
@@ -2343,7 +2445,7 @@ async function detectStack(rootDir) {
|
|
|
2343
2445
|
hasProjectFiles: hasAnyProjectMarker
|
|
2344
2446
|
};
|
|
2345
2447
|
}
|
|
2346
|
-
if (await fileExists(
|
|
2448
|
+
if (await fileExists(path17.join(rootDir, "Cargo.toml"))) {
|
|
2347
2449
|
return {
|
|
2348
2450
|
language: "rust",
|
|
2349
2451
|
framework: "rust",
|
|
@@ -2353,7 +2455,7 @@ async function detectStack(rootDir) {
|
|
|
2353
2455
|
hasProjectFiles: hasAnyProjectMarker
|
|
2354
2456
|
};
|
|
2355
2457
|
}
|
|
2356
|
-
if (await fileExists(
|
|
2458
|
+
if (await fileExists(path17.join(rootDir, "composer.json"))) {
|
|
2357
2459
|
return {
|
|
2358
2460
|
language: "php",
|
|
2359
2461
|
framework: "php",
|
|
@@ -2386,7 +2488,7 @@ function isGreenfieldByEntries(entries) {
|
|
|
2386
2488
|
return meaningful.length === 0;
|
|
2387
2489
|
}
|
|
2388
2490
|
async function runScanner(rootDir) {
|
|
2389
|
-
const normalizedRoot =
|
|
2491
|
+
const normalizedRoot = path18.resolve(rootDir);
|
|
2390
2492
|
const entries = await listDirectory(normalizedRoot);
|
|
2391
2493
|
const stack = await detectStack(normalizedRoot);
|
|
2392
2494
|
const purpose = await detectPurpose(normalizedRoot, stack);
|
|
@@ -2537,10 +2639,13 @@ function preferenceDefaults(onboarding, onboarded) {
|
|
|
2537
2639
|
enabled: false,
|
|
2538
2640
|
backend: "claude",
|
|
2539
2641
|
autoRemediate: false,
|
|
2540
|
-
offerOnExhausted: true
|
|
2642
|
+
offerOnExhausted: true,
|
|
2643
|
+
mode: "paste",
|
|
2644
|
+
midBatchAudits: false,
|
|
2645
|
+
preflight: "off"
|
|
2541
2646
|
},
|
|
2542
2647
|
autoHandoff: false,
|
|
2543
|
-
|
|
2648
|
+
agentPersona: {
|
|
2544
2649
|
default: "autopilot",
|
|
2545
2650
|
modes: {
|
|
2546
2651
|
"continue-plan": "autopilot",
|
|
@@ -2587,7 +2692,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2587
2692
|
});
|
|
2588
2693
|
const changes = [];
|
|
2589
2694
|
for (const relativePath of ESSENTIAL_DIRECTORIES) {
|
|
2590
|
-
const absolutePath =
|
|
2695
|
+
const absolutePath = path19.join(beforeScan.rootDir, relativePath);
|
|
2591
2696
|
const exists = await fileExists(absolutePath);
|
|
2592
2697
|
if (!exists && !dryRun) await ensureDir(absolutePath);
|
|
2593
2698
|
recordChange(
|
|
@@ -2600,7 +2705,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2600
2705
|
);
|
|
2601
2706
|
}
|
|
2602
2707
|
const gitignoreRelativePath = ".gitignore";
|
|
2603
|
-
const gitignorePath =
|
|
2708
|
+
const gitignorePath = path19.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
2604
2709
|
const existingGitignore = await fileExists(gitignorePath) ? await readFile8(gitignorePath, "utf8") : "";
|
|
2605
2710
|
const mergedGitignore = mergeSecretIgnores(existingGitignore);
|
|
2606
2711
|
const gitignoreChanged = mergedGitignore !== existingGitignore;
|
|
@@ -2616,7 +2721,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2616
2721
|
gitignoreChanged ? "required secret patterns are missing" : "required patterns are present"
|
|
2617
2722
|
)
|
|
2618
2723
|
);
|
|
2619
|
-
const profilePath =
|
|
2724
|
+
const profilePath = path19.join(beforeScan.rootDir, PROFILE_RELATIVE_PATH);
|
|
2620
2725
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
2621
2726
|
const desiredProfile = createProfile(beforeScan, before, generatedAt);
|
|
2622
2727
|
const mergedProfile = mergeMissing(existingProfile, desiredProfile);
|
|
@@ -2638,7 +2743,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2638
2743
|
generatorVersion: options.generatorVersion,
|
|
2639
2744
|
generatedAt
|
|
2640
2745
|
});
|
|
2641
|
-
const contextConfigPath =
|
|
2746
|
+
const contextConfigPath = path19.join(beforeScan.rootDir, CONTEXT_CONFIG_RELATIVE_PATH);
|
|
2642
2747
|
const existingContextConfig = await readJson(contextConfigPath) ?? {};
|
|
2643
2748
|
const onboarding = reconcileOnboardingState(evidenceReport, existingContextConfig, generatedAt);
|
|
2644
2749
|
const defaults = preferenceDefaults(onboarding, existingContextConfig.onboarded);
|
|
@@ -2667,17 +2772,17 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2667
2772
|
}
|
|
2668
2773
|
|
|
2669
2774
|
// src/scanner/snapshot.ts
|
|
2670
|
-
import
|
|
2775
|
+
import path20 from "path";
|
|
2671
2776
|
var READINESS_SNAPSHOT_RELATIVE_PATH = ".cursor/context/readiness.json";
|
|
2672
2777
|
async function writeReadinessSnapshot(rootDir, report) {
|
|
2673
|
-
const snapshotPath =
|
|
2778
|
+
const snapshotPath = path20.join(rootDir, READINESS_SNAPSHOT_RELATIVE_PATH);
|
|
2674
2779
|
await writeJson(snapshotPath, report);
|
|
2675
2780
|
return snapshotPath;
|
|
2676
2781
|
}
|
|
2677
2782
|
|
|
2678
2783
|
// src/commands/doctor.ts
|
|
2679
2784
|
async function runDoctor(cwd, options = {}) {
|
|
2680
|
-
const rootDir =
|
|
2785
|
+
const rootDir = path21.resolve(cwd);
|
|
2681
2786
|
if (options.fixSafe) {
|
|
2682
2787
|
const execution = await executeSafeReadinessFixes(rootDir, {
|
|
2683
2788
|
generatorVersion: KIT_VERSION,
|
|
@@ -2708,7 +2813,7 @@ function printDoctorSummary(result) {
|
|
|
2708
2813
|
nextAction ? `Next: ${nextAction.recommendation}` : "Next: repository readiness checks are complete"
|
|
2709
2814
|
);
|
|
2710
2815
|
}
|
|
2711
|
-
var doctorCommand =
|
|
2816
|
+
var doctorCommand = defineCommand6({
|
|
2712
2817
|
meta: {
|
|
2713
2818
|
name: "doctor",
|
|
2714
2819
|
description: "Diagnose repository readiness and optionally apply safe local fixes."
|
|
@@ -2740,10 +2845,10 @@ var doctorCommand = defineCommand5({
|
|
|
2740
2845
|
});
|
|
2741
2846
|
|
|
2742
2847
|
// src/commands/handoff.ts
|
|
2743
|
-
import { spawn as
|
|
2848
|
+
import { spawn as spawn3 } from "child_process";
|
|
2744
2849
|
import { readFile as readFile9, readdir as readdir2, writeFile as writeFile4 } from "fs/promises";
|
|
2745
|
-
import
|
|
2746
|
-
import { defineCommand as
|
|
2850
|
+
import path22 from "path";
|
|
2851
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
2747
2852
|
function parsePlanFrontmatter(raw) {
|
|
2748
2853
|
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
2749
2854
|
if (!match?.[1]) return null;
|
|
@@ -2762,19 +2867,19 @@ async function findActivePlan(plansDir) {
|
|
|
2762
2867
|
if (!await fileExists(plansDir)) return null;
|
|
2763
2868
|
const files = (await readdir2(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
2764
2869
|
for (const file of files) {
|
|
2765
|
-
const raw = await readFile9(
|
|
2870
|
+
const raw = await readFile9(path22.join(plansDir, file), "utf8");
|
|
2766
2871
|
const fm = parsePlanFrontmatter(raw);
|
|
2767
2872
|
if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
|
|
2768
2873
|
return { file, raw };
|
|
2769
2874
|
}
|
|
2770
2875
|
}
|
|
2771
|
-
return files[0] ? { file: files[0], raw: await readFile9(
|
|
2876
|
+
return files[0] ? { file: files[0], raw: await readFile9(path22.join(plansDir, files[0]), "utf8") } : null;
|
|
2772
2877
|
}
|
|
2773
2878
|
function now() {
|
|
2774
2879
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
|
|
2775
2880
|
}
|
|
2776
2881
|
async function loadProfile(rootDir) {
|
|
2777
|
-
const configPath =
|
|
2882
|
+
const configPath = path22.join(rootDir, ".cursor", "agent-kit.config.json");
|
|
2778
2883
|
try {
|
|
2779
2884
|
return await readJson(configPath);
|
|
2780
2885
|
} catch {
|
|
@@ -2852,7 +2957,7 @@ function printV3Guidance() {
|
|
|
2852
2957
|
}
|
|
2853
2958
|
function runCursorHandoff(scriptPath, cwd) {
|
|
2854
2959
|
return new Promise((resolve, reject) => {
|
|
2855
|
-
const child =
|
|
2960
|
+
const child = spawn3("sh", [scriptPath, "handoff"], {
|
|
2856
2961
|
cwd,
|
|
2857
2962
|
stdio: "inherit"
|
|
2858
2963
|
});
|
|
@@ -2860,7 +2965,7 @@ function runCursorHandoff(scriptPath, cwd) {
|
|
|
2860
2965
|
child.on("close", (code) => resolve(code ?? 1));
|
|
2861
2966
|
});
|
|
2862
2967
|
}
|
|
2863
|
-
var handoffCommand =
|
|
2968
|
+
var handoffCommand = defineCommand7({
|
|
2864
2969
|
meta: {
|
|
2865
2970
|
name: "handoff",
|
|
2866
2971
|
description: "Write .cursor/HANDOFF.md from the active Cursor plan, or run ./cursor-handoff handoff when no plan exists."
|
|
@@ -2874,13 +2979,13 @@ var handoffCommand = defineCommand6({
|
|
|
2874
2979
|
},
|
|
2875
2980
|
async run({ args }) {
|
|
2876
2981
|
const profile = await loadProfile(args.cwd);
|
|
2877
|
-
const plansDir =
|
|
2878
|
-
const handoffPath =
|
|
2982
|
+
const plansDir = path22.join(args.cwd, ".cursor", "plans");
|
|
2983
|
+
const handoffPath = path22.join(args.cwd, ".cursor", "HANDOFF.md");
|
|
2879
2984
|
const plan = await findActivePlan(plansDir);
|
|
2880
2985
|
if (plan) {
|
|
2881
2986
|
const fm = parsePlanFrontmatter(plan.raw);
|
|
2882
2987
|
if (fm) {
|
|
2883
|
-
await ensureDir(
|
|
2988
|
+
await ensureDir(path22.join(args.cwd, ".cursor"));
|
|
2884
2989
|
const content = buildHandoff(plan.file, fm, profile);
|
|
2885
2990
|
await writeFile4(handoffPath, content, "utf8");
|
|
2886
2991
|
logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
|
|
@@ -2900,7 +3005,7 @@ var handoffCommand = defineCommand6({
|
|
|
2900
3005
|
}
|
|
2901
3006
|
logger.warn(`Plan ${plan.file} without valid frontmatter; trying legacy flow.`);
|
|
2902
3007
|
}
|
|
2903
|
-
const scriptPath =
|
|
3008
|
+
const scriptPath = path22.join(args.cwd, "cursor-handoff");
|
|
2904
3009
|
if (!await fileExists(scriptPath)) {
|
|
2905
3010
|
printV3Guidance();
|
|
2906
3011
|
return;
|
|
@@ -2926,15 +3031,15 @@ var handoffCommand = defineCommand6({
|
|
|
2926
3031
|
|
|
2927
3032
|
// src/commands/init.ts
|
|
2928
3033
|
import { intro, outro } from "@clack/prompts";
|
|
2929
|
-
import { defineCommand as
|
|
3034
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
2930
3035
|
|
|
2931
3036
|
// src/commands/install.ts
|
|
2932
|
-
import
|
|
2933
|
-
import { defineCommand as
|
|
3037
|
+
import path25 from "path";
|
|
3038
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
2934
3039
|
|
|
2935
3040
|
// src/generator/personalization.ts
|
|
2936
3041
|
import { readFile as readFile10, writeFile as writeFile5 } from "fs/promises";
|
|
2937
|
-
import
|
|
3042
|
+
import path23 from "path";
|
|
2938
3043
|
var PERSONALIZATION_CONTRACT_VERSION = 1;
|
|
2939
3044
|
var CONTEXT_PATH = ".cursor/project-context.md";
|
|
2940
3045
|
var AGENTS_PATH = "AGENTS.md";
|
|
@@ -3085,7 +3190,7 @@ function renderProjectContext(profile) {
|
|
|
3085
3190
|
`;
|
|
3086
3191
|
}
|
|
3087
3192
|
async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
3088
|
-
const target =
|
|
3193
|
+
const target = path23.join(rootDir, relativePath);
|
|
3089
3194
|
if (await fileExists(target)) {
|
|
3090
3195
|
return {
|
|
3091
3196
|
kind: "file",
|
|
@@ -3095,7 +3200,7 @@ async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
|
3095
3200
|
evidence
|
|
3096
3201
|
};
|
|
3097
3202
|
}
|
|
3098
|
-
await ensureDir(
|
|
3203
|
+
await ensureDir(path23.dirname(target));
|
|
3099
3204
|
await writeFile5(target, content, "utf8");
|
|
3100
3205
|
return {
|
|
3101
3206
|
kind: "file",
|
|
@@ -3112,7 +3217,7 @@ async function packTargets(registryRoot, packId) {
|
|
|
3112
3217
|
async function existingTargets(projectRoot, targets) {
|
|
3113
3218
|
const checks = await Promise.all(
|
|
3114
3219
|
targets.map(
|
|
3115
|
-
async (target) => await fileExists(
|
|
3220
|
+
async (target) => await fileExists(path23.join(projectRoot, target)) ? target : null
|
|
3116
3221
|
)
|
|
3117
3222
|
);
|
|
3118
3223
|
return checks.filter((target) => target !== null);
|
|
@@ -3134,14 +3239,14 @@ async function applyPersonalization(input) {
|
|
|
3134
3239
|
componentResults.push({ ...item, status: "unavailable" });
|
|
3135
3240
|
continue;
|
|
3136
3241
|
}
|
|
3137
|
-
const target =
|
|
3242
|
+
const target = path23.posix.join(
|
|
3138
3243
|
".cursor",
|
|
3139
3244
|
"skills",
|
|
3140
3245
|
skill.path.includes("/core/") ? "core" : "community",
|
|
3141
3246
|
skill.id,
|
|
3142
3247
|
"SKILL.md"
|
|
3143
3248
|
);
|
|
3144
|
-
if (await fileExists(
|
|
3249
|
+
if (await fileExists(path23.join(input.rootDir, target))) {
|
|
3145
3250
|
componentResults.push({ ...item, status: "skipped-customized", path: target });
|
|
3146
3251
|
protectedPaths.add(target);
|
|
3147
3252
|
continue;
|
|
@@ -3196,7 +3301,7 @@ async function applyPersonalization(input) {
|
|
|
3196
3301
|
items: [...fileResults, ...componentResults],
|
|
3197
3302
|
protectedPaths: [...protectedPaths].sort()
|
|
3198
3303
|
};
|
|
3199
|
-
await writeJson(
|
|
3304
|
+
await writeJson(path23.join(input.rootDir, RESULT_PATH), result);
|
|
3200
3305
|
return {
|
|
3201
3306
|
result,
|
|
3202
3307
|
manifest: {
|
|
@@ -3214,7 +3319,7 @@ async function applyPersonalization(input) {
|
|
|
3214
3319
|
};
|
|
3215
3320
|
}
|
|
3216
3321
|
async function readRepositoryProfile(rootDir) {
|
|
3217
|
-
const target =
|
|
3322
|
+
const target = path23.join(rootDir, ".cursor/agent-kit.config.json");
|
|
3218
3323
|
if (!await fileExists(target)) return null;
|
|
3219
3324
|
return JSON.parse(await readFile10(target, "utf8"));
|
|
3220
3325
|
}
|
|
@@ -3222,16 +3327,16 @@ async function readRepositoryProfile(rootDir) {
|
|
|
3222
3327
|
// src/lifecycle/onboard-migration.ts
|
|
3223
3328
|
import { createHash as createHash3 } from "crypto";
|
|
3224
3329
|
import { readFile as readFile11, unlink } from "fs/promises";
|
|
3225
|
-
import
|
|
3330
|
+
import path24 from "path";
|
|
3226
3331
|
var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
|
|
3227
3332
|
var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
|
|
3228
3333
|
var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
|
|
3229
3334
|
"b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
|
|
3230
3335
|
]);
|
|
3231
3336
|
async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
|
|
3232
|
-
const legacyPath =
|
|
3337
|
+
const legacyPath = path24.join(projectRoot, LEGACY_ONBOARD_PATH);
|
|
3233
3338
|
if (!await fileExists(legacyPath)) return "absent";
|
|
3234
|
-
const namespacedPath =
|
|
3339
|
+
const namespacedPath = path24.join(projectRoot, NAMESPACED_ONBOARD_PATH);
|
|
3235
3340
|
if (!await fileExists(namespacedPath)) return "preserved-customized";
|
|
3236
3341
|
const content = await readFile11(legacyPath);
|
|
3237
3342
|
const hash = createHash3("sha256").update(content).digest("hex");
|
|
@@ -3302,7 +3407,7 @@ function printReadinessNarrative(result) {
|
|
|
3302
3407
|
);
|
|
3303
3408
|
}
|
|
3304
3409
|
async function performInstall(options) {
|
|
3305
|
-
const projectRoot =
|
|
3410
|
+
const projectRoot = path25.resolve(options.cwd);
|
|
3306
3411
|
const packs = parsePackList(options.pack);
|
|
3307
3412
|
const existing = await loadAgentKitManifest(projectRoot);
|
|
3308
3413
|
const registry = await resolveRegistryFromCli({
|
|
@@ -3356,7 +3461,7 @@ async function performInstall(options) {
|
|
|
3356
3461
|
safeChanges: readinessExecution.changes
|
|
3357
3462
|
};
|
|
3358
3463
|
}
|
|
3359
|
-
var installCommand =
|
|
3464
|
+
var installCommand = defineCommand8({
|
|
3360
3465
|
meta: {
|
|
3361
3466
|
name: "install",
|
|
3362
3467
|
description: "Bootstrap L0 (+ optional packs) from the registry and write agent-kit.json."
|
|
@@ -3378,7 +3483,7 @@ var installCommand = defineCommand7({
|
|
|
3378
3483
|
...REGISTRY_CLI_ARGS
|
|
3379
3484
|
},
|
|
3380
3485
|
async run({ args }) {
|
|
3381
|
-
const projectRoot =
|
|
3486
|
+
const projectRoot = path25.resolve(args.cwd);
|
|
3382
3487
|
logger.info(`Installing into: ${projectRoot}`);
|
|
3383
3488
|
const packs = parsePackList(args.pack);
|
|
3384
3489
|
for (const id of packs) {
|
|
@@ -3406,7 +3511,7 @@ var installCommand = defineCommand7({
|
|
|
3406
3511
|
async function runInitCompatibility(cwd, installer = performInstall) {
|
|
3407
3512
|
return installer({ cwd });
|
|
3408
3513
|
}
|
|
3409
|
-
var initCommand =
|
|
3514
|
+
var initCommand = defineCommand9({
|
|
3410
3515
|
meta: {
|
|
3411
3516
|
name: "init",
|
|
3412
3517
|
description: "Guided compatibility entry point for install and repository readiness."
|
|
@@ -3431,11 +3536,11 @@ var initCommand = defineCommand8({
|
|
|
3431
3536
|
});
|
|
3432
3537
|
|
|
3433
3538
|
// src/commands/run-plan.ts
|
|
3434
|
-
import
|
|
3435
|
-
import { defineCommand as
|
|
3539
|
+
import path30 from "path";
|
|
3540
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
3436
3541
|
|
|
3437
3542
|
// src/plan-loop/backends.ts
|
|
3438
|
-
import { execFileSync, spawn as
|
|
3543
|
+
import { execFileSync, spawn as spawn4 } from "child_process";
|
|
3439
3544
|
import { createWriteStream } from "fs";
|
|
3440
3545
|
async function which(bin) {
|
|
3441
3546
|
try {
|
|
@@ -3448,7 +3553,7 @@ async function which(bin) {
|
|
|
3448
3553
|
function spawnLogged(command, args, logPath) {
|
|
3449
3554
|
return new Promise((resolve, reject) => {
|
|
3450
3555
|
const out = createWriteStream(logPath, { flags: "w" });
|
|
3451
|
-
const child =
|
|
3556
|
+
const child = spawn4(command, args, {
|
|
3452
3557
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3453
3558
|
});
|
|
3454
3559
|
const onData = (chunk) => {
|
|
@@ -3517,13 +3622,13 @@ function listBackendIds() {
|
|
|
3517
3622
|
|
|
3518
3623
|
// src/plan-loop/run-loop.ts
|
|
3519
3624
|
import { mkdir as mkdir4, readFile as readFile14, rm, unlink as unlink2 } from "fs/promises";
|
|
3520
|
-
import
|
|
3625
|
+
import path29 from "path";
|
|
3521
3626
|
|
|
3522
3627
|
// src/plan-loop/external-review.ts
|
|
3523
|
-
import { spawn as
|
|
3524
|
-
import
|
|
3525
|
-
var CANONICAL_REL =
|
|
3526
|
-
var FALLBACK_REL =
|
|
3628
|
+
import { spawn as spawn5 } from "child_process";
|
|
3629
|
+
import path26 from "path";
|
|
3630
|
+
var CANONICAL_REL = path26.join(".cursor", "scripts", "plan-external-review.sh");
|
|
3631
|
+
var FALLBACK_REL = path26.join("scripts", "plan-external-review.sh");
|
|
3527
3632
|
function isPlanExhaustedReason(reason) {
|
|
3528
3633
|
const r = reason.trim().toLowerCase();
|
|
3529
3634
|
if (!r) return false;
|
|
@@ -3541,12 +3646,12 @@ function shouldArmExternalPlanReview(input) {
|
|
|
3541
3646
|
return false;
|
|
3542
3647
|
}
|
|
3543
3648
|
async function armExternalPlanReview(root, options = {}) {
|
|
3544
|
-
const spawnFn = options.spawnFn ??
|
|
3649
|
+
const spawnFn = options.spawnFn ?? spawn5;
|
|
3545
3650
|
const existsFn = options.existsFn ?? fileExists;
|
|
3546
3651
|
const log = options.log ?? ((line) => console.log(line));
|
|
3547
3652
|
const force = options.force === true;
|
|
3548
|
-
const canonicalPath =
|
|
3549
|
-
const fallbackPath =
|
|
3653
|
+
const canonicalPath = path26.join(root, CANONICAL_REL);
|
|
3654
|
+
const fallbackPath = path26.join(root, FALLBACK_REL);
|
|
3550
3655
|
let scriptPath = null;
|
|
3551
3656
|
let scriptRel = CANONICAL_REL;
|
|
3552
3657
|
if (await existsFn(canonicalPath)) {
|
|
@@ -3563,11 +3668,11 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
3563
3668
|
return { invoked: false, exitCode: null, output: "" };
|
|
3564
3669
|
}
|
|
3565
3670
|
log("Plan exhausted: arming optional external plan review (opt-in handled by script)...");
|
|
3566
|
-
const args = force ? [scriptPath, "--force"] : [scriptPath];
|
|
3671
|
+
const args = force ? [scriptPath, "--force", "--print"] : [scriptPath, "--print"];
|
|
3567
3672
|
return new Promise((resolve) => {
|
|
3568
3673
|
const child = spawnFn("bash", args, {
|
|
3569
3674
|
cwd: root,
|
|
3570
|
-
env: process.env
|
|
3675
|
+
env: { ...process.env, AGENT_KIT_HEADLESS: "1" }
|
|
3571
3676
|
});
|
|
3572
3677
|
let output = "";
|
|
3573
3678
|
const append = (chunk) => {
|
|
@@ -3598,9 +3703,105 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
3598
3703
|
});
|
|
3599
3704
|
}
|
|
3600
3705
|
|
|
3706
|
+
// src/plan-loop/persona-banners.ts
|
|
3707
|
+
import path27 from "path";
|
|
3708
|
+
import {
|
|
3709
|
+
blue,
|
|
3710
|
+
cyan as cyan2,
|
|
3711
|
+
gray,
|
|
3712
|
+
green as green2,
|
|
3713
|
+
lightGray,
|
|
3714
|
+
lightGreen,
|
|
3715
|
+
magenta,
|
|
3716
|
+
red as red2,
|
|
3717
|
+
white,
|
|
3718
|
+
yellow as yellow2
|
|
3719
|
+
} from "kolorist";
|
|
3720
|
+
var CLI_RUN_PLAN_MODE = "cli-run-plan";
|
|
3721
|
+
var DEFAULT_CLI_PERSONA_ID = "ghost-runner";
|
|
3722
|
+
var COLORS = {
|
|
3723
|
+
white,
|
|
3724
|
+
gray,
|
|
3725
|
+
green: green2,
|
|
3726
|
+
cyan: cyan2,
|
|
3727
|
+
magenta,
|
|
3728
|
+
yellow: yellow2,
|
|
3729
|
+
red: red2,
|
|
3730
|
+
blue,
|
|
3731
|
+
"light-green": lightGreen,
|
|
3732
|
+
lightgreen: lightGreen,
|
|
3733
|
+
"light-gray": lightGray,
|
|
3734
|
+
lightgray: lightGray
|
|
3735
|
+
};
|
|
3736
|
+
function resolveColor(name, fallback) {
|
|
3737
|
+
if (!name) return fallback;
|
|
3738
|
+
return COLORS[name.toLowerCase()] ?? fallback;
|
|
3739
|
+
}
|
|
3740
|
+
async function resolveCliPersonaId(root) {
|
|
3741
|
+
try {
|
|
3742
|
+
const cfg = await readJson(
|
|
3743
|
+
path27.join(root, ".cursor", "context", "config.json")
|
|
3744
|
+
);
|
|
3745
|
+
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
3746
|
+
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
3747
|
+
if (typeof id === "string" && id.trim()) return id.trim();
|
|
3748
|
+
} catch {
|
|
3749
|
+
}
|
|
3750
|
+
return DEFAULT_CLI_PERSONA_ID;
|
|
3751
|
+
}
|
|
3752
|
+
async function loadPersonaPack(root, personaId) {
|
|
3753
|
+
try {
|
|
3754
|
+
const personaPath = path27.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
3755
|
+
const pack = await readJson(personaPath);
|
|
3756
|
+
if (!pack || typeof pack.id !== "string") return null;
|
|
3757
|
+
return pack;
|
|
3758
|
+
} catch {
|
|
3759
|
+
return null;
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
async function loadCliRunPlanPersona(root) {
|
|
3763
|
+
const id = await resolveCliPersonaId(root);
|
|
3764
|
+
return loadPersonaPack(root, id);
|
|
3765
|
+
}
|
|
3766
|
+
function createPersonaBannerPrinter(persona) {
|
|
3767
|
+
if (!persona?.cliBanners) return null;
|
|
3768
|
+
const banners = persona.cliBanners;
|
|
3769
|
+
if (!banners.tickStart && !banners.tickEnd && !banners.phaseComplete) return null;
|
|
3770
|
+
const primary = resolveColor(persona.ansiPalette?.primary, white);
|
|
3771
|
+
const secondary = resolveColor(persona.ansiPalette?.secondary, gray);
|
|
3772
|
+
const accent = resolveColor(persona.ansiPalette?.accent, green2);
|
|
3773
|
+
return {
|
|
3774
|
+
tickStart(detail) {
|
|
3775
|
+
if (banners.tickStart) {
|
|
3776
|
+
console.log(`${accent(banners.tickStart)} ${primary(detail)}`);
|
|
3777
|
+
} else {
|
|
3778
|
+
console.log(primary(detail));
|
|
3779
|
+
}
|
|
3780
|
+
},
|
|
3781
|
+
tickEnd(detail) {
|
|
3782
|
+
if (!banners.tickEnd) return;
|
|
3783
|
+
const line = detail ? `${banners.tickEnd} ${detail}` : banners.tickEnd;
|
|
3784
|
+
console.log(secondary(line));
|
|
3785
|
+
},
|
|
3786
|
+
phaseComplete(detail) {
|
|
3787
|
+
if (!banners.phaseComplete) return;
|
|
3788
|
+
const line = detail ? `${banners.phaseComplete} ${detail}` : banners.phaseComplete;
|
|
3789
|
+
console.log(accent(line));
|
|
3790
|
+
},
|
|
3791
|
+
sleep(seconds) {
|
|
3792
|
+
console.log(secondary(`sleep ${seconds}s`));
|
|
3793
|
+
},
|
|
3794
|
+
stop(reason) {
|
|
3795
|
+
if (banners.tickEnd) {
|
|
3796
|
+
console.log(secondary(`${banners.tickEnd} ${reason}`));
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
};
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3601
3802
|
// src/plan-loop/plan-state.ts
|
|
3602
3803
|
import { readFile as readFile12, readdir as readdir3 } from "fs/promises";
|
|
3603
|
-
import
|
|
3804
|
+
import path28 from "path";
|
|
3604
3805
|
function countPendingTodos(raw) {
|
|
3605
3806
|
const lines = raw.split(/\r?\n/);
|
|
3606
3807
|
let inFront = 0;
|
|
@@ -3628,7 +3829,7 @@ function countPendingTodos(raw) {
|
|
|
3628
3829
|
async function findActivePlanFile(plansDir) {
|
|
3629
3830
|
if (!await fileExists(plansDir)) return null;
|
|
3630
3831
|
const files = (await readdir3(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
3631
|
-
return files[0] ?
|
|
3832
|
+
return files[0] ? path28.join(plansDir, files[0]) : null;
|
|
3632
3833
|
}
|
|
3633
3834
|
async function readPlan(planPath) {
|
|
3634
3835
|
return readFile12(planPath, "utf8");
|
|
@@ -3694,101 +3895,6 @@ function formatSentinelLine(sentinel) {
|
|
|
3694
3895
|
return "";
|
|
3695
3896
|
}
|
|
3696
3897
|
|
|
3697
|
-
// src/plan-loop/skin-banners.ts
|
|
3698
|
-
import path27 from "path";
|
|
3699
|
-
import {
|
|
3700
|
-
blue,
|
|
3701
|
-
cyan as cyan2,
|
|
3702
|
-
gray,
|
|
3703
|
-
green as green2,
|
|
3704
|
-
lightGray,
|
|
3705
|
-
lightGreen,
|
|
3706
|
-
magenta,
|
|
3707
|
-
red as red2,
|
|
3708
|
-
white,
|
|
3709
|
-
yellow as yellow2
|
|
3710
|
-
} from "kolorist";
|
|
3711
|
-
var CLI_RUN_PLAN_MODE = "cli-run-plan";
|
|
3712
|
-
var DEFAULT_CLI_SKIN_ID = "ghost-runner";
|
|
3713
|
-
var COLORS = {
|
|
3714
|
-
white,
|
|
3715
|
-
gray,
|
|
3716
|
-
green: green2,
|
|
3717
|
-
cyan: cyan2,
|
|
3718
|
-
magenta,
|
|
3719
|
-
yellow: yellow2,
|
|
3720
|
-
red: red2,
|
|
3721
|
-
blue,
|
|
3722
|
-
"light-green": lightGreen,
|
|
3723
|
-
lightgreen: lightGreen,
|
|
3724
|
-
"light-gray": lightGray,
|
|
3725
|
-
lightgray: lightGray
|
|
3726
|
-
};
|
|
3727
|
-
function resolveColor(name, fallback) {
|
|
3728
|
-
if (!name) return fallback;
|
|
3729
|
-
return COLORS[name.toLowerCase()] ?? fallback;
|
|
3730
|
-
}
|
|
3731
|
-
async function resolveCliSkinId(root) {
|
|
3732
|
-
try {
|
|
3733
|
-
const cfg = await readJson(
|
|
3734
|
-
path27.join(root, ".cursor", "context", "config.json")
|
|
3735
|
-
);
|
|
3736
|
-
const id = cfg?.workspaceSkin?.modes?.[CLI_RUN_PLAN_MODE];
|
|
3737
|
-
if (typeof id === "string" && id.trim()) return id.trim();
|
|
3738
|
-
} catch {
|
|
3739
|
-
}
|
|
3740
|
-
return DEFAULT_CLI_SKIN_ID;
|
|
3741
|
-
}
|
|
3742
|
-
async function loadSkinPack(root, skinId) {
|
|
3743
|
-
try {
|
|
3744
|
-
const skinPath = path27.join(root, "registry", "skins", "core", skinId, "skin.json");
|
|
3745
|
-
const pack = await readJson(skinPath);
|
|
3746
|
-
if (!pack || typeof pack.id !== "string") return null;
|
|
3747
|
-
return pack;
|
|
3748
|
-
} catch {
|
|
3749
|
-
return null;
|
|
3750
|
-
}
|
|
3751
|
-
}
|
|
3752
|
-
async function loadCliRunPlanSkin(root) {
|
|
3753
|
-
const id = await resolveCliSkinId(root);
|
|
3754
|
-
return loadSkinPack(root, id);
|
|
3755
|
-
}
|
|
3756
|
-
function createSkinBannerPrinter(skin) {
|
|
3757
|
-
if (!skin?.cliBanners) return null;
|
|
3758
|
-
const banners = skin.cliBanners;
|
|
3759
|
-
if (!banners.tickStart && !banners.tickEnd && !banners.phaseComplete) return null;
|
|
3760
|
-
const primary = resolveColor(skin.ansiPalette?.primary, white);
|
|
3761
|
-
const secondary = resolveColor(skin.ansiPalette?.secondary, gray);
|
|
3762
|
-
const accent = resolveColor(skin.ansiPalette?.accent, green2);
|
|
3763
|
-
return {
|
|
3764
|
-
tickStart(detail) {
|
|
3765
|
-
if (banners.tickStart) {
|
|
3766
|
-
console.log(`${accent(banners.tickStart)} ${primary(detail)}`);
|
|
3767
|
-
} else {
|
|
3768
|
-
console.log(primary(detail));
|
|
3769
|
-
}
|
|
3770
|
-
},
|
|
3771
|
-
tickEnd(detail) {
|
|
3772
|
-
if (!banners.tickEnd) return;
|
|
3773
|
-
const line = detail ? `${banners.tickEnd} ${detail}` : banners.tickEnd;
|
|
3774
|
-
console.log(secondary(line));
|
|
3775
|
-
},
|
|
3776
|
-
phaseComplete(detail) {
|
|
3777
|
-
if (!banners.phaseComplete) return;
|
|
3778
|
-
const line = detail ? `${banners.phaseComplete} ${detail}` : banners.phaseComplete;
|
|
3779
|
-
console.log(accent(line));
|
|
3780
|
-
},
|
|
3781
|
-
sleep(seconds) {
|
|
3782
|
-
console.log(secondary(`sleep ${seconds}s`));
|
|
3783
|
-
},
|
|
3784
|
-
stop(reason) {
|
|
3785
|
-
if (banners.tickEnd) {
|
|
3786
|
-
console.log(secondary(`${banners.tickEnd} ${reason}`));
|
|
3787
|
-
}
|
|
3788
|
-
}
|
|
3789
|
-
};
|
|
3790
|
-
}
|
|
3791
|
-
|
|
3792
3898
|
// src/plan-loop/run-loop.ts
|
|
3793
3899
|
var TICK_PROMPT = '/run-plan - single tick from the headless runner (agent-kit run-plan). Read .cursor/HANDOFF.md and the active plan in .cursor/plans/. Mark the next to-do as in_progress in the frontmatter, execute ONLY that to-do, mark completed and update HANDOFF. If there is a commitable diff: run /git-staging without asking for confirmation. NEVER /git-prod. Do NOT re-arm an internal Loop skill - the external runner starts the next agent. End the response with exactly one line: "LOOP_TICK_RESULT: continue" if implementable to-dos remain, or "LOOP_TICK_RESULT: stop - <reason>" (plan exhausted, external blocker, or human decision needed).';
|
|
3794
3900
|
function stamp() {
|
|
@@ -3800,9 +3906,9 @@ function sleep(ms) {
|
|
|
3800
3906
|
return new Promise((r) => setTimeout(r, ms));
|
|
3801
3907
|
}
|
|
3802
3908
|
async function runPlanLoop(opts) {
|
|
3803
|
-
const plansDir =
|
|
3804
|
-
const stopFile =
|
|
3805
|
-
const logDir =
|
|
3909
|
+
const plansDir = path29.join(opts.root, ".cursor", "plans");
|
|
3910
|
+
const stopFile = path29.join(opts.root, ".cursor", "loop.stop");
|
|
3911
|
+
const logDir = path29.join(opts.root, ".cursor", "loop-logs");
|
|
3806
3912
|
const planPath = await findActivePlanFile(plansDir);
|
|
3807
3913
|
if (!planPath) {
|
|
3808
3914
|
logger.error("No active plan in .cursor/plans/");
|
|
@@ -3821,13 +3927,13 @@ async function runPlanLoop(opts) {
|
|
|
3821
3927
|
};
|
|
3822
3928
|
process.on("SIGINT", onSigInt);
|
|
3823
3929
|
try {
|
|
3824
|
-
const
|
|
3825
|
-
const banners =
|
|
3826
|
-
console.log(`Active plan: ${
|
|
3930
|
+
const persona = await loadCliRunPlanPersona(opts.root);
|
|
3931
|
+
const banners = createPersonaBannerPrinter(persona);
|
|
3932
|
+
console.log(`Active plan: ${path29.basename(planPath)}`);
|
|
3827
3933
|
console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
|
|
3828
3934
|
console.log(`Backend: ${opts.backend.id}`);
|
|
3829
|
-
if (
|
|
3830
|
-
console.log(`CLI
|
|
3935
|
+
if (persona) {
|
|
3936
|
+
console.log(`CLI persona: ${persona.displayName ?? persona.id}`);
|
|
3831
3937
|
}
|
|
3832
3938
|
if (opts.dryRun) {
|
|
3833
3939
|
console.log("--dry-run: no agent will be started. Tick prompt:");
|
|
@@ -3866,8 +3972,8 @@ async function runPlanLoop(opts) {
|
|
|
3866
3972
|
planExhausted = true;
|
|
3867
3973
|
break;
|
|
3868
3974
|
}
|
|
3869
|
-
const logPath =
|
|
3870
|
-
const relLog =
|
|
3975
|
+
const logPath = path29.join(logDir, `tick-${stamp()}.log`);
|
|
3976
|
+
const relLog = path29.relative(opts.root, logPath);
|
|
3871
3977
|
console.log("");
|
|
3872
3978
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
3873
3979
|
if (banners) banners.tickStart(tickLine);
|
|
@@ -3943,7 +4049,7 @@ async function runPlanLoop(opts) {
|
|
|
3943
4049
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
3944
4050
|
if (banners) banners.phaseComplete(finishDetail);
|
|
3945
4051
|
console.log(
|
|
3946
|
-
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${
|
|
4052
|
+
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path29.relative(opts.root, logDir)}/`
|
|
3947
4053
|
);
|
|
3948
4054
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
3949
4055
|
await armExternalPlanReview(opts.root);
|
|
@@ -3955,7 +4061,7 @@ async function runPlanLoop(opts) {
|
|
|
3955
4061
|
}
|
|
3956
4062
|
|
|
3957
4063
|
// src/commands/run-plan.ts
|
|
3958
|
-
var runPlanCommand =
|
|
4064
|
+
var runPlanCommand = defineCommand10({
|
|
3959
4065
|
meta: {
|
|
3960
4066
|
name: "run-plan",
|
|
3961
4067
|
description: "Headless continuous plan runner: one fresh agent per tick (LOOP_TICK_RESULT contract). Never git-prod."
|
|
@@ -4014,7 +4120,7 @@ var runPlanCommand = defineCommand9({
|
|
|
4014
4120
|
return;
|
|
4015
4121
|
}
|
|
4016
4122
|
const code = await runPlanLoop({
|
|
4017
|
-
root:
|
|
4123
|
+
root: path30.resolve(args.cwd),
|
|
4018
4124
|
maxTicks,
|
|
4019
4125
|
sleepSeconds,
|
|
4020
4126
|
model: args.model ? String(args.model) : void 0,
|
|
@@ -4026,8 +4132,8 @@ var runPlanCommand = defineCommand9({
|
|
|
4026
4132
|
});
|
|
4027
4133
|
|
|
4028
4134
|
// src/commands/scan.ts
|
|
4029
|
-
import { defineCommand as
|
|
4030
|
-
var scanCommand =
|
|
4135
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
4136
|
+
var scanCommand = defineCommand11({
|
|
4031
4137
|
meta: {
|
|
4032
4138
|
name: "scan",
|
|
4033
4139
|
description: "Scan the current repository and print detected profile."
|
|
@@ -4048,8 +4154,8 @@ var scanCommand = defineCommand10({
|
|
|
4048
4154
|
});
|
|
4049
4155
|
|
|
4050
4156
|
// src/commands/status.ts
|
|
4051
|
-
import
|
|
4052
|
-
import { defineCommand as
|
|
4157
|
+
import path31 from "path";
|
|
4158
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
4053
4159
|
function profileStatus(profile) {
|
|
4054
4160
|
if (!profile) return { origin: "none", evidence: [], profile: null };
|
|
4055
4161
|
if ("detection" in profile && profile.detection && typeof profile.detection === "object") {
|
|
@@ -4062,7 +4168,7 @@ function profileStatus(profile) {
|
|
|
4062
4168
|
}
|
|
4063
4169
|
return { origin: "legacy-wizard", evidence: [], profile };
|
|
4064
4170
|
}
|
|
4065
|
-
var statusCommand =
|
|
4171
|
+
var statusCommand = defineCommand12({
|
|
4066
4172
|
meta: {
|
|
4067
4173
|
name: "status",
|
|
4068
4174
|
description: "Show Agent Kit distribution status (manifest + optional wizard profile)."
|
|
@@ -4079,11 +4185,11 @@ var statusCommand = defineCommand11({
|
|
|
4079
4185
|
}
|
|
4080
4186
|
},
|
|
4081
4187
|
async run({ args }) {
|
|
4082
|
-
const rootDir =
|
|
4188
|
+
const rootDir = path31.resolve(args.cwd);
|
|
4083
4189
|
const [manifest, rawProfile, scan] = await Promise.all([
|
|
4084
4190
|
loadAgentKitManifest(rootDir),
|
|
4085
4191
|
readJson(
|
|
4086
|
-
|
|
4192
|
+
path31.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
4087
4193
|
),
|
|
4088
4194
|
runScanner(rootDir)
|
|
4089
4195
|
]);
|
|
@@ -4138,20 +4244,297 @@ var statusCommand = defineCommand11({
|
|
|
4138
4244
|
});
|
|
4139
4245
|
|
|
4140
4246
|
// src/commands/update.ts
|
|
4141
|
-
import { defineCommand as
|
|
4142
|
-
|
|
4247
|
+
import { defineCommand as defineCommand13 } from "citty";
|
|
4248
|
+
|
|
4249
|
+
// src/lifecycle/check-updates.ts
|
|
4250
|
+
import { execFile as execFile3 } from "child_process";
|
|
4251
|
+
import path32 from "path";
|
|
4252
|
+
import { promisify as promisify3 } from "util";
|
|
4253
|
+
var execFileAsync2 = promisify3(execFile3);
|
|
4254
|
+
var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i;
|
|
4255
|
+
var FACTORY_URL_MARKERS = ["agent-kit-dev"];
|
|
4256
|
+
var FACTORY_REFS = /* @__PURE__ */ new Set(["staging", "homologacao", "develop", "dev"]);
|
|
4257
|
+
var DEFAULT_UPDATE_CHECK = {
|
|
4258
|
+
enabled: false,
|
|
4259
|
+
intervalDays: 7,
|
|
4260
|
+
lastCheckedAt: null
|
|
4261
|
+
};
|
|
4262
|
+
function gitEnv2() {
|
|
4263
|
+
return { ...process.env, GIT_ALLOW_PROTOCOL: "https" };
|
|
4264
|
+
}
|
|
4265
|
+
function compareSemver(a, b) {
|
|
4266
|
+
const pa = parseSemverParts(a);
|
|
4267
|
+
const pb = parseSemverParts(b);
|
|
4268
|
+
if (!pa || !pb) {
|
|
4269
|
+
throw new Error(`Invalid semver for compare: "${a}" vs "${b}"`);
|
|
4270
|
+
}
|
|
4271
|
+
for (let i = 0; i < 3; i++) {
|
|
4272
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
4273
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
4274
|
+
}
|
|
4275
|
+
return 0;
|
|
4276
|
+
}
|
|
4277
|
+
function parseSemverParts(version) {
|
|
4278
|
+
const m = SEMVER_CORE.exec(version.trim());
|
|
4279
|
+
if (!m) return null;
|
|
4280
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
4281
|
+
}
|
|
4282
|
+
function normalizeSemver(version) {
|
|
4283
|
+
const parts = parseSemverParts(version);
|
|
4284
|
+
if (!parts) return null;
|
|
4285
|
+
return `${parts[0]}.${parts[1]}.${parts[2]}`;
|
|
4286
|
+
}
|
|
4287
|
+
function isFactoryOrDevRegistry(url, ref) {
|
|
4288
|
+
if (url) {
|
|
4289
|
+
const lower = url.toLowerCase();
|
|
4290
|
+
if (FACTORY_URL_MARKERS.some((m) => lower.includes(m))) return true;
|
|
4291
|
+
}
|
|
4292
|
+
if (ref && FACTORY_REFS.has(ref.toLowerCase())) return true;
|
|
4293
|
+
return false;
|
|
4294
|
+
}
|
|
4295
|
+
function pickLatestSemverTag(lsRemoteStdout) {
|
|
4296
|
+
let best = null;
|
|
4297
|
+
for (const line of lsRemoteStdout.split("\n")) {
|
|
4298
|
+
const trimmed = line.trim();
|
|
4299
|
+
if (!trimmed) continue;
|
|
4300
|
+
const ref = trimmed.split(/\s+/)[1];
|
|
4301
|
+
if (!ref?.startsWith("refs/tags/")) continue;
|
|
4302
|
+
if (ref.endsWith("^{}")) continue;
|
|
4303
|
+
const tag = ref.slice("refs/tags/".length);
|
|
4304
|
+
const norm = normalizeSemver(tag);
|
|
4305
|
+
if (!norm) continue;
|
|
4306
|
+
if (!best || compareSemver(norm, best) > 0) best = norm;
|
|
4307
|
+
}
|
|
4308
|
+
return best;
|
|
4309
|
+
}
|
|
4310
|
+
async function fetchLatestPublicVersion(registryUrl = DEFAULT_REGISTRY_URL) {
|
|
4311
|
+
assertSafeRegistrySource(registryUrl, "main");
|
|
4312
|
+
const { stdout } = await execFileAsync2("git", ["ls-remote", "--tags", "--", registryUrl], {
|
|
4313
|
+
env: gitEnv2(),
|
|
4314
|
+
timeout: 2e4
|
|
4315
|
+
});
|
|
4316
|
+
return pickLatestSemverTag(stdout);
|
|
4317
|
+
}
|
|
4318
|
+
function readUpdateCheckPrefs(config) {
|
|
4319
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
4320
|
+
return { ...DEFAULT_UPDATE_CHECK };
|
|
4321
|
+
}
|
|
4322
|
+
const raw = config.updateCheck;
|
|
4323
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
4324
|
+
return { ...DEFAULT_UPDATE_CHECK };
|
|
4325
|
+
}
|
|
4326
|
+
const uc = raw;
|
|
4327
|
+
const intervalDays = typeof uc.intervalDays === "number" && Number.isInteger(uc.intervalDays) && uc.intervalDays >= 1 ? uc.intervalDays : DEFAULT_UPDATE_CHECK.intervalDays;
|
|
4328
|
+
return {
|
|
4329
|
+
enabled: uc.enabled === true,
|
|
4330
|
+
intervalDays,
|
|
4331
|
+
lastCheckedAt: typeof uc.lastCheckedAt === "string" ? uc.lastCheckedAt : null
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
function intervalElapsed(lastCheckedAt, intervalDays) {
|
|
4335
|
+
if (!lastCheckedAt) return true;
|
|
4336
|
+
const last = Date.parse(lastCheckedAt);
|
|
4337
|
+
if (Number.isNaN(last)) return true;
|
|
4338
|
+
const ms = intervalDays * 24 * 60 * 60 * 1e3;
|
|
4339
|
+
return Date.now() - last >= ms;
|
|
4340
|
+
}
|
|
4341
|
+
async function loadContextConfig(cwd) {
|
|
4342
|
+
const configPath = path32.join(cwd, ".cursor", "context", "config.json");
|
|
4343
|
+
return readJson(configPath);
|
|
4344
|
+
}
|
|
4345
|
+
async function stampLastCheckedAt(cwd) {
|
|
4346
|
+
const configPath = path32.join(cwd, ".cursor", "context", "config.json");
|
|
4347
|
+
const existing = await loadContextConfig(cwd) ?? {};
|
|
4348
|
+
const prev = existing.updateCheck && typeof existing.updateCheck === "object" ? { ...existing.updateCheck } : {};
|
|
4349
|
+
existing.updateCheck = {
|
|
4350
|
+
...DEFAULT_UPDATE_CHECK,
|
|
4351
|
+
...prev,
|
|
4352
|
+
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4353
|
+
};
|
|
4354
|
+
await writeJson(configPath, existing);
|
|
4355
|
+
}
|
|
4356
|
+
async function checkForUpdates(cwd, options = {}) {
|
|
4357
|
+
const manifest = await loadAgentKitManifest(cwd);
|
|
4358
|
+
if (!manifest) {
|
|
4359
|
+
return {
|
|
4360
|
+
status: "skipped-no-manifest",
|
|
4361
|
+
installedVersion: null,
|
|
4362
|
+
latestVersion: null,
|
|
4363
|
+
registryUrl: null,
|
|
4364
|
+
registryRef: null,
|
|
4365
|
+
applyRecommended: false,
|
|
4366
|
+
message: "No .cursor/agent-kit.json \u2014 run agent-kit install first."
|
|
4367
|
+
};
|
|
4368
|
+
}
|
|
4369
|
+
const registryUrl = manifest.registry?.url ?? null;
|
|
4370
|
+
const registryRef = manifest.registry?.ref ?? null;
|
|
4371
|
+
if (isFactoryOrDevRegistry(registryUrl, registryRef)) {
|
|
4372
|
+
return {
|
|
4373
|
+
status: "skipped-factory",
|
|
4374
|
+
installedVersion: manifest.version,
|
|
4375
|
+
latestVersion: null,
|
|
4376
|
+
registryUrl,
|
|
4377
|
+
registryRef,
|
|
4378
|
+
applyRecommended: false,
|
|
4379
|
+
message: "Factory/dev registry detected \u2014 skipping public update check. Use /update only with explicit consumer intent."
|
|
4380
|
+
};
|
|
4381
|
+
}
|
|
4382
|
+
if (options.respectPrefs) {
|
|
4383
|
+
const config = await loadContextConfig(cwd);
|
|
4384
|
+
const prefs = readUpdateCheckPrefs(config);
|
|
4385
|
+
if (!prefs.enabled) {
|
|
4386
|
+
return {
|
|
4387
|
+
status: "skipped-disabled",
|
|
4388
|
+
installedVersion: manifest.version,
|
|
4389
|
+
latestVersion: null,
|
|
4390
|
+
registryUrl,
|
|
4391
|
+
registryRef,
|
|
4392
|
+
applyRecommended: false,
|
|
4393
|
+
message: "updateCheck.enabled is false (opt-in). Set true in .cursor/context/config.json to nudge."
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
4396
|
+
if (!intervalElapsed(prefs.lastCheckedAt, prefs.intervalDays)) {
|
|
4397
|
+
return {
|
|
4398
|
+
status: "skipped-interval",
|
|
4399
|
+
installedVersion: manifest.version,
|
|
4400
|
+
latestVersion: null,
|
|
4401
|
+
registryUrl,
|
|
4402
|
+
registryRef,
|
|
4403
|
+
applyRecommended: false,
|
|
4404
|
+
message: `Within updateCheck.intervalDays (${prefs.intervalDays}); last check ${prefs.lastCheckedAt}.`
|
|
4405
|
+
};
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
let latest;
|
|
4409
|
+
try {
|
|
4410
|
+
latest = options.latestVersion !== void 0 ? options.latestVersion : await fetchLatestPublicVersion(options.publicRegistryUrl ?? DEFAULT_REGISTRY_URL);
|
|
4411
|
+
} catch (err) {
|
|
4412
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4413
|
+
return {
|
|
4414
|
+
status: "error",
|
|
4415
|
+
installedVersion: manifest.version,
|
|
4416
|
+
latestVersion: null,
|
|
4417
|
+
registryUrl,
|
|
4418
|
+
registryRef,
|
|
4419
|
+
applyRecommended: false,
|
|
4420
|
+
message: `Failed to fetch public tags: ${msg}`
|
|
4421
|
+
};
|
|
4422
|
+
}
|
|
4423
|
+
if (options.stamp) {
|
|
4424
|
+
try {
|
|
4425
|
+
await stampLastCheckedAt(cwd);
|
|
4426
|
+
} catch {
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
const installed = normalizeSemver(manifest.version);
|
|
4430
|
+
if (!installed) {
|
|
4431
|
+
return {
|
|
4432
|
+
status: "error",
|
|
4433
|
+
installedVersion: manifest.version,
|
|
4434
|
+
latestVersion: latest,
|
|
4435
|
+
registryUrl,
|
|
4436
|
+
registryRef,
|
|
4437
|
+
applyRecommended: false,
|
|
4438
|
+
message: `Installed version "${manifest.version}" is not valid semver.`
|
|
4439
|
+
};
|
|
4440
|
+
}
|
|
4441
|
+
if (!latest) {
|
|
4442
|
+
return {
|
|
4443
|
+
status: "error",
|
|
4444
|
+
installedVersion: installed,
|
|
4445
|
+
latestVersion: null,
|
|
4446
|
+
registryUrl,
|
|
4447
|
+
registryRef,
|
|
4448
|
+
applyRecommended: false,
|
|
4449
|
+
message: "No semver tags found on the public registry."
|
|
4450
|
+
};
|
|
4451
|
+
}
|
|
4452
|
+
const cmp = compareSemver(installed, latest);
|
|
4453
|
+
if (cmp === 0) {
|
|
4454
|
+
return {
|
|
4455
|
+
status: "up-to-date",
|
|
4456
|
+
installedVersion: installed,
|
|
4457
|
+
latestVersion: latest,
|
|
4458
|
+
registryUrl,
|
|
4459
|
+
registryRef,
|
|
4460
|
+
applyRecommended: false,
|
|
4461
|
+
message: `Installed v${installed} matches latest public v${latest}.`
|
|
4462
|
+
};
|
|
4463
|
+
}
|
|
4464
|
+
if (cmp < 0) {
|
|
4465
|
+
return {
|
|
4466
|
+
status: "update-available",
|
|
4467
|
+
installedVersion: installed,
|
|
4468
|
+
latestVersion: latest,
|
|
4469
|
+
registryUrl,
|
|
4470
|
+
registryRef,
|
|
4471
|
+
applyRecommended: false,
|
|
4472
|
+
message: `Update available: v${installed} \u2192 v${latest}. Run /update (Ask confirm) to apply; never silent.`
|
|
4473
|
+
};
|
|
4474
|
+
}
|
|
4475
|
+
return {
|
|
4476
|
+
status: "ahead-of-public",
|
|
4477
|
+
installedVersion: installed,
|
|
4478
|
+
latestVersion: latest,
|
|
4479
|
+
registryUrl,
|
|
4480
|
+
registryRef,
|
|
4481
|
+
applyRecommended: false,
|
|
4482
|
+
message: `Installed v${installed} is ahead of latest public v${latest} (dev/local build).`
|
|
4483
|
+
};
|
|
4484
|
+
}
|
|
4485
|
+
|
|
4486
|
+
// src/commands/update.ts
|
|
4487
|
+
var updateCommand = defineCommand13({
|
|
4143
4488
|
meta: {
|
|
4144
4489
|
name: "update",
|
|
4145
|
-
description: "Re-apply L0/packs/skills from the registry; never overwrites L3 protected paths."
|
|
4490
|
+
description: "Re-apply L0/packs/skills from the registry; never overwrites L3 protected paths. Use --check for notify-only."
|
|
4146
4491
|
},
|
|
4147
4492
|
args: {
|
|
4148
4493
|
cwd: {
|
|
4149
4494
|
type: "string",
|
|
4150
4495
|
default: process.cwd()
|
|
4151
4496
|
},
|
|
4497
|
+
check: {
|
|
4498
|
+
type: "boolean",
|
|
4499
|
+
description: "Check-only: compare installed version to latest public tag; never apply L0 writes",
|
|
4500
|
+
default: false
|
|
4501
|
+
},
|
|
4502
|
+
json: {
|
|
4503
|
+
type: "boolean",
|
|
4504
|
+
description: "Print machine-readable JSON (with --check)",
|
|
4505
|
+
default: false
|
|
4506
|
+
},
|
|
4507
|
+
"respect-prefs": {
|
|
4508
|
+
type: "boolean",
|
|
4509
|
+
description: "Honor updateCheck.enabled and intervalDays from .cursor/context/config.json (hooks)",
|
|
4510
|
+
default: false
|
|
4511
|
+
},
|
|
4512
|
+
stamp: {
|
|
4513
|
+
type: "boolean",
|
|
4514
|
+
description: "Persist updateCheck.lastCheckedAt after a network check",
|
|
4515
|
+
default: false
|
|
4516
|
+
},
|
|
4152
4517
|
...REGISTRY_CLI_ARGS
|
|
4153
4518
|
},
|
|
4154
4519
|
async run({ args }) {
|
|
4520
|
+
if (args.check) {
|
|
4521
|
+
const result = await checkForUpdates(args.cwd, {
|
|
4522
|
+
respectPrefs: Boolean(args["respect-prefs"]),
|
|
4523
|
+
stamp: Boolean(args.stamp),
|
|
4524
|
+
publicRegistryUrl: typeof args.url === "string" && args.url ? args.url : void 0
|
|
4525
|
+
});
|
|
4526
|
+
if (args.json) {
|
|
4527
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4528
|
+
} else {
|
|
4529
|
+
const line = `[${result.status}] ${result.message}`;
|
|
4530
|
+
if (result.status === "error") logger.error(line);
|
|
4531
|
+
else if (result.status === "update-available") logger.warn(line);
|
|
4532
|
+
else if (result.status.startsWith("skipped-")) logger.warn(line);
|
|
4533
|
+
else logger.info(line);
|
|
4534
|
+
}
|
|
4535
|
+
if (result.status === "error") process.exitCode = 2;
|
|
4536
|
+
return;
|
|
4537
|
+
}
|
|
4155
4538
|
const existing = await loadAgentKitManifest(args.cwd);
|
|
4156
4539
|
if (!existing) {
|
|
4157
4540
|
logger.warn("No .cursor/agent-kit.json \u2014 run agent-kit install first.");
|
|
@@ -4184,7 +4567,7 @@ var updateCommand = defineCommand12({
|
|
|
4184
4567
|
});
|
|
4185
4568
|
|
|
4186
4569
|
// src/index.ts
|
|
4187
|
-
var main =
|
|
4570
|
+
var main = defineCommand14({
|
|
4188
4571
|
meta: {
|
|
4189
4572
|
name: "agent-kit",
|
|
4190
4573
|
description: "HITL framework for AI-assisted IDEs"
|
|
@@ -4201,7 +4584,8 @@ var main = defineCommand13({
|
|
|
4201
4584
|
contribute: contributeCommand,
|
|
4202
4585
|
handoff: handoffCommand,
|
|
4203
4586
|
"run-plan": runPlanCommand,
|
|
4204
|
-
dashboard: dashboardCommand
|
|
4587
|
+
dashboard: dashboardCommand,
|
|
4588
|
+
"dashboard-broadcast": dashboardBroadcastCommand
|
|
4205
4589
|
}
|
|
4206
4590
|
});
|
|
4207
4591
|
runMain(main);
|