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.
@@ -31,12 +31,12 @@ var init_types = __esm({
31
31
  });
32
32
 
33
33
  // src/review/cli.ts
34
- import path13 from "path";
34
+ import path12 from "path";
35
35
  import { stripVTControlCharacters } from "util";
36
36
 
37
37
  // src/review/review.ts
38
38
  import fs7 from "fs/promises";
39
- import path12 from "path";
39
+ import path11 from "path";
40
40
  import { execFile as execFile3 } from "child_process";
41
41
  import { promisify as promisify3 } from "util";
42
42
 
@@ -91,10 +91,7 @@ function execGit(args, options) {
91
91
 
92
92
  // src/drift/drift.ts
93
93
  import fs4 from "fs/promises";
94
- import path6 from "path";
95
-
96
- // src/snapshot/snapshot.ts
97
- import path5 from "path";
94
+ import path4 from "path";
98
95
 
99
96
  // src/utils/validation.ts
100
97
  function isRecord(value) {
@@ -420,76 +417,6 @@ import fs3 from "fs/promises";
420
417
  import os from "os";
421
418
  var lockOwnerSchema = z.object({ host: z.string(), pid: z.number().int().positive() });
422
419
 
423
- // src/test-map.ts
424
- import path4 from "path";
425
- async function buildTestMap(dir) {
426
- const rootDir = path4.resolve(dir);
427
- const access = await createFileAccess(rootDir);
428
- const testPatterns = [
429
- "**/*.test.*",
430
- "**/*.spec.*",
431
- "**/*Test.kt",
432
- "**/*Test.java",
433
- "**/*Tests.kt",
434
- "**/*Tests.java",
435
- "**/test_*.py",
436
- "**/*_test.py",
437
- "**/*_test.go",
438
- "**/*Tests.swift",
439
- "**/*Test.swift",
440
- "**/*_test.rs"
441
- ];
442
- const testFiles = await access.list(testPatterns);
443
- const sourceFiles = await access.list();
444
- const sourceByBaseName = /* @__PURE__ */ new Map();
445
- for (const file of sourceFiles) {
446
- if (testFiles.includes(file)) continue;
447
- const baseName = path4.basename(file).replace(/\.[^.]+$/, "");
448
- const existing = sourceByBaseName.get(baseName) ?? [];
449
- existing.push(file);
450
- sourceByBaseName.set(baseName, existing);
451
- }
452
- const paired = [];
453
- const unmatched = [];
454
- for (const testFile of testFiles) {
455
- const testBaseName = path4.basename(testFile).replace(/\.[^.]+$/, "");
456
- const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
457
- if (!sourceName) {
458
- unmatched.push(testFile);
459
- continue;
460
- }
461
- const candidates = sourceByBaseName.get(sourceName);
462
- if (candidates && candidates.length > 0) {
463
- const testDir = path4.dirname(testFile);
464
- const bestMatch = candidates.reduce((best, candidate) => {
465
- const candidateDir = path4.dirname(candidate);
466
- const bestDir = path4.dirname(best);
467
- const candidateOverlap = commonSegments(testDir, candidateDir);
468
- const bestOverlap = commonSegments(testDir, bestDir);
469
- return candidateOverlap > bestOverlap ? candidate : best;
470
- });
471
- paired.push({
472
- test: testFile,
473
- source: bestMatch,
474
- confidence: candidates.length === 1 ? "exact" : "best-guess"
475
- });
476
- } else {
477
- unmatched.push(testFile);
478
- }
479
- }
480
- return { totalTestFiles: testFiles.length, paired, unmatched };
481
- }
482
- function commonSegments(pathA, pathB) {
483
- const segsA = pathA.split("/");
484
- const segsB = pathB.split("/");
485
- let count2 = 0;
486
- for (let i = 0; i < Math.min(segsA.length, segsB.length); i++) {
487
- if (segsA[i] === segsB[i]) count2++;
488
- else break;
489
- }
490
- return count2;
491
- }
492
-
493
420
  // src/snapshot/snapshot.ts
494
421
  var repoPath = z2.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
495
422
  var verificationFields = {
@@ -600,11 +527,11 @@ import fg3 from "fast-glob";
600
527
  // src/audit/inspection.ts
601
528
  import fg2 from "fast-glob";
602
529
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
603
- import path7 from "path";
530
+ import path5 from "path";
604
531
  var inspections = new AsyncLocalStorage2();
605
532
  function inspectionRead(root, key, read) {
606
533
  const inspection = inspections.getStore();
607
- if (!inspection || inspection.root !== path7.resolve(root)) return read();
534
+ if (!inspection || inspection.root !== path5.resolve(root)) return read();
608
535
  if (!inspection.reads.has(key)) {
609
536
  const pending = read();
610
537
  inspection.reads.set(key, pending);
@@ -847,6 +774,7 @@ var currentSchema = legacySchema.extend({
847
774
  inspector: text(200),
848
775
  note: text(1500),
849
776
  outcome: z4.literal("no_contradiction_found"),
777
+ scope: z4.literal("operative").optional(),
850
778
  decisionDigest: z4.string().regex(/^[a-f0-9]{64}$/),
851
779
  headHash: z4.string(),
852
780
  capture: anchorCaptureSchema
@@ -957,8 +885,19 @@ function decisionTrust(record, freshness) {
957
885
  freshness
958
886
  );
959
887
  }
960
- function decisionInspectionDigest(record) {
888
+ function decisionInspectionDigest(record, scope = "all") {
961
889
  const effective = effectiveDecision(record);
890
+ if (scope === "operative")
891
+ return createHash2("sha256").update(
892
+ JSON.stringify([
893
+ "operative",
894
+ record.status,
895
+ decisionApproval(effective),
896
+ effective.version === 2 ? effective.revision : 0,
897
+ decisionContent(effective),
898
+ effective.refreshedHash
899
+ ])
900
+ ).digest("hex");
962
901
  return createHash2("sha256").update(
963
902
  JSON.stringify([
964
903
  record.status,
@@ -970,8 +909,10 @@ function decisionInspectionDigest(record) {
970
909
  ])
971
910
  ).digest("hex");
972
911
  }
973
- function latestDecisionInspection(record) {
974
- return record.version === 2 ? record.inspections?.filter((i) => i.decisionDigest === decisionInspectionDigest(record)).at(-1) : void 0;
912
+ function latestDecisionInspection(record, scope = "all") {
913
+ return record.version === 2 ? record.inspections?.filter(
914
+ (i) => (scope === "operative" || i.scope !== "operative") && i.decisionDigest === decisionInspectionDigest(record, i.scope ?? "all")
915
+ ).at(-1) : void 0;
975
916
  }
976
917
  function revisionKnowledge(record, freshness) {
977
918
  return {
@@ -982,7 +923,10 @@ function revisionKnowledge(record, freshness) {
982
923
  }
983
924
  function decisionKnowledge(record, freshness = "unknown", proposalFreshness = "unknown") {
984
925
  const effective = effectiveDecision(record);
985
- const inspection = latestDecisionInspection(record);
926
+ const inspection = latestDecisionInspection(
927
+ record,
928
+ decisionApproval(effective) === "accepted" ? "operative" : "all"
929
+ );
986
930
  return {
987
931
  ...revisionKnowledge(effective, freshness),
988
932
  ...inspection ? {
@@ -992,21 +936,27 @@ function decisionKnowledge(record, freshness = "unknown", proposalFreshness = "u
992
936
  inspector: inspection.inspector,
993
937
  note: inspection.note,
994
938
  outcome: inspection.outcome,
939
+ scope: inspection.scope ?? "all",
995
940
  headHash: inspection.headHash
996
941
  }
997
942
  } : {},
998
- ...effective !== record ? { pendingProposal: revisionKnowledge(record, proposalFreshness) } : {}
943
+ ...effective !== record ? {
944
+ pendingProposal: {
945
+ ...revisionKnowledge(record, proposalFreshness),
946
+ assessment: latestDecisionInspection(record) ? "no_contradiction_found" : "not_assessed"
947
+ }
948
+ } : {}
999
949
  };
1000
950
  }
1001
951
 
1002
952
  // src/decisions/decisions.ts
1003
953
  import fs6 from "fs/promises";
1004
- import path9 from "path";
954
+ import path7 from "path";
1005
955
  import { createHash as createHash3 } from "crypto";
1006
956
  import { z as z5 } from "zod";
1007
957
 
1008
958
  // src/context/lexical.ts
1009
- import path8 from "path";
959
+ import path6 from "path";
1010
960
 
1011
961
  // src/decisions/decisions.ts
1012
962
  var TITLE_MAX_CHARS = 80;
@@ -1085,9 +1035,9 @@ async function loadDecisionStore(rootDir) {
1085
1035
  }
1086
1036
 
1087
1037
  // src/decisions/drift.ts
1088
- import path10 from "path";
1038
+ import path8 from "path";
1089
1039
  async function computeDecisionDrift(rootDir, decisions) {
1090
- const resolvedRoot = path10.resolve(rootDir);
1040
+ const resolvedRoot = path8.resolve(rootDir);
1091
1041
  const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
1092
1042
  const report = {
1093
1043
  historyAvailable: true,
@@ -1102,11 +1052,15 @@ async function computeDecisionDrift(rootDir, decisions) {
1102
1052
  ]);
1103
1053
  const changesByHash = /* @__PURE__ */ new Map();
1104
1054
  const baselines = store.records.filter((record) => record.status === "active").flatMap((record) => {
1105
- const inspection = latestDecisionInspection(record);
1106
- if (inspection) return [inspection.capture];
1107
- return [.../* @__PURE__ */ new Set([effectiveDecision(record), record])].flatMap(
1108
- (candidate) => candidate.version === 2 && candidate.capture ? [candidate.capture] : []
1109
- );
1055
+ const effective = effectiveDecision(record);
1056
+ return [.../* @__PURE__ */ new Set([effective, record])].flatMap((candidate) => {
1057
+ const inspection = latestDecisionInspection(
1058
+ record,
1059
+ candidate === effective && decisionApproval(effective) === "accepted" ? "operative" : "all"
1060
+ );
1061
+ const capture = inspection?.capture ?? (candidate.version === 2 ? candidate.capture : void 0);
1062
+ return capture ? [capture] : [];
1063
+ });
1110
1064
  });
1111
1065
  const captures = await captureAnchorScopes(
1112
1066
  resolvedRoot,
@@ -1145,12 +1099,18 @@ async function computeDecisionDrift(rootDir, decisions) {
1145
1099
  for (const record of store.records) {
1146
1100
  if (record.status !== "active") continue;
1147
1101
  const effective = effectiveDecision(record);
1148
- const inspection = latestDecisionInspection(record);
1102
+ const inspection = latestDecisionInspection(
1103
+ record,
1104
+ decisionApproval(effective) === "accepted" ? "operative" : "all"
1105
+ );
1149
1106
  const state = await inspect(effective, inspection?.capture);
1150
1107
  report.freshness[record.id] = state.freshness;
1151
1108
  if (state.changedFiles.length) report.staleDecisions[record.id] = state.changedFiles;
1152
1109
  if (effective !== record)
1153
- (report.pendingProposals ??= {})[record.id] = await inspect(record, inspection?.capture);
1110
+ (report.pendingProposals ??= {})[record.id] = await inspect(
1111
+ record,
1112
+ latestDecisionInspection(record)?.capture
1113
+ );
1154
1114
  }
1155
1115
  return report;
1156
1116
  }
@@ -1228,9 +1188,79 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
1228
1188
  }
1229
1189
 
1230
1190
  // src/review/evidence.ts
1231
- import path11 from "path";
1191
+ import path10 from "path";
1232
1192
  import { z as z9 } from "zod";
1233
1193
 
1194
+ // src/test-map.ts
1195
+ import path9 from "path";
1196
+ async function buildTestMap(dir) {
1197
+ const rootDir = path9.resolve(dir);
1198
+ const access = await createFileAccess(rootDir);
1199
+ const testPatterns = [
1200
+ "**/*.test.*",
1201
+ "**/*.spec.*",
1202
+ "**/*Test.kt",
1203
+ "**/*Test.java",
1204
+ "**/*Tests.kt",
1205
+ "**/*Tests.java",
1206
+ "**/test_*.py",
1207
+ "**/*_test.py",
1208
+ "**/*_test.go",
1209
+ "**/*Tests.swift",
1210
+ "**/*Test.swift",
1211
+ "**/*_test.rs"
1212
+ ];
1213
+ const testFiles = await access.list(testPatterns);
1214
+ const sourceFiles = await access.list();
1215
+ const sourceByBaseName = /* @__PURE__ */ new Map();
1216
+ for (const file of sourceFiles) {
1217
+ if (testFiles.includes(file)) continue;
1218
+ const baseName = path9.basename(file).replace(/\.[^.]+$/, "");
1219
+ const existing = sourceByBaseName.get(baseName) ?? [];
1220
+ existing.push(file);
1221
+ sourceByBaseName.set(baseName, existing);
1222
+ }
1223
+ const paired = [];
1224
+ const unmatched = [];
1225
+ for (const testFile of testFiles) {
1226
+ const testBaseName = path9.basename(testFile).replace(/\.[^.]+$/, "");
1227
+ const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
1228
+ if (!sourceName) {
1229
+ unmatched.push(testFile);
1230
+ continue;
1231
+ }
1232
+ const candidates = sourceByBaseName.get(sourceName);
1233
+ if (candidates && candidates.length > 0) {
1234
+ const testDir = path9.dirname(testFile);
1235
+ const bestMatch = candidates.reduce((best, candidate) => {
1236
+ const candidateDir = path9.dirname(candidate);
1237
+ const bestDir = path9.dirname(best);
1238
+ const candidateOverlap = commonSegments(testDir, candidateDir);
1239
+ const bestOverlap = commonSegments(testDir, bestDir);
1240
+ return candidateOverlap > bestOverlap ? candidate : best;
1241
+ });
1242
+ paired.push({
1243
+ test: testFile,
1244
+ source: bestMatch,
1245
+ confidence: candidates.length === 1 ? "exact" : "best-guess"
1246
+ });
1247
+ } else {
1248
+ unmatched.push(testFile);
1249
+ }
1250
+ }
1251
+ return { totalTestFiles: testFiles.length, paired, unmatched };
1252
+ }
1253
+ function commonSegments(pathA, pathB) {
1254
+ const segsA = pathA.split("/");
1255
+ const segsB = pathB.split("/");
1256
+ let count2 = 0;
1257
+ for (let i = 0; i < Math.min(segsA.length, segsB.length); i++) {
1258
+ if (segsA[i] === segsB[i]) count2++;
1259
+ else break;
1260
+ }
1261
+ return count2;
1262
+ }
1263
+
1234
1264
  // src/review/evidence/vitest.ts
1235
1265
  import { z as z6 } from "zod";
1236
1266
 
@@ -1622,7 +1652,7 @@ var checkSchema = z9.object({
1622
1652
  }).optional()
1623
1653
  });
1624
1654
  function relativeArtifact(root, file) {
1625
- const relative = path11.isAbsolute(file) ? path11.relative(root, file) : file;
1655
+ const relative = path10.isAbsolute(file) ? path10.relative(root, file) : file;
1626
1656
  const normalized = normalizeRepoPath(relative);
1627
1657
  if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
1628
1658
  return normalized;
@@ -1751,7 +1781,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
1751
1781
  const report = await readStoreJson(root, file);
1752
1782
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
1753
1783
  const sourceRoot = check.sourceRoot ?? root;
1754
- if (!path11.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot))
1784
+ if (!path10.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot))
1755
1785
  throw new Error(
1756
1786
  "sourceRoot must identify the absolute checkout root on the check runner."
1757
1787
  );
@@ -1856,7 +1886,7 @@ function anchorsTouched(record, changedFiles) {
1856
1886
  return matchingPaths(record.files, changedFiles);
1857
1887
  }
1858
1888
  async function computeReview(rootDir, base, options = {}) {
1859
- const resolvedRoot = path12.resolve(rootDir);
1889
+ const resolvedRoot = path11.resolve(rootDir);
1860
1890
  const head = await getCurrentGitHash(resolvedRoot);
1861
1891
  if (head === "unknown") return null;
1862
1892
  const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
@@ -1917,7 +1947,7 @@ async function computeReview(rootDir, base, options = {}) {
1917
1947
  analyzed,
1918
1948
  async (relPath) => {
1919
1949
  try {
1920
- await fs7.access(path12.join(resolvedRoot, relPath));
1950
+ await fs7.access(path11.join(resolvedRoot, relPath));
1921
1951
  return true;
1922
1952
  } catch {
1923
1953
  return false;
@@ -2152,7 +2182,7 @@ async function runReviewCli(argv, io = {
2152
2182
  io.out(USAGE);
2153
2183
  return 0;
2154
2184
  }
2155
- const rootDir = path13.resolve(args.dir);
2185
+ const rootDir = path12.resolve(args.dir);
2156
2186
  const base = args.base ?? await defaultBase(rootDir);
2157
2187
  if (!base) {
2158
2188
  io.err(