@dadado/agent-kit-cli 5.3.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dashboard/README.md +25 -0
- package/dashboard/dashboard-data.mjs +72 -1
- package/dashboard/dashboard.html +19 -1
- package/dashboard/lib/guards.mjs +4 -4
- package/dashboard/lib/triage-heading.mjs +1 -2
- package/dist/index.js +1301 -296
- 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 defineCommand20, runMain, showUsage } from "citty";
|
|
5
5
|
|
|
6
6
|
// src/commands/add.ts
|
|
7
7
|
import { defineCommand } from "citty";
|
|
@@ -214,7 +214,12 @@ var KNOWN_SHIPPED_OVERLAY_HASHES = /* @__PURE__ */ new Set([
|
|
|
214
214
|
"73cd50bac290b84df6245d3647e686dee8ec3b0434736d08fdba6e30d342ef0a",
|
|
215
215
|
"61b635ea8a08171062bd329a4615d7426eb787eb796271aa882f55f39db56810",
|
|
216
216
|
"86afbea8f64de68a79ad5e374f3132bdbe2582b94321fb3d438314838e36c776",
|
|
217
|
-
"65cc1c0293b145e48ed73ad0ca9ab33cbba5ed834a5bfb31f195ed30c2f143df"
|
|
217
|
+
"65cc1c0293b145e48ed73ad0ca9ab33cbba5ed834a5bfb31f195ed30c2f143df",
|
|
218
|
+
"f04fcfe31354d1b09aeb256a17e4aab91c98ea48a5cff25e4a0281af3cfb289f",
|
|
219
|
+
"34e559ad9036d93d9cbc96d394bc2bbeb10ce50ead00d58a914158ae19daa76c",
|
|
220
|
+
"359a142aeadd769a9b89ac909ae5129940e7e6b0892b70256098c584d97631ce",
|
|
221
|
+
"8eab94f7a78149db1bbbc0fd45f21dc2d874209d534bcff39077fd6e1d2c42fb",
|
|
222
|
+
"9be406f92f7dca71f3af814b14fb26b011f9f12c37c789f7ddc3d647b1a7ab60"
|
|
218
223
|
]);
|
|
219
224
|
|
|
220
225
|
// src/lifecycle/paths.ts
|
|
@@ -237,7 +242,8 @@ var MANAGED_HASHES_REL = ".cursor/agent-kit.managed-hashes.json";
|
|
|
237
242
|
var CONSUMER_OVERLAY_PREFIXES = [
|
|
238
243
|
".cursor/agents/",
|
|
239
244
|
".cursor/skills/",
|
|
240
|
-
".cursor/commands/"
|
|
245
|
+
".cursor/commands/",
|
|
246
|
+
".claude/commands/"
|
|
241
247
|
];
|
|
242
248
|
function isConsumerOverlayPath(relPath) {
|
|
243
249
|
const norm = relPath.split(path3.sep).join("/");
|
|
@@ -740,8 +746,8 @@ async function hasRegistryIndex(root) {
|
|
|
740
746
|
return fileExists(path5.join(root, "registry", "registry.json"));
|
|
741
747
|
}
|
|
742
748
|
async function cloneRegistry(url, ref, dest) {
|
|
743
|
-
const { mkdir:
|
|
744
|
-
await
|
|
749
|
+
const { mkdir: mkdir8 } = await import("fs/promises");
|
|
750
|
+
await mkdir8(path5.dirname(dest), { recursive: true });
|
|
745
751
|
try {
|
|
746
752
|
await execFileAsync("git", ["clone", "--depth", "1", "--branch", ref, "--", url, dest], {
|
|
747
753
|
env: gitEnv()
|
|
@@ -1088,17 +1094,38 @@ async function findPack(rootDir, packId) {
|
|
|
1088
1094
|
}
|
|
1089
1095
|
|
|
1090
1096
|
// src/registry/install.ts
|
|
1091
|
-
import { readFile as readFile5 } from "fs/promises";
|
|
1097
|
+
import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
|
|
1092
1098
|
import path8 from "path";
|
|
1099
|
+
function skillTargetDir(skillPath, skillId) {
|
|
1100
|
+
const category = skillPath.includes("/core/") ? "core" : "community";
|
|
1101
|
+
return path8.posix.join(".cursor", "skills", category, skillId);
|
|
1102
|
+
}
|
|
1103
|
+
async function skillFileTargets(registryRoot, skillPath, skillId) {
|
|
1104
|
+
const targetDir = skillTargetDir(skillPath, skillId);
|
|
1105
|
+
const pair = (rel) => ({
|
|
1106
|
+
sourceRel: path8.posix.join(skillPath, rel),
|
|
1107
|
+
targetRel: path8.posix.join(targetDir, rel)
|
|
1108
|
+
});
|
|
1109
|
+
let companions = [];
|
|
1110
|
+
try {
|
|
1111
|
+
const dirAbs = resolveContained(registryRoot, skillPath);
|
|
1112
|
+
const entries = await readdir3(dirAbs, { withFileTypes: true, recursive: true });
|
|
1113
|
+
companions = entries.filter((entry) => entry.isFile()).map((entry) => {
|
|
1114
|
+
const parent = path8.relative(dirAbs, entry.parentPath ?? dirAbs);
|
|
1115
|
+
return path8.join(parent, entry.name).split(path8.sep).join("/");
|
|
1116
|
+
}).filter((rel) => rel !== "SKILL.md" && !rel.split("/").some((seg) => seg.startsWith("."))).sort();
|
|
1117
|
+
} catch {
|
|
1118
|
+
companions = [];
|
|
1119
|
+
}
|
|
1120
|
+
return [pair("SKILL.md"), ...companions.map(pair)];
|
|
1121
|
+
}
|
|
1093
1122
|
function targetForMember(member) {
|
|
1094
1123
|
switch (member.kind) {
|
|
1095
|
-
case "skill":
|
|
1096
|
-
const category = member.source.includes("/core/") ? "core" : "community";
|
|
1124
|
+
case "skill":
|
|
1097
1125
|
return {
|
|
1098
1126
|
sourceRel: path8.posix.join(member.source, "SKILL.md"),
|
|
1099
|
-
targetRel: path8.posix.join(
|
|
1127
|
+
targetRel: path8.posix.join(skillTargetDir(member.source, member.id), "SKILL.md")
|
|
1100
1128
|
};
|
|
1101
|
-
}
|
|
1102
1129
|
case "rule":
|
|
1103
1130
|
return {
|
|
1104
1131
|
sourceRel: member.source,
|
|
@@ -1139,19 +1166,22 @@ function targetForMember(member) {
|
|
|
1139
1166
|
}
|
|
1140
1167
|
async function installSkill(registryRoot, projectRoot, skill, options = {}) {
|
|
1141
1168
|
const stats = emptyStats();
|
|
1142
|
-
const category = skill.path.includes("/core/") ? "core" : "community";
|
|
1143
|
-
const sourceRel = path8.posix.join(skill.path, "SKILL.md");
|
|
1144
|
-
const targetRel = path8.posix.join(".cursor", "skills", category, skill.id, "SKILL.md");
|
|
1145
1169
|
const managedHashes = await loadManagedHashLedger(projectRoot);
|
|
1146
|
-
const
|
|
1170
|
+
for (const { sourceRel, targetRel } of await skillFileTargets(
|
|
1147
1171
|
registryRoot,
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1172
|
+
skill.path,
|
|
1173
|
+
skill.id
|
|
1174
|
+
)) {
|
|
1175
|
+
const outcome = await copyRegistryFile(
|
|
1176
|
+
registryRoot,
|
|
1177
|
+
projectRoot,
|
|
1178
|
+
sourceRel,
|
|
1179
|
+
targetRel,
|
|
1180
|
+
options.protectedGlobs ?? [],
|
|
1181
|
+
{ managedHashes, persistManagedHashes: false }
|
|
1182
|
+
);
|
|
1183
|
+
recordOutcome(stats, targetRel, outcome);
|
|
1184
|
+
}
|
|
1155
1185
|
await saveManagedHashLedger(projectRoot, managedHashes);
|
|
1156
1186
|
return stats;
|
|
1157
1187
|
}
|
|
@@ -1185,16 +1215,18 @@ async function installPack(registryRoot, projectRoot, packId, options = {}) {
|
|
|
1185
1215
|
const managedHashes = await loadManagedHashLedger(projectRoot);
|
|
1186
1216
|
const copyOpts = { managedHashes, persistManagedHashes: false };
|
|
1187
1217
|
for (const member of packManifest.members) {
|
|
1188
|
-
const
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1218
|
+
const pairs = member.kind === "skill" ? await skillFileTargets(registryRoot, member.source, member.id) : [targetForMember(member)];
|
|
1219
|
+
for (const { sourceRel, targetRel } of pairs) {
|
|
1220
|
+
const outcome = await copyRegistryFile(
|
|
1221
|
+
registryRoot,
|
|
1222
|
+
projectRoot,
|
|
1223
|
+
sourceRel,
|
|
1224
|
+
targetRel,
|
|
1225
|
+
protectedGlobs,
|
|
1226
|
+
copyOpts
|
|
1227
|
+
);
|
|
1228
|
+
recordOutcome(stats, targetRel, outcome);
|
|
1229
|
+
}
|
|
1198
1230
|
}
|
|
1199
1231
|
await saveManagedHashLedger(projectRoot, managedHashes);
|
|
1200
1232
|
return stats;
|
|
@@ -1620,8 +1652,10 @@ async function buildRegistryPathMap(registryRoot, manifest) {
|
|
|
1620
1652
|
for (const packId of manifest.packs ?? []) {
|
|
1621
1653
|
const pack = await loadPackManifest(registryRoot, packId);
|
|
1622
1654
|
for (const member of pack.members) {
|
|
1623
|
-
const
|
|
1624
|
-
|
|
1655
|
+
const pairs = member.kind === "skill" ? await skillFileTargets(registryRoot, member.source, member.id) : [packMemberTargets(member)];
|
|
1656
|
+
for (const { sourceRel, targetRel } of pairs) {
|
|
1657
|
+
map.set(targetRel.split(path9.sep).join("/"), sourceRel.split(path9.sep).join("/"));
|
|
1658
|
+
}
|
|
1625
1659
|
}
|
|
1626
1660
|
}
|
|
1627
1661
|
if ((manifest.skills ?? []).length > 0) {
|
|
@@ -1630,25 +1664,28 @@ async function buildRegistryPathMap(registryRoot, manifest) {
|
|
|
1630
1664
|
for (const id of manifest.skills ?? []) {
|
|
1631
1665
|
const skill = pool.find((s) => s.id === id);
|
|
1632
1666
|
if (!skill) continue;
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1667
|
+
for (const { sourceRel, targetRel } of await skillFileTargets(
|
|
1668
|
+
registryRoot,
|
|
1669
|
+
skill.path,
|
|
1670
|
+
skill.id
|
|
1671
|
+
)) {
|
|
1672
|
+
map.set(targetRel, sourceRel);
|
|
1673
|
+
}
|
|
1637
1674
|
}
|
|
1638
1675
|
}
|
|
1639
1676
|
return map;
|
|
1640
1677
|
}
|
|
1641
1678
|
function guessRegistryPath(projectRel) {
|
|
1642
1679
|
const p = projectRel.split(path9.sep).join("/");
|
|
1643
|
-
if (p.startsWith(".cursor/skills/")
|
|
1680
|
+
if (p.startsWith(".cursor/skills/")) {
|
|
1644
1681
|
const rest = p.slice(".cursor/skills/".length);
|
|
1645
1682
|
const parts = rest.split("/");
|
|
1646
|
-
if (parts.length
|
|
1683
|
+
if (parts.length >= 3 && (parts[0] === "core" || parts[0] === "community")) {
|
|
1647
1684
|
return path9.posix.join("registry/skills", rest);
|
|
1648
1685
|
}
|
|
1649
1686
|
const skillId = parts[0];
|
|
1650
|
-
if (parts.length
|
|
1651
|
-
return path9.posix.join("registry/skills", "community",
|
|
1687
|
+
if (parts.length >= 2 && skillId) {
|
|
1688
|
+
return path9.posix.join("registry/skills", "community", rest);
|
|
1652
1689
|
}
|
|
1653
1690
|
}
|
|
1654
1691
|
if (p.startsWith(".cursor/rules/")) {
|
|
@@ -2350,7 +2387,7 @@ var CONFIG_REVIEW_PREFLIGHT = Object.freeze(["off", "warn", "block"]);
|
|
|
2350
2387
|
function resolveContextConfigPath(repoRoot, fsHooks = {}) {
|
|
2351
2388
|
const exists2 = fsHooks.existsSync;
|
|
2352
2389
|
const realpath = fsHooks.realpathSync;
|
|
2353
|
-
const
|
|
2390
|
+
const mkdir8 = fsHooks.mkdirSync;
|
|
2354
2391
|
if (typeof repoRoot !== "string" || !repoRoot) {
|
|
2355
2392
|
return { ok: false, error: "invalid repo root" };
|
|
2356
2393
|
}
|
|
@@ -2361,8 +2398,8 @@ function resolveContextConfigPath(repoRoot, fsHooks = {}) {
|
|
|
2361
2398
|
return { ok: false, error: "path escape" };
|
|
2362
2399
|
}
|
|
2363
2400
|
try {
|
|
2364
|
-
if (typeof
|
|
2365
|
-
|
|
2401
|
+
if (typeof mkdir8 === "function" && typeof exists2 === "function" && !exists2(contextDir)) {
|
|
2402
|
+
mkdir8(contextDir, { recursive: true });
|
|
2366
2403
|
}
|
|
2367
2404
|
if (typeof realpath === "function" && typeof exists2 === "function" && exists2(abs)) {
|
|
2368
2405
|
const fileReal = String(realpath(abs)).replace(/\\/g, "/");
|
|
@@ -2419,13 +2456,13 @@ function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
|
|
|
2419
2456
|
}
|
|
2420
2457
|
|
|
2421
2458
|
// src/commands/dashboard.ts
|
|
2422
|
-
function readPreferredBrowserFromWorkspace(cwd,
|
|
2459
|
+
function readPreferredBrowserFromWorkspace(cwd, readFile26 = readFileSync2) {
|
|
2423
2460
|
const resolved = resolveContextConfigPath(path11.resolve(cwd), {
|
|
2424
2461
|
existsSync,
|
|
2425
2462
|
realpathSync
|
|
2426
2463
|
});
|
|
2427
2464
|
if (!resolved.ok) return null;
|
|
2428
|
-
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync:
|
|
2465
|
+
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync: readFile26 });
|
|
2429
2466
|
return normalizePreferredBrowser(value);
|
|
2430
2467
|
}
|
|
2431
2468
|
function applyDashboardOpenEnv(env, opts) {
|
|
@@ -2751,8 +2788,10 @@ async function diffAgainstRegistry(registryRoot, projectRoot, manifest) {
|
|
|
2751
2788
|
for (const packId of manifest.packs ?? []) {
|
|
2752
2789
|
const pack = await loadPackManifest(registryRoot, packId);
|
|
2753
2790
|
for (const member of pack.members) {
|
|
2754
|
-
const
|
|
2755
|
-
|
|
2791
|
+
const pairs = member.kind === "skill" ? await skillFileTargets(registryRoot, member.source, member.id) : [packMemberTargets(member)];
|
|
2792
|
+
for (const { sourceRel, targetRel } of pairs) {
|
|
2793
|
+
await pushUnique(sourceRel, targetRel);
|
|
2794
|
+
}
|
|
2756
2795
|
}
|
|
2757
2796
|
}
|
|
2758
2797
|
if ((manifest.skills ?? []).length > 0) {
|
|
@@ -2764,10 +2803,13 @@ async function diffAgainstRegistry(registryRoot, projectRoot, manifest) {
|
|
|
2764
2803
|
entries.push({ path: `skill:${id}`, status: "missing-registry" });
|
|
2765
2804
|
continue;
|
|
2766
2805
|
}
|
|
2767
|
-
const
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2806
|
+
for (const { sourceRel, targetRel } of await skillFileTargets(
|
|
2807
|
+
registryRoot,
|
|
2808
|
+
skill.path,
|
|
2809
|
+
skill.id
|
|
2810
|
+
)) {
|
|
2811
|
+
await pushUnique(sourceRel, targetRel);
|
|
2812
|
+
}
|
|
2771
2813
|
}
|
|
2772
2814
|
}
|
|
2773
2815
|
return entries;
|
|
@@ -2838,12 +2880,12 @@ var diffCommand = defineCommand6({
|
|
|
2838
2880
|
});
|
|
2839
2881
|
|
|
2840
2882
|
// src/commands/doctor.ts
|
|
2841
|
-
import
|
|
2883
|
+
import path25 from "path";
|
|
2842
2884
|
import { defineCommand as defineCommand7 } from "citty";
|
|
2843
2885
|
|
|
2844
2886
|
// src/invariants/hooks-health.ts
|
|
2845
2887
|
import { execFile as execFile2 } from "child_process";
|
|
2846
|
-
import { constants as constants2, access as access5, readFile as readFile9, readdir as
|
|
2888
|
+
import { constants as constants2, access as access5, readFile as readFile9, readdir as readdir4, stat as stat2 } from "fs/promises";
|
|
2847
2889
|
import path14 from "path";
|
|
2848
2890
|
import { promisify as promisify2 } from "util";
|
|
2849
2891
|
var execFileAsync2 = promisify2(execFile2);
|
|
@@ -2927,7 +2969,7 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
2927
2969
|
const installHint = "Install or refresh with: `cp git-hooks/<name> .git/hooks/<name> && chmod +x .git/hooks/<name>` (see git-hooks/README.md)";
|
|
2928
2970
|
let names = [...GIT_HOOK_CANONICAL_NAMES];
|
|
2929
2971
|
try {
|
|
2930
|
-
const listed = await
|
|
2972
|
+
const listed = await readdir4(canonicalDir);
|
|
2931
2973
|
const fromDisk = listed.filter(
|
|
2932
2974
|
(n) => GIT_HOOK_CANONICAL_NAMES.includes(n)
|
|
2933
2975
|
);
|
|
@@ -3054,6 +3096,142 @@ async function assessHooksHealth(rootDir) {
|
|
|
3054
3096
|
};
|
|
3055
3097
|
}
|
|
3056
3098
|
|
|
3099
|
+
// src/readiness/env-checks.ts
|
|
3100
|
+
import { constants as constants3, access as access6, readFile as readFile10, stat as stat3 } from "fs/promises";
|
|
3101
|
+
import { homedir as homedir2 } from "os";
|
|
3102
|
+
import path15 from "path";
|
|
3103
|
+
var MIN_NODE_MAJOR = 20;
|
|
3104
|
+
async function checkBinOnPath(binName, env, platform) {
|
|
3105
|
+
const pathVar = env.PATH ?? env.Path ?? "";
|
|
3106
|
+
if (!pathVar) return false;
|
|
3107
|
+
const dirs = pathVar.split(path15.delimiter).filter(Boolean);
|
|
3108
|
+
const candidates = platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.exe`, `${binName}.bat`] : [binName];
|
|
3109
|
+
for (const dir of dirs) {
|
|
3110
|
+
for (const candidate2 of candidates) {
|
|
3111
|
+
try {
|
|
3112
|
+
await access6(path15.join(dir, candidate2), platform === "win32" ? void 0 : constants3.X_OK);
|
|
3113
|
+
return true;
|
|
3114
|
+
} catch {
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
return false;
|
|
3119
|
+
}
|
|
3120
|
+
function isNodeVersionOk(nodeVersion, minMajor = MIN_NODE_MAJOR) {
|
|
3121
|
+
const match = /^v?(\d+)/.exec(nodeVersion);
|
|
3122
|
+
if (!match) return false;
|
|
3123
|
+
const major = Number(match[1]);
|
|
3124
|
+
return Number.isFinite(major) && major >= minMajor;
|
|
3125
|
+
}
|
|
3126
|
+
function detectShellName(env, platform) {
|
|
3127
|
+
if (platform === "win32") {
|
|
3128
|
+
if (env.PSModulePath) return "powershell";
|
|
3129
|
+
if (env.ComSpec) return "cmd";
|
|
3130
|
+
return null;
|
|
3131
|
+
}
|
|
3132
|
+
const shellPath = env.SHELL;
|
|
3133
|
+
if (!shellPath) return null;
|
|
3134
|
+
const base = path15.basename(shellPath).trim();
|
|
3135
|
+
return base || null;
|
|
3136
|
+
}
|
|
3137
|
+
function detectShellProfile(env, platform, homeDir) {
|
|
3138
|
+
const shellName = detectShellName(env, platform);
|
|
3139
|
+
if (shellName === "zsh") return path15.join(homeDir, ".zshrc");
|
|
3140
|
+
if (shellName === "bash") return path15.join(homeDir, ".bashrc");
|
|
3141
|
+
return null;
|
|
3142
|
+
}
|
|
3143
|
+
function parseNpmrcPrefix(content, homeDir) {
|
|
3144
|
+
const match = /^\s*prefix\s*=\s*(.+?)\s*$/m.exec(content);
|
|
3145
|
+
const captured = match?.[1];
|
|
3146
|
+
if (!captured) return null;
|
|
3147
|
+
let value = captured.trim().replace(/^["']|["']$/g, "");
|
|
3148
|
+
if (value.startsWith("~")) {
|
|
3149
|
+
value = path15.join(homeDir, value.slice(1));
|
|
3150
|
+
}
|
|
3151
|
+
return value || null;
|
|
3152
|
+
}
|
|
3153
|
+
function heuristicPrefixFromExecPath(execPath, platform) {
|
|
3154
|
+
const p = platform === "win32" ? path15.win32 : path15.posix;
|
|
3155
|
+
return platform === "win32" ? p.dirname(execPath) : p.dirname(p.dirname(execPath));
|
|
3156
|
+
}
|
|
3157
|
+
async function detectNpmPrefix(options = {}) {
|
|
3158
|
+
const env = options.env ?? process.env;
|
|
3159
|
+
const platform = options.platform ?? process.platform;
|
|
3160
|
+
const homeDir = options.homeDir ?? homedir2();
|
|
3161
|
+
const execPath = options.execPath ?? process.execPath;
|
|
3162
|
+
const readFileImpl = options.readFileImpl ?? ((filePath) => readFile10(filePath, "utf8"));
|
|
3163
|
+
const envPrefix = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
3164
|
+
if (envPrefix?.trim()) {
|
|
3165
|
+
return { prefix: envPrefix.trim(), source: "env" };
|
|
3166
|
+
}
|
|
3167
|
+
const userconfigPath = env.NPM_CONFIG_USERCONFIG ?? path15.join(homeDir, ".npmrc");
|
|
3168
|
+
try {
|
|
3169
|
+
const content = await readFileImpl(userconfigPath);
|
|
3170
|
+
const npmrcPrefix = parseNpmrcPrefix(content, homeDir);
|
|
3171
|
+
if (npmrcPrefix) {
|
|
3172
|
+
return { prefix: npmrcPrefix, source: "npmrc" };
|
|
3173
|
+
}
|
|
3174
|
+
} catch {
|
|
3175
|
+
}
|
|
3176
|
+
return { prefix: heuristicPrefixFromExecPath(execPath, platform), source: "heuristic" };
|
|
3177
|
+
}
|
|
3178
|
+
async function describeUnwritablePrefix(prefix) {
|
|
3179
|
+
try {
|
|
3180
|
+
const info = await stat3(prefix);
|
|
3181
|
+
const currentUid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
3182
|
+
if (process.platform !== "win32" && currentUid !== void 0 && info.uid === 0 && currentUid !== 0) {
|
|
3183
|
+
return `root-owned prefix (${prefix}); the classic fresh-install PATH/EACCES blocker`;
|
|
3184
|
+
}
|
|
3185
|
+
} catch {
|
|
3186
|
+
}
|
|
3187
|
+
return `npm prefix is not writable: ${prefix}`;
|
|
3188
|
+
}
|
|
3189
|
+
async function checkNpmPrefixWritable(options = {}) {
|
|
3190
|
+
let detected;
|
|
3191
|
+
try {
|
|
3192
|
+
detected = await detectNpmPrefix(options);
|
|
3193
|
+
} catch {
|
|
3194
|
+
return { prefix: null, writable: false, reason: "npm prefix could not be determined" };
|
|
3195
|
+
}
|
|
3196
|
+
try {
|
|
3197
|
+
await access6(detected.prefix, constants3.W_OK);
|
|
3198
|
+
return { prefix: detected.prefix, writable: true, source: detected.source };
|
|
3199
|
+
} catch {
|
|
3200
|
+
return {
|
|
3201
|
+
prefix: detected.prefix,
|
|
3202
|
+
writable: false,
|
|
3203
|
+
source: detected.source,
|
|
3204
|
+
reason: await describeUnwritablePrefix(detected.prefix)
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
async function assessEnvironment(options = {}) {
|
|
3209
|
+
const env = options.env ?? process.env;
|
|
3210
|
+
const platform = options.platform ?? process.platform;
|
|
3211
|
+
const nodeVersion = options.nodeVersion ?? process.version;
|
|
3212
|
+
const homeDir = options.homeDir ?? homedir2();
|
|
3213
|
+
const binName = options.binName ?? "agent-kit";
|
|
3214
|
+
const [binOnPath, npmPrefix] = await Promise.all([
|
|
3215
|
+
checkBinOnPath(binName, env, platform).catch(() => false),
|
|
3216
|
+
checkNpmPrefixWritable(options).catch(
|
|
3217
|
+
() => ({
|
|
3218
|
+
prefix: null,
|
|
3219
|
+
writable: false,
|
|
3220
|
+
reason: "npm prefix check failed unexpectedly"
|
|
3221
|
+
})
|
|
3222
|
+
)
|
|
3223
|
+
]);
|
|
3224
|
+
return {
|
|
3225
|
+
binOnPath,
|
|
3226
|
+
npmPrefixWritable: npmPrefix.writable,
|
|
3227
|
+
npmPrefix,
|
|
3228
|
+
nodeVersionOk: isNodeVersionOk(nodeVersion),
|
|
3229
|
+
nodeVersion,
|
|
3230
|
+
shell: detectShellName(env, platform),
|
|
3231
|
+
shellProfile: detectShellProfile(env, platform, homeDir)
|
|
3232
|
+
};
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3057
3235
|
// src/scanner/readiness.ts
|
|
3058
3236
|
import { createHash as createHash3 } from "crypto";
|
|
3059
3237
|
function action(id, status, recommendation, owner) {
|
|
@@ -3301,12 +3479,12 @@ function createReadinessReport(scan, options) {
|
|
|
3301
3479
|
}
|
|
3302
3480
|
|
|
3303
3481
|
// src/scanner/safe-fixes.ts
|
|
3304
|
-
import { readFile as
|
|
3305
|
-
import
|
|
3482
|
+
import { readFile as readFile13, writeFile as writeFile5 } from "fs/promises";
|
|
3483
|
+
import path23 from "path";
|
|
3306
3484
|
|
|
3307
3485
|
// src/scanner/detect-repository.ts
|
|
3308
|
-
import { readFile as
|
|
3309
|
-
import
|
|
3486
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
3487
|
+
import path16 from "path";
|
|
3310
3488
|
var CONTEXT_PATHS = [
|
|
3311
3489
|
["README.md", "README"],
|
|
3312
3490
|
["README", "README"],
|
|
@@ -3325,7 +3503,7 @@ var CONTEXT_PATHS = [
|
|
|
3325
3503
|
async function existingEvidence(rootDir, candidates) {
|
|
3326
3504
|
const evidence = await Promise.all(
|
|
3327
3505
|
candidates.map(
|
|
3328
|
-
async ([relativePath, label]) => await fileExists(
|
|
3506
|
+
async ([relativePath, label]) => await fileExists(path16.join(rootDir, relativePath)) ? { source: "file", value: `${relativePath}:${label}` } : void 0
|
|
3329
3507
|
)
|
|
3330
3508
|
);
|
|
3331
3509
|
return evidence.flatMap((item) => item ? [item] : []);
|
|
@@ -3346,7 +3524,7 @@ async function detectContext(rootDir) {
|
|
|
3346
3524
|
async function detectPurpose(rootDir, stack) {
|
|
3347
3525
|
const entries = await listDirectory(rootDir);
|
|
3348
3526
|
const lowerEntries = entries.map((entry) => entry.toLowerCase());
|
|
3349
|
-
const packageJson = await readJson(
|
|
3527
|
+
const packageJson = await readJson(path16.join(rootDir, "package.json"));
|
|
3350
3528
|
const categories = [];
|
|
3351
3529
|
const evidence = [];
|
|
3352
3530
|
const add = (category, value2) => {
|
|
@@ -3386,16 +3564,16 @@ async function detectPurpose(rootDir, stack) {
|
|
|
3386
3564
|
}
|
|
3387
3565
|
async function detectAgentKit(rootDir) {
|
|
3388
3566
|
const manifestRelativePath = ".cursor/agent-kit.json";
|
|
3389
|
-
const manifestPath =
|
|
3567
|
+
const manifestPath = path16.join(rootDir, manifestRelativePath);
|
|
3390
3568
|
const installed = await fileExists(manifestPath);
|
|
3391
3569
|
const manifest = installed ? await readJson(manifestPath) : null;
|
|
3392
3570
|
return {
|
|
3393
3571
|
installed,
|
|
3394
3572
|
manifestPath: installed ? manifestRelativePath : void 0,
|
|
3395
3573
|
version: manifest?.version,
|
|
3396
|
-
hasPlans: await fileExists(
|
|
3397
|
-
hasHandoff: await fileExists(
|
|
3398
|
-
hasMemory: await fileExists(
|
|
3574
|
+
hasPlans: await fileExists(path16.join(rootDir, ".cursor/plans")),
|
|
3575
|
+
hasHandoff: await fileExists(path16.join(rootDir, ".cursor/HANDOFF.md")),
|
|
3576
|
+
hasMemory: await fileExists(path16.join(rootDir, ".cursor/memory"))
|
|
3399
3577
|
};
|
|
3400
3578
|
}
|
|
3401
3579
|
var REQUIRED_SECRET_PATTERNS = [
|
|
@@ -3409,9 +3587,9 @@ var REQUIRED_SECRET_PATTERNS = [
|
|
|
3409
3587
|
"*service-account*.json"
|
|
3410
3588
|
];
|
|
3411
3589
|
async function detectSafety(rootDir, trackedFiles) {
|
|
3412
|
-
const gitignorePath =
|
|
3590
|
+
const gitignorePath = path16.join(rootDir, ".gitignore");
|
|
3413
3591
|
const hasGitignore = await fileExists(gitignorePath);
|
|
3414
|
-
const gitignore = hasGitignore ? await
|
|
3592
|
+
const gitignore = hasGitignore ? await readFile11(gitignorePath, "utf8") : "";
|
|
3415
3593
|
const lines = gitignore.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
3416
3594
|
const ignoredSecretPatterns = REQUIRED_SECRET_PATTERNS.filter(
|
|
3417
3595
|
(pattern) => lines.includes(pattern)
|
|
@@ -3420,7 +3598,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
3420
3598
|
(file) => /(^|\/)(\.env(\..+)?|.*\.(key|pem|p12|pfx)|.*credentials.*\.json)$/i.test(file)
|
|
3421
3599
|
);
|
|
3422
3600
|
const hookPaths = [".husky", ".git/hooks/pre-commit", "git-hooks/pre-commit"];
|
|
3423
|
-
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(
|
|
3601
|
+
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(path16.join(rootDir, item))))).some(Boolean);
|
|
3424
3602
|
const guardCandidates = [
|
|
3425
3603
|
".husky/pre-commit",
|
|
3426
3604
|
".husky/pre-push",
|
|
@@ -3429,7 +3607,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
3429
3607
|
];
|
|
3430
3608
|
const guardContents = await Promise.all(
|
|
3431
3609
|
guardCandidates.map(
|
|
3432
|
-
async (item) => await fileExists(
|
|
3610
|
+
async (item) => await fileExists(path16.join(rootDir, item)) ? readFile11(path16.join(rootDir, item), "utf8") : ""
|
|
3433
3611
|
)
|
|
3434
3612
|
);
|
|
3435
3613
|
return {
|
|
@@ -3447,11 +3625,11 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
3447
3625
|
}
|
|
3448
3626
|
|
|
3449
3627
|
// src/scanner/scan.ts
|
|
3450
|
-
import
|
|
3628
|
+
import path22 from "path";
|
|
3451
3629
|
|
|
3452
3630
|
// src/scanner/detect-git.ts
|
|
3453
3631
|
import { execFile as execFile3 } from "child_process";
|
|
3454
|
-
import
|
|
3632
|
+
import path17 from "path";
|
|
3455
3633
|
import { promisify as promisify3 } from "util";
|
|
3456
3634
|
var exec = promisify3(execFile3);
|
|
3457
3635
|
function remoteHostname(remoteUrl) {
|
|
@@ -3475,7 +3653,7 @@ function sanitizeRemoteUrl(remoteUrl) {
|
|
|
3475
3653
|
}
|
|
3476
3654
|
async function detectProvider(rootDir, remoteUrl) {
|
|
3477
3655
|
const configuration = await readJson(
|
|
3478
|
-
|
|
3656
|
+
path17.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
3479
3657
|
);
|
|
3480
3658
|
const configuredProvider = configuration?.git?.provider;
|
|
3481
3659
|
if (configuredProvider) {
|
|
@@ -3532,7 +3710,7 @@ async function detectProvider(rootDir, remoteUrl) {
|
|
|
3532
3710
|
evidence: remoteEvidence
|
|
3533
3711
|
};
|
|
3534
3712
|
}
|
|
3535
|
-
if (await fileExists(
|
|
3713
|
+
if (await fileExists(path17.join(rootDir, ".gitlab-ci.yml"))) {
|
|
3536
3714
|
return {
|
|
3537
3715
|
provider: "gitlab",
|
|
3538
3716
|
providerKind: "gitlab-self-hosted",
|
|
@@ -3621,11 +3799,11 @@ async function detectGit(rootDir) {
|
|
|
3621
3799
|
}
|
|
3622
3800
|
|
|
3623
3801
|
// src/scanner/detect-ide.ts
|
|
3624
|
-
import
|
|
3802
|
+
import path18 from "path";
|
|
3625
3803
|
async function detectIde(rootDir) {
|
|
3626
|
-
const hasCursor = await fileExists(
|
|
3627
|
-
const hasVSCode = await fileExists(
|
|
3628
|
-
const hasWindsurf = await fileExists(
|
|
3804
|
+
const hasCursor = await fileExists(path18.join(rootDir, ".cursor"));
|
|
3805
|
+
const hasVSCode = await fileExists(path18.join(rootDir, ".vscode"));
|
|
3806
|
+
const hasWindsurf = await fileExists(path18.join(rootDir, ".windsurfrules"));
|
|
3629
3807
|
if (hasCursor) return { ide: "cursor", plan: "cursor-pro" };
|
|
3630
3808
|
if (hasVSCode) return { ide: "vscode", plan: "vscode-pro" };
|
|
3631
3809
|
if (hasWindsurf) return { ide: "windsurf", plan: "windsurf" };
|
|
@@ -3633,7 +3811,7 @@ async function detectIde(rootDir) {
|
|
|
3633
3811
|
}
|
|
3634
3812
|
|
|
3635
3813
|
// src/scanner/detect-infra.ts
|
|
3636
|
-
import
|
|
3814
|
+
import path19 from "path";
|
|
3637
3815
|
|
|
3638
3816
|
// src/types.ts
|
|
3639
3817
|
var GIT_PLATFORM_META = {
|
|
@@ -3689,12 +3867,12 @@ var PM_TOOL_LABELS = {
|
|
|
3689
3867
|
|
|
3690
3868
|
// src/scanner/detect-infra.ts
|
|
3691
3869
|
async function detectInfra(rootDir) {
|
|
3692
|
-
const docker = await fileExists(
|
|
3693
|
-
const kubernetes = await fileExists(
|
|
3870
|
+
const docker = await fileExists(path19.join(rootDir, "Dockerfile")) || await fileExists(path19.join(rootDir, "docker-compose.yml")) || await fileExists(path19.join(rootDir, "docker-compose.yaml"));
|
|
3871
|
+
const kubernetes = await fileExists(path19.join(rootDir, "k8s")) || await fileExists(path19.join(rootDir, "kubernetes"));
|
|
3694
3872
|
let ci = "none";
|
|
3695
3873
|
const ciFiles = [];
|
|
3696
3874
|
for (const [platform, filePath] of Object.entries(CI_PLATFORM_FILES)) {
|
|
3697
|
-
if (await fileExists(
|
|
3875
|
+
if (await fileExists(path19.join(rootDir, filePath))) {
|
|
3698
3876
|
if (ci === "none") ci = platform;
|
|
3699
3877
|
ciFiles.push(filePath);
|
|
3700
3878
|
}
|
|
@@ -3719,30 +3897,30 @@ async function detectInfra(rootDir) {
|
|
|
3719
3897
|
];
|
|
3720
3898
|
const infrastructureFiles = (await Promise.all(
|
|
3721
3899
|
infrastructureCandidates.map(
|
|
3722
|
-
async (file) => await fileExists(
|
|
3900
|
+
async (file) => await fileExists(path19.join(rootDir, file)) ? file : void 0
|
|
3723
3901
|
)
|
|
3724
3902
|
)).filter((file) => file !== void 0);
|
|
3725
3903
|
const deploymentFiles = (await Promise.all(
|
|
3726
3904
|
deploymentCandidates.map(
|
|
3727
|
-
async (file) => await fileExists(
|
|
3905
|
+
async (file) => await fileExists(path19.join(rootDir, file)) ? file : void 0
|
|
3728
3906
|
)
|
|
3729
3907
|
)).filter((file) => file !== void 0);
|
|
3730
3908
|
return { docker, kubernetes, ci, ciFiles, infrastructureFiles, deploymentFiles };
|
|
3731
3909
|
}
|
|
3732
3910
|
|
|
3733
3911
|
// src/scanner/detect-services.ts
|
|
3734
|
-
import { readFile as
|
|
3735
|
-
import
|
|
3912
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
3913
|
+
import path20 from "path";
|
|
3736
3914
|
async function detectProjectManagement(rootDir) {
|
|
3737
3915
|
const tools = [];
|
|
3738
3916
|
const mcpConfigPaths = [
|
|
3739
|
-
|
|
3740
|
-
|
|
3917
|
+
path20.join(rootDir, ".cursor", "mcp.json"),
|
|
3918
|
+
path20.join(rootDir, "mcp.json")
|
|
3741
3919
|
];
|
|
3742
3920
|
for (const configPath of mcpConfigPaths) {
|
|
3743
3921
|
if (!await fileExists(configPath)) continue;
|
|
3744
3922
|
try {
|
|
3745
|
-
const raw = await
|
|
3923
|
+
const raw = await readFile12(configPath, "utf8");
|
|
3746
3924
|
const lower = raw.toLowerCase();
|
|
3747
3925
|
if (lower.includes("clickup")) tools.push("clickup");
|
|
3748
3926
|
if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
|
|
@@ -3753,20 +3931,20 @@ async function detectProjectManagement(rootDir) {
|
|
|
3753
3931
|
} catch {
|
|
3754
3932
|
}
|
|
3755
3933
|
}
|
|
3756
|
-
if (await fileExists(
|
|
3934
|
+
if (await fileExists(path20.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
|
|
3757
3935
|
tools.push("github-issues");
|
|
3758
3936
|
}
|
|
3759
|
-
if (await fileExists(
|
|
3937
|
+
if (await fileExists(path20.join(rootDir, ".github", "projects"))) {
|
|
3760
3938
|
tools.push("github-projects");
|
|
3761
3939
|
}
|
|
3762
3940
|
return [...new Set(tools)];
|
|
3763
3941
|
}
|
|
3764
3942
|
async function detectServices(rootDir) {
|
|
3765
|
-
const hasPrisma = await fileExists(
|
|
3766
|
-
const hasSequelize = await fileExists(
|
|
3767
|
-
const hasDrizzle = await fileExists(
|
|
3768
|
-
const hasKnex = await fileExists(
|
|
3769
|
-
const hasTypeorm = await fileExists(
|
|
3943
|
+
const hasPrisma = await fileExists(path20.join(rootDir, "prisma/schema.prisma"));
|
|
3944
|
+
const hasSequelize = await fileExists(path20.join(rootDir, "sequelize"));
|
|
3945
|
+
const hasDrizzle = await fileExists(path20.join(rootDir, "drizzle.config.ts"));
|
|
3946
|
+
const hasKnex = await fileExists(path20.join(rootDir, "knexfile.ts"));
|
|
3947
|
+
const hasTypeorm = await fileExists(path20.join(rootDir, "ormconfig.json"));
|
|
3770
3948
|
const database = hasPrisma || hasSequelize || hasDrizzle || hasKnex || hasTypeorm ? "postgresql" : void 0;
|
|
3771
3949
|
const orm = hasPrisma ? "prisma" : hasDrizzle ? "drizzle" : hasSequelize ? "sequelize" : hasKnex ? "knex" : hasTypeorm ? "typeorm" : void 0;
|
|
3772
3950
|
const projectManagement = await detectProjectManagement(rootDir);
|
|
@@ -3778,7 +3956,7 @@ async function detectServices(rootDir) {
|
|
|
3778
3956
|
}
|
|
3779
3957
|
|
|
3780
3958
|
// src/scanner/detect-stack.ts
|
|
3781
|
-
import
|
|
3959
|
+
import path21 from "path";
|
|
3782
3960
|
var PROJECT_MARKERS = [
|
|
3783
3961
|
"package.json",
|
|
3784
3962
|
"requirements.txt",
|
|
@@ -3807,7 +3985,7 @@ async function detectPackageManager(rootDir, packageJson) {
|
|
|
3807
3985
|
};
|
|
3808
3986
|
}
|
|
3809
3987
|
for (const [lockfile, packageManager] of LOCKFILES) {
|
|
3810
|
-
if (await fileExists(
|
|
3988
|
+
if (await fileExists(path21.join(rootDir, lockfile))) {
|
|
3811
3989
|
return {
|
|
3812
3990
|
packageManager,
|
|
3813
3991
|
evidence: [{ source: "file", value: lockfile }]
|
|
@@ -3824,27 +4002,27 @@ function commandsForScripts(scripts, packageManager) {
|
|
|
3824
4002
|
return { testCommands, validationCommands };
|
|
3825
4003
|
}
|
|
3826
4004
|
async function detectStack(rootDir) {
|
|
3827
|
-
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(
|
|
3828
|
-
const hasPackageJson = await fileExists(
|
|
4005
|
+
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(path21.join(rootDir, item))))).some(Boolean);
|
|
4006
|
+
const hasPackageJson = await fileExists(path21.join(rootDir, "package.json"));
|
|
3829
4007
|
if (hasPackageJson) {
|
|
3830
|
-
const packageJson = await readJson(
|
|
4008
|
+
const packageJson = await readJson(path21.join(rootDir, "package.json")) ?? {};
|
|
3831
4009
|
const scripts = packageJson.scripts ?? {};
|
|
3832
4010
|
const packageManager = await detectPackageManager(rootDir, packageJson);
|
|
3833
4011
|
const commands = commandsForScripts(scripts, packageManager.packageManager);
|
|
3834
|
-
const hasNextConfig = await fileExists(
|
|
3835
|
-
const hasNestConfig = await fileExists(
|
|
4012
|
+
const hasNextConfig = await fileExists(path21.join(rootDir, "next.config.js")) || await fileExists(path21.join(rootDir, "next.config.mjs")) || await fileExists(path21.join(rootDir, "next.config.ts"));
|
|
4013
|
+
const hasNestConfig = await fileExists(path21.join(rootDir, "nest-cli.json"));
|
|
3836
4014
|
return {
|
|
3837
4015
|
language: "node",
|
|
3838
4016
|
framework: hasNextConfig ? "nextjs" : hasNestConfig ? "nestjs" : "node",
|
|
3839
4017
|
packageManager: packageManager.packageManager,
|
|
3840
4018
|
packageManagerEvidence: packageManager.evidence,
|
|
3841
4019
|
scripts,
|
|
3842
|
-
workspaces: packageJson.workspaces !== void 0 || await fileExists(
|
|
4020
|
+
workspaces: packageJson.workspaces !== void 0 || await fileExists(path21.join(rootDir, "pnpm-workspace.yaml")),
|
|
3843
4021
|
...commands,
|
|
3844
4022
|
hasProjectFiles: hasAnyProjectMarker
|
|
3845
4023
|
};
|
|
3846
4024
|
}
|
|
3847
|
-
if (await fileExists(
|
|
4025
|
+
if (await fileExists(path21.join(rootDir, "pyproject.toml"))) {
|
|
3848
4026
|
return {
|
|
3849
4027
|
language: "python",
|
|
3850
4028
|
framework: "python",
|
|
@@ -3854,7 +4032,7 @@ async function detectStack(rootDir) {
|
|
|
3854
4032
|
hasProjectFiles: hasAnyProjectMarker
|
|
3855
4033
|
};
|
|
3856
4034
|
}
|
|
3857
|
-
if (await fileExists(
|
|
4035
|
+
if (await fileExists(path21.join(rootDir, "go.mod"))) {
|
|
3858
4036
|
return {
|
|
3859
4037
|
language: "go",
|
|
3860
4038
|
framework: "go",
|
|
@@ -3864,7 +4042,7 @@ async function detectStack(rootDir) {
|
|
|
3864
4042
|
hasProjectFiles: hasAnyProjectMarker
|
|
3865
4043
|
};
|
|
3866
4044
|
}
|
|
3867
|
-
if (await fileExists(
|
|
4045
|
+
if (await fileExists(path21.join(rootDir, "Cargo.toml"))) {
|
|
3868
4046
|
return {
|
|
3869
4047
|
language: "rust",
|
|
3870
4048
|
framework: "rust",
|
|
@@ -3874,7 +4052,7 @@ async function detectStack(rootDir) {
|
|
|
3874
4052
|
hasProjectFiles: hasAnyProjectMarker
|
|
3875
4053
|
};
|
|
3876
4054
|
}
|
|
3877
|
-
if (await fileExists(
|
|
4055
|
+
if (await fileExists(path21.join(rootDir, "composer.json"))) {
|
|
3878
4056
|
return {
|
|
3879
4057
|
language: "php",
|
|
3880
4058
|
framework: "php",
|
|
@@ -3907,7 +4085,7 @@ function isGreenfieldByEntries(entries) {
|
|
|
3907
4085
|
return meaningful.length === 0;
|
|
3908
4086
|
}
|
|
3909
4087
|
async function runScanner(rootDir) {
|
|
3910
|
-
const normalizedRoot =
|
|
4088
|
+
const normalizedRoot = path22.resolve(rootDir);
|
|
3911
4089
|
const entries = await listDirectory(normalizedRoot);
|
|
3912
4090
|
const stack = await detectStack(normalizedRoot);
|
|
3913
4091
|
const purpose = await detectPurpose(normalizedRoot, stack);
|
|
@@ -4111,7 +4289,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4111
4289
|
});
|
|
4112
4290
|
const changes = [];
|
|
4113
4291
|
for (const relativePath of ESSENTIAL_DIRECTORIES) {
|
|
4114
|
-
const absolutePath =
|
|
4292
|
+
const absolutePath = path23.join(beforeScan.rootDir, relativePath);
|
|
4115
4293
|
const exists2 = await fileExists(absolutePath);
|
|
4116
4294
|
if (!exists2 && !dryRun) await ensureDir(absolutePath);
|
|
4117
4295
|
recordChange(
|
|
@@ -4124,8 +4302,8 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4124
4302
|
);
|
|
4125
4303
|
}
|
|
4126
4304
|
const gitignoreRelativePath = ".gitignore";
|
|
4127
|
-
const gitignorePath =
|
|
4128
|
-
const existingGitignore = await fileExists(gitignorePath) ? await
|
|
4305
|
+
const gitignorePath = path23.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
4306
|
+
const existingGitignore = await fileExists(gitignorePath) ? await readFile13(gitignorePath, "utf8") : "";
|
|
4129
4307
|
const mergedGitignore = mergeSecretIgnores(existingGitignore);
|
|
4130
4308
|
const gitignoreChanged = mergedGitignore !== existingGitignore;
|
|
4131
4309
|
if (gitignoreChanged && !dryRun) await writeFile5(gitignorePath, mergedGitignore, "utf8");
|
|
@@ -4140,7 +4318,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4140
4318
|
gitignoreChanged ? "required secret patterns are missing" : "required patterns are present"
|
|
4141
4319
|
)
|
|
4142
4320
|
);
|
|
4143
|
-
const profilePath =
|
|
4321
|
+
const profilePath = path23.join(beforeScan.rootDir, PROFILE_RELATIVE_PATH);
|
|
4144
4322
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
4145
4323
|
const desiredProfile = createProfile(beforeScan, before, generatedAt);
|
|
4146
4324
|
const mergedProfile = mergeMissing(existingProfile, desiredProfile);
|
|
@@ -4162,7 +4340,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4162
4340
|
generatorVersion: options.generatorVersion,
|
|
4163
4341
|
generatedAt
|
|
4164
4342
|
});
|
|
4165
|
-
const contextConfigPath =
|
|
4343
|
+
const contextConfigPath = path23.join(beforeScan.rootDir, CONTEXT_CONFIG_RELATIVE_PATH);
|
|
4166
4344
|
const existingContextConfig = await readJson(contextConfigPath) ?? {};
|
|
4167
4345
|
const onboarding = reconcileOnboardingState(evidenceReport, existingContextConfig, generatedAt);
|
|
4168
4346
|
const defaults = preferenceDefaults(onboarding, existingContextConfig.onboarded);
|
|
@@ -4191,10 +4369,10 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
4191
4369
|
}
|
|
4192
4370
|
|
|
4193
4371
|
// src/scanner/snapshot.ts
|
|
4194
|
-
import
|
|
4372
|
+
import path24 from "path";
|
|
4195
4373
|
var READINESS_SNAPSHOT_RELATIVE_PATH = ".cursor/context/readiness.json";
|
|
4196
4374
|
async function writeReadinessSnapshot(rootDir, report) {
|
|
4197
|
-
const snapshotPath =
|
|
4375
|
+
const snapshotPath = path24.join(rootDir, READINESS_SNAPSHOT_RELATIVE_PATH);
|
|
4198
4376
|
await writeJson(snapshotPath, report);
|
|
4199
4377
|
return snapshotPath;
|
|
4200
4378
|
}
|
|
@@ -4364,15 +4542,16 @@ async function withCliProgress(label, fn, opts) {
|
|
|
4364
4542
|
|
|
4365
4543
|
// src/commands/doctor.ts
|
|
4366
4544
|
async function runDoctor(cwd, options = {}) {
|
|
4367
|
-
const rootDir =
|
|
4545
|
+
const rootDir = path25.resolve(cwd);
|
|
4368
4546
|
const hooks = await assessHooksHealth(rootDir);
|
|
4547
|
+
const env = await assessEnvironment();
|
|
4369
4548
|
if (options.fixSafe) {
|
|
4370
4549
|
const execution = await executeSafeReadinessFixes(rootDir, {
|
|
4371
4550
|
generatorVersion: KIT_VERSION,
|
|
4372
4551
|
generatedAt: options.generatedAt
|
|
4373
4552
|
});
|
|
4374
4553
|
await writeReadinessSnapshot(rootDir, execution.after);
|
|
4375
|
-
return { report: execution.after, safeChanges: execution.changes, hooks };
|
|
4554
|
+
return { report: execution.after, safeChanges: execution.changes, hooks, env };
|
|
4376
4555
|
}
|
|
4377
4556
|
const scan = await runScanner(rootDir);
|
|
4378
4557
|
const report = createReadinessReport(scan, {
|
|
@@ -4380,7 +4559,7 @@ async function runDoctor(cwd, options = {}) {
|
|
|
4380
4559
|
generatedAt: options.generatedAt
|
|
4381
4560
|
});
|
|
4382
4561
|
await writeReadinessSnapshot(rootDir, report);
|
|
4383
|
-
return { report, safeChanges: [], hooks };
|
|
4562
|
+
return { report, safeChanges: [], hooks, env };
|
|
4384
4563
|
}
|
|
4385
4564
|
function printDoctorSummary(result) {
|
|
4386
4565
|
const { summary, pendingActions } = result.report;
|
|
@@ -4404,6 +4583,20 @@ function printDoctorSummary(result) {
|
|
|
4404
4583
|
console.log(` - ${tip}`);
|
|
4405
4584
|
}
|
|
4406
4585
|
}
|
|
4586
|
+
console.log("environment:");
|
|
4587
|
+
console.log(` - bin on PATH (agent-kit): ${result.env.binOnPath ? "ok" : "MISSING"}`);
|
|
4588
|
+
console.log(
|
|
4589
|
+
` - npm prefix writable: ${result.env.npmPrefixWritable ? "ok" : "BLOCKED"}${result.env.npmPrefix.prefix ? ` (${result.env.npmPrefix.prefix})` : ""}`
|
|
4590
|
+
);
|
|
4591
|
+
if (!result.env.npmPrefixWritable && result.env.npmPrefix.reason) {
|
|
4592
|
+
console.log(` - ${result.env.npmPrefix.reason}`);
|
|
4593
|
+
}
|
|
4594
|
+
console.log(
|
|
4595
|
+
` - node version >= 20: ${result.env.nodeVersionOk ? "ok" : "TOO OLD"} (${result.env.nodeVersion})`
|
|
4596
|
+
);
|
|
4597
|
+
console.log(
|
|
4598
|
+
` - shell profile: ${result.env.shellProfile ?? "not detected (zsh/bash only)"}${result.env.shell ? ` (shell: ${result.env.shell})` : ""}`
|
|
4599
|
+
);
|
|
4407
4600
|
if (process.env.ALLOW_MAIN_PUSH === "1") {
|
|
4408
4601
|
console.log("\u26A0\uFE0F WARNING: ALLOW_MAIN_PUSH=1 is set in environment");
|
|
4409
4602
|
console.log(" This disables main-push protection for agent Shell commands.");
|
|
@@ -4484,13 +4677,21 @@ var SECRET_PATTERNS2 = [
|
|
|
4484
4677
|
id: "github-pat",
|
|
4485
4678
|
re: /\bghp_[A-Za-z0-9_]{36,}\b/
|
|
4486
4679
|
},
|
|
4680
|
+
// Hyphenated vendor keys (`sk-ant-api03-…`, `sk-proj-…`) cannot be matched by
|
|
4681
|
+
// `openai-sk`: its body class excludes `-`, so it stops at the first separator.
|
|
4682
|
+
// Listed before `openai-sk`; the two cannot both hit the same span.
|
|
4487
4683
|
{
|
|
4684
|
+
id: "sk-hyphenated-vendor",
|
|
4685
|
+
re: /\bsk-[A-Za-z0-9]{2,12}-[A-Za-z0-9_-]{16,}\b/
|
|
4686
|
+
},
|
|
4687
|
+
{
|
|
4688
|
+
// Single-segment `sk-` bodies only (no `-` in the class) — see `sk-hyphenated-vendor`.
|
|
4488
4689
|
id: "openai-sk",
|
|
4489
4690
|
re: /\bsk-[A-Za-z0-9]{20,}\b/
|
|
4490
4691
|
}
|
|
4491
4692
|
];
|
|
4492
4693
|
function maskSecretExcerpt(raw) {
|
|
4493
|
-
return raw.replace(/\b(ghp_|sk-|AKIA)([A-Za-z0-9_]{4,})/g, (_m, p1, p2) => {
|
|
4694
|
+
return raw.replace(/\b(ghp_|sk-|AKIA)([A-Za-z0-9_-]{4,})/g, (_m, p1, p2) => {
|
|
4494
4695
|
return `${p1}${"*".repeat(Math.min(8, p2.length))}`;
|
|
4495
4696
|
}).replace(
|
|
4496
4697
|
/(=\s*['"]?)([^\s'"]{4,})/g,
|
|
@@ -4714,7 +4915,7 @@ var guardCommand = defineCommand8({
|
|
|
4714
4915
|
shell: defineCommand8({
|
|
4715
4916
|
meta: {
|
|
4716
4917
|
name: "shell",
|
|
4717
|
-
description: "Evaluate a shell command against the
|
|
4918
|
+
description: "Evaluate a shell command against the git-workflow / protected-branch deny-list (git checkout|restore|reset --hard|clean -fd + pushes to main/master/prod). Not a general destructive-command guard: rm -rf, chmod, dd are allowed."
|
|
4718
4919
|
},
|
|
4719
4920
|
args: {
|
|
4720
4921
|
json: {
|
|
@@ -4772,8 +4973,8 @@ var guardCommand = defineCommand8({
|
|
|
4772
4973
|
|
|
4773
4974
|
// src/commands/handoff.ts
|
|
4774
4975
|
import { spawn as spawn4 } from "child_process";
|
|
4775
|
-
import { readFile as
|
|
4776
|
-
import
|
|
4976
|
+
import { readFile as readFile14, readdir as readdir5, writeFile as writeFile6 } from "fs/promises";
|
|
4977
|
+
import path26 from "path";
|
|
4777
4978
|
import { defineCommand as defineCommand9 } from "citty";
|
|
4778
4979
|
function parsePlanFrontmatter(raw) {
|
|
4779
4980
|
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -4791,21 +4992,21 @@ function parsePlanFrontmatter(raw) {
|
|
|
4791
4992
|
}
|
|
4792
4993
|
async function findActivePlan(plansDir) {
|
|
4793
4994
|
if (!await fileExists(plansDir)) return null;
|
|
4794
|
-
const files = (await
|
|
4995
|
+
const files = (await readdir5(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
4795
4996
|
for (const file of files) {
|
|
4796
|
-
const raw = await
|
|
4997
|
+
const raw = await readFile14(path26.join(plansDir, file), "utf8");
|
|
4797
4998
|
const fm = parsePlanFrontmatter(raw);
|
|
4798
4999
|
if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
|
|
4799
5000
|
return { file, raw };
|
|
4800
5001
|
}
|
|
4801
5002
|
}
|
|
4802
|
-
return files[0] ? { file: files[0], raw: await
|
|
5003
|
+
return files[0] ? { file: files[0], raw: await readFile14(path26.join(plansDir, files[0]), "utf8") } : null;
|
|
4803
5004
|
}
|
|
4804
5005
|
function now() {
|
|
4805
5006
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
|
|
4806
5007
|
}
|
|
4807
5008
|
async function loadProfile(rootDir) {
|
|
4808
|
-
const configPath =
|
|
5009
|
+
const configPath = path26.join(rootDir, ".cursor", "agent-kit.config.json");
|
|
4809
5010
|
try {
|
|
4810
5011
|
return await readJson(configPath);
|
|
4811
5012
|
} catch {
|
|
@@ -4905,13 +5106,13 @@ var handoffCommand = defineCommand9({
|
|
|
4905
5106
|
},
|
|
4906
5107
|
async run({ args }) {
|
|
4907
5108
|
const profile = await loadProfile(args.cwd);
|
|
4908
|
-
const plansDir =
|
|
4909
|
-
const handoffPath =
|
|
5109
|
+
const plansDir = path26.join(args.cwd, ".cursor", "plans");
|
|
5110
|
+
const handoffPath = path26.join(args.cwd, ".cursor", "HANDOFF.md");
|
|
4910
5111
|
const plan = await findActivePlan(plansDir);
|
|
4911
5112
|
if (plan) {
|
|
4912
5113
|
const fm = parsePlanFrontmatter(plan.raw);
|
|
4913
5114
|
if (fm) {
|
|
4914
|
-
await ensureDir(
|
|
5115
|
+
await ensureDir(path26.join(args.cwd, ".cursor"));
|
|
4915
5116
|
const content = buildHandoff(plan.file, fm, profile);
|
|
4916
5117
|
await writeFile6(handoffPath, content, "utf8");
|
|
4917
5118
|
logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
|
|
@@ -4931,7 +5132,7 @@ var handoffCommand = defineCommand9({
|
|
|
4931
5132
|
}
|
|
4932
5133
|
logger.warn(`Plan ${plan.file} without valid frontmatter; trying legacy flow.`);
|
|
4933
5134
|
}
|
|
4934
|
-
const scriptPath =
|
|
5135
|
+
const scriptPath = path26.join(args.cwd, "cursor-handoff");
|
|
4935
5136
|
if (!await fileExists(scriptPath)) {
|
|
4936
5137
|
printV3Guidance();
|
|
4937
5138
|
return;
|
|
@@ -4956,9 +5157,19 @@ var handoffCommand = defineCommand9({
|
|
|
4956
5157
|
});
|
|
4957
5158
|
|
|
4958
5159
|
// src/commands/hook.ts
|
|
4959
|
-
import
|
|
5160
|
+
import path28 from "path";
|
|
4960
5161
|
import { defineCommand as defineCommand10 } from "citty";
|
|
4961
5162
|
|
|
5163
|
+
// src/hooks/format-session-start.ts
|
|
5164
|
+
function resolveSessionStartFormat(value) {
|
|
5165
|
+
return value === "claude" ? "claude" : "cursor";
|
|
5166
|
+
}
|
|
5167
|
+
function formatSessionStartOutput(additionalContext, format) {
|
|
5168
|
+
if (format === "claude") return additionalContext;
|
|
5169
|
+
return JSON.stringify({ additional_context: additionalContext });
|
|
5170
|
+
}
|
|
5171
|
+
var SESSION_START_DEGRADED_MESSAGE = "Agent Kit session-start context is unavailable this session (internal error, fail-open mode). Nothing else is affected; retry next session.";
|
|
5172
|
+
|
|
4962
5173
|
// src/hooks/pre-compact.ts
|
|
4963
5174
|
function buildPreCompactUserMessage(payload = {}) {
|
|
4964
5175
|
const pct = payload.context_usage_percent;
|
|
@@ -4970,8 +5181,8 @@ function buildPreCompactUserMessage(payload = {}) {
|
|
|
4970
5181
|
|
|
4971
5182
|
// src/hooks/session-start.ts
|
|
4972
5183
|
import { spawn as spawn5 } from "child_process";
|
|
4973
|
-
import { access as
|
|
4974
|
-
import
|
|
5184
|
+
import { access as access7, readFile as readFile15, stat as stat4 } from "fs/promises";
|
|
5185
|
+
import path27 from "path";
|
|
4975
5186
|
|
|
4976
5187
|
// src/invariants/handoff-schema.ts
|
|
4977
5188
|
var MACHINE_LIST_CHECKS = [
|
|
@@ -5045,7 +5256,7 @@ var NONE_PLACEHOLDERS = /* @__PURE__ */ new Set(["none", "n/a", "empty", "nil"])
|
|
|
5045
5256
|
var CURSOR_AWARENESS_SPAWN_TIMEOUT_MS = CHANGELOG_FETCH_TIMEOUT_MS + 3e3;
|
|
5046
5257
|
async function readTextLimited(filePath, limit = 60) {
|
|
5047
5258
|
try {
|
|
5048
|
-
const lines = (await
|
|
5259
|
+
const lines = (await readFile15(filePath, "utf8")).split(/\r?\n/);
|
|
5049
5260
|
return lines.slice(0, limit).join("\n").trim();
|
|
5050
5261
|
} catch {
|
|
5051
5262
|
return "";
|
|
@@ -5053,14 +5264,14 @@ async function readTextLimited(filePath, limit = 60) {
|
|
|
5053
5264
|
}
|
|
5054
5265
|
async function readFull(filePath) {
|
|
5055
5266
|
try {
|
|
5056
|
-
return await
|
|
5267
|
+
return await readFile15(filePath, "utf8");
|
|
5057
5268
|
} catch {
|
|
5058
5269
|
return "";
|
|
5059
5270
|
}
|
|
5060
5271
|
}
|
|
5061
5272
|
async function fileExists2(p) {
|
|
5062
5273
|
try {
|
|
5063
|
-
await
|
|
5274
|
+
await access7(p);
|
|
5064
5275
|
return true;
|
|
5065
5276
|
} catch {
|
|
5066
5277
|
return false;
|
|
@@ -5127,8 +5338,8 @@ function extractUnprocessedDogfoodLine(line) {
|
|
|
5127
5338
|
return raw;
|
|
5128
5339
|
}
|
|
5129
5340
|
async function l0Present(root) {
|
|
5130
|
-
const cursor =
|
|
5131
|
-
return await fileExists2(
|
|
5341
|
+
const cursor = path27.join(root, ".cursor");
|
|
5342
|
+
return await fileExists2(path27.join(cursor, "agent-kit.json")) || await fileExists2(path27.join(cursor, "commands", "agent-kit-onboard.md")) || await fileExists2(path27.join(cursor, "commands", "start-project.md"));
|
|
5132
5343
|
}
|
|
5133
5344
|
function checkLabelAndRecommendation(check2) {
|
|
5134
5345
|
const checkId = check2.id;
|
|
@@ -5167,10 +5378,10 @@ function unresolvedReadinessChecks(data) {
|
|
|
5167
5378
|
return { essential, nonessential };
|
|
5168
5379
|
}
|
|
5169
5380
|
async function readinessSection(root) {
|
|
5170
|
-
const snapshotPath =
|
|
5381
|
+
const snapshotPath = path27.join(root, ".cursor", "context", "readiness.json");
|
|
5171
5382
|
let data;
|
|
5172
5383
|
try {
|
|
5173
|
-
data = JSON.parse(await
|
|
5384
|
+
data = JSON.parse(await readFile15(snapshotPath, "utf8"));
|
|
5174
5385
|
} catch {
|
|
5175
5386
|
return null;
|
|
5176
5387
|
}
|
|
@@ -5201,13 +5412,13 @@ Optional readiness item: \`${first.id}\`. ${first.recommendation} This does not
|
|
|
5201
5412
|
}
|
|
5202
5413
|
async function dogfoodInboxSection(root) {
|
|
5203
5414
|
const candidateReadmes = [
|
|
5204
|
-
|
|
5205
|
-
|
|
5415
|
+
path27.join(root, "dogfood", "README.md"),
|
|
5416
|
+
path27.join(root, ".cursor", "dogfood", "README.md")
|
|
5206
5417
|
];
|
|
5207
5418
|
for (const readme of candidateReadmes) {
|
|
5208
5419
|
if (!await fileExists2(readme)) continue;
|
|
5209
5420
|
try {
|
|
5210
|
-
const text = await
|
|
5421
|
+
const text = await readFile15(readme, "utf8");
|
|
5211
5422
|
if (parseUnprocessedDogfoodItems(text).length) return DOGFOOD_INBOX_HINT;
|
|
5212
5423
|
} catch {
|
|
5213
5424
|
}
|
|
@@ -5217,7 +5428,7 @@ async function dogfoodInboxSection(root) {
|
|
|
5217
5428
|
async function loadUpdateCheckPrefs(root) {
|
|
5218
5429
|
try {
|
|
5219
5430
|
const data = JSON.parse(
|
|
5220
|
-
await
|
|
5431
|
+
await readFile15(path27.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
5221
5432
|
);
|
|
5222
5433
|
const uc = data.updateCheck;
|
|
5223
5434
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -5295,7 +5506,7 @@ async function updateCheckSection(root) {
|
|
|
5295
5506
|
async function loadCursorUpdateCheckPrefs(root) {
|
|
5296
5507
|
try {
|
|
5297
5508
|
const data = JSON.parse(
|
|
5298
|
-
await
|
|
5509
|
+
await readFile15(path27.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
5299
5510
|
);
|
|
5300
5511
|
const uc = data.cursorUpdateCheck;
|
|
5301
5512
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -5394,9 +5605,101 @@ async function cursorAwarenessSection(root, deps = {}) {
|
|
|
5394
5605
|
if (!shouldEmitCursorAwarenessNudge(result)) return null;
|
|
5395
5606
|
return CURSOR_AWARENESS_NUDGE;
|
|
5396
5607
|
}
|
|
5608
|
+
var AUDIT_SESSION_NS_PREFIX = "agent-kit-audit-";
|
|
5609
|
+
var AUDIT_SESSION_LIST_TIMEOUT_MS = 2e3;
|
|
5610
|
+
function runAuditSessionCommand(cmd, args) {
|
|
5611
|
+
return new Promise((resolve2) => {
|
|
5612
|
+
const child = spawn5(cmd, args, {
|
|
5613
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
5614
|
+
timeout: AUDIT_SESSION_LIST_TIMEOUT_MS,
|
|
5615
|
+
shell: false
|
|
5616
|
+
});
|
|
5617
|
+
let out = "";
|
|
5618
|
+
child.stdout?.on("data", (chunk) => {
|
|
5619
|
+
out += chunk.toString("utf8");
|
|
5620
|
+
});
|
|
5621
|
+
child.on("error", () => resolve2(null));
|
|
5622
|
+
child.on("close", () => resolve2(out));
|
|
5623
|
+
});
|
|
5624
|
+
}
|
|
5625
|
+
function parseTmuxDetachedAuditSessions(out, nowEpochSeconds) {
|
|
5626
|
+
const sessions = [];
|
|
5627
|
+
for (const line of out.split(/\r?\n/)) {
|
|
5628
|
+
const m = /^(\S+)\s+(\d+)\s+(\d+)$/.exec(line.trim());
|
|
5629
|
+
if (!m) continue;
|
|
5630
|
+
const [, name, attached, created] = m;
|
|
5631
|
+
if (!name || !name.startsWith(AUDIT_SESSION_NS_PREFIX)) continue;
|
|
5632
|
+
if (Number(attached) > 0) continue;
|
|
5633
|
+
const createdEpoch = Number(created);
|
|
5634
|
+
const ageSeconds = Number.isFinite(createdEpoch) && nowEpochSeconds >= createdEpoch ? nowEpochSeconds - createdEpoch : -1;
|
|
5635
|
+
sessions.push({ channel: "tmux", name, ageSeconds });
|
|
5636
|
+
}
|
|
5637
|
+
return sessions;
|
|
5638
|
+
}
|
|
5639
|
+
function parseScreenDetachedAuditSessions(listing) {
|
|
5640
|
+
const lines = listing.split(/\r?\n/);
|
|
5641
|
+
let sockdir = null;
|
|
5642
|
+
for (const line of lines) {
|
|
5643
|
+
const dirMatch = /^\d+\s+Sockets?\s+in\s+(.+)\.$/.exec(line);
|
|
5644
|
+
if (dirMatch?.[1]) sockdir = dirMatch[1];
|
|
5645
|
+
}
|
|
5646
|
+
const entries = [];
|
|
5647
|
+
for (const line of lines) {
|
|
5648
|
+
const m = /^\s+(\d+)\.(\S+)\s+\((.*)\)/.exec(line);
|
|
5649
|
+
if (!m) continue;
|
|
5650
|
+
const [, pid, name, marker] = m;
|
|
5651
|
+
if (!name || !name.startsWith(AUDIT_SESSION_NS_PREFIX)) continue;
|
|
5652
|
+
if (marker && /[Aa]ttached/.test(marker)) continue;
|
|
5653
|
+
entries.push({ name, socketPath: sockdir ? path27.join(sockdir, `${pid}.${name}`) : null });
|
|
5654
|
+
}
|
|
5655
|
+
return entries;
|
|
5656
|
+
}
|
|
5657
|
+
function formatSessionAge(seconds) {
|
|
5658
|
+
if (seconds >= 86400) return `${Math.floor(seconds / 86400)}d`;
|
|
5659
|
+
if (seconds >= 3600) return `${Math.floor(seconds / 3600)}h`;
|
|
5660
|
+
if (seconds >= 60) return `${Math.floor(seconds / 60)}m`;
|
|
5661
|
+
return `${seconds}s`;
|
|
5662
|
+
}
|
|
5663
|
+
async function detachedAuditSessionsSection(deps = {}) {
|
|
5664
|
+
try {
|
|
5665
|
+
const run = deps.runCommand ?? runAuditSessionCommand;
|
|
5666
|
+
const nowMs = (deps.now ?? Date.now)();
|
|
5667
|
+
const [tmuxOut, screenOut] = await Promise.all([
|
|
5668
|
+
run("tmux", [
|
|
5669
|
+
"list-sessions",
|
|
5670
|
+
"-F",
|
|
5671
|
+
"#{session_name} #{session_attached} #{session_created}"
|
|
5672
|
+
]),
|
|
5673
|
+
run("screen", ["-ls"])
|
|
5674
|
+
]);
|
|
5675
|
+
const sessions = tmuxOut ? parseTmuxDetachedAuditSessions(tmuxOut, Math.floor(nowMs / 1e3)) : [];
|
|
5676
|
+
if (screenOut) {
|
|
5677
|
+
for (const entry of parseScreenDetachedAuditSessions(screenOut)) {
|
|
5678
|
+
let ageSeconds = -1;
|
|
5679
|
+
if (entry.socketPath) {
|
|
5680
|
+
try {
|
|
5681
|
+
const { mtimeMs } = await stat4(entry.socketPath);
|
|
5682
|
+
if (nowMs >= mtimeMs) ageSeconds = Math.floor((nowMs - mtimeMs) / 1e3);
|
|
5683
|
+
} catch {
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
sessions.push({ channel: "screen", name: entry.name, ageSeconds });
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
if (sessions.length === 0) return null;
|
|
5690
|
+
const knownAges = sessions.map((s) => s.ageSeconds).filter((a) => a >= 0);
|
|
5691
|
+
const oldest = knownAges.length ? `oldest ~${formatSessionAge(Math.max(...knownAges))}` : "oldest age unknown";
|
|
5692
|
+
const noun = sessions.length === 1 ? "session" : "sessions";
|
|
5693
|
+
return `## Detached audit sessions (host)
|
|
5694
|
+
|
|
5695
|
+
${sessions.length} detached \`agent-kit-audit-*\` PTY ${noun} on this host (${oldest}). These are external plan-review terminals: inspect with \`tmux attach -t <name>\` / \`screen -r <name>\`, or let the audit launcher's session GC dispose of them on the next spawn.`;
|
|
5696
|
+
} catch {
|
|
5697
|
+
return null;
|
|
5698
|
+
}
|
|
5699
|
+
}
|
|
5397
5700
|
async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
5398
|
-
const root =
|
|
5399
|
-
const handoffPath =
|
|
5701
|
+
const root = path27.resolve(rootDir);
|
|
5702
|
+
const handoffPath = path27.join(root, ".cursor", "HANDOFF.md");
|
|
5400
5703
|
const handoffFull = await readFull(handoffPath);
|
|
5401
5704
|
const handoff = await readTextLimited(handoffPath);
|
|
5402
5705
|
const parts = [HARD_RULES];
|
|
@@ -5410,6 +5713,8 @@ async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
|
5410
5713
|
if (updateNudge) parts.push(updateNudge);
|
|
5411
5714
|
const cursorNudge = await cursorAwarenessSection(root);
|
|
5412
5715
|
if (cursorNudge) parts.push(cursorNudge);
|
|
5716
|
+
const auditSessions = await detachedAuditSessionsSection().catch(() => null);
|
|
5717
|
+
if (auditSessions) parts.push(auditSessions);
|
|
5413
5718
|
const formatWarnings = validateHandoffText(handoffFull);
|
|
5414
5719
|
if (formatWarnings.length) {
|
|
5415
5720
|
const bullet = formatWarnings.map((w) => `- ${w.message}`).join("\n");
|
|
@@ -5441,29 +5746,44 @@ function resolveSessionRoot(payload, cwd = process.cwd()) {
|
|
|
5441
5746
|
}
|
|
5442
5747
|
|
|
5443
5748
|
// src/commands/hook.ts
|
|
5749
|
+
async function runSessionStartHook(cwd, formatArg, deps = {}) {
|
|
5750
|
+
const format = resolveSessionStartFormat(formatArg);
|
|
5751
|
+
try {
|
|
5752
|
+
const readStdin = deps.readStdin ?? readStdinJson;
|
|
5753
|
+
const buildContext = deps.buildContext ?? buildSessionStartAdditionalContext;
|
|
5754
|
+
const payload = await readStdin();
|
|
5755
|
+
const root = resolveSessionRoot(payload, path28.resolve(cwd));
|
|
5756
|
+
const out = await buildContext(root, payload);
|
|
5757
|
+
return formatSessionStartOutput(out.additional_context, format);
|
|
5758
|
+
} catch {
|
|
5759
|
+
return formatSessionStartOutput(SESSION_START_DEGRADED_MESSAGE, format);
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5444
5762
|
var hookCommand = defineCommand10({
|
|
5445
5763
|
meta: {
|
|
5446
5764
|
name: "hook",
|
|
5447
|
-
description: "Cursor hook adapters (session-start, pre-compact). CLI is SoT."
|
|
5765
|
+
description: "Cursor + Claude Code hook adapters (session-start, pre-compact). CLI is SoT."
|
|
5448
5766
|
},
|
|
5449
5767
|
subCommands: {
|
|
5450
5768
|
"session-start": defineCommand10({
|
|
5451
5769
|
meta: {
|
|
5452
5770
|
name: "session-start",
|
|
5453
|
-
description: "Emit sessionStart
|
|
5771
|
+
description: "Emit sessionStart context (stdin: host payload). --format cursor (default, JSON additional_context) | claude (plain stdout)"
|
|
5454
5772
|
},
|
|
5455
5773
|
args: {
|
|
5456
5774
|
cwd: {
|
|
5457
5775
|
type: "string",
|
|
5458
5776
|
default: process.cwd()
|
|
5777
|
+
},
|
|
5778
|
+
format: {
|
|
5779
|
+
type: "string",
|
|
5780
|
+
default: "cursor",
|
|
5781
|
+
description: "cursor (default) | claude"
|
|
5459
5782
|
}
|
|
5460
5783
|
},
|
|
5461
5784
|
async run({ args }) {
|
|
5462
|
-
const payload = await readStdinJson();
|
|
5463
5785
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
5464
|
-
|
|
5465
|
-
const out = await buildSessionStartAdditionalContext(root, payload);
|
|
5466
|
-
console.log(JSON.stringify(out));
|
|
5786
|
+
console.log(await runSessionStartHook(cwd, args.format));
|
|
5467
5787
|
}
|
|
5468
5788
|
}),
|
|
5469
5789
|
"pre-compact": defineCommand10({
|
|
@@ -5484,45 +5804,96 @@ import { intro, outro } from "@clack/prompts";
|
|
|
5484
5804
|
import { defineCommand as defineCommand12 } from "citty";
|
|
5485
5805
|
|
|
5486
5806
|
// src/utils/terminal.ts
|
|
5487
|
-
import {
|
|
5488
|
-
import
|
|
5807
|
+
import { readdir as readdir6 } from "fs/promises";
|
|
5808
|
+
import { homedir as homedir3 } from "os";
|
|
5809
|
+
import path29 from "path";
|
|
5489
5810
|
import { confirm, isCancel } from "@clack/prompts";
|
|
5490
5811
|
function isNonInteractive() {
|
|
5491
5812
|
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
5492
5813
|
if (process.env.AGENT_KIT_YES === "1") return true;
|
|
5493
5814
|
return !process.stdin.isTTY;
|
|
5494
5815
|
}
|
|
5816
|
+
var NESTED_REPO_AMBIGUITY_THRESHOLD = 2;
|
|
5817
|
+
var NESTED_REPO_SCAN_LIMIT = 200;
|
|
5818
|
+
async function findNestedRepoChildren(resolved) {
|
|
5819
|
+
let entries;
|
|
5820
|
+
try {
|
|
5821
|
+
entries = await readdir6(resolved, { withFileTypes: true });
|
|
5822
|
+
} catch {
|
|
5823
|
+
return [];
|
|
5824
|
+
}
|
|
5825
|
+
const nested = [];
|
|
5826
|
+
let scanned = 0;
|
|
5827
|
+
for (const entry of entries) {
|
|
5828
|
+
if (nested.length >= NESTED_REPO_AMBIGUITY_THRESHOLD) break;
|
|
5829
|
+
if (scanned >= NESTED_REPO_SCAN_LIMIT) break;
|
|
5830
|
+
if (!entry.isDirectory()) continue;
|
|
5831
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
5832
|
+
scanned += 1;
|
|
5833
|
+
if (await fileExists(path29.join(resolved, entry.name, ".git"))) {
|
|
5834
|
+
nested.push(entry.name);
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
return nested;
|
|
5838
|
+
}
|
|
5495
5839
|
async function validateProjectRoot(resolved) {
|
|
5496
|
-
const home =
|
|
5840
|
+
const home = path29.resolve(homedir3());
|
|
5497
5841
|
if (resolved === "/" || resolved === home) {
|
|
5498
|
-
return {
|
|
5842
|
+
return {
|
|
5843
|
+
ok: false,
|
|
5844
|
+
reason: `Refused to use ${resolved} as a project root.`,
|
|
5845
|
+
recovery: "Change into a project directory and re-run. Agent Kit installs per project."
|
|
5846
|
+
};
|
|
5499
5847
|
}
|
|
5500
|
-
const hasGit = await fileExists(
|
|
5501
|
-
const hasManifest = await fileExists(
|
|
5848
|
+
const hasGit = await fileExists(path29.join(resolved, ".git"));
|
|
5849
|
+
const hasManifest = await fileExists(path29.join(resolved, ".cursor", "agent-kit.json"));
|
|
5502
5850
|
if (!hasGit && !hasManifest) {
|
|
5503
5851
|
return {
|
|
5504
5852
|
ok: false,
|
|
5505
|
-
reason: `Refused ${resolved}: no .git and no .cursor/agent-kit.json
|
|
5853
|
+
reason: `Refused ${resolved}: no .git and no .cursor/agent-kit.json.`,
|
|
5854
|
+
recovery: [
|
|
5855
|
+
"Starting from an empty folder? Pick one of these:",
|
|
5856
|
+
" 1. git init - then re-run. Recommended: readiness and the",
|
|
5857
|
+
" staging -> prod flow both want Git.",
|
|
5858
|
+
" 2. --force-root - install without Git. /agent-kit-onboard will",
|
|
5859
|
+
" still offer to initialize it later.",
|
|
5860
|
+
" 3. Answer yes to the 'Proceed anyway?' prompt in an interactive terminal.",
|
|
5861
|
+
"Or re-run from the project directory you actually meant."
|
|
5862
|
+
].join("\n")
|
|
5506
5863
|
};
|
|
5507
5864
|
}
|
|
5865
|
+
if (hasGit && !hasManifest) {
|
|
5866
|
+
const nested = await findNestedRepoChildren(resolved);
|
|
5867
|
+
if (nested.length >= NESTED_REPO_AMBIGUITY_THRESHOLD) {
|
|
5868
|
+
return {
|
|
5869
|
+
ok: false,
|
|
5870
|
+
reason: `Refused ${resolved}: it has .git but also contains child repositories (${nested.join(", ")}). This looks like a parent-of-repos folder, not a project root.`,
|
|
5871
|
+
recovery: [
|
|
5872
|
+
"L0 belongs in one project, not in the folder that holds several.",
|
|
5873
|
+
" 1. cd into the project you meant, then re-run.",
|
|
5874
|
+
" 2. --force-root - only if this parent folder really is the project root."
|
|
5875
|
+
].join("\n")
|
|
5876
|
+
};
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5508
5879
|
return { ok: true };
|
|
5509
5880
|
}
|
|
5510
5881
|
async function confirmProjectRoot(cwd, opts) {
|
|
5511
|
-
const resolved =
|
|
5882
|
+
const resolved = path29.resolve(cwd);
|
|
5512
5883
|
if (opts.forceRoot) {
|
|
5513
5884
|
return resolved;
|
|
5514
5885
|
}
|
|
5515
5886
|
const validation = await validateProjectRoot(resolved);
|
|
5516
5887
|
if (!validation.ok) {
|
|
5517
5888
|
if (opts.nonInteractive) {
|
|
5518
|
-
throw new RootRefusedError(resolved, validation.reason);
|
|
5889
|
+
throw new RootRefusedError(resolved, validation.reason, validation.recovery);
|
|
5519
5890
|
}
|
|
5520
5891
|
const ok2 = await confirm({
|
|
5521
5892
|
message: `${validation.reason} Proceed anyway?`,
|
|
5522
5893
|
initialValue: false
|
|
5523
5894
|
});
|
|
5524
5895
|
if (isCancel(ok2) || !ok2) {
|
|
5525
|
-
throw new RootRefusedError(resolved, validation.reason);
|
|
5896
|
+
throw new RootRefusedError(resolved, validation.reason, validation.recovery);
|
|
5526
5897
|
}
|
|
5527
5898
|
return resolved;
|
|
5528
5899
|
}
|
|
@@ -5539,16 +5910,38 @@ async function confirmProjectRoot(cwd, opts) {
|
|
|
5539
5910
|
return resolved;
|
|
5540
5911
|
}
|
|
5541
5912
|
var RootRefusedError = class extends Error {
|
|
5542
|
-
constructor(root, reason) {
|
|
5913
|
+
constructor(root, reason, recovery) {
|
|
5543
5914
|
super(reason ?? `Refused to write into ${root}. Re-run from the correct project directory.`);
|
|
5544
5915
|
this.root = root;
|
|
5916
|
+
this.recovery = recovery;
|
|
5545
5917
|
this.name = "RootRefusedError";
|
|
5546
5918
|
}
|
|
5547
5919
|
root;
|
|
5920
|
+
recovery;
|
|
5548
5921
|
};
|
|
5922
|
+
var NPM_GLOBAL_PREFIX_PATH_RE = /\/lib\/node_modules|Program Files\\nodejs\\node_modules/;
|
|
5923
|
+
var NPM_GLOBAL_NODE_MODULES_RE = /node_modules/;
|
|
5924
|
+
function isNpmGlobalPrefixError(msg, code) {
|
|
5925
|
+
const isPermissionError = code === "EPERM" || code === "EACCES" || /EPERM|EACCES/.test(msg);
|
|
5926
|
+
if (!isPermissionError) return false;
|
|
5927
|
+
return NPM_GLOBAL_PREFIX_PATH_RE.test(msg) || NPM_GLOBAL_NODE_MODULES_RE.test(msg);
|
|
5928
|
+
}
|
|
5549
5929
|
function classifyInstallError(err) {
|
|
5550
5930
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5551
5931
|
const code = err?.code;
|
|
5932
|
+
if (isNpmGlobalPrefixError(msg, code)) {
|
|
5933
|
+
return {
|
|
5934
|
+
kind: "npm-global-eacces",
|
|
5935
|
+
message: `Permission error (root-owned npm prefix): ${msg}`,
|
|
5936
|
+
recovery: [
|
|
5937
|
+
"npm's global install prefix (e.g. /usr/local/lib/node_modules) is owned by root, so global installs fail.",
|
|
5938
|
+
"Recovery options:",
|
|
5939
|
+
" 1. Run: npx @dadado/agent-kit-cli setup-global (relocates npm's prefix to a folder you own, fixes PATH, reinstalls)",
|
|
5940
|
+
' 2. Manual fix: mkdir -p ~/.npm-global && npm config set prefix "~/.npm-global" && export PATH="~/.npm-global/bin:$PATH" (add to your shell profile) && npm i -g @dadado/agent-kit-cli',
|
|
5941
|
+
" 3. Use Port B fallback: drag install.md into the Cursor chat"
|
|
5942
|
+
].join("\n")
|
|
5943
|
+
};
|
|
5944
|
+
}
|
|
5552
5945
|
if (code === "EPERM" || code === "EACCES" || /EPERM|EACCES/.test(msg)) {
|
|
5553
5946
|
return {
|
|
5554
5947
|
kind: "eperm",
|
|
@@ -5596,16 +5989,108 @@ function classifyInstallError(err) {
|
|
|
5596
5989
|
}
|
|
5597
5990
|
|
|
5598
5991
|
// src/commands/install.ts
|
|
5599
|
-
import
|
|
5992
|
+
import path36 from "path";
|
|
5600
5993
|
import { defineCommand as defineCommand11 } from "citty";
|
|
5994
|
+
import { bold, cyan as cyan3, green as green2, options as koloristOptions2 } from "kolorist";
|
|
5601
5995
|
|
|
5602
5996
|
// src/generator/personalization.ts
|
|
5603
|
-
import { readFile as
|
|
5604
|
-
import
|
|
5997
|
+
import { readFile as readFile18, writeFile as writeFile11 } from "fs/promises";
|
|
5998
|
+
import path34 from "path";
|
|
5999
|
+
|
|
6000
|
+
// src/generator/claude-command-adapters.ts
|
|
6001
|
+
import { readFile as readFile16, readdir as readdir7, writeFile as writeFile7 } from "fs/promises";
|
|
6002
|
+
import path30 from "path";
|
|
6003
|
+
var CURSOR_COMMANDS_DIR_REL = ".cursor/commands";
|
|
6004
|
+
var CLAUDE_COMMANDS_DIR_REL = ".claude/commands";
|
|
6005
|
+
var RESERVED_ADAPTER_NAMES = /* @__PURE__ */ new Set(["agent-kit"]);
|
|
6006
|
+
function parseCommandFrontmatter(name, raw) {
|
|
6007
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
|
|
6008
|
+
if (!match) return null;
|
|
6009
|
+
const body = match[1] ?? "";
|
|
6010
|
+
const descMatch = /^description:\s*(.+)$/m.exec(body);
|
|
6011
|
+
if (!descMatch) return null;
|
|
6012
|
+
const description = (descMatch[1] ?? "").trim();
|
|
6013
|
+
if (!description) return null;
|
|
6014
|
+
return { name, description };
|
|
6015
|
+
}
|
|
6016
|
+
function renderClaudeCommandAdapter(command) {
|
|
6017
|
+
return `---
|
|
6018
|
+
description: ${command.description}
|
|
6019
|
+
---
|
|
6020
|
+
|
|
6021
|
+
Read \`.cursor/commands/${command.name}.md\` now and follow that contract exactly \u2014 it is the source of truth for /${command.name}; this file is only a thin adapter for Claude Code.
|
|
6022
|
+
|
|
6023
|
+
Adapter rules (Claude Code CLI):
|
|
6024
|
+
- Cursor "Ask questions" is unavailable here: use AskUserQuestion when possible, else present the same labels as one numbered list per message and WAIT for the answer.
|
|
6025
|
+
- Skip or cancel means stop.
|
|
6026
|
+
- Never \`/git-prod\` without an explicit operator yes.
|
|
6027
|
+
- Do not clone Cursor hooks or invent behavior beyond the SoT file.
|
|
6028
|
+
`;
|
|
6029
|
+
}
|
|
6030
|
+
async function discoverInstalledCommands(rootDir) {
|
|
6031
|
+
const dir = path30.join(rootDir, CURSOR_COMMANDS_DIR_REL);
|
|
6032
|
+
let entries;
|
|
6033
|
+
try {
|
|
6034
|
+
entries = (await readdir7(dir)).filter((f) => f.endsWith(".md"));
|
|
6035
|
+
} catch {
|
|
6036
|
+
return [];
|
|
6037
|
+
}
|
|
6038
|
+
const commands = [];
|
|
6039
|
+
for (const file of entries.sort()) {
|
|
6040
|
+
const name = file.slice(0, -3);
|
|
6041
|
+
if (RESERVED_ADAPTER_NAMES.has(name)) continue;
|
|
6042
|
+
try {
|
|
6043
|
+
const raw = await readFile16(path30.join(dir, file), "utf8");
|
|
6044
|
+
const parsed = parseCommandFrontmatter(name, raw);
|
|
6045
|
+
if (parsed) commands.push(parsed);
|
|
6046
|
+
} catch {
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
return commands;
|
|
6050
|
+
}
|
|
6051
|
+
async function generateClaudeCommandAdapters(rootDir) {
|
|
6052
|
+
const commands = await discoverInstalledCommands(rootDir);
|
|
6053
|
+
if (commands.length === 0) return [];
|
|
6054
|
+
const ledger = await loadManagedHashLedger(rootDir);
|
|
6055
|
+
const results = [];
|
|
6056
|
+
let ledgerDirty = false;
|
|
6057
|
+
for (const command of commands) {
|
|
6058
|
+
const relPath = path30.posix.join(CLAUDE_COMMANDS_DIR_REL, `${command.name}.md`);
|
|
6059
|
+
const rendered = renderClaudeCommandAdapter(command);
|
|
6060
|
+
const abs = path30.join(rootDir, relPath);
|
|
6061
|
+
if (!await fileExists(abs)) {
|
|
6062
|
+
await ensureDir(path30.dirname(abs));
|
|
6063
|
+
await writeFile7(abs, rendered, "utf8");
|
|
6064
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
6065
|
+
ledgerDirty = true;
|
|
6066
|
+
results.push({ relativePath: relPath, status: "applied" });
|
|
6067
|
+
continue;
|
|
6068
|
+
}
|
|
6069
|
+
const localContent = await readFile16(abs, "utf8");
|
|
6070
|
+
if (localContent === rendered) {
|
|
6071
|
+
if (ledger.hashes[relPath] !== contentHash(rendered)) {
|
|
6072
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
6073
|
+
ledgerDirty = true;
|
|
6074
|
+
}
|
|
6075
|
+
results.push({ relativePath: relPath, status: "unchanged" });
|
|
6076
|
+
continue;
|
|
6077
|
+
}
|
|
6078
|
+
if (shouldPreserveCustomizedOverlay(localContent, ledger.hashes[relPath])) {
|
|
6079
|
+
results.push({ relativePath: relPath, status: "preserved-customized" });
|
|
6080
|
+
continue;
|
|
6081
|
+
}
|
|
6082
|
+
await writeFile7(abs, rendered, "utf8");
|
|
6083
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
6084
|
+
ledgerDirty = true;
|
|
6085
|
+
results.push({ relativePath: relPath, status: "refreshed" });
|
|
6086
|
+
}
|
|
6087
|
+
if (ledgerDirty) await saveManagedHashLedger(rootDir, ledger);
|
|
6088
|
+
return results;
|
|
6089
|
+
}
|
|
5605
6090
|
|
|
5606
6091
|
// src/generator/claude-kit-load.ts
|
|
5607
|
-
import { writeFile as
|
|
5608
|
-
import
|
|
6092
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
6093
|
+
import path31 from "path";
|
|
5609
6094
|
var CLAUDE_MD_REL = "CLAUDE.md";
|
|
5610
6095
|
var AGENT_KIT_COMMAND_REL = ".claude/commands/agent-kit.md";
|
|
5611
6096
|
function renderClaudeMd() {
|
|
@@ -5631,7 +6116,7 @@ Cursor Ask questions is not available in this CLI. When a command requires a cho
|
|
|
5631
6116
|
- Not Action A7 (Windsurf / VS Code generator parity)
|
|
5632
6117
|
- Not Claude external plan-review audits (\`/plan-external-review\`)
|
|
5633
6118
|
- Not \`--backend claude\` plan-loop ticks
|
|
5634
|
-
- Not a copy of Cursor \`
|
|
6119
|
+
- Not a copy of Cursor hooks beyond the opt-in SessionStart context adapter (\`agent-kit hook session-start --format claude\`); no \`.claude/rules/\` mirrors, no \`.claude/agents/\` generated from the registry
|
|
5635
6120
|
`;
|
|
5636
6121
|
}
|
|
5637
6122
|
function renderAgentKitCommand() {
|
|
@@ -5655,12 +6140,12 @@ Non-goals: not audits / \`/plan-external-review\`, not \`--backend claude\` tick
|
|
|
5655
6140
|
`;
|
|
5656
6141
|
}
|
|
5657
6142
|
async function writeUnlessExists(rootDir, relativePath, content) {
|
|
5658
|
-
const target =
|
|
6143
|
+
const target = path31.join(rootDir, relativePath);
|
|
5659
6144
|
if (await fileExists(target)) {
|
|
5660
6145
|
return { relativePath, status: "skipped-customized" };
|
|
5661
6146
|
}
|
|
5662
|
-
await ensureDir(
|
|
5663
|
-
await
|
|
6147
|
+
await ensureDir(path31.dirname(target));
|
|
6148
|
+
await writeFile8(target, content, "utf8");
|
|
5664
6149
|
return { relativePath, status: "applied" };
|
|
5665
6150
|
}
|
|
5666
6151
|
async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
@@ -5670,9 +6155,109 @@ async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
|
5670
6155
|
]);
|
|
5671
6156
|
}
|
|
5672
6157
|
|
|
6158
|
+
// src/generator/claude-session-start-hook.ts
|
|
6159
|
+
import { readFile as readFile17, writeFile as writeFile9 } from "fs/promises";
|
|
6160
|
+
import path32 from "path";
|
|
6161
|
+
var CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
6162
|
+
var RESOLVE_AGENT_KIT_REL = ".cursor/hooks/agent/resolve-agent-kit.sh";
|
|
6163
|
+
var SESSION_START_HOOK_MARKER = "hook session-start --format claude";
|
|
6164
|
+
var SESSION_START_DEGRADED_TEXT = "Agent Kit hooks are running in degraded fail-open mode: the agent-kit CLI could not be resolved (checked AGENT_KIT_HOOK_BIN, PATH, node_modules/.bin/agent-kit). Slash command adapters still work; session-context injection is inactive. Fix: install the CLI (npm i -D @dadado/agent-kit-cli) or set AGENT_KIT_HOOK_BIN.";
|
|
6165
|
+
function buildSessionStartHookCommand() {
|
|
6166
|
+
return `. "\${CLAUDE_PROJECT_DIR}/${RESOLVE_AGENT_KIT_REL}" 2>/dev/null && resolve_agent_kit && exec $AGENT_KIT_RESOLVED hook session-start --format claude; printf '%s' ${shellSingleQuote(SESSION_START_DEGRADED_TEXT)}`;
|
|
6167
|
+
}
|
|
6168
|
+
function shellSingleQuote(text) {
|
|
6169
|
+
return `'${text.replace(/'/g, "'\\''")}'`;
|
|
6170
|
+
}
|
|
6171
|
+
function buildSessionStartHookEntry() {
|
|
6172
|
+
return {
|
|
6173
|
+
type: "command",
|
|
6174
|
+
command: buildSessionStartHookCommand(),
|
|
6175
|
+
timeout: 15,
|
|
6176
|
+
statusMessage: "Loading Agent Kit session context"
|
|
6177
|
+
};
|
|
6178
|
+
}
|
|
6179
|
+
function isPlainObject2(value) {
|
|
6180
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6181
|
+
}
|
|
6182
|
+
function groupHasMarker(group) {
|
|
6183
|
+
if (!isPlainObject2(group) || !Array.isArray(group.hooks)) return false;
|
|
6184
|
+
return group.hooks.some(
|
|
6185
|
+
(h) => isPlainObject2(h) && typeof h.command === "string" && h.command.includes(SESSION_START_HOOK_MARKER)
|
|
6186
|
+
);
|
|
6187
|
+
}
|
|
6188
|
+
function mergeSessionStartHookIntoSettings(existingRaw) {
|
|
6189
|
+
const entry = buildSessionStartHookEntry();
|
|
6190
|
+
const newGroup = { hooks: [entry] };
|
|
6191
|
+
let root = {};
|
|
6192
|
+
if (existingRaw !== null && existingRaw.trim() !== "") {
|
|
6193
|
+
try {
|
|
6194
|
+
const parsed = JSON.parse(existingRaw);
|
|
6195
|
+
if (!isPlainObject2(parsed)) throw new Error("root is not an object");
|
|
6196
|
+
root = parsed;
|
|
6197
|
+
} catch {
|
|
6198
|
+
return {
|
|
6199
|
+
content: null,
|
|
6200
|
+
status: "unavailable",
|
|
6201
|
+
instructions: instructionsBlock(entry)
|
|
6202
|
+
};
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6205
|
+
const hooks = isPlainObject2(root.hooks) ? { ...root.hooks } : {};
|
|
6206
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? [...hooks.SessionStart] : [];
|
|
6207
|
+
const existingIndex = sessionStart.findIndex((g) => groupHasMarker(g));
|
|
6208
|
+
let status;
|
|
6209
|
+
if (existingIndex === -1) {
|
|
6210
|
+
sessionStart.push(newGroup);
|
|
6211
|
+
status = "applied";
|
|
6212
|
+
} else {
|
|
6213
|
+
const current = sessionStart[existingIndex];
|
|
6214
|
+
if (JSON.stringify(current) === JSON.stringify(newGroup)) {
|
|
6215
|
+
status = "unchanged";
|
|
6216
|
+
} else {
|
|
6217
|
+
sessionStart[existingIndex] = newGroup;
|
|
6218
|
+
status = "refreshed";
|
|
6219
|
+
}
|
|
6220
|
+
}
|
|
6221
|
+
hooks.SessionStart = sessionStart;
|
|
6222
|
+
root.hooks = hooks;
|
|
6223
|
+
return { content: `${JSON.stringify(root, null, 2)}
|
|
6224
|
+
`, status };
|
|
6225
|
+
}
|
|
6226
|
+
function instructionsBlock(entry) {
|
|
6227
|
+
return [
|
|
6228
|
+
`Could not parse the existing ${CLAUDE_SETTINGS_REL} as JSON, so Agent Kit did not touch it.`,
|
|
6229
|
+
"Add this hook by hand under hooks.SessionStart (create the arrays if they do not exist):",
|
|
6230
|
+
"",
|
|
6231
|
+
JSON.stringify(entry, null, 2)
|
|
6232
|
+
].join("\n");
|
|
6233
|
+
}
|
|
6234
|
+
async function writeClaudeSessionStartHook(rootDir) {
|
|
6235
|
+
const abs = path32.join(rootDir, CLAUDE_SETTINGS_REL);
|
|
6236
|
+
let existing = null;
|
|
6237
|
+
try {
|
|
6238
|
+
existing = await readFile17(abs, "utf8");
|
|
6239
|
+
} catch {
|
|
6240
|
+
existing = null;
|
|
6241
|
+
}
|
|
6242
|
+
const merged = mergeSessionStartHookIntoSettings(existing);
|
|
6243
|
+
if (merged.status === "unavailable" || merged.content === null) {
|
|
6244
|
+
return {
|
|
6245
|
+
relativePath: CLAUDE_SETTINGS_REL,
|
|
6246
|
+
status: "unavailable",
|
|
6247
|
+
instructions: merged.instructions
|
|
6248
|
+
};
|
|
6249
|
+
}
|
|
6250
|
+
if (merged.status === "unchanged") {
|
|
6251
|
+
return { relativePath: CLAUDE_SETTINGS_REL, status: "unchanged" };
|
|
6252
|
+
}
|
|
6253
|
+
await ensureDir(path32.dirname(abs));
|
|
6254
|
+
await writeFile9(abs, merged.content, "utf8");
|
|
6255
|
+
return { relativePath: CLAUDE_SETTINGS_REL, status: merged.status };
|
|
6256
|
+
}
|
|
6257
|
+
|
|
5673
6258
|
// src/generator/vscode.ts
|
|
5674
|
-
import { writeFile as
|
|
5675
|
-
import
|
|
6259
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
6260
|
+
import path33 from "path";
|
|
5676
6261
|
|
|
5677
6262
|
// src/generator/platform.ts
|
|
5678
6263
|
function gitProviderLabel(profile) {
|
|
@@ -5696,14 +6281,14 @@ function prTerminology(profile) {
|
|
|
5696
6281
|
// src/generator/vscode.ts
|
|
5697
6282
|
async function generateVSCodeArtifacts(profile) {
|
|
5698
6283
|
const results = [];
|
|
5699
|
-
const vscodeDir =
|
|
5700
|
-
const githubDir =
|
|
6284
|
+
const vscodeDir = path33.join(profile.rootDir, ".vscode");
|
|
6285
|
+
const githubDir = path33.join(profile.rootDir, ".github");
|
|
5701
6286
|
await Promise.all([ensureDir(vscodeDir), ensureDir(githubDir)]);
|
|
5702
|
-
const settingsPath =
|
|
6287
|
+
const settingsPath = path33.join(vscodeDir, "settings.json");
|
|
5703
6288
|
if (await fileExists(settingsPath)) {
|
|
5704
6289
|
results.push({ relativePath: ".vscode/settings.json", status: "skipped-customized" });
|
|
5705
6290
|
} else {
|
|
5706
|
-
await
|
|
6291
|
+
await writeFile10(
|
|
5707
6292
|
settingsPath,
|
|
5708
6293
|
`${JSON.stringify(
|
|
5709
6294
|
{
|
|
@@ -5723,11 +6308,11 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
5723
6308
|
}
|
|
5724
6309
|
const provider = gitProviderLabel(profile);
|
|
5725
6310
|
const prTerm = prTerminology(profile);
|
|
5726
|
-
const copilotPath =
|
|
6311
|
+
const copilotPath = path33.join(githubDir, "copilot-instructions.md");
|
|
5727
6312
|
if (await fileExists(copilotPath)) {
|
|
5728
6313
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "skipped-customized" });
|
|
5729
6314
|
} else {
|
|
5730
|
-
await
|
|
6315
|
+
await writeFile10(
|
|
5731
6316
|
copilotPath,
|
|
5732
6317
|
`# Copilot Instructions
|
|
5733
6318
|
|
|
@@ -5741,14 +6326,14 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
5741
6326
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "applied" });
|
|
5742
6327
|
}
|
|
5743
6328
|
if (profile.ide.plan === "vscode-pro") {
|
|
5744
|
-
const securityPath =
|
|
6329
|
+
const securityPath = path33.join(vscodeDir, "security-review.agent.md");
|
|
5745
6330
|
if (await fileExists(securityPath)) {
|
|
5746
6331
|
results.push({
|
|
5747
6332
|
relativePath: ".vscode/security-review.agent.md",
|
|
5748
6333
|
status: "skipped-customized"
|
|
5749
6334
|
});
|
|
5750
6335
|
} else {
|
|
5751
|
-
await
|
|
6336
|
+
await writeFile10(
|
|
5752
6337
|
securityPath,
|
|
5753
6338
|
"# Security Review Agent\n\nSpecialized mode for security review.\n",
|
|
5754
6339
|
"utf8"
|
|
@@ -5953,7 +6538,7 @@ function renderProjectContext(profile, skillItems = []) {
|
|
|
5953
6538
|
`;
|
|
5954
6539
|
}
|
|
5955
6540
|
async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
5956
|
-
const target =
|
|
6541
|
+
const target = path34.join(rootDir, relativePath);
|
|
5957
6542
|
if (await fileExists(target)) {
|
|
5958
6543
|
return {
|
|
5959
6544
|
kind: "file",
|
|
@@ -5963,8 +6548,8 @@ async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
|
5963
6548
|
evidence
|
|
5964
6549
|
};
|
|
5965
6550
|
}
|
|
5966
|
-
await ensureDir(
|
|
5967
|
-
await
|
|
6551
|
+
await ensureDir(path34.dirname(target));
|
|
6552
|
+
await writeFile11(target, content, "utf8");
|
|
5968
6553
|
return {
|
|
5969
6554
|
kind: "file",
|
|
5970
6555
|
id: relativePath,
|
|
@@ -5980,7 +6565,7 @@ async function packTargets(registryRoot, packId) {
|
|
|
5980
6565
|
async function existingTargets(projectRoot, targets) {
|
|
5981
6566
|
const checks = await Promise.all(
|
|
5982
6567
|
targets.map(
|
|
5983
|
-
async (target) => await fileExists(
|
|
6568
|
+
async (target) => await fileExists(path34.join(projectRoot, target)) ? target : null
|
|
5984
6569
|
)
|
|
5985
6570
|
);
|
|
5986
6571
|
return checks.filter((target) => target !== null);
|
|
@@ -6002,14 +6587,14 @@ async function applyPersonalization(input) {
|
|
|
6002
6587
|
componentResults.push({ ...item, status: "unavailable" });
|
|
6003
6588
|
continue;
|
|
6004
6589
|
}
|
|
6005
|
-
const target =
|
|
6590
|
+
const target = path34.posix.join(
|
|
6006
6591
|
".cursor",
|
|
6007
6592
|
"skills",
|
|
6008
6593
|
skill.path.includes("/core/") ? "core" : "community",
|
|
6009
6594
|
skill.id,
|
|
6010
6595
|
"SKILL.md"
|
|
6011
6596
|
);
|
|
6012
|
-
if (await fileExists(
|
|
6597
|
+
if (await fileExists(path34.join(input.rootDir, target))) {
|
|
6013
6598
|
componentResults.push({ ...item, status: "skipped-customized", path: target });
|
|
6014
6599
|
protectedPaths.add(target);
|
|
6015
6600
|
continue;
|
|
@@ -6068,6 +6653,41 @@ async function applyPersonalization(input) {
|
|
|
6068
6653
|
evidence: profileEvidence
|
|
6069
6654
|
};
|
|
6070
6655
|
});
|
|
6656
|
+
const claudeCommandItems = [];
|
|
6657
|
+
let claudeSessionStartInstructions;
|
|
6658
|
+
if (input.claudeAdapters) {
|
|
6659
|
+
const adapterResults = await generateClaudeCommandAdapters(input.rootDir);
|
|
6660
|
+
for (const artifact of adapterResults) {
|
|
6661
|
+
protectedPaths.add(artifact.relativePath);
|
|
6662
|
+
claudeCommandItems.push({
|
|
6663
|
+
kind: "file",
|
|
6664
|
+
id: artifact.relativePath,
|
|
6665
|
+
path: artifact.relativePath,
|
|
6666
|
+
// Overlay statuses (applied/unchanged/refreshed/preserved-customized)
|
|
6667
|
+
// fold onto the shared PersonalizationStatus union: anything written
|
|
6668
|
+
// or already current reads as "applied"; a hand-edited adapter left
|
|
6669
|
+
// alone reads as "skipped-customized" (same meaning as elsewhere in
|
|
6670
|
+
// this file — never silently clobbered).
|
|
6671
|
+
status: artifact.status === "preserved-customized" ? "skipped-customized" : "applied",
|
|
6672
|
+
evidence: profileEvidence
|
|
6673
|
+
});
|
|
6674
|
+
}
|
|
6675
|
+
const hookResult = await writeClaudeSessionStartHook(input.rootDir);
|
|
6676
|
+
protectedPaths.add(hookResult.relativePath);
|
|
6677
|
+
claudeCommandItems.push({
|
|
6678
|
+
kind: "file",
|
|
6679
|
+
id: hookResult.relativePath,
|
|
6680
|
+
path: hookResult.relativePath,
|
|
6681
|
+
// "unavailable" (existing .claude/settings.json unparseable) is a real
|
|
6682
|
+
// PersonalizationStatus value already; every other hook status folds
|
|
6683
|
+
// onto "applied" the same way the command-adapter statuses do above.
|
|
6684
|
+
status: hookResult.status === "unavailable" ? "unavailable" : "applied",
|
|
6685
|
+
evidence: profileEvidence
|
|
6686
|
+
});
|
|
6687
|
+
if (hookResult.status === "unavailable" && hookResult.instructions) {
|
|
6688
|
+
claudeSessionStartInstructions = hookResult.instructions;
|
|
6689
|
+
}
|
|
6690
|
+
}
|
|
6071
6691
|
const ideDetection = await detectIde(input.rootDir);
|
|
6072
6692
|
if (ideDetection.ide === "vscode" || ideDetection.ide === "other") {
|
|
6073
6693
|
const git = {
|
|
@@ -6115,10 +6735,11 @@ async function applyPersonalization(input) {
|
|
|
6115
6735
|
contractVersion: PERSONALIZATION_CONTRACT_VERSION,
|
|
6116
6736
|
generatorVersion: input.generatorVersion,
|
|
6117
6737
|
repositoryFingerprint: input.report.repositoryFingerprint,
|
|
6118
|
-
items: [...fileResults, ...claudeItems, ...componentResults],
|
|
6119
|
-
protectedPaths: [...protectedPaths].sort()
|
|
6738
|
+
items: [...fileResults, ...claudeItems, ...claudeCommandItems, ...componentResults],
|
|
6739
|
+
protectedPaths: [...protectedPaths].sort(),
|
|
6740
|
+
...claudeSessionStartInstructions ? { claudeSessionStartInstructions } : {}
|
|
6120
6741
|
};
|
|
6121
|
-
await writeJson(
|
|
6742
|
+
await writeJson(path34.join(input.rootDir, RESULT_PATH), result);
|
|
6122
6743
|
return {
|
|
6123
6744
|
result,
|
|
6124
6745
|
manifest: {
|
|
@@ -6136,26 +6757,26 @@ async function applyPersonalization(input) {
|
|
|
6136
6757
|
};
|
|
6137
6758
|
}
|
|
6138
6759
|
async function readRepositoryProfile(rootDir) {
|
|
6139
|
-
const target =
|
|
6760
|
+
const target = path34.join(rootDir, ".cursor/agent-kit.config.json");
|
|
6140
6761
|
if (!await fileExists(target)) return null;
|
|
6141
|
-
return JSON.parse(await
|
|
6762
|
+
return JSON.parse(await readFile18(target, "utf8"));
|
|
6142
6763
|
}
|
|
6143
6764
|
|
|
6144
6765
|
// src/lifecycle/onboard-migration.ts
|
|
6145
6766
|
import { createHash as createHash4 } from "crypto";
|
|
6146
|
-
import { readFile as
|
|
6147
|
-
import
|
|
6767
|
+
import { readFile as readFile19, unlink } from "fs/promises";
|
|
6768
|
+
import path35 from "path";
|
|
6148
6769
|
var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
|
|
6149
6770
|
var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
|
|
6150
6771
|
var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
|
|
6151
6772
|
"b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
|
|
6152
6773
|
]);
|
|
6153
6774
|
async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
|
|
6154
|
-
const legacyPath =
|
|
6775
|
+
const legacyPath = path35.join(projectRoot, LEGACY_ONBOARD_PATH);
|
|
6155
6776
|
if (!await fileExists(legacyPath)) return "absent";
|
|
6156
|
-
const namespacedPath =
|
|
6777
|
+
const namespacedPath = path35.join(projectRoot, NAMESPACED_ONBOARD_PATH);
|
|
6157
6778
|
if (!await fileExists(namespacedPath)) return "preserved-customized";
|
|
6158
|
-
const content = await
|
|
6779
|
+
const content = await readFile19(legacyPath);
|
|
6159
6780
|
const hash = createHash4("sha256").update(content).digest("hex");
|
|
6160
6781
|
if (!managedHashes.has(hash)) return "preserved-customized";
|
|
6161
6782
|
await unlink(legacyPath);
|
|
@@ -6214,6 +6835,9 @@ function parsePackList(raw) {
|
|
|
6214
6835
|
)
|
|
6215
6836
|
];
|
|
6216
6837
|
}
|
|
6838
|
+
function nextStepAfterInstall(pendingActions) {
|
|
6839
|
+
return pendingActions > 0 ? "Next: run /agent-kit-onboard in Cursor to resolve the first pending action." : "Next: run /start-project in Cursor when you have a deliverable.";
|
|
6840
|
+
}
|
|
6217
6841
|
function printReadinessNarrative(result) {
|
|
6218
6842
|
const { summary, pendingActions } = result.readiness;
|
|
6219
6843
|
const fixed = result.safeChanges.filter((change) => change.status === "applied").length;
|
|
@@ -6223,12 +6847,61 @@ function printReadinessNarrative(result) {
|
|
|
6223
6847
|
);
|
|
6224
6848
|
console.log(` safe fixes applied: ${fixed}`);
|
|
6225
6849
|
console.log(` pending actions: ${pendingActions.length}`);
|
|
6226
|
-
console.log(
|
|
6227
|
-
|
|
6228
|
-
|
|
6850
|
+
console.log(nextStepAfterInstall(pendingActions.length));
|
|
6851
|
+
}
|
|
6852
|
+
function paint(fn, text) {
|
|
6853
|
+
const prevEnabled = koloristOptions2.enabled;
|
|
6854
|
+
const prevSupportLevel = koloristOptions2.supportLevel;
|
|
6855
|
+
koloristOptions2.enabled = true;
|
|
6856
|
+
koloristOptions2.supportLevel = 3;
|
|
6857
|
+
try {
|
|
6858
|
+
return fn(text);
|
|
6859
|
+
} finally {
|
|
6860
|
+
koloristOptions2.enabled = prevEnabled;
|
|
6861
|
+
koloristOptions2.supportLevel = prevSupportLevel;
|
|
6862
|
+
}
|
|
6863
|
+
}
|
|
6864
|
+
function printInstallEpilogue(env, options = {}) {
|
|
6865
|
+
const print = options.print ?? ((line) => console.log(line));
|
|
6866
|
+
const color = options.color ?? shouldUseWelcomeColor();
|
|
6867
|
+
if (env.binOnPath) {
|
|
6868
|
+
const line = "`agent-kit` is on PATH \u2014 run it directly, e.g. `agent-kit doctor`.";
|
|
6869
|
+
print(color ? paint(green2, line) : line);
|
|
6870
|
+
return;
|
|
6871
|
+
}
|
|
6872
|
+
const divider = "\u2500".repeat(60);
|
|
6873
|
+
const body = [
|
|
6874
|
+
"You ran this through npx, so a bare `agent-kit` isn't on PATH yet.",
|
|
6875
|
+
'If you try `agent-kit <subcommand>` next, you will see "command not',
|
|
6876
|
+
'found". Pick one:',
|
|
6877
|
+
"",
|
|
6878
|
+
" 1. Keep using npx \u2014 works right now, no action needed",
|
|
6879
|
+
" npx @dadado/agent-kit-cli <subcommand>",
|
|
6880
|
+
"",
|
|
6881
|
+
" 2. Put a bare `agent-kit` on PATH",
|
|
6882
|
+
" npx @dadado/agent-kit-cli setup-global",
|
|
6883
|
+
" (fixes a root-owned npm prefix if that's the blocker, or just installs)",
|
|
6884
|
+
"",
|
|
6885
|
+
" 3. Manual steps",
|
|
6886
|
+
" See docs/getting-started.md (Troubleshooting npm failures), or:",
|
|
6887
|
+
" mkdir -p ~/.npm-global",
|
|
6888
|
+
' npm config set prefix "~/.npm-global"',
|
|
6889
|
+
' export PATH="~/.npm-global/bin:$PATH"',
|
|
6890
|
+
" npm i -g @dadado/agent-kit-cli"
|
|
6891
|
+
];
|
|
6892
|
+
print(color ? paint(cyan3, divider) : divider);
|
|
6893
|
+
const heading = "Heads up: a bare `agent-kit` command won't work yet";
|
|
6894
|
+
print(color ? paint(bold, paint(cyan3, heading)) : heading);
|
|
6895
|
+
for (const line of body) print(line);
|
|
6896
|
+
print(color ? paint(cyan3, divider) : divider);
|
|
6897
|
+
}
|
|
6898
|
+
async function printPostInstallSummary(result) {
|
|
6899
|
+
printReadinessNarrative(result);
|
|
6900
|
+
const env = await assessEnvironment();
|
|
6901
|
+
printInstallEpilogue(env);
|
|
6229
6902
|
}
|
|
6230
6903
|
async function performInstall(options) {
|
|
6231
|
-
const projectRoot =
|
|
6904
|
+
const projectRoot = path36.resolve(options.cwd);
|
|
6232
6905
|
const packs = parsePackList(options.pack);
|
|
6233
6906
|
const existing = await loadAgentKitManifest(projectRoot);
|
|
6234
6907
|
const registry = await resolveRegistryFromCli({
|
|
@@ -6256,6 +6929,7 @@ async function performInstall(options) {
|
|
|
6256
6929
|
generatorVersion: KIT_VERSION
|
|
6257
6930
|
});
|
|
6258
6931
|
let readiness = readinessExecution.after;
|
|
6932
|
+
let claudeSessionStartInstructions;
|
|
6259
6933
|
const profile = await readRepositoryProfile(projectRoot);
|
|
6260
6934
|
if (profile) {
|
|
6261
6935
|
const registryIndex = await loadRegistry(registry.root);
|
|
@@ -6266,9 +6940,11 @@ async function performInstall(options) {
|
|
|
6266
6940
|
report: readinessExecution.after,
|
|
6267
6941
|
registry: registryIndex,
|
|
6268
6942
|
manifest: draft,
|
|
6269
|
-
generatorVersion: KIT_VERSION
|
|
6943
|
+
generatorVersion: KIT_VERSION,
|
|
6944
|
+
claudeAdapters: options.claudeAdapters
|
|
6270
6945
|
});
|
|
6271
6946
|
await saveManifest(projectRoot, personalization.manifest);
|
|
6947
|
+
claudeSessionStartInstructions = personalization.result.claudeSessionStartInstructions;
|
|
6272
6948
|
readiness = createReadinessReport(await runScanner(projectRoot), {
|
|
6273
6949
|
generatorVersion: KIT_VERSION
|
|
6274
6950
|
});
|
|
@@ -6280,7 +6956,8 @@ async function performInstall(options) {
|
|
|
6280
6956
|
manifestPath,
|
|
6281
6957
|
stats,
|
|
6282
6958
|
readiness,
|
|
6283
|
-
safeChanges: readinessExecution.changes
|
|
6959
|
+
safeChanges: readinessExecution.changes,
|
|
6960
|
+
...claudeSessionStartInstructions ? { claudeSessionStartInstructions } : {}
|
|
6284
6961
|
};
|
|
6285
6962
|
} finally {
|
|
6286
6963
|
await registry.unlock?.();
|
|
@@ -6312,6 +6989,11 @@ var installCommand = defineCommand11({
|
|
|
6312
6989
|
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
6313
6990
|
default: false
|
|
6314
6991
|
},
|
|
6992
|
+
claude: {
|
|
6993
|
+
type: "boolean",
|
|
6994
|
+
description: "Opt-in: generate .claude/commands/*.md thin pointer adapters for the installed .cursor/commands set, and merge a SessionStart context hook into .claude/settings.json (default install behavior is unchanged without this flag)",
|
|
6995
|
+
default: false
|
|
6996
|
+
},
|
|
6315
6997
|
cwd: {
|
|
6316
6998
|
type: "string",
|
|
6317
6999
|
default: process.cwd()
|
|
@@ -6333,6 +7015,9 @@ var installCommand = defineCommand11({
|
|
|
6333
7015
|
} catch (err) {
|
|
6334
7016
|
if (err instanceof RootRefusedError) {
|
|
6335
7017
|
logger.error(err.message);
|
|
7018
|
+
if (err.recovery) console.error(`
|
|
7019
|
+
${err.recovery}
|
|
7020
|
+
`);
|
|
6336
7021
|
process.exitCode = 1;
|
|
6337
7022
|
return;
|
|
6338
7023
|
}
|
|
@@ -6355,13 +7040,20 @@ var installCommand = defineCommand11({
|
|
|
6355
7040
|
registry: args.registry,
|
|
6356
7041
|
url: args.url,
|
|
6357
7042
|
ref: args.ref,
|
|
6358
|
-
refresh: args.refresh
|
|
7043
|
+
refresh: args.refresh,
|
|
7044
|
+
claudeAdapters: args.claude
|
|
6359
7045
|
})
|
|
6360
7046
|
);
|
|
6361
7047
|
logApplyStats(result.stats);
|
|
6362
7048
|
logger.success(`Manifest written: ${result.manifestPath}`);
|
|
6363
7049
|
logger.success("Readiness snapshot written: .cursor/context/readiness.json");
|
|
6364
|
-
|
|
7050
|
+
await printPostInstallSummary(result);
|
|
7051
|
+
if (result.claudeSessionStartInstructions) {
|
|
7052
|
+
logger.warn("Could not merge the Claude Code SessionStart hook automatically:");
|
|
7053
|
+
console.log(`
|
|
7054
|
+
${result.claudeSessionStartInstructions}
|
|
7055
|
+
`);
|
|
7056
|
+
}
|
|
6365
7057
|
} catch (err) {
|
|
6366
7058
|
const hint = classifyInstallError(err);
|
|
6367
7059
|
logger.error(hint.message);
|
|
@@ -6388,25 +7080,57 @@ var initCommand = defineCommand12({
|
|
|
6388
7080
|
type: "string",
|
|
6389
7081
|
description: "Project root directory",
|
|
6390
7082
|
default: process.cwd()
|
|
7083
|
+
},
|
|
7084
|
+
yes: {
|
|
7085
|
+
type: "boolean",
|
|
7086
|
+
alias: "y",
|
|
7087
|
+
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
7088
|
+
default: false
|
|
7089
|
+
},
|
|
7090
|
+
"force-root": {
|
|
7091
|
+
type: "boolean",
|
|
7092
|
+
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
7093
|
+
default: false
|
|
6391
7094
|
}
|
|
6392
7095
|
},
|
|
6393
7096
|
async run({ args }) {
|
|
6394
|
-
const nonInteractive = isNonInteractive();
|
|
7097
|
+
const nonInteractive = args.yes || isNonInteractive();
|
|
6395
7098
|
if (!nonInteractive) {
|
|
6396
7099
|
intro(`agent-kit v${KIT_VERSION}`);
|
|
6397
7100
|
} else {
|
|
6398
7101
|
logger.info(`agent-kit v${KIT_VERSION} (non-interactive mode)`);
|
|
6399
7102
|
}
|
|
6400
7103
|
logger.info("init now uses the canonical install and readiness workflow.");
|
|
7104
|
+
let projectRoot;
|
|
6401
7105
|
try {
|
|
6402
|
-
|
|
7106
|
+
projectRoot = await confirmProjectRoot(args.cwd, {
|
|
7107
|
+
nonInteractive,
|
|
7108
|
+
command: "install",
|
|
7109
|
+
forceRoot: args["force-root"]
|
|
7110
|
+
});
|
|
7111
|
+
} catch (err) {
|
|
7112
|
+
if (err instanceof RootRefusedError) {
|
|
7113
|
+
logger.error(err.message);
|
|
7114
|
+
if (err.recovery) console.error(`
|
|
7115
|
+
${err.recovery}
|
|
7116
|
+
`);
|
|
7117
|
+
process.exitCode = 1;
|
|
7118
|
+
return;
|
|
7119
|
+
}
|
|
7120
|
+
throw err;
|
|
7121
|
+
}
|
|
7122
|
+
try {
|
|
7123
|
+
const result = await withCliProgress("init", () => runInitCompatibility(projectRoot));
|
|
6403
7124
|
const pending = result.readiness.pendingActions.length;
|
|
6404
7125
|
logger.success(`L0 and readiness prepared in ${result.projectRoot}`);
|
|
6405
|
-
const nextStep = pending
|
|
7126
|
+
const nextStep = nextStepAfterInstall(pending);
|
|
7127
|
+
const env = await assessEnvironment();
|
|
6406
7128
|
if (!nonInteractive) {
|
|
7129
|
+
printInstallEpilogue(env);
|
|
6407
7130
|
outro(nextStep);
|
|
6408
7131
|
} else {
|
|
6409
7132
|
logger.info(nextStep);
|
|
7133
|
+
printInstallEpilogue(env);
|
|
6410
7134
|
}
|
|
6411
7135
|
} catch (err) {
|
|
6412
7136
|
const hint = classifyInstallError(err);
|
|
@@ -6420,13 +7144,13 @@ ${hint.recovery}
|
|
|
6420
7144
|
});
|
|
6421
7145
|
|
|
6422
7146
|
// src/commands/monitors.ts
|
|
6423
|
-
import
|
|
7147
|
+
import path38 from "path";
|
|
6424
7148
|
import { defineCommand as defineCommand13 } from "citty";
|
|
6425
7149
|
|
|
6426
7150
|
// src/invariants/monitors-untriaged.ts
|
|
6427
7151
|
import { execFile as execFile5 } from "child_process";
|
|
6428
|
-
import { readFile as
|
|
6429
|
-
import
|
|
7152
|
+
import { readFile as readFile20, readdir as readdir8, stat as stat5 } from "fs/promises";
|
|
7153
|
+
import path37 from "path";
|
|
6430
7154
|
import { promisify as promisify5 } from "util";
|
|
6431
7155
|
|
|
6432
7156
|
// src/invariants/triage-heading.ts
|
|
@@ -6450,7 +7174,7 @@ function hasOpenGaps(content) {
|
|
|
6450
7174
|
}
|
|
6451
7175
|
async function listMonitorFiles(memoryDir) {
|
|
6452
7176
|
try {
|
|
6453
|
-
const names = await
|
|
7177
|
+
const names = await readdir8(memoryDir);
|
|
6454
7178
|
return names.filter((n) => n.startsWith("plan-monitor-") && n.endsWith(".md")).sort();
|
|
6455
7179
|
} catch {
|
|
6456
7180
|
return [];
|
|
@@ -6467,7 +7191,7 @@ async function gitFreshMonitorNames(rootDir) {
|
|
|
6467
7191
|
for (const line of stdout.split("\n")) {
|
|
6468
7192
|
if (!line.trim()) continue;
|
|
6469
7193
|
const file = line.slice(3).trim().replace(/^.* -> /, "");
|
|
6470
|
-
const base =
|
|
7194
|
+
const base = path37.basename(file);
|
|
6471
7195
|
if (base.startsWith("plan-monitor-") && base.endsWith(".md")) {
|
|
6472
7196
|
names.add(base);
|
|
6473
7197
|
}
|
|
@@ -6490,15 +7214,15 @@ function monitorSlugFromName(fileName) {
|
|
|
6490
7214
|
return fileName.replace(/^plan-monitor-/, "").replace(/\.md$/, "").toLowerCase();
|
|
6491
7215
|
}
|
|
6492
7216
|
async function selectUntriagedMonitors(rootDir) {
|
|
6493
|
-
const root =
|
|
6494
|
-
const memoryDir =
|
|
7217
|
+
const root = path37.resolve(rootDir);
|
|
7218
|
+
const memoryDir = path37.join(root, ".cursor", "memory");
|
|
6495
7219
|
const allNames = await listMonitorFiles(memoryDir);
|
|
6496
7220
|
const selectionOrder = ["git-fresh", "handoff-aligned", "untriaged-scan"];
|
|
6497
7221
|
const byName = /* @__PURE__ */ new Map();
|
|
6498
7222
|
for (const name of allNames) {
|
|
6499
|
-
const abs =
|
|
7223
|
+
const abs = path37.join(memoryDir, name);
|
|
6500
7224
|
try {
|
|
6501
|
-
const [content, st] = await Promise.all([
|
|
7225
|
+
const [content, st] = await Promise.all([readFile20(abs, "utf8"), stat5(abs)]);
|
|
6502
7226
|
byName.set(name, { content, mtimeMs: st.mtimeMs });
|
|
6503
7227
|
} catch {
|
|
6504
7228
|
}
|
|
@@ -6511,7 +7235,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
6511
7235
|
const gitFreshSet = [...gitFresh].filter(untriaged).sort();
|
|
6512
7236
|
let handoff = "";
|
|
6513
7237
|
try {
|
|
6514
|
-
handoff = await
|
|
7238
|
+
handoff = await readFile20(path37.join(root, ".cursor", "HANDOFF.md"), "utf8");
|
|
6515
7239
|
} catch {
|
|
6516
7240
|
handoff = "";
|
|
6517
7241
|
}
|
|
@@ -6531,8 +7255,8 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
6531
7255
|
const row = byName.get(name);
|
|
6532
7256
|
if (!row) continue;
|
|
6533
7257
|
entries.push({
|
|
6534
|
-
path:
|
|
6535
|
-
relativePath:
|
|
7258
|
+
path: path37.join(memoryDir, name),
|
|
7259
|
+
relativePath: path37.relative(root, path37.join(memoryDir, name)).split(path37.sep).join("/"),
|
|
6536
7260
|
mtimeMs: row.mtimeMs,
|
|
6537
7261
|
hasTriageHeading: false,
|
|
6538
7262
|
hasOpenGaps: hasOpenGaps(row.content),
|
|
@@ -6578,7 +7302,7 @@ var monitorsCommand = defineCommand13({
|
|
|
6578
7302
|
process.exitCode = 2;
|
|
6579
7303
|
return;
|
|
6580
7304
|
}
|
|
6581
|
-
const result = await selectUntriagedMonitors(
|
|
7305
|
+
const result = await selectUntriagedMonitors(path38.resolve(args.cwd));
|
|
6582
7306
|
if (args.json) {
|
|
6583
7307
|
console.log(JSON.stringify(result, null, 2));
|
|
6584
7308
|
return;
|
|
@@ -6594,7 +7318,7 @@ var monitorsCommand = defineCommand13({
|
|
|
6594
7318
|
});
|
|
6595
7319
|
|
|
6596
7320
|
// src/commands/run-plan.ts
|
|
6597
|
-
import
|
|
7321
|
+
import path43 from "path";
|
|
6598
7322
|
import { defineCommand as defineCommand14 } from "citty";
|
|
6599
7323
|
|
|
6600
7324
|
// src/plan-loop/backends.ts
|
|
@@ -6679,14 +7403,14 @@ function listBackendIds() {
|
|
|
6679
7403
|
}
|
|
6680
7404
|
|
|
6681
7405
|
// src/plan-loop/run-loop.ts
|
|
6682
|
-
import { mkdir as mkdir6, readFile as
|
|
6683
|
-
import
|
|
7406
|
+
import { mkdir as mkdir6, readFile as readFile23, rm as rm2, unlink as unlink2 } from "fs/promises";
|
|
7407
|
+
import path42 from "path";
|
|
6684
7408
|
|
|
6685
7409
|
// src/plan-loop/external-review.ts
|
|
6686
7410
|
import { spawn as spawn7 } from "child_process";
|
|
6687
|
-
import
|
|
6688
|
-
var CANONICAL_REL =
|
|
6689
|
-
var FALLBACK_REL =
|
|
7411
|
+
import path39 from "path";
|
|
7412
|
+
var CANONICAL_REL = path39.join(".cursor", "scripts", "plan-external-review.sh");
|
|
7413
|
+
var FALLBACK_REL = path39.join("scripts", "plan-external-review.sh");
|
|
6690
7414
|
function isPlanExhaustedReason(reason) {
|
|
6691
7415
|
const r = reason.trim().toLowerCase();
|
|
6692
7416
|
if (!r) return false;
|
|
@@ -6708,8 +7432,8 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
6708
7432
|
const existsFn = options.existsFn ?? fileExists;
|
|
6709
7433
|
const log = options.log ?? ((line) => console.log(line));
|
|
6710
7434
|
const force = options.force === true;
|
|
6711
|
-
const canonicalPath =
|
|
6712
|
-
const fallbackPath =
|
|
7435
|
+
const canonicalPath = path39.join(root, CANONICAL_REL);
|
|
7436
|
+
const fallbackPath = path39.join(root, FALLBACK_REL);
|
|
6713
7437
|
let scriptPath = null;
|
|
6714
7438
|
let scriptRel = CANONICAL_REL;
|
|
6715
7439
|
if (await existsFn(canonicalPath)) {
|
|
@@ -6762,12 +7486,12 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
6762
7486
|
}
|
|
6763
7487
|
|
|
6764
7488
|
// src/plan-loop/persona-banners.ts
|
|
6765
|
-
import
|
|
7489
|
+
import path40 from "path";
|
|
6766
7490
|
import {
|
|
6767
7491
|
blue,
|
|
6768
|
-
cyan as
|
|
7492
|
+
cyan as cyan4,
|
|
6769
7493
|
gray as gray2,
|
|
6770
|
-
green as
|
|
7494
|
+
green as green3,
|
|
6771
7495
|
lightGray,
|
|
6772
7496
|
lightGreen,
|
|
6773
7497
|
magenta,
|
|
@@ -6780,8 +7504,8 @@ var DEFAULT_CLI_PERSONA_ID = "ghost-runner";
|
|
|
6780
7504
|
var COLORS = {
|
|
6781
7505
|
white,
|
|
6782
7506
|
gray: gray2,
|
|
6783
|
-
green:
|
|
6784
|
-
cyan:
|
|
7507
|
+
green: green3,
|
|
7508
|
+
cyan: cyan4,
|
|
6785
7509
|
magenta,
|
|
6786
7510
|
yellow: yellow2,
|
|
6787
7511
|
red: red2,
|
|
@@ -6798,7 +7522,7 @@ function resolveColor(name, fallback) {
|
|
|
6798
7522
|
async function resolveCliPersonaId(root) {
|
|
6799
7523
|
try {
|
|
6800
7524
|
const cfg = await readJson(
|
|
6801
|
-
|
|
7525
|
+
path40.join(root, ".cursor", "context", "config.json")
|
|
6802
7526
|
);
|
|
6803
7527
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
6804
7528
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -6809,7 +7533,7 @@ async function resolveCliPersonaId(root) {
|
|
|
6809
7533
|
}
|
|
6810
7534
|
async function loadPersonaPack(root, personaId) {
|
|
6811
7535
|
try {
|
|
6812
|
-
const personaPath =
|
|
7536
|
+
const personaPath = path40.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
6813
7537
|
const pack = await readJson(personaPath);
|
|
6814
7538
|
if (!pack || typeof pack.id !== "string") return null;
|
|
6815
7539
|
return pack;
|
|
@@ -6827,7 +7551,7 @@ function createPersonaBannerPrinter(persona) {
|
|
|
6827
7551
|
if (!banners.tickStart && !banners.tickEnd && !banners.phaseComplete) return null;
|
|
6828
7552
|
const primary = resolveColor(persona.ansiPalette?.primary, white);
|
|
6829
7553
|
const secondary = resolveColor(persona.ansiPalette?.secondary, gray2);
|
|
6830
|
-
const accent = resolveColor(persona.ansiPalette?.accent,
|
|
7554
|
+
const accent = resolveColor(persona.ansiPalette?.accent, green3);
|
|
6831
7555
|
return {
|
|
6832
7556
|
tickStart(detail) {
|
|
6833
7557
|
if (banners.tickStart) {
|
|
@@ -6858,8 +7582,8 @@ function createPersonaBannerPrinter(persona) {
|
|
|
6858
7582
|
}
|
|
6859
7583
|
|
|
6860
7584
|
// src/plan-loop/plan-state.ts
|
|
6861
|
-
import { readFile as
|
|
6862
|
-
import
|
|
7585
|
+
import { readFile as readFile21, readdir as readdir9 } from "fs/promises";
|
|
7586
|
+
import path41 from "path";
|
|
6863
7587
|
function countPendingTodos(raw) {
|
|
6864
7588
|
const lines = raw.split(/\r?\n/);
|
|
6865
7589
|
let inFront = 0;
|
|
@@ -6886,15 +7610,15 @@ function countPendingTodos(raw) {
|
|
|
6886
7610
|
}
|
|
6887
7611
|
async function findActivePlanFile(plansDir) {
|
|
6888
7612
|
if (!await fileExists(plansDir)) return null;
|
|
6889
|
-
const files = (await
|
|
6890
|
-
return files[0] ?
|
|
7613
|
+
const files = (await readdir9(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
7614
|
+
return files[0] ? path41.join(plansDir, files[0]) : null;
|
|
6891
7615
|
}
|
|
6892
7616
|
async function readPlan(planPath) {
|
|
6893
|
-
return
|
|
7617
|
+
return readFile21(planPath, "utf8");
|
|
6894
7618
|
}
|
|
6895
7619
|
|
|
6896
7620
|
// src/plan-loop/sentinel.ts
|
|
6897
|
-
import { readFile as
|
|
7621
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
6898
7622
|
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—\-].*)?)/i;
|
|
6899
7623
|
function takeFromText(text) {
|
|
6900
7624
|
if (!text) return null;
|
|
@@ -6941,7 +7665,7 @@ function parseSentinelFromLog(content) {
|
|
|
6941
7665
|
}
|
|
6942
7666
|
async function parseSentinelFromLogFile(logPath) {
|
|
6943
7667
|
try {
|
|
6944
|
-
const content = await
|
|
7668
|
+
const content = await readFile22(logPath, "utf8");
|
|
6945
7669
|
return parseSentinelFromLog(content);
|
|
6946
7670
|
} catch {
|
|
6947
7671
|
return { kind: "missing" };
|
|
@@ -6964,9 +7688,9 @@ function sleep(ms) {
|
|
|
6964
7688
|
return new Promise((r) => setTimeout(r, ms));
|
|
6965
7689
|
}
|
|
6966
7690
|
async function runPlanLoop(opts) {
|
|
6967
|
-
const plansDir =
|
|
6968
|
-
const stopFile =
|
|
6969
|
-
const logDir =
|
|
7691
|
+
const plansDir = path42.join(opts.root, ".cursor", "plans");
|
|
7692
|
+
const stopFile = path42.join(opts.root, ".cursor", "loop.stop");
|
|
7693
|
+
const logDir = path42.join(opts.root, ".cursor", "loop-logs");
|
|
6970
7694
|
const planPath = await findActivePlanFile(plansDir);
|
|
6971
7695
|
if (!planPath) {
|
|
6972
7696
|
logger.error("No active plan in .cursor/plans/");
|
|
@@ -6987,7 +7711,7 @@ async function runPlanLoop(opts) {
|
|
|
6987
7711
|
try {
|
|
6988
7712
|
const persona = await loadCliRunPlanPersona(opts.root);
|
|
6989
7713
|
const banners = createPersonaBannerPrinter(persona);
|
|
6990
|
-
console.log(`Active plan: ${
|
|
7714
|
+
console.log(`Active plan: ${path42.basename(planPath)}`);
|
|
6991
7715
|
console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
|
|
6992
7716
|
console.log(`Backend: ${opts.backend.id}`);
|
|
6993
7717
|
if (persona) {
|
|
@@ -7030,8 +7754,8 @@ async function runPlanLoop(opts) {
|
|
|
7030
7754
|
planExhausted = true;
|
|
7031
7755
|
break;
|
|
7032
7756
|
}
|
|
7033
|
-
const logPath =
|
|
7034
|
-
const relLog =
|
|
7757
|
+
const logPath = path42.join(logDir, `tick-${stamp()}.log`);
|
|
7758
|
+
const relLog = path42.relative(opts.root, logPath);
|
|
7035
7759
|
console.log("");
|
|
7036
7760
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
7037
7761
|
if (banners) banners.tickStart(tickLine);
|
|
@@ -7053,7 +7777,7 @@ async function runPlanLoop(opts) {
|
|
|
7053
7777
|
return 1;
|
|
7054
7778
|
}
|
|
7055
7779
|
try {
|
|
7056
|
-
const logText = await
|
|
7780
|
+
const logText = await readFile23(logPath, "utf8");
|
|
7057
7781
|
if (logText.includes("Too many MCP tools")) {
|
|
7058
7782
|
const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
|
|
7059
7783
|
if (banners) banners.stop(msg);
|
|
@@ -7110,7 +7834,7 @@ async function runPlanLoop(opts) {
|
|
|
7110
7834
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
7111
7835
|
if (banners) banners.phaseComplete(finishDetail);
|
|
7112
7836
|
console.log(
|
|
7113
|
-
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${
|
|
7837
|
+
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path42.relative(opts.root, logDir)}/`
|
|
7114
7838
|
);
|
|
7115
7839
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
7116
7840
|
await armExternalPlanReview(opts.root);
|
|
@@ -7181,7 +7905,7 @@ var runPlanCommand = defineCommand14({
|
|
|
7181
7905
|
return;
|
|
7182
7906
|
}
|
|
7183
7907
|
const code = await runPlanLoop({
|
|
7184
|
-
root:
|
|
7908
|
+
root: path43.resolve(args.cwd),
|
|
7185
7909
|
maxTicks,
|
|
7186
7910
|
sleepSeconds,
|
|
7187
7911
|
model: args.model ? String(args.model) : void 0,
|
|
@@ -7214,9 +7938,286 @@ var scanCommand = defineCommand15({
|
|
|
7214
7938
|
}
|
|
7215
7939
|
});
|
|
7216
7940
|
|
|
7217
|
-
// src/commands/
|
|
7218
|
-
import
|
|
7941
|
+
// src/commands/setup-global.ts
|
|
7942
|
+
import { spawn as spawn8 } from "child_process";
|
|
7943
|
+
import { appendFile, mkdir as mkdir7, readFile as readFile24, writeFile as writeFile12 } from "fs/promises";
|
|
7944
|
+
import { homedir as homedir4 } from "os";
|
|
7945
|
+
import path44 from "path";
|
|
7946
|
+
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
7219
7947
|
import { defineCommand as defineCommand16 } from "citty";
|
|
7948
|
+
import { cyan as cyan5, green as green4, yellow as yellow3 } from "kolorist";
|
|
7949
|
+
var NPM_GLOBAL_DIR_NAME = ".npm-global";
|
|
7950
|
+
var SETUP_GLOBAL_MARKER = "# agent-kit setup-global";
|
|
7951
|
+
var DEFAULT_PACKAGE_SPEC = "@dadado/agent-kit-cli";
|
|
7952
|
+
function planSetupGlobalSteps(env, options = {}) {
|
|
7953
|
+
const homeDir = options.homeDir ?? homedir4();
|
|
7954
|
+
const packageSpec = options.packageSpec ?? DEFAULT_PACKAGE_SPEC;
|
|
7955
|
+
const npmGlobalDir = path44.join(homeDir, NPM_GLOBAL_DIR_NAME);
|
|
7956
|
+
const npmGlobalBin = path44.join(npmGlobalDir, "bin");
|
|
7957
|
+
const npmrcPath = path44.join(homeDir, ".npmrc");
|
|
7958
|
+
const npmrcPrefixValue = `~/${NPM_GLOBAL_DIR_NAME}`;
|
|
7959
|
+
const pathExportLine = `export PATH="${npmGlobalBin}:$PATH"`;
|
|
7960
|
+
const shellProfile = env.shellProfile;
|
|
7961
|
+
const shellSupported = shellProfile != null;
|
|
7962
|
+
const steps = [
|
|
7963
|
+
{
|
|
7964
|
+
id: "set-prefix",
|
|
7965
|
+
title: "Set npm's global install prefix to a folder you own",
|
|
7966
|
+
detail: [
|
|
7967
|
+
`mkdir -p ${npmGlobalDir}`,
|
|
7968
|
+
`npm config set prefix "${npmrcPrefixValue}" (writes "prefix = ${npmrcPrefixValue}" to ${npmrcPath})`
|
|
7969
|
+
]
|
|
7970
|
+
},
|
|
7971
|
+
{
|
|
7972
|
+
id: "append-path",
|
|
7973
|
+
title: shellSupported ? `Put ${npmGlobalBin} on PATH via ${shellProfile}` : "Put npm's global bin on PATH (manual \u2014 shell not auto-detected)",
|
|
7974
|
+
detail: shellSupported ? [`Append to ${shellProfile}:`, ` ${SETUP_GLOBAL_MARKER}`, ` ${pathExportLine}`] : [
|
|
7975
|
+
`Shell could not be auto-detected as zsh or bash (detected: ${env.shell ?? "unknown"}).`,
|
|
7976
|
+
"You'll need to add this line to your shell's startup file yourself:",
|
|
7977
|
+
` ${pathExportLine}`
|
|
7978
|
+
]
|
|
7979
|
+
},
|
|
7980
|
+
{
|
|
7981
|
+
id: "npm-install",
|
|
7982
|
+
title: `Reinstall ${packageSpec} globally, now into the new prefix`,
|
|
7983
|
+
detail: [`npm i -g ${packageSpec}`]
|
|
7984
|
+
},
|
|
7985
|
+
{
|
|
7986
|
+
id: "verify",
|
|
7987
|
+
title: "Verify `agent-kit` resolves on PATH",
|
|
7988
|
+
detail: [
|
|
7989
|
+
"Re-check whether a bare `agent-kit` resolves on PATH.",
|
|
7990
|
+
"Needs a new shell session (or `source` the profile) to take effect \u2014 this process's own PATH can't reflect it."
|
|
7991
|
+
]
|
|
7992
|
+
}
|
|
7993
|
+
];
|
|
7994
|
+
return {
|
|
7995
|
+
packageSpec,
|
|
7996
|
+
homeDir,
|
|
7997
|
+
npmGlobalDir,
|
|
7998
|
+
npmGlobalBin,
|
|
7999
|
+
npmrcPath,
|
|
8000
|
+
npmrcPrefixValue,
|
|
8001
|
+
pathExportLine,
|
|
8002
|
+
markerComment: SETUP_GLOBAL_MARKER,
|
|
8003
|
+
shellProfile,
|
|
8004
|
+
shellSupported,
|
|
8005
|
+
alreadyWritable: env.npmPrefixWritable,
|
|
8006
|
+
currentPrefix: env.npmPrefix.prefix,
|
|
8007
|
+
steps
|
|
8008
|
+
};
|
|
8009
|
+
}
|
|
8010
|
+
function upsertNpmrcPrefix(content, prefixValue) {
|
|
8011
|
+
const line = `prefix = ${prefixValue}`;
|
|
8012
|
+
const prefixLineRe = /^\s*prefix\s*=.*$/m;
|
|
8013
|
+
if (prefixLineRe.test(content)) {
|
|
8014
|
+
return content.replace(prefixLineRe, line);
|
|
8015
|
+
}
|
|
8016
|
+
const withTrailingNewline = content.length > 0 && !content.endsWith("\n") ? `${content}
|
|
8017
|
+
` : content;
|
|
8018
|
+
return `${withTrailingNewline}${line}
|
|
8019
|
+
`;
|
|
8020
|
+
}
|
|
8021
|
+
var defaultFsImpl = {
|
|
8022
|
+
mkdir: async (dir) => {
|
|
8023
|
+
await mkdir7(dir, { recursive: true });
|
|
8024
|
+
},
|
|
8025
|
+
readFile: (filePath) => readFile24(filePath, "utf8"),
|
|
8026
|
+
writeFile: (filePath, content) => writeFile12(filePath, content, "utf8"),
|
|
8027
|
+
appendFile: (filePath, content) => appendFile(filePath, content, "utf8")
|
|
8028
|
+
};
|
|
8029
|
+
async function safeReadFile(fs, filePath) {
|
|
8030
|
+
try {
|
|
8031
|
+
return await fs.readFile(filePath);
|
|
8032
|
+
} catch {
|
|
8033
|
+
return "";
|
|
8034
|
+
}
|
|
8035
|
+
}
|
|
8036
|
+
var defaultNpmInstallImpl = (packageSpec) => new Promise((resolve2) => {
|
|
8037
|
+
const child = spawn8("npm", ["i", "-g", packageSpec], { stdio: "inherit" });
|
|
8038
|
+
child.on("error", (error) => resolve2({ ok: false, error }));
|
|
8039
|
+
child.on("close", (code) => {
|
|
8040
|
+
if (code === 0) resolve2({ ok: true });
|
|
8041
|
+
else resolve2({ ok: false, error: new Error(`npm exited with code ${code ?? "unknown"}`) });
|
|
8042
|
+
});
|
|
8043
|
+
});
|
|
8044
|
+
var defaultConfirmImpl = async (message) => {
|
|
8045
|
+
const answer = await confirm2({ message, initialValue: true });
|
|
8046
|
+
if (isCancel2(answer)) return false;
|
|
8047
|
+
return Boolean(answer);
|
|
8048
|
+
};
|
|
8049
|
+
function printHeader(env, print) {
|
|
8050
|
+
print(cyan5("agent-kit setup-global"));
|
|
8051
|
+
print(
|
|
8052
|
+
` npm prefix: ${env.npmPrefix.prefix ?? "unknown"} (${env.npmPrefixWritable ? "writable" : "NOT writable"})`
|
|
8053
|
+
);
|
|
8054
|
+
if (!env.npmPrefixWritable && env.npmPrefix.reason) {
|
|
8055
|
+
print(` ${env.npmPrefix.reason}`);
|
|
8056
|
+
}
|
|
8057
|
+
print(
|
|
8058
|
+
` shell: ${env.shell ?? "unknown"}${env.shellProfile ? ` (profile: ${env.shellProfile})` : " (profile not auto-detected: zsh/bash only)"}`
|
|
8059
|
+
);
|
|
8060
|
+
}
|
|
8061
|
+
function printPlanSteps(plan, print) {
|
|
8062
|
+
for (const [index, step] of plan.steps.entries()) {
|
|
8063
|
+
print(`${index + 1}. ${step.title}`);
|
|
8064
|
+
for (const line of step.detail) print(` ${line}`);
|
|
8065
|
+
}
|
|
8066
|
+
}
|
|
8067
|
+
function printManualInstructions(plan, print) {
|
|
8068
|
+
print("No changes made. Same fix, as commands you can run yourself:");
|
|
8069
|
+
print(` mkdir -p ${plan.npmGlobalDir}`);
|
|
8070
|
+
print(` npm config set prefix "${plan.npmrcPrefixValue}"`);
|
|
8071
|
+
if (plan.shellSupported) {
|
|
8072
|
+
print(` echo '${plan.markerComment}' >> ${plan.shellProfile}`);
|
|
8073
|
+
print(` echo '${plan.pathExportLine}' >> ${plan.shellProfile}`);
|
|
8074
|
+
print(` source ${plan.shellProfile}`);
|
|
8075
|
+
} else {
|
|
8076
|
+
print(` # add this line to your shell's startup file:`);
|
|
8077
|
+
print(` ${plan.pathExportLine}`);
|
|
8078
|
+
}
|
|
8079
|
+
print(` npm i -g ${plan.packageSpec}`);
|
|
8080
|
+
print(" agent-kit --version # verify, in a new shell session");
|
|
8081
|
+
}
|
|
8082
|
+
async function runSetupGlobal(options = {}) {
|
|
8083
|
+
const print = options.print ?? ((line) => console.log(line));
|
|
8084
|
+
const assessEnvironmentImpl = options.assessEnvironmentImpl ?? assessEnvironment;
|
|
8085
|
+
const homeDir = options.homeDir ?? homedir4();
|
|
8086
|
+
const env = await assessEnvironmentImpl(options);
|
|
8087
|
+
const plan = planSetupGlobalSteps(env, { homeDir, packageSpec: options.packageSpec });
|
|
8088
|
+
if (plan.alreadyWritable) {
|
|
8089
|
+
printHeader(env, print);
|
|
8090
|
+
print(green4("npm's global prefix is already writable \u2014 nothing to fix."));
|
|
8091
|
+
return { exitCode: 0, mutated: false, outcome: "already-ok", env, plan };
|
|
8092
|
+
}
|
|
8093
|
+
if (options.dryRun) {
|
|
8094
|
+
printHeader(env, print);
|
|
8095
|
+
print("Dry run \u2014 no changes will be made. Steps that would run:");
|
|
8096
|
+
printPlanSteps(plan, print);
|
|
8097
|
+
return { exitCode: 0, mutated: false, outcome: "dry-run", env, plan };
|
|
8098
|
+
}
|
|
8099
|
+
const nonInteractive = options.nonInteractive ?? isNonInteractive();
|
|
8100
|
+
if (nonInteractive) {
|
|
8101
|
+
printHeader(env, print);
|
|
8102
|
+
printManualInstructions(plan, print);
|
|
8103
|
+
return { exitCode: 0, mutated: false, outcome: "manual-instructions", env, plan };
|
|
8104
|
+
}
|
|
8105
|
+
printHeader(env, print);
|
|
8106
|
+
print("You just hit the classic 'command not found' / EACCES fresh-install blocker.");
|
|
8107
|
+
print("The following steps need your confirmation, one at a time:");
|
|
8108
|
+
printPlanSteps(plan, print);
|
|
8109
|
+
const fs = options.fsImpl ?? defaultFsImpl;
|
|
8110
|
+
const confirmStep = options.confirmImpl ?? defaultConfirmImpl;
|
|
8111
|
+
const npmInstall = options.npmInstallImpl ?? defaultNpmInstallImpl;
|
|
8112
|
+
let mutated = false;
|
|
8113
|
+
const setPrefixStep = plan.steps[0];
|
|
8114
|
+
print(`
|
|
8115
|
+
${setPrefixStep.title}`);
|
|
8116
|
+
for (const line of setPrefixStep.detail) print(` ${line}`);
|
|
8117
|
+
const proceedPrefix = await confirmStep(`Set npm's global prefix to ${plan.npmGlobalDir}?`);
|
|
8118
|
+
if (!proceedPrefix) {
|
|
8119
|
+
print(yellow3("Cancelled \u2014 no changes made."));
|
|
8120
|
+
return { exitCode: 1, mutated, outcome: "cancelled", env, plan };
|
|
8121
|
+
}
|
|
8122
|
+
await fs.mkdir(plan.npmGlobalDir);
|
|
8123
|
+
const npmrcContent = await safeReadFile(fs, plan.npmrcPath);
|
|
8124
|
+
await fs.writeFile(plan.npmrcPath, upsertNpmrcPrefix(npmrcContent, plan.npmrcPrefixValue));
|
|
8125
|
+
mutated = true;
|
|
8126
|
+
print(green4(` done: prefix set (${plan.npmrcPath}).`));
|
|
8127
|
+
const appendPathStep = plan.steps[1];
|
|
8128
|
+
print(`
|
|
8129
|
+
${appendPathStep.title}`);
|
|
8130
|
+
for (const line of appendPathStep.detail) print(` ${line}`);
|
|
8131
|
+
if (!plan.shellSupported) {
|
|
8132
|
+
print(yellow3(" shell not auto-detected as zsh/bash \u2014 add the line above yourself."));
|
|
8133
|
+
} else {
|
|
8134
|
+
const profilePath = plan.shellProfile;
|
|
8135
|
+
const profileContent = await safeReadFile(fs, profilePath);
|
|
8136
|
+
if (profileContent.includes(plan.markerComment)) {
|
|
8137
|
+
print(` already present in ${profilePath} (marker found) \u2014 skipping.`);
|
|
8138
|
+
} else {
|
|
8139
|
+
const proceedPath = await confirmStep(`Append the PATH export to ${profilePath}?`);
|
|
8140
|
+
if (!proceedPath) {
|
|
8141
|
+
print(yellow3("Cancelled \u2014 prefix was set, PATH export was not appended."));
|
|
8142
|
+
return { exitCode: 1, mutated, outcome: "cancelled", env, plan };
|
|
8143
|
+
}
|
|
8144
|
+
await fs.appendFile(profilePath, `
|
|
8145
|
+
${plan.markerComment}
|
|
8146
|
+
${plan.pathExportLine}
|
|
8147
|
+
`);
|
|
8148
|
+
mutated = true;
|
|
8149
|
+
print(green4(` done: PATH export appended to ${profilePath}.`));
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8152
|
+
const installStep = plan.steps[2];
|
|
8153
|
+
print(`
|
|
8154
|
+
${installStep.title}`);
|
|
8155
|
+
for (const line of installStep.detail) print(` ${line}`);
|
|
8156
|
+
const proceedInstall = await confirmStep(`Run: npm i -g ${plan.packageSpec}?`);
|
|
8157
|
+
if (!proceedInstall) {
|
|
8158
|
+
print(yellow3("Cancelled \u2014 prefix/PATH changes above are still in place."));
|
|
8159
|
+
return { exitCode: 1, mutated, outcome: "cancelled", env, plan };
|
|
8160
|
+
}
|
|
8161
|
+
const installResult = await npmInstall(plan.packageSpec);
|
|
8162
|
+
if (!installResult.ok) {
|
|
8163
|
+
const hint = classifyInstallError(installResult.error);
|
|
8164
|
+
print(` npm install failed: ${hint.message}`);
|
|
8165
|
+
print(hint.recovery);
|
|
8166
|
+
return { exitCode: 1, mutated, outcome: "error", env, plan };
|
|
8167
|
+
}
|
|
8168
|
+
mutated = true;
|
|
8169
|
+
print(green4(` done: ${plan.packageSpec} installed globally.`));
|
|
8170
|
+
const verifyStep = plan.steps[3];
|
|
8171
|
+
print(`
|
|
8172
|
+
${verifyStep.title}`);
|
|
8173
|
+
for (const line of verifyStep.detail) print(` ${line}`);
|
|
8174
|
+
const proceedVerify = await confirmStep("Verify now (re-check PATH in this process)?");
|
|
8175
|
+
if (proceedVerify) {
|
|
8176
|
+
const verifyEnv = await assessEnvironmentImpl(options);
|
|
8177
|
+
if (verifyEnv.binOnPath) {
|
|
8178
|
+
print(green4(" verify: `agent-kit` resolves on PATH."));
|
|
8179
|
+
} else {
|
|
8180
|
+
print(" verify: `agent-kit` isn't resolvable in THIS process's PATH yet \u2014 that's expected.");
|
|
8181
|
+
print(
|
|
8182
|
+
` Open a new terminal (or run: source ${plan.shellProfile ?? "<your shell profile>"}) and re-check with: agent-kit --version`
|
|
8183
|
+
);
|
|
8184
|
+
}
|
|
8185
|
+
} else {
|
|
8186
|
+
print(" skipped verification. Open a new terminal and run: agent-kit --version");
|
|
8187
|
+
}
|
|
8188
|
+
return { exitCode: 0, mutated, outcome: "completed", env, plan };
|
|
8189
|
+
}
|
|
8190
|
+
var setupGlobalCommand = defineCommand16({
|
|
8191
|
+
meta: {
|
|
8192
|
+
name: "setup-global",
|
|
8193
|
+
description: "Self-heal a root-owned npm prefix: relocate to ~/.npm-global, fix PATH, reinstall globally."
|
|
8194
|
+
},
|
|
8195
|
+
args: {
|
|
8196
|
+
"dry-run": {
|
|
8197
|
+
type: "boolean",
|
|
8198
|
+
description: "Print the resolved plan; mutate nothing.",
|
|
8199
|
+
default: false
|
|
8200
|
+
},
|
|
8201
|
+
yes: {
|
|
8202
|
+
type: "boolean",
|
|
8203
|
+
alias: "y",
|
|
8204
|
+
description: "Treat as non-interactive: print the manual steps instead of prompting (never mutates).",
|
|
8205
|
+
default: false
|
|
8206
|
+
}
|
|
8207
|
+
},
|
|
8208
|
+
async run({ args }) {
|
|
8209
|
+
const nonInteractive = args.yes || isNonInteractive();
|
|
8210
|
+
const result = await runSetupGlobal({
|
|
8211
|
+
dryRun: Boolean(args["dry-run"]),
|
|
8212
|
+
nonInteractive
|
|
8213
|
+
});
|
|
8214
|
+
process.exitCode = result.exitCode;
|
|
8215
|
+
}
|
|
8216
|
+
});
|
|
8217
|
+
|
|
8218
|
+
// src/commands/status.ts
|
|
8219
|
+
import path45 from "path";
|
|
8220
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
7220
8221
|
function profileStatus(profile) {
|
|
7221
8222
|
if (!profile) return { origin: "none", evidence: [], profile: null };
|
|
7222
8223
|
if ("detection" in profile && profile.detection && typeof profile.detection === "object") {
|
|
@@ -7229,7 +8230,7 @@ function profileStatus(profile) {
|
|
|
7229
8230
|
}
|
|
7230
8231
|
return { origin: "legacy-wizard", evidence: [], profile };
|
|
7231
8232
|
}
|
|
7232
|
-
var statusCommand =
|
|
8233
|
+
var statusCommand = defineCommand17({
|
|
7233
8234
|
meta: {
|
|
7234
8235
|
name: "status",
|
|
7235
8236
|
description: "Show installed kit version, manifest, and optional profile."
|
|
@@ -7246,11 +8247,11 @@ var statusCommand = defineCommand16({
|
|
|
7246
8247
|
}
|
|
7247
8248
|
},
|
|
7248
8249
|
async run({ args }) {
|
|
7249
|
-
const rootDir =
|
|
8250
|
+
const rootDir = path45.resolve(args.cwd);
|
|
7250
8251
|
const [manifest, rawProfile, scan] = await Promise.all([
|
|
7251
8252
|
loadAgentKitManifest(rootDir),
|
|
7252
8253
|
readJson(
|
|
7253
|
-
|
|
8254
|
+
path45.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
7254
8255
|
),
|
|
7255
8256
|
runScanner(rootDir)
|
|
7256
8257
|
]);
|
|
@@ -7305,11 +8306,11 @@ var statusCommand = defineCommand16({
|
|
|
7305
8306
|
});
|
|
7306
8307
|
|
|
7307
8308
|
// src/commands/update.ts
|
|
7308
|
-
import { defineCommand as
|
|
8309
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
7309
8310
|
|
|
7310
8311
|
// src/lifecycle/check-updates.ts
|
|
7311
8312
|
import { execFile as execFile6 } from "child_process";
|
|
7312
|
-
import
|
|
8313
|
+
import path46 from "path";
|
|
7313
8314
|
import { promisify as promisify6 } from "util";
|
|
7314
8315
|
var execFileAsync5 = promisify6(execFile6);
|
|
7315
8316
|
var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i;
|
|
@@ -7400,11 +8401,11 @@ function intervalElapsed2(lastCheckedAt, intervalDays) {
|
|
|
7400
8401
|
return Date.now() - last >= ms;
|
|
7401
8402
|
}
|
|
7402
8403
|
async function loadContextConfig2(cwd) {
|
|
7403
|
-
const configPath =
|
|
8404
|
+
const configPath = path46.join(cwd, ".cursor", "context", "config.json");
|
|
7404
8405
|
return readJson(configPath);
|
|
7405
8406
|
}
|
|
7406
8407
|
async function stampLastCheckedAt(cwd) {
|
|
7407
|
-
const configPath =
|
|
8408
|
+
const configPath = path46.join(cwd, ".cursor", "context", "config.json");
|
|
7408
8409
|
const existing = await loadContextConfig2(cwd) ?? {};
|
|
7409
8410
|
const prev = existing.updateCheck && typeof existing.updateCheck === "object" ? { ...existing.updateCheck } : {};
|
|
7410
8411
|
existing.updateCheck = {
|
|
@@ -7416,7 +8417,7 @@ async function stampLastCheckedAt(cwd) {
|
|
|
7416
8417
|
}
|
|
7417
8418
|
async function readLocalKitVersion(registryRoot) {
|
|
7418
8419
|
for (const rel of ["packages/cli/package.json", "package.json"]) {
|
|
7419
|
-
const data = await readJson(
|
|
8420
|
+
const data = await readJson(path46.join(registryRoot, rel));
|
|
7420
8421
|
if (data && typeof data.version === "string" && data.version.length > 0) {
|
|
7421
8422
|
return data.version;
|
|
7422
8423
|
}
|
|
@@ -7437,7 +8438,7 @@ async function checkAgainstLocalRegistry(cwd, manifest, options) {
|
|
|
7437
8438
|
status: "error",
|
|
7438
8439
|
installedVersion: manifest.version,
|
|
7439
8440
|
latestVersion: null,
|
|
7440
|
-
registryUrl:
|
|
8441
|
+
registryUrl: path46.resolve(registryPath),
|
|
7441
8442
|
registryRef: "local",
|
|
7442
8443
|
applyRecommended: false,
|
|
7443
8444
|
message: `Failed to resolve --registry: ${msg}`
|
|
@@ -7659,7 +8660,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
7659
8660
|
}
|
|
7660
8661
|
|
|
7661
8662
|
// src/commands/update.ts
|
|
7662
|
-
var updateCommand =
|
|
8663
|
+
var updateCommand = defineCommand18({
|
|
7663
8664
|
meta: {
|
|
7664
8665
|
name: "update",
|
|
7665
8666
|
description: "Re-apply L0/packs/skills from the registry (never overwrites L3). --check = notify-only."
|
|
@@ -7738,6 +8739,9 @@ var updateCommand = defineCommand17({
|
|
|
7738
8739
|
} catch (err) {
|
|
7739
8740
|
if (err instanceof RootRefusedError) {
|
|
7740
8741
|
logger.error(err.message);
|
|
8742
|
+
if (err.recovery) console.error(`
|
|
8743
|
+
${err.recovery}
|
|
8744
|
+
`);
|
|
7741
8745
|
process.exitCode = 1;
|
|
7742
8746
|
return;
|
|
7743
8747
|
}
|
|
@@ -7790,9 +8794,9 @@ var updateCommand = defineCommand17({
|
|
|
7790
8794
|
});
|
|
7791
8795
|
|
|
7792
8796
|
// src/commands/validate.ts
|
|
7793
|
-
import { readFile as
|
|
7794
|
-
import
|
|
7795
|
-
import { defineCommand as
|
|
8797
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
8798
|
+
import path47 from "path";
|
|
8799
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
7796
8800
|
|
|
7797
8801
|
// src/invariants/plan-schema.ts
|
|
7798
8802
|
var CITE5 = "agent-kit validate plan (.cursor/context/templates/plan.md)";
|
|
@@ -7838,9 +8842,9 @@ function validatePlanFrontmatterText(text) {
|
|
|
7838
8842
|
// src/commands/validate.ts
|
|
7839
8843
|
async function resolveEditedPath(cwd, explicit) {
|
|
7840
8844
|
if (explicit) {
|
|
7841
|
-
const filePath2 =
|
|
8845
|
+
const filePath2 = path47.resolve(cwd, explicit);
|
|
7842
8846
|
try {
|
|
7843
|
-
return { filePath: filePath2, content: await
|
|
8847
|
+
return { filePath: filePath2, content: await readFile25(filePath2, "utf8") };
|
|
7844
8848
|
} catch {
|
|
7845
8849
|
return null;
|
|
7846
8850
|
}
|
|
@@ -7848,9 +8852,9 @@ async function resolveEditedPath(cwd, explicit) {
|
|
|
7848
8852
|
const payload = await readStdinJson();
|
|
7849
8853
|
const rel = typeof payload.file_path === "string" && payload.file_path || typeof payload.path === "string" && payload.path || typeof payload.file === "string" && payload.file || "";
|
|
7850
8854
|
if (!rel) return null;
|
|
7851
|
-
const filePath =
|
|
8855
|
+
const filePath = path47.isAbsolute(rel) ? rel : path47.resolve(cwd, rel);
|
|
7852
8856
|
try {
|
|
7853
|
-
return { filePath, content: await
|
|
8857
|
+
return { filePath, content: await readFile25(filePath, "utf8") };
|
|
7854
8858
|
} catch {
|
|
7855
8859
|
return null;
|
|
7856
8860
|
}
|
|
@@ -7862,13 +8866,13 @@ function isPlanPath(filePath) {
|
|
|
7862
8866
|
const norm = filePath.replace(/\\/g, "/");
|
|
7863
8867
|
return norm.includes("/.cursor/plans/") && norm.endsWith(".plan.md");
|
|
7864
8868
|
}
|
|
7865
|
-
var validateCommand =
|
|
8869
|
+
var validateCommand = defineCommand19({
|
|
7866
8870
|
meta: {
|
|
7867
8871
|
name: "validate",
|
|
7868
8872
|
description: "Advisory validators for HANDOFF / plan frontmatter (hook adapter)."
|
|
7869
8873
|
},
|
|
7870
8874
|
subCommands: {
|
|
7871
|
-
handoff:
|
|
8875
|
+
handoff: defineCommand19({
|
|
7872
8876
|
meta: { name: "handoff", description: "Validate HANDOFF machine fields" },
|
|
7873
8877
|
args: {
|
|
7874
8878
|
cwd: { type: "string", default: process.cwd() },
|
|
@@ -7878,10 +8882,10 @@ var validateCommand = defineCommand18({
|
|
|
7878
8882
|
async run({ args }) {
|
|
7879
8883
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
7880
8884
|
const fileArg = typeof args.file === "string" ? args.file : void 0;
|
|
7881
|
-
const filePath = fileArg ?
|
|
8885
|
+
const filePath = fileArg ? path47.resolve(cwd, fileArg) : path47.join(path47.resolve(cwd), ".cursor", "HANDOFF.md");
|
|
7882
8886
|
let content = "";
|
|
7883
8887
|
try {
|
|
7884
|
-
content = await
|
|
8888
|
+
content = await readFile25(filePath, "utf8");
|
|
7885
8889
|
} catch {
|
|
7886
8890
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
7887
8891
|
return;
|
|
@@ -7890,7 +8894,7 @@ var validateCommand = defineCommand18({
|
|
|
7890
8894
|
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
7891
8895
|
}
|
|
7892
8896
|
}),
|
|
7893
|
-
plan:
|
|
8897
|
+
plan: defineCommand19({
|
|
7894
8898
|
meta: { name: "plan", description: "Validate plan frontmatter" },
|
|
7895
8899
|
args: {
|
|
7896
8900
|
cwd: { type: "string", default: process.cwd() },
|
|
@@ -7905,10 +8909,10 @@ var validateCommand = defineCommand18({
|
|
|
7905
8909
|
process.exitCode = 2;
|
|
7906
8910
|
return;
|
|
7907
8911
|
}
|
|
7908
|
-
const filePath =
|
|
8912
|
+
const filePath = path47.resolve(cwd, fileArg);
|
|
7909
8913
|
let content = "";
|
|
7910
8914
|
try {
|
|
7911
|
-
content = await
|
|
8915
|
+
content = await readFile25(filePath, "utf8");
|
|
7912
8916
|
} catch {
|
|
7913
8917
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
7914
8918
|
return;
|
|
@@ -7917,7 +8921,7 @@ var validateCommand = defineCommand18({
|
|
|
7917
8921
|
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
7918
8922
|
}
|
|
7919
8923
|
}),
|
|
7920
|
-
"after-edit":
|
|
8924
|
+
"after-edit": defineCommand19({
|
|
7921
8925
|
meta: {
|
|
7922
8926
|
name: "after-edit",
|
|
7923
8927
|
description: "Advisory afterFileEdit: annotate HANDOFF/plan issues (never block)"
|
|
@@ -7927,7 +8931,7 @@ var validateCommand = defineCommand18({
|
|
|
7927
8931
|
},
|
|
7928
8932
|
async run({ args }) {
|
|
7929
8933
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
7930
|
-
const resolved = await resolveEditedPath(
|
|
8934
|
+
const resolved = await resolveEditedPath(path47.resolve(cwd));
|
|
7931
8935
|
if (!resolved) {
|
|
7932
8936
|
console.log(JSON.stringify({}));
|
|
7933
8937
|
return;
|
|
@@ -7970,14 +8974,14 @@ var validateCommand = defineCommand18({
|
|
|
7970
8974
|
});
|
|
7971
8975
|
|
|
7972
8976
|
// src/welcome/help-groups.ts
|
|
7973
|
-
import { bold, gray as gray4, underline } from "kolorist";
|
|
8977
|
+
import { bold as bold2, gray as gray4, underline } from "kolorist";
|
|
7974
8978
|
|
|
7975
8979
|
// src/welcome/screen.ts
|
|
7976
8980
|
import {
|
|
7977
8981
|
blue as blue2,
|
|
7978
|
-
cyan as
|
|
8982
|
+
cyan as cyan6,
|
|
7979
8983
|
gray as gray3,
|
|
7980
|
-
options as
|
|
8984
|
+
options as koloristOptions3,
|
|
7981
8985
|
lightCyan,
|
|
7982
8986
|
trueColor,
|
|
7983
8987
|
white as white2
|
|
@@ -8006,15 +9010,15 @@ function outlineAnsi(line) {
|
|
|
8006
9010
|
return trueColor(r, g, b)(line);
|
|
8007
9011
|
}
|
|
8008
9012
|
function withKoloristColor(fn) {
|
|
8009
|
-
const prevEnabled =
|
|
8010
|
-
const prevLevel =
|
|
8011
|
-
|
|
8012
|
-
|
|
9013
|
+
const prevEnabled = koloristOptions3.enabled;
|
|
9014
|
+
const prevLevel = koloristOptions3.supportLevel;
|
|
9015
|
+
koloristOptions3.enabled = true;
|
|
9016
|
+
koloristOptions3.supportLevel = KOLORIST_TRUECOLOR;
|
|
8013
9017
|
try {
|
|
8014
9018
|
return fn();
|
|
8015
9019
|
} finally {
|
|
8016
|
-
|
|
8017
|
-
|
|
9020
|
+
koloristOptions3.enabled = prevEnabled;
|
|
9021
|
+
koloristOptions3.supportLevel = prevLevel;
|
|
8018
9022
|
}
|
|
8019
9023
|
}
|
|
8020
9024
|
function hasCliSubcommand(rawArgs) {
|
|
@@ -8041,7 +9045,7 @@ function renderWelcomeScreen(opts = {}) {
|
|
|
8041
9045
|
const version = opts.version ?? KIT_VERSION;
|
|
8042
9046
|
const color = shouldUseWelcomeColor(opts);
|
|
8043
9047
|
const title = color ? white2("Mission Kit") : "Mission Kit";
|
|
8044
|
-
const product = color ?
|
|
9048
|
+
const product = color ? cyan6("agent-kit") : "agent-kit";
|
|
8045
9049
|
const muted = (s) => color ? gray3(s) : s;
|
|
8046
9050
|
const lines = [
|
|
8047
9051
|
renderHelmetAscii(color),
|
|
@@ -8054,7 +9058,7 @@ function renderWelcomeScreen(opts = {}) {
|
|
|
8054
9058
|
const width = Math.max(...WELCOME_UTILITY_HINTS.map(({ cmd }) => cmd.length));
|
|
8055
9059
|
return WELCOME_UTILITY_HINTS.map(({ cmd, hint }) => {
|
|
8056
9060
|
const pad = " ".repeat(width - cmd.length + 2);
|
|
8057
|
-
const left = color ?
|
|
9061
|
+
const left = color ? cyan6(` ${cmd}`) : ` ${cmd}`;
|
|
8058
9062
|
return `${left}${pad}${muted(hint)}`;
|
|
8059
9063
|
});
|
|
8060
9064
|
})(),
|
|
@@ -8115,7 +9119,7 @@ async function resolveSubMeta(subCommands) {
|
|
|
8115
9119
|
}
|
|
8116
9120
|
async function renderGroupedRootHelp(cmd) {
|
|
8117
9121
|
const color = shouldUseWelcomeColor();
|
|
8118
|
-
const u = (s) => color ? underline(
|
|
9122
|
+
const u = (s) => color ? underline(bold2(s)) : s;
|
|
8119
9123
|
const g = (s) => color ? gray4(s) : s;
|
|
8120
9124
|
const meta = await resolveCommandMeta(cmd.meta);
|
|
8121
9125
|
const name = meta?.name ?? "agent-kit";
|
|
@@ -8160,7 +9164,7 @@ async function renderGroupedRootHelp(cmd) {
|
|
|
8160
9164
|
}
|
|
8161
9165
|
|
|
8162
9166
|
// src/index.ts
|
|
8163
|
-
var main =
|
|
9167
|
+
var main = defineCommand20({
|
|
8164
9168
|
meta: {
|
|
8165
9169
|
name: "agent-kit",
|
|
8166
9170
|
description: "HITL framework for AI-assisted IDEs (Mission Kit family)",
|
|
@@ -8172,6 +9176,7 @@ var main = defineCommand19({
|
|
|
8172
9176
|
scan: scanCommand,
|
|
8173
9177
|
add: addCommand,
|
|
8174
9178
|
doctor: doctorCommand,
|
|
9179
|
+
"setup-global": setupGlobalCommand,
|
|
8175
9180
|
status: statusCommand,
|
|
8176
9181
|
update: updateCommand,
|
|
8177
9182
|
"cursor-awareness": cursorAwarenessCommand,
|