finitemonkey 0.1.0-next.13 → 0.1.0-next.14

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 (2) hide show
  1. package/dist/finitemonkey.mjs +124 -91
  2. package/package.json +1 -1
@@ -2227,8 +2227,28 @@ var reportMarkdown = (env, ctx, envelope) => {
2227
2227
  };
2228
2228
  var reportPath = (env) => toAbsolutePath(join5(env.root, ...REPORTS_DIR.split("/"), `doctor-${stamp(env.runtime.now())}.md`));
2229
2229
 
2230
- // src/doctor/settings.ts
2230
+ // src/install/plugin-yield.ts
2231
2231
  import { join as join6 } from "node:path";
2232
+ var PLUGIN_VIA = "plugin";
2233
+ var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
2234
+ `;
2235
+ var projectOwnsHooks = (ctx) => {
2236
+ const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
2237
+ const text6 = ctx.runtime.readText(toAbsolutePath(join6(root, ...SETTINGS_FILE.split("/"))));
2238
+ if (!text6.ok) return false;
2239
+ const parsed = parseJsonObject(text6.value);
2240
+ return parsed.ok && ownHandlersIn(parsed.value) > 0;
2241
+ };
2242
+
2243
+ // src/install/plugin-hooks.ts
2244
+ var PLUGIN_NAME = "finitemonkey";
2245
+ var MARKETPLACE_NAME = "finitemonkey";
2246
+ var NEXT_MARKETPLACE_NAME = "finitemonkey-next";
2247
+ var STABLE_PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
2248
+ var NEXT_PLUGIN_ID = `${PLUGIN_NAME}@${NEXT_MARKETPLACE_NAME}`;
2249
+
2250
+ // src/doctor/settings.ts
2251
+ import { join as join7 } from "node:path";
2232
2252
  var MANAGED = {
2233
2253
  darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
2234
2254
  linux: "/etc/claude-code/managed-settings.json",
@@ -2237,7 +2257,7 @@ var MANAGED = {
2237
2257
  var userSettingsPath = ({ env, runtime }) => {
2238
2258
  const configDir = env["CLAUDE_CONFIG_DIR"];
2239
2259
  return toAbsolutePath(
2240
- configDir === void 0 ? join6(runtime.homeDir(), ".claude", "settings.json") : join6(configDir, "settings.json")
2260
+ configDir === void 0 ? join7(runtime.homeDir(), ".claude", "settings.json") : join7(configDir, "settings.json")
2241
2261
  );
2242
2262
  };
2243
2263
  var settingsPaths = (env) => {
@@ -2245,8 +2265,8 @@ var settingsPaths = (env) => {
2245
2265
  return [
2246
2266
  ...managed === void 0 ? [] : [{ scope: "managed", path: toAbsolutePath(managed) }],
2247
2267
  { scope: "user", path: userSettingsPath(env) },
2248
- { scope: "project", path: toAbsolutePath(join6(env.root, ".claude", "settings.json")) },
2249
- { scope: "local", path: toAbsolutePath(join6(env.root, ".claude", "settings.local.json")) }
2268
+ { scope: "project", path: toAbsolutePath(join7(env.root, ".claude", "settings.json")) },
2269
+ { scope: "local", path: toAbsolutePath(join7(env.root, ".claude", "settings.local.json")) }
2250
2270
  ];
2251
2271
  };
2252
2272
  var readSettings = (env) => settingsPaths(env).flatMap(({ scope: scope2, path }) => {
@@ -2358,6 +2378,21 @@ var cleanupPeriod = (env, all) => {
2358
2378
  )
2359
2379
  ];
2360
2380
  };
2381
+ var PRECEDENCE = ["user", "project", "local", "managed"];
2382
+ var pluginOn = (all, id) => {
2383
+ const ordered = PRECEDENCE.flatMap((scope2) => all.filter((item) => item.scope === scope2));
2384
+ const values = ordered.map(({ settings }) => settings["enabledPlugins"]).map((plugins) => isRecord(plugins) ? plugins[id] : void 0).filter((value) => typeof value === "boolean");
2385
+ return values.at(-1) === true;
2386
+ };
2387
+ var pluginChannels = (all) => pluginOn(all, STABLE_PLUGIN_ID) && pluginOn(all, NEXT_PLUGIN_ID) ? [
2388
+ finding(
2389
+ "settings.plugin_channels",
2390
+ "fail",
2391
+ "doctor.settings.plugin_channels",
2392
+ { stable: STABLE_PLUGIN_ID, next: NEXT_PLUGIN_ID },
2393
+ "doctor.settings.fix_plugin_channels"
2394
+ )
2395
+ ] : [];
2361
2396
  var checkClaudeSettings = (env) => {
2362
2397
  const all = readSettings(env);
2363
2398
  const plugins = enabledPlugins(all);
@@ -2367,6 +2402,7 @@ var checkClaudeSettings = (env) => {
2367
2402
  all,
2368
2403
  plugins.some((id) => id.startsWith("finitemonkey@"))
2369
2404
  ),
2405
+ ...pluginChannels(all),
2370
2406
  ...agentTeams(env, all),
2371
2407
  ...loopPlugins(plugins),
2372
2408
  ...cleanupPeriod(env, all)
@@ -2374,7 +2410,7 @@ var checkClaudeSettings = (env) => {
2374
2410
  };
2375
2411
 
2376
2412
  // src/doctor/environment.ts
2377
- import { join as join7 } from "node:path";
2413
+ import { join as join8 } from "node:path";
2378
2414
  var AUTH_VARIABLES = [
2379
2415
  "ANTHROPIC_API_KEY",
2380
2416
  "ANTHROPIC_AUTH_TOKEN",
@@ -2412,7 +2448,7 @@ var pathDirs = ({ env, platform }) => {
2412
2448
  var copiesOnPath = (env) => {
2413
2449
  const names = EXECUTABLES[env.platform] ?? ["finitemonkey"];
2414
2450
  const found = pathDirs(env).flatMap(
2415
- (dir) => names.map((name) => toAbsolutePath(join7(dir, name))).filter((path) => env.runtime.stat(path)?.kind === "file").map((path) => env.runtime.realpath(path))
2451
+ (dir) => names.map((name) => toAbsolutePath(join8(dir, name))).filter((path) => env.runtime.stat(path)?.kind === "file").map((path) => env.runtime.realpath(path))
2416
2452
  );
2417
2453
  const byDir = new Map(found.map((path) => [path.replace(/\.(cmd|exe|ps1)$/i, ""), path]));
2418
2454
  return [...byDir.values()];
@@ -2446,7 +2482,7 @@ var checkAuthVariables = ({ env, platform }) => {
2446
2482
  };
2447
2483
 
2448
2484
  // src/doctor/hooks.ts
2449
- import { join as join28 } from "node:path";
2485
+ import { join as join29 } from "node:path";
2450
2486
 
2451
2487
  // src/hooks/events.ts
2452
2488
  var HOOK_EVENTS = [
@@ -3509,7 +3545,7 @@ var selfGuard = (input) => {
3509
3545
  };
3510
3546
 
3511
3547
  // src/guards/tests/hook.ts
3512
- import { join as join8, relative as relative3 } from "node:path";
3548
+ import { join as join9, relative as relative3 } from "node:path";
3513
3549
 
3514
3550
  // src/guards/tests/patterns.ts
3515
3551
  var JS_TEST = /(^|\/)(__tests__|__test__)\/|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)tests?\/.*\.[cm]?[jt]sx?$/;
@@ -3699,7 +3735,7 @@ var shellViolations = (input, root) => {
3699
3735
  return parseShell(command).flatMap(
3700
3736
  (simple) => writtenPaths(simple).flatMap((path) => {
3701
3737
  const local = localPath(root, path);
3702
- const exists = input.runtime.stat(toAbsolutePath(join8(root, local))) !== void 0;
3738
+ const exists = input.runtime.stat(toAbsolutePath(join9(root, local))) !== void 0;
3703
3739
  if (!exists) return [];
3704
3740
  if (isProtectedConfig(local)) return [{ kind: "config_changed", path: local }];
3705
3741
  return isTestFile(local) ? [{ kind: "test_removed", path: local }] : [];
@@ -3719,17 +3755,17 @@ var testsGuard = (input) => {
3719
3755
  };
3720
3756
 
3721
3757
  // src/gate/hooks/config.ts
3722
- import { join as join10 } from "node:path";
3758
+ import { join as join11 } from "node:path";
3723
3759
 
3724
3760
  // src/gate/hooks/common.ts
3725
- import { dirname as dirname5, join as join9 } from "node:path";
3761
+ import { dirname as dirname5, join as join10 } from "node:path";
3726
3762
  var DEFAULT_GATE_BUDGET_MS = 3e5;
3727
3763
  var sessionCwd = (input) => input.payload.cwd === void 0 ? input.cwd : toAbsolutePath(input.payload.cwd, input.cwd);
3728
3764
  var MAX_DEPTH2 = 64;
3729
3765
  var findRepoRoot = (runtime, start) => {
3730
3766
  let current = start;
3731
3767
  for (let depth = 0; depth < MAX_DEPTH2; depth += 1) {
3732
- if (runtime.stat(toAbsolutePath(join9(current, ".git"))) !== void 0) return current;
3768
+ if (runtime.stat(toAbsolutePath(join10(current, ".git"))) !== void 0) return current;
3733
3769
  const parent = toAbsolutePath(dirname5(current));
3734
3770
  if (parent === current) return void 0;
3735
3771
  current = parent;
@@ -3766,9 +3802,9 @@ var censusOf = (text6) => {
3766
3802
  return { hooks: countIn(parsed.value["hooks"]), disabled: parsed.value["disableAllHooks"] === true };
3767
3803
  };
3768
3804
  var settingsFiles = (paths, env) => [
3769
- toAbsolutePath(join10(paths.root, ".claude", "settings.json")),
3770
- toAbsolutePath(join10(paths.root, ".claude", "settings.local.json")),
3771
- ...env["HOME"] === void 0 ? [] : [toAbsolutePath(join10(env["HOME"], ".claude", "settings.json"))]
3805
+ toAbsolutePath(join11(paths.root, ".claude", "settings.json")),
3806
+ toAbsolutePath(join11(paths.root, ".claude", "settings.local.json")),
3807
+ ...env["HOME"] === void 0 ? [] : [toAbsolutePath(join11(env["HOME"], ".claude", "settings.json"))]
3772
3808
  ];
3773
3809
  var read2 = (runtime, path) => {
3774
3810
  const text6 = runtime.readText(path);
@@ -3912,7 +3948,7 @@ var implicitContract = (files) => {
3912
3948
  };
3913
3949
 
3914
3950
  // src/gate/prepare.ts
3915
- import { join as join11 } from "node:path";
3951
+ import { join as join12 } from "node:path";
3916
3952
 
3917
3953
  // src/core/yaml/scalar.ts
3918
3954
  var ESCAPES = {
@@ -4585,10 +4621,10 @@ var conflictFiles = (git, base) => {
4585
4621
  // src/gate/prepare.ts
4586
4622
  var diskFiles = (runtime, root) => ({
4587
4623
  read: (relative9) => {
4588
- const text6 = runtime.readText(toAbsolutePath(join11(root, relative9)));
4624
+ const text6 = runtime.readText(toAbsolutePath(join12(root, relative9)));
4589
4625
  return text6.ok ? text6.value : void 0;
4590
4626
  },
4591
- exists: (relative9) => runtime.stat(toAbsolutePath(join11(root, relative9))) !== void 0
4627
+ exists: (relative9) => runtime.stat(toAbsolutePath(join12(root, relative9))) !== void 0
4592
4628
  });
4593
4629
  var baseFiles = (git, base, disk) => ({
4594
4630
  read: (relative9) => base === EMPTY_TREE ? disk.read(relative9) : fileAt(git, base, relative9),
@@ -5006,6 +5042,8 @@ var en_default = {
5006
5042
  "doctor.settings.fix_managed_hooks_only": "Ask the administrator of the managed settings to add the Finite Monkey hooks there, or to turn allowManagedHooksOnly off",
5007
5043
  "doctor.settings.plugin_only": "strictPluginOnlyCustomization is on ({scopes}) and the finitemonkey plugin is not enabled; the project hooks do not load",
5008
5044
  "doctor.settings.fix_plugin_only": "Install the plugin: finitemonkey init --marketplace --yes, then /plugin install finitemonkey@finitemonkey in Claude Code",
5045
+ "doctor.settings.plugin_channels": "Both {stable} and {next} are enabled. They share the plugin name, so Claude Code keeps only one set of hooks",
5046
+ "doctor.settings.fix_plugin_channels": "To stay on stable run claude plugin disable {next}; to test next run claude plugin disable {stable}",
5009
5047
  "doctor.settings.agent_teams": "{name} is on; teammates run in their own sessions, and Finite Monkey does not check their work before they report done",
5010
5048
  "doctor.settings.fix_agent_teams": "Run `finitemonkey gate` before accepting a teammate's result, or unset the variable",
5011
5049
  "doctor.settings.loop_plugins": "{names} enabled: it sends the agent back to work at Stop, and its loop has no guard of its own",
@@ -5517,6 +5555,8 @@ var es_default = {
5517
5555
  "doctor.settings.fix_managed_hooks_only": "Pide al administrador de los settings gestionados que añada allí los hooks de Finite Monkey, o que desactive allowManagedHooksOnly",
5518
5556
  "doctor.settings.plugin_only": "strictPluginOnlyCustomization está activado ({scopes}) y el plugin finitemonkey no está activo; los hooks del proyecto no se cargan",
5519
5557
  "doctor.settings.fix_plugin_only": "Instala el plugin: finitemonkey init --marketplace --yes, después /plugin install finitemonkey@finitemonkey en Claude Code",
5558
+ "doctor.settings.plugin_channels": "{stable} y {next} están activos a la vez. Comparten el nombre del plugin, así que Claude Code se queda con un solo conjunto de hooks",
5559
+ "doctor.settings.fix_plugin_channels": "Para quedarte en la versión estable ejecuta claude plugin disable {next}; para probar next ejecuta claude plugin disable {stable}",
5520
5560
  "doctor.settings.agent_teams": "{name} está activado; los compañeros de equipo corren en sesiones propias, y Finite Monkey no revisa su trabajo antes de que digan que terminaron",
5521
5561
  "doctor.settings.fix_agent_teams": "Ejecuta `finitemonkey gate` antes de aceptar el resultado de un compañero, o quita la variable",
5522
5562
  "doctor.settings.loop_plugins": "{names} activo: devuelve al agente al trabajo en Stop, y su bucle no tiene guardia propia",
@@ -6028,6 +6068,8 @@ var pt_BR_default = {
6028
6068
  "doctor.settings.fix_managed_hooks_only": "Peça ao administrador dos settings gerenciados para incluir os hooks do Finite Monkey lá, ou para desligar allowManagedHooksOnly",
6029
6069
  "doctor.settings.plugin_only": "strictPluginOnlyCustomization está ligado ({scopes}) e o plugin finitemonkey não está ativo; os hooks do projeto não carregam",
6030
6070
  "doctor.settings.fix_plugin_only": "Instale o plugin: finitemonkey init --marketplace --yes, depois /plugin install finitemonkey@finitemonkey no Claude Code",
6071
+ "doctor.settings.plugin_channels": "{stable} e {next} estão ativos ao mesmo tempo. Os dois têm o mesmo nome de plugin, então o Claude Code fica com só um conjunto de hooks",
6072
+ "doctor.settings.fix_plugin_channels": "Para ficar no estável rode claude plugin disable {next}; para testar o next rode claude plugin disable {stable}",
6031
6073
  "doctor.settings.agent_teams": "{name} está ligado; os colegas de equipe rodam em sessões próprias, e o Finite Monkey não confere o trabalho deles antes de dizerem que terminaram",
6032
6074
  "doctor.settings.fix_agent_teams": "Rode `finitemonkey gate` antes de aceitar o resultado de um colega, ou remova a variável",
6033
6075
  "doctor.settings.loop_plugins": "{names} ativo: ele manda o agente de volta ao trabalho no Stop, e o laço dele não tem guarda própria",
@@ -6540,6 +6582,8 @@ var review_default = {
6540
6582
  "doctor.settings.fix_managed_hooks_only": "machine",
6541
6583
  "doctor.settings.plugin_only": "machine",
6542
6584
  "doctor.settings.fix_plugin_only": "machine",
6585
+ "doctor.settings.plugin_channels": "machine",
6586
+ "doctor.settings.fix_plugin_channels": "machine",
6543
6587
  "doctor.settings.agent_teams": "machine",
6544
6588
  "doctor.settings.fix_agent_teams": "machine",
6545
6589
  "doctor.settings.loop_plugins": "machine",
@@ -7049,6 +7093,8 @@ var review_default = {
7049
7093
  "doctor.settings.fix_managed_hooks_only": "machine",
7050
7094
  "doctor.settings.plugin_only": "machine",
7051
7095
  "doctor.settings.fix_plugin_only": "machine",
7096
+ "doctor.settings.plugin_channels": "machine",
7097
+ "doctor.settings.fix_plugin_channels": "machine",
7052
7098
  "doctor.settings.agent_teams": "machine",
7053
7099
  "doctor.settings.fix_agent_teams": "machine",
7054
7100
  "doctor.settings.loop_plugins": "machine",
@@ -7861,7 +7907,7 @@ var runShellCheck = (check2, run4, ctx) => {
7861
7907
  var commandCheck = (check2, ctx) => check2.run === void 0 ? unverified("not_implemented") : runShellCheck(check2, check2.run, ctx);
7862
7908
 
7863
7909
  // src/gate/checks/diff.ts
7864
- import { join as join12 } from "node:path";
7910
+ import { join as join13 } from "node:path";
7865
7911
 
7866
7912
  // src/core/glob.ts
7867
7913
  var escape = (char) => /[.+^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
@@ -7888,7 +7934,7 @@ var matchesAny = (path, globs) => {
7888
7934
  // src/gate/checks/diff.ts
7889
7935
  var MAX_LISTED = 10;
7890
7936
  var untrackedLines = (ctx) => ctx.changes.filter((change) => change.status === "A").reduce((sum, change) => {
7891
- const text6 = ctx.runtime.readText(toAbsolutePath(join12(ctx.paths.root, change.path)));
7937
+ const text6 = ctx.runtime.readText(toAbsolutePath(join13(ctx.paths.root, change.path)));
7892
7938
  return sum + (text6.ok ? text6.value.split("\n").length : 0);
7893
7939
  }, 0);
7894
7940
  var diffCheck = (_check, ctx) => {
@@ -7947,13 +7993,13 @@ var manualCheck = (check2, ctx) => {
7947
7993
  };
7948
7994
 
7949
7995
  // src/gate/checks/placeholders.ts
7950
- import { join as join13 } from "node:path";
7996
+ import { join as join14 } from "node:path";
7951
7997
  var MAX_LISTED2 = 10;
7952
7998
  var SKIP_FILES = /\.(md|mdx|txt|rst|lock|snap|svg|png|jpg|gif|json)$|(^|\/)(CHANGELOG|LICENSE)/i;
7953
7999
  var PLACEHOLDER = /\b(TODO|FIXME|XXX)\b|not implemented|NotImplementedError|unimplemented!|todo!\(|lorem ipsum/i;
7954
8000
  var findPlaceholders = (lines3) => lines3.filter((line2) => !isTestFile(line2.path) && !SKIP_FILES.test(line2.path) && PLACEHOLDER.test(line2.text));
7955
8001
  var untrackedAdded = (ctx) => ctx.changes.filter((change) => change.status === "A" && !SKIP_FILES.test(change.path)).flatMap((change) => {
7956
- const text6 = ctx.runtime.readText(toAbsolutePath(join13(ctx.paths.root, change.path)));
8002
+ const text6 = ctx.runtime.readText(toAbsolutePath(join14(ctx.paths.root, change.path)));
7957
8003
  return text6.ok ? text6.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
7958
8004
  });
7959
8005
  var placeholdersCheck = (_check, ctx) => {
@@ -8652,13 +8698,13 @@ var proseOutcome = (status) => {
8652
8698
  var proseCheck = (_check, ctx) => proseOutcome(proseGateStatus(ctx.runtime, ctx.paths.root));
8653
8699
 
8654
8700
  // src/secrets/baseline.ts
8655
- import { join as join14 } from "node:path";
8701
+ import { join as join15 } from "node:path";
8656
8702
  var BASELINE_FILE2 = ".finitemonkey/findings/baseline.json";
8657
8703
  var BASELINE_SCHEMA = "finitemonkey/secrets-baseline@1";
8658
8704
  var BASELINE_STATES = ["aceito", "falso-positivo"];
8659
8705
  var EXCEPTION_MAX_DAYS = 90;
8660
8706
  var EMPTY_BASELINE = { schema: BASELINE_SCHEMA, entries: [] };
8661
- var baselinePath = (root) => toAbsolutePath(join14(root, BASELINE_FILE2));
8707
+ var baselinePath = (root) => toAbsolutePath(join15(root, BASELINE_FILE2));
8662
8708
  var text5 = (record2, key) => {
8663
8709
  const value = record2[key];
8664
8710
  return typeof value === "string" && value !== "" ? value : void 0;
@@ -8718,14 +8764,14 @@ var withoutFixed = (baseline, fixed) => {
8718
8764
  var acceptedFingerprints = (baseline, now) => new Set(baseline.entries.filter((entry2) => entry2.expiresAt >= now).map((entry2) => entry2.fingerprint));
8719
8765
 
8720
8766
  // src/secrets/incidents.ts
8721
- import { join as join15 } from "node:path";
8767
+ import { join as join16 } from "node:path";
8722
8768
  var INCIDENTS_DIR = ".finitemonkey/incidents";
8723
8769
  var INCIDENT_STATES = ["aberto", "exposicao-local", "rotacionado", "fechado"];
8724
8770
  var INCIDENT_SOURCES = ["tool-output", "prompt", "history", "scan"];
8725
8771
  var ID = /^inc-[0-9a-f]{12}$/;
8726
8772
  var incidentId = (rule, fingerprint) => `inc-${sha256Hex(`${rule}\0${fingerprint}`).slice(0, 12)}`;
8727
- var incidentsDir = (root) => toAbsolutePath(join15(root, INCIDENTS_DIR));
8728
- var incidentFile = (root, id) => toAbsolutePath(join15(incidentsDir(root), `${id}.json`));
8773
+ var incidentsDir = (root) => toAbsolutePath(join16(root, INCIDENTS_DIR));
8774
+ var incidentFile = (root, id) => toAbsolutePath(join16(incidentsDir(root), `${id}.json`));
8729
8775
  var pick2 = (options, value) => options.find((option) => option === value);
8730
8776
  var str2 = (record2, key) => {
8731
8777
  const value = record2[key];
@@ -8780,7 +8826,7 @@ var listIncidents = (runtime, root) => {
8780
8826
  return names.value.filter((name) => name.endsWith(".json")).map((name) => readIncident(runtime, root, name.slice(0, -".json".length))).flatMap((parsed) => parsed.ok ? [parsed.value] : []).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
8781
8827
  };
8782
8828
  var saveIncident = (runtime, root, incident) => {
8783
- const ignore = runtime.writeText(toAbsolutePath(join15(incidentsDir(root), ".gitignore")), "*\n");
8829
+ const ignore = runtime.writeText(toAbsolutePath(join16(incidentsDir(root), ".gitignore")), "*\n");
8784
8830
  if (!ignore.ok) return ignore;
8785
8831
  return runtime.writeText(incidentFile(root, incident.id), `${JSON.stringify(incident, null, 2)}
8786
8832
  `);
@@ -8827,7 +8873,7 @@ var moveIncident = (incident, to, now, by) => ({
8827
8873
  var isRotated = (incident) => incident.state === "rotacionado" || incident.state === "fechado";
8828
8874
 
8829
8875
  // src/secrets/scan.ts
8830
- import { join as join16, relative as relative4 } from "node:path";
8876
+ import { join as join17, relative as relative4 } from "node:path";
8831
8877
 
8832
8878
  // src/secrets/located.ts
8833
8879
  var findingId = (ruleId, file, fingerprint) => `fm-${sha256Hex(`${ruleId}\0${file}\0${fingerprint}`).slice(0, 16)}`;
@@ -8927,7 +8973,7 @@ var listed2 = (runtime, root, target) => {
8927
8973
  const base = toAbsolutePath(target, root);
8928
8974
  const walked = runtime.walkFiles(base);
8929
8975
  if (!walked.ok) return walked;
8930
- return accept(walked.value.map((name) => toPosixPath(relative4(root, join16(base, toPosixPath(name))))));
8976
+ return accept(walked.value.map((name) => toPosixPath(relative4(root, join17(base, toPosixPath(name))))));
8931
8977
  };
8932
8978
  var scanFile = (runtime, root, file) => {
8933
8979
  const text6 = runtime.readText(toAbsolutePath(file, root));
@@ -8995,7 +9041,7 @@ var secretsCheck = (_check, ctx) => {
8995
9041
  };
8996
9042
 
8997
9043
  // src/gate/checks/tests.ts
8998
- import { join as join17 } from "node:path";
9044
+ import { join as join18 } from "node:path";
8999
9045
 
9000
9046
  // src/guards/tests/tree.ts
9001
9047
  var outOfScope = (input, change) => input.testsProtected && input.scopePaths.length > 0 && change.status !== "A" && isTestFile(change.path) && !matchesAny(change.path, input.scopePaths) ? [{ kind: "out_of_scope", path: change.path }] : [];
@@ -9027,7 +9073,7 @@ var treeViolations = (ctx) => {
9027
9073
  changes: relevant,
9028
9074
  before: (path) => fileAt(ctx.git, ctx.base, path),
9029
9075
  after: (path) => {
9030
- const text6 = ctx.runtime.readText(toAbsolutePath(join17(ctx.paths.root, path)));
9076
+ const text6 = ctx.runtime.readText(toAbsolutePath(join18(ctx.paths.root, path)));
9031
9077
  return text6.ok ? text6.value : void 0;
9032
9078
  },
9033
9079
  acceptedConfigs: ctx.trust.configs,
@@ -9048,7 +9094,7 @@ var testsCheck = (check2, ctx) => {
9048
9094
  };
9049
9095
 
9050
9096
  // src/git/tree.ts
9051
- import { join as join18 } from "node:path";
9097
+ import { join as join19 } from "node:path";
9052
9098
 
9053
9099
  // src/secrets/guards/paths.ts
9054
9100
  var SAFE_ENV = /^\.env\.(?:example|sample|template|dist|defaults|schema)$/i;
@@ -9087,7 +9133,7 @@ var dropSensitive = (runtime, repo, env, options) => {
9087
9133
  return removed.ok ? accept(void 0) : removed;
9088
9134
  };
9089
9135
  var withTempIndex = (runtime, repo, options, work) => {
9090
- const path = toAbsolutePath(join18(repo.gitDir, `finitemonkey-${options.stamp}.index`));
9136
+ const path = toAbsolutePath(join19(repo.gitDir, `finitemonkey-${options.stamp}.index`));
9091
9137
  const env = { GIT_INDEX_FILE: path, GIT_OPTIONAL_LOCKS: "0" };
9092
9138
  try {
9093
9139
  return work(env);
@@ -9339,7 +9385,7 @@ var parsePorcelainV2 = (stdout) => {
9339
9385
  };
9340
9386
 
9341
9387
  // src/git/project-config.ts
9342
- import { join as join19 } from "node:path";
9388
+ import { join as join20 } from "node:path";
9343
9389
  var CONFIG_FILE3 = ".finitemonkey/config.yml";
9344
9390
  var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
9345
9391
  var stripComment2 = (line2) => {
@@ -9420,12 +9466,12 @@ var configMap = (tree, ...path) => {
9420
9466
  );
9421
9467
  };
9422
9468
  var loadProjectConfig = (runtime, root) => {
9423
- const text6 = runtime.readText(toAbsolutePath(join19(root, CONFIG_FILE3)));
9469
+ const text6 = runtime.readText(toAbsolutePath(join20(root, CONFIG_FILE3)));
9424
9470
  return text6.ok ? parseConfigYaml(text6.value) : {};
9425
9471
  };
9426
9472
 
9427
9473
  // src/git/merge/dropped.ts
9428
- import { join as join20 } from "node:path";
9474
+ import { join as join21 } from "node:path";
9429
9475
 
9430
9476
  // src/git/merge/lines.ts
9431
9477
  var normalizeLine = (line2) => line2.replace(/\r$/, "").trim().replace(/\s*,$/, "");
@@ -9505,7 +9551,7 @@ var parseDropped = (text6) => {
9505
9551
  return accept({ items, invalid });
9506
9552
  };
9507
9553
  var loadDropped = (runtime, root, file) => {
9508
- const path = toAbsolutePath(file ?? join20(root, DROPPED_FILE), root);
9554
+ const path = toAbsolutePath(file ?? join21(root, DROPPED_FILE), root);
9509
9555
  const text6 = runtime.readText(path);
9510
9556
  if (!text6.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text6.error}`);
9511
9557
  return parseDropped(text6.value);
@@ -9515,7 +9561,7 @@ var isDropped = (finding3, items) => items.some(
9515
9561
  );
9516
9562
 
9517
9563
  // src/git/merge/generated.ts
9518
- import { join as join21 } from "node:path";
9564
+ import { join as join22 } from "node:path";
9519
9565
  var LOCKFILES3 = /* @__PURE__ */ new Set([
9520
9566
  "package-lock.json",
9521
9567
  "npm-shrinkwrap.json",
@@ -9578,7 +9624,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
9578
9624
  if (declared.length === 0) return accept([]);
9579
9625
  const commit2 = resultCommit(runtime, repo, inputs);
9580
9626
  if (!commit2.ok) return commit2;
9581
- const dir = join21(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
9627
+ const dir = join22(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
9582
9628
  if (!runtime.git(["worktree", "add", "--detach", dir, commit2.value], repo.root).ok)
9583
9629
  return reject("could not check out the merge result");
9584
9630
  try {
@@ -9588,7 +9634,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
9588
9634
  }
9589
9635
  return accept(
9590
9636
  declared.flatMap(({ path }) => {
9591
- const fresh3 = runtime.readText(toAbsolutePath(join21(dir, path)));
9637
+ const fresh3 = runtime.readText(toAbsolutePath(join22(dir, path)));
9592
9638
  const merged = blobText(runtime, repo, commit2.value, path);
9593
9639
  if (fresh3.ok && fresh3.value === merged) return [];
9594
9640
  return [{ kind: "generated-differs", file: path, side: void 0, line: void 0, text: path, weak: false }];
@@ -9874,19 +9920,19 @@ var verifyMerge = (runtime, repo, options = {}) => {
9874
9920
  };
9875
9921
 
9876
9922
  // src/git/ownership.ts
9877
- import { join as join22 } from "node:path";
9923
+ import { join as join23 } from "node:path";
9878
9924
  var OWNERSHIP_DIR = ".finitemonkey/git";
9879
9925
  var ownerKey = (raw) => {
9880
9926
  const cleaned = (raw ?? "").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80);
9881
9927
  return cleaned === "" ? "unknown" : cleaned;
9882
9928
  };
9883
- var fileOf = (repo, kind, owner) => toAbsolutePath(join22(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
9929
+ var fileOf = (repo, kind, owner) => toAbsolutePath(join23(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
9884
9930
  var readDirty = (runtime, root) => {
9885
9931
  const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
9886
9932
  return status.ok ? parsePorcelainV2(status.stdout) : void 0;
9887
9933
  };
9888
9934
  var hashDirty = (runtime, repo, entries) => {
9889
- const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join22(repo.root, entry2.path)))?.kind === "file");
9935
+ const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join23(repo.root, entry2.path)))?.kind === "file");
9890
9936
  const hashed = present2.length === 0 ? void 0 : runtime.gitWith(["hash-object", "--stdin-paths"], repo.root, {
9891
9937
  input: `${present2.map((entry2) => entry2.path).join("\n")}
9892
9938
  `
@@ -10321,7 +10367,7 @@ var isOwnWorktree = (input) => {
10321
10367
  var subagentStopGate = (input) => isOwnWorktree(input) ? stopGate(input) : { kind: "allow" };
10322
10368
 
10323
10369
  // src/gate/hooks/subagent.ts
10324
- import { join as join23 } from "node:path";
10370
+ import { join as join24 } from "node:path";
10325
10371
  var NO_CONTRACT_AGENTS = /(^|:)(reviewer|verifier|explore|plan|claude-code-guide|statusline-setup)$/i;
10326
10372
  var KNOWN_KINDS = /* @__PURE__ */ new Set([
10327
10373
  "command",
@@ -10355,12 +10401,12 @@ var subagentStart = (input) => {
10355
10401
  const active = readState(runtime, paths).active;
10356
10402
  if (active === void 0) return { kind: "allow" };
10357
10403
  const disk = (relative9) => {
10358
- const text6 = runtime.readText(toAbsolutePath(join23(paths.root, relative9)));
10404
+ const text6 = runtime.readText(toAbsolutePath(join24(paths.root, relative9)));
10359
10405
  return text6.ok ? text6.value : void 0;
10360
10406
  };
10361
10407
  const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
10362
10408
  if (!loaded.ok) return { kind: "allow" };
10363
- const briefing = runtime.stat(toAbsolutePath(join23(paths.dir, "briefing.md"))) !== void 0;
10409
+ const briefing = runtime.stat(toAbsolutePath(join24(paths.dir, "briefing.md"))) !== void 0;
10364
10410
  return { kind: "allow", output: contextOutput("SubagentStart", contractFacts(t, loaded.value.contract, briefing)) };
10365
10411
  };
10366
10412
 
@@ -10377,7 +10423,7 @@ var PORTAO_HANDLERS = {
10377
10423
  };
10378
10424
 
10379
10425
  // src/prod/guard.ts
10380
- import { join as join25 } from "node:path";
10426
+ import { join as join26 } from "node:path";
10381
10427
 
10382
10428
  // src/git/command-text.ts
10383
10429
  var MAX_SHOWN = 160;
@@ -10719,7 +10765,7 @@ var remoteHostsOf = (command) => {
10719
10765
  };
10720
10766
 
10721
10767
  // src/prod/targets.ts
10722
- import { join as join24 } from "node:path";
10768
+ import { join as join25 } from "node:path";
10723
10769
  var DEPLOY_FILES = [
10724
10770
  "deploy.sh",
10725
10771
  "scripts/deploy.sh",
@@ -10743,12 +10789,12 @@ var hostsInText = (text6) => {
10743
10789
  return [...urls, ...keyed, ...commands].filter((host) => host !== "" && !host.includes("$"));
10744
10790
  };
10745
10791
  var workflowFiles = (runtime, root) => {
10746
- const listed4 = runtime.listDir(toAbsolutePath(join24(root, ".github", "workflows")));
10792
+ const listed4 = runtime.listDir(toAbsolutePath(join25(root, ".github", "workflows")));
10747
10793
  if (!listed4.ok) return [];
10748
10794
  return listed4.value.filter((name) => /deploy/i.test(name) && /\.ya?ml$/i.test(name)).map((name) => `.github/workflows/${name}`);
10749
10795
  };
10750
10796
  var detectedHosts = (runtime, root, files) => files.flatMap((file) => {
10751
- const text6 = runtime.readText(toAbsolutePath(join24(root, file)));
10797
+ const text6 = runtime.readText(toAbsolutePath(join25(root, file)));
10752
10798
  return text6.ok ? hostsInText(text6.value) : [];
10753
10799
  });
10754
10800
  var loadProdTargets = (runtime, root, config) => {
@@ -10760,7 +10806,7 @@ var loadProdTargets = (runtime, root, config) => {
10760
10806
  ...detectedHosts(runtime, root, deployFiles)
10761
10807
  ];
10762
10808
  const declaredDeploy = configAt(config, "prod", "deploy");
10763
- const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join24(root, file))) !== void 0);
10809
+ const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join25(root, file))) !== void 0);
10764
10810
  return {
10765
10811
  hosts: new Set(hosts.map(hostKey).filter((host) => host !== "")),
10766
10812
  kubeContexts: configList(config, "prod", "kube_contexts"),
@@ -10937,8 +10983,8 @@ var recordEvidence = (runtime, root, owner, reads) => {
10937
10983
  ] : []
10938
10984
  );
10939
10985
  if (lines3.length === 0) return;
10940
- const path = toAbsolutePath(join25(root, EVIDENCE_FILE));
10941
- runtime.mkdir(toAbsolutePath(join25(root, ".finitemonkey")));
10986
+ const path = toAbsolutePath(join26(root, EVIDENCE_FILE));
10987
+ runtime.mkdir(toAbsolutePath(join26(root, ".finitemonkey")));
10942
10988
  const before = runtime.readText(path);
10943
10989
  const text6 = before.ok ? before.value : "";
10944
10990
  runtime.writeAtomic(path, `${text6}${text6 === "" || text6.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
@@ -11750,10 +11796,10 @@ var listWorktrees = (runtime, cwd) => {
11750
11796
  };
11751
11797
 
11752
11798
  // src/git/journal.ts
11753
- import { join as join26 } from "node:path";
11799
+ import { join as join27 } from "node:path";
11754
11800
  var JOURNAL_FILE = ".finitemonkey/git-journal.jsonl";
11755
11801
  var JOURNAL_SCHEMA = "finitemonkey/git-journal@1";
11756
- var journalPath = (root) => toAbsolutePath(join26(root, JOURNAL_FILE));
11802
+ var journalPath = (root) => toAbsolutePath(join27(root, JOURNAL_FILE));
11757
11803
  var headsOf = (value) => isRecord(value) ? Object.fromEntries(
11758
11804
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
11759
11805
  ) : {};
@@ -11789,7 +11835,7 @@ var readJournal = (runtime, root) => {
11789
11835
  };
11790
11836
  var appendJournal = (runtime, root, entry2) => {
11791
11837
  const path = journalPath(root);
11792
- const made = runtime.mkdir(toAbsolutePath(join26(root, ".finitemonkey")));
11838
+ const made = runtime.mkdir(toAbsolutePath(join27(root, ".finitemonkey")));
11793
11839
  if (!made.ok) return made;
11794
11840
  const previous = runtime.readText(path);
11795
11841
  const before = previous.ok ? previous.value : "";
@@ -13012,7 +13058,7 @@ var hookSchemaProblems = (hooks, claude) => {
13012
13058
  };
13013
13059
 
13014
13060
  // src/doctor/hook-sources.ts
13015
- import { join as join27 } from "node:path";
13061
+ import { join as join28 } from "node:path";
13016
13062
  var entriesOf = (source, hooks, own) => Object.entries(asRecord(hooks)).flatMap(
13017
13063
  ([event, groups]) => asList(groups).flatMap((group) => {
13018
13064
  const record2 = asRecord(group);
@@ -13025,7 +13071,7 @@ var projectHookFiles = (env) => readSettings(env).flatMap(({ scope: scope2, path
13025
13071
  const source = PROJECT_SCOPES[scope2];
13026
13072
  return source === void 0 || !("hooks" in settings) ? [] : [{ source, path, hooks: settings["hooks"] }];
13027
13073
  });
13028
- var pluginsDir = ({ env, runtime }) => join27(env["CLAUDE_CONFIG_DIR"] ?? join27(runtime.homeDir(), ".claude"), "plugins");
13074
+ var pluginsDir = ({ env, runtime }) => join28(env["CLAUDE_CONFIG_DIR"] ?? join28(runtime.homeDir(), ".claude"), "plugins");
13029
13075
  var installRecord = (value, root) => {
13030
13076
  const records = Array.isArray(value) ? value.filter(isRecord) : isRecord(value) ? [value] : [];
13031
13077
  return records.find((record2) => record2["projectPath"] === root) ?? records[0];
@@ -13036,13 +13082,13 @@ var readJson = (env, path) => {
13036
13082
  return parsed?.ok === true ? parsed.value : void 0;
13037
13083
  };
13038
13084
  var installedPlugin = (env) => {
13039
- const index = readJson(env, toAbsolutePath(join27(pluginsDir(env), "installed_plugins.json")));
13085
+ const index = readJson(env, toAbsolutePath(join28(pluginsDir(env), "installed_plugins.json")));
13040
13086
  const record2 = installRecord(asRecord(index?.["plugins"])[PLUGIN_ID], env.root);
13041
13087
  const installPath = record2 === void 0 ? void 0 : optionalString(record2, "installPath");
13042
13088
  if (record2 === void 0 || installPath === void 0) return void 0;
13043
13089
  const root = toAbsolutePath(installPath);
13044
- const manifest = readJson(env, toAbsolutePath(join27(root, ".claude-plugin", "plugin.json")));
13045
- const hooksPath = toAbsolutePath(join27(root, "hooks", "hooks.json"));
13090
+ const manifest = readJson(env, toAbsolutePath(join28(root, ".claude-plugin", "plugin.json")));
13091
+ const hooksPath = toAbsolutePath(join28(root, "hooks", "hooks.json"));
13046
13092
  const text6 = env.runtime.readText(hooksPath);
13047
13093
  const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
13048
13094
  return {
@@ -13052,10 +13098,10 @@ var installedPlugin = (env) => {
13052
13098
  hooksText: text6.ok ? text6.value : void 0
13053
13099
  };
13054
13100
  };
13055
- var PRECEDENCE = ["managed", "local", "project", "user"];
13101
+ var PRECEDENCE2 = ["managed", "local", "project", "user"];
13056
13102
  var pluginEnabled = (env) => {
13057
13103
  const all = readSettings(env);
13058
- for (const scope2 of PRECEDENCE) {
13104
+ for (const scope2 of PRECEDENCE2) {
13059
13105
  const value = asRecord(all.find((item) => item.scope === scope2)?.settings["enabledPlugins"])[PLUGIN_ID];
13060
13106
  if (typeof value === "boolean") return { value, scope: scope2 };
13061
13107
  }
@@ -13087,7 +13133,7 @@ var schemaFindings = (env, plugin, claude) => {
13087
13133
  const files = [...projectHookFiles(env), ...plugin?.hooksFile === void 0 ? [] : [plugin.hooksFile]];
13088
13134
  const invalidJson = [
13089
13135
  ...unreadableProjectFiles(env),
13090
- ...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(join28(plugin.root, "hooks", "hooks.json"))] : []
13136
+ ...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(join29(plugin.root, "hooks", "hooks.json"))] : []
13091
13137
  ];
13092
13138
  return [
13093
13139
  ...invalidJson.flatMap((file) => schemaFinding(file, ["not valid JSON"], label2)),
@@ -13108,7 +13154,7 @@ var pluginFindings = (env, projectOwn) => {
13108
13154
  ];
13109
13155
  };
13110
13156
  var coverageFindings = (env, entries) => {
13111
- const installed = env.runtime.stat(toAbsolutePath(join28(env.root, ...LOCK_FILE.split("/")))) !== void 0;
13157
+ const installed = env.runtime.stat(toAbsolutePath(join29(env.root, ...LOCK_FILE.split("/")))) !== void 0;
13112
13158
  if (entries.length === 0 && !installed) {
13113
13159
  return [finding("hooks.registered", "warn", "doctor.hooks.none", {}, "doctor.hooks.fix_none")];
13114
13160
  }
@@ -13160,17 +13206,17 @@ var checkHooks = (env) => {
13160
13206
  };
13161
13207
 
13162
13208
  // src/doctor/instructions.ts
13163
- import { join as join29 } from "node:path";
13209
+ import { join as join30 } from "node:path";
13164
13210
  var INSTRUCTION_LINE_CEILING = 200;
13165
13211
  var TOP_INSTRUCTIONS = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"];
13166
13212
  var RULES_DIR = ".claude/rules";
13167
13213
  var SHOWN = 3;
13168
13214
  var readProjectFile = (env, relative9) => {
13169
- const text6 = env.runtime.readText(toAbsolutePath(join29(env.root, ...relative9.split("/"))));
13215
+ const text6 = env.runtime.readText(toAbsolutePath(join30(env.root, ...relative9.split("/"))));
13170
13216
  return text6.ok ? text6.value : void 0;
13171
13217
  };
13172
13218
  var instructionFiles = (env) => {
13173
- const listed4 = env.runtime.listDir(toAbsolutePath(join29(env.root, ...RULES_DIR.split("/"))));
13219
+ const listed4 = env.runtime.listDir(toAbsolutePath(join30(env.root, ...RULES_DIR.split("/"))));
13174
13220
  const rules = listed4.ok ? listed4.value.filter((name) => name.endsWith(".md")).map((name) => `${RULES_DIR}/${name}`) : [];
13175
13221
  return [...TOP_INSTRUCTIONS, ...rules].flatMap((path) => {
13176
13222
  const text6 = readProjectFile(env, path);
@@ -13275,12 +13321,12 @@ var checkInstructions = (env) => {
13275
13321
  };
13276
13322
 
13277
13323
  // src/doctor/line-endings.ts
13278
- import { join as join30 } from "node:path";
13324
+ import { join as join31 } from "node:path";
13279
13325
  var LF_RULE = /^\*\s.*\beol=lf\b/m;
13280
13326
  var checkLineEndings = ({ root, runtime }) => {
13281
13327
  const autocrlf = runtime.git(["config", "--get", "core.autocrlf"], root);
13282
13328
  if (!autocrlf.ok || autocrlf.stdout.trim().toLowerCase() !== "true") return [];
13283
- const attributes = runtime.readText(toAbsolutePath(join30(root, ".gitattributes")));
13329
+ const attributes = runtime.readText(toAbsolutePath(join31(root, ".gitattributes")));
13284
13330
  if (attributes.ok && LF_RULE.test(attributes.value)) return [];
13285
13331
  return [
13286
13332
  finding("git.autocrlf", "warn", "doctor.git.autocrlf", { file: ".gitattributes" }, "doctor.git.fix_autocrlf")
@@ -13288,7 +13334,7 @@ var checkLineEndings = ({ root, runtime }) => {
13288
13334
  };
13289
13335
 
13290
13336
  // src/doctor/memory.ts
13291
- import { join as join31 } from "node:path";
13337
+ import { join as join32 } from "node:path";
13292
13338
  var ceilingOf = (config) => {
13293
13339
  const value = configAt(parseConfigYaml(config ?? ""), "memory", "maxLines");
13294
13340
  const parsed = typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
@@ -13296,9 +13342,9 @@ var ceilingOf = (config) => {
13296
13342
  };
13297
13343
  var lineCount2 = (text6) => text6.replace(/\r?\n$/, "").split(/\r?\n/).length;
13298
13344
  var checkAgentsFile = ({ root, runtime }) => {
13299
- const text6 = runtime.readText(toAbsolutePath(join31(root, AGENTS_FILE)));
13345
+ const text6 = runtime.readText(toAbsolutePath(join32(root, AGENTS_FILE)));
13300
13346
  if (!text6.ok) return [];
13301
- const config = runtime.readText(toAbsolutePath(join31(root, CONFIG_FILE)));
13347
+ const config = runtime.readText(toAbsolutePath(join32(root, CONFIG_FILE)));
13302
13348
  const max = ceilingOf(config.ok ? config.value : void 0);
13303
13349
  const lines3 = lineCount2(text6.value);
13304
13350
  const params2 = { file: AGENTS_FILE, lines: lines3, max };
@@ -13306,7 +13352,7 @@ var checkAgentsFile = ({ root, runtime }) => {
13306
13352
  };
13307
13353
 
13308
13354
  // src/doctor/project-files.ts
13309
- import { join as join32 } from "node:path";
13355
+ import { join as join33 } from "node:path";
13310
13356
  var variants = (base, extensions) => extensions.map((extension) => `${base}${extension}`);
13311
13357
  var SCRIPT = [".js", ".cjs", ".mjs", ".ts", ".cts", ".mts"];
13312
13358
  var DATA = ["", ".json", ".yml", ".yaml"];
@@ -13337,7 +13383,7 @@ var CONFIG_GROUPS = [
13337
13383
  files: [...variants("commitlint.config", SCRIPT), ...variants(".commitlintrc", [...DATA, ".js"])]
13338
13384
  }
13339
13385
  ];
13340
- var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(join32(env.root, file)))?.kind === "file");
13386
+ var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(join33(env.root, file)))?.kind === "file");
13341
13387
  var crowded = (env, groups, build) => groups.flatMap(({ name, files }) => {
13342
13388
  const found = present(env, files);
13343
13389
  return found.length > 1 ? [build(name, found.join(", "))] : [];
@@ -13903,7 +13949,7 @@ var rescueCommand = {
13903
13949
  };
13904
13950
 
13905
13951
  // src/git/undo.ts
13906
- import { basename as basename3, dirname as dirname8, join as join33 } from "node:path";
13952
+ import { basename as basename3, dirname as dirname8, join as join34 } from "node:path";
13907
13953
  var INDEX_SUBJECT = "finitemonkey index:";
13908
13954
  var gitLine = (runtime, repo, args) => {
13909
13955
  const result = runtime.git(args, repo.root);
@@ -13946,7 +13992,7 @@ var findSnapshot = (views, wanted) => {
13946
13992
  };
13947
13993
  var stampOf2 = (iso) => iso.replace(/[:.]/g, "-");
13948
13994
  var worktreePath = (runtime, repo) => {
13949
- const base = join33(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
13995
+ const base = join34(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
13950
13996
  return toAbsolutePath(runtime.stat(toAbsolutePath(base)) === void 0 ? base : `${base}-2`);
13951
13997
  };
13952
13998
  var intoWorktree = (runtime, repo, view2) => {
@@ -14134,19 +14180,6 @@ var helpCommand = {
14134
14180
  )
14135
14181
  };
14136
14182
 
14137
- // src/install/plugin-yield.ts
14138
- import { join as join34 } from "node:path";
14139
- var PLUGIN_VIA = "plugin";
14140
- var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
14141
- `;
14142
- var projectOwnsHooks = (ctx) => {
14143
- const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
14144
- const text6 = ctx.runtime.readText(toAbsolutePath(join34(root, ...SETTINGS_FILE.split("/"))));
14145
- if (!text6.ok) return false;
14146
- const parsed = parseJsonObject(text6.value);
14147
- return parsed.ok && ownHandlersIn(parsed.value) > 0;
14148
- };
14149
-
14150
14183
  // src/commands/hook.ts
14151
14184
  var hookCommand = {
14152
14185
  path: ["hook"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "finitemonkey",
3
- "version": "0.1.0-next.13",
3
+ "version": "0.1.0-next.14",
4
4
  "description": "Claude Code hooks that stop a coding agent from saying done before the tests pass: a machine-checked definition of done, plus guardrails for secrets, git safety and production. Pre-release.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",