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.
package/dist/mason.js CHANGED
@@ -36,12 +36,69 @@ var init_paths = __esm({
36
36
  }
37
37
  });
38
38
 
39
+ // src/utils/profile.ts
40
+ import { AsyncLocalStorage } from "async_hooks";
41
+ async function profilePhase(label, run) {
42
+ const phases = profiles.getStore();
43
+ if (!phases) return run();
44
+ const start = performance.now();
45
+ try {
46
+ return await run();
47
+ } finally {
48
+ const timing = phases[label] ??= { calls: 0, totalMs: 0 };
49
+ timing.calls++;
50
+ timing.totalMs += performance.now() - start;
51
+ }
52
+ }
53
+ async function withProfile(run, emit) {
54
+ const phases = {};
55
+ const start = performance.now();
56
+ return profiles.run(phases, async () => {
57
+ try {
58
+ return await run();
59
+ } finally {
60
+ emit(JSON.stringify({ kind: "mason-profile", version: 1, totalMs: performance.now() - start, phases }));
61
+ }
62
+ });
63
+ }
64
+ var profiles;
65
+ var init_profile = __esm({
66
+ "src/utils/profile.ts"() {
67
+ "use strict";
68
+ profiles = new AsyncLocalStorage();
69
+ }
70
+ });
71
+
72
+ // src/utils/git-read.ts
73
+ import { execFile } from "child_process";
74
+ import { promisify } from "util";
75
+ function execGit(args2, options) {
76
+ const command2 = args2.find((arg) => !arg.startsWith("-")) ?? "other";
77
+ return profilePhase("git." + (commands.has(command2) ? command2 : "other"), async () => {
78
+ const { input, ...processOptions } = options;
79
+ const operation = execute("git", args2, processOptions);
80
+ if (input !== void 0) {
81
+ operation.child.stdin.on("error", () => {
82
+ });
83
+ operation.child.stdin.end(input);
84
+ }
85
+ return operation;
86
+ });
87
+ }
88
+ var execute, commands;
89
+ var init_git_read = __esm({
90
+ "src/utils/git-read.ts"() {
91
+ "use strict";
92
+ init_profile();
93
+ execute = promisify(execFile);
94
+ commands = /* @__PURE__ */ new Set(["rev-parse", "symbolic-ref", "for-each-ref", "ls-files", "check-ignore", "status", "log", "diff", "ls-tree", "show", "merge-base"]);
95
+ }
96
+ });
97
+
39
98
  // src/utils/files.ts
40
99
  import fs from "fs/promises";
41
100
  import { constants } from "fs";
42
101
  import path2 from "path";
43
- import { execFile } from "child_process";
44
- import { promisify } from "util";
45
102
  import fg from "fast-glob";
46
103
  function isSensitiveFile(file) {
47
104
  return file.split(/[\\/]/).some(
@@ -95,12 +152,12 @@ async function createFileAccess(rootDir) {
95
152
  const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
96
153
  let gitFiles = null;
97
154
  try {
98
- const { stdout } = await exec("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
155
+ const { stdout } = await execGit(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
99
156
  gitFiles = new Set(stdout.split("\0").filter(Boolean));
100
157
  } catch {
101
158
  let inGit = false;
102
159
  try {
103
- await exec("git", ["rev-parse", "--git-dir"], { cwd: root });
160
+ await execGit(["rev-parse", "--git-dir"], { cwd: root });
104
161
  inGit = true;
105
162
  } catch {
106
163
  }
@@ -143,12 +200,12 @@ async function createFileAccess(rootDir) {
143
200
  }
144
201
  return { root, config, list, read };
145
202
  }
146
- var exec, SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
203
+ var SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
147
204
  var init_files = __esm({
148
205
  "src/utils/files.ts"() {
149
206
  "use strict";
207
+ init_git_read();
150
208
  init_paths();
151
- exec = promisify(execFile);
152
209
  SOURCE_EXTENSIONS = ["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs", "vue", "svelte", "kt", "kts", "java", "py", "go", "rs", "swift", "rb", "cs", "cpp", "c", "h", "hpp", "dart", "php"];
153
210
  SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(",")}}`;
154
211
  SOURCE_IGNORE = [
@@ -570,14 +627,14 @@ function extractClaims(content2) {
570
627
  const ignored = computeIgnoredLines(lines);
571
628
  const paths = /* @__PURE__ */ new Map();
572
629
  const counts = [];
573
- const commands = /* @__PURE__ */ new Map();
630
+ const commands2 = /* @__PURE__ */ new Map();
574
631
  const addPath = (claim) => {
575
632
  const key = [claim.relativeTo ?? "scope", claim.path].join(":");
576
633
  if (!paths.has(key)) paths.set(key, claim);
577
634
  };
578
635
  const addCommand = (claim) => {
579
636
  const key = JSON.stringify([claim.scriptName, claim.directory, claim.scopeUnknown]);
580
- if (!commands.has(key)) commands.set(key, claim);
637
+ if (!commands2.has(key)) commands2.set(key, claim);
581
638
  };
582
639
  const parsed = markdown.parse(lines.map((line, i) => ignored[i] ? "" : line).join("\n"), {});
583
640
  for (const token of parsed) {
@@ -679,7 +736,7 @@ function extractClaims(content2) {
679
736
  return {
680
737
  paths: [...paths.values()],
681
738
  counts,
682
- commands: [...commands.values()]
739
+ commands: [...commands2.values()]
683
740
  };
684
741
  }
685
742
  var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
@@ -729,9 +786,53 @@ var init_claims = __esm({
729
786
  }
730
787
  });
731
788
 
789
+ // src/audit/inspection.ts
790
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
791
+ import path5 from "path";
792
+ function withRepositoryInspection(root, run) {
793
+ return inspections.run({ root: path5.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
794
+ }
795
+ function inspectionRead(root, key, read) {
796
+ const inspection = inspections.getStore();
797
+ if (!inspection || inspection.root !== path5.resolve(root)) return read();
798
+ if (!inspection.reads.has(key)) {
799
+ const pending = read();
800
+ inspection.reads.set(key, pending);
801
+ void pending.catch(() => {
802
+ if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
803
+ });
804
+ }
805
+ return inspection.reads.get(key);
806
+ }
807
+ function inspectionGit(args2, options) {
808
+ return inspectionRead(options.cwd, "git:" + JSON.stringify([args2, options]), () => execGit(args2, options));
809
+ }
810
+ async function auditGitPaths(root, kind) {
811
+ const shared = inspections.getStore()?.root === path5.resolve(root);
812
+ const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
813
+ const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
814
+ const { stdout } = await inspectionGit(
815
+ ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
816
+ { cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
817
+ );
818
+ const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? /^(readme|agents|claude)\.md$/i.test(path5.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
819
+ if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
820
+ return paths.sort();
821
+ }
822
+ var inspections, sourceSpecs, docSpecs, sourceSuffixes;
823
+ var init_inspection = __esm({
824
+ "src/audit/inspection.ts"() {
825
+ "use strict";
826
+ init_git_read();
827
+ init_files();
828
+ inspections = new AsyncLocalStorage2();
829
+ sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
830
+ docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
831
+ sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
832
+ }
833
+ });
834
+
732
835
  // src/audit/git.ts
733
- import { execFile as execFile2 } from "child_process";
734
- import { promisify as promisify2 } from "util";
735
836
  function parseCommitLine(line) {
736
837
  const parts = line.split(" ");
737
838
  if (parts.length < 3 || !parts[0]) return null;
@@ -739,8 +840,7 @@ function parseCommitLine(line) {
739
840
  }
740
841
  async function lastCommitOf(resolvedRoot, relPath) {
741
842
  try {
742
- const { stdout } = await exec2(
743
- "git",
843
+ const { stdout } = await inspectionGit(
744
844
  ["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
745
845
  { cwd: resolvedRoot }
746
846
  );
@@ -752,8 +852,7 @@ async function lastCommitOf(resolvedRoot, relPath) {
752
852
  }
753
853
  async function deletingCommitOf(resolvedRoot, relPath) {
754
854
  try {
755
- const { stdout } = await exec2(
756
- "git",
855
+ const { stdout } = await inspectionGit(
757
856
  [
758
857
  "log",
759
858
  "-1",
@@ -772,8 +871,7 @@ async function deletingCommitOf(resolvedRoot, relPath) {
772
871
  }
773
872
  async function firstCommitOf(resolvedRoot, relPath) {
774
873
  try {
775
- const { stdout } = await exec2(
776
- "git",
874
+ const { stdout } = await inspectionGit(
777
875
  ["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
778
876
  { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
779
877
  );
@@ -786,8 +884,7 @@ async function firstCommitOf(resolvedRoot, relPath) {
786
884
  async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
787
885
  if (!fromHash || fromHash === "unknown") return null;
788
886
  try {
789
- const { stdout } = await exec2(
790
- "git",
887
+ const { stdout } = await inspectionGit(
791
888
  [
792
889
  "log",
793
890
  `${fromHash}..HEAD`,
@@ -811,11 +908,11 @@ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
811
908
  return null;
812
909
  }
813
910
  }
814
- var exec2, COMMIT_FORMAT;
911
+ var COMMIT_FORMAT;
815
912
  var init_git = __esm({
816
913
  "src/audit/git.ts"() {
817
914
  "use strict";
818
- exec2 = promisify2(execFile2);
915
+ init_inspection();
819
916
  COMMIT_FORMAT = "%H%x09%cI%x09%s";
820
917
  }
821
918
  });
@@ -823,9 +920,7 @@ var init_git = __esm({
823
920
  // src/audit/inputs.ts
824
921
  import fs4 from "fs/promises";
825
922
  import * as nativeFs from "fs";
826
- import path5 from "path";
827
- import { execFile as execFile3 } from "child_process";
828
- import { promisify as promisify3 } from "util";
923
+ import path6 from "path";
829
924
  import fg2 from "fast-glob";
830
925
  async function auditInputPath(root, file) {
831
926
  if (file === ".") return fs4.realpath(root);
@@ -852,7 +947,7 @@ async function auditGlob(root, patterns, options) {
852
947
  if (Array.isArray(patterns) && !patterns.length) return [];
853
948
  const directories = /* @__PURE__ */ new Set();
854
949
  const readdir2 = ((directory, opts, callback) => {
855
- const relative = path5.relative(root, directory).split(path5.sep).join("/") || ".";
950
+ const relative = path6.relative(root, directory).split(path6.sep).join("/") || ".";
856
951
  directories.add(relative);
857
952
  if (directories.size > MAX_INPUTS) {
858
953
  callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
@@ -863,7 +958,7 @@ async function auditGlob(root, patterns, options) {
863
958
  }, callback);
864
959
  });
865
960
  const lstat2 = ((file, callback) => {
866
- const relative = path5.relative(root, file).split(path5.sep).join("/") || ".";
961
+ const relative = path6.relative(root, file).split(path6.sep).join("/") || ".";
867
962
  auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
868
963
  });
869
964
  const stream = fg2.stream(patterns, {
@@ -900,89 +995,71 @@ async function auditGlob(root, patterns, options) {
900
995
  return matches.sort();
901
996
  }
902
997
  async function gitSourcePaths(root) {
903
- const { stdout } = await execute(
904
- "git",
905
- [
906
- "ls-files",
907
- "-z",
908
- "--cached",
909
- "--others",
910
- "--exclude-standard",
911
- "--",
912
- ...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
913
- ],
914
- { cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
915
- );
916
- return [...new Set(stdout.split("\0").filter(Boolean))].sort();
998
+ return auditGitPaths(root, "sources");
917
999
  }
918
1000
  async function gitIgnoredPaths(root, files) {
919
1001
  if (!files.length) return /* @__PURE__ */ new Set();
920
- const operation = execute(
921
- "git",
922
- ["check-ignore", "-z", "--stdin"],
923
- { cwd: root, maxBuffer: 4 * 1024 * 1024, timeout: 1e4 }
924
- );
925
- operation.child.stdin.on("error", () => {
926
- });
927
- operation.child.stdin.end(files.join("\0") + "\0");
928
- try {
929
- return new Set((await operation).stdout.split("\0").filter(Boolean));
930
- } catch (error) {
931
- if (error.code === 1) return /* @__PURE__ */ new Set();
932
- throw error;
1002
+ const ignored = /* @__PURE__ */ new Set();
1003
+ for (let offset = 0; offset < files.length; offset += 1e3) {
1004
+ try {
1005
+ const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
1006
+ cwd: root,
1007
+ maxBuffer: 4 * 1024 * 1024,
1008
+ timeout: 1e4,
1009
+ input: files.slice(offset, offset + 1e3).join("\0") + "\0"
1010
+ });
1011
+ for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
1012
+ } catch (error) {
1013
+ if (error.code !== 1) throw error;
1014
+ }
933
1015
  }
1016
+ return ignored;
934
1017
  }
935
- var execute, MAX_INPUTS;
1018
+ var MAX_INPUTS;
936
1019
  var init_inputs = __esm({
937
1020
  "src/audit/inputs.ts"() {
938
1021
  "use strict";
939
1022
  init_files();
940
1023
  init_storage();
941
- execute = promisify3(execFile3);
1024
+ init_git_read();
1025
+ init_inspection();
942
1026
  MAX_INPUTS = 1e5;
943
1027
  }
944
1028
  });
945
1029
 
946
1030
  // src/audit/docs.ts
947
1031
  import fs5 from "fs/promises";
948
- import path6 from "path";
949
- import { execFile as execFile4 } from "child_process";
950
- import { promisify as promisify4 } from "util";
1032
+ import path7 from "path";
951
1033
  import fg3 from "fast-glob";
952
- async function isDirty(resolvedRoot, relPath) {
1034
+ async function dirtyDocs(resolvedRoot, files) {
1035
+ if (!files.length) return /* @__PURE__ */ new Set();
953
1036
  try {
954
- const { stdout } = await exec3(
955
- "git",
956
- ["status", "--porcelain", "--", `:(literal)${relPath}`],
957
- { cwd: resolvedRoot }
958
- );
959
- return stdout.trim().length > 0;
1037
+ const dirty = /* @__PURE__ */ new Set();
1038
+ for (let offset = 0; offset < files.length; offset += 256) {
1039
+ const { stdout } = await execGit(
1040
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
1041
+ { cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
1042
+ );
1043
+ const entries = stdout.split("\0");
1044
+ for (let i = 0; i < entries.length; i++) {
1045
+ if (!entries[i]) continue;
1046
+ const status = entries[i].slice(0, 2);
1047
+ dirty.add(entries[i].slice(3));
1048
+ if (/[RC]/.test(status) && entries[i + 1]) dirty.add(entries[++i]);
1049
+ }
1050
+ }
1051
+ return dirty;
960
1052
  } catch {
961
- return true;
1053
+ return new Set(files);
962
1054
  }
963
1055
  }
964
1056
  async function discoverDocPaths(root) {
965
1057
  let candidates = [];
966
1058
  try {
967
- const { stdout } = await exec3(
968
- "git",
969
- [
970
- "ls-files",
971
- "-z",
972
- "--cached",
973
- "--others",
974
- "--exclude-standard",
975
- "--",
976
- ":(icase,glob)**/readme.md",
977
- ":(icase,glob)**/agents.md",
978
- ":(icase,glob)**/claude.md"
979
- ],
980
- { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
981
- );
982
- candidates = stdout.split("\0").filter(Boolean);
1059
+ candidates = await auditGitPaths(root, "documents");
983
1060
  } catch (error) {
984
1061
  try {
985
- await exec3("git", ["rev-parse", "--git-dir"], { cwd: root });
1062
+ await execGit(["rev-parse", "--git-dir"], { cwd: root });
986
1063
  } catch {
987
1064
  return localDocPaths(root);
988
1065
  }
@@ -1029,43 +1106,54 @@ async function localDocPaths(root) {
1029
1106
  throw error;
1030
1107
  }
1031
1108
  for (const entry of entries) if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
1032
- paths.push(path6.posix.join(dir, entry.name));
1109
+ paths.push(path7.posix.join(dir, entry.name));
1033
1110
  }
1034
1111
  }
1035
1112
  return paths;
1036
1113
  }
1037
1114
  function documentScope(file) {
1038
- const parent = path6.posix.dirname(file);
1039
- return path6.posix.basename(parent).toLowerCase() === ".claude" ? path6.posix.dirname(parent) : parent;
1115
+ const parent = path7.posix.dirname(file);
1116
+ return path7.posix.basename(parent).toLowerCase() === ".claude" ? path7.posix.dirname(parent) : parent;
1040
1117
  }
1041
1118
  async function discoverDocs(resolvedRoot) {
1119
+ return profilePhase("audit.documents", () => readDocs(resolvedRoot));
1120
+ }
1121
+ async function readDocs(resolvedRoot) {
1042
1122
  const docs = [];
1043
- for (const candidate of await discoverDocPaths(resolvedRoot)) {
1044
- const content2 = await readAuditInput(resolvedRoot, candidate);
1045
- if (content2 === null) throw new Error("Document disappeared during discovery: " + candidate);
1046
- const [lastCommit, dirty] = await Promise.all([lastCommitOf(resolvedRoot, candidate), isDirty(resolvedRoot, candidate)]);
1047
- docs.push({
1048
- path: candidate,
1049
- content: content2,
1050
- scope: documentScope(candidate),
1051
- kind: /^readme\.md$/i.test(path6.posix.basename(candidate)) ? "readme" : "instructions",
1052
- lineCount: content2.split("\n").length,
1053
- lastCommit,
1054
- dirty,
1055
- claims: extractClaims(content2)
1056
- });
1123
+ const files = await discoverDocPaths(resolvedRoot);
1124
+ const dirty = await dirtyDocs(resolvedRoot, files);
1125
+ for (let offset = 0; offset < files.length; offset += 4) {
1126
+ docs.push(...await Promise.all(files.slice(offset, offset + 4).map(async (candidate) => {
1127
+ const [content2, lastCommit] = await Promise.all([
1128
+ readAuditInput(resolvedRoot, candidate),
1129
+ profilePhase("audit.document-history", () => lastCommitOf(resolvedRoot, candidate))
1130
+ ]);
1131
+ if (content2 === null) throw new Error("Document disappeared during discovery: " + candidate);
1132
+ return {
1133
+ path: candidate,
1134
+ content: content2,
1135
+ scope: documentScope(candidate),
1136
+ kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : "instructions",
1137
+ lineCount: content2.split("\n").length,
1138
+ lastCommit,
1139
+ dirty: dirty.has(candidate),
1140
+ claims: extractClaims(content2)
1141
+ };
1142
+ })));
1057
1143
  }
1058
1144
  return docs;
1059
1145
  }
1060
- var exec3, DOC_CANDIDATES;
1146
+ var DOC_CANDIDATES;
1061
1147
  var init_docs = __esm({
1062
1148
  "src/audit/docs.ts"() {
1063
1149
  "use strict";
1150
+ init_git_read();
1151
+ init_profile();
1064
1152
  init_claims();
1065
1153
  init_git();
1066
1154
  init_inputs();
1067
1155
  init_files();
1068
- exec3 = promisify4(execFile4);
1156
+ init_inspection();
1069
1157
  DOC_CANDIDATES = [
1070
1158
  "AGENTS.md",
1071
1159
  "CLAUDE.md",
@@ -1076,14 +1164,14 @@ var init_docs = __esm({
1076
1164
 
1077
1165
  // src/audit/scope.ts
1078
1166
  import fs6 from "fs/promises";
1079
- import path7 from "path";
1167
+ import path8 from "path";
1080
1168
  function scopedPath(directory, value) {
1081
- if (path7.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
1082
- const resolved = path7.posix.normalize(path7.posix.join(directory, value));
1169
+ if (path8.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
1170
+ const resolved = path8.posix.normalize(path8.posix.join(directory, value));
1083
1171
  return resolved === ".." || resolved.startsWith("../") ? null : resolved;
1084
1172
  }
1085
1173
  function pathClaimScope(doc, claim) {
1086
- const directory = claim.relativeTo === "document" ? path7.posix.dirname(doc) : documentScope(doc);
1174
+ const directory = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
1087
1175
  const local = scopedPath(directory, claim.path);
1088
1176
  if (!local) return null;
1089
1177
  const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
@@ -1117,9 +1205,9 @@ var init_scope = __esm({
1117
1205
  });
1118
1206
 
1119
1207
  // src/test-map.ts
1120
- import path8 from "path";
1208
+ import path9 from "path";
1121
1209
  async function buildTestMap(dir) {
1122
- const rootDir = path8.resolve(dir);
1210
+ const rootDir = path9.resolve(dir);
1123
1211
  const access = await createFileAccess(rootDir);
1124
1212
  const testPatterns = [
1125
1213
  "**/*.test.*",
@@ -1140,7 +1228,7 @@ async function buildTestMap(dir) {
1140
1228
  const sourceByBaseName = /* @__PURE__ */ new Map();
1141
1229
  for (const file of sourceFiles) {
1142
1230
  if (testFiles.includes(file)) continue;
1143
- const baseName = path8.basename(file).replace(/\.[^.]+$/, "");
1231
+ const baseName = path9.basename(file).replace(/\.[^.]+$/, "");
1144
1232
  const existing = sourceByBaseName.get(baseName) ?? [];
1145
1233
  existing.push(file);
1146
1234
  sourceByBaseName.set(baseName, existing);
@@ -1148,7 +1236,7 @@ async function buildTestMap(dir) {
1148
1236
  const paired = [];
1149
1237
  const unmatched = [];
1150
1238
  for (const testFile of testFiles) {
1151
- const testBaseName = path8.basename(testFile).replace(/\.[^.]+$/, "");
1239
+ const testBaseName = path9.basename(testFile).replace(/\.[^.]+$/, "");
1152
1240
  const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
1153
1241
  if (!sourceName) {
1154
1242
  unmatched.push(testFile);
@@ -1156,10 +1244,10 @@ async function buildTestMap(dir) {
1156
1244
  }
1157
1245
  const candidates = sourceByBaseName.get(sourceName);
1158
1246
  if (candidates && candidates.length > 0) {
1159
- const testDir = path8.dirname(testFile);
1247
+ const testDir = path9.dirname(testFile);
1160
1248
  const bestMatch = candidates.reduce((best, candidate) => {
1161
- const candidateDir = path8.dirname(candidate);
1162
- const bestDir = path8.dirname(best);
1249
+ const candidateDir = path9.dirname(candidate);
1250
+ const bestDir = path9.dirname(best);
1163
1251
  const candidateOverlap = commonSegments(testDir, candidateDir);
1164
1252
  const bestOverlap = commonSegments(testDir, bestDir);
1165
1253
  return candidateOverlap > bestOverlap ? candidate : best;
@@ -1193,9 +1281,7 @@ var init_test_map = __esm({
1193
1281
  });
1194
1282
 
1195
1283
  // src/snapshot/snapshot.ts
1196
- import path9 from "path";
1197
- import { execFile as execFile5 } from "child_process";
1198
- import { promisify as promisify5 } from "util";
1284
+ import path10 from "path";
1199
1285
  import { z as z2 } from "zod";
1200
1286
  async function inspectSnapshot(rootDir) {
1201
1287
  try {
@@ -1211,7 +1297,7 @@ async function inspectSnapshot(rootDir) {
1211
1297
  }
1212
1298
  async function getCurrentGitHash(rootDir) {
1213
1299
  try {
1214
- const { stdout } = await exec4("git", ["rev-parse", "HEAD"], {
1300
+ const { stdout } = await execGit(["rev-parse", "HEAD"], {
1215
1301
  cwd: rootDir
1216
1302
  });
1217
1303
  return stdout.trim();
@@ -1219,16 +1305,16 @@ async function getCurrentGitHash(rootDir) {
1219
1305
  return "unknown";
1220
1306
  }
1221
1307
  }
1222
- var exec4, repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
1308
+ var repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
1223
1309
  var init_snapshot = __esm({
1224
1310
  "src/snapshot/snapshot.ts"() {
1225
1311
  "use strict";
1312
+ init_git_read();
1226
1313
  init_files();
1227
1314
  init_files();
1228
1315
  init_storage();
1229
1316
  init_paths();
1230
1317
  init_test_map();
1231
- exec4 = promisify5(execFile5);
1232
1318
  repoPath = z2.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
1233
1319
  verificationFields = {
1234
1320
  refreshedHash: z2.string().optional(),
@@ -1258,9 +1344,7 @@ var init_snapshot = __esm({
1258
1344
 
1259
1345
  // src/drift/drift.ts
1260
1346
  import fs7 from "fs/promises";
1261
- import path10 from "path";
1262
- import { execFile as execFile6 } from "child_process";
1263
- import { promisify as promisify6 } from "util";
1347
+ import path11 from "path";
1264
1348
  function parseChanges(output) {
1265
1349
  const fields = output.split("\0");
1266
1350
  const changes = [];
@@ -1281,7 +1365,7 @@ function touchedPaths(changes) {
1281
1365
  async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
1282
1366
  if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-") || !toHash || toHash === "unknown" || toHash.startsWith("-")) return null;
1283
1367
  try {
1284
- const { stdout } = await exec5("git", ["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
1368
+ const { stdout } = await execGit(["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
1285
1369
  return parseChanges(stdout);
1286
1370
  } catch {
1287
1371
  return null;
@@ -1290,8 +1374,8 @@ async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
1290
1374
  async function getWorkingTree(resolvedRoot) {
1291
1375
  try {
1292
1376
  const [diff, untracked] = await Promise.all([
1293
- exec5("git", ["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
1294
- exec5("git", ["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
1377
+ execGit(["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
1378
+ execGit(["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
1295
1379
  ]);
1296
1380
  const untrackedFiles = untracked.stdout.split("\0").filter((f) => f && !f.startsWith(".mason/"));
1297
1381
  return { available: true, changedFiles: [.../* @__PURE__ */ new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles])].sort(), untrackedFiles };
@@ -1299,13 +1383,12 @@ async function getWorkingTree(resolvedRoot) {
1299
1383
  return { available: false, changedFiles: [], untrackedFiles: [] };
1300
1384
  }
1301
1385
  }
1302
- var exec5;
1303
1386
  var init_drift = __esm({
1304
1387
  "src/drift/drift.ts"() {
1305
1388
  "use strict";
1389
+ init_git_read();
1306
1390
  init_snapshot();
1307
1391
  init_paths();
1308
- exec5 = promisify6(execFile6);
1309
1392
  }
1310
1393
  });
1311
1394
 
@@ -1326,7 +1409,7 @@ var init_types = __esm({
1326
1409
  });
1327
1410
 
1328
1411
  // src/audit/checks/deleted-reference.ts
1329
- import path11 from "path";
1412
+ import path12 from "path";
1330
1413
  async function checkDeletedReferences(ctx) {
1331
1414
  const result = emptyResult();
1332
1415
  for (const doc of ctx.docs) {
@@ -1355,7 +1438,7 @@ async function checkDeletedReferences(ctx) {
1355
1438
  const candidates = await Promise.all(scope.candidates.map(async (file) => ({
1356
1439
  file,
1357
1440
  tracked: await lastCommitOf(ctx.root, file),
1358
- parent: await pathExists(ctx.root, path11.posix.dirname(file))
1441
+ parent: await pathExists(ctx.root, path12.posix.dirname(file))
1359
1442
  })));
1360
1443
  const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
1361
1444
  if (!candidate && claim.relativeTo !== "document") continue;
@@ -1395,7 +1478,7 @@ var init_deleted_reference = __esm({
1395
1478
  // src/audit/checks/new-module.ts
1396
1479
  import fg4 from "fast-glob";
1397
1480
  import fs8 from "fs/promises";
1398
- import path12 from "path";
1481
+ import path13 from "path";
1399
1482
  function escapeRegExp(text2) {
1400
1483
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1401
1484
  }
@@ -1433,23 +1516,36 @@ async function checkNewModules(ctx) {
1433
1516
  return result;
1434
1517
  }
1435
1518
  function moduleDocumentation(docs) {
1436
- return docs.map((doc) => doc.content + "\n" + (path12.posix.dirname(doc.path) === "." ? "" : path12.posix.dirname(doc.path) + "/")).join("\n");
1519
+ return docs.map((doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")).join("\n");
1520
+ }
1521
+ function moduleCandidates(root, combinedDocs) {
1522
+ return inspectionRead(root, "modules:" + combinedDocs, () => collectModuleCandidates(root, combinedDocs));
1437
1523
  }
1438
- async function moduleCandidates(root, combinedDocs) {
1524
+ async function collectModuleCandidates(root, combinedDocs) {
1439
1525
  const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
1440
1526
  const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
1441
1527
  const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
1442
- const listSubdirs = async (dir) => {
1443
- const entries = await fs8.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1444
- 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));
1445
- const ignored = await gitIgnoredPaths(root, dirs);
1446
- const visible = new Set(dirs.filter((dir2) => !ignored.has(dir2) || sources.some((file) => file.startsWith(dir2 + "/"))));
1447
- return (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1448
- ignore,
1449
- onlyDirectories: true,
1450
- label: "Module directory discovery",
1451
- select: (file) => visible.has(file)
1452
- })).map((file) => path12.posix.basename(file));
1528
+ const listSubdirs = async (parents) => {
1529
+ const byParent = /* @__PURE__ */ new Map();
1530
+ for (let offset = 0; offset < parents.length; offset += 16) {
1531
+ await Promise.all(parents.slice(offset, offset + 16).map(async (dir) => {
1532
+ const entries = await fs8.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1533
+ 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));
1534
+ byParent.set(dir, dirs);
1535
+ }));
1536
+ }
1537
+ const ignored = await gitIgnoredPaths(root, [...byParent.values()].flat());
1538
+ const result = /* @__PURE__ */ new Map();
1539
+ for (const dir of parents) {
1540
+ const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
1541
+ result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1542
+ ignore,
1543
+ onlyDirectories: true,
1544
+ label: "Module directory discovery",
1545
+ select: (file) => visible.has(file)
1546
+ })).map((file) => path13.posix.basename(file)));
1547
+ }
1548
+ return result;
1453
1549
  };
1454
1550
  const countSourceFiles = async (dir) => {
1455
1551
  const files = sources.filter((file) => file.startsWith(dir + "/"));
@@ -1465,14 +1561,16 @@ async function moduleCandidates(root, combinedDocs) {
1465
1561
  return count4;
1466
1562
  };
1467
1563
  const candidates = [];
1468
- for (const topDir of await listSubdirs(".")) {
1564
+ const topDirs = (await listSubdirs(["."])).get(".");
1565
+ const nested = await listSubdirs(topDirs.filter((dir) => isMentioned(combinedDocs, dir)));
1566
+ for (const topDir of topDirs) {
1469
1567
  const topMentioned = isMentioned(combinedDocs, topDir);
1470
1568
  if (!topMentioned) {
1471
1569
  const count4 = await countSourceFiles(topDir);
1472
1570
  if (count4 >= 1) candidates.push({ dir: topDir, sourceFileCount: count4 });
1473
1571
  continue;
1474
1572
  }
1475
- const subdirs = await listSubdirs(topDir);
1573
+ const subdirs = nested.get(topDir);
1476
1574
  const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
1477
1575
  if (mentioned.length < ENUMERATION_THRESHOLD) continue;
1478
1576
  for (const sub of subdirs) {
@@ -1489,6 +1587,7 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
1489
1587
  var init_new_module = __esm({
1490
1588
  "src/audit/checks/new-module.ts"() {
1491
1589
  "use strict";
1590
+ init_inspection();
1492
1591
  init_files();
1493
1592
  init_inputs();
1494
1593
  init_git();
@@ -1518,7 +1617,7 @@ var init_new_module = __esm({
1518
1617
  });
1519
1618
 
1520
1619
  // src/audit/checks/stale-count.ts
1521
- import path13 from "path";
1620
+ import path14 from "path";
1522
1621
  async function countGradleModules(root) {
1523
1622
  for (const name of ["settings.gradle.kts", "settings.gradle"]) {
1524
1623
  const content2 = await readAuditInput(root, name);
@@ -1552,7 +1651,7 @@ async function countNpmWorkspaces(root) {
1552
1651
  return {
1553
1652
  actual: matched.length,
1554
1653
  countedFrom: "package.json workspaces",
1555
- members: matched.map((m) => path13.dirname(m)).sort()
1654
+ members: matched.map((m) => path14.dirname(m)).sort()
1556
1655
  };
1557
1656
  }
1558
1657
  }
@@ -1583,7 +1682,7 @@ async function countNpmWorkspaces(root) {
1583
1682
  return {
1584
1683
  actual: matched.length,
1585
1684
  countedFrom: "pnpm-workspace.yaml",
1586
- members: matched.map((m) => path13.dirname(m)).sort()
1685
+ members: matched.map((m) => path14.dirname(m)).sort()
1587
1686
  };
1588
1687
  }
1589
1688
  }
@@ -1605,8 +1704,8 @@ async function countCargoCrates(root) {
1605
1704
  ignore: ["**/target/**"],
1606
1705
  label: "Cargo workspace discovery"
1607
1706
  });
1608
- for (const m of matched) members.add(path13.dirname(m));
1609
- } else if (await readAuditInput(root, path13.posix.join(entry, "Cargo.toml")) !== null) {
1707
+ for (const m of matched) members.add(path14.dirname(m));
1708
+ } else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
1610
1709
  members.add(entry);
1611
1710
  }
1612
1711
  }
@@ -1626,7 +1725,7 @@ async function resolveCountSource(root, claim) {
1626
1725
  }
1627
1726
  async function resolveDocCountSource(root, doc, claim) {
1628
1727
  const scope = documentScope(doc);
1629
- const source = await resolveCountSource(path13.join(root, scope), claim);
1728
+ const source = await resolveCountSource(path14.join(root, scope), claim);
1630
1729
  if (!source || scope === ".") return source;
1631
1730
  return {
1632
1731
  ...source,
@@ -1678,7 +1777,7 @@ var init_stale_count = __esm({
1678
1777
  });
1679
1778
 
1680
1779
  // src/audit/checks/dead-command.ts
1681
- import path14 from "path";
1780
+ import path15 from "path";
1682
1781
  function scriptsOf(raw) {
1683
1782
  try {
1684
1783
  const pkg = JSON.parse(raw);
@@ -1714,7 +1813,7 @@ async function commandInputs(root, docs) {
1714
1813
  }
1715
1814
  let cursor = directory;
1716
1815
  while (true) {
1717
- const manifest = path14.posix.join(cursor, "package.json");
1816
+ const manifest = path15.posix.join(cursor, "package.json");
1718
1817
  scope.candidates.push(manifest);
1719
1818
  const raw = await read(manifest);
1720
1819
  if (raw !== null) {
@@ -1725,7 +1824,7 @@ async function commandInputs(root, docs) {
1725
1824
  break;
1726
1825
  }
1727
1826
  if (claim.directory !== void 0 || cursor === ".") break;
1728
- cursor = path14.posix.dirname(cursor);
1827
+ cursor = path15.posix.dirname(cursor);
1729
1828
  }
1730
1829
  if (!resolution.manifest && !resolution.problem) {
1731
1830
  resolution.problem = `No package.json for the command's scope (${directory}).`;
@@ -1788,10 +1887,8 @@ var init_dead_command = __esm({
1788
1887
  });
1789
1888
 
1790
1889
  // src/audit/release-metadata.ts
1791
- import { execFile as execFile7 } from "child_process";
1792
- import { promisify as promisify7 } from "util";
1793
1890
  async function git(root, args2) {
1794
- return (await exec6("git", args2, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1891
+ return (await execGit(args2, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1795
1892
  }
1796
1893
  function withoutAndroidReleaseValues(text2) {
1797
1894
  if (/\/\*|"""|'''/.test(text2) || !/id\s*\(?\s*["']com\.android\.(application|library)["']/.test(text2)) return null;
@@ -1837,11 +1934,10 @@ async function releaseMetadataOnly(root, commit) {
1837
1934
  return false;
1838
1935
  }
1839
1936
  }
1840
- var exec6;
1841
1937
  var init_release_metadata = __esm({
1842
1938
  "src/audit/release-metadata.ts"() {
1843
1939
  "use strict";
1844
- exec6 = promisify7(execFile7);
1940
+ init_git_read();
1845
1941
  }
1846
1942
  });
1847
1943
 
@@ -1937,7 +2033,7 @@ var init_deps_changed = __esm({
1937
2033
  });
1938
2034
 
1939
2035
  // src/context/lexical.ts
1940
- import path15 from "path";
2036
+ import path16 from "path";
1941
2037
  var init_lexical = __esm({
1942
2038
  "src/context/lexical.ts"() {
1943
2039
  "use strict";
@@ -2121,7 +2217,7 @@ var init_provenance = __esm({
2121
2217
 
2122
2218
  // src/decisions/decisions.ts
2123
2219
  import fs9 from "fs/promises";
2124
- import path16 from "path";
2220
+ import path17 from "path";
2125
2221
  import { createHash } from "crypto";
2126
2222
  import { z as z4 } from "zod";
2127
2223
  async function loadDecisionStore(rootDir) {
@@ -2172,9 +2268,9 @@ var init_decisions = __esm({
2172
2268
  });
2173
2269
 
2174
2270
  // src/decisions/drift.ts
2175
- import path17 from "path";
2271
+ import path18 from "path";
2176
2272
  async function computeDecisionDrift(rootDir, decisions) {
2177
- const resolvedRoot = path17.resolve(rootDir);
2273
+ const resolvedRoot = path18.resolve(rootDir);
2178
2274
  const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
2179
2275
  const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
2180
2276
  const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
@@ -2398,8 +2494,6 @@ var init_findings = __esm({
2398
2494
 
2399
2495
  // src/audit/advisory-review.ts
2400
2496
  import fs10 from "fs/promises";
2401
- import { execFile as execFile8 } from "child_process";
2402
- import { promisify as promisify8 } from "util";
2403
2497
  import { z as z6 } from "zod";
2404
2498
  async function readRecord(root, id) {
2405
2499
  const raw = await readStoreJson(root, recordPath(id));
@@ -2526,10 +2620,12 @@ async function advisoryReviewInventory(root) {
2526
2620
  if (names.length > 5e3) throw new Error("Advisory review inventory exceeds 5,000 records.");
2527
2621
  return Promise.all(names.filter((name) => name.endsWith(".json")).sort().map(async (name) => [name, await readAuditInput(root, ".mason/reviews/advisories/" + name)]));
2528
2622
  }
2529
- var execute2, git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2623
+ var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2530
2624
  var init_advisory_review = __esm({
2531
2625
  "src/audit/advisory-review.ts"() {
2532
2626
  "use strict";
2627
+ init_inspection();
2628
+ init_git_read();
2533
2629
  init_findings();
2534
2630
  init_deps_changed();
2535
2631
  init_inputs();
@@ -2539,8 +2635,7 @@ var init_advisory_review = __esm({
2539
2635
  init_store();
2540
2636
  init_decisions();
2541
2637
  init_drift2();
2542
- execute2 = promisify8(execFile8);
2543
- git2 = async (root, ...args2) => (await execute2("git", args2, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
2638
+ git2 = async (root, ...args2) => (await (["log", "ls-tree", "ls-files", "merge-base"].includes(args2[0]) ? inspectionGit : execGit)(args2, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
2544
2639
  hashSchema = z6.string().regex(/^[a-f0-9]{64}$/);
2545
2640
  commitSchema2 = z6.string().regex(/^[a-f0-9]{40,64}$/);
2546
2641
  evidenceSchema2 = z6.object({
@@ -2584,9 +2679,12 @@ var init_advisory_review = __esm({
2584
2679
 
2585
2680
  // src/audit/audit.ts
2586
2681
  import fs11 from "fs/promises";
2587
- import path18 from "path";
2682
+ import path19 from "path";
2588
2683
  async function computeAudit(rootDir, options = {}) {
2589
- const resolvedRoot = path18.resolve(rootDir);
2684
+ return profilePhase("audit.current", () => auditCurrent(rootDir, options));
2685
+ }
2686
+ async function auditCurrent(rootDir, options) {
2687
+ const resolvedRoot = path19.resolve(rootDir);
2590
2688
  const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
2591
2689
  if (docs.length === 0) return null;
2592
2690
  const report = {
@@ -2612,15 +2710,15 @@ async function computeAudit(rootDir, options = {}) {
2612
2710
  };
2613
2711
  if (!report.gitAvailable) return report;
2614
2712
  const changesSinceDoc = /* @__PURE__ */ new Map();
2713
+ const changesByCommit = /* @__PURE__ */ new Map();
2615
2714
  for (const doc of docs) {
2616
- changesSinceDoc.set(
2617
- doc.path,
2618
- doc.lastCommit ? await getChangesWithStatus(resolvedRoot, doc.lastCommit.hash) : null
2619
- );
2715
+ const hash2 = doc.lastCommit?.hash;
2716
+ if (hash2 && !changesByCommit.has(hash2)) changesByCommit.set(hash2, await getChangesWithStatus(resolvedRoot, hash2));
2717
+ changesSinceDoc.set(doc.path, hash2 ? changesByCommit.get(hash2) : null);
2620
2718
  }
2621
2719
  let decisionsPresent = false;
2622
2720
  try {
2623
- await fs11.access(path18.join(resolvedRoot, ".mason", "decisions"));
2721
+ await fs11.access(path19.join(resolvedRoot, ".mason", "decisions"));
2624
2722
  decisionsPresent = true;
2625
2723
  } catch {
2626
2724
  }
@@ -2650,6 +2748,7 @@ async function computeAudit(rootDir, options = {}) {
2650
2748
  var init_audit = __esm({
2651
2749
  "src/audit/audit.ts"() {
2652
2750
  "use strict";
2751
+ init_profile();
2653
2752
  init_drift();
2654
2753
  init_snapshot();
2655
2754
  init_docs();
@@ -2661,7 +2760,7 @@ var init_audit = __esm({
2661
2760
 
2662
2761
  // src/audit/repair.ts
2663
2762
  import fs12 from "fs/promises";
2664
- import path19 from "path";
2763
+ import path20 from "path";
2665
2764
  import { randomUUID as randomUUID2 } from "crypto";
2666
2765
  import { z as z7 } from "zod";
2667
2766
  function recheckable(finding) {
@@ -2685,6 +2784,9 @@ async function docState(root) {
2685
2784
  return digest(docs);
2686
2785
  }
2687
2786
  async function stableAudit(root, checks, options = {}) {
2787
+ return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
2788
+ }
2789
+ async function readStableAudit(root, checks, options) {
2688
2790
  const head = await getCurrentGitHash(root);
2689
2791
  const before = await docState(root);
2690
2792
  const report = await computeAudit(root, { ...options, checks });
@@ -2693,10 +2795,43 @@ async function stableAudit(root, checks, options = {}) {
2693
2795
  }
2694
2796
  return report;
2695
2797
  }
2696
- async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
2798
+ async function withRepairSession(root, options, run, validate, cachedAudit) {
2799
+ const head = await getCurrentGitHash(root);
2800
+ const audits = /* @__PURE__ */ new Map();
2801
+ const documents = /* @__PURE__ */ new Map();
2802
+ const history = /* @__PURE__ */ new Map();
2803
+ const assessments = /* @__PURE__ */ new Map();
2804
+ const memo = (cache, key, read) => {
2805
+ if (!cache.has(key)) cache.set(key, read());
2806
+ return cache.get(key);
2807
+ };
2808
+ const session = {
2809
+ audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
2810
+ if (cachedAudit && cachedAudit.headHash === head && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
2811
+ const current = structuredClone(cachedAudit);
2812
+ const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, head)))).filter((r) => r !== null);
2813
+ delete current.advisoryReviews;
2814
+ if (reviews.length) current.advisoryReviews = reviews;
2815
+ return current;
2816
+ }
2817
+ return computeAudit(root, { ...options, checks });
2818
+ }),
2819
+ document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
2820
+ history: (head2) => memo(history, head2, () => getChangesWithStatus(root, head2)),
2821
+ assessment: (dir, finding, head2) => memo(assessments, digest([dir, finding, head2]), () => assessAdvisory(dir, finding, head2))
2822
+ };
2823
+ const result = await run({
2824
+ prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
2825
+ verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
2826
+ currentAudit: () => session.audit(ALL_CHECKS)
2827
+ });
2828
+ await validate();
2829
+ return result;
2830
+ }
2831
+ async function prepareOriginal(rootDir, checks, options, session) {
2697
2832
  const root = await fs12.realpath(rootDir);
2698
2833
  const selected = z7.array(checkSchema).nonempty().parse(checks);
2699
- const report = await stableAudit(root, selected, options);
2834
+ const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
2700
2835
  if (!report) throw new Error("No context files found to prepare a repair.");
2701
2836
  if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
2702
2837
  const storedReport = reportSchema.parse(report);
@@ -2712,8 +2847,8 @@ async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
2712
2847
  }
2713
2848
  async function loadRepairBaseline(rootDir, baselinePath) {
2714
2849
  const root = await fs12.realpath(rootDir);
2715
- const declaredRoot = path19.resolve(rootDir);
2716
- const relative = path19.isAbsolute(baselinePath) ? path19.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
2850
+ const declaredRoot = path20.resolve(rootDir);
2851
+ const relative = path20.isAbsolute(baselinePath) ? path20.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
2717
2852
  const stored = baselineSchema.parse(await readStoreJson(root, relative));
2718
2853
  const { digest: savedDigest, ...payload } = stored;
2719
2854
  if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
@@ -2721,24 +2856,24 @@ async function loadRepairBaseline(rootDir, baselinePath) {
2721
2856
  const original = stored.report;
2722
2857
  return { root, relative, original };
2723
2858
  }
2724
- async function verifyRepair(rootDir, baselinePath, options = {}) {
2859
+ async function verifyOriginal(rootDir, baselinePath, options, session) {
2725
2860
  const { root, relative, original } = await loadRepairBaseline(rootDir, baselinePath);
2726
2861
  const diagnostics = [];
2727
2862
  let reviewDigest;
2728
2863
  let current = null;
2729
2864
  try {
2730
- reviewDigest = digest(await advisoryReviewInventory(root));
2731
- current = await stableAudit(root, original.checksRun, options);
2865
+ if (!session) reviewDigest = digest(await advisoryReviewInventory(root));
2866
+ current = await (session ? session.audit(original.checksRun) : stableAudit(root, original.checksRun, options));
2732
2867
  if (!current) diagnostics.push("No context files remain available to audit.");
2733
2868
  else if (!current.gitAvailable) diagnostics.push("Git history is unavailable.");
2734
2869
  for (const doc of original.docs) {
2735
2870
  if (!allFindings(original).some((f) => f.anchor.doc === doc.path) || !current?.docs.some((d) => d.path === doc.path)) continue;
2736
- const content2 = await readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024);
2871
+ const content2 = await (session ? session.document(doc.path) : readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024));
2737
2872
  if (content2 === null || !content2.trim()) {
2738
2873
  diagnostics.push("Original context file " + doc.path + " is empty or unreadable; losing its claims does not verify a repair.");
2739
2874
  }
2740
2875
  }
2741
- if (await getChangesWithStatus(root, original.headHash) === null) {
2876
+ if (await (session ? session.history(original.headHash) : getChangesWithStatus(root, original.headHash)) === null) {
2742
2877
  diagnostics.push("The original audit commit is unavailable; repair history cannot be verified.");
2743
2878
  }
2744
2879
  } catch (error) {
@@ -2763,7 +2898,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
2763
2898
  if (!current.checksRun?.includes(finding.type) || skipped.length) {
2764
2899
  return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
2765
2900
  }
2766
- const review = await assessAdvisory(root, now ?? finding, original.headHash);
2901
+ const review = await (session?.assessment ?? assessAdvisory)(root, now ?? finding, original.headHash);
2767
2902
  if (review) {
2768
2903
  if (review.status === "unverified") return { ...base, review, status: "unverified", reason: review.reason };
2769
2904
  if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
@@ -2778,7 +2913,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
2778
2913
  }
2779
2914
  return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
2780
2915
  }));
2781
- try {
2916
+ if (!session) try {
2782
2917
  if (reviewDigest !== digest(await advisoryReviewInventory(root)) || current?.headHash !== await getCurrentGitHash(root)) {
2783
2918
  diagnostics.push("HEAD or advisory review records changed during verification; repeat against stable evidence.");
2784
2919
  for (const finding of findings) {
@@ -2818,6 +2953,7 @@ var count2, reportSchema, baselineSchema, repairFindingSchema;
2818
2953
  var init_repair = __esm({
2819
2954
  "src/audit/repair.ts"() {
2820
2955
  "use strict";
2956
+ init_profile();
2821
2957
  init_audit();
2822
2958
  init_docs();
2823
2959
  init_snapshot();
@@ -2871,27 +3007,25 @@ var init_repair = __esm({
2871
3007
 
2872
3008
  // src/automation/evidence.ts
2873
3009
  import fs13 from "fs/promises";
2874
- import path20 from "path";
3010
+ import path21 from "path";
2875
3011
  import { createHash as createHash3 } from "crypto";
2876
- import { execFile as execFile9 } from "child_process";
2877
- import { promisify as promisify9 } from "util";
2878
3012
  import fg5 from "fast-glob";
2879
3013
  import { z as z8 } from "zod";
2880
3014
  async function git3(root, ...args2) {
2881
- return (await exec7("git", args2, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout;
3015
+ return profilePhase("automation.git", async () => (await execGit(args2, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
2882
3016
  }
2883
3017
  async function workspace(dir) {
2884
3018
  const root = await fs13.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
2885
- const gitDir = await fs13.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
2886
- let branch;
2887
- try {
2888
- branch = (await git3(root, "symbolic-ref", "--quiet", "HEAD")).trim();
2889
- } catch {
2890
- branch = "detached";
2891
- }
3019
+ const [gitDir, branch] = await Promise.all([
3020
+ git3(root, "rev-parse", "--absolute-git-dir").then((value) => fs13.realpath(value.trim())),
3021
+ git3(root, "symbolic-ref", "--quiet", "HEAD").then((value) => value.trim(), () => "detached")
3022
+ ]);
2892
3023
  return { root, gitDir, branch, directory: ".mason/reports/automation/" + hash([root, gitDir, branch]).slice(0, 24) };
2893
3024
  }
2894
3025
  async function readInputs(root) {
3026
+ return profilePhase("automation.inputs", () => collectInputs(root));
3027
+ }
3028
+ async function collectInputs(root) {
2895
3029
  const docPaths = await discoverDocPaths(root);
2896
3030
  const [headText, docStatus, shallowPath, replacements] = await Promise.all([
2897
3031
  git3(root, "rev-parse", "HEAD"),
@@ -2902,27 +3036,35 @@ async function readInputs(root) {
2902
3036
  const head = headText.trim();
2903
3037
  let shallow = null;
2904
3038
  try {
2905
- shallow = await fs13.readFile(path20.resolve(root, shallowPath.trim()), "utf8");
3039
+ shallow = await fs13.readFile(path21.resolve(root, shallowPath.trim()), "utf8");
2906
3040
  } catch (error) {
2907
3041
  if (error.code !== "ENOENT") throw error;
2908
3042
  }
2909
3043
  const docs = {};
2910
3044
  const docContents = [];
3045
+ const parsedDocs = [];
2911
3046
  const claims = [];
3047
+ const presence = /* @__PURE__ */ new Map();
3048
+ const exists = (file) => {
3049
+ if (!presence.has(file)) presence.set(file, pathExists(root, file));
3050
+ return presence.get(file);
3051
+ };
2912
3052
  for (const file of docPaths) {
2913
3053
  const text2 = await content(root, file);
2914
3054
  docs[file] = text2 === null ? null : hash(text2);
2915
3055
  docContents.push([file, text2]);
2916
- for (const claim of text2 ? extractClaims(text2).paths : []) {
3056
+ const parsed = { path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") };
3057
+ parsedDocs.push(parsed);
3058
+ for (const claim of parsed.claims.paths) {
2917
3059
  const scope = pathClaimScope(file, claim);
2918
3060
  if (scope?.candidates.some(gitMetadataPath)) continue;
2919
3061
  for (const candidate of scope?.candidates ?? []) {
2920
3062
  if (optionalMasonPath(candidate)) continue;
2921
- claims.push([candidate, await pathExists(root, candidate), await pathExists(root, path20.posix.dirname(candidate))]);
3063
+ const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path21.posix.dirname(candidate))]);
3064
+ claims.push([candidate, filePresent, parentPresent]);
2922
3065
  }
2923
3066
  }
2924
3067
  }
2925
- const parsedDocs = docContents.map(([file, text2]) => ({ path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") }));
2926
3068
  const combinedDocs = moduleDocumentation(parsedDocs);
2927
3069
  const countClaims = parsedDocs.flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
2928
3070
  const decisionDirectory = await storePath(root, ".mason/decisions");
@@ -2930,7 +3072,7 @@ async function readInputs(root) {
2930
3072
  if (error.code === "ENOENT") return "absent";
2931
3073
  throw error;
2932
3074
  });
2933
- const [modules, counts, commands, decisionFiles] = await Promise.all([
3075
+ const [modules, counts, commands2, decisionFiles] = await Promise.all([
2934
3076
  combinedDocs ? moduleCandidates(root, combinedDocs) : [],
2935
3077
  Promise.all(countClaims.map(({ doc, claim }) => resolveDocCountSource(root, doc, claim))),
2936
3078
  commandInputs(root, parsedDocs),
@@ -2944,12 +3086,12 @@ async function readInputs(root) {
2944
3086
  git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
2945
3087
  git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
2946
3088
  ]) : ["", ""];
2947
- const common = [4, engineVersion, head, shallow, replacements, docContents];
3089
+ const common = [5, engineVersion, head, shallow, replacements, docContents];
2948
3090
  const keys = {
2949
3091
  "deleted-reference": hash([common, claims, docStatus]),
2950
3092
  "new-module": hash([common, modules]),
2951
3093
  "stale-count": hash([common, counts]),
2952
- "dead-command": hash([common, commands]),
3094
+ "dead-command": hash([common, commands2]),
2953
3095
  "deps-changed": hash([common, docStatus]),
2954
3096
  "decision-anchor-drift": hash([common, decisionPresence, decisions, status, index2])
2955
3097
  };
@@ -2969,21 +3111,30 @@ function checkCache(raw, inputs) {
2969
3111
  reused.add(name);
2970
3112
  return structuredClone(entries[name].result);
2971
3113
  }
2972
- const result = await CHECKS[name](ctx);
3114
+ const result = await profilePhase("check." + name, () => CHECKS[name](ctx));
2973
3115
  ran.add(name);
2974
3116
  if (!result.skipped.length) entries[name] = { key: inputs.keys[name], result };
2975
3117
  else delete entries[name];
2976
3118
  return result;
2977
3119
  } };
2978
- return { options, ran, reused, diagnostic, serialize: () => {
2979
- const canonical = cacheSchema.shape.entries.parse(entries);
2980
- return { version: 1, entries: canonical, digest: hash(canonical) };
2981
- } };
3120
+ return {
3121
+ options,
3122
+ ran,
3123
+ reused,
3124
+ diagnostic,
3125
+ has: (name) => entries[name]?.key === inputs.keys[name] && !entries[name].result.skipped.length,
3126
+ serialize: () => {
3127
+ const canonical = cacheSchema.shape.entries.parse(entries);
3128
+ return { version: 1, entries: canonical, digest: hash(canonical) };
3129
+ }
3130
+ };
2982
3131
  }
2983
- var exec7, engineVersion, hash, content, cacheSchema;
3132
+ var engineVersion, hash, content, cacheSchema;
2984
3133
  var init_evidence = __esm({
2985
3134
  "src/automation/evidence.ts"() {
2986
3135
  "use strict";
3136
+ init_git_read();
3137
+ init_profile();
2987
3138
  init_docs();
2988
3139
  init_scope();
2989
3140
  init_claims();
@@ -2995,8 +3146,7 @@ var init_evidence = __esm({
2995
3146
  init_dead_command();
2996
3147
  init_storage();
2997
3148
  init_advisory_review();
2998
- exec7 = promisify9(execFile9);
2999
- engineVersion = true ? "0.17.2" : "development";
3149
+ engineVersion = true ? "0.17.4" : "development";
3000
3150
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
3001
3151
  content = readAuditInput;
3002
3152
  cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
@@ -3190,7 +3340,7 @@ var init_execution = __esm({
3190
3340
  import { randomUUID as randomUUID4 } from "crypto";
3191
3341
  import { z as z10 } from "zod";
3192
3342
  async function observeReadOnlyTool(dir, event) {
3193
- const ws = await workspace(dir);
3343
+ const ws = await profilePhase("automation.workspace", () => workspace(dir));
3194
3344
  await withLock(ws.root, ws.directory, async () => {
3195
3345
  const statePath = ws.directory + "/state.json";
3196
3346
  const raw = await readStoreJson(ws.root, statePath);
@@ -3237,24 +3387,41 @@ async function loadState(ws) {
3237
3387
  }
3238
3388
  return state;
3239
3389
  }
3240
- async function sharedAnalysis(ws, state, inputs, startedAt) {
3390
+ async function matchingAnalysis(ws, state, inputs) {
3241
3391
  const pointer = state.analysis;
3242
- if (!pointer || pointer.fingerprint !== inputs.fingerprint || pointer.completedAt < startedAt) return null;
3243
- const stored = sharedSchema.parse(await readStoreJson(ws.root, pointer.path));
3392
+ if (!pointer || pointer.fingerprint !== inputs.fingerprint) return { analysis: null };
3393
+ if (pointer.path !== ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json") {
3394
+ throw new Error("Invalid automation analysis cache path; inspect the retained state.");
3395
+ }
3396
+ const invalid = { analysis: null, diagnostic: "Discarded an invalid current-audit cache; audit preparation and original verification were repeated." };
3397
+ let raw;
3398
+ try {
3399
+ raw = await readStoreJson(ws.root, pointer.path);
3400
+ } catch (error) {
3401
+ const cause = error.cause;
3402
+ if (cause instanceof SyntaxError || cause instanceof Error && cause.message === "expected a JSON object, received null") return invalid;
3403
+ throw error;
3404
+ }
3405
+ if (raw === null) return { analysis: null };
3406
+ const parsed = sharedSchema.safeParse(raw);
3407
+ if (!parsed.success) return invalid;
3408
+ const stored = parsed.data;
3244
3409
  const a = stored.analysis;
3245
3410
  if (stored.digest !== hash(a) || a.id !== pointer.id || a.fingerprint !== inputs.fingerprint || a.completedAt !== pointer.completedAt || a.report.root !== ws.root || a.report.branch !== ws.branch || a.report.head !== inputs.head || hash(a.report.baselinePaths) !== hash(state.baselines.map((b) => b.path))) {
3246
- throw new Error("Invalid shared automation evidence; run a new check.");
3411
+ return invalid;
3247
3412
  }
3248
3413
  if (a.baselineDigest !== await baselineDigest(ws, state)) throw new Error("Repair baseline was modified; use the original baseline.");
3249
- return a;
3414
+ return { analysis: a };
3250
3415
  }
3251
3416
  async function verifyInputs(ws, inputs) {
3252
- const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
3253
- if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
3254
- throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
3255
- }
3417
+ return profilePhase("automation.revalidate", () => withRepositoryInspection(ws.root, async () => {
3418
+ const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
3419
+ if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
3420
+ throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
3421
+ }
3422
+ }));
3256
3423
  }
3257
- async function computeAnalysis(ws, state, inputs, event) {
3424
+ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
3258
3425
  const baselines = [...state.baselines];
3259
3426
  const now = (/* @__PURE__ */ new Date()).toISOString();
3260
3427
  const empty = {
@@ -3272,7 +3439,10 @@ async function computeAnalysis(ws, state, inputs, event) {
3272
3439
  capture: "unknown",
3273
3440
  scope: SCOPE
3274
3441
  };
3275
- if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) return { baselines, report: empty, cache: null };
3442
+ if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) {
3443
+ await verifyInputs(ws, inputs);
3444
+ return { baselines, report: empty, cache: null };
3445
+ }
3276
3446
  if (ws.branch === "detached" && state.latest) {
3277
3447
  const previous = await readStoreJson(ws.root, state.latest);
3278
3448
  if (!previous?.head || !/^[a-f0-9]{40,64}$/.test(previous.head)) throw new Error("The previous detached checkout evidence is unavailable.");
@@ -3283,7 +3453,7 @@ async function computeAnalysis(ws, state, inputs, event) {
3283
3453
  }
3284
3454
  }
3285
3455
  let cached = null;
3286
- const diagnostics = [];
3456
+ const diagnostics = cacheDiagnostic ? [cacheDiagnostic] : [];
3287
3457
  try {
3288
3458
  cached = await readStoreJson(ws.root, ws.directory + "/cache.json");
3289
3459
  } catch {
@@ -3291,19 +3461,26 @@ async function computeAnalysis(ws, state, inputs, event) {
3291
3461
  }
3292
3462
  const cache = checkCache(cached, inputs);
3293
3463
  if (cache.diagnostic) diagnostics.push(cache.diagnostic);
3294
- const saveBaseline = async () => {
3295
- if (baselines.length >= 128) throw new Error("128 retained baselines need review; automatic capture stopped without discarding original evidence.");
3296
- const prepared = await prepareRepair(ws.root, ALL_CHECKS, cache.options);
3297
- baselines.push({ path: prepared.baselinePath, at: now, event: event.event, fingerprint: inputs.fingerprint });
3298
- };
3299
- if (!baselines.length) await saveBaseline();
3300
- const verifications = [];
3301
- for (const baseline of baselines) verifications.push(await verifyRepair(ws.root, baseline.path, cache.options));
3302
- const known = new Set(verifications.flatMap((v) => v.findings.map((f) => f.id)));
3303
- if (verifications.some((v) => v.newFindings.some((f) => !known.has(findingId(f))))) {
3304
- await saveBaseline();
3305
- verifications.push(await verifyRepair(ws.root, baselines.at(-1).path, cache.options));
3306
- }
3464
+ const cachedAudit = previousAudit?.gitAvailable && previousAudit.headHash === inputs.head && previousAudit.root === ws.root && !previousAudit.skippedChecks.length && ALL_CHECKS.every((name) => previousAudit.checksRun?.includes(name) && cache.has(name)) ? previousAudit : void 0;
3465
+ if (cachedAudit) for (const name of ALL_CHECKS) cache.reused.add(name);
3466
+ let currentAudit = null;
3467
+ const verifications = await withRepairSession(ws.root, cache.options, async (repairs) => {
3468
+ const saveBaseline = async () => {
3469
+ if (baselines.length >= 128) throw new Error("128 retained baselines need review; automatic capture stopped without discarding original evidence.");
3470
+ const prepared = await repairs.prepare();
3471
+ baselines.push({ path: prepared.baselinePath, at: now, event: event.event, fingerprint: inputs.fingerprint });
3472
+ };
3473
+ if (!baselines.length) await saveBaseline();
3474
+ const verifications2 = [];
3475
+ for (const baseline of baselines) verifications2.push(await repairs.verify(baseline.path));
3476
+ const known = new Set(verifications2.flatMap((v) => v.findings.map((f) => f.id)));
3477
+ if (verifications2.some((v) => v.newFindings.some((f) => !known.has(findingId(f))))) {
3478
+ await saveBaseline();
3479
+ verifications2.push(await repairs.verify(baselines.at(-1).path));
3480
+ }
3481
+ currentAudit = await repairs.currentAudit();
3482
+ return verifications2;
3483
+ }, () => verifyInputs(ws, inputs), cachedAudit);
3307
3484
  const merged = /* @__PURE__ */ new Map();
3308
3485
  for (const verification of verifications) {
3309
3486
  for (const finding of verification.findings) {
@@ -3324,21 +3501,20 @@ async function computeAnalysis(ws, state, inputs, event) {
3324
3501
  reportPath: "",
3325
3502
  findings: [...merged.values()],
3326
3503
  diagnostics: [...new Set(diagnostics)],
3327
- checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [] },
3504
+ checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [], ...cachedAudit ? { auditReused: true } : {} },
3328
3505
  counts,
3329
3506
  capture: "unknown",
3330
3507
  scope: SCOPE
3331
3508
  };
3332
- return { baselines, report, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
3509
+ return { baselines, report, currentAudit, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
3333
3510
  }
3334
3511
  async function analyze(ws, inputs, event, startedAt) {
3335
3512
  return withLock(ws.root, ws.directory + "/analysis/" + inputs.fingerprint, async () => {
3336
3513
  const before = await loadState(ws);
3337
- const shared = await sharedAnalysis(ws, before, inputs, startedAt);
3338
- if (shared) return { analysis: shared, shared: true };
3514
+ const { analysis: previous, diagnostic } = await matchingAnalysis(ws, before, inputs);
3515
+ if (previous && previous.completedAt >= startedAt) return { analysis: previous, shared: true };
3339
3516
  const originalDigest = await baselineDigest(ws, before);
3340
- const computed = await computeAnalysis(ws, before, inputs, event);
3341
- await verifyInputs(ws, inputs);
3517
+ const computed = await computeAnalysis(ws, before, inputs, event, previous?.currentAudit, diagnostic);
3342
3518
  if (originalDigest !== await baselineDigest(ws, before)) throw new Error("Repair baseline was modified during automation; original evidence must be inspected.");
3343
3519
  const id = randomUUID4();
3344
3520
  const analysis = analysisSchema.parse({
@@ -3346,7 +3522,8 @@ async function analyze(ws, inputs, event, startedAt) {
3346
3522
  fingerprint: inputs.fingerprint,
3347
3523
  completedAt: Date.now(),
3348
3524
  baselineDigest: await baselineDigest(ws, { ...before, baselines: computed.baselines }),
3349
- report: computed.report
3525
+ report: computed.report,
3526
+ currentAudit: computed.currentAudit
3350
3527
  });
3351
3528
  const analysisPath = ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json";
3352
3529
  await withLock(ws.root, ws.directory, async () => {
@@ -3365,17 +3542,17 @@ async function analyze(ws, inputs, event, startedAt) {
3365
3542
  }
3366
3543
  async function automate(dir, event) {
3367
3544
  const startedAt = Date.now();
3368
- const ws = await workspace(dir);
3369
- return recordExecution(ws.root, ws.directory, event.event, async () => {
3545
+ const ws = await profilePhase("automation.workspace", () => workspace(dir));
3546
+ return recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
3370
3547
  const inputs = await readInputs(ws.root);
3371
- const result = await analyze(ws, inputs, event, startedAt);
3548
+ const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
3372
3549
  if (result.shared) {
3373
3550
  await verifyInputs(ws, inputs);
3374
3551
  if (result.analysis.baselineDigest !== await baselineDigest(ws, await loadState(ws))) {
3375
3552
  throw new Error("Repair baseline was modified during shared automation; original evidence must be inspected.");
3376
3553
  }
3377
3554
  }
3378
- return withLock(ws.root, ws.directory, async () => {
3555
+ return profilePhase("automation.publish", () => withLock(ws.root, ws.directory, async () => {
3379
3556
  const state = await loadState(ws);
3380
3557
  if (state.analysis?.id !== result.analysis.id) throw new Error("Automation evidence changed during publication; retry against the current inputs.");
3381
3558
  const report = structuredClone(result.analysis.report);
@@ -3438,11 +3615,11 @@ async function automate(dir, event) {
3438
3615
  if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
3439
3616
  await writeStoreJson(ws.root, ws.directory + "/state.json", state);
3440
3617
  return { report, message: notify ? summarize(report) : null, continueOnce };
3441
- });
3442
- });
3618
+ }));
3619
+ }));
3443
3620
  }
3444
3621
  async function automationStatus(dir) {
3445
- const ws = await workspace(dir);
3622
+ const ws = await profilePhase("automation.workspace", () => workspace(dir));
3446
3623
  const execution = await executionStatus(ws.root, ws.directory);
3447
3624
  const notifications = await failureNotifications(ws.root, ws.directory);
3448
3625
  const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
@@ -3476,6 +3653,8 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
3476
3653
  var init_runtime = __esm({
3477
3654
  "src/automation/runtime.ts"() {
3478
3655
  "use strict";
3656
+ init_profile();
3657
+ init_inspection();
3479
3658
  init_execution();
3480
3659
  init_repair();
3481
3660
  init_types();
@@ -3490,6 +3669,7 @@ var init_runtime = __esm({
3490
3669
  fingerprint: z10.string(),
3491
3670
  completedAt: z10.number(),
3492
3671
  baselineDigest: z10.string(),
3672
+ currentAudit: reportSchema.nullable().optional(),
3493
3673
  report: z10.object({
3494
3674
  version: z10.literal(1),
3495
3675
  status: z10.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
@@ -3503,7 +3683,8 @@ var init_runtime = __esm({
3503
3683
  checks: z10.object({
3504
3684
  ran: z10.array(z10.string()),
3505
3685
  reused: z10.array(z10.string()),
3506
- skipped: z10.array(z10.object({ check: z10.string(), reason: z10.string(), doc: z10.string().optional() }))
3686
+ skipped: z10.array(z10.object({ check: z10.string(), reason: z10.string(), doc: z10.string().optional() })),
3687
+ auditReused: z10.boolean().optional()
3507
3688
  }),
3508
3689
  counts: z10.object({ resolved: z10.number(), unresolved: z10.number(), "review-required": z10.number(), unverified: z10.number() }),
3509
3690
  capture: z10.enum(["observed", "unknown"]),
@@ -3511,17 +3692,17 @@ var init_runtime = __esm({
3511
3692
  })
3512
3693
  });
3513
3694
  sharedSchema = z10.object({ analysis: analysisSchema, digest: z10.string() });
3514
- baselineDigest = async (ws, state) => hash(await Promise.all(
3695
+ baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
3515
3696
  state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
3516
- ));
3697
+ )));
3517
3698
  }
3518
3699
  });
3519
3700
 
3520
3701
  // src/automation/hook-root.ts
3521
3702
  import fs15 from "fs/promises";
3522
- import path21 from "path";
3703
+ import path22 from "path";
3523
3704
  async function repositoryRoot(dir) {
3524
- if (!path21.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
3705
+ if (!path22.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
3525
3706
  try {
3526
3707
  return await fs15.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
3527
3708
  } catch (error) {
@@ -3605,8 +3786,8 @@ __export(launcher_exports, {
3605
3786
  mcpCommand: () => mcpCommand,
3606
3787
  prepareLaunch: () => prepareLaunch
3607
3788
  });
3608
- import { execFile as execFile10 } from "child_process";
3609
- import { promisify as promisify10 } from "util";
3789
+ import { execFile as execFile2 } from "child_process";
3790
+ import { promisify as promisify2 } from "util";
3610
3791
  import fs16 from "fs/promises";
3611
3792
  function globalCommand(args2, platform = process.platform) {
3612
3793
  return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args2] } : { command: "mason", args: args2 };
@@ -3614,7 +3795,7 @@ function globalCommand(args2, platform = process.platform) {
3614
3795
  async function installedCommand() {
3615
3796
  const invocation = globalCommand(["internal-integration-version"]);
3616
3797
  try {
3617
- const { stdout } = await promisify10(execFile10)(
3798
+ const { stdout } = await promisify2(execFile2)(
3618
3799
  invocation.command,
3619
3800
  invocation.args,
3620
3801
  { timeout: 5e3, maxBuffer: 8192, windowsHide: true }
@@ -3650,7 +3831,7 @@ var init_launcher = __esm({
3650
3831
  "use strict";
3651
3832
  init_evidence();
3652
3833
  init_model();
3653
- executingVersion = () => true ? "0.17.2" : "development";
3834
+ executingVersion = () => true ? "0.17.4" : "development";
3654
3835
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3655
3836
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3656
3837
  }
@@ -3866,7 +4047,7 @@ __export(files_exports, {
3866
4047
  readText: () => readText
3867
4048
  });
3868
4049
  import fs18 from "fs/promises";
3869
- import path22 from "path";
4050
+ import path23 from "path";
3870
4051
  import { randomUUID as randomUUID5 } from "crypto";
3871
4052
  async function readText(root, file) {
3872
4053
  try {
@@ -3899,7 +4080,7 @@ async function applyEdit(root, edit) {
3899
4080
  return true;
3900
4081
  }
3901
4082
  const file = await storePath(root, edit.path, true);
3902
- const temporary = path22.join(path22.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
4083
+ const temporary = path23.join(path23.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
3903
4084
  try {
3904
4085
  const mode = await fs18.stat(file).then((s) => s.mode & 511, () => 384);
3905
4086
  const handle = await fs18.open(temporary, "wx", mode);
@@ -4344,14 +4525,14 @@ async function planTeardown(root, selected) {
4344
4525
  const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
4345
4526
  const automationBefore = await read(AUTOMATION_PATH);
4346
4527
  const automation = automationBefore === null ? null : recordSchema2.parse(JSON.parse(automationBefore));
4347
- const commands = (host) => new Set([
4528
+ const commands2 = (host) => new Set([
4348
4529
  automation?.hosts[host]?.command,
4349
4530
  ...ownership.files[configPath(host)]?.hookCommands ?? [],
4350
4531
  ...knownManagedHookCommands(host),
4351
4532
  hookConfig(host, hookCommand(host)).hooks.SessionStart[0].hooks[0].command,
4352
4533
  hookConfig(host).hooks.SessionStart[0].hooks[0].command
4353
4534
  ].filter((command2) => !!command2));
4354
- const possibleHook = (command2, host) => commands(host).has(command2) || /\bmason(?:-auto|-hook)?\b/.test(command2);
4535
+ const possibleHook = (command2, host) => commands2(host).has(command2) || /\bmason(?:-auto|-hook)?\b/.test(command2);
4355
4536
  const add = (file, before, after, empty) => {
4356
4537
  edits.push({ path: file, before, after: ownership.files[file]?.created && (empty || ownership.files[file].initialHash === hash(before)) ? null : after });
4357
4538
  };
@@ -4415,12 +4596,12 @@ async function planTeardown(root, selected) {
4415
4596
  for (const [event, groups] of Object.entries(config.hooks ?? {})) {
4416
4597
  const known = ownership.files[hookFile]?.hooks?.[event];
4417
4598
  const handlers = groups.flatMap((group) => group.hooks);
4418
- if (known && !handlers.some((handler) => handler.type === "command" && handler.command && commands(host).has(handler.command)) && handlers.some((handler) => !known.includes(hash(handler)))) {
4599
+ if (known && !handlers.some((handler) => handler.type === "command" && handler.command && commands2(host).has(handler.command)) && handlers.some((handler) => !known.includes(hash(handler)))) {
4419
4600
  diagnostics.push({ path: hookFile, message: "A recorded Mason hook is missing beside a new or edited handler (" + event + "); inspect ownership manually." });
4420
4601
  }
4421
4602
  const kept = groups.flatMap((group) => {
4422
4603
  const handlers2 = group.hooks.filter((handler) => {
4423
- if (handler.type === "command" && handler.command && commands(host).has(handler.command)) {
4604
+ if (handler.type === "command" && handler.command && commands2(host).has(handler.command)) {
4424
4605
  changed = true;
4425
4606
  return false;
4426
4607
  }
@@ -4552,13 +4733,13 @@ var init_teardown = __esm({
4552
4733
  });
4553
4734
 
4554
4735
  // src/review/cochange.ts
4555
- import { execFile as execFile11 } from "child_process";
4556
- import { promisify as promisify11 } from "util";
4736
+ import { execFile as execFile3 } from "child_process";
4737
+ import { promisify as promisify3 } from "util";
4557
4738
  async function buildMatrix(resolvedRoot) {
4558
4739
  try {
4559
- const { stdout: shallow } = await exec8("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
4740
+ const { stdout: shallow } = await exec("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
4560
4741
  if (shallow.trim() === "true") return null;
4561
- const { stdout } = await exec8(
4742
+ const { stdout } = await exec(
4562
4743
  "git",
4563
4744
  ["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
4564
4745
  { cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
@@ -4614,11 +4795,11 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
4614
4795
  findings.sort((a, b) => b.rate - a.rate || b.sharedCommits - a.sharedCommits);
4615
4796
  return findings;
4616
4797
  }
4617
- var exec8, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
4798
+ var exec, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
4618
4799
  var init_cochange = __esm({
4619
4800
  "src/review/cochange.ts"() {
4620
4801
  "use strict";
4621
- exec8 = promisify11(execFile11);
4802
+ exec = promisify3(execFile3);
4622
4803
  HISTORY_COMMITS = 1500;
4623
4804
  MIN_FILE_COMMITS = 5;
4624
4805
  MIN_SHARED_COMMITS = 4;
@@ -4926,10 +5107,10 @@ var init_check = __esm({
4926
5107
  });
4927
5108
 
4928
5109
  // src/review/evidence.ts
4929
- import path23 from "path";
5110
+ import path24 from "path";
4930
5111
  import { z as z22 } from "zod";
4931
5112
  function relativeArtifact(root, file) {
4932
- const relative = path23.isAbsolute(file) ? path23.relative(root, file) : file;
5113
+ const relative = path24.isAbsolute(file) ? path24.relative(root, file) : file;
4933
5114
  const normalized = normalizeRepoPath(relative);
4934
5115
  if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
4935
5116
  return normalized;
@@ -5026,7 +5207,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5026
5207
  const report = await readStoreJson(root, file);
5027
5208
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
5028
5209
  const sourceRoot = check.sourceRoot ?? root;
5029
- if (!path23.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5210
+ if (!path24.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5030
5211
  const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
5031
5212
  result.outcome = parsed.outcome;
5032
5213
  result.counts = parsed.counts;
@@ -5148,12 +5329,12 @@ var init_evidence2 = __esm({
5148
5329
 
5149
5330
  // src/review/review.ts
5150
5331
  import fs19 from "fs/promises";
5151
- import path24 from "path";
5152
- import { execFile as execFile12 } from "child_process";
5153
- import { promisify as promisify12 } from "util";
5332
+ import path25 from "path";
5333
+ import { execFile as execFile4 } from "child_process";
5334
+ import { promisify as promisify4 } from "util";
5154
5335
  async function resolveMergeBase(resolvedRoot, base, head) {
5155
5336
  try {
5156
- const { stdout } = await exec9("git", ["merge-base", base, head], {
5337
+ const { stdout } = await exec2("git", ["merge-base", base, head], {
5157
5338
  cwd: resolvedRoot
5158
5339
  });
5159
5340
  return stdout.trim() || null;
@@ -5164,7 +5345,7 @@ async function resolveMergeBase(resolvedRoot, base, head) {
5164
5345
  async function defaultBase(resolvedRoot) {
5165
5346
  for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
5166
5347
  try {
5167
- await exec9("git", ["rev-parse", "--verify", "--quiet", ref], {
5348
+ await exec2("git", ["rev-parse", "--verify", "--quiet", ref], {
5168
5349
  cwd: resolvedRoot
5169
5350
  });
5170
5351
  return ref;
@@ -5177,7 +5358,7 @@ function anchorsTouched(record, changedFiles) {
5177
5358
  return matchingPaths(record.files, changedFiles);
5178
5359
  }
5179
5360
  async function computeReview(rootDir, base, options = {}) {
5180
- const resolvedRoot = path24.resolve(rootDir);
5361
+ const resolvedRoot = path25.resolve(rootDir);
5181
5362
  const head = await getCurrentGitHash(resolvedRoot);
5182
5363
  if (head === "unknown") return null;
5183
5364
  const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
@@ -5227,7 +5408,7 @@ async function computeReview(rootDir, base, options = {}) {
5227
5408
  analyzed,
5228
5409
  async (relPath) => {
5229
5410
  try {
5230
- await fs19.access(path24.join(resolvedRoot, relPath));
5411
+ await fs19.access(path25.join(resolvedRoot, relPath));
5231
5412
  return true;
5232
5413
  } catch {
5233
5414
  return false;
@@ -5260,7 +5441,7 @@ async function computeReview(rootDir, base, options = {}) {
5260
5441
  }
5261
5442
  return finalize();
5262
5443
  }
5263
- var exec9, MAX_ANALYZED_FILES;
5444
+ var exec2, MAX_ANALYZED_FILES;
5264
5445
  var init_review = __esm({
5265
5446
  "src/review/review.ts"() {
5266
5447
  "use strict";
@@ -5272,7 +5453,7 @@ var init_review = __esm({
5272
5453
  init_cochange();
5273
5454
  init_evidence2();
5274
5455
  init_snapshot();
5275
- exec9 = promisify12(execFile12);
5456
+ exec2 = promisify4(execFile4);
5276
5457
  MAX_ANALYZED_FILES = 50;
5277
5458
  }
5278
5459
  });
@@ -5607,7 +5788,7 @@ var init_setup = __esm({
5607
5788
 
5608
5789
  // src/distribution/bundle.ts
5609
5790
  import fs20 from "fs/promises";
5610
- import path25 from "path";
5791
+ import path26 from "path";
5611
5792
  import { createHash as createHash4 } from "crypto";
5612
5793
  import { z as z23 } from "zod";
5613
5794
  async function readBundle(root) {
@@ -5615,7 +5796,7 @@ async function readBundle(root) {
5615
5796
  const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
5616
5797
  const windows = manifest.target.startsWith("win32-");
5617
5798
  for (const file of Object.keys(manifest.files)) {
5618
- if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path25.isAbsolute(file)) {
5799
+ if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path26.isAbsolute(file)) {
5619
5800
  throw new Error("Unsafe path in Mason bundle: " + file);
5620
5801
  }
5621
5802
  }
@@ -5655,16 +5836,16 @@ var init_bundle = __esm({
5655
5836
  // src/distribution/path.ts
5656
5837
  import fs21 from "fs/promises";
5657
5838
  import os3 from "os";
5658
- import path26 from "path";
5659
- import { execFile as execFile13 } from "child_process";
5660
- import { promisify as promisify13 } from "util";
5839
+ import path27 from "path";
5840
+ import { execFile as execFile5 } from "child_process";
5841
+ import { promisify as promisify5 } from "util";
5661
5842
  import { createHash as createHash5, randomUUID as randomUUID7 } from "crypto";
5662
5843
  import { z as z24 } from "zod";
5663
5844
  async function configurePath(bin, previous, save, options = {}) {
5664
5845
  const env = options.env ?? process.env, platform = options.platform ?? process.platform;
5665
5846
  const changes = [...previous];
5666
5847
  const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
5667
- const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path26.posix.isAbsolute(entry) && path26.posix.resolve(entry) === path26.posix.resolve(bin));
5848
+ const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path27.posix.isAbsolute(entry) && path27.posix.resolve(entry) === path27.posix.resolve(bin));
5668
5849
  const setupHint = "To connect a project, run one of these inside its directory:\n Codex: mason setup --host codex\n Claude Code: mason setup --host claude";
5669
5850
  const message2 = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
5670
5851
  const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
@@ -5684,15 +5865,15 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
5684
5865
  }
5685
5866
  return { status: "configured", changes, message: message2 };
5686
5867
  }
5687
- const shell = path26.basename(env.SHELL ?? os3.userInfo().shell ?? "");
5868
+ const shell = path27.basename(env.SHELL ?? os3.userInfo().shell ?? "");
5688
5869
  const userHome = options.userHome ?? os3.homedir();
5689
- let profiles;
5870
+ let profiles2;
5690
5871
  if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
5691
- if (env.MASON_PROFILE) profiles = [path26.resolve(env.MASON_PROFILE)];
5692
- else if (shell === "zsh") profiles = [path26.join(env.ZDOTDIR || userHome, ".zshrc")];
5693
- else if (shell === "sh") profiles = [path26.join(userHome, ".profile")];
5872
+ if (env.MASON_PROFILE) profiles2 = [path27.resolve(env.MASON_PROFILE)];
5873
+ else if (shell === "zsh") profiles2 = [path27.join(env.ZDOTDIR || userHome, ".zshrc")];
5874
+ else if (shell === "sh") profiles2 = [path27.join(userHome, ".profile")];
5694
5875
  else {
5695
- const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path26.join(userHome, name));
5876
+ const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path27.join(userHome, name));
5696
5877
  let login = candidates[2];
5697
5878
  for (const file of candidates) if (await fs21.lstat(file).then(() => true, (error) => {
5698
5879
  if (error.code === "ENOENT") return false;
@@ -5701,15 +5882,15 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
5701
5882
  login = file;
5702
5883
  break;
5703
5884
  }
5704
- profiles = [path26.join(userHome, ".bashrc"), login];
5885
+ profiles2 = [path27.join(userHome, ".bashrc"), login];
5705
5886
  }
5706
5887
  const marker = `# >>> mason PATH ${createHash5("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
5707
- for (const profile of profiles) {
5888
+ for (const profile of profiles2) {
5708
5889
  const stat = await fs21.lstat(profile).catch((error) => {
5709
5890
  if (error.code === "ENOENT") return null;
5710
5891
  throw error;
5711
5892
  });
5712
- const file = stat?.isSymbolicLink() ? await fs21.realpath(profile) : path26.resolve(profile);
5893
+ const file = stat?.isSymbolicLink() ? await fs21.realpath(profile) : path27.resolve(profile);
5713
5894
  const original = await fs21.readFile(file).catch((error) => {
5714
5895
  if (error.code === "ENOENT" && !stat) return null;
5715
5896
  throw error;
@@ -5725,7 +5906,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
5725
5906
  changes.push({ kind: "profile", file, block, created: original === null });
5726
5907
  await save(changes);
5727
5908
  }
5728
- await fs21.mkdir(path26.dirname(file), { recursive: true });
5909
+ await fs21.mkdir(path27.dirname(file), { recursive: true });
5729
5910
  const handle = await fs21.open(file, original === null ? "ax+" : "a+", 384);
5730
5911
  try {
5731
5912
  if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
@@ -5813,20 +5994,20 @@ try {
5813
5994
  @{ value = $next; kind = $kind } | ConvertTo-Json -Compress
5814
5995
  } finally { $key.Dispose() }
5815
5996
  `;
5816
- const { stdout } = await execute3("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { windowsHide: true, timeout: 15e3, maxBuffer: 1024 * 1024 });
5997
+ const { stdout } = await execute2("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { windowsHide: true, timeout: 15e3, maxBuffer: 1024 * 1024 });
5817
5998
  return JSON.parse(stdout.replace(/^\uFEFF/, "").trim());
5818
5999
  }
5819
- var execute3, pathChangeSchema, quote, normalizeWindowsEntry, hasWindowsEntry;
6000
+ var execute2, pathChangeSchema, quote, normalizeWindowsEntry, hasWindowsEntry;
5820
6001
  var init_path = __esm({
5821
6002
  "src/distribution/path.ts"() {
5822
6003
  "use strict";
5823
- execute3 = promisify13(execFile13);
6004
+ execute2 = promisify5(execFile5);
5824
6005
  pathChangeSchema = z24.discriminatedUnion("kind", [
5825
6006
  z24.object({ kind: z24.literal("profile"), file: z24.string(), block: z24.string(), created: z24.boolean() }),
5826
6007
  z24.object({ kind: z24.literal("windows"), entry: z24.string(), created: z24.boolean() })
5827
6008
  ]);
5828
6009
  quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
5829
- normalizeWindowsEntry = (value) => path26.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
6010
+ normalizeWindowsEntry = (value) => path27.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
5830
6011
  hasWindowsEntry = (value, entry, env = process.env) => {
5831
6012
  const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
5832
6013
  const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
@@ -5846,7 +6027,7 @@ __export(install_exports2, {
5846
6027
  });
5847
6028
  import fs22 from "fs/promises";
5848
6029
  import os4 from "os";
5849
- import path27 from "path";
6030
+ import path28 from "path";
5850
6031
  import { fileURLToPath } from "url";
5851
6032
  import { spawn } from "child_process";
5852
6033
  import { randomUUID as randomUUID8 } from "crypto";
@@ -5863,16 +6044,16 @@ async function replace(file, content2, mode = 384) {
5863
6044
  async function installStandalone(source, progress2) {
5864
6045
  progress2?.step("Checking installation files and location");
5865
6046
  const bundle = await verifyBundle(source);
5866
- const selectedHome = path27.resolve(defaultHome());
6047
+ const selectedHome = path28.resolve(defaultHome());
5867
6048
  await fs22.mkdir(selectedHome, { recursive: true });
5868
6049
  const home = await fs22.realpath(selectedHome);
5869
- const selectedBin = path27.resolve(process.env.MASON_BIN_DIR ?? (process.platform === "win32" ? path27.join(home, "bin") : path27.join(os4.homedir(), ".local/bin")));
6050
+ const selectedBin = path28.resolve(process.env.MASON_BIN_DIR ?? (process.platform === "win32" ? path28.join(home, "bin") : path28.join(os4.homedir(), ".local/bin")));
5870
6051
  return withLock(home, ".install-lock", async () => {
5871
6052
  const raw = await readStoreJson(home, "install.json");
5872
6053
  const previous = raw === null ? null : recordSchema3.parse(raw);
5873
6054
  if (!previous) for (const file of await fs22.readdir(home)) {
5874
6055
  if (file === ".install-lock") continue;
5875
- const directory = path27.join(home, file);
6056
+ const directory = path28.join(home, file);
5876
6057
  const stat = await fs22.lstat(directory);
5877
6058
  if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs22.readdir(directory)).length) {
5878
6059
  throw new Error("Mason installation directory is not empty and has no ownership receipt.");
@@ -5890,10 +6071,10 @@ $previousToken = $env:MASON_UNINSTALL_TOKEN
5890
6071
  $token = [Guid]::NewGuid().ToString()
5891
6072
  $env:MASON_UNINSTALL_TOKEN = $token
5892
6073
  try {
5893
- & ${psQuote(path27.join(destination, "node.exe"))} ${psQuote(path27.join(destination, "app/dist/mason.js"))} @args
6074
+ & ${psQuote(path28.join(destination, "node.exe"))} ${psQuote(path28.join(destination, "app/dist/mason.js"))} @args
5894
6075
  $code = $LASTEXITCODE
5895
6076
  if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
5896
- if ([IO.File]::ReadAllText(${psQuote(path27.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
6077
+ if ([IO.File]::ReadAllText(${psQuote(path28.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
5897
6078
  Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
5898
6079
  Write-Output ${psQuote("Removed standalone installation: " + home)}
5899
6080
  }
@@ -5902,7 +6083,7 @@ try {
5902
6083
  finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
5903
6084
  `
5904
6085
  } : { mason: `#!/bin/sh
5905
- exec ${shQuote(path27.join(destination, "node"))} ${shQuote(path27.join(destination, "app/dist/mason.js"))} "$@"
6086
+ exec ${shQuote(path28.join(destination, "node"))} ${shQuote(path28.join(destination, "app/dist/mason.js"))} "$@"
5906
6087
  ` };
5907
6088
  for (const [name, content2] of Object.entries(launchers)) {
5908
6089
  const file = await storePath(bin, name);
@@ -5958,12 +6139,12 @@ exec ${shQuote(path27.join(destination, "node"))} ${shQuote(path27.join(destinat
5958
6139
  });
5959
6140
  }
5960
6141
  async function currentInstallation() {
5961
- const app = path27.dirname(path27.dirname(fileURLToPath(import.meta.url)));
5962
- const bundle = path27.dirname(app), home = path27.dirname(path27.dirname(bundle));
6142
+ const app = path28.dirname(path28.dirname(fileURLToPath(import.meta.url)));
6143
+ const bundle = path28.dirname(app), home = path28.dirname(path28.dirname(bundle));
5963
6144
  const raw = await readStoreJson(home, "install.json");
5964
6145
  if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
5965
6146
  const record = recordSchema3.parse(raw);
5966
- if (record.home !== await fs22.realpath(home) || path27.dirname(bundle) !== path27.join(home, "versions")) throw new Error("Invalid standalone installation receipt.");
6147
+ if (record.home !== await fs22.realpath(home) || path28.dirname(bundle) !== path28.join(home, "versions")) throw new Error("Invalid standalone installation receipt.");
5967
6148
  return { home, bundle, record };
5968
6149
  }
5969
6150
  async function upgradeStandalone(version, progress2) {
@@ -5972,7 +6153,7 @@ async function upgradeStandalone(version, progress2) {
5972
6153
  const { home, bundle, record } = await currentInstallation();
5973
6154
  await verifyBundle(bundle);
5974
6155
  const windows = process.platform === "win32";
5975
- const script = path27.join(bundle, windows ? "install.ps1" : "install.sh");
6156
+ const script = path28.join(bundle, windows ? "install.ps1" : "install.sh");
5976
6157
  progress2?.stop();
5977
6158
  return new Promise((resolve, reject) => {
5978
6159
  const child = spawn(
@@ -6029,7 +6210,7 @@ var init_install2 = __esm({
6029
6210
  psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
6030
6211
  WINDOWS_BATCH_LAUNCHER = '@echo off\r\ngoto #_mason_handoff_# 2>NUL || title %COMSPEC% & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0mason-launcher.ps1" %*\r\n';
6031
6212
  launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
6032
- defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path27.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path27.join(os4.homedir(), ".local/share/mason"));
6213
+ defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path28.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path28.join(os4.homedir(), ".local/share/mason"));
6033
6214
  }
6034
6215
  });
6035
6216
 
@@ -6094,6 +6275,7 @@ function createProgress(options = {}) {
6094
6275
  }
6095
6276
 
6096
6277
  // src/automation/cli.ts
6278
+ init_profile();
6097
6279
  var TEARDOWN_USAGE = `Usage: mason teardown [options]
6098
6280
 
6099
6281
  Disconnect Mason from this Git project. Defaults to all project hosts.
@@ -6121,6 +6303,7 @@ var USAGE = `Usage: mason-auto <setup|teardown|install|config|status|check|hook>
6121
6303
  --command <prefix> Installed executable prefix for install/config
6122
6304
  --dry-run Preview teardown without writing
6123
6305
  --json Machine-readable output (status also uses JSON when piped)
6306
+ --profile Local phase timings on stderr for check/hook; no file contents
6124
6307
 
6125
6308
  check exits 0 for verified checks, 1 for issues, 2 for incomplete/unavailable.
6126
6309
  teardown exits 0 for complete/no-op, 2 when cleanup needs attention (also in dry runs).
@@ -6132,7 +6315,8 @@ var parseCli = (argv) => parseArgs({ args: argv, allowPositionals: true, options
6132
6315
  command: { type: "string" },
6133
6316
  json: { type: "boolean" },
6134
6317
  help: { type: "boolean", short: "h" },
6135
- "dry-run": { type: "boolean" }
6318
+ "dry-run": { type: "boolean" },
6319
+ profile: { type: "boolean" }
6136
6320
  } });
6137
6321
  function isHookCommand(argv) {
6138
6322
  try {
@@ -6157,6 +6341,8 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
6157
6341
  }
6158
6342
  if (positionals.length !== 1) throw new Error("Expected one command.");
6159
6343
  [action] = positionals;
6344
+ if (values.profile && !["check", "hook"].includes(action)) throw new Error("--profile applies only to check/hook.");
6345
+ const measured = (run) => values.profile ? withProfile(run, io.err) : run();
6160
6346
  const dir = values.dir ?? process.cwd();
6161
6347
  if (values["dry-run"] !== void 0 && action !== "teardown") throw new Error("--dry-run applies only to teardown.");
6162
6348
  if (action === "teardown") {
@@ -6169,7 +6355,7 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
6169
6355
  if (action === "hook") {
6170
6356
  const host = hostSchema.parse(values.host);
6171
6357
  if (options.managedHost && options.managedHost !== host) throw new Error("Managed hook --host does not match its launcher.");
6172
- const output = await runAutomationHook(host, stdin, { managed: !!options.managedHost });
6358
+ const output = await measured(() => runAutomationHook(host, stdin, { managed: !!options.managedHost }));
6173
6359
  if (output) io.out(JSON.stringify(output));
6174
6360
  return 0;
6175
6361
  }
@@ -6200,7 +6386,7 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
6200
6386
  return 0;
6201
6387
  }
6202
6388
  if (action !== "check") throw new Error("Unknown automation command: " + action);
6203
- const { report } = await automate(dir, { event: "task_end" });
6389
+ const { report } = await measured(() => automate(dir, { event: "task_end" }));
6204
6390
  io.out(values.json ? JSON.stringify(report, null, 2) : summarize(report));
6205
6391
  return report.status === "verified" ? 0 : report.status === "issues-remain" ? 1 : 2;
6206
6392
  } catch (error) {
@@ -6221,7 +6407,7 @@ var inputArgs = process.argv.slice(2);
6221
6407
  var managedLaunch = inputArgs[0] === "--setup-host";
6222
6408
  var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
6223
6409
  var [command, ...args] = inputArgs;
6224
- var usage = `Mason ${"0.17.2"}
6410
+ var usage = `Mason ${"0.17.4"}
6225
6411
 
6226
6412
  Usage: mason <command> [options]
6227
6413
 
@@ -6247,9 +6433,9 @@ try {
6247
6433
  await prepareLaunch2(setupHost, { allowInactive: command === "mcp" });
6248
6434
  }
6249
6435
  }
6250
- if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.17.2" }));
6436
+ if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.17.4" }));
6251
6437
  else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
6252
- else if (["--version", "-v"].includes(command)) console.log("0.17.2");
6438
+ else if (["--version", "-v"].includes(command)) console.log("0.17.4");
6253
6439
  else if (command === "internal-install") {
6254
6440
  if (args.length) throw new Error("internal-install takes no arguments.");
6255
6441
  const { installStandalone: installStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));