finitemonkey 0.1.0-next.20 → 0.1.0-next.21

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 = (text6) => {
31
+ var parseJson = (text7) => {
32
32
  try {
33
- return accept(JSON.parse(text6));
33
+ return accept(JSON.parse(text7));
34
34
  } catch (error) {
35
35
  return reject(error instanceof Error ? error.message : String(error));
36
36
  }
37
37
  };
38
- var parseJsonObject = (text6) => {
39
- const parsed = parseJson(text6);
38
+ var parseJsonObject = (text7) => {
39
+ const parsed = parseJson(text7);
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, text6) => {
163
+ var nodeWriteText = (path, text7) => {
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, text6);
169
+ writeSync(fd, text7);
170
170
  fsyncSync(fd);
171
171
  } finally {
172
172
  closeSync(fd);
@@ -778,7 +778,7 @@ import { dirname as dirname3 } from "node:path";
778
778
 
779
779
  // src/install/hash.ts
780
780
  import { createHash } from "node:crypto";
781
- var hashText = (text6) => `sha256:${createHash("sha256").update(text6, "utf8").digest("hex")}`;
781
+ var hashText = (text7) => `sha256:${createHash("sha256").update(text7, "utf8").digest("hex")}`;
782
782
 
783
783
  // src/install/layout.ts
784
784
  var STATE_DIR = ".finitemonkey";
@@ -863,36 +863,36 @@ var fromRecord = (record3) => {
863
863
  }
864
864
  return accept({ schema, version, preset: known2, installedAt, updatedAt, createdDirs, entries });
865
865
  };
866
- var parseLock = (text6) => {
867
- const parsed = parseJsonObject(text6);
866
+ var parseLock = (text7) => {
867
+ const parsed = parseJsonObject(text7);
868
868
  return parsed.ok ? fromRecord(parsed.value) : reject(parsed.error);
869
869
  };
870
870
  var renderLock = (lock) => `${JSON.stringify(lock, null, 2)}
871
871
  `;
872
872
 
873
873
  // src/install/managed-block.ts
874
- var lineBreak = (text6) => text6.includes("\r\n") ? "\r\n" : "\n";
874
+ var lineBreak = (text7) => text7.includes("\r\n") ? "\r\n" : "\n";
875
875
  var blockRange = (lines3, markers) => {
876
876
  const start = lines3.findIndex((line2) => line2.trimEnd() === markers.begin);
877
877
  if (start < 0) return void 0;
878
878
  const end = lines3.findIndex((line2, index) => index > start && line2.trimEnd() === markers.end);
879
879
  return end < 0 ? void 0 : { start, end };
880
880
  };
881
- var withManagedBlock = (text6, markers, body2) => {
882
- const eol = text6 === void 0 ? "\n" : lineBreak(text6);
881
+ var withManagedBlock = (text7, markers, body2) => {
882
+ const eol = text7 === void 0 ? "\n" : lineBreak(text7);
883
883
  const block4 = [markers.begin, ...body2, markers.end];
884
- if (text6 === void 0 || text6 === "") return `${block4.join(eol)}${eol}`;
885
- const lines3 = text6.split(eol);
884
+ if (text7 === void 0 || text7 === "") return `${block4.join(eol)}${eol}`;
885
+ const lines3 = text7.split(eol);
886
886
  const range = blockRange(lines3, markers);
887
887
  if (range !== void 0) return [...lines3.slice(0, range.start), ...block4, ...lines3.slice(range.end + 1)].join(eol);
888
- const separator = text6.endsWith(eol) ? "" : eol;
889
- return `${text6}${separator}${block4.join(eol)}${eol}`;
888
+ const separator = text7.endsWith(eol) ? "" : eol;
889
+ return `${text7}${separator}${block4.join(eol)}${eol}`;
890
890
  };
891
- var withoutManagedBlock = (text6, markers) => {
892
- const eol = lineBreak(text6);
893
- const lines3 = text6.split(eol);
891
+ var withoutManagedBlock = (text7, markers) => {
892
+ const eol = lineBreak(text7);
893
+ const lines3 = text7.split(eol);
894
894
  const range = blockRange(lines3, markers);
895
- if (range === void 0) return text6;
895
+ if (range === void 0) return text7;
896
896
  return [...lines3.slice(0, range.start), ...lines3.slice(range.end + 1)].join(eol);
897
897
  };
898
898
 
@@ -989,7 +989,7 @@ var configTemplate = (preset, lang) => [
989
989
  `lang: ${lang}`,
990
990
  ""
991
991
  ].join("\n");
992
- var hasMarker = (text6) => text6.includes(GENERATED_MARKER);
992
+ var hasMarker = (text7) => text7.includes(GENERATED_MARKER);
993
993
 
994
994
  // src/install/hook-entries.ts
995
995
  var INSTALLED_HOOKS = [
@@ -1095,7 +1095,7 @@ var lefthookBody = (runner) => [
1095
1095
  ` run: ${cliCommand(runner, "push check {1}")}`,
1096
1096
  " use_stdin: true"
1097
1097
  ];
1098
- var definesHook = (text6) => GIT_HOOK_NAMES.some((hook) => new RegExp(`^${hook}:`, "m").test(withoutManagedBlock(text6, HOOK_MARKERS)));
1098
+ var definesHook = (text7) => GIT_HOOK_NAMES.some((hook) => new RegExp(`^${hook}:`, "m").test(withoutManagedBlock(text7, HOOK_MARKERS)));
1099
1099
  var lefthookItem = (project, runner, path) => {
1100
1100
  const before = project.read(path);
1101
1101
  const after = withManagedBlock(before, HOOK_MARKERS, lefthookBody(runner));
@@ -1120,8 +1120,8 @@ var preCommitBody = (runner, indent) => {
1120
1120
  ];
1121
1121
  };
1122
1122
  var TOP_KEY = /^[A-Za-z_][\w-]*:/;
1123
- var reposIsLast = (text6) => {
1124
- const keys = withoutManagedBlock(text6, HOOK_MARKERS).split(/\r?\n/).filter((line2) => TOP_KEY.test(line2));
1123
+ var reposIsLast = (text7) => {
1124
+ const keys = withoutManagedBlock(text7, HOOK_MARKERS).split(/\r?\n/).filter((line2) => TOP_KEY.test(line2));
1125
1125
  return keys.at(-1)?.startsWith("repos:") === true;
1126
1126
  };
1127
1127
  var preCommitItem = (project, runner) => {
@@ -1202,10 +1202,10 @@ var ensureTree = (tree, key) => {
1202
1202
  tree[key] = fresh3;
1203
1203
  return fresh3;
1204
1204
  };
1205
- var parseConfigYaml = (text6) => {
1205
+ var parseConfigYaml = (text7) => {
1206
1206
  const root = {};
1207
1207
  const stack = [{ indent: -1, tree: root, key: void 0 }];
1208
- for (const raw of text6.split(/\r?\n/)) {
1208
+ for (const raw of text7.split(/\r?\n/)) {
1209
1209
  const line2 = stripComment(raw).trimEnd();
1210
1210
  if (line2.trim() === "" || line2.trim() === "---") continue;
1211
1211
  const indent = line2.length - line2.trimStart().length;
@@ -1228,8 +1228,8 @@ var configMap = (tree, ...path) => {
1228
1228
  );
1229
1229
  };
1230
1230
  var loadProjectConfig = (runtime, root) => {
1231
- const text6 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE2)));
1232
- return text6.ok ? parseConfigYaml(text6.value) : {};
1231
+ const text7 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE2)));
1232
+ return text7.ok ? parseConfigYaml(text7.value) : {};
1233
1233
  };
1234
1234
 
1235
1235
  // src/policy/policy.ts
@@ -1247,8 +1247,8 @@ var DEFAULT_POLICY = {
1247
1247
  guards: {}
1248
1248
  };
1249
1249
  var pick = (options, value, fallback) => options.find((option) => option === value) ?? fallback;
1250
- var parsePolicy = (text6) => {
1251
- const tree = parseConfigYaml(text6);
1250
+ var parsePolicy = (text7) => {
1251
+ const tree = parseConfigYaml(text7);
1252
1252
  const raw = configAt(tree, "promoteAfter");
1253
1253
  const promoteAfter = typeof raw === "string" ? Number.parseInt(raw, 10) : Number.NaN;
1254
1254
  const guards = Object.fromEntries(
@@ -1267,23 +1267,23 @@ var parsePolicy = (text6) => {
1267
1267
  };
1268
1268
  var policyPath = (root) => toAbsolutePath(join3(root, POLICY_FILE));
1269
1269
  var loadPolicy = (runtime, root) => {
1270
- const text6 = runtime.readText(policyPath(root));
1271
- return text6.ok ? parsePolicy(text6.value) : DEFAULT_POLICY;
1270
+ const text7 = runtime.readText(policyPath(root));
1271
+ return text7.ok ? parsePolicy(text7.value) : DEFAULT_POLICY;
1272
1272
  };
1273
1273
 
1274
1274
  // src/install/agents-block.ts
1275
1275
  var MARKDOWN_MARKERS = { begin: MD_BLOCK_BEGIN, end: MD_BLOCK_END };
1276
1276
  var IMPORT_LINE = /^@AGENTS\.md[ \t]*$/m;
1277
- var withAgentsBlock = (text6, runner, lang) => withManagedBlock(
1278
- text6,
1277
+ var withAgentsBlock = (text7, runner, lang) => withManagedBlock(
1278
+ text7,
1279
1279
  MARKDOWN_MARKERS,
1280
1280
  TEMPLATES[lang].agentsBlock({
1281
1281
  gate: cliCommand(runner, "gate --fast --json"),
1282
1282
  doctor: cliCommand(runner, "doctor --json")
1283
1283
  })
1284
1284
  );
1285
- var withAgentsImport = (text6) => text6 !== void 0 && IMPORT_LINE.test(withoutManagedBlock(text6, MARKDOWN_MARKERS)) ? text6 : withManagedBlock(text6, MARKDOWN_MARKERS, [AGENTS_IMPORT]);
1286
- var withoutMarkdownBlock = (text6) => withoutManagedBlock(text6, MARKDOWN_MARKERS);
1285
+ var withAgentsImport = (text7) => text7 !== void 0 && IMPORT_LINE.test(withoutManagedBlock(text7, MARKDOWN_MARKERS)) ? text7 : withManagedBlock(text7, MARKDOWN_MARKERS, [AGENTS_IMPORT]);
1286
+ var withoutMarkdownBlock = (text7) => withoutManagedBlock(text7, MARKDOWN_MARKERS);
1287
1287
 
1288
1288
  // src/install/ask-floor.ts
1289
1289
  var DEFAULT_BRANCHES = ["main", "master"];
@@ -1357,9 +1357,9 @@ var ruleFor = (raw) => {
1357
1357
  const path = line2.endsWith("/") ? `${body2}/**` : body2;
1358
1358
  return `Read(${anchored || body2.includes("/") ? "./" : ""}${path})`;
1359
1359
  };
1360
- var claudeignoreRules = (texts) => [
1360
+ var claudeignoreRules = (texts2) => [
1361
1361
  ...new Set(
1362
- texts.flatMap((text6) => (text6 ?? "").split("\n")).map(ruleFor).filter((rule) => rule !== void 0)
1362
+ texts2.flatMap((text7) => (text7 ?? "").split("\n")).map(ruleFor).filter((rule) => rule !== void 0)
1363
1363
  )
1364
1364
  ];
1365
1365
 
@@ -1392,12 +1392,12 @@ import { join as join4 } from "node:path";
1392
1392
 
1393
1393
  // src/install/json-text.ts
1394
1394
  var DEFAULT_STYLE = { indent: " ", finalNewline: true };
1395
- var styleOf = (text6) => {
1396
- const indented = /\n([ \t]+)\S/.exec(text6);
1397
- return { indent: indented?.[1] ?? DEFAULT_STYLE.indent, finalNewline: text6.endsWith("\n") };
1395
+ var styleOf = (text7) => {
1396
+ const indented = /\n([ \t]+)\S/.exec(text7);
1397
+ return { indent: indented?.[1] ?? DEFAULT_STYLE.indent, finalNewline: text7.endsWith("\n") };
1398
1398
  };
1399
1399
  var renderJson = (value, style) => `${JSON.stringify(value, null, style.indent)}${style.finalNewline ? "\n" : ""}`;
1400
- var parseSettings = (text6) => text6 === void 0 || text6.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text6);
1400
+ var parseSettings = (text7) => text7 === void 0 || text7.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text7);
1401
1401
  var asList = (value) => Array.isArray(value) ? value : [];
1402
1402
  var asRecord = (value) => isRecord(value) ? value : {};
1403
1403
  var setKey = (record3, key, value) => {
@@ -1434,8 +1434,8 @@ var WORKFLOW_DIRS = [
1434
1434
  { dir: "openspec", name: "openspec" }
1435
1435
  ];
1436
1436
  var fromPackageJson = (project) => {
1437
- const text6 = project.read("package.json");
1438
- const parsed = text6 === void 0 ? void 0 : parseJsonObject(text6);
1437
+ const text7 = project.read("package.json");
1438
+ const parsed = text7 === void 0 ? void 0 : parseJsonObject(text7);
1439
1439
  const field = parsed?.ok === true ? parsed.value["packageManager"] : void 0;
1440
1440
  if (typeof field !== "string") return void 0;
1441
1441
  return PACKAGE_MANAGERS.find(({ name }) => field.startsWith(`${name}@`))?.name;
@@ -1451,9 +1451,9 @@ var allHandlers = (settings) => {
1451
1451
  const hooks = settings["hooks"];
1452
1452
  return isRecord(hooks) ? Object.keys(hooks).flatMap((event) => handlersOf(settings, event)) : [];
1453
1453
  };
1454
- var settingsInfo = (text6) => {
1455
- if (text6 === void 0) return { exists: false, valid: true, userHooks: 0, ownHooks: 0 };
1456
- const parsed = text6.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text6);
1454
+ var settingsInfo = (text7) => {
1455
+ if (text7 === void 0) return { exists: false, valid: true, userHooks: 0, ownHooks: 0 };
1456
+ const parsed = text7.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text7);
1457
1457
  if (!parsed.ok) return { exists: true, valid: false, userHooks: 0, ownHooks: 0 };
1458
1458
  const handlers = allHandlers(parsed.value);
1459
1459
  const own = handlers.filter(isOwnHandler).length;
@@ -1467,8 +1467,8 @@ var pluginAuthorities = (settings, source) => {
1467
1467
  return LOOP_PLUGINS.includes(name) ? [{ kind: "loop-plugin", source, name }] : [];
1468
1468
  });
1469
1469
  };
1470
- var scopeAuthorities = (text6, source) => {
1471
- const parsed = text6 === void 0 ? void 0 : parseJsonObject(text6);
1470
+ var scopeAuthorities = (text7, source) => {
1471
+ const parsed = text7 === void 0 ? void 0 : parseJsonObject(text7);
1472
1472
  if (parsed?.ok !== true) return [];
1473
1473
  const stops = ["Stop", "SubagentStop"].flatMap(
1474
1474
  (event) => handlersOf(parsed.value, event).filter((handler) => !isOwnHandler(handler)).map((handler) => {
@@ -1482,8 +1482,8 @@ var userSettingsText = (project, env) => {
1482
1482
  const home = env["CLAUDE_CONFIG_DIR"] ?? env["HOME"] ?? env["USERPROFILE"];
1483
1483
  if (home === void 0) return void 0;
1484
1484
  const file = env["CLAUDE_CONFIG_DIR"] === void 0 ? join4(home, ".claude", "settings.json") : join4(home, "settings.json");
1485
- const text6 = project.runtime.readText(toAbsolutePath(file));
1486
- return text6.ok ? text6.value : void 0;
1485
+ const text7 = project.runtime.readText(toAbsolutePath(file));
1486
+ return text7.ok ? text7.value : void 0;
1487
1487
  };
1488
1488
  var detectAuthorities = (project, env) => [
1489
1489
  ...scopeAuthorities(project.read(SETTINGS_FILE), "project"),
@@ -1503,8 +1503,8 @@ var chooseRunner = (project, bin) => {
1503
1503
  // src/install/gitignore-block.ts
1504
1504
  var MARKERS = { begin: BLOCK_BEGIN, end: BLOCK_END };
1505
1505
  var body = () => [`${STATE_DIR}/*`, ...COMMITTED_STATE.map((path) => `!${STATE_DIR}/${path}`)];
1506
- var withBlock = (text6) => withManagedBlock(text6, MARKERS, body());
1507
- var withoutBlock = (text6) => withoutManagedBlock(text6, MARKERS);
1506
+ var withBlock = (text7) => withManagedBlock(text7, MARKERS, body());
1507
+ var withoutBlock = (text7) => withoutManagedBlock(text7, MARKERS);
1508
1508
 
1509
1509
  // src/core/semver.ts
1510
1510
  var SEMVER = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -1547,7 +1547,7 @@ var pendingMigrations = (migrations, from, to) => {
1547
1547
  };
1548
1548
  var migrateItem = (item2, previous, steps) => {
1549
1549
  if (steps.length === 0 || item2.before === void 0) return item2;
1550
- const migrated = steps.reduce((text6, step3) => step3(text6), item2.after);
1550
+ const migrated = steps.reduce((text7, step3) => step3(text7), item2.after);
1551
1551
  if (migrated === item2.after) return item2;
1552
1552
  const recorded = previous.entries.find((entry2) => entry2.path === item2.path)?.hash;
1553
1553
  if (recorded !== hashText(item2.before)) {
@@ -1688,9 +1688,9 @@ var unmergeSettings = (current2, added, recorded) => {
1688
1688
  var ownHandlersIn = (settings) => Object.values(asRecord(settings["hooks"])).flatMap((groups) => asList(groups)).flatMap((group) => asList(asRecord(group)["hooks"])).filter(isOwnHandler).length;
1689
1689
 
1690
1690
  // src/install/vendor.ts
1691
- var isBundle = (text6) => text6.startsWith("#!/usr/bin/env node") && text6.includes("finitemonkey");
1692
- var isOwnPackage = (text6) => {
1693
- const parsed = parseJsonObject(text6);
1691
+ var isBundle = (text7) => text7.startsWith("#!/usr/bin/env node") && text7.includes("finitemonkey");
1692
+ var isOwnPackage = (text7) => {
1693
+ const parsed = parseJsonObject(text7);
1694
1694
  return parsed.ok && parsed.value["name"] === "finitemonkey";
1695
1695
  };
1696
1696
  var vendoredPackage = (version) => `${JSON.stringify({ name: "finitemonkey", version, private: true, type: "module" }, null, 2)}
@@ -1721,9 +1721,9 @@ var detect = (project, gitRoot2, input) => ({
1721
1721
  location: locationOf(project.root, input.env)
1722
1722
  });
1723
1723
  var readPreviousLock = (project) => {
1724
- const text6 = project.read(LOCK_FILE);
1725
- if (text6 === void 0) return accept(void 0);
1726
- const parsed = parseLock(text6);
1724
+ const text7 = project.read(LOCK_FILE);
1725
+ if (text7 === void 0) return accept(void 0);
1726
+ const parsed = parseLock(text7);
1727
1727
  return parsed.ok ? accept(parsed.value) : reject({ kind: "lock", detail: parsed.error });
1728
1728
  };
1729
1729
  var actionFor = (before, after, existing) => {
@@ -1770,7 +1770,7 @@ var settingsItem = (project, detection, input, previous) => {
1770
1770
  });
1771
1771
  };
1772
1772
  var markdownItems = (project, runner, lang) => [
1773
- { path: AGENTS_FILE, render: (text6) => withAgentsBlock(text6, runner, lang) },
1773
+ { path: AGENTS_FILE, render: (text7) => withAgentsBlock(text7, runner, lang) },
1774
1774
  { path: CLAUDE_FILE, render: withAgentsImport }
1775
1775
  ].map(({ path, render: render3 }) => {
1776
1776
  const before = project.read(path);
@@ -1836,11 +1836,11 @@ var needsWrite = (plan) => hasChanges(plan) || plan.previous === void 0;
1836
1836
 
1837
1837
  // src/install/apply.ts
1838
1838
  var stamp = (iso) => iso.replace(/[:.]/g, "-");
1839
- var ensureParent = (project, relative9) => project.runtime.mkdir(project.abs(dirname3(relative9)));
1840
- var writeText = (project, relative9, text6) => {
1841
- const parent = ensureParent(project, relative9);
1842
- const written = parent.ok ? project.runtime.writeAtomic(project.abs(relative9), text6) : parent;
1843
- return written.ok ? accept(void 0) : reject({ path: relative9, detail: written.error });
1839
+ var ensureParent = (project, relative11) => project.runtime.mkdir(project.abs(dirname3(relative11)));
1840
+ var writeText = (project, relative11, text7) => {
1841
+ const parent = ensureParent(project, relative11);
1842
+ const written = parent.ok ? project.runtime.writeAtomic(project.abs(relative11), text7) : parent;
1843
+ return written.ok ? accept(void 0) : reject({ path: relative11, detail: written.error });
1844
1844
  };
1845
1845
  var restore = (project, items) => {
1846
1846
  for (const item2 of items) {
@@ -1981,17 +1981,17 @@ var detectLang = (flag2, env, settings = {}) => {
1981
1981
  // src/install/project.ts
1982
1982
  import { dirname as dirname4, join as join5 } from "node:path";
1983
1983
  var projectAt = (root, runtime) => {
1984
- const abs = (relative9) => toAbsolutePath(join5(root, ...relative9.split("/")));
1984
+ const abs = (relative11) => toAbsolutePath(join5(root, ...relative11.split("/")));
1985
1985
  return {
1986
1986
  root,
1987
1987
  runtime,
1988
1988
  abs,
1989
- read: (relative9) => {
1990
- const text6 = runtime.readText(abs(relative9));
1991
- return text6.ok ? text6.value : void 0;
1989
+ read: (relative11) => {
1990
+ const text7 = runtime.readText(abs(relative11));
1991
+ return text7.ok ? text7.value : void 0;
1992
1992
  },
1993
- exists: (relative9) => runtime.stat(abs(relative9)) !== void 0,
1994
- isDir: (relative9) => runtime.stat(abs(relative9))?.kind === "dir"
1993
+ exists: (relative11) => runtime.stat(abs(relative11)) !== void 0,
1994
+ isDir: (relative11) => runtime.stat(abs(relative11))?.kind === "dir"
1995
1995
  };
1996
1996
  };
1997
1997
  var findGitRoot = (start, runtime) => {
@@ -2061,8 +2061,8 @@ var invalidPreset = (raw) => problem("invalid_arguments", "error.invalid_preset"
2061
2061
  });
2062
2062
  var selfBundle = (ctx) => {
2063
2063
  if (!isBundlePath(ctx.runtime.selfPath)) return void 0;
2064
- const text6 = ctx.runtime.readText(ctx.runtime.selfPath);
2065
- return text6.ok ? text6.value : void 0;
2064
+ const text7 = ctx.runtime.readText(ctx.runtime.selfPath);
2065
+ return text7.ok ? text7.value : void 0;
2066
2066
  };
2067
2067
  var INHERITED = /* @__PURE__ */ new Set(["default", "config", "claude-settings"]);
2068
2068
  var planInputFor = (ctx, preset, bin, marketplace) => ({
@@ -2166,13 +2166,13 @@ var isPlaceholder = (value) => {
2166
2166
  var isTestOrLockPath = (path) => path !== void 0 && (LOCKFILES.test(toPosixPath(path)) || TEST_PATH.test(toPosixPath(path)));
2167
2167
 
2168
2168
  // src/secrets/entropy.ts
2169
- var shannonEntropy = (text6) => {
2170
- if (text6.length === 0) return 0;
2169
+ var shannonEntropy = (text7) => {
2170
+ if (text7.length === 0) return 0;
2171
2171
  const counts = /* @__PURE__ */ new Map();
2172
- for (const char of text6) counts.set(char, (counts.get(char) ?? 0) + 1);
2172
+ for (const char of text7) counts.set(char, (counts.get(char) ?? 0) + 1);
2173
2173
  let entropy = 0;
2174
2174
  for (const count4 of counts.values()) {
2175
- const p = count4 / text6.length;
2175
+ const p = count4 / text7.length;
2176
2176
  entropy -= p * Math.log2(p);
2177
2177
  }
2178
2178
  return entropy;
@@ -2180,7 +2180,7 @@ var shannonEntropy = (text6) => {
2180
2180
 
2181
2181
  // src/secrets/fingerprint.ts
2182
2182
  import { createHash as createHash2 } from "node:crypto";
2183
- var sha256Hex = (text6) => createHash2("sha256").update(text6).digest("hex");
2183
+ var sha256Hex = (text7) => createHash2("sha256").update(text7).digest("hex");
2184
2184
  var fingerprintOf = (secret) => sha256Hex(`finitemonkey:secret:${secret}`).slice(0, 16);
2185
2185
  var ALNUM = /[A-Za-z0-9]/g;
2186
2186
  var MIN_TAIL_LENGTH = 12;
@@ -2190,9 +2190,9 @@ var tailOf = (secret) => {
2190
2190
  };
2191
2191
 
2192
2192
  // src/secrets/lines.ts
2193
- var lineLocator = (text6) => {
2193
+ var lineLocator = (text7) => {
2194
2194
  const starts = [0];
2195
- for (let index = text6.indexOf("\n"); index !== -1; index = text6.indexOf("\n", index + 1)) starts.push(index + 1);
2195
+ for (let index = text7.indexOf("\n"); index !== -1; index = text7.indexOf("\n", index + 1)) starts.push(index + 1);
2196
2196
  return (offset) => {
2197
2197
  let low = 0;
2198
2198
  let high = starts.length - 1;
@@ -2221,8 +2221,8 @@ var defineRule = (spec2) => ({
2221
2221
  skipTestPaths: spec2.skipTestPaths ?? false,
2222
2222
  placeholders: spec2.placeholders ?? true
2223
2223
  });
2224
- var isWarning = (finding3) => finding3.severity === "warning";
2225
- var blocking = (findings) => findings.filter((finding3) => !isWarning(finding3));
2224
+ var isWarning = (finding4) => finding4.severity === "warning";
2225
+ var blocking = (findings) => findings.filter((finding4) => !isWarning(finding4));
2226
2226
 
2227
2227
  // src/secrets/rules/exposure.ts
2228
2228
  var ARGUMENT_CONTEXT = String.raw`(?:^|\n)[ \t-]*(?:command|entrypoint|CMD|ENTRYPOINT|RUN|ExecStart(?:Pre|Post)?|ExecReload)\b[^\n]*?`;
@@ -2486,12 +2486,12 @@ var hasRandomRun = (secret, length) => (secret.match(new RegExp(`[A-Za-z0-9]{${S
2486
2486
  );
2487
2487
  var accepted = (rule, secret) => secret.length >= rule.minLength && (!rule.requireDigit || DIGIT.test(secret)) && (rule.minRandomRun === 0 || hasRandomRun(secret, rule.minRandomRun)) && (!rule.placeholders || !isPlaceholder(secret)) && (rule.minEntropy === 0 || shannonEntropy(secret) >= rule.minEntropy);
2488
2488
  var NAME_CHAR = /[A-Za-z0-9_.-]/;
2489
- var nameAround = (text6, at) => {
2489
+ var nameAround = (text7, at) => {
2490
2490
  let start = at;
2491
- while (start > 0 && NAME_CHAR.test(text6.charAt(start - 1))) start -= 1;
2491
+ while (start > 0 && NAME_CHAR.test(text7.charAt(start - 1))) start -= 1;
2492
2492
  let end = at;
2493
- while (end < text6.length && NAME_CHAR.test(text6.charAt(end))) end += 1;
2494
- return text6.slice(start, end).toLowerCase();
2493
+ while (end < text7.length && NAME_CHAR.test(text7.charAt(end))) end += 1;
2494
+ return text7.slice(start, end).toLowerCase();
2495
2495
  };
2496
2496
  var ignoredName = (rule, match) => rule.ignoredNames.length > 0 && rule.ignoredNames.includes(nameAround(match.input, match.index));
2497
2497
  var spanOf = (rule, match) => {
@@ -2501,9 +2501,9 @@ var spanOf = (rule, match) => {
2501
2501
  const secret = match.input.slice(start, end);
2502
2502
  return accepted(rule, secret) ? { rule, start, end, secret } : void 0;
2503
2503
  };
2504
- var ruleSpans = (rule, text6) => {
2504
+ var ruleSpans = (rule, text7) => {
2505
2505
  const spans = [];
2506
- for (const match of text6.matchAll(rule.pattern)) {
2506
+ for (const match of text7.matchAll(rule.pattern)) {
2507
2507
  const span = spanOf(rule, match);
2508
2508
  if (span !== void 0) spans.push(span);
2509
2509
  }
@@ -2529,31 +2529,31 @@ var toFinding = (span, locate2) => {
2529
2529
  tail: tailOf(span.secret)
2530
2530
  };
2531
2531
  };
2532
- var scanText = (text6, options = {}) => {
2533
- if (text6.length === 0) return [];
2534
- const rules = applicable(options.rules ?? RULES, text6.toLowerCase(), options.path);
2532
+ var scanText = (text7, options = {}) => {
2533
+ if (text7.length === 0) return [];
2534
+ const rules = applicable(options.rules ?? RULES, text7.toLowerCase(), options.path);
2535
2535
  if (rules.length === 0) return [];
2536
2536
  const taken = [];
2537
2537
  for (const rule of rules) {
2538
- for (const span of ruleSpans(rule, text6)) if (!overlaps(taken, span)) taken.push(span);
2538
+ for (const span of ruleSpans(rule, text7)) if (!overlaps(taken, span)) taken.push(span);
2539
2539
  }
2540
2540
  if (taken.length === 0) return [];
2541
- const locate2 = lineLocator(text6);
2541
+ const locate2 = lineLocator(text7);
2542
2542
  return taken.sort((a, b) => a.start - b.start).map((span) => toFinding(span, locate2));
2543
2543
  };
2544
2544
 
2545
2545
  // src/mask/mask.ts
2546
- var maskToken = (finding3) => `«secret:${finding3.ruleId}:${finding3.tail}»`;
2547
- var maskText = (text6) => {
2548
- const findings = scanText(text6, { rules: CREDENTIAL_RULES });
2549
- if (findings.length === 0) return text6;
2546
+ var maskToken = (finding4) => `«secret:${finding4.ruleId}:${finding4.tail}»`;
2547
+ var maskText = (text7) => {
2548
+ const findings = scanText(text7, { rules: CREDENTIAL_RULES });
2549
+ if (findings.length === 0) return text7;
2550
2550
  let out = "";
2551
2551
  let cursor = 0;
2552
- for (const finding3 of findings) {
2553
- out += text6.slice(cursor, finding3.start) + maskToken(finding3);
2554
- cursor = finding3.end;
2552
+ for (const finding4 of findings) {
2553
+ out += text7.slice(cursor, finding4.start) + maskToken(finding4);
2554
+ cursor = finding4.end;
2555
2555
  }
2556
- return out + text6.slice(cursor);
2556
+ return out + text7.slice(cursor);
2557
2557
  };
2558
2558
  var maskDeep = (value) => {
2559
2559
  if (typeof value === "string") return maskText(value);
@@ -2771,14 +2771,14 @@ var toolVersion = (env, command) => {
2771
2771
  return result.ok ? result.stdout.trim().split("\n")[0] ?? "" : "not found";
2772
2772
  };
2773
2773
  var lastHookFailures = (env) => {
2774
- const text6 = env.runtime.readText(fmPaths(env.root).log);
2775
- if (!text6.ok) return [];
2776
- return text6.value.split("\n").filter((line2) => {
2774
+ const text7 = env.runtime.readText(fmPaths(env.root).log);
2775
+ if (!text7.ok) return [];
2776
+ return text7.value.split("\n").filter((line2) => {
2777
2777
  const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
2778
2778
  return parsed?.ok === true && isRecord(parsed.value) && parsed.value["event"] === HOOK_FAILURE_EVENT;
2779
2779
  }).slice(-MAX_FAILURES);
2780
2780
  };
2781
- var withoutHome = (text6, home) => home.length > 1 ? text6.split(home).join("~") : text6;
2781
+ var withoutHome = (text7, home) => home.length > 1 ? text7.split(home).join("~") : text7;
2782
2782
  var reportMarkdown = (env, ctx, envelope) => {
2783
2783
  const failures = lastHookFailures(env);
2784
2784
  const lines3 = [
@@ -2814,9 +2814,9 @@ var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
2814
2814
  `;
2815
2815
  var projectOwnsHooks = (ctx) => {
2816
2816
  const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
2817
- const text6 = ctx.runtime.readText(toAbsolutePath(join8(root, ...SETTINGS_FILE.split("/"))));
2818
- if (!text6.ok) return false;
2819
- const parsed = parseJsonObject(text6.value);
2817
+ const text7 = ctx.runtime.readText(toAbsolutePath(join8(root, ...SETTINGS_FILE.split("/"))));
2818
+ if (!text7.ok) return false;
2819
+ const parsed = parseJsonObject(text7.value);
2820
2820
  return parsed.ok && ownHandlersIn(parsed.value) > 0;
2821
2821
  };
2822
2822
 
@@ -2850,8 +2850,8 @@ var settingsPaths = (env) => {
2850
2850
  ];
2851
2851
  };
2852
2852
  var readSettings = (env) => settingsPaths(env).flatMap(({ scope: scope2, path }) => {
2853
- const text6 = env.runtime.readText(path);
2854
- const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
2853
+ const text7 = env.runtime.readText(path);
2854
+ const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
2855
2855
  return parsed?.ok === true ? [{ scope: scope2, path, settings: parsed.value }] : [];
2856
2856
  });
2857
2857
  var scopesWith = (all, test) => all.filter(({ settings }) => test(settings)).map(({ scope: scope2 }) => scope2);
@@ -3062,7 +3062,7 @@ var checkAuthVariables = ({ env, platform }) => {
3062
3062
  };
3063
3063
 
3064
3064
  // src/doctor/hooks.ts
3065
- import { join as join31 } from "node:path";
3065
+ import { join as join34 } from "node:path";
3066
3066
 
3067
3067
  // src/hooks/events.ts
3068
3068
  var HOOK_EVENTS = [
@@ -3137,7 +3137,7 @@ var editsOf = (input) => {
3137
3137
  });
3138
3138
  };
3139
3139
  var applyEdits = (before, edits) => edits.reduce(
3140
- (text6, edit) => edit.all ? text6.split(edit.oldText).join(edit.newText) : text6.replace(edit.oldText, () => edit.newText),
3140
+ (text7, edit) => edit.all ? text7.split(edit.oldText).join(edit.newText) : text7.replace(edit.oldText, () => edit.newText),
3141
3141
  before
3142
3142
  );
3143
3143
 
@@ -3154,9 +3154,9 @@ var textRecord = (value) => isRecord(value) ? Object.fromEntries(
3154
3154
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
3155
3155
  ) : {};
3156
3156
  var readJsonFile = (runtime, path) => {
3157
- const text6 = runtime.readText(path);
3158
- if (!text6.ok) return void 0;
3159
- const parsed = parseJsonObject(text6.value);
3157
+ const text7 = runtime.readText(path);
3158
+ if (!text7.ok) return void 0;
3159
+ const parsed = parseJsonObject(text7.value);
3160
3160
  return parsed.ok ? parsed.value : void 0;
3161
3161
  };
3162
3162
  var writeJsonFile = (runtime, path, value) => runtime.writeText(path, `${JSON.stringify({ schemaVersion: 1, ...value }, null, 2)}
@@ -3268,7 +3268,7 @@ var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
3268
3268
  import { posix } from "node:path";
3269
3269
 
3270
3270
  // src/shell/scan.ts
3271
- var cursorOf = (text6) => ({ text: text6, pos: 0, incomplete: false, subs: [] });
3271
+ var cursorOf = (text7) => ({ text: text7, pos: 0, incomplete: false, subs: [] });
3272
3272
  var peek = (cursor, offset = 0) => cursor.text.charAt(cursor.pos + offset);
3273
3273
  var atEnd = (cursor) => cursor.pos >= cursor.text.length;
3274
3274
  var skipQuoted = (cursor, quote) => {
@@ -3483,8 +3483,8 @@ var nextToken = (cursor, tokens, pending) => {
3483
3483
  tokens.push({ kind: "op", text: op });
3484
3484
  if (op === "\n") flushHeredocs(cursor, tokens, pending);
3485
3485
  };
3486
- var lexBash = (text6) => {
3487
- const cursor = cursorOf(text6.replace(/\r\n/g, "\n"));
3486
+ var lexBash = (text7) => {
3487
+ const cursor = cursorOf(text7.replace(/\r\n/g, "\n"));
3488
3488
  const tokens = [];
3489
3489
  const pending = [];
3490
3490
  skipBlank(cursor);
@@ -3632,9 +3632,9 @@ var next = (cursor, tokens, cmd) => {
3632
3632
  }
3633
3633
  const op = OPERATORS2.find((candidate) => cursor.text.startsWith(candidate, cursor.pos));
3634
3634
  if (op !== void 0 || cmd && peek(cursor) === "&") {
3635
- const text6 = op ?? "&";
3636
- cursor.pos += text6.length;
3637
- tokens.push({ kind: "op", text: text6 });
3635
+ const text7 = op ?? "&";
3636
+ cursor.pos += text7.length;
3637
+ tokens.push({ kind: "op", text: text7 });
3638
3638
  return;
3639
3639
  }
3640
3640
  if ((peek(cursor) === "&" || peek(cursor) === ".") && peek(cursor, 1) === " " && commandStart(tokens)) {
@@ -3643,8 +3643,8 @@ var next = (cursor, tokens, cmd) => {
3643
3643
  }
3644
3644
  tokens.push(word(cursor, cmd));
3645
3645
  };
3646
- var lexPowerShell = (text6, cmd = false) => {
3647
- const cursor = cursorOf(text6.replace(/\r\n/g, "\n"));
3646
+ var lexPowerShell = (text7, cmd = false) => {
3647
+ const cursor = cursorOf(text7.replace(/\r\n/g, "\n"));
3648
3648
  const tokens = [];
3649
3649
  const skip = () => {
3650
3650
  while (peek(cursor) === " " || peek(cursor) === " ") cursor.pos += 1;
@@ -3847,7 +3847,7 @@ var MAX_DEPTH = 8;
3847
3847
  var ASSIGNMENT2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s;
3848
3848
  var PS_ENV = /^\$env:([A-Za-z_][A-Za-z0-9_]*)=?(.*)$/is;
3849
3849
  var CHANGES_DIR = /* @__PURE__ */ new Set(["cd", "pushd", "set-location", "push-location"]);
3850
- var lex = (text6, language) => language === "bash" ? lexBash(text6) : lexPowerShell(text6, language === "cmd");
3850
+ var lex = (text7, language) => language === "bash" ? lexBash(text7) : lexPowerShell(text7, language === "cmd");
3851
3851
  var isWindowsAbsolute = (path) => /^[A-Za-z]:\//.test(path);
3852
3852
  var joinDir = (current2, target2) => {
3853
3853
  const next2 = toPosixPath(target2);
@@ -3918,21 +3918,21 @@ var visit = (raw, language, context, depth, collector) => {
3918
3918
  emit(result.stage, raw, language, context, collector);
3919
3919
  for (const extra of result.extra) emit(extra, raw, language, context, collector);
3920
3920
  };
3921
- var collect = (text6, language, context, depth, collector) => {
3921
+ var collect = (text7, language, context, depth, collector) => {
3922
3922
  if (depth > MAX_DEPTH) {
3923
3923
  collector.incomplete = true;
3924
3924
  return;
3925
3925
  }
3926
- const lexed = lex(text6, language);
3926
+ const lexed = lex(text7, language);
3927
3927
  if (lexed.incomplete) collector.incomplete = true;
3928
3928
  for (const substitution of lexed.substitutions) {
3929
3929
  collect(substitution, language, { ...context }, depth + 1, collector);
3930
3930
  }
3931
3931
  for (const raw of rawCommands(lexed.tokens)) visit(raw, language, context, depth, collector);
3932
3932
  };
3933
- var normalizeCommand = (text6, dialect = "bash") => {
3933
+ var normalizeCommand = (text7, dialect = "bash") => {
3934
3934
  const collector = { out: [], incomplete: false };
3935
- collect(text6, dialect, { env: {}, dir: void 0, via: [] }, 0, collector);
3935
+ collect(text7, dialect, { env: {}, dir: void 0, via: [] }, 0, collector);
3936
3936
  return { commands: collector.out, incomplete: collector.incomplete };
3937
3937
  };
3938
3938
  var dialectOf = (toolName3) => toolName3 === "PowerShell" ? "powershell" : "bash";
@@ -3944,7 +3944,7 @@ var segmentsOf = (command) => normalizeCommand(command).commands.map((item2) =>
3944
3944
 
3945
3945
  // src/guards/shell.ts
3946
3946
  var HEREDOC = /^<<[-<]?$/;
3947
- var parseShell = (text6) => normalizeCommand(text6).commands.map((command) => ({
3947
+ var parseShell = (text7) => normalizeCommand(text7).commands.map((command) => ({
3948
3948
  env: command.env,
3949
3949
  argv: command.program === "" ? [] : [command.program, ...command.args],
3950
3950
  redirects: command.redirects.filter((redirect) => !HEREDOC.test(redirect.op)).map((redirect) => redirect.target)
@@ -4097,9 +4097,9 @@ var writtenPaths = (command) => {
4097
4097
  if (name === "git" && GIT_WRITERS.has(operands2[0] ?? "")) return [...command.redirects, ...operands2.slice(1)];
4098
4098
  return command.redirects;
4099
4099
  };
4100
- var mentioned = (text6) => {
4101
- if (/\.finitemonkey[\\/]allow/.test(text6)) return "allow_store";
4102
- if (/\.finitemonkey|\.gitleaksignore/.test(text6)) return "policy";
4100
+ var mentioned = (text7) => {
4101
+ if (/\.finitemonkey[\\/]allow/.test(text7)) return "allow_store";
4102
+ if (/\.finitemonkey|\.gitleaksignore/.test(text7)) return "policy";
4103
4103
  return void 0;
4104
4104
  };
4105
4105
  var bashSelfTargets = (root, commandLine) => parseShell(commandLine).flatMap(
@@ -4300,8 +4300,8 @@ var compareConfig = (path, before, after, accepted2) => {
4300
4300
  // src/guards/tests/hook.ts
4301
4301
  var localPath = (root, path) => toPosixPath(relative3(root, toAbsolutePath(path, root)));
4302
4302
  var read = (input, path) => {
4303
- const text6 = input.runtime.readText(path);
4304
- return text6.ok ? text6.value : void 0;
4303
+ const text7 = input.runtime.readText(path);
4304
+ return text7.ok ? text7.value : void 0;
4305
4305
  };
4306
4306
  var proposed = (tool, args2, before) => {
4307
4307
  if (tool === "Write") return textField(args2, "content") ?? "";
@@ -4345,6 +4345,9 @@ var testsGuard = (input) => {
4345
4345
  return deny(t("gate.guard.tests.model", { detail: reason }), t("gate.guard.tests.human", { detail: reason }));
4346
4346
  };
4347
4347
 
4348
+ // src/look/reads.ts
4349
+ import { relative as relative4 } from "node:path";
4350
+
4348
4351
  // src/gate/hooks/common.ts
4349
4352
  import { dirname as dirname5, join as join12 } from "node:path";
4350
4353
  var DEFAULT_GATE_BUDGET_MS = 3e5;
@@ -4373,7 +4376,32 @@ var contextOutput = (event, additionalContext, systemMessage) => ({
4373
4376
  hookSpecificOutput: { hookEventName: event, additionalContext },
4374
4377
  ...systemMessage === void 0 ? {} : { systemMessage }
4375
4378
  });
4376
- var human = (text6) => `finitemonkey: ${text6}`;
4379
+ var human = (text7) => `finitemonkey: ${text7}`;
4380
+
4381
+ // src/look/reads.ts
4382
+ var READS_FILE = "look-reads.json";
4383
+ var KEEP = 500;
4384
+ var readLookReads = (runtime, paths) => {
4385
+ const raw = readJsonFile(runtime, paths.cache(READS_FILE))?.["reads"];
4386
+ return isRecord(raw) ? Object.fromEntries(Object.entries(raw).filter((entry2) => typeof entry2[1] === "string")) : {};
4387
+ };
4388
+ var recordLookRead = (runtime, paths, path) => {
4389
+ const entries = Object.entries({ ...readLookReads(runtime, paths), [path]: runtime.now() }).slice(-KEEP);
4390
+ return writeJsonFile(runtime, paths.cache(READS_FILE), { reads: Object.fromEntries(entries) }).ok;
4391
+ };
4392
+ var lookCaptureOf = (paths, file) => {
4393
+ const relativePath = toPosixPath(relative4(paths.root, toAbsolutePath(file, paths.root)));
4394
+ return relativePath.startsWith(".finitemonkey/look/") && relativePath.endsWith(".png") ? relativePath : void 0;
4395
+ };
4396
+ var lookReadTracker = (input) => {
4397
+ if (toolName(input.payload.raw) !== "Read") return { kind: "allow" };
4398
+ const file = textField(toolInput(input.payload.raw), "file_path");
4399
+ const paths = projectPaths(input);
4400
+ if (file === void 0 || paths === void 0) return { kind: "allow" };
4401
+ const capture2 = lookCaptureOf(paths, file);
4402
+ if (capture2 !== void 0) recordLookRead(input.runtime, paths, capture2);
4403
+ return { kind: "allow" };
4404
+ };
4377
4405
 
4378
4406
  // src/guards/release/markers.ts
4379
4407
  var LINE = /^\s*(?:finitemonkey|fm):\s+(allow|approve|trust)\s+(\S+)(?:\s+(.*))?$/i;
@@ -4410,8 +4438,8 @@ var parseMarkers = (prompt) => {
4410
4438
  // src/policy/pause.ts
4411
4439
  var PAUSE_FILE = "pause.json";
4412
4440
  var readPause = (runtime, paths) => {
4413
- const text6 = runtime.readText(paths.cache(PAUSE_FILE));
4414
- const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
4441
+ const text7 = runtime.readText(paths.cache(PAUSE_FILE));
4442
+ const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
4415
4443
  if (parsed?.ok !== true || !isRecord(parsed.value)) return void 0;
4416
4444
  const { sessionId, since, by } = parsed.value;
4417
4445
  if (typeof sessionId !== "string" || typeof since !== "string") return void 0;
@@ -4466,9 +4494,9 @@ var countIn = (value) => {
4466
4494
  if (Array.isArray(value)) return value.reduce((sum, item2) => sum + countIn(item2), 0);
4467
4495
  return isRecord(value) ? Object.values(value).reduce((sum, item2) => sum + countIn(item2), 0) : 0;
4468
4496
  };
4469
- var censusOf = (text6) => {
4470
- if (text6 === void 0) return { hooks: 0, disabled: false };
4471
- const parsed = parseJsonObject(text6);
4497
+ var censusOf = (text7) => {
4498
+ if (text7 === void 0) return { hooks: 0, disabled: false };
4499
+ const parsed = parseJsonObject(text7);
4472
4500
  if (!parsed.ok) return void 0;
4473
4501
  return { hooks: countIn(parsed.value["hooks"]), disabled: parsed.value["disableAllHooks"] === true };
4474
4502
  };
@@ -4478,8 +4506,8 @@ var settingsFiles = (paths, env) => [
4478
4506
  ...env["HOME"] === void 0 ? [] : [toAbsolutePath(join13(env["HOME"], ".claude", "settings.json"))]
4479
4507
  ];
4480
4508
  var read2 = (runtime, path) => {
4481
- const text6 = runtime.readText(path);
4482
- return text6.ok ? text6.value : void 0;
4509
+ const text7 = runtime.readText(path);
4510
+ return text7.ok ? text7.value : void 0;
4483
4511
  };
4484
4512
  var known = (runtime, paths) => {
4485
4513
  const raw = readJsonFile(runtime, paths.cache(CONFIG_FILE3))?.["hooks"];
@@ -4534,9 +4562,9 @@ var LOCKFILES2 = [
4534
4562
  ];
4535
4563
  var packageManager = (files) => LOCKFILES2.find(([file]) => files.exists(file))?.[1] ?? "npm";
4536
4564
  var scriptChecks = (files) => {
4537
- const text6 = files.read("package.json");
4538
- if (text6 === void 0) return [];
4539
- const parsed = parseJsonObject(text6);
4565
+ const text7 = files.read("package.json");
4566
+ if (text7 === void 0) return [];
4567
+ const parsed = parseJsonObject(text7);
4540
4568
  const scripts = parsed.ok ? parsed.value["scripts"] : void 0;
4541
4569
  if (!isRecord(scripts)) return [];
4542
4570
  const pm = packageManager(files);
@@ -4626,16 +4654,16 @@ var unescapeDouble = (body2) => {
4626
4654
  }
4627
4655
  return accept(out);
4628
4656
  };
4629
- var quotedEnd = (text6, start) => {
4630
- const quote = text6[start];
4631
- for (let index = start + 1; index < text6.length; index += 1) {
4632
- const char = text6[index];
4657
+ var quotedEnd = (text7, start) => {
4658
+ const quote = text7[start];
4659
+ for (let index = start + 1; index < text7.length; index += 1) {
4660
+ const char = text7[index];
4633
4661
  if (quote === '"' && char === "\\") {
4634
4662
  index += 1;
4635
4663
  continue;
4636
4664
  }
4637
4665
  if (char !== quote) continue;
4638
- if (quote === "'" && text6[index + 1] === "'") {
4666
+ if (quote === "'" && text7[index + 1] === "'") {
4639
4667
  index += 1;
4640
4668
  continue;
4641
4669
  }
@@ -4643,20 +4671,20 @@ var quotedEnd = (text6, start) => {
4643
4671
  }
4644
4672
  return -1;
4645
4673
  };
4646
- var parseQuoted = (text6) => {
4647
- const end = quotedEnd(text6, 0);
4648
- if (end !== text6.length - 1) return reject(`unterminated or trailing text after quoted string`);
4649
- const body2 = text6.slice(1, -1);
4650
- return text6.startsWith('"') ? unescapeDouble(body2) : accept(body2.replace(/''/g, "'"));
4674
+ var parseQuoted = (text7) => {
4675
+ const end = quotedEnd(text7, 0);
4676
+ if (end !== text7.length - 1) return reject(`unterminated or trailing text after quoted string`);
4677
+ const body2 = text7.slice(1, -1);
4678
+ return text7.startsWith('"') ? unescapeDouble(body2) : accept(body2.replace(/''/g, "'"));
4651
4679
  };
4652
4680
  var NUMBER = /^-?(0|[1-9]\d*)(\.\d+)?([eE][-+]?\d+)?$/;
4653
4681
  var UNSUPPORTED = /^[&*!|>%@`]/;
4654
- var parsePlain = (text6) => {
4655
- if (UNSUPPORTED.test(text6)) return reject(`unsupported YAML syntax "${text6.slice(0, 1)}"`);
4656
- if (text6 === "true" || text6 === "false") return accept(text6 === "true");
4657
- if (text6 === "null" || text6 === "~") return accept(null);
4658
- if (NUMBER.test(text6)) return accept(Number(text6));
4659
- return accept(text6);
4682
+ var parsePlain = (text7) => {
4683
+ if (UNSUPPORTED.test(text7)) return reject(`unsupported YAML syntax "${text7.slice(0, 1)}"`);
4684
+ if (text7 === "true" || text7 === "false") return accept(text7 === "true");
4685
+ if (text7 === "null" || text7 === "~") return accept(null);
4686
+ if (NUMBER.test(text7)) return accept(Number(text7));
4687
+ return accept(text7);
4660
4688
  };
4661
4689
 
4662
4690
  // src/core/yaml/flow.ts
@@ -4683,17 +4711,17 @@ var splitTop = (body2) => {
4683
4711
  if (last.trim() !== "" || parts.length > 0) parts.push(last);
4684
4712
  return accept(parts.map((part2) => part2.trim()));
4685
4713
  };
4686
- var splitKey = (text6) => {
4714
+ var splitKey = (text7) => {
4687
4715
  let index = 0;
4688
- if (text6.startsWith('"') || text6.startsWith("'")) {
4689
- index = quotedEnd(text6, 0) + 1;
4716
+ if (text7.startsWith('"') || text7.startsWith("'")) {
4717
+ index = quotedEnd(text7, 0) + 1;
4690
4718
  if (index <= 0) return void 0;
4691
4719
  }
4692
- for (; index < text6.length; index += 1) {
4693
- const char = text6[index];
4694
- if (char === "#" && text6[index - 1] === " ") return void 0;
4695
- if (char === ":" && (index + 1 === text6.length || text6[index + 1] === " ")) {
4696
- return { key: text6.slice(0, index).trim(), rest: text6.slice(index + 1).trim() };
4720
+ for (; index < text7.length; index += 1) {
4721
+ const char = text7[index];
4722
+ if (char === "#" && text7[index - 1] === " ") return void 0;
4723
+ if (char === ":" && (index + 1 === text7.length || text7[index + 1] === " ")) {
4724
+ return { key: text7.slice(0, index).trim(), rest: text7.slice(index + 1).trim() };
4697
4725
  }
4698
4726
  }
4699
4727
  return void 0;
@@ -4722,39 +4750,39 @@ var flowSeq = (parts) => {
4722
4750
  }
4723
4751
  return accept(out);
4724
4752
  };
4725
- var parseValue = (text6) => {
4726
- if (text6 === "") return accept(null);
4727
- const first = text6[0] ?? "";
4728
- if (first === '"' || first === "'") return parseQuoted(text6);
4753
+ var parseValue = (text7) => {
4754
+ if (text7 === "") return accept(null);
4755
+ const first = text7[0] ?? "";
4756
+ if (first === '"' || first === "'") return parseQuoted(text7);
4729
4757
  const close2 = OPEN[first];
4730
- if (close2 === void 0) return parsePlain(text6);
4731
- if (!text6.endsWith(close2)) return reject(`flow collection must end on the same line with "${close2}"`);
4732
- const parts = splitTop(text6.slice(1, -1));
4758
+ if (close2 === void 0) return parsePlain(text7);
4759
+ if (!text7.endsWith(close2)) return reject(`flow collection must end on the same line with "${close2}"`);
4760
+ const parts = splitTop(text7.slice(1, -1));
4733
4761
  if (!parts.ok) return parts;
4734
4762
  return first === "[" ? flowSeq(parts.value) : flowMap(parts.value);
4735
4763
  };
4736
4764
 
4737
4765
  // src/core/yaml/parse.ts
4738
4766
  var BOM = new RegExp(`^${String.fromCharCode(65279)}`);
4739
- var stripComment2 = (text6) => {
4740
- for (let index = 0; index < text6.length; index += 1) {
4741
- const char = text6[index];
4767
+ var stripComment2 = (text7) => {
4768
+ for (let index = 0; index < text7.length; index += 1) {
4769
+ const char = text7[index];
4742
4770
  if (char === '"' || char === "'") {
4743
- const end = quotedEnd(text6, index);
4744
- if (end < 0) return text6;
4771
+ const end = quotedEnd(text7, index);
4772
+ if (end < 0) return text7;
4745
4773
  index = end;
4746
- } else if (char === "#" && (index === 0 || text6[index - 1] === " ")) return text6.slice(0, index);
4774
+ } else if (char === "#" && (index === 0 || text7[index - 1] === " ")) return text7.slice(0, index);
4747
4775
  }
4748
- return text6;
4776
+ return text7;
4749
4777
  };
4750
4778
  var toLines = (source) => {
4751
4779
  const lines3 = [];
4752
4780
  const raw = source.replace(BOM, "").split(/\r?\n/);
4753
4781
  for (const [index, full] of raw.entries()) {
4754
- const text6 = stripComment2(full).trimEnd();
4755
- const body2 = text6.trimStart();
4782
+ const text7 = stripComment2(full).trimEnd();
4783
+ const body2 = text7.trimStart();
4756
4784
  if (body2 === "" || body2 === "---" && lines3.length === 0) continue;
4757
- const indentText = text6.slice(0, text6.length - body2.length);
4785
+ const indentText = text7.slice(0, text7.length - body2.length);
4758
4786
  if (indentText.includes(" ")) return reject({ line: index + 1, message: "tabs are not allowed for indentation" });
4759
4787
  if (body2 === "---" || body2 === "...")
4760
4788
  return reject({ line: index + 1, message: "only one YAML document is allowed" });
@@ -4763,11 +4791,11 @@ var toLines = (source) => {
4763
4791
  return accept(lines3);
4764
4792
  };
4765
4793
  var fail = (line2, message) => reject({ line: line2?.number ?? 0, message });
4766
- var scalar = (line2, text6) => {
4767
- const value = parseValue(text6);
4794
+ var scalar = (line2, text7) => {
4795
+ const value = parseValue(text7);
4768
4796
  return value.ok ? value : fail(line2, value.error);
4769
4797
  };
4770
- var isItem = (text6) => text6 === "-" || text6.startsWith("- ");
4798
+ var isItem = (text7) => text7 === "-" || text7.startsWith("- ");
4771
4799
  var child = (lines3, index, parent, sameIndentSeq) => {
4772
4800
  const next2 = lines3[index];
4773
4801
  if (next2 !== void 0 && next2.indent > parent.indent) return block(lines3, index, next2.indent);
@@ -5010,8 +5038,8 @@ var validateContract = (value, implicit = false) => {
5010
5038
  // src/task/load.ts
5011
5039
  var contractHash = (contract) => sha256Hex(JSON.stringify(contract.acceptance.map((check2) => [check2.id, check2.kind, check2.run ?? ""])));
5012
5040
  var taskRelative = (id) => `.finitemonkey/tasks/${id}.yml`;
5013
- var parseContract = (id, text6) => {
5014
- const yaml = parseYaml(text6);
5041
+ var parseContract = (id, text7) => {
5042
+ const yaml = parseYaml(text7);
5015
5043
  if (!yaml.ok)
5016
5044
  return reject({ kind: "invalid", id, issues: [`line ${String(yaml.error.line)}: ${yaml.error.message}`] });
5017
5045
  const valid = validateContract(yaml.value);
@@ -5121,10 +5149,10 @@ var resolveBase = (git, declared) => {
5121
5149
  const merged2 = remote === void 0 ? void 0 : git.out(["merge-base", "HEAD", remote])?.trim();
5122
5150
  return nonEmpty(merged2) ?? nonEmpty(headSha(git)) ?? EMPTY_TREE;
5123
5151
  };
5124
- var nulFields = (text6) => text6.split("\0").filter((part2) => part2 !== "");
5152
+ var nulFields = (text7) => text7.split("\0").filter((part2) => part2 !== "");
5125
5153
  var untracked = (git) => nulFields(git.out(["ls-files", "--others", "--exclude-standard", "-z", "--", ".", EXCLUDE_FM]) ?? "");
5126
- var parseNameStatus = (text6) => {
5127
- const fields = nulFields(text6);
5154
+ var parseNameStatus = (text7) => {
5155
+ const fields = nulFields(text7);
5128
5156
  const changes = [];
5129
5157
  for (let index = 0; index < fields.length; index += 1) {
5130
5158
  const code = fields[index] ?? "";
@@ -5192,8 +5220,8 @@ var aheadOfUpstream = (git) => {
5192
5220
 
5193
5221
  // src/gate/diff-shape.ts
5194
5222
  var untrackedLines = (runtime, root, changes) => changes.filter((change) => change.status === "A").reduce((sum, change) => {
5195
- const text6 = runtime.readText(toAbsolutePath(join14(root, change.path)));
5196
- return sum + (text6.ok ? text6.value.split("\n").length : 0);
5223
+ const text7 = runtime.readText(toAbsolutePath(join14(root, change.path)));
5224
+ return sum + (text7.ok ? text7.value.split("\n").length : 0);
5197
5225
  }, 0);
5198
5226
  var diffShape = (runtime, git, base, changes) => ({
5199
5227
  files: changes.map((change) => change.path),
@@ -5216,14 +5244,14 @@ var ciChecks = (value) => Array.isArray(value) ? value.flatMap((item2) => {
5216
5244
  const name = optionalString(item2, "name") ?? optionalString(item2, "context") ?? "?";
5217
5245
  return [{ name, state: ciState(item2) }];
5218
5246
  }) : [];
5219
- var parsePrView = (text6) => {
5220
- const parsed = parseJsonObject(text6);
5247
+ var parsePrView = (text7) => {
5248
+ const parsed = parseJsonObject(text7);
5221
5249
  if (!parsed.ok) return void 0;
5222
5250
  const record3 = parsed.value;
5223
- const number = record3["number"];
5224
- if (typeof number !== "number") return void 0;
5251
+ const number2 = record3["number"];
5252
+ if (typeof number2 !== "number") return void 0;
5225
5253
  return {
5226
- number,
5254
+ number: number2,
5227
5255
  url: optionalString(record3, "url") ?? "",
5228
5256
  state: optionalString(record3, "state") ?? "",
5229
5257
  mergeable: optionalString(record3, "mergeable") ?? "UNKNOWN",
@@ -5397,21 +5425,21 @@ var applySize = (contract, verdict) => {
5397
5425
 
5398
5426
  // src/gate/prepare.ts
5399
5427
  var diskFiles = (runtime, root) => ({
5400
- read: (relative9) => {
5401
- const text6 = runtime.readText(toAbsolutePath(join15(root, relative9)));
5402
- return text6.ok ? text6.value : void 0;
5428
+ read: (relative11) => {
5429
+ const text7 = runtime.readText(toAbsolutePath(join15(root, relative11)));
5430
+ return text7.ok ? text7.value : void 0;
5403
5431
  },
5404
- exists: (relative9) => runtime.stat(toAbsolutePath(join15(root, relative9))) !== void 0
5432
+ exists: (relative11) => runtime.stat(toAbsolutePath(join15(root, relative11))) !== void 0
5405
5433
  });
5406
5434
  var baseFiles = (git, base, disk) => ({
5407
- read: (relative9) => base === EMPTY_TREE ? disk.read(relative9) : fileAt(git, base, relative9),
5435
+ read: (relative11) => base === EMPTY_TREE ? disk.read(relative11) : fileAt(git, base, relative11),
5408
5436
  exists: disk.exists
5409
5437
  });
5410
5438
  var loadContract = (options, paths, git, base) => {
5411
5439
  const disk = diskFiles(options.runtime, paths.root);
5412
5440
  const id = activeTaskId(options.runtime, paths, options.taskId);
5413
5441
  if (id === void 0) return accept(loadImplicit(options.runtime, paths, baseFiles(git, base, disk)));
5414
- return loadTask(options.runtime, paths, { disk: disk.read, base: (relative9) => fileAt(git, base, relative9) }, id);
5442
+ return loadTask(options.runtime, paths, { disk: disk.read, base: (relative11) => fileAt(git, base, relative11) }, id);
5415
5443
  };
5416
5444
  var memo = (make) => {
5417
5445
  let value;
@@ -5520,11 +5548,11 @@ var trust = (input, paths, task) => {
5520
5548
  });
5521
5549
  if (ready.kind !== "ready" || ready.prepared.ctx.contract.id !== task) return false;
5522
5550
  const lock = readTrust(runtime, paths);
5523
- const text6 = runtime.readText(paths.task(task));
5551
+ const text7 = runtime.readText(paths.task(task));
5524
5552
  return writeTrust(runtime, paths, {
5525
5553
  ...lock,
5526
5554
  contracts: { ...lock.contracts, [task]: ready.prepared.loaded.hash },
5527
- approved: task === IMPLICIT_ID || !text6.ok ? lock.approved : { ...lock.approved, [task]: text6.value }
5555
+ approved: task === IMPLICIT_ID || !text7.ok ? lock.approved : { ...lock.approved, [task]: text7.value }
5528
5556
  }).ok;
5529
5557
  };
5530
5558
  var apply = (input, paths, markers) => {
@@ -5576,8 +5604,8 @@ var promptRelease = (input) => {
5576
5604
  var PRS_CACHE_MS = 10 * 6e4;
5577
5605
  var PRS_TIMEOUT_MS = 3e3;
5578
5606
  var PRS_AUTH_TIMEOUT_MS = 2e3;
5579
- var parsePrList = (text6) => {
5580
- const parsed = parseJson(text6);
5607
+ var parsePrList = (text7) => {
5608
+ const parsed = parseJson(text7);
5581
5609
  if (!parsed.ok || !Array.isArray(parsed.value)) return void 0;
5582
5610
  return parsed.value.flatMap((item2) => {
5583
5611
  if (!isRecord(item2) || typeof item2["number"] !== "number") return [];
@@ -6303,7 +6331,23 @@ var en_default = {
6303
6331
  "githooks.pass": "ok {check}",
6304
6332
  "githooks.fail": "FAIL {check}: {detail}. Fix: {fix}",
6305
6333
  "githooks.error.failed": "{count, plural, one {# check failed} other {# checks failed}}",
6306
- "githooks.error.no_base": "the base {detail} does not exist here; fetch it first (actions/checkout needs fetch-depth: 0)"
6334
+ "githooks.error.no_base": "the base {detail} does not exist here; fetch it first (actions/checkout needs fetch-depth: 0)",
6335
+ "command.look.summary": "Capture the screens in .finitemonkey/look.yml at 1440 and 390 px and measure them",
6336
+ "look.done": "{count, plural, one {# capture} other {# captures}}, {failures, plural, one {# failure} other {# failures}}. Open every capture with Read.",
6337
+ "look.recorded": "{captures} captures, {failures} failures",
6338
+ "look.error.not_a_repo": "look runs inside a git repository",
6339
+ "look.error.not_configured": ".finitemonkey/look.yml is missing or has no port",
6340
+ "look.error.playwright_missing": "Playwright is not installed in this project",
6341
+ "look.error.browser_missing": "no Chromium, Chrome or Edge could be started",
6342
+ "look.error.app_failed": "the app did not come up: {detail}",
6343
+ "gate.require.look": "changed screens captured with zero failures and every capture opened after the last edit",
6344
+ "gate.look.not_run": "no capture of the current tree: run `finitemonkey look`",
6345
+ "gate.look.no_captures": "the last look run produced no capture",
6346
+ "gate.look.finding": "{rule} on {route} at {viewport}px: {detail}",
6347
+ "gate.look.allow_ignored": "exception ignored, its decision is not recorded: {entry}",
6348
+ "gate.look.unopened": "{count, plural, one {# capture} other {# captures}} not opened after the last edit to a screen file",
6349
+ "gate.look.open": "open with Read: {file}",
6350
+ "gate.look.ok": "{count, plural, one {# capture} other {# captures}}, no failure, all opened"
6307
6351
  };
6308
6352
 
6309
6353
  // src/i18n/es.json
@@ -6881,7 +6925,23 @@ var es_default = {
6881
6925
  "githooks.pass": "ok {check}",
6882
6926
  "githooks.fail": "FALLÓ {check}: {detail}. Arreglo: {fix}",
6883
6927
  "githooks.error.failed": "{count, plural, one {# revisión falló} other {# revisiones fallaron}}",
6884
- "githooks.error.no_base": "la base {detail} no existe aquí; tráela antes (actions/checkout necesita fetch-depth: 0)"
6928
+ "githooks.error.no_base": "la base {detail} no existe aquí; tráela antes (actions/checkout necesita fetch-depth: 0)",
6929
+ "command.look.summary": "Captura las pantallas de .finitemonkey/look.yml a 1440 y 390 px y las mide",
6930
+ "look.done": "{count, plural, one {# captura} other {# capturas}}, {failures, plural, one {# fallo} other {# fallos}}. Abre cada captura con Read.",
6931
+ "look.recorded": "{captures} capturas, {failures} fallos",
6932
+ "look.error.not_a_repo": "look se ejecuta dentro de un repositorio git",
6933
+ "look.error.not_configured": ".finitemonkey/look.yml no existe o no tiene port",
6934
+ "look.error.playwright_missing": "Playwright no está instalado en este proyecto",
6935
+ "look.error.browser_missing": "no se pudo abrir ningún Chromium, Chrome o Edge",
6936
+ "look.error.app_failed": "la app no arrancó: {detail}",
6937
+ "gate.require.look": "pantallas cambiadas capturadas sin fallos y cada captura abierta después de la última edición",
6938
+ "gate.look.not_run": "ninguna captura del árbol actual: ejecuta `finitemonkey look`",
6939
+ "gate.look.no_captures": "la última ejecución de look no produjo capturas",
6940
+ "gate.look.finding": "{rule} en {route} a {viewport}px: {detail}",
6941
+ "gate.look.allow_ignored": "excepción ignorada, su decisión no está registrada: {entry}",
6942
+ "gate.look.unopened": "{count, plural, one {# captura sin abrir} other {# capturas sin abrir}} después de la última edición de un archivo de pantalla",
6943
+ "gate.look.open": "abre con Read: {file}",
6944
+ "gate.look.ok": "{count, plural, one {# captura} other {# capturas}}, ningún fallo, todas abiertas"
6885
6945
  };
6886
6946
 
6887
6947
  // src/i18n/pt-BR.json
@@ -7459,7 +7519,23 @@ var pt_BR_default = {
7459
7519
  "githooks.pass": "ok {check}",
7460
7520
  "githooks.fail": "FALHOU {check}: {detail}. Conserto: {fix}",
7461
7521
  "githooks.error.failed": "{count, plural, one {# checagem falhou} other {# checagens falharam}}",
7462
- "githooks.error.no_base": "a base {detail} não existe aqui; busque antes (o actions/checkout precisa de fetch-depth: 0)"
7522
+ "githooks.error.no_base": "a base {detail} não existe aqui; busque antes (o actions/checkout precisa de fetch-depth: 0)",
7523
+ "command.look.summary": "Captura as telas de .finitemonkey/look.yml em 1440 e 390 px e mede cada uma",
7524
+ "look.done": "{count, plural, one {# captura} other {# capturas}}, {failures, plural, one {# falha} other {# falhas}}. Abra cada captura com Read.",
7525
+ "look.recorded": "{captures} capturas, {failures} falhas",
7526
+ "look.error.not_a_repo": "o look roda dentro de um repositório git",
7527
+ "look.error.not_configured": ".finitemonkey/look.yml não existe ou não tem port",
7528
+ "look.error.playwright_missing": "o Playwright não está instalado neste projeto",
7529
+ "look.error.browser_missing": "nenhum Chromium, Chrome ou Edge pôde ser aberto",
7530
+ "look.error.app_failed": "o app não subiu: {detail}",
7531
+ "gate.require.look": "telas alteradas capturadas sem falha e cada captura aberta depois da última edição",
7532
+ "gate.look.not_run": "nenhuma captura da árvore atual: rode `finitemonkey look`",
7533
+ "gate.look.no_captures": "a última rodada do look não gerou captura",
7534
+ "gate.look.finding": "{rule} em {route} a {viewport}px: {detail}",
7535
+ "gate.look.allow_ignored": "exceção ignorada, a decisão não foi registrada: {entry}",
7536
+ "gate.look.unopened": "{count, plural, one {# captura não aberta} other {# capturas não abertas}} depois da última edição de arquivo de tela",
7537
+ "gate.look.open": "abra com Read: {file}",
7538
+ "gate.look.ok": "{count, plural, one {# captura} other {# capturas}}, nenhuma falha, todas abertas"
7463
7539
  };
7464
7540
 
7465
7541
  // src/i18n/review.json
@@ -8038,7 +8114,23 @@ var review_default = {
8038
8114
  "githooks.pass": "machine",
8039
8115
  "githooks.fail": "machine",
8040
8116
  "githooks.error.failed": "machine",
8041
- "githooks.error.no_base": "machine"
8117
+ "githooks.error.no_base": "machine",
8118
+ "command.look.summary": "machine",
8119
+ "look.done": "machine",
8120
+ "look.recorded": "machine",
8121
+ "look.error.not_a_repo": "machine",
8122
+ "look.error.not_configured": "machine",
8123
+ "look.error.playwright_missing": "machine",
8124
+ "look.error.browser_missing": "machine",
8125
+ "look.error.app_failed": "machine",
8126
+ "gate.require.look": "machine",
8127
+ "gate.look.not_run": "machine",
8128
+ "gate.look.no_captures": "machine",
8129
+ "gate.look.finding": "machine",
8130
+ "gate.look.allow_ignored": "machine",
8131
+ "gate.look.unopened": "machine",
8132
+ "gate.look.open": "machine",
8133
+ "gate.look.ok": "machine"
8042
8134
  },
8043
8135
  es: {
8044
8136
  "command.init.summary": "machine",
@@ -8614,7 +8706,23 @@ var review_default = {
8614
8706
  "githooks.pass": "machine",
8615
8707
  "githooks.fail": "machine",
8616
8708
  "githooks.error.failed": "machine",
8617
- "githooks.error.no_base": "machine"
8709
+ "githooks.error.no_base": "machine",
8710
+ "command.look.summary": "machine",
8711
+ "look.done": "machine",
8712
+ "look.recorded": "machine",
8713
+ "look.error.not_a_repo": "machine",
8714
+ "look.error.not_configured": "machine",
8715
+ "look.error.playwright_missing": "machine",
8716
+ "look.error.browser_missing": "machine",
8717
+ "look.error.app_failed": "machine",
8718
+ "gate.require.look": "machine",
8719
+ "gate.look.not_run": "machine",
8720
+ "gate.look.no_captures": "machine",
8721
+ "gate.look.finding": "machine",
8722
+ "gate.look.allow_ignored": "machine",
8723
+ "gate.look.unopened": "machine",
8724
+ "gate.look.open": "machine",
8725
+ "gate.look.ok": "machine"
8618
8726
  }
8619
8727
  };
8620
8728
 
@@ -8875,7 +8983,7 @@ var preCompact = (input) => {
8875
8983
  // src/gate/review.ts
8876
8984
  var REVIEWS_FILE = "reviews.json";
8877
8985
  var REVIEWER_AGENT = /(^|:)[\w-]*reviewer$/i;
8878
- var KEEP = 50;
8986
+ var KEEP2 = 50;
8879
8987
  var toReview = (value) => {
8880
8988
  if (!isRecord(value)) return void 0;
8881
8989
  const { treeHash: treeHash2, agent, at } = value;
@@ -8889,7 +8997,7 @@ var readReviews = (runtime, paths) => {
8889
8997
  }) : [];
8890
8998
  };
8891
8999
  var recordReview = (runtime, paths, treeHash2, agent) => {
8892
- const reviews = [...readReviews(runtime, paths), { treeHash: treeHash2, agent, at: runtime.now() }].slice(-KEEP);
9000
+ const reviews = [...readReviews(runtime, paths), { treeHash: treeHash2, agent, at: runtime.now() }].slice(-KEEP2);
8893
9001
  return writeJsonFile(runtime, paths.cache(REVIEWS_FILE), { reviews: reviews.map((review2) => ({ ...review2 })) }).ok;
8894
9002
  };
8895
9003
 
@@ -8904,10 +9012,10 @@ var pairs = (record3) => {
8904
9012
  return names.filter((name) => typeof name === "string").map((name) => `${task}\0${name}`);
8905
9013
  };
8906
9014
  var callersRate = (runtime, paths) => {
8907
- const text6 = runtime.readText(paths.log);
9015
+ const text7 = runtime.readText(paths.log);
8908
9016
  const flagged = /* @__PURE__ */ new Set();
8909
9017
  const released2 = /* @__PURE__ */ new Set();
8910
- for (const line2 of text6.ok ? text6.value.split("\n") : []) {
9018
+ for (const line2 of text7.ok ? text7.value.split("\n") : []) {
8911
9019
  const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
8912
9020
  if (parsed === void 0 || !parsed.ok || !isRecord(parsed.value)) continue;
8913
9021
  const event = parsed.value["event"];
@@ -8932,7 +9040,7 @@ import { posix as posix2 } from "node:path";
8932
9040
  var JS_SPECIFIER = /(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["']([^"']+)["']/g;
8933
9041
  var JS_EXTENSION = /\.(?:[cm]?[jt]sx?)$/;
8934
9042
  var stripModule = (path) => path.replace(JS_EXTENSION, "").replace(/\/index$/, "");
8935
- var jsSpecifiers = (text6) => [...text6.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
9043
+ var jsSpecifiers = (text7) => [...text7.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
8936
9044
  var jsImports = (callerPath, callerText, origin) => {
8937
9045
  const target2 = stripModule(origin);
8938
9046
  return jsSpecifiers(callerText).some((specifier) => {
@@ -9034,9 +9142,9 @@ var languageOf = (path) => {
9034
9142
  const extension = extname(path).toLowerCase();
9035
9143
  return LANGUAGES2.find((language) => language.extensions.includes(extension));
9036
9144
  };
9037
- var definitionsIn = (language, text6) => {
9145
+ var definitionsIn = (language, text7) => {
9038
9146
  const names = /* @__PURE__ */ new Set();
9039
- for (const line2 of text6.split(/\r?\n/)) {
9147
+ for (const line2 of text7.split(/\r?\n/)) {
9040
9148
  for (const pattern of language.definitions) {
9041
9149
  const name = pattern.exec(line2)?.[1];
9042
9150
  if (name !== void 0 && !KEYWORDS.has(name)) names.add(name);
@@ -9049,8 +9157,8 @@ var definesName = (language, line2, name) => language.definitions.some((pattern)
9049
9157
  // src/gate/callers/scan.ts
9050
9158
  var MAX_SYMBOLS = 200;
9051
9159
  var current = (runtime, root, path) => {
9052
- const text6 = runtime.readText(toAbsolutePath(join16(root, path)));
9053
- return text6.ok ? text6.value : "";
9160
+ const text7 = runtime.readText(toAbsolutePath(join16(root, path)));
9161
+ return text7.ok ? text7.value : "";
9054
9162
  };
9055
9163
  var keyOf = (language, name) => `${language.id}\0${name}`;
9056
9164
  var redefinedNames = (runtime, git, changes) => {
@@ -9081,8 +9189,8 @@ var removedSymbols = (runtime, git, base, changes) => {
9081
9189
  };
9082
9190
  var parseGrep = (output) => output.split("\n").filter((row) => row !== "").flatMap((row) => {
9083
9191
  const [file = "", line2 = "", ...rest] = row.split("\0");
9084
- const number = Number(line2);
9085
- return file === "" || !Number.isInteger(number) ? [] : [{ file: toPosixPath(file), line: number, text: rest.join("\0") }];
9192
+ const number2 = Number(line2);
9193
+ return file === "" || !Number.isInteger(number2) ? [] : [{ file: toPosixPath(file), line: number2, text: rest.join("\0") }];
9086
9194
  });
9087
9195
  var grep = (git, names) => {
9088
9196
  if (names.length === 0) return [];
@@ -9091,13 +9199,13 @@ var grep = (git, names) => {
9091
9199
  git.out(["grep", "-n", "-z", "-w", "-I", "--untracked", "--no-color", ...patterns, "--", ".", EXCLUDE_FM]) ?? ""
9092
9200
  );
9093
9201
  };
9094
- var mentions = (text6, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text6);
9202
+ var mentions = (text7, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text7);
9095
9203
  var callSites = (runtime, git, symbols) => {
9096
9204
  const hits = grep(git, [...new Set(symbols.map((symbol) => symbol.name))]);
9097
- const texts = /* @__PURE__ */ new Map();
9205
+ const texts2 = /* @__PURE__ */ new Map();
9098
9206
  const textOf4 = (file) => {
9099
- const cached2 = texts.get(file) ?? current(runtime, git.cwd, file);
9100
- texts.set(file, cached2);
9207
+ const cached2 = texts2.get(file) ?? current(runtime, git.cwd, file);
9208
+ texts2.set(file, cached2);
9101
9209
  return cached2;
9102
9210
  };
9103
9211
  return symbols.flatMap(
@@ -9166,31 +9274,31 @@ var ZERO = [
9166
9274
  /^ℹ tests 0$/m,
9167
9275
  /No tests found/
9168
9276
  ];
9169
- var pyTotal = (text6) => {
9170
- const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(text6)?.[1];
9277
+ var pyTotal = (text7) => {
9278
+ const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(text7)?.[1];
9171
9279
  if (line2 === void 0) return void 0;
9172
9280
  return [...line2.matchAll(/(\d+) (passed|failed|error|errors|skipped|xfailed|xpassed)/g)].reduce(
9173
9281
  (sum, match) => sum + Number(match[1] ?? "0"),
9174
9282
  0
9175
9283
  );
9176
9284
  };
9177
- var counted = (text6) => {
9285
+ var counted = (text7) => {
9178
9286
  for (const counter of COUNTERS) {
9179
- const total = counter.total.exec(text6)?.[1];
9287
+ const total = counter.total.exec(text7)?.[1];
9180
9288
  if (total === void 0) continue;
9181
- const failed4 = counter.failed?.exec(text6)?.[1];
9289
+ const failed4 = counter.failed?.exec(text7)?.[1];
9182
9290
  return { total: Number(total), ...failed4 === void 0 ? {} : { failed: Number(failed4) } };
9183
9291
  }
9184
- const py = pyTotal(text6);
9292
+ const py = pyTotal(text7);
9185
9293
  return py === void 0 ? {} : { total: py };
9186
9294
  };
9187
9295
  var parseTestSummary = (output) => {
9188
- const text6 = output.replace(ANSI2, "");
9296
+ const text7 = output.replace(ANSI2, "");
9189
9297
  const failures = [
9190
- ...new Set(FAILURE_LINES.flatMap((pattern) => [...text6.matchAll(pattern)].map((match) => (match[1] ?? "").trim())))
9298
+ ...new Set(FAILURE_LINES.flatMap((pattern) => [...text7.matchAll(pattern)].map((match) => (match[1] ?? "").trim())))
9191
9299
  ].filter((name) => name !== "" && !/^failing tests:?$/i.test(name));
9192
- if (ZERO.some((pattern) => pattern.test(text6))) return { total: 0, failures };
9193
- return { ...counted(text6), failures };
9300
+ if (ZERO.some((pattern) => pattern.test(text7))) return { total: 0, failures };
9301
+ return { ...counted(text7), failures };
9194
9302
  };
9195
9303
 
9196
9304
  // src/gate/checks/command.ts
@@ -9201,8 +9309,8 @@ var REWRITERS = /^\s*(rtk)\b/;
9201
9309
  var TRIVIAL = /* @__PURE__ */ new Set(["echo", "true", ":", "printf", "exit", "sleep", "cat", "yes", "pwd", "ls", "date", "env"]);
9202
9310
  var TAIL = 160;
9203
9311
  var MAX_FAILURES2 = 5;
9204
- var tail = (text6) => {
9205
- const lines3 = text6.split(/\r?\n/).filter((line2) => line2.trim() !== "");
9312
+ var tail = (text7) => {
9313
+ const lines3 = text7.split(/\r?\n/).filter((line2) => line2.trim() !== "");
9206
9314
  const last = lines3[lines3.length - 1]?.trim() ?? "";
9207
9315
  return last.length > TAIL ? `${last.slice(0, TAIL)}…` : last;
9208
9316
  };
@@ -9321,6 +9429,147 @@ var gitCheck = (check2, ctx) => {
9321
9429
  return missing2.length > 0 ? failed(missing2) : passed([note("gate.git.ok", { require: wants.join(", ") })]);
9322
9430
  };
9323
9431
 
9432
+ // src/gate/checks/look.ts
9433
+ import { join as join19 } from "node:path";
9434
+
9435
+ // src/look/config.ts
9436
+ import { join as join17 } from "node:path";
9437
+
9438
+ // src/look/types.ts
9439
+ var LOOK_RULES = [
9440
+ "look/overflow",
9441
+ "look/console-error",
9442
+ "look/request-failed",
9443
+ "look/i18n-key",
9444
+ "look/broken-image",
9445
+ "look/touch-target"
9446
+ ];
9447
+ var BLOCKING_RULES = [
9448
+ "look/overflow",
9449
+ "look/console-error",
9450
+ "look/request-failed",
9451
+ "look/i18n-key",
9452
+ "look/broken-image"
9453
+ ];
9454
+ var VIEWPORTS = [1440, 390];
9455
+ var LOOK_SCHEMA = "finitemonkey/look@1";
9456
+
9457
+ // src/look/config.ts
9458
+ var LOOK_CONFIG = ".finitemonkey/look.yml";
9459
+ var DEFAULT_SCREENS = [
9460
+ "**/*.tsx",
9461
+ "**/*.jsx",
9462
+ "**/*.vue",
9463
+ "**/*.svelte",
9464
+ "**/*.astro",
9465
+ "**/*.html",
9466
+ "**/*.css",
9467
+ "**/*.scss",
9468
+ "**/*.sass",
9469
+ "**/*.less"
9470
+ ];
9471
+ var text5 = (value) => typeof value === "string" && value !== "" ? value : void 0;
9472
+ var number = (value) => {
9473
+ const parsed = typeof value === "string" ? Number(value) : Number.NaN;
9474
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
9475
+ };
9476
+ var allowEntries = (tree) => LOOK_RULES.flatMap(
9477
+ (rule) => configList(tree, "allow", rule).flatMap((entry2) => {
9478
+ const [route, decision] = entry2.trim().split(/\s+/);
9479
+ return route === void 0 || decision === void 0 ? [] : [{ rule, route, decision }];
9480
+ })
9481
+ );
9482
+ var parseLookConfig = (source) => {
9483
+ const tree = parseConfigYaml(source);
9484
+ const screens = configAt(tree, "screens");
9485
+ const top = configMap(tree);
9486
+ return {
9487
+ port: number(top["port"]),
9488
+ start: text5(top["start"]),
9489
+ host: text5(top["host"]) ?? "127.0.0.1",
9490
+ routes: configList(tree, "routes").length === 0 ? ["/"] : configList(tree, "routes"),
9491
+ mocks: configList(tree, "mocks"),
9492
+ screens: screens === void 0 ? DEFAULT_SCREENS : configList(tree, "screens"),
9493
+ allow: allowEntries(tree),
9494
+ timeoutS: number(top["timeout_s"]) ?? 60
9495
+ };
9496
+ };
9497
+ var loadLookConfig = (runtime, root) => {
9498
+ const source = runtime.readText(toAbsolutePath(join17(root, LOOK_CONFIG)));
9499
+ return source.ok ? parseLookConfig(source.value) : void 0;
9500
+ };
9501
+ var withMock = (route, mock) => `${route}${route.includes("?") ? "&" : "?"}mock=${encodeURIComponent(mock)}`;
9502
+ var lookRoutes = (config) => config.routes.flatMap((route) => [route, ...config.mocks.map((mock) => withMock(route, mock))]);
9503
+
9504
+ // src/look/paths.ts
9505
+ import { join as join18, relative as relative5 } from "node:path";
9506
+ var LOOK_DIR = "look";
9507
+ var SUMMARY_FILE = "summary.json";
9508
+ var lookDir = (paths, treeHash2) => toAbsolutePath(join18(paths.dir, LOOK_DIR, treeHash2));
9509
+ var summaryPath = (paths, treeHash2) => toAbsolutePath(join18(lookDir(paths, treeHash2), SUMMARY_FILE));
9510
+ var projectRelative = (paths, absolute) => toPosixPath(relative5(paths.root, absolute));
9511
+
9512
+ // src/gate/checks/look.ts
9513
+ var MAX_LISTED2 = 10;
9514
+ var findingNote = (value) => {
9515
+ if (!isRecord(value) || value["blocking"] !== true) return [];
9516
+ const { rule, route, viewport, detail } = value;
9517
+ return [
9518
+ note("gate.look.finding", {
9519
+ rule: typeof rule === "string" ? rule : "",
9520
+ route: typeof route === "string" ? route : "",
9521
+ viewport: typeof viewport === "number" ? viewport : 0,
9522
+ detail: typeof detail === "string" ? detail : ""
9523
+ })
9524
+ ];
9525
+ };
9526
+ var readSummary = (ctx) => {
9527
+ const text7 = ctx.runtime.readText(summaryPath(ctx.paths, ctx.treeHash));
9528
+ const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
9529
+ if (!parsed?.ok) return void 0;
9530
+ const { at, captures, findings, ignoredAllows } = parsed.value;
9531
+ if (typeof at !== "string" || !Array.isArray(captures) || !Array.isArray(findings)) return void 0;
9532
+ return {
9533
+ at,
9534
+ captures: captures.flatMap(
9535
+ (item2) => isRecord(item2) && typeof item2["path"] === "string" ? [item2["path"]] : []
9536
+ ),
9537
+ blocking: findings.flatMap(findingNote),
9538
+ ignored: Array.isArray(ignoredAllows) ? ignoredAllows.filter((item2) => typeof item2 === "string") : []
9539
+ };
9540
+ };
9541
+ var lastScreenEdit = (ctx) => {
9542
+ const screens = loadLookConfig(ctx.runtime, ctx.paths.root)?.screens ?? DEFAULT_SCREENS;
9543
+ return ctx.changes.filter((change) => change.status !== "D" && matchesAny(change.path, screens)).reduce((latest, change) => {
9544
+ const stat = ctx.runtime.stat(toAbsolutePath(join19(ctx.paths.root, change.path)));
9545
+ return Math.max(latest, stat?.mtimeMs ?? 0);
9546
+ }, 0);
9547
+ };
9548
+ var unopened = (ctx, captures) => {
9549
+ const reads = readLookReads(ctx.runtime, ctx.paths);
9550
+ const edited = lastScreenEdit(ctx);
9551
+ return captures.filter((path) => {
9552
+ const at = reads[path];
9553
+ return at === void 0 || Date.parse(at) < edited;
9554
+ });
9555
+ };
9556
+ var lookCheck = (_check, ctx) => {
9557
+ const summary = readSummary(ctx);
9558
+ if (summary === void 0) return failed([note("gate.look.not_run")]);
9559
+ const ignored = summary.ignored.slice(0, MAX_LISTED2).map((entry2) => note("gate.look.allow_ignored", { entry: entry2 }));
9560
+ if (summary.blocking.length > 0) return failed([...summary.blocking.slice(0, MAX_LISTED2), ...ignored]);
9561
+ if (summary.captures.length === 0) return failed([note("gate.look.no_captures"), ...ignored]);
9562
+ const closed = unopened(ctx, summary.captures);
9563
+ if (closed.length > 0) {
9564
+ return failed([
9565
+ note("gate.look.unopened", { count: closed.length }),
9566
+ ...closed.slice(0, MAX_LISTED2).map((file) => note("gate.look.open", { file })),
9567
+ ...ignored
9568
+ ]);
9569
+ }
9570
+ return passed([note("gate.look.ok", { count: summary.captures.length }), ...ignored]);
9571
+ };
9572
+
9324
9573
  // src/gate/checks/manual.ts
9325
9574
  var manualCheck = (check2, ctx) => {
9326
9575
  const approval = readApprovals(ctx.runtime, ctx.paths).findLast(
@@ -9339,14 +9588,14 @@ var manualCheck = (check2, ctx) => {
9339
9588
  };
9340
9589
 
9341
9590
  // src/gate/checks/placeholders.ts
9342
- import { join as join17 } from "node:path";
9343
- var MAX_LISTED2 = 10;
9591
+ import { join as join20 } from "node:path";
9592
+ var MAX_LISTED3 = 10;
9344
9593
  var SKIP_FILES = /\.(md|mdx|txt|rst|lock|snap|svg|png|jpg|gif|json)$|(^|\/)(CHANGELOG|LICENSE)/i;
9345
9594
  var PLACEHOLDER = /\b(TODO|FIXME|XXX)\b|not implemented|NotImplementedError|unimplemented!|todo!\(|lorem ipsum/i;
9346
9595
  var findPlaceholders = (lines3) => lines3.filter((line2) => !isTestFile(line2.path) && !SKIP_FILES.test(line2.path) && PLACEHOLDER.test(line2.text));
9347
9596
  var untrackedAdded = (ctx) => ctx.changes.filter((change) => change.status === "A" && !SKIP_FILES.test(change.path)).flatMap((change) => {
9348
- const text6 = ctx.runtime.readText(toAbsolutePath(join17(ctx.paths.root, change.path)));
9349
- return text6.ok ? text6.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
9597
+ const text7 = ctx.runtime.readText(toAbsolutePath(join20(ctx.paths.root, change.path)));
9598
+ return text7.ok ? text7.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
9350
9599
  });
9351
9600
  var placeholdersCheck = (_check, ctx) => {
9352
9601
  const tracked = addedLines(ctx.git, ctx.base);
@@ -9354,7 +9603,7 @@ var placeholdersCheck = (_check, ctx) => {
9354
9603
  const found = findPlaceholders([...tracked, ...untrackedAdded(ctx).filter((line2) => !seen.has(line2.path))]);
9355
9604
  if (found.length === 0) return passed([note("gate.placeholders.clean")]);
9356
9605
  return failed(
9357
- found.slice(0, MAX_LISTED2).map((line2) => note("gate.placeholders.found", { file: line2.path, line: line2.line }))
9606
+ found.slice(0, MAX_LISTED3).map((line2) => note("gate.placeholders.found", { file: line2.path, line: line2.line }))
9358
9607
  );
9359
9608
  };
9360
9609
 
@@ -9405,7 +9654,7 @@ var prOutcome = (check2, ctx) => {
9405
9654
 
9406
9655
  // src/prose/preset.ts
9407
9656
  var PRESET_LINE = /^preset:\s*([A-Za-z0-9_-]+)\s*$/m;
9408
- var presetFromConfig = (text6) => parsePreset(PRESET_LINE.exec(text6)?.[1]) ?? "neutral";
9657
+ var presetFromConfig = (text7) => parsePreset(PRESET_LINE.exec(text7)?.[1]) ?? "neutral";
9409
9658
  var projectPreset = (runtime, root) => {
9410
9659
  const config = runtime.readText(toAbsolutePath(CONFIG_FILE, root));
9411
9660
  return config.ok ? presetFromConfig(config.value) : "neutral";
@@ -9582,16 +9831,16 @@ var ANY_MARKER = /<!--\s*finitemonkey:prose-allow(?:-file)?\s*-->/g;
9582
9831
  var INLINE_CODE = /`[^`]*`/g;
9583
9832
  var FENCE2 = /^\s*(```|~~~)/;
9584
9833
  var outsideCode = (line2) => line2.replace(INLINE_CODE, "");
9585
- var unfencedLines = (text6) => {
9834
+ var unfencedLines = (text7) => {
9586
9835
  let fenced = false;
9587
- return text6.split("\n").filter((line2) => {
9836
+ return text7.split("\n").filter((line2) => {
9588
9837
  if (FENCE2.test(line2)) fenced = !fenced;
9589
9838
  return !fenced && !FENCE2.test(line2);
9590
9839
  });
9591
9840
  };
9592
- var allowsWholeFile = (text6) => unfencedLines(text6).some((line2) => FILE_MARKER.test(outsideCode(line2)));
9841
+ var allowsWholeFile = (text7) => unfencedLines(text7).some((line2) => FILE_MARKER.test(outsideCode(line2)));
9593
9842
  var allowsLine = (line2) => LINE_MARKER.test(outsideCode(line2));
9594
- var markerCount = (text6) => text6.match(ANY_MARKER)?.length ?? 0;
9843
+ var markerCount = (text7) => text7.match(ANY_MARKER)?.length ?? 0;
9595
9844
  var isMarkerOnlyLine = (line2) => allowsLine(line2) && outsideCode(line2).replace(LINE_MARKER, "").trim() === "";
9596
9845
 
9597
9846
  // src/prose/extract.ts
@@ -9611,7 +9860,7 @@ var MASKS = [
9611
9860
  [/<[^>]+>/g, ""],
9612
9861
  [/\*\*|__/g, ""]
9613
9862
  ];
9614
- var maskInline = (line2) => MASKS.reduce((text6, [pattern, replacement]) => text6.replace(pattern, replacement), line2);
9863
+ var maskInline = (line2) => MASKS.reduce((text7, [pattern, replacement]) => text7.replace(pattern, replacement), line2);
9615
9864
  var BlockReader = class {
9616
9865
  blocks = [];
9617
9866
  current;
@@ -9702,8 +9951,8 @@ var splitBlock = (block4) => {
9702
9951
  return joined.split(SENTENCE_END).flatMap((sentence) => {
9703
9952
  const at = joined.indexOf(sentence, offset);
9704
9953
  offset = at + sentence.length;
9705
- const text6 = sentence.trim();
9706
- return text6 === "" ? [] : [{ text: text6, line: lineAt(at) }];
9954
+ const text7 = sentence.trim();
9955
+ return text7 === "" ? [] : [{ text: text7, line: lineAt(at) }];
9707
9956
  });
9708
9957
  };
9709
9958
  var unitsOf = (blocks) => {
@@ -9724,9 +9973,9 @@ var unitsOf = (blocks) => {
9724
9973
  }));
9725
9974
  });
9726
9975
  };
9727
- var extractProse = (text6) => {
9728
- if (allowsWholeFile(text6)) return { units: [], allowed: true };
9729
- const lines3 = text6.split("\n").map(withoutCarriageReturn);
9976
+ var extractProse = (text7) => {
9977
+ if (allowsWholeFile(text7)) return { units: [], allowed: true };
9978
+ const lines3 = text7.split("\n").map(withoutCarriageReturn);
9730
9979
  return { units: unitsOf(readBlocks(lines3)), allowed: false };
9731
9980
  };
9732
9981
 
@@ -9851,7 +10100,7 @@ var CONNECTIVES = new Set(
9851
10100
  " "
9852
10101
  )
9853
10102
  );
9854
- var wordsOf = (text6) => text6.split(/\s+/).filter((word2) => WORD.test(word2));
10103
+ var wordsOf = (text7) => text7.split(/\s+/).filter((word2) => WORD.test(word2));
9855
10104
  var hit = (rule, units, index, weight) => {
9856
10105
  const unit = units[index];
9857
10106
  return { rule, unit: index, line: unit?.line ?? 0, weight, excerpt: unit?.text ?? "" };
@@ -9979,8 +10228,8 @@ var mergeSpans = (hits, windows) => {
9979
10228
  return ranges.map(([from, to]) => spanOf2(hits, from, to)).filter((span) => span.hits.length > 0);
9980
10229
  };
9981
10230
  var byPosition = (left, right) => left.unit - right.unit || left.line - right.line;
9982
- var scoreProse = (text6, options = {}) => {
9983
- const { units, allowed: allowed2 } = extractProse(text6);
10231
+ var scoreProse = (text7, options = {}) => {
10232
+ const { units, allowed: allowed2 } = extractProse(text7);
9984
10233
  const size = options.window ?? DENSITY_WINDOW;
9985
10234
  const threshold2 = options.threshold ?? DENSITY_THRESHOLD;
9986
10235
  const catalog = options.context === "record" ? [...phraseRules(), ...recordRules()] : phraseRules();
@@ -10012,9 +10261,9 @@ var changedMarkdown = (runtime, root) => {
10012
10261
  var scoreFiles = (runtime, root, files, forced) => {
10013
10262
  const reports = [];
10014
10263
  for (const file of files) {
10015
- const text6 = runtime.readText(toAbsolutePath(file, root));
10016
- if (!text6.ok) return reject(`${file}: ${text6.error}`);
10017
- reports.push({ file: toPosixPath(file), report: scoreProse(text6.value, { context: forced ?? contextOf(file) }) });
10264
+ const text7 = runtime.readText(toAbsolutePath(file, root));
10265
+ if (!text7.ok) return reject(`${file}: ${text7.error}`);
10266
+ reports.push({ file: toPosixPath(file), report: scoreProse(text7.value, { context: forced ?? contextOf(file) }) });
10018
10267
  }
10019
10268
  return accept(reports);
10020
10269
  };
@@ -10031,13 +10280,13 @@ var proseGateStatus = (runtime, root) => {
10031
10280
  };
10032
10281
 
10033
10282
  // src/gate/checks/prose.ts
10034
- var MAX_LISTED3 = 10;
10283
+ var MAX_LISTED4 = 10;
10035
10284
  var rulesOf = (span) => [...new Set(span.hits.map((hit3) => hit3.rule))].join(", ");
10036
10285
  var proseOutcome = (status) => {
10037
10286
  if (!status.ok) return unverified("tool_missing", [note("gate.prose.unavailable")]);
10038
10287
  const { files, flagged, blocking: blocking2 } = status.value;
10039
10288
  const key = blocking2 ? "gate.prose.finding" : "gate.prose.warning";
10040
- const notes2 = flagged.flatMap((item2) => item2.report.spans.map((span) => ({ file: item2.file, span }))).slice(0, MAX_LISTED3).map(({ file, span }) => note(key, { file, line: span.startLine, rules: rulesOf(span) }));
10289
+ const notes2 = flagged.flatMap((item2) => item2.report.spans.map((span) => ({ file: item2.file, span }))).slice(0, MAX_LISTED4).map(({ file, span }) => note(key, { file, line: span.startLine, rules: rulesOf(span) }));
10041
10290
  if (notes2.length === 0) return passed([note("gate.prose.clean", { count: files.length })]);
10042
10291
  return blocking2 ? failed(notes2) : passed(notes2);
10043
10292
  };
@@ -10113,8 +10362,8 @@ var parseAppCheck = (stdout) => {
10113
10362
  var PROBE_MARGIN_MS = 15e3;
10114
10363
  var MIN_REMAINING_MS2 = 5e3;
10115
10364
  var TAIL2 = 160;
10116
- var lastLine = (text6) => {
10117
- const rows = text6.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
10365
+ var lastLine = (text7) => {
10366
+ const rows = text7.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
10118
10367
  const line2 = (rows.findLast((row) => /error/i.test(row)) ?? rows.at(-1))?.trim() ?? "";
10119
10368
  return line2.length > TAIL2 ? `${line2.slice(0, TAIL2)}…` : line2;
10120
10369
  };
@@ -10192,14 +10441,14 @@ ${run4.stderr}`)) })]);
10192
10441
  };
10193
10442
 
10194
10443
  // src/secrets/baseline.ts
10195
- import { join as join18 } from "node:path";
10444
+ import { join as join21 } from "node:path";
10196
10445
  var BASELINE_FILE2 = ".finitemonkey/findings/baseline.json";
10197
10446
  var BASELINE_SCHEMA = "finitemonkey/secrets-baseline@1";
10198
10447
  var BASELINE_STATES = ["aceito", "falso-positivo"];
10199
10448
  var EXCEPTION_MAX_DAYS = 90;
10200
10449
  var EMPTY_BASELINE = { schema: BASELINE_SCHEMA, entries: [] };
10201
- var baselinePath = (root) => toAbsolutePath(join18(root, BASELINE_FILE2));
10202
- var text5 = (record3, key) => {
10450
+ var baselinePath = (root) => toAbsolutePath(join21(root, BASELINE_FILE2));
10451
+ var text6 = (record3, key) => {
10203
10452
  const value = record3[key];
10204
10453
  return typeof value === "string" && value !== "" ? value : void 0;
10205
10454
  };
@@ -10207,7 +10456,7 @@ var stateOf = (value) => BASELINE_STATES.find((state) => state === value);
10207
10456
  var parseEntry2 = (value) => {
10208
10457
  if (!isRecord(value)) return void 0;
10209
10458
  const fields = ["id", "rule", "file", "fingerprint", "owner", "reason", "acceptedAt", "expiresAt"];
10210
- const [id, rule, file, fingerprint, owner, reason, acceptedAt, expiresAt] = fields.map((key) => text5(value, key));
10459
+ const [id, rule, file, fingerprint, owner, reason, acceptedAt, expiresAt] = fields.map((key) => text6(value, key));
10211
10460
  const state = stateOf(value["state"]);
10212
10461
  const line2 = value["line"];
10213
10462
  if (id === void 0 || rule === void 0 || file === void 0 || fingerprint === void 0) return void 0;
@@ -10239,13 +10488,13 @@ var serializeBaseline = (baseline) => `${JSON.stringify({ schema: baseline.schem
10239
10488
  var saveBaseline = (runtime, root, baseline) => runtime.writeText(baselinePath(root), serializeBaseline(baseline));
10240
10489
  var ratchet = (findings, baseline, now, inScope) => {
10241
10490
  const byId = new Map(baseline.entries.map((entry2) => [entry2.id, entry2]));
10242
- const seen = new Set(findings.map((finding3) => finding3.id));
10243
- const entryOf4 = (finding3) => byId.get(finding3.id);
10491
+ const seen = new Set(findings.map((finding4) => finding4.id));
10492
+ const entryOf4 = (finding4) => byId.get(finding4.id);
10244
10493
  return {
10245
- fresh: findings.filter((finding3) => entryOf4(finding3) === void 0),
10246
- known: findings.filter((finding3) => (entryOf4(finding3)?.expiresAt ?? "") >= now),
10247
- expired: findings.filter((finding3) => {
10248
- const entry2 = entryOf4(finding3);
10494
+ fresh: findings.filter((finding4) => entryOf4(finding4) === void 0),
10495
+ known: findings.filter((finding4) => (entryOf4(finding4)?.expiresAt ?? "") >= now),
10496
+ expired: findings.filter((finding4) => {
10497
+ const entry2 = entryOf4(finding4);
10249
10498
  return entry2 !== void 0 && entry2.expiresAt < now;
10250
10499
  }),
10251
10500
  fixed: inScope === void 0 ? [] : baseline.entries.filter((entry2) => !seen.has(entry2.id) && inScope(entry2.file))
@@ -10258,14 +10507,14 @@ var withoutFixed = (baseline, fixed) => {
10258
10507
  var acceptedFingerprints = (baseline, now) => new Set(baseline.entries.filter((entry2) => entry2.expiresAt >= now).map((entry2) => entry2.fingerprint));
10259
10508
 
10260
10509
  // src/secrets/incidents.ts
10261
- import { join as join19 } from "node:path";
10510
+ import { join as join22 } from "node:path";
10262
10511
  var INCIDENTS_DIR = ".finitemonkey/incidents";
10263
10512
  var INCIDENT_STATES = ["aberto", "exposicao-local", "rotacionado", "fechado"];
10264
10513
  var INCIDENT_SOURCES = ["tool-output", "prompt", "history", "scan"];
10265
10514
  var ID = /^inc-[0-9a-f]{12}$/;
10266
10515
  var incidentId = (rule, fingerprint) => `inc-${sha256Hex(`${rule}\0${fingerprint}`).slice(0, 12)}`;
10267
- var incidentsDir = (root) => toAbsolutePath(join19(root, INCIDENTS_DIR));
10268
- var incidentFile = (root, id) => toAbsolutePath(join19(incidentsDir(root), `${id}.json`));
10516
+ var incidentsDir = (root) => toAbsolutePath(join22(root, INCIDENTS_DIR));
10517
+ var incidentFile = (root, id) => toAbsolutePath(join22(incidentsDir(root), `${id}.json`));
10269
10518
  var pick3 = (options, value) => options.find((option) => option === value);
10270
10519
  var str2 = (record3, key) => {
10271
10520
  const value = record3[key];
@@ -10320,7 +10569,7 @@ var listIncidents = (runtime, root) => {
10320
10569
  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));
10321
10570
  };
10322
10571
  var saveIncident = (runtime, root, incident) => {
10323
- const ignore = runtime.writeText(toAbsolutePath(join19(incidentsDir(root), ".gitignore")), "*\n");
10572
+ const ignore = runtime.writeText(toAbsolutePath(join22(incidentsDir(root), ".gitignore")), "*\n");
10324
10573
  if (!ignore.ok) return ignore;
10325
10574
  return runtime.writeText(incidentFile(root, incident.id), `${JSON.stringify(incident, null, 2)}
10326
10575
  `);
@@ -10367,25 +10616,25 @@ var moveIncident = (incident, to, now, by) => ({
10367
10616
  var isRotated = (incident) => incident.state === "rotacionado" || incident.state === "fechado";
10368
10617
 
10369
10618
  // src/secrets/scan.ts
10370
- import { join as join20, relative as relative4 } from "node:path";
10619
+ import { join as join23, relative as relative6 } from "node:path";
10371
10620
 
10372
10621
  // src/secrets/located.ts
10373
10622
  var findingId = (ruleId, file, fingerprint) => `fm-${sha256Hex(`${ruleId}\0${file}\0${fingerprint}`).slice(0, 16)}`;
10374
- var locate = (finding3, rawFile, line2, commit2) => {
10623
+ var locate = (finding4, rawFile, line2, commit2) => {
10375
10624
  const file = toPosixPath(rawFile);
10376
10625
  return {
10377
- id: findingId(finding3.ruleId, file, finding3.fingerprint),
10378
- ruleId: finding3.ruleId,
10379
- severity: finding3.severity,
10626
+ id: findingId(finding4.ruleId, file, finding4.fingerprint),
10627
+ ruleId: finding4.ruleId,
10628
+ severity: finding4.severity,
10380
10629
  file,
10381
10630
  line: line2,
10382
- column: finding3.column,
10383
- fingerprint: finding3.fingerprint,
10384
- tail: finding3.tail,
10631
+ column: finding4.column,
10632
+ fingerprint: finding4.fingerprint,
10633
+ tail: finding4.tail,
10385
10634
  ...commit2 === void 0 ? {} : { commit: commit2 }
10386
10635
  };
10387
10636
  };
10388
- var whereOf = (finding3) => `${finding3.file}:${String(finding3.line)}`;
10637
+ var whereOf = (finding4) => `${finding4.file}:${String(finding4.line)}`;
10389
10638
 
10390
10639
  // src/secrets/diff.ts
10391
10640
  var COMMIT = /^commit ([0-9a-f]{7,64})\b/;
@@ -10400,8 +10649,8 @@ var targetOf = (line2) => {
10400
10649
  var collect2 = (chunk, commit2, found) => {
10401
10650
  const { file } = chunk;
10402
10651
  if (file === void 0 || chunk.text.length === 0) return;
10403
- for (const finding3 of scanText(chunk.text.join("\n"), { path: file })) {
10404
- const located = locate(finding3, file, chunk.lines[finding3.line - 1] ?? finding3.line, commit2);
10652
+ for (const finding4 of scanText(chunk.text.join("\n"), { path: file })) {
10653
+ const located = locate(finding4, file, chunk.lines[finding4.line - 1] ?? finding4.line, commit2);
10405
10654
  found.set(located.id, located);
10406
10655
  }
10407
10656
  };
@@ -10457,7 +10706,7 @@ var diffScanner = () => {
10457
10706
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
10458
10707
  var SKIPPED = /(?:^|\/)(?:\.git|node_modules|\.finitemonkey)\//;
10459
10708
  var DIFF_FLAGS = ["-U0", "--no-color", "--no-ext-diff", "--no-renames"];
10460
- var binary = (text6) => text6.slice(0, 8e3).includes("\0");
10709
+ var binary = (text7) => text7.slice(0, 8e3).includes("\0");
10461
10710
  var listed2 = (runtime, root, target2) => {
10462
10711
  const git = runtime.git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", target2], root);
10463
10712
  if (git.ok)
@@ -10467,12 +10716,12 @@ var listed2 = (runtime, root, target2) => {
10467
10716
  const base = toAbsolutePath(target2, root);
10468
10717
  const walked = runtime.walkFiles(base);
10469
10718
  if (!walked.ok) return walked;
10470
- return accept(walked.value.map((name) => toPosixPath(relative4(root, join20(base, toPosixPath(name))))));
10719
+ return accept(walked.value.map((name) => toPosixPath(relative6(root, join23(base, toPosixPath(name))))));
10471
10720
  };
10472
10721
  var scanFile = (runtime, root, file) => {
10473
- const text6 = runtime.readText(toAbsolutePath(file, root));
10474
- if (!text6.ok || text6.value.length > MAX_FILE_BYTES || binary(text6.value)) return [];
10475
- return scanText(text6.value, { path: file }).map((finding3) => locate(finding3, file, finding3.line));
10722
+ const text7 = runtime.readText(toAbsolutePath(file, root));
10723
+ if (!text7.ok || text7.value.length > MAX_FILE_BYTES || binary(text7.value)) return [];
10724
+ return scanText(text7.value, { path: file }).map((finding4) => locate(finding4, file, finding4.line));
10476
10725
  };
10477
10726
  var scanPath = (runtime, root, target2) => {
10478
10727
  const files = listed2(runtime, root, target2);
@@ -10482,7 +10731,7 @@ var scanPath = (runtime, root, target2) => {
10482
10731
  var diffFindings = (diff) => {
10483
10732
  const scanner = diffScanner();
10484
10733
  for (const line2 of diff.split("\n")) scanner.push(line2);
10485
- return scanner.end().filter((finding3) => !SKIPPED.test(finding3.file));
10734
+ return scanner.end().filter((finding4) => !SKIPPED.test(finding4.file));
10486
10735
  };
10487
10736
  var scanStaged = (runtime, root, includeUnstaged = false) => {
10488
10737
  const args2 = includeUnstaged ? ["diff", "HEAD", ...DIFF_FLAGS] : ["diff", "--cached", ...DIFF_FLAGS];
@@ -10495,7 +10744,7 @@ var scanHistory = async (runtime, root) => {
10495
10744
  const args2 = ["log", "-p", "--all", "--format=commit %H", ...DIFF_FLAGS];
10496
10745
  const git = await runtime.gitLines(args2, root, scanner.push);
10497
10746
  if (!git.ok) return reject(git.found ? "git log failed" : "git not found");
10498
- return accept(scanner.end().filter((finding3) => !SKIPPED.test(finding3.file)));
10747
+ return accept(scanner.end().filter((finding4) => !SKIPPED.test(finding4.file)));
10499
10748
  };
10500
10749
 
10501
10750
  // src/secrets/gate.ts
@@ -10517,7 +10766,7 @@ var secretsGateStatus = (runtime, root, since) => {
10517
10766
  };
10518
10767
 
10519
10768
  // src/gate/checks/secrets.ts
10520
- var MAX_LISTED4 = 10;
10769
+ var MAX_LISTED5 = 10;
10521
10770
  var EPOCH = "1970-01-01T00:00:00.000Z";
10522
10771
  var sinceOf = (ctx) => {
10523
10772
  const session = readSession(ctx.runtime, ctx.paths);
@@ -10527,17 +10776,17 @@ var secretsCheck = (_check, ctx) => {
10527
10776
  const status = secretsGateStatus(ctx.runtime, ctx.paths.root, sinceOf(ctx));
10528
10777
  if (!status.ok) return unverified("tool_missing", [note("gate.secrets.unavailable")]);
10529
10778
  const { findings, openIncidents } = status.value;
10530
- const warnings = findings.filter(isWarning).slice(0, MAX_LISTED4).map((finding3) => note("gate.secrets.warning", { file: finding3.file, line: finding3.line, rule: finding3.ruleId }));
10779
+ const warnings = findings.filter(isWarning).slice(0, MAX_LISTED5).map((finding4) => note("gate.secrets.warning", { file: finding4.file, line: finding4.line, rule: finding4.ruleId }));
10531
10780
  const notes2 = [
10532
- ...blocking(findings).slice(0, MAX_LISTED4).map((finding3) => note("gate.secrets.finding", { file: finding3.file, line: finding3.line, rule: finding3.ruleId })),
10533
- ...openIncidents.slice(0, MAX_LISTED4).map((incident) => note("gate.secrets.incident", { id: incident.id, state: incident.state }))
10781
+ ...blocking(findings).slice(0, MAX_LISTED5).map((finding4) => note("gate.secrets.finding", { file: finding4.file, line: finding4.line, rule: finding4.ruleId })),
10782
+ ...openIncidents.slice(0, MAX_LISTED5).map((incident) => note("gate.secrets.incident", { id: incident.id, state: incident.state }))
10534
10783
  ];
10535
10784
  if (notes2.length > 0) return failed([...notes2, ...warnings]);
10536
10785
  return passed(warnings.length > 0 ? warnings : [note("gate.secrets.clean")]);
10537
10786
  };
10538
10787
 
10539
10788
  // src/gate/checks/tests.ts
10540
- import { join as join21 } from "node:path";
10789
+ import { join as join24 } from "node:path";
10541
10790
 
10542
10791
  // src/guards/tests/tree.ts
10543
10792
  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 }] : [];
@@ -10555,7 +10804,7 @@ var forChange = (input, change) => {
10555
10804
  var analyzeTree = (input) => input.changes.flatMap((change) => forChange(input, change));
10556
10805
 
10557
10806
  // src/gate/checks/tests.ts
10558
- var MAX_LISTED5 = 10;
10807
+ var MAX_LISTED6 = 10;
10559
10808
  var violationNote = (violation) => note(`gate.tests.${violation.kind}`, {
10560
10809
  file: violation.path,
10561
10810
  before: violation.before ?? 0,
@@ -10569,8 +10818,8 @@ var treeViolations = (ctx) => {
10569
10818
  changes: relevant,
10570
10819
  before: (path) => fileAt(ctx.git, ctx.base, path),
10571
10820
  after: (path) => {
10572
- const text6 = ctx.runtime.readText(toAbsolutePath(join21(ctx.paths.root, path)));
10573
- return text6.ok ? text6.value : void 0;
10821
+ const text7 = ctx.runtime.readText(toAbsolutePath(join24(ctx.paths.root, path)));
10822
+ return text7.ok ? text7.value : void 0;
10574
10823
  },
10575
10824
  acceptedConfigs: ctx.trust.configs,
10576
10825
  scopePaths: ctx.contract.scope.paths,
@@ -10580,7 +10829,7 @@ var treeViolations = (ctx) => {
10580
10829
  var testsCheck = (check2, ctx) => {
10581
10830
  const violations = treeViolations(ctx);
10582
10831
  const grant = violations.length === 0 ? void 0 : activeGrant(ctx.runtime, ctx.paths, "tests", nowMs(ctx.runtime), ctx.sessionId);
10583
- const notes2 = violations.slice(0, MAX_LISTED5).map(violationNote);
10832
+ const notes2 = violations.slice(0, MAX_LISTED6).map(violationNote);
10584
10833
  if (violations.length > 0 && grant === void 0) return failed([...notes2, note("gate.tests.fix")]);
10585
10834
  const released2 = grant === void 0 ? {} : { releasedBy: `person@${grant.grantedAt}` };
10586
10835
  const releaseNotes = grant === void 0 ? [] : [...notes2, note("gate.note.released", { guard: "tests", reason: grant.reason })];
@@ -10590,7 +10839,7 @@ var testsCheck = (check2, ctx) => {
10590
10839
  };
10591
10840
 
10592
10841
  // src/git/tree.ts
10593
- import { join as join22 } from "node:path";
10842
+ import { join as join25 } from "node:path";
10594
10843
 
10595
10844
  // src/secrets/guards/paths.ts
10596
10845
  var SAFE_ENV = /^\.env\.(?:example|sample|template|dist|defaults|schema)$/i;
@@ -10629,7 +10878,7 @@ var dropSensitive = (runtime, repo, env, options) => {
10629
10878
  return removed.ok ? accept(void 0) : removed;
10630
10879
  };
10631
10880
  var withTempIndex = (runtime, repo, options, work) => {
10632
- const path = toAbsolutePath(join22(repo.gitDir, `finitemonkey-${options.stamp}.index`));
10881
+ const path = toAbsolutePath(join25(repo.gitDir, `finitemonkey-${options.stamp}.index`));
10633
10882
  const env = { GIT_INDEX_FILE: path, GIT_OPTIONAL_LOCKS: "0" };
10634
10883
  try {
10635
10884
  return work(env);
@@ -10734,10 +10983,10 @@ var bodyLine = (draft, line2) => {
10734
10983
  else if (line2.startsWith("+")) draft.current.added.push(line2.slice(1));
10735
10984
  };
10736
10985
  var DIFF_GIT = /^diff --git a\/(.+) b\/(.+)$/;
10737
- var parseUnifiedDiff = (text6) => {
10986
+ var parseUnifiedDiff = (text7) => {
10738
10987
  const out = [];
10739
10988
  let draft;
10740
- for (const raw of text6.split("\n")) {
10989
+ for (const raw of text7.split("\n")) {
10741
10990
  const line2 = withoutCarriageReturn(raw);
10742
10991
  const start = DIFF_GIT.exec(line2);
10743
10992
  if (start !== null) {
@@ -10881,14 +11130,14 @@ var parsePorcelainV2 = (stdout) => {
10881
11130
  };
10882
11131
 
10883
11132
  // src/git/merge/dropped.ts
10884
- import { join as join23 } from "node:path";
11133
+ import { join as join26 } from "node:path";
10885
11134
 
10886
11135
  // src/git/merge/lines.ts
10887
11136
  var normalizeLine = (line2) => line2.replace(/\r$/, "").trim().replace(/\s*,$/, "");
10888
11137
  var WEAK = /^[\p{P}\p{S}\s]+$/u;
10889
11138
  var isWeak = (line2) => WEAK.test(line2);
10890
11139
  var meaningful = (lines3) => lines3.map(normalizeLine).filter((line2) => line2 !== "");
10891
- var contentLines = (text6) => text6 === void 0 ? [] : meaningful(text6.split("\n"));
11140
+ var contentLines = (text7) => text7 === void 0 ? [] : meaningful(text7.split("\n"));
10892
11141
  var countLines = (lines3) => {
10893
11142
  const counts = /* @__PURE__ */ new Map();
10894
11143
  for (const line2 of lines3) counts.set(line2, (counts.get(line2) ?? 0) + 1);
@@ -10937,16 +11186,16 @@ var itemOf = (value) => {
10937
11186
  approvedBy: optionalString(value, "approved_by")
10938
11187
  };
10939
11188
  };
10940
- var invalidFinding = (file, text6) => ({
11189
+ var invalidFinding = (file, text7) => ({
10941
11190
  kind: "invalid-dropped",
10942
11191
  file,
10943
11192
  side: void 0,
10944
11193
  line: void 0,
10945
- text: text6,
11194
+ text: text7,
10946
11195
  weak: false
10947
11196
  });
10948
- var parseDropped = (text6) => {
10949
- const parsed = parseJson(text6);
11197
+ var parseDropped = (text7) => {
11198
+ const parsed = parseJson(text7);
10950
11199
  if (!parsed.ok) return parsed;
10951
11200
  const list4 = isRecord(parsed.value) ? parsed.value["dropped"] : parsed.value;
10952
11201
  if (!Array.isArray(list4)) return reject("expected a list of dropped items");
@@ -10961,17 +11210,17 @@ var parseDropped = (text6) => {
10961
11210
  return accept({ items, invalid });
10962
11211
  };
10963
11212
  var loadDropped = (runtime, root, file) => {
10964
- const path = toAbsolutePath(file ?? join23(root, DROPPED_FILE), root);
10965
- const text6 = runtime.readText(path);
10966
- if (!text6.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text6.error}`);
10967
- return parseDropped(text6.value);
11213
+ const path = toAbsolutePath(file ?? join26(root, DROPPED_FILE), root);
11214
+ const text7 = runtime.readText(path);
11215
+ if (!text7.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text7.error}`);
11216
+ return parseDropped(text7.value);
10968
11217
  };
10969
- var isDropped = (finding3, items) => items.some(
10970
- (item2) => item2.file === finding3.file && (item2.side === void 0 || finding3.side === void 0 || item2.side === finding3.side) && (item2.text === void 0 || normalizeLine(item2.text) === finding3.text)
11218
+ var isDropped = (finding4, items) => items.some(
11219
+ (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)
10971
11220
  );
10972
11221
 
10973
11222
  // src/git/merge/generated.ts
10974
- import { join as join24 } from "node:path";
11223
+ import { join as join27 } from "node:path";
10975
11224
  var LOCKFILES3 = /* @__PURE__ */ new Set([
10976
11225
  "package-lock.json",
10977
11226
  "npm-shrinkwrap.json",
@@ -11034,7 +11283,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
11034
11283
  if (declared.length === 0) return accept([]);
11035
11284
  const commit2 = resultCommit(runtime, repo, inputs);
11036
11285
  if (!commit2.ok) return commit2;
11037
- const dir = join24(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
11286
+ const dir = join27(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
11038
11287
  if (!runtime.git(["worktree", "add", "--detach", dir, commit2.value], repo.root).ok)
11039
11288
  return reject("could not check out the merge result");
11040
11289
  try {
@@ -11044,7 +11293,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
11044
11293
  }
11045
11294
  return accept(
11046
11295
  declared.flatMap(({ path }) => {
11047
- const fresh3 = runtime.readText(toAbsolutePath(join24(dir, path)));
11296
+ const fresh3 = runtime.readText(toAbsolutePath(join27(dir, path)));
11048
11297
  const merged2 = blobText(runtime, repo, commit2.value, path);
11049
11298
  if (fresh3.ok && fresh3.value === merged2) return [];
11050
11299
  return [{ kind: "generated-differs", file: path, side: void 0, line: void 0, text: path, weak: false }];
@@ -11087,13 +11336,13 @@ var removalOf = (ours, theirs, paired) => {
11087
11336
  var missingAdditions = (hunks, file, elsewhere) => {
11088
11337
  const present2 = new Set(file.result);
11089
11338
  return hunks.flatMap(({ side, hunk, added }) => {
11090
- const text6 = added.find((line2) => !isWeak(line2) && !present2.has(line2));
11091
- if (text6 === void 0 || containsSequence(file.result, added) || elsewhere(added)) return [];
11339
+ const text7 = added.find((line2) => !isWeak(line2) && !present2.has(line2));
11340
+ if (text7 === void 0 || containsSequence(file.result, added) || elsewhere(added)) return [];
11092
11341
  const offset = Math.max(
11093
11342
  0,
11094
- hunk.added.findIndex((line2) => normalizeLine(line2) === text6)
11343
+ hunk.added.findIndex((line2) => normalizeLine(line2) === text7)
11095
11344
  );
11096
- return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text: text6, weak: false }];
11345
+ return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text: text7, weak: false }];
11097
11346
  });
11098
11347
  };
11099
11348
  var expectations = (file, hunks, removal) => {
@@ -11166,8 +11415,8 @@ var symbolsOf = (line2) => {
11166
11415
  ...title === void 0 ? [] : [{ name: title, test: true }]
11167
11416
  ];
11168
11417
  };
11169
- var escapeRegex = (text6) => text6.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11170
- var containsSymbol = (text6, symbol) => symbol.test ? text6.includes(symbol.name) : new RegExp(`(?<![\\w$])${escapeRegex(symbol.name)}(?![\\w$])`).test(text6);
11418
+ var escapeRegex = (text7) => text7.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11419
+ var containsSymbol = (text7, symbol) => symbol.test ? text7.includes(symbol.name) : new RegExp(`(?<![\\w$])${escapeRegex(symbol.name)}(?![\\w$])`).test(text7);
11171
11420
  var missingSymbols = (file, side, hunks, present2) => {
11172
11421
  const seen = /* @__PURE__ */ new Set();
11173
11422
  return hunks.flatMap(
@@ -11186,12 +11435,12 @@ var created2 = (diff) => diff?.status === "added" || diff?.status === "copied";
11186
11435
  var resultPathOf = (context, pair) => [pair.ours?.newPath, pair.theirs?.newPath, pair.key].find(
11187
11436
  (path) => path !== void 0 && context.resultText(path) !== void 0
11188
11437
  );
11189
- var finding2 = (kind, file, side, text6) => ({
11438
+ var finding2 = (kind, file, side, text7) => ({
11190
11439
  kind,
11191
11440
  file,
11192
11441
  side,
11193
11442
  line: void 0,
11194
- text: text6,
11443
+ text: text7,
11195
11444
  weak: false
11196
11445
  });
11197
11446
  var binaryFindings = (context, pair, path) => {
@@ -11210,8 +11459,8 @@ var binaryFindings = (context, pair, path) => {
11210
11459
  return result === expected ? [] : [finding2("binary", file, side, file)];
11211
11460
  };
11212
11461
  var elsewhereOf = (context) => (lines3) => context.resultFiles().some((path) => containsSequence(contentLines(context.resultText(path)), lines3));
11213
- var symbolFindings = (context, pair, path, text6) => {
11214
- const present2 = (symbol) => text6 !== void 0 && containsSymbol(text6, symbol) || context.resultFiles().some((other) => containsSymbol(context.resultText(other) ?? "", symbol));
11462
+ var symbolFindings = (context, pair, path, text7) => {
11463
+ const present2 = (symbol) => text7 !== void 0 && containsSymbol(text7, symbol) || context.resultFiles().some((other) => containsSymbol(context.resultText(other) ?? "", symbol));
11215
11464
  return [
11216
11465
  ...missingSymbols(path, "ours", pair.ours?.hunks ?? [], present2),
11217
11466
  ...missingSymbols(path, "theirs", pair.theirs?.hunks ?? [], present2)
@@ -11227,19 +11476,19 @@ var verifyFile = (context, pair) => {
11227
11476
  const copied = [pair.ours, pair.theirs].find((diff) => diff?.status === "copied");
11228
11477
  const baseSource = copied?.oldPath ?? (created2(pair.ours) || created2(pair.theirs) ? void 0 : pair.key);
11229
11478
  const baseText = baseSource === void 0 ? void 0 : blobText(context.runtime, context.repo, context.inputs.base, baseSource);
11230
- const text6 = path === void 0 ? void 0 : context.resultText(path);
11479
+ const text7 = path === void 0 ? void 0 : context.resultText(path);
11231
11480
  const file = path ?? pair.key;
11232
11481
  const lines3 = checkFile(
11233
11482
  {
11234
11483
  path: file,
11235
11484
  base: contentLines(baseText),
11236
- result: contentLines(text6),
11485
+ result: contentLines(text7),
11237
11486
  ours: pair.ours?.hunks ?? [],
11238
11487
  theirs: pair.theirs?.hunks ?? []
11239
11488
  },
11240
11489
  elsewhereOf(context)
11241
11490
  );
11242
- return [...lines3, ...symbolFindings(context, pair, file, text6)];
11491
+ return [...lines3, ...symbolFindings(context, pair, file, text7)];
11243
11492
  };
11244
11493
 
11245
11494
  // src/git/merge/verify.ts
@@ -11330,19 +11579,19 @@ var verifyMerge = (runtime, repo, options = {}) => {
11330
11579
  };
11331
11580
 
11332
11581
  // src/git/ownership.ts
11333
- import { join as join25 } from "node:path";
11582
+ import { join as join28 } from "node:path";
11334
11583
  var OWNERSHIP_DIR = ".finitemonkey/git";
11335
11584
  var ownerKey = (raw) => {
11336
11585
  const cleaned = (raw ?? "").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80);
11337
11586
  return cleaned === "" ? "unknown" : cleaned;
11338
11587
  };
11339
- var fileOf = (repo, kind, owner) => toAbsolutePath(join25(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
11588
+ var fileOf = (repo, kind, owner) => toAbsolutePath(join28(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
11340
11589
  var readDirty = (runtime, root) => {
11341
11590
  const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
11342
11591
  return status.ok ? parsePorcelainV2(status.stdout) : void 0;
11343
11592
  };
11344
11593
  var hashDirty = (runtime, repo, entries) => {
11345
- const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join25(repo.root, entry2.path)))?.kind === "file");
11594
+ const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join28(repo.root, entry2.path)))?.kind === "file");
11346
11595
  const hashed = present2.length === 0 ? void 0 : runtime.gitWith(["hash-object", "--stdin-paths"], repo.root, {
11347
11596
  input: `${present2.map((entry2) => entry2.path).join("\n")}
11348
11597
  `
@@ -11359,8 +11608,8 @@ var hashesOf = (value) => isRecord(value) ? Object.fromEntries(
11359
11608
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
11360
11609
  ) : {};
11361
11610
  var readObject = (runtime, path) => {
11362
- const text6 = runtime.readText(path);
11363
- return text6.ok ? parseJsonObject(text6.value) : text6;
11611
+ const text7 = runtime.readText(path);
11612
+ return text7.ok ? parseJsonObject(text7.value) : text7;
11364
11613
  };
11365
11614
  var readBaseline2 = (runtime, repo, owner) => {
11366
11615
  const parsed = readObject(runtime, fileOf(repo, "baseline", owner));
@@ -11426,7 +11675,7 @@ var foreignFiles = (runtime, repo, owner, targets2) => {
11426
11675
  };
11427
11676
 
11428
11677
  // src/git/gate-status.ts
11429
- var lines = (text6) => text6.split("\n").filter((line2) => line2.trim() !== "");
11678
+ var lines = (text7) => text7.split("\n").filter((line2) => line2.trim() !== "");
11430
11679
  var unpushedSince = (runtime, repo, since) => {
11431
11680
  if (lines(runtime.git(["remote"], repo.root).stdout).length === 0) return [];
11432
11681
  const result = runtime.git(["rev-list", `${since}..HEAD`, "--not", "--remotes"], repo.root);
@@ -11449,9 +11698,9 @@ var mergeGateStatus = (runtime, repo, owner) => {
11449
11698
  const commits = since === void 0 ? [] : mergeCommitsSince(runtime, repo, since);
11450
11699
  return [...pending, ...commits.map((commit2) => ({ commit: commit2, report: verifyMerge(runtime, repo, { rev: commit2 }) }))];
11451
11700
  };
11452
- var findingLabel = (finding3) => {
11453
- const where = finding3.line === void 0 ? finding3.file : `${finding3.file}:${String(finding3.line)}`;
11454
- return `${where} ${finding3.text === "" ? finding3.kind : `"${finding3.text}"`}`;
11701
+ var findingLabel = (finding4) => {
11702
+ const where = finding4.line === void 0 ? finding4.file : `${finding4.file}:${String(finding4.line)}`;
11703
+ return `${where} ${finding4.text === "" ? finding4.kind : `"${finding4.text}"`}`;
11455
11704
  };
11456
11705
 
11457
11706
  // src/git/repo.ts
@@ -11535,10 +11784,10 @@ var repoFacts = (runtime, repo, config = loadProjectConfig(runtime, repo.root))
11535
11784
  };
11536
11785
 
11537
11786
  // src/gate/checks/worktree.ts
11538
- var MAX_LISTED6 = 8;
11787
+ var MAX_LISTED7 = 8;
11539
11788
  var listed3 = (items) => {
11540
- const head = items.slice(0, MAX_LISTED6).join(", ");
11541
- return items.length > MAX_LISTED6 ? `${head} (+${String(items.length - MAX_LISTED6)})` : head;
11789
+ const head = items.slice(0, MAX_LISTED7).join(", ");
11790
+ return items.length > MAX_LISTED7 ? `${head} (+${String(items.length - MAX_LISTED7)})` : head;
11542
11791
  };
11543
11792
  var repoOf = (ctx) => openRepo(ctx.runtime, ctx.paths.root);
11544
11793
  var ownerOf = (ctx) => ownerKey(ctx.sessionId);
@@ -11597,7 +11846,8 @@ var DEFAULT_CHECKS = {
11597
11846
  merge: mergeCheck,
11598
11847
  review: reviewCheck,
11599
11848
  callers: callersCheck,
11600
- runtime: runtimeCheck
11849
+ runtime: runtimeCheck,
11850
+ look: lookCheck
11601
11851
  };
11602
11852
 
11603
11853
  // src/gate/run.ts
@@ -11791,7 +12041,7 @@ var subagentStopGate = (input) => {
11791
12041
  };
11792
12042
 
11793
12043
  // src/gate/hooks/subagent.ts
11794
- import { join as join26 } from "node:path";
12044
+ import { join as join29 } from "node:path";
11795
12045
  var NO_CONTRACT_AGENTS = /(^|:)(reviewer|verifier|explore|plan|claude-code-guide|statusline-setup)$/i;
11796
12046
  var KNOWN_KINDS = /* @__PURE__ */ new Set([
11797
12047
  "command",
@@ -11807,7 +12057,8 @@ var KNOWN_KINDS = /* @__PURE__ */ new Set([
11807
12057
  "merge",
11808
12058
  "review",
11809
12059
  "callers",
11810
- "runtime"
12060
+ "runtime",
12061
+ "look"
11811
12062
  ]);
11812
12063
  var requirementText = (t, check2) => {
11813
12064
  const key = `gate.require.${check2.kind}`;
@@ -11827,13 +12078,13 @@ var subagentStart = (input) => {
11827
12078
  const { runtime, t } = input;
11828
12079
  const active = readState(runtime, paths).active;
11829
12080
  if (active === void 0) return { kind: "allow" };
11830
- const disk = (relative9) => {
11831
- const text6 = runtime.readText(toAbsolutePath(join26(paths.root, relative9)));
11832
- return text6.ok ? text6.value : void 0;
12081
+ const disk = (relative11) => {
12082
+ const text7 = runtime.readText(toAbsolutePath(join29(paths.root, relative11)));
12083
+ return text7.ok ? text7.value : void 0;
11833
12084
  };
11834
12085
  const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
11835
12086
  if (!loaded.ok) return { kind: "allow" };
11836
- const briefing = runtime.stat(toAbsolutePath(join26(paths.dir, "briefing.md"))) !== void 0;
12087
+ const briefing = runtime.stat(toAbsolutePath(join29(paths.dir, "briefing.md"))) !== void 0;
11837
12088
  return { kind: "allow", output: contextOutput("SubagentStart", contractFacts(t, loaded.value.contract, briefing)) };
11838
12089
  };
11839
12090
 
@@ -11846,16 +12097,17 @@ var PORTAO_HANDLERS = {
11846
12097
  SubagentStart: [subagentStart],
11847
12098
  PreCompact: [preCompact],
11848
12099
  SessionStart: [sessionStart],
11849
- ConfigChange: [configChange]
12100
+ ConfigChange: [configChange],
12101
+ PostToolUse: [lookReadTracker]
11850
12102
  };
11851
12103
 
11852
12104
  // src/prod/guard.ts
11853
- import { join as join28 } from "node:path";
12105
+ import { join as join31 } from "node:path";
11854
12106
 
11855
12107
  // src/git/command-text.ts
11856
12108
  var MAX_SHOWN = 160;
11857
- var shown = (text6) => {
11858
- const flat = text6.replace(/\s+/g, " ").trim();
12109
+ var shown = (text7) => {
12110
+ const flat = text7.replace(/\s+/g, " ").trim();
11859
12111
  return flat.length > MAX_SHOWN ? `${flat.slice(0, MAX_SHOWN - 3)}...` : flat;
11860
12112
  };
11861
12113
  var commandText = (command) => shown([command.program, ...command.args].join(" "));
@@ -12192,7 +12444,7 @@ var remoteHostsOf = (command) => {
12192
12444
  };
12193
12445
 
12194
12446
  // src/prod/targets.ts
12195
- import { join as join27 } from "node:path";
12447
+ import { join as join30 } from "node:path";
12196
12448
  var DEPLOY_FILES = [
12197
12449
  "deploy.sh",
12198
12450
  "scripts/deploy.sh",
@@ -12205,9 +12457,9 @@ var SSH_URL = /ssh:\/\/(?:[^@\s/]+@)?([A-Za-z0-9.-]+)/g;
12205
12457
  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;
12206
12458
  var hostKey = (raw) => raw.trim().toLowerCase().replace(/^[^@]*@/, "").replace(/^\[([^\]]+)\].*$/, "$1").replace(/:\d+$/, "").replace(/\.$/, "");
12207
12459
  var shellLine = (line2) => line2.replace(/^\s*(?:-\s*)?(?:run:\s*\|?)?/, "");
12208
- var hostsInText = (text6) => {
12209
- const urls = [...text6.matchAll(SSH_URL)].map((match) => match[1] ?? "");
12210
- const lines3 = text6.split(/\r?\n/);
12460
+ var hostsInText = (text7) => {
12461
+ const urls = [...text7.matchAll(SSH_URL)].map((match) => match[1] ?? "");
12462
+ const lines3 = text7.split(/\r?\n/);
12211
12463
  const keyed = lines3.flatMap((line2) => {
12212
12464
  const match = HOST_KEY.exec(line2);
12213
12465
  return match?.[1] === void 0 ? [] : [match[1]];
@@ -12216,13 +12468,13 @@ var hostsInText = (text6) => {
12216
12468
  return [...urls, ...keyed, ...commands].filter((host) => host !== "" && !host.includes("$"));
12217
12469
  };
12218
12470
  var workflowFiles = (runtime, root) => {
12219
- const listed4 = runtime.listDir(toAbsolutePath(join27(root, ".github", "workflows")));
12471
+ const listed4 = runtime.listDir(toAbsolutePath(join30(root, ".github", "workflows")));
12220
12472
  if (!listed4.ok) return [];
12221
12473
  return listed4.value.filter((name) => /deploy/i.test(name) && /\.ya?ml$/i.test(name)).map((name) => `.github/workflows/${name}`);
12222
12474
  };
12223
12475
  var detectedHosts = (runtime, root, files) => files.flatMap((file) => {
12224
- const text6 = runtime.readText(toAbsolutePath(join27(root, file)));
12225
- return text6.ok ? hostsInText(text6.value) : [];
12476
+ const text7 = runtime.readText(toAbsolutePath(join30(root, file)));
12477
+ return text7.ok ? hostsInText(text7.value) : [];
12226
12478
  });
12227
12479
  var loadProdTargets = (runtime, root, config) => {
12228
12480
  const workflows = workflowFiles(runtime, root);
@@ -12233,7 +12485,7 @@ var loadProdTargets = (runtime, root, config) => {
12233
12485
  ...detectedHosts(runtime, root, deployFiles)
12234
12486
  ];
12235
12487
  const declaredDeploy = configAt(config, "prod", "deploy");
12236
- const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join27(root, file))) !== void 0);
12488
+ const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join30(root, file))) !== void 0);
12237
12489
  return {
12238
12490
  hosts: new Set(hosts.map(hostKey).filter((host) => host !== "")),
12239
12491
  kubeContexts: configList(config, "prod", "kube_contexts"),
@@ -12392,8 +12644,8 @@ var prodGuard = (runtime, root, t) => {
12392
12644
  if (!PROD_PROGRAMS.has(command.program)) return void 0;
12393
12645
  const hit3 = prodHit(command, targets2(), isProd);
12394
12646
  if (hit3 === void 0) return void 0;
12395
- const text6 = commandText(command);
12396
- return hit3.effect === "read" ? { kind: "read", hit: hit3, command: text6 } : blockOf(t, hit3, text6, targets2().deployPath);
12647
+ const text7 = commandText(command);
12648
+ return hit3.effect === "read" ? { kind: "read", hit: hit3, command: text7 } : blockOf(t, hit3, text7, targets2().deployPath);
12397
12649
  }
12398
12650
  };
12399
12651
  };
@@ -12410,11 +12662,11 @@ var recordEvidence = (runtime, root, owner, reads) => {
12410
12662
  ] : []
12411
12663
  );
12412
12664
  if (lines3.length === 0) return;
12413
- const path = toAbsolutePath(join28(root, EVIDENCE_FILE));
12414
- runtime.mkdir(toAbsolutePath(join28(root, ".finitemonkey")));
12665
+ const path = toAbsolutePath(join31(root, EVIDENCE_FILE));
12666
+ runtime.mkdir(toAbsolutePath(join31(root, ".finitemonkey")));
12415
12667
  const before = runtime.readText(path);
12416
- const text6 = before.ok ? before.value : "";
12417
- runtime.writeAtomic(path, `${text6}${text6 === "" || text6.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
12668
+ const text7 = before.ok ? before.value : "";
12669
+ runtime.writeAtomic(path, `${text7}${text7 === "" || text7.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
12418
12670
  `);
12419
12671
  };
12420
12672
 
@@ -12429,15 +12681,15 @@ var editTexts = (input, file) => {
12429
12681
  if (!Array.isArray(edits)) return [];
12430
12682
  return edits.flatMap((edit) => {
12431
12683
  if (!isRecord(edit)) return [];
12432
- const text6 = optionalString(edit, "new_string");
12433
- return text6 === void 0 ? [] : [{ file, text: text6, whole: false }];
12684
+ const text7 = optionalString(edit, "new_string");
12685
+ return text7 === void 0 ? [] : [{ file, text: text7, whole: false }];
12434
12686
  });
12435
12687
  };
12436
12688
  var writtenTexts = (tool, input) => {
12437
12689
  const file = optionalString(input, "file_path") ?? optionalString(input, "notebook_path") ?? "";
12438
12690
  const single2 = (key, whole) => {
12439
- const text6 = optionalString(input, key);
12440
- return text6 === void 0 ? [] : [{ file, text: text6, whole }];
12691
+ const text7 = optionalString(input, key);
12692
+ return text7 === void 0 ? [] : [{ file, text: text7, whole }];
12441
12693
  };
12442
12694
  switch (tool) {
12443
12695
  case "Write":
@@ -13119,24 +13371,24 @@ var withChain = (facts, chain) => {
13119
13371
  const branch = chain?.branch;
13120
13372
  return branch === void 0 ? facts : { ...facts, currentBranch: () => branch };
13121
13373
  };
13122
- var decide = (context, repo, verdict, facts, text6) => {
13374
+ var decide = (context, repo, verdict, facts, text7) => {
13123
13375
  switch (verdict.kind) {
13124
13376
  case "pass":
13125
13377
  return { kind: "pass" };
13126
13378
  case "block":
13127
- return { kind: "block", rule: verdict.rule, subject: verdict.subject, command: text6 };
13379
+ return { kind: "block", rule: verdict.rule, subject: verdict.subject, command: text7 };
13128
13380
  case "snapshot":
13129
- return { kind: "snapshot", repo, op: verdict.op, command: text6 };
13381
+ return { kind: "snapshot", repo, op: verdict.op, command: text7 };
13130
13382
  case "discard": {
13131
13383
  const entries = facts.dirty();
13132
- if (entries === void 0) return { kind: "unknown", command: text6 };
13384
+ if (entries === void 0) return { kind: "unknown", command: text7 };
13133
13385
  const foreign = foreignFiles(
13134
13386
  context.runtime,
13135
13387
  repo,
13136
13388
  context.owner,
13137
13389
  targetsOf(entries, verdict.discard, facts.prefix)
13138
13390
  );
13139
- return foreign.length > 0 ? { kind: "foreign", files: foreign, command: text6 } : { kind: "snapshot", repo, op: verdict.op, command: text6 };
13391
+ return foreign.length > 0 ? { kind: "foreign", files: foreign, command: text7 } : { kind: "snapshot", repo, op: verdict.op, command: text7 };
13140
13392
  }
13141
13393
  }
13142
13394
  };
@@ -13223,10 +13475,10 @@ var listWorktrees = (runtime, cwd) => {
13223
13475
  };
13224
13476
 
13225
13477
  // src/git/journal.ts
13226
- import { join as join29 } from "node:path";
13478
+ import { join as join32 } from "node:path";
13227
13479
  var JOURNAL_FILE = ".finitemonkey/git-journal.jsonl";
13228
13480
  var JOURNAL_SCHEMA = "finitemonkey/git-journal@1";
13229
- var journalPath = (root) => toAbsolutePath(join29(root, JOURNAL_FILE));
13481
+ var journalPath = (root) => toAbsolutePath(join32(root, JOURNAL_FILE));
13230
13482
  var headsOf = (value) => isRecord(value) ? Object.fromEntries(
13231
13483
  Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
13232
13484
  ) : {};
@@ -13253,16 +13505,16 @@ var parseJournalLine = (line2) => {
13253
13505
  };
13254
13506
  };
13255
13507
  var readJournal = (runtime, root) => {
13256
- const text6 = runtime.readText(journalPath(root));
13257
- if (!text6.ok) return [];
13258
- return text6.value.split("\n").flatMap((line2) => {
13508
+ const text7 = runtime.readText(journalPath(root));
13509
+ if (!text7.ok) return [];
13510
+ return text7.value.split("\n").flatMap((line2) => {
13259
13511
  const entry2 = line2.trim() === "" ? void 0 : parseJournalLine(line2);
13260
13512
  return entry2 === void 0 ? [] : [entry2];
13261
13513
  });
13262
13514
  };
13263
13515
  var appendJournal = (runtime, root, entry2) => {
13264
13516
  const path = journalPath(root);
13265
- const made = runtime.mkdir(toAbsolutePath(join29(root, ".finitemonkey")));
13517
+ const made = runtime.mkdir(toAbsolutePath(join32(root, ".finitemonkey")));
13266
13518
  if (!made.ok) return made;
13267
13519
  const previous = runtime.readText(path);
13268
13520
  const before = previous.ok ? previous.value : "";
@@ -13446,9 +13698,9 @@ var review = (input, commands, owner) => {
13446
13698
  return { blocked: void 0, snapshots, reads };
13447
13699
  };
13448
13700
  var gitPreToolUse = (input) => {
13449
- const text6 = shellText(input.payload);
13450
- if (text6 === void 0) return ALLOW;
13451
- const commands = normalizeCommand(text6, dialectOf(toolName2(input.payload))).commands;
13701
+ const text7 = shellText(input.payload);
13702
+ if (text7 === void 0) return ALLOW;
13703
+ const commands = normalizeCommand(text7, dialectOf(toolName2(input.payload))).commands;
13452
13704
  if (commands.length === 0) return ALLOW;
13453
13705
  const owner = ownerOf2(input.payload);
13454
13706
  const result = review(input, commands, owner);
@@ -13465,7 +13717,7 @@ var gitPreToolUse = (input) => {
13465
13717
  };
13466
13718
 
13467
13719
  // src/git/hooks/session.ts
13468
- import { dirname as dirname6, relative as relative5 } from "node:path";
13720
+ import { dirname as dirname6, relative as relative7 } from "node:path";
13469
13721
  var ALLOW2 = { kind: "allow" };
13470
13722
  var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
13471
13723
  var gitSessionStart = (input) => {
@@ -13480,7 +13732,7 @@ var editedPath = (input) => {
13480
13732
  const file = toAbsolutePath(raw, input.cwd);
13481
13733
  const repo = openRepo(input.runtime, toAbsolutePath(dirname6(file))) ?? openRepo(input.runtime, input.cwd);
13482
13734
  if (repo === void 0) return void 0;
13483
- const path = toPosixPath(relative5(input.runtime.realpath(repo.root), input.runtime.realpath(file)));
13735
+ const path = toPosixPath(relative7(input.runtime.realpath(repo.root), input.runtime.realpath(file)));
13484
13736
  return path === "" || path.startsWith("..") || path.startsWith(STATE_PREFIX) ? void 0 : { repo, path };
13485
13737
  };
13486
13738
  var gitPostToolUse = (input) => {
@@ -13508,8 +13760,8 @@ var GIT_HANDLERS = {
13508
13760
  var SHADOW_FILE = "shadow.json";
13509
13761
  var numberOf = (value) => typeof value === "number" && Number.isInteger(value) ? value : 0;
13510
13762
  var readShadow = (runtime, paths) => {
13511
- const text6 = runtime.readText(paths.cache(SHADOW_FILE));
13512
- const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
13763
+ const text7 = runtime.readText(paths.cache(SHADOW_FILE));
13764
+ const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
13513
13765
  if (parsed?.ok !== true) return {};
13514
13766
  return Object.fromEntries(
13515
13767
  Object.entries(parsed.value).flatMap(
@@ -13583,7 +13835,7 @@ var withPolicy = (set) => {
13583
13835
  };
13584
13836
 
13585
13837
  // src/prose/marker-guard.ts
13586
- import { relative as relative6 } from "node:path";
13838
+ import { relative as relative8 } from "node:path";
13587
13839
 
13588
13840
  // src/prose/shell-words.ts
13589
13841
  var HEREDOC2 = /<<-?[ \t]*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1[^\n]*\n([\s\S]*?)\n[ \t]*\2[ \t]*(?=\n|$)/g;
@@ -13593,11 +13845,11 @@ var ASSIGNMENT3 = /^[A-Za-z_][A-Za-z0-9_]*=/;
13593
13845
  var WRAPPERS2 = /* @__PURE__ */ new Set(["sudo", "command", "exec", "time", "nice", "nohup", "builtin", "env"]);
13594
13846
  var liftHeredocs = (command) => {
13595
13847
  const heredocs = [];
13596
- const text6 = command.replace(HEREDOC2, (_match, _quote, _tag, body2) => {
13848
+ const text7 = command.replace(HEREDOC2, (_match, _quote, _tag, body2) => {
13597
13849
  heredocs.push(body2);
13598
13850
  return `<<__FM_HEREDOC_${String(heredocs.length - 1)}__`;
13599
13851
  });
13600
- return { text: text6, heredocs };
13852
+ return { text: text7, heredocs };
13601
13853
  };
13602
13854
  var endWord = (scan) => {
13603
13855
  if (scan.word !== void 0) scan.words.push(scan.word);
@@ -13608,23 +13860,23 @@ var endSegment = (scan) => {
13608
13860
  if (scan.words.length > 0) scan.segments.push(scan.words);
13609
13861
  scan.words = [];
13610
13862
  };
13611
- var quotedEnd2 = (text6, from, quote) => {
13863
+ var quotedEnd2 = (text7, from, quote) => {
13612
13864
  let index = from;
13613
- while (index < text6.length && text6[index] !== quote) index += quote === '"' && text6[index] === "\\" ? 2 : 1;
13865
+ while (index < text7.length && text7[index] !== quote) index += quote === '"' && text7[index] === "\\" ? 2 : 1;
13614
13866
  return index;
13615
13867
  };
13616
13868
  var unescape = (value) => value.replace(/\\(["\\$`])/g, "$1");
13617
- var splitWords = (text6) => {
13869
+ var splitWords = (text7) => {
13618
13870
  const scan = { words: [], segments: [], word: void 0 };
13619
- for (let index = 0; index < text6.length; index += 1) {
13620
- const char = text6[index] ?? "";
13871
+ for (let index = 0; index < text7.length; index += 1) {
13872
+ const char = text7[index] ?? "";
13621
13873
  if (char === '"' || char === "'") {
13622
- const end = quotedEnd2(text6, index + 1, char);
13623
- const inner = text6.slice(index + 1, end);
13874
+ const end = quotedEnd2(text7, index + 1, char);
13875
+ const inner = text7.slice(index + 1, end);
13624
13876
  scan.word = (scan.word ?? "") + (char === '"' ? unescape(inner) : inner);
13625
13877
  index = end;
13626
- } else if (char === "\\" && index + 1 < text6.length) {
13627
- scan.word = (scan.word ?? "") + (text6[index + 1] === "\n" ? "" : text6[index + 1] ?? "");
13878
+ } else if (char === "\\" && index + 1 < text7.length) {
13879
+ scan.word = (scan.word ?? "") + (text7[index + 1] === "\n" ? "" : text7[index + 1] ?? "");
13628
13880
  index += 1;
13629
13881
  } else if (SEPARATOR.test(char)) {
13630
13882
  endSegment(scan);
@@ -13645,8 +13897,8 @@ var segmentOf = (words) => {
13645
13897
  return { program: program.split(/[\\/]/).pop() ?? program, args: words.slice(index + 1) };
13646
13898
  };
13647
13899
  var shellWords = (command) => {
13648
- const { text: text6, heredocs } = liftHeredocs(command);
13649
- const segments = splitWords(text6).flatMap((words) => {
13900
+ const { text: text7, heredocs } = liftHeredocs(command);
13901
+ const segments = splitWords(text7).flatMap((words) => {
13650
13902
  const segment = segmentOf(words);
13651
13903
  return segment === void 0 ? [] : [segment];
13652
13904
  });
@@ -13690,7 +13942,7 @@ var addsMarker = (input, tool, inputs, file) => {
13690
13942
  var isReadOnly = (program, args2) => READ_ONLY.has(program) || program === "git" && GIT_READ_ONLY.has(args2[0] ?? "");
13691
13943
  var shellAddsMarker = (command) => markerCount(command) > 0 && shellWords(command).segments.some((segment) => !isReadOnly(segment.program, segment.args));
13692
13944
  var shown2 = (cwd, file) => {
13693
- const rel = toPosixPath(relative6(cwd, file));
13945
+ const rel = toPosixPath(relative8(cwd, file));
13694
13946
  return file === "" || rel.startsWith("..") ? toPosixPath(file) : rel;
13695
13947
  };
13696
13948
  var blocked = (input, file) => ({
@@ -13746,7 +13998,7 @@ var sourceOf = (value, isFile, segment, heredocs) => {
13746
13998
  if (inline3 !== void 0) return { kind: "inline", text: inline3 };
13747
13999
  if (!isFile) return { kind: "inline", text: value };
13748
14000
  if (value !== "-") return { kind: "file", path: value };
13749
- const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text6) => text6 !== void 0);
14001
+ const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text7) => text7 !== void 0);
13750
14002
  return { kind: "inline", text: piped ?? "" };
13751
14003
  };
13752
14004
  var collect3 = (target2, segment, args2, spec2, heredocs) => {
@@ -13843,11 +14095,11 @@ var textOf2 = (input, found) => {
13843
14095
  return accept(parts.join("\n\n"));
13844
14096
  };
13845
14097
  var decisionFor = (input, found, blocking2) => {
13846
- const text6 = textOf2(input, found);
13847
- if (!text6.ok) {
13848
- return blocking2 ? { kind: "block", reason: input.t("prose.block.unreadable", { ...text6.error }) } : void 0;
14098
+ const text7 = textOf2(input, found);
14099
+ if (!text7.ok) {
14100
+ return blocking2 ? { kind: "block", reason: input.t("prose.block.unreadable", { ...text7.error }) } : void 0;
13849
14101
  }
13850
- const report2 = scoreProse(text6.value, { context: "record" });
14102
+ const report2 = scoreProse(text7.value, { context: "record" });
13851
14103
  if (report2.allowed || report2.spans.length === 0) return void 0;
13852
14104
  const params2 = {
13853
14105
  target: input.t(TARGET_KEYS[found.target]),
@@ -13876,27 +14128,27 @@ var PROSE_HANDLERS = {
13876
14128
  };
13877
14129
 
13878
14130
  // src/secrets/guards/describe.ts
13879
- var MAX_LISTED7 = 5;
14131
+ var MAX_LISTED8 = 5;
13880
14132
  var describeFindings = (t, items) => {
13881
- const listed4 = items.slice(0, MAX_LISTED7).map(({ ruleId, where }) => t("secrets.found_item", { rule: ruleId, where }));
14133
+ const listed4 = items.slice(0, MAX_LISTED8).map(({ ruleId, where }) => t("secrets.found_item", { rule: ruleId, where }));
13882
14134
  const rest = items.length - listed4.length;
13883
14135
  return rest > 0 ? `${listed4.join("; ")}; ${t("secrets.found_more", { count: rest })}` : listed4.join("; ");
13884
14136
  };
13885
- var lineWhere = (t, finding3) => t("secrets.where.line", { line: finding3.line, column: finding3.column });
14137
+ var lineWhere = (t, finding4) => t("secrets.where.line", { line: finding4.line, column: finding4.column });
13886
14138
 
13887
14139
  // src/secrets/guards/exposure.ts
13888
14140
  var HOOK_ACTOR = "finitemonkey";
13889
- var liveFindings = (input, texts) => {
14141
+ var liveFindings = (input, texts2) => {
13890
14142
  const baseline = loadBaseline(input.runtime, input.cwd);
13891
14143
  const accepted2 = acceptedFingerprints(baseline.ok ? baseline.value : EMPTY_BASELINE, input.runtime.now());
13892
- return texts.flatMap((text6) => scanText(text6, { rules: CREDENTIAL_RULES })).filter((finding3) => !accepted2.has(finding3.fingerprint));
14144
+ return texts2.flatMap((text7) => scanText(text7, { rules: CREDENTIAL_RULES })).filter((finding4) => !accepted2.has(finding4.fingerprint));
13893
14145
  };
13894
14146
  var record2 = (input, findings, draft, where) => {
13895
- const ids2 = findings.map((finding3) => {
14147
+ const ids2 = findings.map((finding4) => {
13896
14148
  const saved = recordIncident(
13897
14149
  input.runtime,
13898
14150
  input.cwd,
13899
- { ...draft, rule: finding3.ruleId, fingerprint: finding3.fingerprint, where: `${where}:${String(finding3.line)}` },
14151
+ { ...draft, rule: finding4.ruleId, fingerprint: finding4.fingerprint, where: `${where}:${String(finding4.line)}` },
13900
14152
  HOOK_ACTOR
13901
14153
  );
13902
14154
  return saved.ok ? saved.value.id : input.t("secrets.incident.unsaved", { detail: saved.error });
@@ -13908,7 +14160,7 @@ var secretsUserPromptSubmit = (input) => {
13908
14160
  const findings = liveFindings(input, [prompt]);
13909
14161
  if (findings.length === 0) return { kind: "allow" };
13910
14162
  const incident = record2(input, findings, { source: "prompt", state: "exposicao-local" }, "prompt");
13911
- const items = findings.map((finding3) => ({ ruleId: finding3.ruleId, where: lineWhere(input.t, finding3) }));
14163
+ const items = findings.map((finding4) => ({ ruleId: finding4.ruleId, where: lineWhere(input.t, finding4) }));
13912
14164
  return {
13913
14165
  kind: "block",
13914
14166
  reason: input.t("secrets.block.prompt", { found: describeFindings(input.t, items), incident })
@@ -13919,7 +14171,7 @@ var secretsPostToolUse = (input) => {
13919
14171
  if (findings.length === 0) return { kind: "allow" };
13920
14172
  const tool = toolName2(input.payload) || "tool";
13921
14173
  const incident = record2(input, findings, { source: "tool-output", state: "aberto" }, `tool-output:${tool}`);
13922
- const items = findings.map((finding3) => ({ ruleId: finding3.ruleId, where: lineWhere(input.t, finding3) }));
14174
+ const items = findings.map((finding4) => ({ ruleId: finding4.ruleId, where: lineWhere(input.t, finding4) }));
13923
14175
  const context = input.t("secrets.post.context", { tool, found: describeFindings(input.t, items), incident });
13924
14176
  return {
13925
14177
  kind: "allow",
@@ -13928,7 +14180,7 @@ var secretsPostToolUse = (input) => {
13928
14180
  };
13929
14181
 
13930
14182
  // src/secrets/guards/pre-tool.ts
13931
- import { relative as relative7 } from "node:path";
14183
+ import { relative as relative9 } from "node:path";
13932
14184
 
13933
14185
  // src/secrets/guards/bash-rules.ts
13934
14186
  var READERS = /* @__PURE__ */ new Set([
@@ -14025,7 +14277,7 @@ var commitIntent = (segments) => {
14025
14277
  var ALLOW5 = { kind: "allow" };
14026
14278
  var relativeFile = (cwd, file) => {
14027
14279
  if (file === "") return file;
14028
- const rel = relative7(cwd, file).split("\\").join("/");
14280
+ const rel = relative9(cwd, file).split("\\").join("/");
14029
14281
  return rel.startsWith("..") || rel === "" ? file : rel;
14030
14282
  };
14031
14283
  var warningDecision = (t, warnings) => {
@@ -14040,38 +14292,38 @@ var writeDecision = (input, tool) => {
14040
14292
  const file = relativeFile(input.cwd, written.file);
14041
14293
  const all = scanText(written.text, { path: file });
14042
14294
  warnings.push(
14043
- ...all.filter(isWarning).map((finding3) => ({
14044
- ruleId: finding3.ruleId,
14045
- where: t("secrets.where.file", { file, line: finding3.line, column: finding3.column })
14295
+ ...all.filter(isWarning).map((finding4) => ({
14296
+ ruleId: finding4.ruleId,
14297
+ where: t("secrets.where.file", { file, line: finding4.line, column: finding4.column })
14046
14298
  }))
14047
14299
  );
14048
14300
  const found = blocking(all);
14049
14301
  if (found.length === 0) continue;
14050
- const items = found.map((finding3) => ({
14051
- ruleId: finding3.ruleId,
14052
- where: written.whole ? t("secrets.where.file", { file, line: finding3.line, column: finding3.column }) : t("secrets.where.new_text", { file, line: finding3.line })
14302
+ const items = found.map((finding4) => ({
14303
+ ruleId: finding4.ruleId,
14304
+ where: written.whole ? t("secrets.where.file", { file, line: finding4.line, column: finding4.column }) : t("secrets.where.new_text", { file, line: finding4.line })
14053
14305
  }));
14054
14306
  return { kind: "block", reason: t("secrets.block.write", { file, found: describeFindings(t, items) }) };
14055
14307
  }
14056
14308
  return warningDecision(t, warnings);
14057
14309
  };
14058
- var bashMessage = (t, finding3) => {
14059
- switch (finding3.kind) {
14310
+ var bashMessage = (t, finding4) => {
14311
+ switch (finding4.kind) {
14060
14312
  case "read":
14061
- return t("secrets.block.read", { target: finding3.target });
14313
+ return t("secrets.block.read", { target: finding4.target });
14062
14314
  case "env-dump":
14063
- return t("secrets.block.env_dump", { command: finding3.command });
14315
+ return t("secrets.block.env_dump", { command: finding4.command });
14064
14316
  case "person-only":
14065
- return t("secrets.block.person_only", { command: finding3.command });
14317
+ return t("secrets.block.person_only", { command: finding4.command });
14066
14318
  }
14067
14319
  };
14068
14320
  var commitDecision = (input, includeUnstaged) => {
14069
14321
  const staged = scanStaged(input.runtime, input.cwd, includeUnstaged);
14070
14322
  const found = staged.ok ? blocking(staged.value) : [];
14071
14323
  if (found.length === 0) return ALLOW5;
14072
- const items = found.map((finding3) => ({
14073
- ruleId: finding3.ruleId,
14074
- where: input.t("secrets.where.file", { file: finding3.file, line: finding3.line, column: finding3.column })
14324
+ const items = found.map((finding4) => ({
14325
+ ruleId: finding4.ruleId,
14326
+ where: input.t("secrets.where.file", { file: finding4.file, line: finding4.line, column: finding4.column })
14075
14327
  }));
14076
14328
  return { kind: "block", reason: input.t("secrets.block.commit", { found: describeFindings(input.t, items) }) };
14077
14329
  };
@@ -14083,7 +14335,7 @@ var bashDecision = (input) => {
14083
14335
  if (first !== void 0) return { kind: "block", reason: bashMessage(input.t, first) };
14084
14336
  const found = blocking(scanText(command));
14085
14337
  if (found.length > 0) {
14086
- const items = found.map((finding3) => ({ ruleId: finding3.ruleId, where: lineWhere(input.t, finding3) }));
14338
+ const items = found.map((finding4) => ({ ruleId: finding4.ruleId, where: lineWhere(input.t, finding4) }));
14087
14339
  return { kind: "block", reason: input.t("secrets.block.bash", { found: describeFindings(input.t, items) }) };
14088
14340
  }
14089
14341
  const commit2 = commitIntent(segments);
@@ -14134,12 +14386,12 @@ var secretsSessionStart = (input) => {
14134
14386
  const remotes = remotesWithCredentials(input.runtime, input.cwd);
14135
14387
  if (remotes.length === 0) return { kind: "allow" };
14136
14388
  const params2 = { remotes: remotes.join(", "), remote: remotes[0] ?? "origin" };
14137
- const text6 = input.t("secrets.session.remote_credential", params2);
14389
+ const text7 = input.t("secrets.session.remote_credential", params2);
14138
14390
  return {
14139
14391
  kind: "allow",
14140
14392
  output: {
14141
- systemMessage: `finitemonkey: ${text6}`,
14142
- hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text6 }
14393
+ systemMessage: `finitemonkey: ${text7}`,
14394
+ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text7 }
14143
14395
  }
14144
14396
  };
14145
14397
  };
@@ -14216,9 +14468,9 @@ var EMPTY_PAYLOAD = {
14216
14468
  stopHookActive: false,
14217
14469
  raw: {}
14218
14470
  };
14219
- var parseHookPayload = (event, text6, interactive) => {
14220
- if (interactive && text6.trim() === "") return accept(EMPTY_PAYLOAD);
14221
- const parsed = parseJsonObject(text6);
14471
+ var parseHookPayload = (event, text7, interactive) => {
14472
+ if (interactive && text7.trim() === "") return accept(EMPTY_PAYLOAD);
14473
+ const parsed = parseJsonObject(text7);
14222
14474
  if (!parsed.ok) return reject(`stdin: ${parsed.error}`);
14223
14475
  const payload = {
14224
14476
  hookEventName: optionalString(parsed.value, "hook_event_name"),
@@ -14278,8 +14530,8 @@ var HOOK_PROBE_ENV = "FINITEMONKEY_HOOK_PROBE";
14278
14530
  var messageOf2 = (error) => error instanceof Error ? error.message : String(error);
14279
14531
  var decide2 = (input, handlers) => composeDecisions(input, handlers[input.event] ?? [allow]);
14280
14532
  var evaluate = async (event, options) => {
14281
- const text6 = await readAll(options.stdin);
14282
- const parsed = parseHookPayload(event, text6, options.stdin.isTTY === true);
14533
+ const text7 = await readAll(options.stdin);
14534
+ const parsed = parseHookPayload(event, text7, options.stdin.isTTY === true);
14283
14535
  if (!parsed.ok) return { kind: "failed", payload: void 0, detail: parsed.error };
14284
14536
  const payload = parsed.value;
14285
14537
  if (options.env?.[HOOK_PROBE_ENV] === "1") return { kind: "decided", payload, decision: { kind: "allow" } };
@@ -14356,7 +14608,7 @@ var runHook = async (event, options) => {
14356
14608
  var PROBE_TIMEOUT_MS2 = 1e4;
14357
14609
  var SLOW_HOOK_MS = 1500;
14358
14610
  var DEFAULT_TIMEOUT_S = 60;
14359
- var expand = (text6, vars) => text6.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
14611
+ var expand = (text7, vars) => text7.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
14360
14612
  const name = braced ?? bare ?? "";
14361
14613
  return vars[name] ?? match;
14362
14614
  });
@@ -14405,7 +14657,7 @@ var run2 = (exec, invocation, cwd, env, input) => exec({
14405
14657
  env: { ...env, [HOOK_PROBE_ENV]: "1" },
14406
14658
  ...input === void 0 ? {} : { input }
14407
14659
  });
14408
- var firstLine = (text6) => maskText(text6.trim().split("\n")[0] ?? "").slice(0, 160);
14660
+ var firstLine = (text7) => maskText(text7.trim().split("\n")[0] ?? "").slice(0, 160);
14409
14661
  var classify2 = (result) => {
14410
14662
  if (!result.found) return { kind: "failed", problem: "missing", detail: "" };
14411
14663
  if (result.timedOut) return { kind: "failed", problem: "timeout", detail: "" };
@@ -14617,7 +14869,7 @@ var hookSchemaProblems = (hooks, claude) => {
14617
14869
  };
14618
14870
 
14619
14871
  // src/doctor/hook-sources.ts
14620
- import { join as join30 } from "node:path";
14872
+ import { join as join33 } from "node:path";
14621
14873
  var entriesOf = (source, hooks, own) => Object.entries(asRecord(hooks)).flatMap(
14622
14874
  ([event, groups]) => asList(groups).flatMap((group) => {
14623
14875
  const record3 = asRecord(group);
@@ -14630,31 +14882,31 @@ var projectHookFiles = (env) => readSettings(env).flatMap(({ scope: scope2, path
14630
14882
  const source = PROJECT_SCOPES[scope2];
14631
14883
  return source === void 0 || !("hooks" in settings) ? [] : [{ source, path, hooks: settings["hooks"] }];
14632
14884
  });
14633
- var pluginsDir = ({ env, runtime }) => join30(env["CLAUDE_CONFIG_DIR"] ?? join30(runtime.homeDir(), ".claude"), "plugins");
14885
+ var pluginsDir = ({ env, runtime }) => join33(env["CLAUDE_CONFIG_DIR"] ?? join33(runtime.homeDir(), ".claude"), "plugins");
14634
14886
  var installRecord = (value, root) => {
14635
14887
  const records = Array.isArray(value) ? value.filter(isRecord) : isRecord(value) ? [value] : [];
14636
14888
  return records.find((record3) => record3["projectPath"] === root) ?? records[0];
14637
14889
  };
14638
14890
  var readJson = (env, path) => {
14639
- const text6 = env.runtime.readText(path);
14640
- const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
14891
+ const text7 = env.runtime.readText(path);
14892
+ const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
14641
14893
  return parsed?.ok === true ? parsed.value : void 0;
14642
14894
  };
14643
14895
  var installedPlugin = (env) => {
14644
- const index = readJson(env, toAbsolutePath(join30(pluginsDir(env), "installed_plugins.json")));
14896
+ const index = readJson(env, toAbsolutePath(join33(pluginsDir(env), "installed_plugins.json")));
14645
14897
  const record3 = installRecord(asRecord(index?.["plugins"])[PLUGIN_ID], env.root);
14646
14898
  const installPath = record3 === void 0 ? void 0 : optionalString(record3, "installPath");
14647
14899
  if (record3 === void 0 || installPath === void 0) return void 0;
14648
14900
  const root = toAbsolutePath(installPath);
14649
- const manifest = readJson(env, toAbsolutePath(join30(root, ".claude-plugin", "plugin.json")));
14650
- const hooksPath = toAbsolutePath(join30(root, "hooks", "hooks.json"));
14651
- const text6 = env.runtime.readText(hooksPath);
14652
- const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
14901
+ const manifest = readJson(env, toAbsolutePath(join33(root, ".claude-plugin", "plugin.json")));
14902
+ const hooksPath = toAbsolutePath(join33(root, "hooks", "hooks.json"));
14903
+ const text7 = env.runtime.readText(hooksPath);
14904
+ const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
14653
14905
  return {
14654
14906
  root,
14655
14907
  version: (manifest === void 0 ? void 0 : optionalString(manifest, "version")) ?? optionalString(record3, "version"),
14656
14908
  hooksFile: parsed?.ok === true ? { source: "plugin", path: hooksPath, hooks: parsed.value["hooks"] } : void 0,
14657
- hooksText: text6.ok ? text6.value : void 0
14909
+ hooksText: text7.ok ? text7.value : void 0
14658
14910
  };
14659
14911
  };
14660
14912
  var PRECEDENCE2 = ["managed", "local", "project", "user"];
@@ -14684,15 +14936,15 @@ var schemaFinding = (file, problems, claude) => problems.length === 0 ? [] : [
14684
14936
  )
14685
14937
  ];
14686
14938
  var unreadableProjectFiles = (env) => settingsPaths(env).filter(({ scope: scope2 }) => scope2 === "project" || scope2 === "local").flatMap(({ path }) => {
14687
- const text6 = env.runtime.readText(path);
14688
- return text6.ok && text6.value.trim() !== "" && !parseJsonObject(text6.value).ok ? [path] : [];
14939
+ const text7 = env.runtime.readText(path);
14940
+ return text7.ok && text7.value.trim() !== "" && !parseJsonObject(text7.value).ok ? [path] : [];
14689
14941
  });
14690
14942
  var schemaFindings = (env, plugin, claude) => {
14691
14943
  const label2 = claude === void 0 ? "?" : claude.join(".");
14692
14944
  const files = [...projectHookFiles(env), ...plugin?.hooksFile === void 0 ? [] : [plugin.hooksFile]];
14693
14945
  const invalidJson = [
14694
14946
  ...unreadableProjectFiles(env),
14695
- ...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(join31(plugin.root, "hooks", "hooks.json"))] : []
14947
+ ...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(join34(plugin.root, "hooks", "hooks.json"))] : []
14696
14948
  ];
14697
14949
  return [
14698
14950
  ...invalidJson.flatMap((file) => schemaFinding(file, ["not valid JSON"], label2)),
@@ -14713,7 +14965,7 @@ var pluginFindings = (env, projectOwn) => {
14713
14965
  ];
14714
14966
  };
14715
14967
  var coverageFindings = (env, entries) => {
14716
- const installed = env.runtime.stat(toAbsolutePath(join31(env.root, ...LOCK_FILE.split("/")))) !== void 0;
14968
+ const installed = env.runtime.stat(toAbsolutePath(join34(env.root, ...LOCK_FILE.split("/")))) !== void 0;
14717
14969
  if (entries.length === 0 && !installed) {
14718
14970
  return [finding("hooks.registered", "warn", "doctor.hooks.none", {}, "doctor.hooks.fix_none")];
14719
14971
  }
@@ -14765,21 +15017,21 @@ var checkHooks = (env) => {
14765
15017
  };
14766
15018
 
14767
15019
  // src/doctor/instructions.ts
14768
- import { join as join32 } from "node:path";
15020
+ import { join as join35 } from "node:path";
14769
15021
  var INSTRUCTION_LINE_CEILING = 200;
14770
15022
  var TOP_INSTRUCTIONS = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"];
14771
15023
  var RULES_DIR = ".claude/rules";
14772
15024
  var SHOWN = 3;
14773
- var readProjectFile = (env, relative9) => {
14774
- const text6 = env.runtime.readText(toAbsolutePath(join32(env.root, ...relative9.split("/"))));
14775
- return text6.ok ? text6.value : void 0;
15025
+ var readProjectFile = (env, relative11) => {
15026
+ const text7 = env.runtime.readText(toAbsolutePath(join35(env.root, ...relative11.split("/"))));
15027
+ return text7.ok ? text7.value : void 0;
14776
15028
  };
14777
15029
  var instructionFiles = (env) => {
14778
- const listed4 = env.runtime.listDir(toAbsolutePath(join32(env.root, ...RULES_DIR.split("/"))));
15030
+ const listed4 = env.runtime.listDir(toAbsolutePath(join35(env.root, ...RULES_DIR.split("/"))));
14779
15031
  const rules = listed4.ok ? listed4.value.filter((name) => name.endsWith(".md")).map((name) => `${RULES_DIR}/${name}`) : [];
14780
15032
  return [...TOP_INSTRUCTIONS, ...rules].flatMap((path) => {
14781
- const text6 = readProjectFile(env, path);
14782
- return text6 === void 0 ? [] : [{ path, text: text6 }];
15033
+ const text7 = readProjectFile(env, path);
15034
+ return text7 === void 0 ? [] : [{ path, text: text7 }];
14783
15035
  });
14784
15036
  };
14785
15037
  var shortList = (items) => items.length <= SHOWN ? items.join(", ") : `${items.slice(0, SHOWN).join(", ")} (+${String(items.length - SHOWN)})`;
@@ -14827,10 +15079,10 @@ var hiddenInstructions = (env, deny2) => {
14827
15079
  ];
14828
15080
  };
14829
15081
  var localFile = (env) => readProjectFile(env, "CLAUDE.local.md") === void 0 ? [] : [finding("instructions.local", "warn", "doctor.instructions.local", {}, "doctor.instructions.fix_local")];
14830
- var lineCount = (text6) => text6.split("\n").length - (text6.endsWith("\n") ? 1 : 0);
15082
+ var lineCount = (text7) => text7.split("\n").length - (text7.endsWith("\n") ? 1 : 0);
14831
15083
  var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
14832
- const text6 = readProjectFile(env, file);
14833
- const lines3 = text6 === void 0 ? 0 : lineCount(text6);
15084
+ const text7 = readProjectFile(env, file);
15085
+ const lines3 = text7 === void 0 ? 0 : lineCount(text7);
14834
15086
  if (lines3 <= INSTRUCTION_LINE_CEILING) return [];
14835
15087
  return [
14836
15088
  finding(
@@ -14843,13 +15095,13 @@ var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
14843
15095
  ];
14844
15096
  });
14845
15097
  var MIN_RULE_WORDS = 6;
14846
- var ruleLines = (text6) => text6.split("\n").map((raw) => withoutCarriageReturn(raw).trim()).filter((line2) => !/^(#|\||```|@|>)/.test(line2)).map(
15098
+ var ruleLines = (text7) => text7.split("\n").map((raw) => withoutCarriageReturn(raw).trim()).filter((line2) => !/^(#|\||```|@|>)/.test(line2)).map(
14847
15099
  (line2) => line2.replace(/^([-*+]|\d+[.)])\s+/, "").replace(/\s+/g, " ").toLowerCase()
14848
15100
  ).filter((line2) => line2.split(" ").length >= MIN_RULE_WORDS);
14849
15101
  var duplicatedRules = (files) => {
14850
15102
  const seen = /* @__PURE__ */ new Map();
14851
- for (const { path, text: text6 } of files) {
14852
- for (const line2 of ruleLines(text6)) seen.set(line2, (seen.get(line2) ?? /* @__PURE__ */ new Set()).add(path));
15103
+ for (const { path, text: text7 } of files) {
15104
+ for (const line2 of ruleLines(text7)) seen.set(line2, (seen.get(line2) ?? /* @__PURE__ */ new Set()).add(path));
14853
15105
  }
14854
15106
  return [...seen].filter(([, paths]) => paths.size > 1).map(([line2]) => line2);
14855
15107
  };
@@ -14880,12 +15132,12 @@ var checkInstructions = (env) => {
14880
15132
  };
14881
15133
 
14882
15134
  // src/doctor/line-endings.ts
14883
- import { join as join33 } from "node:path";
15135
+ import { join as join36 } from "node:path";
14884
15136
  var LF_RULE = /^\*\s.*\beol=lf\b/m;
14885
15137
  var checkLineEndings = ({ root, runtime }) => {
14886
15138
  const autocrlf = runtime.git(["config", "--get", "core.autocrlf"], root);
14887
15139
  if (!autocrlf.ok || autocrlf.stdout.trim().toLowerCase() !== "true") return [];
14888
- const attributes = runtime.readText(toAbsolutePath(join33(root, ".gitattributes")));
15140
+ const attributes = runtime.readText(toAbsolutePath(join36(root, ".gitattributes")));
14889
15141
  if (attributes.ok && LF_RULE.test(attributes.value)) return [];
14890
15142
  return [
14891
15143
  finding("git.autocrlf", "warn", "doctor.git.autocrlf", { file: ".gitattributes" }, "doctor.git.fix_autocrlf")
@@ -14893,25 +15145,25 @@ var checkLineEndings = ({ root, runtime }) => {
14893
15145
  };
14894
15146
 
14895
15147
  // src/doctor/memory.ts
14896
- import { join as join34 } from "node:path";
15148
+ import { join as join37 } from "node:path";
14897
15149
  var ceilingOf = (config) => {
14898
15150
  const value = configAt(parseConfigYaml(config ?? ""), "memory", "maxLines");
14899
15151
  const parsed = typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
14900
15152
  return Number.isInteger(parsed) && parsed > 0 ? parsed : AGENTS_MAX_LINES;
14901
15153
  };
14902
- var lineCount2 = (text6) => text6.replace(/\r?\n$/, "").split(/\r?\n/).length;
15154
+ var lineCount2 = (text7) => text7.replace(/\r?\n$/, "").split(/\r?\n/).length;
14903
15155
  var checkAgentsFile = ({ root, runtime }) => {
14904
- const text6 = runtime.readText(toAbsolutePath(join34(root, AGENTS_FILE)));
14905
- if (!text6.ok) return [];
14906
- const config = runtime.readText(toAbsolutePath(join34(root, CONFIG_FILE)));
15156
+ const text7 = runtime.readText(toAbsolutePath(join37(root, AGENTS_FILE)));
15157
+ if (!text7.ok) return [];
15158
+ const config = runtime.readText(toAbsolutePath(join37(root, CONFIG_FILE)));
14907
15159
  const max = ceilingOf(config.ok ? config.value : void 0);
14908
- const lines3 = lineCount2(text6.value);
15160
+ const lines3 = lineCount2(text7.value);
14909
15161
  const params2 = { file: AGENTS_FILE, lines: lines3, max };
14910
15162
  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)];
14911
15163
  };
14912
15164
 
14913
15165
  // src/doctor/project-files.ts
14914
- import { join as join35 } from "node:path";
15166
+ import { join as join38 } from "node:path";
14915
15167
  var variants = (base, extensions) => extensions.map((extension) => `${base}${extension}`);
14916
15168
  var SCRIPT = [".js", ".cjs", ".mjs", ".ts", ".cts", ".mts"];
14917
15169
  var DATA = ["", ".json", ".yml", ".yaml"];
@@ -14942,7 +15194,7 @@ var CONFIG_GROUPS = [
14942
15194
  files: [...variants("commitlint.config", SCRIPT), ...variants(".commitlintrc", [...DATA, ".js"])]
14943
15195
  }
14944
15196
  ];
14945
- var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(join35(env.root, file)))?.kind === "file");
15197
+ var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(join38(env.root, file)))?.kind === "file");
14946
15198
  var crowded = (env, groups, build) => groups.flatMap(({ name, files }) => {
14947
15199
  const found = present(env, files);
14948
15200
  return found.length > 1 ? [build(name, found.join(", "))] : [];
@@ -15019,24 +15271,24 @@ var doctorEnvFrom = (ctx) => ({
15019
15271
  runtime: { ...ctx.runtime, probe: probeOnce(ctx.runtime.probe) }
15020
15272
  });
15021
15273
  var runChecks2 = (env, checks = CHECKS) => checks.flatMap((check2) => check2(env));
15022
- var toItem = (ctx, finding3) => ({
15023
- check: finding3.check,
15024
- status: finding3.status,
15025
- detail: ctx.t(finding3.messageKey, finding3.params),
15026
- ...finding3.fixKey === void 0 ? {} : { fix: ctx.t(finding3.fixKey, finding3.params) }
15274
+ var toItem = (ctx, finding4) => ({
15275
+ check: finding4.check,
15276
+ status: finding4.status,
15277
+ detail: ctx.t(finding4.messageKey, finding4.params),
15278
+ ...finding4.fixKey === void 0 ? {} : { fix: ctx.t(finding4.fixKey, finding4.params) }
15027
15279
  });
15028
15280
  var count3 = (items, status) => items.filter((item2) => item2.status === status).length;
15029
15281
  var hookItems = (ctx, findings) => findings.flatMap(
15030
- (finding3) => finding3.hook === void 0 ? [] : [
15282
+ (finding4) => finding4.hook === void 0 ? [] : [
15031
15283
  {
15032
- check: finding3.check,
15033
- status: finding3.hook,
15034
- fix: finding3.fixKey === void 0 ? null : ctx.t(finding3.fixKey, finding3.params)
15284
+ check: finding4.check,
15285
+ status: finding4.hook,
15286
+ fix: finding4.fixKey === void 0 ? null : ctx.t(finding4.fixKey, finding4.params)
15035
15287
  }
15036
15288
  ]
15037
15289
  );
15038
15290
  var buildReport2 = (ctx, findings) => {
15039
- const checks = findings.map((finding3) => toItem(ctx, finding3));
15291
+ const checks = findings.map((finding4) => toItem(ctx, finding4));
15040
15292
  return {
15041
15293
  schema: DOCTOR_SCHEMA,
15042
15294
  checks,
@@ -15086,9 +15338,9 @@ var manualLines = (ctx, report2) => report2.checks.some((item2) => item2.status
15086
15338
  var writeReport = (ctx, data, result) => {
15087
15339
  const env = doctorEnvFrom(ctx);
15088
15340
  const path = reportPath(env);
15089
- const text6 = reportMarkdown(env, ctx, toEnvelope("doctor", result));
15341
+ const text7 = reportMarkdown(env, ctx, toEnvelope("doctor", result));
15090
15342
  const dir = ctx.runtime.mkdir(toAbsolutePath(dirname7(path)));
15091
- const written = dir.ok ? ctx.runtime.writeAtomic(path, text6) : dir;
15343
+ const written = dir.ok ? ctx.runtime.writeAtomic(path, text7) : dir;
15092
15344
  if (!written.ok) return failureFrom(ctx.t, applyProblem({ path, detail: written.error }));
15093
15345
  const human2 = [...result.human ?? [], ctx.t("doctor.report.written", { path })];
15094
15346
  return { ...result, data: { ...data, reportPath: path }, human: human2 };
@@ -15357,10 +15609,10 @@ var prePushChecks = (runtime, repo, remote, updates) => {
15357
15609
  };
15358
15610
 
15359
15611
  // src/githooks/render.ts
15360
- var MAX_LISTED8 = 5;
15612
+ var MAX_LISTED9 = 5;
15361
15613
  var detailOf = (check2) => {
15362
- const listed4 = check2.failures.slice(0, MAX_LISTED8).join("; ");
15363
- const rest = check2.failures.length - MAX_LISTED8;
15614
+ const listed4 = check2.failures.slice(0, MAX_LISTED9).join("; ");
15615
+ const rest = check2.failures.length - MAX_LISTED9;
15364
15616
  return rest > 0 ? `${listed4}; +${String(rest)}` : listed4;
15365
15617
  };
15366
15618
  var checkLine = (t, check2) => check2.failures.length === 0 ? t("githooks.pass", { check: check2.id }) : t("githooks.fail", { check: check2.id, detail: detailOf(check2), fix: check2.fix });
@@ -15414,15 +15666,15 @@ var commitMsgCommand = {
15414
15666
  usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: "<message-file>" })
15415
15667
  )
15416
15668
  );
15417
- const text6 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
15418
- if (!text6.ok)
15669
+ const text7 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
15670
+ if (!text7.ok)
15419
15671
  return Promise.resolve(
15420
15672
  failureFrom(
15421
15673
  ctx.t,
15422
15674
  usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: file })
15423
15675
  )
15424
15676
  );
15425
- const failures = commitMessageProblems(text6.value);
15677
+ const failures = commitMessageProblems(text7.value);
15426
15678
  const fix = "git commit --amend, using type(scope): subject and no AI attribution lines";
15427
15679
  return Promise.resolve(
15428
15680
  checksResult(ctx.t, COMMIT_MSG_SCHEMA, "githook_failed", [{ id: "commit-messages", failures, fix }])
@@ -15486,11 +15738,11 @@ var humanOf = (ctx, item2) => {
15486
15738
  return [
15487
15739
  ctx.t("git.merge.lost", { ...range, count: report2.findings.length }),
15488
15740
  ...report2.findings.map(
15489
- (finding3) => ctx.t("git.merge.finding", {
15490
- where: findingLabel(finding3),
15491
- kind: finding3.kind,
15492
- side: finding3.side ?? "-",
15493
- text: finding3.text
15741
+ (finding4) => ctx.t("git.merge.finding", {
15742
+ where: findingLabel(finding4),
15743
+ kind: finding4.kind,
15744
+ side: finding4.side ?? "-",
15745
+ text: finding4.text
15494
15746
  })
15495
15747
  )
15496
15748
  ];
@@ -15560,7 +15812,7 @@ var mergeVerifyCommand = {
15560
15812
  var REFLOG_LIMIT = 300;
15561
15813
  var LIST_LIMIT = 50;
15562
15814
  var SHA = /^[0-9a-f]{40,64}$/;
15563
- var linesOf = (text6) => text6.split("\n").filter((line2) => line2.trim() !== "");
15815
+ var linesOf = (text7) => text7.split("\n").filter((line2) => line2.trim() !== "");
15564
15816
  var reflogLines = (runtime, repo) => {
15565
15817
  const result = runtime.git(["reflog", "show", "--all", `-n${String(REFLOG_LIMIT)}`, "--format=%H %gD"], repo.root);
15566
15818
  if (!result.ok) return [];
@@ -15681,7 +15933,7 @@ var rescueCommand = {
15681
15933
  };
15682
15934
 
15683
15935
  // src/git/undo.ts
15684
- import { basename as basename3, dirname as dirname8, join as join36 } from "node:path";
15936
+ import { basename as basename3, dirname as dirname8, join as join39 } from "node:path";
15685
15937
  var INDEX_SUBJECT = "finitemonkey index:";
15686
15938
  var gitLine = (runtime, repo, args2) => {
15687
15939
  const result = runtime.git(args2, repo.root);
@@ -15724,7 +15976,7 @@ var findSnapshot = (views, wanted) => {
15724
15976
  };
15725
15977
  var stampOf2 = (iso) => iso.replace(/[:.]/g, "-");
15726
15978
  var worktreePath = (runtime, repo) => {
15727
- const base = join36(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
15979
+ const base = join39(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
15728
15980
  return toAbsolutePath(runtime.stat(toAbsolutePath(base)) === void 0 ? base : `${base}-2`);
15729
15981
  };
15730
15982
  var intoWorktree = (runtime, repo, view2) => {
@@ -15849,7 +16101,7 @@ var GLOBAL_FLAGS = [
15849
16101
  { flag: "--help, -h", key: "flag.help" }
15850
16102
  ];
15851
16103
  var exitCodeHelp = (ctx) => EXIT_CODE_NAMES.map((name) => ({ code: ExitCode[name], name, summary: ctx.t(exitCodeDocs[ExitCode[name]]) }));
15852
- var pad = (text6, width) => text6.padEnd(Math.max(width, text6.length + 1));
16104
+ var pad = (text7, width) => text7.padEnd(Math.max(width, text7.length + 1));
15853
16105
  var describe2 = (ctx, spec2) => ({
15854
16106
  name: commandName(spec2),
15855
16107
  usage: spec2.usage,
@@ -15959,7 +16211,7 @@ var hookCommand = {
15959
16211
  };
15960
16212
 
15961
16213
  // src/commands/i18n-check.ts
15962
- import { join as join37 } from "node:path";
16214
+ import { join as join40 } from "node:path";
15963
16215
 
15964
16216
  // src/i18n/message.ts
15965
16217
  var ARG = /^\s*([a-zA-Z][a-zA-Z0-9_]*)\s*(?:,\s*(plural|select)\s*,)?/;
@@ -15991,10 +16243,10 @@ var parseBranches = (body2, inPlural) => {
15991
16243
  };
15992
16244
  var parseNodes = (source, inPlural) => {
15993
16245
  const nodes = [];
15994
- let text6 = "";
16246
+ let text7 = "";
15995
16247
  const flush2 = () => {
15996
- if (text6 !== "") nodes.push({ kind: "text", text: text6 });
15997
- text6 = "";
16248
+ if (text7 !== "") nodes.push({ kind: "text", text: text7 });
16249
+ text7 = "";
15998
16250
  };
15999
16251
  for (let index = 0; index < source.length; index += 1) {
16000
16252
  const char = source.charAt(index);
@@ -16004,7 +16256,7 @@ var parseNodes = (source, inPlural) => {
16004
16256
  continue;
16005
16257
  }
16006
16258
  if (char !== "{") {
16007
- text6 += char;
16259
+ text7 += char;
16008
16260
  continue;
16009
16261
  }
16010
16262
  const cursor = { source, index: index + 1 };
@@ -16012,18 +16264,18 @@ var parseNodes = (source, inPlural) => {
16012
16264
  const inner = end < 0 ? void 0 : source.slice(index + 1, end);
16013
16265
  const match = inner === void 0 ? null : ARG.exec(inner);
16014
16266
  if (inner === void 0 || match === null) {
16015
- text6 += char;
16267
+ text7 += char;
16016
16268
  continue;
16017
16269
  }
16018
16270
  const name = match[1] ?? "";
16019
16271
  const type = match[2] === "plural" || match[2] === "select" ? match[2] : void 0;
16020
16272
  if (type === void 0 && match[0].trim().length !== inner.trim().length) {
16021
- text6 += char;
16273
+ text7 += char;
16022
16274
  continue;
16023
16275
  }
16024
16276
  const branches = type === void 0 ? void 0 : parseBranches(inner.slice(match[0].length), type === "plural");
16025
16277
  if (type !== void 0 && branches === void 0) {
16026
- text6 += char;
16278
+ text7 += char;
16027
16279
  continue;
16028
16280
  }
16029
16281
  flush2();
@@ -16161,10 +16413,10 @@ var unreadable = (dir, file, detail) => ({
16161
16413
  exitCode: ExitCode.NotConfigured
16162
16414
  });
16163
16415
  var readCatalog = (ctx, dir, lang) => {
16164
- const file = toAbsolutePath(join37(dir, `${lang}.json`));
16165
- const text6 = ctx.runtime.readText(file);
16166
- if (!text6.ok) return reject(unreadable(dir, file, text6.error));
16167
- const parsed = parseJsonObject(text6.value);
16416
+ const file = toAbsolutePath(join40(dir, `${lang}.json`));
16417
+ const text7 = ctx.runtime.readText(file);
16418
+ if (!text7.ok) return reject(unreadable(dir, file, text7.error));
16419
+ const parsed = parseJsonObject(text7.value);
16168
16420
  return parsed.ok ? parsed : reject(unreadable(dir, file, parsed.error));
16169
16421
  };
16170
16422
  var loadCatalogs = (ctx, dir) => {
@@ -16184,8 +16436,8 @@ var describeProblem = (ctx, problem4) => {
16184
16436
  return ` ${problem4.lang} ${problem4.key} ${kind}`;
16185
16437
  };
16186
16438
  var loadReview = (ctx, dir) => {
16187
- const text6 = ctx.runtime.readText(toAbsolutePath(join37(dir, "review.json")));
16188
- const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
16439
+ const text7 = ctx.runtime.readText(toAbsolutePath(join40(dir, "review.json")));
16440
+ const parsed = text7.ok ? parseJsonObject(text7.value) : void 0;
16189
16441
  if (parsed?.ok !== true) return void 0;
16190
16442
  const statuses = (lang) => {
16191
16443
  const value = parsed.value[lang];
@@ -16225,7 +16477,7 @@ var i18nCheckCommand = {
16225
16477
  };
16226
16478
 
16227
16479
  // src/history/look-back.ts
16228
- import { join as join38 } from "node:path";
16480
+ import { join as join41 } from "node:path";
16229
16481
 
16230
16482
  // src/history/patterns.ts
16231
16483
  import { posix as posix5 } from "node:path";
@@ -16330,8 +16582,8 @@ var addResults = (draft, blocks) => {
16330
16582
  let any = false;
16331
16583
  for (const block4 of blocks) {
16332
16584
  if (!isRecord(block4) || block4["type"] !== "tool_result" || typeof block4["tool_use_id"] !== "string") continue;
16333
- const text6 = textOf3(block4["content"]);
16334
- draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(text6), text: text6 });
16585
+ const text7 = textOf3(block4["content"]);
16586
+ draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(text7), text: text7 });
16335
16587
  any = true;
16336
16588
  }
16337
16589
  return any;
@@ -16361,7 +16613,7 @@ var parseTranscript = (jsonl) => {
16361
16613
  // src/history/look-back.ts
16362
16614
  var SAMPLES = 3;
16363
16615
  var projectLogKey = (root) => root.replace(/[^A-Za-z0-9-]/g, "-");
16364
- var projectLogDir = (runtime, root, env) => join38(env["CLAUDE_CONFIG_DIR"] ?? join38(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
16616
+ var projectLogDir = (runtime, root, env) => join41(env["CLAUDE_CONFIG_DIR"] ?? join41(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
16365
16617
  var lookBack = (runtime, root, env) => {
16366
16618
  const dir = projectLogDir(runtime, root, env);
16367
16619
  const listed4 = runtime.listDir(toAbsolutePath(dir));
@@ -16382,9 +16634,9 @@ var lookBack = (runtime, root, env) => {
16382
16634
  };
16383
16635
  let turns = 0;
16384
16636
  for (const name of files) {
16385
- const text6 = runtime.readText(toAbsolutePath(join38(dir, name)));
16386
- if (!text6.ok) continue;
16387
- for (const turn of parseTranscript(text6.value)) {
16637
+ const text7 = runtime.readText(toAbsolutePath(join41(dir, name)));
16638
+ if (!text7.ok) continue;
16639
+ for (const turn of parseTranscript(text7.value)) {
16388
16640
  turns += 1;
16389
16641
  for (const hit3 of hitsIn(turn)) {
16390
16642
  counts[hit3.pattern] += 1;
@@ -16432,7 +16684,7 @@ var walk2 = (a, b) => {
16432
16684
  }
16433
16685
  return ops;
16434
16686
  };
16435
- var splitLines = (text6) => text6 === "" ? [] : text6.replace(/\r?\n$/, "").split(/\r?\n/);
16687
+ var splitLines = (text7) => text7 === "" ? [] : text7.replace(/\r?\n$/, "").split(/\r?\n/);
16436
16688
  var sharedPrefix = (a, b) => {
16437
16689
  let count4 = 0;
16438
16690
  while (count4 < a.length && count4 < b.length && a[count4] === b[count4]) count4 += 1;
@@ -16509,7 +16761,7 @@ var planData = (t, plan, mode, applied, backup) => ({
16509
16761
  diff: planDiff(plan),
16510
16762
  ...backup === void 0 ? {} : { backup }
16511
16763
  });
16512
- var pad2 = (text6, width) => text6.padEnd(width);
16764
+ var pad2 = (text7, width) => text7.padEnd(width);
16513
16765
  var planLines = (t, data, withDiff) => [
16514
16766
  t("install.plan.title", { root: data.root, preset: data.preset }),
16515
16767
  ...data.actions.map(({ path, action }) => ` ${pad2(action, 13)}${path}`),
@@ -16598,6 +16850,346 @@ var initCommand = {
16598
16850
  }
16599
16851
  };
16600
16852
 
16853
+ // src/look/findings.ts
16854
+ var MAX_PER_RULE = 5;
16855
+ var finding3 = (result, rule, detail) => ({
16856
+ rule,
16857
+ route: result.route,
16858
+ viewport: result.viewport,
16859
+ detail,
16860
+ blocking: BLOCKING_RULES.includes(rule)
16861
+ });
16862
+ var many = (result, rule, items) => items.slice(0, MAX_PER_RULE).map((item2) => finding3(result, rule, item2));
16863
+ var pageFindings = (result) => {
16864
+ if (result.loadError !== void 0) return [finding3(result, "look/request-failed", result.loadError)];
16865
+ const measure = result.measure;
16866
+ return [
16867
+ ...measure !== void 0 && measure.overflow > 0 ? [finding3(result, "look/overflow", `${String(measure.overflow)}px wider than the viewport`)] : [],
16868
+ ...many(result, "look/console-error", result.consoleErrors),
16869
+ ...many(result, "look/request-failed", result.failedRequests),
16870
+ ...many(result, "look/i18n-key", measure?.keys ?? []),
16871
+ ...many(result, "look/broken-image", measure?.broken ?? []),
16872
+ ...many(result, "look/touch-target", measure?.small ?? [])
16873
+ ];
16874
+ };
16875
+ var applyAllows = (findings, allows, decided) => {
16876
+ const accepted2 = allows.filter((allow2) => decided(allow2.decision));
16877
+ const ignored = allows.filter((allow2) => !decided(allow2.decision)).map((allow2) => `${allow2.rule} ${allow2.route} ${allow2.decision}`);
16878
+ return {
16879
+ ignored,
16880
+ findings: findings.map((item2) => {
16881
+ const allow2 = accepted2.find((entry2) => entry2.rule === item2.rule && entry2.route === item2.route);
16882
+ return allow2 === void 0 ? item2 : { ...item2, blocking: false, allowedBy: allow2.decision };
16883
+ })
16884
+ };
16885
+ };
16886
+
16887
+ // src/look/node-capture.ts
16888
+ import { mkdirSync as mkdirSync3 } from "node:fs";
16889
+ import { join as join42 } from "node:path";
16890
+
16891
+ // src/look/measure.ts
16892
+ var measurePage = (doc) => {
16893
+ const KEY2 = /^[a-z][A-Za-z0-9_]*(?:\.[A-Za-z0-9_-]+)+$/;
16894
+ const PLACEHOLDER2 = /\{\{\s*[\w.]+\s*\}\}|\$t\(/;
16895
+ const NOT_KEY = /\.(?:com|net|org|io|dev|br|app|js|ts|json|png|svg|jpg|md)$/i;
16896
+ const SHOW_TEXT = 4;
16897
+ const walker = doc.createTreeWalker(doc.body, SHOW_TEXT);
16898
+ const keys = /* @__PURE__ */ new Set();
16899
+ for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
16900
+ const text7 = (node.textContent ?? "").trim();
16901
+ if (KEY2.test(text7) && !NOT_KEY.test(text7) || PLACEHOLDER2.test(text7)) keys.add(text7.slice(0, 80));
16902
+ }
16903
+ const small = [...doc.querySelectorAll("a, button, input, select, textarea, [role=button], [role=link]")].filter((element) => {
16904
+ const box = element.getBoundingClientRect();
16905
+ return box.width > 0 && box.height > 0 && (box.width < 40 || box.height < 40);
16906
+ }).map((element) => `${element.tagName.toLowerCase()} "${(element.textContent ?? "").trim().slice(0, 40)}"`);
16907
+ const broken = [...doc.images].filter((image) => image.complete && image.naturalWidth === 0 && image.getAttribute("src") !== null).map((image) => image.getAttribute("src") ?? "");
16908
+ const root = doc.documentElement;
16909
+ return { overflow: Math.max(0, root.scrollWidth - root.clientWidth), keys: [...keys], small, broken };
16910
+ };
16911
+ var MEASURE_SCRIPT = `(${measurePage.toString()})(document)`;
16912
+ var texts = (value) => Array.isArray(value) ? value.filter((item2) => typeof item2 === "string") : [];
16913
+ var parseMeasure = (value) => {
16914
+ if (!isRecord(value) || typeof value["overflow"] !== "number") return void 0;
16915
+ return {
16916
+ overflow: value["overflow"],
16917
+ keys: texts(value["keys"]),
16918
+ small: texts(value["small"]),
16919
+ broken: texts(value["broken"])
16920
+ };
16921
+ };
16922
+
16923
+ // src/look/node-capture.ts
16924
+ var HEIGHT = 900;
16925
+ var MAX_EVENTS = 20;
16926
+ var RESOURCE_ERROR = /^Failed to load resource\b/;
16927
+ var slugOf = (route) => {
16928
+ const slug2 = route.replace(/^\//, "").replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
16929
+ return slug2 === "" ? "root" : slug2.slice(0, 80);
16930
+ };
16931
+ var watch = (page) => {
16932
+ const errors = [];
16933
+ const failed4 = [];
16934
+ const push = (list4, text7) => {
16935
+ if (list4.length < MAX_EVENTS) list4.push(text7.slice(0, 200));
16936
+ };
16937
+ page.on("console", (message) => {
16938
+ if (message.type() === "error" && !RESOURCE_ERROR.test(message.text())) push(errors, message.text());
16939
+ });
16940
+ page.on("pageerror", (error) => {
16941
+ push(errors, error.message);
16942
+ });
16943
+ page.on("requestfailed", (request) => {
16944
+ push(failed4, `${request.method()} ${request.url()}`);
16945
+ });
16946
+ page.on("response", (response) => {
16947
+ if (response.status() >= 400 && !response.url().endsWith("/favicon.ico"))
16948
+ push(failed4, `${String(response.status())} ${response.url()}`);
16949
+ });
16950
+ return { errors, failed: failed4 };
16951
+ };
16952
+ var loadAndShoot = async (page, url2, path, timeoutMs) => {
16953
+ try {
16954
+ await page.goto(url2, { waitUntil: "networkidle", timeout: timeoutMs });
16955
+ } catch (error) {
16956
+ return error instanceof Error ? error.message.split("\n")[0] : String(error);
16957
+ }
16958
+ await page.screenshot({ path, fullPage: true, animations: "disabled" });
16959
+ return void 0;
16960
+ };
16961
+ var captureOne = async (browser, request, route, viewport) => {
16962
+ const context = await browser.newContext({ viewport: { width: viewport, height: HEIGHT }, reducedMotion: "reduce" });
16963
+ const page = await context.newPage();
16964
+ const events = watch(page);
16965
+ const path = join42(request.dir, `${slugOf(route)}-${String(viewport)}.png`);
16966
+ const loadError = await loadAndShoot(page, `${request.baseUrl}${route}`, path, request.timeoutMs);
16967
+ const measure = loadError === void 0 ? parseMeasure(await page.evaluate(MEASURE_SCRIPT)) : void 0;
16968
+ await context.close();
16969
+ return { route, viewport, path, consoleErrors: events.errors, failedRequests: events.failed, measure, loadError };
16970
+ };
16971
+ var captureAll = async (browser, request) => {
16972
+ mkdirSync3(request.dir, { recursive: true });
16973
+ const results = [];
16974
+ for (const route of request.routes) {
16975
+ for (const viewport of request.viewports) results.push(await captureOne(browser, request, route, viewport));
16976
+ }
16977
+ return results;
16978
+ };
16979
+
16980
+ // src/look/node-playwright.ts
16981
+ import { createRequire } from "node:module";
16982
+ import { join as join43 } from "node:path";
16983
+ import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
16984
+ var NAMES = ["playwright", "playwright-core"];
16985
+ var isPlaywright = (value) => isRecord(value) && isRecord(value["chromium"]) && typeof value["chromium"]["launch"] === "function";
16986
+ var unwrap2 = (value) => isRecord(value) && !("chromium" in value) && isRecord(value["default"]) ? value["default"] : value;
16987
+ var tryLoad = async (base, name) => {
16988
+ try {
16989
+ const resolved = createRequire(base).resolve(name);
16990
+ const loaded = unwrap2(await import(pathToFileURL(resolved).href));
16991
+ return isPlaywright(loaded) ? loaded : void 0;
16992
+ } catch {
16993
+ return void 0;
16994
+ }
16995
+ };
16996
+ var loadPlaywright = async (root) => {
16997
+ for (const base of [join43(root, "package.json"), fileURLToPath3(import.meta.url)]) {
16998
+ for (const name of NAMES) {
16999
+ const found = await tryLoad(base, name);
17000
+ if (found !== void 0) return found;
17001
+ }
17002
+ }
17003
+ return void 0;
17004
+ };
17005
+ var launchBrowser = async (playwright) => {
17006
+ for (const options of [{}, { channel: "chrome" }, { channel: "msedge" }]) {
17007
+ try {
17008
+ return await playwright.chromium.launch({ headless: true, ...options });
17009
+ } catch {
17010
+ continue;
17011
+ }
17012
+ }
17013
+ return void 0;
17014
+ };
17015
+
17016
+ // src/look/node-sheet.ts
17017
+ import { readFileSync as readFileSync2 } from "node:fs";
17018
+ import { join as join44 } from "node:path";
17019
+ var escapeHtml = (text7) => text7.replace(/[&<>"]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[char] ?? char);
17020
+ var card = (result) => {
17021
+ const image = (() => {
17022
+ try {
17023
+ return `<img src="data:image/png;base64,${readFileSync2(result.path).toString("base64")}">`;
17024
+ } catch {
17025
+ return "<p>no capture</p>";
17026
+ }
17027
+ })();
17028
+ return `<figure><figcaption>${escapeHtml(result.route)} @ ${String(result.viewport)}px</figcaption>${image}</figure>`;
17029
+ };
17030
+ var STYLE = [
17031
+ "body{margin:16px;font:14px sans-serif;background:#fff;display:grid;grid-template-columns:repeat(4,1fr);gap:12px}",
17032
+ "figure{margin:0;border:1px solid #ccc}",
17033
+ "figcaption{padding:4px 6px;background:#eee}",
17034
+ "img{display:block;width:100%;max-height:720px;object-fit:cover;object-position:top}"
17035
+ ].join("");
17036
+ var contactSheet = async (browser, results, dir) => {
17037
+ const shown3 = results.filter((result) => result.loadError === void 0);
17038
+ if (shown3.length === 0) return void 0;
17039
+ const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
17040
+ await page.setContent(`<!doctype html><style>${STYLE}</style>${shown3.map(card).join("")}`);
17041
+ const path = join44(dir, "contact-sheet.png");
17042
+ await page.screenshot({ path, fullPage: true });
17043
+ await page.close();
17044
+ return path;
17045
+ };
17046
+
17047
+ // src/look/record.ts
17048
+ var LOOK_CHECK = "*look*";
17049
+ var recordLook = (runtime, paths, tree, shot, allowed2) => {
17050
+ const failures = allowed2.findings.filter((item2) => item2.blocking).length;
17051
+ const summary = {
17052
+ schema: LOOK_SCHEMA,
17053
+ treeHash: tree,
17054
+ at: runtime.now(),
17055
+ failures,
17056
+ notes: allowed2.findings.length - failures,
17057
+ captures: shot.results.filter((result) => result.loadError === void 0).map((result) => ({ route: result.route, viewport: result.viewport, path: projectRelative(paths, result.path) })),
17058
+ contactSheet: shot.sheet === void 0 ? null : projectRelative(paths, shot.sheet),
17059
+ findings: allowed2.findings,
17060
+ ignoredAllows: allowed2.ignored
17061
+ };
17062
+ const file = summaryPath(paths, tree);
17063
+ runtime.writeText(file, `${JSON.stringify(summary, null, 2)}
17064
+ `);
17065
+ const task = activeTaskId(runtime, paths) ?? IMPLICIT_ID;
17066
+ appendEvidence(runtime, paths.evidence(task), [
17067
+ {
17068
+ schema: EVIDENCE_SCHEMA,
17069
+ task,
17070
+ check: LOOK_CHECK,
17071
+ kind: "look",
17072
+ status: failures === 0 ? "passed" : "failed",
17073
+ treeHash: tree,
17074
+ at: summary.at,
17075
+ notes: [{ key: "look.recorded", params: { failures, captures: summary.captures.length } }]
17076
+ }
17077
+ ]);
17078
+ return { kind: "done", summary, summaryPath: projectRelative(paths, file) };
17079
+ };
17080
+
17081
+ // src/look/run.ts
17082
+ var prepare = (runtime, cwd) => {
17083
+ const root = gitRoot(gitAt(runtime, cwd));
17084
+ if (root === void 0) return { kind: "not_a_repo" };
17085
+ const tree = treeHash(gitAt(runtime, root));
17086
+ const config = loadLookConfig(runtime, root);
17087
+ if (tree === void 0) return { kind: "not_a_repo" };
17088
+ if (config?.port === void 0) return { kind: "not_configured" };
17089
+ return { root, tree, config: { ...config, port: config.port } };
17090
+ };
17091
+ var bootApp = async (ready) => {
17092
+ const { config } = ready;
17093
+ if (await portOpen(config.host, config.port)) return void 0;
17094
+ if (config.start === void 0) return { detail: `nothing listens on ${config.host}:${String(config.port)}` };
17095
+ const app = startApp(config.start, ready.root, config.port);
17096
+ const state = await waitForPort(app, config.host, config.port, Date.now() + config.timeoutS * 1e3);
17097
+ if (state === "open") return app;
17098
+ await stopApp(app);
17099
+ return {
17100
+ detail: state === "exited" ? `the app exited with code ${String(app.exited()?.code ?? -1)}` : "port timeout"
17101
+ };
17102
+ };
17103
+ var capture = async (ready) => {
17104
+ const playwright = await loadPlaywright(ready.root);
17105
+ if (playwright === void 0) return { kind: "playwright_missing" };
17106
+ const browser = await launchBrowser(playwright);
17107
+ if (browser === void 0) return { kind: "browser_missing" };
17108
+ try {
17109
+ const dir = lookDir(fmPaths(ready.root), ready.tree);
17110
+ const results = await captureAll(browser, {
17111
+ baseUrl: `http://${ready.config.host}:${String(ready.config.port)}`,
17112
+ routes: lookRoutes(ready.config),
17113
+ viewports: VIEWPORTS,
17114
+ dir,
17115
+ timeoutMs: ready.config.timeoutS * 1e3
17116
+ });
17117
+ return { results, sheet: await contactSheet(browser, results, dir) };
17118
+ } finally {
17119
+ await browser.close();
17120
+ }
17121
+ };
17122
+ var runLook = async (runtime, cwd) => {
17123
+ const ready = prepare(runtime, cwd);
17124
+ if ("kind" in ready) return ready;
17125
+ const app = await bootApp(ready);
17126
+ if (app !== void 0 && "detail" in app) return { kind: "app_failed", detail: app.detail };
17127
+ try {
17128
+ const shot = await capture(ready);
17129
+ if ("kind" in shot) return shot;
17130
+ const paths = fmPaths(ready.root);
17131
+ const approvals = readApprovals(runtime, paths);
17132
+ const allowed2 = applyAllows(
17133
+ shot.results.flatMap(pageFindings),
17134
+ ready.config.allow,
17135
+ (decision) => approvals.some((approval) => approval.check === decision)
17136
+ );
17137
+ return recordLook(runtime, paths, ready.tree, shot, allowed2);
17138
+ } finally {
17139
+ if (app !== void 0) await stopApp(app);
17140
+ }
17141
+ };
17142
+
17143
+ // src/commands/look.ts
17144
+ var MAX_FINDINGS = 10;
17145
+ var setupError = (ctx, run4) => {
17146
+ const fix = run4.kind === "playwright_missing" || run4.kind === "browser_missing" ? "npm install --save-dev playwright && npx playwright install chromium" : run4.kind === "not_configured" ? `echo "port: 3000" > ${LOOK_CONFIG}` : "finitemonkey look";
17147
+ const message = ctx.t(`look.error.${run4.kind}`, { detail: run4.kind === "app_failed" ? run4.detail : "" });
17148
+ return {
17149
+ ok: false,
17150
+ exitCode: run4.kind === "app_failed" ? ExitCode.CheckFailed : ExitCode.NotConfigured,
17151
+ human: [message],
17152
+ error: { code: run4.kind === "app_failed" ? "look_failed" : "look_unavailable", message, fix }
17153
+ };
17154
+ };
17155
+ var dataOf = (run4) => ({
17156
+ schema: LOOK_SCHEMA,
17157
+ treeHash: run4.summary.treeHash,
17158
+ failures: run4.summary.failures,
17159
+ notes: run4.summary.notes,
17160
+ captures: run4.summary.captures.map((capture2) => capture2.path),
17161
+ contactSheet: run4.summary.contactSheet,
17162
+ summary: run4.summaryPath,
17163
+ findings: run4.summary.findings.slice(0, MAX_FINDINGS).map(({ rule, route, viewport, detail, blocking: blocking2 }) => ({ rule, route, viewport, detail, blocking: blocking2 }))
17164
+ });
17165
+ var doneResult = (ctx, data) => {
17166
+ const human2 = [
17167
+ ...data.captures,
17168
+ ...data.contactSheet === null ? [] : [data.contactSheet],
17169
+ ...data.findings.map((item2) => `${item2.rule} ${item2.route} @${String(item2.viewport)}: ${item2.detail}`),
17170
+ ctx.t("look.done", { failures: data.failures, count: data.captures.length })
17171
+ ];
17172
+ if (data.failures === 0) return success(data, human2);
17173
+ const message = ctx.t("look.done", { failures: data.failures, count: data.captures.length });
17174
+ return {
17175
+ ok: false,
17176
+ exitCode: ExitCode.CheckFailed,
17177
+ data,
17178
+ human: human2,
17179
+ error: { code: "look_failed", message, fix: "finitemonkey look" }
17180
+ };
17181
+ };
17182
+ var lookCommand = {
17183
+ path: ["look"],
17184
+ summaryKey: "command.look.summary",
17185
+ usage: "finitemonkey look",
17186
+ status: "ready",
17187
+ run: async (ctx) => {
17188
+ const run4 = await runLook(ctx.runtime, ctx.cwd);
17189
+ return run4.kind === "done" ? doneResult(ctx, dataOf(run4)) : setupError(ctx, run4);
17190
+ }
17191
+ };
17192
+
16601
17193
  // src/commands/pause.ts
16602
17194
  var toggle = (ctx, off) => {
16603
17195
  const command = off ? "finitemonkey off" : "finitemonkey on";
@@ -16654,7 +17246,7 @@ var policyFalsePositiveCommand = {
16654
17246
  };
16655
17247
 
16656
17248
  // src/commands/prose-check.ts
16657
- import { relative as relative8 } from "node:path";
17249
+ import { relative as relative10 } from "node:path";
16658
17250
 
16659
17251
  // src/commands/prose-view.ts
16660
17252
  var PROSE_CHECK_SCHEMA = "finitemonkey/prose-check@1";
@@ -16714,7 +17306,7 @@ var problem2 = (code, messageKey, params2) => ({
16714
17306
  fix: "finitemonkey help prose check",
16715
17307
  exitCode: ExitCode.Usage
16716
17308
  });
16717
- var underRoot = (root, path) => toPosixPath(relative8(root, path));
17309
+ var underRoot = (root, path) => toPosixPath(relative10(root, path));
16718
17310
  var expand2 = (ctx, root, target2) => {
16719
17311
  const path = toAbsolutePath(target2, ctx.cwd);
16720
17312
  const stat = ctx.runtime.stat(path);
@@ -16834,14 +17426,14 @@ var inputProblem = (ctx, id, reason, days) => {
16834
17426
  }
16835
17427
  return void 0;
16836
17428
  };
16837
- var entryOf3 = (ctx, finding3, args2) => {
17429
+ var entryOf3 = (ctx, finding4, args2) => {
16838
17430
  const now = ctx.runtime.now();
16839
17431
  return {
16840
- id: finding3.id,
16841
- rule: finding3.ruleId,
16842
- file: finding3.file,
16843
- line: finding3.line,
16844
- fingerprint: finding3.fingerprint,
17432
+ id: finding4.id,
17433
+ rule: finding4.ruleId,
17434
+ file: finding4.file,
17435
+ line: finding4.line,
17436
+ fingerprint: finding4.fingerprint,
16845
17437
  state: args2.falsePositive ? "falso-positivo" : "aceito",
16846
17438
  owner: personName(ctx),
16847
17439
  reason: args2.reason,
@@ -16856,8 +17448,8 @@ var acceptEntry = (ctx, args2) => {
16856
17448
  return failure3(ctx, baselineInvalid(baseline.error));
16857
17449
  }
16858
17450
  const scanned = scanPath(ctx.runtime, root, ".");
16859
- const finding3 = scanned.ok ? scanned.value.find((item2) => item2.id === args2.id) : void 0;
16860
- if (finding3 === void 0) {
17451
+ const finding4 = scanned.ok ? scanned.value.find((item2) => item2.id === args2.id) : void 0;
17452
+ if (finding4 === void 0) {
16861
17453
  return failure3(
16862
17454
  ctx,
16863
17455
  usageProblem("secrets_finding_not_found", "secrets.error.finding_not_found", "finitemonkey secrets scan", {
@@ -16865,7 +17457,7 @@ var acceptEntry = (ctx, args2) => {
16865
17457
  })
16866
17458
  );
16867
17459
  }
16868
- const entry2 = entryOf3(ctx, finding3, args2);
17460
+ const entry2 = entryOf3(ctx, finding4, args2);
16869
17461
  const entries = [...baseline.value.entries.filter((item2) => item2.id !== entry2.id), entry2];
16870
17462
  const saved = saveBaseline(ctx.runtime, root, { schema: baseline.value.schema, entries });
16871
17463
  if (!saved.ok) {
@@ -16874,7 +17466,7 @@ var acceptEntry = (ctx, args2) => {
16874
17466
  const line2 = ctx.t("secrets.accept.done", {
16875
17467
  id: entry2.id,
16876
17468
  rule: entry2.rule,
16877
- where: whereOf(finding3),
17469
+ where: whereOf(finding4),
16878
17470
  state: entry2.state,
16879
17471
  expires: entry2.expiresAt.slice(0, 10),
16880
17472
  owner: entry2.owner
@@ -16908,7 +17500,7 @@ var secretsAcceptCommand = {
16908
17500
  };
16909
17501
 
16910
17502
  // src/secrets/runbooks.ts
16911
- import { dirname as dirname9, join as join39 } from "node:path";
17503
+ import { dirname as dirname9, join as join45 } from "node:path";
16912
17504
  var PROVIDER_OF_RULE = {
16913
17505
  "aws-access-token": "aws",
16914
17506
  "aws-secret-access-key": "aws",
@@ -16930,7 +17522,7 @@ var RUNBOOKS_URL = "https://github.com/gabrielvieiraf/finitemonkey/blob/main/run
16930
17522
  var runbookProvider = (rule) => PROVIDER_OF_RULE[rule] ?? "generic";
16931
17523
  var runbookLink = (runtime, rule) => {
16932
17524
  const file = `${runbookProvider(rule)}.md`;
16933
- const local = toAbsolutePath(join39(dirname9(dirname9(runtime.selfPath)), "runbooks", file));
17525
+ const local = toAbsolutePath(join45(dirname9(dirname9(runtime.selfPath)), "runbooks", file));
16934
17526
  return runtime.stat(local)?.kind === "file" ? local : `${RUNBOOKS_URL}/${file}`;
16935
17527
  };
16936
17528
 
@@ -17167,17 +17759,17 @@ var secretsLiveCommand = {
17167
17759
 
17168
17760
  // src/commands/secrets-scan.ts
17169
17761
  var SECRETS_SCAN_SCHEMA = "finitemonkey/secrets-scan@1";
17170
- var view = (finding3, status) => ({
17171
- id: finding3.id,
17172
- rule: finding3.ruleId,
17173
- severity: finding3.severity,
17174
- file: finding3.file,
17175
- line: finding3.line,
17176
- column: finding3.column,
17177
- fingerprint: finding3.fingerprint,
17178
- mask: maskToken(finding3),
17762
+ var view = (finding4, status) => ({
17763
+ id: finding4.id,
17764
+ rule: finding4.ruleId,
17765
+ severity: finding4.severity,
17766
+ file: finding4.file,
17767
+ line: finding4.line,
17768
+ column: finding4.column,
17769
+ fingerprint: finding4.fingerprint,
17770
+ mask: maskToken(finding4),
17179
17771
  status,
17180
- ...finding3.commit === void 0 ? {} : { commit: finding3.commit }
17772
+ ...finding4.commit === void 0 ? {} : { commit: finding4.commit }
17181
17773
  });
17182
17774
  var modeOf3 = (values) => {
17183
17775
  const chosen = [values["staged"] === true, values["history"] === true, stringOption(values, "path") !== void 0];
@@ -17195,24 +17787,24 @@ var runScan = async (ctx, root, mode, target2) => {
17195
17787
  if (mode === "history") return scanHistory(ctx.runtime, root);
17196
17788
  return scanPath(ctx.runtime, root, normalizedTarget(target2) || ".");
17197
17789
  };
17198
- var historyIncidents = (ctx, root, fresh3) => fresh3.flatMap((finding3) => {
17199
- if (ruleById(finding3.ruleId)?.kind !== "credential") return [];
17790
+ var historyIncidents = (ctx, root, fresh3) => fresh3.flatMap((finding4) => {
17791
+ if (ruleById(finding4.ruleId)?.kind !== "credential") return [];
17200
17792
  const draft = {
17201
- rule: finding3.ruleId,
17202
- fingerprint: finding3.fingerprint,
17203
- where: whereOf(finding3),
17793
+ rule: finding4.ruleId,
17794
+ fingerprint: finding4.fingerprint,
17795
+ where: whereOf(finding4),
17204
17796
  source: "history",
17205
17797
  state: "aberto",
17206
- ...finding3.commit === void 0 ? {} : { firstCommit: finding3.commit }
17798
+ ...finding4.commit === void 0 ? {} : { firstCommit: finding4.commit }
17207
17799
  };
17208
17800
  const saved = recordIncident(ctx.runtime, root, draft, HOOK_ACTOR);
17209
17801
  return saved.ok ? [saved.value.id] : [];
17210
17802
  });
17211
- var dataOf = (mode, outcome2, incidents) => {
17803
+ var dataOf2 = (mode, outcome2, incidents) => {
17212
17804
  const findings = [
17213
- ...outcome2.fresh.map((finding3) => view(finding3, "new")),
17214
- ...outcome2.known.map((finding3) => view(finding3, "known")),
17215
- ...outcome2.expired.map((finding3) => view(finding3, "expired"))
17805
+ ...outcome2.fresh.map((finding4) => view(finding4, "new")),
17806
+ ...outcome2.known.map((finding4) => view(finding4, "known")),
17807
+ ...outcome2.expired.map((finding4) => view(finding4, "expired"))
17216
17808
  ];
17217
17809
  return {
17218
17810
  schema: SECRETS_SCAN_SCHEMA,
@@ -17287,12 +17879,12 @@ var secretsScanCommand = {
17287
17879
  );
17288
17880
  if (mode === "path") prune(ctx, root, baseline.value, outcome2);
17289
17881
  const incidents = mode === "history" ? historyIncidents(ctx, root, outcome2.fresh) : [];
17290
- return resultOf3(ctx, dataOf(mode, outcome2, incidents));
17882
+ return resultOf3(ctx, dataOf2(mode, outcome2, incidents));
17291
17883
  }
17292
17884
  };
17293
17885
 
17294
17886
  // src/task/create.ts
17295
- import { join as join40 } from "node:path";
17887
+ import { join as join46 } from "node:path";
17296
17888
 
17297
17889
  // src/core/yaml/write.ts
17298
17890
  var PLAIN_SAFE = /^[A-Za-z_./][A-Za-z0-9_./ ()-]*$/;
@@ -17380,11 +17972,11 @@ var explicitChecks = (draft) => [
17380
17972
  ];
17381
17973
  var draftChecks = (runtime, paths, draft) => {
17382
17974
  const disk = {
17383
- read: (relative9) => {
17384
- const text6 = runtime.readText(toAbsolutePath(join40(paths.root, relative9)));
17385
- return text6.ok ? text6.value : void 0;
17975
+ read: (relative11) => {
17976
+ const text7 = runtime.readText(toAbsolutePath(join46(paths.root, relative11)));
17977
+ return text7.ok ? text7.value : void 0;
17386
17978
  },
17387
- exists: (relative9) => runtime.stat(toAbsolutePath(join40(paths.root, relative9))) !== void 0
17979
+ exists: (relative11) => runtime.stat(toAbsolutePath(join46(paths.root, relative11))) !== void 0
17388
17980
  };
17389
17981
  const detected = detectChecks(disk);
17390
17982
  const base = draft.runs.length > 0 || draft.tests !== void 0 ? explicitChecks(draft) : detected;
@@ -17399,6 +17991,7 @@ var draftChecks = (runtime, paths, draft) => {
17399
17991
  { id: "merge", kind: "merge" },
17400
17992
  { id: "callers", kind: "callers" },
17401
17993
  ...draft.paths.length > 0 || draft.budget !== void 0 ? [{ id: "diff", kind: "diff" }] : [],
17994
+ ...runtime.stat(toAbsolutePath(join46(paths.root, LOOK_CONFIG))) === void 0 ? [] : [{ id: "look", kind: "look" }],
17402
17995
  ...draft.git.length > 0 ? [{ id: "git", kind: "git", require: draft.git }] : [],
17403
17996
  { id: "pr", kind: "pr", requireOpen: draft.git.includes("pr_open") },
17404
17997
  ...draft.manual.map((description, index) => ({
@@ -17424,19 +18017,19 @@ var createTask = (runtime, paths, draft) => {
17424
18017
  decisionsNeeded: [],
17425
18018
  implicit: false
17426
18019
  };
17427
- const text6 = stringifyYaml(contractYaml(shape));
18020
+ const text7 = stringifyYaml(contractYaml(shape));
17428
18021
  const valid = validateContract(contractYaml(shape));
17429
18022
  if (!valid.ok) return reject({ kind: "invalid", issues: valid.error });
17430
- const wrote = runtime.writeText(paths.task(draft.id), text6);
18023
+ const wrote = runtime.writeText(paths.task(draft.id), text7);
17431
18024
  if (!wrote.ok) return reject({ kind: "write", detail: wrote.error });
17432
18025
  const lock = readTrust(runtime, paths);
17433
18026
  writeTrust(runtime, paths, {
17434
18027
  ...lock,
17435
18028
  contracts: { ...lock.contracts, [draft.id]: contractHash(valid.value) },
17436
- approved: { ...lock.approved, [draft.id]: text6 }
18029
+ approved: { ...lock.approved, [draft.id]: text7 }
17437
18030
  });
17438
18031
  writeState(runtime, paths, { ...readState(runtime, paths), active: draft.id });
17439
- return accept({ contract: valid.value, text: text6 });
18032
+ return accept({ contract: valid.value, text: text7 });
17440
18033
  };
17441
18034
 
17442
18035
  // src/commands/task.ts
@@ -17587,7 +18180,7 @@ var taskDoneCommand = {
17587
18180
  };
17588
18181
 
17589
18182
  // src/install/githook-block.ts
17590
- var withoutHookBlock = (text6) => withoutManagedBlock(text6, { begin: BLOCK_BEGIN, end: BLOCK_END });
18183
+ var withoutHookBlock = (text7) => withoutManagedBlock(text7, { begin: BLOCK_BEGIN, end: BLOCK_END });
17591
18184
 
17592
18185
  // src/install/uninstall.ts
17593
18186
  var blockless = (kind, current2) => {
@@ -17596,8 +18189,8 @@ var blockless = (kind, current2) => {
17596
18189
  };
17597
18190
  var structural = (entry2, current2) => {
17598
18191
  if (entry2.kind === "gitignore" || entry2.kind === "markdown" || entry2.kind === "githook") {
17599
- const text6 = blockless(entry2.kind, current2);
17600
- return text6.trim() === "" && !entry2.existed ? void 0 : text6;
18192
+ const text7 = blockless(entry2.kind, current2);
18193
+ return text7.trim() === "" && !entry2.existed ? void 0 : text7;
17601
18194
  }
17602
18195
  const parsed = parseSettings(current2);
17603
18196
  if (!parsed.ok || entry2.added === void 0) return current2;
@@ -17636,8 +18229,8 @@ var applyItem = (project, item2) => {
17636
18229
  if (item2.after === void 0 || item2.action === "gone" || item2.action.startsWith("keep")) return accept(void 0);
17637
18230
  return writeText(project, item2.path, item2.after);
17638
18231
  };
17639
- var isEmptyDir = (project, relative9) => {
17640
- const listed4 = project.runtime.listDir(project.abs(relative9));
18232
+ var isEmptyDir = (project, relative11) => {
18233
+ const listed4 = project.runtime.listDir(project.abs(relative11));
17641
18234
  return listed4.ok && listed4.value.length === 0;
17642
18235
  };
17643
18236
  var finish3 = (project, plan) => {
@@ -17669,7 +18262,7 @@ var OUTSIDE = [
17669
18262
  command: "git for-each-ref --format='delete %(refname)' refs/finitemonkey/ | git update-ref --stdin"
17670
18263
  }
17671
18264
  ];
17672
- var dataOf2 = (root, plan, applied) => ({
18265
+ var dataOf3 = (root, plan, applied) => ({
17673
18266
  schema: UNINSTALL_SCHEMA,
17674
18267
  applied,
17675
18268
  purge: plan.purge,
@@ -17698,7 +18291,7 @@ var runUninstall = (ctx, args2) => {
17698
18291
  if (lock.value === void 0) return failureFrom(ctx.t, notInstalled());
17699
18292
  const options = { purge: args2.values["purge"] === true, keepGenerated: args2.values["keep-generated"] === true };
17700
18293
  const plan = planRemoval(project, lock.value, options);
17701
- const preview = dataOf2(project.root, plan, false);
18294
+ const preview = dataOf3(project.root, plan, false);
17702
18295
  if (ctx.dryRun) return success(preview, [...linesOf2(ctx, preview), ctx.t("install.dry_run")]);
17703
18296
  if (!ctx.yes) {
17704
18297
  const next2 = fixFor(args2);
@@ -17707,7 +18300,7 @@ var runUninstall = (ctx, args2) => {
17707
18300
  }
17708
18301
  const removed = applyRemoval(project, plan);
17709
18302
  if (!removed.ok) return failureFrom(ctx.t, applyProblem(removed.error));
17710
- const data = dataOf2(project.root, plan, true);
18303
+ const data = dataOf3(project.root, plan, true);
17711
18304
  return success(data, [...linesOf2(ctx, data), ctx.t("install.uninstall.done")]);
17712
18305
  };
17713
18306
  var uninstallCommand = {
@@ -17799,34 +18392,35 @@ var REGISTRY = [
17799
18392
  commitMsgCommand,
17800
18393
  pushCheckCommand,
17801
18394
  ciGateCommand,
18395
+ lookCommand,
17802
18396
  versionCommand,
17803
18397
  helpCommand
17804
18398
  ];
17805
18399
 
17806
18400
  // src/i18n/lang-settings.ts
17807
- import { join as join41 } from "node:path";
18401
+ import { join as join47 } from "node:path";
17808
18402
  var read3 = (runtime, path) => {
17809
- const text6 = runtime.readText(toAbsolutePath(path));
17810
- return text6.ok ? text6.value : void 0;
18403
+ const text7 = runtime.readText(toAbsolutePath(path));
18404
+ return text7.ok ? text7.value : void 0;
17811
18405
  };
17812
18406
  var configText = (tree, ...path) => {
17813
18407
  const value = configAt(tree, ...path);
17814
18408
  return typeof value === "string" && value !== "" ? value : void 0;
17815
18409
  };
17816
- var languageIn = (text6) => {
17817
- if (text6 === void 0) return void 0;
17818
- const parsed = parseJsonObject(text6);
18410
+ var languageIn = (text7) => {
18411
+ if (text7 === void 0) return void 0;
18412
+ const parsed = parseJsonObject(text7);
17819
18413
  const value = parsed.ok && isRecord(parsed.value) ? parsed.value["language"] : void 0;
17820
18414
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
17821
18415
  };
17822
- var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join41(runtime.homeDir(), ".claude");
18416
+ var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join47(runtime.homeDir(), ".claude");
17823
18417
  var langSettings = (runtime, cwd, env, platform) => {
17824
18418
  const root = findGitRoot(cwd, runtime) ?? cwd;
17825
- const config = parseConfigYaml(read3(runtime, join41(root, CONFIG_FILE)) ?? "");
18419
+ const config = parseConfigYaml(read3(runtime, join47(root, CONFIG_FILE)) ?? "");
17826
18420
  const claudeFiles = [
17827
- join41(root, ".claude", "settings.local.json"),
17828
- join41(root, ".claude", "settings.json"),
17829
- join41(claudeDir(runtime, env), "settings.json")
18421
+ join47(root, ".claude", "settings.local.json"),
18422
+ join47(root, ".claude", "settings.json"),
18423
+ join47(claudeDir(runtime, env), "settings.json")
17830
18424
  ];
17831
18425
  return {
17832
18426
  config: configText(config, "lang"),
@@ -17913,8 +18507,8 @@ var resultFromThrown = (thrown, t) => {
17913
18507
  };
17914
18508
  var block3 = (items) => items.length > 0 ? `${items.join("\n")}
17915
18509
  ` : "";
17916
- var write2 = (sink, text6) => {
17917
- if (text6 !== "") sink.write(maskText(text6));
18510
+ var write2 = (sink, text7) => {
18511
+ if (text7 !== "") sink.write(maskText(text7));
17918
18512
  };
17919
18513
  var render2 = (ctx, command, result) => {
17920
18514
  if (result.raw !== void 0) {
@@ -17979,20 +18573,20 @@ var run3 = async (options) => {
17979
18573
  };
17980
18574
 
17981
18575
  // src/core/version.ts
17982
- import { fileURLToPath as fileURLToPath3 } from "node:url";
18576
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
17983
18577
  var UNKNOWN_VERSION = "0.0.0-unknown";
17984
18578
  var VERSION_SOURCES = ["../package.json", "../.claude-plugin/plugin.json"];
17985
- var versionAt = (readText, moduleUrl, relative9) => {
17986
- const text6 = readText(toAbsolutePath(fileURLToPath3(new URL(relative9, moduleUrl))));
17987
- if (!text6.ok) return void 0;
17988
- const parsed = parseJson(text6.value);
18579
+ var versionAt = (readText, moduleUrl, relative11) => {
18580
+ const text7 = readText(toAbsolutePath(fileURLToPath4(new URL(relative11, moduleUrl))));
18581
+ if (!text7.ok) return void 0;
18582
+ const parsed = parseJson(text7.value);
17989
18583
  if (!parsed.ok || !isRecord(parsed.value)) return void 0;
17990
18584
  const version = parsed.value["version"];
17991
18585
  return typeof version === "string" ? version : void 0;
17992
18586
  };
17993
18587
  var readPackageVersion = (readText, moduleUrl) => {
17994
- for (const relative9 of VERSION_SOURCES) {
17995
- const version = versionAt(readText, moduleUrl, relative9);
18588
+ for (const relative11 of VERSION_SOURCES) {
18589
+ const version = versionAt(readText, moduleUrl, relative11);
17996
18590
  if (version !== void 0) return version;
17997
18591
  }
17998
18592
  return UNKNOWN_VERSION;