mason-context 0.10.0 → 0.11.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.
@@ -376,6 +376,19 @@ async function getWorkingTree(resolvedRoot) {
376
376
 
377
377
  // src/decisions/provenance.ts
378
378
  import { z as z2 } from "zod";
379
+
380
+ // src/context/trust.ts
381
+ function assessTrust(entry, freshness) {
382
+ const verification = entry.verificationFailed ? "failed" : entry.verifiedAt ? "passed" : "unverified";
383
+ const reasons = [];
384
+ if (freshness === "unknown") reasons.push("Anchors, history, or working-tree evidence are unavailable; verify before relying on this entry.");
385
+ if (freshness === "changed") reasons.push("Anchored files changed; verify against current code before relying on this entry.");
386
+ if (verification === "failed") reasons.push(`Verification failed: ${entry.verificationNote ?? "re-map this entry before relying on it"}`);
387
+ if (verification === "unverified") reasons.push("No correctness verification has been recorded.");
388
+ return { freshness, verification, verifiedAt: entry.verifiedAt, verifiedHash: entry.verifiedHash, reasons };
389
+ }
390
+
391
+ // src/decisions/provenance.ts
379
392
  var text = (max) => z2.string().trim().min(1).max(max);
380
393
  var decisionSourceSchema = z2.object({
381
394
  kind: z2.enum(["pull_request", "issue", "incident", "discussion", "document", "other"]),
@@ -480,6 +493,23 @@ function decisionContent(record) {
480
493
  function decisionApproval(record) {
481
494
  return record.version === 1 ? "unreviewed" : record.approval;
482
495
  }
496
+ function effectiveDecision(record) {
497
+ if (record.version !== 2 || record.status !== "active" || record.approval !== "proposed") return record;
498
+ let index2 = record.history.length - 1;
499
+ while (index2 >= 0 && !["accepted", "reaffirmed"].includes(record.history[index2].kind)) index2--;
500
+ if (index2 < 0) return record;
501
+ const event = record.history[index2];
502
+ return {
503
+ ...record,
504
+ ...event.content,
505
+ owner: event.content.owner,
506
+ approval: "accepted",
507
+ revision: event.revision,
508
+ refreshedHash: event.refreshedHash,
509
+ updatedAt: event.at,
510
+ history: record.history.slice(0, index2 + 1)
511
+ };
512
+ }
483
513
  function decisionProvenance(record, freshness = "unknown") {
484
514
  const approval = decisionApproval(record);
485
515
  const review = record.version === 2 ? [...record.history].reverse().find((e) => ["accepted", "reaffirmed"].includes(e.kind) && e.revision === record.revision) : void 0;
@@ -493,6 +523,20 @@ function decisionProvenance(record, freshness = "unknown") {
493
523
  lastReview: review ? { reviewer: review.actor, at: review.at, note: review.note, gitHash: review.refreshedHash } : null
494
524
  };
495
525
  }
526
+ function decisionTrust(record, freshness) {
527
+ const review = decisionProvenance(record, freshness).lastReview;
528
+ return assessTrust(review ? { verifiedAt: review.at, verifiedHash: review.gitHash } : {}, freshness);
529
+ }
530
+ function revisionKnowledge(record, freshness) {
531
+ return { ...decisionContent(record), ...decisionProvenance(record, freshness), trust: decisionTrust(record, freshness) };
532
+ }
533
+ function decisionKnowledge(record, freshness = "unknown", proposalFreshness = "unknown") {
534
+ const effective = effectiveDecision(record);
535
+ return {
536
+ ...revisionKnowledge(effective, freshness),
537
+ ...effective !== record ? { pendingProposal: revisionKnowledge(record, proposalFreshness) } : {}
538
+ };
539
+ }
496
540
 
497
541
  // src/decisions/decisions.ts
498
542
  import fs4 from "fs/promises";
@@ -535,12 +579,8 @@ async function computeDecisionDrift(rootDir, decisions) {
535
579
  const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
536
580
  const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
537
581
  const changesByHash = /* @__PURE__ */ new Map();
538
- for (const record of store.records) {
539
- if (record.status !== "active") continue;
540
- if (record.files.length === 0) {
541
- report.freshness[record.id] = "unknown";
542
- continue;
543
- }
582
+ const inspect = async (record) => {
583
+ if (record.files.length === 0) return { freshness: "unknown", changedFiles: [] };
544
584
  let touched = changesByHash.get(record.refreshedHash);
545
585
  if (touched === void 0) {
546
586
  const changes = record.refreshedHash === head && head !== "unknown" ? [] : await getChangesWithStatus(resolvedRoot, record.refreshedHash);
@@ -549,9 +589,16 @@ async function computeDecisionDrift(rootDir, decisions) {
549
589
  }
550
590
  if (touched === null) report.historyAvailable = false;
551
591
  const hits = touched ? matchingPaths(record.files, touched) : [];
552
- if (hits.length) report.staleDecisions[record.id] = hits;
553
592
  const localHits = matchingPaths(record.files, workingTree.changedFiles);
554
- report.freshness[record.id] = touched === null || !workingTree.available ? "unknown" : hits.length || localHits.length ? "changed" : "current";
593
+ return { freshness: touched === null || !workingTree.available ? "unknown" : hits.length || localHits.length ? "changed" : "current", changedFiles: hits };
594
+ };
595
+ for (const record of store.records) {
596
+ if (record.status !== "active") continue;
597
+ const effective = effectiveDecision(record);
598
+ const state = await inspect(effective);
599
+ report.freshness[record.id] = state.freshness;
600
+ if (state.changedFiles.length) report.staleDecisions[record.id] = state.changedFiles;
601
+ if (effective !== record) (report.pendingProposals ??= {})[record.id] = await inspect(record);
555
602
  }
556
603
  return report;
557
604
  }
@@ -873,7 +920,7 @@ function linkFinding(finding, changed, pairs, decisions, freshness) {
873
920
  if (changed.has(pair.source) && !relatedChangedFiles.some((f) => f.file === pair.source)) relatedChangedFiles.push({ file: pair.source, relationship: "paired-test", confidence: pair.confidence });
874
921
  }
875
922
  const allFiles = [.../* @__PURE__ */ new Set([...located, ...paired.map((pair) => pair.source)])];
876
- const acceptedDecisions = decisions.filter((d) => d.status === "active" && decisionApproval(d) === "accepted").map((d) => ({ d, viaFiles: matchingPaths(d.files, allFiles) })).filter((match) => match.viaFiles.length).map(({ d, viaFiles }) => {
923
+ 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 }) => {
877
924
  const state = freshness[d.id] ?? "unknown", provenance = decisionProvenance(d, state);
878
925
  return { id: d.id, title: d.title, owner: provenance.owner, freshness: state, reviewRequired: provenance.reviewRequired, viaFiles };
879
926
  });
@@ -1105,16 +1152,17 @@ async function computeReview(rootDir, base, options = {}) {
1105
1152
  }
1106
1153
  const decisions = store.records;
1107
1154
  for (const record of decisions) {
1108
- if (record.status !== "active" || record.files.length === 0) continue;
1109
- const touched = anchorsTouched(record, changedFiles);
1110
- if (touched.length > 0) {
1155
+ if (record.status !== "active") continue;
1156
+ const effective = effectiveDecision(record);
1157
+ const touched = anchorsTouched(effective, changedFiles);
1158
+ const proposalTouched = effective !== record ? anchorsTouched(record, changedFiles) : [];
1159
+ if (touched.length > 0 || proposalTouched.length > 0) {
1160
+ const { pendingProposal, ...knowledge } = decisionKnowledge(record, decisionDrift.freshness?.[record.id] ?? "unknown", decisionDrift.pendingProposals?.[record.id]?.freshness ?? "unknown");
1111
1161
  report.touchedDecisions.push({
1112
- ...decisionProvenance(record, decisionDrift.freshness?.[record.id] ?? "unknown"),
1162
+ ...knowledge,
1163
+ ...pendingProposal ? { pendingProposal: { ...pendingProposal, touchedFiles: proposalTouched } } : {},
1113
1164
  id: record.id,
1114
- title: record.title,
1115
- body: record.body,
1116
- category: record.category,
1117
- anchors: record.files,
1165
+ anchors: effective.files,
1118
1166
  freshness: decisionDrift.freshness?.[record.id] ?? "unknown",
1119
1167
  touchedFiles: touched
1120
1168
  });
@@ -1242,10 +1290,14 @@ function formatReviewSummary(report) {
1242
1290
  lines.push("Recorded decisions touched by this diff (check approval and freshness before relying on them):");
1243
1291
  for (const d of report.touchedDecisions) {
1244
1292
  lines.push(
1245
- ` [decision] ${d.title} (${d.category}; ${d.approval ?? "unreviewed"}; owner ${d.owner ?? "unknown"}; freshness ${d.freshness ?? "unknown"}; via ${d.touchedFiles.join(", ")})`
1293
+ ` [decision] ${d.title} (${d.category}; ${d.approval ?? "unreviewed"}; owner ${d.owner ?? "unknown"}; freshness ${d.freshness ?? "unknown"}; via ${d.touchedFiles.join(", ") || "pending proposal"})`
1246
1294
  );
1247
1295
  if (d.sources?.length) lines.push(` sources: ${d.sources.map((s) => s.reference).join(", ")}`);
1248
1296
  if (d.lastReview) lines.push(` last review: ${d.lastReview.reviewer} at ${d.lastReview.gitHash.slice(0, 7)}`);
1297
+ if (d.pendingProposal) {
1298
+ const p = d.pendingProposal;
1299
+ lines.push(` [proposed revision ${p.revision}] ${p.title} (owner ${p.owner ?? "unknown"}; freshness ${p.trust.freshness}; via ${p.touchedFiles.join(", ") || "no draft anchors touched"}); accepted revision ${d.revision} remains operative`);
1300
+ }
1249
1301
  }
1250
1302
  }
1251
1303
  lines.push(