finitemonkey 0.1.0-next.25 → 0.1.0-next.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,15 +28,15 @@ import { dirname } from "node:path";
28
28
  var accept = (value) => ({ ok: true, value });
29
29
  var reject = (error) => ({ ok: false, error });
30
30
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
31
- var parseJson = (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) => {
@@ -880,7 +1371,7 @@ var parseStatusLineAdded = (value) => {
880
1371
  };
881
1372
 
882
1373
  // src/install/lock-file.ts
883
- var ENTRY_KINDS = ["settings", "gitignore", "generated", "markdown", "githook"];
1374
+ var ENTRY_KINDS = ["settings", "gitignore", "generated", "markdown", "githook", "sandbox"];
884
1375
  var strings = (value) => Array.isArray(value) && value.every((item2) => typeof item2 === "string") ? value : void 0;
885
1376
  var parseAdded = (value) => {
886
1377
  if (!isRecord(value)) return void 0;
@@ -914,6 +1405,7 @@ var parseEntry = (value) => {
914
1405
  }
915
1406
  const added = parseAdded(value["added"]);
916
1407
  const handlers = Array.isArray(value["handlers"]) ? value["handlers"].filter(isRecord) : void 0;
1408
+ const sandbox = isRecord(value["sandbox"]) ? value["sandbox"] : void 0;
917
1409
  return {
918
1410
  path,
919
1411
  kind: known2,
@@ -921,7 +1413,8 @@ var parseEntry = (value) => {
921
1413
  hash,
922
1414
  ...typeof backup === "string" ? { backup } : {},
923
1415
  ...added === void 0 ? {} : { added },
924
- ...handlers === void 0 ? {} : { handlers }
1416
+ ...handlers === void 0 ? {} : { handlers },
1417
+ ...sandbox === void 0 ? {} : { sandbox }
925
1418
  };
926
1419
  };
927
1420
  var parseEntries = (value) => {
@@ -935,130 +1428,44 @@ var fromRecord = (record3) => {
935
1428
  const entries = parseEntries(record3["entries"]);
936
1429
  const createdDirs = strings(record3["createdDirs"]);
937
1430
  if (schema !== LOCK_SCHEMA) return reject(`schema is not ${LOCK_SCHEMA}`);
938
- if (typeof version !== "string" || typeof installedAt !== "string" || typeof updatedAt !== "string") {
939
- return reject("version, installedAt and updatedAt must be text");
940
- }
941
- if (known2 === void 0 || entries === void 0 || createdDirs === void 0) {
942
- return reject("preset, entries or createdDirs is malformed");
943
- }
944
- return accept({ schema, version, preset: known2, installedAt, updatedAt, createdDirs, entries });
945
- };
946
- var parseLock = (text8) => {
947
- const parsed = parseJsonObject(text8);
948
- return parsed.ok ? fromRecord(parsed.value) : reject(parsed.error);
949
- };
950
- var renderLock = (lock) => `${JSON.stringify(lock, null, 2)}
951
- `;
952
-
953
- // src/git/project-config.ts
954
- import { join as join2 } from "node:path";
955
- var CONFIG_FILE2 = ".finitemonkey/config.yml";
956
- var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
957
- var stripComment = (line2) => {
958
- let quote;
959
- for (let index = 0; index < line2.length; index += 1) {
960
- const char = line2.charAt(index);
961
- if (quote === void 0 && (char === '"' || char === "'")) quote = char;
962
- else if (char === quote) quote = void 0;
963
- else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
964
- return line2.slice(0, index);
965
- }
966
- }
967
- return line2;
968
- };
969
- var flowList = (value) => {
970
- const trimmed = value.trim();
971
- if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
972
- return trimmed.slice(1, -1).split(",").map(unquote).filter((item2) => item2 !== "");
973
- };
974
- var keyValue = (body2) => {
975
- const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
976
- if (match === null) return void 0;
977
- return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
978
- };
979
- var addItem = (frame, item2) => {
980
- if (frame.key === void 0) return;
981
- const parent = frame.tree[frame.key];
982
- if (Array.isArray(parent)) parent.push(item2);
983
- else frame.tree[frame.key] = [item2];
984
- };
985
- var placeLine = (stack, indent, body2) => {
986
- while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
987
- const top = stack[stack.length - 1];
988
- if (top === void 0) return;
989
- if (body2.startsWith("- ")) {
990
- addItem(top, unquote(body2.slice(2)));
991
- return;
992
- }
993
- const pair = keyValue(body2);
994
- if (pair === void 0) return;
995
- const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
996
- if (pair.value === "") {
997
- stack.push({ indent, tree: parentTree, key: pair.key });
998
- return;
999
- }
1000
- parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
1001
- };
1002
- var ensureTree = (tree, key) => {
1003
- const existing = tree[key];
1004
- if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
1005
- const fresh3 = {};
1006
- tree[key] = fresh3;
1007
- return fresh3;
1008
- };
1009
- var parseConfigYaml = (text8) => {
1010
- const root = {};
1011
- const stack = [{ indent: -1, tree: root, key: void 0 }];
1012
- for (const raw of text8.split(/\r?\n/)) {
1013
- const line2 = stripComment(raw).trimEnd();
1014
- if (line2.trim() === "" || line2.trim() === "---") continue;
1015
- const indent = line2.length - line2.trimStart().length;
1016
- placeLine(stack, indent, line2.trim());
1017
- }
1018
- return root;
1019
- };
1020
- var isTree = (node) => typeof node === "object" && !Array.isArray(node);
1021
- var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
1022
- var configList = (tree, ...path) => {
1023
- const node = configAt(tree, ...path);
1024
- if (typeof node === "string") return node === "" ? [] : [node];
1025
- return Array.isArray(node) ? node.filter((item2) => typeof item2 === "string") : [];
1026
- };
1027
- var configMap = (tree, ...path) => {
1028
- const node = configAt(tree, ...path);
1029
- if (!isTree(node)) return {};
1030
- return Object.fromEntries(
1031
- Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
1032
- );
1431
+ if (typeof version !== "string" || typeof installedAt !== "string" || typeof updatedAt !== "string") {
1432
+ return reject("version, installedAt and updatedAt must be text");
1433
+ }
1434
+ if (known2 === void 0 || entries === void 0 || createdDirs === void 0) {
1435
+ return reject("preset, entries or createdDirs is malformed");
1436
+ }
1437
+ return accept({ schema, version, preset: known2, installedAt, updatedAt, createdDirs, entries });
1033
1438
  };
1034
- var loadProjectConfig = (runtime, root) => {
1035
- const text8 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE2)));
1036
- return text8.ok ? parseConfigYaml(text8.value) : {};
1439
+ var parseLock = (text9) => {
1440
+ const parsed = parseJsonObject(text9);
1441
+ return parsed.ok ? fromRecord(parsed.value) : reject(parsed.error);
1037
1442
  };
1443
+ var renderLock = (lock) => `${JSON.stringify(lock, null, 2)}
1444
+ `;
1038
1445
 
1039
1446
  // src/install/managed-block.ts
1040
- var lineBreak = (text8) => text8.includes("\r\n") ? "\r\n" : "\n";
1447
+ var lineBreak = (text9) => text9.includes("\r\n") ? "\r\n" : "\n";
1041
1448
  var blockRange = (lines3, markers) => {
1042
1449
  const start = lines3.findIndex((line2) => line2.trimEnd() === markers.begin);
1043
1450
  if (start < 0) return void 0;
1044
1451
  const end = lines3.findIndex((line2, index) => index > start && line2.trimEnd() === markers.end);
1045
1452
  return end < 0 ? void 0 : { start, end };
1046
1453
  };
1047
- var withManagedBlock = (text8, markers, body2) => {
1048
- const eol = text8 === void 0 ? "\n" : lineBreak(text8);
1454
+ var withManagedBlock = (text9, markers, body2) => {
1455
+ const eol = text9 === void 0 ? "\n" : lineBreak(text9);
1049
1456
  const block4 = [markers.begin, ...body2, markers.end];
1050
- if (text8 === void 0 || text8 === "") return `${block4.join(eol)}${eol}`;
1051
- const lines3 = text8.split(eol);
1457
+ if (text9 === void 0 || text9 === "") return `${block4.join(eol)}${eol}`;
1458
+ const lines3 = text9.split(eol);
1052
1459
  const range = blockRange(lines3, markers);
1053
1460
  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}`;
1461
+ const separator = text9.endsWith(eol) ? "" : eol;
1462
+ return `${text9}${separator}${block4.join(eol)}${eol}`;
1056
1463
  };
1057
- var withoutManagedBlock = (text8, markers) => {
1058
- const eol = lineBreak(text8);
1059
- const lines3 = text8.split(eol);
1464
+ var withoutManagedBlock = (text9, markers) => {
1465
+ const eol = lineBreak(text9);
1466
+ const lines3 = text9.split(eol);
1060
1467
  const range = blockRange(lines3, markers);
1061
- if (range === void 0) return text8;
1468
+ if (range === void 0) return text9;
1062
1469
  return [...lines3.slice(0, range.start), ...lines3.slice(range.end + 1)].join(eol);
1063
1470
  };
1064
1471
 
@@ -1155,7 +1562,7 @@ var configTemplate = (preset, lang) => [
1155
1562
  `lang: ${lang}`,
1156
1563
  ""
1157
1564
  ].join("\n");
1158
- var hasMarker = (text8) => text8.includes(GENERATED_MARKER);
1565
+ var hasMarker = (text9) => text9.includes(GENERATED_MARKER);
1159
1566
 
1160
1567
  // src/install/skill-template.ts
1161
1568
  var cliCommand = (runner, args2) => {
@@ -1220,7 +1627,7 @@ var lefthookBody = (runner) => [
1220
1627
  ` run: ${cliCommand(runner, "push check {1}")}`,
1221
1628
  " use_stdin: true"
1222
1629
  ];
1223
- var definesHook = (text8) => GIT_HOOK_NAMES.some((hook) => new RegExp(`^${hook}:`, "m").test(withoutManagedBlock(text8, HOOK_MARKERS)));
1630
+ var definesHook = (text9) => GIT_HOOK_NAMES.some((hook) => new RegExp(`^${hook}:`, "m").test(withoutManagedBlock(text9, HOOK_MARKERS)));
1224
1631
  var lefthookItem = (project, runner, path) => {
1225
1632
  const before = project.read(path);
1226
1633
  const after = withManagedBlock(before, HOOK_MARKERS, lefthookBody(runner));
@@ -1245,8 +1652,8 @@ var preCommitBody = (runner, indent) => {
1245
1652
  ];
1246
1653
  };
1247
1654
  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));
1655
+ var reposIsLast = (text9) => {
1656
+ const keys = withoutManagedBlock(text9, HOOK_MARKERS).split(/\r?\n/).filter((line2) => TOP_KEY.test(line2));
1250
1657
  return keys.at(-1)?.startsWith("repos:") === true;
1251
1658
  };
1252
1659
  var preCommitItem = (project, runner) => {
@@ -1269,7 +1676,7 @@ var gitHookItems = (project, runner) => {
1269
1676
  };
1270
1677
 
1271
1678
  // src/policy/policy.ts
1272
- import { join as join3 } from "node:path";
1679
+ import { join as join5 } from "node:path";
1273
1680
  var POLICY_FILE = ".finitemonkey/policy.yml";
1274
1681
  var GUARD_MODES = ["off", "shadow", "active"];
1275
1682
  var LEVELS = ["light", "standard", "strict"];
@@ -1284,8 +1691,8 @@ var DEFAULT_POLICY = {
1284
1691
  guards: {}
1285
1692
  };
1286
1693
  var pick = (options, value, fallback) => options.find((option) => option === value) ?? fallback;
1287
- var parsePolicy = (text8) => {
1288
- const tree = parseConfigYaml(text8);
1694
+ var parsePolicy = (text9) => {
1695
+ const tree = parseConfigYaml(text9);
1289
1696
  const raw = configAt(tree, "promoteAfter");
1290
1697
  const promoteAfter = typeof raw === "string" ? Number.parseInt(raw, 10) : Number.NaN;
1291
1698
  const guards = Object.fromEntries(
@@ -1302,10 +1709,10 @@ var parsePolicy = (text8) => {
1302
1709
  guards
1303
1710
  };
1304
1711
  };
1305
- var policyPath = (root) => toAbsolutePath(join3(root, POLICY_FILE));
1712
+ var policyPath = (root) => toAbsolutePath(join5(root, POLICY_FILE));
1306
1713
  var loadPolicy = (runtime, root) => {
1307
- const text8 = runtime.readText(policyPath(root));
1308
- return text8.ok ? parsePolicy(text8.value) : DEFAULT_POLICY;
1714
+ const text9 = runtime.readText(policyPath(root));
1715
+ return text9.ok ? parsePolicy(text9.value) : DEFAULT_POLICY;
1309
1716
  };
1310
1717
 
1311
1718
  // src/review/agents.ts
@@ -1322,6 +1729,7 @@ var REVIEWER_BODY = [
1322
1729
  "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
1730
  "4. Every must-fix finding carries a reproduction command and the expected result. A finding without one goes under should fix.",
1324
1731
  "5. End with a verdict: approve, approve with changes, or request changes.",
1732
+ '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
1733
  "",
1326
1734
  "You cannot edit files or start other agents. Report; do not repair."
1327
1735
  ].join("\n");
@@ -1330,9 +1738,9 @@ var VERIFIER_BODY = [
1330
1738
  "",
1331
1739
  "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
1740
  "",
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.",
1741
+ "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`.",
1742
+ "2. Fails only on head: the finding stands. Fails on both: it was already there. Fails on neither: not reproduced.",
1743
+ "3. Report the table it prints, unchanged. Do not rerun a command by hand to get a different answer.",
1336
1744
  "",
1337
1745
  "You cannot edit files or start other agents. Report; do not repair."
1338
1746
  ].join("\n");
@@ -1370,16 +1778,16 @@ var agentMarkdown = (spec2, marker) => [
1370
1778
  // src/install/agents-block.ts
1371
1779
  var MARKDOWN_MARKERS = { begin: MD_BLOCK_BEGIN, end: MD_BLOCK_END };
1372
1780
  var IMPORT_LINE = /^@AGENTS\.md[ \t]*$/m;
1373
- var withAgentsBlock = (text8, runner, lang) => withManagedBlock(
1374
- text8,
1781
+ var withAgentsBlock = (text9, runner, lang) => withManagedBlock(
1782
+ text9,
1375
1783
  MARKDOWN_MARKERS,
1376
1784
  TEMPLATES[lang].agentsBlock({
1377
1785
  gate: cliCommand(runner, "gate --fast --json"),
1378
1786
  doctor: cliCommand(runner, "doctor --json")
1379
1787
  })
1380
1788
  );
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);
1789
+ var withAgentsImport = (text9) => text9 !== void 0 && IMPORT_LINE.test(withoutManagedBlock(text9, MARKDOWN_MARKERS)) ? text9 : withManagedBlock(text9, MARKDOWN_MARKERS, [AGENTS_IMPORT]);
1790
+ var withoutMarkdownBlock = (text9) => withoutManagedBlock(text9, MARKDOWN_MARKERS);
1383
1791
 
1384
1792
  // src/install/ask-floor.ts
1385
1793
  var DEFAULT_BRANCHES = ["main", "master"];
@@ -1455,7 +1863,7 @@ var ruleFor = (raw) => {
1455
1863
  };
1456
1864
  var claudeignoreRules = (texts2) => [
1457
1865
  ...new Set(
1458
- texts2.flatMap((text8) => (text8 ?? "").split("\n")).map(ruleFor).filter((rule) => rule !== void 0)
1866
+ texts2.flatMap((text9) => (text9 ?? "").split("\n")).map(ruleFor).filter((rule) => rule !== void 0)
1459
1867
  )
1460
1868
  ];
1461
1869
 
@@ -1484,16 +1892,16 @@ var DENY_FLOOR = {
1484
1892
  var DENY_RULES = [...DENY_FLOOR.secrets, ...DENY_FLOOR.git, ...DENY_FLOOR.proof];
1485
1893
 
1486
1894
  // src/install/detect.ts
1487
- import { join as join4 } from "node:path";
1895
+ import { join as join6 } from "node:path";
1488
1896
 
1489
1897
  // src/install/json-text.ts
1490
1898
  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") };
1899
+ var styleOf = (text9) => {
1900
+ const indented = /\n([ \t]+)\S/.exec(text9);
1901
+ return { indent: indented?.[1] ?? DEFAULT_STYLE.indent, finalNewline: text9.endsWith("\n") };
1494
1902
  };
1495
1903
  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);
1904
+ var parseSettings = (text9) => text9 === void 0 || text9.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text9);
1497
1905
  var asList = (value) => Array.isArray(value) ? value : [];
1498
1906
  var asRecord = (value) => isRecord(value) ? value : {};
1499
1907
  var setKey = (record3, key, value) => {
@@ -1530,8 +1938,8 @@ var WORKFLOW_DIRS = [
1530
1938
  { dir: "openspec", name: "openspec" }
1531
1939
  ];
1532
1940
  var fromPackageJson = (project) => {
1533
- const text8 = project.read("package.json");
1534
- const parsed = text8 === void 0 ? void 0 : parseJsonObject(text8);
1941
+ const text9 = project.read("package.json");
1942
+ const parsed = text9 === void 0 ? void 0 : parseJsonObject(text9);
1535
1943
  const field = parsed?.ok === true ? parsed.value["packageManager"] : void 0;
1536
1944
  if (typeof field !== "string") return void 0;
1537
1945
  return PACKAGE_MANAGERS.find(({ name }) => field.startsWith(`${name}@`))?.name;
@@ -1547,9 +1955,9 @@ var allHandlers = (settings) => {
1547
1955
  const hooks = settings["hooks"];
1548
1956
  return isRecord(hooks) ? Object.keys(hooks).flatMap((event) => handlersOf(settings, event)) : [];
1549
1957
  };
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);
1958
+ var settingsInfo = (text9) => {
1959
+ if (text9 === void 0) return { exists: false, valid: true, userHooks: 0, ownHooks: 0 };
1960
+ const parsed = text9.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text9);
1553
1961
  if (!parsed.ok) return { exists: true, valid: false, userHooks: 0, ownHooks: 0 };
1554
1962
  const handlers = allHandlers(parsed.value);
1555
1963
  const own = handlers.filter(isOwnHandler).length;
@@ -1563,8 +1971,8 @@ var pluginAuthorities = (settings, source) => {
1563
1971
  return LOOP_PLUGINS.includes(name) ? [{ kind: "loop-plugin", source, name }] : [];
1564
1972
  });
1565
1973
  };
1566
- var scopeAuthorities = (text8, source) => {
1567
- const parsed = text8 === void 0 ? void 0 : parseJsonObject(text8);
1974
+ var scopeAuthorities = (text9, source) => {
1975
+ const parsed = text9 === void 0 ? void 0 : parseJsonObject(text9);
1568
1976
  if (parsed?.ok !== true) return [];
1569
1977
  const stops = ["Stop", "SubagentStop"].flatMap(
1570
1978
  (event) => handlersOf(parsed.value, event).filter((handler) => !isOwnHandler(handler)).map((handler) => {
@@ -1577,9 +1985,9 @@ var scopeAuthorities = (text8, source) => {
1577
1985
  var userSettingsText = (project, env) => {
1578
1986
  const home = env["CLAUDE_CONFIG_DIR"] ?? env["HOME"] ?? env["USERPROFILE"];
1579
1987
  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;
1988
+ const file = env["CLAUDE_CONFIG_DIR"] === void 0 ? join6(home, ".claude", "settings.json") : join6(home, "settings.json");
1989
+ const text9 = project.runtime.readText(toAbsolutePath(file));
1990
+ return text9.ok ? text9.value : void 0;
1583
1991
  };
1584
1992
  var detectAuthorities = (project, env) => [
1585
1993
  ...scopeAuthorities(project.read(SETTINGS_FILE), "project"),
@@ -1599,8 +2007,8 @@ var chooseRunner = (project, bin) => {
1599
2007
  // src/install/gitignore-block.ts
1600
2008
  var MARKERS = { begin: BLOCK_BEGIN, end: BLOCK_END };
1601
2009
  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);
2010
+ var withBlock = (text9) => withManagedBlock(text9, MARKERS, body());
2011
+ var withoutBlock = (text9) => withoutManagedBlock(text9, MARKERS);
1604
2012
 
1605
2013
  // src/core/semver.ts
1606
2014
  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 +2051,7 @@ var pendingMigrations = (migrations, from, to) => {
1643
2051
  };
1644
2052
  var migrateItem = (item2, previous, steps) => {
1645
2053
  if (steps.length === 0 || item2.before === void 0) return item2;
1646
- const migrated = steps.reduce((text8, step3) => step3(text8), item2.after);
2054
+ const migrated = steps.reduce((text9, step3) => step3(text9), item2.after);
1647
2055
  if (migrated === item2.after) return item2;
1648
2056
  const recorded = previous.entries.find((entry2) => entry2.path === item2.path)?.hash;
1649
2057
  if (recorded !== hashText(item2.before)) {
@@ -1681,6 +2089,37 @@ var policyTemplate = (lang) => [
1681
2089
  ""
1682
2090
  ].join("\n");
1683
2091
 
2092
+ // src/install/sandbox.ts
2093
+ var SANDBOX_FILE = ".claude/settings.local.json";
2094
+ var DEFAULT_HOSTS = [
2095
+ "registry.npmjs.org",
2096
+ "github.com",
2097
+ "api.github.com",
2098
+ "codeload.github.com",
2099
+ "objects.githubusercontent.com"
2100
+ ];
2101
+ var SANDBOX_PLATFORMS = ["darwin", "linux"];
2102
+ var sandboxBlock = (root, hosts) => ({
2103
+ enabled: true,
2104
+ failIfUnavailable: true,
2105
+ filesystem: { denyRead: ["~/.ssh", "~/.aws", `${root}/.env`, `${root}/.env.*`] },
2106
+ network: { allowedDomains: [...hosts] }
2107
+ });
2108
+ var withSandbox = (text9, block4) => {
2109
+ const parsed = parseSettings(text9);
2110
+ if (!parsed.ok) return void 0;
2111
+ if ("sandbox" in parsed.value) return text9;
2112
+ return renderJson({ ...parsed.value, sandbox: block4 }, text9 === void 0 ? DEFAULT_STYLE : styleOf(text9));
2113
+ };
2114
+ var withoutSandbox = (text9, added) => {
2115
+ const parsed = parseSettings(text9);
2116
+ if (!parsed.ok) return text9;
2117
+ const current2 = parsed.value["sandbox"];
2118
+ if (!isRecord(current2) || JSON.stringify(current2) !== JSON.stringify(added)) return text9;
2119
+ const rest = Object.fromEntries(Object.entries(parsed.value).filter(([key]) => key !== "sandbox"));
2120
+ return Object.keys(rest).length === 0 ? void 0 : renderJson(rest, styleOf(text9));
2121
+ };
2122
+
1684
2123
  // src/install/settings-merge.ts
1685
2124
  var SETTINGS_SCHEMA_URL = "https://json.schemastore.org/claude-code-settings.json";
1686
2125
  var ATTRIBUTION_OFF = { commit: "", pr: "", sessionUrl: false };
@@ -1809,9 +2248,9 @@ var LOCAL_SETTINGS_FILE = ".claude/settings.local.json";
1809
2248
  var existingStatusLine = (project, projectSettings, env) => statusLineCommand(projectSettings["statusLine"]) ?? statusLineCommand(statusLineIn(project.read(LOCAL_SETTINGS_FILE))) ?? statusLineCommand(statusLineIn(userSettingsText(project, env)));
1810
2249
 
1811
2250
  // 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);
2251
+ var isBundle = (text9) => text9.startsWith("#!/usr/bin/env node") && text9.includes("finitemonkey");
2252
+ var isOwnPackage = (text9) => {
2253
+ const parsed = parseJsonObject(text9);
1815
2254
  return parsed.ok && parsed.value["name"] === "finitemonkey";
1816
2255
  };
1817
2256
  var vendoredPackage = (version) => `${JSON.stringify({ name: "finitemonkey", version, private: true, type: "module" }, null, 2)}
@@ -1842,9 +2281,9 @@ var detect = (project, gitRoot2, input) => ({
1842
2281
  location: locationOf(project.root, input.env)
1843
2282
  });
1844
2283
  var readPreviousLock = (project) => {
1845
- const text8 = project.read(LOCK_FILE);
1846
- if (text8 === void 0) return accept(void 0);
1847
- const parsed = parseLock(text8);
2284
+ const text9 = project.read(LOCK_FILE);
2285
+ if (text9 === void 0) return accept(void 0);
2286
+ const parsed = parseLock(text9);
1848
2287
  return parsed.ok ? accept(parsed.value) : reject({ kind: "lock", detail: parsed.error });
1849
2288
  };
1850
2289
  var actionFor = (before, after, existing) => {
@@ -1893,13 +2332,24 @@ var settingsItem = (project, detection, input, previous) => {
1893
2332
  });
1894
2333
  };
1895
2334
  var markdownItems = (project, runner, lang) => [
1896
- { path: AGENTS_FILE, render: (text8) => withAgentsBlock(text8, runner, lang) },
2335
+ { path: AGENTS_FILE, render: (text9) => withAgentsBlock(text9, runner, lang) },
1897
2336
  { path: CLAUDE_FILE, render: withAgentsImport }
1898
2337
  ].map(({ path, render: render3 }) => {
1899
2338
  const before = project.read(path);
1900
2339
  const after = render3(before);
1901
2340
  return { path, kind: "markdown", action: actionFor(before, after, "merge"), before, after };
1902
2341
  });
2342
+ var sandboxItems = (project, input) => {
2343
+ if (!input.sandbox || !SANDBOX_PLATFORMS.includes(input.platform)) return [];
2344
+ const declared = configList(parseConfigYaml(project.read(CONFIG_FILE2) ?? ""), "sandbox", "hosts");
2345
+ const block4 = sandboxBlock(project.root, declared.length > 0 ? declared : DEFAULT_HOSTS);
2346
+ const before = project.read(SANDBOX_FILE);
2347
+ const after = withSandbox(before, block4);
2348
+ if (after === void 0) return [];
2349
+ return [
2350
+ { path: SANDBOX_FILE, kind: "sandbox", action: actionFor(before, after, "merge"), before, after, sandbox: block4 }
2351
+ ];
2352
+ };
1903
2353
  var gitignoreItem = (project) => {
1904
2354
  const before = project.read(GITIGNORE_FILE);
1905
2355
  const after = withBlock(before);
@@ -1920,14 +2370,14 @@ var generatedItem = (project, file, previous) => {
1920
2370
  };
1921
2371
  var PLAYWRIGHT_VERIFIER = ".claude/agents/verifier.md";
1922
2372
  var playwrightVerifier = (project) => {
1923
- const wanted = configAt(parseConfigYaml(project.read(CONFIG_FILE) ?? ""), "review", "playwright") === "true";
2373
+ const wanted = configAt(parseConfigYaml(project.read(CONFIG_FILE2) ?? ""), "review", "playwright") === "true";
1924
2374
  if (!wanted) return [];
1925
2375
  const template = agentMarkdown(withPlaywright(VERIFIER), GENERATED_MARKER);
1926
- return [{ path: PLAYWRIGHT_VERIFIER, template, isOurs: (text8) => text8.includes(GENERATED_MARKER) }];
2376
+ return [{ path: PLAYWRIGHT_VERIFIER, template, isOurs: (text9) => text9.includes(GENERATED_MARKER) }];
1927
2377
  };
1928
2378
  var generatedFiles = (project, input, runner, vendor) => {
1929
2379
  const own = [
1930
- { path: CONFIG_FILE, template: configTemplate(input.preset, input.lang), isOurs: hasMarker },
2380
+ { path: CONFIG_FILE2, template: configTemplate(input.preset, input.lang), isOurs: hasMarker },
1931
2381
  { path: VERIFY_SKILL, template: verifySkillTemplate(runner, input.lang), isOurs: isOwnSkill },
1932
2382
  { path: POLICY_FILE, template: policyTemplate(input.lang), isOurs: hasMarker },
1933
2383
  ...playwrightVerifier(project)
@@ -1944,14 +2394,18 @@ var buildPlan = (project, detection, input, previous, migrations = MIGRATIONS) =
1944
2394
  const files = generatedFiles(project, input, detection.runner, vendor);
1945
2395
  if (!files.ok) return files;
1946
2396
  const generated = files.value.map((file) => generatedItem(project, file, entryFor(previous, file.path)));
2397
+ const sandboxSupported = SANDBOX_PLATFORMS.includes(input.platform);
2398
+ const sandbox = input.sandbox ? sandboxSupported ? "added" : "unsupported" : input.offerSandbox ? "offer" : "none";
1947
2399
  return accept({
1948
2400
  detection,
2401
+ sandbox,
1949
2402
  preset: input.preset,
1950
2403
  items: applyMigrations(
1951
2404
  [
1952
2405
  settings.value,
1953
2406
  gitignoreItem(project),
1954
2407
  ...markdownItems(project, detection.runner, input.lang),
2408
+ ...sandboxItems(project, input),
1955
2409
  ...generated,
1956
2410
  ...gitHookItems(project, detection.runner)
1957
2411
  ],
@@ -1968,9 +2422,9 @@ var needsWrite = (plan) => hasChanges(plan) || plan.previous === void 0;
1968
2422
  // src/install/apply.ts
1969
2423
  var stamp = (iso) => iso.replace(/[:.]/g, "-");
1970
2424
  var ensureParent = (project, relative11) => project.runtime.mkdir(project.abs(dirname3(relative11)));
1971
- var writeText = (project, relative11, text8) => {
2425
+ var writeText = (project, relative11, text9) => {
1972
2426
  const parent = ensureParent(project, relative11);
1973
- const written = parent.ok ? project.runtime.writeAtomic(project.abs(relative11), text8) : parent;
2427
+ const written = parent.ok ? project.runtime.writeAtomic(project.abs(relative11), text9) : parent;
1974
2428
  return written.ok ? accept(void 0) : reject({ path: relative11, detail: written.error });
1975
2429
  };
1976
2430
  var restore = (project, items) => {
@@ -1988,7 +2442,7 @@ var backupItems = (project, dir, items) => {
1988
2442
  return writeText(project, `${dir}/manifest.json`, `${JSON.stringify(manifest, null, 2)}
1989
2443
  `);
1990
2444
  };
1991
- var entryOf = (plan, item2, backupDir) => {
2445
+ var entryOf2 = (plan, item2, backupDir) => {
1992
2446
  const previous = plan.previous?.entries.find((entry2) => entry2.path === item2.path);
1993
2447
  const existed = previous?.existed ?? item2.before !== void 0;
1994
2448
  const backup = previous?.backup ?? (existed && backupDir !== void 0 ? `${backupDir}/${item2.path}` : void 0);
@@ -2000,7 +2454,8 @@ var entryOf = (plan, item2, backupDir) => {
2000
2454
  hash,
2001
2455
  ...backup === void 0 ? {} : { backup },
2002
2456
  ...item2.added === void 0 ? {} : { added: item2.added },
2003
- ...item2.handlers === void 0 ? {} : { handlers: item2.handlers }
2457
+ ...item2.handlers === void 0 ? {} : { handlers: item2.handlers },
2458
+ ...item2.sandbox === void 0 ? {} : { sandbox: item2.sandbox }
2004
2459
  };
2005
2460
  };
2006
2461
  var nextLock = (plan, version, now, backupDir, createdDirs, only) => ({
@@ -2011,7 +2466,7 @@ var nextLock = (plan, version, now, backupDir, createdDirs, only) => ({
2011
2466
  updatedAt: now,
2012
2467
  createdDirs: [.../* @__PURE__ */ new Set([...plan.previous?.createdDirs ?? [], ...createdDirs])],
2013
2468
  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)];
2469
+ if (only === void 0 || only.includes(item2.path)) return [entryOf2(plan, item2, backupDir)];
2015
2470
  const kept = plan.previous?.entries.find((entry2) => entry2.path === item2.path);
2016
2471
  return kept === void 0 ? [] : [kept];
2017
2472
  })
@@ -2110,16 +2565,16 @@ var detectLang = (flag2, env, settings = {}) => {
2110
2565
  };
2111
2566
 
2112
2567
  // src/install/project.ts
2113
- import { dirname as dirname4, join as join5 } from "node:path";
2568
+ import { dirname as dirname4, join as join7 } from "node:path";
2114
2569
  var projectAt = (root, runtime) => {
2115
- const abs = (relative11) => toAbsolutePath(join5(root, ...relative11.split("/")));
2570
+ const abs = (relative11) => toAbsolutePath(join7(root, ...relative11.split("/")));
2116
2571
  return {
2117
2572
  root,
2118
2573
  runtime,
2119
2574
  abs,
2120
2575
  read: (relative11) => {
2121
- const text8 = runtime.readText(abs(relative11));
2122
- return text8.ok ? text8.value : void 0;
2576
+ const text9 = runtime.readText(abs(relative11));
2577
+ return text9.ok ? text9.value : void 0;
2123
2578
  },
2124
2579
  exists: (relative11) => runtime.stat(abs(relative11)) !== void 0,
2125
2580
  isDir: (relative11) => runtime.stat(abs(relative11))?.kind === "dir"
@@ -2128,7 +2583,7 @@ var projectAt = (root, runtime) => {
2128
2583
  var findGitRoot = (start, runtime) => {
2129
2584
  let current2 = start;
2130
2585
  for (; ; ) {
2131
- if (runtime.stat(toAbsolutePath(join5(current2, ".git"))) !== void 0) return toAbsolutePath(current2);
2586
+ if (runtime.stat(toAbsolutePath(join7(current2, ".git"))) !== void 0) return toAbsolutePath(current2);
2132
2587
  const parent = dirname4(current2);
2133
2588
  if (parent === current2) return void 0;
2134
2589
  current2 = parent;
@@ -2192,17 +2647,20 @@ var invalidPreset = (raw) => problem("invalid_arguments", "error.invalid_preset"
2192
2647
  });
2193
2648
  var selfBundle = (ctx) => {
2194
2649
  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;
2650
+ const text9 = ctx.runtime.readText(ctx.runtime.selfPath);
2651
+ return text9.ok ? text9.value : void 0;
2197
2652
  };
2198
2653
  var INHERITED = /* @__PURE__ */ new Set(["default", "config", "claude-settings"]);
2199
- var planInputFor = (ctx, preset, bin, marketplace) => ({
2654
+ var planInputFor = (ctx, preset, bin, marketplace, sandbox = false) => ({
2200
2655
  preset,
2201
2656
  lang: ctx.lang,
2202
2657
  claudeLanguage: INHERITED.has(ctx.langSource) ? void 0 : CLAUDE_LANGUAGE[ctx.lang],
2203
2658
  bin,
2204
2659
  version: ctx.version,
2205
2660
  marketplace,
2661
+ sandbox,
2662
+ offerSandbox: !sandbox && SANDBOX_PLATFORMS.includes(ctx.platform) && (ctx.io.stdin.isTTY === true || ctx.yes),
2663
+ platform: ctx.platform,
2206
2664
  bundle: selfBundle(ctx),
2207
2665
  env: ctx.env,
2208
2666
  t: ctx.t
@@ -2229,7 +2687,7 @@ var applyFixes = (ctx, write3) => {
2229
2687
  };
2230
2688
 
2231
2689
  // src/doctor/report-file.ts
2232
- import { join as join7 } from "node:path";
2690
+ import { join as join9 } from "node:path";
2233
2691
 
2234
2692
  // src/evidence/log.ts
2235
2693
  var logEvent = (runtime, paths, event, data) => runtime.appendText(paths.log, `${JSON.stringify({ at: runtime.now(), event, ...data })}
@@ -2297,13 +2755,13 @@ var isPlaceholder = (value) => {
2297
2755
  var isTestOrLockPath = (path) => path !== void 0 && (LOCKFILES.test(toPosixPath(path)) || TEST_PATH.test(toPosixPath(path)));
2298
2756
 
2299
2757
  // src/secrets/entropy.ts
2300
- var shannonEntropy = (text8) => {
2301
- if (text8.length === 0) return 0;
2758
+ var shannonEntropy = (text9) => {
2759
+ if (text9.length === 0) return 0;
2302
2760
  const counts = /* @__PURE__ */ new Map();
2303
- for (const char of text8) counts.set(char, (counts.get(char) ?? 0) + 1);
2761
+ for (const char of text9) counts.set(char, (counts.get(char) ?? 0) + 1);
2304
2762
  let entropy = 0;
2305
2763
  for (const count4 of counts.values()) {
2306
- const p = count4 / text8.length;
2764
+ const p = count4 / text9.length;
2307
2765
  entropy -= p * Math.log2(p);
2308
2766
  }
2309
2767
  return entropy;
@@ -2311,7 +2769,7 @@ var shannonEntropy = (text8) => {
2311
2769
 
2312
2770
  // src/secrets/fingerprint.ts
2313
2771
  import { createHash as createHash2 } from "node:crypto";
2314
- var sha256Hex = (text8) => createHash2("sha256").update(text8).digest("hex");
2772
+ var sha256Hex = (text9) => createHash2("sha256").update(text9).digest("hex");
2315
2773
  var fingerprintOf = (secret) => sha256Hex(`finitemonkey:secret:${secret}`).slice(0, 16);
2316
2774
  var ALNUM = /[A-Za-z0-9]/g;
2317
2775
  var MIN_TAIL_LENGTH = 12;
@@ -2321,9 +2779,9 @@ var tailOf = (secret) => {
2321
2779
  };
2322
2780
 
2323
2781
  // src/secrets/lines.ts
2324
- var lineLocator = (text8) => {
2782
+ var lineLocator = (text9) => {
2325
2783
  const starts = [0];
2326
- for (let index = text8.indexOf("\n"); index !== -1; index = text8.indexOf("\n", index + 1)) starts.push(index + 1);
2784
+ for (let index = text9.indexOf("\n"); index !== -1; index = text9.indexOf("\n", index + 1)) starts.push(index + 1);
2327
2785
  return (offset) => {
2328
2786
  let low = 0;
2329
2787
  let high = starts.length - 1;
@@ -2617,12 +3075,12 @@ var hasRandomRun = (secret, length) => (secret.match(new RegExp(`[A-Za-z0-9]{${S
2617
3075
  );
2618
3076
  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
3077
  var NAME_CHAR = /[A-Za-z0-9_.-]/;
2620
- var nameAround = (text8, at) => {
3078
+ var nameAround = (text9, at) => {
2621
3079
  let start = at;
2622
- while (start > 0 && NAME_CHAR.test(text8.charAt(start - 1))) start -= 1;
3080
+ while (start > 0 && NAME_CHAR.test(text9.charAt(start - 1))) start -= 1;
2623
3081
  let end = at;
2624
- while (end < text8.length && NAME_CHAR.test(text8.charAt(end))) end += 1;
2625
- return text8.slice(start, end).toLowerCase();
3082
+ while (end < text9.length && NAME_CHAR.test(text9.charAt(end))) end += 1;
3083
+ return text9.slice(start, end).toLowerCase();
2626
3084
  };
2627
3085
  var ignoredName = (rule, match) => rule.ignoredNames.length > 0 && rule.ignoredNames.includes(nameAround(match.input, match.index));
2628
3086
  var spanOf = (rule, match) => {
@@ -2632,9 +3090,9 @@ var spanOf = (rule, match) => {
2632
3090
  const secret = match.input.slice(start, end);
2633
3091
  return accepted(rule, secret) ? { rule, start, end, secret } : void 0;
2634
3092
  };
2635
- var ruleSpans = (rule, text8) => {
3093
+ var ruleSpans = (rule, text9) => {
2636
3094
  const spans = [];
2637
- for (const match of text8.matchAll(rule.pattern)) {
3095
+ for (const match of text9.matchAll(rule.pattern)) {
2638
3096
  const span = spanOf(rule, match);
2639
3097
  if (span !== void 0) spans.push(span);
2640
3098
  }
@@ -2660,31 +3118,31 @@ var toFinding = (span, locate2) => {
2660
3118
  tail: tailOf(span.secret)
2661
3119
  };
2662
3120
  };
2663
- var scanText = (text8, options = {}) => {
2664
- if (text8.length === 0) return [];
2665
- const rules = applicable(options.rules ?? RULES, text8.toLowerCase(), options.path);
3121
+ var scanText = (text9, options = {}) => {
3122
+ if (text9.length === 0) return [];
3123
+ const rules = applicable(options.rules ?? RULES, text9.toLowerCase(), options.path);
2666
3124
  if (rules.length === 0) return [];
2667
3125
  const taken = [];
2668
3126
  for (const rule of rules) {
2669
- for (const span of ruleSpans(rule, text8)) if (!overlaps(taken, span)) taken.push(span);
3127
+ for (const span of ruleSpans(rule, text9)) if (!overlaps(taken, span)) taken.push(span);
2670
3128
  }
2671
3129
  if (taken.length === 0) return [];
2672
- const locate2 = lineLocator(text8);
3130
+ const locate2 = lineLocator(text9);
2673
3131
  return taken.sort((a, b) => a.start - b.start).map((span) => toFinding(span, locate2));
2674
3132
  };
2675
3133
 
2676
3134
  // src/mask/mask.ts
2677
3135
  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;
3136
+ var maskText = (text9) => {
3137
+ const findings = scanText(text9, { rules: CREDENTIAL_RULES });
3138
+ if (findings.length === 0) return text9;
2681
3139
  let out = "";
2682
3140
  let cursor = 0;
2683
3141
  for (const finding4 of findings) {
2684
- out += text8.slice(cursor, finding4.start) + maskToken(finding4);
3142
+ out += text9.slice(cursor, finding4.start) + maskToken(finding4);
2685
3143
  cursor = finding4.end;
2686
3144
  }
2687
- return out + text8.slice(cursor);
3145
+ return out + text9.slice(cursor);
2688
3146
  };
2689
3147
  var maskDeep = (value) => {
2690
3148
  if (typeof value === "string") return maskText(value);
@@ -2696,11 +3154,11 @@ var maskDeep = (value) => {
2696
3154
  };
2697
3155
 
2698
3156
  // src/task/paths.ts
2699
- import { join as join6 } from "node:path";
3157
+ import { join as join8 } from "node:path";
2700
3158
  var FM_DIR = ".finitemonkey";
2701
3159
  var fmPaths = (root) => {
2702
- const dir = toAbsolutePath(join6(root, FM_DIR));
2703
- const at = (...parts) => toAbsolutePath(join6(dir, ...parts));
3160
+ const dir = toAbsolutePath(join8(root, FM_DIR));
3161
+ const at = (...parts) => toAbsolutePath(join8(dir, ...parts));
2704
3162
  return {
2705
3163
  root,
2706
3164
  dir,
@@ -2902,14 +3360,14 @@ var toolVersion = (env, command) => {
2902
3360
  return result.ok ? result.stdout.trim().split("\n")[0] ?? "" : "not found";
2903
3361
  };
2904
3362
  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) => {
3363
+ const text9 = env.runtime.readText(fmPaths(env.root).log);
3364
+ if (!text9.ok) return [];
3365
+ return text9.value.split("\n").filter((line2) => {
2908
3366
  const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
2909
3367
  return parsed?.ok === true && isRecord(parsed.value) && parsed.value["event"] === HOOK_FAILURE_EVENT;
2910
3368
  }).slice(-MAX_FAILURES);
2911
3369
  };
2912
- var withoutHome = (text8, home) => home.length > 1 ? text8.split(home).join("~") : text8;
3370
+ var withoutHome = (text9, home) => home.length > 1 ? text9.split(home).join("~") : text9;
2913
3371
  var reportMarkdown = (env, ctx, envelope) => {
2914
3372
  const failures = lastHookFailures(env);
2915
3373
  const lines3 = [
@@ -2936,18 +3394,18 @@ var reportMarkdown = (env, ctx, envelope) => {
2936
3394
  ];
2937
3395
  return maskText(withoutHome(lines3.join("\n"), env.runtime.homeDir()));
2938
3396
  };
2939
- var reportPath = (env) => toAbsolutePath(join7(env.root, ...REPORTS_DIR.split("/"), `doctor-${stamp(env.runtime.now())}.md`));
3397
+ var reportPath = (env) => toAbsolutePath(join9(env.root, ...REPORTS_DIR.split("/"), `doctor-${stamp(env.runtime.now())}.md`));
2940
3398
 
2941
3399
  // src/install/plugin-yield.ts
2942
- import { join as join8 } from "node:path";
3400
+ import { join as join10 } from "node:path";
2943
3401
  var PLUGIN_VIA = "plugin";
2944
3402
  var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
2945
3403
  `;
2946
3404
  var projectOwnsHooks = (ctx) => {
2947
3405
  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);
3406
+ const text9 = ctx.runtime.readText(toAbsolutePath(join10(root, ...SETTINGS_FILE.split("/"))));
3407
+ if (!text9.ok) return false;
3408
+ const parsed = parseJsonObject(text9.value);
2951
3409
  return parsed.ok && ownHandlersIn(parsed.value) > 0;
2952
3410
  };
2953
3411
 
@@ -2959,7 +3417,7 @@ var STABLE_PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
2959
3417
  var NEXT_PLUGIN_ID = `${PLUGIN_NAME}@${NEXT_MARKETPLACE_NAME}`;
2960
3418
 
2961
3419
  // src/doctor/settings.ts
2962
- import { join as join9 } from "node:path";
3420
+ import { join as join11 } from "node:path";
2963
3421
  var MANAGED = {
2964
3422
  darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
2965
3423
  linux: "/etc/claude-code/managed-settings.json",
@@ -2968,7 +3426,7 @@ var MANAGED = {
2968
3426
  var userSettingsPath = ({ env, runtime }) => {
2969
3427
  const configDir = env["CLAUDE_CONFIG_DIR"];
2970
3428
  return toAbsolutePath(
2971
- configDir === void 0 ? join9(runtime.homeDir(), ".claude", "settings.json") : join9(configDir, "settings.json")
3429
+ configDir === void 0 ? join11(runtime.homeDir(), ".claude", "settings.json") : join11(configDir, "settings.json")
2972
3430
  );
2973
3431
  };
2974
3432
  var settingsPaths = (env) => {
@@ -2976,13 +3434,13 @@ var settingsPaths = (env) => {
2976
3434
  return [
2977
3435
  ...managed === void 0 ? [] : [{ scope: "managed", path: toAbsolutePath(managed) }],
2978
3436
  { 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")) }
3437
+ { scope: "project", path: toAbsolutePath(join11(env.root, ".claude", "settings.json")) },
3438
+ { scope: "local", path: toAbsolutePath(join11(env.root, ".claude", "settings.local.json")) }
2981
3439
  ];
2982
3440
  };
2983
3441
  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;
3442
+ const text9 = env.runtime.readText(path);
3443
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
2986
3444
  return parsed?.ok === true ? [{ scope: scope2, path, settings: parsed.value }] : [];
2987
3445
  });
2988
3446
  var scopesWith = (all, test) => all.filter(({ settings }) => test(settings)).map(({ scope: scope2 }) => scope2);
@@ -3121,7 +3579,7 @@ var checkClaudeSettings = (env) => {
3121
3579
  };
3122
3580
 
3123
3581
  // src/doctor/environment.ts
3124
- import { join as join10 } from "node:path";
3582
+ import { join as join12 } from "node:path";
3125
3583
  var AUTH_VARIABLES = [
3126
3584
  "ANTHROPIC_API_KEY",
3127
3585
  "ANTHROPIC_AUTH_TOKEN",
@@ -3159,7 +3617,7 @@ var pathDirs = ({ env, platform }) => {
3159
3617
  var copiesOnPath = (env) => {
3160
3618
  const names = EXECUTABLES[env.platform] ?? ["finitemonkey"];
3161
3619
  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))
3620
+ (dir) => names.map((name) => toAbsolutePath(join12(dir, name))).filter((path) => env.runtime.stat(path)?.kind === "file").map((path) => env.runtime.realpath(path))
3163
3621
  );
3164
3622
  const byDir = new Map(found.map((path) => [path.replace(/\.(cmd|exe|ps1)$/i, ""), path]));
3165
3623
  return [...byDir.values()];
@@ -3193,7 +3651,7 @@ var checkAuthVariables = ({ env, platform }) => {
3193
3651
  };
3194
3652
 
3195
3653
  // src/doctor/hooks.ts
3196
- import { join as join34 } from "node:path";
3654
+ import { join as join36 } from "node:path";
3197
3655
 
3198
3656
  // src/hooks/events.ts
3199
3657
  var HOOK_EVENTS = [
@@ -3240,14 +3698,14 @@ var deadlineFor = (event, raw) => {
3240
3698
  };
3241
3699
 
3242
3700
  // src/gate/hooks/common.ts
3243
- import { dirname as dirname5, join as join11 } from "node:path";
3701
+ import { dirname as dirname5, join as join13 } from "node:path";
3244
3702
  var DEFAULT_GATE_BUDGET_MS = 3e5;
3245
3703
  var sessionCwd = (input) => input.payload.cwd === void 0 ? input.cwd : toAbsolutePath(input.payload.cwd, input.cwd);
3246
3704
  var MAX_DEPTH = 64;
3247
3705
  var findRepoRoot = (runtime, start) => {
3248
3706
  let current2 = start;
3249
3707
  for (let depth = 0; depth < MAX_DEPTH; depth += 1) {
3250
- if (runtime.stat(toAbsolutePath(join11(current2, ".git"))) !== void 0) return current2;
3708
+ if (runtime.stat(toAbsolutePath(join13(current2, ".git"))) !== void 0) return current2;
3251
3709
  const parent = toAbsolutePath(dirname5(current2));
3252
3710
  if (parent === current2) return void 0;
3253
3711
  current2 = parent;
@@ -3267,7 +3725,7 @@ var contextOutput = (event, additionalContext, systemMessage) => ({
3267
3725
  hookSpecificOutput: { hookEventName: event, additionalContext },
3268
3726
  ...systemMessage === void 0 ? {} : { systemMessage }
3269
3727
  });
3270
- var human = (text8) => `finitemonkey: ${text8}`;
3728
+ var human = (text9) => `finitemonkey: ${text9}`;
3271
3729
 
3272
3730
  // src/hooks/fields.ts
3273
3731
  var textField = (record3, ...names) => {
@@ -3298,7 +3756,7 @@ var editsOf = (input) => {
3298
3756
  });
3299
3757
  };
3300
3758
  var applyEdits = (before, edits) => edits.reduce(
3301
- (text8, edit) => edit.all ? text8.split(edit.oldText).join(edit.newText) : text8.replace(edit.oldText, () => edit.newText),
3759
+ (text9, edit) => edit.all ? text9.split(edit.oldText).join(edit.newText) : text9.replace(edit.oldText, () => edit.newText),
3302
3760
  before
3303
3761
  );
3304
3762
 
@@ -3318,7 +3776,7 @@ var familyOf = (base) => {
3318
3776
  if (found === void 0) throw new Error(`no reason family for ${base}`);
3319
3777
  return found;
3320
3778
  };
3321
- var capReason = (text8) => text8.length <= REASON_LIMIT ? text8 : `${text8.slice(0, REASON_LIMIT - 1)}…`;
3779
+ var capReason = (text9) => text9.length <= REASON_LIMIT ? text9 : `${text9.slice(0, REASON_LIMIT - 1)}…`;
3322
3780
  var modelReason = (t, base, params2 = {}) => {
3323
3781
  const claim = `${base}.claim`;
3324
3782
  const requires = `${base}.requires`;
@@ -3345,9 +3803,9 @@ var textRecord = (value) => isRecord(value) ? Object.fromEntries(
3345
3803
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
3346
3804
  ) : {};
3347
3805
  var readJsonFile = (runtime, path) => {
3348
- const text8 = runtime.readText(path);
3349
- if (!text8.ok) return void 0;
3350
- const parsed = parseJsonObject(text8.value);
3806
+ const text9 = runtime.readText(path);
3807
+ if (!text9.ok) return void 0;
3808
+ const parsed = parseJsonObject(text9.value);
3351
3809
  return parsed.ok ? parsed.value : void 0;
3352
3810
  };
3353
3811
  var writeJsonFile = (runtime, path, value) => runtime.writeText(path, `${JSON.stringify({ schemaVersion: 1, ...value }, null, 2)}
@@ -3397,28 +3855,28 @@ var closeTask = (state, id) => assemble({
3397
3855
 
3398
3856
  // src/guards/release/grants.ts
3399
3857
  var GRANT_MINUTES = 30;
3400
- var text2 = (record3, key) => {
3858
+ var text3 = (record3, key) => {
3401
3859
  const value = record3[key];
3402
3860
  return typeof value === "string" ? value : void 0;
3403
3861
  };
3404
3862
  var toGrant = (value) => {
3405
3863
  if (!isRecord(value)) return void 0;
3406
- const guard = text2(value, "guard");
3407
- const reason = text2(value, "reason");
3408
- const grantedAt = text2(value, "grantedAt");
3409
- const expiresAt = text2(value, "expiresAt");
3410
- const sessionId = text2(value, "sessionId");
3864
+ const guard = text3(value, "guard");
3865
+ const reason = text3(value, "reason");
3866
+ const grantedAt = text3(value, "grantedAt");
3867
+ const expiresAt = text3(value, "expiresAt");
3868
+ const sessionId = text3(value, "sessionId");
3411
3869
  if (guard === void 0 || reason === void 0 || grantedAt === void 0 || expiresAt === void 0)
3412
3870
  return void 0;
3413
3871
  return { guard, reason, grantedAt, expiresAt, ...sessionId === void 0 ? {} : { sessionId } };
3414
3872
  };
3415
3873
  var toApproval = (value) => {
3416
3874
  if (!isRecord(value)) return void 0;
3417
- const task = text2(value, "task");
3418
- const check2 = text2(value, "check");
3419
- const at = text2(value, "at");
3875
+ const task = text3(value, "task");
3876
+ const check2 = text3(value, "check");
3877
+ const at = text3(value, "at");
3420
3878
  if (task === void 0 || check2 === void 0 || at === void 0) return void 0;
3421
- return { task, check: check2, at, reason: text2(value, "reason") ?? "" };
3879
+ return { task, check: check2, at, reason: text3(value, "reason") ?? "" };
3422
3880
  };
3423
3881
  var list = (raw, map) => Array.isArray(raw) ? raw.flatMap((item2) => {
3424
3882
  const value = map(item2);
@@ -3460,7 +3918,7 @@ var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
3460
3918
  import { posix } from "node:path";
3461
3919
 
3462
3920
  // src/shell/scan.ts
3463
- var cursorOf = (text8) => ({ text: text8, pos: 0, incomplete: false, subs: [] });
3921
+ var cursorOf = (text9) => ({ text: text9, pos: 0, incomplete: false, subs: [] });
3464
3922
  var peek = (cursor, offset = 0) => cursor.text.charAt(cursor.pos + offset);
3465
3923
  var atEnd = (cursor) => cursor.pos >= cursor.text.length;
3466
3924
  var skipQuoted = (cursor, quote) => {
@@ -3675,8 +4133,8 @@ var nextToken = (cursor, tokens, pending) => {
3675
4133
  tokens.push({ kind: "op", text: op });
3676
4134
  if (op === "\n") flushHeredocs(cursor, tokens, pending);
3677
4135
  };
3678
- var lexBash = (text8) => {
3679
- const cursor = cursorOf(text8.replace(/\r\n/g, "\n"));
4136
+ var lexBash = (text9) => {
4137
+ const cursor = cursorOf(text9.replace(/\r\n/g, "\n"));
3680
4138
  const tokens = [];
3681
4139
  const pending = [];
3682
4140
  skipBlank(cursor);
@@ -3824,9 +4282,9 @@ var next = (cursor, tokens, cmd) => {
3824
4282
  }
3825
4283
  const op = OPERATORS2.find((candidate) => cursor.text.startsWith(candidate, cursor.pos));
3826
4284
  if (op !== void 0 || cmd && peek(cursor) === "&") {
3827
- const text8 = op ?? "&";
3828
- cursor.pos += text8.length;
3829
- tokens.push({ kind: "op", text: text8 });
4285
+ const text9 = op ?? "&";
4286
+ cursor.pos += text9.length;
4287
+ tokens.push({ kind: "op", text: text9 });
3830
4288
  return;
3831
4289
  }
3832
4290
  if ((peek(cursor) === "&" || peek(cursor) === ".") && peek(cursor, 1) === " " && commandStart(tokens)) {
@@ -3835,8 +4293,8 @@ var next = (cursor, tokens, cmd) => {
3835
4293
  }
3836
4294
  tokens.push(word(cursor, cmd));
3837
4295
  };
3838
- var lexPowerShell = (text8, cmd = false) => {
3839
- const cursor = cursorOf(text8.replace(/\r\n/g, "\n"));
4296
+ var lexPowerShell = (text9, cmd = false) => {
4297
+ const cursor = cursorOf(text9.replace(/\r\n/g, "\n"));
3840
4298
  const tokens = [];
3841
4299
  const skip = () => {
3842
4300
  while (peek(cursor) === " " || peek(cursor) === " ") cursor.pos += 1;
@@ -4039,7 +4497,7 @@ var MAX_DEPTH2 = 8;
4039
4497
  var ASSIGNMENT2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s;
4040
4498
  var PS_ENV = /^\$env:([A-Za-z_][A-Za-z0-9_]*)=?(.*)$/is;
4041
4499
  var CHANGES_DIR = /* @__PURE__ */ new Set(["cd", "pushd", "set-location", "push-location"]);
4042
- var lex = (text8, language) => language === "bash" ? lexBash(text8) : lexPowerShell(text8, language === "cmd");
4500
+ var lex = (text9, language) => language === "bash" ? lexBash(text9) : lexPowerShell(text9, language === "cmd");
4043
4501
  var isWindowsAbsolute = (path) => /^[A-Za-z]:\//.test(path);
4044
4502
  var joinDir = (current2, target2) => {
4045
4503
  const next2 = toPosixPath(target2);
@@ -4110,21 +4568,21 @@ var visit = (raw, language, context, depth, collector) => {
4110
4568
  emit(result.stage, raw, language, context, collector);
4111
4569
  for (const extra of result.extra) emit(extra, raw, language, context, collector);
4112
4570
  };
4113
- var collect = (text8, language, context, depth, collector) => {
4571
+ var collect = (text9, language, context, depth, collector) => {
4114
4572
  if (depth > MAX_DEPTH2) {
4115
4573
  collector.incomplete = true;
4116
4574
  return;
4117
4575
  }
4118
- const lexed = lex(text8, language);
4576
+ const lexed = lex(text9, language);
4119
4577
  if (lexed.incomplete) collector.incomplete = true;
4120
4578
  for (const substitution of lexed.substitutions) {
4121
4579
  collect(substitution, language, { ...context }, depth + 1, collector);
4122
4580
  }
4123
4581
  for (const raw of rawCommands(lexed.tokens)) visit(raw, language, context, depth, collector);
4124
4582
  };
4125
- var normalizeCommand = (text8, dialect = "bash") => {
4583
+ var normalizeCommand = (text9, dialect = "bash") => {
4126
4584
  const collector = { out: [], incomplete: false };
4127
- collect(text8, dialect, { env: {}, dir: void 0, via: [] }, 0, collector);
4585
+ collect(text9, dialect, { env: {}, dir: void 0, via: [] }, 0, collector);
4128
4586
  return { commands: collector.out, incomplete: collector.incomplete };
4129
4587
  };
4130
4588
  var dialectOf = (toolName3) => toolName3 === "PowerShell" ? "powershell" : "bash";
@@ -4136,7 +4594,7 @@ var segmentsOf = (command) => normalizeCommand(command).commands.map((item2) =>
4136
4594
 
4137
4595
  // src/guards/shell.ts
4138
4596
  var HEREDOC = /^<<[-<]?$/;
4139
- var parseShell = (text8) => normalizeCommand(text8).commands.map((command) => ({
4597
+ var parseShell = (text9) => normalizeCommand(text9).commands.map((command) => ({
4140
4598
  env: command.env,
4141
4599
  argv: command.program === "" ? [] : [command.program, ...command.args],
4142
4600
  redirects: command.redirects.filter((redirect) => !HEREDOC.test(redirect.op)).map((redirect) => redirect.target)
@@ -4298,9 +4756,9 @@ var writtenPaths = (command) => {
4298
4756
  if (name === "git" && GIT_WRITERS.has(operands2[0] ?? "")) return [...command.redirects, ...operands2.slice(1)];
4299
4757
  return command.redirects;
4300
4758
  };
4301
- var mentioned = (text8) => {
4302
- if (/\.finitemonkey[\\/]allow/.test(text8)) return "allow_store";
4303
- if (/\.finitemonkey|\.gitleaksignore/.test(text8)) return "policy";
4759
+ var mentioned = (text9) => {
4760
+ if (/\.finitemonkey[\\/]allow/.test(text9)) return "allow_store";
4761
+ if (/\.finitemonkey|\.gitleaksignore/.test(text9)) return "policy";
4304
4762
  return void 0;
4305
4763
  };
4306
4764
  var bashSelfTargets = (root, commandLine) => parseShell(commandLine).flatMap(
@@ -4340,7 +4798,7 @@ var selfGuard = (input) => {
4340
4798
  };
4341
4799
 
4342
4800
  // src/guards/tests/hook.ts
4343
- import { join as join12, relative as relative3 } from "node:path";
4801
+ import { join as join14, relative as relative3 } from "node:path";
4344
4802
 
4345
4803
  // src/guards/tests/patterns.ts
4346
4804
  var JS_TEST = /(^|\/)(__tests__|__test__)\/|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)tests?\/.*\.[cm]?[jt]sx?$/;
@@ -4504,8 +4962,8 @@ var compareConfig = (path, before, after, accepted2) => {
4504
4962
  // src/guards/tests/hook.ts
4505
4963
  var localPath = (root, path) => toPosixPath(relative3(root, toAbsolutePath(path, root)));
4506
4964
  var read = (input, path) => {
4507
- const text8 = input.runtime.readText(path);
4508
- return text8.ok ? text8.value : void 0;
4965
+ const text9 = input.runtime.readText(path);
4966
+ return text9.ok ? text9.value : void 0;
4509
4967
  };
4510
4968
  var proposed = (tool, args2, before) => {
4511
4969
  if (tool === "Write") return textField(args2, "content") ?? "";
@@ -4530,7 +4988,7 @@ var shellViolations = (input, root) => {
4530
4988
  return parseShell(command).flatMap(
4531
4989
  (simple) => writtenPaths(simple).flatMap((path) => {
4532
4990
  const local = localPath(root, path);
4533
- const exists = input.runtime.stat(toAbsolutePath(join12(root, local))) !== void 0;
4991
+ const exists = input.runtime.stat(toAbsolutePath(join14(root, local))) !== void 0;
4534
4992
  if (!exists) return [];
4535
4993
  if (isProtectedConfig(local)) return [{ kind: "config_changed", path: local }];
4536
4994
  return isTestFile(local) ? [{ kind: "test_removed", path: local }] : [];
@@ -4610,8 +5068,8 @@ var parseMarkers = (prompt) => {
4610
5068
  // src/policy/pause.ts
4611
5069
  var PAUSE_FILE = "pause.json";
4612
5070
  var readPause = (runtime, paths) => {
4613
- const text8 = runtime.readText(paths.cache(PAUSE_FILE));
4614
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
5071
+ const text9 = runtime.readText(paths.cache(PAUSE_FILE));
5072
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
4615
5073
  if (parsed?.ok !== true || !isRecord(parsed.value)) return void 0;
4616
5074
  const { sessionId, since, by } = parsed.value;
4617
5075
  if (typeof sessionId !== "string" || typeof since !== "string") return void 0;
@@ -4658,7 +5116,7 @@ var unlessPaused = (handler) => (input) => {
4658
5116
  };
4659
5117
 
4660
5118
  // src/gate/hooks/config.ts
4661
- import { join as join13 } from "node:path";
5119
+ import { join as join15 } from "node:path";
4662
5120
  var CONFIG_FILE3 = "config-hooks.json";
4663
5121
  var GUARDED_SOURCES = /* @__PURE__ */ new Set(["user_settings", "project_settings", "local_settings"]);
4664
5122
  var countIn = (value) => {
@@ -4666,20 +5124,20 @@ var countIn = (value) => {
4666
5124
  if (Array.isArray(value)) return value.reduce((sum, item2) => sum + countIn(item2), 0);
4667
5125
  return isRecord(value) ? Object.values(value).reduce((sum, item2) => sum + countIn(item2), 0) : 0;
4668
5126
  };
4669
- var censusOf = (text8) => {
4670
- if (text8 === void 0) return { hooks: 0, disabled: false };
4671
- const parsed = parseJsonObject(text8);
5127
+ var censusOf = (text9) => {
5128
+ if (text9 === void 0) return { hooks: 0, disabled: false };
5129
+ const parsed = parseJsonObject(text9);
4672
5130
  if (!parsed.ok) return void 0;
4673
5131
  return { hooks: countIn(parsed.value["hooks"]), disabled: parsed.value["disableAllHooks"] === true };
4674
5132
  };
4675
5133
  var settingsFiles = (paths, env) => [
4676
- toAbsolutePath(join13(paths.root, ".claude", "settings.json")),
4677
- toAbsolutePath(join13(paths.root, ".claude", "settings.local.json")),
4678
- ...env["HOME"] === void 0 ? [] : [toAbsolutePath(join13(env["HOME"], ".claude", "settings.json"))]
5134
+ toAbsolutePath(join15(paths.root, ".claude", "settings.json")),
5135
+ toAbsolutePath(join15(paths.root, ".claude", "settings.local.json")),
5136
+ ...env["HOME"] === void 0 ? [] : [toAbsolutePath(join15(env["HOME"], ".claude", "settings.json"))]
4679
5137
  ];
4680
5138
  var read2 = (runtime, path) => {
4681
- const text8 = runtime.readText(path);
4682
- return text8.ok ? text8.value : void 0;
5139
+ const text9 = runtime.readText(path);
5140
+ return text9.ok ? text9.value : void 0;
4683
5141
  };
4684
5142
  var known = (runtime, paths) => {
4685
5143
  const raw = readJsonFile(runtime, paths.cache(CONFIG_FILE3))?.["hooks"];
@@ -4734,9 +5192,9 @@ var LOCKFILES2 = [
4734
5192
  ];
4735
5193
  var packageManager = (files) => LOCKFILES2.find(([file]) => files.exists(file))?.[1] ?? "npm";
4736
5194
  var scriptChecks = (files) => {
4737
- const text8 = files.read("package.json");
4738
- if (text8 === void 0) return [];
4739
- const parsed = parseJsonObject(text8);
5195
+ const text9 = files.read("package.json");
5196
+ if (text9 === void 0) return [];
5197
+ const parsed = parseJsonObject(text9);
4740
5198
  const scripts = parsed.ok ? parsed.value["scripts"] : void 0;
4741
5199
  if (!isRecord(scripts)) return [];
4742
5200
  const pm = packageManager(files);
@@ -4788,7 +5246,7 @@ var implicitContract = (files) => {
4788
5246
  };
4789
5247
 
4790
5248
  // src/gate/prepare.ts
4791
- import { join as join15 } from "node:path";
5249
+ import { join as join17 } from "node:path";
4792
5250
 
4793
5251
  // src/core/yaml/scalar.ts
4794
5252
  var ESCAPES = {
@@ -4826,16 +5284,16 @@ var unescapeDouble = (body2) => {
4826
5284
  }
4827
5285
  return accept(out);
4828
5286
  };
4829
- var quotedEnd = (text8, start) => {
4830
- const quote = text8[start];
4831
- for (let index = start + 1; index < text8.length; index += 1) {
4832
- const char = text8[index];
5287
+ var quotedEnd = (text9, start) => {
5288
+ const quote = text9[start];
5289
+ for (let index = start + 1; index < text9.length; index += 1) {
5290
+ const char = text9[index];
4833
5291
  if (quote === '"' && char === "\\") {
4834
5292
  index += 1;
4835
5293
  continue;
4836
5294
  }
4837
5295
  if (char !== quote) continue;
4838
- if (quote === "'" && text8[index + 1] === "'") {
5296
+ if (quote === "'" && text9[index + 1] === "'") {
4839
5297
  index += 1;
4840
5298
  continue;
4841
5299
  }
@@ -4843,20 +5301,20 @@ var quotedEnd = (text8, start) => {
4843
5301
  }
4844
5302
  return -1;
4845
5303
  };
4846
- var parseQuoted = (text8) => {
4847
- const end = quotedEnd(text8, 0);
4848
- if (end !== text8.length - 1) return reject(`unterminated or trailing text after quoted string`);
4849
- const body2 = text8.slice(1, -1);
4850
- return text8.startsWith('"') ? unescapeDouble(body2) : accept(body2.replace(/''/g, "'"));
5304
+ var parseQuoted = (text9) => {
5305
+ const end = quotedEnd(text9, 0);
5306
+ if (end !== text9.length - 1) return reject(`unterminated or trailing text after quoted string`);
5307
+ const body2 = text9.slice(1, -1);
5308
+ return text9.startsWith('"') ? unescapeDouble(body2) : accept(body2.replace(/''/g, "'"));
4851
5309
  };
4852
5310
  var NUMBER = /^-?(0|[1-9]\d*)(\.\d+)?([eE][-+]?\d+)?$/;
4853
5311
  var UNSUPPORTED = /^[&*!|>%@`]/;
4854
- var parsePlain = (text8) => {
4855
- if (UNSUPPORTED.test(text8)) return reject(`unsupported YAML syntax "${text8.slice(0, 1)}"`);
4856
- if (text8 === "true" || text8 === "false") return accept(text8 === "true");
4857
- if (text8 === "null" || text8 === "~") return accept(null);
4858
- if (NUMBER.test(text8)) return accept(Number(text8));
4859
- return accept(text8);
5312
+ var parsePlain = (text9) => {
5313
+ if (UNSUPPORTED.test(text9)) return reject(`unsupported YAML syntax "${text9.slice(0, 1)}"`);
5314
+ if (text9 === "true" || text9 === "false") return accept(text9 === "true");
5315
+ if (text9 === "null" || text9 === "~") return accept(null);
5316
+ if (NUMBER.test(text9)) return accept(Number(text9));
5317
+ return accept(text9);
4860
5318
  };
4861
5319
 
4862
5320
  // src/core/yaml/flow.ts
@@ -4883,17 +5341,17 @@ var splitTop = (body2) => {
4883
5341
  if (last.trim() !== "" || parts.length > 0) parts.push(last);
4884
5342
  return accept(parts.map((part2) => part2.trim()));
4885
5343
  };
4886
- var splitKey = (text8) => {
5344
+ var splitKey = (text9) => {
4887
5345
  let index = 0;
4888
- if (text8.startsWith('"') || text8.startsWith("'")) {
4889
- index = quotedEnd(text8, 0) + 1;
5346
+ if (text9.startsWith('"') || text9.startsWith("'")) {
5347
+ index = quotedEnd(text9, 0) + 1;
4890
5348
  if (index <= 0) return void 0;
4891
5349
  }
4892
- for (; index < text8.length; index += 1) {
4893
- const char = text8[index];
4894
- if (char === "#" && text8[index - 1] === " ") return void 0;
4895
- if (char === ":" && (index + 1 === text8.length || text8[index + 1] === " ")) {
4896
- return { key: text8.slice(0, index).trim(), rest: text8.slice(index + 1).trim() };
5350
+ for (; index < text9.length; index += 1) {
5351
+ const char = text9[index];
5352
+ if (char === "#" && text9[index - 1] === " ") return void 0;
5353
+ if (char === ":" && (index + 1 === text9.length || text9[index + 1] === " ")) {
5354
+ return { key: text9.slice(0, index).trim(), rest: text9.slice(index + 1).trim() };
4897
5355
  }
4898
5356
  }
4899
5357
  return void 0;
@@ -4922,39 +5380,39 @@ var flowSeq = (parts) => {
4922
5380
  }
4923
5381
  return accept(out);
4924
5382
  };
4925
- var parseValue = (text8) => {
4926
- if (text8 === "") return accept(null);
4927
- const first = text8[0] ?? "";
4928
- if (first === '"' || first === "'") return parseQuoted(text8);
5383
+ var parseValue = (text9) => {
5384
+ if (text9 === "") return accept(null);
5385
+ const first = text9[0] ?? "";
5386
+ if (first === '"' || first === "'") return parseQuoted(text9);
4929
5387
  const close2 = OPEN[first];
4930
- if (close2 === void 0) return parsePlain(text8);
4931
- if (!text8.endsWith(close2)) return reject(`flow collection must end on the same line with "${close2}"`);
4932
- const parts = splitTop(text8.slice(1, -1));
5388
+ if (close2 === void 0) return parsePlain(text9);
5389
+ if (!text9.endsWith(close2)) return reject(`flow collection must end on the same line with "${close2}"`);
5390
+ const parts = splitTop(text9.slice(1, -1));
4933
5391
  if (!parts.ok) return parts;
4934
5392
  return first === "[" ? flowSeq(parts.value) : flowMap(parts.value);
4935
5393
  };
4936
5394
 
4937
5395
  // src/core/yaml/parse.ts
4938
5396
  var BOM = new RegExp(`^${String.fromCharCode(65279)}`);
4939
- var stripComment2 = (text8) => {
4940
- for (let index = 0; index < text8.length; index += 1) {
4941
- const char = text8[index];
5397
+ var stripComment2 = (text9) => {
5398
+ for (let index = 0; index < text9.length; index += 1) {
5399
+ const char = text9[index];
4942
5400
  if (char === '"' || char === "'") {
4943
- const end = quotedEnd(text8, index);
4944
- if (end < 0) return text8;
5401
+ const end = quotedEnd(text9, index);
5402
+ if (end < 0) return text9;
4945
5403
  index = end;
4946
- } else if (char === "#" && (index === 0 || text8[index - 1] === " ")) return text8.slice(0, index);
5404
+ } else if (char === "#" && (index === 0 || text9[index - 1] === " ")) return text9.slice(0, index);
4947
5405
  }
4948
- return text8;
5406
+ return text9;
4949
5407
  };
4950
5408
  var toLines = (source) => {
4951
5409
  const lines3 = [];
4952
5410
  const raw = source.replace(BOM, "").split(/\r?\n/);
4953
5411
  for (const [index, full] of raw.entries()) {
4954
- const text8 = stripComment2(full).trimEnd();
4955
- const body2 = text8.trimStart();
5412
+ const text9 = stripComment2(full).trimEnd();
5413
+ const body2 = text9.trimStart();
4956
5414
  if (body2 === "" || body2 === "---" && lines3.length === 0) continue;
4957
- const indentText = text8.slice(0, text8.length - body2.length);
5415
+ const indentText = text9.slice(0, text9.length - body2.length);
4958
5416
  if (indentText.includes(" ")) return reject({ line: index + 1, message: "tabs are not allowed for indentation" });
4959
5417
  if (body2 === "---" || body2 === "...")
4960
5418
  return reject({ line: index + 1, message: "only one YAML document is allowed" });
@@ -4963,11 +5421,11 @@ var toLines = (source) => {
4963
5421
  return accept(lines3);
4964
5422
  };
4965
5423
  var fail = (line2, message) => reject({ line: line2?.number ?? 0, message });
4966
- var scalar = (line2, text8) => {
4967
- const value = parseValue(text8);
5424
+ var scalar = (line2, text9) => {
5425
+ const value = parseValue(text9);
4968
5426
  return value.ok ? value : fail(line2, value.error);
4969
5427
  };
4970
- var isItem = (text8) => text8 === "-" || text8.startsWith("- ");
5428
+ var isItem = (text9) => text9 === "-" || text9.startsWith("- ");
4971
5429
  var child = (lines3, index, parent, sameIndentSeq) => {
4972
5430
  const next2 = lines3[index];
4973
5431
  if (next2 !== void 0 && next2.indent > parent.indent) return block(lines3, index, next2.indent);
@@ -5210,8 +5668,8 @@ var validateContract = (value, implicit = false) => {
5210
5668
  // src/task/load.ts
5211
5669
  var contractHash = (contract) => sha256Hex(JSON.stringify(contract.acceptance.map((check2) => [check2.id, check2.kind, check2.run ?? ""])));
5212
5670
  var taskRelative = (id) => `.finitemonkey/tasks/${id}.yml`;
5213
- var parseContract = (id, text8) => {
5214
- const yaml = parseYaml(text8);
5671
+ var parseContract = (id, text9) => {
5672
+ const yaml = parseYaml(text9);
5215
5673
  if (!yaml.ok)
5216
5674
  return reject({ kind: "invalid", id, issues: [`line ${String(yaml.error.line)}: ${yaml.error.message}`] });
5217
5675
  const valid = validateContract(yaml.value);
@@ -5285,7 +5743,7 @@ var readBaseline = (runtime, paths) => {
5285
5743
  var writeBaseline = (runtime, paths, treeHash2, checks) => writeJsonFile(runtime, paths.cache(BASELINE_FILE), { treeHash: treeHash2, checks: { ...checks } }).ok;
5286
5744
 
5287
5745
  // src/gate/diff-shape.ts
5288
- import { join as join14 } from "node:path";
5746
+ import { join as join16 } from "node:path";
5289
5747
 
5290
5748
  // src/gate/git.ts
5291
5749
  var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
@@ -5321,10 +5779,10 @@ var resolveBase = (git2, declared) => {
5321
5779
  const merged2 = remote === void 0 ? void 0 : git2.out(["merge-base", "HEAD", remote])?.trim();
5322
5780
  return nonEmpty(merged2) ?? nonEmpty(headSha(git2)) ?? EMPTY_TREE;
5323
5781
  };
5324
- var nulFields = (text8) => text8.split("\0").filter((part2) => part2 !== "");
5782
+ var nulFields = (text9) => text9.split("\0").filter((part2) => part2 !== "");
5325
5783
  var untracked = (git2) => nulFields(git2.out(["ls-files", "--others", "--exclude-standard", "-z", "--", ".", EXCLUDE_FM]) ?? "");
5326
- var parseNameStatus = (text8) => {
5327
- const fields = nulFields(text8);
5784
+ var parseNameStatus = (text9) => {
5785
+ const fields = nulFields(text9);
5328
5786
  const changes = [];
5329
5787
  for (let index = 0; index < fields.length; index += 1) {
5330
5788
  const code = fields[index] ?? "";
@@ -5392,8 +5850,8 @@ var aheadOfUpstream = (git2) => {
5392
5850
 
5393
5851
  // src/gate/diff-shape.ts
5394
5852
  var untrackedLines = (runtime, root, changes) => changes.filter((change) => change.status === "A").reduce((sum, change) => {
5395
- const text8 = runtime.readText(toAbsolutePath(join14(root, change.path)));
5396
- return sum + (text8.ok ? text8.value.split("\n").length : 0);
5853
+ const text9 = runtime.readText(toAbsolutePath(join16(root, change.path)));
5854
+ return sum + (text9.ok ? text9.value.split("\n").length : 0);
5397
5855
  }, 0);
5398
5856
  var diffShape = (runtime, git2, base, changes) => ({
5399
5857
  files: changes.map((change) => change.path),
@@ -5416,8 +5874,8 @@ var ciChecks = (value) => Array.isArray(value) ? value.flatMap((item2) => {
5416
5874
  const name = optionalString(item2, "name") ?? optionalString(item2, "context") ?? "?";
5417
5875
  return [{ name, state: ciState(item2) }];
5418
5876
  }) : [];
5419
- var parsePrView = (text8) => {
5420
- const parsed = parseJsonObject(text8);
5877
+ var parsePrView = (text9) => {
5878
+ const parsed = parseJsonObject(text9);
5421
5879
  if (!parsed.ok) return void 0;
5422
5880
  const record3 = parsed.value;
5423
5881
  const number2 = record3["number"];
@@ -5598,10 +6056,10 @@ var applySize = (contract, verdict) => {
5598
6056
  // src/gate/prepare.ts
5599
6057
  var diskFiles = (runtime, root) => ({
5600
6058
  read: (relative11) => {
5601
- const text8 = runtime.readText(toAbsolutePath(join15(root, relative11)));
5602
- return text8.ok ? text8.value : void 0;
6059
+ const text9 = runtime.readText(toAbsolutePath(join17(root, relative11)));
6060
+ return text9.ok ? text9.value : void 0;
5603
6061
  },
5604
- exists: (relative11) => runtime.stat(toAbsolutePath(join15(root, relative11))) !== void 0
6062
+ exists: (relative11) => runtime.stat(toAbsolutePath(join17(root, relative11))) !== void 0
5605
6063
  });
5606
6064
  var baseFiles = (git2, base, disk) => ({
5607
6065
  read: (relative11) => base === EMPTY_TREE ? disk.read(relative11) : fileAt(git2, base, relative11),
@@ -5613,7 +6071,7 @@ var loadContract = (options, paths, git2, base) => {
5613
6071
  if (id === void 0) return accept(loadImplicit(options.runtime, paths, baseFiles(git2, base, disk)));
5614
6072
  return loadTask(options.runtime, paths, { disk: disk.read, base: (relative11) => fileAt(git2, base, relative11) }, id);
5615
6073
  };
5616
- var memo = (make) => {
6074
+ var memo2 = (make) => {
5617
6075
  let value;
5618
6076
  return () => {
5619
6077
  value ??= { v: make() };
@@ -5652,7 +6110,7 @@ var prepareGate = (options) => {
5652
6110
  if (tree === void 0) return { kind: "failed", failure: { kind: "no_tree_hash" } };
5653
6111
  const skip = options.noSkip === true ? void 0 : skipReason(options.runtime, paths, loaded.value, changes, tree, options.sessionId);
5654
6112
  if (skip !== void 0) return { kind: "skipped", reason: skip };
5655
- const pr = memo(() => prStatus(options.runtime, git2, paths));
6113
+ const pr = memo2(() => prStatus(options.runtime, git2, paths));
5656
6114
  const size = measureSize(options.runtime, git2, base, changes, loaded.value.contract);
5657
6115
  const ctx = {
5658
6116
  runtime: options.runtime,
@@ -5720,11 +6178,11 @@ var trust = (input, paths, task) => {
5720
6178
  });
5721
6179
  if (ready.kind !== "ready" || ready.prepared.ctx.contract.id !== task) return false;
5722
6180
  const lock = readTrust(runtime, paths);
5723
- const text8 = runtime.readText(paths.task(task));
6181
+ const text9 = runtime.readText(paths.task(task));
5724
6182
  return writeTrust(runtime, paths, {
5725
6183
  ...lock,
5726
6184
  contracts: { ...lock.contracts, [task]: ready.prepared.loaded.hash },
5727
- approved: task === IMPLICIT_ID || !text8.ok ? lock.approved : { ...lock.approved, [task]: text8.value }
6185
+ approved: task === IMPLICIT_ID || !text9.ok ? lock.approved : { ...lock.approved, [task]: text9.value }
5728
6186
  }).ok;
5729
6187
  };
5730
6188
  var apply = (input, paths, markers) => {
@@ -5776,8 +6234,8 @@ var promptRelease = (input) => {
5776
6234
  var PRS_CACHE_MS = 10 * 6e4;
5777
6235
  var PRS_TIMEOUT_MS = 3e3;
5778
6236
  var PRS_AUTH_TIMEOUT_MS = 2e3;
5779
- var parsePrList = (text8) => {
5780
- const parsed = parseJson(text8);
6237
+ var parsePrList = (text9) => {
6238
+ const parsed = parseJson(text9);
5781
6239
  if (!parsed.ok || !Array.isArray(parsed.value)) return void 0;
5782
6240
  return parsed.value.flatMap((item2) => {
5783
6241
  if (!isRecord(item2) || typeof item2["number"] !== "number") return [];
@@ -5863,16 +6321,16 @@ var params = (value) => isRecord(value) ? Object.fromEntries(
5863
6321
  var notes = (value) => Array.isArray(value) ? value.flatMap(
5864
6322
  (item2) => isRecord(item2) && typeof item2["key"] === "string" ? [{ key: item2["key"], params: params(item2["params"]) }] : []
5865
6323
  ) : [];
5866
- var text3 = (record3, key) => {
6324
+ var text4 = (record3, key) => {
5867
6325
  const value = record3[key];
5868
6326
  return typeof value === "string" ? value : void 0;
5869
6327
  };
5870
6328
  var optional = (record3) => {
5871
6329
  const exitCode = record3["exitCode"];
5872
6330
  const durationMs = record3["durationMs"];
5873
- const command = text3(record3, "command");
5874
- const stdoutSha256 = text3(record3, "stdoutSha256");
5875
- const releasedBy = text3(record3, "releasedBy");
6331
+ const command = text4(record3, "command");
6332
+ const stdoutSha256 = text4(record3, "stdoutSha256");
6333
+ const releasedBy = text4(record3, "releasedBy");
5876
6334
  const reason = oneOf2(UNVERIFIED_REASONS, record3["reason"]);
5877
6335
  const failures = record3["failures"];
5878
6336
  const testCount = record3["testCount"];
@@ -5892,11 +6350,11 @@ var parseEvidence = (line2) => {
5892
6350
  if (!parsed.ok) return void 0;
5893
6351
  const record3 = parsed.value;
5894
6352
  const status = oneOf2(EVIDENCE_STATUSES, record3["status"]);
5895
- const task = text3(record3, "task");
5896
- const check2 = text3(record3, "check");
5897
- const kind = text3(record3, "kind");
5898
- const treeHash2 = text3(record3, "treeHash");
5899
- const at = text3(record3, "at");
6353
+ const task = text4(record3, "task");
6354
+ const check2 = text4(record3, "check");
6355
+ const kind = text4(record3, "kind");
6356
+ const treeHash2 = text4(record3, "treeHash");
6357
+ const at = text4(record3, "at");
5900
6358
  if (record3["schema"] !== EVIDENCE_SCHEMA || status === void 0) return void 0;
5901
6359
  if (task === void 0 || check2 === void 0 || kind === void 0 || treeHash2 === void 0 || at === void 0) {
5902
6360
  return void 0;
@@ -5913,7 +6371,7 @@ var parseEvidence = (line2) => {
5913
6371
  ...optional(record3)
5914
6372
  };
5915
6373
  };
5916
- var readLedger = (runtime, path) => {
6374
+ var readLedger2 = (runtime, path) => {
5917
6375
  const content = runtime.readText(path);
5918
6376
  if (!content.ok) return [];
5919
6377
  return content.value.split("\n").flatMap((line2) => {
@@ -6595,7 +7053,28 @@ var en_default = {
6595
7053
  "gain.row.gate_time": "{minutes, plural, one {# minute} other {# minutes}} spent in the gate",
6596
7054
  "gain.up": "up {delta} on the window before",
6597
7055
  "gain.down": "down {delta} on the window before",
6598
- "gain.footer": "Counts come from local files only; a save is a block followed by a passing gate, not proof that the block caused it."
7056
+ "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.",
7057
+ "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",
7058
+ "critique.title": "Critique",
7059
+ "critique.group.must": "Must fix",
7060
+ "critique.group.should": "Should fix",
7061
+ "critique.group.nice": "Nice to have",
7062
+ "critique.group.already-there": "Already there (fails on base too)",
7063
+ "critique.group.not-reproduced": "Not reproduced",
7064
+ "critique.finding": "{id}. {what}. Problem: {problem}. Fix: {fix}. If not fixed: {risk}.",
7065
+ "critique.table.command": "reproduction",
7066
+ "critique.table.outcome": "outcome",
7067
+ "critique.outcome.head-only": "fails only on head: kept",
7068
+ "critique.outcome.already-there": "fails on both: already there",
7069
+ "critique.outcome.not-reproduced": "fails on neither: not reproduced",
7070
+ "critique.outcome.not-run": "not run",
7071
+ "critique.demoted": "Finding {id} was marked must fix without a reproduction command and expected result, so it counts as should fix.",
7072
+ "critique.decision": "Decision needed: finding {id} ({what}) already fails on the base branch and is an active risk.",
7073
+ "critique.verdict": "Verdict: {verdict}",
7074
+ "critique.error.invalid": "the findings file is not valid: {detail}",
7075
+ "critique.error.must_fix": "{count, plural, one {# must-fix finding reproduced on head only} other {# must-fix findings reproduced on head only}}",
7076
+ "install.warn.sandbox_offer": "The Claude Code sandbox runs here. Add --sandbox to turn it on for you only, in .claude/settings.local.json: it fails to start without the sandbox, hides ~/.ssh, ~/.aws and .env files from commands, and lets them reach only the hosts in sandbox.hosts.",
7077
+ "install.warn.sandbox_unsupported": "--sandbox was ignored: native Windows has no Claude Code sandbox, so the guards are the only protection here. Run Claude Code inside WSL2 to use it."
6599
7078
  };
6600
7079
 
6601
7080
  // src/i18n/es.json
@@ -7265,7 +7744,28 @@ var es_default = {
7265
7744
  "gain.row.gate_time": "{minutes, plural, one {# minuto} other {# minutos}} en la compuerta",
7266
7745
  "gain.up": "+{delta} respecto a la ventana anterior",
7267
7746
  "gain.down": "-{delta} respecto a la ventana anterior",
7268
- "gain.footer": "Los conteos salen solo de archivos locales; un acierto es un bloqueo seguido de una compuerta en verde, no prueba de que el bloqueo lo causara."
7747
+ "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.",
7748
+ "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",
7749
+ "critique.title": "Crítica",
7750
+ "critique.group.must": "Corregir sí o sí",
7751
+ "critique.group.should": "Debería corregirse",
7752
+ "critique.group.nice": "Sería bueno",
7753
+ "critique.group.already-there": "Ya estaba (falla también en base)",
7754
+ "critique.group.not-reproduced": "No reproducido",
7755
+ "critique.finding": "{id}. {what}. Problema: {problem}. Arreglo: {fix}. Si no se corrige: {risk}.",
7756
+ "critique.table.command": "reproducción",
7757
+ "critique.table.outcome": "resultado",
7758
+ "critique.outcome.head-only": "falla solo en head: se mantiene",
7759
+ "critique.outcome.already-there": "falla en ambos: ya estaba",
7760
+ "critique.outcome.not-reproduced": "no falla en ninguno: no reproducido",
7761
+ "critique.outcome.not-run": "no se ejecutó",
7762
+ "critique.demoted": "El hallazgo {id} venía como obligatorio sin comando de reproducción ni resultado esperado, así que cuenta como debería corregirse.",
7763
+ "critique.decision": "Decisión necesaria: el hallazgo {id} ({what}) ya falla en la rama base y es un riesgo activo.",
7764
+ "critique.verdict": "Veredicto: {verdict}",
7765
+ "critique.error.invalid": "el archivo de hallazgos no es válido: {detail}",
7766
+ "critique.error.must_fix": "{count, plural, one {# hallazgo obligatorio reproducido solo en head} other {# hallazgos obligatorios reproducidos solo en head}}",
7767
+ "install.warn.sandbox_offer": "El sandbox de Claude Code funciona aquí. Añade --sandbox para activarlo solo para ti, en .claude/settings.local.json: no arranca sin el sandbox, oculta ~/.ssh, ~/.aws y los archivos .env a los comandos y solo deja llegar a los hosts de sandbox.hosts.",
7768
+ "install.warn.sandbox_unsupported": "Se ignoró --sandbox: Windows nativo no tiene sandbox de Claude Code, así que aquí solo protegen los bloqueos. Ejecuta Claude Code dentro de WSL2 para usarlo."
7269
7769
  };
7270
7770
 
7271
7771
  // src/i18n/pt-BR.json
@@ -7935,7 +8435,28 @@ var pt_BR_default = {
7935
8435
  "gain.row.gate_time": "{minutes, plural, one {# minuto} other {# minutos}} no portão",
7936
8436
  "gain.up": "+{delta} em relação à janela anterior",
7937
8437
  "gain.down": "-{delta} em relação à janela anterior",
7938
- "gain.footer": "As contagens vêm só de arquivos locais; um acerto é um bloqueio seguido de portão verde, não prova de que o bloqueio causou isso."
8438
+ "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.",
8439
+ "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",
8440
+ "critique.title": "Crítica",
8441
+ "critique.group.must": "Corrigir obrigatoriamente",
8442
+ "critique.group.should": "Deveria corrigir",
8443
+ "critique.group.nice": "Seria bom",
8444
+ "critique.group.already-there": "Já existia (falha na base também)",
8445
+ "critique.group.not-reproduced": "Não reproduzido",
8446
+ "critique.finding": "{id}. {what}. Problema: {problem}. Conserto: {fix}. Se não corrigir: {risk}.",
8447
+ "critique.table.command": "reprodução",
8448
+ "critique.table.outcome": "resultado",
8449
+ "critique.outcome.head-only": "falha só no head: mantido",
8450
+ "critique.outcome.already-there": "falha nos dois: já existia",
8451
+ "critique.outcome.not-reproduced": "não falha em nenhum: não reproduzido",
8452
+ "critique.outcome.not-run": "não rodou",
8453
+ "critique.demoted": "O achado {id} veio como obrigatório sem comando de reprodução e resultado esperado, então conta como deveria corrigir.",
8454
+ "critique.decision": "Decisão necessária: o achado {id} ({what}) já falha na branch base e é um risco ativo.",
8455
+ "critique.verdict": "Veredito: {verdict}",
8456
+ "critique.error.invalid": "o arquivo de achados não é válido: {detail}",
8457
+ "critique.error.must_fix": "{count, plural, one {# achado obrigatório reproduzido só no head} other {# achados obrigatórios reproduzidos só no head}}",
8458
+ "install.warn.sandbox_offer": "O sandbox do Claude Code roda aqui. Acrescente --sandbox para ligar só para você, em .claude/settings.local.json: ele se recusa a iniciar sem o sandbox, esconde ~/.ssh, ~/.aws e os arquivos .env dos comandos e só deixa chegar aos hosts de sandbox.hosts.",
8459
+ "install.warn.sandbox_unsupported": "--sandbox foi ignorado: o Windows nativo não tem sandbox do Claude Code, então aqui só as travas protegem. Rode o Claude Code dentro do WSL2 para usar."
7939
8460
  };
7940
8461
 
7941
8462
  // src/i18n/review.json
@@ -8606,7 +9127,28 @@ var review_default = {
8606
9127
  "gain.row.gate_time": "machine",
8607
9128
  "gain.up": "machine",
8608
9129
  "gain.down": "machine",
8609
- "gain.footer": "machine"
9130
+ "gain.footer": "machine",
9131
+ "critique.command.verify": "machine",
9132
+ "critique.title": "machine",
9133
+ "critique.group.must": "machine",
9134
+ "critique.group.should": "machine",
9135
+ "critique.group.nice": "machine",
9136
+ "critique.group.already-there": "machine",
9137
+ "critique.group.not-reproduced": "machine",
9138
+ "critique.finding": "machine",
9139
+ "critique.table.command": "machine",
9140
+ "critique.table.outcome": "machine",
9141
+ "critique.outcome.head-only": "machine",
9142
+ "critique.outcome.already-there": "machine",
9143
+ "critique.outcome.not-reproduced": "machine",
9144
+ "critique.outcome.not-run": "machine",
9145
+ "critique.demoted": "machine",
9146
+ "critique.decision": "machine",
9147
+ "critique.verdict": "machine",
9148
+ "critique.error.invalid": "machine",
9149
+ "critique.error.must_fix": "machine",
9150
+ "install.warn.sandbox_offer": "machine",
9151
+ "install.warn.sandbox_unsupported": "machine"
8610
9152
  },
8611
9153
  es: {
8612
9154
  "command.init.summary": "machine",
@@ -9274,7 +9816,28 @@ var review_default = {
9274
9816
  "gain.row.gate_time": "machine",
9275
9817
  "gain.up": "machine",
9276
9818
  "gain.down": "machine",
9277
- "gain.footer": "machine"
9819
+ "gain.footer": "machine",
9820
+ "critique.command.verify": "machine",
9821
+ "critique.title": "machine",
9822
+ "critique.group.must": "machine",
9823
+ "critique.group.should": "machine",
9824
+ "critique.group.nice": "machine",
9825
+ "critique.group.already-there": "machine",
9826
+ "critique.group.not-reproduced": "machine",
9827
+ "critique.finding": "machine",
9828
+ "critique.table.command": "machine",
9829
+ "critique.table.outcome": "machine",
9830
+ "critique.outcome.head-only": "machine",
9831
+ "critique.outcome.already-there": "machine",
9832
+ "critique.outcome.not-reproduced": "machine",
9833
+ "critique.outcome.not-run": "machine",
9834
+ "critique.demoted": "machine",
9835
+ "critique.decision": "machine",
9836
+ "critique.verdict": "machine",
9837
+ "critique.error.invalid": "machine",
9838
+ "critique.error.must_fix": "machine",
9839
+ "install.warn.sandbox_offer": "machine",
9840
+ "install.warn.sandbox_unsupported": "machine"
9278
9841
  }
9279
9842
  };
9280
9843
 
@@ -9431,7 +9994,7 @@ var currentReport = (runtime, paths, sessionId, taskId) => {
9431
9994
  const { ctx, loaded } = ready.prepared;
9432
9995
  return buildReport({
9433
9996
  contract: ctx.contract,
9434
- ledger: readLedger(runtime, paths.evidence(ctx.contract.id)),
9997
+ ledger: readLedger2(runtime, paths.evidence(ctx.contract.id)),
9435
9998
  treeHash: ctx.treeHash,
9436
9999
  base: ctx.base,
9437
10000
  source: loaded.source,
@@ -9457,22 +10020,22 @@ var saveSnapshot = (runtime, paths, t, sessionId) => {
9457
10020
  const snapshot2 = snapshotOf(t, currentReport(runtime, paths, sessionId), grants, new Date(now).toISOString());
9458
10021
  return writeState(runtime, paths, { ...readState(runtime, paths), snapshot: snapshot2 }).ok;
9459
10022
  };
9460
- var text4 = (snapshot2, key) => {
10023
+ var text5 = (snapshot2, key) => {
9461
10024
  const value = snapshot2[key];
9462
10025
  return typeof value === "string" ? value : "";
9463
10026
  };
9464
10027
  var snapshotFacts = (t, snapshot2) => {
9465
- if (snapshot2 === void 0 || text4(snapshot2, "at") === "") return void 0;
9466
- const task = text4(snapshot2, "task");
10028
+ if (snapshot2 === void 0 || text5(snapshot2, "at") === "") return void 0;
10029
+ const task = text5(snapshot2, "task");
9467
10030
  return t(task === "" || task === "implicit" ? "gate.context.state_implicit" : "gate.context.state", {
9468
- at: text4(snapshot2, "at"),
10031
+ at: text5(snapshot2, "at"),
9469
10032
  task,
9470
- goal: text4(snapshot2, "goal"),
9471
- tree: text4(snapshot2, "tree"),
9472
- passed: text4(snapshot2, "passed"),
9473
- failed: text4(snapshot2, "failed"),
9474
- unverified: text4(snapshot2, "unverified"),
9475
- grants: text4(snapshot2, "grants")
10033
+ goal: text5(snapshot2, "goal"),
10034
+ tree: text5(snapshot2, "tree"),
10035
+ passed: text5(snapshot2, "passed"),
10036
+ failed: text5(snapshot2, "failed"),
10037
+ unverified: text5(snapshot2, "unverified"),
10038
+ grants: text5(snapshot2, "grants")
9476
10039
  });
9477
10040
  };
9478
10041
 
@@ -9564,10 +10127,10 @@ var pairs = (record3) => {
9564
10127
  return names.filter((name) => typeof name === "string").map((name) => `${task}\0${name}`);
9565
10128
  };
9566
10129
  var callersRate = (runtime, paths) => {
9567
- const text8 = runtime.readText(paths.log);
10130
+ const text9 = runtime.readText(paths.log);
9568
10131
  const flagged = /* @__PURE__ */ new Set();
9569
10132
  const released2 = /* @__PURE__ */ new Set();
9570
- for (const line2 of text8.ok ? text8.value.split("\n") : []) {
10133
+ for (const line2 of text9.ok ? text9.value.split("\n") : []) {
9571
10134
  const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
9572
10135
  if (parsed === void 0 || !parsed.ok || !isRecord(parsed.value)) continue;
9573
10136
  const event = parsed.value["event"];
@@ -9585,14 +10148,14 @@ var callersRate = (runtime, paths) => {
9585
10148
  };
9586
10149
 
9587
10150
  // src/gate/callers/scan.ts
9588
- import { join as join16 } from "node:path";
10151
+ import { join as join18 } from "node:path";
9589
10152
 
9590
10153
  // src/gate/callers/imports.ts
9591
10154
  import { posix as posix2 } from "node:path";
9592
10155
  var JS_SPECIFIER = /(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["']([^"']+)["']/g;
9593
10156
  var JS_EXTENSION = /\.(?:[cm]?[jt]sx?)$/;
9594
10157
  var stripModule = (path) => path.replace(JS_EXTENSION, "").replace(/\/index$/, "");
9595
- var jsSpecifiers = (text8) => [...text8.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
10158
+ var jsSpecifiers = (text9) => [...text9.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
9596
10159
  var jsImports = (callerPath, callerText, origin) => {
9597
10160
  const target2 = stripModule(origin);
9598
10161
  return jsSpecifiers(callerText).some((specifier) => {
@@ -9694,9 +10257,9 @@ var languageOf = (path) => {
9694
10257
  const extension = extname(path).toLowerCase();
9695
10258
  return LANGUAGES2.find((language) => language.extensions.includes(extension));
9696
10259
  };
9697
- var definitionsIn = (language, text8) => {
10260
+ var definitionsIn = (language, text9) => {
9698
10261
  const names = /* @__PURE__ */ new Set();
9699
- for (const line2 of text8.split(/\r?\n/)) {
10262
+ for (const line2 of text9.split(/\r?\n/)) {
9700
10263
  for (const pattern of language.definitions) {
9701
10264
  const name = pattern.exec(line2)?.[1];
9702
10265
  if (name !== void 0 && !KEYWORDS.has(name)) names.add(name);
@@ -9709,8 +10272,8 @@ var definesName = (language, line2, name) => language.definitions.some((pattern)
9709
10272
  // src/gate/callers/scan.ts
9710
10273
  var MAX_SYMBOLS = 200;
9711
10274
  var current = (runtime, root, path) => {
9712
- const text8 = runtime.readText(toAbsolutePath(join16(root, path)));
9713
- return text8.ok ? text8.value : "";
10275
+ const text9 = runtime.readText(toAbsolutePath(join18(root, path)));
10276
+ return text9.ok ? text9.value : "";
9714
10277
  };
9715
10278
  var keyOf = (language, name) => `${language.id}\0${name}`;
9716
10279
  var redefinedNames = (runtime, git2, changes) => {
@@ -9751,7 +10314,7 @@ var grep = (git2, names) => {
9751
10314
  git2.out(["grep", "-n", "-z", "-w", "-I", "--untracked", "--no-color", ...patterns, "--", ".", EXCLUDE_FM]) ?? ""
9752
10315
  );
9753
10316
  };
9754
- var mentions = (text8, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text8);
10317
+ var mentions = (text9, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text9);
9755
10318
  var callSites = (runtime, git2, symbols) => {
9756
10319
  const hits = grep(git2, [...new Set(symbols.map((symbol) => symbol.name))]);
9757
10320
  const texts2 = /* @__PURE__ */ new Map();
@@ -9826,31 +10389,31 @@ var ZERO = [
9826
10389
  /^ℹ tests 0$/m,
9827
10390
  /No tests found/
9828
10391
  ];
9829
- var pyTotal = (text8) => {
9830
- const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(text8)?.[1];
10392
+ var pyTotal = (text9) => {
10393
+ const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(text9)?.[1];
9831
10394
  if (line2 === void 0) return void 0;
9832
10395
  return [...line2.matchAll(/(\d+) (passed|failed|error|errors|skipped|xfailed|xpassed)/g)].reduce(
9833
10396
  (sum, match) => sum + Number(match[1] ?? "0"),
9834
10397
  0
9835
10398
  );
9836
10399
  };
9837
- var counted = (text8) => {
10400
+ var counted = (text9) => {
9838
10401
  for (const counter of COUNTERS) {
9839
- const total = counter.total.exec(text8)?.[1];
10402
+ const total = counter.total.exec(text9)?.[1];
9840
10403
  if (total === void 0) continue;
9841
- const failed4 = counter.failed?.exec(text8)?.[1];
10404
+ const failed4 = counter.failed?.exec(text9)?.[1];
9842
10405
  return { total: Number(total), ...failed4 === void 0 ? {} : { failed: Number(failed4) } };
9843
10406
  }
9844
- const py = pyTotal(text8);
10407
+ const py = pyTotal(text9);
9845
10408
  return py === void 0 ? {} : { total: py };
9846
10409
  };
9847
10410
  var parseTestSummary = (output) => {
9848
- const text8 = output.replace(ANSI2, "");
10411
+ const text9 = output.replace(ANSI2, "");
9849
10412
  const failures = [
9850
- ...new Set(FAILURE_LINES.flatMap((pattern) => [...text8.matchAll(pattern)].map((match) => (match[1] ?? "").trim())))
10413
+ ...new Set(FAILURE_LINES.flatMap((pattern) => [...text9.matchAll(pattern)].map((match) => (match[1] ?? "").trim())))
9851
10414
  ].filter((name) => name !== "" && !/^failing tests:?$/i.test(name));
9852
- if (ZERO.some((pattern) => pattern.test(text8))) return { total: 0, failures };
9853
- return { ...counted(text8), failures };
10415
+ if (ZERO.some((pattern) => pattern.test(text9))) return { total: 0, failures };
10416
+ return { ...counted(text9), failures };
9854
10417
  };
9855
10418
 
9856
10419
  // src/gate/checks/command.ts
@@ -9861,8 +10424,8 @@ var REWRITERS = /^\s*(rtk)\b/;
9861
10424
  var TRIVIAL = /* @__PURE__ */ new Set(["echo", "true", ":", "printf", "exit", "sleep", "cat", "yes", "pwd", "ls", "date", "env"]);
9862
10425
  var TAIL = 160;
9863
10426
  var MAX_FAILURES2 = 5;
9864
- var tail = (text8) => {
9865
- const lines3 = text8.split(/\r?\n/).filter((line2) => line2.trim() !== "");
10427
+ var tail = (text9) => {
10428
+ const lines3 = text9.split(/\r?\n/).filter((line2) => line2.trim() !== "");
9866
10429
  const last = lines3[lines3.length - 1]?.trim() ?? "";
9867
10430
  return last.length > TAIL ? `${last.slice(0, TAIL)}…` : last;
9868
10431
  };
@@ -9982,10 +10545,10 @@ var gitCheck = (check2, ctx) => {
9982
10545
  };
9983
10546
 
9984
10547
  // src/gate/checks/look.ts
9985
- import { join as join19 } from "node:path";
10548
+ import { join as join21 } from "node:path";
9986
10549
 
9987
10550
  // src/look/config.ts
9988
- import { join as join17 } from "node:path";
10551
+ import { join as join19 } from "node:path";
9989
10552
 
9990
10553
  // src/look/types.ts
9991
10554
  var LOOK_RULES = [
@@ -10020,7 +10583,7 @@ var DEFAULT_SCREENS = [
10020
10583
  "**/*.sass",
10021
10584
  "**/*.less"
10022
10585
  ];
10023
- var text5 = (value) => typeof value === "string" && value !== "" ? value : void 0;
10586
+ var text6 = (value) => typeof value === "string" && value !== "" ? value : void 0;
10024
10587
  var number = (value) => {
10025
10588
  const parsed = typeof value === "string" ? Number(value) : Number.NaN;
10026
10589
  return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
@@ -10037,8 +10600,8 @@ var parseLookConfig = (source) => {
10037
10600
  const top = configMap(tree);
10038
10601
  return {
10039
10602
  port: number(top["port"]),
10040
- start: text5(top["start"]),
10041
- host: text5(top["host"]) ?? "127.0.0.1",
10603
+ start: text6(top["start"]),
10604
+ host: text6(top["host"]) ?? "127.0.0.1",
10042
10605
  routes: configList(tree, "routes").length === 0 ? ["/"] : configList(tree, "routes"),
10043
10606
  mocks: configList(tree, "mocks"),
10044
10607
  screens: screens === void 0 ? DEFAULT_SCREENS : configList(tree, "screens"),
@@ -10047,18 +10610,18 @@ var parseLookConfig = (source) => {
10047
10610
  };
10048
10611
  };
10049
10612
  var loadLookConfig = (runtime, root) => {
10050
- const source = runtime.readText(toAbsolutePath(join17(root, LOOK_CONFIG)));
10613
+ const source = runtime.readText(toAbsolutePath(join19(root, LOOK_CONFIG)));
10051
10614
  return source.ok ? parseLookConfig(source.value) : void 0;
10052
10615
  };
10053
10616
  var withMock = (route, mock) => `${route}${route.includes("?") ? "&" : "?"}mock=${encodeURIComponent(mock)}`;
10054
10617
  var lookRoutes = (config) => config.routes.flatMap((route) => [route, ...config.mocks.map((mock) => withMock(route, mock))]);
10055
10618
 
10056
10619
  // src/look/paths.ts
10057
- import { join as join18, relative as relative5 } from "node:path";
10620
+ import { join as join20, relative as relative5 } from "node:path";
10058
10621
  var LOOK_DIR = "look";
10059
10622
  var SUMMARY_FILE = "summary.json";
10060
- var lookDir = (paths, treeHash2) => toAbsolutePath(join18(paths.dir, LOOK_DIR, treeHash2));
10061
- var summaryPath = (paths, treeHash2) => toAbsolutePath(join18(lookDir(paths, treeHash2), SUMMARY_FILE));
10623
+ var lookDir = (paths, treeHash2) => toAbsolutePath(join20(paths.dir, LOOK_DIR, treeHash2));
10624
+ var summaryPath = (paths, treeHash2) => toAbsolutePath(join20(lookDir(paths, treeHash2), SUMMARY_FILE));
10062
10625
  var projectRelative = (paths, absolute) => toPosixPath(relative5(paths.root, absolute));
10063
10626
 
10064
10627
  // src/gate/checks/look.ts
@@ -10076,8 +10639,8 @@ var findingNote = (value) => {
10076
10639
  ];
10077
10640
  };
10078
10641
  var readSummary = (ctx) => {
10079
- const text8 = ctx.runtime.readText(summaryPath(ctx.paths, ctx.treeHash));
10080
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
10642
+ const text9 = ctx.runtime.readText(summaryPath(ctx.paths, ctx.treeHash));
10643
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
10081
10644
  if (!parsed?.ok) return void 0;
10082
10645
  const { at, captures, findings, ignoredAllows } = parsed.value;
10083
10646
  if (typeof at !== "string" || !Array.isArray(captures) || !Array.isArray(findings)) return void 0;
@@ -10093,7 +10656,7 @@ var readSummary = (ctx) => {
10093
10656
  var lastScreenEdit = (ctx) => {
10094
10657
  const screens = loadLookConfig(ctx.runtime, ctx.paths.root)?.screens ?? DEFAULT_SCREENS;
10095
10658
  return ctx.changes.filter((change) => change.status !== "D" && matchesAny(change.path, screens)).reduce((latest, change) => {
10096
- const stat = ctx.runtime.stat(toAbsolutePath(join19(ctx.paths.root, change.path)));
10659
+ const stat = ctx.runtime.stat(toAbsolutePath(join21(ctx.paths.root, change.path)));
10097
10660
  return Math.max(latest, stat?.mtimeMs ?? 0);
10098
10661
  }, 0);
10099
10662
  };
@@ -10140,14 +10703,14 @@ var manualCheck = (check2, ctx) => {
10140
10703
  };
10141
10704
 
10142
10705
  // src/gate/checks/placeholders.ts
10143
- import { join as join20 } from "node:path";
10706
+ import { join as join22 } from "node:path";
10144
10707
  var MAX_LISTED3 = 10;
10145
10708
  var SKIP_FILES = /\.(md|mdx|txt|rst|lock|snap|svg|png|jpg|gif|json)$|(^|\/)(CHANGELOG|LICENSE)/i;
10146
10709
  var PLACEHOLDER = /\b(TODO|FIXME|XXX)\b|not implemented|NotImplementedError|unimplemented!|todo!\(|lorem ipsum/i;
10147
10710
  var findPlaceholders = (lines3) => lines3.filter((line2) => !isTestFile(line2.path) && !SKIP_FILES.test(line2.path) && PLACEHOLDER.test(line2.text));
10148
10711
  var untrackedAdded = (ctx) => ctx.changes.filter((change) => change.status === "A" && !SKIP_FILES.test(change.path)).flatMap((change) => {
10149
- const text8 = ctx.runtime.readText(toAbsolutePath(join20(ctx.paths.root, change.path)));
10150
- return text8.ok ? text8.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
10712
+ const text9 = ctx.runtime.readText(toAbsolutePath(join22(ctx.paths.root, change.path)));
10713
+ return text9.ok ? text9.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
10151
10714
  });
10152
10715
  var placeholdersCheck = (_check, ctx) => {
10153
10716
  const tracked = addedLines(ctx.git, ctx.base);
@@ -10206,9 +10769,9 @@ var prOutcome = (check2, ctx) => {
10206
10769
 
10207
10770
  // src/prose/preset.ts
10208
10771
  var PRESET_LINE = /^preset:\s*([A-Za-z0-9_-]+)\s*$/m;
10209
- var presetFromConfig = (text8) => parsePreset(PRESET_LINE.exec(text8)?.[1]) ?? "neutral";
10772
+ var presetFromConfig = (text9) => parsePreset(PRESET_LINE.exec(text9)?.[1]) ?? "neutral";
10210
10773
  var projectPreset = (runtime, root) => {
10211
- const config = runtime.readText(toAbsolutePath(CONFIG_FILE, root));
10774
+ const config = runtime.readText(toAbsolutePath(CONFIG_FILE2, root));
10212
10775
  return config.ok ? presetFromConfig(config.value) : "neutral";
10213
10776
  };
10214
10777
 
@@ -10383,16 +10946,16 @@ var ANY_MARKER = /<!--\s*finitemonkey:prose-allow(?:-file)?\s*-->/g;
10383
10946
  var INLINE_CODE = /`[^`]*`/g;
10384
10947
  var FENCE2 = /^\s*(```|~~~)/;
10385
10948
  var outsideCode = (line2) => line2.replace(INLINE_CODE, "");
10386
- var unfencedLines = (text8) => {
10949
+ var unfencedLines = (text9) => {
10387
10950
  let fenced = false;
10388
- return text8.split("\n").filter((line2) => {
10951
+ return text9.split("\n").filter((line2) => {
10389
10952
  if (FENCE2.test(line2)) fenced = !fenced;
10390
10953
  return !fenced && !FENCE2.test(line2);
10391
10954
  });
10392
10955
  };
10393
- var allowsWholeFile = (text8) => unfencedLines(text8).some((line2) => FILE_MARKER.test(outsideCode(line2)));
10956
+ var allowsWholeFile = (text9) => unfencedLines(text9).some((line2) => FILE_MARKER.test(outsideCode(line2)));
10394
10957
  var allowsLine = (line2) => LINE_MARKER.test(outsideCode(line2));
10395
- var markerCount = (text8) => text8.match(ANY_MARKER)?.length ?? 0;
10958
+ var markerCount = (text9) => text9.match(ANY_MARKER)?.length ?? 0;
10396
10959
  var isMarkerOnlyLine = (line2) => allowsLine(line2) && outsideCode(line2).replace(LINE_MARKER, "").trim() === "";
10397
10960
 
10398
10961
  // src/prose/extract.ts
@@ -10412,7 +10975,7 @@ var MASKS = [
10412
10975
  [/<[^>]+>/g, ""],
10413
10976
  [/\*\*|__/g, ""]
10414
10977
  ];
10415
- var maskInline = (line2) => MASKS.reduce((text8, [pattern, replacement]) => text8.replace(pattern, replacement), line2);
10978
+ var maskInline = (line2) => MASKS.reduce((text9, [pattern, replacement]) => text9.replace(pattern, replacement), line2);
10416
10979
  var BlockReader = class {
10417
10980
  blocks = [];
10418
10981
  current;
@@ -10503,8 +11066,8 @@ var splitBlock = (block4) => {
10503
11066
  return joined.split(SENTENCE_END).flatMap((sentence) => {
10504
11067
  const at = joined.indexOf(sentence, offset);
10505
11068
  offset = at + sentence.length;
10506
- const text8 = sentence.trim();
10507
- return text8 === "" ? [] : [{ text: text8, line: lineAt(at) }];
11069
+ const text9 = sentence.trim();
11070
+ return text9 === "" ? [] : [{ text: text9, line: lineAt(at) }];
10508
11071
  });
10509
11072
  };
10510
11073
  var unitsOf = (blocks) => {
@@ -10525,9 +11088,9 @@ var unitsOf = (blocks) => {
10525
11088
  }));
10526
11089
  });
10527
11090
  };
10528
- var extractProse = (text8) => {
10529
- if (allowsWholeFile(text8)) return { units: [], allowed: true };
10530
- const lines3 = text8.split("\n").map(withoutCarriageReturn);
11091
+ var extractProse = (text9) => {
11092
+ if (allowsWholeFile(text9)) return { units: [], allowed: true };
11093
+ const lines3 = text9.split("\n").map(withoutCarriageReturn);
10531
11094
  return { units: unitsOf(readBlocks(lines3)), allowed: false };
10532
11095
  };
10533
11096
 
@@ -10652,7 +11215,7 @@ var CONNECTIVES = new Set(
10652
11215
  " "
10653
11216
  )
10654
11217
  );
10655
- var wordsOf = (text8) => text8.split(/\s+/).filter((word2) => WORD.test(word2));
11218
+ var wordsOf = (text9) => text9.split(/\s+/).filter((word2) => WORD.test(word2));
10656
11219
  var hit = (rule, units, index, weight) => {
10657
11220
  const unit = units[index];
10658
11221
  return { rule, unit: index, line: unit?.line ?? 0, weight, excerpt: unit?.text ?? "" };
@@ -10780,8 +11343,8 @@ var mergeSpans = (hits, windows) => {
10780
11343
  return ranges.map(([from, to]) => spanOf2(hits, from, to)).filter((span) => span.hits.length > 0);
10781
11344
  };
10782
11345
  var byPosition = (left, right) => left.unit - right.unit || left.line - right.line;
10783
- var scoreProse = (text8, options = {}) => {
10784
- const { units, allowed: allowed2 } = extractProse(text8);
11346
+ var scoreProse = (text9, options = {}) => {
11347
+ const { units, allowed: allowed2 } = extractProse(text9);
10785
11348
  const size = options.window ?? DENSITY_WINDOW;
10786
11349
  const threshold2 = options.threshold ?? DENSITY_THRESHOLD;
10787
11350
  const catalog = options.context === "record" ? [...phraseRules(), ...recordRules()] : phraseRules();
@@ -10813,9 +11376,9 @@ var changedMarkdown = (runtime, root) => {
10813
11376
  var scoreFiles = (runtime, root, files, forced) => {
10814
11377
  const reports = [];
10815
11378
  for (const file of files) {
10816
- const text8 = runtime.readText(toAbsolutePath(file, root));
10817
- if (!text8.ok) return reject(`${file}: ${text8.error}`);
10818
- reports.push({ file: toPosixPath(file), report: scoreProse(text8.value, { context: forced ?? contextOf(file) }) });
11379
+ const text9 = runtime.readText(toAbsolutePath(file, root));
11380
+ if (!text9.ok) return reject(`${file}: ${text9.error}`);
11381
+ reports.push({ file: toPosixPath(file), report: scoreProse(text9.value, { context: forced ?? contextOf(file) }) });
10819
11382
  }
10820
11383
  return accept(reports);
10821
11384
  };
@@ -10914,8 +11477,8 @@ var parseAppCheck = (stdout) => {
10914
11477
  var PROBE_MARGIN_MS = 15e3;
10915
11478
  var MIN_REMAINING_MS2 = 5e3;
10916
11479
  var TAIL2 = 160;
10917
- var lastLine = (text8) => {
10918
- const rows = text8.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
11480
+ var lastLine = (text9) => {
11481
+ const rows = text9.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
10919
11482
  const line2 = (rows.findLast((row) => /error/i.test(row)) ?? rows.at(-1))?.trim() ?? "";
10920
11483
  return line2.length > TAIL2 ? `${line2.slice(0, TAIL2)}…` : line2;
10921
11484
  };
@@ -10993,14 +11556,14 @@ ${run4.stderr}`)) })]);
10993
11556
  };
10994
11557
 
10995
11558
  // src/secrets/baseline.ts
10996
- import { join as join21 } from "node:path";
11559
+ import { join as join23 } from "node:path";
10997
11560
  var BASELINE_FILE2 = ".finitemonkey/findings/baseline.json";
10998
11561
  var BASELINE_SCHEMA = "finitemonkey/secrets-baseline@1";
10999
11562
  var BASELINE_STATES = ["aceito", "falso-positivo"];
11000
11563
  var EXCEPTION_MAX_DAYS = 90;
11001
11564
  var EMPTY_BASELINE = { schema: BASELINE_SCHEMA, entries: [] };
11002
- var baselinePath = (root) => toAbsolutePath(join21(root, BASELINE_FILE2));
11003
- var text6 = (record3, key) => {
11565
+ var baselinePath = (root) => toAbsolutePath(join23(root, BASELINE_FILE2));
11566
+ var text7 = (record3, key) => {
11004
11567
  const value = record3[key];
11005
11568
  return typeof value === "string" && value !== "" ? value : void 0;
11006
11569
  };
@@ -11008,7 +11571,7 @@ var stateOf = (value) => BASELINE_STATES.find((state) => state === value);
11008
11571
  var parseEntry2 = (value) => {
11009
11572
  if (!isRecord(value)) return void 0;
11010
11573
  const fields = ["id", "rule", "file", "fingerprint", "owner", "reason", "acceptedAt", "expiresAt"];
11011
- const [id, rule, file, fingerprint, owner, reason, acceptedAt, expiresAt] = fields.map((key) => text6(value, key));
11574
+ const [id, rule, file, fingerprint, owner, reason, acceptedAt, expiresAt] = fields.map((key) => text7(value, key));
11012
11575
  const state = stateOf(value["state"]);
11013
11576
  const line2 = value["line"];
11014
11577
  if (id === void 0 || rule === void 0 || file === void 0 || fingerprint === void 0) return void 0;
@@ -11059,14 +11622,14 @@ var withoutFixed = (baseline, fixed) => {
11059
11622
  var acceptedFingerprints = (baseline, now) => new Set(baseline.entries.filter((entry2) => entry2.expiresAt >= now).map((entry2) => entry2.fingerprint));
11060
11623
 
11061
11624
  // src/secrets/incidents.ts
11062
- import { join as join22 } from "node:path";
11625
+ import { join as join24 } from "node:path";
11063
11626
  var INCIDENTS_DIR = ".finitemonkey/incidents";
11064
11627
  var INCIDENT_STATES = ["aberto", "exposicao-local", "rotacionado", "fechado"];
11065
11628
  var INCIDENT_SOURCES = ["tool-output", "prompt", "history", "scan"];
11066
11629
  var ID = /^inc-[0-9a-f]{12}$/;
11067
11630
  var incidentId = (rule, fingerprint) => `inc-${sha256Hex(`${rule}\0${fingerprint}`).slice(0, 12)}`;
11068
- var incidentsDir = (root) => toAbsolutePath(join22(root, INCIDENTS_DIR));
11069
- var incidentFile = (root, id) => toAbsolutePath(join22(incidentsDir(root), `${id}.json`));
11631
+ var incidentsDir = (root) => toAbsolutePath(join24(root, INCIDENTS_DIR));
11632
+ var incidentFile = (root, id) => toAbsolutePath(join24(incidentsDir(root), `${id}.json`));
11070
11633
  var pick3 = (options, value) => options.find((option) => option === value);
11071
11634
  var str2 = (record3, key) => {
11072
11635
  const value = record3[key];
@@ -11121,7 +11684,7 @@ var listIncidents = (runtime, root) => {
11121
11684
  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));
11122
11685
  };
11123
11686
  var saveIncident = (runtime, root, incident) => {
11124
- const ignore = runtime.writeText(toAbsolutePath(join22(incidentsDir(root), ".gitignore")), "*\n");
11687
+ const ignore = runtime.writeText(toAbsolutePath(join24(incidentsDir(root), ".gitignore")), "*\n");
11125
11688
  if (!ignore.ok) return ignore;
11126
11689
  return runtime.writeText(incidentFile(root, incident.id), `${JSON.stringify(incident, null, 2)}
11127
11690
  `);
@@ -11168,7 +11731,7 @@ var moveIncident = (incident, to, now, by) => ({
11168
11731
  var isRotated = (incident) => incident.state === "rotacionado" || incident.state === "fechado";
11169
11732
 
11170
11733
  // src/secrets/scan.ts
11171
- import { join as join23, relative as relative6 } from "node:path";
11734
+ import { join as join25, relative as relative6 } from "node:path";
11172
11735
 
11173
11736
  // src/secrets/located.ts
11174
11737
  var findingId = (ruleId, file, fingerprint) => `fm-${sha256Hex(`${ruleId}\0${file}\0${fingerprint}`).slice(0, 16)}`;
@@ -11258,7 +11821,7 @@ var diffScanner = () => {
11258
11821
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
11259
11822
  var SKIPPED = /(?:^|\/)(?:\.git|node_modules|\.finitemonkey)\//;
11260
11823
  var DIFF_FLAGS = ["-U0", "--no-color", "--no-ext-diff", "--no-renames"];
11261
- var binary = (text8) => text8.slice(0, 8e3).includes("\0");
11824
+ var binary = (text9) => text9.slice(0, 8e3).includes("\0");
11262
11825
  var listed2 = (runtime, root, target2) => {
11263
11826
  const git2 = runtime.git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", target2], root);
11264
11827
  if (git2.ok)
@@ -11268,12 +11831,12 @@ var listed2 = (runtime, root, target2) => {
11268
11831
  const base = toAbsolutePath(target2, root);
11269
11832
  const walked = runtime.walkFiles(base);
11270
11833
  if (!walked.ok) return walked;
11271
- return accept(walked.value.map((name) => toPosixPath(relative6(root, join23(base, toPosixPath(name))))));
11834
+ return accept(walked.value.map((name) => toPosixPath(relative6(root, join25(base, toPosixPath(name))))));
11272
11835
  };
11273
11836
  var scanFile = (runtime, root, file) => {
11274
- const text8 = runtime.readText(toAbsolutePath(file, root));
11275
- if (!text8.ok || text8.value.length > MAX_FILE_BYTES || binary(text8.value)) return [];
11276
- return scanText(text8.value, { path: file }).map((finding4) => locate(finding4, file, finding4.line));
11837
+ const text9 = runtime.readText(toAbsolutePath(file, root));
11838
+ if (!text9.ok || text9.value.length > MAX_FILE_BYTES || binary(text9.value)) return [];
11839
+ return scanText(text9.value, { path: file }).map((finding4) => locate(finding4, file, finding4.line));
11277
11840
  };
11278
11841
  var scanPath = (runtime, root, target2) => {
11279
11842
  const files = listed2(runtime, root, target2);
@@ -11338,7 +11901,7 @@ var secretsCheck = (_check, ctx) => {
11338
11901
  };
11339
11902
 
11340
11903
  // src/gate/checks/tests.ts
11341
- import { join as join24 } from "node:path";
11904
+ import { join as join26 } from "node:path";
11342
11905
 
11343
11906
  // src/guards/tests/tree.ts
11344
11907
  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 }] : [];
@@ -11370,8 +11933,8 @@ var treeViolations = (ctx) => {
11370
11933
  changes: relevant,
11371
11934
  before: (path) => fileAt(ctx.git, ctx.base, path),
11372
11935
  after: (path) => {
11373
- const text8 = ctx.runtime.readText(toAbsolutePath(join24(ctx.paths.root, path)));
11374
- return text8.ok ? text8.value : void 0;
11936
+ const text9 = ctx.runtime.readText(toAbsolutePath(join26(ctx.paths.root, path)));
11937
+ return text9.ok ? text9.value : void 0;
11375
11938
  },
11376
11939
  acceptedConfigs: ctx.trust.configs,
11377
11940
  scopePaths: ctx.contract.scope.paths,
@@ -11391,7 +11954,7 @@ var testsCheck = (check2, ctx) => {
11391
11954
  };
11392
11955
 
11393
11956
  // src/git/tree.ts
11394
- import { join as join25 } from "node:path";
11957
+ import { join as join27 } from "node:path";
11395
11958
 
11396
11959
  // src/secrets/guards/paths.ts
11397
11960
  var SAFE_ENV = /^\.env\.(?:example|sample|template|dist|defaults|schema)$/i;
@@ -11430,7 +11993,7 @@ var dropSensitive = (runtime, repo, env, options) => {
11430
11993
  return removed.ok ? accept(void 0) : removed;
11431
11994
  };
11432
11995
  var withTempIndex = (runtime, repo, options, work) => {
11433
- const path = toAbsolutePath(join25(repo.gitDir, `finitemonkey-${options.stamp}.index`));
11996
+ const path = toAbsolutePath(join27(repo.gitDir, `finitemonkey-${options.stamp}.index`));
11434
11997
  const env = { GIT_INDEX_FILE: path, GIT_OPTIONAL_LOCKS: "0" };
11435
11998
  try {
11436
11999
  return work(env);
@@ -11535,10 +12098,10 @@ var bodyLine = (draft, line2) => {
11535
12098
  else if (line2.startsWith("+")) draft.current.added.push(line2.slice(1));
11536
12099
  };
11537
12100
  var DIFF_GIT = /^diff --git a\/(.+) b\/(.+)$/;
11538
- var parseUnifiedDiff = (text8) => {
12101
+ var parseUnifiedDiff = (text9) => {
11539
12102
  const out = [];
11540
12103
  let draft;
11541
- for (const raw of text8.split("\n")) {
12104
+ for (const raw of text9.split("\n")) {
11542
12105
  const line2 = withoutCarriageReturn(raw);
11543
12106
  const start = DIFF_GIT.exec(line2);
11544
12107
  if (start !== null) {
@@ -11637,59 +12200,15 @@ var mergeCommitsSince = (runtime, repo, since) => {
11637
12200
  };
11638
12201
  var mergeInProgress = (runtime, repo) => revParse(runtime, repo, "MERGE_HEAD") !== void 0;
11639
12202
 
11640
- // src/git/facts.ts
11641
- var memo2 = (compute) => {
11642
- let cached3;
11643
- return () => {
11644
- cached3 ??= { value: compute() };
11645
- return cached3.value;
11646
- };
11647
- };
11648
- var memoBy = (compute) => {
11649
- const cache = /* @__PURE__ */ new Map();
11650
- return (key) => {
11651
- const hit3 = cache.get(key);
11652
- if (hit3 !== void 0) return hit3.value;
11653
- const value = compute(key);
11654
- cache.set(key, { value });
11655
- return value;
11656
- };
11657
- };
11658
- var STATE_PREFIX = ".finitemonkey/";
11659
- var PORCELAIN_FIELDS = { ordinary: 8, renamed: 9, unmerged: 10 };
11660
- var entryOf2 = (kind, xy, path) => ({
11661
- path,
11662
- kind,
11663
- staged: kind !== "untracked" && !xy.startsWith("."),
11664
- unstaged: kind === "untracked" || xy.charAt(1) !== "."
11665
- });
11666
- var afterFields = (record3, count4) => record3.split(" ").slice(count4).join(" ");
11667
- var parsePorcelainV2 = (stdout) => {
11668
- const records = stdout.split("\0");
11669
- const out = [];
11670
- for (let index = 0; index < records.length; index += 1) {
11671
- const record3 = records[index] ?? "";
11672
- const xy = record3.split(" ")[1] ?? "..";
11673
- if (record3.startsWith("1 ")) out.push(entryOf2("changed", xy, afterFields(record3, PORCELAIN_FIELDS.ordinary)));
11674
- else if (record3.startsWith("2 ")) {
11675
- out.push(entryOf2("changed", xy, afterFields(record3, PORCELAIN_FIELDS.renamed)));
11676
- index += 1;
11677
- } else if (record3.startsWith("u ")) {
11678
- out.push(entryOf2("unmerged", xy, afterFields(record3, PORCELAIN_FIELDS.unmerged)));
11679
- } else if (record3.startsWith("? ")) out.push(entryOf2("untracked", "??", record3.slice(2)));
11680
- }
11681
- return out.filter((entry2) => entry2.path !== "" && !entry2.path.startsWith(STATE_PREFIX));
11682
- };
11683
-
11684
12203
  // src/git/merge/dropped.ts
11685
- import { join as join26 } from "node:path";
12204
+ import { join as join28 } from "node:path";
11686
12205
 
11687
12206
  // src/git/merge/lines.ts
11688
12207
  var normalizeLine = (line2) => line2.replace(/\r$/, "").trim().replace(/\s*,$/, "");
11689
12208
  var WEAK = /^[\p{P}\p{S}\s]+$/u;
11690
12209
  var isWeak = (line2) => WEAK.test(line2);
11691
12210
  var meaningful = (lines3) => lines3.map(normalizeLine).filter((line2) => line2 !== "");
11692
- var contentLines = (text8) => text8 === void 0 ? [] : meaningful(text8.split("\n"));
12211
+ var contentLines = (text9) => text9 === void 0 ? [] : meaningful(text9.split("\n"));
11693
12212
  var countLines = (lines3) => {
11694
12213
  const counts = /* @__PURE__ */ new Map();
11695
12214
  for (const line2 of lines3) counts.set(line2, (counts.get(line2) ?? 0) + 1);
@@ -11738,16 +12257,16 @@ var itemOf = (value) => {
11738
12257
  approvedBy: optionalString(value, "approved_by")
11739
12258
  };
11740
12259
  };
11741
- var invalidFinding = (file, text8) => ({
12260
+ var invalidFinding = (file, text9) => ({
11742
12261
  kind: "invalid-dropped",
11743
12262
  file,
11744
12263
  side: void 0,
11745
12264
  line: void 0,
11746
- text: text8,
12265
+ text: text9,
11747
12266
  weak: false
11748
12267
  });
11749
- var parseDropped = (text8) => {
11750
- const parsed = parseJson(text8);
12268
+ var parseDropped = (text9) => {
12269
+ const parsed = parseJson(text9);
11751
12270
  if (!parsed.ok) return parsed;
11752
12271
  const list4 = isRecord(parsed.value) ? parsed.value["dropped"] : parsed.value;
11753
12272
  if (!Array.isArray(list4)) return reject("expected a list of dropped items");
@@ -11762,17 +12281,17 @@ var parseDropped = (text8) => {
11762
12281
  return accept({ items, invalid });
11763
12282
  };
11764
12283
  var loadDropped = (runtime, root, file) => {
11765
- const path = toAbsolutePath(file ?? join26(root, DROPPED_FILE), root);
11766
- const text8 = runtime.readText(path);
11767
- if (!text8.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text8.error}`);
11768
- return parseDropped(text8.value);
12284
+ const path = toAbsolutePath(file ?? join28(root, DROPPED_FILE), root);
12285
+ const text9 = runtime.readText(path);
12286
+ if (!text9.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text9.error}`);
12287
+ return parseDropped(text9.value);
11769
12288
  };
11770
12289
  var isDropped = (finding4, items) => items.some(
11771
12290
  (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)
11772
12291
  );
11773
12292
 
11774
12293
  // src/git/merge/generated.ts
11775
- import { join as join27 } from "node:path";
12294
+ import { join as join29 } from "node:path";
11776
12295
  var LOCKFILES3 = /* @__PURE__ */ new Set([
11777
12296
  "package-lock.json",
11778
12297
  "npm-shrinkwrap.json",
@@ -11790,11 +12309,11 @@ var LOCKFILES3 = /* @__PURE__ */ new Set([
11790
12309
  ]);
11791
12310
  var GENERATOR_TIMEOUT_MS = 3e5;
11792
12311
  var DOUBLE_STAR = "";
11793
- var globRegex = (pattern) => new RegExp(
12312
+ var globRegex2 = (pattern) => new RegExp(
11794
12313
  `^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").split("**").join(DOUBLE_STAR).replace(/\*/g, "[^/]*").split(DOUBLE_STAR).join(".*")}$`
11795
12314
  );
11796
12315
  var generatorFor = (path, generators) => Object.entries(generators).find(
11797
- ([pattern]) => globRegex(pattern).test(path) || globRegex(pattern).test(path.split("/").pop() ?? "")
12316
+ ([pattern]) => globRegex2(pattern).test(path) || globRegex2(pattern).test(path.split("/").pop() ?? "")
11798
12317
  )?.[1];
11799
12318
  var generatedPaths = (runtime, repo, paths, generators) => {
11800
12319
  const out = new Set(
@@ -11835,7 +12354,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
11835
12354
  if (declared.length === 0) return accept([]);
11836
12355
  const commit2 = resultCommit(runtime, repo, inputs);
11837
12356
  if (!commit2.ok) return commit2;
11838
- const dir = join27(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
12357
+ const dir = join29(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
11839
12358
  if (!runtime.git(["worktree", "add", "--detach", dir, commit2.value], repo.root).ok)
11840
12359
  return reject("could not check out the merge result");
11841
12360
  try {
@@ -11845,7 +12364,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
11845
12364
  }
11846
12365
  return accept(
11847
12366
  declared.flatMap(({ path }) => {
11848
- const fresh3 = runtime.readText(toAbsolutePath(join27(dir, path)));
12367
+ const fresh3 = runtime.readText(toAbsolutePath(join29(dir, path)));
11849
12368
  const merged2 = blobText(runtime, repo, commit2.value, path);
11850
12369
  if (fresh3.ok && fresh3.value === merged2) return [];
11851
12370
  return [{ kind: "generated-differs", file: path, side: void 0, line: void 0, text: path, weak: false }];
@@ -11888,13 +12407,13 @@ var removalOf = (ours, theirs, paired) => {
11888
12407
  var missingAdditions = (hunks, file, elsewhere) => {
11889
12408
  const present2 = new Set(file.result);
11890
12409
  return hunks.flatMap(({ side, hunk, added }) => {
11891
- const text8 = added.find((line2) => !isWeak(line2) && !present2.has(line2));
11892
- if (text8 === void 0 || containsSequence(file.result, added) || elsewhere(added)) return [];
12410
+ const text9 = added.find((line2) => !isWeak(line2) && !present2.has(line2));
12411
+ if (text9 === void 0 || containsSequence(file.result, added) || elsewhere(added)) return [];
11893
12412
  const offset = Math.max(
11894
12413
  0,
11895
- hunk.added.findIndex((line2) => normalizeLine(line2) === text8)
12414
+ hunk.added.findIndex((line2) => normalizeLine(line2) === text9)
11896
12415
  );
11897
- return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text: text8, weak: false }];
12416
+ return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text: text9, weak: false }];
11898
12417
  });
11899
12418
  };
11900
12419
  var expectations = (file, hunks, removal) => {
@@ -11967,8 +12486,8 @@ var symbolsOf = (line2) => {
11967
12486
  ...title === void 0 ? [] : [{ name: title, test: true }]
11968
12487
  ];
11969
12488
  };
11970
- var escapeRegex = (text8) => text8.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11971
- var containsSymbol = (text8, symbol) => symbol.test ? text8.includes(symbol.name) : new RegExp(`(?<![\\w$])${escapeRegex(symbol.name)}(?![\\w$])`).test(text8);
12489
+ var escapeRegex = (text9) => text9.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12490
+ var containsSymbol = (text9, symbol) => symbol.test ? text9.includes(symbol.name) : new RegExp(`(?<![\\w$])${escapeRegex(symbol.name)}(?![\\w$])`).test(text9);
11972
12491
  var missingSymbols = (file, side, hunks, present2) => {
11973
12492
  const seen = /* @__PURE__ */ new Set();
11974
12493
  return hunks.flatMap(
@@ -11987,12 +12506,12 @@ var created2 = (diff) => diff?.status === "added" || diff?.status === "copied";
11987
12506
  var resultPathOf = (context, pair) => [pair.ours?.newPath, pair.theirs?.newPath, pair.key].find(
11988
12507
  (path) => path !== void 0 && context.resultText(path) !== void 0
11989
12508
  );
11990
- var finding2 = (kind, file, side, text8) => ({
12509
+ var finding2 = (kind, file, side, text9) => ({
11991
12510
  kind,
11992
12511
  file,
11993
12512
  side,
11994
12513
  line: void 0,
11995
- text: text8,
12514
+ text: text9,
11996
12515
  weak: false
11997
12516
  });
11998
12517
  var binaryFindings = (context, pair, path) => {
@@ -12011,8 +12530,8 @@ var binaryFindings = (context, pair, path) => {
12011
12530
  return result === expected ? [] : [finding2("binary", file, side, file)];
12012
12531
  };
12013
12532
  var elsewhereOf = (context) => (lines3) => context.resultFiles().some((path) => containsSequence(contentLines(context.resultText(path)), lines3));
12014
- var symbolFindings = (context, pair, path, text8) => {
12015
- const present2 = (symbol) => text8 !== void 0 && containsSymbol(text8, symbol) || context.resultFiles().some((other) => containsSymbol(context.resultText(other) ?? "", symbol));
12533
+ var symbolFindings = (context, pair, path, text9) => {
12534
+ const present2 = (symbol) => text9 !== void 0 && containsSymbol(text9, symbol) || context.resultFiles().some((other) => containsSymbol(context.resultText(other) ?? "", symbol));
12016
12535
  return [
12017
12536
  ...missingSymbols(path, "ours", pair.ours?.hunks ?? [], present2),
12018
12537
  ...missingSymbols(path, "theirs", pair.theirs?.hunks ?? [], present2)
@@ -12028,19 +12547,19 @@ var verifyFile = (context, pair) => {
12028
12547
  const copied = [pair.ours, pair.theirs].find((diff) => diff?.status === "copied");
12029
12548
  const baseSource = copied?.oldPath ?? (created2(pair.ours) || created2(pair.theirs) ? void 0 : pair.key);
12030
12549
  const baseText = baseSource === void 0 ? void 0 : blobText(context.runtime, context.repo, context.inputs.base, baseSource);
12031
- const text8 = path === void 0 ? void 0 : context.resultText(path);
12550
+ const text9 = path === void 0 ? void 0 : context.resultText(path);
12032
12551
  const file = path ?? pair.key;
12033
12552
  const lines3 = checkFile(
12034
12553
  {
12035
12554
  path: file,
12036
12555
  base: contentLines(baseText),
12037
- result: contentLines(text8),
12556
+ result: contentLines(text9),
12038
12557
  ours: pair.ours?.hunks ?? [],
12039
12558
  theirs: pair.theirs?.hunks ?? []
12040
12559
  },
12041
12560
  elsewhereOf(context)
12042
12561
  );
12043
- return [...lines3, ...symbolFindings(context, pair, file, text8)];
12562
+ return [...lines3, ...symbolFindings(context, pair, file, text9)];
12044
12563
  };
12045
12564
 
12046
12565
  // src/git/merge/verify.ts
@@ -12069,13 +12588,13 @@ var pairsOf = (ours, theirs) => {
12069
12588
  }));
12070
12589
  };
12071
12590
  var contextOf2 = (runtime, repo, inputs, pairs2) => {
12072
- const resultText = memoBy((path) => blobText(runtime, repo, inputs.result, path));
12591
+ const resultText2 = memoBy((path) => blobText(runtime, repo, inputs.result, path));
12073
12592
  const context = {
12074
12593
  runtime,
12075
12594
  repo,
12076
12595
  inputs,
12077
- resultText,
12078
- resultFiles: memo2(
12596
+ resultText: resultText2,
12597
+ resultFiles: memo(
12079
12598
  () => pairs2.flatMap((pair) => {
12080
12599
  const path = resultPathOf(context, pair);
12081
12600
  return path === void 0 ? [] : [path];
@@ -12131,19 +12650,19 @@ var verifyMerge = (runtime, repo, options = {}) => {
12131
12650
  };
12132
12651
 
12133
12652
  // src/git/ownership.ts
12134
- import { join as join28 } from "node:path";
12653
+ import { join as join30 } from "node:path";
12135
12654
  var OWNERSHIP_DIR = ".finitemonkey/git";
12136
12655
  var ownerKey = (raw) => {
12137
12656
  const cleaned = (raw ?? "").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80);
12138
12657
  return cleaned === "" ? "unknown" : cleaned;
12139
12658
  };
12140
- var fileOf = (repo, kind, owner) => toAbsolutePath(join28(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
12659
+ var fileOf = (repo, kind, owner) => toAbsolutePath(join30(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
12141
12660
  var readDirty = (runtime, root) => {
12142
12661
  const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
12143
12662
  return status.ok ? parsePorcelainV2(status.stdout) : void 0;
12144
12663
  };
12145
12664
  var hashDirty = (runtime, repo, entries) => {
12146
- const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join28(repo.root, entry2.path)))?.kind === "file");
12665
+ const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join30(repo.root, entry2.path)))?.kind === "file");
12147
12666
  const hashed = present2.length === 0 ? void 0 : runtime.gitWith(["hash-object", "--stdin-paths"], repo.root, {
12148
12667
  input: `${present2.map((entry2) => entry2.path).join("\n")}
12149
12668
  `
@@ -12160,8 +12679,8 @@ var hashesOf = (value) => isRecord(value) ? Object.fromEntries(
12160
12679
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
12161
12680
  ) : {};
12162
12681
  var readObject = (runtime, path) => {
12163
- const text8 = runtime.readText(path);
12164
- return text8.ok ? parseJsonObject(text8.value) : text8;
12682
+ const text9 = runtime.readText(path);
12683
+ return text9.ok ? parseJsonObject(text9.value) : text9;
12165
12684
  };
12166
12685
  var readBaseline2 = (runtime, repo, owner) => {
12167
12686
  const parsed = readObject(runtime, fileOf(repo, "baseline", owner));
@@ -12227,7 +12746,7 @@ var foreignFiles = (runtime, repo, owner, targets2) => {
12227
12746
  };
12228
12747
 
12229
12748
  // src/git/gate-status.ts
12230
- var lines = (text8) => text8.split("\n").filter((line2) => line2.trim() !== "");
12749
+ var lines = (text9) => text9.split("\n").filter((line2) => line2.trim() !== "");
12231
12750
  var unpushedSince = (runtime, repo, since) => {
12232
12751
  if (lines(runtime.git(["remote"], repo.root).stdout).length === 0) return [];
12233
12752
  const result = runtime.git(["rev-list", `${since}..HEAD`, "--not", "--remotes"], repo.root);
@@ -12255,97 +12774,17 @@ var findingLabel = (finding4) => {
12255
12774
  return `${where} ${finding4.text === "" ? finding4.kind : `"${finding4.text}"`}`;
12256
12775
  };
12257
12776
 
12258
- // src/git/repo.ts
12259
- var DEFAULT_PROTECTED = ["main", "master", "release/*", "prod", "production", "rc-*"];
12260
- var openRepo = (runtime, dir) => {
12261
- const result = runtime.git(["rev-parse", "--show-toplevel", "--absolute-git-dir", "--show-prefix"], dir);
12262
- if (!result.ok) return void 0;
12263
- const [root, gitDir, prefix] = result.stdout.split("\n");
12264
- if (root === void 0 || root === "" || gitDir === void 0 || gitDir === "") return void 0;
12265
- return {
12266
- root: toAbsolutePath(root),
12267
- gitDir: toAbsolutePath(gitDir),
12268
- prefix: (prefix ?? "").replace(/\/$/, "")
12269
- };
12270
- };
12271
- var globRegex2 = (pattern) => new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`);
12272
- var matchesBranch = (branch, patterns) => patterns.some((pattern) => globRegex2(pattern).test(branch));
12273
- var line = (runtime, root, args2) => {
12274
- const result = runtime.git(args2, root);
12275
- const value = result.stdout.trim();
12276
- return result.ok && value !== "" ? value : void 0;
12277
- };
12278
- var refExists = (runtime, root, ref) => runtime.git(["show-ref", "--verify", "--quiet", ref], root).ok;
12279
- var defaultBranchOf = (runtime, root) => {
12280
- const remote = line(runtime, root, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
12281
- if (remote !== void 0) return remote.replace(/^[^/]+\//, "");
12282
- const configured = line(runtime, root, ["config", "--get", "init.defaultBranch"]);
12283
- if (configured !== void 0 && refExists(runtime, root, `refs/heads/${configured}`)) return configured;
12284
- return refExists(runtime, root, "refs/heads/master") && !refExists(runtime, root, "refs/heads/main") ? "master" : "main";
12285
- };
12286
- var upstreamOf = (runtime, root, branch) => {
12287
- const remote = line(runtime, root, ["config", "--get", `branch.${branch}.remote`]);
12288
- const merge = line(runtime, root, ["config", "--get", `branch.${branch}.merge`]);
12289
- if (remote === void 0 || merge === void 0 || remote === ".") return void 0;
12290
- const name = merge.replace(/^refs\/heads\//, "");
12291
- return { branch: name, remoteRef: `refs/remotes/${remote}/${name}` };
12292
- };
12293
- var integrationRefs = (runtime, root, defaultBranch) => [`refs/heads/${defaultBranch}`, `refs/remotes/origin/${defaultBranch}`].filter(
12294
- (ref) => refExists(runtime, root, ref)
12295
- );
12296
- var contained = (runtime, root, branch, targets2) => {
12297
- const tip = line(runtime, root, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]);
12298
- if (tip === void 0) return true;
12299
- return targets2.filter((target2) => target2 !== `refs/heads/${branch}`).some((target2) => runtime.git(["merge-base", "--is-ancestor", tip, target2], root).ok);
12300
- };
12301
- var shared = (runtime, root, upstream, base) => {
12302
- if (upstream === void 0 || !refExists(runtime, root, upstream.remoteRef)) return false;
12303
- const own = /<([^>]*)>/.exec(line(runtime, root, ["var", "GIT_AUTHOR_IDENT"]) ?? "")?.[1] ?? "";
12304
- const log = runtime.git(["log", "--format=%ae", upstream.remoteRef, ...base.map((ref) => `^${ref}`)], root);
12305
- return log.ok && log.stdout.split("\n").some((email) => email.trim() !== "" && email.trim() !== own);
12306
- };
12307
- var protectedPatterns = (config, defaultBranch) => [
12308
- defaultBranch,
12309
- ...DEFAULT_PROTECTED,
12310
- ...configList(config, "git", "protected")
12311
- ];
12312
- var repoFacts = (runtime, repo, config = loadProjectConfig(runtime, repo.root)) => {
12313
- const { root } = repo;
12314
- const defaultBranch = memo2(() => defaultBranchOf(runtime, root));
12315
- const patterns = memo2(() => protectedPatterns(config, defaultBranch()));
12316
- const upstream = memoBy((branch) => upstreamOf(runtime, root, branch));
12317
- const integration = memo2(() => integrationRefs(runtime, root, defaultBranch()));
12318
- return {
12319
- prefix: repo.prefix,
12320
- currentBranch: memo2(() => line(runtime, root, ["symbolic-ref", "--quiet", "--short", "HEAD"])),
12321
- defaultBranch,
12322
- isProtected: (branch) => matchesBranch(branch, patterns()),
12323
- dirty: memo2(() => {
12324
- const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
12325
- return status.ok ? parsePorcelainV2(status.stdout) : void 0;
12326
- }),
12327
- isRef: memoBy((name) => runtime.git(["rev-parse", "--verify", "--quiet", `${name}^{commit}`], root).ok),
12328
- branchExists: memoBy((name) => refExists(runtime, root, `refs/heads/${name}`)),
12329
- branchContained: memoBy((branch) => {
12330
- const up = upstream(branch);
12331
- return contained(runtime, root, branch, [...up === void 0 ? [] : [up.remoteRef], ...integration()]);
12332
- }),
12333
- upstream,
12334
- sharedBranch: memoBy((branch) => shared(runtime, root, upstream(branch), integration()))
12335
- };
12336
- };
12337
-
12338
12777
  // src/gate/checks/worktree.ts
12339
12778
  var MAX_LISTED7 = 8;
12340
12779
  var listed3 = (items) => {
12341
12780
  const head = items.slice(0, MAX_LISTED7).join(", ");
12342
12781
  return items.length > MAX_LISTED7 ? `${head} (+${String(items.length - MAX_LISTED7)})` : head;
12343
12782
  };
12344
- var repoOf = (ctx) => openRepo(ctx.runtime, ctx.paths.root);
12783
+ var repoOf2 = (ctx) => openRepo(ctx.runtime, ctx.paths.root);
12345
12784
  var ownerOf = (ctx) => ownerKey(ctx.sessionId);
12346
12785
  var declaresDestination = (ctx) => ctx.contract.acceptance.some((check2) => check2.kind === "git" && (check2.require ?? []).includes("pushed"));
12347
12786
  var worktreeCheck = (_check, ctx) => {
12348
- const repo = repoOf(ctx);
12787
+ const repo = repoOf2(ctx);
12349
12788
  if (repo === void 0) return unverified("tool_missing", [note("gate.worktree.unavailable")]);
12350
12789
  const status = worktreeGateStatus(ctx.runtime, repo, ownerOf(ctx));
12351
12790
  const unpushed = declaresDestination(ctx) ? [] : status.unpushed;
@@ -12356,7 +12795,7 @@ var worktreeCheck = (_check, ctx) => {
12356
12795
  return notes2.length > 0 ? failed(notes2) : passed([note("gate.worktree.clean")]);
12357
12796
  };
12358
12797
  var mergeCheck = (_check, ctx) => {
12359
- const repo = repoOf(ctx);
12798
+ const repo = repoOf2(ctx);
12360
12799
  if (repo === void 0) return unverified("tool_missing", [note("gate.merge.unavailable")]);
12361
12800
  const items = mergeGateStatus(ctx.runtime, repo, ownerOf(ctx));
12362
12801
  const short3 = (commit2) => commit2 === "MERGE_HEAD" ? commit2 : commit2.slice(0, 12);
@@ -12467,15 +12906,15 @@ var runGate = (options, registry = DEFAULT_CHECKS) => {
12467
12906
  if (ready.kind !== "ready") return ready;
12468
12907
  const { prepared } = ready;
12469
12908
  const path = prepared.ctx.paths.evidence(prepared.ctx.contract.id);
12470
- const before = readLedger(options.runtime, path);
12909
+ const before = readLedger2(options.runtime, path);
12471
12910
  if (options.fast && fastPathHit(prepared, before)) return { kind: "done", report: report(prepared, before, true) };
12472
12911
  const fresh3 = runChecks(prepared.ctx, before, registry);
12473
12912
  if (!appendEvidence(options.runtime, path, fresh3)) return { kind: "failed", failure: { kind: "ledger_unwritable" } };
12474
- const withChecks = readLedger(options.runtime, path);
12913
+ const withChecks = readLedger2(options.runtime, path);
12475
12914
  if (!appendEvidence(options.runtime, path, [summaryEntry(prepared, withChecks)])) {
12476
12915
  return { kind: "failed", failure: { kind: "ledger_unwritable" } };
12477
12916
  }
12478
- return { kind: "done", report: report(prepared, readLedger(options.runtime, path), false) };
12917
+ return { kind: "done", report: report(prepared, readLedger2(options.runtime, path), false) };
12479
12918
  };
12480
12919
 
12481
12920
  // src/gate/hooks/stop.ts
@@ -12593,7 +13032,7 @@ var subagentStopGate = (input) => {
12593
13032
  };
12594
13033
 
12595
13034
  // src/gate/hooks/subagent.ts
12596
- import { join as join29 } from "node:path";
13035
+ import { join as join31 } from "node:path";
12597
13036
  var NO_CONTRACT_AGENTS = /(^|:)(reviewer|verifier|explore|plan|claude-code-guide|statusline-setup)$/i;
12598
13037
  var KNOWN_KINDS = /* @__PURE__ */ new Set([
12599
13038
  "command",
@@ -12631,12 +13070,12 @@ var subagentStart = (input) => {
12631
13070
  const active = readState(runtime, paths).active;
12632
13071
  if (active === void 0) return { kind: "allow" };
12633
13072
  const disk = (relative11) => {
12634
- const text8 = runtime.readText(toAbsolutePath(join29(paths.root, relative11)));
12635
- return text8.ok ? text8.value : void 0;
13073
+ const text9 = runtime.readText(toAbsolutePath(join31(paths.root, relative11)));
13074
+ return text9.ok ? text9.value : void 0;
12636
13075
  };
12637
13076
  const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
12638
13077
  if (!loaded.ok) return { kind: "allow" };
12639
- const briefing = runtime.stat(toAbsolutePath(join29(paths.dir, "briefing.md"))) !== void 0;
13078
+ const briefing = runtime.stat(toAbsolutePath(join31(paths.dir, "briefing.md"))) !== void 0;
12640
13079
  return { kind: "allow", output: contextOutput("SubagentStart", contractFacts(t, loaded.value.contract, briefing)) };
12641
13080
  };
12642
13081
 
@@ -12654,12 +13093,12 @@ var PORTAO_HANDLERS = {
12654
13093
  };
12655
13094
 
12656
13095
  // src/prod/guard.ts
12657
- import { join as join31 } from "node:path";
13096
+ import { join as join33 } from "node:path";
12658
13097
 
12659
13098
  // src/git/command-text.ts
12660
13099
  var MAX_SHOWN = 160;
12661
- var shown = (text8) => {
12662
- const flat = text8.replace(/\s+/g, " ").trim();
13100
+ var shown = (text9) => {
13101
+ const flat = text9.replace(/\s+/g, " ").trim();
12663
13102
  return flat.length > MAX_SHOWN ? `${flat.slice(0, MAX_SHOWN - 3)}...` : flat;
12664
13103
  };
12665
13104
  var commandText = (command) => shown([command.program, ...command.args].join(" "));
@@ -12996,7 +13435,7 @@ var remoteHostsOf = (command) => {
12996
13435
  };
12997
13436
 
12998
13437
  // src/prod/targets.ts
12999
- import { join as join30 } from "node:path";
13438
+ import { join as join32 } from "node:path";
13000
13439
  var DEPLOY_FILES = [
13001
13440
  "deploy.sh",
13002
13441
  "scripts/deploy.sh",
@@ -13009,9 +13448,9 @@ var SSH_URL = /ssh:\/\/(?:[^@\s/]+@)?([A-Za-z0-9.-]+)/g;
13009
13448
  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;
13010
13449
  var hostKey = (raw) => raw.trim().toLowerCase().replace(/^[^@]*@/, "").replace(/^\[([^\]]+)\].*$/, "$1").replace(/:\d+$/, "").replace(/\.$/, "");
13011
13450
  var shellLine = (line2) => line2.replace(/^\s*(?:-\s*)?(?:run:\s*\|?)?/, "");
13012
- var hostsInText = (text8) => {
13013
- const urls = [...text8.matchAll(SSH_URL)].map((match) => match[1] ?? "");
13014
- const lines3 = text8.split(/\r?\n/);
13451
+ var hostsInText = (text9) => {
13452
+ const urls = [...text9.matchAll(SSH_URL)].map((match) => match[1] ?? "");
13453
+ const lines3 = text9.split(/\r?\n/);
13015
13454
  const keyed = lines3.flatMap((line2) => {
13016
13455
  const match = HOST_KEY.exec(line2);
13017
13456
  return match?.[1] === void 0 ? [] : [match[1]];
@@ -13020,13 +13459,13 @@ var hostsInText = (text8) => {
13020
13459
  return [...urls, ...keyed, ...commands].filter((host) => host !== "" && !host.includes("$"));
13021
13460
  };
13022
13461
  var workflowFiles = (runtime, root) => {
13023
- const listed4 = runtime.listDir(toAbsolutePath(join30(root, ".github", "workflows")));
13462
+ const listed4 = runtime.listDir(toAbsolutePath(join32(root, ".github", "workflows")));
13024
13463
  if (!listed4.ok) return [];
13025
13464
  return listed4.value.filter((name) => /deploy/i.test(name) && /\.ya?ml$/i.test(name)).map((name) => `.github/workflows/${name}`);
13026
13465
  };
13027
13466
  var detectedHosts = (runtime, root, files) => files.flatMap((file) => {
13028
- const text8 = runtime.readText(toAbsolutePath(join30(root, file)));
13029
- return text8.ok ? hostsInText(text8.value) : [];
13467
+ const text9 = runtime.readText(toAbsolutePath(join32(root, file)));
13468
+ return text9.ok ? hostsInText(text9.value) : [];
13030
13469
  });
13031
13470
  var loadProdTargets = (runtime, root, config) => {
13032
13471
  const workflows = workflowFiles(runtime, root);
@@ -13037,7 +13476,7 @@ var loadProdTargets = (runtime, root, config) => {
13037
13476
  ...detectedHosts(runtime, root, deployFiles)
13038
13477
  ];
13039
13478
  const declaredDeploy = configAt(config, "prod", "deploy");
13040
- const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join30(root, file))) !== void 0);
13479
+ const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join32(root, file))) !== void 0);
13041
13480
  return {
13042
13481
  hosts: new Set(hosts.map(hostKey).filter((host) => host !== "")),
13043
13482
  kubeContexts: configList(config, "prod", "kube_contexts"),
@@ -13193,15 +13632,15 @@ var blockOf = (t, hit3, command, deploy) => ({
13193
13632
  })
13194
13633
  });
13195
13634
  var prodGuard = (runtime, root, t) => {
13196
- const targets2 = memo2(() => loadProdTargets(runtime, root, loadProjectConfig(runtime, root)));
13635
+ const targets2 = memo(() => loadProdTargets(runtime, root, loadProjectConfig(runtime, root)));
13197
13636
  const isProd = isProdHostOf(runtime, root, targets2);
13198
13637
  return {
13199
13638
  decide: (command) => {
13200
13639
  if (!PROD_PROGRAMS.has(command.program)) return void 0;
13201
13640
  const hit3 = prodHit(command, targets2(), isProd);
13202
13641
  if (hit3 === void 0) return void 0;
13203
- const text8 = commandText(command);
13204
- return hit3.effect === "read" ? { kind: "read", hit: hit3, command: text8 } : blockOf(t, hit3, text8, targets2().deployPath);
13642
+ const text9 = commandText(command);
13643
+ return hit3.effect === "read" ? { kind: "read", hit: hit3, command: text9 } : blockOf(t, hit3, text9, targets2().deployPath);
13205
13644
  }
13206
13645
  };
13207
13646
  };
@@ -13218,11 +13657,11 @@ var recordEvidence = (runtime, root, owner, reads) => {
13218
13657
  ] : []
13219
13658
  );
13220
13659
  if (lines3.length === 0) return;
13221
- const path = toAbsolutePath(join31(root, EVIDENCE_FILE));
13222
- runtime.mkdir(toAbsolutePath(join31(root, ".finitemonkey")));
13660
+ const path = toAbsolutePath(join33(root, EVIDENCE_FILE));
13661
+ runtime.mkdir(toAbsolutePath(join33(root, ".finitemonkey")));
13223
13662
  const before = runtime.readText(path);
13224
- const text8 = before.ok ? before.value : "";
13225
- runtime.writeAtomic(path, `${text8}${text8 === "" || text8.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
13663
+ const text9 = before.ok ? before.value : "";
13664
+ runtime.writeAtomic(path, `${text9}${text9 === "" || text9.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
13226
13665
  `);
13227
13666
  };
13228
13667
 
@@ -13237,15 +13676,15 @@ var editTexts = (input, file) => {
13237
13676
  if (!Array.isArray(edits)) return [];
13238
13677
  return edits.flatMap((edit) => {
13239
13678
  if (!isRecord(edit)) return [];
13240
- const text8 = optionalString(edit, "new_string");
13241
- return text8 === void 0 ? [] : [{ file, text: text8, whole: false }];
13679
+ const text9 = optionalString(edit, "new_string");
13680
+ return text9 === void 0 ? [] : [{ file, text: text9, whole: false }];
13242
13681
  });
13243
13682
  };
13244
13683
  var writtenTexts = (tool, input) => {
13245
13684
  const file = optionalString(input, "file_path") ?? optionalString(input, "notebook_path") ?? "";
13246
13685
  const single2 = (key, whole) => {
13247
- const text8 = optionalString(input, key);
13248
- return text8 === void 0 ? [] : [{ file, text: text8, whole }];
13686
+ const text9 = optionalString(input, key);
13687
+ return text9 === void 0 ? [] : [{ file, text: text9, whole }];
13249
13688
  };
13250
13689
  switch (tool) {
13251
13690
  case "Write":
@@ -13927,24 +14366,24 @@ var withChain = (facts, chain) => {
13927
14366
  const branch = chain?.branch;
13928
14367
  return branch === void 0 ? facts : { ...facts, currentBranch: () => branch };
13929
14368
  };
13930
- var decide = (context, repo, verdict, facts, text8) => {
14369
+ var decide = (context, repo, verdict, facts, text9) => {
13931
14370
  switch (verdict.kind) {
13932
14371
  case "pass":
13933
14372
  return { kind: "pass" };
13934
14373
  case "block":
13935
- return { kind: "block", rule: verdict.rule, subject: verdict.subject, command: text8 };
14374
+ return { kind: "block", rule: verdict.rule, subject: verdict.subject, command: text9 };
13936
14375
  case "snapshot":
13937
- return { kind: "snapshot", repo, op: verdict.op, command: text8 };
14376
+ return { kind: "snapshot", repo, op: verdict.op, command: text9 };
13938
14377
  case "discard": {
13939
14378
  const entries = facts.dirty();
13940
- if (entries === void 0) return { kind: "unknown", command: text8 };
14379
+ if (entries === void 0) return { kind: "unknown", command: text9 };
13941
14380
  const foreign = foreignFiles(
13942
14381
  context.runtime,
13943
14382
  repo,
13944
14383
  context.owner,
13945
14384
  targetsOf(entries, verdict.discard, facts.prefix)
13946
14385
  );
13947
- return foreign.length > 0 ? { kind: "foreign", files: foreign, command: text8 } : { kind: "snapshot", repo, op: verdict.op, command: text8 };
14386
+ return foreign.length > 0 ? { kind: "foreign", files: foreign, command: text9 } : { kind: "snapshot", repo, op: verdict.op, command: text9 };
13948
14387
  }
13949
14388
  }
13950
14389
  };
@@ -14031,10 +14470,10 @@ var listWorktrees = (runtime, cwd) => {
14031
14470
  };
14032
14471
 
14033
14472
  // src/git/journal.ts
14034
- import { join as join32 } from "node:path";
14473
+ import { join as join34 } from "node:path";
14035
14474
  var JOURNAL_FILE = ".finitemonkey/git-journal.jsonl";
14036
14475
  var JOURNAL_SCHEMA = "finitemonkey/git-journal@1";
14037
- var journalPath = (root) => toAbsolutePath(join32(root, JOURNAL_FILE));
14476
+ var journalPath = (root) => toAbsolutePath(join34(root, JOURNAL_FILE));
14038
14477
  var headsOf = (value) => isRecord(value) ? Object.fromEntries(
14039
14478
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
14040
14479
  ) : {};
@@ -14061,16 +14500,16 @@ var parseJournalLine = (line2) => {
14061
14500
  };
14062
14501
  };
14063
14502
  var readJournal = (runtime, root) => {
14064
- const text8 = runtime.readText(journalPath(root));
14065
- if (!text8.ok) return [];
14066
- return text8.value.split("\n").flatMap((line2) => {
14503
+ const text9 = runtime.readText(journalPath(root));
14504
+ if (!text9.ok) return [];
14505
+ return text9.value.split("\n").flatMap((line2) => {
14067
14506
  const entry2 = line2.trim() === "" ? void 0 : parseJournalLine(line2);
14068
14507
  return entry2 === void 0 ? [] : [entry2];
14069
14508
  });
14070
14509
  };
14071
14510
  var appendJournal = (runtime, root, entry2) => {
14072
14511
  const path = journalPath(root);
14073
- const made = runtime.mkdir(toAbsolutePath(join32(root, ".finitemonkey")));
14512
+ const made = runtime.mkdir(toAbsolutePath(join34(root, ".finitemonkey")));
14074
14513
  if (!made.ok) return made;
14075
14514
  const previous = runtime.readText(path);
14076
14515
  const before = previous.ok ? previous.value : "";
@@ -14234,9 +14673,9 @@ var snapshotsOf = (input, owner, pending) => {
14234
14673
  return { kind: "allow", output: { hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: context } } };
14235
14674
  };
14236
14675
  var review = (input, commands, owner) => {
14237
- const worktrees = memo2(() => listWorktrees(input.runtime, input.cwd));
14238
- const repo = memo2(() => openRepo(input.runtime, input.cwd));
14239
- const prod = memo2(() => prodGuard(input.runtime, repo()?.root ?? input.cwd, input.t));
14676
+ const worktrees = memo(() => listWorktrees(input.runtime, input.cwd));
14677
+ const repo = memo(() => openRepo(input.runtime, input.cwd));
14678
+ const prod = memo(() => prodGuard(input.runtime, repo()?.root ?? input.cwd, input.t));
14240
14679
  const snapshots = [];
14241
14680
  const reads = [];
14242
14681
  const chain = { branch: void 0 };
@@ -14255,9 +14694,9 @@ var review = (input, commands, owner) => {
14255
14694
  return { blocked: void 0, snapshots, reads };
14256
14695
  };
14257
14696
  var gitPreToolUse = (input) => {
14258
- const text8 = shellText(input.payload);
14259
- if (text8 === void 0) return ALLOW;
14260
- const commands = normalizeCommand(text8, dialectOf(toolName2(input.payload))).commands;
14697
+ const text9 = shellText(input.payload);
14698
+ if (text9 === void 0) return ALLOW;
14699
+ const commands = normalizeCommand(text9, dialectOf(toolName2(input.payload))).commands;
14261
14700
  if (commands.length === 0) return ALLOW;
14262
14701
  const owner = ownerOf2(input.payload);
14263
14702
  const result = review(input, commands, owner);
@@ -14319,8 +14758,8 @@ var GIT_HANDLERS = {
14319
14758
  var SHADOW_FILE = "shadow.json";
14320
14759
  var numberOf = (value) => typeof value === "number" && Number.isInteger(value) ? value : 0;
14321
14760
  var readShadow = (runtime, paths) => {
14322
- const text8 = runtime.readText(paths.cache(SHADOW_FILE));
14323
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
14761
+ const text9 = runtime.readText(paths.cache(SHADOW_FILE));
14762
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
14324
14763
  if (parsed?.ok !== true) return {};
14325
14764
  return Object.fromEntries(
14326
14765
  Object.entries(parsed.value).flatMap(
@@ -14404,11 +14843,11 @@ var ASSIGNMENT3 = /^[A-Za-z_][A-Za-z0-9_]*=/;
14404
14843
  var WRAPPERS2 = /* @__PURE__ */ new Set(["sudo", "command", "exec", "time", "nice", "nohup", "builtin", "env"]);
14405
14844
  var liftHeredocs = (command) => {
14406
14845
  const heredocs = [];
14407
- const text8 = command.replace(HEREDOC2, (_match, _quote, _tag, body2) => {
14846
+ const text9 = command.replace(HEREDOC2, (_match, _quote, _tag, body2) => {
14408
14847
  heredocs.push(body2);
14409
14848
  return `<<__FM_HEREDOC_${String(heredocs.length - 1)}__`;
14410
14849
  });
14411
- return { text: text8, heredocs };
14850
+ return { text: text9, heredocs };
14412
14851
  };
14413
14852
  var endWord = (scan) => {
14414
14853
  if (scan.word !== void 0) scan.words.push(scan.word);
@@ -14419,23 +14858,23 @@ var endSegment = (scan) => {
14419
14858
  if (scan.words.length > 0) scan.segments.push(scan.words);
14420
14859
  scan.words = [];
14421
14860
  };
14422
- var quotedEnd2 = (text8, from, quote) => {
14861
+ var quotedEnd2 = (text9, from, quote) => {
14423
14862
  let index = from;
14424
- while (index < text8.length && text8[index] !== quote) index += quote === '"' && text8[index] === "\\" ? 2 : 1;
14863
+ while (index < text9.length && text9[index] !== quote) index += quote === '"' && text9[index] === "\\" ? 2 : 1;
14425
14864
  return index;
14426
14865
  };
14427
14866
  var unescape = (value) => value.replace(/\\(["\\$`])/g, "$1");
14428
- var splitWords = (text8) => {
14867
+ var splitWords = (text9) => {
14429
14868
  const scan = { words: [], segments: [], word: void 0 };
14430
- for (let index = 0; index < text8.length; index += 1) {
14431
- const char = text8[index] ?? "";
14869
+ for (let index = 0; index < text9.length; index += 1) {
14870
+ const char = text9[index] ?? "";
14432
14871
  if (char === '"' || char === "'") {
14433
- const end = quotedEnd2(text8, index + 1, char);
14434
- const inner = text8.slice(index + 1, end);
14872
+ const end = quotedEnd2(text9, index + 1, char);
14873
+ const inner = text9.slice(index + 1, end);
14435
14874
  scan.word = (scan.word ?? "") + (char === '"' ? unescape(inner) : inner);
14436
14875
  index = end;
14437
- } else if (char === "\\" && index + 1 < text8.length) {
14438
- scan.word = (scan.word ?? "") + (text8[index + 1] === "\n" ? "" : text8[index + 1] ?? "");
14876
+ } else if (char === "\\" && index + 1 < text9.length) {
14877
+ scan.word = (scan.word ?? "") + (text9[index + 1] === "\n" ? "" : text9[index + 1] ?? "");
14439
14878
  index += 1;
14440
14879
  } else if (SEPARATOR.test(char)) {
14441
14880
  endSegment(scan);
@@ -14456,8 +14895,8 @@ var segmentOf = (words) => {
14456
14895
  return { program: program.split(/[\\/]/).pop() ?? program, args: words.slice(index + 1) };
14457
14896
  };
14458
14897
  var shellWords = (command) => {
14459
- const { text: text8, heredocs } = liftHeredocs(command);
14460
- const segments = splitWords(text8).flatMap((words) => {
14898
+ const { text: text9, heredocs } = liftHeredocs(command);
14899
+ const segments = splitWords(text9).flatMap((words) => {
14461
14900
  const segment = segmentOf(words);
14462
14901
  return segment === void 0 ? [] : [segment];
14463
14902
  });
@@ -14562,7 +15001,7 @@ var sourceOf = (value, isFile, segment, heredocs) => {
14562
15001
  if (inline3 !== void 0) return { kind: "inline", text: inline3 };
14563
15002
  if (!isFile) return { kind: "inline", text: value };
14564
15003
  if (value !== "-") return { kind: "file", path: value };
14565
- const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text8) => text8 !== void 0);
15004
+ const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text9) => text9 !== void 0);
14566
15005
  return { kind: "inline", text: piped ?? "" };
14567
15006
  };
14568
15007
  var collect3 = (target2, segment, args2, spec2, heredocs) => {
@@ -14659,15 +15098,15 @@ var textOf2 = (input, found) => {
14659
15098
  return accept(parts.join("\n\n"));
14660
15099
  };
14661
15100
  var decisionFor = (input, found, blocking2) => {
14662
- const text8 = textOf2(input, found);
14663
- if (!text8.ok) {
15101
+ const text9 = textOf2(input, found);
15102
+ if (!text9.ok) {
14664
15103
  return blocking2 ? {
14665
15104
  kind: "block",
14666
- reason: modelReason(input.t, "prose.block.unreadable", { ...text8.error }),
15105
+ reason: modelReason(input.t, "prose.block.unreadable", { ...text9.error }),
14667
15106
  human: input.t("prose.block.human")
14668
15107
  } : void 0;
14669
15108
  }
14670
- const report2 = scoreProse(text8.value, { context: "record" });
15109
+ const report2 = scoreProse(text9.value, { context: "record" });
14671
15110
  if (report2.allowed || report2.spans.length === 0) return void 0;
14672
15111
  const params2 = {
14673
15112
  target: input.t(TARGET_KEYS[found.target]),
@@ -14714,7 +15153,7 @@ var HOOK_ACTOR = "finitemonkey";
14714
15153
  var liveFindings = (input, texts2) => {
14715
15154
  const baseline = loadBaseline(input.runtime, input.cwd);
14716
15155
  const accepted2 = acceptedFingerprints(baseline.ok ? baseline.value : EMPTY_BASELINE, input.runtime.now());
14717
- return texts2.flatMap((text8) => scanText(text8, { rules: CREDENTIAL_RULES })).filter((finding4) => !accepted2.has(finding4.fingerprint));
15156
+ return texts2.flatMap((text9) => scanText(text9, { rules: CREDENTIAL_RULES })).filter((finding4) => !accepted2.has(finding4.fingerprint));
14718
15157
  };
14719
15158
  var record2 = (input, findings, draft, where) => {
14720
15159
  const ids2 = findings.map((finding4) => {
@@ -14975,12 +15414,12 @@ var secretsSessionStart = (input) => {
14975
15414
  const remotes = remotesWithCredentials(input.runtime, input.cwd);
14976
15415
  if (remotes.length === 0) return { kind: "allow" };
14977
15416
  const params2 = { remotes: remotes.join(", "), remote: remotes[0] ?? "origin" };
14978
- const text8 = input.t("secrets.session.remote_credential", params2);
15417
+ const text9 = input.t("secrets.session.remote_credential", params2);
14979
15418
  return {
14980
15419
  kind: "allow",
14981
15420
  output: {
14982
- systemMessage: `finitemonkey: ${text8}`,
14983
- hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text8 }
15421
+ systemMessage: `finitemonkey: ${text9}`,
15422
+ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text9 }
14984
15423
  }
14985
15424
  };
14986
15425
  };
@@ -15080,9 +15519,9 @@ var EMPTY_PAYLOAD = {
15080
15519
  stopHookActive: false,
15081
15520
  raw: {}
15082
15521
  };
15083
- var parseHookPayload = (event, text8, interactive) => {
15084
- if (interactive && text8.trim() === "") return accept(EMPTY_PAYLOAD);
15085
- const parsed = parseJsonObject(text8);
15522
+ var parseHookPayload = (event, text9, interactive) => {
15523
+ if (interactive && text9.trim() === "") return accept(EMPTY_PAYLOAD);
15524
+ const parsed = parseJsonObject(text9);
15086
15525
  if (!parsed.ok) return reject(`stdin: ${parsed.error}`);
15087
15526
  const payload = {
15088
15527
  hookEventName: optionalString(parsed.value, "hook_event_name"),
@@ -15097,53 +15536,13 @@ var parseHookPayload = (event, text8, interactive) => {
15097
15536
  return accept(payload);
15098
15537
  };
15099
15538
 
15100
- // src/hooks/stdin.ts
15101
- var readAll = (source) => new Promise((resolve2, reject2) => {
15102
- if (source.isTTY === true) {
15103
- resolve2("");
15104
- return;
15105
- }
15106
- const chunks = [];
15107
- source.setEncoding?.("utf8");
15108
- source.on("data", (chunk) => {
15109
- chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
15110
- });
15111
- source.on("end", () => {
15112
- resolve2(chunks.join(""));
15113
- });
15114
- source.on("error", reject2);
15115
- });
15116
- var DeadlineError = class extends Error {
15117
- ms;
15118
- constructor(ms) {
15119
- super(`deadline of ${String(ms)} ms exceeded`);
15120
- this.name = "DeadlineError";
15121
- this.ms = ms;
15122
- }
15123
- };
15124
- var withDeadline = (work, ms, startTimer) => new Promise((resolve2, reject2) => {
15125
- const cancel = startTimer(ms, () => {
15126
- reject2(new DeadlineError(ms));
15127
- });
15128
- work.then(
15129
- (value) => {
15130
- cancel();
15131
- resolve2(value);
15132
- },
15133
- (error) => {
15134
- cancel();
15135
- reject2(error instanceof Error ? error : new Error(String(error)));
15136
- }
15137
- );
15138
- });
15139
-
15140
15539
  // src/hooks/runner.ts
15141
15540
  var HOOK_PROBE_ENV = "FINITEMONKEY_HOOK_PROBE";
15142
15541
  var messageOf2 = (error) => error instanceof Error ? error.message : String(error);
15143
15542
  var decide2 = (input, handlers) => composeDecisions(input, handlers[input.event] ?? [allow]);
15144
15543
  var evaluate = async (event, options) => {
15145
- const text8 = await readAll(options.stdin);
15146
- const parsed = parseHookPayload(event, text8, options.stdin.isTTY === true);
15544
+ const text9 = await readAll(options.stdin);
15545
+ const parsed = parseHookPayload(event, text9, options.stdin.isTTY === true);
15147
15546
  if (!parsed.ok) return { kind: "failed", payload: void 0, detail: parsed.error };
15148
15547
  const payload = parsed.value;
15149
15548
  if (options.env?.[HOOK_PROBE_ENV] === "1") return { kind: "decided", payload, decision: { kind: "allow" } };
@@ -15220,7 +15619,7 @@ var runHook = async (event, options) => {
15220
15619
  var PROBE_TIMEOUT_MS2 = 1e4;
15221
15620
  var SLOW_HOOK_MS = 1500;
15222
15621
  var DEFAULT_TIMEOUT_S = 60;
15223
- var expand = (text8, vars) => text8.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
15622
+ var expand = (text9, vars) => text9.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
15224
15623
  const name = braced ?? bare ?? "";
15225
15624
  return vars[name] ?? match;
15226
15625
  });
@@ -15269,7 +15668,7 @@ var run2 = (exec, invocation, cwd, env, input) => exec({
15269
15668
  env: { ...env, [HOOK_PROBE_ENV]: "1" },
15270
15669
  ...input === void 0 ? {} : { input }
15271
15670
  });
15272
- var firstLine = (text8) => maskText(text8.trim().split("\n")[0] ?? "").slice(0, 160);
15671
+ var firstLine = (text9) => maskText(text9.trim().split("\n")[0] ?? "").slice(0, 160);
15273
15672
  var classify2 = (result) => {
15274
15673
  if (!result.found) return { kind: "failed", problem: "missing", detail: "" };
15275
15674
  if (result.timedOut) return { kind: "failed", problem: "timeout", detail: "" };
@@ -15481,7 +15880,7 @@ var hookSchemaProblems = (hooks, claude) => {
15481
15880
  };
15482
15881
 
15483
15882
  // src/doctor/hook-sources.ts
15484
- import { join as join33 } from "node:path";
15883
+ import { join as join35 } from "node:path";
15485
15884
  var entriesOf = (source, hooks, own) => Object.entries(asRecord(hooks)).flatMap(
15486
15885
  ([event, groups]) => asList(groups).flatMap((group) => {
15487
15886
  const record3 = asRecord(group);
@@ -15494,31 +15893,31 @@ var projectHookFiles = (env) => readSettings(env).flatMap(({ scope: scope2, path
15494
15893
  const source = PROJECT_SCOPES[scope2];
15495
15894
  return source === void 0 || !("hooks" in settings) ? [] : [{ source, path, hooks: settings["hooks"] }];
15496
15895
  });
15497
- var pluginsDir = ({ env, runtime }) => join33(env["CLAUDE_CONFIG_DIR"] ?? join33(runtime.homeDir(), ".claude"), "plugins");
15896
+ var pluginsDir = ({ env, runtime }) => join35(env["CLAUDE_CONFIG_DIR"] ?? join35(runtime.homeDir(), ".claude"), "plugins");
15498
15897
  var installRecord = (value, root) => {
15499
15898
  const records = Array.isArray(value) ? value.filter(isRecord) : isRecord(value) ? [value] : [];
15500
15899
  return records.find((record3) => record3["projectPath"] === root) ?? records[0];
15501
15900
  };
15502
15901
  var readJson = (env, path) => {
15503
- const text8 = env.runtime.readText(path);
15504
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
15902
+ const text9 = env.runtime.readText(path);
15903
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
15505
15904
  return parsed?.ok === true ? parsed.value : void 0;
15506
15905
  };
15507
15906
  var installedPlugin = (env) => {
15508
- const index = readJson(env, toAbsolutePath(join33(pluginsDir(env), "installed_plugins.json")));
15907
+ const index = readJson(env, toAbsolutePath(join35(pluginsDir(env), "installed_plugins.json")));
15509
15908
  const record3 = installRecord(asRecord(index?.["plugins"])[PLUGIN_ID], env.root);
15510
15909
  const installPath = record3 === void 0 ? void 0 : optionalString(record3, "installPath");
15511
15910
  if (record3 === void 0 || installPath === void 0) return void 0;
15512
15911
  const root = toAbsolutePath(installPath);
15513
- const manifest = readJson(env, toAbsolutePath(join33(root, ".claude-plugin", "plugin.json")));
15514
- const hooksPath = toAbsolutePath(join33(root, "hooks", "hooks.json"));
15515
- const text8 = env.runtime.readText(hooksPath);
15516
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
15912
+ const manifest = readJson(env, toAbsolutePath(join35(root, ".claude-plugin", "plugin.json")));
15913
+ const hooksPath = toAbsolutePath(join35(root, "hooks", "hooks.json"));
15914
+ const text9 = env.runtime.readText(hooksPath);
15915
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
15517
15916
  return {
15518
15917
  root,
15519
15918
  version: (manifest === void 0 ? void 0 : optionalString(manifest, "version")) ?? optionalString(record3, "version"),
15520
15919
  hooksFile: parsed?.ok === true ? { source: "plugin", path: hooksPath, hooks: parsed.value["hooks"] } : void 0,
15521
- hooksText: text8.ok ? text8.value : void 0
15920
+ hooksText: text9.ok ? text9.value : void 0
15522
15921
  };
15523
15922
  };
15524
15923
  var PRECEDENCE2 = ["managed", "local", "project", "user"];
@@ -15548,15 +15947,15 @@ var schemaFinding = (file, problems, claude) => problems.length === 0 ? [] : [
15548
15947
  )
15549
15948
  ];
15550
15949
  var unreadableProjectFiles = (env) => settingsPaths(env).filter(({ scope: scope2 }) => scope2 === "project" || scope2 === "local").flatMap(({ path }) => {
15551
- const text8 = env.runtime.readText(path);
15552
- return text8.ok && text8.value.trim() !== "" && !parseJsonObject(text8.value).ok ? [path] : [];
15950
+ const text9 = env.runtime.readText(path);
15951
+ return text9.ok && text9.value.trim() !== "" && !parseJsonObject(text9.value).ok ? [path] : [];
15553
15952
  });
15554
15953
  var schemaFindings = (env, plugin, claude) => {
15555
15954
  const label2 = claude === void 0 ? "?" : claude.join(".");
15556
15955
  const files = [...projectHookFiles(env), ...plugin?.hooksFile === void 0 ? [] : [plugin.hooksFile]];
15557
15956
  const invalidJson = [
15558
15957
  ...unreadableProjectFiles(env),
15559
- ...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(join34(plugin.root, "hooks", "hooks.json"))] : []
15958
+ ...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(join36(plugin.root, "hooks", "hooks.json"))] : []
15560
15959
  ];
15561
15960
  return [
15562
15961
  ...invalidJson.flatMap((file) => schemaFinding(file, ["not valid JSON"], label2)),
@@ -15577,7 +15976,7 @@ var pluginFindings = (env, projectOwn) => {
15577
15976
  ];
15578
15977
  };
15579
15978
  var coverageFindings = (env, entries) => {
15580
- const installed = env.runtime.stat(toAbsolutePath(join34(env.root, ...LOCK_FILE.split("/")))) !== void 0;
15979
+ const installed = env.runtime.stat(toAbsolutePath(join36(env.root, ...LOCK_FILE.split("/")))) !== void 0;
15581
15980
  if (entries.length === 0 && !installed) {
15582
15981
  return [finding("hooks.registered", "warn", "doctor.hooks.none", {}, "doctor.hooks.fix_none")];
15583
15982
  }
@@ -15629,21 +16028,21 @@ var checkHooks = (env) => {
15629
16028
  };
15630
16029
 
15631
16030
  // src/doctor/instructions.ts
15632
- import { join as join35 } from "node:path";
16031
+ import { join as join37 } from "node:path";
15633
16032
  var INSTRUCTION_LINE_CEILING = 200;
15634
16033
  var TOP_INSTRUCTIONS = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"];
15635
16034
  var RULES_DIR = ".claude/rules";
15636
16035
  var SHOWN = 3;
15637
16036
  var readProjectFile = (env, relative11) => {
15638
- const text8 = env.runtime.readText(toAbsolutePath(join35(env.root, ...relative11.split("/"))));
15639
- return text8.ok ? text8.value : void 0;
16037
+ const text9 = env.runtime.readText(toAbsolutePath(join37(env.root, ...relative11.split("/"))));
16038
+ return text9.ok ? text9.value : void 0;
15640
16039
  };
15641
16040
  var instructionFiles = (env) => {
15642
- const listed4 = env.runtime.listDir(toAbsolutePath(join35(env.root, ...RULES_DIR.split("/"))));
16041
+ const listed4 = env.runtime.listDir(toAbsolutePath(join37(env.root, ...RULES_DIR.split("/"))));
15643
16042
  const rules = listed4.ok ? listed4.value.filter((name) => name.endsWith(".md")).map((name) => `${RULES_DIR}/${name}`) : [];
15644
16043
  return [...TOP_INSTRUCTIONS, ...rules].flatMap((path) => {
15645
- const text8 = readProjectFile(env, path);
15646
- return text8 === void 0 ? [] : [{ path, text: text8 }];
16044
+ const text9 = readProjectFile(env, path);
16045
+ return text9 === void 0 ? [] : [{ path, text: text9 }];
15647
16046
  });
15648
16047
  };
15649
16048
  var shortList = (items) => items.length <= SHOWN ? items.join(", ") : `${items.slice(0, SHOWN).join(", ")} (+${String(items.length - SHOWN)})`;
@@ -15691,10 +16090,10 @@ var hiddenInstructions = (env, deny2) => {
15691
16090
  ];
15692
16091
  };
15693
16092
  var localFile = (env) => readProjectFile(env, "CLAUDE.local.md") === void 0 ? [] : [finding("instructions.local", "warn", "doctor.instructions.local", {}, "doctor.instructions.fix_local")];
15694
- var lineCount = (text8) => text8.split("\n").length - (text8.endsWith("\n") ? 1 : 0);
16093
+ var lineCount = (text9) => text9.split("\n").length - (text9.endsWith("\n") ? 1 : 0);
15695
16094
  var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
15696
- const text8 = readProjectFile(env, file);
15697
- const lines3 = text8 === void 0 ? 0 : lineCount(text8);
16095
+ const text9 = readProjectFile(env, file);
16096
+ const lines3 = text9 === void 0 ? 0 : lineCount(text9);
15698
16097
  if (lines3 <= INSTRUCTION_LINE_CEILING) return [];
15699
16098
  return [
15700
16099
  finding(
@@ -15707,13 +16106,13 @@ var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
15707
16106
  ];
15708
16107
  });
15709
16108
  var MIN_RULE_WORDS = 6;
15710
- var ruleLines = (text8) => text8.split("\n").map((raw) => withoutCarriageReturn(raw).trim()).filter((line2) => !/^(#|\||```|@|>)/.test(line2)).map(
16109
+ var ruleLines = (text9) => text9.split("\n").map((raw) => withoutCarriageReturn(raw).trim()).filter((line2) => !/^(#|\||```|@|>)/.test(line2)).map(
15711
16110
  (line2) => line2.replace(/^([-*+]|\d+[.)])\s+/, "").replace(/\s+/g, " ").toLowerCase()
15712
16111
  ).filter((line2) => line2.split(" ").length >= MIN_RULE_WORDS);
15713
16112
  var duplicatedRules = (files) => {
15714
16113
  const seen = /* @__PURE__ */ new Map();
15715
- for (const { path, text: text8 } of files) {
15716
- for (const line2 of ruleLines(text8)) seen.set(line2, (seen.get(line2) ?? /* @__PURE__ */ new Set()).add(path));
16114
+ for (const { path, text: text9 } of files) {
16115
+ for (const line2 of ruleLines(text9)) seen.set(line2, (seen.get(line2) ?? /* @__PURE__ */ new Set()).add(path));
15717
16116
  }
15718
16117
  return [...seen].filter(([, paths]) => paths.size > 1).map(([line2]) => line2);
15719
16118
  };
@@ -15744,12 +16143,12 @@ var checkInstructions = (env) => {
15744
16143
  };
15745
16144
 
15746
16145
  // src/doctor/line-endings.ts
15747
- import { join as join36 } from "node:path";
16146
+ import { join as join38 } from "node:path";
15748
16147
  var LF_RULE = /^\*\s.*\beol=lf\b/m;
15749
16148
  var checkLineEndings = ({ root, runtime }) => {
15750
16149
  const autocrlf = runtime.git(["config", "--get", "core.autocrlf"], root);
15751
16150
  if (!autocrlf.ok || autocrlf.stdout.trim().toLowerCase() !== "true") return [];
15752
- const attributes = runtime.readText(toAbsolutePath(join36(root, ".gitattributes")));
16151
+ const attributes = runtime.readText(toAbsolutePath(join38(root, ".gitattributes")));
15753
16152
  if (attributes.ok && LF_RULE.test(attributes.value)) return [];
15754
16153
  return [
15755
16154
  finding("git.autocrlf", "warn", "doctor.git.autocrlf", { file: ".gitattributes" }, "doctor.git.fix_autocrlf")
@@ -15757,25 +16156,25 @@ var checkLineEndings = ({ root, runtime }) => {
15757
16156
  };
15758
16157
 
15759
16158
  // src/doctor/memory.ts
15760
- import { join as join37 } from "node:path";
16159
+ import { join as join39 } from "node:path";
15761
16160
  var ceilingOf = (config) => {
15762
16161
  const value = configAt(parseConfigYaml(config ?? ""), "memory", "maxLines");
15763
16162
  const parsed = typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
15764
16163
  return Number.isInteger(parsed) && parsed > 0 ? parsed : AGENTS_MAX_LINES;
15765
16164
  };
15766
- var lineCount2 = (text8) => text8.replace(/\r?\n$/, "").split(/\r?\n/).length;
16165
+ var lineCount2 = (text9) => text9.replace(/\r?\n$/, "").split(/\r?\n/).length;
15767
16166
  var checkAgentsFile = ({ root, runtime }) => {
15768
- const text8 = runtime.readText(toAbsolutePath(join37(root, AGENTS_FILE)));
15769
- if (!text8.ok) return [];
15770
- const config = runtime.readText(toAbsolutePath(join37(root, CONFIG_FILE)));
16167
+ const text9 = runtime.readText(toAbsolutePath(join39(root, AGENTS_FILE)));
16168
+ if (!text9.ok) return [];
16169
+ const config = runtime.readText(toAbsolutePath(join39(root, CONFIG_FILE2)));
15771
16170
  const max = ceilingOf(config.ok ? config.value : void 0);
15772
- const lines3 = lineCount2(text8.value);
16171
+ const lines3 = lineCount2(text9.value);
15773
16172
  const params2 = { file: AGENTS_FILE, lines: lines3, max };
15774
16173
  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)];
15775
16174
  };
15776
16175
 
15777
16176
  // src/doctor/project-files.ts
15778
- import { join as join38 } from "node:path";
16177
+ import { join as join40 } from "node:path";
15779
16178
  var variants = (base, extensions) => extensions.map((extension) => `${base}${extension}`);
15780
16179
  var SCRIPT = [".js", ".cjs", ".mjs", ".ts", ".cts", ".mts"];
15781
16180
  var DATA = ["", ".json", ".yml", ".yaml"];
@@ -15806,7 +16205,7 @@ var CONFIG_GROUPS = [
15806
16205
  files: [...variants("commitlint.config", SCRIPT), ...variants(".commitlintrc", [...DATA, ".js"])]
15807
16206
  }
15808
16207
  ];
15809
- var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(join38(env.root, file)))?.kind === "file");
16208
+ var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(join40(env.root, file)))?.kind === "file");
15810
16209
  var crowded = (env, groups, build) => groups.flatMap(({ name, files }) => {
15811
16210
  const found = present(env, files);
15812
16211
  return found.length > 1 ? [build(name, found.join(", "))] : [];
@@ -15950,9 +16349,9 @@ var manualLines = (ctx, report2) => report2.checks.some((item2) => item2.status
15950
16349
  var writeReport = (ctx, data, result) => {
15951
16350
  const env = doctorEnvFrom(ctx);
15952
16351
  const path = reportPath(env);
15953
- const text8 = reportMarkdown(env, ctx, toEnvelope("doctor", result));
16352
+ const text9 = reportMarkdown(env, ctx, toEnvelope("doctor", result));
15954
16353
  const dir = ctx.runtime.mkdir(toAbsolutePath(dirname7(path)));
15955
- const written = dir.ok ? ctx.runtime.writeAtomic(path, text8) : dir;
16354
+ const written = dir.ok ? ctx.runtime.writeAtomic(path, text9) : dir;
15956
16355
  if (!written.ok) return failureFrom(ctx.t, applyProblem({ path, detail: written.error }));
15957
16356
  const human2 = [...result.human ?? [], ctx.t("doctor.report.written", { path })];
15958
16357
  return { ...result, data: { ...data, reportPath: path }, human: human2 };
@@ -15984,14 +16383,14 @@ var doctorCommand = {
15984
16383
  };
15985
16384
 
15986
16385
  // src/gain/gain.ts
15987
- import { join as join39 } from "node:path";
16386
+ import { join as join41 } from "node:path";
15988
16387
  var GAIN_SCHEMA = "finitemonkey/gain@1";
15989
16388
  var DAY_MS2 = 864e5;
15990
16389
  var SAVE_WINDOW_MS = DAY_MS2;
15991
16390
  var readLog = (runtime, paths) => {
15992
- const text8 = runtime.readText(paths.log);
15993
- if (!text8.ok) return [];
15994
- return text8.value.split("\n").flatMap((line2) => {
16391
+ const text9 = runtime.readText(paths.log);
16392
+ if (!text9.ok) return [];
16393
+ return text9.value.split("\n").flatMap((line2) => {
15995
16394
  const parsed = parseJsonObject(line2);
15996
16395
  if (!parsed.ok || !isRecord(parsed.value)) return [];
15997
16396
  const { at, event } = parsed.value;
@@ -16000,9 +16399,9 @@ var readLog = (runtime, paths) => {
16000
16399
  });
16001
16400
  };
16002
16401
  var readGates = (runtime, paths) => {
16003
- const listed4 = runtime.listDir(toAbsolutePath(join39(paths.dir, "evidence")));
16402
+ const listed4 = runtime.listDir(toAbsolutePath(join41(paths.dir, "evidence")));
16004
16403
  const files = listed4.ok ? listed4.value.filter((name) => name.endsWith(".jsonl")) : [];
16005
- return files.flatMap((name) => readLedger(runtime, paths.evidence(name.replace(/\.jsonl$/, "")))).filter((entry2) => entry2.check === GATE_CHECK);
16404
+ return files.flatMap((name) => readLedger2(runtime, paths.evidence(name.replace(/\.jsonl$/, "")))).filter((entry2) => entry2.check === GATE_CHECK);
16006
16405
  };
16007
16406
  var countsIn = (log, gates, from, to) => {
16008
16407
  const inWindow = (at) => at >= from && at < to;
@@ -16341,17 +16740,6 @@ var checksResult = (t, schema, code, checks) => {
16341
16740
  };
16342
16741
  };
16343
16742
 
16344
- // src/commands/git-problems.ts
16345
- var notRepository = (dir) => ({
16346
- code: "not_a_git_repo",
16347
- messageKey: "error.not_a_git_repo",
16348
- params: { dir },
16349
- fix: "git init",
16350
- exitCode: ExitCode.NotConfigured
16351
- });
16352
- var gitProblem = (code, messageKey, detail, fix, exitCode = ExitCode.CheckFailed) => ({ code, messageKey, params: { detail }, fix, exitCode });
16353
- var repoOf2 = (ctx) => openRepo(ctx.runtime, ctx.cwd);
16354
-
16355
16743
  // src/commands/git-checks.ts
16356
16744
  var COMMIT_MSG_SCHEMA = "finitemonkey/commit-msg@1";
16357
16745
  var PUSH_CHECK_SCHEMA = "finitemonkey/push-check@1";
@@ -16372,15 +16760,15 @@ var commitMsgCommand = {
16372
16760
  usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: "<message-file>" })
16373
16761
  )
16374
16762
  );
16375
- const text8 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
16376
- if (!text8.ok)
16763
+ const text9 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
16764
+ if (!text9.ok)
16377
16765
  return Promise.resolve(
16378
16766
  failureFrom(
16379
16767
  ctx.t,
16380
16768
  usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: file })
16381
16769
  )
16382
16770
  );
16383
- const failures = commitMessageProblems(text8.value);
16771
+ const failures = commitMessageProblems(text9.value);
16384
16772
  const fix = "git commit --amend, using type(scope): subject and no AI attribution lines";
16385
16773
  return Promise.resolve(
16386
16774
  checksResult(ctx.t, COMMIT_MSG_SCHEMA, "githook_failed", [{ id: "commit-messages", failures, fix }])
@@ -16394,7 +16782,7 @@ var pushCheckCommand = {
16394
16782
  status: "ready",
16395
16783
  maxPositionals: 2,
16396
16784
  run: async (ctx, args2) => {
16397
- const repo = repoOf2(ctx);
16785
+ const repo = repoOf(ctx);
16398
16786
  if (repo === void 0) return failureFrom(ctx.t, notRepository(ctx.cwd));
16399
16787
  const piped = parsePushUpdates(ctx.io.stdin.isTTY === true ? "" : await readAll(ctx.io.stdin));
16400
16788
  const updates = piped.length > 0 ? piped : updatesFromEnv(ctx.env);
@@ -16409,7 +16797,7 @@ var ciGateCommand = {
16409
16797
  status: "ready",
16410
16798
  options: { base: { type: "string" } },
16411
16799
  run: (ctx, args2) => {
16412
- const repo = repoOf2(ctx);
16800
+ const repo = repoOf(ctx);
16413
16801
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
16414
16802
  const base = ciBase(ctx.runtime, repo, ctx.env, stringOption(args2.values, "base"));
16415
16803
  if (!baseExists(ctx.runtime, repo, base))
@@ -16495,7 +16883,7 @@ var mergeVerifyCommand = {
16495
16883
  options: { since: { type: "string" }, dropped: { type: "string" } },
16496
16884
  maxPositionals: 1,
16497
16885
  run: (ctx, args2) => {
16498
- const repo = repoOf2(ctx);
16886
+ const repo = repoOf(ctx);
16499
16887
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
16500
16888
  const dropped = stringOption(args2.values, "dropped");
16501
16889
  const targets2 = targetsOf2(ctx, repo, args2.positionals[0], stringOption(args2.values, "since"));
@@ -16518,7 +16906,7 @@ var mergeVerifyCommand = {
16518
16906
  var REFLOG_LIMIT = 300;
16519
16907
  var LIST_LIMIT = 50;
16520
16908
  var SHA = /^[0-9a-f]{40,64}$/;
16521
- var linesOf2 = (text8) => text8.split("\n").filter((line2) => line2.trim() !== "");
16909
+ var linesOf2 = (text9) => text9.split("\n").filter((line2) => line2.trim() !== "");
16522
16910
  var reflogLines = (runtime, repo) => {
16523
16911
  const result = runtime.git(["reflog", "show", "--all", `-n${String(REFLOG_LIMIT)}`, "--format=%H %gD"], repo.root);
16524
16912
  if (!result.ok) return [];
@@ -16607,7 +16995,7 @@ var rescueCommand = {
16607
16995
  status: "ready",
16608
16996
  options: { restore: { type: "string" }, branch: { type: "string" } },
16609
16997
  run: (ctx, args2) => {
16610
- const repo = repoOf2(ctx);
16998
+ const repo = repoOf(ctx);
16611
16999
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
16612
17000
  const sha = stringOption(args2.values, "restore");
16613
17001
  const branch = stringOption(args2.values, "branch");
@@ -16639,7 +17027,7 @@ var rescueCommand = {
16639
17027
  };
16640
17028
 
16641
17029
  // src/git/undo.ts
16642
- import { basename as basename3, dirname as dirname8, join as join40 } from "node:path";
17030
+ import { basename as basename3, dirname as dirname8, join as join42 } from "node:path";
16643
17031
  var INDEX_SUBJECT = "finitemonkey index:";
16644
17032
  var gitLine = (runtime, repo, args2) => {
16645
17033
  const result = runtime.git(args2, repo.root);
@@ -16682,7 +17070,7 @@ var findSnapshot = (views, wanted) => {
16682
17070
  };
16683
17071
  var stampOf2 = (iso) => iso.replace(/[:.]/g, "-");
16684
17072
  var worktreePath = (runtime, repo) => {
16685
- const base = join40(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
17073
+ const base = join42(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
16686
17074
  return toAbsolutePath(runtime.stat(toAbsolutePath(base)) === void 0 ? base : `${base}-2`);
16687
17075
  };
16688
17076
  var intoWorktree = (runtime, repo, view2) => {
@@ -16726,7 +17114,7 @@ var snapshotCommand = {
16726
17114
  status: "ready",
16727
17115
  options: { message: { type: "string", short: "m" } },
16728
17116
  run: (ctx, args2) => {
16729
- const repo = repoOf2(ctx);
17117
+ const repo = repoOf(ctx);
16730
17118
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
16731
17119
  const message = args2.values["message"];
16732
17120
  const op = typeof message === "string" && message.trim() !== "" ? message.trim() : "manual";
@@ -16759,7 +17147,7 @@ var undoCommand = {
16759
17147
  options: { list: { type: "boolean" }, "in-place": { type: "boolean" } },
16760
17148
  maxPositionals: 1,
16761
17149
  run: (ctx, args2) => {
16762
- const repo = repoOf2(ctx);
17150
+ const repo = repoOf(ctx);
16763
17151
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
16764
17152
  const views = snapshotViews(ctx.runtime, repo);
16765
17153
  if (args2.values["list"] === true) return Promise.resolve(listResult(ctx, views));
@@ -16807,7 +17195,7 @@ var GLOBAL_FLAGS = [
16807
17195
  { flag: "--help, -h", key: "flag.help" }
16808
17196
  ];
16809
17197
  var exitCodeHelp = (ctx) => EXIT_CODE_NAMES.map((name) => ({ code: ExitCode[name], name, summary: ctx.t(exitCodeDocs[ExitCode[name]]) }));
16810
- var pad = (text8, width) => text8.padEnd(Math.max(width, text8.length + 1));
17198
+ var pad = (text9, width) => text9.padEnd(Math.max(width, text9.length + 1));
16811
17199
  var describe2 = (ctx, spec2) => ({
16812
17200
  name: commandName(spec2),
16813
17201
  usage: spec2.usage,
@@ -16917,7 +17305,7 @@ var hookCommand = {
16917
17305
  };
16918
17306
 
16919
17307
  // src/commands/i18n-check.ts
16920
- import { join as join41 } from "node:path";
17308
+ import { join as join43 } from "node:path";
16921
17309
 
16922
17310
  // src/i18n/message.ts
16923
17311
  var ARG = /^\s*([a-zA-Z][a-zA-Z0-9_]*)\s*(?:,\s*(plural|select)\s*,)?/;
@@ -16949,10 +17337,10 @@ var parseBranches = (body2, inPlural) => {
16949
17337
  };
16950
17338
  var parseNodes = (source, inPlural) => {
16951
17339
  const nodes = [];
16952
- let text8 = "";
17340
+ let text9 = "";
16953
17341
  const flush2 = () => {
16954
- if (text8 !== "") nodes.push({ kind: "text", text: text8 });
16955
- text8 = "";
17342
+ if (text9 !== "") nodes.push({ kind: "text", text: text9 });
17343
+ text9 = "";
16956
17344
  };
16957
17345
  for (let index = 0; index < source.length; index += 1) {
16958
17346
  const char = source.charAt(index);
@@ -16962,7 +17350,7 @@ var parseNodes = (source, inPlural) => {
16962
17350
  continue;
16963
17351
  }
16964
17352
  if (char !== "{") {
16965
- text8 += char;
17353
+ text9 += char;
16966
17354
  continue;
16967
17355
  }
16968
17356
  const cursor = { source, index: index + 1 };
@@ -16970,18 +17358,18 @@ var parseNodes = (source, inPlural) => {
16970
17358
  const inner = end < 0 ? void 0 : source.slice(index + 1, end);
16971
17359
  const match = inner === void 0 ? null : ARG.exec(inner);
16972
17360
  if (inner === void 0 || match === null) {
16973
- text8 += char;
17361
+ text9 += char;
16974
17362
  continue;
16975
17363
  }
16976
17364
  const name = match[1] ?? "";
16977
17365
  const type = match[2] === "plural" || match[2] === "select" ? match[2] : void 0;
16978
17366
  if (type === void 0 && match[0].trim().length !== inner.trim().length) {
16979
- text8 += char;
17367
+ text9 += char;
16980
17368
  continue;
16981
17369
  }
16982
17370
  const branches = type === void 0 ? void 0 : parseBranches(inner.slice(match[0].length), type === "plural");
16983
17371
  if (type !== void 0 && branches === void 0) {
16984
- text8 += char;
17372
+ text9 += char;
16985
17373
  continue;
16986
17374
  }
16987
17375
  flush2();
@@ -17104,8 +17492,8 @@ var RELEASE_HINTS = [
17104
17492
  /\bFINITEMONKEY_[A-Z_]+/,
17105
17493
  /\b[A-Z][A-Z0-9_]{2,}=/
17106
17494
  ];
17107
- var releaseHints = (text8) => RELEASE_HINTS.flatMap((pattern) => {
17108
- const match = pattern.exec(text8);
17495
+ var releaseHints = (text9) => RELEASE_HINTS.flatMap((pattern) => {
17496
+ const match = pattern.exec(text9);
17109
17497
  return match === null ? [] : [match[0]];
17110
17498
  });
17111
17499
  var hintProblems = (lang, catalog) => Object.keys(catalog).filter((key) => MODEL_FACING.test(key)).sort().flatMap((key) => {
@@ -17143,10 +17531,10 @@ var unreadable = (dir, file, detail) => ({
17143
17531
  exitCode: ExitCode.NotConfigured
17144
17532
  });
17145
17533
  var readCatalog = (ctx, dir, lang) => {
17146
- const file = toAbsolutePath(join41(dir, `${lang}.json`));
17147
- const text8 = ctx.runtime.readText(file);
17148
- if (!text8.ok) return reject(unreadable(dir, file, text8.error));
17149
- const parsed = parseJsonObject(text8.value);
17534
+ const file = toAbsolutePath(join43(dir, `${lang}.json`));
17535
+ const text9 = ctx.runtime.readText(file);
17536
+ if (!text9.ok) return reject(unreadable(dir, file, text9.error));
17537
+ const parsed = parseJsonObject(text9.value);
17150
17538
  return parsed.ok ? parsed : reject(unreadable(dir, file, parsed.error));
17151
17539
  };
17152
17540
  var loadCatalogs = (ctx, dir) => {
@@ -17166,8 +17554,8 @@ var describeProblem = (ctx, problem4) => {
17166
17554
  return ` ${problem4.lang} ${problem4.key} ${kind}`;
17167
17555
  };
17168
17556
  var loadReview = (ctx, dir) => {
17169
- const text8 = ctx.runtime.readText(toAbsolutePath(join41(dir, "review.json")));
17170
- const parsed = text8.ok ? parseJsonObject(text8.value) : void 0;
17557
+ const text9 = ctx.runtime.readText(toAbsolutePath(join43(dir, "review.json")));
17558
+ const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
17171
17559
  if (parsed?.ok !== true) return void 0;
17172
17560
  const statuses = (lang) => {
17173
17561
  const value = parsed.value[lang];
@@ -17207,7 +17595,7 @@ var i18nCheckCommand = {
17207
17595
  };
17208
17596
 
17209
17597
  // src/history/look-back.ts
17210
- import { join as join42 } from "node:path";
17598
+ import { join as join44 } from "node:path";
17211
17599
 
17212
17600
  // src/history/patterns.ts
17213
17601
  import { posix as posix5 } from "node:path";
@@ -17312,8 +17700,8 @@ var addResults = (draft, blocks) => {
17312
17700
  let any = false;
17313
17701
  for (const block4 of blocks) {
17314
17702
  if (!isRecord(block4) || block4["type"] !== "tool_result" || typeof block4["tool_use_id"] !== "string") continue;
17315
- const text8 = textOf3(block4["content"]);
17316
- draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(text8), text: text8 });
17703
+ const text9 = textOf3(block4["content"]);
17704
+ draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(text9), text: text9 });
17317
17705
  any = true;
17318
17706
  }
17319
17707
  return any;
@@ -17343,7 +17731,7 @@ var parseTranscript = (jsonl) => {
17343
17731
  // src/history/look-back.ts
17344
17732
  var SAMPLES = 3;
17345
17733
  var projectLogKey = (root) => root.replace(/[^A-Za-z0-9-]/g, "-");
17346
- var projectLogDir = (runtime, root, env) => join42(env["CLAUDE_CONFIG_DIR"] ?? join42(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
17734
+ var projectLogDir = (runtime, root, env) => join44(env["CLAUDE_CONFIG_DIR"] ?? join44(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
17347
17735
  var lookBack = (runtime, root, env) => {
17348
17736
  const dir = projectLogDir(runtime, root, env);
17349
17737
  const listed4 = runtime.listDir(toAbsolutePath(dir));
@@ -17364,9 +17752,9 @@ var lookBack = (runtime, root, env) => {
17364
17752
  };
17365
17753
  let turns = 0;
17366
17754
  for (const name of files) {
17367
- const text8 = runtime.readText(toAbsolutePath(join42(dir, name)));
17368
- if (!text8.ok) continue;
17369
- for (const turn of parseTranscript(text8.value)) {
17755
+ const text9 = runtime.readText(toAbsolutePath(join44(dir, name)));
17756
+ if (!text9.ok) continue;
17757
+ for (const turn of parseTranscript(text9.value)) {
17370
17758
  turns += 1;
17371
17759
  for (const hit3 of hitsIn(turn)) {
17372
17760
  counts[hit3.pattern] += 1;
@@ -17414,7 +17802,7 @@ var walk2 = (a, b) => {
17414
17802
  }
17415
17803
  return ops;
17416
17804
  };
17417
- var splitLines = (text8) => text8 === "" ? [] : text8.replace(/\r?\n$/, "").split(/\r?\n/);
17805
+ var splitLines = (text9) => text9 === "" ? [] : text9.replace(/\r?\n$/, "").split(/\r?\n/);
17418
17806
  var sharedPrefix = (a, b) => {
17419
17807
  let count4 = 0;
17420
17808
  while (count4 < a.length && count4 < b.length && a[count4] === b[count4]) count4 += 1;
@@ -17468,11 +17856,18 @@ var itemWarning = (t, item2) => {
17468
17856
  return [{ code: "edited_file", message: t("install.warn.edited", { path: item2.path }) }];
17469
17857
  return [];
17470
17858
  };
17859
+ var sandboxWarnings = (t, plan) => {
17860
+ if (plan.sandbox === "unsupported")
17861
+ return [{ code: "sandbox_unsupported", message: t("install.warn.sandbox_unsupported") }];
17862
+ if (plan.sandbox === "offer") return [{ code: "sandbox_offer", message: t("install.warn.sandbox_offer") }];
17863
+ return [];
17864
+ };
17471
17865
  var planWarnings = (t, plan) => [
17472
17866
  ...plan.detection.gitRoot ? [] : [{ code: "not_git", message: t("install.warn.not_git") }],
17473
17867
  ...plan.detection.claude.failClosed ? [] : [{ code: "fail_open", message: t("install.warn.fail_open", { version: plan.detection.claude.version ?? "?" }) }],
17474
17868
  ...plan.detection.location.wslMount ? [{ code: "wsl_mount", message: t("install.warn.wsl_mount", { path: plan.detection.root }) }] : [],
17475
17869
  ...plan.detection.location.synced ? [{ code: "synced_folder", message: t("install.warn.synced_folder", { path: plan.detection.root }) }] : [],
17870
+ ...sandboxWarnings(t, plan),
17476
17871
  ...plan.detection.authorities.map((authority) => authorityWarning(t, authority)),
17477
17872
  ...plan.items.flatMap((item2) => itemWarning(t, item2))
17478
17873
  ];
@@ -17491,7 +17886,7 @@ var planData = (t, plan, mode, applied, backup) => ({
17491
17886
  diff: planDiff(plan),
17492
17887
  ...backup === void 0 ? {} : { backup }
17493
17888
  });
17494
- var pad2 = (text8, width) => text8.padEnd(width);
17889
+ var pad2 = (text9, width) => text9.padEnd(width);
17495
17890
  var planLines = (t, data, withDiff) => [
17496
17891
  t("install.plan.title", { root: data.root, preset: data.preset }),
17497
17892
  ...data.actions.map(({ path, action }) => ` ${pad2(action, 13)}${path}`),
@@ -17513,7 +17908,8 @@ var flagsFor = (request, preset) => [
17513
17908
  `finitemonkey ${request.mode} --yes`,
17514
17909
  ...request.mode === "init" && preset !== "neutral" ? [`--preset ${preset}`] : [],
17515
17910
  ...request.bin === void 0 ? [] : [`--bin "${request.bin}"`],
17516
- ...request.marketplace ? ["--marketplace"] : []
17911
+ ...request.marketplace ? ["--marketplace"] : [],
17912
+ ...request.sandbox === true ? ["--sandbox"] : []
17517
17913
  ].join(" ");
17518
17914
  var presentPlan = (ctx, request, plan) => {
17519
17915
  const data = planData(ctx.t, plan, request.mode, false, void 0);
@@ -17539,7 +17935,7 @@ var runInstall = (ctx, request, presetFallback) => {
17539
17935
  const { project, gitRoot: gitRoot2 } = openProject(ctx);
17540
17936
  const previous = readPreviousLock(project);
17541
17937
  if (!previous.ok) return failureFrom(ctx.t, planProblem(previous.error));
17542
- const input = planInputFor(ctx, preset, request.bin, request.marketplace);
17938
+ const input = planInputFor(ctx, preset, request.bin, request.marketplace, request.sandbox === true);
17543
17939
  const plan = buildPlan(project, detect(project, gitRoot2, input), input, previous.value);
17544
17940
  if (!plan.ok) return failureFrom(ctx.t, planProblem(plan.error));
17545
17941
  if (ctx.dryRun || !ctx.yes || !needsWrite(plan.value)) return presentPlan(ctx, request, plan.value);
@@ -17550,6 +17946,7 @@ var initRequest = (args2) => ({
17550
17946
  preset: stringOption(args2.values, "preset"),
17551
17947
  bin: stringOption(args2.values, "bin"),
17552
17948
  marketplace: args2.values["marketplace"] === true,
17949
+ sandbox: args2.values["sandbox"] === true,
17553
17950
  showDiff: false
17554
17951
  });
17555
17952
  var withLookBack = (ctx, result) => {
@@ -17566,12 +17963,13 @@ var withLookBack = (ctx, result) => {
17566
17963
  var initCommand = {
17567
17964
  path: ["init"],
17568
17965
  summaryKey: "command.init.summary",
17569
- usage: "finitemonkey init [--preset neutral|gabriel] [--bin <path>] [--marketplace] [--look-back] [--dry-run] [--yes]",
17966
+ usage: "finitemonkey init [--preset neutral|gabriel] [--bin <path>] [--marketplace] [--sandbox] [--look-back] [--dry-run] [--yes]",
17570
17967
  status: "ready",
17571
17968
  options: {
17572
17969
  preset: { type: "string" },
17573
17970
  bin: { type: "string" },
17574
17971
  marketplace: { type: "boolean" },
17972
+ sandbox: { type: "boolean" },
17575
17973
  "look-back": { type: "boolean" }
17576
17974
  },
17577
17975
  run: (ctx, args2) => {
@@ -17616,7 +18014,7 @@ var applyAllows = (findings, allows, decided) => {
17616
18014
 
17617
18015
  // src/look/node-capture.ts
17618
18016
  import { mkdirSync as mkdirSync3 } from "node:fs";
17619
- import { join as join43 } from "node:path";
18017
+ import { join as join45 } from "node:path";
17620
18018
 
17621
18019
  // src/look/measure.ts
17622
18020
  var measurePage = (doc) => {
@@ -17627,8 +18025,8 @@ var measurePage = (doc) => {
17627
18025
  const walker = doc.createTreeWalker(doc.body, SHOW_TEXT);
17628
18026
  const keys = /* @__PURE__ */ new Set();
17629
18027
  for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
17630
- const text8 = (node.textContent ?? "").trim();
17631
- if (KEY2.test(text8) && !NOT_KEY.test(text8) || PLACEHOLDER2.test(text8)) keys.add(text8.slice(0, 80));
18028
+ const text9 = (node.textContent ?? "").trim();
18029
+ if (KEY2.test(text9) && !NOT_KEY.test(text9) || PLACEHOLDER2.test(text9)) keys.add(text9.slice(0, 80));
17632
18030
  }
17633
18031
  const small = [...doc.querySelectorAll("a, button, input, select, textarea, [role=button], [role=link]")].filter((element) => {
17634
18032
  const box = element.getBoundingClientRect();
@@ -17661,8 +18059,8 @@ var slugOf = (route) => {
17661
18059
  var watch = (page) => {
17662
18060
  const errors = [];
17663
18061
  const failed4 = [];
17664
- const push = (list4, text8) => {
17665
- if (list4.length < MAX_EVENTS) list4.push(text8.slice(0, 200));
18062
+ const push = (list4, text9) => {
18063
+ if (list4.length < MAX_EVENTS) list4.push(text9.slice(0, 200));
17666
18064
  };
17667
18065
  page.on("console", (message) => {
17668
18066
  if (message.type() === "error" && !RESOURCE_ERROR.test(message.text())) push(errors, message.text());
@@ -17692,7 +18090,7 @@ var captureOne = async (browser, request, route, viewport) => {
17692
18090
  const context = await browser.newContext({ viewport: { width: viewport, height: HEIGHT }, reducedMotion: "reduce" });
17693
18091
  const page = await context.newPage();
17694
18092
  const events = watch(page);
17695
- const path = join43(request.dir, `${slugOf(route)}-${String(viewport)}.png`);
18093
+ const path = join45(request.dir, `${slugOf(route)}-${String(viewport)}.png`);
17696
18094
  const loadError = await loadAndShoot(page, `${request.baseUrl}${route}`, path, request.timeoutMs);
17697
18095
  const measure = loadError === void 0 ? parseMeasure(await page.evaluate(MEASURE_SCRIPT)) : void 0;
17698
18096
  await context.close();
@@ -17709,7 +18107,7 @@ var captureAll = async (browser, request) => {
17709
18107
 
17710
18108
  // src/look/node-playwright.ts
17711
18109
  import { createRequire } from "node:module";
17712
- import { join as join44 } from "node:path";
18110
+ import { join as join46 } from "node:path";
17713
18111
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
17714
18112
  var NAMES = ["playwright", "playwright-core"];
17715
18113
  var isPlaywright = (value) => isRecord(value) && isRecord(value["chromium"]) && typeof value["chromium"]["launch"] === "function";
@@ -17724,7 +18122,7 @@ var tryLoad = async (base, name) => {
17724
18122
  }
17725
18123
  };
17726
18124
  var loadPlaywright = async (root) => {
17727
- for (const base of [join44(root, "package.json"), fileURLToPath3(import.meta.url)]) {
18125
+ for (const base of [join46(root, "package.json"), fileURLToPath3(import.meta.url)]) {
17728
18126
  for (const name of NAMES) {
17729
18127
  const found = await tryLoad(base, name);
17730
18128
  if (found !== void 0) return found;
@@ -17745,8 +18143,8 @@ var launchBrowser = async (playwright) => {
17745
18143
 
17746
18144
  // src/look/node-sheet.ts
17747
18145
  import { readFileSync as readFileSync2 } from "node:fs";
17748
- import { join as join45 } from "node:path";
17749
- var escapeHtml = (text8) => text8.replace(/[&<>"]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[char] ?? char);
18146
+ import { join as join47 } from "node:path";
18147
+ var escapeHtml = (text9) => text9.replace(/[&<>"]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[char] ?? char);
17750
18148
  var card = (result) => {
17751
18149
  const image = (() => {
17752
18150
  try {
@@ -17768,7 +18166,7 @@ var contactSheet = async (browser, results, dir) => {
17768
18166
  if (shown3.length === 0) return void 0;
17769
18167
  const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
17770
18168
  await page.setContent(`<!doctype html><style>${STYLE}</style>${shown3.map(card).join("")}`);
17771
- const path = join45(dir, "contact-sheet.png");
18169
+ const path = join47(dir, "contact-sheet.png");
17772
18170
  await page.screenshot({ path, fullPage: true });
17773
18171
  await page.close();
17774
18172
  return path;
@@ -18108,12 +18506,12 @@ var proseCheckCommand = {
18108
18506
  };
18109
18507
 
18110
18508
  // src/review/packet.ts
18111
- import { join as join46 } from "node:path";
18509
+ import { join as join48 } from "node:path";
18112
18510
  var REVIEW_STATE_FILE = ".finitemonkey/review/state.json";
18113
18511
  var FULL_ROUNDS = 3;
18114
18512
  var REVIEW_FILE_MAX_LINES = 200;
18115
18513
  var LEDGER_MAX_LINES = 100;
18116
- var statePath = (root) => toAbsolutePath(join46(root, REVIEW_STATE_FILE));
18514
+ var statePath = (root) => toAbsolutePath(join48(root, REVIEW_STATE_FILE));
18117
18515
  var readReviewState = (runtime, root) => {
18118
18516
  const raw = runtime.readText(statePath(root));
18119
18517
  const parsed = raw.ok ? parseJsonObject(raw.value) : void 0;
@@ -18125,7 +18523,7 @@ var git = (runtime, root, args2) => {
18125
18523
  const result = runtime.git(args2, root);
18126
18524
  return result.ok ? result.stdout : void 0;
18127
18525
  };
18128
- var lastLines = (text8, max) => text8.split("\n").slice(-max).join("\n");
18526
+ var lastLines = (text9, max) => text9.split("\n").slice(-max).join("\n");
18129
18527
  var section = (title, body2, empty) => `## ${title}
18130
18528
 
18131
18529
  ${body2 === void 0 || body2.trim() === "" ? empty : body2.trimEnd()}
@@ -18135,7 +18533,7 @@ var taskSections = (runtime, root) => {
18135
18533
  const active = readState(runtime, paths).active;
18136
18534
  if (active === void 0)
18137
18535
  return [section("Contract", void 0, "(no active task)"), section("Evidence ledger", void 0, "(none)")];
18138
- const contract = runtime.readText(toAbsolutePath(join46(root, taskRelative(active))));
18536
+ const contract = runtime.readText(toAbsolutePath(join48(root, taskRelative(active))));
18139
18537
  const ledger = runtime.readText(paths.evidence(active));
18140
18538
  return [
18141
18539
  section("Contract", contract.ok ? contract.value : void 0, "(contract file missing)"),
@@ -18143,8 +18541,8 @@ var taskSections = (runtime, root) => {
18143
18541
  ];
18144
18542
  };
18145
18543
  var reviewFile = (runtime, root, base) => {
18146
- const text8 = git(runtime, root, ["show", `${base}:REVIEW.md`]);
18147
- return text8 === void 0 ? void 0 : text8.split("\n").slice(0, REVIEW_FILE_MAX_LINES).join("\n");
18544
+ const text9 = git(runtime, root, ["show", `${base}:REVIEW.md`]);
18545
+ return text9 === void 0 ? void 0 : text9.split("\n").slice(0, REVIEW_FILE_MAX_LINES).join("\n");
18148
18546
  };
18149
18547
  var nextState = (previous, base, head) => {
18150
18548
  if (previous?.base !== base) return { base, rounds: 1, lastHead: head };
@@ -18157,7 +18555,7 @@ var buildPacket = (runtime, root, base, head) => {
18157
18555
  const range = `${from}...${head}`;
18158
18556
  const diff = git(runtime, root, ["diff", "--no-color", "--no-ext-diff", range, "--", ".", ":(exclude).finitemonkey"]);
18159
18557
  const owners = reviewFile(runtime, root, base);
18160
- const text8 = [
18558
+ const text9 = [
18161
18559
  `# Review packet (round ${String(state.rounds)}, ${range})`,
18162
18560
  "",
18163
18561
  ...state.rounds > FULL_ROUNDS ? ["Only the diff since the last review round is included.", ""] : [],
@@ -18165,10 +18563,10 @@ var buildPacket = (runtime, root, base, head) => {
18165
18563
  ...taskSections(runtime, root),
18166
18564
  ...owners === void 0 ? [] : [section("REVIEW.md (base branch)", owners, "")]
18167
18565
  ].join("\n");
18168
- runtime.mkdir(toAbsolutePath(join46(root, ".finitemonkey", "review")));
18566
+ runtime.mkdir(toAbsolutePath(join48(root, ".finitemonkey", "review")));
18169
18567
  runtime.writeAtomic(statePath(root), `${JSON.stringify(state, null, 2)}
18170
18568
  `);
18171
- return { round: state.rounds, base, head, range, text: text8 };
18569
+ return { round: state.rounds, base, head, range, text: text9 };
18172
18570
  };
18173
18571
 
18174
18572
  // src/commands/review.ts
@@ -18180,7 +18578,7 @@ var reviewPacketCommand = {
18180
18578
  status: "ready",
18181
18579
  options: { base: { type: "string" } },
18182
18580
  run: (ctx, args2) => {
18183
- const repo = repoOf2(ctx);
18581
+ const repo = repoOf(ctx);
18184
18582
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
18185
18583
  const base = stringOption(args2.values, "base") ?? repoFacts(ctx.runtime, repo).defaultBranch();
18186
18584
  const resolved = ctx.runtime.git(["rev-parse", "--verify", "--quiet", `${base}^{commit}`], repo.root);
@@ -18320,7 +18718,7 @@ var secretsAcceptCommand = {
18320
18718
  };
18321
18719
 
18322
18720
  // src/secrets/runbooks.ts
18323
- import { dirname as dirname9, join as join47 } from "node:path";
18721
+ import { dirname as dirname9, join as join49 } from "node:path";
18324
18722
  var PROVIDER_OF_RULE = {
18325
18723
  "aws-access-token": "aws",
18326
18724
  "aws-secret-access-key": "aws",
@@ -18342,13 +18740,13 @@ var RUNBOOKS_URL = "https://github.com/gabrielvieiraf/finitemonkey/blob/main/run
18342
18740
  var runbookProvider = (rule) => PROVIDER_OF_RULE[rule] ?? "generic";
18343
18741
  var runbookLink = (runtime, rule) => {
18344
18742
  const file = `${runbookProvider(rule)}.md`;
18345
- const local = toAbsolutePath(join47(dirname9(dirname9(runtime.selfPath)), "runbooks", file));
18743
+ const local = toAbsolutePath(join49(dirname9(dirname9(runtime.selfPath)), "runbooks", file));
18346
18744
  return runtime.stat(local)?.kind === "file" ? local : `${RUNBOOKS_URL}/${file}`;
18347
18745
  };
18348
18746
 
18349
18747
  // src/commands/secrets-incident.ts
18350
18748
  var INCIDENT_ACTIONS = ["list", "rotated", "close", "purge"];
18351
- var USAGE = "finitemonkey secrets incident [list | rotated <id> | close <id> | purge <id>]";
18749
+ var USAGE2 = "finitemonkey secrets incident [list | rotated <id> | close <id> | purge <id>]";
18352
18750
  var TARGET = { rotated: "rotacionado", close: "fechado" };
18353
18751
  var PURGE_STEPS = [
18354
18752
  "secrets.purge.step1",
@@ -18406,7 +18804,7 @@ var move = (ctx, root, incident, action) => {
18406
18804
  if (!canMove(incident.state, to))
18407
18805
  return action === "close" ? notRotated(ctx, incident) : fail2(
18408
18806
  ctx,
18409
- usageProblem("invalid_arguments", "secrets.error.bad_transition", USAGE, {
18807
+ usageProblem("invalid_arguments", "secrets.error.bad_transition", USAGE2, {
18410
18808
  id: incident.id,
18411
18809
  from: incident.state,
18412
18810
  to
@@ -18429,7 +18827,7 @@ var actionOf = (raw) => INCIDENT_ACTIONS.find((action) => action === (raw ?? "li
18429
18827
  var secretsIncidentCommand = {
18430
18828
  path: ["secrets", "incident"],
18431
18829
  summaryKey: "secrets.command.incident",
18432
- usage: USAGE,
18830
+ usage: USAGE2,
18433
18831
  status: "ready",
18434
18832
  maxPositionals: 2,
18435
18833
  run: (ctx, args2) => {
@@ -18439,7 +18837,7 @@ var secretsIncidentCommand = {
18439
18837
  return Promise.resolve(
18440
18838
  fail2(
18441
18839
  ctx,
18442
- usageProblem("invalid_arguments", "error.invalid_arguments", USAGE, { detail: args2.positionals[0] ?? "" })
18840
+ usageProblem("invalid_arguments", "error.invalid_arguments", USAGE2, { detail: args2.positionals[0] ?? "" })
18443
18841
  )
18444
18842
  );
18445
18843
  if (action === "list") return Promise.resolve(list2(ctx, root));
@@ -18511,7 +18909,7 @@ var liveOutcome = (result) => {
18511
18909
 
18512
18910
  // src/commands/secrets-live.ts
18513
18911
  var SECRETS_LIVE_SCHEMA = "finitemonkey/secrets-live@1";
18514
- var USAGE2 = `finitemonkey secrets live <${LIVE_PROVIDERS.join("|")}> --env <VARIABLE> [--send]`;
18912
+ var USAGE3 = `finitemonkey secrets live <${LIVE_PROVIDERS.join("|")}> --env <VARIABLE> [--send]`;
18515
18913
  var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
18516
18914
  var target = (provider) => {
18517
18915
  const probe = LIVE_PROBES[provider];
@@ -18542,7 +18940,7 @@ var send = (ctx, provider, variable, key) => {
18542
18940
  var secretsLiveCommand = {
18543
18941
  path: ["secrets", "live"],
18544
18942
  summaryKey: "secrets.command.live",
18545
- usage: USAGE2,
18943
+ usage: USAGE3,
18546
18944
  status: "ready",
18547
18945
  maxPositionals: 1,
18548
18946
  options: { env: { type: "string" }, send: { type: "boolean" } },
@@ -18553,7 +18951,7 @@ var secretsLiveCommand = {
18553
18951
  return Promise.resolve(
18554
18952
  failureFrom(
18555
18953
  ctx.t,
18556
- usageProblem("invalid_arguments", "error.invalid_arguments", USAGE2, {
18954
+ usageProblem("invalid_arguments", "error.invalid_arguments", USAGE3, {
18557
18955
  detail: provider === void 0 ? args2.positionals[0] ?? "<provider>" : "--env <VARIABLE>"
18558
18956
  })
18559
18957
  )
@@ -18563,7 +18961,7 @@ var secretsLiveCommand = {
18563
18961
  return Promise.resolve(
18564
18962
  failureFrom(
18565
18963
  ctx.t,
18566
- needsPersonProblem("secrets.error.person_only", `${USAGE2.replace(" [--send]", "")} --send`, {
18964
+ needsPersonProblem("secrets.error.person_only", `${USAGE3.replace(" [--send]", "")} --send`, {
18567
18965
  action: ctx.t("secrets.action.live")
18568
18966
  })
18569
18967
  )
@@ -18571,7 +18969,7 @@ var secretsLiveCommand = {
18571
18969
  const key = ctx.env[variable] ?? "";
18572
18970
  if (key === "")
18573
18971
  return Promise.resolve(
18574
- failureFrom(ctx.t, usageProblem("invalid_arguments", "secrets.live.no_value", USAGE2, { variable }))
18972
+ failureFrom(ctx.t, usageProblem("invalid_arguments", "secrets.live.no_value", USAGE3, { variable }))
18575
18973
  );
18576
18974
  return Promise.resolve(send(ctx, provider, variable, key));
18577
18975
  }
@@ -18740,7 +19138,7 @@ var gateLight = (runtime, paths) => {
18740
19138
  };
18741
19139
 
18742
19140
  // src/statusline/input.ts
18743
- var text7 = (value) => typeof value === "string" && value !== "" ? value : void 0;
19141
+ var text8 = (value) => typeof value === "string" && value !== "" ? value : void 0;
18744
19142
  var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
18745
19143
  var usedTokens = (usage) => {
18746
19144
  if (!isRecord(usage)) return void 0;
@@ -18760,8 +19158,8 @@ var parseStatusInput = (raw) => {
18760
19158
  const record3 = parsed.ok ? parsed.value : {};
18761
19159
  const workspace = isRecord(record3["workspace"]) ? record3["workspace"] : {};
18762
19160
  return {
18763
- sessionId: text7(record3["session_id"]),
18764
- dir: text7(workspace["project_dir"]) ?? text7(workspace["current_dir"]) ?? text7(record3["cwd"]),
19161
+ sessionId: text8(record3["session_id"]),
19162
+ dir: text8(workspace["project_dir"]) ?? text8(workspace["current_dir"]) ?? text8(record3["cwd"]),
18765
19163
  contextPercent: contextPercent(record3),
18766
19164
  raw
18767
19165
  };
@@ -18776,7 +19174,7 @@ var segmentText = (t, segment) => [
18776
19174
  ...segment.contextPercent === void 0 ? [] : [t("statusline.context", { percent: segment.contextPercent })]
18777
19175
  ].join(" · ");
18778
19176
  var ANSI3 = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`, "g");
18779
- var graphemes = (text8) => Array.from(new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text8), (part2) => part2.segment);
19177
+ var graphemes = (text9) => Array.from(new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text9), (part2) => part2.segment);
18780
19178
  var fit = (line2, columns) => {
18781
19179
  if (columns === void 0) return line2;
18782
19180
  const visible = graphemes(line2.replace(ANSI3, ""));
@@ -18837,16 +19235,16 @@ var cached2 = (runtime, paths) => {
18837
19235
  const raw = readJsonFile(runtime, paths.cache(WRAP_CACHE))?.["sessions"];
18838
19236
  return isRecord(raw) ? Object.fromEntries(Object.entries(raw).filter((entry2) => typeof entry2[1] === "string")) : {};
18839
19237
  };
18840
- var remember = (runtime, paths, session, text8) => {
18841
- const sessions = Object.entries({ ...cached2(runtime, paths), [session]: text8 }).slice(-KEEP3);
19238
+ var remember = (runtime, paths, session, text9) => {
19239
+ const sessions = Object.entries({ ...cached2(runtime, paths), [session]: text9 }).slice(-KEEP3);
18842
19240
  writeJsonFile(runtime, paths.cache(WRAP_CACHE), { sessions: Object.fromEntries(sessions) });
18843
19241
  };
18844
19242
  var runWrapped = async (runtime, paths, request, shell = nodeShell) => {
18845
19243
  const result = await shell(request.command, request.input, request.cwd, request.timeoutMs);
18846
19244
  if (result.text !== void 0) {
18847
- const text8 = result.text.replace(/\s+$/, "");
18848
- if (paths !== void 0) remember(runtime, paths, request.session, text8);
18849
- return text8;
19245
+ const text9 = result.text.replace(/\s+$/, "");
19246
+ if (paths !== void 0) remember(runtime, paths, request.session, text9);
19247
+ return text9;
18850
19248
  }
18851
19249
  return paths === void 0 ? "" : cached2(runtime, paths)[request.session] ?? "";
18852
19250
  };
@@ -18855,8 +19253,8 @@ var runWrapped = async (runtime, paths, request, shell = nodeShell) => {
18855
19253
  var STDIN_MS = 300;
18856
19254
  var WRAP_MS = 700;
18857
19255
  var wrapOf = (ctx, root) => {
18858
- const text8 = ctx.runtime.readText(toAbsolutePath(`${root}/${LOCK_FILE}`));
18859
- const lock = text8.ok ? parseLock(text8.value) : void 0;
19256
+ const text9 = ctx.runtime.readText(toAbsolutePath(`${root}/${LOCK_FILE}`));
19257
+ const lock = text9.ok ? parseLock(text9.value) : void 0;
18860
19258
  if (!lock?.ok) return void 0;
18861
19259
  return lock.value.entries.find((entry2) => entry2.added?.statusLine !== void 0)?.added?.statusLine?.wrap ?? void 0;
18862
19260
  };
@@ -18902,7 +19300,7 @@ var statuslineCommand = {
18902
19300
  };
18903
19301
 
18904
19302
  // src/task/create.ts
18905
- import { join as join48 } from "node:path";
19303
+ import { join as join50 } from "node:path";
18906
19304
 
18907
19305
  // src/core/yaml/write.ts
18908
19306
  var PLAIN_SAFE = /^[A-Za-z_./][A-Za-z0-9_./ ()-]*$/;
@@ -18991,10 +19389,10 @@ var explicitChecks = (draft) => [
18991
19389
  var draftChecks = (runtime, paths, draft) => {
18992
19390
  const disk = {
18993
19391
  read: (relative11) => {
18994
- const text8 = runtime.readText(toAbsolutePath(join48(paths.root, relative11)));
18995
- return text8.ok ? text8.value : void 0;
19392
+ const text9 = runtime.readText(toAbsolutePath(join50(paths.root, relative11)));
19393
+ return text9.ok ? text9.value : void 0;
18996
19394
  },
18997
- exists: (relative11) => runtime.stat(toAbsolutePath(join48(paths.root, relative11))) !== void 0
19395
+ exists: (relative11) => runtime.stat(toAbsolutePath(join50(paths.root, relative11))) !== void 0
18998
19396
  };
18999
19397
  const detected = detectChecks(disk);
19000
19398
  const base = draft.runs.length > 0 || draft.tests !== void 0 ? explicitChecks(draft) : detected;
@@ -19009,7 +19407,7 @@ var draftChecks = (runtime, paths, draft) => {
19009
19407
  { id: "merge", kind: "merge" },
19010
19408
  { id: "callers", kind: "callers" },
19011
19409
  ...draft.paths.length > 0 || draft.budget !== void 0 ? [{ id: "diff", kind: "diff" }] : [],
19012
- ...runtime.stat(toAbsolutePath(join48(paths.root, LOOK_CONFIG))) === void 0 ? [] : [{ id: "look", kind: "look" }],
19410
+ ...runtime.stat(toAbsolutePath(join50(paths.root, LOOK_CONFIG))) === void 0 ? [] : [{ id: "look", kind: "look" }],
19013
19411
  ...draft.git.length > 0 ? [{ id: "git", kind: "git", require: draft.git }] : [],
19014
19412
  { id: "pr", kind: "pr", requireOpen: draft.git.includes("pr_open") },
19015
19413
  ...draft.manual.map((description, index) => ({
@@ -19035,19 +19433,19 @@ var createTask = (runtime, paths, draft) => {
19035
19433
  decisionsNeeded: [],
19036
19434
  implicit: false
19037
19435
  };
19038
- const text8 = stringifyYaml(contractYaml(shape));
19436
+ const text9 = stringifyYaml(contractYaml(shape));
19039
19437
  const valid = validateContract(contractYaml(shape));
19040
19438
  if (!valid.ok) return reject({ kind: "invalid", issues: valid.error });
19041
- const wrote = runtime.writeText(paths.task(draft.id), text8);
19439
+ const wrote = runtime.writeText(paths.task(draft.id), text9);
19042
19440
  if (!wrote.ok) return reject({ kind: "write", detail: wrote.error });
19043
19441
  const lock = readTrust(runtime, paths);
19044
19442
  writeTrust(runtime, paths, {
19045
19443
  ...lock,
19046
19444
  contracts: { ...lock.contracts, [draft.id]: contractHash(valid.value) },
19047
- approved: { ...lock.approved, [draft.id]: text8 }
19445
+ approved: { ...lock.approved, [draft.id]: text9 }
19048
19446
  });
19049
19447
  writeState(runtime, paths, { ...readState(runtime, paths), active: draft.id });
19050
- return accept({ contract: valid.value, text: text8 });
19448
+ return accept({ contract: valid.value, text: text9 });
19051
19449
  };
19052
19450
 
19053
19451
  // src/commands/task.ts
@@ -19198,7 +19596,7 @@ var taskDoneCommand = {
19198
19596
  };
19199
19597
 
19200
19598
  // src/install/githook-block.ts
19201
- var withoutHookBlock = (text8) => withoutManagedBlock(text8, { begin: BLOCK_BEGIN, end: BLOCK_END });
19599
+ var withoutHookBlock = (text9) => withoutManagedBlock(text9, { begin: BLOCK_BEGIN, end: BLOCK_END });
19202
19600
 
19203
19601
  // src/install/uninstall.ts
19204
19602
  var blockless = (kind, current2) => {
@@ -19207,8 +19605,12 @@ var blockless = (kind, current2) => {
19207
19605
  };
19208
19606
  var structural = (entry2, current2) => {
19209
19607
  if (entry2.kind === "gitignore" || entry2.kind === "markdown" || entry2.kind === "githook") {
19210
- const text8 = blockless(entry2.kind, current2);
19211
- return text8.trim() === "" && !entry2.existed ? void 0 : text8;
19608
+ const text9 = blockless(entry2.kind, current2);
19609
+ return text9.trim() === "" && !entry2.existed ? void 0 : text9;
19610
+ }
19611
+ if (entry2.kind === "sandbox") {
19612
+ const text9 = withoutSandbox(current2, entry2.sandbox ?? {});
19613
+ return text9 === void 0 && entry2.existed ? "{}\n" : text9;
19212
19614
  }
19213
19615
  const parsed = parseSettings(current2);
19214
19616
  if (!parsed.ok || entry2.added === void 0) return current2;
@@ -19414,34 +19816,35 @@ var REGISTRY = [
19414
19816
  lookCommand,
19415
19817
  reviewPacketCommand,
19416
19818
  statuslineCommand,
19819
+ critiqueVerifyCommand,
19417
19820
  versionCommand,
19418
19821
  helpCommand
19419
19822
  ];
19420
19823
 
19421
19824
  // src/i18n/lang-settings.ts
19422
- import { join as join49 } from "node:path";
19825
+ import { join as join51 } from "node:path";
19423
19826
  var read3 = (runtime, path) => {
19424
- const text8 = runtime.readText(toAbsolutePath(path));
19425
- return text8.ok ? text8.value : void 0;
19827
+ const text9 = runtime.readText(toAbsolutePath(path));
19828
+ return text9.ok ? text9.value : void 0;
19426
19829
  };
19427
19830
  var configText = (tree, ...path) => {
19428
19831
  const value = configAt(tree, ...path);
19429
19832
  return typeof value === "string" && value !== "" ? value : void 0;
19430
19833
  };
19431
- var languageIn = (text8) => {
19432
- if (text8 === void 0) return void 0;
19433
- const parsed = parseJsonObject(text8);
19834
+ var languageIn = (text9) => {
19835
+ if (text9 === void 0) return void 0;
19836
+ const parsed = parseJsonObject(text9);
19434
19837
  const value = parsed.ok && isRecord(parsed.value) ? parsed.value["language"] : void 0;
19435
19838
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
19436
19839
  };
19437
- var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join49(runtime.homeDir(), ".claude");
19840
+ var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join51(runtime.homeDir(), ".claude");
19438
19841
  var langSettings = (runtime, cwd, env, platform) => {
19439
19842
  const root = findGitRoot(cwd, runtime) ?? cwd;
19440
- const config = parseConfigYaml(read3(runtime, join49(root, CONFIG_FILE)) ?? "");
19843
+ const config = parseConfigYaml(read3(runtime, join51(root, CONFIG_FILE2)) ?? "");
19441
19844
  const claudeFiles = [
19442
- join49(root, ".claude", "settings.local.json"),
19443
- join49(root, ".claude", "settings.json"),
19444
- join49(claudeDir(runtime, env), "settings.json")
19845
+ join51(root, ".claude", "settings.local.json"),
19846
+ join51(root, ".claude", "settings.json"),
19847
+ join51(claudeDir(runtime, env), "settings.json")
19445
19848
  ];
19446
19849
  return {
19447
19850
  config: configText(config, "lang"),
@@ -19528,8 +19931,8 @@ var resultFromThrown = (thrown, t) => {
19528
19931
  };
19529
19932
  var block3 = (items) => items.length > 0 ? `${items.join("\n")}
19530
19933
  ` : "";
19531
- var write2 = (sink, text8) => {
19532
- if (text8 !== "") sink.write(maskText(text8));
19934
+ var write2 = (sink, text9) => {
19935
+ if (text9 !== "") sink.write(maskText(text9));
19533
19936
  };
19534
19937
  var render2 = (ctx, command, result) => {
19535
19938
  if (result.raw !== void 0) {
@@ -19598,9 +20001,9 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
19598
20001
  var UNKNOWN_VERSION = "0.0.0-unknown";
19599
20002
  var VERSION_SOURCES = ["../package.json", "../.claude-plugin/plugin.json"];
19600
20003
  var versionAt = (readText, moduleUrl, relative11) => {
19601
- const text8 = readText(toAbsolutePath(fileURLToPath4(new URL(relative11, moduleUrl))));
19602
- if (!text8.ok) return void 0;
19603
- const parsed = parseJson(text8.value);
20004
+ const text9 = readText(toAbsolutePath(fileURLToPath4(new URL(relative11, moduleUrl))));
20005
+ if (!text9.ok) return void 0;
20006
+ const parsed = parseJson(text9.value);
19604
20007
  if (!parsed.ok || !isRecord(parsed.value)) return void 0;
19605
20008
  const version = parsed.value["version"];
19606
20009
  return typeof version === "string" ? version : void 0;