@dadado/agent-kit-cli 5.16.0 → 5.18.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.
Files changed (40) hide show
  1. package/dashboard/README.md +10 -1
  2. package/dashboard/dashboard-data.mjs +12 -2
  3. package/dashboard/dashboard.html +58 -0
  4. package/dashboard/lib/run-logs.d.mts +34 -0
  5. package/dashboard/lib/run-logs.mjs +218 -0
  6. package/dist/{add-7UMSGVWC.js → add-KQJE3ZTG.js} +3 -3
  7. package/dist/{chunk-KD2MYBB6.js → chunk-3J3I2W22.js} +1 -1
  8. package/dist/{chunk-YGBBHKBE.js → chunk-6FXQINND.js} +2 -2
  9. package/dist/{chunk-OUQZC744.js → chunk-B5CH47OS.js} +852 -74
  10. package/dist/chunk-C26I6HTO.js +39 -0
  11. package/dist/{chunk-6QV2BFGA.js → chunk-GSCXFJB7.js} +1 -1
  12. package/dist/{chunk-KI6ZHBPV.js → chunk-HRILMD5N.js} +312 -11
  13. package/dist/{chunk-OZ4QV6LM.js → chunk-KIXK7KOW.js} +42 -148
  14. package/dist/chunk-P6PMKYWU.js +50 -0
  15. package/dist/{chunk-WW3YZKHW.js → chunk-RZ5TNBHU.js} +25 -4
  16. package/dist/{chunk-TFZ2JUXH.js → chunk-TOWH3XCJ.js} +520 -70
  17. package/dist/{chunk-CGTE7DHN.js → chunk-TZYKLR5J.js} +135 -20
  18. package/dist/{chunk-5OXPNVCF.js → chunk-UEKWZ7BN.js} +2 -2
  19. package/dist/chunk-WGLPZXNU.js +45 -0
  20. package/dist/{chunk-VS2JD2XC.js → chunk-WSJ77EHO.js} +5 -2
  21. package/dist/{chunk-VGZBARQC.js → chunk-YJMZ6UM2.js} +2 -2
  22. package/dist/{chunk-XZZAVHH4.js → chunk-YMYLO5LG.js} +2 -2
  23. package/dist/{chunk-XWPO3VYQ.js → chunk-ZDCEAGD4.js} +1 -1
  24. package/dist/{contribute-YKK6YCSI.js → contribute-LFT762Y4.js} +4 -4
  25. package/dist/{diff-YTR7VOAN.js → diff-56KMRCER.js} +5 -5
  26. package/dist/doctor-AA64XVEY.js +237 -0
  27. package/dist/{handoff-3UI6VKVG.js → handoff-34SDU4FF.js} +2 -2
  28. package/dist/{hook-MUV6Y7UW.js → hook-KRIXBLNW.js} +7 -7
  29. package/dist/index.js +19 -17
  30. package/dist/{init-TTTKGC4C.js → init-XP5EAUSJ.js} +12 -11
  31. package/dist/{install-ZMFJBPOI.js → install-SHDEX54F.js} +12 -11
  32. package/dist/{plan-index-JMC7Z4DW.js → plan-index-O3545OA7.js} +2 -2
  33. package/dist/{run-I3BFGULT.js → run-FADX6QCP.js} +6 -5
  34. package/dist/{run-plan-FO7CL4M3.js → run-plan-MO4IFAKV.js} +36 -5
  35. package/dist/{setup-global-JUVMJXJ5.js → setup-global-CBWLXOQG.js} +6 -6
  36. package/dist/{status-MVCUNCWX.js → status-BSIXWZMY.js} +19 -7
  37. package/dist/{update-Q4EBIRUB.js → update-FJX3DA6X.js} +33 -10
  38. package/package.json +3 -3
  39. package/dist/chunk-5VCOWTTP.js +0 -6
  40. package/dist/doctor-TA2FWREU.js +0 -497
@@ -0,0 +1,39 @@
1
+ // src/utils/kit-paths.ts
2
+ import { mkdir, writeFile } from "fs/promises";
3
+ import path from "path";
4
+ var HANDOFF_REL = ".cursor/HANDOFF.md";
5
+ var STATE_ROOT_ENV = "AGENT_KIT_STATE_ROOT";
6
+ var STATE_ROOT_DIRS = [".cursor", ".agent-kit"];
7
+ function resolveStateDir(env = process.env) {
8
+ const raw = (env[STATE_ROOT_ENV] ?? "").trim().replace(/\/+$/, "");
9
+ if (!raw) return { stateDir: ".cursor" };
10
+ if (STATE_ROOT_DIRS.includes(raw))
11
+ return { stateDir: raw };
12
+ return { stateDir: ".cursor", invalid: raw };
13
+ }
14
+ function runnerStatePaths(root, env = process.env) {
15
+ const { stateDir, invalid } = resolveStateDir(env);
16
+ const base = path.join(root, stateDir);
17
+ return {
18
+ stateDir,
19
+ loopLogsDir: path.join(base, "loop-logs"),
20
+ stopFile: path.join(base, "loop.stop"),
21
+ ...invalid !== void 0 ? { invalid } : {}
22
+ };
23
+ }
24
+ async function ensureLoopLogsDir(paths) {
25
+ await mkdir(paths.loopLogsDir, { recursive: true });
26
+ if (paths.stateDir === ".cursor") return;
27
+ const ignore = path.join(paths.loopLogsDir, ".gitignore");
28
+ try {
29
+ await writeFile(ignore, "*\n", { flag: "wx" });
30
+ } catch {
31
+ }
32
+ }
33
+
34
+ export {
35
+ HANDOFF_REL,
36
+ STATE_ROOT_ENV,
37
+ runnerStatePaths,
38
+ ensureLoopLogsDir
39
+ };
@@ -4,7 +4,7 @@ import {
4
4
  loadRegistry,
5
5
  packMemberFileTargets,
6
6
  skillFileTargets
7
- } from "./chunk-VS2JD2XC.js";
7
+ } from "./chunk-WSJ77EHO.js";
8
8
 
9
9
  // src/lifecycle/l0.ts
10
10
  var L0_ARTIFACTS = [
@@ -1,3 +1,14 @@
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-TZYKLR5J.js";
9
+ import {
10
+ findOnPath
11
+ } from "./chunk-WGLPZXNU.js";
1
12
  import {
2
13
  createReadinessReport
3
14
  } from "./chunk-PNFF6OU3.js";
@@ -19,9 +30,297 @@ import {
19
30
  writeJson
20
31
  } from "./chunk-77V75A7O.js";
21
32
 
22
- // src/scanner/safe-fixes.ts
23
- import { readFile, writeFile } from "fs/promises";
33
+ // src/invariants/hooks-health.ts
34
+ import { execFile } from "child_process";
35
+ import { constants, access, readFile, readdir, stat } from "fs/promises";
24
36
  import path from "path";
37
+ import { promisify } from "util";
38
+ var execFileAsync = promisify(execFile);
39
+ var GIT_HOOK_CANONICAL_NAMES = ["pre-commit", "pre-push", "prepare-commit-msg"];
40
+ var EXPECTED_EVENTS = [
41
+ "sessionStart",
42
+ "preCompact",
43
+ "beforeShellExecution",
44
+ "afterFileEdit",
45
+ "beforeSubmitPrompt"
46
+ ];
47
+ async function exists(p) {
48
+ try {
49
+ await access(p);
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+ async function isExecutable(p) {
56
+ try {
57
+ await access(p, constants.X_OK);
58
+ return true;
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+ async function resolveAgentKitCli(rootDir) {
64
+ const root = path.resolve(rootDir);
65
+ const candidates = [
66
+ path.join(root, "node_modules", ".bin", "agent-kit"),
67
+ path.join(root, "packages", "cli", "dist", "index.js")
68
+ ];
69
+ for (const c of candidates) {
70
+ if (await exists(c)) return c;
71
+ }
72
+ return findOnPath("agent-kit");
73
+ }
74
+ function commandLooksLikeAdapter(command) {
75
+ const trimmed = command.trim();
76
+ if (!trimmed) return null;
77
+ const m = trimmed.match(/(\.cursor\/hooks\/agent\/[A-Za-z0-9._-]+\.sh)\b/);
78
+ return m?.[1] ?? null;
79
+ }
80
+ async function assessGitHooksInstallDrift(rootDir) {
81
+ const root = path.resolve(rootDir);
82
+ const canonicalDir = path.join(root, "git-hooks");
83
+ if (!await exists(canonicalDir)) return [];
84
+ let hooksPathCfg = "";
85
+ try {
86
+ const { stdout } = await execFileAsync(
87
+ "git",
88
+ ["-C", root, "config", "--get", "core.hooksPath"],
89
+ { encoding: "utf8" }
90
+ );
91
+ hooksPathCfg = stdout.trim();
92
+ } catch {
93
+ }
94
+ if (hooksPathCfg) {
95
+ const resolved = path.isAbsolute(hooksPathCfg) ? path.normalize(hooksPathCfg) : path.normalize(path.join(root, hooksPathCfg));
96
+ if (resolved === path.normalize(canonicalDir)) {
97
+ return [];
98
+ }
99
+ }
100
+ const gitDir = path.join(root, ".git");
101
+ if (!await exists(gitDir)) return [];
102
+ try {
103
+ const st = await stat(gitDir);
104
+ if (!st.isDirectory()) return [];
105
+ } catch {
106
+ return [];
107
+ }
108
+ const installedDir = path.join(gitDir, "hooks");
109
+ const advisories = [];
110
+ const installHint = "Install or refresh with: `cp git-hooks/<name> .git/hooks/<name> && chmod +x .git/hooks/<name>` (see git-hooks/README.md)";
111
+ let names = [...GIT_HOOK_CANONICAL_NAMES];
112
+ try {
113
+ const listed = await readdir(canonicalDir);
114
+ const fromDisk = listed.filter(
115
+ (n) => GIT_HOOK_CANONICAL_NAMES.includes(n)
116
+ );
117
+ if (fromDisk.length > 0) names = fromDisk;
118
+ } catch {
119
+ }
120
+ for (const name of names) {
121
+ const canonical = path.join(canonicalDir, name);
122
+ const installed = path.join(installedDir, name);
123
+ if (!await exists(canonical)) continue;
124
+ if (!await exists(installed)) {
125
+ advisories.push(
126
+ `git-hooks drift: \`.git/hooks/${name}\` missing (canonical \`git-hooks/${name}\` present). ${installHint}`
127
+ );
128
+ continue;
129
+ }
130
+ try {
131
+ const [a, b] = await Promise.all([readFile(canonical, "utf8"), readFile(installed, "utf8")]);
132
+ if (a !== b) {
133
+ advisories.push(
134
+ `git-hooks drift: \`.git/hooks/${name}\` differs from \`git-hooks/${name}\`. ${installHint}`
135
+ );
136
+ }
137
+ } catch {
138
+ advisories.push(
139
+ `git-hooks drift: could not compare \`git-hooks/${name}\` with \`.git/hooks/${name}\`. ${installHint}`
140
+ );
141
+ }
142
+ }
143
+ return advisories;
144
+ }
145
+ async function assessSignatureGateHook(rootDir) {
146
+ const root = path.resolve(rootDir);
147
+ if (!await exists(path.join(root, "autogit", "gitupdate.md"))) return [];
148
+ if (await exists(path.join(root, "git-hooks", "prepare-commit-msg"))) return [];
149
+ return [
150
+ "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."
151
+ ];
152
+ }
153
+ async function assessClaudeCommandAdapters(rootDir) {
154
+ const root = path.resolve(rootDir);
155
+ if (!await exists(path.join(root, ".claude"))) return [];
156
+ const sources = await discoverInstalledCommands(root);
157
+ if (sources.length === 0) return [];
158
+ const missing = [];
159
+ for (const { name } of sources) {
160
+ if (!await exists(path.join(root, CLAUDE_COMMANDS_DIR_REL, `${name}.md`))) missing.push(name);
161
+ }
162
+ if (missing.length === 0) return [];
163
+ const shown = missing.slice(0, 3).map((n) => `/${n}`);
164
+ const more = missing.length > shown.length ? `, +${missing.length - shown.length} more` : "";
165
+ return [
166
+ `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/*\`).`
167
+ ];
168
+ }
169
+ async function resolveGuardProbeArgv(root) {
170
+ const hookBin = process.env.AGENT_KIT_HOOK_BIN;
171
+ if (hookBin && await isExecutable(hookBin)) return [hookBin];
172
+ const local = path.join(root, "node_modules", ".bin", "agent-kit");
173
+ if (await isExecutable(local)) return [local];
174
+ const dist = path.join(root, "packages", "cli", "dist", "index.js");
175
+ if (await exists(dist)) return [process.execPath, dist];
176
+ const hit = await findOnPath("agent-kit");
177
+ return hit ? [hit] : null;
178
+ }
179
+ async function assessClaudeGuardProbe(rootDir) {
180
+ const root = path.resolve(rootDir);
181
+ let text;
182
+ try {
183
+ text = await readFile(path.join(root, CLAUDE_SETTINGS_REL), "utf8");
184
+ } catch {
185
+ return [];
186
+ }
187
+ if (!text.includes(GUARD_SHELL_HOOK_MARKER)) {
188
+ return text.includes(SESSION_START_HOOK_MARKER) ? [
189
+ "Claude SessionStart hook is installed but the PreToolUse guard hook is absent from `.claude/settings.json`. Run `agent-kit install --claude`."
190
+ ] : [];
191
+ }
192
+ const stale = "Claude guard hook active but resolved CLI is too old or stale dist; update the CLI / rebuild.";
193
+ const argv = await resolveGuardProbeArgv(root);
194
+ if (!argv) return [`${stale} (agent-kit CLI not resolvable)`];
195
+ const [bin, ...lead] = argv;
196
+ try {
197
+ const { stdout } = await execFileAsync(
198
+ bin,
199
+ [
200
+ ...lead,
201
+ "guard",
202
+ "shell",
203
+ "--format",
204
+ "claude",
205
+ "--command",
206
+ "git push --force origin main"
207
+ ],
208
+ {
209
+ cwd: root,
210
+ encoding: "utf8",
211
+ timeout: 1e4,
212
+ env: { ...process.env, ALLOW_MAIN_PUSH: "" }
213
+ }
214
+ );
215
+ const decision = JSON.parse(stdout).hookSpecificOutput?.permissionDecision;
216
+ return decision === "deny" ? [] : [stale];
217
+ } catch {
218
+ return [stale];
219
+ }
220
+ }
221
+ async function assessHooksHealth(rootDir) {
222
+ const root = path.resolve(rootDir);
223
+ const hooksJsonPath = ".cursor/hooks.json";
224
+ const hooksJsonAbs = path.join(root, hooksJsonPath);
225
+ const reasons = [];
226
+ const wiredEvents = [];
227
+ const advisories = [
228
+ ...await assessGitHooksInstallDrift(root),
229
+ ...await assessSignatureGateHook(root),
230
+ ...await assessClaudeGuardProbe(root),
231
+ ...await assessClaudeCommandAdapters(root)
232
+ ];
233
+ if (!await exists(hooksJsonAbs)) {
234
+ return {
235
+ status: "missing",
236
+ reasons: ["`.cursor/hooks.json` not found"],
237
+ advisories,
238
+ hooksJsonPath,
239
+ expectedEvents: [...EXPECTED_EVENTS],
240
+ wiredEvents
241
+ };
242
+ }
243
+ let parsed;
244
+ try {
245
+ parsed = JSON.parse(await readFile(hooksJsonAbs, "utf8"));
246
+ } catch {
247
+ return {
248
+ status: "degraded",
249
+ reasons: ["`.cursor/hooks.json` is not valid JSON"],
250
+ advisories,
251
+ hooksJsonPath,
252
+ expectedEvents: [...EXPECTED_EVENTS],
253
+ wiredEvents
254
+ };
255
+ }
256
+ const hooks = parsed.hooks ?? {};
257
+ const adapterRels = /* @__PURE__ */ new Set();
258
+ for (const event of EXPECTED_EVENTS) {
259
+ const list = hooks[event];
260
+ if (Array.isArray(list) && list.length > 0) {
261
+ wiredEvents.push(event);
262
+ for (const entry of list) {
263
+ if (!entry || typeof entry !== "object") continue;
264
+ const command = String(entry.command ?? "");
265
+ if (command.endsWith(".py") || command.includes("python")) {
266
+ reasons.push(`${event} still points at a Python script (${command})`);
267
+ }
268
+ const rel = commandLooksLikeAdapter(command);
269
+ if (rel) adapterRels.add(rel);
270
+ }
271
+ } else {
272
+ reasons.push(`missing hook event: ${event}`);
273
+ }
274
+ }
275
+ const resolveLib = path.join(root, RESOLVE_AGENT_KIT_REL);
276
+ if (!await exists(resolveLib)) {
277
+ reasons.push(`missing \`${RESOLVE_AGENT_KIT_REL}\` (thin adapter resolver)`);
278
+ } else if (!await isExecutable(resolveLib)) {
279
+ reasons.push(`\`${RESOLVE_AGENT_KIT_REL}\` is not executable (chmod +x)`);
280
+ }
281
+ for (const rel of adapterRels) {
282
+ const abs = path.join(root, rel);
283
+ if (!await exists(abs)) {
284
+ reasons.push(`missing adapter script: \`${rel}\``);
285
+ continue;
286
+ }
287
+ try {
288
+ const st = await stat(abs);
289
+ if (!st.isFile()) {
290
+ reasons.push(`adapter path is not a file: \`${rel}\``);
291
+ continue;
292
+ }
293
+ } catch {
294
+ reasons.push(`unreadable adapter script: \`${rel}\``);
295
+ continue;
296
+ }
297
+ if (!await isExecutable(abs)) {
298
+ reasons.push(`adapter not executable: \`${rel}\` (chmod +x)`);
299
+ }
300
+ }
301
+ const cli = await resolveAgentKitCli(root);
302
+ if (!cli) {
303
+ reasons.push(
304
+ "agent-kit CLI not resolvable (node_modules/.bin/agent-kit, packages/cli/dist, or PATH)"
305
+ );
306
+ }
307
+ if (Array.isArray(hooks.stop) && hooks.stop.length > 0) {
308
+ reasons.push("`stop` hook is registered (forbidden; remove it)");
309
+ }
310
+ const status = reasons.length === 0 && wiredEvents.length === EXPECTED_EVENTS.length ? "active" : "degraded";
311
+ return {
312
+ status,
313
+ reasons,
314
+ advisories,
315
+ hooksJsonPath,
316
+ expectedEvents: [...EXPECTED_EVENTS],
317
+ wiredEvents
318
+ };
319
+ }
320
+
321
+ // src/scanner/safe-fixes.ts
322
+ import { readFile as readFile2, writeFile } from "fs/promises";
323
+ import path2 from "path";
25
324
  var CONTEXT_CONFIG_REL_POSIX = CONTEXT_CONFIG_REL.replace(/\\/g, "/");
26
325
  var ESSENTIAL_DIRECTORIES = [
27
326
  ".cursor",
@@ -207,7 +506,7 @@ async function refreshRepositoryProfile(rootDir, options) {
207
506
  generatedAt
208
507
  });
209
508
  const desiredProfile = createProfile(scan, report, generatedAt);
210
- const profilePath = path.join(scan.rootDir, REPOSITORY_PROFILE_REL);
509
+ const profilePath = path2.join(scan.rootDir, REPOSITORY_PROFILE_REL);
211
510
  const existingProfile = await readJson(profilePath) ?? {};
212
511
  const reconciled = reconcileFreshOverExisting(desiredProfile, existingProfile);
213
512
  const meaningfulChange = !jsonEqual(
@@ -230,21 +529,21 @@ async function executeSafeReadinessFixes(rootDir, options) {
230
529
  });
231
530
  const changes = [];
232
531
  for (const relativePath of ESSENTIAL_DIRECTORIES) {
233
- const absolutePath = path.join(beforeScan.rootDir, relativePath);
234
- const exists = await fileExists(absolutePath);
235
- if (!exists && !dryRun) await ensureDir(absolutePath);
532
+ const absolutePath = path2.join(beforeScan.rootDir, relativePath);
533
+ const exists2 = await fileExists(absolutePath);
534
+ if (!exists2 && !dryRun) await ensureDir(absolutePath);
236
535
  recordChange(
237
536
  changes,
238
537
  "ensure-agent-kit-directory",
239
538
  relativePath,
240
- !exists,
539
+ !exists2,
241
540
  dryRun,
242
- relativeEvidence(relativePath, exists ? "already exists" : "missing directory")
541
+ relativeEvidence(relativePath, exists2 ? "already exists" : "missing directory")
243
542
  );
244
543
  }
245
544
  const gitignoreRelativePath = ".gitignore";
246
- const gitignorePath = path.join(beforeScan.rootDir, gitignoreRelativePath);
247
- const existingGitignore = await fileExists(gitignorePath) ? await readFile(gitignorePath, "utf8") : "";
545
+ const gitignorePath = path2.join(beforeScan.rootDir, gitignoreRelativePath);
546
+ const existingGitignore = await fileExists(gitignorePath) ? await readFile2(gitignorePath, "utf8") : "";
248
547
  const gitignoreAfterSecrets = mergeSecretIgnores(existingGitignore);
249
548
  const secretsChanged = gitignoreAfterSecrets !== existingGitignore;
250
549
  const mergedGitignore = mergeKitOwnedIgnores(gitignoreAfterSecrets);
@@ -273,7 +572,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
273
572
  kitOwnedChanged ? "kit-owned session/derived paths are missing" : "kit-owned session/derived paths are present"
274
573
  )
275
574
  );
276
- const profilePath = path.join(beforeScan.rootDir, REPOSITORY_PROFILE_REL);
575
+ const profilePath = path2.join(beforeScan.rootDir, REPOSITORY_PROFILE_REL);
277
576
  const existingProfile = await readJson(profilePath) ?? {};
278
577
  const desiredProfile = createProfile(beforeScan, before, generatedAt);
279
578
  const mergedProfile = mergeMissing(existingProfile, desiredProfile);
@@ -327,6 +626,8 @@ async function executeSafeReadinessFixes(rootDir, options) {
327
626
  }
328
627
 
329
628
  export {
629
+ assessClaudeCommandAdapters,
630
+ assessHooksHealth,
330
631
  refreshRepositoryProfile,
331
632
  executeSafeReadinessFixes
332
633
  };