mason-context 0.17.3 → 0.18.0

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.
@@ -36,12 +36,69 @@ var init_paths = __esm({
36
36
  }
37
37
  });
38
38
 
39
+ // src/utils/profile.ts
40
+ import { AsyncLocalStorage } from "async_hooks";
41
+ async function profilePhase(label, run) {
42
+ const phases = profiles.getStore();
43
+ if (!phases) return run();
44
+ const start = performance.now();
45
+ try {
46
+ return await run();
47
+ } finally {
48
+ const timing = phases[label] ??= { calls: 0, totalMs: 0 };
49
+ timing.calls++;
50
+ timing.totalMs += performance.now() - start;
51
+ }
52
+ }
53
+ async function withProfile(run, emit) {
54
+ const phases = {};
55
+ const start = performance.now();
56
+ return profiles.run(phases, async () => {
57
+ try {
58
+ return await run();
59
+ } finally {
60
+ emit(JSON.stringify({ kind: "mason-profile", version: 1, totalMs: performance.now() - start, phases }));
61
+ }
62
+ });
63
+ }
64
+ var profiles;
65
+ var init_profile = __esm({
66
+ "src/utils/profile.ts"() {
67
+ "use strict";
68
+ profiles = new AsyncLocalStorage();
69
+ }
70
+ });
71
+
72
+ // src/utils/git-read.ts
73
+ import { execFile } from "child_process";
74
+ import { promisify } from "util";
75
+ function execGit(args, options) {
76
+ const command = args.find((arg) => !arg.startsWith("-")) ?? "other";
77
+ return profilePhase("git." + (commands.has(command) ? command : "other"), async () => {
78
+ const { input: input2, ...processOptions } = options;
79
+ const operation = execute("git", args, processOptions);
80
+ if (input2 !== void 0) {
81
+ operation.child.stdin.on("error", () => {
82
+ });
83
+ operation.child.stdin.end(input2);
84
+ }
85
+ return operation;
86
+ });
87
+ }
88
+ var execute, commands;
89
+ var init_git_read = __esm({
90
+ "src/utils/git-read.ts"() {
91
+ "use strict";
92
+ init_profile();
93
+ execute = promisify(execFile);
94
+ commands = /* @__PURE__ */ new Set(["rev-parse", "symbolic-ref", "for-each-ref", "ls-files", "check-ignore", "status", "log", "diff", "ls-tree", "show", "merge-base"]);
95
+ }
96
+ });
97
+
39
98
  // src/utils/files.ts
40
99
  import fs from "fs/promises";
41
100
  import { constants } from "fs";
42
101
  import path2 from "path";
43
- import { execFile } from "child_process";
44
- import { promisify } from "util";
45
102
  import fg from "fast-glob";
46
103
  function isSensitiveFile(file) {
47
104
  return file.split(/[\\/]/).some(
@@ -95,12 +152,12 @@ async function createFileAccess(rootDir) {
95
152
  const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
96
153
  let gitFiles = null;
97
154
  try {
98
- const { stdout } = await exec("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
155
+ const { stdout } = await execGit(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
99
156
  gitFiles = new Set(stdout.split("\0").filter(Boolean));
100
157
  } catch {
101
158
  let inGit = false;
102
159
  try {
103
- await exec("git", ["rev-parse", "--git-dir"], { cwd: root });
160
+ await execGit(["rev-parse", "--git-dir"], { cwd: root });
104
161
  inGit = true;
105
162
  } catch {
106
163
  }
@@ -143,12 +200,12 @@ async function createFileAccess(rootDir) {
143
200
  }
144
201
  return { root, config, list, read };
145
202
  }
146
- var exec, SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
203
+ var SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
147
204
  var init_files = __esm({
148
205
  "src/utils/files.ts"() {
149
206
  "use strict";
207
+ init_git_read();
150
208
  init_paths();
151
- exec = promisify(execFile);
152
209
  SOURCE_EXTENSIONS = ["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs", "vue", "svelte", "kt", "kts", "java", "py", "go", "rs", "swift", "rb", "cs", "cpp", "c", "h", "hpp", "dart", "php"];
153
210
  SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(",")}}`;
154
211
  SOURCE_IGNORE = [
@@ -248,18 +305,34 @@ var init_storage = __esm({
248
305
  }
249
306
  });
250
307
 
251
- // src/automation/store.ts
308
+ // src/utils/store-lock.ts
252
309
  import fs3 from "fs/promises";
253
310
  import os from "os";
254
- import { z } from "zod";
255
- function parseState(raw) {
311
+ async function lockDiagnostic(file) {
312
+ let reason2;
256
313
  try {
257
- return stateSchema.parse(raw);
314
+ const owner = JSON.parse(await fs3.readFile(file, "utf8"));
315
+ if (!owner || typeof owner.host !== "string" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
316
+ reason2 = "Lock owner data is incomplete or malformed.";
317
+ } else if (owner.host !== os.hostname()) {
318
+ reason2 = "The lock belongs to another host; its process cannot be checked locally.";
319
+ } else {
320
+ try {
321
+ process.kill(owner.pid, 0);
322
+ reason2 = `Local owner PID ${owner.pid} is still running.`;
323
+ } catch (error) {
324
+ reason2 = error.code === "ESRCH" ? `Local owner PID ${owner.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.pid} could not be checked.`;
325
+ }
326
+ }
258
327
  } catch (error) {
259
- throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
328
+ reason2 = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
260
329
  }
330
+ if (await fs3.lstat(file + ".reclaim").then(() => true, () => false)) {
331
+ reason2 += " A recovery guard also needs inspection: " + file + ".reclaim.";
332
+ }
333
+ return reason2 + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
261
334
  }
262
- async function withLock(root, directory, run, waitMs = 5e3) {
335
+ async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason store") {
263
336
  const file = await storePath(root, directory + "/lock", true);
264
337
  const deadline = Date.now() + waitMs;
265
338
  let handle;
@@ -302,7 +375,7 @@ async function withLock(root, directory, run, waitMs = 5e3) {
302
375
  }
303
376
  } catch {
304
377
  }
305
- if (Date.now() >= deadline) throw new Error("Automation is busy or its lock needs inspection: " + file);
378
+ if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
306
379
  await new Promise((resolve) => setTimeout(resolve, 40));
307
380
  }
308
381
  }
@@ -313,11 +386,30 @@ async function withLock(root, directory, run, waitMs = 5e3) {
313
386
  await fs3.unlink(file);
314
387
  }
315
388
  }
389
+ var init_store_lock = __esm({
390
+ "src/utils/store-lock.ts"() {
391
+ "use strict";
392
+ init_storage();
393
+ }
394
+ });
395
+
396
+ // src/automation/store.ts
397
+ import { z } from "zod";
398
+ function parseState(raw) {
399
+ try {
400
+ return stateSchema.parse(raw);
401
+ } catch (error) {
402
+ throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
403
+ }
404
+ }
405
+ function withLock(root, directory, run, waitMs = 5e3) {
406
+ return withStoreLock(root, directory, run, waitMs, "Automation");
407
+ }
316
408
  var hostSchema, events, stateSchema;
317
409
  var init_store = __esm({
318
410
  "src/automation/store.ts"() {
319
411
  "use strict";
320
- init_storage();
412
+ init_store_lock();
321
413
  hostSchema = z.enum(["claude", "codex"]);
322
414
  events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
323
415
  stateSchema = z.object({
@@ -346,39 +438,6 @@ var init_store = __esm({
346
438
  }
347
439
  });
348
440
 
349
- // src/utils/profile.ts
350
- import { AsyncLocalStorage } from "async_hooks";
351
- async function profilePhase(label, run) {
352
- const phases = profiles.getStore();
353
- if (!phases) return run();
354
- const start = performance.now();
355
- try {
356
- return await run();
357
- } finally {
358
- const timing = phases[label] ??= { calls: 0, totalMs: 0 };
359
- timing.calls++;
360
- timing.totalMs += performance.now() - start;
361
- }
362
- }
363
- async function withProfile(run, emit) {
364
- const phases = {};
365
- const start = performance.now();
366
- return profiles.run(phases, async () => {
367
- try {
368
- return await run();
369
- } finally {
370
- emit(JSON.stringify({ kind: "mason-profile", version: 1, totalMs: performance.now() - start, phases }));
371
- }
372
- });
373
- }
374
- var profiles;
375
- var init_profile = __esm({
376
- "src/utils/profile.ts"() {
377
- "use strict";
378
- profiles = new AsyncLocalStorage();
379
- }
380
- });
381
-
382
441
  // src/audit/commands.ts
383
442
  import path4 from "path";
384
443
  function splitCommands(line) {
@@ -603,14 +662,14 @@ function extractClaims(content2) {
603
662
  const ignored = computeIgnoredLines(lines);
604
663
  const paths = /* @__PURE__ */ new Map();
605
664
  const counts = [];
606
- const commands = /* @__PURE__ */ new Map();
665
+ const commands2 = /* @__PURE__ */ new Map();
607
666
  const addPath = (claim) => {
608
667
  const key = [claim.relativeTo ?? "scope", claim.path].join(":");
609
668
  if (!paths.has(key)) paths.set(key, claim);
610
669
  };
611
670
  const addCommand = (claim) => {
612
671
  const key = JSON.stringify([claim.scriptName, claim.directory, claim.scopeUnknown]);
613
- if (!commands.has(key)) commands.set(key, claim);
672
+ if (!commands2.has(key)) commands2.set(key, claim);
614
673
  };
615
674
  const parsed = markdown.parse(lines.map((line, i) => ignored[i] ? "" : line).join("\n"), {});
616
675
  for (const token of parsed) {
@@ -712,7 +771,7 @@ function extractClaims(content2) {
712
771
  return {
713
772
  paths: [...paths.values()],
714
773
  counts,
715
- commands: [...commands.values()]
774
+ commands: [...commands2.values()]
716
775
  };
717
776
  }
718
777
  var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
@@ -762,9 +821,53 @@ var init_claims = __esm({
762
821
  }
763
822
  });
764
823
 
824
+ // src/audit/inspection.ts
825
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
826
+ import path5 from "path";
827
+ function withRepositoryInspection(root, run) {
828
+ return inspections.run({ root: path5.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
829
+ }
830
+ function inspectionRead(root, key, read) {
831
+ const inspection = inspections.getStore();
832
+ if (!inspection || inspection.root !== path5.resolve(root)) return read();
833
+ if (!inspection.reads.has(key)) {
834
+ const pending = read();
835
+ inspection.reads.set(key, pending);
836
+ void pending.catch(() => {
837
+ if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
838
+ });
839
+ }
840
+ return inspection.reads.get(key);
841
+ }
842
+ function inspectionGit(args, options) {
843
+ return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
844
+ }
845
+ async function auditGitPaths(root, kind) {
846
+ const shared = inspections.getStore()?.root === path5.resolve(root);
847
+ const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
848
+ const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
849
+ const { stdout } = await inspectionGit(
850
+ ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
851
+ { cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
852
+ );
853
+ const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? /^(readme|agents|claude)\.md$/i.test(path5.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
854
+ if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
855
+ return paths.sort();
856
+ }
857
+ var inspections, sourceSpecs, docSpecs, sourceSuffixes;
858
+ var init_inspection = __esm({
859
+ "src/audit/inspection.ts"() {
860
+ "use strict";
861
+ init_git_read();
862
+ init_files();
863
+ inspections = new AsyncLocalStorage2();
864
+ sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
865
+ docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
866
+ sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
867
+ }
868
+ });
869
+
765
870
  // src/audit/git.ts
766
- import { execFile as execFile2 } from "child_process";
767
- import { promisify as promisify2 } from "util";
768
871
  function parseCommitLine(line) {
769
872
  const parts = line.split(" ");
770
873
  if (parts.length < 3 || !parts[0]) return null;
@@ -772,8 +875,7 @@ function parseCommitLine(line) {
772
875
  }
773
876
  async function lastCommitOf(resolvedRoot, relPath) {
774
877
  try {
775
- const { stdout } = await exec2(
776
- "git",
878
+ const { stdout } = await inspectionGit(
777
879
  ["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
778
880
  { cwd: resolvedRoot }
779
881
  );
@@ -785,8 +887,7 @@ async function lastCommitOf(resolvedRoot, relPath) {
785
887
  }
786
888
  async function deletingCommitOf(resolvedRoot, relPath) {
787
889
  try {
788
- const { stdout } = await exec2(
789
- "git",
890
+ const { stdout } = await inspectionGit(
790
891
  [
791
892
  "log",
792
893
  "-1",
@@ -805,8 +906,7 @@ async function deletingCommitOf(resolvedRoot, relPath) {
805
906
  }
806
907
  async function firstCommitOf(resolvedRoot, relPath) {
807
908
  try {
808
- const { stdout } = await exec2(
809
- "git",
909
+ const { stdout } = await inspectionGit(
810
910
  ["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
811
911
  { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
812
912
  );
@@ -819,8 +919,7 @@ async function firstCommitOf(resolvedRoot, relPath) {
819
919
  async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
820
920
  if (!fromHash || fromHash === "unknown") return null;
821
921
  try {
822
- const { stdout } = await exec2(
823
- "git",
922
+ const { stdout } = await inspectionGit(
824
923
  [
825
924
  "log",
826
925
  `${fromHash}..HEAD`,
@@ -844,11 +943,11 @@ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
844
943
  return null;
845
944
  }
846
945
  }
847
- var exec2, COMMIT_FORMAT;
946
+ var COMMIT_FORMAT;
848
947
  var init_git = __esm({
849
948
  "src/audit/git.ts"() {
850
949
  "use strict";
851
- exec2 = promisify2(execFile2);
950
+ init_inspection();
852
951
  COMMIT_FORMAT = "%H%x09%cI%x09%s";
853
952
  }
854
953
  });
@@ -856,9 +955,7 @@ var init_git = __esm({
856
955
  // src/audit/inputs.ts
857
956
  import fs4 from "fs/promises";
858
957
  import * as nativeFs from "fs";
859
- import path5 from "path";
860
- import { execFile as execFile3 } from "child_process";
861
- import { promisify as promisify3 } from "util";
958
+ import path6 from "path";
862
959
  import fg2 from "fast-glob";
863
960
  async function auditInputPath(root, file) {
864
961
  if (file === ".") return fs4.realpath(root);
@@ -885,7 +982,7 @@ async function auditGlob(root, patterns, options) {
885
982
  if (Array.isArray(patterns) && !patterns.length) return [];
886
983
  const directories = /* @__PURE__ */ new Set();
887
984
  const readdir2 = ((directory, opts, callback) => {
888
- const relative = path5.relative(root, directory).split(path5.sep).join("/") || ".";
985
+ const relative = path6.relative(root, directory).split(path6.sep).join("/") || ".";
889
986
  directories.add(relative);
890
987
  if (directories.size > MAX_INPUTS) {
891
988
  callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
@@ -896,7 +993,7 @@ async function auditGlob(root, patterns, options) {
896
993
  }, callback);
897
994
  });
898
995
  const lstat2 = ((file, callback) => {
899
- const relative = path5.relative(root, file).split(path5.sep).join("/") || ".";
996
+ const relative = path6.relative(root, file).split(path6.sep).join("/") || ".";
900
997
  auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
901
998
  });
902
999
  const stream = fg2.stream(patterns, {
@@ -933,62 +1030,48 @@ async function auditGlob(root, patterns, options) {
933
1030
  return matches.sort();
934
1031
  }
935
1032
  async function gitSourcePaths(root) {
936
- const { stdout } = await execute(
937
- "git",
938
- [
939
- "ls-files",
940
- "-z",
941
- "--cached",
942
- "--others",
943
- "--exclude-standard",
944
- "--",
945
- ...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
946
- ],
947
- { cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
948
- );
949
- return [...new Set(stdout.split("\0").filter(Boolean))].sort();
1033
+ return auditGitPaths(root, "sources");
950
1034
  }
951
1035
  async function gitIgnoredPaths(root, files) {
952
1036
  if (!files.length) return /* @__PURE__ */ new Set();
953
- const operation = execute(
954
- "git",
955
- ["check-ignore", "-z", "--stdin"],
956
- { cwd: root, maxBuffer: 4 * 1024 * 1024, timeout: 1e4 }
957
- );
958
- operation.child.stdin.on("error", () => {
959
- });
960
- operation.child.stdin.end(files.join("\0") + "\0");
961
- try {
962
- return new Set((await operation).stdout.split("\0").filter(Boolean));
963
- } catch (error) {
964
- if (error.code === 1) return /* @__PURE__ */ new Set();
965
- throw error;
1037
+ const ignored = /* @__PURE__ */ new Set();
1038
+ for (let offset = 0; offset < files.length; offset += 1e3) {
1039
+ try {
1040
+ const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
1041
+ cwd: root,
1042
+ maxBuffer: 4 * 1024 * 1024,
1043
+ timeout: 1e4,
1044
+ input: files.slice(offset, offset + 1e3).join("\0") + "\0"
1045
+ });
1046
+ for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
1047
+ } catch (error) {
1048
+ if (error.code !== 1) throw error;
1049
+ }
966
1050
  }
1051
+ return ignored;
967
1052
  }
968
- var execute, MAX_INPUTS;
1053
+ var MAX_INPUTS;
969
1054
  var init_inputs = __esm({
970
1055
  "src/audit/inputs.ts"() {
971
1056
  "use strict";
972
1057
  init_files();
973
1058
  init_storage();
974
- execute = promisify3(execFile3);
1059
+ init_git_read();
1060
+ init_inspection();
975
1061
  MAX_INPUTS = 1e5;
976
1062
  }
977
1063
  });
978
1064
 
979
1065
  // src/audit/docs.ts
980
1066
  import fs5 from "fs/promises";
981
- import path6 from "path";
982
- import { execFile as execFile4 } from "child_process";
983
- import { promisify as promisify4 } from "util";
1067
+ import path7 from "path";
984
1068
  import fg3 from "fast-glob";
985
1069
  async function dirtyDocs(resolvedRoot, files) {
986
1070
  if (!files.length) return /* @__PURE__ */ new Set();
987
1071
  try {
988
1072
  const dirty = /* @__PURE__ */ new Set();
989
1073
  for (let offset = 0; offset < files.length; offset += 256) {
990
- const { stdout } = await exec3(
991
- "git",
1074
+ const { stdout } = await execGit(
992
1075
  ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
993
1076
  { cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
994
1077
  );
@@ -1008,25 +1091,10 @@ async function dirtyDocs(resolvedRoot, files) {
1008
1091
  async function discoverDocPaths(root) {
1009
1092
  let candidates = [];
1010
1093
  try {
1011
- const { stdout } = await exec3(
1012
- "git",
1013
- [
1014
- "ls-files",
1015
- "-z",
1016
- "--cached",
1017
- "--others",
1018
- "--exclude-standard",
1019
- "--",
1020
- ":(icase,glob)**/readme.md",
1021
- ":(icase,glob)**/agents.md",
1022
- ":(icase,glob)**/claude.md"
1023
- ],
1024
- { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
1025
- );
1026
- candidates = stdout.split("\0").filter(Boolean);
1094
+ candidates = await auditGitPaths(root, "documents");
1027
1095
  } catch (error) {
1028
1096
  try {
1029
- await exec3("git", ["rev-parse", "--git-dir"], { cwd: root });
1097
+ await execGit(["rev-parse", "--git-dir"], { cwd: root });
1030
1098
  } catch {
1031
1099
  return localDocPaths(root);
1032
1100
  }
@@ -1073,14 +1141,14 @@ async function localDocPaths(root) {
1073
1141
  throw error;
1074
1142
  }
1075
1143
  for (const entry of entries) if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
1076
- paths.push(path6.posix.join(dir, entry.name));
1144
+ paths.push(path7.posix.join(dir, entry.name));
1077
1145
  }
1078
1146
  }
1079
1147
  return paths;
1080
1148
  }
1081
1149
  function documentScope(file) {
1082
- const parent = path6.posix.dirname(file);
1083
- return path6.posix.basename(parent).toLowerCase() === ".claude" ? path6.posix.dirname(parent) : parent;
1150
+ const parent = path7.posix.dirname(file);
1151
+ return path7.posix.basename(parent).toLowerCase() === ".claude" ? path7.posix.dirname(parent) : parent;
1084
1152
  }
1085
1153
  async function discoverDocs(resolvedRoot) {
1086
1154
  return profilePhase("audit.documents", () => readDocs(resolvedRoot));
@@ -1100,7 +1168,7 @@ async function readDocs(resolvedRoot) {
1100
1168
  path: candidate,
1101
1169
  content: content2,
1102
1170
  scope: documentScope(candidate),
1103
- kind: /^readme\.md$/i.test(path6.posix.basename(candidate)) ? "readme" : "instructions",
1171
+ kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : "instructions",
1104
1172
  lineCount: content2.split("\n").length,
1105
1173
  lastCommit,
1106
1174
  dirty: dirty.has(candidate),
@@ -1110,16 +1178,17 @@ async function readDocs(resolvedRoot) {
1110
1178
  }
1111
1179
  return docs;
1112
1180
  }
1113
- var exec3, DOC_CANDIDATES;
1181
+ var DOC_CANDIDATES;
1114
1182
  var init_docs = __esm({
1115
1183
  "src/audit/docs.ts"() {
1116
1184
  "use strict";
1185
+ init_git_read();
1117
1186
  init_profile();
1118
1187
  init_claims();
1119
1188
  init_git();
1120
1189
  init_inputs();
1121
1190
  init_files();
1122
- exec3 = promisify4(execFile4);
1191
+ init_inspection();
1123
1192
  DOC_CANDIDATES = [
1124
1193
  "AGENTS.md",
1125
1194
  "CLAUDE.md",
@@ -1130,14 +1199,14 @@ var init_docs = __esm({
1130
1199
 
1131
1200
  // src/audit/scope.ts
1132
1201
  import fs6 from "fs/promises";
1133
- import path7 from "path";
1202
+ import path8 from "path";
1134
1203
  function scopedPath(directory, value) {
1135
- if (path7.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
1136
- const resolved = path7.posix.normalize(path7.posix.join(directory, value));
1204
+ if (path8.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
1205
+ const resolved = path8.posix.normalize(path8.posix.join(directory, value));
1137
1206
  return resolved === ".." || resolved.startsWith("../") ? null : resolved;
1138
1207
  }
1139
1208
  function pathClaimScope(doc, claim) {
1140
- const directory = claim.relativeTo === "document" ? path7.posix.dirname(doc) : documentScope(doc);
1209
+ const directory = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
1141
1210
  const local = scopedPath(directory, claim.path);
1142
1211
  if (!local) return null;
1143
1212
  const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
@@ -1170,10 +1239,18 @@ var init_scope = __esm({
1170
1239
  }
1171
1240
  });
1172
1241
 
1242
+ // src/snapshot/lock.ts
1243
+ var init_lock = __esm({
1244
+ "src/snapshot/lock.ts"() {
1245
+ "use strict";
1246
+ init_store_lock();
1247
+ }
1248
+ });
1249
+
1173
1250
  // src/test-map.ts
1174
- import path8 from "path";
1251
+ import path9 from "path";
1175
1252
  async function buildTestMap(dir) {
1176
- const rootDir = path8.resolve(dir);
1253
+ const rootDir = path9.resolve(dir);
1177
1254
  const access = await createFileAccess(rootDir);
1178
1255
  const testPatterns = [
1179
1256
  "**/*.test.*",
@@ -1194,7 +1271,7 @@ async function buildTestMap(dir) {
1194
1271
  const sourceByBaseName = /* @__PURE__ */ new Map();
1195
1272
  for (const file of sourceFiles) {
1196
1273
  if (testFiles.includes(file)) continue;
1197
- const baseName = path8.basename(file).replace(/\.[^.]+$/, "");
1274
+ const baseName = path9.basename(file).replace(/\.[^.]+$/, "");
1198
1275
  const existing = sourceByBaseName.get(baseName) ?? [];
1199
1276
  existing.push(file);
1200
1277
  sourceByBaseName.set(baseName, existing);
@@ -1202,7 +1279,7 @@ async function buildTestMap(dir) {
1202
1279
  const paired = [];
1203
1280
  const unmatched = [];
1204
1281
  for (const testFile of testFiles) {
1205
- const testBaseName = path8.basename(testFile).replace(/\.[^.]+$/, "");
1282
+ const testBaseName = path9.basename(testFile).replace(/\.[^.]+$/, "");
1206
1283
  const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
1207
1284
  if (!sourceName) {
1208
1285
  unmatched.push(testFile);
@@ -1210,10 +1287,10 @@ async function buildTestMap(dir) {
1210
1287
  }
1211
1288
  const candidates = sourceByBaseName.get(sourceName);
1212
1289
  if (candidates && candidates.length > 0) {
1213
- const testDir = path8.dirname(testFile);
1290
+ const testDir = path9.dirname(testFile);
1214
1291
  const bestMatch = candidates.reduce((best, candidate) => {
1215
- const candidateDir = path8.dirname(candidate);
1216
- const bestDir = path8.dirname(best);
1292
+ const candidateDir = path9.dirname(candidate);
1293
+ const bestDir = path9.dirname(best);
1217
1294
  const candidateOverlap = commonSegments(testDir, candidateDir);
1218
1295
  const bestOverlap = commonSegments(testDir, bestDir);
1219
1296
  return candidateOverlap > bestOverlap ? candidate : best;
@@ -1247,9 +1324,7 @@ var init_test_map = __esm({
1247
1324
  });
1248
1325
 
1249
1326
  // src/snapshot/snapshot.ts
1250
- import path9 from "path";
1251
- import { execFile as execFile5 } from "child_process";
1252
- import { promisify as promisify5 } from "util";
1327
+ import path10 from "path";
1253
1328
  import { z as z2 } from "zod";
1254
1329
  async function inspectSnapshot(rootDir) {
1255
1330
  try {
@@ -1265,7 +1340,7 @@ async function inspectSnapshot(rootDir) {
1265
1340
  }
1266
1341
  async function getCurrentGitHash(rootDir) {
1267
1342
  try {
1268
- const { stdout } = await exec4("git", ["rev-parse", "HEAD"], {
1343
+ const { stdout } = await execGit(["rev-parse", "HEAD"], {
1269
1344
  cwd: rootDir
1270
1345
  });
1271
1346
  return stdout.trim();
@@ -1273,21 +1348,23 @@ async function getCurrentGitHash(rootDir) {
1273
1348
  return "unknown";
1274
1349
  }
1275
1350
  }
1276
- var exec4, repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
1351
+ var repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
1277
1352
  var init_snapshot = __esm({
1278
1353
  "src/snapshot/snapshot.ts"() {
1279
1354
  "use strict";
1355
+ init_git_read();
1280
1356
  init_files();
1281
1357
  init_files();
1282
1358
  init_storage();
1359
+ init_lock();
1283
1360
  init_paths();
1284
1361
  init_test_map();
1285
- exec4 = promisify5(execFile5);
1286
1362
  repoPath = z2.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
1287
1363
  verificationFields = {
1288
1364
  refreshedHash: z2.string().optional(),
1289
1365
  verifiedAt: z2.string().optional(),
1290
1366
  verifiedHash: z2.string().optional(),
1367
+ verificationToken: z2.string().optional(),
1291
1368
  verificationFailed: z2.boolean().optional(),
1292
1369
  verificationNote: z2.string().optional()
1293
1370
  };
@@ -1312,9 +1389,7 @@ var init_snapshot = __esm({
1312
1389
 
1313
1390
  // src/drift/drift.ts
1314
1391
  import fs7 from "fs/promises";
1315
- import path10 from "path";
1316
- import { execFile as execFile6 } from "child_process";
1317
- import { promisify as promisify6 } from "util";
1392
+ import path11 from "path";
1318
1393
  function parseChanges(output) {
1319
1394
  const fields = output.split("\0");
1320
1395
  const changes = [];
@@ -1335,7 +1410,7 @@ function touchedPaths(changes) {
1335
1410
  async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
1336
1411
  if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-") || !toHash || toHash === "unknown" || toHash.startsWith("-")) return null;
1337
1412
  try {
1338
- const { stdout } = await exec5("git", ["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
1413
+ const { stdout } = await execGit(["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
1339
1414
  return parseChanges(stdout);
1340
1415
  } catch {
1341
1416
  return null;
@@ -1344,8 +1419,8 @@ async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
1344
1419
  async function getWorkingTree(resolvedRoot) {
1345
1420
  try {
1346
1421
  const [diff, untracked] = await Promise.all([
1347
- exec5("git", ["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
1348
- exec5("git", ["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
1422
+ execGit(["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
1423
+ execGit(["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
1349
1424
  ]);
1350
1425
  const untrackedFiles = untracked.stdout.split("\0").filter((f) => f && !f.startsWith(".mason/"));
1351
1426
  return { available: true, changedFiles: [.../* @__PURE__ */ new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles])].sort(), untrackedFiles };
@@ -1353,13 +1428,12 @@ async function getWorkingTree(resolvedRoot) {
1353
1428
  return { available: false, changedFiles: [], untrackedFiles: [] };
1354
1429
  }
1355
1430
  }
1356
- var exec5;
1357
1431
  var init_drift = __esm({
1358
1432
  "src/drift/drift.ts"() {
1359
1433
  "use strict";
1434
+ init_git_read();
1360
1435
  init_snapshot();
1361
1436
  init_paths();
1362
- exec5 = promisify6(execFile6);
1363
1437
  }
1364
1438
  });
1365
1439
 
@@ -1380,7 +1454,7 @@ var init_types = __esm({
1380
1454
  });
1381
1455
 
1382
1456
  // src/audit/checks/deleted-reference.ts
1383
- import path11 from "path";
1457
+ import path12 from "path";
1384
1458
  async function checkDeletedReferences(ctx) {
1385
1459
  const result = emptyResult();
1386
1460
  for (const doc of ctx.docs) {
@@ -1409,7 +1483,7 @@ async function checkDeletedReferences(ctx) {
1409
1483
  const candidates = await Promise.all(scope.candidates.map(async (file) => ({
1410
1484
  file,
1411
1485
  tracked: await lastCommitOf(ctx.root, file),
1412
- parent: await pathExists(ctx.root, path11.posix.dirname(file))
1486
+ parent: await pathExists(ctx.root, path12.posix.dirname(file))
1413
1487
  })));
1414
1488
  const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
1415
1489
  if (!candidate && claim.relativeTo !== "document") continue;
@@ -1449,7 +1523,7 @@ var init_deleted_reference = __esm({
1449
1523
  // src/audit/checks/new-module.ts
1450
1524
  import fg4 from "fast-glob";
1451
1525
  import fs8 from "fs/promises";
1452
- import path12 from "path";
1526
+ import path13 from "path";
1453
1527
  function escapeRegExp(text2) {
1454
1528
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1455
1529
  }
@@ -1487,23 +1561,36 @@ async function checkNewModules(ctx) {
1487
1561
  return result;
1488
1562
  }
1489
1563
  function moduleDocumentation(docs) {
1490
- return docs.map((doc) => doc.content + "\n" + (path12.posix.dirname(doc.path) === "." ? "" : path12.posix.dirname(doc.path) + "/")).join("\n");
1564
+ return docs.map((doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")).join("\n");
1565
+ }
1566
+ function moduleCandidates(root, combinedDocs) {
1567
+ return inspectionRead(root, "modules:" + combinedDocs, () => collectModuleCandidates(root, combinedDocs));
1491
1568
  }
1492
- async function moduleCandidates(root, combinedDocs) {
1569
+ async function collectModuleCandidates(root, combinedDocs) {
1493
1570
  const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
1494
1571
  const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
1495
1572
  const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
1496
- const listSubdirs = async (dir) => {
1497
- const entries = await fs8.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1498
- const dirs = entries.filter((entry) => (entry.isDirectory() || entry.isSymbolicLink() && sources.some((file) => file.startsWith(path12.posix.join(dir, entry.name) + "/"))) && !entry.name.startsWith(".") && !DIR_DENYLIST.has(entry.name)).map((entry) => path12.posix.join(dir, entry.name));
1499
- const ignored = await gitIgnoredPaths(root, dirs);
1500
- const visible = new Set(dirs.filter((dir2) => !ignored.has(dir2) || sources.some((file) => file.startsWith(dir2 + "/"))));
1501
- return (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1502
- ignore,
1503
- onlyDirectories: true,
1504
- label: "Module directory discovery",
1505
- select: (file) => visible.has(file)
1506
- })).map((file) => path12.posix.basename(file));
1573
+ const listSubdirs = async (parents) => {
1574
+ const byParent = /* @__PURE__ */ new Map();
1575
+ for (let offset = 0; offset < parents.length; offset += 16) {
1576
+ await Promise.all(parents.slice(offset, offset + 16).map(async (dir) => {
1577
+ const entries = await fs8.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1578
+ const dirs = entries.filter((entry) => (entry.isDirectory() || entry.isSymbolicLink() && sources.some((file) => file.startsWith(path13.posix.join(dir, entry.name) + "/"))) && !entry.name.startsWith(".") && !DIR_DENYLIST.has(entry.name)).map((entry) => path13.posix.join(dir, entry.name));
1579
+ byParent.set(dir, dirs);
1580
+ }));
1581
+ }
1582
+ const ignored = await gitIgnoredPaths(root, [...byParent.values()].flat());
1583
+ const result = /* @__PURE__ */ new Map();
1584
+ for (const dir of parents) {
1585
+ const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
1586
+ result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1587
+ ignore,
1588
+ onlyDirectories: true,
1589
+ label: "Module directory discovery",
1590
+ select: (file) => visible.has(file)
1591
+ })).map((file) => path13.posix.basename(file)));
1592
+ }
1593
+ return result;
1507
1594
  };
1508
1595
  const countSourceFiles = async (dir) => {
1509
1596
  const files = sources.filter((file) => file.startsWith(dir + "/"));
@@ -1519,14 +1606,16 @@ async function moduleCandidates(root, combinedDocs) {
1519
1606
  return count4;
1520
1607
  };
1521
1608
  const candidates = [];
1522
- for (const topDir of await listSubdirs(".")) {
1609
+ const topDirs = (await listSubdirs(["."])).get(".");
1610
+ const nested = await listSubdirs(topDirs.filter((dir) => isMentioned(combinedDocs, dir)));
1611
+ for (const topDir of topDirs) {
1523
1612
  const topMentioned = isMentioned(combinedDocs, topDir);
1524
1613
  if (!topMentioned) {
1525
1614
  const count4 = await countSourceFiles(topDir);
1526
1615
  if (count4 >= 1) candidates.push({ dir: topDir, sourceFileCount: count4 });
1527
1616
  continue;
1528
1617
  }
1529
- const subdirs = await listSubdirs(topDir);
1618
+ const subdirs = nested.get(topDir);
1530
1619
  const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
1531
1620
  if (mentioned.length < ENUMERATION_THRESHOLD) continue;
1532
1621
  for (const sub of subdirs) {
@@ -1543,6 +1632,7 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
1543
1632
  var init_new_module = __esm({
1544
1633
  "src/audit/checks/new-module.ts"() {
1545
1634
  "use strict";
1635
+ init_inspection();
1546
1636
  init_files();
1547
1637
  init_inputs();
1548
1638
  init_git();
@@ -1572,7 +1662,7 @@ var init_new_module = __esm({
1572
1662
  });
1573
1663
 
1574
1664
  // src/audit/checks/stale-count.ts
1575
- import path13 from "path";
1665
+ import path14 from "path";
1576
1666
  async function countGradleModules(root) {
1577
1667
  for (const name of ["settings.gradle.kts", "settings.gradle"]) {
1578
1668
  const content2 = await readAuditInput(root, name);
@@ -1606,7 +1696,7 @@ async function countNpmWorkspaces(root) {
1606
1696
  return {
1607
1697
  actual: matched.length,
1608
1698
  countedFrom: "package.json workspaces",
1609
- members: matched.map((m) => path13.dirname(m)).sort()
1699
+ members: matched.map((m) => path14.dirname(m)).sort()
1610
1700
  };
1611
1701
  }
1612
1702
  }
@@ -1637,7 +1727,7 @@ async function countNpmWorkspaces(root) {
1637
1727
  return {
1638
1728
  actual: matched.length,
1639
1729
  countedFrom: "pnpm-workspace.yaml",
1640
- members: matched.map((m) => path13.dirname(m)).sort()
1730
+ members: matched.map((m) => path14.dirname(m)).sort()
1641
1731
  };
1642
1732
  }
1643
1733
  }
@@ -1659,8 +1749,8 @@ async function countCargoCrates(root) {
1659
1749
  ignore: ["**/target/**"],
1660
1750
  label: "Cargo workspace discovery"
1661
1751
  });
1662
- for (const m of matched) members.add(path13.dirname(m));
1663
- } else if (await readAuditInput(root, path13.posix.join(entry, "Cargo.toml")) !== null) {
1752
+ for (const m of matched) members.add(path14.dirname(m));
1753
+ } else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
1664
1754
  members.add(entry);
1665
1755
  }
1666
1756
  }
@@ -1680,7 +1770,7 @@ async function resolveCountSource(root, claim) {
1680
1770
  }
1681
1771
  async function resolveDocCountSource(root, doc, claim) {
1682
1772
  const scope = documentScope(doc);
1683
- const source = await resolveCountSource(path13.join(root, scope), claim);
1773
+ const source = await resolveCountSource(path14.join(root, scope), claim);
1684
1774
  if (!source || scope === ".") return source;
1685
1775
  return {
1686
1776
  ...source,
@@ -1732,7 +1822,7 @@ var init_stale_count = __esm({
1732
1822
  });
1733
1823
 
1734
1824
  // src/audit/checks/dead-command.ts
1735
- import path14 from "path";
1825
+ import path15 from "path";
1736
1826
  function scriptsOf(raw) {
1737
1827
  try {
1738
1828
  const pkg = JSON.parse(raw);
@@ -1768,7 +1858,7 @@ async function commandInputs(root, docs) {
1768
1858
  }
1769
1859
  let cursor = directory;
1770
1860
  while (true) {
1771
- const manifest = path14.posix.join(cursor, "package.json");
1861
+ const manifest = path15.posix.join(cursor, "package.json");
1772
1862
  scope.candidates.push(manifest);
1773
1863
  const raw = await read(manifest);
1774
1864
  if (raw !== null) {
@@ -1779,7 +1869,7 @@ async function commandInputs(root, docs) {
1779
1869
  break;
1780
1870
  }
1781
1871
  if (claim.directory !== void 0 || cursor === ".") break;
1782
- cursor = path14.posix.dirname(cursor);
1872
+ cursor = path15.posix.dirname(cursor);
1783
1873
  }
1784
1874
  if (!resolution.manifest && !resolution.problem) {
1785
1875
  resolution.problem = `No package.json for the command's scope (${directory}).`;
@@ -1842,10 +1932,8 @@ var init_dead_command = __esm({
1842
1932
  });
1843
1933
 
1844
1934
  // src/audit/release-metadata.ts
1845
- import { execFile as execFile7 } from "child_process";
1846
- import { promisify as promisify7 } from "util";
1847
1935
  async function git(root, args) {
1848
- return (await exec6("git", args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1936
+ return (await execGit(args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1849
1937
  }
1850
1938
  function withoutAndroidReleaseValues(text2) {
1851
1939
  if (/\/\*|"""|'''/.test(text2) || !/id\s*\(?\s*["']com\.android\.(application|library)["']/.test(text2)) return null;
@@ -1891,11 +1979,10 @@ async function releaseMetadataOnly(root, commit) {
1891
1979
  return false;
1892
1980
  }
1893
1981
  }
1894
- var exec6;
1895
1982
  var init_release_metadata = __esm({
1896
1983
  "src/audit/release-metadata.ts"() {
1897
1984
  "use strict";
1898
- exec6 = promisify7(execFile7);
1985
+ init_git_read();
1899
1986
  }
1900
1987
  });
1901
1988
 
@@ -1991,7 +2078,7 @@ var init_deps_changed = __esm({
1991
2078
  });
1992
2079
 
1993
2080
  // src/context/lexical.ts
1994
- import path15 from "path";
2081
+ import path16 from "path";
1995
2082
  var init_lexical = __esm({
1996
2083
  "src/context/lexical.ts"() {
1997
2084
  "use strict";
@@ -2175,7 +2262,7 @@ var init_provenance = __esm({
2175
2262
 
2176
2263
  // src/decisions/decisions.ts
2177
2264
  import fs9 from "fs/promises";
2178
- import path16 from "path";
2265
+ import path17 from "path";
2179
2266
  import { createHash } from "crypto";
2180
2267
  import { z as z4 } from "zod";
2181
2268
  async function loadDecisionStore(rootDir) {
@@ -2226,9 +2313,9 @@ var init_decisions = __esm({
2226
2313
  });
2227
2314
 
2228
2315
  // src/decisions/drift.ts
2229
- import path17 from "path";
2316
+ import path18 from "path";
2230
2317
  async function computeDecisionDrift(rootDir, decisions) {
2231
- const resolvedRoot = path17.resolve(rootDir);
2318
+ const resolvedRoot = path18.resolve(rootDir);
2232
2319
  const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
2233
2320
  const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
2234
2321
  const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
@@ -2452,8 +2539,6 @@ var init_findings = __esm({
2452
2539
 
2453
2540
  // src/audit/advisory-review.ts
2454
2541
  import fs10 from "fs/promises";
2455
- import { execFile as execFile8 } from "child_process";
2456
- import { promisify as promisify8 } from "util";
2457
2542
  import { z as z6 } from "zod";
2458
2543
  async function readRecord(root, id) {
2459
2544
  const raw = await readStoreJson(root, recordPath(id));
@@ -2580,10 +2665,12 @@ async function advisoryReviewInventory(root) {
2580
2665
  if (names.length > 5e3) throw new Error("Advisory review inventory exceeds 5,000 records.");
2581
2666
  return Promise.all(names.filter((name) => name.endsWith(".json")).sort().map(async (name) => [name, await readAuditInput(root, ".mason/reviews/advisories/" + name)]));
2582
2667
  }
2583
- var execute2, git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2668
+ var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2584
2669
  var init_advisory_review = __esm({
2585
2670
  "src/audit/advisory-review.ts"() {
2586
2671
  "use strict";
2672
+ init_inspection();
2673
+ init_git_read();
2587
2674
  init_findings();
2588
2675
  init_deps_changed();
2589
2676
  init_inputs();
@@ -2593,8 +2680,7 @@ var init_advisory_review = __esm({
2593
2680
  init_store();
2594
2681
  init_decisions();
2595
2682
  init_drift2();
2596
- execute2 = promisify8(execFile8);
2597
- git2 = async (root, ...args) => (await execute2("git", args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
2683
+ git2 = async (root, ...args) => (await (["log", "ls-tree", "ls-files", "merge-base"].includes(args[0]) ? inspectionGit : execGit)(args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
2598
2684
  hashSchema = z6.string().regex(/^[a-f0-9]{64}$/);
2599
2685
  commitSchema2 = z6.string().regex(/^[a-f0-9]{40,64}$/);
2600
2686
  evidenceSchema2 = z6.object({
@@ -2638,12 +2724,12 @@ var init_advisory_review = __esm({
2638
2724
 
2639
2725
  // src/audit/audit.ts
2640
2726
  import fs11 from "fs/promises";
2641
- import path18 from "path";
2727
+ import path19 from "path";
2642
2728
  async function computeAudit(rootDir, options = {}) {
2643
2729
  return profilePhase("audit.current", () => auditCurrent(rootDir, options));
2644
2730
  }
2645
2731
  async function auditCurrent(rootDir, options) {
2646
- const resolvedRoot = path18.resolve(rootDir);
2732
+ const resolvedRoot = path19.resolve(rootDir);
2647
2733
  const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
2648
2734
  if (docs.length === 0) return null;
2649
2735
  const report = {
@@ -2677,7 +2763,7 @@ async function auditCurrent(rootDir, options) {
2677
2763
  }
2678
2764
  let decisionsPresent = false;
2679
2765
  try {
2680
- await fs11.access(path18.join(resolvedRoot, ".mason", "decisions"));
2766
+ await fs11.access(path19.join(resolvedRoot, ".mason", "decisions"));
2681
2767
  decisionsPresent = true;
2682
2768
  } catch {
2683
2769
  }
@@ -2719,7 +2805,7 @@ var init_audit = __esm({
2719
2805
 
2720
2806
  // src/audit/repair.ts
2721
2807
  import fs12 from "fs/promises";
2722
- import path19 from "path";
2808
+ import path20 from "path";
2723
2809
  import { randomUUID as randomUUID2 } from "crypto";
2724
2810
  import { z as z7 } from "zod";
2725
2811
  function recheckable(finding) {
@@ -2754,8 +2840,8 @@ async function readStableAudit(root, checks, options) {
2754
2840
  }
2755
2841
  return report;
2756
2842
  }
2757
- async function withRepairSession(root, options, run, cachedAudit) {
2758
- const before = await Promise.all([getCurrentGitHash(root), docState(root), advisoryReviewInventory(root).then(digest)]);
2843
+ async function withRepairSession(root, options, run, validate, cachedAudit) {
2844
+ const head = await getCurrentGitHash(root);
2759
2845
  const audits = /* @__PURE__ */ new Map();
2760
2846
  const documents = /* @__PURE__ */ new Map();
2761
2847
  const history = /* @__PURE__ */ new Map();
@@ -2766,9 +2852,9 @@ async function withRepairSession(root, options, run, cachedAudit) {
2766
2852
  };
2767
2853
  const session = {
2768
2854
  audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
2769
- if (cachedAudit && cachedAudit.headHash === before[0] && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
2855
+ if (cachedAudit && cachedAudit.headHash === head && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
2770
2856
  const current = structuredClone(cachedAudit);
2771
- const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, before[0])))).filter((r) => r !== null);
2857
+ const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, head)))).filter((r) => r !== null);
2772
2858
  delete current.advisoryReviews;
2773
2859
  if (reviews.length) current.advisoryReviews = reviews;
2774
2860
  return current;
@@ -2776,16 +2862,15 @@ async function withRepairSession(root, options, run, cachedAudit) {
2776
2862
  return computeAudit(root, { ...options, checks });
2777
2863
  }),
2778
2864
  document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
2779
- history: (head) => memo(history, head, () => getChangesWithStatus(root, head)),
2780
- assessment: (dir, finding, head) => memo(assessments, digest([dir, finding, head]), () => assessAdvisory(dir, finding, head))
2865
+ history: (head2) => memo(history, head2, () => getChangesWithStatus(root, head2)),
2866
+ assessment: (dir, finding, head2) => memo(assessments, digest([dir, finding, head2]), () => assessAdvisory(dir, finding, head2))
2781
2867
  };
2782
2868
  const result = await run({
2783
2869
  prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
2784
2870
  verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
2785
2871
  currentAudit: () => session.audit(ALL_CHECKS)
2786
2872
  });
2787
- const after = await Promise.all([getCurrentGitHash(root), docState(root), advisoryReviewInventory(root).then(digest)]);
2788
- if (digest(before) !== digest(after)) throw new Error("HEAD, context files or advisory review records changed during verification; retry against stable evidence.");
2873
+ await validate();
2789
2874
  return result;
2790
2875
  }
2791
2876
  async function prepareOriginal(rootDir, checks, options, session) {
@@ -2807,8 +2892,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
2807
2892
  }
2808
2893
  async function loadRepairBaseline(rootDir, baselinePath) {
2809
2894
  const root = await fs12.realpath(rootDir);
2810
- const declaredRoot = path19.resolve(rootDir);
2811
- const relative = path19.isAbsolute(baselinePath) ? path19.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
2895
+ const declaredRoot = path20.resolve(rootDir);
2896
+ const relative = path20.isAbsolute(baselinePath) ? path20.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
2812
2897
  const stored = baselineSchema.parse(await readStoreJson(root, relative));
2813
2898
  const { digest: savedDigest, ...payload } = stored;
2814
2899
  if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
@@ -2967,14 +3052,12 @@ var init_repair = __esm({
2967
3052
 
2968
3053
  // src/automation/evidence.ts
2969
3054
  import fs13 from "fs/promises";
2970
- import path20 from "path";
3055
+ import path21 from "path";
2971
3056
  import { createHash as createHash3 } from "crypto";
2972
- import { execFile as execFile9 } from "child_process";
2973
- import { promisify as promisify9 } from "util";
2974
3057
  import fg5 from "fast-glob";
2975
3058
  import { z as z8 } from "zod";
2976
3059
  async function git3(root, ...args) {
2977
- return profilePhase("automation.git", async () => (await exec7("git", args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
3060
+ return profilePhase("automation.git", async () => (await execGit(args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
2978
3061
  }
2979
3062
  async function workspace(dir) {
2980
3063
  const root = await fs13.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
@@ -2998,7 +3081,7 @@ async function collectInputs(root) {
2998
3081
  const head = headText.trim();
2999
3082
  let shallow = null;
3000
3083
  try {
3001
- shallow = await fs13.readFile(path20.resolve(root, shallowPath.trim()), "utf8");
3084
+ shallow = await fs13.readFile(path21.resolve(root, shallowPath.trim()), "utf8");
3002
3085
  } catch (error) {
3003
3086
  if (error.code !== "ENOENT") throw error;
3004
3087
  }
@@ -3022,7 +3105,7 @@ async function collectInputs(root) {
3022
3105
  if (scope?.candidates.some(gitMetadataPath)) continue;
3023
3106
  for (const candidate of scope?.candidates ?? []) {
3024
3107
  if (optionalMasonPath(candidate)) continue;
3025
- const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path20.posix.dirname(candidate))]);
3108
+ const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path21.posix.dirname(candidate))]);
3026
3109
  claims.push([candidate, filePresent, parentPresent]);
3027
3110
  }
3028
3111
  }
@@ -3034,7 +3117,7 @@ async function collectInputs(root) {
3034
3117
  if (error.code === "ENOENT") return "absent";
3035
3118
  throw error;
3036
3119
  });
3037
- const [modules, counts, commands, decisionFiles] = await Promise.all([
3120
+ const [modules, counts, commands2, decisionFiles] = await Promise.all([
3038
3121
  combinedDocs ? moduleCandidates(root, combinedDocs) : [],
3039
3122
  Promise.all(countClaims.map(({ doc, claim }) => resolveDocCountSource(root, doc, claim))),
3040
3123
  commandInputs(root, parsedDocs),
@@ -3053,7 +3136,7 @@ async function collectInputs(root) {
3053
3136
  "deleted-reference": hash([common, claims, docStatus]),
3054
3137
  "new-module": hash([common, modules]),
3055
3138
  "stale-count": hash([common, counts]),
3056
- "dead-command": hash([common, commands]),
3139
+ "dead-command": hash([common, commands2]),
3057
3140
  "deps-changed": hash([common, docStatus]),
3058
3141
  "decision-anchor-drift": hash([common, decisionPresence, decisions, status, index2])
3059
3142
  };
@@ -3091,10 +3174,11 @@ function checkCache(raw, inputs) {
3091
3174
  }
3092
3175
  };
3093
3176
  }
3094
- var exec7, engineVersion, hash, content, cacheSchema;
3177
+ var engineVersion, hash, content, cacheSchema;
3095
3178
  var init_evidence = __esm({
3096
3179
  "src/automation/evidence.ts"() {
3097
3180
  "use strict";
3181
+ init_git_read();
3098
3182
  init_profile();
3099
3183
  init_docs();
3100
3184
  init_scope();
@@ -3107,8 +3191,7 @@ var init_evidence = __esm({
3107
3191
  init_dead_command();
3108
3192
  init_storage();
3109
3193
  init_advisory_review();
3110
- exec7 = promisify9(execFile9);
3111
- engineVersion = true ? "0.17.3" : "development";
3194
+ engineVersion = true ? "0.18.0" : "development";
3112
3195
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
3113
3196
  content = readAuditInput;
3114
3197
  cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
@@ -3376,12 +3459,12 @@ async function matchingAnalysis(ws, state, inputs) {
3376
3459
  return { analysis: a };
3377
3460
  }
3378
3461
  async function verifyInputs(ws, inputs) {
3379
- return profilePhase("automation.revalidate", async () => {
3462
+ return profilePhase("automation.revalidate", () => withRepositoryInspection(ws.root, async () => {
3380
3463
  const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
3381
3464
  if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
3382
3465
  throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
3383
3466
  }
3384
- });
3467
+ }));
3385
3468
  }
3386
3469
  async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
3387
3470
  const baselines = [...state.baselines];
@@ -3401,7 +3484,10 @@ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDia
3401
3484
  capture: "unknown",
3402
3485
  scope: SCOPE
3403
3486
  };
3404
- if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) return { baselines, report: empty, cache: null };
3487
+ if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) {
3488
+ await verifyInputs(ws, inputs);
3489
+ return { baselines, report: empty, cache: null };
3490
+ }
3405
3491
  if (ws.branch === "detached" && state.latest) {
3406
3492
  const previous = await readStoreJson(ws.root, state.latest);
3407
3493
  if (!previous?.head || !/^[a-f0-9]{40,64}$/.test(previous.head)) throw new Error("The previous detached checkout evidence is unavailable.");
@@ -3439,7 +3525,7 @@ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDia
3439
3525
  }
3440
3526
  currentAudit = await repairs.currentAudit();
3441
3527
  return verifications2;
3442
- }, cachedAudit);
3528
+ }, () => verifyInputs(ws, inputs), cachedAudit);
3443
3529
  const merged = /* @__PURE__ */ new Map();
3444
3530
  for (const verification of verifications) {
3445
3531
  for (const finding of verification.findings) {
@@ -3474,7 +3560,6 @@ async function analyze(ws, inputs, event, startedAt) {
3474
3560
  if (previous && previous.completedAt >= startedAt) return { analysis: previous, shared: true };
3475
3561
  const originalDigest = await baselineDigest(ws, before);
3476
3562
  const computed = await computeAnalysis(ws, before, inputs, event, previous?.currentAudit, diagnostic);
3477
- await verifyInputs(ws, inputs);
3478
3563
  if (originalDigest !== await baselineDigest(ws, before)) throw new Error("Repair baseline was modified during automation; original evidence must be inspected.");
3479
3564
  const id = randomUUID4();
3480
3565
  const analysis = analysisSchema.parse({
@@ -3503,7 +3588,7 @@ async function analyze(ws, inputs, event, startedAt) {
3503
3588
  async function automate(dir, event) {
3504
3589
  const startedAt = Date.now();
3505
3590
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
3506
- return recordExecution(ws.root, ws.directory, event.event, async () => {
3591
+ return recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
3507
3592
  const inputs = await readInputs(ws.root);
3508
3593
  const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
3509
3594
  if (result.shared) {
@@ -3576,7 +3661,7 @@ async function automate(dir, event) {
3576
3661
  await writeStoreJson(ws.root, ws.directory + "/state.json", state);
3577
3662
  return { report, message: notify ? summarize(report) : null, continueOnce };
3578
3663
  }));
3579
- });
3664
+ }));
3580
3665
  }
3581
3666
  async function automationStatus(dir) {
3582
3667
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
@@ -3614,6 +3699,7 @@ var init_runtime = __esm({
3614
3699
  "src/automation/runtime.ts"() {
3615
3700
  "use strict";
3616
3701
  init_profile();
3702
+ init_inspection();
3617
3703
  init_execution();
3618
3704
  init_repair();
3619
3705
  init_types();
@@ -3659,9 +3745,9 @@ var init_runtime = __esm({
3659
3745
 
3660
3746
  // src/automation/hook-root.ts
3661
3747
  import fs15 from "fs/promises";
3662
- import path21 from "path";
3748
+ import path22 from "path";
3663
3749
  async function repositoryRoot(dir) {
3664
- if (!path21.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
3750
+ if (!path22.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
3665
3751
  try {
3666
3752
  return await fs15.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
3667
3753
  } catch (error) {
@@ -3745,8 +3831,8 @@ __export(launcher_exports, {
3745
3831
  mcpCommand: () => mcpCommand,
3746
3832
  prepareLaunch: () => prepareLaunch
3747
3833
  });
3748
- import { execFile as execFile10 } from "child_process";
3749
- import { promisify as promisify10 } from "util";
3834
+ import { execFile as execFile2 } from "child_process";
3835
+ import { promisify as promisify2 } from "util";
3750
3836
  import fs16 from "fs/promises";
3751
3837
  function globalCommand(args, platform = process.platform) {
3752
3838
  return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args] } : { command: "mason", args };
@@ -3754,7 +3840,7 @@ function globalCommand(args, platform = process.platform) {
3754
3840
  async function installedCommand() {
3755
3841
  const invocation = globalCommand(["internal-integration-version"]);
3756
3842
  try {
3757
- const { stdout } = await promisify10(execFile10)(
3843
+ const { stdout } = await promisify2(execFile2)(
3758
3844
  invocation.command,
3759
3845
  invocation.args,
3760
3846
  { timeout: 5e3, maxBuffer: 8192, windowsHide: true }
@@ -3790,7 +3876,7 @@ var init_launcher = __esm({
3790
3876
  "use strict";
3791
3877
  init_evidence();
3792
3878
  init_model();
3793
- executingVersion = () => true ? "0.17.3" : "development";
3879
+ executingVersion = () => true ? "0.18.0" : "development";
3794
3880
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3795
3881
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3796
3882
  }
@@ -4006,7 +4092,7 @@ __export(files_exports, {
4006
4092
  readText: () => readText
4007
4093
  });
4008
4094
  import fs18 from "fs/promises";
4009
- import path22 from "path";
4095
+ import path23 from "path";
4010
4096
  import { randomUUID as randomUUID5 } from "crypto";
4011
4097
  async function readText(root, file) {
4012
4098
  try {
@@ -4039,7 +4125,7 @@ async function applyEdit(root, edit) {
4039
4125
  return true;
4040
4126
  }
4041
4127
  const file = await storePath(root, edit.path, true);
4042
- const temporary = path22.join(path22.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
4128
+ const temporary = path23.join(path23.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
4043
4129
  try {
4044
4130
  const mode = await fs18.stat(file).then((s) => s.mode & 511, () => 384);
4045
4131
  const handle = await fs18.open(temporary, "wx", mode);
@@ -4159,7 +4245,7 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
4159
4245
  - Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
4160
4246
  - Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` before finishing. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
4161
4247
  - Before saving, compare the lesson with matching \`get_context\` records and any pending proposal. If new evidence changes the same lesson's assumptions, scope, or recommended action, revise it with \`save_decision(id: existingId)\`, including when it is accepted. Preserve supported rationale and sources, replace obsolete instructions, and add the new evidence. Create a separate record for a genuinely distinct lesson; skip unchanged restatements. Revision preserves history and leaves the accepted version operative until authorized review.
4162
- - Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, and failed verification means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
4248
+ - Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, stale or unknown map verification requires a fresh evidence review, and a recorded failed verdict means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
4163
4249
  - Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
4164
4250
  - For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
4165
4251
  - \`mason_init\` returns documentation audit and committed-diff review results, plus a short setup guide. Pass \`evidence\` with local CI manifest paths to include test and analysis results; the CLI equivalent is \`mason-review --evidence <manifest>\`. State skipped, unavailable, stale, or unknown checks explicitly. Related accepted decisions identify review context, not proven violations.
@@ -4484,14 +4570,14 @@ async function planTeardown(root, selected) {
4484
4570
  const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
4485
4571
  const automationBefore = await read(AUTOMATION_PATH);
4486
4572
  const automation = automationBefore === null ? null : recordSchema2.parse(JSON.parse(automationBefore));
4487
- const commands = (host) => new Set([
4573
+ const commands2 = (host) => new Set([
4488
4574
  automation?.hosts[host]?.command,
4489
4575
  ...ownership.files[configPath(host)]?.hookCommands ?? [],
4490
4576
  ...knownManagedHookCommands(host),
4491
4577
  hookConfig(host, hookCommand(host)).hooks.SessionStart[0].hooks[0].command,
4492
4578
  hookConfig(host).hooks.SessionStart[0].hooks[0].command
4493
4579
  ].filter((command) => !!command));
4494
- const possibleHook = (command, host) => commands(host).has(command) || /\bmason(?:-auto|-hook)?\b/.test(command);
4580
+ const possibleHook = (command, host) => commands2(host).has(command) || /\bmason(?:-auto|-hook)?\b/.test(command);
4495
4581
  const add = (file, before, after, empty) => {
4496
4582
  edits.push({ path: file, before, after: ownership.files[file]?.created && (empty || ownership.files[file].initialHash === hash(before)) ? null : after });
4497
4583
  };
@@ -4555,12 +4641,12 @@ async function planTeardown(root, selected) {
4555
4641
  for (const [event, groups] of Object.entries(config.hooks ?? {})) {
4556
4642
  const known = ownership.files[hookFile]?.hooks?.[event];
4557
4643
  const handlers = groups.flatMap((group) => group.hooks);
4558
- if (known && !handlers.some((handler) => handler.type === "command" && handler.command && commands(host).has(handler.command)) && handlers.some((handler) => !known.includes(hash(handler)))) {
4644
+ if (known && !handlers.some((handler) => handler.type === "command" && handler.command && commands2(host).has(handler.command)) && handlers.some((handler) => !known.includes(hash(handler)))) {
4559
4645
  diagnostics.push({ path: hookFile, message: "A recorded Mason hook is missing beside a new or edited handler (" + event + "); inspect ownership manually." });
4560
4646
  }
4561
4647
  const kept = groups.flatMap((group) => {
4562
4648
  const handlers2 = group.hooks.filter((handler) => {
4563
- if (handler.type === "command" && handler.command && commands(host).has(handler.command)) {
4649
+ if (handler.type === "command" && handler.command && commands2(host).has(handler.command)) {
4564
4650
  changed = true;
4565
4651
  return false;
4566
4652
  }
@@ -4692,13 +4778,13 @@ var init_teardown = __esm({
4692
4778
  });
4693
4779
 
4694
4780
  // src/review/cochange.ts
4695
- import { execFile as execFile11 } from "child_process";
4696
- import { promisify as promisify11 } from "util";
4781
+ import { execFile as execFile3 } from "child_process";
4782
+ import { promisify as promisify3 } from "util";
4697
4783
  async function buildMatrix(resolvedRoot) {
4698
4784
  try {
4699
- const { stdout: shallow } = await exec8("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
4785
+ const { stdout: shallow } = await exec("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
4700
4786
  if (shallow.trim() === "true") return null;
4701
- const { stdout } = await exec8(
4787
+ const { stdout } = await exec(
4702
4788
  "git",
4703
4789
  ["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
4704
4790
  { cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
@@ -4754,11 +4840,11 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
4754
4840
  findings.sort((a, b) => b.rate - a.rate || b.sharedCommits - a.sharedCommits);
4755
4841
  return findings;
4756
4842
  }
4757
- var exec8, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
4843
+ var exec, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
4758
4844
  var init_cochange = __esm({
4759
4845
  "src/review/cochange.ts"() {
4760
4846
  "use strict";
4761
- exec8 = promisify11(execFile11);
4847
+ exec = promisify3(execFile3);
4762
4848
  HISTORY_COMMITS = 1500;
4763
4849
  MIN_FILE_COMMITS = 5;
4764
4850
  MIN_SHARED_COMMITS = 4;
@@ -5066,10 +5152,10 @@ var init_check = __esm({
5066
5152
  });
5067
5153
 
5068
5154
  // src/review/evidence.ts
5069
- import path23 from "path";
5155
+ import path24 from "path";
5070
5156
  import { z as z22 } from "zod";
5071
5157
  function relativeArtifact(root, file) {
5072
- const relative = path23.isAbsolute(file) ? path23.relative(root, file) : file;
5158
+ const relative = path24.isAbsolute(file) ? path24.relative(root, file) : file;
5073
5159
  const normalized = normalizeRepoPath(relative);
5074
5160
  if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
5075
5161
  return normalized;
@@ -5166,7 +5252,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5166
5252
  const report = await readStoreJson(root, file);
5167
5253
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
5168
5254
  const sourceRoot = check.sourceRoot ?? root;
5169
- if (!path23.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5255
+ if (!path24.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5170
5256
  const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
5171
5257
  result.outcome = parsed.outcome;
5172
5258
  result.counts = parsed.counts;
@@ -5288,12 +5374,12 @@ var init_evidence2 = __esm({
5288
5374
 
5289
5375
  // src/review/review.ts
5290
5376
  import fs19 from "fs/promises";
5291
- import path24 from "path";
5292
- import { execFile as execFile12 } from "child_process";
5293
- import { promisify as promisify12 } from "util";
5377
+ import path25 from "path";
5378
+ import { execFile as execFile4 } from "child_process";
5379
+ import { promisify as promisify4 } from "util";
5294
5380
  async function resolveMergeBase(resolvedRoot, base, head) {
5295
5381
  try {
5296
- const { stdout } = await exec9("git", ["merge-base", base, head], {
5382
+ const { stdout } = await exec2("git", ["merge-base", base, head], {
5297
5383
  cwd: resolvedRoot
5298
5384
  });
5299
5385
  return stdout.trim() || null;
@@ -5304,7 +5390,7 @@ async function resolveMergeBase(resolvedRoot, base, head) {
5304
5390
  async function defaultBase(resolvedRoot) {
5305
5391
  for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
5306
5392
  try {
5307
- await exec9("git", ["rev-parse", "--verify", "--quiet", ref], {
5393
+ await exec2("git", ["rev-parse", "--verify", "--quiet", ref], {
5308
5394
  cwd: resolvedRoot
5309
5395
  });
5310
5396
  return ref;
@@ -5317,7 +5403,7 @@ function anchorsTouched(record, changedFiles) {
5317
5403
  return matchingPaths(record.files, changedFiles);
5318
5404
  }
5319
5405
  async function computeReview(rootDir, base, options = {}) {
5320
- const resolvedRoot = path24.resolve(rootDir);
5406
+ const resolvedRoot = path25.resolve(rootDir);
5321
5407
  const head = await getCurrentGitHash(resolvedRoot);
5322
5408
  if (head === "unknown") return null;
5323
5409
  const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
@@ -5367,7 +5453,7 @@ async function computeReview(rootDir, base, options = {}) {
5367
5453
  analyzed,
5368
5454
  async (relPath) => {
5369
5455
  try {
5370
- await fs19.access(path24.join(resolvedRoot, relPath));
5456
+ await fs19.access(path25.join(resolvedRoot, relPath));
5371
5457
  return true;
5372
5458
  } catch {
5373
5459
  return false;
@@ -5400,7 +5486,7 @@ async function computeReview(rootDir, base, options = {}) {
5400
5486
  }
5401
5487
  return finalize();
5402
5488
  }
5403
- var exec9, MAX_ANALYZED_FILES;
5489
+ var exec2, MAX_ANALYZED_FILES;
5404
5490
  var init_review = __esm({
5405
5491
  "src/review/review.ts"() {
5406
5492
  "use strict";
@@ -5412,7 +5498,7 @@ var init_review = __esm({
5412
5498
  init_cochange();
5413
5499
  init_evidence2();
5414
5500
  init_snapshot();
5415
- exec9 = promisify12(execFile12);
5501
+ exec2 = promisify4(execFile4);
5416
5502
  MAX_ANALYZED_FILES = 50;
5417
5503
  }
5418
5504
  });