mason-context 0.19.1 → 0.20.1
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 +25 -0
- package/dist/mason-audit.js +115 -88
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +939 -577
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js.map +1 -1
- package/dist/mason-runtime.js +173 -64
- package/dist/mason-runtime.js.map +1 -1
- package/dist/mason.js +1264 -758
- package/dist/mason.js.map +1 -1
- package/package.json +2 -2
- package/dist/mason-auto.js +0 -6281
- package/dist/mason-auto.js.map +0 -1
package/dist/mason-mcp.js
CHANGED
|
@@ -138,9 +138,9 @@ async function readBoundedFile(file, maxBytes) {
|
|
|
138
138
|
async function loadProjectConfig(root) {
|
|
139
139
|
try {
|
|
140
140
|
const canonicalRoot = await fs.realpath(root);
|
|
141
|
-
const
|
|
142
|
-
if (!isWithinRoot(canonicalRoot,
|
|
143
|
-
const raw = await readBoundedFile(
|
|
141
|
+
const configPath3 = await fs.realpath(path2.join(root, ".mason/config.json"));
|
|
142
|
+
if (!isWithinRoot(canonicalRoot, configPath3)) throw new Error("Project configuration resolves outside the repository");
|
|
143
|
+
const raw = await readBoundedFile(configPath3, 64 * 1024);
|
|
144
144
|
if (raw === null) throw new Error("Project configuration is not a regular file or exceeds 64 KiB");
|
|
145
145
|
const value = JSON.parse(raw);
|
|
146
146
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected a configuration object");
|
|
@@ -419,15 +419,15 @@ async function readAuditInput(root, file) {
|
|
|
419
419
|
async function auditGlob(root, patterns, options) {
|
|
420
420
|
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
421
421
|
const directories = /* @__PURE__ */ new Set();
|
|
422
|
-
const readdir2 = ((
|
|
423
|
-
const relative = path5.relative(root,
|
|
422
|
+
const readdir2 = ((directory2, opts, callback) => {
|
|
423
|
+
const relative = path5.relative(root, directory2).split(path5.sep).join("/") || ".";
|
|
424
424
|
directories.add(relative);
|
|
425
425
|
if (directories.size > MAX_INPUTS) {
|
|
426
426
|
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
427
427
|
return;
|
|
428
428
|
}
|
|
429
429
|
auditInputPath(root, relative).then(() => {
|
|
430
|
-
nativeFs.readdir(
|
|
430
|
+
nativeFs.readdir(directory2, opts, callback);
|
|
431
431
|
}, callback);
|
|
432
432
|
});
|
|
433
433
|
const lstat2 = ((file, callback) => {
|
|
@@ -557,25 +557,25 @@ function commandClaims(line, lineNo, cwd, shell = false) {
|
|
|
557
557
|
if (text2.startsWith("#") || /^(?:echo|printf)\b/.test(text2)) continue;
|
|
558
558
|
const cd = text2.match(/^cd\s+(.+)$/);
|
|
559
559
|
if (cd) {
|
|
560
|
-
const
|
|
561
|
-
cwd =
|
|
560
|
+
const directory2 = literal(cd[1]);
|
|
561
|
+
cwd = directory2 === null || cwd === null || /\s/.test(directory2) && !/^["']/.test(cd[1]) ? null : path6.posix.join(cwd ?? ".", directory2);
|
|
562
562
|
continue;
|
|
563
563
|
}
|
|
564
564
|
for (const match of text2.matchAll(COMMAND)) {
|
|
565
565
|
const flags = match[2].trim();
|
|
566
566
|
const selector = flags.match(/^(?:--prefix|--dir|-C)(?:\s+|=)(.+)$/);
|
|
567
|
-
let
|
|
567
|
+
let directory2 = cwd;
|
|
568
568
|
let scopeUnknown;
|
|
569
569
|
if (shell && match.index !== 0 || match[3].startsWith("-")) scopeUnknown = "Unsupported shell wrapper or options before the script.";
|
|
570
570
|
if (selector) {
|
|
571
571
|
const selected = literal(selector[1]);
|
|
572
|
-
|
|
572
|
+
directory2 = selected === null || cwd === null ? null : path6.posix.join(cwd ?? ".", selected);
|
|
573
573
|
} else if (flags) scopeUnknown = "Unsupported package-manager options before run: " + flags;
|
|
574
574
|
const tail = text2.slice(match.index + match[0].length).split(/\s--(?:\s|$)/)[0];
|
|
575
575
|
if (/(?:^|\s)(?:--workspace(?:s)?|-w|--filter|-F|--prefix|--dir|-C|--if-present)(?:\s|=|$)/.test(tail)) {
|
|
576
576
|
scopeUnknown = "Package selection or conditional options after the script need review.";
|
|
577
577
|
}
|
|
578
|
-
if (/[|()]/.test(text2) || /\b(?:pushd|popd|if|for|while)\b/.test(text2) ||
|
|
578
|
+
if (/[|()]/.test(text2) || /\b(?:pushd|popd|if|for|while)\b/.test(text2) || directory2 === null) {
|
|
579
579
|
scopeUnknown = "The command's working directory or shell control flow could not be resolved.";
|
|
580
580
|
}
|
|
581
581
|
claims.push({
|
|
@@ -583,7 +583,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
|
|
|
583
583
|
invocation: match[0],
|
|
584
584
|
excerpt: match[0],
|
|
585
585
|
line: lineNo,
|
|
586
|
-
...
|
|
586
|
+
...directory2 !== void 0 && directory2 !== null ? { directory: directory2 } : {},
|
|
587
587
|
...scopeUnknown ? { scopeUnknown } : {}
|
|
588
588
|
});
|
|
589
589
|
}
|
|
@@ -1128,19 +1128,19 @@ var init_docs = __esm({
|
|
|
1128
1128
|
// src/audit/scope.ts
|
|
1129
1129
|
import fs5 from "fs/promises";
|
|
1130
1130
|
import path8 from "path";
|
|
1131
|
-
function scopedPath(
|
|
1131
|
+
function scopedPath(directory2, value) {
|
|
1132
1132
|
if (path8.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
|
|
1133
|
-
const resolved = path8.posix.normalize(path8.posix.join(
|
|
1133
|
+
const resolved = path8.posix.normalize(path8.posix.join(directory2, value));
|
|
1134
1134
|
return resolved === ".." || resolved.startsWith("../") ? null : resolved;
|
|
1135
1135
|
}
|
|
1136
1136
|
function pathClaimScope(doc, claim) {
|
|
1137
|
-
const
|
|
1138
|
-
const local = scopedPath(
|
|
1137
|
+
const directory2 = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
|
|
1138
|
+
const local = scopedPath(directory2, claim.path);
|
|
1139
1139
|
if (!local) return null;
|
|
1140
|
-
const candidates = claim.relativeTo === "document" ||
|
|
1140
|
+
const candidates = claim.relativeTo === "document" || directory2 === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
|
|
1141
1141
|
return {
|
|
1142
|
-
basis: claim.relativeTo === "document" ? "document-link" :
|
|
1143
|
-
directory,
|
|
1142
|
+
basis: claim.relativeTo === "document" ? "document-link" : directory2 === "." ? "repository" : "document",
|
|
1143
|
+
directory: directory2,
|
|
1144
1144
|
candidates
|
|
1145
1145
|
};
|
|
1146
1146
|
}
|
|
@@ -1194,24 +1194,19 @@ async function lockDiagnostic(file) {
|
|
|
1194
1194
|
}
|
|
1195
1195
|
return reason2 + " 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.";
|
|
1196
1196
|
}
|
|
1197
|
-
async function withStoreLock(root,
|
|
1198
|
-
const file = await storePath(root,
|
|
1197
|
+
async function withStoreLock(root, directory2, run, waitMs = 5e3, label = "Mason store") {
|
|
1198
|
+
const file = await storePath(root, directory2 + "/lock", true);
|
|
1199
1199
|
const deadline = Date.now() + waitMs;
|
|
1200
1200
|
let handle;
|
|
1201
1201
|
while (!handle) {
|
|
1202
1202
|
try {
|
|
1203
1203
|
handle = await fs6.open(file, "wx", 384);
|
|
1204
|
-
try {
|
|
1205
|
-
await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
|
|
1206
|
-
} catch (error) {
|
|
1207
|
-
await handle.close().catch(() => {
|
|
1208
|
-
});
|
|
1209
|
-
handle = void 0;
|
|
1210
|
-
await fs6.rm(file, { force: true }).catch(() => {
|
|
1211
|
-
});
|
|
1212
|
-
throw error;
|
|
1213
|
-
}
|
|
1214
1204
|
} catch (error) {
|
|
1205
|
+
if (process.platform === "win32" && error.code === "EPERM") {
|
|
1206
|
+
if (Date.now() >= deadline) throw error;
|
|
1207
|
+
await new Promise((resolve2) => setTimeout(resolve2, 40));
|
|
1208
|
+
continue;
|
|
1209
|
+
}
|
|
1215
1210
|
if (error.code !== "EEXIST") throw error;
|
|
1216
1211
|
try {
|
|
1217
1212
|
const owner = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
@@ -1240,6 +1235,17 @@ async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason
|
|
|
1240
1235
|
if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
|
|
1241
1236
|
await new Promise((resolve2) => setTimeout(resolve2, 40));
|
|
1242
1237
|
}
|
|
1238
|
+
if (handle) {
|
|
1239
|
+
try {
|
|
1240
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
await handle.close().catch(() => {
|
|
1243
|
+
});
|
|
1244
|
+
await fs6.rm(file, { force: true }).catch(() => {
|
|
1245
|
+
});
|
|
1246
|
+
throw error;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1243
1249
|
}
|
|
1244
1250
|
try {
|
|
1245
1251
|
return await run();
|
|
@@ -2044,19 +2050,19 @@ async function commandInputs(root, docs) {
|
|
|
2044
2050
|
const resolutions = [];
|
|
2045
2051
|
for (const doc of docs) for (const claim of doc.claims.commands) {
|
|
2046
2052
|
const base = documentScope(doc.path);
|
|
2047
|
-
const
|
|
2053
|
+
const directory2 = scopedPath(base, claim.directory ?? ".");
|
|
2048
2054
|
const scope = {
|
|
2049
2055
|
basis: claim.directory === void 0 ? "document" : "explicit",
|
|
2050
|
-
directory:
|
|
2056
|
+
directory: directory2 ?? base,
|
|
2051
2057
|
candidates: []
|
|
2052
2058
|
};
|
|
2053
2059
|
const resolution = { doc: doc.path, claim, scope, manifest: null, scripts: [], elsewhere: [] };
|
|
2054
2060
|
resolutions.push(resolution);
|
|
2055
|
-
if (claim.scopeUnknown ||
|
|
2061
|
+
if (claim.scopeUnknown || directory2 === null) {
|
|
2056
2062
|
resolution.problem = claim.scopeUnknown ?? "The command's directory leaves the repository.";
|
|
2057
2063
|
continue;
|
|
2058
2064
|
}
|
|
2059
|
-
let cursor =
|
|
2065
|
+
let cursor = directory2;
|
|
2060
2066
|
while (true) {
|
|
2061
2067
|
const manifest = path15.posix.join(cursor, "package.json");
|
|
2062
2068
|
scope.candidates.push(manifest);
|
|
@@ -2072,7 +2078,7 @@ async function commandInputs(root, docs) {
|
|
|
2072
2078
|
cursor = path15.posix.dirname(cursor);
|
|
2073
2079
|
}
|
|
2074
2080
|
if (!resolution.manifest && !resolution.problem) {
|
|
2075
|
-
resolution.problem = `No package.json for the command's scope (${
|
|
2081
|
+
resolution.problem = `No package.json for the command's scope (${directory2}).`;
|
|
2076
2082
|
}
|
|
2077
2083
|
if (resolution.problem || resolution.scripts.includes(claim.scriptName) || claim.directory !== void 0) continue;
|
|
2078
2084
|
inventory ??= await commandManifests(root);
|
|
@@ -3182,8 +3188,121 @@ var init_findings = __esm({
|
|
|
3182
3188
|
}
|
|
3183
3189
|
});
|
|
3184
3190
|
|
|
3185
|
-
// src/automation/
|
|
3191
|
+
// src/automation/notifications.ts
|
|
3186
3192
|
import { z as z5 } from "zod";
|
|
3193
|
+
function condition(finding) {
|
|
3194
|
+
const source = finding.current ?? finding.original;
|
|
3195
|
+
const e = source.evidence;
|
|
3196
|
+
const id = e.kind === "decision-anchor" ? digest([source.type, source.anchor.doc, e.decisionId]) : finding.id;
|
|
3197
|
+
const result = { level: "confidence" in source ? 2 : 1, facts: [] };
|
|
3198
|
+
switch (e.kind) {
|
|
3199
|
+
case "count-mismatch":
|
|
3200
|
+
result.magnitude = Math.abs(e.actual - e.claimed);
|
|
3201
|
+
break;
|
|
3202
|
+
case "unmentioned-dir":
|
|
3203
|
+
result.magnitude = e.sourceFileCount;
|
|
3204
|
+
break;
|
|
3205
|
+
case "decision-anchor":
|
|
3206
|
+
result.facts = [...e.changedFiles].sort();
|
|
3207
|
+
break;
|
|
3208
|
+
case "doc-behind-manifests":
|
|
3209
|
+
result.facts = (e.matches ?? []).map((m) => JSON.stringify([m.manifest, m.dependency, m.after])).sort();
|
|
3210
|
+
break;
|
|
3211
|
+
}
|
|
3212
|
+
return { id, condition: result };
|
|
3213
|
+
}
|
|
3214
|
+
function notificationConditions(findings) {
|
|
3215
|
+
const active = findings.filter((f) => f.status !== "resolved");
|
|
3216
|
+
const currentIds = new Set(active.filter((f) => f.current).map((f) => condition(f).id));
|
|
3217
|
+
const result = {};
|
|
3218
|
+
for (const f of active) {
|
|
3219
|
+
const value = condition(f);
|
|
3220
|
+
if (!f.current && currentIds.has(value.id)) continue;
|
|
3221
|
+
const previous = result[value.id];
|
|
3222
|
+
result[value.id] = previous ? {
|
|
3223
|
+
level: Math.max(previous.level, value.condition.level),
|
|
3224
|
+
facts: [.../* @__PURE__ */ new Set([...previous.facts, ...value.condition.facts])].sort(),
|
|
3225
|
+
...previous.magnitude !== void 0 || value.condition.magnitude !== void 0 ? { magnitude: Math.max(previous.magnitude ?? 0, value.condition.magnitude ?? 0) } : {}
|
|
3226
|
+
} : value.condition;
|
|
3227
|
+
}
|
|
3228
|
+
return result;
|
|
3229
|
+
}
|
|
3230
|
+
function createNotificationState(findings, head) {
|
|
3231
|
+
return { version: 1, baselineHead: head, baseline: notificationConditions(findings), delivered: {}, cleared: [] };
|
|
3232
|
+
}
|
|
3233
|
+
function worsened(current, previous) {
|
|
3234
|
+
return !previous || current.level > previous.level || (current.magnitude ?? 0) > (previous.magnitude ?? 0) || current.facts.some((fact) => !previous.facts.includes(fact));
|
|
3235
|
+
}
|
|
3236
|
+
function completionFindings(state, findings) {
|
|
3237
|
+
const current = notificationConditions(findings);
|
|
3238
|
+
const cleared = new Set(state.cleared);
|
|
3239
|
+
const resolvedIds = new Set(findings.filter((f) => f.status === "resolved").map((f) => condition(f).id));
|
|
3240
|
+
for (const id of Object.keys(state.baseline)) if (resolvedIds.has(id) && !current[id]) cleared.add(id);
|
|
3241
|
+
state.cleared = [...cleared];
|
|
3242
|
+
const selected = [];
|
|
3243
|
+
const selectedIds = /* @__PURE__ */ new Set();
|
|
3244
|
+
const delivered = { ...state.delivered };
|
|
3245
|
+
for (const finding of findings) {
|
|
3246
|
+
const value = condition(finding);
|
|
3247
|
+
const previous = state.delivered[value.id];
|
|
3248
|
+
value.condition = current[value.id] ?? value.condition;
|
|
3249
|
+
if (finding.status === "unverified" || !finding.current && finding.status !== "resolved") {
|
|
3250
|
+
continue;
|
|
3251
|
+
}
|
|
3252
|
+
if (finding.status === "resolved") {
|
|
3253
|
+
if (!current[value.id]) delete delivered[value.id];
|
|
3254
|
+
continue;
|
|
3255
|
+
}
|
|
3256
|
+
const eligible = cleared.has(value.id) || worsened(value.condition, state.baseline[value.id]);
|
|
3257
|
+
if (!eligible) continue;
|
|
3258
|
+
if (!selectedIds.has(value.id) && worsened(value.condition, previous)) {
|
|
3259
|
+
selectedIds.add(value.id);
|
|
3260
|
+
const source = finding.current;
|
|
3261
|
+
selected.push(source.evidence.kind === "decision-anchor" ? { ...finding, current: { ...source, evidence: { ...source.evidence, changedFiles: value.condition.facts } } } : finding);
|
|
3262
|
+
if (selected.length <= MAX_COMPLETION_FINDINGS) delivered[value.id] = value.condition;
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
state.delivered = delivered;
|
|
3266
|
+
return selected;
|
|
3267
|
+
}
|
|
3268
|
+
function completionSummary(findings, reportPath) {
|
|
3269
|
+
if (!findings.length) return null;
|
|
3270
|
+
const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
|
|
3271
|
+
return [
|
|
3272
|
+
`Mason: ${findings.length} new or worsened finding(s) since the retained baseline.`,
|
|
3273
|
+
...findings.slice(0, MAX_COMPLETION_FINDINGS).map((f) => {
|
|
3274
|
+
const source = f.current ?? f.original;
|
|
3275
|
+
const detail = source.evidence.kind === "decision-anchor" ? `Changes to ${source.evidence.changedFiles.join(", ")} may affect decision "${source.evidence.title}". Inspect the diff if relevant; acceptance is not required.` : source.message;
|
|
3276
|
+
return `[${f.status === "unresolved" ? "issue" : "advisory"}] ${safe(source.anchor.doc)}: ${safe(detail)}`;
|
|
3277
|
+
}),
|
|
3278
|
+
...findings.length > MAX_COMPLETION_FINDINGS ? [`${findings.length - MAX_COMPLETION_FINDINGS} more in the report; pending later feedback.`] : [],
|
|
3279
|
+
`Evidence: ${reportPath}. Explicit audit: mason_automation(action: "check").`,
|
|
3280
|
+
"Inspect findings within the authorized work. Concurrent changes may also contribute; this summary does not establish who introduced them."
|
|
3281
|
+
].join("\n");
|
|
3282
|
+
}
|
|
3283
|
+
var conditionSchema, MAX_COMPLETION_FINDINGS, notificationStateSchema;
|
|
3284
|
+
var init_notifications = __esm({
|
|
3285
|
+
"src/automation/notifications.ts"() {
|
|
3286
|
+
"use strict";
|
|
3287
|
+
init_findings();
|
|
3288
|
+
conditionSchema = z5.object({
|
|
3289
|
+
level: z5.number(),
|
|
3290
|
+
facts: z5.array(z5.string()),
|
|
3291
|
+
magnitude: z5.number().optional()
|
|
3292
|
+
});
|
|
3293
|
+
MAX_COMPLETION_FINDINGS = 4;
|
|
3294
|
+
notificationStateSchema = z5.object({
|
|
3295
|
+
version: z5.literal(1),
|
|
3296
|
+
baselineHead: z5.string(),
|
|
3297
|
+
baseline: z5.record(conditionSchema),
|
|
3298
|
+
delivered: z5.record(conditionSchema),
|
|
3299
|
+
cleared: z5.array(z5.string())
|
|
3300
|
+
});
|
|
3301
|
+
}
|
|
3302
|
+
});
|
|
3303
|
+
|
|
3304
|
+
// src/automation/store.ts
|
|
3305
|
+
import { z as z6 } from "zod";
|
|
3187
3306
|
function parseState(raw) {
|
|
3188
3307
|
try {
|
|
3189
3308
|
return stateSchema.parse(raw);
|
|
@@ -3191,45 +3310,44 @@ function parseState(raw) {
|
|
|
3191
3310
|
throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
|
|
3192
3311
|
}
|
|
3193
3312
|
}
|
|
3194
|
-
function withLock(root,
|
|
3195
|
-
return withStoreLock(root,
|
|
3313
|
+
function withLock(root, directory2, run, waitMs = 5e3) {
|
|
3314
|
+
return withStoreLock(root, directory2, run, waitMs, "Automation");
|
|
3196
3315
|
}
|
|
3197
3316
|
var hostSchema, events, stateSchema;
|
|
3198
3317
|
var init_store = __esm({
|
|
3199
3318
|
"src/automation/store.ts"() {
|
|
3200
3319
|
"use strict";
|
|
3201
3320
|
init_store_lock();
|
|
3202
|
-
|
|
3321
|
+
init_notifications();
|
|
3322
|
+
hostSchema = z6.enum(["claude", "codex"]);
|
|
3203
3323
|
events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
|
|
3204
|
-
stateSchema =
|
|
3205
|
-
version:
|
|
3206
|
-
root:
|
|
3207
|
-
gitDir:
|
|
3208
|
-
branch:
|
|
3209
|
-
baselines:
|
|
3210
|
-
sessions:
|
|
3324
|
+
stateSchema = z6.object({
|
|
3325
|
+
version: z6.literal(1),
|
|
3326
|
+
root: z6.string(),
|
|
3327
|
+
gitDir: z6.string(),
|
|
3328
|
+
branch: z6.string(),
|
|
3329
|
+
baselines: z6.array(z6.object({ path: z6.string(), at: z6.string(), event: z6.string(), fingerprint: z6.string() })).max(128),
|
|
3330
|
+
sessions: z6.record(z6.object({
|
|
3211
3331
|
host: hostSchema,
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
mutationObserved: z5.boolean(),
|
|
3218
|
-
pending: z5.record(z5.string()),
|
|
3219
|
-
coverageGaps: z5.array(z5.string()),
|
|
3220
|
-
events: z5.record(z5.object({ at: z5.string(), count: z5.number().int().positive() }))
|
|
3332
|
+
lastUsed: z6.string(),
|
|
3333
|
+
notifiedDiagnostics: z6.array(z6.string()).default([]),
|
|
3334
|
+
pending: z6.record(z6.string()),
|
|
3335
|
+
coverageGaps: z6.array(z6.string()),
|
|
3336
|
+
events: z6.record(z6.object({ at: z6.string(), count: z6.number().int().positive() }))
|
|
3221
3337
|
})),
|
|
3222
|
-
updatedAt:
|
|
3223
|
-
fingerprint:
|
|
3224
|
-
latest:
|
|
3225
|
-
|
|
3338
|
+
updatedAt: z6.string(),
|
|
3339
|
+
fingerprint: z6.string().nullable(),
|
|
3340
|
+
latest: z6.string().nullable(),
|
|
3341
|
+
notifications: notificationStateSchema.optional(),
|
|
3342
|
+
agentNotifications: notificationStateSchema.optional(),
|
|
3343
|
+
analysis: z6.object({ id: z6.string(), fingerprint: z6.string(), completedAt: z6.number(), path: z6.string() }).optional()
|
|
3226
3344
|
});
|
|
3227
3345
|
}
|
|
3228
3346
|
});
|
|
3229
3347
|
|
|
3230
3348
|
// src/audit/advisory-review.ts
|
|
3231
3349
|
import fs10 from "fs/promises";
|
|
3232
|
-
import { z as
|
|
3350
|
+
import { z as z7 } from "zod";
|
|
3233
3351
|
async function readRecord(root, id) {
|
|
3234
3352
|
const raw = await readStoreJson(root, recordPath(id));
|
|
3235
3353
|
if (raw === null) return null;
|
|
@@ -3448,41 +3566,41 @@ var init_advisory_review = __esm({
|
|
|
3448
3566
|
init_decisions();
|
|
3449
3567
|
init_drift2();
|
|
3450
3568
|
git2 = async (root, ...args) => (await (["log", "ls-tree", "ls-files", "merge-base"].includes(args[0]) ? inspectionGit : execGit)(args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
3451
|
-
hashSchema =
|
|
3452
|
-
commitSchema2 =
|
|
3453
|
-
evidenceSchema2 =
|
|
3569
|
+
hashSchema = z7.string().regex(/^[a-f0-9]{64}$/);
|
|
3570
|
+
commitSchema2 = z7.string().regex(/^[a-f0-9]{40,64}$/);
|
|
3571
|
+
evidenceSchema2 = z7.object({
|
|
3454
3572
|
head: commitSchema2,
|
|
3455
3573
|
fingerprint: hashSchema,
|
|
3456
|
-
scope:
|
|
3457
|
-
tree:
|
|
3574
|
+
scope: z7.array(z7.string()).min(1).max(1e3),
|
|
3575
|
+
tree: z7.string().max(2 * 1024 * 1024),
|
|
3458
3576
|
lastScopeCommit: commitSchema2,
|
|
3459
|
-
paths:
|
|
3577
|
+
paths: z7.array(z7.object({ path: z7.string(), exists: z7.boolean() })).max(1e3)
|
|
3460
3578
|
});
|
|
3461
|
-
dismissalReasonSchema =
|
|
3462
|
-
eventSchema2 =
|
|
3463
|
-
at:
|
|
3464
|
-
reviewer:
|
|
3465
|
-
note:
|
|
3466
|
-
outcome:
|
|
3579
|
+
dismissalReasonSchema = z7.enum(["no-dependency-claims", "unrelated-manifest-change"]);
|
|
3580
|
+
eventSchema2 = z7.object({
|
|
3581
|
+
at: z7.string().datetime(),
|
|
3582
|
+
reviewer: z7.string().trim().min(1).max(200).optional(),
|
|
3583
|
+
note: z7.string().trim().min(1).max(2500),
|
|
3584
|
+
outcome: z7.enum(["addressed", "inapplicable", "deferred", "dismissed"]),
|
|
3467
3585
|
reasonCode: dismissalReasonSchema.optional(),
|
|
3468
3586
|
finding: advisorySchema.refine((finding) => finding.evidence.kind !== "decision-anchor", "Decision findings use decision reviews"),
|
|
3469
3587
|
evidence: evidenceSchema2
|
|
3470
3588
|
});
|
|
3471
|
-
recordSchema =
|
|
3472
|
-
reviewSummarySchema =
|
|
3589
|
+
recordSchema = z7.object({ version: z7.literal(1), id: hashSchema, events: z7.array(eventSchema2).min(1).max(200), digest: hashSchema });
|
|
3590
|
+
reviewSummarySchema = z7.object({
|
|
3473
3591
|
id: hashSchema,
|
|
3474
|
-
status:
|
|
3475
|
-
reason:
|
|
3476
|
-
recordPath:
|
|
3477
|
-
reviewer:
|
|
3478
|
-
note:
|
|
3479
|
-
outcome:
|
|
3480
|
-
reviewedHead:
|
|
3592
|
+
status: z7.enum(["current", "reopened", "deferred", "unverified"]),
|
|
3593
|
+
reason: z7.string(),
|
|
3594
|
+
recordPath: z7.string().optional(),
|
|
3595
|
+
reviewer: z7.string().optional(),
|
|
3596
|
+
note: z7.string().optional(),
|
|
3597
|
+
outcome: z7.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired", "dismissed"]).optional(),
|
|
3598
|
+
reviewedHead: z7.string().optional()
|
|
3481
3599
|
});
|
|
3482
|
-
advisoryReviewRequest =
|
|
3483
|
-
baselinePath:
|
|
3600
|
+
advisoryReviewRequest = z7.object({
|
|
3601
|
+
baselinePath: z7.string().min(1),
|
|
3484
3602
|
findingId: hashSchema,
|
|
3485
|
-
action:
|
|
3603
|
+
action: z7.enum(["prepare", "addressed", "inapplicable", "deferred", "dismiss"]).default("prepare"),
|
|
3486
3604
|
reasonCode: dismissalReasonSchema.optional(),
|
|
3487
3605
|
reviewer: eventSchema2.shape.reviewer.optional(),
|
|
3488
3606
|
note: eventSchema2.shape.note.optional(),
|
|
@@ -3600,7 +3718,7 @@ __export(repair_exports, {
|
|
|
3600
3718
|
import fs12 from "fs/promises";
|
|
3601
3719
|
import path21 from "path";
|
|
3602
3720
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3603
|
-
import { z as
|
|
3721
|
+
import { z as z8 } from "zod";
|
|
3604
3722
|
function recheckable(finding) {
|
|
3605
3723
|
return !("confidence" in finding) && finding.resolution === "recheck" && ["deleted-reference", "dead-command", "new-module"].includes(finding.type) && ["missing-path", "missing-script", "unmentioned-dir"].includes(finding.evidence.kind);
|
|
3606
3724
|
}
|
|
@@ -3671,7 +3789,7 @@ async function withRepairSession(root, options, run, validate, cachedAudit) {
|
|
|
3671
3789
|
}
|
|
3672
3790
|
async function prepareOriginal(rootDir, checks, options, session) {
|
|
3673
3791
|
const root = await fs12.realpath(rootDir);
|
|
3674
|
-
const selected =
|
|
3792
|
+
const selected = z8.array(checkSchema).nonempty().parse(checks);
|
|
3675
3793
|
const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
|
|
3676
3794
|
if (!report) throw new Error("No context files found to prepare a repair.");
|
|
3677
3795
|
if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
|
|
@@ -3828,42 +3946,42 @@ var init_repair = __esm({
|
|
|
3828
3946
|
init_findings();
|
|
3829
3947
|
init_advisory_review();
|
|
3830
3948
|
init_findings();
|
|
3831
|
-
count2 =
|
|
3832
|
-
reportSchema =
|
|
3833
|
-
version:
|
|
3834
|
-
root:
|
|
3835
|
-
gitAvailable:
|
|
3949
|
+
count2 = z8.number().int().nonnegative();
|
|
3950
|
+
reportSchema = z8.object({
|
|
3951
|
+
version: z8.literal(1),
|
|
3952
|
+
root: z8.string(),
|
|
3953
|
+
gitAvailable: z8.literal(true),
|
|
3836
3954
|
headHash: commitSchema.shape.hash,
|
|
3837
|
-
checksRun:
|
|
3838
|
-
docs:
|
|
3839
|
-
path:
|
|
3840
|
-
scope:
|
|
3841
|
-
kind:
|
|
3955
|
+
checksRun: z8.array(checkSchema).nonempty(),
|
|
3956
|
+
docs: z8.array(z8.object({
|
|
3957
|
+
path: z8.string().refine((value) => normalizeRepoPath(value) === value),
|
|
3958
|
+
scope: z8.string().optional(),
|
|
3959
|
+
kind: z8.enum(["instructions", "readme", "guide"]).optional(),
|
|
3842
3960
|
lastCommit: commitSchema.nullable(),
|
|
3843
|
-
dirty:
|
|
3961
|
+
dirty: z8.boolean(),
|
|
3844
3962
|
lineCount: count2
|
|
3845
3963
|
})).nonempty(),
|
|
3846
|
-
decisionsChecked:
|
|
3847
|
-
clean:
|
|
3848
|
-
excludedChecks:
|
|
3849
|
-
issues:
|
|
3850
|
-
advisories:
|
|
3851
|
-
suppressedAdvisories:
|
|
3852
|
-
skippedChecks:
|
|
3964
|
+
decisionsChecked: z8.boolean(),
|
|
3965
|
+
clean: z8.boolean(),
|
|
3966
|
+
excludedChecks: z8.array(z8.object({ check: checkSchema, doc: z8.string() })).optional(),
|
|
3967
|
+
issues: z8.array(issueSchema),
|
|
3968
|
+
advisories: z8.array(advisorySchema),
|
|
3969
|
+
suppressedAdvisories: z8.array(advisorySchema).optional(),
|
|
3970
|
+
skippedChecks: z8.array(z8.object({ check: z8.string(), reason: z8.string(), doc: z8.string().optional() }))
|
|
3853
3971
|
});
|
|
3854
|
-
baselineSchema =
|
|
3855
|
-
kind:
|
|
3856
|
-
version:
|
|
3857
|
-
createdAt:
|
|
3972
|
+
baselineSchema = z8.object({
|
|
3973
|
+
kind: z8.literal("mason-audit-repair"),
|
|
3974
|
+
version: z8.literal(1),
|
|
3975
|
+
createdAt: z8.string().datetime(),
|
|
3858
3976
|
report: reportSchema,
|
|
3859
|
-
digest:
|
|
3977
|
+
digest: z8.string().regex(/^[a-f0-9]{64}$/)
|
|
3860
3978
|
});
|
|
3861
|
-
repairFindingSchema =
|
|
3862
|
-
id:
|
|
3863
|
-
original:
|
|
3864
|
-
status:
|
|
3865
|
-
reason:
|
|
3866
|
-
current:
|
|
3979
|
+
repairFindingSchema = z8.object({
|
|
3980
|
+
id: z8.string(),
|
|
3981
|
+
original: z8.union([issueSchema, advisorySchema]),
|
|
3982
|
+
status: z8.enum(["resolved", "unresolved", "review-required", "unverified"]),
|
|
3983
|
+
reason: z8.string(),
|
|
3984
|
+
current: z8.union([issueSchema, advisorySchema]).optional(),
|
|
3867
3985
|
review: reviewSummarySchema.optional()
|
|
3868
3986
|
});
|
|
3869
3987
|
}
|
|
@@ -3874,7 +3992,7 @@ import fs13 from "fs/promises";
|
|
|
3874
3992
|
import path22 from "path";
|
|
3875
3993
|
import { createHash as createHash3 } from "crypto";
|
|
3876
3994
|
import fg7 from "fast-glob";
|
|
3877
|
-
import { z as
|
|
3995
|
+
import { z as z9 } from "zod";
|
|
3878
3996
|
async function git3(root, ...args) {
|
|
3879
3997
|
return profilePhase("automation.git", async () => (await execGit(args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
|
|
3880
3998
|
}
|
|
@@ -4017,10 +4135,10 @@ var init_evidence = __esm({
|
|
|
4017
4135
|
init_dead_command();
|
|
4018
4136
|
init_storage();
|
|
4019
4137
|
init_advisory_review();
|
|
4020
|
-
engineVersion = true ? "0.
|
|
4138
|
+
engineVersion = true ? "0.20.1" : "development";
|
|
4021
4139
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
4022
4140
|
content = readAuditInput;
|
|
4023
|
-
cacheSchema =
|
|
4141
|
+
cacheSchema = z9.object({ version: z9.literal(1), entries: z9.record(z9.object({ key: z9.string(), result: checkResultSchema })), digest: z9.string() });
|
|
4024
4142
|
}
|
|
4025
4143
|
});
|
|
4026
4144
|
|
|
@@ -4036,7 +4154,7 @@ __export(model_exports, {
|
|
|
4036
4154
|
setupHostSchema: () => setupHostSchema,
|
|
4037
4155
|
setupSchema: () => setupSchema
|
|
4038
4156
|
});
|
|
4039
|
-
import { z as
|
|
4157
|
+
import { z as z10 } from "zod";
|
|
4040
4158
|
import fs14 from "fs/promises";
|
|
4041
4159
|
import path23 from "path";
|
|
4042
4160
|
async function loadSetup(root) {
|
|
@@ -4070,8 +4188,8 @@ async function effectiveSetup(root) {
|
|
|
4070
4188
|
if (inactive && setup) for (const host of inactiveHostsSchema.parse(inactive).hosts) delete setup.hosts[host];
|
|
4071
4189
|
return { root: inheritedRoot, setup };
|
|
4072
4190
|
}
|
|
4073
|
-
async function loadSetupReceipt(root,
|
|
4074
|
-
const raw = await readStoreJson(root,
|
|
4191
|
+
async function loadSetupReceipt(root, directory2, host) {
|
|
4192
|
+
const raw = await readStoreJson(root, directory2 + "/setup-" + host + ".json");
|
|
4075
4193
|
if (raw === null) return null;
|
|
4076
4194
|
const receipt = receiptSchema.parse(raw);
|
|
4077
4195
|
if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
|
|
@@ -4083,29 +4201,29 @@ var init_model = __esm({
|
|
|
4083
4201
|
"use strict";
|
|
4084
4202
|
init_evidence();
|
|
4085
4203
|
init_storage();
|
|
4086
|
-
setupHostSchema =
|
|
4087
|
-
configuredVersion:
|
|
4088
|
-
revision:
|
|
4089
|
-
fingerprint:
|
|
4090
|
-
mcpFingerprint:
|
|
4091
|
-
instructions:
|
|
4204
|
+
setupHostSchema = z10.object({
|
|
4205
|
+
configuredVersion: z10.string(),
|
|
4206
|
+
revision: z10.string().uuid(),
|
|
4207
|
+
fingerprint: z10.string(),
|
|
4208
|
+
mcpFingerprint: z10.string(),
|
|
4209
|
+
instructions: z10.array(z10.string())
|
|
4092
4210
|
});
|
|
4093
|
-
setupSchema =
|
|
4211
|
+
setupSchema = z10.object({ version: z10.literal(2), hosts: z10.object({
|
|
4094
4212
|
codex: setupHostSchema.optional(),
|
|
4095
4213
|
claude: setupHostSchema.optional()
|
|
4096
4214
|
}) });
|
|
4097
4215
|
SETUP_PATH = ".mason/local/setup.json";
|
|
4098
4216
|
INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
|
|
4099
|
-
inactiveHostsSchema =
|
|
4100
|
-
receiptSchema =
|
|
4101
|
-
version:
|
|
4102
|
-
host:
|
|
4103
|
-
status:
|
|
4104
|
-
initialReportPath:
|
|
4105
|
-
initialBaselinePaths:
|
|
4106
|
-
root:
|
|
4107
|
-
revision:
|
|
4108
|
-
configuredAt:
|
|
4217
|
+
inactiveHostsSchema = z10.object({ version: z10.literal(1), hosts: z10.array(z10.enum(["claude", "codex"])) });
|
|
4218
|
+
receiptSchema = z10.object({
|
|
4219
|
+
version: z10.literal(1),
|
|
4220
|
+
host: z10.enum(["codex", "claude"]),
|
|
4221
|
+
status: z10.enum(["installing", "configured"]),
|
|
4222
|
+
initialReportPath: z10.string(),
|
|
4223
|
+
initialBaselinePaths: z10.array(z10.string()),
|
|
4224
|
+
root: z10.string(),
|
|
4225
|
+
revision: z10.string().uuid(),
|
|
4226
|
+
configuredAt: z10.string().optional()
|
|
4109
4227
|
});
|
|
4110
4228
|
}
|
|
4111
4229
|
});
|
|
@@ -4143,7 +4261,7 @@ var init_launcher = __esm({
|
|
|
4143
4261
|
"use strict";
|
|
4144
4262
|
init_evidence();
|
|
4145
4263
|
init_model();
|
|
4146
|
-
executingVersion = () => true ? "0.
|
|
4264
|
+
executingVersion = () => true ? "0.20.1" : "development";
|
|
4147
4265
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
4148
4266
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
4149
4267
|
}
|
|
@@ -4327,6 +4445,146 @@ var init_files2 = __esm({
|
|
|
4327
4445
|
}
|
|
4328
4446
|
});
|
|
4329
4447
|
|
|
4448
|
+
// src/automation/usefulness.ts
|
|
4449
|
+
import fs19 from "fs/promises";
|
|
4450
|
+
import { z as z13 } from "zod";
|
|
4451
|
+
async function enabled(root) {
|
|
4452
|
+
const raw = await readStoreJson(root, configPath);
|
|
4453
|
+
return raw !== null && configSchema.parse(raw).enabled;
|
|
4454
|
+
}
|
|
4455
|
+
async function configureUsefulness(dir, value, options = {}) {
|
|
4456
|
+
const ws = await workspace(dir);
|
|
4457
|
+
const configured = await withStoreLock(ws.root, directory, async () => {
|
|
4458
|
+
if (options.onlyIfUnset) {
|
|
4459
|
+
const existing = await readStoreJson(ws.root, configPath);
|
|
4460
|
+
if (existing !== null) return configSchema.parse(existing).enabled;
|
|
4461
|
+
}
|
|
4462
|
+
const ignore = await storePath(ws.root, directory + "/.gitignore", true);
|
|
4463
|
+
try {
|
|
4464
|
+
await fs19.writeFile(ignore, "*\n", { flag: "wx", mode: 384 });
|
|
4465
|
+
} catch (error) {
|
|
4466
|
+
if (error.code !== "EEXIST") throw error;
|
|
4467
|
+
}
|
|
4468
|
+
await writeStoreJson(ws.root, configPath, { version: 1, enabled: value });
|
|
4469
|
+
return value;
|
|
4470
|
+
});
|
|
4471
|
+
return { enabled: configured, storage: directory, note: "Local only. Disabling retains prior observations." };
|
|
4472
|
+
}
|
|
4473
|
+
async function observeUsefulness(root, event, report, agent, completion, elapsedMs, observedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
4474
|
+
if (!event.host || !event.sessionId || !await enabled(root)) return;
|
|
4475
|
+
const ws = await workspace(root);
|
|
4476
|
+
await withStoreLock(ws.root, directory, async () => {
|
|
4477
|
+
if (!await enabled(root)) return;
|
|
4478
|
+
const raw = await readStoreJson(root, statePath(ws));
|
|
4479
|
+
const state = raw === null ? empty() : stateSchema2.parse(raw);
|
|
4480
|
+
const now = observedAt;
|
|
4481
|
+
const key = hash([event.host, event.sessionId]);
|
|
4482
|
+
const session = state.sessions[key] ??= {
|
|
4483
|
+
host: event.host,
|
|
4484
|
+
updatedAt: now,
|
|
4485
|
+
checks: 0,
|
|
4486
|
+
checkMs: 0,
|
|
4487
|
+
findings: {},
|
|
4488
|
+
decisions: [],
|
|
4489
|
+
retrievalObserved: false,
|
|
4490
|
+
receiptIds: [],
|
|
4491
|
+
reportPath: report.reportPath,
|
|
4492
|
+
truncated: false
|
|
4493
|
+
};
|
|
4494
|
+
const newer = now >= session.updatedAt;
|
|
4495
|
+
if (newer) {
|
|
4496
|
+
session.updatedAt = now;
|
|
4497
|
+
session.reportPath = report.reportPath;
|
|
4498
|
+
}
|
|
4499
|
+
session.checks++;
|
|
4500
|
+
session.checkMs += Math.max(0, elapsedMs);
|
|
4501
|
+
if (newer) {
|
|
4502
|
+
if (event.event === "task_end") session.endedAt = now;
|
|
4503
|
+
else delete session.endedAt;
|
|
4504
|
+
}
|
|
4505
|
+
let latestFindings = [];
|
|
4506
|
+
if (!newer && [...agent.slice(0, 4), ...completion.slice(0, 4)].some((f) => !session.findings[f.id])) {
|
|
4507
|
+
try {
|
|
4508
|
+
if (!session.reportPath.startsWith(ws.directory + "/checks/")) throw new Error("Unexpected report path");
|
|
4509
|
+
latestFindings = z13.object({ findings: z13.array(z13.object({ id: z13.string(), status: findingSchema2.shape.status })) }).parse(await readStoreJson(root, session.reportPath)).findings;
|
|
4510
|
+
} catch {
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
for (const [channel, findings] of [["agent", agent], ["completion", completion]]) {
|
|
4514
|
+
for (const finding of findings.slice(0, 4)) {
|
|
4515
|
+
if (!session.findings[finding.id] && Object.keys(session.findings).length >= 200) {
|
|
4516
|
+
session.truncated = true;
|
|
4517
|
+
continue;
|
|
4518
|
+
}
|
|
4519
|
+
const saved = session.findings[finding.id] ??= {
|
|
4520
|
+
type: finding.original.type,
|
|
4521
|
+
channels: [],
|
|
4522
|
+
status: newer ? finding.status : latestFindings.find((f) => f.id === finding.id)?.status ?? "unknown",
|
|
4523
|
+
deliveredAt: now,
|
|
4524
|
+
observedAt: newer ? now : session.updatedAt
|
|
4525
|
+
};
|
|
4526
|
+
if (!saved.channels.includes(channel)) saved.channels.push(channel);
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
for (const [id, saved] of Object.entries(session.findings)) {
|
|
4530
|
+
if (now < saved.observedAt) continue;
|
|
4531
|
+
const current = report.findings.find((f) => f.id === id);
|
|
4532
|
+
saved.status = current?.status ?? "unknown";
|
|
4533
|
+
saved.observedAt = now;
|
|
4534
|
+
}
|
|
4535
|
+
if (event.retrievedDecisionIds !== void 0 && event.toolId) {
|
|
4536
|
+
const receipt = hash([event.toolId]);
|
|
4537
|
+
if (!session.receiptIds.includes(receipt)) {
|
|
4538
|
+
session.retrievalObserved = true;
|
|
4539
|
+
const decisions = [.../* @__PURE__ */ new Set([...session.decisions, ...event.retrievedDecisionIds.map((id) => hash([id]))])];
|
|
4540
|
+
if (decisions.length > 200) session.truncated = true;
|
|
4541
|
+
session.decisions = decisions.slice(0, 200);
|
|
4542
|
+
session.receiptIds = [...session.receiptIds, receipt].slice(-200);
|
|
4543
|
+
}
|
|
4544
|
+
}
|
|
4545
|
+
const keys = Object.keys(state.sessions).sort((a, b) => state.sessions[b].updatedAt.localeCompare(state.sessions[a].updatedAt));
|
|
4546
|
+
for (const key2 of keys.slice(50)) delete state.sessions[key2];
|
|
4547
|
+
await writeStoreJson(root, statePath(ws), state);
|
|
4548
|
+
});
|
|
4549
|
+
}
|
|
4550
|
+
var directory, configPath, configSchema, usefulnessRating, findingSchema2, sessionSchema, stateSchema2, empty, statePath;
|
|
4551
|
+
var init_usefulness = __esm({
|
|
4552
|
+
"src/automation/usefulness.ts"() {
|
|
4553
|
+
"use strict";
|
|
4554
|
+
init_evidence();
|
|
4555
|
+
init_storage();
|
|
4556
|
+
init_store_lock();
|
|
4557
|
+
directory = ".mason/local/usefulness";
|
|
4558
|
+
configPath = directory + "/config.json";
|
|
4559
|
+
configSchema = z13.object({ version: z13.literal(1), enabled: z13.boolean() });
|
|
4560
|
+
usefulnessRating = z13.enum(["helpful", "already-knew", "irrelevant", "deferred"]);
|
|
4561
|
+
findingSchema2 = z13.object({
|
|
4562
|
+
type: z13.string(),
|
|
4563
|
+
channels: z13.array(z13.enum(["agent", "completion"])),
|
|
4564
|
+
status: z13.enum(["resolved", "unresolved", "review-required", "unverified", "unknown"]),
|
|
4565
|
+
deliveredAt: z13.string(),
|
|
4566
|
+
observedAt: z13.string(),
|
|
4567
|
+
rating: usefulnessRating.optional()
|
|
4568
|
+
});
|
|
4569
|
+
sessionSchema = z13.object({
|
|
4570
|
+
host: z13.string(),
|
|
4571
|
+
updatedAt: z13.string(),
|
|
4572
|
+
endedAt: z13.string().optional(),
|
|
4573
|
+
checks: z13.number(),
|
|
4574
|
+
checkMs: z13.number(),
|
|
4575
|
+
findings: z13.record(findingSchema2),
|
|
4576
|
+
decisions: z13.array(z13.string()),
|
|
4577
|
+
retrievalObserved: z13.boolean(),
|
|
4578
|
+
receiptIds: z13.array(z13.string()),
|
|
4579
|
+
reportPath: z13.string(),
|
|
4580
|
+
truncated: z13.boolean()
|
|
4581
|
+
});
|
|
4582
|
+
stateSchema2 = z13.object({ version: z13.literal(1), sessions: z13.record(sessionSchema) });
|
|
4583
|
+
empty = () => ({ version: 1, sessions: {} });
|
|
4584
|
+
statePath = (ws) => `${directory}/${hash([ws.root, ws.gitDir, ws.branch]).slice(0, 24)}.json`;
|
|
4585
|
+
}
|
|
4586
|
+
});
|
|
4587
|
+
|
|
4330
4588
|
// src/automation/execution.ts
|
|
4331
4589
|
var execution_exports = {};
|
|
4332
4590
|
__export(execution_exports, {
|
|
@@ -4338,9 +4596,9 @@ __export(execution_exports, {
|
|
|
4338
4596
|
recordExecution: () => recordExecution
|
|
4339
4597
|
});
|
|
4340
4598
|
import os2 from "os";
|
|
4341
|
-
import
|
|
4599
|
+
import fs20 from "fs/promises";
|
|
4342
4600
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4343
|
-
import { z as
|
|
4601
|
+
import { z as z14 } from "zod";
|
|
4344
4602
|
function parseExecution(raw) {
|
|
4345
4603
|
try {
|
|
4346
4604
|
return executionSchema.parse(raw);
|
|
@@ -4358,15 +4616,15 @@ function automationFailure(error) {
|
|
|
4358
4616
|
codes.add(String(cause.code));
|
|
4359
4617
|
cause = cause.cause;
|
|
4360
4618
|
}
|
|
4361
|
-
const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(message2) ? "inputs-changed" : /Automation is busy/.test(message2) ? "busy" : /not a git repository|unknown revision|bad revision|different history|unreachable/.test(message2) ? "history-unavailable" : error instanceof
|
|
4619
|
+
const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(message2) ? "inputs-changed" : /Automation is busy/.test(message2) ? "busy" : /not a git repository|unknown revision|bad revision|different history|unreachable/.test(message2) ? "history-unavailable" : error instanceof z14.ZodError || error instanceof SyntaxError || /Hook input|Expected one command|Unknown automation command|--host/.test(message2) ? "invalid-input" : /store|baseline|modified|symbolic link|Symlink|automation state|state belongs/.test(message2) ? "invalid-evidence" : [...codes].some((c) => /^E[A-Z]+$/.test(c)) ? "io-error" : "internal";
|
|
4362
4620
|
return { code, message: message2, retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code), receiptRecorded: false };
|
|
4363
4621
|
}
|
|
4364
4622
|
function failureMessage(error) {
|
|
4365
4623
|
const failure = automationFailure(error);
|
|
4366
4624
|
return `Mason automation unavailable [${failure.code}]; evidence capture/verification was not established. ${failure.message}` + (failure.receiptRecorded ? "" : " No durable failure receipt was recorded.");
|
|
4367
4625
|
}
|
|
4368
|
-
async function failureNotifications(root,
|
|
4369
|
-
const raw = await readStoreJson(root,
|
|
4626
|
+
async function failureNotifications(root, directory2) {
|
|
4627
|
+
const raw = await readStoreJson(root, directory2 + "/notifications.json");
|
|
4370
4628
|
return raw === null ? { version: 1, sessions: {} } : notificationsSchema.parse(raw);
|
|
4371
4629
|
}
|
|
4372
4630
|
async function hookFailureMessage(dir, host, sessionId, error) {
|
|
@@ -4396,12 +4654,12 @@ async function hookFailureMessage(dir, host, sessionId, error) {
|
|
|
4396
4654
|
return failureMessage(error);
|
|
4397
4655
|
}
|
|
4398
4656
|
}
|
|
4399
|
-
async function refreshRunning(root,
|
|
4657
|
+
async function refreshRunning(root, directory2, attempts) {
|
|
4400
4658
|
for (const attempt of attempts) {
|
|
4401
4659
|
if (attempt.status !== "running") continue;
|
|
4402
4660
|
let alive = false;
|
|
4403
4661
|
try {
|
|
4404
|
-
const owner = await readStoreJson(root, attempt.lease ? leasePath(
|
|
4662
|
+
const owner = await readStoreJson(root, attempt.lease ? leasePath(directory2, attempt.id) : directory2 + "/lock");
|
|
4405
4663
|
if (attempt.host === os2.hostname() && owner?.pid === attempt.pid && owner.host === attempt.host) {
|
|
4406
4664
|
try {
|
|
4407
4665
|
process.kill(attempt.pid, 0);
|
|
@@ -4415,12 +4673,12 @@ async function refreshRunning(root, directory, attempts) {
|
|
|
4415
4673
|
if (!alive) attempt.status = "unknown";
|
|
4416
4674
|
}
|
|
4417
4675
|
}
|
|
4418
|
-
async function editExecution(root,
|
|
4419
|
-
return withLock(root,
|
|
4420
|
-
const file =
|
|
4676
|
+
async function editExecution(root, directory2, edit) {
|
|
4677
|
+
return withLock(root, directory2 + "/execution-lock", async () => {
|
|
4678
|
+
const file = directory2 + "/execution.json";
|
|
4421
4679
|
const raw = await readStoreJson(root, file);
|
|
4422
4680
|
const log = raw === null ? { version: 1, attempts: [], discardedAttempts: 0 } : parseExecution(raw);
|
|
4423
|
-
await refreshRunning(root,
|
|
4681
|
+
await refreshRunning(root, directory2, log.attempts);
|
|
4424
4682
|
edit(log);
|
|
4425
4683
|
const finished = log.attempts.filter((a) => a.status !== "running");
|
|
4426
4684
|
const discard = new Set(finished.slice(0, Math.max(0, finished.length - 32)).map((a) => a.id));
|
|
@@ -4429,7 +4687,7 @@ async function editExecution(root, directory, edit) {
|
|
|
4429
4687
|
await writeStoreJson(root, file, log);
|
|
4430
4688
|
});
|
|
4431
4689
|
}
|
|
4432
|
-
async function recordExecution(root,
|
|
4690
|
+
async function recordExecution(root, directory2, event, run) {
|
|
4433
4691
|
const started = performance.now();
|
|
4434
4692
|
const attempt = {
|
|
4435
4693
|
id: randomUUID4(),
|
|
@@ -4440,15 +4698,15 @@ async function recordExecution(root, directory, event, run) {
|
|
|
4440
4698
|
status: "running",
|
|
4441
4699
|
lease: true
|
|
4442
4700
|
};
|
|
4443
|
-
const owner = leasePath(
|
|
4444
|
-
const finish = async (changes) => editExecution(root,
|
|
4701
|
+
const owner = leasePath(directory2, attempt.id);
|
|
4702
|
+
const finish = async (changes) => editExecution(root, directory2, (log) => {
|
|
4445
4703
|
const current = log.attempts.find((a) => a.id === attempt.id);
|
|
4446
4704
|
if (!current) throw new Error("Automation execution receipt disappeared; completion could not be recorded.");
|
|
4447
4705
|
Object.assign(current, changes, { finishedAt: (/* @__PURE__ */ new Date()).toISOString(), durationMs: performance.now() - started });
|
|
4448
4706
|
});
|
|
4449
4707
|
await writeStoreJson(root, owner, { pid: attempt.pid, host: attempt.host });
|
|
4450
4708
|
try {
|
|
4451
|
-
await editExecution(root,
|
|
4709
|
+
await editExecution(root, directory2, (log) => {
|
|
4452
4710
|
if (log.attempts.filter((a) => a.status === "running").length >= 128) throw new Error("Too many active automation executions; existing receipts were retained.");
|
|
4453
4711
|
log.attempts.push(attempt);
|
|
4454
4712
|
});
|
|
@@ -4466,15 +4724,15 @@ async function recordExecution(root, directory, event, run) {
|
|
|
4466
4724
|
throw Object.assign(new Error(failure.message, { cause: error }), { failure });
|
|
4467
4725
|
}
|
|
4468
4726
|
} finally {
|
|
4469
|
-
await
|
|
4727
|
+
await fs20.rm(await storePath(root, owner), { force: true }).catch(() => {
|
|
4470
4728
|
});
|
|
4471
4729
|
}
|
|
4472
4730
|
}
|
|
4473
|
-
async function executionStatus(root,
|
|
4474
|
-
const raw = await readStoreJson(root,
|
|
4731
|
+
async function executionStatus(root, directory2) {
|
|
4732
|
+
const raw = await readStoreJson(root, directory2 + "/execution.json");
|
|
4475
4733
|
if (raw === null) return { status: "not-observed", attempts: [] };
|
|
4476
4734
|
const log = parseExecution(raw);
|
|
4477
|
-
await refreshRunning(root,
|
|
4735
|
+
await refreshRunning(root, directory2, log.attempts);
|
|
4478
4736
|
const completed = [...log.attempts].reverse().find((a) => a.status === "completed");
|
|
4479
4737
|
const outstanding = [...log.attempts].reverse().find((a) => ["failed", "unknown"].includes(a.status) && (!completed || (a.finishedAt ?? a.startedAt) >= completed.startedAt));
|
|
4480
4738
|
const status = log.attempts.some((a) => a.status === "running") ? "running" : outstanding?.status ?? log.attempts.at(-1)?.status ?? "not-observed";
|
|
@@ -4487,35 +4745,97 @@ var init_execution = __esm({
|
|
|
4487
4745
|
init_store();
|
|
4488
4746
|
init_evidence();
|
|
4489
4747
|
init_storage();
|
|
4490
|
-
failureSchema =
|
|
4491
|
-
code:
|
|
4492
|
-
message:
|
|
4493
|
-
retryable:
|
|
4494
|
-
receiptRecorded:
|
|
4748
|
+
failureSchema = z14.object({
|
|
4749
|
+
code: z14.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
|
|
4750
|
+
message: z14.string(),
|
|
4751
|
+
retryable: z14.boolean(),
|
|
4752
|
+
receiptRecorded: z14.boolean()
|
|
4495
4753
|
});
|
|
4496
|
-
attemptSchema =
|
|
4497
|
-
id:
|
|
4498
|
-
lease:
|
|
4499
|
-
event:
|
|
4500
|
-
startedAt:
|
|
4501
|
-
finishedAt:
|
|
4502
|
-
durationMs:
|
|
4503
|
-
pid:
|
|
4504
|
-
host:
|
|
4505
|
-
status:
|
|
4506
|
-
verificationStatus:
|
|
4507
|
-
reportPath:
|
|
4754
|
+
attemptSchema = z14.object({
|
|
4755
|
+
id: z14.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
4756
|
+
lease: z14.literal(true).optional(),
|
|
4757
|
+
event: z14.string(),
|
|
4758
|
+
startedAt: z14.string(),
|
|
4759
|
+
finishedAt: z14.string().optional(),
|
|
4760
|
+
durationMs: z14.number().nonnegative().optional(),
|
|
4761
|
+
pid: z14.number().int().positive(),
|
|
4762
|
+
host: z14.string(),
|
|
4763
|
+
status: z14.enum(["running", "completed", "failed", "unknown"]),
|
|
4764
|
+
verificationStatus: z14.string().optional(),
|
|
4765
|
+
reportPath: z14.string().optional(),
|
|
4508
4766
|
failure: failureSchema.optional()
|
|
4509
4767
|
});
|
|
4510
|
-
executionSchema =
|
|
4511
|
-
notificationsSchema =
|
|
4512
|
-
signature:
|
|
4768
|
+
executionSchema = z14.object({ version: z14.literal(1), attempts: z14.array(attemptSchema).max(160), discardedAttempts: z14.number().int().nonnegative().default(0) });
|
|
4769
|
+
notificationsSchema = z14.object({ version: z14.literal(1), sessions: z14.record(z14.object({
|
|
4770
|
+
signature: z14.string(),
|
|
4513
4771
|
code: failureSchema.shape.code,
|
|
4514
|
-
message:
|
|
4515
|
-
count:
|
|
4516
|
-
lastAt:
|
|
4772
|
+
message: z14.string(),
|
|
4773
|
+
count: z14.number().int().positive(),
|
|
4774
|
+
lastAt: z14.string()
|
|
4517
4775
|
})) });
|
|
4518
|
-
leasePath = (
|
|
4776
|
+
leasePath = (directory2, id) => `${directory2}/executions/${id}.json`;
|
|
4777
|
+
}
|
|
4778
|
+
});
|
|
4779
|
+
|
|
4780
|
+
// src/decisions/pending.ts
|
|
4781
|
+
var pending_exports = {};
|
|
4782
|
+
__export(pending_exports, {
|
|
4783
|
+
pendingKnowledge: () => pendingKnowledge
|
|
4784
|
+
});
|
|
4785
|
+
async function pendingKnowledge(root, now = Date.now()) {
|
|
4786
|
+
try {
|
|
4787
|
+
const [store, status] = await Promise.all([
|
|
4788
|
+
loadDecisionStore(root),
|
|
4789
|
+
execGit(
|
|
4790
|
+
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".mason/decisions"],
|
|
4791
|
+
{ cwd: root, timeout: 1e4, maxBuffer: 1024 * 1024 }
|
|
4792
|
+
)
|
|
4793
|
+
]);
|
|
4794
|
+
const changed = /* @__PURE__ */ new Set();
|
|
4795
|
+
const entries = status.stdout.split("\0");
|
|
4796
|
+
for (let i = 0; i < entries.length; i++) {
|
|
4797
|
+
const entry = entries[i];
|
|
4798
|
+
if (!entry) continue;
|
|
4799
|
+
const file = entry.slice(3);
|
|
4800
|
+
if (/^\.mason\/decisions\/[^/]+\.json$/.test(file)) changed.add(file);
|
|
4801
|
+
if (/[RC]/.test(entry.slice(0, 2))) i++;
|
|
4802
|
+
}
|
|
4803
|
+
const records = new Map(store.records.map((record) => [`.mason/decisions/${record.id}.json`, record]));
|
|
4804
|
+
const age = (at) => {
|
|
4805
|
+
const time = at ? Date.parse(at) : NaN;
|
|
4806
|
+
return Number.isFinite(time) ? `${Math.max(0, Math.floor((now - time) / 864e5))} days` : "unknown age";
|
|
4807
|
+
};
|
|
4808
|
+
const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ");
|
|
4809
|
+
const list = (items) => items.slice(0, 5).map(safe).join("; ") + (items.length > 5 ? `; ${items.length - 5} more` : "");
|
|
4810
|
+
const proposals = store.records.filter((record) => record.status === "active" && decisionApproval(record) === "proposed").map((record) => {
|
|
4811
|
+
const history = record.version === 2 ? record.history : [];
|
|
4812
|
+
let lastReview = -1;
|
|
4813
|
+
history.forEach((event, index2) => {
|
|
4814
|
+
if (event.kind === "accepted" || event.kind === "reaffirmed") lastReview = index2;
|
|
4815
|
+
});
|
|
4816
|
+
const since = history.slice(lastReview + 1).find((event) => event.kind === "created" || event.kind === "revised")?.at ?? record.createdAt;
|
|
4817
|
+
return { record, since };
|
|
4818
|
+
}).sort((a, b) => {
|
|
4819
|
+
const timestamp = (value) => Number.isFinite(Date.parse(value)) ? Date.parse(value) : Infinity;
|
|
4820
|
+
return timestamp(a.since) - timestamp(b.since) || a.record.id.localeCompare(b.record.id);
|
|
4821
|
+
});
|
|
4822
|
+
const lines = [];
|
|
4823
|
+
if (changed.size) lines.push(`${changed.size} uncommitted decision record(s): ${list([...changed].sort().map((file) => `${file} (saved-record age: ${age(records.get(file)?.updatedAt)})`))}.`);
|
|
4824
|
+
if (proposals.length) lines.push(`${proposals.length} proposal(s) awaiting review (oldest first): ${list(proposals.map(({ record, since }) => `.mason/decisions/${record.id}.json (${age(since)})`))}.`);
|
|
4825
|
+
if (store.diagnostics.length) lines.push(`${store.diagnostics.length} decision record(s) could not be read; pending knowledge summary is incomplete.`);
|
|
4826
|
+
return lines.length ? `Mason pending knowledge:
|
|
4827
|
+
${lines.join("\n")}
|
|
4828
|
+
Informational only; review and commit through the normal project workflow. No approval is implied.` : null;
|
|
4829
|
+
} catch {
|
|
4830
|
+
return "Mason pending knowledge: unavailable; decision records or Git status could not be read.";
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
var init_pending = __esm({
|
|
4834
|
+
"src/decisions/pending.ts"() {
|
|
4835
|
+
"use strict";
|
|
4836
|
+
init_git_read();
|
|
4837
|
+
init_decisions();
|
|
4838
|
+
init_provenance();
|
|
4519
4839
|
}
|
|
4520
4840
|
});
|
|
4521
4841
|
|
|
@@ -4528,12 +4848,12 @@ __export(runtime_exports, {
|
|
|
4528
4848
|
summarize: () => summarize
|
|
4529
4849
|
});
|
|
4530
4850
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
4531
|
-
import { z as
|
|
4851
|
+
import { z as z15 } from "zod";
|
|
4532
4852
|
async function observeReadOnlyTool(dir, event) {
|
|
4533
4853
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
4534
4854
|
await withLock(ws.root, ws.directory, async () => {
|
|
4535
|
-
const
|
|
4536
|
-
const raw = await readStoreJson(ws.root,
|
|
4855
|
+
const statePath2 = ws.directory + "/state.json";
|
|
4856
|
+
const raw = await readStoreJson(ws.root, statePath2);
|
|
4537
4857
|
if (raw === null) return;
|
|
4538
4858
|
const state = parseState(raw);
|
|
4539
4859
|
if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) {
|
|
@@ -4544,7 +4864,7 @@ async function observeReadOnlyTool(dir, event) {
|
|
|
4544
4864
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4545
4865
|
session.lastUsed = now;
|
|
4546
4866
|
session.events[event.event] = { at: now, count: (session.events[event.event]?.count ?? 0) + 1 };
|
|
4547
|
-
await writeStoreJson(ws.root,
|
|
4867
|
+
await writeStoreJson(ws.root, statePath2, state);
|
|
4548
4868
|
});
|
|
4549
4869
|
return { root: ws.root, directory: ws.directory };
|
|
4550
4870
|
}
|
|
@@ -4559,7 +4879,7 @@ function summarize(report) {
|
|
|
4559
4879
|
}),
|
|
4560
4880
|
...open.length > 4 ? [`${open.length - 4} more findings in the report.`] : [],
|
|
4561
4881
|
...report.diagnostics.slice(0, 2).map(cleanText),
|
|
4562
|
-
`Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason
|
|
4882
|
+
`Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason check.`,
|
|
4563
4883
|
"Keep original evidence. Address findings relevant to the authorized task; report unrelated findings with a brief scope reason and suggested follow-up. Use review_advisory for authorized assessments; otherwise leave advisories open without approving them."
|
|
4564
4884
|
].join("\n");
|
|
4565
4885
|
}
|
|
@@ -4618,7 +4938,7 @@ async function verifyInputs(ws, inputs) {
|
|
|
4618
4938
|
async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
|
|
4619
4939
|
const baselines = [...state.baselines];
|
|
4620
4940
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4621
|
-
const
|
|
4941
|
+
const empty2 = {
|
|
4622
4942
|
version: 1,
|
|
4623
4943
|
status: "unavailable",
|
|
4624
4944
|
root: ws.root,
|
|
@@ -4635,7 +4955,7 @@ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDia
|
|
|
4635
4955
|
};
|
|
4636
4956
|
if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) {
|
|
4637
4957
|
await verifyInputs(ws, inputs);
|
|
4638
|
-
return { baselines, report:
|
|
4958
|
+
return { baselines, report: empty2, cache: null };
|
|
4639
4959
|
}
|
|
4640
4960
|
if (ws.branch === "detached" && state.latest) {
|
|
4641
4961
|
const previous = await readStoreJson(ws.root, state.latest);
|
|
@@ -4737,7 +5057,7 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
4737
5057
|
async function automate(dir, event) {
|
|
4738
5058
|
const startedAt = Date.now();
|
|
4739
5059
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
4740
|
-
|
|
5060
|
+
const output = await recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
|
|
4741
5061
|
const inputs = await readInputs(ws.root);
|
|
4742
5062
|
const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
|
|
4743
5063
|
if (result.shared) {
|
|
@@ -4750,6 +5070,19 @@ async function automate(dir, event) {
|
|
|
4750
5070
|
const state = await loadState(ws);
|
|
4751
5071
|
if (state.analysis?.id !== result.analysis.id) throw new Error("Automation evidence changed during publication; retry against the current inputs.");
|
|
4752
5072
|
const report = structuredClone(result.analysis.report);
|
|
5073
|
+
if (!state.notifications) {
|
|
5074
|
+
let baseline = { findings: report.findings, head: report.head };
|
|
5075
|
+
if (state.latest) {
|
|
5076
|
+
try {
|
|
5077
|
+
const previous = z15.object({ findings: z15.array(repairFindingSchema), head: z15.string() }).safeParse(await readStoreJson(ws.root, state.latest));
|
|
5078
|
+
if (!previous.success) throw new Error("Unsupported saved report");
|
|
5079
|
+
baseline = previous.data;
|
|
5080
|
+
} catch {
|
|
5081
|
+
report.diagnostics.push("The saved notification baseline could not be read; initialized from the current audit. Original repair evidence is retained. Run an explicit check for all outstanding findings.");
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
state.notifications = createNotificationState(baseline.findings, baseline.head);
|
|
5085
|
+
}
|
|
4753
5086
|
if (result.shared) report.checks = { ...report.checks, ran: [], reused: [.../* @__PURE__ */ new Set([...report.checks.ran, ...report.checks.reused])], shared: true };
|
|
4754
5087
|
report.reportPath = ws.directory + "/checks/" + randomUUID5() + ".json";
|
|
4755
5088
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4762,12 +5095,8 @@ async function automate(dir, event) {
|
|
|
4762
5095
|
if (Object.keys(state.sessions).length >= 128) throw new Error("Too many sessions with unfinished tool calls; original evidence was retained.");
|
|
4763
5096
|
state.sessions[key] = {
|
|
4764
5097
|
host: event.host,
|
|
4765
|
-
|
|
4766
|
-
continued: false,
|
|
4767
|
-
initialIssues: report.findings.filter((f) => f.status === "unresolved").map((f) => f.id),
|
|
4768
|
-
initialDocs: inputs.docs,
|
|
5098
|
+
notifiedDiagnostics: [],
|
|
4769
5099
|
lastUsed: now,
|
|
4770
|
-
mutationObserved: false,
|
|
4771
5100
|
pending: {},
|
|
4772
5101
|
coverageGaps: [],
|
|
4773
5102
|
events: {}
|
|
@@ -4782,7 +5111,6 @@ async function automate(dir, event) {
|
|
|
4782
5111
|
session.pending[event.toolId] = inputs.fingerprint;
|
|
4783
5112
|
}
|
|
4784
5113
|
if (event.event === "after_tool" && event.mutating) {
|
|
4785
|
-
session.mutationObserved = true;
|
|
4786
5114
|
if (!event.toolId || !session.pending[event.toolId]) {
|
|
4787
5115
|
const gap = "A tool completed without an observed matching pre-tool capture in this checkout; pre-edit coverage is unknown. The tool may have changed checkouts; captures are kept separately for each worktree.";
|
|
4788
5116
|
if (!session.coverageGaps.includes(gap)) session.coverageGaps.push(gap);
|
|
@@ -4793,31 +5121,45 @@ async function automate(dir, event) {
|
|
|
4793
5121
|
if (session.coverageGaps.length && report.status !== "unavailable") report.status = "incomplete";
|
|
4794
5122
|
report.capture = report.status !== "unavailable" && !session.coverageGaps.length && (session.events.session_start || session.events.before_tool) ? "observed" : "unknown";
|
|
4795
5123
|
}
|
|
4796
|
-
const
|
|
4797
|
-
|
|
4798
|
-
const
|
|
4799
|
-
const
|
|
4800
|
-
if (session)
|
|
4801
|
-
session.
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
const
|
|
5124
|
+
const selected = session && event.event === "task_end" ? completionFindings(state.notifications, report.findings) : [];
|
|
5125
|
+
state.agentNotifications ??= { ...structuredClone(state.notifications), delivered: {} };
|
|
5126
|
+
const feedback = session && ["turn_start", "after_tool"].includes(event.event) ? completionFindings(state.agentNotifications, report.findings) : [];
|
|
5127
|
+
const diagnostics = session ? report.diagnostics.filter((d) => !session.notifiedDiagnostics.includes(d)).slice(0, 2) : [];
|
|
5128
|
+
if (session) session.notifiedDiagnostics = [.../* @__PURE__ */ new Set([
|
|
5129
|
+
...session.notifiedDiagnostics.filter((d) => report.diagnostics.includes(d)),
|
|
5130
|
+
...diagnostics
|
|
5131
|
+
])];
|
|
5132
|
+
const message2 = !session ? summarize(report) : [
|
|
5133
|
+
completionSummary(selected, report.reportPath),
|
|
5134
|
+
completionSummary(feedback, report.reportPath),
|
|
5135
|
+
...diagnostics.length ? [`Mason: verification limited. ${diagnostics.slice(0, 2).map(cleanText).join(" ")} Evidence: ${report.reportPath}.`] : []
|
|
5136
|
+
].filter(Boolean).join("\n") || null;
|
|
5137
|
+
const notify = message2 !== null;
|
|
5138
|
+
const persistReport = !state.latest || state.fingerprint !== inputs.fingerprint || notify || newSession || event.event === "task_end";
|
|
4805
5139
|
if (!persistReport) report.reportPath = state.latest;
|
|
4806
5140
|
state.updatedAt = now;
|
|
4807
5141
|
state.fingerprint = inputs.fingerprint;
|
|
4808
5142
|
state.latest = report.reportPath;
|
|
4809
5143
|
if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
|
|
4810
5144
|
await writeStoreJson(ws.root, ws.directory + "/state.json", state);
|
|
4811
|
-
return { report, message:
|
|
5145
|
+
return { report, message: message2, observedAt: now, delivery: { agent: feedback.slice(0, 4), completion: selected.slice(0, 4) } };
|
|
4812
5146
|
}));
|
|
4813
5147
|
}));
|
|
5148
|
+
try {
|
|
5149
|
+
await observeUsefulness(ws.root, event, output.report, output.delivery.agent, output.delivery.completion, Date.now() - startedAt, output.observedAt);
|
|
5150
|
+
} catch {
|
|
5151
|
+
if (event.event === "task_end") output.message = [output.message, "Mason: local usefulness tracking unavailable; inspect mason stats --json."].filter(Boolean).join("\n");
|
|
5152
|
+
}
|
|
5153
|
+
return output;
|
|
4814
5154
|
}
|
|
4815
5155
|
async function automationStatus(dir) {
|
|
5156
|
+
const { pendingKnowledge: pendingKnowledge2 } = await Promise.resolve().then(() => (init_pending(), pending_exports));
|
|
4816
5157
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
5158
|
+
const knowledge = await pendingKnowledge2(ws.root);
|
|
4817
5159
|
const execution = await executionStatus(ws.root, ws.directory);
|
|
4818
5160
|
const notifications = await failureNotifications(ws.root, ws.directory);
|
|
4819
5161
|
const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
|
|
4820
|
-
if (raw === null) return { version: 1, status: execution.status === "not-observed" ? "not-observed" : "unavailable", root: ws.root, branch: ws.branch, baselinePaths: [], hosts: {}, execution, notifications };
|
|
5162
|
+
if (raw === null) return { version: 1, status: execution.status === "not-observed" ? "not-observed" : "unavailable", root: ws.root, branch: ws.branch, baselinePaths: [], hosts: {}, execution, notifications, knowledge };
|
|
4821
5163
|
const state = parseState(raw);
|
|
4822
5164
|
if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) throw new Error("Automation state belongs to another workspace.");
|
|
4823
5165
|
const inputs = await readInputs(ws.root);
|
|
@@ -4840,6 +5182,8 @@ async function automationStatus(dir) {
|
|
|
4840
5182
|
hosts,
|
|
4841
5183
|
execution,
|
|
4842
5184
|
notifications,
|
|
5185
|
+
knowledge,
|
|
5186
|
+
notificationBaselineHead: state.notifications?.baselineHead ?? null,
|
|
4843
5187
|
note: "Observed events do not prove all tool paths are intercepted. Run check to verify the retained evidence."
|
|
4844
5188
|
};
|
|
4845
5189
|
}
|
|
@@ -4847,6 +5191,8 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
|
|
|
4847
5191
|
var init_runtime = __esm({
|
|
4848
5192
|
"src/automation/runtime.ts"() {
|
|
4849
5193
|
"use strict";
|
|
5194
|
+
init_usefulness();
|
|
5195
|
+
init_notifications();
|
|
4850
5196
|
init_profile();
|
|
4851
5197
|
init_inspection();
|
|
4852
5198
|
init_execution();
|
|
@@ -4858,34 +5204,34 @@ var init_runtime = __esm({
|
|
|
4858
5204
|
SCOPE = "Documentation audit evidence only. Hook receipts show observed events, not complete interception. Resolved findings no longer fail their checks or have a recorded assessment covering current evidence. Repair and review only within the user's task authorization.";
|
|
4859
5205
|
priority = { resolved: 0, "review-required": 1, unresolved: 2, unverified: 3 };
|
|
4860
5206
|
cleanText = (text2) => text2.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
|
|
4861
|
-
analysisSchema =
|
|
4862
|
-
id:
|
|
4863
|
-
fingerprint:
|
|
4864
|
-
completedAt:
|
|
4865
|
-
baselineDigest:
|
|
5207
|
+
analysisSchema = z15.object({
|
|
5208
|
+
id: z15.string(),
|
|
5209
|
+
fingerprint: z15.string(),
|
|
5210
|
+
completedAt: z15.number(),
|
|
5211
|
+
baselineDigest: z15.string(),
|
|
4866
5212
|
currentAudit: reportSchema.nullable().optional(),
|
|
4867
|
-
report:
|
|
4868
|
-
version:
|
|
4869
|
-
status:
|
|
4870
|
-
root:
|
|
4871
|
-
branch:
|
|
4872
|
-
head:
|
|
4873
|
-
baselinePaths:
|
|
4874
|
-
reportPath:
|
|
4875
|
-
findings:
|
|
4876
|
-
diagnostics:
|
|
4877
|
-
checks:
|
|
4878
|
-
ran:
|
|
4879
|
-
reused:
|
|
4880
|
-
skipped:
|
|
4881
|
-
auditReused:
|
|
5213
|
+
report: z15.object({
|
|
5214
|
+
version: z15.literal(1),
|
|
5215
|
+
status: z15.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
|
|
5216
|
+
root: z15.string(),
|
|
5217
|
+
branch: z15.string(),
|
|
5218
|
+
head: z15.string(),
|
|
5219
|
+
baselinePaths: z15.array(z15.string()),
|
|
5220
|
+
reportPath: z15.string(),
|
|
5221
|
+
findings: z15.array(repairFindingSchema),
|
|
5222
|
+
diagnostics: z15.array(z15.string()),
|
|
5223
|
+
checks: z15.object({
|
|
5224
|
+
ran: z15.array(z15.string()),
|
|
5225
|
+
reused: z15.array(z15.string()),
|
|
5226
|
+
skipped: z15.array(z15.object({ check: z15.string(), reason: z15.string(), doc: z15.string().optional() })),
|
|
5227
|
+
auditReused: z15.boolean().optional()
|
|
4882
5228
|
}),
|
|
4883
|
-
counts:
|
|
4884
|
-
capture:
|
|
4885
|
-
scope:
|
|
5229
|
+
counts: z15.object({ resolved: z15.number(), unresolved: z15.number(), "review-required": z15.number(), unverified: z15.number() }),
|
|
5230
|
+
capture: z15.enum(["observed", "unknown"]),
|
|
5231
|
+
scope: z15.string()
|
|
4886
5232
|
})
|
|
4887
5233
|
});
|
|
4888
|
-
sharedSchema =
|
|
5234
|
+
sharedSchema = z15.object({ analysis: analysisSchema, digest: z15.string() });
|
|
4889
5235
|
baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
|
|
4890
5236
|
state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
|
|
4891
5237
|
)));
|
|
@@ -4893,7 +5239,7 @@ var init_runtime = __esm({
|
|
|
4893
5239
|
});
|
|
4894
5240
|
|
|
4895
5241
|
// src/automation/hook-root.ts
|
|
4896
|
-
import
|
|
5242
|
+
import fs21 from "fs/promises";
|
|
4897
5243
|
import path28 from "path";
|
|
4898
5244
|
var init_hook_root = __esm({
|
|
4899
5245
|
"src/automation/hook-root.ts"() {
|
|
@@ -4903,7 +5249,7 @@ var init_hook_root = __esm({
|
|
|
4903
5249
|
});
|
|
4904
5250
|
|
|
4905
5251
|
// src/automation/adapters.ts
|
|
4906
|
-
import { z as
|
|
5252
|
+
import { z as z16 } from "zod";
|
|
4907
5253
|
function managedHookCommand(host, platform = process.platform) {
|
|
4908
5254
|
const command = `mason --setup-host ${host} auto hook --host ${host}`;
|
|
4909
5255
|
return platform === "win32" ? `cmd.exe /d /s /c "where mason >nul 2>nul & if errorlevel 1 (exit /b 0) else (${command})"` : `if command -v mason >/dev/null 2>&1; then ${command}; fi`;
|
|
@@ -4914,7 +5260,7 @@ function knownManagedHookCommands(host) {
|
|
|
4914
5260
|
function compatibleManagedHookCommands(host, platform = process.platform) {
|
|
4915
5261
|
return [managedHookCommand(host, platform), `mason --setup-host ${host} auto hook --host ${host}`];
|
|
4916
5262
|
}
|
|
4917
|
-
function hookConfig(host, command = "npx --no-install --package mason-context mason
|
|
5263
|
+
function hookConfig(host, command = "npx --no-install --package mason-context mason auto") {
|
|
4918
5264
|
const handler = { type: "command", command: command === `mason --setup-host ${host} auto` ? managedHookCommand(host) : command + " hook --host " + host, timeout: 30 };
|
|
4919
5265
|
return { hooks: Object.fromEntries(HOOK_EVENTS.map((name) => [
|
|
4920
5266
|
name,
|
|
@@ -4925,19 +5271,21 @@ var inputSchema, HOOK_EVENTS;
|
|
|
4925
5271
|
var init_adapters = __esm({
|
|
4926
5272
|
"src/automation/adapters.ts"() {
|
|
4927
5273
|
"use strict";
|
|
5274
|
+
init_usefulness();
|
|
4928
5275
|
init_execution();
|
|
4929
5276
|
init_runtime();
|
|
4930
5277
|
init_store();
|
|
4931
5278
|
init_hook_root();
|
|
4932
|
-
inputSchema =
|
|
4933
|
-
cwd:
|
|
4934
|
-
session_id:
|
|
4935
|
-
hook_event_name:
|
|
4936
|
-
tool_name:
|
|
4937
|
-
tool_use_id:
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
5279
|
+
inputSchema = z16.object({
|
|
5280
|
+
cwd: z16.string().min(1),
|
|
5281
|
+
session_id: z16.string().min(1).max(500),
|
|
5282
|
+
hook_event_name: z16.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
|
|
5283
|
+
tool_name: z16.string().optional(),
|
|
5284
|
+
tool_use_id: z16.string().max(500).optional(),
|
|
5285
|
+
tool_response: z16.unknown().optional(),
|
|
5286
|
+
tool_input: z16.unknown().optional(),
|
|
5287
|
+
stop_hook_active: z16.boolean().optional(),
|
|
5288
|
+
permission_mode: z16.string().optional()
|
|
4941
5289
|
});
|
|
4942
5290
|
HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"];
|
|
4943
5291
|
}
|
|
@@ -4953,7 +5301,7 @@ __export(ownership_exports, {
|
|
|
4953
5301
|
ownershipEdit: () => ownershipEdit,
|
|
4954
5302
|
ownershipSchema: () => ownershipSchema
|
|
4955
5303
|
});
|
|
4956
|
-
import { z as
|
|
5304
|
+
import { z as z17 } from "zod";
|
|
4957
5305
|
import { parse } from "smol-toml";
|
|
4958
5306
|
function blockSpan(text2, start, end) {
|
|
4959
5307
|
const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
|
|
@@ -5004,15 +5352,15 @@ var init_ownership = __esm({
|
|
|
5004
5352
|
["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
|
|
5005
5353
|
["# mason:mcp:start", "# mason:mcp:end"]
|
|
5006
5354
|
];
|
|
5007
|
-
fileSchema =
|
|
5008
|
-
created:
|
|
5009
|
-
initialHash:
|
|
5010
|
-
blocks:
|
|
5011
|
-
mcpHash:
|
|
5012
|
-
hookCommands:
|
|
5013
|
-
hooks:
|
|
5355
|
+
fileSchema = z17.object({
|
|
5356
|
+
created: z17.boolean(),
|
|
5357
|
+
initialHash: z17.string(),
|
|
5358
|
+
blocks: z17.record(z17.string()),
|
|
5359
|
+
mcpHash: z17.string().optional(),
|
|
5360
|
+
hookCommands: z17.array(z17.string()).optional(),
|
|
5361
|
+
hooks: z17.record(z17.array(z17.string())).optional()
|
|
5014
5362
|
});
|
|
5015
|
-
ownershipSchema =
|
|
5363
|
+
ownershipSchema = z17.object({ version: z17.literal(1), files: z17.record(fileSchema) });
|
|
5016
5364
|
}
|
|
5017
5365
|
});
|
|
5018
5366
|
|
|
@@ -5021,13 +5369,13 @@ var install_exports = {};
|
|
|
5021
5369
|
__export(install_exports, {
|
|
5022
5370
|
AUTOMATION_PATH: () => AUTOMATION_PATH,
|
|
5023
5371
|
automationConfigSchema: () => automationConfigSchema,
|
|
5024
|
-
configPath: () =>
|
|
5372
|
+
configPath: () => configPath2,
|
|
5025
5373
|
installAutomation: () => installAutomation,
|
|
5026
5374
|
installedAutomation: () => installedAutomation,
|
|
5027
5375
|
planAutomationInstall: () => planAutomationInstall,
|
|
5028
5376
|
recordSchema: () => recordSchema2
|
|
5029
5377
|
});
|
|
5030
|
-
import { z as
|
|
5378
|
+
import { z as z18 } from "zod";
|
|
5031
5379
|
async function installationRecord(root) {
|
|
5032
5380
|
return recordSchema2.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
|
|
5033
5381
|
}
|
|
@@ -5036,7 +5384,7 @@ async function installAutomation(dir, host, command) {
|
|
|
5036
5384
|
return withLock(ws.root, ".mason/reports/setup-lock", () => installLocked(ws.root, host, command));
|
|
5037
5385
|
}
|
|
5038
5386
|
async function planAutomationInstall(root, host, command) {
|
|
5039
|
-
const file =
|
|
5387
|
+
const file = configPath2(host);
|
|
5040
5388
|
const before = await readText(root, file);
|
|
5041
5389
|
const existing = automationConfigSchema.parse(JSON.parse(before ?? "{}"));
|
|
5042
5390
|
const record = await installationRecord(root);
|
|
@@ -5076,7 +5424,7 @@ async function installLocked(root, host, command) {
|
|
|
5076
5424
|
status: "configured",
|
|
5077
5425
|
command: newCommand,
|
|
5078
5426
|
events: HOOK_EVENTS,
|
|
5079
|
-
next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason
|
|
5427
|
+
next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason status reports observed events separately from configuration." : "Start a new Claude Code session. mason status reports observed events separately from configuration.",
|
|
5080
5428
|
note: "Install mason-context in the project before using the default command. Ignore .mason/reports/ to keep local evidence out of commits. Hooks preserve evidence and suggest scoped repairs; they do not approve edits or decisions."
|
|
5081
5429
|
};
|
|
5082
5430
|
}
|
|
@@ -5087,10 +5435,10 @@ async function installedAutomation(dir) {
|
|
|
5087
5435
|
for (const host of ["claude", "codex"]) {
|
|
5088
5436
|
const expected = record.hosts[host];
|
|
5089
5437
|
if (!expected) continue;
|
|
5090
|
-
const current = automationConfigSchema.parse(await readStoreJson(ws.root,
|
|
5438
|
+
const current = automationConfigSchema.parse(await readStoreJson(ws.root, configPath2(host)) ?? {});
|
|
5091
5439
|
const configuredEvents = HOOK_EVENTS.filter((event) => current.hooks?.[event]?.some((group) => group.hooks.some((handler) => handler.type === "command" && handler.command === expected.command)));
|
|
5092
5440
|
result[host] = {
|
|
5093
|
-
configPath:
|
|
5441
|
+
configPath: configPath2(host),
|
|
5094
5442
|
configuredEvents,
|
|
5095
5443
|
status: current.disableAllHooks === true ? "disabled" : configuredEvents.length === HOOK_EVENTS.length ? "configured" : "incomplete",
|
|
5096
5444
|
runtime: "Host version, trust, policy, and tool coverage still determine execution; inspect observed events."
|
|
@@ -5098,7 +5446,7 @@ async function installedAutomation(dir) {
|
|
|
5098
5446
|
}
|
|
5099
5447
|
return result;
|
|
5100
5448
|
}
|
|
5101
|
-
var groupSchema, automationConfigSchema, recordSchema2, AUTOMATION_PATH,
|
|
5449
|
+
var groupSchema, automationConfigSchema, recordSchema2, AUTOMATION_PATH, configPath2;
|
|
5102
5450
|
var init_install = __esm({
|
|
5103
5451
|
"src/automation/install.ts"() {
|
|
5104
5452
|
"use strict";
|
|
@@ -5108,11 +5456,11 @@ var init_install = __esm({
|
|
|
5108
5456
|
init_store();
|
|
5109
5457
|
init_files2();
|
|
5110
5458
|
init_ownership();
|
|
5111
|
-
groupSchema =
|
|
5112
|
-
automationConfigSchema =
|
|
5113
|
-
recordSchema2 =
|
|
5459
|
+
groupSchema = z18.object({ hooks: z18.array(z18.object({ type: z18.string(), command: z18.string().optional() }).passthrough()) }).passthrough();
|
|
5460
|
+
automationConfigSchema = z18.object({ hooks: z18.record(z18.array(groupSchema)).optional() }).passthrough();
|
|
5461
|
+
recordSchema2 = z18.object({ version: z18.literal(1), hosts: z18.record(z18.object({ command: z18.string() })) });
|
|
5114
5462
|
AUTOMATION_PATH = ".mason/local/automation.json";
|
|
5115
|
-
|
|
5463
|
+
configPath2 = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
|
|
5116
5464
|
}
|
|
5117
5465
|
});
|
|
5118
5466
|
|
|
@@ -5128,7 +5476,7 @@ __export(config_exports, {
|
|
|
5128
5476
|
});
|
|
5129
5477
|
import { isDeepStrictEqual } from "util";
|
|
5130
5478
|
import { parse as parse2, stringify } from "smol-toml";
|
|
5131
|
-
import { z as
|
|
5479
|
+
import { z as z19 } from "zod";
|
|
5132
5480
|
async function instructionEdits(root, host) {
|
|
5133
5481
|
const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
|
|
5134
5482
|
const existing = found.find((f) => f.text !== null);
|
|
@@ -5187,7 +5535,7 @@ async function mcpEdit(root, host) {
|
|
|
5187
5535
|
return { path: file, before, after };
|
|
5188
5536
|
}
|
|
5189
5537
|
async function hookEdits(root, host) {
|
|
5190
|
-
const file =
|
|
5538
|
+
const file = configPath2(host), before = await readText(root, file);
|
|
5191
5539
|
const { planAutomationInstall: planAutomationInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
5192
5540
|
const plan = await planAutomationInstall2(root, host, hookCommand(host));
|
|
5193
5541
|
return [
|
|
@@ -5215,7 +5563,7 @@ async function inspectHostConfig(root, host, plannedMcp) {
|
|
|
5215
5563
|
const text2 = plannedMcp ?? await readText(root, mcpPath(host));
|
|
5216
5564
|
const config = host === "codex" ? parse2(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
|
|
5217
5565
|
const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
|
|
5218
|
-
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root,
|
|
5566
|
+
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath2(host)) ?? "{}"));
|
|
5219
5567
|
const expected = hookConfig(host, hookCommand(host));
|
|
5220
5568
|
const compatible = compatibleManagedHookCommands(host);
|
|
5221
5569
|
const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
|
|
@@ -5239,7 +5587,7 @@ var init_config = __esm({
|
|
|
5239
5587
|
init_install();
|
|
5240
5588
|
init_files2();
|
|
5241
5589
|
init_launcher();
|
|
5242
|
-
object =
|
|
5590
|
+
object = z19.record(z19.unknown());
|
|
5243
5591
|
mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
5244
5592
|
TOML_START = "# mason:mcp:start";
|
|
5245
5593
|
TOML_END = "# mason:mcp:end";
|
|
@@ -5249,7 +5597,7 @@ var init_config = __esm({
|
|
|
5249
5597
|
});
|
|
5250
5598
|
|
|
5251
5599
|
// src/mcp/init.ts
|
|
5252
|
-
import { z as
|
|
5600
|
+
import { z as z20 } from "zod";
|
|
5253
5601
|
async function loadProjectMarker(rootDir) {
|
|
5254
5602
|
const raw = await readStoreJson(rootDir, ".mason/local/project.json");
|
|
5255
5603
|
if (raw === null) return null;
|
|
@@ -5257,8 +5605,8 @@ async function loadProjectMarker(rootDir) {
|
|
|
5257
5605
|
return { ...marker, features: await loadProjectFeatures(rootDir) };
|
|
5258
5606
|
}
|
|
5259
5607
|
async function loadProjectFeatures(rootDir) {
|
|
5260
|
-
const config =
|
|
5261
|
-
return
|
|
5608
|
+
const config = z20.record(z20.unknown()).parse(await readStoreJson(rootDir, ".mason/config.json") ?? {});
|
|
5609
|
+
return z20.object({ confluence: z20.boolean().optional() }).passthrough().parse(config.features ?? {});
|
|
5262
5610
|
}
|
|
5263
5611
|
async function saveProjectMarker(rootDir, marker) {
|
|
5264
5612
|
const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
@@ -5269,10 +5617,10 @@ async function projectMarkerEdits(rootDir, marker) {
|
|
|
5269
5617
|
const file = ".mason/local/project.json";
|
|
5270
5618
|
const edits = [{ path: file, before: await readText(rootDir, file), after: JSON.stringify(local, null, 2) + "\n" }];
|
|
5271
5619
|
if (features && Object.keys(features).length) {
|
|
5272
|
-
const
|
|
5273
|
-
const config =
|
|
5274
|
-
config.features = { ...
|
|
5275
|
-
edits.push({ path:
|
|
5620
|
+
const configPath3 = ".mason/config.json", before = await readText(rootDir, configPath3);
|
|
5621
|
+
const config = z20.record(z20.unknown()).parse(JSON.parse(before ?? "{}"));
|
|
5622
|
+
config.features = { ...z20.record(z20.unknown()).parse(config.features ?? {}), ...features };
|
|
5623
|
+
edits.push({ path: configPath3, before, after: JSON.stringify(config, null, 2) + "\n" });
|
|
5276
5624
|
}
|
|
5277
5625
|
return edits;
|
|
5278
5626
|
}
|
|
@@ -5285,10 +5633,10 @@ var init_init = __esm({
|
|
|
5285
5633
|
"use strict";
|
|
5286
5634
|
init_storage();
|
|
5287
5635
|
init_files2();
|
|
5288
|
-
markerSchema =
|
|
5289
|
-
version:
|
|
5290
|
-
initializedAt:
|
|
5291
|
-
features:
|
|
5636
|
+
markerSchema = z20.object({
|
|
5637
|
+
version: z20.literal(1),
|
|
5638
|
+
initializedAt: z20.string(),
|
|
5639
|
+
features: z20.object({ confluence: z20.boolean().optional() }).passthrough().optional()
|
|
5292
5640
|
}).passthrough();
|
|
5293
5641
|
CLAUDE_MD_SECTION = `<!-- mason:start -->
|
|
5294
5642
|
## Mason project knowledge
|
|
@@ -5297,9 +5645,10 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
|
|
|
5297
5645
|
|
|
5298
5646
|
- Task, bug, change request, or investigation of why the project works this way \u2192 \`get_context\` with the task text and known files: matching decisions, related tests, impact, and any available map entries.
|
|
5299
5647
|
- Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
|
|
5300
|
-
- Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\`
|
|
5648
|
+
- Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` as soon as the lesson is supported by evidence; do not wait for implementation, task completion, or merge. Record the established observation now and identify unimplemented remedies as tentative. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
|
|
5301
5649
|
- 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.
|
|
5302
5650
|
- 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.
|
|
5651
|
+
- 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.
|
|
5303
5652
|
- Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. 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.
|
|
5304
5653
|
- For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
|
|
5305
5654
|
- \`mason_init\` returns documentation audit and committed-diff review results, plus a short setup guide. Pass \`evidence\` with local CI manifest paths to include test and analysis results; the CLI equivalent is \`mason-review --evidence <manifest>\`. State skipped, unavailable, stale, or unknown checks explicitly. Related accepted decisions identify review context, not proven violations.
|
|
@@ -5309,13 +5658,13 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
|
|
|
5309
5658
|
|
|
5310
5659
|
- 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.
|
|
5311
5660
|
|
|
5312
|
-
- 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
|
|
5661
|
+
- 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.
|
|
5313
5662
|
|
|
5314
5663
|
Inspect source for what the retrieved context does not answer.
|
|
5315
5664
|
<!-- mason:end -->`;
|
|
5316
5665
|
ASSISTANT_SETUP = `UNIFIED SETUP
|
|
5317
|
-
When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. This uses the same engine as mason
|
|
5318
|
-
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
|
|
5666
|
+
When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. This uses the same engine as mason setup: retain the initial audit before instruction edits, 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.
|
|
5667
|
+
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.
|
|
5319
5668
|
|
|
5320
5669
|
The managed project guidance is:
|
|
5321
5670
|
${CLAUDE_MD_SECTION}`;
|
|
@@ -5323,7 +5672,7 @@ ${CLAUDE_MD_SECTION}`;
|
|
|
5323
5672
|
|
|
5324
5673
|
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.
|
|
5325
5674
|
2. Address findings within the user's requested scope. Setup alone authorizes Mason host configuration, hooks, instructions, and ignore rules; rewriting existing project claims requires repair scope. If repair is authorized, call \`mason_repair(dir, action: "prepare")\` before editing; it saves the full original findings even when this summary is truncated. Inspect relevant source, make grounded edits, then call \`mason_repair(dir, action: "verify", baselinePath)\` 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 setup has already dirtied a doc. Do not invent a decision just to populate the store.
|
|
5326
|
-
3. 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)\`.
|
|
5675
|
+
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)\`.
|
|
5327
5676
|
|
|
5328
5677
|
${ASSISTANT_SETUP}
|
|
5329
5678
|
|
|
@@ -5486,7 +5835,7 @@ var init_types2 = __esm({
|
|
|
5486
5835
|
});
|
|
5487
5836
|
|
|
5488
5837
|
// src/review/evidence/vitest.ts
|
|
5489
|
-
import { z as
|
|
5838
|
+
import { z as z21 } from "zod";
|
|
5490
5839
|
function parseVitest(raw, sourceRoot) {
|
|
5491
5840
|
const report = schema.parse(raw);
|
|
5492
5841
|
const findings = [], diagnostics = [];
|
|
@@ -5535,24 +5884,24 @@ var init_vitest = __esm({
|
|
|
5535
5884
|
"use strict";
|
|
5536
5885
|
init_paths2();
|
|
5537
5886
|
init_types2();
|
|
5538
|
-
count3 =
|
|
5539
|
-
schema =
|
|
5540
|
-
success:
|
|
5887
|
+
count3 = z21.number().int().nonnegative();
|
|
5888
|
+
schema = z21.object({
|
|
5889
|
+
success: z21.boolean(),
|
|
5541
5890
|
numTotalTests: count3,
|
|
5542
5891
|
numPassedTests: count3,
|
|
5543
5892
|
numFailedTests: count3,
|
|
5544
5893
|
numPendingTests: count3,
|
|
5545
5894
|
numTodoTests: count3,
|
|
5546
5895
|
numFailedTestSuites: count3,
|
|
5547
|
-
testResults:
|
|
5548
|
-
name:
|
|
5549
|
-
status:
|
|
5550
|
-
message:
|
|
5551
|
-
assertionResults:
|
|
5552
|
-
fullName:
|
|
5553
|
-
status:
|
|
5554
|
-
failureMessages:
|
|
5555
|
-
location:
|
|
5896
|
+
testResults: z21.array(z21.object({
|
|
5897
|
+
name: z21.string().min(1),
|
|
5898
|
+
status: z21.enum(["passed", "failed"]),
|
|
5899
|
+
message: z21.string().optional(),
|
|
5900
|
+
assertionResults: z21.array(z21.object({
|
|
5901
|
+
fullName: z21.string(),
|
|
5902
|
+
status: z21.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
|
|
5903
|
+
failureMessages: z21.array(z21.string()).nullable().optional(),
|
|
5904
|
+
location: z21.object({ line: count3, column: count3 }).nullable().optional()
|
|
5556
5905
|
}))
|
|
5557
5906
|
}))
|
|
5558
5907
|
});
|
|
@@ -5560,7 +5909,7 @@ var init_vitest = __esm({
|
|
|
5560
5909
|
});
|
|
5561
5910
|
|
|
5562
5911
|
// src/review/evidence/sarif.ts
|
|
5563
|
-
import { z as
|
|
5912
|
+
import { z as z22 } from "zod";
|
|
5564
5913
|
function parseSarif(raw, sourceRoot) {
|
|
5565
5914
|
const report = schema2.parse(raw), findings = [], diagnostics = [];
|
|
5566
5915
|
let active = 0, suppressed = 0, absent = 0, unresolved = 0;
|
|
@@ -5651,29 +6000,29 @@ var init_sarif = __esm({
|
|
|
5651
6000
|
"use strict";
|
|
5652
6001
|
init_paths2();
|
|
5653
6002
|
init_types2();
|
|
5654
|
-
index =
|
|
5655
|
-
artifact =
|
|
5656
|
-
message =
|
|
5657
|
-
level =
|
|
5658
|
-
location =
|
|
5659
|
-
schema2 =
|
|
5660
|
-
version:
|
|
5661
|
-
runs:
|
|
5662
|
-
tool:
|
|
5663
|
-
invocations:
|
|
5664
|
-
versionControlProvenance:
|
|
5665
|
-
originalUriBaseIds:
|
|
5666
|
-
artifacts:
|
|
5667
|
-
results:
|
|
5668
|
-
ruleId:
|
|
6003
|
+
index = z22.number().int().nonnegative();
|
|
6004
|
+
artifact = z22.object({ uri: z22.string().optional(), uriBaseId: z22.string().optional(), index: index.optional() });
|
|
6005
|
+
message = z22.object({ text: z22.string().optional(), markdown: z22.string().optional(), id: z22.string().optional(), arguments: z22.array(z22.string()).optional() });
|
|
6006
|
+
level = z22.enum(["error", "warning", "note", "none"]);
|
|
6007
|
+
location = z22.object({ physicalLocation: z22.object({ artifactLocation: artifact.optional(), region: z22.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
|
|
6008
|
+
schema2 = z22.object({
|
|
6009
|
+
version: z22.literal("2.1.0"),
|
|
6010
|
+
runs: z22.array(z22.object({
|
|
6011
|
+
tool: z22.object({ driver: z22.object({ name: z22.string().min(1), rules: z22.array(z22.object({ id: z22.string(), defaultConfiguration: z22.object({ level: level.optional() }).optional(), messageStrings: z22.record(message).optional() })).optional(), globalMessageStrings: z22.record(message).optional() }) }),
|
|
6012
|
+
invocations: z22.array(z22.object({ executionSuccessful: z22.boolean(), commandLine: z22.string().optional(), toolExecutionNotifications: z22.array(z22.object({ level: level.optional(), message })).optional() })).optional(),
|
|
6013
|
+
versionControlProvenance: z22.array(z22.object({ revisionId: z22.string().optional() })).optional(),
|
|
6014
|
+
originalUriBaseIds: z22.record(artifact).optional(),
|
|
6015
|
+
artifacts: z22.array(z22.object({ location: artifact.optional() })).optional(),
|
|
6016
|
+
results: z22.array(z22.object({
|
|
6017
|
+
ruleId: z22.string().optional(),
|
|
5669
6018
|
ruleIndex: index.optional(),
|
|
5670
6019
|
message,
|
|
5671
6020
|
level: level.optional(),
|
|
5672
|
-
kind:
|
|
5673
|
-
baselineState:
|
|
5674
|
-
suppressions:
|
|
5675
|
-
locations:
|
|
5676
|
-
relatedLocations:
|
|
6021
|
+
kind: z22.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
|
|
6022
|
+
baselineState: z22.enum(["new", "unchanged", "updated", "absent"]).optional(),
|
|
6023
|
+
suppressions: z22.array(z22.object({ kind: z22.enum(["inSource", "external"]), status: z22.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
|
|
6024
|
+
locations: z22.array(location).optional(),
|
|
6025
|
+
relatedLocations: z22.array(location).optional()
|
|
5677
6026
|
})).optional()
|
|
5678
6027
|
}))
|
|
5679
6028
|
});
|
|
@@ -5681,7 +6030,7 @@ var init_sarif = __esm({
|
|
|
5681
6030
|
});
|
|
5682
6031
|
|
|
5683
6032
|
// src/review/evidence/check.ts
|
|
5684
|
-
import { z as
|
|
6033
|
+
import { z as z23 } from "zod";
|
|
5685
6034
|
function parseCheck(raw, sourceRoot) {
|
|
5686
6035
|
const report = schema3.parse(raw);
|
|
5687
6036
|
const diagnostics = [], findings = [];
|
|
@@ -5721,17 +6070,17 @@ var init_check = __esm({
|
|
|
5721
6070
|
"use strict";
|
|
5722
6071
|
init_paths2();
|
|
5723
6072
|
init_types2();
|
|
5724
|
-
schema3 =
|
|
5725
|
-
version:
|
|
5726
|
-
commit:
|
|
5727
|
-
results:
|
|
5728
|
-
name:
|
|
5729
|
-
status:
|
|
5730
|
-
message:
|
|
5731
|
-
locations:
|
|
5732
|
-
file:
|
|
5733
|
-
line:
|
|
5734
|
-
column:
|
|
6073
|
+
schema3 = z23.object({
|
|
6074
|
+
version: z23.literal(1),
|
|
6075
|
+
commit: z23.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
|
|
6076
|
+
results: z23.array(z23.object({
|
|
6077
|
+
name: z23.string().min(1).max(1e3),
|
|
6078
|
+
status: z23.enum(["passed", "failed", "skipped"]),
|
|
6079
|
+
message: z23.string().max(1e5).optional(),
|
|
6080
|
+
locations: z23.array(z23.object({
|
|
6081
|
+
file: z23.string().min(1).max(4e3),
|
|
6082
|
+
line: z23.number().int().positive().optional(),
|
|
6083
|
+
column: z23.number().int().positive().optional()
|
|
5735
6084
|
})).max(100).default([])
|
|
5736
6085
|
})).max(1e5)
|
|
5737
6086
|
});
|
|
@@ -5740,7 +6089,7 @@ var init_check = __esm({
|
|
|
5740
6089
|
|
|
5741
6090
|
// src/review/evidence.ts
|
|
5742
6091
|
import path29 from "path";
|
|
5743
|
-
import { z as
|
|
6092
|
+
import { z as z24 } from "zod";
|
|
5744
6093
|
function relativeArtifact(root, file) {
|
|
5745
6094
|
const relative = path29.isAbsolute(file) ? path29.relative(root, file) : file;
|
|
5746
6095
|
const normalized = normalizeRepoPath(relative);
|
|
@@ -5781,7 +6130,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
5781
6130
|
let manifest, raw;
|
|
5782
6131
|
try {
|
|
5783
6132
|
manifest = relativeArtifact(root, supplied);
|
|
5784
|
-
raw =
|
|
6133
|
+
raw = z24.object({ version: z24.literal(1), checks: z24.array(z24.unknown()) }).parse(await readStoreJson(root, manifest));
|
|
5785
6134
|
} catch (error) {
|
|
5786
6135
|
output.diagnostics.push(`${supplied}: ${String(error)}`);
|
|
5787
6136
|
continue;
|
|
@@ -5936,31 +6285,31 @@ var init_evidence2 = __esm({
|
|
|
5936
6285
|
init_sarif();
|
|
5937
6286
|
init_check();
|
|
5938
6287
|
init_types2();
|
|
5939
|
-
checkSchema2 =
|
|
5940
|
-
id:
|
|
5941
|
-
kind:
|
|
5942
|
-
tool:
|
|
5943
|
-
command:
|
|
5944
|
-
commit:
|
|
5945
|
-
workingTreeClean:
|
|
5946
|
-
source:
|
|
5947
|
-
sourceRoot:
|
|
5948
|
-
status:
|
|
5949
|
-
reason:
|
|
5950
|
-
exitCode:
|
|
5951
|
-
environment:
|
|
5952
|
-
toolVersion:
|
|
5953
|
-
runtime:
|
|
5954
|
-
platform:
|
|
5955
|
-
configuration:
|
|
6288
|
+
checkSchema2 = z24.object({
|
|
6289
|
+
id: z24.string().min(1).max(100),
|
|
6290
|
+
kind: z24.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
|
|
6291
|
+
tool: z24.string().min(1).max(200),
|
|
6292
|
+
command: z24.string().min(1).max(2e3),
|
|
6293
|
+
commit: z24.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
|
|
6294
|
+
workingTreeClean: z24.boolean().optional(),
|
|
6295
|
+
source: z24.string().max(2e3).optional(),
|
|
6296
|
+
sourceRoot: z24.string().min(1).max(4e3).optional(),
|
|
6297
|
+
status: z24.enum(["completed", "skipped", "unavailable"]).default("completed"),
|
|
6298
|
+
reason: z24.string().min(1).max(2e3).optional(),
|
|
6299
|
+
exitCode: z24.number().int().nullable().optional(),
|
|
6300
|
+
environment: z24.object({
|
|
6301
|
+
toolVersion: z24.string().min(1).max(200),
|
|
6302
|
+
runtime: z24.string().min(1).max(200).optional(),
|
|
6303
|
+
platform: z24.string().min(1).max(200).optional(),
|
|
6304
|
+
configuration: z24.array(z24.string().min(1).max(4e3)).max(100).optional()
|
|
5956
6305
|
}).optional(),
|
|
5957
|
-
report:
|
|
6306
|
+
report: z24.object({ format: z24.enum(["vitest-json", "sarif", "mason-check-json"]), path: z24.string().min(1).max(4e3) }).optional()
|
|
5958
6307
|
});
|
|
5959
6308
|
}
|
|
5960
6309
|
});
|
|
5961
6310
|
|
|
5962
6311
|
// src/review/review.ts
|
|
5963
|
-
import
|
|
6312
|
+
import fs22 from "fs/promises";
|
|
5964
6313
|
import path30 from "path";
|
|
5965
6314
|
import { execFile as execFile7 } from "child_process";
|
|
5966
6315
|
import { promisify as promisify7 } from "util";
|
|
@@ -6040,7 +6389,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
6040
6389
|
analyzed,
|
|
6041
6390
|
async (relPath) => {
|
|
6042
6391
|
try {
|
|
6043
|
-
await
|
|
6392
|
+
await fs22.access(path30.join(resolvedRoot, relPath));
|
|
6044
6393
|
return true;
|
|
6045
6394
|
} catch {
|
|
6046
6395
|
return false;
|
|
@@ -6516,12 +6865,12 @@ __export(assemble_exports, {
|
|
|
6516
6865
|
assembleContext: () => assembleContext
|
|
6517
6866
|
});
|
|
6518
6867
|
import path33 from "path";
|
|
6519
|
-
import
|
|
6868
|
+
import fs23 from "fs/promises";
|
|
6520
6869
|
async function collectImpact(root, candidates) {
|
|
6521
6870
|
const targets2 = /* @__PURE__ */ new Set();
|
|
6522
6871
|
let sourceFiles;
|
|
6523
6872
|
for (const candidate of sanitizeRepoPaths(candidates)) {
|
|
6524
|
-
const stat = await
|
|
6873
|
+
const stat = await fs23.stat(path33.join(root, candidate)).catch(() => null);
|
|
6525
6874
|
if (stat?.isDirectory()) {
|
|
6526
6875
|
sourceFiles ??= await (await createFileAccess(root)).list();
|
|
6527
6876
|
for (const file of sourceFiles) {
|
|
@@ -6743,13 +7092,13 @@ __export(observations_exports, {
|
|
|
6743
7092
|
observeActivation: () => observeActivation,
|
|
6744
7093
|
readObservation: () => readObservation
|
|
6745
7094
|
});
|
|
6746
|
-
import
|
|
6747
|
-
import { z as
|
|
6748
|
-
function observationPath(
|
|
6749
|
-
return `${
|
|
7095
|
+
import fs24 from "fs/promises";
|
|
7096
|
+
import { z as z25 } from "zod";
|
|
7097
|
+
function observationPath(directory2, host, revision, version = executingVersion()) {
|
|
7098
|
+
return `${directory2}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
6750
7099
|
}
|
|
6751
|
-
async function readObservation(root,
|
|
6752
|
-
const raw = await readStoreJson(root, observationPath(
|
|
7100
|
+
async function readObservation(root, directory2, host, revision, version = executingVersion()) {
|
|
7101
|
+
const raw = await readStoreJson(root, observationPath(directory2, host, revision, version));
|
|
6753
7102
|
if (raw === null) return null;
|
|
6754
7103
|
const record = observationSchema.parse(raw);
|
|
6755
7104
|
if (record.root !== root || record.host !== host || record.revision !== revision || record.masonVersion !== version) throw new Error("Activation receipt belongs to another installation.");
|
|
@@ -6766,12 +7115,12 @@ async function observeActivation(dir, event, options = {}) {
|
|
|
6766
7115
|
return inactiveSetupMessage();
|
|
6767
7116
|
}
|
|
6768
7117
|
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];
|
|
6769
|
-
const ws = capturedDirectory ? { root: await
|
|
6770
|
-
if (ws.root !== await
|
|
7118
|
+
const ws = capturedDirectory ? { root: await fs24.realpath(dir), directory: capturedDirectory } : await workspace(dir);
|
|
7119
|
+
if (ws.root !== await fs24.realpath(process.env.MASON_SETUP_ROOT)) return null;
|
|
6771
7120
|
const { setup } = await effectiveSetup(ws.root);
|
|
6772
7121
|
if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
|
|
6773
|
-
const
|
|
6774
|
-
await withLock(ws.root,
|
|
7122
|
+
const directory2 = ws.directory + "/activation";
|
|
7123
|
+
await withLock(ws.root, directory2, async () => {
|
|
6775
7124
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6776
7125
|
const record = await readObservation(ws.root, ws.directory, host, revision) ?? {
|
|
6777
7126
|
version: 1,
|
|
@@ -6815,19 +7164,19 @@ var init_observations = __esm({
|
|
|
6815
7164
|
init_storage();
|
|
6816
7165
|
init_model();
|
|
6817
7166
|
init_launcher();
|
|
6818
|
-
observationSchema =
|
|
6819
|
-
version:
|
|
6820
|
-
masonVersion:
|
|
6821
|
-
root:
|
|
6822
|
-
revision:
|
|
6823
|
-
host:
|
|
6824
|
-
contextCalls:
|
|
6825
|
-
lastContextAt:
|
|
6826
|
-
sessions:
|
|
6827
|
-
events:
|
|
6828
|
-
at:
|
|
6829
|
-
verificationStatus:
|
|
6830
|
-
reportPath:
|
|
7167
|
+
observationSchema = z25.object({
|
|
7168
|
+
version: z25.literal(1),
|
|
7169
|
+
masonVersion: z25.string(),
|
|
7170
|
+
root: z25.string(),
|
|
7171
|
+
revision: z25.string(),
|
|
7172
|
+
host: z25.enum(["codex", "claude"]),
|
|
7173
|
+
contextCalls: z25.number().int().nonnegative(),
|
|
7174
|
+
lastContextAt: z25.string().optional(),
|
|
7175
|
+
sessions: z25.record(z25.object({
|
|
7176
|
+
events: z25.array(z25.enum(events)),
|
|
7177
|
+
at: z25.string(),
|
|
7178
|
+
verificationStatus: z25.string().optional(),
|
|
7179
|
+
reportPath: z25.string().optional()
|
|
6831
7180
|
}))
|
|
6832
7181
|
});
|
|
6833
7182
|
}
|
|
@@ -6854,7 +7203,7 @@ async function setupStatus(dir) {
|
|
|
6854
7203
|
version: 1,
|
|
6855
7204
|
status: pending.length ? "incomplete" : "not-configured",
|
|
6856
7205
|
hosts: {},
|
|
6857
|
-
next: pending.length ? "Setup did not finish. Rerun mason
|
|
7206
|
+
next: pending.length ? "Setup did not finish. Rerun mason setup --host " + pending[0] + " to resume using the retained original evidence." : "Project hooks are inactive until local setup. Run mason setup --host codex or --host claude."
|
|
6858
7207
|
};
|
|
6859
7208
|
}
|
|
6860
7209
|
const hosts = {};
|
|
@@ -6888,7 +7237,7 @@ async function setupStatus(dir) {
|
|
|
6888
7237
|
if (!mcp || !hooks || !instructionsCurrent || !configured) pending.push("Rerun setup to reconcile project configuration; inspect any disabled host settings.");
|
|
6889
7238
|
if (!observation?.contextCalls) pending.push("Start a new assistant session and request task context through Mason's get_context tool.");
|
|
6890
7239
|
if (!complete) pending.push("Review/trust the host configuration, then complete a normal task to observe the full hook lifecycle.");
|
|
6891
|
-
if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason
|
|
7240
|
+
if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason check and inspect its diagnostics.");
|
|
6892
7241
|
const healthy = installed.available && mcp && hooks && instructionsCurrent && configured && automation.status !== "unavailable";
|
|
6893
7242
|
hosts[host] = {
|
|
6894
7243
|
status: healthy ? complete && observation?.contextCalls ? "active" : "pending" : "attention",
|
|
@@ -7022,6 +7371,11 @@ async function setupProject(dir, options = {}) {
|
|
|
7022
7371
|
options.progress?.step("Checking configuration and retained findings");
|
|
7023
7372
|
const checked = await automate(ws.root, { event: "turn_start" });
|
|
7024
7373
|
const configured = await inspectHostConfig(ws.root, host, void 0);
|
|
7374
|
+
const stats = await configureUsefulness(ws.root, true, { onlyIfUnset: true }).catch(() => ({
|
|
7375
|
+
enabled: null,
|
|
7376
|
+
storage: ".mason/local/usefulness",
|
|
7377
|
+
note: "Local stats unavailable. Inspect mason stats --json, or use mason stats --disable to reset the preference. Existing observations were retained."
|
|
7378
|
+
}));
|
|
7025
7379
|
await writeStoreJson(ws.root, receiptPath, {
|
|
7026
7380
|
version: 1,
|
|
7027
7381
|
host,
|
|
@@ -7044,6 +7398,7 @@ async function setupProject(dir, options = {}) {
|
|
|
7044
7398
|
findings,
|
|
7045
7399
|
reportPath: checked.report.reportPath,
|
|
7046
7400
|
activation: await setupStatus(ws.root),
|
|
7401
|
+
stats,
|
|
7047
7402
|
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."
|
|
7048
7403
|
};
|
|
7049
7404
|
});
|
|
@@ -7055,18 +7410,20 @@ function summarizeSetup(result) {
|
|
|
7055
7410
|
" MCP server and lifecycle hooks use mason from PATH.",
|
|
7056
7411
|
" Project instructions updated; original audit evidence retained.",
|
|
7057
7412
|
` ${result.changedFiles.length} files changed.`,
|
|
7413
|
+
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.",
|
|
7058
7414
|
`Initial audit: ${audit.status}${"counts" in audit ? `; ${audit.counts.issues} issues, ${audit.counts.advisories + audit.counts.suppressedAdvisories} advisories` : ""}.`,
|
|
7059
7415
|
..."issues" in audit ? audit.issues.slice(0, 3).map((f) => " " + f.message) : [],
|
|
7060
7416
|
`Original evidence: ${result.initialReportPath}`,
|
|
7061
7417
|
"Activation: " + result.activation.status + ".",
|
|
7062
7418
|
result.next,
|
|
7063
7419
|
"If the assistant cannot find mason, restart the desktop app so it picks up PATH.",
|
|
7064
|
-
"After the task finishes, run mason
|
|
7420
|
+
"After the task finishes, run mason status. Configuration alone does not establish activation."
|
|
7065
7421
|
].join("\n");
|
|
7066
7422
|
}
|
|
7067
7423
|
var init_setup = __esm({
|
|
7068
7424
|
"src/setup/setup.ts"() {
|
|
7069
7425
|
"use strict";
|
|
7426
|
+
init_usefulness();
|
|
7070
7427
|
init_evidence();
|
|
7071
7428
|
init_store();
|
|
7072
7429
|
init_runtime();
|
|
@@ -7204,7 +7561,7 @@ __export(config_exports2, {
|
|
|
7204
7561
|
saveConfluenceConfig: () => saveConfluenceConfig,
|
|
7205
7562
|
validateProvider: () => validateProvider
|
|
7206
7563
|
});
|
|
7207
|
-
import
|
|
7564
|
+
import fs25 from "fs/promises";
|
|
7208
7565
|
import path34 from "path";
|
|
7209
7566
|
import os3 from "os";
|
|
7210
7567
|
import { execFile as execFile9 } from "child_process";
|
|
@@ -7217,15 +7574,15 @@ function configFile() {
|
|
|
7217
7574
|
}
|
|
7218
7575
|
async function loadConfig() {
|
|
7219
7576
|
try {
|
|
7220
|
-
const raw = await
|
|
7577
|
+
const raw = await fs25.readFile(configFile(), "utf-8");
|
|
7221
7578
|
return JSON.parse(raw);
|
|
7222
7579
|
} catch {
|
|
7223
7580
|
return null;
|
|
7224
7581
|
}
|
|
7225
7582
|
}
|
|
7226
7583
|
async function saveConfig(config) {
|
|
7227
|
-
await
|
|
7228
|
-
await
|
|
7584
|
+
await fs25.mkdir(configDir(), { recursive: true });
|
|
7585
|
+
await fs25.writeFile(configFile(), JSON.stringify(config, null, 2), "utf-8");
|
|
7229
7586
|
}
|
|
7230
7587
|
function getDefaultModel(provider) {
|
|
7231
7588
|
return DEFAULT_MODELS[provider];
|
|
@@ -7385,7 +7742,7 @@ var init_renderer = __esm({
|
|
|
7385
7742
|
});
|
|
7386
7743
|
|
|
7387
7744
|
// src/confluence/diff.ts
|
|
7388
|
-
import
|
|
7745
|
+
import fs26 from "fs/promises";
|
|
7389
7746
|
import path35 from "path";
|
|
7390
7747
|
import { createHash as createHash6 } from "crypto";
|
|
7391
7748
|
function hashDescription(description) {
|
|
@@ -7399,7 +7756,7 @@ function syncStatePath(rootDir) {
|
|
|
7399
7756
|
}
|
|
7400
7757
|
async function loadSyncState(rootDir) {
|
|
7401
7758
|
try {
|
|
7402
|
-
const raw = await
|
|
7759
|
+
const raw = await fs26.readFile(syncStatePath(rootDir), "utf-8");
|
|
7403
7760
|
const parsed = JSON.parse(raw);
|
|
7404
7761
|
if (parsed.version !== 2) return null;
|
|
7405
7762
|
return parsed;
|
|
@@ -7408,8 +7765,8 @@ async function loadSyncState(rootDir) {
|
|
|
7408
7765
|
}
|
|
7409
7766
|
}
|
|
7410
7767
|
async function saveSyncState(rootDir, state) {
|
|
7411
|
-
await
|
|
7412
|
-
await
|
|
7768
|
+
await fs26.mkdir(syncStateDir(rootDir), { recursive: true });
|
|
7769
|
+
await fs26.writeFile(
|
|
7413
7770
|
syncStatePath(rootDir),
|
|
7414
7771
|
JSON.stringify(state, null, 2),
|
|
7415
7772
|
"utf-8"
|
|
@@ -7962,12 +8319,12 @@ init_advisory_review();
|
|
|
7962
8319
|
init_decisions();
|
|
7963
8320
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7964
8321
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7965
|
-
import { z as
|
|
8322
|
+
import { z as z26 } from "zod";
|
|
7966
8323
|
|
|
7967
8324
|
// src/mcp/tools.ts
|
|
7968
8325
|
init_repair();
|
|
7969
8326
|
init_advisory_review();
|
|
7970
|
-
import
|
|
8327
|
+
import fs27 from "fs/promises";
|
|
7971
8328
|
import path36 from "path";
|
|
7972
8329
|
import { execFile as execFile11 } from "child_process";
|
|
7973
8330
|
import { promisify as promisify11 } from "util";
|
|
@@ -8591,14 +8948,14 @@ init_files();
|
|
|
8591
8948
|
import { createHash as createHash4 } from "crypto";
|
|
8592
8949
|
import { execFile as execFile5 } from "child_process";
|
|
8593
8950
|
import { promisify as promisify5 } from "util";
|
|
8594
|
-
import { z as
|
|
8951
|
+
import { z as z11 } from "zod";
|
|
8595
8952
|
var exec3 = promisify5(execFile5);
|
|
8596
|
-
var requestSchema =
|
|
8597
|
-
id:
|
|
8598
|
-
action:
|
|
8599
|
-
reviewer:
|
|
8600
|
-
note:
|
|
8601
|
-
reviewToken:
|
|
8953
|
+
var requestSchema = z11.object({
|
|
8954
|
+
id: z11.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
8955
|
+
action: z11.enum(["prepare", "accept", "reaffirm", "retire"]).default("prepare"),
|
|
8956
|
+
reviewer: z11.string().trim().min(1).max(200).optional(),
|
|
8957
|
+
note: z11.string().trim().min(1).max(1500).optional(),
|
|
8958
|
+
reviewToken: z11.string().regex(/^[a-f0-9]{64}$/).optional()
|
|
8602
8959
|
});
|
|
8603
8960
|
async function reviewState(root, record) {
|
|
8604
8961
|
const [headHash, workingTree, changes] = await Promise.all([
|
|
@@ -8825,16 +9182,16 @@ init_lock();
|
|
|
8825
9182
|
init_storage();
|
|
8826
9183
|
init_snapshot();
|
|
8827
9184
|
import fs17 from "fs/promises";
|
|
8828
|
-
import { z as
|
|
9185
|
+
import { z as z12 } from "zod";
|
|
8829
9186
|
var DIRECTORY = ".mason/partial-snapshots";
|
|
8830
|
-
var partialSchema =
|
|
8831
|
-
batchId:
|
|
8832
|
-
offset:
|
|
8833
|
-
features:
|
|
8834
|
-
flows:
|
|
8835
|
-
savedAt:
|
|
9187
|
+
var partialSchema = z12.object({
|
|
9188
|
+
batchId: z12.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
9189
|
+
offset: z12.number().int().nonnegative(),
|
|
9190
|
+
features: z12.record(featureSchema),
|
|
9191
|
+
flows: z12.record(flowSchema),
|
|
9192
|
+
savedAt: z12.string()
|
|
8836
9193
|
});
|
|
8837
|
-
var scopeSchema2 =
|
|
9194
|
+
var scopeSchema2 = z12.object({ files: z12.array(z12.string()), savedAt: z12.string() });
|
|
8838
9195
|
async function savePartial(rootDir, partial) {
|
|
8839
9196
|
if (!/^[a-zA-Z0-9_-]+$/.test(partial.batchId)) throw new Error(`Invalid batchId: ${partial.batchId}`);
|
|
8840
9197
|
await withSnapshotWrite(rootDir, () => writeStoreJson(rootDir, `${DIRECTORY}/${partial.batchId}.json`, partialSchema.parse(partial)));
|
|
@@ -8941,7 +9298,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
8941
9298
|
const present = [];
|
|
8942
9299
|
for (const file of buildFiles) {
|
|
8943
9300
|
try {
|
|
8944
|
-
await
|
|
9301
|
+
await fs27.access(path36.join(rootDir, file));
|
|
8945
9302
|
present.push(file);
|
|
8946
9303
|
} catch {
|
|
8947
9304
|
}
|
|
@@ -9833,21 +10190,21 @@ function createMcpServer() {
|
|
|
9833
10190
|
const server = new McpServer(
|
|
9834
10191
|
{
|
|
9835
10192
|
name: "mason",
|
|
9836
|
-
version: "0.
|
|
10193
|
+
version: "0.20.1"
|
|
9837
10194
|
},
|
|
9838
10195
|
{
|
|
9839
|
-
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, stale or unknown map verification needs a fresh review, recorded failed verdicts need 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.'
|
|
10196
|
+
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 as soon as evidence supports them, without waiting for implementation or merge; 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, stale or unknown map verification needs a fresh review, recorded failed verdicts need 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.'
|
|
9840
10197
|
}
|
|
9841
10198
|
);
|
|
9842
10199
|
server.tool(
|
|
9843
10200
|
"mason_init",
|
|
9844
10201
|
"Inspect this project now: returns documentation audit findings, committed-diff review findings, decision/map status, and a quickstart playbook. Quickstart and map modes are read-only and deterministic. Explicit mode: setup configures MCP, instructions and lifecycle hooks to use mason on PATH while retaining original audit evidence; use it only when the user requests setup. Optional host selects codex or claude. Optional base selects the review comparison; evidence imports CI manifests with check outcomes, commit freshness, and links to changed files and accepted decisions. mode: map returns the full Map-Reduce build workflow. Repeat calls refresh findings even after setup.",
|
|
9845
10202
|
{
|
|
9846
|
-
dir:
|
|
9847
|
-
mode:
|
|
9848
|
-
host:
|
|
9849
|
-
base:
|
|
9850
|
-
evidence:
|
|
10203
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10204
|
+
mode: z26.enum(["quickstart", "map", "setup"]).optional().default("quickstart").describe("Quickstart inspects without edits; map requests an architecture build; setup installs the shared onboarding flow."),
|
|
10205
|
+
host: z26.enum(["codex", "claude"]).optional().describe("Assistant to configure in setup mode; inferred only when unambiguous."),
|
|
10206
|
+
base: z26.string().optional().describe("Git ref for committed-diff review. Defaults to the first available main branch ref."),
|
|
10207
|
+
evidence: z26.array(z26.string()).max(10).optional().describe("Repository-local CI evidence manifests to include in the review. Imports Vitest JSON, SARIF, and native-validator mason-check-json results without executing check commands.")
|
|
9851
10208
|
},
|
|
9852
10209
|
async ({ dir, mode, host, base, evidence }) => {
|
|
9853
10210
|
const result = await masonInit(dir, { mode, host, base, evidence });
|
|
@@ -9856,10 +10213,10 @@ function createMcpServer() {
|
|
|
9856
10213
|
);
|
|
9857
10214
|
server.tool(
|
|
9858
10215
|
"mason_automation",
|
|
9859
|
-
"Inspect installed automation
|
|
10216
|
+
"Inspect installed automation, observed host events, pending decision records and the notification baseline, or check all retained documentation findings across sessions. status is read-only; check saves local baselines and verification reports without editing source, approving advisories or consuming pending completion notices. Returns concise results with a full report path. Works without a map.",
|
|
9860
10217
|
{
|
|
9861
|
-
dir:
|
|
9862
|
-
action:
|
|
10218
|
+
dir: z26.string().describe("Absolute path to the project directory"),
|
|
10219
|
+
action: z26.enum(["status", "check"]).describe("Inspect configuration and receipts, or capture/resume and verify original audit evidence.")
|
|
9863
10220
|
},
|
|
9864
10221
|
async ({ dir, action }) => ({ content: [{ type: "text", text: await masonAutomation(dir, action) }] })
|
|
9865
10222
|
);
|
|
@@ -9867,10 +10224,10 @@ function createMcpServer() {
|
|
|
9867
10224
|
"mason_repair",
|
|
9868
10225
|
"Track documentation repairs against original audit evidence. prepare saves a local baseline and returns a scoped work order; verify reads that baseline and reports resolved, unresolved, review-required, unverified, and new findings. Suppressed advisories remain unresolved. Does not edit documentation or approve decisions. No map required.",
|
|
9869
10226
|
{
|
|
9870
|
-
dir:
|
|
9871
|
-
action:
|
|
9872
|
-
baselinePath:
|
|
9873
|
-
checks:
|
|
10227
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10228
|
+
action: z26.enum(["prepare", "verify"]),
|
|
10229
|
+
baselinePath: z26.string().optional().describe("Original baseline path returned by prepare; required for verify."),
|
|
10230
|
+
checks: z26.array(z26.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"])).min(1).optional().describe("Optional audit check subset for prepare. Verification always uses the original checks.")
|
|
9874
10231
|
},
|
|
9875
10232
|
async ({ dir, action, baselinePath, checks }) => {
|
|
9876
10233
|
const result = await masonRepair(dir, { action, baselinePath, checks });
|
|
@@ -9880,15 +10237,15 @@ function createMcpServer() {
|
|
|
9880
10237
|
server.tool(
|
|
9881
10238
|
"review_advisory",
|
|
9882
10239
|
"Prepare an assessment of an original repair advisory and its exact repository scope, then record authorized addressed, inapplicable or deferred outcomes with reviewToken, reviewer and note. Relevant changes reopen reviews; unrelated commits preserve them. Records belong in Git. Decision findings route to review_decision; this tool never approves decisions. Recorded identities are assertions, not authenticated approvals. For dependency advisories, action dismiss with reasonCode and note records a one-step dismissal without a token or reviewer. no-dependency-claims reopens only on document content changes; unrelated-manifest-change also tracks scoped manifests.",
|
|
9883
|
-
{ dir:
|
|
10240
|
+
{ dir: z26.string().describe("Absolute path to the project root"), ...advisoryReviewRequest.shape },
|
|
9884
10241
|
async ({ dir, ...request }) => ({ content: [{ type: "text", text: await masonReviewAdvisory(dir, request) }] })
|
|
9885
10242
|
);
|
|
9886
10243
|
server.tool(
|
|
9887
10244
|
"mason_complete_init",
|
|
9888
10245
|
"Record assistant instruction setup locally in ignored .mason/local/project.json, with feature settings in shared .mason/config.json. Other tools work without this marker. Repeated calls preserve the original setup time and existing settings; pass confluenceConfigured only to change that setting.",
|
|
9889
10246
|
{
|
|
9890
|
-
dir:
|
|
9891
|
-
confluenceConfigured:
|
|
10247
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10248
|
+
confluenceConfigured: z26.boolean().optional().describe("Set the Confluence setup status; omit to preserve the existing value")
|
|
9892
10249
|
},
|
|
9893
10250
|
async ({ dir, confluenceConfigured }) => {
|
|
9894
10251
|
const result = await masonCompleteInit(dir, { confluenceConfigured });
|
|
@@ -9899,11 +10256,11 @@ function createMcpServer() {
|
|
|
9899
10256
|
"mason_set_confluence",
|
|
9900
10257
|
"Configure Confluence credentials. Two-step flow: (1) call without `spaceKey` to validate the credentials and receive a list of available spaces \u2014 relay them to the user. (2) call again with the same `baseUrl`/`email`/`apiToken` plus the chosen `spaceKey` to persist. Credentials are stored in `~/.mason/config.json`. Warn the user that the API token will be visible in chat history before they paste it.",
|
|
9901
10258
|
{
|
|
9902
|
-
baseUrl:
|
|
9903
|
-
email:
|
|
9904
|
-
apiToken:
|
|
9905
|
-
spaceKey:
|
|
9906
|
-
parentPageId:
|
|
10259
|
+
baseUrl: z26.string().describe("Confluence base URL. Accepts `acme`, `acme.atlassian.net`, or `https://acme.atlassian.net` (normalized automatically)."),
|
|
10260
|
+
email: z26.string().describe("User's Atlassian account email"),
|
|
10261
|
+
apiToken: z26.string().describe("API token from id.atlassian.com/manage-profile/security/api-tokens"),
|
|
10262
|
+
spaceKey: z26.string().optional().describe("Confluence space key. Omit on the first call to list available spaces."),
|
|
10263
|
+
parentPageId: z26.string().optional().describe("Optional parent page ID under which Mason's index page is created")
|
|
9907
10264
|
},
|
|
9908
10265
|
async ({ baseUrl, email, apiToken, spaceKey, parentPageId }) => {
|
|
9909
10266
|
const result = await masonSetConfluence({
|
|
@@ -9920,7 +10277,7 @@ function createMcpServer() {
|
|
|
9920
10277
|
"full_analysis",
|
|
9921
10278
|
"One-shot orientation for a project WITHOUT a concept map (get_snapshot returned exists:false). Returns git history stats, project structure with file counts, curated code sample previews (~60 lines each), and test-to-source mapping. On a mapped project, prefer get_snapshot \u2014 it is cheaper and answers feature/architecture questions directly.",
|
|
9922
10279
|
{
|
|
9923
|
-
dir:
|
|
10280
|
+
dir: z26.string().describe("Absolute path to the project root directory")
|
|
9924
10281
|
},
|
|
9925
10282
|
async ({ dir }) => {
|
|
9926
10283
|
const result = await fullAnalysis(dir);
|
|
@@ -9933,7 +10290,7 @@ function createMcpServer() {
|
|
|
9933
10290
|
"analyze_project",
|
|
9934
10291
|
"Run git history analysis on a codebase. Returns commit convention patterns, stale directories, and frequently changed files. These are aggregate stats across hundreds of commits that would be expensive to compute manually.",
|
|
9935
10292
|
{
|
|
9936
|
-
dir:
|
|
10293
|
+
dir: z26.string().describe("Absolute path to the project root directory")
|
|
9937
10294
|
},
|
|
9938
10295
|
async ({ dir }) => {
|
|
9939
10296
|
const result = await analyzeProject(dir);
|
|
@@ -9946,8 +10303,8 @@ function createMcpServer() {
|
|
|
9946
10303
|
"get_code_samples",
|
|
9947
10304
|
"Get previews (first ~60 lines) of representative source files from the codebase. Includes entry points, config files, hot files (frequently changed), test examples, and one file per directory for breadth. Read files natively for full content.",
|
|
9948
10305
|
{
|
|
9949
|
-
dir:
|
|
9950
|
-
count:
|
|
10306
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10307
|
+
count: z26.number().optional().default(15).describe("Maximum number of files to sample (default: 15)")
|
|
9951
10308
|
},
|
|
9952
10309
|
async ({ dir, count: count4 }) => {
|
|
9953
10310
|
const result = await getCodeSamples(dir, count4);
|
|
@@ -9960,7 +10317,7 @@ function createMcpServer() {
|
|
|
9960
10317
|
"get_snapshot",
|
|
9961
10318
|
"Return the optional feature-to-file architecture map with drift and trust evidence. Map verification is rechecked against sampled source contents; stale or unknown verification requires a fresh review, and recordedVerdict preserves the historical verdict. If no map exists, returns exists:false plus project structure, Git signals, and test pairs. Decision capture, get_context, and get_impact still work. No initialization required.",
|
|
9962
10319
|
{
|
|
9963
|
-
dir:
|
|
10320
|
+
dir: z26.string().describe("Absolute path to the project root directory")
|
|
9964
10321
|
},
|
|
9965
10322
|
async ({ dir }) => {
|
|
9966
10323
|
const result = await getSnapshot(dir);
|
|
@@ -9973,9 +10330,9 @@ function createMcpServer() {
|
|
|
9973
10330
|
"get_context",
|
|
9974
10331
|
"Assemble task context: matching decisions with rationale, approval, owner, sources, last review, and freshness, plus related tests, file impact, and any available map entries. Map verification is rechecked against sampled source contents; stale or unknown verification requires a fresh review, and recordedVerdict preserves the historical verdict. Proposals are suggestions; legacy records are unreviewed; accepted decisions are constraints subject to freshness. No initialization or map required. Pass task and optional files. map.status and diagnostics preserve missing or invalid knowledge. Impact covers up to three unique files, expanding directory anchors.",
|
|
9975
10332
|
{
|
|
9976
|
-
dir:
|
|
9977
|
-
task:
|
|
9978
|
-
files:
|
|
10333
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10334
|
+
task: z26.string().describe("The task, bug, or change request in natural language \u2014 e.g. 'add rate limiting to the API client' or a ticket description"),
|
|
10335
|
+
files: z26.array(z26.string()).optional().describe("Optional file paths already known to be involved (e.g. from a diff or stack trace). Entries containing them are boosted above pure text matches.")
|
|
9979
10336
|
},
|
|
9980
10337
|
async ({ dir, task, files }) => {
|
|
9981
10338
|
const result = await getContext(dir, task, files);
|
|
@@ -9991,10 +10348,10 @@ function createMcpServer() {
|
|
|
9991
10348
|
"generate_snapshot_batch",
|
|
9992
10349
|
"Map step of the concept-map build. Returns one batch of source files (skeletons of every file in the batch plus a few deeper-read bodies for grounding), along with a system prompt instructing you to derive features and flows for ONLY this batch. Call repeatedly with the returned `nextOffset` until it is null, calling `save_partial_snapshot` between each call. Use product-natural feature names so partials merge cleanly in the reduce step.",
|
|
9993
10350
|
{
|
|
9994
|
-
dir:
|
|
9995
|
-
offset:
|
|
9996
|
-
batchSize:
|
|
9997
|
-
files:
|
|
10351
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10352
|
+
offset: z26.number().int().optional().describe("0-indexed file offset to start the batch at. Omit on the first call; pass the `nextOffset` from the previous response for subsequent calls."),
|
|
10353
|
+
batchSize: z26.number().int().optional().describe("Files per batch. Defaults to 50."),
|
|
10354
|
+
files: z26.array(z26.string()).optional().describe("Scope the batch walk to this explicit file list \u2014 e.g. the drift set from mason_check_drift (changedFiles + unmappedFiles). Pass the SAME list on every batch call of one refresh run. Triggers refresh mode: reduce_snapshot will merge the partials into the existing map instead of rebuilding it.")
|
|
9998
10355
|
},
|
|
9999
10356
|
async ({ dir, offset, batchSize, files }) => {
|
|
10000
10357
|
const result = await generateSnapshotBatch(dir, offset, batchSize, files);
|
|
@@ -10007,23 +10364,23 @@ function createMcpServer() {
|
|
|
10007
10364
|
"save_partial_snapshot",
|
|
10008
10365
|
"Persist the partial concept map you derived for one batch. Call this once per batch, with the `batchId` from the `generate_snapshot_batch` response. Partials accumulate in `.mason/partial-snapshots/` and are merged in the reduce step.",
|
|
10009
10366
|
{
|
|
10010
|
-
dir:
|
|
10011
|
-
batchId:
|
|
10012
|
-
offset:
|
|
10013
|
-
features:
|
|
10014
|
-
|
|
10015
|
-
description:
|
|
10016
|
-
files:
|
|
10017
|
-
tests:
|
|
10018
|
-
type:
|
|
10367
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10368
|
+
batchId: z26.string().describe("The `batchId` returned by `generate_snapshot_batch`."),
|
|
10369
|
+
offset: z26.number().int().describe("The `offset` returned by `generate_snapshot_batch`. Used to order partials in the reduce step."),
|
|
10370
|
+
features: z26.record(
|
|
10371
|
+
z26.object({
|
|
10372
|
+
description: z26.string(),
|
|
10373
|
+
files: z26.array(z26.string()),
|
|
10374
|
+
tests: z26.array(z26.string()).optional(),
|
|
10375
|
+
type: z26.enum(["capability", "infrastructure"]).optional().describe(
|
|
10019
10376
|
'"capability" (user-facing functionality) or "infrastructure" (internal plumbing with no end user \u2014 DI/service wiring, config, logging, adapters). Defaults to "capability".'
|
|
10020
10377
|
)
|
|
10021
10378
|
})
|
|
10022
10379
|
).describe("Partial features for this batch only \u2014 files outside the batch will be added by other partials."),
|
|
10023
|
-
flows:
|
|
10024
|
-
|
|
10025
|
-
description:
|
|
10026
|
-
chain:
|
|
10380
|
+
flows: z26.record(
|
|
10381
|
+
z26.object({
|
|
10382
|
+
description: z26.string(),
|
|
10383
|
+
chain: z26.array(z26.string())
|
|
10027
10384
|
})
|
|
10028
10385
|
).describe("Partial flows whose entire chain is in this batch. Cross-batch flows are reconstructed in reduce.")
|
|
10029
10386
|
},
|
|
@@ -10038,7 +10395,7 @@ function createMcpServer() {
|
|
|
10038
10395
|
"reduce_snapshot",
|
|
10039
10396
|
"Reduce step of the concept-map build. Returns every partial snapshot plus a system prompt asking you to merge them into one coherent project-wide map. Resolve platform variants into single product features, dedupe near-duplicates, and ensure no file is dropped. After producing the unified map, call `save_snapshot` to persist it (this also clears the partials).",
|
|
10040
10397
|
{
|
|
10041
|
-
dir:
|
|
10398
|
+
dir: z26.string().describe("Absolute path to the project root directory")
|
|
10042
10399
|
},
|
|
10043
10400
|
async ({ dir }) => {
|
|
10044
10401
|
const result = await reduceSnapshot(dir);
|
|
@@ -10051,25 +10408,25 @@ function createMcpServer() {
|
|
|
10051
10408
|
"save_snapshot",
|
|
10052
10409
|
"Save a concept-to-files map as a persistent project snapshot. Maps feature names and data flows to the files that implement them. Persists across conversations \u2014 future sessions can call get_snapshot to instantly find relevant files. No API key needed \u2014 you are the LLM generating the map.",
|
|
10053
10410
|
{
|
|
10054
|
-
dir:
|
|
10055
|
-
features:
|
|
10056
|
-
|
|
10057
|
-
description:
|
|
10058
|
-
files:
|
|
10059
|
-
tests:
|
|
10060
|
-
type:
|
|
10411
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10412
|
+
features: z26.record(
|
|
10413
|
+
z26.object({
|
|
10414
|
+
description: z26.string().describe("One-line description of the feature"),
|
|
10415
|
+
files: z26.array(z26.string()).describe("File paths that implement this feature"),
|
|
10416
|
+
tests: z26.array(z26.string()).optional().describe("Test file paths for this feature"),
|
|
10417
|
+
type: z26.enum(["capability", "infrastructure"]).optional().describe(
|
|
10061
10418
|
'Classification: "capability" for user-facing functionality, "infrastructure" for internal plumbing with no end user (DI/service wiring, config, logging, adapters). Capabilities are published to Confluence; infrastructure stays in the AI concept map only. Defaults to "capability".'
|
|
10062
10419
|
)
|
|
10063
10420
|
})
|
|
10064
10421
|
).describe("Map of feature names to their implementing files"),
|
|
10065
|
-
flows:
|
|
10066
|
-
|
|
10067
|
-
description:
|
|
10068
|
-
chain:
|
|
10422
|
+
flows: z26.record(
|
|
10423
|
+
z26.object({
|
|
10424
|
+
description: z26.string().describe("One-line description of the flow"),
|
|
10425
|
+
chain: z26.array(z26.string()).describe("Ordered list of file paths showing data/call flow")
|
|
10069
10426
|
})
|
|
10070
10427
|
).describe("Map of flow names to ordered file chains"),
|
|
10071
|
-
removeFeatures:
|
|
10072
|
-
removeFlows:
|
|
10428
|
+
removeFeatures: z26.array(z26.string()).optional().describe("Feature names to delete from the existing map \u2014 for features that were renamed or no longer exist. Applied before merging; only meaningful on incremental saves."),
|
|
10429
|
+
removeFlows: z26.array(z26.string()).optional().describe("Flow names to delete from the existing map. Applied before merging; only meaningful on incremental saves.")
|
|
10073
10430
|
},
|
|
10074
10431
|
async ({ dir, features, flows, removeFeatures, removeFlows }) => {
|
|
10075
10432
|
const result = await saveSnapshotData(
|
|
@@ -10086,19 +10443,19 @@ function createMcpServer() {
|
|
|
10086
10443
|
);
|
|
10087
10444
|
server.tool(
|
|
10088
10445
|
"save_decision",
|
|
10089
|
-
"Capture or revise a decision proposal
|
|
10446
|
+
"Capture or revise a decision proposal as soon as the lesson is supported by evidence, without waiting for implementation or merge. Keep unimplemented remedies tentative. Include rationale, anchors, optional owner, sources, and a known actor. Compare matching get_context records and pending proposals before saving. When new evidence changes the same lesson's assumptions, scope, or recommended action, pass its existing id, including for accepted records. Preserve supported rationale and sources and replace obsolete instructions. Create a new record for a genuinely distinct lesson; skip unchanged restatements. No setup or map required. Writes a local record and preserves content history. Changes create a pending proposal while the last accepted revision remains operative; unchanged content does not re-verify or refresh it. Use review_decision for authorized acceptance or reaffirmation. A proposal cannot supersede a record with an operative accepted revision; review its replacement and retire the original separately.",
|
|
10090
10447
|
{
|
|
10091
|
-
dir:
|
|
10448
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10092
10449
|
title: decisionTitleSchema.describe(`Short, specific headline (max ${TITLE_MAX_CHARS} characters). Generates a stable id on creation; revising the title keeps the existing id.`),
|
|
10093
10450
|
body: decisionBodySchema.describe(`The knowledge itself: what was tried/decided, why, and what to avoid. Must contain information NOT derivable by reading the code. Aim for ${BODY_RECOMMENDED_CHARS} characters or fewer; up to ${BODY_MAX_CHARS} is accepted with a warning above the target. Matching context and hooks include the full body. Preserve exceptions; use sources for supporting references.`),
|
|
10094
|
-
category:
|
|
10095
|
-
files:
|
|
10096
|
-
id:
|
|
10097
|
-
supersedes:
|
|
10451
|
+
category: z26.enum(["decision", "gotcha", "deprecation", "convention"]),
|
|
10452
|
+
files: z26.array(z26.string()).optional().describe("Repo-relative files or directory prefixes this applies to. Matching is shared by retrieval, hooks, review, and drift checking; changes flag the decision for re-verification."),
|
|
10453
|
+
id: z26.string().optional().describe("Pass the matching record's existing id when evidence changes that lesson's assumptions, scope, or recommended action, including for accepted records. Changed content becomes a proposal while the accepted version remains operative; unchanged content leaves review and freshness untouched."),
|
|
10454
|
+
supersedes: z26.string().optional().describe("Id of an unreviewed record or proposal with no accepted revision to replace. Operative accepted decisions require separate review and retirement."),
|
|
10098
10455
|
owner: attributionSchema.shape.owner.describe("Responsible person or team, when known. Null clears it. Required for acceptance."),
|
|
10099
10456
|
sources: attributionSchema.shape.sources.describe("Known PR, issue, incident, discussion, or document references. Omit to preserve; [] clears. At least one is required for acceptance."),
|
|
10100
10457
|
actor: attributionSchema.shape.actor.describe("Known person or agent recording this revision. Omit if unknown; do not infer from Git identity."),
|
|
10101
|
-
force:
|
|
10458
|
+
force: z26.boolean().optional().describe("Save even when a near-duplicate was detected")
|
|
10102
10459
|
},
|
|
10103
10460
|
async ({ dir, title, body, category, files, id, supersedes, force, owner, sources, actor }) => {
|
|
10104
10461
|
const result = await saveDecision(dir, {
|
|
@@ -10120,12 +10477,12 @@ function createMcpServer() {
|
|
|
10120
10477
|
"review_decision",
|
|
10121
10478
|
"Prepare a decision review: returns the full record and history, any operative accepted revision, provenance, changes and previews for both sets of anchors, and a reviewToken. Then record accept, reaffirm, or retire with that token, the authorized reviewer, and a reason. Acceptance replaces the operative revision; retirement withdraws the entire record including its proposal. Acceptance requires owner, source, readable Git HEAD, and committed anchor changes. Changed records or code invalidate the token. Identities and approvals are recorded assertions for normal PR review, not authenticated proof.",
|
|
10122
10479
|
{
|
|
10123
|
-
dir:
|
|
10124
|
-
id:
|
|
10125
|
-
action:
|
|
10126
|
-
reviewer:
|
|
10127
|
-
note:
|
|
10128
|
-
reviewToken:
|
|
10480
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10481
|
+
id: z26.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Decision id from get_context or save_decision"),
|
|
10482
|
+
action: z26.enum(["prepare", "accept", "reaffirm", "retire"]).optional().default("prepare").describe("Prepare is read-only. Other actions record an explicitly authorized review."),
|
|
10483
|
+
reviewer: z26.string().trim().min(1).max(200).optional().describe("Identity of the actual reviewer; required for a verdict. Never invent one."),
|
|
10484
|
+
note: z26.string().trim().min(1).max(1500).optional().describe("Review rationale; required for a verdict. Cite evidence for the decision."),
|
|
10485
|
+
reviewToken: z26.string().regex(/^[a-f0-9]{64}$/).optional().describe("Token from the prepared review; rejects stale record or code revisions")
|
|
10129
10486
|
},
|
|
10130
10487
|
async ({ dir, ...input }) => {
|
|
10131
10488
|
const result = await reviewDecision2(dir, input);
|
|
@@ -10136,7 +10493,7 @@ function createMcpServer() {
|
|
|
10136
10493
|
"mason_check_drift",
|
|
10137
10494
|
"Check how far the concept map has drifted from HEAD. Deterministic (git + filesystem, no LLM). Returns which features/flows are stale and the changed files behind them, new source files not yet mapped, ghost files (mapped but deleted), renames, and a `recommendation`: `up-to-date` (nothing to do), `incremental` (update just the stale entries via save_snapshot), or `full-rebuild` (re-run the Map-Reduce build). Call this before trusting the map in a long session, or periodically to keep the map and any synced wikis fresh.",
|
|
10138
10495
|
{
|
|
10139
|
-
dir:
|
|
10496
|
+
dir: z26.string().describe("Absolute path to the project root directory")
|
|
10140
10497
|
},
|
|
10141
10498
|
async ({ dir }) => {
|
|
10142
10499
|
const result = await checkDrift(dir);
|
|
@@ -10149,8 +10506,8 @@ function createMcpServer() {
|
|
|
10149
10506
|
"verify_snapshot",
|
|
10150
10507
|
"Spot-check the concept map's CORRECTNESS (drift checks freshness; this checks entries were right to begin with). Returns a sample of entries \u2014 always the never-verified and least-recently-verified first \u2014 with skeletons of their claimed files, for you to judge whether the files actually implement what the entry claims. Report verdicts back via save_verification with each entry\u2019s kind and reviewToken. Tokens bind the entry and bounded source evidence; changed evidence requires a fresh review. Run periodically, or after an automated refresh wrote entries no human reviewed.",
|
|
10151
10508
|
{
|
|
10152
|
-
dir:
|
|
10153
|
-
sample:
|
|
10509
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10510
|
+
sample: z26.number().int().optional().describe("Entries to sample (default 5)")
|
|
10154
10511
|
},
|
|
10155
10512
|
async ({ dir, sample }) => {
|
|
10156
10513
|
const result = await verifySnapshot(dir, sample);
|
|
@@ -10161,13 +10518,13 @@ function createMcpServer() {
|
|
|
10161
10518
|
"save_verification",
|
|
10162
10519
|
"Record verify_snapshot verdicts using each entry\u2019s kind and reviewToken. Missing tokens request a fresh review; changed or deleted entries return conflicts without being stamped. Entries judged ok are stamped verifiedAt; failures are flagged verificationFailed with your note and surface in get_context, get_snapshot, and mason_check_drift until corrected. Verdict notes are required for failures.",
|
|
10163
10520
|
{
|
|
10164
|
-
dir:
|
|
10165
|
-
verdicts:
|
|
10166
|
-
|
|
10167
|
-
kind:
|
|
10168
|
-
reviewToken:
|
|
10169
|
-
ok:
|
|
10170
|
-
note:
|
|
10521
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10522
|
+
verdicts: z26.record(
|
|
10523
|
+
z26.object({
|
|
10524
|
+
kind: z26.enum(["feature", "flow"]).optional().describe("Entry kind returned by verify_snapshot; required to record a verdict"),
|
|
10525
|
+
reviewToken: z26.string().optional().describe("Copy the reviewToken from verify_snapshot after inspecting its evidence; required to record a verdict"),
|
|
10526
|
+
ok: z26.boolean(),
|
|
10527
|
+
note: z26.string().optional().describe("One line on what's wrong \u2014 required when ok is false")
|
|
10171
10528
|
})
|
|
10172
10529
|
).describe("Entry name \u2192 verdict, exactly as returned by verify_snapshot")
|
|
10173
10530
|
},
|
|
@@ -10180,8 +10537,8 @@ function createMcpServer() {
|
|
|
10180
10537
|
"get_impact",
|
|
10181
10538
|
"Trace historical co-change partners, related tests, and references with evidence: resolved imports, explicit paths, or uncertain name candidates. Candidates are not proven dependencies. Deterministic and read-only; no initialization or concept map required.",
|
|
10182
10539
|
{
|
|
10183
|
-
dir:
|
|
10184
|
-
files:
|
|
10540
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10541
|
+
files: z26.array(z26.string()).describe("File paths or names to analyze (e.g., ['WeatherRepository.kt'] or ['src/services/auth.ts'])")
|
|
10185
10542
|
},
|
|
10186
10543
|
async ({ dir, files }) => {
|
|
10187
10544
|
const result = await getImpact(dir, files);
|
|
@@ -10194,12 +10551,12 @@ function createMcpServer() {
|
|
|
10194
10551
|
"export_to_confluence",
|
|
10195
10552
|
"Sync the project's concept map to Confluence as product-readable wiki pages: an index page, one page per feature (PM-language descriptions, no file paths), and a changelog page. Mason replaces managed page bodies; manual edits to those bodies are overwritten. Requires `mason_set_confluence` to have been called first.",
|
|
10196
10553
|
{
|
|
10197
|
-
dir:
|
|
10198
|
-
spaceKey:
|
|
10199
|
-
parentPageId:
|
|
10200
|
-
indexPageTitle:
|
|
10201
|
-
changelogPageTitle:
|
|
10202
|
-
featurePagePrefix:
|
|
10554
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10555
|
+
spaceKey: z26.string().optional().describe("Override the configured space key"),
|
|
10556
|
+
parentPageId: z26.string().optional().describe("Override the configured parent page ID"),
|
|
10557
|
+
indexPageTitle: z26.string().optional().describe("Title of the index page (default: 'Mason \u2014 System Map')"),
|
|
10558
|
+
changelogPageTitle: z26.string().optional().describe("Title of the changelog page (default: 'Mason \u2014 Changelog')"),
|
|
10559
|
+
featurePagePrefix: z26.string().optional().describe("Prefix for each feature page title (default: 'Feature: ')")
|
|
10203
10560
|
},
|
|
10204
10561
|
async ({ dir, spaceKey, parentPageId, indexPageTitle, changelogPageTitle, featurePagePrefix }) => {
|
|
10205
10562
|
const result = await exportToConfluenceTool(dir, {
|
|
@@ -10224,32 +10581,32 @@ async function startMcpServer(onClose) {
|
|
|
10224
10581
|
// src/distribution/updates.ts
|
|
10225
10582
|
init_store();
|
|
10226
10583
|
init_storage();
|
|
10227
|
-
import
|
|
10584
|
+
import fs34 from "fs/promises";
|
|
10228
10585
|
import path42 from "path";
|
|
10229
10586
|
import { spawn as spawn3 } from "child_process";
|
|
10230
10587
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
10231
|
-
import { z as
|
|
10588
|
+
import { z as z32 } from "zod";
|
|
10232
10589
|
|
|
10233
10590
|
// src/distribution/state.ts
|
|
10234
10591
|
init_storage();
|
|
10235
10592
|
import os5 from "os";
|
|
10236
10593
|
import path38 from "path";
|
|
10237
10594
|
import { fileURLToPath } from "url";
|
|
10238
|
-
import
|
|
10239
|
-
import { z as
|
|
10595
|
+
import fs29 from "fs/promises";
|
|
10596
|
+
import { z as z28 } from "zod";
|
|
10240
10597
|
|
|
10241
10598
|
// src/distribution/path.ts
|
|
10242
|
-
import
|
|
10599
|
+
import fs28 from "fs/promises";
|
|
10243
10600
|
import os4 from "os";
|
|
10244
10601
|
import path37 from "path";
|
|
10245
10602
|
import { execFile as execFile12 } from "child_process";
|
|
10246
10603
|
import { promisify as promisify12 } from "util";
|
|
10247
10604
|
import { createHash as createHash7, randomUUID as randomUUID7 } from "crypto";
|
|
10248
|
-
import { z as
|
|
10605
|
+
import { z as z27 } from "zod";
|
|
10249
10606
|
var execute2 = promisify12(execFile12);
|
|
10250
|
-
var pathChangeSchema =
|
|
10251
|
-
|
|
10252
|
-
|
|
10607
|
+
var pathChangeSchema = z27.discriminatedUnion("kind", [
|
|
10608
|
+
z27.object({ kind: z27.literal("profile"), file: z27.string(), block: z27.string(), created: z27.boolean() }),
|
|
10609
|
+
z27.object({ kind: z27.literal("windows"), entry: z27.string(), created: z27.boolean() })
|
|
10253
10610
|
]);
|
|
10254
10611
|
var quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
10255
10612
|
async function configurePath(bin, previous, save, options = {}) {
|
|
@@ -10286,7 +10643,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
10286
10643
|
else {
|
|
10287
10644
|
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path37.join(userHome, name));
|
|
10288
10645
|
let login = candidates[2];
|
|
10289
|
-
for (const file of candidates) if (await
|
|
10646
|
+
for (const file of candidates) if (await fs28.lstat(file).then(() => true, (error) => {
|
|
10290
10647
|
if (error.code === "ENOENT") return false;
|
|
10291
10648
|
throw error;
|
|
10292
10649
|
})) {
|
|
@@ -10297,12 +10654,12 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
10297
10654
|
}
|
|
10298
10655
|
const marker = `# >>> mason PATH ${createHash7("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
10299
10656
|
for (const profile of profiles2) {
|
|
10300
|
-
const stat = await
|
|
10657
|
+
const stat = await fs28.lstat(profile).catch((error) => {
|
|
10301
10658
|
if (error.code === "ENOENT") return null;
|
|
10302
10659
|
throw error;
|
|
10303
10660
|
});
|
|
10304
|
-
const file = stat?.isSymbolicLink() ? await
|
|
10305
|
-
const original = await
|
|
10661
|
+
const file = stat?.isSymbolicLink() ? await fs28.realpath(profile) : path37.resolve(profile);
|
|
10662
|
+
const original = await fs28.readFile(file).catch((error) => {
|
|
10306
10663
|
if (error.code === "ENOENT" && !stat) return null;
|
|
10307
10664
|
throw error;
|
|
10308
10665
|
});
|
|
@@ -10317,8 +10674,8 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
10317
10674
|
changes.push({ kind: "profile", file, block, created: original === null });
|
|
10318
10675
|
await save(changes);
|
|
10319
10676
|
}
|
|
10320
|
-
await
|
|
10321
|
-
const handle = await
|
|
10677
|
+
await fs28.mkdir(path37.dirname(file), { recursive: true });
|
|
10678
|
+
const handle = await fs28.open(file, original === null ? "ax+" : "a+", 384);
|
|
10322
10679
|
try {
|
|
10323
10680
|
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
10324
10681
|
await handle.writeFile(block);
|
|
@@ -10369,32 +10726,32 @@ try {
|
|
|
10369
10726
|
}
|
|
10370
10727
|
|
|
10371
10728
|
// src/distribution/state.ts
|
|
10372
|
-
var releaseVersion =
|
|
10373
|
-
var bundleId =
|
|
10374
|
-
var updatePolicySchema =
|
|
10375
|
-
enabled:
|
|
10376
|
-
revision:
|
|
10729
|
+
var releaseVersion = z28.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
|
|
10730
|
+
var bundleId = z28.string().regex(/^[a-f0-9]{24}$/);
|
|
10731
|
+
var updatePolicySchema = z28.object({
|
|
10732
|
+
enabled: z28.boolean(),
|
|
10733
|
+
revision: z28.string(),
|
|
10377
10734
|
pinnedVersion: releaseVersion.optional(),
|
|
10378
|
-
mirror:
|
|
10735
|
+
mirror: z28.string().optional()
|
|
10379
10736
|
});
|
|
10380
|
-
var recordSchema3 =
|
|
10381
|
-
format:
|
|
10382
|
-
home:
|
|
10383
|
-
bin:
|
|
10737
|
+
var recordSchema3 = z28.object({
|
|
10738
|
+
format: z28.literal(1),
|
|
10739
|
+
home: z28.string(),
|
|
10740
|
+
bin: z28.string(),
|
|
10384
10741
|
current: bundleId,
|
|
10385
10742
|
version: releaseVersion,
|
|
10386
|
-
launchers:
|
|
10387
|
-
previousLaunchers:
|
|
10388
|
-
pathChanges:
|
|
10743
|
+
launchers: z28.record(z28.string()),
|
|
10744
|
+
previousLaunchers: z28.record(z28.string()).optional(),
|
|
10745
|
+
pathChanges: z28.array(pathChangeSchema).optional(),
|
|
10389
10746
|
updates: updatePolicySchema.optional(),
|
|
10390
|
-
previous:
|
|
10391
|
-
pending:
|
|
10747
|
+
previous: z28.object({ id: bundleId, version: releaseVersion }).optional(),
|
|
10748
|
+
pending: z28.object({ id: bundleId, version: releaseVersion, policyRevision: z28.string() }).optional()
|
|
10392
10749
|
});
|
|
10393
10750
|
async function readInstallation(home) {
|
|
10394
10751
|
const raw = await readStoreJson(home, "install.json");
|
|
10395
10752
|
if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
10396
10753
|
const record = recordSchema3.parse(raw);
|
|
10397
|
-
if (record.home !== await
|
|
10754
|
+
if (record.home !== await fs29.realpath(home)) throw new Error("Invalid standalone installation receipt.");
|
|
10398
10755
|
return record;
|
|
10399
10756
|
}
|
|
10400
10757
|
function standaloneLocation() {
|
|
@@ -10426,22 +10783,22 @@ function newerStableVersion(candidate, installed) {
|
|
|
10426
10783
|
|
|
10427
10784
|
// src/distribution/bundle.ts
|
|
10428
10785
|
init_storage();
|
|
10429
|
-
import
|
|
10786
|
+
import fs30 from "fs/promises";
|
|
10430
10787
|
import path39 from "path";
|
|
10431
10788
|
import { createHash as createHash8 } from "crypto";
|
|
10432
|
-
import { z as
|
|
10789
|
+
import { z as z29 } from "zod";
|
|
10433
10790
|
var targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
10434
|
-
var digest2 =
|
|
10435
|
-
var bundleSchema =
|
|
10436
|
-
format:
|
|
10437
|
-
version:
|
|
10438
|
-
target:
|
|
10439
|
-
nodeVersion:
|
|
10440
|
-
files:
|
|
10791
|
+
var digest2 = z29.string().regex(/^[a-f0-9]{64}$/);
|
|
10792
|
+
var bundleSchema = z29.object({
|
|
10793
|
+
format: z29.literal(1),
|
|
10794
|
+
version: z29.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
10795
|
+
target: z29.enum(targets),
|
|
10796
|
+
nodeVersion: z29.string(),
|
|
10797
|
+
files: z29.record(digest2)
|
|
10441
10798
|
}).strict();
|
|
10442
10799
|
var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
|
|
10443
10800
|
async function readBundle(root) {
|
|
10444
|
-
const bytes = await
|
|
10801
|
+
const bytes = await fs30.readFile(await storePath(root, "bundle.json"));
|
|
10445
10802
|
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
10446
10803
|
const windows = manifest.target.startsWith("win32-");
|
|
10447
10804
|
for (const file of Object.keys(manifest.files)) {
|
|
@@ -10449,7 +10806,7 @@ async function readBundle(root) {
|
|
|
10449
10806
|
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
10450
10807
|
}
|
|
10451
10808
|
}
|
|
10452
|
-
for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-
|
|
10809
|
+
for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-mcp.js"]) {
|
|
10453
10810
|
if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
|
|
10454
10811
|
}
|
|
10455
10812
|
return { manifest, manifestHash: sha256(bytes) };
|
|
@@ -10460,7 +10817,7 @@ async function verifyBundle(root, expectedHash) {
|
|
|
10460
10817
|
if (bundle.manifest.target !== `${process.platform}-${process.arch}`) throw new Error("Mason bundle is for another platform. Install this platform's bundle and rerun setup.");
|
|
10461
10818
|
const files = Object.keys(bundle.manifest.files);
|
|
10462
10819
|
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
10463
|
-
if (sha256(await
|
|
10820
|
+
if (sha256(await fs30.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
10464
10821
|
}));
|
|
10465
10822
|
return bundle;
|
|
10466
10823
|
}
|
|
@@ -10468,16 +10825,21 @@ async function verifyBundle(root, expectedHash) {
|
|
|
10468
10825
|
// src/distribution/runtime.ts
|
|
10469
10826
|
init_store();
|
|
10470
10827
|
init_storage();
|
|
10471
|
-
import
|
|
10828
|
+
import fs31 from "fs/promises";
|
|
10472
10829
|
import { unlinkSync } from "fs";
|
|
10473
10830
|
import os6 from "os";
|
|
10474
10831
|
import path40 from "path";
|
|
10475
10832
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
10476
|
-
import { z as
|
|
10477
|
-
|
|
10833
|
+
import { z as z30 } from "zod";
|
|
10834
|
+
|
|
10835
|
+
// src/distribution/trust.ts
|
|
10836
|
+
import tls from "tls";
|
|
10837
|
+
|
|
10838
|
+
// src/distribution/runtime.ts
|
|
10839
|
+
var leaseSchema = z30.object({ pid: z30.number().int().positive(), host: z30.string(), bundle: z30.string().regex(/^[a-f0-9]{24}$/) });
|
|
10478
10840
|
|
|
10479
10841
|
// src/distribution/install.ts
|
|
10480
|
-
import
|
|
10842
|
+
import fs33 from "fs/promises";
|
|
10481
10843
|
import os8 from "os";
|
|
10482
10844
|
import path41 from "path";
|
|
10483
10845
|
import { execFile as execFile13, spawn as spawn2 } from "child_process";
|
|
@@ -10488,14 +10850,14 @@ init_store();
|
|
|
10488
10850
|
|
|
10489
10851
|
// src/distribution/sessions.ts
|
|
10490
10852
|
init_storage();
|
|
10491
|
-
import
|
|
10853
|
+
import fs32 from "fs/promises";
|
|
10492
10854
|
import os7 from "os";
|
|
10493
10855
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
10494
|
-
import { z as
|
|
10495
|
-
var leaseSchema2 =
|
|
10856
|
+
import { z as z31 } from "zod";
|
|
10857
|
+
var leaseSchema2 = z31.object({ pid: z31.number().int().positive(), host: z31.string() });
|
|
10496
10858
|
async function hasRunningMcp(home) {
|
|
10497
|
-
const
|
|
10498
|
-
const files = await
|
|
10859
|
+
const directory2 = await storePath(home, "sessions");
|
|
10860
|
+
const files = await fs32.readdir(directory2).catch((error) => {
|
|
10499
10861
|
if (error.code === "ENOENT") return [];
|
|
10500
10862
|
throw error;
|
|
10501
10863
|
});
|
|
@@ -10513,7 +10875,7 @@ async function hasRunningMcp(home) {
|
|
|
10513
10875
|
} catch (error) {
|
|
10514
10876
|
if (error.code !== "ESRCH") return true;
|
|
10515
10877
|
}
|
|
10516
|
-
await
|
|
10878
|
+
await fs32.rm(await storePath(home, relative), { force: true });
|
|
10517
10879
|
} catch {
|
|
10518
10880
|
return true;
|
|
10519
10881
|
}
|
|
@@ -10524,7 +10886,7 @@ async function registerMcp(home) {
|
|
|
10524
10886
|
const name = `sessions/${randomUUID9()}.json`;
|
|
10525
10887
|
await writeStoreJson(home, name, { pid: process.pid, host: os7.hostname() });
|
|
10526
10888
|
return async () => {
|
|
10527
|
-
await
|
|
10889
|
+
await fs32.rm(await storePath(home, name), { force: true }).catch(() => {
|
|
10528
10890
|
});
|
|
10529
10891
|
};
|
|
10530
10892
|
}
|
|
@@ -10537,10 +10899,10 @@ var launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
|
|
|
10537
10899
|
async function replace(file, content2, mode = 384) {
|
|
10538
10900
|
const temp = file + ".tmp-" + randomUUID10();
|
|
10539
10901
|
try {
|
|
10540
|
-
await
|
|
10541
|
-
await
|
|
10902
|
+
await fs33.writeFile(temp, content2, { mode, flag: "wx" });
|
|
10903
|
+
await fs33.rename(temp, file);
|
|
10542
10904
|
} finally {
|
|
10543
|
-
await
|
|
10905
|
+
await fs33.rm(temp, { force: true });
|
|
10544
10906
|
}
|
|
10545
10907
|
}
|
|
10546
10908
|
function assertUpdatePolicy(record, version, revision) {
|
|
@@ -10552,17 +10914,17 @@ async function installLocked2(home, source, bundle, progress, options, alreadySt
|
|
|
10552
10914
|
const raw = await readStoreJson(home, "install.json");
|
|
10553
10915
|
const previous = raw === null ? null : recordSchema3.parse(raw);
|
|
10554
10916
|
if (options.stageOnly) assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
|
|
10555
|
-
if (!previous) for (const file of await
|
|
10917
|
+
if (!previous) for (const file of await fs33.readdir(home)) {
|
|
10556
10918
|
if (file === ".install-lock") continue;
|
|
10557
|
-
const
|
|
10558
|
-
const stat = await
|
|
10559
|
-
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await
|
|
10919
|
+
const directory2 = path41.join(home, file);
|
|
10920
|
+
const stat = await fs33.lstat(directory2);
|
|
10921
|
+
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs33.readdir(directory2)).length) {
|
|
10560
10922
|
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
10561
10923
|
}
|
|
10562
10924
|
}
|
|
10563
10925
|
const selectedBin = path41.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path41.join(home, "bin") : path41.join(os8.homedir(), ".local/bin")));
|
|
10564
|
-
await
|
|
10565
|
-
const bin = await
|
|
10926
|
+
await fs33.mkdir(selectedBin, { recursive: true });
|
|
10927
|
+
const bin = await fs33.realpath(selectedBin);
|
|
10566
10928
|
if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
|
|
10567
10929
|
const id = bundle.manifestHash.slice(0, 24);
|
|
10568
10930
|
const destination = await storePath(home, "versions/" + id);
|
|
@@ -10594,7 +10956,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
|
|
|
10594
10956
|
` };
|
|
10595
10957
|
for (const [name, content2] of Object.entries(launchers)) {
|
|
10596
10958
|
const file = await storePath(bin, name);
|
|
10597
|
-
const old = await
|
|
10959
|
+
const old = await fs33.readFile(file, "utf8").catch((error) => {
|
|
10598
10960
|
if (error.code === "ENOENT") return null;
|
|
10599
10961
|
throw error;
|
|
10600
10962
|
});
|
|
@@ -10603,7 +10965,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
|
|
|
10603
10965
|
const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
|
|
10604
10966
|
const checkRetired = async (name, expected) => {
|
|
10605
10967
|
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
10606
|
-
const actual = await
|
|
10968
|
+
const actual = await fs33.readFile(await storePath(bin, name), "utf8").catch((error) => {
|
|
10607
10969
|
if (error.code === "ENOENT") return null;
|
|
10608
10970
|
throw error;
|
|
10609
10971
|
});
|
|
@@ -10638,7 +11000,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
|
|
|
10638
11000
|
for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
10639
11001
|
for (const [name, expected] of retired) {
|
|
10640
11002
|
await checkRetired(name, expected);
|
|
10641
|
-
await
|
|
11003
|
+
await fs33.rm(await storePath(bin, name), { force: true });
|
|
10642
11004
|
}
|
|
10643
11005
|
await writeStoreJson(home, "install.json", receipt);
|
|
10644
11006
|
} catch (error) {
|
|
@@ -10657,7 +11019,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
|
|
|
10657
11019
|
return { home, bin, version: bundle.manifest.version, previousVersion: previous?.version, path: pathResult };
|
|
10658
11020
|
}
|
|
10659
11021
|
async function copyBundle(home, source, destination, bundle) {
|
|
10660
|
-
const exists = await
|
|
11022
|
+
const exists = await fs33.lstat(destination).then(() => true, (error) => {
|
|
10661
11023
|
if (error.code === "ENOENT") return false;
|
|
10662
11024
|
throw error;
|
|
10663
11025
|
});
|
|
@@ -10666,16 +11028,16 @@ async function copyBundle(home, source, destination, bundle) {
|
|
|
10666
11028
|
return;
|
|
10667
11029
|
}
|
|
10668
11030
|
const stage = await storePath(home, "versions/.install-" + randomUUID10(), true);
|
|
10669
|
-
await
|
|
11031
|
+
await fs33.mkdir(stage);
|
|
10670
11032
|
try {
|
|
10671
11033
|
for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
|
|
10672
11034
|
const from = await storePath(source, file), to = await storePath(stage, file, true);
|
|
10673
|
-
await
|
|
10674
|
-
await
|
|
11035
|
+
await fs33.copyFile(from, to);
|
|
11036
|
+
await fs33.chmod(to, (await fs33.stat(from)).mode & 511);
|
|
10675
11037
|
}
|
|
10676
11038
|
await verifyBundle(stage, bundle.manifestHash);
|
|
10677
11039
|
try {
|
|
10678
|
-
await
|
|
11040
|
+
await fs33.rename(stage, destination);
|
|
10679
11041
|
} catch (error) {
|
|
10680
11042
|
try {
|
|
10681
11043
|
await verifyBundle(destination, bundle.manifestHash);
|
|
@@ -10684,7 +11046,7 @@ async function copyBundle(home, source, destination, bundle) {
|
|
|
10684
11046
|
}
|
|
10685
11047
|
}
|
|
10686
11048
|
} finally {
|
|
10687
|
-
await
|
|
11049
|
+
await fs33.rm(stage, { recursive: true, force: true });
|
|
10688
11050
|
}
|
|
10689
11051
|
}
|
|
10690
11052
|
async function prepareStandaloneMcp(home) {
|
|
@@ -10722,14 +11084,14 @@ async function prepareStandaloneMcp(home) {
|
|
|
10722
11084
|
// src/distribution/updates.ts
|
|
10723
11085
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
10724
11086
|
var RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
10725
|
-
var releaseListSchema =
|
|
10726
|
-
var updateManifestSchema =
|
|
10727
|
-
format:
|
|
10728
|
-
version:
|
|
10729
|
-
publishedAt:
|
|
10730
|
-
assets:
|
|
11087
|
+
var releaseListSchema = z32.array(z32.object({ tag_name: z32.string(), draft: z32.boolean(), prerelease: z32.boolean(), published_at: z32.string().nullable() }));
|
|
11088
|
+
var updateManifestSchema = z32.object({
|
|
11089
|
+
format: z32.literal(1),
|
|
11090
|
+
version: z32.string().regex(/^\d+\.\d+\.\d+$/),
|
|
11091
|
+
publishedAt: z32.string().datetime(),
|
|
11092
|
+
assets: z32.record(z32.string().regex(/^[a-f0-9]{64}$/))
|
|
10731
11093
|
}).strict();
|
|
10732
|
-
var statusSchema2 =
|
|
11094
|
+
var statusSchema2 = z32.object({ lastAttempt: z32.number(), availableVersion: z32.string().optional(), error: z32.string().optional() });
|
|
10733
11095
|
async function scheduleAutomaticUpdate() {
|
|
10734
11096
|
try {
|
|
10735
11097
|
const { home, record } = await currentInstallation();
|