finitemonkey 0.1.0-next.23 → 0.1.0-next.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/finitemonkey.mjs +992 -533
  2. package/package.json +1 -1
@@ -28,15 +28,15 @@ import { dirname } from "node:path";
28
28
  var accept = (value) => ({ ok: true, value });
29
29
  var reject = (error) => ({ ok: false, error });
30
30
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
31
- var parseJson = (text7) => {
31
+ var parseJson = (text8) => {
32
32
  try {
33
- return accept(JSON.parse(text7));
33
+ return accept(JSON.parse(text8));
34
34
  } catch (error) {
35
35
  return reject(error instanceof Error ? error.message : String(error));
36
36
  }
37
37
  };
38
- var parseJsonObject = (text7) => {
39
- const parsed = parseJson(text7);
38
+ var parseJsonObject = (text8) => {
39
+ const parsed = parseJson(text8);
40
40
  if (!parsed.ok) return parsed;
41
41
  return isRecord(parsed.value) ? accept(parsed.value) : reject("expected a JSON object");
42
42
  };
@@ -160,13 +160,13 @@ var attempt = (work) => {
160
160
  return reject(messageOf(error));
161
161
  }
162
162
  };
163
- var nodeWriteText = (path, text7) => {
163
+ var nodeWriteText = (path, text8) => {
164
164
  const temp = `${path}.${String(process.pid)}.tmp`;
165
165
  try {
166
166
  mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
167
167
  const fd = openSync(temp, "w", 384);
168
168
  try {
169
- writeSync(fd, text7);
169
+ writeSync(fd, text8);
170
170
  fsyncSync(fd);
171
171
  } finally {
172
172
  closeSync(fd);
@@ -778,7 +778,7 @@ import { dirname as dirname3 } from "node:path";
778
778
 
779
779
  // src/install/hash.ts
780
780
  import { createHash } from "node:crypto";
781
- var hashText = (text7) => `sha256:${createHash("sha256").update(text7, "utf8").digest("hex")}`;
781
+ var hashText = (text8) => `sha256:${createHash("sha256").update(text8, "utf8").digest("hex")}`;
782
782
 
783
783
  // src/install/layout.ts
784
784
  var STATE_DIR = ".finitemonkey";
@@ -809,6 +809,76 @@ var PRESETS = ["neutral", "gabriel"];
809
809
  var parsePreset = (raw) => raw === void 0 ? "neutral" : PRESETS.find((preset) => preset === raw);
810
810
  var COMMITTED_STATE = ["config.yml", "runtime/", "tasks/", "rules.yml", "policy.yml", "findings/"];
811
811
 
812
+ // src/install/hook-entries.ts
813
+ var INSTALLED_HOOKS = [
814
+ { event: "SessionStart", timeout: 30, guard: false },
815
+ { event: "UserPromptSubmit", timeout: 10, guard: true },
816
+ { event: "PreToolUse", timeout: 10, guard: true },
817
+ { event: "PostToolUse", timeout: 30, guard: false },
818
+ { event: "Stop", timeout: 600, guard: false },
819
+ { event: "SubagentStart", timeout: 30, guard: false },
820
+ { event: "SubagentStop", timeout: 600, guard: false },
821
+ { event: "PreCompact", timeout: 30, guard: false },
822
+ { event: "ConfigChange", timeout: 10, guard: true }
823
+ ];
824
+ var PROJECT_BIN = `\${CLAUDE_PROJECT_DIR}/${LOCAL_BIN}`;
825
+ var VENDORED_BIN = `\${CLAUDE_PROJECT_DIR}/${VENDOR_BIN}`;
826
+ var isScript = (path) => /\.(c|m)?js$/i.test(path);
827
+ var execForm = (runner, event) => {
828
+ if (runner.kind === "local") return { command: "node", args: [PROJECT_BIN, "hook", event] };
829
+ if (runner.kind === "vendored") return { command: "node", args: [VENDORED_BIN, "hook", event] };
830
+ if (isScript(runner.path)) return { command: "node", args: [runner.path, "hook", event] };
831
+ return { command: runner.path, args: ["hook", event] };
832
+ };
833
+ var hookHandler = (runner, spec2, failClosedSupported) => {
834
+ const failure4 = spec2.guard && failClosedSupported ? { onFailure: "block" } : {};
835
+ const { command, args: args2 } = execForm(runner, spec2.event);
836
+ return { type: "command", command, args: args2, timeout: spec2.timeout, ...failure4 };
837
+ };
838
+ var OWN_SHELL = /(^|[\s/"'])finitemonkey(@[^\s"]+|\.mjs|\.cmd|\.exe)?"?\s+hook\s/;
839
+ var OWN_SCRIPT = /finitemonkey(\.mjs|\.cmd|\.exe)?$/;
840
+ var stringsOf = (value) => Array.isArray(value) ? value.filter((item2) => typeof item2 === "string") : [];
841
+ var isOwnHandler = (handler) => {
842
+ if (!isRecord(handler)) return false;
843
+ const command = handler["command"];
844
+ if (typeof command !== "string") return false;
845
+ const args2 = stringsOf(handler["args"]);
846
+ if (args2.length === 0) return OWN_SHELL.test(`${command} `);
847
+ const hookAt = args2.indexOf("hook");
848
+ if (hookAt < 0) return false;
849
+ const target2 = hookAt === 0 ? command : args2[hookAt - 1] ?? "";
850
+ return OWN_SCRIPT.test(target2);
851
+ };
852
+
853
+ // src/install/statusline.ts
854
+ var OWN = /finitemonkey(\.mjs|\.cmd|\.exe)?"?\s+statusline\s*$/;
855
+ var projectFile = (relative11) => relative11;
856
+ var commandFor = (runner) => {
857
+ if (runner.kind === "local") return `node ${projectFile(LOCAL_BIN)} statusline`;
858
+ if (runner.kind === "vendored") return `node ${projectFile(VENDOR_BIN)} statusline`;
859
+ return isScript(runner.path) ? `node "${runner.path}" statusline` : `"${runner.path}" statusline`;
860
+ };
861
+ var ownStatusLine = (runner) => ({
862
+ type: "command",
863
+ command: commandFor(runner),
864
+ padding: 0
865
+ });
866
+ var isOwnStatusLine = (value) => isRecord(value) && typeof value["command"] === "string" && OWN.test(value["command"]);
867
+ var statusLineCommand = (value) => {
868
+ if (!isRecord(value) || isOwnStatusLine(value)) return void 0;
869
+ const command = value["command"];
870
+ return value["type"] === "command" && typeof command === "string" && command.trim() !== "" ? command : void 0;
871
+ };
872
+ var statusLineIn = (text8) => {
873
+ const parsed = text8 === void 0 ? void 0 : parseJsonObject(text8);
874
+ return parsed?.ok === true ? parsed.value["statusLine"] : void 0;
875
+ };
876
+ var parseStatusLineAdded = (value) => {
877
+ if (!isRecord(value) || !("previous" in value)) return void 0;
878
+ const wrap = value["wrap"];
879
+ return { previous: value["previous"], wrap: typeof wrap === "string" ? wrap : null };
880
+ };
881
+
812
882
  // src/install/lock-file.ts
813
883
  var ENTRY_KINDS = ["settings", "gitignore", "generated", "markdown", "githook"];
814
884
  var strings = (value) => Array.isArray(value) && value.every((item2) => typeof item2 === "string") ? value : void 0;
@@ -822,7 +892,17 @@ var parseAdded = (value) => {
822
892
  const marketplace = value["marketplace"] === true;
823
893
  const plugin = value["plugin"] === true;
824
894
  const language = value["language"];
825
- const added = { schema, attribution, deny: deny2, ask: ask2, blockReads, marketplace, plugin };
895
+ const statusLine = parseStatusLineAdded(value["statusLine"]);
896
+ const added = {
897
+ schema,
898
+ attribution,
899
+ deny: deny2,
900
+ ask: ask2,
901
+ blockReads,
902
+ marketplace,
903
+ plugin,
904
+ ...statusLine === void 0 ? {} : { statusLine }
905
+ };
826
906
  return typeof language === "string" ? { ...added, language } : added;
827
907
  };
828
908
  var parseEntry = (value) => {
@@ -863,8 +943,8 @@ var fromRecord = (record3) => {
863
943
  }
864
944
  return accept({ schema, version, preset: known2, installedAt, updatedAt, createdDirs, entries });
865
945
  };
866
- var parseLock = (text7) => {
867
- const parsed = parseJsonObject(text7);
946
+ var parseLock = (text8) => {
947
+ const parsed = parseJsonObject(text8);
868
948
  return parsed.ok ? fromRecord(parsed.value) : reject(parsed.error);
869
949
  };
870
950
  var renderLock = (lock) => `${JSON.stringify(lock, null, 2)}
@@ -926,10 +1006,10 @@ var ensureTree = (tree, key) => {
926
1006
  tree[key] = fresh3;
927
1007
  return fresh3;
928
1008
  };
929
- var parseConfigYaml = (text7) => {
1009
+ var parseConfigYaml = (text8) => {
930
1010
  const root = {};
931
1011
  const stack = [{ indent: -1, tree: root, key: void 0 }];
932
- for (const raw of text7.split(/\r?\n/)) {
1012
+ for (const raw of text8.split(/\r?\n/)) {
933
1013
  const line2 = stripComment(raw).trimEnd();
934
1014
  if (line2.trim() === "" || line2.trim() === "---") continue;
935
1015
  const indent = line2.length - line2.trimStart().length;
@@ -952,33 +1032,33 @@ var configMap = (tree, ...path) => {
952
1032
  );
953
1033
  };
954
1034
  var loadProjectConfig = (runtime, root) => {
955
- const text7 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE2)));
956
- return text7.ok ? parseConfigYaml(text7.value) : {};
1035
+ const text8 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE2)));
1036
+ return text8.ok ? parseConfigYaml(text8.value) : {};
957
1037
  };
958
1038
 
959
1039
  // src/install/managed-block.ts
960
- var lineBreak = (text7) => text7.includes("\r\n") ? "\r\n" : "\n";
1040
+ var lineBreak = (text8) => text8.includes("\r\n") ? "\r\n" : "\n";
961
1041
  var blockRange = (lines3, markers) => {
962
1042
  const start = lines3.findIndex((line2) => line2.trimEnd() === markers.begin);
963
1043
  if (start < 0) return void 0;
964
1044
  const end = lines3.findIndex((line2, index) => index > start && line2.trimEnd() === markers.end);
965
1045
  return end < 0 ? void 0 : { start, end };
966
1046
  };
967
- var withManagedBlock = (text7, markers, body2) => {
968
- const eol = text7 === void 0 ? "\n" : lineBreak(text7);
1047
+ var withManagedBlock = (text8, markers, body2) => {
1048
+ const eol = text8 === void 0 ? "\n" : lineBreak(text8);
969
1049
  const block4 = [markers.begin, ...body2, markers.end];
970
- if (text7 === void 0 || text7 === "") return `${block4.join(eol)}${eol}`;
971
- const lines3 = text7.split(eol);
1050
+ if (text8 === void 0 || text8 === "") return `${block4.join(eol)}${eol}`;
1051
+ const lines3 = text8.split(eol);
972
1052
  const range = blockRange(lines3, markers);
973
1053
  if (range !== void 0) return [...lines3.slice(0, range.start), ...block4, ...lines3.slice(range.end + 1)].join(eol);
974
- const separator = text7.endsWith(eol) ? "" : eol;
975
- return `${text7}${separator}${block4.join(eol)}${eol}`;
1054
+ const separator = text8.endsWith(eol) ? "" : eol;
1055
+ return `${text8}${separator}${block4.join(eol)}${eol}`;
976
1056
  };
977
- var withoutManagedBlock = (text7, markers) => {
978
- const eol = lineBreak(text7);
979
- const lines3 = text7.split(eol);
1057
+ var withoutManagedBlock = (text8, markers) => {
1058
+ const eol = lineBreak(text8);
1059
+ const lines3 = text8.split(eol);
980
1060
  const range = blockRange(lines3, markers);
981
- if (range === void 0) return text7;
1061
+ if (range === void 0) return text8;
982
1062
  return [...lines3.slice(0, range.start), ...lines3.slice(range.end + 1)].join(eol);
983
1063
  };
984
1064
 
@@ -1075,48 +1155,7 @@ var configTemplate = (preset, lang) => [
1075
1155
  `lang: ${lang}`,
1076
1156
  ""
1077
1157
  ].join("\n");
1078
- var hasMarker = (text7) => text7.includes(GENERATED_MARKER);
1079
-
1080
- // src/install/hook-entries.ts
1081
- var INSTALLED_HOOKS = [
1082
- { event: "SessionStart", timeout: 30, guard: false },
1083
- { event: "UserPromptSubmit", timeout: 10, guard: true },
1084
- { event: "PreToolUse", timeout: 10, guard: true },
1085
- { event: "PostToolUse", timeout: 30, guard: false },
1086
- { event: "Stop", timeout: 600, guard: false },
1087
- { event: "SubagentStart", timeout: 30, guard: false },
1088
- { event: "SubagentStop", timeout: 600, guard: false },
1089
- { event: "PreCompact", timeout: 30, guard: false },
1090
- { event: "ConfigChange", timeout: 10, guard: true }
1091
- ];
1092
- var PROJECT_BIN = `\${CLAUDE_PROJECT_DIR}/${LOCAL_BIN}`;
1093
- var VENDORED_BIN = `\${CLAUDE_PROJECT_DIR}/${VENDOR_BIN}`;
1094
- var isScript = (path) => /\.(c|m)?js$/i.test(path);
1095
- var execForm = (runner, event) => {
1096
- if (runner.kind === "local") return { command: "node", args: [PROJECT_BIN, "hook", event] };
1097
- if (runner.kind === "vendored") return { command: "node", args: [VENDORED_BIN, "hook", event] };
1098
- if (isScript(runner.path)) return { command: "node", args: [runner.path, "hook", event] };
1099
- return { command: runner.path, args: ["hook", event] };
1100
- };
1101
- var hookHandler = (runner, spec2, failClosedSupported) => {
1102
- const failure4 = spec2.guard && failClosedSupported ? { onFailure: "block" } : {};
1103
- const { command, args: args2 } = execForm(runner, spec2.event);
1104
- return { type: "command", command, args: args2, timeout: spec2.timeout, ...failure4 };
1105
- };
1106
- var OWN_SHELL = /(^|[\s/"'])finitemonkey(@[^\s"]+|\.mjs|\.cmd|\.exe)?"?\s+hook\s/;
1107
- var OWN_SCRIPT = /finitemonkey(\.mjs|\.cmd|\.exe)?$/;
1108
- var stringsOf = (value) => Array.isArray(value) ? value.filter((item2) => typeof item2 === "string") : [];
1109
- var isOwnHandler = (handler) => {
1110
- if (!isRecord(handler)) return false;
1111
- const command = handler["command"];
1112
- if (typeof command !== "string") return false;
1113
- const args2 = stringsOf(handler["args"]);
1114
- if (args2.length === 0) return OWN_SHELL.test(`${command} `);
1115
- const hookAt = args2.indexOf("hook");
1116
- if (hookAt < 0) return false;
1117
- const target2 = hookAt === 0 ? command : args2[hookAt - 1] ?? "";
1118
- return OWN_SCRIPT.test(target2);
1119
- };
1158
+ var hasMarker = (text8) => text8.includes(GENERATED_MARKER);
1120
1159
 
1121
1160
  // src/install/skill-template.ts
1122
1161
  var cliCommand = (runner, args2) => {
@@ -1181,7 +1220,7 @@ var lefthookBody = (runner) => [
1181
1220
  ` run: ${cliCommand(runner, "push check {1}")}`,
1182
1221
  " use_stdin: true"
1183
1222
  ];
1184
- var definesHook = (text7) => GIT_HOOK_NAMES.some((hook) => new RegExp(`^${hook}:`, "m").test(withoutManagedBlock(text7, HOOK_MARKERS)));
1223
+ var definesHook = (text8) => GIT_HOOK_NAMES.some((hook) => new RegExp(`^${hook}:`, "m").test(withoutManagedBlock(text8, HOOK_MARKERS)));
1185
1224
  var lefthookItem = (project, runner, path) => {
1186
1225
  const before = project.read(path);
1187
1226
  const after = withManagedBlock(before, HOOK_MARKERS, lefthookBody(runner));
@@ -1206,8 +1245,8 @@ var preCommitBody = (runner, indent) => {
1206
1245
  ];
1207
1246
  };
1208
1247
  var TOP_KEY = /^[A-Za-z_][\w-]*:/;
1209
- var reposIsLast = (text7) => {
1210
- const keys = withoutManagedBlock(text7, HOOK_MARKERS).split(/\r?\n/).filter((line2) => TOP_KEY.test(line2));
1248
+ var reposIsLast = (text8) => {
1249
+ const keys = withoutManagedBlock(text8, HOOK_MARKERS).split(/\r?\n/).filter((line2) => TOP_KEY.test(line2));
1211
1250
  return keys.at(-1)?.startsWith("repos:") === true;
1212
1251
  };
1213
1252
  var preCommitItem = (project, runner) => {
@@ -1245,8 +1284,8 @@ var DEFAULT_POLICY = {
1245
1284
  guards: {}
1246
1285
  };
1247
1286
  var pick = (options, value, fallback) => options.find((option) => option === value) ?? fallback;
1248
- var parsePolicy = (text7) => {
1249
- const tree = parseConfigYaml(text7);
1287
+ var parsePolicy = (text8) => {
1288
+ const tree = parseConfigYaml(text8);
1250
1289
  const raw = configAt(tree, "promoteAfter");
1251
1290
  const promoteAfter = typeof raw === "string" ? Number.parseInt(raw, 10) : Number.NaN;
1252
1291
  const guards = Object.fromEntries(
@@ -1265,8 +1304,8 @@ var parsePolicy = (text7) => {
1265
1304
  };
1266
1305
  var policyPath = (root) => toAbsolutePath(join3(root, POLICY_FILE));
1267
1306
  var loadPolicy = (runtime, root) => {
1268
- const text7 = runtime.readText(policyPath(root));
1269
- return text7.ok ? parsePolicy(text7.value) : DEFAULT_POLICY;
1307
+ const text8 = runtime.readText(policyPath(root));
1308
+ return text8.ok ? parsePolicy(text8.value) : DEFAULT_POLICY;
1270
1309
  };
1271
1310
 
1272
1311
  // src/review/agents.ts
@@ -1331,16 +1370,16 @@ var agentMarkdown = (spec2, marker) => [
1331
1370
  // src/install/agents-block.ts
1332
1371
  var MARKDOWN_MARKERS = { begin: MD_BLOCK_BEGIN, end: MD_BLOCK_END };
1333
1372
  var IMPORT_LINE = /^@AGENTS\.md[ \t]*$/m;
1334
- var withAgentsBlock = (text7, runner, lang) => withManagedBlock(
1335
- text7,
1373
+ var withAgentsBlock = (text8, runner, lang) => withManagedBlock(
1374
+ text8,
1336
1375
  MARKDOWN_MARKERS,
1337
1376
  TEMPLATES[lang].agentsBlock({
1338
1377
  gate: cliCommand(runner, "gate --fast --json"),
1339
1378
  doctor: cliCommand(runner, "doctor --json")
1340
1379
  })
1341
1380
  );
1342
- var withAgentsImport = (text7) => text7 !== void 0 && IMPORT_LINE.test(withoutManagedBlock(text7, MARKDOWN_MARKERS)) ? text7 : withManagedBlock(text7, MARKDOWN_MARKERS, [AGENTS_IMPORT]);
1343
- var withoutMarkdownBlock = (text7) => withoutManagedBlock(text7, MARKDOWN_MARKERS);
1381
+ var withAgentsImport = (text8) => text8 !== void 0 && IMPORT_LINE.test(withoutManagedBlock(text8, MARKDOWN_MARKERS)) ? text8 : withManagedBlock(text8, MARKDOWN_MARKERS, [AGENTS_IMPORT]);
1382
+ var withoutMarkdownBlock = (text8) => withoutManagedBlock(text8, MARKDOWN_MARKERS);
1344
1383
 
1345
1384
  // src/install/ask-floor.ts
1346
1385
  var DEFAULT_BRANCHES = ["main", "master"];
@@ -1416,7 +1455,7 @@ var ruleFor = (raw) => {
1416
1455
  };
1417
1456
  var claudeignoreRules = (texts2) => [
1418
1457
  ...new Set(
1419
- texts2.flatMap((text7) => (text7 ?? "").split("\n")).map(ruleFor).filter((rule) => rule !== void 0)
1458
+ texts2.flatMap((text8) => (text8 ?? "").split("\n")).map(ruleFor).filter((rule) => rule !== void 0)
1420
1459
  )
1421
1460
  ];
1422
1461
 
@@ -1449,12 +1488,12 @@ import { join as join4 } from "node:path";
1449
1488
 
1450
1489
  // src/install/json-text.ts
1451
1490
  var DEFAULT_STYLE = { indent: " ", finalNewline: true };
1452
- var styleOf = (text7) => {
1453
- const indented = /\n([ \t]+)\S/.exec(text7);
1454
- return { indent: indented?.[1] ?? DEFAULT_STYLE.indent, finalNewline: text7.endsWith("\n") };
1491
+ var styleOf = (text8) => {
1492
+ const indented = /\n([ \t]+)\S/.exec(text8);
1493
+ return { indent: indented?.[1] ?? DEFAULT_STYLE.indent, finalNewline: text8.endsWith("\n") };
1455
1494
  };
1456
1495
  var renderJson = (value, style) => `${JSON.stringify(value, null, style.indent)}${style.finalNewline ? "\n" : ""}`;
1457
- var parseSettings = (text7) => text7 === void 0 || text7.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text7);
1496
+ var parseSettings = (text8) => text8 === void 0 || text8.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text8);
1458
1497
  var asList = (value) => Array.isArray(value) ? value : [];
1459
1498
  var asRecord = (value) => isRecord(value) ? value : {};
1460
1499
  var setKey = (record3, key, value) => {
@@ -1491,8 +1530,8 @@ var WORKFLOW_DIRS = [
1491
1530
  { dir: "openspec", name: "openspec" }
1492
1531
  ];
1493
1532
  var fromPackageJson = (project) => {
1494
- const text7 = project.read("package.json");
1495
- const parsed = text7 === void 0 ? void 0 : parseJsonObject(text7);
1533
+ const text8 = project.read("package.json");
1534
+ const parsed = text8 === void 0 ? void 0 : parseJsonObject(text8);
1496
1535
  const field = parsed?.ok === true ? parsed.value["packageManager"] : void 0;
1497
1536
  if (typeof field !== "string") return void 0;
1498
1537
  return PACKAGE_MANAGERS.find(({ name }) => field.startsWith(`${name}@`))?.name;
@@ -1508,9 +1547,9 @@ var allHandlers = (settings) => {
1508
1547
  const hooks = settings["hooks"];
1509
1548
  return isRecord(hooks) ? Object.keys(hooks).flatMap((event) => handlersOf(settings, event)) : [];
1510
1549
  };
1511
- var settingsInfo = (text7) => {
1512
- if (text7 === void 0) return { exists: false, valid: true, userHooks: 0, ownHooks: 0 };
1513
- const parsed = text7.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text7);
1550
+ var settingsInfo = (text8) => {
1551
+ if (text8 === void 0) return { exists: false, valid: true, userHooks: 0, ownHooks: 0 };
1552
+ const parsed = text8.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text8);
1514
1553
  if (!parsed.ok) return { exists: true, valid: false, userHooks: 0, ownHooks: 0 };
1515
1554
  const handlers = allHandlers(parsed.value);
1516
1555
  const own = handlers.filter(isOwnHandler).length;
@@ -1524,8 +1563,8 @@ var pluginAuthorities = (settings, source) => {
1524
1563
  return LOOP_PLUGINS.includes(name) ? [{ kind: "loop-plugin", source, name }] : [];
1525
1564
  });
1526
1565
  };
1527
- var scopeAuthorities = (text7, source) => {
1528
- const parsed = text7 === void 0 ? void 0 : parseJsonObject(text7);
1566
+ var scopeAuthorities = (text8, source) => {
1567
+ const parsed = text8 === void 0 ? void 0 : parseJsonObject(text8);
1529
1568
  if (parsed?.ok !== true) return [];
1530
1569
  const stops = ["Stop", "SubagentStop"].flatMap(
1531
1570
  (event) => handlersOf(parsed.value, event).filter((handler) => !isOwnHandler(handler)).map((handler) => {
@@ -1539,8 +1578,8 @@ var userSettingsText = (project, env) => {
1539
1578
  const home = env["CLAUDE_CONFIG_DIR"] ?? env["HOME"] ?? env["USERPROFILE"];
1540
1579
  if (home === void 0) return void 0;
1541
1580
  const file = env["CLAUDE_CONFIG_DIR"] === void 0 ? join4(home, ".claude", "settings.json") : join4(home, "settings.json");
1542
- const text7 = project.runtime.readText(toAbsolutePath(file));
1543
- return text7.ok ? text7.value : void 0;
1581
+ const text8 = project.runtime.readText(toAbsolutePath(file));
1582
+ return text8.ok ? text8.value : void 0;
1544
1583
  };
1545
1584
  var detectAuthorities = (project, env) => [
1546
1585
  ...scopeAuthorities(project.read(SETTINGS_FILE), "project"),
@@ -1560,8 +1599,8 @@ var chooseRunner = (project, bin) => {
1560
1599
  // src/install/gitignore-block.ts
1561
1600
  var MARKERS = { begin: BLOCK_BEGIN, end: BLOCK_END };
1562
1601
  var body = () => [`${STATE_DIR}/*`, ...COMMITTED_STATE.map((path) => `!${STATE_DIR}/${path}`)];
1563
- var withBlock = (text7) => withManagedBlock(text7, MARKERS, body());
1564
- var withoutBlock = (text7) => withoutManagedBlock(text7, MARKERS);
1602
+ var withBlock = (text8) => withManagedBlock(text8, MARKERS, body());
1603
+ var withoutBlock = (text8) => withoutManagedBlock(text8, MARKERS);
1565
1604
 
1566
1605
  // src/core/semver.ts
1567
1606
  var SEMVER = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -1604,7 +1643,7 @@ var pendingMigrations = (migrations, from, to) => {
1604
1643
  };
1605
1644
  var migrateItem = (item2, previous, steps) => {
1606
1645
  if (steps.length === 0 || item2.before === void 0) return item2;
1607
- const migrated = steps.reduce((text7, step3) => step3(text7), item2.after);
1646
+ const migrated = steps.reduce((text8, step3) => step3(text8), item2.after);
1608
1647
  if (migrated === item2.after) return item2;
1609
1648
  const recorded = previous.entries.find((entry2) => entry2.path === item2.path)?.hash;
1610
1649
  if (recorded !== hashText(item2.before)) {
@@ -1696,6 +1735,15 @@ var withPermissions = (settings, lists) => {
1696
1735
  var missing = (wanted, existing) => wanted.filter((rule) => !existing.includes(rule));
1697
1736
  var withoutAdded = (existing, added) => existing.filter((rule) => typeof rule !== "string" || !added.includes(rule));
1698
1737
  var ownHookMatcher = (recorded) => (handler) => isOwnHandler(handler) || recorded.some((own) => JSON.stringify(own) === JSON.stringify(handler));
1738
+ var statusLineFor = (base, wanted) => {
1739
+ if (wanted.statusLine === void 0 || isOwnStatusLine(base["statusLine"])) return void 0;
1740
+ return { previous: base["statusLine"] ?? null, wrap: wanted.statusLine.wrap ?? null };
1741
+ };
1742
+ var restoreStatusLine = (settings, added) => {
1743
+ if (added.statusLine === void 0 || !isOwnStatusLine(settings["statusLine"])) return settings;
1744
+ const previous = added.statusLine.previous;
1745
+ return setKey(settings, "statusLine", previous === null ? void 0 : previous);
1746
+ };
1699
1747
  var mergeSettings = (current2, wanted, recorded) => {
1700
1748
  const schema = Object.keys(current2).length === 0;
1701
1749
  const base = schema ? { $schema: SETTINGS_SCHEMA_URL } : current2;
@@ -1719,7 +1767,18 @@ var mergeSettings = (current2, wanted, recorded) => {
1719
1767
  if (marketplace) settings = withEntry(settings, "extraKnownMarketplaces", MARKETPLACE_KEY, MARKETPLACE_ENTRY);
1720
1768
  if (plugin) settings = withEntry(settings, "enabledPlugins", PLUGIN_ID, true);
1721
1769
  if (language !== void 0) settings = { ...settings, language };
1722
- const added = { schema, deny: deny2, ask: ask2, blockReads, attribution, marketplace, plugin };
1770
+ const status = statusLineFor(base, wanted);
1771
+ if (status !== void 0) settings = { ...settings, statusLine: wanted.statusLine?.entry };
1772
+ const added = {
1773
+ schema,
1774
+ deny: deny2,
1775
+ ask: ask2,
1776
+ blockReads,
1777
+ attribution,
1778
+ marketplace,
1779
+ plugin,
1780
+ ...status === void 0 ? {} : { statusLine: status }
1781
+ };
1723
1782
  return { settings, added: language === void 0 ? added : { ...added, language } };
1724
1783
  };
1725
1784
  var sameAttribution = (value) => JSON.stringify(value) === JSON.stringify(ATTRIBUTION_OFF);
@@ -1739,15 +1798,20 @@ var unmergeSettings = (current2, added, recorded) => {
1739
1798
  if (added.language !== void 0 && settings["language"] === added.language) {
1740
1799
  settings = setKey(settings, "language", void 0);
1741
1800
  }
1801
+ settings = restoreStatusLine(settings, added);
1742
1802
  if (added.schema && Object.keys(settings).length === 1 && settings["$schema"] === SETTINGS_SCHEMA_URL) return {};
1743
1803
  return settings;
1744
1804
  };
1745
1805
  var ownHandlersIn = (settings) => Object.values(asRecord(settings["hooks"])).flatMap((groups) => asList(groups)).flatMap((group) => asList(asRecord(group)["hooks"])).filter(isOwnHandler).length;
1746
1806
 
1807
+ // src/install/statusline-search.ts
1808
+ var LOCAL_SETTINGS_FILE = ".claude/settings.local.json";
1809
+ var existingStatusLine = (project, projectSettings, env) => statusLineCommand(projectSettings["statusLine"]) ?? statusLineCommand(statusLineIn(project.read(LOCAL_SETTINGS_FILE))) ?? statusLineCommand(statusLineIn(userSettingsText(project, env)));
1810
+
1747
1811
  // src/install/vendor.ts
1748
- var isBundle = (text7) => text7.startsWith("#!/usr/bin/env node") && text7.includes("finitemonkey");
1749
- var isOwnPackage = (text7) => {
1750
- const parsed = parseJsonObject(text7);
1812
+ var isBundle = (text8) => text8.startsWith("#!/usr/bin/env node") && text8.includes("finitemonkey");
1813
+ var isOwnPackage = (text8) => {
1814
+ const parsed = parseJsonObject(text8);
1751
1815
  return parsed.ok && parsed.value["name"] === "finitemonkey";
1752
1816
  };
1753
1817
  var vendoredPackage = (version) => `${JSON.stringify({ name: "finitemonkey", version, private: true, type: "module" }, null, 2)}
@@ -1778,9 +1842,9 @@ var detect = (project, gitRoot2, input) => ({
1778
1842
  location: locationOf(project.root, input.env)
1779
1843
  });
1780
1844
  var readPreviousLock = (project) => {
1781
- const text7 = project.read(LOCK_FILE);
1782
- if (text7 === void 0) return accept(void 0);
1783
- const parsed = parseLock(text7);
1845
+ const text8 = project.read(LOCK_FILE);
1846
+ if (text8 === void 0) return accept(void 0);
1847
+ const parsed = parseLock(text8);
1784
1848
  return parsed.ok ? accept(parsed.value) : reject({ kind: "lock", detail: parsed.error });
1785
1849
  };
1786
1850
  var actionFor = (before, after, existing) => {
@@ -1796,7 +1860,8 @@ var joinAdded = (previous, next2) => previous === void 0 ? next2 : {
1796
1860
  blockReads: previous.blockReads || next2.blockReads,
1797
1861
  marketplace: previous.marketplace || next2.marketplace,
1798
1862
  plugin: previous.plugin || next2.plugin,
1799
- ...(previous.language ?? next2.language) === void 0 ? {} : { language: previous.language ?? next2.language }
1863
+ ...(previous.language ?? next2.language) === void 0 ? {} : { language: previous.language ?? next2.language },
1864
+ ...(previous.statusLine ?? next2.statusLine) === void 0 ? {} : { statusLine: previous.statusLine ?? next2.statusLine }
1800
1865
  };
1801
1866
  var wantedHooks = (detection) => Object.fromEntries(
1802
1867
  INSTALLED_HOOKS.map((spec2) => [spec2.event, hookHandler(detection.runner, spec2, detection.claude.failClosed)])
@@ -1812,7 +1877,8 @@ var settingsItem = (project, detection, input, previous) => {
1812
1877
  ask: ASK_RULES,
1813
1878
  attributionOff: input.preset === "gabriel",
1814
1879
  marketplace: input.marketplace,
1815
- language: input.claudeLanguage
1880
+ language: input.claudeLanguage,
1881
+ statusLine: { entry: ownStatusLine(detection.runner), wrap: existingStatusLine(project, current2.value, input.env) }
1816
1882
  };
1817
1883
  const merged2 = mergeSettings(current2.value, wanted, previous?.handlers ?? []);
1818
1884
  const after = renderJson(merged2.settings, before === void 0 ? DEFAULT_STYLE : styleOf(before));
@@ -1827,7 +1893,7 @@ var settingsItem = (project, detection, input, previous) => {
1827
1893
  });
1828
1894
  };
1829
1895
  var markdownItems = (project, runner, lang) => [
1830
- { path: AGENTS_FILE, render: (text7) => withAgentsBlock(text7, runner, lang) },
1896
+ { path: AGENTS_FILE, render: (text8) => withAgentsBlock(text8, runner, lang) },
1831
1897
  { path: CLAUDE_FILE, render: withAgentsImport }
1832
1898
  ].map(({ path, render: render3 }) => {
1833
1899
  const before = project.read(path);
@@ -1857,7 +1923,7 @@ var playwrightVerifier = (project) => {
1857
1923
  const wanted = configAt(parseConfigYaml(project.read(CONFIG_FILE) ?? ""), "review", "playwright") === "true";
1858
1924
  if (!wanted) return [];
1859
1925
  const template = agentMarkdown(withPlaywright(VERIFIER), GENERATED_MARKER);
1860
- return [{ path: PLAYWRIGHT_VERIFIER, template, isOurs: (text7) => text7.includes(GENERATED_MARKER) }];
1926
+ return [{ path: PLAYWRIGHT_VERIFIER, template, isOurs: (text8) => text8.includes(GENERATED_MARKER) }];
1861
1927
  };
1862
1928
  var generatedFiles = (project, input, runner, vendor) => {
1863
1929
  const own = [
@@ -1902,9 +1968,9 @@ var needsWrite = (plan) => hasChanges(plan) || plan.previous === void 0;
1902
1968
  // src/install/apply.ts
1903
1969
  var stamp = (iso) => iso.replace(/[:.]/g, "-");
1904
1970
  var ensureParent = (project, relative11) => project.runtime.mkdir(project.abs(dirname3(relative11)));
1905
- var writeText = (project, relative11, text7) => {
1971
+ var writeText = (project, relative11, text8) => {
1906
1972
  const parent = ensureParent(project, relative11);
1907
- const written = parent.ok ? project.runtime.writeAtomic(project.abs(relative11), text7) : parent;
1973
+ const written = parent.ok ? project.runtime.writeAtomic(project.abs(relative11), text8) : parent;
1908
1974
  return written.ok ? accept(void 0) : reject({ path: relative11, detail: written.error });
1909
1975
  };
1910
1976
  var restore = (project, items) => {
@@ -2052,8 +2118,8 @@ var projectAt = (root, runtime) => {
2052
2118
  runtime,
2053
2119
  abs,
2054
2120
  read: (relative11) => {
2055
- const text7 = runtime.readText(abs(relative11));
2056
- return text7.ok ? text7.value : void 0;
2121
+ const text8 = runtime.readText(abs(relative11));
2122
+ return text8.ok ? text8.value : void 0;
2057
2123
  },
2058
2124
  exists: (relative11) => runtime.stat(abs(relative11)) !== void 0,
2059
2125
  isDir: (relative11) => runtime.stat(abs(relative11))?.kind === "dir"
@@ -2126,8 +2192,8 @@ var invalidPreset = (raw) => problem("invalid_arguments", "error.invalid_preset"
2126
2192
  });
2127
2193
  var selfBundle = (ctx) => {
2128
2194
  if (!isBundlePath(ctx.runtime.selfPath)) return void 0;
2129
- const text7 = ctx.runtime.readText(ctx.runtime.selfPath);
2130
- return text7.ok ? text7.value : void 0;
2195
+ const text8 = ctx.runtime.readText(ctx.runtime.selfPath);
2196
+ return text8.ok ? text8.value : void 0;
2131
2197
  };
2132
2198
  var INHERITED = /* @__PURE__ */ new Set(["default", "config", "claude-settings"]);
2133
2199
  var planInputFor = (ctx, preset, bin, marketplace) => ({
@@ -2231,13 +2297,13 @@ var isPlaceholder = (value) => {
2231
2297
  var isTestOrLockPath = (path) => path !== void 0 && (LOCKFILES.test(toPosixPath(path)) || TEST_PATH.test(toPosixPath(path)));
2232
2298
 
2233
2299
  // src/secrets/entropy.ts
2234
- var shannonEntropy = (text7) => {
2235
- if (text7.length === 0) return 0;
2300
+ var shannonEntropy = (text8) => {
2301
+ if (text8.length === 0) return 0;
2236
2302
  const counts = /* @__PURE__ */ new Map();
2237
- for (const char of text7) counts.set(char, (counts.get(char) ?? 0) + 1);
2303
+ for (const char of text8) counts.set(char, (counts.get(char) ?? 0) + 1);
2238
2304
  let entropy = 0;
2239
2305
  for (const count4 of counts.values()) {
2240
- const p = count4 / text7.length;
2306
+ const p = count4 / text8.length;
2241
2307
  entropy -= p * Math.log2(p);
2242
2308
  }
2243
2309
  return entropy;
@@ -2245,7 +2311,7 @@ var shannonEntropy = (text7) => {
2245
2311
 
2246
2312
  // src/secrets/fingerprint.ts
2247
2313
  import { createHash as createHash2 } from "node:crypto";
2248
- var sha256Hex = (text7) => createHash2("sha256").update(text7).digest("hex");
2314
+ var sha256Hex = (text8) => createHash2("sha256").update(text8).digest("hex");
2249
2315
  var fingerprintOf = (secret) => sha256Hex(`finitemonkey:secret:${secret}`).slice(0, 16);
2250
2316
  var ALNUM = /[A-Za-z0-9]/g;
2251
2317
  var MIN_TAIL_LENGTH = 12;
@@ -2255,9 +2321,9 @@ var tailOf = (secret) => {
2255
2321
  };
2256
2322
 
2257
2323
  // src/secrets/lines.ts
2258
- var lineLocator = (text7) => {
2324
+ var lineLocator = (text8) => {
2259
2325
  const starts = [0];
2260
- for (let index = text7.indexOf("\n"); index !== -1; index = text7.indexOf("\n", index + 1)) starts.push(index + 1);
2326
+ for (let index = text8.indexOf("\n"); index !== -1; index = text8.indexOf("\n", index + 1)) starts.push(index + 1);
2261
2327
  return (offset) => {
2262
2328
  let low = 0;
2263
2329
  let high = starts.length - 1;
@@ -2551,12 +2617,12 @@ var hasRandomRun = (secret, length) => (secret.match(new RegExp(`[A-Za-z0-9]{${S
2551
2617
  );
2552
2618
  var accepted = (rule, secret) => secret.length >= rule.minLength && (!rule.requireDigit || DIGIT.test(secret)) && (rule.minRandomRun === 0 || hasRandomRun(secret, rule.minRandomRun)) && (!rule.placeholders || !isPlaceholder(secret)) && (rule.minEntropy === 0 || shannonEntropy(secret) >= rule.minEntropy);
2553
2619
  var NAME_CHAR = /[A-Za-z0-9_.-]/;
2554
- var nameAround = (text7, at) => {
2620
+ var nameAround = (text8, at) => {
2555
2621
  let start = at;
2556
- while (start > 0 && NAME_CHAR.test(text7.charAt(start - 1))) start -= 1;
2622
+ while (start > 0 && NAME_CHAR.test(text8.charAt(start - 1))) start -= 1;
2557
2623
  let end = at;
2558
- while (end < text7.length && NAME_CHAR.test(text7.charAt(end))) end += 1;
2559
- return text7.slice(start, end).toLowerCase();
2624
+ while (end < text8.length && NAME_CHAR.test(text8.charAt(end))) end += 1;
2625
+ return text8.slice(start, end).toLowerCase();
2560
2626
  };
2561
2627
  var ignoredName = (rule, match) => rule.ignoredNames.length > 0 && rule.ignoredNames.includes(nameAround(match.input, match.index));
2562
2628
  var spanOf = (rule, match) => {
@@ -2566,9 +2632,9 @@ var spanOf = (rule, match) => {
2566
2632
  const secret = match.input.slice(start, end);
2567
2633
  return accepted(rule, secret) ? { rule, start, end, secret } : void 0;
2568
2634
  };
2569
- var ruleSpans = (rule, text7) => {
2635
+ var ruleSpans = (rule, text8) => {
2570
2636
  const spans = [];
2571
- for (const match of text7.matchAll(rule.pattern)) {
2637
+ for (const match of text8.matchAll(rule.pattern)) {
2572
2638
  const span = spanOf(rule, match);
2573
2639
  if (span !== void 0) spans.push(span);
2574
2640
  }
@@ -2594,31 +2660,31 @@ var toFinding = (span, locate2) => {
2594
2660
  tail: tailOf(span.secret)
2595
2661
  };
2596
2662
  };
2597
- var scanText = (text7, options = {}) => {
2598
- if (text7.length === 0) return [];
2599
- const rules = applicable(options.rules ?? RULES, text7.toLowerCase(), options.path);
2663
+ var scanText = (text8, options = {}) => {
2664
+ if (text8.length === 0) return [];
2665
+ const rules = applicable(options.rules ?? RULES, text8.toLowerCase(), options.path);
2600
2666
  if (rules.length === 0) return [];
2601
2667
  const taken = [];
2602
2668
  for (const rule of rules) {
2603
- for (const span of ruleSpans(rule, text7)) if (!overlaps(taken, span)) taken.push(span);
2669
+ for (const span of ruleSpans(rule, text8)) if (!overlaps(taken, span)) taken.push(span);
2604
2670
  }
2605
2671
  if (taken.length === 0) return [];
2606
- const locate2 = lineLocator(text7);
2672
+ const locate2 = lineLocator(text8);
2607
2673
  return taken.sort((a, b) => a.start - b.start).map((span) => toFinding(span, locate2));
2608
2674
  };
2609
2675
 
2610
2676
  // src/mask/mask.ts
2611
2677
  var maskToken = (finding4) => `«secret:${finding4.ruleId}:${finding4.tail}»`;
2612
- var maskText = (text7) => {
2613
- const findings = scanText(text7, { rules: CREDENTIAL_RULES });
2614
- if (findings.length === 0) return text7;
2678
+ var maskText = (text8) => {
2679
+ const findings = scanText(text8, { rules: CREDENTIAL_RULES });
2680
+ if (findings.length === 0) return text8;
2615
2681
  let out = "";
2616
2682
  let cursor = 0;
2617
2683
  for (const finding4 of findings) {
2618
- out += text7.slice(cursor, finding4.start) + maskToken(finding4);
2684
+ out += text8.slice(cursor, finding4.start) + maskToken(finding4);
2619
2685
  cursor = finding4.end;
2620
2686
  }
2621
- return out + text7.slice(cursor);
2687
+ return out + text8.slice(cursor);
2622
2688
  };
2623
2689
  var maskDeep = (value) => {
2624
2690
  if (typeof value === "string") return maskText(value);
@@ -2836,14 +2902,14 @@ var toolVersion = (env, command) => {
2836
2902
  return result.ok ? result.stdout.trim().split("\n")[0] ?? "" : "not found";
2837
2903
  };
2838
2904
  var lastHookFailures = (env) => {
2839
- const text7 = env.runtime.readText(fmPaths(env.root).log);
2840
- if (!text7.ok) return [];
2841
- return text7.value.split("\n").filter((line2) => {
2905
+ const text8 = env.runtime.readText(fmPaths(env.root).log);
2906
+ if (!text8.ok) return [];
2907
+ return text8.value.split("\n").filter((line2) => {
2842
2908
  const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
2843
2909
  return parsed?.ok === true && isRecord(parsed.value) && parsed.value["event"] === HOOK_FAILURE_EVENT;
2844
2910
  }).slice(-MAX_FAILURES);
2845
2911
  };
2846
- var withoutHome = (text7, home) => home.length > 1 ? text7.split(home).join("~") : text7;
2912
+ var withoutHome = (text8, home) => home.length > 1 ? text8.split(home).join("~") : text8;
2847
2913
  var reportMarkdown = (env, ctx, envelope) => {
2848
2914
  const failures = lastHookFailures(env);
2849
2915
  const lines3 = [
@@ -2879,9 +2945,9 @@ var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
2879
2945
  `;
2880
2946
  var projectOwnsHooks = (ctx) => {
2881
2947
  const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
2882
- const text7 = ctx.runtime.readText(toAbsolutePath(join8(root, ...SETTINGS_FILE.split("/"))));
2883
- if (!text7.ok) return false;
2884
- const parsed = parseJsonObject(text7.value);
2948
+ const text8 = ctx.runtime.readText(toAbsolutePath(join8(root, ...SETTINGS_FILE.split("/"))));
2949
+ if (!text8.ok) return false;
2950
+ const parsed = parseJsonObject(text8.value);
2885
2951
  return parsed.ok && ownHandlersIn(parsed.value) > 0;
2886
2952
  };
2887
2953
 
@@ -2915,8 +2981,8 @@ var settingsPaths = (env) => {
2915
2981
  ];
2916
2982
  };
2917
2983
  var readSettings = (env) => settingsPaths(env).flatMap(({ scope: scope2, path }) => {
2918
- const text7 = env.runtime.readText(path);
2919
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
2984
+ const text8 = env.runtime.readText(path);
2985
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
2920
2986
  return parsed?.ok === true ? [{ scope: scope2, path, settings: parsed.value }] : [];
2921
2987
  });
2922
2988
  var scopesWith = (all, test) => all.filter(({ settings }) => test(settings)).map(({ scope: scope2 }) => scope2);
@@ -3173,6 +3239,36 @@ var deadlineFor = (event, raw) => {
3173
3239
  return EVENT_DEADLINES[event] ?? DEFAULT_HOOK_DEADLINE_MS;
3174
3240
  };
3175
3241
 
3242
+ // src/gate/hooks/common.ts
3243
+ import { dirname as dirname5, join as join11 } from "node:path";
3244
+ var DEFAULT_GATE_BUDGET_MS = 3e5;
3245
+ var sessionCwd = (input) => input.payload.cwd === void 0 ? input.cwd : toAbsolutePath(input.payload.cwd, input.cwd);
3246
+ var MAX_DEPTH = 64;
3247
+ var findRepoRoot = (runtime, start) => {
3248
+ let current2 = start;
3249
+ for (let depth = 0; depth < MAX_DEPTH; depth += 1) {
3250
+ if (runtime.stat(toAbsolutePath(join11(current2, ".git"))) !== void 0) return current2;
3251
+ const parent = toAbsolutePath(dirname5(current2));
3252
+ if (parent === current2) return void 0;
3253
+ current2 = parent;
3254
+ }
3255
+ return void 0;
3256
+ };
3257
+ var projectPaths = (input) => {
3258
+ const root = findRepoRoot(input.runtime, sessionCwd(input));
3259
+ return root === void 0 ? void 0 : fmPaths(root);
3260
+ };
3261
+ var gateBudget = (input) => {
3262
+ const parsed = Number.parseInt(input.env["FINITEMONKEY_GATE_BUDGET_MS"] ?? "", 10);
3263
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_GATE_BUDGET_MS;
3264
+ };
3265
+ var allowWith = (output) => ({ kind: "allow", output });
3266
+ var contextOutput = (event, additionalContext, systemMessage) => ({
3267
+ hookSpecificOutput: { hookEventName: event, additionalContext },
3268
+ ...systemMessage === void 0 ? {} : { systemMessage }
3269
+ });
3270
+ var human = (text8) => `finitemonkey: ${text8}`;
3271
+
3176
3272
  // src/hooks/fields.ts
3177
3273
  var textField = (record3, ...names) => {
3178
3274
  for (const name of names) {
@@ -3202,7 +3298,7 @@ var editsOf = (input) => {
3202
3298
  });
3203
3299
  };
3204
3300
  var applyEdits = (before, edits) => edits.reduce(
3205
- (text7, edit) => edit.all ? text7.split(edit.oldText).join(edit.newText) : text7.replace(edit.oldText, () => edit.newText),
3301
+ (text8, edit) => edit.all ? text8.split(edit.oldText).join(edit.newText) : text8.replace(edit.oldText, () => edit.newText),
3206
3302
  before
3207
3303
  );
3208
3304
 
@@ -3222,7 +3318,7 @@ var familyOf = (base) => {
3222
3318
  if (found === void 0) throw new Error(`no reason family for ${base}`);
3223
3319
  return found;
3224
3320
  };
3225
- var capReason = (text7) => text7.length <= REASON_LIMIT ? text7 : `${text7.slice(0, REASON_LIMIT - 1)}…`;
3321
+ var capReason = (text8) => text8.length <= REASON_LIMIT ? text8 : `${text8.slice(0, REASON_LIMIT - 1)}…`;
3226
3322
  var modelReason = (t, base, params2 = {}) => {
3227
3323
  const claim = `${base}.claim`;
3228
3324
  const requires = `${base}.requires`;
@@ -3249,9 +3345,9 @@ var textRecord = (value) => isRecord(value) ? Object.fromEntries(
3249
3345
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
3250
3346
  ) : {};
3251
3347
  var readJsonFile = (runtime, path) => {
3252
- const text7 = runtime.readText(path);
3253
- if (!text7.ok) return void 0;
3254
- const parsed = parseJsonObject(text7.value);
3348
+ const text8 = runtime.readText(path);
3349
+ if (!text8.ok) return void 0;
3350
+ const parsed = parseJsonObject(text8.value);
3255
3351
  return parsed.ok ? parsed.value : void 0;
3256
3352
  };
3257
3353
  var writeJsonFile = (runtime, path, value) => runtime.writeText(path, `${JSON.stringify({ schemaVersion: 1, ...value }, null, 2)}
@@ -3364,7 +3460,7 @@ var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
3364
3460
  import { posix } from "node:path";
3365
3461
 
3366
3462
  // src/shell/scan.ts
3367
- var cursorOf = (text7) => ({ text: text7, pos: 0, incomplete: false, subs: [] });
3463
+ var cursorOf = (text8) => ({ text: text8, pos: 0, incomplete: false, subs: [] });
3368
3464
  var peek = (cursor, offset = 0) => cursor.text.charAt(cursor.pos + offset);
3369
3465
  var atEnd = (cursor) => cursor.pos >= cursor.text.length;
3370
3466
  var skipQuoted = (cursor, quote) => {
@@ -3579,8 +3675,8 @@ var nextToken = (cursor, tokens, pending) => {
3579
3675
  tokens.push({ kind: "op", text: op });
3580
3676
  if (op === "\n") flushHeredocs(cursor, tokens, pending);
3581
3677
  };
3582
- var lexBash = (text7) => {
3583
- const cursor = cursorOf(text7.replace(/\r\n/g, "\n"));
3678
+ var lexBash = (text8) => {
3679
+ const cursor = cursorOf(text8.replace(/\r\n/g, "\n"));
3584
3680
  const tokens = [];
3585
3681
  const pending = [];
3586
3682
  skipBlank(cursor);
@@ -3728,9 +3824,9 @@ var next = (cursor, tokens, cmd) => {
3728
3824
  }
3729
3825
  const op = OPERATORS2.find((candidate) => cursor.text.startsWith(candidate, cursor.pos));
3730
3826
  if (op !== void 0 || cmd && peek(cursor) === "&") {
3731
- const text7 = op ?? "&";
3732
- cursor.pos += text7.length;
3733
- tokens.push({ kind: "op", text: text7 });
3827
+ const text8 = op ?? "&";
3828
+ cursor.pos += text8.length;
3829
+ tokens.push({ kind: "op", text: text8 });
3734
3830
  return;
3735
3831
  }
3736
3832
  if ((peek(cursor) === "&" || peek(cursor) === ".") && peek(cursor, 1) === " " && commandStart(tokens)) {
@@ -3739,8 +3835,8 @@ var next = (cursor, tokens, cmd) => {
3739
3835
  }
3740
3836
  tokens.push(word(cursor, cmd));
3741
3837
  };
3742
- var lexPowerShell = (text7, cmd = false) => {
3743
- const cursor = cursorOf(text7.replace(/\r\n/g, "\n"));
3838
+ var lexPowerShell = (text8, cmd = false) => {
3839
+ const cursor = cursorOf(text8.replace(/\r\n/g, "\n"));
3744
3840
  const tokens = [];
3745
3841
  const skip = () => {
3746
3842
  while (peek(cursor) === " " || peek(cursor) === " ") cursor.pos += 1;
@@ -3939,11 +4035,11 @@ var unwrap = (initial, stdin) => {
3939
4035
  };
3940
4036
 
3941
4037
  // src/shell/normalize.ts
3942
- var MAX_DEPTH = 8;
4038
+ var MAX_DEPTH2 = 8;
3943
4039
  var ASSIGNMENT2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s;
3944
4040
  var PS_ENV = /^\$env:([A-Za-z_][A-Za-z0-9_]*)=?(.*)$/is;
3945
4041
  var CHANGES_DIR = /* @__PURE__ */ new Set(["cd", "pushd", "set-location", "push-location"]);
3946
- var lex = (text7, language) => language === "bash" ? lexBash(text7) : lexPowerShell(text7, language === "cmd");
4042
+ var lex = (text8, language) => language === "bash" ? lexBash(text8) : lexPowerShell(text8, language === "cmd");
3947
4043
  var isWindowsAbsolute = (path) => /^[A-Za-z]:\//.test(path);
3948
4044
  var joinDir = (current2, target2) => {
3949
4045
  const next2 = toPosixPath(target2);
@@ -4014,21 +4110,21 @@ var visit = (raw, language, context, depth, collector) => {
4014
4110
  emit(result.stage, raw, language, context, collector);
4015
4111
  for (const extra of result.extra) emit(extra, raw, language, context, collector);
4016
4112
  };
4017
- var collect = (text7, language, context, depth, collector) => {
4018
- if (depth > MAX_DEPTH) {
4113
+ var collect = (text8, language, context, depth, collector) => {
4114
+ if (depth > MAX_DEPTH2) {
4019
4115
  collector.incomplete = true;
4020
4116
  return;
4021
4117
  }
4022
- const lexed = lex(text7, language);
4118
+ const lexed = lex(text8, language);
4023
4119
  if (lexed.incomplete) collector.incomplete = true;
4024
4120
  for (const substitution of lexed.substitutions) {
4025
4121
  collect(substitution, language, { ...context }, depth + 1, collector);
4026
4122
  }
4027
4123
  for (const raw of rawCommands(lexed.tokens)) visit(raw, language, context, depth, collector);
4028
4124
  };
4029
- var normalizeCommand = (text7, dialect = "bash") => {
4125
+ var normalizeCommand = (text8, dialect = "bash") => {
4030
4126
  const collector = { out: [], incomplete: false };
4031
- collect(text7, dialect, { env: {}, dir: void 0, via: [] }, 0, collector);
4127
+ collect(text8, dialect, { env: {}, dir: void 0, via: [] }, 0, collector);
4032
4128
  return { commands: collector.out, incomplete: collector.incomplete };
4033
4129
  };
4034
4130
  var dialectOf = (toolName3) => toolName3 === "PowerShell" ? "powershell" : "bash";
@@ -4040,7 +4136,7 @@ var segmentsOf = (command) => normalizeCommand(command).commands.map((item2) =>
4040
4136
 
4041
4137
  // src/guards/shell.ts
4042
4138
  var HEREDOC = /^<<[-<]?$/;
4043
- var parseShell = (text7) => normalizeCommand(text7).commands.map((command) => ({
4139
+ var parseShell = (text8) => normalizeCommand(text8).commands.map((command) => ({
4044
4140
  env: command.env,
4045
4141
  argv: command.program === "" ? [] : [command.program, ...command.args],
4046
4142
  redirects: command.redirects.filter((redirect) => !HEREDOC.test(redirect.op)).map((redirect) => redirect.target)
@@ -4202,9 +4298,9 @@ var writtenPaths = (command) => {
4202
4298
  if (name === "git" && GIT_WRITERS.has(operands2[0] ?? "")) return [...command.redirects, ...operands2.slice(1)];
4203
4299
  return command.redirects;
4204
4300
  };
4205
- var mentioned = (text7) => {
4206
- if (/\.finitemonkey[\\/]allow/.test(text7)) return "allow_store";
4207
- if (/\.finitemonkey|\.gitleaksignore/.test(text7)) return "policy";
4301
+ var mentioned = (text8) => {
4302
+ if (/\.finitemonkey[\\/]allow/.test(text8)) return "allow_store";
4303
+ if (/\.finitemonkey|\.gitleaksignore/.test(text8)) return "policy";
4208
4304
  return void 0;
4209
4305
  };
4210
4306
  var bashSelfTargets = (root, commandLine) => parseShell(commandLine).flatMap(
@@ -4244,7 +4340,7 @@ var selfGuard = (input) => {
4244
4340
  };
4245
4341
 
4246
4342
  // src/guards/tests/hook.ts
4247
- import { join as join11, relative as relative3 } from "node:path";
4343
+ import { join as join12, relative as relative3 } from "node:path";
4248
4344
 
4249
4345
  // src/guards/tests/patterns.ts
4250
4346
  var JS_TEST = /(^|\/)(__tests__|__test__)\/|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)tests?\/.*\.[cm]?[jt]sx?$/;
@@ -4408,8 +4504,8 @@ var compareConfig = (path, before, after, accepted2) => {
4408
4504
  // src/guards/tests/hook.ts
4409
4505
  var localPath = (root, path) => toPosixPath(relative3(root, toAbsolutePath(path, root)));
4410
4506
  var read = (input, path) => {
4411
- const text7 = input.runtime.readText(path);
4412
- return text7.ok ? text7.value : void 0;
4507
+ const text8 = input.runtime.readText(path);
4508
+ return text8.ok ? text8.value : void 0;
4413
4509
  };
4414
4510
  var proposed = (tool, args2, before) => {
4415
4511
  if (tool === "Write") return textField(args2, "content") ?? "";
@@ -4434,7 +4530,7 @@ var shellViolations = (input, root) => {
4434
4530
  return parseShell(command).flatMap(
4435
4531
  (simple) => writtenPaths(simple).flatMap((path) => {
4436
4532
  const local = localPath(root, path);
4437
- const exists = input.runtime.stat(toAbsolutePath(join11(root, local))) !== void 0;
4533
+ const exists = input.runtime.stat(toAbsolutePath(join12(root, local))) !== void 0;
4438
4534
  if (!exists) return [];
4439
4535
  if (isProtectedConfig(local)) return [{ kind: "config_changed", path: local }];
4440
4536
  return isTestFile(local) ? [{ kind: "test_removed", path: local }] : [];
@@ -4455,38 +4551,6 @@ var testsGuard = (input) => {
4455
4551
 
4456
4552
  // src/look/reads.ts
4457
4553
  import { relative as relative4 } from "node:path";
4458
-
4459
- // src/gate/hooks/common.ts
4460
- import { dirname as dirname5, join as join12 } from "node:path";
4461
- var DEFAULT_GATE_BUDGET_MS = 3e5;
4462
- var sessionCwd = (input) => input.payload.cwd === void 0 ? input.cwd : toAbsolutePath(input.payload.cwd, input.cwd);
4463
- var MAX_DEPTH2 = 64;
4464
- var findRepoRoot = (runtime, start) => {
4465
- let current2 = start;
4466
- for (let depth = 0; depth < MAX_DEPTH2; depth += 1) {
4467
- if (runtime.stat(toAbsolutePath(join12(current2, ".git"))) !== void 0) return current2;
4468
- const parent = toAbsolutePath(dirname5(current2));
4469
- if (parent === current2) return void 0;
4470
- current2 = parent;
4471
- }
4472
- return void 0;
4473
- };
4474
- var projectPaths = (input) => {
4475
- const root = findRepoRoot(input.runtime, sessionCwd(input));
4476
- return root === void 0 ? void 0 : fmPaths(root);
4477
- };
4478
- var gateBudget = (input) => {
4479
- const parsed = Number.parseInt(input.env["FINITEMONKEY_GATE_BUDGET_MS"] ?? "", 10);
4480
- return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_GATE_BUDGET_MS;
4481
- };
4482
- var allowWith = (output) => ({ kind: "allow", output });
4483
- var contextOutput = (event, additionalContext, systemMessage) => ({
4484
- hookSpecificOutput: { hookEventName: event, additionalContext },
4485
- ...systemMessage === void 0 ? {} : { systemMessage }
4486
- });
4487
- var human = (text7) => `finitemonkey: ${text7}`;
4488
-
4489
- // src/look/reads.ts
4490
4554
  var READS_FILE = "look-reads.json";
4491
4555
  var KEEP = 500;
4492
4556
  var readLookReads = (runtime, paths) => {
@@ -4546,8 +4610,8 @@ var parseMarkers = (prompt) => {
4546
4610
  // src/policy/pause.ts
4547
4611
  var PAUSE_FILE = "pause.json";
4548
4612
  var readPause = (runtime, paths) => {
4549
- const text7 = runtime.readText(paths.cache(PAUSE_FILE));
4550
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
4613
+ const text8 = runtime.readText(paths.cache(PAUSE_FILE));
4614
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
4551
4615
  if (parsed?.ok !== true || !isRecord(parsed.value)) return void 0;
4552
4616
  const { sessionId, since, by } = parsed.value;
4553
4617
  if (typeof sessionId !== "string" || typeof since !== "string") return void 0;
@@ -4602,9 +4666,9 @@ var countIn = (value) => {
4602
4666
  if (Array.isArray(value)) return value.reduce((sum, item2) => sum + countIn(item2), 0);
4603
4667
  return isRecord(value) ? Object.values(value).reduce((sum, item2) => sum + countIn(item2), 0) : 0;
4604
4668
  };
4605
- var censusOf = (text7) => {
4606
- if (text7 === void 0) return { hooks: 0, disabled: false };
4607
- const parsed = parseJsonObject(text7);
4669
+ var censusOf = (text8) => {
4670
+ if (text8 === void 0) return { hooks: 0, disabled: false };
4671
+ const parsed = parseJsonObject(text8);
4608
4672
  if (!parsed.ok) return void 0;
4609
4673
  return { hooks: countIn(parsed.value["hooks"]), disabled: parsed.value["disableAllHooks"] === true };
4610
4674
  };
@@ -4614,8 +4678,8 @@ var settingsFiles = (paths, env) => [
4614
4678
  ...env["HOME"] === void 0 ? [] : [toAbsolutePath(join13(env["HOME"], ".claude", "settings.json"))]
4615
4679
  ];
4616
4680
  var read2 = (runtime, path) => {
4617
- const text7 = runtime.readText(path);
4618
- return text7.ok ? text7.value : void 0;
4681
+ const text8 = runtime.readText(path);
4682
+ return text8.ok ? text8.value : void 0;
4619
4683
  };
4620
4684
  var known = (runtime, paths) => {
4621
4685
  const raw = readJsonFile(runtime, paths.cache(CONFIG_FILE3))?.["hooks"];
@@ -4670,9 +4734,9 @@ var LOCKFILES2 = [
4670
4734
  ];
4671
4735
  var packageManager = (files) => LOCKFILES2.find(([file]) => files.exists(file))?.[1] ?? "npm";
4672
4736
  var scriptChecks = (files) => {
4673
- const text7 = files.read("package.json");
4674
- if (text7 === void 0) return [];
4675
- const parsed = parseJsonObject(text7);
4737
+ const text8 = files.read("package.json");
4738
+ if (text8 === void 0) return [];
4739
+ const parsed = parseJsonObject(text8);
4676
4740
  const scripts = parsed.ok ? parsed.value["scripts"] : void 0;
4677
4741
  if (!isRecord(scripts)) return [];
4678
4742
  const pm = packageManager(files);
@@ -4762,16 +4826,16 @@ var unescapeDouble = (body2) => {
4762
4826
  }
4763
4827
  return accept(out);
4764
4828
  };
4765
- var quotedEnd = (text7, start) => {
4766
- const quote = text7[start];
4767
- for (let index = start + 1; index < text7.length; index += 1) {
4768
- const char = text7[index];
4829
+ var quotedEnd = (text8, start) => {
4830
+ const quote = text8[start];
4831
+ for (let index = start + 1; index < text8.length; index += 1) {
4832
+ const char = text8[index];
4769
4833
  if (quote === '"' && char === "\\") {
4770
4834
  index += 1;
4771
4835
  continue;
4772
4836
  }
4773
4837
  if (char !== quote) continue;
4774
- if (quote === "'" && text7[index + 1] === "'") {
4838
+ if (quote === "'" && text8[index + 1] === "'") {
4775
4839
  index += 1;
4776
4840
  continue;
4777
4841
  }
@@ -4779,20 +4843,20 @@ var quotedEnd = (text7, start) => {
4779
4843
  }
4780
4844
  return -1;
4781
4845
  };
4782
- var parseQuoted = (text7) => {
4783
- const end = quotedEnd(text7, 0);
4784
- if (end !== text7.length - 1) return reject(`unterminated or trailing text after quoted string`);
4785
- const body2 = text7.slice(1, -1);
4786
- return text7.startsWith('"') ? unescapeDouble(body2) : accept(body2.replace(/''/g, "'"));
4846
+ var parseQuoted = (text8) => {
4847
+ const end = quotedEnd(text8, 0);
4848
+ if (end !== text8.length - 1) return reject(`unterminated or trailing text after quoted string`);
4849
+ const body2 = text8.slice(1, -1);
4850
+ return text8.startsWith('"') ? unescapeDouble(body2) : accept(body2.replace(/''/g, "'"));
4787
4851
  };
4788
4852
  var NUMBER = /^-?(0|[1-9]\d*)(\.\d+)?([eE][-+]?\d+)?$/;
4789
4853
  var UNSUPPORTED = /^[&*!|>%@`]/;
4790
- var parsePlain = (text7) => {
4791
- if (UNSUPPORTED.test(text7)) return reject(`unsupported YAML syntax "${text7.slice(0, 1)}"`);
4792
- if (text7 === "true" || text7 === "false") return accept(text7 === "true");
4793
- if (text7 === "null" || text7 === "~") return accept(null);
4794
- if (NUMBER.test(text7)) return accept(Number(text7));
4795
- return accept(text7);
4854
+ var parsePlain = (text8) => {
4855
+ if (UNSUPPORTED.test(text8)) return reject(`unsupported YAML syntax "${text8.slice(0, 1)}"`);
4856
+ if (text8 === "true" || text8 === "false") return accept(text8 === "true");
4857
+ if (text8 === "null" || text8 === "~") return accept(null);
4858
+ if (NUMBER.test(text8)) return accept(Number(text8));
4859
+ return accept(text8);
4796
4860
  };
4797
4861
 
4798
4862
  // src/core/yaml/flow.ts
@@ -4819,17 +4883,17 @@ var splitTop = (body2) => {
4819
4883
  if (last.trim() !== "" || parts.length > 0) parts.push(last);
4820
4884
  return accept(parts.map((part2) => part2.trim()));
4821
4885
  };
4822
- var splitKey = (text7) => {
4886
+ var splitKey = (text8) => {
4823
4887
  let index = 0;
4824
- if (text7.startsWith('"') || text7.startsWith("'")) {
4825
- index = quotedEnd(text7, 0) + 1;
4888
+ if (text8.startsWith('"') || text8.startsWith("'")) {
4889
+ index = quotedEnd(text8, 0) + 1;
4826
4890
  if (index <= 0) return void 0;
4827
4891
  }
4828
- for (; index < text7.length; index += 1) {
4829
- const char = text7[index];
4830
- if (char === "#" && text7[index - 1] === " ") return void 0;
4831
- if (char === ":" && (index + 1 === text7.length || text7[index + 1] === " ")) {
4832
- return { key: text7.slice(0, index).trim(), rest: text7.slice(index + 1).trim() };
4892
+ for (; index < text8.length; index += 1) {
4893
+ const char = text8[index];
4894
+ if (char === "#" && text8[index - 1] === " ") return void 0;
4895
+ if (char === ":" && (index + 1 === text8.length || text8[index + 1] === " ")) {
4896
+ return { key: text8.slice(0, index).trim(), rest: text8.slice(index + 1).trim() };
4833
4897
  }
4834
4898
  }
4835
4899
  return void 0;
@@ -4858,39 +4922,39 @@ var flowSeq = (parts) => {
4858
4922
  }
4859
4923
  return accept(out);
4860
4924
  };
4861
- var parseValue = (text7) => {
4862
- if (text7 === "") return accept(null);
4863
- const first = text7[0] ?? "";
4864
- if (first === '"' || first === "'") return parseQuoted(text7);
4925
+ var parseValue = (text8) => {
4926
+ if (text8 === "") return accept(null);
4927
+ const first = text8[0] ?? "";
4928
+ if (first === '"' || first === "'") return parseQuoted(text8);
4865
4929
  const close2 = OPEN[first];
4866
- if (close2 === void 0) return parsePlain(text7);
4867
- if (!text7.endsWith(close2)) return reject(`flow collection must end on the same line with "${close2}"`);
4868
- const parts = splitTop(text7.slice(1, -1));
4930
+ if (close2 === void 0) return parsePlain(text8);
4931
+ if (!text8.endsWith(close2)) return reject(`flow collection must end on the same line with "${close2}"`);
4932
+ const parts = splitTop(text8.slice(1, -1));
4869
4933
  if (!parts.ok) return parts;
4870
4934
  return first === "[" ? flowSeq(parts.value) : flowMap(parts.value);
4871
4935
  };
4872
4936
 
4873
4937
  // src/core/yaml/parse.ts
4874
4938
  var BOM = new RegExp(`^${String.fromCharCode(65279)}`);
4875
- var stripComment2 = (text7) => {
4876
- for (let index = 0; index < text7.length; index += 1) {
4877
- const char = text7[index];
4939
+ var stripComment2 = (text8) => {
4940
+ for (let index = 0; index < text8.length; index += 1) {
4941
+ const char = text8[index];
4878
4942
  if (char === '"' || char === "'") {
4879
- const end = quotedEnd(text7, index);
4880
- if (end < 0) return text7;
4943
+ const end = quotedEnd(text8, index);
4944
+ if (end < 0) return text8;
4881
4945
  index = end;
4882
- } else if (char === "#" && (index === 0 || text7[index - 1] === " ")) return text7.slice(0, index);
4946
+ } else if (char === "#" && (index === 0 || text8[index - 1] === " ")) return text8.slice(0, index);
4883
4947
  }
4884
- return text7;
4948
+ return text8;
4885
4949
  };
4886
4950
  var toLines = (source) => {
4887
4951
  const lines3 = [];
4888
4952
  const raw = source.replace(BOM, "").split(/\r?\n/);
4889
4953
  for (const [index, full] of raw.entries()) {
4890
- const text7 = stripComment2(full).trimEnd();
4891
- const body2 = text7.trimStart();
4954
+ const text8 = stripComment2(full).trimEnd();
4955
+ const body2 = text8.trimStart();
4892
4956
  if (body2 === "" || body2 === "---" && lines3.length === 0) continue;
4893
- const indentText = text7.slice(0, text7.length - body2.length);
4957
+ const indentText = text8.slice(0, text8.length - body2.length);
4894
4958
  if (indentText.includes(" ")) return reject({ line: index + 1, message: "tabs are not allowed for indentation" });
4895
4959
  if (body2 === "---" || body2 === "...")
4896
4960
  return reject({ line: index + 1, message: "only one YAML document is allowed" });
@@ -4899,11 +4963,11 @@ var toLines = (source) => {
4899
4963
  return accept(lines3);
4900
4964
  };
4901
4965
  var fail = (line2, message) => reject({ line: line2?.number ?? 0, message });
4902
- var scalar = (line2, text7) => {
4903
- const value = parseValue(text7);
4966
+ var scalar = (line2, text8) => {
4967
+ const value = parseValue(text8);
4904
4968
  return value.ok ? value : fail(line2, value.error);
4905
4969
  };
4906
- var isItem = (text7) => text7 === "-" || text7.startsWith("- ");
4970
+ var isItem = (text8) => text8 === "-" || text8.startsWith("- ");
4907
4971
  var child = (lines3, index, parent, sameIndentSeq) => {
4908
4972
  const next2 = lines3[index];
4909
4973
  if (next2 !== void 0 && next2.indent > parent.indent) return block(lines3, index, next2.indent);
@@ -5146,8 +5210,8 @@ var validateContract = (value, implicit = false) => {
5146
5210
  // src/task/load.ts
5147
5211
  var contractHash = (contract) => sha256Hex(JSON.stringify(contract.acceptance.map((check2) => [check2.id, check2.kind, check2.run ?? ""])));
5148
5212
  var taskRelative = (id) => `.finitemonkey/tasks/${id}.yml`;
5149
- var parseContract = (id, text7) => {
5150
- const yaml = parseYaml(text7);
5213
+ var parseContract = (id, text8) => {
5214
+ const yaml = parseYaml(text8);
5151
5215
  if (!yaml.ok)
5152
5216
  return reject({ kind: "invalid", id, issues: [`line ${String(yaml.error.line)}: ${yaml.error.message}`] });
5153
5217
  const valid = validateContract(yaml.value);
@@ -5257,10 +5321,10 @@ var resolveBase = (git2, declared) => {
5257
5321
  const merged2 = remote === void 0 ? void 0 : git2.out(["merge-base", "HEAD", remote])?.trim();
5258
5322
  return nonEmpty(merged2) ?? nonEmpty(headSha(git2)) ?? EMPTY_TREE;
5259
5323
  };
5260
- var nulFields = (text7) => text7.split("\0").filter((part2) => part2 !== "");
5324
+ var nulFields = (text8) => text8.split("\0").filter((part2) => part2 !== "");
5261
5325
  var untracked = (git2) => nulFields(git2.out(["ls-files", "--others", "--exclude-standard", "-z", "--", ".", EXCLUDE_FM]) ?? "");
5262
- var parseNameStatus = (text7) => {
5263
- const fields = nulFields(text7);
5326
+ var parseNameStatus = (text8) => {
5327
+ const fields = nulFields(text8);
5264
5328
  const changes = [];
5265
5329
  for (let index = 0; index < fields.length; index += 1) {
5266
5330
  const code = fields[index] ?? "";
@@ -5328,8 +5392,8 @@ var aheadOfUpstream = (git2) => {
5328
5392
 
5329
5393
  // src/gate/diff-shape.ts
5330
5394
  var untrackedLines = (runtime, root, changes) => changes.filter((change) => change.status === "A").reduce((sum, change) => {
5331
- const text7 = runtime.readText(toAbsolutePath(join14(root, change.path)));
5332
- return sum + (text7.ok ? text7.value.split("\n").length : 0);
5395
+ const text8 = runtime.readText(toAbsolutePath(join14(root, change.path)));
5396
+ return sum + (text8.ok ? text8.value.split("\n").length : 0);
5333
5397
  }, 0);
5334
5398
  var diffShape = (runtime, git2, base, changes) => ({
5335
5399
  files: changes.map((change) => change.path),
@@ -5352,8 +5416,8 @@ var ciChecks = (value) => Array.isArray(value) ? value.flatMap((item2) => {
5352
5416
  const name = optionalString(item2, "name") ?? optionalString(item2, "context") ?? "?";
5353
5417
  return [{ name, state: ciState(item2) }];
5354
5418
  }) : [];
5355
- var parsePrView = (text7) => {
5356
- const parsed = parseJsonObject(text7);
5419
+ var parsePrView = (text8) => {
5420
+ const parsed = parseJsonObject(text8);
5357
5421
  if (!parsed.ok) return void 0;
5358
5422
  const record3 = parsed.value;
5359
5423
  const number2 = record3["number"];
@@ -5534,8 +5598,8 @@ var applySize = (contract, verdict) => {
5534
5598
  // src/gate/prepare.ts
5535
5599
  var diskFiles = (runtime, root) => ({
5536
5600
  read: (relative11) => {
5537
- const text7 = runtime.readText(toAbsolutePath(join15(root, relative11)));
5538
- return text7.ok ? text7.value : void 0;
5601
+ const text8 = runtime.readText(toAbsolutePath(join15(root, relative11)));
5602
+ return text8.ok ? text8.value : void 0;
5539
5603
  },
5540
5604
  exists: (relative11) => runtime.stat(toAbsolutePath(join15(root, relative11))) !== void 0
5541
5605
  });
@@ -5656,11 +5720,11 @@ var trust = (input, paths, task) => {
5656
5720
  });
5657
5721
  if (ready.kind !== "ready" || ready.prepared.ctx.contract.id !== task) return false;
5658
5722
  const lock = readTrust(runtime, paths);
5659
- const text7 = runtime.readText(paths.task(task));
5723
+ const text8 = runtime.readText(paths.task(task));
5660
5724
  return writeTrust(runtime, paths, {
5661
5725
  ...lock,
5662
5726
  contracts: { ...lock.contracts, [task]: ready.prepared.loaded.hash },
5663
- approved: task === IMPLICIT_ID || !text7.ok ? lock.approved : { ...lock.approved, [task]: text7.value }
5727
+ approved: task === IMPLICIT_ID || !text8.ok ? lock.approved : { ...lock.approved, [task]: text8.value }
5664
5728
  }).ok;
5665
5729
  };
5666
5730
  var apply = (input, paths, markers) => {
@@ -5712,8 +5776,8 @@ var promptRelease = (input) => {
5712
5776
  var PRS_CACHE_MS = 10 * 6e4;
5713
5777
  var PRS_TIMEOUT_MS = 3e3;
5714
5778
  var PRS_AUTH_TIMEOUT_MS = 2e3;
5715
- var parsePrList = (text7) => {
5716
- const parsed = parseJson(text7);
5779
+ var parsePrList = (text8) => {
5780
+ const parsed = parseJson(text8);
5717
5781
  if (!parsed.ok || !Array.isArray(parsed.value)) return void 0;
5718
5782
  return parsed.value.flatMap((item2) => {
5719
5783
  if (!isRecord(item2) || typeof item2["number"] !== "number") return [];
@@ -6513,7 +6577,25 @@ var en_default = {
6513
6577
  "reason.human.generic": "Blocked a tool call; the reason went to Claude.",
6514
6578
  "review.command.packet": "Print the review packet for the reviewer and verifier agents: diff, task contract, evidence ledger and REVIEW.md, never the conversation",
6515
6579
  "review.guard.read_only": "blocked: the {agent} agent is read-only and cannot use {tool}. Report the finding; the main session makes the change.",
6516
- "review.error.no_base": "the base {detail} is not a commit here"
6580
+ "review.error.no_base": "the base {detail} is not a commit here",
6581
+ "command.statusline.summary": "Print the status line: active task, gate state and context use",
6582
+ "statusline.gate.green": "gate green",
6583
+ "statusline.gate.red": "gate red",
6584
+ "statusline.gate.incomplete": "gate incomplete",
6585
+ "statusline.gate.did_not_run": "gate did not run",
6586
+ "statusline.context": "ctx {percent}%",
6587
+ "command.gain.summary": "What the guards did: saves next to false alarms and gate failures",
6588
+ "gain.title": "Finite Monkey, last {days, plural, one {# day} other {# days}} (from .finitemonkey/log.jsonl and the evidence ledgers):",
6589
+ "gain.row.blocks": "{count, plural, one {# block} other {# blocks}} by the guards",
6590
+ "gain.row.saves": "{count, plural, one {# block} other {# blocks}} followed by a green gate within a day",
6591
+ "gain.row.false_positives": "{count, plural, one {# false alarm} other {# false alarms}} (releases typed by the person, and false positives recorded)",
6592
+ "gain.row.gate_runs": "{count, plural, one {# gate run} other {# gate runs}}",
6593
+ "gain.row.gate_failed": "{count, plural, one {# gate run} other {# gate runs}} failed",
6594
+ "gain.row.gate_unverified": "{count, plural, one {# gate run} other {# gate runs}} ended not verified",
6595
+ "gain.row.gate_time": "{minutes, plural, one {# minute} other {# minutes}} spent in the gate",
6596
+ "gain.up": "up {delta} on the window before",
6597
+ "gain.down": "down {delta} on the window before",
6598
+ "gain.footer": "Counts come from local files only; a save is a block followed by a passing gate, not proof that the block caused it."
6517
6599
  };
6518
6600
 
6519
6601
  // src/i18n/es.json
@@ -7165,7 +7247,25 @@ var es_default = {
7165
7247
  "reason.human.generic": "Bloqueé una llamada de herramienta; el motivo fue a Claude.",
7166
7248
  "review.command.packet": "Muestra el paquete de revisión para los agentes revisor y verificador: diff, contrato de la tarea, registro de evidencias y REVIEW.md, nunca la conversación",
7167
7249
  "review.guard.read_only": "bloqueado: el agente {agent} es de solo lectura y no puede usar {tool}. Informa el hallazgo; la sesión principal hace el cambio.",
7168
- "review.error.no_base": "la base {detail} no es un commit aquí"
7250
+ "review.error.no_base": "la base {detail} no es un commit aquí",
7251
+ "command.statusline.summary": "Imprime la línea de estado: tarea activa, estado de la compuerta y uso del contexto",
7252
+ "statusline.gate.green": "compuerta verde",
7253
+ "statusline.gate.red": "compuerta roja",
7254
+ "statusline.gate.incomplete": "compuerta incompleta",
7255
+ "statusline.gate.did_not_run": "la compuerta no se ejecutó",
7256
+ "statusline.context": "ctx {percent}%",
7257
+ "command.gain.summary": "Lo que hicieron los bloqueos: aciertos junto a falsas alarmas y fallos de la compuerta",
7258
+ "gain.title": "Finite Monkey, {days, plural, one {último # día} other {últimos # días}} (de .finitemonkey/log.jsonl y los ledgers de evidencia):",
7259
+ "gain.row.blocks": "{count, plural, one {# bloqueo} other {# bloqueos}} de los guards",
7260
+ "gain.row.saves": "{count, plural, one {# bloqueo seguido} other {# bloqueos seguidos}} de una compuerta en verde en menos de un día",
7261
+ "gain.row.false_positives": "{count, plural, one {# falsa alarma} other {# falsas alarmas}} (liberaciones escritas por la persona y falsos positivos registrados)",
7262
+ "gain.row.gate_runs": "{count, plural, one {# ejecución de la compuerta} other {# ejecuciones de la compuerta}}",
7263
+ "gain.row.gate_failed": "{count, plural, one {# ejecución de la compuerta falló} other {# ejecuciones de la compuerta fallaron}}",
7264
+ "gain.row.gate_unverified": "{count, plural, one {# ejecución de la compuerta terminó} other {# ejecuciones de la compuerta terminaron}} sin verificar",
7265
+ "gain.row.gate_time": "{minutes, plural, one {# minuto} other {# minutos}} en la compuerta",
7266
+ "gain.up": "+{delta} respecto a la ventana anterior",
7267
+ "gain.down": "-{delta} respecto a la ventana anterior",
7268
+ "gain.footer": "Los conteos salen solo de archivos locales; un acierto es un bloqueo seguido de una compuerta en verde, no prueba de que el bloqueo lo causara."
7169
7269
  };
7170
7270
 
7171
7271
  // src/i18n/pt-BR.json
@@ -7817,7 +7917,25 @@ var pt_BR_default = {
7817
7917
  "reason.human.generic": "Bloqueei uma chamada de ferramenta; o motivo foi para o Claude.",
7818
7918
  "review.command.packet": "Mostra o pacote de revisão para os agentes revisor e verificador: diff, contrato da tarefa, registro de evidências e REVIEW.md, nunca a conversa",
7819
7919
  "review.guard.read_only": "bloqueado: o agente {agent} é só leitura e não pode usar {tool}. Relate o achado; a sessão principal faz a mudança.",
7820
- "review.error.no_base": "a base {detail} não é um commit aqui"
7920
+ "review.error.no_base": "a base {detail} não é um commit aqui",
7921
+ "command.statusline.summary": "Imprime a linha de status: tarefa ativa, estado do portão e uso do contexto",
7922
+ "statusline.gate.green": "portão verde",
7923
+ "statusline.gate.red": "portão vermelho",
7924
+ "statusline.gate.incomplete": "portão incompleto",
7925
+ "statusline.gate.did_not_run": "portão não rodou",
7926
+ "statusline.context": "ctx {percent}%",
7927
+ "command.gain.summary": "O que as travas fizeram: acertos ao lado dos alarmes falsos e das falhas do portão",
7928
+ "gain.title": "Finite Monkey, últimos {days, plural, one {# dia} other {# dias}} (de .finitemonkey/log.jsonl e dos ledgers de evidência):",
7929
+ "gain.row.blocks": "{count, plural, one {# bloqueio} other {# bloqueios}} das travas",
7930
+ "gain.row.saves": "{count, plural, one {# bloqueio seguido} other {# bloqueios seguidos}} de portão verde em até um dia",
7931
+ "gain.row.false_positives": "{count, plural, one {# alarme falso} other {# alarmes falsos}} (liberações digitadas pela pessoa e falsos positivos registrados)",
7932
+ "gain.row.gate_runs": "{count, plural, one {# execução do portão} other {# execuções do portão}}",
7933
+ "gain.row.gate_failed": "{count, plural, one {# execução do portão reprovou} other {# execuções do portão reprovaram}}",
7934
+ "gain.row.gate_unverified": "{count, plural, one {# execução do portão terminou} other {# execuções do portão terminaram}} sem verificar",
7935
+ "gain.row.gate_time": "{minutes, plural, one {# minuto} other {# minutos}} no portão",
7936
+ "gain.up": "+{delta} em relação à janela anterior",
7937
+ "gain.down": "-{delta} em relação à janela anterior",
7938
+ "gain.footer": "As contagens vêm só de arquivos locais; um acerto é um bloqueio seguido de portão verde, não prova de que o bloqueio causou isso."
7821
7939
  };
7822
7940
 
7823
7941
  // src/i18n/review.json
@@ -8470,7 +8588,25 @@ var review_default = {
8470
8588
  "reason.human.generic": "machine",
8471
8589
  "review.command.packet": "machine",
8472
8590
  "review.guard.read_only": "machine",
8473
- "review.error.no_base": "machine"
8591
+ "review.error.no_base": "machine",
8592
+ "command.statusline.summary": "machine",
8593
+ "statusline.gate.green": "machine",
8594
+ "statusline.gate.red": "machine",
8595
+ "statusline.gate.incomplete": "machine",
8596
+ "statusline.gate.did_not_run": "machine",
8597
+ "statusline.context": "machine",
8598
+ "command.gain.summary": "machine",
8599
+ "gain.title": "machine",
8600
+ "gain.row.blocks": "machine",
8601
+ "gain.row.saves": "machine",
8602
+ "gain.row.false_positives": "machine",
8603
+ "gain.row.gate_runs": "machine",
8604
+ "gain.row.gate_failed": "machine",
8605
+ "gain.row.gate_unverified": "machine",
8606
+ "gain.row.gate_time": "machine",
8607
+ "gain.up": "machine",
8608
+ "gain.down": "machine",
8609
+ "gain.footer": "machine"
8474
8610
  },
8475
8611
  es: {
8476
8612
  "command.init.summary": "machine",
@@ -9120,7 +9256,25 @@ var review_default = {
9120
9256
  "reason.human.generic": "machine",
9121
9257
  "review.command.packet": "machine",
9122
9258
  "review.guard.read_only": "machine",
9123
- "review.error.no_base": "machine"
9259
+ "review.error.no_base": "machine",
9260
+ "command.statusline.summary": "machine",
9261
+ "statusline.gate.green": "machine",
9262
+ "statusline.gate.red": "machine",
9263
+ "statusline.gate.incomplete": "machine",
9264
+ "statusline.gate.did_not_run": "machine",
9265
+ "statusline.context": "machine",
9266
+ "command.gain.summary": "machine",
9267
+ "gain.title": "machine",
9268
+ "gain.row.blocks": "machine",
9269
+ "gain.row.saves": "machine",
9270
+ "gain.row.false_positives": "machine",
9271
+ "gain.row.gate_runs": "machine",
9272
+ "gain.row.gate_failed": "machine",
9273
+ "gain.row.gate_unverified": "machine",
9274
+ "gain.row.gate_time": "machine",
9275
+ "gain.up": "machine",
9276
+ "gain.down": "machine",
9277
+ "gain.footer": "machine"
9124
9278
  }
9125
9279
  };
9126
9280
 
@@ -9410,10 +9564,10 @@ var pairs = (record3) => {
9410
9564
  return names.filter((name) => typeof name === "string").map((name) => `${task}\0${name}`);
9411
9565
  };
9412
9566
  var callersRate = (runtime, paths) => {
9413
- const text7 = runtime.readText(paths.log);
9567
+ const text8 = runtime.readText(paths.log);
9414
9568
  const flagged = /* @__PURE__ */ new Set();
9415
9569
  const released2 = /* @__PURE__ */ new Set();
9416
- for (const line2 of text7.ok ? text7.value.split("\n") : []) {
9570
+ for (const line2 of text8.ok ? text8.value.split("\n") : []) {
9417
9571
  const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
9418
9572
  if (parsed === void 0 || !parsed.ok || !isRecord(parsed.value)) continue;
9419
9573
  const event = parsed.value["event"];
@@ -9438,7 +9592,7 @@ import { posix as posix2 } from "node:path";
9438
9592
  var JS_SPECIFIER = /(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["']([^"']+)["']/g;
9439
9593
  var JS_EXTENSION = /\.(?:[cm]?[jt]sx?)$/;
9440
9594
  var stripModule = (path) => path.replace(JS_EXTENSION, "").replace(/\/index$/, "");
9441
- var jsSpecifiers = (text7) => [...text7.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
9595
+ var jsSpecifiers = (text8) => [...text8.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
9442
9596
  var jsImports = (callerPath, callerText, origin) => {
9443
9597
  const target2 = stripModule(origin);
9444
9598
  return jsSpecifiers(callerText).some((specifier) => {
@@ -9540,9 +9694,9 @@ var languageOf = (path) => {
9540
9694
  const extension = extname(path).toLowerCase();
9541
9695
  return LANGUAGES2.find((language) => language.extensions.includes(extension));
9542
9696
  };
9543
- var definitionsIn = (language, text7) => {
9697
+ var definitionsIn = (language, text8) => {
9544
9698
  const names = /* @__PURE__ */ new Set();
9545
- for (const line2 of text7.split(/\r?\n/)) {
9699
+ for (const line2 of text8.split(/\r?\n/)) {
9546
9700
  for (const pattern of language.definitions) {
9547
9701
  const name = pattern.exec(line2)?.[1];
9548
9702
  if (name !== void 0 && !KEYWORDS.has(name)) names.add(name);
@@ -9555,8 +9709,8 @@ var definesName = (language, line2, name) => language.definitions.some((pattern)
9555
9709
  // src/gate/callers/scan.ts
9556
9710
  var MAX_SYMBOLS = 200;
9557
9711
  var current = (runtime, root, path) => {
9558
- const text7 = runtime.readText(toAbsolutePath(join16(root, path)));
9559
- return text7.ok ? text7.value : "";
9712
+ const text8 = runtime.readText(toAbsolutePath(join16(root, path)));
9713
+ return text8.ok ? text8.value : "";
9560
9714
  };
9561
9715
  var keyOf = (language, name) => `${language.id}\0${name}`;
9562
9716
  var redefinedNames = (runtime, git2, changes) => {
@@ -9597,14 +9751,14 @@ var grep = (git2, names) => {
9597
9751
  git2.out(["grep", "-n", "-z", "-w", "-I", "--untracked", "--no-color", ...patterns, "--", ".", EXCLUDE_FM]) ?? ""
9598
9752
  );
9599
9753
  };
9600
- var mentions = (text7, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text7);
9754
+ var mentions = (text8, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text8);
9601
9755
  var callSites = (runtime, git2, symbols) => {
9602
9756
  const hits = grep(git2, [...new Set(symbols.map((symbol) => symbol.name))]);
9603
9757
  const texts2 = /* @__PURE__ */ new Map();
9604
9758
  const textOf4 = (file) => {
9605
- const cached2 = texts2.get(file) ?? current(runtime, git2.cwd, file);
9606
- texts2.set(file, cached2);
9607
- return cached2;
9759
+ const cached3 = texts2.get(file) ?? current(runtime, git2.cwd, file);
9760
+ texts2.set(file, cached3);
9761
+ return cached3;
9608
9762
  };
9609
9763
  return symbols.flatMap(
9610
9764
  (symbol) => hits.filter((hit3) => languageOf(hit3.file)?.id === symbol.language.id && mentions(hit3.text, symbol.name)).filter((hit3) => !symbol.language.comment.test(hit3.text) && !definesName(symbol.language, hit3.text, symbol.name)).filter((hit3) => importsOrigin(symbol.language, hit3.file, textOf4(hit3.file), symbol.origins)).map((hit3) => ({ name: symbol.name, file: hit3.file, line: hit3.line }))
@@ -9672,31 +9826,31 @@ var ZERO = [
9672
9826
  /^ℹ tests 0$/m,
9673
9827
  /No tests found/
9674
9828
  ];
9675
- var pyTotal = (text7) => {
9676
- const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(text7)?.[1];
9829
+ var pyTotal = (text8) => {
9830
+ const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(text8)?.[1];
9677
9831
  if (line2 === void 0) return void 0;
9678
9832
  return [...line2.matchAll(/(\d+) (passed|failed|error|errors|skipped|xfailed|xpassed)/g)].reduce(
9679
9833
  (sum, match) => sum + Number(match[1] ?? "0"),
9680
9834
  0
9681
9835
  );
9682
9836
  };
9683
- var counted = (text7) => {
9837
+ var counted = (text8) => {
9684
9838
  for (const counter of COUNTERS) {
9685
- const total = counter.total.exec(text7)?.[1];
9839
+ const total = counter.total.exec(text8)?.[1];
9686
9840
  if (total === void 0) continue;
9687
- const failed4 = counter.failed?.exec(text7)?.[1];
9841
+ const failed4 = counter.failed?.exec(text8)?.[1];
9688
9842
  return { total: Number(total), ...failed4 === void 0 ? {} : { failed: Number(failed4) } };
9689
9843
  }
9690
- const py = pyTotal(text7);
9844
+ const py = pyTotal(text8);
9691
9845
  return py === void 0 ? {} : { total: py };
9692
9846
  };
9693
9847
  var parseTestSummary = (output) => {
9694
- const text7 = output.replace(ANSI2, "");
9848
+ const text8 = output.replace(ANSI2, "");
9695
9849
  const failures = [
9696
- ...new Set(FAILURE_LINES.flatMap((pattern) => [...text7.matchAll(pattern)].map((match) => (match[1] ?? "").trim())))
9850
+ ...new Set(FAILURE_LINES.flatMap((pattern) => [...text8.matchAll(pattern)].map((match) => (match[1] ?? "").trim())))
9697
9851
  ].filter((name) => name !== "" && !/^failing tests:?$/i.test(name));
9698
- if (ZERO.some((pattern) => pattern.test(text7))) return { total: 0, failures };
9699
- return { ...counted(text7), failures };
9852
+ if (ZERO.some((pattern) => pattern.test(text8))) return { total: 0, failures };
9853
+ return { ...counted(text8), failures };
9700
9854
  };
9701
9855
 
9702
9856
  // src/gate/checks/command.ts
@@ -9707,8 +9861,8 @@ var REWRITERS = /^\s*(rtk)\b/;
9707
9861
  var TRIVIAL = /* @__PURE__ */ new Set(["echo", "true", ":", "printf", "exit", "sleep", "cat", "yes", "pwd", "ls", "date", "env"]);
9708
9862
  var TAIL = 160;
9709
9863
  var MAX_FAILURES2 = 5;
9710
- var tail = (text7) => {
9711
- const lines3 = text7.split(/\r?\n/).filter((line2) => line2.trim() !== "");
9864
+ var tail = (text8) => {
9865
+ const lines3 = text8.split(/\r?\n/).filter((line2) => line2.trim() !== "");
9712
9866
  const last = lines3[lines3.length - 1]?.trim() ?? "";
9713
9867
  return last.length > TAIL ? `${last.slice(0, TAIL)}…` : last;
9714
9868
  };
@@ -9922,8 +10076,8 @@ var findingNote = (value) => {
9922
10076
  ];
9923
10077
  };
9924
10078
  var readSummary = (ctx) => {
9925
- const text7 = ctx.runtime.readText(summaryPath(ctx.paths, ctx.treeHash));
9926
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
10079
+ const text8 = ctx.runtime.readText(summaryPath(ctx.paths, ctx.treeHash));
10080
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
9927
10081
  if (!parsed?.ok) return void 0;
9928
10082
  const { at, captures, findings, ignoredAllows } = parsed.value;
9929
10083
  if (typeof at !== "string" || !Array.isArray(captures) || !Array.isArray(findings)) return void 0;
@@ -9992,8 +10146,8 @@ var SKIP_FILES = /\.(md|mdx|txt|rst|lock|snap|svg|png|jpg|gif|json)$|(^|\/)(CHAN
9992
10146
  var PLACEHOLDER = /\b(TODO|FIXME|XXX)\b|not implemented|NotImplementedError|unimplemented!|todo!\(|lorem ipsum/i;
9993
10147
  var findPlaceholders = (lines3) => lines3.filter((line2) => !isTestFile(line2.path) && !SKIP_FILES.test(line2.path) && PLACEHOLDER.test(line2.text));
9994
10148
  var untrackedAdded = (ctx) => ctx.changes.filter((change) => change.status === "A" && !SKIP_FILES.test(change.path)).flatMap((change) => {
9995
- const text7 = ctx.runtime.readText(toAbsolutePath(join20(ctx.paths.root, change.path)));
9996
- return text7.ok ? text7.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
10149
+ const text8 = ctx.runtime.readText(toAbsolutePath(join20(ctx.paths.root, change.path)));
10150
+ return text8.ok ? text8.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
9997
10151
  });
9998
10152
  var placeholdersCheck = (_check, ctx) => {
9999
10153
  const tracked = addedLines(ctx.git, ctx.base);
@@ -10052,7 +10206,7 @@ var prOutcome = (check2, ctx) => {
10052
10206
 
10053
10207
  // src/prose/preset.ts
10054
10208
  var PRESET_LINE = /^preset:\s*([A-Za-z0-9_-]+)\s*$/m;
10055
- var presetFromConfig = (text7) => parsePreset(PRESET_LINE.exec(text7)?.[1]) ?? "neutral";
10209
+ var presetFromConfig = (text8) => parsePreset(PRESET_LINE.exec(text8)?.[1]) ?? "neutral";
10056
10210
  var projectPreset = (runtime, root) => {
10057
10211
  const config = runtime.readText(toAbsolutePath(CONFIG_FILE, root));
10058
10212
  return config.ok ? presetFromConfig(config.value) : "neutral";
@@ -10229,16 +10383,16 @@ var ANY_MARKER = /<!--\s*finitemonkey:prose-allow(?:-file)?\s*-->/g;
10229
10383
  var INLINE_CODE = /`[^`]*`/g;
10230
10384
  var FENCE2 = /^\s*(```|~~~)/;
10231
10385
  var outsideCode = (line2) => line2.replace(INLINE_CODE, "");
10232
- var unfencedLines = (text7) => {
10386
+ var unfencedLines = (text8) => {
10233
10387
  let fenced = false;
10234
- return text7.split("\n").filter((line2) => {
10388
+ return text8.split("\n").filter((line2) => {
10235
10389
  if (FENCE2.test(line2)) fenced = !fenced;
10236
10390
  return !fenced && !FENCE2.test(line2);
10237
10391
  });
10238
10392
  };
10239
- var allowsWholeFile = (text7) => unfencedLines(text7).some((line2) => FILE_MARKER.test(outsideCode(line2)));
10393
+ var allowsWholeFile = (text8) => unfencedLines(text8).some((line2) => FILE_MARKER.test(outsideCode(line2)));
10240
10394
  var allowsLine = (line2) => LINE_MARKER.test(outsideCode(line2));
10241
- var markerCount = (text7) => text7.match(ANY_MARKER)?.length ?? 0;
10395
+ var markerCount = (text8) => text8.match(ANY_MARKER)?.length ?? 0;
10242
10396
  var isMarkerOnlyLine = (line2) => allowsLine(line2) && outsideCode(line2).replace(LINE_MARKER, "").trim() === "";
10243
10397
 
10244
10398
  // src/prose/extract.ts
@@ -10258,7 +10412,7 @@ var MASKS = [
10258
10412
  [/<[^>]+>/g, ""],
10259
10413
  [/\*\*|__/g, ""]
10260
10414
  ];
10261
- var maskInline = (line2) => MASKS.reduce((text7, [pattern, replacement]) => text7.replace(pattern, replacement), line2);
10415
+ var maskInline = (line2) => MASKS.reduce((text8, [pattern, replacement]) => text8.replace(pattern, replacement), line2);
10262
10416
  var BlockReader = class {
10263
10417
  blocks = [];
10264
10418
  current;
@@ -10349,8 +10503,8 @@ var splitBlock = (block4) => {
10349
10503
  return joined.split(SENTENCE_END).flatMap((sentence) => {
10350
10504
  const at = joined.indexOf(sentence, offset);
10351
10505
  offset = at + sentence.length;
10352
- const text7 = sentence.trim();
10353
- return text7 === "" ? [] : [{ text: text7, line: lineAt(at) }];
10506
+ const text8 = sentence.trim();
10507
+ return text8 === "" ? [] : [{ text: text8, line: lineAt(at) }];
10354
10508
  });
10355
10509
  };
10356
10510
  var unitsOf = (blocks) => {
@@ -10371,9 +10525,9 @@ var unitsOf = (blocks) => {
10371
10525
  }));
10372
10526
  });
10373
10527
  };
10374
- var extractProse = (text7) => {
10375
- if (allowsWholeFile(text7)) return { units: [], allowed: true };
10376
- const lines3 = text7.split("\n").map(withoutCarriageReturn);
10528
+ var extractProse = (text8) => {
10529
+ if (allowsWholeFile(text8)) return { units: [], allowed: true };
10530
+ const lines3 = text8.split("\n").map(withoutCarriageReturn);
10377
10531
  return { units: unitsOf(readBlocks(lines3)), allowed: false };
10378
10532
  };
10379
10533
 
@@ -10498,7 +10652,7 @@ var CONNECTIVES = new Set(
10498
10652
  " "
10499
10653
  )
10500
10654
  );
10501
- var wordsOf = (text7) => text7.split(/\s+/).filter((word2) => WORD.test(word2));
10655
+ var wordsOf = (text8) => text8.split(/\s+/).filter((word2) => WORD.test(word2));
10502
10656
  var hit = (rule, units, index, weight) => {
10503
10657
  const unit = units[index];
10504
10658
  return { rule, unit: index, line: unit?.line ?? 0, weight, excerpt: unit?.text ?? "" };
@@ -10626,8 +10780,8 @@ var mergeSpans = (hits, windows) => {
10626
10780
  return ranges.map(([from, to]) => spanOf2(hits, from, to)).filter((span) => span.hits.length > 0);
10627
10781
  };
10628
10782
  var byPosition = (left, right) => left.unit - right.unit || left.line - right.line;
10629
- var scoreProse = (text7, options = {}) => {
10630
- const { units, allowed: allowed2 } = extractProse(text7);
10783
+ var scoreProse = (text8, options = {}) => {
10784
+ const { units, allowed: allowed2 } = extractProse(text8);
10631
10785
  const size = options.window ?? DENSITY_WINDOW;
10632
10786
  const threshold2 = options.threshold ?? DENSITY_THRESHOLD;
10633
10787
  const catalog = options.context === "record" ? [...phraseRules(), ...recordRules()] : phraseRules();
@@ -10659,9 +10813,9 @@ var changedMarkdown = (runtime, root) => {
10659
10813
  var scoreFiles = (runtime, root, files, forced) => {
10660
10814
  const reports = [];
10661
10815
  for (const file of files) {
10662
- const text7 = runtime.readText(toAbsolutePath(file, root));
10663
- if (!text7.ok) return reject(`${file}: ${text7.error}`);
10664
- reports.push({ file: toPosixPath(file), report: scoreProse(text7.value, { context: forced ?? contextOf(file) }) });
10816
+ const text8 = runtime.readText(toAbsolutePath(file, root));
10817
+ if (!text8.ok) return reject(`${file}: ${text8.error}`);
10818
+ reports.push({ file: toPosixPath(file), report: scoreProse(text8.value, { context: forced ?? contextOf(file) }) });
10665
10819
  }
10666
10820
  return accept(reports);
10667
10821
  };
@@ -10760,8 +10914,8 @@ var parseAppCheck = (stdout) => {
10760
10914
  var PROBE_MARGIN_MS = 15e3;
10761
10915
  var MIN_REMAINING_MS2 = 5e3;
10762
10916
  var TAIL2 = 160;
10763
- var lastLine = (text7) => {
10764
- const rows = text7.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
10917
+ var lastLine = (text8) => {
10918
+ const rows = text8.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
10765
10919
  const line2 = (rows.findLast((row) => /error/i.test(row)) ?? rows.at(-1))?.trim() ?? "";
10766
10920
  return line2.length > TAIL2 ? `${line2.slice(0, TAIL2)}…` : line2;
10767
10921
  };
@@ -11104,7 +11258,7 @@ var diffScanner = () => {
11104
11258
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
11105
11259
  var SKIPPED = /(?:^|\/)(?:\.git|node_modules|\.finitemonkey)\//;
11106
11260
  var DIFF_FLAGS = ["-U0", "--no-color", "--no-ext-diff", "--no-renames"];
11107
- var binary = (text7) => text7.slice(0, 8e3).includes("\0");
11261
+ var binary = (text8) => text8.slice(0, 8e3).includes("\0");
11108
11262
  var listed2 = (runtime, root, target2) => {
11109
11263
  const git2 = runtime.git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", target2], root);
11110
11264
  if (git2.ok)
@@ -11117,9 +11271,9 @@ var listed2 = (runtime, root, target2) => {
11117
11271
  return accept(walked.value.map((name) => toPosixPath(relative6(root, join23(base, toPosixPath(name))))));
11118
11272
  };
11119
11273
  var scanFile = (runtime, root, file) => {
11120
- const text7 = runtime.readText(toAbsolutePath(file, root));
11121
- if (!text7.ok || text7.value.length > MAX_FILE_BYTES || binary(text7.value)) return [];
11122
- return scanText(text7.value, { path: file }).map((finding4) => locate(finding4, file, finding4.line));
11274
+ const text8 = runtime.readText(toAbsolutePath(file, root));
11275
+ if (!text8.ok || text8.value.length > MAX_FILE_BYTES || binary(text8.value)) return [];
11276
+ return scanText(text8.value, { path: file }).map((finding4) => locate(finding4, file, finding4.line));
11123
11277
  };
11124
11278
  var scanPath = (runtime, root, target2) => {
11125
11279
  const files = listed2(runtime, root, target2);
@@ -11216,8 +11370,8 @@ var treeViolations = (ctx) => {
11216
11370
  changes: relevant,
11217
11371
  before: (path) => fileAt(ctx.git, ctx.base, path),
11218
11372
  after: (path) => {
11219
- const text7 = ctx.runtime.readText(toAbsolutePath(join24(ctx.paths.root, path)));
11220
- return text7.ok ? text7.value : void 0;
11373
+ const text8 = ctx.runtime.readText(toAbsolutePath(join24(ctx.paths.root, path)));
11374
+ return text8.ok ? text8.value : void 0;
11221
11375
  },
11222
11376
  acceptedConfigs: ctx.trust.configs,
11223
11377
  scopePaths: ctx.contract.scope.paths,
@@ -11381,10 +11535,10 @@ var bodyLine = (draft, line2) => {
11381
11535
  else if (line2.startsWith("+")) draft.current.added.push(line2.slice(1));
11382
11536
  };
11383
11537
  var DIFF_GIT = /^diff --git a\/(.+) b\/(.+)$/;
11384
- var parseUnifiedDiff = (text7) => {
11538
+ var parseUnifiedDiff = (text8) => {
11385
11539
  const out = [];
11386
11540
  let draft;
11387
- for (const raw of text7.split("\n")) {
11541
+ for (const raw of text8.split("\n")) {
11388
11542
  const line2 = withoutCarriageReturn(raw);
11389
11543
  const start = DIFF_GIT.exec(line2);
11390
11544
  if (start !== null) {
@@ -11485,10 +11639,10 @@ var mergeInProgress = (runtime, repo) => revParse(runtime, repo, "MERGE_HEAD") !
11485
11639
 
11486
11640
  // src/git/facts.ts
11487
11641
  var memo2 = (compute) => {
11488
- let cached2;
11642
+ let cached3;
11489
11643
  return () => {
11490
- cached2 ??= { value: compute() };
11491
- return cached2.value;
11644
+ cached3 ??= { value: compute() };
11645
+ return cached3.value;
11492
11646
  };
11493
11647
  };
11494
11648
  var memoBy = (compute) => {
@@ -11535,7 +11689,7 @@ var normalizeLine = (line2) => line2.replace(/\r$/, "").trim().replace(/\s*,$/,
11535
11689
  var WEAK = /^[\p{P}\p{S}\s]+$/u;
11536
11690
  var isWeak = (line2) => WEAK.test(line2);
11537
11691
  var meaningful = (lines3) => lines3.map(normalizeLine).filter((line2) => line2 !== "");
11538
- var contentLines = (text7) => text7 === void 0 ? [] : meaningful(text7.split("\n"));
11692
+ var contentLines = (text8) => text8 === void 0 ? [] : meaningful(text8.split("\n"));
11539
11693
  var countLines = (lines3) => {
11540
11694
  const counts = /* @__PURE__ */ new Map();
11541
11695
  for (const line2 of lines3) counts.set(line2, (counts.get(line2) ?? 0) + 1);
@@ -11584,16 +11738,16 @@ var itemOf = (value) => {
11584
11738
  approvedBy: optionalString(value, "approved_by")
11585
11739
  };
11586
11740
  };
11587
- var invalidFinding = (file, text7) => ({
11741
+ var invalidFinding = (file, text8) => ({
11588
11742
  kind: "invalid-dropped",
11589
11743
  file,
11590
11744
  side: void 0,
11591
11745
  line: void 0,
11592
- text: text7,
11746
+ text: text8,
11593
11747
  weak: false
11594
11748
  });
11595
- var parseDropped = (text7) => {
11596
- const parsed = parseJson(text7);
11749
+ var parseDropped = (text8) => {
11750
+ const parsed = parseJson(text8);
11597
11751
  if (!parsed.ok) return parsed;
11598
11752
  const list4 = isRecord(parsed.value) ? parsed.value["dropped"] : parsed.value;
11599
11753
  if (!Array.isArray(list4)) return reject("expected a list of dropped items");
@@ -11609,9 +11763,9 @@ var parseDropped = (text7) => {
11609
11763
  };
11610
11764
  var loadDropped = (runtime, root, file) => {
11611
11765
  const path = toAbsolutePath(file ?? join26(root, DROPPED_FILE), root);
11612
- const text7 = runtime.readText(path);
11613
- if (!text7.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text7.error}`);
11614
- return parseDropped(text7.value);
11766
+ const text8 = runtime.readText(path);
11767
+ if (!text8.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text8.error}`);
11768
+ return parseDropped(text8.value);
11615
11769
  };
11616
11770
  var isDropped = (finding4, items) => items.some(
11617
11771
  (item2) => item2.file === finding4.file && (item2.side === void 0 || finding4.side === void 0 || item2.side === finding4.side) && (item2.text === void 0 || normalizeLine(item2.text) === finding4.text)
@@ -11734,13 +11888,13 @@ var removalOf = (ours, theirs, paired) => {
11734
11888
  var missingAdditions = (hunks, file, elsewhere) => {
11735
11889
  const present2 = new Set(file.result);
11736
11890
  return hunks.flatMap(({ side, hunk, added }) => {
11737
- const text7 = added.find((line2) => !isWeak(line2) && !present2.has(line2));
11738
- if (text7 === void 0 || containsSequence(file.result, added) || elsewhere(added)) return [];
11891
+ const text8 = added.find((line2) => !isWeak(line2) && !present2.has(line2));
11892
+ if (text8 === void 0 || containsSequence(file.result, added) || elsewhere(added)) return [];
11739
11893
  const offset = Math.max(
11740
11894
  0,
11741
- hunk.added.findIndex((line2) => normalizeLine(line2) === text7)
11895
+ hunk.added.findIndex((line2) => normalizeLine(line2) === text8)
11742
11896
  );
11743
- return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text: text7, weak: false }];
11897
+ return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text: text8, weak: false }];
11744
11898
  });
11745
11899
  };
11746
11900
  var expectations = (file, hunks, removal) => {
@@ -11813,8 +11967,8 @@ var symbolsOf = (line2) => {
11813
11967
  ...title === void 0 ? [] : [{ name: title, test: true }]
11814
11968
  ];
11815
11969
  };
11816
- var escapeRegex = (text7) => text7.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11817
- var containsSymbol = (text7, symbol) => symbol.test ? text7.includes(symbol.name) : new RegExp(`(?<![\\w$])${escapeRegex(symbol.name)}(?![\\w$])`).test(text7);
11970
+ var escapeRegex = (text8) => text8.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11971
+ var containsSymbol = (text8, symbol) => symbol.test ? text8.includes(symbol.name) : new RegExp(`(?<![\\w$])${escapeRegex(symbol.name)}(?![\\w$])`).test(text8);
11818
11972
  var missingSymbols = (file, side, hunks, present2) => {
11819
11973
  const seen = /* @__PURE__ */ new Set();
11820
11974
  return hunks.flatMap(
@@ -11833,12 +11987,12 @@ var created2 = (diff) => diff?.status === "added" || diff?.status === "copied";
11833
11987
  var resultPathOf = (context, pair) => [pair.ours?.newPath, pair.theirs?.newPath, pair.key].find(
11834
11988
  (path) => path !== void 0 && context.resultText(path) !== void 0
11835
11989
  );
11836
- var finding2 = (kind, file, side, text7) => ({
11990
+ var finding2 = (kind, file, side, text8) => ({
11837
11991
  kind,
11838
11992
  file,
11839
11993
  side,
11840
11994
  line: void 0,
11841
- text: text7,
11995
+ text: text8,
11842
11996
  weak: false
11843
11997
  });
11844
11998
  var binaryFindings = (context, pair, path) => {
@@ -11857,8 +12011,8 @@ var binaryFindings = (context, pair, path) => {
11857
12011
  return result === expected ? [] : [finding2("binary", file, side, file)];
11858
12012
  };
11859
12013
  var elsewhereOf = (context) => (lines3) => context.resultFiles().some((path) => containsSequence(contentLines(context.resultText(path)), lines3));
11860
- var symbolFindings = (context, pair, path, text7) => {
11861
- const present2 = (symbol) => text7 !== void 0 && containsSymbol(text7, symbol) || context.resultFiles().some((other) => containsSymbol(context.resultText(other) ?? "", symbol));
12014
+ var symbolFindings = (context, pair, path, text8) => {
12015
+ const present2 = (symbol) => text8 !== void 0 && containsSymbol(text8, symbol) || context.resultFiles().some((other) => containsSymbol(context.resultText(other) ?? "", symbol));
11862
12016
  return [
11863
12017
  ...missingSymbols(path, "ours", pair.ours?.hunks ?? [], present2),
11864
12018
  ...missingSymbols(path, "theirs", pair.theirs?.hunks ?? [], present2)
@@ -11874,19 +12028,19 @@ var verifyFile = (context, pair) => {
11874
12028
  const copied = [pair.ours, pair.theirs].find((diff) => diff?.status === "copied");
11875
12029
  const baseSource = copied?.oldPath ?? (created2(pair.ours) || created2(pair.theirs) ? void 0 : pair.key);
11876
12030
  const baseText = baseSource === void 0 ? void 0 : blobText(context.runtime, context.repo, context.inputs.base, baseSource);
11877
- const text7 = path === void 0 ? void 0 : context.resultText(path);
12031
+ const text8 = path === void 0 ? void 0 : context.resultText(path);
11878
12032
  const file = path ?? pair.key;
11879
12033
  const lines3 = checkFile(
11880
12034
  {
11881
12035
  path: file,
11882
12036
  base: contentLines(baseText),
11883
- result: contentLines(text7),
12037
+ result: contentLines(text8),
11884
12038
  ours: pair.ours?.hunks ?? [],
11885
12039
  theirs: pair.theirs?.hunks ?? []
11886
12040
  },
11887
12041
  elsewhereOf(context)
11888
12042
  );
11889
- return [...lines3, ...symbolFindings(context, pair, file, text7)];
12043
+ return [...lines3, ...symbolFindings(context, pair, file, text8)];
11890
12044
  };
11891
12045
 
11892
12046
  // src/git/merge/verify.ts
@@ -12006,8 +12160,8 @@ var hashesOf = (value) => isRecord(value) ? Object.fromEntries(
12006
12160
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
12007
12161
  ) : {};
12008
12162
  var readObject = (runtime, path) => {
12009
- const text7 = runtime.readText(path);
12010
- return text7.ok ? parseJsonObject(text7.value) : text7;
12163
+ const text8 = runtime.readText(path);
12164
+ return text8.ok ? parseJsonObject(text8.value) : text8;
12011
12165
  };
12012
12166
  var readBaseline2 = (runtime, repo, owner) => {
12013
12167
  const parsed = readObject(runtime, fileOf(repo, "baseline", owner));
@@ -12073,7 +12227,7 @@ var foreignFiles = (runtime, repo, owner, targets2) => {
12073
12227
  };
12074
12228
 
12075
12229
  // src/git/gate-status.ts
12076
- var lines = (text7) => text7.split("\n").filter((line2) => line2.trim() !== "");
12230
+ var lines = (text8) => text8.split("\n").filter((line2) => line2.trim() !== "");
12077
12231
  var unpushedSince = (runtime, repo, since) => {
12078
12232
  if (lines(runtime.git(["remote"], repo.root).stdout).length === 0) return [];
12079
12233
  const result = runtime.git(["rev-list", `${since}..HEAD`, "--not", "--remotes"], repo.root);
@@ -12477,8 +12631,8 @@ var subagentStart = (input) => {
12477
12631
  const active = readState(runtime, paths).active;
12478
12632
  if (active === void 0) return { kind: "allow" };
12479
12633
  const disk = (relative11) => {
12480
- const text7 = runtime.readText(toAbsolutePath(join29(paths.root, relative11)));
12481
- return text7.ok ? text7.value : void 0;
12634
+ const text8 = runtime.readText(toAbsolutePath(join29(paths.root, relative11)));
12635
+ return text8.ok ? text8.value : void 0;
12482
12636
  };
12483
12637
  const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
12484
12638
  if (!loaded.ok) return { kind: "allow" };
@@ -12504,8 +12658,8 @@ import { join as join31 } from "node:path";
12504
12658
 
12505
12659
  // src/git/command-text.ts
12506
12660
  var MAX_SHOWN = 160;
12507
- var shown = (text7) => {
12508
- const flat = text7.replace(/\s+/g, " ").trim();
12661
+ var shown = (text8) => {
12662
+ const flat = text8.replace(/\s+/g, " ").trim();
12509
12663
  return flat.length > MAX_SHOWN ? `${flat.slice(0, MAX_SHOWN - 3)}...` : flat;
12510
12664
  };
12511
12665
  var commandText = (command) => shown([command.program, ...command.args].join(" "));
@@ -12855,9 +13009,9 @@ var SSH_URL = /ssh:\/\/(?:[^@\s/]+@)?([A-Za-z0-9.-]+)/g;
12855
13009
  var HOST_KEY = /^\s*-?\s*(?:host|hostname|server|ssh_host|deploy_host|remote_host)\s*:\s*["']?([A-Za-z0-9][A-Za-z0-9.-]*)["']?\s*$/i;
12856
13010
  var hostKey = (raw) => raw.trim().toLowerCase().replace(/^[^@]*@/, "").replace(/^\[([^\]]+)\].*$/, "$1").replace(/:\d+$/, "").replace(/\.$/, "");
12857
13011
  var shellLine = (line2) => line2.replace(/^\s*(?:-\s*)?(?:run:\s*\|?)?/, "");
12858
- var hostsInText = (text7) => {
12859
- const urls = [...text7.matchAll(SSH_URL)].map((match) => match[1] ?? "");
12860
- const lines3 = text7.split(/\r?\n/);
13012
+ var hostsInText = (text8) => {
13013
+ const urls = [...text8.matchAll(SSH_URL)].map((match) => match[1] ?? "");
13014
+ const lines3 = text8.split(/\r?\n/);
12861
13015
  const keyed = lines3.flatMap((line2) => {
12862
13016
  const match = HOST_KEY.exec(line2);
12863
13017
  return match?.[1] === void 0 ? [] : [match[1]];
@@ -12871,8 +13025,8 @@ var workflowFiles = (runtime, root) => {
12871
13025
  return listed4.value.filter((name) => /deploy/i.test(name) && /\.ya?ml$/i.test(name)).map((name) => `.github/workflows/${name}`);
12872
13026
  };
12873
13027
  var detectedHosts = (runtime, root, files) => files.flatMap((file) => {
12874
- const text7 = runtime.readText(toAbsolutePath(join30(root, file)));
12875
- return text7.ok ? hostsInText(text7.value) : [];
13028
+ const text8 = runtime.readText(toAbsolutePath(join30(root, file)));
13029
+ return text8.ok ? hostsInText(text8.value) : [];
12876
13030
  });
12877
13031
  var loadProdTargets = (runtime, root, config) => {
12878
13032
  const workflows = workflowFiles(runtime, root);
@@ -13046,8 +13200,8 @@ var prodGuard = (runtime, root, t) => {
13046
13200
  if (!PROD_PROGRAMS.has(command.program)) return void 0;
13047
13201
  const hit3 = prodHit(command, targets2(), isProd);
13048
13202
  if (hit3 === void 0) return void 0;
13049
- const text7 = commandText(command);
13050
- return hit3.effect === "read" ? { kind: "read", hit: hit3, command: text7 } : blockOf(t, hit3, text7, targets2().deployPath);
13203
+ const text8 = commandText(command);
13204
+ return hit3.effect === "read" ? { kind: "read", hit: hit3, command: text8 } : blockOf(t, hit3, text8, targets2().deployPath);
13051
13205
  }
13052
13206
  };
13053
13207
  };
@@ -13067,8 +13221,8 @@ var recordEvidence = (runtime, root, owner, reads) => {
13067
13221
  const path = toAbsolutePath(join31(root, EVIDENCE_FILE));
13068
13222
  runtime.mkdir(toAbsolutePath(join31(root, ".finitemonkey")));
13069
13223
  const before = runtime.readText(path);
13070
- const text7 = before.ok ? before.value : "";
13071
- runtime.writeAtomic(path, `${text7}${text7 === "" || text7.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
13224
+ const text8 = before.ok ? before.value : "";
13225
+ runtime.writeAtomic(path, `${text8}${text8 === "" || text8.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
13072
13226
  `);
13073
13227
  };
13074
13228
 
@@ -13083,15 +13237,15 @@ var editTexts = (input, file) => {
13083
13237
  if (!Array.isArray(edits)) return [];
13084
13238
  return edits.flatMap((edit) => {
13085
13239
  if (!isRecord(edit)) return [];
13086
- const text7 = optionalString(edit, "new_string");
13087
- return text7 === void 0 ? [] : [{ file, text: text7, whole: false }];
13240
+ const text8 = optionalString(edit, "new_string");
13241
+ return text8 === void 0 ? [] : [{ file, text: text8, whole: false }];
13088
13242
  });
13089
13243
  };
13090
13244
  var writtenTexts = (tool, input) => {
13091
13245
  const file = optionalString(input, "file_path") ?? optionalString(input, "notebook_path") ?? "";
13092
13246
  const single2 = (key, whole) => {
13093
- const text7 = optionalString(input, key);
13094
- return text7 === void 0 ? [] : [{ file, text: text7, whole }];
13247
+ const text8 = optionalString(input, key);
13248
+ return text8 === void 0 ? [] : [{ file, text: text8, whole }];
13095
13249
  };
13096
13250
  switch (tool) {
13097
13251
  case "Write":
@@ -13773,24 +13927,24 @@ var withChain = (facts, chain) => {
13773
13927
  const branch = chain?.branch;
13774
13928
  return branch === void 0 ? facts : { ...facts, currentBranch: () => branch };
13775
13929
  };
13776
- var decide = (context, repo, verdict, facts, text7) => {
13930
+ var decide = (context, repo, verdict, facts, text8) => {
13777
13931
  switch (verdict.kind) {
13778
13932
  case "pass":
13779
13933
  return { kind: "pass" };
13780
13934
  case "block":
13781
- return { kind: "block", rule: verdict.rule, subject: verdict.subject, command: text7 };
13935
+ return { kind: "block", rule: verdict.rule, subject: verdict.subject, command: text8 };
13782
13936
  case "snapshot":
13783
- return { kind: "snapshot", repo, op: verdict.op, command: text7 };
13937
+ return { kind: "snapshot", repo, op: verdict.op, command: text8 };
13784
13938
  case "discard": {
13785
13939
  const entries = facts.dirty();
13786
- if (entries === void 0) return { kind: "unknown", command: text7 };
13940
+ if (entries === void 0) return { kind: "unknown", command: text8 };
13787
13941
  const foreign = foreignFiles(
13788
13942
  context.runtime,
13789
13943
  repo,
13790
13944
  context.owner,
13791
13945
  targetsOf(entries, verdict.discard, facts.prefix)
13792
13946
  );
13793
- return foreign.length > 0 ? { kind: "foreign", files: foreign, command: text7 } : { kind: "snapshot", repo, op: verdict.op, command: text7 };
13947
+ return foreign.length > 0 ? { kind: "foreign", files: foreign, command: text8 } : { kind: "snapshot", repo, op: verdict.op, command: text8 };
13794
13948
  }
13795
13949
  }
13796
13950
  };
@@ -13907,9 +14061,9 @@ var parseJournalLine = (line2) => {
13907
14061
  };
13908
14062
  };
13909
14063
  var readJournal = (runtime, root) => {
13910
- const text7 = runtime.readText(journalPath(root));
13911
- if (!text7.ok) return [];
13912
- return text7.value.split("\n").flatMap((line2) => {
14064
+ const text8 = runtime.readText(journalPath(root));
14065
+ if (!text8.ok) return [];
14066
+ return text8.value.split("\n").flatMap((line2) => {
13913
14067
  const entry2 = line2.trim() === "" ? void 0 : parseJournalLine(line2);
13914
14068
  return entry2 === void 0 ? [] : [entry2];
13915
14069
  });
@@ -14101,9 +14255,9 @@ var review = (input, commands, owner) => {
14101
14255
  return { blocked: void 0, snapshots, reads };
14102
14256
  };
14103
14257
  var gitPreToolUse = (input) => {
14104
- const text7 = shellText(input.payload);
14105
- if (text7 === void 0) return ALLOW;
14106
- const commands = normalizeCommand(text7, dialectOf(toolName2(input.payload))).commands;
14258
+ const text8 = shellText(input.payload);
14259
+ if (text8 === void 0) return ALLOW;
14260
+ const commands = normalizeCommand(text8, dialectOf(toolName2(input.payload))).commands;
14107
14261
  if (commands.length === 0) return ALLOW;
14108
14262
  const owner = ownerOf2(input.payload);
14109
14263
  const result = review(input, commands, owner);
@@ -14165,8 +14319,8 @@ var GIT_HANDLERS = {
14165
14319
  var SHADOW_FILE = "shadow.json";
14166
14320
  var numberOf = (value) => typeof value === "number" && Number.isInteger(value) ? value : 0;
14167
14321
  var readShadow = (runtime, paths) => {
14168
- const text7 = runtime.readText(paths.cache(SHADOW_FILE));
14169
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
14322
+ const text8 = runtime.readText(paths.cache(SHADOW_FILE));
14323
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
14170
14324
  if (parsed?.ok !== true) return {};
14171
14325
  return Object.fromEntries(
14172
14326
  Object.entries(parsed.value).flatMap(
@@ -14250,11 +14404,11 @@ var ASSIGNMENT3 = /^[A-Za-z_][A-Za-z0-9_]*=/;
14250
14404
  var WRAPPERS2 = /* @__PURE__ */ new Set(["sudo", "command", "exec", "time", "nice", "nohup", "builtin", "env"]);
14251
14405
  var liftHeredocs = (command) => {
14252
14406
  const heredocs = [];
14253
- const text7 = command.replace(HEREDOC2, (_match, _quote, _tag, body2) => {
14407
+ const text8 = command.replace(HEREDOC2, (_match, _quote, _tag, body2) => {
14254
14408
  heredocs.push(body2);
14255
14409
  return `<<__FM_HEREDOC_${String(heredocs.length - 1)}__`;
14256
14410
  });
14257
- return { text: text7, heredocs };
14411
+ return { text: text8, heredocs };
14258
14412
  };
14259
14413
  var endWord = (scan) => {
14260
14414
  if (scan.word !== void 0) scan.words.push(scan.word);
@@ -14265,23 +14419,23 @@ var endSegment = (scan) => {
14265
14419
  if (scan.words.length > 0) scan.segments.push(scan.words);
14266
14420
  scan.words = [];
14267
14421
  };
14268
- var quotedEnd2 = (text7, from, quote) => {
14422
+ var quotedEnd2 = (text8, from, quote) => {
14269
14423
  let index = from;
14270
- while (index < text7.length && text7[index] !== quote) index += quote === '"' && text7[index] === "\\" ? 2 : 1;
14424
+ while (index < text8.length && text8[index] !== quote) index += quote === '"' && text8[index] === "\\" ? 2 : 1;
14271
14425
  return index;
14272
14426
  };
14273
14427
  var unescape = (value) => value.replace(/\\(["\\$`])/g, "$1");
14274
- var splitWords = (text7) => {
14428
+ var splitWords = (text8) => {
14275
14429
  const scan = { words: [], segments: [], word: void 0 };
14276
- for (let index = 0; index < text7.length; index += 1) {
14277
- const char = text7[index] ?? "";
14430
+ for (let index = 0; index < text8.length; index += 1) {
14431
+ const char = text8[index] ?? "";
14278
14432
  if (char === '"' || char === "'") {
14279
- const end = quotedEnd2(text7, index + 1, char);
14280
- const inner = text7.slice(index + 1, end);
14433
+ const end = quotedEnd2(text8, index + 1, char);
14434
+ const inner = text8.slice(index + 1, end);
14281
14435
  scan.word = (scan.word ?? "") + (char === '"' ? unescape(inner) : inner);
14282
14436
  index = end;
14283
- } else if (char === "\\" && index + 1 < text7.length) {
14284
- scan.word = (scan.word ?? "") + (text7[index + 1] === "\n" ? "" : text7[index + 1] ?? "");
14437
+ } else if (char === "\\" && index + 1 < text8.length) {
14438
+ scan.word = (scan.word ?? "") + (text8[index + 1] === "\n" ? "" : text8[index + 1] ?? "");
14285
14439
  index += 1;
14286
14440
  } else if (SEPARATOR.test(char)) {
14287
14441
  endSegment(scan);
@@ -14302,8 +14456,8 @@ var segmentOf = (words) => {
14302
14456
  return { program: program.split(/[\\/]/).pop() ?? program, args: words.slice(index + 1) };
14303
14457
  };
14304
14458
  var shellWords = (command) => {
14305
- const { text: text7, heredocs } = liftHeredocs(command);
14306
- const segments = splitWords(text7).flatMap((words) => {
14459
+ const { text: text8, heredocs } = liftHeredocs(command);
14460
+ const segments = splitWords(text8).flatMap((words) => {
14307
14461
  const segment = segmentOf(words);
14308
14462
  return segment === void 0 ? [] : [segment];
14309
14463
  });
@@ -14408,7 +14562,7 @@ var sourceOf = (value, isFile, segment, heredocs) => {
14408
14562
  if (inline3 !== void 0) return { kind: "inline", text: inline3 };
14409
14563
  if (!isFile) return { kind: "inline", text: value };
14410
14564
  if (value !== "-") return { kind: "file", path: value };
14411
- const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text7) => text7 !== void 0);
14565
+ const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text8) => text8 !== void 0);
14412
14566
  return { kind: "inline", text: piped ?? "" };
14413
14567
  };
14414
14568
  var collect3 = (target2, segment, args2, spec2, heredocs) => {
@@ -14505,15 +14659,15 @@ var textOf2 = (input, found) => {
14505
14659
  return accept(parts.join("\n\n"));
14506
14660
  };
14507
14661
  var decisionFor = (input, found, blocking2) => {
14508
- const text7 = textOf2(input, found);
14509
- if (!text7.ok) {
14662
+ const text8 = textOf2(input, found);
14663
+ if (!text8.ok) {
14510
14664
  return blocking2 ? {
14511
14665
  kind: "block",
14512
- reason: modelReason(input.t, "prose.block.unreadable", { ...text7.error }),
14666
+ reason: modelReason(input.t, "prose.block.unreadable", { ...text8.error }),
14513
14667
  human: input.t("prose.block.human")
14514
14668
  } : void 0;
14515
14669
  }
14516
- const report2 = scoreProse(text7.value, { context: "record" });
14670
+ const report2 = scoreProse(text8.value, { context: "record" });
14517
14671
  if (report2.allowed || report2.spans.length === 0) return void 0;
14518
14672
  const params2 = {
14519
14673
  target: input.t(TARGET_KEYS[found.target]),
@@ -14560,7 +14714,7 @@ var HOOK_ACTOR = "finitemonkey";
14560
14714
  var liveFindings = (input, texts2) => {
14561
14715
  const baseline = loadBaseline(input.runtime, input.cwd);
14562
14716
  const accepted2 = acceptedFingerprints(baseline.ok ? baseline.value : EMPTY_BASELINE, input.runtime.now());
14563
- return texts2.flatMap((text7) => scanText(text7, { rules: CREDENTIAL_RULES })).filter((finding4) => !accepted2.has(finding4.fingerprint));
14717
+ return texts2.flatMap((text8) => scanText(text8, { rules: CREDENTIAL_RULES })).filter((finding4) => !accepted2.has(finding4.fingerprint));
14564
14718
  };
14565
14719
  var record2 = (input, findings, draft, where) => {
14566
14720
  const ids2 = findings.map((finding4) => {
@@ -14821,12 +14975,12 @@ var secretsSessionStart = (input) => {
14821
14975
  const remotes = remotesWithCredentials(input.runtime, input.cwd);
14822
14976
  if (remotes.length === 0) return { kind: "allow" };
14823
14977
  const params2 = { remotes: remotes.join(", "), remote: remotes[0] ?? "origin" };
14824
- const text7 = input.t("secrets.session.remote_credential", params2);
14978
+ const text8 = input.t("secrets.session.remote_credential", params2);
14825
14979
  return {
14826
14980
  kind: "allow",
14827
14981
  output: {
14828
- systemMessage: `finitemonkey: ${text7}`,
14829
- hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text7 }
14982
+ systemMessage: `finitemonkey: ${text8}`,
14983
+ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text8 }
14830
14984
  }
14831
14985
  };
14832
14986
  };
@@ -14862,7 +15016,18 @@ var mergeOutputs = (left, right) => {
14862
15016
  if (specific !== void 0) merged2["hookSpecificOutput"] = specific;
14863
15017
  return merged2;
14864
15018
  };
15019
+ var recordBlock = (input, decision) => {
15020
+ const paths = projectPaths(input);
15021
+ if (paths === void 0) return;
15022
+ logEvent(input.runtime, paths, "guard_block", {
15023
+ hook: input.event,
15024
+ tool: toolName2(input.payload),
15025
+ sessionId: input.payload.sessionId ?? null,
15026
+ claim: (decision.reason.split("\n")[0] ?? "").slice(0, 200)
15027
+ });
15028
+ };
14865
15029
  var shaped = (input, decision) => {
15030
+ recordBlock(input, decision);
14866
15031
  if (input.event !== "PreToolUse" || decision.output !== void 0) return decision;
14867
15032
  const reason = capReason(decision.reason);
14868
15033
  return {
@@ -14915,9 +15080,9 @@ var EMPTY_PAYLOAD = {
14915
15080
  stopHookActive: false,
14916
15081
  raw: {}
14917
15082
  };
14918
- var parseHookPayload = (event, text7, interactive) => {
14919
- if (interactive && text7.trim() === "") return accept(EMPTY_PAYLOAD);
14920
- const parsed = parseJsonObject(text7);
15083
+ var parseHookPayload = (event, text8, interactive) => {
15084
+ if (interactive && text8.trim() === "") return accept(EMPTY_PAYLOAD);
15085
+ const parsed = parseJsonObject(text8);
14921
15086
  if (!parsed.ok) return reject(`stdin: ${parsed.error}`);
14922
15087
  const payload = {
14923
15088
  hookEventName: optionalString(parsed.value, "hook_event_name"),
@@ -14977,8 +15142,8 @@ var HOOK_PROBE_ENV = "FINITEMONKEY_HOOK_PROBE";
14977
15142
  var messageOf2 = (error) => error instanceof Error ? error.message : String(error);
14978
15143
  var decide2 = (input, handlers) => composeDecisions(input, handlers[input.event] ?? [allow]);
14979
15144
  var evaluate = async (event, options) => {
14980
- const text7 = await readAll(options.stdin);
14981
- const parsed = parseHookPayload(event, text7, options.stdin.isTTY === true);
15145
+ const text8 = await readAll(options.stdin);
15146
+ const parsed = parseHookPayload(event, text8, options.stdin.isTTY === true);
14982
15147
  if (!parsed.ok) return { kind: "failed", payload: void 0, detail: parsed.error };
14983
15148
  const payload = parsed.value;
14984
15149
  if (options.env?.[HOOK_PROBE_ENV] === "1") return { kind: "decided", payload, decision: { kind: "allow" } };
@@ -15055,7 +15220,7 @@ var runHook = async (event, options) => {
15055
15220
  var PROBE_TIMEOUT_MS2 = 1e4;
15056
15221
  var SLOW_HOOK_MS = 1500;
15057
15222
  var DEFAULT_TIMEOUT_S = 60;
15058
- var expand = (text7, vars) => text7.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
15223
+ var expand = (text8, vars) => text8.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
15059
15224
  const name = braced ?? bare ?? "";
15060
15225
  return vars[name] ?? match;
15061
15226
  });
@@ -15104,7 +15269,7 @@ var run2 = (exec, invocation, cwd, env, input) => exec({
15104
15269
  env: { ...env, [HOOK_PROBE_ENV]: "1" },
15105
15270
  ...input === void 0 ? {} : { input }
15106
15271
  });
15107
- var firstLine = (text7) => maskText(text7.trim().split("\n")[0] ?? "").slice(0, 160);
15272
+ var firstLine = (text8) => maskText(text8.trim().split("\n")[0] ?? "").slice(0, 160);
15108
15273
  var classify2 = (result) => {
15109
15274
  if (!result.found) return { kind: "failed", problem: "missing", detail: "" };
15110
15275
  if (result.timedOut) return { kind: "failed", problem: "timeout", detail: "" };
@@ -15335,8 +15500,8 @@ var installRecord = (value, root) => {
15335
15500
  return records.find((record3) => record3["projectPath"] === root) ?? records[0];
15336
15501
  };
15337
15502
  var readJson = (env, path) => {
15338
- const text7 = env.runtime.readText(path);
15339
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
15503
+ const text8 = env.runtime.readText(path);
15504
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
15340
15505
  return parsed?.ok === true ? parsed.value : void 0;
15341
15506
  };
15342
15507
  var installedPlugin = (env) => {
@@ -15347,13 +15512,13 @@ var installedPlugin = (env) => {
15347
15512
  const root = toAbsolutePath(installPath);
15348
15513
  const manifest = readJson(env, toAbsolutePath(join33(root, ".claude-plugin", "plugin.json")));
15349
15514
  const hooksPath = toAbsolutePath(join33(root, "hooks", "hooks.json"));
15350
- const text7 = env.runtime.readText(hooksPath);
15351
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
15515
+ const text8 = env.runtime.readText(hooksPath);
15516
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
15352
15517
  return {
15353
15518
  root,
15354
15519
  version: (manifest === void 0 ? void 0 : optionalString(manifest, "version")) ?? optionalString(record3, "version"),
15355
15520
  hooksFile: parsed?.ok === true ? { source: "plugin", path: hooksPath, hooks: parsed.value["hooks"] } : void 0,
15356
- hooksText: text7.ok ? text7.value : void 0
15521
+ hooksText: text8.ok ? text8.value : void 0
15357
15522
  };
15358
15523
  };
15359
15524
  var PRECEDENCE2 = ["managed", "local", "project", "user"];
@@ -15383,8 +15548,8 @@ var schemaFinding = (file, problems, claude) => problems.length === 0 ? [] : [
15383
15548
  )
15384
15549
  ];
15385
15550
  var unreadableProjectFiles = (env) => settingsPaths(env).filter(({ scope: scope2 }) => scope2 === "project" || scope2 === "local").flatMap(({ path }) => {
15386
- const text7 = env.runtime.readText(path);
15387
- return text7.ok && text7.value.trim() !== "" && !parseJsonObject(text7.value).ok ? [path] : [];
15551
+ const text8 = env.runtime.readText(path);
15552
+ return text8.ok && text8.value.trim() !== "" && !parseJsonObject(text8.value).ok ? [path] : [];
15388
15553
  });
15389
15554
  var schemaFindings = (env, plugin, claude) => {
15390
15555
  const label2 = claude === void 0 ? "?" : claude.join(".");
@@ -15470,15 +15635,15 @@ var TOP_INSTRUCTIONS = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.l
15470
15635
  var RULES_DIR = ".claude/rules";
15471
15636
  var SHOWN = 3;
15472
15637
  var readProjectFile = (env, relative11) => {
15473
- const text7 = env.runtime.readText(toAbsolutePath(join35(env.root, ...relative11.split("/"))));
15474
- return text7.ok ? text7.value : void 0;
15638
+ const text8 = env.runtime.readText(toAbsolutePath(join35(env.root, ...relative11.split("/"))));
15639
+ return text8.ok ? text8.value : void 0;
15475
15640
  };
15476
15641
  var instructionFiles = (env) => {
15477
15642
  const listed4 = env.runtime.listDir(toAbsolutePath(join35(env.root, ...RULES_DIR.split("/"))));
15478
15643
  const rules = listed4.ok ? listed4.value.filter((name) => name.endsWith(".md")).map((name) => `${RULES_DIR}/${name}`) : [];
15479
15644
  return [...TOP_INSTRUCTIONS, ...rules].flatMap((path) => {
15480
- const text7 = readProjectFile(env, path);
15481
- return text7 === void 0 ? [] : [{ path, text: text7 }];
15645
+ const text8 = readProjectFile(env, path);
15646
+ return text8 === void 0 ? [] : [{ path, text: text8 }];
15482
15647
  });
15483
15648
  };
15484
15649
  var shortList = (items) => items.length <= SHOWN ? items.join(", ") : `${items.slice(0, SHOWN).join(", ")} (+${String(items.length - SHOWN)})`;
@@ -15526,10 +15691,10 @@ var hiddenInstructions = (env, deny2) => {
15526
15691
  ];
15527
15692
  };
15528
15693
  var localFile = (env) => readProjectFile(env, "CLAUDE.local.md") === void 0 ? [] : [finding("instructions.local", "warn", "doctor.instructions.local", {}, "doctor.instructions.fix_local")];
15529
- var lineCount = (text7) => text7.split("\n").length - (text7.endsWith("\n") ? 1 : 0);
15694
+ var lineCount = (text8) => text8.split("\n").length - (text8.endsWith("\n") ? 1 : 0);
15530
15695
  var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
15531
- const text7 = readProjectFile(env, file);
15532
- const lines3 = text7 === void 0 ? 0 : lineCount(text7);
15696
+ const text8 = readProjectFile(env, file);
15697
+ const lines3 = text8 === void 0 ? 0 : lineCount(text8);
15533
15698
  if (lines3 <= INSTRUCTION_LINE_CEILING) return [];
15534
15699
  return [
15535
15700
  finding(
@@ -15542,13 +15707,13 @@ var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
15542
15707
  ];
15543
15708
  });
15544
15709
  var MIN_RULE_WORDS = 6;
15545
- var ruleLines = (text7) => text7.split("\n").map((raw) => withoutCarriageReturn(raw).trim()).filter((line2) => !/^(#|\||```|@|>)/.test(line2)).map(
15710
+ var ruleLines = (text8) => text8.split("\n").map((raw) => withoutCarriageReturn(raw).trim()).filter((line2) => !/^(#|\||```|@|>)/.test(line2)).map(
15546
15711
  (line2) => line2.replace(/^([-*+]|\d+[.)])\s+/, "").replace(/\s+/g, " ").toLowerCase()
15547
15712
  ).filter((line2) => line2.split(" ").length >= MIN_RULE_WORDS);
15548
15713
  var duplicatedRules = (files) => {
15549
15714
  const seen = /* @__PURE__ */ new Map();
15550
- for (const { path, text: text7 } of files) {
15551
- for (const line2 of ruleLines(text7)) seen.set(line2, (seen.get(line2) ?? /* @__PURE__ */ new Set()).add(path));
15715
+ for (const { path, text: text8 } of files) {
15716
+ for (const line2 of ruleLines(text8)) seen.set(line2, (seen.get(line2) ?? /* @__PURE__ */ new Set()).add(path));
15552
15717
  }
15553
15718
  return [...seen].filter(([, paths]) => paths.size > 1).map(([line2]) => line2);
15554
15719
  };
@@ -15598,13 +15763,13 @@ var ceilingOf = (config) => {
15598
15763
  const parsed = typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
15599
15764
  return Number.isInteger(parsed) && parsed > 0 ? parsed : AGENTS_MAX_LINES;
15600
15765
  };
15601
- var lineCount2 = (text7) => text7.replace(/\r?\n$/, "").split(/\r?\n/).length;
15766
+ var lineCount2 = (text8) => text8.replace(/\r?\n$/, "").split(/\r?\n/).length;
15602
15767
  var checkAgentsFile = ({ root, runtime }) => {
15603
- const text7 = runtime.readText(toAbsolutePath(join37(root, AGENTS_FILE)));
15604
- if (!text7.ok) return [];
15768
+ const text8 = runtime.readText(toAbsolutePath(join37(root, AGENTS_FILE)));
15769
+ if (!text8.ok) return [];
15605
15770
  const config = runtime.readText(toAbsolutePath(join37(root, CONFIG_FILE)));
15606
15771
  const max = ceilingOf(config.ok ? config.value : void 0);
15607
- const lines3 = lineCount2(text7.value);
15772
+ const lines3 = lineCount2(text8.value);
15608
15773
  const params2 = { file: AGENTS_FILE, lines: lines3, max };
15609
15774
  return lines3 > max ? [finding("memory.agents_lines", "fail", "doctor.memory.too_long", params2, "doctor.memory.fix_too_long")] : [finding("memory.agents_lines", "ok", "doctor.memory.lines", params2)];
15610
15775
  };
@@ -15785,9 +15950,9 @@ var manualLines = (ctx, report2) => report2.checks.some((item2) => item2.status
15785
15950
  var writeReport = (ctx, data, result) => {
15786
15951
  const env = doctorEnvFrom(ctx);
15787
15952
  const path = reportPath(env);
15788
- const text7 = reportMarkdown(env, ctx, toEnvelope("doctor", result));
15953
+ const text8 = reportMarkdown(env, ctx, toEnvelope("doctor", result));
15789
15954
  const dir = ctx.runtime.mkdir(toAbsolutePath(dirname7(path)));
15790
- const written = dir.ok ? ctx.runtime.writeAtomic(path, text7) : dir;
15955
+ const written = dir.ok ? ctx.runtime.writeAtomic(path, text8) : dir;
15791
15956
  if (!written.ok) return failureFrom(ctx.t, applyProblem({ path, detail: written.error }));
15792
15957
  const human2 = [...result.human ?? [], ctx.t("doctor.report.written", { path })];
15793
15958
  return { ...result, data: { ...data, reportPath: path }, human: human2 };
@@ -15818,6 +15983,100 @@ var doctorCommand = {
15818
15983
  run: (ctx, args2) => Promise.resolve(runDoctor(ctx, args2.values["fix"] === true, args2.values["report"] === true))
15819
15984
  };
15820
15985
 
15986
+ // src/gain/gain.ts
15987
+ import { join as join39 } from "node:path";
15988
+ var GAIN_SCHEMA = "finitemonkey/gain@1";
15989
+ var DAY_MS2 = 864e5;
15990
+ var SAVE_WINDOW_MS = DAY_MS2;
15991
+ var readLog = (runtime, paths) => {
15992
+ const text8 = runtime.readText(paths.log);
15993
+ if (!text8.ok) return [];
15994
+ return text8.value.split("\n").flatMap((line2) => {
15995
+ const parsed = parseJsonObject(line2);
15996
+ if (!parsed.ok || !isRecord(parsed.value)) return [];
15997
+ const { at, event } = parsed.value;
15998
+ const time = typeof at === "string" ? Date.parse(at) : Number.NaN;
15999
+ return typeof event === "string" && Number.isFinite(time) ? [{ at: time, event }] : [];
16000
+ });
16001
+ };
16002
+ var readGates = (runtime, paths) => {
16003
+ const listed4 = runtime.listDir(toAbsolutePath(join39(paths.dir, "evidence")));
16004
+ const files = listed4.ok ? listed4.value.filter((name) => name.endsWith(".jsonl")) : [];
16005
+ return files.flatMap((name) => readLedger(runtime, paths.evidence(name.replace(/\.jsonl$/, "")))).filter((entry2) => entry2.check === GATE_CHECK);
16006
+ };
16007
+ var countsIn = (log, gates, from, to) => {
16008
+ const inWindow = (at) => at >= from && at < to;
16009
+ const lines3 = log.filter((line2) => inWindow(line2.at));
16010
+ const runs = gates.filter((entry2) => inWindow(Date.parse(entry2.at)));
16011
+ const passedAt = gates.filter((entry2) => entry2.status === "passed").map((entry2) => Date.parse(entry2.at));
16012
+ const blocks = lines3.filter((line2) => line2.event === "guard_block");
16013
+ return {
16014
+ blocks: blocks.length,
16015
+ saves: blocks.filter((block4) => passedAt.some((at) => at > block4.at && at - block4.at <= SAVE_WINDOW_MS)).length,
16016
+ releases: lines3.filter((line2) => line2.event === "person.allow").length,
16017
+ falsePositives: lines3.filter((line2) => line2.event === "person.allow").length + lines3.filter((line2) => line2.event === "guard_false_positive").length,
16018
+ gateRuns: runs.length,
16019
+ gateFailed: runs.filter((entry2) => entry2.status === "failed").length,
16020
+ gateUnverified: runs.filter((entry2) => entry2.status === "unverified").length,
16021
+ gateMs: runs.reduce((sum, entry2) => sum + (entry2.durationMs ?? 0), 0)
16022
+ };
16023
+ };
16024
+ var gainReport = (runtime, paths, days) => {
16025
+ const to = Date.parse(runtime.now());
16026
+ const from = to - days * DAY_MS2;
16027
+ const log = readLog(runtime, paths);
16028
+ const gates = readGates(runtime, paths);
16029
+ return {
16030
+ schema: GAIN_SCHEMA,
16031
+ days,
16032
+ from: new Date(from).toISOString(),
16033
+ to: new Date(to).toISOString(),
16034
+ current: countsIn(log, gates, from, to + 1),
16035
+ previous: countsIn(log, gates, from - days * DAY_MS2, from)
16036
+ };
16037
+ };
16038
+
16039
+ // src/commands/gain.ts
16040
+ var DEFAULT_DAYS = 30;
16041
+ var trend = (ctx, now, before) => now === before ? "" : ` (${ctx.t(now > before ? "gain.up" : "gain.down", { delta: Math.abs(now - before) })})`;
16042
+ var ROWS = [
16043
+ { key: "gain.row.blocks", pick: (counts) => counts.blocks },
16044
+ { key: "gain.row.saves", pick: (counts) => counts.saves },
16045
+ { key: "gain.row.false_positives", pick: (counts) => counts.falsePositives },
16046
+ { key: "gain.row.gate_runs", pick: (counts) => counts.gateRuns },
16047
+ { key: "gain.row.gate_failed", pick: (counts) => counts.gateFailed },
16048
+ { key: "gain.row.gate_unverified", pick: (counts) => counts.gateUnverified }
16049
+ ];
16050
+ var linesOf = (ctx, report2) => [
16051
+ ctx.t("gain.title", { days: report2.days }),
16052
+ ...ROWS.map(
16053
+ ({ key, pick: pick4 }) => ` ${ctx.t(key, { count: pick4(report2.current) })}${trend(ctx, pick4(report2.current), pick4(report2.previous))}`
16054
+ ),
16055
+ ` ${ctx.t("gain.row.gate_time", { minutes: Math.round(report2.current.gateMs / 6e4) })}`,
16056
+ ctx.t("gain.footer")
16057
+ ];
16058
+ var runGain = (ctx, rawDays) => {
16059
+ const days = rawDays === void 0 ? DEFAULT_DAYS : Number.parseInt(rawDays, 10);
16060
+ if (!Number.isInteger(days) || days < 1 || days > 365) {
16061
+ return failureFrom(
16062
+ ctx.t,
16063
+ usageProblem("invalid_arguments", "error.invalid_arguments", "finitemonkey gain --days 30", {
16064
+ detail: "--days 1-365"
16065
+ })
16066
+ );
16067
+ }
16068
+ const report2 = gainReport(ctx.runtime, fmPaths(findRepoRoot(ctx.runtime, ctx.cwd) ?? ctx.cwd), days);
16069
+ return success(report2, linesOf(ctx, report2));
16070
+ };
16071
+ var gainCommand = {
16072
+ path: ["gain"],
16073
+ summaryKey: "command.gain.summary",
16074
+ usage: "finitemonkey gain [--days 1-365]",
16075
+ status: "ready",
16076
+ options: { days: { type: "string" } },
16077
+ run: (ctx, args2) => Promise.resolve(runGain(ctx, stringOption(args2.values, "days")))
16078
+ };
16079
+
15821
16080
  // src/gate/baseline-run.ts
15822
16081
  var runBaseline = (options) => {
15823
16082
  const ready = prepareGate({ ...options, noSkip: true });
@@ -16113,15 +16372,15 @@ var commitMsgCommand = {
16113
16372
  usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: "<message-file>" })
16114
16373
  )
16115
16374
  );
16116
- const text7 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
16117
- if (!text7.ok)
16375
+ const text8 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
16376
+ if (!text8.ok)
16118
16377
  return Promise.resolve(
16119
16378
  failureFrom(
16120
16379
  ctx.t,
16121
16380
  usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: file })
16122
16381
  )
16123
16382
  );
16124
- const failures = commitMessageProblems(text7.value);
16383
+ const failures = commitMessageProblems(text8.value);
16125
16384
  const fix = "git commit --amend, using type(scope): subject and no AI attribution lines";
16126
16385
  return Promise.resolve(
16127
16386
  checksResult(ctx.t, COMMIT_MSG_SCHEMA, "githook_failed", [{ id: "commit-messages", failures, fix }])
@@ -16259,11 +16518,11 @@ var mergeVerifyCommand = {
16259
16518
  var REFLOG_LIMIT = 300;
16260
16519
  var LIST_LIMIT = 50;
16261
16520
  var SHA = /^[0-9a-f]{40,64}$/;
16262
- var linesOf = (text7) => text7.split("\n").filter((line2) => line2.trim() !== "");
16521
+ var linesOf2 = (text8) => text8.split("\n").filter((line2) => line2.trim() !== "");
16263
16522
  var reflogLines = (runtime, repo) => {
16264
16523
  const result = runtime.git(["reflog", "show", "--all", `-n${String(REFLOG_LIMIT)}`, "--format=%H %gD"], repo.root);
16265
16524
  if (!result.ok) return [];
16266
- return linesOf(result.stdout).flatMap((line2) => {
16525
+ return linesOf2(result.stdout).flatMap((line2) => {
16267
16526
  const [sha, ref] = line2.split(" ");
16268
16527
  return sha === void 0 || ref === void 0 || !SHA.test(sha) || ref.startsWith("refs/finitemonkey/") ? [] : [{ sha, ref }];
16269
16528
  });
@@ -16271,11 +16530,11 @@ var reflogLines = (runtime, repo) => {
16271
16530
  var unreachable = (runtime, repo, shas) => {
16272
16531
  if (shas.length === 0) return /* @__PURE__ */ new Set();
16273
16532
  const result = runtime.git(["rev-list", ...shas, "--not", "--branches", "--tags", "--remotes"], repo.root);
16274
- return new Set(result.ok ? linesOf(result.stdout) : []);
16533
+ return new Set(result.ok ? linesOf2(result.stdout) : []);
16275
16534
  };
16276
16535
  var fsckDangling = (runtime, repo) => {
16277
16536
  const result = runtime.git(["fsck", "--no-reflogs", "--no-progress", "--dangling"], repo.root);
16278
- const lines3 = linesOf(result.stdout);
16537
+ const lines3 = linesOf2(result.stdout);
16279
16538
  const pick4 = (kind) => lines3.flatMap((line2) => {
16280
16539
  const [label2, type, sha] = line2.split(" ");
16281
16540
  return label2 === "dangling" && type === kind && sha !== void 0 ? [sha] : [];
@@ -16286,7 +16545,7 @@ var describe = (runtime, repo, shas) => {
16286
16545
  if (shas.length === 0) return /* @__PURE__ */ new Map();
16287
16546
  const result = runtime.git(["log", "--no-walk=unsorted", "--format=%H%x09%cr%x09%s", ...shas], repo.root);
16288
16547
  return new Map(
16289
- linesOf(result.stdout).flatMap((line2) => {
16548
+ linesOf2(result.stdout).flatMap((line2) => {
16290
16549
  const [sha, when, ...subject2] = line2.split(" ");
16291
16550
  return sha === void 0 ? [] : [[sha, [when ?? "", subject2.join(" ")]]];
16292
16551
  })
@@ -16380,7 +16639,7 @@ var rescueCommand = {
16380
16639
  };
16381
16640
 
16382
16641
  // src/git/undo.ts
16383
- import { basename as basename3, dirname as dirname8, join as join39 } from "node:path";
16642
+ import { basename as basename3, dirname as dirname8, join as join40 } from "node:path";
16384
16643
  var INDEX_SUBJECT = "finitemonkey index:";
16385
16644
  var gitLine = (runtime, repo, args2) => {
16386
16645
  const result = runtime.git(args2, repo.root);
@@ -16423,7 +16682,7 @@ var findSnapshot = (views, wanted) => {
16423
16682
  };
16424
16683
  var stampOf2 = (iso) => iso.replace(/[:.]/g, "-");
16425
16684
  var worktreePath = (runtime, repo) => {
16426
- const base = join39(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
16685
+ const base = join40(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
16427
16686
  return toAbsolutePath(runtime.stat(toAbsolutePath(base)) === void 0 ? base : `${base}-2`);
16428
16687
  };
16429
16688
  var intoWorktree = (runtime, repo, view2) => {
@@ -16548,7 +16807,7 @@ var GLOBAL_FLAGS = [
16548
16807
  { flag: "--help, -h", key: "flag.help" }
16549
16808
  ];
16550
16809
  var exitCodeHelp = (ctx) => EXIT_CODE_NAMES.map((name) => ({ code: ExitCode[name], name, summary: ctx.t(exitCodeDocs[ExitCode[name]]) }));
16551
- var pad = (text7, width) => text7.padEnd(Math.max(width, text7.length + 1));
16810
+ var pad = (text8, width) => text8.padEnd(Math.max(width, text8.length + 1));
16552
16811
  var describe2 = (ctx, spec2) => ({
16553
16812
  name: commandName(spec2),
16554
16813
  usage: spec2.usage,
@@ -16658,7 +16917,7 @@ var hookCommand = {
16658
16917
  };
16659
16918
 
16660
16919
  // src/commands/i18n-check.ts
16661
- import { join as join40 } from "node:path";
16920
+ import { join as join41 } from "node:path";
16662
16921
 
16663
16922
  // src/i18n/message.ts
16664
16923
  var ARG = /^\s*([a-zA-Z][a-zA-Z0-9_]*)\s*(?:,\s*(plural|select)\s*,)?/;
@@ -16690,10 +16949,10 @@ var parseBranches = (body2, inPlural) => {
16690
16949
  };
16691
16950
  var parseNodes = (source, inPlural) => {
16692
16951
  const nodes = [];
16693
- let text7 = "";
16952
+ let text8 = "";
16694
16953
  const flush2 = () => {
16695
- if (text7 !== "") nodes.push({ kind: "text", text: text7 });
16696
- text7 = "";
16954
+ if (text8 !== "") nodes.push({ kind: "text", text: text8 });
16955
+ text8 = "";
16697
16956
  };
16698
16957
  for (let index = 0; index < source.length; index += 1) {
16699
16958
  const char = source.charAt(index);
@@ -16703,7 +16962,7 @@ var parseNodes = (source, inPlural) => {
16703
16962
  continue;
16704
16963
  }
16705
16964
  if (char !== "{") {
16706
- text7 += char;
16965
+ text8 += char;
16707
16966
  continue;
16708
16967
  }
16709
16968
  const cursor = { source, index: index + 1 };
@@ -16711,18 +16970,18 @@ var parseNodes = (source, inPlural) => {
16711
16970
  const inner = end < 0 ? void 0 : source.slice(index + 1, end);
16712
16971
  const match = inner === void 0 ? null : ARG.exec(inner);
16713
16972
  if (inner === void 0 || match === null) {
16714
- text7 += char;
16973
+ text8 += char;
16715
16974
  continue;
16716
16975
  }
16717
16976
  const name = match[1] ?? "";
16718
16977
  const type = match[2] === "plural" || match[2] === "select" ? match[2] : void 0;
16719
16978
  if (type === void 0 && match[0].trim().length !== inner.trim().length) {
16720
- text7 += char;
16979
+ text8 += char;
16721
16980
  continue;
16722
16981
  }
16723
16982
  const branches = type === void 0 ? void 0 : parseBranches(inner.slice(match[0].length), type === "plural");
16724
16983
  if (type !== void 0 && branches === void 0) {
16725
- text7 += char;
16984
+ text8 += char;
16726
16985
  continue;
16727
16986
  }
16728
16987
  flush2();
@@ -16845,8 +17104,8 @@ var RELEASE_HINTS = [
16845
17104
  /\bFINITEMONKEY_[A-Z_]+/,
16846
17105
  /\b[A-Z][A-Z0-9_]{2,}=/
16847
17106
  ];
16848
- var releaseHints = (text7) => RELEASE_HINTS.flatMap((pattern) => {
16849
- const match = pattern.exec(text7);
17107
+ var releaseHints = (text8) => RELEASE_HINTS.flatMap((pattern) => {
17108
+ const match = pattern.exec(text8);
16850
17109
  return match === null ? [] : [match[0]];
16851
17110
  });
16852
17111
  var hintProblems = (lang, catalog) => Object.keys(catalog).filter((key) => MODEL_FACING.test(key)).sort().flatMap((key) => {
@@ -16884,10 +17143,10 @@ var unreadable = (dir, file, detail) => ({
16884
17143
  exitCode: ExitCode.NotConfigured
16885
17144
  });
16886
17145
  var readCatalog = (ctx, dir, lang) => {
16887
- const file = toAbsolutePath(join40(dir, `${lang}.json`));
16888
- const text7 = ctx.runtime.readText(file);
16889
- if (!text7.ok) return reject(unreadable(dir, file, text7.error));
16890
- const parsed = parseJsonObject(text7.value);
17146
+ const file = toAbsolutePath(join41(dir, `${lang}.json`));
17147
+ const text8 = ctx.runtime.readText(file);
17148
+ if (!text8.ok) return reject(unreadable(dir, file, text8.error));
17149
+ const parsed = parseJsonObject(text8.value);
16891
17150
  return parsed.ok ? parsed : reject(unreadable(dir, file, parsed.error));
16892
17151
  };
16893
17152
  var loadCatalogs = (ctx, dir) => {
@@ -16907,8 +17166,8 @@ var describeProblem = (ctx, problem4) => {
16907
17166
  return ` ${problem4.lang} ${problem4.key} ${kind}`;
16908
17167
  };
16909
17168
  var loadReview = (ctx, dir) => {
16910
- const text7 = ctx.runtime.readText(toAbsolutePath(join40(dir, "review.json")));
16911
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
17169
+ const text8 = ctx.runtime.readText(toAbsolutePath(join41(dir, "review.json")));
17170
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
16912
17171
  if (parsed?.ok !== true) return void 0;
16913
17172
  const statuses = (lang) => {
16914
17173
  const value = parsed.value[lang];
@@ -16948,7 +17207,7 @@ var i18nCheckCommand = {
16948
17207
  };
16949
17208
 
16950
17209
  // src/history/look-back.ts
16951
- import { join as join41 } from "node:path";
17210
+ import { join as join42 } from "node:path";
16952
17211
 
16953
17212
  // src/history/patterns.ts
16954
17213
  import { posix as posix5 } from "node:path";
@@ -17053,8 +17312,8 @@ var addResults = (draft, blocks) => {
17053
17312
  let any = false;
17054
17313
  for (const block4 of blocks) {
17055
17314
  if (!isRecord(block4) || block4["type"] !== "tool_result" || typeof block4["tool_use_id"] !== "string") continue;
17056
- const text7 = textOf3(block4["content"]);
17057
- draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(text7), text: text7 });
17315
+ const text8 = textOf3(block4["content"]);
17316
+ draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(text8), text: text8 });
17058
17317
  any = true;
17059
17318
  }
17060
17319
  return any;
@@ -17084,7 +17343,7 @@ var parseTranscript = (jsonl) => {
17084
17343
  // src/history/look-back.ts
17085
17344
  var SAMPLES = 3;
17086
17345
  var projectLogKey = (root) => root.replace(/[^A-Za-z0-9-]/g, "-");
17087
- var projectLogDir = (runtime, root, env) => join41(env["CLAUDE_CONFIG_DIR"] ?? join41(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
17346
+ var projectLogDir = (runtime, root, env) => join42(env["CLAUDE_CONFIG_DIR"] ?? join42(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
17088
17347
  var lookBack = (runtime, root, env) => {
17089
17348
  const dir = projectLogDir(runtime, root, env);
17090
17349
  const listed4 = runtime.listDir(toAbsolutePath(dir));
@@ -17105,9 +17364,9 @@ var lookBack = (runtime, root, env) => {
17105
17364
  };
17106
17365
  let turns = 0;
17107
17366
  for (const name of files) {
17108
- const text7 = runtime.readText(toAbsolutePath(join41(dir, name)));
17109
- if (!text7.ok) continue;
17110
- for (const turn of parseTranscript(text7.value)) {
17367
+ const text8 = runtime.readText(toAbsolutePath(join42(dir, name)));
17368
+ if (!text8.ok) continue;
17369
+ for (const turn of parseTranscript(text8.value)) {
17111
17370
  turns += 1;
17112
17371
  for (const hit3 of hitsIn(turn)) {
17113
17372
  counts[hit3.pattern] += 1;
@@ -17155,7 +17414,7 @@ var walk2 = (a, b) => {
17155
17414
  }
17156
17415
  return ops;
17157
17416
  };
17158
- var splitLines = (text7) => text7 === "" ? [] : text7.replace(/\r?\n$/, "").split(/\r?\n/);
17417
+ var splitLines = (text8) => text8 === "" ? [] : text8.replace(/\r?\n$/, "").split(/\r?\n/);
17159
17418
  var sharedPrefix = (a, b) => {
17160
17419
  let count4 = 0;
17161
17420
  while (count4 < a.length && count4 < b.length && a[count4] === b[count4]) count4 += 1;
@@ -17232,7 +17491,7 @@ var planData = (t, plan, mode, applied, backup) => ({
17232
17491
  diff: planDiff(plan),
17233
17492
  ...backup === void 0 ? {} : { backup }
17234
17493
  });
17235
- var pad2 = (text7, width) => text7.padEnd(width);
17494
+ var pad2 = (text8, width) => text8.padEnd(width);
17236
17495
  var planLines = (t, data, withDiff) => [
17237
17496
  t("install.plan.title", { root: data.root, preset: data.preset }),
17238
17497
  ...data.actions.map(({ path, action }) => ` ${pad2(action, 13)}${path}`),
@@ -17357,7 +17616,7 @@ var applyAllows = (findings, allows, decided) => {
17357
17616
 
17358
17617
  // src/look/node-capture.ts
17359
17618
  import { mkdirSync as mkdirSync3 } from "node:fs";
17360
- import { join as join42 } from "node:path";
17619
+ import { join as join43 } from "node:path";
17361
17620
 
17362
17621
  // src/look/measure.ts
17363
17622
  var measurePage = (doc) => {
@@ -17368,8 +17627,8 @@ var measurePage = (doc) => {
17368
17627
  const walker = doc.createTreeWalker(doc.body, SHOW_TEXT);
17369
17628
  const keys = /* @__PURE__ */ new Set();
17370
17629
  for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
17371
- const text7 = (node.textContent ?? "").trim();
17372
- if (KEY2.test(text7) && !NOT_KEY.test(text7) || PLACEHOLDER2.test(text7)) keys.add(text7.slice(0, 80));
17630
+ const text8 = (node.textContent ?? "").trim();
17631
+ if (KEY2.test(text8) && !NOT_KEY.test(text8) || PLACEHOLDER2.test(text8)) keys.add(text8.slice(0, 80));
17373
17632
  }
17374
17633
  const small = [...doc.querySelectorAll("a, button, input, select, textarea, [role=button], [role=link]")].filter((element) => {
17375
17634
  const box = element.getBoundingClientRect();
@@ -17402,8 +17661,8 @@ var slugOf = (route) => {
17402
17661
  var watch = (page) => {
17403
17662
  const errors = [];
17404
17663
  const failed4 = [];
17405
- const push = (list4, text7) => {
17406
- if (list4.length < MAX_EVENTS) list4.push(text7.slice(0, 200));
17664
+ const push = (list4, text8) => {
17665
+ if (list4.length < MAX_EVENTS) list4.push(text8.slice(0, 200));
17407
17666
  };
17408
17667
  page.on("console", (message) => {
17409
17668
  if (message.type() === "error" && !RESOURCE_ERROR.test(message.text())) push(errors, message.text());
@@ -17433,7 +17692,7 @@ var captureOne = async (browser, request, route, viewport) => {
17433
17692
  const context = await browser.newContext({ viewport: { width: viewport, height: HEIGHT }, reducedMotion: "reduce" });
17434
17693
  const page = await context.newPage();
17435
17694
  const events = watch(page);
17436
- const path = join42(request.dir, `${slugOf(route)}-${String(viewport)}.png`);
17695
+ const path = join43(request.dir, `${slugOf(route)}-${String(viewport)}.png`);
17437
17696
  const loadError = await loadAndShoot(page, `${request.baseUrl}${route}`, path, request.timeoutMs);
17438
17697
  const measure = loadError === void 0 ? parseMeasure(await page.evaluate(MEASURE_SCRIPT)) : void 0;
17439
17698
  await context.close();
@@ -17450,7 +17709,7 @@ var captureAll = async (browser, request) => {
17450
17709
 
17451
17710
  // src/look/node-playwright.ts
17452
17711
  import { createRequire } from "node:module";
17453
- import { join as join43 } from "node:path";
17712
+ import { join as join44 } from "node:path";
17454
17713
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
17455
17714
  var NAMES = ["playwright", "playwright-core"];
17456
17715
  var isPlaywright = (value) => isRecord(value) && isRecord(value["chromium"]) && typeof value["chromium"]["launch"] === "function";
@@ -17465,7 +17724,7 @@ var tryLoad = async (base, name) => {
17465
17724
  }
17466
17725
  };
17467
17726
  var loadPlaywright = async (root) => {
17468
- for (const base of [join43(root, "package.json"), fileURLToPath3(import.meta.url)]) {
17727
+ for (const base of [join44(root, "package.json"), fileURLToPath3(import.meta.url)]) {
17469
17728
  for (const name of NAMES) {
17470
17729
  const found = await tryLoad(base, name);
17471
17730
  if (found !== void 0) return found;
@@ -17486,8 +17745,8 @@ var launchBrowser = async (playwright) => {
17486
17745
 
17487
17746
  // src/look/node-sheet.ts
17488
17747
  import { readFileSync as readFileSync2 } from "node:fs";
17489
- import { join as join44 } from "node:path";
17490
- var escapeHtml = (text7) => text7.replace(/[&<>"]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[char] ?? char);
17748
+ import { join as join45 } from "node:path";
17749
+ var escapeHtml = (text8) => text8.replace(/[&<>"]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[char] ?? char);
17491
17750
  var card = (result) => {
17492
17751
  const image = (() => {
17493
17752
  try {
@@ -17509,7 +17768,7 @@ var contactSheet = async (browser, results, dir) => {
17509
17768
  if (shown3.length === 0) return void 0;
17510
17769
  const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
17511
17770
  await page.setContent(`<!doctype html><style>${STYLE}</style>${shown3.map(card).join("")}`);
17512
- const path = join44(dir, "contact-sheet.png");
17771
+ const path = join45(dir, "contact-sheet.png");
17513
17772
  await page.screenshot({ path, fullPage: true });
17514
17773
  await page.close();
17515
17774
  return path;
@@ -17849,12 +18108,12 @@ var proseCheckCommand = {
17849
18108
  };
17850
18109
 
17851
18110
  // src/review/packet.ts
17852
- import { join as join45 } from "node:path";
18111
+ import { join as join46 } from "node:path";
17853
18112
  var REVIEW_STATE_FILE = ".finitemonkey/review/state.json";
17854
18113
  var FULL_ROUNDS = 3;
17855
18114
  var REVIEW_FILE_MAX_LINES = 200;
17856
18115
  var LEDGER_MAX_LINES = 100;
17857
- var statePath = (root) => toAbsolutePath(join45(root, REVIEW_STATE_FILE));
18116
+ var statePath = (root) => toAbsolutePath(join46(root, REVIEW_STATE_FILE));
17858
18117
  var readReviewState = (runtime, root) => {
17859
18118
  const raw = runtime.readText(statePath(root));
17860
18119
  const parsed = raw.ok ? parseJsonObject(raw.value) : void 0;
@@ -17866,7 +18125,7 @@ var git = (runtime, root, args2) => {
17866
18125
  const result = runtime.git(args2, root);
17867
18126
  return result.ok ? result.stdout : void 0;
17868
18127
  };
17869
- var lastLines = (text7, max) => text7.split("\n").slice(-max).join("\n");
18128
+ var lastLines = (text8, max) => text8.split("\n").slice(-max).join("\n");
17870
18129
  var section = (title, body2, empty) => `## ${title}
17871
18130
 
17872
18131
  ${body2 === void 0 || body2.trim() === "" ? empty : body2.trimEnd()}
@@ -17876,7 +18135,7 @@ var taskSections = (runtime, root) => {
17876
18135
  const active = readState(runtime, paths).active;
17877
18136
  if (active === void 0)
17878
18137
  return [section("Contract", void 0, "(no active task)"), section("Evidence ledger", void 0, "(none)")];
17879
- const contract = runtime.readText(toAbsolutePath(join45(root, taskRelative(active))));
18138
+ const contract = runtime.readText(toAbsolutePath(join46(root, taskRelative(active))));
17880
18139
  const ledger = runtime.readText(paths.evidence(active));
17881
18140
  return [
17882
18141
  section("Contract", contract.ok ? contract.value : void 0, "(contract file missing)"),
@@ -17884,8 +18143,8 @@ var taskSections = (runtime, root) => {
17884
18143
  ];
17885
18144
  };
17886
18145
  var reviewFile = (runtime, root, base) => {
17887
- const text7 = git(runtime, root, ["show", `${base}:REVIEW.md`]);
17888
- return text7 === void 0 ? void 0 : text7.split("\n").slice(0, REVIEW_FILE_MAX_LINES).join("\n");
18146
+ const text8 = git(runtime, root, ["show", `${base}:REVIEW.md`]);
18147
+ return text8 === void 0 ? void 0 : text8.split("\n").slice(0, REVIEW_FILE_MAX_LINES).join("\n");
17889
18148
  };
17890
18149
  var nextState = (previous, base, head) => {
17891
18150
  if (previous?.base !== base) return { base, rounds: 1, lastHead: head };
@@ -17898,7 +18157,7 @@ var buildPacket = (runtime, root, base, head) => {
17898
18157
  const range = `${from}...${head}`;
17899
18158
  const diff = git(runtime, root, ["diff", "--no-color", "--no-ext-diff", range, "--", ".", ":(exclude).finitemonkey"]);
17900
18159
  const owners = reviewFile(runtime, root, base);
17901
- const text7 = [
18160
+ const text8 = [
17902
18161
  `# Review packet (round ${String(state.rounds)}, ${range})`,
17903
18162
  "",
17904
18163
  ...state.rounds > FULL_ROUNDS ? ["Only the diff since the last review round is included.", ""] : [],
@@ -17906,10 +18165,10 @@ var buildPacket = (runtime, root, base, head) => {
17906
18165
  ...taskSections(runtime, root),
17907
18166
  ...owners === void 0 ? [] : [section("REVIEW.md (base branch)", owners, "")]
17908
18167
  ].join("\n");
17909
- runtime.mkdir(toAbsolutePath(join45(root, ".finitemonkey", "review")));
18168
+ runtime.mkdir(toAbsolutePath(join46(root, ".finitemonkey", "review")));
17910
18169
  runtime.writeAtomic(statePath(root), `${JSON.stringify(state, null, 2)}
17911
18170
  `);
17912
- return { round: state.rounds, base, head, range, text: text7 };
18171
+ return { round: state.rounds, base, head, range, text: text8 };
17913
18172
  };
17914
18173
 
17915
18174
  // src/commands/review.ts
@@ -17962,14 +18221,14 @@ var gitUnavailable = (detail) => ({
17962
18221
  });
17963
18222
 
17964
18223
  // src/commands/secrets-accept.ts
17965
- var DAY_MS2 = 864e5;
18224
+ var DAY_MS3 = 864e5;
17966
18225
  var daysOf = (values) => {
17967
18226
  const raw = stringOption(values, "days");
17968
18227
  if (raw === void 0) return EXCEPTION_MAX_DAYS;
17969
18228
  const days = Number.parseInt(raw, 10);
17970
18229
  return Number.isInteger(days) && days > 0 && days <= EXCEPTION_MAX_DAYS ? days : void 0;
17971
18230
  };
17972
- var plusDays = (iso, days) => new Date(Date.parse(iso) + days * DAY_MS2).toISOString();
18231
+ var plusDays = (iso, days) => new Date(Date.parse(iso) + days * DAY_MS3).toISOString();
17973
18232
  var failure3 = (ctx, problem4) => failureFrom(ctx.t, problem4);
17974
18233
  var inputProblem = (ctx, id, reason, days) => {
17975
18234
  const fix = "finitemonkey secrets accept <id> --reason <why> [--days 1-90] [--false-positive]";
@@ -18061,7 +18320,7 @@ var secretsAcceptCommand = {
18061
18320
  };
18062
18321
 
18063
18322
  // src/secrets/runbooks.ts
18064
- import { dirname as dirname9, join as join46 } from "node:path";
18323
+ import { dirname as dirname9, join as join47 } from "node:path";
18065
18324
  var PROVIDER_OF_RULE = {
18066
18325
  "aws-access-token": "aws",
18067
18326
  "aws-secret-access-key": "aws",
@@ -18083,7 +18342,7 @@ var RUNBOOKS_URL = "https://github.com/gabrielvieiraf/finitemonkey/blob/main/run
18083
18342
  var runbookProvider = (rule) => PROVIDER_OF_RULE[rule] ?? "generic";
18084
18343
  var runbookLink = (runtime, rule) => {
18085
18344
  const file = `${runbookProvider(rule)}.md`;
18086
- const local = toAbsolutePath(join46(dirname9(dirname9(runtime.selfPath)), "runbooks", file));
18345
+ const local = toAbsolutePath(join47(dirname9(dirname9(runtime.selfPath)), "runbooks", file));
18087
18346
  return runtime.stat(local)?.kind === "file" ? local : `${RUNBOOKS_URL}/${file}`;
18088
18347
  };
18089
18348
 
@@ -18444,8 +18703,206 @@ var secretsScanCommand = {
18444
18703
  }
18445
18704
  };
18446
18705
 
18706
+ // src/statusline/gate-state.ts
18707
+ var GATE_HOOKS = /* @__PURE__ */ new Set(["Stop", "SubagentStop"]);
18708
+ var TAIL_BYTES = 64 * 1024;
18709
+ var lastGateFailure = (runtime, paths) => {
18710
+ const log = runtime.readText(paths.log);
18711
+ if (!log.ok) return void 0;
18712
+ const lines3 = log.value.slice(-TAIL_BYTES).split("\n").reverse();
18713
+ for (const line2 of lines3) {
18714
+ if (!line2.includes(HOOK_FAILURE_EVENT)) continue;
18715
+ const parsed = parseJsonObject(line2);
18716
+ if (!parsed.ok || !isRecord(parsed.value)) continue;
18717
+ const { event, hook, at } = parsed.value;
18718
+ if (event === HOOK_FAILURE_EVENT && typeof hook === "string" && GATE_HOOKS.has(hook) && typeof at === "string")
18719
+ return at;
18720
+ }
18721
+ return void 0;
18722
+ };
18723
+ var gateLight = (runtime, paths) => {
18724
+ const status = readGateStatus(runtime, paths);
18725
+ const failure4 = lastGateFailure(runtime, paths);
18726
+ const greenSince = status?.status === "passed" ? Date.parse(status.at) : Number.NEGATIVE_INFINITY;
18727
+ if (failure4 !== void 0 && Date.parse(failure4) >= greenSince) return "did_not_run";
18728
+ if (status === void 0) return "none";
18729
+ switch (status.status) {
18730
+ case "passed":
18731
+ return "green";
18732
+ case "failed":
18733
+ case "not_done":
18734
+ return "red";
18735
+ case "incomplete":
18736
+ return "incomplete";
18737
+ case "not_run":
18738
+ return "did_not_run";
18739
+ }
18740
+ };
18741
+
18742
+ // src/statusline/input.ts
18743
+ var text7 = (value) => typeof value === "string" && value !== "" ? value : void 0;
18744
+ var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
18745
+ var usedTokens = (usage) => {
18746
+ if (!isRecord(usage)) return void 0;
18747
+ return (num(usage["input_tokens"]) ?? 0) + (num(usage["cache_creation_input_tokens"]) ?? 0) + (num(usage["cache_read_input_tokens"]) ?? 0);
18748
+ };
18749
+ var contextPercent = (record3) => {
18750
+ const window = record3["context_window"];
18751
+ if (!isRecord(window)) return void 0;
18752
+ const direct = window["used_percentage"];
18753
+ if (typeof direct === "number" && Number.isFinite(direct)) return Math.round(direct);
18754
+ const size = window["context_window_size"];
18755
+ const used = usedTokens(window["current_usage"]);
18756
+ return typeof size === "number" && size > 0 && used !== void 0 ? Math.round(used / size * 100) : void 0;
18757
+ };
18758
+ var parseStatusInput = (raw) => {
18759
+ const parsed = parseJsonObject(raw.trim() === "" ? "{}" : raw);
18760
+ const record3 = parsed.ok ? parsed.value : {};
18761
+ const workspace = isRecord(record3["workspace"]) ? record3["workspace"] : {};
18762
+ return {
18763
+ sessionId: text7(record3["session_id"]),
18764
+ dir: text7(workspace["project_dir"]) ?? text7(workspace["current_dir"]) ?? text7(record3["cwd"]),
18765
+ contextPercent: contextPercent(record3),
18766
+ raw
18767
+ };
18768
+ };
18769
+
18770
+ // src/statusline/render.ts
18771
+ var SEPARATOR2 = " | ";
18772
+ var segmentText = (t, segment) => [
18773
+ "fm",
18774
+ ...segment.task === void 0 ? [] : [segment.task],
18775
+ ...segment.gate === "none" ? [] : [t(`statusline.gate.${segment.gate}`)],
18776
+ ...segment.contextPercent === void 0 ? [] : [t("statusline.context", { percent: segment.contextPercent })]
18777
+ ].join(" · ");
18778
+ var ANSI3 = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`, "g");
18779
+ var graphemes = (text8) => Array.from(new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text8), (part2) => part2.segment);
18780
+ var fit = (line2, columns) => {
18781
+ if (columns === void 0) return line2;
18782
+ const visible = graphemes(line2.replace(ANSI3, ""));
18783
+ return visible.length <= columns ? line2 : `${visible.slice(0, Math.max(0, columns - 1)).join("")}…`;
18784
+ };
18785
+ var composeLine = (wrapped, segment, columns) => {
18786
+ const lines3 = wrapped === "" ? [] : wrapped.split(/\r?\n/);
18787
+ const last = lines3.pop();
18788
+ const joined = last === void 0 || last.trim() === "" ? segment : `${last}${SEPARATOR2}${segment}`;
18789
+ return [...lines3, joined].map((line2) => fit(line2, columns)).join("\n");
18790
+ };
18791
+
18792
+ // src/statusline/node-shell.ts
18793
+ import { spawn as spawn4 } from "node:child_process";
18794
+ var killTree = (child2) => {
18795
+ const pid = child2.pid;
18796
+ if (pid === void 0) return;
18797
+ try {
18798
+ if (process.platform === "win32") {
18799
+ spawn4("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true }).unref();
18800
+ } else process.kill(-pid, "SIGKILL");
18801
+ } catch {
18802
+ child2.kill("SIGKILL");
18803
+ }
18804
+ };
18805
+ var nodeShell = (command, input, cwd, timeoutMs) => new Promise((resolve2) => {
18806
+ const child2 = spawn4(command, {
18807
+ cwd,
18808
+ shell: true,
18809
+ detached: process.platform !== "win32",
18810
+ windowsHide: true,
18811
+ stdio: ["pipe", "pipe", "ignore"]
18812
+ });
18813
+ let out = "";
18814
+ const timer = setTimeout(() => {
18815
+ killTree(child2);
18816
+ resolve2({ text: void 0, timedOut: true });
18817
+ }, timeoutMs);
18818
+ child2.stdout.on("data", (chunk) => {
18819
+ out += chunk.toString("utf8");
18820
+ });
18821
+ child2.once("error", () => {
18822
+ clearTimeout(timer);
18823
+ resolve2({ text: void 0, timedOut: false });
18824
+ });
18825
+ child2.once("close", () => {
18826
+ clearTimeout(timer);
18827
+ resolve2({ text: out, timedOut: false });
18828
+ });
18829
+ child2.stdin.on("error", () => void 0);
18830
+ child2.stdin.end(input);
18831
+ });
18832
+
18833
+ // src/statusline/wrap.ts
18834
+ var WRAP_CACHE = "statusline.json";
18835
+ var KEEP3 = 20;
18836
+ var cached2 = (runtime, paths) => {
18837
+ const raw = readJsonFile(runtime, paths.cache(WRAP_CACHE))?.["sessions"];
18838
+ return isRecord(raw) ? Object.fromEntries(Object.entries(raw).filter((entry2) => typeof entry2[1] === "string")) : {};
18839
+ };
18840
+ var remember = (runtime, paths, session, text8) => {
18841
+ const sessions = Object.entries({ ...cached2(runtime, paths), [session]: text8 }).slice(-KEEP3);
18842
+ writeJsonFile(runtime, paths.cache(WRAP_CACHE), { sessions: Object.fromEntries(sessions) });
18843
+ };
18844
+ var runWrapped = async (runtime, paths, request, shell = nodeShell) => {
18845
+ const result = await shell(request.command, request.input, request.cwd, request.timeoutMs);
18846
+ if (result.text !== void 0) {
18847
+ const text8 = result.text.replace(/\s+$/, "");
18848
+ if (paths !== void 0) remember(runtime, paths, request.session, text8);
18849
+ return text8;
18850
+ }
18851
+ return paths === void 0 ? "" : cached2(runtime, paths)[request.session] ?? "";
18852
+ };
18853
+
18854
+ // src/statusline/run.ts
18855
+ var STDIN_MS = 300;
18856
+ var WRAP_MS = 700;
18857
+ var wrapOf = (ctx, root) => {
18858
+ const text8 = ctx.runtime.readText(toAbsolutePath(`${root}/${LOCK_FILE}`));
18859
+ const lock = text8.ok ? parseLock(text8.value) : void 0;
18860
+ if (!lock?.ok) return void 0;
18861
+ return lock.value.entries.find((entry2) => entry2.added?.statusLine !== void 0)?.added?.statusLine?.wrap ?? void 0;
18862
+ };
18863
+ var columnsOf = (ctx) => {
18864
+ const columns = Number.parseInt(ctx.env["COLUMNS"] ?? "", 10);
18865
+ return Number.isInteger(columns) && columns > 0 ? columns : void 0;
18866
+ };
18867
+ var lineFor = async (ctx, input) => {
18868
+ const start = input.dir === void 0 ? ctx.cwd : toAbsolutePath(input.dir, ctx.cwd);
18869
+ const root = findGitRoot(start, ctx.runtime);
18870
+ const paths = root === void 0 ? void 0 : fmPaths(root);
18871
+ const wrap = root === void 0 ? void 0 : wrapOf(ctx, root);
18872
+ const wrapped = wrap === void 0 || root === void 0 ? "" : await runWrapped(ctx.runtime, paths, {
18873
+ command: wrap,
18874
+ input: input.raw,
18875
+ cwd: root,
18876
+ timeoutMs: WRAP_MS,
18877
+ session: input.sessionId ?? "none"
18878
+ });
18879
+ const segment = segmentText(ctx.t, {
18880
+ task: paths === void 0 ? void 0 : readState(ctx.runtime, paths).active,
18881
+ gate: paths === void 0 ? "none" : gateLight(ctx.runtime, paths),
18882
+ contextPercent: input.contextPercent
18883
+ });
18884
+ return composeLine(wrapped, segment, columnsOf(ctx));
18885
+ };
18886
+ var renderStatusLine = async (ctx) => {
18887
+ try {
18888
+ const raw = await withDeadline(readAll(ctx.io.stdin), STDIN_MS, ctx.runtime.startTimer).catch(() => "");
18889
+ return await lineFor(ctx, parseStatusInput(raw));
18890
+ } catch {
18891
+ return "fm";
18892
+ }
18893
+ };
18894
+
18895
+ // src/commands/statusline.ts
18896
+ var statuslineCommand = {
18897
+ path: ["statusline"],
18898
+ summaryKey: "command.statusline.summary",
18899
+ usage: "finitemonkey statusline",
18900
+ status: "ready",
18901
+ run: async (ctx) => success({}, [await renderStatusLine(ctx)])
18902
+ };
18903
+
18447
18904
  // src/task/create.ts
18448
- import { join as join47 } from "node:path";
18905
+ import { join as join48 } from "node:path";
18449
18906
 
18450
18907
  // src/core/yaml/write.ts
18451
18908
  var PLAIN_SAFE = /^[A-Za-z_./][A-Za-z0-9_./ ()-]*$/;
@@ -18534,10 +18991,10 @@ var explicitChecks = (draft) => [
18534
18991
  var draftChecks = (runtime, paths, draft) => {
18535
18992
  const disk = {
18536
18993
  read: (relative11) => {
18537
- const text7 = runtime.readText(toAbsolutePath(join47(paths.root, relative11)));
18538
- return text7.ok ? text7.value : void 0;
18994
+ const text8 = runtime.readText(toAbsolutePath(join48(paths.root, relative11)));
18995
+ return text8.ok ? text8.value : void 0;
18539
18996
  },
18540
- exists: (relative11) => runtime.stat(toAbsolutePath(join47(paths.root, relative11))) !== void 0
18997
+ exists: (relative11) => runtime.stat(toAbsolutePath(join48(paths.root, relative11))) !== void 0
18541
18998
  };
18542
18999
  const detected = detectChecks(disk);
18543
19000
  const base = draft.runs.length > 0 || draft.tests !== void 0 ? explicitChecks(draft) : detected;
@@ -18552,7 +19009,7 @@ var draftChecks = (runtime, paths, draft) => {
18552
19009
  { id: "merge", kind: "merge" },
18553
19010
  { id: "callers", kind: "callers" },
18554
19011
  ...draft.paths.length > 0 || draft.budget !== void 0 ? [{ id: "diff", kind: "diff" }] : [],
18555
- ...runtime.stat(toAbsolutePath(join47(paths.root, LOOK_CONFIG))) === void 0 ? [] : [{ id: "look", kind: "look" }],
19012
+ ...runtime.stat(toAbsolutePath(join48(paths.root, LOOK_CONFIG))) === void 0 ? [] : [{ id: "look", kind: "look" }],
18556
19013
  ...draft.git.length > 0 ? [{ id: "git", kind: "git", require: draft.git }] : [],
18557
19014
  { id: "pr", kind: "pr", requireOpen: draft.git.includes("pr_open") },
18558
19015
  ...draft.manual.map((description, index) => ({
@@ -18578,19 +19035,19 @@ var createTask = (runtime, paths, draft) => {
18578
19035
  decisionsNeeded: [],
18579
19036
  implicit: false
18580
19037
  };
18581
- const text7 = stringifyYaml(contractYaml(shape));
19038
+ const text8 = stringifyYaml(contractYaml(shape));
18582
19039
  const valid = validateContract(contractYaml(shape));
18583
19040
  if (!valid.ok) return reject({ kind: "invalid", issues: valid.error });
18584
- const wrote = runtime.writeText(paths.task(draft.id), text7);
19041
+ const wrote = runtime.writeText(paths.task(draft.id), text8);
18585
19042
  if (!wrote.ok) return reject({ kind: "write", detail: wrote.error });
18586
19043
  const lock = readTrust(runtime, paths);
18587
19044
  writeTrust(runtime, paths, {
18588
19045
  ...lock,
18589
19046
  contracts: { ...lock.contracts, [draft.id]: contractHash(valid.value) },
18590
- approved: { ...lock.approved, [draft.id]: text7 }
19047
+ approved: { ...lock.approved, [draft.id]: text8 }
18591
19048
  });
18592
19049
  writeState(runtime, paths, { ...readState(runtime, paths), active: draft.id });
18593
- return accept({ contract: valid.value, text: text7 });
19050
+ return accept({ contract: valid.value, text: text8 });
18594
19051
  };
18595
19052
 
18596
19053
  // src/commands/task.ts
@@ -18741,7 +19198,7 @@ var taskDoneCommand = {
18741
19198
  };
18742
19199
 
18743
19200
  // src/install/githook-block.ts
18744
- var withoutHookBlock = (text7) => withoutManagedBlock(text7, { begin: BLOCK_BEGIN, end: BLOCK_END });
19201
+ var withoutHookBlock = (text8) => withoutManagedBlock(text8, { begin: BLOCK_BEGIN, end: BLOCK_END });
18745
19202
 
18746
19203
  // src/install/uninstall.ts
18747
19204
  var blockless = (kind, current2) => {
@@ -18750,8 +19207,8 @@ var blockless = (kind, current2) => {
18750
19207
  };
18751
19208
  var structural = (entry2, current2) => {
18752
19209
  if (entry2.kind === "gitignore" || entry2.kind === "markdown" || entry2.kind === "githook") {
18753
- const text7 = blockless(entry2.kind, current2);
18754
- return text7.trim() === "" && !entry2.existed ? void 0 : text7;
19210
+ const text8 = blockless(entry2.kind, current2);
19211
+ return text8.trim() === "" && !entry2.existed ? void 0 : text8;
18755
19212
  }
18756
19213
  const parsed = parseSettings(current2);
18757
19214
  if (!parsed.ok || entry2.added === void 0) return current2;
@@ -18832,7 +19289,7 @@ var dataOf3 = (root, plan, applied) => ({
18832
19289
  kept: plan.items.filter((item2) => item2.action.startsWith("keep")).map(({ path }) => path),
18833
19290
  outside: OUTSIDE.map(({ command }) => command)
18834
19291
  });
18835
- var linesOf2 = (ctx, data) => [
19292
+ var linesOf3 = (ctx, data) => [
18836
19293
  ctx.t("install.uninstall.title", { root: data.root }),
18837
19294
  ...data.actions.map(({ path, action }) => ` ${action.padEnd(15)}${path}`),
18838
19295
  ...data.purge ? [ctx.t("install.uninstall.purge")] : [ctx.t("install.uninstall.keeps_state")],
@@ -18853,16 +19310,16 @@ var runUninstall = (ctx, args2) => {
18853
19310
  const options = { purge: args2.values["purge"] === true, keepGenerated: args2.values["keep-generated"] === true };
18854
19311
  const plan = planRemoval(project, lock.value, options);
18855
19312
  const preview = dataOf3(project.root, plan, false);
18856
- if (ctx.dryRun) return success(preview, [...linesOf2(ctx, preview), ctx.t("install.dry_run")]);
19313
+ if (ctx.dryRun) return success(preview, [...linesOf3(ctx, preview), ctx.t("install.dry_run")]);
18857
19314
  if (!ctx.yes) {
18858
19315
  const next2 = fixFor(args2);
18859
19316
  const problem4 = confirmProblem("uninstall", ctx.t("install.uninstall.question"), next2);
18860
- return { ...failureFrom(ctx.t, { ...problem4, data: preview }, linesOf2(ctx, preview)), next: next2 };
19317
+ return { ...failureFrom(ctx.t, { ...problem4, data: preview }, linesOf3(ctx, preview)), next: next2 };
18861
19318
  }
18862
19319
  const removed = applyRemoval(project, plan);
18863
19320
  if (!removed.ok) return failureFrom(ctx.t, applyProblem(removed.error));
18864
19321
  const data = dataOf3(project.root, plan, true);
18865
- return success(data, [...linesOf2(ctx, data), ctx.t("install.uninstall.done")]);
19322
+ return success(data, [...linesOf3(ctx, data), ctx.t("install.uninstall.done")]);
18866
19323
  };
18867
19324
  var uninstallCommand = {
18868
19325
  path: ["uninstall"],
@@ -18942,6 +19399,7 @@ var REGISTRY = [
18942
19399
  policyFalsePositiveCommand,
18943
19400
  offCommand,
18944
19401
  onCommand,
19402
+ gainCommand,
18945
19403
  secretsIncidentCommand,
18946
19404
  secretsLiveCommand,
18947
19405
  proseCheckCommand,
@@ -18955,34 +19413,35 @@ var REGISTRY = [
18955
19413
  ciGateCommand,
18956
19414
  lookCommand,
18957
19415
  reviewPacketCommand,
19416
+ statuslineCommand,
18958
19417
  versionCommand,
18959
19418
  helpCommand
18960
19419
  ];
18961
19420
 
18962
19421
  // src/i18n/lang-settings.ts
18963
- import { join as join48 } from "node:path";
19422
+ import { join as join49 } from "node:path";
18964
19423
  var read3 = (runtime, path) => {
18965
- const text7 = runtime.readText(toAbsolutePath(path));
18966
- return text7.ok ? text7.value : void 0;
19424
+ const text8 = runtime.readText(toAbsolutePath(path));
19425
+ return text8.ok ? text8.value : void 0;
18967
19426
  };
18968
19427
  var configText = (tree, ...path) => {
18969
19428
  const value = configAt(tree, ...path);
18970
19429
  return typeof value === "string" && value !== "" ? value : void 0;
18971
19430
  };
18972
- var languageIn = (text7) => {
18973
- if (text7 === void 0) return void 0;
18974
- const parsed = parseJsonObject(text7);
19431
+ var languageIn = (text8) => {
19432
+ if (text8 === void 0) return void 0;
19433
+ const parsed = parseJsonObject(text8);
18975
19434
  const value = parsed.ok && isRecord(parsed.value) ? parsed.value["language"] : void 0;
18976
19435
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
18977
19436
  };
18978
- var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join48(runtime.homeDir(), ".claude");
19437
+ var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join49(runtime.homeDir(), ".claude");
18979
19438
  var langSettings = (runtime, cwd, env, platform) => {
18980
19439
  const root = findGitRoot(cwd, runtime) ?? cwd;
18981
- const config = parseConfigYaml(read3(runtime, join48(root, CONFIG_FILE)) ?? "");
19440
+ const config = parseConfigYaml(read3(runtime, join49(root, CONFIG_FILE)) ?? "");
18982
19441
  const claudeFiles = [
18983
- join48(root, ".claude", "settings.local.json"),
18984
- join48(root, ".claude", "settings.json"),
18985
- join48(claudeDir(runtime, env), "settings.json")
19442
+ join49(root, ".claude", "settings.local.json"),
19443
+ join49(root, ".claude", "settings.json"),
19444
+ join49(claudeDir(runtime, env), "settings.json")
18986
19445
  ];
18987
19446
  return {
18988
19447
  config: configText(config, "lang"),
@@ -19069,8 +19528,8 @@ var resultFromThrown = (thrown, t) => {
19069
19528
  };
19070
19529
  var block3 = (items) => items.length > 0 ? `${items.join("\n")}
19071
19530
  ` : "";
19072
- var write2 = (sink, text7) => {
19073
- if (text7 !== "") sink.write(maskText(text7));
19531
+ var write2 = (sink, text8) => {
19532
+ if (text8 !== "") sink.write(maskText(text8));
19074
19533
  };
19075
19534
  var render2 = (ctx, command, result) => {
19076
19535
  if (result.raw !== void 0) {
@@ -19139,9 +19598,9 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
19139
19598
  var UNKNOWN_VERSION = "0.0.0-unknown";
19140
19599
  var VERSION_SOURCES = ["../package.json", "../.claude-plugin/plugin.json"];
19141
19600
  var versionAt = (readText, moduleUrl, relative11) => {
19142
- const text7 = readText(toAbsolutePath(fileURLToPath4(new URL(relative11, moduleUrl))));
19143
- if (!text7.ok) return void 0;
19144
- const parsed = parseJson(text7.value);
19601
+ const text8 = readText(toAbsolutePath(fileURLToPath4(new URL(relative11, moduleUrl))));
19602
+ if (!text8.ok) return void 0;
19603
+ const parsed = parseJson(text8.value);
19145
19604
  if (!parsed.ok || !isRecord(parsed.value)) return void 0;
19146
19605
  const version = parsed.value["version"];
19147
19606
  return typeof version === "string" ? version : void 0;