mason-context 0.17.2 → 0.17.3
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 +9 -0
- package/dist/mason-audit.js +127 -34
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-auto.js +241 -96
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-mcp.js +230 -93
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason.js +250 -105
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/dist/mason-mcp.js
CHANGED
|
@@ -9,6 +9,28 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/utils/profile.ts
|
|
13
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
14
|
+
async function profilePhase(label, run) {
|
|
15
|
+
const phases = profiles.getStore();
|
|
16
|
+
if (!phases) return run();
|
|
17
|
+
const start = performance.now();
|
|
18
|
+
try {
|
|
19
|
+
return await run();
|
|
20
|
+
} finally {
|
|
21
|
+
const timing = phases[label] ??= { calls: 0, totalMs: 0 };
|
|
22
|
+
timing.calls++;
|
|
23
|
+
timing.totalMs += performance.now() - start;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
var profiles;
|
|
27
|
+
var init_profile = __esm({
|
|
28
|
+
"src/utils/profile.ts"() {
|
|
29
|
+
"use strict";
|
|
30
|
+
profiles = new AsyncLocalStorage();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
12
34
|
// src/audit/commands.ts
|
|
13
35
|
import path from "path";
|
|
14
36
|
function splitCommands(line) {
|
|
@@ -854,16 +876,27 @@ import path6 from "path";
|
|
|
854
876
|
import { execFile as execFile4 } from "child_process";
|
|
855
877
|
import { promisify as promisify4 } from "util";
|
|
856
878
|
import fg3 from "fast-glob";
|
|
857
|
-
async function
|
|
879
|
+
async function dirtyDocs(resolvedRoot, files) {
|
|
880
|
+
if (!files.length) return /* @__PURE__ */ new Set();
|
|
858
881
|
try {
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
882
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
883
|
+
for (let offset = 0; offset < files.length; offset += 256) {
|
|
884
|
+
const { stdout } = await exec3(
|
|
885
|
+
"git",
|
|
886
|
+
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
|
|
887
|
+
{ cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
888
|
+
);
|
|
889
|
+
const entries = stdout.split("\0");
|
|
890
|
+
for (let i = 0; i < entries.length; i++) {
|
|
891
|
+
if (!entries[i]) continue;
|
|
892
|
+
const status = entries[i].slice(0, 2);
|
|
893
|
+
dirty.add(entries[i].slice(3));
|
|
894
|
+
if (/[RC]/.test(status) && entries[i + 1]) dirty.add(entries[++i]);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return dirty;
|
|
865
898
|
} catch {
|
|
866
|
-
return
|
|
899
|
+
return new Set(files);
|
|
867
900
|
}
|
|
868
901
|
}
|
|
869
902
|
async function discoverDocPaths(root) {
|
|
@@ -944,21 +977,30 @@ function documentScope(file) {
|
|
|
944
977
|
return path6.posix.basename(parent).toLowerCase() === ".claude" ? path6.posix.dirname(parent) : parent;
|
|
945
978
|
}
|
|
946
979
|
async function discoverDocs(resolvedRoot) {
|
|
980
|
+
return profilePhase("audit.documents", () => readDocs(resolvedRoot));
|
|
981
|
+
}
|
|
982
|
+
async function readDocs(resolvedRoot) {
|
|
947
983
|
const docs = [];
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
984
|
+
const files = await discoverDocPaths(resolvedRoot);
|
|
985
|
+
const dirty = await dirtyDocs(resolvedRoot, files);
|
|
986
|
+
for (let offset = 0; offset < files.length; offset += 4) {
|
|
987
|
+
docs.push(...await Promise.all(files.slice(offset, offset + 4).map(async (candidate) => {
|
|
988
|
+
const [content2, lastCommit] = await Promise.all([
|
|
989
|
+
readAuditInput(resolvedRoot, candidate),
|
|
990
|
+
profilePhase("audit.document-history", () => lastCommitOf(resolvedRoot, candidate))
|
|
991
|
+
]);
|
|
992
|
+
if (content2 === null) throw new Error("Document disappeared during discovery: " + candidate);
|
|
993
|
+
return {
|
|
994
|
+
path: candidate,
|
|
995
|
+
content: content2,
|
|
996
|
+
scope: documentScope(candidate),
|
|
997
|
+
kind: /^readme\.md$/i.test(path6.posix.basename(candidate)) ? "readme" : "instructions",
|
|
998
|
+
lineCount: content2.split("\n").length,
|
|
999
|
+
lastCommit,
|
|
1000
|
+
dirty: dirty.has(candidate),
|
|
1001
|
+
claims: extractClaims(content2)
|
|
1002
|
+
};
|
|
1003
|
+
})));
|
|
962
1004
|
}
|
|
963
1005
|
return docs;
|
|
964
1006
|
}
|
|
@@ -966,6 +1008,7 @@ var exec3, DOC_CANDIDATES;
|
|
|
966
1008
|
var init_docs = __esm({
|
|
967
1009
|
"src/audit/docs.ts"() {
|
|
968
1010
|
"use strict";
|
|
1011
|
+
init_profile();
|
|
969
1012
|
init_claims();
|
|
970
1013
|
init_git();
|
|
971
1014
|
init_inputs();
|
|
@@ -3180,6 +3223,9 @@ var init_advisory_review = __esm({
|
|
|
3180
3223
|
import fs11 from "fs/promises";
|
|
3181
3224
|
import path18 from "path";
|
|
3182
3225
|
async function computeAudit(rootDir, options = {}) {
|
|
3226
|
+
return profilePhase("audit.current", () => auditCurrent(rootDir, options));
|
|
3227
|
+
}
|
|
3228
|
+
async function auditCurrent(rootDir, options) {
|
|
3183
3229
|
const resolvedRoot = path18.resolve(rootDir);
|
|
3184
3230
|
const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
|
|
3185
3231
|
if (docs.length === 0) return null;
|
|
@@ -3206,11 +3252,11 @@ async function computeAudit(rootDir, options = {}) {
|
|
|
3206
3252
|
};
|
|
3207
3253
|
if (!report.gitAvailable) return report;
|
|
3208
3254
|
const changesSinceDoc = /* @__PURE__ */ new Map();
|
|
3255
|
+
const changesByCommit = /* @__PURE__ */ new Map();
|
|
3209
3256
|
for (const doc of docs) {
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
);
|
|
3257
|
+
const hash2 = doc.lastCommit?.hash;
|
|
3258
|
+
if (hash2 && !changesByCommit.has(hash2)) changesByCommit.set(hash2, await getChangesWithStatus(resolvedRoot, hash2));
|
|
3259
|
+
changesSinceDoc.set(doc.path, hash2 ? changesByCommit.get(hash2) : null);
|
|
3214
3260
|
}
|
|
3215
3261
|
let decisionsPresent = false;
|
|
3216
3262
|
try {
|
|
@@ -3244,6 +3290,7 @@ async function computeAudit(rootDir, options = {}) {
|
|
|
3244
3290
|
var init_audit = __esm({
|
|
3245
3291
|
"src/audit/audit.ts"() {
|
|
3246
3292
|
"use strict";
|
|
3293
|
+
init_profile();
|
|
3247
3294
|
init_drift();
|
|
3248
3295
|
init_snapshot();
|
|
3249
3296
|
init_docs();
|
|
@@ -3263,7 +3310,9 @@ __export(repair_exports, {
|
|
|
3263
3310
|
prepareRepair: () => prepareRepair,
|
|
3264
3311
|
repairExitCode: () => repairExitCode,
|
|
3265
3312
|
repairFindingSchema: () => repairFindingSchema,
|
|
3266
|
-
|
|
3313
|
+
reportSchema: () => reportSchema,
|
|
3314
|
+
verifyRepair: () => verifyRepair,
|
|
3315
|
+
withRepairSession: () => withRepairSession
|
|
3267
3316
|
});
|
|
3268
3317
|
import fs12 from "fs/promises";
|
|
3269
3318
|
import path19 from "path";
|
|
@@ -3290,6 +3339,9 @@ async function docState(root) {
|
|
|
3290
3339
|
return digest(docs);
|
|
3291
3340
|
}
|
|
3292
3341
|
async function stableAudit(root, checks, options = {}) {
|
|
3342
|
+
return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
|
|
3343
|
+
}
|
|
3344
|
+
async function readStableAudit(root, checks, options) {
|
|
3293
3345
|
const head = await getCurrentGitHash(root);
|
|
3294
3346
|
const before = await docState(root);
|
|
3295
3347
|
const report = await computeAudit(root, { ...options, checks });
|
|
@@ -3299,9 +3351,46 @@ async function stableAudit(root, checks, options = {}) {
|
|
|
3299
3351
|
return report;
|
|
3300
3352
|
}
|
|
3301
3353
|
async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
|
|
3354
|
+
return prepareOriginal(rootDir, checks, options);
|
|
3355
|
+
}
|
|
3356
|
+
async function withRepairSession(root, options, run, cachedAudit) {
|
|
3357
|
+
const before = await Promise.all([getCurrentGitHash(root), docState(root), advisoryReviewInventory(root).then(digest)]);
|
|
3358
|
+
const audits = /* @__PURE__ */ new Map();
|
|
3359
|
+
const documents = /* @__PURE__ */ new Map();
|
|
3360
|
+
const history = /* @__PURE__ */ new Map();
|
|
3361
|
+
const assessments = /* @__PURE__ */ new Map();
|
|
3362
|
+
const memo = (cache, key, read) => {
|
|
3363
|
+
if (!cache.has(key)) cache.set(key, read());
|
|
3364
|
+
return cache.get(key);
|
|
3365
|
+
};
|
|
3366
|
+
const session = {
|
|
3367
|
+
audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
|
|
3368
|
+
if (cachedAudit && cachedAudit.headHash === before[0] && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
|
|
3369
|
+
const current = structuredClone(cachedAudit);
|
|
3370
|
+
const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, before[0])))).filter((r) => r !== null);
|
|
3371
|
+
delete current.advisoryReviews;
|
|
3372
|
+
if (reviews.length) current.advisoryReviews = reviews;
|
|
3373
|
+
return current;
|
|
3374
|
+
}
|
|
3375
|
+
return computeAudit(root, { ...options, checks });
|
|
3376
|
+
}),
|
|
3377
|
+
document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
|
|
3378
|
+
history: (head) => memo(history, head, () => getChangesWithStatus(root, head)),
|
|
3379
|
+
assessment: (dir, finding, head) => memo(assessments, digest([dir, finding, head]), () => assessAdvisory(dir, finding, head))
|
|
3380
|
+
};
|
|
3381
|
+
const result = await run({
|
|
3382
|
+
prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
|
|
3383
|
+
verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
|
|
3384
|
+
currentAudit: () => session.audit(ALL_CHECKS)
|
|
3385
|
+
});
|
|
3386
|
+
const after = await Promise.all([getCurrentGitHash(root), docState(root), advisoryReviewInventory(root).then(digest)]);
|
|
3387
|
+
if (digest(before) !== digest(after)) throw new Error("HEAD, context files or advisory review records changed during verification; retry against stable evidence.");
|
|
3388
|
+
return result;
|
|
3389
|
+
}
|
|
3390
|
+
async function prepareOriginal(rootDir, checks, options, session) {
|
|
3302
3391
|
const root = await fs12.realpath(rootDir);
|
|
3303
3392
|
const selected = z7.array(checkSchema).nonempty().parse(checks);
|
|
3304
|
-
const report = await stableAudit(root, selected, options);
|
|
3393
|
+
const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
|
|
3305
3394
|
if (!report) throw new Error("No context files found to prepare a repair.");
|
|
3306
3395
|
if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
|
|
3307
3396
|
const storedReport = reportSchema.parse(report);
|
|
@@ -3327,23 +3416,26 @@ async function loadRepairBaseline(rootDir, baselinePath) {
|
|
|
3327
3416
|
return { root, relative, original };
|
|
3328
3417
|
}
|
|
3329
3418
|
async function verifyRepair(rootDir, baselinePath, options = {}) {
|
|
3419
|
+
return profilePhase("repair.verify", () => verifyOriginal(rootDir, baselinePath, options));
|
|
3420
|
+
}
|
|
3421
|
+
async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
3330
3422
|
const { root, relative, original } = await loadRepairBaseline(rootDir, baselinePath);
|
|
3331
3423
|
const diagnostics = [];
|
|
3332
3424
|
let reviewDigest;
|
|
3333
3425
|
let current = null;
|
|
3334
3426
|
try {
|
|
3335
|
-
reviewDigest = digest(await advisoryReviewInventory(root));
|
|
3336
|
-
current = await stableAudit(root, original.checksRun, options);
|
|
3427
|
+
if (!session) reviewDigest = digest(await advisoryReviewInventory(root));
|
|
3428
|
+
current = await (session ? session.audit(original.checksRun) : stableAudit(root, original.checksRun, options));
|
|
3337
3429
|
if (!current) diagnostics.push("No context files remain available to audit.");
|
|
3338
3430
|
else if (!current.gitAvailable) diagnostics.push("Git history is unavailable.");
|
|
3339
3431
|
for (const doc of original.docs) {
|
|
3340
3432
|
if (!allFindings(original).some((f) => f.anchor.doc === doc.path) || !current?.docs.some((d) => d.path === doc.path)) continue;
|
|
3341
|
-
const content2 = await readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024);
|
|
3433
|
+
const content2 = await (session ? session.document(doc.path) : readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024));
|
|
3342
3434
|
if (content2 === null || !content2.trim()) {
|
|
3343
3435
|
diagnostics.push("Original context file " + doc.path + " is empty or unreadable; losing its claims does not verify a repair.");
|
|
3344
3436
|
}
|
|
3345
3437
|
}
|
|
3346
|
-
if (await getChangesWithStatus(root, original.headHash) === null) {
|
|
3438
|
+
if (await (session ? session.history(original.headHash) : getChangesWithStatus(root, original.headHash)) === null) {
|
|
3347
3439
|
diagnostics.push("The original audit commit is unavailable; repair history cannot be verified.");
|
|
3348
3440
|
}
|
|
3349
3441
|
} catch (error) {
|
|
@@ -3368,7 +3460,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
|
|
|
3368
3460
|
if (!current.checksRun?.includes(finding.type) || skipped.length) {
|
|
3369
3461
|
return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
|
|
3370
3462
|
}
|
|
3371
|
-
const review = await assessAdvisory(root, now ?? finding, original.headHash);
|
|
3463
|
+
const review = await (session?.assessment ?? assessAdvisory)(root, now ?? finding, original.headHash);
|
|
3372
3464
|
if (review) {
|
|
3373
3465
|
if (review.status === "unverified") return { ...base, review, status: "unverified", reason: review.reason };
|
|
3374
3466
|
if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
|
|
@@ -3383,7 +3475,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
|
|
|
3383
3475
|
}
|
|
3384
3476
|
return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
|
|
3385
3477
|
}));
|
|
3386
|
-
try {
|
|
3478
|
+
if (!session) try {
|
|
3387
3479
|
if (reviewDigest !== digest(await advisoryReviewInventory(root)) || current?.headHash !== await getCurrentGitHash(root)) {
|
|
3388
3480
|
diagnostics.push("HEAD or advisory review records changed during verification; repeat against stable evidence.");
|
|
3389
3481
|
for (const finding of findings) {
|
|
@@ -3436,6 +3528,7 @@ var count2, reportSchema, baselineSchema, repairFindingSchema;
|
|
|
3436
3528
|
var init_repair = __esm({
|
|
3437
3529
|
"src/audit/repair.ts"() {
|
|
3438
3530
|
"use strict";
|
|
3531
|
+
init_profile();
|
|
3439
3532
|
init_audit();
|
|
3440
3533
|
init_docs();
|
|
3441
3534
|
init_snapshot();
|
|
@@ -3496,20 +3589,20 @@ import { promisify as promisify9 } from "util";
|
|
|
3496
3589
|
import fg5 from "fast-glob";
|
|
3497
3590
|
import { z as z8 } from "zod";
|
|
3498
3591
|
async function git3(root, ...args) {
|
|
3499
|
-
return (await exec7("git", args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout;
|
|
3592
|
+
return profilePhase("automation.git", async () => (await exec7("git", args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
|
|
3500
3593
|
}
|
|
3501
3594
|
async function workspace(dir) {
|
|
3502
3595
|
const root = await fs13.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
|
|
3503
|
-
const gitDir = await
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
} catch {
|
|
3508
|
-
branch = "detached";
|
|
3509
|
-
}
|
|
3596
|
+
const [gitDir, branch] = await Promise.all([
|
|
3597
|
+
git3(root, "rev-parse", "--absolute-git-dir").then((value) => fs13.realpath(value.trim())),
|
|
3598
|
+
git3(root, "symbolic-ref", "--quiet", "HEAD").then((value) => value.trim(), () => "detached")
|
|
3599
|
+
]);
|
|
3510
3600
|
return { root, gitDir, branch, directory: ".mason/reports/automation/" + hash([root, gitDir, branch]).slice(0, 24) };
|
|
3511
3601
|
}
|
|
3512
3602
|
async function readInputs(root) {
|
|
3603
|
+
return profilePhase("automation.inputs", () => collectInputs(root));
|
|
3604
|
+
}
|
|
3605
|
+
async function collectInputs(root) {
|
|
3513
3606
|
const docPaths = await discoverDocPaths(root);
|
|
3514
3607
|
const [headText, docStatus, shallowPath, replacements] = await Promise.all([
|
|
3515
3608
|
git3(root, "rev-parse", "HEAD"),
|
|
@@ -3526,21 +3619,29 @@ async function readInputs(root) {
|
|
|
3526
3619
|
}
|
|
3527
3620
|
const docs = {};
|
|
3528
3621
|
const docContents = [];
|
|
3622
|
+
const parsedDocs = [];
|
|
3529
3623
|
const claims = [];
|
|
3624
|
+
const presence = /* @__PURE__ */ new Map();
|
|
3625
|
+
const exists = (file) => {
|
|
3626
|
+
if (!presence.has(file)) presence.set(file, pathExists(root, file));
|
|
3627
|
+
return presence.get(file);
|
|
3628
|
+
};
|
|
3530
3629
|
for (const file of docPaths) {
|
|
3531
3630
|
const text2 = await content(root, file);
|
|
3532
3631
|
docs[file] = text2 === null ? null : hash(text2);
|
|
3533
3632
|
docContents.push([file, text2]);
|
|
3534
|
-
|
|
3633
|
+
const parsed = { path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") };
|
|
3634
|
+
parsedDocs.push(parsed);
|
|
3635
|
+
for (const claim of parsed.claims.paths) {
|
|
3535
3636
|
const scope = pathClaimScope(file, claim);
|
|
3536
3637
|
if (scope?.candidates.some(gitMetadataPath)) continue;
|
|
3537
3638
|
for (const candidate of scope?.candidates ?? []) {
|
|
3538
3639
|
if (optionalMasonPath(candidate)) continue;
|
|
3539
|
-
|
|
3640
|
+
const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path20.posix.dirname(candidate))]);
|
|
3641
|
+
claims.push([candidate, filePresent, parentPresent]);
|
|
3540
3642
|
}
|
|
3541
3643
|
}
|
|
3542
3644
|
}
|
|
3543
|
-
const parsedDocs = docContents.map(([file, text2]) => ({ path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") }));
|
|
3544
3645
|
const combinedDocs = moduleDocumentation(parsedDocs);
|
|
3545
3646
|
const countClaims = parsedDocs.flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
|
|
3546
3647
|
const decisionDirectory = await storePath(root, ".mason/decisions");
|
|
@@ -3562,7 +3663,7 @@ async function readInputs(root) {
|
|
|
3562
3663
|
git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
|
|
3563
3664
|
git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
|
|
3564
3665
|
]) : ["", ""];
|
|
3565
|
-
const common = [
|
|
3666
|
+
const common = [5, engineVersion, head, shallow, replacements, docContents];
|
|
3566
3667
|
const keys = {
|
|
3567
3668
|
"deleted-reference": hash([common, claims, docStatus]),
|
|
3568
3669
|
"new-module": hash([common, modules]),
|
|
@@ -3587,21 +3688,29 @@ function checkCache(raw, inputs) {
|
|
|
3587
3688
|
reused.add(name);
|
|
3588
3689
|
return structuredClone(entries[name].result);
|
|
3589
3690
|
}
|
|
3590
|
-
const result = await CHECKS[name](ctx);
|
|
3691
|
+
const result = await profilePhase("check." + name, () => CHECKS[name](ctx));
|
|
3591
3692
|
ran.add(name);
|
|
3592
3693
|
if (!result.skipped.length) entries[name] = { key: inputs.keys[name], result };
|
|
3593
3694
|
else delete entries[name];
|
|
3594
3695
|
return result;
|
|
3595
3696
|
} };
|
|
3596
|
-
return {
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3697
|
+
return {
|
|
3698
|
+
options,
|
|
3699
|
+
ran,
|
|
3700
|
+
reused,
|
|
3701
|
+
diagnostic,
|
|
3702
|
+
has: (name) => entries[name]?.key === inputs.keys[name] && !entries[name].result.skipped.length,
|
|
3703
|
+
serialize: () => {
|
|
3704
|
+
const canonical = cacheSchema.shape.entries.parse(entries);
|
|
3705
|
+
return { version: 1, entries: canonical, digest: hash(canonical) };
|
|
3706
|
+
}
|
|
3707
|
+
};
|
|
3600
3708
|
}
|
|
3601
3709
|
var exec7, engineVersion, hash, content, cacheSchema;
|
|
3602
3710
|
var init_evidence = __esm({
|
|
3603
3711
|
"src/automation/evidence.ts"() {
|
|
3604
3712
|
"use strict";
|
|
3713
|
+
init_profile();
|
|
3605
3714
|
init_docs();
|
|
3606
3715
|
init_scope();
|
|
3607
3716
|
init_claims();
|
|
@@ -3614,7 +3723,7 @@ var init_evidence = __esm({
|
|
|
3614
3723
|
init_storage();
|
|
3615
3724
|
init_advisory_review();
|
|
3616
3725
|
exec7 = promisify9(execFile9);
|
|
3617
|
-
engineVersion = true ? "0.17.
|
|
3726
|
+
engineVersion = true ? "0.17.3" : "development";
|
|
3618
3727
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3619
3728
|
content = readAuditInput;
|
|
3620
3729
|
cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
|
|
@@ -3706,7 +3815,7 @@ var init_launcher = __esm({
|
|
|
3706
3815
|
"use strict";
|
|
3707
3816
|
init_evidence();
|
|
3708
3817
|
init_model();
|
|
3709
|
-
executingVersion = () => true ? "0.17.
|
|
3818
|
+
executingVersion = () => true ? "0.17.3" : "development";
|
|
3710
3819
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
3711
3820
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
3712
3821
|
}
|
|
@@ -3981,7 +4090,7 @@ __export(runtime_exports, {
|
|
|
3981
4090
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
3982
4091
|
import { z as z13 } from "zod";
|
|
3983
4092
|
async function observeReadOnlyTool(dir, event) {
|
|
3984
|
-
const ws = await workspace(dir);
|
|
4093
|
+
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
3985
4094
|
await withLock(ws.root, ws.directory, async () => {
|
|
3986
4095
|
const statePath = ws.directory + "/state.json";
|
|
3987
4096
|
const raw = await readStoreJson(ws.root, statePath);
|
|
@@ -4028,24 +4137,41 @@ async function loadState(ws) {
|
|
|
4028
4137
|
}
|
|
4029
4138
|
return state;
|
|
4030
4139
|
}
|
|
4031
|
-
async function
|
|
4140
|
+
async function matchingAnalysis(ws, state, inputs) {
|
|
4032
4141
|
const pointer = state.analysis;
|
|
4033
|
-
if (!pointer || pointer.fingerprint !== inputs.fingerprint
|
|
4034
|
-
|
|
4142
|
+
if (!pointer || pointer.fingerprint !== inputs.fingerprint) return { analysis: null };
|
|
4143
|
+
if (pointer.path !== ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json") {
|
|
4144
|
+
throw new Error("Invalid automation analysis cache path; inspect the retained state.");
|
|
4145
|
+
}
|
|
4146
|
+
const invalid = { analysis: null, diagnostic: "Discarded an invalid current-audit cache; audit preparation and original verification were repeated." };
|
|
4147
|
+
let raw;
|
|
4148
|
+
try {
|
|
4149
|
+
raw = await readStoreJson(ws.root, pointer.path);
|
|
4150
|
+
} catch (error) {
|
|
4151
|
+
const cause = error.cause;
|
|
4152
|
+
if (cause instanceof SyntaxError || cause instanceof Error && cause.message === "expected a JSON object, received null") return invalid;
|
|
4153
|
+
throw error;
|
|
4154
|
+
}
|
|
4155
|
+
if (raw === null) return { analysis: null };
|
|
4156
|
+
const parsed = sharedSchema.safeParse(raw);
|
|
4157
|
+
if (!parsed.success) return invalid;
|
|
4158
|
+
const stored = parsed.data;
|
|
4035
4159
|
const a = stored.analysis;
|
|
4036
4160
|
if (stored.digest !== hash(a) || a.id !== pointer.id || a.fingerprint !== inputs.fingerprint || a.completedAt !== pointer.completedAt || a.report.root !== ws.root || a.report.branch !== ws.branch || a.report.head !== inputs.head || hash(a.report.baselinePaths) !== hash(state.baselines.map((b) => b.path))) {
|
|
4037
|
-
|
|
4161
|
+
return invalid;
|
|
4038
4162
|
}
|
|
4039
4163
|
if (a.baselineDigest !== await baselineDigest(ws, state)) throw new Error("Repair baseline was modified; use the original baseline.");
|
|
4040
|
-
return a;
|
|
4164
|
+
return { analysis: a };
|
|
4041
4165
|
}
|
|
4042
4166
|
async function verifyInputs(ws, inputs) {
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4167
|
+
return profilePhase("automation.revalidate", async () => {
|
|
4168
|
+
const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
|
|
4169
|
+
if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
|
|
4170
|
+
throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
|
|
4171
|
+
}
|
|
4172
|
+
});
|
|
4047
4173
|
}
|
|
4048
|
-
async function computeAnalysis(ws, state, inputs, event) {
|
|
4174
|
+
async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
|
|
4049
4175
|
const baselines = [...state.baselines];
|
|
4050
4176
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4051
4177
|
const empty = {
|
|
@@ -4074,7 +4200,7 @@ async function computeAnalysis(ws, state, inputs, event) {
|
|
|
4074
4200
|
}
|
|
4075
4201
|
}
|
|
4076
4202
|
let cached = null;
|
|
4077
|
-
const diagnostics = [];
|
|
4203
|
+
const diagnostics = cacheDiagnostic ? [cacheDiagnostic] : [];
|
|
4078
4204
|
try {
|
|
4079
4205
|
cached = await readStoreJson(ws.root, ws.directory + "/cache.json");
|
|
4080
4206
|
} catch {
|
|
@@ -4082,19 +4208,26 @@ async function computeAnalysis(ws, state, inputs, event) {
|
|
|
4082
4208
|
}
|
|
4083
4209
|
const cache = checkCache(cached, inputs);
|
|
4084
4210
|
if (cache.diagnostic) diagnostics.push(cache.diagnostic);
|
|
4085
|
-
const
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4211
|
+
const cachedAudit = previousAudit?.gitAvailable && previousAudit.headHash === inputs.head && previousAudit.root === ws.root && !previousAudit.skippedChecks.length && ALL_CHECKS.every((name) => previousAudit.checksRun?.includes(name) && cache.has(name)) ? previousAudit : void 0;
|
|
4212
|
+
if (cachedAudit) for (const name of ALL_CHECKS) cache.reused.add(name);
|
|
4213
|
+
let currentAudit = null;
|
|
4214
|
+
const verifications = await withRepairSession(ws.root, cache.options, async (repairs) => {
|
|
4215
|
+
const saveBaseline = async () => {
|
|
4216
|
+
if (baselines.length >= 128) throw new Error("128 retained baselines need review; automatic capture stopped without discarding original evidence.");
|
|
4217
|
+
const prepared = await repairs.prepare();
|
|
4218
|
+
baselines.push({ path: prepared.baselinePath, at: now, event: event.event, fingerprint: inputs.fingerprint });
|
|
4219
|
+
};
|
|
4220
|
+
if (!baselines.length) await saveBaseline();
|
|
4221
|
+
const verifications2 = [];
|
|
4222
|
+
for (const baseline of baselines) verifications2.push(await repairs.verify(baseline.path));
|
|
4223
|
+
const known = new Set(verifications2.flatMap((v) => v.findings.map((f) => f.id)));
|
|
4224
|
+
if (verifications2.some((v) => v.newFindings.some((f) => !known.has(findingId(f))))) {
|
|
4225
|
+
await saveBaseline();
|
|
4226
|
+
verifications2.push(await repairs.verify(baselines.at(-1).path));
|
|
4227
|
+
}
|
|
4228
|
+
currentAudit = await repairs.currentAudit();
|
|
4229
|
+
return verifications2;
|
|
4230
|
+
}, cachedAudit);
|
|
4098
4231
|
const merged = /* @__PURE__ */ new Map();
|
|
4099
4232
|
for (const verification of verifications) {
|
|
4100
4233
|
for (const finding of verification.findings) {
|
|
@@ -4115,20 +4248,20 @@ async function computeAnalysis(ws, state, inputs, event) {
|
|
|
4115
4248
|
reportPath: "",
|
|
4116
4249
|
findings: [...merged.values()],
|
|
4117
4250
|
diagnostics: [...new Set(diagnostics)],
|
|
4118
|
-
checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [] },
|
|
4251
|
+
checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [], ...cachedAudit ? { auditReused: true } : {} },
|
|
4119
4252
|
counts,
|
|
4120
4253
|
capture: "unknown",
|
|
4121
4254
|
scope: SCOPE
|
|
4122
4255
|
};
|
|
4123
|
-
return { baselines, report, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
|
|
4256
|
+
return { baselines, report, currentAudit, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
|
|
4124
4257
|
}
|
|
4125
4258
|
async function analyze(ws, inputs, event, startedAt) {
|
|
4126
4259
|
return withLock(ws.root, ws.directory + "/analysis/" + inputs.fingerprint, async () => {
|
|
4127
4260
|
const before = await loadState(ws);
|
|
4128
|
-
const
|
|
4129
|
-
if (
|
|
4261
|
+
const { analysis: previous, diagnostic } = await matchingAnalysis(ws, before, inputs);
|
|
4262
|
+
if (previous && previous.completedAt >= startedAt) return { analysis: previous, shared: true };
|
|
4130
4263
|
const originalDigest = await baselineDigest(ws, before);
|
|
4131
|
-
const computed = await computeAnalysis(ws, before, inputs, event);
|
|
4264
|
+
const computed = await computeAnalysis(ws, before, inputs, event, previous?.currentAudit, diagnostic);
|
|
4132
4265
|
await verifyInputs(ws, inputs);
|
|
4133
4266
|
if (originalDigest !== await baselineDigest(ws, before)) throw new Error("Repair baseline was modified during automation; original evidence must be inspected.");
|
|
4134
4267
|
const id = randomUUID5();
|
|
@@ -4137,7 +4270,8 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
4137
4270
|
fingerprint: inputs.fingerprint,
|
|
4138
4271
|
completedAt: Date.now(),
|
|
4139
4272
|
baselineDigest: await baselineDigest(ws, { ...before, baselines: computed.baselines }),
|
|
4140
|
-
report: computed.report
|
|
4273
|
+
report: computed.report,
|
|
4274
|
+
currentAudit: computed.currentAudit
|
|
4141
4275
|
});
|
|
4142
4276
|
const analysisPath = ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json";
|
|
4143
4277
|
await withLock(ws.root, ws.directory, async () => {
|
|
@@ -4156,17 +4290,17 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
4156
4290
|
}
|
|
4157
4291
|
async function automate(dir, event) {
|
|
4158
4292
|
const startedAt = Date.now();
|
|
4159
|
-
const ws = await workspace(dir);
|
|
4293
|
+
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
4160
4294
|
return recordExecution(ws.root, ws.directory, event.event, async () => {
|
|
4161
4295
|
const inputs = await readInputs(ws.root);
|
|
4162
|
-
const result = await analyze(ws, inputs, event, startedAt);
|
|
4296
|
+
const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
|
|
4163
4297
|
if (result.shared) {
|
|
4164
4298
|
await verifyInputs(ws, inputs);
|
|
4165
4299
|
if (result.analysis.baselineDigest !== await baselineDigest(ws, await loadState(ws))) {
|
|
4166
4300
|
throw new Error("Repair baseline was modified during shared automation; original evidence must be inspected.");
|
|
4167
4301
|
}
|
|
4168
4302
|
}
|
|
4169
|
-
return withLock(ws.root, ws.directory, async () => {
|
|
4303
|
+
return profilePhase("automation.publish", () => withLock(ws.root, ws.directory, async () => {
|
|
4170
4304
|
const state = await loadState(ws);
|
|
4171
4305
|
if (state.analysis?.id !== result.analysis.id) throw new Error("Automation evidence changed during publication; retry against the current inputs.");
|
|
4172
4306
|
const report = structuredClone(result.analysis.report);
|
|
@@ -4229,11 +4363,11 @@ async function automate(dir, event) {
|
|
|
4229
4363
|
if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
|
|
4230
4364
|
await writeStoreJson(ws.root, ws.directory + "/state.json", state);
|
|
4231
4365
|
return { report, message: notify ? summarize(report) : null, continueOnce };
|
|
4232
|
-
});
|
|
4366
|
+
}));
|
|
4233
4367
|
});
|
|
4234
4368
|
}
|
|
4235
4369
|
async function automationStatus(dir) {
|
|
4236
|
-
const ws = await workspace(dir);
|
|
4370
|
+
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
4237
4371
|
const execution = await executionStatus(ws.root, ws.directory);
|
|
4238
4372
|
const notifications = await failureNotifications(ws.root, ws.directory);
|
|
4239
4373
|
const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
|
|
@@ -4267,6 +4401,7 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
|
|
|
4267
4401
|
var init_runtime = __esm({
|
|
4268
4402
|
"src/automation/runtime.ts"() {
|
|
4269
4403
|
"use strict";
|
|
4404
|
+
init_profile();
|
|
4270
4405
|
init_execution();
|
|
4271
4406
|
init_repair();
|
|
4272
4407
|
init_types();
|
|
@@ -4281,6 +4416,7 @@ var init_runtime = __esm({
|
|
|
4281
4416
|
fingerprint: z13.string(),
|
|
4282
4417
|
completedAt: z13.number(),
|
|
4283
4418
|
baselineDigest: z13.string(),
|
|
4419
|
+
currentAudit: reportSchema.nullable().optional(),
|
|
4284
4420
|
report: z13.object({
|
|
4285
4421
|
version: z13.literal(1),
|
|
4286
4422
|
status: z13.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
|
|
@@ -4294,7 +4430,8 @@ var init_runtime = __esm({
|
|
|
4294
4430
|
checks: z13.object({
|
|
4295
4431
|
ran: z13.array(z13.string()),
|
|
4296
4432
|
reused: z13.array(z13.string()),
|
|
4297
|
-
skipped: z13.array(z13.object({ check: z13.string(), reason: z13.string(), doc: z13.string().optional() }))
|
|
4433
|
+
skipped: z13.array(z13.object({ check: z13.string(), reason: z13.string(), doc: z13.string().optional() })),
|
|
4434
|
+
auditReused: z13.boolean().optional()
|
|
4298
4435
|
}),
|
|
4299
4436
|
counts: z13.object({ resolved: z13.number(), unresolved: z13.number(), "review-required": z13.number(), unverified: z13.number() }),
|
|
4300
4437
|
capture: z13.enum(["observed", "unknown"]),
|
|
@@ -4302,9 +4439,9 @@ var init_runtime = __esm({
|
|
|
4302
4439
|
})
|
|
4303
4440
|
});
|
|
4304
4441
|
sharedSchema = z13.object({ analysis: analysisSchema, digest: z13.string() });
|
|
4305
|
-
baselineDigest = async (ws, state) => hash(await Promise.all(
|
|
4442
|
+
baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
|
|
4306
4443
|
state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
|
|
4307
|
-
));
|
|
4444
|
+
)));
|
|
4308
4445
|
}
|
|
4309
4446
|
});
|
|
4310
4447
|
|
|
@@ -9226,7 +9363,7 @@ function createMcpServer() {
|
|
|
9226
9363
|
const server = new McpServer(
|
|
9227
9364
|
{
|
|
9228
9365
|
name: "mason",
|
|
9229
|
-
version: "0.17.
|
|
9366
|
+
version: "0.17.3"
|
|
9230
9367
|
},
|
|
9231
9368
|
{
|
|
9232
9369
|
instructions: (setupNotice ? setupNotice + "\n\n" : "") + 'Mason retrieves recorded decisions, file impact, and optional feature/flow maps. Use get_context with the task and known files, and get_impact before editing. Save learned rationale and constraints with save_decision; review and commit the local records through the project workflow. These tools work without initialization or a concept map. Consult trust and diagnostics: changed or unknown freshness needs source inspection, failed verification needs correction, and proposals are suggestions and legacy records are unreviewed. Accepted decisions are recorded team constraints, still subject to freshness checks. Use review_decision to prepare code evidence and record only authorized acceptance, reaffirmation, or retirement. Never invent a reviewer or treat these recorded identities as authenticated approval. mason_init returns documentation audit and committed-diff review findings with a quickstart guide. Use mode: "map" only when a full architecture map is requested. A missing map is not a setup failure; use decisions and source evidence. get_snapshot provides architecture navigation when a map is available. The mason-audit and mason-review CLIs also work without setup.'
|