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.
- package/CHANGELOG.md +19 -0
- package/README.md +32 -5
- package/dist/mason-audit.js +392 -36
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-drift.js +33 -8
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +74 -22
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +2631 -2220
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +70 -18
- package/dist/mason-review.js.map +1 -1
- package/package.json +1 -1
package/dist/mason-review.js
CHANGED
|
@@ -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
|
-
|
|
539
|
-
if (record.
|
|
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
|
-
|
|
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"
|
|
1109
|
-
const
|
|
1110
|
-
|
|
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
|
-
...
|
|
1162
|
+
...knowledge,
|
|
1163
|
+
...pendingProposal ? { pendingProposal: { ...pendingProposal, touchedFiles: proposalTouched } } : {},
|
|
1113
1164
|
id: record.id,
|
|
1114
|
-
|
|
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(
|