mason-context 0.19.0 → 0.19.2

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 {};
@@ -394,8 +435,229 @@ var init_store_lock = __esm({
394
435
  }
395
436
  });
396
437
 
397
- // src/automation/store.ts
438
+ // src/audit/findings.ts
439
+ import { createHash } from "crypto";
398
440
  import { z } from "zod";
441
+ function findingId(finding) {
442
+ const e = finding.evidence;
443
+ let key;
444
+ switch (e.kind) {
445
+ case "missing-path":
446
+ key = e.resolvedPath && e.resolvedPath !== e.claimed ? [e.claimed, e.resolvedPath] : e.claimed;
447
+ break;
448
+ case "unmentioned-dir":
449
+ key = e.dir;
450
+ break;
451
+ case "count-mismatch":
452
+ key = [e.unit.replace(/s$/, ""), e.countedFrom];
453
+ break;
454
+ case "missing-script":
455
+ key = e.scope && e.scope.directory !== "." ? [e.scriptName, e.scope.directory] : e.scriptName;
456
+ break;
457
+ case "doc-behind-manifests":
458
+ key = null;
459
+ break;
460
+ case "decision-anchor":
461
+ key = [e.decisionId, e.provenance?.revision, e.provenance?.approval];
462
+ break;
463
+ }
464
+ return digest([finding.type, finding.anchor.doc, key]);
465
+ }
466
+ var digest, checkSchema, commitSchema, anchorSchema, count, scopeSchema, evidenceSchema, findingSchema, issueSchema, advisorySchema, checkResultSchema;
467
+ var init_findings = __esm({
468
+ "src/audit/findings.ts"() {
469
+ "use strict";
470
+ digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
471
+ checkSchema = z.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"]);
472
+ commitSchema = z.object({ hash: z.string().regex(/^[a-f0-9]{40,64}$/), date: z.string(), subject: z.string() });
473
+ anchorSchema = z.object({ doc: z.string(), line: z.number().int().positive().nullable(), excerpt: z.string().nullable() });
474
+ count = z.number().int().nonnegative();
475
+ scopeSchema = z.object({
476
+ basis: z.enum(["repository", "document", "document-link", "explicit"]),
477
+ directory: z.string(),
478
+ candidates: z.array(z.string())
479
+ });
480
+ evidenceSchema = z.discriminatedUnion("kind", [
481
+ z.object({
482
+ kind: z.literal("missing-path"),
483
+ claimed: z.string(),
484
+ renamedTo: z.string().nullable(),
485
+ deletedInCommit: commitSchema.nullable(),
486
+ everTracked: z.boolean(),
487
+ parentDirExists: z.boolean(),
488
+ resolvedPath: z.string().optional(),
489
+ scope: scopeSchema.optional()
490
+ }),
491
+ z.object({
492
+ kind: z.literal("unmentioned-dir"),
493
+ dir: z.string(),
494
+ sourceFileCount: count,
495
+ firstCommit: commitSchema.nullable(),
496
+ checkedDocs: z.array(z.string())
497
+ }),
498
+ z.object({
499
+ kind: z.literal("count-mismatch"),
500
+ claimed: count,
501
+ actual: count,
502
+ unit: z.string(),
503
+ countedFrom: z.string(),
504
+ members: z.array(z.string())
505
+ }),
506
+ z.object({
507
+ kind: z.literal("missing-script"),
508
+ scriptName: z.string(),
509
+ invocation: z.string(),
510
+ manifestsChecked: z.array(z.string()),
511
+ availableScripts: z.array(z.string()),
512
+ scope: scopeSchema.optional()
513
+ }),
514
+ z.object({
515
+ kind: z.literal("doc-behind-manifests"),
516
+ docLastCommit: commitSchema,
517
+ manifestCommits: z.array(commitSchema.extend({ files: z.array(z.string()) })),
518
+ totalCommits: count,
519
+ matches: z.array(z.object({ dependency: z.string(), manifest: z.string(), before: z.string().nullable(), after: z.string().nullable(), line: z.number().int().positive(), excerpt: z.string() })).optional(),
520
+ matchingIncomplete: z.boolean().optional()
521
+ }),
522
+ z.object({
523
+ kind: z.literal("decision-anchor"),
524
+ decisionId: z.string(),
525
+ title: z.string(),
526
+ changedFiles: z.array(z.string()),
527
+ refreshedHash: z.string(),
528
+ provenance: z.object({}).passthrough().optional()
529
+ })
530
+ ]);
531
+ findingSchema = z.object({ message: z.string(), anchor: anchorSchema, evidence: evidenceSchema });
532
+ issueSchema = findingSchema.extend({
533
+ type: z.enum(["deleted-reference", "new-module", "stale-count", "dead-command"]),
534
+ confidence: z.enum(["certain", "likely"])
535
+ });
536
+ advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z.enum(["recheck", "informational"]).optional() });
537
+ checkResultSchema = z.object({
538
+ issues: z.array(issueSchema),
539
+ advisories: z.array(advisorySchema),
540
+ suppressedAdvisories: z.array(advisorySchema).optional(),
541
+ skipped: z.array(z.object({ check: z.string(), reason: z.string(), doc: z.string().optional() }))
542
+ });
543
+ }
544
+ });
545
+
546
+ // src/automation/notifications.ts
547
+ import { z as z2 } from "zod";
548
+ function condition(finding) {
549
+ const source = finding.current ?? finding.original;
550
+ const e = source.evidence;
551
+ const id = e.kind === "decision-anchor" ? digest([source.type, source.anchor.doc, e.decisionId]) : finding.id;
552
+ const result = { level: "confidence" in source ? 2 : 1, facts: [] };
553
+ switch (e.kind) {
554
+ case "count-mismatch":
555
+ result.magnitude = Math.abs(e.actual - e.claimed);
556
+ break;
557
+ case "unmentioned-dir":
558
+ result.magnitude = e.sourceFileCount;
559
+ break;
560
+ case "decision-anchor":
561
+ result.facts = [...e.changedFiles].sort();
562
+ break;
563
+ case "doc-behind-manifests":
564
+ result.facts = (e.matches ?? []).map((m) => JSON.stringify([m.manifest, m.dependency, m.after])).sort();
565
+ break;
566
+ }
567
+ return { id, condition: result };
568
+ }
569
+ function notificationConditions(findings) {
570
+ const active = findings.filter((f) => f.status !== "resolved");
571
+ const currentIds = new Set(active.filter((f) => f.current).map((f) => condition(f).id));
572
+ const result = {};
573
+ for (const f of active) {
574
+ const value = condition(f);
575
+ if (!f.current && currentIds.has(value.id)) continue;
576
+ const previous = result[value.id];
577
+ result[value.id] = previous ? {
578
+ level: Math.max(previous.level, value.condition.level),
579
+ facts: [.../* @__PURE__ */ new Set([...previous.facts, ...value.condition.facts])].sort(),
580
+ ...previous.magnitude !== void 0 || value.condition.magnitude !== void 0 ? { magnitude: Math.max(previous.magnitude ?? 0, value.condition.magnitude ?? 0) } : {}
581
+ } : value.condition;
582
+ }
583
+ return result;
584
+ }
585
+ function createNotificationState(findings, head) {
586
+ return { version: 1, baselineHead: head, baseline: notificationConditions(findings), delivered: {}, cleared: [] };
587
+ }
588
+ function worsened(current, previous) {
589
+ return !previous || current.level > previous.level || (current.magnitude ?? 0) > (previous.magnitude ?? 0) || current.facts.some((fact) => !previous.facts.includes(fact));
590
+ }
591
+ function completionFindings(state, findings) {
592
+ const current = notificationConditions(findings);
593
+ const cleared = new Set(state.cleared);
594
+ const resolvedIds = new Set(findings.filter((f) => f.status === "resolved").map((f) => condition(f).id));
595
+ for (const id of Object.keys(state.baseline)) if (resolvedIds.has(id) && !current[id]) cleared.add(id);
596
+ state.cleared = [...cleared];
597
+ const selected = [];
598
+ const selectedIds = /* @__PURE__ */ new Set();
599
+ const delivered = { ...state.delivered };
600
+ for (const finding of findings) {
601
+ const value = condition(finding);
602
+ const previous = state.delivered[value.id];
603
+ value.condition = current[value.id] ?? value.condition;
604
+ if (finding.status === "unverified" || !finding.current && finding.status !== "resolved") {
605
+ continue;
606
+ }
607
+ if (finding.status === "resolved") {
608
+ if (!current[value.id]) delete delivered[value.id];
609
+ continue;
610
+ }
611
+ const eligible = cleared.has(value.id) || worsened(value.condition, state.baseline[value.id]);
612
+ if (!eligible) continue;
613
+ if (!selectedIds.has(value.id) && worsened(value.condition, previous)) {
614
+ selectedIds.add(value.id);
615
+ const source = finding.current;
616
+ selected.push(source.evidence.kind === "decision-anchor" ? { ...finding, current: { ...source, evidence: { ...source.evidence, changedFiles: value.condition.facts } } } : finding);
617
+ if (selected.length <= MAX_COMPLETION_FINDINGS) delivered[value.id] = value.condition;
618
+ }
619
+ }
620
+ state.delivered = delivered;
621
+ return selected;
622
+ }
623
+ function completionSummary(findings, reportPath) {
624
+ if (!findings.length) return null;
625
+ const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
626
+ return [
627
+ `Mason: ${findings.length} new or worsened finding(s) since the retained baseline.`,
628
+ ...findings.slice(0, MAX_COMPLETION_FINDINGS).map((f) => {
629
+ const source = f.current ?? f.original;
630
+ const detail = source.evidence.kind === "decision-anchor" ? `Changes to ${source.evidence.changedFiles.join(", ")} may affect decision "${source.evidence.title}". Inspect the diff if relevant; acceptance is not required.` : source.message;
631
+ return `[${f.status === "unresolved" ? "issue" : "advisory"}] ${safe(source.anchor.doc)}: ${safe(detail)}`;
632
+ }),
633
+ ...findings.length > MAX_COMPLETION_FINDINGS ? [`${findings.length - MAX_COMPLETION_FINDINGS} more in the report; pending later feedback.`] : [],
634
+ `Evidence: ${reportPath}. Explicit audit: mason_automation(action: "check").`,
635
+ "Inspect findings within the authorized work. Concurrent changes may also contribute; this summary does not establish who introduced them."
636
+ ].join("\n");
637
+ }
638
+ var conditionSchema, MAX_COMPLETION_FINDINGS, notificationStateSchema;
639
+ var init_notifications = __esm({
640
+ "src/automation/notifications.ts"() {
641
+ "use strict";
642
+ init_findings();
643
+ conditionSchema = z2.object({
644
+ level: z2.number(),
645
+ facts: z2.array(z2.string()),
646
+ magnitude: z2.number().optional()
647
+ });
648
+ MAX_COMPLETION_FINDINGS = 4;
649
+ notificationStateSchema = z2.object({
650
+ version: z2.literal(1),
651
+ baselineHead: z2.string(),
652
+ baseline: z2.record(conditionSchema),
653
+ delivered: z2.record(conditionSchema),
654
+ cleared: z2.array(z2.string())
655
+ });
656
+ }
657
+ });
658
+
659
+ // src/automation/store.ts
660
+ import { z as z3 } from "zod";
399
661
  function parseState(raw) {
400
662
  try {
401
663
  return stateSchema.parse(raw);
@@ -411,36 +673,217 @@ var init_store = __esm({
411
673
  "src/automation/store.ts"() {
412
674
  "use strict";
413
675
  init_store_lock();
414
- hostSchema = z.enum(["claude", "codex"]);
676
+ init_notifications();
677
+ hostSchema = z3.enum(["claude", "codex"]);
415
678
  events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
416
- stateSchema = z.object({
417
- version: z.literal(1),
418
- root: z.string(),
419
- gitDir: z.string(),
420
- branch: z.string(),
421
- baselines: z.array(z.object({ path: z.string(), at: z.string(), event: z.string(), fingerprint: z.string() })).max(128),
422
- sessions: z.record(z.object({
679
+ stateSchema = z3.object({
680
+ version: z3.literal(1),
681
+ root: z3.string(),
682
+ gitDir: z3.string(),
683
+ branch: z3.string(),
684
+ baselines: z3.array(z3.object({ path: z3.string(), at: z3.string(), event: z3.string(), fingerprint: z3.string() })).max(128),
685
+ sessions: z3.record(z3.object({
423
686
  host: hostSchema,
424
- seen: z.string().nullable(),
425
- continued: z.boolean(),
426
- initialIssues: z.array(z.string()),
427
- initialDocs: z.record(z.string().nullable()),
428
- lastUsed: z.string(),
429
- mutationObserved: z.boolean(),
430
- pending: z.record(z.string()),
431
- coverageGaps: z.array(z.string()),
432
- events: z.record(z.object({ at: z.string(), count: z.number().int().positive() }))
687
+ lastUsed: z3.string(),
688
+ notifiedDiagnostics: z3.array(z3.string()).default([]),
689
+ pending: z3.record(z3.string()),
690
+ coverageGaps: z3.array(z3.string()),
691
+ events: z3.record(z3.object({ at: z3.string(), count: z3.number().int().positive() }))
433
692
  })),
434
- updatedAt: z.string(),
435
- fingerprint: z.string().nullable(),
436
- latest: z.string().nullable(),
437
- analysis: z.object({ id: z.string(), fingerprint: z.string(), completedAt: z.number(), path: z.string() }).optional()
693
+ updatedAt: z3.string(),
694
+ fingerprint: z3.string().nullable(),
695
+ latest: z3.string().nullable(),
696
+ notifications: notificationStateSchema.optional(),
697
+ agentNotifications: notificationStateSchema.optional(),
698
+ analysis: z3.object({ id: z3.string(), fingerprint: z3.string(), completedAt: z3.number(), path: z3.string() }).optional()
438
699
  });
439
700
  }
440
701
  });
441
702
 
442
- // src/audit/commands.ts
703
+ // src/audit/inspection.ts
704
+ import fg2 from "fast-glob";
705
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
443
706
  import path4 from "path";
707
+ function withRepositoryInspection(root, run) {
708
+ return inspections.run({ root: path4.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
709
+ }
710
+ function inspectionRead(root, key, read) {
711
+ const inspection = inspections.getStore();
712
+ if (!inspection || inspection.root !== path4.resolve(root)) return read();
713
+ if (!inspection.reads.has(key)) {
714
+ const pending = read();
715
+ inspection.reads.set(key, pending);
716
+ void pending.catch(() => {
717
+ if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
718
+ });
719
+ }
720
+ return inspection.reads.get(key);
721
+ }
722
+ function inspectionGit(args, options) {
723
+ return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
724
+ }
725
+ async function auditGitPaths(root, kind) {
726
+ const custom = (await loadProjectConfig(root)).audit?.include ?? [];
727
+ const shared = inspections.getStore()?.root === path4.resolve(root) && !custom.length;
728
+ const expanded = fg2.generateTasks(custom).flatMap((task) => task.positive);
729
+ if (expanded.length > 1e3) throw new Error("Audit include patterns expand to more than 1,000 globs");
730
+ const documentSpecs = [...docSpecs, ...expanded.map((pattern) => `:(glob)${pattern}`)];
731
+ const specs = shared ? [...documentSpecs, ...sourceSpecs] : kind === "documents" ? documentSpecs : sourceSpecs;
732
+ const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
733
+ const { stdout } = await inspectionGit(
734
+ ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
735
+ { cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
736
+ );
737
+ 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("."))));
738
+ if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
739
+ return paths.sort();
740
+ }
741
+ var inspections, sourceSpecs, docSpecs, sourceSuffixes;
742
+ var init_inspection = __esm({
743
+ "src/audit/inspection.ts"() {
744
+ "use strict";
745
+ init_git_read();
746
+ init_files();
747
+ inspections = new AsyncLocalStorage2();
748
+ sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
749
+ docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
750
+ sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
751
+ }
752
+ });
753
+
754
+ // src/audit/inputs.ts
755
+ import fs4 from "fs/promises";
756
+ import * as nativeFs from "fs";
757
+ import path5 from "path";
758
+ import fg3 from "fast-glob";
759
+ async function auditInputPath(root, file) {
760
+ if (file === ".") return fs4.realpath(root);
761
+ try {
762
+ return await storePath(root, file);
763
+ } catch (error) {
764
+ if (error instanceof Error && error.message.startsWith("Symlink")) {
765
+ throw new Error(`Audit input contains a symbolic link: ${file}. Its evidence could not be verified.`, { cause: error });
766
+ }
767
+ throw error;
768
+ }
769
+ }
770
+ async function readAuditInput(root, file) {
771
+ try {
772
+ const value = await readBoundedFile(await auditInputPath(root, file), 10 * 1024 * 1024);
773
+ if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
774
+ return value;
775
+ } catch (error) {
776
+ if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return null;
777
+ throw error;
778
+ }
779
+ }
780
+ async function auditGlob(root, patterns, options) {
781
+ if (Array.isArray(patterns) && !patterns.length) return [];
782
+ const directories = /* @__PURE__ */ new Set();
783
+ const readdir2 = ((directory, opts, callback) => {
784
+ const relative = path5.relative(root, directory).split(path5.sep).join("/") || ".";
785
+ directories.add(relative);
786
+ if (directories.size > MAX_INPUTS) {
787
+ callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
788
+ return;
789
+ }
790
+ auditInputPath(root, relative).then(() => {
791
+ nativeFs.readdir(directory, opts, callback);
792
+ }, callback);
793
+ });
794
+ const lstat2 = ((file, callback) => {
795
+ const relative = path5.relative(root, file).split(path5.sep).join("/") || ".";
796
+ auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
797
+ });
798
+ const stream = fg3.stream(patterns, {
799
+ cwd: root,
800
+ ignore: options.ignore,
801
+ onlyFiles: false,
802
+ onlyDirectories: options.onlyDirectories,
803
+ objectMode: true,
804
+ followSymbolicLinks: options.followSymbolicLinks ?? true,
805
+ fs: { readdir: readdir2, lstat: lstat2 },
806
+ concurrency: 16
807
+ });
808
+ const matches = [];
809
+ try {
810
+ for await (const value of stream) {
811
+ const entry = value;
812
+ const file = entry.path;
813
+ if (options.select && !options.select(file)) continue;
814
+ await auditInputPath(root, file);
815
+ if (!options.onlyDirectories && !entry.dirent.isFile()) continue;
816
+ matches.push(file);
817
+ if (matches.length > MAX_INPUTS) {
818
+ const largest = [...matches.reduce((counts, item) => {
819
+ const dir = item.split("/")[0];
820
+ counts.set(dir, (counts.get(dir) ?? 0) + 1);
821
+ return counts;
822
+ }, /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1]).slice(0, 3);
823
+ 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.`);
824
+ }
825
+ }
826
+ } finally {
827
+ stream.destroy();
828
+ }
829
+ return matches.sort();
830
+ }
831
+ async function gitSourcePaths(root) {
832
+ return auditGitPaths(root, "sources");
833
+ }
834
+ async function gitIgnoredPaths(root, files) {
835
+ if (!files.length) return /* @__PURE__ */ new Set();
836
+ const ignored = /* @__PURE__ */ new Set();
837
+ for (let offset = 0; offset < files.length; offset += 1e3) {
838
+ try {
839
+ const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
840
+ cwd: root,
841
+ maxBuffer: 4 * 1024 * 1024,
842
+ timeout: 1e4,
843
+ input: files.slice(offset, offset + 1e3).join("\0") + "\0"
844
+ });
845
+ for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
846
+ } catch (error) {
847
+ if (error.code !== 1) throw error;
848
+ }
849
+ }
850
+ return ignored;
851
+ }
852
+ var MAX_INPUTS;
853
+ var init_inputs = __esm({
854
+ "src/audit/inputs.ts"() {
855
+ "use strict";
856
+ init_files();
857
+ init_storage();
858
+ init_git_read();
859
+ init_inspection();
860
+ MAX_INPUTS = 1e5;
861
+ }
862
+ });
863
+
864
+ // src/audit/policy.ts
865
+ import fg4 from "fast-glob";
866
+ async function includedCheckPaths(root, paths, exclude) {
867
+ if (!exclude.length) return new Set(paths);
868
+ const included = /* @__PURE__ */ new Set();
869
+ for (let offset = 0; offset < paths.length; offset += 256) {
870
+ for (const file of await auditGlob(root, paths.slice(offset, offset + 256).map((file2) => fg4.escapePath(file2)), {
871
+ ignore: exclude,
872
+ followSymbolicLinks: false,
873
+ label: "Audit check exclusions"
874
+ })) included.add(file);
875
+ }
876
+ return included;
877
+ }
878
+ var init_policy = __esm({
879
+ "src/audit/policy.ts"() {
880
+ "use strict";
881
+ init_inputs();
882
+ }
883
+ });
884
+
885
+ // src/audit/commands.ts
886
+ import path6 from "path";
444
887
  function splitCommands(line) {
445
888
  const parts = [];
446
889
  let quote = "", start = 0;
@@ -476,7 +919,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
476
919
  const cd = text2.match(/^cd\s+(.+)$/);
477
920
  if (cd) {
478
921
  const directory = literal(cd[1]);
479
- cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path4.posix.join(cwd ?? ".", directory);
922
+ cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path6.posix.join(cwd ?? ".", directory);
480
923
  continue;
481
924
  }
482
925
  for (const match of text2.matchAll(COMMAND)) {
@@ -487,7 +930,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
487
930
  if (shell && match.index !== 0 || match[3].startsWith("-")) scopeUnknown = "Unsupported shell wrapper or options before the script.";
488
931
  if (selector) {
489
932
  const selected = literal(selector[1]);
490
- directory = selected === null || cwd === null ? null : path4.posix.join(cwd ?? ".", selected);
933
+ directory = selected === null || cwd === null ? null : path6.posix.join(cwd ?? ".", selected);
491
934
  } else if (flags) scopeUnknown = "Unsupported package-manager options before run: " + flags;
492
935
  const tail = text2.slice(match.index + match[0].length).split(/\s--(?:\s|$)/)[0];
493
936
  if (/(?:^|\s)(?:--workspace(?:s)?|-w|--filter|-F|--prefix|--dir|-C|--if-present)(?:\s|=|$)/.test(tail)) {
@@ -517,7 +960,7 @@ var init_commands = __esm({
517
960
  COMMAND = new RegExp(`\\b(npm|pnpm|yarn)((?:[ \\t]+${TOKEN})*?)[ \\t]+run[ \\t]+([A-Za-z0-9:_.-]+)`, "g");
518
961
  literal = (value) => {
519
962
  const unquoted = value.replace(/^(["'])(.*)\1$/, "$2");
520
- return /[$`*?\[\]{}~\\\x00-\x1f]/.test(unquoted) || path4.posix.isAbsolute(unquoted) ? null : unquoted;
963
+ return /[$`*?\[\]{}~\\\x00-\x1f]/.test(unquoted) || path6.posix.isAbsolute(unquoted) ? null : unquoted;
521
964
  };
522
965
  }
523
966
  });
@@ -822,52 +1265,6 @@ var init_claims = __esm({
822
1265
  }
823
1266
  });
824
1267
 
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
1268
  // src/audit/git.ts
872
1269
  function parseCommitLine(line) {
873
1270
  const parts = line.split(" ");
@@ -914,159 +1311,49 @@ async function firstCommitOf(resolvedRoot, relPath) {
914
1311
  const line = stdout.trim().split("\n")[0];
915
1312
  return line ? parseCommitLine(line) : null;
916
1313
  } catch {
917
- return null;
918
- }
919
- }
920
- async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
921
- if (!fromHash || fromHash === "unknown") return null;
922
- try {
923
- const { stdout } = await inspectionGit(
924
- [
925
- "log",
926
- `${fromHash}..HEAD`,
927
- `--format=%x01${COMMIT_FORMAT}`,
928
- "--name-only",
929
- "--",
930
- ...pathspecs
931
- ],
932
- { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
933
- );
934
- const commits = [];
935
- for (const block of stdout.split("")) {
936
- if (!block.trim()) continue;
937
- const lines = block.split("\n").filter((l) => l.trim().length > 0);
938
- const ref = parseCommitLine(lines[0]);
939
- if (!ref) continue;
940
- commits.push({ ...ref, files: lines.slice(1).map((l) => l.trim()) });
941
- }
942
- return { commits, total: commits.length };
943
- } catch {
944
- return null;
945
- }
946
- }
947
- var COMMIT_FORMAT;
948
- var init_git = __esm({
949
- "src/audit/git.ts"() {
950
- "use strict";
951
- init_inspection();
952
- COMMIT_FORMAT = "%H%x09%cI%x09%s";
953
- }
954
- });
955
-
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;
1314
+ return null;
1315
+ }
1316
+ }
1317
+ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
1318
+ if (!fromHash || fromHash === "unknown") return null;
1319
+ try {
1320
+ const { stdout } = await inspectionGit(
1321
+ [
1322
+ "log",
1323
+ `${fromHash}..HEAD`,
1324
+ `--format=%x01${COMMIT_FORMAT}`,
1325
+ "--name-only",
1326
+ "--",
1327
+ ...pathspecs
1328
+ ],
1329
+ { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
1330
+ );
1331
+ const commits = [];
1332
+ for (const block of stdout.split("")) {
1333
+ if (!block.trim()) continue;
1334
+ const lines = block.split("\n").filter((l) => l.trim().length > 0);
1335
+ const ref = parseCommitLine(lines[0]);
1336
+ if (!ref) continue;
1337
+ commits.push({ ...ref, files: lines.slice(1).map((l) => l.trim()) });
1050
1338
  }
1339
+ return { commits, total: commits.length };
1340
+ } catch {
1341
+ return null;
1051
1342
  }
1052
- return ignored;
1053
1343
  }
1054
- var MAX_INPUTS;
1055
- var init_inputs = __esm({
1056
- "src/audit/inputs.ts"() {
1344
+ var COMMIT_FORMAT;
1345
+ var init_git = __esm({
1346
+ "src/audit/git.ts"() {
1057
1347
  "use strict";
1058
- init_files();
1059
- init_storage();
1060
- init_git_read();
1061
1348
  init_inspection();
1062
- MAX_INPUTS = 1e5;
1349
+ COMMIT_FORMAT = "%H%x09%cI%x09%s";
1063
1350
  }
1064
1351
  });
1065
1352
 
1066
1353
  // src/audit/docs.ts
1067
1354
  import fs5 from "fs/promises";
1068
1355
  import path7 from "path";
1069
- import fg3 from "fast-glob";
1356
+ import fg5 from "fast-glob";
1070
1357
  async function dirtyDocs(resolvedRoot, files) {
1071
1358
  if (!files.length) return /* @__PURE__ */ new Set();
1072
1359
  try {
@@ -1118,7 +1405,7 @@ async function discoverDocPaths(root) {
1118
1405
  const paths = [];
1119
1406
  const unique = [...new Set(candidates)].sort();
1120
1407
  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)), {
1408
+ paths.push(...await auditGlob(root, unique.slice(offset, offset + 256).map((p) => fg5.escapePath(p)), {
1122
1409
  ignore: excluded,
1123
1410
  followSymbolicLinks: false,
1124
1411
  label: "Documentation discovery"
@@ -1148,6 +1435,7 @@ async function localDocPaths(root) {
1148
1435
  return paths;
1149
1436
  }
1150
1437
  function documentScope(file) {
1438
+ if (!/^(readme|agents|claude)\.md$/i.test(path7.posix.basename(file))) return ".";
1151
1439
  const parent = path7.posix.dirname(file);
1152
1440
  return path7.posix.basename(parent).toLowerCase() === ".claude" ? path7.posix.dirname(parent) : parent;
1153
1441
  }
@@ -1169,7 +1457,7 @@ async function readDocs(resolvedRoot) {
1169
1457
  path: candidate,
1170
1458
  content: content2,
1171
1459
  scope: documentScope(candidate),
1172
- kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : "instructions",
1460
+ kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : /^(agents|claude)\.md$/i.test(path7.posix.basename(candidate)) ? "instructions" : "guide",
1173
1461
  lineCount: content2.split("\n").length,
1174
1462
  lastCommit,
1175
1463
  dirty: dirty.has(candidate),
@@ -1326,7 +1614,7 @@ var init_test_map = __esm({
1326
1614
 
1327
1615
  // src/snapshot/snapshot.ts
1328
1616
  import path10 from "path";
1329
- import { z as z2 } from "zod";
1617
+ import { z as z4 } from "zod";
1330
1618
  async function inspectSnapshot(rootDir) {
1331
1619
  try {
1332
1620
  const raw = await readStoreJson(rootDir, ".mason/snapshot.json");
@@ -1360,30 +1648,30 @@ var init_snapshot = __esm({
1360
1648
  init_lock();
1361
1649
  init_paths();
1362
1650
  init_test_map();
1363
- repoPath = z2.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
1651
+ repoPath = z4.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
1364
1652
  verificationFields = {
1365
- refreshedHash: z2.string().optional(),
1366
- verifiedAt: z2.string().optional(),
1367
- verifiedHash: z2.string().optional(),
1368
- verificationToken: z2.string().optional(),
1369
- verificationFailed: z2.boolean().optional(),
1370
- verificationNote: z2.string().optional()
1653
+ refreshedHash: z4.string().optional(),
1654
+ verifiedAt: z4.string().optional(),
1655
+ verifiedHash: z4.string().optional(),
1656
+ verificationToken: z4.string().optional(),
1657
+ verificationFailed: z4.boolean().optional(),
1658
+ verificationNote: z4.string().optional()
1371
1659
  };
1372
- featureSchema = z2.object({
1373
- description: z2.string(),
1374
- files: z2.array(repoPath),
1375
- tests: z2.array(repoPath).optional(),
1376
- type: z2.enum(["capability", "infrastructure"]).optional(),
1660
+ featureSchema = z4.object({
1661
+ description: z4.string(),
1662
+ files: z4.array(repoPath),
1663
+ tests: z4.array(repoPath).optional(),
1664
+ type: z4.enum(["capability", "infrastructure"]).optional(),
1377
1665
  ...verificationFields
1378
1666
  }).passthrough();
1379
- flowSchema = z2.object({ description: z2.string(), chain: z2.array(repoPath), ...verificationFields }).passthrough();
1380
- snapshotSchema = z2.object({
1381
- version: z2.literal(2),
1382
- createdAt: z2.string(),
1383
- updatedAt: z2.string(),
1384
- gitHash: z2.string(),
1385
- features: z2.record(featureSchema),
1386
- flows: z2.record(flowSchema)
1667
+ flowSchema = z4.object({ description: z4.string(), chain: z4.array(repoPath), ...verificationFields }).passthrough();
1668
+ snapshotSchema = z4.object({
1669
+ version: z4.literal(2),
1670
+ createdAt: z4.string(),
1671
+ updatedAt: z4.string(),
1672
+ gitHash: z4.string(),
1673
+ features: z4.record(featureSchema),
1674
+ flows: z4.record(flowSchema)
1387
1675
  }).passthrough();
1388
1676
  }
1389
1677
  });
@@ -1438,22 +1726,6 @@ var init_drift = __esm({
1438
1726
  }
1439
1727
  });
1440
1728
 
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
1729
  // src/audit/checks/deleted-reference.ts
1458
1730
  import path12 from "path";
1459
1731
  async function checkDeletedReferences(ctx) {
@@ -1522,7 +1794,7 @@ var init_deleted_reference = __esm({
1522
1794
  });
1523
1795
 
1524
1796
  // src/audit/checks/new-module.ts
1525
- import fg4 from "fast-glob";
1797
+ import fg6 from "fast-glob";
1526
1798
  import fs8 from "fs/promises";
1527
1799
  import path13 from "path";
1528
1800
  function escapeRegExp(text2) {
@@ -1584,7 +1856,7 @@ async function collectModuleCandidates(root, combinedDocs) {
1584
1856
  const result = /* @__PURE__ */ new Map();
1585
1857
  for (const dir of parents) {
1586
1858
  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)}/*`, {
1859
+ result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg6.escapePath(dir)}/*`, {
1588
1860
  ignore,
1589
1861
  onlyDirectories: true,
1590
1862
  label: "Module directory discovery",
@@ -1597,7 +1869,7 @@ async function collectModuleCandidates(root, combinedDocs) {
1597
1869
  const files = sources.filter((file) => file.startsWith(dir + "/"));
1598
1870
  let count4 = 0;
1599
1871
  for (let offset = 0; offset < files.length; offset += 256) {
1600
- count4 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) => fg4.escapePath(file)), {
1872
+ count4 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) => fg6.escapePath(file)), {
1601
1873
  ignore,
1602
1874
  followSymbolicLinks: false,
1603
1875
  label: `Source discovery in ${dir}`
@@ -1932,6 +2204,87 @@ var init_dead_command = __esm({
1932
2204
  }
1933
2205
  });
1934
2206
 
2207
+ // src/audit/dependency-claims.ts
2208
+ import path16 from "path";
2209
+ import { parse as parseToml } from "smol-toml";
2210
+ function dependencyText(content2) {
2211
+ return content2.replace(/<!-- mason:start -->[\s\S]*?<!-- mason:end -->/g, (text2) => text2.replace(/[^\n]/g, " ")).replace(/<!--[\s\S]*?-->/g, (text2) => text2.replace(/[^\n]/g, " "));
2212
+ }
2213
+ function declarations(file, source) {
2214
+ const result = /* @__PURE__ */ new Map();
2215
+ if (!source) return result;
2216
+ if (path16.posix.basename(file) === "package.json") {
2217
+ const json2 = JSON.parse(source);
2218
+ for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies", "engines"]) {
2219
+ for (const [name, value] of Object.entries(json2[section] ?? {})) {
2220
+ if (typeof value === "string") result.set(name, [...result.has(name) ? [result.get(name)] : [], value].join("; "));
2221
+ }
2222
+ }
2223
+ } else if (file.endsWith("libs.versions.toml")) {
2224
+ const catalog = parseToml(source);
2225
+ for (const section of ["libraries", "plugins"]) for (const [alias, entry] of Object.entries(catalog[section] ?? {})) {
2226
+ if (typeof entry === "string") {
2227
+ const parts = entry.split(":");
2228
+ if (parts.length >= 3) result.set(parts.slice(0, -1).join(":"), parts.at(-1));
2229
+ } else if (entry && typeof entry === "object") {
2230
+ const name = entry.module ?? (entry.group && entry.name ? `${entry.group}:${entry.name}` : entry.id);
2231
+ const version = typeof entry.version === "string" ? entry.version : catalog.versions?.[entry.version?.ref];
2232
+ if (typeof name === "string") result.set(name, typeof version === "string" ? version : "unspecified");
2233
+ }
2234
+ }
2235
+ } else if (/build\.gradle(?:\.kts)?$/.test(file)) {
2236
+ for (const match of source.matchAll(/["']([\w.-]+:[\w.-]+):([^"'\s]+)["']/g)) result.set(match[1], match[2]);
2237
+ for (const match of source.matchAll(/\bid\s*\(?\s*["']([\w.-]+)["']\s*\)?\s*version\s*["']([^"']+)["']/g)) result.set(match[1], match[2]);
2238
+ }
2239
+ return result;
2240
+ }
2241
+ async function matchDependencyChanges(root, from, head, files, content2) {
2242
+ const matches = [];
2243
+ const lines = dependencyText(content2).split("\n");
2244
+ let incomplete = files.length > 64;
2245
+ for (const file of files.slice(0, 64)) {
2246
+ if (!/(?:^|\/)(?:package\.json|libs\.versions\.toml|build\.gradle(?:\.kts)?)$/.test(file)) {
2247
+ incomplete = true;
2248
+ continue;
2249
+ }
2250
+ try {
2251
+ const read = async (revision) => {
2252
+ const { stdout: entry } = await inspectionGit(["ls-tree", revision, "--", `:(literal)${file}`], { cwd: root, timeout: 1e4 });
2253
+ if (!entry) return "";
2254
+ if (!entry.startsWith("100644 ") && !entry.startsWith("100755 ")) throw new Error("Unsupported manifest mode");
2255
+ return (await inspectionGit(["show", `${revision}:${file}`], { cwd: root, maxBuffer: 1024 * 1024, timeout: 1e4 })).stdout;
2256
+ };
2257
+ const [before, after] = await Promise.all([read(from), read(head)]);
2258
+ const old = declarations(file, before), next = declarations(file, after);
2259
+ for (const dependency of /* @__PURE__ */ new Set([...old.keys(), ...next.keys()])) {
2260
+ if (old.get(dependency) === next.get(dependency)) continue;
2261
+ const escaped = dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2262
+ const mention = new RegExp(`(^|[^a-zA-Z0-9_@./-])${escaped}(?![a-zA-Z0-9_/-]|\\.[a-zA-Z0-9_])`, "i");
2263
+ const line = lines.findIndex((text2) => mention.test(text2));
2264
+ if (line < 0) continue;
2265
+ matches.push({
2266
+ dependency,
2267
+ manifest: file,
2268
+ before: old.get(dependency) ?? null,
2269
+ after: next.get(dependency) ?? null,
2270
+ line: line + 1,
2271
+ excerpt: lines[line].trim().slice(0, 500)
2272
+ });
2273
+ if (matches.length >= 100) return { matches, incomplete: true };
2274
+ }
2275
+ } catch {
2276
+ incomplete = true;
2277
+ }
2278
+ }
2279
+ return { matches, incomplete };
2280
+ }
2281
+ var init_dependency_claims = __esm({
2282
+ "src/audit/dependency-claims.ts"() {
2283
+ "use strict";
2284
+ init_inspection();
2285
+ }
2286
+ });
2287
+
1935
2288
  // src/audit/release-metadata.ts
1936
2289
  async function git(root, args) {
1937
2290
  return (await execGit(args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
@@ -2011,13 +2364,6 @@ async function checkDepsChanged(ctx) {
2011
2364
  });
2012
2365
  continue;
2013
2366
  }
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
2367
  let content2 = doc.content;
2022
2368
  if (doc.dirty) {
2023
2369
  try {
@@ -2030,7 +2376,6 @@ async function checkDepsChanged(ctx) {
2030
2376
  continue;
2031
2377
  }
2032
2378
  }
2033
- if (!hasDependencyContent(content2)) continue;
2034
2379
  const range = await commitsTouchingSince(
2035
2380
  ctx.root,
2036
2381
  doc.lastCommit.hash,
@@ -2054,16 +2399,32 @@ async function checkDepsChanged(ctx) {
2054
2399
  range.commits = relevant;
2055
2400
  range.total = relevant.length;
2056
2401
  if (range.total === 0) continue;
2057
- const latest = range.commits[0];
2402
+ const relevance = await matchDependencyChanges(
2403
+ ctx.root,
2404
+ doc.lastCommit.hash,
2405
+ ctx.headHash,
2406
+ [...new Set(range.commits.flatMap((commit) => commit.files))],
2407
+ content2
2408
+ );
2409
+ if (!relevance.matches.length && !hasDependencyContent(content2)) continue;
2410
+ const match = relevance.matches[0];
2411
+ if (doc.dirty && match) result.skipped.push({
2412
+ check: "deps-changed",
2413
+ doc: doc.path,
2414
+ reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
2415
+ });
2058
2416
  (doc.dirty ? result.suppressedAdvisories : result.advisories).push({
2059
2417
  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 },
2418
+ ...match ? {} : { resolution: "informational" },
2419
+ 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." : ""}`,
2420
+ anchor: { doc: doc.path, line: match?.line ?? null, excerpt: match?.excerpt ?? null },
2062
2421
  evidence: {
2063
2422
  kind: "doc-behind-manifests",
2064
2423
  docLastCommit: doc.lastCommit,
2065
2424
  manifestCommits: range.commits.slice(0, MANIFEST_COMMITS_CAP),
2066
- totalCommits: range.total
2425
+ totalCommits: range.total,
2426
+ matches: relevance.matches,
2427
+ matchingIncomplete: relevance.incomplete
2067
2428
  }
2068
2429
  });
2069
2430
  }
@@ -2073,6 +2434,7 @@ var MANIFEST_COMMITS_CAP, MANIFEST_PATHSPECS;
2073
2434
  var init_deps_changed = __esm({
2074
2435
  "src/audit/checks/deps-changed.ts"() {
2075
2436
  "use strict";
2437
+ init_dependency_claims();
2076
2438
  init_docs();
2077
2439
  init_release_metadata();
2078
2440
  init_git();
@@ -2097,7 +2459,7 @@ var init_deps_changed = __esm({
2097
2459
  });
2098
2460
 
2099
2461
  // src/context/lexical.ts
2100
- import path16 from "path";
2462
+ import path17 from "path";
2101
2463
  var init_lexical = __esm({
2102
2464
  "src/context/lexical.ts"() {
2103
2465
  "use strict";
@@ -2121,7 +2483,7 @@ var init_trust = __esm({
2121
2483
  });
2122
2484
 
2123
2485
  // src/decisions/provenance.ts
2124
- import { z as z3 } from "zod";
2486
+ import { z as z5 } from "zod";
2125
2487
  function decisionContent(record) {
2126
2488
  return {
2127
2489
  title: record.title,
@@ -2185,67 +2547,67 @@ var init_provenance = __esm({
2185
2547
  "use strict";
2186
2548
  init_paths();
2187
2549
  init_trust();
2188
- text = (max) => z3.string().trim().min(1).max(max);
2189
- decisionSourceSchema = z3.object({
2190
- kind: z3.enum(["pull_request", "issue", "incident", "discussion", "document", "other"]),
2550
+ text = (max) => z5.string().trim().min(1).max(max);
2551
+ decisionSourceSchema = z5.object({
2552
+ kind: z5.enum(["pull_request", "issue", "incident", "discussion", "document", "other"]),
2191
2553
  reference: text(1e3),
2192
2554
  note: text(500).optional()
2193
2555
  }).strict();
2194
- attributionSchema = z3.object({
2556
+ attributionSchema = z5.object({
2195
2557
  owner: text(200).nullable().optional(),
2196
- sources: z3.array(decisionSourceSchema).max(20).optional(),
2558
+ sources: z5.array(decisionSourceSchema).max(20).optional(),
2197
2559
  actor: text(200).optional()
2198
2560
  });
2199
- contentSchema = z3.object({
2200
- title: z3.string().min(1),
2201
- body: z3.string().min(1),
2202
- category: z3.enum(["decision", "gotcha", "deprecation", "convention"]),
2203
- files: z3.array(z3.string().refine((f) => normalizeRepoPath(f) !== null)),
2561
+ contentSchema = z5.object({
2562
+ title: z5.string().min(1),
2563
+ body: z5.string().min(1),
2564
+ category: z5.enum(["decision", "gotcha", "deprecation", "convention"]),
2565
+ files: z5.array(z5.string().refine((f) => normalizeRepoPath(f) !== null)),
2204
2566
  owner: text(200).optional(),
2205
- sources: z3.array(decisionSourceSchema).max(20)
2567
+ sources: z5.array(decisionSourceSchema).max(20)
2206
2568
  });
2207
- approvalSchema = z3.enum(["unreviewed", "proposed", "accepted"]);
2208
- statusSchema = z3.enum(["active", "superseded", "retired"]);
2209
- reviewEvidenceSchema = z3.object({
2210
- baseHash: z3.string(),
2211
- headHash: z3.string(),
2212
- historyAvailable: z3.boolean(),
2213
- changedFiles: z3.array(z3.string()),
2214
- localChanges: z3.array(z3.string())
2569
+ approvalSchema = z5.enum(["unreviewed", "proposed", "accepted"]);
2570
+ statusSchema = z5.enum(["active", "superseded", "retired"]);
2571
+ reviewEvidenceSchema = z5.object({
2572
+ baseHash: z5.string(),
2573
+ headHash: z5.string(),
2574
+ historyAvailable: z5.boolean(),
2575
+ changedFiles: z5.array(z5.string()),
2576
+ localChanges: z5.array(z5.string())
2215
2577
  });
2216
- eventSchema = z3.object({
2217
- kind: z3.enum(["imported", "created", "revised", "accepted", "reaffirmed", "retired", "superseded"]),
2218
- at: z3.string().datetime(),
2578
+ eventSchema = z5.object({
2579
+ kind: z5.enum(["imported", "created", "revised", "accepted", "reaffirmed", "retired", "superseded"]),
2580
+ at: z5.string().datetime(),
2219
2581
  actor: text(200).optional(),
2220
2582
  note: text(1500).optional(),
2221
- revision: z3.number().int().positive(),
2583
+ revision: z5.number().int().positive(),
2222
2584
  content: contentSchema,
2223
2585
  approval: approvalSchema,
2224
2586
  status: statusSchema,
2225
- refreshedHash: z3.string(),
2587
+ refreshedHash: z5.string(),
2226
2588
  evidence: reviewEvidenceSchema.optional()
2227
2589
  });
2228
- legacySchema = z3.object({
2229
- version: z3.literal(1),
2230
- id: z3.string().regex(/^[a-zA-Z0-9_-]+$/),
2231
- title: z3.string().min(1),
2232
- body: z3.string().min(1),
2590
+ legacySchema = z5.object({
2591
+ version: z5.literal(1),
2592
+ id: z5.string().regex(/^[a-zA-Z0-9_-]+$/),
2593
+ title: z5.string().min(1),
2594
+ body: z5.string().min(1),
2233
2595
  category: contentSchema.shape.category,
2234
2596
  files: contentSchema.shape.files,
2235
- createdAt: z3.string(),
2236
- updatedAt: z3.string(),
2237
- refreshedHash: z3.string(),
2238
- status: z3.enum(["active", "superseded"]),
2239
- supersededBy: z3.string().optional()
2597
+ createdAt: z5.string(),
2598
+ updatedAt: z5.string(),
2599
+ refreshedHash: z5.string(),
2600
+ status: z5.enum(["active", "superseded"]),
2601
+ supersededBy: z5.string().optional()
2240
2602
  }).passthrough();
2241
2603
  currentSchema = legacySchema.extend({
2242
- version: z3.literal(2),
2604
+ version: z5.literal(2),
2243
2605
  status: statusSchema,
2244
2606
  approval: approvalSchema,
2245
- revision: z3.number().int().positive(),
2607
+ revision: z5.number().int().positive(),
2246
2608
  owner: text(200).optional(),
2247
- sources: z3.array(decisionSourceSchema).max(20),
2248
- history: z3.array(eventSchema).min(1)
2609
+ sources: z5.array(decisionSourceSchema).max(20),
2610
+ history: z5.array(eventSchema).min(1)
2249
2611
  }).superRefine((record, ctx) => {
2250
2612
  const invalid = (message2) => ctx.addIssue({ code: "custom", message: message2 });
2251
2613
  const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
@@ -2275,15 +2637,15 @@ var init_provenance = __esm({
2275
2637
  }
2276
2638
  if (!previous || !same(previous.content, decisionContent(record)) || previous.approval !== record.approval || previous.status !== record.status || previous.revision !== record.revision || previous.refreshedHash !== record.refreshedHash) invalid("Decision does not match the final history event");
2277
2639
  });
2278
- decisionSchema = z3.union([legacySchema, currentSchema]);
2640
+ decisionSchema = z5.union([legacySchema, currentSchema]);
2279
2641
  }
2280
2642
  });
2281
2643
 
2282
2644
  // src/decisions/decisions.ts
2283
2645
  import fs9 from "fs/promises";
2284
- import path17 from "path";
2285
- import { createHash } from "crypto";
2286
- import { z as z4 } from "zod";
2646
+ import path18 from "path";
2647
+ import { createHash as createHash2 } from "crypto";
2648
+ import { z as z6 } from "zod";
2287
2649
  async function loadDecisionStore(rootDir) {
2288
2650
  const records = [];
2289
2651
  const diagnostics = [];
@@ -2319,7 +2681,7 @@ var init_decisions = __esm({
2319
2681
  TITLE_MAX_CHARS = 80;
2320
2682
  BODY_RECOMMENDED_CHARS = 1500;
2321
2683
  BODY_MAX_CHARS = 2500;
2322
- boundedText = (field, max, hint) => z4.string({
2684
+ boundedText = (field, max, hint) => z6.string({
2323
2685
  errorMap: (issue, ctx) => ({ message: issue.code === "too_big" ? `${field} has ${ctx.data.length} characters; maximum is ${max} (${ctx.data.length - max} over). ${hint}` : ctx.defaultError })
2324
2686
  }).trim().min(1, `${field} must be non-empty`).max(max);
2325
2687
  decisionTitleSchema = boundedText("title", TITLE_MAX_CHARS, "Tighten it to a specific headline.");
@@ -2332,9 +2694,9 @@ var init_decisions = __esm({
2332
2694
  });
2333
2695
 
2334
2696
  // src/decisions/drift.ts
2335
- import path18 from "path";
2697
+ import path19 from "path";
2336
2698
  async function computeDecisionDrift(rootDir, decisions) {
2337
- const resolvedRoot = path18.resolve(rootDir);
2699
+ const resolvedRoot = path19.resolve(rootDir);
2338
2700
  const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
2339
2701
  const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
2340
2702
  const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
@@ -2450,128 +2812,26 @@ var init_checks = __esm({
2450
2812
  }
2451
2813
  });
2452
2814
 
2453
- // src/audit/findings.ts
2454
- import { createHash as createHash2 } from "crypto";
2455
- import { z as z5 } from "zod";
2456
- function findingId(finding) {
2457
- const e = finding.evidence;
2458
- let key;
2459
- switch (e.kind) {
2460
- case "missing-path":
2461
- key = e.resolvedPath && e.resolvedPath !== e.claimed ? [e.claimed, e.resolvedPath] : e.claimed;
2462
- break;
2463
- case "unmentioned-dir":
2464
- key = e.dir;
2465
- break;
2466
- case "count-mismatch":
2467
- key = [e.unit.replace(/s$/, ""), e.countedFrom];
2468
- break;
2469
- case "missing-script":
2470
- key = e.scope && e.scope.directory !== "." ? [e.scriptName, e.scope.directory] : e.scriptName;
2471
- break;
2472
- case "doc-behind-manifests":
2473
- key = null;
2474
- break;
2475
- case "decision-anchor":
2476
- key = [e.decisionId, e.provenance?.revision, e.provenance?.approval];
2477
- break;
2478
- }
2479
- return digest([finding.type, finding.anchor.doc, key]);
2480
- }
2481
- var digest, checkSchema, commitSchema, anchorSchema, count, scopeSchema, evidenceSchema, findingSchema, issueSchema, advisorySchema, checkResultSchema;
2482
- var init_findings = __esm({
2483
- "src/audit/findings.ts"() {
2484
- "use strict";
2485
- digest = (value) => createHash2("sha256").update(JSON.stringify(value)).digest("hex");
2486
- checkSchema = z5.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"]);
2487
- commitSchema = z5.object({ hash: z5.string().regex(/^[a-f0-9]{40,64}$/), date: z5.string(), subject: z5.string() });
2488
- anchorSchema = z5.object({ doc: z5.string(), line: z5.number().int().positive().nullable(), excerpt: z5.string().nullable() });
2489
- count = z5.number().int().nonnegative();
2490
- scopeSchema = z5.object({
2491
- basis: z5.enum(["repository", "document", "document-link", "explicit"]),
2492
- directory: z5.string(),
2493
- candidates: z5.array(z5.string())
2494
- });
2495
- evidenceSchema = z5.discriminatedUnion("kind", [
2496
- z5.object({
2497
- kind: z5.literal("missing-path"),
2498
- claimed: z5.string(),
2499
- renamedTo: z5.string().nullable(),
2500
- deletedInCommit: commitSchema.nullable(),
2501
- everTracked: z5.boolean(),
2502
- parentDirExists: z5.boolean(),
2503
- resolvedPath: z5.string().optional(),
2504
- scope: scopeSchema.optional()
2505
- }),
2506
- z5.object({
2507
- kind: z5.literal("unmentioned-dir"),
2508
- dir: z5.string(),
2509
- sourceFileCount: count,
2510
- firstCommit: commitSchema.nullable(),
2511
- checkedDocs: z5.array(z5.string())
2512
- }),
2513
- z5.object({
2514
- kind: z5.literal("count-mismatch"),
2515
- claimed: count,
2516
- actual: count,
2517
- unit: z5.string(),
2518
- countedFrom: z5.string(),
2519
- members: z5.array(z5.string())
2520
- }),
2521
- z5.object({
2522
- kind: z5.literal("missing-script"),
2523
- scriptName: z5.string(),
2524
- invocation: z5.string(),
2525
- manifestsChecked: z5.array(z5.string()),
2526
- availableScripts: z5.array(z5.string()),
2527
- scope: scopeSchema.optional()
2528
- }),
2529
- z5.object({
2530
- kind: z5.literal("doc-behind-manifests"),
2531
- docLastCommit: commitSchema,
2532
- manifestCommits: z5.array(commitSchema.extend({ files: z5.array(z5.string()) })),
2533
- totalCommits: count
2534
- }),
2535
- z5.object({
2536
- kind: z5.literal("decision-anchor"),
2537
- decisionId: z5.string(),
2538
- title: z5.string(),
2539
- changedFiles: z5.array(z5.string()),
2540
- refreshedHash: z5.string(),
2541
- provenance: z5.object({}).passthrough().optional()
2542
- })
2543
- ]);
2544
- findingSchema = z5.object({ message: z5.string(), anchor: anchorSchema, evidence: evidenceSchema });
2545
- issueSchema = findingSchema.extend({
2546
- type: z5.enum(["deleted-reference", "new-module", "stale-count", "dead-command"]),
2547
- confidence: z5.enum(["certain", "likely"])
2548
- });
2549
- advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z5.literal("recheck").optional() });
2550
- checkResultSchema = z5.object({
2551
- issues: z5.array(issueSchema),
2552
- advisories: z5.array(advisorySchema),
2553
- suppressedAdvisories: z5.array(advisorySchema).optional(),
2554
- skipped: z5.array(z5.object({ check: z5.string(), reason: z5.string(), doc: z5.string().optional() }))
2555
- });
2556
- }
2557
- });
2558
-
2559
2815
  // src/audit/advisory-review.ts
2560
2816
  import fs10 from "fs/promises";
2561
- import { z as z6 } from "zod";
2817
+ import { z as z7 } from "zod";
2562
2818
  async function readRecord(root, id) {
2563
2819
  const raw = await readStoreJson(root, recordPath(id));
2564
2820
  if (raw === null) return null;
2565
2821
  const record = recordSchema.parse(raw);
2566
2822
  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]))) {
2823
+ 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
2824
  throw new Error("Advisory review record is inconsistent or modified; inspect its history.");
2569
2825
  }
2570
2826
  return record;
2571
2827
  }
2572
- function scopes(finding) {
2828
+ function evidenceFingerprint(evidence, reason2) {
2829
+ return digest([evidence.scope, evidence.tree, ...reason2 === "no-dependency-claims" ? [] : [evidence.lastScopeCommit], evidence.paths]);
2830
+ }
2831
+ function scopes(finding, reason2) {
2573
2832
  const files = [finding.anchor.doc], e = finding.evidence;
2574
2833
  if (e.kind === "decision-anchor") throw new Error("Use review_decision for decision acceptance, reaffirmation or retirement.");
2834
+ if (reason2 === "no-dependency-claims" && e.kind === "doc-behind-manifests") return [`:(literal)${finding.anchor.doc}`];
2575
2835
  if (e.kind === "doc-behind-manifests") return [`:(literal)${finding.anchor.doc}`, ...manifestPathspecs(finding.anchor.doc)];
2576
2836
  if (e.kind === "missing-path") files.push(...e.scope?.candidates ?? [e.resolvedPath ?? e.claimed]);
2577
2837
  else if (e.kind === "missing-script") files.push(...e.manifestsChecked, ...e.scope?.candidates ?? []);
@@ -2582,9 +2842,9 @@ function scopes(finding) {
2582
2842
  return `:(literal)${file}`;
2583
2843
  }).sort();
2584
2844
  }
2585
- async function reviewEvidence(root, finding) {
2845
+ async function reviewEvidence(root, finding, reason2) {
2586
2846
  if (normalizeRepoPath(finding.anchor.doc) !== finding.anchor.doc) throw new Error("Invalid advisory document path.");
2587
- const scope = scopes(finding);
2847
+ const scope = scopes(finding, reason2);
2588
2848
  const head = (await git2(root, "rev-parse", "HEAD")).trim();
2589
2849
  const tracked = (await git2(root, "ls-files", "--cached", "-z", "--", ...scope)).split("\0").filter(Boolean);
2590
2850
  if (tracked.length > 2e4) throw new Error("Advisory scope exceeds 20,000 tracked files.");
@@ -2610,7 +2870,7 @@ async function reviewEvidence(root, finding) {
2610
2870
  if (!tracked.includes(file) && await pathExists(root, file)) dirty = true;
2611
2871
  }
2612
2872
  }
2613
- const fingerprint = digest([scope, tree, lastScopeCommit, paths]);
2873
+ const fingerprint = evidenceFingerprint({ head, scope, tree, lastScopeCommit, paths, fingerprint: "" }, reason2);
2614
2874
  if ((await git2(root, "rev-parse", "HEAD")).trim() !== head) throw new Error("HEAD changed while collecting review evidence.");
2615
2875
  return {
2616
2876
  ...evidenceSchema2.parse({ head, scope, tree, lastScopeCommit, paths, fingerprint }),
@@ -2657,7 +2917,7 @@ async function assessAdvisory(root, finding, baselineHead) {
2657
2917
  const record = await readRecord(root, id);
2658
2918
  if (!record) return null;
2659
2919
  const event = record.events.at(-1);
2660
- const current = await reviewEvidence(root, event.finding);
2920
+ const current = await reviewEvidence(root, event.finding, event.reasonCode);
2661
2921
  await git2(root, "merge-base", "--is-ancestor", event.evidence.head, current.head);
2662
2922
  const base = {
2663
2923
  id,
@@ -2667,7 +2927,7 @@ async function assessAdvisory(root, finding, baselineHead) {
2667
2927
  outcome: event.outcome,
2668
2928
  reviewedHead: event.evidence.head
2669
2929
  };
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." };
2930
+ 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
2931
  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
2932
  } catch (error) {
2673
2933
  return { id, status: "unverified", reason: error instanceof Error ? error.message : String(error) };
@@ -2684,7 +2944,7 @@ async function advisoryReviewInventory(root) {
2684
2944
  if (names.length > 5e3) throw new Error("Advisory review inventory exceeds 5,000 records.");
2685
2945
  return Promise.all(names.filter((name) => name.endsWith(".json")).sort().map(async (name) => [name, await readAuditInput(root, ".mason/reviews/advisories/" + name)]));
2686
2946
  }
2687
- var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2947
+ var git2, hashSchema, commitSchema2, evidenceSchema2, dismissalReasonSchema, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
2688
2948
  var init_advisory_review = __esm({
2689
2949
  "src/audit/advisory-review.ts"() {
2690
2950
  "use strict";
@@ -2700,39 +2960,42 @@ var init_advisory_review = __esm({
2700
2960
  init_decisions();
2701
2961
  init_drift2();
2702
2962
  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;
2703
- hashSchema = z6.string().regex(/^[a-f0-9]{64}$/);
2704
- commitSchema2 = z6.string().regex(/^[a-f0-9]{40,64}$/);
2705
- evidenceSchema2 = z6.object({
2963
+ hashSchema = z7.string().regex(/^[a-f0-9]{64}$/);
2964
+ commitSchema2 = z7.string().regex(/^[a-f0-9]{40,64}$/);
2965
+ evidenceSchema2 = z7.object({
2706
2966
  head: commitSchema2,
2707
2967
  fingerprint: hashSchema,
2708
- scope: z6.array(z6.string()).min(1).max(1e3),
2709
- tree: z6.string().max(2 * 1024 * 1024),
2968
+ scope: z7.array(z7.string()).min(1).max(1e3),
2969
+ tree: z7.string().max(2 * 1024 * 1024),
2710
2970
  lastScopeCommit: commitSchema2,
2711
- paths: z6.array(z6.object({ path: z6.string(), exists: z6.boolean() })).max(1e3)
2971
+ paths: z7.array(z7.object({ path: z7.string(), exists: z7.boolean() })).max(1e3)
2712
2972
  });
2713
- eventSchema2 = z6.object({
2714
- at: z6.string().datetime(),
2715
- reviewer: z6.string().trim().min(1).max(200),
2716
- note: z6.string().trim().min(1).max(2500),
2717
- outcome: z6.enum(["addressed", "inapplicable", "deferred"]),
2973
+ dismissalReasonSchema = z7.enum(["no-dependency-claims", "unrelated-manifest-change"]);
2974
+ eventSchema2 = z7.object({
2975
+ at: z7.string().datetime(),
2976
+ reviewer: z7.string().trim().min(1).max(200).optional(),
2977
+ note: z7.string().trim().min(1).max(2500),
2978
+ outcome: z7.enum(["addressed", "inapplicable", "deferred", "dismissed"]),
2979
+ reasonCode: dismissalReasonSchema.optional(),
2718
2980
  finding: advisorySchema.refine((finding) => finding.evidence.kind !== "decision-anchor", "Decision findings use decision reviews"),
2719
2981
  evidence: evidenceSchema2
2720
2982
  });
2721
- recordSchema = z6.object({ version: z6.literal(1), id: hashSchema, events: z6.array(eventSchema2).min(1).max(200), digest: hashSchema });
2722
- reviewSummarySchema = z6.object({
2983
+ recordSchema = z7.object({ version: z7.literal(1), id: hashSchema, events: z7.array(eventSchema2).min(1).max(200), digest: hashSchema });
2984
+ reviewSummarySchema = z7.object({
2723
2985
  id: hashSchema,
2724
- status: z6.enum(["current", "reopened", "deferred", "unverified"]),
2725
- reason: z6.string(),
2726
- recordPath: z6.string().optional(),
2727
- reviewer: z6.string().optional(),
2728
- note: z6.string().optional(),
2729
- outcome: z6.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired"]).optional(),
2730
- reviewedHead: z6.string().optional()
2986
+ status: z7.enum(["current", "reopened", "deferred", "unverified"]),
2987
+ reason: z7.string(),
2988
+ recordPath: z7.string().optional(),
2989
+ reviewer: z7.string().optional(),
2990
+ note: z7.string().optional(),
2991
+ outcome: z7.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired", "dismissed"]).optional(),
2992
+ reviewedHead: z7.string().optional()
2731
2993
  });
2732
- advisoryReviewRequest = z6.object({
2733
- baselinePath: z6.string().min(1),
2994
+ advisoryReviewRequest = z7.object({
2995
+ baselinePath: z7.string().min(1),
2734
2996
  findingId: hashSchema,
2735
- action: z6.enum(["prepare", "addressed", "inapplicable", "deferred"]).default("prepare"),
2997
+ action: z7.enum(["prepare", "addressed", "inapplicable", "deferred", "dismiss"]).default("prepare"),
2998
+ reasonCode: dismissalReasonSchema.optional(),
2736
2999
  reviewer: eventSchema2.shape.reviewer.optional(),
2737
3000
  note: eventSchema2.shape.note.optional(),
2738
3001
  reviewToken: hashSchema.optional()
@@ -2743,12 +3006,12 @@ var init_advisory_review = __esm({
2743
3006
 
2744
3007
  // src/audit/audit.ts
2745
3008
  import fs11 from "fs/promises";
2746
- import path19 from "path";
3009
+ import path20 from "path";
2747
3010
  async function computeAudit(rootDir, options = {}) {
2748
3011
  return profilePhase("audit.current", () => auditCurrent(rootDir, options));
2749
3012
  }
2750
3013
  async function auditCurrent(rootDir, options) {
2751
- const resolvedRoot = path19.resolve(rootDir);
3014
+ const resolvedRoot = path20.resolve(rootDir);
2752
3015
  const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
2753
3016
  if (docs.length === 0) return null;
2754
3017
  const report = {
@@ -2782,7 +3045,7 @@ async function auditCurrent(rootDir, options) {
2782
3045
  }
2783
3046
  let decisionsPresent = false;
2784
3047
  try {
2785
- await fs11.access(path19.join(resolvedRoot, ".mason", "decisions"));
3048
+ await fs11.access(path20.join(resolvedRoot, ".mason", "decisions"));
2786
3049
  decisionsPresent = true;
2787
3050
  } catch {
2788
3051
  }
@@ -2794,13 +3057,21 @@ async function auditCurrent(rootDir, options) {
2794
3057
  changesSinceDoc,
2795
3058
  decisionsPresent
2796
3059
  };
3060
+ const policy = (await loadProjectConfig(resolvedRoot)).audit;
2797
3061
  const selected = options.checks ?? ALL_CHECKS;
2798
3062
  for (const name of ALL_CHECKS) {
2799
3063
  if (!selected.includes(name)) continue;
2800
- const { issues, advisories, suppressedAdvisories, skipped } = await (options.runCheck ? options.runCheck(name, ctx) : CHECKS[name](ctx));
3064
+ const exclusions = policy?.exclude?.[name] ?? [];
3065
+ const included = await includedCheckPaths(resolvedRoot, docs.map((doc) => doc.path), exclusions);
3066
+ const excluded = docs.filter((doc) => !included.has(doc.path)).map((doc) => ({ check: name, doc: doc.path }));
3067
+ if (excluded.length) (report.excludedChecks ??= []).push(...excluded);
3068
+ const scoped = { ...ctx, docs: docs.filter((doc) => included.has(doc.path)) };
3069
+ const { issues, advisories, suppressedAdvisories, skipped } = await (options.runCheck ? options.runCheck(name, scoped) : CHECKS[name](scoped));
2801
3070
  report.checksRun.push(name);
2802
3071
  report.issues.push(...issues);
2803
- report.advisories.push(...advisories);
3072
+ const advisoryPaths = await includedCheckPaths(resolvedRoot, [...new Set(advisories.map((f) => f.anchor.doc))], exclusions);
3073
+ report.advisories.push(...advisories.filter((f) => advisoryPaths.has(f.anchor.doc)));
3074
+ for (const f of advisories) if (!advisoryPaths.has(f.anchor.doc)) (report.excludedChecks ??= []).push({ check: name, doc: f.anchor.doc });
2804
3075
  report.suppressedAdvisories.push(...suppressedAdvisories ?? []);
2805
3076
  report.skippedChecks.push(...skipped);
2806
3077
  }
@@ -2812,6 +3083,8 @@ async function auditCurrent(rootDir, options) {
2812
3083
  var init_audit = __esm({
2813
3084
  "src/audit/audit.ts"() {
2814
3085
  "use strict";
3086
+ init_files();
3087
+ init_policy();
2815
3088
  init_profile();
2816
3089
  init_drift();
2817
3090
  init_snapshot();
@@ -2824,9 +3097,9 @@ var init_audit = __esm({
2824
3097
 
2825
3098
  // src/audit/repair.ts
2826
3099
  import fs12 from "fs/promises";
2827
- import path20 from "path";
3100
+ import path21 from "path";
2828
3101
  import { randomUUID as randomUUID2 } from "crypto";
2829
- import { z as z7 } from "zod";
3102
+ import { z as z8 } from "zod";
2830
3103
  function recheckable(finding) {
2831
3104
  return !("confidence" in finding) && finding.resolution === "recheck" && ["deleted-reference", "dead-command", "new-module"].includes(finding.type) && ["missing-path", "missing-script", "unmentioned-dir"].includes(finding.evidence.kind);
2832
3105
  }
@@ -2845,7 +3118,7 @@ async function docState(root) {
2845
3118
  docs.push([doc, null]);
2846
3119
  }
2847
3120
  }
2848
- return digest(docs);
3121
+ return digest([docs, (await loadProjectConfig(root)).audit]);
2849
3122
  }
2850
3123
  async function stableAudit(root, checks, options = {}) {
2851
3124
  return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
@@ -2894,7 +3167,7 @@ async function withRepairSession(root, options, run, validate, cachedAudit) {
2894
3167
  }
2895
3168
  async function prepareOriginal(rootDir, checks, options, session) {
2896
3169
  const root = await fs12.realpath(rootDir);
2897
- const selected = z7.array(checkSchema).nonempty().parse(checks);
3170
+ const selected = z8.array(checkSchema).nonempty().parse(checks);
2898
3171
  const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
2899
3172
  if (!report) throw new Error("No context files found to prepare a repair.");
2900
3173
  if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
@@ -2911,8 +3184,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
2911
3184
  }
2912
3185
  async function loadRepairBaseline(rootDir, baselinePath) {
2913
3186
  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;
3187
+ const declaredRoot = path21.resolve(rootDir);
3188
+ const relative = path21.isAbsolute(baselinePath) ? path21.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
2916
3189
  const stored = baselineSchema.parse(await readStoreJson(root, relative));
2917
3190
  const { digest: savedDigest, ...payload } = stored;
2918
3191
  if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
@@ -2958,6 +3231,9 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
2958
3231
  if (now && ("confidence" in finding || "confidence" in now)) {
2959
3232
  return { ...base, status: "unresolved", reason: "The original check still reports this claim." };
2960
3233
  }
3234
+ if (current.excludedChecks?.some((excluded) => excluded.check === finding.type && excluded.doc === finding.anchor.doc)) {
3235
+ return { ...base, status: "resolved", reason: "Excluded by project audit policy; this is not a correctness assessment. Original evidence is retained." };
3236
+ }
2961
3237
  const skipped = current.skippedChecks.filter((s) => s.check === finding.type && (!s.doc || s.doc === finding.anchor.doc));
2962
3238
  if (!current.checksRun?.includes(finding.type) || skipped.length) {
2963
3239
  return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
@@ -2968,6 +3244,9 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
2968
3244
  if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
2969
3245
  return { ...base, review, status: "review-required", reason: review.reason };
2970
3246
  }
3247
+ if (!("confidence" in finding) && finding.evidence.kind === "doc-behind-manifests" && !finding.evidence.matches?.length && (!now || !("confidence" in now) && now.resolution === "informational")) {
3248
+ return { ...base, status: "resolved", reason: "Manifest recency alone establishes no affected passage. Retained as information; no assessment is required." };
3249
+ }
2971
3250
  if (!("confidence" in finding) && (!recheckable(finding) || now)) {
2972
3251
  return {
2973
3252
  ...base,
@@ -2996,7 +3275,7 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
2996
3275
  const reviewedIds = new Set(current?.advisoryReviews?.filter((review) => review.status === "current").map((review) => review.id));
2997
3276
  const counts = { resolved: 0, unresolved: 0, "review-required": 0, unverified: 0 };
2998
3277
  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)));
3278
+ 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
3279
  const issuesRemain = counts.unresolved > 0 || newFindings.some((f) => "confidence" in f);
3001
3280
  return {
3002
3281
  version: 1,
@@ -3029,41 +3308,42 @@ var init_repair = __esm({
3029
3308
  init_findings();
3030
3309
  init_advisory_review();
3031
3310
  init_findings();
3032
- count2 = z7.number().int().nonnegative();
3033
- reportSchema = z7.object({
3034
- version: z7.literal(1),
3035
- root: z7.string(),
3036
- gitAvailable: z7.literal(true),
3311
+ count2 = z8.number().int().nonnegative();
3312
+ reportSchema = z8.object({
3313
+ version: z8.literal(1),
3314
+ root: z8.string(),
3315
+ gitAvailable: z8.literal(true),
3037
3316
  headHash: commitSchema.shape.hash,
3038
- checksRun: z7.array(checkSchema).nonempty(),
3039
- docs: z7.array(z7.object({
3040
- path: z7.string().refine((value) => normalizeRepoPath(value) === value),
3041
- scope: z7.string().optional(),
3042
- kind: z7.enum(["instructions", "readme"]).optional(),
3317
+ checksRun: z8.array(checkSchema).nonempty(),
3318
+ docs: z8.array(z8.object({
3319
+ path: z8.string().refine((value) => normalizeRepoPath(value) === value),
3320
+ scope: z8.string().optional(),
3321
+ kind: z8.enum(["instructions", "readme", "guide"]).optional(),
3043
3322
  lastCommit: commitSchema.nullable(),
3044
- dirty: z7.boolean(),
3323
+ dirty: z8.boolean(),
3045
3324
  lineCount: count2
3046
3325
  })).nonempty(),
3047
- decisionsChecked: z7.boolean(),
3048
- clean: z7.boolean(),
3049
- issues: z7.array(issueSchema),
3050
- advisories: z7.array(advisorySchema),
3051
- suppressedAdvisories: z7.array(advisorySchema).optional(),
3052
- skippedChecks: z7.array(z7.object({ check: z7.string(), reason: z7.string(), doc: z7.string().optional() }))
3326
+ decisionsChecked: z8.boolean(),
3327
+ clean: z8.boolean(),
3328
+ excludedChecks: z8.array(z8.object({ check: checkSchema, doc: z8.string() })).optional(),
3329
+ issues: z8.array(issueSchema),
3330
+ advisories: z8.array(advisorySchema),
3331
+ suppressedAdvisories: z8.array(advisorySchema).optional(),
3332
+ skippedChecks: z8.array(z8.object({ check: z8.string(), reason: z8.string(), doc: z8.string().optional() }))
3053
3333
  });
3054
- baselineSchema = z7.object({
3055
- kind: z7.literal("mason-audit-repair"),
3056
- version: z7.literal(1),
3057
- createdAt: z7.string().datetime(),
3334
+ baselineSchema = z8.object({
3335
+ kind: z8.literal("mason-audit-repair"),
3336
+ version: z8.literal(1),
3337
+ createdAt: z8.string().datetime(),
3058
3338
  report: reportSchema,
3059
- digest: z7.string().regex(/^[a-f0-9]{64}$/)
3339
+ digest: z8.string().regex(/^[a-f0-9]{64}$/)
3060
3340
  });
3061
- repairFindingSchema = z7.object({
3062
- id: z7.string(),
3063
- original: z7.union([issueSchema, advisorySchema]),
3064
- status: z7.enum(["resolved", "unresolved", "review-required", "unverified"]),
3065
- reason: z7.string(),
3066
- current: z7.union([issueSchema, advisorySchema]).optional(),
3341
+ repairFindingSchema = z8.object({
3342
+ id: z8.string(),
3343
+ original: z8.union([issueSchema, advisorySchema]),
3344
+ status: z8.enum(["resolved", "unresolved", "review-required", "unverified"]),
3345
+ reason: z8.string(),
3346
+ current: z8.union([issueSchema, advisorySchema]).optional(),
3067
3347
  review: reviewSummarySchema.optional()
3068
3348
  });
3069
3349
  }
@@ -3071,10 +3351,10 @@ var init_repair = __esm({
3071
3351
 
3072
3352
  // src/automation/evidence.ts
3073
3353
  import fs13 from "fs/promises";
3074
- import path21 from "path";
3354
+ import path22 from "path";
3075
3355
  import { createHash as createHash3 } from "crypto";
3076
- import fg5 from "fast-glob";
3077
- import { z as z8 } from "zod";
3356
+ import fg7 from "fast-glob";
3357
+ import { z as z9 } from "zod";
3078
3358
  async function git3(root, ...args) {
3079
3359
  return profilePhase("automation.git", async () => (await execGit(args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
3080
3360
  }
@@ -3091,6 +3371,11 @@ async function readInputs(root) {
3091
3371
  }
3092
3372
  async function collectInputs(root) {
3093
3373
  const docPaths = await discoverDocPaths(root);
3374
+ const auditPolicy = (await loadProjectConfig(root)).audit;
3375
+ const allowed = /* @__PURE__ */ new Map();
3376
+ for (const name of ["deleted-reference", "new-module", "stale-count", "dead-command"]) {
3377
+ allowed.set(name, await includedCheckPaths(root, docPaths, auditPolicy?.exclude?.[name] ?? []));
3378
+ }
3094
3379
  const [headText, docStatus, shallowPath, replacements] = await Promise.all([
3095
3380
  git3(root, "rev-parse", "HEAD"),
3096
3381
  docPaths.length ? git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...docPaths.map((p) => `:(literal)${p}`)) : "",
@@ -3100,7 +3385,7 @@ async function collectInputs(root) {
3100
3385
  const head = headText.trim();
3101
3386
  let shallow = null;
3102
3387
  try {
3103
- shallow = await fs13.readFile(path21.resolve(root, shallowPath.trim()), "utf8");
3388
+ shallow = await fs13.readFile(path22.resolve(root, shallowPath.trim()), "utf8");
3104
3389
  } catch (error) {
3105
3390
  if (error.code !== "ENOENT") throw error;
3106
3391
  }
@@ -3119,18 +3404,18 @@ async function collectInputs(root) {
3119
3404
  docContents.push([file, text2]);
3120
3405
  const parsed = { path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") };
3121
3406
  parsedDocs.push(parsed);
3122
- for (const claim of parsed.claims.paths) {
3407
+ for (const claim of allowed.get("deleted-reference").has(file) ? parsed.claims.paths : []) {
3123
3408
  const scope = pathClaimScope(file, claim);
3124
3409
  if (scope?.candidates.some(gitMetadataPath)) continue;
3125
3410
  for (const candidate of scope?.candidates ?? []) {
3126
3411
  if (optionalMasonPath(candidate)) continue;
3127
- const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path21.posix.dirname(candidate))]);
3412
+ const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path22.posix.dirname(candidate))]);
3128
3413
  claims.push([candidate, filePresent, parentPresent]);
3129
3414
  }
3130
3415
  }
3131
3416
  }
3132
- const combinedDocs = moduleDocumentation(parsedDocs);
3133
- const countClaims = parsedDocs.flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
3417
+ const combinedDocs = moduleDocumentation(parsedDocs.filter((doc) => allowed.get("new-module").has(doc.path)));
3418
+ const countClaims = parsedDocs.filter((doc) => allowed.get("stale-count").has(doc.path)).flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
3134
3419
  const decisionDirectory = await storePath(root, ".mason/decisions");
3135
3420
  const decisionPresence = await fs13.lstat(decisionDirectory).then((stat) => stat.isDirectory() ? "directory" : "file", (error) => {
3136
3421
  if (error.code === "ENOENT") return "absent";
@@ -3139,8 +3424,8 @@ async function collectInputs(root) {
3139
3424
  const [modules, counts, commands2, decisionFiles] = await Promise.all([
3140
3425
  combinedDocs ? moduleCandidates(root, combinedDocs) : [],
3141
3426
  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 })
3427
+ commandInputs(root, parsedDocs.filter((doc) => allowed.get("dead-command").has(doc.path))),
3428
+ fg7(".mason/decisions/*.json", { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false })
3144
3429
  ]);
3145
3430
  const decisions = await Promise.all(decisionFiles.sort().map(async (file) => {
3146
3431
  await storePath(root, file);
@@ -3150,7 +3435,7 @@ async function collectInputs(root) {
3150
3435
  git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
3151
3436
  git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
3152
3437
  ]) : ["", ""];
3153
- const common = [6, engineVersion, head, shallow, replacements, docContents];
3438
+ const common = [7, engineVersion, head, shallow, replacements, docContents, auditPolicy];
3154
3439
  const keys = {
3155
3440
  "deleted-reference": hash([common, claims, docStatus]),
3156
3441
  "new-module": hash([common, modules]),
@@ -3197,6 +3482,8 @@ var engineVersion, hash, content, cacheSchema;
3197
3482
  var init_evidence = __esm({
3198
3483
  "src/automation/evidence.ts"() {
3199
3484
  "use strict";
3485
+ init_policy();
3486
+ init_files();
3200
3487
  init_git_read();
3201
3488
  init_profile();
3202
3489
  init_docs();
@@ -3210,10 +3497,10 @@ var init_evidence = __esm({
3210
3497
  init_dead_command();
3211
3498
  init_storage();
3212
3499
  init_advisory_review();
3213
- engineVersion = true ? "0.19.0" : "development";
3500
+ engineVersion = true ? "0.19.2" : "development";
3214
3501
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
3215
3502
  content = readAuditInput;
3216
- cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
3503
+ cacheSchema = z9.object({ version: z9.literal(1), entries: z9.record(z9.object({ key: z9.string(), result: checkResultSchema })), digest: z9.string() });
3217
3504
  }
3218
3505
  });
3219
3506
 
@@ -3221,7 +3508,7 @@ var init_evidence = __esm({
3221
3508
  import os2 from "os";
3222
3509
  import fs14 from "fs/promises";
3223
3510
  import { randomUUID as randomUUID3 } from "crypto";
3224
- import { z as z9 } from "zod";
3511
+ import { z as z10 } from "zod";
3225
3512
  function parseExecution(raw) {
3226
3513
  try {
3227
3514
  return executionSchema.parse(raw);
@@ -3239,7 +3526,7 @@ function automationFailure(error) {
3239
3526
  codes.add(String(cause.code));
3240
3527
  cause = cause.cause;
3241
3528
  }
3242
- const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(message2) ? "inputs-changed" : /Automation is busy/.test(message2) ? "busy" : /not a git repository|unknown revision|bad revision|different history|unreachable/.test(message2) ? "history-unavailable" : error instanceof z9.ZodError || error instanceof SyntaxError || /Hook input|Expected one command|Unknown automation command|--host/.test(message2) ? "invalid-input" : /store|baseline|modified|symbolic link|Symlink|automation state|state belongs/.test(message2) ? "invalid-evidence" : [...codes].some((c) => /^E[A-Z]+$/.test(c)) ? "io-error" : "internal";
3529
+ const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(message2) ? "inputs-changed" : /Automation is busy/.test(message2) ? "busy" : /not a git repository|unknown revision|bad revision|different history|unreachable/.test(message2) ? "history-unavailable" : error instanceof z10.ZodError || error instanceof SyntaxError || /Hook input|Expected one command|Unknown automation command|--host/.test(message2) ? "invalid-input" : /store|baseline|modified|symbolic link|Symlink|automation state|state belongs/.test(message2) ? "invalid-evidence" : [...codes].some((c) => /^E[A-Z]+$/.test(c)) ? "io-error" : "internal";
3243
3530
  return { code, message: message2, retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code), receiptRecorded: false };
3244
3531
  }
3245
3532
  function failureMessage(error) {
@@ -3368,41 +3655,103 @@ var init_execution = __esm({
3368
3655
  init_store();
3369
3656
  init_evidence();
3370
3657
  init_storage();
3371
- failureSchema = z9.object({
3372
- code: z9.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
3373
- message: z9.string(),
3374
- retryable: z9.boolean(),
3375
- receiptRecorded: z9.boolean()
3658
+ failureSchema = z10.object({
3659
+ code: z10.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
3660
+ message: z10.string(),
3661
+ retryable: z10.boolean(),
3662
+ receiptRecorded: z10.boolean()
3376
3663
  });
3377
- attemptSchema = z9.object({
3378
- id: z9.string().regex(/^[a-zA-Z0-9_-]+$/),
3379
- lease: z9.literal(true).optional(),
3380
- event: z9.string(),
3381
- startedAt: z9.string(),
3382
- finishedAt: z9.string().optional(),
3383
- durationMs: z9.number().nonnegative().optional(),
3384
- pid: z9.number().int().positive(),
3385
- host: z9.string(),
3386
- status: z9.enum(["running", "completed", "failed", "unknown"]),
3387
- verificationStatus: z9.string().optional(),
3388
- reportPath: z9.string().optional(),
3664
+ attemptSchema = z10.object({
3665
+ id: z10.string().regex(/^[a-zA-Z0-9_-]+$/),
3666
+ lease: z10.literal(true).optional(),
3667
+ event: z10.string(),
3668
+ startedAt: z10.string(),
3669
+ finishedAt: z10.string().optional(),
3670
+ durationMs: z10.number().nonnegative().optional(),
3671
+ pid: z10.number().int().positive(),
3672
+ host: z10.string(),
3673
+ status: z10.enum(["running", "completed", "failed", "unknown"]),
3674
+ verificationStatus: z10.string().optional(),
3675
+ reportPath: z10.string().optional(),
3389
3676
  failure: failureSchema.optional()
3390
3677
  });
3391
- executionSchema = z9.object({ version: z9.literal(1), attempts: z9.array(attemptSchema).max(160), discardedAttempts: z9.number().int().nonnegative().default(0) });
3392
- notificationsSchema = z9.object({ version: z9.literal(1), sessions: z9.record(z9.object({
3393
- signature: z9.string(),
3678
+ executionSchema = z10.object({ version: z10.literal(1), attempts: z10.array(attemptSchema).max(160), discardedAttempts: z10.number().int().nonnegative().default(0) });
3679
+ notificationsSchema = z10.object({ version: z10.literal(1), sessions: z10.record(z10.object({
3680
+ signature: z10.string(),
3394
3681
  code: failureSchema.shape.code,
3395
- message: z9.string(),
3396
- count: z9.number().int().positive(),
3397
- lastAt: z9.string()
3682
+ message: z10.string(),
3683
+ count: z10.number().int().positive(),
3684
+ lastAt: z10.string()
3398
3685
  })) });
3399
3686
  leasePath = (directory, id) => `${directory}/executions/${id}.json`;
3400
3687
  }
3401
3688
  });
3402
3689
 
3690
+ // src/decisions/pending.ts
3691
+ var pending_exports = {};
3692
+ __export(pending_exports, {
3693
+ pendingKnowledge: () => pendingKnowledge
3694
+ });
3695
+ async function pendingKnowledge(root, now = Date.now()) {
3696
+ try {
3697
+ const [store, status] = await Promise.all([
3698
+ loadDecisionStore(root),
3699
+ execGit(
3700
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".mason/decisions"],
3701
+ { cwd: root, timeout: 1e4, maxBuffer: 1024 * 1024 }
3702
+ )
3703
+ ]);
3704
+ const changed = /* @__PURE__ */ new Set();
3705
+ const entries = status.stdout.split("\0");
3706
+ for (let i = 0; i < entries.length; i++) {
3707
+ const entry = entries[i];
3708
+ if (!entry) continue;
3709
+ const file = entry.slice(3);
3710
+ if (/^\.mason\/decisions\/[^/]+\.json$/.test(file)) changed.add(file);
3711
+ if (/[RC]/.test(entry.slice(0, 2))) i++;
3712
+ }
3713
+ const records = new Map(store.records.map((record) => [`.mason/decisions/${record.id}.json`, record]));
3714
+ const age = (at) => {
3715
+ const time = at ? Date.parse(at) : NaN;
3716
+ return Number.isFinite(time) ? `${Math.max(0, Math.floor((now - time) / 864e5))} days` : "unknown age";
3717
+ };
3718
+ const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ");
3719
+ const list = (items) => items.slice(0, 5).map(safe).join("; ") + (items.length > 5 ? `; ${items.length - 5} more` : "");
3720
+ const proposals = store.records.filter((record) => record.status === "active" && decisionApproval(record) === "proposed").map((record) => {
3721
+ const history = record.version === 2 ? record.history : [];
3722
+ let lastReview = -1;
3723
+ history.forEach((event, index2) => {
3724
+ if (event.kind === "accepted" || event.kind === "reaffirmed") lastReview = index2;
3725
+ });
3726
+ const since = history.slice(lastReview + 1).find((event) => event.kind === "created" || event.kind === "revised")?.at ?? record.createdAt;
3727
+ return { record, since };
3728
+ }).sort((a, b) => {
3729
+ const timestamp = (value) => Number.isFinite(Date.parse(value)) ? Date.parse(value) : Infinity;
3730
+ return timestamp(a.since) - timestamp(b.since) || a.record.id.localeCompare(b.record.id);
3731
+ });
3732
+ const lines = [];
3733
+ if (changed.size) lines.push(`${changed.size} uncommitted decision record(s): ${list([...changed].sort().map((file) => `${file} (saved-record age: ${age(records.get(file)?.updatedAt)})`))}.`);
3734
+ if (proposals.length) lines.push(`${proposals.length} proposal(s) awaiting review (oldest first): ${list(proposals.map(({ record, since }) => `.mason/decisions/${record.id}.json (${age(since)})`))}.`);
3735
+ if (store.diagnostics.length) lines.push(`${store.diagnostics.length} decision record(s) could not be read; pending knowledge summary is incomplete.`);
3736
+ return lines.length ? `Mason pending knowledge:
3737
+ ${lines.join("\n")}
3738
+ Informational only; review and commit through the normal project workflow. No approval is implied.` : null;
3739
+ } catch {
3740
+ return "Mason pending knowledge: unavailable; decision records or Git status could not be read.";
3741
+ }
3742
+ }
3743
+ var init_pending = __esm({
3744
+ "src/decisions/pending.ts"() {
3745
+ "use strict";
3746
+ init_git_read();
3747
+ init_decisions();
3748
+ init_provenance();
3749
+ }
3750
+ });
3751
+
3403
3752
  // src/automation/runtime.ts
3404
3753
  import { randomUUID as randomUUID4 } from "crypto";
3405
- import { z as z10 } from "zod";
3754
+ import { z as z11 } from "zod";
3406
3755
  async function observeReadOnlyTool(dir, event) {
3407
3756
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
3408
3757
  await withLock(ws.root, ws.directory, async () => {
@@ -3624,6 +3973,19 @@ async function automate(dir, event) {
3624
3973
  const state = await loadState(ws);
3625
3974
  if (state.analysis?.id !== result.analysis.id) throw new Error("Automation evidence changed during publication; retry against the current inputs.");
3626
3975
  const report = structuredClone(result.analysis.report);
3976
+ if (!state.notifications) {
3977
+ let baseline = { findings: report.findings, head: report.head };
3978
+ if (state.latest) {
3979
+ try {
3980
+ const previous = z11.object({ findings: z11.array(repairFindingSchema), head: z11.string() }).safeParse(await readStoreJson(ws.root, state.latest));
3981
+ if (!previous.success) throw new Error("Unsupported saved report");
3982
+ baseline = previous.data;
3983
+ } catch {
3984
+ report.diagnostics.push("The saved notification baseline could not be read; initialized from the current audit. Original repair evidence is retained. Run an explicit check for all outstanding findings.");
3985
+ }
3986
+ }
3987
+ state.notifications = createNotificationState(baseline.findings, baseline.head);
3988
+ }
3627
3989
  if (result.shared) report.checks = { ...report.checks, ran: [], reused: [.../* @__PURE__ */ new Set([...report.checks.ran, ...report.checks.reused])], shared: true };
3628
3990
  report.reportPath = ws.directory + "/checks/" + randomUUID4() + ".json";
3629
3991
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -3636,12 +3998,8 @@ async function automate(dir, event) {
3636
3998
  if (Object.keys(state.sessions).length >= 128) throw new Error("Too many sessions with unfinished tool calls; original evidence was retained.");
3637
3999
  state.sessions[key] = {
3638
4000
  host: event.host,
3639
- seen: null,
3640
- continued: false,
3641
- initialIssues: report.findings.filter((f) => f.status === "unresolved").map((f) => f.id),
3642
- initialDocs: inputs.docs,
4001
+ notifiedDiagnostics: [],
3643
4002
  lastUsed: now,
3644
- mutationObserved: false,
3645
4003
  pending: {},
3646
4004
  coverageGaps: [],
3647
4005
  events: {}
@@ -3656,7 +4014,6 @@ async function automate(dir, event) {
3656
4014
  session.pending[event.toolId] = inputs.fingerprint;
3657
4015
  }
3658
4016
  if (event.event === "after_tool" && event.mutating) {
3659
- session.mutationObserved = true;
3660
4017
  if (!event.toolId || !session.pending[event.toolId]) {
3661
4018
  const gap = "A tool completed without an observed matching pre-tool capture in this checkout; pre-edit coverage is unknown. The tool may have changed checkouts; captures are kept separately for each worktree.";
3662
4019
  if (!session.coverageGaps.includes(gap)) session.coverageGaps.push(gap);
@@ -3667,31 +4024,39 @@ async function automate(dir, event) {
3667
4024
  if (session.coverageGaps.length && report.status !== "unavailable") report.status = "incomplete";
3668
4025
  report.capture = report.status !== "unavailable" && !session.coverageGaps.length && (session.events.session_start || session.events.before_tool) ? "observed" : "unknown";
3669
4026
  }
3670
- const signature = hash([report.status, report.findings, report.diagnostics, report.checks.skipped]);
3671
- const relevant = report.findings.some((f) => f.status === "unresolved" && session && (!session.initialIssues.includes(f.id) || session.initialDocs[f.original.anchor.doc] !== inputs.docs[f.original.anchor.doc]));
3672
- const continueOnce = event.event === "task_end" && !!session?.mutationObserved && relevant && !session.continued && !event.stopHookActive;
3673
- const notify = !session || newSession || signature !== session.seen || continueOnce;
3674
- if (session) {
3675
- session.seen = signature;
3676
- if (continueOnce) session.continued = true;
3677
- }
3678
- const persistReport = !state.latest || state.fingerprint !== inputs.fingerprint || notify || event.event === "task_end";
4027
+ const selected = session && event.event === "task_end" ? completionFindings(state.notifications, report.findings) : [];
4028
+ state.agentNotifications ??= { ...structuredClone(state.notifications), delivered: {} };
4029
+ const feedback = session && ["turn_start", "after_tool"].includes(event.event) ? completionFindings(state.agentNotifications, report.findings) : [];
4030
+ const diagnostics = session ? report.diagnostics.filter((d) => !session.notifiedDiagnostics.includes(d)).slice(0, 2) : [];
4031
+ if (session) session.notifiedDiagnostics = [.../* @__PURE__ */ new Set([
4032
+ ...session.notifiedDiagnostics.filter((d) => report.diagnostics.includes(d)),
4033
+ ...diagnostics
4034
+ ])];
4035
+ const message2 = !session ? summarize(report) : [
4036
+ completionSummary(selected, report.reportPath),
4037
+ completionSummary(feedback, report.reportPath),
4038
+ ...diagnostics.length ? [`Mason: verification limited. ${diagnostics.slice(0, 2).map(cleanText).join(" ")} Evidence: ${report.reportPath}.`] : []
4039
+ ].filter(Boolean).join("\n") || null;
4040
+ const notify = message2 !== null;
4041
+ const persistReport = !state.latest || state.fingerprint !== inputs.fingerprint || notify || newSession || event.event === "task_end";
3679
4042
  if (!persistReport) report.reportPath = state.latest;
3680
4043
  state.updatedAt = now;
3681
4044
  state.fingerprint = inputs.fingerprint;
3682
4045
  state.latest = report.reportPath;
3683
4046
  if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
3684
4047
  await writeStoreJson(ws.root, ws.directory + "/state.json", state);
3685
- return { report, message: notify ? summarize(report) : null, continueOnce };
4048
+ return { report, message: message2 };
3686
4049
  }));
3687
4050
  }));
3688
4051
  }
3689
4052
  async function automationStatus(dir) {
4053
+ const { pendingKnowledge: pendingKnowledge2 } = await Promise.resolve().then(() => (init_pending(), pending_exports));
3690
4054
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
4055
+ const knowledge = await pendingKnowledge2(ws.root);
3691
4056
  const execution = await executionStatus(ws.root, ws.directory);
3692
4057
  const notifications = await failureNotifications(ws.root, ws.directory);
3693
4058
  const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
3694
- if (raw === null) return { version: 1, status: execution.status === "not-observed" ? "not-observed" : "unavailable", root: ws.root, branch: ws.branch, baselinePaths: [], hosts: {}, execution, notifications };
4059
+ if (raw === null) return { version: 1, status: execution.status === "not-observed" ? "not-observed" : "unavailable", root: ws.root, branch: ws.branch, baselinePaths: [], hosts: {}, execution, notifications, knowledge };
3695
4060
  const state = parseState(raw);
3696
4061
  if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) throw new Error("Automation state belongs to another workspace.");
3697
4062
  const inputs = await readInputs(ws.root);
@@ -3714,6 +4079,8 @@ async function automationStatus(dir) {
3714
4079
  hosts: hosts2,
3715
4080
  execution,
3716
4081
  notifications,
4082
+ knowledge,
4083
+ notificationBaselineHead: state.notifications?.baselineHead ?? null,
3717
4084
  note: "Observed events do not prove all tool paths are intercepted. Run check to verify the retained evidence."
3718
4085
  };
3719
4086
  }
@@ -3721,6 +4088,7 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
3721
4088
  var init_runtime = __esm({
3722
4089
  "src/automation/runtime.ts"() {
3723
4090
  "use strict";
4091
+ init_notifications();
3724
4092
  init_profile();
3725
4093
  init_inspection();
3726
4094
  init_execution();
@@ -3732,34 +4100,34 @@ var init_runtime = __esm({
3732
4100
  SCOPE = "Documentation audit evidence only. Hook receipts show observed events, not complete interception. Resolved findings no longer fail their checks or have a recorded assessment covering current evidence. Repair and review only within the user's task authorization.";
3733
4101
  priority = { resolved: 0, "review-required": 1, unresolved: 2, unverified: 3 };
3734
4102
  cleanText = (text2) => text2.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
3735
- analysisSchema = z10.object({
3736
- id: z10.string(),
3737
- fingerprint: z10.string(),
3738
- completedAt: z10.number(),
3739
- baselineDigest: z10.string(),
4103
+ analysisSchema = z11.object({
4104
+ id: z11.string(),
4105
+ fingerprint: z11.string(),
4106
+ completedAt: z11.number(),
4107
+ baselineDigest: z11.string(),
3740
4108
  currentAudit: reportSchema.nullable().optional(),
3741
- report: z10.object({
3742
- version: z10.literal(1),
3743
- status: z10.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
3744
- root: z10.string(),
3745
- branch: z10.string(),
3746
- head: z10.string(),
3747
- baselinePaths: z10.array(z10.string()),
3748
- reportPath: z10.string(),
3749
- findings: z10.array(repairFindingSchema),
3750
- diagnostics: z10.array(z10.string()),
3751
- checks: z10.object({
3752
- ran: z10.array(z10.string()),
3753
- reused: z10.array(z10.string()),
3754
- skipped: z10.array(z10.object({ check: z10.string(), reason: z10.string(), doc: z10.string().optional() })),
3755
- auditReused: z10.boolean().optional()
4109
+ report: z11.object({
4110
+ version: z11.literal(1),
4111
+ status: z11.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
4112
+ root: z11.string(),
4113
+ branch: z11.string(),
4114
+ head: z11.string(),
4115
+ baselinePaths: z11.array(z11.string()),
4116
+ reportPath: z11.string(),
4117
+ findings: z11.array(repairFindingSchema),
4118
+ diagnostics: z11.array(z11.string()),
4119
+ checks: z11.object({
4120
+ ran: z11.array(z11.string()),
4121
+ reused: z11.array(z11.string()),
4122
+ skipped: z11.array(z11.object({ check: z11.string(), reason: z11.string(), doc: z11.string().optional() })),
4123
+ auditReused: z11.boolean().optional()
3756
4124
  }),
3757
- counts: z10.object({ resolved: z10.number(), unresolved: z10.number(), "review-required": z10.number(), unverified: z10.number() }),
3758
- capture: z10.enum(["observed", "unknown"]),
3759
- scope: z10.string()
4125
+ counts: z11.object({ resolved: z11.number(), unresolved: z11.number(), "review-required": z11.number(), unverified: z11.number() }),
4126
+ capture: z11.enum(["observed", "unknown"]),
4127
+ scope: z11.string()
3760
4128
  })
3761
4129
  });
3762
- sharedSchema = z10.object({ analysis: analysisSchema, digest: z10.string() });
4130
+ sharedSchema = z11.object({ analysis: analysisSchema, digest: z11.string() });
3763
4131
  baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
3764
4132
  state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
3765
4133
  )));
@@ -3768,9 +4136,9 @@ var init_runtime = __esm({
3768
4136
 
3769
4137
  // src/automation/hook-root.ts
3770
4138
  import fs15 from "fs/promises";
3771
- import path22 from "path";
4139
+ import path23 from "path";
3772
4140
  async function repositoryRoot(dir) {
3773
- if (!path22.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
4141
+ if (!path23.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
3774
4142
  try {
3775
4143
  return await fs15.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
3776
4144
  } catch (error) {
@@ -3800,9 +4168,9 @@ var init_hook_root = __esm({
3800
4168
  });
3801
4169
 
3802
4170
  // src/setup/model.ts
3803
- import { z as z11 } from "zod";
4171
+ import { z as z12 } from "zod";
3804
4172
  import fs16 from "fs/promises";
3805
- import path23 from "path";
4173
+ import path24 from "path";
3806
4174
  async function loadSetup(root) {
3807
4175
  const raw = await readStoreJson(root, SETUP_PATH);
3808
4176
  if (raw === null) return null;
@@ -3811,7 +4179,7 @@ async function loadSetup(root) {
3811
4179
  async function effectiveSetup(root) {
3812
4180
  const local = await loadSetup(root);
3813
4181
  if (local) return { root, setup: local };
3814
- const common = await fs16.realpath(path23.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
4182
+ const common = await fs16.realpath(path24.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
3815
4183
  const own = await fs16.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
3816
4184
  if (common === own) return { root, setup: null };
3817
4185
  const primary = (await git3(root, "worktree", "list", "--porcelain", "-z")).split("\0\0")[0].split("\0");
@@ -3824,7 +4192,7 @@ async function effectiveSetup(root) {
3824
4192
  if (/must be run in a work tree/.test(String(error.stderr))) return { root, setup: null };
3825
4193
  throw error;
3826
4194
  }
3827
- const inheritedCommon = await fs16.realpath(path23.resolve(
4195
+ const inheritedCommon = await fs16.realpath(path24.resolve(
3828
4196
  inheritedRoot,
3829
4197
  (await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim()
3830
4198
  ));
@@ -3847,29 +4215,29 @@ var init_model = __esm({
3847
4215
  "use strict";
3848
4216
  init_evidence();
3849
4217
  init_storage();
3850
- setupHostSchema = z11.object({
3851
- configuredVersion: z11.string(),
3852
- revision: z11.string().uuid(),
3853
- fingerprint: z11.string(),
3854
- mcpFingerprint: z11.string(),
3855
- instructions: z11.array(z11.string())
4218
+ setupHostSchema = z12.object({
4219
+ configuredVersion: z12.string(),
4220
+ revision: z12.string().uuid(),
4221
+ fingerprint: z12.string(),
4222
+ mcpFingerprint: z12.string(),
4223
+ instructions: z12.array(z12.string())
3856
4224
  });
3857
- setupSchema = z11.object({ version: z11.literal(2), hosts: z11.object({
4225
+ setupSchema = z12.object({ version: z12.literal(2), hosts: z12.object({
3858
4226
  codex: setupHostSchema.optional(),
3859
4227
  claude: setupHostSchema.optional()
3860
4228
  }) });
3861
4229
  SETUP_PATH = ".mason/local/setup.json";
3862
4230
  INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
3863
- inactiveHostsSchema = z11.object({ version: z11.literal(1), hosts: z11.array(z11.enum(["claude", "codex"])) });
3864
- receiptSchema = z11.object({
3865
- version: z11.literal(1),
3866
- host: z11.enum(["codex", "claude"]),
3867
- status: z11.enum(["installing", "configured"]),
3868
- initialReportPath: z11.string(),
3869
- initialBaselinePaths: z11.array(z11.string()),
3870
- root: z11.string(),
3871
- revision: z11.string().uuid(),
3872
- configuredAt: z11.string().optional()
4231
+ inactiveHostsSchema = z12.object({ version: z12.literal(1), hosts: z12.array(z12.enum(["claude", "codex"])) });
4232
+ receiptSchema = z12.object({
4233
+ version: z12.literal(1),
4234
+ host: z12.enum(["codex", "claude"]),
4235
+ status: z12.enum(["installing", "configured"]),
4236
+ initialReportPath: z12.string(),
4237
+ initialBaselinePaths: z12.array(z12.string()),
4238
+ root: z12.string(),
4239
+ revision: z12.string().uuid(),
4240
+ configuredAt: z12.string().optional()
3873
4241
  });
3874
4242
  }
3875
4243
  });
@@ -3930,7 +4298,7 @@ var init_launcher = __esm({
3930
4298
  "use strict";
3931
4299
  init_evidence();
3932
4300
  init_model();
3933
- executingVersion = () => true ? "0.19.0" : "development";
4301
+ executingVersion = () => true ? "0.19.2" : "development";
3934
4302
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3935
4303
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3936
4304
  }
@@ -3944,7 +4312,7 @@ __export(observations_exports, {
3944
4312
  readObservation: () => readObservation
3945
4313
  });
3946
4314
  import fs18 from "fs/promises";
3947
- import { z as z12 } from "zod";
4315
+ import { z as z13 } from "zod";
3948
4316
  function observationPath(directory, host, revision, version = executingVersion()) {
3949
4317
  return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
3950
4318
  }
@@ -4015,26 +4383,26 @@ var init_observations = __esm({
4015
4383
  init_storage();
4016
4384
  init_model();
4017
4385
  init_launcher();
4018
- observationSchema = z12.object({
4019
- version: z12.literal(1),
4020
- masonVersion: z12.string(),
4021
- root: z12.string(),
4022
- revision: z12.string(),
4023
- host: z12.enum(["codex", "claude"]),
4024
- contextCalls: z12.number().int().nonnegative(),
4025
- lastContextAt: z12.string().optional(),
4026
- sessions: z12.record(z12.object({
4027
- events: z12.array(z12.enum(events)),
4028
- at: z12.string(),
4029
- verificationStatus: z12.string().optional(),
4030
- reportPath: z12.string().optional()
4386
+ observationSchema = z13.object({
4387
+ version: z13.literal(1),
4388
+ masonVersion: z13.string(),
4389
+ root: z13.string(),
4390
+ revision: z13.string(),
4391
+ host: z13.enum(["codex", "claude"]),
4392
+ contextCalls: z13.number().int().nonnegative(),
4393
+ lastContextAt: z13.string().optional(),
4394
+ sessions: z13.record(z13.object({
4395
+ events: z13.array(z13.enum(events)),
4396
+ at: z13.string(),
4397
+ verificationStatus: z13.string().optional(),
4398
+ reportPath: z13.string().optional()
4031
4399
  }))
4032
4400
  });
4033
4401
  }
4034
4402
  });
4035
4403
 
4036
4404
  // src/automation/adapters.ts
4037
- import { z as z13 } from "zod";
4405
+ import { z as z14 } from "zod";
4038
4406
  function normalizeHook(host, raw) {
4039
4407
  hostSchema.parse(host);
4040
4408
  const input2 = inputSchema.parse(raw);
@@ -4044,8 +4412,7 @@ function normalizeHook(host, raw) {
4044
4412
  host,
4045
4413
  sessionId: input2.session_id,
4046
4414
  toolId: input2.tool_use_id,
4047
- mutating: !!input2.tool_name && !readOnly,
4048
- stopHookActive: input2.stop_hook_active || input2.permission_mode === "plan"
4415
+ mutating: !!input2.tool_name && !readOnly
4049
4416
  } };
4050
4417
  }
4051
4418
  async function runAutomationHook(host, stdin, options = {}) {
@@ -4083,7 +4450,7 @@ async function runAutomationHook(host, stdin, options = {}) {
4083
4450
  if (warning) result.message = [result.message, warning].filter(Boolean).join("\n");
4084
4451
  if (!result.message) return null;
4085
4452
  if (name === "Stop") {
4086
- return result.continueOnce ? { decision: "block", reason: result.message } : { systemMessage: result.message };
4453
+ return { systemMessage: result.message };
4087
4454
  }
4088
4455
  return { hookSpecificOutput: { hookEventName: name, additionalContext: result.message } };
4089
4456
  } catch (error) {
@@ -4117,15 +4484,15 @@ var init_adapters = __esm({
4117
4484
  init_runtime();
4118
4485
  init_store();
4119
4486
  init_hook_root();
4120
- inputSchema = z13.object({
4121
- cwd: z13.string().min(1),
4122
- session_id: z13.string().min(1).max(500),
4123
- hook_event_name: z13.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
4124
- tool_name: z13.string().optional(),
4125
- tool_use_id: z13.string().max(500).optional(),
4126
- tool_input: z13.unknown().optional(),
4127
- stop_hook_active: z13.boolean().optional(),
4128
- permission_mode: z13.string().optional()
4487
+ inputSchema = z14.object({
4488
+ cwd: z14.string().min(1),
4489
+ session_id: z14.string().min(1).max(500),
4490
+ hook_event_name: z14.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
4491
+ tool_name: z14.string().optional(),
4492
+ tool_use_id: z14.string().max(500).optional(),
4493
+ tool_input: z14.unknown().optional(),
4494
+ stop_hook_active: z14.boolean().optional(),
4495
+ permission_mode: z14.string().optional()
4129
4496
  });
4130
4497
  lifecycle = {
4131
4498
  SessionStart: "session_start",
@@ -4146,7 +4513,7 @@ __export(files_exports, {
4146
4513
  readText: () => readText
4147
4514
  });
4148
4515
  import fs19 from "fs/promises";
4149
- import path24 from "path";
4516
+ import path25 from "path";
4150
4517
  import { randomUUID as randomUUID5 } from "crypto";
4151
4518
  async function readText(root, file) {
4152
4519
  try {
@@ -4179,7 +4546,7 @@ async function applyEdit(root, edit) {
4179
4546
  return true;
4180
4547
  }
4181
4548
  const file = await storePath(root, edit.path, true);
4182
- const temporary = path24.join(path24.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
4549
+ const temporary = path25.join(path25.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
4183
4550
  try {
4184
4551
  const mode = await fs19.stat(file).then((s) => s.mode & 511, () => 384);
4185
4552
  const handle = await fs19.open(temporary, "wx", mode);
@@ -4214,7 +4581,7 @@ __export(ownership_exports, {
4214
4581
  ownershipEdit: () => ownershipEdit,
4215
4582
  ownershipSchema: () => ownershipSchema
4216
4583
  });
4217
- import { z as z14 } from "zod";
4584
+ import { z as z15 } from "zod";
4218
4585
  import { parse } from "smol-toml";
4219
4586
  function blockSpan(text2, start, end) {
4220
4587
  const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
@@ -4265,30 +4632,30 @@ var init_ownership = __esm({
4265
4632
  ["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
4266
4633
  ["# mason:mcp:start", "# mason:mcp:end"]
4267
4634
  ];
4268
- fileSchema = z14.object({
4269
- created: z14.boolean(),
4270
- initialHash: z14.string(),
4271
- blocks: z14.record(z14.string()),
4272
- mcpHash: z14.string().optional(),
4273
- hookCommands: z14.array(z14.string()).optional(),
4274
- hooks: z14.record(z14.array(z14.string())).optional()
4635
+ fileSchema = z15.object({
4636
+ created: z15.boolean(),
4637
+ initialHash: z15.string(),
4638
+ blocks: z15.record(z15.string()),
4639
+ mcpHash: z15.string().optional(),
4640
+ hookCommands: z15.array(z15.string()).optional(),
4641
+ hooks: z15.record(z15.array(z15.string())).optional()
4275
4642
  });
4276
- ownershipSchema = z14.object({ version: z14.literal(1), files: z14.record(fileSchema) });
4643
+ ownershipSchema = z15.object({ version: z15.literal(1), files: z15.record(fileSchema) });
4277
4644
  }
4278
4645
  });
4279
4646
 
4280
4647
  // src/mcp/init.ts
4281
- import { z as z15 } from "zod";
4648
+ import { z as z16 } from "zod";
4282
4649
  var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_PLAYBOOK, MAP_PLAYBOOK;
4283
4650
  var init_init = __esm({
4284
4651
  "src/mcp/init.ts"() {
4285
4652
  "use strict";
4286
4653
  init_storage();
4287
4654
  init_files2();
4288
- markerSchema = z15.object({
4289
- version: z15.literal(1),
4290
- initializedAt: z15.string(),
4291
- features: z15.object({ confluence: z15.boolean().optional() }).passthrough().optional()
4655
+ markerSchema = z16.object({
4656
+ version: z16.literal(1),
4657
+ initializedAt: z16.string(),
4658
+ features: z16.object({ confluence: z16.boolean().optional() }).passthrough().optional()
4292
4659
  }).passthrough();
4293
4660
  CLAUDE_MD_SECTION = `<!-- mason:start -->
4294
4661
  ## Mason project knowledge
@@ -4297,16 +4664,19 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
4297
4664
 
4298
4665
  - Task, bug, change request, or investigation of why the project works this way \u2192 \`get_context\` with the task text and known files: matching decisions, related tests, impact, and any available map entries.
4299
4666
  - Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
4300
- - Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` before finishing. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
4667
+ - Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` as soon as the lesson is supported by evidence; do not wait for implementation, task completion, or merge. Record the established observation now and identify unimplemented remedies as tentative. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
4301
4668
  - Before saving, compare the lesson with matching \`get_context\` records and any pending proposal. If new evidence changes the same lesson's assumptions, scope, or recommended action, revise it with \`save_decision(id: existingId)\`, including when it is accepted. Preserve supported rationale and sources, replace obsolete instructions, and add the new evidence. Create a separate record for a genuinely distinct lesson; skip unchanged restatements. Revision preserves history and leaves the accepted version operative until authorized review.
4302
4669
  - Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, stale or unknown map verification requires a fresh evidence review, and a recorded failed verdict means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
4670
+ - Asked to review pending project knowledge \u2192 \`mason_automation(action: "status")\` lists pending proposals and uncommitted records. For ordinary tasks, inspect matching proposals through \`get_context\`; do not request acceptance merely because a proposal exists.
4303
4671
  - Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
4304
4672
  - 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
4673
  - \`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
4674
 
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.
4675
+ - 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
4676
  - 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
4677
 
4678
+ - 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.
4679
+
4310
4680
  - 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
4681
 
4312
4682
  Inspect source for what the retrieved context does not answer.
@@ -4321,7 +4691,7 @@ ${CLAUDE_MD_SECTION}`;
4321
4691
 
4322
4692
  1. Explain the actionable findings with their source evidence. Separate audit issues, advisories, and skipped checks. The review covers committed changes from the merge base to HEAD; workingTree paths are not included in that review. An unavailable or empty check is not proof that the project is correct. Use the CLI for full output if a summary is truncated.
4323
4693
  2. Address findings within the user's requested scope. Setup alone authorizes Mason host configuration, hooks, instructions, and ignore rules; rewriting existing project claims requires repair scope. If repair is authorized, call \`mason_repair(dir, action: "prepare")\` before editing; it saves the full original findings even when this summary is truncated. Inspect relevant source, make grounded edits, then call \`mason_repair(dir, action: "verify", baselinePath)\` with that same baseline, including after any final doc commit. Report resolved, unresolved, review-required, unverified, and new findings. Keep suppressed advisories visible even when setup has already dirtied a doc. Do not invent a decision just to populate the store.
4324
- 3. When the task reveals a real lesson or constraint, compare matching \`get_context\` records first. If its assumptions, scope, or recommended action changed, revise that record using \`save_decision(id: existingId)\`; create a separate record for a genuinely distinct lesson and skip unchanged restatements. Include title, body, category, anchors, and known owner/source/actor information. Preserve supported rationale and sources, replace obsolete instructions, and add new evidence. Missing attribution can be added later. The tool writes a local proposal; editing it preserves revision history and requires a new acceptance, while any earlier accepted revision remains operative. An unchanged save never refreshes its evidence. When decision review is requested, \`review_decision\` prepares the record and code evidence before any authorized verdict. Review and commit records through the normal workflow. Retrieve it on the next relevant task with \`get_context(dir, task, files)\`.
4694
+ 3. Capture a supported lesson as soon as it is established; do not wait for implementation, task completion, or merge. Keep unimplemented remedies tentative. When the task reveals a real lesson or constraint, compare matching \`get_context\` records first. If its assumptions, scope, or recommended action changed, revise that record using \`save_decision(id: existingId)\`; create a separate record for a genuinely distinct lesson and skip unchanged restatements. Include title, body, category, anchors, and known owner/source/actor information. Preserve supported rationale and sources, replace obsolete instructions, and add new evidence. Missing attribution can be added later. The tool writes a local proposal; editing it preserves revision history and requires a new acceptance, while any earlier accepted revision remains operative. An unchanged save never refreshes its evidence. When decision review is requested, \`review_decision\` prepares the record and code evidence before any authorized verdict. Review and commit records through the normal workflow. Retrieve it on the next relevant task with \`get_context(dir, task, files)\`.
4325
4695
 
4326
4696
  ${ASSISTANT_SETUP}
4327
4697
 
@@ -4374,7 +4744,7 @@ __export(config_exports, {
4374
4744
  });
4375
4745
  import { isDeepStrictEqual } from "util";
4376
4746
  import { parse as parse2, stringify } from "smol-toml";
4377
- import { z as z16 } from "zod";
4747
+ import { z as z17 } from "zod";
4378
4748
  async function instructionEdits(root, host) {
4379
4749
  const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
4380
4750
  const existing = found.find((f) => f.text !== null);
@@ -4485,7 +4855,7 @@ var init_config = __esm({
4485
4855
  init_install();
4486
4856
  init_files2();
4487
4857
  init_launcher();
4488
- object = z16.record(z16.unknown());
4858
+ object = z17.record(z17.unknown());
4489
4859
  mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
4490
4860
  TOML_START = "# mason:mcp:start";
4491
4861
  TOML_END = "# mason:mcp:end";
@@ -4505,7 +4875,7 @@ __export(install_exports, {
4505
4875
  planAutomationInstall: () => planAutomationInstall,
4506
4876
  recordSchema: () => recordSchema2
4507
4877
  });
4508
- import { z as z17 } from "zod";
4878
+ import { z as z18 } from "zod";
4509
4879
  async function installationRecord(root) {
4510
4880
  return recordSchema2.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
4511
4881
  }
@@ -4586,9 +4956,9 @@ var init_install = __esm({
4586
4956
  init_store();
4587
4957
  init_files2();
4588
4958
  init_ownership();
4589
- groupSchema = z17.object({ hooks: z17.array(z17.object({ type: z17.string(), command: z17.string().optional() }).passthrough()) }).passthrough();
4590
- automationConfigSchema = z17.object({ hooks: z17.record(z17.array(groupSchema)).optional() }).passthrough();
4591
- recordSchema2 = z17.object({ version: z17.literal(1), hosts: z17.record(z17.object({ command: z17.string() })) });
4959
+ groupSchema = z18.object({ hooks: z18.array(z18.object({ type: z18.string(), command: z18.string().optional() }).passthrough()) }).passthrough();
4960
+ automationConfigSchema = z18.object({ hooks: z18.record(z18.array(groupSchema)).optional() }).passthrough();
4961
+ recordSchema2 = z18.object({ version: z18.literal(1), hosts: z18.record(z18.object({ command: z18.string() })) });
4592
4962
  AUTOMATION_PATH = ".mason/local/automation.json";
4593
4963
  configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
4594
4964
  }
@@ -4602,10 +4972,10 @@ __export(teardown_exports, {
4602
4972
  });
4603
4973
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
4604
4974
  import fs20 from "fs/promises";
4605
- import path25 from "path";
4975
+ import path26 from "path";
4606
4976
  import { parse as parse3, stringify as stringify2 } from "smol-toml";
4607
- import fg6 from "fast-glob";
4608
- import { z as z18 } from "zod";
4977
+ import fg8 from "fast-glob";
4978
+ import { z as z19 } from "zod";
4609
4979
  async function planTeardown(root, selected) {
4610
4980
  const edits = [], diagnostics = [];
4611
4981
  const inputs = /* @__PURE__ */ new Map();
@@ -4625,7 +4995,7 @@ async function planTeardown(root, selected) {
4625
4995
  const setupBefore = await read(SETUP_PATH);
4626
4996
  const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
4627
4997
  const linked = (await workspace(root)).gitDir !== await fs20.realpath(
4628
- path25.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
4998
+ path26.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
4629
4999
  );
4630
5000
  const inactiveBefore = linked && !setup ? await read(INACTIVE_HOSTS_PATH) : null;
4631
5001
  const inactive = inactiveBefore === null ? { version: 1, hosts: [] } : inactiveHostsSchema.parse(JSON.parse(inactiveBefore));
@@ -4748,7 +5118,7 @@ async function planTeardown(root, selected) {
4748
5118
  if (diagnostics.length) edits.splice(instructionEditStart);
4749
5119
  if (!diagnostics.length) {
4750
5120
  const cleanup = [];
4751
- const receiptFiles = await fg6(
5121
+ const receiptFiles = await fg8(
4752
5122
  selected.map((host) => ".mason/reports/automation/*/setup-" + host + ".json"),
4753
5123
  { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false }
4754
5124
  );
@@ -4761,7 +5131,7 @@ async function planTeardown(root, selected) {
4761
5131
  if (!remainingHost) await attempt(".mason/local/project.json", async () => {
4762
5132
  const before = await read(".mason/local/project.json");
4763
5133
  if (before === null) return;
4764
- z18.object({ version: z18.literal(1), initializedAt: z18.string() }).strict().parse(JSON.parse(before));
5134
+ z19.object({ version: z19.literal(1), initializedAt: z19.string() }).strict().parse(JSON.parse(before));
4765
5135
  cleanup.push({ path: ".mason/local/project.json", before, after: null });
4766
5136
  });
4767
5137
  if (!diagnostics.length) {
@@ -4842,7 +5212,7 @@ var init_teardown = __esm({
4842
5212
  init_model();
4843
5213
  init_ownership();
4844
5214
  hosts = ["codex", "claude"];
4845
- object2 = z18.record(z18.unknown());
5215
+ object2 = z19.record(z19.unknown());
4846
5216
  json = (value) => JSON.stringify(value, null, 2) + "\n";
4847
5217
  }
4848
5218
  });
@@ -4969,7 +5339,7 @@ var init_types2 = __esm({
4969
5339
  });
4970
5340
 
4971
5341
  // src/review/evidence/vitest.ts
4972
- import { z as z19 } from "zod";
5342
+ import { z as z20 } from "zod";
4973
5343
  function parseVitest(raw, sourceRoot) {
4974
5344
  const report = schema.parse(raw);
4975
5345
  const findings = [], diagnostics = [];
@@ -5018,24 +5388,24 @@ var init_vitest = __esm({
5018
5388
  "use strict";
5019
5389
  init_paths2();
5020
5390
  init_types2();
5021
- count3 = z19.number().int().nonnegative();
5022
- schema = z19.object({
5023
- success: z19.boolean(),
5391
+ count3 = z20.number().int().nonnegative();
5392
+ schema = z20.object({
5393
+ success: z20.boolean(),
5024
5394
  numTotalTests: count3,
5025
5395
  numPassedTests: count3,
5026
5396
  numFailedTests: count3,
5027
5397
  numPendingTests: count3,
5028
5398
  numTodoTests: count3,
5029
5399
  numFailedTestSuites: count3,
5030
- testResults: z19.array(z19.object({
5031
- name: z19.string().min(1),
5032
- status: z19.enum(["passed", "failed"]),
5033
- message: z19.string().optional(),
5034
- assertionResults: z19.array(z19.object({
5035
- fullName: z19.string(),
5036
- status: z19.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
5037
- failureMessages: z19.array(z19.string()).nullable().optional(),
5038
- location: z19.object({ line: count3, column: count3 }).nullable().optional()
5400
+ testResults: z20.array(z20.object({
5401
+ name: z20.string().min(1),
5402
+ status: z20.enum(["passed", "failed"]),
5403
+ message: z20.string().optional(),
5404
+ assertionResults: z20.array(z20.object({
5405
+ fullName: z20.string(),
5406
+ status: z20.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
5407
+ failureMessages: z20.array(z20.string()).nullable().optional(),
5408
+ location: z20.object({ line: count3, column: count3 }).nullable().optional()
5039
5409
  }))
5040
5410
  }))
5041
5411
  });
@@ -5043,7 +5413,7 @@ var init_vitest = __esm({
5043
5413
  });
5044
5414
 
5045
5415
  // src/review/evidence/sarif.ts
5046
- import { z as z20 } from "zod";
5416
+ import { z as z21 } from "zod";
5047
5417
  function parseSarif(raw, sourceRoot) {
5048
5418
  const report = schema2.parse(raw), findings = [], diagnostics = [];
5049
5419
  let active = 0, suppressed = 0, absent = 0, unresolved = 0;
@@ -5134,29 +5504,29 @@ var init_sarif = __esm({
5134
5504
  "use strict";
5135
5505
  init_paths2();
5136
5506
  init_types2();
5137
- index = z20.number().int().nonnegative();
5138
- artifact = z20.object({ uri: z20.string().optional(), uriBaseId: z20.string().optional(), index: index.optional() });
5139
- message = z20.object({ text: z20.string().optional(), markdown: z20.string().optional(), id: z20.string().optional(), arguments: z20.array(z20.string()).optional() });
5140
- level = z20.enum(["error", "warning", "note", "none"]);
5141
- location = z20.object({ physicalLocation: z20.object({ artifactLocation: artifact.optional(), region: z20.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
5142
- schema2 = z20.object({
5143
- version: z20.literal("2.1.0"),
5144
- runs: z20.array(z20.object({
5145
- tool: z20.object({ driver: z20.object({ name: z20.string().min(1), rules: z20.array(z20.object({ id: z20.string(), defaultConfiguration: z20.object({ level: level.optional() }).optional(), messageStrings: z20.record(message).optional() })).optional(), globalMessageStrings: z20.record(message).optional() }) }),
5146
- invocations: z20.array(z20.object({ executionSuccessful: z20.boolean(), commandLine: z20.string().optional(), toolExecutionNotifications: z20.array(z20.object({ level: level.optional(), message })).optional() })).optional(),
5147
- versionControlProvenance: z20.array(z20.object({ revisionId: z20.string().optional() })).optional(),
5148
- originalUriBaseIds: z20.record(artifact).optional(),
5149
- artifacts: z20.array(z20.object({ location: artifact.optional() })).optional(),
5150
- results: z20.array(z20.object({
5151
- ruleId: z20.string().optional(),
5507
+ index = z21.number().int().nonnegative();
5508
+ artifact = z21.object({ uri: z21.string().optional(), uriBaseId: z21.string().optional(), index: index.optional() });
5509
+ message = z21.object({ text: z21.string().optional(), markdown: z21.string().optional(), id: z21.string().optional(), arguments: z21.array(z21.string()).optional() });
5510
+ level = z21.enum(["error", "warning", "note", "none"]);
5511
+ location = z21.object({ physicalLocation: z21.object({ artifactLocation: artifact.optional(), region: z21.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
5512
+ schema2 = z21.object({
5513
+ version: z21.literal("2.1.0"),
5514
+ runs: z21.array(z21.object({
5515
+ tool: z21.object({ driver: z21.object({ name: z21.string().min(1), rules: z21.array(z21.object({ id: z21.string(), defaultConfiguration: z21.object({ level: level.optional() }).optional(), messageStrings: z21.record(message).optional() })).optional(), globalMessageStrings: z21.record(message).optional() }) }),
5516
+ invocations: z21.array(z21.object({ executionSuccessful: z21.boolean(), commandLine: z21.string().optional(), toolExecutionNotifications: z21.array(z21.object({ level: level.optional(), message })).optional() })).optional(),
5517
+ versionControlProvenance: z21.array(z21.object({ revisionId: z21.string().optional() })).optional(),
5518
+ originalUriBaseIds: z21.record(artifact).optional(),
5519
+ artifacts: z21.array(z21.object({ location: artifact.optional() })).optional(),
5520
+ results: z21.array(z21.object({
5521
+ ruleId: z21.string().optional(),
5152
5522
  ruleIndex: index.optional(),
5153
5523
  message,
5154
5524
  level: level.optional(),
5155
- kind: z20.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
5156
- baselineState: z20.enum(["new", "unchanged", "updated", "absent"]).optional(),
5157
- suppressions: z20.array(z20.object({ kind: z20.enum(["inSource", "external"]), status: z20.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
5158
- locations: z20.array(location).optional(),
5159
- relatedLocations: z20.array(location).optional()
5525
+ kind: z21.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
5526
+ baselineState: z21.enum(["new", "unchanged", "updated", "absent"]).optional(),
5527
+ suppressions: z21.array(z21.object({ kind: z21.enum(["inSource", "external"]), status: z21.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
5528
+ locations: z21.array(location).optional(),
5529
+ relatedLocations: z21.array(location).optional()
5160
5530
  })).optional()
5161
5531
  }))
5162
5532
  });
@@ -5164,7 +5534,7 @@ var init_sarif = __esm({
5164
5534
  });
5165
5535
 
5166
5536
  // src/review/evidence/check.ts
5167
- import { z as z21 } from "zod";
5537
+ import { z as z22 } from "zod";
5168
5538
  function parseCheck(raw, sourceRoot) {
5169
5539
  const report = schema3.parse(raw);
5170
5540
  const diagnostics = [], findings = [];
@@ -5204,17 +5574,17 @@ var init_check = __esm({
5204
5574
  "use strict";
5205
5575
  init_paths2();
5206
5576
  init_types2();
5207
- schema3 = z21.object({
5208
- version: z21.literal(1),
5209
- commit: z21.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
5210
- results: z21.array(z21.object({
5211
- name: z21.string().min(1).max(1e3),
5212
- status: z21.enum(["passed", "failed", "skipped"]),
5213
- message: z21.string().max(1e5).optional(),
5214
- locations: z21.array(z21.object({
5215
- file: z21.string().min(1).max(4e3),
5216
- line: z21.number().int().positive().optional(),
5217
- column: z21.number().int().positive().optional()
5577
+ schema3 = z22.object({
5578
+ version: z22.literal(1),
5579
+ commit: z22.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
5580
+ results: z22.array(z22.object({
5581
+ name: z22.string().min(1).max(1e3),
5582
+ status: z22.enum(["passed", "failed", "skipped"]),
5583
+ message: z22.string().max(1e5).optional(),
5584
+ locations: z22.array(z22.object({
5585
+ file: z22.string().min(1).max(4e3),
5586
+ line: z22.number().int().positive().optional(),
5587
+ column: z22.number().int().positive().optional()
5218
5588
  })).max(100).default([])
5219
5589
  })).max(1e5)
5220
5590
  });
@@ -5222,10 +5592,10 @@ var init_check = __esm({
5222
5592
  });
5223
5593
 
5224
5594
  // src/review/evidence.ts
5225
- import path26 from "path";
5226
- import { z as z22 } from "zod";
5595
+ import path27 from "path";
5596
+ import { z as z23 } from "zod";
5227
5597
  function relativeArtifact(root, file) {
5228
- const relative = path26.isAbsolute(file) ? path26.relative(root, file) : file;
5598
+ const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
5229
5599
  const normalized = normalizeRepoPath(relative);
5230
5600
  if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
5231
5601
  return normalized;
@@ -5264,7 +5634,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5264
5634
  let manifest, raw;
5265
5635
  try {
5266
5636
  manifest = relativeArtifact(root, supplied);
5267
- raw = z22.object({ version: z22.literal(1), checks: z22.array(z22.unknown()) }).parse(await readStoreJson(root, manifest));
5637
+ raw = z23.object({ version: z23.literal(1), checks: z23.array(z23.unknown()) }).parse(await readStoreJson(root, manifest));
5268
5638
  } catch (error) {
5269
5639
  output.diagnostics.push(`${supplied}: ${String(error)}`);
5270
5640
  continue;
@@ -5322,7 +5692,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5322
5692
  const report = await readStoreJson(root, file);
5323
5693
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
5324
5694
  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.");
5695
+ 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
5696
  const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
5327
5697
  result.outcome = parsed.outcome;
5328
5698
  result.counts = parsed.counts;
@@ -5419,32 +5789,32 @@ var init_evidence2 = __esm({
5419
5789
  init_sarif();
5420
5790
  init_check();
5421
5791
  init_types2();
5422
- checkSchema2 = z22.object({
5423
- id: z22.string().min(1).max(100),
5424
- kind: z22.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
5425
- tool: z22.string().min(1).max(200),
5426
- command: z22.string().min(1).max(2e3),
5427
- commit: z22.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
5428
- workingTreeClean: z22.boolean().optional(),
5429
- source: z22.string().max(2e3).optional(),
5430
- sourceRoot: z22.string().min(1).max(4e3).optional(),
5431
- status: z22.enum(["completed", "skipped", "unavailable"]).default("completed"),
5432
- reason: z22.string().min(1).max(2e3).optional(),
5433
- exitCode: z22.number().int().nullable().optional(),
5434
- environment: z22.object({
5435
- toolVersion: z22.string().min(1).max(200),
5436
- runtime: z22.string().min(1).max(200).optional(),
5437
- platform: z22.string().min(1).max(200).optional(),
5438
- configuration: z22.array(z22.string().min(1).max(4e3)).max(100).optional()
5792
+ checkSchema2 = z23.object({
5793
+ id: z23.string().min(1).max(100),
5794
+ kind: z23.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
5795
+ tool: z23.string().min(1).max(200),
5796
+ command: z23.string().min(1).max(2e3),
5797
+ commit: z23.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
5798
+ workingTreeClean: z23.boolean().optional(),
5799
+ source: z23.string().max(2e3).optional(),
5800
+ sourceRoot: z23.string().min(1).max(4e3).optional(),
5801
+ status: z23.enum(["completed", "skipped", "unavailable"]).default("completed"),
5802
+ reason: z23.string().min(1).max(2e3).optional(),
5803
+ exitCode: z23.number().int().nullable().optional(),
5804
+ environment: z23.object({
5805
+ toolVersion: z23.string().min(1).max(200),
5806
+ runtime: z23.string().min(1).max(200).optional(),
5807
+ platform: z23.string().min(1).max(200).optional(),
5808
+ configuration: z23.array(z23.string().min(1).max(4e3)).max(100).optional()
5439
5809
  }).optional(),
5440
- report: z22.object({ format: z22.enum(["vitest-json", "sarif", "mason-check-json"]), path: z22.string().min(1).max(4e3) }).optional()
5810
+ report: z23.object({ format: z23.enum(["vitest-json", "sarif", "mason-check-json"]), path: z23.string().min(1).max(4e3) }).optional()
5441
5811
  });
5442
5812
  }
5443
5813
  });
5444
5814
 
5445
5815
  // src/review/review.ts
5446
5816
  import fs21 from "fs/promises";
5447
- import path27 from "path";
5817
+ import path28 from "path";
5448
5818
  import { execFile as execFile4 } from "child_process";
5449
5819
  import { promisify as promisify4 } from "util";
5450
5820
  async function resolveMergeBase(resolvedRoot, base, head) {
@@ -5473,7 +5843,7 @@ function anchorsTouched(record, changedFiles) {
5473
5843
  return matchingPaths(record.files, changedFiles);
5474
5844
  }
5475
5845
  async function computeReview(rootDir, base, options = {}) {
5476
- const resolvedRoot = path27.resolve(rootDir);
5846
+ const resolvedRoot = path28.resolve(rootDir);
5477
5847
  const head = await getCurrentGitHash(resolvedRoot);
5478
5848
  if (head === "unknown") return null;
5479
5849
  const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
@@ -5523,7 +5893,7 @@ async function computeReview(rootDir, base, options = {}) {
5523
5893
  analyzed,
5524
5894
  async (relPath) => {
5525
5895
  try {
5526
- await fs21.access(path27.join(resolvedRoot, relPath));
5896
+ await fs21.access(path28.join(resolvedRoot, relPath));
5527
5897
  return true;
5528
5898
  } catch {
5529
5899
  return false;
@@ -6071,7 +6441,7 @@ async function runAutomationCli(argv2, stdin = "", io = defaultIo, options = {})
6071
6441
  const { setupStatus: setupStatus2, summarizeActivation: summarizeActivation2 } = await Promise.resolve().then(() => (init_status(), status_exports));
6072
6442
  const setup = await setupStatus2(dir);
6073
6443
  const result = { ...await automationStatus(dir), configured: await installedAutomation(dir), setup };
6074
- io.out(values.json || !process.stdout.isTTY ? JSON.stringify(result, null, 2) : summarizeActivation2(setup));
6444
+ io.out(values.json || !process.stdout.isTTY ? JSON.stringify(result, null, 2) : [summarizeActivation2(setup), result.knowledge].filter(Boolean).join("\n\n"));
6075
6445
  return 0;
6076
6446
  }
6077
6447
  if (action !== "check") throw new Error("Unknown automation command: " + action);