@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.
@@ -1,497 +0,0 @@
1
- import {
2
- executeSafeReadinessFixes,
3
- refreshRepositoryProfile
4
- } from "./chunk-KI6ZHBPV.js";
5
- import {
6
- writeReadinessSnapshot
7
- } from "./chunk-ZXKZBTXE.js";
8
- import {
9
- CLAUDE_SETTINGS_REL,
10
- GUARD_SHELL_HOOK_MARKER,
11
- RESOLVE_AGENT_KIT_REL,
12
- SESSION_START_HOOK_MARKER
13
- } from "./chunk-CGTE7DHN.js";
14
- import "./chunk-MAPOQEA2.js";
15
- import {
16
- withCliProgress
17
- } from "./chunk-QTON2GQX.js";
18
- import {
19
- createReadinessReport
20
- } from "./chunk-PNFF6OU3.js";
21
- import {
22
- runScanner
23
- } from "./chunk-ZCTSUQRR.js";
24
- import "./chunk-F6C3GFKV.js";
25
- import {
26
- logger
27
- } from "./chunk-ZB43YMHW.js";
28
- import {
29
- assessEnvironment
30
- } from "./chunk-4IWG5CVE.js";
31
- import {
32
- warnIfRunningCliBehindNpm
33
- } from "./chunk-XZZAVHH4.js";
34
- import "./chunk-NQ7IQ5XT.js";
35
- import "./chunk-YGBBHKBE.js";
36
- import "./chunk-6QV2BFGA.js";
37
- import "./chunk-VS2JD2XC.js";
38
- import "./chunk-5VCOWTTP.js";
39
- import {
40
- KIT_VERSION
41
- } from "./chunk-7HDQEK6T.js";
42
- import {
43
- readJson
44
- } from "./chunk-77V75A7O.js";
45
-
46
- // src/commands/doctor.ts
47
- import path2 from "path";
48
- import { defineCommand } from "citty";
49
-
50
- // src/invariants/hooks-health.ts
51
- import { execFile } from "child_process";
52
- import { constants, access, readFile, readdir, stat } from "fs/promises";
53
- import path from "path";
54
- import { promisify } from "util";
55
- var execFileAsync = promisify(execFile);
56
- var GIT_HOOK_CANONICAL_NAMES = ["pre-commit", "pre-push", "prepare-commit-msg"];
57
- var EXPECTED_EVENTS = [
58
- "sessionStart",
59
- "preCompact",
60
- "beforeShellExecution",
61
- "afterFileEdit",
62
- "beforeSubmitPrompt"
63
- ];
64
- async function exists(p) {
65
- try {
66
- await access(p);
67
- return true;
68
- } catch {
69
- return false;
70
- }
71
- }
72
- async function isExecutable(p) {
73
- try {
74
- await access(p, constants.X_OK);
75
- return true;
76
- } catch {
77
- return false;
78
- }
79
- }
80
- async function resolveAgentKitCli(rootDir) {
81
- const root = path.resolve(rootDir);
82
- const candidates = [
83
- path.join(root, "node_modules", ".bin", "agent-kit"),
84
- path.join(root, "packages", "cli", "dist", "index.js")
85
- ];
86
- for (const c of candidates) {
87
- if (await exists(c)) return c;
88
- }
89
- try {
90
- const { stdout } = await execFileAsync("which", ["agent-kit"], { encoding: "utf8" });
91
- const hit = stdout.trim().split("\n")[0]?.trim();
92
- if (hit) return hit;
93
- } catch {
94
- }
95
- return null;
96
- }
97
- function commandLooksLikeAdapter(command) {
98
- const trimmed = command.trim();
99
- if (!trimmed) return null;
100
- const m = trimmed.match(/(\.cursor\/hooks\/agent\/[A-Za-z0-9._-]+\.sh)\b/);
101
- return m?.[1] ?? null;
102
- }
103
- async function assessGitHooksInstallDrift(rootDir) {
104
- const root = path.resolve(rootDir);
105
- const canonicalDir = path.join(root, "git-hooks");
106
- if (!await exists(canonicalDir)) return [];
107
- let hooksPathCfg = "";
108
- try {
109
- const { stdout } = await execFileAsync(
110
- "git",
111
- ["-C", root, "config", "--get", "core.hooksPath"],
112
- { encoding: "utf8" }
113
- );
114
- hooksPathCfg = stdout.trim();
115
- } catch {
116
- }
117
- if (hooksPathCfg) {
118
- const resolved = path.isAbsolute(hooksPathCfg) ? path.normalize(hooksPathCfg) : path.normalize(path.join(root, hooksPathCfg));
119
- if (resolved === path.normalize(canonicalDir)) {
120
- return [];
121
- }
122
- }
123
- const gitDir = path.join(root, ".git");
124
- if (!await exists(gitDir)) return [];
125
- try {
126
- const st = await stat(gitDir);
127
- if (!st.isDirectory()) return [];
128
- } catch {
129
- return [];
130
- }
131
- const installedDir = path.join(gitDir, "hooks");
132
- const advisories = [];
133
- const installHint = "Install or refresh with: `cp git-hooks/<name> .git/hooks/<name> && chmod +x .git/hooks/<name>` (see git-hooks/README.md)";
134
- let names = [...GIT_HOOK_CANONICAL_NAMES];
135
- try {
136
- const listed = await readdir(canonicalDir);
137
- const fromDisk = listed.filter(
138
- (n) => GIT_HOOK_CANONICAL_NAMES.includes(n)
139
- );
140
- if (fromDisk.length > 0) names = fromDisk;
141
- } catch {
142
- }
143
- for (const name of names) {
144
- const canonical = path.join(canonicalDir, name);
145
- const installed = path.join(installedDir, name);
146
- if (!await exists(canonical)) continue;
147
- if (!await exists(installed)) {
148
- advisories.push(
149
- `git-hooks drift: \`.git/hooks/${name}\` missing (canonical \`git-hooks/${name}\` present). ${installHint}`
150
- );
151
- continue;
152
- }
153
- try {
154
- const [a, b] = await Promise.all([readFile(canonical, "utf8"), readFile(installed, "utf8")]);
155
- if (a !== b) {
156
- advisories.push(
157
- `git-hooks drift: \`.git/hooks/${name}\` differs from \`git-hooks/${name}\`. ${installHint}`
158
- );
159
- }
160
- } catch {
161
- advisories.push(
162
- `git-hooks drift: could not compare \`git-hooks/${name}\` with \`.git/hooks/${name}\`. ${installHint}`
163
- );
164
- }
165
- }
166
- return advisories;
167
- }
168
- async function assessSignatureGateHook(rootDir) {
169
- const root = path.resolve(rootDir);
170
- if (!await exists(path.join(root, "autogit", "gitupdate.md"))) return [];
171
- if (await exists(path.join(root, "git-hooks", "prepare-commit-msg"))) return [];
172
- return [
173
- "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."
174
- ];
175
- }
176
- async function resolveGuardProbeArgv(root) {
177
- const hookBin = process.env.AGENT_KIT_HOOK_BIN;
178
- if (hookBin && await isExecutable(hookBin)) return [hookBin];
179
- const local = path.join(root, "node_modules", ".bin", "agent-kit");
180
- if (await isExecutable(local)) return [local];
181
- const dist = path.join(root, "packages", "cli", "dist", "index.js");
182
- if (await exists(dist)) return [process.execPath, dist];
183
- try {
184
- const { stdout } = await execFileAsync("which", ["agent-kit"], { encoding: "utf8" });
185
- const hit = stdout.trim().split("\n")[0]?.trim();
186
- if (hit) return [hit];
187
- } catch {
188
- }
189
- return null;
190
- }
191
- async function assessClaudeGuardProbe(rootDir) {
192
- const root = path.resolve(rootDir);
193
- let text;
194
- try {
195
- text = await readFile(path.join(root, CLAUDE_SETTINGS_REL), "utf8");
196
- } catch {
197
- return [];
198
- }
199
- if (!text.includes(GUARD_SHELL_HOOK_MARKER)) {
200
- return text.includes(SESSION_START_HOOK_MARKER) ? [
201
- "Claude SessionStart hook is installed but the PreToolUse guard hook is absent from `.claude/settings.json`. Run `agent-kit install --claude`."
202
- ] : [];
203
- }
204
- const stale = "Claude guard hook active but resolved CLI is too old or stale dist; update the CLI / rebuild.";
205
- const argv = await resolveGuardProbeArgv(root);
206
- if (!argv) return [`${stale} (agent-kit CLI not resolvable)`];
207
- const [bin, ...lead] = argv;
208
- try {
209
- const { stdout } = await execFileAsync(
210
- bin,
211
- [
212
- ...lead,
213
- "guard",
214
- "shell",
215
- "--format",
216
- "claude",
217
- "--command",
218
- "git push --force origin main"
219
- ],
220
- {
221
- cwd: root,
222
- encoding: "utf8",
223
- timeout: 1e4,
224
- env: { ...process.env, ALLOW_MAIN_PUSH: "" }
225
- }
226
- );
227
- const decision = JSON.parse(stdout).hookSpecificOutput?.permissionDecision;
228
- return decision === "deny" ? [] : [stale];
229
- } catch {
230
- return [stale];
231
- }
232
- }
233
- async function assessHooksHealth(rootDir) {
234
- const root = path.resolve(rootDir);
235
- const hooksJsonPath = ".cursor/hooks.json";
236
- const hooksJsonAbs = path.join(root, hooksJsonPath);
237
- const reasons = [];
238
- const wiredEvents = [];
239
- const advisories = [
240
- ...await assessGitHooksInstallDrift(root),
241
- ...await assessSignatureGateHook(root),
242
- ...await assessClaudeGuardProbe(root)
243
- ];
244
- if (!await exists(hooksJsonAbs)) {
245
- return {
246
- status: "missing",
247
- reasons: ["`.cursor/hooks.json` not found"],
248
- advisories,
249
- hooksJsonPath,
250
- expectedEvents: [...EXPECTED_EVENTS],
251
- wiredEvents
252
- };
253
- }
254
- let parsed;
255
- try {
256
- parsed = JSON.parse(await readFile(hooksJsonAbs, "utf8"));
257
- } catch {
258
- return {
259
- status: "degraded",
260
- reasons: ["`.cursor/hooks.json` is not valid JSON"],
261
- advisories,
262
- hooksJsonPath,
263
- expectedEvents: [...EXPECTED_EVENTS],
264
- wiredEvents
265
- };
266
- }
267
- const hooks = parsed.hooks ?? {};
268
- const adapterRels = /* @__PURE__ */ new Set();
269
- for (const event of EXPECTED_EVENTS) {
270
- const list = hooks[event];
271
- if (Array.isArray(list) && list.length > 0) {
272
- wiredEvents.push(event);
273
- for (const entry of list) {
274
- if (!entry || typeof entry !== "object") continue;
275
- const command = String(entry.command ?? "");
276
- if (command.endsWith(".py") || command.includes("python")) {
277
- reasons.push(`${event} still points at a Python script (${command})`);
278
- }
279
- const rel = commandLooksLikeAdapter(command);
280
- if (rel) adapterRels.add(rel);
281
- }
282
- } else {
283
- reasons.push(`missing hook event: ${event}`);
284
- }
285
- }
286
- const resolveLib = path.join(root, RESOLVE_AGENT_KIT_REL);
287
- if (!await exists(resolveLib)) {
288
- reasons.push(`missing \`${RESOLVE_AGENT_KIT_REL}\` (thin adapter resolver)`);
289
- } else if (!await isExecutable(resolveLib)) {
290
- reasons.push(`\`${RESOLVE_AGENT_KIT_REL}\` is not executable (chmod +x)`);
291
- }
292
- for (const rel of adapterRels) {
293
- const abs = path.join(root, rel);
294
- if (!await exists(abs)) {
295
- reasons.push(`missing adapter script: \`${rel}\``);
296
- continue;
297
- }
298
- try {
299
- const st = await stat(abs);
300
- if (!st.isFile()) {
301
- reasons.push(`adapter path is not a file: \`${rel}\``);
302
- continue;
303
- }
304
- } catch {
305
- reasons.push(`unreadable adapter script: \`${rel}\``);
306
- continue;
307
- }
308
- if (!await isExecutable(abs)) {
309
- reasons.push(`adapter not executable: \`${rel}\` (chmod +x)`);
310
- }
311
- }
312
- const cli = await resolveAgentKitCli(root);
313
- if (!cli) {
314
- reasons.push(
315
- "agent-kit CLI not resolvable (node_modules/.bin/agent-kit, packages/cli/dist, or PATH)"
316
- );
317
- }
318
- if (Array.isArray(hooks.stop) && hooks.stop.length > 0) {
319
- reasons.push("`stop` hook is registered (forbidden; remove it)");
320
- }
321
- const status = reasons.length === 0 && wiredEvents.length === EXPECTED_EVENTS.length ? "active" : "degraded";
322
- return {
323
- status,
324
- reasons,
325
- advisories,
326
- hooksJsonPath,
327
- expectedEvents: [...EXPECTED_EVENTS],
328
- wiredEvents
329
- };
330
- }
331
-
332
- // src/commands/doctor.ts
333
- async function readDeferredEssentials(rootDir, report) {
334
- const config = await readJson(
335
- path2.join(rootDir, ".cursor", "context", "config.json")
336
- );
337
- const deferredItems = config?.onboarding?.deferredItems;
338
- if (!Array.isArray(deferredItems)) return [];
339
- const essentialIds = new Set(
340
- report.pillars.flatMap((pillar) => pillar.checks).filter((check) => check.essential).map((check) => check.id)
341
- );
342
- return deferredItems.filter(
343
- (item) => typeof item?.checkId === "string" && typeof item?.reason === "string" && essentialIds.has(item.checkId)
344
- ).map((item) => ({ checkId: item.checkId, reason: item.reason }));
345
- }
346
- async function runDoctor(cwd, options = {}) {
347
- const rootDir = path2.resolve(cwd);
348
- const hooks = await assessHooksHealth(rootDir);
349
- const env = await assessEnvironment();
350
- if (options.fixSafe) {
351
- const execution = await executeSafeReadinessFixes(rootDir, {
352
- generatorVersion: KIT_VERSION,
353
- generatedAt: options.generatedAt
354
- });
355
- await writeReadinessSnapshot(rootDir, execution.after);
356
- return {
357
- report: execution.after,
358
- safeChanges: execution.changes,
359
- hooks,
360
- env,
361
- deferredEssentials: await readDeferredEssentials(rootDir, execution.after)
362
- };
363
- }
364
- if (options.refreshProfile) {
365
- const refresh = await refreshRepositoryProfile(rootDir, {
366
- generatorVersion: KIT_VERSION,
367
- generatedAt: options.generatedAt
368
- });
369
- const scan2 = await runScanner(rootDir);
370
- const report2 = createReadinessReport(scan2, {
371
- generatorVersion: KIT_VERSION,
372
- generatedAt: options.generatedAt
373
- });
374
- await writeReadinessSnapshot(rootDir, report2);
375
- return {
376
- report: report2,
377
- safeChanges: [],
378
- hooks,
379
- env,
380
- profileRefreshed: refresh.changed,
381
- deferredEssentials: await readDeferredEssentials(rootDir, report2)
382
- };
383
- }
384
- const scan = await runScanner(rootDir);
385
- const report = createReadinessReport(scan, {
386
- generatorVersion: KIT_VERSION,
387
- generatedAt: options.generatedAt
388
- });
389
- await writeReadinessSnapshot(rootDir, report);
390
- return {
391
- report,
392
- safeChanges: [],
393
- hooks,
394
- env,
395
- deferredEssentials: await readDeferredEssentials(rootDir, report)
396
- };
397
- }
398
- function printDoctorSummary(result) {
399
- const { summary, pendingActions } = result.report;
400
- const fixed = result.safeChanges.filter((change) => change.status === "applied").length;
401
- const nextAction = pendingActions[0];
402
- console.log("Repository readiness");
403
- console.log(
404
- ` ready: ${summary.ready}, choices: ${summary.needs_choice}, manual: ${summary.manual}, blocked: ${summary.blocked}`
405
- );
406
- console.log(` safe fixes applied: ${fixed}`);
407
- console.log(` pending actions: ${pendingActions.length}`);
408
- if (result.profileRefreshed !== void 0) {
409
- console.log(
410
- ` profile refreshed: ${result.profileRefreshed ? "yes (facts changed)" : "no (already current)"}`
411
- );
412
- }
413
- for (const item of result.deferredEssentials) {
414
- console.log(` Deferred essential: ${item.checkId} \u2014 ${item.reason}`);
415
- }
416
- console.log(`hooks: ${result.hooks.status}`);
417
- if (result.hooks.reasons.length > 0) {
418
- for (const reason of result.hooks.reasons.slice(0, 5)) {
419
- console.log(` - ${reason}`);
420
- }
421
- }
422
- if (result.hooks.advisories.length > 0) {
423
- console.log("hooks advisories (soft; install via cp, see git-hooks/README.md):");
424
- for (const tip of result.hooks.advisories.slice(0, 5)) {
425
- console.log(` - ${tip}`);
426
- }
427
- }
428
- console.log("environment:");
429
- const pathLabel = result.env.binOnPath ? `ok (v${result.env.binVersion ?? "unknown"} at ${result.env.binPath})` : "MISSING";
430
- console.log(` - bin on PATH (agent-kit): ${pathLabel}`);
431
- console.log(
432
- ` - npm prefix writable: ${result.env.npmPrefixWritable ? "ok" : "BLOCKED"}${result.env.npmPrefix.prefix ? ` (${result.env.npmPrefix.prefix})` : ""}`
433
- );
434
- if (!result.env.npmPrefixWritable && result.env.npmPrefix.reason) {
435
- console.log(` - ${result.env.npmPrefix.reason}`);
436
- }
437
- console.log(
438
- ` - node version >= 20: ${result.env.nodeVersionOk ? "ok" : "TOO OLD"} (${result.env.nodeVersion})`
439
- );
440
- console.log(
441
- ` - shell profile: ${result.env.shellProfile ?? "not detected (zsh/bash only)"}${result.env.shell ? ` (shell: ${result.env.shell})` : ""}`
442
- );
443
- if (process.env.ALLOW_MAIN_PUSH === "1") {
444
- console.log("\u26A0\uFE0F WARNING: ALLOW_MAIN_PUSH=1 is set in environment");
445
- console.log(" This disables main-push protection for agent Shell commands.");
446
- console.log(" Consider unsetting it: unset ALLOW_MAIN_PUSH");
447
- }
448
- console.log(
449
- nextAction ? `Next: ${nextAction.recommendation}` : "Next: repository readiness checks are complete"
450
- );
451
- }
452
- var doctorCommand = defineCommand({
453
- meta: {
454
- name: "doctor",
455
- description: "Diagnose repository readiness; optional --fix-safe local repairs."
456
- },
457
- args: {
458
- cwd: {
459
- type: "string",
460
- default: process.cwd()
461
- },
462
- json: {
463
- type: "boolean",
464
- description: "Print deterministic machine-readable JSON without status messages",
465
- default: false
466
- },
467
- "fix-safe": {
468
- type: "boolean",
469
- description: "Apply only local, reversible, merge-safe readiness fixes",
470
- default: false
471
- },
472
- "refresh-profile": {
473
- type: "boolean",
474
- description: "Reconcile .cursor/agent-kit.config.json with current scanner facts: fresh values win on shared keys, unrecognized existing keys are preserved",
475
- default: false
476
- }
477
- },
478
- async run({ args }) {
479
- if (!args.json) {
480
- await warnIfRunningCliBehindNpm(args.cwd, { warn: (message) => logger.warn(message) });
481
- }
482
- const run = () => runDoctor(args.cwd, {
483
- fixSafe: args["fix-safe"],
484
- refreshProfile: args["refresh-profile"]
485
- });
486
- const result = args.json ? await run() : await withCliProgress("doctor", run);
487
- if (args.json) {
488
- console.log(JSON.stringify(result, null, 2));
489
- return;
490
- }
491
- printDoctorSummary(result);
492
- }
493
- });
494
- export {
495
- doctorCommand,
496
- runDoctor
497
- };