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.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,
|
|
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(
|
|
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
|
|
61
|
+
for (const index of current.scopes) selected.add(index);
|
|
62
62
|
}
|
|
63
|
-
for (const
|
|
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,
|
|
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
|
|
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
|
-
|
|
685
|
+
reason = "Lock owner data is incomplete or malformed.";
|
|
686
686
|
} else if (owner.data.host !== os.hostname()) {
|
|
687
|
-
|
|
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
|
-
|
|
691
|
+
reason = `Local owner PID ${owner.data.pid} is still running.`;
|
|
692
692
|
} catch (error) {
|
|
693
|
-
|
|
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
|
-
|
|
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
|
-
|
|
703
|
+
reason += " A recovery guard also needs inspection: " + file + ".reclaim.";
|
|
704
704
|
}
|
|
705
|
-
return
|
|
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
|
|
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
|
|
993
|
-
while (
|
|
994
|
-
if (
|
|
995
|
-
const event = record.history[
|
|
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,
|
|
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(
|
|
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(
|
|
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 ? {
|
|
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 = (
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
|
|
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 :
|
|
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 :
|
|
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) ||
|
|
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
|
|
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
|
|
1933
|
+
const index = DOC_CANDIDATES.findIndex(
|
|
2007
1934
|
(p) => p.toLowerCase() === file.toLowerCase()
|
|
2008
1935
|
);
|
|
2009
|
-
return
|
|
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(
|
|
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(
|
|
2032
|
-
const parent =
|
|
2033
|
-
return
|
|
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(
|
|
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
|
|
2012
|
+
import path10 from "path";
|
|
2086
2013
|
function scopedPath(directory3, value) {
|
|
2087
|
-
if (
|
|
2014
|
+
if (path10.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value))
|
|
2088
2015
|
return null;
|
|
2089
|
-
const resolved =
|
|
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" ?
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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" + (
|
|
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(
|
|
2273
|
+
(file) => file.startsWith(path13.posix.join(dir, entry.name) + "/")
|
|
2347
2274
|
)) && !entry.name.startsWith(".") && !DIR_DENYLIST.has(entry.name)
|
|
2348
|
-
).map((entry) =>
|
|
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) =>
|
|
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
|
|
2302
|
+
let count3 = 0;
|
|
2376
2303
|
for (let offset = 0; offset < files.length; offset += 256) {
|
|
2377
|
-
|
|
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 (
|
|
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
|
|
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
|
|
2400
|
-
if (
|
|
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
|
|
2409
|
-
if (
|
|
2410
|
-
candidates.push({ dir: `${topDir}/${sub}`, sourceFileCount:
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
2539
|
-
} else if (await readAuditInput(root,
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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 (
|
|
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
|
|
3007
|
+
import path17 from "path";
|
|
3081
3008
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
3082
|
-
const resolvedRoot =
|
|
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
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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,
|
|
3643
|
+
function evidenceFingerprint(evidence, reason) {
|
|
3706
3644
|
return digest([
|
|
3707
3645
|
evidence.scope,
|
|
3708
3646
|
evidence.tree,
|
|
3709
|
-
...
|
|
3647
|
+
...reason === "no-dependency-claims" ? [] : [evidence.lastScopeCommit],
|
|
3710
3648
|
evidence.paths
|
|
3711
3649
|
]);
|
|
3712
3650
|
}
|
|
3713
|
-
function scopes(finding,
|
|
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 (
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
4187
|
-
const relative =
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
4495
|
-
|
|
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,
|
|
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
|
|
4542
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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(
|
|
4971
|
-
|
|
4972
|
-
) ? "history-unavailable" : error instanceof z15.ZodError || error instanceof SyntaxError || /Hook input|Expected one command|Unknown automation command|--host/.test(
|
|
4973
|
-
|
|
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
|
|
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,
|
|
5212
|
-
if (event.kind === "accepted" || event.kind === "reaffirmed") lastReview =
|
|
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
|
|
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([
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 =
|
|
5655
|
-
const
|
|
5656
|
-
|
|
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
|
-
|
|
5659
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5882
|
+
import path21 from "path";
|
|
5810
5883
|
async function repositoryRoot(dir) {
|
|
5811
|
-
if (!
|
|
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/
|
|
5928
|
+
// src/hook/hook.ts
|
|
5856
5929
|
import { z as z17 } from "zod";
|
|
5857
5930
|
import fs18 from "fs/promises";
|
|
5858
|
-
import
|
|
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
|
|
5868
|
-
|
|
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
|
|
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
|
|
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
|
|
5886
|
-
|
|
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 =
|
|
5911
|
-
configuredVersion:
|
|
5912
|
-
revision:
|
|
5913
|
-
fingerprint:
|
|
5914
|
-
mcpFingerprint:
|
|
5915
|
-
instructions:
|
|
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 =
|
|
5918
|
-
version:
|
|
5919
|
-
hosts:
|
|
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 =
|
|
5927
|
-
version:
|
|
5928
|
-
hosts:
|
|
6149
|
+
inactiveHostsSchema = z18.object({
|
|
6150
|
+
version: z18.literal(1),
|
|
6151
|
+
hosts: z18.array(z18.enum(["claude", "codex"]))
|
|
5929
6152
|
});
|
|
5930
|
-
receiptSchema =
|
|
5931
|
-
version:
|
|
5932
|
-
host:
|
|
5933
|
-
status:
|
|
5934
|
-
initialReportPath:
|
|
5935
|
-
initialBaselinePaths:
|
|
5936
|
-
root:
|
|
5937
|
-
revision:
|
|
5938
|
-
configuredAt:
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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.
|
|
6233
|
+
executingVersion = () => true ? "0.22.0" : "development";
|
|
6011
6234
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
6012
|
-
hookCommand = (host) => `mason --setup-host ${host}
|
|
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
|
|
6024
|
-
import { z as
|
|
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
|
|
6048
|
-
if (ws.root !== await
|
|
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 =
|
|
6100
|
-
version:
|
|
6101
|
-
masonVersion:
|
|
6102
|
-
root:
|
|
6103
|
-
revision:
|
|
6104
|
-
host:
|
|
6105
|
-
contextCalls:
|
|
6106
|
-
lastContextAt:
|
|
6107
|
-
sessions:
|
|
6108
|
-
|
|
6109
|
-
events:
|
|
6110
|
-
at:
|
|
6111
|
-
verificationStatus:
|
|
6112
|
-
reportPath:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
6186
|
-
if (!
|
|
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:
|
|
6189
|
-
...["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse"].includes(name) ? { hookSpecificOutput: { hookEventName: name, additionalContext:
|
|
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
|
|
6462
|
+
function hookConfig(host, command2 = "npx --no-install --package mason-context mason") {
|
|
6211
6463
|
const handler = {
|
|
6212
6464
|
type: "command",
|
|
6213
|
-
command:
|
|
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
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
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:
|
|
6250
|
-
tool_use_id:
|
|
6251
|
-
tool_response:
|
|
6252
|
-
tool_input:
|
|
6253
|
-
stop_hook_active:
|
|
6254
|
-
permission_mode:
|
|
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
|
|
6281
|
-
import
|
|
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
|
|
6571
|
+
await fs22.unlink(file2);
|
|
6319
6572
|
return true;
|
|
6320
6573
|
}
|
|
6321
6574
|
const file = await storePath(root, edit.path, true);
|
|
6322
|
-
const temporary =
|
|
6575
|
+
const temporary = path24.join(path24.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
6323
6576
|
try {
|
|
6324
|
-
const mode = await
|
|
6577
|
+
const mode = await fs22.stat(file).then(
|
|
6325
6578
|
(s) => s.mode & 511,
|
|
6326
6579
|
() => 384
|
|
6327
6580
|
);
|
|
6328
|
-
const handle = await
|
|
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
|
|
6590
|
+
await fs22.rename(temporary, file);
|
|
6338
6591
|
return true;
|
|
6339
6592
|
} finally {
|
|
6340
|
-
await
|
|
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
|
|
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 =
|
|
6388
|
-
mcpServers:
|
|
6389
|
-
hooks:
|
|
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 =
|
|
6432
|
-
created:
|
|
6433
|
-
initialHash:
|
|
6434
|
-
blocks:
|
|
6435
|
-
mcpHash:
|
|
6436
|
-
hookCommands:
|
|
6437
|
-
hooks:
|
|
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 =
|
|
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
|
|
6445
|
-
var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_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 =
|
|
6452
|
-
version:
|
|
6453
|
-
initializedAt:
|
|
6454
|
-
features:
|
|
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
|
-
-
|
|
6469
|
-
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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,
|
|
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 =
|
|
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
|
|
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 =
|
|
6837
|
-
hooks:
|
|
7089
|
+
groupSchema = z25.object({
|
|
7090
|
+
hooks: z25.array(z25.object({ type: z25.string(), command: z25.string().optional() }).passthrough())
|
|
6838
7091
|
}).passthrough();
|
|
6839
|
-
automationConfigSchema =
|
|
6840
|
-
recordSchema2 =
|
|
6841
|
-
version:
|
|
6842
|
-
hosts:
|
|
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
|
|
6857
|
-
import
|
|
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
|
|
6861
|
-
async function
|
|
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
|
|
6883
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
8232
|
-
const instructionsCurrent =
|
|
8233
|
-
const
|
|
8234
|
-
const
|
|
8235
|
-
const
|
|
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 || !
|
|
7558
|
+
if (!mcp || !hooks || !config.instructionCompatible || !configured)
|
|
8269
7559
|
pending.push(
|
|
8270
|
-
"
|
|
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 &&
|
|
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" : "
|
|
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:
|
|
8296
|
-
"
|
|
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((
|
|
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((
|
|
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
|
|
8379
|
-
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
8413
|
-
|
|
8414
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8434
|
-
|
|
8435
|
-
await
|
|
8436
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
|
8512
|
-
import
|
|
8513
|
-
import { execFile as
|
|
8514
|
-
import { promisify as
|
|
8515
|
-
import { createHash as
|
|
8516
|
-
import { z as
|
|
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) =>
|
|
7859
|
+
(entry) => path26.posix.isAbsolute(entry) && path26.posix.resolve(entry) === path26.posix.resolve(bin)
|
|
8523
7860
|
);
|
|
8524
|
-
const setupHint = "To
|
|
8525
|
-
const
|
|
8526
|
-
const manual = (
|
|
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: `${
|
|
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
|
|
7884
|
+
return { status: "configured", changes, message };
|
|
8548
7885
|
}
|
|
8549
|
-
const shell =
|
|
8550
|
-
const userHome = options.userHome ??
|
|
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 = [
|
|
8555
|
-
else if (shell === "zsh") profiles2 = [
|
|
8556
|
-
else if (shell === "sh") profiles2 = [
|
|
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) =>
|
|
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 = [
|
|
7910
|
+
profiles2 = [path26.join(userHome, ".bashrc"), login];
|
|
8574
7911
|
}
|
|
8575
|
-
const marker = `# >>> mason PATH ${
|
|
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) :
|
|
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(
|
|
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
|
|
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
|
|
8639
|
-
if (
|
|
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,
|
|
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
|
|
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 =
|
|
8719
|
-
pathChangeSchema =
|
|
8720
|
-
|
|
8721
|
-
kind:
|
|
8722
|
-
file:
|
|
8723
|
-
block:
|
|
8724
|
-
created:
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
8752
|
-
import
|
|
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
|
|
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 =
|
|
8769
|
-
const home =
|
|
8770
|
-
if (
|
|
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
|
|
8777
|
-
if (!
|
|
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 } =
|
|
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 =
|
|
8803
|
-
bundleId =
|
|
8804
|
-
updatePolicySchema =
|
|
8805
|
-
enabled:
|
|
8806
|
-
revision:
|
|
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:
|
|
8145
|
+
mirror: z28.string().optional()
|
|
8809
8146
|
});
|
|
8810
|
-
recordSchema3 =
|
|
8811
|
-
format:
|
|
8812
|
-
home:
|
|
8813
|
-
bin:
|
|
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:
|
|
8817
|
-
previousLaunchers:
|
|
8818
|
-
pathChanges:
|
|
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:
|
|
8821
|
-
pending:
|
|
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" ?
|
|
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
|
|
8830
|
-
import { createHash as
|
|
8831
|
-
import { z as
|
|
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 === "..") ||
|
|
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 =
|
|
8883
|
-
bundleSchema =
|
|
8884
|
-
format:
|
|
8885
|
-
version:
|
|
8886
|
-
target:
|
|
8887
|
-
nodeVersion:
|
|
8888
|
-
files:
|
|
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) =>
|
|
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
|
|
8916
|
-
import
|
|
8252
|
+
import os6 from "os";
|
|
8253
|
+
import path29 from "path";
|
|
8917
8254
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
8918
|
-
import { z as
|
|
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 !==
|
|
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 =
|
|
8989
|
-
pid:
|
|
8990
|
-
host:
|
|
8991
|
-
bundle:
|
|
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
|
|
8335
|
+
import os7 from "os";
|
|
8999
8336
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
9000
|
-
import { z as
|
|
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 !==
|
|
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:
|
|
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 =
|
|
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
|
|
9059
|
-
import
|
|
9060
|
-
import { execFile as
|
|
9061
|
-
import { promisify as
|
|
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 =
|
|
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
|
|
9087
|
-
|
|
9088
|
-
[
|
|
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 =
|
|
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 =
|
|
9120
|
-
process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ?
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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 (
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
9638
|
-
[
|
|
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
|
-
|
|
9689
|
-
[
|
|
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 =
|
|
9745
|
-
|
|
9746
|
-
tag_name:
|
|
9747
|
-
draft:
|
|
9748
|
-
prerelease:
|
|
9749
|
-
published_at:
|
|
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 =
|
|
9753
|
-
format:
|
|
9754
|
-
version:
|
|
9755
|
-
publishedAt:
|
|
9756
|
-
assets:
|
|
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 =
|
|
9759
|
-
lastAttempt:
|
|
9760
|
-
availableVersion:
|
|
9761
|
-
error:
|
|
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
|
|
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} ${
|
|
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 (
|
|
9138
|
+
if (message && interactive) {
|
|
9802
9139
|
render(success ? "\u2713" : "\u2717");
|
|
9803
9140
|
write("\n");
|
|
9804
9141
|
}
|
|
9805
|
-
|
|
9142
|
+
message = "";
|
|
9806
9143
|
};
|
|
9807
9144
|
return {
|
|
9808
9145
|
step(next) {
|
|
9809
9146
|
stop();
|
|
9810
|
-
|
|
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: ${
|
|
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
|
|
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 (
|
|
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
|
|
9193
|
+
var USAGE = `Usage: mason <init|setup|teardown|install|config|status|check|hook|stats> [options]
|
|
9856
9194
|
|
|
9857
|
-
setup [--host claude|codex]
|
|
9858
|
-
|
|
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 &&
|
|
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
|
-
|
|
9980
|
-
|
|
9981
|
-
|
|
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
|
|
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:
|
|
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(
|
|
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.
|
|
9422
|
+
var usage = `Mason ${"0.22.0"}
|
|
10073
9423
|
|
|
10074
9424
|
Usage: mason <command> [options]
|
|
10075
9425
|
|
|
10076
|
-
setup --host codex|claude
|
|
10077
|
-
|
|
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
|
-
|
|
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 (!
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
|
10240
|
-
if (
|
|
10241
|
-
console.log(JSON.stringify({ systemMessage:
|
|
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(
|
|
9596
|
+
console.error(message);
|
|
10245
9597
|
process.exitCode = 2;
|
|
10246
9598
|
}
|
|
10247
9599
|
}
|