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/CHANGELOG.md +19 -0
- package/README.md +10 -5
- package/dist/mason-audit.js +89 -73
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-drift.js +52 -38
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +60 -30
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +2767 -5123
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +130 -100
- package/dist/mason-review.js.map +1 -1
- package/dist/mason-runtime.js +1 -0
- package/dist/mason-runtime.js.map +1 -1
- package/dist/mason.js +1055 -1703
- package/dist/mason.js.map +1 -1
- package/package.json +1 -4
package/dist/mason-hook.js
CHANGED
|
@@ -43,7 +43,7 @@ function isStringArray(value) {
|
|
|
43
43
|
|
|
44
44
|
// src/hook/hook.ts
|
|
45
45
|
import fs7 from "fs/promises";
|
|
46
|
-
import
|
|
46
|
+
import path9 from "path";
|
|
47
47
|
import os2 from "os";
|
|
48
48
|
|
|
49
49
|
// src/audit/inspection.ts
|
|
@@ -543,12 +543,11 @@ function changedCaptureFiles(before, after) {
|
|
|
543
543
|
|
|
544
544
|
// src/decisions/decisions.ts
|
|
545
545
|
import fs5 from "fs/promises";
|
|
546
|
-
import
|
|
546
|
+
import path6 from "path";
|
|
547
547
|
import { createHash as createHash3 } from "crypto";
|
|
548
548
|
import { z as z5 } from "zod";
|
|
549
549
|
|
|
550
550
|
// src/snapshot/snapshot.ts
|
|
551
|
-
import path6 from "path";
|
|
552
551
|
import { z as z3 } from "zod";
|
|
553
552
|
|
|
554
553
|
// src/utils/store-lock.ts
|
|
@@ -557,9 +556,6 @@ import fs4 from "fs/promises";
|
|
|
557
556
|
import os from "os";
|
|
558
557
|
var lockOwnerSchema = z2.object({ host: z2.string(), pid: z2.number().int().positive() });
|
|
559
558
|
|
|
560
|
-
// src/test-map.ts
|
|
561
|
-
import path5 from "path";
|
|
562
|
-
|
|
563
559
|
// src/snapshot/snapshot.ts
|
|
564
560
|
var repoPath2 = z3.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
|
|
565
561
|
var verificationFields = {
|
|
@@ -598,7 +594,7 @@ async function getCurrentGitHash(rootDir) {
|
|
|
598
594
|
}
|
|
599
595
|
|
|
600
596
|
// src/context/lexical.ts
|
|
601
|
-
import
|
|
597
|
+
import path5 from "path";
|
|
602
598
|
|
|
603
599
|
// src/decisions/provenance.ts
|
|
604
600
|
import { z as z4 } from "zod";
|
|
@@ -708,6 +704,7 @@ var currentSchema = legacySchema.extend({
|
|
|
708
704
|
inspector: text(200),
|
|
709
705
|
note: text(1500),
|
|
710
706
|
outcome: z4.literal("no_contradiction_found"),
|
|
707
|
+
scope: z4.literal("operative").optional(),
|
|
711
708
|
decisionDigest: z4.string().regex(/^[a-f0-9]{64}$/),
|
|
712
709
|
headHash: z4.string(),
|
|
713
710
|
capture: anchorCaptureSchema
|
|
@@ -821,8 +818,19 @@ function decisionTrust(record, freshness) {
|
|
|
821
818
|
freshness
|
|
822
819
|
);
|
|
823
820
|
}
|
|
824
|
-
function decisionInspectionDigest(record) {
|
|
821
|
+
function decisionInspectionDigest(record, scope = "all") {
|
|
825
822
|
const effective = effectiveDecision(record);
|
|
823
|
+
if (scope === "operative")
|
|
824
|
+
return createHash2("sha256").update(
|
|
825
|
+
JSON.stringify([
|
|
826
|
+
"operative",
|
|
827
|
+
record.status,
|
|
828
|
+
decisionApproval(effective),
|
|
829
|
+
effective.version === 2 ? effective.revision : 0,
|
|
830
|
+
decisionContent(effective),
|
|
831
|
+
effective.refreshedHash
|
|
832
|
+
])
|
|
833
|
+
).digest("hex");
|
|
826
834
|
return createHash2("sha256").update(
|
|
827
835
|
JSON.stringify([
|
|
828
836
|
record.status,
|
|
@@ -834,8 +842,10 @@ function decisionInspectionDigest(record) {
|
|
|
834
842
|
])
|
|
835
843
|
).digest("hex");
|
|
836
844
|
}
|
|
837
|
-
function latestDecisionInspection(record) {
|
|
838
|
-
return record.version === 2 ? record.inspections?.filter(
|
|
845
|
+
function latestDecisionInspection(record, scope = "all") {
|
|
846
|
+
return record.version === 2 ? record.inspections?.filter(
|
|
847
|
+
(i) => (scope === "operative" || i.scope !== "operative") && i.decisionDigest === decisionInspectionDigest(record, i.scope ?? "all")
|
|
848
|
+
).at(-1) : void 0;
|
|
839
849
|
}
|
|
840
850
|
function revisionKnowledge(record, freshness) {
|
|
841
851
|
return {
|
|
@@ -846,7 +856,10 @@ function revisionKnowledge(record, freshness) {
|
|
|
846
856
|
}
|
|
847
857
|
function decisionKnowledge(record, freshness = "unknown", proposalFreshness = "unknown") {
|
|
848
858
|
const effective = effectiveDecision(record);
|
|
849
|
-
const inspection = latestDecisionInspection(
|
|
859
|
+
const inspection = latestDecisionInspection(
|
|
860
|
+
record,
|
|
861
|
+
decisionApproval(effective) === "accepted" ? "operative" : "all"
|
|
862
|
+
);
|
|
850
863
|
return {
|
|
851
864
|
...revisionKnowledge(effective, freshness),
|
|
852
865
|
...inspection ? {
|
|
@@ -856,10 +869,16 @@ function decisionKnowledge(record, freshness = "unknown", proposalFreshness = "u
|
|
|
856
869
|
inspector: inspection.inspector,
|
|
857
870
|
note: inspection.note,
|
|
858
871
|
outcome: inspection.outcome,
|
|
872
|
+
scope: inspection.scope ?? "all",
|
|
859
873
|
headHash: inspection.headHash
|
|
860
874
|
}
|
|
861
875
|
} : {},
|
|
862
|
-
...effective !== record ? {
|
|
876
|
+
...effective !== record ? {
|
|
877
|
+
pendingProposal: {
|
|
878
|
+
...revisionKnowledge(record, proposalFreshness),
|
|
879
|
+
assessment: latestDecisionInspection(record) ? "no_contradiction_found" : "not_assessed"
|
|
880
|
+
}
|
|
881
|
+
} : {}
|
|
863
882
|
};
|
|
864
883
|
}
|
|
865
884
|
var 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.";
|
|
@@ -944,11 +963,11 @@ async function loadDecisionStore(rootDir) {
|
|
|
944
963
|
import { createHash as createHash4 } from "crypto";
|
|
945
964
|
|
|
946
965
|
// src/decisions/drift.ts
|
|
947
|
-
import
|
|
966
|
+
import path8 from "path";
|
|
948
967
|
|
|
949
968
|
// src/drift/drift.ts
|
|
950
969
|
import fs6 from "fs/promises";
|
|
951
|
-
import
|
|
970
|
+
import path7 from "path";
|
|
952
971
|
function parseChanges(output) {
|
|
953
972
|
const fields = output.split("\0");
|
|
954
973
|
const changes = [];
|
|
@@ -1009,7 +1028,7 @@ async function getWorkingTree(resolvedRoot) {
|
|
|
1009
1028
|
|
|
1010
1029
|
// src/decisions/drift.ts
|
|
1011
1030
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
1012
|
-
const resolvedRoot =
|
|
1031
|
+
const resolvedRoot = path8.resolve(rootDir);
|
|
1013
1032
|
const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
|
|
1014
1033
|
const report = {
|
|
1015
1034
|
historyAvailable: true,
|
|
@@ -1024,11 +1043,15 @@ async function computeDecisionDrift(rootDir, decisions) {
|
|
|
1024
1043
|
]);
|
|
1025
1044
|
const changesByHash = /* @__PURE__ */ new Map();
|
|
1026
1045
|
const baselines = store.records.filter((record) => record.status === "active").flatMap((record) => {
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1046
|
+
const effective = effectiveDecision(record);
|
|
1047
|
+
return [.../* @__PURE__ */ new Set([effective, record])].flatMap((candidate) => {
|
|
1048
|
+
const inspection = latestDecisionInspection(
|
|
1049
|
+
record,
|
|
1050
|
+
candidate === effective && decisionApproval(effective) === "accepted" ? "operative" : "all"
|
|
1051
|
+
);
|
|
1052
|
+
const capture = inspection?.capture ?? (candidate.version === 2 ? candidate.capture : void 0);
|
|
1053
|
+
return capture ? [capture] : [];
|
|
1054
|
+
});
|
|
1032
1055
|
});
|
|
1033
1056
|
const captures = await captureAnchorScopes(
|
|
1034
1057
|
resolvedRoot,
|
|
@@ -1067,12 +1090,18 @@ async function computeDecisionDrift(rootDir, decisions) {
|
|
|
1067
1090
|
for (const record of store.records) {
|
|
1068
1091
|
if (record.status !== "active") continue;
|
|
1069
1092
|
const effective = effectiveDecision(record);
|
|
1070
|
-
const inspection = latestDecisionInspection(
|
|
1093
|
+
const inspection = latestDecisionInspection(
|
|
1094
|
+
record,
|
|
1095
|
+
decisionApproval(effective) === "accepted" ? "operative" : "all"
|
|
1096
|
+
);
|
|
1071
1097
|
const state = await inspect(effective, inspection?.capture);
|
|
1072
1098
|
report.freshness[record.id] = state.freshness;
|
|
1073
1099
|
if (state.changedFiles.length) report.staleDecisions[record.id] = state.changedFiles;
|
|
1074
1100
|
if (effective !== record)
|
|
1075
|
-
(report.pendingProposals ??= {})[record.id] = await inspect(
|
|
1101
|
+
(report.pendingProposals ??= {})[record.id] = await inspect(
|
|
1102
|
+
record,
|
|
1103
|
+
latestDecisionInspection(record)?.capture
|
|
1104
|
+
);
|
|
1076
1105
|
}
|
|
1077
1106
|
return report;
|
|
1078
1107
|
}
|
|
@@ -1100,9 +1129,9 @@ async function exists(p) {
|
|
|
1100
1129
|
async function findMasonRoot(startDir) {
|
|
1101
1130
|
let dir = startDir;
|
|
1102
1131
|
for (let i = 0; i < MAX_WALK_UP; i++) {
|
|
1103
|
-
if (await exists(
|
|
1104
|
-
if (await exists(
|
|
1105
|
-
const parent =
|
|
1132
|
+
if (await exists(path9.join(dir, ".mason", "decisions"))) return dir;
|
|
1133
|
+
if (await exists(path9.join(dir, ".git"))) return null;
|
|
1134
|
+
const parent = path9.dirname(dir);
|
|
1106
1135
|
if (parent === dir) return null;
|
|
1107
1136
|
dir = parent;
|
|
1108
1137
|
}
|
|
@@ -1170,14 +1199,15 @@ async function runHook(stdinText, env = {}) {
|
|
|
1170
1199
|
if (input.tool_name && !SUPPORTED_TOOLS.has(input.tool_name)) return null;
|
|
1171
1200
|
const filePath = input.tool_input?.file_path;
|
|
1172
1201
|
if (!filePath || typeof filePath !== "string") return null;
|
|
1173
|
-
const absPath =
|
|
1174
|
-
const root = await findMasonRoot(
|
|
1202
|
+
const absPath = path9.isAbsolute(filePath) ? filePath : path9.resolve(input.cwd ?? process.cwd(), filePath);
|
|
1203
|
+
const root = await findMasonRoot(path9.dirname(absPath));
|
|
1175
1204
|
if (!root) return null;
|
|
1176
|
-
|
|
1205
|
+
if (env.rootDir && await fs7.realpath(root) !== await fs7.realpath(env.rootDir)) return null;
|
|
1206
|
+
const relPath = path9.relative(root, absPath).split(path9.sep).join("/");
|
|
1177
1207
|
if (relPath.startsWith("..")) return null;
|
|
1178
1208
|
const { records } = await loadDecisionStore(root);
|
|
1179
1209
|
const stateDir = env.stateDir ?? os2.tmpdir();
|
|
1180
|
-
const stateFile =
|
|
1210
|
+
const stateFile = path9.join(
|
|
1181
1211
|
stateDir,
|
|
1182
1212
|
`mason-hook-${createHash4("sha256").update(root).digest("hex").slice(0, 12)}-${stateKey(input)}.json`
|
|
1183
1213
|
);
|
|
@@ -1204,7 +1234,7 @@ async function runHook(stdinText, env = {}) {
|
|
|
1204
1234
|
}
|
|
1205
1235
|
return JSON.stringify({
|
|
1206
1236
|
hookSpecificOutput: {
|
|
1207
|
-
hookEventName: "PostToolUse",
|
|
1237
|
+
hookEventName: env.hookEventName ?? "PostToolUse",
|
|
1208
1238
|
additionalContext: formatContext(relPath, selected, drift)
|
|
1209
1239
|
}
|
|
1210
1240
|
});
|