finitemonkey 0.1.0-next.22 → 0.1.0-next.24

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.
@@ -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,36 +943,122 @@ 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)}
871
951
  `;
872
952
 
953
+ // src/git/project-config.ts
954
+ import { join as join2 } from "node:path";
955
+ var CONFIG_FILE2 = ".finitemonkey/config.yml";
956
+ var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
957
+ var stripComment = (line2) => {
958
+ let quote;
959
+ for (let index = 0; index < line2.length; index += 1) {
960
+ const char = line2.charAt(index);
961
+ if (quote === void 0 && (char === '"' || char === "'")) quote = char;
962
+ else if (char === quote) quote = void 0;
963
+ else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
964
+ return line2.slice(0, index);
965
+ }
966
+ }
967
+ return line2;
968
+ };
969
+ var flowList = (value) => {
970
+ const trimmed = value.trim();
971
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
972
+ return trimmed.slice(1, -1).split(",").map(unquote).filter((item2) => item2 !== "");
973
+ };
974
+ var keyValue = (body2) => {
975
+ const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
976
+ if (match === null) return void 0;
977
+ return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
978
+ };
979
+ var addItem = (frame, item2) => {
980
+ if (frame.key === void 0) return;
981
+ const parent = frame.tree[frame.key];
982
+ if (Array.isArray(parent)) parent.push(item2);
983
+ else frame.tree[frame.key] = [item2];
984
+ };
985
+ var placeLine = (stack, indent, body2) => {
986
+ while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
987
+ const top = stack[stack.length - 1];
988
+ if (top === void 0) return;
989
+ if (body2.startsWith("- ")) {
990
+ addItem(top, unquote(body2.slice(2)));
991
+ return;
992
+ }
993
+ const pair = keyValue(body2);
994
+ if (pair === void 0) return;
995
+ const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
996
+ if (pair.value === "") {
997
+ stack.push({ indent, tree: parentTree, key: pair.key });
998
+ return;
999
+ }
1000
+ parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
1001
+ };
1002
+ var ensureTree = (tree, key) => {
1003
+ const existing = tree[key];
1004
+ if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
1005
+ const fresh3 = {};
1006
+ tree[key] = fresh3;
1007
+ return fresh3;
1008
+ };
1009
+ var parseConfigYaml = (text8) => {
1010
+ const root = {};
1011
+ const stack = [{ indent: -1, tree: root, key: void 0 }];
1012
+ for (const raw of text8.split(/\r?\n/)) {
1013
+ const line2 = stripComment(raw).trimEnd();
1014
+ if (line2.trim() === "" || line2.trim() === "---") continue;
1015
+ const indent = line2.length - line2.trimStart().length;
1016
+ placeLine(stack, indent, line2.trim());
1017
+ }
1018
+ return root;
1019
+ };
1020
+ var isTree = (node) => typeof node === "object" && !Array.isArray(node);
1021
+ var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
1022
+ var configList = (tree, ...path) => {
1023
+ const node = configAt(tree, ...path);
1024
+ if (typeof node === "string") return node === "" ? [] : [node];
1025
+ return Array.isArray(node) ? node.filter((item2) => typeof item2 === "string") : [];
1026
+ };
1027
+ var configMap = (tree, ...path) => {
1028
+ const node = configAt(tree, ...path);
1029
+ if (!isTree(node)) return {};
1030
+ return Object.fromEntries(
1031
+ Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
1032
+ );
1033
+ };
1034
+ var loadProjectConfig = (runtime, root) => {
1035
+ const text8 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE2)));
1036
+ return text8.ok ? parseConfigYaml(text8.value) : {};
1037
+ };
1038
+
873
1039
  // src/install/managed-block.ts
874
- var lineBreak = (text7) => text7.includes("\r\n") ? "\r\n" : "\n";
1040
+ var lineBreak = (text8) => text8.includes("\r\n") ? "\r\n" : "\n";
875
1041
  var blockRange = (lines3, markers) => {
876
1042
  const start = lines3.findIndex((line2) => line2.trimEnd() === markers.begin);
877
1043
  if (start < 0) return void 0;
878
1044
  const end = lines3.findIndex((line2, index) => index > start && line2.trimEnd() === markers.end);
879
1045
  return end < 0 ? void 0 : { start, end };
880
1046
  };
881
- var withManagedBlock = (text7, markers, body2) => {
882
- const eol = text7 === void 0 ? "\n" : lineBreak(text7);
1047
+ var withManagedBlock = (text8, markers, body2) => {
1048
+ const eol = text8 === void 0 ? "\n" : lineBreak(text8);
883
1049
  const block4 = [markers.begin, ...body2, markers.end];
884
- if (text7 === void 0 || text7 === "") return `${block4.join(eol)}${eol}`;
885
- const lines3 = text7.split(eol);
1050
+ if (text8 === void 0 || text8 === "") return `${block4.join(eol)}${eol}`;
1051
+ const lines3 = text8.split(eol);
886
1052
  const range = blockRange(lines3, markers);
887
1053
  if (range !== void 0) return [...lines3.slice(0, range.start), ...block4, ...lines3.slice(range.end + 1)].join(eol);
888
- const separator = text7.endsWith(eol) ? "" : eol;
889
- return `${text7}${separator}${block4.join(eol)}${eol}`;
1054
+ const separator = text8.endsWith(eol) ? "" : eol;
1055
+ return `${text8}${separator}${block4.join(eol)}${eol}`;
890
1056
  };
891
- var withoutManagedBlock = (text7, markers) => {
892
- const eol = lineBreak(text7);
893
- const lines3 = text7.split(eol);
1057
+ var withoutManagedBlock = (text8, markers) => {
1058
+ const eol = lineBreak(text8);
1059
+ const lines3 = text8.split(eol);
894
1060
  const range = blockRange(lines3, markers);
895
- if (range === void 0) return text7;
1061
+ if (range === void 0) return text8;
896
1062
  return [...lines3.slice(0, range.start), ...lines3.slice(range.end + 1)].join(eol);
897
1063
  };
898
1064
 
@@ -989,48 +1155,7 @@ var configTemplate = (preset, lang) => [
989
1155
  `lang: ${lang}`,
990
1156
  ""
991
1157
  ].join("\n");
992
- var hasMarker = (text7) => text7.includes(GENERATED_MARKER);
993
-
994
- // src/install/hook-entries.ts
995
- var INSTALLED_HOOKS = [
996
- { event: "SessionStart", timeout: 30, guard: false },
997
- { event: "UserPromptSubmit", timeout: 10, guard: true },
998
- { event: "PreToolUse", timeout: 10, guard: true },
999
- { event: "PostToolUse", timeout: 30, guard: false },
1000
- { event: "Stop", timeout: 600, guard: false },
1001
- { event: "SubagentStart", timeout: 30, guard: false },
1002
- { event: "SubagentStop", timeout: 600, guard: false },
1003
- { event: "PreCompact", timeout: 30, guard: false },
1004
- { event: "ConfigChange", timeout: 10, guard: true }
1005
- ];
1006
- var PROJECT_BIN = `\${CLAUDE_PROJECT_DIR}/${LOCAL_BIN}`;
1007
- var VENDORED_BIN = `\${CLAUDE_PROJECT_DIR}/${VENDOR_BIN}`;
1008
- var isScript = (path) => /\.(c|m)?js$/i.test(path);
1009
- var execForm = (runner, event) => {
1010
- if (runner.kind === "local") return { command: "node", args: [PROJECT_BIN, "hook", event] };
1011
- if (runner.kind === "vendored") return { command: "node", args: [VENDORED_BIN, "hook", event] };
1012
- if (isScript(runner.path)) return { command: "node", args: [runner.path, "hook", event] };
1013
- return { command: runner.path, args: ["hook", event] };
1014
- };
1015
- var hookHandler = (runner, spec2, failClosedSupported) => {
1016
- const failure4 = spec2.guard && failClosedSupported ? { onFailure: "block" } : {};
1017
- const { command, args: args2 } = execForm(runner, spec2.event);
1018
- return { type: "command", command, args: args2, timeout: spec2.timeout, ...failure4 };
1019
- };
1020
- var OWN_SHELL = /(^|[\s/"'])finitemonkey(@[^\s"]+|\.mjs|\.cmd|\.exe)?"?\s+hook\s/;
1021
- var OWN_SCRIPT = /finitemonkey(\.mjs|\.cmd|\.exe)?$/;
1022
- var stringsOf = (value) => Array.isArray(value) ? value.filter((item2) => typeof item2 === "string") : [];
1023
- var isOwnHandler = (handler) => {
1024
- if (!isRecord(handler)) return false;
1025
- const command = handler["command"];
1026
- if (typeof command !== "string") return false;
1027
- const args2 = stringsOf(handler["args"]);
1028
- if (args2.length === 0) return OWN_SHELL.test(`${command} `);
1029
- const hookAt = args2.indexOf("hook");
1030
- if (hookAt < 0) return false;
1031
- const target2 = hookAt === 0 ? command : args2[hookAt - 1] ?? "";
1032
- return OWN_SCRIPT.test(target2);
1033
- };
1158
+ var hasMarker = (text8) => text8.includes(GENERATED_MARKER);
1034
1159
 
1035
1160
  // src/install/skill-template.ts
1036
1161
  var cliCommand = (runner, args2) => {
@@ -1095,7 +1220,7 @@ var lefthookBody = (runner) => [
1095
1220
  ` run: ${cliCommand(runner, "push check {1}")}`,
1096
1221
  " use_stdin: true"
1097
1222
  ];
1098
- 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)));
1099
1224
  var lefthookItem = (project, runner, path) => {
1100
1225
  const before = project.read(path);
1101
1226
  const after = withManagedBlock(before, HOOK_MARKERS, lefthookBody(runner));
@@ -1120,8 +1245,8 @@ var preCommitBody = (runner, indent) => {
1120
1245
  ];
1121
1246
  };
1122
1247
  var TOP_KEY = /^[A-Za-z_][\w-]*:/;
1123
- var reposIsLast = (text7) => {
1124
- 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));
1125
1250
  return keys.at(-1)?.startsWith("repos:") === true;
1126
1251
  };
1127
1252
  var preCommitItem = (project, runner) => {
@@ -1145,94 +1270,6 @@ var gitHookItems = (project, runner) => {
1145
1270
 
1146
1271
  // src/policy/policy.ts
1147
1272
  import { join as join3 } from "node:path";
1148
-
1149
- // src/git/project-config.ts
1150
- import { join as join2 } from "node:path";
1151
- var CONFIG_FILE2 = ".finitemonkey/config.yml";
1152
- var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
1153
- var stripComment = (line2) => {
1154
- let quote;
1155
- for (let index = 0; index < line2.length; index += 1) {
1156
- const char = line2.charAt(index);
1157
- if (quote === void 0 && (char === '"' || char === "'")) quote = char;
1158
- else if (char === quote) quote = void 0;
1159
- else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
1160
- return line2.slice(0, index);
1161
- }
1162
- }
1163
- return line2;
1164
- };
1165
- var flowList = (value) => {
1166
- const trimmed = value.trim();
1167
- if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
1168
- return trimmed.slice(1, -1).split(",").map(unquote).filter((item2) => item2 !== "");
1169
- };
1170
- var keyValue = (body2) => {
1171
- const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
1172
- if (match === null) return void 0;
1173
- return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
1174
- };
1175
- var addItem = (frame, item2) => {
1176
- if (frame.key === void 0) return;
1177
- const parent = frame.tree[frame.key];
1178
- if (Array.isArray(parent)) parent.push(item2);
1179
- else frame.tree[frame.key] = [item2];
1180
- };
1181
- var placeLine = (stack, indent, body2) => {
1182
- while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
1183
- const top = stack[stack.length - 1];
1184
- if (top === void 0) return;
1185
- if (body2.startsWith("- ")) {
1186
- addItem(top, unquote(body2.slice(2)));
1187
- return;
1188
- }
1189
- const pair = keyValue(body2);
1190
- if (pair === void 0) return;
1191
- const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
1192
- if (pair.value === "") {
1193
- stack.push({ indent, tree: parentTree, key: pair.key });
1194
- return;
1195
- }
1196
- parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
1197
- };
1198
- var ensureTree = (tree, key) => {
1199
- const existing = tree[key];
1200
- if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
1201
- const fresh3 = {};
1202
- tree[key] = fresh3;
1203
- return fresh3;
1204
- };
1205
- var parseConfigYaml = (text7) => {
1206
- const root = {};
1207
- const stack = [{ indent: -1, tree: root, key: void 0 }];
1208
- for (const raw of text7.split(/\r?\n/)) {
1209
- const line2 = stripComment(raw).trimEnd();
1210
- if (line2.trim() === "" || line2.trim() === "---") continue;
1211
- const indent = line2.length - line2.trimStart().length;
1212
- placeLine(stack, indent, line2.trim());
1213
- }
1214
- return root;
1215
- };
1216
- var isTree = (node) => typeof node === "object" && !Array.isArray(node);
1217
- var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
1218
- var configList = (tree, ...path) => {
1219
- const node = configAt(tree, ...path);
1220
- if (typeof node === "string") return node === "" ? [] : [node];
1221
- return Array.isArray(node) ? node.filter((item2) => typeof item2 === "string") : [];
1222
- };
1223
- var configMap = (tree, ...path) => {
1224
- const node = configAt(tree, ...path);
1225
- if (!isTree(node)) return {};
1226
- return Object.fromEntries(
1227
- Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
1228
- );
1229
- };
1230
- var loadProjectConfig = (runtime, root) => {
1231
- const text7 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE2)));
1232
- return text7.ok ? parseConfigYaml(text7.value) : {};
1233
- };
1234
-
1235
- // src/policy/policy.ts
1236
1273
  var POLICY_FILE = ".finitemonkey/policy.yml";
1237
1274
  var GUARD_MODES = ["off", "shadow", "active"];
1238
1275
  var LEVELS = ["light", "standard", "strict"];
@@ -1247,8 +1284,8 @@ var DEFAULT_POLICY = {
1247
1284
  guards: {}
1248
1285
  };
1249
1286
  var pick = (options, value, fallback) => options.find((option) => option === value) ?? fallback;
1250
- var parsePolicy = (text7) => {
1251
- const tree = parseConfigYaml(text7);
1287
+ var parsePolicy = (text8) => {
1288
+ const tree = parseConfigYaml(text8);
1252
1289
  const raw = configAt(tree, "promoteAfter");
1253
1290
  const promoteAfter = typeof raw === "string" ? Number.parseInt(raw, 10) : Number.NaN;
1254
1291
  const guards = Object.fromEntries(
@@ -1267,23 +1304,82 @@ var parsePolicy = (text7) => {
1267
1304
  };
1268
1305
  var policyPath = (root) => toAbsolutePath(join3(root, POLICY_FILE));
1269
1306
  var loadPolicy = (runtime, root) => {
1270
- const text7 = runtime.readText(policyPath(root));
1271
- return text7.ok ? parsePolicy(text7.value) : DEFAULT_POLICY;
1272
- };
1307
+ const text8 = runtime.readText(policyPath(root));
1308
+ return text8.ok ? parsePolicy(text8.value) : DEFAULT_POLICY;
1309
+ };
1310
+
1311
+ // src/review/agents.ts
1312
+ var REVIEW_AGENTS = ["reviewer", "verifier"];
1313
+ var FORBIDDEN_TOOLS = ["Edit", "Write", "MultiEdit", "NotebookEdit", "Agent", "Task"];
1314
+ var reviewAgentOf = (agentType) => REVIEW_AGENTS.find((name) => agentType === name || agentType.endsWith(`:${name}`));
1315
+ var REVIEWER_BODY = [
1316
+ "# Reviewer",
1317
+ "",
1318
+ "You review a change you did not write. Your input is the review packet in your prompt: the diff, the task contract, the evidence ledger and, when present, the code owners' REVIEW.md. You never see the conversation that produced the change, and you must not ask for it.",
1319
+ "",
1320
+ "1. Read the diff first, then open the files it touches when you need context.",
1321
+ "2. Judge the change against the contract: does each acceptance check have evidence for the current tree?",
1322
+ "3. Write numbered findings in three groups: must fix, should fix, nice to have. For each: what it is, the problem, the fix, and what happens if it is not fixed.",
1323
+ "4. Every must-fix finding carries a reproduction command and the expected result. A finding without one goes under should fix.",
1324
+ "5. End with a verdict: approve, approve with changes, or request changes.",
1325
+ "",
1326
+ "You cannot edit files or start other agents. Report; do not repair."
1327
+ ].join("\n");
1328
+ var VERIFIER_BODY = [
1329
+ "# Verifier",
1330
+ "",
1331
+ "You check claims with commands, not with opinions. Your input is the review packet and, when there is one, the reviewer's must-fix findings with their reproduction commands. You never see the conversation that produced the change.",
1332
+ "",
1333
+ "1. Run each reproduction command in a throwaway worktree on the base commit and on the head commit (`git worktree add <tmp> <rev>`), never in the person's working tree.",
1334
+ "2. Record both exit codes. Fails only on head: the finding stands. Fails on both: it was already there. Fails on neither: not reproduced.",
1335
+ "3. Report one line per finding with the command and both results, then remove the worktrees.",
1336
+ "",
1337
+ "You cannot edit files or start other agents. Report; do not repair."
1338
+ ].join("\n");
1339
+ var VERIFIER = {
1340
+ name: "verifier",
1341
+ description: "Runs reproduction commands from a review on the base and head commits in throwaway worktrees and reports which findings are real. Read-only.",
1342
+ tools: ["Read", "Grep", "Glob", "Bash"],
1343
+ model: "sonnet",
1344
+ maxTurns: 60,
1345
+ body: VERIFIER_BODY
1346
+ };
1347
+ var PLAYWRIGHT_SERVER = "playwright";
1348
+ var withPlaywright = (spec2) => ({
1349
+ ...spec2,
1350
+ tools: [...spec2.tools, `mcp__${PLAYWRIGHT_SERVER}`],
1351
+ mcpServers: [PLAYWRIGHT_SERVER]
1352
+ });
1353
+ var agentMarkdown = (spec2, marker) => [
1354
+ "---",
1355
+ `name: ${spec2.name}`,
1356
+ `description: ${spec2.description}`,
1357
+ `tools: ${spec2.tools.join(", ")}`,
1358
+ `disallowedTools: ${FORBIDDEN_TOOLS.join(", ")}`,
1359
+ `model: ${spec2.model}`,
1360
+ "effort: high",
1361
+ `maxTurns: ${String(spec2.maxTurns)}`,
1362
+ ...spec2.mcpServers === void 0 ? [] : ["mcpServers:", ...spec2.mcpServers.map((name) => ` - ${name}`)],
1363
+ "---",
1364
+ "",
1365
+ ...marker === void 0 ? [] : [`<!-- ${marker} -->`, ""],
1366
+ spec2.body,
1367
+ ""
1368
+ ].join("\n");
1273
1369
 
1274
1370
  // src/install/agents-block.ts
1275
1371
  var MARKDOWN_MARKERS = { begin: MD_BLOCK_BEGIN, end: MD_BLOCK_END };
1276
1372
  var IMPORT_LINE = /^@AGENTS\.md[ \t]*$/m;
1277
- var withAgentsBlock = (text7, runner, lang) => withManagedBlock(
1278
- text7,
1373
+ var withAgentsBlock = (text8, runner, lang) => withManagedBlock(
1374
+ text8,
1279
1375
  MARKDOWN_MARKERS,
1280
1376
  TEMPLATES[lang].agentsBlock({
1281
1377
  gate: cliCommand(runner, "gate --fast --json"),
1282
1378
  doctor: cliCommand(runner, "doctor --json")
1283
1379
  })
1284
1380
  );
1285
- var withAgentsImport = (text7) => text7 !== void 0 && IMPORT_LINE.test(withoutManagedBlock(text7, MARKDOWN_MARKERS)) ? text7 : withManagedBlock(text7, MARKDOWN_MARKERS, [AGENTS_IMPORT]);
1286
- 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);
1287
1383
 
1288
1384
  // src/install/ask-floor.ts
1289
1385
  var DEFAULT_BRANCHES = ["main", "master"];
@@ -1359,7 +1455,7 @@ var ruleFor = (raw) => {
1359
1455
  };
1360
1456
  var claudeignoreRules = (texts2) => [
1361
1457
  ...new Set(
1362
- 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)
1363
1459
  )
1364
1460
  ];
1365
1461
 
@@ -1392,12 +1488,12 @@ import { join as join4 } from "node:path";
1392
1488
 
1393
1489
  // src/install/json-text.ts
1394
1490
  var DEFAULT_STYLE = { indent: " ", finalNewline: true };
1395
- var styleOf = (text7) => {
1396
- const indented = /\n([ \t]+)\S/.exec(text7);
1397
- 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") };
1398
1494
  };
1399
1495
  var renderJson = (value, style) => `${JSON.stringify(value, null, style.indent)}${style.finalNewline ? "\n" : ""}`;
1400
- 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);
1401
1497
  var asList = (value) => Array.isArray(value) ? value : [];
1402
1498
  var asRecord = (value) => isRecord(value) ? value : {};
1403
1499
  var setKey = (record3, key, value) => {
@@ -1434,8 +1530,8 @@ var WORKFLOW_DIRS = [
1434
1530
  { dir: "openspec", name: "openspec" }
1435
1531
  ];
1436
1532
  var fromPackageJson = (project) => {
1437
- const text7 = project.read("package.json");
1438
- 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);
1439
1535
  const field = parsed?.ok === true ? parsed.value["packageManager"] : void 0;
1440
1536
  if (typeof field !== "string") return void 0;
1441
1537
  return PACKAGE_MANAGERS.find(({ name }) => field.startsWith(`${name}@`))?.name;
@@ -1451,9 +1547,9 @@ var allHandlers = (settings) => {
1451
1547
  const hooks = settings["hooks"];
1452
1548
  return isRecord(hooks) ? Object.keys(hooks).flatMap((event) => handlersOf(settings, event)) : [];
1453
1549
  };
1454
- var settingsInfo = (text7) => {
1455
- if (text7 === void 0) return { exists: false, valid: true, userHooks: 0, ownHooks: 0 };
1456
- 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);
1457
1553
  if (!parsed.ok) return { exists: true, valid: false, userHooks: 0, ownHooks: 0 };
1458
1554
  const handlers = allHandlers(parsed.value);
1459
1555
  const own = handlers.filter(isOwnHandler).length;
@@ -1467,8 +1563,8 @@ var pluginAuthorities = (settings, source) => {
1467
1563
  return LOOP_PLUGINS.includes(name) ? [{ kind: "loop-plugin", source, name }] : [];
1468
1564
  });
1469
1565
  };
1470
- var scopeAuthorities = (text7, source) => {
1471
- const parsed = text7 === void 0 ? void 0 : parseJsonObject(text7);
1566
+ var scopeAuthorities = (text8, source) => {
1567
+ const parsed = text8 === void 0 ? void 0 : parseJsonObject(text8);
1472
1568
  if (parsed?.ok !== true) return [];
1473
1569
  const stops = ["Stop", "SubagentStop"].flatMap(
1474
1570
  (event) => handlersOf(parsed.value, event).filter((handler) => !isOwnHandler(handler)).map((handler) => {
@@ -1482,8 +1578,8 @@ var userSettingsText = (project, env) => {
1482
1578
  const home = env["CLAUDE_CONFIG_DIR"] ?? env["HOME"] ?? env["USERPROFILE"];
1483
1579
  if (home === void 0) return void 0;
1484
1580
  const file = env["CLAUDE_CONFIG_DIR"] === void 0 ? join4(home, ".claude", "settings.json") : join4(home, "settings.json");
1485
- const text7 = project.runtime.readText(toAbsolutePath(file));
1486
- return text7.ok ? text7.value : void 0;
1581
+ const text8 = project.runtime.readText(toAbsolutePath(file));
1582
+ return text8.ok ? text8.value : void 0;
1487
1583
  };
1488
1584
  var detectAuthorities = (project, env) => [
1489
1585
  ...scopeAuthorities(project.read(SETTINGS_FILE), "project"),
@@ -1503,8 +1599,8 @@ var chooseRunner = (project, bin) => {
1503
1599
  // src/install/gitignore-block.ts
1504
1600
  var MARKERS = { begin: BLOCK_BEGIN, end: BLOCK_END };
1505
1601
  var body = () => [`${STATE_DIR}/*`, ...COMMITTED_STATE.map((path) => `!${STATE_DIR}/${path}`)];
1506
- var withBlock = (text7) => withManagedBlock(text7, MARKERS, body());
1507
- var withoutBlock = (text7) => withoutManagedBlock(text7, MARKERS);
1602
+ var withBlock = (text8) => withManagedBlock(text8, MARKERS, body());
1603
+ var withoutBlock = (text8) => withoutManagedBlock(text8, MARKERS);
1508
1604
 
1509
1605
  // src/core/semver.ts
1510
1606
  var SEMVER = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -1547,7 +1643,7 @@ var pendingMigrations = (migrations, from, to) => {
1547
1643
  };
1548
1644
  var migrateItem = (item2, previous, steps) => {
1549
1645
  if (steps.length === 0 || item2.before === void 0) return item2;
1550
- const migrated = steps.reduce((text7, step3) => step3(text7), item2.after);
1646
+ const migrated = steps.reduce((text8, step3) => step3(text8), item2.after);
1551
1647
  if (migrated === item2.after) return item2;
1552
1648
  const recorded = previous.entries.find((entry2) => entry2.path === item2.path)?.hash;
1553
1649
  if (recorded !== hashText(item2.before)) {
@@ -1639,6 +1735,15 @@ var withPermissions = (settings, lists) => {
1639
1735
  var missing = (wanted, existing) => wanted.filter((rule) => !existing.includes(rule));
1640
1736
  var withoutAdded = (existing, added) => existing.filter((rule) => typeof rule !== "string" || !added.includes(rule));
1641
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
+ };
1642
1747
  var mergeSettings = (current2, wanted, recorded) => {
1643
1748
  const schema = Object.keys(current2).length === 0;
1644
1749
  const base = schema ? { $schema: SETTINGS_SCHEMA_URL } : current2;
@@ -1662,7 +1767,18 @@ var mergeSettings = (current2, wanted, recorded) => {
1662
1767
  if (marketplace) settings = withEntry(settings, "extraKnownMarketplaces", MARKETPLACE_KEY, MARKETPLACE_ENTRY);
1663
1768
  if (plugin) settings = withEntry(settings, "enabledPlugins", PLUGIN_ID, true);
1664
1769
  if (language !== void 0) settings = { ...settings, language };
1665
- 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
+ };
1666
1782
  return { settings, added: language === void 0 ? added : { ...added, language } };
1667
1783
  };
1668
1784
  var sameAttribution = (value) => JSON.stringify(value) === JSON.stringify(ATTRIBUTION_OFF);
@@ -1682,15 +1798,20 @@ var unmergeSettings = (current2, added, recorded) => {
1682
1798
  if (added.language !== void 0 && settings["language"] === added.language) {
1683
1799
  settings = setKey(settings, "language", void 0);
1684
1800
  }
1801
+ settings = restoreStatusLine(settings, added);
1685
1802
  if (added.schema && Object.keys(settings).length === 1 && settings["$schema"] === SETTINGS_SCHEMA_URL) return {};
1686
1803
  return settings;
1687
1804
  };
1688
1805
  var ownHandlersIn = (settings) => Object.values(asRecord(settings["hooks"])).flatMap((groups) => asList(groups)).flatMap((group) => asList(asRecord(group)["hooks"])).filter(isOwnHandler).length;
1689
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
+
1690
1811
  // src/install/vendor.ts
1691
- var isBundle = (text7) => text7.startsWith("#!/usr/bin/env node") && text7.includes("finitemonkey");
1692
- var isOwnPackage = (text7) => {
1693
- 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);
1694
1815
  return parsed.ok && parsed.value["name"] === "finitemonkey";
1695
1816
  };
1696
1817
  var vendoredPackage = (version) => `${JSON.stringify({ name: "finitemonkey", version, private: true, type: "module" }, null, 2)}
@@ -1721,9 +1842,9 @@ var detect = (project, gitRoot2, input) => ({
1721
1842
  location: locationOf(project.root, input.env)
1722
1843
  });
1723
1844
  var readPreviousLock = (project) => {
1724
- const text7 = project.read(LOCK_FILE);
1725
- if (text7 === void 0) return accept(void 0);
1726
- 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);
1727
1848
  return parsed.ok ? accept(parsed.value) : reject({ kind: "lock", detail: parsed.error });
1728
1849
  };
1729
1850
  var actionFor = (before, after, existing) => {
@@ -1739,7 +1860,8 @@ var joinAdded = (previous, next2) => previous === void 0 ? next2 : {
1739
1860
  blockReads: previous.blockReads || next2.blockReads,
1740
1861
  marketplace: previous.marketplace || next2.marketplace,
1741
1862
  plugin: previous.plugin || next2.plugin,
1742
- ...(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 }
1743
1865
  };
1744
1866
  var wantedHooks = (detection) => Object.fromEntries(
1745
1867
  INSTALLED_HOOKS.map((spec2) => [spec2.event, hookHandler(detection.runner, spec2, detection.claude.failClosed)])
@@ -1755,7 +1877,8 @@ var settingsItem = (project, detection, input, previous) => {
1755
1877
  ask: ASK_RULES,
1756
1878
  attributionOff: input.preset === "gabriel",
1757
1879
  marketplace: input.marketplace,
1758
- language: input.claudeLanguage
1880
+ language: input.claudeLanguage,
1881
+ statusLine: { entry: ownStatusLine(detection.runner), wrap: existingStatusLine(project, current2.value, input.env) }
1759
1882
  };
1760
1883
  const merged2 = mergeSettings(current2.value, wanted, previous?.handlers ?? []);
1761
1884
  const after = renderJson(merged2.settings, before === void 0 ? DEFAULT_STYLE : styleOf(before));
@@ -1770,7 +1893,7 @@ var settingsItem = (project, detection, input, previous) => {
1770
1893
  });
1771
1894
  };
1772
1895
  var markdownItems = (project, runner, lang) => [
1773
- { path: AGENTS_FILE, render: (text7) => withAgentsBlock(text7, runner, lang) },
1896
+ { path: AGENTS_FILE, render: (text8) => withAgentsBlock(text8, runner, lang) },
1774
1897
  { path: CLAUDE_FILE, render: withAgentsImport }
1775
1898
  ].map(({ path, render: render3 }) => {
1776
1899
  const before = project.read(path);
@@ -1795,11 +1918,19 @@ var generatedItem = (project, file, previous) => {
1795
1918
  }
1796
1919
  return { ...base, action: actionFor(before, template, "update"), after: template };
1797
1920
  };
1798
- var generatedFiles = (input, runner, vendor) => {
1921
+ var PLAYWRIGHT_VERIFIER = ".claude/agents/verifier.md";
1922
+ var playwrightVerifier = (project) => {
1923
+ const wanted = configAt(parseConfigYaml(project.read(CONFIG_FILE) ?? ""), "review", "playwright") === "true";
1924
+ if (!wanted) return [];
1925
+ const template = agentMarkdown(withPlaywright(VERIFIER), GENERATED_MARKER);
1926
+ return [{ path: PLAYWRIGHT_VERIFIER, template, isOurs: (text8) => text8.includes(GENERATED_MARKER) }];
1927
+ };
1928
+ var generatedFiles = (project, input, runner, vendor) => {
1799
1929
  const own = [
1800
1930
  { path: CONFIG_FILE, template: configTemplate(input.preset, input.lang), isOurs: hasMarker },
1801
1931
  { path: VERIFY_SKILL, template: verifySkillTemplate(runner, input.lang), isOurs: isOwnSkill },
1802
- { path: POLICY_FILE, template: policyTemplate(input.lang), isOurs: hasMarker }
1932
+ { path: POLICY_FILE, template: policyTemplate(input.lang), isOurs: hasMarker },
1933
+ ...playwrightVerifier(project)
1803
1934
  ];
1804
1935
  if (!vendor) return accept(own);
1805
1936
  if (input.bundle === void 0) return reject({ kind: "bundle", detail: "the running finitemonkey is not a bundle" });
@@ -1810,7 +1941,7 @@ var buildPlan = (project, detection, input, previous, migrations = MIGRATIONS) =
1810
1941
  const settings = settingsItem(project, detection, input, entryFor(previous, SETTINGS_FILE));
1811
1942
  if (!settings.ok) return settings;
1812
1943
  const vendor = detection.runner.kind === "vendored" || entryFor(previous, VENDOR_BIN) !== void 0;
1813
- const files = generatedFiles(input, detection.runner, vendor);
1944
+ const files = generatedFiles(project, input, detection.runner, vendor);
1814
1945
  if (!files.ok) return files;
1815
1946
  const generated = files.value.map((file) => generatedItem(project, file, entryFor(previous, file.path)));
1816
1947
  return accept({
@@ -1837,9 +1968,9 @@ var needsWrite = (plan) => hasChanges(plan) || plan.previous === void 0;
1837
1968
  // src/install/apply.ts
1838
1969
  var stamp = (iso) => iso.replace(/[:.]/g, "-");
1839
1970
  var ensureParent = (project, relative11) => project.runtime.mkdir(project.abs(dirname3(relative11)));
1840
- var writeText = (project, relative11, text7) => {
1971
+ var writeText = (project, relative11, text8) => {
1841
1972
  const parent = ensureParent(project, relative11);
1842
- 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;
1843
1974
  return written.ok ? accept(void 0) : reject({ path: relative11, detail: written.error });
1844
1975
  };
1845
1976
  var restore = (project, items) => {
@@ -1987,8 +2118,8 @@ var projectAt = (root, runtime) => {
1987
2118
  runtime,
1988
2119
  abs,
1989
2120
  read: (relative11) => {
1990
- const text7 = runtime.readText(abs(relative11));
1991
- return text7.ok ? text7.value : void 0;
2121
+ const text8 = runtime.readText(abs(relative11));
2122
+ return text8.ok ? text8.value : void 0;
1992
2123
  },
1993
2124
  exists: (relative11) => runtime.stat(abs(relative11)) !== void 0,
1994
2125
  isDir: (relative11) => runtime.stat(abs(relative11))?.kind === "dir"
@@ -2061,8 +2192,8 @@ var invalidPreset = (raw) => problem("invalid_arguments", "error.invalid_preset"
2061
2192
  });
2062
2193
  var selfBundle = (ctx) => {
2063
2194
  if (!isBundlePath(ctx.runtime.selfPath)) return void 0;
2064
- const text7 = ctx.runtime.readText(ctx.runtime.selfPath);
2065
- return text7.ok ? text7.value : void 0;
2195
+ const text8 = ctx.runtime.readText(ctx.runtime.selfPath);
2196
+ return text8.ok ? text8.value : void 0;
2066
2197
  };
2067
2198
  var INHERITED = /* @__PURE__ */ new Set(["default", "config", "claude-settings"]);
2068
2199
  var planInputFor = (ctx, preset, bin, marketplace) => ({
@@ -2166,13 +2297,13 @@ var isPlaceholder = (value) => {
2166
2297
  var isTestOrLockPath = (path) => path !== void 0 && (LOCKFILES.test(toPosixPath(path)) || TEST_PATH.test(toPosixPath(path)));
2167
2298
 
2168
2299
  // src/secrets/entropy.ts
2169
- var shannonEntropy = (text7) => {
2170
- if (text7.length === 0) return 0;
2300
+ var shannonEntropy = (text8) => {
2301
+ if (text8.length === 0) return 0;
2171
2302
  const counts = /* @__PURE__ */ new Map();
2172
- 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);
2173
2304
  let entropy = 0;
2174
2305
  for (const count4 of counts.values()) {
2175
- const p = count4 / text7.length;
2306
+ const p = count4 / text8.length;
2176
2307
  entropy -= p * Math.log2(p);
2177
2308
  }
2178
2309
  return entropy;
@@ -2180,7 +2311,7 @@ var shannonEntropy = (text7) => {
2180
2311
 
2181
2312
  // src/secrets/fingerprint.ts
2182
2313
  import { createHash as createHash2 } from "node:crypto";
2183
- var sha256Hex = (text7) => createHash2("sha256").update(text7).digest("hex");
2314
+ var sha256Hex = (text8) => createHash2("sha256").update(text8).digest("hex");
2184
2315
  var fingerprintOf = (secret) => sha256Hex(`finitemonkey:secret:${secret}`).slice(0, 16);
2185
2316
  var ALNUM = /[A-Za-z0-9]/g;
2186
2317
  var MIN_TAIL_LENGTH = 12;
@@ -2190,9 +2321,9 @@ var tailOf = (secret) => {
2190
2321
  };
2191
2322
 
2192
2323
  // src/secrets/lines.ts
2193
- var lineLocator = (text7) => {
2324
+ var lineLocator = (text8) => {
2194
2325
  const starts = [0];
2195
- 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);
2196
2327
  return (offset) => {
2197
2328
  let low = 0;
2198
2329
  let high = starts.length - 1;
@@ -2486,12 +2617,12 @@ var hasRandomRun = (secret, length) => (secret.match(new RegExp(`[A-Za-z0-9]{${S
2486
2617
  );
2487
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);
2488
2619
  var NAME_CHAR = /[A-Za-z0-9_.-]/;
2489
- var nameAround = (text7, at) => {
2620
+ var nameAround = (text8, at) => {
2490
2621
  let start = at;
2491
- 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;
2492
2623
  let end = at;
2493
- while (end < text7.length && NAME_CHAR.test(text7.charAt(end))) end += 1;
2494
- 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();
2495
2626
  };
2496
2627
  var ignoredName = (rule, match) => rule.ignoredNames.length > 0 && rule.ignoredNames.includes(nameAround(match.input, match.index));
2497
2628
  var spanOf = (rule, match) => {
@@ -2501,9 +2632,9 @@ var spanOf = (rule, match) => {
2501
2632
  const secret = match.input.slice(start, end);
2502
2633
  return accepted(rule, secret) ? { rule, start, end, secret } : void 0;
2503
2634
  };
2504
- var ruleSpans = (rule, text7) => {
2635
+ var ruleSpans = (rule, text8) => {
2505
2636
  const spans = [];
2506
- for (const match of text7.matchAll(rule.pattern)) {
2637
+ for (const match of text8.matchAll(rule.pattern)) {
2507
2638
  const span = spanOf(rule, match);
2508
2639
  if (span !== void 0) spans.push(span);
2509
2640
  }
@@ -2529,31 +2660,31 @@ var toFinding = (span, locate2) => {
2529
2660
  tail: tailOf(span.secret)
2530
2661
  };
2531
2662
  };
2532
- var scanText = (text7, options = {}) => {
2533
- if (text7.length === 0) return [];
2534
- 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);
2535
2666
  if (rules.length === 0) return [];
2536
2667
  const taken = [];
2537
2668
  for (const rule of rules) {
2538
- 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);
2539
2670
  }
2540
2671
  if (taken.length === 0) return [];
2541
- const locate2 = lineLocator(text7);
2672
+ const locate2 = lineLocator(text8);
2542
2673
  return taken.sort((a, b) => a.start - b.start).map((span) => toFinding(span, locate2));
2543
2674
  };
2544
2675
 
2545
2676
  // src/mask/mask.ts
2546
2677
  var maskToken = (finding4) => `«secret:${finding4.ruleId}:${finding4.tail}»`;
2547
- var maskText = (text7) => {
2548
- const findings = scanText(text7, { rules: CREDENTIAL_RULES });
2549
- 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;
2550
2681
  let out = "";
2551
2682
  let cursor = 0;
2552
2683
  for (const finding4 of findings) {
2553
- out += text7.slice(cursor, finding4.start) + maskToken(finding4);
2684
+ out += text8.slice(cursor, finding4.start) + maskToken(finding4);
2554
2685
  cursor = finding4.end;
2555
2686
  }
2556
- return out + text7.slice(cursor);
2687
+ return out + text8.slice(cursor);
2557
2688
  };
2558
2689
  var maskDeep = (value) => {
2559
2690
  if (typeof value === "string") return maskText(value);
@@ -2771,14 +2902,14 @@ var toolVersion = (env, command) => {
2771
2902
  return result.ok ? result.stdout.trim().split("\n")[0] ?? "" : "not found";
2772
2903
  };
2773
2904
  var lastHookFailures = (env) => {
2774
- const text7 = env.runtime.readText(fmPaths(env.root).log);
2775
- if (!text7.ok) return [];
2776
- 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) => {
2777
2908
  const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
2778
2909
  return parsed?.ok === true && isRecord(parsed.value) && parsed.value["event"] === HOOK_FAILURE_EVENT;
2779
2910
  }).slice(-MAX_FAILURES);
2780
2911
  };
2781
- 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;
2782
2913
  var reportMarkdown = (env, ctx, envelope) => {
2783
2914
  const failures = lastHookFailures(env);
2784
2915
  const lines3 = [
@@ -2814,9 +2945,9 @@ var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
2814
2945
  `;
2815
2946
  var projectOwnsHooks = (ctx) => {
2816
2947
  const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
2817
- const text7 = ctx.runtime.readText(toAbsolutePath(join8(root, ...SETTINGS_FILE.split("/"))));
2818
- if (!text7.ok) return false;
2819
- 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);
2820
2951
  return parsed.ok && ownHandlersIn(parsed.value) > 0;
2821
2952
  };
2822
2953
 
@@ -2850,8 +2981,8 @@ var settingsPaths = (env) => {
2850
2981
  ];
2851
2982
  };
2852
2983
  var readSettings = (env) => settingsPaths(env).flatMap(({ scope: scope2, path }) => {
2853
- const text7 = env.runtime.readText(path);
2854
- 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;
2855
2986
  return parsed?.ok === true ? [{ scope: scope2, path, settings: parsed.value }] : [];
2856
2987
  });
2857
2988
  var scopesWith = (all, test) => all.filter(({ settings }) => test(settings)).map(({ scope: scope2 }) => scope2);
@@ -3137,7 +3268,7 @@ var editsOf = (input) => {
3137
3268
  });
3138
3269
  };
3139
3270
  var applyEdits = (before, edits) => edits.reduce(
3140
- (text7, edit) => edit.all ? text7.split(edit.oldText).join(edit.newText) : text7.replace(edit.oldText, () => edit.newText),
3271
+ (text8, edit) => edit.all ? text8.split(edit.oldText).join(edit.newText) : text8.replace(edit.oldText, () => edit.newText),
3141
3272
  before
3142
3273
  );
3143
3274
 
@@ -3157,7 +3288,7 @@ var familyOf = (base) => {
3157
3288
  if (found === void 0) throw new Error(`no reason family for ${base}`);
3158
3289
  return found;
3159
3290
  };
3160
- var capReason = (text7) => text7.length <= REASON_LIMIT ? text7 : `${text7.slice(0, REASON_LIMIT - 1)}…`;
3291
+ var capReason = (text8) => text8.length <= REASON_LIMIT ? text8 : `${text8.slice(0, REASON_LIMIT - 1)}…`;
3161
3292
  var modelReason = (t, base, params2 = {}) => {
3162
3293
  const claim = `${base}.claim`;
3163
3294
  const requires = `${base}.requires`;
@@ -3184,9 +3315,9 @@ var textRecord = (value) => isRecord(value) ? Object.fromEntries(
3184
3315
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
3185
3316
  ) : {};
3186
3317
  var readJsonFile = (runtime, path) => {
3187
- const text7 = runtime.readText(path);
3188
- if (!text7.ok) return void 0;
3189
- const parsed = parseJsonObject(text7.value);
3318
+ const text8 = runtime.readText(path);
3319
+ if (!text8.ok) return void 0;
3320
+ const parsed = parseJsonObject(text8.value);
3190
3321
  return parsed.ok ? parsed.value : void 0;
3191
3322
  };
3192
3323
  var writeJsonFile = (runtime, path, value) => runtime.writeText(path, `${JSON.stringify({ schemaVersion: 1, ...value }, null, 2)}
@@ -3299,7 +3430,7 @@ var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
3299
3430
  import { posix } from "node:path";
3300
3431
 
3301
3432
  // src/shell/scan.ts
3302
- var cursorOf = (text7) => ({ text: text7, pos: 0, incomplete: false, subs: [] });
3433
+ var cursorOf = (text8) => ({ text: text8, pos: 0, incomplete: false, subs: [] });
3303
3434
  var peek = (cursor, offset = 0) => cursor.text.charAt(cursor.pos + offset);
3304
3435
  var atEnd = (cursor) => cursor.pos >= cursor.text.length;
3305
3436
  var skipQuoted = (cursor, quote) => {
@@ -3514,8 +3645,8 @@ var nextToken = (cursor, tokens, pending) => {
3514
3645
  tokens.push({ kind: "op", text: op });
3515
3646
  if (op === "\n") flushHeredocs(cursor, tokens, pending);
3516
3647
  };
3517
- var lexBash = (text7) => {
3518
- const cursor = cursorOf(text7.replace(/\r\n/g, "\n"));
3648
+ var lexBash = (text8) => {
3649
+ const cursor = cursorOf(text8.replace(/\r\n/g, "\n"));
3519
3650
  const tokens = [];
3520
3651
  const pending = [];
3521
3652
  skipBlank(cursor);
@@ -3663,9 +3794,9 @@ var next = (cursor, tokens, cmd) => {
3663
3794
  }
3664
3795
  const op = OPERATORS2.find((candidate) => cursor.text.startsWith(candidate, cursor.pos));
3665
3796
  if (op !== void 0 || cmd && peek(cursor) === "&") {
3666
- const text7 = op ?? "&";
3667
- cursor.pos += text7.length;
3668
- tokens.push({ kind: "op", text: text7 });
3797
+ const text8 = op ?? "&";
3798
+ cursor.pos += text8.length;
3799
+ tokens.push({ kind: "op", text: text8 });
3669
3800
  return;
3670
3801
  }
3671
3802
  if ((peek(cursor) === "&" || peek(cursor) === ".") && peek(cursor, 1) === " " && commandStart(tokens)) {
@@ -3674,8 +3805,8 @@ var next = (cursor, tokens, cmd) => {
3674
3805
  }
3675
3806
  tokens.push(word(cursor, cmd));
3676
3807
  };
3677
- var lexPowerShell = (text7, cmd = false) => {
3678
- const cursor = cursorOf(text7.replace(/\r\n/g, "\n"));
3808
+ var lexPowerShell = (text8, cmd = false) => {
3809
+ const cursor = cursorOf(text8.replace(/\r\n/g, "\n"));
3679
3810
  const tokens = [];
3680
3811
  const skip = () => {
3681
3812
  while (peek(cursor) === " " || peek(cursor) === " ") cursor.pos += 1;
@@ -3878,7 +4009,7 @@ var MAX_DEPTH = 8;
3878
4009
  var ASSIGNMENT2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s;
3879
4010
  var PS_ENV = /^\$env:([A-Za-z_][A-Za-z0-9_]*)=?(.*)$/is;
3880
4011
  var CHANGES_DIR = /* @__PURE__ */ new Set(["cd", "pushd", "set-location", "push-location"]);
3881
- var lex = (text7, language) => language === "bash" ? lexBash(text7) : lexPowerShell(text7, language === "cmd");
4012
+ var lex = (text8, language) => language === "bash" ? lexBash(text8) : lexPowerShell(text8, language === "cmd");
3882
4013
  var isWindowsAbsolute = (path) => /^[A-Za-z]:\//.test(path);
3883
4014
  var joinDir = (current2, target2) => {
3884
4015
  const next2 = toPosixPath(target2);
@@ -3949,21 +4080,21 @@ var visit = (raw, language, context, depth, collector) => {
3949
4080
  emit(result.stage, raw, language, context, collector);
3950
4081
  for (const extra of result.extra) emit(extra, raw, language, context, collector);
3951
4082
  };
3952
- var collect = (text7, language, context, depth, collector) => {
4083
+ var collect = (text8, language, context, depth, collector) => {
3953
4084
  if (depth > MAX_DEPTH) {
3954
4085
  collector.incomplete = true;
3955
4086
  return;
3956
4087
  }
3957
- const lexed = lex(text7, language);
4088
+ const lexed = lex(text8, language);
3958
4089
  if (lexed.incomplete) collector.incomplete = true;
3959
4090
  for (const substitution of lexed.substitutions) {
3960
4091
  collect(substitution, language, { ...context }, depth + 1, collector);
3961
4092
  }
3962
4093
  for (const raw of rawCommands(lexed.tokens)) visit(raw, language, context, depth, collector);
3963
4094
  };
3964
- var normalizeCommand = (text7, dialect = "bash") => {
4095
+ var normalizeCommand = (text8, dialect = "bash") => {
3965
4096
  const collector = { out: [], incomplete: false };
3966
- collect(text7, dialect, { env: {}, dir: void 0, via: [] }, 0, collector);
4097
+ collect(text8, dialect, { env: {}, dir: void 0, via: [] }, 0, collector);
3967
4098
  return { commands: collector.out, incomplete: collector.incomplete };
3968
4099
  };
3969
4100
  var dialectOf = (toolName3) => toolName3 === "PowerShell" ? "powershell" : "bash";
@@ -3975,7 +4106,7 @@ var segmentsOf = (command) => normalizeCommand(command).commands.map((item2) =>
3975
4106
 
3976
4107
  // src/guards/shell.ts
3977
4108
  var HEREDOC = /^<<[-<]?$/;
3978
- var parseShell = (text7) => normalizeCommand(text7).commands.map((command) => ({
4109
+ var parseShell = (text8) => normalizeCommand(text8).commands.map((command) => ({
3979
4110
  env: command.env,
3980
4111
  argv: command.program === "" ? [] : [command.program, ...command.args],
3981
4112
  redirects: command.redirects.filter((redirect) => !HEREDOC.test(redirect.op)).map((redirect) => redirect.target)
@@ -4080,6 +4211,15 @@ var bypassGuard = (input) => {
4080
4211
  );
4081
4212
  };
4082
4213
 
4214
+ // src/guards/review/hook.ts
4215
+ var FORBIDDEN = new Set(FORBIDDEN_TOOLS);
4216
+ var reviewAgentGuard = (input) => {
4217
+ const agent = reviewAgentOf(textField(input.payload.raw, "agent_type") ?? "");
4218
+ const tool = textField(input.payload.raw, "tool_name") ?? "";
4219
+ if (agent === void 0 || !FORBIDDEN.has(tool)) return { kind: "allow" };
4220
+ return { kind: "block", reason: input.t("review.guard.read_only", { agent, tool }) };
4221
+ };
4222
+
4083
4223
  // src/guards/self/protected.ts
4084
4224
  import { relative as relative2 } from "node:path";
4085
4225
  var normalize = (path) => toPosixPath(path).replace(/^\.\//, "");
@@ -4128,9 +4268,9 @@ var writtenPaths = (command) => {
4128
4268
  if (name === "git" && GIT_WRITERS.has(operands2[0] ?? "")) return [...command.redirects, ...operands2.slice(1)];
4129
4269
  return command.redirects;
4130
4270
  };
4131
- var mentioned = (text7) => {
4132
- if (/\.finitemonkey[\\/]allow/.test(text7)) return "allow_store";
4133
- if (/\.finitemonkey|\.gitleaksignore/.test(text7)) return "policy";
4271
+ var mentioned = (text8) => {
4272
+ if (/\.finitemonkey[\\/]allow/.test(text8)) return "allow_store";
4273
+ if (/\.finitemonkey|\.gitleaksignore/.test(text8)) return "policy";
4134
4274
  return void 0;
4135
4275
  };
4136
4276
  var bashSelfTargets = (root, commandLine) => parseShell(commandLine).flatMap(
@@ -4334,8 +4474,8 @@ var compareConfig = (path, before, after, accepted2) => {
4334
4474
  // src/guards/tests/hook.ts
4335
4475
  var localPath = (root, path) => toPosixPath(relative3(root, toAbsolutePath(path, root)));
4336
4476
  var read = (input, path) => {
4337
- const text7 = input.runtime.readText(path);
4338
- return text7.ok ? text7.value : void 0;
4477
+ const text8 = input.runtime.readText(path);
4478
+ return text8.ok ? text8.value : void 0;
4339
4479
  };
4340
4480
  var proposed = (tool, args2, before) => {
4341
4481
  if (tool === "Write") return textField(args2, "content") ?? "";
@@ -4410,7 +4550,7 @@ var contextOutput = (event, additionalContext, systemMessage) => ({
4410
4550
  hookSpecificOutput: { hookEventName: event, additionalContext },
4411
4551
  ...systemMessage === void 0 ? {} : { systemMessage }
4412
4552
  });
4413
- var human = (text7) => `finitemonkey: ${text7}`;
4553
+ var human = (text8) => `finitemonkey: ${text8}`;
4414
4554
 
4415
4555
  // src/look/reads.ts
4416
4556
  var READS_FILE = "look-reads.json";
@@ -4472,8 +4612,8 @@ var parseMarkers = (prompt) => {
4472
4612
  // src/policy/pause.ts
4473
4613
  var PAUSE_FILE = "pause.json";
4474
4614
  var readPause = (runtime, paths) => {
4475
- const text7 = runtime.readText(paths.cache(PAUSE_FILE));
4476
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
4615
+ const text8 = runtime.readText(paths.cache(PAUSE_FILE));
4616
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
4477
4617
  if (parsed?.ok !== true || !isRecord(parsed.value)) return void 0;
4478
4618
  const { sessionId, since, by } = parsed.value;
4479
4619
  if (typeof sessionId !== "string" || typeof since !== "string") return void 0;
@@ -4528,9 +4668,9 @@ var countIn = (value) => {
4528
4668
  if (Array.isArray(value)) return value.reduce((sum, item2) => sum + countIn(item2), 0);
4529
4669
  return isRecord(value) ? Object.values(value).reduce((sum, item2) => sum + countIn(item2), 0) : 0;
4530
4670
  };
4531
- var censusOf = (text7) => {
4532
- if (text7 === void 0) return { hooks: 0, disabled: false };
4533
- const parsed = parseJsonObject(text7);
4671
+ var censusOf = (text8) => {
4672
+ if (text8 === void 0) return { hooks: 0, disabled: false };
4673
+ const parsed = parseJsonObject(text8);
4534
4674
  if (!parsed.ok) return void 0;
4535
4675
  return { hooks: countIn(parsed.value["hooks"]), disabled: parsed.value["disableAllHooks"] === true };
4536
4676
  };
@@ -4540,8 +4680,8 @@ var settingsFiles = (paths, env) => [
4540
4680
  ...env["HOME"] === void 0 ? [] : [toAbsolutePath(join13(env["HOME"], ".claude", "settings.json"))]
4541
4681
  ];
4542
4682
  var read2 = (runtime, path) => {
4543
- const text7 = runtime.readText(path);
4544
- return text7.ok ? text7.value : void 0;
4683
+ const text8 = runtime.readText(path);
4684
+ return text8.ok ? text8.value : void 0;
4545
4685
  };
4546
4686
  var known = (runtime, paths) => {
4547
4687
  const raw = readJsonFile(runtime, paths.cache(CONFIG_FILE3))?.["hooks"];
@@ -4596,9 +4736,9 @@ var LOCKFILES2 = [
4596
4736
  ];
4597
4737
  var packageManager = (files) => LOCKFILES2.find(([file]) => files.exists(file))?.[1] ?? "npm";
4598
4738
  var scriptChecks = (files) => {
4599
- const text7 = files.read("package.json");
4600
- if (text7 === void 0) return [];
4601
- const parsed = parseJsonObject(text7);
4739
+ const text8 = files.read("package.json");
4740
+ if (text8 === void 0) return [];
4741
+ const parsed = parseJsonObject(text8);
4602
4742
  const scripts = parsed.ok ? parsed.value["scripts"] : void 0;
4603
4743
  if (!isRecord(scripts)) return [];
4604
4744
  const pm = packageManager(files);
@@ -4688,16 +4828,16 @@ var unescapeDouble = (body2) => {
4688
4828
  }
4689
4829
  return accept(out);
4690
4830
  };
4691
- var quotedEnd = (text7, start) => {
4692
- const quote = text7[start];
4693
- for (let index = start + 1; index < text7.length; index += 1) {
4694
- const char = text7[index];
4831
+ var quotedEnd = (text8, start) => {
4832
+ const quote = text8[start];
4833
+ for (let index = start + 1; index < text8.length; index += 1) {
4834
+ const char = text8[index];
4695
4835
  if (quote === '"' && char === "\\") {
4696
4836
  index += 1;
4697
4837
  continue;
4698
4838
  }
4699
4839
  if (char !== quote) continue;
4700
- if (quote === "'" && text7[index + 1] === "'") {
4840
+ if (quote === "'" && text8[index + 1] === "'") {
4701
4841
  index += 1;
4702
4842
  continue;
4703
4843
  }
@@ -4705,20 +4845,20 @@ var quotedEnd = (text7, start) => {
4705
4845
  }
4706
4846
  return -1;
4707
4847
  };
4708
- var parseQuoted = (text7) => {
4709
- const end = quotedEnd(text7, 0);
4710
- if (end !== text7.length - 1) return reject(`unterminated or trailing text after quoted string`);
4711
- const body2 = text7.slice(1, -1);
4712
- return text7.startsWith('"') ? unescapeDouble(body2) : accept(body2.replace(/''/g, "'"));
4848
+ var parseQuoted = (text8) => {
4849
+ const end = quotedEnd(text8, 0);
4850
+ if (end !== text8.length - 1) return reject(`unterminated or trailing text after quoted string`);
4851
+ const body2 = text8.slice(1, -1);
4852
+ return text8.startsWith('"') ? unescapeDouble(body2) : accept(body2.replace(/''/g, "'"));
4713
4853
  };
4714
4854
  var NUMBER = /^-?(0|[1-9]\d*)(\.\d+)?([eE][-+]?\d+)?$/;
4715
4855
  var UNSUPPORTED = /^[&*!|>%@`]/;
4716
- var parsePlain = (text7) => {
4717
- if (UNSUPPORTED.test(text7)) return reject(`unsupported YAML syntax "${text7.slice(0, 1)}"`);
4718
- if (text7 === "true" || text7 === "false") return accept(text7 === "true");
4719
- if (text7 === "null" || text7 === "~") return accept(null);
4720
- if (NUMBER.test(text7)) return accept(Number(text7));
4721
- return accept(text7);
4856
+ var parsePlain = (text8) => {
4857
+ if (UNSUPPORTED.test(text8)) return reject(`unsupported YAML syntax "${text8.slice(0, 1)}"`);
4858
+ if (text8 === "true" || text8 === "false") return accept(text8 === "true");
4859
+ if (text8 === "null" || text8 === "~") return accept(null);
4860
+ if (NUMBER.test(text8)) return accept(Number(text8));
4861
+ return accept(text8);
4722
4862
  };
4723
4863
 
4724
4864
  // src/core/yaml/flow.ts
@@ -4745,17 +4885,17 @@ var splitTop = (body2) => {
4745
4885
  if (last.trim() !== "" || parts.length > 0) parts.push(last);
4746
4886
  return accept(parts.map((part2) => part2.trim()));
4747
4887
  };
4748
- var splitKey = (text7) => {
4888
+ var splitKey = (text8) => {
4749
4889
  let index = 0;
4750
- if (text7.startsWith('"') || text7.startsWith("'")) {
4751
- index = quotedEnd(text7, 0) + 1;
4890
+ if (text8.startsWith('"') || text8.startsWith("'")) {
4891
+ index = quotedEnd(text8, 0) + 1;
4752
4892
  if (index <= 0) return void 0;
4753
4893
  }
4754
- for (; index < text7.length; index += 1) {
4755
- const char = text7[index];
4756
- if (char === "#" && text7[index - 1] === " ") return void 0;
4757
- if (char === ":" && (index + 1 === text7.length || text7[index + 1] === " ")) {
4758
- return { key: text7.slice(0, index).trim(), rest: text7.slice(index + 1).trim() };
4894
+ for (; index < text8.length; index += 1) {
4895
+ const char = text8[index];
4896
+ if (char === "#" && text8[index - 1] === " ") return void 0;
4897
+ if (char === ":" && (index + 1 === text8.length || text8[index + 1] === " ")) {
4898
+ return { key: text8.slice(0, index).trim(), rest: text8.slice(index + 1).trim() };
4759
4899
  }
4760
4900
  }
4761
4901
  return void 0;
@@ -4784,39 +4924,39 @@ var flowSeq = (parts) => {
4784
4924
  }
4785
4925
  return accept(out);
4786
4926
  };
4787
- var parseValue = (text7) => {
4788
- if (text7 === "") return accept(null);
4789
- const first = text7[0] ?? "";
4790
- if (first === '"' || first === "'") return parseQuoted(text7);
4927
+ var parseValue = (text8) => {
4928
+ if (text8 === "") return accept(null);
4929
+ const first = text8[0] ?? "";
4930
+ if (first === '"' || first === "'") return parseQuoted(text8);
4791
4931
  const close2 = OPEN[first];
4792
- if (close2 === void 0) return parsePlain(text7);
4793
- if (!text7.endsWith(close2)) return reject(`flow collection must end on the same line with "${close2}"`);
4794
- const parts = splitTop(text7.slice(1, -1));
4932
+ if (close2 === void 0) return parsePlain(text8);
4933
+ if (!text8.endsWith(close2)) return reject(`flow collection must end on the same line with "${close2}"`);
4934
+ const parts = splitTop(text8.slice(1, -1));
4795
4935
  if (!parts.ok) return parts;
4796
4936
  return first === "[" ? flowSeq(parts.value) : flowMap(parts.value);
4797
4937
  };
4798
4938
 
4799
4939
  // src/core/yaml/parse.ts
4800
4940
  var BOM = new RegExp(`^${String.fromCharCode(65279)}`);
4801
- var stripComment2 = (text7) => {
4802
- for (let index = 0; index < text7.length; index += 1) {
4803
- const char = text7[index];
4941
+ var stripComment2 = (text8) => {
4942
+ for (let index = 0; index < text8.length; index += 1) {
4943
+ const char = text8[index];
4804
4944
  if (char === '"' || char === "'") {
4805
- const end = quotedEnd(text7, index);
4806
- if (end < 0) return text7;
4945
+ const end = quotedEnd(text8, index);
4946
+ if (end < 0) return text8;
4807
4947
  index = end;
4808
- } else if (char === "#" && (index === 0 || text7[index - 1] === " ")) return text7.slice(0, index);
4948
+ } else if (char === "#" && (index === 0 || text8[index - 1] === " ")) return text8.slice(0, index);
4809
4949
  }
4810
- return text7;
4950
+ return text8;
4811
4951
  };
4812
4952
  var toLines = (source) => {
4813
4953
  const lines3 = [];
4814
4954
  const raw = source.replace(BOM, "").split(/\r?\n/);
4815
4955
  for (const [index, full] of raw.entries()) {
4816
- const text7 = stripComment2(full).trimEnd();
4817
- const body2 = text7.trimStart();
4956
+ const text8 = stripComment2(full).trimEnd();
4957
+ const body2 = text8.trimStart();
4818
4958
  if (body2 === "" || body2 === "---" && lines3.length === 0) continue;
4819
- const indentText = text7.slice(0, text7.length - body2.length);
4959
+ const indentText = text8.slice(0, text8.length - body2.length);
4820
4960
  if (indentText.includes(" ")) return reject({ line: index + 1, message: "tabs are not allowed for indentation" });
4821
4961
  if (body2 === "---" || body2 === "...")
4822
4962
  return reject({ line: index + 1, message: "only one YAML document is allowed" });
@@ -4825,11 +4965,11 @@ var toLines = (source) => {
4825
4965
  return accept(lines3);
4826
4966
  };
4827
4967
  var fail = (line2, message) => reject({ line: line2?.number ?? 0, message });
4828
- var scalar = (line2, text7) => {
4829
- const value = parseValue(text7);
4968
+ var scalar = (line2, text8) => {
4969
+ const value = parseValue(text8);
4830
4970
  return value.ok ? value : fail(line2, value.error);
4831
4971
  };
4832
- var isItem = (text7) => text7 === "-" || text7.startsWith("- ");
4972
+ var isItem = (text8) => text8 === "-" || text8.startsWith("- ");
4833
4973
  var child = (lines3, index, parent, sameIndentSeq) => {
4834
4974
  const next2 = lines3[index];
4835
4975
  if (next2 !== void 0 && next2.indent > parent.indent) return block(lines3, index, next2.indent);
@@ -5072,8 +5212,8 @@ var validateContract = (value, implicit = false) => {
5072
5212
  // src/task/load.ts
5073
5213
  var contractHash = (contract) => sha256Hex(JSON.stringify(contract.acceptance.map((check2) => [check2.id, check2.kind, check2.run ?? ""])));
5074
5214
  var taskRelative = (id) => `.finitemonkey/tasks/${id}.yml`;
5075
- var parseContract = (id, text7) => {
5076
- const yaml = parseYaml(text7);
5215
+ var parseContract = (id, text8) => {
5216
+ const yaml = parseYaml(text8);
5077
5217
  if (!yaml.ok)
5078
5218
  return reject({ kind: "invalid", id, issues: [`line ${String(yaml.error.line)}: ${yaml.error.message}`] });
5079
5219
  const valid = validateContract(yaml.value);
@@ -5164,29 +5304,29 @@ var gitAt = (runtime, cwd) => {
5164
5304
  }
5165
5305
  };
5166
5306
  };
5167
- var gitRoot = (git) => {
5168
- const top = git.out(["rev-parse", "--show-toplevel"])?.trim();
5307
+ var gitRoot = (git2) => {
5308
+ const top = git2.out(["rev-parse", "--show-toplevel"])?.trim();
5169
5309
  return top === void 0 || top === "" ? void 0 : toAbsolutePath(top);
5170
5310
  };
5171
5311
  var nonEmpty = (value) => value === "" ? void 0 : value;
5172
- var headSha = (git) => git.out(["rev-parse", "--verify", "-q", "HEAD"])?.trim();
5173
- var currentBranch = (git) => {
5174
- const name = git.out(["rev-parse", "--abbrev-ref", "HEAD"])?.trim();
5312
+ var headSha = (git2) => git2.out(["rev-parse", "--verify", "-q", "HEAD"])?.trim();
5313
+ var currentBranch = (git2) => {
5314
+ const name = git2.out(["rev-parse", "--abbrev-ref", "HEAD"])?.trim();
5175
5315
  return name === void 0 || name === "HEAD" || name === "" ? void 0 : name;
5176
5316
  };
5177
- var defaultRemoteBranch = (git) => nonEmpty(git.out(["rev-parse", "--abbrev-ref", "origin/HEAD"])?.trim());
5178
- var resolveBase = (git, declared) => {
5179
- if (declared !== void 0 && git.out(["rev-parse", "--verify", "-q", `${declared}^{commit}`]) !== void 0) {
5317
+ var defaultRemoteBranch = (git2) => nonEmpty(git2.out(["rev-parse", "--abbrev-ref", "origin/HEAD"])?.trim());
5318
+ var resolveBase = (git2, declared) => {
5319
+ if (declared !== void 0 && git2.out(["rev-parse", "--verify", "-q", `${declared}^{commit}`]) !== void 0) {
5180
5320
  return declared;
5181
5321
  }
5182
- const remote = defaultRemoteBranch(git);
5183
- const merged2 = remote === void 0 ? void 0 : git.out(["merge-base", "HEAD", remote])?.trim();
5184
- return nonEmpty(merged2) ?? nonEmpty(headSha(git)) ?? EMPTY_TREE;
5322
+ const remote = defaultRemoteBranch(git2);
5323
+ const merged2 = remote === void 0 ? void 0 : git2.out(["merge-base", "HEAD", remote])?.trim();
5324
+ return nonEmpty(merged2) ?? nonEmpty(headSha(git2)) ?? EMPTY_TREE;
5185
5325
  };
5186
- var nulFields = (text7) => text7.split("\0").filter((part2) => part2 !== "");
5187
- var untracked = (git) => nulFields(git.out(["ls-files", "--others", "--exclude-standard", "-z", "--", ".", EXCLUDE_FM]) ?? "");
5188
- var parseNameStatus = (text7) => {
5189
- const fields = nulFields(text7);
5326
+ var nulFields = (text8) => text8.split("\0").filter((part2) => part2 !== "");
5327
+ var untracked = (git2) => nulFields(git2.out(["ls-files", "--others", "--exclude-standard", "-z", "--", ".", EXCLUDE_FM]) ?? "");
5328
+ var parseNameStatus = (text8) => {
5329
+ const fields = nulFields(text8);
5190
5330
  const changes = [];
5191
5331
  for (let index = 0; index < fields.length; index += 1) {
5192
5332
  const code = fields[index] ?? "";
@@ -5204,21 +5344,21 @@ var parseNameStatus = (text7) => {
5204
5344
  }
5205
5345
  return changes;
5206
5346
  };
5207
- var changedFiles = (git, base) => {
5347
+ var changedFiles = (git2, base) => {
5208
5348
  const tracked = parseNameStatus(
5209
- git.out(["diff", "--name-status", "-M", "-z", "--no-color", base, "--", ".", EXCLUDE_FM]) ?? ""
5349
+ git2.out(["diff", "--name-status", "-M", "-z", "--no-color", base, "--", ".", EXCLUDE_FM]) ?? ""
5210
5350
  );
5211
5351
  const known2 = new Set(tracked.map((change) => change.path));
5212
- const fresh3 = untracked(git).filter((path) => !known2.has(path)).map((path) => ({ status: "A", path }));
5352
+ const fresh3 = untracked(git2).filter((path) => !known2.has(path)).map((path) => ({ status: "A", path }));
5213
5353
  return [...tracked, ...fresh3];
5214
5354
  };
5215
- var fileAt = (git, rev, path) => rev === EMPTY_TREE ? void 0 : git.out(["show", `${rev}:${path}`]);
5216
- var treeHash = (git) => {
5217
- const head = headSha(git) ?? EMPTY_TREE;
5218
- const diff = git.run(["diff", "HEAD", "--binary", "--no-ext-diff", "--no-color", "--", ".", EXCLUDE_FM]);
5355
+ var fileAt = (git2, rev, path) => rev === EMPTY_TREE ? void 0 : git2.out(["show", `${rev}:${path}`]);
5356
+ var treeHash = (git2) => {
5357
+ const head = headSha(git2) ?? EMPTY_TREE;
5358
+ const diff = git2.run(["diff", "HEAD", "--binary", "--no-ext-diff", "--no-color", "--", ".", EXCLUDE_FM]);
5219
5359
  if (!diff.found || diff.exitCode !== 0 && head !== EMPTY_TREE) return void 0;
5220
- const files = untracked(git);
5221
- const hashes = files.length === 0 ? "" : git.out(["hash-object", "--", ...files]) ?? "";
5360
+ const files = untracked(git2);
5361
+ const hashes = files.length === 0 ? "" : git2.out(["hash-object", "--", ...files]) ?? "";
5222
5362
  const digest = sha256Hex([head, diff.stdout, files.join("\0"), hashes].join("\n--fm--\n"));
5223
5363
  return `w${digest.slice(0, 16)}`;
5224
5364
  };
@@ -5237,29 +5377,29 @@ var parseAddedLines = (diff) => {
5237
5377
  }
5238
5378
  return added;
5239
5379
  };
5240
- var addedLines = (git, base) => parseAddedLines(git.out(["diff", "-U0", "--no-color", "--no-ext-diff", base, "--", ".", EXCLUDE_FM]) ?? "");
5241
- var numstat = (git, base) => {
5242
- const rows = (git.out(["diff", "--numstat", "--no-color", base, "--", ".", EXCLUDE_FM]) ?? "").split("\n").filter((row) => row.trim() !== "");
5380
+ var addedLines = (git2, base) => parseAddedLines(git2.out(["diff", "-U0", "--no-color", "--no-ext-diff", base, "--", ".", EXCLUDE_FM]) ?? "");
5381
+ var numstat = (git2, base) => {
5382
+ const rows = (git2.out(["diff", "--numstat", "--no-color", base, "--", ".", EXCLUDE_FM]) ?? "").split("\n").filter((row) => row.trim() !== "");
5243
5383
  const lines3 = rows.reduce((sum, row) => {
5244
5384
  const [plus = "0", minus = "0"] = row.split(" ");
5245
5385
  return sum + (Number(plus) || 0) + (Number(minus) || 0);
5246
5386
  }, 0);
5247
5387
  return { files: rows.length, lines: lines3 };
5248
5388
  };
5249
- var dirtyPaths = (git) => nulFields(git.out(["status", "--porcelain", "-z", "--", ".", EXCLUDE_FM]) ?? "").map((entry2) => entry2.slice(3));
5250
- var aheadOfUpstream = (git) => {
5251
- const count4 = git.out(["rev-list", "--count", "@{u}..HEAD"])?.trim();
5389
+ var dirtyPaths = (git2) => nulFields(git2.out(["status", "--porcelain", "-z", "--", ".", EXCLUDE_FM]) ?? "").map((entry2) => entry2.slice(3));
5390
+ var aheadOfUpstream = (git2) => {
5391
+ const count4 = git2.out(["rev-list", "--count", "@{u}..HEAD"])?.trim();
5252
5392
  return count4 === void 0 ? void 0 : Number(count4);
5253
5393
  };
5254
5394
 
5255
5395
  // src/gate/diff-shape.ts
5256
5396
  var untrackedLines = (runtime, root, changes) => changes.filter((change) => change.status === "A").reduce((sum, change) => {
5257
- const text7 = runtime.readText(toAbsolutePath(join14(root, change.path)));
5258
- return sum + (text7.ok ? text7.value.split("\n").length : 0);
5397
+ const text8 = runtime.readText(toAbsolutePath(join14(root, change.path)));
5398
+ return sum + (text8.ok ? text8.value.split("\n").length : 0);
5259
5399
  }, 0);
5260
- var diffShape = (runtime, git, base, changes) => ({
5400
+ var diffShape = (runtime, git2, base, changes) => ({
5261
5401
  files: changes.map((change) => change.path),
5262
- lines: numstat(git, base).lines + untrackedLines(runtime, git.cwd, changes)
5402
+ lines: numstat(git2, base).lines + untrackedLines(runtime, git2.cwd, changes)
5263
5403
  });
5264
5404
 
5265
5405
  // src/gate/pr-parse.ts
@@ -5278,8 +5418,8 @@ var ciChecks = (value) => Array.isArray(value) ? value.flatMap((item2) => {
5278
5418
  const name = optionalString(item2, "name") ?? optionalString(item2, "context") ?? "?";
5279
5419
  return [{ name, state: ciState(item2) }];
5280
5420
  }) : [];
5281
- var parsePrView = (text7) => {
5282
- const parsed = parseJsonObject(text7);
5421
+ var parsePrView = (text8) => {
5422
+ const parsed = parseJsonObject(text8);
5283
5423
  if (!parsed.ok) return void 0;
5284
5424
  const record3 = parsed.value;
5285
5425
  const number2 = record3["number"];
@@ -5319,11 +5459,11 @@ var cached = (runtime, paths, branch, ttlMs) => {
5319
5459
  const view2 = raw["view"];
5320
5460
  return typeof view2 === "string" ? parsePrView(view2) : void 0;
5321
5461
  };
5322
- var fetchView = (runtime, git, branch) => {
5323
- const auth = ghAuthenticated(runtime, git.cwd);
5462
+ var fetchView = (runtime, git2, branch) => {
5463
+ const auth = ghAuthenticated(runtime, git2.cwd);
5324
5464
  if (auth === "timeout") return { kind: "network", detail: "gh auth status timed out" };
5325
5465
  if (auth === "no") return { kind: "no_gh" };
5326
- const view2 = gh(runtime, git.cwd, ["pr", "view", branch, "--json", PR_FIELDS]);
5466
+ const view2 = gh(runtime, git2.cwd, ["pr", "view", branch, "--json", PR_FIELDS]);
5327
5467
  if (view2.timedOut) return { kind: "network", detail: "gh pr view timed out" };
5328
5468
  if (view2.exitCode !== 0) {
5329
5469
  if (/no (open )?pull requests? found|no git remotes/i.test(view2.stderr)) return { kind: "none", branch };
@@ -5332,20 +5472,20 @@ var fetchView = (runtime, git, branch) => {
5332
5472
  const pr = parsePrView(view2.stdout);
5333
5473
  return pr === void 0 ? { kind: "network", detail: "gh returned unexpected JSON" } : { kind: "fetched", branch, pr, view: view2.stdout };
5334
5474
  };
5335
- var prStatus = (runtime, git, paths, ttlMs = PR_CACHE_TTL_MS) => {
5336
- const branch = currentBranch(git);
5475
+ var prStatus = (runtime, git2, paths, ttlMs = PR_CACHE_TTL_MS) => {
5476
+ const branch = currentBranch(git2);
5337
5477
  if (branch === void 0) return { kind: "no_branch" };
5338
5478
  const hit3 = cached(runtime, paths, branch, ttlMs);
5339
- if (hit3 !== void 0) return { kind: "found", branch, pr: hit3, localHead: headSha(git) };
5340
- const status = fetchView(runtime, git, branch);
5479
+ if (hit3 !== void 0) return { kind: "found", branch, pr: hit3, localHead: headSha(git2) };
5480
+ const status = fetchView(runtime, git2, branch);
5341
5481
  if (status.kind !== "fetched") return status;
5342
5482
  writeJsonFile(runtime, paths.cache("pr.json"), { fetchedAt: nowMs(runtime), branch, view: status.view });
5343
- return { kind: "found", branch, pr: status.pr, localHead: headSha(git) };
5483
+ return { kind: "found", branch, pr: status.pr, localHead: headSha(git2) };
5344
5484
  };
5345
- var conflictFiles = (git, base) => {
5485
+ var conflictFiles = (git2, base) => {
5346
5486
  for (const target2 of [`origin/${base}`, base]) {
5347
- if (git.out(["rev-parse", "--verify", "-q", `${target2}^{commit}`]) === void 0) continue;
5348
- const result = git.run(["merge-tree", "--write-tree", "--name-only", "--no-messages", target2, "HEAD"]);
5487
+ if (git2.out(["rev-parse", "--verify", "-q", `${target2}^{commit}`]) === void 0) continue;
5488
+ const result = git2.run(["merge-tree", "--write-tree", "--name-only", "--no-messages", target2, "HEAD"]);
5349
5489
  if (result.found && result.exitCode === 1) return parseMergeTreeConflicts(result.stdout);
5350
5490
  if (result.found && result.exitCode === 0) return [];
5351
5491
  }
@@ -5460,20 +5600,20 @@ var applySize = (contract, verdict) => {
5460
5600
  // src/gate/prepare.ts
5461
5601
  var diskFiles = (runtime, root) => ({
5462
5602
  read: (relative11) => {
5463
- const text7 = runtime.readText(toAbsolutePath(join15(root, relative11)));
5464
- return text7.ok ? text7.value : void 0;
5603
+ const text8 = runtime.readText(toAbsolutePath(join15(root, relative11)));
5604
+ return text8.ok ? text8.value : void 0;
5465
5605
  },
5466
5606
  exists: (relative11) => runtime.stat(toAbsolutePath(join15(root, relative11))) !== void 0
5467
5607
  });
5468
- var baseFiles = (git, base, disk) => ({
5469
- read: (relative11) => base === EMPTY_TREE ? disk.read(relative11) : fileAt(git, base, relative11),
5608
+ var baseFiles = (git2, base, disk) => ({
5609
+ read: (relative11) => base === EMPTY_TREE ? disk.read(relative11) : fileAt(git2, base, relative11),
5470
5610
  exists: disk.exists
5471
5611
  });
5472
- var loadContract = (options, paths, git, base) => {
5612
+ var loadContract = (options, paths, git2, base) => {
5473
5613
  const disk = diskFiles(options.runtime, paths.root);
5474
5614
  const id = activeTaskId(options.runtime, paths, options.taskId);
5475
- if (id === void 0) return accept(loadImplicit(options.runtime, paths, baseFiles(git, base, disk)));
5476
- return loadTask(options.runtime, paths, { disk: disk.read, base: (relative11) => fileAt(git, base, relative11) }, id);
5615
+ if (id === void 0) return accept(loadImplicit(options.runtime, paths, baseFiles(git2, base, disk)));
5616
+ return loadTask(options.runtime, paths, { disk: disk.read, base: (relative11) => fileAt(git2, base, relative11) }, id);
5477
5617
  };
5478
5618
  var memo = (make) => {
5479
5619
  let value;
@@ -5495,30 +5635,30 @@ var skipReason = (runtime, paths, loaded, changes, tree, sessionId) => {
5495
5635
  const session = readSession(runtime, paths);
5496
5636
  return session !== void 0 && session.sessionId === sessionId && session.startTree === tree ? "no_session_edits" : void 0;
5497
5637
  };
5498
- var measureSize = (runtime, git, base, changes, contract) => sizeVerdict(contract.size, diffShape(runtime, git, base, changes), sizeConfig(loadProjectConfig(runtime, git.cwd)));
5638
+ var measureSize = (runtime, git2, base, changes, contract) => sizeVerdict(contract.size, diffShape(runtime, git2, base, changes), sizeConfig(loadProjectConfig(runtime, git2.cwd)));
5499
5639
  var prepareGate = (options) => {
5500
5640
  const probe = gitAt(options.runtime, options.cwd);
5501
5641
  const root = gitRoot(probe);
5502
5642
  if (root === void 0) {
5503
5643
  return options.taskId === void 0 ? { kind: "skipped", reason: "no_repo" } : { kind: "failed", failure: { kind: "not_a_repo" } };
5504
5644
  }
5505
- const git = gitAt(options.runtime, root);
5645
+ const git2 = gitAt(options.runtime, root);
5506
5646
  const paths = fmPaths(root);
5507
5647
  const trust2 = readTrust(options.runtime, paths);
5508
- const firstBase = resolveBase(git, void 0);
5509
- const loaded = loadContract(options, paths, git, firstBase);
5648
+ const firstBase = resolveBase(git2, void 0);
5649
+ const loaded = loadContract(options, paths, git2, firstBase);
5510
5650
  if (!loaded.ok) return { kind: "failed", failure: { kind: "contract", problem: loaded.error } };
5511
- const base = resolveBase(git, loaded.value.contract.base);
5512
- const changes = changedFiles(git, base);
5513
- const tree = treeHash(git);
5651
+ const base = resolveBase(git2, loaded.value.contract.base);
5652
+ const changes = changedFiles(git2, base);
5653
+ const tree = treeHash(git2);
5514
5654
  if (tree === void 0) return { kind: "failed", failure: { kind: "no_tree_hash" } };
5515
5655
  const skip = options.noSkip === true ? void 0 : skipReason(options.runtime, paths, loaded.value, changes, tree, options.sessionId);
5516
5656
  if (skip !== void 0) return { kind: "skipped", reason: skip };
5517
- const pr = memo(() => prStatus(options.runtime, git, paths));
5518
- const size = measureSize(options.runtime, git, base, changes, loaded.value.contract);
5657
+ const pr = memo(() => prStatus(options.runtime, git2, paths));
5658
+ const size = measureSize(options.runtime, git2, base, changes, loaded.value.contract);
5519
5659
  const ctx = {
5520
5660
  runtime: options.runtime,
5521
- git,
5661
+ git: git2,
5522
5662
  paths,
5523
5663
  contract: applySize(loaded.value.contract, size),
5524
5664
  size,
@@ -5582,11 +5722,11 @@ var trust = (input, paths, task) => {
5582
5722
  });
5583
5723
  if (ready.kind !== "ready" || ready.prepared.ctx.contract.id !== task) return false;
5584
5724
  const lock = readTrust(runtime, paths);
5585
- const text7 = runtime.readText(paths.task(task));
5725
+ const text8 = runtime.readText(paths.task(task));
5586
5726
  return writeTrust(runtime, paths, {
5587
5727
  ...lock,
5588
5728
  contracts: { ...lock.contracts, [task]: ready.prepared.loaded.hash },
5589
- approved: task === IMPLICIT_ID || !text7.ok ? lock.approved : { ...lock.approved, [task]: text7.value }
5729
+ approved: task === IMPLICIT_ID || !text8.ok ? lock.approved : { ...lock.approved, [task]: text8.value }
5590
5730
  }).ok;
5591
5731
  };
5592
5732
  var apply = (input, paths, markers) => {
@@ -5638,8 +5778,8 @@ var promptRelease = (input) => {
5638
5778
  var PRS_CACHE_MS = 10 * 6e4;
5639
5779
  var PRS_TIMEOUT_MS = 3e3;
5640
5780
  var PRS_AUTH_TIMEOUT_MS = 2e3;
5641
- var parsePrList = (text7) => {
5642
- const parsed = parseJson(text7);
5781
+ var parsePrList = (text8) => {
5782
+ const parsed = parseJson(text8);
5643
5783
  if (!parsed.ok || !Array.isArray(parsed.value)) return void 0;
5644
5784
  return parsed.value.flatMap((item2) => {
5645
5785
  if (!isRecord(item2) || typeof item2["number"] !== "number") return [];
@@ -6436,7 +6576,16 @@ var en_default = {
6436
6576
  "reason.label.blocked": "blocked",
6437
6577
  "reason.label.requires": "requires",
6438
6578
  "reason.label.not": "does not count",
6439
- "reason.human.generic": "Blocked a tool call; the reason went to Claude."
6579
+ "reason.human.generic": "Blocked a tool call; the reason went to Claude.",
6580
+ "review.command.packet": "Print the review packet for the reviewer and verifier agents: diff, task contract, evidence ledger and REVIEW.md, never the conversation",
6581
+ "review.guard.read_only": "blocked: the {agent} agent is read-only and cannot use {tool}. Report the finding; the main session makes the change.",
6582
+ "review.error.no_base": "the base {detail} is not a commit here",
6583
+ "command.statusline.summary": "Print the status line: active task, gate state and context use",
6584
+ "statusline.gate.green": "gate green",
6585
+ "statusline.gate.red": "gate red",
6586
+ "statusline.gate.incomplete": "gate incomplete",
6587
+ "statusline.gate.did_not_run": "gate did not run",
6588
+ "statusline.context": "ctx {percent}%"
6440
6589
  };
6441
6590
 
6442
6591
  // src/i18n/es.json
@@ -7085,7 +7234,16 @@ var es_default = {
7085
7234
  "reason.label.blocked": "bloqueado",
7086
7235
  "reason.label.requires": "exige",
7087
7236
  "reason.label.not": "no cuenta",
7088
- "reason.human.generic": "Bloqueé una llamada de herramienta; el motivo fue a Claude."
7237
+ "reason.human.generic": "Bloqueé una llamada de herramienta; el motivo fue a Claude.",
7238
+ "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",
7239
+ "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.",
7240
+ "review.error.no_base": "la base {detail} no es un commit aquí",
7241
+ "command.statusline.summary": "Imprime la línea de estado: tarea activa, estado de la compuerta y uso del contexto",
7242
+ "statusline.gate.green": "compuerta verde",
7243
+ "statusline.gate.red": "compuerta roja",
7244
+ "statusline.gate.incomplete": "compuerta incompleta",
7245
+ "statusline.gate.did_not_run": "la compuerta no se ejecutó",
7246
+ "statusline.context": "ctx {percent}%"
7089
7247
  };
7090
7248
 
7091
7249
  // src/i18n/pt-BR.json
@@ -7734,7 +7892,16 @@ var pt_BR_default = {
7734
7892
  "reason.label.blocked": "bloqueado",
7735
7893
  "reason.label.requires": "exige",
7736
7894
  "reason.label.not": "não vale",
7737
- "reason.human.generic": "Bloqueei uma chamada de ferramenta; o motivo foi para o Claude."
7895
+ "reason.human.generic": "Bloqueei uma chamada de ferramenta; o motivo foi para o Claude.",
7896
+ "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",
7897
+ "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.",
7898
+ "review.error.no_base": "a base {detail} não é um commit aqui",
7899
+ "command.statusline.summary": "Imprime a linha de status: tarefa ativa, estado do portão e uso do contexto",
7900
+ "statusline.gate.green": "portão verde",
7901
+ "statusline.gate.red": "portão vermelho",
7902
+ "statusline.gate.incomplete": "portão incompleto",
7903
+ "statusline.gate.did_not_run": "portão não rodou",
7904
+ "statusline.context": "ctx {percent}%"
7738
7905
  };
7739
7906
 
7740
7907
  // src/i18n/review.json
@@ -8384,7 +8551,16 @@ var review_default = {
8384
8551
  "reason.label.blocked": "machine",
8385
8552
  "reason.label.requires": "machine",
8386
8553
  "reason.label.not": "machine",
8387
- "reason.human.generic": "machine"
8554
+ "reason.human.generic": "machine",
8555
+ "review.command.packet": "machine",
8556
+ "review.guard.read_only": "machine",
8557
+ "review.error.no_base": "machine",
8558
+ "command.statusline.summary": "machine",
8559
+ "statusline.gate.green": "machine",
8560
+ "statusline.gate.red": "machine",
8561
+ "statusline.gate.incomplete": "machine",
8562
+ "statusline.gate.did_not_run": "machine",
8563
+ "statusline.context": "machine"
8388
8564
  },
8389
8565
  es: {
8390
8566
  "command.init.summary": "machine",
@@ -9031,7 +9207,16 @@ var review_default = {
9031
9207
  "reason.label.blocked": "machine",
9032
9208
  "reason.label.requires": "machine",
9033
9209
  "reason.label.not": "machine",
9034
- "reason.human.generic": "machine"
9210
+ "reason.human.generic": "machine",
9211
+ "review.command.packet": "machine",
9212
+ "review.guard.read_only": "machine",
9213
+ "review.error.no_base": "machine",
9214
+ "command.statusline.summary": "machine",
9215
+ "statusline.gate.green": "machine",
9216
+ "statusline.gate.red": "machine",
9217
+ "statusline.gate.incomplete": "machine",
9218
+ "statusline.gate.did_not_run": "machine",
9219
+ "statusline.context": "machine"
9035
9220
  }
9036
9221
  };
9037
9222
 
@@ -9262,9 +9447,9 @@ var notRunLine = (t, runtime, paths) => {
9262
9447
  var markSession = (input, paths) => {
9263
9448
  const { runtime } = input;
9264
9449
  const sessionId = input.payload.sessionId;
9265
- const git = gitAt(runtime, paths.root);
9266
- if (sessionId === void 0 || headSha(git) === void 0) return;
9267
- const tree = treeHash(git);
9450
+ const git2 = gitAt(runtime, paths.root);
9451
+ if (sessionId === void 0 || headSha(git2) === void 0) return;
9452
+ const tree = treeHash(git2);
9268
9453
  if (tree === void 0) return;
9269
9454
  writeSession(runtime, paths, { sessionId, startTree: tree, at: nowMs(runtime) });
9270
9455
  startBaseline(runtime, paths, tree);
@@ -9321,10 +9506,10 @@ var pairs = (record3) => {
9321
9506
  return names.filter((name) => typeof name === "string").map((name) => `${task}\0${name}`);
9322
9507
  };
9323
9508
  var callersRate = (runtime, paths) => {
9324
- const text7 = runtime.readText(paths.log);
9509
+ const text8 = runtime.readText(paths.log);
9325
9510
  const flagged = /* @__PURE__ */ new Set();
9326
9511
  const released2 = /* @__PURE__ */ new Set();
9327
- for (const line2 of text7.ok ? text7.value.split("\n") : []) {
9512
+ for (const line2 of text8.ok ? text8.value.split("\n") : []) {
9328
9513
  const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
9329
9514
  if (parsed === void 0 || !parsed.ok || !isRecord(parsed.value)) continue;
9330
9515
  const event = parsed.value["event"];
@@ -9349,7 +9534,7 @@ import { posix as posix2 } from "node:path";
9349
9534
  var JS_SPECIFIER = /(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["']([^"']+)["']/g;
9350
9535
  var JS_EXTENSION = /\.(?:[cm]?[jt]sx?)$/;
9351
9536
  var stripModule = (path) => path.replace(JS_EXTENSION, "").replace(/\/index$/, "");
9352
- var jsSpecifiers = (text7) => [...text7.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
9537
+ var jsSpecifiers = (text8) => [...text8.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
9353
9538
  var jsImports = (callerPath, callerText, origin) => {
9354
9539
  const target2 = stripModule(origin);
9355
9540
  return jsSpecifiers(callerText).some((specifier) => {
@@ -9451,9 +9636,9 @@ var languageOf = (path) => {
9451
9636
  const extension = extname(path).toLowerCase();
9452
9637
  return LANGUAGES2.find((language) => language.extensions.includes(extension));
9453
9638
  };
9454
- var definitionsIn = (language, text7) => {
9639
+ var definitionsIn = (language, text8) => {
9455
9640
  const names = /* @__PURE__ */ new Set();
9456
- for (const line2 of text7.split(/\r?\n/)) {
9641
+ for (const line2 of text8.split(/\r?\n/)) {
9457
9642
  for (const pattern of language.definitions) {
9458
9643
  const name = pattern.exec(line2)?.[1];
9459
9644
  if (name !== void 0 && !KEYWORDS.has(name)) names.add(name);
@@ -9466,26 +9651,26 @@ var definesName = (language, line2, name) => language.definitions.some((pattern)
9466
9651
  // src/gate/callers/scan.ts
9467
9652
  var MAX_SYMBOLS = 200;
9468
9653
  var current = (runtime, root, path) => {
9469
- const text7 = runtime.readText(toAbsolutePath(join16(root, path)));
9470
- return text7.ok ? text7.value : "";
9654
+ const text8 = runtime.readText(toAbsolutePath(join16(root, path)));
9655
+ return text8.ok ? text8.value : "";
9471
9656
  };
9472
9657
  var keyOf = (language, name) => `${language.id}\0${name}`;
9473
- var redefinedNames = (runtime, git, changes) => {
9658
+ var redefinedNames = (runtime, git2, changes) => {
9474
9659
  const keys = /* @__PURE__ */ new Set();
9475
9660
  for (const change of changes) {
9476
9661
  const language = languageOf(change.path);
9477
9662
  if (language === void 0 || change.status === "D") continue;
9478
- for (const name of definitionsIn(language, current(runtime, git.cwd, change.path))) keys.add(keyOf(language, name));
9663
+ for (const name of definitionsIn(language, current(runtime, git2.cwd, change.path))) keys.add(keyOf(language, name));
9479
9664
  }
9480
9665
  return keys;
9481
9666
  };
9482
- var removedSymbols = (runtime, git, base, changes) => {
9483
- const redefined = redefinedNames(runtime, git, changes);
9667
+ var removedSymbols = (runtime, git2, base, changes) => {
9668
+ const redefined = redefinedNames(runtime, git2, changes);
9484
9669
  const found = /* @__PURE__ */ new Map();
9485
9670
  for (const change of changes) {
9486
9671
  const language = languageOf(change.oldPath ?? change.path);
9487
9672
  if (language === void 0 || change.status === "A") continue;
9488
- const before = definitionsIn(language, fileAt(git, base, change.oldPath ?? change.path) ?? "");
9673
+ const before = definitionsIn(language, fileAt(git2, base, change.oldPath ?? change.path) ?? "");
9489
9674
  const origins = [change.path, ...change.oldPath === void 0 ? [] : [change.oldPath]];
9490
9675
  for (const name of before) {
9491
9676
  const key = keyOf(language, name);
@@ -9501,21 +9686,21 @@ var parseGrep = (output) => output.split("\n").filter((row) => row !== "").flatM
9501
9686
  const number2 = Number(line2);
9502
9687
  return file === "" || !Number.isInteger(number2) ? [] : [{ file: toPosixPath(file), line: number2, text: rest.join("\0") }];
9503
9688
  });
9504
- var grep = (git, names) => {
9689
+ var grep = (git2, names) => {
9505
9690
  if (names.length === 0) return [];
9506
9691
  const patterns = names.flatMap((name) => ["-e", name]);
9507
9692
  return parseGrep(
9508
- git.out(["grep", "-n", "-z", "-w", "-I", "--untracked", "--no-color", ...patterns, "--", ".", EXCLUDE_FM]) ?? ""
9693
+ git2.out(["grep", "-n", "-z", "-w", "-I", "--untracked", "--no-color", ...patterns, "--", ".", EXCLUDE_FM]) ?? ""
9509
9694
  );
9510
9695
  };
9511
- var mentions = (text7, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text7);
9512
- var callSites = (runtime, git, symbols) => {
9513
- const hits = grep(git, [...new Set(symbols.map((symbol) => symbol.name))]);
9696
+ var mentions = (text8, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text8);
9697
+ var callSites = (runtime, git2, symbols) => {
9698
+ const hits = grep(git2, [...new Set(symbols.map((symbol) => symbol.name))]);
9514
9699
  const texts2 = /* @__PURE__ */ new Map();
9515
9700
  const textOf4 = (file) => {
9516
- const cached2 = texts2.get(file) ?? current(runtime, git.cwd, file);
9517
- texts2.set(file, cached2);
9518
- return cached2;
9701
+ const cached3 = texts2.get(file) ?? current(runtime, git2.cwd, file);
9702
+ texts2.set(file, cached3);
9703
+ return cached3;
9519
9704
  };
9520
9705
  return symbols.flatMap(
9521
9706
  (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 }))
@@ -9583,31 +9768,31 @@ var ZERO = [
9583
9768
  /^ℹ tests 0$/m,
9584
9769
  /No tests found/
9585
9770
  ];
9586
- var pyTotal = (text7) => {
9587
- const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(text7)?.[1];
9771
+ var pyTotal = (text8) => {
9772
+ const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(text8)?.[1];
9588
9773
  if (line2 === void 0) return void 0;
9589
9774
  return [...line2.matchAll(/(\d+) (passed|failed|error|errors|skipped|xfailed|xpassed)/g)].reduce(
9590
9775
  (sum, match) => sum + Number(match[1] ?? "0"),
9591
9776
  0
9592
9777
  );
9593
9778
  };
9594
- var counted = (text7) => {
9779
+ var counted = (text8) => {
9595
9780
  for (const counter of COUNTERS) {
9596
- const total = counter.total.exec(text7)?.[1];
9781
+ const total = counter.total.exec(text8)?.[1];
9597
9782
  if (total === void 0) continue;
9598
- const failed4 = counter.failed?.exec(text7)?.[1];
9783
+ const failed4 = counter.failed?.exec(text8)?.[1];
9599
9784
  return { total: Number(total), ...failed4 === void 0 ? {} : { failed: Number(failed4) } };
9600
9785
  }
9601
- const py = pyTotal(text7);
9786
+ const py = pyTotal(text8);
9602
9787
  return py === void 0 ? {} : { total: py };
9603
9788
  };
9604
9789
  var parseTestSummary = (output) => {
9605
- const text7 = output.replace(ANSI2, "");
9790
+ const text8 = output.replace(ANSI2, "");
9606
9791
  const failures = [
9607
- ...new Set(FAILURE_LINES.flatMap((pattern) => [...text7.matchAll(pattern)].map((match) => (match[1] ?? "").trim())))
9792
+ ...new Set(FAILURE_LINES.flatMap((pattern) => [...text8.matchAll(pattern)].map((match) => (match[1] ?? "").trim())))
9608
9793
  ].filter((name) => name !== "" && !/^failing tests:?$/i.test(name));
9609
- if (ZERO.some((pattern) => pattern.test(text7))) return { total: 0, failures };
9610
- return { ...counted(text7), failures };
9794
+ if (ZERO.some((pattern) => pattern.test(text8))) return { total: 0, failures };
9795
+ return { ...counted(text8), failures };
9611
9796
  };
9612
9797
 
9613
9798
  // src/gate/checks/command.ts
@@ -9618,8 +9803,8 @@ var REWRITERS = /^\s*(rtk)\b/;
9618
9803
  var TRIVIAL = /* @__PURE__ */ new Set(["echo", "true", ":", "printf", "exit", "sleep", "cat", "yes", "pwd", "ls", "date", "env"]);
9619
9804
  var TAIL = 160;
9620
9805
  var MAX_FAILURES2 = 5;
9621
- var tail = (text7) => {
9622
- const lines3 = text7.split(/\r?\n/).filter((line2) => line2.trim() !== "");
9806
+ var tail = (text8) => {
9807
+ const lines3 = text8.split(/\r?\n/).filter((line2) => line2.trim() !== "");
9623
9808
  const last = lines3[lines3.length - 1]?.trim() ?? "";
9624
9809
  return last.length > TAIL ? `${last.slice(0, TAIL)}…` : last;
9625
9810
  };
@@ -9833,8 +10018,8 @@ var findingNote = (value) => {
9833
10018
  ];
9834
10019
  };
9835
10020
  var readSummary = (ctx) => {
9836
- const text7 = ctx.runtime.readText(summaryPath(ctx.paths, ctx.treeHash));
9837
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
10021
+ const text8 = ctx.runtime.readText(summaryPath(ctx.paths, ctx.treeHash));
10022
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
9838
10023
  if (!parsed?.ok) return void 0;
9839
10024
  const { at, captures, findings, ignoredAllows } = parsed.value;
9840
10025
  if (typeof at !== "string" || !Array.isArray(captures) || !Array.isArray(findings)) return void 0;
@@ -9903,8 +10088,8 @@ var SKIP_FILES = /\.(md|mdx|txt|rst|lock|snap|svg|png|jpg|gif|json)$|(^|\/)(CHAN
9903
10088
  var PLACEHOLDER = /\b(TODO|FIXME|XXX)\b|not implemented|NotImplementedError|unimplemented!|todo!\(|lorem ipsum/i;
9904
10089
  var findPlaceholders = (lines3) => lines3.filter((line2) => !isTestFile(line2.path) && !SKIP_FILES.test(line2.path) && PLACEHOLDER.test(line2.text));
9905
10090
  var untrackedAdded = (ctx) => ctx.changes.filter((change) => change.status === "A" && !SKIP_FILES.test(change.path)).flatMap((change) => {
9906
- const text7 = ctx.runtime.readText(toAbsolutePath(join20(ctx.paths.root, change.path)));
9907
- return text7.ok ? text7.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
10091
+ const text8 = ctx.runtime.readText(toAbsolutePath(join20(ctx.paths.root, change.path)));
10092
+ return text8.ok ? text8.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
9908
10093
  });
9909
10094
  var placeholdersCheck = (_check, ctx) => {
9910
10095
  const tracked = addedLines(ctx.git, ctx.base);
@@ -9963,7 +10148,7 @@ var prOutcome = (check2, ctx) => {
9963
10148
 
9964
10149
  // src/prose/preset.ts
9965
10150
  var PRESET_LINE = /^preset:\s*([A-Za-z0-9_-]+)\s*$/m;
9966
- var presetFromConfig = (text7) => parsePreset(PRESET_LINE.exec(text7)?.[1]) ?? "neutral";
10151
+ var presetFromConfig = (text8) => parsePreset(PRESET_LINE.exec(text8)?.[1]) ?? "neutral";
9967
10152
  var projectPreset = (runtime, root) => {
9968
10153
  const config = runtime.readText(toAbsolutePath(CONFIG_FILE, root));
9969
10154
  return config.ok ? presetFromConfig(config.value) : "neutral";
@@ -10140,16 +10325,16 @@ var ANY_MARKER = /<!--\s*finitemonkey:prose-allow(?:-file)?\s*-->/g;
10140
10325
  var INLINE_CODE = /`[^`]*`/g;
10141
10326
  var FENCE2 = /^\s*(```|~~~)/;
10142
10327
  var outsideCode = (line2) => line2.replace(INLINE_CODE, "");
10143
- var unfencedLines = (text7) => {
10328
+ var unfencedLines = (text8) => {
10144
10329
  let fenced = false;
10145
- return text7.split("\n").filter((line2) => {
10330
+ return text8.split("\n").filter((line2) => {
10146
10331
  if (FENCE2.test(line2)) fenced = !fenced;
10147
10332
  return !fenced && !FENCE2.test(line2);
10148
10333
  });
10149
10334
  };
10150
- var allowsWholeFile = (text7) => unfencedLines(text7).some((line2) => FILE_MARKER.test(outsideCode(line2)));
10335
+ var allowsWholeFile = (text8) => unfencedLines(text8).some((line2) => FILE_MARKER.test(outsideCode(line2)));
10151
10336
  var allowsLine = (line2) => LINE_MARKER.test(outsideCode(line2));
10152
- var markerCount = (text7) => text7.match(ANY_MARKER)?.length ?? 0;
10337
+ var markerCount = (text8) => text8.match(ANY_MARKER)?.length ?? 0;
10153
10338
  var isMarkerOnlyLine = (line2) => allowsLine(line2) && outsideCode(line2).replace(LINE_MARKER, "").trim() === "";
10154
10339
 
10155
10340
  // src/prose/extract.ts
@@ -10169,7 +10354,7 @@ var MASKS = [
10169
10354
  [/<[^>]+>/g, ""],
10170
10355
  [/\*\*|__/g, ""]
10171
10356
  ];
10172
- var maskInline = (line2) => MASKS.reduce((text7, [pattern, replacement]) => text7.replace(pattern, replacement), line2);
10357
+ var maskInline = (line2) => MASKS.reduce((text8, [pattern, replacement]) => text8.replace(pattern, replacement), line2);
10173
10358
  var BlockReader = class {
10174
10359
  blocks = [];
10175
10360
  current;
@@ -10260,14 +10445,14 @@ var splitBlock = (block4) => {
10260
10445
  return joined.split(SENTENCE_END).flatMap((sentence) => {
10261
10446
  const at = joined.indexOf(sentence, offset);
10262
10447
  offset = at + sentence.length;
10263
- const text7 = sentence.trim();
10264
- return text7 === "" ? [] : [{ text: text7, line: lineAt(at) }];
10448
+ const text8 = sentence.trim();
10449
+ return text8 === "" ? [] : [{ text: text8, line: lineAt(at) }];
10265
10450
  });
10266
10451
  };
10267
10452
  var unitsOf = (blocks) => {
10268
- let section = 0;
10453
+ let section2 = 0;
10269
10454
  return blocks.flatMap((block4, paragraph) => {
10270
- if (block4.kind === "heading") section += 1;
10455
+ if (block4.kind === "heading") section2 += 1;
10271
10456
  const pieces = splitBlock(block4);
10272
10457
  const next2 = blocks[paragraph + 1];
10273
10458
  const closesSection = block4.kind === "sentence" && (next2 === void 0 || next2.kind === "heading");
@@ -10275,16 +10460,16 @@ var unitsOf = (blocks) => {
10275
10460
  text: piece.text,
10276
10461
  line: piece.line,
10277
10462
  paragraph,
10278
- section,
10463
+ section: section2,
10279
10464
  kind: block4.kind,
10280
10465
  lastInParagraph: index === pieces.length - 1,
10281
10466
  closesSection
10282
10467
  }));
10283
10468
  });
10284
10469
  };
10285
- var extractProse = (text7) => {
10286
- if (allowsWholeFile(text7)) return { units: [], allowed: true };
10287
- const lines3 = text7.split("\n").map(withoutCarriageReturn);
10470
+ var extractProse = (text8) => {
10471
+ if (allowsWholeFile(text8)) return { units: [], allowed: true };
10472
+ const lines3 = text8.split("\n").map(withoutCarriageReturn);
10288
10473
  return { units: unitsOf(readBlocks(lines3)), allowed: false };
10289
10474
  };
10290
10475
 
@@ -10409,7 +10594,7 @@ var CONNECTIVES = new Set(
10409
10594
  " "
10410
10595
  )
10411
10596
  );
10412
- var wordsOf = (text7) => text7.split(/\s+/).filter((word2) => WORD.test(word2));
10597
+ var wordsOf = (text8) => text8.split(/\s+/).filter((word2) => WORD.test(word2));
10413
10598
  var hit = (rule, units, index, weight) => {
10414
10599
  const unit = units[index];
10415
10600
  return { rule, unit: index, line: unit?.line ?? 0, weight, excerpt: unit?.text ?? "" };
@@ -10503,9 +10688,9 @@ var phraseHits = (units, catalog) => units.flatMap(
10503
10688
  })
10504
10689
  );
10505
10690
  var windowEnd = (units, from, size) => {
10506
- const section = units[from]?.section;
10691
+ const section2 = units[from]?.section;
10507
10692
  let to = from;
10508
- while (to + 1 < Math.min(units.length, from + size) && units[to + 1]?.section === section) to += 1;
10693
+ while (to + 1 < Math.min(units.length, from + size) && units[to + 1]?.section === section2) to += 1;
10509
10694
  return to;
10510
10695
  };
10511
10696
  var windowScore = (hits, window) => hits.filter((item2) => item2.unit >= window.from && item2.unit <= window.to).reduce((sum, item2) => sum + item2.weight, 0);
@@ -10537,8 +10722,8 @@ var mergeSpans = (hits, windows) => {
10537
10722
  return ranges.map(([from, to]) => spanOf2(hits, from, to)).filter((span) => span.hits.length > 0);
10538
10723
  };
10539
10724
  var byPosition = (left, right) => left.unit - right.unit || left.line - right.line;
10540
- var scoreProse = (text7, options = {}) => {
10541
- const { units, allowed: allowed2 } = extractProse(text7);
10725
+ var scoreProse = (text8, options = {}) => {
10726
+ const { units, allowed: allowed2 } = extractProse(text8);
10542
10727
  const size = options.window ?? DENSITY_WINDOW;
10543
10728
  const threshold2 = options.threshold ?? DENSITY_THRESHOLD;
10544
10729
  const catalog = options.context === "record" ? [...phraseRules(), ...recordRules()] : phraseRules();
@@ -10570,9 +10755,9 @@ var changedMarkdown = (runtime, root) => {
10570
10755
  var scoreFiles = (runtime, root, files, forced) => {
10571
10756
  const reports = [];
10572
10757
  for (const file of files) {
10573
- const text7 = runtime.readText(toAbsolutePath(file, root));
10574
- if (!text7.ok) return reject(`${file}: ${text7.error}`);
10575
- reports.push({ file: toPosixPath(file), report: scoreProse(text7.value, { context: forced ?? contextOf(file) }) });
10758
+ const text8 = runtime.readText(toAbsolutePath(file, root));
10759
+ if (!text8.ok) return reject(`${file}: ${text8.error}`);
10760
+ reports.push({ file: toPosixPath(file), report: scoreProse(text8.value, { context: forced ?? contextOf(file) }) });
10576
10761
  }
10577
10762
  return accept(reports);
10578
10763
  };
@@ -10671,8 +10856,8 @@ var parseAppCheck = (stdout) => {
10671
10856
  var PROBE_MARGIN_MS = 15e3;
10672
10857
  var MIN_REMAINING_MS2 = 5e3;
10673
10858
  var TAIL2 = 160;
10674
- var lastLine = (text7) => {
10675
- const rows = text7.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
10859
+ var lastLine = (text8) => {
10860
+ const rows = text8.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
10676
10861
  const line2 = (rows.findLast((row) => /error/i.test(row)) ?? rows.at(-1))?.trim() ?? "";
10677
10862
  return line2.length > TAIL2 ? `${line2.slice(0, TAIL2)}…` : line2;
10678
10863
  };
@@ -11015,12 +11200,12 @@ var diffScanner = () => {
11015
11200
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
11016
11201
  var SKIPPED = /(?:^|\/)(?:\.git|node_modules|\.finitemonkey)\//;
11017
11202
  var DIFF_FLAGS = ["-U0", "--no-color", "--no-ext-diff", "--no-renames"];
11018
- var binary = (text7) => text7.slice(0, 8e3).includes("\0");
11203
+ var binary = (text8) => text8.slice(0, 8e3).includes("\0");
11019
11204
  var listed2 = (runtime, root, target2) => {
11020
- const git = runtime.git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", target2], root);
11021
- if (git.ok)
11205
+ const git2 = runtime.git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", target2], root);
11206
+ if (git2.ok)
11022
11207
  return accept(
11023
- git.stdout.split("\0").filter((name) => name !== "").map(toPosixPath)
11208
+ git2.stdout.split("\0").filter((name) => name !== "").map(toPosixPath)
11024
11209
  );
11025
11210
  const base = toAbsolutePath(target2, root);
11026
11211
  const walked = runtime.walkFiles(base);
@@ -11028,9 +11213,9 @@ var listed2 = (runtime, root, target2) => {
11028
11213
  return accept(walked.value.map((name) => toPosixPath(relative6(root, join23(base, toPosixPath(name))))));
11029
11214
  };
11030
11215
  var scanFile = (runtime, root, file) => {
11031
- const text7 = runtime.readText(toAbsolutePath(file, root));
11032
- if (!text7.ok || text7.value.length > MAX_FILE_BYTES || binary(text7.value)) return [];
11033
- return scanText(text7.value, { path: file }).map((finding4) => locate(finding4, file, finding4.line));
11216
+ const text8 = runtime.readText(toAbsolutePath(file, root));
11217
+ if (!text8.ok || text8.value.length > MAX_FILE_BYTES || binary(text8.value)) return [];
11218
+ return scanText(text8.value, { path: file }).map((finding4) => locate(finding4, file, finding4.line));
11034
11219
  };
11035
11220
  var scanPath = (runtime, root, target2) => {
11036
11221
  const files = listed2(runtime, root, target2);
@@ -11044,15 +11229,15 @@ var diffFindings = (diff) => {
11044
11229
  };
11045
11230
  var scanStaged = (runtime, root, includeUnstaged = false) => {
11046
11231
  const args2 = includeUnstaged ? ["diff", "HEAD", ...DIFF_FLAGS] : ["diff", "--cached", ...DIFF_FLAGS];
11047
- const git = runtime.git(args2, root);
11048
- if (!git.ok) return reject(git.found ? "git diff failed" : "git not found");
11049
- return accept(diffFindings(git.stdout));
11232
+ const git2 = runtime.git(args2, root);
11233
+ if (!git2.ok) return reject(git2.found ? "git diff failed" : "git not found");
11234
+ return accept(diffFindings(git2.stdout));
11050
11235
  };
11051
11236
  var scanHistory = async (runtime, root) => {
11052
11237
  const scanner = diffScanner();
11053
11238
  const args2 = ["log", "-p", "--all", "--format=commit %H", ...DIFF_FLAGS];
11054
- const git = await runtime.gitLines(args2, root, scanner.push);
11055
- if (!git.ok) return reject(git.found ? "git log failed" : "git not found");
11239
+ const git2 = await runtime.gitLines(args2, root, scanner.push);
11240
+ if (!git2.ok) return reject(git2.found ? "git log failed" : "git not found");
11056
11241
  return accept(scanner.end().filter((finding4) => !SKIPPED.test(finding4.file)));
11057
11242
  };
11058
11243
 
@@ -11127,8 +11312,8 @@ var treeViolations = (ctx) => {
11127
11312
  changes: relevant,
11128
11313
  before: (path) => fileAt(ctx.git, ctx.base, path),
11129
11314
  after: (path) => {
11130
- const text7 = ctx.runtime.readText(toAbsolutePath(join24(ctx.paths.root, path)));
11131
- return text7.ok ? text7.value : void 0;
11315
+ const text8 = ctx.runtime.readText(toAbsolutePath(join24(ctx.paths.root, path)));
11316
+ return text8.ok ? text8.value : void 0;
11132
11317
  },
11133
11318
  acceptedConfigs: ctx.trust.configs,
11134
11319
  scopePaths: ctx.contract.scope.paths,
@@ -11292,10 +11477,10 @@ var bodyLine = (draft, line2) => {
11292
11477
  else if (line2.startsWith("+")) draft.current.added.push(line2.slice(1));
11293
11478
  };
11294
11479
  var DIFF_GIT = /^diff --git a\/(.+) b\/(.+)$/;
11295
- var parseUnifiedDiff = (text7) => {
11480
+ var parseUnifiedDiff = (text8) => {
11296
11481
  const out = [];
11297
11482
  let draft;
11298
- for (const raw of text7.split("\n")) {
11483
+ for (const raw of text8.split("\n")) {
11299
11484
  const line2 = withoutCarriageReturn(raw);
11300
11485
  const start = DIFF_GIT.exec(line2);
11301
11486
  if (start !== null) {
@@ -11396,10 +11581,10 @@ var mergeInProgress = (runtime, repo) => revParse(runtime, repo, "MERGE_HEAD") !
11396
11581
 
11397
11582
  // src/git/facts.ts
11398
11583
  var memo2 = (compute) => {
11399
- let cached2;
11584
+ let cached3;
11400
11585
  return () => {
11401
- cached2 ??= { value: compute() };
11402
- return cached2.value;
11586
+ cached3 ??= { value: compute() };
11587
+ return cached3.value;
11403
11588
  };
11404
11589
  };
11405
11590
  var memoBy = (compute) => {
@@ -11446,7 +11631,7 @@ var normalizeLine = (line2) => line2.replace(/\r$/, "").trim().replace(/\s*,$/,
11446
11631
  var WEAK = /^[\p{P}\p{S}\s]+$/u;
11447
11632
  var isWeak = (line2) => WEAK.test(line2);
11448
11633
  var meaningful = (lines3) => lines3.map(normalizeLine).filter((line2) => line2 !== "");
11449
- var contentLines = (text7) => text7 === void 0 ? [] : meaningful(text7.split("\n"));
11634
+ var contentLines = (text8) => text8 === void 0 ? [] : meaningful(text8.split("\n"));
11450
11635
  var countLines = (lines3) => {
11451
11636
  const counts = /* @__PURE__ */ new Map();
11452
11637
  for (const line2 of lines3) counts.set(line2, (counts.get(line2) ?? 0) + 1);
@@ -11495,16 +11680,16 @@ var itemOf = (value) => {
11495
11680
  approvedBy: optionalString(value, "approved_by")
11496
11681
  };
11497
11682
  };
11498
- var invalidFinding = (file, text7) => ({
11683
+ var invalidFinding = (file, text8) => ({
11499
11684
  kind: "invalid-dropped",
11500
11685
  file,
11501
11686
  side: void 0,
11502
11687
  line: void 0,
11503
- text: text7,
11688
+ text: text8,
11504
11689
  weak: false
11505
11690
  });
11506
- var parseDropped = (text7) => {
11507
- const parsed = parseJson(text7);
11691
+ var parseDropped = (text8) => {
11692
+ const parsed = parseJson(text8);
11508
11693
  if (!parsed.ok) return parsed;
11509
11694
  const list4 = isRecord(parsed.value) ? parsed.value["dropped"] : parsed.value;
11510
11695
  if (!Array.isArray(list4)) return reject("expected a list of dropped items");
@@ -11520,9 +11705,9 @@ var parseDropped = (text7) => {
11520
11705
  };
11521
11706
  var loadDropped = (runtime, root, file) => {
11522
11707
  const path = toAbsolutePath(file ?? join26(root, DROPPED_FILE), root);
11523
- const text7 = runtime.readText(path);
11524
- if (!text7.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text7.error}`);
11525
- return parseDropped(text7.value);
11708
+ const text8 = runtime.readText(path);
11709
+ if (!text8.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text8.error}`);
11710
+ return parseDropped(text8.value);
11526
11711
  };
11527
11712
  var isDropped = (finding4, items) => items.some(
11528
11713
  (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)
@@ -11645,13 +11830,13 @@ var removalOf = (ours, theirs, paired) => {
11645
11830
  var missingAdditions = (hunks, file, elsewhere) => {
11646
11831
  const present2 = new Set(file.result);
11647
11832
  return hunks.flatMap(({ side, hunk, added }) => {
11648
- const text7 = added.find((line2) => !isWeak(line2) && !present2.has(line2));
11649
- if (text7 === void 0 || containsSequence(file.result, added) || elsewhere(added)) return [];
11833
+ const text8 = added.find((line2) => !isWeak(line2) && !present2.has(line2));
11834
+ if (text8 === void 0 || containsSequence(file.result, added) || elsewhere(added)) return [];
11650
11835
  const offset = Math.max(
11651
11836
  0,
11652
- hunk.added.findIndex((line2) => normalizeLine(line2) === text7)
11837
+ hunk.added.findIndex((line2) => normalizeLine(line2) === text8)
11653
11838
  );
11654
- return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text: text7, weak: false }];
11839
+ return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text: text8, weak: false }];
11655
11840
  });
11656
11841
  };
11657
11842
  var expectations = (file, hunks, removal) => {
@@ -11724,8 +11909,8 @@ var symbolsOf = (line2) => {
11724
11909
  ...title === void 0 ? [] : [{ name: title, test: true }]
11725
11910
  ];
11726
11911
  };
11727
- var escapeRegex = (text7) => text7.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11728
- var containsSymbol = (text7, symbol) => symbol.test ? text7.includes(symbol.name) : new RegExp(`(?<![\\w$])${escapeRegex(symbol.name)}(?![\\w$])`).test(text7);
11912
+ var escapeRegex = (text8) => text8.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11913
+ var containsSymbol = (text8, symbol) => symbol.test ? text8.includes(symbol.name) : new RegExp(`(?<![\\w$])${escapeRegex(symbol.name)}(?![\\w$])`).test(text8);
11729
11914
  var missingSymbols = (file, side, hunks, present2) => {
11730
11915
  const seen = /* @__PURE__ */ new Set();
11731
11916
  return hunks.flatMap(
@@ -11744,12 +11929,12 @@ var created2 = (diff) => diff?.status === "added" || diff?.status === "copied";
11744
11929
  var resultPathOf = (context, pair) => [pair.ours?.newPath, pair.theirs?.newPath, pair.key].find(
11745
11930
  (path) => path !== void 0 && context.resultText(path) !== void 0
11746
11931
  );
11747
- var finding2 = (kind, file, side, text7) => ({
11932
+ var finding2 = (kind, file, side, text8) => ({
11748
11933
  kind,
11749
11934
  file,
11750
11935
  side,
11751
11936
  line: void 0,
11752
- text: text7,
11937
+ text: text8,
11753
11938
  weak: false
11754
11939
  });
11755
11940
  var binaryFindings = (context, pair, path) => {
@@ -11768,8 +11953,8 @@ var binaryFindings = (context, pair, path) => {
11768
11953
  return result === expected ? [] : [finding2("binary", file, side, file)];
11769
11954
  };
11770
11955
  var elsewhereOf = (context) => (lines3) => context.resultFiles().some((path) => containsSequence(contentLines(context.resultText(path)), lines3));
11771
- var symbolFindings = (context, pair, path, text7) => {
11772
- const present2 = (symbol) => text7 !== void 0 && containsSymbol(text7, symbol) || context.resultFiles().some((other) => containsSymbol(context.resultText(other) ?? "", symbol));
11956
+ var symbolFindings = (context, pair, path, text8) => {
11957
+ const present2 = (symbol) => text8 !== void 0 && containsSymbol(text8, symbol) || context.resultFiles().some((other) => containsSymbol(context.resultText(other) ?? "", symbol));
11773
11958
  return [
11774
11959
  ...missingSymbols(path, "ours", pair.ours?.hunks ?? [], present2),
11775
11960
  ...missingSymbols(path, "theirs", pair.theirs?.hunks ?? [], present2)
@@ -11785,19 +11970,19 @@ var verifyFile = (context, pair) => {
11785
11970
  const copied = [pair.ours, pair.theirs].find((diff) => diff?.status === "copied");
11786
11971
  const baseSource = copied?.oldPath ?? (created2(pair.ours) || created2(pair.theirs) ? void 0 : pair.key);
11787
11972
  const baseText = baseSource === void 0 ? void 0 : blobText(context.runtime, context.repo, context.inputs.base, baseSource);
11788
- const text7 = path === void 0 ? void 0 : context.resultText(path);
11973
+ const text8 = path === void 0 ? void 0 : context.resultText(path);
11789
11974
  const file = path ?? pair.key;
11790
11975
  const lines3 = checkFile(
11791
11976
  {
11792
11977
  path: file,
11793
11978
  base: contentLines(baseText),
11794
- result: contentLines(text7),
11979
+ result: contentLines(text8),
11795
11980
  ours: pair.ours?.hunks ?? [],
11796
11981
  theirs: pair.theirs?.hunks ?? []
11797
11982
  },
11798
11983
  elsewhereOf(context)
11799
11984
  );
11800
- return [...lines3, ...symbolFindings(context, pair, file, text7)];
11985
+ return [...lines3, ...symbolFindings(context, pair, file, text8)];
11801
11986
  };
11802
11987
 
11803
11988
  // src/git/merge/verify.ts
@@ -11917,8 +12102,8 @@ var hashesOf = (value) => isRecord(value) ? Object.fromEntries(
11917
12102
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
11918
12103
  ) : {};
11919
12104
  var readObject = (runtime, path) => {
11920
- const text7 = runtime.readText(path);
11921
- return text7.ok ? parseJsonObject(text7.value) : text7;
12105
+ const text8 = runtime.readText(path);
12106
+ return text8.ok ? parseJsonObject(text8.value) : text8;
11922
12107
  };
11923
12108
  var readBaseline2 = (runtime, repo, owner) => {
11924
12109
  const parsed = readObject(runtime, fileOf(repo, "baseline", owner));
@@ -11984,7 +12169,7 @@ var foreignFiles = (runtime, repo, owner, targets2) => {
11984
12169
  };
11985
12170
 
11986
12171
  // src/git/gate-status.ts
11987
- var lines = (text7) => text7.split("\n").filter((line2) => line2.trim() !== "");
12172
+ var lines = (text8) => text8.split("\n").filter((line2) => line2.trim() !== "");
11988
12173
  var unpushedSince = (runtime, repo, since) => {
11989
12174
  if (lines(runtime.git(["remote"], repo.root).stdout).length === 0) return [];
11990
12175
  const result = runtime.git(["rev-list", `${since}..HEAD`, "--not", "--remotes"], repo.root);
@@ -12388,8 +12573,8 @@ var subagentStart = (input) => {
12388
12573
  const active = readState(runtime, paths).active;
12389
12574
  if (active === void 0) return { kind: "allow" };
12390
12575
  const disk = (relative11) => {
12391
- const text7 = runtime.readText(toAbsolutePath(join29(paths.root, relative11)));
12392
- return text7.ok ? text7.value : void 0;
12576
+ const text8 = runtime.readText(toAbsolutePath(join29(paths.root, relative11)));
12577
+ return text8.ok ? text8.value : void 0;
12393
12578
  };
12394
12579
  const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
12395
12580
  if (!loaded.ok) return { kind: "allow" };
@@ -12399,7 +12584,7 @@ var subagentStart = (input) => {
12399
12584
 
12400
12585
  // src/gate/hooks/index.ts
12401
12586
  var PORTAO_HANDLERS = {
12402
- PreToolUse: [selfGuard, bypassGuard, testsGuard],
12587
+ PreToolUse: [reviewAgentGuard, selfGuard, bypassGuard, testsGuard],
12403
12588
  UserPromptSubmit: [pausePrompt, promptRelease],
12404
12589
  Stop: [unlessPaused(stopGate)],
12405
12590
  SubagentStop: [unlessPaused(subagentStopGate)],
@@ -12415,8 +12600,8 @@ import { join as join31 } from "node:path";
12415
12600
 
12416
12601
  // src/git/command-text.ts
12417
12602
  var MAX_SHOWN = 160;
12418
- var shown = (text7) => {
12419
- const flat = text7.replace(/\s+/g, " ").trim();
12603
+ var shown = (text8) => {
12604
+ const flat = text8.replace(/\s+/g, " ").trim();
12420
12605
  return flat.length > MAX_SHOWN ? `${flat.slice(0, MAX_SHOWN - 3)}...` : flat;
12421
12606
  };
12422
12607
  var commandText = (command) => shown([command.program, ...command.args].join(" "));
@@ -12766,9 +12951,9 @@ var SSH_URL = /ssh:\/\/(?:[^@\s/]+@)?([A-Za-z0-9.-]+)/g;
12766
12951
  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;
12767
12952
  var hostKey = (raw) => raw.trim().toLowerCase().replace(/^[^@]*@/, "").replace(/^\[([^\]]+)\].*$/, "$1").replace(/:\d+$/, "").replace(/\.$/, "");
12768
12953
  var shellLine = (line2) => line2.replace(/^\s*(?:-\s*)?(?:run:\s*\|?)?/, "");
12769
- var hostsInText = (text7) => {
12770
- const urls = [...text7.matchAll(SSH_URL)].map((match) => match[1] ?? "");
12771
- const lines3 = text7.split(/\r?\n/);
12954
+ var hostsInText = (text8) => {
12955
+ const urls = [...text8.matchAll(SSH_URL)].map((match) => match[1] ?? "");
12956
+ const lines3 = text8.split(/\r?\n/);
12772
12957
  const keyed = lines3.flatMap((line2) => {
12773
12958
  const match = HOST_KEY.exec(line2);
12774
12959
  return match?.[1] === void 0 ? [] : [match[1]];
@@ -12782,8 +12967,8 @@ var workflowFiles = (runtime, root) => {
12782
12967
  return listed4.value.filter((name) => /deploy/i.test(name) && /\.ya?ml$/i.test(name)).map((name) => `.github/workflows/${name}`);
12783
12968
  };
12784
12969
  var detectedHosts = (runtime, root, files) => files.flatMap((file) => {
12785
- const text7 = runtime.readText(toAbsolutePath(join30(root, file)));
12786
- return text7.ok ? hostsInText(text7.value) : [];
12970
+ const text8 = runtime.readText(toAbsolutePath(join30(root, file)));
12971
+ return text8.ok ? hostsInText(text8.value) : [];
12787
12972
  });
12788
12973
  var loadProdTargets = (runtime, root, config) => {
12789
12974
  const workflows = workflowFiles(runtime, root);
@@ -12957,8 +13142,8 @@ var prodGuard = (runtime, root, t) => {
12957
13142
  if (!PROD_PROGRAMS.has(command.program)) return void 0;
12958
13143
  const hit3 = prodHit(command, targets2(), isProd);
12959
13144
  if (hit3 === void 0) return void 0;
12960
- const text7 = commandText(command);
12961
- return hit3.effect === "read" ? { kind: "read", hit: hit3, command: text7 } : blockOf(t, hit3, text7, targets2().deployPath);
13145
+ const text8 = commandText(command);
13146
+ return hit3.effect === "read" ? { kind: "read", hit: hit3, command: text8 } : blockOf(t, hit3, text8, targets2().deployPath);
12962
13147
  }
12963
13148
  };
12964
13149
  };
@@ -12978,8 +13163,8 @@ var recordEvidence = (runtime, root, owner, reads) => {
12978
13163
  const path = toAbsolutePath(join31(root, EVIDENCE_FILE));
12979
13164
  runtime.mkdir(toAbsolutePath(join31(root, ".finitemonkey")));
12980
13165
  const before = runtime.readText(path);
12981
- const text7 = before.ok ? before.value : "";
12982
- runtime.writeAtomic(path, `${text7}${text7 === "" || text7.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
13166
+ const text8 = before.ok ? before.value : "";
13167
+ runtime.writeAtomic(path, `${text8}${text8 === "" || text8.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
12983
13168
  `);
12984
13169
  };
12985
13170
 
@@ -12994,15 +13179,15 @@ var editTexts = (input, file) => {
12994
13179
  if (!Array.isArray(edits)) return [];
12995
13180
  return edits.flatMap((edit) => {
12996
13181
  if (!isRecord(edit)) return [];
12997
- const text7 = optionalString(edit, "new_string");
12998
- return text7 === void 0 ? [] : [{ file, text: text7, whole: false }];
13182
+ const text8 = optionalString(edit, "new_string");
13183
+ return text8 === void 0 ? [] : [{ file, text: text8, whole: false }];
12999
13184
  });
13000
13185
  };
13001
13186
  var writtenTexts = (tool, input) => {
13002
13187
  const file = optionalString(input, "file_path") ?? optionalString(input, "notebook_path") ?? "";
13003
13188
  const single2 = (key, whole) => {
13004
- const text7 = optionalString(input, key);
13005
- return text7 === void 0 ? [] : [{ file, text: text7, whole }];
13189
+ const text8 = optionalString(input, key);
13190
+ return text8 === void 0 ? [] : [{ file, text: text8, whole }];
13006
13191
  };
13007
13192
  switch (tool) {
13008
13193
  case "Write":
@@ -13684,24 +13869,24 @@ var withChain = (facts, chain) => {
13684
13869
  const branch = chain?.branch;
13685
13870
  return branch === void 0 ? facts : { ...facts, currentBranch: () => branch };
13686
13871
  };
13687
- var decide = (context, repo, verdict, facts, text7) => {
13872
+ var decide = (context, repo, verdict, facts, text8) => {
13688
13873
  switch (verdict.kind) {
13689
13874
  case "pass":
13690
13875
  return { kind: "pass" };
13691
13876
  case "block":
13692
- return { kind: "block", rule: verdict.rule, subject: verdict.subject, command: text7 };
13877
+ return { kind: "block", rule: verdict.rule, subject: verdict.subject, command: text8 };
13693
13878
  case "snapshot":
13694
- return { kind: "snapshot", repo, op: verdict.op, command: text7 };
13879
+ return { kind: "snapshot", repo, op: verdict.op, command: text8 };
13695
13880
  case "discard": {
13696
13881
  const entries = facts.dirty();
13697
- if (entries === void 0) return { kind: "unknown", command: text7 };
13882
+ if (entries === void 0) return { kind: "unknown", command: text8 };
13698
13883
  const foreign = foreignFiles(
13699
13884
  context.runtime,
13700
13885
  repo,
13701
13886
  context.owner,
13702
13887
  targetsOf(entries, verdict.discard, facts.prefix)
13703
13888
  );
13704
- return foreign.length > 0 ? { kind: "foreign", files: foreign, command: text7 } : { kind: "snapshot", repo, op: verdict.op, command: text7 };
13889
+ return foreign.length > 0 ? { kind: "foreign", files: foreign, command: text8 } : { kind: "snapshot", repo, op: verdict.op, command: text8 };
13705
13890
  }
13706
13891
  }
13707
13892
  };
@@ -13818,9 +14003,9 @@ var parseJournalLine = (line2) => {
13818
14003
  };
13819
14004
  };
13820
14005
  var readJournal = (runtime, root) => {
13821
- const text7 = runtime.readText(journalPath(root));
13822
- if (!text7.ok) return [];
13823
- return text7.value.split("\n").flatMap((line2) => {
14006
+ const text8 = runtime.readText(journalPath(root));
14007
+ if (!text8.ok) return [];
14008
+ return text8.value.split("\n").flatMap((line2) => {
13824
14009
  const entry2 = line2.trim() === "" ? void 0 : parseJournalLine(line2);
13825
14010
  return entry2 === void 0 ? [] : [entry2];
13826
14011
  });
@@ -14012,9 +14197,9 @@ var review = (input, commands, owner) => {
14012
14197
  return { blocked: void 0, snapshots, reads };
14013
14198
  };
14014
14199
  var gitPreToolUse = (input) => {
14015
- const text7 = shellText(input.payload);
14016
- if (text7 === void 0) return ALLOW;
14017
- const commands = normalizeCommand(text7, dialectOf(toolName2(input.payload))).commands;
14200
+ const text8 = shellText(input.payload);
14201
+ if (text8 === void 0) return ALLOW;
14202
+ const commands = normalizeCommand(text8, dialectOf(toolName2(input.payload))).commands;
14018
14203
  if (commands.length === 0) return ALLOW;
14019
14204
  const owner = ownerOf2(input.payload);
14020
14205
  const result = review(input, commands, owner);
@@ -14076,8 +14261,8 @@ var GIT_HANDLERS = {
14076
14261
  var SHADOW_FILE = "shadow.json";
14077
14262
  var numberOf = (value) => typeof value === "number" && Number.isInteger(value) ? value : 0;
14078
14263
  var readShadow = (runtime, paths) => {
14079
- const text7 = runtime.readText(paths.cache(SHADOW_FILE));
14080
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
14264
+ const text8 = runtime.readText(paths.cache(SHADOW_FILE));
14265
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
14081
14266
  if (parsed?.ok !== true) return {};
14082
14267
  return Object.fromEntries(
14083
14268
  Object.entries(parsed.value).flatMap(
@@ -14161,11 +14346,11 @@ var ASSIGNMENT3 = /^[A-Za-z_][A-Za-z0-9_]*=/;
14161
14346
  var WRAPPERS2 = /* @__PURE__ */ new Set(["sudo", "command", "exec", "time", "nice", "nohup", "builtin", "env"]);
14162
14347
  var liftHeredocs = (command) => {
14163
14348
  const heredocs = [];
14164
- const text7 = command.replace(HEREDOC2, (_match, _quote, _tag, body2) => {
14349
+ const text8 = command.replace(HEREDOC2, (_match, _quote, _tag, body2) => {
14165
14350
  heredocs.push(body2);
14166
14351
  return `<<__FM_HEREDOC_${String(heredocs.length - 1)}__`;
14167
14352
  });
14168
- return { text: text7, heredocs };
14353
+ return { text: text8, heredocs };
14169
14354
  };
14170
14355
  var endWord = (scan) => {
14171
14356
  if (scan.word !== void 0) scan.words.push(scan.word);
@@ -14176,23 +14361,23 @@ var endSegment = (scan) => {
14176
14361
  if (scan.words.length > 0) scan.segments.push(scan.words);
14177
14362
  scan.words = [];
14178
14363
  };
14179
- var quotedEnd2 = (text7, from, quote) => {
14364
+ var quotedEnd2 = (text8, from, quote) => {
14180
14365
  let index = from;
14181
- while (index < text7.length && text7[index] !== quote) index += quote === '"' && text7[index] === "\\" ? 2 : 1;
14366
+ while (index < text8.length && text8[index] !== quote) index += quote === '"' && text8[index] === "\\" ? 2 : 1;
14182
14367
  return index;
14183
14368
  };
14184
14369
  var unescape = (value) => value.replace(/\\(["\\$`])/g, "$1");
14185
- var splitWords = (text7) => {
14370
+ var splitWords = (text8) => {
14186
14371
  const scan = { words: [], segments: [], word: void 0 };
14187
- for (let index = 0; index < text7.length; index += 1) {
14188
- const char = text7[index] ?? "";
14372
+ for (let index = 0; index < text8.length; index += 1) {
14373
+ const char = text8[index] ?? "";
14189
14374
  if (char === '"' || char === "'") {
14190
- const end = quotedEnd2(text7, index + 1, char);
14191
- const inner = text7.slice(index + 1, end);
14375
+ const end = quotedEnd2(text8, index + 1, char);
14376
+ const inner = text8.slice(index + 1, end);
14192
14377
  scan.word = (scan.word ?? "") + (char === '"' ? unescape(inner) : inner);
14193
14378
  index = end;
14194
- } else if (char === "\\" && index + 1 < text7.length) {
14195
- scan.word = (scan.word ?? "") + (text7[index + 1] === "\n" ? "" : text7[index + 1] ?? "");
14379
+ } else if (char === "\\" && index + 1 < text8.length) {
14380
+ scan.word = (scan.word ?? "") + (text8[index + 1] === "\n" ? "" : text8[index + 1] ?? "");
14196
14381
  index += 1;
14197
14382
  } else if (SEPARATOR.test(char)) {
14198
14383
  endSegment(scan);
@@ -14213,8 +14398,8 @@ var segmentOf = (words) => {
14213
14398
  return { program: program.split(/[\\/]/).pop() ?? program, args: words.slice(index + 1) };
14214
14399
  };
14215
14400
  var shellWords = (command) => {
14216
- const { text: text7, heredocs } = liftHeredocs(command);
14217
- const segments = splitWords(text7).flatMap((words) => {
14401
+ const { text: text8, heredocs } = liftHeredocs(command);
14402
+ const segments = splitWords(text8).flatMap((words) => {
14218
14403
  const segment = segmentOf(words);
14219
14404
  return segment === void 0 ? [] : [segment];
14220
14405
  });
@@ -14319,7 +14504,7 @@ var sourceOf = (value, isFile, segment, heredocs) => {
14319
14504
  if (inline3 !== void 0) return { kind: "inline", text: inline3 };
14320
14505
  if (!isFile) return { kind: "inline", text: value };
14321
14506
  if (value !== "-") return { kind: "file", path: value };
14322
- const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text7) => text7 !== void 0);
14507
+ const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text8) => text8 !== void 0);
14323
14508
  return { kind: "inline", text: piped ?? "" };
14324
14509
  };
14325
14510
  var collect3 = (target2, segment, args2, spec2, heredocs) => {
@@ -14416,15 +14601,15 @@ var textOf2 = (input, found) => {
14416
14601
  return accept(parts.join("\n\n"));
14417
14602
  };
14418
14603
  var decisionFor = (input, found, blocking2) => {
14419
- const text7 = textOf2(input, found);
14420
- if (!text7.ok) {
14604
+ const text8 = textOf2(input, found);
14605
+ if (!text8.ok) {
14421
14606
  return blocking2 ? {
14422
14607
  kind: "block",
14423
- reason: modelReason(input.t, "prose.block.unreadable", { ...text7.error }),
14608
+ reason: modelReason(input.t, "prose.block.unreadable", { ...text8.error }),
14424
14609
  human: input.t("prose.block.human")
14425
14610
  } : void 0;
14426
14611
  }
14427
- const report2 = scoreProse(text7.value, { context: "record" });
14612
+ const report2 = scoreProse(text8.value, { context: "record" });
14428
14613
  if (report2.allowed || report2.spans.length === 0) return void 0;
14429
14614
  const params2 = {
14430
14615
  target: input.t(TARGET_KEYS[found.target]),
@@ -14471,7 +14656,7 @@ var HOOK_ACTOR = "finitemonkey";
14471
14656
  var liveFindings = (input, texts2) => {
14472
14657
  const baseline = loadBaseline(input.runtime, input.cwd);
14473
14658
  const accepted2 = acceptedFingerprints(baseline.ok ? baseline.value : EMPTY_BASELINE, input.runtime.now());
14474
- return texts2.flatMap((text7) => scanText(text7, { rules: CREDENTIAL_RULES })).filter((finding4) => !accepted2.has(finding4.fingerprint));
14659
+ return texts2.flatMap((text8) => scanText(text8, { rules: CREDENTIAL_RULES })).filter((finding4) => !accepted2.has(finding4.fingerprint));
14475
14660
  };
14476
14661
  var record2 = (input, findings, draft, where) => {
14477
14662
  const ids2 = findings.map((finding4) => {
@@ -14732,12 +14917,12 @@ var secretsSessionStart = (input) => {
14732
14917
  const remotes = remotesWithCredentials(input.runtime, input.cwd);
14733
14918
  if (remotes.length === 0) return { kind: "allow" };
14734
14919
  const params2 = { remotes: remotes.join(", "), remote: remotes[0] ?? "origin" };
14735
- const text7 = input.t("secrets.session.remote_credential", params2);
14920
+ const text8 = input.t("secrets.session.remote_credential", params2);
14736
14921
  return {
14737
14922
  kind: "allow",
14738
14923
  output: {
14739
- systemMessage: `finitemonkey: ${text7}`,
14740
- hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text7 }
14924
+ systemMessage: `finitemonkey: ${text8}`,
14925
+ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text8 }
14741
14926
  }
14742
14927
  };
14743
14928
  };
@@ -14826,9 +15011,9 @@ var EMPTY_PAYLOAD = {
14826
15011
  stopHookActive: false,
14827
15012
  raw: {}
14828
15013
  };
14829
- var parseHookPayload = (event, text7, interactive) => {
14830
- if (interactive && text7.trim() === "") return accept(EMPTY_PAYLOAD);
14831
- const parsed = parseJsonObject(text7);
15014
+ var parseHookPayload = (event, text8, interactive) => {
15015
+ if (interactive && text8.trim() === "") return accept(EMPTY_PAYLOAD);
15016
+ const parsed = parseJsonObject(text8);
14832
15017
  if (!parsed.ok) return reject(`stdin: ${parsed.error}`);
14833
15018
  const payload = {
14834
15019
  hookEventName: optionalString(parsed.value, "hook_event_name"),
@@ -14888,8 +15073,8 @@ var HOOK_PROBE_ENV = "FINITEMONKEY_HOOK_PROBE";
14888
15073
  var messageOf2 = (error) => error instanceof Error ? error.message : String(error);
14889
15074
  var decide2 = (input, handlers) => composeDecisions(input, handlers[input.event] ?? [allow]);
14890
15075
  var evaluate = async (event, options) => {
14891
- const text7 = await readAll(options.stdin);
14892
- const parsed = parseHookPayload(event, text7, options.stdin.isTTY === true);
15076
+ const text8 = await readAll(options.stdin);
15077
+ const parsed = parseHookPayload(event, text8, options.stdin.isTTY === true);
14893
15078
  if (!parsed.ok) return { kind: "failed", payload: void 0, detail: parsed.error };
14894
15079
  const payload = parsed.value;
14895
15080
  if (options.env?.[HOOK_PROBE_ENV] === "1") return { kind: "decided", payload, decision: { kind: "allow" } };
@@ -14966,7 +15151,7 @@ var runHook = async (event, options) => {
14966
15151
  var PROBE_TIMEOUT_MS2 = 1e4;
14967
15152
  var SLOW_HOOK_MS = 1500;
14968
15153
  var DEFAULT_TIMEOUT_S = 60;
14969
- var expand = (text7, vars) => text7.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
15154
+ var expand = (text8, vars) => text8.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
14970
15155
  const name = braced ?? bare ?? "";
14971
15156
  return vars[name] ?? match;
14972
15157
  });
@@ -15015,7 +15200,7 @@ var run2 = (exec, invocation, cwd, env, input) => exec({
15015
15200
  env: { ...env, [HOOK_PROBE_ENV]: "1" },
15016
15201
  ...input === void 0 ? {} : { input }
15017
15202
  });
15018
- var firstLine = (text7) => maskText(text7.trim().split("\n")[0] ?? "").slice(0, 160);
15203
+ var firstLine = (text8) => maskText(text8.trim().split("\n")[0] ?? "").slice(0, 160);
15019
15204
  var classify2 = (result) => {
15020
15205
  if (!result.found) return { kind: "failed", problem: "missing", detail: "" };
15021
15206
  if (result.timedOut) return { kind: "failed", problem: "timeout", detail: "" };
@@ -15246,8 +15431,8 @@ var installRecord = (value, root) => {
15246
15431
  return records.find((record3) => record3["projectPath"] === root) ?? records[0];
15247
15432
  };
15248
15433
  var readJson = (env, path) => {
15249
- const text7 = env.runtime.readText(path);
15250
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
15434
+ const text8 = env.runtime.readText(path);
15435
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
15251
15436
  return parsed?.ok === true ? parsed.value : void 0;
15252
15437
  };
15253
15438
  var installedPlugin = (env) => {
@@ -15258,13 +15443,13 @@ var installedPlugin = (env) => {
15258
15443
  const root = toAbsolutePath(installPath);
15259
15444
  const manifest = readJson(env, toAbsolutePath(join33(root, ".claude-plugin", "plugin.json")));
15260
15445
  const hooksPath = toAbsolutePath(join33(root, "hooks", "hooks.json"));
15261
- const text7 = env.runtime.readText(hooksPath);
15262
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
15446
+ const text8 = env.runtime.readText(hooksPath);
15447
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
15263
15448
  return {
15264
15449
  root,
15265
15450
  version: (manifest === void 0 ? void 0 : optionalString(manifest, "version")) ?? optionalString(record3, "version"),
15266
15451
  hooksFile: parsed?.ok === true ? { source: "plugin", path: hooksPath, hooks: parsed.value["hooks"] } : void 0,
15267
- hooksText: text7.ok ? text7.value : void 0
15452
+ hooksText: text8.ok ? text8.value : void 0
15268
15453
  };
15269
15454
  };
15270
15455
  var PRECEDENCE2 = ["managed", "local", "project", "user"];
@@ -15294,8 +15479,8 @@ var schemaFinding = (file, problems, claude) => problems.length === 0 ? [] : [
15294
15479
  )
15295
15480
  ];
15296
15481
  var unreadableProjectFiles = (env) => settingsPaths(env).filter(({ scope: scope2 }) => scope2 === "project" || scope2 === "local").flatMap(({ path }) => {
15297
- const text7 = env.runtime.readText(path);
15298
- return text7.ok && text7.value.trim() !== "" && !parseJsonObject(text7.value).ok ? [path] : [];
15482
+ const text8 = env.runtime.readText(path);
15483
+ return text8.ok && text8.value.trim() !== "" && !parseJsonObject(text8.value).ok ? [path] : [];
15299
15484
  });
15300
15485
  var schemaFindings = (env, plugin, claude) => {
15301
15486
  const label2 = claude === void 0 ? "?" : claude.join(".");
@@ -15381,15 +15566,15 @@ var TOP_INSTRUCTIONS = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.l
15381
15566
  var RULES_DIR = ".claude/rules";
15382
15567
  var SHOWN = 3;
15383
15568
  var readProjectFile = (env, relative11) => {
15384
- const text7 = env.runtime.readText(toAbsolutePath(join35(env.root, ...relative11.split("/"))));
15385
- return text7.ok ? text7.value : void 0;
15569
+ const text8 = env.runtime.readText(toAbsolutePath(join35(env.root, ...relative11.split("/"))));
15570
+ return text8.ok ? text8.value : void 0;
15386
15571
  };
15387
15572
  var instructionFiles = (env) => {
15388
15573
  const listed4 = env.runtime.listDir(toAbsolutePath(join35(env.root, ...RULES_DIR.split("/"))));
15389
15574
  const rules = listed4.ok ? listed4.value.filter((name) => name.endsWith(".md")).map((name) => `${RULES_DIR}/${name}`) : [];
15390
15575
  return [...TOP_INSTRUCTIONS, ...rules].flatMap((path) => {
15391
- const text7 = readProjectFile(env, path);
15392
- return text7 === void 0 ? [] : [{ path, text: text7 }];
15576
+ const text8 = readProjectFile(env, path);
15577
+ return text8 === void 0 ? [] : [{ path, text: text8 }];
15393
15578
  });
15394
15579
  };
15395
15580
  var shortList = (items) => items.length <= SHOWN ? items.join(", ") : `${items.slice(0, SHOWN).join(", ")} (+${String(items.length - SHOWN)})`;
@@ -15437,10 +15622,10 @@ var hiddenInstructions = (env, deny2) => {
15437
15622
  ];
15438
15623
  };
15439
15624
  var localFile = (env) => readProjectFile(env, "CLAUDE.local.md") === void 0 ? [] : [finding("instructions.local", "warn", "doctor.instructions.local", {}, "doctor.instructions.fix_local")];
15440
- var lineCount = (text7) => text7.split("\n").length - (text7.endsWith("\n") ? 1 : 0);
15625
+ var lineCount = (text8) => text8.split("\n").length - (text8.endsWith("\n") ? 1 : 0);
15441
15626
  var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
15442
- const text7 = readProjectFile(env, file);
15443
- const lines3 = text7 === void 0 ? 0 : lineCount(text7);
15627
+ const text8 = readProjectFile(env, file);
15628
+ const lines3 = text8 === void 0 ? 0 : lineCount(text8);
15444
15629
  if (lines3 <= INSTRUCTION_LINE_CEILING) return [];
15445
15630
  return [
15446
15631
  finding(
@@ -15453,13 +15638,13 @@ var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
15453
15638
  ];
15454
15639
  });
15455
15640
  var MIN_RULE_WORDS = 6;
15456
- var ruleLines = (text7) => text7.split("\n").map((raw) => withoutCarriageReturn(raw).trim()).filter((line2) => !/^(#|\||```|@|>)/.test(line2)).map(
15641
+ var ruleLines = (text8) => text8.split("\n").map((raw) => withoutCarriageReturn(raw).trim()).filter((line2) => !/^(#|\||```|@|>)/.test(line2)).map(
15457
15642
  (line2) => line2.replace(/^([-*+]|\d+[.)])\s+/, "").replace(/\s+/g, " ").toLowerCase()
15458
15643
  ).filter((line2) => line2.split(" ").length >= MIN_RULE_WORDS);
15459
15644
  var duplicatedRules = (files) => {
15460
15645
  const seen = /* @__PURE__ */ new Map();
15461
- for (const { path, text: text7 } of files) {
15462
- for (const line2 of ruleLines(text7)) seen.set(line2, (seen.get(line2) ?? /* @__PURE__ */ new Set()).add(path));
15646
+ for (const { path, text: text8 } of files) {
15647
+ for (const line2 of ruleLines(text8)) seen.set(line2, (seen.get(line2) ?? /* @__PURE__ */ new Set()).add(path));
15463
15648
  }
15464
15649
  return [...seen].filter(([, paths]) => paths.size > 1).map(([line2]) => line2);
15465
15650
  };
@@ -15509,13 +15694,13 @@ var ceilingOf = (config) => {
15509
15694
  const parsed = typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
15510
15695
  return Number.isInteger(parsed) && parsed > 0 ? parsed : AGENTS_MAX_LINES;
15511
15696
  };
15512
- var lineCount2 = (text7) => text7.replace(/\r?\n$/, "").split(/\r?\n/).length;
15697
+ var lineCount2 = (text8) => text8.replace(/\r?\n$/, "").split(/\r?\n/).length;
15513
15698
  var checkAgentsFile = ({ root, runtime }) => {
15514
- const text7 = runtime.readText(toAbsolutePath(join37(root, AGENTS_FILE)));
15515
- if (!text7.ok) return [];
15699
+ const text8 = runtime.readText(toAbsolutePath(join37(root, AGENTS_FILE)));
15700
+ if (!text8.ok) return [];
15516
15701
  const config = runtime.readText(toAbsolutePath(join37(root, CONFIG_FILE)));
15517
15702
  const max = ceilingOf(config.ok ? config.value : void 0);
15518
- const lines3 = lineCount2(text7.value);
15703
+ const lines3 = lineCount2(text8.value);
15519
15704
  const params2 = { file: AGENTS_FILE, lines: lines3, max };
15520
15705
  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)];
15521
15706
  };
@@ -15696,9 +15881,9 @@ var manualLines = (ctx, report2) => report2.checks.some((item2) => item2.status
15696
15881
  var writeReport = (ctx, data, result) => {
15697
15882
  const env = doctorEnvFrom(ctx);
15698
15883
  const path = reportPath(env);
15699
- const text7 = reportMarkdown(env, ctx, toEnvelope("doctor", result));
15884
+ const text8 = reportMarkdown(env, ctx, toEnvelope("doctor", result));
15700
15885
  const dir = ctx.runtime.mkdir(toAbsolutePath(dirname7(path)));
15701
- const written = dir.ok ? ctx.runtime.writeAtomic(path, text7) : dir;
15886
+ const written = dir.ok ? ctx.runtime.writeAtomic(path, text8) : dir;
15702
15887
  if (!written.ok) return failureFrom(ctx.t, applyProblem({ path, detail: written.error }));
15703
15888
  const human2 = [...result.human ?? [], ctx.t("doctor.report.written", { path })];
15704
15889
  return { ...result, data: { ...data, reportPath: path }, human: human2 };
@@ -16024,15 +16209,15 @@ var commitMsgCommand = {
16024
16209
  usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: "<message-file>" })
16025
16210
  )
16026
16211
  );
16027
- const text7 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
16028
- if (!text7.ok)
16212
+ const text8 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
16213
+ if (!text8.ok)
16029
16214
  return Promise.resolve(
16030
16215
  failureFrom(
16031
16216
  ctx.t,
16032
16217
  usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: file })
16033
16218
  )
16034
16219
  );
16035
- const failures = commitMessageProblems(text7.value);
16220
+ const failures = commitMessageProblems(text8.value);
16036
16221
  const fix = "git commit --amend, using type(scope): subject and no AI attribution lines";
16037
16222
  return Promise.resolve(
16038
16223
  checksResult(ctx.t, COMMIT_MSG_SCHEMA, "githook_failed", [{ id: "commit-messages", failures, fix }])
@@ -16170,7 +16355,7 @@ var mergeVerifyCommand = {
16170
16355
  var REFLOG_LIMIT = 300;
16171
16356
  var LIST_LIMIT = 50;
16172
16357
  var SHA = /^[0-9a-f]{40,64}$/;
16173
- var linesOf = (text7) => text7.split("\n").filter((line2) => line2.trim() !== "");
16358
+ var linesOf = (text8) => text8.split("\n").filter((line2) => line2.trim() !== "");
16174
16359
  var reflogLines = (runtime, repo) => {
16175
16360
  const result = runtime.git(["reflog", "show", "--all", `-n${String(REFLOG_LIMIT)}`, "--format=%H %gD"], repo.root);
16176
16361
  if (!result.ok) return [];
@@ -16459,7 +16644,7 @@ var GLOBAL_FLAGS = [
16459
16644
  { flag: "--help, -h", key: "flag.help" }
16460
16645
  ];
16461
16646
  var exitCodeHelp = (ctx) => EXIT_CODE_NAMES.map((name) => ({ code: ExitCode[name], name, summary: ctx.t(exitCodeDocs[ExitCode[name]]) }));
16462
- var pad = (text7, width) => text7.padEnd(Math.max(width, text7.length + 1));
16647
+ var pad = (text8, width) => text8.padEnd(Math.max(width, text8.length + 1));
16463
16648
  var describe2 = (ctx, spec2) => ({
16464
16649
  name: commandName(spec2),
16465
16650
  usage: spec2.usage,
@@ -16601,10 +16786,10 @@ var parseBranches = (body2, inPlural) => {
16601
16786
  };
16602
16787
  var parseNodes = (source, inPlural) => {
16603
16788
  const nodes = [];
16604
- let text7 = "";
16789
+ let text8 = "";
16605
16790
  const flush2 = () => {
16606
- if (text7 !== "") nodes.push({ kind: "text", text: text7 });
16607
- text7 = "";
16791
+ if (text8 !== "") nodes.push({ kind: "text", text: text8 });
16792
+ text8 = "";
16608
16793
  };
16609
16794
  for (let index = 0; index < source.length; index += 1) {
16610
16795
  const char = source.charAt(index);
@@ -16614,7 +16799,7 @@ var parseNodes = (source, inPlural) => {
16614
16799
  continue;
16615
16800
  }
16616
16801
  if (char !== "{") {
16617
- text7 += char;
16802
+ text8 += char;
16618
16803
  continue;
16619
16804
  }
16620
16805
  const cursor = { source, index: index + 1 };
@@ -16622,18 +16807,18 @@ var parseNodes = (source, inPlural) => {
16622
16807
  const inner = end < 0 ? void 0 : source.slice(index + 1, end);
16623
16808
  const match = inner === void 0 ? null : ARG.exec(inner);
16624
16809
  if (inner === void 0 || match === null) {
16625
- text7 += char;
16810
+ text8 += char;
16626
16811
  continue;
16627
16812
  }
16628
16813
  const name = match[1] ?? "";
16629
16814
  const type = match[2] === "plural" || match[2] === "select" ? match[2] : void 0;
16630
16815
  if (type === void 0 && match[0].trim().length !== inner.trim().length) {
16631
- text7 += char;
16816
+ text8 += char;
16632
16817
  continue;
16633
16818
  }
16634
16819
  const branches = type === void 0 ? void 0 : parseBranches(inner.slice(match[0].length), type === "plural");
16635
16820
  if (type !== void 0 && branches === void 0) {
16636
- text7 += char;
16821
+ text8 += char;
16637
16822
  continue;
16638
16823
  }
16639
16824
  flush2();
@@ -16756,8 +16941,8 @@ var RELEASE_HINTS = [
16756
16941
  /\bFINITEMONKEY_[A-Z_]+/,
16757
16942
  /\b[A-Z][A-Z0-9_]{2,}=/
16758
16943
  ];
16759
- var releaseHints = (text7) => RELEASE_HINTS.flatMap((pattern) => {
16760
- const match = pattern.exec(text7);
16944
+ var releaseHints = (text8) => RELEASE_HINTS.flatMap((pattern) => {
16945
+ const match = pattern.exec(text8);
16761
16946
  return match === null ? [] : [match[0]];
16762
16947
  });
16763
16948
  var hintProblems = (lang, catalog) => Object.keys(catalog).filter((key) => MODEL_FACING.test(key)).sort().flatMap((key) => {
@@ -16796,9 +16981,9 @@ var unreadable = (dir, file, detail) => ({
16796
16981
  });
16797
16982
  var readCatalog = (ctx, dir, lang) => {
16798
16983
  const file = toAbsolutePath(join40(dir, `${lang}.json`));
16799
- const text7 = ctx.runtime.readText(file);
16800
- if (!text7.ok) return reject(unreadable(dir, file, text7.error));
16801
- const parsed = parseJsonObject(text7.value);
16984
+ const text8 = ctx.runtime.readText(file);
16985
+ if (!text8.ok) return reject(unreadable(dir, file, text8.error));
16986
+ const parsed = parseJsonObject(text8.value);
16802
16987
  return parsed.ok ? parsed : reject(unreadable(dir, file, parsed.error));
16803
16988
  };
16804
16989
  var loadCatalogs = (ctx, dir) => {
@@ -16818,8 +17003,8 @@ var describeProblem = (ctx, problem4) => {
16818
17003
  return ` ${problem4.lang} ${problem4.key} ${kind}`;
16819
17004
  };
16820
17005
  var loadReview = (ctx, dir) => {
16821
- const text7 = ctx.runtime.readText(toAbsolutePath(join40(dir, "review.json")));
16822
- const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
17006
+ const text8 = ctx.runtime.readText(toAbsolutePath(join40(dir, "review.json")));
17007
+ const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
16823
17008
  if (parsed?.ok !== true) return void 0;
16824
17009
  const statuses = (lang) => {
16825
17010
  const value = parsed.value[lang];
@@ -16964,8 +17149,8 @@ var addResults = (draft, blocks) => {
16964
17149
  let any = false;
16965
17150
  for (const block4 of blocks) {
16966
17151
  if (!isRecord(block4) || block4["type"] !== "tool_result" || typeof block4["tool_use_id"] !== "string") continue;
16967
- const text7 = textOf3(block4["content"]);
16968
- draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(text7), text: text7 });
17152
+ const text8 = textOf3(block4["content"]);
17153
+ draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(text8), text: text8 });
16969
17154
  any = true;
16970
17155
  }
16971
17156
  return any;
@@ -17016,9 +17201,9 @@ var lookBack = (runtime, root, env) => {
17016
17201
  };
17017
17202
  let turns = 0;
17018
17203
  for (const name of files) {
17019
- const text7 = runtime.readText(toAbsolutePath(join41(dir, name)));
17020
- if (!text7.ok) continue;
17021
- for (const turn of parseTranscript(text7.value)) {
17204
+ const text8 = runtime.readText(toAbsolutePath(join41(dir, name)));
17205
+ if (!text8.ok) continue;
17206
+ for (const turn of parseTranscript(text8.value)) {
17022
17207
  turns += 1;
17023
17208
  for (const hit3 of hitsIn(turn)) {
17024
17209
  counts[hit3.pattern] += 1;
@@ -17066,7 +17251,7 @@ var walk2 = (a, b) => {
17066
17251
  }
17067
17252
  return ops;
17068
17253
  };
17069
- var splitLines = (text7) => text7 === "" ? [] : text7.replace(/\r?\n$/, "").split(/\r?\n/);
17254
+ var splitLines = (text8) => text8 === "" ? [] : text8.replace(/\r?\n$/, "").split(/\r?\n/);
17070
17255
  var sharedPrefix = (a, b) => {
17071
17256
  let count4 = 0;
17072
17257
  while (count4 < a.length && count4 < b.length && a[count4] === b[count4]) count4 += 1;
@@ -17143,7 +17328,7 @@ var planData = (t, plan, mode, applied, backup) => ({
17143
17328
  diff: planDiff(plan),
17144
17329
  ...backup === void 0 ? {} : { backup }
17145
17330
  });
17146
- var pad2 = (text7, width) => text7.padEnd(width);
17331
+ var pad2 = (text8, width) => text8.padEnd(width);
17147
17332
  var planLines = (t, data, withDiff) => [
17148
17333
  t("install.plan.title", { root: data.root, preset: data.preset }),
17149
17334
  ...data.actions.map(({ path, action }) => ` ${pad2(action, 13)}${path}`),
@@ -17279,8 +17464,8 @@ var measurePage = (doc) => {
17279
17464
  const walker = doc.createTreeWalker(doc.body, SHOW_TEXT);
17280
17465
  const keys = /* @__PURE__ */ new Set();
17281
17466
  for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
17282
- const text7 = (node.textContent ?? "").trim();
17283
- if (KEY2.test(text7) && !NOT_KEY.test(text7) || PLACEHOLDER2.test(text7)) keys.add(text7.slice(0, 80));
17467
+ const text8 = (node.textContent ?? "").trim();
17468
+ if (KEY2.test(text8) && !NOT_KEY.test(text8) || PLACEHOLDER2.test(text8)) keys.add(text8.slice(0, 80));
17284
17469
  }
17285
17470
  const small = [...doc.querySelectorAll("a, button, input, select, textarea, [role=button], [role=link]")].filter((element) => {
17286
17471
  const box = element.getBoundingClientRect();
@@ -17313,8 +17498,8 @@ var slugOf = (route) => {
17313
17498
  var watch = (page) => {
17314
17499
  const errors = [];
17315
17500
  const failed4 = [];
17316
- const push = (list4, text7) => {
17317
- if (list4.length < MAX_EVENTS) list4.push(text7.slice(0, 200));
17501
+ const push = (list4, text8) => {
17502
+ if (list4.length < MAX_EVENTS) list4.push(text8.slice(0, 200));
17318
17503
  };
17319
17504
  page.on("console", (message) => {
17320
17505
  if (message.type() === "error" && !RESOURCE_ERROR.test(message.text())) push(errors, message.text());
@@ -17398,7 +17583,7 @@ var launchBrowser = async (playwright) => {
17398
17583
  // src/look/node-sheet.ts
17399
17584
  import { readFileSync as readFileSync2 } from "node:fs";
17400
17585
  import { join as join44 } from "node:path";
17401
- var escapeHtml = (text7) => text7.replace(/[&<>"]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[char] ?? char);
17586
+ var escapeHtml = (text8) => text8.replace(/[&<>"]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[char] ?? char);
17402
17587
  var card = (result) => {
17403
17588
  const image = (() => {
17404
17589
  try {
@@ -17759,6 +17944,96 @@ var proseCheckCommand = {
17759
17944
  }
17760
17945
  };
17761
17946
 
17947
+ // src/review/packet.ts
17948
+ import { join as join45 } from "node:path";
17949
+ var REVIEW_STATE_FILE = ".finitemonkey/review/state.json";
17950
+ var FULL_ROUNDS = 3;
17951
+ var REVIEW_FILE_MAX_LINES = 200;
17952
+ var LEDGER_MAX_LINES = 100;
17953
+ var statePath = (root) => toAbsolutePath(join45(root, REVIEW_STATE_FILE));
17954
+ var readReviewState = (runtime, root) => {
17955
+ const raw = runtime.readText(statePath(root));
17956
+ const parsed = raw.ok ? parseJsonObject(raw.value) : void 0;
17957
+ if (parsed === void 0 || !parsed.ok || !isRecord(parsed.value)) return void 0;
17958
+ const { base, rounds, lastHead } = parsed.value;
17959
+ return typeof base === "string" && typeof rounds === "number" && typeof lastHead === "string" ? { base, rounds, lastHead } : void 0;
17960
+ };
17961
+ var git = (runtime, root, args2) => {
17962
+ const result = runtime.git(args2, root);
17963
+ return result.ok ? result.stdout : void 0;
17964
+ };
17965
+ var lastLines = (text8, max) => text8.split("\n").slice(-max).join("\n");
17966
+ var section = (title, body2, empty) => `## ${title}
17967
+
17968
+ ${body2 === void 0 || body2.trim() === "" ? empty : body2.trimEnd()}
17969
+ `;
17970
+ var taskSections = (runtime, root) => {
17971
+ const paths = fmPaths(root);
17972
+ const active = readState(runtime, paths).active;
17973
+ if (active === void 0)
17974
+ return [section("Contract", void 0, "(no active task)"), section("Evidence ledger", void 0, "(none)")];
17975
+ const contract = runtime.readText(toAbsolutePath(join45(root, taskRelative(active))));
17976
+ const ledger = runtime.readText(paths.evidence(active));
17977
+ return [
17978
+ section("Contract", contract.ok ? contract.value : void 0, "(contract file missing)"),
17979
+ section("Evidence ledger", ledger.ok ? lastLines(ledger.value, LEDGER_MAX_LINES) : void 0, "(none)")
17980
+ ];
17981
+ };
17982
+ var reviewFile = (runtime, root, base) => {
17983
+ const text8 = git(runtime, root, ["show", `${base}:REVIEW.md`]);
17984
+ return text8 === void 0 ? void 0 : text8.split("\n").slice(0, REVIEW_FILE_MAX_LINES).join("\n");
17985
+ };
17986
+ var nextState = (previous, base, head) => {
17987
+ if (previous?.base !== base) return { base, rounds: 1, lastHead: head };
17988
+ return { base, rounds: previous.rounds + 1, lastHead: head };
17989
+ };
17990
+ var buildPacket = (runtime, root, base, head) => {
17991
+ const previous = readReviewState(runtime, root);
17992
+ const state = nextState(previous, base, head);
17993
+ const from = state.rounds > FULL_ROUNDS && previous !== void 0 ? previous.lastHead : base;
17994
+ const range = `${from}...${head}`;
17995
+ const diff = git(runtime, root, ["diff", "--no-color", "--no-ext-diff", range, "--", ".", ":(exclude).finitemonkey"]);
17996
+ const owners = reviewFile(runtime, root, base);
17997
+ const text8 = [
17998
+ `# Review packet (round ${String(state.rounds)}, ${range})`,
17999
+ "",
18000
+ ...state.rounds > FULL_ROUNDS ? ["Only the diff since the last review round is included.", ""] : [],
18001
+ section("Diff", diff, "(empty diff)"),
18002
+ ...taskSections(runtime, root),
18003
+ ...owners === void 0 ? [] : [section("REVIEW.md (base branch)", owners, "")]
18004
+ ].join("\n");
18005
+ runtime.mkdir(toAbsolutePath(join45(root, ".finitemonkey", "review")));
18006
+ runtime.writeAtomic(statePath(root), `${JSON.stringify(state, null, 2)}
18007
+ `);
18008
+ return { round: state.rounds, base, head, range, text: text8 };
18009
+ };
18010
+
18011
+ // src/commands/review.ts
18012
+ var REVIEW_PACKET_SCHEMA = "finitemonkey/review-packet@1";
18013
+ var reviewPacketCommand = {
18014
+ path: ["review", "packet"],
18015
+ summaryKey: "review.command.packet",
18016
+ usage: "finitemonkey review packet [--base <ref>] [--json]",
18017
+ status: "ready",
18018
+ options: { base: { type: "string" } },
18019
+ run: (ctx, args2) => {
18020
+ const repo = repoOf2(ctx);
18021
+ if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
18022
+ const base = stringOption(args2.values, "base") ?? repoFacts(ctx.runtime, repo).defaultBranch();
18023
+ const resolved = ctx.runtime.git(["rev-parse", "--verify", "--quiet", `${base}^{commit}`], repo.root);
18024
+ const head = ctx.runtime.git(["rev-parse", "HEAD"], repo.root).stdout.trim();
18025
+ if (!resolved.ok || head === "")
18026
+ return Promise.resolve(
18027
+ failureFrom(
18028
+ ctx.t,
18029
+ gitProblem("invalid_arguments", "review.error.no_base", base, "finitemonkey review packet --base <ref>")
18030
+ )
18031
+ );
18032
+ const packet = buildPacket(ctx.runtime, repo.root, base, head);
18033
+ return Promise.resolve(success({ schema: REVIEW_PACKET_SCHEMA, ...packet }, [packet.text]));
18034
+ }
18035
+ };
18036
+
17762
18037
  // src/commands/secrets-problems.ts
17763
18038
  var baselineInvalid = (detail) => ({
17764
18039
  code: "secrets_baseline_invalid",
@@ -17882,7 +18157,7 @@ var secretsAcceptCommand = {
17882
18157
  };
17883
18158
 
17884
18159
  // src/secrets/runbooks.ts
17885
- import { dirname as dirname9, join as join45 } from "node:path";
18160
+ import { dirname as dirname9, join as join46 } from "node:path";
17886
18161
  var PROVIDER_OF_RULE = {
17887
18162
  "aws-access-token": "aws",
17888
18163
  "aws-secret-access-key": "aws",
@@ -17904,7 +18179,7 @@ var RUNBOOKS_URL = "https://github.com/gabrielvieiraf/finitemonkey/blob/main/run
17904
18179
  var runbookProvider = (rule) => PROVIDER_OF_RULE[rule] ?? "generic";
17905
18180
  var runbookLink = (runtime, rule) => {
17906
18181
  const file = `${runbookProvider(rule)}.md`;
17907
- const local = toAbsolutePath(join45(dirname9(dirname9(runtime.selfPath)), "runbooks", file));
18182
+ const local = toAbsolutePath(join46(dirname9(dirname9(runtime.selfPath)), "runbooks", file));
17908
18183
  return runtime.stat(local)?.kind === "file" ? local : `${RUNBOOKS_URL}/${file}`;
17909
18184
  };
17910
18185
 
@@ -18265,8 +18540,206 @@ var secretsScanCommand = {
18265
18540
  }
18266
18541
  };
18267
18542
 
18543
+ // src/statusline/gate-state.ts
18544
+ var GATE_HOOKS = /* @__PURE__ */ new Set(["Stop", "SubagentStop"]);
18545
+ var TAIL_BYTES = 64 * 1024;
18546
+ var lastGateFailure = (runtime, paths) => {
18547
+ const log = runtime.readText(paths.log);
18548
+ if (!log.ok) return void 0;
18549
+ const lines3 = log.value.slice(-TAIL_BYTES).split("\n").reverse();
18550
+ for (const line2 of lines3) {
18551
+ if (!line2.includes(HOOK_FAILURE_EVENT)) continue;
18552
+ const parsed = parseJsonObject(line2);
18553
+ if (!parsed.ok || !isRecord(parsed.value)) continue;
18554
+ const { event, hook, at } = parsed.value;
18555
+ if (event === HOOK_FAILURE_EVENT && typeof hook === "string" && GATE_HOOKS.has(hook) && typeof at === "string")
18556
+ return at;
18557
+ }
18558
+ return void 0;
18559
+ };
18560
+ var gateLight = (runtime, paths) => {
18561
+ const status = readGateStatus(runtime, paths);
18562
+ const failure4 = lastGateFailure(runtime, paths);
18563
+ const greenSince = status?.status === "passed" ? Date.parse(status.at) : Number.NEGATIVE_INFINITY;
18564
+ if (failure4 !== void 0 && Date.parse(failure4) >= greenSince) return "did_not_run";
18565
+ if (status === void 0) return "none";
18566
+ switch (status.status) {
18567
+ case "passed":
18568
+ return "green";
18569
+ case "failed":
18570
+ case "not_done":
18571
+ return "red";
18572
+ case "incomplete":
18573
+ return "incomplete";
18574
+ case "not_run":
18575
+ return "did_not_run";
18576
+ }
18577
+ };
18578
+
18579
+ // src/statusline/input.ts
18580
+ var text7 = (value) => typeof value === "string" && value !== "" ? value : void 0;
18581
+ var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
18582
+ var usedTokens = (usage) => {
18583
+ if (!isRecord(usage)) return void 0;
18584
+ return (num(usage["input_tokens"]) ?? 0) + (num(usage["cache_creation_input_tokens"]) ?? 0) + (num(usage["cache_read_input_tokens"]) ?? 0);
18585
+ };
18586
+ var contextPercent = (record3) => {
18587
+ const window = record3["context_window"];
18588
+ if (!isRecord(window)) return void 0;
18589
+ const direct = window["used_percentage"];
18590
+ if (typeof direct === "number" && Number.isFinite(direct)) return Math.round(direct);
18591
+ const size = window["context_window_size"];
18592
+ const used = usedTokens(window["current_usage"]);
18593
+ return typeof size === "number" && size > 0 && used !== void 0 ? Math.round(used / size * 100) : void 0;
18594
+ };
18595
+ var parseStatusInput = (raw) => {
18596
+ const parsed = parseJsonObject(raw.trim() === "" ? "{}" : raw);
18597
+ const record3 = parsed.ok ? parsed.value : {};
18598
+ const workspace = isRecord(record3["workspace"]) ? record3["workspace"] : {};
18599
+ return {
18600
+ sessionId: text7(record3["session_id"]),
18601
+ dir: text7(workspace["project_dir"]) ?? text7(workspace["current_dir"]) ?? text7(record3["cwd"]),
18602
+ contextPercent: contextPercent(record3),
18603
+ raw
18604
+ };
18605
+ };
18606
+
18607
+ // src/statusline/render.ts
18608
+ var SEPARATOR2 = " | ";
18609
+ var segmentText = (t, segment) => [
18610
+ "fm",
18611
+ ...segment.task === void 0 ? [] : [segment.task],
18612
+ ...segment.gate === "none" ? [] : [t(`statusline.gate.${segment.gate}`)],
18613
+ ...segment.contextPercent === void 0 ? [] : [t("statusline.context", { percent: segment.contextPercent })]
18614
+ ].join(" · ");
18615
+ var ANSI3 = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`, "g");
18616
+ var graphemes = (text8) => Array.from(new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text8), (part2) => part2.segment);
18617
+ var fit = (line2, columns) => {
18618
+ if (columns === void 0) return line2;
18619
+ const visible = graphemes(line2.replace(ANSI3, ""));
18620
+ return visible.length <= columns ? line2 : `${visible.slice(0, Math.max(0, columns - 1)).join("")}…`;
18621
+ };
18622
+ var composeLine = (wrapped, segment, columns) => {
18623
+ const lines3 = wrapped === "" ? [] : wrapped.split(/\r?\n/);
18624
+ const last = lines3.pop();
18625
+ const joined = last === void 0 || last.trim() === "" ? segment : `${last}${SEPARATOR2}${segment}`;
18626
+ return [...lines3, joined].map((line2) => fit(line2, columns)).join("\n");
18627
+ };
18628
+
18629
+ // src/statusline/node-shell.ts
18630
+ import { spawn as spawn4 } from "node:child_process";
18631
+ var killTree = (child2) => {
18632
+ const pid = child2.pid;
18633
+ if (pid === void 0) return;
18634
+ try {
18635
+ if (process.platform === "win32") {
18636
+ spawn4("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true }).unref();
18637
+ } else process.kill(-pid, "SIGKILL");
18638
+ } catch {
18639
+ child2.kill("SIGKILL");
18640
+ }
18641
+ };
18642
+ var nodeShell = (command, input, cwd, timeoutMs) => new Promise((resolve2) => {
18643
+ const child2 = spawn4(command, {
18644
+ cwd,
18645
+ shell: true,
18646
+ detached: process.platform !== "win32",
18647
+ windowsHide: true,
18648
+ stdio: ["pipe", "pipe", "ignore"]
18649
+ });
18650
+ let out = "";
18651
+ const timer = setTimeout(() => {
18652
+ killTree(child2);
18653
+ resolve2({ text: void 0, timedOut: true });
18654
+ }, timeoutMs);
18655
+ child2.stdout.on("data", (chunk) => {
18656
+ out += chunk.toString("utf8");
18657
+ });
18658
+ child2.once("error", () => {
18659
+ clearTimeout(timer);
18660
+ resolve2({ text: void 0, timedOut: false });
18661
+ });
18662
+ child2.once("close", () => {
18663
+ clearTimeout(timer);
18664
+ resolve2({ text: out, timedOut: false });
18665
+ });
18666
+ child2.stdin.on("error", () => void 0);
18667
+ child2.stdin.end(input);
18668
+ });
18669
+
18670
+ // src/statusline/wrap.ts
18671
+ var WRAP_CACHE = "statusline.json";
18672
+ var KEEP3 = 20;
18673
+ var cached2 = (runtime, paths) => {
18674
+ const raw = readJsonFile(runtime, paths.cache(WRAP_CACHE))?.["sessions"];
18675
+ return isRecord(raw) ? Object.fromEntries(Object.entries(raw).filter((entry2) => typeof entry2[1] === "string")) : {};
18676
+ };
18677
+ var remember = (runtime, paths, session, text8) => {
18678
+ const sessions = Object.entries({ ...cached2(runtime, paths), [session]: text8 }).slice(-KEEP3);
18679
+ writeJsonFile(runtime, paths.cache(WRAP_CACHE), { sessions: Object.fromEntries(sessions) });
18680
+ };
18681
+ var runWrapped = async (runtime, paths, request, shell = nodeShell) => {
18682
+ const result = await shell(request.command, request.input, request.cwd, request.timeoutMs);
18683
+ if (result.text !== void 0) {
18684
+ const text8 = result.text.replace(/\s+$/, "");
18685
+ if (paths !== void 0) remember(runtime, paths, request.session, text8);
18686
+ return text8;
18687
+ }
18688
+ return paths === void 0 ? "" : cached2(runtime, paths)[request.session] ?? "";
18689
+ };
18690
+
18691
+ // src/statusline/run.ts
18692
+ var STDIN_MS = 300;
18693
+ var WRAP_MS = 700;
18694
+ var wrapOf = (ctx, root) => {
18695
+ const text8 = ctx.runtime.readText(toAbsolutePath(`${root}/${LOCK_FILE}`));
18696
+ const lock = text8.ok ? parseLock(text8.value) : void 0;
18697
+ if (!lock?.ok) return void 0;
18698
+ return lock.value.entries.find((entry2) => entry2.added?.statusLine !== void 0)?.added?.statusLine?.wrap ?? void 0;
18699
+ };
18700
+ var columnsOf = (ctx) => {
18701
+ const columns = Number.parseInt(ctx.env["COLUMNS"] ?? "", 10);
18702
+ return Number.isInteger(columns) && columns > 0 ? columns : void 0;
18703
+ };
18704
+ var lineFor = async (ctx, input) => {
18705
+ const start = input.dir === void 0 ? ctx.cwd : toAbsolutePath(input.dir, ctx.cwd);
18706
+ const root = findGitRoot(start, ctx.runtime);
18707
+ const paths = root === void 0 ? void 0 : fmPaths(root);
18708
+ const wrap = root === void 0 ? void 0 : wrapOf(ctx, root);
18709
+ const wrapped = wrap === void 0 || root === void 0 ? "" : await runWrapped(ctx.runtime, paths, {
18710
+ command: wrap,
18711
+ input: input.raw,
18712
+ cwd: root,
18713
+ timeoutMs: WRAP_MS,
18714
+ session: input.sessionId ?? "none"
18715
+ });
18716
+ const segment = segmentText(ctx.t, {
18717
+ task: paths === void 0 ? void 0 : readState(ctx.runtime, paths).active,
18718
+ gate: paths === void 0 ? "none" : gateLight(ctx.runtime, paths),
18719
+ contextPercent: input.contextPercent
18720
+ });
18721
+ return composeLine(wrapped, segment, columnsOf(ctx));
18722
+ };
18723
+ var renderStatusLine = async (ctx) => {
18724
+ try {
18725
+ const raw = await withDeadline(readAll(ctx.io.stdin), STDIN_MS, ctx.runtime.startTimer).catch(() => "");
18726
+ return await lineFor(ctx, parseStatusInput(raw));
18727
+ } catch {
18728
+ return "fm";
18729
+ }
18730
+ };
18731
+
18732
+ // src/commands/statusline.ts
18733
+ var statuslineCommand = {
18734
+ path: ["statusline"],
18735
+ summaryKey: "command.statusline.summary",
18736
+ usage: "finitemonkey statusline",
18737
+ status: "ready",
18738
+ run: async (ctx) => success({}, [await renderStatusLine(ctx)])
18739
+ };
18740
+
18268
18741
  // src/task/create.ts
18269
- import { join as join46 } from "node:path";
18742
+ import { join as join47 } from "node:path";
18270
18743
 
18271
18744
  // src/core/yaml/write.ts
18272
18745
  var PLAIN_SAFE = /^[A-Za-z_./][A-Za-z0-9_./ ()-]*$/;
@@ -18355,10 +18828,10 @@ var explicitChecks = (draft) => [
18355
18828
  var draftChecks = (runtime, paths, draft) => {
18356
18829
  const disk = {
18357
18830
  read: (relative11) => {
18358
- const text7 = runtime.readText(toAbsolutePath(join46(paths.root, relative11)));
18359
- return text7.ok ? text7.value : void 0;
18831
+ const text8 = runtime.readText(toAbsolutePath(join47(paths.root, relative11)));
18832
+ return text8.ok ? text8.value : void 0;
18360
18833
  },
18361
- exists: (relative11) => runtime.stat(toAbsolutePath(join46(paths.root, relative11))) !== void 0
18834
+ exists: (relative11) => runtime.stat(toAbsolutePath(join47(paths.root, relative11))) !== void 0
18362
18835
  };
18363
18836
  const detected = detectChecks(disk);
18364
18837
  const base = draft.runs.length > 0 || draft.tests !== void 0 ? explicitChecks(draft) : detected;
@@ -18373,7 +18846,7 @@ var draftChecks = (runtime, paths, draft) => {
18373
18846
  { id: "merge", kind: "merge" },
18374
18847
  { id: "callers", kind: "callers" },
18375
18848
  ...draft.paths.length > 0 || draft.budget !== void 0 ? [{ id: "diff", kind: "diff" }] : [],
18376
- ...runtime.stat(toAbsolutePath(join46(paths.root, LOOK_CONFIG))) === void 0 ? [] : [{ id: "look", kind: "look" }],
18849
+ ...runtime.stat(toAbsolutePath(join47(paths.root, LOOK_CONFIG))) === void 0 ? [] : [{ id: "look", kind: "look" }],
18377
18850
  ...draft.git.length > 0 ? [{ id: "git", kind: "git", require: draft.git }] : [],
18378
18851
  { id: "pr", kind: "pr", requireOpen: draft.git.includes("pr_open") },
18379
18852
  ...draft.manual.map((description, index) => ({
@@ -18399,19 +18872,19 @@ var createTask = (runtime, paths, draft) => {
18399
18872
  decisionsNeeded: [],
18400
18873
  implicit: false
18401
18874
  };
18402
- const text7 = stringifyYaml(contractYaml(shape));
18875
+ const text8 = stringifyYaml(contractYaml(shape));
18403
18876
  const valid = validateContract(contractYaml(shape));
18404
18877
  if (!valid.ok) return reject({ kind: "invalid", issues: valid.error });
18405
- const wrote = runtime.writeText(paths.task(draft.id), text7);
18878
+ const wrote = runtime.writeText(paths.task(draft.id), text8);
18406
18879
  if (!wrote.ok) return reject({ kind: "write", detail: wrote.error });
18407
18880
  const lock = readTrust(runtime, paths);
18408
18881
  writeTrust(runtime, paths, {
18409
18882
  ...lock,
18410
18883
  contracts: { ...lock.contracts, [draft.id]: contractHash(valid.value) },
18411
- approved: { ...lock.approved, [draft.id]: text7 }
18884
+ approved: { ...lock.approved, [draft.id]: text8 }
18412
18885
  });
18413
18886
  writeState(runtime, paths, { ...readState(runtime, paths), active: draft.id });
18414
- return accept({ contract: valid.value, text: text7 });
18887
+ return accept({ contract: valid.value, text: text8 });
18415
18888
  };
18416
18889
 
18417
18890
  // src/commands/task.ts
@@ -18562,7 +19035,7 @@ var taskDoneCommand = {
18562
19035
  };
18563
19036
 
18564
19037
  // src/install/githook-block.ts
18565
- var withoutHookBlock = (text7) => withoutManagedBlock(text7, { begin: BLOCK_BEGIN, end: BLOCK_END });
19038
+ var withoutHookBlock = (text8) => withoutManagedBlock(text8, { begin: BLOCK_BEGIN, end: BLOCK_END });
18566
19039
 
18567
19040
  // src/install/uninstall.ts
18568
19041
  var blockless = (kind, current2) => {
@@ -18571,8 +19044,8 @@ var blockless = (kind, current2) => {
18571
19044
  };
18572
19045
  var structural = (entry2, current2) => {
18573
19046
  if (entry2.kind === "gitignore" || entry2.kind === "markdown" || entry2.kind === "githook") {
18574
- const text7 = blockless(entry2.kind, current2);
18575
- return text7.trim() === "" && !entry2.existed ? void 0 : text7;
19047
+ const text8 = blockless(entry2.kind, current2);
19048
+ return text8.trim() === "" && !entry2.existed ? void 0 : text8;
18576
19049
  }
18577
19050
  const parsed = parseSettings(current2);
18578
19051
  if (!parsed.ok || entry2.added === void 0) return current2;
@@ -18775,34 +19248,36 @@ var REGISTRY = [
18775
19248
  pushCheckCommand,
18776
19249
  ciGateCommand,
18777
19250
  lookCommand,
19251
+ reviewPacketCommand,
19252
+ statuslineCommand,
18778
19253
  versionCommand,
18779
19254
  helpCommand
18780
19255
  ];
18781
19256
 
18782
19257
  // src/i18n/lang-settings.ts
18783
- import { join as join47 } from "node:path";
19258
+ import { join as join48 } from "node:path";
18784
19259
  var read3 = (runtime, path) => {
18785
- const text7 = runtime.readText(toAbsolutePath(path));
18786
- return text7.ok ? text7.value : void 0;
19260
+ const text8 = runtime.readText(toAbsolutePath(path));
19261
+ return text8.ok ? text8.value : void 0;
18787
19262
  };
18788
19263
  var configText = (tree, ...path) => {
18789
19264
  const value = configAt(tree, ...path);
18790
19265
  return typeof value === "string" && value !== "" ? value : void 0;
18791
19266
  };
18792
- var languageIn = (text7) => {
18793
- if (text7 === void 0) return void 0;
18794
- const parsed = parseJsonObject(text7);
19267
+ var languageIn = (text8) => {
19268
+ if (text8 === void 0) return void 0;
19269
+ const parsed = parseJsonObject(text8);
18795
19270
  const value = parsed.ok && isRecord(parsed.value) ? parsed.value["language"] : void 0;
18796
19271
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
18797
19272
  };
18798
- var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join47(runtime.homeDir(), ".claude");
19273
+ var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join48(runtime.homeDir(), ".claude");
18799
19274
  var langSettings = (runtime, cwd, env, platform) => {
18800
19275
  const root = findGitRoot(cwd, runtime) ?? cwd;
18801
- const config = parseConfigYaml(read3(runtime, join47(root, CONFIG_FILE)) ?? "");
19276
+ const config = parseConfigYaml(read3(runtime, join48(root, CONFIG_FILE)) ?? "");
18802
19277
  const claudeFiles = [
18803
- join47(root, ".claude", "settings.local.json"),
18804
- join47(root, ".claude", "settings.json"),
18805
- join47(claudeDir(runtime, env), "settings.json")
19278
+ join48(root, ".claude", "settings.local.json"),
19279
+ join48(root, ".claude", "settings.json"),
19280
+ join48(claudeDir(runtime, env), "settings.json")
18806
19281
  ];
18807
19282
  return {
18808
19283
  config: configText(config, "lang"),
@@ -18889,8 +19364,8 @@ var resultFromThrown = (thrown, t) => {
18889
19364
  };
18890
19365
  var block3 = (items) => items.length > 0 ? `${items.join("\n")}
18891
19366
  ` : "";
18892
- var write2 = (sink, text7) => {
18893
- if (text7 !== "") sink.write(maskText(text7));
19367
+ var write2 = (sink, text8) => {
19368
+ if (text8 !== "") sink.write(maskText(text8));
18894
19369
  };
18895
19370
  var render2 = (ctx, command, result) => {
18896
19371
  if (result.raw !== void 0) {
@@ -18959,9 +19434,9 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
18959
19434
  var UNKNOWN_VERSION = "0.0.0-unknown";
18960
19435
  var VERSION_SOURCES = ["../package.json", "../.claude-plugin/plugin.json"];
18961
19436
  var versionAt = (readText, moduleUrl, relative11) => {
18962
- const text7 = readText(toAbsolutePath(fileURLToPath4(new URL(relative11, moduleUrl))));
18963
- if (!text7.ok) return void 0;
18964
- const parsed = parseJson(text7.value);
19437
+ const text8 = readText(toAbsolutePath(fileURLToPath4(new URL(relative11, moduleUrl))));
19438
+ if (!text8.ok) return void 0;
19439
+ const parsed = parseJson(text8.value);
18965
19440
  if (!parsed.ok || !isRecord(parsed.value)) return void 0;
18966
19441
  const version = parsed.value["version"];
18967
19442
  return typeof version === "string" ? version : void 0;