mason-context 0.19.0 → 0.19.1

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.
@@ -96,6 +96,26 @@ var init_git_read = __esm({
96
96
  }
97
97
  });
98
98
 
99
+ // src/audit/types.ts
100
+ var types_exports = {};
101
+ __export(types_exports, {
102
+ ALL_CHECKS: () => ALL_CHECKS
103
+ });
104
+ var ALL_CHECKS;
105
+ var init_types = __esm({
106
+ "src/audit/types.ts"() {
107
+ "use strict";
108
+ ALL_CHECKS = [
109
+ "deleted-reference",
110
+ "new-module",
111
+ "stale-count",
112
+ "dead-command",
113
+ "deps-changed",
114
+ "decision-anchor-drift"
115
+ ];
116
+ }
117
+ });
118
+
99
119
  // src/utils/files.ts
100
120
  import fs from "fs/promises";
101
121
  import { constants } from "fs";
@@ -140,6 +160,27 @@ async function loadProjectConfig(root) {
140
160
  }
141
161
  config[key] = value[key];
142
162
  }
163
+ if (value.audit !== void 0) {
164
+ const audit = value.audit;
165
+ if (!audit || typeof audit !== "object" || Array.isArray(audit)) throw new Error("Configuration audit must be an object");
166
+ const patterns = (raw2) => {
167
+ if (!Array.isArray(raw2) || raw2.length > 100 || !raw2.every((s) => typeof s === "string" && normalizeRepoPath(s) === s && !s.startsWith("!") && !/[\x00-\x1f]/.test(s))) {
168
+ throw new Error("Audit patterns must be at most 100 repository-relative globs without traversal or negation");
169
+ }
170
+ return raw2;
171
+ };
172
+ config.audit = {};
173
+ if (audit.include !== void 0) config.audit.include = patterns(audit.include);
174
+ if (audit.exclude !== void 0) {
175
+ if (!audit.exclude || typeof audit.exclude !== "object" || Array.isArray(audit.exclude)) throw new Error("audit.exclude must map check names to patterns");
176
+ const { ALL_CHECKS: ALL_CHECKS2 } = await Promise.resolve().then(() => (init_types(), types_exports));
177
+ config.audit.exclude = {};
178
+ for (const [check, globs] of Object.entries(audit.exclude)) {
179
+ if (!ALL_CHECKS2.includes(check)) throw new Error("Unknown audit check: " + check);
180
+ config.audit.exclude[check] = patterns(globs);
181
+ }
182
+ }
183
+ }
143
184
  return config;
144
185
  } catch (error) {
145
186
  if (error.code === "ENOENT") return {};
@@ -439,8 +480,190 @@ var init_store = __esm({
439
480
  }
440
481
  });
441
482
 
442
- // src/audit/commands.ts
483
+ // src/audit/inspection.ts
484
+ import fg2 from "fast-glob";
485
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
443
486
  import path4 from "path";
487
+ function withRepositoryInspection(root, run) {
488
+ return inspections.run({ root: path4.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
489
+ }
490
+ function inspectionRead(root, key, read) {
491
+ const inspection = inspections.getStore();
492
+ if (!inspection || inspection.root !== path4.resolve(root)) return read();
493
+ if (!inspection.reads.has(key)) {
494
+ const pending = read();
495
+ inspection.reads.set(key, pending);
496
+ void pending.catch(() => {
497
+ if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
498
+ });
499
+ }
500
+ return inspection.reads.get(key);
501
+ }
502
+ function inspectionGit(args, options) {
503
+ return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
504
+ }
505
+ async function auditGitPaths(root, kind) {
506
+ const custom = (await loadProjectConfig(root)).audit?.include ?? [];
507
+ const shared = inspections.getStore()?.root === path4.resolve(root) && !custom.length;
508
+ const expanded = fg2.generateTasks(custom).flatMap((task) => task.positive);
509
+ if (expanded.length > 1e3) throw new Error("Audit include patterns expand to more than 1,000 globs");
510
+ const documentSpecs = [...docSpecs, ...expanded.map((pattern) => `:(glob)${pattern}`)];
511
+ const specs = shared ? [...documentSpecs, ...sourceSpecs] : kind === "documents" ? documentSpecs : sourceSpecs;
512
+ const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
513
+ const { stdout } = await inspectionGit(
514
+ ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
515
+ { cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
516
+ );
517
+ const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? custom.length > 0 || /^(readme|agents|claude)\.md$/i.test(path4.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
518
+ if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
519
+ return paths.sort();
520
+ }
521
+ var inspections, sourceSpecs, docSpecs, sourceSuffixes;
522
+ var init_inspection = __esm({
523
+ "src/audit/inspection.ts"() {
524
+ "use strict";
525
+ init_git_read();
526
+ init_files();
527
+ inspections = new AsyncLocalStorage2();
528
+ sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
529
+ docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
530
+ sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
531
+ }
532
+ });
533
+
534
+ // src/audit/inputs.ts
535
+ import fs4 from "fs/promises";
536
+ import * as nativeFs from "fs";
537
+ import path5 from "path";
538
+ import fg3 from "fast-glob";
539
+ async function auditInputPath(root, file) {
540
+ if (file === ".") return fs4.realpath(root);
541
+ try {
542
+ return await storePath(root, file);
543
+ } catch (error) {
544
+ if (error instanceof Error && error.message.startsWith("Symlink")) {
545
+ throw new Error(`Audit input contains a symbolic link: ${file}. Its evidence could not be verified.`, { cause: error });
546
+ }
547
+ throw error;
548
+ }
549
+ }
550
+ async function readAuditInput(root, file) {
551
+ try {
552
+ const value = await readBoundedFile(await auditInputPath(root, file), 10 * 1024 * 1024);
553
+ if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
554
+ return value;
555
+ } catch (error) {
556
+ if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return null;
557
+ throw error;
558
+ }
559
+ }
560
+ async function auditGlob(root, patterns, options) {
561
+ if (Array.isArray(patterns) && !patterns.length) return [];
562
+ const directories = /* @__PURE__ */ new Set();
563
+ const readdir2 = ((directory, opts, callback) => {
564
+ const relative = path5.relative(root, directory).split(path5.sep).join("/") || ".";
565
+ directories.add(relative);
566
+ if (directories.size > MAX_INPUTS) {
567
+ callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
568
+ return;
569
+ }
570
+ auditInputPath(root, relative).then(() => {
571
+ nativeFs.readdir(directory, opts, callback);
572
+ }, callback);
573
+ });
574
+ const lstat2 = ((file, callback) => {
575
+ const relative = path5.relative(root, file).split(path5.sep).join("/") || ".";
576
+ auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
577
+ });
578
+ const stream = fg3.stream(patterns, {
579
+ cwd: root,
580
+ ignore: options.ignore,
581
+ onlyFiles: false,
582
+ onlyDirectories: options.onlyDirectories,
583
+ objectMode: true,
584
+ followSymbolicLinks: options.followSymbolicLinks ?? true,
585
+ fs: { readdir: readdir2, lstat: lstat2 },
586
+ concurrency: 16
587
+ });
588
+ const matches = [];
589
+ try {
590
+ for await (const value of stream) {
591
+ const entry = value;
592
+ const file = entry.path;
593
+ if (options.select && !options.select(file)) continue;
594
+ await auditInputPath(root, file);
595
+ if (!options.onlyDirectories && !entry.dirent.isFile()) continue;
596
+ matches.push(file);
597
+ if (matches.length > MAX_INPUTS) {
598
+ const largest = [...matches.reduce((counts, item) => {
599
+ const dir = item.split("/")[0];
600
+ counts.set(dir, (counts.get(dir) ?? 0) + 1);
601
+ return counts;
602
+ }, /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1]).slice(0, 3);
603
+ throw new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count4]) => `${dir} (${count4})`).join(", ")}. Narrow this check's inputs.`);
604
+ }
605
+ }
606
+ } finally {
607
+ stream.destroy();
608
+ }
609
+ return matches.sort();
610
+ }
611
+ async function gitSourcePaths(root) {
612
+ return auditGitPaths(root, "sources");
613
+ }
614
+ async function gitIgnoredPaths(root, files) {
615
+ if (!files.length) return /* @__PURE__ */ new Set();
616
+ const ignored = /* @__PURE__ */ new Set();
617
+ for (let offset = 0; offset < files.length; offset += 1e3) {
618
+ try {
619
+ const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
620
+ cwd: root,
621
+ maxBuffer: 4 * 1024 * 1024,
622
+ timeout: 1e4,
623
+ input: files.slice(offset, offset + 1e3).join("\0") + "\0"
624
+ });
625
+ for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
626
+ } catch (error) {
627
+ if (error.code !== 1) throw error;
628
+ }
629
+ }
630
+ return ignored;
631
+ }
632
+ var MAX_INPUTS;
633
+ var init_inputs = __esm({
634
+ "src/audit/inputs.ts"() {
635
+ "use strict";
636
+ init_files();
637
+ init_storage();
638
+ init_git_read();
639
+ init_inspection();
640
+ MAX_INPUTS = 1e5;
641
+ }
642
+ });
643
+
644
+ // src/audit/policy.ts
645
+ import fg4 from "fast-glob";
646
+ async function includedCheckPaths(root, paths, exclude) {
647
+ if (!exclude.length) return new Set(paths);
648
+ const included = /* @__PURE__ */ new Set();
649
+ for (let offset = 0; offset < paths.length; offset += 256) {
650
+ for (const file of await auditGlob(root, paths.slice(offset, offset + 256).map((file2) => fg4.escapePath(file2)), {
651
+ ignore: exclude,
652
+ followSymbolicLinks: false,
653
+ label: "Audit check exclusions"
654
+ })) included.add(file);
655
+ }
656
+ return included;
657
+ }
658
+ var init_policy = __esm({
659
+ "src/audit/policy.ts"() {
660
+ "use strict";
661
+ init_inputs();
662
+ }
663
+ });
664
+
665
+ // src/audit/commands.ts
666
+ import path6 from "path";
444
667
  function splitCommands(line) {
445
668
  const parts = [];
446
669
  let quote = "", start = 0;
@@ -476,7 +699,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
476
699
  const cd = text2.match(/^cd\s+(.+)$/);
477
700
  if (cd) {
478
701
  const directory = literal(cd[1]);
479
- cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path4.posix.join(cwd ?? ".", directory);
702
+ cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path6.posix.join(cwd ?? ".", directory);
480
703
  continue;
481
704
  }
482
705
  for (const match of text2.matchAll(COMMAND)) {
@@ -487,7 +710,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
487
710
  if (shell && match.index !== 0 || match[3].startsWith("-")) scopeUnknown = "Unsupported shell wrapper or options before the script.";
488
711
  if (selector) {
489
712
  const selected = literal(selector[1]);
490
- directory = selected === null || cwd === null ? null : path4.posix.join(cwd ?? ".", selected);
713
+ directory = selected === null || cwd === null ? null : path6.posix.join(cwd ?? ".", selected);
491
714
  } else if (flags) scopeUnknown = "Unsupported package-manager options before run: " + flags;
492
715
  const tail = text2.slice(match.index + match[0].length).split(/\s--(?:\s|$)/)[0];
493
716
  if (/(?:^|\s)(?:--workspace(?:s)?|-w|--filter|-F|--prefix|--dir|-C|--if-present)(?:\s|=|$)/.test(tail)) {
@@ -517,7 +740,7 @@ var init_commands = __esm({
517
740
  COMMAND = new RegExp(`\\b(npm|pnpm|yarn)((?:[ \\t]+${TOKEN})*?)[ \\t]+run[ \\t]+([A-Za-z0-9:_.-]+)`, "g");
518
741
  literal = (value) => {
519
742
  const unquoted = value.replace(/^(["'])(.*)\1$/, "$2");
520
- return /[$`*?\[\]{}~\\\x00-\x1f]/.test(unquoted) || path4.posix.isAbsolute(unquoted) ? null : unquoted;
743
+ return /[$`*?\[\]{}~\\\x00-\x1f]/.test(unquoted) || path6.posix.isAbsolute(unquoted) ? null : unquoted;
521
744
  };
522
745
  }
523
746
  });
@@ -822,52 +1045,6 @@ var init_claims = __esm({
822
1045
  }
823
1046
  });
824
1047
 
825
- // src/audit/inspection.ts
826
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
827
- import path5 from "path";
828
- function withRepositoryInspection(root, run) {
829
- return inspections.run({ root: path5.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
830
- }
831
- function inspectionRead(root, key, read) {
832
- const inspection = inspections.getStore();
833
- if (!inspection || inspection.root !== path5.resolve(root)) return read();
834
- if (!inspection.reads.has(key)) {
835
- const pending = read();
836
- inspection.reads.set(key, pending);
837
- void pending.catch(() => {
838
- if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
839
- });
840
- }
841
- return inspection.reads.get(key);
842
- }
843
- function inspectionGit(args, options) {
844
- return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
845
- }
846
- async function auditGitPaths(root, kind) {
847
- const shared = inspections.getStore()?.root === path5.resolve(root);
848
- const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
849
- const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
850
- const { stdout } = await inspectionGit(
851
- ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
852
- { cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
853
- );
854
- const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? /^(readme|agents|claude)\.md$/i.test(path5.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
855
- if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
856
- return paths.sort();
857
- }
858
- var inspections, sourceSpecs, docSpecs, sourceSuffixes;
859
- var init_inspection = __esm({
860
- "src/audit/inspection.ts"() {
861
- "use strict";
862
- init_git_read();
863
- init_files();
864
- inspections = new AsyncLocalStorage2();
865
- sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
866
- docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
867
- sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
868
- }
869
- });
870
-
871
1048
  // src/audit/git.ts
872
1049
  function parseCommitLine(line) {
873
1050
  const parts = line.split(" ");
@@ -953,120 +1130,10 @@ var init_git = __esm({
953
1130
  }
954
1131
  });
955
1132
 
956
- // src/audit/inputs.ts
957
- import fs4 from "fs/promises";
958
- import * as nativeFs from "fs";
959
- import path6 from "path";
960
- import fg2 from "fast-glob";
961
- async function auditInputPath(root, file) {
962
- if (file === ".") return fs4.realpath(root);
963
- try {
964
- return await storePath(root, file);
965
- } catch (error) {
966
- if (error instanceof Error && error.message.startsWith("Symlink")) {
967
- throw new Error(`Audit input contains a symbolic link: ${file}. Its evidence could not be verified.`, { cause: error });
968
- }
969
- throw error;
970
- }
971
- }
972
- async function readAuditInput(root, file) {
973
- try {
974
- const value = await readBoundedFile(await auditInputPath(root, file), 10 * 1024 * 1024);
975
- if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
976
- return value;
977
- } catch (error) {
978
- if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return null;
979
- throw error;
980
- }
981
- }
982
- async function auditGlob(root, patterns, options) {
983
- if (Array.isArray(patterns) && !patterns.length) return [];
984
- const directories = /* @__PURE__ */ new Set();
985
- const readdir2 = ((directory, opts, callback) => {
986
- const relative = path6.relative(root, directory).split(path6.sep).join("/") || ".";
987
- directories.add(relative);
988
- if (directories.size > MAX_INPUTS) {
989
- callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
990
- return;
991
- }
992
- auditInputPath(root, relative).then(() => {
993
- nativeFs.readdir(directory, opts, callback);
994
- }, callback);
995
- });
996
- const lstat2 = ((file, callback) => {
997
- const relative = path6.relative(root, file).split(path6.sep).join("/") || ".";
998
- auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
999
- });
1000
- const stream = fg2.stream(patterns, {
1001
- cwd: root,
1002
- ignore: options.ignore,
1003
- onlyFiles: false,
1004
- onlyDirectories: options.onlyDirectories,
1005
- objectMode: true,
1006
- followSymbolicLinks: options.followSymbolicLinks ?? true,
1007
- fs: { readdir: readdir2, lstat: lstat2 },
1008
- concurrency: 16
1009
- });
1010
- const matches = [];
1011
- try {
1012
- for await (const value of stream) {
1013
- const entry = value;
1014
- const file = entry.path;
1015
- if (options.select && !options.select(file)) continue;
1016
- await auditInputPath(root, file);
1017
- if (!options.onlyDirectories && !entry.dirent.isFile()) continue;
1018
- matches.push(file);
1019
- if (matches.length > MAX_INPUTS) {
1020
- const largest = [...matches.reduce((counts, item) => {
1021
- const dir = item.split("/")[0];
1022
- counts.set(dir, (counts.get(dir) ?? 0) + 1);
1023
- return counts;
1024
- }, /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1]).slice(0, 3);
1025
- throw new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count4]) => `${dir} (${count4})`).join(", ")}. Narrow this check's inputs.`);
1026
- }
1027
- }
1028
- } finally {
1029
- stream.destroy();
1030
- }
1031
- return matches.sort();
1032
- }
1033
- async function gitSourcePaths(root) {
1034
- return auditGitPaths(root, "sources");
1035
- }
1036
- async function gitIgnoredPaths(root, files) {
1037
- if (!files.length) return /* @__PURE__ */ new Set();
1038
- const ignored = /* @__PURE__ */ new Set();
1039
- for (let offset = 0; offset < files.length; offset += 1e3) {
1040
- try {
1041
- const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
1042
- cwd: root,
1043
- maxBuffer: 4 * 1024 * 1024,
1044
- timeout: 1e4,
1045
- input: files.slice(offset, offset + 1e3).join("\0") + "\0"
1046
- });
1047
- for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
1048
- } catch (error) {
1049
- if (error.code !== 1) throw error;
1050
- }
1051
- }
1052
- return ignored;
1053
- }
1054
- var MAX_INPUTS;
1055
- var init_inputs = __esm({
1056
- "src/audit/inputs.ts"() {
1057
- "use strict";
1058
- init_files();
1059
- init_storage();
1060
- init_git_read();
1061
- init_inspection();
1062
- MAX_INPUTS = 1e5;
1063
- }
1064
- });
1065
-
1066
1133
  // src/audit/docs.ts
1067
1134
  import fs5 from "fs/promises";
1068
1135
  import path7 from "path";
1069
- import fg3 from "fast-glob";
1136
+ import fg5 from "fast-glob";
1070
1137
  async function dirtyDocs(resolvedRoot, files) {
1071
1138
  if (!files.length) return /* @__PURE__ */ new Set();
1072
1139
  try {
@@ -1118,7 +1185,7 @@ async function discoverDocPaths(root) {
1118
1185
  const paths = [];
1119
1186
  const unique = [...new Set(candidates)].sort();
1120
1187
  for (let offset = 0; offset < unique.length; offset += 256) {
1121
- paths.push(...await auditGlob(root, unique.slice(offset, offset + 256).map((p) => fg3.escapePath(p)), {
1188
+ paths.push(...await auditGlob(root, unique.slice(offset, offset + 256).map((p) => fg5.escapePath(p)), {
1122
1189
  ignore: excluded,
1123
1190
  followSymbolicLinks: false,
1124
1191
  label: "Documentation discovery"
@@ -1148,6 +1215,7 @@ async function localDocPaths(root) {
1148
1215
  return paths;
1149
1216
  }
1150
1217
  function documentScope(file) {
1218
+ if (!/^(readme|agents|claude)\.md$/i.test(path7.posix.basename(file))) return ".";
1151
1219
  const parent = path7.posix.dirname(file);
1152
1220
  return path7.posix.basename(parent).toLowerCase() === ".claude" ? path7.posix.dirname(parent) : parent;
1153
1221
  }
@@ -1169,7 +1237,7 @@ async function readDocs(resolvedRoot) {
1169
1237
  path: candidate,
1170
1238
  content: content2,
1171
1239
  scope: documentScope(candidate),
1172
- kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : "instructions",
1240
+ kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : /^(agents|claude)\.md$/i.test(path7.posix.basename(candidate)) ? "instructions" : "guide",
1173
1241
  lineCount: content2.split("\n").length,
1174
1242
  lastCommit,
1175
1243
  dirty: dirty.has(candidate),
@@ -1438,22 +1506,6 @@ var init_drift = __esm({
1438
1506
  }
1439
1507
  });
1440
1508
 
1441
- // src/audit/types.ts
1442
- var ALL_CHECKS;
1443
- var init_types = __esm({
1444
- "src/audit/types.ts"() {
1445
- "use strict";
1446
- ALL_CHECKS = [
1447
- "deleted-reference",
1448
- "new-module",
1449
- "stale-count",
1450
- "dead-command",
1451
- "deps-changed",
1452
- "decision-anchor-drift"
1453
- ];
1454
- }
1455
- });
1456
-
1457
1509
  // src/audit/checks/deleted-reference.ts
1458
1510
  import path12 from "path";
1459
1511
  async function checkDeletedReferences(ctx) {
@@ -1522,7 +1574,7 @@ var init_deleted_reference = __esm({
1522
1574
  });
1523
1575
 
1524
1576
  // src/audit/checks/new-module.ts
1525
- import fg4 from "fast-glob";
1577
+ import fg6 from "fast-glob";
1526
1578
  import fs8 from "fs/promises";
1527
1579
  import path13 from "path";
1528
1580
  function escapeRegExp(text2) {
@@ -1584,7 +1636,7 @@ async function collectModuleCandidates(root, combinedDocs) {
1584
1636
  const result = /* @__PURE__ */ new Map();
1585
1637
  for (const dir of parents) {
1586
1638
  const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
1587
- result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1639
+ result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg6.escapePath(dir)}/*`, {
1588
1640
  ignore,
1589
1641
  onlyDirectories: true,
1590
1642
  label: "Module directory discovery",
@@ -1597,7 +1649,7 @@ async function collectModuleCandidates(root, combinedDocs) {
1597
1649
  const files = sources.filter((file) => file.startsWith(dir + "/"));
1598
1650
  let count4 = 0;
1599
1651
  for (let offset = 0; offset < files.length; offset += 256) {
1600
- count4 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) => fg4.escapePath(file)), {
1652
+ count4 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) => fg6.escapePath(file)), {
1601
1653
  ignore,
1602
1654
  followSymbolicLinks: false,
1603
1655
  label: `Source discovery in ${dir}`
@@ -1932,6 +1984,87 @@ var init_dead_command = __esm({
1932
1984
  }
1933
1985
  });
1934
1986
 
1987
+ // src/audit/dependency-claims.ts
1988
+ import path16 from "path";
1989
+ import { parse as parseToml } from "smol-toml";
1990
+ function dependencyText(content2) {
1991
+ return content2.replace(/<!-- mason:start -->[\s\S]*?<!-- mason:end -->/g, (text2) => text2.replace(/[^\n]/g, " ")).replace(/<!--[\s\S]*?-->/g, (text2) => text2.replace(/[^\n]/g, " "));
1992
+ }
1993
+ function declarations(file, source) {
1994
+ const result = /* @__PURE__ */ new Map();
1995
+ if (!source) return result;
1996
+ if (path16.posix.basename(file) === "package.json") {
1997
+ const json2 = JSON.parse(source);
1998
+ for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies", "engines"]) {
1999
+ for (const [name, value] of Object.entries(json2[section] ?? {})) {
2000
+ if (typeof value === "string") result.set(name, [...result.has(name) ? [result.get(name)] : [], value].join("; "));
2001
+ }
2002
+ }
2003
+ } else if (file.endsWith("libs.versions.toml")) {
2004
+ const catalog = parseToml(source);
2005
+ for (const section of ["libraries", "plugins"]) for (const [alias, entry] of Object.entries(catalog[section] ?? {})) {
2006
+ if (typeof entry === "string") {
2007
+ const parts = entry.split(":");
2008
+ if (parts.length >= 3) result.set(parts.slice(0, -1).join(":"), parts.at(-1));
2009
+ } else if (entry && typeof entry === "object") {
2010
+ const name = entry.module ?? (entry.group && entry.name ? `${entry.group}:${entry.name}` : entry.id);
2011
+ const version = typeof entry.version === "string" ? entry.version : catalog.versions?.[entry.version?.ref];
2012
+ if (typeof name === "string") result.set(name, typeof version === "string" ? version : "unspecified");
2013
+ }
2014
+ }
2015
+ } else if (/build\.gradle(?:\.kts)?$/.test(file)) {
2016
+ for (const match of source.matchAll(/["']([\w.-]+:[\w.-]+):([^"'\s]+)["']/g)) result.set(match[1], match[2]);
2017
+ for (const match of source.matchAll(/\bid\s*\(?\s*["']([\w.-]+)["']\s*\)?\s*version\s*["']([^"']+)["']/g)) result.set(match[1], match[2]);
2018
+ }
2019
+ return result;
2020
+ }
2021
+ async function matchDependencyChanges(root, from, head, files, content2) {
2022
+ const matches = [];
2023
+ const lines = dependencyText(content2).split("\n");
2024
+ let incomplete = files.length > 64;
2025
+ for (const file of files.slice(0, 64)) {
2026
+ if (!/(?:^|\/)(?:package\.json|libs\.versions\.toml|build\.gradle(?:\.kts)?)$/.test(file)) {
2027
+ incomplete = true;
2028
+ continue;
2029
+ }
2030
+ try {
2031
+ const read = async (revision) => {
2032
+ const { stdout: entry } = await inspectionGit(["ls-tree", revision, "--", `:(literal)${file}`], { cwd: root, timeout: 1e4 });
2033
+ if (!entry) return "";
2034
+ if (!entry.startsWith("100644 ") && !entry.startsWith("100755 ")) throw new Error("Unsupported manifest mode");
2035
+ return (await inspectionGit(["show", `${revision}:${file}`], { cwd: root, maxBuffer: 1024 * 1024, timeout: 1e4 })).stdout;
2036
+ };
2037
+ const [before, after] = await Promise.all([read(from), read(head)]);
2038
+ const old = declarations(file, before), next = declarations(file, after);
2039
+ for (const dependency of /* @__PURE__ */ new Set([...old.keys(), ...next.keys()])) {
2040
+ if (old.get(dependency) === next.get(dependency)) continue;
2041
+ const escaped = dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2042
+ const mention = new RegExp(`(^|[^a-zA-Z0-9_@./-])${escaped}(?![a-zA-Z0-9_/-]|\\.[a-zA-Z0-9_])`, "i");
2043
+ const line = lines.findIndex((text2) => mention.test(text2));
2044
+ if (line < 0) continue;
2045
+ matches.push({
2046
+ dependency,
2047
+ manifest: file,
2048
+ before: old.get(dependency) ?? null,
2049
+ after: next.get(dependency) ?? null,
2050
+ line: line + 1,
2051
+ excerpt: lines[line].trim().slice(0, 500)
2052
+ });
2053
+ if (matches.length >= 100) return { matches, incomplete: true };
2054
+ }
2055
+ } catch {
2056
+ incomplete = true;
2057
+ }
2058
+ }
2059
+ return { matches, incomplete };
2060
+ }
2061
+ var init_dependency_claims = __esm({
2062
+ "src/audit/dependency-claims.ts"() {
2063
+ "use strict";
2064
+ init_inspection();
2065
+ }
2066
+ });
2067
+
1935
2068
  // src/audit/release-metadata.ts
1936
2069
  async function git(root, args) {
1937
2070
  return (await execGit(args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
@@ -2011,13 +2144,6 @@ async function checkDepsChanged(ctx) {
2011
2144
  });
2012
2145
  continue;
2013
2146
  }
2014
- if (doc.dirty) {
2015
- result.skipped.push({
2016
- check: "deps-changed",
2017
- doc: doc.path,
2018
- reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
2019
- });
2020
- }
2021
2147
  let content2 = doc.content;
2022
2148
  if (doc.dirty) {
2023
2149
  try {
@@ -2030,7 +2156,6 @@ async function checkDepsChanged(ctx) {
2030
2156
  continue;
2031
2157
  }
2032
2158
  }
2033
- if (!hasDependencyContent(content2)) continue;
2034
2159
  const range = await commitsTouchingSince(
2035
2160
  ctx.root,
2036
2161
  doc.lastCommit.hash,
@@ -2054,16 +2179,32 @@ async function checkDepsChanged(ctx) {
2054
2179
  range.commits = relevant;
2055
2180
  range.total = relevant.length;
2056
2181
  if (range.total === 0) continue;
2057
- const latest = range.commits[0];
2182
+ const relevance = await matchDependencyChanges(
2183
+ ctx.root,
2184
+ doc.lastCommit.hash,
2185
+ ctx.headHash,
2186
+ [...new Set(range.commits.flatMap((commit) => commit.files))],
2187
+ content2
2188
+ );
2189
+ if (!relevance.matches.length && !hasDependencyContent(content2)) continue;
2190
+ const match = relevance.matches[0];
2191
+ if (doc.dirty && match) result.skipped.push({
2192
+ check: "deps-changed",
2193
+ doc: doc.path,
2194
+ reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
2195
+ });
2058
2196
  (doc.dirty ? result.suppressedAdvisories : result.advisories).push({
2059
2197
  type: "deps-changed",
2060
- message: `dependency manifests touched by ${range.total} commit${range.total === 1 ? "" : "s"} since ${doc.path} was last committed (latest: ${latest.hash.slice(0, 7)} "${latest.subject}")`,
2061
- anchor: { doc: doc.path, line: null, excerpt: null },
2198
+ ...match ? {} : { resolution: "informational" },
2199
+ message: match ? `${match.dependency} changed from ${match.before ?? "absent"} to ${match.after ?? "removed"} in ${match.manifest}; review ${doc.path}:${match.line}: ${match.excerpt}${relevance.matches.length > 1 ? ` (${relevance.matches.length - 1} more matches in evidence)` : ""}` : `Dependency manifests changed after the document baseline ${doc.lastCommit.hash.slice(0, 7)} (${doc.lastCommit.date.slice(0, 10)}); no specific affected passage was established. Informational only.${relevance.incomplete ? " Some manifest declarations could not be matched." : ""}`,
2200
+ anchor: { doc: doc.path, line: match?.line ?? null, excerpt: match?.excerpt ?? null },
2062
2201
  evidence: {
2063
2202
  kind: "doc-behind-manifests",
2064
2203
  docLastCommit: doc.lastCommit,
2065
2204
  manifestCommits: range.commits.slice(0, MANIFEST_COMMITS_CAP),
2066
- totalCommits: range.total
2205
+ totalCommits: range.total,
2206
+ matches: relevance.matches,
2207
+ matchingIncomplete: relevance.incomplete
2067
2208
  }
2068
2209
  });
2069
2210
  }
@@ -2073,6 +2214,7 @@ var MANIFEST_COMMITS_CAP, MANIFEST_PATHSPECS;
2073
2214
  var init_deps_changed = __esm({
2074
2215
  "src/audit/checks/deps-changed.ts"() {
2075
2216
  "use strict";
2217
+ init_dependency_claims();
2076
2218
  init_docs();
2077
2219
  init_release_metadata();
2078
2220
  init_git();
@@ -2097,7 +2239,7 @@ var init_deps_changed = __esm({
2097
2239
  });
2098
2240
 
2099
2241
  // src/context/lexical.ts
2100
- import path16 from "path";
2242
+ import path17 from "path";
2101
2243
  var init_lexical = __esm({
2102
2244
  "src/context/lexical.ts"() {
2103
2245
  "use strict";
@@ -2281,7 +2423,7 @@ var init_provenance = __esm({
2281
2423
 
2282
2424
  // src/decisions/decisions.ts
2283
2425
  import fs9 from "fs/promises";
2284
- import path17 from "path";
2426
+ import path18 from "path";
2285
2427
  import { createHash } from "crypto";
2286
2428
  import { z as z4 } from "zod";
2287
2429
  async function loadDecisionStore(rootDir) {
@@ -2332,9 +2474,9 @@ var init_decisions = __esm({
2332
2474
  });
2333
2475
 
2334
2476
  // src/decisions/drift.ts
2335
- import path18 from "path";
2477
+ import path19 from "path";
2336
2478
  async function computeDecisionDrift(rootDir, decisions) {
2337
- const resolvedRoot = path18.resolve(rootDir);
2479
+ const resolvedRoot = path19.resolve(rootDir);
2338
2480
  const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
2339
2481
  const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
2340
2482
  const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
@@ -2530,7 +2672,9 @@ var init_findings = __esm({
2530
2672
  kind: z5.literal("doc-behind-manifests"),
2531
2673
  docLastCommit: commitSchema,
2532
2674
  manifestCommits: z5.array(commitSchema.extend({ files: z5.array(z5.string()) })),
2533
- totalCommits: count
2675
+ totalCommits: count,
2676
+ matches: z5.array(z5.object({ dependency: z5.string(), manifest: z5.string(), before: z5.string().nullable(), after: z5.string().nullable(), line: z5.number().int().positive(), excerpt: z5.string() })).optional(),
2677
+ matchingIncomplete: z5.boolean().optional()
2534
2678
  }),
2535
2679
  z5.object({
2536
2680
  kind: z5.literal("decision-anchor"),
@@ -2546,7 +2690,7 @@ var init_findings = __esm({
2546
2690
  type: z5.enum(["deleted-reference", "new-module", "stale-count", "dead-command"]),
2547
2691
  confidence: z5.enum(["certain", "likely"])
2548
2692
  });
2549
- advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z5.literal("recheck").optional() });
2693
+ advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z5.enum(["recheck", "informational"]).optional() });
2550
2694
  checkResultSchema = z5.object({
2551
2695
  issues: z5.array(issueSchema),
2552
2696
  advisories: z5.array(advisorySchema),
@@ -2564,14 +2708,18 @@ async function readRecord(root, id) {
2564
2708
  if (raw === null) return null;
2565
2709
  const record = recordSchema.parse(raw);
2566
2710
  const { digest: stored, ...payload } = record;
2567
- if (record.id !== id || digest(payload) !== stored || record.events.some((event) => findingId(event.finding) !== id || event.evidence.fingerprint !== digest([event.evidence.scope, event.evidence.tree, event.evidence.lastScopeCommit, event.evidence.paths]))) {
2711
+ if (record.id !== id || digest(payload) !== stored || record.events.some((event) => findingId(event.finding) !== id || (event.outcome === "dismissed" ? !event.reasonCode || event.finding.type !== "deps-changed" : !event.reviewer || !!event.reasonCode) || event.evidence.fingerprint !== evidenceFingerprint(event.evidence, event.reasonCode))) {
2568
2712
  throw new Error("Advisory review record is inconsistent or modified; inspect its history.");
2569
2713
  }
2570
2714
  return record;
2571
2715
  }
2572
- function scopes(finding) {
2716
+ function evidenceFingerprint(evidence, reason2) {
2717
+ return digest([evidence.scope, evidence.tree, ...reason2 === "no-dependency-claims" ? [] : [evidence.lastScopeCommit], evidence.paths]);
2718
+ }
2719
+ function scopes(finding, reason2) {
2573
2720
  const files = [finding.anchor.doc], e = finding.evidence;
2574
2721
  if (e.kind === "decision-anchor") throw new Error("Use review_decision for decision acceptance, reaffirmation or retirement.");
2722
+ if (reason2 === "no-dependency-claims" && e.kind === "doc-behind-manifests") return [`:(literal)${finding.anchor.doc}`];
2575
2723
  if (e.kind === "doc-behind-manifests") return [`:(literal)${finding.anchor.doc}`, ...manifestPathspecs(finding.anchor.doc)];
2576
2724
  if (e.kind === "missing-path") files.push(...e.scope?.candidates ?? [e.resolvedPath ?? e.claimed]);
2577
2725
  else if (e.kind === "missing-script") files.push(...e.manifestsChecked, ...e.scope?.candidates ?? []);
@@ -2582,9 +2730,9 @@ function scopes(finding) {
2582
2730
  return `:(literal)${file}`;
2583
2731
  }).sort();
2584
2732
  }
2585
- async function reviewEvidence(root, finding) {
2733
+ async function reviewEvidence(root, finding, reason2) {
2586
2734
  if (normalizeRepoPath(finding.anchor.doc) !== finding.anchor.doc) throw new Error("Invalid advisory document path.");
2587
- const scope = scopes(finding);
2735
+ const scope = scopes(finding, reason2);
2588
2736
  const head = (await git2(root, "rev-parse", "HEAD")).trim();
2589
2737
  const tracked = (await git2(root, "ls-files", "--cached", "-z", "--", ...scope)).split("\0").filter(Boolean);
2590
2738
  if (tracked.length > 2e4) throw new Error("Advisory scope exceeds 20,000 tracked files.");
@@ -2610,7 +2758,7 @@ async function reviewEvidence(root, finding) {
2610
2758
  if (!tracked.includes(file) && await pathExists(root, file)) dirty = true;
2611
2759
  }
2612
2760
  }
2613
- const fingerprint = digest([scope, tree, lastScopeCommit, paths]);
2761
+ const fingerprint = evidenceFingerprint({ head, scope, tree, lastScopeCommit, paths, fingerprint: "" }, reason2);
2614
2762
  if ((await git2(root, "rev-parse", "HEAD")).trim() !== head) throw new Error("HEAD changed while collecting review evidence.");
2615
2763
  return {
2616
2764
  ...evidenceSchema2.parse({ head, scope, tree, lastScopeCommit, paths, fingerprint }),
@@ -2657,7 +2805,7 @@ async function assessAdvisory(root, finding, baselineHead) {
2657
2805
  const record = await readRecord(root, id);
2658
2806
  if (!record) return null;
2659
2807
  const event = record.events.at(-1);
2660
- const current = await reviewEvidence(root, event.finding);
2808
+ const current = await reviewEvidence(root, event.finding, event.reasonCode);
2661
2809
  await git2(root, "merge-base", "--is-ancestor", event.evidence.head, current.head);
2662
2810
  const base = {
2663
2811
  id,
@@ -2667,7 +2815,7 @@ async function assessAdvisory(root, finding, baselineHead) {
2667
2815
  outcome: event.outcome,
2668
2816
  reviewedHead: event.evidence.head
2669
2817
  };
2670
- if (current.dirty || current.fingerprint !== event.evidence.fingerprint || digest(scopes(finding)) !== digest(event.evidence.scope)) return { ...base, status: "reopened", reason: "Relevant document or repository evidence changed after the assessment; prepare a new review." };
2818
+ if (current.dirty || current.fingerprint !== event.evidence.fingerprint || digest(scopes(finding, event.reasonCode)) !== digest(event.evidence.scope)) return { ...base, status: "reopened", reason: "Relevant document or repository evidence changed after the assessment; prepare a new review." };
2671
2819
  return { ...base, status: event.outcome === "deferred" ? "deferred" : "current", reason: event.outcome === "deferred" ? "The review was deferred and remains outstanding." : "An explicit assessment covers the current committed scope. This does not approve an engineering decision or certify correctness." };
2672
2820
  } catch (error) {
2673
2821
  return { id, status: "unverified", reason: error instanceof Error ? error.message : String(error) };
@@ -2684,7 +2832,7 @@ async function advisoryReviewInventory(root) {
2684
2832
  if (names.length > 5e3) throw new Error("Advisory review inventory exceeds 5,000 records.");
2685
2833
  return Promise.all(names.filter((name) => name.endsWith(".json")).sort().map(async (name) => [name, await readAuditInput(root, ".mason/reviews/advisories/" + name)]));
2686
2834
  }
2687
- var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2835
+ var git2, hashSchema, commitSchema2, evidenceSchema2, dismissalReasonSchema, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2688
2836
  var init_advisory_review = __esm({
2689
2837
  "src/audit/advisory-review.ts"() {
2690
2838
  "use strict";
@@ -2710,11 +2858,13 @@ var init_advisory_review = __esm({
2710
2858
  lastScopeCommit: commitSchema2,
2711
2859
  paths: z6.array(z6.object({ path: z6.string(), exists: z6.boolean() })).max(1e3)
2712
2860
  });
2861
+ dismissalReasonSchema = z6.enum(["no-dependency-claims", "unrelated-manifest-change"]);
2713
2862
  eventSchema2 = z6.object({
2714
2863
  at: z6.string().datetime(),
2715
- reviewer: z6.string().trim().min(1).max(200),
2864
+ reviewer: z6.string().trim().min(1).max(200).optional(),
2716
2865
  note: z6.string().trim().min(1).max(2500),
2717
- outcome: z6.enum(["addressed", "inapplicable", "deferred"]),
2866
+ outcome: z6.enum(["addressed", "inapplicable", "deferred", "dismissed"]),
2867
+ reasonCode: dismissalReasonSchema.optional(),
2718
2868
  finding: advisorySchema.refine((finding) => finding.evidence.kind !== "decision-anchor", "Decision findings use decision reviews"),
2719
2869
  evidence: evidenceSchema2
2720
2870
  });
@@ -2726,13 +2876,14 @@ var init_advisory_review = __esm({
2726
2876
  recordPath: z6.string().optional(),
2727
2877
  reviewer: z6.string().optional(),
2728
2878
  note: z6.string().optional(),
2729
- outcome: z6.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired"]).optional(),
2879
+ outcome: z6.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired", "dismissed"]).optional(),
2730
2880
  reviewedHead: z6.string().optional()
2731
2881
  });
2732
2882
  advisoryReviewRequest = z6.object({
2733
2883
  baselinePath: z6.string().min(1),
2734
2884
  findingId: hashSchema,
2735
- action: z6.enum(["prepare", "addressed", "inapplicable", "deferred"]).default("prepare"),
2885
+ action: z6.enum(["prepare", "addressed", "inapplicable", "deferred", "dismiss"]).default("prepare"),
2886
+ reasonCode: dismissalReasonSchema.optional(),
2736
2887
  reviewer: eventSchema2.shape.reviewer.optional(),
2737
2888
  note: eventSchema2.shape.note.optional(),
2738
2889
  reviewToken: hashSchema.optional()
@@ -2743,12 +2894,12 @@ var init_advisory_review = __esm({
2743
2894
 
2744
2895
  // src/audit/audit.ts
2745
2896
  import fs11 from "fs/promises";
2746
- import path19 from "path";
2897
+ import path20 from "path";
2747
2898
  async function computeAudit(rootDir, options = {}) {
2748
2899
  return profilePhase("audit.current", () => auditCurrent(rootDir, options));
2749
2900
  }
2750
2901
  async function auditCurrent(rootDir, options) {
2751
- const resolvedRoot = path19.resolve(rootDir);
2902
+ const resolvedRoot = path20.resolve(rootDir);
2752
2903
  const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
2753
2904
  if (docs.length === 0) return null;
2754
2905
  const report = {
@@ -2782,7 +2933,7 @@ async function auditCurrent(rootDir, options) {
2782
2933
  }
2783
2934
  let decisionsPresent = false;
2784
2935
  try {
2785
- await fs11.access(path19.join(resolvedRoot, ".mason", "decisions"));
2936
+ await fs11.access(path20.join(resolvedRoot, ".mason", "decisions"));
2786
2937
  decisionsPresent = true;
2787
2938
  } catch {
2788
2939
  }
@@ -2794,13 +2945,21 @@ async function auditCurrent(rootDir, options) {
2794
2945
  changesSinceDoc,
2795
2946
  decisionsPresent
2796
2947
  };
2948
+ const policy = (await loadProjectConfig(resolvedRoot)).audit;
2797
2949
  const selected = options.checks ?? ALL_CHECKS;
2798
2950
  for (const name of ALL_CHECKS) {
2799
2951
  if (!selected.includes(name)) continue;
2800
- const { issues, advisories, suppressedAdvisories, skipped } = await (options.runCheck ? options.runCheck(name, ctx) : CHECKS[name](ctx));
2952
+ const exclusions = policy?.exclude?.[name] ?? [];
2953
+ const included = await includedCheckPaths(resolvedRoot, docs.map((doc) => doc.path), exclusions);
2954
+ const excluded = docs.filter((doc) => !included.has(doc.path)).map((doc) => ({ check: name, doc: doc.path }));
2955
+ if (excluded.length) (report.excludedChecks ??= []).push(...excluded);
2956
+ const scoped = { ...ctx, docs: docs.filter((doc) => included.has(doc.path)) };
2957
+ const { issues, advisories, suppressedAdvisories, skipped } = await (options.runCheck ? options.runCheck(name, scoped) : CHECKS[name](scoped));
2801
2958
  report.checksRun.push(name);
2802
2959
  report.issues.push(...issues);
2803
- report.advisories.push(...advisories);
2960
+ const advisoryPaths = await includedCheckPaths(resolvedRoot, [...new Set(advisories.map((f) => f.anchor.doc))], exclusions);
2961
+ report.advisories.push(...advisories.filter((f) => advisoryPaths.has(f.anchor.doc)));
2962
+ for (const f of advisories) if (!advisoryPaths.has(f.anchor.doc)) (report.excludedChecks ??= []).push({ check: name, doc: f.anchor.doc });
2804
2963
  report.suppressedAdvisories.push(...suppressedAdvisories ?? []);
2805
2964
  report.skippedChecks.push(...skipped);
2806
2965
  }
@@ -2812,6 +2971,8 @@ async function auditCurrent(rootDir, options) {
2812
2971
  var init_audit = __esm({
2813
2972
  "src/audit/audit.ts"() {
2814
2973
  "use strict";
2974
+ init_files();
2975
+ init_policy();
2815
2976
  init_profile();
2816
2977
  init_drift();
2817
2978
  init_snapshot();
@@ -2824,7 +2985,7 @@ var init_audit = __esm({
2824
2985
 
2825
2986
  // src/audit/repair.ts
2826
2987
  import fs12 from "fs/promises";
2827
- import path20 from "path";
2988
+ import path21 from "path";
2828
2989
  import { randomUUID as randomUUID2 } from "crypto";
2829
2990
  import { z as z7 } from "zod";
2830
2991
  function recheckable(finding) {
@@ -2845,7 +3006,7 @@ async function docState(root) {
2845
3006
  docs.push([doc, null]);
2846
3007
  }
2847
3008
  }
2848
- return digest(docs);
3009
+ return digest([docs, (await loadProjectConfig(root)).audit]);
2849
3010
  }
2850
3011
  async function stableAudit(root, checks, options = {}) {
2851
3012
  return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
@@ -2911,8 +3072,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
2911
3072
  }
2912
3073
  async function loadRepairBaseline(rootDir, baselinePath) {
2913
3074
  const root = await fs12.realpath(rootDir);
2914
- const declaredRoot = path20.resolve(rootDir);
2915
- const relative = path20.isAbsolute(baselinePath) ? path20.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
3075
+ const declaredRoot = path21.resolve(rootDir);
3076
+ const relative = path21.isAbsolute(baselinePath) ? path21.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
2916
3077
  const stored = baselineSchema.parse(await readStoreJson(root, relative));
2917
3078
  const { digest: savedDigest, ...payload } = stored;
2918
3079
  if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
@@ -2958,6 +3119,9 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
2958
3119
  if (now && ("confidence" in finding || "confidence" in now)) {
2959
3120
  return { ...base, status: "unresolved", reason: "The original check still reports this claim." };
2960
3121
  }
3122
+ if (current.excludedChecks?.some((excluded) => excluded.check === finding.type && excluded.doc === finding.anchor.doc)) {
3123
+ return { ...base, status: "resolved", reason: "Excluded by project audit policy; this is not a correctness assessment. Original evidence is retained." };
3124
+ }
2961
3125
  const skipped = current.skippedChecks.filter((s) => s.check === finding.type && (!s.doc || s.doc === finding.anchor.doc));
2962
3126
  if (!current.checksRun?.includes(finding.type) || skipped.length) {
2963
3127
  return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
@@ -2968,6 +3132,9 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
2968
3132
  if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
2969
3133
  return { ...base, review, status: "review-required", reason: review.reason };
2970
3134
  }
3135
+ if (!("confidence" in finding) && finding.evidence.kind === "doc-behind-manifests" && !finding.evidence.matches?.length && (!now || !("confidence" in now) && now.resolution === "informational")) {
3136
+ return { ...base, status: "resolved", reason: "Manifest recency alone establishes no affected passage. Retained as information; no assessment is required." };
3137
+ }
2971
3138
  if (!("confidence" in finding) && (!recheckable(finding) || now)) {
2972
3139
  return {
2973
3140
  ...base,
@@ -2996,7 +3163,7 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
2996
3163
  const reviewedIds = new Set(current?.advisoryReviews?.filter((review) => review.status === "current").map((review) => review.id));
2997
3164
  const counts = { resolved: 0, unresolved: 0, "review-required": 0, unverified: 0 };
2998
3165
  for (const f of findings) counts[f.status]++;
2999
- const incomplete = diagnostics.length > 0 || counts.unverified > 0 || counts["review-required"] > 0 || (current?.skippedChecks.length ?? 0) > 0 || newFindings.some((f) => !("confidence" in f) && !reviewedIds.has(findingId(f)));
3166
+ const incomplete = diagnostics.length > 0 || counts.unverified > 0 || counts["review-required"] > 0 || (current?.skippedChecks.length ?? 0) > 0 || newFindings.some((f) => !("confidence" in f) && f.resolution !== "informational" && !reviewedIds.has(findingId(f)));
3000
3167
  const issuesRemain = counts.unresolved > 0 || newFindings.some((f) => "confidence" in f);
3001
3168
  return {
3002
3169
  version: 1,
@@ -3039,13 +3206,14 @@ var init_repair = __esm({
3039
3206
  docs: z7.array(z7.object({
3040
3207
  path: z7.string().refine((value) => normalizeRepoPath(value) === value),
3041
3208
  scope: z7.string().optional(),
3042
- kind: z7.enum(["instructions", "readme"]).optional(),
3209
+ kind: z7.enum(["instructions", "readme", "guide"]).optional(),
3043
3210
  lastCommit: commitSchema.nullable(),
3044
3211
  dirty: z7.boolean(),
3045
3212
  lineCount: count2
3046
3213
  })).nonempty(),
3047
3214
  decisionsChecked: z7.boolean(),
3048
3215
  clean: z7.boolean(),
3216
+ excludedChecks: z7.array(z7.object({ check: checkSchema, doc: z7.string() })).optional(),
3049
3217
  issues: z7.array(issueSchema),
3050
3218
  advisories: z7.array(advisorySchema),
3051
3219
  suppressedAdvisories: z7.array(advisorySchema).optional(),
@@ -3071,9 +3239,9 @@ var init_repair = __esm({
3071
3239
 
3072
3240
  // src/automation/evidence.ts
3073
3241
  import fs13 from "fs/promises";
3074
- import path21 from "path";
3242
+ import path22 from "path";
3075
3243
  import { createHash as createHash3 } from "crypto";
3076
- import fg5 from "fast-glob";
3244
+ import fg7 from "fast-glob";
3077
3245
  import { z as z8 } from "zod";
3078
3246
  async function git3(root, ...args) {
3079
3247
  return profilePhase("automation.git", async () => (await execGit(args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
@@ -3091,6 +3259,11 @@ async function readInputs(root) {
3091
3259
  }
3092
3260
  async function collectInputs(root) {
3093
3261
  const docPaths = await discoverDocPaths(root);
3262
+ const auditPolicy = (await loadProjectConfig(root)).audit;
3263
+ const allowed = /* @__PURE__ */ new Map();
3264
+ for (const name of ["deleted-reference", "new-module", "stale-count", "dead-command"]) {
3265
+ allowed.set(name, await includedCheckPaths(root, docPaths, auditPolicy?.exclude?.[name] ?? []));
3266
+ }
3094
3267
  const [headText, docStatus, shallowPath, replacements] = await Promise.all([
3095
3268
  git3(root, "rev-parse", "HEAD"),
3096
3269
  docPaths.length ? git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...docPaths.map((p) => `:(literal)${p}`)) : "",
@@ -3100,7 +3273,7 @@ async function collectInputs(root) {
3100
3273
  const head = headText.trim();
3101
3274
  let shallow = null;
3102
3275
  try {
3103
- shallow = await fs13.readFile(path21.resolve(root, shallowPath.trim()), "utf8");
3276
+ shallow = await fs13.readFile(path22.resolve(root, shallowPath.trim()), "utf8");
3104
3277
  } catch (error) {
3105
3278
  if (error.code !== "ENOENT") throw error;
3106
3279
  }
@@ -3119,18 +3292,18 @@ async function collectInputs(root) {
3119
3292
  docContents.push([file, text2]);
3120
3293
  const parsed = { path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") };
3121
3294
  parsedDocs.push(parsed);
3122
- for (const claim of parsed.claims.paths) {
3295
+ for (const claim of allowed.get("deleted-reference").has(file) ? parsed.claims.paths : []) {
3123
3296
  const scope = pathClaimScope(file, claim);
3124
3297
  if (scope?.candidates.some(gitMetadataPath)) continue;
3125
3298
  for (const candidate of scope?.candidates ?? []) {
3126
3299
  if (optionalMasonPath(candidate)) continue;
3127
- const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path21.posix.dirname(candidate))]);
3300
+ const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path22.posix.dirname(candidate))]);
3128
3301
  claims.push([candidate, filePresent, parentPresent]);
3129
3302
  }
3130
3303
  }
3131
3304
  }
3132
- const combinedDocs = moduleDocumentation(parsedDocs);
3133
- const countClaims = parsedDocs.flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
3305
+ const combinedDocs = moduleDocumentation(parsedDocs.filter((doc) => allowed.get("new-module").has(doc.path)));
3306
+ const countClaims = parsedDocs.filter((doc) => allowed.get("stale-count").has(doc.path)).flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
3134
3307
  const decisionDirectory = await storePath(root, ".mason/decisions");
3135
3308
  const decisionPresence = await fs13.lstat(decisionDirectory).then((stat) => stat.isDirectory() ? "directory" : "file", (error) => {
3136
3309
  if (error.code === "ENOENT") return "absent";
@@ -3139,8 +3312,8 @@ async function collectInputs(root) {
3139
3312
  const [modules, counts, commands2, decisionFiles] = await Promise.all([
3140
3313
  combinedDocs ? moduleCandidates(root, combinedDocs) : [],
3141
3314
  Promise.all(countClaims.map(({ doc, claim }) => resolveDocCountSource(root, doc, claim))),
3142
- commandInputs(root, parsedDocs),
3143
- fg5(".mason/decisions/*.json", { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false })
3315
+ commandInputs(root, parsedDocs.filter((doc) => allowed.get("dead-command").has(doc.path))),
3316
+ fg7(".mason/decisions/*.json", { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false })
3144
3317
  ]);
3145
3318
  const decisions = await Promise.all(decisionFiles.sort().map(async (file) => {
3146
3319
  await storePath(root, file);
@@ -3150,7 +3323,7 @@ async function collectInputs(root) {
3150
3323
  git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
3151
3324
  git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
3152
3325
  ]) : ["", ""];
3153
- const common = [6, engineVersion, head, shallow, replacements, docContents];
3326
+ const common = [7, engineVersion, head, shallow, replacements, docContents, auditPolicy];
3154
3327
  const keys = {
3155
3328
  "deleted-reference": hash([common, claims, docStatus]),
3156
3329
  "new-module": hash([common, modules]),
@@ -3197,6 +3370,8 @@ var engineVersion, hash, content, cacheSchema;
3197
3370
  var init_evidence = __esm({
3198
3371
  "src/automation/evidence.ts"() {
3199
3372
  "use strict";
3373
+ init_policy();
3374
+ init_files();
3200
3375
  init_git_read();
3201
3376
  init_profile();
3202
3377
  init_docs();
@@ -3210,7 +3385,7 @@ var init_evidence = __esm({
3210
3385
  init_dead_command();
3211
3386
  init_storage();
3212
3387
  init_advisory_review();
3213
- engineVersion = true ? "0.19.0" : "development";
3388
+ engineVersion = true ? "0.19.1" : "development";
3214
3389
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
3215
3390
  content = readAuditInput;
3216
3391
  cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
@@ -3768,9 +3943,9 @@ var init_runtime = __esm({
3768
3943
 
3769
3944
  // src/automation/hook-root.ts
3770
3945
  import fs15 from "fs/promises";
3771
- import path22 from "path";
3946
+ import path23 from "path";
3772
3947
  async function repositoryRoot(dir) {
3773
- if (!path22.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
3948
+ if (!path23.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
3774
3949
  try {
3775
3950
  return await fs15.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
3776
3951
  } catch (error) {
@@ -3802,7 +3977,7 @@ var init_hook_root = __esm({
3802
3977
  // src/setup/model.ts
3803
3978
  import { z as z11 } from "zod";
3804
3979
  import fs16 from "fs/promises";
3805
- import path23 from "path";
3980
+ import path24 from "path";
3806
3981
  async function loadSetup(root) {
3807
3982
  const raw = await readStoreJson(root, SETUP_PATH);
3808
3983
  if (raw === null) return null;
@@ -3811,7 +3986,7 @@ async function loadSetup(root) {
3811
3986
  async function effectiveSetup(root) {
3812
3987
  const local = await loadSetup(root);
3813
3988
  if (local) return { root, setup: local };
3814
- const common = await fs16.realpath(path23.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
3989
+ const common = await fs16.realpath(path24.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
3815
3990
  const own = await fs16.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
3816
3991
  if (common === own) return { root, setup: null };
3817
3992
  const primary = (await git3(root, "worktree", "list", "--porcelain", "-z")).split("\0\0")[0].split("\0");
@@ -3824,7 +3999,7 @@ async function effectiveSetup(root) {
3824
3999
  if (/must be run in a work tree/.test(String(error.stderr))) return { root, setup: null };
3825
4000
  throw error;
3826
4001
  }
3827
- const inheritedCommon = await fs16.realpath(path23.resolve(
4002
+ const inheritedCommon = await fs16.realpath(path24.resolve(
3828
4003
  inheritedRoot,
3829
4004
  (await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim()
3830
4005
  ));
@@ -3930,7 +4105,7 @@ var init_launcher = __esm({
3930
4105
  "use strict";
3931
4106
  init_evidence();
3932
4107
  init_model();
3933
- executingVersion = () => true ? "0.19.0" : "development";
4108
+ executingVersion = () => true ? "0.19.1" : "development";
3934
4109
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3935
4110
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3936
4111
  }
@@ -4146,7 +4321,7 @@ __export(files_exports, {
4146
4321
  readText: () => readText
4147
4322
  });
4148
4323
  import fs19 from "fs/promises";
4149
- import path24 from "path";
4324
+ import path25 from "path";
4150
4325
  import { randomUUID as randomUUID5 } from "crypto";
4151
4326
  async function readText(root, file) {
4152
4327
  try {
@@ -4179,7 +4354,7 @@ async function applyEdit(root, edit) {
4179
4354
  return true;
4180
4355
  }
4181
4356
  const file = await storePath(root, edit.path, true);
4182
- const temporary = path24.join(path24.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
4357
+ const temporary = path25.join(path25.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
4183
4358
  try {
4184
4359
  const mode = await fs19.stat(file).then((s) => s.mode & 511, () => 384);
4185
4360
  const handle = await fs19.open(temporary, "wx", mode);
@@ -4304,9 +4479,11 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
4304
4479
  - For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
4305
4480
  - \`mason_init\` returns documentation audit and committed-diff review results, plus a short setup guide. Pass \`evidence\` with local CI manifest paths to include test and analysis results; the CLI equivalent is \`mason-review --evidence <manifest>\`. State skipped, unavailable, stale, or unknown checks explicitly. Related accepted decisions identify review context, not proven violations.
4306
4481
 
4307
- - When documentation repair is authorized, use \`mason_repair(action: "prepare")\` before edits, keep its baselinePath, and use \`mason_repair(action: "verify", baselinePath)\` after edits and any final doc commit. Report every original finding's outcome and any new findings. Suppressed advisories remain unresolved; editing a doc does not approve it.
4482
+ - When documentation repair is authorized, use \`mason_repair(action: "prepare")\` before edits, keep its baselinePath, and use \`mason_repair(action: "verify", baselinePath)\` after edits and any final doc commit. Report every original finding's outcome and any new findings. Specific suppressed advisories remain unresolved; editing a doc does not approve it. Informational dependency notices require no review.
4308
4483
  - When advisory assessment is authorized, use \`review_advisory(action: "prepare", baselinePath, findingId)\` to inspect original evidence and history. Record \`addressed\`, \`inapplicable\`, or \`deferred\` with the actual reviewer, reason in note, and returned reviewToken only after relevant edits are committed. Commit the assessment and verify the same baseline again. Relevant evidence changes reopen it; deferral stays outstanding. Decision findings use \`review_decision\`; advisory closure never approves a decision.
4309
4484
 
4485
+ - For a dependency advisory, \`review_advisory(action: "dismiss", baselinePath, findingId, reasonCode, note)\` records a one-step dismissal without reviewer or token. Use \`no-dependency-claims\` for a document with no dependency claims (reopens on document content changes), or \`unrelated-manifest-change\` for reviewed changes that do not affect its guidance (reopens on document or manifest changes). Keep the relevant scope committed; commit the dismissal to share it. This never approves a decision.
4486
+
4310
4487
  - When Mason automation is installed, use \`mason_automation(action: "status")\` to inspect configured hooks and observed events, and \`mason_automation(action: "check")\` to resume its retained repair evidence. CLI fallback: \`mason-auto status\` / \`mason-auto check\`. Preserve existing baselines across sessions. Automatic checks do not authorize unrelated repairs or approve advisories.
4311
4488
 
4312
4489
  Inspect source for what the retrieved context does not answer.
@@ -4602,9 +4779,9 @@ __export(teardown_exports, {
4602
4779
  });
4603
4780
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
4604
4781
  import fs20 from "fs/promises";
4605
- import path25 from "path";
4782
+ import path26 from "path";
4606
4783
  import { parse as parse3, stringify as stringify2 } from "smol-toml";
4607
- import fg6 from "fast-glob";
4784
+ import fg8 from "fast-glob";
4608
4785
  import { z as z18 } from "zod";
4609
4786
  async function planTeardown(root, selected) {
4610
4787
  const edits = [], diagnostics = [];
@@ -4625,7 +4802,7 @@ async function planTeardown(root, selected) {
4625
4802
  const setupBefore = await read(SETUP_PATH);
4626
4803
  const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
4627
4804
  const linked = (await workspace(root)).gitDir !== await fs20.realpath(
4628
- path25.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
4805
+ path26.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
4629
4806
  );
4630
4807
  const inactiveBefore = linked && !setup ? await read(INACTIVE_HOSTS_PATH) : null;
4631
4808
  const inactive = inactiveBefore === null ? { version: 1, hosts: [] } : inactiveHostsSchema.parse(JSON.parse(inactiveBefore));
@@ -4748,7 +4925,7 @@ async function planTeardown(root, selected) {
4748
4925
  if (diagnostics.length) edits.splice(instructionEditStart);
4749
4926
  if (!diagnostics.length) {
4750
4927
  const cleanup = [];
4751
- const receiptFiles = await fg6(
4928
+ const receiptFiles = await fg8(
4752
4929
  selected.map((host) => ".mason/reports/automation/*/setup-" + host + ".json"),
4753
4930
  { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false }
4754
4931
  );
@@ -5222,10 +5399,10 @@ var init_check = __esm({
5222
5399
  });
5223
5400
 
5224
5401
  // src/review/evidence.ts
5225
- import path26 from "path";
5402
+ import path27 from "path";
5226
5403
  import { z as z22 } from "zod";
5227
5404
  function relativeArtifact(root, file) {
5228
- const relative = path26.isAbsolute(file) ? path26.relative(root, file) : file;
5405
+ const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
5229
5406
  const normalized = normalizeRepoPath(relative);
5230
5407
  if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
5231
5408
  return normalized;
@@ -5322,7 +5499,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5322
5499
  const report = await readStoreJson(root, file);
5323
5500
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
5324
5501
  const sourceRoot = check.sourceRoot ?? root;
5325
- if (!path26.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5502
+ if (!path27.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5326
5503
  const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
5327
5504
  result.outcome = parsed.outcome;
5328
5505
  result.counts = parsed.counts;
@@ -5444,7 +5621,7 @@ var init_evidence2 = __esm({
5444
5621
 
5445
5622
  // src/review/review.ts
5446
5623
  import fs21 from "fs/promises";
5447
- import path27 from "path";
5624
+ import path28 from "path";
5448
5625
  import { execFile as execFile4 } from "child_process";
5449
5626
  import { promisify as promisify4 } from "util";
5450
5627
  async function resolveMergeBase(resolvedRoot, base, head) {
@@ -5473,7 +5650,7 @@ function anchorsTouched(record, changedFiles) {
5473
5650
  return matchingPaths(record.files, changedFiles);
5474
5651
  }
5475
5652
  async function computeReview(rootDir, base, options = {}) {
5476
- const resolvedRoot = path27.resolve(rootDir);
5653
+ const resolvedRoot = path28.resolve(rootDir);
5477
5654
  const head = await getCurrentGitHash(resolvedRoot);
5478
5655
  if (head === "unknown") return null;
5479
5656
  const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
@@ -5523,7 +5700,7 @@ async function computeReview(rootDir, base, options = {}) {
5523
5700
  analyzed,
5524
5701
  async (relPath) => {
5525
5702
  try {
5526
- await fs21.access(path27.join(resolvedRoot, relPath));
5703
+ await fs21.access(path28.join(resolvedRoot, relPath));
5527
5704
  return true;
5528
5705
  } catch {
5529
5706
  return false;