@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,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/scanner/safe-fixes.ts
23
- import { readFile, writeFile } from "fs/promises";
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 = path.join(scan.rootDir, REPOSITORY_PROFILE_REL);
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 = path.join(beforeScan.rootDir, relativePath);
234
- const exists = await fileExists(absolutePath);
235
- if (!exists && !dryRun) await ensureDir(absolutePath);
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
- !exists,
547
+ !exists2,
241
548
  dryRun,
242
- relativeEvidence(relativePath, exists ? "already exists" : "missing directory")
549
+ relativeEvidence(relativePath, exists2 ? "already exists" : "missing directory")
243
550
  );
244
551
  }
245
552
  const gitignoreRelativePath = ".gitignore";
246
- const gitignorePath = path.join(beforeScan.rootDir, gitignoreRelativePath);
247
- const existingGitignore = await fileExists(gitignorePath) ? await readFile(gitignorePath, "utf8") : "";
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 = path.join(beforeScan.rootDir, REPOSITORY_PROFILE_REL);
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
- resolveContained
9
- } from "./chunk-VS2JD2XC.js";
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 readFile2, writeFile as writeFile2 } from "fs/promises";
16
- import path2 from "path";
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 path from "path";
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(path3, detail) {
24
- super(`Refusing to use Claude settings ledger ${path3}: ${detail}`);
25
- this.path = path3;
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(path3) {
142
+ constructor(path4) {
32
143
  super(
33
- `Claude settings ledger ${path3} is malformed or has an unknown schemaVersion; not overwriting without authorization.`
144
+ `Claude settings ledger ${path4} is malformed or has an unknown schemaVersion; not overwriting without authorization.`
34
145
  );
35
- this.path = path3;
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 readFile(abs, "utf8"));
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(path.dirname(abs), { recursive: true });
119
- await writeFile(abs, `${JSON.stringify(merged, null, 2)}
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 readFile2(abs, "utf8");
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(path2.dirname(abs));
311
- await writeFile2(abs, merged.content, "utf8");
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 readFile2(abs, "utf8");
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-XWPO3VYQ.js";
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-6QV2BFGA.js";
10
+ } from "./chunk-N3HTJVBR.js";
11
11
  import {
12
12
  loadAgentKitManifest,
13
13
  normalizeRelPath,
14
14
  resolveContained
15
- } from "./chunk-VS2JD2XC.js";
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-XWPO3VYQ.js";
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-YGBBHKBE.js";
12
- import "./chunk-6QV2BFGA.js";
11
+ } from "./chunk-2RDRMGD3.js";
12
+ import "./chunk-N3HTJVBR.js";
13
13
  import {
14
14
  loadAgentKitManifest
15
- } from "./chunk-VS2JD2XC.js";
15
+ } from "./chunk-AW74INUW.js";
16
16
  import "./chunk-5VCOWTTP.js";
17
17
  import "./chunk-77V75A7O.js";
18
18