mason-context 0.17.2 → 0.17.4

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.
@@ -9,6 +9,54 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/utils/profile.ts
13
+ import { AsyncLocalStorage } from "async_hooks";
14
+ async function profilePhase(label, run) {
15
+ const phases = profiles.getStore();
16
+ if (!phases) return run();
17
+ const start = performance.now();
18
+ try {
19
+ return await run();
20
+ } finally {
21
+ const timing = phases[label] ??= { calls: 0, totalMs: 0 };
22
+ timing.calls++;
23
+ timing.totalMs += performance.now() - start;
24
+ }
25
+ }
26
+ var profiles;
27
+ var init_profile = __esm({
28
+ "src/utils/profile.ts"() {
29
+ "use strict";
30
+ profiles = new AsyncLocalStorage();
31
+ }
32
+ });
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
+
12
60
  // src/utils/paths.ts
13
61
  import path from "path";
14
62
  function normalizeRepoPath(value) {
@@ -40,8 +88,6 @@ var init_paths = __esm({
40
88
  import fs from "fs/promises";
41
89
  import { constants } from "fs";
42
90
  import path2 from "path";
43
- import { execFile } from "child_process";
44
- import { promisify } from "util";
45
91
  import fg from "fast-glob";
46
92
  async function readBoundedFile(file, maxBytes) {
47
93
  const handle = await fs.open(file, constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW);
@@ -83,12 +129,12 @@ async function loadProjectConfig(root) {
83
129
  throw new Error(`Cannot apply project file policy: ${error instanceof Error ? error.message : String(error)}`);
84
130
  }
85
131
  }
86
- var exec, SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
132
+ var SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
87
133
  var init_files = __esm({
88
134
  "src/utils/files.ts"() {
89
135
  "use strict";
136
+ init_git_read();
90
137
  init_paths();
91
- exec = promisify(execFile);
92
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"];
93
139
  SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(",")}}`;
94
140
  SOURCE_IGNORE = [
@@ -199,12 +245,10 @@ var init_test_map = __esm({
199
245
 
200
246
  // src/snapshot/snapshot.ts
201
247
  import path5 from "path";
202
- import { execFile as execFile2 } from "child_process";
203
- import { promisify as promisify2 } from "util";
204
248
  import { z } from "zod";
205
249
  async function getCurrentGitHash(rootDir) {
206
250
  try {
207
- const { stdout } = await exec2("git", ["rev-parse", "HEAD"], {
251
+ const { stdout } = await execGit(["rev-parse", "HEAD"], {
208
252
  cwd: rootDir
209
253
  });
210
254
  return stdout.trim();
@@ -212,16 +256,16 @@ async function getCurrentGitHash(rootDir) {
212
256
  return "unknown";
213
257
  }
214
258
  }
215
- var exec2, repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
259
+ var repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
216
260
  var init_snapshot = __esm({
217
261
  "src/snapshot/snapshot.ts"() {
218
262
  "use strict";
263
+ init_git_read();
219
264
  init_files();
220
265
  init_files();
221
266
  init_storage();
222
267
  init_paths();
223
268
  init_test_map();
224
- exec2 = promisify2(execFile2);
225
269
  repoPath = z.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
226
270
  verificationFields = {
227
271
  refreshedHash: z.string().optional(),
@@ -252,8 +296,6 @@ var init_snapshot = __esm({
252
296
  // src/drift/drift.ts
253
297
  import fs3 from "fs/promises";
254
298
  import path6 from "path";
255
- import { execFile as execFile3 } from "child_process";
256
- import { promisify as promisify3 } from "util";
257
299
  function parseChanges(output) {
258
300
  const fields = output.split("\0");
259
301
  const changes = [];
@@ -274,7 +316,7 @@ function touchedPaths(changes) {
274
316
  async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
275
317
  if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-") || !toHash || toHash === "unknown" || toHash.startsWith("-")) return null;
276
318
  try {
277
- const { stdout } = await exec3("git", ["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
319
+ const { stdout } = await execGit(["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
278
320
  return parseChanges(stdout);
279
321
  } catch {
280
322
  return null;
@@ -283,8 +325,8 @@ async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
283
325
  async function getWorkingTree(resolvedRoot) {
284
326
  try {
285
327
  const [diff, untracked] = await Promise.all([
286
- exec3("git", ["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
287
- exec3("git", ["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
328
+ execGit(["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
329
+ execGit(["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
288
330
  ]);
289
331
  const untrackedFiles = untracked.stdout.split("\0").filter((f) => f && !f.startsWith(".mason/"));
290
332
  return { available: true, changedFiles: [.../* @__PURE__ */ new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles])].sort(), untrackedFiles };
@@ -292,13 +334,12 @@ async function getWorkingTree(resolvedRoot) {
292
334
  return { available: false, changedFiles: [], untrackedFiles: [] };
293
335
  }
294
336
  }
295
- var exec3;
296
337
  var init_drift = __esm({
297
338
  "src/drift/drift.ts"() {
298
339
  "use strict";
340
+ init_git_read();
299
341
  init_snapshot();
300
342
  init_paths();
301
- exec3 = promisify3(execFile3);
302
343
  }
303
344
  });
304
345
 
@@ -526,14 +567,14 @@ function extractClaims(content) {
526
567
  const ignored = computeIgnoredLines(lines);
527
568
  const paths = /* @__PURE__ */ new Map();
528
569
  const counts = [];
529
- const commands = /* @__PURE__ */ new Map();
570
+ const commands2 = /* @__PURE__ */ new Map();
530
571
  const addPath = (claim) => {
531
572
  const key = [claim.relativeTo ?? "scope", claim.path].join(":");
532
573
  if (!paths.has(key)) paths.set(key, claim);
533
574
  };
534
575
  const addCommand = (claim) => {
535
576
  const key = JSON.stringify([claim.scriptName, claim.directory, claim.scopeUnknown]);
536
- if (!commands.has(key)) commands.set(key, claim);
577
+ if (!commands2.has(key)) commands2.set(key, claim);
537
578
  };
538
579
  const parsed = markdown.parse(lines.map((line, i) => ignored[i] ? "" : line).join("\n"), {});
539
580
  for (const token of parsed) {
@@ -635,7 +676,7 @@ function extractClaims(content) {
635
676
  return {
636
677
  paths: [...paths.values()],
637
678
  counts,
638
- commands: [...commands.values()]
679
+ commands: [...commands2.values()]
639
680
  };
640
681
  }
641
682
  var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
@@ -685,9 +726,50 @@ var init_claims = __esm({
685
726
  }
686
727
  });
687
728
 
729
+ // src/audit/inspection.ts
730
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
731
+ import path8 from "path";
732
+ function inspectionRead(root, key, read) {
733
+ const inspection = inspections.getStore();
734
+ if (!inspection || inspection.root !== path8.resolve(root)) return read();
735
+ if (!inspection.reads.has(key)) {
736
+ const pending = read();
737
+ inspection.reads.set(key, pending);
738
+ void pending.catch(() => {
739
+ if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
740
+ });
741
+ }
742
+ return inspection.reads.get(key);
743
+ }
744
+ function inspectionGit(args, options) {
745
+ return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
746
+ }
747
+ async function auditGitPaths(root, kind) {
748
+ const shared = inspections.getStore()?.root === path8.resolve(root);
749
+ const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
750
+ const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
751
+ const { stdout } = await inspectionGit(
752
+ ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
753
+ { cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
754
+ );
755
+ 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("."))));
756
+ if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
757
+ return paths.sort();
758
+ }
759
+ var inspections, sourceSpecs, docSpecs, sourceSuffixes;
760
+ var init_inspection = __esm({
761
+ "src/audit/inspection.ts"() {
762
+ "use strict";
763
+ init_git_read();
764
+ init_files();
765
+ inspections = new AsyncLocalStorage2();
766
+ sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
767
+ docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
768
+ sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
769
+ }
770
+ });
771
+
688
772
  // src/audit/git.ts
689
- import { execFile as execFile4 } from "child_process";
690
- import { promisify as promisify4 } from "util";
691
773
  function parseCommitLine(line) {
692
774
  const parts = line.split(" ");
693
775
  if (parts.length < 3 || !parts[0]) return null;
@@ -695,8 +777,7 @@ function parseCommitLine(line) {
695
777
  }
696
778
  async function lastCommitOf(resolvedRoot, relPath) {
697
779
  try {
698
- const { stdout } = await exec4(
699
- "git",
780
+ const { stdout } = await inspectionGit(
700
781
  ["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
701
782
  { cwd: resolvedRoot }
702
783
  );
@@ -708,8 +789,7 @@ async function lastCommitOf(resolvedRoot, relPath) {
708
789
  }
709
790
  async function deletingCommitOf(resolvedRoot, relPath) {
710
791
  try {
711
- const { stdout } = await exec4(
712
- "git",
792
+ const { stdout } = await inspectionGit(
713
793
  [
714
794
  "log",
715
795
  "-1",
@@ -728,8 +808,7 @@ async function deletingCommitOf(resolvedRoot, relPath) {
728
808
  }
729
809
  async function firstCommitOf(resolvedRoot, relPath) {
730
810
  try {
731
- const { stdout } = await exec4(
732
- "git",
811
+ const { stdout } = await inspectionGit(
733
812
  ["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
734
813
  { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
735
814
  );
@@ -742,8 +821,7 @@ async function firstCommitOf(resolvedRoot, relPath) {
742
821
  async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
743
822
  if (!fromHash || fromHash === "unknown") return null;
744
823
  try {
745
- const { stdout } = await exec4(
746
- "git",
824
+ const { stdout } = await inspectionGit(
747
825
  [
748
826
  "log",
749
827
  `${fromHash}..HEAD`,
@@ -767,11 +845,11 @@ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
767
845
  return null;
768
846
  }
769
847
  }
770
- var exec4, COMMIT_FORMAT;
848
+ var COMMIT_FORMAT;
771
849
  var init_git = __esm({
772
850
  "src/audit/git.ts"() {
773
851
  "use strict";
774
- exec4 = promisify4(execFile4);
852
+ init_inspection();
775
853
  COMMIT_FORMAT = "%H%x09%cI%x09%s";
776
854
  }
777
855
  });
@@ -779,9 +857,7 @@ var init_git = __esm({
779
857
  // src/audit/inputs.ts
780
858
  import fs4 from "fs/promises";
781
859
  import * as nativeFs from "fs";
782
- import path8 from "path";
783
- import { execFile as execFile5 } from "child_process";
784
- import { promisify as promisify5 } from "util";
860
+ import path9 from "path";
785
861
  import fg2 from "fast-glob";
786
862
  async function auditInputPath(root, file) {
787
863
  if (file === ".") return fs4.realpath(root);
@@ -808,7 +884,7 @@ async function auditGlob(root, patterns, options) {
808
884
  if (Array.isArray(patterns) && !patterns.length) return [];
809
885
  const directories = /* @__PURE__ */ new Set();
810
886
  const readdir2 = ((directory, opts, callback) => {
811
- const relative = path8.relative(root, directory).split(path8.sep).join("/") || ".";
887
+ const relative = path9.relative(root, directory).split(path9.sep).join("/") || ".";
812
888
  directories.add(relative);
813
889
  if (directories.size > MAX_INPUTS) {
814
890
  callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
@@ -819,7 +895,7 @@ async function auditGlob(root, patterns, options) {
819
895
  }, callback);
820
896
  });
821
897
  const lstat2 = ((file, callback) => {
822
- const relative = path8.relative(root, file).split(path8.sep).join("/") || ".";
898
+ const relative = path9.relative(root, file).split(path9.sep).join("/") || ".";
823
899
  auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
824
900
  });
825
901
  const stream = fg2.stream(patterns, {
@@ -856,89 +932,71 @@ async function auditGlob(root, patterns, options) {
856
932
  return matches.sort();
857
933
  }
858
934
  async function gitSourcePaths(root) {
859
- const { stdout } = await execute(
860
- "git",
861
- [
862
- "ls-files",
863
- "-z",
864
- "--cached",
865
- "--others",
866
- "--exclude-standard",
867
- "--",
868
- ...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
869
- ],
870
- { cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
871
- );
872
- return [...new Set(stdout.split("\0").filter(Boolean))].sort();
935
+ return auditGitPaths(root, "sources");
873
936
  }
874
937
  async function gitIgnoredPaths(root, files) {
875
938
  if (!files.length) return /* @__PURE__ */ new Set();
876
- const operation = execute(
877
- "git",
878
- ["check-ignore", "-z", "--stdin"],
879
- { cwd: root, maxBuffer: 4 * 1024 * 1024, timeout: 1e4 }
880
- );
881
- operation.child.stdin.on("error", () => {
882
- });
883
- operation.child.stdin.end(files.join("\0") + "\0");
884
- try {
885
- return new Set((await operation).stdout.split("\0").filter(Boolean));
886
- } catch (error) {
887
- if (error.code === 1) return /* @__PURE__ */ new Set();
888
- throw error;
939
+ const ignored = /* @__PURE__ */ new Set();
940
+ for (let offset = 0; offset < files.length; offset += 1e3) {
941
+ try {
942
+ const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
943
+ cwd: root,
944
+ maxBuffer: 4 * 1024 * 1024,
945
+ timeout: 1e4,
946
+ input: files.slice(offset, offset + 1e3).join("\0") + "\0"
947
+ });
948
+ for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
949
+ } catch (error) {
950
+ if (error.code !== 1) throw error;
951
+ }
889
952
  }
953
+ return ignored;
890
954
  }
891
- var execute, MAX_INPUTS;
955
+ var MAX_INPUTS;
892
956
  var init_inputs = __esm({
893
957
  "src/audit/inputs.ts"() {
894
958
  "use strict";
895
959
  init_files();
896
960
  init_storage();
897
- execute = promisify5(execFile5);
961
+ init_git_read();
962
+ init_inspection();
898
963
  MAX_INPUTS = 1e5;
899
964
  }
900
965
  });
901
966
 
902
967
  // src/audit/docs.ts
903
968
  import fs5 from "fs/promises";
904
- import path9 from "path";
905
- import { execFile as execFile6 } from "child_process";
906
- import { promisify as promisify6 } from "util";
969
+ import path10 from "path";
907
970
  import fg3 from "fast-glob";
908
- async function isDirty(resolvedRoot, relPath) {
971
+ async function dirtyDocs(resolvedRoot, files) {
972
+ if (!files.length) return /* @__PURE__ */ new Set();
909
973
  try {
910
- const { stdout } = await exec5(
911
- "git",
912
- ["status", "--porcelain", "--", `:(literal)${relPath}`],
913
- { cwd: resolvedRoot }
914
- );
915
- return stdout.trim().length > 0;
974
+ const dirty = /* @__PURE__ */ new Set();
975
+ for (let offset = 0; offset < files.length; offset += 256) {
976
+ const { stdout } = await execGit(
977
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
978
+ { cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
979
+ );
980
+ const entries = stdout.split("\0");
981
+ for (let i = 0; i < entries.length; i++) {
982
+ if (!entries[i]) continue;
983
+ const status = entries[i].slice(0, 2);
984
+ dirty.add(entries[i].slice(3));
985
+ if (/[RC]/.test(status) && entries[i + 1]) dirty.add(entries[++i]);
986
+ }
987
+ }
988
+ return dirty;
916
989
  } catch {
917
- return true;
990
+ return new Set(files);
918
991
  }
919
992
  }
920
993
  async function discoverDocPaths(root) {
921
994
  let candidates = [];
922
995
  try {
923
- const { stdout } = await exec5(
924
- "git",
925
- [
926
- "ls-files",
927
- "-z",
928
- "--cached",
929
- "--others",
930
- "--exclude-standard",
931
- "--",
932
- ":(icase,glob)**/readme.md",
933
- ":(icase,glob)**/agents.md",
934
- ":(icase,glob)**/claude.md"
935
- ],
936
- { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
937
- );
938
- candidates = stdout.split("\0").filter(Boolean);
996
+ candidates = await auditGitPaths(root, "documents");
939
997
  } catch (error) {
940
998
  try {
941
- await exec5("git", ["rev-parse", "--git-dir"], { cwd: root });
999
+ await execGit(["rev-parse", "--git-dir"], { cwd: root });
942
1000
  } catch {
943
1001
  return localDocPaths(root);
944
1002
  }
@@ -985,43 +1043,54 @@ async function localDocPaths(root) {
985
1043
  throw error;
986
1044
  }
987
1045
  for (const entry of entries) if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
988
- paths.push(path9.posix.join(dir, entry.name));
1046
+ paths.push(path10.posix.join(dir, entry.name));
989
1047
  }
990
1048
  }
991
1049
  return paths;
992
1050
  }
993
1051
  function documentScope(file) {
994
- const parent = path9.posix.dirname(file);
995
- return path9.posix.basename(parent).toLowerCase() === ".claude" ? path9.posix.dirname(parent) : parent;
1052
+ const parent = path10.posix.dirname(file);
1053
+ return path10.posix.basename(parent).toLowerCase() === ".claude" ? path10.posix.dirname(parent) : parent;
996
1054
  }
997
1055
  async function discoverDocs(resolvedRoot) {
1056
+ return profilePhase("audit.documents", () => readDocs(resolvedRoot));
1057
+ }
1058
+ async function readDocs(resolvedRoot) {
998
1059
  const docs = [];
999
- for (const candidate of await discoverDocPaths(resolvedRoot)) {
1000
- const content = await readAuditInput(resolvedRoot, candidate);
1001
- if (content === null) throw new Error("Document disappeared during discovery: " + candidate);
1002
- const [lastCommit, dirty] = await Promise.all([lastCommitOf(resolvedRoot, candidate), isDirty(resolvedRoot, candidate)]);
1003
- docs.push({
1004
- path: candidate,
1005
- content,
1006
- scope: documentScope(candidate),
1007
- kind: /^readme\.md$/i.test(path9.posix.basename(candidate)) ? "readme" : "instructions",
1008
- lineCount: content.split("\n").length,
1009
- lastCommit,
1010
- dirty,
1011
- claims: extractClaims(content)
1012
- });
1060
+ const files = await discoverDocPaths(resolvedRoot);
1061
+ const dirty = await dirtyDocs(resolvedRoot, files);
1062
+ for (let offset = 0; offset < files.length; offset += 4) {
1063
+ docs.push(...await Promise.all(files.slice(offset, offset + 4).map(async (candidate) => {
1064
+ const [content, lastCommit] = await Promise.all([
1065
+ readAuditInput(resolvedRoot, candidate),
1066
+ profilePhase("audit.document-history", () => lastCommitOf(resolvedRoot, candidate))
1067
+ ]);
1068
+ if (content === null) throw new Error("Document disappeared during discovery: " + candidate);
1069
+ return {
1070
+ path: candidate,
1071
+ content,
1072
+ scope: documentScope(candidate),
1073
+ kind: /^readme\.md$/i.test(path10.posix.basename(candidate)) ? "readme" : "instructions",
1074
+ lineCount: content.split("\n").length,
1075
+ lastCommit,
1076
+ dirty: dirty.has(candidate),
1077
+ claims: extractClaims(content)
1078
+ };
1079
+ })));
1013
1080
  }
1014
1081
  return docs;
1015
1082
  }
1016
- var exec5, DOC_CANDIDATES;
1083
+ var DOC_CANDIDATES;
1017
1084
  var init_docs = __esm({
1018
1085
  "src/audit/docs.ts"() {
1019
1086
  "use strict";
1087
+ init_git_read();
1088
+ init_profile();
1020
1089
  init_claims();
1021
1090
  init_git();
1022
1091
  init_inputs();
1023
1092
  init_files();
1024
- exec5 = promisify6(execFile6);
1093
+ init_inspection();
1025
1094
  DOC_CANDIDATES = [
1026
1095
  "AGENTS.md",
1027
1096
  "CLAUDE.md",
@@ -1048,14 +1117,14 @@ var init_types = __esm({
1048
1117
 
1049
1118
  // src/audit/scope.ts
1050
1119
  import fs6 from "fs/promises";
1051
- import path10 from "path";
1120
+ import path11 from "path";
1052
1121
  function scopedPath(directory, value) {
1053
- if (path10.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
1054
- const resolved = path10.posix.normalize(path10.posix.join(directory, value));
1122
+ if (path11.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
1123
+ const resolved = path11.posix.normalize(path11.posix.join(directory, value));
1055
1124
  return resolved === ".." || resolved.startsWith("../") ? null : resolved;
1056
1125
  }
1057
1126
  function pathClaimScope(doc, claim) {
1058
- const directory = claim.relativeTo === "document" ? path10.posix.dirname(doc) : documentScope(doc);
1127
+ const directory = claim.relativeTo === "document" ? path11.posix.dirname(doc) : documentScope(doc);
1059
1128
  const local = scopedPath(directory, claim.path);
1060
1129
  if (!local) return null;
1061
1130
  const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
@@ -1089,7 +1158,7 @@ var init_scope = __esm({
1089
1158
  });
1090
1159
 
1091
1160
  // src/audit/checks/deleted-reference.ts
1092
- import path11 from "path";
1161
+ import path12 from "path";
1093
1162
  async function checkDeletedReferences(ctx) {
1094
1163
  const result = emptyResult();
1095
1164
  for (const doc of ctx.docs) {
@@ -1118,7 +1187,7 @@ async function checkDeletedReferences(ctx) {
1118
1187
  const candidates = await Promise.all(scope.candidates.map(async (file) => ({
1119
1188
  file,
1120
1189
  tracked: await lastCommitOf(ctx.root, file),
1121
- parent: await pathExists(ctx.root, path11.posix.dirname(file))
1190
+ parent: await pathExists(ctx.root, path12.posix.dirname(file))
1122
1191
  })));
1123
1192
  const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
1124
1193
  if (!candidate && claim.relativeTo !== "document") continue;
@@ -1158,7 +1227,7 @@ var init_deleted_reference = __esm({
1158
1227
  // src/audit/checks/new-module.ts
1159
1228
  import fg4 from "fast-glob";
1160
1229
  import fs7 from "fs/promises";
1161
- import path12 from "path";
1230
+ import path13 from "path";
1162
1231
  function escapeRegExp(text2) {
1163
1232
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1164
1233
  }
@@ -1196,23 +1265,36 @@ async function checkNewModules(ctx) {
1196
1265
  return result;
1197
1266
  }
1198
1267
  function moduleDocumentation(docs) {
1199
- return docs.map((doc) => doc.content + "\n" + (path12.posix.dirname(doc.path) === "." ? "" : path12.posix.dirname(doc.path) + "/")).join("\n");
1268
+ return docs.map((doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")).join("\n");
1269
+ }
1270
+ function moduleCandidates(root, combinedDocs) {
1271
+ return inspectionRead(root, "modules:" + combinedDocs, () => collectModuleCandidates(root, combinedDocs));
1200
1272
  }
1201
- async function moduleCandidates(root, combinedDocs) {
1273
+ async function collectModuleCandidates(root, combinedDocs) {
1202
1274
  const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
1203
1275
  const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
1204
1276
  const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
1205
- const listSubdirs = async (dir) => {
1206
- const entries = await fs7.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1207
- 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));
1208
- const ignored = await gitIgnoredPaths(root, dirs);
1209
- const visible = new Set(dirs.filter((dir2) => !ignored.has(dir2) || sources.some((file) => file.startsWith(dir2 + "/"))));
1210
- return (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1211
- ignore,
1212
- onlyDirectories: true,
1213
- label: "Module directory discovery",
1214
- select: (file) => visible.has(file)
1215
- })).map((file) => path12.posix.basename(file));
1277
+ const listSubdirs = async (parents) => {
1278
+ const byParent = /* @__PURE__ */ new Map();
1279
+ for (let offset = 0; offset < parents.length; offset += 16) {
1280
+ await Promise.all(parents.slice(offset, offset + 16).map(async (dir) => {
1281
+ const entries = await fs7.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1282
+ 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));
1283
+ byParent.set(dir, dirs);
1284
+ }));
1285
+ }
1286
+ const ignored = await gitIgnoredPaths(root, [...byParent.values()].flat());
1287
+ const result = /* @__PURE__ */ new Map();
1288
+ for (const dir of parents) {
1289
+ const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
1290
+ result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1291
+ ignore,
1292
+ onlyDirectories: true,
1293
+ label: "Module directory discovery",
1294
+ select: (file) => visible.has(file)
1295
+ })).map((file) => path13.posix.basename(file)));
1296
+ }
1297
+ return result;
1216
1298
  };
1217
1299
  const countSourceFiles = async (dir) => {
1218
1300
  const files = sources.filter((file) => file.startsWith(dir + "/"));
@@ -1228,14 +1310,16 @@ async function moduleCandidates(root, combinedDocs) {
1228
1310
  return count3;
1229
1311
  };
1230
1312
  const candidates = [];
1231
- for (const topDir of await listSubdirs(".")) {
1313
+ const topDirs = (await listSubdirs(["."])).get(".");
1314
+ const nested = await listSubdirs(topDirs.filter((dir) => isMentioned(combinedDocs, dir)));
1315
+ for (const topDir of topDirs) {
1232
1316
  const topMentioned = isMentioned(combinedDocs, topDir);
1233
1317
  if (!topMentioned) {
1234
1318
  const count3 = await countSourceFiles(topDir);
1235
1319
  if (count3 >= 1) candidates.push({ dir: topDir, sourceFileCount: count3 });
1236
1320
  continue;
1237
1321
  }
1238
- const subdirs = await listSubdirs(topDir);
1322
+ const subdirs = nested.get(topDir);
1239
1323
  const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
1240
1324
  if (mentioned.length < ENUMERATION_THRESHOLD) continue;
1241
1325
  for (const sub of subdirs) {
@@ -1252,6 +1336,7 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
1252
1336
  var init_new_module = __esm({
1253
1337
  "src/audit/checks/new-module.ts"() {
1254
1338
  "use strict";
1339
+ init_inspection();
1255
1340
  init_files();
1256
1341
  init_inputs();
1257
1342
  init_git();
@@ -1281,7 +1366,7 @@ var init_new_module = __esm({
1281
1366
  });
1282
1367
 
1283
1368
  // src/audit/checks/stale-count.ts
1284
- import path13 from "path";
1369
+ import path14 from "path";
1285
1370
  async function countGradleModules(root) {
1286
1371
  for (const name of ["settings.gradle.kts", "settings.gradle"]) {
1287
1372
  const content = await readAuditInput(root, name);
@@ -1315,7 +1400,7 @@ async function countNpmWorkspaces(root) {
1315
1400
  return {
1316
1401
  actual: matched.length,
1317
1402
  countedFrom: "package.json workspaces",
1318
- members: matched.map((m) => path13.dirname(m)).sort()
1403
+ members: matched.map((m) => path14.dirname(m)).sort()
1319
1404
  };
1320
1405
  }
1321
1406
  }
@@ -1346,7 +1431,7 @@ async function countNpmWorkspaces(root) {
1346
1431
  return {
1347
1432
  actual: matched.length,
1348
1433
  countedFrom: "pnpm-workspace.yaml",
1349
- members: matched.map((m) => path13.dirname(m)).sort()
1434
+ members: matched.map((m) => path14.dirname(m)).sort()
1350
1435
  };
1351
1436
  }
1352
1437
  }
@@ -1368,8 +1453,8 @@ async function countCargoCrates(root) {
1368
1453
  ignore: ["**/target/**"],
1369
1454
  label: "Cargo workspace discovery"
1370
1455
  });
1371
- for (const m of matched) members.add(path13.dirname(m));
1372
- } else if (await readAuditInput(root, path13.posix.join(entry, "Cargo.toml")) !== null) {
1456
+ for (const m of matched) members.add(path14.dirname(m));
1457
+ } else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
1373
1458
  members.add(entry);
1374
1459
  }
1375
1460
  }
@@ -1389,7 +1474,7 @@ async function resolveCountSource(root, claim) {
1389
1474
  }
1390
1475
  async function resolveDocCountSource(root, doc, claim) {
1391
1476
  const scope = documentScope(doc);
1392
- const source = await resolveCountSource(path13.join(root, scope), claim);
1477
+ const source = await resolveCountSource(path14.join(root, scope), claim);
1393
1478
  if (!source || scope === ".") return source;
1394
1479
  return {
1395
1480
  ...source,
@@ -1441,7 +1526,7 @@ var init_stale_count = __esm({
1441
1526
  });
1442
1527
 
1443
1528
  // src/audit/checks/dead-command.ts
1444
- import path14 from "path";
1529
+ import path15 from "path";
1445
1530
  function scriptsOf(raw) {
1446
1531
  try {
1447
1532
  const pkg = JSON.parse(raw);
@@ -1477,7 +1562,7 @@ async function commandInputs(root, docs) {
1477
1562
  }
1478
1563
  let cursor = directory;
1479
1564
  while (true) {
1480
- const manifest = path14.posix.join(cursor, "package.json");
1565
+ const manifest = path15.posix.join(cursor, "package.json");
1481
1566
  scope.candidates.push(manifest);
1482
1567
  const raw = await read(manifest);
1483
1568
  if (raw !== null) {
@@ -1488,7 +1573,7 @@ async function commandInputs(root, docs) {
1488
1573
  break;
1489
1574
  }
1490
1575
  if (claim.directory !== void 0 || cursor === ".") break;
1491
- cursor = path14.posix.dirname(cursor);
1576
+ cursor = path15.posix.dirname(cursor);
1492
1577
  }
1493
1578
  if (!resolution.manifest && !resolution.problem) {
1494
1579
  resolution.problem = `No package.json for the command's scope (${directory}).`;
@@ -1551,10 +1636,8 @@ var init_dead_command = __esm({
1551
1636
  });
1552
1637
 
1553
1638
  // src/audit/release-metadata.ts
1554
- import { execFile as execFile7 } from "child_process";
1555
- import { promisify as promisify7 } from "util";
1556
1639
  async function git(root, args) {
1557
- return (await exec6("git", args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1640
+ return (await execGit(args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1558
1641
  }
1559
1642
  function withoutAndroidReleaseValues(text2) {
1560
1643
  if (/\/\*|"""|'''/.test(text2) || !/id\s*\(?\s*["']com\.android\.(application|library)["']/.test(text2)) return null;
@@ -1600,11 +1683,10 @@ async function releaseMetadataOnly(root, commit) {
1600
1683
  return false;
1601
1684
  }
1602
1685
  }
1603
- var exec6;
1604
1686
  var init_release_metadata = __esm({
1605
1687
  "src/audit/release-metadata.ts"() {
1606
1688
  "use strict";
1607
- exec6 = promisify7(execFile7);
1689
+ init_git_read();
1608
1690
  }
1609
1691
  });
1610
1692
 
@@ -1700,7 +1782,7 @@ var init_deps_changed = __esm({
1700
1782
  });
1701
1783
 
1702
1784
  // src/context/lexical.ts
1703
- import path15 from "path";
1785
+ import path16 from "path";
1704
1786
  var init_lexical = __esm({
1705
1787
  "src/context/lexical.ts"() {
1706
1788
  "use strict";
@@ -1861,7 +1943,7 @@ var init_provenance = __esm({
1861
1943
 
1862
1944
  // src/decisions/decisions.ts
1863
1945
  import fs8 from "fs/promises";
1864
- import path16 from "path";
1946
+ import path17 from "path";
1865
1947
  import { createHash } from "crypto";
1866
1948
  import { z as z3 } from "zod";
1867
1949
  async function loadDecisionStore(rootDir) {
@@ -1912,9 +1994,9 @@ var init_decisions = __esm({
1912
1994
  });
1913
1995
 
1914
1996
  // src/decisions/drift.ts
1915
- import path17 from "path";
1997
+ import path18 from "path";
1916
1998
  async function computeDecisionDrift(rootDir, decisions) {
1917
- const resolvedRoot = path17.resolve(rootDir);
1999
+ const resolvedRoot = path18.resolve(rootDir);
1918
2000
  const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
1919
2001
  const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
1920
2002
  const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
@@ -2236,10 +2318,12 @@ __export(repair_exports, {
2236
2318
  prepareRepair: () => prepareRepair,
2237
2319
  repairExitCode: () => repairExitCode,
2238
2320
  repairFindingSchema: () => repairFindingSchema,
2239
- verifyRepair: () => verifyRepair
2321
+ reportSchema: () => reportSchema,
2322
+ verifyRepair: () => verifyRepair,
2323
+ withRepairSession: () => withRepairSession
2240
2324
  });
2241
2325
  import fs10 from "fs/promises";
2242
- import path18 from "path";
2326
+ import path19 from "path";
2243
2327
  import { randomUUID as randomUUID2 } from "crypto";
2244
2328
  import { z as z6 } from "zod";
2245
2329
  function recheckable(finding) {
@@ -2263,6 +2347,9 @@ async function docState(root) {
2263
2347
  return digest(docs);
2264
2348
  }
2265
2349
  async function stableAudit(root, checks, options = {}) {
2350
+ return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
2351
+ }
2352
+ async function readStableAudit(root, checks, options) {
2266
2353
  const head = await getCurrentGitHash(root);
2267
2354
  const before = await docState(root);
2268
2355
  const report = await computeAudit(root, { ...options, checks });
@@ -2272,9 +2359,45 @@ async function stableAudit(root, checks, options = {}) {
2272
2359
  return report;
2273
2360
  }
2274
2361
  async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
2362
+ return prepareOriginal(rootDir, checks, options);
2363
+ }
2364
+ async function withRepairSession(root, options, run, validate, cachedAudit) {
2365
+ const head = await getCurrentGitHash(root);
2366
+ const audits = /* @__PURE__ */ new Map();
2367
+ const documents = /* @__PURE__ */ new Map();
2368
+ const history = /* @__PURE__ */ new Map();
2369
+ const assessments = /* @__PURE__ */ new Map();
2370
+ const memo = (cache, key, read) => {
2371
+ if (!cache.has(key)) cache.set(key, read());
2372
+ return cache.get(key);
2373
+ };
2374
+ const session = {
2375
+ audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
2376
+ if (cachedAudit && cachedAudit.headHash === head && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
2377
+ const current = structuredClone(cachedAudit);
2378
+ const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, head)))).filter((r) => r !== null);
2379
+ delete current.advisoryReviews;
2380
+ if (reviews.length) current.advisoryReviews = reviews;
2381
+ return current;
2382
+ }
2383
+ return computeAudit(root, { ...options, checks });
2384
+ }),
2385
+ document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
2386
+ history: (head2) => memo(history, head2, () => getChangesWithStatus(root, head2)),
2387
+ assessment: (dir, finding, head2) => memo(assessments, digest([dir, finding, head2]), () => assessAdvisory(dir, finding, head2))
2388
+ };
2389
+ const result = await run({
2390
+ prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
2391
+ verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
2392
+ currentAudit: () => session.audit(ALL_CHECKS)
2393
+ });
2394
+ await validate();
2395
+ return result;
2396
+ }
2397
+ async function prepareOriginal(rootDir, checks, options, session) {
2275
2398
  const root = await fs10.realpath(rootDir);
2276
2399
  const selected = z6.array(checkSchema).nonempty().parse(checks);
2277
- const report = await stableAudit(root, selected, options);
2400
+ const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
2278
2401
  if (!report) throw new Error("No context files found to prepare a repair.");
2279
2402
  if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
2280
2403
  const storedReport = reportSchema.parse(report);
@@ -2290,8 +2413,8 @@ async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
2290
2413
  }
2291
2414
  async function loadRepairBaseline(rootDir, baselinePath) {
2292
2415
  const root = await fs10.realpath(rootDir);
2293
- const declaredRoot = path18.resolve(rootDir);
2294
- const relative = path18.isAbsolute(baselinePath) ? path18.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
2416
+ const declaredRoot = path19.resolve(rootDir);
2417
+ const relative = path19.isAbsolute(baselinePath) ? path19.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
2295
2418
  const stored = baselineSchema.parse(await readStoreJson(root, relative));
2296
2419
  const { digest: savedDigest, ...payload } = stored;
2297
2420
  if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
@@ -2300,23 +2423,26 @@ async function loadRepairBaseline(rootDir, baselinePath) {
2300
2423
  return { root, relative, original };
2301
2424
  }
2302
2425
  async function verifyRepair(rootDir, baselinePath, options = {}) {
2426
+ return profilePhase("repair.verify", () => verifyOriginal(rootDir, baselinePath, options));
2427
+ }
2428
+ async function verifyOriginal(rootDir, baselinePath, options, session) {
2303
2429
  const { root, relative, original } = await loadRepairBaseline(rootDir, baselinePath);
2304
2430
  const diagnostics = [];
2305
2431
  let reviewDigest;
2306
2432
  let current = null;
2307
2433
  try {
2308
- reviewDigest = digest(await advisoryReviewInventory(root));
2309
- current = await stableAudit(root, original.checksRun, options);
2434
+ if (!session) reviewDigest = digest(await advisoryReviewInventory(root));
2435
+ current = await (session ? session.audit(original.checksRun) : stableAudit(root, original.checksRun, options));
2310
2436
  if (!current) diagnostics.push("No context files remain available to audit.");
2311
2437
  else if (!current.gitAvailable) diagnostics.push("Git history is unavailable.");
2312
2438
  for (const doc of original.docs) {
2313
2439
  if (!allFindings(original).some((f) => f.anchor.doc === doc.path) || !current?.docs.some((d) => d.path === doc.path)) continue;
2314
- const content = await readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024);
2440
+ const content = await (session ? session.document(doc.path) : readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024));
2315
2441
  if (content === null || !content.trim()) {
2316
2442
  diagnostics.push("Original context file " + doc.path + " is empty or unreadable; losing its claims does not verify a repair.");
2317
2443
  }
2318
2444
  }
2319
- if (await getChangesWithStatus(root, original.headHash) === null) {
2445
+ if (await (session ? session.history(original.headHash) : getChangesWithStatus(root, original.headHash)) === null) {
2320
2446
  diagnostics.push("The original audit commit is unavailable; repair history cannot be verified.");
2321
2447
  }
2322
2448
  } catch (error) {
@@ -2341,7 +2467,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
2341
2467
  if (!current.checksRun?.includes(finding.type) || skipped.length) {
2342
2468
  return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
2343
2469
  }
2344
- const review = await assessAdvisory(root, now ?? finding, original.headHash);
2470
+ const review = await (session?.assessment ?? assessAdvisory)(root, now ?? finding, original.headHash);
2345
2471
  if (review) {
2346
2472
  if (review.status === "unverified") return { ...base, review, status: "unverified", reason: review.reason };
2347
2473
  if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
@@ -2356,7 +2482,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
2356
2482
  }
2357
2483
  return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
2358
2484
  }));
2359
- try {
2485
+ if (!session) try {
2360
2486
  if (reviewDigest !== digest(await advisoryReviewInventory(root)) || current?.headHash !== await getCurrentGitHash(root)) {
2361
2487
  diagnostics.push("HEAD or advisory review records changed during verification; repeat against stable evidence.");
2362
2488
  for (const finding of findings) {
@@ -2409,6 +2535,7 @@ var count2, reportSchema, baselineSchema, repairFindingSchema;
2409
2535
  var init_repair = __esm({
2410
2536
  "src/audit/repair.ts"() {
2411
2537
  "use strict";
2538
+ init_profile();
2412
2539
  init_audit();
2413
2540
  init_docs();
2414
2541
  init_snapshot();
@@ -2462,8 +2589,6 @@ var init_repair = __esm({
2462
2589
 
2463
2590
  // src/audit/advisory-review.ts
2464
2591
  import fs11 from "fs/promises";
2465
- import { execFile as execFile8 } from "child_process";
2466
- import { promisify as promisify8 } from "util";
2467
2592
  import { z as z7 } from "zod";
2468
2593
  async function readRecord(root, id) {
2469
2594
  const raw = await readStoreJson(root, recordPath(id));
@@ -2658,10 +2783,12 @@ async function reviewAdvisory(rootDir, input) {
2658
2783
  };
2659
2784
  });
2660
2785
  }
2661
- var execute2, git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2786
+ var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2662
2787
  var init_advisory_review = __esm({
2663
2788
  "src/audit/advisory-review.ts"() {
2664
2789
  "use strict";
2790
+ init_inspection();
2791
+ init_git_read();
2665
2792
  init_findings();
2666
2793
  init_deps_changed();
2667
2794
  init_inputs();
@@ -2671,8 +2798,7 @@ var init_advisory_review = __esm({
2671
2798
  init_store();
2672
2799
  init_decisions();
2673
2800
  init_drift2();
2674
- execute2 = promisify8(execFile8);
2675
- git2 = async (root, ...args) => (await execute2("git", args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
2801
+ 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;
2676
2802
  hashSchema = z7.string().regex(/^[a-f0-9]{64}$/);
2677
2803
  commitSchema2 = z7.string().regex(/^[a-f0-9]{40,64}$/);
2678
2804
  evidenceSchema2 = z7.object({
@@ -2716,9 +2842,12 @@ var init_advisory_review = __esm({
2716
2842
 
2717
2843
  // src/audit/audit.ts
2718
2844
  import fs12 from "fs/promises";
2719
- import path19 from "path";
2845
+ import path20 from "path";
2720
2846
  async function computeAudit(rootDir, options = {}) {
2721
- const resolvedRoot = path19.resolve(rootDir);
2847
+ return profilePhase("audit.current", () => auditCurrent(rootDir, options));
2848
+ }
2849
+ async function auditCurrent(rootDir, options) {
2850
+ const resolvedRoot = path20.resolve(rootDir);
2722
2851
  const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
2723
2852
  if (docs.length === 0) return null;
2724
2853
  const report = {
@@ -2744,15 +2873,15 @@ async function computeAudit(rootDir, options = {}) {
2744
2873
  };
2745
2874
  if (!report.gitAvailable) return report;
2746
2875
  const changesSinceDoc = /* @__PURE__ */ new Map();
2876
+ const changesByCommit = /* @__PURE__ */ new Map();
2747
2877
  for (const doc of docs) {
2748
- changesSinceDoc.set(
2749
- doc.path,
2750
- doc.lastCommit ? await getChangesWithStatus(resolvedRoot, doc.lastCommit.hash) : null
2751
- );
2878
+ const hash = doc.lastCommit?.hash;
2879
+ if (hash && !changesByCommit.has(hash)) changesByCommit.set(hash, await getChangesWithStatus(resolvedRoot, hash));
2880
+ changesSinceDoc.set(doc.path, hash ? changesByCommit.get(hash) : null);
2752
2881
  }
2753
2882
  let decisionsPresent = false;
2754
2883
  try {
2755
- await fs12.access(path19.join(resolvedRoot, ".mason", "decisions"));
2884
+ await fs12.access(path20.join(resolvedRoot, ".mason", "decisions"));
2756
2885
  decisionsPresent = true;
2757
2886
  } catch {
2758
2887
  }
@@ -2782,6 +2911,7 @@ async function computeAudit(rootDir, options = {}) {
2782
2911
  var init_audit = __esm({
2783
2912
  "src/audit/audit.ts"() {
2784
2913
  "use strict";
2914
+ init_profile();
2785
2915
  init_drift();
2786
2916
  init_snapshot();
2787
2917
  init_docs();
@@ -2796,11 +2926,11 @@ init_audit();
2796
2926
  init_types();
2797
2927
  init_repair();
2798
2928
  init_findings();
2799
- import path21 from "path";
2929
+ import path22 from "path";
2800
2930
 
2801
2931
  // src/audit/advisory-cli.ts
2802
2932
  init_advisory_review();
2803
- import path20 from "path";
2933
+ import path21 from "path";
2804
2934
  import { stripVTControlCharacters } from "util";
2805
2935
  var USAGE = `Usage: mason audit review --baseline <path> --finding <id> [options]
2806
2936
 
@@ -2846,7 +2976,7 @@ async function runAdvisoryCli(args, io = { out: (text2) => console.log(text2), e
2846
2976
  note: flags["--note"],
2847
2977
  reviewToken: flags["--token"]
2848
2978
  };
2849
- const result = await reviewAdvisory(path20.resolve(flags["--dir"] ?? process.cwd()), input);
2979
+ const result = await reviewAdvisory(path21.resolve(flags["--dir"] ?? process.cwd()), input);
2850
2980
  if (json) io.out(JSON.stringify(result, null, 2));
2851
2981
  else {
2852
2982
  const lines = [`Advisory review: ${result.status}.`];
@@ -3079,7 +3209,7 @@ async function runAuditCli(argv, io = {
3079
3209
  io.out(USAGE2);
3080
3210
  return 0;
3081
3211
  }
3082
- const rootDir = path21.resolve(args.dir);
3212
+ const rootDir = path22.resolve(args.dir);
3083
3213
  if (args.baseline || args.prepareRepair) {
3084
3214
  try {
3085
3215
  if (args.baseline) {