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 +1 -0
- package/README.md +1 -0
- package/README.pt-BR.md +1 -0
- package/dist/finitemonkey.mjs +363 -108
- package/package.json +1 -1
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. |
|
package/dist/finitemonkey.mjs
CHANGED
|
@@ -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/
|
|
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 ?
|
|
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(
|
|
2249
|
-
{ scope: "local", path: toAbsolutePath(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
3758
|
+
import { join as join11 } from "node:path";
|
|
3723
3759
|
|
|
3724
3760
|
// src/gate/hooks/common.ts
|
|
3725
|
-
import { dirname as dirname5, join as
|
|
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(
|
|
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(
|
|
3770
|
-
toAbsolutePath(
|
|
3771
|
-
...env["HOME"] === void 0 ? [] : [toAbsolutePath(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
8693
|
-
var incidentFile = (root, id) => toAbsolutePath(
|
|
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(
|
|
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
|
|
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,
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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 ??
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
10906
|
-
runtime.mkdir(toAbsolutePath(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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 }) =>
|
|
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(
|
|
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(
|
|
13010
|
-
const hooksPath = toAbsolutePath(
|
|
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
|
|
13101
|
+
var PRECEDENCE2 = ["managed", "local", "project", "user"];
|
|
13021
13102
|
var pluginEnabled = (env) => {
|
|
13022
13103
|
const all = readSettings(env);
|
|
13023
|
-
for (const scope2 of
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
13345
|
+
const text6 = runtime.readText(toAbsolutePath(join32(root, AGENTS_FILE)));
|
|
13265
13346
|
if (!text6.ok) return [];
|
|
13266
|
-
const config = runtime.readText(toAbsolutePath(
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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) =>
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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"] ??
|
|
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,
|
|
15833
|
+
const config = parseConfigYaml(read3(runtime, join38(root, CONFIG_FILE)) ?? "");
|
|
15579
15834
|
const claudeFiles = [
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
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.
|
|
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",
|