mason-context 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mason.js CHANGED
@@ -38,7 +38,7 @@ function indexMatchingPaths(scopes2, files) {
38
38
  const node = () => ({ children: /* @__PURE__ */ new Map(), scopes: /* @__PURE__ */ new Set() });
39
39
  const root = node();
40
40
  const matches = scopes2.map(() => []);
41
- scopes2.forEach((anchors, index2) => {
41
+ scopes2.forEach((anchors, index) => {
42
42
  for (const anchor of anchors) {
43
43
  const normalized = normalizeRepoPath(anchor);
44
44
  if (!normalized) continue;
@@ -47,7 +47,7 @@ function indexMatchingPaths(scopes2, files) {
47
47
  if (!current.children.has(part)) current.children.set(part, node());
48
48
  current = current.children.get(part);
49
49
  }
50
- current.scopes.add(index2);
50
+ current.scopes.add(index);
51
51
  }
52
52
  });
53
53
  for (const file of new Set(files)) {
@@ -58,9 +58,9 @@ function indexMatchingPaths(scopes2, files) {
58
58
  for (const part of normalized.split("/")) {
59
59
  current = current.children.get(part);
60
60
  if (!current) break;
61
- for (const index2 of current.scopes) selected.add(index2);
61
+ for (const index of current.scopes) selected.add(index);
62
62
  }
63
- for (const index2 of selected) matches[index2].push(file);
63
+ for (const index of selected) matches[index].push(file);
64
64
  }
65
65
  return matches;
66
66
  }
@@ -581,7 +581,7 @@ async function captureAnchorScopes(root, scopes2) {
581
581
  const eligible = new Set(await access.list(patterns, { dot: true }));
582
582
  const indexed = indexMatchingPaths(scopes2, files);
583
583
  const filesByScope = new Map(
584
- scopes2.map((scope, index2) => [JSON.stringify([...new Set(scope)].sort()), indexed[index2]])
584
+ scopes2.map((scope, index) => [JSON.stringify([...new Set(scope)].sort()), indexed[index]])
585
585
  );
586
586
  return { filesByScope, eligible };
587
587
  };
@@ -678,31 +678,31 @@ import { z as z2 } from "zod";
678
678
  import fs4 from "fs/promises";
679
679
  import os from "os";
680
680
  async function lockDiagnostic(file) {
681
- let reason2;
681
+ let reason;
682
682
  try {
683
683
  const owner = lockOwnerSchema.safeParse(JSON.parse(await fs4.readFile(file, "utf8")));
684
684
  if (!owner.success) {
685
- reason2 = "Lock owner data is incomplete or malformed.";
685
+ reason = "Lock owner data is incomplete or malformed.";
686
686
  } else if (owner.data.host !== os.hostname()) {
687
- reason2 = "The lock belongs to another host; its process cannot be checked locally.";
687
+ reason = "The lock belongs to another host; its process cannot be checked locally.";
688
688
  } else {
689
689
  try {
690
690
  process.kill(owner.data.pid, 0);
691
- reason2 = `Local owner PID ${owner.data.pid} is still running.`;
691
+ reason = `Local owner PID ${owner.data.pid} is still running.`;
692
692
  } catch (error) {
693
- reason2 = error.code === "ESRCH" ? `Local owner PID ${owner.data.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.data.pid} could not be checked.`;
693
+ reason = error.code === "ESRCH" ? `Local owner PID ${owner.data.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.data.pid} could not be checked.`;
694
694
  }
695
695
  }
696
696
  } catch (error) {
697
- reason2 = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
697
+ reason = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
698
698
  }
699
699
  if (await fs4.lstat(file + ".reclaim").then(
700
700
  () => true,
701
701
  () => false
702
702
  )) {
703
- reason2 += " A recovery guard also needs inspection: " + file + ".reclaim.";
703
+ reason += " A recovery guard also needs inspection: " + file + ".reclaim.";
704
704
  }
705
- return reason2 + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
705
+ return reason + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
706
706
  }
707
707
  async function withStoreLock(root, directory3, run, waitMs = 5e3, label = "Mason store") {
708
708
  const file = await storePath(root, directory3 + "/lock", true);
@@ -785,103 +785,8 @@ var init_lock = __esm({
785
785
  }
786
786
  });
787
787
 
788
- // src/test-map.ts
789
- import path5 from "path";
790
- async function buildTestMap(dir) {
791
- const rootDir = path5.resolve(dir);
792
- const access = await createFileAccess(rootDir);
793
- const testPatterns = [
794
- "**/*.test.*",
795
- "**/*.spec.*",
796
- "**/*Test.kt",
797
- "**/*Test.java",
798
- "**/*Tests.kt",
799
- "**/*Tests.java",
800
- "**/test_*.py",
801
- "**/*_test.py",
802
- "**/*_test.go",
803
- "**/*Tests.swift",
804
- "**/*Test.swift",
805
- "**/*_test.rs"
806
- ];
807
- const testFiles = await access.list(testPatterns);
808
- const sourceFiles = await access.list();
809
- const sourceByBaseName = /* @__PURE__ */ new Map();
810
- for (const file of sourceFiles) {
811
- if (testFiles.includes(file)) continue;
812
- const baseName = path5.basename(file).replace(/\.[^.]+$/, "");
813
- const existing = sourceByBaseName.get(baseName) ?? [];
814
- existing.push(file);
815
- sourceByBaseName.set(baseName, existing);
816
- }
817
- const paired = [];
818
- const unmatched = [];
819
- for (const testFile of testFiles) {
820
- const testBaseName = path5.basename(testFile).replace(/\.[^.]+$/, "");
821
- const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
822
- if (!sourceName) {
823
- unmatched.push(testFile);
824
- continue;
825
- }
826
- const candidates = sourceByBaseName.get(sourceName);
827
- if (candidates && candidates.length > 0) {
828
- const testDir = path5.dirname(testFile);
829
- const bestMatch = candidates.reduce((best, candidate) => {
830
- const candidateDir = path5.dirname(candidate);
831
- const bestDir = path5.dirname(best);
832
- const candidateOverlap = commonSegments(testDir, candidateDir);
833
- const bestOverlap = commonSegments(testDir, bestDir);
834
- return candidateOverlap > bestOverlap ? candidate : best;
835
- });
836
- paired.push({
837
- test: testFile,
838
- source: bestMatch,
839
- confidence: candidates.length === 1 ? "exact" : "best-guess"
840
- });
841
- } else {
842
- unmatched.push(testFile);
843
- }
844
- }
845
- return { totalTestFiles: testFiles.length, paired, unmatched };
846
- }
847
- function commonSegments(pathA, pathB) {
848
- const segsA = pathA.split("/");
849
- const segsB = pathB.split("/");
850
- let count4 = 0;
851
- for (let i = 0; i < Math.min(segsA.length, segsB.length); i++) {
852
- if (segsA[i] === segsB[i]) count4++;
853
- else break;
854
- }
855
- return count4;
856
- }
857
- var init_test_map = __esm({
858
- "src/test-map.ts"() {
859
- "use strict";
860
- init_files();
861
- }
862
- });
863
-
864
788
  // src/snapshot/snapshot.ts
865
- import path6 from "path";
866
789
  import { z as z3 } from "zod";
867
- async function inspectSnapshot(rootDir) {
868
- try {
869
- const raw = await readStoreJson(rootDir, ".mason/snapshot.json");
870
- const snapshot = raw === null ? null : snapshotSchema.parse(raw);
871
- return { status: snapshot ? "available" : "missing", snapshot, diagnostics: [] };
872
- } catch (error) {
873
- return {
874
- status: "invalid",
875
- snapshot: null,
876
- diagnostics: [
877
- {
878
- path: ".mason/snapshot.json",
879
- message: error instanceof Error ? error.message : String(error)
880
- }
881
- ]
882
- };
883
- }
884
- }
885
790
  async function getCurrentGitHash(rootDir) {
886
791
  try {
887
792
  const { stdout } = await execGit(["rev-parse", "HEAD"], {
@@ -898,11 +803,9 @@ var init_snapshot = __esm({
898
803
  "use strict";
899
804
  init_git_read();
900
805
  init_files();
901
- init_files();
902
806
  init_storage();
903
807
  init_lock();
904
808
  init_paths();
905
- init_test_map();
906
809
  repoPath2 = z3.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
907
810
  verificationFields = {
908
811
  refreshedHash: z3.string().optional(),
@@ -932,7 +835,7 @@ var init_snapshot = __esm({
932
835
  });
933
836
 
934
837
  // src/context/lexical.ts
935
- import path7 from "path";
838
+ import path5 from "path";
936
839
  var init_lexical = __esm({
937
840
  "src/context/lexical.ts"() {
938
841
  "use strict";
@@ -989,10 +892,10 @@ function decisionApproval(record) {
989
892
  function effectiveDecision(record) {
990
893
  if (record.version !== 2 || record.status !== "active" || record.approval !== "proposed")
991
894
  return record;
992
- let index2 = record.history.length - 1;
993
- while (index2 >= 0 && !["accepted", "reaffirmed"].includes(record.history[index2].kind)) index2--;
994
- if (index2 < 0) return record;
995
- const event = record.history[index2];
895
+ let index = record.history.length - 1;
896
+ while (index >= 0 && !["accepted", "reaffirmed"].includes(record.history[index].kind)) index--;
897
+ if (index < 0) return record;
898
+ const event = record.history[index];
996
899
  return {
997
900
  ...record,
998
901
  ...event.content,
@@ -1002,7 +905,7 @@ function effectiveDecision(record) {
1002
905
  refreshedHash: event.refreshedHash,
1003
906
  capture: event.capture,
1004
907
  updatedAt: event.at,
1005
- history: record.history.slice(0, index2 + 1)
908
+ history: record.history.slice(0, index + 1)
1006
909
  };
1007
910
  }
1008
911
  function decisionAnchors(record) {
@@ -1035,8 +938,19 @@ function decisionTrust(record, freshness) {
1035
938
  freshness
1036
939
  );
1037
940
  }
1038
- function decisionInspectionDigest(record) {
941
+ function decisionInspectionDigest(record, scope = "all") {
1039
942
  const effective = effectiveDecision(record);
943
+ if (scope === "operative")
944
+ return createHash2("sha256").update(
945
+ JSON.stringify([
946
+ "operative",
947
+ record.status,
948
+ decisionApproval(effective),
949
+ effective.version === 2 ? effective.revision : 0,
950
+ decisionContent(effective),
951
+ effective.refreshedHash
952
+ ])
953
+ ).digest("hex");
1040
954
  return createHash2("sha256").update(
1041
955
  JSON.stringify([
1042
956
  record.status,
@@ -1048,8 +962,10 @@ function decisionInspectionDigest(record) {
1048
962
  ])
1049
963
  ).digest("hex");
1050
964
  }
1051
- function latestDecisionInspection(record) {
1052
- return record.version === 2 ? record.inspections?.filter((i) => i.decisionDigest === decisionInspectionDigest(record)).at(-1) : void 0;
965
+ function latestDecisionInspection(record, scope = "all") {
966
+ return record.version === 2 ? record.inspections?.filter(
967
+ (i) => (scope === "operative" || i.scope !== "operative") && i.decisionDigest === decisionInspectionDigest(record, i.scope ?? "all")
968
+ ).at(-1) : void 0;
1053
969
  }
1054
970
  function revisionKnowledge(record, freshness) {
1055
971
  return {
@@ -1060,7 +976,10 @@ function revisionKnowledge(record, freshness) {
1060
976
  }
1061
977
  function decisionKnowledge(record, freshness = "unknown", proposalFreshness = "unknown") {
1062
978
  const effective = effectiveDecision(record);
1063
- const inspection = latestDecisionInspection(record);
979
+ const inspection = latestDecisionInspection(
980
+ record,
981
+ decisionApproval(effective) === "accepted" ? "operative" : "all"
982
+ );
1064
983
  return {
1065
984
  ...revisionKnowledge(effective, freshness),
1066
985
  ...inspection ? {
@@ -1070,13 +989,19 @@ function decisionKnowledge(record, freshness = "unknown", proposalFreshness = "u
1070
989
  inspector: inspection.inspector,
1071
990
  note: inspection.note,
1072
991
  outcome: inspection.outcome,
992
+ scope: inspection.scope ?? "all",
1073
993
  headHash: inspection.headHash
1074
994
  }
1075
995
  } : {},
1076
- ...effective !== record ? { pendingProposal: revisionKnowledge(record, proposalFreshness) } : {}
996
+ ...effective !== record ? {
997
+ pendingProposal: {
998
+ ...revisionKnowledge(record, proposalFreshness),
999
+ assessment: latestDecisionInspection(record) ? "no_contradiction_found" : "not_assessed"
1000
+ }
1001
+ } : {}
1077
1002
  };
1078
1003
  }
1079
- var text, decisionSourceSchema, attributionSchema, contentSchema, approvalSchema, statusSchema, reviewEvidenceSchema, eventSchema, legacySchema, currentSchema, decisionSchema;
1004
+ var text, decisionSourceSchema, attributionSchema, contentSchema, approvalSchema, statusSchema, reviewEvidenceSchema, eventSchema, legacySchema, currentSchema, decisionSchema, DECISION_GUIDANCE;
1080
1005
  var init_provenance = __esm({
1081
1006
  "src/decisions/provenance.ts"() {
1082
1007
  "use strict";
@@ -1160,13 +1085,14 @@ var init_provenance = __esm({
1160
1085
  inspector: text(200),
1161
1086
  note: text(1500),
1162
1087
  outcome: z4.literal("no_contradiction_found"),
1088
+ scope: z4.literal("operative").optional(),
1163
1089
  decisionDigest: z4.string().regex(/^[a-f0-9]{64}$/),
1164
1090
  headHash: z4.string(),
1165
1091
  capture: anchorCaptureSchema
1166
1092
  })
1167
1093
  ).max(200).optional()
1168
1094
  }).superRefine((record, ctx) => {
1169
- const invalid = (message2) => ctx.addIssue({ code: "custom", message: message2 });
1095
+ const invalid = (message) => ctx.addIssue({ code: "custom", message });
1170
1096
  const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
1171
1097
  let previous;
1172
1098
  for (const event of record.history) {
@@ -1211,12 +1137,13 @@ var init_provenance = __esm({
1211
1137
  invalid("Decision does not match the final history event");
1212
1138
  });
1213
1139
  decisionSchema = z4.union([legacySchema, currentSchema]);
1140
+ DECISION_GUIDANCE = "Accepted decisions are recorded team constraints, subject to freshness checks. A pendingProposal is an unaccepted replacement; the accepted revision remains operative until explicit acceptance or retirement. Proposals are suggestions; legacy unreviewed records need confirmation. Use review_decision to prepare evidence and record a separate source inspection without human approval, or an authorized human review; identities and sources are recorded assertions, not authenticated proof.";
1214
1141
  }
1215
1142
  });
1216
1143
 
1217
1144
  // src/decisions/decisions.ts
1218
1145
  import fs5 from "fs/promises";
1219
- import path8 from "path";
1146
+ import path6 from "path";
1220
1147
  import { createHash as createHash3 } from "crypto";
1221
1148
  import { z as z5 } from "zod";
1222
1149
  async function readDecisionInputs(rootDir) {
@@ -1311,7 +1238,7 @@ var init_decisions = __esm({
1311
1238
  // src/audit/inputs.ts
1312
1239
  import fs6 from "fs/promises";
1313
1240
  import * as nativeFs from "fs";
1314
- import path9 from "path";
1241
+ import path7 from "path";
1315
1242
  import fg4 from "fast-glob";
1316
1243
  async function auditInputPath(root, file) {
1317
1244
  if (file === ".") return fs6.realpath(root);
@@ -1341,7 +1268,7 @@ async function auditGlob(root, patterns, options) {
1341
1268
  if (Array.isArray(patterns) && !patterns.length) return [];
1342
1269
  const directories = /* @__PURE__ */ new Set();
1343
1270
  const readdir2 = ((directory3, opts, callback) => {
1344
- const relative = path9.relative(root, directory3).split(path9.sep).join("/") || ".";
1271
+ const relative = path7.relative(root, directory3).split(path7.sep).join("/") || ".";
1345
1272
  directories.add(relative);
1346
1273
  if (directories.size > MAX_INPUTS) {
1347
1274
  callback(
@@ -1360,7 +1287,7 @@ async function auditGlob(root, patterns, options) {
1360
1287
  }, callback);
1361
1288
  });
1362
1289
  const lstat2 = ((file, callback) => {
1363
- const relative = path9.relative(root, file).split(path9.sep).join("/") || ".";
1290
+ const relative = path7.relative(root, file).split(path7.sep).join("/") || ".";
1364
1291
  auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
1365
1292
  });
1366
1293
  const stream = fg4.stream(patterns, {
@@ -1391,7 +1318,7 @@ async function auditGlob(root, patterns, options) {
1391
1318
  }, /* @__PURE__ */ new Map())
1392
1319
  ].sort((a, b) => b[1] - a[1]).slice(0, 3);
1393
1320
  throw new Error(
1394
- `${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count4]) => `${dir} (${count4})`).join(", ")}. Narrow this check's inputs.`
1321
+ `${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count3]) => `${dir} (${count3})`).join(", ")}. Narrow this check's inputs.`
1395
1322
  );
1396
1323
  }
1397
1324
  }
@@ -1460,7 +1387,7 @@ var init_policy = __esm({
1460
1387
  });
1461
1388
 
1462
1389
  // src/audit/commands.ts
1463
- import path10 from "path";
1390
+ import path8 from "path";
1464
1391
  function splitCommands(line) {
1465
1392
  const parts = [];
1466
1393
  let quote2 = "", start = 0;
@@ -1496,7 +1423,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
1496
1423
  const cd = text2.match(/^cd\s+(.+)$/);
1497
1424
  if (cd) {
1498
1425
  const directory3 = literal(cd[1]);
1499
- cwd = directory3 === null || cwd === null || /\s/.test(directory3) && !/^["']/.test(cd[1]) ? null : path10.posix.join(cwd ?? ".", directory3);
1426
+ cwd = directory3 === null || cwd === null || /\s/.test(directory3) && !/^["']/.test(cd[1]) ? null : path8.posix.join(cwd ?? ".", directory3);
1500
1427
  continue;
1501
1428
  }
1502
1429
  for (const match of text2.matchAll(COMMAND)) {
@@ -1508,7 +1435,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
1508
1435
  scopeUnknown = "Unsupported shell wrapper or options before the script.";
1509
1436
  if (selector) {
1510
1437
  const selected = literal(selector[1]);
1511
- directory3 = selected === null || cwd === null ? null : path10.posix.join(cwd ?? ".", selected);
1438
+ directory3 = selected === null || cwd === null ? null : path8.posix.join(cwd ?? ".", selected);
1512
1439
  } else if (flags) scopeUnknown = "Unsupported package-manager options before run: " + flags;
1513
1440
  const tail = text2.slice(match.index + match[0].length).split(/\s--(?:\s|$)/)[0];
1514
1441
  if (/(?:^|\s)(?:--workspace(?:s)?|-w|--filter|-F|--prefix|--dir|-C|--if-present)(?:\s|=|$)/.test(
@@ -1543,7 +1470,7 @@ var init_commands = __esm({
1543
1470
  );
1544
1471
  literal = (value) => {
1545
1472
  const unquoted = value.replace(/^(["'])(.*)\1$/, "$2");
1546
- return /[$`*?[\]{}~\\\x00-\x1f]/.test(unquoted) || path10.posix.isAbsolute(unquoted) ? null : unquoted;
1473
+ return /[$`*?[\]{}~\\\x00-\x1f]/.test(unquoted) || path8.posix.isAbsolute(unquoted) ? null : unquoted;
1547
1474
  };
1548
1475
  }
1549
1476
  });
@@ -1926,7 +1853,7 @@ var init_git = __esm({
1926
1853
 
1927
1854
  // src/audit/docs.ts
1928
1855
  import fs7 from "fs/promises";
1929
- import path11 from "path";
1856
+ import path9 from "path";
1930
1857
  import fg6 from "fast-glob";
1931
1858
  async function dirtyDocs(resolvedRoot, files) {
1932
1859
  if (!files.length) return /* @__PURE__ */ new Set();
@@ -2003,10 +1930,10 @@ async function discoverDocPaths(root) {
2003
1930
  );
2004
1931
  }
2005
1932
  const priority2 = (file) => {
2006
- const index2 = DOC_CANDIDATES.findIndex(
1933
+ const index = DOC_CANDIDATES.findIndex(
2007
1934
  (p) => p.toLowerCase() === file.toLowerCase()
2008
1935
  );
2009
- return index2 < 0 ? DOC_CANDIDATES.length : index2;
1936
+ return index < 0 ? DOC_CANDIDATES.length : index;
2010
1937
  };
2011
1938
  return paths.sort((a, b) => priority2(a) - priority2(b) || a.localeCompare(b));
2012
1939
  }
@@ -2022,15 +1949,15 @@ async function localDocPaths(root) {
2022
1949
  }
2023
1950
  for (const entry of entries)
2024
1951
  if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
2025
- paths.push(path11.posix.join(dir, entry.name));
1952
+ paths.push(path9.posix.join(dir, entry.name));
2026
1953
  }
2027
1954
  }
2028
1955
  return paths;
2029
1956
  }
2030
1957
  function documentScope(file) {
2031
- if (!/^(readme|agents|claude)\.md$/i.test(path11.posix.basename(file))) return ".";
2032
- const parent = path11.posix.dirname(file);
2033
- return path11.posix.basename(parent).toLowerCase() === ".claude" ? path11.posix.dirname(parent) : parent;
1958
+ if (!/^(readme|agents|claude)\.md$/i.test(path9.posix.basename(file))) return ".";
1959
+ const parent = path9.posix.dirname(file);
1960
+ return path9.posix.basename(parent).toLowerCase() === ".claude" ? path9.posix.dirname(parent) : parent;
2034
1961
  }
2035
1962
  async function discoverDocs(resolvedRoot) {
2036
1963
  return profilePhase("audit.documents", () => readDocs(resolvedRoot));
@@ -2053,7 +1980,7 @@ async function readDocs(resolvedRoot) {
2053
1980
  path: candidate,
2054
1981
  content: content2,
2055
1982
  scope: documentScope(candidate),
2056
- kind: /^readme\.md$/i.test(path11.posix.basename(candidate)) ? "readme" : /^(agents|claude)\.md$/i.test(path11.posix.basename(candidate)) ? "instructions" : "guide",
1983
+ kind: /^readme\.md$/i.test(path9.posix.basename(candidate)) ? "readme" : /^(agents|claude)\.md$/i.test(path9.posix.basename(candidate)) ? "instructions" : "guide",
2057
1984
  lineCount: content2.split("\n").length,
2058
1985
  lastCommit,
2059
1986
  dirty: dirty.has(candidate),
@@ -2082,15 +2009,15 @@ var init_docs = __esm({
2082
2009
 
2083
2010
  // src/audit/scope.ts
2084
2011
  import fs8 from "fs/promises";
2085
- import path12 from "path";
2012
+ import path10 from "path";
2086
2013
  function scopedPath(directory3, value) {
2087
- if (path12.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value))
2014
+ if (path10.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value))
2088
2015
  return null;
2089
- const resolved = path12.posix.normalize(path12.posix.join(directory3, value));
2016
+ const resolved = path10.posix.normalize(path10.posix.join(directory3, value));
2090
2017
  return resolved === ".." || resolved.startsWith("../") ? null : resolved;
2091
2018
  }
2092
2019
  function pathClaimScope(doc, claim) {
2093
- const directory3 = claim.relativeTo === "document" ? path12.posix.dirname(doc) : documentScope(doc);
2020
+ const directory3 = claim.relativeTo === "document" ? path10.posix.dirname(doc) : documentScope(doc);
2094
2021
  const local = scopedPath(directory3, claim.path);
2095
2022
  if (!local) return null;
2096
2023
  const candidates = claim.relativeTo === "document" || directory3 === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
@@ -2125,7 +2052,7 @@ var init_scope = __esm({
2125
2052
 
2126
2053
  // src/drift/drift.ts
2127
2054
  import fs9 from "fs/promises";
2128
- import path13 from "path";
2055
+ import path11 from "path";
2129
2056
  function parseChanges(output) {
2130
2057
  const fields = output.split("\0");
2131
2058
  const changes = [];
@@ -2193,7 +2120,7 @@ var init_drift = __esm({
2193
2120
  });
2194
2121
 
2195
2122
  // src/audit/checks/deleted-reference.ts
2196
- import path14 from "path";
2123
+ import path12 from "path";
2197
2124
  async function checkDeletedReferences(ctx) {
2198
2125
  const result = emptyResult();
2199
2126
  for (const doc of ctx.docs) {
@@ -2228,7 +2155,7 @@ async function checkDeletedReferences(ctx) {
2228
2155
  scope.candidates.map(async (file) => ({
2229
2156
  file,
2230
2157
  tracked: await lastCommitOf(ctx.root, file),
2231
- parent: await pathExists(ctx.root, path14.posix.dirname(file))
2158
+ parent: await pathExists(ctx.root, path12.posix.dirname(file))
2232
2159
  }))
2233
2160
  );
2234
2161
  const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
@@ -2247,11 +2174,11 @@ async function checkDeletedReferences(ctx) {
2247
2174
  parentDirExists: candidate?.parent ?? false
2248
2175
  };
2249
2176
  const anchor = { doc: doc.path, line: claim.line, excerpt: claim.excerpt };
2250
- const message2 = renamedTo ? `\`${resolvedPath}\` was renamed to \`${renamedTo}\`` : `\`${resolvedPath}\` does not exist` + (deletedInCommit ? ` \u2013 deleted in ${deletedInCommit.hash.slice(0, 7)} "${deletedInCommit.subject}" (${deletedInCommit.date.slice(0, 10)})` : "");
2177
+ const message = renamedTo ? `\`${resolvedPath}\` was renamed to \`${renamedTo}\`` : `\`${resolvedPath}\` does not exist` + (deletedInCommit ? ` \u2013 deleted in ${deletedInCommit.hash.slice(0, 7)} "${deletedInCommit.subject}" (${deletedInCommit.date.slice(0, 10)})` : "");
2251
2178
  if (scope.basis !== "document" && (renamedTo || candidate?.tracked)) {
2252
2179
  result.issues.push({
2253
2180
  type: "deleted-reference",
2254
- message: message2,
2181
+ message,
2255
2182
  anchor,
2256
2183
  confidence: "certain",
2257
2184
  evidence
@@ -2260,7 +2187,7 @@ async function checkDeletedReferences(ctx) {
2260
2187
  result.advisories.push({
2261
2188
  resolution: "recheck",
2262
2189
  type: "deleted-reference",
2263
- message: message2 + (scope.basis === "document" ? "; the bare path's document/repository scope needs review." : "; no tracked history establishes it as a required file. Review for a typo, generated output, or example."),
2190
+ message: message + (scope.basis === "document" ? "; the bare path's document/repository scope needs review." : "; no tracked history establishes it as a required file. Review for a typo, generated output, or example."),
2264
2191
  anchor,
2265
2192
  evidence
2266
2193
  });
@@ -2281,7 +2208,7 @@ var init_deleted_reference = __esm({
2281
2208
  // src/audit/checks/new-module.ts
2282
2209
  import fg7 from "fast-glob";
2283
2210
  import fs10 from "fs/promises";
2284
- import path15 from "path";
2211
+ import path13 from "path";
2285
2212
  function escapeRegExp(text2) {
2286
2213
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2287
2214
  }
@@ -2317,7 +2244,7 @@ async function checkNewModules(ctx) {
2317
2244
  }
2318
2245
  function moduleDocumentation(docs) {
2319
2246
  return docs.map(
2320
- (doc) => doc.content + "\n" + (path15.posix.dirname(doc.path) === "." ? "" : path15.posix.dirname(doc.path) + "/")
2247
+ (doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")
2321
2248
  ).join("\n");
2322
2249
  }
2323
2250
  function moduleCandidates(root, combinedDocs) {
@@ -2343,9 +2270,9 @@ async function collectModuleCandidates(root, combinedDocs) {
2343
2270
  });
2344
2271
  const dirs = entries.filter(
2345
2272
  (entry) => (entry.isDirectory() || entry.isSymbolicLink() && sources.some(
2346
- (file) => file.startsWith(path15.posix.join(dir, entry.name) + "/")
2273
+ (file) => file.startsWith(path13.posix.join(dir, entry.name) + "/")
2347
2274
  )) && !entry.name.startsWith(".") && !DIR_DENYLIST.has(entry.name)
2348
- ).map((entry) => path15.posix.join(dir, entry.name));
2275
+ ).map((entry) => path13.posix.join(dir, entry.name));
2349
2276
  byParent.set(dir, dirs);
2350
2277
  })
2351
2278
  );
@@ -2365,16 +2292,16 @@ async function collectModuleCandidates(root, combinedDocs) {
2365
2292
  onlyDirectories: true,
2366
2293
  label: "Module directory discovery",
2367
2294
  select: (file) => visible.has(file)
2368
- })).map((file) => path15.posix.basename(file))
2295
+ })).map((file) => path13.posix.basename(file))
2369
2296
  );
2370
2297
  }
2371
2298
  return result;
2372
2299
  };
2373
2300
  const countSourceFiles = async (dir) => {
2374
2301
  const files = sources.filter((file) => file.startsWith(dir + "/"));
2375
- let count4 = 0;
2302
+ let count3 = 0;
2376
2303
  for (let offset = 0; offset < files.length; offset += 256) {
2377
- count4 += (await auditGlob(
2304
+ count3 += (await auditGlob(
2378
2305
  root,
2379
2306
  files.slice(offset, offset + 256).map((file) => fg7.escapePath(file)),
2380
2307
  {
@@ -2383,12 +2310,12 @@ async function collectModuleCandidates(root, combinedDocs) {
2383
2310
  label: `Source discovery in ${dir}`
2384
2311
  }
2385
2312
  )).length;
2386
- if (count4 > 1e5)
2313
+ if (count3 > 1e5)
2387
2314
  throw new Error(
2388
2315
  `Source discovery in ${dir} exceeds 100,000 relevant files. Exclude generated source with .mason/config.json ignore patterns or narrow this audit's checks.`
2389
2316
  );
2390
2317
  }
2391
- return count4;
2318
+ return count3;
2392
2319
  };
2393
2320
  const candidates = [];
2394
2321
  const topDirs = (await listSubdirs(["."])).get(".");
@@ -2396,8 +2323,8 @@ async function collectModuleCandidates(root, combinedDocs) {
2396
2323
  for (const topDir of topDirs) {
2397
2324
  const topMentioned = isMentioned(combinedDocs, topDir);
2398
2325
  if (!topMentioned) {
2399
- const count4 = await countSourceFiles(topDir);
2400
- if (count4 >= 1) candidates.push({ dir: topDir, sourceFileCount: count4 });
2326
+ const count3 = await countSourceFiles(topDir);
2327
+ if (count3 >= 1) candidates.push({ dir: topDir, sourceFileCount: count3 });
2401
2328
  continue;
2402
2329
  }
2403
2330
  const subdirs = nested.get(topDir);
@@ -2405,9 +2332,9 @@ async function collectModuleCandidates(root, combinedDocs) {
2405
2332
  if (mentioned.length < ENUMERATION_THRESHOLD) continue;
2406
2333
  for (const sub of subdirs) {
2407
2334
  if (isMentioned(combinedDocs, sub)) continue;
2408
- const count4 = await countSourceFiles(`${topDir}/${sub}`);
2409
- if (count4 >= SECOND_LEVEL_MIN_SOURCE_FILES) {
2410
- candidates.push({ dir: `${topDir}/${sub}`, sourceFileCount: count4 });
2335
+ const count3 = await countSourceFiles(`${topDir}/${sub}`);
2336
+ if (count3 >= SECOND_LEVEL_MIN_SOURCE_FILES) {
2337
+ candidates.push({ dir: `${topDir}/${sub}`, sourceFileCount: count3 });
2411
2338
  }
2412
2339
  }
2413
2340
  }
@@ -2447,7 +2374,7 @@ var init_new_module = __esm({
2447
2374
  });
2448
2375
 
2449
2376
  // src/audit/checks/stale-count.ts
2450
- import path16 from "path";
2377
+ import path14 from "path";
2451
2378
  async function countGradleModules(root) {
2452
2379
  for (const name of ["settings.gradle.kts", "settings.gradle"]) {
2453
2380
  const content2 = await readAuditInput(root, name);
@@ -2484,7 +2411,7 @@ async function countNpmWorkspaces(root) {
2484
2411
  return {
2485
2412
  actual: matched.length,
2486
2413
  countedFrom: "package.json workspaces",
2487
- members: matched.map((m) => path16.dirname(m)).sort()
2414
+ members: matched.map((m) => path14.dirname(m)).sort()
2488
2415
  };
2489
2416
  }
2490
2417
  }
@@ -2515,7 +2442,7 @@ async function countNpmWorkspaces(root) {
2515
2442
  return {
2516
2443
  actual: matched.length,
2517
2444
  countedFrom: "pnpm-workspace.yaml",
2518
- members: matched.map((m) => path16.dirname(m)).sort()
2445
+ members: matched.map((m) => path14.dirname(m)).sort()
2519
2446
  };
2520
2447
  }
2521
2448
  }
@@ -2535,8 +2462,8 @@ async function countCargoCrates(root) {
2535
2462
  ignore: ["**/target/**"],
2536
2463
  label: "Cargo workspace discovery"
2537
2464
  });
2538
- for (const m of matched) members.add(path16.dirname(m));
2539
- } else if (await readAuditInput(root, path16.posix.join(entry, "Cargo.toml")) !== null) {
2465
+ for (const m of matched) members.add(path14.dirname(m));
2466
+ } else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
2540
2467
  members.add(entry);
2541
2468
  }
2542
2469
  }
@@ -2556,7 +2483,7 @@ async function resolveCountSource(root, claim) {
2556
2483
  }
2557
2484
  async function resolveDocCountSource(root, doc, claim) {
2558
2485
  const scope = documentScope(doc);
2559
- const source = await resolveCountSource(path16.join(root, scope), claim);
2486
+ const source = await resolveCountSource(path14.join(root, scope), claim);
2560
2487
  if (!source || scope === ".") return source;
2561
2488
  return {
2562
2489
  ...source,
@@ -2609,7 +2536,7 @@ var init_stale_count = __esm({
2609
2536
  });
2610
2537
 
2611
2538
  // src/audit/checks/dead-command.ts
2612
- import path17 from "path";
2539
+ import path15 from "path";
2613
2540
  function scriptsOf(raw) {
2614
2541
  try {
2615
2542
  const pkg = JSON.parse(raw);
@@ -2653,7 +2580,7 @@ async function commandInputs(root, docs) {
2653
2580
  }
2654
2581
  let cursor = directory3;
2655
2582
  while (true) {
2656
- const manifest = path17.posix.join(cursor, "package.json");
2583
+ const manifest = path15.posix.join(cursor, "package.json");
2657
2584
  scope.candidates.push(manifest);
2658
2585
  const raw = await read(manifest);
2659
2586
  if (raw !== null) {
@@ -2664,7 +2591,7 @@ async function commandInputs(root, docs) {
2664
2591
  break;
2665
2592
  }
2666
2593
  if (claim.directory !== void 0 || cursor === ".") break;
2667
- cursor = path17.posix.dirname(cursor);
2594
+ cursor = path15.posix.dirname(cursor);
2668
2595
  }
2669
2596
  if (!resolution.manifest && !resolution.problem) {
2670
2597
  resolution.problem = `No package.json for the command's scope (${directory3}).`;
@@ -2703,12 +2630,12 @@ async function checkDeadCommands(ctx) {
2703
2630
  scope
2704
2631
  };
2705
2632
  const anchor = { doc, line: claim.line, excerpt: claim.excerpt };
2706
- const message2 = `\`${claim.invocation}\` names script "${claim.scriptName}", which is absent from ${manifest}`;
2633
+ const message = `\`${claim.invocation}\` names script "${claim.scriptName}", which is absent from ${manifest}`;
2707
2634
  const certain = documentScope(doc) === "." && (scope.basis === "explicit" || !elsewhere.length);
2708
2635
  if (certain)
2709
2636
  result.issues.push({
2710
2637
  type: "dead-command",
2711
- message: message2,
2638
+ message,
2712
2639
  anchor,
2713
2640
  confidence: "certain",
2714
2641
  evidence
@@ -2717,7 +2644,7 @@ async function checkDeadCommands(ctx) {
2717
2644
  result.advisories.push({
2718
2645
  resolution: "recheck",
2719
2646
  type: "dead-command",
2720
- message: message2 + (elsewhere.length ? `; it exists in ${elsewhere.join(", ")}. Clarify the intended working directory.` : "; the intended working directory needs review."),
2647
+ message: message + (elsewhere.length ? `; it exists in ${elsewhere.join(", ")}. Clarify the intended working directory.` : "; the intended working directory needs review."),
2721
2648
  anchor,
2722
2649
  evidence
2723
2650
  });
@@ -2750,7 +2677,7 @@ var init_dead_command = __esm({
2750
2677
  });
2751
2678
 
2752
2679
  // src/audit/dependency-claims.ts
2753
- import path18 from "path";
2680
+ import path16 from "path";
2754
2681
  import { parse as parseToml } from "smol-toml";
2755
2682
  function dependencyText(content2) {
2756
2683
  return content2.replace(
@@ -2761,7 +2688,7 @@ function dependencyText(content2) {
2761
2688
  function declarations(file, source) {
2762
2689
  const result = /* @__PURE__ */ new Map();
2763
2690
  if (!source) return result;
2764
- if (path18.posix.basename(file) === "package.json") {
2691
+ if (path16.posix.basename(file) === "package.json") {
2765
2692
  const json2 = JSON.parse(source);
2766
2693
  if (!isRecord(json2)) throw new Error("Invalid package manifest");
2767
2694
  for (const section of [
@@ -3077,9 +3004,9 @@ var init_deps_changed = __esm({
3077
3004
  });
3078
3005
 
3079
3006
  // src/decisions/drift.ts
3080
- import path19 from "path";
3007
+ import path17 from "path";
3081
3008
  async function computeDecisionDrift(rootDir, decisions) {
3082
- const resolvedRoot = path19.resolve(rootDir);
3009
+ const resolvedRoot = path17.resolve(rootDir);
3083
3010
  const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
3084
3011
  const report = {
3085
3012
  historyAvailable: true,
@@ -3094,18 +3021,22 @@ async function computeDecisionDrift(rootDir, decisions) {
3094
3021
  ]);
3095
3022
  const changesByHash = /* @__PURE__ */ new Map();
3096
3023
  const baselines = store.records.filter((record) => record.status === "active").flatMap((record) => {
3097
- const inspection = latestDecisionInspection(record);
3098
- if (inspection) return [inspection.capture];
3099
- return [.../* @__PURE__ */ new Set([effectiveDecision(record), record])].flatMap(
3100
- (candidate) => candidate.version === 2 && candidate.capture ? [candidate.capture] : []
3101
- );
3024
+ const effective = effectiveDecision(record);
3025
+ return [.../* @__PURE__ */ new Set([effective, record])].flatMap((candidate) => {
3026
+ const inspection = latestDecisionInspection(
3027
+ record,
3028
+ candidate === effective && decisionApproval(effective) === "accepted" ? "operative" : "all"
3029
+ );
3030
+ const capture = inspection?.capture ?? (candidate.version === 2 ? candidate.capture : void 0);
3031
+ return capture ? [capture] : [];
3032
+ });
3102
3033
  });
3103
3034
  const captures = await captureAnchorScopes(
3104
3035
  resolvedRoot,
3105
3036
  baselines.map((baseline) => baseline.anchors)
3106
3037
  );
3107
3038
  const currentCaptures = new Map(
3108
- baselines.map((baseline, index2) => [JSON.stringify(baseline.anchors), captures[index2]])
3039
+ baselines.map((baseline, index) => [JSON.stringify(baseline.anchors), captures[index]])
3109
3040
  );
3110
3041
  const inspect = async (record, inspectionCapture) => {
3111
3042
  if (record.files.length === 0) return { freshness: "unknown", changedFiles: [] };
@@ -3137,12 +3068,18 @@ async function computeDecisionDrift(rootDir, decisions) {
3137
3068
  for (const record of store.records) {
3138
3069
  if (record.status !== "active") continue;
3139
3070
  const effective = effectiveDecision(record);
3140
- const inspection = latestDecisionInspection(record);
3071
+ const inspection = latestDecisionInspection(
3072
+ record,
3073
+ decisionApproval(effective) === "accepted" ? "operative" : "all"
3074
+ );
3141
3075
  const state = await inspect(effective, inspection?.capture);
3142
3076
  report.freshness[record.id] = state.freshness;
3143
3077
  if (state.changedFiles.length) report.staleDecisions[record.id] = state.changedFiles;
3144
3078
  if (effective !== record)
3145
- (report.pendingProposals ??= {})[record.id] = await inspect(record, inspection?.capture);
3079
+ (report.pendingProposals ??= {})[record.id] = await inspect(
3080
+ record,
3081
+ latestDecisionInspection(record)?.capture
3082
+ );
3146
3083
  }
3147
3084
  return report;
3148
3085
  }
@@ -3467,16 +3404,16 @@ function decisionStopReview(session, inputs, report, reentry) {
3467
3404
  };
3468
3405
  }
3469
3406
  const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
3470
- const reason2 = [
3407
+ const reason = [
3471
3408
  "Mason: unresolved decision drift overlaps changes observed during this session. Before ending the turn, inspect these decisions:",
3472
3409
  ...[...decisions].map(
3473
3410
  ([id, value]) => `Decision ${JSON.stringify(safe(value.title))} (${safe(id)}): review_decision(action: "prepare", id: ${JSON.stringify(id)}).`
3474
3411
  ),
3475
- 'Check the claims against the source and diff. If no contradiction is found, record action: "inspect" with the reviewToken, actual inspector, and a note explaining the evidence. "Expected changes" or "my own commits" are not evidence.',
3412
+ 'Check the claims against the source and diff. When an accepted revision exists, prepare and inspect with inspectionScope: "operative" to assess it separately; a contradictory pending draft remains unassessed and must not be promoted. For proposal-only or legacy records use the default all scope. If no contradiction is found in the selected scope, record action: "inspect" with the reviewToken, actual inspector, and a note explaining the evidence. "Expected changes" or "my own commits" are not evidence.',
3476
3413
  "If contradicted or uncertain, explain the mismatch to the user and request direction. Do not accept, reaffirm, or retire a decision without authorized human review. A revised proposal leaves the accepted revision operative until reviewed.",
3477
3414
  `Evidence: ${report.reportPath}. Concurrent changes can contribute; captures do not prove authorship. This reminder does not resolve the finding.`
3478
3415
  ].join("\n");
3479
- return { reason: reason2, block: !reentry && fresh };
3416
+ return { reason, block: !reentry && fresh };
3480
3417
  }
3481
3418
  var evidenceSchema2, stopReviewSchema, empty;
3482
3419
  var init_stop_review = __esm({
@@ -3680,6 +3617,7 @@ var init_store = __esm({
3680
3617
  updatedAt: z9.string(),
3681
3618
  fingerprint: z9.string().nullable(),
3682
3619
  latest: z9.string().nullable(),
3620
+ guidance: z9.object({ fingerprint: z9.string(), path: z9.string() }).optional(),
3683
3621
  notifications: notificationStateSchema.optional(),
3684
3622
  agentNotifications: notificationStateSchema.optional(),
3685
3623
  analysis: z9.object({ id: z9.string(), fingerprint: z9.string(), completedAt: z9.number(), path: z9.string() }).optional()
@@ -3702,19 +3640,19 @@ async function readRecord(root, id) {
3702
3640
  }
3703
3641
  return record;
3704
3642
  }
3705
- function evidenceFingerprint(evidence, reason2) {
3643
+ function evidenceFingerprint(evidence, reason) {
3706
3644
  return digest([
3707
3645
  evidence.scope,
3708
3646
  evidence.tree,
3709
- ...reason2 === "no-dependency-claims" ? [] : [evidence.lastScopeCommit],
3647
+ ...reason === "no-dependency-claims" ? [] : [evidence.lastScopeCommit],
3710
3648
  evidence.paths
3711
3649
  ]);
3712
3650
  }
3713
- function scopes(finding, reason2) {
3651
+ function scopes(finding, reason) {
3714
3652
  const files = [finding.anchor.doc], e = finding.evidence;
3715
3653
  if (e.kind === "decision-anchor")
3716
3654
  throw new Error("Use review_decision for decision acceptance, reaffirmation or retirement.");
3717
- if (reason2 === "no-dependency-claims" && e.kind === "doc-behind-manifests")
3655
+ if (reason === "no-dependency-claims" && e.kind === "doc-behind-manifests")
3718
3656
  return [`:(literal)${finding.anchor.doc}`];
3719
3657
  if (e.kind === "doc-behind-manifests")
3720
3658
  return [`:(literal)${finding.anchor.doc}`, ...manifestPathspecs(finding.anchor.doc)];
@@ -3730,10 +3668,10 @@ function scopes(finding, reason2) {
3730
3668
  return `:(literal)${file}`;
3731
3669
  }).sort();
3732
3670
  }
3733
- async function reviewEvidence(root, finding, reason2) {
3671
+ async function reviewEvidence(root, finding, reason) {
3734
3672
  if (normalizeRepoPath(finding.anchor.doc) !== finding.anchor.doc)
3735
3673
  throw new Error("Invalid advisory document path.");
3736
- const scope = scopes(finding, reason2);
3674
+ const scope = scopes(finding, reason);
3737
3675
  const head = (await git2(root, "rev-parse", "HEAD")).trim();
3738
3676
  const tracked = (await git2(root, "ls-files", "--cached", "-z", "--", ...scope)).split("\0").filter(Boolean);
3739
3677
  if (tracked.length > 2e4) throw new Error("Advisory scope exceeds 20,000 tracked files.");
@@ -3779,7 +3717,7 @@ async function reviewEvidence(root, finding, reason2) {
3779
3717
  }
3780
3718
  const fingerprint = evidenceFingerprint(
3781
3719
  { head, scope, tree, lastScopeCommit, paths, fingerprint: "" },
3782
- reason2
3720
+ reason
3783
3721
  );
3784
3722
  if ((await git2(root, "rev-parse", "HEAD")).trim() !== head)
3785
3723
  throw new Error("HEAD changed while collecting review evidence.");
@@ -3800,7 +3738,10 @@ async function decisionAssessment(root, finding, baselineHead) {
3800
3738
  );
3801
3739
  const record = store.records.find((record2) => record2.id === evidence.decisionId);
3802
3740
  if (!record || record.version !== 2) return null;
3803
- const inspection = latestDecisionInspection(record);
3741
+ const inspection = latestDecisionInspection(
3742
+ record,
3743
+ evidence.provenance?.approval === "accepted" ? "operative" : "all"
3744
+ );
3804
3745
  if (inspection && record.status === "active") {
3805
3746
  const capture = await captureAnchors(root, inspection.capture.anchors);
3806
3747
  let ancestor = true;
@@ -3979,12 +3920,12 @@ var init_advisory_review = __esm({
3979
3920
 
3980
3921
  // src/audit/audit.ts
3981
3922
  import fs12 from "fs/promises";
3982
- import path20 from "path";
3923
+ import path18 from "path";
3983
3924
  async function computeAudit(rootDir, options = {}) {
3984
3925
  return profilePhase("audit.current", () => auditCurrent(rootDir, options));
3985
3926
  }
3986
3927
  async function auditCurrent(rootDir, options) {
3987
- const resolvedRoot = path20.resolve(rootDir);
3928
+ const resolvedRoot = path18.resolve(rootDir);
3988
3929
  const [docs, headHash] = await Promise.all([
3989
3930
  discoverDocs(resolvedRoot),
3990
3931
  getCurrentGitHash(resolvedRoot)
@@ -4022,7 +3963,7 @@ async function auditCurrent(rootDir, options) {
4022
3963
  }
4023
3964
  let decisionsPresent = false;
4024
3965
  try {
4025
- await fs12.access(path20.join(resolvedRoot, ".mason", "decisions"));
3966
+ await fs12.access(path18.join(resolvedRoot, ".mason", "decisions"));
4026
3967
  decisionsPresent = true;
4027
3968
  } catch {
4028
3969
  }
@@ -4088,7 +4029,7 @@ var init_audit = __esm({
4088
4029
 
4089
4030
  // src/audit/repair.ts
4090
4031
  import fs13 from "fs/promises";
4091
- import path21 from "path";
4032
+ import path19 from "path";
4092
4033
  import { randomUUID as randomUUID2 } from "crypto";
4093
4034
  import { z as z11 } from "zod";
4094
4035
  function recheckable(finding) {
@@ -4183,8 +4124,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
4183
4124
  }
4184
4125
  async function loadRepairBaseline(rootDir, baselinePath) {
4185
4126
  const root = await fs13.realpath(rootDir);
4186
- const declaredRoot = path21.resolve(rootDir);
4187
- const relative = path21.isAbsolute(baselinePath) ? path21.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
4127
+ const declaredRoot = path19.resolve(rootDir);
4128
+ const relative = path19.isAbsolute(baselinePath) ? path19.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
4188
4129
  const stored = baselineSchema.parse(await readStoreJson(root, relative));
4189
4130
  const { digest: savedDigest, ...payload } = stored;
4190
4131
  if (digest(payload) !== savedDigest)
@@ -4414,7 +4355,7 @@ var init_repair = __esm({
4414
4355
 
4415
4356
  // src/automation/evidence.ts
4416
4357
  import fs14 from "fs/promises";
4417
- import path22 from "path";
4358
+ import path20 from "path";
4418
4359
  import { createHash as createHash5 } from "crypto";
4419
4360
  import { z as z12 } from "zod";
4420
4361
  async function git3(root, ...args2) {
@@ -4439,6 +4380,66 @@ async function workspace(dir) {
4439
4380
  directory: ".mason/reports/automation/" + hash([root, gitDir, branch]).slice(0, 24)
4440
4381
  };
4441
4382
  }
4383
+ async function captureDecisionEvidence(root, records) {
4384
+ const scopes2 = new Map(
4385
+ records.filter((record) => record.status === "active").map((record) => {
4386
+ const anchors = decisionAnchors(record).sort();
4387
+ return [JSON.stringify(anchors), anchors];
4388
+ })
4389
+ );
4390
+ const captures = await captureAnchorScopes(root, [...scopes2.values()]);
4391
+ const capturesByScope = new Map([...scopes2.keys()].map((key, index) => [key, captures[index]]));
4392
+ const decisionEvidence = {};
4393
+ for (const record of records.filter((record2) => record2.status === "active")) {
4394
+ const key = JSON.stringify(decisionAnchors(record).sort());
4395
+ decisionEvidence[record.id] = {
4396
+ digest: decisionInspectionDigest(record),
4397
+ capture: capturesByScope.get(key)
4398
+ };
4399
+ }
4400
+ return decisionEvidence;
4401
+ }
4402
+ async function readGuidanceInputs(root) {
4403
+ const [head, raw, store, shallowPath, replacements] = await Promise.all([
4404
+ git3(root, "rev-parse", "HEAD").then((text2) => text2.trim()),
4405
+ readDecisionInputs(root),
4406
+ loadDecisionStore(root),
4407
+ git3(root, "rev-parse", "--git-path", "shallow"),
4408
+ git3(root, "for-each-ref", "--format=%(refname) %(objectname)", "refs/replace")
4409
+ ]);
4410
+ if (raw.diagnostics.length || store.diagnostics.length)
4411
+ throw new Error(
4412
+ "Cannot read decision inputs: " + [...raw.diagnostics, ...store.diagnostics].map((d) => `${d.path}: ${d.message}`).join("; ")
4413
+ );
4414
+ const decisionEvidence = await captureDecisionEvidence(root, store.records);
4415
+ const shallow = await fs14.readFile(path20.resolve(root, shallowPath.trim()), "utf8").catch((error) => {
4416
+ if (hasErrorCode(error, "ENOENT")) return null;
4417
+ throw error;
4418
+ });
4419
+ const fingerprint = hash([
4420
+ "guidance",
4421
+ engineVersion,
4422
+ head,
4423
+ raw.inputs,
4424
+ decisionEvidence,
4425
+ shallow,
4426
+ replacements
4427
+ ]);
4428
+ return {
4429
+ fingerprint,
4430
+ head,
4431
+ decisionEvidence,
4432
+ docs: {},
4433
+ keys: {
4434
+ "deleted-reference": "",
4435
+ "new-module": "",
4436
+ "stale-count": "",
4437
+ "dead-command": "",
4438
+ "deps-changed": "",
4439
+ "decision-anchor-drift": fingerprint
4440
+ }
4441
+ };
4442
+ }
4442
4443
  async function readInputs(root) {
4443
4444
  return profilePhase("automation.inputs", () => collectInputs(root));
4444
4445
  }
@@ -4466,7 +4467,7 @@ async function collectInputs(root) {
4466
4467
  const head = headText.trim();
4467
4468
  let shallow = null;
4468
4469
  try {
4469
- shallow = await fs14.readFile(path22.resolve(root, shallowPath.trim()), "utf8");
4470
+ shallow = await fs14.readFile(path20.resolve(root, shallowPath.trim()), "utf8");
4470
4471
  } catch (error) {
4471
4472
  if (error.code !== "ENOENT") throw error;
4472
4473
  }
@@ -4475,7 +4476,7 @@ async function collectInputs(root) {
4475
4476
  const parsedDocs = [];
4476
4477
  const claims = [];
4477
4478
  const presence = /* @__PURE__ */ new Map();
4478
- const exists = (file) => {
4479
+ const exists2 = (file) => {
4479
4480
  if (!presence.has(file)) presence.set(file, pathExists(root, file));
4480
4481
  return presence.get(file);
4481
4482
  };
@@ -4491,8 +4492,8 @@ async function collectInputs(root) {
4491
4492
  for (const candidate of scope?.candidates ?? []) {
4492
4493
  if (optionalMasonPath(candidate)) continue;
4493
4494
  const [filePresent, parentPresent] = await Promise.all([
4494
- exists(candidate),
4495
- exists(path22.posix.dirname(candidate))
4495
+ exists2(candidate),
4496
+ exists2(path20.posix.dirname(candidate))
4496
4497
  ]);
4497
4498
  claims.push([candidate, filePresent, parentPresent]);
4498
4499
  }
@@ -4524,7 +4525,7 @@ async function collectInputs(root) {
4524
4525
  "Cannot read decision inputs: " + decisionInputs.diagnostics.map((d) => `${d.path}: ${d.message}`).join("; ")
4525
4526
  );
4526
4527
  const decisions = decisionInputs.inputs;
4527
- const [status, index2] = decisions.length ? await Promise.all([
4528
+ const [status, index] = decisions.length ? await Promise.all([
4528
4529
  git3(
4529
4530
  root,
4530
4531
  "status",
@@ -4538,23 +4539,8 @@ async function collectInputs(root) {
4538
4539
  git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
4539
4540
  ]) : ["", ""];
4540
4541
  const records = decisions.length ? (await loadDecisionStore(root)).records : [];
4541
- const scopes2 = new Map(
4542
- records.filter((record) => record.status === "active").map((record) => {
4543
- const anchors = decisionAnchors(record).sort();
4544
- return [JSON.stringify(anchors), anchors];
4545
- })
4546
- );
4547
- const captures = await captureAnchorScopes(root, [...scopes2.values()]);
4548
- const capturesByScope = new Map([...scopes2.keys()].map((key, index3) => [key, captures[index3]]));
4549
- const decisionEvidence = {};
4550
- for (const record of records.filter((record2) => record2.status === "active")) {
4551
- const key = JSON.stringify(decisionAnchors(record).sort());
4552
- decisionEvidence[record.id] = {
4553
- digest: decisionInspectionDigest(record),
4554
- capture: capturesByScope.get(key)
4555
- };
4556
- }
4557
- const anchorContent = [...scopes2.keys()].map((key, index3) => [key, captures[index3]]);
4542
+ const decisionEvidence = await captureDecisionEvidence(root, records);
4543
+ const anchorContent = Object.entries(decisionEvidence);
4558
4544
  const common = [8, engineVersion, head, shallow, replacements, docContents, auditPolicy];
4559
4545
  const keys = {
4560
4546
  "deleted-reference": hash([common, claims, docStatus]),
@@ -4567,7 +4553,7 @@ async function collectInputs(root) {
4567
4553
  decisionPresence,
4568
4554
  decisions,
4569
4555
  status,
4570
- index2,
4556
+ index,
4571
4557
  anchorContent
4572
4558
  ])
4573
4559
  };
@@ -4637,7 +4623,7 @@ var init_evidence = __esm({
4637
4623
  init_dead_command();
4638
4624
  init_storage();
4639
4625
  init_advisory_review();
4640
- engineVersion = true ? "0.21.0" : "development";
4626
+ engineVersion = true ? "0.22.0" : "development";
4641
4627
  hash = (value) => createHash5("sha256").update(JSON.stringify(value)).digest("hex");
4642
4628
  content = readAuditInput;
4643
4629
  cacheSchema = z12.object({
@@ -4960,21 +4946,21 @@ function parseExecution(raw) {
4960
4946
  function automationFailure(error) {
4961
4947
  const recorded = failureSchema.safeParse(error?.failure);
4962
4948
  if (recorded.success) return recorded.data;
4963
- const message2 = (error instanceof Error ? error.message : String(error)).replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 700);
4949
+ const message = (error instanceof Error ? error.message : String(error)).replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 700);
4964
4950
  const codes = /* @__PURE__ */ new Set();
4965
4951
  let cause = error;
4966
4952
  for (let i = 0; cause && i < 8; i++) {
4967
4953
  codes.add(String(cause.code));
4968
4954
  cause = cause.cause;
4969
4955
  }
4970
- 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(
4971
- message2
4972
- ) ? "history-unavailable" : error instanceof z15.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(
4973
- message2
4956
+ const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(message) ? "inputs-changed" : /Automation is busy/.test(message) ? "busy" : /not a git repository|unknown revision|bad revision|different history|unreachable/.test(
4957
+ message
4958
+ ) ? "history-unavailable" : error instanceof z15.ZodError || error instanceof SyntaxError || /Hook input|Expected one command|Unknown automation command|--host/.test(message) ? "invalid-input" : /store|baseline|modified|symbolic link|Symlink|automation state|state belongs/.test(
4959
+ message
4974
4960
  ) ? "invalid-evidence" : [...codes].some((c) => /^E[A-Z]+$/.test(c)) ? "io-error" : "internal";
4975
4961
  return {
4976
4962
  code,
4977
- message: message2,
4963
+ message,
4978
4964
  retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code),
4979
4965
  receiptRecorded: false
4980
4966
  };
@@ -5208,8 +5194,8 @@ async function pendingKnowledge(root, now = Date.now()) {
5208
5194
  const proposals = store.records.filter((record) => record.status === "active" && decisionApproval(record) === "proposed").map((record) => {
5209
5195
  const history = record.version === 2 ? record.history : [];
5210
5196
  let lastReview = -1;
5211
- history.forEach((event, index2) => {
5212
- if (event.kind === "accepted" || event.kind === "reaffirmed") lastReview = index2;
5197
+ history.forEach((event, index) => {
5198
+ if (event.kind === "accepted" || event.kind === "reaffirmed") lastReview = index;
5213
5199
  });
5214
5200
  const since = history.slice(lastReview + 1).find((event) => event.kind === "created" || event.kind === "revised")?.at ?? record.createdAt;
5215
5201
  return { record, since };
@@ -5288,7 +5274,7 @@ function summarize(report) {
5288
5274
  ...open.length > 4 ? [`${open.length - 4} more findings in the report.`] : [],
5289
5275
  ...report.diagnostics.slice(0, 2).map(cleanText),
5290
5276
  `Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason check.`,
5291
- "Keep original evidence. Address findings relevant to the authorized task; report unrelated findings with a brief scope reason and suggested follow-up. Use review_advisory for authorized assessments; otherwise leave advisories open without approving them."
5277
+ "Keep original evidence. Address findings relevant to the authorized task; report unrelated findings with a brief scope reason and suggested follow-up. Use mason audit review for authorized assessments; otherwise leave advisories open without approving them."
5292
5278
  ].join("\n");
5293
5279
  }
5294
5280
  async function loadState(ws) {
@@ -5342,11 +5328,14 @@ async function matchingAnalysis(ws, state, inputs) {
5342
5328
  throw new Error("Repair baseline was modified; use the original baseline.");
5343
5329
  return { analysis: a };
5344
5330
  }
5345
- async function verifyInputs(ws, inputs) {
5331
+ async function verifyInputs(ws, inputs, guidance = false) {
5346
5332
  return profilePhase(
5347
5333
  "automation.revalidate",
5348
5334
  () => withRepositoryInspection(ws.root, async () => {
5349
- const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
5335
+ const [after, currentWs] = await Promise.all([
5336
+ guidance ? readGuidanceInputs(ws.root) : readInputs(ws.root),
5337
+ workspace(ws.root)
5338
+ ]);
5350
5339
  if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
5351
5340
  throw new Error(
5352
5341
  "Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout."
@@ -5528,7 +5517,76 @@ async function analyze(ws, inputs, event, startedAt) {
5528
5517
  Math.max(0, 25e3 - (Date.now() - startedAt))
5529
5518
  );
5530
5519
  }
5520
+ async function guidanceAnalysis(ws, inputs) {
5521
+ const checked = await checkDecisionAnchors({
5522
+ root: ws.root,
5523
+ docs: [],
5524
+ headHash: inputs.head,
5525
+ changesSinceDoc: /* @__PURE__ */ new Map(),
5526
+ decisionsPresent: true
5527
+ });
5528
+ const findings = checked.advisories.map((current) => ({
5529
+ id: findingId(current),
5530
+ original: current,
5531
+ current,
5532
+ status: "review-required",
5533
+ reason: "Current decision anchors changed; inspect their reasoning."
5534
+ }));
5535
+ const before = await loadState(ws);
5536
+ if (before.guidance) {
5537
+ const prior = z16.object({ findings: z16.array(repairFindingSchema) }).parse(await readStoreJson(ws.root, before.guidance.path));
5538
+ const currentIds = new Set(findings.map((finding) => finding.id));
5539
+ const records = (await loadDecisionStore(ws.root)).records;
5540
+ for (const previous of prior.findings) {
5541
+ if (currentIds.has(previous.id) || previous.original.evidence.kind !== "decision-anchor")
5542
+ continue;
5543
+ const evidence = previous.original.evidence;
5544
+ const record = records.find((record2) => record2.id === evidence.decisionId);
5545
+ const capture = record && inputs.decisionEvidence[record.id]?.capture;
5546
+ const resolved = record?.status === "retired" || !!record && !!capture?.complete && capture.anchors.length > 0 && !checked.skipped.length;
5547
+ findings.push({
5548
+ id: previous.id,
5549
+ original: previous.original,
5550
+ status: resolved ? "resolved" : "unverified",
5551
+ reason: resolved ? "Current decision evidence no longer reports this drift, or the retained record is retired." : "Decision evidence could not establish closure."
5552
+ });
5553
+ }
5554
+ }
5555
+ const counts = {
5556
+ resolved: 0,
5557
+ unresolved: 0,
5558
+ "review-required": 0,
5559
+ unverified: checked.skipped.length
5560
+ };
5561
+ for (const finding of findings) counts[finding.status]++;
5562
+ const report = {
5563
+ version: 1,
5564
+ status: counts.unverified || counts["review-required"] ? "incomplete" : "verified",
5565
+ root: ws.root,
5566
+ branch: ws.branch,
5567
+ head: inputs.head,
5568
+ baselinePaths: [],
5569
+ reportPath: "",
5570
+ findings,
5571
+ diagnostics: [
5572
+ ...checked.skipped.map((item) => item.reason),
5573
+ ...findings.filter((finding) => finding.status === "unverified").map((finding) => `Decision ${finding.id}: ${finding.reason}`)
5574
+ ],
5575
+ checks: { ran: ["decision-anchor-drift"], reused: [], skipped: checked.skipped },
5576
+ counts,
5577
+ capture: "unknown",
5578
+ scope: "Decision anchor evidence only. Documentation audits and retained repair baselines were not checked. Source inspection remains separate from human approval."
5579
+ };
5580
+ await verifyInputs(ws, inputs, true);
5581
+ return { analysis: { id: "guidance", report }, shared: false };
5582
+ }
5531
5583
  async function automate(dir, event) {
5584
+ return runAutomation(dir, event, false);
5585
+ }
5586
+ async function observeDecisionLifecycle(dir, event) {
5587
+ return runAutomation(dir, event, true);
5588
+ }
5589
+ async function runAutomation(dir, event, guidance) {
5532
5590
  const startedAt = Date.now();
5533
5591
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
5534
5592
  const config = await readAutomationConfig(ws.root);
@@ -5537,12 +5595,12 @@ async function automate(dir, event) {
5537
5595
  ws.directory,
5538
5596
  event.event,
5539
5597
  () => withRepositoryInspection(ws.root, async () => {
5540
- const inputs = await readInputs(ws.root);
5598
+ const inputs = await (guidance ? readGuidanceInputs(ws.root) : readInputs(ws.root));
5541
5599
  const result = await profilePhase(
5542
5600
  "automation.analysis",
5543
- () => analyze(ws, inputs, event, startedAt)
5601
+ async () => guidance ? await guidanceAnalysis(ws, inputs) : await analyze(ws, inputs, event, startedAt)
5544
5602
  );
5545
- if (result.shared) {
5603
+ if (!guidance && result.shared) {
5546
5604
  await verifyInputs(ws, inputs);
5547
5605
  if (result.analysis.baselineDigest !== await baselineDigest(ws, await loadState(ws))) {
5548
5606
  throw new Error(
@@ -5554,16 +5612,18 @@ async function automate(dir, event) {
5554
5612
  "automation.publish",
5555
5613
  () => withLock(ws.root, ws.directory, async () => {
5556
5614
  const state = await loadState(ws);
5557
- if (state.analysis?.id !== result.analysis.id)
5615
+ if (!guidance && state.analysis?.id !== result.analysis.id)
5558
5616
  throw new Error(
5559
5617
  "Automation evidence changed during publication; retry against the current inputs."
5560
5618
  );
5561
5619
  const report = structuredClone(result.analysis.report);
5620
+ if (guidance) await verifyInputs(ws, inputs, true);
5562
5621
  if (!state.notifications) {
5563
5622
  let baseline = { findings: report.findings, head: report.head };
5564
- if (state.latest) {
5623
+ const previousPath2 = guidance ? state.guidance?.path : state.latest;
5624
+ if (previousPath2) {
5565
5625
  try {
5566
- const previous = z16.object({ findings: z16.array(repairFindingSchema), head: z16.string() }).safeParse(await readStoreJson(ws.root, state.latest));
5626
+ const previous = z16.object({ findings: z16.array(repairFindingSchema), head: z16.string() }).safeParse(await readStoreJson(ws.root, previousPath2));
5567
5627
  if (!previous.success) throw new Error("Unsupported saved report");
5568
5628
  baseline = previous.data;
5569
5629
  } catch {
@@ -5581,7 +5641,7 @@ async function automate(dir, event) {
5581
5641
  reused: [.../* @__PURE__ */ new Set([...report.checks.ran, ...report.checks.reused])],
5582
5642
  shared: true
5583
5643
  };
5584
- report.reportPath = ws.directory + "/checks/" + randomUUID4() + ".json";
5644
+ report.reportPath = ws.directory + "/checks/" + (guidance ? "guidance-" : "") + randomUUID4() + ".json";
5585
5645
  const now = (/* @__PURE__ */ new Date()).toISOString();
5586
5646
  const key = event.host && event.sessionId ? hash([event.host, event.sessionId]) : null;
5587
5647
  const newSession = key !== null && !state.sessions[key];
@@ -5643,7 +5703,7 @@ async function automate(dir, event) {
5643
5703
  ])
5644
5704
  ];
5645
5705
  const stopReview = session && event.host === "claude" && event.event === "task_end" && config.stopBlock === "drift" ? decisionStopReview(session, inputs, report, !!event.stopHookActive) : { reason: null, block: false };
5646
- const message2 = !session ? summarize(report) : [
5706
+ const message = !session ? summarize(report) : [
5647
5707
  stopReview.reason,
5648
5708
  completionSummary(selected, report.reportPath),
5649
5709
  completionSummary(feedback, report.reportPath),
@@ -5651,17 +5711,23 @@ async function automate(dir, event) {
5651
5711
  `Mason: verification limited. ${diagnostics.slice(0, 2).map(cleanText).join(" ")} Evidence: ${report.reportPath}.`
5652
5712
  ] : []
5653
5713
  ].filter(Boolean).join("\n") || null;
5654
- const notify = message2 !== null;
5655
- const persistReport = !state.latest || state.fingerprint !== inputs.fingerprint || notify || newSession || event.event === "task_end";
5656
- if (!persistReport) report.reportPath = state.latest;
5714
+ const notify = message !== null;
5715
+ const previousPath = guidance ? state.guidance?.path : state.latest;
5716
+ const previousFingerprint = guidance ? state.guidance?.fingerprint : state.fingerprint;
5717
+ const persistReport = !previousPath || previousFingerprint !== inputs.fingerprint || notify || newSession || event.event === "task_end";
5718
+ if (!persistReport) report.reportPath = previousPath;
5657
5719
  state.updatedAt = now;
5658
- state.fingerprint = inputs.fingerprint;
5659
- state.latest = report.reportPath;
5720
+ if (guidance)
5721
+ state.guidance = { fingerprint: inputs.fingerprint, path: report.reportPath };
5722
+ else {
5723
+ state.fingerprint = inputs.fingerprint;
5724
+ state.latest = report.reportPath;
5725
+ }
5660
5726
  if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
5661
5727
  await writeStoreJson(ws.root, ws.directory + "/state.json", state);
5662
5728
  return {
5663
5729
  report,
5664
- message: message2,
5730
+ message,
5665
5731
  stopBlockReason: stopReview.block ? stopReview.reason : null,
5666
5732
  observedAt: now,
5667
5733
  delivery: { agent: feedback.slice(0, 4), completion: selected.slice(0, 4) }
@@ -5705,6 +5771,8 @@ async function automationStatus(dir) {
5705
5771
  root: ws.root,
5706
5772
  branch: ws.branch,
5707
5773
  baselinePaths: [],
5774
+ scope: "not-observed",
5775
+ auditReportPath: null,
5708
5776
  hosts: {},
5709
5777
  execution,
5710
5778
  notifications,
@@ -5713,8 +5781,9 @@ async function automationStatus(dir) {
5713
5781
  const state = parseState(raw);
5714
5782
  if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch)
5715
5783
  throw new Error("Automation state belongs to another workspace.");
5716
- const inputs = await readInputs(ws.root);
5717
- const latest = state.latest ? await readStoreJson(ws.root, state.latest) : null;
5784
+ const inputs = await (state.guidance ? readGuidanceInputs(ws.root) : readInputs(ws.root));
5785
+ const reportPath = state.guidance?.path ?? state.latest;
5786
+ const latest = reportPath ? await readStoreJson(ws.root, reportPath) : null;
5718
5787
  const hosts2 = {};
5719
5788
  for (const session of Object.values(state.sessions)) {
5720
5789
  const host = hosts2[session.host] ??= { sessions: 0, observedEvents: [] };
@@ -5725,24 +5794,28 @@ async function automationStatus(dir) {
5725
5794
  return {
5726
5795
  version: 1,
5727
5796
  stopBlock: config.stopBlock,
5728
- status: unfinished ? "unavailable" : inputs.fingerprint === state.fingerprint ? "current" : "changed",
5797
+ status: unfinished ? "unavailable" : inputs.fingerprint === (state.guidance?.fingerprint ?? state.fingerprint) ? "current" : "changed",
5729
5798
  root: ws.root,
5730
5799
  branch: ws.branch,
5731
5800
  baselinePaths: state.baselines.map((b) => b.path),
5732
- reportPath: state.latest,
5801
+ reportPath,
5802
+ scope: state.guidance ? "decision-guidance" : "documentation-audit",
5803
+ auditReportPath: state.latest,
5733
5804
  verificationStatus: unfinished ? "unavailable" : latest?.status ?? "unavailable",
5734
5805
  hosts: hosts2,
5735
5806
  execution,
5736
5807
  notifications,
5737
5808
  knowledge,
5738
5809
  notificationBaselineHead: state.notifications?.baselineHead ?? null,
5739
- note: "Observed events do not prove all tool paths are intercepted. Run check to verify the retained evidence."
5810
+ note: "Status describes the reported scope only. Decision guidance does not verify documentation repairs. Observed events do not prove all tool paths are intercepted. Run mason check for retained audit evidence."
5740
5811
  };
5741
5812
  }
5742
5813
  var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
5743
5814
  var init_runtime = __esm({
5744
5815
  "src/automation/runtime.ts"() {
5745
5816
  "use strict";
5817
+ init_decisions();
5818
+ init_decision_anchor();
5746
5819
  init_config();
5747
5820
  init_stop_review();
5748
5821
  init_usefulness();
@@ -5806,9 +5879,9 @@ var init_runtime = __esm({
5806
5879
 
5807
5880
  // src/automation/hook-root.ts
5808
5881
  import fs17 from "fs/promises";
5809
- import path23 from "path";
5882
+ import path21 from "path";
5810
5883
  async function repositoryRoot(dir) {
5811
- if (!path23.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
5884
+ if (!path21.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
5812
5885
  try {
5813
5886
  return await fs17.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
5814
5887
  } catch (error) {
@@ -5852,10 +5925,160 @@ var init_hook_root = __esm({
5852
5925
  }
5853
5926
  });
5854
5927
 
5855
- // src/setup/model.ts
5928
+ // src/hook/hook.ts
5856
5929
  import { z as z17 } from "zod";
5857
5930
  import fs18 from "fs/promises";
5858
- import path24 from "path";
5931
+ import path22 from "path";
5932
+ import os3 from "os";
5933
+ import { createHash as createHash6 } from "crypto";
5934
+ async function exists(p) {
5935
+ try {
5936
+ await fs18.access(p);
5937
+ return true;
5938
+ } catch {
5939
+ return false;
5940
+ }
5941
+ }
5942
+ async function findMasonRoot(startDir) {
5943
+ let dir = startDir;
5944
+ for (let i = 0; i < MAX_WALK_UP; i++) {
5945
+ if (await exists(path22.join(dir, ".mason", "decisions"))) return dir;
5946
+ if (await exists(path22.join(dir, ".git"))) return null;
5947
+ const parent = path22.dirname(dir);
5948
+ if (parent === dir) return null;
5949
+ dir = parent;
5950
+ }
5951
+ return null;
5952
+ }
5953
+ function anchorsCover(record, relPath) {
5954
+ return decisionAnchors(record).some((anchor) => anchorMatches(anchor, relPath));
5955
+ }
5956
+ function exactAnchor(record, relPath) {
5957
+ return decisionAnchors(record).some((a) => a.replace(/\/+$/, "") === relPath);
5958
+ }
5959
+ function stateKey(input) {
5960
+ const raw = `${input.session_id ?? "nosession"}${input.agent_id ? `-${input.agent_id}` : ""}`;
5961
+ return raw.replace(/[^A-Za-z0-9_-]/g, "").slice(0, 120) || "nosession";
5962
+ }
5963
+ async function loadInjected(stateFile) {
5964
+ try {
5965
+ const parsed = JSON.parse(await fs18.readFile(stateFile, "utf-8"));
5966
+ return new Set(isStringArray(parsed) ? parsed : []);
5967
+ } catch {
5968
+ return /* @__PURE__ */ new Set();
5969
+ }
5970
+ }
5971
+ function formatContext(relPath, records, drift) {
5972
+ const lines = [];
5973
+ lines.push(
5974
+ `Mason: decision knowledge relevant to ${relPath} or updated since this session saw it. This replaces earlier guidance for the same decision id. ${DECISION_GUIDANCE} Retired or superseded records are no longer active. Use Mason tools to record decision changes and source inspections; do not edit the JSON in .mason/decisions/ directly.`
5975
+ );
5976
+ const append = (id, knowledge, label, freshness) => {
5977
+ const stale = freshness === "current" ? "" : freshness === "changed" ? " [recorded against changed files \u2013 verify against current code before relying on it]" : " [freshness unknown \u2013 verify against current code before relying on it]";
5978
+ lines.push(
5979
+ `- [${label}] [${knowledge.category}] ${knowledge.title}: ${knowledge.body} (id: ${id}; revision: ${knowledge.revision}; anchors: ${knowledge.files.join(", ")}; owner: ${knowledge.owner ?? "unknown"}; sources: ${knowledge.sources.slice(0, 2).map((s) => s.reference).join(", ") || "unrecorded"})${stale}`
5980
+ );
5981
+ };
5982
+ for (const record of records) {
5983
+ const knowledge = decisionKnowledge(
5984
+ record,
5985
+ drift.freshness?.[record.id] ?? "unknown",
5986
+ drift.pendingProposals?.[record.id]?.freshness ?? "unknown"
5987
+ );
5988
+ append(
5989
+ record.id,
5990
+ knowledge,
5991
+ record.status === "active" ? knowledge.approval : record.status,
5992
+ knowledge.trust.freshness
5993
+ );
5994
+ if (knowledge.pendingProposal)
5995
+ append(
5996
+ record.id,
5997
+ knowledge.pendingProposal,
5998
+ "proposed",
5999
+ knowledge.pendingProposal.trust.freshness
6000
+ );
6001
+ }
6002
+ return lines.join("\n");
6003
+ }
6004
+ async function runHook(stdinText, env = {}) {
6005
+ let input;
6006
+ try {
6007
+ input = hookStdinSchema.parse(JSON.parse(stdinText));
6008
+ } catch {
6009
+ return null;
6010
+ }
6011
+ if (!input || typeof input !== "object") return null;
6012
+ if (input.tool_name && !SUPPORTED_TOOLS.has(input.tool_name)) return null;
6013
+ const filePath = input.tool_input?.file_path;
6014
+ if (!filePath || typeof filePath !== "string") return null;
6015
+ const absPath = path22.isAbsolute(filePath) ? filePath : path22.resolve(input.cwd ?? process.cwd(), filePath);
6016
+ const root = await findMasonRoot(path22.dirname(absPath));
6017
+ if (!root) return null;
6018
+ if (env.rootDir && await fs18.realpath(root) !== await fs18.realpath(env.rootDir)) return null;
6019
+ const relPath = path22.relative(root, absPath).split(path22.sep).join("/");
6020
+ if (relPath.startsWith("..")) return null;
6021
+ const { records } = await loadDecisionStore(root);
6022
+ const stateDir = env.stateDir ?? os3.tmpdir();
6023
+ const stateFile = path22.join(
6024
+ stateDir,
6025
+ `mason-hook-${createHash6("sha256").update(root).digest("hex").slice(0, 12)}-${stateKey(input)}.json`
6026
+ );
6027
+ const injected = await loadInjected(stateFile);
6028
+ const recordKey = (record) => `${record.id}:${createHash6("sha256").update(JSON.stringify(record)).digest("hex")}`;
6029
+ const previouslyInjected = (record) => [...injected].some((key) => key === record.id || key.startsWith(`${record.id}:`));
6030
+ const fresh = records.filter(
6031
+ (r) => !injected.has(recordKey(r)) && (previouslyInjected(r) || r.status === "active" && anchorsCover(r, relPath))
6032
+ );
6033
+ if (fresh.length === 0) return null;
6034
+ fresh.sort((a, b) => {
6035
+ const withdrawn = Number(b.status !== "active") - Number(a.status !== "active");
6036
+ if (withdrawn) return withdrawn;
6037
+ const exactDiff = Number(exactAnchor(b, relPath)) - Number(exactAnchor(a, relPath));
6038
+ if (exactDiff !== 0) return exactDiff;
6039
+ return b.updatedAt.localeCompare(a.updatedAt);
6040
+ });
6041
+ const selected = fresh.slice(0, MAX_INJECTED_DECISIONS);
6042
+ const drift = await computeDecisionDrift(root, selected);
6043
+ for (const record of selected) injected.add(recordKey(record));
6044
+ try {
6045
+ await fs18.writeFile(stateFile, JSON.stringify([...injected]), "utf-8");
6046
+ } catch {
6047
+ }
6048
+ return JSON.stringify({
6049
+ hookSpecificOutput: {
6050
+ hookEventName: env.hookEventName ?? "PostToolUse",
6051
+ additionalContext: formatContext(relPath, selected, drift)
6052
+ }
6053
+ });
6054
+ }
6055
+ var MAX_INJECTED_DECISIONS, MAX_WALK_UP, SUPPORTED_TOOLS, hookStdinSchema;
6056
+ var init_hook = __esm({
6057
+ "src/hook/hook.ts"() {
6058
+ "use strict";
6059
+ init_validation();
6060
+ init_decisions();
6061
+ init_provenance();
6062
+ init_paths();
6063
+ init_drift2();
6064
+ MAX_INJECTED_DECISIONS = 3;
6065
+ MAX_WALK_UP = 30;
6066
+ SUPPORTED_TOOLS = /* @__PURE__ */ new Set(["Read", "Edit", "Write"]);
6067
+ hookStdinSchema = z17.object({
6068
+ session_id: z17.string().optional(),
6069
+ agent_id: z17.string().optional(),
6070
+ cwd: z17.string().optional(),
6071
+ hook_event_name: z17.string().optional(),
6072
+ tool_name: z17.string().optional(),
6073
+ tool_input: z17.object({ file_path: z17.string().optional() }).optional()
6074
+ });
6075
+ }
6076
+ });
6077
+
6078
+ // src/setup/model.ts
6079
+ import { z as z18 } from "zod";
6080
+ import fs19 from "fs/promises";
6081
+ import path23 from "path";
5859
6082
  async function loadSetup(root) {
5860
6083
  const raw = await readStoreJson(root, SETUP_PATH);
5861
6084
  if (raw === null) return null;
@@ -5864,17 +6087,17 @@ async function loadSetup(root) {
5864
6087
  async function effectiveSetup(root) {
5865
6088
  const local = await loadSetup(root);
5866
6089
  if (local) return { root, setup: local };
5867
- const common = await fs18.realpath(
5868
- path24.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
6090
+ const common = await fs19.realpath(
6091
+ path23.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
5869
6092
  );
5870
- const own = await fs18.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
6093
+ const own = await fs19.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
5871
6094
  if (common === own) return { root, setup: null };
5872
6095
  const primary = (await git3(root, "worktree", "list", "--porcelain", "-z")).split("\0\0")[0].split("\0");
5873
6096
  const candidate = primary.find((field) => field.startsWith("worktree "))?.slice(9);
5874
6097
  if (!candidate || primary.includes("bare")) return { root, setup: null };
5875
6098
  let inheritedRoot;
5876
6099
  try {
5877
- inheritedRoot = await fs18.realpath(
6100
+ inheritedRoot = await fs19.realpath(
5878
6101
  (await git3(candidate, "rev-parse", "--show-toplevel")).trim()
5879
6102
  );
5880
6103
  } catch (error) {
@@ -5882,8 +6105,8 @@ async function effectiveSetup(root) {
5882
6105
  return { root, setup: null };
5883
6106
  throw error;
5884
6107
  }
5885
- const inheritedCommon = await fs18.realpath(
5886
- path24.resolve(inheritedRoot, (await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim())
6108
+ const inheritedCommon = await fs19.realpath(
6109
+ path23.resolve(inheritedRoot, (await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim())
5887
6110
  );
5888
6111
  if (inheritedCommon !== common)
5889
6112
  throw new Error("Linked worktree setup does not belong to the same Git repository.");
@@ -5907,35 +6130,35 @@ var init_model = __esm({
5907
6130
  "use strict";
5908
6131
  init_evidence();
5909
6132
  init_storage();
5910
- setupHostSchema = z17.object({
5911
- configuredVersion: z17.string(),
5912
- revision: z17.string().uuid(),
5913
- fingerprint: z17.string(),
5914
- mcpFingerprint: z17.string(),
5915
- instructions: z17.array(z17.string())
6133
+ setupHostSchema = z18.object({
6134
+ configuredVersion: z18.string(),
6135
+ revision: z18.string().uuid(),
6136
+ fingerprint: z18.string(),
6137
+ mcpFingerprint: z18.string(),
6138
+ instructions: z18.array(z18.string())
5916
6139
  });
5917
- setupSchema = z17.object({
5918
- version: z17.literal(2),
5919
- hosts: z17.object({
6140
+ setupSchema = z18.object({
6141
+ version: z18.literal(2),
6142
+ hosts: z18.object({
5920
6143
  codex: setupHostSchema.optional(),
5921
6144
  claude: setupHostSchema.optional()
5922
6145
  })
5923
6146
  });
5924
6147
  SETUP_PATH = ".mason/local/setup.json";
5925
6148
  INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
5926
- inactiveHostsSchema = z17.object({
5927
- version: z17.literal(1),
5928
- hosts: z17.array(z17.enum(["claude", "codex"]))
6149
+ inactiveHostsSchema = z18.object({
6150
+ version: z18.literal(1),
6151
+ hosts: z18.array(z18.enum(["claude", "codex"]))
5929
6152
  });
5930
- receiptSchema = z17.object({
5931
- version: z17.literal(1),
5932
- host: z17.enum(["codex", "claude"]),
5933
- status: z17.enum(["installing", "configured"]),
5934
- initialReportPath: z17.string(),
5935
- initialBaselinePaths: z17.array(z17.string()),
5936
- root: z17.string(),
5937
- revision: z17.string().uuid(),
5938
- configuredAt: z17.string().optional()
6153
+ receiptSchema = z18.object({
6154
+ version: z18.literal(1),
6155
+ host: z18.enum(["codex", "claude"]),
6156
+ status: z18.enum(["installing", "configured"]),
6157
+ initialReportPath: z18.string().optional(),
6158
+ initialBaselinePaths: z18.array(z18.string()).optional(),
6159
+ root: z18.string(),
6160
+ revision: z18.string().uuid(),
6161
+ configuredAt: z18.string().optional()
5939
6162
  });
5940
6163
  }
5941
6164
  });
@@ -5951,10 +6174,10 @@ __export(launcher_exports, {
5951
6174
  mcpCommand: () => mcpCommand,
5952
6175
  prepareLaunch: () => prepareLaunch
5953
6176
  });
5954
- import { z as z18 } from "zod";
6177
+ import { z as z19 } from "zod";
5955
6178
  import { execFile as execFile2 } from "child_process";
5956
6179
  import { promisify as promisify2 } from "util";
5957
- import fs19 from "fs/promises";
6180
+ import fs20 from "fs/promises";
5958
6181
  function globalCommand(args2, platform = process.platform) {
5959
6182
  return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args2] } : { command: "mason", args: args2 };
5960
6183
  }
@@ -5966,7 +6189,7 @@ async function installedCommand() {
5966
6189
  maxBuffer: 8192,
5967
6190
  windowsHide: true
5968
6191
  });
5969
- const result = z18.object({ protocol: z18.literal(1), version: z18.string() }).parse(JSON.parse(stdout));
6192
+ const result = z19.object({ protocol: z19.literal(1), version: z19.string() }).parse(JSON.parse(stdout));
5970
6193
  if (result.protocol !== 1 || typeof result.version !== "string")
5971
6194
  throw new Error("Unsupported Mason integration protocol.");
5972
6195
  return { available: true, version: result.version, message: null };
@@ -5979,7 +6202,7 @@ async function installedCommand() {
5979
6202
  }
5980
6203
  }
5981
6204
  async function prepareLaunch(host, options = {}) {
5982
- const root = await fs19.realpath(
6205
+ const root = await fs20.realpath(
5983
6206
  (await git3(options.dir ?? process.cwd(), "rev-parse", "--show-toplevel")).trim()
5984
6207
  );
5985
6208
  const entry = (await effectiveSetup(root)).setup?.hosts[host];
@@ -6007,9 +6230,9 @@ var init_launcher = __esm({
6007
6230
  "use strict";
6008
6231
  init_evidence();
6009
6232
  init_model();
6010
- executingVersion = () => true ? "0.21.0" : "development";
6233
+ executingVersion = () => true ? "0.22.0" : "development";
6011
6234
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
6012
- hookCommand = (host) => `mason --setup-host ${host} auto`;
6235
+ hookCommand = (host) => `mason --setup-host ${host}`;
6013
6236
  }
6014
6237
  });
6015
6238
 
@@ -6020,8 +6243,8 @@ __export(observations_exports, {
6020
6243
  observeActivation: () => observeActivation,
6021
6244
  readObservation: () => readObservation
6022
6245
  });
6023
- import fs20 from "fs/promises";
6024
- import { z as z19 } from "zod";
6246
+ import fs21 from "fs/promises";
6247
+ import { z as z20 } from "zod";
6025
6248
  function observationPath(directory3, host, revision, version = executingVersion()) {
6026
6249
  return `${directory3}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
6027
6250
  }
@@ -6044,8 +6267,8 @@ async function observeActivation(dir, event, options = {}) {
6044
6267
  return inactiveSetupMessage();
6045
6268
  }
6046
6269
  const capturedDirectory = options.directory?.match(/^\.mason\/reports\/automation\/[a-f0-9]{24}$/)?.[0] ?? options.reportPath?.match(/^(\.mason\/reports\/automation\/[a-f0-9]{24})\/checks\//)?.[1];
6047
- const ws = capturedDirectory ? { root: await fs20.realpath(dir), directory: capturedDirectory } : await workspace(dir);
6048
- if (ws.root !== await fs20.realpath(process.env.MASON_SETUP_ROOT)) return null;
6270
+ const ws = capturedDirectory ? { root: await fs21.realpath(dir), directory: capturedDirectory } : await workspace(dir);
6271
+ if (ws.root !== await fs21.realpath(process.env.MASON_SETUP_ROOT)) return null;
6049
6272
  const { setup } = await effectiveSetup(ws.root);
6050
6273
  if (setup?.hosts[host]?.revision !== revision)
6051
6274
  return "Mason setup changed; restart the assistant to observe the current integration.";
@@ -6096,20 +6319,20 @@ var init_observations = __esm({
6096
6319
  init_storage();
6097
6320
  init_model();
6098
6321
  init_launcher();
6099
- observationSchema = z19.object({
6100
- version: z19.literal(1),
6101
- masonVersion: z19.string(),
6102
- root: z19.string(),
6103
- revision: z19.string(),
6104
- host: z19.enum(["codex", "claude"]),
6105
- contextCalls: z19.number().int().nonnegative(),
6106
- lastContextAt: z19.string().optional(),
6107
- sessions: z19.record(
6108
- z19.object({
6109
- events: z19.array(z19.enum(events)),
6110
- at: z19.string(),
6111
- verificationStatus: z19.string().optional(),
6112
- reportPath: z19.string().optional()
6322
+ observationSchema = z20.object({
6323
+ version: z20.literal(1),
6324
+ masonVersion: z20.string(),
6325
+ root: z20.string(),
6326
+ revision: z20.string(),
6327
+ host: z20.enum(["codex", "claude"]),
6328
+ contextCalls: z20.number().int().nonnegative(),
6329
+ lastContextAt: z20.string().optional(),
6330
+ sessions: z20.record(
6331
+ z20.object({
6332
+ events: z20.array(z20.enum(events)),
6333
+ at: z20.string(),
6334
+ verificationStatus: z20.string().optional(),
6335
+ reportPath: z20.string().optional()
6113
6336
  })
6114
6337
  )
6115
6338
  });
@@ -6117,7 +6340,23 @@ var init_observations = __esm({
6117
6340
  });
6118
6341
 
6119
6342
  // src/automation/adapters.ts
6120
- import { z as z20 } from "zod";
6343
+ import { z as z21 } from "zod";
6344
+ async function decisionContext(host, raw, root) {
6345
+ const input = inputSchema.parse(raw);
6346
+ if (host !== "claude" || !(input.hook_event_name === "PostToolUse" && input.tool_name === "Read" || input.hook_event_name === "PreToolUse" && ["Edit", "Write"].includes(input.tool_name ?? "")))
6347
+ return null;
6348
+ try {
6349
+ const output = await runHook(JSON.stringify(raw), {
6350
+ rootDir: root,
6351
+ hookEventName: input.hook_event_name
6352
+ });
6353
+ if (!output) return null;
6354
+ const parsed = JSON.parse(output);
6355
+ return z21.object({ hookSpecificOutput: z21.object({ additionalContext: z21.string() }) }).parse(parsed).hookSpecificOutput.additionalContext;
6356
+ } catch (error) {
6357
+ return failureMessage(error) + " Decision knowledge was not delivered.";
6358
+ }
6359
+ }
6121
6360
  function normalizeHook(host, raw) {
6122
6361
  hostSchema.parse(host);
6123
6362
  const input = inputSchema.parse(raw);
@@ -6163,9 +6402,13 @@ async function runAutomationHook(host, stdin, options = {}) {
6163
6402
  sessionId: input.event.sessionId,
6164
6403
  directory: observed.directory
6165
6404
  });
6166
- return warning2 ? { systemMessage: warning2 } : null;
6405
+ const knowledge2 = await decisionContext(host, raw, observed.root);
6406
+ return knowledge2 ? {
6407
+ ...warning2 ? { systemMessage: warning2 } : {},
6408
+ hookSpecificOutput: { hookEventName: name, additionalContext: knowledge2 }
6409
+ } : warning2 ? { systemMessage: warning2 } : null;
6167
6410
  }
6168
- const result = await automate(input.cwd, input.event);
6411
+ const result = await observeDecisionLifecycle(input.cwd, input.event);
6169
6412
  const { observeActivation: observeActivation2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
6170
6413
  const warning = await observeActivation2(result.report.root, input.event.event, {
6171
6414
  sessionId: input.event.sessionId,
@@ -6173,6 +6416,8 @@ async function runAutomationHook(host, stdin, options = {}) {
6173
6416
  reportPath: result.report.reportPath
6174
6417
  });
6175
6418
  if (warning) result.message = [result.message, warning].filter(Boolean).join("\n");
6419
+ const knowledge = await decisionContext(host, raw, result.report.root);
6420
+ if (knowledge) result.message = [result.message, knowledge].filter(Boolean).join("\n\n");
6176
6421
  if (name === "Stop" && result.stopBlockReason) {
6177
6422
  return { decision: "block", reason: result.message ?? result.stopBlockReason };
6178
6423
  }
@@ -6182,35 +6427,42 @@ async function runAutomationHook(host, stdin, options = {}) {
6182
6427
  }
6183
6428
  return { hookSpecificOutput: { hookEventName: name, additionalContext: result.message } };
6184
6429
  } catch (error) {
6185
- const message2 = hook ? await hookFailureMessage(hook.cwd, host, hook.event.sessionId, error) : failureMessage(error);
6186
- if (!message2) return null;
6430
+ const message = hook ? await hookFailureMessage(hook.cwd, host, hook.event.sessionId, error) : failureMessage(error);
6431
+ if (!message) return null;
6187
6432
  return {
6188
- systemMessage: message2,
6189
- ...["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse"].includes(name) ? { hookSpecificOutput: { hookEventName: name, additionalContext: message2 } } : {}
6433
+ systemMessage: message,
6434
+ ...["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse"].includes(name) ? { hookSpecificOutput: { hookEventName: name, additionalContext: message } } : {}
6190
6435
  };
6191
6436
  }
6192
6437
  }
6193
- function managedHookCommand(host, platform = process.platform) {
6194
- const command2 = `mason --setup-host ${host} auto hook --host ${host}`;
6438
+ function managedHookCommand(host, platform = process.platform, legacy = false) {
6439
+ const command2 = `mason --setup-host ${host}${legacy ? " auto" : ""} hook --host ${host}`;
6195
6440
  return platform === "win32" ? `cmd.exe /d /s /c "where mason >nul 2>nul & if errorlevel 1 (exit /b 0) else (${command2})"` : `if command -v mason >/dev/null 2>&1; then ${command2}; fi`;
6196
6441
  }
6197
6442
  function knownManagedHookCommands(host) {
6198
6443
  return [
6444
+ "npx --no-install --package mason-context mason auto hook --host " + host,
6445
+ "npx --no-install --package mason-context mason hook --host " + host,
6199
6446
  managedHookCommand(host, "linux"),
6200
6447
  managedHookCommand(host, "win32"),
6448
+ managedHookCommand(host, "linux", true),
6449
+ managedHookCommand(host, "win32", true),
6450
+ `mason --setup-host ${host} hook --host ${host}`,
6201
6451
  `mason --setup-host ${host} auto hook --host ${host}`
6202
6452
  ];
6203
6453
  }
6204
6454
  function compatibleManagedHookCommands(host, platform = process.platform) {
6205
6455
  return [
6206
6456
  managedHookCommand(host, platform),
6457
+ managedHookCommand(host, platform, true),
6458
+ `mason --setup-host ${host} hook --host ${host}`,
6207
6459
  `mason --setup-host ${host} auto hook --host ${host}`
6208
6460
  ];
6209
6461
  }
6210
- function hookConfig(host, command2 = "npx --no-install --package mason-context mason auto") {
6462
+ function hookConfig(host, command2 = "npx --no-install --package mason-context mason") {
6211
6463
  const handler = {
6212
6464
  type: "command",
6213
- command: command2 === `mason --setup-host ${host} auto` ? managedHookCommand(host) : command2 + " hook --host " + host,
6465
+ command: [`mason --setup-host ${host}`, `mason --setup-host ${host} auto`].includes(command2) ? managedHookCommand(host) : command2 + " hook --host " + host,
6214
6466
  timeout: 30
6215
6467
  };
6216
6468
  return {
@@ -6236,22 +6488,23 @@ var init_adapters = __esm({
6236
6488
  init_runtime();
6237
6489
  init_store();
6238
6490
  init_hook_root();
6239
- inputSchema = z20.object({
6240
- cwd: z20.string().min(1),
6241
- session_id: z20.string().min(1).max(500),
6242
- hook_event_name: z20.enum([
6491
+ init_hook();
6492
+ inputSchema = z21.object({
6493
+ cwd: z21.string().min(1),
6494
+ session_id: z21.string().min(1).max(500),
6495
+ hook_event_name: z21.enum([
6243
6496
  "SessionStart",
6244
6497
  "UserPromptSubmit",
6245
6498
  "PreToolUse",
6246
6499
  "PostToolUse",
6247
6500
  "Stop"
6248
6501
  ]),
6249
- tool_name: z20.string().optional(),
6250
- tool_use_id: z20.string().max(500).optional(),
6251
- tool_response: z20.unknown().optional(),
6252
- tool_input: z20.unknown().optional(),
6253
- stop_hook_active: z20.boolean().optional(),
6254
- permission_mode: z20.string().optional()
6502
+ tool_name: z21.string().optional(),
6503
+ tool_use_id: z21.string().max(500).optional(),
6504
+ tool_response: z21.unknown().optional(),
6505
+ tool_input: z21.unknown().optional(),
6506
+ stop_hook_active: z21.boolean().optional(),
6507
+ permission_mode: z21.string().optional()
6255
6508
  });
6256
6509
  lifecycle = {
6257
6510
  SessionStart: "session_start",
@@ -6277,8 +6530,8 @@ __export(files_exports, {
6277
6530
  managedBlock: () => managedBlock,
6278
6531
  readText: () => readText
6279
6532
  });
6280
- import fs21 from "fs/promises";
6281
- import path25 from "path";
6533
+ import fs22 from "fs/promises";
6534
+ import path24 from "path";
6282
6535
  import { randomUUID as randomUUID5 } from "crypto";
6283
6536
  async function readText(root, file) {
6284
6537
  try {
@@ -6315,17 +6568,17 @@ async function applyEdit(root, edit) {
6315
6568
  const file2 = await storePath(root, edit.path);
6316
6569
  if (await readText(root, edit.path) !== edit.before)
6317
6570
  throw new Error("Input changed during removal: " + edit.path);
6318
- await fs21.unlink(file2);
6571
+ await fs22.unlink(file2);
6319
6572
  return true;
6320
6573
  }
6321
6574
  const file = await storePath(root, edit.path, true);
6322
- const temporary = path25.join(path25.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
6575
+ const temporary = path24.join(path24.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
6323
6576
  try {
6324
- const mode = await fs21.stat(file).then(
6577
+ const mode = await fs22.stat(file).then(
6325
6578
  (s) => s.mode & 511,
6326
6579
  () => 384
6327
6580
  );
6328
- const handle = await fs21.open(temporary, "wx", mode);
6581
+ const handle = await fs22.open(temporary, "wx", mode);
6329
6582
  try {
6330
6583
  await handle.writeFile(edit.after, "utf8");
6331
6584
  await handle.sync();
@@ -6334,10 +6587,10 @@ async function applyEdit(root, edit) {
6334
6587
  }
6335
6588
  if (await readText(root, edit.path) !== edit.before)
6336
6589
  throw new Error("Integration input changed during update: " + edit.path);
6337
- await fs21.rename(temporary, file);
6590
+ await fs22.rename(temporary, file);
6338
6591
  return true;
6339
6592
  } finally {
6340
- await fs21.rm(temporary, { force: true });
6593
+ await fs22.rm(temporary, { force: true });
6341
6594
  }
6342
6595
  }
6343
6596
  var init_files2 = __esm({
@@ -6358,7 +6611,7 @@ __export(ownership_exports, {
6358
6611
  ownershipEdit: () => ownershipEdit,
6359
6612
  ownershipSchema: () => ownershipSchema
6360
6613
  });
6361
- import { z as z21 } from "zod";
6614
+ import { z as z22 } from "zod";
6362
6615
  import { parse } from "smol-toml";
6363
6616
  function blockSpan(text2, start, end) {
6364
6617
  const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
@@ -6384,9 +6637,9 @@ async function ownershipEdit(root, edits, hookCommands = {}) {
6384
6637
  )
6385
6638
  };
6386
6639
  const hostConfig = edit.path.endsWith(".json") ? JSON.parse(edit.after) : void 0;
6387
- const parsedHost = z21.object({
6388
- mcpServers: z21.record(z21.unknown()).optional(),
6389
- hooks: z21.record(z21.array(z21.object({ hooks: z21.array(z21.unknown()) }))).optional()
6640
+ const parsedHost = z22.object({
6641
+ mcpServers: z22.record(z22.unknown()).optional(),
6642
+ hooks: z22.record(z22.array(z22.object({ hooks: z22.array(z22.unknown()) }))).optional()
6390
6643
  }).parse(hostConfig ?? {});
6391
6644
  const mcp = edit.path === ".codex/config.toml" ? parse(edit.after).mcp_servers?.mason : edit.path === ".mcp.json" ? parsedHost.mcpServers?.mason : void 0;
6392
6645
  const hooks = edit.path === ".codex/hooks.json" || edit.path === ".claude/settings.json" ? parsedHost.hooks : void 0;
@@ -6428,30 +6681,30 @@ var init_ownership = __esm({
6428
6681
  ["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
6429
6682
  ["# mason:mcp:start", "# mason:mcp:end"]
6430
6683
  ];
6431
- fileSchema = z21.object({
6432
- created: z21.boolean(),
6433
- initialHash: z21.string(),
6434
- blocks: z21.record(z21.string()),
6435
- mcpHash: z21.string().optional(),
6436
- hookCommands: z21.array(z21.string()).optional(),
6437
- hooks: z21.record(z21.array(z21.string())).optional()
6684
+ fileSchema = z22.object({
6685
+ created: z22.boolean(),
6686
+ initialHash: z22.string(),
6687
+ blocks: z22.record(z22.string()),
6688
+ mcpHash: z22.string().optional(),
6689
+ hookCommands: z22.array(z22.string()).optional(),
6690
+ hooks: z22.record(z22.array(z22.string())).optional()
6438
6691
  });
6439
- ownershipSchema = z21.object({ version: z21.literal(1), files: z21.record(fileSchema) });
6692
+ ownershipSchema = z22.object({ version: z22.literal(1), files: z22.record(fileSchema) });
6440
6693
  }
6441
6694
  });
6442
6695
 
6443
6696
  // src/mcp/init.ts
6444
- import { z as z22 } from "zod";
6445
- var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_PLAYBOOK, MAP_PLAYBOOK;
6697
+ import { z as z23 } from "zod";
6698
+ var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_PLAYBOOK;
6446
6699
  var init_init = __esm({
6447
6700
  "src/mcp/init.ts"() {
6448
6701
  "use strict";
6449
6702
  init_storage();
6450
6703
  init_files2();
6451
- markerSchema = z22.object({
6452
- version: z22.literal(1),
6453
- initializedAt: z22.string(),
6454
- features: z22.object({ confluence: z22.boolean().optional() }).passthrough().optional()
6704
+ markerSchema = z23.object({
6705
+ version: z23.literal(1),
6706
+ initializedAt: z23.string(),
6707
+ features: z23.record(z23.unknown()).optional()
6455
6708
  }).passthrough();
6456
6709
  CLAUDE_MD_SECTION = `<!-- mason:start -->
6457
6710
  ## Mason project knowledge
@@ -6464,21 +6717,14 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
6464
6717
  - 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.
6465
6718
  - 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.
6466
6719
  - 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.
6467
- - Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. After source inspection finds no contradiction, record action \`inspect\` with the actual inspector, note, and prepared token; this does not claim human approval. 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.
6468
- - 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.
6469
- - \`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.
6470
-
6471
- - 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.
6472
- - 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.
6473
-
6474
- - 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.
6475
-
6476
- - 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 status\` / \`mason check\`. Preserve existing baselines across sessions. Automatic checks do not authorize unrelated repairs or approve advisories.
6720
+ - Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. To inspect accepted guidance separately, use inspectionScope \`operative\` on both preparation and inspection; a pending draft remains visible and unassessed. Default all scope covers both revisions. After source inspection finds no contradiction in the selected scope, record action \`inspect\` with the actual inspector, note, and prepared token; this does not claim human approval. 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.
6721
+ - Use \`mason status\` or \`mason_automation(action: "status")\` to inspect configuration and observed use. Lifecycle hooks check decision anchors and surface guidance; changed anchors request inspection and do not prove the rationale obsolete.
6722
+ - Audits and repair verification are explicit workflows. Run \`mason audit --help\` or \`mason check\` when that work is requested; setup does not perform an audit or prepare repairs.
6477
6723
 
6478
6724
  Inspect source for what the retrieved context does not answer.
6479
6725
  <!-- mason:end -->`;
6480
6726
  ASSISTANT_SETUP = `UNIFIED SETUP
6481
- When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. This uses the same engine as mason setup: retain the initial audit before instruction edits, use the installed mason command on PATH, merge project MCP configuration and hooks, update marker-delimited instructions and Git ignore rules, and record setup. A concept map is optional.
6727
+ When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. Default setup validates existing shared configuration and writes local activation state only. To explicitly prepare or update shared configuration for review and commit, use mode: "init" (CLI: mason init); dryRun: true previews without writes. Initialization does not activate the checkout. This uses the same engine as mason init: use the installed mason command on PATH, merge project MCP configuration and hooks, update marker-delimited instructions and Git ignore rules, and record setup. A concept map is optional.
6482
6728
  If the user only requested inspection or review, report findings without running setup. The setup result distinguishes configured from observed activation. Explain the host's native trust step and request a new session; never trust hooks on the user's behalf or claim activation from generated configuration alone. After a normal task finishes, mason_automation(action: "status") or mason status reports observed context use and hook events. Existing findings remain reviewable; setup does not approve advisories or manufacture decisions.
6483
6729
 
6484
6730
  The managed project guidance is:
@@ -6486,45 +6732,13 @@ ${CLAUDE_MD_SECTION}`;
6486
6732
  QUICKSTART_PLAYBOOK = `Start with the audit and review results included in this response. No concept map is required.
6487
6733
 
6488
6734
  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.
6489
- 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.
6735
+ 2. Address findings within the user's requested scope. Initialization prepares shared configuration; setup only activates existing configuration locally; rewriting existing project claims requires repair scope. If repair is authorized, run \`mason audit --prepare-repair --json\` before editing; it saves the full original findings even when this summary is truncated. Inspect relevant source, make grounded edits, then run \`mason audit --verify-repair <baselinePath> --json\` 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 initialization has already edited an instruction file. Do not invent a decision just to populate the store.
6490
6736
  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)\`.
6491
6737
 
6492
6738
  ${ASSISTANT_SETUP}
6493
6739
 
6494
6740
  OPTIONAL ARCHITECTURE MAP
6495
- A full map adds feature and flow navigation. Build it only if the user requests it, by calling \`mason_init(dir, mode: "map")\`. Decision capture and task context do not depend on that build.`;
6496
- MAP_PLAYBOOK = `The user has requested a full architecture map. Follow this Map-Reduce workflow to cover the codebase. Report the audit and review findings included in this response before beginning.
6497
-
6498
- PHASE 1 \u2014 Map (loop until done)
6499
- Goal: process every file in the codebase, batch by batch, producing a partial concept map per batch.
6500
-
6501
- 1. Call \`generate_snapshot_batch(dir)\` (omit offset on the first call).
6502
- The response includes:
6503
- - \`batchId\`: identifier for this batch
6504
- - \`offset\`, \`nextOffset\`, \`totalFiles\`: progress markers
6505
- - \`instructions\`: the system prompt for the batch step
6506
- - \`prompt\`: the files in this batch (skeletons + a few deeper bodies)
6507
- 2. Following the \`instructions\`, derive features and flows that involve ONLY the files in this batch. Use product-natural feature names ("home screen", not "HomeScreenAndroid") so the reduce step can merge platform variants.
6508
- 3. Call \`save_partial_snapshot(dir, batchId, offset, features, flows)\` to persist the partial.
6509
- 4. If \`nextOffset\` is null, the Map phase is done. Otherwise call \`generate_snapshot_batch(dir, offset=nextOffset)\` and repeat from step 2.
6510
-
6511
- Briefly tell the user "Batch N of M done" each iteration so they see progress.
6512
-
6513
- CRITICAL RULES FOR PHASE 1:
6514
- - Derive features and file paths ONLY from what appears verbatim in the \`prompt\` field of each batch response. NEVER invent paths from memory, prior projects, or what you assume a project of this kind would contain. If you have not seen a path in a batch \`prompt\`, do not put it in \`features.files\` or \`flows.chain\`.
6515
- - Process batches SEQUENTIALLY: one \`generate_snapshot_batch\` \u2192 derive \u2192 one \`save_partial_snapshot\` \u2192 next \`generate_snapshot_batch\`. Do not parallelise. Do not call \`save_snapshot\` during this phase \u2014 that is a Phase 2 step.
6516
- - You must walk every batch until \`nextOffset\` is null. Do not stop early. Do not skip ahead to reduce until every batch has been saved as a partial.
6517
-
6518
- PHASE 2 \u2014 Reduce (once)
6519
- Goal: merge all partial maps into one coherent product-shaped catalog.
6520
-
6521
- 1. Call \`reduce_snapshot(dir)\`. It returns every partial map plus reconciliation instructions.
6522
- 2. Follow the instructions to produce a unified \`features\` and \`flows\` map. Specifically: merge platform variants ("home Android" + "home iOS" \u2192 "home screen"), dedupe near-duplicates, reconcile descriptions, and ensure every file from every partial appears somewhere in the final map.
6523
- 3. Call \`save_snapshot(dir, features, flows)\` ONCE with the unified map. Mason detects that partials exist and replaces the snapshot wholesale (rather than merging with any earlier state) and then clears the partials. Do not call \`save_snapshot\` more than once per Map-Reduce run.
6524
-
6525
- ${ASSISTANT_SETUP}
6526
-
6527
- If a build is interrupted, its partials remain in \`.mason/partial-snapshots/\`. Re-run \`mason_init(dir, mode: "map")\` to obtain this workflow again.`;
6741
+ Existing maps remain readable for legacy projects. Managed map creation has been removed; use the agent's own source tools for architecture investigations.`;
6528
6742
  }
6529
6743
  });
6530
6744
 
@@ -6534,13 +6748,14 @@ __export(config_exports, {
6534
6748
  ancillaryEdits: () => ancillaryEdits,
6535
6749
  hookEdits: () => hookEdits,
6536
6750
  inspectHostConfig: () => inspectHostConfig,
6751
+ inspectIntegration: () => inspectIntegration,
6537
6752
  instructionEdits: () => instructionEdits,
6538
6753
  mcpEdit: () => mcpEdit,
6539
6754
  mcpPath: () => mcpPath
6540
6755
  });
6541
6756
  import { isDeepStrictEqual } from "util";
6542
6757
  import { parse as parse2, stringify } from "smol-toml";
6543
- import { z as z23 } from "zod";
6758
+ import { z as z24 } from "zod";
6544
6759
  async function instructionEdits(root, host) {
6545
6760
  const found = await Promise.all(
6546
6761
  DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) }))
@@ -6621,7 +6836,7 @@ async function mcpEdit(root, host) {
6621
6836
  if (servers?.mason && !managed) {
6622
6837
  const headers = [...base.matchAll(/^\s*\[\[?.+?\]\]?[^\S\r\n]*(?:#.*)?$/gm)];
6623
6838
  const spans = headers.flatMap(
6624
- (header, index2) => /^\s*\[mcp_servers\.mason(?:\.[A-Za-z0-9_-]+)*\]/.test(header[0]) ? [{ start: header.index, end: headers[index2 + 1]?.index ?? base.length }] : []
6839
+ (header, index) => /^\s*\[mcp_servers\.mason(?:\.[A-Za-z0-9_-]+)*\]/.test(header[0]) ? [{ start: header.index, end: headers[index + 1]?.index ?? base.length }] : []
6625
6840
  );
6626
6841
  if (!spans.length)
6627
6842
  throw new Error(
@@ -6702,6 +6917,44 @@ async function inspectHostConfig(root, host, plannedMcp) {
6702
6917
  disabled: hooks.disableAllHooks === true || config.features?.hooks === false || config.features?.codex_hooks === false
6703
6918
  };
6704
6919
  }
6920
+ async function inspectIntegration(root, host) {
6921
+ const config = await inspectHostConfig(root, host);
6922
+ const instructions = await instructionEdits(root, host);
6923
+ const primary = instructions[0]?.before ?? "";
6924
+ const { blockSpan: blockSpan2 } = await Promise.resolve().then(() => (init_ownership(), ownership_exports));
6925
+ const block = blockSpan2(primary, "<!-- mason:start -->", "<!-- mason:end -->");
6926
+ const instructionCompatible = !!block && ["get_context", "save_decision", "review_decision"].every(
6927
+ (tool) => block.text.includes(tool)
6928
+ ) && instructions.slice(1).every((edit) => {
6929
+ const imported = edit.path.startsWith(".claude/") ? "@../AGENTS.md" : "@AGENTS.md";
6930
+ return (edit.before ?? "").split(/\r?\n/).some((line) => line.trim() === imported);
6931
+ });
6932
+ const expectedHooks = hookConfig(host, hookCommand(host)).hooks;
6933
+ const normalizedHooks = Object.fromEntries(
6934
+ Object.entries(config.hooks).map(([event, groups]) => [
6935
+ event,
6936
+ groups.map((group) => ({
6937
+ ...group,
6938
+ hooks: group.hooks.map((handler) => ({
6939
+ ...handler,
6940
+ command: expectedHooks.SessionStart[0].hooks[0].command
6941
+ }))
6942
+ }))
6943
+ ])
6944
+ );
6945
+ const mcpCompatible = isDeepStrictEqual({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host)) && (config.mcp?.type === void 0 || config.mcp.type === "stdio");
6946
+ const hooksCompatible = isDeepStrictEqual(normalizedHooks, expectedHooks);
6947
+ return {
6948
+ ...config,
6949
+ instructions,
6950
+ instructionCompatible,
6951
+ instructionsCurrent: instructions.every((edit) => edit.before === edit.after),
6952
+ mcpCompatible,
6953
+ hooksCompatible,
6954
+ hooksCurrent: isDeepStrictEqual(config.hooks, expectedHooks),
6955
+ compatible: instructionCompatible && mcpCompatible && hooksCompatible
6956
+ };
6957
+ }
6705
6958
  var object, mcpPath, TOML_START, TOML_END, POINTER_START, POINTER_END;
6706
6959
  var init_config2 = __esm({
6707
6960
  "src/setup/config.ts"() {
@@ -6712,7 +6965,7 @@ var init_config2 = __esm({
6712
6965
  init_install();
6713
6966
  init_files2();
6714
6967
  init_launcher();
6715
- object = z23.record(z23.unknown());
6968
+ object = z24.record(z24.unknown());
6716
6969
  mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
6717
6970
  TOML_START = "# mason:mcp:start";
6718
6971
  TOML_END = "# mason:mcp:end";
@@ -6732,7 +6985,7 @@ __export(install_exports, {
6732
6985
  planAutomationInstall: () => planAutomationInstall,
6733
6986
  recordSchema: () => recordSchema2
6734
6987
  });
6735
- import { z as z24 } from "zod";
6988
+ import { z as z25 } from "zod";
6736
6989
  async function installationRecord(root) {
6737
6990
  return recordSchema2.parse(
6738
6991
  await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} }
@@ -6833,13 +7086,13 @@ var init_install = __esm({
6833
7086
  init_store();
6834
7087
  init_files2();
6835
7088
  init_ownership();
6836
- groupSchema = z24.object({
6837
- hooks: z24.array(z24.object({ type: z24.string(), command: z24.string().optional() }).passthrough())
7089
+ groupSchema = z25.object({
7090
+ hooks: z25.array(z25.object({ type: z25.string(), command: z25.string().optional() }).passthrough())
6838
7091
  }).passthrough();
6839
- automationConfigSchema = z24.object({ hooks: z24.record(z24.array(groupSchema)).optional() }).passthrough();
6840
- recordSchema2 = z24.object({
6841
- version: z24.literal(1),
6842
- hosts: z24.record(z24.object({ command: z24.string() }))
7092
+ automationConfigSchema = z25.object({ hooks: z25.record(z25.array(groupSchema)).optional() }).passthrough();
7093
+ recordSchema2 = z25.object({
7094
+ version: z25.literal(1),
7095
+ hosts: z25.record(z25.object({ command: z25.string() }))
6843
7096
  });
6844
7097
  AUTOMATION_PATH = ".mason/local/automation.json";
6845
7098
  configPath3 = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
@@ -6853,12 +7106,12 @@ __export(teardown_exports, {
6853
7106
  teardownProject: () => teardownProject
6854
7107
  });
6855
7108
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
6856
- import fs22 from "fs/promises";
6857
- import path26 from "path";
7109
+ import fs23 from "fs/promises";
7110
+ import path25 from "path";
6858
7111
  import { parse as parse3, stringify as stringify2 } from "smol-toml";
6859
7112
  import fg8 from "fast-glob";
6860
- import { z as z25 } from "zod";
6861
- async function planTeardown(root, selected) {
7113
+ import { z as z26 } from "zod";
7114
+ async function planProjectTeardown(root, selected) {
6862
7115
  const edits = [], diagnostics = [];
6863
7116
  const inputs = /* @__PURE__ */ new Map();
6864
7117
  const read = async (file) => {
@@ -6879,8 +7132,8 @@ async function planTeardown(root, selected) {
6879
7132
  const ownership = ownershipBefore === null ? { version: 1, files: {} } : ownershipSchema.parse(JSON.parse(ownershipBefore));
6880
7133
  const setupBefore = await read(SETUP_PATH);
6881
7134
  const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
6882
- const linked = (await workspace(root)).gitDir !== await fs22.realpath(
6883
- path26.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
7135
+ const linked = (await workspace(root)).gitDir !== await fs23.realpath(
7136
+ path25.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
6884
7137
  );
6885
7138
  const inactiveBefore = linked && !setup ? await read(INACTIVE_HOSTS_PATH) : null;
6886
7139
  const inactive = inactiveBefore === null ? { version: 1, hosts: [] } : inactiveHostsSchema.parse(JSON.parse(inactiveBefore));
@@ -7059,7 +7312,7 @@ async function planTeardown(root, selected) {
7059
7312
  await attempt(".mason/local/project.json", async () => {
7060
7313
  const before = await read(".mason/local/project.json");
7061
7314
  if (before === null) return;
7062
- z25.object({ version: z25.literal(1), initializedAt: z25.string() }).strict().parse(JSON.parse(before));
7315
+ z26.object({ version: z26.literal(1), initializedAt: z26.string() }).strict().parse(JSON.parse(before));
7063
7316
  cleanup.push({ path: ".mason/local/project.json", before, after: null });
7064
7317
  });
7065
7318
  if (!diagnostics.length) {
@@ -7104,11 +7357,60 @@ async function planTeardown(root, selected) {
7104
7357
  inputs
7105
7358
  };
7106
7359
  }
7360
+ async function planLocalTeardown(root, selected) {
7361
+ const inputs = /* @__PURE__ */ new Map();
7362
+ const read = async (file) => {
7363
+ const value = await readText(root, file);
7364
+ inputs.set(file, value);
7365
+ return value;
7366
+ };
7367
+ const edits = [];
7368
+ const setupBefore = await read(SETUP_PATH);
7369
+ const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
7370
+ const automationBefore = await read(AUTOMATION_PATH);
7371
+ const automation = automationBefore === null ? null : recordSchema2.parse(JSON.parse(automationBefore));
7372
+ for (const host of selected) {
7373
+ if (setup) delete setup.hosts[host];
7374
+ if (automation) delete automation.hosts[host];
7375
+ }
7376
+ if (setup) edits.push({ path: SETUP_PATH, before: setupBefore, after: json(setup) });
7377
+ if (automation)
7378
+ edits.push({ path: AUTOMATION_PATH, before: automationBefore, after: json(automation) });
7379
+ const linked = (await workspace(root)).gitDir !== await fs23.realpath(
7380
+ path25.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
7381
+ );
7382
+ if (linked && !setup) {
7383
+ const before = await read(INACTIVE_HOSTS_PATH);
7384
+ const inactive = before === null ? { version: 1, hosts: [] } : inactiveHostsSchema.parse(JSON.parse(before));
7385
+ edits.push({
7386
+ path: INACTIVE_HOSTS_PATH,
7387
+ before,
7388
+ after: json({ version: 1, hosts: [.../* @__PURE__ */ new Set([...inactive.hosts, ...selected])].sort() })
7389
+ });
7390
+ }
7391
+ const receipts = await fg8(
7392
+ selected.map((host) => ".mason/reports/automation/*/setup-" + host + ".json"),
7393
+ { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false }
7394
+ );
7395
+ if (receipts.length > 1e3)
7396
+ throw new Error("Too many setup receipts; inspect local setup history before teardown.");
7397
+ for (const file of receipts) {
7398
+ const host = file.endsWith("setup-codex.json") ? "codex" : "claude";
7399
+ await loadSetupReceipt(root, file.slice(0, file.lastIndexOf("/")), host);
7400
+ edits.push({ path: file, before: await read(file), after: null });
7401
+ }
7402
+ return {
7403
+ edits: edits.filter((edit) => edit.before !== edit.after),
7404
+ inputs,
7405
+ diagnostics: [],
7406
+ remainingHost: true
7407
+ };
7408
+ }
7107
7409
  async function teardownProject(dir, options = {}) {
7108
7410
  const { root } = await workspace(dir);
7109
7411
  const selected = options.host ? [options.host] : [...hosts];
7110
7412
  const run = async (apply) => {
7111
- const plan = await planTeardown(root, selected);
7413
+ const plan = options.project ? await planProjectTeardown(root, selected) : await planLocalTeardown(root, selected);
7112
7414
  if (apply) {
7113
7415
  for (const [file, before] of plan.inputs)
7114
7416
  if (await readText(root, file) !== before)
@@ -7118,6 +7420,7 @@ async function teardownProject(dir, options = {}) {
7118
7420
  return {
7119
7421
  version: 1,
7120
7422
  action: "teardown",
7423
+ scope: options.project ? "project" : "local",
7121
7424
  status: plan.diagnostics.length ? "incomplete" : "complete",
7122
7425
  root,
7123
7426
  hosts: selected,
@@ -7128,23 +7431,32 @@ async function teardownProject(dir, options = {}) {
7128
7431
  })),
7129
7432
  diagnostics: plan.diagnostics,
7130
7433
  sharedInstructionsRetained: plan.remainingHost || !!plan.diagnostics.length,
7131
- next: plan.diagnostics.length ? "Inspect the retained entries, remove or correct them, then rerun teardown." : "Restart running assistants to unload the project integration. Run mason setup --host codex or --host claude to reconnect."
7434
+ next: plan.diagnostics.length ? "Inspect the retained entries, remove or correct them, then rerun teardown." : options.project ? "Review and commit shared removals. Run mason init to restore shared configuration." : "Restart running assistants to unload the local integration. Shared configuration is retained; run mason setup to reconnect."
7132
7435
  };
7133
7436
  };
7134
7437
  const preview = await run(false);
7135
7438
  if (options.dryRun || !preview.changes.length) return preview;
7439
+ if ((await git3(
7440
+ root,
7441
+ "ls-files",
7442
+ "--",
7443
+ ".mason/local",
7444
+ ".mason/reports/setup-lock",
7445
+ ...preview.changes.filter((change) => change.path.startsWith(".mason/reports/")).map((change) => change.path)
7446
+ )).trim())
7447
+ throw new Error("Mason local state must be untracked before teardown.");
7136
7448
  return withLock(root, ".mason/reports/setup-lock", () => run(true));
7137
7449
  }
7138
7450
  function summarizeTeardown(result) {
7139
7451
  return [
7140
- `Mason teardown${result.dryRun ? " preview" : ""}: ${result.status}.`,
7452
+ `Mason ${result.scope} teardown${result.dryRun ? " preview" : ""}: ${result.status}.`,
7141
7453
  ...result.changes.map(
7142
7454
  (change) => ` ${result.dryRun ? "Would " : ""}${change.action}: ${change.path}`
7143
7455
  ),
7144
7456
  ...result.diagnostics.map((item) => ` Retained ${item.path}: ${item.message}`),
7145
7457
  ...!result.changes.length ? ["No changes."] : [],
7146
7458
  "Shared knowledge, repair evidence, ignore rules and the installed Mason command are retained.",
7147
- ...result.sharedInstructionsRetained ? [
7459
+ ...result.scope === "project" && result.sharedInstructionsRetained ? [
7148
7460
  "Shared instruction cleanup is deferred while another host or incomplete removal needs inspection."
7149
7461
  ] : [],
7150
7462
  ...result.dryRun ? ["Preview only; no files were changed."] : [result.next]
@@ -7166,1043 +7478,17 @@ var init_teardown = __esm({
7166
7478
  init_model();
7167
7479
  init_ownership();
7168
7480
  hosts = ["codex", "claude"];
7169
- object2 = z25.record(z25.unknown());
7481
+ object2 = z26.record(z26.unknown());
7170
7482
  json = (value) => JSON.stringify(value, null, 2) + "\n";
7171
7483
  }
7172
7484
  });
7173
7485
 
7174
- // src/review/cochange.ts
7175
- import { execFile as execFile3 } from "child_process";
7176
- import { promisify as promisify3 } from "util";
7177
- async function buildMatrix(resolvedRoot) {
7178
- try {
7179
- const { stdout: shallow } = await exec("git", ["rev-parse", "--is-shallow-repository"], {
7180
- cwd: resolvedRoot
7181
- });
7182
- if (shallow.trim() === "true") return null;
7183
- const { stdout } = await exec(
7184
- "git",
7185
- ["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
7186
- { cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
7187
- );
7188
- const commitsByFile = /* @__PURE__ */ new Map();
7189
- const blocks = stdout.split("");
7190
- let index2 = 0;
7191
- for (const block of blocks) {
7192
- const files = block.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith(".mason/"));
7193
- if (files.length === 0 || files.length > MAX_COMMIT_FILES) continue;
7194
- for (const file of files) {
7195
- let set = commitsByFile.get(file);
7196
- if (!set) {
7197
- set = /* @__PURE__ */ new Set();
7198
- commitsByFile.set(file, set);
7199
- }
7200
- set.add(index2);
7201
- }
7202
- index2++;
7203
- }
7204
- return { commitsByFile, totalCommits: index2 };
7205
- } catch {
7206
- return null;
7207
- }
7208
- }
7209
- async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, allChangedFiles = changedFiles) {
7210
- const matrix = await buildMatrix(resolvedRoot);
7211
- if (!matrix) return null;
7212
- const changedSet = new Set(allChangedFiles);
7213
- const findings = [];
7214
- for (const changedFile of changedFiles) {
7215
- const fileCommits = matrix.commitsByFile.get(changedFile);
7216
- if (!fileCommits || fileCommits.size < MIN_FILE_COMMITS) continue;
7217
- for (const [partner, partnerCommits] of matrix.commitsByFile) {
7218
- if (partner === changedFile || changedSet.has(partner)) continue;
7219
- let shared = 0;
7220
- for (const c of fileCommits) {
7221
- if (partnerCommits.has(c)) shared++;
7222
- }
7223
- if (shared < MIN_SHARED_COMMITS) continue;
7224
- const rate = shared / fileCommits.size;
7225
- if (rate < MIN_COCHANGE_RATE) continue;
7226
- if (!await existsOnDisk(partner)) continue;
7227
- findings.push({
7228
- changedFile,
7229
- missingPartner: partner,
7230
- sharedCommits: shared,
7231
- fileCommits: fileCommits.size,
7232
- rate: Math.round(rate * 100) / 100
7233
- });
7234
- }
7235
- }
7236
- findings.sort((a, b) => b.rate - a.rate || b.sharedCommits - a.sharedCommits);
7237
- return findings;
7238
- }
7239
- var exec, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
7240
- var init_cochange = __esm({
7241
- "src/review/cochange.ts"() {
7242
- "use strict";
7243
- exec = promisify3(execFile3);
7244
- HISTORY_COMMITS = 1500;
7245
- MIN_FILE_COMMITS = 5;
7246
- MIN_SHARED_COMMITS = 4;
7247
- MIN_COCHANGE_RATE = 0.6;
7248
- MAX_COMMIT_FILES = 30;
7249
- }
7250
- });
7251
-
7252
- // src/review/evidence/paths.ts
7253
- function evidencePath(value, sourceRoot, uri = false) {
7254
- if (value.length > 4e3) return null;
7255
- let file = value;
7256
- try {
7257
- if (uri) {
7258
- if (file.startsWith("file:")) {
7259
- const url = new URL(file);
7260
- if (url.hostname && url.hostname !== "localhost") return null;
7261
- file = decodeURIComponent(url.pathname).replace(/^\/([A-Za-z]:\/)/, "$1");
7262
- } else {
7263
- if (/^[a-z][a-z0-9+.-]*:/i.test(file)) return null;
7264
- file = decodeURIComponent(file);
7265
- }
7266
- }
7267
- } catch {
7268
- return null;
7269
- }
7270
- file = file.replace(/\\/g, "/");
7271
- const root = sourceRoot.replace(/\\/g, "/").replace(/\/+$/, "");
7272
- if (file.startsWith("/") || /^[A-Za-z]:/.test(file)) {
7273
- if (!file.startsWith(root + "/")) return null;
7274
- file = file.slice(root.length + 1);
7275
- }
7276
- return normalizeRepoPath(file);
7277
- }
7278
- var init_paths2 = __esm({
7279
- "src/review/evidence/paths.ts"() {
7280
- "use strict";
7281
- init_paths();
7282
- }
7283
- });
7284
-
7285
- // src/review/evidence/types.ts
7286
- function messagePreview(message2) {
7287
- return { message: message2.slice(0, 4e3), ...message2.length > 4e3 ? { truncated: true } : {} };
7288
- }
7289
- var MAX_FINDINGS;
7290
- var init_types2 = __esm({
7291
- "src/review/evidence/types.ts"() {
7292
- "use strict";
7293
- MAX_FINDINGS = 200;
7294
- }
7295
- });
7296
-
7297
- // src/review/evidence/vitest.ts
7298
- import { z as z26 } from "zod";
7299
- function parseVitest(raw, sourceRoot) {
7300
- const report = schema.parse(raw);
7301
- const findings = [], diagnostics = [];
7302
- let passed = 0, failed = 0, skipped = 0;
7303
- for (const [index2, suite] of report.testResults.entries()) {
7304
- const file = evidencePath(suite.name, sourceRoot);
7305
- if (!file)
7306
- diagnostics.push(`Test path is outside the declared checkout or invalid: ${suite.name}`);
7307
- let suiteFailures = 0;
7308
- for (const [testIndex, test] of suite.assertionResults.entries()) {
7309
- if (test.status === "passed") passed++;
7310
- else if (test.status === "failed") {
7311
- failed++;
7312
- suiteFailures++;
7313
- findings.push({
7314
- id: `${index2}:${testIndex}`,
7315
- ...messagePreview([test.fullName, ...test.failureMessages ?? []].join("\n")),
7316
- severity: "error",
7317
- state: "active",
7318
- locations: file ? [
7319
- {
7320
- file,
7321
- ...test.location ? { line: test.location.line, column: test.location.column } : {}
7322
- }
7323
- ] : []
7324
- });
7325
- } else skipped++;
7326
- }
7327
- if (suite.status === "failed" && !suiteFailures) {
7328
- findings.push({
7329
- id: `${index2}:suite`,
7330
- ...messagePreview(suite.message || `Test suite failed: ${suite.name}`),
7331
- severity: "error",
7332
- state: "active",
7333
- locations: file ? [{ file }] : []
7334
- });
7335
- }
7336
- }
7337
- if (passed !== report.numPassedTests || failed !== report.numFailedTests || skipped !== report.numPendingTests + report.numTodoTests || passed + failed + skipped !== report.numTotalTests) {
7338
- throw new Error("Vitest summary counts disagree with its assertion results");
7339
- }
7340
- const hasFailures = failed > 0 || report.numFailedTestSuites > 0 || report.testResults.some((s) => s.status === "failed");
7341
- if (report.success && hasFailures)
7342
- throw new Error("Vitest success conflicts with failed tests or suites");
7343
- const outcome = hasFailures || !report.success ? "failed" : !report.numTotalTests ? "unavailable" : !passed ? "skipped" : "passed";
7344
- if (!report.numTotalTests)
7345
- diagnostics.push("No tests executed; an empty report is not passing test evidence.");
7346
- if (skipped) diagnostics.push(`${skipped} tests were skipped, pending, disabled, or todo.`);
7347
- return {
7348
- outcome,
7349
- findings,
7350
- counts: {
7351
- total: report.numTotalTests,
7352
- passed,
7353
- failed,
7354
- skipped,
7355
- failedSuites: report.numFailedTestSuites
7356
- },
7357
- incomplete: skipped > 0 || diagnostics.length > 0,
7358
- diagnostics
7359
- };
7360
- }
7361
- var count3, schema;
7362
- var init_vitest = __esm({
7363
- "src/review/evidence/vitest.ts"() {
7364
- "use strict";
7365
- init_paths2();
7366
- init_types2();
7367
- count3 = z26.number().int().nonnegative();
7368
- schema = z26.object({
7369
- success: z26.boolean(),
7370
- numTotalTests: count3,
7371
- numPassedTests: count3,
7372
- numFailedTests: count3,
7373
- numPendingTests: count3,
7374
- numTodoTests: count3,
7375
- numFailedTestSuites: count3,
7376
- testResults: z26.array(
7377
- z26.object({
7378
- name: z26.string().min(1),
7379
- status: z26.enum(["passed", "failed"]),
7380
- message: z26.string().optional(),
7381
- assertionResults: z26.array(
7382
- z26.object({
7383
- fullName: z26.string(),
7384
- status: z26.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
7385
- failureMessages: z26.array(z26.string()).nullable().optional(),
7386
- location: z26.object({ line: count3, column: count3 }).nullable().optional()
7387
- })
7388
- )
7389
- })
7390
- )
7391
- });
7392
- }
7393
- });
7394
-
7395
- // src/review/evidence/sarif.ts
7396
- import { z as z27 } from "zod";
7397
- function parseSarif(raw, sourceRoot) {
7398
- const report = schema2.parse(raw), findings = [], diagnostics = [];
7399
- let active = 0, suppressed = 0, absent = 0, unresolved = 0;
7400
- let executed = report.runs.length > 0, executionFailed = false;
7401
- const reportedCommits = [], reportedCommands = [], reportedTools = [];
7402
- for (const [runIndex, run] of report.runs.entries()) {
7403
- reportedTools.push(run.tool.driver.name);
7404
- reportedCommits.push(
7405
- ...(run.versionControlProvenance ?? []).flatMap((v) => v.revisionId ? [v.revisionId] : [])
7406
- );
7407
- if (!run.invocations?.length) executed = false;
7408
- for (const invocation of run.invocations ?? []) {
7409
- if (invocation.commandLine) reportedCommands.push(invocation.commandLine);
7410
- if (!invocation.executionSuccessful) executionFailed = true;
7411
- for (const notification of invocation.toolExecutionNotifications ?? []) {
7412
- diagnostics.push(
7413
- notification.message.text ?? notification.message.markdown ?? "SARIF tool execution notification"
7414
- );
7415
- if (notification.level === "error") executionFailed = true;
7416
- }
7417
- }
7418
- if (!run.results) {
7419
- diagnostics.push(`Run ${runIndex} omits results; analysis output is unavailable.`);
7420
- executed = false;
7421
- }
7422
- const resolve = (ref, seen = /* @__PURE__ */ new Set()) => {
7423
- if (!ref.uri && ref.index !== void 0) {
7424
- const key = `artifact:${ref.index}`;
7425
- if (seen.has(key)) return null;
7426
- const entry = run.artifacts?.[ref.index]?.location;
7427
- return entry ? resolve(entry, /* @__PURE__ */ new Set([...seen, key])) : null;
7428
- }
7429
- if (!ref.uri) return null;
7430
- if (!ref.uriBaseId) return ref.uri;
7431
- if (seen.has(ref.uriBaseId)) return null;
7432
- const base = run.originalUriBaseIds?.[ref.uriBaseId];
7433
- if (!base) return null;
7434
- const prefix = resolve(base, /* @__PURE__ */ new Set([...seen, ref.uriBaseId]));
7435
- if (!prefix) return null;
7436
- return /^[a-z][a-z0-9+.-]*:/i.test(ref.uri) ? ref.uri : prefix + ref.uri;
7437
- };
7438
- for (const [resultIndex, result] of (run.results ?? []).entries()) {
7439
- const kind = result.kind ?? "fail";
7440
- const isSuppressed = result.suppressions?.some((s) => s.status === "accepted");
7441
- if (result.suppressions?.some((s) => !s.status || s.status === "underReview")) {
7442
- diagnostics.push(`Suppression state unresolved for result ${runIndex}:${resultIndex}.`);
7443
- unresolved++;
7444
- }
7445
- const state = result.baselineState === "absent" ? "absent" : isSuppressed ? "suppressed" : kind === "fail" ? "active" : "informational";
7446
- if (state === "active") active++;
7447
- if (state === "absent") absent++;
7448
- if (state === "suppressed") suppressed++;
7449
- if (["open", "review"].includes(kind) && state === "informational") {
7450
- unresolved++;
7451
- diagnostics.push(`Result ${runIndex}:${resultIndex} needs further analysis or review.`);
7452
- }
7453
- if (kind === "pass" || kind === "notApplicable") continue;
7454
- const rule = result.ruleIndex !== void 0 ? run.tool.driver.rules?.[result.ruleIndex] : run.tool.driver.rules?.find((rule2) => rule2.id === result.ruleId);
7455
- const template = result.message.id ? rule?.messageStrings?.[result.message.id] ?? run.tool.driver.globalMessageStrings?.[result.message.id] : void 0;
7456
- const rawMessage = result.message.text ?? result.message.markdown ?? template?.text ?? template?.markdown;
7457
- if (!rawMessage)
7458
- diagnostics.push(`Message cannot be resolved for result ${runIndex}:${resultIndex}.`);
7459
- const rendered = (rawMessage ?? `Unresolved SARIF message ${result.message.id ?? ""}`).replace(/\{(\d+)\}/g, (match, n) => result.message.arguments?.[Number(n)] ?? match);
7460
- const locations = [];
7461
- for (const entry of [...result.locations ?? [], ...result.relatedLocations ?? []]) {
7462
- const ref = entry.physicalLocation?.artifactLocation;
7463
- const uri = ref ? resolve(ref) : null;
7464
- const file = uri ? evidencePath(uri, sourceRoot, true) : null;
7465
- if (file)
7466
- locations.push({
7467
- file,
7468
- line: entry.physicalLocation?.region?.startLine,
7469
- column: entry.physicalLocation?.region?.startColumn
7470
- });
7471
- else
7472
- diagnostics.push(
7473
- `Unresolved or out-of-checkout location for result ${runIndex}:${resultIndex}.`
7474
- );
7475
- }
7476
- const severity = result.level ?? rule?.defaultConfiguration?.level ?? (kind === "fail" ? "warning" : "none");
7477
- findings.push({
7478
- id: `${runIndex}:${resultIndex}`,
7479
- ruleId: result.ruleId ?? rule?.id,
7480
- ...messagePreview(rendered),
7481
- severity: severity === "none" ? "note" : severity,
7482
- state,
7483
- locations
7484
- });
7485
- }
7486
- }
7487
- if (!report.runs.length) diagnostics.push("SARIF contains no analysis runs.");
7488
- if (executionFailed)
7489
- diagnostics.push("The SARIF tool reported an unsuccessful analysis invocation.");
7490
- return {
7491
- outcome: executionFailed || !report.runs.length || report.runs.some((r) => !r.results) ? "unavailable" : active ? "failed" : "passed",
7492
- findings,
7493
- counts: { active, suppressed, absent, unresolved },
7494
- incomplete: diagnostics.length > 0,
7495
- diagnostics,
7496
- reportedCommits,
7497
- reportedCommands,
7498
- reportedTools,
7499
- executed: executed && !executionFailed
7500
- };
7501
- }
7502
- var index, artifact, message, level, location, schema2;
7503
- var init_sarif = __esm({
7504
- "src/review/evidence/sarif.ts"() {
7505
- "use strict";
7506
- init_paths2();
7507
- init_types2();
7508
- index = z27.number().int().nonnegative();
7509
- artifact = z27.object({
7510
- uri: z27.string().optional(),
7511
- uriBaseId: z27.string().optional(),
7512
- index: index.optional()
7513
- });
7514
- message = z27.object({
7515
- text: z27.string().optional(),
7516
- markdown: z27.string().optional(),
7517
- id: z27.string().optional(),
7518
- arguments: z27.array(z27.string()).optional()
7519
- });
7520
- level = z27.enum(["error", "warning", "note", "none"]);
7521
- location = z27.object({
7522
- physicalLocation: z27.object({
7523
- artifactLocation: artifact.optional(),
7524
- region: z27.object({ startLine: index.optional(), startColumn: index.optional() }).optional()
7525
- }).optional()
7526
- });
7527
- schema2 = z27.object({
7528
- version: z27.literal("2.1.0"),
7529
- runs: z27.array(
7530
- z27.object({
7531
- tool: z27.object({
7532
- driver: z27.object({
7533
- name: z27.string().min(1),
7534
- rules: z27.array(
7535
- z27.object({
7536
- id: z27.string(),
7537
- defaultConfiguration: z27.object({ level: level.optional() }).optional(),
7538
- messageStrings: z27.record(message).optional()
7539
- })
7540
- ).optional(),
7541
- globalMessageStrings: z27.record(message).optional()
7542
- })
7543
- }),
7544
- invocations: z27.array(
7545
- z27.object({
7546
- executionSuccessful: z27.boolean(),
7547
- commandLine: z27.string().optional(),
7548
- toolExecutionNotifications: z27.array(z27.object({ level: level.optional(), message })).optional()
7549
- })
7550
- ).optional(),
7551
- versionControlProvenance: z27.array(z27.object({ revisionId: z27.string().optional() })).optional(),
7552
- originalUriBaseIds: z27.record(artifact).optional(),
7553
- artifacts: z27.array(z27.object({ location: artifact.optional() })).optional(),
7554
- results: z27.array(
7555
- z27.object({
7556
- ruleId: z27.string().optional(),
7557
- ruleIndex: index.optional(),
7558
- message,
7559
- level: level.optional(),
7560
- kind: z27.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
7561
- baselineState: z27.enum(["new", "unchanged", "updated", "absent"]).optional(),
7562
- suppressions: z27.array(
7563
- z27.object({
7564
- kind: z27.enum(["inSource", "external"]),
7565
- status: z27.enum(["accepted", "underReview", "rejected"]).optional()
7566
- })
7567
- ).nullable().optional(),
7568
- locations: z27.array(location).optional(),
7569
- relatedLocations: z27.array(location).optional()
7570
- })
7571
- ).optional()
7572
- })
7573
- )
7574
- });
7575
- }
7576
- });
7577
-
7578
- // src/review/evidence/check.ts
7579
- import { z as z28 } from "zod";
7580
- function parseCheck(raw, sourceRoot) {
7581
- const report = schema3.parse(raw);
7582
- const diagnostics = [], findings = [];
7583
- const counts = { total: report.results.length, passed: 0, failed: 0, skipped: 0 };
7584
- for (const [index2, result] of report.results.entries()) {
7585
- counts[result.status]++;
7586
- const locations = result.locations.flatMap((location2) => {
7587
- const file = evidencePath(location2.file, sourceRoot);
7588
- if (!file) {
7589
- diagnostics.push(
7590
- `Check location is outside the declared checkout or invalid: ${location2.file}`
7591
- );
7592
- return [];
7593
- }
7594
- return [{ ...location2, file }];
7595
- });
7596
- if (result.status === "failed")
7597
- findings.push({
7598
- id: String(index2),
7599
- ...messagePreview([result.name, result.message].filter(Boolean).join("\n")),
7600
- severity: "error",
7601
- state: "active",
7602
- locations
7603
- });
7604
- }
7605
- if (!counts.total)
7606
- diagnostics.push("No check results were recorded; an empty report is not passing evidence.");
7607
- if (counts.skipped)
7608
- diagnostics.push(`${counts.skipped} checks were skipped; verification is incomplete.`);
7609
- return {
7610
- outcome: counts.failed ? "failed" : !counts.total ? "unavailable" : !counts.passed ? "skipped" : "passed",
7611
- counts,
7612
- findings,
7613
- incomplete: diagnostics.length > 0,
7614
- diagnostics,
7615
- ...report.commit ? { reportedCommits: [report.commit] } : {}
7616
- };
7617
- }
7618
- var schema3;
7619
- var init_check = __esm({
7620
- "src/review/evidence/check.ts"() {
7621
- "use strict";
7622
- init_paths2();
7623
- init_types2();
7624
- schema3 = z28.object({
7625
- version: z28.literal(1),
7626
- commit: z28.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
7627
- results: z28.array(
7628
- z28.object({
7629
- name: z28.string().min(1).max(1e3),
7630
- status: z28.enum(["passed", "failed", "skipped"]),
7631
- message: z28.string().max(1e5).optional(),
7632
- locations: z28.array(
7633
- z28.object({
7634
- file: z28.string().min(1).max(4e3),
7635
- line: z28.number().int().positive().optional(),
7636
- column: z28.number().int().positive().optional()
7637
- })
7638
- ).max(100).default([])
7639
- })
7640
- ).max(1e5)
7641
- });
7642
- }
7643
- });
7644
-
7645
- // src/review/evidence.ts
7646
- import path27 from "path";
7647
- import { z as z29 } from "zod";
7648
- function relativeArtifact(root, file) {
7649
- const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
7650
- const normalized = normalizeRepoPath(relative);
7651
- if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
7652
- return normalized;
7653
- }
7654
- function linkFinding(finding, changed, pairs, decisions, freshness) {
7655
- const located = [...new Set(finding.locations.map((l) => l.file))];
7656
- const paired = pairs.filter((pair) => located.includes(pair.test));
7657
- const relatedChangedFiles = located.filter((file) => changed.has(file)).map((file) => ({ file, relationship: "direct" }));
7658
- for (const pair of paired) {
7659
- if (changed.has(pair.source) && !relatedChangedFiles.some((f) => f.file === pair.source))
7660
- relatedChangedFiles.push({
7661
- file: pair.source,
7662
- relationship: "paired-test",
7663
- confidence: pair.confidence
7664
- });
7665
- }
7666
- const allFiles = [.../* @__PURE__ */ new Set([...located, ...paired.map((pair) => pair.source)])];
7667
- const acceptedDecisions = decisions.map(effectiveDecision).filter((d) => d.status === "active" && decisionApproval(d) === "accepted").map((d) => ({ d, viaFiles: matchingPaths(d.files, allFiles) })).filter((match) => match.viaFiles.length).map(({ d, viaFiles }) => {
7668
- const state = freshness[d.id] ?? "unknown", provenance = decisionProvenance(d, state);
7669
- return {
7670
- id: d.id,
7671
- title: d.title,
7672
- owner: provenance.owner,
7673
- freshness: state,
7674
- reviewRequired: provenance.reviewRequired,
7675
- viaFiles
7676
- };
7677
- });
7678
- return {
7679
- ...finding,
7680
- relatedChangedFiles,
7681
- acceptedDecisions: acceptedDecisions.slice(0, 5),
7682
- totalAcceptedDecisions: acceptedDecisions.length
7683
- };
7684
- }
7685
- async function collectReviewEvidence(root, manifests, changedFiles, decisions, decisionFreshness = {}, reviewedHead) {
7686
- const [headHash, workingTree] = await Promise.all([
7687
- reviewedHead ?? getCurrentGitHash(root),
7688
- getWorkingTree(root)
7689
- ]);
7690
- const output = {
7691
- version: 1,
7692
- scope: "committed",
7693
- headHash,
7694
- status: "unavailable",
7695
- checks: [],
7696
- diagnostics: [],
7697
- workingTree,
7698
- summary: { passed: 0, failed: 0, skipped: 0, unavailable: 0, stale: 0, unknown: 0 },
7699
- hint: "Evidence describes the recorded check runs for a commit, not uncommitted edits or complete test coverage. Commands and CI provenance are imported assertions, not authenticated execution. File and test-pair associations identify relevant decisions; they do not prove a decision was violated. Missing checks must be declared in the manifest to be reported."
7700
- };
7701
- const seen = /* @__PURE__ */ new Set(), changed = new Set(changedFiles);
7702
- let pairs;
7703
- if (manifests.length > 10)
7704
- output.diagnostics.push("Only the first 10 evidence manifests were imported.");
7705
- for (const supplied of manifests.slice(0, 10)) {
7706
- let manifest, raw;
7707
- try {
7708
- manifest = relativeArtifact(root, supplied);
7709
- raw = z29.object({ version: z29.literal(1), checks: z29.array(z29.unknown()) }).parse(await readStoreJson(root, manifest));
7710
- } catch (error) {
7711
- output.diagnostics.push(`${supplied}: ${String(error)}`);
7712
- continue;
7713
- }
7714
- if (!raw.checks.length) output.diagnostics.push(`${manifest}: no checks declared.`);
7715
- for (const [index2, item] of raw.checks.entries()) {
7716
- if (output.checks.length >= 50) {
7717
- output.diagnostics.push("Only the first 50 checks were imported.");
7718
- break;
7719
- }
7720
- const input = checkSchema2.safeParse(item);
7721
- if (!input.success) {
7722
- output.diagnostics.push(`${manifest} check ${index2}: ${input.error.message}`);
7723
- continue;
7724
- }
7725
- const check = input.data;
7726
- if (seen.has(check.id)) {
7727
- output.diagnostics.push(
7728
- `Duplicate check id ${check.id}; give different runs distinct ids.`
7729
- );
7730
- continue;
7731
- }
7732
- seen.add(check.id);
7733
- const result = {
7734
- id: check.id,
7735
- kind: check.kind,
7736
- tool: check.tool,
7737
- command: check.command,
7738
- commit: check.commit?.toLowerCase() ?? null,
7739
- workingTreeClean: check.workingTreeClean ?? null,
7740
- source: check.source ?? null,
7741
- manifest,
7742
- report: check.report ?? null,
7743
- outcome: "unavailable",
7744
- freshness: "unknown",
7745
- findings: [],
7746
- totalFindings: 0,
7747
- counts: {},
7748
- incomplete: false,
7749
- diagnostics: [],
7750
- truncated: false,
7751
- ...check.environment ? { environment: check.environment } : {}
7752
- };
7753
- output.checks.push(result);
7754
- if (check.workingTreeClean !== true)
7755
- result.diagnostics.push(
7756
- "The check's working tree was dirty or not recorded; its results cannot be attributed to the claimed commit alone."
7757
- );
7758
- else if (result.commit && headHash !== "unknown")
7759
- result.freshness = result.commit === headHash.toLowerCase() ? "current" : "stale";
7760
- else result.diagnostics.push("The tested commit or reviewed HEAD is unknown.");
7761
- if (check.status !== "completed") {
7762
- result.outcome = check.status;
7763
- result.diagnostics.push(
7764
- check.reason ?? "No reason was recorded for this skipped or unavailable check."
7765
- );
7766
- continue;
7767
- }
7768
- try {
7769
- if (!check.report) throw new Error("Completed check has no report artifact.");
7770
- if (check.report.format !== "mason-check-json" && check.kind === "tests" !== (check.report.format === "vitest-json"))
7771
- throw new Error(
7772
- "Test checks require vitest-json or mason-check-json; analysis checks require sarif or mason-check-json."
7773
- );
7774
- const file = relativeArtifact(root, check.report.path);
7775
- const report = await readStoreJson(root, file);
7776
- if (report === null) throw new Error(`Report artifact is missing: ${file}`);
7777
- const sourceRoot = check.sourceRoot ?? root;
7778
- if (!path27.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot))
7779
- throw new Error(
7780
- "sourceRoot must identify the absolute checkout root on the check runner."
7781
- );
7782
- const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
7783
- result.outcome = parsed.outcome;
7784
- result.counts = parsed.counts;
7785
- result.incomplete = parsed.incomplete;
7786
- result.diagnostics.push(...parsed.diagnostics);
7787
- result.reportedCommands = parsed.reportedCommands;
7788
- result.reportedTools = parsed.reportedTools;
7789
- if (parsed.reportedCommits?.some(
7790
- (commit) => !result.commit || commit.toLowerCase() !== result.commit
7791
- )) {
7792
- result.freshness = "unknown";
7793
- result.incomplete = true;
7794
- result.diagnostics.push(
7795
- "Report revision metadata conflicts with, or cannot be tied to, the manifest's tested commit."
7796
- );
7797
- }
7798
- if (check.exitCode == null && !parsed.executed) {
7799
- result.incomplete = true;
7800
- result.diagnostics.push(
7801
- "Neither an exit code nor successful analysis invocation was recorded; completion cannot be confirmed."
7802
- );
7803
- }
7804
- if (check.exitCode != null && check.exitCode !== 0 && result.outcome === "passed") {
7805
- result.outcome = "failed";
7806
- result.diagnostics.push(
7807
- `Check command exited ${check.exitCode} despite a report with no active failures.`
7808
- );
7809
- }
7810
- if (check.kind === "tests" && pairs === void 0) {
7811
- try {
7812
- pairs = (await buildTestMap(root)).paired;
7813
- } catch (error) {
7814
- pairs = [];
7815
- output.diagnostics.push(`Test pairing unavailable: ${String(error)}`);
7816
- }
7817
- }
7818
- result.totalFindings = parsed.findings.length;
7819
- const testPairs = check.kind === "tests" ? pairs ?? [] : [];
7820
- const relevant = /* @__PURE__ */ new Set([
7821
- ...changed,
7822
- ...testPairs.filter((p) => changed.has(p.source)).map((p) => p.test)
7823
- ]);
7824
- const touchesChange = (f) => f.locations.some((l) => relevant.has(l.file));
7825
- parsed.findings.sort(
7826
- (a, b) => Number(b.state === "active") - Number(a.state === "active") || Number(touchesChange(b)) - Number(touchesChange(a))
7827
- );
7828
- result.findings = parsed.findings.slice(0, MAX_FINDINGS).map((f) => linkFinding(f, changed, testPairs, decisions, decisionFreshness));
7829
- result.truncated = parsed.findings.length > MAX_FINDINGS || result.findings.some(
7830
- (f) => f.truncated || f.totalAcceptedDecisions > f.acceptedDecisions.length
7831
- );
7832
- } catch (error) {
7833
- result.outcome = "unavailable";
7834
- result.incomplete = true;
7835
- result.diagnostics.push(String(error));
7836
- }
7837
- }
7838
- }
7839
- if (!output.checks.length) output.diagnostics.push("No readable checks were imported.");
7840
- for (const check of output.checks) {
7841
- output.summary[check.outcome]++;
7842
- if (check.freshness !== "current") output.summary[check.freshness]++;
7843
- }
7844
- if (output.checks.some((c) => c.outcome === "failed" && c.freshness === "current"))
7845
- output.status = "failed";
7846
- else if (!output.checks.length || output.checks.every((c) => c.outcome === "unavailable"))
7847
- output.status = "unavailable";
7848
- else if (output.diagnostics.length || output.checks.some((c) => c.outcome !== "passed" || c.freshness !== "current" || c.incomplete))
7849
- output.status = "incomplete";
7850
- else output.status = "passed";
7851
- return output;
7852
- }
7853
- function summarizeEvidence(evidence) {
7854
- const previews = (values, limit) => values.slice(0, limit).map((value) => value.slice(0, 2e3));
7855
- const checks = evidence.checks.slice(0, 10).map((check) => {
7856
- const findings = check.findings.slice(0, 5).map((finding) => ({
7857
- ...finding,
7858
- ruleId: finding.ruleId?.slice(0, 200),
7859
- locations: finding.locations.slice(0, 5),
7860
- totalLocations: finding.locations.length,
7861
- relatedChangedFiles: finding.relatedChangedFiles.slice(0, 5),
7862
- totalRelatedChangedFiles: finding.relatedChangedFiles.length,
7863
- acceptedDecisions: finding.acceptedDecisions.map((decision) => ({
7864
- ...decision,
7865
- viaFiles: decision.viaFiles.slice(0, 5)
7866
- })),
7867
- truncated: finding.truncated || finding.locations.length > 5 || finding.relatedChangedFiles.length > 5 || (finding.ruleId?.length ?? 0) > 200 || finding.acceptedDecisions.some((d) => d.viaFiles.length > 5)
7868
- }));
7869
- return {
7870
- ...check,
7871
- findings,
7872
- reportedCommands: check.reportedCommands ? previews(check.reportedCommands, 5) : void 0,
7873
- reportedTools: check.reportedTools ? previews(check.reportedTools, 5) : void 0,
7874
- diagnostics: previews(check.diagnostics, 10),
7875
- truncated: check.truncated || check.findings.length > 5 || findings.some((f) => f.truncated) || check.diagnostics.length > 10 || check.diagnostics.some((d) => d.length > 2e3) || [check.reportedCommands ?? [], check.reportedTools ?? []].some(
7876
- (values) => values.length > 5 || values.some((v) => v.length > 2e3)
7877
- )
7878
- };
7879
- });
7880
- return {
7881
- ...evidence,
7882
- checks,
7883
- diagnostics: previews(evidence.diagnostics, 20),
7884
- truncated: evidence.checks.length > 10 || evidence.diagnostics.length > 20 || evidence.diagnostics.some((d) => d.length > 2e3) || checks.some((c) => c.truncated)
7885
- };
7886
- }
7887
- var checkSchema2;
7888
- var init_evidence2 = __esm({
7889
- "src/review/evidence.ts"() {
7890
- "use strict";
7891
- init_storage();
7892
- init_paths();
7893
- init_snapshot();
7894
- init_drift();
7895
- init_test_map();
7896
- init_provenance();
7897
- init_vitest();
7898
- init_sarif();
7899
- init_check();
7900
- init_types2();
7901
- checkSchema2 = z29.object({
7902
- id: z29.string().min(1).max(100),
7903
- kind: z29.enum([
7904
- "tests",
7905
- "static-analysis",
7906
- "security",
7907
- "complexity",
7908
- "duplication",
7909
- "documentation"
7910
- ]),
7911
- tool: z29.string().min(1).max(200),
7912
- command: z29.string().min(1).max(2e3),
7913
- commit: z29.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
7914
- workingTreeClean: z29.boolean().optional(),
7915
- source: z29.string().max(2e3).optional(),
7916
- sourceRoot: z29.string().min(1).max(4e3).optional(),
7917
- status: z29.enum(["completed", "skipped", "unavailable"]).default("completed"),
7918
- reason: z29.string().min(1).max(2e3).optional(),
7919
- exitCode: z29.number().int().nullable().optional(),
7920
- environment: z29.object({
7921
- toolVersion: z29.string().min(1).max(200),
7922
- runtime: z29.string().min(1).max(200).optional(),
7923
- platform: z29.string().min(1).max(200).optional(),
7924
- configuration: z29.array(z29.string().min(1).max(4e3)).max(100).optional()
7925
- }).optional(),
7926
- report: z29.object({
7927
- format: z29.enum(["vitest-json", "sarif", "mason-check-json"]),
7928
- path: z29.string().min(1).max(4e3)
7929
- }).optional()
7930
- });
7931
- }
7932
- });
7933
-
7934
- // src/review/review.ts
7935
- import fs23 from "fs/promises";
7936
- import path28 from "path";
7937
- import { execFile as execFile4 } from "child_process";
7938
- import { promisify as promisify4 } from "util";
7939
- async function resolveMergeBase(resolvedRoot, base, head) {
7940
- try {
7941
- const { stdout } = await exec2("git", ["merge-base", base, head], {
7942
- cwd: resolvedRoot
7943
- });
7944
- return stdout.trim() || null;
7945
- } catch {
7946
- return null;
7947
- }
7948
- }
7949
- async function defaultBase(resolvedRoot) {
7950
- for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
7951
- try {
7952
- await exec2("git", ["rev-parse", "--verify", "--quiet", ref], {
7953
- cwd: resolvedRoot
7954
- });
7955
- return ref;
7956
- } catch {
7957
- }
7958
- }
7959
- return null;
7960
- }
7961
- function anchorsTouched(record, changedFiles) {
7962
- return matchingPaths(record.files, changedFiles);
7963
- }
7964
- async function computeReview(rootDir, base, options = {}) {
7965
- const resolvedRoot = path28.resolve(rootDir);
7966
- const head = await getCurrentGitHash(resolvedRoot);
7967
- if (head === "unknown") return null;
7968
- const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
7969
- if (!mergeBase) return null;
7970
- const changes = await getChangesWithStatus(resolvedRoot, mergeBase, head);
7971
- if (changes === null) return null;
7972
- const changedFiles = touchedPaths(changes);
7973
- const report = {
7974
- version: 1,
7975
- root: resolvedRoot,
7976
- base,
7977
- mergeBase,
7978
- changedFiles,
7979
- missingPartners: [],
7980
- touchedDecisions: [],
7981
- historyAvailable: true,
7982
- truncated: false
7983
- };
7984
- const store = await loadDecisionStore(resolvedRoot);
7985
- report.diagnostics = store.diagnostics;
7986
- const decisionDrift = await computeDecisionDrift(resolvedRoot, store.records);
7987
- if (options.evidence !== void 0) {
7988
- report.evidence = await collectReviewEvidence(
7989
- resolvedRoot,
7990
- options.evidence,
7991
- changedFiles,
7992
- store.records,
7993
- decisionDrift.freshness,
7994
- head
7995
- );
7996
- if (store.diagnostics.length) {
7997
- report.evidence.diagnostics.push(
7998
- "Invalid decision records make knowledge associations incomplete; consult review diagnostics."
7999
- );
8000
- if (report.evidence.status === "passed") report.evidence.status = "incomplete";
8001
- }
8002
- }
8003
- const finalize = async () => {
8004
- if (report.evidence && await getCurrentGitHash(resolvedRoot) !== head) {
8005
- report.evidence.diagnostics.push(
8006
- "HEAD changed during the review; rerun to obtain consistent change and knowledge associations."
8007
- );
8008
- for (const check of report.evidence.checks) check.freshness = "unknown";
8009
- report.evidence.summary.stale = 0;
8010
- report.evidence.summary.unknown = report.evidence.checks.length;
8011
- if (report.evidence.status !== "unavailable") report.evidence.status = "incomplete";
8012
- }
8013
- return report;
8014
- };
8015
- if (changedFiles.length === 0) return finalize();
8016
- let analyzed = changedFiles;
8017
- if (changedFiles.length > MAX_ANALYZED_FILES) {
8018
- analyzed = changedFiles.slice(0, MAX_ANALYZED_FILES);
8019
- report.truncated = true;
8020
- }
8021
- const partners = await findMissingPartners(
8022
- resolvedRoot,
8023
- analyzed,
8024
- async (relPath) => {
8025
- try {
8026
- await fs23.access(path28.join(resolvedRoot, relPath));
8027
- return true;
8028
- } catch {
8029
- return false;
8030
- }
8031
- },
8032
- changedFiles
8033
- );
8034
- if (partners === null) {
8035
- report.historyAvailable = false;
8036
- } else {
8037
- report.missingPartners = partners;
8038
- }
8039
- const decisions = store.records;
8040
- for (const record of decisions) {
8041
- if (record.status !== "active") continue;
8042
- const effective = effectiveDecision(record);
8043
- const touched = anchorsTouched(effective, changedFiles);
8044
- const proposalTouched = effective !== record ? anchorsTouched(record, changedFiles) : [];
8045
- if (touched.length > 0 || proposalTouched.length > 0) {
8046
- const { pendingProposal, ...knowledge } = decisionKnowledge(
8047
- record,
8048
- decisionDrift.freshness?.[record.id] ?? "unknown",
8049
- decisionDrift.pendingProposals?.[record.id]?.freshness ?? "unknown"
8050
- );
8051
- report.touchedDecisions.push({
8052
- ...knowledge,
8053
- ...pendingProposal ? { pendingProposal: { ...pendingProposal, touchedFiles: proposalTouched } } : {},
8054
- id: record.id,
8055
- anchors: effective.files,
8056
- freshness: decisionDrift.freshness?.[record.id] ?? "unknown",
8057
- touchedFiles: touched
8058
- });
8059
- }
8060
- }
8061
- return finalize();
8062
- }
8063
- var exec2, MAX_ANALYZED_FILES;
8064
- var init_review = __esm({
8065
- "src/review/review.ts"() {
8066
- "use strict";
8067
- init_drift();
8068
- init_provenance();
8069
- init_decisions();
8070
- init_paths();
8071
- init_drift2();
8072
- init_cochange();
8073
- init_evidence2();
8074
- init_snapshot();
8075
- exec2 = promisify4(execFile4);
8076
- MAX_ANALYZED_FILES = 50;
8077
- }
8078
- });
8079
-
8080
- // src/mcp/onboarding.ts
8081
- async function auditSummary(root) {
8082
- try {
8083
- const report = await computeAudit(root);
8084
- if (!report)
8085
- return {
8086
- status: "no-context-files",
8087
- reason: "No README.md or agent instruction files found to audit."
8088
- };
8089
- if (!report.gitAvailable)
8090
- return {
8091
- status: "unavailable",
8092
- reason: "Audit needs readable Git history to verify documentation claims.",
8093
- docs: report.docs
8094
- };
8095
- return {
8096
- ...report,
8097
- status: "complete",
8098
- issues: report.issues.slice(0, MAX_FINDINGS2),
8099
- advisories: report.advisories.slice(0, MAX_FINDINGS2),
8100
- suppressedAdvisories: (report.suppressedAdvisories ?? []).slice(0, MAX_FINDINGS2),
8101
- counts: {
8102
- issues: report.issues.length,
8103
- advisories: report.advisories.length,
8104
- suppressedAdvisories: report.suppressedAdvisories?.length ?? 0
8105
- },
8106
- truncated: report.issues.length > MAX_FINDINGS2 || report.advisories.length > MAX_FINDINGS2 || (report.suppressedAdvisories?.length ?? 0) > MAX_FINDINGS2
8107
- };
8108
- } catch (error) {
8109
- return { status: "unavailable", reason: reason(error) };
8110
- }
8111
- }
8112
- async function reviewSummary(root, requestedBase, evidence) {
8113
- const workingTree = await getWorkingTree(root);
8114
- const scope = "committed";
8115
- try {
8116
- const base = requestedBase ?? await defaultBase(root);
8117
- if (!base)
8118
- return {
8119
- status: "unavailable",
8120
- scope,
8121
- workingTree,
8122
- reason: "No default review base resolves. Pass base to mason_init, or run mason-review --base <ref>."
8123
- };
8124
- const report = await computeReview(root, base, { evidence });
8125
- if (!report)
8126
- return {
8127
- status: "unavailable",
8128
- scope,
8129
- base,
8130
- workingTree,
8131
- reason: "The review base, merge base, or committed diff could not be read."
8132
- };
8133
- return {
8134
- ...report,
8135
- scope,
8136
- workingTree,
8137
- ...report.evidence ? { evidence: summarizeEvidence(report.evidence) } : {},
8138
- status: !report.historyAvailable ? "unavailable" : report.changedFiles.length ? "complete" : "no-changes",
8139
- ...!report.historyAvailable ? { reason: "Co-change history could not be read; review findings are incomplete." } : {},
8140
- changedFiles: report.changedFiles.slice(0, MAX_FINDINGS2),
8141
- missingPartners: report.missingPartners.slice(0, MAX_FINDINGS2),
8142
- touchedDecisions: report.touchedDecisions.slice(0, MAX_FINDINGS2),
8143
- counts: {
8144
- changedFiles: report.changedFiles.length,
8145
- missingPartners: report.missingPartners.length,
8146
- touchedDecisions: report.touchedDecisions.length
8147
- },
8148
- truncated: report.truncated || [report.changedFiles, report.missingPartners, report.touchedDecisions].some(
8149
- (list) => list.length > MAX_FINDINGS2
8150
- ),
8151
- hint: "Reviews cover merge-base..HEAD. Uncommitted paths are reported separately in workingTree; they have not been reviewed."
8152
- };
8153
- } catch (error) {
8154
- return { status: "unavailable", scope, workingTree, reason: reason(error) };
8155
- }
8156
- }
8157
- async function inspectOnboarding(root, base, evidence) {
8158
- const [audit, review, map, decisions] = await Promise.all([
8159
- auditSummary(root),
8160
- reviewSummary(root, base, evidence),
8161
- inspectSnapshot(root),
8162
- loadDecisionStore(root)
8163
- ]);
8164
- return {
8165
- audit,
8166
- review,
8167
- map: { status: map.status },
8168
- decisions: {
8169
- active: decisions.records.filter((record) => record.status === "active").length,
8170
- ...Object.fromEntries(
8171
- ["accepted", "proposed", "unreviewed"].map((approval) => [
8172
- approval,
8173
- decisions.records.filter(
8174
- (record) => record.status === "active" && decisionApproval(effectiveDecision(record)) === approval
8175
- ).length
8176
- ])
8177
- ),
8178
- pendingProposals: decisions.records.filter((record) => effectiveDecision(record) !== record).length
8179
- },
8180
- diagnostics: [...map.diagnostics, ...decisions.diagnostics]
8181
- };
8182
- }
8183
- var MAX_FINDINGS2, reason;
8184
- var init_onboarding = __esm({
8185
- "src/mcp/onboarding.ts"() {
8186
- "use strict";
8187
- init_audit();
8188
- init_review();
8189
- init_drift();
8190
- init_snapshot();
8191
- init_provenance();
8192
- init_evidence2();
8193
- init_decisions();
8194
- MAX_FINDINGS2 = 20;
8195
- reason = (error) => error instanceof Error ? error.message : String(error);
8196
- }
8197
- });
8198
-
8199
7486
  // src/setup/status.ts
8200
7487
  var status_exports = {};
8201
7488
  __export(status_exports, {
8202
7489
  setupStatus: () => setupStatus,
8203
7490
  summarizeActivation: () => summarizeActivation
8204
7491
  });
8205
- import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
8206
7492
  async function setupStatus(dir) {
8207
7493
  const ws = await workspace(dir);
8208
7494
  const activation = await effectiveSetup(ws.root);
@@ -8215,11 +7501,29 @@ async function setupStatus(dir) {
8215
7501
  }))
8216
7502
  );
8217
7503
  const pending = partial.filter((item) => item.receipt !== null).map((item) => item.host);
7504
+ const configuration = {};
7505
+ await Promise.all(
7506
+ ["codex", "claude"].map(async (host) => {
7507
+ try {
7508
+ const inspected = await inspectIntegration(ws.root, host);
7509
+ configuration[host] = {
7510
+ compatible: inspected.compatible,
7511
+ disabled: inspected.disabled || inspected.mcpDisabled
7512
+ };
7513
+ } catch (error) {
7514
+ configuration[host] = {
7515
+ compatible: false,
7516
+ diagnostic: error instanceof Error ? error.message : String(error)
7517
+ };
7518
+ }
7519
+ })
7520
+ );
8218
7521
  return {
8219
7522
  version: 1,
8220
7523
  status: pending.length ? "incomplete" : "not-configured",
8221
7524
  hosts: {},
8222
- next: pending.length ? "Setup did not finish. Rerun mason setup --host " + pending[0] + " to resume using the retained original evidence." : "Project hooks are inactive until local setup. Run mason setup --host codex or --host claude."
7525
+ configuration,
7526
+ next: pending.length ? "Setup did not finish. Rerun mason setup --host " + pending[0] + " to resume local activation; use mason init if shared configuration needs updating." : "Project hooks are inactive. Run mason setup --host codex or --host claude for compatible shared configuration; a maintainer uses mason init to prepare missing or incompatible configuration."
8223
7527
  };
8224
7528
  }
8225
7529
  const hosts2 = {};
@@ -8228,25 +7532,11 @@ async function setupStatus(dir) {
8228
7532
  for (const host of ["codex", "claude"]) {
8229
7533
  const entry = setup.hosts[host];
8230
7534
  if (!entry) continue;
8231
- const instructions = await instructionEdits(ws.root, host);
8232
- const instructionsCurrent = instructions.every((edit) => edit.before === edit.after);
8233
- const config = await inspectHostConfig(ws.root, host, void 0);
8234
- const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual3({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host));
8235
- const expectedHooks = hookConfig(host, hookCommand(host)).hooks;
8236
- const currentHooks = isDeepStrictEqual3(config.hooks, expectedHooks);
8237
- const compatibleHooks = Object.fromEntries(
8238
- Object.entries(config.hooks).map(([event, groups]) => [
8239
- event,
8240
- groups.map((group) => ({
8241
- ...group,
8242
- hooks: group.hooks.map((handler) => ({
8243
- ...handler,
8244
- command: expectedHooks.SessionStart[0].hooks[0].command
8245
- }))
8246
- }))
8247
- ])
8248
- );
8249
- const hooks = isDeepStrictEqual3(compatibleHooks, expectedHooks) && !config.disabled;
7535
+ const config = await inspectIntegration(ws.root, host);
7536
+ const instructionsCurrent = config.instructionsCurrent;
7537
+ const mcp = config.mcpCompatible && !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint;
7538
+ const currentHooks = config.hooksCurrent;
7539
+ const hooks = config.hooksCompatible && !config.disabled;
8250
7540
  const local = await loadSetupReceipt(ws.root, ws.directory, host);
8251
7541
  const configured = activation.root !== ws.root || local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
8252
7542
  const observation = await readObservation(
@@ -8265,9 +7555,9 @@ async function setupStatus(dir) {
8265
7555
  const observedEvents = complete?.events ?? sessions[0]?.events ?? [];
8266
7556
  const pending = [];
8267
7557
  if (!installed.available) pending.push(installed.message);
8268
- if (!mcp || !hooks || !instructionsCurrent || !configured)
7558
+ if (!mcp || !hooks || !config.instructionCompatible || !configured)
8269
7559
  pending.push(
8270
- "Rerun setup to reconcile project configuration; inspect any disabled host settings."
7560
+ "Run mason setup to activate existing configuration; incompatible shared configuration needs mason init and review. Inspect any disabled host settings."
8271
7561
  );
8272
7562
  if (!observation?.contextCalls)
8273
7563
  pending.push(
@@ -8281,19 +7571,20 @@ async function setupStatus(dir) {
8281
7571
  pending.push(
8282
7572
  "The latest automation attempt did not establish verification. Run mason check and inspect its diagnostics."
8283
7573
  );
8284
- const healthy = installed.available && mcp && hooks && instructionsCurrent && configured && automation.status !== "unavailable";
7574
+ const healthy = installed.available && mcp && hooks && config.instructionCompatible && configured && automation.status !== "unavailable";
8285
7575
  hosts2[host] = {
8286
7576
  status: healthy ? complete && observation?.contextCalls ? "active" : "pending" : "attention",
8287
7577
  runtime: installed.available ? "global (" + installed.version + ")" : "unavailable-on-path",
8288
7578
  mcp: mcp ? "configured" : "changed",
8289
- instructions: instructionsCurrent ? "current" : "changed",
7579
+ instructions: instructionsCurrent ? "current" : config.instructionCompatible ? "compatible" : "incompatible",
8290
7580
  hookConfiguration: hooks ? currentHooks ? "configured" : "compatible" : "disabled-or-changed",
8291
7581
  observedEvents,
8292
7582
  contextCalls: observation?.contextCalls ?? 0,
7583
+ verificationScope: automation.scope,
8293
7584
  verificationStatus: automation.status === "current" ? automation.verificationStatus ?? "unavailable" : "unavailable",
8294
7585
  pending,
8295
- updates: hooks && !currentHooks ? [
8296
- "A newer hook template is available. Run mason setup --host " + host + " to update it when convenient; review and commit the configuration changes."
7586
+ updates: config.compatible && (!currentHooks || !instructionsCurrent) ? [
7587
+ "Updated integration templates are available. A maintainer can run mason init --host " + host + " to update it when convenient; review and commit the configuration changes."
8297
7588
  ] : []
8298
7589
  };
8299
7590
  }
@@ -8322,10 +7613,10 @@ function summarizeActivation(status) {
8322
7613
  `${host}: ${state.status}`,
8323
7614
  ` Runtime: ${state.runtime}; MCP: ${state.mcp}; instructions: ${state.instructions}.`,
8324
7615
  ` Hooks: ${state.hookConfiguration}; observed: ${state.observedEvents.join(", ") || "none"}.`,
8325
- ` Task context requests: ${state.contextCalls}; verification: ${state.verificationStatus}.`,
8326
- ...state.pending.map((message2) => " Next: " + message2)
7616
+ ` Task context requests: ${state.contextCalls}; verification: ${state.verificationStatus} (${state.verificationScope}).`,
7617
+ ...state.pending.map((message) => " Next: " + message)
8327
7618
  );
8328
- lines.push(...state.updates.map((message2) => " Optional: " + message2));
7619
+ lines.push(...state.updates.map((message) => " Optional: " + message));
8329
7620
  }
8330
7621
  if ("decisionRecords" in status) lines.push(`Decision records: ${status.decisionRecords}.`);
8331
7622
  if (status.next) lines.push(status.next);
@@ -8338,7 +7629,6 @@ var init_status = __esm({
8338
7629
  init_store();
8339
7630
  init_runtime();
8340
7631
  init_decisions();
8341
- init_adapters();
8342
7632
  init_model();
8343
7633
  init_config2();
8344
7634
  init_launcher();
@@ -8349,8 +7639,10 @@ var init_status = __esm({
8349
7639
  // src/setup/setup.ts
8350
7640
  var setup_exports = {};
8351
7641
  __export(setup_exports, {
7642
+ initializeProject: () => initializeProject,
8352
7643
  selectHost: () => selectHost,
8353
7644
  setupProject: () => setupProject,
7645
+ summarizeInitialization: () => summarizeInitialization,
8354
7646
  summarizeSetup: () => summarizeSetup
8355
7647
  });
8356
7648
  import { randomUUID as randomUUID6 } from "crypto";
@@ -8370,27 +7662,45 @@ async function setupProject(dir, options = {}) {
8370
7662
  options.progress?.step("Checking project configuration");
8371
7663
  const ws = await workspace(dir);
8372
7664
  const host = await selectHost(ws.root, options.host);
7665
+ if ((await git3(
7666
+ ws.root,
7667
+ "ls-files",
7668
+ "--",
7669
+ ".mason/local",
7670
+ ws.directory,
7671
+ ".mason/reports/setup-lock"
7672
+ )).trim())
7673
+ throw new Error("Mason local state must be untracked before setup can activate this checkout.");
8373
7674
  return withLock(ws.root, ".mason/reports/setup-lock", async () => {
8374
7675
  const existing = await loadSetup(ws.root);
8375
7676
  const previousReceipt = await loadSetupReceipt(ws.root, ws.directory, host);
8376
7677
  const installed = await installedCommand();
8377
7678
  if (!installed.available) throw new Error(installed.message);
8378
- const instructions = await instructionEdits(ws.root, host);
8379
- const mcp = await mcpEdit(ws.root, host);
7679
+ const configured = await inspectIntegration(ws.root, host);
7680
+ if (!configured.compatible)
7681
+ throw new Error(
7682
+ "Shared Mason configuration is missing or incompatible. A maintainer should run mason init --host " + host + " and review/commit the changes. Local setup did not edit shared files."
7683
+ );
7684
+ const privatePaths = [SETUP_PATH, ws.directory + "/setup-" + host + ".json"];
7685
+ const ignored = (await git3(ws.root, "check-ignore", "--no-index", "--", ...privatePaths).catch(() => "")).trim().split("\n");
7686
+ if (!privatePaths.every((file) => ignored.includes(file)))
7687
+ throw new Error(
7688
+ "Mason local state must be ignored. A maintainer should run mason init and commit the ignore rules."
7689
+ );
7690
+ const instructions = configured.instructions;
7691
+ const sharedInputs = [
7692
+ ...instructions.map((edit) => ({ path: edit.path, before: edit.before })),
7693
+ { path: mcpPath(host), before: await readText(ws.root, mcpPath(host)) },
7694
+ { path: configPath3(host), before: await readText(ws.root, configPath3(host)) }
7695
+ ];
8380
7696
  const hooks = await hookEdits(ws.root, host);
8381
- const ancillary = await ancillaryEdits(ws.root);
8382
- const edits = [...ancillary, ...instructions, mcp, ...hooks];
7697
+ const edits = hooks.slice(1);
8383
7698
  const setupBefore = await readText(ws.root, SETUP_PATH);
8384
- const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after)).mcp);
8385
- const desiredHooks = hookConfig(host, hookCommand(host)).hooks;
8386
- const configuredHook = desiredHooks.SessionStart[0].hooks[0].command;
8387
- const ownership = await ownershipEdit(ws.root, [...instructions, mcp, hooks[0]], {
8388
- [hooks[0].path]: configuredHook
8389
- });
7699
+ const mcpFingerprint = hash(configured.mcp);
8390
7700
  const fingerprint = hash({
8391
7701
  command: "mason",
8392
7702
  mcp: mcpFingerprint,
8393
- hooks: desiredHooks,
7703
+ hooks: configured.hooks,
8394
7704
  instructions: instructions.map((edit) => edit.path)
8395
7705
  });
8396
7706
  const previous = existing?.hosts[host];
@@ -8404,25 +7714,21 @@ async function setupProject(dir, options = {}) {
8404
7714
  mcpFingerprint,
8405
7715
  instructions: instructions.map((e) => e.path)
8406
7716
  };
8407
- options.progress?.step("Auditing documentation and preserving original findings");
8408
- const initial = await automate(ws.root, { event: "turn_start" });
8409
- options.progress?.step("Reviewing project context and committed changes");
8410
- const findings = await inspectOnboarding(ws.root, options.base, options.evidence);
8411
7717
  const receiptPath = ws.directory + "/setup-" + host + ".json";
8412
- const initialReportPath = previousReceipt?.initialReportPath ?? initial.report.reportPath;
8413
- const initialBaselinePaths = previousReceipt?.initialBaselinePaths ?? initial.report.baselinePaths;
8414
- options.progress?.step(`Configuring ${host === "codex" ? "Codex" : "Claude Code"} integration`);
7718
+ const legacyEvidence = previousReceipt?.initialReportPath ? {
7719
+ initialReportPath: previousReceipt.initialReportPath,
7720
+ initialBaselinePaths: previousReceipt.initialBaselinePaths ?? []
7721
+ } : {};
7722
+ options.progress?.step(`Activating ${host === "codex" ? "Codex" : "Claude Code"} integration`);
8415
7723
  await writeStoreJson(ws.root, receiptPath, {
8416
7724
  version: 1,
8417
7725
  host,
8418
7726
  status: "installing",
8419
- initialReportPath,
8420
- initialBaselinePaths,
7727
+ ...legacyEvidence,
8421
7728
  root: ws.root,
8422
7729
  revision
8423
7730
  });
8424
7731
  const changedFiles = [];
8425
- if (await applyEdit(ws.root, ownership)) changedFiles.push(ownership.path);
8426
7732
  for (const edit of edits) if (await applyEdit(ws.root, edit)) changedFiles.push(edit.path);
8427
7733
  if (await applyEdit(ws.root, {
8428
7734
  path: SETUP_PATH,
@@ -8430,25 +7736,15 @@ async function setupProject(dir, options = {}) {
8430
7736
  after: JSON.stringify(setup, null, 2) + "\n"
8431
7737
  }))
8432
7738
  changedFiles.push(SETUP_PATH);
8433
- if (await applyEdit(
8434
- ws.root,
8435
- await completedHookOwnership(ws.root, hooks[0].path, configuredHook)
8436
- ) && !changedFiles.includes(ownership.path))
8437
- changedFiles.push(ownership.path);
8438
- options.progress?.step("Checking configuration and retained findings");
8439
- const checked = await automate(ws.root, { event: "turn_start" });
8440
- const configured = await inspectHostConfig(ws.root, host, void 0);
8441
- const stats = await configureUsefulness(ws.root, true, { onlyIfUnset: true }).catch(() => ({
8442
- enabled: null,
8443
- storage: ".mason/local/usefulness",
8444
- note: "Local stats unavailable. Inspect mason stats --json, or use mason stats --disable to reset the preference. Existing observations were retained."
8445
- }));
7739
+ options.progress?.step("Checking agent configuration");
7740
+ for (const input of sharedInputs)
7741
+ if (await readText(ws.root, input.path) !== input.before)
7742
+ throw new Error(input.path + " changed during setup; inspect it and retry.");
8446
7743
  await writeStoreJson(ws.root, receiptPath, {
8447
7744
  version: 1,
8448
7745
  host,
8449
7746
  status: "configured",
8450
- initialReportPath,
8451
- initialBaselinePaths,
7747
+ ...legacyEvidence,
8452
7748
  root: ws.root,
8453
7749
  revision,
8454
7750
  configuredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8456,50 +7752,91 @@ async function setupProject(dir, options = {}) {
8456
7752
  return {
8457
7753
  version: 1,
8458
7754
  action: "setup",
7755
+ mode: "activate",
8459
7756
  status: "configured",
8460
7757
  host,
8461
7758
  root: ws.root,
8462
7759
  runtime: { kind: "global", command: "mason", version: installed.version },
8463
7760
  changedFiles,
8464
- initialReportPath,
8465
- findings,
8466
- reportPath: checked.report.reportPath,
7761
+ receiptPath,
8467
7762
  activation: await setupStatus(ws.root),
8468
- stats,
8469
7763
  next: configured.disabled || configured.mcpDisabled ? "Mason hooks or MCP are disabled in project configuration. Review that setting before activation." : host === "codex" ? "Review/trust this project's MCP configuration and hooks in Codex (/hooks in the CLI), then start a new session and give it a normal task." : "Approve the project MCP server in Claude Code, then start a new session and give it a normal task."
8470
7764
  };
8471
7765
  });
8472
7766
  }
8473
7767
  function summarizeSetup(result) {
8474
- const audit = result.findings.audit;
8475
7768
  return [
8476
7769
  `Mason configured for ${result.host}.`,
8477
7770
  " MCP server and lifecycle hooks use mason from PATH.",
8478
- " Project instructions updated; original audit evidence retained.",
7771
+ " Existing shared configuration validated; only local activation state was written.",
8479
7772
  ` ${result.changedFiles.length} files changed.`,
8480
- result.stats.enabled === null ? result.stats.note : result.stats.enabled ? "Local stats enabled: metadata stays in .mason/local/usefulness/; nothing is uploaded. View with mason stats; disable with mason stats --disable." : "Local stats remain disabled by your saved preference. Enable with mason stats --enable.",
8481
- `Initial audit: ${audit.status}${"counts" in audit ? `; ${audit.counts.issues} issues, ${audit.counts.advisories + audit.counts.suppressedAdvisories} advisories` : ""}.`,
8482
- ..."issues" in audit ? audit.issues.slice(0, 3).map((f) => " " + f.message) : [],
8483
- `Original evidence: ${result.initialReportPath}`,
8484
7773
  "Activation: " + result.activation.status + ".",
8485
7774
  result.next,
8486
7775
  "If the assistant cannot find mason, restart the desktop app so it picks up PATH.",
8487
7776
  "After the task finishes, run mason status. Configuration alone does not establish activation."
8488
7777
  ].join("\n");
8489
7778
  }
7779
+ async function initializeProject(dir, options = {}) {
7780
+ const ws = await workspace(dir);
7781
+ const host = await selectHost(ws.root, options.host);
7782
+ const run = async (apply) => {
7783
+ const instructions = await instructionEdits(ws.root, host);
7784
+ const mcp = await mcpEdit(ws.root, host);
7785
+ const hooks = await hookEdits(ws.root, host);
7786
+ const edits = [...await ancillaryEdits(ws.root), ...instructions, mcp, hooks[0]];
7787
+ const configuredHook = hookConfig(host, hookCommand(host)).hooks.SessionStart[0].hooks[0].command;
7788
+ const ownership = await ownershipEdit(ws.root, [...instructions, mcp, hooks[0]], {
7789
+ [hooks[0].path]: configuredHook
7790
+ });
7791
+ if (apply) {
7792
+ options.progress?.step("Preparing shared agent configuration");
7793
+ await applyEdit(ws.root, ownership);
7794
+ for (const edit of edits) await applyEdit(ws.root, edit);
7795
+ await applyEdit(
7796
+ ws.root,
7797
+ await completedHookOwnership(ws.root, hooks[0].path, configuredHook)
7798
+ );
7799
+ }
7800
+ return {
7801
+ version: 1,
7802
+ action: "init",
7803
+ status: options.dryRun ? "preview" : "initialized",
7804
+ host,
7805
+ root: ws.root,
7806
+ dryRun: !!options.dryRun,
7807
+ changes: edits.filter((edit) => edit.before !== edit.after).map((edit) => ({
7808
+ path: edit.path,
7809
+ action: edit.before === null ? "create" : "update"
7810
+ })),
7811
+ next: "Review and commit shared configuration, then run mason setup --host " + host + " in each developer's checkout."
7812
+ };
7813
+ };
7814
+ if (options.dryRun) return run(false);
7815
+ if ((await git3(ws.root, "ls-files", "--", ".mason/local", ".mason/reports/setup-lock")).trim())
7816
+ throw new Error("Mason local bookkeeping must be untracked before initialization.");
7817
+ return withLock(ws.root, ".mason/reports/setup-lock", () => run(true));
7818
+ }
7819
+ function summarizeInitialization(result) {
7820
+ return [
7821
+ `Mason initialization${result.dryRun ? " preview" : ""} for ${result.host}.`,
7822
+ ...result.changes.map(
7823
+ (change) => ` ${result.dryRun ? "Would " : ""}${change.action}: ${change.path}`
7824
+ ),
7825
+ ...!result.changes.length ? ["Shared configuration is current; no changes needed."] : [],
7826
+ result.dryRun ? "Preview only; no files were changed." : result.next
7827
+ ].join("\n");
7828
+ }
8490
7829
  var init_setup = __esm({
8491
7830
  "src/setup/setup.ts"() {
8492
7831
  "use strict";
8493
- init_usefulness();
8494
7832
  init_evidence();
8495
7833
  init_store();
8496
- init_runtime();
8497
- init_onboarding();
8498
7834
  init_storage();
8499
7835
  init_files2();
8500
7836
  init_config2();
8501
- init_launcher();
8502
7837
  init_adapters();
7838
+ init_install();
7839
+ init_launcher();
8503
7840
  init_model();
8504
7841
  init_status();
8505
7842
  init_ownership();
@@ -8508,25 +7845,25 @@ var init_setup = __esm({
8508
7845
 
8509
7846
  // src/distribution/path.ts
8510
7847
  import fs24 from "fs/promises";
8511
- import os3 from "os";
8512
- import path29 from "path";
8513
- import { execFile as execFile5 } from "child_process";
8514
- import { promisify as promisify5 } from "util";
8515
- import { createHash as createHash6, randomUUID as randomUUID7 } from "crypto";
8516
- import { z as z30 } from "zod";
7848
+ import os4 from "os";
7849
+ import path26 from "path";
7850
+ import { execFile as execFile3 } from "child_process";
7851
+ import { promisify as promisify3 } from "util";
7852
+ import { createHash as createHash7, randomUUID as randomUUID7 } from "crypto";
7853
+ import { z as z27 } from "zod";
8517
7854
  async function configurePath(bin, previous, save, options = {}) {
8518
7855
  const env = options.env ?? process.env, platform = options.platform ?? process.platform;
8519
7856
  const changes = [...previous];
8520
7857
  const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
8521
7858
  const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some(
8522
- (entry) => path29.posix.isAbsolute(entry) && path29.posix.resolve(entry) === path29.posix.resolve(bin)
7859
+ (entry) => path26.posix.isAbsolute(entry) && path26.posix.resolve(entry) === path26.posix.resolve(bin)
8523
7860
  );
8524
- const setupHint = "To connect a project, run one of these inside its directory:\n Codex: mason setup --host codex\n Claude Code: mason setup --host claude";
8525
- const message2 = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
8526
- const manual = (reason2) => ({
7861
+ const setupHint = "To prepare a new project for review, run one of these inside its directory (use mason setup to activate existing team configuration):\n Codex: mason init --host codex\n Claude Code: mason init --host claude";
7862
+ const message = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
7863
+ const manual = (reason) => ({
8527
7864
  status: "manual",
8528
7865
  changes,
8529
- message: `${reason2}
7866
+ message: `${reason}
8530
7867
  ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
8531
7868
  `}${setupHint}`
8532
7869
  });
@@ -8544,19 +7881,19 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
8544
7881
  }
8545
7882
  await windowsPath("add", bin, current);
8546
7883
  }
8547
- return { status: "configured", changes, message: message2 };
7884
+ return { status: "configured", changes, message };
8548
7885
  }
8549
- const shell = path29.basename(env.SHELL ?? os3.userInfo().shell ?? "");
8550
- const userHome = options.userHome ?? os3.homedir();
7886
+ const shell = path26.basename(env.SHELL ?? os4.userInfo().shell ?? "");
7887
+ const userHome = options.userHome ?? os4.homedir();
8551
7888
  let profiles2;
8552
7889
  if (!["bash", "zsh", "sh"].includes(shell))
8553
7890
  return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
8554
- if (env.MASON_PROFILE) profiles2 = [path29.resolve(env.MASON_PROFILE)];
8555
- else if (shell === "zsh") profiles2 = [path29.join(env.ZDOTDIR || userHome, ".zshrc")];
8556
- else if (shell === "sh") profiles2 = [path29.join(userHome, ".profile")];
7891
+ if (env.MASON_PROFILE) profiles2 = [path26.resolve(env.MASON_PROFILE)];
7892
+ else if (shell === "zsh") profiles2 = [path26.join(env.ZDOTDIR || userHome, ".zshrc")];
7893
+ else if (shell === "sh") profiles2 = [path26.join(userHome, ".profile")];
8557
7894
  else {
8558
7895
  const candidates = [".bash_profile", ".bash_login", ".profile"].map(
8559
- (name) => path29.join(userHome, name)
7896
+ (name) => path26.join(userHome, name)
8560
7897
  );
8561
7898
  let login = candidates[2];
8562
7899
  for (const file of candidates)
@@ -8570,15 +7907,15 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
8570
7907
  login = file;
8571
7908
  break;
8572
7909
  }
8573
- profiles2 = [path29.join(userHome, ".bashrc"), login];
7910
+ profiles2 = [path26.join(userHome, ".bashrc"), login];
8574
7911
  }
8575
- const marker = `# >>> mason PATH ${createHash6("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
7912
+ const marker = `# >>> mason PATH ${createHash7("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
8576
7913
  for (const profile of profiles2) {
8577
7914
  const stat = await fs24.lstat(profile).catch((error) => {
8578
7915
  if (hasErrorCode(error, "ENOENT")) return null;
8579
7916
  throw error;
8580
7917
  });
8581
- const file = stat?.isSymbolicLink() ? await fs24.realpath(profile) : path29.resolve(profile);
7918
+ const file = stat?.isSymbolicLink() ? await fs24.realpath(profile) : path26.resolve(profile);
8582
7919
  const original = await fs24.readFile(file).catch((error) => {
8583
7920
  if (hasErrorCode(error, "ENOENT") && !stat) return null;
8584
7921
  throw error;
@@ -8598,7 +7935,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
8598
7935
  changes.push({ kind: "profile", file, block, created: original === null });
8599
7936
  await save(changes);
8600
7937
  }
8601
- await fs24.mkdir(path29.dirname(file), { recursive: true });
7938
+ await fs24.mkdir(path26.dirname(file), { recursive: true });
8602
7939
  const handle = await fs24.open(file, original === null ? "ax+" : "a+", 384);
8603
7940
  try {
8604
7941
  if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0)))
@@ -8609,7 +7946,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
8609
7946
  await handle.close();
8610
7947
  }
8611
7948
  }
8612
- return { status: "configured", changes, message: message2 };
7949
+ return { status: "configured", changes, message };
8613
7950
  } catch (error) {
8614
7951
  return manual(
8615
7952
  `Could not configure PATH: ${error instanceof Error ? error.message : String(error)}`
@@ -8635,10 +7972,10 @@ async function removePath(changes) {
8635
7972
  if (!stat.isFile()) throw new Error("profile type changed");
8636
7973
  const bytes = await fs24.readFile(change.file), text2 = bytes.toString("utf8");
8637
7974
  if (!Buffer.from(text2).equals(bytes)) throw new Error("profile encoding changed");
8638
- const index2 = text2.indexOf(change.block);
8639
- if (index2 < 0 || text2.indexOf(change.block, index2 + change.block.length) >= 0)
7975
+ const index = text2.indexOf(change.block);
7976
+ if (index < 0 || text2.indexOf(change.block, index + change.block.length) >= 0)
8640
7977
  throw new Error("PATH block was edited or removed");
8641
- const prefix = text2.slice(0, index2), suffix = text2.slice(index2 + change.block.length);
7978
+ const prefix = text2.slice(0, index), suffix = text2.slice(index + change.block.length);
8642
7979
  const separator = prefix && suffix && !prefix.endsWith("\n") ? change.block.startsWith("\r\n") ? "\r\n" : "\n" : "";
8643
7980
  const next = Buffer.from(prefix + separator + suffix);
8644
7981
  const temporary = change.file + ".mason-" + randomUUID7();
@@ -8708,25 +8045,25 @@ try {
8708
8045
  ],
8709
8046
  { windowsHide: true, timeout: 15e3, maxBuffer: 1024 * 1024 }
8710
8047
  );
8711
- return z30.object({ value: z30.string().nullable(), kind: z30.string().nullable() }).parse(JSON.parse(stdout.replace(/^\uFEFF/, "").trim()));
8048
+ return z27.object({ value: z27.string().nullable(), kind: z27.string().nullable() }).parse(JSON.parse(stdout.replace(/^\uFEFF/, "").trim()));
8712
8049
  }
8713
8050
  var execute2, pathChangeSchema, quote, normalizeWindowsEntry, hasWindowsEntry;
8714
8051
  var init_path = __esm({
8715
8052
  "src/distribution/path.ts"() {
8716
8053
  "use strict";
8717
8054
  init_validation();
8718
- execute2 = promisify5(execFile5);
8719
- pathChangeSchema = z30.discriminatedUnion("kind", [
8720
- z30.object({
8721
- kind: z30.literal("profile"),
8722
- file: z30.string(),
8723
- block: z30.string(),
8724
- created: z30.boolean()
8055
+ execute2 = promisify3(execFile3);
8056
+ pathChangeSchema = z27.discriminatedUnion("kind", [
8057
+ z27.object({
8058
+ kind: z27.literal("profile"),
8059
+ file: z27.string(),
8060
+ block: z27.string(),
8061
+ created: z27.boolean()
8725
8062
  }),
8726
- z30.object({ kind: z30.literal("windows"), entry: z30.string(), created: z30.boolean() })
8063
+ z27.object({ kind: z27.literal("windows"), entry: z27.string(), created: z27.boolean() })
8727
8064
  ]);
8728
8065
  quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
8729
- normalizeWindowsEntry = (value) => path29.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
8066
+ normalizeWindowsEntry = (value) => path26.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
8730
8067
  hasWindowsEntry = (value, entry, env = process.env) => {
8731
8068
  const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
8732
8069
  const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
@@ -8748,11 +8085,11 @@ __export(state_exports, {
8748
8085
  standaloneLocation: () => standaloneLocation,
8749
8086
  updatePolicySchema: () => updatePolicySchema
8750
8087
  });
8751
- import os4 from "os";
8752
- import path30 from "path";
8088
+ import os5 from "os";
8089
+ import path27 from "path";
8753
8090
  import { fileURLToPath } from "url";
8754
8091
  import fs25 from "fs/promises";
8755
- import { z as z31 } from "zod";
8092
+ import { z as z28 } from "zod";
8756
8093
  async function readInstallation(home) {
8757
8094
  const raw = await readStoreJson(home, "install.json");
8758
8095
  if (!raw)
@@ -8765,20 +8102,20 @@ async function readInstallation(home) {
8765
8102
  return record;
8766
8103
  }
8767
8104
  function standaloneLocation() {
8768
- const bundle = path30.resolve(path30.dirname(fileURLToPath(import.meta.url)), "../..");
8769
- const home = path30.dirname(path30.dirname(bundle));
8770
- if (path30.dirname(bundle) !== path30.join(home, "versions") || !/^[a-f0-9]{24}$/.test(path30.basename(bundle))) {
8105
+ const bundle = path27.resolve(path27.dirname(fileURLToPath(import.meta.url)), "../..");
8106
+ const home = path27.dirname(path27.dirname(bundle));
8107
+ if (path27.dirname(bundle) !== path27.join(home, "versions") || !/^[a-f0-9]{24}$/.test(path27.basename(bundle))) {
8771
8108
  return null;
8772
8109
  }
8773
8110
  return { home, bundle };
8774
8111
  }
8775
8112
  async function currentInstallation() {
8776
- const location2 = standaloneLocation();
8777
- if (!location2)
8113
+ const location = standaloneLocation();
8114
+ if (!location)
8778
8115
  throw new Error(
8779
8116
  "Run this command from a standalone installation. npm users can upgrade with npm."
8780
8117
  );
8781
- const { home, bundle } = location2;
8118
+ const { home, bundle } = location;
8782
8119
  const record = await readInstallation(home);
8783
8120
  return { home, bundle, record };
8784
8121
  }
@@ -8799,42 +8136,42 @@ var init_state = __esm({
8799
8136
  "use strict";
8800
8137
  init_storage();
8801
8138
  init_path();
8802
- releaseVersion = z31.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
8803
- bundleId = z31.string().regex(/^[a-f0-9]{24}$/);
8804
- updatePolicySchema = z31.object({
8805
- enabled: z31.boolean(),
8806
- revision: z31.string(),
8139
+ releaseVersion = z28.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
8140
+ bundleId = z28.string().regex(/^[a-f0-9]{24}$/);
8141
+ updatePolicySchema = z28.object({
8142
+ enabled: z28.boolean(),
8143
+ revision: z28.string(),
8807
8144
  pinnedVersion: releaseVersion.optional(),
8808
- mirror: z31.string().optional()
8145
+ mirror: z28.string().optional()
8809
8146
  });
8810
- recordSchema3 = z31.object({
8811
- format: z31.literal(1),
8812
- home: z31.string(),
8813
- bin: z31.string(),
8147
+ recordSchema3 = z28.object({
8148
+ format: z28.literal(1),
8149
+ home: z28.string(),
8150
+ bin: z28.string(),
8814
8151
  current: bundleId,
8815
8152
  version: releaseVersion,
8816
- launchers: z31.record(z31.string()),
8817
- previousLaunchers: z31.record(z31.string()).optional(),
8818
- pathChanges: z31.array(pathChangeSchema).optional(),
8153
+ launchers: z28.record(z28.string()),
8154
+ previousLaunchers: z28.record(z28.string()).optional(),
8155
+ pathChanges: z28.array(pathChangeSchema).optional(),
8819
8156
  updates: updatePolicySchema.optional(),
8820
- previous: z31.object({ id: bundleId, version: releaseVersion }).optional(),
8821
- pending: z31.object({ id: bundleId, version: releaseVersion, policyRevision: z31.string() }).optional()
8157
+ previous: z28.object({ id: bundleId, version: releaseVersion }).optional(),
8158
+ pending: z28.object({ id: bundleId, version: releaseVersion, policyRevision: z28.string() }).optional()
8822
8159
  });
8823
- defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path30.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path30.join(os4.homedir(), ".local/share/mason"));
8160
+ defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path27.join(process.env.LOCALAPPDATA ?? os5.homedir(), "Mason") : path27.join(os5.homedir(), ".local/share/mason"));
8824
8161
  }
8825
8162
  });
8826
8163
 
8827
8164
  // src/distribution/bundle.ts
8828
8165
  import fs26 from "fs/promises";
8829
- import path31 from "path";
8830
- import { createHash as createHash7 } from "crypto";
8831
- import { z as z32 } from "zod";
8166
+ import path28 from "path";
8167
+ import { createHash as createHash8 } from "crypto";
8168
+ import { z as z29 } from "zod";
8832
8169
  async function readBundle(root) {
8833
8170
  const bytes = await fs26.readFile(await storePath(root, "bundle.json"));
8834
8171
  const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
8835
8172
  const windows = manifest.target.startsWith("win32-");
8836
8173
  for (const file of Object.keys(manifest.files)) {
8837
- if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path31.isAbsolute(file)) {
8174
+ if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path28.isAbsolute(file)) {
8838
8175
  throw new Error("Unsafe path in Mason bundle: " + file);
8839
8176
  }
8840
8177
  }
@@ -8879,15 +8216,15 @@ var init_bundle = __esm({
8879
8216
  "win32-arm64",
8880
8217
  "win32-x64"
8881
8218
  ];
8882
- digest2 = z32.string().regex(/^[a-f0-9]{64}$/);
8883
- bundleSchema = z32.object({
8884
- format: z32.literal(1),
8885
- version: z32.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
8886
- target: z32.enum(targets),
8887
- nodeVersion: z32.string(),
8888
- files: z32.record(digest2)
8219
+ digest2 = z29.string().regex(/^[a-f0-9]{64}$/);
8220
+ bundleSchema = z29.object({
8221
+ format: z29.literal(1),
8222
+ version: z29.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
8223
+ target: z29.enum(targets),
8224
+ nodeVersion: z29.string(),
8225
+ files: z29.record(digest2)
8889
8226
  }).strict();
8890
- sha256 = (bytes) => createHash7("sha256").update(bytes).digest("hex");
8227
+ sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
8891
8228
  }
8892
8229
  });
8893
8230
 
@@ -8912,10 +8249,10 @@ var init_trust2 = __esm({
8912
8249
  // src/distribution/runtime.ts
8913
8250
  import fs27 from "fs/promises";
8914
8251
  import { unlinkSync } from "fs";
8915
- import os5 from "os";
8916
- import path32 from "path";
8252
+ import os6 from "os";
8253
+ import path29 from "path";
8917
8254
  import { randomUUID as randomUUID8 } from "crypto";
8918
- import { z as z33 } from "zod";
8255
+ import { z as z30 } from "zod";
8919
8256
  async function pruneUnusedVersions(home) {
8920
8257
  const versions = await storePath(home, "versions");
8921
8258
  const candidates = [];
@@ -8950,7 +8287,7 @@ async function pruneUnusedVersions(home) {
8950
8287
  const raw = await readStoreJson(home, `runtimes/${name}`);
8951
8288
  if (raw === null) continue;
8952
8289
  const lease = leaseSchema.parse(raw);
8953
- if (lease.host !== os5.hostname()) return;
8290
+ if (lease.host !== os6.hostname()) return;
8954
8291
  try {
8955
8292
  process.kill(lease.pid, 0);
8956
8293
  keep.add(lease.bundle);
@@ -8985,19 +8322,19 @@ var init_runtime2 = __esm({
8985
8322
  init_state();
8986
8323
  init_bundle();
8987
8324
  init_trust2();
8988
- leaseSchema = z33.object({
8989
- pid: z33.number().int().positive(),
8990
- host: z33.string(),
8991
- bundle: z33.string().regex(/^[a-f0-9]{24}$/)
8325
+ leaseSchema = z30.object({
8326
+ pid: z30.number().int().positive(),
8327
+ host: z30.string(),
8328
+ bundle: z30.string().regex(/^[a-f0-9]{24}$/)
8992
8329
  });
8993
8330
  }
8994
8331
  });
8995
8332
 
8996
8333
  // src/distribution/sessions.ts
8997
8334
  import fs28 from "fs/promises";
8998
- import os6 from "os";
8335
+ import os7 from "os";
8999
8336
  import { randomUUID as randomUUID9 } from "crypto";
9000
- import { z as z34 } from "zod";
8337
+ import { z as z31 } from "zod";
9001
8338
  async function hasRunningMcp(home) {
9002
8339
  const directory3 = await storePath(home, "sessions");
9003
8340
  const files = await fs28.readdir(directory3).catch((error) => {
@@ -9011,7 +8348,7 @@ async function hasRunningMcp(home) {
9011
8348
  const raw = await readStoreJson(home, relative);
9012
8349
  if (raw === null) continue;
9013
8350
  const lease = leaseSchema2.parse(raw);
9014
- if (lease.host !== os6.hostname()) return true;
8351
+ if (lease.host !== os7.hostname()) return true;
9015
8352
  try {
9016
8353
  process.kill(lease.pid, 0);
9017
8354
  return true;
@@ -9027,7 +8364,7 @@ async function hasRunningMcp(home) {
9027
8364
  }
9028
8365
  async function registerMcp(home) {
9029
8366
  const name = `sessions/${randomUUID9()}.json`;
9030
- await writeStoreJson(home, name, { pid: process.pid, host: os6.hostname() });
8367
+ await writeStoreJson(home, name, { pid: process.pid, host: os7.hostname() });
9031
8368
  return async () => {
9032
8369
  await fs28.rm(await storePath(home, name), { force: true }).catch(() => {
9033
8370
  });
@@ -9039,7 +8376,7 @@ var init_sessions = __esm({
9039
8376
  "use strict";
9040
8377
  init_validation();
9041
8378
  init_storage();
9042
- leaseSchema2 = z34.object({ pid: z34.number().int().positive(), host: z34.string() });
8379
+ leaseSchema2 = z31.object({ pid: z31.number().int().positive(), host: z31.string() });
9043
8380
  }
9044
8381
  });
9045
8382
 
@@ -9055,10 +8392,10 @@ __export(install_exports2, {
9055
8392
  upgradeStandalone: () => upgradeStandalone
9056
8393
  });
9057
8394
  import fs29 from "fs/promises";
9058
- import os7 from "os";
9059
- import path33 from "path";
9060
- import { execFile as execFile6, spawn } from "child_process";
9061
- import { promisify as promisify6 } from "util";
8395
+ import os8 from "os";
8396
+ import path30 from "path";
8397
+ import { execFile as execFile4, spawn } from "child_process";
8398
+ import { promisify as promisify4 } from "util";
9062
8399
  import { randomUUID as randomUUID10 } from "crypto";
9063
8400
  async function replace(file, content2, mode = 384) {
9064
8401
  const temp = file + ".tmp-" + randomUUID10();
@@ -9072,7 +8409,7 @@ async function replace(file, content2, mode = 384) {
9072
8409
  async function installStandalone(source, progress2, options = {}) {
9073
8410
  progress2?.step("Checking installation files and location");
9074
8411
  const bundle = await verifyBundle(source);
9075
- const selectedHome = path33.resolve(defaultHome());
8412
+ const selectedHome = path30.resolve(defaultHome());
9076
8413
  if (!options.stageOnly) await fs29.mkdir(selectedHome, { recursive: true });
9077
8414
  const home = await fs29.realpath(selectedHome);
9078
8415
  if (options.stageOnly) {
@@ -9083,9 +8420,9 @@ async function installStandalone(source, progress2, options = {}) {
9083
8420
  );
9084
8421
  const destination = await storePath(home, "versions/" + bundle.manifestHash.slice(0, 24));
9085
8422
  await copyBundle(home, source, destination, bundle);
9086
- const { stdout } = await promisify6(execFile6)(
9087
- path33.join(destination, process.platform === "win32" ? "node.exe" : "node"),
9088
- [path33.join(destination, "app/dist/mason.js"), "--version"],
8423
+ const { stdout } = await promisify4(execFile4)(
8424
+ path30.join(destination, process.platform === "win32" ? "node.exe" : "node"),
8425
+ [path30.join(destination, "app/dist/mason.js"), "--version"],
9089
8426
  { timeout: 3e4, windowsHide: true, env: { ...process.env, MASON_NO_AUTO_UPDATE: "1" } }
9090
8427
  );
9091
8428
  if (stdout.trim() !== bundle.manifest.version)
@@ -9110,14 +8447,14 @@ async function installLocked2(home, source, bundle, progress2, options, alreadyS
9110
8447
  if (!previous)
9111
8448
  for (const file of await fs29.readdir(home)) {
9112
8449
  if (file === ".install-lock") continue;
9113
- const directory3 = path33.join(home, file);
8450
+ const directory3 = path30.join(home, file);
9114
8451
  const stat = await fs29.lstat(directory3);
9115
8452
  if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs29.readdir(directory3)).length) {
9116
8453
  throw new Error("Mason installation directory is not empty and has no ownership receipt.");
9117
8454
  }
9118
8455
  }
9119
- const selectedBin = path33.resolve(
9120
- process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path33.join(home, "bin") : path33.join(os7.homedir(), ".local/bin"))
8456
+ const selectedBin = path30.resolve(
8457
+ process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path30.join(home, "bin") : path30.join(os8.homedir(), ".local/bin"))
9121
8458
  );
9122
8459
  await fs29.mkdir(selectedBin, { recursive: true });
9123
8460
  const bin = await fs29.realpath(selectedBin);
@@ -9146,10 +8483,10 @@ $previousToken = $env:MASON_UNINSTALL_TOKEN
9146
8483
  $token = [Guid]::NewGuid().ToString()
9147
8484
  $env:MASON_UNINSTALL_TOKEN = $token
9148
8485
  try {
9149
- & ${psQuote(path33.join(destination, "node.exe"))} ${psQuote(path33.join(destination, "app/dist/mason.js"))} @args
8486
+ & ${psQuote(path30.join(destination, "node.exe"))} ${psQuote(path30.join(destination, "app/dist/mason.js"))} @args
9150
8487
  $code = $LASTEXITCODE
9151
8488
  if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
9152
- if ([IO.File]::ReadAllText(${psQuote(path33.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
8489
+ if ([IO.File]::ReadAllText(${psQuote(path30.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
9153
8490
  Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
9154
8491
  Write-Output ${psQuote("Removed standalone installation: " + home)}
9155
8492
  }
@@ -9159,7 +8496,7 @@ finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
9159
8496
  `
9160
8497
  } : {
9161
8498
  mason: `#!/bin/sh
9162
- exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destination, "app/dist/mason.js"))} "$@"
8499
+ exec ${shQuote(path30.join(destination, "node"))} ${shQuote(path30.join(destination, "app/dist/mason.js"))} "$@"
9163
8500
  `
9164
8501
  };
9165
8502
  for (const [name, content2] of Object.entries(launchers)) {
@@ -9252,14 +8589,14 @@ exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destinat
9252
8589
  };
9253
8590
  }
9254
8591
  async function copyBundle(home, source, destination, bundle) {
9255
- const exists = await fs29.lstat(destination).then(
8592
+ const exists2 = await fs29.lstat(destination).then(
9256
8593
  () => true,
9257
8594
  (error) => {
9258
8595
  if (hasErrorCode(error, "ENOENT")) return false;
9259
8596
  throw error;
9260
8597
  }
9261
8598
  );
9262
- if (exists) {
8599
+ if (exists2) {
9263
8600
  await verifyBundle(destination, bundle.manifestHash);
9264
8601
  return;
9265
8602
  }
@@ -9380,7 +8717,7 @@ async function upgradeStandalone(version, progress2) {
9380
8717
  return 0;
9381
8718
  }
9382
8719
  const windows = process.platform === "win32";
9383
- const script = path33.join(bundle, windows ? "install.ps1" : "install.sh");
8720
+ const script = path30.join(bundle, windows ? "install.ps1" : "install.sh");
9384
8721
  progress2?.stop();
9385
8722
  const code = await new Promise((resolve, reject) => {
9386
8723
  const child = spawn(
@@ -9479,10 +8816,10 @@ __export(updates_exports, {
9479
8816
  updateStatus: () => updateStatus
9480
8817
  });
9481
8818
  import fs30 from "fs/promises";
9482
- import path34 from "path";
8819
+ import path31 from "path";
9483
8820
  import { spawn as spawn2 } from "child_process";
9484
8821
  import { randomUUID as randomUUID11 } from "crypto";
9485
- import { z as z35 } from "zod";
8822
+ import { z as z32 } from "zod";
9486
8823
  async function boundedDownload(url) {
9487
8824
  const response = await fetch(url, { signal: AbortSignal.timeout(15e3), redirect: "follow" });
9488
8825
  if (!response.ok || !response.body)
@@ -9552,7 +8889,7 @@ async function runInstaller(home, version, digest3, revision) {
9552
8889
  const record = await readInstallation(home);
9553
8890
  const bundle = await storePath(home, "versions/" + record.current);
9554
8891
  await verifyBundle(bundle);
9555
- const windows = process.platform === "win32", script = path34.join(bundle, windows ? "install.ps1" : "install.sh");
8892
+ const windows = process.platform === "win32", script = path31.join(bundle, windows ? "install.ps1" : "install.sh");
9556
8893
  await new Promise((resolve, reject) => {
9557
8894
  const child = spawn2(
9558
8895
  windows ? "powershell.exe" : "sh",
@@ -9615,7 +8952,7 @@ async function runAutomaticUpdate(home, dependencies = { fetchRelease: fetchVeri
9615
8952
  } catch (error) {
9616
8953
  status.error = error instanceof Error ? error.message : String(error);
9617
8954
  }
9618
- if (await fs30.access(path34.join(home, "install.json")).then(
8955
+ if (await fs30.access(path31.join(home, "install.json")).then(
9619
8956
  () => true,
9620
8957
  () => false
9621
8958
  ))
@@ -9634,8 +8971,8 @@ async function scheduleAutomaticUpdate() {
9634
8971
  return;
9635
8972
  const bundle = await storePath(home, "versions/" + record.current);
9636
8973
  const child = spawn2(
9637
- path34.join(bundle, process.platform === "win32" ? "node.exe" : "node"),
9638
- [path34.join(bundle, "app/dist/mason.js"), "internal-update"],
8974
+ path31.join(bundle, process.platform === "win32" ? "node.exe" : "node"),
8975
+ [path31.join(bundle, "app/dist/mason.js"), "internal-update"],
9639
8976
  { cwd: home, env: process.env, stdio: "ignore", detached: true, windowsHide: true }
9640
8977
  );
9641
8978
  child.on("error", () => {
@@ -9685,8 +9022,8 @@ async function startMcpWithUpdates(start) {
9685
9022
  const session = await prepareStandaloneMcp(current.home);
9686
9023
  if (session.bundle !== current.bundle) {
9687
9024
  const child = spawn2(
9688
- path34.join(session.bundle, process.platform === "win32" ? "node.exe" : "node"),
9689
- [path34.join(session.bundle, "app/dist/mason-mcp.js"), ...process.argv.slice(2)],
9025
+ path31.join(session.bundle, process.platform === "win32" ? "node.exe" : "node"),
9026
+ [path31.join(session.bundle, "app/dist/mason-mcp.js"), ...process.argv.slice(2)],
9690
9027
  { stdio: "inherit", windowsHide: true }
9691
9028
  );
9692
9029
  const interrupt = () => {
@@ -9741,24 +9078,24 @@ var init_updates = __esm({
9741
9078
  CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
9742
9079
  RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
9743
9080
  releaseBase = "https://github.com/adrianczuczka/mason/releases";
9744
- releaseListSchema = z35.array(
9745
- z35.object({
9746
- tag_name: z35.string(),
9747
- draft: z35.boolean(),
9748
- prerelease: z35.boolean(),
9749
- published_at: z35.string().nullable()
9081
+ releaseListSchema = z32.array(
9082
+ z32.object({
9083
+ tag_name: z32.string(),
9084
+ draft: z32.boolean(),
9085
+ prerelease: z32.boolean(),
9086
+ published_at: z32.string().nullable()
9750
9087
  })
9751
9088
  );
9752
- updateManifestSchema = z35.object({
9753
- format: z35.literal(1),
9754
- version: z35.string().regex(/^\d+\.\d+\.\d+$/),
9755
- publishedAt: z35.string().datetime(),
9756
- assets: z35.record(z35.string().regex(/^[a-f0-9]{64}$/))
9089
+ updateManifestSchema = z32.object({
9090
+ format: z32.literal(1),
9091
+ version: z32.string().regex(/^\d+\.\d+\.\d+$/),
9092
+ publishedAt: z32.string().datetime(),
9093
+ assets: z32.record(z32.string().regex(/^[a-f0-9]{64}$/))
9757
9094
  }).strict();
9758
- statusSchema2 = z35.object({
9759
- lastAttempt: z35.number(),
9760
- availableVersion: z35.string().optional(),
9761
- error: z35.string().optional()
9095
+ statusSchema2 = z32.object({
9096
+ lastAttempt: z32.number(),
9097
+ availableVersion: z32.string().optional(),
9098
+ error: z32.string().optional()
9762
9099
  });
9763
9100
  }
9764
9101
  });
@@ -9784,34 +9121,34 @@ function createProgress(options = {}) {
9784
9121
  const interactive = options.interactive ?? (!!process.stderr.isTTY && process.env.TERM !== "dumb" && !process.env.CI);
9785
9122
  const columns = options.columns ?? (() => process.stderr.columns || 80);
9786
9123
  const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
9787
- let message2 = "", started = 0, frame = 0;
9124
+ let message = "", started = 0, frame = 0;
9788
9125
  let timer;
9789
9126
  const elapsed = () => {
9790
9127
  const seconds = Math.floor((Date.now() - started) / 1e3);
9791
9128
  return seconds ? ` (${seconds}s)` : "";
9792
9129
  };
9793
9130
  const render = (prefix) => {
9794
- const text2 = `${prefix} ${message2}${elapsed()}`;
9131
+ const text2 = `${prefix} ${message}${elapsed()}`;
9795
9132
  const width = Math.max(1, columns() - 1);
9796
9133
  write("\r\x1B[2K" + (text2.length > width ? text2.slice(0, width - 1) + "\u2026" : text2));
9797
9134
  };
9798
9135
  const stop = (success = true) => {
9799
9136
  if (timer) clearInterval(timer);
9800
9137
  timer = void 0;
9801
- if (message2 && interactive) {
9138
+ if (message && interactive) {
9802
9139
  render(success ? "\u2713" : "\u2717");
9803
9140
  write("\n");
9804
9141
  }
9805
- message2 = "";
9142
+ message = "";
9806
9143
  };
9807
9144
  return {
9808
9145
  step(next) {
9809
9146
  stop();
9810
- message2 = next.replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
9147
+ message = next.replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
9811
9148
  started = Date.now();
9812
9149
  frame = 0;
9813
9150
  if (!interactive) {
9814
- write(`Mason: ${message2}\u2026
9151
+ write(`Mason: ${message}\u2026
9815
9152
  `);
9816
9153
  return;
9817
9154
  }
@@ -9827,11 +9164,12 @@ function createProgress(options = {}) {
9827
9164
 
9828
9165
  // src/automation/cli.ts
9829
9166
  init_profile();
9830
- var TEARDOWN_USAGE = `Usage: mason teardown [options]
9167
+ var TEARDOWN_USAGE = `Usage: mason teardown [--project] [options]
9831
9168
 
9832
- Disconnect Mason from this Git project. Defaults to all project hosts.
9169
+ Disconnect Mason locally. Defaults to all locally configured hosts.
9833
9170
 
9834
9171
  --host codex|claude Disconnect only this host
9172
+ --project Remove shared integration files for review and commit
9835
9173
  --dir <path> Project directory (defaults to cwd; subdirectories work)
9836
9174
  --dry-run Preview changes without writing
9837
9175
  --json Machine-readable changes and diagnostics
@@ -9844,7 +9182,7 @@ var STATS_USAGE = `Usage: mason stats [options]
9844
9182
 
9845
9183
  Show aggregate local observations, or inspect one session.
9846
9184
 
9847
- --enable | --disable Enable/disable collection (setup enables it)
9185
+ --enable | --disable Enable/disable collection (disabled by default)
9848
9186
  --json Include session/finding IDs and evidence paths
9849
9187
  --dir <path> Project directory (defaults to cwd)
9850
9188
  --session <id> Filter to one retained session
@@ -9852,10 +9190,11 @@ Show aggregate local observations, or inspect one session.
9852
9190
  Supply feedback without changing audit status
9853
9191
 
9854
9192
  No uploads. Observed resolution does not establish causation.`;
9855
- var USAGE = `Usage: mason auto <setup|teardown|install|config|status|check|hook|stats> [options]
9193
+ var USAGE = `Usage: mason <init|setup|teardown|install|config|status|check|hook|stats> [options]
9856
9194
 
9857
- setup [--host claude|codex] Connect MCP, instructions and hooks to mason on PATH; retain the initial audit
9858
- teardown [--host claude|codex] Disconnect one host or all project hosts; retain knowledge and evidence
9195
+ setup [--host claude|codex] Activate existing shared configuration locally
9196
+ init --host claude|codex Prepare shared configuration for review and commit
9197
+ teardown [--host claude|codex] Disconnect locally; --project removes shared configuration
9859
9198
  install --host claude|codex Merge lifecycle hooks into this project's host config
9860
9199
  config --host claude|codex Print the host config without writing
9861
9200
  config --stop-block off|drift Set local Claude Stop review behavior (default: drift)
@@ -9867,12 +9206,13 @@ var USAGE = `Usage: mason auto <setup|teardown|install|config|status|check|hook|
9867
9206
 
9868
9207
  --dir <path> Project directory (defaults to cwd)
9869
9208
  --command <prefix> Installed executable prefix for install/config
9870
- --dry-run Preview teardown without writing
9209
+ --dry-run Preview init or teardown without writing
9871
9210
  --json Machine-readable output (status also uses JSON when piped)
9872
9211
  --profile Local phase timings on stderr for check/hook; no file contents
9873
9212
 
9874
9213
  check exits 0 for verified checks, 1 for issues, 2 for incomplete/unavailable.
9875
9214
  teardown exits 0 for complete/no-op, 2 when cleanup needs attention (also in dry runs).
9215
+ Hooks check decision anchors only, without creating or verifying repair baselines.
9876
9216
  Hooks exit 0; Claude Stop can block once for uninspected session anchor edits.
9877
9217
  Re-entry and Codex remain advisory; failed captures are reported explicitly.
9878
9218
  Local evidence is written under .mason/reports/. No LLM calls or source edits.`;
@@ -9887,6 +9227,7 @@ var parseCli = (argv) => parseArgs({
9887
9227
  json: { type: "boolean" },
9888
9228
  help: { type: "boolean", short: "h" },
9889
9229
  "dry-run": { type: "boolean" },
9230
+ project: { type: "boolean" },
9890
9231
  profile: { type: "boolean" },
9891
9232
  enable: { type: "boolean" },
9892
9233
  disable: { type: "boolean" },
@@ -9922,6 +9263,8 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
9922
9263
  [action] = positionals;
9923
9264
  if (values.profile && !["check", "hook"].includes(action))
9924
9265
  throw new Error("--profile applies only to check/hook.");
9266
+ if (values.project !== void 0 && action !== "teardown")
9267
+ throw new Error("--project applies only to teardown.");
9925
9268
  const trackingOptions = [
9926
9269
  values.enable,
9927
9270
  values.disable,
@@ -9933,8 +9276,8 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
9933
9276
  throw new Error("Tracking options apply only to stats.");
9934
9277
  const measured = (run) => values.profile ? withProfile(run, io.err) : run();
9935
9278
  const dir = values.dir ?? process.cwd();
9936
- if (values["dry-run"] !== void 0 && action !== "teardown")
9937
- throw new Error("--dry-run applies only to teardown.");
9279
+ if (values["dry-run"] !== void 0 && !["init", "teardown"].includes(action))
9280
+ throw new Error("--dry-run applies only to init/teardown.");
9938
9281
  if (values["stop-block"] !== void 0) {
9939
9282
  if (action !== "config" || values.host || values.command)
9940
9283
  throw new Error(
@@ -9949,7 +9292,8 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
9949
9292
  const { teardownProject: teardownProject2, summarizeTeardown: summarizeTeardown2 } = await Promise.resolve().then(() => (init_teardown(), teardown_exports));
9950
9293
  const result = await teardownProject2(dir, {
9951
9294
  host: values.host === void 0 ? void 0 : hostSchema.parse(values.host),
9952
- dryRun: values["dry-run"]
9295
+ dryRun: values["dry-run"],
9296
+ project: values.project
9953
9297
  });
9954
9298
  io.out(values.json ? JSON.stringify(result, null, 2) : summarizeTeardown2(result));
9955
9299
  return result.status === "complete" ? 0 : 2;
@@ -9964,10 +9308,10 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
9964
9308
  if (output) io.out(JSON.stringify(output));
9965
9309
  return 0;
9966
9310
  }
9967
- if (action === "setup") {
9311
+ if (action === "setup" || action === "init") {
9968
9312
  if (values.command)
9969
9313
  throw new Error("--command applies to install/config, not managed setup.");
9970
- const { setupProject: setupProject2, summarizeSetup: summarizeSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
9314
+ const { setupProject: setupProject2, summarizeSetup: summarizeSetup2, initializeProject: initializeProject2, summarizeInitialization: summarizeInitialization2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
9971
9315
  const host = values.host ? hostSchema.parse(values.host) : void 0;
9972
9316
  if (!values.json)
9973
9317
  progress2 = createProgress(
@@ -9976,9 +9320,15 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
9976
9320
  interactive: false
9977
9321
  }
9978
9322
  );
9979
- const result = await setupProject2(dir, { host, progress: progress2 });
9980
- progress2?.stop();
9981
- io.out(values.json ? JSON.stringify(result, null, 2) : summarizeSetup2(result));
9323
+ if (action === "init") {
9324
+ const result = await initializeProject2(dir, { host, dryRun: values["dry-run"], progress: progress2 });
9325
+ progress2?.stop();
9326
+ io.out(values.json ? JSON.stringify(result, null, 2) : summarizeInitialization2(result));
9327
+ } else {
9328
+ const result = await setupProject2(dir, { host, progress: progress2 });
9329
+ progress2?.stop();
9330
+ io.out(values.json ? JSON.stringify(result, null, 2) : summarizeSetup2(result));
9331
+ }
9982
9332
  return 0;
9983
9333
  }
9984
9334
  if (action === "install" || action === "config") {
@@ -10042,16 +9392,16 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
10042
9392
  return report.status === "verified" ? 0 : report.status === "issues-remain" ? 1 : 2;
10043
9393
  } catch (error) {
10044
9394
  progress2?.stop(false);
10045
- const message2 = action === "setup" ? "Mason setup incomplete: " + automationFailure(error).message + ". Rerun the same setup command to resume; retained audit evidence is preserved." : action === "teardown" ? "Mason teardown incomplete: " + automationFailure(error).message + ". Inspect the reported input, then rerun teardown; knowledge and repair evidence are retained." : failureMessage(error);
9395
+ const message = action === "setup" ? "Mason setup incomplete: " + automationFailure(error).message + ". Rerun the same setup command to resume configuration." : action === "teardown" ? "Mason teardown incomplete: " + automationFailure(error).message + ". Inspect the reported input, then rerun teardown; knowledge and repair evidence are retained." : failureMessage(error);
10046
9396
  if (action === "hook" || isHookCommand(argv)) {
10047
- io.out(JSON.stringify({ systemMessage: message2 }));
9397
+ io.out(JSON.stringify({ systemMessage: message }));
10048
9398
  return 0;
10049
9399
  }
10050
9400
  if (argv.includes("--json"))
10051
9401
  io.out(
10052
9402
  JSON.stringify({
10053
9403
  version: 1,
10054
- ...["setup", "teardown"].includes(action) ? {
9404
+ ...["init", "setup", "teardown"].includes(action) ? {
10055
9405
  action,
10056
9406
  status: "incomplete",
10057
9407
  next: "Inspect the reported input, then rerun the same command to resume."
@@ -10059,7 +9409,7 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
10059
9409
  failure: automationFailure(error)
10060
9410
  })
10061
9411
  );
10062
- else io.err(message2);
9412
+ else io.err(message);
10063
9413
  return 2;
10064
9414
  }
10065
9415
  }
@@ -10069,15 +9419,18 @@ var inputArgs = process.argv.slice(2);
10069
9419
  var managedLaunch = inputArgs[0] === "--setup-host";
10070
9420
  var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
10071
9421
  var [command, ...args] = inputArgs;
10072
- var usage = `Mason ${"0.21.0"}
9422
+ var usage = `Mason ${"0.22.0"}
10073
9423
 
10074
9424
  Usage: mason <command> [options]
10075
9425
 
10076
- setup --host codex|claude Connect this project to your assistant
10077
- teardown [--host codex|claude] Disconnect project integrations; retain knowledge
9426
+ setup --host codex|claude Activate this checkout without editing shared files
9427
+ init --host codex|claude Prepare shared configuration for review
9428
+ teardown [--host codex|claude] Disconnect locally; --project removes shared configuration
10078
9429
  status Show configuration, observed use and verification
10079
9430
  stats Show local stats and feedback
10080
- auto Configure and run lifecycle hooks
9431
+ install --host codex|claude Install lifecycle hooks
9432
+ config Configure lifecycle hooks and Stop behavior
9433
+ hook --host codex|claude Process an assistant lifecycle event
10081
9434
  check Resume and verify retained documentation findings
10082
9435
  audit Audit project instructions
10083
9436
  review --base <ref> Review committed changes
@@ -10091,20 +9444,21 @@ Usage: mason <command> [options]
10091
9444
 
10092
9445
  Use mason <command> --help for options. npm users can also use
10093
9446
  mason-audit, mason-review, mason-drift, mason-hook and mason-mcp.`;
9447
+ var lifecycleHook = command === "auto" && isHookCommand(args) || command === "hook" && !args.includes("--print-config") && (managedLaunch || args.length > 0);
10094
9448
  var progress;
10095
9449
  try {
10096
9450
  if (managedLaunch) {
10097
- if (!setupHost || !["codex", "claude"].includes(setupHost) || !["mcp", "auto"].includes(command))
9451
+ if (!setupHost || !["codex", "claude"].includes(setupHost) || !(["mcp", "auto"].includes(command) || lifecycleHook))
10098
9452
  throw new Error("Invalid setup invocation.");
10099
- if (!(command === "auto" && isHookCommand(args))) {
9453
+ if (!lifecycleHook) {
10100
9454
  const { prepareLaunch: prepareLaunch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
10101
9455
  await prepareLaunch2(setupHost, { allowInactive: command === "mcp" });
10102
9456
  }
10103
9457
  }
10104
9458
  if (command === "internal-integration-version")
10105
- console.log(JSON.stringify({ protocol: 1, version: "0.21.0" }));
9459
+ console.log(JSON.stringify({ protocol: 1, version: "0.22.0" }));
10106
9460
  else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
10107
- else if (["--version", "-v"].includes(command)) console.log("0.21.0");
9461
+ else if (["--version", "-v"].includes(command)) console.log("0.22.0");
10108
9462
  else if (command === "internal-update") {
10109
9463
  if (args.length) throw new Error("internal-update takes no arguments.");
10110
9464
  const { currentInstallation: currentInstallation2 } = await Promise.resolve().then(() => (init_state(), state_exports));
@@ -10211,7 +9565,9 @@ Automatic updates: ${status.effective ? "enabled" : "inactive"} (preference: ${s
10211
9565
  (process.platform === "win32" ? "Finishing installation cleanup after runtime exit: " : "Removed standalone installation: ") + await uninstallStandalone2() + ". Project configuration and knowledge were retained. Reinstall Mason to use its integrations."
10212
9566
  );
10213
9567
  }
10214
- } else if (["setup", "teardown", "status", "check", "stats", "auto"].includes(command)) {
9568
+ } else if (["init", "setup", "teardown", "status", "check", "stats", "install", "config", "auto"].includes(
9569
+ command
9570
+ ) || lifecycleHook) {
10215
9571
  const forwarded = command === "auto" ? args : [command, ...args];
10216
9572
  let input = "";
10217
9573
  if (isHookCommand(forwarded) && !process.stdin.isTTY)
@@ -10225,10 +9581,6 @@ Automatic updates: ${status.effective ? "enabled" : "inactive"} (preference: ${s
10225
9581
  process.exitCode = await runAutomationCli(forwarded, input, void 0, {
10226
9582
  managedHost: managedLaunch ? setupHost : void 0
10227
9583
  });
10228
- if (command === "setup" && process.exitCode === 0 && !args.includes("--help")) {
10229
- const { scheduleAutomaticUpdate: scheduleAutomaticUpdate2 } = await Promise.resolve().then(() => (init_updates(), updates_exports));
10230
- void scheduleAutomaticUpdate2();
10231
- }
10232
9584
  } else if (["audit", "review", "drift", "hook", "mcp"].includes(command)) {
10233
9585
  const binary = new URL(`./mason-${command}.js`, import.meta.url);
10234
9586
  process.argv = [process.execPath, fileURLToPath2(binary), ...args];
@@ -10236,12 +9588,12 @@ Automatic updates: ${status.effective ? "enabled" : "inactive"} (preference: ${s
10236
9588
  } else throw new Error("Unknown command: " + command + ". Run mason --help.");
10237
9589
  } catch (error) {
10238
9590
  progress?.stop(false);
10239
- const message2 = "Mason: " + (error instanceof Error ? error.message : String(error));
10240
- if (command === "auto" && isHookCommand(args)) {
10241
- console.log(JSON.stringify({ systemMessage: message2 + " Verification was not established." }));
9591
+ const message = "Mason: " + (error instanceof Error ? error.message : String(error));
9592
+ if (lifecycleHook) {
9593
+ console.log(JSON.stringify({ systemMessage: message + " Verification was not established." }));
10242
9594
  process.exitCode = 0;
10243
9595
  } else {
10244
- console.error(message2);
9596
+ console.error(message);
10245
9597
  process.exitCode = 2;
10246
9598
  }
10247
9599
  }