@dadado/agent-kit-cli 5.16.0 → 5.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-7UMSGVWC.js → add-HTYIFKMN.js} +2 -2
- package/dist/{chunk-YGBBHKBE.js → chunk-2RDRMGD3.js} +2 -2
- package/dist/{chunk-VS2JD2XC.js → chunk-AW74INUW.js} +4 -1
- package/dist/{chunk-OZ4QV6LM.js → chunk-B73KQ5KX.js} +42 -148
- package/dist/{chunk-5OXPNVCF.js → chunk-DQA434Y4.js} +2 -2
- package/dist/{chunk-VGZBARQC.js → chunk-F4VMYR7U.js} +2 -2
- package/dist/{chunk-6QV2BFGA.js → chunk-N3HTJVBR.js} +1 -1
- package/dist/{chunk-XWPO3VYQ.js → chunk-PNG7LO2F.js} +1 -1
- package/dist/chunk-S6GMFDKV.js +50 -0
- package/dist/{chunk-XZZAVHH4.js → chunk-SMGFN3Z4.js} +2 -2
- package/dist/{chunk-KI6ZHBPV.js → chunk-STGFBAYT.js} +320 -11
- package/dist/{chunk-CGTE7DHN.js → chunk-W24CMQ32.js} +135 -20
- package/dist/{contribute-YKK6YCSI.js → contribute-C6AW2P6J.js} +3 -3
- package/dist/{diff-YTR7VOAN.js → diff-VHNR62SI.js} +4 -4
- package/dist/doctor-PET2HRRJ.js +236 -0
- package/dist/{hook-MUV6Y7UW.js → hook-DYIMKJYL.js} +4 -4
- package/dist/index.js +10 -10
- package/dist/{init-TTTKGC4C.js → init-Q4LBM6KT.js} +10 -10
- package/dist/{install-ZMFJBPOI.js → install-RHOS3OYZ.js} +10 -10
- package/dist/{setup-global-JUVMJXJ5.js → setup-global-M42GEODI.js} +5 -5
- package/dist/{status-MVCUNCWX.js → status-XPVQVQMX.js} +18 -6
- package/dist/{update-Q4EBIRUB.js → update-FJ2EBZVO.js} +32 -9
- package/package.json +3 -3
- package/dist/doctor-TA2FWREU.js +0 -497
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLAUDE_COMMANDS_DIR_REL,
|
|
3
|
+
CLAUDE_SETTINGS_REL,
|
|
4
|
+
GUARD_SHELL_HOOK_MARKER,
|
|
5
|
+
RESOLVE_AGENT_KIT_REL,
|
|
6
|
+
SESSION_START_HOOK_MARKER,
|
|
7
|
+
discoverInstalledCommands
|
|
8
|
+
} from "./chunk-W24CMQ32.js";
|
|
1
9
|
import {
|
|
2
10
|
createReadinessReport
|
|
3
11
|
} from "./chunk-PNFF6OU3.js";
|
|
@@ -19,9 +27,308 @@ import {
|
|
|
19
27
|
writeJson
|
|
20
28
|
} from "./chunk-77V75A7O.js";
|
|
21
29
|
|
|
22
|
-
// src/
|
|
23
|
-
import {
|
|
30
|
+
// src/invariants/hooks-health.ts
|
|
31
|
+
import { execFile } from "child_process";
|
|
32
|
+
import { constants, access, readFile, readdir, stat } from "fs/promises";
|
|
24
33
|
import path from "path";
|
|
34
|
+
import { promisify } from "util";
|
|
35
|
+
var execFileAsync = promisify(execFile);
|
|
36
|
+
var GIT_HOOK_CANONICAL_NAMES = ["pre-commit", "pre-push", "prepare-commit-msg"];
|
|
37
|
+
var EXPECTED_EVENTS = [
|
|
38
|
+
"sessionStart",
|
|
39
|
+
"preCompact",
|
|
40
|
+
"beforeShellExecution",
|
|
41
|
+
"afterFileEdit",
|
|
42
|
+
"beforeSubmitPrompt"
|
|
43
|
+
];
|
|
44
|
+
async function exists(p) {
|
|
45
|
+
try {
|
|
46
|
+
await access(p);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function isExecutable(p) {
|
|
53
|
+
try {
|
|
54
|
+
await access(p, constants.X_OK);
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function resolveAgentKitCli(rootDir) {
|
|
61
|
+
const root = path.resolve(rootDir);
|
|
62
|
+
const candidates = [
|
|
63
|
+
path.join(root, "node_modules", ".bin", "agent-kit"),
|
|
64
|
+
path.join(root, "packages", "cli", "dist", "index.js")
|
|
65
|
+
];
|
|
66
|
+
for (const c of candidates) {
|
|
67
|
+
if (await exists(c)) return c;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const { stdout } = await execFileAsync("which", ["agent-kit"], { encoding: "utf8" });
|
|
71
|
+
const hit = stdout.trim().split("\n")[0]?.trim();
|
|
72
|
+
if (hit) return hit;
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function commandLooksLikeAdapter(command) {
|
|
78
|
+
const trimmed = command.trim();
|
|
79
|
+
if (!trimmed) return null;
|
|
80
|
+
const m = trimmed.match(/(\.cursor\/hooks\/agent\/[A-Za-z0-9._-]+\.sh)\b/);
|
|
81
|
+
return m?.[1] ?? null;
|
|
82
|
+
}
|
|
83
|
+
async function assessGitHooksInstallDrift(rootDir) {
|
|
84
|
+
const root = path.resolve(rootDir);
|
|
85
|
+
const canonicalDir = path.join(root, "git-hooks");
|
|
86
|
+
if (!await exists(canonicalDir)) return [];
|
|
87
|
+
let hooksPathCfg = "";
|
|
88
|
+
try {
|
|
89
|
+
const { stdout } = await execFileAsync(
|
|
90
|
+
"git",
|
|
91
|
+
["-C", root, "config", "--get", "core.hooksPath"],
|
|
92
|
+
{ encoding: "utf8" }
|
|
93
|
+
);
|
|
94
|
+
hooksPathCfg = stdout.trim();
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
if (hooksPathCfg) {
|
|
98
|
+
const resolved = path.isAbsolute(hooksPathCfg) ? path.normalize(hooksPathCfg) : path.normalize(path.join(root, hooksPathCfg));
|
|
99
|
+
if (resolved === path.normalize(canonicalDir)) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const gitDir = path.join(root, ".git");
|
|
104
|
+
if (!await exists(gitDir)) return [];
|
|
105
|
+
try {
|
|
106
|
+
const st = await stat(gitDir);
|
|
107
|
+
if (!st.isDirectory()) return [];
|
|
108
|
+
} catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const installedDir = path.join(gitDir, "hooks");
|
|
112
|
+
const advisories = [];
|
|
113
|
+
const installHint = "Install or refresh with: `cp git-hooks/<name> .git/hooks/<name> && chmod +x .git/hooks/<name>` (see git-hooks/README.md)";
|
|
114
|
+
let names = [...GIT_HOOK_CANONICAL_NAMES];
|
|
115
|
+
try {
|
|
116
|
+
const listed = await readdir(canonicalDir);
|
|
117
|
+
const fromDisk = listed.filter(
|
|
118
|
+
(n) => GIT_HOOK_CANONICAL_NAMES.includes(n)
|
|
119
|
+
);
|
|
120
|
+
if (fromDisk.length > 0) names = fromDisk;
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
for (const name of names) {
|
|
124
|
+
const canonical = path.join(canonicalDir, name);
|
|
125
|
+
const installed = path.join(installedDir, name);
|
|
126
|
+
if (!await exists(canonical)) continue;
|
|
127
|
+
if (!await exists(installed)) {
|
|
128
|
+
advisories.push(
|
|
129
|
+
`git-hooks drift: \`.git/hooks/${name}\` missing (canonical \`git-hooks/${name}\` present). ${installHint}`
|
|
130
|
+
);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const [a, b] = await Promise.all([readFile(canonical, "utf8"), readFile(installed, "utf8")]);
|
|
135
|
+
if (a !== b) {
|
|
136
|
+
advisories.push(
|
|
137
|
+
`git-hooks drift: \`.git/hooks/${name}\` differs from \`git-hooks/${name}\`. ${installHint}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
advisories.push(
|
|
142
|
+
`git-hooks drift: could not compare \`git-hooks/${name}\` with \`.git/hooks/${name}\`. ${installHint}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return advisories;
|
|
147
|
+
}
|
|
148
|
+
async function assessSignatureGateHook(rootDir) {
|
|
149
|
+
const root = path.resolve(rootDir);
|
|
150
|
+
if (!await exists(path.join(root, "autogit", "gitupdate.md"))) return [];
|
|
151
|
+
if (await exists(path.join(root, "git-hooks", "prepare-commit-msg"))) return [];
|
|
152
|
+
return [
|
|
153
|
+
"signature gate hook missing: `git-hooks/prepare-commit-msg` (/git-staging runs `sh git-hooks/prepare-commit-msg --check -`; a missing file exits 127 and reads as red). Run `agent-kit update` to restore it."
|
|
154
|
+
];
|
|
155
|
+
}
|
|
156
|
+
async function assessClaudeCommandAdapters(rootDir) {
|
|
157
|
+
const root = path.resolve(rootDir);
|
|
158
|
+
if (!await exists(path.join(root, ".claude"))) return [];
|
|
159
|
+
const sources = await discoverInstalledCommands(root);
|
|
160
|
+
if (sources.length === 0) return [];
|
|
161
|
+
const missing = [];
|
|
162
|
+
for (const { name } of sources) {
|
|
163
|
+
if (!await exists(path.join(root, CLAUDE_COMMANDS_DIR_REL, `${name}.md`))) missing.push(name);
|
|
164
|
+
}
|
|
165
|
+
if (missing.length === 0) return [];
|
|
166
|
+
const shown = missing.slice(0, 3).map((n) => `/${n}`);
|
|
167
|
+
const more = missing.length > shown.length ? `, +${missing.length - shown.length} more` : "";
|
|
168
|
+
return [
|
|
169
|
+
`Claude Code command adapters missing: ${missing.length} of ${sources.length} installed kit commands have no \`.claude/commands/<name>.md\` (${shown.join(", ")}${more}), so they are not recognized as slash commands. Run \`agent-kit update --claude\` to generate them (writes only \`.claude/commands/*\`).`
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
async function resolveGuardProbeArgv(root) {
|
|
173
|
+
const hookBin = process.env.AGENT_KIT_HOOK_BIN;
|
|
174
|
+
if (hookBin && await isExecutable(hookBin)) return [hookBin];
|
|
175
|
+
const local = path.join(root, "node_modules", ".bin", "agent-kit");
|
|
176
|
+
if (await isExecutable(local)) return [local];
|
|
177
|
+
const dist = path.join(root, "packages", "cli", "dist", "index.js");
|
|
178
|
+
if (await exists(dist)) return [process.execPath, dist];
|
|
179
|
+
try {
|
|
180
|
+
const { stdout } = await execFileAsync("which", ["agent-kit"], { encoding: "utf8" });
|
|
181
|
+
const hit = stdout.trim().split("\n")[0]?.trim();
|
|
182
|
+
if (hit) return [hit];
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
async function assessClaudeGuardProbe(rootDir) {
|
|
188
|
+
const root = path.resolve(rootDir);
|
|
189
|
+
let text;
|
|
190
|
+
try {
|
|
191
|
+
text = await readFile(path.join(root, CLAUDE_SETTINGS_REL), "utf8");
|
|
192
|
+
} catch {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
if (!text.includes(GUARD_SHELL_HOOK_MARKER)) {
|
|
196
|
+
return text.includes(SESSION_START_HOOK_MARKER) ? [
|
|
197
|
+
"Claude SessionStart hook is installed but the PreToolUse guard hook is absent from `.claude/settings.json`. Run `agent-kit install --claude`."
|
|
198
|
+
] : [];
|
|
199
|
+
}
|
|
200
|
+
const stale = "Claude guard hook active but resolved CLI is too old or stale dist; update the CLI / rebuild.";
|
|
201
|
+
const argv = await resolveGuardProbeArgv(root);
|
|
202
|
+
if (!argv) return [`${stale} (agent-kit CLI not resolvable)`];
|
|
203
|
+
const [bin, ...lead] = argv;
|
|
204
|
+
try {
|
|
205
|
+
const { stdout } = await execFileAsync(
|
|
206
|
+
bin,
|
|
207
|
+
[
|
|
208
|
+
...lead,
|
|
209
|
+
"guard",
|
|
210
|
+
"shell",
|
|
211
|
+
"--format",
|
|
212
|
+
"claude",
|
|
213
|
+
"--command",
|
|
214
|
+
"git push --force origin main"
|
|
215
|
+
],
|
|
216
|
+
{
|
|
217
|
+
cwd: root,
|
|
218
|
+
encoding: "utf8",
|
|
219
|
+
timeout: 1e4,
|
|
220
|
+
env: { ...process.env, ALLOW_MAIN_PUSH: "" }
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
const decision = JSON.parse(stdout).hookSpecificOutput?.permissionDecision;
|
|
224
|
+
return decision === "deny" ? [] : [stale];
|
|
225
|
+
} catch {
|
|
226
|
+
return [stale];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function assessHooksHealth(rootDir) {
|
|
230
|
+
const root = path.resolve(rootDir);
|
|
231
|
+
const hooksJsonPath = ".cursor/hooks.json";
|
|
232
|
+
const hooksJsonAbs = path.join(root, hooksJsonPath);
|
|
233
|
+
const reasons = [];
|
|
234
|
+
const wiredEvents = [];
|
|
235
|
+
const advisories = [
|
|
236
|
+
...await assessGitHooksInstallDrift(root),
|
|
237
|
+
...await assessSignatureGateHook(root),
|
|
238
|
+
...await assessClaudeGuardProbe(root),
|
|
239
|
+
...await assessClaudeCommandAdapters(root)
|
|
240
|
+
];
|
|
241
|
+
if (!await exists(hooksJsonAbs)) {
|
|
242
|
+
return {
|
|
243
|
+
status: "missing",
|
|
244
|
+
reasons: ["`.cursor/hooks.json` not found"],
|
|
245
|
+
advisories,
|
|
246
|
+
hooksJsonPath,
|
|
247
|
+
expectedEvents: [...EXPECTED_EVENTS],
|
|
248
|
+
wiredEvents
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
let parsed;
|
|
252
|
+
try {
|
|
253
|
+
parsed = JSON.parse(await readFile(hooksJsonAbs, "utf8"));
|
|
254
|
+
} catch {
|
|
255
|
+
return {
|
|
256
|
+
status: "degraded",
|
|
257
|
+
reasons: ["`.cursor/hooks.json` is not valid JSON"],
|
|
258
|
+
advisories,
|
|
259
|
+
hooksJsonPath,
|
|
260
|
+
expectedEvents: [...EXPECTED_EVENTS],
|
|
261
|
+
wiredEvents
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const hooks = parsed.hooks ?? {};
|
|
265
|
+
const adapterRels = /* @__PURE__ */ new Set();
|
|
266
|
+
for (const event of EXPECTED_EVENTS) {
|
|
267
|
+
const list = hooks[event];
|
|
268
|
+
if (Array.isArray(list) && list.length > 0) {
|
|
269
|
+
wiredEvents.push(event);
|
|
270
|
+
for (const entry of list) {
|
|
271
|
+
if (!entry || typeof entry !== "object") continue;
|
|
272
|
+
const command = String(entry.command ?? "");
|
|
273
|
+
if (command.endsWith(".py") || command.includes("python")) {
|
|
274
|
+
reasons.push(`${event} still points at a Python script (${command})`);
|
|
275
|
+
}
|
|
276
|
+
const rel = commandLooksLikeAdapter(command);
|
|
277
|
+
if (rel) adapterRels.add(rel);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
reasons.push(`missing hook event: ${event}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const resolveLib = path.join(root, RESOLVE_AGENT_KIT_REL);
|
|
284
|
+
if (!await exists(resolveLib)) {
|
|
285
|
+
reasons.push(`missing \`${RESOLVE_AGENT_KIT_REL}\` (thin adapter resolver)`);
|
|
286
|
+
} else if (!await isExecutable(resolveLib)) {
|
|
287
|
+
reasons.push(`\`${RESOLVE_AGENT_KIT_REL}\` is not executable (chmod +x)`);
|
|
288
|
+
}
|
|
289
|
+
for (const rel of adapterRels) {
|
|
290
|
+
const abs = path.join(root, rel);
|
|
291
|
+
if (!await exists(abs)) {
|
|
292
|
+
reasons.push(`missing adapter script: \`${rel}\``);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
const st = await stat(abs);
|
|
297
|
+
if (!st.isFile()) {
|
|
298
|
+
reasons.push(`adapter path is not a file: \`${rel}\``);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
} catch {
|
|
302
|
+
reasons.push(`unreadable adapter script: \`${rel}\``);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (!await isExecutable(abs)) {
|
|
306
|
+
reasons.push(`adapter not executable: \`${rel}\` (chmod +x)`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const cli = await resolveAgentKitCli(root);
|
|
310
|
+
if (!cli) {
|
|
311
|
+
reasons.push(
|
|
312
|
+
"agent-kit CLI not resolvable (node_modules/.bin/agent-kit, packages/cli/dist, or PATH)"
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
if (Array.isArray(hooks.stop) && hooks.stop.length > 0) {
|
|
316
|
+
reasons.push("`stop` hook is registered (forbidden; remove it)");
|
|
317
|
+
}
|
|
318
|
+
const status = reasons.length === 0 && wiredEvents.length === EXPECTED_EVENTS.length ? "active" : "degraded";
|
|
319
|
+
return {
|
|
320
|
+
status,
|
|
321
|
+
reasons,
|
|
322
|
+
advisories,
|
|
323
|
+
hooksJsonPath,
|
|
324
|
+
expectedEvents: [...EXPECTED_EVENTS],
|
|
325
|
+
wiredEvents
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/scanner/safe-fixes.ts
|
|
330
|
+
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
331
|
+
import path2 from "path";
|
|
25
332
|
var CONTEXT_CONFIG_REL_POSIX = CONTEXT_CONFIG_REL.replace(/\\/g, "/");
|
|
26
333
|
var ESSENTIAL_DIRECTORIES = [
|
|
27
334
|
".cursor",
|
|
@@ -207,7 +514,7 @@ async function refreshRepositoryProfile(rootDir, options) {
|
|
|
207
514
|
generatedAt
|
|
208
515
|
});
|
|
209
516
|
const desiredProfile = createProfile(scan, report, generatedAt);
|
|
210
|
-
const profilePath =
|
|
517
|
+
const profilePath = path2.join(scan.rootDir, REPOSITORY_PROFILE_REL);
|
|
211
518
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
212
519
|
const reconciled = reconcileFreshOverExisting(desiredProfile, existingProfile);
|
|
213
520
|
const meaningfulChange = !jsonEqual(
|
|
@@ -230,21 +537,21 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
230
537
|
});
|
|
231
538
|
const changes = [];
|
|
232
539
|
for (const relativePath of ESSENTIAL_DIRECTORIES) {
|
|
233
|
-
const absolutePath =
|
|
234
|
-
const
|
|
235
|
-
if (!
|
|
540
|
+
const absolutePath = path2.join(beforeScan.rootDir, relativePath);
|
|
541
|
+
const exists2 = await fileExists(absolutePath);
|
|
542
|
+
if (!exists2 && !dryRun) await ensureDir(absolutePath);
|
|
236
543
|
recordChange(
|
|
237
544
|
changes,
|
|
238
545
|
"ensure-agent-kit-directory",
|
|
239
546
|
relativePath,
|
|
240
|
-
!
|
|
547
|
+
!exists2,
|
|
241
548
|
dryRun,
|
|
242
|
-
relativeEvidence(relativePath,
|
|
549
|
+
relativeEvidence(relativePath, exists2 ? "already exists" : "missing directory")
|
|
243
550
|
);
|
|
244
551
|
}
|
|
245
552
|
const gitignoreRelativePath = ".gitignore";
|
|
246
|
-
const gitignorePath =
|
|
247
|
-
const existingGitignore = await fileExists(gitignorePath) ? await
|
|
553
|
+
const gitignorePath = path2.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
554
|
+
const existingGitignore = await fileExists(gitignorePath) ? await readFile2(gitignorePath, "utf8") : "";
|
|
248
555
|
const gitignoreAfterSecrets = mergeSecretIgnores(existingGitignore);
|
|
249
556
|
const secretsChanged = gitignoreAfterSecrets !== existingGitignore;
|
|
250
557
|
const mergedGitignore = mergeKitOwnedIgnores(gitignoreAfterSecrets);
|
|
@@ -273,7 +580,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
273
580
|
kitOwnedChanged ? "kit-owned session/derived paths are missing" : "kit-owned session/derived paths are present"
|
|
274
581
|
)
|
|
275
582
|
);
|
|
276
|
-
const profilePath =
|
|
583
|
+
const profilePath = path2.join(beforeScan.rootDir, REPOSITORY_PROFILE_REL);
|
|
277
584
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
278
585
|
const desiredProfile = createProfile(beforeScan, before, generatedAt);
|
|
279
586
|
const mergedProfile = mergeMissing(existingProfile, desiredProfile);
|
|
@@ -327,6 +634,8 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
327
634
|
}
|
|
328
635
|
|
|
329
636
|
export {
|
|
637
|
+
assessClaudeCommandAdapters,
|
|
638
|
+
assessHooksHealth,
|
|
330
639
|
refreshRepositoryProfile,
|
|
331
640
|
executeSafeReadinessFixes
|
|
332
641
|
};
|
|
@@ -4,35 +4,146 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
assertRealContained,
|
|
6
6
|
assertRealParentContained,
|
|
7
|
+
contentHash,
|
|
7
8
|
isRealWriteTargetContained,
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
loadManagedHashLedger,
|
|
10
|
+
resolveContained,
|
|
11
|
+
saveManagedHashLedger,
|
|
12
|
+
shouldPreserveCustomizedOverlay
|
|
13
|
+
} from "./chunk-AW74INUW.js";
|
|
10
14
|
import {
|
|
11
|
-
ensureDir
|
|
15
|
+
ensureDir,
|
|
16
|
+
fileExists
|
|
12
17
|
} from "./chunk-77V75A7O.js";
|
|
13
18
|
|
|
19
|
+
// src/generator/claude-command-adapters.ts
|
|
20
|
+
import { readFile, readdir, writeFile } from "fs/promises";
|
|
21
|
+
import path from "path";
|
|
22
|
+
var CURSOR_COMMANDS_DIR_REL = ".cursor/commands";
|
|
23
|
+
var CLAUDE_COMMANDS_DIR_REL = ".claude/commands";
|
|
24
|
+
var RESERVED_ADAPTER_NAMES = /* @__PURE__ */ new Set(["agent-kit"]);
|
|
25
|
+
function parseCommandFrontmatter(name, raw) {
|
|
26
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
|
|
27
|
+
if (!match) return null;
|
|
28
|
+
const body = match[1] ?? "";
|
|
29
|
+
const descMatch = /^description:\s*(.+)$/m.exec(body);
|
|
30
|
+
if (!descMatch) return null;
|
|
31
|
+
const description = (descMatch[1] ?? "").trim();
|
|
32
|
+
if (!description) return null;
|
|
33
|
+
return { name, description };
|
|
34
|
+
}
|
|
35
|
+
var CLAUDE_HITL_FALLBACK = `Cursor "Ask questions" is unavailable here: use AskUserQuestion when possible, else print one line \`HITL_GATE: <ask-id> | <label 1> | <label 2> | ...\` immediately followed by the same labels as one numbered list, one list per message, and WAIT for the answer (ask-id and labels: \`.cursor/skills/core/hitl-gates/SKILL.md\`). A headless reply arrives as \`HITL_REPLY: <ask-id> | operator reply <n> | <label>\`; cite that line as the Ask provenance.`;
|
|
36
|
+
var MODEL_INVOCATION_DISABLED = /* @__PURE__ */ new Set([
|
|
37
|
+
"git-prod",
|
|
38
|
+
"kit-prod",
|
|
39
|
+
"backlog-delete",
|
|
40
|
+
"backlog-cancel",
|
|
41
|
+
"archive-plan",
|
|
42
|
+
"hotfix",
|
|
43
|
+
"run-plan-all"
|
|
44
|
+
]);
|
|
45
|
+
function renderClaudeCommandAdapter(command) {
|
|
46
|
+
const invocation = MODEL_INVOCATION_DISABLED.has(command.name) ? "disable-model-invocation: true\n" : "";
|
|
47
|
+
return `---
|
|
48
|
+
description: ${command.description}
|
|
49
|
+
${invocation}---
|
|
50
|
+
|
|
51
|
+
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.
|
|
52
|
+
|
|
53
|
+
Adapter rules (Claude Code CLI):
|
|
54
|
+
- ${CLAUDE_HITL_FALLBACK}
|
|
55
|
+
- Skip or cancel means stop.
|
|
56
|
+
- Never \`/git-prod\` without an explicit operator yes.
|
|
57
|
+
- Do not clone Cursor hooks or invent behavior beyond the SoT file.
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
async function discoverInstalledCommands(rootDir) {
|
|
61
|
+
const dir = path.join(rootDir, CURSOR_COMMANDS_DIR_REL);
|
|
62
|
+
let entries;
|
|
63
|
+
try {
|
|
64
|
+
entries = (await readdir(dir)).filter((f) => f.endsWith(".md"));
|
|
65
|
+
} catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const commands = [];
|
|
69
|
+
for (const file of entries.sort()) {
|
|
70
|
+
const name = file.slice(0, -3);
|
|
71
|
+
if (RESERVED_ADAPTER_NAMES.has(name)) continue;
|
|
72
|
+
try {
|
|
73
|
+
const raw = await readFile(path.join(dir, file), "utf8");
|
|
74
|
+
const parsed = parseCommandFrontmatter(name, raw);
|
|
75
|
+
if (parsed) commands.push(parsed);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return commands;
|
|
80
|
+
}
|
|
81
|
+
async function generateClaudeCommandAdapters(rootDir) {
|
|
82
|
+
const commands = await discoverInstalledCommands(rootDir);
|
|
83
|
+
if (commands.length === 0) return [];
|
|
84
|
+
const ledger = await loadManagedHashLedger(rootDir);
|
|
85
|
+
const results = [];
|
|
86
|
+
let ledgerDirty = false;
|
|
87
|
+
for (const command of commands) {
|
|
88
|
+
const relPath = path.posix.join(CLAUDE_COMMANDS_DIR_REL, `${command.name}.md`);
|
|
89
|
+
const rendered = renderClaudeCommandAdapter(command);
|
|
90
|
+
const abs = resolveContained(rootDir, relPath);
|
|
91
|
+
if (!await isRealWriteTargetContained(rootDir, abs)) {
|
|
92
|
+
results.push({ relativePath: relPath, status: "skipped-symlink" });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!await fileExists(abs)) {
|
|
96
|
+
await ensureDir(path.dirname(abs));
|
|
97
|
+
await writeFile(abs, rendered, "utf8");
|
|
98
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
99
|
+
ledgerDirty = true;
|
|
100
|
+
results.push({ relativePath: relPath, status: "applied" });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const localContent = await readFile(abs, "utf8");
|
|
104
|
+
if (localContent === rendered) {
|
|
105
|
+
if (ledger.hashes[relPath] !== contentHash(rendered)) {
|
|
106
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
107
|
+
ledgerDirty = true;
|
|
108
|
+
}
|
|
109
|
+
results.push({ relativePath: relPath, status: "unchanged" });
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (shouldPreserveCustomizedOverlay(localContent, ledger.hashes[relPath])) {
|
|
113
|
+
results.push({ relativePath: relPath, status: "preserved-customized" });
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
await writeFile(abs, rendered, "utf8");
|
|
117
|
+
ledger.hashes[relPath] = contentHash(rendered);
|
|
118
|
+
ledgerDirty = true;
|
|
119
|
+
results.push({ relativePath: relPath, status: "refreshed" });
|
|
120
|
+
}
|
|
121
|
+
if (ledgerDirty) await saveManagedHashLedger(rootDir, ledger);
|
|
122
|
+
return results;
|
|
123
|
+
}
|
|
124
|
+
|
|
14
125
|
// src/generator/claude-session-start-hook.ts
|
|
15
|
-
import { readFile as
|
|
16
|
-
import
|
|
126
|
+
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
127
|
+
import path3 from "path";
|
|
17
128
|
|
|
18
129
|
// src/generator/claude-settings-ledger.ts
|
|
19
|
-
import { lstat, mkdir, readFile, writeFile } from "fs/promises";
|
|
20
|
-
import
|
|
130
|
+
import { lstat, mkdir, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
131
|
+
import path2 from "path";
|
|
21
132
|
var CLAUDE_SETTINGS_LEDGER_REL = ".cursor/agent-kit.claude-settings.json";
|
|
22
133
|
var ClaudeSettingsLedgerSymlinkError = class extends Error {
|
|
23
|
-
constructor(
|
|
24
|
-
super(`Refusing to use Claude settings ledger ${
|
|
25
|
-
this.path =
|
|
134
|
+
constructor(path4, detail) {
|
|
135
|
+
super(`Refusing to use Claude settings ledger ${path4}: ${detail}`);
|
|
136
|
+
this.path = path4;
|
|
26
137
|
this.name = "ClaudeSettingsLedgerSymlinkError";
|
|
27
138
|
}
|
|
28
139
|
path;
|
|
29
140
|
};
|
|
30
141
|
var ClaudeSettingsLedgerInvalidError = class extends Error {
|
|
31
|
-
constructor(
|
|
142
|
+
constructor(path4) {
|
|
32
143
|
super(
|
|
33
|
-
`Claude settings ledger ${
|
|
144
|
+
`Claude settings ledger ${path4} is malformed or has an unknown schemaVersion; not overwriting without authorization.`
|
|
34
145
|
);
|
|
35
|
-
this.path =
|
|
146
|
+
this.path = path4;
|
|
36
147
|
this.name = "ClaudeSettingsLedgerInvalidError";
|
|
37
148
|
}
|
|
38
149
|
path;
|
|
@@ -98,7 +209,7 @@ async function readState(root) {
|
|
|
98
209
|
);
|
|
99
210
|
}
|
|
100
211
|
if (!exists) return { abs, state: { kind: "missing" } };
|
|
101
|
-
const ledger = parseLedger(await
|
|
212
|
+
const ledger = parseLedger(await readFile2(abs, "utf8"));
|
|
102
213
|
return { abs, state: ledger ? { kind: "ok", ledger } : { kind: "invalid" } };
|
|
103
214
|
}
|
|
104
215
|
async function loadClaudeSettingsLedger(root) {
|
|
@@ -115,8 +226,8 @@ async function saveClaudeSettingsLedger(root, additions, opts = {}) {
|
|
|
115
226
|
[...base.denyRowsWritten, ...additions.denyRowsWritten ?? []],
|
|
116
227
|
[...base.hookEntriesWritten, ...additions.hookEntriesWritten ?? []]
|
|
117
228
|
);
|
|
118
|
-
await mkdir(
|
|
119
|
-
await
|
|
229
|
+
await mkdir(path2.dirname(abs), { recursive: true });
|
|
230
|
+
await writeFile2(abs, `${JSON.stringify(merged, null, 2)}
|
|
120
231
|
`, "utf8");
|
|
121
232
|
return merged;
|
|
122
233
|
}
|
|
@@ -294,7 +405,7 @@ async function writeClaudeSessionStartHook(rootDir, opts = {}) {
|
|
|
294
405
|
}
|
|
295
406
|
let existing = null;
|
|
296
407
|
try {
|
|
297
|
-
existing = await
|
|
408
|
+
existing = await readFile3(abs, "utf8");
|
|
298
409
|
} catch {
|
|
299
410
|
existing = null;
|
|
300
411
|
}
|
|
@@ -307,8 +418,8 @@ async function writeClaudeSessionStartHook(rootDir, opts = {}) {
|
|
|
307
418
|
};
|
|
308
419
|
}
|
|
309
420
|
if (merged.status !== "unchanged") {
|
|
310
|
-
await ensureDir(
|
|
311
|
-
await
|
|
421
|
+
await ensureDir(path3.dirname(abs));
|
|
422
|
+
await writeFile3(abs, merged.content, "utf8");
|
|
312
423
|
}
|
|
313
424
|
const result = {
|
|
314
425
|
relativePath: CLAUDE_SETTINGS_REL,
|
|
@@ -334,7 +445,7 @@ async function planClaudeSettingsDrift(rootDir) {
|
|
|
334
445
|
if (!await isRealWriteTargetContained(rootDir, abs)) return null;
|
|
335
446
|
let raw;
|
|
336
447
|
try {
|
|
337
|
-
raw = await
|
|
448
|
+
raw = await readFile3(abs, "utf8");
|
|
338
449
|
} catch {
|
|
339
450
|
return null;
|
|
340
451
|
}
|
|
@@ -400,6 +511,10 @@ function formatClaudeSettingsDrift(report, opts) {
|
|
|
400
511
|
}
|
|
401
512
|
|
|
402
513
|
export {
|
|
514
|
+
CLAUDE_COMMANDS_DIR_REL,
|
|
515
|
+
CLAUDE_HITL_FALLBACK,
|
|
516
|
+
discoverInstalledCommands,
|
|
517
|
+
generateClaudeCommandAdapters,
|
|
403
518
|
CLAUDE_SETTINGS_REL,
|
|
404
519
|
RESOLVE_AGENT_KIT_REL,
|
|
405
520
|
SESSION_START_HOOK_MARKER,
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
REGISTRY_CLI_ARGS,
|
|
3
3
|
resolveRegistryFromCli
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PNG7LO2F.js";
|
|
5
5
|
import {
|
|
6
6
|
logger
|
|
7
7
|
} from "./chunk-ZB43YMHW.js";
|
|
8
8
|
import {
|
|
9
9
|
managedPairs
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-N3HTJVBR.js";
|
|
11
11
|
import {
|
|
12
12
|
loadAgentKitManifest,
|
|
13
13
|
normalizeRelPath,
|
|
14
14
|
resolveContained
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-AW74INUW.js";
|
|
16
16
|
import "./chunk-5VCOWTTP.js";
|
|
17
17
|
import "./chunk-77V75A7O.js";
|
|
18
18
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
REGISTRY_CLI_ARGS,
|
|
3
3
|
resolveRegistryFromCli
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PNG7LO2F.js";
|
|
5
5
|
import {
|
|
6
6
|
logger
|
|
7
7
|
} from "./chunk-ZB43YMHW.js";
|
|
8
8
|
import {
|
|
9
9
|
diffAgainstRegistry,
|
|
10
10
|
summarizeDiff
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-2RDRMGD3.js";
|
|
12
|
+
import "./chunk-N3HTJVBR.js";
|
|
13
13
|
import {
|
|
14
14
|
loadAgentKitManifest
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-AW74INUW.js";
|
|
16
16
|
import "./chunk-5VCOWTTP.js";
|
|
17
17
|
import "./chunk-77V75A7O.js";
|
|
18
18
|
|