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.
@@ -31,6 +31,32 @@ var init_profile = __esm({
31
31
  }
32
32
  });
33
33
 
34
+ // src/utils/git-read.ts
35
+ import { execFile } from "child_process";
36
+ import { promisify } from "util";
37
+ function execGit(args, options) {
38
+ const command = args.find((arg) => !arg.startsWith("-")) ?? "other";
39
+ return profilePhase("git." + (commands.has(command) ? command : "other"), async () => {
40
+ const { input, ...processOptions } = options;
41
+ const operation = execute("git", args, processOptions);
42
+ if (input !== void 0) {
43
+ operation.child.stdin.on("error", () => {
44
+ });
45
+ operation.child.stdin.end(input);
46
+ }
47
+ return operation;
48
+ });
49
+ }
50
+ var execute, commands;
51
+ var init_git_read = __esm({
52
+ "src/utils/git-read.ts"() {
53
+ "use strict";
54
+ init_profile();
55
+ execute = promisify(execFile);
56
+ commands = /* @__PURE__ */ new Set(["rev-parse", "symbolic-ref", "for-each-ref", "ls-files", "check-ignore", "status", "log", "diff", "ls-tree", "show", "merge-base"]);
57
+ }
58
+ });
59
+
34
60
  // src/utils/paths.ts
35
61
  import path from "path";
36
62
  function normalizeRepoPath(value) {
@@ -62,8 +88,6 @@ var init_paths = __esm({
62
88
  import fs from "fs/promises";
63
89
  import { constants } from "fs";
64
90
  import path2 from "path";
65
- import { execFile } from "child_process";
66
- import { promisify } from "util";
67
91
  import fg from "fast-glob";
68
92
  async function readBoundedFile(file, maxBytes) {
69
93
  const handle = await fs.open(file, constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW);
@@ -105,12 +129,12 @@ async function loadProjectConfig(root) {
105
129
  throw new Error(`Cannot apply project file policy: ${error instanceof Error ? error.message : String(error)}`);
106
130
  }
107
131
  }
108
- var exec, SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
132
+ var SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
109
133
  var init_files = __esm({
110
134
  "src/utils/files.ts"() {
111
135
  "use strict";
136
+ init_git_read();
112
137
  init_paths();
113
- exec = promisify(execFile);
114
138
  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"];
115
139
  SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(",")}}`;
116
140
  SOURCE_IGNORE = [
@@ -210,6 +234,102 @@ var init_storage = __esm({
210
234
  }
211
235
  });
212
236
 
237
+ // src/utils/store-lock.ts
238
+ import fs3 from "fs/promises";
239
+ import os from "os";
240
+ async function lockDiagnostic(file) {
241
+ let reason;
242
+ try {
243
+ const owner = JSON.parse(await fs3.readFile(file, "utf8"));
244
+ if (!owner || typeof owner.host !== "string" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
245
+ reason = "Lock owner data is incomplete or malformed.";
246
+ } else if (owner.host !== os.hostname()) {
247
+ reason = "The lock belongs to another host; its process cannot be checked locally.";
248
+ } else {
249
+ try {
250
+ process.kill(owner.pid, 0);
251
+ reason = `Local owner PID ${owner.pid} is still running.`;
252
+ } catch (error) {
253
+ reason = 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.`;
254
+ }
255
+ }
256
+ } catch (error) {
257
+ reason = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
258
+ }
259
+ if (await fs3.lstat(file + ".reclaim").then(() => true, () => false)) {
260
+ reason += " A recovery guard also needs inspection: " + file + ".reclaim.";
261
+ }
262
+ return reason + " 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.";
263
+ }
264
+ async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason store") {
265
+ const file = await storePath(root, directory + "/lock", true);
266
+ const deadline = Date.now() + waitMs;
267
+ let handle;
268
+ while (!handle) {
269
+ try {
270
+ handle = await fs3.open(file, "wx", 384);
271
+ try {
272
+ await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
273
+ } catch (error) {
274
+ await handle.close().catch(() => {
275
+ });
276
+ handle = void 0;
277
+ await fs3.rm(file, { force: true }).catch(() => {
278
+ });
279
+ throw error;
280
+ }
281
+ } catch (error) {
282
+ if (error.code !== "EEXIST") throw error;
283
+ try {
284
+ const owner = JSON.parse(await fs3.readFile(file, "utf8"));
285
+ if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
286
+ try {
287
+ process.kill(owner.pid, 0);
288
+ } catch (probe) {
289
+ if (probe.code === "ESRCH") {
290
+ const reclaim = file + ".reclaim";
291
+ let guard;
292
+ try {
293
+ guard = await fs3.open(reclaim, "wx", 384);
294
+ const current = JSON.parse(await fs3.readFile(file, "utf8"));
295
+ if (current.pid === owner.pid && current.host === owner.host) await fs3.unlink(file);
296
+ } finally {
297
+ if (guard) {
298
+ await guard.close();
299
+ await fs3.rm(reclaim, { force: true });
300
+ }
301
+ }
302
+ }
303
+ }
304
+ }
305
+ } catch {
306
+ }
307
+ if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
308
+ await new Promise((resolve) => setTimeout(resolve, 40));
309
+ }
310
+ }
311
+ try {
312
+ return await run();
313
+ } finally {
314
+ await handle.close();
315
+ await fs3.unlink(file);
316
+ }
317
+ }
318
+ var init_store_lock = __esm({
319
+ "src/utils/store-lock.ts"() {
320
+ "use strict";
321
+ init_storage();
322
+ }
323
+ });
324
+
325
+ // src/snapshot/lock.ts
326
+ var init_lock = __esm({
327
+ "src/snapshot/lock.ts"() {
328
+ "use strict";
329
+ init_store_lock();
330
+ }
331
+ });
332
+
213
333
  // src/test-map.ts
214
334
  import path4 from "path";
215
335
  var init_test_map = __esm({
@@ -221,12 +341,10 @@ var init_test_map = __esm({
221
341
 
222
342
  // src/snapshot/snapshot.ts
223
343
  import path5 from "path";
224
- import { execFile as execFile2 } from "child_process";
225
- import { promisify as promisify2 } from "util";
226
344
  import { z } from "zod";
227
345
  async function getCurrentGitHash(rootDir) {
228
346
  try {
229
- const { stdout } = await exec2("git", ["rev-parse", "HEAD"], {
347
+ const { stdout } = await execGit(["rev-parse", "HEAD"], {
230
348
  cwd: rootDir
231
349
  });
232
350
  return stdout.trim();
@@ -234,21 +352,23 @@ async function getCurrentGitHash(rootDir) {
234
352
  return "unknown";
235
353
  }
236
354
  }
237
- var exec2, repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
355
+ var repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
238
356
  var init_snapshot = __esm({
239
357
  "src/snapshot/snapshot.ts"() {
240
358
  "use strict";
359
+ init_git_read();
241
360
  init_files();
242
361
  init_files();
243
362
  init_storage();
363
+ init_lock();
244
364
  init_paths();
245
365
  init_test_map();
246
- exec2 = promisify2(execFile2);
247
366
  repoPath = z.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
248
367
  verificationFields = {
249
368
  refreshedHash: z.string().optional(),
250
369
  verifiedAt: z.string().optional(),
251
370
  verifiedHash: z.string().optional(),
371
+ verificationToken: z.string().optional(),
252
372
  verificationFailed: z.boolean().optional(),
253
373
  verificationNote: z.string().optional()
254
374
  };
@@ -272,10 +392,8 @@ var init_snapshot = __esm({
272
392
  });
273
393
 
274
394
  // src/drift/drift.ts
275
- import fs3 from "fs/promises";
395
+ import fs4 from "fs/promises";
276
396
  import path6 from "path";
277
- import { execFile as execFile3 } from "child_process";
278
- import { promisify as promisify3 } from "util";
279
397
  function parseChanges(output) {
280
398
  const fields = output.split("\0");
281
399
  const changes = [];
@@ -296,7 +414,7 @@ function touchedPaths(changes) {
296
414
  async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
297
415
  if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-") || !toHash || toHash === "unknown" || toHash.startsWith("-")) return null;
298
416
  try {
299
- const { stdout } = await exec3("git", ["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
417
+ const { stdout } = await execGit(["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
300
418
  return parseChanges(stdout);
301
419
  } catch {
302
420
  return null;
@@ -305,8 +423,8 @@ async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
305
423
  async function getWorkingTree(resolvedRoot) {
306
424
  try {
307
425
  const [diff, untracked] = await Promise.all([
308
- exec3("git", ["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
309
- exec3("git", ["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
426
+ execGit(["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
427
+ execGit(["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
310
428
  ]);
311
429
  const untrackedFiles = untracked.stdout.split("\0").filter((f) => f && !f.startsWith(".mason/"));
312
430
  return { available: true, changedFiles: [.../* @__PURE__ */ new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles])].sort(), untrackedFiles };
@@ -314,13 +432,12 @@ async function getWorkingTree(resolvedRoot) {
314
432
  return { available: false, changedFiles: [], untrackedFiles: [] };
315
433
  }
316
434
  }
317
- var exec3;
318
435
  var init_drift = __esm({
319
436
  "src/drift/drift.ts"() {
320
437
  "use strict";
438
+ init_git_read();
321
439
  init_snapshot();
322
440
  init_paths();
323
- exec3 = promisify3(execFile3);
324
441
  }
325
442
  });
326
443
 
@@ -548,14 +665,14 @@ function extractClaims(content) {
548
665
  const ignored = computeIgnoredLines(lines);
549
666
  const paths = /* @__PURE__ */ new Map();
550
667
  const counts = [];
551
- const commands = /* @__PURE__ */ new Map();
668
+ const commands2 = /* @__PURE__ */ new Map();
552
669
  const addPath = (claim) => {
553
670
  const key = [claim.relativeTo ?? "scope", claim.path].join(":");
554
671
  if (!paths.has(key)) paths.set(key, claim);
555
672
  };
556
673
  const addCommand = (claim) => {
557
674
  const key = JSON.stringify([claim.scriptName, claim.directory, claim.scopeUnknown]);
558
- if (!commands.has(key)) commands.set(key, claim);
675
+ if (!commands2.has(key)) commands2.set(key, claim);
559
676
  };
560
677
  const parsed = markdown.parse(lines.map((line, i) => ignored[i] ? "" : line).join("\n"), {});
561
678
  for (const token of parsed) {
@@ -657,7 +774,7 @@ function extractClaims(content) {
657
774
  return {
658
775
  paths: [...paths.values()],
659
776
  counts,
660
- commands: [...commands.values()]
777
+ commands: [...commands2.values()]
661
778
  };
662
779
  }
663
780
  var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
@@ -707,9 +824,50 @@ var init_claims = __esm({
707
824
  }
708
825
  });
709
826
 
827
+ // src/audit/inspection.ts
828
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
829
+ import path8 from "path";
830
+ function inspectionRead(root, key, read) {
831
+ const inspection = inspections.getStore();
832
+ if (!inspection || inspection.root !== path8.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 === path8.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(path8.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
+
710
870
  // src/audit/git.ts
711
- import { execFile as execFile4 } from "child_process";
712
- import { promisify as promisify4 } from "util";
713
871
  function parseCommitLine(line) {
714
872
  const parts = line.split(" ");
715
873
  if (parts.length < 3 || !parts[0]) return null;
@@ -717,8 +875,7 @@ function parseCommitLine(line) {
717
875
  }
718
876
  async function lastCommitOf(resolvedRoot, relPath) {
719
877
  try {
720
- const { stdout } = await exec4(
721
- "git",
878
+ const { stdout } = await inspectionGit(
722
879
  ["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
723
880
  { cwd: resolvedRoot }
724
881
  );
@@ -730,8 +887,7 @@ async function lastCommitOf(resolvedRoot, relPath) {
730
887
  }
731
888
  async function deletingCommitOf(resolvedRoot, relPath) {
732
889
  try {
733
- const { stdout } = await exec4(
734
- "git",
890
+ const { stdout } = await inspectionGit(
735
891
  [
736
892
  "log",
737
893
  "-1",
@@ -750,8 +906,7 @@ async function deletingCommitOf(resolvedRoot, relPath) {
750
906
  }
751
907
  async function firstCommitOf(resolvedRoot, relPath) {
752
908
  try {
753
- const { stdout } = await exec4(
754
- "git",
909
+ const { stdout } = await inspectionGit(
755
910
  ["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
756
911
  { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
757
912
  );
@@ -764,8 +919,7 @@ async function firstCommitOf(resolvedRoot, relPath) {
764
919
  async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
765
920
  if (!fromHash || fromHash === "unknown") return null;
766
921
  try {
767
- const { stdout } = await exec4(
768
- "git",
922
+ const { stdout } = await inspectionGit(
769
923
  [
770
924
  "log",
771
925
  `${fromHash}..HEAD`,
@@ -789,24 +943,22 @@ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
789
943
  return null;
790
944
  }
791
945
  }
792
- var exec4, COMMIT_FORMAT;
946
+ var COMMIT_FORMAT;
793
947
  var init_git = __esm({
794
948
  "src/audit/git.ts"() {
795
949
  "use strict";
796
- exec4 = promisify4(execFile4);
950
+ init_inspection();
797
951
  COMMIT_FORMAT = "%H%x09%cI%x09%s";
798
952
  }
799
953
  });
800
954
 
801
955
  // src/audit/inputs.ts
802
- import fs4 from "fs/promises";
956
+ import fs5 from "fs/promises";
803
957
  import * as nativeFs from "fs";
804
- import path8 from "path";
805
- import { execFile as execFile5 } from "child_process";
806
- import { promisify as promisify5 } from "util";
958
+ import path9 from "path";
807
959
  import fg2 from "fast-glob";
808
960
  async function auditInputPath(root, file) {
809
- if (file === ".") return fs4.realpath(root);
961
+ if (file === ".") return fs5.realpath(root);
810
962
  try {
811
963
  return await storePath(root, file);
812
964
  } catch (error) {
@@ -830,7 +982,7 @@ async function auditGlob(root, patterns, options) {
830
982
  if (Array.isArray(patterns) && !patterns.length) return [];
831
983
  const directories = /* @__PURE__ */ new Set();
832
984
  const readdir2 = ((directory, opts, callback) => {
833
- const relative = path8.relative(root, directory).split(path8.sep).join("/") || ".";
985
+ const relative = path9.relative(root, directory).split(path9.sep).join("/") || ".";
834
986
  directories.add(relative);
835
987
  if (directories.size > MAX_INPUTS) {
836
988
  callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
@@ -841,7 +993,7 @@ async function auditGlob(root, patterns, options) {
841
993
  }, callback);
842
994
  });
843
995
  const lstat2 = ((file, callback) => {
844
- const relative = path8.relative(root, file).split(path8.sep).join("/") || ".";
996
+ const relative = path9.relative(root, file).split(path9.sep).join("/") || ".";
845
997
  auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
846
998
  });
847
999
  const stream = fg2.stream(patterns, {
@@ -878,62 +1030,48 @@ async function auditGlob(root, patterns, options) {
878
1030
  return matches.sort();
879
1031
  }
880
1032
  async function gitSourcePaths(root) {
881
- const { stdout } = await execute(
882
- "git",
883
- [
884
- "ls-files",
885
- "-z",
886
- "--cached",
887
- "--others",
888
- "--exclude-standard",
889
- "--",
890
- ...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
891
- ],
892
- { cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
893
- );
894
- return [...new Set(stdout.split("\0").filter(Boolean))].sort();
1033
+ return auditGitPaths(root, "sources");
895
1034
  }
896
1035
  async function gitIgnoredPaths(root, files) {
897
1036
  if (!files.length) return /* @__PURE__ */ new Set();
898
- const operation = execute(
899
- "git",
900
- ["check-ignore", "-z", "--stdin"],
901
- { cwd: root, maxBuffer: 4 * 1024 * 1024, timeout: 1e4 }
902
- );
903
- operation.child.stdin.on("error", () => {
904
- });
905
- operation.child.stdin.end(files.join("\0") + "\0");
906
- try {
907
- return new Set((await operation).stdout.split("\0").filter(Boolean));
908
- } catch (error) {
909
- if (error.code === 1) return /* @__PURE__ */ new Set();
910
- 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
+ }
911
1050
  }
1051
+ return ignored;
912
1052
  }
913
- var execute, MAX_INPUTS;
1053
+ var MAX_INPUTS;
914
1054
  var init_inputs = __esm({
915
1055
  "src/audit/inputs.ts"() {
916
1056
  "use strict";
917
1057
  init_files();
918
1058
  init_storage();
919
- execute = promisify5(execFile5);
1059
+ init_git_read();
1060
+ init_inspection();
920
1061
  MAX_INPUTS = 1e5;
921
1062
  }
922
1063
  });
923
1064
 
924
1065
  // src/audit/docs.ts
925
- import fs5 from "fs/promises";
926
- import path9 from "path";
927
- import { execFile as execFile6 } from "child_process";
928
- import { promisify as promisify6 } from "util";
1066
+ import fs6 from "fs/promises";
1067
+ import path10 from "path";
929
1068
  import fg3 from "fast-glob";
930
1069
  async function dirtyDocs(resolvedRoot, files) {
931
1070
  if (!files.length) return /* @__PURE__ */ new Set();
932
1071
  try {
933
1072
  const dirty = /* @__PURE__ */ new Set();
934
1073
  for (let offset = 0; offset < files.length; offset += 256) {
935
- const { stdout } = await exec5(
936
- "git",
1074
+ const { stdout } = await execGit(
937
1075
  ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
938
1076
  { cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
939
1077
  );
@@ -953,25 +1091,10 @@ async function dirtyDocs(resolvedRoot, files) {
953
1091
  async function discoverDocPaths(root) {
954
1092
  let candidates = [];
955
1093
  try {
956
- const { stdout } = await exec5(
957
- "git",
958
- [
959
- "ls-files",
960
- "-z",
961
- "--cached",
962
- "--others",
963
- "--exclude-standard",
964
- "--",
965
- ":(icase,glob)**/readme.md",
966
- ":(icase,glob)**/agents.md",
967
- ":(icase,glob)**/claude.md"
968
- ],
969
- { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
970
- );
971
- candidates = stdout.split("\0").filter(Boolean);
1094
+ candidates = await auditGitPaths(root, "documents");
972
1095
  } catch (error) {
973
1096
  try {
974
- await exec5("git", ["rev-parse", "--git-dir"], { cwd: root });
1097
+ await execGit(["rev-parse", "--git-dir"], { cwd: root });
975
1098
  } catch {
976
1099
  return localDocPaths(root);
977
1100
  }
@@ -1012,20 +1135,20 @@ async function localDocPaths(root) {
1012
1135
  for (const dir of [".", ".claude"]) {
1013
1136
  let entries;
1014
1137
  try {
1015
- entries = await fs5.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1138
+ entries = await fs6.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1016
1139
  } catch (error) {
1017
1140
  if (error.code === "ENOENT") continue;
1018
1141
  throw error;
1019
1142
  }
1020
1143
  for (const entry of entries) if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
1021
- paths.push(path9.posix.join(dir, entry.name));
1144
+ paths.push(path10.posix.join(dir, entry.name));
1022
1145
  }
1023
1146
  }
1024
1147
  return paths;
1025
1148
  }
1026
1149
  function documentScope(file) {
1027
- const parent = path9.posix.dirname(file);
1028
- return path9.posix.basename(parent).toLowerCase() === ".claude" ? path9.posix.dirname(parent) : parent;
1150
+ const parent = path10.posix.dirname(file);
1151
+ return path10.posix.basename(parent).toLowerCase() === ".claude" ? path10.posix.dirname(parent) : parent;
1029
1152
  }
1030
1153
  async function discoverDocs(resolvedRoot) {
1031
1154
  return profilePhase("audit.documents", () => readDocs(resolvedRoot));
@@ -1045,7 +1168,7 @@ async function readDocs(resolvedRoot) {
1045
1168
  path: candidate,
1046
1169
  content,
1047
1170
  scope: documentScope(candidate),
1048
- kind: /^readme\.md$/i.test(path9.posix.basename(candidate)) ? "readme" : "instructions",
1171
+ kind: /^readme\.md$/i.test(path10.posix.basename(candidate)) ? "readme" : "instructions",
1049
1172
  lineCount: content.split("\n").length,
1050
1173
  lastCommit,
1051
1174
  dirty: dirty.has(candidate),
@@ -1055,16 +1178,17 @@ async function readDocs(resolvedRoot) {
1055
1178
  }
1056
1179
  return docs;
1057
1180
  }
1058
- var exec5, DOC_CANDIDATES;
1181
+ var DOC_CANDIDATES;
1059
1182
  var init_docs = __esm({
1060
1183
  "src/audit/docs.ts"() {
1061
1184
  "use strict";
1185
+ init_git_read();
1062
1186
  init_profile();
1063
1187
  init_claims();
1064
1188
  init_git();
1065
1189
  init_inputs();
1066
1190
  init_files();
1067
- exec5 = promisify6(execFile6);
1191
+ init_inspection();
1068
1192
  DOC_CANDIDATES = [
1069
1193
  "AGENTS.md",
1070
1194
  "CLAUDE.md",
@@ -1090,15 +1214,15 @@ var init_types = __esm({
1090
1214
  });
1091
1215
 
1092
1216
  // src/audit/scope.ts
1093
- import fs6 from "fs/promises";
1094
- import path10 from "path";
1217
+ import fs7 from "fs/promises";
1218
+ import path11 from "path";
1095
1219
  function scopedPath(directory, value) {
1096
- if (path10.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
1097
- const resolved = path10.posix.normalize(path10.posix.join(directory, value));
1220
+ if (path11.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
1221
+ const resolved = path11.posix.normalize(path11.posix.join(directory, value));
1098
1222
  return resolved === ".." || resolved.startsWith("../") ? null : resolved;
1099
1223
  }
1100
1224
  function pathClaimScope(doc, claim) {
1101
- const directory = claim.relativeTo === "document" ? path10.posix.dirname(doc) : documentScope(doc);
1225
+ const directory = claim.relativeTo === "document" ? path11.posix.dirname(doc) : documentScope(doc);
1102
1226
  const local = scopedPath(directory, claim.path);
1103
1227
  if (!local) return null;
1104
1228
  const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
@@ -1110,7 +1234,7 @@ function pathClaimScope(doc, claim) {
1110
1234
  }
1111
1235
  async function pathExists(root, file) {
1112
1236
  try {
1113
- await fs6.access(await auditInputPath(root, file));
1237
+ await fs7.access(await auditInputPath(root, file));
1114
1238
  return true;
1115
1239
  } catch (error) {
1116
1240
  if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return false;
@@ -1132,7 +1256,7 @@ var init_scope = __esm({
1132
1256
  });
1133
1257
 
1134
1258
  // src/audit/checks/deleted-reference.ts
1135
- import path11 from "path";
1259
+ import path12 from "path";
1136
1260
  async function checkDeletedReferences(ctx) {
1137
1261
  const result = emptyResult();
1138
1262
  for (const doc of ctx.docs) {
@@ -1161,7 +1285,7 @@ async function checkDeletedReferences(ctx) {
1161
1285
  const candidates = await Promise.all(scope.candidates.map(async (file) => ({
1162
1286
  file,
1163
1287
  tracked: await lastCommitOf(ctx.root, file),
1164
- parent: await pathExists(ctx.root, path11.posix.dirname(file))
1288
+ parent: await pathExists(ctx.root, path12.posix.dirname(file))
1165
1289
  })));
1166
1290
  const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
1167
1291
  if (!candidate && claim.relativeTo !== "document") continue;
@@ -1200,8 +1324,8 @@ var init_deleted_reference = __esm({
1200
1324
 
1201
1325
  // src/audit/checks/new-module.ts
1202
1326
  import fg4 from "fast-glob";
1203
- import fs7 from "fs/promises";
1204
- import path12 from "path";
1327
+ import fs8 from "fs/promises";
1328
+ import path13 from "path";
1205
1329
  function escapeRegExp(text2) {
1206
1330
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1207
1331
  }
@@ -1239,23 +1363,36 @@ async function checkNewModules(ctx) {
1239
1363
  return result;
1240
1364
  }
1241
1365
  function moduleDocumentation(docs) {
1242
- return docs.map((doc) => doc.content + "\n" + (path12.posix.dirname(doc.path) === "." ? "" : path12.posix.dirname(doc.path) + "/")).join("\n");
1366
+ return docs.map((doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")).join("\n");
1243
1367
  }
1244
- async function moduleCandidates(root, combinedDocs) {
1368
+ function moduleCandidates(root, combinedDocs) {
1369
+ return inspectionRead(root, "modules:" + combinedDocs, () => collectModuleCandidates(root, combinedDocs));
1370
+ }
1371
+ async function collectModuleCandidates(root, combinedDocs) {
1245
1372
  const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
1246
1373
  const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
1247
1374
  const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
1248
- const listSubdirs = async (dir) => {
1249
- const entries = await fs7.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1250
- 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));
1251
- const ignored = await gitIgnoredPaths(root, dirs);
1252
- const visible = new Set(dirs.filter((dir2) => !ignored.has(dir2) || sources.some((file) => file.startsWith(dir2 + "/"))));
1253
- return (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1254
- ignore,
1255
- onlyDirectories: true,
1256
- label: "Module directory discovery",
1257
- select: (file) => visible.has(file)
1258
- })).map((file) => path12.posix.basename(file));
1375
+ const listSubdirs = async (parents) => {
1376
+ const byParent = /* @__PURE__ */ new Map();
1377
+ for (let offset = 0; offset < parents.length; offset += 16) {
1378
+ await Promise.all(parents.slice(offset, offset + 16).map(async (dir) => {
1379
+ const entries = await fs8.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1380
+ 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));
1381
+ byParent.set(dir, dirs);
1382
+ }));
1383
+ }
1384
+ const ignored = await gitIgnoredPaths(root, [...byParent.values()].flat());
1385
+ const result = /* @__PURE__ */ new Map();
1386
+ for (const dir of parents) {
1387
+ const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
1388
+ result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1389
+ ignore,
1390
+ onlyDirectories: true,
1391
+ label: "Module directory discovery",
1392
+ select: (file) => visible.has(file)
1393
+ })).map((file) => path13.posix.basename(file)));
1394
+ }
1395
+ return result;
1259
1396
  };
1260
1397
  const countSourceFiles = async (dir) => {
1261
1398
  const files = sources.filter((file) => file.startsWith(dir + "/"));
@@ -1271,14 +1408,16 @@ async function moduleCandidates(root, combinedDocs) {
1271
1408
  return count3;
1272
1409
  };
1273
1410
  const candidates = [];
1274
- for (const topDir of await listSubdirs(".")) {
1411
+ const topDirs = (await listSubdirs(["."])).get(".");
1412
+ const nested = await listSubdirs(topDirs.filter((dir) => isMentioned(combinedDocs, dir)));
1413
+ for (const topDir of topDirs) {
1275
1414
  const topMentioned = isMentioned(combinedDocs, topDir);
1276
1415
  if (!topMentioned) {
1277
1416
  const count3 = await countSourceFiles(topDir);
1278
1417
  if (count3 >= 1) candidates.push({ dir: topDir, sourceFileCount: count3 });
1279
1418
  continue;
1280
1419
  }
1281
- const subdirs = await listSubdirs(topDir);
1420
+ const subdirs = nested.get(topDir);
1282
1421
  const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
1283
1422
  if (mentioned.length < ENUMERATION_THRESHOLD) continue;
1284
1423
  for (const sub of subdirs) {
@@ -1295,6 +1434,7 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
1295
1434
  var init_new_module = __esm({
1296
1435
  "src/audit/checks/new-module.ts"() {
1297
1436
  "use strict";
1437
+ init_inspection();
1298
1438
  init_files();
1299
1439
  init_inputs();
1300
1440
  init_git();
@@ -1324,7 +1464,7 @@ var init_new_module = __esm({
1324
1464
  });
1325
1465
 
1326
1466
  // src/audit/checks/stale-count.ts
1327
- import path13 from "path";
1467
+ import path14 from "path";
1328
1468
  async function countGradleModules(root) {
1329
1469
  for (const name of ["settings.gradle.kts", "settings.gradle"]) {
1330
1470
  const content = await readAuditInput(root, name);
@@ -1358,7 +1498,7 @@ async function countNpmWorkspaces(root) {
1358
1498
  return {
1359
1499
  actual: matched.length,
1360
1500
  countedFrom: "package.json workspaces",
1361
- members: matched.map((m) => path13.dirname(m)).sort()
1501
+ members: matched.map((m) => path14.dirname(m)).sort()
1362
1502
  };
1363
1503
  }
1364
1504
  }
@@ -1389,7 +1529,7 @@ async function countNpmWorkspaces(root) {
1389
1529
  return {
1390
1530
  actual: matched.length,
1391
1531
  countedFrom: "pnpm-workspace.yaml",
1392
- members: matched.map((m) => path13.dirname(m)).sort()
1532
+ members: matched.map((m) => path14.dirname(m)).sort()
1393
1533
  };
1394
1534
  }
1395
1535
  }
@@ -1411,8 +1551,8 @@ async function countCargoCrates(root) {
1411
1551
  ignore: ["**/target/**"],
1412
1552
  label: "Cargo workspace discovery"
1413
1553
  });
1414
- for (const m of matched) members.add(path13.dirname(m));
1415
- } else if (await readAuditInput(root, path13.posix.join(entry, "Cargo.toml")) !== null) {
1554
+ for (const m of matched) members.add(path14.dirname(m));
1555
+ } else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
1416
1556
  members.add(entry);
1417
1557
  }
1418
1558
  }
@@ -1432,7 +1572,7 @@ async function resolveCountSource(root, claim) {
1432
1572
  }
1433
1573
  async function resolveDocCountSource(root, doc, claim) {
1434
1574
  const scope = documentScope(doc);
1435
- const source = await resolveCountSource(path13.join(root, scope), claim);
1575
+ const source = await resolveCountSource(path14.join(root, scope), claim);
1436
1576
  if (!source || scope === ".") return source;
1437
1577
  return {
1438
1578
  ...source,
@@ -1484,7 +1624,7 @@ var init_stale_count = __esm({
1484
1624
  });
1485
1625
 
1486
1626
  // src/audit/checks/dead-command.ts
1487
- import path14 from "path";
1627
+ import path15 from "path";
1488
1628
  function scriptsOf(raw) {
1489
1629
  try {
1490
1630
  const pkg = JSON.parse(raw);
@@ -1520,7 +1660,7 @@ async function commandInputs(root, docs) {
1520
1660
  }
1521
1661
  let cursor = directory;
1522
1662
  while (true) {
1523
- const manifest = path14.posix.join(cursor, "package.json");
1663
+ const manifest = path15.posix.join(cursor, "package.json");
1524
1664
  scope.candidates.push(manifest);
1525
1665
  const raw = await read(manifest);
1526
1666
  if (raw !== null) {
@@ -1531,7 +1671,7 @@ async function commandInputs(root, docs) {
1531
1671
  break;
1532
1672
  }
1533
1673
  if (claim.directory !== void 0 || cursor === ".") break;
1534
- cursor = path14.posix.dirname(cursor);
1674
+ cursor = path15.posix.dirname(cursor);
1535
1675
  }
1536
1676
  if (!resolution.manifest && !resolution.problem) {
1537
1677
  resolution.problem = `No package.json for the command's scope (${directory}).`;
@@ -1594,10 +1734,8 @@ var init_dead_command = __esm({
1594
1734
  });
1595
1735
 
1596
1736
  // src/audit/release-metadata.ts
1597
- import { execFile as execFile7 } from "child_process";
1598
- import { promisify as promisify7 } from "util";
1599
1737
  async function git(root, args) {
1600
- return (await exec6("git", args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1738
+ return (await execGit(args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1601
1739
  }
1602
1740
  function withoutAndroidReleaseValues(text2) {
1603
1741
  if (/\/\*|"""|'''/.test(text2) || !/id\s*\(?\s*["']com\.android\.(application|library)["']/.test(text2)) return null;
@@ -1643,11 +1781,10 @@ async function releaseMetadataOnly(root, commit) {
1643
1781
  return false;
1644
1782
  }
1645
1783
  }
1646
- var exec6;
1647
1784
  var init_release_metadata = __esm({
1648
1785
  "src/audit/release-metadata.ts"() {
1649
1786
  "use strict";
1650
- exec6 = promisify7(execFile7);
1787
+ init_git_read();
1651
1788
  }
1652
1789
  });
1653
1790
 
@@ -1743,7 +1880,7 @@ var init_deps_changed = __esm({
1743
1880
  });
1744
1881
 
1745
1882
  // src/context/lexical.ts
1746
- import path15 from "path";
1883
+ import path16 from "path";
1747
1884
  var init_lexical = __esm({
1748
1885
  "src/context/lexical.ts"() {
1749
1886
  "use strict";
@@ -1903,8 +2040,8 @@ var init_provenance = __esm({
1903
2040
  });
1904
2041
 
1905
2042
  // src/decisions/decisions.ts
1906
- import fs8 from "fs/promises";
1907
- import path16 from "path";
2043
+ import fs9 from "fs/promises";
2044
+ import path17 from "path";
1908
2045
  import { createHash } from "crypto";
1909
2046
  import { z as z3 } from "zod";
1910
2047
  async function loadDecisionStore(rootDir) {
@@ -1912,7 +2049,7 @@ async function loadDecisionStore(rootDir) {
1912
2049
  const diagnostics = [];
1913
2050
  let entries;
1914
2051
  try {
1915
- entries = await fs8.readdir(await storePath(rootDir, ".mason/decisions"));
2052
+ entries = await fs9.readdir(await storePath(rootDir, ".mason/decisions"));
1916
2053
  } catch (error) {
1917
2054
  if (error.code !== "ENOENT") diagnostics.push({ path: ".mason/decisions", message: String(error) });
1918
2055
  return { records, diagnostics };
@@ -1955,9 +2092,9 @@ var init_decisions = __esm({
1955
2092
  });
1956
2093
 
1957
2094
  // src/decisions/drift.ts
1958
- import path17 from "path";
2095
+ import path18 from "path";
1959
2096
  async function computeDecisionDrift(rootDir, decisions) {
1960
- const resolvedRoot = path17.resolve(rootDir);
2097
+ const resolvedRoot = path18.resolve(rootDir);
1961
2098
  const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
1962
2099
  const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
1963
2100
  const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
@@ -2180,68 +2317,15 @@ var init_findings = __esm({
2180
2317
  });
2181
2318
 
2182
2319
  // src/automation/store.ts
2183
- import fs9 from "fs/promises";
2184
- import os from "os";
2185
2320
  import { z as z5 } from "zod";
2186
- async function withLock(root, directory, run, waitMs = 5e3) {
2187
- const file = await storePath(root, directory + "/lock", true);
2188
- const deadline = Date.now() + waitMs;
2189
- let handle;
2190
- while (!handle) {
2191
- try {
2192
- handle = await fs9.open(file, "wx", 384);
2193
- try {
2194
- await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
2195
- } catch (error) {
2196
- await handle.close().catch(() => {
2197
- });
2198
- handle = void 0;
2199
- await fs9.rm(file, { force: true }).catch(() => {
2200
- });
2201
- throw error;
2202
- }
2203
- } catch (error) {
2204
- if (error.code !== "EEXIST") throw error;
2205
- try {
2206
- const owner = JSON.parse(await fs9.readFile(file, "utf8"));
2207
- if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
2208
- try {
2209
- process.kill(owner.pid, 0);
2210
- } catch (probe) {
2211
- if (probe.code === "ESRCH") {
2212
- const reclaim = file + ".reclaim";
2213
- let guard;
2214
- try {
2215
- guard = await fs9.open(reclaim, "wx", 384);
2216
- const current = JSON.parse(await fs9.readFile(file, "utf8"));
2217
- if (current.pid === owner.pid && current.host === owner.host) await fs9.unlink(file);
2218
- } finally {
2219
- if (guard) {
2220
- await guard.close();
2221
- await fs9.rm(reclaim, { force: true });
2222
- }
2223
- }
2224
- }
2225
- }
2226
- }
2227
- } catch {
2228
- }
2229
- if (Date.now() >= deadline) throw new Error("Automation is busy or its lock needs inspection: " + file);
2230
- await new Promise((resolve) => setTimeout(resolve, 40));
2231
- }
2232
- }
2233
- try {
2234
- return await run();
2235
- } finally {
2236
- await handle.close();
2237
- await fs9.unlink(file);
2238
- }
2321
+ function withLock(root, directory, run, waitMs = 5e3) {
2322
+ return withStoreLock(root, directory, run, waitMs, "Automation");
2239
2323
  }
2240
2324
  var hostSchema, stateSchema;
2241
2325
  var init_store = __esm({
2242
2326
  "src/automation/store.ts"() {
2243
2327
  "use strict";
2244
- init_storage();
2328
+ init_store_lock();
2245
2329
  hostSchema = z5.enum(["claude", "codex"]);
2246
2330
  stateSchema = z5.object({
2247
2331
  version: z5.literal(1),
@@ -2284,7 +2368,7 @@ __export(repair_exports, {
2284
2368
  withRepairSession: () => withRepairSession
2285
2369
  });
2286
2370
  import fs10 from "fs/promises";
2287
- import path18 from "path";
2371
+ import path19 from "path";
2288
2372
  import { randomUUID as randomUUID2 } from "crypto";
2289
2373
  import { z as z6 } from "zod";
2290
2374
  function recheckable(finding) {
@@ -2322,8 +2406,8 @@ async function readStableAudit(root, checks, options) {
2322
2406
  async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
2323
2407
  return prepareOriginal(rootDir, checks, options);
2324
2408
  }
2325
- async function withRepairSession(root, options, run, cachedAudit) {
2326
- const before = await Promise.all([getCurrentGitHash(root), docState(root), advisoryReviewInventory(root).then(digest)]);
2409
+ async function withRepairSession(root, options, run, validate, cachedAudit) {
2410
+ const head = await getCurrentGitHash(root);
2327
2411
  const audits = /* @__PURE__ */ new Map();
2328
2412
  const documents = /* @__PURE__ */ new Map();
2329
2413
  const history = /* @__PURE__ */ new Map();
@@ -2334,9 +2418,9 @@ async function withRepairSession(root, options, run, cachedAudit) {
2334
2418
  };
2335
2419
  const session = {
2336
2420
  audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
2337
- if (cachedAudit && cachedAudit.headHash === before[0] && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
2421
+ if (cachedAudit && cachedAudit.headHash === head && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
2338
2422
  const current = structuredClone(cachedAudit);
2339
- const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, before[0])))).filter((r) => r !== null);
2423
+ const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, head)))).filter((r) => r !== null);
2340
2424
  delete current.advisoryReviews;
2341
2425
  if (reviews.length) current.advisoryReviews = reviews;
2342
2426
  return current;
@@ -2344,16 +2428,15 @@ async function withRepairSession(root, options, run, cachedAudit) {
2344
2428
  return computeAudit(root, { ...options, checks });
2345
2429
  }),
2346
2430
  document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
2347
- history: (head) => memo(history, head, () => getChangesWithStatus(root, head)),
2348
- assessment: (dir, finding, head) => memo(assessments, digest([dir, finding, head]), () => assessAdvisory(dir, finding, head))
2431
+ history: (head2) => memo(history, head2, () => getChangesWithStatus(root, head2)),
2432
+ assessment: (dir, finding, head2) => memo(assessments, digest([dir, finding, head2]), () => assessAdvisory(dir, finding, head2))
2349
2433
  };
2350
2434
  const result = await run({
2351
2435
  prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
2352
2436
  verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
2353
2437
  currentAudit: () => session.audit(ALL_CHECKS)
2354
2438
  });
2355
- const after = await Promise.all([getCurrentGitHash(root), docState(root), advisoryReviewInventory(root).then(digest)]);
2356
- if (digest(before) !== digest(after)) throw new Error("HEAD, context files or advisory review records changed during verification; retry against stable evidence.");
2439
+ await validate();
2357
2440
  return result;
2358
2441
  }
2359
2442
  async function prepareOriginal(rootDir, checks, options, session) {
@@ -2375,8 +2458,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
2375
2458
  }
2376
2459
  async function loadRepairBaseline(rootDir, baselinePath) {
2377
2460
  const root = await fs10.realpath(rootDir);
2378
- const declaredRoot = path18.resolve(rootDir);
2379
- const relative = path18.isAbsolute(baselinePath) ? path18.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
2461
+ const declaredRoot = path19.resolve(rootDir);
2462
+ const relative = path19.isAbsolute(baselinePath) ? path19.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
2380
2463
  const stored = baselineSchema.parse(await readStoreJson(root, relative));
2381
2464
  const { digest: savedDigest, ...payload } = stored;
2382
2465
  if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
@@ -2551,8 +2634,6 @@ var init_repair = __esm({
2551
2634
 
2552
2635
  // src/audit/advisory-review.ts
2553
2636
  import fs11 from "fs/promises";
2554
- import { execFile as execFile8 } from "child_process";
2555
- import { promisify as promisify8 } from "util";
2556
2637
  import { z as z7 } from "zod";
2557
2638
  async function readRecord(root, id) {
2558
2639
  const raw = await readStoreJson(root, recordPath(id));
@@ -2747,10 +2828,12 @@ async function reviewAdvisory(rootDir, input) {
2747
2828
  };
2748
2829
  });
2749
2830
  }
2750
- var execute2, git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2831
+ var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2751
2832
  var init_advisory_review = __esm({
2752
2833
  "src/audit/advisory-review.ts"() {
2753
2834
  "use strict";
2835
+ init_inspection();
2836
+ init_git_read();
2754
2837
  init_findings();
2755
2838
  init_deps_changed();
2756
2839
  init_inputs();
@@ -2760,8 +2843,7 @@ var init_advisory_review = __esm({
2760
2843
  init_store();
2761
2844
  init_decisions();
2762
2845
  init_drift2();
2763
- execute2 = promisify8(execFile8);
2764
- git2 = async (root, ...args) => (await execute2("git", args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
2846
+ 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;
2765
2847
  hashSchema = z7.string().regex(/^[a-f0-9]{64}$/);
2766
2848
  commitSchema2 = z7.string().regex(/^[a-f0-9]{40,64}$/);
2767
2849
  evidenceSchema2 = z7.object({
@@ -2805,12 +2887,12 @@ var init_advisory_review = __esm({
2805
2887
 
2806
2888
  // src/audit/audit.ts
2807
2889
  import fs12 from "fs/promises";
2808
- import path19 from "path";
2890
+ import path20 from "path";
2809
2891
  async function computeAudit(rootDir, options = {}) {
2810
2892
  return profilePhase("audit.current", () => auditCurrent(rootDir, options));
2811
2893
  }
2812
2894
  async function auditCurrent(rootDir, options) {
2813
- const resolvedRoot = path19.resolve(rootDir);
2895
+ const resolvedRoot = path20.resolve(rootDir);
2814
2896
  const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
2815
2897
  if (docs.length === 0) return null;
2816
2898
  const report = {
@@ -2844,7 +2926,7 @@ async function auditCurrent(rootDir, options) {
2844
2926
  }
2845
2927
  let decisionsPresent = false;
2846
2928
  try {
2847
- await fs12.access(path19.join(resolvedRoot, ".mason", "decisions"));
2929
+ await fs12.access(path20.join(resolvedRoot, ".mason", "decisions"));
2848
2930
  decisionsPresent = true;
2849
2931
  } catch {
2850
2932
  }
@@ -2889,11 +2971,11 @@ init_audit();
2889
2971
  init_types();
2890
2972
  init_repair();
2891
2973
  init_findings();
2892
- import path21 from "path";
2974
+ import path22 from "path";
2893
2975
 
2894
2976
  // src/audit/advisory-cli.ts
2895
2977
  init_advisory_review();
2896
- import path20 from "path";
2978
+ import path21 from "path";
2897
2979
  import { stripVTControlCharacters } from "util";
2898
2980
  var USAGE = `Usage: mason audit review --baseline <path> --finding <id> [options]
2899
2981
 
@@ -2939,7 +3021,7 @@ async function runAdvisoryCli(args, io = { out: (text2) => console.log(text2), e
2939
3021
  note: flags["--note"],
2940
3022
  reviewToken: flags["--token"]
2941
3023
  };
2942
- const result = await reviewAdvisory(path20.resolve(flags["--dir"] ?? process.cwd()), input);
3024
+ const result = await reviewAdvisory(path21.resolve(flags["--dir"] ?? process.cwd()), input);
2943
3025
  if (json) io.out(JSON.stringify(result, null, 2));
2944
3026
  else {
2945
3027
  const lines = [`Advisory review: ${result.status}.`];
@@ -3172,7 +3254,7 @@ async function runAuditCli(argv, io = {
3172
3254
  io.out(USAGE2);
3173
3255
  return 0;
3174
3256
  }
3175
- const rootDir = path21.resolve(args.dir);
3257
+ const rootDir = path22.resolve(args.dir);
3176
3258
  if (args.baseline || args.prepareRepair) {
3177
3259
  try {
3178
3260
  if (args.baseline) {