finitemonkey 0.1.0-next.24 → 0.1.0-next.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/finitemonkey.mjs +1407 -916
  2. package/package.json +1 -1
@@ -28,15 +28,15 @@ import { dirname } from "node:path";
28
28
  var accept = (value) => ({ ok: true, value });
29
29
  var reject = (error) => ({ ok: false, error });
30
30
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
31
- var parseJson = (text8) => {
31
+ var parseJson = (text9) => {
32
32
  try {
33
- return accept(JSON.parse(text8));
33
+ return accept(JSON.parse(text9));
34
34
  } catch (error) {
35
35
  return reject(error instanceof Error ? error.message : String(error));
36
36
  }
37
37
  };
38
- var parseJsonObject = (text8) => {
39
- const parsed = parseJson(text8);
38
+ var parseJsonObject = (text9) => {
39
+ const parsed = parseJson(text9);
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, text8) => {
163
+ var nodeWriteText = (path, text9) => {
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, text8);
169
+ writeSync(fd, text9);
170
170
  fsyncSync(fd);
171
171
  } finally {
172
172
  closeSync(fd);
@@ -770,6 +770,497 @@ var appCheckCommand = {
770
770
  }
771
771
  };
772
772
 
773
+ // src/git/facts.ts
774
+ var memo = (compute) => {
775
+ let cached3;
776
+ return () => {
777
+ cached3 ??= { value: compute() };
778
+ return cached3.value;
779
+ };
780
+ };
781
+ var memoBy = (compute) => {
782
+ const cache = /* @__PURE__ */ new Map();
783
+ return (key) => {
784
+ const hit3 = cache.get(key);
785
+ if (hit3 !== void 0) return hit3.value;
786
+ const value = compute(key);
787
+ cache.set(key, { value });
788
+ return value;
789
+ };
790
+ };
791
+ var STATE_PREFIX = ".finitemonkey/";
792
+ var PORCELAIN_FIELDS = { ordinary: 8, renamed: 9, unmerged: 10 };
793
+ var entryOf = (kind, xy, path) => ({
794
+ path,
795
+ kind,
796
+ staged: kind !== "untracked" && !xy.startsWith("."),
797
+ unstaged: kind === "untracked" || xy.charAt(1) !== "."
798
+ });
799
+ var afterFields = (record3, count4) => record3.split(" ").slice(count4).join(" ");
800
+ var parsePorcelainV2 = (stdout) => {
801
+ const records = stdout.split("\0");
802
+ const out = [];
803
+ for (let index = 0; index < records.length; index += 1) {
804
+ const record3 = records[index] ?? "";
805
+ const xy = record3.split(" ")[1] ?? "..";
806
+ if (record3.startsWith("1 ")) out.push(entryOf("changed", xy, afterFields(record3, PORCELAIN_FIELDS.ordinary)));
807
+ else if (record3.startsWith("2 ")) {
808
+ out.push(entryOf("changed", xy, afterFields(record3, PORCELAIN_FIELDS.renamed)));
809
+ index += 1;
810
+ } else if (record3.startsWith("u ")) {
811
+ out.push(entryOf("unmerged", xy, afterFields(record3, PORCELAIN_FIELDS.unmerged)));
812
+ } else if (record3.startsWith("? ")) out.push(entryOf("untracked", "??", record3.slice(2)));
813
+ }
814
+ return out.filter((entry2) => entry2.path !== "" && !entry2.path.startsWith(STATE_PREFIX));
815
+ };
816
+
817
+ // src/git/project-config.ts
818
+ import { join as join2 } from "node:path";
819
+ var CONFIG_FILE = ".finitemonkey/config.yml";
820
+ var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
821
+ var stripComment = (line2) => {
822
+ let quote;
823
+ for (let index = 0; index < line2.length; index += 1) {
824
+ const char = line2.charAt(index);
825
+ if (quote === void 0 && (char === '"' || char === "'")) quote = char;
826
+ else if (char === quote) quote = void 0;
827
+ else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
828
+ return line2.slice(0, index);
829
+ }
830
+ }
831
+ return line2;
832
+ };
833
+ var flowList = (value) => {
834
+ const trimmed = value.trim();
835
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
836
+ return trimmed.slice(1, -1).split(",").map(unquote).filter((item2) => item2 !== "");
837
+ };
838
+ var keyValue = (body2) => {
839
+ const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
840
+ if (match === null) return void 0;
841
+ return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
842
+ };
843
+ var addItem = (frame, item2) => {
844
+ if (frame.key === void 0) return;
845
+ const parent = frame.tree[frame.key];
846
+ if (Array.isArray(parent)) parent.push(item2);
847
+ else frame.tree[frame.key] = [item2];
848
+ };
849
+ var placeLine = (stack, indent, body2) => {
850
+ while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
851
+ const top = stack[stack.length - 1];
852
+ if (top === void 0) return;
853
+ if (body2.startsWith("- ")) {
854
+ addItem(top, unquote(body2.slice(2)));
855
+ return;
856
+ }
857
+ const pair = keyValue(body2);
858
+ if (pair === void 0) return;
859
+ const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
860
+ if (pair.value === "") {
861
+ stack.push({ indent, tree: parentTree, key: pair.key });
862
+ return;
863
+ }
864
+ parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
865
+ };
866
+ var ensureTree = (tree, key) => {
867
+ const existing = tree[key];
868
+ if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
869
+ const fresh3 = {};
870
+ tree[key] = fresh3;
871
+ return fresh3;
872
+ };
873
+ var parseConfigYaml = (text9) => {
874
+ const root = {};
875
+ const stack = [{ indent: -1, tree: root, key: void 0 }];
876
+ for (const raw of text9.split(/\r?\n/)) {
877
+ const line2 = stripComment(raw).trimEnd();
878
+ if (line2.trim() === "" || line2.trim() === "---") continue;
879
+ const indent = line2.length - line2.trimStart().length;
880
+ placeLine(stack, indent, line2.trim());
881
+ }
882
+ return root;
883
+ };
884
+ var isTree = (node) => typeof node === "object" && !Array.isArray(node);
885
+ var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
886
+ var configList = (tree, ...path) => {
887
+ const node = configAt(tree, ...path);
888
+ if (typeof node === "string") return node === "" ? [] : [node];
889
+ return Array.isArray(node) ? node.filter((item2) => typeof item2 === "string") : [];
890
+ };
891
+ var configMap = (tree, ...path) => {
892
+ const node = configAt(tree, ...path);
893
+ if (!isTree(node)) return {};
894
+ return Object.fromEntries(
895
+ Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
896
+ );
897
+ };
898
+ var loadProjectConfig = (runtime, root) => {
899
+ const text9 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE)));
900
+ return text9.ok ? parseConfigYaml(text9.value) : {};
901
+ };
902
+
903
+ // src/git/repo.ts
904
+ var DEFAULT_PROTECTED = ["main", "master", "release/*", "prod", "production", "rc-*"];
905
+ var openRepo = (runtime, dir) => {
906
+ const result = runtime.git(["rev-parse", "--show-toplevel", "--absolute-git-dir", "--show-prefix"], dir);
907
+ if (!result.ok) return void 0;
908
+ const [root, gitDir, prefix] = result.stdout.split("\n");
909
+ if (root === void 0 || root === "" || gitDir === void 0 || gitDir === "") return void 0;
910
+ return {
911
+ root: toAbsolutePath(root),
912
+ gitDir: toAbsolutePath(gitDir),
913
+ prefix: (prefix ?? "").replace(/\/$/, "")
914
+ };
915
+ };
916
+ var globRegex = (pattern) => new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`);
917
+ var matchesBranch = (branch, patterns) => patterns.some((pattern) => globRegex(pattern).test(branch));
918
+ var line = (runtime, root, args2) => {
919
+ const result = runtime.git(args2, root);
920
+ const value = result.stdout.trim();
921
+ return result.ok && value !== "" ? value : void 0;
922
+ };
923
+ var refExists = (runtime, root, ref) => runtime.git(["show-ref", "--verify", "--quiet", ref], root).ok;
924
+ var defaultBranchOf = (runtime, root) => {
925
+ const remote = line(runtime, root, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
926
+ if (remote !== void 0) return remote.replace(/^[^/]+\//, "");
927
+ const configured = line(runtime, root, ["config", "--get", "init.defaultBranch"]);
928
+ if (configured !== void 0 && refExists(runtime, root, `refs/heads/${configured}`)) return configured;
929
+ return refExists(runtime, root, "refs/heads/master") && !refExists(runtime, root, "refs/heads/main") ? "master" : "main";
930
+ };
931
+ var upstreamOf = (runtime, root, branch) => {
932
+ const remote = line(runtime, root, ["config", "--get", `branch.${branch}.remote`]);
933
+ const merge = line(runtime, root, ["config", "--get", `branch.${branch}.merge`]);
934
+ if (remote === void 0 || merge === void 0 || remote === ".") return void 0;
935
+ const name = merge.replace(/^refs\/heads\//, "");
936
+ return { branch: name, remoteRef: `refs/remotes/${remote}/${name}` };
937
+ };
938
+ var integrationRefs = (runtime, root, defaultBranch) => [`refs/heads/${defaultBranch}`, `refs/remotes/origin/${defaultBranch}`].filter(
939
+ (ref) => refExists(runtime, root, ref)
940
+ );
941
+ var contained = (runtime, root, branch, targets2) => {
942
+ const tip = line(runtime, root, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]);
943
+ if (tip === void 0) return true;
944
+ return targets2.filter((target2) => target2 !== `refs/heads/${branch}`).some((target2) => runtime.git(["merge-base", "--is-ancestor", tip, target2], root).ok);
945
+ };
946
+ var shared = (runtime, root, upstream, base) => {
947
+ if (upstream === void 0 || !refExists(runtime, root, upstream.remoteRef)) return false;
948
+ const own = /<([^>]*)>/.exec(line(runtime, root, ["var", "GIT_AUTHOR_IDENT"]) ?? "")?.[1] ?? "";
949
+ const log = runtime.git(["log", "--format=%ae", upstream.remoteRef, ...base.map((ref) => `^${ref}`)], root);
950
+ return log.ok && log.stdout.split("\n").some((email) => email.trim() !== "" && email.trim() !== own);
951
+ };
952
+ var protectedPatterns = (config, defaultBranch) => [
953
+ defaultBranch,
954
+ ...DEFAULT_PROTECTED,
955
+ ...configList(config, "git", "protected")
956
+ ];
957
+ var repoFacts = (runtime, repo, config = loadProjectConfig(runtime, repo.root)) => {
958
+ const { root } = repo;
959
+ const defaultBranch = memo(() => defaultBranchOf(runtime, root));
960
+ const patterns = memo(() => protectedPatterns(config, defaultBranch()));
961
+ const upstream = memoBy((branch) => upstreamOf(runtime, root, branch));
962
+ const integration = memo(() => integrationRefs(runtime, root, defaultBranch()));
963
+ return {
964
+ prefix: repo.prefix,
965
+ currentBranch: memo(() => line(runtime, root, ["symbolic-ref", "--quiet", "--short", "HEAD"])),
966
+ defaultBranch,
967
+ isProtected: (branch) => matchesBranch(branch, patterns()),
968
+ dirty: memo(() => {
969
+ const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
970
+ return status.ok ? parsePorcelainV2(status.stdout) : void 0;
971
+ }),
972
+ isRef: memoBy((name) => runtime.git(["rev-parse", "--verify", "--quiet", `${name}^{commit}`], root).ok),
973
+ branchExists: memoBy((name) => refExists(runtime, root, `refs/heads/${name}`)),
974
+ branchContained: memoBy((branch) => {
975
+ const up = upstream(branch);
976
+ return contained(runtime, root, branch, [...up === void 0 ? [] : [up.remoteRef], ...integration()]);
977
+ }),
978
+ upstream,
979
+ sharedBranch: memoBy((branch) => shared(runtime, root, upstream(branch), integration()))
980
+ };
981
+ };
982
+
983
+ // src/hooks/stdin.ts
984
+ var readAll = (source) => new Promise((resolve2, reject2) => {
985
+ if (source.isTTY === true) {
986
+ resolve2("");
987
+ return;
988
+ }
989
+ const chunks = [];
990
+ source.setEncoding?.("utf8");
991
+ source.on("data", (chunk) => {
992
+ chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
993
+ });
994
+ source.on("end", () => {
995
+ resolve2(chunks.join(""));
996
+ });
997
+ source.on("error", reject2);
998
+ });
999
+ var DeadlineError = class extends Error {
1000
+ ms;
1001
+ constructor(ms) {
1002
+ super(`deadline of ${String(ms)} ms exceeded`);
1003
+ this.name = "DeadlineError";
1004
+ this.ms = ms;
1005
+ }
1006
+ };
1007
+ var withDeadline = (work, ms, startTimer) => new Promise((resolve2, reject2) => {
1008
+ const cancel = startTimer(ms, () => {
1009
+ reject2(new DeadlineError(ms));
1010
+ });
1011
+ work.then(
1012
+ (value) => {
1013
+ cancel();
1014
+ resolve2(value);
1015
+ },
1016
+ (error) => {
1017
+ cancel();
1018
+ reject2(error instanceof Error ? error : new Error(String(error)));
1019
+ }
1020
+ );
1021
+ });
1022
+
1023
+ // src/review/findings.ts
1024
+ var GROUPS = ["must", "should", "nice"];
1025
+ var VERDICTS = ["approve", "approve-with-changes", "request-changes"];
1026
+ var text2 = (record3, key) => {
1027
+ const value = record3[key];
1028
+ return typeof value === "string" ? value.trim() : "";
1029
+ };
1030
+ var reproOf = (value) => {
1031
+ if (!isRecord(value)) return void 0;
1032
+ const command = text2(value, "command");
1033
+ const expect = text2(value, "expect");
1034
+ return command === "" || expect === "" ? void 0 : { command, expect };
1035
+ };
1036
+ var findingOf = (value, index) => {
1037
+ if (!isRecord(value)) return `finding ${String(index + 1)} is not an object`;
1038
+ const group = GROUPS.find((item2) => item2 === value["group"]);
1039
+ if (group === void 0) return `finding ${String(index + 1)} needs group must, should or nice`;
1040
+ const fields = ["what", "problem", "fix", "ifNotFixed"];
1041
+ const missing2 = fields.filter((key) => text2(value, key) === "");
1042
+ if (missing2.length > 0) return `finding ${String(index + 1)} is missing ${missing2.join(", ")}`;
1043
+ const repro = reproOf(value["repro"]);
1044
+ const demoted = group === "must" && repro === void 0;
1045
+ return {
1046
+ id: index + 1,
1047
+ group: demoted ? "should" : group,
1048
+ what: text2(value, "what"),
1049
+ problem: text2(value, "problem"),
1050
+ fix: text2(value, "fix"),
1051
+ ifNotFixed: text2(value, "ifNotFixed"),
1052
+ ...repro === void 0 ? {} : { repro },
1053
+ activeRisk: value["activeRisk"] === true,
1054
+ demoted
1055
+ };
1056
+ };
1057
+ var parseCritique = (value) => {
1058
+ if (!isRecord(value)) return ["the critique must be a JSON object"];
1059
+ const verdict = VERDICTS.find((item2) => item2 === value["verdict"]);
1060
+ const list4 = value["findings"];
1061
+ const parsed = Array.isArray(list4) ? list4.map(findingOf) : ["findings must be a list"];
1062
+ const errors = [
1063
+ ...verdict === void 0 ? [`verdict must be one of ${VERDICTS.join(", ")}`] : [],
1064
+ ...parsed.filter((item2) => typeof item2 === "string")
1065
+ ];
1066
+ if (errors.length > 0 || verdict === void 0) return errors;
1067
+ return { verdict, findings: parsed.filter((item2) => typeof item2 !== "string") };
1068
+ };
1069
+
1070
+ // src/review/report.ts
1071
+ import { join as join4 } from "node:path";
1072
+
1073
+ // src/review/reproduce.ts
1074
+ import { join as join3 } from "node:path";
1075
+ var REVIEW_LEDGER = ".finitemonkey/review/ledger.jsonl";
1076
+ var REPRO_TIMEOUT_MS = 12e4;
1077
+ var fails = (run4) => run4 !== void 0 && run4.exitCode !== 0;
1078
+ var outcomeOf = (base, head) => {
1079
+ if (base === void 0 || head === void 0 || head.exitCode === null || base.exitCode === null) return "not-run";
1080
+ if (fails(head) && !fails(base)) return "head-only";
1081
+ return fails(head) && fails(base) ? "already-there" : "not-reproduced";
1082
+ };
1083
+ var worktreeDir = (root, side) => toAbsolutePath(join3(root, ".finitemonkey", "review", `worktree-${side}`));
1084
+ var runAt = (runtime, root, side, rev, findings) => {
1085
+ const dir = worktreeDir(root, side);
1086
+ runtime.git(["worktree", "remove", "--force", dir], root);
1087
+ const added = runtime.git(["worktree", "add", "--detach", "--force", dir, rev], root);
1088
+ if (!added.ok) return [];
1089
+ try {
1090
+ return findings.flatMap((finding4) => {
1091
+ if (finding4.repro === void 0) return [];
1092
+ const result = runtime.exec({
1093
+ command: finding4.repro.command,
1094
+ args: [],
1095
+ cwd: dir,
1096
+ timeoutMs: REPRO_TIMEOUT_MS,
1097
+ shell: true
1098
+ });
1099
+ const exitCode = result.found && !result.timedOut ? result.exitCode : null;
1100
+ return [{ finding: finding4.id, rev, side, command: finding4.repro.command, exitCode, at: runtime.now() }];
1101
+ });
1102
+ } finally {
1103
+ runtime.git(["worktree", "remove", "--force", dir], root);
1104
+ }
1105
+ };
1106
+ var reproduce = (runtime, root, sides, findings) => {
1107
+ const musts = findings.filter((finding4) => finding4.group === "must");
1108
+ const runs = [...runAt(runtime, root, "base", sides.base, musts), ...runAt(runtime, root, "head", sides.head, musts)];
1109
+ if (runs.length > 0) {
1110
+ runtime.mkdir(toAbsolutePath(join3(root, ".finitemonkey", "review")));
1111
+ runtime.appendText(
1112
+ toAbsolutePath(join3(root, REVIEW_LEDGER)),
1113
+ runs.map((run4) => `${JSON.stringify(run4)}
1114
+ `).join("")
1115
+ );
1116
+ }
1117
+ return findings.map((finding4) => {
1118
+ if (finding4.group !== "must") return { finding: finding4, outcome: "not-run" };
1119
+ const base = runs.find((run4) => run4.finding === finding4.id && run4.side === "base");
1120
+ const head = runs.find((run4) => run4.finding === finding4.id && run4.side === "head");
1121
+ return {
1122
+ finding: finding4,
1123
+ ...base === void 0 ? {} : { base },
1124
+ ...head === void 0 ? {} : { head },
1125
+ outcome: outcomeOf(base, head)
1126
+ };
1127
+ });
1128
+ };
1129
+ var settle = (item2) => {
1130
+ const { finding: finding4, outcome: outcome2 } = item2;
1131
+ if (finding4.group !== "must" || outcome2 === "head-only" || outcome2 === "not-run")
1132
+ return { finding: finding4, group: finding4.group, decisionNeeded: false };
1133
+ if (outcome2 === "already-there") return { finding: finding4, group: "already-there", decisionNeeded: finding4.activeRisk };
1134
+ return { finding: finding4, group: "not-reproduced", decisionNeeded: false };
1135
+ };
1136
+
1137
+ // src/review/report.ts
1138
+ var readLedger = (runtime, root) => {
1139
+ const raw = runtime.readText(toAbsolutePath(join4(root, REVIEW_LEDGER)));
1140
+ if (!raw.ok) return [];
1141
+ return raw.value.split("\n").flatMap((line2) => {
1142
+ const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
1143
+ if (parsed === void 0 || !parsed.ok || !isRecord(parsed.value)) return [];
1144
+ const { finding: finding4, side, command, exitCode } = parsed.value;
1145
+ if (typeof finding4 !== "number" || typeof side !== "string" || typeof command !== "string") return [];
1146
+ return [{ finding: finding4, side, command, exitCode: typeof exitCode === "number" ? exitCode : null }];
1147
+ });
1148
+ };
1149
+ var GROUP_ORDER = ["must", "should", "nice", "already-there", "not-reproduced"];
1150
+ var resultText = (row) => {
1151
+ if (row === void 0) return "-";
1152
+ return row.exitCode === null ? "did not run" : `exit ${String(row.exitCode)}`;
1153
+ };
1154
+ var findingLines = (t, settled) => GROUP_ORDER.flatMap((group) => {
1155
+ const items = settled.filter((item2) => item2.group === group);
1156
+ if (items.length === 0) return [];
1157
+ return [
1158
+ "",
1159
+ `## ${t(`critique.group.${group}`)}`,
1160
+ ...items.map(
1161
+ ({ finding: finding4 }) => t("critique.finding", {
1162
+ id: finding4.id,
1163
+ what: finding4.what,
1164
+ problem: finding4.problem,
1165
+ fix: finding4.fix,
1166
+ risk: finding4.ifNotFixed
1167
+ })
1168
+ )
1169
+ ];
1170
+ });
1171
+ var tableLines = (t, checked, ledger) => {
1172
+ const rows = checked.filter((item2) => item2.finding.group === "must");
1173
+ if (rows.length === 0) return [];
1174
+ const last = (id, side) => ledger.filter((row) => row.finding === id && row.side === side).at(-1);
1175
+ return [
1176
+ "",
1177
+ `| # | ${t("critique.table.command")} | base | head | ${t("critique.table.outcome")} |`,
1178
+ "|---|---|---|---|---|",
1179
+ ...rows.map(
1180
+ (item2) => `| ${String(item2.finding.id)} | \`${item2.finding.repro?.command ?? "-"}\` | ${resultText(last(item2.finding.id, "base"))} | ${resultText(last(item2.finding.id, "head"))} | ${t(`critique.outcome.${item2.outcome}`)} |`
1181
+ )
1182
+ ];
1183
+ };
1184
+ var critiqueLines = (t, critique, checked, ledger) => {
1185
+ const settled = checked.map(settle);
1186
+ const demoted = critique.findings.filter((finding4) => finding4.demoted);
1187
+ const decisions = settled.filter((item2) => item2.decisionNeeded);
1188
+ return [
1189
+ `# ${t("critique.title")}`,
1190
+ ...findingLines(t, settled),
1191
+ ...tableLines(t, checked, ledger),
1192
+ ...demoted.map((finding4) => t("critique.demoted", { id: finding4.id })),
1193
+ ...decisions.map((item2) => t("critique.decision", { id: item2.finding.id, what: item2.finding.what })),
1194
+ "",
1195
+ t("critique.verdict", { verdict: critique.verdict })
1196
+ ];
1197
+ };
1198
+
1199
+ // src/commands/git-problems.ts
1200
+ var notRepository = (dir) => ({
1201
+ code: "not_a_git_repo",
1202
+ messageKey: "error.not_a_git_repo",
1203
+ params: { dir },
1204
+ fix: "git init",
1205
+ exitCode: ExitCode.NotConfigured
1206
+ });
1207
+ var gitProblem = (code, messageKey, detail, fix, exitCode = ExitCode.CheckFailed) => ({ code, messageKey, params: { detail }, fix, exitCode });
1208
+ var repoOf = (ctx) => openRepo(ctx.runtime, ctx.cwd);
1209
+
1210
+ // src/commands/critique.ts
1211
+ var CRITIQUE_SCHEMA = "finitemonkey/critique@1";
1212
+ var USAGE = "finitemonkey critique verify [--base <ref>] [--file <findings.json>] (or the findings JSON on stdin)";
1213
+ var findingsText = async (ctx, root, file) => {
1214
+ if (file === void 0) return ctx.io.stdin.isTTY === true ? void 0 : readAll(ctx.io.stdin);
1215
+ const text9 = ctx.runtime.readText(toAbsolutePath(file, root));
1216
+ return text9.ok ? text9.value : void 0;
1217
+ };
1218
+ var critiqueVerifyCommand = {
1219
+ path: ["critique", "verify"],
1220
+ summaryKey: "critique.command.verify",
1221
+ usage: USAGE,
1222
+ status: "ready",
1223
+ options: { file: { type: "string" }, base: { type: "string" } },
1224
+ run: async (ctx, args2) => {
1225
+ const repo = repoOf(ctx);
1226
+ if (repo === void 0) return failureFrom(ctx.t, notRepository(ctx.cwd));
1227
+ const file = stringOption(args2.values, "file");
1228
+ const raw = await findingsText(ctx, repo.root, file);
1229
+ const parsed = raw === void 0 ? void 0 : parseJsonObject(raw);
1230
+ const critique = parsed?.ok === true ? parseCritique(parsed.value) : [`${file ?? "stdin"} is missing or not JSON`];
1231
+ if (Array.isArray(critique) || !("verdict" in critique))
1232
+ return failureFrom(
1233
+ ctx.t,
1234
+ usageProblem("invalid_arguments", "critique.error.invalid", USAGE, { detail: critique.join("; ") })
1235
+ );
1236
+ const base = stringOption(args2.values, "base") ?? repoFacts(ctx.runtime, repo).defaultBranch();
1237
+ const head = ctx.runtime.git(["rev-parse", "HEAD"], repo.root).stdout.trim();
1238
+ const checked = reproduce(ctx.runtime, repo.root, { base, head }, critique.findings);
1239
+ const human2 = critiqueLines(ctx.t, critique, checked, readLedger(ctx.runtime, repo.root));
1240
+ const confirmed = checked.map(settle).filter((item2) => item2.group === "must").length;
1241
+ const data = {
1242
+ schema: CRITIQUE_SCHEMA,
1243
+ verdict: critique.verdict,
1244
+ base,
1245
+ head,
1246
+ checked: checked.map((item2) => ({ ...item2, settled: settle(item2).group }))
1247
+ };
1248
+ if (confirmed === 0) return success(data, human2);
1249
+ return failureFrom(
1250
+ ctx.t,
1251
+ {
1252
+ code: "critique_must_fix",
1253
+ messageKey: "critique.error.must_fix",
1254
+ params: { count: confirmed },
1255
+ fix: "fix the must-fix findings, then run finitemonkey critique verify again",
1256
+ exitCode: ExitCode.CheckFailed,
1257
+ data
1258
+ },
1259
+ human2
1260
+ );
1261
+ }
1262
+ };
1263
+
773
1264
  // src/commands/doctor.ts
774
1265
  import { dirname as dirname7 } from "node:path";
775
1266
 
@@ -778,13 +1269,13 @@ import { dirname as dirname3 } from "node:path";
778
1269
 
779
1270
  // src/install/hash.ts
780
1271
  import { createHash } from "node:crypto";
781
- var hashText = (text8) => `sha256:${createHash("sha256").update(text8, "utf8").digest("hex")}`;
1272
+ var hashText = (text9) => `sha256:${createHash("sha256").update(text9, "utf8").digest("hex")}`;
782
1273
 
783
1274
  // src/install/layout.ts
784
1275
  var STATE_DIR = ".finitemonkey";
785
1276
  var SETTINGS_FILE = ".claude/settings.json";
786
1277
  var GITIGNORE_FILE = ".gitignore";
787
- var CONFIG_FILE = ".finitemonkey/config.yml";
1278
+ var CONFIG_FILE2 = ".finitemonkey/config.yml";
788
1279
  var LOCK_FILE = ".finitemonkey/lock.json";
789
1280
  var BACKUPS_DIR = ".finitemonkey/backups";
790
1281
  var LOCKS_DIR = ".finitemonkey/locks";
@@ -869,8 +1360,8 @@ var statusLineCommand = (value) => {
869
1360
  const command = value["command"];
870
1361
  return value["type"] === "command" && typeof command === "string" && command.trim() !== "" ? command : void 0;
871
1362
  };
872
- var statusLineIn = (text8) => {
873
- const parsed = text8 === void 0 ? void 0 : parseJsonObject(text8);
1363
+ var statusLineIn = (text9) => {
1364
+ const parsed = text9 === void 0 ? void 0 : parseJsonObject(text9);
874
1365
  return parsed?.ok === true ? parsed.value["statusLine"] : void 0;
875
1366
  };
876
1367
  var parseStatusLineAdded = (value) => {
@@ -943,122 +1434,36 @@ var fromRecord = (record3) => {
943
1434
  }
944
1435
  return accept({ schema, version, preset: known2, installedAt, updatedAt, createdDirs, entries });
945
1436
  };
946
- var parseLock = (text8) => {
947
- const parsed = parseJsonObject(text8);
1437
+ var parseLock = (text9) => {
1438
+ const parsed = parseJsonObject(text9);
948
1439
  return parsed.ok ? fromRecord(parsed.value) : reject(parsed.error);
949
1440
  };
950
1441
  var renderLock = (lock) => `${JSON.stringify(lock, null, 2)}
951
1442
  `;
952
1443
 
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
-
1039
1444
  // src/install/managed-block.ts
1040
- var lineBreak = (text8) => text8.includes("\r\n") ? "\r\n" : "\n";
1445
+ var lineBreak = (text9) => text9.includes("\r\n") ? "\r\n" : "\n";
1041
1446
  var blockRange = (lines3, markers) => {
1042
1447
  const start = lines3.findIndex((line2) => line2.trimEnd() === markers.begin);
1043
1448
  if (start < 0) return void 0;
1044
1449
  const end = lines3.findIndex((line2, index) => index > start && line2.trimEnd() === markers.end);
1045
1450
  return end < 0 ? void 0 : { start, end };
1046
1451
  };
1047
- var withManagedBlock = (text8, markers, body2) => {
1048
- const eol = text8 === void 0 ? "\n" : lineBreak(text8);
1452
+ var withManagedBlock = (text9, markers, body2) => {
1453
+ const eol = text9 === void 0 ? "\n" : lineBreak(text9);
1049
1454
  const block4 = [markers.begin, ...body2, markers.end];
1050
- if (text8 === void 0 || text8 === "") return `${block4.join(eol)}${eol}`;
1051
- const lines3 = text8.split(eol);
1455
+ if (text9 === void 0 || text9 === "") return `${block4.join(eol)}${eol}`;
1456
+ const lines3 = text9.split(eol);
1052
1457
  const range = blockRange(lines3, markers);
1053
1458
  if (range !== void 0) return [...lines3.slice(0, range.start), ...block4, ...lines3.slice(range.end + 1)].join(eol);
1054
- const separator = text8.endsWith(eol) ? "" : eol;
1055
- return `${text8}${separator}${block4.join(eol)}${eol}`;
1459
+ const separator = text9.endsWith(eol) ? "" : eol;
1460
+ return `${text9}${separator}${block4.join(eol)}${eol}`;
1056
1461
  };
1057
- var withoutManagedBlock = (text8, markers) => {
1058
- const eol = lineBreak(text8);
1059
- const lines3 = text8.split(eol);
1462
+ var withoutManagedBlock = (text9, markers) => {
1463
+ const eol = lineBreak(text9);
1464
+ const lines3 = text9.split(eol);
1060
1465
  const range = blockRange(lines3, markers);
1061
- if (range === void 0) return text8;
1466
+ if (range === void 0) return text9;
1062
1467
  return [...lines3.slice(0, range.start), ...lines3.slice(range.end + 1)].join(eol);
1063
1468
  };
1064
1469
 
@@ -1155,7 +1560,7 @@ var configTemplate = (preset, lang) => [
1155
1560
  `lang: ${lang}`,
1156
1561
  ""
1157
1562
  ].join("\n");
1158
- var hasMarker = (text8) => text8.includes(GENERATED_MARKER);
1563
+ var hasMarker = (text9) => text9.includes(GENERATED_MARKER);
1159
1564
 
1160
1565
  // src/install/skill-template.ts
1161
1566
  var cliCommand = (runner, args2) => {
@@ -1220,7 +1625,7 @@ var lefthookBody = (runner) => [
1220
1625
  ` run: ${cliCommand(runner, "push check {1}")}`,
1221
1626
  " use_stdin: true"
1222
1627
  ];
1223
- var definesHook = (text8) => GIT_HOOK_NAMES.some((hook) => new RegExp(`^${hook}:`, "m").test(withoutManagedBlock(text8, HOOK_MARKERS)));
1628
+ var definesHook = (text9) => GIT_HOOK_NAMES.some((hook) => new RegExp(`^${hook}:`, "m").test(withoutManagedBlock(text9, HOOK_MARKERS)));
1224
1629
  var lefthookItem = (project, runner, path) => {
1225
1630
  const before = project.read(path);
1226
1631
  const after = withManagedBlock(before, HOOK_MARKERS, lefthookBody(runner));
@@ -1245,8 +1650,8 @@ var preCommitBody = (runner, indent) => {
1245
1650
  ];
1246
1651
  };
1247
1652
  var TOP_KEY = /^[A-Za-z_][\w-]*:/;
1248
- var reposIsLast = (text8) => {
1249
- const keys = withoutManagedBlock(text8, HOOK_MARKERS).split(/\r?\n/).filter((line2) => TOP_KEY.test(line2));
1653
+ var reposIsLast = (text9) => {
1654
+ const keys = withoutManagedBlock(text9, HOOK_MARKERS).split(/\r?\n/).filter((line2) => TOP_KEY.test(line2));
1250
1655
  return keys.at(-1)?.startsWith("repos:") === true;
1251
1656
  };
1252
1657
  var preCommitItem = (project, runner) => {
@@ -1269,7 +1674,7 @@ var gitHookItems = (project, runner) => {
1269
1674
  };
1270
1675
 
1271
1676
  // src/policy/policy.ts
1272
- import { join as join3 } from "node:path";
1677
+ import { join as join5 } from "node:path";
1273
1678
  var POLICY_FILE = ".finitemonkey/policy.yml";
1274
1679
  var GUARD_MODES = ["off", "shadow", "active"];
1275
1680
  var LEVELS = ["light", "standard", "strict"];
@@ -1284,8 +1689,8 @@ var DEFAULT_POLICY = {
1284
1689
  guards: {}
1285
1690
  };
1286
1691
  var pick = (options, value, fallback) => options.find((option) => option === value) ?? fallback;
1287
- var parsePolicy = (text8) => {
1288
- const tree = parseConfigYaml(text8);
1692
+ var parsePolicy = (text9) => {
1693
+ const tree = parseConfigYaml(text9);
1289
1694
  const raw = configAt(tree, "promoteAfter");
1290
1695
  const promoteAfter = typeof raw === "string" ? Number.parseInt(raw, 10) : Number.NaN;
1291
1696
  const guards = Object.fromEntries(
@@ -1302,10 +1707,10 @@ var parsePolicy = (text8) => {
1302
1707
  guards
1303
1708
  };
1304
1709
  };
1305
- var policyPath = (root) => toAbsolutePath(join3(root, POLICY_FILE));
1710
+ var policyPath = (root) => toAbsolutePath(join5(root, POLICY_FILE));
1306
1711
  var loadPolicy = (runtime, root) => {
1307
- const text8 = runtime.readText(policyPath(root));
1308
- return text8.ok ? parsePolicy(text8.value) : DEFAULT_POLICY;
1712
+ const text9 = runtime.readText(policyPath(root));
1713
+ return text9.ok ? parsePolicy(text9.value) : DEFAULT_POLICY;
1309
1714
  };
1310
1715
 
1311
1716
  // src/review/agents.ts
@@ -1322,6 +1727,7 @@ var REVIEWER_BODY = [
1322
1727
  "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
1728
  "4. Every must-fix finding carries a reproduction command and the expected result. A finding without one goes under should fix.",
1324
1729
  "5. End with a verdict: approve, approve with changes, or request changes.",
1730
+ '6. Close with the same findings as one JSON block for `finitemonkey critique verify`: `{"verdict": "approve|approve-with-changes|request-changes", "findings": [{"group": "must|should|nice", "what", "problem", "fix", "ifNotFixed", "repro": {"command", "expect"}, "activeRisk": true|false}]}`.',
1325
1731
  "",
1326
1732
  "You cannot edit files or start other agents. Report; do not repair."
1327
1733
  ].join("\n");
@@ -1330,9 +1736,9 @@ var VERIFIER_BODY = [
1330
1736
  "",
1331
1737
  "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
1738
  "",
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.",
1739
+ "1. Pipe the reviewer's JSON block into `finitemonkey critique verify --base <base>` on stdin. It runs each reproduction command in throwaway worktrees on the base and head commits, never in the person's working tree, and records both exit codes in `.finitemonkey/review/ledger.jsonl`.",
1740
+ "2. Fails only on head: the finding stands. Fails on both: it was already there. Fails on neither: not reproduced.",
1741
+ "3. Report the table it prints, unchanged. Do not rerun a command by hand to get a different answer.",
1336
1742
  "",
1337
1743
  "You cannot edit files or start other agents. Report; do not repair."
1338
1744
  ].join("\n");
@@ -1370,16 +1776,16 @@ var agentMarkdown = (spec2, marker) => [
1370
1776
  // src/install/agents-block.ts
1371
1777
  var MARKDOWN_MARKERS = { begin: MD_BLOCK_BEGIN, end: MD_BLOCK_END };
1372
1778
  var IMPORT_LINE = /^@AGENTS\.md[ \t]*$/m;
1373
- var withAgentsBlock = (text8, runner, lang) => withManagedBlock(
1374
- text8,
1779
+ var withAgentsBlock = (text9, runner, lang) => withManagedBlock(
1780
+ text9,
1375
1781
  MARKDOWN_MARKERS,
1376
1782
  TEMPLATES[lang].agentsBlock({
1377
1783
  gate: cliCommand(runner, "gate --fast --json"),
1378
1784
  doctor: cliCommand(runner, "doctor --json")
1379
1785
  })
1380
1786
  );
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);
1787
+ var withAgentsImport = (text9) => text9 !== void 0 && IMPORT_LINE.test(withoutManagedBlock(text9, MARKDOWN_MARKERS)) ? text9 : withManagedBlock(text9, MARKDOWN_MARKERS, [AGENTS_IMPORT]);
1788
+ var withoutMarkdownBlock = (text9) => withoutManagedBlock(text9, MARKDOWN_MARKERS);
1383
1789
 
1384
1790
  // src/install/ask-floor.ts
1385
1791
  var DEFAULT_BRANCHES = ["main", "master"];
@@ -1455,7 +1861,7 @@ var ruleFor = (raw) => {
1455
1861
  };
1456
1862
  var claudeignoreRules = (texts2) => [
1457
1863
  ...new Set(
1458
- texts2.flatMap((text8) => (text8 ?? "").split("\n")).map(ruleFor).filter((rule) => rule !== void 0)
1864
+ texts2.flatMap((text9) => (text9 ?? "").split("\n")).map(ruleFor).filter((rule) => rule !== void 0)
1459
1865
  )
1460
1866
  ];
1461
1867
 
@@ -1484,16 +1890,16 @@ var DENY_FLOOR = {
1484
1890
  var DENY_RULES = [...DENY_FLOOR.secrets, ...DENY_FLOOR.git, ...DENY_FLOOR.proof];
1485
1891
 
1486
1892
  // src/install/detect.ts
1487
- import { join as join4 } from "node:path";
1893
+ import { join as join6 } from "node:path";
1488
1894
 
1489
1895
  // src/install/json-text.ts
1490
1896
  var DEFAULT_STYLE = { indent: " ", finalNewline: true };
1491
- var styleOf = (text8) => {
1492
- const indented = /\n([ \t]+)\S/.exec(text8);
1493
- return { indent: indented?.[1] ?? DEFAULT_STYLE.indent, finalNewline: text8.endsWith("\n") };
1897
+ var styleOf = (text9) => {
1898
+ const indented = /\n([ \t]+)\S/.exec(text9);
1899
+ return { indent: indented?.[1] ?? DEFAULT_STYLE.indent, finalNewline: text9.endsWith("\n") };
1494
1900
  };
1495
1901
  var renderJson = (value, style) => `${JSON.stringify(value, null, style.indent)}${style.finalNewline ? "\n" : ""}`;
1496
- var parseSettings = (text8) => text8 === void 0 || text8.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text8);
1902
+ var parseSettings = (text9) => text9 === void 0 || text9.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text9);
1497
1903
  var asList = (value) => Array.isArray(value) ? value : [];
1498
1904
  var asRecord = (value) => isRecord(value) ? value : {};
1499
1905
  var setKey = (record3, key, value) => {
@@ -1530,8 +1936,8 @@ var WORKFLOW_DIRS = [
1530
1936
  { dir: "openspec", name: "openspec" }
1531
1937
  ];
1532
1938
  var fromPackageJson = (project) => {
1533
- const text8 = project.read("package.json");
1534
- const parsed = text8 === void 0 ? void 0 : parseJsonObject(text8);
1939
+ const text9 = project.read("package.json");
1940
+ const parsed = text9 === void 0 ? void 0 : parseJsonObject(text9);
1535
1941
  const field = parsed?.ok === true ? parsed.value["packageManager"] : void 0;
1536
1942
  if (typeof field !== "string") return void 0;
1537
1943
  return PACKAGE_MANAGERS.find(({ name }) => field.startsWith(`${name}@`))?.name;
@@ -1547,9 +1953,9 @@ var allHandlers = (settings) => {
1547
1953
  const hooks = settings["hooks"];
1548
1954
  return isRecord(hooks) ? Object.keys(hooks).flatMap((event) => handlersOf(settings, event)) : [];
1549
1955
  };
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);
1956
+ var settingsInfo = (text9) => {
1957
+ if (text9 === void 0) return { exists: false, valid: true, userHooks: 0, ownHooks: 0 };
1958
+ const parsed = text9.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text9);
1553
1959
  if (!parsed.ok) return { exists: true, valid: false, userHooks: 0, ownHooks: 0 };
1554
1960
  const handlers = allHandlers(parsed.value);
1555
1961
  const own = handlers.filter(isOwnHandler).length;
@@ -1563,8 +1969,8 @@ var pluginAuthorities = (settings, source) => {
1563
1969
  return LOOP_PLUGINS.includes(name) ? [{ kind: "loop-plugin", source, name }] : [];
1564
1970
  });
1565
1971
  };
1566
- var scopeAuthorities = (text8, source) => {
1567
- const parsed = text8 === void 0 ? void 0 : parseJsonObject(text8);
1972
+ var scopeAuthorities = (text9, source) => {
1973
+ const parsed = text9 === void 0 ? void 0 : parseJsonObject(text9);
1568
1974
  if (parsed?.ok !== true) return [];
1569
1975
  const stops = ["Stop", "SubagentStop"].flatMap(
1570
1976
  (event) => handlersOf(parsed.value, event).filter((handler) => !isOwnHandler(handler)).map((handler) => {
@@ -1577,9 +1983,9 @@ var scopeAuthorities = (text8, source) => {
1577
1983
  var userSettingsText = (project, env) => {
1578
1984
  const home = env["CLAUDE_CONFIG_DIR"] ?? env["HOME"] ?? env["USERPROFILE"];
1579
1985
  if (home === void 0) return void 0;
1580
- const file = env["CLAUDE_CONFIG_DIR"] === void 0 ? join4(home, ".claude", "settings.json") : join4(home, "settings.json");
1581
- const text8 = project.runtime.readText(toAbsolutePath(file));
1582
- return text8.ok ? text8.value : void 0;
1986
+ const file = env["CLAUDE_CONFIG_DIR"] === void 0 ? join6(home, ".claude", "settings.json") : join6(home, "settings.json");
1987
+ const text9 = project.runtime.readText(toAbsolutePath(file));
1988
+ return text9.ok ? text9.value : void 0;
1583
1989
  };
1584
1990
  var detectAuthorities = (project, env) => [
1585
1991
  ...scopeAuthorities(project.read(SETTINGS_FILE), "project"),
@@ -1599,8 +2005,8 @@ var chooseRunner = (project, bin) => {
1599
2005
  // src/install/gitignore-block.ts
1600
2006
  var MARKERS = { begin: BLOCK_BEGIN, end: BLOCK_END };
1601
2007
  var body = () => [`${STATE_DIR}/*`, ...COMMITTED_STATE.map((path) => `!${STATE_DIR}/${path}`)];
1602
- var withBlock = (text8) => withManagedBlock(text8, MARKERS, body());
1603
- var withoutBlock = (text8) => withoutManagedBlock(text8, MARKERS);
2008
+ var withBlock = (text9) => withManagedBlock(text9, MARKERS, body());
2009
+ var withoutBlock = (text9) => withoutManagedBlock(text9, MARKERS);
1604
2010
 
1605
2011
  // src/core/semver.ts
1606
2012
  var SEMVER = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -1643,7 +2049,7 @@ var pendingMigrations = (migrations, from, to) => {
1643
2049
  };
1644
2050
  var migrateItem = (item2, previous, steps) => {
1645
2051
  if (steps.length === 0 || item2.before === void 0) return item2;
1646
- const migrated = steps.reduce((text8, step3) => step3(text8), item2.after);
2052
+ const migrated = steps.reduce((text9, step3) => step3(text9), item2.after);
1647
2053
  if (migrated === item2.after) return item2;
1648
2054
  const recorded = previous.entries.find((entry2) => entry2.path === item2.path)?.hash;
1649
2055
  if (recorded !== hashText(item2.before)) {
@@ -1809,9 +2215,9 @@ var LOCAL_SETTINGS_FILE = ".claude/settings.local.json";
1809
2215
  var existingStatusLine = (project, projectSettings, env) => statusLineCommand(projectSettings["statusLine"]) ?? statusLineCommand(statusLineIn(project.read(LOCAL_SETTINGS_FILE))) ?? statusLineCommand(statusLineIn(userSettingsText(project, env)));
1810
2216
 
1811
2217
  // src/install/vendor.ts
1812
- var isBundle = (text8) => text8.startsWith("#!/usr/bin/env node") && text8.includes("finitemonkey");
1813
- var isOwnPackage = (text8) => {
1814
- const parsed = parseJsonObject(text8);
2218
+ var isBundle = (text9) => text9.startsWith("#!/usr/bin/env node") && text9.includes("finitemonkey");
2219
+ var isOwnPackage = (text9) => {
2220
+ const parsed = parseJsonObject(text9);
1815
2221
  return parsed.ok && parsed.value["name"] === "finitemonkey";
1816
2222
  };
1817
2223
  var vendoredPackage = (version) => `${JSON.stringify({ name: "finitemonkey", version, private: true, type: "module" }, null, 2)}
@@ -1842,9 +2248,9 @@ var detect = (project, gitRoot2, input) => ({
1842
2248
  location: locationOf(project.root, input.env)
1843
2249
  });
1844
2250
  var readPreviousLock = (project) => {
1845
- const text8 = project.read(LOCK_FILE);
1846
- if (text8 === void 0) return accept(void 0);
1847
- const parsed = parseLock(text8);
2251
+ const text9 = project.read(LOCK_FILE);
2252
+ if (text9 === void 0) return accept(void 0);
2253
+ const parsed = parseLock(text9);
1848
2254
  return parsed.ok ? accept(parsed.value) : reject({ kind: "lock", detail: parsed.error });
1849
2255
  };
1850
2256
  var actionFor = (before, after, existing) => {
@@ -1893,7 +2299,7 @@ var settingsItem = (project, detection, input, previous) => {
1893
2299
  });
1894
2300
  };
1895
2301
  var markdownItems = (project, runner, lang) => [
1896
- { path: AGENTS_FILE, render: (text8) => withAgentsBlock(text8, runner, lang) },
2302
+ { path: AGENTS_FILE, render: (text9) => withAgentsBlock(text9, runner, lang) },
1897
2303
  { path: CLAUDE_FILE, render: withAgentsImport }
1898
2304
  ].map(({ path, render: render3 }) => {
1899
2305
  const before = project.read(path);
@@ -1920,14 +2326,14 @@ var generatedItem = (project, file, previous) => {
1920
2326
  };
1921
2327
  var PLAYWRIGHT_VERIFIER = ".claude/agents/verifier.md";
1922
2328
  var playwrightVerifier = (project) => {
1923
- const wanted = configAt(parseConfigYaml(project.read(CONFIG_FILE) ?? ""), "review", "playwright") === "true";
2329
+ const wanted = configAt(parseConfigYaml(project.read(CONFIG_FILE2) ?? ""), "review", "playwright") === "true";
1924
2330
  if (!wanted) return [];
1925
2331
  const template = agentMarkdown(withPlaywright(VERIFIER), GENERATED_MARKER);
1926
- return [{ path: PLAYWRIGHT_VERIFIER, template, isOurs: (text8) => text8.includes(GENERATED_MARKER) }];
2332
+ return [{ path: PLAYWRIGHT_VERIFIER, template, isOurs: (text9) => text9.includes(GENERATED_MARKER) }];
1927
2333
  };
1928
2334
  var generatedFiles = (project, input, runner, vendor) => {
1929
2335
  const own = [
1930
- { path: CONFIG_FILE, template: configTemplate(input.preset, input.lang), isOurs: hasMarker },
2336
+ { path: CONFIG_FILE2, template: configTemplate(input.preset, input.lang), isOurs: hasMarker },
1931
2337
  { path: VERIFY_SKILL, template: verifySkillTemplate(runner, input.lang), isOurs: isOwnSkill },
1932
2338
  { path: POLICY_FILE, template: policyTemplate(input.lang), isOurs: hasMarker },
1933
2339
  ...playwrightVerifier(project)
@@ -1968,9 +2374,9 @@ var needsWrite = (plan) => hasChanges(plan) || plan.previous === void 0;
1968
2374
  // src/install/apply.ts
1969
2375
  var stamp = (iso) => iso.replace(/[:.]/g, "-");
1970
2376
  var ensureParent = (project, relative11) => project.runtime.mkdir(project.abs(dirname3(relative11)));
1971
- var writeText = (project, relative11, text8) => {
2377
+ var writeText = (project, relative11, text9) => {
1972
2378
  const parent = ensureParent(project, relative11);
1973
- const written = parent.ok ? project.runtime.writeAtomic(project.abs(relative11), text8) : parent;
2379
+ const written = parent.ok ? project.runtime.writeAtomic(project.abs(relative11), text9) : parent;
1974
2380
  return written.ok ? accept(void 0) : reject({ path: relative11, detail: written.error });
1975
2381
  };
1976
2382
  var restore = (project, items) => {
@@ -1988,7 +2394,7 @@ var backupItems = (project, dir, items) => {
1988
2394
  return writeText(project, `${dir}/manifest.json`, `${JSON.stringify(manifest, null, 2)}
1989
2395
  `);
1990
2396
  };
1991
- var entryOf = (plan, item2, backupDir) => {
2397
+ var entryOf2 = (plan, item2, backupDir) => {
1992
2398
  const previous = plan.previous?.entries.find((entry2) => entry2.path === item2.path);
1993
2399
  const existed = previous?.existed ?? item2.before !== void 0;
1994
2400
  const backup = previous?.backup ?? (existed && backupDir !== void 0 ? `${backupDir}/${item2.path}` : void 0);
@@ -2011,7 +2417,7 @@ var nextLock = (plan, version, now, backupDir, createdDirs, only) => ({
2011
2417
  updatedAt: now,
2012
2418
  createdDirs: [.../* @__PURE__ */ new Set([...plan.previous?.createdDirs ?? [], ...createdDirs])],
2013
2419
  entries: plan.items.filter((item2) => item2.action !== "skip-foreign").flatMap((item2) => {
2014
- if (only === void 0 || only.includes(item2.path)) return [entryOf(plan, item2, backupDir)];
2420
+ if (only === void 0 || only.includes(item2.path)) return [entryOf2(plan, item2, backupDir)];
2015
2421
  const kept = plan.previous?.entries.find((entry2) => entry2.path === item2.path);
2016
2422
  return kept === void 0 ? [] : [kept];
2017
2423
  })
@@ -2110,16 +2516,16 @@ var detectLang = (flag2, env, settings = {}) => {
2110
2516
  };
2111
2517
 
2112
2518
  // src/install/project.ts
2113
- import { dirname as dirname4, join as join5 } from "node:path";
2519
+ import { dirname as dirname4, join as join7 } from "node:path";
2114
2520
  var projectAt = (root, runtime) => {
2115
- const abs = (relative11) => toAbsolutePath(join5(root, ...relative11.split("/")));
2521
+ const abs = (relative11) => toAbsolutePath(join7(root, ...relative11.split("/")));
2116
2522
  return {
2117
2523
  root,
2118
2524
  runtime,
2119
2525
  abs,
2120
2526
  read: (relative11) => {
2121
- const text8 = runtime.readText(abs(relative11));
2122
- return text8.ok ? text8.value : void 0;
2527
+ const text9 = runtime.readText(abs(relative11));
2528
+ return text9.ok ? text9.value : void 0;
2123
2529
  },
2124
2530
  exists: (relative11) => runtime.stat(abs(relative11)) !== void 0,
2125
2531
  isDir: (relative11) => runtime.stat(abs(relative11))?.kind === "dir"
@@ -2128,7 +2534,7 @@ var projectAt = (root, runtime) => {
2128
2534
  var findGitRoot = (start, runtime) => {
2129
2535
  let current2 = start;
2130
2536
  for (; ; ) {
2131
- if (runtime.stat(toAbsolutePath(join5(current2, ".git"))) !== void 0) return toAbsolutePath(current2);
2537
+ if (runtime.stat(toAbsolutePath(join7(current2, ".git"))) !== void 0) return toAbsolutePath(current2);
2132
2538
  const parent = dirname4(current2);
2133
2539
  if (parent === current2) return void 0;
2134
2540
  current2 = parent;
@@ -2192,8 +2598,8 @@ var invalidPreset = (raw) => problem("invalid_arguments", "error.invalid_preset"
2192
2598
  });
2193
2599
  var selfBundle = (ctx) => {
2194
2600
  if (!isBundlePath(ctx.runtime.selfPath)) return void 0;
2195
- const text8 = ctx.runtime.readText(ctx.runtime.selfPath);
2196
- return text8.ok ? text8.value : void 0;
2601
+ const text9 = ctx.runtime.readText(ctx.runtime.selfPath);
2602
+ return text9.ok ? text9.value : void 0;
2197
2603
  };
2198
2604
  var INHERITED = /* @__PURE__ */ new Set(["default", "config", "claude-settings"]);
2199
2605
  var planInputFor = (ctx, preset, bin, marketplace) => ({
@@ -2229,7 +2635,7 @@ var applyFixes = (ctx, write3) => {
2229
2635
  };
2230
2636
 
2231
2637
  // src/doctor/report-file.ts
2232
- import { join as join7 } from "node:path";
2638
+ import { join as join9 } from "node:path";
2233
2639
 
2234
2640
  // src/evidence/log.ts
2235
2641
  var logEvent = (runtime, paths, event, data) => runtime.appendText(paths.log, `${JSON.stringify({ at: runtime.now(), event, ...data })}
@@ -2297,13 +2703,13 @@ var isPlaceholder = (value) => {
2297
2703
  var isTestOrLockPath = (path) => path !== void 0 && (LOCKFILES.test(toPosixPath(path)) || TEST_PATH.test(toPosixPath(path)));
2298
2704
 
2299
2705
  // src/secrets/entropy.ts
2300
- var shannonEntropy = (text8) => {
2301
- if (text8.length === 0) return 0;
2706
+ var shannonEntropy = (text9) => {
2707
+ if (text9.length === 0) return 0;
2302
2708
  const counts = /* @__PURE__ */ new Map();
2303
- for (const char of text8) counts.set(char, (counts.get(char) ?? 0) + 1);
2709
+ for (const char of text9) counts.set(char, (counts.get(char) ?? 0) + 1);
2304
2710
  let entropy = 0;
2305
2711
  for (const count4 of counts.values()) {
2306
- const p = count4 / text8.length;
2712
+ const p = count4 / text9.length;
2307
2713
  entropy -= p * Math.log2(p);
2308
2714
  }
2309
2715
  return entropy;
@@ -2311,7 +2717,7 @@ var shannonEntropy = (text8) => {
2311
2717
 
2312
2718
  // src/secrets/fingerprint.ts
2313
2719
  import { createHash as createHash2 } from "node:crypto";
2314
- var sha256Hex = (text8) => createHash2("sha256").update(text8).digest("hex");
2720
+ var sha256Hex = (text9) => createHash2("sha256").update(text9).digest("hex");
2315
2721
  var fingerprintOf = (secret) => sha256Hex(`finitemonkey:secret:${secret}`).slice(0, 16);
2316
2722
  var ALNUM = /[A-Za-z0-9]/g;
2317
2723
  var MIN_TAIL_LENGTH = 12;
@@ -2321,9 +2727,9 @@ var tailOf = (secret) => {
2321
2727
  };
2322
2728
 
2323
2729
  // src/secrets/lines.ts
2324
- var lineLocator = (text8) => {
2730
+ var lineLocator = (text9) => {
2325
2731
  const starts = [0];
2326
- for (let index = text8.indexOf("\n"); index !== -1; index = text8.indexOf("\n", index + 1)) starts.push(index + 1);
2732
+ for (let index = text9.indexOf("\n"); index !== -1; index = text9.indexOf("\n", index + 1)) starts.push(index + 1);
2327
2733
  return (offset) => {
2328
2734
  let low = 0;
2329
2735
  let high = starts.length - 1;
@@ -2617,12 +3023,12 @@ var hasRandomRun = (secret, length) => (secret.match(new RegExp(`[A-Za-z0-9]{${S
2617
3023
  );
2618
3024
  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);
2619
3025
  var NAME_CHAR = /[A-Za-z0-9_.-]/;
2620
- var nameAround = (text8, at) => {
3026
+ var nameAround = (text9, at) => {
2621
3027
  let start = at;
2622
- while (start > 0 && NAME_CHAR.test(text8.charAt(start - 1))) start -= 1;
3028
+ while (start > 0 && NAME_CHAR.test(text9.charAt(start - 1))) start -= 1;
2623
3029
  let end = at;
2624
- while (end < text8.length && NAME_CHAR.test(text8.charAt(end))) end += 1;
2625
- return text8.slice(start, end).toLowerCase();
3030
+ while (end < text9.length && NAME_CHAR.test(text9.charAt(end))) end += 1;
3031
+ return text9.slice(start, end).toLowerCase();
2626
3032
  };
2627
3033
  var ignoredName = (rule, match) => rule.ignoredNames.length > 0 && rule.ignoredNames.includes(nameAround(match.input, match.index));
2628
3034
  var spanOf = (rule, match) => {
@@ -2632,9 +3038,9 @@ var spanOf = (rule, match) => {
2632
3038
  const secret = match.input.slice(start, end);
2633
3039
  return accepted(rule, secret) ? { rule, start, end, secret } : void 0;
2634
3040
  };
2635
- var ruleSpans = (rule, text8) => {
3041
+ var ruleSpans = (rule, text9) => {
2636
3042
  const spans = [];
2637
- for (const match of text8.matchAll(rule.pattern)) {
3043
+ for (const match of text9.matchAll(rule.pattern)) {
2638
3044
  const span = spanOf(rule, match);
2639
3045
  if (span !== void 0) spans.push(span);
2640
3046
  }
@@ -2660,31 +3066,31 @@ var toFinding = (span, locate2) => {
2660
3066
  tail: tailOf(span.secret)
2661
3067
  };
2662
3068
  };
2663
- var scanText = (text8, options = {}) => {
2664
- if (text8.length === 0) return [];
2665
- const rules = applicable(options.rules ?? RULES, text8.toLowerCase(), options.path);
3069
+ var scanText = (text9, options = {}) => {
3070
+ if (text9.length === 0) return [];
3071
+ const rules = applicable(options.rules ?? RULES, text9.toLowerCase(), options.path);
2666
3072
  if (rules.length === 0) return [];
2667
3073
  const taken = [];
2668
3074
  for (const rule of rules) {
2669
- for (const span of ruleSpans(rule, text8)) if (!overlaps(taken, span)) taken.push(span);
3075
+ for (const span of ruleSpans(rule, text9)) if (!overlaps(taken, span)) taken.push(span);
2670
3076
  }
2671
3077
  if (taken.length === 0) return [];
2672
- const locate2 = lineLocator(text8);
3078
+ const locate2 = lineLocator(text9);
2673
3079
  return taken.sort((a, b) => a.start - b.start).map((span) => toFinding(span, locate2));
2674
3080
  };
2675
3081
 
2676
3082
  // src/mask/mask.ts
2677
3083
  var maskToken = (finding4) => `«secret:${finding4.ruleId}:${finding4.tail}»`;
2678
- var maskText = (text8) => {
2679
- const findings = scanText(text8, { rules: CREDENTIAL_RULES });
2680
- if (findings.length === 0) return text8;
3084
+ var maskText = (text9) => {
3085
+ const findings = scanText(text9, { rules: CREDENTIAL_RULES });
3086
+ if (findings.length === 0) return text9;
2681
3087
  let out = "";
2682
3088
  let cursor = 0;
2683
3089
  for (const finding4 of findings) {
2684
- out += text8.slice(cursor, finding4.start) + maskToken(finding4);
3090
+ out += text9.slice(cursor, finding4.start) + maskToken(finding4);
2685
3091
  cursor = finding4.end;
2686
3092
  }
2687
- return out + text8.slice(cursor);
3093
+ return out + text9.slice(cursor);
2688
3094
  };
2689
3095
  var maskDeep = (value) => {
2690
3096
  if (typeof value === "string") return maskText(value);
@@ -2696,11 +3102,11 @@ var maskDeep = (value) => {
2696
3102
  };
2697
3103
 
2698
3104
  // src/task/paths.ts
2699
- import { join as join6 } from "node:path";
3105
+ import { join as join8 } from "node:path";
2700
3106
  var FM_DIR = ".finitemonkey";
2701
3107
  var fmPaths = (root) => {
2702
- const dir = toAbsolutePath(join6(root, FM_DIR));
2703
- const at = (...parts) => toAbsolutePath(join6(dir, ...parts));
3108
+ const dir = toAbsolutePath(join8(root, FM_DIR));
3109
+ const at = (...parts) => toAbsolutePath(join8(dir, ...parts));
2704
3110
  return {
2705
3111
  root,
2706
3112
  dir,
@@ -2902,14 +3308,14 @@ var toolVersion = (env, command) => {
2902
3308
  return result.ok ? result.stdout.trim().split("\n")[0] ?? "" : "not found";
2903
3309
  };
2904
3310
  var lastHookFailures = (env) => {
2905
- const text8 = env.runtime.readText(fmPaths(env.root).log);
2906
- if (!text8.ok) return [];
2907
- return text8.value.split("\n").filter((line2) => {
3311
+ const text9 = env.runtime.readText(fmPaths(env.root).log);
3312
+ if (!text9.ok) return [];
3313
+ return text9.value.split("\n").filter((line2) => {
2908
3314
  const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
2909
3315
  return parsed?.ok === true && isRecord(parsed.value) && parsed.value["event"] === HOOK_FAILURE_EVENT;
2910
3316
  }).slice(-MAX_FAILURES);
2911
3317
  };
2912
- var withoutHome = (text8, home) => home.length > 1 ? text8.split(home).join("~") : text8;
3318
+ var withoutHome = (text9, home) => home.length > 1 ? text9.split(home).join("~") : text9;
2913
3319
  var reportMarkdown = (env, ctx, envelope) => {
2914
3320
  const failures = lastHookFailures(env);
2915
3321
  const lines3 = [
@@ -2936,18 +3342,18 @@ var reportMarkdown = (env, ctx, envelope) => {
2936
3342
  ];
2937
3343
  return maskText(withoutHome(lines3.join("\n"), env.runtime.homeDir()));
2938
3344
  };
2939
- var reportPath = (env) => toAbsolutePath(join7(env.root, ...REPORTS_DIR.split("/"), `doctor-${stamp(env.runtime.now())}.md`));
3345
+ var reportPath = (env) => toAbsolutePath(join9(env.root, ...REPORTS_DIR.split("/"), `doctor-${stamp(env.runtime.now())}.md`));
2940
3346
 
2941
3347
  // src/install/plugin-yield.ts
2942
- import { join as join8 } from "node:path";
3348
+ import { join as join10 } from "node:path";
2943
3349
  var PLUGIN_VIA = "plugin";
2944
3350
  var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
2945
3351
  `;
2946
3352
  var projectOwnsHooks = (ctx) => {
2947
3353
  const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
2948
- const text8 = ctx.runtime.readText(toAbsolutePath(join8(root, ...SETTINGS_FILE.split("/"))));
2949
- if (!text8.ok) return false;
2950
- const parsed = parseJsonObject(text8.value);
3354
+ const text9 = ctx.runtime.readText(toAbsolutePath(join10(root, ...SETTINGS_FILE.split("/"))));
3355
+ if (!text9.ok) return false;
3356
+ const parsed = parseJsonObject(text9.value);
2951
3357
  return parsed.ok && ownHandlersIn(parsed.value) > 0;
2952
3358
  };
2953
3359
 
@@ -2959,7 +3365,7 @@ var STABLE_PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
2959
3365
  var NEXT_PLUGIN_ID = `${PLUGIN_NAME}@${NEXT_MARKETPLACE_NAME}`;
2960
3366
 
2961
3367
  // src/doctor/settings.ts
2962
- import { join as join9 } from "node:path";
3368
+ import { join as join11 } from "node:path";
2963
3369
  var MANAGED = {
2964
3370
  darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
2965
3371
  linux: "/etc/claude-code/managed-settings.json",
@@ -2968,7 +3374,7 @@ var MANAGED = {
2968
3374
  var userSettingsPath = ({ env, runtime }) => {
2969
3375
  const configDir = env["CLAUDE_CONFIG_DIR"];
2970
3376
  return toAbsolutePath(
2971
- configDir === void 0 ? join9(runtime.homeDir(), ".claude", "settings.json") : join9(configDir, "settings.json")
3377
+ configDir === void 0 ? join11(runtime.homeDir(), ".claude", "settings.json") : join11(configDir, "settings.json")
2972
3378
  );
2973
3379
  };
2974
3380
  var settingsPaths = (env) => {
@@ -2976,13 +3382,13 @@ var settingsPaths = (env) => {
2976
3382
  return [
2977
3383
  ...managed === void 0 ? [] : [{ scope: "managed", path: toAbsolutePath(managed) }],
2978
3384
  { scope: "user", path: userSettingsPath(env) },
2979
- { scope: "project", path: toAbsolutePath(join9(env.root, ".claude", "settings.json")) },
2980
- { scope: "local", path: toAbsolutePath(join9(env.root, ".claude", "settings.local.json")) }
3385
+ { scope: "project", path: toAbsolutePath(join11(env.root, ".claude", "settings.json")) },
3386
+ { scope: "local", path: toAbsolutePath(join11(env.root, ".claude", "settings.local.json")) }
2981
3387
  ];
2982
3388
  };
2983
3389
  var readSettings = (env) => settingsPaths(env).flatMap(({ scope: scope2, path }) => {
2984
- const text8 = env.runtime.readText(path);
2985
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
3390
+ const text9 = env.runtime.readText(path);
3391
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
2986
3392
  return parsed?.ok === true ? [{ scope: scope2, path, settings: parsed.value }] : [];
2987
3393
  });
2988
3394
  var scopesWith = (all, test) => all.filter(({ settings }) => test(settings)).map(({ scope: scope2 }) => scope2);
@@ -3121,7 +3527,7 @@ var checkClaudeSettings = (env) => {
3121
3527
  };
3122
3528
 
3123
3529
  // src/doctor/environment.ts
3124
- import { join as join10 } from "node:path";
3530
+ import { join as join12 } from "node:path";
3125
3531
  var AUTH_VARIABLES = [
3126
3532
  "ANTHROPIC_API_KEY",
3127
3533
  "ANTHROPIC_AUTH_TOKEN",
@@ -3159,7 +3565,7 @@ var pathDirs = ({ env, platform }) => {
3159
3565
  var copiesOnPath = (env) => {
3160
3566
  const names = EXECUTABLES[env.platform] ?? ["finitemonkey"];
3161
3567
  const found = pathDirs(env).flatMap(
3162
- (dir) => names.map((name) => toAbsolutePath(join10(dir, name))).filter((path) => env.runtime.stat(path)?.kind === "file").map((path) => env.runtime.realpath(path))
3568
+ (dir) => names.map((name) => toAbsolutePath(join12(dir, name))).filter((path) => env.runtime.stat(path)?.kind === "file").map((path) => env.runtime.realpath(path))
3163
3569
  );
3164
3570
  const byDir = new Map(found.map((path) => [path.replace(/\.(cmd|exe|ps1)$/i, ""), path]));
3165
3571
  return [...byDir.values()];
@@ -3193,7 +3599,7 @@ var checkAuthVariables = ({ env, platform }) => {
3193
3599
  };
3194
3600
 
3195
3601
  // src/doctor/hooks.ts
3196
- import { join as join34 } from "node:path";
3602
+ import { join as join36 } from "node:path";
3197
3603
 
3198
3604
  // src/hooks/events.ts
3199
3605
  var HOOK_EVENTS = [
@@ -3239,6 +3645,36 @@ var deadlineFor = (event, raw) => {
3239
3645
  return EVENT_DEADLINES[event] ?? DEFAULT_HOOK_DEADLINE_MS;
3240
3646
  };
3241
3647
 
3648
+ // src/gate/hooks/common.ts
3649
+ import { dirname as dirname5, join as join13 } from "node:path";
3650
+ var DEFAULT_GATE_BUDGET_MS = 3e5;
3651
+ var sessionCwd = (input) => input.payload.cwd === void 0 ? input.cwd : toAbsolutePath(input.payload.cwd, input.cwd);
3652
+ var MAX_DEPTH = 64;
3653
+ var findRepoRoot = (runtime, start) => {
3654
+ let current2 = start;
3655
+ for (let depth = 0; depth < MAX_DEPTH; depth += 1) {
3656
+ if (runtime.stat(toAbsolutePath(join13(current2, ".git"))) !== void 0) return current2;
3657
+ const parent = toAbsolutePath(dirname5(current2));
3658
+ if (parent === current2) return void 0;
3659
+ current2 = parent;
3660
+ }
3661
+ return void 0;
3662
+ };
3663
+ var projectPaths = (input) => {
3664
+ const root = findRepoRoot(input.runtime, sessionCwd(input));
3665
+ return root === void 0 ? void 0 : fmPaths(root);
3666
+ };
3667
+ var gateBudget = (input) => {
3668
+ const parsed = Number.parseInt(input.env["FINITEMONKEY_GATE_BUDGET_MS"] ?? "", 10);
3669
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_GATE_BUDGET_MS;
3670
+ };
3671
+ var allowWith = (output) => ({ kind: "allow", output });
3672
+ var contextOutput = (event, additionalContext, systemMessage) => ({
3673
+ hookSpecificOutput: { hookEventName: event, additionalContext },
3674
+ ...systemMessage === void 0 ? {} : { systemMessage }
3675
+ });
3676
+ var human = (text9) => `finitemonkey: ${text9}`;
3677
+
3242
3678
  // src/hooks/fields.ts
3243
3679
  var textField = (record3, ...names) => {
3244
3680
  for (const name of names) {
@@ -3268,7 +3704,7 @@ var editsOf = (input) => {
3268
3704
  });
3269
3705
  };
3270
3706
  var applyEdits = (before, edits) => edits.reduce(
3271
- (text8, edit) => edit.all ? text8.split(edit.oldText).join(edit.newText) : text8.replace(edit.oldText, () => edit.newText),
3707
+ (text9, edit) => edit.all ? text9.split(edit.oldText).join(edit.newText) : text9.replace(edit.oldText, () => edit.newText),
3272
3708
  before
3273
3709
  );
3274
3710
 
@@ -3288,7 +3724,7 @@ var familyOf = (base) => {
3288
3724
  if (found === void 0) throw new Error(`no reason family for ${base}`);
3289
3725
  return found;
3290
3726
  };
3291
- var capReason = (text8) => text8.length <= REASON_LIMIT ? text8 : `${text8.slice(0, REASON_LIMIT - 1)}…`;
3727
+ var capReason = (text9) => text9.length <= REASON_LIMIT ? text9 : `${text9.slice(0, REASON_LIMIT - 1)}…`;
3292
3728
  var modelReason = (t, base, params2 = {}) => {
3293
3729
  const claim = `${base}.claim`;
3294
3730
  const requires = `${base}.requires`;
@@ -3315,9 +3751,9 @@ var textRecord = (value) => isRecord(value) ? Object.fromEntries(
3315
3751
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
3316
3752
  ) : {};
3317
3753
  var readJsonFile = (runtime, path) => {
3318
- const text8 = runtime.readText(path);
3319
- if (!text8.ok) return void 0;
3320
- const parsed = parseJsonObject(text8.value);
3754
+ const text9 = runtime.readText(path);
3755
+ if (!text9.ok) return void 0;
3756
+ const parsed = parseJsonObject(text9.value);
3321
3757
  return parsed.ok ? parsed.value : void 0;
3322
3758
  };
3323
3759
  var writeJsonFile = (runtime, path, value) => runtime.writeText(path, `${JSON.stringify({ schemaVersion: 1, ...value }, null, 2)}
@@ -3367,28 +3803,28 @@ var closeTask = (state, id) => assemble({
3367
3803
 
3368
3804
  // src/guards/release/grants.ts
3369
3805
  var GRANT_MINUTES = 30;
3370
- var text2 = (record3, key) => {
3806
+ var text3 = (record3, key) => {
3371
3807
  const value = record3[key];
3372
3808
  return typeof value === "string" ? value : void 0;
3373
3809
  };
3374
3810
  var toGrant = (value) => {
3375
3811
  if (!isRecord(value)) return void 0;
3376
- const guard = text2(value, "guard");
3377
- const reason = text2(value, "reason");
3378
- const grantedAt = text2(value, "grantedAt");
3379
- const expiresAt = text2(value, "expiresAt");
3380
- const sessionId = text2(value, "sessionId");
3812
+ const guard = text3(value, "guard");
3813
+ const reason = text3(value, "reason");
3814
+ const grantedAt = text3(value, "grantedAt");
3815
+ const expiresAt = text3(value, "expiresAt");
3816
+ const sessionId = text3(value, "sessionId");
3381
3817
  if (guard === void 0 || reason === void 0 || grantedAt === void 0 || expiresAt === void 0)
3382
3818
  return void 0;
3383
3819
  return { guard, reason, grantedAt, expiresAt, ...sessionId === void 0 ? {} : { sessionId } };
3384
3820
  };
3385
3821
  var toApproval = (value) => {
3386
3822
  if (!isRecord(value)) return void 0;
3387
- const task = text2(value, "task");
3388
- const check2 = text2(value, "check");
3389
- const at = text2(value, "at");
3823
+ const task = text3(value, "task");
3824
+ const check2 = text3(value, "check");
3825
+ const at = text3(value, "at");
3390
3826
  if (task === void 0 || check2 === void 0 || at === void 0) return void 0;
3391
- return { task, check: check2, at, reason: text2(value, "reason") ?? "" };
3827
+ return { task, check: check2, at, reason: text3(value, "reason") ?? "" };
3392
3828
  };
3393
3829
  var list = (raw, map) => Array.isArray(raw) ? raw.flatMap((item2) => {
3394
3830
  const value = map(item2);
@@ -3430,7 +3866,7 @@ var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
3430
3866
  import { posix } from "node:path";
3431
3867
 
3432
3868
  // src/shell/scan.ts
3433
- var cursorOf = (text8) => ({ text: text8, pos: 0, incomplete: false, subs: [] });
3869
+ var cursorOf = (text9) => ({ text: text9, pos: 0, incomplete: false, subs: [] });
3434
3870
  var peek = (cursor, offset = 0) => cursor.text.charAt(cursor.pos + offset);
3435
3871
  var atEnd = (cursor) => cursor.pos >= cursor.text.length;
3436
3872
  var skipQuoted = (cursor, quote) => {
@@ -3645,8 +4081,8 @@ var nextToken = (cursor, tokens, pending) => {
3645
4081
  tokens.push({ kind: "op", text: op });
3646
4082
  if (op === "\n") flushHeredocs(cursor, tokens, pending);
3647
4083
  };
3648
- var lexBash = (text8) => {
3649
- const cursor = cursorOf(text8.replace(/\r\n/g, "\n"));
4084
+ var lexBash = (text9) => {
4085
+ const cursor = cursorOf(text9.replace(/\r\n/g, "\n"));
3650
4086
  const tokens = [];
3651
4087
  const pending = [];
3652
4088
  skipBlank(cursor);
@@ -3794,9 +4230,9 @@ var next = (cursor, tokens, cmd) => {
3794
4230
  }
3795
4231
  const op = OPERATORS2.find((candidate) => cursor.text.startsWith(candidate, cursor.pos));
3796
4232
  if (op !== void 0 || cmd && peek(cursor) === "&") {
3797
- const text8 = op ?? "&";
3798
- cursor.pos += text8.length;
3799
- tokens.push({ kind: "op", text: text8 });
4233
+ const text9 = op ?? "&";
4234
+ cursor.pos += text9.length;
4235
+ tokens.push({ kind: "op", text: text9 });
3800
4236
  return;
3801
4237
  }
3802
4238
  if ((peek(cursor) === "&" || peek(cursor) === ".") && peek(cursor, 1) === " " && commandStart(tokens)) {
@@ -3805,8 +4241,8 @@ var next = (cursor, tokens, cmd) => {
3805
4241
  }
3806
4242
  tokens.push(word(cursor, cmd));
3807
4243
  };
3808
- var lexPowerShell = (text8, cmd = false) => {
3809
- const cursor = cursorOf(text8.replace(/\r\n/g, "\n"));
4244
+ var lexPowerShell = (text9, cmd = false) => {
4245
+ const cursor = cursorOf(text9.replace(/\r\n/g, "\n"));
3810
4246
  const tokens = [];
3811
4247
  const skip = () => {
3812
4248
  while (peek(cursor) === " " || peek(cursor) === " ") cursor.pos += 1;
@@ -4005,11 +4441,11 @@ var unwrap = (initial, stdin) => {
4005
4441
  };
4006
4442
 
4007
4443
  // src/shell/normalize.ts
4008
- var MAX_DEPTH = 8;
4444
+ var MAX_DEPTH2 = 8;
4009
4445
  var ASSIGNMENT2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s;
4010
4446
  var PS_ENV = /^\$env:([A-Za-z_][A-Za-z0-9_]*)=?(.*)$/is;
4011
4447
  var CHANGES_DIR = /* @__PURE__ */ new Set(["cd", "pushd", "set-location", "push-location"]);
4012
- var lex = (text8, language) => language === "bash" ? lexBash(text8) : lexPowerShell(text8, language === "cmd");
4448
+ var lex = (text9, language) => language === "bash" ? lexBash(text9) : lexPowerShell(text9, language === "cmd");
4013
4449
  var isWindowsAbsolute = (path) => /^[A-Za-z]:\//.test(path);
4014
4450
  var joinDir = (current2, target2) => {
4015
4451
  const next2 = toPosixPath(target2);
@@ -4080,21 +4516,21 @@ var visit = (raw, language, context, depth, collector) => {
4080
4516
  emit(result.stage, raw, language, context, collector);
4081
4517
  for (const extra of result.extra) emit(extra, raw, language, context, collector);
4082
4518
  };
4083
- var collect = (text8, language, context, depth, collector) => {
4084
- if (depth > MAX_DEPTH) {
4519
+ var collect = (text9, language, context, depth, collector) => {
4520
+ if (depth > MAX_DEPTH2) {
4085
4521
  collector.incomplete = true;
4086
4522
  return;
4087
4523
  }
4088
- const lexed = lex(text8, language);
4524
+ const lexed = lex(text9, language);
4089
4525
  if (lexed.incomplete) collector.incomplete = true;
4090
4526
  for (const substitution of lexed.substitutions) {
4091
4527
  collect(substitution, language, { ...context }, depth + 1, collector);
4092
4528
  }
4093
4529
  for (const raw of rawCommands(lexed.tokens)) visit(raw, language, context, depth, collector);
4094
4530
  };
4095
- var normalizeCommand = (text8, dialect = "bash") => {
4531
+ var normalizeCommand = (text9, dialect = "bash") => {
4096
4532
  const collector = { out: [], incomplete: false };
4097
- collect(text8, dialect, { env: {}, dir: void 0, via: [] }, 0, collector);
4533
+ collect(text9, dialect, { env: {}, dir: void 0, via: [] }, 0, collector);
4098
4534
  return { commands: collector.out, incomplete: collector.incomplete };
4099
4535
  };
4100
4536
  var dialectOf = (toolName3) => toolName3 === "PowerShell" ? "powershell" : "bash";
@@ -4106,7 +4542,7 @@ var segmentsOf = (command) => normalizeCommand(command).commands.map((item2) =>
4106
4542
 
4107
4543
  // src/guards/shell.ts
4108
4544
  var HEREDOC = /^<<[-<]?$/;
4109
- var parseShell = (text8) => normalizeCommand(text8).commands.map((command) => ({
4545
+ var parseShell = (text9) => normalizeCommand(text9).commands.map((command) => ({
4110
4546
  env: command.env,
4111
4547
  argv: command.program === "" ? [] : [command.program, ...command.args],
4112
4548
  redirects: command.redirects.filter((redirect) => !HEREDOC.test(redirect.op)).map((redirect) => redirect.target)
@@ -4268,9 +4704,9 @@ var writtenPaths = (command) => {
4268
4704
  if (name === "git" && GIT_WRITERS.has(operands2[0] ?? "")) return [...command.redirects, ...operands2.slice(1)];
4269
4705
  return command.redirects;
4270
4706
  };
4271
- var mentioned = (text8) => {
4272
- if (/\.finitemonkey[\\/]allow/.test(text8)) return "allow_store";
4273
- if (/\.finitemonkey|\.gitleaksignore/.test(text8)) return "policy";
4707
+ var mentioned = (text9) => {
4708
+ if (/\.finitemonkey[\\/]allow/.test(text9)) return "allow_store";
4709
+ if (/\.finitemonkey|\.gitleaksignore/.test(text9)) return "policy";
4274
4710
  return void 0;
4275
4711
  };
4276
4712
  var bashSelfTargets = (root, commandLine) => parseShell(commandLine).flatMap(
@@ -4310,7 +4746,7 @@ var selfGuard = (input) => {
4310
4746
  };
4311
4747
 
4312
4748
  // src/guards/tests/hook.ts
4313
- import { join as join11, relative as relative3 } from "node:path";
4749
+ import { join as join14, relative as relative3 } from "node:path";
4314
4750
 
4315
4751
  // src/guards/tests/patterns.ts
4316
4752
  var JS_TEST = /(^|\/)(__tests__|__test__)\/|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)tests?\/.*\.[cm]?[jt]sx?$/;
@@ -4474,8 +4910,8 @@ var compareConfig = (path, before, after, accepted2) => {
4474
4910
  // src/guards/tests/hook.ts
4475
4911
  var localPath = (root, path) => toPosixPath(relative3(root, toAbsolutePath(path, root)));
4476
4912
  var read = (input, path) => {
4477
- const text8 = input.runtime.readText(path);
4478
- return text8.ok ? text8.value : void 0;
4913
+ const text9 = input.runtime.readText(path);
4914
+ return text9.ok ? text9.value : void 0;
4479
4915
  };
4480
4916
  var proposed = (tool, args2, before) => {
4481
4917
  if (tool === "Write") return textField(args2, "content") ?? "";
@@ -4500,7 +4936,7 @@ var shellViolations = (input, root) => {
4500
4936
  return parseShell(command).flatMap(
4501
4937
  (simple) => writtenPaths(simple).flatMap((path) => {
4502
4938
  const local = localPath(root, path);
4503
- const exists = input.runtime.stat(toAbsolutePath(join11(root, local))) !== void 0;
4939
+ const exists = input.runtime.stat(toAbsolutePath(join14(root, local))) !== void 0;
4504
4940
  if (!exists) return [];
4505
4941
  if (isProtectedConfig(local)) return [{ kind: "config_changed", path: local }];
4506
4942
  return isTestFile(local) ? [{ kind: "test_removed", path: local }] : [];
@@ -4513,46 +4949,14 @@ var testsGuard = (input) => {
4513
4949
  const found = FILE_TOOLS.has(tool) ? fileViolations(input, root) : SHELL_TOOLS.has(tool) ? shellViolations(input, root) : [];
4514
4950
  const first = found[0];
4515
4951
  if (first === void 0 || grantFor(input, "tests") !== void 0) return { kind: "allow" };
4516
- const { t } = input;
4517
- const params2 = { file: first.path, before: first.before ?? 0, after: first.after ?? 0 };
4518
- const reason = SHELL_TOOLS.has(tool) ? t("gate.guard.tests.shell", params2) : t(`gate.tests.${first.kind}`, params2);
4519
- return deny(modelReason(t, "gate.guard.tests", { detail: reason }), t("gate.guard.tests.human", { detail: reason }));
4520
- };
4521
-
4522
- // src/look/reads.ts
4523
- import { relative as relative4 } from "node:path";
4524
-
4525
- // src/gate/hooks/common.ts
4526
- import { dirname as dirname5, join as join12 } from "node:path";
4527
- var DEFAULT_GATE_BUDGET_MS = 3e5;
4528
- var sessionCwd = (input) => input.payload.cwd === void 0 ? input.cwd : toAbsolutePath(input.payload.cwd, input.cwd);
4529
- var MAX_DEPTH2 = 64;
4530
- var findRepoRoot = (runtime, start) => {
4531
- let current2 = start;
4532
- for (let depth = 0; depth < MAX_DEPTH2; depth += 1) {
4533
- if (runtime.stat(toAbsolutePath(join12(current2, ".git"))) !== void 0) return current2;
4534
- const parent = toAbsolutePath(dirname5(current2));
4535
- if (parent === current2) return void 0;
4536
- current2 = parent;
4537
- }
4538
- return void 0;
4539
- };
4540
- var projectPaths = (input) => {
4541
- const root = findRepoRoot(input.runtime, sessionCwd(input));
4542
- return root === void 0 ? void 0 : fmPaths(root);
4543
- };
4544
- var gateBudget = (input) => {
4545
- const parsed = Number.parseInt(input.env["FINITEMONKEY_GATE_BUDGET_MS"] ?? "", 10);
4546
- return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_GATE_BUDGET_MS;
4952
+ const { t } = input;
4953
+ const params2 = { file: first.path, before: first.before ?? 0, after: first.after ?? 0 };
4954
+ const reason = SHELL_TOOLS.has(tool) ? t("gate.guard.tests.shell", params2) : t(`gate.tests.${first.kind}`, params2);
4955
+ return deny(modelReason(t, "gate.guard.tests", { detail: reason }), t("gate.guard.tests.human", { detail: reason }));
4547
4956
  };
4548
- var allowWith = (output) => ({ kind: "allow", output });
4549
- var contextOutput = (event, additionalContext, systemMessage) => ({
4550
- hookSpecificOutput: { hookEventName: event, additionalContext },
4551
- ...systemMessage === void 0 ? {} : { systemMessage }
4552
- });
4553
- var human = (text8) => `finitemonkey: ${text8}`;
4554
4957
 
4555
4958
  // src/look/reads.ts
4959
+ import { relative as relative4 } from "node:path";
4556
4960
  var READS_FILE = "look-reads.json";
4557
4961
  var KEEP = 500;
4558
4962
  var readLookReads = (runtime, paths) => {
@@ -4612,8 +5016,8 @@ var parseMarkers = (prompt) => {
4612
5016
  // src/policy/pause.ts
4613
5017
  var PAUSE_FILE = "pause.json";
4614
5018
  var readPause = (runtime, paths) => {
4615
- const text8 = runtime.readText(paths.cache(PAUSE_FILE));
4616
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
5019
+ const text9 = runtime.readText(paths.cache(PAUSE_FILE));
5020
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
4617
5021
  if (parsed?.ok !== true || !isRecord(parsed.value)) return void 0;
4618
5022
  const { sessionId, since, by } = parsed.value;
4619
5023
  if (typeof sessionId !== "string" || typeof since !== "string") return void 0;
@@ -4660,7 +5064,7 @@ var unlessPaused = (handler) => (input) => {
4660
5064
  };
4661
5065
 
4662
5066
  // src/gate/hooks/config.ts
4663
- import { join as join13 } from "node:path";
5067
+ import { join as join15 } from "node:path";
4664
5068
  var CONFIG_FILE3 = "config-hooks.json";
4665
5069
  var GUARDED_SOURCES = /* @__PURE__ */ new Set(["user_settings", "project_settings", "local_settings"]);
4666
5070
  var countIn = (value) => {
@@ -4668,20 +5072,20 @@ var countIn = (value) => {
4668
5072
  if (Array.isArray(value)) return value.reduce((sum, item2) => sum + countIn(item2), 0);
4669
5073
  return isRecord(value) ? Object.values(value).reduce((sum, item2) => sum + countIn(item2), 0) : 0;
4670
5074
  };
4671
- var censusOf = (text8) => {
4672
- if (text8 === void 0) return { hooks: 0, disabled: false };
4673
- const parsed = parseJsonObject(text8);
5075
+ var censusOf = (text9) => {
5076
+ if (text9 === void 0) return { hooks: 0, disabled: false };
5077
+ const parsed = parseJsonObject(text9);
4674
5078
  if (!parsed.ok) return void 0;
4675
5079
  return { hooks: countIn(parsed.value["hooks"]), disabled: parsed.value["disableAllHooks"] === true };
4676
5080
  };
4677
5081
  var settingsFiles = (paths, env) => [
4678
- toAbsolutePath(join13(paths.root, ".claude", "settings.json")),
4679
- toAbsolutePath(join13(paths.root, ".claude", "settings.local.json")),
4680
- ...env["HOME"] === void 0 ? [] : [toAbsolutePath(join13(env["HOME"], ".claude", "settings.json"))]
5082
+ toAbsolutePath(join15(paths.root, ".claude", "settings.json")),
5083
+ toAbsolutePath(join15(paths.root, ".claude", "settings.local.json")),
5084
+ ...env["HOME"] === void 0 ? [] : [toAbsolutePath(join15(env["HOME"], ".claude", "settings.json"))]
4681
5085
  ];
4682
5086
  var read2 = (runtime, path) => {
4683
- const text8 = runtime.readText(path);
4684
- return text8.ok ? text8.value : void 0;
5087
+ const text9 = runtime.readText(path);
5088
+ return text9.ok ? text9.value : void 0;
4685
5089
  };
4686
5090
  var known = (runtime, paths) => {
4687
5091
  const raw = readJsonFile(runtime, paths.cache(CONFIG_FILE3))?.["hooks"];
@@ -4736,9 +5140,9 @@ var LOCKFILES2 = [
4736
5140
  ];
4737
5141
  var packageManager = (files) => LOCKFILES2.find(([file]) => files.exists(file))?.[1] ?? "npm";
4738
5142
  var scriptChecks = (files) => {
4739
- const text8 = files.read("package.json");
4740
- if (text8 === void 0) return [];
4741
- const parsed = parseJsonObject(text8);
5143
+ const text9 = files.read("package.json");
5144
+ if (text9 === void 0) return [];
5145
+ const parsed = parseJsonObject(text9);
4742
5146
  const scripts = parsed.ok ? parsed.value["scripts"] : void 0;
4743
5147
  if (!isRecord(scripts)) return [];
4744
5148
  const pm = packageManager(files);
@@ -4790,7 +5194,7 @@ var implicitContract = (files) => {
4790
5194
  };
4791
5195
 
4792
5196
  // src/gate/prepare.ts
4793
- import { join as join15 } from "node:path";
5197
+ import { join as join17 } from "node:path";
4794
5198
 
4795
5199
  // src/core/yaml/scalar.ts
4796
5200
  var ESCAPES = {
@@ -4828,16 +5232,16 @@ var unescapeDouble = (body2) => {
4828
5232
  }
4829
5233
  return accept(out);
4830
5234
  };
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];
5235
+ var quotedEnd = (text9, start) => {
5236
+ const quote = text9[start];
5237
+ for (let index = start + 1; index < text9.length; index += 1) {
5238
+ const char = text9[index];
4835
5239
  if (quote === '"' && char === "\\") {
4836
5240
  index += 1;
4837
5241
  continue;
4838
5242
  }
4839
5243
  if (char !== quote) continue;
4840
- if (quote === "'" && text8[index + 1] === "'") {
5244
+ if (quote === "'" && text9[index + 1] === "'") {
4841
5245
  index += 1;
4842
5246
  continue;
4843
5247
  }
@@ -4845,20 +5249,20 @@ var quotedEnd = (text8, start) => {
4845
5249
  }
4846
5250
  return -1;
4847
5251
  };
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, "'"));
5252
+ var parseQuoted = (text9) => {
5253
+ const end = quotedEnd(text9, 0);
5254
+ if (end !== text9.length - 1) return reject(`unterminated or trailing text after quoted string`);
5255
+ const body2 = text9.slice(1, -1);
5256
+ return text9.startsWith('"') ? unescapeDouble(body2) : accept(body2.replace(/''/g, "'"));
4853
5257
  };
4854
5258
  var NUMBER = /^-?(0|[1-9]\d*)(\.\d+)?([eE][-+]?\d+)?$/;
4855
5259
  var UNSUPPORTED = /^[&*!|>%@`]/;
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);
5260
+ var parsePlain = (text9) => {
5261
+ if (UNSUPPORTED.test(text9)) return reject(`unsupported YAML syntax "${text9.slice(0, 1)}"`);
5262
+ if (text9 === "true" || text9 === "false") return accept(text9 === "true");
5263
+ if (text9 === "null" || text9 === "~") return accept(null);
5264
+ if (NUMBER.test(text9)) return accept(Number(text9));
5265
+ return accept(text9);
4862
5266
  };
4863
5267
 
4864
5268
  // src/core/yaml/flow.ts
@@ -4885,17 +5289,17 @@ var splitTop = (body2) => {
4885
5289
  if (last.trim() !== "" || parts.length > 0) parts.push(last);
4886
5290
  return accept(parts.map((part2) => part2.trim()));
4887
5291
  };
4888
- var splitKey = (text8) => {
5292
+ var splitKey = (text9) => {
4889
5293
  let index = 0;
4890
- if (text8.startsWith('"') || text8.startsWith("'")) {
4891
- index = quotedEnd(text8, 0) + 1;
5294
+ if (text9.startsWith('"') || text9.startsWith("'")) {
5295
+ index = quotedEnd(text9, 0) + 1;
4892
5296
  if (index <= 0) return void 0;
4893
5297
  }
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() };
5298
+ for (; index < text9.length; index += 1) {
5299
+ const char = text9[index];
5300
+ if (char === "#" && text9[index - 1] === " ") return void 0;
5301
+ if (char === ":" && (index + 1 === text9.length || text9[index + 1] === " ")) {
5302
+ return { key: text9.slice(0, index).trim(), rest: text9.slice(index + 1).trim() };
4899
5303
  }
4900
5304
  }
4901
5305
  return void 0;
@@ -4924,39 +5328,39 @@ var flowSeq = (parts) => {
4924
5328
  }
4925
5329
  return accept(out);
4926
5330
  };
4927
- var parseValue = (text8) => {
4928
- if (text8 === "") return accept(null);
4929
- const first = text8[0] ?? "";
4930
- if (first === '"' || first === "'") return parseQuoted(text8);
5331
+ var parseValue = (text9) => {
5332
+ if (text9 === "") return accept(null);
5333
+ const first = text9[0] ?? "";
5334
+ if (first === '"' || first === "'") return parseQuoted(text9);
4931
5335
  const close2 = OPEN[first];
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));
5336
+ if (close2 === void 0) return parsePlain(text9);
5337
+ if (!text9.endsWith(close2)) return reject(`flow collection must end on the same line with "${close2}"`);
5338
+ const parts = splitTop(text9.slice(1, -1));
4935
5339
  if (!parts.ok) return parts;
4936
5340
  return first === "[" ? flowSeq(parts.value) : flowMap(parts.value);
4937
5341
  };
4938
5342
 
4939
5343
  // src/core/yaml/parse.ts
4940
5344
  var BOM = new RegExp(`^${String.fromCharCode(65279)}`);
4941
- var stripComment2 = (text8) => {
4942
- for (let index = 0; index < text8.length; index += 1) {
4943
- const char = text8[index];
5345
+ var stripComment2 = (text9) => {
5346
+ for (let index = 0; index < text9.length; index += 1) {
5347
+ const char = text9[index];
4944
5348
  if (char === '"' || char === "'") {
4945
- const end = quotedEnd(text8, index);
4946
- if (end < 0) return text8;
5349
+ const end = quotedEnd(text9, index);
5350
+ if (end < 0) return text9;
4947
5351
  index = end;
4948
- } else if (char === "#" && (index === 0 || text8[index - 1] === " ")) return text8.slice(0, index);
5352
+ } else if (char === "#" && (index === 0 || text9[index - 1] === " ")) return text9.slice(0, index);
4949
5353
  }
4950
- return text8;
5354
+ return text9;
4951
5355
  };
4952
5356
  var toLines = (source) => {
4953
5357
  const lines3 = [];
4954
5358
  const raw = source.replace(BOM, "").split(/\r?\n/);
4955
5359
  for (const [index, full] of raw.entries()) {
4956
- const text8 = stripComment2(full).trimEnd();
4957
- const body2 = text8.trimStart();
5360
+ const text9 = stripComment2(full).trimEnd();
5361
+ const body2 = text9.trimStart();
4958
5362
  if (body2 === "" || body2 === "---" && lines3.length === 0) continue;
4959
- const indentText = text8.slice(0, text8.length - body2.length);
5363
+ const indentText = text9.slice(0, text9.length - body2.length);
4960
5364
  if (indentText.includes(" ")) return reject({ line: index + 1, message: "tabs are not allowed for indentation" });
4961
5365
  if (body2 === "---" || body2 === "...")
4962
5366
  return reject({ line: index + 1, message: "only one YAML document is allowed" });
@@ -4965,11 +5369,11 @@ var toLines = (source) => {
4965
5369
  return accept(lines3);
4966
5370
  };
4967
5371
  var fail = (line2, message) => reject({ line: line2?.number ?? 0, message });
4968
- var scalar = (line2, text8) => {
4969
- const value = parseValue(text8);
5372
+ var scalar = (line2, text9) => {
5373
+ const value = parseValue(text9);
4970
5374
  return value.ok ? value : fail(line2, value.error);
4971
5375
  };
4972
- var isItem = (text8) => text8 === "-" || text8.startsWith("- ");
5376
+ var isItem = (text9) => text9 === "-" || text9.startsWith("- ");
4973
5377
  var child = (lines3, index, parent, sameIndentSeq) => {
4974
5378
  const next2 = lines3[index];
4975
5379
  if (next2 !== void 0 && next2.indent > parent.indent) return block(lines3, index, next2.indent);
@@ -5212,8 +5616,8 @@ var validateContract = (value, implicit = false) => {
5212
5616
  // src/task/load.ts
5213
5617
  var contractHash = (contract) => sha256Hex(JSON.stringify(contract.acceptance.map((check2) => [check2.id, check2.kind, check2.run ?? ""])));
5214
5618
  var taskRelative = (id) => `.finitemonkey/tasks/${id}.yml`;
5215
- var parseContract = (id, text8) => {
5216
- const yaml = parseYaml(text8);
5619
+ var parseContract = (id, text9) => {
5620
+ const yaml = parseYaml(text9);
5217
5621
  if (!yaml.ok)
5218
5622
  return reject({ kind: "invalid", id, issues: [`line ${String(yaml.error.line)}: ${yaml.error.message}`] });
5219
5623
  const valid = validateContract(yaml.value);
@@ -5287,7 +5691,7 @@ var readBaseline = (runtime, paths) => {
5287
5691
  var writeBaseline = (runtime, paths, treeHash2, checks) => writeJsonFile(runtime, paths.cache(BASELINE_FILE), { treeHash: treeHash2, checks: { ...checks } }).ok;
5288
5692
 
5289
5693
  // src/gate/diff-shape.ts
5290
- import { join as join14 } from "node:path";
5694
+ import { join as join16 } from "node:path";
5291
5695
 
5292
5696
  // src/gate/git.ts
5293
5697
  var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
@@ -5323,10 +5727,10 @@ var resolveBase = (git2, declared) => {
5323
5727
  const merged2 = remote === void 0 ? void 0 : git2.out(["merge-base", "HEAD", remote])?.trim();
5324
5728
  return nonEmpty(merged2) ?? nonEmpty(headSha(git2)) ?? EMPTY_TREE;
5325
5729
  };
5326
- var nulFields = (text8) => text8.split("\0").filter((part2) => part2 !== "");
5730
+ var nulFields = (text9) => text9.split("\0").filter((part2) => part2 !== "");
5327
5731
  var untracked = (git2) => nulFields(git2.out(["ls-files", "--others", "--exclude-standard", "-z", "--", ".", EXCLUDE_FM]) ?? "");
5328
- var parseNameStatus = (text8) => {
5329
- const fields = nulFields(text8);
5732
+ var parseNameStatus = (text9) => {
5733
+ const fields = nulFields(text9);
5330
5734
  const changes = [];
5331
5735
  for (let index = 0; index < fields.length; index += 1) {
5332
5736
  const code = fields[index] ?? "";
@@ -5394,8 +5798,8 @@ var aheadOfUpstream = (git2) => {
5394
5798
 
5395
5799
  // src/gate/diff-shape.ts
5396
5800
  var untrackedLines = (runtime, root, changes) => changes.filter((change) => change.status === "A").reduce((sum, change) => {
5397
- const text8 = runtime.readText(toAbsolutePath(join14(root, change.path)));
5398
- return sum + (text8.ok ? text8.value.split("\n").length : 0);
5801
+ const text9 = runtime.readText(toAbsolutePath(join16(root, change.path)));
5802
+ return sum + (text9.ok ? text9.value.split("\n").length : 0);
5399
5803
  }, 0);
5400
5804
  var diffShape = (runtime, git2, base, changes) => ({
5401
5805
  files: changes.map((change) => change.path),
@@ -5418,8 +5822,8 @@ var ciChecks = (value) => Array.isArray(value) ? value.flatMap((item2) => {
5418
5822
  const name = optionalString(item2, "name") ?? optionalString(item2, "context") ?? "?";
5419
5823
  return [{ name, state: ciState(item2) }];
5420
5824
  }) : [];
5421
- var parsePrView = (text8) => {
5422
- const parsed = parseJsonObject(text8);
5825
+ var parsePrView = (text9) => {
5826
+ const parsed = parseJsonObject(text9);
5423
5827
  if (!parsed.ok) return void 0;
5424
5828
  const record3 = parsed.value;
5425
5829
  const number2 = record3["number"];
@@ -5600,10 +6004,10 @@ var applySize = (contract, verdict) => {
5600
6004
  // src/gate/prepare.ts
5601
6005
  var diskFiles = (runtime, root) => ({
5602
6006
  read: (relative11) => {
5603
- const text8 = runtime.readText(toAbsolutePath(join15(root, relative11)));
5604
- return text8.ok ? text8.value : void 0;
6007
+ const text9 = runtime.readText(toAbsolutePath(join17(root, relative11)));
6008
+ return text9.ok ? text9.value : void 0;
5605
6009
  },
5606
- exists: (relative11) => runtime.stat(toAbsolutePath(join15(root, relative11))) !== void 0
6010
+ exists: (relative11) => runtime.stat(toAbsolutePath(join17(root, relative11))) !== void 0
5607
6011
  });
5608
6012
  var baseFiles = (git2, base, disk) => ({
5609
6013
  read: (relative11) => base === EMPTY_TREE ? disk.read(relative11) : fileAt(git2, base, relative11),
@@ -5615,7 +6019,7 @@ var loadContract = (options, paths, git2, base) => {
5615
6019
  if (id === void 0) return accept(loadImplicit(options.runtime, paths, baseFiles(git2, base, disk)));
5616
6020
  return loadTask(options.runtime, paths, { disk: disk.read, base: (relative11) => fileAt(git2, base, relative11) }, id);
5617
6021
  };
5618
- var memo = (make) => {
6022
+ var memo2 = (make) => {
5619
6023
  let value;
5620
6024
  return () => {
5621
6025
  value ??= { v: make() };
@@ -5654,7 +6058,7 @@ var prepareGate = (options) => {
5654
6058
  if (tree === void 0) return { kind: "failed", failure: { kind: "no_tree_hash" } };
5655
6059
  const skip = options.noSkip === true ? void 0 : skipReason(options.runtime, paths, loaded.value, changes, tree, options.sessionId);
5656
6060
  if (skip !== void 0) return { kind: "skipped", reason: skip };
5657
- const pr = memo(() => prStatus(options.runtime, git2, paths));
6061
+ const pr = memo2(() => prStatus(options.runtime, git2, paths));
5658
6062
  const size = measureSize(options.runtime, git2, base, changes, loaded.value.contract);
5659
6063
  const ctx = {
5660
6064
  runtime: options.runtime,
@@ -5722,11 +6126,11 @@ var trust = (input, paths, task) => {
5722
6126
  });
5723
6127
  if (ready.kind !== "ready" || ready.prepared.ctx.contract.id !== task) return false;
5724
6128
  const lock = readTrust(runtime, paths);
5725
- const text8 = runtime.readText(paths.task(task));
6129
+ const text9 = runtime.readText(paths.task(task));
5726
6130
  return writeTrust(runtime, paths, {
5727
6131
  ...lock,
5728
6132
  contracts: { ...lock.contracts, [task]: ready.prepared.loaded.hash },
5729
- approved: task === IMPLICIT_ID || !text8.ok ? lock.approved : { ...lock.approved, [task]: text8.value }
6133
+ approved: task === IMPLICIT_ID || !text9.ok ? lock.approved : { ...lock.approved, [task]: text9.value }
5730
6134
  }).ok;
5731
6135
  };
5732
6136
  var apply = (input, paths, markers) => {
@@ -5778,8 +6182,8 @@ var promptRelease = (input) => {
5778
6182
  var PRS_CACHE_MS = 10 * 6e4;
5779
6183
  var PRS_TIMEOUT_MS = 3e3;
5780
6184
  var PRS_AUTH_TIMEOUT_MS = 2e3;
5781
- var parsePrList = (text8) => {
5782
- const parsed = parseJson(text8);
6185
+ var parsePrList = (text9) => {
6186
+ const parsed = parseJson(text9);
5783
6187
  if (!parsed.ok || !Array.isArray(parsed.value)) return void 0;
5784
6188
  return parsed.value.flatMap((item2) => {
5785
6189
  if (!isRecord(item2) || typeof item2["number"] !== "number") return [];
@@ -5865,16 +6269,16 @@ var params = (value) => isRecord(value) ? Object.fromEntries(
5865
6269
  var notes = (value) => Array.isArray(value) ? value.flatMap(
5866
6270
  (item2) => isRecord(item2) && typeof item2["key"] === "string" ? [{ key: item2["key"], params: params(item2["params"]) }] : []
5867
6271
  ) : [];
5868
- var text3 = (record3, key) => {
6272
+ var text4 = (record3, key) => {
5869
6273
  const value = record3[key];
5870
6274
  return typeof value === "string" ? value : void 0;
5871
6275
  };
5872
6276
  var optional = (record3) => {
5873
6277
  const exitCode = record3["exitCode"];
5874
6278
  const durationMs = record3["durationMs"];
5875
- const command = text3(record3, "command");
5876
- const stdoutSha256 = text3(record3, "stdoutSha256");
5877
- const releasedBy = text3(record3, "releasedBy");
6279
+ const command = text4(record3, "command");
6280
+ const stdoutSha256 = text4(record3, "stdoutSha256");
6281
+ const releasedBy = text4(record3, "releasedBy");
5878
6282
  const reason = oneOf2(UNVERIFIED_REASONS, record3["reason"]);
5879
6283
  const failures = record3["failures"];
5880
6284
  const testCount = record3["testCount"];
@@ -5894,11 +6298,11 @@ var parseEvidence = (line2) => {
5894
6298
  if (!parsed.ok) return void 0;
5895
6299
  const record3 = parsed.value;
5896
6300
  const status = oneOf2(EVIDENCE_STATUSES, record3["status"]);
5897
- const task = text3(record3, "task");
5898
- const check2 = text3(record3, "check");
5899
- const kind = text3(record3, "kind");
5900
- const treeHash2 = text3(record3, "treeHash");
5901
- const at = text3(record3, "at");
6301
+ const task = text4(record3, "task");
6302
+ const check2 = text4(record3, "check");
6303
+ const kind = text4(record3, "kind");
6304
+ const treeHash2 = text4(record3, "treeHash");
6305
+ const at = text4(record3, "at");
5902
6306
  if (record3["schema"] !== EVIDENCE_SCHEMA || status === void 0) return void 0;
5903
6307
  if (task === void 0 || check2 === void 0 || kind === void 0 || treeHash2 === void 0 || at === void 0) {
5904
6308
  return void 0;
@@ -5915,7 +6319,7 @@ var parseEvidence = (line2) => {
5915
6319
  ...optional(record3)
5916
6320
  };
5917
6321
  };
5918
- var readLedger = (runtime, path) => {
6322
+ var readLedger2 = (runtime, path) => {
5919
6323
  const content = runtime.readText(path);
5920
6324
  if (!content.ok) return [];
5921
6325
  return content.value.split("\n").flatMap((line2) => {
@@ -6585,7 +6989,38 @@ var en_default = {
6585
6989
  "statusline.gate.red": "gate red",
6586
6990
  "statusline.gate.incomplete": "gate incomplete",
6587
6991
  "statusline.gate.did_not_run": "gate did not run",
6588
- "statusline.context": "ctx {percent}%"
6992
+ "statusline.context": "ctx {percent}%",
6993
+ "command.gain.summary": "What the guards did: saves next to false alarms and gate failures",
6994
+ "gain.title": "Finite Monkey, last {days, plural, one {# day} other {# days}} (from .finitemonkey/log.jsonl and the evidence ledgers):",
6995
+ "gain.row.blocks": "{count, plural, one {# block} other {# blocks}} by the guards",
6996
+ "gain.row.saves": "{count, plural, one {# block} other {# blocks}} followed by a green gate within a day",
6997
+ "gain.row.false_positives": "{count, plural, one {# false alarm} other {# false alarms}} (releases typed by the person, and false positives recorded)",
6998
+ "gain.row.gate_runs": "{count, plural, one {# gate run} other {# gate runs}}",
6999
+ "gain.row.gate_failed": "{count, plural, one {# gate run} other {# gate runs}} failed",
7000
+ "gain.row.gate_unverified": "{count, plural, one {# gate run} other {# gate runs}} ended not verified",
7001
+ "gain.row.gate_time": "{minutes, plural, one {# minute} other {# minutes}} spent in the gate",
7002
+ "gain.up": "up {delta} on the window before",
7003
+ "gain.down": "down {delta} on the window before",
7004
+ "gain.footer": "Counts come from local files only; a save is a block followed by a passing gate, not proof that the block caused it.",
7005
+ "critique.command.verify": "Check a review's must-fix findings: run each reproduction command on base and head in throwaway worktrees and keep only what fails on head alone",
7006
+ "critique.title": "Critique",
7007
+ "critique.group.must": "Must fix",
7008
+ "critique.group.should": "Should fix",
7009
+ "critique.group.nice": "Nice to have",
7010
+ "critique.group.already-there": "Already there (fails on base too)",
7011
+ "critique.group.not-reproduced": "Not reproduced",
7012
+ "critique.finding": "{id}. {what}. Problem: {problem}. Fix: {fix}. If not fixed: {risk}.",
7013
+ "critique.table.command": "reproduction",
7014
+ "critique.table.outcome": "outcome",
7015
+ "critique.outcome.head-only": "fails only on head: kept",
7016
+ "critique.outcome.already-there": "fails on both: already there",
7017
+ "critique.outcome.not-reproduced": "fails on neither: not reproduced",
7018
+ "critique.outcome.not-run": "not run",
7019
+ "critique.demoted": "Finding {id} was marked must fix without a reproduction command and expected result, so it counts as should fix.",
7020
+ "critique.decision": "Decision needed: finding {id} ({what}) already fails on the base branch and is an active risk.",
7021
+ "critique.verdict": "Verdict: {verdict}",
7022
+ "critique.error.invalid": "the findings file is not valid: {detail}",
7023
+ "critique.error.must_fix": "{count, plural, one {# must-fix finding reproduced on head only} other {# must-fix findings reproduced on head only}}"
6589
7024
  };
6590
7025
 
6591
7026
  // src/i18n/es.json
@@ -7243,7 +7678,38 @@ var es_default = {
7243
7678
  "statusline.gate.red": "compuerta roja",
7244
7679
  "statusline.gate.incomplete": "compuerta incompleta",
7245
7680
  "statusline.gate.did_not_run": "la compuerta no se ejecutó",
7246
- "statusline.context": "ctx {percent}%"
7681
+ "statusline.context": "ctx {percent}%",
7682
+ "command.gain.summary": "Lo que hicieron los bloqueos: aciertos junto a falsas alarmas y fallos de la compuerta",
7683
+ "gain.title": "Finite Monkey, {days, plural, one {último # día} other {últimos # días}} (de .finitemonkey/log.jsonl y los ledgers de evidencia):",
7684
+ "gain.row.blocks": "{count, plural, one {# bloqueo} other {# bloqueos}} de los guards",
7685
+ "gain.row.saves": "{count, plural, one {# bloqueo seguido} other {# bloqueos seguidos}} de una compuerta en verde en menos de un día",
7686
+ "gain.row.false_positives": "{count, plural, one {# falsa alarma} other {# falsas alarmas}} (liberaciones escritas por la persona y falsos positivos registrados)",
7687
+ "gain.row.gate_runs": "{count, plural, one {# ejecución de la compuerta} other {# ejecuciones de la compuerta}}",
7688
+ "gain.row.gate_failed": "{count, plural, one {# ejecución de la compuerta falló} other {# ejecuciones de la compuerta fallaron}}",
7689
+ "gain.row.gate_unverified": "{count, plural, one {# ejecución de la compuerta terminó} other {# ejecuciones de la compuerta terminaron}} sin verificar",
7690
+ "gain.row.gate_time": "{minutes, plural, one {# minuto} other {# minutos}} en la compuerta",
7691
+ "gain.up": "+{delta} respecto a la ventana anterior",
7692
+ "gain.down": "-{delta} respecto a la ventana anterior",
7693
+ "gain.footer": "Los conteos salen solo de archivos locales; un acierto es un bloqueo seguido de una compuerta en verde, no prueba de que el bloqueo lo causara.",
7694
+ "critique.command.verify": "Revisa los hallazgos obligatorios de una revisión: ejecuta cada comando de reproducción en base y en head en worktrees desechables y conserva solo lo que falla únicamente en head",
7695
+ "critique.title": "Crítica",
7696
+ "critique.group.must": "Corregir sí o sí",
7697
+ "critique.group.should": "Debería corregirse",
7698
+ "critique.group.nice": "Sería bueno",
7699
+ "critique.group.already-there": "Ya estaba (falla también en base)",
7700
+ "critique.group.not-reproduced": "No reproducido",
7701
+ "critique.finding": "{id}. {what}. Problema: {problem}. Arreglo: {fix}. Si no se corrige: {risk}.",
7702
+ "critique.table.command": "reproducción",
7703
+ "critique.table.outcome": "resultado",
7704
+ "critique.outcome.head-only": "falla solo en head: se mantiene",
7705
+ "critique.outcome.already-there": "falla en ambos: ya estaba",
7706
+ "critique.outcome.not-reproduced": "no falla en ninguno: no reproducido",
7707
+ "critique.outcome.not-run": "no se ejecutó",
7708
+ "critique.demoted": "El hallazgo {id} venía como obligatorio sin comando de reproducción ni resultado esperado, así que cuenta como debería corregirse.",
7709
+ "critique.decision": "Decisión necesaria: el hallazgo {id} ({what}) ya falla en la rama base y es un riesgo activo.",
7710
+ "critique.verdict": "Veredicto: {verdict}",
7711
+ "critique.error.invalid": "el archivo de hallazgos no es válido: {detail}",
7712
+ "critique.error.must_fix": "{count, plural, one {# hallazgo obligatorio reproducido solo en head} other {# hallazgos obligatorios reproducidos solo en head}}"
7247
7713
  };
7248
7714
 
7249
7715
  // src/i18n/pt-BR.json
@@ -7901,7 +8367,38 @@ var pt_BR_default = {
7901
8367
  "statusline.gate.red": "portão vermelho",
7902
8368
  "statusline.gate.incomplete": "portão incompleto",
7903
8369
  "statusline.gate.did_not_run": "portão não rodou",
7904
- "statusline.context": "ctx {percent}%"
8370
+ "statusline.context": "ctx {percent}%",
8371
+ "command.gain.summary": "O que as travas fizeram: acertos ao lado dos alarmes falsos e das falhas do portão",
8372
+ "gain.title": "Finite Monkey, últimos {days, plural, one {# dia} other {# dias}} (de .finitemonkey/log.jsonl e dos ledgers de evidência):",
8373
+ "gain.row.blocks": "{count, plural, one {# bloqueio} other {# bloqueios}} das travas",
8374
+ "gain.row.saves": "{count, plural, one {# bloqueio seguido} other {# bloqueios seguidos}} de portão verde em até um dia",
8375
+ "gain.row.false_positives": "{count, plural, one {# alarme falso} other {# alarmes falsos}} (liberações digitadas pela pessoa e falsos positivos registrados)",
8376
+ "gain.row.gate_runs": "{count, plural, one {# execução do portão} other {# execuções do portão}}",
8377
+ "gain.row.gate_failed": "{count, plural, one {# execução do portão reprovou} other {# execuções do portão reprovaram}}",
8378
+ "gain.row.gate_unverified": "{count, plural, one {# execução do portão terminou} other {# execuções do portão terminaram}} sem verificar",
8379
+ "gain.row.gate_time": "{minutes, plural, one {# minuto} other {# minutos}} no portão",
8380
+ "gain.up": "+{delta} em relação à janela anterior",
8381
+ "gain.down": "-{delta} em relação à janela anterior",
8382
+ "gain.footer": "As contagens vêm só de arquivos locais; um acerto é um bloqueio seguido de portão verde, não prova de que o bloqueio causou isso.",
8383
+ "critique.command.verify": "Confere os achados obrigatórios de uma revisão: roda cada comando de reprodução na base e no head em worktrees descartáveis e fica só com o que falha apenas no head",
8384
+ "critique.title": "Crítica",
8385
+ "critique.group.must": "Corrigir obrigatoriamente",
8386
+ "critique.group.should": "Deveria corrigir",
8387
+ "critique.group.nice": "Seria bom",
8388
+ "critique.group.already-there": "Já existia (falha na base também)",
8389
+ "critique.group.not-reproduced": "Não reproduzido",
8390
+ "critique.finding": "{id}. {what}. Problema: {problem}. Conserto: {fix}. Se não corrigir: {risk}.",
8391
+ "critique.table.command": "reprodução",
8392
+ "critique.table.outcome": "resultado",
8393
+ "critique.outcome.head-only": "falha só no head: mantido",
8394
+ "critique.outcome.already-there": "falha nos dois: já existia",
8395
+ "critique.outcome.not-reproduced": "não falha em nenhum: não reproduzido",
8396
+ "critique.outcome.not-run": "não rodou",
8397
+ "critique.demoted": "O achado {id} veio como obrigatório sem comando de reprodução e resultado esperado, então conta como deveria corrigir.",
8398
+ "critique.decision": "Decisão necessária: o achado {id} ({what}) já falha na branch base e é um risco ativo.",
8399
+ "critique.verdict": "Veredito: {verdict}",
8400
+ "critique.error.invalid": "o arquivo de achados não é válido: {detail}",
8401
+ "critique.error.must_fix": "{count, plural, one {# achado obrigatório reproduzido só no head} other {# achados obrigatórios reproduzidos só no head}}"
7905
8402
  };
7906
8403
 
7907
8404
  // src/i18n/review.json
@@ -8560,7 +9057,38 @@ var review_default = {
8560
9057
  "statusline.gate.red": "machine",
8561
9058
  "statusline.gate.incomplete": "machine",
8562
9059
  "statusline.gate.did_not_run": "machine",
8563
- "statusline.context": "machine"
9060
+ "statusline.context": "machine",
9061
+ "command.gain.summary": "machine",
9062
+ "gain.title": "machine",
9063
+ "gain.row.blocks": "machine",
9064
+ "gain.row.saves": "machine",
9065
+ "gain.row.false_positives": "machine",
9066
+ "gain.row.gate_runs": "machine",
9067
+ "gain.row.gate_failed": "machine",
9068
+ "gain.row.gate_unverified": "machine",
9069
+ "gain.row.gate_time": "machine",
9070
+ "gain.up": "machine",
9071
+ "gain.down": "machine",
9072
+ "gain.footer": "machine",
9073
+ "critique.command.verify": "machine",
9074
+ "critique.title": "machine",
9075
+ "critique.group.must": "machine",
9076
+ "critique.group.should": "machine",
9077
+ "critique.group.nice": "machine",
9078
+ "critique.group.already-there": "machine",
9079
+ "critique.group.not-reproduced": "machine",
9080
+ "critique.finding": "machine",
9081
+ "critique.table.command": "machine",
9082
+ "critique.table.outcome": "machine",
9083
+ "critique.outcome.head-only": "machine",
9084
+ "critique.outcome.already-there": "machine",
9085
+ "critique.outcome.not-reproduced": "machine",
9086
+ "critique.outcome.not-run": "machine",
9087
+ "critique.demoted": "machine",
9088
+ "critique.decision": "machine",
9089
+ "critique.verdict": "machine",
9090
+ "critique.error.invalid": "machine",
9091
+ "critique.error.must_fix": "machine"
8564
9092
  },
8565
9093
  es: {
8566
9094
  "command.init.summary": "machine",
@@ -9216,7 +9744,38 @@ var review_default = {
9216
9744
  "statusline.gate.red": "machine",
9217
9745
  "statusline.gate.incomplete": "machine",
9218
9746
  "statusline.gate.did_not_run": "machine",
9219
- "statusline.context": "machine"
9747
+ "statusline.context": "machine",
9748
+ "command.gain.summary": "machine",
9749
+ "gain.title": "machine",
9750
+ "gain.row.blocks": "machine",
9751
+ "gain.row.saves": "machine",
9752
+ "gain.row.false_positives": "machine",
9753
+ "gain.row.gate_runs": "machine",
9754
+ "gain.row.gate_failed": "machine",
9755
+ "gain.row.gate_unverified": "machine",
9756
+ "gain.row.gate_time": "machine",
9757
+ "gain.up": "machine",
9758
+ "gain.down": "machine",
9759
+ "gain.footer": "machine",
9760
+ "critique.command.verify": "machine",
9761
+ "critique.title": "machine",
9762
+ "critique.group.must": "machine",
9763
+ "critique.group.should": "machine",
9764
+ "critique.group.nice": "machine",
9765
+ "critique.group.already-there": "machine",
9766
+ "critique.group.not-reproduced": "machine",
9767
+ "critique.finding": "machine",
9768
+ "critique.table.command": "machine",
9769
+ "critique.table.outcome": "machine",
9770
+ "critique.outcome.head-only": "machine",
9771
+ "critique.outcome.already-there": "machine",
9772
+ "critique.outcome.not-reproduced": "machine",
9773
+ "critique.outcome.not-run": "machine",
9774
+ "critique.demoted": "machine",
9775
+ "critique.decision": "machine",
9776
+ "critique.verdict": "machine",
9777
+ "critique.error.invalid": "machine",
9778
+ "critique.error.must_fix": "machine"
9220
9779
  }
9221
9780
  };
9222
9781
 
@@ -9373,7 +9932,7 @@ var currentReport = (runtime, paths, sessionId, taskId) => {
9373
9932
  const { ctx, loaded } = ready.prepared;
9374
9933
  return buildReport({
9375
9934
  contract: ctx.contract,
9376
- ledger: readLedger(runtime, paths.evidence(ctx.contract.id)),
9935
+ ledger: readLedger2(runtime, paths.evidence(ctx.contract.id)),
9377
9936
  treeHash: ctx.treeHash,
9378
9937
  base: ctx.base,
9379
9938
  source: loaded.source,
@@ -9399,22 +9958,22 @@ var saveSnapshot = (runtime, paths, t, sessionId) => {
9399
9958
  const snapshot2 = snapshotOf(t, currentReport(runtime, paths, sessionId), grants, new Date(now).toISOString());
9400
9959
  return writeState(runtime, paths, { ...readState(runtime, paths), snapshot: snapshot2 }).ok;
9401
9960
  };
9402
- var text4 = (snapshot2, key) => {
9961
+ var text5 = (snapshot2, key) => {
9403
9962
  const value = snapshot2[key];
9404
9963
  return typeof value === "string" ? value : "";
9405
9964
  };
9406
9965
  var snapshotFacts = (t, snapshot2) => {
9407
- if (snapshot2 === void 0 || text4(snapshot2, "at") === "") return void 0;
9408
- const task = text4(snapshot2, "task");
9966
+ if (snapshot2 === void 0 || text5(snapshot2, "at") === "") return void 0;
9967
+ const task = text5(snapshot2, "task");
9409
9968
  return t(task === "" || task === "implicit" ? "gate.context.state_implicit" : "gate.context.state", {
9410
- at: text4(snapshot2, "at"),
9969
+ at: text5(snapshot2, "at"),
9411
9970
  task,
9412
- goal: text4(snapshot2, "goal"),
9413
- tree: text4(snapshot2, "tree"),
9414
- passed: text4(snapshot2, "passed"),
9415
- failed: text4(snapshot2, "failed"),
9416
- unverified: text4(snapshot2, "unverified"),
9417
- grants: text4(snapshot2, "grants")
9971
+ goal: text5(snapshot2, "goal"),
9972
+ tree: text5(snapshot2, "tree"),
9973
+ passed: text5(snapshot2, "passed"),
9974
+ failed: text5(snapshot2, "failed"),
9975
+ unverified: text5(snapshot2, "unverified"),
9976
+ grants: text5(snapshot2, "grants")
9418
9977
  });
9419
9978
  };
9420
9979
 
@@ -9506,10 +10065,10 @@ var pairs = (record3) => {
9506
10065
  return names.filter((name) => typeof name === "string").map((name) => `${task}\0${name}`);
9507
10066
  };
9508
10067
  var callersRate = (runtime, paths) => {
9509
- const text8 = runtime.readText(paths.log);
10068
+ const text9 = runtime.readText(paths.log);
9510
10069
  const flagged = /* @__PURE__ */ new Set();
9511
10070
  const released2 = /* @__PURE__ */ new Set();
9512
- for (const line2 of text8.ok ? text8.value.split("\n") : []) {
10071
+ for (const line2 of text9.ok ? text9.value.split("\n") : []) {
9513
10072
  const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
9514
10073
  if (parsed === void 0 || !parsed.ok || !isRecord(parsed.value)) continue;
9515
10074
  const event = parsed.value["event"];
@@ -9527,14 +10086,14 @@ var callersRate = (runtime, paths) => {
9527
10086
  };
9528
10087
 
9529
10088
  // src/gate/callers/scan.ts
9530
- import { join as join16 } from "node:path";
10089
+ import { join as join18 } from "node:path";
9531
10090
 
9532
10091
  // src/gate/callers/imports.ts
9533
10092
  import { posix as posix2 } from "node:path";
9534
10093
  var JS_SPECIFIER = /(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["']([^"']+)["']/g;
9535
10094
  var JS_EXTENSION = /\.(?:[cm]?[jt]sx?)$/;
9536
10095
  var stripModule = (path) => path.replace(JS_EXTENSION, "").replace(/\/index$/, "");
9537
- var jsSpecifiers = (text8) => [...text8.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
10096
+ var jsSpecifiers = (text9) => [...text9.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
9538
10097
  var jsImports = (callerPath, callerText, origin) => {
9539
10098
  const target2 = stripModule(origin);
9540
10099
  return jsSpecifiers(callerText).some((specifier) => {
@@ -9636,9 +10195,9 @@ var languageOf = (path) => {
9636
10195
  const extension = extname(path).toLowerCase();
9637
10196
  return LANGUAGES2.find((language) => language.extensions.includes(extension));
9638
10197
  };
9639
- var definitionsIn = (language, text8) => {
10198
+ var definitionsIn = (language, text9) => {
9640
10199
  const names = /* @__PURE__ */ new Set();
9641
- for (const line2 of text8.split(/\r?\n/)) {
10200
+ for (const line2 of text9.split(/\r?\n/)) {
9642
10201
  for (const pattern of language.definitions) {
9643
10202
  const name = pattern.exec(line2)?.[1];
9644
10203
  if (name !== void 0 && !KEYWORDS.has(name)) names.add(name);
@@ -9651,8 +10210,8 @@ var definesName = (language, line2, name) => language.definitions.some((pattern)
9651
10210
  // src/gate/callers/scan.ts
9652
10211
  var MAX_SYMBOLS = 200;
9653
10212
  var current = (runtime, root, path) => {
9654
- const text8 = runtime.readText(toAbsolutePath(join16(root, path)));
9655
- return text8.ok ? text8.value : "";
10213
+ const text9 = runtime.readText(toAbsolutePath(join18(root, path)));
10214
+ return text9.ok ? text9.value : "";
9656
10215
  };
9657
10216
  var keyOf = (language, name) => `${language.id}\0${name}`;
9658
10217
  var redefinedNames = (runtime, git2, changes) => {
@@ -9693,7 +10252,7 @@ var grep = (git2, names) => {
9693
10252
  git2.out(["grep", "-n", "-z", "-w", "-I", "--untracked", "--no-color", ...patterns, "--", ".", EXCLUDE_FM]) ?? ""
9694
10253
  );
9695
10254
  };
9696
- var mentions = (text8, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text8);
10255
+ var mentions = (text9, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text9);
9697
10256
  var callSites = (runtime, git2, symbols) => {
9698
10257
  const hits = grep(git2, [...new Set(symbols.map((symbol) => symbol.name))]);
9699
10258
  const texts2 = /* @__PURE__ */ new Map();
@@ -9768,31 +10327,31 @@ var ZERO = [
9768
10327
  /^ℹ tests 0$/m,
9769
10328
  /No tests found/
9770
10329
  ];
9771
- var pyTotal = (text8) => {
9772
- const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(text8)?.[1];
10330
+ var pyTotal = (text9) => {
10331
+ const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(text9)?.[1];
9773
10332
  if (line2 === void 0) return void 0;
9774
10333
  return [...line2.matchAll(/(\d+) (passed|failed|error|errors|skipped|xfailed|xpassed)/g)].reduce(
9775
10334
  (sum, match) => sum + Number(match[1] ?? "0"),
9776
10335
  0
9777
10336
  );
9778
10337
  };
9779
- var counted = (text8) => {
10338
+ var counted = (text9) => {
9780
10339
  for (const counter of COUNTERS) {
9781
- const total = counter.total.exec(text8)?.[1];
10340
+ const total = counter.total.exec(text9)?.[1];
9782
10341
  if (total === void 0) continue;
9783
- const failed4 = counter.failed?.exec(text8)?.[1];
10342
+ const failed4 = counter.failed?.exec(text9)?.[1];
9784
10343
  return { total: Number(total), ...failed4 === void 0 ? {} : { failed: Number(failed4) } };
9785
10344
  }
9786
- const py = pyTotal(text8);
10345
+ const py = pyTotal(text9);
9787
10346
  return py === void 0 ? {} : { total: py };
9788
10347
  };
9789
10348
  var parseTestSummary = (output) => {
9790
- const text8 = output.replace(ANSI2, "");
10349
+ const text9 = output.replace(ANSI2, "");
9791
10350
  const failures = [
9792
- ...new Set(FAILURE_LINES.flatMap((pattern) => [...text8.matchAll(pattern)].map((match) => (match[1] ?? "").trim())))
10351
+ ...new Set(FAILURE_LINES.flatMap((pattern) => [...text9.matchAll(pattern)].map((match) => (match[1] ?? "").trim())))
9793
10352
  ].filter((name) => name !== "" && !/^failing tests:?$/i.test(name));
9794
- if (ZERO.some((pattern) => pattern.test(text8))) return { total: 0, failures };
9795
- return { ...counted(text8), failures };
10353
+ if (ZERO.some((pattern) => pattern.test(text9))) return { total: 0, failures };
10354
+ return { ...counted(text9), failures };
9796
10355
  };
9797
10356
 
9798
10357
  // src/gate/checks/command.ts
@@ -9803,8 +10362,8 @@ var REWRITERS = /^\s*(rtk)\b/;
9803
10362
  var TRIVIAL = /* @__PURE__ */ new Set(["echo", "true", ":", "printf", "exit", "sleep", "cat", "yes", "pwd", "ls", "date", "env"]);
9804
10363
  var TAIL = 160;
9805
10364
  var MAX_FAILURES2 = 5;
9806
- var tail = (text8) => {
9807
- const lines3 = text8.split(/\r?\n/).filter((line2) => line2.trim() !== "");
10365
+ var tail = (text9) => {
10366
+ const lines3 = text9.split(/\r?\n/).filter((line2) => line2.trim() !== "");
9808
10367
  const last = lines3[lines3.length - 1]?.trim() ?? "";
9809
10368
  return last.length > TAIL ? `${last.slice(0, TAIL)}…` : last;
9810
10369
  };
@@ -9924,10 +10483,10 @@ var gitCheck = (check2, ctx) => {
9924
10483
  };
9925
10484
 
9926
10485
  // src/gate/checks/look.ts
9927
- import { join as join19 } from "node:path";
10486
+ import { join as join21 } from "node:path";
9928
10487
 
9929
10488
  // src/look/config.ts
9930
- import { join as join17 } from "node:path";
10489
+ import { join as join19 } from "node:path";
9931
10490
 
9932
10491
  // src/look/types.ts
9933
10492
  var LOOK_RULES = [
@@ -9962,7 +10521,7 @@ var DEFAULT_SCREENS = [
9962
10521
  "**/*.sass",
9963
10522
  "**/*.less"
9964
10523
  ];
9965
- var text5 = (value) => typeof value === "string" && value !== "" ? value : void 0;
10524
+ var text6 = (value) => typeof value === "string" && value !== "" ? value : void 0;
9966
10525
  var number = (value) => {
9967
10526
  const parsed = typeof value === "string" ? Number(value) : Number.NaN;
9968
10527
  return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
@@ -9979,8 +10538,8 @@ var parseLookConfig = (source) => {
9979
10538
  const top = configMap(tree);
9980
10539
  return {
9981
10540
  port: number(top["port"]),
9982
- start: text5(top["start"]),
9983
- host: text5(top["host"]) ?? "127.0.0.1",
10541
+ start: text6(top["start"]),
10542
+ host: text6(top["host"]) ?? "127.0.0.1",
9984
10543
  routes: configList(tree, "routes").length === 0 ? ["/"] : configList(tree, "routes"),
9985
10544
  mocks: configList(tree, "mocks"),
9986
10545
  screens: screens === void 0 ? DEFAULT_SCREENS : configList(tree, "screens"),
@@ -9989,18 +10548,18 @@ var parseLookConfig = (source) => {
9989
10548
  };
9990
10549
  };
9991
10550
  var loadLookConfig = (runtime, root) => {
9992
- const source = runtime.readText(toAbsolutePath(join17(root, LOOK_CONFIG)));
10551
+ const source = runtime.readText(toAbsolutePath(join19(root, LOOK_CONFIG)));
9993
10552
  return source.ok ? parseLookConfig(source.value) : void 0;
9994
10553
  };
9995
10554
  var withMock = (route, mock) => `${route}${route.includes("?") ? "&" : "?"}mock=${encodeURIComponent(mock)}`;
9996
10555
  var lookRoutes = (config) => config.routes.flatMap((route) => [route, ...config.mocks.map((mock) => withMock(route, mock))]);
9997
10556
 
9998
10557
  // src/look/paths.ts
9999
- import { join as join18, relative as relative5 } from "node:path";
10558
+ import { join as join20, relative as relative5 } from "node:path";
10000
10559
  var LOOK_DIR = "look";
10001
10560
  var SUMMARY_FILE = "summary.json";
10002
- var lookDir = (paths, treeHash2) => toAbsolutePath(join18(paths.dir, LOOK_DIR, treeHash2));
10003
- var summaryPath = (paths, treeHash2) => toAbsolutePath(join18(lookDir(paths, treeHash2), SUMMARY_FILE));
10561
+ var lookDir = (paths, treeHash2) => toAbsolutePath(join20(paths.dir, LOOK_DIR, treeHash2));
10562
+ var summaryPath = (paths, treeHash2) => toAbsolutePath(join20(lookDir(paths, treeHash2), SUMMARY_FILE));
10004
10563
  var projectRelative = (paths, absolute) => toPosixPath(relative5(paths.root, absolute));
10005
10564
 
10006
10565
  // src/gate/checks/look.ts
@@ -10018,8 +10577,8 @@ var findingNote = (value) => {
10018
10577
  ];
10019
10578
  };
10020
10579
  var readSummary = (ctx) => {
10021
- const text8 = ctx.runtime.readText(summaryPath(ctx.paths, ctx.treeHash));
10022
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
10580
+ const text9 = ctx.runtime.readText(summaryPath(ctx.paths, ctx.treeHash));
10581
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
10023
10582
  if (!parsed?.ok) return void 0;
10024
10583
  const { at, captures, findings, ignoredAllows } = parsed.value;
10025
10584
  if (typeof at !== "string" || !Array.isArray(captures) || !Array.isArray(findings)) return void 0;
@@ -10035,7 +10594,7 @@ var readSummary = (ctx) => {
10035
10594
  var lastScreenEdit = (ctx) => {
10036
10595
  const screens = loadLookConfig(ctx.runtime, ctx.paths.root)?.screens ?? DEFAULT_SCREENS;
10037
10596
  return ctx.changes.filter((change) => change.status !== "D" && matchesAny(change.path, screens)).reduce((latest, change) => {
10038
- const stat = ctx.runtime.stat(toAbsolutePath(join19(ctx.paths.root, change.path)));
10597
+ const stat = ctx.runtime.stat(toAbsolutePath(join21(ctx.paths.root, change.path)));
10039
10598
  return Math.max(latest, stat?.mtimeMs ?? 0);
10040
10599
  }, 0);
10041
10600
  };
@@ -10082,14 +10641,14 @@ var manualCheck = (check2, ctx) => {
10082
10641
  };
10083
10642
 
10084
10643
  // src/gate/checks/placeholders.ts
10085
- import { join as join20 } from "node:path";
10644
+ import { join as join22 } from "node:path";
10086
10645
  var MAX_LISTED3 = 10;
10087
10646
  var SKIP_FILES = /\.(md|mdx|txt|rst|lock|snap|svg|png|jpg|gif|json)$|(^|\/)(CHANGELOG|LICENSE)/i;
10088
10647
  var PLACEHOLDER = /\b(TODO|FIXME|XXX)\b|not implemented|NotImplementedError|unimplemented!|todo!\(|lorem ipsum/i;
10089
10648
  var findPlaceholders = (lines3) => lines3.filter((line2) => !isTestFile(line2.path) && !SKIP_FILES.test(line2.path) && PLACEHOLDER.test(line2.text));
10090
10649
  var untrackedAdded = (ctx) => ctx.changes.filter((change) => change.status === "A" && !SKIP_FILES.test(change.path)).flatMap((change) => {
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 })) : [];
10650
+ const text9 = ctx.runtime.readText(toAbsolutePath(join22(ctx.paths.root, change.path)));
10651
+ return text9.ok ? text9.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
10093
10652
  });
10094
10653
  var placeholdersCheck = (_check, ctx) => {
10095
10654
  const tracked = addedLines(ctx.git, ctx.base);
@@ -10148,9 +10707,9 @@ var prOutcome = (check2, ctx) => {
10148
10707
 
10149
10708
  // src/prose/preset.ts
10150
10709
  var PRESET_LINE = /^preset:\s*([A-Za-z0-9_-]+)\s*$/m;
10151
- var presetFromConfig = (text8) => parsePreset(PRESET_LINE.exec(text8)?.[1]) ?? "neutral";
10710
+ var presetFromConfig = (text9) => parsePreset(PRESET_LINE.exec(text9)?.[1]) ?? "neutral";
10152
10711
  var projectPreset = (runtime, root) => {
10153
- const config = runtime.readText(toAbsolutePath(CONFIG_FILE, root));
10712
+ const config = runtime.readText(toAbsolutePath(CONFIG_FILE2, root));
10154
10713
  return config.ok ? presetFromConfig(config.value) : "neutral";
10155
10714
  };
10156
10715
 
@@ -10325,16 +10884,16 @@ var ANY_MARKER = /<!--\s*finitemonkey:prose-allow(?:-file)?\s*-->/g;
10325
10884
  var INLINE_CODE = /`[^`]*`/g;
10326
10885
  var FENCE2 = /^\s*(```|~~~)/;
10327
10886
  var outsideCode = (line2) => line2.replace(INLINE_CODE, "");
10328
- var unfencedLines = (text8) => {
10887
+ var unfencedLines = (text9) => {
10329
10888
  let fenced = false;
10330
- return text8.split("\n").filter((line2) => {
10889
+ return text9.split("\n").filter((line2) => {
10331
10890
  if (FENCE2.test(line2)) fenced = !fenced;
10332
10891
  return !fenced && !FENCE2.test(line2);
10333
10892
  });
10334
10893
  };
10335
- var allowsWholeFile = (text8) => unfencedLines(text8).some((line2) => FILE_MARKER.test(outsideCode(line2)));
10894
+ var allowsWholeFile = (text9) => unfencedLines(text9).some((line2) => FILE_MARKER.test(outsideCode(line2)));
10336
10895
  var allowsLine = (line2) => LINE_MARKER.test(outsideCode(line2));
10337
- var markerCount = (text8) => text8.match(ANY_MARKER)?.length ?? 0;
10896
+ var markerCount = (text9) => text9.match(ANY_MARKER)?.length ?? 0;
10338
10897
  var isMarkerOnlyLine = (line2) => allowsLine(line2) && outsideCode(line2).replace(LINE_MARKER, "").trim() === "";
10339
10898
 
10340
10899
  // src/prose/extract.ts
@@ -10354,7 +10913,7 @@ var MASKS = [
10354
10913
  [/<[^>]+>/g, ""],
10355
10914
  [/\*\*|__/g, ""]
10356
10915
  ];
10357
- var maskInline = (line2) => MASKS.reduce((text8, [pattern, replacement]) => text8.replace(pattern, replacement), line2);
10916
+ var maskInline = (line2) => MASKS.reduce((text9, [pattern, replacement]) => text9.replace(pattern, replacement), line2);
10358
10917
  var BlockReader = class {
10359
10918
  blocks = [];
10360
10919
  current;
@@ -10445,8 +11004,8 @@ var splitBlock = (block4) => {
10445
11004
  return joined.split(SENTENCE_END).flatMap((sentence) => {
10446
11005
  const at = joined.indexOf(sentence, offset);
10447
11006
  offset = at + sentence.length;
10448
- const text8 = sentence.trim();
10449
- return text8 === "" ? [] : [{ text: text8, line: lineAt(at) }];
11007
+ const text9 = sentence.trim();
11008
+ return text9 === "" ? [] : [{ text: text9, line: lineAt(at) }];
10450
11009
  });
10451
11010
  };
10452
11011
  var unitsOf = (blocks) => {
@@ -10467,9 +11026,9 @@ var unitsOf = (blocks) => {
10467
11026
  }));
10468
11027
  });
10469
11028
  };
10470
- var extractProse = (text8) => {
10471
- if (allowsWholeFile(text8)) return { units: [], allowed: true };
10472
- const lines3 = text8.split("\n").map(withoutCarriageReturn);
11029
+ var extractProse = (text9) => {
11030
+ if (allowsWholeFile(text9)) return { units: [], allowed: true };
11031
+ const lines3 = text9.split("\n").map(withoutCarriageReturn);
10473
11032
  return { units: unitsOf(readBlocks(lines3)), allowed: false };
10474
11033
  };
10475
11034
 
@@ -10594,7 +11153,7 @@ var CONNECTIVES = new Set(
10594
11153
  " "
10595
11154
  )
10596
11155
  );
10597
- var wordsOf = (text8) => text8.split(/\s+/).filter((word2) => WORD.test(word2));
11156
+ var wordsOf = (text9) => text9.split(/\s+/).filter((word2) => WORD.test(word2));
10598
11157
  var hit = (rule, units, index, weight) => {
10599
11158
  const unit = units[index];
10600
11159
  return { rule, unit: index, line: unit?.line ?? 0, weight, excerpt: unit?.text ?? "" };
@@ -10722,8 +11281,8 @@ var mergeSpans = (hits, windows) => {
10722
11281
  return ranges.map(([from, to]) => spanOf2(hits, from, to)).filter((span) => span.hits.length > 0);
10723
11282
  };
10724
11283
  var byPosition = (left, right) => left.unit - right.unit || left.line - right.line;
10725
- var scoreProse = (text8, options = {}) => {
10726
- const { units, allowed: allowed2 } = extractProse(text8);
11284
+ var scoreProse = (text9, options = {}) => {
11285
+ const { units, allowed: allowed2 } = extractProse(text9);
10727
11286
  const size = options.window ?? DENSITY_WINDOW;
10728
11287
  const threshold2 = options.threshold ?? DENSITY_THRESHOLD;
10729
11288
  const catalog = options.context === "record" ? [...phraseRules(), ...recordRules()] : phraseRules();
@@ -10755,9 +11314,9 @@ var changedMarkdown = (runtime, root) => {
10755
11314
  var scoreFiles = (runtime, root, files, forced) => {
10756
11315
  const reports = [];
10757
11316
  for (const file of files) {
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) }) });
11317
+ const text9 = runtime.readText(toAbsolutePath(file, root));
11318
+ if (!text9.ok) return reject(`${file}: ${text9.error}`);
11319
+ reports.push({ file: toPosixPath(file), report: scoreProse(text9.value, { context: forced ?? contextOf(file) }) });
10761
11320
  }
10762
11321
  return accept(reports);
10763
11322
  };
@@ -10856,8 +11415,8 @@ var parseAppCheck = (stdout) => {
10856
11415
  var PROBE_MARGIN_MS = 15e3;
10857
11416
  var MIN_REMAINING_MS2 = 5e3;
10858
11417
  var TAIL2 = 160;
10859
- var lastLine = (text8) => {
10860
- const rows = text8.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
11418
+ var lastLine = (text9) => {
11419
+ const rows = text9.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
10861
11420
  const line2 = (rows.findLast((row) => /error/i.test(row)) ?? rows.at(-1))?.trim() ?? "";
10862
11421
  return line2.length > TAIL2 ? `${line2.slice(0, TAIL2)}…` : line2;
10863
11422
  };
@@ -10935,14 +11494,14 @@ ${run4.stderr}`)) })]);
10935
11494
  };
10936
11495
 
10937
11496
  // src/secrets/baseline.ts
10938
- import { join as join21 } from "node:path";
11497
+ import { join as join23 } from "node:path";
10939
11498
  var BASELINE_FILE2 = ".finitemonkey/findings/baseline.json";
10940
11499
  var BASELINE_SCHEMA = "finitemonkey/secrets-baseline@1";
10941
11500
  var BASELINE_STATES = ["aceito", "falso-positivo"];
10942
11501
  var EXCEPTION_MAX_DAYS = 90;
10943
11502
  var EMPTY_BASELINE = { schema: BASELINE_SCHEMA, entries: [] };
10944
- var baselinePath = (root) => toAbsolutePath(join21(root, BASELINE_FILE2));
10945
- var text6 = (record3, key) => {
11503
+ var baselinePath = (root) => toAbsolutePath(join23(root, BASELINE_FILE2));
11504
+ var text7 = (record3, key) => {
10946
11505
  const value = record3[key];
10947
11506
  return typeof value === "string" && value !== "" ? value : void 0;
10948
11507
  };
@@ -10950,7 +11509,7 @@ var stateOf = (value) => BASELINE_STATES.find((state) => state === value);
10950
11509
  var parseEntry2 = (value) => {
10951
11510
  if (!isRecord(value)) return void 0;
10952
11511
  const fields = ["id", "rule", "file", "fingerprint", "owner", "reason", "acceptedAt", "expiresAt"];
10953
- const [id, rule, file, fingerprint, owner, reason, acceptedAt, expiresAt] = fields.map((key) => text6(value, key));
11512
+ const [id, rule, file, fingerprint, owner, reason, acceptedAt, expiresAt] = fields.map((key) => text7(value, key));
10954
11513
  const state = stateOf(value["state"]);
10955
11514
  const line2 = value["line"];
10956
11515
  if (id === void 0 || rule === void 0 || file === void 0 || fingerprint === void 0) return void 0;
@@ -11001,14 +11560,14 @@ var withoutFixed = (baseline, fixed) => {
11001
11560
  var acceptedFingerprints = (baseline, now) => new Set(baseline.entries.filter((entry2) => entry2.expiresAt >= now).map((entry2) => entry2.fingerprint));
11002
11561
 
11003
11562
  // src/secrets/incidents.ts
11004
- import { join as join22 } from "node:path";
11563
+ import { join as join24 } from "node:path";
11005
11564
  var INCIDENTS_DIR = ".finitemonkey/incidents";
11006
11565
  var INCIDENT_STATES = ["aberto", "exposicao-local", "rotacionado", "fechado"];
11007
11566
  var INCIDENT_SOURCES = ["tool-output", "prompt", "history", "scan"];
11008
11567
  var ID = /^inc-[0-9a-f]{12}$/;
11009
11568
  var incidentId = (rule, fingerprint) => `inc-${sha256Hex(`${rule}\0${fingerprint}`).slice(0, 12)}`;
11010
- var incidentsDir = (root) => toAbsolutePath(join22(root, INCIDENTS_DIR));
11011
- var incidentFile = (root, id) => toAbsolutePath(join22(incidentsDir(root), `${id}.json`));
11569
+ var incidentsDir = (root) => toAbsolutePath(join24(root, INCIDENTS_DIR));
11570
+ var incidentFile = (root, id) => toAbsolutePath(join24(incidentsDir(root), `${id}.json`));
11012
11571
  var pick3 = (options, value) => options.find((option) => option === value);
11013
11572
  var str2 = (record3, key) => {
11014
11573
  const value = record3[key];
@@ -11063,7 +11622,7 @@ var listIncidents = (runtime, root) => {
11063
11622
  return names.value.filter((name) => name.endsWith(".json")).map((name) => readIncident(runtime, root, name.slice(0, -".json".length))).flatMap((parsed) => parsed.ok ? [parsed.value] : []).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
11064
11623
  };
11065
11624
  var saveIncident = (runtime, root, incident) => {
11066
- const ignore = runtime.writeText(toAbsolutePath(join22(incidentsDir(root), ".gitignore")), "*\n");
11625
+ const ignore = runtime.writeText(toAbsolutePath(join24(incidentsDir(root), ".gitignore")), "*\n");
11067
11626
  if (!ignore.ok) return ignore;
11068
11627
  return runtime.writeText(incidentFile(root, incident.id), `${JSON.stringify(incident, null, 2)}
11069
11628
  `);
@@ -11110,7 +11669,7 @@ var moveIncident = (incident, to, now, by) => ({
11110
11669
  var isRotated = (incident) => incident.state === "rotacionado" || incident.state === "fechado";
11111
11670
 
11112
11671
  // src/secrets/scan.ts
11113
- import { join as join23, relative as relative6 } from "node:path";
11672
+ import { join as join25, relative as relative6 } from "node:path";
11114
11673
 
11115
11674
  // src/secrets/located.ts
11116
11675
  var findingId = (ruleId, file, fingerprint) => `fm-${sha256Hex(`${ruleId}\0${file}\0${fingerprint}`).slice(0, 16)}`;
@@ -11200,7 +11759,7 @@ var diffScanner = () => {
11200
11759
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
11201
11760
  var SKIPPED = /(?:^|\/)(?:\.git|node_modules|\.finitemonkey)\//;
11202
11761
  var DIFF_FLAGS = ["-U0", "--no-color", "--no-ext-diff", "--no-renames"];
11203
- var binary = (text8) => text8.slice(0, 8e3).includes("\0");
11762
+ var binary = (text9) => text9.slice(0, 8e3).includes("\0");
11204
11763
  var listed2 = (runtime, root, target2) => {
11205
11764
  const git2 = runtime.git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", target2], root);
11206
11765
  if (git2.ok)
@@ -11210,12 +11769,12 @@ var listed2 = (runtime, root, target2) => {
11210
11769
  const base = toAbsolutePath(target2, root);
11211
11770
  const walked = runtime.walkFiles(base);
11212
11771
  if (!walked.ok) return walked;
11213
- return accept(walked.value.map((name) => toPosixPath(relative6(root, join23(base, toPosixPath(name))))));
11772
+ return accept(walked.value.map((name) => toPosixPath(relative6(root, join25(base, toPosixPath(name))))));
11214
11773
  };
11215
11774
  var scanFile = (runtime, root, file) => {
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));
11775
+ const text9 = runtime.readText(toAbsolutePath(file, root));
11776
+ if (!text9.ok || text9.value.length > MAX_FILE_BYTES || binary(text9.value)) return [];
11777
+ return scanText(text9.value, { path: file }).map((finding4) => locate(finding4, file, finding4.line));
11219
11778
  };
11220
11779
  var scanPath = (runtime, root, target2) => {
11221
11780
  const files = listed2(runtime, root, target2);
@@ -11280,7 +11839,7 @@ var secretsCheck = (_check, ctx) => {
11280
11839
  };
11281
11840
 
11282
11841
  // src/gate/checks/tests.ts
11283
- import { join as join24 } from "node:path";
11842
+ import { join as join26 } from "node:path";
11284
11843
 
11285
11844
  // src/guards/tests/tree.ts
11286
11845
  var outOfScope = (input, change) => input.testsProtected && input.scopePaths.length > 0 && change.status !== "A" && isTestFile(change.path) && !matchesAny(change.path, input.scopePaths) ? [{ kind: "out_of_scope", path: change.path }] : [];
@@ -11312,8 +11871,8 @@ var treeViolations = (ctx) => {
11312
11871
  changes: relevant,
11313
11872
  before: (path) => fileAt(ctx.git, ctx.base, path),
11314
11873
  after: (path) => {
11315
- const text8 = ctx.runtime.readText(toAbsolutePath(join24(ctx.paths.root, path)));
11316
- return text8.ok ? text8.value : void 0;
11874
+ const text9 = ctx.runtime.readText(toAbsolutePath(join26(ctx.paths.root, path)));
11875
+ return text9.ok ? text9.value : void 0;
11317
11876
  },
11318
11877
  acceptedConfigs: ctx.trust.configs,
11319
11878
  scopePaths: ctx.contract.scope.paths,
@@ -11333,7 +11892,7 @@ var testsCheck = (check2, ctx) => {
11333
11892
  };
11334
11893
 
11335
11894
  // src/git/tree.ts
11336
- import { join as join25 } from "node:path";
11895
+ import { join as join27 } from "node:path";
11337
11896
 
11338
11897
  // src/secrets/guards/paths.ts
11339
11898
  var SAFE_ENV = /^\.env\.(?:example|sample|template|dist|defaults|schema)$/i;
@@ -11372,7 +11931,7 @@ var dropSensitive = (runtime, repo, env, options) => {
11372
11931
  return removed.ok ? accept(void 0) : removed;
11373
11932
  };
11374
11933
  var withTempIndex = (runtime, repo, options, work) => {
11375
- const path = toAbsolutePath(join25(repo.gitDir, `finitemonkey-${options.stamp}.index`));
11934
+ const path = toAbsolutePath(join27(repo.gitDir, `finitemonkey-${options.stamp}.index`));
11376
11935
  const env = { GIT_INDEX_FILE: path, GIT_OPTIONAL_LOCKS: "0" };
11377
11936
  try {
11378
11937
  return work(env);
@@ -11477,10 +12036,10 @@ var bodyLine = (draft, line2) => {
11477
12036
  else if (line2.startsWith("+")) draft.current.added.push(line2.slice(1));
11478
12037
  };
11479
12038
  var DIFF_GIT = /^diff --git a\/(.+) b\/(.+)$/;
11480
- var parseUnifiedDiff = (text8) => {
12039
+ var parseUnifiedDiff = (text9) => {
11481
12040
  const out = [];
11482
12041
  let draft;
11483
- for (const raw of text8.split("\n")) {
12042
+ for (const raw of text9.split("\n")) {
11484
12043
  const line2 = withoutCarriageReturn(raw);
11485
12044
  const start = DIFF_GIT.exec(line2);
11486
12045
  if (start !== null) {
@@ -11579,59 +12138,15 @@ var mergeCommitsSince = (runtime, repo, since) => {
11579
12138
  };
11580
12139
  var mergeInProgress = (runtime, repo) => revParse(runtime, repo, "MERGE_HEAD") !== void 0;
11581
12140
 
11582
- // src/git/facts.ts
11583
- var memo2 = (compute) => {
11584
- let cached3;
11585
- return () => {
11586
- cached3 ??= { value: compute() };
11587
- return cached3.value;
11588
- };
11589
- };
11590
- var memoBy = (compute) => {
11591
- const cache = /* @__PURE__ */ new Map();
11592
- return (key) => {
11593
- const hit3 = cache.get(key);
11594
- if (hit3 !== void 0) return hit3.value;
11595
- const value = compute(key);
11596
- cache.set(key, { value });
11597
- return value;
11598
- };
11599
- };
11600
- var STATE_PREFIX = ".finitemonkey/";
11601
- var PORCELAIN_FIELDS = { ordinary: 8, renamed: 9, unmerged: 10 };
11602
- var entryOf2 = (kind, xy, path) => ({
11603
- path,
11604
- kind,
11605
- staged: kind !== "untracked" && !xy.startsWith("."),
11606
- unstaged: kind === "untracked" || xy.charAt(1) !== "."
11607
- });
11608
- var afterFields = (record3, count4) => record3.split(" ").slice(count4).join(" ");
11609
- var parsePorcelainV2 = (stdout) => {
11610
- const records = stdout.split("\0");
11611
- const out = [];
11612
- for (let index = 0; index < records.length; index += 1) {
11613
- const record3 = records[index] ?? "";
11614
- const xy = record3.split(" ")[1] ?? "..";
11615
- if (record3.startsWith("1 ")) out.push(entryOf2("changed", xy, afterFields(record3, PORCELAIN_FIELDS.ordinary)));
11616
- else if (record3.startsWith("2 ")) {
11617
- out.push(entryOf2("changed", xy, afterFields(record3, PORCELAIN_FIELDS.renamed)));
11618
- index += 1;
11619
- } else if (record3.startsWith("u ")) {
11620
- out.push(entryOf2("unmerged", xy, afterFields(record3, PORCELAIN_FIELDS.unmerged)));
11621
- } else if (record3.startsWith("? ")) out.push(entryOf2("untracked", "??", record3.slice(2)));
11622
- }
11623
- return out.filter((entry2) => entry2.path !== "" && !entry2.path.startsWith(STATE_PREFIX));
11624
- };
11625
-
11626
12141
  // src/git/merge/dropped.ts
11627
- import { join as join26 } from "node:path";
12142
+ import { join as join28 } from "node:path";
11628
12143
 
11629
12144
  // src/git/merge/lines.ts
11630
12145
  var normalizeLine = (line2) => line2.replace(/\r$/, "").trim().replace(/\s*,$/, "");
11631
12146
  var WEAK = /^[\p{P}\p{S}\s]+$/u;
11632
12147
  var isWeak = (line2) => WEAK.test(line2);
11633
12148
  var meaningful = (lines3) => lines3.map(normalizeLine).filter((line2) => line2 !== "");
11634
- var contentLines = (text8) => text8 === void 0 ? [] : meaningful(text8.split("\n"));
12149
+ var contentLines = (text9) => text9 === void 0 ? [] : meaningful(text9.split("\n"));
11635
12150
  var countLines = (lines3) => {
11636
12151
  const counts = /* @__PURE__ */ new Map();
11637
12152
  for (const line2 of lines3) counts.set(line2, (counts.get(line2) ?? 0) + 1);
@@ -11680,16 +12195,16 @@ var itemOf = (value) => {
11680
12195
  approvedBy: optionalString(value, "approved_by")
11681
12196
  };
11682
12197
  };
11683
- var invalidFinding = (file, text8) => ({
12198
+ var invalidFinding = (file, text9) => ({
11684
12199
  kind: "invalid-dropped",
11685
12200
  file,
11686
12201
  side: void 0,
11687
12202
  line: void 0,
11688
- text: text8,
12203
+ text: text9,
11689
12204
  weak: false
11690
12205
  });
11691
- var parseDropped = (text8) => {
11692
- const parsed = parseJson(text8);
12206
+ var parseDropped = (text9) => {
12207
+ const parsed = parseJson(text9);
11693
12208
  if (!parsed.ok) return parsed;
11694
12209
  const list4 = isRecord(parsed.value) ? parsed.value["dropped"] : parsed.value;
11695
12210
  if (!Array.isArray(list4)) return reject("expected a list of dropped items");
@@ -11704,17 +12219,17 @@ var parseDropped = (text8) => {
11704
12219
  return accept({ items, invalid });
11705
12220
  };
11706
12221
  var loadDropped = (runtime, root, file) => {
11707
- const path = toAbsolutePath(file ?? join26(root, DROPPED_FILE), root);
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);
12222
+ const path = toAbsolutePath(file ?? join28(root, DROPPED_FILE), root);
12223
+ const text9 = runtime.readText(path);
12224
+ if (!text9.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text9.error}`);
12225
+ return parseDropped(text9.value);
11711
12226
  };
11712
12227
  var isDropped = (finding4, items) => items.some(
11713
12228
  (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)
11714
12229
  );
11715
12230
 
11716
12231
  // src/git/merge/generated.ts
11717
- import { join as join27 } from "node:path";
12232
+ import { join as join29 } from "node:path";
11718
12233
  var LOCKFILES3 = /* @__PURE__ */ new Set([
11719
12234
  "package-lock.json",
11720
12235
  "npm-shrinkwrap.json",
@@ -11732,11 +12247,11 @@ var LOCKFILES3 = /* @__PURE__ */ new Set([
11732
12247
  ]);
11733
12248
  var GENERATOR_TIMEOUT_MS = 3e5;
11734
12249
  var DOUBLE_STAR = "";
11735
- var globRegex = (pattern) => new RegExp(
12250
+ var globRegex2 = (pattern) => new RegExp(
11736
12251
  `^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").split("**").join(DOUBLE_STAR).replace(/\*/g, "[^/]*").split(DOUBLE_STAR).join(".*")}$`
11737
12252
  );
11738
12253
  var generatorFor = (path, generators) => Object.entries(generators).find(
11739
- ([pattern]) => globRegex(pattern).test(path) || globRegex(pattern).test(path.split("/").pop() ?? "")
12254
+ ([pattern]) => globRegex2(pattern).test(path) || globRegex2(pattern).test(path.split("/").pop() ?? "")
11740
12255
  )?.[1];
11741
12256
  var generatedPaths = (runtime, repo, paths, generators) => {
11742
12257
  const out = new Set(
@@ -11777,7 +12292,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
11777
12292
  if (declared.length === 0) return accept([]);
11778
12293
  const commit2 = resultCommit(runtime, repo, inputs);
11779
12294
  if (!commit2.ok) return commit2;
11780
- const dir = join27(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
12295
+ const dir = join29(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
11781
12296
  if (!runtime.git(["worktree", "add", "--detach", dir, commit2.value], repo.root).ok)
11782
12297
  return reject("could not check out the merge result");
11783
12298
  try {
@@ -11787,7 +12302,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
11787
12302
  }
11788
12303
  return accept(
11789
12304
  declared.flatMap(({ path }) => {
11790
- const fresh3 = runtime.readText(toAbsolutePath(join27(dir, path)));
12305
+ const fresh3 = runtime.readText(toAbsolutePath(join29(dir, path)));
11791
12306
  const merged2 = blobText(runtime, repo, commit2.value, path);
11792
12307
  if (fresh3.ok && fresh3.value === merged2) return [];
11793
12308
  return [{ kind: "generated-differs", file: path, side: void 0, line: void 0, text: path, weak: false }];
@@ -11830,13 +12345,13 @@ var removalOf = (ours, theirs, paired) => {
11830
12345
  var missingAdditions = (hunks, file, elsewhere) => {
11831
12346
  const present2 = new Set(file.result);
11832
12347
  return hunks.flatMap(({ side, hunk, added }) => {
11833
- const text8 = added.find((line2) => !isWeak(line2) && !present2.has(line2));
11834
- if (text8 === void 0 || containsSequence(file.result, added) || elsewhere(added)) return [];
12348
+ const text9 = added.find((line2) => !isWeak(line2) && !present2.has(line2));
12349
+ if (text9 === void 0 || containsSequence(file.result, added) || elsewhere(added)) return [];
11835
12350
  const offset = Math.max(
11836
12351
  0,
11837
- hunk.added.findIndex((line2) => normalizeLine(line2) === text8)
12352
+ hunk.added.findIndex((line2) => normalizeLine(line2) === text9)
11838
12353
  );
11839
- return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text: text8, weak: false }];
12354
+ return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text: text9, weak: false }];
11840
12355
  });
11841
12356
  };
11842
12357
  var expectations = (file, hunks, removal) => {
@@ -11909,8 +12424,8 @@ var symbolsOf = (line2) => {
11909
12424
  ...title === void 0 ? [] : [{ name: title, test: true }]
11910
12425
  ];
11911
12426
  };
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);
12427
+ var escapeRegex = (text9) => text9.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12428
+ var containsSymbol = (text9, symbol) => symbol.test ? text9.includes(symbol.name) : new RegExp(`(?<![\\w$])${escapeRegex(symbol.name)}(?![\\w$])`).test(text9);
11914
12429
  var missingSymbols = (file, side, hunks, present2) => {
11915
12430
  const seen = /* @__PURE__ */ new Set();
11916
12431
  return hunks.flatMap(
@@ -11929,12 +12444,12 @@ var created2 = (diff) => diff?.status === "added" || diff?.status === "copied";
11929
12444
  var resultPathOf = (context, pair) => [pair.ours?.newPath, pair.theirs?.newPath, pair.key].find(
11930
12445
  (path) => path !== void 0 && context.resultText(path) !== void 0
11931
12446
  );
11932
- var finding2 = (kind, file, side, text8) => ({
12447
+ var finding2 = (kind, file, side, text9) => ({
11933
12448
  kind,
11934
12449
  file,
11935
12450
  side,
11936
12451
  line: void 0,
11937
- text: text8,
12452
+ text: text9,
11938
12453
  weak: false
11939
12454
  });
11940
12455
  var binaryFindings = (context, pair, path) => {
@@ -11953,8 +12468,8 @@ var binaryFindings = (context, pair, path) => {
11953
12468
  return result === expected ? [] : [finding2("binary", file, side, file)];
11954
12469
  };
11955
12470
  var elsewhereOf = (context) => (lines3) => context.resultFiles().some((path) => containsSequence(contentLines(context.resultText(path)), lines3));
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));
12471
+ var symbolFindings = (context, pair, path, text9) => {
12472
+ const present2 = (symbol) => text9 !== void 0 && containsSymbol(text9, symbol) || context.resultFiles().some((other) => containsSymbol(context.resultText(other) ?? "", symbol));
11958
12473
  return [
11959
12474
  ...missingSymbols(path, "ours", pair.ours?.hunks ?? [], present2),
11960
12475
  ...missingSymbols(path, "theirs", pair.theirs?.hunks ?? [], present2)
@@ -11970,19 +12485,19 @@ var verifyFile = (context, pair) => {
11970
12485
  const copied = [pair.ours, pair.theirs].find((diff) => diff?.status === "copied");
11971
12486
  const baseSource = copied?.oldPath ?? (created2(pair.ours) || created2(pair.theirs) ? void 0 : pair.key);
11972
12487
  const baseText = baseSource === void 0 ? void 0 : blobText(context.runtime, context.repo, context.inputs.base, baseSource);
11973
- const text8 = path === void 0 ? void 0 : context.resultText(path);
12488
+ const text9 = path === void 0 ? void 0 : context.resultText(path);
11974
12489
  const file = path ?? pair.key;
11975
12490
  const lines3 = checkFile(
11976
12491
  {
11977
12492
  path: file,
11978
12493
  base: contentLines(baseText),
11979
- result: contentLines(text8),
12494
+ result: contentLines(text9),
11980
12495
  ours: pair.ours?.hunks ?? [],
11981
12496
  theirs: pair.theirs?.hunks ?? []
11982
12497
  },
11983
12498
  elsewhereOf(context)
11984
12499
  );
11985
- return [...lines3, ...symbolFindings(context, pair, file, text8)];
12500
+ return [...lines3, ...symbolFindings(context, pair, file, text9)];
11986
12501
  };
11987
12502
 
11988
12503
  // src/git/merge/verify.ts
@@ -12011,13 +12526,13 @@ var pairsOf = (ours, theirs) => {
12011
12526
  }));
12012
12527
  };
12013
12528
  var contextOf2 = (runtime, repo, inputs, pairs2) => {
12014
- const resultText = memoBy((path) => blobText(runtime, repo, inputs.result, path));
12529
+ const resultText2 = memoBy((path) => blobText(runtime, repo, inputs.result, path));
12015
12530
  const context = {
12016
12531
  runtime,
12017
12532
  repo,
12018
12533
  inputs,
12019
- resultText,
12020
- resultFiles: memo2(
12534
+ resultText: resultText2,
12535
+ resultFiles: memo(
12021
12536
  () => pairs2.flatMap((pair) => {
12022
12537
  const path = resultPathOf(context, pair);
12023
12538
  return path === void 0 ? [] : [path];
@@ -12073,19 +12588,19 @@ var verifyMerge = (runtime, repo, options = {}) => {
12073
12588
  };
12074
12589
 
12075
12590
  // src/git/ownership.ts
12076
- import { join as join28 } from "node:path";
12591
+ import { join as join30 } from "node:path";
12077
12592
  var OWNERSHIP_DIR = ".finitemonkey/git";
12078
12593
  var ownerKey = (raw) => {
12079
12594
  const cleaned = (raw ?? "").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80);
12080
12595
  return cleaned === "" ? "unknown" : cleaned;
12081
12596
  };
12082
- var fileOf = (repo, kind, owner) => toAbsolutePath(join28(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
12597
+ var fileOf = (repo, kind, owner) => toAbsolutePath(join30(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
12083
12598
  var readDirty = (runtime, root) => {
12084
12599
  const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
12085
12600
  return status.ok ? parsePorcelainV2(status.stdout) : void 0;
12086
12601
  };
12087
12602
  var hashDirty = (runtime, repo, entries) => {
12088
- const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join28(repo.root, entry2.path)))?.kind === "file");
12603
+ const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join30(repo.root, entry2.path)))?.kind === "file");
12089
12604
  const hashed = present2.length === 0 ? void 0 : runtime.gitWith(["hash-object", "--stdin-paths"], repo.root, {
12090
12605
  input: `${present2.map((entry2) => entry2.path).join("\n")}
12091
12606
  `
@@ -12102,8 +12617,8 @@ var hashesOf = (value) => isRecord(value) ? Object.fromEntries(
12102
12617
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
12103
12618
  ) : {};
12104
12619
  var readObject = (runtime, path) => {
12105
- const text8 = runtime.readText(path);
12106
- return text8.ok ? parseJsonObject(text8.value) : text8;
12620
+ const text9 = runtime.readText(path);
12621
+ return text9.ok ? parseJsonObject(text9.value) : text9;
12107
12622
  };
12108
12623
  var readBaseline2 = (runtime, repo, owner) => {
12109
12624
  const parsed = readObject(runtime, fileOf(repo, "baseline", owner));
@@ -12169,7 +12684,7 @@ var foreignFiles = (runtime, repo, owner, targets2) => {
12169
12684
  };
12170
12685
 
12171
12686
  // src/git/gate-status.ts
12172
- var lines = (text8) => text8.split("\n").filter((line2) => line2.trim() !== "");
12687
+ var lines = (text9) => text9.split("\n").filter((line2) => line2.trim() !== "");
12173
12688
  var unpushedSince = (runtime, repo, since) => {
12174
12689
  if (lines(runtime.git(["remote"], repo.root).stdout).length === 0) return [];
12175
12690
  const result = runtime.git(["rev-list", `${since}..HEAD`, "--not", "--remotes"], repo.root);
@@ -12197,97 +12712,17 @@ var findingLabel = (finding4) => {
12197
12712
  return `${where} ${finding4.text === "" ? finding4.kind : `"${finding4.text}"`}`;
12198
12713
  };
12199
12714
 
12200
- // src/git/repo.ts
12201
- var DEFAULT_PROTECTED = ["main", "master", "release/*", "prod", "production", "rc-*"];
12202
- var openRepo = (runtime, dir) => {
12203
- const result = runtime.git(["rev-parse", "--show-toplevel", "--absolute-git-dir", "--show-prefix"], dir);
12204
- if (!result.ok) return void 0;
12205
- const [root, gitDir, prefix] = result.stdout.split("\n");
12206
- if (root === void 0 || root === "" || gitDir === void 0 || gitDir === "") return void 0;
12207
- return {
12208
- root: toAbsolutePath(root),
12209
- gitDir: toAbsolutePath(gitDir),
12210
- prefix: (prefix ?? "").replace(/\/$/, "")
12211
- };
12212
- };
12213
- var globRegex2 = (pattern) => new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`);
12214
- var matchesBranch = (branch, patterns) => patterns.some((pattern) => globRegex2(pattern).test(branch));
12215
- var line = (runtime, root, args2) => {
12216
- const result = runtime.git(args2, root);
12217
- const value = result.stdout.trim();
12218
- return result.ok && value !== "" ? value : void 0;
12219
- };
12220
- var refExists = (runtime, root, ref) => runtime.git(["show-ref", "--verify", "--quiet", ref], root).ok;
12221
- var defaultBranchOf = (runtime, root) => {
12222
- const remote = line(runtime, root, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
12223
- if (remote !== void 0) return remote.replace(/^[^/]+\//, "");
12224
- const configured = line(runtime, root, ["config", "--get", "init.defaultBranch"]);
12225
- if (configured !== void 0 && refExists(runtime, root, `refs/heads/${configured}`)) return configured;
12226
- return refExists(runtime, root, "refs/heads/master") && !refExists(runtime, root, "refs/heads/main") ? "master" : "main";
12227
- };
12228
- var upstreamOf = (runtime, root, branch) => {
12229
- const remote = line(runtime, root, ["config", "--get", `branch.${branch}.remote`]);
12230
- const merge = line(runtime, root, ["config", "--get", `branch.${branch}.merge`]);
12231
- if (remote === void 0 || merge === void 0 || remote === ".") return void 0;
12232
- const name = merge.replace(/^refs\/heads\//, "");
12233
- return { branch: name, remoteRef: `refs/remotes/${remote}/${name}` };
12234
- };
12235
- var integrationRefs = (runtime, root, defaultBranch) => [`refs/heads/${defaultBranch}`, `refs/remotes/origin/${defaultBranch}`].filter(
12236
- (ref) => refExists(runtime, root, ref)
12237
- );
12238
- var contained = (runtime, root, branch, targets2) => {
12239
- const tip = line(runtime, root, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]);
12240
- if (tip === void 0) return true;
12241
- return targets2.filter((target2) => target2 !== `refs/heads/${branch}`).some((target2) => runtime.git(["merge-base", "--is-ancestor", tip, target2], root).ok);
12242
- };
12243
- var shared = (runtime, root, upstream, base) => {
12244
- if (upstream === void 0 || !refExists(runtime, root, upstream.remoteRef)) return false;
12245
- const own = /<([^>]*)>/.exec(line(runtime, root, ["var", "GIT_AUTHOR_IDENT"]) ?? "")?.[1] ?? "";
12246
- const log = runtime.git(["log", "--format=%ae", upstream.remoteRef, ...base.map((ref) => `^${ref}`)], root);
12247
- return log.ok && log.stdout.split("\n").some((email) => email.trim() !== "" && email.trim() !== own);
12248
- };
12249
- var protectedPatterns = (config, defaultBranch) => [
12250
- defaultBranch,
12251
- ...DEFAULT_PROTECTED,
12252
- ...configList(config, "git", "protected")
12253
- ];
12254
- var repoFacts = (runtime, repo, config = loadProjectConfig(runtime, repo.root)) => {
12255
- const { root } = repo;
12256
- const defaultBranch = memo2(() => defaultBranchOf(runtime, root));
12257
- const patterns = memo2(() => protectedPatterns(config, defaultBranch()));
12258
- const upstream = memoBy((branch) => upstreamOf(runtime, root, branch));
12259
- const integration = memo2(() => integrationRefs(runtime, root, defaultBranch()));
12260
- return {
12261
- prefix: repo.prefix,
12262
- currentBranch: memo2(() => line(runtime, root, ["symbolic-ref", "--quiet", "--short", "HEAD"])),
12263
- defaultBranch,
12264
- isProtected: (branch) => matchesBranch(branch, patterns()),
12265
- dirty: memo2(() => {
12266
- const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
12267
- return status.ok ? parsePorcelainV2(status.stdout) : void 0;
12268
- }),
12269
- isRef: memoBy((name) => runtime.git(["rev-parse", "--verify", "--quiet", `${name}^{commit}`], root).ok),
12270
- branchExists: memoBy((name) => refExists(runtime, root, `refs/heads/${name}`)),
12271
- branchContained: memoBy((branch) => {
12272
- const up = upstream(branch);
12273
- return contained(runtime, root, branch, [...up === void 0 ? [] : [up.remoteRef], ...integration()]);
12274
- }),
12275
- upstream,
12276
- sharedBranch: memoBy((branch) => shared(runtime, root, upstream(branch), integration()))
12277
- };
12278
- };
12279
-
12280
12715
  // src/gate/checks/worktree.ts
12281
12716
  var MAX_LISTED7 = 8;
12282
12717
  var listed3 = (items) => {
12283
12718
  const head = items.slice(0, MAX_LISTED7).join(", ");
12284
12719
  return items.length > MAX_LISTED7 ? `${head} (+${String(items.length - MAX_LISTED7)})` : head;
12285
12720
  };
12286
- var repoOf = (ctx) => openRepo(ctx.runtime, ctx.paths.root);
12721
+ var repoOf2 = (ctx) => openRepo(ctx.runtime, ctx.paths.root);
12287
12722
  var ownerOf = (ctx) => ownerKey(ctx.sessionId);
12288
12723
  var declaresDestination = (ctx) => ctx.contract.acceptance.some((check2) => check2.kind === "git" && (check2.require ?? []).includes("pushed"));
12289
12724
  var worktreeCheck = (_check, ctx) => {
12290
- const repo = repoOf(ctx);
12725
+ const repo = repoOf2(ctx);
12291
12726
  if (repo === void 0) return unverified("tool_missing", [note("gate.worktree.unavailable")]);
12292
12727
  const status = worktreeGateStatus(ctx.runtime, repo, ownerOf(ctx));
12293
12728
  const unpushed = declaresDestination(ctx) ? [] : status.unpushed;
@@ -12298,7 +12733,7 @@ var worktreeCheck = (_check, ctx) => {
12298
12733
  return notes2.length > 0 ? failed(notes2) : passed([note("gate.worktree.clean")]);
12299
12734
  };
12300
12735
  var mergeCheck = (_check, ctx) => {
12301
- const repo = repoOf(ctx);
12736
+ const repo = repoOf2(ctx);
12302
12737
  if (repo === void 0) return unverified("tool_missing", [note("gate.merge.unavailable")]);
12303
12738
  const items = mergeGateStatus(ctx.runtime, repo, ownerOf(ctx));
12304
12739
  const short3 = (commit2) => commit2 === "MERGE_HEAD" ? commit2 : commit2.slice(0, 12);
@@ -12409,15 +12844,15 @@ var runGate = (options, registry = DEFAULT_CHECKS) => {
12409
12844
  if (ready.kind !== "ready") return ready;
12410
12845
  const { prepared } = ready;
12411
12846
  const path = prepared.ctx.paths.evidence(prepared.ctx.contract.id);
12412
- const before = readLedger(options.runtime, path);
12847
+ const before = readLedger2(options.runtime, path);
12413
12848
  if (options.fast && fastPathHit(prepared, before)) return { kind: "done", report: report(prepared, before, true) };
12414
12849
  const fresh3 = runChecks(prepared.ctx, before, registry);
12415
12850
  if (!appendEvidence(options.runtime, path, fresh3)) return { kind: "failed", failure: { kind: "ledger_unwritable" } };
12416
- const withChecks = readLedger(options.runtime, path);
12851
+ const withChecks = readLedger2(options.runtime, path);
12417
12852
  if (!appendEvidence(options.runtime, path, [summaryEntry(prepared, withChecks)])) {
12418
12853
  return { kind: "failed", failure: { kind: "ledger_unwritable" } };
12419
12854
  }
12420
- return { kind: "done", report: report(prepared, readLedger(options.runtime, path), false) };
12855
+ return { kind: "done", report: report(prepared, readLedger2(options.runtime, path), false) };
12421
12856
  };
12422
12857
 
12423
12858
  // src/gate/hooks/stop.ts
@@ -12535,7 +12970,7 @@ var subagentStopGate = (input) => {
12535
12970
  };
12536
12971
 
12537
12972
  // src/gate/hooks/subagent.ts
12538
- import { join as join29 } from "node:path";
12973
+ import { join as join31 } from "node:path";
12539
12974
  var NO_CONTRACT_AGENTS = /(^|:)(reviewer|verifier|explore|plan|claude-code-guide|statusline-setup)$/i;
12540
12975
  var KNOWN_KINDS = /* @__PURE__ */ new Set([
12541
12976
  "command",
@@ -12573,12 +13008,12 @@ var subagentStart = (input) => {
12573
13008
  const active = readState(runtime, paths).active;
12574
13009
  if (active === void 0) return { kind: "allow" };
12575
13010
  const disk = (relative11) => {
12576
- const text8 = runtime.readText(toAbsolutePath(join29(paths.root, relative11)));
12577
- return text8.ok ? text8.value : void 0;
13011
+ const text9 = runtime.readText(toAbsolutePath(join31(paths.root, relative11)));
13012
+ return text9.ok ? text9.value : void 0;
12578
13013
  };
12579
13014
  const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
12580
13015
  if (!loaded.ok) return { kind: "allow" };
12581
- const briefing = runtime.stat(toAbsolutePath(join29(paths.dir, "briefing.md"))) !== void 0;
13016
+ const briefing = runtime.stat(toAbsolutePath(join31(paths.dir, "briefing.md"))) !== void 0;
12582
13017
  return { kind: "allow", output: contextOutput("SubagentStart", contractFacts(t, loaded.value.contract, briefing)) };
12583
13018
  };
12584
13019
 
@@ -12596,12 +13031,12 @@ var PORTAO_HANDLERS = {
12596
13031
  };
12597
13032
 
12598
13033
  // src/prod/guard.ts
12599
- import { join as join31 } from "node:path";
13034
+ import { join as join33 } from "node:path";
12600
13035
 
12601
13036
  // src/git/command-text.ts
12602
13037
  var MAX_SHOWN = 160;
12603
- var shown = (text8) => {
12604
- const flat = text8.replace(/\s+/g, " ").trim();
13038
+ var shown = (text9) => {
13039
+ const flat = text9.replace(/\s+/g, " ").trim();
12605
13040
  return flat.length > MAX_SHOWN ? `${flat.slice(0, MAX_SHOWN - 3)}...` : flat;
12606
13041
  };
12607
13042
  var commandText = (command) => shown([command.program, ...command.args].join(" "));
@@ -12938,7 +13373,7 @@ var remoteHostsOf = (command) => {
12938
13373
  };
12939
13374
 
12940
13375
  // src/prod/targets.ts
12941
- import { join as join30 } from "node:path";
13376
+ import { join as join32 } from "node:path";
12942
13377
  var DEPLOY_FILES = [
12943
13378
  "deploy.sh",
12944
13379
  "scripts/deploy.sh",
@@ -12951,9 +13386,9 @@ var SSH_URL = /ssh:\/\/(?:[^@\s/]+@)?([A-Za-z0-9.-]+)/g;
12951
13386
  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;
12952
13387
  var hostKey = (raw) => raw.trim().toLowerCase().replace(/^[^@]*@/, "").replace(/^\[([^\]]+)\].*$/, "$1").replace(/:\d+$/, "").replace(/\.$/, "");
12953
13388
  var shellLine = (line2) => line2.replace(/^\s*(?:-\s*)?(?:run:\s*\|?)?/, "");
12954
- var hostsInText = (text8) => {
12955
- const urls = [...text8.matchAll(SSH_URL)].map((match) => match[1] ?? "");
12956
- const lines3 = text8.split(/\r?\n/);
13389
+ var hostsInText = (text9) => {
13390
+ const urls = [...text9.matchAll(SSH_URL)].map((match) => match[1] ?? "");
13391
+ const lines3 = text9.split(/\r?\n/);
12957
13392
  const keyed = lines3.flatMap((line2) => {
12958
13393
  const match = HOST_KEY.exec(line2);
12959
13394
  return match?.[1] === void 0 ? [] : [match[1]];
@@ -12962,13 +13397,13 @@ var hostsInText = (text8) => {
12962
13397
  return [...urls, ...keyed, ...commands].filter((host) => host !== "" && !host.includes("$"));
12963
13398
  };
12964
13399
  var workflowFiles = (runtime, root) => {
12965
- const listed4 = runtime.listDir(toAbsolutePath(join30(root, ".github", "workflows")));
13400
+ const listed4 = runtime.listDir(toAbsolutePath(join32(root, ".github", "workflows")));
12966
13401
  if (!listed4.ok) return [];
12967
13402
  return listed4.value.filter((name) => /deploy/i.test(name) && /\.ya?ml$/i.test(name)).map((name) => `.github/workflows/${name}`);
12968
13403
  };
12969
13404
  var detectedHosts = (runtime, root, files) => files.flatMap((file) => {
12970
- const text8 = runtime.readText(toAbsolutePath(join30(root, file)));
12971
- return text8.ok ? hostsInText(text8.value) : [];
13405
+ const text9 = runtime.readText(toAbsolutePath(join32(root, file)));
13406
+ return text9.ok ? hostsInText(text9.value) : [];
12972
13407
  });
12973
13408
  var loadProdTargets = (runtime, root, config) => {
12974
13409
  const workflows = workflowFiles(runtime, root);
@@ -12979,7 +13414,7 @@ var loadProdTargets = (runtime, root, config) => {
12979
13414
  ...detectedHosts(runtime, root, deployFiles)
12980
13415
  ];
12981
13416
  const declaredDeploy = configAt(config, "prod", "deploy");
12982
- const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join30(root, file))) !== void 0);
13417
+ const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join32(root, file))) !== void 0);
12983
13418
  return {
12984
13419
  hosts: new Set(hosts.map(hostKey).filter((host) => host !== "")),
12985
13420
  kubeContexts: configList(config, "prod", "kube_contexts"),
@@ -13135,15 +13570,15 @@ var blockOf = (t, hit3, command, deploy) => ({
13135
13570
  })
13136
13571
  });
13137
13572
  var prodGuard = (runtime, root, t) => {
13138
- const targets2 = memo2(() => loadProdTargets(runtime, root, loadProjectConfig(runtime, root)));
13573
+ const targets2 = memo(() => loadProdTargets(runtime, root, loadProjectConfig(runtime, root)));
13139
13574
  const isProd = isProdHostOf(runtime, root, targets2);
13140
13575
  return {
13141
13576
  decide: (command) => {
13142
13577
  if (!PROD_PROGRAMS.has(command.program)) return void 0;
13143
13578
  const hit3 = prodHit(command, targets2(), isProd);
13144
13579
  if (hit3 === void 0) return void 0;
13145
- const text8 = commandText(command);
13146
- return hit3.effect === "read" ? { kind: "read", hit: hit3, command: text8 } : blockOf(t, hit3, text8, targets2().deployPath);
13580
+ const text9 = commandText(command);
13581
+ return hit3.effect === "read" ? { kind: "read", hit: hit3, command: text9 } : blockOf(t, hit3, text9, targets2().deployPath);
13147
13582
  }
13148
13583
  };
13149
13584
  };
@@ -13160,11 +13595,11 @@ var recordEvidence = (runtime, root, owner, reads) => {
13160
13595
  ] : []
13161
13596
  );
13162
13597
  if (lines3.length === 0) return;
13163
- const path = toAbsolutePath(join31(root, EVIDENCE_FILE));
13164
- runtime.mkdir(toAbsolutePath(join31(root, ".finitemonkey")));
13598
+ const path = toAbsolutePath(join33(root, EVIDENCE_FILE));
13599
+ runtime.mkdir(toAbsolutePath(join33(root, ".finitemonkey")));
13165
13600
  const before = runtime.readText(path);
13166
- const text8 = before.ok ? before.value : "";
13167
- runtime.writeAtomic(path, `${text8}${text8 === "" || text8.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
13601
+ const text9 = before.ok ? before.value : "";
13602
+ runtime.writeAtomic(path, `${text9}${text9 === "" || text9.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
13168
13603
  `);
13169
13604
  };
13170
13605
 
@@ -13179,15 +13614,15 @@ var editTexts = (input, file) => {
13179
13614
  if (!Array.isArray(edits)) return [];
13180
13615
  return edits.flatMap((edit) => {
13181
13616
  if (!isRecord(edit)) return [];
13182
- const text8 = optionalString(edit, "new_string");
13183
- return text8 === void 0 ? [] : [{ file, text: text8, whole: false }];
13617
+ const text9 = optionalString(edit, "new_string");
13618
+ return text9 === void 0 ? [] : [{ file, text: text9, whole: false }];
13184
13619
  });
13185
13620
  };
13186
13621
  var writtenTexts = (tool, input) => {
13187
13622
  const file = optionalString(input, "file_path") ?? optionalString(input, "notebook_path") ?? "";
13188
13623
  const single2 = (key, whole) => {
13189
- const text8 = optionalString(input, key);
13190
- return text8 === void 0 ? [] : [{ file, text: text8, whole }];
13624
+ const text9 = optionalString(input, key);
13625
+ return text9 === void 0 ? [] : [{ file, text: text9, whole }];
13191
13626
  };
13192
13627
  switch (tool) {
13193
13628
  case "Write":
@@ -13869,24 +14304,24 @@ var withChain = (facts, chain) => {
13869
14304
  const branch = chain?.branch;
13870
14305
  return branch === void 0 ? facts : { ...facts, currentBranch: () => branch };
13871
14306
  };
13872
- var decide = (context, repo, verdict, facts, text8) => {
14307
+ var decide = (context, repo, verdict, facts, text9) => {
13873
14308
  switch (verdict.kind) {
13874
14309
  case "pass":
13875
14310
  return { kind: "pass" };
13876
14311
  case "block":
13877
- return { kind: "block", rule: verdict.rule, subject: verdict.subject, command: text8 };
14312
+ return { kind: "block", rule: verdict.rule, subject: verdict.subject, command: text9 };
13878
14313
  case "snapshot":
13879
- return { kind: "snapshot", repo, op: verdict.op, command: text8 };
14314
+ return { kind: "snapshot", repo, op: verdict.op, command: text9 };
13880
14315
  case "discard": {
13881
14316
  const entries = facts.dirty();
13882
- if (entries === void 0) return { kind: "unknown", command: text8 };
14317
+ if (entries === void 0) return { kind: "unknown", command: text9 };
13883
14318
  const foreign = foreignFiles(
13884
14319
  context.runtime,
13885
14320
  repo,
13886
14321
  context.owner,
13887
14322
  targetsOf(entries, verdict.discard, facts.prefix)
13888
14323
  );
13889
- return foreign.length > 0 ? { kind: "foreign", files: foreign, command: text8 } : { kind: "snapshot", repo, op: verdict.op, command: text8 };
14324
+ return foreign.length > 0 ? { kind: "foreign", files: foreign, command: text9 } : { kind: "snapshot", repo, op: verdict.op, command: text9 };
13890
14325
  }
13891
14326
  }
13892
14327
  };
@@ -13973,10 +14408,10 @@ var listWorktrees = (runtime, cwd) => {
13973
14408
  };
13974
14409
 
13975
14410
  // src/git/journal.ts
13976
- import { join as join32 } from "node:path";
14411
+ import { join as join34 } from "node:path";
13977
14412
  var JOURNAL_FILE = ".finitemonkey/git-journal.jsonl";
13978
14413
  var JOURNAL_SCHEMA = "finitemonkey/git-journal@1";
13979
- var journalPath = (root) => toAbsolutePath(join32(root, JOURNAL_FILE));
14414
+ var journalPath = (root) => toAbsolutePath(join34(root, JOURNAL_FILE));
13980
14415
  var headsOf = (value) => isRecord(value) ? Object.fromEntries(
13981
14416
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
13982
14417
  ) : {};
@@ -14003,16 +14438,16 @@ var parseJournalLine = (line2) => {
14003
14438
  };
14004
14439
  };
14005
14440
  var readJournal = (runtime, root) => {
14006
- const text8 = runtime.readText(journalPath(root));
14007
- if (!text8.ok) return [];
14008
- return text8.value.split("\n").flatMap((line2) => {
14441
+ const text9 = runtime.readText(journalPath(root));
14442
+ if (!text9.ok) return [];
14443
+ return text9.value.split("\n").flatMap((line2) => {
14009
14444
  const entry2 = line2.trim() === "" ? void 0 : parseJournalLine(line2);
14010
14445
  return entry2 === void 0 ? [] : [entry2];
14011
14446
  });
14012
14447
  };
14013
14448
  var appendJournal = (runtime, root, entry2) => {
14014
14449
  const path = journalPath(root);
14015
- const made = runtime.mkdir(toAbsolutePath(join32(root, ".finitemonkey")));
14450
+ const made = runtime.mkdir(toAbsolutePath(join34(root, ".finitemonkey")));
14016
14451
  if (!made.ok) return made;
14017
14452
  const previous = runtime.readText(path);
14018
14453
  const before = previous.ok ? previous.value : "";
@@ -14176,9 +14611,9 @@ var snapshotsOf = (input, owner, pending) => {
14176
14611
  return { kind: "allow", output: { hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: context } } };
14177
14612
  };
14178
14613
  var review = (input, commands, owner) => {
14179
- const worktrees = memo2(() => listWorktrees(input.runtime, input.cwd));
14180
- const repo = memo2(() => openRepo(input.runtime, input.cwd));
14181
- const prod = memo2(() => prodGuard(input.runtime, repo()?.root ?? input.cwd, input.t));
14614
+ const worktrees = memo(() => listWorktrees(input.runtime, input.cwd));
14615
+ const repo = memo(() => openRepo(input.runtime, input.cwd));
14616
+ const prod = memo(() => prodGuard(input.runtime, repo()?.root ?? input.cwd, input.t));
14182
14617
  const snapshots = [];
14183
14618
  const reads = [];
14184
14619
  const chain = { branch: void 0 };
@@ -14197,9 +14632,9 @@ var review = (input, commands, owner) => {
14197
14632
  return { blocked: void 0, snapshots, reads };
14198
14633
  };
14199
14634
  var gitPreToolUse = (input) => {
14200
- const text8 = shellText(input.payload);
14201
- if (text8 === void 0) return ALLOW;
14202
- const commands = normalizeCommand(text8, dialectOf(toolName2(input.payload))).commands;
14635
+ const text9 = shellText(input.payload);
14636
+ if (text9 === void 0) return ALLOW;
14637
+ const commands = normalizeCommand(text9, dialectOf(toolName2(input.payload))).commands;
14203
14638
  if (commands.length === 0) return ALLOW;
14204
14639
  const owner = ownerOf2(input.payload);
14205
14640
  const result = review(input, commands, owner);
@@ -14261,8 +14696,8 @@ var GIT_HANDLERS = {
14261
14696
  var SHADOW_FILE = "shadow.json";
14262
14697
  var numberOf = (value) => typeof value === "number" && Number.isInteger(value) ? value : 0;
14263
14698
  var readShadow = (runtime, paths) => {
14264
- const text8 = runtime.readText(paths.cache(SHADOW_FILE));
14265
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
14699
+ const text9 = runtime.readText(paths.cache(SHADOW_FILE));
14700
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
14266
14701
  if (parsed?.ok !== true) return {};
14267
14702
  return Object.fromEntries(
14268
14703
  Object.entries(parsed.value).flatMap(
@@ -14346,11 +14781,11 @@ var ASSIGNMENT3 = /^[A-Za-z_][A-Za-z0-9_]*=/;
14346
14781
  var WRAPPERS2 = /* @__PURE__ */ new Set(["sudo", "command", "exec", "time", "nice", "nohup", "builtin", "env"]);
14347
14782
  var liftHeredocs = (command) => {
14348
14783
  const heredocs = [];
14349
- const text8 = command.replace(HEREDOC2, (_match, _quote, _tag, body2) => {
14784
+ const text9 = command.replace(HEREDOC2, (_match, _quote, _tag, body2) => {
14350
14785
  heredocs.push(body2);
14351
14786
  return `<<__FM_HEREDOC_${String(heredocs.length - 1)}__`;
14352
14787
  });
14353
- return { text: text8, heredocs };
14788
+ return { text: text9, heredocs };
14354
14789
  };
14355
14790
  var endWord = (scan) => {
14356
14791
  if (scan.word !== void 0) scan.words.push(scan.word);
@@ -14361,23 +14796,23 @@ var endSegment = (scan) => {
14361
14796
  if (scan.words.length > 0) scan.segments.push(scan.words);
14362
14797
  scan.words = [];
14363
14798
  };
14364
- var quotedEnd2 = (text8, from, quote) => {
14799
+ var quotedEnd2 = (text9, from, quote) => {
14365
14800
  let index = from;
14366
- while (index < text8.length && text8[index] !== quote) index += quote === '"' && text8[index] === "\\" ? 2 : 1;
14801
+ while (index < text9.length && text9[index] !== quote) index += quote === '"' && text9[index] === "\\" ? 2 : 1;
14367
14802
  return index;
14368
14803
  };
14369
14804
  var unescape = (value) => value.replace(/\\(["\\$`])/g, "$1");
14370
- var splitWords = (text8) => {
14805
+ var splitWords = (text9) => {
14371
14806
  const scan = { words: [], segments: [], word: void 0 };
14372
- for (let index = 0; index < text8.length; index += 1) {
14373
- const char = text8[index] ?? "";
14807
+ for (let index = 0; index < text9.length; index += 1) {
14808
+ const char = text9[index] ?? "";
14374
14809
  if (char === '"' || char === "'") {
14375
- const end = quotedEnd2(text8, index + 1, char);
14376
- const inner = text8.slice(index + 1, end);
14810
+ const end = quotedEnd2(text9, index + 1, char);
14811
+ const inner = text9.slice(index + 1, end);
14377
14812
  scan.word = (scan.word ?? "") + (char === '"' ? unescape(inner) : inner);
14378
14813
  index = end;
14379
- } else if (char === "\\" && index + 1 < text8.length) {
14380
- scan.word = (scan.word ?? "") + (text8[index + 1] === "\n" ? "" : text8[index + 1] ?? "");
14814
+ } else if (char === "\\" && index + 1 < text9.length) {
14815
+ scan.word = (scan.word ?? "") + (text9[index + 1] === "\n" ? "" : text9[index + 1] ?? "");
14381
14816
  index += 1;
14382
14817
  } else if (SEPARATOR.test(char)) {
14383
14818
  endSegment(scan);
@@ -14398,8 +14833,8 @@ var segmentOf = (words) => {
14398
14833
  return { program: program.split(/[\\/]/).pop() ?? program, args: words.slice(index + 1) };
14399
14834
  };
14400
14835
  var shellWords = (command) => {
14401
- const { text: text8, heredocs } = liftHeredocs(command);
14402
- const segments = splitWords(text8).flatMap((words) => {
14836
+ const { text: text9, heredocs } = liftHeredocs(command);
14837
+ const segments = splitWords(text9).flatMap((words) => {
14403
14838
  const segment = segmentOf(words);
14404
14839
  return segment === void 0 ? [] : [segment];
14405
14840
  });
@@ -14504,7 +14939,7 @@ var sourceOf = (value, isFile, segment, heredocs) => {
14504
14939
  if (inline3 !== void 0) return { kind: "inline", text: inline3 };
14505
14940
  if (!isFile) return { kind: "inline", text: value };
14506
14941
  if (value !== "-") return { kind: "file", path: value };
14507
- const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text8) => text8 !== void 0);
14942
+ const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text9) => text9 !== void 0);
14508
14943
  return { kind: "inline", text: piped ?? "" };
14509
14944
  };
14510
14945
  var collect3 = (target2, segment, args2, spec2, heredocs) => {
@@ -14601,15 +15036,15 @@ var textOf2 = (input, found) => {
14601
15036
  return accept(parts.join("\n\n"));
14602
15037
  };
14603
15038
  var decisionFor = (input, found, blocking2) => {
14604
- const text8 = textOf2(input, found);
14605
- if (!text8.ok) {
15039
+ const text9 = textOf2(input, found);
15040
+ if (!text9.ok) {
14606
15041
  return blocking2 ? {
14607
15042
  kind: "block",
14608
- reason: modelReason(input.t, "prose.block.unreadable", { ...text8.error }),
15043
+ reason: modelReason(input.t, "prose.block.unreadable", { ...text9.error }),
14609
15044
  human: input.t("prose.block.human")
14610
15045
  } : void 0;
14611
15046
  }
14612
- const report2 = scoreProse(text8.value, { context: "record" });
15047
+ const report2 = scoreProse(text9.value, { context: "record" });
14613
15048
  if (report2.allowed || report2.spans.length === 0) return void 0;
14614
15049
  const params2 = {
14615
15050
  target: input.t(TARGET_KEYS[found.target]),
@@ -14656,7 +15091,7 @@ var HOOK_ACTOR = "finitemonkey";
14656
15091
  var liveFindings = (input, texts2) => {
14657
15092
  const baseline = loadBaseline(input.runtime, input.cwd);
14658
15093
  const accepted2 = acceptedFingerprints(baseline.ok ? baseline.value : EMPTY_BASELINE, input.runtime.now());
14659
- return texts2.flatMap((text8) => scanText(text8, { rules: CREDENTIAL_RULES })).filter((finding4) => !accepted2.has(finding4.fingerprint));
15094
+ return texts2.flatMap((text9) => scanText(text9, { rules: CREDENTIAL_RULES })).filter((finding4) => !accepted2.has(finding4.fingerprint));
14660
15095
  };
14661
15096
  var record2 = (input, findings, draft, where) => {
14662
15097
  const ids2 = findings.map((finding4) => {
@@ -14917,12 +15352,12 @@ var secretsSessionStart = (input) => {
14917
15352
  const remotes = remotesWithCredentials(input.runtime, input.cwd);
14918
15353
  if (remotes.length === 0) return { kind: "allow" };
14919
15354
  const params2 = { remotes: remotes.join(", "), remote: remotes[0] ?? "origin" };
14920
- const text8 = input.t("secrets.session.remote_credential", params2);
15355
+ const text9 = input.t("secrets.session.remote_credential", params2);
14921
15356
  return {
14922
15357
  kind: "allow",
14923
15358
  output: {
14924
- systemMessage: `finitemonkey: ${text8}`,
14925
- hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text8 }
15359
+ systemMessage: `finitemonkey: ${text9}`,
15360
+ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text9 }
14926
15361
  }
14927
15362
  };
14928
15363
  };
@@ -14958,7 +15393,18 @@ var mergeOutputs = (left, right) => {
14958
15393
  if (specific !== void 0) merged2["hookSpecificOutput"] = specific;
14959
15394
  return merged2;
14960
15395
  };
15396
+ var recordBlock = (input, decision) => {
15397
+ const paths = projectPaths(input);
15398
+ if (paths === void 0) return;
15399
+ logEvent(input.runtime, paths, "guard_block", {
15400
+ hook: input.event,
15401
+ tool: toolName2(input.payload),
15402
+ sessionId: input.payload.sessionId ?? null,
15403
+ claim: (decision.reason.split("\n")[0] ?? "").slice(0, 200)
15404
+ });
15405
+ };
14961
15406
  var shaped = (input, decision) => {
15407
+ recordBlock(input, decision);
14962
15408
  if (input.event !== "PreToolUse" || decision.output !== void 0) return decision;
14963
15409
  const reason = capReason(decision.reason);
14964
15410
  return {
@@ -15011,9 +15457,9 @@ var EMPTY_PAYLOAD = {
15011
15457
  stopHookActive: false,
15012
15458
  raw: {}
15013
15459
  };
15014
- var parseHookPayload = (event, text8, interactive) => {
15015
- if (interactive && text8.trim() === "") return accept(EMPTY_PAYLOAD);
15016
- const parsed = parseJsonObject(text8);
15460
+ var parseHookPayload = (event, text9, interactive) => {
15461
+ if (interactive && text9.trim() === "") return accept(EMPTY_PAYLOAD);
15462
+ const parsed = parseJsonObject(text9);
15017
15463
  if (!parsed.ok) return reject(`stdin: ${parsed.error}`);
15018
15464
  const payload = {
15019
15465
  hookEventName: optionalString(parsed.value, "hook_event_name"),
@@ -15028,53 +15474,13 @@ var parseHookPayload = (event, text8, interactive) => {
15028
15474
  return accept(payload);
15029
15475
  };
15030
15476
 
15031
- // src/hooks/stdin.ts
15032
- var readAll = (source) => new Promise((resolve2, reject2) => {
15033
- if (source.isTTY === true) {
15034
- resolve2("");
15035
- return;
15036
- }
15037
- const chunks = [];
15038
- source.setEncoding?.("utf8");
15039
- source.on("data", (chunk) => {
15040
- chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
15041
- });
15042
- source.on("end", () => {
15043
- resolve2(chunks.join(""));
15044
- });
15045
- source.on("error", reject2);
15046
- });
15047
- var DeadlineError = class extends Error {
15048
- ms;
15049
- constructor(ms) {
15050
- super(`deadline of ${String(ms)} ms exceeded`);
15051
- this.name = "DeadlineError";
15052
- this.ms = ms;
15053
- }
15054
- };
15055
- var withDeadline = (work, ms, startTimer) => new Promise((resolve2, reject2) => {
15056
- const cancel = startTimer(ms, () => {
15057
- reject2(new DeadlineError(ms));
15058
- });
15059
- work.then(
15060
- (value) => {
15061
- cancel();
15062
- resolve2(value);
15063
- },
15064
- (error) => {
15065
- cancel();
15066
- reject2(error instanceof Error ? error : new Error(String(error)));
15067
- }
15068
- );
15069
- });
15070
-
15071
15477
  // src/hooks/runner.ts
15072
15478
  var HOOK_PROBE_ENV = "FINITEMONKEY_HOOK_PROBE";
15073
15479
  var messageOf2 = (error) => error instanceof Error ? error.message : String(error);
15074
15480
  var decide2 = (input, handlers) => composeDecisions(input, handlers[input.event] ?? [allow]);
15075
15481
  var evaluate = async (event, options) => {
15076
- const text8 = await readAll(options.stdin);
15077
- const parsed = parseHookPayload(event, text8, options.stdin.isTTY === true);
15482
+ const text9 = await readAll(options.stdin);
15483
+ const parsed = parseHookPayload(event, text9, options.stdin.isTTY === true);
15078
15484
  if (!parsed.ok) return { kind: "failed", payload: void 0, detail: parsed.error };
15079
15485
  const payload = parsed.value;
15080
15486
  if (options.env?.[HOOK_PROBE_ENV] === "1") return { kind: "decided", payload, decision: { kind: "allow" } };
@@ -15151,7 +15557,7 @@ var runHook = async (event, options) => {
15151
15557
  var PROBE_TIMEOUT_MS2 = 1e4;
15152
15558
  var SLOW_HOOK_MS = 1500;
15153
15559
  var DEFAULT_TIMEOUT_S = 60;
15154
- var expand = (text8, vars) => text8.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
15560
+ var expand = (text9, vars) => text9.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
15155
15561
  const name = braced ?? bare ?? "";
15156
15562
  return vars[name] ?? match;
15157
15563
  });
@@ -15200,7 +15606,7 @@ var run2 = (exec, invocation, cwd, env, input) => exec({
15200
15606
  env: { ...env, [HOOK_PROBE_ENV]: "1" },
15201
15607
  ...input === void 0 ? {} : { input }
15202
15608
  });
15203
- var firstLine = (text8) => maskText(text8.trim().split("\n")[0] ?? "").slice(0, 160);
15609
+ var firstLine = (text9) => maskText(text9.trim().split("\n")[0] ?? "").slice(0, 160);
15204
15610
  var classify2 = (result) => {
15205
15611
  if (!result.found) return { kind: "failed", problem: "missing", detail: "" };
15206
15612
  if (result.timedOut) return { kind: "failed", problem: "timeout", detail: "" };
@@ -15412,7 +15818,7 @@ var hookSchemaProblems = (hooks, claude) => {
15412
15818
  };
15413
15819
 
15414
15820
  // src/doctor/hook-sources.ts
15415
- import { join as join33 } from "node:path";
15821
+ import { join as join35 } from "node:path";
15416
15822
  var entriesOf = (source, hooks, own) => Object.entries(asRecord(hooks)).flatMap(
15417
15823
  ([event, groups]) => asList(groups).flatMap((group) => {
15418
15824
  const record3 = asRecord(group);
@@ -15425,31 +15831,31 @@ var projectHookFiles = (env) => readSettings(env).flatMap(({ scope: scope2, path
15425
15831
  const source = PROJECT_SCOPES[scope2];
15426
15832
  return source === void 0 || !("hooks" in settings) ? [] : [{ source, path, hooks: settings["hooks"] }];
15427
15833
  });
15428
- var pluginsDir = ({ env, runtime }) => join33(env["CLAUDE_CONFIG_DIR"] ?? join33(runtime.homeDir(), ".claude"), "plugins");
15834
+ var pluginsDir = ({ env, runtime }) => join35(env["CLAUDE_CONFIG_DIR"] ?? join35(runtime.homeDir(), ".claude"), "plugins");
15429
15835
  var installRecord = (value, root) => {
15430
15836
  const records = Array.isArray(value) ? value.filter(isRecord) : isRecord(value) ? [value] : [];
15431
15837
  return records.find((record3) => record3["projectPath"] === root) ?? records[0];
15432
15838
  };
15433
15839
  var readJson = (env, path) => {
15434
- const text8 = env.runtime.readText(path);
15435
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
15840
+ const text9 = env.runtime.readText(path);
15841
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
15436
15842
  return parsed?.ok === true ? parsed.value : void 0;
15437
15843
  };
15438
15844
  var installedPlugin = (env) => {
15439
- const index = readJson(env, toAbsolutePath(join33(pluginsDir(env), "installed_plugins.json")));
15845
+ const index = readJson(env, toAbsolutePath(join35(pluginsDir(env), "installed_plugins.json")));
15440
15846
  const record3 = installRecord(asRecord(index?.["plugins"])[PLUGIN_ID], env.root);
15441
15847
  const installPath = record3 === void 0 ? void 0 : optionalString(record3, "installPath");
15442
15848
  if (record3 === void 0 || installPath === void 0) return void 0;
15443
15849
  const root = toAbsolutePath(installPath);
15444
- const manifest = readJson(env, toAbsolutePath(join33(root, ".claude-plugin", "plugin.json")));
15445
- const hooksPath = toAbsolutePath(join33(root, "hooks", "hooks.json"));
15446
- const text8 = env.runtime.readText(hooksPath);
15447
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
15850
+ const manifest = readJson(env, toAbsolutePath(join35(root, ".claude-plugin", "plugin.json")));
15851
+ const hooksPath = toAbsolutePath(join35(root, "hooks", "hooks.json"));
15852
+ const text9 = env.runtime.readText(hooksPath);
15853
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
15448
15854
  return {
15449
15855
  root,
15450
15856
  version: (manifest === void 0 ? void 0 : optionalString(manifest, "version")) ?? optionalString(record3, "version"),
15451
15857
  hooksFile: parsed?.ok === true ? { source: "plugin", path: hooksPath, hooks: parsed.value["hooks"] } : void 0,
15452
- hooksText: text8.ok ? text8.value : void 0
15858
+ hooksText: text9.ok ? text9.value : void 0
15453
15859
  };
15454
15860
  };
15455
15861
  var PRECEDENCE2 = ["managed", "local", "project", "user"];
@@ -15479,15 +15885,15 @@ var schemaFinding = (file, problems, claude) => problems.length === 0 ? [] : [
15479
15885
  )
15480
15886
  ];
15481
15887
  var unreadableProjectFiles = (env) => settingsPaths(env).filter(({ scope: scope2 }) => scope2 === "project" || scope2 === "local").flatMap(({ path }) => {
15482
- const text8 = env.runtime.readText(path);
15483
- return text8.ok && text8.value.trim() !== "" && !parseJsonObject(text8.value).ok ? [path] : [];
15888
+ const text9 = env.runtime.readText(path);
15889
+ return text9.ok && text9.value.trim() !== "" && !parseJsonObject(text9.value).ok ? [path] : [];
15484
15890
  });
15485
15891
  var schemaFindings = (env, plugin, claude) => {
15486
15892
  const label2 = claude === void 0 ? "?" : claude.join(".");
15487
15893
  const files = [...projectHookFiles(env), ...plugin?.hooksFile === void 0 ? [] : [plugin.hooksFile]];
15488
15894
  const invalidJson = [
15489
15895
  ...unreadableProjectFiles(env),
15490
- ...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(join34(plugin.root, "hooks", "hooks.json"))] : []
15896
+ ...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(join36(plugin.root, "hooks", "hooks.json"))] : []
15491
15897
  ];
15492
15898
  return [
15493
15899
  ...invalidJson.flatMap((file) => schemaFinding(file, ["not valid JSON"], label2)),
@@ -15508,7 +15914,7 @@ var pluginFindings = (env, projectOwn) => {
15508
15914
  ];
15509
15915
  };
15510
15916
  var coverageFindings = (env, entries) => {
15511
- const installed = env.runtime.stat(toAbsolutePath(join34(env.root, ...LOCK_FILE.split("/")))) !== void 0;
15917
+ const installed = env.runtime.stat(toAbsolutePath(join36(env.root, ...LOCK_FILE.split("/")))) !== void 0;
15512
15918
  if (entries.length === 0 && !installed) {
15513
15919
  return [finding("hooks.registered", "warn", "doctor.hooks.none", {}, "doctor.hooks.fix_none")];
15514
15920
  }
@@ -15560,21 +15966,21 @@ var checkHooks = (env) => {
15560
15966
  };
15561
15967
 
15562
15968
  // src/doctor/instructions.ts
15563
- import { join as join35 } from "node:path";
15969
+ import { join as join37 } from "node:path";
15564
15970
  var INSTRUCTION_LINE_CEILING = 200;
15565
15971
  var TOP_INSTRUCTIONS = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"];
15566
15972
  var RULES_DIR = ".claude/rules";
15567
15973
  var SHOWN = 3;
15568
15974
  var readProjectFile = (env, relative11) => {
15569
- const text8 = env.runtime.readText(toAbsolutePath(join35(env.root, ...relative11.split("/"))));
15570
- return text8.ok ? text8.value : void 0;
15975
+ const text9 = env.runtime.readText(toAbsolutePath(join37(env.root, ...relative11.split("/"))));
15976
+ return text9.ok ? text9.value : void 0;
15571
15977
  };
15572
15978
  var instructionFiles = (env) => {
15573
- const listed4 = env.runtime.listDir(toAbsolutePath(join35(env.root, ...RULES_DIR.split("/"))));
15979
+ const listed4 = env.runtime.listDir(toAbsolutePath(join37(env.root, ...RULES_DIR.split("/"))));
15574
15980
  const rules = listed4.ok ? listed4.value.filter((name) => name.endsWith(".md")).map((name) => `${RULES_DIR}/${name}`) : [];
15575
15981
  return [...TOP_INSTRUCTIONS, ...rules].flatMap((path) => {
15576
- const text8 = readProjectFile(env, path);
15577
- return text8 === void 0 ? [] : [{ path, text: text8 }];
15982
+ const text9 = readProjectFile(env, path);
15983
+ return text9 === void 0 ? [] : [{ path, text: text9 }];
15578
15984
  });
15579
15985
  };
15580
15986
  var shortList = (items) => items.length <= SHOWN ? items.join(", ") : `${items.slice(0, SHOWN).join(", ")} (+${String(items.length - SHOWN)})`;
@@ -15622,10 +16028,10 @@ var hiddenInstructions = (env, deny2) => {
15622
16028
  ];
15623
16029
  };
15624
16030
  var localFile = (env) => readProjectFile(env, "CLAUDE.local.md") === void 0 ? [] : [finding("instructions.local", "warn", "doctor.instructions.local", {}, "doctor.instructions.fix_local")];
15625
- var lineCount = (text8) => text8.split("\n").length - (text8.endsWith("\n") ? 1 : 0);
16031
+ var lineCount = (text9) => text9.split("\n").length - (text9.endsWith("\n") ? 1 : 0);
15626
16032
  var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
15627
- const text8 = readProjectFile(env, file);
15628
- const lines3 = text8 === void 0 ? 0 : lineCount(text8);
16033
+ const text9 = readProjectFile(env, file);
16034
+ const lines3 = text9 === void 0 ? 0 : lineCount(text9);
15629
16035
  if (lines3 <= INSTRUCTION_LINE_CEILING) return [];
15630
16036
  return [
15631
16037
  finding(
@@ -15638,13 +16044,13 @@ var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
15638
16044
  ];
15639
16045
  });
15640
16046
  var MIN_RULE_WORDS = 6;
15641
- var ruleLines = (text8) => text8.split("\n").map((raw) => withoutCarriageReturn(raw).trim()).filter((line2) => !/^(#|\||```|@|>)/.test(line2)).map(
16047
+ var ruleLines = (text9) => text9.split("\n").map((raw) => withoutCarriageReturn(raw).trim()).filter((line2) => !/^(#|\||```|@|>)/.test(line2)).map(
15642
16048
  (line2) => line2.replace(/^([-*+]|\d+[.)])\s+/, "").replace(/\s+/g, " ").toLowerCase()
15643
16049
  ).filter((line2) => line2.split(" ").length >= MIN_RULE_WORDS);
15644
16050
  var duplicatedRules = (files) => {
15645
16051
  const seen = /* @__PURE__ */ new Map();
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));
16052
+ for (const { path, text: text9 } of files) {
16053
+ for (const line2 of ruleLines(text9)) seen.set(line2, (seen.get(line2) ?? /* @__PURE__ */ new Set()).add(path));
15648
16054
  }
15649
16055
  return [...seen].filter(([, paths]) => paths.size > 1).map(([line2]) => line2);
15650
16056
  };
@@ -15675,12 +16081,12 @@ var checkInstructions = (env) => {
15675
16081
  };
15676
16082
 
15677
16083
  // src/doctor/line-endings.ts
15678
- import { join as join36 } from "node:path";
16084
+ import { join as join38 } from "node:path";
15679
16085
  var LF_RULE = /^\*\s.*\beol=lf\b/m;
15680
16086
  var checkLineEndings = ({ root, runtime }) => {
15681
16087
  const autocrlf = runtime.git(["config", "--get", "core.autocrlf"], root);
15682
16088
  if (!autocrlf.ok || autocrlf.stdout.trim().toLowerCase() !== "true") return [];
15683
- const attributes = runtime.readText(toAbsolutePath(join36(root, ".gitattributes")));
16089
+ const attributes = runtime.readText(toAbsolutePath(join38(root, ".gitattributes")));
15684
16090
  if (attributes.ok && LF_RULE.test(attributes.value)) return [];
15685
16091
  return [
15686
16092
  finding("git.autocrlf", "warn", "doctor.git.autocrlf", { file: ".gitattributes" }, "doctor.git.fix_autocrlf")
@@ -15688,25 +16094,25 @@ var checkLineEndings = ({ root, runtime }) => {
15688
16094
  };
15689
16095
 
15690
16096
  // src/doctor/memory.ts
15691
- import { join as join37 } from "node:path";
16097
+ import { join as join39 } from "node:path";
15692
16098
  var ceilingOf = (config) => {
15693
16099
  const value = configAt(parseConfigYaml(config ?? ""), "memory", "maxLines");
15694
16100
  const parsed = typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
15695
16101
  return Number.isInteger(parsed) && parsed > 0 ? parsed : AGENTS_MAX_LINES;
15696
16102
  };
15697
- var lineCount2 = (text8) => text8.replace(/\r?\n$/, "").split(/\r?\n/).length;
16103
+ var lineCount2 = (text9) => text9.replace(/\r?\n$/, "").split(/\r?\n/).length;
15698
16104
  var checkAgentsFile = ({ root, runtime }) => {
15699
- const text8 = runtime.readText(toAbsolutePath(join37(root, AGENTS_FILE)));
15700
- if (!text8.ok) return [];
15701
- const config = runtime.readText(toAbsolutePath(join37(root, CONFIG_FILE)));
16105
+ const text9 = runtime.readText(toAbsolutePath(join39(root, AGENTS_FILE)));
16106
+ if (!text9.ok) return [];
16107
+ const config = runtime.readText(toAbsolutePath(join39(root, CONFIG_FILE2)));
15702
16108
  const max = ceilingOf(config.ok ? config.value : void 0);
15703
- const lines3 = lineCount2(text8.value);
16109
+ const lines3 = lineCount2(text9.value);
15704
16110
  const params2 = { file: AGENTS_FILE, lines: lines3, max };
15705
16111
  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)];
15706
16112
  };
15707
16113
 
15708
16114
  // src/doctor/project-files.ts
15709
- import { join as join38 } from "node:path";
16115
+ import { join as join40 } from "node:path";
15710
16116
  var variants = (base, extensions) => extensions.map((extension) => `${base}${extension}`);
15711
16117
  var SCRIPT = [".js", ".cjs", ".mjs", ".ts", ".cts", ".mts"];
15712
16118
  var DATA = ["", ".json", ".yml", ".yaml"];
@@ -15737,7 +16143,7 @@ var CONFIG_GROUPS = [
15737
16143
  files: [...variants("commitlint.config", SCRIPT), ...variants(".commitlintrc", [...DATA, ".js"])]
15738
16144
  }
15739
16145
  ];
15740
- var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(join38(env.root, file)))?.kind === "file");
16146
+ var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(join40(env.root, file)))?.kind === "file");
15741
16147
  var crowded = (env, groups, build) => groups.flatMap(({ name, files }) => {
15742
16148
  const found = present(env, files);
15743
16149
  return found.length > 1 ? [build(name, found.join(", "))] : [];
@@ -15881,9 +16287,9 @@ var manualLines = (ctx, report2) => report2.checks.some((item2) => item2.status
15881
16287
  var writeReport = (ctx, data, result) => {
15882
16288
  const env = doctorEnvFrom(ctx);
15883
16289
  const path = reportPath(env);
15884
- const text8 = reportMarkdown(env, ctx, toEnvelope("doctor", result));
16290
+ const text9 = reportMarkdown(env, ctx, toEnvelope("doctor", result));
15885
16291
  const dir = ctx.runtime.mkdir(toAbsolutePath(dirname7(path)));
15886
- const written = dir.ok ? ctx.runtime.writeAtomic(path, text8) : dir;
16292
+ const written = dir.ok ? ctx.runtime.writeAtomic(path, text9) : dir;
15887
16293
  if (!written.ok) return failureFrom(ctx.t, applyProblem({ path, detail: written.error }));
15888
16294
  const human2 = [...result.human ?? [], ctx.t("doctor.report.written", { path })];
15889
16295
  return { ...result, data: { ...data, reportPath: path }, human: human2 };
@@ -15914,6 +16320,100 @@ var doctorCommand = {
15914
16320
  run: (ctx, args2) => Promise.resolve(runDoctor(ctx, args2.values["fix"] === true, args2.values["report"] === true))
15915
16321
  };
15916
16322
 
16323
+ // src/gain/gain.ts
16324
+ import { join as join41 } from "node:path";
16325
+ var GAIN_SCHEMA = "finitemonkey/gain@1";
16326
+ var DAY_MS2 = 864e5;
16327
+ var SAVE_WINDOW_MS = DAY_MS2;
16328
+ var readLog = (runtime, paths) => {
16329
+ const text9 = runtime.readText(paths.log);
16330
+ if (!text9.ok) return [];
16331
+ return text9.value.split("\n").flatMap((line2) => {
16332
+ const parsed = parseJsonObject(line2);
16333
+ if (!parsed.ok || !isRecord(parsed.value)) return [];
16334
+ const { at, event } = parsed.value;
16335
+ const time = typeof at === "string" ? Date.parse(at) : Number.NaN;
16336
+ return typeof event === "string" && Number.isFinite(time) ? [{ at: time, event }] : [];
16337
+ });
16338
+ };
16339
+ var readGates = (runtime, paths) => {
16340
+ const listed4 = runtime.listDir(toAbsolutePath(join41(paths.dir, "evidence")));
16341
+ const files = listed4.ok ? listed4.value.filter((name) => name.endsWith(".jsonl")) : [];
16342
+ return files.flatMap((name) => readLedger2(runtime, paths.evidence(name.replace(/\.jsonl$/, "")))).filter((entry2) => entry2.check === GATE_CHECK);
16343
+ };
16344
+ var countsIn = (log, gates, from, to) => {
16345
+ const inWindow = (at) => at >= from && at < to;
16346
+ const lines3 = log.filter((line2) => inWindow(line2.at));
16347
+ const runs = gates.filter((entry2) => inWindow(Date.parse(entry2.at)));
16348
+ const passedAt = gates.filter((entry2) => entry2.status === "passed").map((entry2) => Date.parse(entry2.at));
16349
+ const blocks = lines3.filter((line2) => line2.event === "guard_block");
16350
+ return {
16351
+ blocks: blocks.length,
16352
+ saves: blocks.filter((block4) => passedAt.some((at) => at > block4.at && at - block4.at <= SAVE_WINDOW_MS)).length,
16353
+ releases: lines3.filter((line2) => line2.event === "person.allow").length,
16354
+ falsePositives: lines3.filter((line2) => line2.event === "person.allow").length + lines3.filter((line2) => line2.event === "guard_false_positive").length,
16355
+ gateRuns: runs.length,
16356
+ gateFailed: runs.filter((entry2) => entry2.status === "failed").length,
16357
+ gateUnverified: runs.filter((entry2) => entry2.status === "unverified").length,
16358
+ gateMs: runs.reduce((sum, entry2) => sum + (entry2.durationMs ?? 0), 0)
16359
+ };
16360
+ };
16361
+ var gainReport = (runtime, paths, days) => {
16362
+ const to = Date.parse(runtime.now());
16363
+ const from = to - days * DAY_MS2;
16364
+ const log = readLog(runtime, paths);
16365
+ const gates = readGates(runtime, paths);
16366
+ return {
16367
+ schema: GAIN_SCHEMA,
16368
+ days,
16369
+ from: new Date(from).toISOString(),
16370
+ to: new Date(to).toISOString(),
16371
+ current: countsIn(log, gates, from, to + 1),
16372
+ previous: countsIn(log, gates, from - days * DAY_MS2, from)
16373
+ };
16374
+ };
16375
+
16376
+ // src/commands/gain.ts
16377
+ var DEFAULT_DAYS = 30;
16378
+ var trend = (ctx, now, before) => now === before ? "" : ` (${ctx.t(now > before ? "gain.up" : "gain.down", { delta: Math.abs(now - before) })})`;
16379
+ var ROWS = [
16380
+ { key: "gain.row.blocks", pick: (counts) => counts.blocks },
16381
+ { key: "gain.row.saves", pick: (counts) => counts.saves },
16382
+ { key: "gain.row.false_positives", pick: (counts) => counts.falsePositives },
16383
+ { key: "gain.row.gate_runs", pick: (counts) => counts.gateRuns },
16384
+ { key: "gain.row.gate_failed", pick: (counts) => counts.gateFailed },
16385
+ { key: "gain.row.gate_unverified", pick: (counts) => counts.gateUnverified }
16386
+ ];
16387
+ var linesOf = (ctx, report2) => [
16388
+ ctx.t("gain.title", { days: report2.days }),
16389
+ ...ROWS.map(
16390
+ ({ key, pick: pick4 }) => ` ${ctx.t(key, { count: pick4(report2.current) })}${trend(ctx, pick4(report2.current), pick4(report2.previous))}`
16391
+ ),
16392
+ ` ${ctx.t("gain.row.gate_time", { minutes: Math.round(report2.current.gateMs / 6e4) })}`,
16393
+ ctx.t("gain.footer")
16394
+ ];
16395
+ var runGain = (ctx, rawDays) => {
16396
+ const days = rawDays === void 0 ? DEFAULT_DAYS : Number.parseInt(rawDays, 10);
16397
+ if (!Number.isInteger(days) || days < 1 || days > 365) {
16398
+ return failureFrom(
16399
+ ctx.t,
16400
+ usageProblem("invalid_arguments", "error.invalid_arguments", "finitemonkey gain --days 30", {
16401
+ detail: "--days 1-365"
16402
+ })
16403
+ );
16404
+ }
16405
+ const report2 = gainReport(ctx.runtime, fmPaths(findRepoRoot(ctx.runtime, ctx.cwd) ?? ctx.cwd), days);
16406
+ return success(report2, linesOf(ctx, report2));
16407
+ };
16408
+ var gainCommand = {
16409
+ path: ["gain"],
16410
+ summaryKey: "command.gain.summary",
16411
+ usage: "finitemonkey gain [--days 1-365]",
16412
+ status: "ready",
16413
+ options: { days: { type: "string" } },
16414
+ run: (ctx, args2) => Promise.resolve(runGain(ctx, stringOption(args2.values, "days")))
16415
+ };
16416
+
15917
16417
  // src/gate/baseline-run.ts
15918
16418
  var runBaseline = (options) => {
15919
16419
  const ready = prepareGate({ ...options, noSkip: true });
@@ -16178,17 +16678,6 @@ var checksResult = (t, schema, code, checks) => {
16178
16678
  };
16179
16679
  };
16180
16680
 
16181
- // src/commands/git-problems.ts
16182
- var notRepository = (dir) => ({
16183
- code: "not_a_git_repo",
16184
- messageKey: "error.not_a_git_repo",
16185
- params: { dir },
16186
- fix: "git init",
16187
- exitCode: ExitCode.NotConfigured
16188
- });
16189
- var gitProblem = (code, messageKey, detail, fix, exitCode = ExitCode.CheckFailed) => ({ code, messageKey, params: { detail }, fix, exitCode });
16190
- var repoOf2 = (ctx) => openRepo(ctx.runtime, ctx.cwd);
16191
-
16192
16681
  // src/commands/git-checks.ts
16193
16682
  var COMMIT_MSG_SCHEMA = "finitemonkey/commit-msg@1";
16194
16683
  var PUSH_CHECK_SCHEMA = "finitemonkey/push-check@1";
@@ -16209,15 +16698,15 @@ var commitMsgCommand = {
16209
16698
  usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: "<message-file>" })
16210
16699
  )
16211
16700
  );
16212
- const text8 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
16213
- if (!text8.ok)
16701
+ const text9 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
16702
+ if (!text9.ok)
16214
16703
  return Promise.resolve(
16215
16704
  failureFrom(
16216
16705
  ctx.t,
16217
16706
  usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: file })
16218
16707
  )
16219
16708
  );
16220
- const failures = commitMessageProblems(text8.value);
16709
+ const failures = commitMessageProblems(text9.value);
16221
16710
  const fix = "git commit --amend, using type(scope): subject and no AI attribution lines";
16222
16711
  return Promise.resolve(
16223
16712
  checksResult(ctx.t, COMMIT_MSG_SCHEMA, "githook_failed", [{ id: "commit-messages", failures, fix }])
@@ -16231,7 +16720,7 @@ var pushCheckCommand = {
16231
16720
  status: "ready",
16232
16721
  maxPositionals: 2,
16233
16722
  run: async (ctx, args2) => {
16234
- const repo = repoOf2(ctx);
16723
+ const repo = repoOf(ctx);
16235
16724
  if (repo === void 0) return failureFrom(ctx.t, notRepository(ctx.cwd));
16236
16725
  const piped = parsePushUpdates(ctx.io.stdin.isTTY === true ? "" : await readAll(ctx.io.stdin));
16237
16726
  const updates = piped.length > 0 ? piped : updatesFromEnv(ctx.env);
@@ -16246,7 +16735,7 @@ var ciGateCommand = {
16246
16735
  status: "ready",
16247
16736
  options: { base: { type: "string" } },
16248
16737
  run: (ctx, args2) => {
16249
- const repo = repoOf2(ctx);
16738
+ const repo = repoOf(ctx);
16250
16739
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
16251
16740
  const base = ciBase(ctx.runtime, repo, ctx.env, stringOption(args2.values, "base"));
16252
16741
  if (!baseExists(ctx.runtime, repo, base))
@@ -16332,7 +16821,7 @@ var mergeVerifyCommand = {
16332
16821
  options: { since: { type: "string" }, dropped: { type: "string" } },
16333
16822
  maxPositionals: 1,
16334
16823
  run: (ctx, args2) => {
16335
- const repo = repoOf2(ctx);
16824
+ const repo = repoOf(ctx);
16336
16825
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
16337
16826
  const dropped = stringOption(args2.values, "dropped");
16338
16827
  const targets2 = targetsOf2(ctx, repo, args2.positionals[0], stringOption(args2.values, "since"));
@@ -16355,11 +16844,11 @@ var mergeVerifyCommand = {
16355
16844
  var REFLOG_LIMIT = 300;
16356
16845
  var LIST_LIMIT = 50;
16357
16846
  var SHA = /^[0-9a-f]{40,64}$/;
16358
- var linesOf = (text8) => text8.split("\n").filter((line2) => line2.trim() !== "");
16847
+ var linesOf2 = (text9) => text9.split("\n").filter((line2) => line2.trim() !== "");
16359
16848
  var reflogLines = (runtime, repo) => {
16360
16849
  const result = runtime.git(["reflog", "show", "--all", `-n${String(REFLOG_LIMIT)}`, "--format=%H %gD"], repo.root);
16361
16850
  if (!result.ok) return [];
16362
- return linesOf(result.stdout).flatMap((line2) => {
16851
+ return linesOf2(result.stdout).flatMap((line2) => {
16363
16852
  const [sha, ref] = line2.split(" ");
16364
16853
  return sha === void 0 || ref === void 0 || !SHA.test(sha) || ref.startsWith("refs/finitemonkey/") ? [] : [{ sha, ref }];
16365
16854
  });
@@ -16367,11 +16856,11 @@ var reflogLines = (runtime, repo) => {
16367
16856
  var unreachable = (runtime, repo, shas) => {
16368
16857
  if (shas.length === 0) return /* @__PURE__ */ new Set();
16369
16858
  const result = runtime.git(["rev-list", ...shas, "--not", "--branches", "--tags", "--remotes"], repo.root);
16370
- return new Set(result.ok ? linesOf(result.stdout) : []);
16859
+ return new Set(result.ok ? linesOf2(result.stdout) : []);
16371
16860
  };
16372
16861
  var fsckDangling = (runtime, repo) => {
16373
16862
  const result = runtime.git(["fsck", "--no-reflogs", "--no-progress", "--dangling"], repo.root);
16374
- const lines3 = linesOf(result.stdout);
16863
+ const lines3 = linesOf2(result.stdout);
16375
16864
  const pick4 = (kind) => lines3.flatMap((line2) => {
16376
16865
  const [label2, type, sha] = line2.split(" ");
16377
16866
  return label2 === "dangling" && type === kind && sha !== void 0 ? [sha] : [];
@@ -16382,7 +16871,7 @@ var describe = (runtime, repo, shas) => {
16382
16871
  if (shas.length === 0) return /* @__PURE__ */ new Map();
16383
16872
  const result = runtime.git(["log", "--no-walk=unsorted", "--format=%H%x09%cr%x09%s", ...shas], repo.root);
16384
16873
  return new Map(
16385
- linesOf(result.stdout).flatMap((line2) => {
16874
+ linesOf2(result.stdout).flatMap((line2) => {
16386
16875
  const [sha, when, ...subject2] = line2.split(" ");
16387
16876
  return sha === void 0 ? [] : [[sha, [when ?? "", subject2.join(" ")]]];
16388
16877
  })
@@ -16444,7 +16933,7 @@ var rescueCommand = {
16444
16933
  status: "ready",
16445
16934
  options: { restore: { type: "string" }, branch: { type: "string" } },
16446
16935
  run: (ctx, args2) => {
16447
- const repo = repoOf2(ctx);
16936
+ const repo = repoOf(ctx);
16448
16937
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
16449
16938
  const sha = stringOption(args2.values, "restore");
16450
16939
  const branch = stringOption(args2.values, "branch");
@@ -16476,7 +16965,7 @@ var rescueCommand = {
16476
16965
  };
16477
16966
 
16478
16967
  // src/git/undo.ts
16479
- import { basename as basename3, dirname as dirname8, join as join39 } from "node:path";
16968
+ import { basename as basename3, dirname as dirname8, join as join42 } from "node:path";
16480
16969
  var INDEX_SUBJECT = "finitemonkey index:";
16481
16970
  var gitLine = (runtime, repo, args2) => {
16482
16971
  const result = runtime.git(args2, repo.root);
@@ -16519,7 +17008,7 @@ var findSnapshot = (views, wanted) => {
16519
17008
  };
16520
17009
  var stampOf2 = (iso) => iso.replace(/[:.]/g, "-");
16521
17010
  var worktreePath = (runtime, repo) => {
16522
- const base = join39(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
17011
+ const base = join42(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
16523
17012
  return toAbsolutePath(runtime.stat(toAbsolutePath(base)) === void 0 ? base : `${base}-2`);
16524
17013
  };
16525
17014
  var intoWorktree = (runtime, repo, view2) => {
@@ -16563,7 +17052,7 @@ var snapshotCommand = {
16563
17052
  status: "ready",
16564
17053
  options: { message: { type: "string", short: "m" } },
16565
17054
  run: (ctx, args2) => {
16566
- const repo = repoOf2(ctx);
17055
+ const repo = repoOf(ctx);
16567
17056
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
16568
17057
  const message = args2.values["message"];
16569
17058
  const op = typeof message === "string" && message.trim() !== "" ? message.trim() : "manual";
@@ -16596,7 +17085,7 @@ var undoCommand = {
16596
17085
  options: { list: { type: "boolean" }, "in-place": { type: "boolean" } },
16597
17086
  maxPositionals: 1,
16598
17087
  run: (ctx, args2) => {
16599
- const repo = repoOf2(ctx);
17088
+ const repo = repoOf(ctx);
16600
17089
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
16601
17090
  const views = snapshotViews(ctx.runtime, repo);
16602
17091
  if (args2.values["list"] === true) return Promise.resolve(listResult(ctx, views));
@@ -16644,7 +17133,7 @@ var GLOBAL_FLAGS = [
16644
17133
  { flag: "--help, -h", key: "flag.help" }
16645
17134
  ];
16646
17135
  var exitCodeHelp = (ctx) => EXIT_CODE_NAMES.map((name) => ({ code: ExitCode[name], name, summary: ctx.t(exitCodeDocs[ExitCode[name]]) }));
16647
- var pad = (text8, width) => text8.padEnd(Math.max(width, text8.length + 1));
17136
+ var pad = (text9, width) => text9.padEnd(Math.max(width, text9.length + 1));
16648
17137
  var describe2 = (ctx, spec2) => ({
16649
17138
  name: commandName(spec2),
16650
17139
  usage: spec2.usage,
@@ -16754,7 +17243,7 @@ var hookCommand = {
16754
17243
  };
16755
17244
 
16756
17245
  // src/commands/i18n-check.ts
16757
- import { join as join40 } from "node:path";
17246
+ import { join as join43 } from "node:path";
16758
17247
 
16759
17248
  // src/i18n/message.ts
16760
17249
  var ARG = /^\s*([a-zA-Z][a-zA-Z0-9_]*)\s*(?:,\s*(plural|select)\s*,)?/;
@@ -16786,10 +17275,10 @@ var parseBranches = (body2, inPlural) => {
16786
17275
  };
16787
17276
  var parseNodes = (source, inPlural) => {
16788
17277
  const nodes = [];
16789
- let text8 = "";
17278
+ let text9 = "";
16790
17279
  const flush2 = () => {
16791
- if (text8 !== "") nodes.push({ kind: "text", text: text8 });
16792
- text8 = "";
17280
+ if (text9 !== "") nodes.push({ kind: "text", text: text9 });
17281
+ text9 = "";
16793
17282
  };
16794
17283
  for (let index = 0; index < source.length; index += 1) {
16795
17284
  const char = source.charAt(index);
@@ -16799,7 +17288,7 @@ var parseNodes = (source, inPlural) => {
16799
17288
  continue;
16800
17289
  }
16801
17290
  if (char !== "{") {
16802
- text8 += char;
17291
+ text9 += char;
16803
17292
  continue;
16804
17293
  }
16805
17294
  const cursor = { source, index: index + 1 };
@@ -16807,18 +17296,18 @@ var parseNodes = (source, inPlural) => {
16807
17296
  const inner = end < 0 ? void 0 : source.slice(index + 1, end);
16808
17297
  const match = inner === void 0 ? null : ARG.exec(inner);
16809
17298
  if (inner === void 0 || match === null) {
16810
- text8 += char;
17299
+ text9 += char;
16811
17300
  continue;
16812
17301
  }
16813
17302
  const name = match[1] ?? "";
16814
17303
  const type = match[2] === "plural" || match[2] === "select" ? match[2] : void 0;
16815
17304
  if (type === void 0 && match[0].trim().length !== inner.trim().length) {
16816
- text8 += char;
17305
+ text9 += char;
16817
17306
  continue;
16818
17307
  }
16819
17308
  const branches = type === void 0 ? void 0 : parseBranches(inner.slice(match[0].length), type === "plural");
16820
17309
  if (type !== void 0 && branches === void 0) {
16821
- text8 += char;
17310
+ text9 += char;
16822
17311
  continue;
16823
17312
  }
16824
17313
  flush2();
@@ -16941,8 +17430,8 @@ var RELEASE_HINTS = [
16941
17430
  /\bFINITEMONKEY_[A-Z_]+/,
16942
17431
  /\b[A-Z][A-Z0-9_]{2,}=/
16943
17432
  ];
16944
- var releaseHints = (text8) => RELEASE_HINTS.flatMap((pattern) => {
16945
- const match = pattern.exec(text8);
17433
+ var releaseHints = (text9) => RELEASE_HINTS.flatMap((pattern) => {
17434
+ const match = pattern.exec(text9);
16946
17435
  return match === null ? [] : [match[0]];
16947
17436
  });
16948
17437
  var hintProblems = (lang, catalog) => Object.keys(catalog).filter((key) => MODEL_FACING.test(key)).sort().flatMap((key) => {
@@ -16980,10 +17469,10 @@ var unreadable = (dir, file, detail) => ({
16980
17469
  exitCode: ExitCode.NotConfigured
16981
17470
  });
16982
17471
  var readCatalog = (ctx, dir, lang) => {
16983
- const file = toAbsolutePath(join40(dir, `${lang}.json`));
16984
- const text8 = ctx.runtime.readText(file);
16985
- if (!text8.ok) return reject(unreadable(dir, file, text8.error));
16986
- const parsed = parseJsonObject(text8.value);
17472
+ const file = toAbsolutePath(join43(dir, `${lang}.json`));
17473
+ const text9 = ctx.runtime.readText(file);
17474
+ if (!text9.ok) return reject(unreadable(dir, file, text9.error));
17475
+ const parsed = parseJsonObject(text9.value);
16987
17476
  return parsed.ok ? parsed : reject(unreadable(dir, file, parsed.error));
16988
17477
  };
16989
17478
  var loadCatalogs = (ctx, dir) => {
@@ -17003,8 +17492,8 @@ var describeProblem = (ctx, problem4) => {
17003
17492
  return ` ${problem4.lang} ${problem4.key} ${kind}`;
17004
17493
  };
17005
17494
  var loadReview = (ctx, dir) => {
17006
- const text8 = ctx.runtime.readText(toAbsolutePath(join40(dir, "review.json")));
17007
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
17495
+ const text9 = ctx.runtime.readText(toAbsolutePath(join43(dir, "review.json")));
17496
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
17008
17497
  if (parsed?.ok !== true) return void 0;
17009
17498
  const statuses = (lang) => {
17010
17499
  const value = parsed.value[lang];
@@ -17044,7 +17533,7 @@ var i18nCheckCommand = {
17044
17533
  };
17045
17534
 
17046
17535
  // src/history/look-back.ts
17047
- import { join as join41 } from "node:path";
17536
+ import { join as join44 } from "node:path";
17048
17537
 
17049
17538
  // src/history/patterns.ts
17050
17539
  import { posix as posix5 } from "node:path";
@@ -17149,8 +17638,8 @@ var addResults = (draft, blocks) => {
17149
17638
  let any = false;
17150
17639
  for (const block4 of blocks) {
17151
17640
  if (!isRecord(block4) || block4["type"] !== "tool_result" || typeof block4["tool_use_id"] !== "string") continue;
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 });
17641
+ const text9 = textOf3(block4["content"]);
17642
+ draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(text9), text: text9 });
17154
17643
  any = true;
17155
17644
  }
17156
17645
  return any;
@@ -17180,7 +17669,7 @@ var parseTranscript = (jsonl) => {
17180
17669
  // src/history/look-back.ts
17181
17670
  var SAMPLES = 3;
17182
17671
  var projectLogKey = (root) => root.replace(/[^A-Za-z0-9-]/g, "-");
17183
- var projectLogDir = (runtime, root, env) => join41(env["CLAUDE_CONFIG_DIR"] ?? join41(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
17672
+ var projectLogDir = (runtime, root, env) => join44(env["CLAUDE_CONFIG_DIR"] ?? join44(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
17184
17673
  var lookBack = (runtime, root, env) => {
17185
17674
  const dir = projectLogDir(runtime, root, env);
17186
17675
  const listed4 = runtime.listDir(toAbsolutePath(dir));
@@ -17201,9 +17690,9 @@ var lookBack = (runtime, root, env) => {
17201
17690
  };
17202
17691
  let turns = 0;
17203
17692
  for (const name of files) {
17204
- const text8 = runtime.readText(toAbsolutePath(join41(dir, name)));
17205
- if (!text8.ok) continue;
17206
- for (const turn of parseTranscript(text8.value)) {
17693
+ const text9 = runtime.readText(toAbsolutePath(join44(dir, name)));
17694
+ if (!text9.ok) continue;
17695
+ for (const turn of parseTranscript(text9.value)) {
17207
17696
  turns += 1;
17208
17697
  for (const hit3 of hitsIn(turn)) {
17209
17698
  counts[hit3.pattern] += 1;
@@ -17251,7 +17740,7 @@ var walk2 = (a, b) => {
17251
17740
  }
17252
17741
  return ops;
17253
17742
  };
17254
- var splitLines = (text8) => text8 === "" ? [] : text8.replace(/\r?\n$/, "").split(/\r?\n/);
17743
+ var splitLines = (text9) => text9 === "" ? [] : text9.replace(/\r?\n$/, "").split(/\r?\n/);
17255
17744
  var sharedPrefix = (a, b) => {
17256
17745
  let count4 = 0;
17257
17746
  while (count4 < a.length && count4 < b.length && a[count4] === b[count4]) count4 += 1;
@@ -17328,7 +17817,7 @@ var planData = (t, plan, mode, applied, backup) => ({
17328
17817
  diff: planDiff(plan),
17329
17818
  ...backup === void 0 ? {} : { backup }
17330
17819
  });
17331
- var pad2 = (text8, width) => text8.padEnd(width);
17820
+ var pad2 = (text9, width) => text9.padEnd(width);
17332
17821
  var planLines = (t, data, withDiff) => [
17333
17822
  t("install.plan.title", { root: data.root, preset: data.preset }),
17334
17823
  ...data.actions.map(({ path, action }) => ` ${pad2(action, 13)}${path}`),
@@ -17453,7 +17942,7 @@ var applyAllows = (findings, allows, decided) => {
17453
17942
 
17454
17943
  // src/look/node-capture.ts
17455
17944
  import { mkdirSync as mkdirSync3 } from "node:fs";
17456
- import { join as join42 } from "node:path";
17945
+ import { join as join45 } from "node:path";
17457
17946
 
17458
17947
  // src/look/measure.ts
17459
17948
  var measurePage = (doc) => {
@@ -17464,8 +17953,8 @@ var measurePage = (doc) => {
17464
17953
  const walker = doc.createTreeWalker(doc.body, SHOW_TEXT);
17465
17954
  const keys = /* @__PURE__ */ new Set();
17466
17955
  for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
17467
- const text8 = (node.textContent ?? "").trim();
17468
- if (KEY2.test(text8) && !NOT_KEY.test(text8) || PLACEHOLDER2.test(text8)) keys.add(text8.slice(0, 80));
17956
+ const text9 = (node.textContent ?? "").trim();
17957
+ if (KEY2.test(text9) && !NOT_KEY.test(text9) || PLACEHOLDER2.test(text9)) keys.add(text9.slice(0, 80));
17469
17958
  }
17470
17959
  const small = [...doc.querySelectorAll("a, button, input, select, textarea, [role=button], [role=link]")].filter((element) => {
17471
17960
  const box = element.getBoundingClientRect();
@@ -17498,8 +17987,8 @@ var slugOf = (route) => {
17498
17987
  var watch = (page) => {
17499
17988
  const errors = [];
17500
17989
  const failed4 = [];
17501
- const push = (list4, text8) => {
17502
- if (list4.length < MAX_EVENTS) list4.push(text8.slice(0, 200));
17990
+ const push = (list4, text9) => {
17991
+ if (list4.length < MAX_EVENTS) list4.push(text9.slice(0, 200));
17503
17992
  };
17504
17993
  page.on("console", (message) => {
17505
17994
  if (message.type() === "error" && !RESOURCE_ERROR.test(message.text())) push(errors, message.text());
@@ -17529,7 +18018,7 @@ var captureOne = async (browser, request, route, viewport) => {
17529
18018
  const context = await browser.newContext({ viewport: { width: viewport, height: HEIGHT }, reducedMotion: "reduce" });
17530
18019
  const page = await context.newPage();
17531
18020
  const events = watch(page);
17532
- const path = join42(request.dir, `${slugOf(route)}-${String(viewport)}.png`);
18021
+ const path = join45(request.dir, `${slugOf(route)}-${String(viewport)}.png`);
17533
18022
  const loadError = await loadAndShoot(page, `${request.baseUrl}${route}`, path, request.timeoutMs);
17534
18023
  const measure = loadError === void 0 ? parseMeasure(await page.evaluate(MEASURE_SCRIPT)) : void 0;
17535
18024
  await context.close();
@@ -17546,7 +18035,7 @@ var captureAll = async (browser, request) => {
17546
18035
 
17547
18036
  // src/look/node-playwright.ts
17548
18037
  import { createRequire } from "node:module";
17549
- import { join as join43 } from "node:path";
18038
+ import { join as join46 } from "node:path";
17550
18039
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
17551
18040
  var NAMES = ["playwright", "playwright-core"];
17552
18041
  var isPlaywright = (value) => isRecord(value) && isRecord(value["chromium"]) && typeof value["chromium"]["launch"] === "function";
@@ -17561,7 +18050,7 @@ var tryLoad = async (base, name) => {
17561
18050
  }
17562
18051
  };
17563
18052
  var loadPlaywright = async (root) => {
17564
- for (const base of [join43(root, "package.json"), fileURLToPath3(import.meta.url)]) {
18053
+ for (const base of [join46(root, "package.json"), fileURLToPath3(import.meta.url)]) {
17565
18054
  for (const name of NAMES) {
17566
18055
  const found = await tryLoad(base, name);
17567
18056
  if (found !== void 0) return found;
@@ -17582,8 +18071,8 @@ var launchBrowser = async (playwright) => {
17582
18071
 
17583
18072
  // src/look/node-sheet.ts
17584
18073
  import { readFileSync as readFileSync2 } from "node:fs";
17585
- import { join as join44 } from "node:path";
17586
- var escapeHtml = (text8) => text8.replace(/[&<>"]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[char] ?? char);
18074
+ import { join as join47 } from "node:path";
18075
+ var escapeHtml = (text9) => text9.replace(/[&<>"]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[char] ?? char);
17587
18076
  var card = (result) => {
17588
18077
  const image = (() => {
17589
18078
  try {
@@ -17605,7 +18094,7 @@ var contactSheet = async (browser, results, dir) => {
17605
18094
  if (shown3.length === 0) return void 0;
17606
18095
  const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
17607
18096
  await page.setContent(`<!doctype html><style>${STYLE}</style>${shown3.map(card).join("")}`);
17608
- const path = join44(dir, "contact-sheet.png");
18097
+ const path = join47(dir, "contact-sheet.png");
17609
18098
  await page.screenshot({ path, fullPage: true });
17610
18099
  await page.close();
17611
18100
  return path;
@@ -17945,12 +18434,12 @@ var proseCheckCommand = {
17945
18434
  };
17946
18435
 
17947
18436
  // src/review/packet.ts
17948
- import { join as join45 } from "node:path";
18437
+ import { join as join48 } from "node:path";
17949
18438
  var REVIEW_STATE_FILE = ".finitemonkey/review/state.json";
17950
18439
  var FULL_ROUNDS = 3;
17951
18440
  var REVIEW_FILE_MAX_LINES = 200;
17952
18441
  var LEDGER_MAX_LINES = 100;
17953
- var statePath = (root) => toAbsolutePath(join45(root, REVIEW_STATE_FILE));
18442
+ var statePath = (root) => toAbsolutePath(join48(root, REVIEW_STATE_FILE));
17954
18443
  var readReviewState = (runtime, root) => {
17955
18444
  const raw = runtime.readText(statePath(root));
17956
18445
  const parsed = raw.ok ? parseJsonObject(raw.value) : void 0;
@@ -17962,7 +18451,7 @@ var git = (runtime, root, args2) => {
17962
18451
  const result = runtime.git(args2, root);
17963
18452
  return result.ok ? result.stdout : void 0;
17964
18453
  };
17965
- var lastLines = (text8, max) => text8.split("\n").slice(-max).join("\n");
18454
+ var lastLines = (text9, max) => text9.split("\n").slice(-max).join("\n");
17966
18455
  var section = (title, body2, empty) => `## ${title}
17967
18456
 
17968
18457
  ${body2 === void 0 || body2.trim() === "" ? empty : body2.trimEnd()}
@@ -17972,7 +18461,7 @@ var taskSections = (runtime, root) => {
17972
18461
  const active = readState(runtime, paths).active;
17973
18462
  if (active === void 0)
17974
18463
  return [section("Contract", void 0, "(no active task)"), section("Evidence ledger", void 0, "(none)")];
17975
- const contract = runtime.readText(toAbsolutePath(join45(root, taskRelative(active))));
18464
+ const contract = runtime.readText(toAbsolutePath(join48(root, taskRelative(active))));
17976
18465
  const ledger = runtime.readText(paths.evidence(active));
17977
18466
  return [
17978
18467
  section("Contract", contract.ok ? contract.value : void 0, "(contract file missing)"),
@@ -17980,8 +18469,8 @@ var taskSections = (runtime, root) => {
17980
18469
  ];
17981
18470
  };
17982
18471
  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");
18472
+ const text9 = git(runtime, root, ["show", `${base}:REVIEW.md`]);
18473
+ return text9 === void 0 ? void 0 : text9.split("\n").slice(0, REVIEW_FILE_MAX_LINES).join("\n");
17985
18474
  };
17986
18475
  var nextState = (previous, base, head) => {
17987
18476
  if (previous?.base !== base) return { base, rounds: 1, lastHead: head };
@@ -17994,7 +18483,7 @@ var buildPacket = (runtime, root, base, head) => {
17994
18483
  const range = `${from}...${head}`;
17995
18484
  const diff = git(runtime, root, ["diff", "--no-color", "--no-ext-diff", range, "--", ".", ":(exclude).finitemonkey"]);
17996
18485
  const owners = reviewFile(runtime, root, base);
17997
- const text8 = [
18486
+ const text9 = [
17998
18487
  `# Review packet (round ${String(state.rounds)}, ${range})`,
17999
18488
  "",
18000
18489
  ...state.rounds > FULL_ROUNDS ? ["Only the diff since the last review round is included.", ""] : [],
@@ -18002,10 +18491,10 @@ var buildPacket = (runtime, root, base, head) => {
18002
18491
  ...taskSections(runtime, root),
18003
18492
  ...owners === void 0 ? [] : [section("REVIEW.md (base branch)", owners, "")]
18004
18493
  ].join("\n");
18005
- runtime.mkdir(toAbsolutePath(join45(root, ".finitemonkey", "review")));
18494
+ runtime.mkdir(toAbsolutePath(join48(root, ".finitemonkey", "review")));
18006
18495
  runtime.writeAtomic(statePath(root), `${JSON.stringify(state, null, 2)}
18007
18496
  `);
18008
- return { round: state.rounds, base, head, range, text: text8 };
18497
+ return { round: state.rounds, base, head, range, text: text9 };
18009
18498
  };
18010
18499
 
18011
18500
  // src/commands/review.ts
@@ -18017,7 +18506,7 @@ var reviewPacketCommand = {
18017
18506
  status: "ready",
18018
18507
  options: { base: { type: "string" } },
18019
18508
  run: (ctx, args2) => {
18020
- const repo = repoOf2(ctx);
18509
+ const repo = repoOf(ctx);
18021
18510
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
18022
18511
  const base = stringOption(args2.values, "base") ?? repoFacts(ctx.runtime, repo).defaultBranch();
18023
18512
  const resolved = ctx.runtime.git(["rev-parse", "--verify", "--quiet", `${base}^{commit}`], repo.root);
@@ -18058,14 +18547,14 @@ var gitUnavailable = (detail) => ({
18058
18547
  });
18059
18548
 
18060
18549
  // src/commands/secrets-accept.ts
18061
- var DAY_MS2 = 864e5;
18550
+ var DAY_MS3 = 864e5;
18062
18551
  var daysOf = (values) => {
18063
18552
  const raw = stringOption(values, "days");
18064
18553
  if (raw === void 0) return EXCEPTION_MAX_DAYS;
18065
18554
  const days = Number.parseInt(raw, 10);
18066
18555
  return Number.isInteger(days) && days > 0 && days <= EXCEPTION_MAX_DAYS ? days : void 0;
18067
18556
  };
18068
- var plusDays = (iso, days) => new Date(Date.parse(iso) + days * DAY_MS2).toISOString();
18557
+ var plusDays = (iso, days) => new Date(Date.parse(iso) + days * DAY_MS3).toISOString();
18069
18558
  var failure3 = (ctx, problem4) => failureFrom(ctx.t, problem4);
18070
18559
  var inputProblem = (ctx, id, reason, days) => {
18071
18560
  const fix = "finitemonkey secrets accept <id> --reason <why> [--days 1-90] [--false-positive]";
@@ -18157,7 +18646,7 @@ var secretsAcceptCommand = {
18157
18646
  };
18158
18647
 
18159
18648
  // src/secrets/runbooks.ts
18160
- import { dirname as dirname9, join as join46 } from "node:path";
18649
+ import { dirname as dirname9, join as join49 } from "node:path";
18161
18650
  var PROVIDER_OF_RULE = {
18162
18651
  "aws-access-token": "aws",
18163
18652
  "aws-secret-access-key": "aws",
@@ -18179,13 +18668,13 @@ var RUNBOOKS_URL = "https://github.com/gabrielvieiraf/finitemonkey/blob/main/run
18179
18668
  var runbookProvider = (rule) => PROVIDER_OF_RULE[rule] ?? "generic";
18180
18669
  var runbookLink = (runtime, rule) => {
18181
18670
  const file = `${runbookProvider(rule)}.md`;
18182
- const local = toAbsolutePath(join46(dirname9(dirname9(runtime.selfPath)), "runbooks", file));
18671
+ const local = toAbsolutePath(join49(dirname9(dirname9(runtime.selfPath)), "runbooks", file));
18183
18672
  return runtime.stat(local)?.kind === "file" ? local : `${RUNBOOKS_URL}/${file}`;
18184
18673
  };
18185
18674
 
18186
18675
  // src/commands/secrets-incident.ts
18187
18676
  var INCIDENT_ACTIONS = ["list", "rotated", "close", "purge"];
18188
- var USAGE = "finitemonkey secrets incident [list | rotated <id> | close <id> | purge <id>]";
18677
+ var USAGE2 = "finitemonkey secrets incident [list | rotated <id> | close <id> | purge <id>]";
18189
18678
  var TARGET = { rotated: "rotacionado", close: "fechado" };
18190
18679
  var PURGE_STEPS = [
18191
18680
  "secrets.purge.step1",
@@ -18243,7 +18732,7 @@ var move = (ctx, root, incident, action) => {
18243
18732
  if (!canMove(incident.state, to))
18244
18733
  return action === "close" ? notRotated(ctx, incident) : fail2(
18245
18734
  ctx,
18246
- usageProblem("invalid_arguments", "secrets.error.bad_transition", USAGE, {
18735
+ usageProblem("invalid_arguments", "secrets.error.bad_transition", USAGE2, {
18247
18736
  id: incident.id,
18248
18737
  from: incident.state,
18249
18738
  to
@@ -18266,7 +18755,7 @@ var actionOf = (raw) => INCIDENT_ACTIONS.find((action) => action === (raw ?? "li
18266
18755
  var secretsIncidentCommand = {
18267
18756
  path: ["secrets", "incident"],
18268
18757
  summaryKey: "secrets.command.incident",
18269
- usage: USAGE,
18758
+ usage: USAGE2,
18270
18759
  status: "ready",
18271
18760
  maxPositionals: 2,
18272
18761
  run: (ctx, args2) => {
@@ -18276,7 +18765,7 @@ var secretsIncidentCommand = {
18276
18765
  return Promise.resolve(
18277
18766
  fail2(
18278
18767
  ctx,
18279
- usageProblem("invalid_arguments", "error.invalid_arguments", USAGE, { detail: args2.positionals[0] ?? "" })
18768
+ usageProblem("invalid_arguments", "error.invalid_arguments", USAGE2, { detail: args2.positionals[0] ?? "" })
18280
18769
  )
18281
18770
  );
18282
18771
  if (action === "list") return Promise.resolve(list2(ctx, root));
@@ -18348,7 +18837,7 @@ var liveOutcome = (result) => {
18348
18837
 
18349
18838
  // src/commands/secrets-live.ts
18350
18839
  var SECRETS_LIVE_SCHEMA = "finitemonkey/secrets-live@1";
18351
- var USAGE2 = `finitemonkey secrets live <${LIVE_PROVIDERS.join("|")}> --env <VARIABLE> [--send]`;
18840
+ var USAGE3 = `finitemonkey secrets live <${LIVE_PROVIDERS.join("|")}> --env <VARIABLE> [--send]`;
18352
18841
  var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
18353
18842
  var target = (provider) => {
18354
18843
  const probe = LIVE_PROBES[provider];
@@ -18379,7 +18868,7 @@ var send = (ctx, provider, variable, key) => {
18379
18868
  var secretsLiveCommand = {
18380
18869
  path: ["secrets", "live"],
18381
18870
  summaryKey: "secrets.command.live",
18382
- usage: USAGE2,
18871
+ usage: USAGE3,
18383
18872
  status: "ready",
18384
18873
  maxPositionals: 1,
18385
18874
  options: { env: { type: "string" }, send: { type: "boolean" } },
@@ -18390,7 +18879,7 @@ var secretsLiveCommand = {
18390
18879
  return Promise.resolve(
18391
18880
  failureFrom(
18392
18881
  ctx.t,
18393
- usageProblem("invalid_arguments", "error.invalid_arguments", USAGE2, {
18882
+ usageProblem("invalid_arguments", "error.invalid_arguments", USAGE3, {
18394
18883
  detail: provider === void 0 ? args2.positionals[0] ?? "<provider>" : "--env <VARIABLE>"
18395
18884
  })
18396
18885
  )
@@ -18400,7 +18889,7 @@ var secretsLiveCommand = {
18400
18889
  return Promise.resolve(
18401
18890
  failureFrom(
18402
18891
  ctx.t,
18403
- needsPersonProblem("secrets.error.person_only", `${USAGE2.replace(" [--send]", "")} --send`, {
18892
+ needsPersonProblem("secrets.error.person_only", `${USAGE3.replace(" [--send]", "")} --send`, {
18404
18893
  action: ctx.t("secrets.action.live")
18405
18894
  })
18406
18895
  )
@@ -18408,7 +18897,7 @@ var secretsLiveCommand = {
18408
18897
  const key = ctx.env[variable] ?? "";
18409
18898
  if (key === "")
18410
18899
  return Promise.resolve(
18411
- failureFrom(ctx.t, usageProblem("invalid_arguments", "secrets.live.no_value", USAGE2, { variable }))
18900
+ failureFrom(ctx.t, usageProblem("invalid_arguments", "secrets.live.no_value", USAGE3, { variable }))
18412
18901
  );
18413
18902
  return Promise.resolve(send(ctx, provider, variable, key));
18414
18903
  }
@@ -18577,7 +19066,7 @@ var gateLight = (runtime, paths) => {
18577
19066
  };
18578
19067
 
18579
19068
  // src/statusline/input.ts
18580
- var text7 = (value) => typeof value === "string" && value !== "" ? value : void 0;
19069
+ var text8 = (value) => typeof value === "string" && value !== "" ? value : void 0;
18581
19070
  var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
18582
19071
  var usedTokens = (usage) => {
18583
19072
  if (!isRecord(usage)) return void 0;
@@ -18597,8 +19086,8 @@ var parseStatusInput = (raw) => {
18597
19086
  const record3 = parsed.ok ? parsed.value : {};
18598
19087
  const workspace = isRecord(record3["workspace"]) ? record3["workspace"] : {};
18599
19088
  return {
18600
- sessionId: text7(record3["session_id"]),
18601
- dir: text7(workspace["project_dir"]) ?? text7(workspace["current_dir"]) ?? text7(record3["cwd"]),
19089
+ sessionId: text8(record3["session_id"]),
19090
+ dir: text8(workspace["project_dir"]) ?? text8(workspace["current_dir"]) ?? text8(record3["cwd"]),
18602
19091
  contextPercent: contextPercent(record3),
18603
19092
  raw
18604
19093
  };
@@ -18613,7 +19102,7 @@ var segmentText = (t, segment) => [
18613
19102
  ...segment.contextPercent === void 0 ? [] : [t("statusline.context", { percent: segment.contextPercent })]
18614
19103
  ].join(" · ");
18615
19104
  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);
19105
+ var graphemes = (text9) => Array.from(new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text9), (part2) => part2.segment);
18617
19106
  var fit = (line2, columns) => {
18618
19107
  if (columns === void 0) return line2;
18619
19108
  const visible = graphemes(line2.replace(ANSI3, ""));
@@ -18674,16 +19163,16 @@ var cached2 = (runtime, paths) => {
18674
19163
  const raw = readJsonFile(runtime, paths.cache(WRAP_CACHE))?.["sessions"];
18675
19164
  return isRecord(raw) ? Object.fromEntries(Object.entries(raw).filter((entry2) => typeof entry2[1] === "string")) : {};
18676
19165
  };
18677
- var remember = (runtime, paths, session, text8) => {
18678
- const sessions = Object.entries({ ...cached2(runtime, paths), [session]: text8 }).slice(-KEEP3);
19166
+ var remember = (runtime, paths, session, text9) => {
19167
+ const sessions = Object.entries({ ...cached2(runtime, paths), [session]: text9 }).slice(-KEEP3);
18679
19168
  writeJsonFile(runtime, paths.cache(WRAP_CACHE), { sessions: Object.fromEntries(sessions) });
18680
19169
  };
18681
19170
  var runWrapped = async (runtime, paths, request, shell = nodeShell) => {
18682
19171
  const result = await shell(request.command, request.input, request.cwd, request.timeoutMs);
18683
19172
  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;
19173
+ const text9 = result.text.replace(/\s+$/, "");
19174
+ if (paths !== void 0) remember(runtime, paths, request.session, text9);
19175
+ return text9;
18687
19176
  }
18688
19177
  return paths === void 0 ? "" : cached2(runtime, paths)[request.session] ?? "";
18689
19178
  };
@@ -18692,8 +19181,8 @@ var runWrapped = async (runtime, paths, request, shell = nodeShell) => {
18692
19181
  var STDIN_MS = 300;
18693
19182
  var WRAP_MS = 700;
18694
19183
  var wrapOf = (ctx, root) => {
18695
- const text8 = ctx.runtime.readText(toAbsolutePath(`${root}/${LOCK_FILE}`));
18696
- const lock = text8.ok ? parseLock(text8.value) : void 0;
19184
+ const text9 = ctx.runtime.readText(toAbsolutePath(`${root}/${LOCK_FILE}`));
19185
+ const lock = text9.ok ? parseLock(text9.value) : void 0;
18697
19186
  if (!lock?.ok) return void 0;
18698
19187
  return lock.value.entries.find((entry2) => entry2.added?.statusLine !== void 0)?.added?.statusLine?.wrap ?? void 0;
18699
19188
  };
@@ -18739,7 +19228,7 @@ var statuslineCommand = {
18739
19228
  };
18740
19229
 
18741
19230
  // src/task/create.ts
18742
- import { join as join47 } from "node:path";
19231
+ import { join as join50 } from "node:path";
18743
19232
 
18744
19233
  // src/core/yaml/write.ts
18745
19234
  var PLAIN_SAFE = /^[A-Za-z_./][A-Za-z0-9_./ ()-]*$/;
@@ -18828,10 +19317,10 @@ var explicitChecks = (draft) => [
18828
19317
  var draftChecks = (runtime, paths, draft) => {
18829
19318
  const disk = {
18830
19319
  read: (relative11) => {
18831
- const text8 = runtime.readText(toAbsolutePath(join47(paths.root, relative11)));
18832
- return text8.ok ? text8.value : void 0;
19320
+ const text9 = runtime.readText(toAbsolutePath(join50(paths.root, relative11)));
19321
+ return text9.ok ? text9.value : void 0;
18833
19322
  },
18834
- exists: (relative11) => runtime.stat(toAbsolutePath(join47(paths.root, relative11))) !== void 0
19323
+ exists: (relative11) => runtime.stat(toAbsolutePath(join50(paths.root, relative11))) !== void 0
18835
19324
  };
18836
19325
  const detected = detectChecks(disk);
18837
19326
  const base = draft.runs.length > 0 || draft.tests !== void 0 ? explicitChecks(draft) : detected;
@@ -18846,7 +19335,7 @@ var draftChecks = (runtime, paths, draft) => {
18846
19335
  { id: "merge", kind: "merge" },
18847
19336
  { id: "callers", kind: "callers" },
18848
19337
  ...draft.paths.length > 0 || draft.budget !== void 0 ? [{ id: "diff", kind: "diff" }] : [],
18849
- ...runtime.stat(toAbsolutePath(join47(paths.root, LOOK_CONFIG))) === void 0 ? [] : [{ id: "look", kind: "look" }],
19338
+ ...runtime.stat(toAbsolutePath(join50(paths.root, LOOK_CONFIG))) === void 0 ? [] : [{ id: "look", kind: "look" }],
18850
19339
  ...draft.git.length > 0 ? [{ id: "git", kind: "git", require: draft.git }] : [],
18851
19340
  { id: "pr", kind: "pr", requireOpen: draft.git.includes("pr_open") },
18852
19341
  ...draft.manual.map((description, index) => ({
@@ -18872,19 +19361,19 @@ var createTask = (runtime, paths, draft) => {
18872
19361
  decisionsNeeded: [],
18873
19362
  implicit: false
18874
19363
  };
18875
- const text8 = stringifyYaml(contractYaml(shape));
19364
+ const text9 = stringifyYaml(contractYaml(shape));
18876
19365
  const valid = validateContract(contractYaml(shape));
18877
19366
  if (!valid.ok) return reject({ kind: "invalid", issues: valid.error });
18878
- const wrote = runtime.writeText(paths.task(draft.id), text8);
19367
+ const wrote = runtime.writeText(paths.task(draft.id), text9);
18879
19368
  if (!wrote.ok) return reject({ kind: "write", detail: wrote.error });
18880
19369
  const lock = readTrust(runtime, paths);
18881
19370
  writeTrust(runtime, paths, {
18882
19371
  ...lock,
18883
19372
  contracts: { ...lock.contracts, [draft.id]: contractHash(valid.value) },
18884
- approved: { ...lock.approved, [draft.id]: text8 }
19373
+ approved: { ...lock.approved, [draft.id]: text9 }
18885
19374
  });
18886
19375
  writeState(runtime, paths, { ...readState(runtime, paths), active: draft.id });
18887
- return accept({ contract: valid.value, text: text8 });
19376
+ return accept({ contract: valid.value, text: text9 });
18888
19377
  };
18889
19378
 
18890
19379
  // src/commands/task.ts
@@ -19035,7 +19524,7 @@ var taskDoneCommand = {
19035
19524
  };
19036
19525
 
19037
19526
  // src/install/githook-block.ts
19038
- var withoutHookBlock = (text8) => withoutManagedBlock(text8, { begin: BLOCK_BEGIN, end: BLOCK_END });
19527
+ var withoutHookBlock = (text9) => withoutManagedBlock(text9, { begin: BLOCK_BEGIN, end: BLOCK_END });
19039
19528
 
19040
19529
  // src/install/uninstall.ts
19041
19530
  var blockless = (kind, current2) => {
@@ -19044,8 +19533,8 @@ var blockless = (kind, current2) => {
19044
19533
  };
19045
19534
  var structural = (entry2, current2) => {
19046
19535
  if (entry2.kind === "gitignore" || entry2.kind === "markdown" || entry2.kind === "githook") {
19047
- const text8 = blockless(entry2.kind, current2);
19048
- return text8.trim() === "" && !entry2.existed ? void 0 : text8;
19536
+ const text9 = blockless(entry2.kind, current2);
19537
+ return text9.trim() === "" && !entry2.existed ? void 0 : text9;
19049
19538
  }
19050
19539
  const parsed = parseSettings(current2);
19051
19540
  if (!parsed.ok || entry2.added === void 0) return current2;
@@ -19126,7 +19615,7 @@ var dataOf3 = (root, plan, applied) => ({
19126
19615
  kept: plan.items.filter((item2) => item2.action.startsWith("keep")).map(({ path }) => path),
19127
19616
  outside: OUTSIDE.map(({ command }) => command)
19128
19617
  });
19129
- var linesOf2 = (ctx, data) => [
19618
+ var linesOf3 = (ctx, data) => [
19130
19619
  ctx.t("install.uninstall.title", { root: data.root }),
19131
19620
  ...data.actions.map(({ path, action }) => ` ${action.padEnd(15)}${path}`),
19132
19621
  ...data.purge ? [ctx.t("install.uninstall.purge")] : [ctx.t("install.uninstall.keeps_state")],
@@ -19147,16 +19636,16 @@ var runUninstall = (ctx, args2) => {
19147
19636
  const options = { purge: args2.values["purge"] === true, keepGenerated: args2.values["keep-generated"] === true };
19148
19637
  const plan = planRemoval(project, lock.value, options);
19149
19638
  const preview = dataOf3(project.root, plan, false);
19150
- if (ctx.dryRun) return success(preview, [...linesOf2(ctx, preview), ctx.t("install.dry_run")]);
19639
+ if (ctx.dryRun) return success(preview, [...linesOf3(ctx, preview), ctx.t("install.dry_run")]);
19151
19640
  if (!ctx.yes) {
19152
19641
  const next2 = fixFor(args2);
19153
19642
  const problem4 = confirmProblem("uninstall", ctx.t("install.uninstall.question"), next2);
19154
- return { ...failureFrom(ctx.t, { ...problem4, data: preview }, linesOf2(ctx, preview)), next: next2 };
19643
+ return { ...failureFrom(ctx.t, { ...problem4, data: preview }, linesOf3(ctx, preview)), next: next2 };
19155
19644
  }
19156
19645
  const removed = applyRemoval(project, plan);
19157
19646
  if (!removed.ok) return failureFrom(ctx.t, applyProblem(removed.error));
19158
19647
  const data = dataOf3(project.root, plan, true);
19159
- return success(data, [...linesOf2(ctx, data), ctx.t("install.uninstall.done")]);
19648
+ return success(data, [...linesOf3(ctx, data), ctx.t("install.uninstall.done")]);
19160
19649
  };
19161
19650
  var uninstallCommand = {
19162
19651
  path: ["uninstall"],
@@ -19236,6 +19725,7 @@ var REGISTRY = [
19236
19725
  policyFalsePositiveCommand,
19237
19726
  offCommand,
19238
19727
  onCommand,
19728
+ gainCommand,
19239
19729
  secretsIncidentCommand,
19240
19730
  secretsLiveCommand,
19241
19731
  proseCheckCommand,
@@ -19250,34 +19740,35 @@ var REGISTRY = [
19250
19740
  lookCommand,
19251
19741
  reviewPacketCommand,
19252
19742
  statuslineCommand,
19743
+ critiqueVerifyCommand,
19253
19744
  versionCommand,
19254
19745
  helpCommand
19255
19746
  ];
19256
19747
 
19257
19748
  // src/i18n/lang-settings.ts
19258
- import { join as join48 } from "node:path";
19749
+ import { join as join51 } from "node:path";
19259
19750
  var read3 = (runtime, path) => {
19260
- const text8 = runtime.readText(toAbsolutePath(path));
19261
- return text8.ok ? text8.value : void 0;
19751
+ const text9 = runtime.readText(toAbsolutePath(path));
19752
+ return text9.ok ? text9.value : void 0;
19262
19753
  };
19263
19754
  var configText = (tree, ...path) => {
19264
19755
  const value = configAt(tree, ...path);
19265
19756
  return typeof value === "string" && value !== "" ? value : void 0;
19266
19757
  };
19267
- var languageIn = (text8) => {
19268
- if (text8 === void 0) return void 0;
19269
- const parsed = parseJsonObject(text8);
19758
+ var languageIn = (text9) => {
19759
+ if (text9 === void 0) return void 0;
19760
+ const parsed = parseJsonObject(text9);
19270
19761
  const value = parsed.ok && isRecord(parsed.value) ? parsed.value["language"] : void 0;
19271
19762
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
19272
19763
  };
19273
- var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join48(runtime.homeDir(), ".claude");
19764
+ var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join51(runtime.homeDir(), ".claude");
19274
19765
  var langSettings = (runtime, cwd, env, platform) => {
19275
19766
  const root = findGitRoot(cwd, runtime) ?? cwd;
19276
- const config = parseConfigYaml(read3(runtime, join48(root, CONFIG_FILE)) ?? "");
19767
+ const config = parseConfigYaml(read3(runtime, join51(root, CONFIG_FILE2)) ?? "");
19277
19768
  const claudeFiles = [
19278
- join48(root, ".claude", "settings.local.json"),
19279
- join48(root, ".claude", "settings.json"),
19280
- join48(claudeDir(runtime, env), "settings.json")
19769
+ join51(root, ".claude", "settings.local.json"),
19770
+ join51(root, ".claude", "settings.json"),
19771
+ join51(claudeDir(runtime, env), "settings.json")
19281
19772
  ];
19282
19773
  return {
19283
19774
  config: configText(config, "lang"),
@@ -19364,8 +19855,8 @@ var resultFromThrown = (thrown, t) => {
19364
19855
  };
19365
19856
  var block3 = (items) => items.length > 0 ? `${items.join("\n")}
19366
19857
  ` : "";
19367
- var write2 = (sink, text8) => {
19368
- if (text8 !== "") sink.write(maskText(text8));
19858
+ var write2 = (sink, text9) => {
19859
+ if (text9 !== "") sink.write(maskText(text9));
19369
19860
  };
19370
19861
  var render2 = (ctx, command, result) => {
19371
19862
  if (result.raw !== void 0) {
@@ -19434,9 +19925,9 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
19434
19925
  var UNKNOWN_VERSION = "0.0.0-unknown";
19435
19926
  var VERSION_SOURCES = ["../package.json", "../.claude-plugin/plugin.json"];
19436
19927
  var versionAt = (readText, moduleUrl, relative11) => {
19437
- const text8 = readText(toAbsolutePath(fileURLToPath4(new URL(relative11, moduleUrl))));
19438
- if (!text8.ok) return void 0;
19439
- const parsed = parseJson(text8.value);
19928
+ const text9 = readText(toAbsolutePath(fileURLToPath4(new URL(relative11, moduleUrl))));
19929
+ if (!text9.ok) return void 0;
19930
+ const parsed = parseJson(text9.value);
19440
19931
  if (!parsed.ok || !isRecord(parsed.value)) return void 0;
19441
19932
  const version = parsed.value["version"];
19442
19933
  return typeof version === "string" ? version : void 0;