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-mcp.js CHANGED
@@ -9,6 +9,54 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/utils/profile.ts
13
+ import { AsyncLocalStorage } from "async_hooks";
14
+ async function profilePhase(label, run) {
15
+ const phases = profiles.getStore();
16
+ if (!phases) return run();
17
+ const start = performance.now();
18
+ try {
19
+ return await run();
20
+ } finally {
21
+ const timing = phases[label] ??= { calls: 0, totalMs: 0 };
22
+ timing.calls++;
23
+ timing.totalMs += performance.now() - start;
24
+ }
25
+ }
26
+ var profiles;
27
+ var init_profile = __esm({
28
+ "src/utils/profile.ts"() {
29
+ "use strict";
30
+ profiles = new AsyncLocalStorage();
31
+ }
32
+ });
33
+
34
+ // src/utils/git-read.ts
35
+ import { execFile } from "child_process";
36
+ import { promisify } from "util";
37
+ function execGit(args, options) {
38
+ const command = args.find((arg) => !arg.startsWith("-")) ?? "other";
39
+ return profilePhase("git." + (commands.has(command) ? command : "other"), async () => {
40
+ const { input, ...processOptions } = options;
41
+ const operation = execute("git", args, processOptions);
42
+ if (input !== void 0) {
43
+ operation.child.stdin.on("error", () => {
44
+ });
45
+ operation.child.stdin.end(input);
46
+ }
47
+ return operation;
48
+ });
49
+ }
50
+ var execute, commands;
51
+ var init_git_read = __esm({
52
+ "src/utils/git-read.ts"() {
53
+ "use strict";
54
+ init_profile();
55
+ execute = promisify(execFile);
56
+ commands = /* @__PURE__ */ new Set(["rev-parse", "symbolic-ref", "for-each-ref", "ls-files", "check-ignore", "status", "log", "diff", "ls-tree", "show", "merge-base"]);
57
+ }
58
+ });
59
+
12
60
  // src/audit/commands.ts
13
61
  import path from "path";
14
62
  function splitCommands(line) {
@@ -233,14 +281,14 @@ function extractClaims(content2) {
233
281
  const ignored = computeIgnoredLines(lines);
234
282
  const paths = /* @__PURE__ */ new Map();
235
283
  const counts = [];
236
- const commands = /* @__PURE__ */ new Map();
284
+ const commands2 = /* @__PURE__ */ new Map();
237
285
  const addPath = (claim) => {
238
286
  const key = [claim.relativeTo ?? "scope", claim.path].join(":");
239
287
  if (!paths.has(key)) paths.set(key, claim);
240
288
  };
241
289
  const addCommand = (claim) => {
242
290
  const key = JSON.stringify([claim.scriptName, claim.directory, claim.scopeUnknown]);
243
- if (!commands.has(key)) commands.set(key, claim);
291
+ if (!commands2.has(key)) commands2.set(key, claim);
244
292
  };
245
293
  const parsed = markdown.parse(lines.map((line, i) => ignored[i] ? "" : line).join("\n"), {});
246
294
  for (const token of parsed) {
@@ -342,7 +390,7 @@ function extractClaims(content2) {
342
390
  return {
343
391
  paths: [...paths.values()],
344
392
  counts,
345
- commands: [...commands.values()]
393
+ commands: [...commands2.values()]
346
394
  };
347
395
  }
348
396
  var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
@@ -392,97 +440,6 @@ var init_claims = __esm({
392
440
  }
393
441
  });
394
442
 
395
- // src/audit/git.ts
396
- import { execFile } from "child_process";
397
- import { promisify } from "util";
398
- function parseCommitLine(line) {
399
- const parts = line.split(" ");
400
- if (parts.length < 3 || !parts[0]) return null;
401
- return { hash: parts[0], date: parts[1], subject: parts.slice(2).join(" ") };
402
- }
403
- async function lastCommitOf(resolvedRoot, relPath) {
404
- try {
405
- const { stdout } = await exec(
406
- "git",
407
- ["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
408
- { cwd: resolvedRoot }
409
- );
410
- const line = stdout.trim().split("\n")[0];
411
- return line ? parseCommitLine(line) : null;
412
- } catch {
413
- return null;
414
- }
415
- }
416
- async function deletingCommitOf(resolvedRoot, relPath) {
417
- try {
418
- const { stdout } = await exec(
419
- "git",
420
- [
421
- "log",
422
- "-1",
423
- "--diff-filter=D",
424
- `--format=${COMMIT_FORMAT}`,
425
- "--",
426
- `:(literal)${relPath}`
427
- ],
428
- { cwd: resolvedRoot }
429
- );
430
- const line = stdout.trim().split("\n")[0];
431
- return line ? parseCommitLine(line) : null;
432
- } catch {
433
- return null;
434
- }
435
- }
436
- async function firstCommitOf(resolvedRoot, relPath) {
437
- try {
438
- const { stdout } = await exec(
439
- "git",
440
- ["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
441
- { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
442
- );
443
- const line = stdout.trim().split("\n")[0];
444
- return line ? parseCommitLine(line) : null;
445
- } catch {
446
- return null;
447
- }
448
- }
449
- async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
450
- if (!fromHash || fromHash === "unknown") return null;
451
- try {
452
- const { stdout } = await exec(
453
- "git",
454
- [
455
- "log",
456
- `${fromHash}..HEAD`,
457
- `--format=%x01${COMMIT_FORMAT}`,
458
- "--name-only",
459
- "--",
460
- ...pathspecs
461
- ],
462
- { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
463
- );
464
- const commits = [];
465
- for (const block of stdout.split("")) {
466
- if (!block.trim()) continue;
467
- const lines = block.split("\n").filter((l) => l.trim().length > 0);
468
- const ref = parseCommitLine(lines[0]);
469
- if (!ref) continue;
470
- commits.push({ ...ref, files: lines.slice(1).map((l) => l.trim()) });
471
- }
472
- return { commits, total: commits.length };
473
- } catch {
474
- return null;
475
- }
476
- }
477
- var exec, COMMIT_FORMAT;
478
- var init_git = __esm({
479
- "src/audit/git.ts"() {
480
- "use strict";
481
- exec = promisify(execFile);
482
- COMMIT_FORMAT = "%H%x09%cI%x09%s";
483
- }
484
- });
485
-
486
443
  // src/utils/paths.ts
487
444
  import path2 from "path";
488
445
  function normalizeRepoPath(value) {
@@ -517,8 +474,6 @@ var init_paths = __esm({
517
474
  import fs from "fs/promises";
518
475
  import { constants } from "fs";
519
476
  import path3 from "path";
520
- import { execFile as execFile2 } from "child_process";
521
- import { promisify as promisify2 } from "util";
522
477
  import fg from "fast-glob";
523
478
  function isSensitiveFile(file) {
524
479
  return file.split(/[\\/]/).some(
@@ -572,12 +527,12 @@ async function createFileAccess(rootDir) {
572
527
  const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
573
528
  let gitFiles = null;
574
529
  try {
575
- const { stdout } = await exec2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
530
+ const { stdout } = await execGit(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
576
531
  gitFiles = new Set(stdout.split("\0").filter(Boolean));
577
532
  } catch {
578
533
  let inGit = false;
579
534
  try {
580
- await exec2("git", ["rev-parse", "--git-dir"], { cwd: root });
535
+ await execGit(["rev-parse", "--git-dir"], { cwd: root });
581
536
  inGit = true;
582
537
  } catch {
583
538
  }
@@ -620,12 +575,12 @@ async function createFileAccess(rootDir) {
620
575
  }
621
576
  return { root, config, list, read };
622
577
  }
623
- var exec2, SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
578
+ var SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
624
579
  var init_files = __esm({
625
580
  "src/utils/files.ts"() {
626
581
  "use strict";
582
+ init_git_read();
627
583
  init_paths();
628
- exec2 = promisify2(execFile2);
629
584
  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"];
630
585
  SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(",")}}`;
631
586
  SOURCE_IGNORE = [
@@ -655,9 +610,140 @@ var init_files = __esm({
655
610
  }
656
611
  });
657
612
 
613
+ // src/audit/inspection.ts
614
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
615
+ import path4 from "path";
616
+ function withRepositoryInspection(root, run) {
617
+ return inspections.run({ root: path4.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
618
+ }
619
+ function inspectionRead(root, key, read) {
620
+ const inspection = inspections.getStore();
621
+ if (!inspection || inspection.root !== path4.resolve(root)) return read();
622
+ if (!inspection.reads.has(key)) {
623
+ const pending = read();
624
+ inspection.reads.set(key, pending);
625
+ void pending.catch(() => {
626
+ if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
627
+ });
628
+ }
629
+ return inspection.reads.get(key);
630
+ }
631
+ function inspectionGit(args, options) {
632
+ return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
633
+ }
634
+ async function auditGitPaths(root, kind) {
635
+ const shared = inspections.getStore()?.root === path4.resolve(root);
636
+ const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
637
+ const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
638
+ const { stdout } = await inspectionGit(
639
+ ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
640
+ { cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
641
+ );
642
+ const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? /^(readme|agents|claude)\.md$/i.test(path4.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
643
+ if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
644
+ return paths.sort();
645
+ }
646
+ var inspections, sourceSpecs, docSpecs, sourceSuffixes;
647
+ var init_inspection = __esm({
648
+ "src/audit/inspection.ts"() {
649
+ "use strict";
650
+ init_git_read();
651
+ init_files();
652
+ inspections = new AsyncLocalStorage2();
653
+ sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
654
+ docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
655
+ sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
656
+ }
657
+ });
658
+
659
+ // src/audit/git.ts
660
+ function parseCommitLine(line) {
661
+ const parts = line.split(" ");
662
+ if (parts.length < 3 || !parts[0]) return null;
663
+ return { hash: parts[0], date: parts[1], subject: parts.slice(2).join(" ") };
664
+ }
665
+ async function lastCommitOf(resolvedRoot, relPath) {
666
+ try {
667
+ const { stdout } = await inspectionGit(
668
+ ["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
669
+ { cwd: resolvedRoot }
670
+ );
671
+ const line = stdout.trim().split("\n")[0];
672
+ return line ? parseCommitLine(line) : null;
673
+ } catch {
674
+ return null;
675
+ }
676
+ }
677
+ async function deletingCommitOf(resolvedRoot, relPath) {
678
+ try {
679
+ const { stdout } = await inspectionGit(
680
+ [
681
+ "log",
682
+ "-1",
683
+ "--diff-filter=D",
684
+ `--format=${COMMIT_FORMAT}`,
685
+ "--",
686
+ `:(literal)${relPath}`
687
+ ],
688
+ { cwd: resolvedRoot }
689
+ );
690
+ const line = stdout.trim().split("\n")[0];
691
+ return line ? parseCommitLine(line) : null;
692
+ } catch {
693
+ return null;
694
+ }
695
+ }
696
+ async function firstCommitOf(resolvedRoot, relPath) {
697
+ try {
698
+ const { stdout } = await inspectionGit(
699
+ ["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
700
+ { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
701
+ );
702
+ const line = stdout.trim().split("\n")[0];
703
+ return line ? parseCommitLine(line) : null;
704
+ } catch {
705
+ return null;
706
+ }
707
+ }
708
+ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
709
+ if (!fromHash || fromHash === "unknown") return null;
710
+ try {
711
+ const { stdout } = await inspectionGit(
712
+ [
713
+ "log",
714
+ `${fromHash}..HEAD`,
715
+ `--format=%x01${COMMIT_FORMAT}`,
716
+ "--name-only",
717
+ "--",
718
+ ...pathspecs
719
+ ],
720
+ { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
721
+ );
722
+ const commits = [];
723
+ for (const block of stdout.split("")) {
724
+ if (!block.trim()) continue;
725
+ const lines = block.split("\n").filter((l) => l.trim().length > 0);
726
+ const ref = parseCommitLine(lines[0]);
727
+ if (!ref) continue;
728
+ commits.push({ ...ref, files: lines.slice(1).map((l) => l.trim()) });
729
+ }
730
+ return { commits, total: commits.length };
731
+ } catch {
732
+ return null;
733
+ }
734
+ }
735
+ var COMMIT_FORMAT;
736
+ var init_git = __esm({
737
+ "src/audit/git.ts"() {
738
+ "use strict";
739
+ init_inspection();
740
+ COMMIT_FORMAT = "%H%x09%cI%x09%s";
741
+ }
742
+ });
743
+
658
744
  // src/utils/storage.ts
659
745
  import fs2 from "fs/promises";
660
- import path4 from "path";
746
+ import path5 from "path";
661
747
  import { randomUUID } from "crypto";
662
748
  async function storePath(root, relative, createParents = false) {
663
749
  const normalized = normalizeRepoPath(relative);
@@ -665,7 +751,7 @@ async function storePath(root, relative, createParents = false) {
665
751
  let current = await fs2.realpath(root);
666
752
  const parts = normalized.split("/");
667
753
  for (let i = 0; i < parts.length; i++) {
668
- current = path4.join(current, parts[i]);
754
+ current = path5.join(current, parts[i]);
669
755
  let stat;
670
756
  try {
671
757
  stat = await fs2.lstat(current);
@@ -703,7 +789,7 @@ async function writeStoreJson(root, relative, value) {
703
789
  throw new Error(`Mason store ${relative} exceeds 10 MiB`);
704
790
  }
705
791
  const file = await storePath(root, relative, true);
706
- const temporary = path4.join(path4.dirname(file), `.${path4.basename(file)}.${randomUUID()}.tmp`);
792
+ const temporary = path5.join(path5.dirname(file), `.${path5.basename(file)}.${randomUUID()}.tmp`);
707
793
  try {
708
794
  const handle = await fs2.open(temporary, "wx", 384);
709
795
  try {
@@ -728,9 +814,7 @@ var init_storage = __esm({
728
814
  // src/audit/inputs.ts
729
815
  import fs3 from "fs/promises";
730
816
  import * as nativeFs from "fs";
731
- import path5 from "path";
732
- import { execFile as execFile3 } from "child_process";
733
- import { promisify as promisify3 } from "util";
817
+ import path6 from "path";
734
818
  import fg2 from "fast-glob";
735
819
  async function auditInputPath(root, file) {
736
820
  if (file === ".") return fs3.realpath(root);
@@ -757,7 +841,7 @@ async function auditGlob(root, patterns, options) {
757
841
  if (Array.isArray(patterns) && !patterns.length) return [];
758
842
  const directories = /* @__PURE__ */ new Set();
759
843
  const readdir2 = ((directory, opts, callback) => {
760
- const relative = path5.relative(root, directory).split(path5.sep).join("/") || ".";
844
+ const relative = path6.relative(root, directory).split(path6.sep).join("/") || ".";
761
845
  directories.add(relative);
762
846
  if (directories.size > MAX_INPUTS) {
763
847
  callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
@@ -768,7 +852,7 @@ async function auditGlob(root, patterns, options) {
768
852
  }, callback);
769
853
  });
770
854
  const lstat2 = ((file, callback) => {
771
- const relative = path5.relative(root, file).split(path5.sep).join("/") || ".";
855
+ const relative = path6.relative(root, file).split(path6.sep).join("/") || ".";
772
856
  auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
773
857
  });
774
858
  const stream = fg2.stream(patterns, {
@@ -805,89 +889,71 @@ async function auditGlob(root, patterns, options) {
805
889
  return matches.sort();
806
890
  }
807
891
  async function gitSourcePaths(root) {
808
- const { stdout } = await execute(
809
- "git",
810
- [
811
- "ls-files",
812
- "-z",
813
- "--cached",
814
- "--others",
815
- "--exclude-standard",
816
- "--",
817
- ...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
818
- ],
819
- { cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
820
- );
821
- return [...new Set(stdout.split("\0").filter(Boolean))].sort();
892
+ return auditGitPaths(root, "sources");
822
893
  }
823
894
  async function gitIgnoredPaths(root, files) {
824
895
  if (!files.length) return /* @__PURE__ */ new Set();
825
- const operation = execute(
826
- "git",
827
- ["check-ignore", "-z", "--stdin"],
828
- { cwd: root, maxBuffer: 4 * 1024 * 1024, timeout: 1e4 }
829
- );
830
- operation.child.stdin.on("error", () => {
831
- });
832
- operation.child.stdin.end(files.join("\0") + "\0");
833
- try {
834
- return new Set((await operation).stdout.split("\0").filter(Boolean));
835
- } catch (error) {
836
- if (error.code === 1) return /* @__PURE__ */ new Set();
837
- throw error;
896
+ const ignored = /* @__PURE__ */ new Set();
897
+ for (let offset = 0; offset < files.length; offset += 1e3) {
898
+ try {
899
+ const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
900
+ cwd: root,
901
+ maxBuffer: 4 * 1024 * 1024,
902
+ timeout: 1e4,
903
+ input: files.slice(offset, offset + 1e3).join("\0") + "\0"
904
+ });
905
+ for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
906
+ } catch (error) {
907
+ if (error.code !== 1) throw error;
908
+ }
838
909
  }
910
+ return ignored;
839
911
  }
840
- var execute, MAX_INPUTS;
912
+ var MAX_INPUTS;
841
913
  var init_inputs = __esm({
842
914
  "src/audit/inputs.ts"() {
843
915
  "use strict";
844
916
  init_files();
845
917
  init_storage();
846
- execute = promisify3(execFile3);
918
+ init_git_read();
919
+ init_inspection();
847
920
  MAX_INPUTS = 1e5;
848
921
  }
849
922
  });
850
923
 
851
924
  // src/audit/docs.ts
852
925
  import fs4 from "fs/promises";
853
- import path6 from "path";
854
- import { execFile as execFile4 } from "child_process";
855
- import { promisify as promisify4 } from "util";
926
+ import path7 from "path";
856
927
  import fg3 from "fast-glob";
857
- async function isDirty(resolvedRoot, relPath) {
928
+ async function dirtyDocs(resolvedRoot, files) {
929
+ if (!files.length) return /* @__PURE__ */ new Set();
858
930
  try {
859
- const { stdout } = await exec3(
860
- "git",
861
- ["status", "--porcelain", "--", `:(literal)${relPath}`],
862
- { cwd: resolvedRoot }
863
- );
864
- return stdout.trim().length > 0;
931
+ const dirty = /* @__PURE__ */ new Set();
932
+ for (let offset = 0; offset < files.length; offset += 256) {
933
+ const { stdout } = await execGit(
934
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
935
+ { cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
936
+ );
937
+ const entries = stdout.split("\0");
938
+ for (let i = 0; i < entries.length; i++) {
939
+ if (!entries[i]) continue;
940
+ const status = entries[i].slice(0, 2);
941
+ dirty.add(entries[i].slice(3));
942
+ if (/[RC]/.test(status) && entries[i + 1]) dirty.add(entries[++i]);
943
+ }
944
+ }
945
+ return dirty;
865
946
  } catch {
866
- return true;
947
+ return new Set(files);
867
948
  }
868
949
  }
869
950
  async function discoverDocPaths(root) {
870
951
  let candidates = [];
871
952
  try {
872
- const { stdout } = await exec3(
873
- "git",
874
- [
875
- "ls-files",
876
- "-z",
877
- "--cached",
878
- "--others",
879
- "--exclude-standard",
880
- "--",
881
- ":(icase,glob)**/readme.md",
882
- ":(icase,glob)**/agents.md",
883
- ":(icase,glob)**/claude.md"
884
- ],
885
- { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
886
- );
887
- candidates = stdout.split("\0").filter(Boolean);
953
+ candidates = await auditGitPaths(root, "documents");
888
954
  } catch (error) {
889
955
  try {
890
- await exec3("git", ["rev-parse", "--git-dir"], { cwd: root });
956
+ await execGit(["rev-parse", "--git-dir"], { cwd: root });
891
957
  } catch {
892
958
  return localDocPaths(root);
893
959
  }
@@ -934,43 +1000,54 @@ async function localDocPaths(root) {
934
1000
  throw error;
935
1001
  }
936
1002
  for (const entry of entries) if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
937
- paths.push(path6.posix.join(dir, entry.name));
1003
+ paths.push(path7.posix.join(dir, entry.name));
938
1004
  }
939
1005
  }
940
1006
  return paths;
941
1007
  }
942
1008
  function documentScope(file) {
943
- const parent = path6.posix.dirname(file);
944
- return path6.posix.basename(parent).toLowerCase() === ".claude" ? path6.posix.dirname(parent) : parent;
1009
+ const parent = path7.posix.dirname(file);
1010
+ return path7.posix.basename(parent).toLowerCase() === ".claude" ? path7.posix.dirname(parent) : parent;
945
1011
  }
946
1012
  async function discoverDocs(resolvedRoot) {
1013
+ return profilePhase("audit.documents", () => readDocs(resolvedRoot));
1014
+ }
1015
+ async function readDocs(resolvedRoot) {
947
1016
  const docs = [];
948
- for (const candidate of await discoverDocPaths(resolvedRoot)) {
949
- const content2 = await readAuditInput(resolvedRoot, candidate);
950
- if (content2 === null) throw new Error("Document disappeared during discovery: " + candidate);
951
- const [lastCommit, dirty] = await Promise.all([lastCommitOf(resolvedRoot, candidate), isDirty(resolvedRoot, candidate)]);
952
- docs.push({
953
- path: candidate,
954
- content: content2,
955
- scope: documentScope(candidate),
956
- kind: /^readme\.md$/i.test(path6.posix.basename(candidate)) ? "readme" : "instructions",
957
- lineCount: content2.split("\n").length,
958
- lastCommit,
959
- dirty,
960
- claims: extractClaims(content2)
961
- });
1017
+ const files = await discoverDocPaths(resolvedRoot);
1018
+ const dirty = await dirtyDocs(resolvedRoot, files);
1019
+ for (let offset = 0; offset < files.length; offset += 4) {
1020
+ docs.push(...await Promise.all(files.slice(offset, offset + 4).map(async (candidate) => {
1021
+ const [content2, lastCommit] = await Promise.all([
1022
+ readAuditInput(resolvedRoot, candidate),
1023
+ profilePhase("audit.document-history", () => lastCommitOf(resolvedRoot, candidate))
1024
+ ]);
1025
+ if (content2 === null) throw new Error("Document disappeared during discovery: " + candidate);
1026
+ return {
1027
+ path: candidate,
1028
+ content: content2,
1029
+ scope: documentScope(candidate),
1030
+ kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : "instructions",
1031
+ lineCount: content2.split("\n").length,
1032
+ lastCommit,
1033
+ dirty: dirty.has(candidate),
1034
+ claims: extractClaims(content2)
1035
+ };
1036
+ })));
962
1037
  }
963
1038
  return docs;
964
1039
  }
965
- var exec3, DOC_CANDIDATES;
1040
+ var DOC_CANDIDATES;
966
1041
  var init_docs = __esm({
967
1042
  "src/audit/docs.ts"() {
968
1043
  "use strict";
1044
+ init_git_read();
1045
+ init_profile();
969
1046
  init_claims();
970
1047
  init_git();
971
1048
  init_inputs();
972
1049
  init_files();
973
- exec3 = promisify4(execFile4);
1050
+ init_inspection();
974
1051
  DOC_CANDIDATES = [
975
1052
  "AGENTS.md",
976
1053
  "CLAUDE.md",
@@ -981,14 +1058,14 @@ var init_docs = __esm({
981
1058
 
982
1059
  // src/audit/scope.ts
983
1060
  import fs5 from "fs/promises";
984
- import path7 from "path";
1061
+ import path8 from "path";
985
1062
  function scopedPath(directory, value) {
986
- if (path7.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
987
- const resolved = path7.posix.normalize(path7.posix.join(directory, value));
1063
+ if (path8.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
1064
+ const resolved = path8.posix.normalize(path8.posix.join(directory, value));
988
1065
  return resolved === ".." || resolved.startsWith("../") ? null : resolved;
989
1066
  }
990
1067
  function pathClaimScope(doc, claim) {
991
- const directory = claim.relativeTo === "document" ? path7.posix.dirname(doc) : documentScope(doc);
1068
+ const directory = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
992
1069
  const local = scopedPath(directory, claim.path);
993
1070
  if (!local) return null;
994
1071
  const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
@@ -1026,9 +1103,9 @@ var test_map_exports = {};
1026
1103
  __export(test_map_exports, {
1027
1104
  buildTestMap: () => buildTestMap
1028
1105
  });
1029
- import path8 from "path";
1106
+ import path9 from "path";
1030
1107
  async function buildTestMap(dir) {
1031
- const rootDir = path8.resolve(dir);
1108
+ const rootDir = path9.resolve(dir);
1032
1109
  const access = await createFileAccess(rootDir);
1033
1110
  const testPatterns = [
1034
1111
  "**/*.test.*",
@@ -1049,7 +1126,7 @@ async function buildTestMap(dir) {
1049
1126
  const sourceByBaseName = /* @__PURE__ */ new Map();
1050
1127
  for (const file of sourceFiles) {
1051
1128
  if (testFiles.includes(file)) continue;
1052
- const baseName = path8.basename(file).replace(/\.[^.]+$/, "");
1129
+ const baseName = path9.basename(file).replace(/\.[^.]+$/, "");
1053
1130
  const existing = sourceByBaseName.get(baseName) ?? [];
1054
1131
  existing.push(file);
1055
1132
  sourceByBaseName.set(baseName, existing);
@@ -1057,7 +1134,7 @@ async function buildTestMap(dir) {
1057
1134
  const paired = [];
1058
1135
  const unmatched = [];
1059
1136
  for (const testFile of testFiles) {
1060
- const testBaseName = path8.basename(testFile).replace(/\.[^.]+$/, "");
1137
+ const testBaseName = path9.basename(testFile).replace(/\.[^.]+$/, "");
1061
1138
  const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
1062
1139
  if (!sourceName) {
1063
1140
  unmatched.push(testFile);
@@ -1065,10 +1142,10 @@ async function buildTestMap(dir) {
1065
1142
  }
1066
1143
  const candidates = sourceByBaseName.get(sourceName);
1067
1144
  if (candidates && candidates.length > 0) {
1068
- const testDir = path8.dirname(testFile);
1145
+ const testDir = path9.dirname(testFile);
1069
1146
  const bestMatch = candidates.reduce((best, candidate) => {
1070
- const candidateDir = path8.dirname(candidate);
1071
- const bestDir = path8.dirname(best);
1147
+ const candidateDir = path9.dirname(candidate);
1148
+ const bestDir = path9.dirname(best);
1072
1149
  const candidateOverlap = commonSegments(testDir, candidateDir);
1073
1150
  const bestOverlap = commonSegments(testDir, bestDir);
1074
1151
  return candidateOverlap > bestOverlap ? candidate : best;
@@ -1102,9 +1179,7 @@ var init_test_map = __esm({
1102
1179
  });
1103
1180
 
1104
1181
  // src/snapshot/snapshot.ts
1105
- import path9 from "path";
1106
- import { execFile as execFile5 } from "child_process";
1107
- import { promisify as promisify5 } from "util";
1182
+ import path10 from "path";
1108
1183
  import { z } from "zod";
1109
1184
  function normalizeFeatureType(value) {
1110
1185
  return value === "infrastructure" ? "infrastructure" : "capability";
@@ -1133,7 +1208,7 @@ async function saveSnapshot(rootDir, snapshot) {
1133
1208
  }
1134
1209
  async function getCurrentGitHash(rootDir) {
1135
1210
  try {
1136
- const { stdout } = await exec4("git", ["rev-parse", "HEAD"], {
1211
+ const { stdout } = await execGit(["rev-parse", "HEAD"], {
1137
1212
  cwd: rootDir
1138
1213
  });
1139
1214
  return stdout.trim();
@@ -1145,7 +1220,7 @@ async function listSourceFiles(resolvedRoot) {
1145
1220
  return (await createFileAccess(resolvedRoot)).list();
1146
1221
  }
1147
1222
  async function prepareSnapshotBatch(rootDir, offset, batchSize = DEFAULT_BATCH_SIZE, scopeFiles) {
1148
- const resolvedRoot = path9.resolve(rootDir);
1223
+ const resolvedRoot = path10.resolve(rootDir);
1149
1224
  const access = await createFileAccess(resolvedRoot);
1150
1225
  let allFiles = await access.list();
1151
1226
  if (scopeFiles) {
@@ -1194,16 +1269,16 @@ async function prepareSnapshotBatch(rootDir, offset, batchSize = DEFAULT_BATCH_S
1194
1269
  testPairs
1195
1270
  };
1196
1271
  }
1197
- var exec4, repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema, DEFAULT_BATCH_SIZE, SKELETON_CHARS, DEEP_SAMPLE_CHARS, DEEP_SAMPLES_PER_BATCH;
1272
+ var repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema, DEFAULT_BATCH_SIZE, SKELETON_CHARS, DEEP_SAMPLE_CHARS, DEEP_SAMPLES_PER_BATCH;
1198
1273
  var init_snapshot = __esm({
1199
1274
  "src/snapshot/snapshot.ts"() {
1200
1275
  "use strict";
1276
+ init_git_read();
1201
1277
  init_files();
1202
1278
  init_files();
1203
1279
  init_storage();
1204
1280
  init_paths();
1205
1281
  init_test_map();
1206
- exec4 = promisify5(execFile5);
1207
1282
  repoPath = z.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
1208
1283
  verificationFields = {
1209
1284
  refreshedHash: z.string().optional(),
@@ -1237,9 +1312,7 @@ var init_snapshot = __esm({
1237
1312
 
1238
1313
  // src/drift/drift.ts
1239
1314
  import fs6 from "fs/promises";
1240
- import path10 from "path";
1241
- import { execFile as execFile6 } from "child_process";
1242
- import { promisify as promisify6 } from "util";
1315
+ import path11 from "path";
1243
1316
  function parseChanges(output) {
1244
1317
  const fields = output.split("\0");
1245
1318
  const changes = [];
@@ -1260,7 +1333,7 @@ function touchedPaths(changes) {
1260
1333
  async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
1261
1334
  if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-") || !toHash || toHash === "unknown" || toHash.startsWith("-")) return null;
1262
1335
  try {
1263
- const { stdout } = await exec5("git", ["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
1336
+ const { stdout } = await execGit(["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
1264
1337
  return parseChanges(stdout);
1265
1338
  } catch {
1266
1339
  return null;
@@ -1269,8 +1342,8 @@ async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
1269
1342
  async function getWorkingTree(resolvedRoot) {
1270
1343
  try {
1271
1344
  const [diff, untracked] = await Promise.all([
1272
- exec5("git", ["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
1273
- exec5("git", ["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
1345
+ execGit(["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
1346
+ execGit(["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
1274
1347
  ]);
1275
1348
  const untrackedFiles = untracked.stdout.split("\0").filter((f) => f && !f.startsWith(".mason/"));
1276
1349
  return { available: true, changedFiles: [.../* @__PURE__ */ new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles])].sort(), untrackedFiles };
@@ -1281,8 +1354,7 @@ async function getWorkingTree(resolvedRoot) {
1281
1354
  async function countCommitsBehind(resolvedRoot, fromHash) {
1282
1355
  if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-")) return null;
1283
1356
  try {
1284
- const { stdout } = await exec5(
1285
- "git",
1357
+ const { stdout } = await execGit(
1286
1358
  ["rev-list", "--count", `${fromHash}..HEAD`],
1287
1359
  { cwd: resolvedRoot }
1288
1360
  );
@@ -1307,7 +1379,7 @@ async function findGhostFiles(resolvedRoot, mappedFiles) {
1307
1379
  const ghosts = [];
1308
1380
  for (const file of mappedFiles) {
1309
1381
  try {
1310
- await fs6.access(path10.join(resolvedRoot, file));
1382
+ await fs6.access(path11.join(resolvedRoot, file));
1311
1383
  } catch {
1312
1384
  ghosts.push(file);
1313
1385
  }
@@ -1315,7 +1387,7 @@ async function findGhostFiles(resolvedRoot, mappedFiles) {
1315
1387
  return ghosts.sort();
1316
1388
  }
1317
1389
  async function computeDrift(rootDir) {
1318
- const root = path10.resolve(rootDir);
1390
+ const root = path11.resolve(rootDir);
1319
1391
  const snapshot = await loadSnapshot(root);
1320
1392
  if (!snapshot) return null;
1321
1393
  const [headHash, workingTree] = await Promise.all([getCurrentGitHash(root), getWorkingTree(root)]);
@@ -1372,7 +1444,7 @@ async function computeDrift(rootDir) {
1372
1444
  const sourceFiles = new Set(await listSourceFiles(root));
1373
1445
  let committedFiles = /* @__PURE__ */ new Set();
1374
1446
  try {
1375
- const { stdout } = await exec5("git", ["ls-tree", "-r", "--name-only", "-z", "HEAD"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
1447
+ const { stdout } = await execGit(["ls-tree", "-r", "--name-only", "-z", "HEAD"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
1376
1448
  committedFiles = new Set(stdout.split("\0").filter(Boolean));
1377
1449
  } catch {
1378
1450
  report.historyAvailable = false;
@@ -1392,13 +1464,13 @@ async function computeDrift(rootDir) {
1392
1464
  else report.recommendation = changedMapped.size >= FULL_REBUILD_MIN_CHANGED_MAPPED_FILES && changedMapped.size / Math.max(1, mappedFiles.size) > FULL_REBUILD_FRACTION ? "full-rebuild" : "incremental";
1393
1465
  return report;
1394
1466
  }
1395
- var exec5, FULL_REBUILD_FRACTION, FULL_REBUILD_MIN_CHANGED_MAPPED_FILES;
1467
+ var FULL_REBUILD_FRACTION, FULL_REBUILD_MIN_CHANGED_MAPPED_FILES;
1396
1468
  var init_drift = __esm({
1397
1469
  "src/drift/drift.ts"() {
1398
1470
  "use strict";
1471
+ init_git_read();
1399
1472
  init_snapshot();
1400
1473
  init_paths();
1401
- exec5 = promisify6(execFile6);
1402
1474
  FULL_REBUILD_FRACTION = 0.4;
1403
1475
  FULL_REBUILD_MIN_CHANGED_MAPPED_FILES = 10;
1404
1476
  }
@@ -1421,7 +1493,7 @@ var init_types = __esm({
1421
1493
  });
1422
1494
 
1423
1495
  // src/audit/checks/deleted-reference.ts
1424
- import path11 from "path";
1496
+ import path12 from "path";
1425
1497
  async function checkDeletedReferences(ctx) {
1426
1498
  const result = emptyResult();
1427
1499
  for (const doc of ctx.docs) {
@@ -1450,7 +1522,7 @@ async function checkDeletedReferences(ctx) {
1450
1522
  const candidates = await Promise.all(scope.candidates.map(async (file) => ({
1451
1523
  file,
1452
1524
  tracked: await lastCommitOf(ctx.root, file),
1453
- parent: await pathExists(ctx.root, path11.posix.dirname(file))
1525
+ parent: await pathExists(ctx.root, path12.posix.dirname(file))
1454
1526
  })));
1455
1527
  const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
1456
1528
  if (!candidate && claim.relativeTo !== "document") continue;
@@ -1490,7 +1562,7 @@ var init_deleted_reference = __esm({
1490
1562
  // src/audit/checks/new-module.ts
1491
1563
  import fg4 from "fast-glob";
1492
1564
  import fs7 from "fs/promises";
1493
- import path12 from "path";
1565
+ import path13 from "path";
1494
1566
  function escapeRegExp(text2) {
1495
1567
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1496
1568
  }
@@ -1528,23 +1600,36 @@ async function checkNewModules(ctx) {
1528
1600
  return result;
1529
1601
  }
1530
1602
  function moduleDocumentation(docs) {
1531
- return docs.map((doc) => doc.content + "\n" + (path12.posix.dirname(doc.path) === "." ? "" : path12.posix.dirname(doc.path) + "/")).join("\n");
1603
+ return docs.map((doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")).join("\n");
1604
+ }
1605
+ function moduleCandidates(root, combinedDocs) {
1606
+ return inspectionRead(root, "modules:" + combinedDocs, () => collectModuleCandidates(root, combinedDocs));
1532
1607
  }
1533
- async function moduleCandidates(root, combinedDocs) {
1608
+ async function collectModuleCandidates(root, combinedDocs) {
1534
1609
  const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
1535
1610
  const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
1536
1611
  const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
1537
- const listSubdirs = async (dir) => {
1538
- const entries = await fs7.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1539
- 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));
1540
- const ignored = await gitIgnoredPaths(root, dirs);
1541
- const visible = new Set(dirs.filter((dir2) => !ignored.has(dir2) || sources.some((file) => file.startsWith(dir2 + "/"))));
1542
- return (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1543
- ignore,
1544
- onlyDirectories: true,
1545
- label: "Module directory discovery",
1546
- select: (file) => visible.has(file)
1547
- })).map((file) => path12.posix.basename(file));
1612
+ const listSubdirs = async (parents) => {
1613
+ const byParent = /* @__PURE__ */ new Map();
1614
+ for (let offset = 0; offset < parents.length; offset += 16) {
1615
+ await Promise.all(parents.slice(offset, offset + 16).map(async (dir) => {
1616
+ const entries = await fs7.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1617
+ 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));
1618
+ byParent.set(dir, dirs);
1619
+ }));
1620
+ }
1621
+ const ignored = await gitIgnoredPaths(root, [...byParent.values()].flat());
1622
+ const result = /* @__PURE__ */ new Map();
1623
+ for (const dir of parents) {
1624
+ const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
1625
+ result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1626
+ ignore,
1627
+ onlyDirectories: true,
1628
+ label: "Module directory discovery",
1629
+ select: (file) => visible.has(file)
1630
+ })).map((file) => path13.posix.basename(file)));
1631
+ }
1632
+ return result;
1548
1633
  };
1549
1634
  const countSourceFiles = async (dir) => {
1550
1635
  const files = sources.filter((file) => file.startsWith(dir + "/"));
@@ -1560,14 +1645,16 @@ async function moduleCandidates(root, combinedDocs) {
1560
1645
  return count4;
1561
1646
  };
1562
1647
  const candidates = [];
1563
- for (const topDir of await listSubdirs(".")) {
1648
+ const topDirs = (await listSubdirs(["."])).get(".");
1649
+ const nested = await listSubdirs(topDirs.filter((dir) => isMentioned(combinedDocs, dir)));
1650
+ for (const topDir of topDirs) {
1564
1651
  const topMentioned = isMentioned(combinedDocs, topDir);
1565
1652
  if (!topMentioned) {
1566
1653
  const count4 = await countSourceFiles(topDir);
1567
1654
  if (count4 >= 1) candidates.push({ dir: topDir, sourceFileCount: count4 });
1568
1655
  continue;
1569
1656
  }
1570
- const subdirs = await listSubdirs(topDir);
1657
+ const subdirs = nested.get(topDir);
1571
1658
  const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
1572
1659
  if (mentioned.length < ENUMERATION_THRESHOLD) continue;
1573
1660
  for (const sub of subdirs) {
@@ -1584,6 +1671,7 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
1584
1671
  var init_new_module = __esm({
1585
1672
  "src/audit/checks/new-module.ts"() {
1586
1673
  "use strict";
1674
+ init_inspection();
1587
1675
  init_files();
1588
1676
  init_inputs();
1589
1677
  init_git();
@@ -1613,7 +1701,7 @@ var init_new_module = __esm({
1613
1701
  });
1614
1702
 
1615
1703
  // src/audit/checks/stale-count.ts
1616
- import path13 from "path";
1704
+ import path14 from "path";
1617
1705
  async function countGradleModules(root) {
1618
1706
  for (const name of ["settings.gradle.kts", "settings.gradle"]) {
1619
1707
  const content2 = await readAuditInput(root, name);
@@ -1647,7 +1735,7 @@ async function countNpmWorkspaces(root) {
1647
1735
  return {
1648
1736
  actual: matched.length,
1649
1737
  countedFrom: "package.json workspaces",
1650
- members: matched.map((m) => path13.dirname(m)).sort()
1738
+ members: matched.map((m) => path14.dirname(m)).sort()
1651
1739
  };
1652
1740
  }
1653
1741
  }
@@ -1678,7 +1766,7 @@ async function countNpmWorkspaces(root) {
1678
1766
  return {
1679
1767
  actual: matched.length,
1680
1768
  countedFrom: "pnpm-workspace.yaml",
1681
- members: matched.map((m) => path13.dirname(m)).sort()
1769
+ members: matched.map((m) => path14.dirname(m)).sort()
1682
1770
  };
1683
1771
  }
1684
1772
  }
@@ -1700,8 +1788,8 @@ async function countCargoCrates(root) {
1700
1788
  ignore: ["**/target/**"],
1701
1789
  label: "Cargo workspace discovery"
1702
1790
  });
1703
- for (const m of matched) members.add(path13.dirname(m));
1704
- } else if (await readAuditInput(root, path13.posix.join(entry, "Cargo.toml")) !== null) {
1791
+ for (const m of matched) members.add(path14.dirname(m));
1792
+ } else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
1705
1793
  members.add(entry);
1706
1794
  }
1707
1795
  }
@@ -1721,7 +1809,7 @@ async function resolveCountSource(root, claim) {
1721
1809
  }
1722
1810
  async function resolveDocCountSource(root, doc, claim) {
1723
1811
  const scope = documentScope(doc);
1724
- const source = await resolveCountSource(path13.join(root, scope), claim);
1812
+ const source = await resolveCountSource(path14.join(root, scope), claim);
1725
1813
  if (!source || scope === ".") return source;
1726
1814
  return {
1727
1815
  ...source,
@@ -1773,7 +1861,7 @@ var init_stale_count = __esm({
1773
1861
  });
1774
1862
 
1775
1863
  // src/audit/checks/dead-command.ts
1776
- import path14 from "path";
1864
+ import path15 from "path";
1777
1865
  function scriptsOf(raw) {
1778
1866
  try {
1779
1867
  const pkg = JSON.parse(raw);
@@ -1809,7 +1897,7 @@ async function commandInputs(root, docs) {
1809
1897
  }
1810
1898
  let cursor = directory;
1811
1899
  while (true) {
1812
- const manifest = path14.posix.join(cursor, "package.json");
1900
+ const manifest = path15.posix.join(cursor, "package.json");
1813
1901
  scope.candidates.push(manifest);
1814
1902
  const raw = await read(manifest);
1815
1903
  if (raw !== null) {
@@ -1820,7 +1908,7 @@ async function commandInputs(root, docs) {
1820
1908
  break;
1821
1909
  }
1822
1910
  if (claim.directory !== void 0 || cursor === ".") break;
1823
- cursor = path14.posix.dirname(cursor);
1911
+ cursor = path15.posix.dirname(cursor);
1824
1912
  }
1825
1913
  if (!resolution.manifest && !resolution.problem) {
1826
1914
  resolution.problem = `No package.json for the command's scope (${directory}).`;
@@ -1883,10 +1971,8 @@ var init_dead_command = __esm({
1883
1971
  });
1884
1972
 
1885
1973
  // src/audit/release-metadata.ts
1886
- import { execFile as execFile7 } from "child_process";
1887
- import { promisify as promisify7 } from "util";
1888
1974
  async function git(root, args) {
1889
- return (await exec6("git", args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1975
+ return (await execGit(args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1890
1976
  }
1891
1977
  function withoutAndroidReleaseValues(text2) {
1892
1978
  if (/\/\*|"""|'''/.test(text2) || !/id\s*\(?\s*["']com\.android\.(application|library)["']/.test(text2)) return null;
@@ -1932,11 +2018,10 @@ async function releaseMetadataOnly(root, commit) {
1932
2018
  return false;
1933
2019
  }
1934
2020
  }
1935
- var exec6;
1936
2021
  var init_release_metadata = __esm({
1937
2022
  "src/audit/release-metadata.ts"() {
1938
2023
  "use strict";
1939
- exec6 = promisify7(execFile7);
2024
+ init_git_read();
1940
2025
  }
1941
2026
  });
1942
2027
 
@@ -2032,7 +2117,7 @@ var init_deps_changed = __esm({
2032
2117
  });
2033
2118
 
2034
2119
  // src/context/lexical.ts
2035
- import path15 from "path";
2120
+ import path16 from "path";
2036
2121
  function tokenize(text2) {
2037
2122
  return text2.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2 && !STOPWORDS.has(t));
2038
2123
  }
@@ -2045,7 +2130,7 @@ function tokenSet(text2) {
2045
2130
  function scoreEntry(taskTokens, entry) {
2046
2131
  const nameTokens = tokenSet(entry.name);
2047
2132
  const descTokens = tokenSet(entry.description);
2048
- const fileTokens = tokenSet(entry.files.map((f) => path15.basename(f)).join(" "));
2133
+ const fileTokens = tokenSet(entry.files.map((f) => path16.basename(f)).join(" "));
2049
2134
  let score = 0;
2050
2135
  for (const token of taskTokens) {
2051
2136
  if (nameTokens.has(token)) score += 3;
@@ -2377,7 +2462,7 @@ __export(decisions_exports, {
2377
2462
  withDecisionWrite: () => withDecisionWrite
2378
2463
  });
2379
2464
  import fs8 from "fs/promises";
2380
- import path16 from "path";
2465
+ import path17 from "path";
2381
2466
  import { createHash } from "crypto";
2382
2467
  import { z as z3 } from "zod";
2383
2468
  async function loadDecisionStore(rootDir) {
@@ -2475,7 +2560,7 @@ async function upsertDecision(rootDir, input) {
2475
2560
  if (input.files && files.length < input.files.length) warnings.push("some anchor paths were outside the repo or duplicated and were dropped");
2476
2561
  for (const file of files) {
2477
2562
  try {
2478
- await fs8.access(path16.join(rootDir, file));
2563
+ await fs8.access(path17.join(rootDir, file));
2479
2564
  } catch {
2480
2565
  warnings.push(`anchor file does not exist on disk: ${file}`);
2481
2566
  }
@@ -2600,9 +2685,9 @@ var init_decisions = __esm({
2600
2685
  });
2601
2686
 
2602
2687
  // src/decisions/drift.ts
2603
- import path17 from "path";
2688
+ import path18 from "path";
2604
2689
  async function computeDecisionDrift(rootDir, decisions) {
2605
- const resolvedRoot = path17.resolve(rootDir);
2690
+ const resolvedRoot = path18.resolve(rootDir);
2606
2691
  const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
2607
2692
  const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
2608
2693
  const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
@@ -2924,8 +3009,6 @@ var init_store = __esm({
2924
3009
 
2925
3010
  // src/audit/advisory-review.ts
2926
3011
  import fs10 from "fs/promises";
2927
- import { execFile as execFile8 } from "child_process";
2928
- import { promisify as promisify8 } from "util";
2929
3012
  import { z as z6 } from "zod";
2930
3013
  async function readRecord(root, id) {
2931
3014
  const raw = await readStoreJson(root, recordPath(id));
@@ -3120,10 +3203,12 @@ async function reviewAdvisory(rootDir, input) {
3120
3203
  };
3121
3204
  });
3122
3205
  }
3123
- var execute2, git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
3206
+ var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
3124
3207
  var init_advisory_review = __esm({
3125
3208
  "src/audit/advisory-review.ts"() {
3126
3209
  "use strict";
3210
+ init_inspection();
3211
+ init_git_read();
3127
3212
  init_findings();
3128
3213
  init_deps_changed();
3129
3214
  init_inputs();
@@ -3133,8 +3218,7 @@ var init_advisory_review = __esm({
3133
3218
  init_store();
3134
3219
  init_decisions();
3135
3220
  init_drift2();
3136
- execute2 = promisify8(execFile8);
3137
- git2 = async (root, ...args) => (await execute2("git", args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
3221
+ git2 = async (root, ...args) => (await (["log", "ls-tree", "ls-files", "merge-base"].includes(args[0]) ? inspectionGit : execGit)(args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
3138
3222
  hashSchema = z6.string().regex(/^[a-f0-9]{64}$/);
3139
3223
  commitSchema2 = z6.string().regex(/^[a-f0-9]{40,64}$/);
3140
3224
  evidenceSchema2 = z6.object({
@@ -3178,9 +3262,12 @@ var init_advisory_review = __esm({
3178
3262
 
3179
3263
  // src/audit/audit.ts
3180
3264
  import fs11 from "fs/promises";
3181
- import path18 from "path";
3265
+ import path19 from "path";
3182
3266
  async function computeAudit(rootDir, options = {}) {
3183
- const resolvedRoot = path18.resolve(rootDir);
3267
+ return profilePhase("audit.current", () => auditCurrent(rootDir, options));
3268
+ }
3269
+ async function auditCurrent(rootDir, options) {
3270
+ const resolvedRoot = path19.resolve(rootDir);
3184
3271
  const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
3185
3272
  if (docs.length === 0) return null;
3186
3273
  const report = {
@@ -3206,15 +3293,15 @@ async function computeAudit(rootDir, options = {}) {
3206
3293
  };
3207
3294
  if (!report.gitAvailable) return report;
3208
3295
  const changesSinceDoc = /* @__PURE__ */ new Map();
3296
+ const changesByCommit = /* @__PURE__ */ new Map();
3209
3297
  for (const doc of docs) {
3210
- changesSinceDoc.set(
3211
- doc.path,
3212
- doc.lastCommit ? await getChangesWithStatus(resolvedRoot, doc.lastCommit.hash) : null
3213
- );
3298
+ const hash2 = doc.lastCommit?.hash;
3299
+ if (hash2 && !changesByCommit.has(hash2)) changesByCommit.set(hash2, await getChangesWithStatus(resolvedRoot, hash2));
3300
+ changesSinceDoc.set(doc.path, hash2 ? changesByCommit.get(hash2) : null);
3214
3301
  }
3215
3302
  let decisionsPresent = false;
3216
3303
  try {
3217
- await fs11.access(path18.join(resolvedRoot, ".mason", "decisions"));
3304
+ await fs11.access(path19.join(resolvedRoot, ".mason", "decisions"));
3218
3305
  decisionsPresent = true;
3219
3306
  } catch {
3220
3307
  }
@@ -3244,6 +3331,7 @@ async function computeAudit(rootDir, options = {}) {
3244
3331
  var init_audit = __esm({
3245
3332
  "src/audit/audit.ts"() {
3246
3333
  "use strict";
3334
+ init_profile();
3247
3335
  init_drift();
3248
3336
  init_snapshot();
3249
3337
  init_docs();
@@ -3263,10 +3351,12 @@ __export(repair_exports, {
3263
3351
  prepareRepair: () => prepareRepair,
3264
3352
  repairExitCode: () => repairExitCode,
3265
3353
  repairFindingSchema: () => repairFindingSchema,
3266
- verifyRepair: () => verifyRepair
3354
+ reportSchema: () => reportSchema,
3355
+ verifyRepair: () => verifyRepair,
3356
+ withRepairSession: () => withRepairSession
3267
3357
  });
3268
3358
  import fs12 from "fs/promises";
3269
- import path19 from "path";
3359
+ import path20 from "path";
3270
3360
  import { randomUUID as randomUUID2 } from "crypto";
3271
3361
  import { z as z7 } from "zod";
3272
3362
  function recheckable(finding) {
@@ -3290,6 +3380,9 @@ async function docState(root) {
3290
3380
  return digest(docs);
3291
3381
  }
3292
3382
  async function stableAudit(root, checks, options = {}) {
3383
+ return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
3384
+ }
3385
+ async function readStableAudit(root, checks, options) {
3293
3386
  const head = await getCurrentGitHash(root);
3294
3387
  const before = await docState(root);
3295
3388
  const report = await computeAudit(root, { ...options, checks });
@@ -3299,9 +3392,45 @@ async function stableAudit(root, checks, options = {}) {
3299
3392
  return report;
3300
3393
  }
3301
3394
  async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
3395
+ return prepareOriginal(rootDir, checks, options);
3396
+ }
3397
+ async function withRepairSession(root, options, run, validate, cachedAudit) {
3398
+ const head = await getCurrentGitHash(root);
3399
+ const audits = /* @__PURE__ */ new Map();
3400
+ const documents = /* @__PURE__ */ new Map();
3401
+ const history = /* @__PURE__ */ new Map();
3402
+ const assessments = /* @__PURE__ */ new Map();
3403
+ const memo = (cache, key, read) => {
3404
+ if (!cache.has(key)) cache.set(key, read());
3405
+ return cache.get(key);
3406
+ };
3407
+ const session = {
3408
+ audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
3409
+ if (cachedAudit && cachedAudit.headHash === head && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
3410
+ const current = structuredClone(cachedAudit);
3411
+ const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, head)))).filter((r) => r !== null);
3412
+ delete current.advisoryReviews;
3413
+ if (reviews.length) current.advisoryReviews = reviews;
3414
+ return current;
3415
+ }
3416
+ return computeAudit(root, { ...options, checks });
3417
+ }),
3418
+ document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
3419
+ history: (head2) => memo(history, head2, () => getChangesWithStatus(root, head2)),
3420
+ assessment: (dir, finding, head2) => memo(assessments, digest([dir, finding, head2]), () => assessAdvisory(dir, finding, head2))
3421
+ };
3422
+ const result = await run({
3423
+ prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
3424
+ verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
3425
+ currentAudit: () => session.audit(ALL_CHECKS)
3426
+ });
3427
+ await validate();
3428
+ return result;
3429
+ }
3430
+ async function prepareOriginal(rootDir, checks, options, session) {
3302
3431
  const root = await fs12.realpath(rootDir);
3303
3432
  const selected = z7.array(checkSchema).nonempty().parse(checks);
3304
- const report = await stableAudit(root, selected, options);
3433
+ const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
3305
3434
  if (!report) throw new Error("No context files found to prepare a repair.");
3306
3435
  if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
3307
3436
  const storedReport = reportSchema.parse(report);
@@ -3317,8 +3446,8 @@ async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
3317
3446
  }
3318
3447
  async function loadRepairBaseline(rootDir, baselinePath) {
3319
3448
  const root = await fs12.realpath(rootDir);
3320
- const declaredRoot = path19.resolve(rootDir);
3321
- const relative = path19.isAbsolute(baselinePath) ? path19.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
3449
+ const declaredRoot = path20.resolve(rootDir);
3450
+ const relative = path20.isAbsolute(baselinePath) ? path20.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
3322
3451
  const stored = baselineSchema.parse(await readStoreJson(root, relative));
3323
3452
  const { digest: savedDigest, ...payload } = stored;
3324
3453
  if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
@@ -3327,23 +3456,26 @@ async function loadRepairBaseline(rootDir, baselinePath) {
3327
3456
  return { root, relative, original };
3328
3457
  }
3329
3458
  async function verifyRepair(rootDir, baselinePath, options = {}) {
3459
+ return profilePhase("repair.verify", () => verifyOriginal(rootDir, baselinePath, options));
3460
+ }
3461
+ async function verifyOriginal(rootDir, baselinePath, options, session) {
3330
3462
  const { root, relative, original } = await loadRepairBaseline(rootDir, baselinePath);
3331
3463
  const diagnostics = [];
3332
3464
  let reviewDigest;
3333
3465
  let current = null;
3334
3466
  try {
3335
- reviewDigest = digest(await advisoryReviewInventory(root));
3336
- current = await stableAudit(root, original.checksRun, options);
3467
+ if (!session) reviewDigest = digest(await advisoryReviewInventory(root));
3468
+ current = await (session ? session.audit(original.checksRun) : stableAudit(root, original.checksRun, options));
3337
3469
  if (!current) diagnostics.push("No context files remain available to audit.");
3338
3470
  else if (!current.gitAvailable) diagnostics.push("Git history is unavailable.");
3339
3471
  for (const doc of original.docs) {
3340
3472
  if (!allFindings(original).some((f) => f.anchor.doc === doc.path) || !current?.docs.some((d) => d.path === doc.path)) continue;
3341
- const content2 = await readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024);
3473
+ const content2 = await (session ? session.document(doc.path) : readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024));
3342
3474
  if (content2 === null || !content2.trim()) {
3343
3475
  diagnostics.push("Original context file " + doc.path + " is empty or unreadable; losing its claims does not verify a repair.");
3344
3476
  }
3345
3477
  }
3346
- if (await getChangesWithStatus(root, original.headHash) === null) {
3478
+ if (await (session ? session.history(original.headHash) : getChangesWithStatus(root, original.headHash)) === null) {
3347
3479
  diagnostics.push("The original audit commit is unavailable; repair history cannot be verified.");
3348
3480
  }
3349
3481
  } catch (error) {
@@ -3368,7 +3500,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
3368
3500
  if (!current.checksRun?.includes(finding.type) || skipped.length) {
3369
3501
  return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
3370
3502
  }
3371
- const review = await assessAdvisory(root, now ?? finding, original.headHash);
3503
+ const review = await (session?.assessment ?? assessAdvisory)(root, now ?? finding, original.headHash);
3372
3504
  if (review) {
3373
3505
  if (review.status === "unverified") return { ...base, review, status: "unverified", reason: review.reason };
3374
3506
  if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
@@ -3383,7 +3515,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
3383
3515
  }
3384
3516
  return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
3385
3517
  }));
3386
- try {
3518
+ if (!session) try {
3387
3519
  if (reviewDigest !== digest(await advisoryReviewInventory(root)) || current?.headHash !== await getCurrentGitHash(root)) {
3388
3520
  diagnostics.push("HEAD or advisory review records changed during verification; repeat against stable evidence.");
3389
3521
  for (const finding of findings) {
@@ -3436,6 +3568,7 @@ var count2, reportSchema, baselineSchema, repairFindingSchema;
3436
3568
  var init_repair = __esm({
3437
3569
  "src/audit/repair.ts"() {
3438
3570
  "use strict";
3571
+ init_profile();
3439
3572
  init_audit();
3440
3573
  init_docs();
3441
3574
  init_snapshot();
@@ -3489,27 +3622,25 @@ var init_repair = __esm({
3489
3622
 
3490
3623
  // src/automation/evidence.ts
3491
3624
  import fs13 from "fs/promises";
3492
- import path20 from "path";
3625
+ import path21 from "path";
3493
3626
  import { createHash as createHash3 } from "crypto";
3494
- import { execFile as execFile9 } from "child_process";
3495
- import { promisify as promisify9 } from "util";
3496
3627
  import fg5 from "fast-glob";
3497
3628
  import { z as z8 } from "zod";
3498
3629
  async function git3(root, ...args) {
3499
- return (await exec7("git", args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout;
3630
+ return profilePhase("automation.git", async () => (await execGit(args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
3500
3631
  }
3501
3632
  async function workspace(dir) {
3502
3633
  const root = await fs13.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
3503
- const gitDir = await fs13.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
3504
- let branch;
3505
- try {
3506
- branch = (await git3(root, "symbolic-ref", "--quiet", "HEAD")).trim();
3507
- } catch {
3508
- branch = "detached";
3509
- }
3634
+ const [gitDir, branch] = await Promise.all([
3635
+ git3(root, "rev-parse", "--absolute-git-dir").then((value) => fs13.realpath(value.trim())),
3636
+ git3(root, "symbolic-ref", "--quiet", "HEAD").then((value) => value.trim(), () => "detached")
3637
+ ]);
3510
3638
  return { root, gitDir, branch, directory: ".mason/reports/automation/" + hash([root, gitDir, branch]).slice(0, 24) };
3511
3639
  }
3512
3640
  async function readInputs(root) {
3641
+ return profilePhase("automation.inputs", () => collectInputs(root));
3642
+ }
3643
+ async function collectInputs(root) {
3513
3644
  const docPaths = await discoverDocPaths(root);
3514
3645
  const [headText, docStatus, shallowPath, replacements] = await Promise.all([
3515
3646
  git3(root, "rev-parse", "HEAD"),
@@ -3520,27 +3651,35 @@ async function readInputs(root) {
3520
3651
  const head = headText.trim();
3521
3652
  let shallow = null;
3522
3653
  try {
3523
- shallow = await fs13.readFile(path20.resolve(root, shallowPath.trim()), "utf8");
3654
+ shallow = await fs13.readFile(path21.resolve(root, shallowPath.trim()), "utf8");
3524
3655
  } catch (error) {
3525
3656
  if (error.code !== "ENOENT") throw error;
3526
3657
  }
3527
3658
  const docs = {};
3528
3659
  const docContents = [];
3660
+ const parsedDocs = [];
3529
3661
  const claims = [];
3662
+ const presence = /* @__PURE__ */ new Map();
3663
+ const exists = (file) => {
3664
+ if (!presence.has(file)) presence.set(file, pathExists(root, file));
3665
+ return presence.get(file);
3666
+ };
3530
3667
  for (const file of docPaths) {
3531
3668
  const text2 = await content(root, file);
3532
3669
  docs[file] = text2 === null ? null : hash(text2);
3533
3670
  docContents.push([file, text2]);
3534
- for (const claim of text2 ? extractClaims(text2).paths : []) {
3671
+ const parsed = { path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") };
3672
+ parsedDocs.push(parsed);
3673
+ for (const claim of parsed.claims.paths) {
3535
3674
  const scope = pathClaimScope(file, claim);
3536
3675
  if (scope?.candidates.some(gitMetadataPath)) continue;
3537
3676
  for (const candidate of scope?.candidates ?? []) {
3538
3677
  if (optionalMasonPath(candidate)) continue;
3539
- claims.push([candidate, await pathExists(root, candidate), await pathExists(root, path20.posix.dirname(candidate))]);
3678
+ const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path21.posix.dirname(candidate))]);
3679
+ claims.push([candidate, filePresent, parentPresent]);
3540
3680
  }
3541
3681
  }
3542
3682
  }
3543
- const parsedDocs = docContents.map(([file, text2]) => ({ path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") }));
3544
3683
  const combinedDocs = moduleDocumentation(parsedDocs);
3545
3684
  const countClaims = parsedDocs.flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
3546
3685
  const decisionDirectory = await storePath(root, ".mason/decisions");
@@ -3548,7 +3687,7 @@ async function readInputs(root) {
3548
3687
  if (error.code === "ENOENT") return "absent";
3549
3688
  throw error;
3550
3689
  });
3551
- const [modules, counts, commands, decisionFiles] = await Promise.all([
3690
+ const [modules, counts, commands2, decisionFiles] = await Promise.all([
3552
3691
  combinedDocs ? moduleCandidates(root, combinedDocs) : [],
3553
3692
  Promise.all(countClaims.map(({ doc, claim }) => resolveDocCountSource(root, doc, claim))),
3554
3693
  commandInputs(root, parsedDocs),
@@ -3562,12 +3701,12 @@ async function readInputs(root) {
3562
3701
  git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
3563
3702
  git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
3564
3703
  ]) : ["", ""];
3565
- const common = [4, engineVersion, head, shallow, replacements, docContents];
3704
+ const common = [5, engineVersion, head, shallow, replacements, docContents];
3566
3705
  const keys = {
3567
3706
  "deleted-reference": hash([common, claims, docStatus]),
3568
3707
  "new-module": hash([common, modules]),
3569
3708
  "stale-count": hash([common, counts]),
3570
- "dead-command": hash([common, commands]),
3709
+ "dead-command": hash([common, commands2]),
3571
3710
  "deps-changed": hash([common, docStatus]),
3572
3711
  "decision-anchor-drift": hash([common, decisionPresence, decisions, status, index2])
3573
3712
  };
@@ -3587,21 +3726,30 @@ function checkCache(raw, inputs) {
3587
3726
  reused.add(name);
3588
3727
  return structuredClone(entries[name].result);
3589
3728
  }
3590
- const result = await CHECKS[name](ctx);
3729
+ const result = await profilePhase("check." + name, () => CHECKS[name](ctx));
3591
3730
  ran.add(name);
3592
3731
  if (!result.skipped.length) entries[name] = { key: inputs.keys[name], result };
3593
3732
  else delete entries[name];
3594
3733
  return result;
3595
3734
  } };
3596
- return { options, ran, reused, diagnostic, serialize: () => {
3597
- const canonical = cacheSchema.shape.entries.parse(entries);
3598
- return { version: 1, entries: canonical, digest: hash(canonical) };
3599
- } };
3735
+ return {
3736
+ options,
3737
+ ran,
3738
+ reused,
3739
+ diagnostic,
3740
+ has: (name) => entries[name]?.key === inputs.keys[name] && !entries[name].result.skipped.length,
3741
+ serialize: () => {
3742
+ const canonical = cacheSchema.shape.entries.parse(entries);
3743
+ return { version: 1, entries: canonical, digest: hash(canonical) };
3744
+ }
3745
+ };
3600
3746
  }
3601
- var exec7, engineVersion, hash, content, cacheSchema;
3747
+ var engineVersion, hash, content, cacheSchema;
3602
3748
  var init_evidence = __esm({
3603
3749
  "src/automation/evidence.ts"() {
3604
3750
  "use strict";
3751
+ init_git_read();
3752
+ init_profile();
3605
3753
  init_docs();
3606
3754
  init_scope();
3607
3755
  init_claims();
@@ -3613,8 +3761,7 @@ var init_evidence = __esm({
3613
3761
  init_dead_command();
3614
3762
  init_storage();
3615
3763
  init_advisory_review();
3616
- exec7 = promisify9(execFile9);
3617
- engineVersion = true ? "0.17.2" : "development";
3764
+ engineVersion = true ? "0.17.4" : "development";
3618
3765
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
3619
3766
  content = readAuditInput;
3620
3767
  cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
@@ -3674,8 +3821,8 @@ var init_model = __esm({
3674
3821
  });
3675
3822
 
3676
3823
  // src/setup/launcher.ts
3677
- import { execFile as execFile10 } from "child_process";
3678
- import { promisify as promisify10 } from "util";
3824
+ import { execFile as execFile2 } from "child_process";
3825
+ import { promisify as promisify2 } from "util";
3679
3826
  import fs14 from "fs/promises";
3680
3827
  function globalCommand(args, platform = process.platform) {
3681
3828
  return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args] } : { command: "mason", args };
@@ -3683,7 +3830,7 @@ function globalCommand(args, platform = process.platform) {
3683
3830
  async function installedCommand() {
3684
3831
  const invocation = globalCommand(["internal-integration-version"]);
3685
3832
  try {
3686
- const { stdout } = await promisify10(execFile10)(
3833
+ const { stdout } = await promisify2(execFile2)(
3687
3834
  invocation.command,
3688
3835
  invocation.args,
3689
3836
  { timeout: 5e3, maxBuffer: 8192, windowsHide: true }
@@ -3706,7 +3853,7 @@ var init_launcher = __esm({
3706
3853
  "use strict";
3707
3854
  init_evidence();
3708
3855
  init_model();
3709
- executingVersion = () => true ? "0.17.2" : "development";
3856
+ executingVersion = () => true ? "0.17.4" : "development";
3710
3857
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3711
3858
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3712
3859
  }
@@ -3720,7 +3867,7 @@ __export(files_exports, {
3720
3867
  readText: () => readText
3721
3868
  });
3722
3869
  import fs17 from "fs/promises";
3723
- import path24 from "path";
3870
+ import path25 from "path";
3724
3871
  import { randomUUID as randomUUID3 } from "crypto";
3725
3872
  async function readText(root, file) {
3726
3873
  try {
@@ -3753,7 +3900,7 @@ async function applyEdit(root, edit) {
3753
3900
  return true;
3754
3901
  }
3755
3902
  const file = await storePath(root, edit.path, true);
3756
- const temporary = path24.join(path24.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
3903
+ const temporary = path25.join(path25.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
3757
3904
  try {
3758
3905
  const mode = await fs17.stat(file).then((s) => s.mode & 511, () => 384);
3759
3906
  const handle = await fs17.open(temporary, "wx", mode);
@@ -3981,7 +4128,7 @@ __export(runtime_exports, {
3981
4128
  import { randomUUID as randomUUID5 } from "crypto";
3982
4129
  import { z as z13 } from "zod";
3983
4130
  async function observeReadOnlyTool(dir, event) {
3984
- const ws = await workspace(dir);
4131
+ const ws = await profilePhase("automation.workspace", () => workspace(dir));
3985
4132
  await withLock(ws.root, ws.directory, async () => {
3986
4133
  const statePath = ws.directory + "/state.json";
3987
4134
  const raw = await readStoreJson(ws.root, statePath);
@@ -4028,24 +4175,41 @@ async function loadState(ws) {
4028
4175
  }
4029
4176
  return state;
4030
4177
  }
4031
- async function sharedAnalysis(ws, state, inputs, startedAt) {
4178
+ async function matchingAnalysis(ws, state, inputs) {
4032
4179
  const pointer = state.analysis;
4033
- if (!pointer || pointer.fingerprint !== inputs.fingerprint || pointer.completedAt < startedAt) return null;
4034
- const stored = sharedSchema.parse(await readStoreJson(ws.root, pointer.path));
4180
+ if (!pointer || pointer.fingerprint !== inputs.fingerprint) return { analysis: null };
4181
+ if (pointer.path !== ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json") {
4182
+ throw new Error("Invalid automation analysis cache path; inspect the retained state.");
4183
+ }
4184
+ const invalid = { analysis: null, diagnostic: "Discarded an invalid current-audit cache; audit preparation and original verification were repeated." };
4185
+ let raw;
4186
+ try {
4187
+ raw = await readStoreJson(ws.root, pointer.path);
4188
+ } catch (error) {
4189
+ const cause = error.cause;
4190
+ if (cause instanceof SyntaxError || cause instanceof Error && cause.message === "expected a JSON object, received null") return invalid;
4191
+ throw error;
4192
+ }
4193
+ if (raw === null) return { analysis: null };
4194
+ const parsed = sharedSchema.safeParse(raw);
4195
+ if (!parsed.success) return invalid;
4196
+ const stored = parsed.data;
4035
4197
  const a = stored.analysis;
4036
4198
  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))) {
4037
- throw new Error("Invalid shared automation evidence; run a new check.");
4199
+ return invalid;
4038
4200
  }
4039
4201
  if (a.baselineDigest !== await baselineDigest(ws, state)) throw new Error("Repair baseline was modified; use the original baseline.");
4040
- return a;
4202
+ return { analysis: a };
4041
4203
  }
4042
4204
  async function verifyInputs(ws, inputs) {
4043
- const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
4044
- if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
4045
- throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
4046
- }
4205
+ return profilePhase("automation.revalidate", () => withRepositoryInspection(ws.root, async () => {
4206
+ const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
4207
+ if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
4208
+ throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
4209
+ }
4210
+ }));
4047
4211
  }
4048
- async function computeAnalysis(ws, state, inputs, event) {
4212
+ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
4049
4213
  const baselines = [...state.baselines];
4050
4214
  const now = (/* @__PURE__ */ new Date()).toISOString();
4051
4215
  const empty = {
@@ -4063,7 +4227,10 @@ async function computeAnalysis(ws, state, inputs, event) {
4063
4227
  capture: "unknown",
4064
4228
  scope: SCOPE
4065
4229
  };
4066
- if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) return { baselines, report: empty, cache: null };
4230
+ if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) {
4231
+ await verifyInputs(ws, inputs);
4232
+ return { baselines, report: empty, cache: null };
4233
+ }
4067
4234
  if (ws.branch === "detached" && state.latest) {
4068
4235
  const previous = await readStoreJson(ws.root, state.latest);
4069
4236
  if (!previous?.head || !/^[a-f0-9]{40,64}$/.test(previous.head)) throw new Error("The previous detached checkout evidence is unavailable.");
@@ -4074,7 +4241,7 @@ async function computeAnalysis(ws, state, inputs, event) {
4074
4241
  }
4075
4242
  }
4076
4243
  let cached = null;
4077
- const diagnostics = [];
4244
+ const diagnostics = cacheDiagnostic ? [cacheDiagnostic] : [];
4078
4245
  try {
4079
4246
  cached = await readStoreJson(ws.root, ws.directory + "/cache.json");
4080
4247
  } catch {
@@ -4082,19 +4249,26 @@ async function computeAnalysis(ws, state, inputs, event) {
4082
4249
  }
4083
4250
  const cache = checkCache(cached, inputs);
4084
4251
  if (cache.diagnostic) diagnostics.push(cache.diagnostic);
4085
- const saveBaseline = async () => {
4086
- if (baselines.length >= 128) throw new Error("128 retained baselines need review; automatic capture stopped without discarding original evidence.");
4087
- const prepared = await prepareRepair(ws.root, ALL_CHECKS, cache.options);
4088
- baselines.push({ path: prepared.baselinePath, at: now, event: event.event, fingerprint: inputs.fingerprint });
4089
- };
4090
- if (!baselines.length) await saveBaseline();
4091
- const verifications = [];
4092
- for (const baseline of baselines) verifications.push(await verifyRepair(ws.root, baseline.path, cache.options));
4093
- const known = new Set(verifications.flatMap((v) => v.findings.map((f) => f.id)));
4094
- if (verifications.some((v) => v.newFindings.some((f) => !known.has(findingId(f))))) {
4095
- await saveBaseline();
4096
- verifications.push(await verifyRepair(ws.root, baselines.at(-1).path, cache.options));
4097
- }
4252
+ 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;
4253
+ if (cachedAudit) for (const name of ALL_CHECKS) cache.reused.add(name);
4254
+ let currentAudit = null;
4255
+ const verifications = await withRepairSession(ws.root, cache.options, async (repairs) => {
4256
+ const saveBaseline = async () => {
4257
+ if (baselines.length >= 128) throw new Error("128 retained baselines need review; automatic capture stopped without discarding original evidence.");
4258
+ const prepared = await repairs.prepare();
4259
+ baselines.push({ path: prepared.baselinePath, at: now, event: event.event, fingerprint: inputs.fingerprint });
4260
+ };
4261
+ if (!baselines.length) await saveBaseline();
4262
+ const verifications2 = [];
4263
+ for (const baseline of baselines) verifications2.push(await repairs.verify(baseline.path));
4264
+ const known = new Set(verifications2.flatMap((v) => v.findings.map((f) => f.id)));
4265
+ if (verifications2.some((v) => v.newFindings.some((f) => !known.has(findingId(f))))) {
4266
+ await saveBaseline();
4267
+ verifications2.push(await repairs.verify(baselines.at(-1).path));
4268
+ }
4269
+ currentAudit = await repairs.currentAudit();
4270
+ return verifications2;
4271
+ }, () => verifyInputs(ws, inputs), cachedAudit);
4098
4272
  const merged = /* @__PURE__ */ new Map();
4099
4273
  for (const verification of verifications) {
4100
4274
  for (const finding of verification.findings) {
@@ -4115,21 +4289,20 @@ async function computeAnalysis(ws, state, inputs, event) {
4115
4289
  reportPath: "",
4116
4290
  findings: [...merged.values()],
4117
4291
  diagnostics: [...new Set(diagnostics)],
4118
- checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [] },
4292
+ checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [], ...cachedAudit ? { auditReused: true } : {} },
4119
4293
  counts,
4120
4294
  capture: "unknown",
4121
4295
  scope: SCOPE
4122
4296
  };
4123
- return { baselines, report, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
4297
+ return { baselines, report, currentAudit, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
4124
4298
  }
4125
4299
  async function analyze(ws, inputs, event, startedAt) {
4126
4300
  return withLock(ws.root, ws.directory + "/analysis/" + inputs.fingerprint, async () => {
4127
4301
  const before = await loadState(ws);
4128
- const shared = await sharedAnalysis(ws, before, inputs, startedAt);
4129
- if (shared) return { analysis: shared, shared: true };
4302
+ const { analysis: previous, diagnostic } = await matchingAnalysis(ws, before, inputs);
4303
+ if (previous && previous.completedAt >= startedAt) return { analysis: previous, shared: true };
4130
4304
  const originalDigest = await baselineDigest(ws, before);
4131
- const computed = await computeAnalysis(ws, before, inputs, event);
4132
- await verifyInputs(ws, inputs);
4305
+ const computed = await computeAnalysis(ws, before, inputs, event, previous?.currentAudit, diagnostic);
4133
4306
  if (originalDigest !== await baselineDigest(ws, before)) throw new Error("Repair baseline was modified during automation; original evidence must be inspected.");
4134
4307
  const id = randomUUID5();
4135
4308
  const analysis = analysisSchema.parse({
@@ -4137,7 +4310,8 @@ async function analyze(ws, inputs, event, startedAt) {
4137
4310
  fingerprint: inputs.fingerprint,
4138
4311
  completedAt: Date.now(),
4139
4312
  baselineDigest: await baselineDigest(ws, { ...before, baselines: computed.baselines }),
4140
- report: computed.report
4313
+ report: computed.report,
4314
+ currentAudit: computed.currentAudit
4141
4315
  });
4142
4316
  const analysisPath = ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json";
4143
4317
  await withLock(ws.root, ws.directory, async () => {
@@ -4156,17 +4330,17 @@ async function analyze(ws, inputs, event, startedAt) {
4156
4330
  }
4157
4331
  async function automate(dir, event) {
4158
4332
  const startedAt = Date.now();
4159
- const ws = await workspace(dir);
4160
- return recordExecution(ws.root, ws.directory, event.event, async () => {
4333
+ const ws = await profilePhase("automation.workspace", () => workspace(dir));
4334
+ return recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
4161
4335
  const inputs = await readInputs(ws.root);
4162
- const result = await analyze(ws, inputs, event, startedAt);
4336
+ const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
4163
4337
  if (result.shared) {
4164
4338
  await verifyInputs(ws, inputs);
4165
4339
  if (result.analysis.baselineDigest !== await baselineDigest(ws, await loadState(ws))) {
4166
4340
  throw new Error("Repair baseline was modified during shared automation; original evidence must be inspected.");
4167
4341
  }
4168
4342
  }
4169
- return withLock(ws.root, ws.directory, async () => {
4343
+ return profilePhase("automation.publish", () => withLock(ws.root, ws.directory, async () => {
4170
4344
  const state = await loadState(ws);
4171
4345
  if (state.analysis?.id !== result.analysis.id) throw new Error("Automation evidence changed during publication; retry against the current inputs.");
4172
4346
  const report = structuredClone(result.analysis.report);
@@ -4229,11 +4403,11 @@ async function automate(dir, event) {
4229
4403
  if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
4230
4404
  await writeStoreJson(ws.root, ws.directory + "/state.json", state);
4231
4405
  return { report, message: notify ? summarize(report) : null, continueOnce };
4232
- });
4233
- });
4406
+ }));
4407
+ }));
4234
4408
  }
4235
4409
  async function automationStatus(dir) {
4236
- const ws = await workspace(dir);
4410
+ const ws = await profilePhase("automation.workspace", () => workspace(dir));
4237
4411
  const execution = await executionStatus(ws.root, ws.directory);
4238
4412
  const notifications = await failureNotifications(ws.root, ws.directory);
4239
4413
  const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
@@ -4267,6 +4441,8 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
4267
4441
  var init_runtime = __esm({
4268
4442
  "src/automation/runtime.ts"() {
4269
4443
  "use strict";
4444
+ init_profile();
4445
+ init_inspection();
4270
4446
  init_execution();
4271
4447
  init_repair();
4272
4448
  init_types();
@@ -4281,6 +4457,7 @@ var init_runtime = __esm({
4281
4457
  fingerprint: z13.string(),
4282
4458
  completedAt: z13.number(),
4283
4459
  baselineDigest: z13.string(),
4460
+ currentAudit: reportSchema.nullable().optional(),
4284
4461
  report: z13.object({
4285
4462
  version: z13.literal(1),
4286
4463
  status: z13.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
@@ -4294,7 +4471,8 @@ var init_runtime = __esm({
4294
4471
  checks: z13.object({
4295
4472
  ran: z13.array(z13.string()),
4296
4473
  reused: z13.array(z13.string()),
4297
- skipped: z13.array(z13.object({ check: z13.string(), reason: z13.string(), doc: z13.string().optional() }))
4474
+ skipped: z13.array(z13.object({ check: z13.string(), reason: z13.string(), doc: z13.string().optional() })),
4475
+ auditReused: z13.boolean().optional()
4298
4476
  }),
4299
4477
  counts: z13.object({ resolved: z13.number(), unresolved: z13.number(), "review-required": z13.number(), unverified: z13.number() }),
4300
4478
  capture: z13.enum(["observed", "unknown"]),
@@ -4302,15 +4480,15 @@ var init_runtime = __esm({
4302
4480
  })
4303
4481
  });
4304
4482
  sharedSchema = z13.object({ analysis: analysisSchema, digest: z13.string() });
4305
- baselineDigest = async (ws, state) => hash(await Promise.all(
4483
+ baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
4306
4484
  state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
4307
- ));
4485
+ )));
4308
4486
  }
4309
4487
  });
4310
4488
 
4311
4489
  // src/automation/hook-root.ts
4312
4490
  import fs19 from "fs/promises";
4313
- import path25 from "path";
4491
+ import path26 from "path";
4314
4492
  var init_hook_root = __esm({
4315
4493
  "src/automation/hook-root.ts"() {
4316
4494
  "use strict";
@@ -4779,13 +4957,13 @@ If a build is interrupted, its partials remain in \`.mason/partial-snapshots/\`.
4779
4957
  });
4780
4958
 
4781
4959
  // src/review/cochange.ts
4782
- import { execFile as execFile15 } from "child_process";
4783
- import { promisify as promisify15 } from "util";
4960
+ import { execFile as execFile6 } from "child_process";
4961
+ import { promisify as promisify6 } from "util";
4784
4962
  async function buildMatrix(resolvedRoot) {
4785
4963
  try {
4786
- const { stdout: shallow } = await exec12("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
4964
+ const { stdout: shallow } = await exec4("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
4787
4965
  if (shallow.trim() === "true") return null;
4788
- const { stdout } = await exec12(
4966
+ const { stdout } = await exec4(
4789
4967
  "git",
4790
4968
  ["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
4791
4969
  { cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
@@ -4841,11 +5019,11 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
4841
5019
  findings.sort((a, b) => b.rate - a.rate || b.sharedCommits - a.sharedCommits);
4842
5020
  return findings;
4843
5021
  }
4844
- var exec12, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
5022
+ var exec4, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
4845
5023
  var init_cochange = __esm({
4846
5024
  "src/review/cochange.ts"() {
4847
5025
  "use strict";
4848
- exec12 = promisify15(execFile15);
5026
+ exec4 = promisify6(execFile6);
4849
5027
  HISTORY_COMMITS = 1500;
4850
5028
  MIN_FILE_COMMITS = 5;
4851
5029
  MIN_SHARED_COMMITS = 4;
@@ -5153,10 +5331,10 @@ var init_check = __esm({
5153
5331
  });
5154
5332
 
5155
5333
  // src/review/evidence.ts
5156
- import path26 from "path";
5334
+ import path27 from "path";
5157
5335
  import { z as z22 } from "zod";
5158
5336
  function relativeArtifact(root, file) {
5159
- const relative = path26.isAbsolute(file) ? path26.relative(root, file) : file;
5337
+ const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
5160
5338
  const normalized = normalizeRepoPath(relative);
5161
5339
  if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
5162
5340
  return normalized;
@@ -5253,7 +5431,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5253
5431
  const report = await readStoreJson(root, file);
5254
5432
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
5255
5433
  const sourceRoot = check.sourceRoot ?? root;
5256
- if (!path26.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5434
+ if (!path27.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5257
5435
  const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
5258
5436
  result.outcome = parsed.outcome;
5259
5437
  result.counts = parsed.counts;
@@ -5375,12 +5553,12 @@ var init_evidence2 = __esm({
5375
5553
 
5376
5554
  // src/review/review.ts
5377
5555
  import fs20 from "fs/promises";
5378
- import path27 from "path";
5379
- import { execFile as execFile16 } from "child_process";
5380
- import { promisify as promisify16 } from "util";
5556
+ import path28 from "path";
5557
+ import { execFile as execFile7 } from "child_process";
5558
+ import { promisify as promisify7 } from "util";
5381
5559
  async function resolveMergeBase(resolvedRoot, base, head) {
5382
5560
  try {
5383
- const { stdout } = await exec13("git", ["merge-base", base, head], {
5561
+ const { stdout } = await exec5("git", ["merge-base", base, head], {
5384
5562
  cwd: resolvedRoot
5385
5563
  });
5386
5564
  return stdout.trim() || null;
@@ -5391,7 +5569,7 @@ async function resolveMergeBase(resolvedRoot, base, head) {
5391
5569
  async function defaultBase(resolvedRoot) {
5392
5570
  for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
5393
5571
  try {
5394
- await exec13("git", ["rev-parse", "--verify", "--quiet", ref], {
5572
+ await exec5("git", ["rev-parse", "--verify", "--quiet", ref], {
5395
5573
  cwd: resolvedRoot
5396
5574
  });
5397
5575
  return ref;
@@ -5404,7 +5582,7 @@ function anchorsTouched(record, changedFiles) {
5404
5582
  return matchingPaths(record.files, changedFiles);
5405
5583
  }
5406
5584
  async function computeReview(rootDir, base, options = {}) {
5407
- const resolvedRoot = path27.resolve(rootDir);
5585
+ const resolvedRoot = path28.resolve(rootDir);
5408
5586
  const head = await getCurrentGitHash(resolvedRoot);
5409
5587
  if (head === "unknown") return null;
5410
5588
  const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
@@ -5454,7 +5632,7 @@ async function computeReview(rootDir, base, options = {}) {
5454
5632
  analyzed,
5455
5633
  async (relPath) => {
5456
5634
  try {
5457
- await fs20.access(path27.join(resolvedRoot, relPath));
5635
+ await fs20.access(path28.join(resolvedRoot, relPath));
5458
5636
  return true;
5459
5637
  } catch {
5460
5638
  return false;
@@ -5487,7 +5665,7 @@ async function computeReview(rootDir, base, options = {}) {
5487
5665
  }
5488
5666
  return finalize();
5489
5667
  }
5490
- var exec13, MAX_ANALYZED_FILES;
5668
+ var exec5, MAX_ANALYZED_FILES;
5491
5669
  var init_review = __esm({
5492
5670
  "src/review/review.ts"() {
5493
5671
  "use strict";
@@ -5499,7 +5677,7 @@ var init_review = __esm({
5499
5677
  init_cochange();
5500
5678
  init_evidence2();
5501
5679
  init_snapshot();
5502
- exec13 = promisify16(execFile16);
5680
+ exec5 = promisify7(execFile7);
5503
5681
  MAX_ANALYZED_FILES = 50;
5504
5682
  }
5505
5683
  });
@@ -5589,7 +5767,7 @@ var init_onboarding = __esm({
5589
5767
  });
5590
5768
 
5591
5769
  // src/impact/references.ts
5592
- import path28 from "path";
5770
+ import path29 from "path";
5593
5771
  function templateEnd(content2, start) {
5594
5772
  let i = start + 1, interpolation = 0;
5595
5773
  while (i < content2.length) {
@@ -5715,10 +5893,10 @@ function jvmImports(content2) {
5715
5893
  }
5716
5894
  function resolveImport(file, specifier, available) {
5717
5895
  if (!specifier.startsWith("./") && !specifier.startsWith("../")) return null;
5718
- const relative = path28.posix.normalize(path28.posix.join(path28.posix.dirname(file), specifier));
5896
+ const relative = path29.posix.normalize(path29.posix.join(path29.posix.dirname(file), specifier));
5719
5897
  if (relative.startsWith("../")) return null;
5720
5898
  if (available.has(relative)) return relative;
5721
- const ext = path28.posix.extname(relative);
5899
+ const ext = path29.posix.extname(relative);
5722
5900
  const replacements = { ".js": [".ts", ".tsx"], ".mjs": [".mts"], ".cjs": [".cts"] };
5723
5901
  const candidates = ext ? (replacements[ext] ?? []).map((e) => relative.slice(0, -ext.length) + e) : jsExtensions.flatMap((e) => [relative + e, relative + "/index" + e]);
5724
5902
  const found = candidates.filter((f) => available.has(f));
@@ -5730,13 +5908,13 @@ function mentionsPath(content2, target) {
5730
5908
  function createReferenceMatcher(targets, available, targetContents = /* @__PURE__ */ new Map()) {
5731
5909
  const counts = /* @__PURE__ */ new Map();
5732
5910
  for (const file of available) {
5733
- const name = path28.posix.basename(file, path28.posix.extname(file));
5911
+ const name = path29.posix.basename(file, path29.posix.extname(file));
5734
5912
  counts.set(name, (counts.get(name) ?? 0) + 1);
5735
5913
  }
5736
5914
  const jvmTargets = new Map([...targetContents].filter(([file]) => /\.(?:kt|kts|java)$/.test(file)).map(([file, content2]) => [file, declaredSymbols(content2)]));
5737
5915
  return (source) => {
5738
5916
  if (targets.includes(source.path)) return [];
5739
- const resolved = new Set(jsExtensions.includes(path28.posix.extname(source.path)) ? imports(source.content).map((s) => resolveImport(source.path, s, available)).filter(Boolean) : []);
5917
+ const resolved = new Set(jsExtensions.includes(path29.posix.extname(source.path)) ? imports(source.content).map((s) => resolveImport(source.path, s, available)).filter(Boolean) : []);
5740
5918
  if (/\.(?:kt|kts|java)$/.test(source.path)) {
5741
5919
  const imported = jvmImports(source.content);
5742
5920
  for (const [target, symbols] of jvmTargets) if (symbols.some((symbol) => imported.some((name) => name === symbol || name.startsWith(symbol + ".")))) resolved.add(target);
@@ -5752,7 +5930,7 @@ function createReferenceMatcher(targets, available, targetContents = /* @__PURE_
5752
5930
  add("explicit-path", target);
5753
5931
  continue;
5754
5932
  }
5755
- const ext = path28.posix.extname(target), name = path28.posix.basename(target, ext);
5933
+ const ext = path29.posix.extname(target), name = path29.posix.basename(target, ext);
5756
5934
  if (!/\.(?:[cm]?[jt]sx?|py|kt|java|go|rs|swift|rb|cs|cpp|c|h|hpp|dart|php)$/.test(ext) || name.startsWith(".") || name.length < 3 || genericNames.has(name.toLowerCase()) || counts.get(name) !== 1) continue;
5757
5935
  if (word(name).test(source.content)) {
5758
5936
  add("name-candidate", name);
@@ -5781,11 +5959,11 @@ var impact_exports = {};
5781
5959
  __export(impact_exports, {
5782
5960
  analyzeImpact: () => analyzeImpact
5783
5961
  });
5784
- import path29 from "path";
5785
- import { execFile as execFile17 } from "child_process";
5786
- import { promisify as promisify17 } from "util";
5962
+ import path30 from "path";
5963
+ import { execFile as execFile8 } from "child_process";
5964
+ import { promisify as promisify8 } from "util";
5787
5965
  async function analyzeImpact(rootDir, targetFiles) {
5788
- const resolvedRoot = path29.resolve(rootDir);
5966
+ const resolvedRoot = path30.resolve(rootDir);
5789
5967
  const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
5790
5968
  const [cochange, references, tests] = await Promise.all([
5791
5969
  getCochangeFiles(resolvedRoot, resolvedTargets),
@@ -5828,7 +6006,7 @@ async function getCochangeFiles(rootDir, targetFiles) {
5828
6006
  let totalTargetCommits = 0;
5829
6007
  for (const targetFile of targetFiles) {
5830
6008
  try {
5831
- const { stdout: commitLog } = await exec14(
6009
+ const { stdout: commitLog } = await exec6(
5832
6010
  "git",
5833
6011
  ["log", "--format=%H", "-n", "500", "--", targetFile],
5834
6012
  { cwd: rootDir, maxBuffer: 5e6 }
@@ -5838,7 +6016,7 @@ async function getCochangeFiles(rootDir, targetFiles) {
5838
6016
  if (commits.length === 0) continue;
5839
6017
  for (const commit of commits) {
5840
6018
  try {
5841
- const { stdout: filesInCommit } = await exec14(
6019
+ const { stdout: filesInCommit } = await exec6(
5842
6020
  "git",
5843
6021
  ["diff-tree", "--no-commit-id", "--name-only", "-r", commit],
5844
6022
  { cwd: rootDir }
@@ -5899,9 +6077,9 @@ async function getRelatedTests(rootDir, targetFiles) {
5899
6077
  const testFiles = await (await createFileAccess(rootDir)).list(testPatterns);
5900
6078
  const results = [];
5901
6079
  for (const target of targetFiles) {
5902
- const targetBaseName = path29.basename(target).replace(/\.[^.]+$/, "");
6080
+ const targetBaseName = path30.basename(target).replace(/\.[^.]+$/, "");
5903
6081
  for (const testFile of testFiles) {
5904
- const testBaseName = path29.basename(testFile).replace(/\.[^.]+$/, "");
6082
+ const testBaseName = path30.basename(testFile).replace(/\.[^.]+$/, "");
5905
6083
  const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
5906
6084
  if (sourceName === targetBaseName) {
5907
6085
  results.push({
@@ -5913,14 +6091,14 @@ async function getRelatedTests(rootDir, targetFiles) {
5913
6091
  }
5914
6092
  return results;
5915
6093
  }
5916
- var exec14;
6094
+ var exec6;
5917
6095
  var init_impact = __esm({
5918
6096
  "src/impact/impact.ts"() {
5919
6097
  "use strict";
5920
6098
  init_files();
5921
6099
  init_paths();
5922
6100
  init_references();
5923
- exec14 = promisify17(execFile17);
6101
+ exec6 = promisify8(execFile8);
5924
6102
  }
5925
6103
  });
5926
6104
 
@@ -5929,13 +6107,13 @@ var assemble_exports = {};
5929
6107
  __export(assemble_exports, {
5930
6108
  assembleContext: () => assembleContext
5931
6109
  });
5932
- import path30 from "path";
6110
+ import path31 from "path";
5933
6111
  import fs21 from "fs/promises";
5934
6112
  async function collectImpact(root, candidates) {
5935
6113
  const targets = /* @__PURE__ */ new Set();
5936
6114
  let sourceFiles;
5937
6115
  for (const candidate of sanitizeRepoPaths(candidates)) {
5938
- const stat = await fs21.stat(path30.join(root, candidate)).catch(() => null);
6116
+ const stat = await fs21.stat(path31.join(root, candidate)).catch(() => null);
5939
6117
  if (stat?.isDirectory()) {
5940
6118
  sourceFiles ??= await (await createFileAccess(root)).list();
5941
6119
  for (const file of sourceFiles) {
@@ -5961,7 +6139,7 @@ async function collectImpact(root, candidates) {
5961
6139
  };
5962
6140
  }
5963
6141
  async function assembleContext(rootDir, task, files) {
5964
- const resolvedRoot = path30.resolve(rootDir);
6142
+ const resolvedRoot = path31.resolve(rootDir);
5965
6143
  const mapState = await inspectSnapshot(resolvedRoot);
5966
6144
  const snapshot = mapState.snapshot;
5967
6145
  const store = await loadDecisionStore(resolvedRoot);
@@ -6504,8 +6682,8 @@ __export(client_exports, {
6504
6682
  function createConfluenceClient(config, fetchFn = fetch) {
6505
6683
  const baseUrl = config.baseUrl.replace(/\/+$/, "");
6506
6684
  const auth = "Basic " + Buffer.from(`${config.email}:${config.apiToken}`).toString("base64");
6507
- async function call(method, path34, body) {
6508
- const res = await fetchFn(`${baseUrl}${path34}`, {
6685
+ async function call(method, path35, body) {
6686
+ const res = await fetchFn(`${baseUrl}${path35}`, {
6509
6687
  method,
6510
6688
  headers: {
6511
6689
  Authorization: auth,
@@ -6517,7 +6695,7 @@ function createConfluenceClient(config, fetchFn = fetch) {
6517
6695
  if (!res.ok) {
6518
6696
  const text2 = await res.text();
6519
6697
  throw new Error(
6520
- `Confluence ${method} ${path34} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
6698
+ `Confluence ${method} ${path35} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
6521
6699
  );
6522
6700
  }
6523
6701
  if (res.status === 204) return null;
@@ -6619,15 +6797,15 @@ __export(config_exports2, {
6619
6797
  validateProvider: () => validateProvider
6620
6798
  });
6621
6799
  import fs23 from "fs/promises";
6622
- import path31 from "path";
6800
+ import path32 from "path";
6623
6801
  import os3 from "os";
6624
- import { execFile as execFile18 } from "child_process";
6625
- import { promisify as promisify18 } from "util";
6802
+ import { execFile as execFile9 } from "child_process";
6803
+ import { promisify as promisify9 } from "util";
6626
6804
  function configDir() {
6627
- return path31.join(os3.homedir(), ".mason");
6805
+ return path32.join(os3.homedir(), ".mason");
6628
6806
  }
6629
6807
  function configFile() {
6630
- return path31.join(configDir(), "config.json");
6808
+ return path32.join(configDir(), "config.json");
6631
6809
  }
6632
6810
  async function loadConfig() {
6633
6811
  try {
@@ -6657,7 +6835,7 @@ async function detectCLI(provider) {
6657
6835
  const cliName = provider === "claude" ? "claude" : provider === "gemini" ? "gemini" : provider === "ollama" ? "ollama" : null;
6658
6836
  if (!cliName) return { available: false };
6659
6837
  try {
6660
- const { stdout } = await exec15(cliName, ["--version"]);
6838
+ const { stdout } = await exec7(cliName, ["--version"]);
6661
6839
  return { available: true, version: stdout.trim() };
6662
6840
  } catch {
6663
6841
  return { available: false };
@@ -6674,11 +6852,11 @@ async function loadConfluenceConfig() {
6674
6852
  const config = await loadConfig();
6675
6853
  return config?.confluence ?? null;
6676
6854
  }
6677
- var exec15, DEFAULT_MODELS;
6855
+ var exec7, DEFAULT_MODELS;
6678
6856
  var init_config2 = __esm({
6679
6857
  "src/llm/config.ts"() {
6680
6858
  "use strict";
6681
- exec15 = promisify18(execFile18);
6859
+ exec7 = promisify9(execFile9);
6682
6860
  DEFAULT_MODELS = {
6683
6861
  claude: "claude-sonnet-4-20250514",
6684
6862
  gemini: "gemini-2.5-flash",
@@ -6800,16 +6978,16 @@ var init_renderer = __esm({
6800
6978
 
6801
6979
  // src/confluence/diff.ts
6802
6980
  import fs24 from "fs/promises";
6803
- import path32 from "path";
6981
+ import path33 from "path";
6804
6982
  import { createHash as createHash5 } from "crypto";
6805
6983
  function hashDescription(description) {
6806
6984
  return createHash5("sha256").update(description, "utf8").digest("hex");
6807
6985
  }
6808
6986
  function syncStateDir(rootDir) {
6809
- return path32.join(rootDir, ".mason");
6987
+ return path33.join(rootDir, ".mason");
6810
6988
  }
6811
6989
  function syncStatePath(rootDir) {
6812
- return path32.join(syncStateDir(rootDir), "confluence-sync.json");
6990
+ return path33.join(syncStateDir(rootDir), "confluence-sync.json");
6813
6991
  }
6814
6992
  async function loadSyncState(rootDir) {
6815
6993
  try {
@@ -6877,8 +7055,8 @@ var init_diff = __esm({
6877
7055
  });
6878
7056
 
6879
7057
  // src/llm/providers.ts
6880
- import { execFile as execFile19, spawn } from "child_process";
6881
- import { promisify as promisify19 } from "util";
7058
+ import { execFile as execFile10, spawn } from "child_process";
7059
+ import { promisify as promisify10 } from "util";
6882
7060
  async function callLLM(config, userMessage, systemPrompt) {
6883
7061
  const model = config.model ?? getDefaultModel(config.provider);
6884
7062
  const system = systemPrompt ?? CLAUDE_MD_SYSTEM_PROMPT;
@@ -7035,12 +7213,12 @@ async function callOpenAIAPI(apiKey, model, system, userMessage) {
7035
7213
  });
7036
7214
  return response.choices[0]?.message?.content ?? "";
7037
7215
  }
7038
- var exec16, CLAUDE_MD_SYSTEM_PROMPT;
7216
+ var exec8, CLAUDE_MD_SYSTEM_PROMPT;
7039
7217
  var init_providers = __esm({
7040
7218
  "src/llm/providers.ts"() {
7041
7219
  "use strict";
7042
7220
  init_config2();
7043
- exec16 = promisify19(execFile19);
7221
+ exec8 = promisify10(execFile10);
7044
7222
  CLAUDE_MD_SYSTEM_PROMPT = `You are Mason, a context engineering tool. You've been given a comprehensive analysis of a codebase including:
7045
7223
  - Git history stats (commit patterns, frequently changed files, stale directories)
7046
7224
  - Project structure (directory layout, file counts by type)
@@ -7382,20 +7560,20 @@ import { z as z24 } from "zod";
7382
7560
  init_repair();
7383
7561
  init_advisory_review();
7384
7562
  import fs25 from "fs/promises";
7385
- import path33 from "path";
7386
- import { execFile as execFile20 } from "child_process";
7387
- import { promisify as promisify20 } from "util";
7563
+ import path34 from "path";
7564
+ import { execFile as execFile11 } from "child_process";
7565
+ import { promisify as promisify11 } from "util";
7388
7566
 
7389
7567
  // src/audit/cli.ts
7390
7568
  init_audit();
7391
7569
  init_types();
7392
7570
  init_repair();
7393
7571
  init_findings();
7394
- import path22 from "path";
7572
+ import path23 from "path";
7395
7573
 
7396
7574
  // src/audit/advisory-cli.ts
7397
7575
  init_advisory_review();
7398
- import path21 from "path";
7576
+ import path22 from "path";
7399
7577
  import { stripVTControlCharacters } from "util";
7400
7578
 
7401
7579
  // src/audit/cli.ts
@@ -7488,8 +7666,8 @@ function formatFixPrompt(report, baselinePath) {
7488
7666
  }
7489
7667
 
7490
7668
  // src/analyzers/git-history.ts
7491
- import { execFile as execFile11 } from "child_process";
7492
- import { promisify as promisify11 } from "util";
7669
+ import { execFile as execFile3 } from "child_process";
7670
+ import { promisify as promisify3 } from "util";
7493
7671
 
7494
7672
  // src/analyzers/base.ts
7495
7673
  import fs15 from "fs/promises";
@@ -7526,7 +7704,7 @@ var BaseAnalyzer = class {
7526
7704
  };
7527
7705
 
7528
7706
  // src/analyzers/git-history.ts
7529
- var exec8 = promisify11(execFile11);
7707
+ var exec = promisify3(execFile3);
7530
7708
  var GitHistoryAnalyzer = class extends BaseAnalyzer {
7531
7709
  name = "git-history";
7532
7710
  async analyze(context) {
@@ -7547,7 +7725,7 @@ var GitHistoryAnalyzer = class extends BaseAnalyzer {
7547
7725
  }
7548
7726
  async git(args, cwd) {
7549
7727
  try {
7550
- const { stdout } = await exec8("git", args, { cwd, maxBuffer: 1e7 });
7728
+ const { stdout } = await exec("git", args, { cwd, maxBuffer: 1e7 });
7551
7729
  return stdout.trim();
7552
7730
  } catch {
7553
7731
  return "";
@@ -7696,12 +7874,10 @@ async function runAll(context) {
7696
7874
  }
7697
7875
 
7698
7876
  // src/utils/git.ts
7699
- import { execFile as execFile12 } from "child_process";
7700
- import { promisify as promisify12 } from "util";
7701
- var exec9 = promisify12(execFile12);
7877
+ init_git_read();
7702
7878
  async function isGitRepo(dir) {
7703
7879
  try {
7704
- await exec9("git", ["rev-parse", "--git-dir"], { cwd: dir });
7880
+ await execGit(["rev-parse", "--git-dir"], { cwd: dir });
7705
7881
  return true;
7706
7882
  } catch {
7707
7883
  return false;
@@ -7710,10 +7886,10 @@ async function isGitRepo(dir) {
7710
7886
 
7711
7887
  // src/mcp/sampler.ts
7712
7888
  init_files();
7713
- import path23 from "path";
7714
- import { execFile as execFile13 } from "child_process";
7715
- import { promisify as promisify13 } from "util";
7716
- var exec10 = promisify13(execFile13);
7889
+ import path24 from "path";
7890
+ import { execFile as execFile4 } from "child_process";
7891
+ import { promisify as promisify4 } from "util";
7892
+ var exec2 = promisify4(execFile4);
7717
7893
  var CONFIG_FILES = [
7718
7894
  // Build & project config
7719
7895
  "package.json",
@@ -7818,8 +7994,8 @@ async function sampleFiles(rootDir, maxFiles = 25) {
7818
7994
  const projectConfig = access.config;
7819
7995
  for (const filePath of projectConfig.alwaysInclude ?? []) {
7820
7996
  if (selected.size >= maxFiles) break;
7821
- const resolvedPath = path23.resolve(rootDir, filePath);
7822
- if (!resolvedPath.startsWith(path23.resolve(rootDir))) continue;
7997
+ const resolvedPath = path24.resolve(rootDir, filePath);
7998
+ if (!resolvedPath.startsWith(path24.resolve(rootDir))) continue;
7823
7999
  selected.set(filePath, "always-include (project config)");
7824
8000
  }
7825
8001
  let configCount = 0;
@@ -7875,7 +8051,7 @@ async function sampleFiles(rootDir, maxFiles = 25) {
7875
8051
  }
7876
8052
  }
7877
8053
  try {
7878
- const { stdout } = await exec10(
8054
+ const { stdout } = await exec2(
7879
8055
  "git",
7880
8056
  ["log", "--since=3 months ago", "--format=", "--name-only"],
7881
8057
  { cwd: rootDir, maxBuffer: 5e6 }
@@ -7885,7 +8061,7 @@ async function sampleFiles(rootDir, maxFiles = 25) {
7885
8061
  if (!line) continue;
7886
8062
  if (line.includes("node_modules") || line.includes("/build/") || line.includes(".gradle") || line.includes("/generated/"))
7887
8063
  continue;
7888
- const ext = path23.extname(line).slice(1);
8064
+ const ext = path24.extname(line).slice(1);
7889
8065
  if (!SOURCE_EXTENSIONS.includes(ext)) continue;
7890
8066
  fileCounts.set(line, (fileCounts.get(line) ?? 0) + 1);
7891
8067
  }
@@ -8005,10 +8181,10 @@ init_drift();
8005
8181
  init_paths();
8006
8182
  init_files();
8007
8183
  import { createHash as createHash4 } from "crypto";
8008
- import { execFile as execFile14 } from "child_process";
8009
- import { promisify as promisify14 } from "util";
8184
+ import { execFile as execFile5 } from "child_process";
8185
+ import { promisify as promisify5 } from "util";
8010
8186
  import { z as z10 } from "zod";
8011
- var exec11 = promisify14(execFile14);
8187
+ var exec3 = promisify5(execFile5);
8012
8188
  var requestSchema = z10.object({
8013
8189
  id: z10.string().regex(/^[a-zA-Z0-9_-]+$/),
8014
8190
  action: z10.enum(["prepare", "accept", "reaffirm", "retire"]).default("prepare"),
@@ -8063,7 +8239,7 @@ async function previews(root, record, state) {
8063
8239
  let diffUnavailable;
8064
8240
  if (state.evidence.historyAvailable && diffPaths.length) {
8065
8241
  try {
8066
- const { stdout } = await exec11("git", ["--literal-pathspecs", "diff", "--no-ext-diff", "--no-textconv", "--no-color", "--no-renames", "-U3", record.refreshedHash, state.evidence.headHash, "--", ...diffPaths], { cwd: root, maxBuffer: 1024 * 1024 });
8242
+ const { stdout } = await exec3("git", ["--literal-pathspecs", "diff", "--no-ext-diff", "--no-textconv", "--no-color", "--no-renames", "-U3", record.refreshedHash, state.evidence.headHash, "--", ...diffPaths], { cwd: root, maxBuffer: 1024 * 1024 });
8067
8243
  diff = stdout.slice(0, 16e3);
8068
8244
  if (stdout.length > diff.length) diffUnavailable = "Diff preview was truncated; inspect the full diff before reviewing.";
8069
8245
  } catch {
@@ -8289,7 +8465,7 @@ function batchIdFor(offset) {
8289
8465
  // src/mcp/tools.ts
8290
8466
  init_init();
8291
8467
  init_onboarding();
8292
- var exec17 = promisify20(execFile20);
8468
+ var exec9 = promisify11(execFile11);
8293
8469
  async function buildContext(dir) {
8294
8470
  return {
8295
8471
  rootDir: dir,
@@ -8297,7 +8473,7 @@ async function buildContext(dir) {
8297
8473
  };
8298
8474
  }
8299
8475
  async function analyzeProject(dir) {
8300
- const rootDir = path33.resolve(dir);
8476
+ const rootDir = path34.resolve(dir);
8301
8477
  const context = await buildContext(rootDir);
8302
8478
  const results = await runAll(context);
8303
8479
  const projectSnapshot = await detectProjectSnapshot(rootDir);
@@ -8352,7 +8528,7 @@ async function detectProjectSnapshot(rootDir) {
8352
8528
  const present = [];
8353
8529
  for (const file of buildFiles) {
8354
8530
  try {
8355
- await fs25.access(path33.join(rootDir, file));
8531
+ await fs25.access(path34.join(rootDir, file));
8356
8532
  present.push(file);
8357
8533
  } catch {
8358
8534
  }
@@ -8394,7 +8570,7 @@ async function detectProjectSnapshot(rootDir) {
8394
8570
  const sourceFiles = await access.list();
8395
8571
  const fileCounts = {};
8396
8572
  for (const file of sourceFiles) {
8397
- const ext = path33.extname(file).slice(1);
8573
+ const ext = path34.extname(file).slice(1);
8398
8574
  fileCounts[ext] = (fileCounts[ext] ?? 0) + 1;
8399
8575
  }
8400
8576
  return {
@@ -8405,7 +8581,7 @@ async function detectProjectSnapshot(rootDir) {
8405
8581
  };
8406
8582
  }
8407
8583
  async function getCodeSamples(dir, count4 = 15) {
8408
- const rootDir = path33.resolve(dir);
8584
+ const rootDir = path34.resolve(dir);
8409
8585
  const samples = await sampleFiles(rootDir, count4);
8410
8586
  const output = {
8411
8587
  note: "These are previews (first ~60 lines). Read the file directly with your own tools to see it in full.",
@@ -8448,7 +8624,7 @@ async function unmappedContextResponse(rootDir) {
8448
8624
  });
8449
8625
  }
8450
8626
  async function getProjectStructure(dir) {
8451
- const rootDir = path33.resolve(dir);
8627
+ const rootDir = path34.resolve(dir);
8452
8628
  const allFiles = await (await createFileAccess(rootDir)).list("**/*");
8453
8629
  const dirInfo = /* @__PURE__ */ new Map();
8454
8630
  for (const file of allFiles) {
@@ -8460,7 +8636,7 @@ async function getProjectStructure(dir) {
8460
8636
  }
8461
8637
  const info = dirInfo.get(dirPath);
8462
8638
  info.fileCount++;
8463
- const ext = path33.extname(file).slice(1);
8639
+ const ext = path34.extname(file).slice(1);
8464
8640
  if (ext) {
8465
8641
  info.extensions.set(ext, (info.extensions.get(ext) ?? 0) + 1);
8466
8642
  }
@@ -8505,7 +8681,7 @@ async function buildChangedFilePreviews(rootDir, changedFiles) {
8505
8681
  return previews2;
8506
8682
  }
8507
8683
  async function getSnapshot(dir) {
8508
- const rootDir = path33.resolve(dir);
8684
+ const rootDir = path34.resolve(dir);
8509
8685
  const snapshot = await loadSnapshot(rootDir);
8510
8686
  if (!snapshot) {
8511
8687
  return unmappedContextResponse(rootDir);
@@ -8606,7 +8782,7 @@ function driftHint(report) {
8606
8782
  return "Read the changed files under staleFeatures/staleFlows, update those entries (fold unmappedFiles into the right features), and call save_snapshot with only the affected entries \u2014 unchanged entries are preserved. Drop ghostFiles from any entries that reference them, and delete features/flows that no longer exist via save_snapshot's removeFeatures/removeFlows.";
8607
8783
  }
8608
8784
  async function checkDrift(dir) {
8609
- const rootDir = path33.resolve(dir);
8785
+ const rootDir = path34.resolve(dir);
8610
8786
  const report = await computeDrift(rootDir);
8611
8787
  if (!report) {
8612
8788
  return JSON.stringify({
@@ -8637,7 +8813,7 @@ async function checkDrift(dir) {
8637
8813
  return JSON.stringify({ exists: true, ...report, verification, hint });
8638
8814
  }
8639
8815
  async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_SIZE, files) {
8640
- const rootDir = path33.resolve(dir);
8816
+ const rootDir = path34.resolve(dir);
8641
8817
  const scoped = files !== void 0 && files.length > 0;
8642
8818
  const scopeFiles = scoped ? sanitizePaths(rootDir, files) : void 0;
8643
8819
  const batch = await prepareSnapshotBatch(dir, offset, batchSize, scopeFiles);
@@ -8684,7 +8860,7 @@ async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_
8684
8860
  );
8685
8861
  }
8686
8862
  async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
8687
- const rootDir = path33.resolve(dir);
8863
+ const rootDir = path34.resolve(dir);
8688
8864
  for (const feat of Object.values(features)) {
8689
8865
  feat.files = sanitizePaths(rootDir, feat.files);
8690
8866
  if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
@@ -8713,7 +8889,7 @@ async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
8713
8889
  );
8714
8890
  }
8715
8891
  async function reduceSnapshot(dir) {
8716
- const rootDir = path33.resolve(dir);
8892
+ const rootDir = path34.resolve(dir);
8717
8893
  const partials = await loadAllPartials(rootDir);
8718
8894
  if (partials.length === 0) {
8719
8895
  return JSON.stringify(
@@ -8775,7 +8951,7 @@ async function reduceSnapshot(dir) {
8775
8951
  );
8776
8952
  }
8777
8953
  async function fullAnalysis(dir) {
8778
- const rootDir = path33.resolve(dir);
8954
+ const rootDir = path34.resolve(dir);
8779
8955
  const [analysis, structure, samples, testMap, snapshot] = await Promise.all([
8780
8956
  analyzeProject(dir),
8781
8957
  getProjectStructure(dir),
@@ -8804,7 +8980,7 @@ function sanitizePaths(rootDir, files) {
8804
8980
  return sanitizeRepoPaths(files);
8805
8981
  }
8806
8982
  async function saveSnapshotData(dir, features, flows, removeFeatures = [], removeFlows = []) {
8807
- const rootDir = path33.resolve(dir);
8983
+ const rootDir = path34.resolve(dir);
8808
8984
  const gitHash = await getCurrentGitHash(rootDir);
8809
8985
  const now = (/* @__PURE__ */ new Date()).toISOString();
8810
8986
  for (const feat of Object.values(features)) {
@@ -8888,7 +9064,7 @@ async function saveSnapshotData(dir, features, flows, removeFeatures = [], remov
8888
9064
  });
8889
9065
  }
8890
9066
  async function getImpact(dir, files) {
8891
- const rootDir = path33.resolve(dir);
9067
+ const rootDir = path34.resolve(dir);
8892
9068
  const { analyzeImpact: analyzeImpact2 } = await Promise.resolve().then(() => (init_impact(), impact_exports));
8893
9069
  const result = await analyzeImpact2(rootDir, files);
8894
9070
  return JSON.stringify(result, null, 2);
@@ -8897,7 +9073,7 @@ var VERIFY_DEFAULT_SAMPLE = 5;
8897
9073
  var VERIFY_MAX_FILES_PER_ENTRY = 8;
8898
9074
  var VERIFY_SKELETON_CHARS = 500;
8899
9075
  async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
8900
- const rootDir = path33.resolve(dir);
9076
+ const rootDir = path34.resolve(dir);
8901
9077
  const snapshot = await loadSnapshot(rootDir);
8902
9078
  if (!snapshot) {
8903
9079
  return JSON.stringify({
@@ -8962,7 +9138,7 @@ async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
8962
9138
  });
8963
9139
  }
8964
9140
  async function saveVerification(dir, verdicts) {
8965
- const rootDir = path33.resolve(dir);
9141
+ const rootDir = path34.resolve(dir);
8966
9142
  const snapshot = await loadSnapshot(rootDir);
8967
9143
  if (!snapshot) {
8968
9144
  return JSON.stringify({ exists: false, hint: "No concept map exists." });
@@ -9000,16 +9176,16 @@ async function saveVerification(dir, verdicts) {
9000
9176
  });
9001
9177
  }
9002
9178
  async function saveDecision(dir, input) {
9003
- const rootDir = path33.resolve(dir);
9179
+ const rootDir = path34.resolve(dir);
9004
9180
  const { upsertDecision: upsertDecision2 } = await Promise.resolve().then(() => (init_decisions(), decisions_exports));
9005
9181
  const result = await upsertDecision2(rootDir, input);
9006
9182
  return JSON.stringify(result);
9007
9183
  }
9008
9184
  async function reviewDecision2(dir, input) {
9009
- return JSON.stringify(await reviewDecision(path33.resolve(dir), input));
9185
+ return JSON.stringify(await reviewDecision(path34.resolve(dir), input));
9010
9186
  }
9011
9187
  async function getContext(dir, task, files) {
9012
- const rootDir = path33.resolve(dir);
9188
+ const rootDir = path34.resolve(dir);
9013
9189
  const { assembleContext: assembleContext2 } = await Promise.resolve().then(() => (init_assemble(), assemble_exports));
9014
9190
  const bundle = await assembleContext2(rootDir, task, files);
9015
9191
  return JSON.stringify(bundle);
@@ -9058,7 +9234,7 @@ async function masonInit(dir, options = {}) {
9058
9234
  return JSON.stringify(await setupProject2(dir, options), null, 2);
9059
9235
  }
9060
9236
  if (options.host) throw new Error("host applies only to mode: setup.");
9061
- const rootDir = path33.resolve(dir);
9237
+ const rootDir = path34.resolve(dir);
9062
9238
  const marker = await loadProjectMarker(rootDir);
9063
9239
  const { loadSetup: loadSetup2 } = await Promise.resolve().then(() => (init_model(), model_exports));
9064
9240
  const setup = await loadSetup2(rootDir);
@@ -9078,7 +9254,7 @@ async function masonInit(dir, options = {}) {
9078
9254
  );
9079
9255
  }
9080
9256
  async function masonCompleteInit(dir, options = {}) {
9081
- const rootDir = path33.resolve(dir);
9257
+ const rootDir = path34.resolve(dir);
9082
9258
  const existing = await loadProjectMarker(rootDir);
9083
9259
  const features = await loadProjectFeatures(rootDir);
9084
9260
  const marker = {
@@ -9187,7 +9363,7 @@ async function masonSetConfluence(input) {
9187
9363
  );
9188
9364
  }
9189
9365
  async function exportToConfluenceTool(dir, overrides) {
9190
- const rootDir = path33.resolve(dir);
9366
+ const rootDir = path34.resolve(dir);
9191
9367
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports2));
9192
9368
  const { exportToConfluence: exportToConfluence2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
9193
9369
  const config = await loadConfig2();
@@ -9226,7 +9402,7 @@ function createMcpServer() {
9226
9402
  const server = new McpServer(
9227
9403
  {
9228
9404
  name: "mason",
9229
- version: "0.17.2"
9405
+ version: "0.17.4"
9230
9406
  },
9231
9407
  {
9232
9408
  instructions: (setupNotice ? setupNotice + "\n\n" : "") + 'Mason retrieves recorded decisions, file impact, and optional feature/flow maps. Use get_context with the task and known files, and get_impact before editing. Save learned rationale and constraints with save_decision; review and commit the local records through the project workflow. These tools work without initialization or a concept map. Consult trust and diagnostics: changed or unknown freshness needs source inspection, failed verification needs correction, and proposals are suggestions and legacy records are unreviewed. Accepted decisions are recorded team constraints, still subject to freshness checks. Use review_decision to prepare code evidence and record only authorized acceptance, reaffirmation, or retirement. Never invent a reviewer or treat these recorded identities as authenticated approval. mason_init returns documentation audit and committed-diff review findings with a quickstart guide. Use mode: "map" only when a full architecture map is requested. A missing map is not a setup failure; use decisions and source evidence. get_snapshot provides architecture navigation when a map is available. The mason-audit and mason-review CLIs also work without setup.'