finitemonkey 0.1.0-next.12 → 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.
package/README.es.md CHANGED
@@ -133,6 +133,7 @@ Generado desde `src/core/footprint.ts`, la lista que los tests exigen al código
133
133
  | Lee | `stdin` | El evento que Claude Code pasa a cada hook: la llamada a la herramienta, el prompt o la petición de parada. |
134
134
  | Lee | `.claude/settings.json`, `.claude/settings.local.json`, `~/.claude/settings.json` | La configuración de Claude Code, para comprobar que los hooks están instalados y que nada los desactiva. `$CLAUDE_CONFIG_DIR` reemplaza `~/.claude` si está definido. |
135
135
  | Lee | `CLAUDE.md`, `.claude/CLAUDE.md`, `CLAUDE.local.md`, `AGENTS.md`, `.claude/rules/`, `.claudeignore` | Archivos de instrucciones, que `doctor` lee para señalar reglas que solo existen como texto. |
136
+ | Lee | `~/.claude/projects/` | Los logs locales de sesión de Claude Code de este proyecto, leídos una vez y solo con `init --look-back`. |
136
137
  | Lee | `.git/` | El repositorio a través del comando `git`: estado, diffs, refs y el reflog. |
137
138
  | Lee | `git diff` | Archivos modificados, revisados en busca de secretos, tests saltados, placeholders y patrones de prosa. |
138
139
  | Lee | `.finitemonkey/config.yml`, `.finitemonkey/briefing.md` | La configuración del proyecto y el briefing opcional para subagentes. |
package/README.md CHANGED
@@ -133,6 +133,7 @@ Generated from `src/core/footprint.ts`, the list the tests hold the code to. No
133
133
  | Reads | `stdin` | The event Claude Code passes to each hook: the tool call, the prompt or the stop request. |
134
134
  | Reads | `.claude/settings.json`, `.claude/settings.local.json`, `~/.claude/settings.json` | Claude Code settings, to check that the hooks are installed and nothing switches them off. `$CLAUDE_CONFIG_DIR` replaces `~/.claude` when set. |
135
135
  | Reads | `CLAUDE.md`, `.claude/CLAUDE.md`, `CLAUDE.local.md`, `AGENTS.md`, `.claude/rules/`, `.claudeignore` | Instruction files, read by `doctor` to report rules that exist only as text. |
136
+ | Reads | `~/.claude/projects/` | This project's local Claude Code session logs, read once and only with `init --look-back`. |
136
137
  | Reads | `.git/` | The repository through the `git` command: status, diffs, refs and the reflog. |
137
138
  | Reads | `git diff` | Changed files, scanned for secrets, test skips, placeholders and prose patterns. |
138
139
  | Reads | `.finitemonkey/config.yml`, `.finitemonkey/briefing.md` | Project configuration and the optional briefing for subagents. |
package/README.pt-BR.md CHANGED
@@ -133,6 +133,7 @@ Gerado a partir de `src/core/footprint.ts`, a lista que os testes cobram do cód
133
133
  | Lê | `stdin` | O evento que o Claude Code passa a cada hook: a chamada de ferramenta, o prompt ou o pedido de parada. |
134
134
  | Lê | `.claude/settings.json`, `.claude/settings.local.json`, `~/.claude/settings.json` | As configurações do Claude Code, para conferir que os hooks estão instalados e que nada os desliga. `$CLAUDE_CONFIG_DIR` substitui `~/.claude` quando definido. |
135
135
  | Lê | `CLAUDE.md`, `.claude/CLAUDE.md`, `CLAUDE.local.md`, `AGENTS.md`, `.claude/rules/`, `.claudeignore` | Arquivos de instrução, lidos pelo `doctor` para apontar regras que só existem como texto. |
136
+ | Lê | `~/.claude/projects/` | Os logs locais de sessão do Claude Code deste projeto, lidos uma vez e só com `init --look-back`. |
136
137
  | Lê | `.git/` | O repositório pelo comando `git`: status, diffs, refs e o reflog. |
137
138
  | Lê | `git diff` | Arquivos alterados, varridos atrás de segredos, testes pulados, placeholders e padrões de prosa. |
138
139
  | Lê | `.finitemonkey/config.yml`, `.finitemonkey/briefing.md` | A configuração do projeto e o briefing opcional para subagentes. |
@@ -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",
@@ -5413,7 +5451,14 @@ var en_default = {
5413
5451
  "doctor.memory.too_long": "{file}: {lines, plural, one {# line} other {# lines}}, above the ceiling of {max}; long instruction files get followed less",
5414
5452
  "doctor.memory.fix_too_long": "Move details out of {file} into docs or skills until it has at most {max} lines, or raise memory.maxLines in .finitemonkey/config.yml",
5415
5453
  "doctor.git.autocrlf": 'git converts line endings to CRLF on checkout (core.autocrlf=true) and {file} does not pin LF; shell hooks and scripts checked out with CRLF fail with "bad interpreter"',
5416
- "doctor.git.fix_autocrlf": "add `* text=auto eol=lf` to {file}, commit it, then run `git add --renormalize .`"
5454
+ "doctor.git.fix_autocrlf": "add `* text=auto eol=lf` to {file}, commit it, then run `git add --renormalize .`",
5455
+ "history.title": "Looking back at this project's local Claude Code sessions: {sessions, plural, one {# session} other {# sessions}}, {turns, plural, one {# turn} other {# turns}}.",
5456
+ "history.pattern.done_after_failing_tests": '{count, plural, one {# turn} other {# turns}} ended with "done" right after a failing test run',
5457
+ "history.pattern.env_read": "{count, plural, one {# read} other {# reads}} of a .env file",
5458
+ "history.pattern.no_verify": "{count, plural, one {# git command} other {# git commands}} with --no-verify",
5459
+ "history.pattern.force_push": "{count, plural, one {# force push} other {# force pushes}}",
5460
+ "history.pattern.test_skip": "{count, plural, one {# test edit} other {# test edits}} that added .skip, .only or a skip marker",
5461
+ "history.estimate": "These are estimates from pattern matching, not evidence; nothing was saved and nothing left this machine."
5417
5462
  };
5418
5463
 
5419
5464
  // src/i18n/es.json
@@ -5510,6 +5555,8 @@ var es_default = {
5510
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",
5511
5556
  "doctor.settings.plugin_only": "strictPluginOnlyCustomization está activado ({scopes}) y el plugin finitemonkey no está activo; los hooks del proyecto no se cargan",
5512
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}",
5513
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",
5514
5561
  "doctor.settings.fix_agent_teams": "Ejecuta `finitemonkey gate` antes de aceptar el resultado de un compañero, o quita la variable",
5515
5562
  "doctor.settings.loop_plugins": "{names} activo: devuelve al agente al trabajo en Stop, y su bucle no tiene guardia propia",
@@ -5917,7 +5964,14 @@ var es_default = {
5917
5964
  "doctor.memory.too_long": "{file}: {lines, plural, one {# línea} other {# líneas}}, por encima del techo de {max}; un archivo de instrucciones largo se sigue menos",
5918
5965
  "doctor.memory.fix_too_long": "Lleva detalles de {file} a docs o skills hasta que tenga como máximo {max} líneas, o sube memory.maxLines en .finitemonkey/config.yml",
5919
5966
  "doctor.git.autocrlf": 'git convierte los saltos de línea a CRLF al hacer checkout (core.autocrlf=true) y {file} no fija LF; los hooks y scripts de shell con CRLF fallan con "bad interpreter"',
5920
- "doctor.git.fix_autocrlf": "añade `* text=auto eol=lf` a {file}, haz commit y ejecuta `git add --renormalize .`"
5967
+ "doctor.git.fix_autocrlf": "añade `* text=auto eol=lf` a {file}, haz commit y ejecuta `git add --renormalize .`",
5968
+ "history.title": "Revisando las sesiones locales de Claude Code en este proyecto: {sessions, plural, one {# sesión} other {# sesiones}}, {turns, plural, one {# turno} other {# turnos}}.",
5969
+ "history.pattern.done_after_failing_tests": '{count, plural, one {# turno terminó} other {# turnos terminaron}} con "terminado" justo después de pruebas fallidas',
5970
+ "history.pattern.env_read": "{count, plural, one {# lectura} other {# lecturas}} de un archivo .env",
5971
+ "history.pattern.no_verify": "{count, plural, one {# comando git} other {# comandos git}} con --no-verify",
5972
+ "history.pattern.force_push": "{count, plural, one {# push forzado} other {# push forzados}}",
5973
+ "history.pattern.test_skip": "{count, plural, one {# edición} other {# ediciones}} de pruebas que añadieron .skip, .only o una marca para saltar",
5974
+ "history.estimate": "Son estimaciones por patrones de texto, no evidencia; no se guardó nada y nada salió de esta máquina."
5921
5975
  };
5922
5976
 
5923
5977
  // src/i18n/pt-BR.json
@@ -6014,6 +6068,8 @@ var pt_BR_default = {
6014
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",
6015
6069
  "doctor.settings.plugin_only": "strictPluginOnlyCustomization está ligado ({scopes}) e o plugin finitemonkey não está ativo; os hooks do projeto não carregam",
6016
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}",
6017
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",
6018
6074
  "doctor.settings.fix_agent_teams": "Rode `finitemonkey gate` antes de aceitar o resultado de um colega, ou remova a variável",
6019
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",
@@ -6421,7 +6477,14 @@ var pt_BR_default = {
6421
6477
  "doctor.memory.too_long": "{file}: {lines, plural, one {# linha} other {# linhas}}, acima do teto de {max}; arquivo de instruções longo é menos seguido",
6422
6478
  "doctor.memory.fix_too_long": "Leve detalhes de {file} para docs ou skills até ficar com no máximo {max} linhas, ou aumente memory.maxLines em .finitemonkey/config.yml",
6423
6479
  "doctor.git.autocrlf": 'o git converte quebras de linha para CRLF no checkout (core.autocrlf=true) e o {file} não fixa LF; hooks e scripts de shell com CRLF falham com "bad interpreter"',
6424
- "doctor.git.fix_autocrlf": "acrescente `* text=auto eol=lf` ao {file}, faça commit e rode `git add --renormalize .`"
6480
+ "doctor.git.fix_autocrlf": "acrescente `* text=auto eol=lf` ao {file}, faça commit e rode `git add --renormalize .`",
6481
+ "history.title": "Olhando as sessões locais do Claude Code neste projeto: {sessions, plural, one {# sessão} other {# sessões}}, {turns, plural, one {# turno} other {# turnos}}.",
6482
+ "history.pattern.done_after_failing_tests": '{count, plural, one {# turno terminou} other {# turnos terminaram}} com "pronto" logo depois de testes falhando',
6483
+ "history.pattern.env_read": "{count, plural, one {# leitura} other {# leituras}} de arquivo .env",
6484
+ "history.pattern.no_verify": "{count, plural, one {# comando git} other {# comandos git}} com --no-verify",
6485
+ "history.pattern.force_push": "{count, plural, one {# push forçado} other {# pushes forçados}}",
6486
+ "history.pattern.test_skip": "{count, plural, one {# edição} other {# edições}} de teste que acrescentaram .skip, .only ou marcador de pular",
6487
+ "history.estimate": "São estimativas por padrão de texto, não evidência; nada foi salvo e nada saiu desta máquina."
6425
6488
  };
6426
6489
 
6427
6490
  // src/i18n/review.json
@@ -6519,6 +6582,8 @@ var review_default = {
6519
6582
  "doctor.settings.fix_managed_hooks_only": "machine",
6520
6583
  "doctor.settings.plugin_only": "machine",
6521
6584
  "doctor.settings.fix_plugin_only": "machine",
6585
+ "doctor.settings.plugin_channels": "machine",
6586
+ "doctor.settings.fix_plugin_channels": "machine",
6522
6587
  "doctor.settings.agent_teams": "machine",
6523
6588
  "doctor.settings.fix_agent_teams": "machine",
6524
6589
  "doctor.settings.loop_plugins": "machine",
@@ -6926,7 +6991,14 @@ var review_default = {
6926
6991
  "doctor.memory.too_long": "machine",
6927
6992
  "doctor.memory.fix_too_long": "machine",
6928
6993
  "doctor.git.autocrlf": "machine",
6929
- "doctor.git.fix_autocrlf": "machine"
6994
+ "doctor.git.fix_autocrlf": "machine",
6995
+ "history.title": "machine",
6996
+ "history.pattern.done_after_failing_tests": "machine",
6997
+ "history.pattern.env_read": "machine",
6998
+ "history.pattern.no_verify": "machine",
6999
+ "history.pattern.force_push": "machine",
7000
+ "history.pattern.test_skip": "machine",
7001
+ "history.estimate": "machine"
6930
7002
  },
6931
7003
  es: {
6932
7004
  "command.init.summary": "machine",
@@ -7021,6 +7093,8 @@ var review_default = {
7021
7093
  "doctor.settings.fix_managed_hooks_only": "machine",
7022
7094
  "doctor.settings.plugin_only": "machine",
7023
7095
  "doctor.settings.fix_plugin_only": "machine",
7096
+ "doctor.settings.plugin_channels": "machine",
7097
+ "doctor.settings.fix_plugin_channels": "machine",
7024
7098
  "doctor.settings.agent_teams": "machine",
7025
7099
  "doctor.settings.fix_agent_teams": "machine",
7026
7100
  "doctor.settings.loop_plugins": "machine",
@@ -7428,7 +7502,14 @@ var review_default = {
7428
7502
  "doctor.memory.too_long": "machine",
7429
7503
  "doctor.memory.fix_too_long": "machine",
7430
7504
  "doctor.git.autocrlf": "machine",
7431
- "doctor.git.fix_autocrlf": "machine"
7505
+ "doctor.git.fix_autocrlf": "machine",
7506
+ "history.title": "machine",
7507
+ "history.pattern.done_after_failing_tests": "machine",
7508
+ "history.pattern.env_read": "machine",
7509
+ "history.pattern.no_verify": "machine",
7510
+ "history.pattern.force_push": "machine",
7511
+ "history.pattern.test_skip": "machine",
7512
+ "history.estimate": "machine"
7432
7513
  }
7433
7514
  };
7434
7515
 
@@ -7826,7 +7907,7 @@ var runShellCheck = (check2, run4, ctx) => {
7826
7907
  var commandCheck = (check2, ctx) => check2.run === void 0 ? unverified("not_implemented") : runShellCheck(check2, check2.run, ctx);
7827
7908
 
7828
7909
  // src/gate/checks/diff.ts
7829
- import { join as join12 } from "node:path";
7910
+ import { join as join13 } from "node:path";
7830
7911
 
7831
7912
  // src/core/glob.ts
7832
7913
  var escape = (char) => /[.+^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
@@ -7853,7 +7934,7 @@ var matchesAny = (path, globs) => {
7853
7934
  // src/gate/checks/diff.ts
7854
7935
  var MAX_LISTED = 10;
7855
7936
  var untrackedLines = (ctx) => ctx.changes.filter((change) => change.status === "A").reduce((sum, change) => {
7856
- 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)));
7857
7938
  return sum + (text6.ok ? text6.value.split("\n").length : 0);
7858
7939
  }, 0);
7859
7940
  var diffCheck = (_check, ctx) => {
@@ -7912,13 +7993,13 @@ var manualCheck = (check2, ctx) => {
7912
7993
  };
7913
7994
 
7914
7995
  // src/gate/checks/placeholders.ts
7915
- import { join as join13 } from "node:path";
7996
+ import { join as join14 } from "node:path";
7916
7997
  var MAX_LISTED2 = 10;
7917
7998
  var SKIP_FILES = /\.(md|mdx|txt|rst|lock|snap|svg|png|jpg|gif|json)$|(^|\/)(CHANGELOG|LICENSE)/i;
7918
7999
  var PLACEHOLDER = /\b(TODO|FIXME|XXX)\b|not implemented|NotImplementedError|unimplemented!|todo!\(|lorem ipsum/i;
7919
8000
  var findPlaceholders = (lines3) => lines3.filter((line2) => !isTestFile(line2.path) && !SKIP_FILES.test(line2.path) && PLACEHOLDER.test(line2.text));
7920
8001
  var untrackedAdded = (ctx) => ctx.changes.filter((change) => change.status === "A" && !SKIP_FILES.test(change.path)).flatMap((change) => {
7921
- 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)));
7922
8003
  return text6.ok ? text6.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
7923
8004
  });
7924
8005
  var placeholdersCheck = (_check, ctx) => {
@@ -8617,13 +8698,13 @@ var proseOutcome = (status) => {
8617
8698
  var proseCheck = (_check, ctx) => proseOutcome(proseGateStatus(ctx.runtime, ctx.paths.root));
8618
8699
 
8619
8700
  // src/secrets/baseline.ts
8620
- import { join as join14 } from "node:path";
8701
+ import { join as join15 } from "node:path";
8621
8702
  var BASELINE_FILE2 = ".finitemonkey/findings/baseline.json";
8622
8703
  var BASELINE_SCHEMA = "finitemonkey/secrets-baseline@1";
8623
8704
  var BASELINE_STATES = ["aceito", "falso-positivo"];
8624
8705
  var EXCEPTION_MAX_DAYS = 90;
8625
8706
  var EMPTY_BASELINE = { schema: BASELINE_SCHEMA, entries: [] };
8626
- var baselinePath = (root) => toAbsolutePath(join14(root, BASELINE_FILE2));
8707
+ var baselinePath = (root) => toAbsolutePath(join15(root, BASELINE_FILE2));
8627
8708
  var text5 = (record2, key) => {
8628
8709
  const value = record2[key];
8629
8710
  return typeof value === "string" && value !== "" ? value : void 0;
@@ -8683,14 +8764,14 @@ var withoutFixed = (baseline, fixed) => {
8683
8764
  var acceptedFingerprints = (baseline, now) => new Set(baseline.entries.filter((entry2) => entry2.expiresAt >= now).map((entry2) => entry2.fingerprint));
8684
8765
 
8685
8766
  // src/secrets/incidents.ts
8686
- import { join as join15 } from "node:path";
8767
+ import { join as join16 } from "node:path";
8687
8768
  var INCIDENTS_DIR = ".finitemonkey/incidents";
8688
8769
  var INCIDENT_STATES = ["aberto", "exposicao-local", "rotacionado", "fechado"];
8689
8770
  var INCIDENT_SOURCES = ["tool-output", "prompt", "history", "scan"];
8690
8771
  var ID = /^inc-[0-9a-f]{12}$/;
8691
8772
  var incidentId = (rule, fingerprint) => `inc-${sha256Hex(`${rule}\0${fingerprint}`).slice(0, 12)}`;
8692
- var incidentsDir = (root) => toAbsolutePath(join15(root, INCIDENTS_DIR));
8693
- 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`));
8694
8775
  var pick2 = (options, value) => options.find((option) => option === value);
8695
8776
  var str2 = (record2, key) => {
8696
8777
  const value = record2[key];
@@ -8745,7 +8826,7 @@ var listIncidents = (runtime, root) => {
8745
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));
8746
8827
  };
8747
8828
  var saveIncident = (runtime, root, incident) => {
8748
- const ignore = runtime.writeText(toAbsolutePath(join15(incidentsDir(root), ".gitignore")), "*\n");
8829
+ const ignore = runtime.writeText(toAbsolutePath(join16(incidentsDir(root), ".gitignore")), "*\n");
8749
8830
  if (!ignore.ok) return ignore;
8750
8831
  return runtime.writeText(incidentFile(root, incident.id), `${JSON.stringify(incident, null, 2)}
8751
8832
  `);
@@ -8792,7 +8873,7 @@ var moveIncident = (incident, to, now, by) => ({
8792
8873
  var isRotated = (incident) => incident.state === "rotacionado" || incident.state === "fechado";
8793
8874
 
8794
8875
  // src/secrets/scan.ts
8795
- import { join as join16, relative as relative4 } from "node:path";
8876
+ import { join as join17, relative as relative4 } from "node:path";
8796
8877
 
8797
8878
  // src/secrets/located.ts
8798
8879
  var findingId = (ruleId, file, fingerprint) => `fm-${sha256Hex(`${ruleId}\0${file}\0${fingerprint}`).slice(0, 16)}`;
@@ -8892,7 +8973,7 @@ var listed2 = (runtime, root, target) => {
8892
8973
  const base = toAbsolutePath(target, root);
8893
8974
  const walked = runtime.walkFiles(base);
8894
8975
  if (!walked.ok) return walked;
8895
- 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))))));
8896
8977
  };
8897
8978
  var scanFile = (runtime, root, file) => {
8898
8979
  const text6 = runtime.readText(toAbsolutePath(file, root));
@@ -8960,7 +9041,7 @@ var secretsCheck = (_check, ctx) => {
8960
9041
  };
8961
9042
 
8962
9043
  // src/gate/checks/tests.ts
8963
- import { join as join17 } from "node:path";
9044
+ import { join as join18 } from "node:path";
8964
9045
 
8965
9046
  // src/guards/tests/tree.ts
8966
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 }] : [];
@@ -8992,7 +9073,7 @@ var treeViolations = (ctx) => {
8992
9073
  changes: relevant,
8993
9074
  before: (path) => fileAt(ctx.git, ctx.base, path),
8994
9075
  after: (path) => {
8995
- const text6 = ctx.runtime.readText(toAbsolutePath(join17(ctx.paths.root, path)));
9076
+ const text6 = ctx.runtime.readText(toAbsolutePath(join18(ctx.paths.root, path)));
8996
9077
  return text6.ok ? text6.value : void 0;
8997
9078
  },
8998
9079
  acceptedConfigs: ctx.trust.configs,
@@ -9013,7 +9094,7 @@ var testsCheck = (check2, ctx) => {
9013
9094
  };
9014
9095
 
9015
9096
  // src/git/tree.ts
9016
- import { join as join18 } from "node:path";
9097
+ import { join as join19 } from "node:path";
9017
9098
 
9018
9099
  // src/secrets/guards/paths.ts
9019
9100
  var SAFE_ENV = /^\.env\.(?:example|sample|template|dist|defaults|schema)$/i;
@@ -9052,7 +9133,7 @@ var dropSensitive = (runtime, repo, env, options) => {
9052
9133
  return removed.ok ? accept(void 0) : removed;
9053
9134
  };
9054
9135
  var withTempIndex = (runtime, repo, options, work) => {
9055
- const path = toAbsolutePath(join18(repo.gitDir, `finitemonkey-${options.stamp}.index`));
9136
+ const path = toAbsolutePath(join19(repo.gitDir, `finitemonkey-${options.stamp}.index`));
9056
9137
  const env = { GIT_INDEX_FILE: path, GIT_OPTIONAL_LOCKS: "0" };
9057
9138
  try {
9058
9139
  return work(env);
@@ -9304,7 +9385,7 @@ var parsePorcelainV2 = (stdout) => {
9304
9385
  };
9305
9386
 
9306
9387
  // src/git/project-config.ts
9307
- import { join as join19 } from "node:path";
9388
+ import { join as join20 } from "node:path";
9308
9389
  var CONFIG_FILE3 = ".finitemonkey/config.yml";
9309
9390
  var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
9310
9391
  var stripComment2 = (line2) => {
@@ -9385,12 +9466,12 @@ var configMap = (tree, ...path) => {
9385
9466
  );
9386
9467
  };
9387
9468
  var loadProjectConfig = (runtime, root) => {
9388
- const text6 = runtime.readText(toAbsolutePath(join19(root, CONFIG_FILE3)));
9469
+ const text6 = runtime.readText(toAbsolutePath(join20(root, CONFIG_FILE3)));
9389
9470
  return text6.ok ? parseConfigYaml(text6.value) : {};
9390
9471
  };
9391
9472
 
9392
9473
  // src/git/merge/dropped.ts
9393
- import { join as join20 } from "node:path";
9474
+ import { join as join21 } from "node:path";
9394
9475
 
9395
9476
  // src/git/merge/lines.ts
9396
9477
  var normalizeLine = (line2) => line2.replace(/\r$/, "").trim().replace(/\s*,$/, "");
@@ -9470,7 +9551,7 @@ var parseDropped = (text6) => {
9470
9551
  return accept({ items, invalid });
9471
9552
  };
9472
9553
  var loadDropped = (runtime, root, file) => {
9473
- const path = toAbsolutePath(file ?? join20(root, DROPPED_FILE), root);
9554
+ const path = toAbsolutePath(file ?? join21(root, DROPPED_FILE), root);
9474
9555
  const text6 = runtime.readText(path);
9475
9556
  if (!text6.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text6.error}`);
9476
9557
  return parseDropped(text6.value);
@@ -9480,7 +9561,7 @@ var isDropped = (finding3, items) => items.some(
9480
9561
  );
9481
9562
 
9482
9563
  // src/git/merge/generated.ts
9483
- import { join as join21 } from "node:path";
9564
+ import { join as join22 } from "node:path";
9484
9565
  var LOCKFILES3 = /* @__PURE__ */ new Set([
9485
9566
  "package-lock.json",
9486
9567
  "npm-shrinkwrap.json",
@@ -9543,7 +9624,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
9543
9624
  if (declared.length === 0) return accept([]);
9544
9625
  const commit2 = resultCommit(runtime, repo, inputs);
9545
9626
  if (!commit2.ok) return commit2;
9546
- const dir = join21(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
9627
+ const dir = join22(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
9547
9628
  if (!runtime.git(["worktree", "add", "--detach", dir, commit2.value], repo.root).ok)
9548
9629
  return reject("could not check out the merge result");
9549
9630
  try {
@@ -9553,7 +9634,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
9553
9634
  }
9554
9635
  return accept(
9555
9636
  declared.flatMap(({ path }) => {
9556
- const fresh3 = runtime.readText(toAbsolutePath(join21(dir, path)));
9637
+ const fresh3 = runtime.readText(toAbsolutePath(join22(dir, path)));
9557
9638
  const merged = blobText(runtime, repo, commit2.value, path);
9558
9639
  if (fresh3.ok && fresh3.value === merged) return [];
9559
9640
  return [{ kind: "generated-differs", file: path, side: void 0, line: void 0, text: path, weak: false }];
@@ -9839,19 +9920,19 @@ var verifyMerge = (runtime, repo, options = {}) => {
9839
9920
  };
9840
9921
 
9841
9922
  // src/git/ownership.ts
9842
- import { join as join22 } from "node:path";
9923
+ import { join as join23 } from "node:path";
9843
9924
  var OWNERSHIP_DIR = ".finitemonkey/git";
9844
9925
  var ownerKey = (raw) => {
9845
9926
  const cleaned = (raw ?? "").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80);
9846
9927
  return cleaned === "" ? "unknown" : cleaned;
9847
9928
  };
9848
- 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`));
9849
9930
  var readDirty = (runtime, root) => {
9850
9931
  const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
9851
9932
  return status.ok ? parsePorcelainV2(status.stdout) : void 0;
9852
9933
  };
9853
9934
  var hashDirty = (runtime, repo, entries) => {
9854
- 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");
9855
9936
  const hashed = present2.length === 0 ? void 0 : runtime.gitWith(["hash-object", "--stdin-paths"], repo.root, {
9856
9937
  input: `${present2.map((entry2) => entry2.path).join("\n")}
9857
9938
  `
@@ -10286,7 +10367,7 @@ var isOwnWorktree = (input) => {
10286
10367
  var subagentStopGate = (input) => isOwnWorktree(input) ? stopGate(input) : { kind: "allow" };
10287
10368
 
10288
10369
  // src/gate/hooks/subagent.ts
10289
- import { join as join23 } from "node:path";
10370
+ import { join as join24 } from "node:path";
10290
10371
  var NO_CONTRACT_AGENTS = /(^|:)(reviewer|verifier|explore|plan|claude-code-guide|statusline-setup)$/i;
10291
10372
  var KNOWN_KINDS = /* @__PURE__ */ new Set([
10292
10373
  "command",
@@ -10320,12 +10401,12 @@ var subagentStart = (input) => {
10320
10401
  const active = readState(runtime, paths).active;
10321
10402
  if (active === void 0) return { kind: "allow" };
10322
10403
  const disk = (relative9) => {
10323
- const text6 = runtime.readText(toAbsolutePath(join23(paths.root, relative9)));
10404
+ const text6 = runtime.readText(toAbsolutePath(join24(paths.root, relative9)));
10324
10405
  return text6.ok ? text6.value : void 0;
10325
10406
  };
10326
10407
  const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
10327
10408
  if (!loaded.ok) return { kind: "allow" };
10328
- 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;
10329
10410
  return { kind: "allow", output: contextOutput("SubagentStart", contractFacts(t, loaded.value.contract, briefing)) };
10330
10411
  };
10331
10412
 
@@ -10342,7 +10423,7 @@ var PORTAO_HANDLERS = {
10342
10423
  };
10343
10424
 
10344
10425
  // src/prod/guard.ts
10345
- import { join as join25 } from "node:path";
10426
+ import { join as join26 } from "node:path";
10346
10427
 
10347
10428
  // src/git/command-text.ts
10348
10429
  var MAX_SHOWN = 160;
@@ -10684,7 +10765,7 @@ var remoteHostsOf = (command) => {
10684
10765
  };
10685
10766
 
10686
10767
  // src/prod/targets.ts
10687
- import { join as join24 } from "node:path";
10768
+ import { join as join25 } from "node:path";
10688
10769
  var DEPLOY_FILES = [
10689
10770
  "deploy.sh",
10690
10771
  "scripts/deploy.sh",
@@ -10708,12 +10789,12 @@ var hostsInText = (text6) => {
10708
10789
  return [...urls, ...keyed, ...commands].filter((host) => host !== "" && !host.includes("$"));
10709
10790
  };
10710
10791
  var workflowFiles = (runtime, root) => {
10711
- const listed4 = runtime.listDir(toAbsolutePath(join24(root, ".github", "workflows")));
10792
+ const listed4 = runtime.listDir(toAbsolutePath(join25(root, ".github", "workflows")));
10712
10793
  if (!listed4.ok) return [];
10713
10794
  return listed4.value.filter((name) => /deploy/i.test(name) && /\.ya?ml$/i.test(name)).map((name) => `.github/workflows/${name}`);
10714
10795
  };
10715
10796
  var detectedHosts = (runtime, root, files) => files.flatMap((file) => {
10716
- const text6 = runtime.readText(toAbsolutePath(join24(root, file)));
10797
+ const text6 = runtime.readText(toAbsolutePath(join25(root, file)));
10717
10798
  return text6.ok ? hostsInText(text6.value) : [];
10718
10799
  });
10719
10800
  var loadProdTargets = (runtime, root, config) => {
@@ -10725,7 +10806,7 @@ var loadProdTargets = (runtime, root, config) => {
10725
10806
  ...detectedHosts(runtime, root, deployFiles)
10726
10807
  ];
10727
10808
  const declaredDeploy = configAt(config, "prod", "deploy");
10728
- 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);
10729
10810
  return {
10730
10811
  hosts: new Set(hosts.map(hostKey).filter((host) => host !== "")),
10731
10812
  kubeContexts: configList(config, "prod", "kube_contexts"),
@@ -10902,8 +10983,8 @@ var recordEvidence = (runtime, root, owner, reads) => {
10902
10983
  ] : []
10903
10984
  );
10904
10985
  if (lines3.length === 0) return;
10905
- const path = toAbsolutePath(join25(root, EVIDENCE_FILE));
10906
- runtime.mkdir(toAbsolutePath(join25(root, ".finitemonkey")));
10986
+ const path = toAbsolutePath(join26(root, EVIDENCE_FILE));
10987
+ runtime.mkdir(toAbsolutePath(join26(root, ".finitemonkey")));
10907
10988
  const before = runtime.readText(path);
10908
10989
  const text6 = before.ok ? before.value : "";
10909
10990
  runtime.writeAtomic(path, `${text6}${text6 === "" || text6.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
@@ -11715,10 +11796,10 @@ var listWorktrees = (runtime, cwd) => {
11715
11796
  };
11716
11797
 
11717
11798
  // src/git/journal.ts
11718
- import { join as join26 } from "node:path";
11799
+ import { join as join27 } from "node:path";
11719
11800
  var JOURNAL_FILE = ".finitemonkey/git-journal.jsonl";
11720
11801
  var JOURNAL_SCHEMA = "finitemonkey/git-journal@1";
11721
- var journalPath = (root) => toAbsolutePath(join26(root, JOURNAL_FILE));
11802
+ var journalPath = (root) => toAbsolutePath(join27(root, JOURNAL_FILE));
11722
11803
  var headsOf = (value) => isRecord(value) ? Object.fromEntries(
11723
11804
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
11724
11805
  ) : {};
@@ -11754,7 +11835,7 @@ var readJournal = (runtime, root) => {
11754
11835
  };
11755
11836
  var appendJournal = (runtime, root, entry2) => {
11756
11837
  const path = journalPath(root);
11757
- const made = runtime.mkdir(toAbsolutePath(join26(root, ".finitemonkey")));
11838
+ const made = runtime.mkdir(toAbsolutePath(join27(root, ".finitemonkey")));
11758
11839
  if (!made.ok) return made;
11759
11840
  const previous = runtime.readText(path);
11760
11841
  const before = previous.ok ? previous.value : "";
@@ -12977,7 +13058,7 @@ var hookSchemaProblems = (hooks, claude) => {
12977
13058
  };
12978
13059
 
12979
13060
  // src/doctor/hook-sources.ts
12980
- import { join as join27 } from "node:path";
13061
+ import { join as join28 } from "node:path";
12981
13062
  var entriesOf = (source, hooks, own) => Object.entries(asRecord(hooks)).flatMap(
12982
13063
  ([event, groups]) => asList(groups).flatMap((group) => {
12983
13064
  const record2 = asRecord(group);
@@ -12990,7 +13071,7 @@ var projectHookFiles = (env) => readSettings(env).flatMap(({ scope: scope2, path
12990
13071
  const source = PROJECT_SCOPES[scope2];
12991
13072
  return source === void 0 || !("hooks" in settings) ? [] : [{ source, path, hooks: settings["hooks"] }];
12992
13073
  });
12993
- 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");
12994
13075
  var installRecord = (value, root) => {
12995
13076
  const records = Array.isArray(value) ? value.filter(isRecord) : isRecord(value) ? [value] : [];
12996
13077
  return records.find((record2) => record2["projectPath"] === root) ?? records[0];
@@ -13001,13 +13082,13 @@ var readJson = (env, path) => {
13001
13082
  return parsed?.ok === true ? parsed.value : void 0;
13002
13083
  };
13003
13084
  var installedPlugin = (env) => {
13004
- const index = readJson(env, toAbsolutePath(join27(pluginsDir(env), "installed_plugins.json")));
13085
+ const index = readJson(env, toAbsolutePath(join28(pluginsDir(env), "installed_plugins.json")));
13005
13086
  const record2 = installRecord(asRecord(index?.["plugins"])[PLUGIN_ID], env.root);
13006
13087
  const installPath = record2 === void 0 ? void 0 : optionalString(record2, "installPath");
13007
13088
  if (record2 === void 0 || installPath === void 0) return void 0;
13008
13089
  const root = toAbsolutePath(installPath);
13009
- const manifest = readJson(env, toAbsolutePath(join27(root, ".claude-plugin", "plugin.json")));
13010
- 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"));
13011
13092
  const text6 = env.runtime.readText(hooksPath);
13012
13093
  const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
13013
13094
  return {
@@ -13017,10 +13098,10 @@ var installedPlugin = (env) => {
13017
13098
  hooksText: text6.ok ? text6.value : void 0
13018
13099
  };
13019
13100
  };
13020
- var PRECEDENCE = ["managed", "local", "project", "user"];
13101
+ var PRECEDENCE2 = ["managed", "local", "project", "user"];
13021
13102
  var pluginEnabled = (env) => {
13022
13103
  const all = readSettings(env);
13023
- for (const scope2 of PRECEDENCE) {
13104
+ for (const scope2 of PRECEDENCE2) {
13024
13105
  const value = asRecord(all.find((item) => item.scope === scope2)?.settings["enabledPlugins"])[PLUGIN_ID];
13025
13106
  if (typeof value === "boolean") return { value, scope: scope2 };
13026
13107
  }
@@ -13052,7 +13133,7 @@ var schemaFindings = (env, plugin, claude) => {
13052
13133
  const files = [...projectHookFiles(env), ...plugin?.hooksFile === void 0 ? [] : [plugin.hooksFile]];
13053
13134
  const invalidJson = [
13054
13135
  ...unreadableProjectFiles(env),
13055
- ...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"))] : []
13056
13137
  ];
13057
13138
  return [
13058
13139
  ...invalidJson.flatMap((file) => schemaFinding(file, ["not valid JSON"], label2)),
@@ -13073,7 +13154,7 @@ var pluginFindings = (env, projectOwn) => {
13073
13154
  ];
13074
13155
  };
13075
13156
  var coverageFindings = (env, entries) => {
13076
- 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;
13077
13158
  if (entries.length === 0 && !installed) {
13078
13159
  return [finding("hooks.registered", "warn", "doctor.hooks.none", {}, "doctor.hooks.fix_none")];
13079
13160
  }
@@ -13125,17 +13206,17 @@ var checkHooks = (env) => {
13125
13206
  };
13126
13207
 
13127
13208
  // src/doctor/instructions.ts
13128
- import { join as join29 } from "node:path";
13209
+ import { join as join30 } from "node:path";
13129
13210
  var INSTRUCTION_LINE_CEILING = 200;
13130
13211
  var TOP_INSTRUCTIONS = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"];
13131
13212
  var RULES_DIR = ".claude/rules";
13132
13213
  var SHOWN = 3;
13133
13214
  var readProjectFile = (env, relative9) => {
13134
- const text6 = env.runtime.readText(toAbsolutePath(join29(env.root, ...relative9.split("/"))));
13215
+ const text6 = env.runtime.readText(toAbsolutePath(join30(env.root, ...relative9.split("/"))));
13135
13216
  return text6.ok ? text6.value : void 0;
13136
13217
  };
13137
13218
  var instructionFiles = (env) => {
13138
- 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("/"))));
13139
13220
  const rules = listed4.ok ? listed4.value.filter((name) => name.endsWith(".md")).map((name) => `${RULES_DIR}/${name}`) : [];
13140
13221
  return [...TOP_INSTRUCTIONS, ...rules].flatMap((path) => {
13141
13222
  const text6 = readProjectFile(env, path);
@@ -13240,12 +13321,12 @@ var checkInstructions = (env) => {
13240
13321
  };
13241
13322
 
13242
13323
  // src/doctor/line-endings.ts
13243
- import { join as join30 } from "node:path";
13324
+ import { join as join31 } from "node:path";
13244
13325
  var LF_RULE = /^\*\s.*\beol=lf\b/m;
13245
13326
  var checkLineEndings = ({ root, runtime }) => {
13246
13327
  const autocrlf = runtime.git(["config", "--get", "core.autocrlf"], root);
13247
13328
  if (!autocrlf.ok || autocrlf.stdout.trim().toLowerCase() !== "true") return [];
13248
- const attributes = runtime.readText(toAbsolutePath(join30(root, ".gitattributes")));
13329
+ const attributes = runtime.readText(toAbsolutePath(join31(root, ".gitattributes")));
13249
13330
  if (attributes.ok && LF_RULE.test(attributes.value)) return [];
13250
13331
  return [
13251
13332
  finding("git.autocrlf", "warn", "doctor.git.autocrlf", { file: ".gitattributes" }, "doctor.git.fix_autocrlf")
@@ -13253,7 +13334,7 @@ var checkLineEndings = ({ root, runtime }) => {
13253
13334
  };
13254
13335
 
13255
13336
  // src/doctor/memory.ts
13256
- import { join as join31 } from "node:path";
13337
+ import { join as join32 } from "node:path";
13257
13338
  var ceilingOf = (config) => {
13258
13339
  const value = configAt(parseConfigYaml(config ?? ""), "memory", "maxLines");
13259
13340
  const parsed = typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
@@ -13261,9 +13342,9 @@ var ceilingOf = (config) => {
13261
13342
  };
13262
13343
  var lineCount2 = (text6) => text6.replace(/\r?\n$/, "").split(/\r?\n/).length;
13263
13344
  var checkAgentsFile = ({ root, runtime }) => {
13264
- const text6 = runtime.readText(toAbsolutePath(join31(root, AGENTS_FILE)));
13345
+ const text6 = runtime.readText(toAbsolutePath(join32(root, AGENTS_FILE)));
13265
13346
  if (!text6.ok) return [];
13266
- const config = runtime.readText(toAbsolutePath(join31(root, CONFIG_FILE)));
13347
+ const config = runtime.readText(toAbsolutePath(join32(root, CONFIG_FILE)));
13267
13348
  const max = ceilingOf(config.ok ? config.value : void 0);
13268
13349
  const lines3 = lineCount2(text6.value);
13269
13350
  const params2 = { file: AGENTS_FILE, lines: lines3, max };
@@ -13271,7 +13352,7 @@ var checkAgentsFile = ({ root, runtime }) => {
13271
13352
  };
13272
13353
 
13273
13354
  // src/doctor/project-files.ts
13274
- import { join as join32 } from "node:path";
13355
+ import { join as join33 } from "node:path";
13275
13356
  var variants = (base, extensions) => extensions.map((extension) => `${base}${extension}`);
13276
13357
  var SCRIPT = [".js", ".cjs", ".mjs", ".ts", ".cts", ".mts"];
13277
13358
  var DATA = ["", ".json", ".yml", ".yaml"];
@@ -13302,7 +13383,7 @@ var CONFIG_GROUPS = [
13302
13383
  files: [...variants("commitlint.config", SCRIPT), ...variants(".commitlintrc", [...DATA, ".js"])]
13303
13384
  }
13304
13385
  ];
13305
- 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");
13306
13387
  var crowded = (env, groups, build) => groups.flatMap(({ name, files }) => {
13307
13388
  const found = present(env, files);
13308
13389
  return found.length > 1 ? [build(name, found.join(", "))] : [];
@@ -13868,7 +13949,7 @@ var rescueCommand = {
13868
13949
  };
13869
13950
 
13870
13951
  // src/git/undo.ts
13871
- 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";
13872
13953
  var INDEX_SUBJECT = "finitemonkey index:";
13873
13954
  var gitLine = (runtime, repo, args) => {
13874
13955
  const result = runtime.git(args, repo.root);
@@ -13911,7 +13992,7 @@ var findSnapshot = (views, wanted) => {
13911
13992
  };
13912
13993
  var stampOf2 = (iso) => iso.replace(/[:.]/g, "-");
13913
13994
  var worktreePath = (runtime, repo) => {
13914
- 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())}`);
13915
13996
  return toAbsolutePath(runtime.stat(toAbsolutePath(base)) === void 0 ? base : `${base}-2`);
13916
13997
  };
13917
13998
  var intoWorktree = (runtime, repo, view2) => {
@@ -14099,19 +14180,6 @@ var helpCommand = {
14099
14180
  )
14100
14181
  };
14101
14182
 
14102
- // src/install/plugin-yield.ts
14103
- import { join as join34 } from "node:path";
14104
- var PLUGIN_VIA = "plugin";
14105
- var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
14106
- `;
14107
- var projectOwnsHooks = (ctx) => {
14108
- const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
14109
- const text6 = ctx.runtime.readText(toAbsolutePath(join34(root, ...SETTINGS_FILE.split("/"))));
14110
- if (!text6.ok) return false;
14111
- const parsed = parseJsonObject(text6.value);
14112
- return parsed.ok && ownHandlersIn(parsed.value) > 0;
14113
- };
14114
-
14115
14183
  // src/commands/hook.ts
14116
14184
  var hookCommand = {
14117
14185
  path: ["hook"],
@@ -14424,6 +14492,178 @@ var i18nCheckCommand = {
14424
14492
  }
14425
14493
  };
14426
14494
 
14495
+ // src/history/look-back.ts
14496
+ import { join as join36 } from "node:path";
14497
+
14498
+ // src/history/patterns.ts
14499
+ import { posix as posix4 } from "node:path";
14500
+ var PATTERNS = ["done_after_failing_tests", "env_read", "no_verify", "force_push", "test_skip"];
14501
+ var TEST_COMMAND = /(^|[\s;&|(])((npm|pnpm|yarn|bun)\s+(run\s+)?(test|check)\b|npx\s+(vitest|jest)\b|vitest\b|jest\b|pytest\b|go\s+test\b|cargo\s+test\b|node\s+--test\b|mocha\b)/;
14502
+ var DONE_CLAIM = /\b(done|all (tests|checks) (pass|passed|are passing)|everything (passes|works)|it works|pronto|feito|conclu[ií]do|listo|terminado)\b/i;
14503
+ var ENV_FILE2 = /(^|[\s"'=/])\.env(\.[A-Za-z0-9_-]+)?(?=$|[\s"';|&)])/;
14504
+ var ENV_TEMPLATE = /\.env\.(example|sample|template|dist|defaults)\b/;
14505
+ var READ_COMMAND = /(^|[\s;&|(])(cat|less|more|head|tail|bat|type|Get-Content|source|\.)\s/;
14506
+ var PUSH = /(^|[\s;&|(])git\s+push\b([^;&|\n]*)/;
14507
+ var FORCE = /\s(--force(-with-lease)?(=\S*)?|-f|-[A-Za-z]*f[A-Za-z]*)(?=\s|$)|\s\+\S/;
14508
+ var NO_VERIFY = /(^|[\s;&|(])git\s+(commit|push|merge|rebase|am|cherry-pick)\b[^;&|\n]*\s--no-verify\b/;
14509
+ var TEST_FILE = /(\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)__tests__\/|_test\.(go|py)$|(^|\/)test_[^/]+\.py$)/;
14510
+ var SKIP = /\b(it|test|describe)\.skip\s*\(|\bx(it|describe|test)\s*\(|@pytest\.mark\.skip|\bt\.Skip\(|\.only\s*\(/;
14511
+ var commandOf = (tool) => {
14512
+ if (tool.name !== "Bash" && tool.name !== "PowerShell") return void 0;
14513
+ const command = tool.input["command"];
14514
+ return typeof command === "string" ? command : void 0;
14515
+ };
14516
+ var stringField = (tool, name) => {
14517
+ const value = tool.input[name];
14518
+ return typeof value === "string" ? value : "";
14519
+ };
14520
+ var isEnvPath = (path) => {
14521
+ const name = posix4.basename(path.replace(/\\/g, "/"));
14522
+ return /^\.env(\.[A-Za-z0-9_-]+)?$/.test(name) && !ENV_TEMPLATE.test(name);
14523
+ };
14524
+ var envRead = (tool) => {
14525
+ if (tool.name === "Read") return isEnvPath(stringField(tool, "file_path"));
14526
+ const command = commandOf(tool);
14527
+ if (command === void 0 || !READ_COMMAND.test(command)) return false;
14528
+ const match = ENV_FILE2.exec(command);
14529
+ return match !== null && !ENV_TEMPLATE.test(command.slice(match.index, match.index + match[0].length + 12));
14530
+ };
14531
+ var forcePush = (command) => {
14532
+ const match = PUSH.exec(command);
14533
+ return match !== null && FORCE.test(match[2] ?? "");
14534
+ };
14535
+ var addedSkip = (tool) => {
14536
+ const path = stringField(tool, "file_path");
14537
+ if (!TEST_FILE.test(path.replace(/\\/g, "/"))) return false;
14538
+ if (tool.name === "Write") return SKIP.test(stringField(tool, "content"));
14539
+ if (tool.name === "Edit")
14540
+ return SKIP.test(stringField(tool, "new_string")) && !SKIP.test(stringField(tool, "old_string"));
14541
+ if (tool.name !== "MultiEdit") return false;
14542
+ const edits = tool.input["edits"];
14543
+ return Array.isArray(edits) && edits.some((edit) => {
14544
+ if (typeof edit !== "object" || edit === null) return false;
14545
+ const next2 = "new_string" in edit && typeof edit.new_string === "string" ? edit.new_string : "";
14546
+ const before = "old_string" in edit && typeof edit.old_string === "string" ? edit.old_string : "";
14547
+ return SKIP.test(next2) && !SKIP.test(before);
14548
+ });
14549
+ };
14550
+ var doneAfterFailure = (turn) => {
14551
+ const runs = turn.tools.filter((tool) => TEST_COMMAND.test(commandOf(tool) ?? ""));
14552
+ const last = runs.at(-1);
14553
+ if (last === void 0 || turn.results.get(last.id)?.failed !== true) return void 0;
14554
+ return DONE_CLAIM.test(turn.finalText) ? last : void 0;
14555
+ };
14556
+ var sampleOf = (tool) => (commandOf(tool) ?? stringField(tool, "file_path")).split("\n")[0]?.slice(0, 120) ?? "";
14557
+ var hitsIn = (turn) => {
14558
+ const hits = [];
14559
+ const done = doneAfterFailure(turn);
14560
+ if (done !== void 0) hits.push({ pattern: "done_after_failing_tests", sample: sampleOf(done) });
14561
+ for (const tool of turn.tools) {
14562
+ const command = commandOf(tool);
14563
+ if (envRead(tool)) hits.push({ pattern: "env_read", sample: sampleOf(tool) });
14564
+ if (command !== void 0 && NO_VERIFY.test(command)) hits.push({ pattern: "no_verify", sample: sampleOf(tool) });
14565
+ if (command !== void 0 && forcePush(command)) hits.push({ pattern: "force_push", sample: sampleOf(tool) });
14566
+ if (addedSkip(tool)) hits.push({ pattern: "test_skip", sample: sampleOf(tool) });
14567
+ }
14568
+ return hits;
14569
+ };
14570
+
14571
+ // src/history/transcript.ts
14572
+ var blocksOf = (message) => {
14573
+ if (!isRecord(message)) return [];
14574
+ const content = message["content"];
14575
+ if (typeof content === "string") return [{ type: "text", text: content }];
14576
+ return Array.isArray(content) ? content : [];
14577
+ };
14578
+ var textOf3 = (value) => {
14579
+ if (typeof value === "string") return value;
14580
+ if (Array.isArray(value)) return value.map(textOf3).join("\n");
14581
+ return isRecord(value) && typeof value["text"] === "string" ? value["text"] : "";
14582
+ };
14583
+ var FAILED_EXIT = /^Exit code [1-9]\d*/m;
14584
+ var startTurn = (prompt) => ({ prompt, tools: [], results: /* @__PURE__ */ new Map(), finalText: "" });
14585
+ var addAssistant = (draft, blocks) => {
14586
+ for (const block4 of blocks) {
14587
+ if (!isRecord(block4)) continue;
14588
+ if (block4["type"] === "text" && typeof block4["text"] === "string" && block4["text"].trim() !== "") {
14589
+ draft.finalText = block4["text"];
14590
+ }
14591
+ const { id, name, input } = block4;
14592
+ if (block4["type"] === "tool_use" && typeof id === "string" && typeof name === "string" && isRecord(input)) {
14593
+ draft.tools.push({ id, name, input });
14594
+ }
14595
+ }
14596
+ };
14597
+ var addResults = (draft, blocks) => {
14598
+ let any = false;
14599
+ for (const block4 of blocks) {
14600
+ if (!isRecord(block4) || block4["type"] !== "tool_result" || typeof block4["tool_use_id"] !== "string") continue;
14601
+ const text6 = textOf3(block4["content"]);
14602
+ draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(text6), text: text6 });
14603
+ any = true;
14604
+ }
14605
+ return any;
14606
+ };
14607
+ var parseTranscript = (jsonl) => {
14608
+ const turns = [];
14609
+ let current;
14610
+ for (const line2 of jsonl.split(/\r?\n/)) {
14611
+ if (line2.trim() === "") continue;
14612
+ const parsed = parseJsonObject(line2);
14613
+ if (!parsed.ok) continue;
14614
+ const entry2 = parsed.value;
14615
+ const blocks = blocksOf(entry2["message"]);
14616
+ if (entry2["type"] === "user") {
14617
+ if (current !== void 0 && addResults(current, blocks)) continue;
14618
+ const prompt = blocks.map(textOf3).join("\n").trim();
14619
+ if (prompt === "") continue;
14620
+ current = startTurn(prompt);
14621
+ turns.push(current);
14622
+ } else if (entry2["type"] === "assistant" && current !== void 0) {
14623
+ addAssistant(current, blocks);
14624
+ }
14625
+ }
14626
+ return turns;
14627
+ };
14628
+
14629
+ // src/history/look-back.ts
14630
+ var SAMPLES = 3;
14631
+ var projectLogKey = (root) => root.replace(/[^A-Za-z0-9-]/g, "-");
14632
+ var projectLogDir = (runtime, root, env) => join36(env["CLAUDE_CONFIG_DIR"] ?? join36(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
14633
+ var lookBack = (runtime, root, env) => {
14634
+ const dir = projectLogDir(runtime, root, env);
14635
+ const listed4 = runtime.listDir(toAbsolutePath(dir));
14636
+ const files = listed4.ok ? listed4.value.filter((name) => name.endsWith(".jsonl")).sort() : [];
14637
+ const counts = {
14638
+ done_after_failing_tests: 0,
14639
+ env_read: 0,
14640
+ no_verify: 0,
14641
+ force_push: 0,
14642
+ test_skip: 0
14643
+ };
14644
+ const samples = {
14645
+ done_after_failing_tests: [],
14646
+ env_read: [],
14647
+ no_verify: [],
14648
+ force_push: [],
14649
+ test_skip: []
14650
+ };
14651
+ let turns = 0;
14652
+ for (const name of files) {
14653
+ const text6 = runtime.readText(toAbsolutePath(join36(dir, name)));
14654
+ if (!text6.ok) continue;
14655
+ for (const turn of parseTranscript(text6.value)) {
14656
+ turns += 1;
14657
+ for (const hit3 of hitsIn(turn)) {
14658
+ counts[hit3.pattern] += 1;
14659
+ const list4 = samples[hit3.pattern];
14660
+ if (list4.length < SAMPLES) list4.push(maskText(hit3.sample));
14661
+ }
14662
+ }
14663
+ }
14664
+ return { estimate: true, sessions: files.length, turns, counts, samples };
14665
+ };
14666
+
14427
14667
  // src/install/diff.ts
14428
14668
  var MAX_DIFF_LINES = 120;
14429
14669
  var table = (a, b) => {
@@ -14578,17 +14818,32 @@ var initRequest = (args) => ({
14578
14818
  marketplace: args.values["marketplace"] === true,
14579
14819
  showDiff: false
14580
14820
  });
14821
+ var withLookBack = (ctx, result) => {
14822
+ if (!result.ok) return result;
14823
+ const root = openProject(ctx).project.root;
14824
+ const found = lookBack(ctx.runtime, root, ctx.env);
14825
+ const lines3 = [
14826
+ ctx.t("history.title", { sessions: found.sessions, turns: found.turns }),
14827
+ ...PATTERNS.map((pattern) => ` ${ctx.t(`history.pattern.${pattern}`, { count: found.counts[pattern] })}`),
14828
+ ctx.t("history.estimate")
14829
+ ];
14830
+ return { ...result, data: { ...result.data, lookBack: found }, human: [...result.human ?? [], ...lines3] };
14831
+ };
14581
14832
  var initCommand = {
14582
14833
  path: ["init"],
14583
14834
  summaryKey: "command.init.summary",
14584
- usage: "finitemonkey init [--preset neutral|gabriel] [--bin <path>] [--marketplace] [--dry-run] [--yes]",
14835
+ usage: "finitemonkey init [--preset neutral|gabriel] [--bin <path>] [--marketplace] [--look-back] [--dry-run] [--yes]",
14585
14836
  status: "ready",
14586
14837
  options: {
14587
14838
  preset: { type: "string" },
14588
14839
  bin: { type: "string" },
14589
- marketplace: { type: "boolean" }
14840
+ marketplace: { type: "boolean" },
14841
+ "look-back": { type: "boolean" }
14590
14842
  },
14591
- run: (ctx, args) => Promise.resolve(runInstall(ctx, initRequest(args), "neutral"))
14843
+ run: (ctx, args) => {
14844
+ const result = runInstall(ctx, initRequest(args), "neutral");
14845
+ return Promise.resolve(args.values["look-back"] === true ? withLookBack(ctx, result) : result);
14846
+ }
14592
14847
  };
14593
14848
 
14594
14849
  // src/commands/prose-check.ts
@@ -15066,7 +15321,7 @@ var secretsScanCommand = {
15066
15321
  };
15067
15322
 
15068
15323
  // src/task/create.ts
15069
- import { join as join36 } from "node:path";
15324
+ import { join as join37 } from "node:path";
15070
15325
 
15071
15326
  // src/core/yaml/write.ts
15072
15327
  var PLAIN_SAFE = /^[A-Za-z_./][A-Za-z0-9_./ ()-]*$/;
@@ -15150,10 +15405,10 @@ var explicitChecks = (draft) => [
15150
15405
  var draftChecks = (runtime, paths, draft) => {
15151
15406
  const disk = {
15152
15407
  read: (relative9) => {
15153
- const text6 = runtime.readText(toAbsolutePath(join36(paths.root, relative9)));
15408
+ const text6 = runtime.readText(toAbsolutePath(join37(paths.root, relative9)));
15154
15409
  return text6.ok ? text6.value : void 0;
15155
15410
  },
15156
- exists: (relative9) => runtime.stat(toAbsolutePath(join36(paths.root, relative9))) !== void 0
15411
+ exists: (relative9) => runtime.stat(toAbsolutePath(join37(paths.root, relative9))) !== void 0
15157
15412
  };
15158
15413
  const detected = detectChecks(disk);
15159
15414
  const base = draft.runs.length > 0 || draft.tests !== void 0 ? explicitChecks(draft) : detected;
@@ -15557,7 +15812,7 @@ var REGISTRY = [
15557
15812
  ];
15558
15813
 
15559
15814
  // src/i18n/lang-settings.ts
15560
- import { join as join37 } from "node:path";
15815
+ import { join as join38 } from "node:path";
15561
15816
  var read3 = (runtime, path) => {
15562
15817
  const text6 = runtime.readText(toAbsolutePath(path));
15563
15818
  return text6.ok ? text6.value : void 0;
@@ -15572,14 +15827,14 @@ var languageIn = (text6) => {
15572
15827
  const value = parsed.ok && isRecord(parsed.value) ? parsed.value["language"] : void 0;
15573
15828
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
15574
15829
  };
15575
- var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join37(runtime.homeDir(), ".claude");
15830
+ var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join38(runtime.homeDir(), ".claude");
15576
15831
  var langSettings = (runtime, cwd, env, platform) => {
15577
15832
  const root = findGitRoot(cwd, runtime) ?? cwd;
15578
- const config = parseConfigYaml(read3(runtime, join37(root, CONFIG_FILE)) ?? "");
15833
+ const config = parseConfigYaml(read3(runtime, join38(root, CONFIG_FILE)) ?? "");
15579
15834
  const claudeFiles = [
15580
- join37(root, ".claude", "settings.local.json"),
15581
- join37(root, ".claude", "settings.json"),
15582
- join37(claudeDir(runtime, env), "settings.json")
15835
+ join38(root, ".claude", "settings.local.json"),
15836
+ join38(root, ".claude", "settings.json"),
15837
+ join38(claudeDir(runtime, env), "settings.json")
15583
15838
  ];
15584
15839
  return {
15585
15840
  config: configText(config, "lang"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "finitemonkey",
3
- "version": "0.1.0-next.12",
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",