mason-context 0.19.2 → 0.20.2
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 +18 -0
- package/dist/mason-audit.js +16 -10
- 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 +616 -446
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js.map +1 -1
- package/dist/mason-runtime.js +16 -10
- package/dist/mason-runtime.js.map +1 -1
- package/dist/mason.js +1203 -903
- package/dist/mason.js.map +1 -1
- package/package.json +2 -2
- package/dist/mason-auto.js +0 -6474
- 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);
|
|
@@ -3266,8 +3272,15 @@ function completionSummary(findings, reportPath) {
|
|
|
3266
3272
|
`Mason: ${findings.length} new or worsened finding(s) since the retained baseline.`,
|
|
3267
3273
|
...findings.slice(0, MAX_COMPLETION_FINDINGS).map((f) => {
|
|
3268
3274
|
const source = f.current ?? f.original;
|
|
3269
|
-
|
|
3270
|
-
|
|
3275
|
+
if (source.evidence.kind === "decision-anchor") {
|
|
3276
|
+
const evidence = source.evidence;
|
|
3277
|
+
return [
|
|
3278
|
+
`[advisory] Decision "${safe(evidence.title)}" may no longer hold: ${evidence.changedFiles.length} anchored file(s) changed.`,
|
|
3279
|
+
`Inspect the decision and diff with review_decision(action: "prepare", id: ${JSON.stringify(evidence.decisionId)}); acceptance is not required.`,
|
|
3280
|
+
`Changed files: ${safe(evidence.changedFiles.join(", "))}`
|
|
3281
|
+
].join("\n");
|
|
3282
|
+
}
|
|
3283
|
+
return `[${f.status === "unresolved" ? "issue" : "advisory"}] ${safe(source.anchor.doc)}: ${safe(source.message)}`;
|
|
3271
3284
|
}),
|
|
3272
3285
|
...findings.length > MAX_COMPLETION_FINDINGS ? [`${findings.length - MAX_COMPLETION_FINDINGS} more in the report; pending later feedback.`] : [],
|
|
3273
3286
|
`Evidence: ${reportPath}. Explicit audit: mason_automation(action: "check").`,
|
|
@@ -3304,8 +3317,8 @@ function parseState(raw) {
|
|
|
3304
3317
|
throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
|
|
3305
3318
|
}
|
|
3306
3319
|
}
|
|
3307
|
-
function withLock(root,
|
|
3308
|
-
return withStoreLock(root,
|
|
3320
|
+
function withLock(root, directory2, run, waitMs = 5e3) {
|
|
3321
|
+
return withStoreLock(root, directory2, run, waitMs, "Automation");
|
|
3309
3322
|
}
|
|
3310
3323
|
var hostSchema, events, stateSchema;
|
|
3311
3324
|
var init_store = __esm({
|
|
@@ -4129,7 +4142,7 @@ var init_evidence = __esm({
|
|
|
4129
4142
|
init_dead_command();
|
|
4130
4143
|
init_storage();
|
|
4131
4144
|
init_advisory_review();
|
|
4132
|
-
engineVersion = true ? "0.
|
|
4145
|
+
engineVersion = true ? "0.20.2" : "development";
|
|
4133
4146
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
4134
4147
|
content = readAuditInput;
|
|
4135
4148
|
cacheSchema = z9.object({ version: z9.literal(1), entries: z9.record(z9.object({ key: z9.string(), result: checkResultSchema })), digest: z9.string() });
|
|
@@ -4182,8 +4195,8 @@ async function effectiveSetup(root) {
|
|
|
4182
4195
|
if (inactive && setup) for (const host of inactiveHostsSchema.parse(inactive).hosts) delete setup.hosts[host];
|
|
4183
4196
|
return { root: inheritedRoot, setup };
|
|
4184
4197
|
}
|
|
4185
|
-
async function loadSetupReceipt(root,
|
|
4186
|
-
const raw = await readStoreJson(root,
|
|
4198
|
+
async function loadSetupReceipt(root, directory2, host) {
|
|
4199
|
+
const raw = await readStoreJson(root, directory2 + "/setup-" + host + ".json");
|
|
4187
4200
|
if (raw === null) return null;
|
|
4188
4201
|
const receipt = receiptSchema.parse(raw);
|
|
4189
4202
|
if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
|
|
@@ -4255,7 +4268,7 @@ var init_launcher = __esm({
|
|
|
4255
4268
|
"use strict";
|
|
4256
4269
|
init_evidence();
|
|
4257
4270
|
init_model();
|
|
4258
|
-
executingVersion = () => true ? "0.
|
|
4271
|
+
executingVersion = () => true ? "0.20.2" : "development";
|
|
4259
4272
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
4260
4273
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
4261
4274
|
}
|
|
@@ -4439,6 +4452,146 @@ var init_files2 = __esm({
|
|
|
4439
4452
|
}
|
|
4440
4453
|
});
|
|
4441
4454
|
|
|
4455
|
+
// src/automation/usefulness.ts
|
|
4456
|
+
import fs19 from "fs/promises";
|
|
4457
|
+
import { z as z13 } from "zod";
|
|
4458
|
+
async function enabled(root) {
|
|
4459
|
+
const raw = await readStoreJson(root, configPath);
|
|
4460
|
+
return raw !== null && configSchema.parse(raw).enabled;
|
|
4461
|
+
}
|
|
4462
|
+
async function configureUsefulness(dir, value, options = {}) {
|
|
4463
|
+
const ws = await workspace(dir);
|
|
4464
|
+
const configured = await withStoreLock(ws.root, directory, async () => {
|
|
4465
|
+
if (options.onlyIfUnset) {
|
|
4466
|
+
const existing = await readStoreJson(ws.root, configPath);
|
|
4467
|
+
if (existing !== null) return configSchema.parse(existing).enabled;
|
|
4468
|
+
}
|
|
4469
|
+
const ignore = await storePath(ws.root, directory + "/.gitignore", true);
|
|
4470
|
+
try {
|
|
4471
|
+
await fs19.writeFile(ignore, "*\n", { flag: "wx", mode: 384 });
|
|
4472
|
+
} catch (error) {
|
|
4473
|
+
if (error.code !== "EEXIST") throw error;
|
|
4474
|
+
}
|
|
4475
|
+
await writeStoreJson(ws.root, configPath, { version: 1, enabled: value });
|
|
4476
|
+
return value;
|
|
4477
|
+
});
|
|
4478
|
+
return { enabled: configured, storage: directory, note: "Local only. Disabling retains prior observations." };
|
|
4479
|
+
}
|
|
4480
|
+
async function observeUsefulness(root, event, report, agent, completion, elapsedMs, observedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
4481
|
+
if (!event.host || !event.sessionId || !await enabled(root)) return;
|
|
4482
|
+
const ws = await workspace(root);
|
|
4483
|
+
await withStoreLock(ws.root, directory, async () => {
|
|
4484
|
+
if (!await enabled(root)) return;
|
|
4485
|
+
const raw = await readStoreJson(root, statePath(ws));
|
|
4486
|
+
const state = raw === null ? empty() : stateSchema2.parse(raw);
|
|
4487
|
+
const now = observedAt;
|
|
4488
|
+
const key = hash([event.host, event.sessionId]);
|
|
4489
|
+
const session = state.sessions[key] ??= {
|
|
4490
|
+
host: event.host,
|
|
4491
|
+
updatedAt: now,
|
|
4492
|
+
checks: 0,
|
|
4493
|
+
checkMs: 0,
|
|
4494
|
+
findings: {},
|
|
4495
|
+
decisions: [],
|
|
4496
|
+
retrievalObserved: false,
|
|
4497
|
+
receiptIds: [],
|
|
4498
|
+
reportPath: report.reportPath,
|
|
4499
|
+
truncated: false
|
|
4500
|
+
};
|
|
4501
|
+
const newer = now >= session.updatedAt;
|
|
4502
|
+
if (newer) {
|
|
4503
|
+
session.updatedAt = now;
|
|
4504
|
+
session.reportPath = report.reportPath;
|
|
4505
|
+
}
|
|
4506
|
+
session.checks++;
|
|
4507
|
+
session.checkMs += Math.max(0, elapsedMs);
|
|
4508
|
+
if (newer) {
|
|
4509
|
+
if (event.event === "task_end") session.endedAt = now;
|
|
4510
|
+
else delete session.endedAt;
|
|
4511
|
+
}
|
|
4512
|
+
let latestFindings = [];
|
|
4513
|
+
if (!newer && [...agent.slice(0, 4), ...completion.slice(0, 4)].some((f) => !session.findings[f.id])) {
|
|
4514
|
+
try {
|
|
4515
|
+
if (!session.reportPath.startsWith(ws.directory + "/checks/")) throw new Error("Unexpected report path");
|
|
4516
|
+
latestFindings = z13.object({ findings: z13.array(z13.object({ id: z13.string(), status: findingSchema2.shape.status })) }).parse(await readStoreJson(root, session.reportPath)).findings;
|
|
4517
|
+
} catch {
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
for (const [channel, findings] of [["agent", agent], ["completion", completion]]) {
|
|
4521
|
+
for (const finding of findings.slice(0, 4)) {
|
|
4522
|
+
if (!session.findings[finding.id] && Object.keys(session.findings).length >= 200) {
|
|
4523
|
+
session.truncated = true;
|
|
4524
|
+
continue;
|
|
4525
|
+
}
|
|
4526
|
+
const saved = session.findings[finding.id] ??= {
|
|
4527
|
+
type: finding.original.type,
|
|
4528
|
+
channels: [],
|
|
4529
|
+
status: newer ? finding.status : latestFindings.find((f) => f.id === finding.id)?.status ?? "unknown",
|
|
4530
|
+
deliveredAt: now,
|
|
4531
|
+
observedAt: newer ? now : session.updatedAt
|
|
4532
|
+
};
|
|
4533
|
+
if (!saved.channels.includes(channel)) saved.channels.push(channel);
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
for (const [id, saved] of Object.entries(session.findings)) {
|
|
4537
|
+
if (now < saved.observedAt) continue;
|
|
4538
|
+
const current = report.findings.find((f) => f.id === id);
|
|
4539
|
+
saved.status = current?.status ?? "unknown";
|
|
4540
|
+
saved.observedAt = now;
|
|
4541
|
+
}
|
|
4542
|
+
if (event.retrievedDecisionIds !== void 0 && event.toolId) {
|
|
4543
|
+
const receipt = hash([event.toolId]);
|
|
4544
|
+
if (!session.receiptIds.includes(receipt)) {
|
|
4545
|
+
session.retrievalObserved = true;
|
|
4546
|
+
const decisions = [.../* @__PURE__ */ new Set([...session.decisions, ...event.retrievedDecisionIds.map((id) => hash([id]))])];
|
|
4547
|
+
if (decisions.length > 200) session.truncated = true;
|
|
4548
|
+
session.decisions = decisions.slice(0, 200);
|
|
4549
|
+
session.receiptIds = [...session.receiptIds, receipt].slice(-200);
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
const keys = Object.keys(state.sessions).sort((a, b) => state.sessions[b].updatedAt.localeCompare(state.sessions[a].updatedAt));
|
|
4553
|
+
for (const key2 of keys.slice(50)) delete state.sessions[key2];
|
|
4554
|
+
await writeStoreJson(root, statePath(ws), state);
|
|
4555
|
+
});
|
|
4556
|
+
}
|
|
4557
|
+
var directory, configPath, configSchema, usefulnessRating, findingSchema2, sessionSchema, stateSchema2, empty, statePath;
|
|
4558
|
+
var init_usefulness = __esm({
|
|
4559
|
+
"src/automation/usefulness.ts"() {
|
|
4560
|
+
"use strict";
|
|
4561
|
+
init_evidence();
|
|
4562
|
+
init_storage();
|
|
4563
|
+
init_store_lock();
|
|
4564
|
+
directory = ".mason/local/usefulness";
|
|
4565
|
+
configPath = directory + "/config.json";
|
|
4566
|
+
configSchema = z13.object({ version: z13.literal(1), enabled: z13.boolean() });
|
|
4567
|
+
usefulnessRating = z13.enum(["helpful", "already-knew", "irrelevant", "deferred"]);
|
|
4568
|
+
findingSchema2 = z13.object({
|
|
4569
|
+
type: z13.string(),
|
|
4570
|
+
channels: z13.array(z13.enum(["agent", "completion"])),
|
|
4571
|
+
status: z13.enum(["resolved", "unresolved", "review-required", "unverified", "unknown"]),
|
|
4572
|
+
deliveredAt: z13.string(),
|
|
4573
|
+
observedAt: z13.string(),
|
|
4574
|
+
rating: usefulnessRating.optional()
|
|
4575
|
+
});
|
|
4576
|
+
sessionSchema = z13.object({
|
|
4577
|
+
host: z13.string(),
|
|
4578
|
+
updatedAt: z13.string(),
|
|
4579
|
+
endedAt: z13.string().optional(),
|
|
4580
|
+
checks: z13.number(),
|
|
4581
|
+
checkMs: z13.number(),
|
|
4582
|
+
findings: z13.record(findingSchema2),
|
|
4583
|
+
decisions: z13.array(z13.string()),
|
|
4584
|
+
retrievalObserved: z13.boolean(),
|
|
4585
|
+
receiptIds: z13.array(z13.string()),
|
|
4586
|
+
reportPath: z13.string(),
|
|
4587
|
+
truncated: z13.boolean()
|
|
4588
|
+
});
|
|
4589
|
+
stateSchema2 = z13.object({ version: z13.literal(1), sessions: z13.record(sessionSchema) });
|
|
4590
|
+
empty = () => ({ version: 1, sessions: {} });
|
|
4591
|
+
statePath = (ws) => `${directory}/${hash([ws.root, ws.gitDir, ws.branch]).slice(0, 24)}.json`;
|
|
4592
|
+
}
|
|
4593
|
+
});
|
|
4594
|
+
|
|
4442
4595
|
// src/automation/execution.ts
|
|
4443
4596
|
var execution_exports = {};
|
|
4444
4597
|
__export(execution_exports, {
|
|
@@ -4450,9 +4603,9 @@ __export(execution_exports, {
|
|
|
4450
4603
|
recordExecution: () => recordExecution
|
|
4451
4604
|
});
|
|
4452
4605
|
import os2 from "os";
|
|
4453
|
-
import
|
|
4606
|
+
import fs20 from "fs/promises";
|
|
4454
4607
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4455
|
-
import { z as
|
|
4608
|
+
import { z as z14 } from "zod";
|
|
4456
4609
|
function parseExecution(raw) {
|
|
4457
4610
|
try {
|
|
4458
4611
|
return executionSchema.parse(raw);
|
|
@@ -4470,15 +4623,15 @@ function automationFailure(error) {
|
|
|
4470
4623
|
codes.add(String(cause.code));
|
|
4471
4624
|
cause = cause.cause;
|
|
4472
4625
|
}
|
|
4473
|
-
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
|
|
4626
|
+
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";
|
|
4474
4627
|
return { code, message: message2, retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code), receiptRecorded: false };
|
|
4475
4628
|
}
|
|
4476
4629
|
function failureMessage(error) {
|
|
4477
4630
|
const failure = automationFailure(error);
|
|
4478
4631
|
return `Mason automation unavailable [${failure.code}]; evidence capture/verification was not established. ${failure.message}` + (failure.receiptRecorded ? "" : " No durable failure receipt was recorded.");
|
|
4479
4632
|
}
|
|
4480
|
-
async function failureNotifications(root,
|
|
4481
|
-
const raw = await readStoreJson(root,
|
|
4633
|
+
async function failureNotifications(root, directory2) {
|
|
4634
|
+
const raw = await readStoreJson(root, directory2 + "/notifications.json");
|
|
4482
4635
|
return raw === null ? { version: 1, sessions: {} } : notificationsSchema.parse(raw);
|
|
4483
4636
|
}
|
|
4484
4637
|
async function hookFailureMessage(dir, host, sessionId, error) {
|
|
@@ -4508,12 +4661,12 @@ async function hookFailureMessage(dir, host, sessionId, error) {
|
|
|
4508
4661
|
return failureMessage(error);
|
|
4509
4662
|
}
|
|
4510
4663
|
}
|
|
4511
|
-
async function refreshRunning(root,
|
|
4664
|
+
async function refreshRunning(root, directory2, attempts) {
|
|
4512
4665
|
for (const attempt of attempts) {
|
|
4513
4666
|
if (attempt.status !== "running") continue;
|
|
4514
4667
|
let alive = false;
|
|
4515
4668
|
try {
|
|
4516
|
-
const owner = await readStoreJson(root, attempt.lease ? leasePath(
|
|
4669
|
+
const owner = await readStoreJson(root, attempt.lease ? leasePath(directory2, attempt.id) : directory2 + "/lock");
|
|
4517
4670
|
if (attempt.host === os2.hostname() && owner?.pid === attempt.pid && owner.host === attempt.host) {
|
|
4518
4671
|
try {
|
|
4519
4672
|
process.kill(attempt.pid, 0);
|
|
@@ -4527,12 +4680,12 @@ async function refreshRunning(root, directory, attempts) {
|
|
|
4527
4680
|
if (!alive) attempt.status = "unknown";
|
|
4528
4681
|
}
|
|
4529
4682
|
}
|
|
4530
|
-
async function editExecution(root,
|
|
4531
|
-
return withLock(root,
|
|
4532
|
-
const file =
|
|
4683
|
+
async function editExecution(root, directory2, edit) {
|
|
4684
|
+
return withLock(root, directory2 + "/execution-lock", async () => {
|
|
4685
|
+
const file = directory2 + "/execution.json";
|
|
4533
4686
|
const raw = await readStoreJson(root, file);
|
|
4534
4687
|
const log = raw === null ? { version: 1, attempts: [], discardedAttempts: 0 } : parseExecution(raw);
|
|
4535
|
-
await refreshRunning(root,
|
|
4688
|
+
await refreshRunning(root, directory2, log.attempts);
|
|
4536
4689
|
edit(log);
|
|
4537
4690
|
const finished = log.attempts.filter((a) => a.status !== "running");
|
|
4538
4691
|
const discard = new Set(finished.slice(0, Math.max(0, finished.length - 32)).map((a) => a.id));
|
|
@@ -4541,7 +4694,7 @@ async function editExecution(root, directory, edit) {
|
|
|
4541
4694
|
await writeStoreJson(root, file, log);
|
|
4542
4695
|
});
|
|
4543
4696
|
}
|
|
4544
|
-
async function recordExecution(root,
|
|
4697
|
+
async function recordExecution(root, directory2, event, run) {
|
|
4545
4698
|
const started = performance.now();
|
|
4546
4699
|
const attempt = {
|
|
4547
4700
|
id: randomUUID4(),
|
|
@@ -4552,15 +4705,15 @@ async function recordExecution(root, directory, event, run) {
|
|
|
4552
4705
|
status: "running",
|
|
4553
4706
|
lease: true
|
|
4554
4707
|
};
|
|
4555
|
-
const owner = leasePath(
|
|
4556
|
-
const finish = async (changes) => editExecution(root,
|
|
4708
|
+
const owner = leasePath(directory2, attempt.id);
|
|
4709
|
+
const finish = async (changes) => editExecution(root, directory2, (log) => {
|
|
4557
4710
|
const current = log.attempts.find((a) => a.id === attempt.id);
|
|
4558
4711
|
if (!current) throw new Error("Automation execution receipt disappeared; completion could not be recorded.");
|
|
4559
4712
|
Object.assign(current, changes, { finishedAt: (/* @__PURE__ */ new Date()).toISOString(), durationMs: performance.now() - started });
|
|
4560
4713
|
});
|
|
4561
4714
|
await writeStoreJson(root, owner, { pid: attempt.pid, host: attempt.host });
|
|
4562
4715
|
try {
|
|
4563
|
-
await editExecution(root,
|
|
4716
|
+
await editExecution(root, directory2, (log) => {
|
|
4564
4717
|
if (log.attempts.filter((a) => a.status === "running").length >= 128) throw new Error("Too many active automation executions; existing receipts were retained.");
|
|
4565
4718
|
log.attempts.push(attempt);
|
|
4566
4719
|
});
|
|
@@ -4578,15 +4731,15 @@ async function recordExecution(root, directory, event, run) {
|
|
|
4578
4731
|
throw Object.assign(new Error(failure.message, { cause: error }), { failure });
|
|
4579
4732
|
}
|
|
4580
4733
|
} finally {
|
|
4581
|
-
await
|
|
4734
|
+
await fs20.rm(await storePath(root, owner), { force: true }).catch(() => {
|
|
4582
4735
|
});
|
|
4583
4736
|
}
|
|
4584
4737
|
}
|
|
4585
|
-
async function executionStatus(root,
|
|
4586
|
-
const raw = await readStoreJson(root,
|
|
4738
|
+
async function executionStatus(root, directory2) {
|
|
4739
|
+
const raw = await readStoreJson(root, directory2 + "/execution.json");
|
|
4587
4740
|
if (raw === null) return { status: "not-observed", attempts: [] };
|
|
4588
4741
|
const log = parseExecution(raw);
|
|
4589
|
-
await refreshRunning(root,
|
|
4742
|
+
await refreshRunning(root, directory2, log.attempts);
|
|
4590
4743
|
const completed = [...log.attempts].reverse().find((a) => a.status === "completed");
|
|
4591
4744
|
const outstanding = [...log.attempts].reverse().find((a) => ["failed", "unknown"].includes(a.status) && (!completed || (a.finishedAt ?? a.startedAt) >= completed.startedAt));
|
|
4592
4745
|
const status = log.attempts.some((a) => a.status === "running") ? "running" : outstanding?.status ?? log.attempts.at(-1)?.status ?? "not-observed";
|
|
@@ -4599,35 +4752,35 @@ var init_execution = __esm({
|
|
|
4599
4752
|
init_store();
|
|
4600
4753
|
init_evidence();
|
|
4601
4754
|
init_storage();
|
|
4602
|
-
failureSchema =
|
|
4603
|
-
code:
|
|
4604
|
-
message:
|
|
4605
|
-
retryable:
|
|
4606
|
-
receiptRecorded:
|
|
4755
|
+
failureSchema = z14.object({
|
|
4756
|
+
code: z14.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
|
|
4757
|
+
message: z14.string(),
|
|
4758
|
+
retryable: z14.boolean(),
|
|
4759
|
+
receiptRecorded: z14.boolean()
|
|
4607
4760
|
});
|
|
4608
|
-
attemptSchema =
|
|
4609
|
-
id:
|
|
4610
|
-
lease:
|
|
4611
|
-
event:
|
|
4612
|
-
startedAt:
|
|
4613
|
-
finishedAt:
|
|
4614
|
-
durationMs:
|
|
4615
|
-
pid:
|
|
4616
|
-
host:
|
|
4617
|
-
status:
|
|
4618
|
-
verificationStatus:
|
|
4619
|
-
reportPath:
|
|
4761
|
+
attemptSchema = z14.object({
|
|
4762
|
+
id: z14.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
4763
|
+
lease: z14.literal(true).optional(),
|
|
4764
|
+
event: z14.string(),
|
|
4765
|
+
startedAt: z14.string(),
|
|
4766
|
+
finishedAt: z14.string().optional(),
|
|
4767
|
+
durationMs: z14.number().nonnegative().optional(),
|
|
4768
|
+
pid: z14.number().int().positive(),
|
|
4769
|
+
host: z14.string(),
|
|
4770
|
+
status: z14.enum(["running", "completed", "failed", "unknown"]),
|
|
4771
|
+
verificationStatus: z14.string().optional(),
|
|
4772
|
+
reportPath: z14.string().optional(),
|
|
4620
4773
|
failure: failureSchema.optional()
|
|
4621
4774
|
});
|
|
4622
|
-
executionSchema =
|
|
4623
|
-
notificationsSchema =
|
|
4624
|
-
signature:
|
|
4775
|
+
executionSchema = z14.object({ version: z14.literal(1), attempts: z14.array(attemptSchema).max(160), discardedAttempts: z14.number().int().nonnegative().default(0) });
|
|
4776
|
+
notificationsSchema = z14.object({ version: z14.literal(1), sessions: z14.record(z14.object({
|
|
4777
|
+
signature: z14.string(),
|
|
4625
4778
|
code: failureSchema.shape.code,
|
|
4626
|
-
message:
|
|
4627
|
-
count:
|
|
4628
|
-
lastAt:
|
|
4779
|
+
message: z14.string(),
|
|
4780
|
+
count: z14.number().int().positive(),
|
|
4781
|
+
lastAt: z14.string()
|
|
4629
4782
|
})) });
|
|
4630
|
-
leasePath = (
|
|
4783
|
+
leasePath = (directory2, id) => `${directory2}/executions/${id}.json`;
|
|
4631
4784
|
}
|
|
4632
4785
|
});
|
|
4633
4786
|
|
|
@@ -4702,12 +4855,12 @@ __export(runtime_exports, {
|
|
|
4702
4855
|
summarize: () => summarize
|
|
4703
4856
|
});
|
|
4704
4857
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
4705
|
-
import { z as
|
|
4858
|
+
import { z as z15 } from "zod";
|
|
4706
4859
|
async function observeReadOnlyTool(dir, event) {
|
|
4707
4860
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
4708
4861
|
await withLock(ws.root, ws.directory, async () => {
|
|
4709
|
-
const
|
|
4710
|
-
const raw = await readStoreJson(ws.root,
|
|
4862
|
+
const statePath2 = ws.directory + "/state.json";
|
|
4863
|
+
const raw = await readStoreJson(ws.root, statePath2);
|
|
4711
4864
|
if (raw === null) return;
|
|
4712
4865
|
const state = parseState(raw);
|
|
4713
4866
|
if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) {
|
|
@@ -4718,7 +4871,7 @@ async function observeReadOnlyTool(dir, event) {
|
|
|
4718
4871
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4719
4872
|
session.lastUsed = now;
|
|
4720
4873
|
session.events[event.event] = { at: now, count: (session.events[event.event]?.count ?? 0) + 1 };
|
|
4721
|
-
await writeStoreJson(ws.root,
|
|
4874
|
+
await writeStoreJson(ws.root, statePath2, state);
|
|
4722
4875
|
});
|
|
4723
4876
|
return { root: ws.root, directory: ws.directory };
|
|
4724
4877
|
}
|
|
@@ -4733,7 +4886,7 @@ function summarize(report) {
|
|
|
4733
4886
|
}),
|
|
4734
4887
|
...open.length > 4 ? [`${open.length - 4} more findings in the report.`] : [],
|
|
4735
4888
|
...report.diagnostics.slice(0, 2).map(cleanText),
|
|
4736
|
-
`Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason
|
|
4889
|
+
`Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason check.`,
|
|
4737
4890
|
"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."
|
|
4738
4891
|
].join("\n");
|
|
4739
4892
|
}
|
|
@@ -4792,7 +4945,7 @@ async function verifyInputs(ws, inputs) {
|
|
|
4792
4945
|
async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
|
|
4793
4946
|
const baselines = [...state.baselines];
|
|
4794
4947
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4795
|
-
const
|
|
4948
|
+
const empty2 = {
|
|
4796
4949
|
version: 1,
|
|
4797
4950
|
status: "unavailable",
|
|
4798
4951
|
root: ws.root,
|
|
@@ -4809,7 +4962,7 @@ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDia
|
|
|
4809
4962
|
};
|
|
4810
4963
|
if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) {
|
|
4811
4964
|
await verifyInputs(ws, inputs);
|
|
4812
|
-
return { baselines, report:
|
|
4965
|
+
return { baselines, report: empty2, cache: null };
|
|
4813
4966
|
}
|
|
4814
4967
|
if (ws.branch === "detached" && state.latest) {
|
|
4815
4968
|
const previous = await readStoreJson(ws.root, state.latest);
|
|
@@ -4911,7 +5064,7 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
4911
5064
|
async function automate(dir, event) {
|
|
4912
5065
|
const startedAt = Date.now();
|
|
4913
5066
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
4914
|
-
|
|
5067
|
+
const output = await recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
|
|
4915
5068
|
const inputs = await readInputs(ws.root);
|
|
4916
5069
|
const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
|
|
4917
5070
|
if (result.shared) {
|
|
@@ -4928,7 +5081,7 @@ async function automate(dir, event) {
|
|
|
4928
5081
|
let baseline = { findings: report.findings, head: report.head };
|
|
4929
5082
|
if (state.latest) {
|
|
4930
5083
|
try {
|
|
4931
|
-
const previous =
|
|
5084
|
+
const previous = z15.object({ findings: z15.array(repairFindingSchema), head: z15.string() }).safeParse(await readStoreJson(ws.root, state.latest));
|
|
4932
5085
|
if (!previous.success) throw new Error("Unsupported saved report");
|
|
4933
5086
|
baseline = previous.data;
|
|
4934
5087
|
} catch {
|
|
@@ -4996,9 +5149,15 @@ async function automate(dir, event) {
|
|
|
4996
5149
|
state.latest = report.reportPath;
|
|
4997
5150
|
if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
|
|
4998
5151
|
await writeStoreJson(ws.root, ws.directory + "/state.json", state);
|
|
4999
|
-
return { report, message: message2 };
|
|
5152
|
+
return { report, message: message2, observedAt: now, delivery: { agent: feedback.slice(0, 4), completion: selected.slice(0, 4) } };
|
|
5000
5153
|
}));
|
|
5001
5154
|
}));
|
|
5155
|
+
try {
|
|
5156
|
+
await observeUsefulness(ws.root, event, output.report, output.delivery.agent, output.delivery.completion, Date.now() - startedAt, output.observedAt);
|
|
5157
|
+
} catch {
|
|
5158
|
+
if (event.event === "task_end") output.message = [output.message, "Mason: local usefulness tracking unavailable; inspect mason stats --json."].filter(Boolean).join("\n");
|
|
5159
|
+
}
|
|
5160
|
+
return output;
|
|
5002
5161
|
}
|
|
5003
5162
|
async function automationStatus(dir) {
|
|
5004
5163
|
const { pendingKnowledge: pendingKnowledge2 } = await Promise.resolve().then(() => (init_pending(), pending_exports));
|
|
@@ -5039,6 +5198,7 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
|
|
|
5039
5198
|
var init_runtime = __esm({
|
|
5040
5199
|
"src/automation/runtime.ts"() {
|
|
5041
5200
|
"use strict";
|
|
5201
|
+
init_usefulness();
|
|
5042
5202
|
init_notifications();
|
|
5043
5203
|
init_profile();
|
|
5044
5204
|
init_inspection();
|
|
@@ -5051,34 +5211,34 @@ var init_runtime = __esm({
|
|
|
5051
5211
|
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.";
|
|
5052
5212
|
priority = { resolved: 0, "review-required": 1, unresolved: 2, unverified: 3 };
|
|
5053
5213
|
cleanText = (text2) => text2.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
|
|
5054
|
-
analysisSchema =
|
|
5055
|
-
id:
|
|
5056
|
-
fingerprint:
|
|
5057
|
-
completedAt:
|
|
5058
|
-
baselineDigest:
|
|
5214
|
+
analysisSchema = z15.object({
|
|
5215
|
+
id: z15.string(),
|
|
5216
|
+
fingerprint: z15.string(),
|
|
5217
|
+
completedAt: z15.number(),
|
|
5218
|
+
baselineDigest: z15.string(),
|
|
5059
5219
|
currentAudit: reportSchema.nullable().optional(),
|
|
5060
|
-
report:
|
|
5061
|
-
version:
|
|
5062
|
-
status:
|
|
5063
|
-
root:
|
|
5064
|
-
branch:
|
|
5065
|
-
head:
|
|
5066
|
-
baselinePaths:
|
|
5067
|
-
reportPath:
|
|
5068
|
-
findings:
|
|
5069
|
-
diagnostics:
|
|
5070
|
-
checks:
|
|
5071
|
-
ran:
|
|
5072
|
-
reused:
|
|
5073
|
-
skipped:
|
|
5074
|
-
auditReused:
|
|
5220
|
+
report: z15.object({
|
|
5221
|
+
version: z15.literal(1),
|
|
5222
|
+
status: z15.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
|
|
5223
|
+
root: z15.string(),
|
|
5224
|
+
branch: z15.string(),
|
|
5225
|
+
head: z15.string(),
|
|
5226
|
+
baselinePaths: z15.array(z15.string()),
|
|
5227
|
+
reportPath: z15.string(),
|
|
5228
|
+
findings: z15.array(repairFindingSchema),
|
|
5229
|
+
diagnostics: z15.array(z15.string()),
|
|
5230
|
+
checks: z15.object({
|
|
5231
|
+
ran: z15.array(z15.string()),
|
|
5232
|
+
reused: z15.array(z15.string()),
|
|
5233
|
+
skipped: z15.array(z15.object({ check: z15.string(), reason: z15.string(), doc: z15.string().optional() })),
|
|
5234
|
+
auditReused: z15.boolean().optional()
|
|
5075
5235
|
}),
|
|
5076
|
-
counts:
|
|
5077
|
-
capture:
|
|
5078
|
-
scope:
|
|
5236
|
+
counts: z15.object({ resolved: z15.number(), unresolved: z15.number(), "review-required": z15.number(), unverified: z15.number() }),
|
|
5237
|
+
capture: z15.enum(["observed", "unknown"]),
|
|
5238
|
+
scope: z15.string()
|
|
5079
5239
|
})
|
|
5080
5240
|
});
|
|
5081
|
-
sharedSchema =
|
|
5241
|
+
sharedSchema = z15.object({ analysis: analysisSchema, digest: z15.string() });
|
|
5082
5242
|
baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
|
|
5083
5243
|
state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
|
|
5084
5244
|
)));
|
|
@@ -5086,7 +5246,7 @@ var init_runtime = __esm({
|
|
|
5086
5246
|
});
|
|
5087
5247
|
|
|
5088
5248
|
// src/automation/hook-root.ts
|
|
5089
|
-
import
|
|
5249
|
+
import fs21 from "fs/promises";
|
|
5090
5250
|
import path28 from "path";
|
|
5091
5251
|
var init_hook_root = __esm({
|
|
5092
5252
|
"src/automation/hook-root.ts"() {
|
|
@@ -5096,7 +5256,7 @@ var init_hook_root = __esm({
|
|
|
5096
5256
|
});
|
|
5097
5257
|
|
|
5098
5258
|
// src/automation/adapters.ts
|
|
5099
|
-
import { z as
|
|
5259
|
+
import { z as z16 } from "zod";
|
|
5100
5260
|
function managedHookCommand(host, platform = process.platform) {
|
|
5101
5261
|
const command = `mason --setup-host ${host} auto hook --host ${host}`;
|
|
5102
5262
|
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`;
|
|
@@ -5107,7 +5267,7 @@ function knownManagedHookCommands(host) {
|
|
|
5107
5267
|
function compatibleManagedHookCommands(host, platform = process.platform) {
|
|
5108
5268
|
return [managedHookCommand(host, platform), `mason --setup-host ${host} auto hook --host ${host}`];
|
|
5109
5269
|
}
|
|
5110
|
-
function hookConfig(host, command = "npx --no-install --package mason-context mason
|
|
5270
|
+
function hookConfig(host, command = "npx --no-install --package mason-context mason auto") {
|
|
5111
5271
|
const handler = { type: "command", command: command === `mason --setup-host ${host} auto` ? managedHookCommand(host) : command + " hook --host " + host, timeout: 30 };
|
|
5112
5272
|
return { hooks: Object.fromEntries(HOOK_EVENTS.map((name) => [
|
|
5113
5273
|
name,
|
|
@@ -5118,19 +5278,21 @@ var inputSchema, HOOK_EVENTS;
|
|
|
5118
5278
|
var init_adapters = __esm({
|
|
5119
5279
|
"src/automation/adapters.ts"() {
|
|
5120
5280
|
"use strict";
|
|
5281
|
+
init_usefulness();
|
|
5121
5282
|
init_execution();
|
|
5122
5283
|
init_runtime();
|
|
5123
5284
|
init_store();
|
|
5124
5285
|
init_hook_root();
|
|
5125
|
-
inputSchema =
|
|
5126
|
-
cwd:
|
|
5127
|
-
session_id:
|
|
5128
|
-
hook_event_name:
|
|
5129
|
-
tool_name:
|
|
5130
|
-
tool_use_id:
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5286
|
+
inputSchema = z16.object({
|
|
5287
|
+
cwd: z16.string().min(1),
|
|
5288
|
+
session_id: z16.string().min(1).max(500),
|
|
5289
|
+
hook_event_name: z16.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
|
|
5290
|
+
tool_name: z16.string().optional(),
|
|
5291
|
+
tool_use_id: z16.string().max(500).optional(),
|
|
5292
|
+
tool_response: z16.unknown().optional(),
|
|
5293
|
+
tool_input: z16.unknown().optional(),
|
|
5294
|
+
stop_hook_active: z16.boolean().optional(),
|
|
5295
|
+
permission_mode: z16.string().optional()
|
|
5134
5296
|
});
|
|
5135
5297
|
HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"];
|
|
5136
5298
|
}
|
|
@@ -5146,7 +5308,7 @@ __export(ownership_exports, {
|
|
|
5146
5308
|
ownershipEdit: () => ownershipEdit,
|
|
5147
5309
|
ownershipSchema: () => ownershipSchema
|
|
5148
5310
|
});
|
|
5149
|
-
import { z as
|
|
5311
|
+
import { z as z17 } from "zod";
|
|
5150
5312
|
import { parse } from "smol-toml";
|
|
5151
5313
|
function blockSpan(text2, start, end) {
|
|
5152
5314
|
const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
|
|
@@ -5197,15 +5359,15 @@ var init_ownership = __esm({
|
|
|
5197
5359
|
["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
|
|
5198
5360
|
["# mason:mcp:start", "# mason:mcp:end"]
|
|
5199
5361
|
];
|
|
5200
|
-
fileSchema =
|
|
5201
|
-
created:
|
|
5202
|
-
initialHash:
|
|
5203
|
-
blocks:
|
|
5204
|
-
mcpHash:
|
|
5205
|
-
hookCommands:
|
|
5206
|
-
hooks:
|
|
5362
|
+
fileSchema = z17.object({
|
|
5363
|
+
created: z17.boolean(),
|
|
5364
|
+
initialHash: z17.string(),
|
|
5365
|
+
blocks: z17.record(z17.string()),
|
|
5366
|
+
mcpHash: z17.string().optional(),
|
|
5367
|
+
hookCommands: z17.array(z17.string()).optional(),
|
|
5368
|
+
hooks: z17.record(z17.array(z17.string())).optional()
|
|
5207
5369
|
});
|
|
5208
|
-
ownershipSchema =
|
|
5370
|
+
ownershipSchema = z17.object({ version: z17.literal(1), files: z17.record(fileSchema) });
|
|
5209
5371
|
}
|
|
5210
5372
|
});
|
|
5211
5373
|
|
|
@@ -5214,13 +5376,13 @@ var install_exports = {};
|
|
|
5214
5376
|
__export(install_exports, {
|
|
5215
5377
|
AUTOMATION_PATH: () => AUTOMATION_PATH,
|
|
5216
5378
|
automationConfigSchema: () => automationConfigSchema,
|
|
5217
|
-
configPath: () =>
|
|
5379
|
+
configPath: () => configPath2,
|
|
5218
5380
|
installAutomation: () => installAutomation,
|
|
5219
5381
|
installedAutomation: () => installedAutomation,
|
|
5220
5382
|
planAutomationInstall: () => planAutomationInstall,
|
|
5221
5383
|
recordSchema: () => recordSchema2
|
|
5222
5384
|
});
|
|
5223
|
-
import { z as
|
|
5385
|
+
import { z as z18 } from "zod";
|
|
5224
5386
|
async function installationRecord(root) {
|
|
5225
5387
|
return recordSchema2.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
|
|
5226
5388
|
}
|
|
@@ -5229,7 +5391,7 @@ async function installAutomation(dir, host, command) {
|
|
|
5229
5391
|
return withLock(ws.root, ".mason/reports/setup-lock", () => installLocked(ws.root, host, command));
|
|
5230
5392
|
}
|
|
5231
5393
|
async function planAutomationInstall(root, host, command) {
|
|
5232
|
-
const file =
|
|
5394
|
+
const file = configPath2(host);
|
|
5233
5395
|
const before = await readText(root, file);
|
|
5234
5396
|
const existing = automationConfigSchema.parse(JSON.parse(before ?? "{}"));
|
|
5235
5397
|
const record = await installationRecord(root);
|
|
@@ -5269,7 +5431,7 @@ async function installLocked(root, host, command) {
|
|
|
5269
5431
|
status: "configured",
|
|
5270
5432
|
command: newCommand,
|
|
5271
5433
|
events: HOOK_EVENTS,
|
|
5272
|
-
next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason
|
|
5434
|
+
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.",
|
|
5273
5435
|
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."
|
|
5274
5436
|
};
|
|
5275
5437
|
}
|
|
@@ -5280,10 +5442,10 @@ async function installedAutomation(dir) {
|
|
|
5280
5442
|
for (const host of ["claude", "codex"]) {
|
|
5281
5443
|
const expected = record.hosts[host];
|
|
5282
5444
|
if (!expected) continue;
|
|
5283
|
-
const current = automationConfigSchema.parse(await readStoreJson(ws.root,
|
|
5445
|
+
const current = automationConfigSchema.parse(await readStoreJson(ws.root, configPath2(host)) ?? {});
|
|
5284
5446
|
const configuredEvents = HOOK_EVENTS.filter((event) => current.hooks?.[event]?.some((group) => group.hooks.some((handler) => handler.type === "command" && handler.command === expected.command)));
|
|
5285
5447
|
result[host] = {
|
|
5286
|
-
configPath:
|
|
5448
|
+
configPath: configPath2(host),
|
|
5287
5449
|
configuredEvents,
|
|
5288
5450
|
status: current.disableAllHooks === true ? "disabled" : configuredEvents.length === HOOK_EVENTS.length ? "configured" : "incomplete",
|
|
5289
5451
|
runtime: "Host version, trust, policy, and tool coverage still determine execution; inspect observed events."
|
|
@@ -5291,7 +5453,7 @@ async function installedAutomation(dir) {
|
|
|
5291
5453
|
}
|
|
5292
5454
|
return result;
|
|
5293
5455
|
}
|
|
5294
|
-
var groupSchema, automationConfigSchema, recordSchema2, AUTOMATION_PATH,
|
|
5456
|
+
var groupSchema, automationConfigSchema, recordSchema2, AUTOMATION_PATH, configPath2;
|
|
5295
5457
|
var init_install = __esm({
|
|
5296
5458
|
"src/automation/install.ts"() {
|
|
5297
5459
|
"use strict";
|
|
@@ -5301,11 +5463,11 @@ var init_install = __esm({
|
|
|
5301
5463
|
init_store();
|
|
5302
5464
|
init_files2();
|
|
5303
5465
|
init_ownership();
|
|
5304
|
-
groupSchema =
|
|
5305
|
-
automationConfigSchema =
|
|
5306
|
-
recordSchema2 =
|
|
5466
|
+
groupSchema = z18.object({ hooks: z18.array(z18.object({ type: z18.string(), command: z18.string().optional() }).passthrough()) }).passthrough();
|
|
5467
|
+
automationConfigSchema = z18.object({ hooks: z18.record(z18.array(groupSchema)).optional() }).passthrough();
|
|
5468
|
+
recordSchema2 = z18.object({ version: z18.literal(1), hosts: z18.record(z18.object({ command: z18.string() })) });
|
|
5307
5469
|
AUTOMATION_PATH = ".mason/local/automation.json";
|
|
5308
|
-
|
|
5470
|
+
configPath2 = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
|
|
5309
5471
|
}
|
|
5310
5472
|
});
|
|
5311
5473
|
|
|
@@ -5321,7 +5483,7 @@ __export(config_exports, {
|
|
|
5321
5483
|
});
|
|
5322
5484
|
import { isDeepStrictEqual } from "util";
|
|
5323
5485
|
import { parse as parse2, stringify } from "smol-toml";
|
|
5324
|
-
import { z as
|
|
5486
|
+
import { z as z19 } from "zod";
|
|
5325
5487
|
async function instructionEdits(root, host) {
|
|
5326
5488
|
const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
|
|
5327
5489
|
const existing = found.find((f) => f.text !== null);
|
|
@@ -5380,7 +5542,7 @@ async function mcpEdit(root, host) {
|
|
|
5380
5542
|
return { path: file, before, after };
|
|
5381
5543
|
}
|
|
5382
5544
|
async function hookEdits(root, host) {
|
|
5383
|
-
const file =
|
|
5545
|
+
const file = configPath2(host), before = await readText(root, file);
|
|
5384
5546
|
const { planAutomationInstall: planAutomationInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
5385
5547
|
const plan = await planAutomationInstall2(root, host, hookCommand(host));
|
|
5386
5548
|
return [
|
|
@@ -5408,7 +5570,7 @@ async function inspectHostConfig(root, host, plannedMcp) {
|
|
|
5408
5570
|
const text2 = plannedMcp ?? await readText(root, mcpPath(host));
|
|
5409
5571
|
const config = host === "codex" ? parse2(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
|
|
5410
5572
|
const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
|
|
5411
|
-
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root,
|
|
5573
|
+
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath2(host)) ?? "{}"));
|
|
5412
5574
|
const expected = hookConfig(host, hookCommand(host));
|
|
5413
5575
|
const compatible = compatibleManagedHookCommands(host);
|
|
5414
5576
|
const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
|
|
@@ -5432,7 +5594,7 @@ var init_config = __esm({
|
|
|
5432
5594
|
init_install();
|
|
5433
5595
|
init_files2();
|
|
5434
5596
|
init_launcher();
|
|
5435
|
-
object =
|
|
5597
|
+
object = z19.record(z19.unknown());
|
|
5436
5598
|
mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
5437
5599
|
TOML_START = "# mason:mcp:start";
|
|
5438
5600
|
TOML_END = "# mason:mcp:end";
|
|
@@ -5442,7 +5604,7 @@ var init_config = __esm({
|
|
|
5442
5604
|
});
|
|
5443
5605
|
|
|
5444
5606
|
// src/mcp/init.ts
|
|
5445
|
-
import { z as
|
|
5607
|
+
import { z as z20 } from "zod";
|
|
5446
5608
|
async function loadProjectMarker(rootDir) {
|
|
5447
5609
|
const raw = await readStoreJson(rootDir, ".mason/local/project.json");
|
|
5448
5610
|
if (raw === null) return null;
|
|
@@ -5450,8 +5612,8 @@ async function loadProjectMarker(rootDir) {
|
|
|
5450
5612
|
return { ...marker, features: await loadProjectFeatures(rootDir) };
|
|
5451
5613
|
}
|
|
5452
5614
|
async function loadProjectFeatures(rootDir) {
|
|
5453
|
-
const config =
|
|
5454
|
-
return
|
|
5615
|
+
const config = z20.record(z20.unknown()).parse(await readStoreJson(rootDir, ".mason/config.json") ?? {});
|
|
5616
|
+
return z20.object({ confluence: z20.boolean().optional() }).passthrough().parse(config.features ?? {});
|
|
5455
5617
|
}
|
|
5456
5618
|
async function saveProjectMarker(rootDir, marker) {
|
|
5457
5619
|
const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
@@ -5462,10 +5624,10 @@ async function projectMarkerEdits(rootDir, marker) {
|
|
|
5462
5624
|
const file = ".mason/local/project.json";
|
|
5463
5625
|
const edits = [{ path: file, before: await readText(rootDir, file), after: JSON.stringify(local, null, 2) + "\n" }];
|
|
5464
5626
|
if (features && Object.keys(features).length) {
|
|
5465
|
-
const
|
|
5466
|
-
const config =
|
|
5467
|
-
config.features = { ...
|
|
5468
|
-
edits.push({ path:
|
|
5627
|
+
const configPath3 = ".mason/config.json", before = await readText(rootDir, configPath3);
|
|
5628
|
+
const config = z20.record(z20.unknown()).parse(JSON.parse(before ?? "{}"));
|
|
5629
|
+
config.features = { ...z20.record(z20.unknown()).parse(config.features ?? {}), ...features };
|
|
5630
|
+
edits.push({ path: configPath3, before, after: JSON.stringify(config, null, 2) + "\n" });
|
|
5469
5631
|
}
|
|
5470
5632
|
return edits;
|
|
5471
5633
|
}
|
|
@@ -5478,10 +5640,10 @@ var init_init = __esm({
|
|
|
5478
5640
|
"use strict";
|
|
5479
5641
|
init_storage();
|
|
5480
5642
|
init_files2();
|
|
5481
|
-
markerSchema =
|
|
5482
|
-
version:
|
|
5483
|
-
initializedAt:
|
|
5484
|
-
features:
|
|
5643
|
+
markerSchema = z20.object({
|
|
5644
|
+
version: z20.literal(1),
|
|
5645
|
+
initializedAt: z20.string(),
|
|
5646
|
+
features: z20.object({ confluence: z20.boolean().optional() }).passthrough().optional()
|
|
5485
5647
|
}).passthrough();
|
|
5486
5648
|
CLAUDE_MD_SECTION = `<!-- mason:start -->
|
|
5487
5649
|
## Mason project knowledge
|
|
@@ -5503,13 +5665,13 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
|
|
|
5503
5665
|
|
|
5504
5666
|
- 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.
|
|
5505
5667
|
|
|
5506
|
-
- 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
|
|
5668
|
+
- 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.
|
|
5507
5669
|
|
|
5508
5670
|
Inspect source for what the retrieved context does not answer.
|
|
5509
5671
|
<!-- mason:end -->`;
|
|
5510
5672
|
ASSISTANT_SETUP = `UNIFIED SETUP
|
|
5511
|
-
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
|
|
5512
|
-
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
|
|
5673
|
+
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.
|
|
5674
|
+
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.
|
|
5513
5675
|
|
|
5514
5676
|
The managed project guidance is:
|
|
5515
5677
|
${CLAUDE_MD_SECTION}`;
|
|
@@ -5680,7 +5842,7 @@ var init_types2 = __esm({
|
|
|
5680
5842
|
});
|
|
5681
5843
|
|
|
5682
5844
|
// src/review/evidence/vitest.ts
|
|
5683
|
-
import { z as
|
|
5845
|
+
import { z as z21 } from "zod";
|
|
5684
5846
|
function parseVitest(raw, sourceRoot) {
|
|
5685
5847
|
const report = schema.parse(raw);
|
|
5686
5848
|
const findings = [], diagnostics = [];
|
|
@@ -5729,24 +5891,24 @@ var init_vitest = __esm({
|
|
|
5729
5891
|
"use strict";
|
|
5730
5892
|
init_paths2();
|
|
5731
5893
|
init_types2();
|
|
5732
|
-
count3 =
|
|
5733
|
-
schema =
|
|
5734
|
-
success:
|
|
5894
|
+
count3 = z21.number().int().nonnegative();
|
|
5895
|
+
schema = z21.object({
|
|
5896
|
+
success: z21.boolean(),
|
|
5735
5897
|
numTotalTests: count3,
|
|
5736
5898
|
numPassedTests: count3,
|
|
5737
5899
|
numFailedTests: count3,
|
|
5738
5900
|
numPendingTests: count3,
|
|
5739
5901
|
numTodoTests: count3,
|
|
5740
5902
|
numFailedTestSuites: count3,
|
|
5741
|
-
testResults:
|
|
5742
|
-
name:
|
|
5743
|
-
status:
|
|
5744
|
-
message:
|
|
5745
|
-
assertionResults:
|
|
5746
|
-
fullName:
|
|
5747
|
-
status:
|
|
5748
|
-
failureMessages:
|
|
5749
|
-
location:
|
|
5903
|
+
testResults: z21.array(z21.object({
|
|
5904
|
+
name: z21.string().min(1),
|
|
5905
|
+
status: z21.enum(["passed", "failed"]),
|
|
5906
|
+
message: z21.string().optional(),
|
|
5907
|
+
assertionResults: z21.array(z21.object({
|
|
5908
|
+
fullName: z21.string(),
|
|
5909
|
+
status: z21.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
|
|
5910
|
+
failureMessages: z21.array(z21.string()).nullable().optional(),
|
|
5911
|
+
location: z21.object({ line: count3, column: count3 }).nullable().optional()
|
|
5750
5912
|
}))
|
|
5751
5913
|
}))
|
|
5752
5914
|
});
|
|
@@ -5754,7 +5916,7 @@ var init_vitest = __esm({
|
|
|
5754
5916
|
});
|
|
5755
5917
|
|
|
5756
5918
|
// src/review/evidence/sarif.ts
|
|
5757
|
-
import { z as
|
|
5919
|
+
import { z as z22 } from "zod";
|
|
5758
5920
|
function parseSarif(raw, sourceRoot) {
|
|
5759
5921
|
const report = schema2.parse(raw), findings = [], diagnostics = [];
|
|
5760
5922
|
let active = 0, suppressed = 0, absent = 0, unresolved = 0;
|
|
@@ -5845,29 +6007,29 @@ var init_sarif = __esm({
|
|
|
5845
6007
|
"use strict";
|
|
5846
6008
|
init_paths2();
|
|
5847
6009
|
init_types2();
|
|
5848
|
-
index =
|
|
5849
|
-
artifact =
|
|
5850
|
-
message =
|
|
5851
|
-
level =
|
|
5852
|
-
location =
|
|
5853
|
-
schema2 =
|
|
5854
|
-
version:
|
|
5855
|
-
runs:
|
|
5856
|
-
tool:
|
|
5857
|
-
invocations:
|
|
5858
|
-
versionControlProvenance:
|
|
5859
|
-
originalUriBaseIds:
|
|
5860
|
-
artifacts:
|
|
5861
|
-
results:
|
|
5862
|
-
ruleId:
|
|
6010
|
+
index = z22.number().int().nonnegative();
|
|
6011
|
+
artifact = z22.object({ uri: z22.string().optional(), uriBaseId: z22.string().optional(), index: index.optional() });
|
|
6012
|
+
message = z22.object({ text: z22.string().optional(), markdown: z22.string().optional(), id: z22.string().optional(), arguments: z22.array(z22.string()).optional() });
|
|
6013
|
+
level = z22.enum(["error", "warning", "note", "none"]);
|
|
6014
|
+
location = z22.object({ physicalLocation: z22.object({ artifactLocation: artifact.optional(), region: z22.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
|
|
6015
|
+
schema2 = z22.object({
|
|
6016
|
+
version: z22.literal("2.1.0"),
|
|
6017
|
+
runs: z22.array(z22.object({
|
|
6018
|
+
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() }) }),
|
|
6019
|
+
invocations: z22.array(z22.object({ executionSuccessful: z22.boolean(), commandLine: z22.string().optional(), toolExecutionNotifications: z22.array(z22.object({ level: level.optional(), message })).optional() })).optional(),
|
|
6020
|
+
versionControlProvenance: z22.array(z22.object({ revisionId: z22.string().optional() })).optional(),
|
|
6021
|
+
originalUriBaseIds: z22.record(artifact).optional(),
|
|
6022
|
+
artifacts: z22.array(z22.object({ location: artifact.optional() })).optional(),
|
|
6023
|
+
results: z22.array(z22.object({
|
|
6024
|
+
ruleId: z22.string().optional(),
|
|
5863
6025
|
ruleIndex: index.optional(),
|
|
5864
6026
|
message,
|
|
5865
6027
|
level: level.optional(),
|
|
5866
|
-
kind:
|
|
5867
|
-
baselineState:
|
|
5868
|
-
suppressions:
|
|
5869
|
-
locations:
|
|
5870
|
-
relatedLocations:
|
|
6028
|
+
kind: z22.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
|
|
6029
|
+
baselineState: z22.enum(["new", "unchanged", "updated", "absent"]).optional(),
|
|
6030
|
+
suppressions: z22.array(z22.object({ kind: z22.enum(["inSource", "external"]), status: z22.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
|
|
6031
|
+
locations: z22.array(location).optional(),
|
|
6032
|
+
relatedLocations: z22.array(location).optional()
|
|
5871
6033
|
})).optional()
|
|
5872
6034
|
}))
|
|
5873
6035
|
});
|
|
@@ -5875,7 +6037,7 @@ var init_sarif = __esm({
|
|
|
5875
6037
|
});
|
|
5876
6038
|
|
|
5877
6039
|
// src/review/evidence/check.ts
|
|
5878
|
-
import { z as
|
|
6040
|
+
import { z as z23 } from "zod";
|
|
5879
6041
|
function parseCheck(raw, sourceRoot) {
|
|
5880
6042
|
const report = schema3.parse(raw);
|
|
5881
6043
|
const diagnostics = [], findings = [];
|
|
@@ -5915,17 +6077,17 @@ var init_check = __esm({
|
|
|
5915
6077
|
"use strict";
|
|
5916
6078
|
init_paths2();
|
|
5917
6079
|
init_types2();
|
|
5918
|
-
schema3 =
|
|
5919
|
-
version:
|
|
5920
|
-
commit:
|
|
5921
|
-
results:
|
|
5922
|
-
name:
|
|
5923
|
-
status:
|
|
5924
|
-
message:
|
|
5925
|
-
locations:
|
|
5926
|
-
file:
|
|
5927
|
-
line:
|
|
5928
|
-
column:
|
|
6080
|
+
schema3 = z23.object({
|
|
6081
|
+
version: z23.literal(1),
|
|
6082
|
+
commit: z23.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
|
|
6083
|
+
results: z23.array(z23.object({
|
|
6084
|
+
name: z23.string().min(1).max(1e3),
|
|
6085
|
+
status: z23.enum(["passed", "failed", "skipped"]),
|
|
6086
|
+
message: z23.string().max(1e5).optional(),
|
|
6087
|
+
locations: z23.array(z23.object({
|
|
6088
|
+
file: z23.string().min(1).max(4e3),
|
|
6089
|
+
line: z23.number().int().positive().optional(),
|
|
6090
|
+
column: z23.number().int().positive().optional()
|
|
5929
6091
|
})).max(100).default([])
|
|
5930
6092
|
})).max(1e5)
|
|
5931
6093
|
});
|
|
@@ -5934,7 +6096,7 @@ var init_check = __esm({
|
|
|
5934
6096
|
|
|
5935
6097
|
// src/review/evidence.ts
|
|
5936
6098
|
import path29 from "path";
|
|
5937
|
-
import { z as
|
|
6099
|
+
import { z as z24 } from "zod";
|
|
5938
6100
|
function relativeArtifact(root, file) {
|
|
5939
6101
|
const relative = path29.isAbsolute(file) ? path29.relative(root, file) : file;
|
|
5940
6102
|
const normalized = normalizeRepoPath(relative);
|
|
@@ -5975,7 +6137,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
5975
6137
|
let manifest, raw;
|
|
5976
6138
|
try {
|
|
5977
6139
|
manifest = relativeArtifact(root, supplied);
|
|
5978
|
-
raw =
|
|
6140
|
+
raw = z24.object({ version: z24.literal(1), checks: z24.array(z24.unknown()) }).parse(await readStoreJson(root, manifest));
|
|
5979
6141
|
} catch (error) {
|
|
5980
6142
|
output.diagnostics.push(`${supplied}: ${String(error)}`);
|
|
5981
6143
|
continue;
|
|
@@ -6130,31 +6292,31 @@ var init_evidence2 = __esm({
|
|
|
6130
6292
|
init_sarif();
|
|
6131
6293
|
init_check();
|
|
6132
6294
|
init_types2();
|
|
6133
|
-
checkSchema2 =
|
|
6134
|
-
id:
|
|
6135
|
-
kind:
|
|
6136
|
-
tool:
|
|
6137
|
-
command:
|
|
6138
|
-
commit:
|
|
6139
|
-
workingTreeClean:
|
|
6140
|
-
source:
|
|
6141
|
-
sourceRoot:
|
|
6142
|
-
status:
|
|
6143
|
-
reason:
|
|
6144
|
-
exitCode:
|
|
6145
|
-
environment:
|
|
6146
|
-
toolVersion:
|
|
6147
|
-
runtime:
|
|
6148
|
-
platform:
|
|
6149
|
-
configuration:
|
|
6295
|
+
checkSchema2 = z24.object({
|
|
6296
|
+
id: z24.string().min(1).max(100),
|
|
6297
|
+
kind: z24.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
|
|
6298
|
+
tool: z24.string().min(1).max(200),
|
|
6299
|
+
command: z24.string().min(1).max(2e3),
|
|
6300
|
+
commit: z24.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
|
|
6301
|
+
workingTreeClean: z24.boolean().optional(),
|
|
6302
|
+
source: z24.string().max(2e3).optional(),
|
|
6303
|
+
sourceRoot: z24.string().min(1).max(4e3).optional(),
|
|
6304
|
+
status: z24.enum(["completed", "skipped", "unavailable"]).default("completed"),
|
|
6305
|
+
reason: z24.string().min(1).max(2e3).optional(),
|
|
6306
|
+
exitCode: z24.number().int().nullable().optional(),
|
|
6307
|
+
environment: z24.object({
|
|
6308
|
+
toolVersion: z24.string().min(1).max(200),
|
|
6309
|
+
runtime: z24.string().min(1).max(200).optional(),
|
|
6310
|
+
platform: z24.string().min(1).max(200).optional(),
|
|
6311
|
+
configuration: z24.array(z24.string().min(1).max(4e3)).max(100).optional()
|
|
6150
6312
|
}).optional(),
|
|
6151
|
-
report:
|
|
6313
|
+
report: z24.object({ format: z24.enum(["vitest-json", "sarif", "mason-check-json"]), path: z24.string().min(1).max(4e3) }).optional()
|
|
6152
6314
|
});
|
|
6153
6315
|
}
|
|
6154
6316
|
});
|
|
6155
6317
|
|
|
6156
6318
|
// src/review/review.ts
|
|
6157
|
-
import
|
|
6319
|
+
import fs22 from "fs/promises";
|
|
6158
6320
|
import path30 from "path";
|
|
6159
6321
|
import { execFile as execFile7 } from "child_process";
|
|
6160
6322
|
import { promisify as promisify7 } from "util";
|
|
@@ -6234,7 +6396,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
6234
6396
|
analyzed,
|
|
6235
6397
|
async (relPath) => {
|
|
6236
6398
|
try {
|
|
6237
|
-
await
|
|
6399
|
+
await fs22.access(path30.join(resolvedRoot, relPath));
|
|
6238
6400
|
return true;
|
|
6239
6401
|
} catch {
|
|
6240
6402
|
return false;
|
|
@@ -6710,12 +6872,12 @@ __export(assemble_exports, {
|
|
|
6710
6872
|
assembleContext: () => assembleContext
|
|
6711
6873
|
});
|
|
6712
6874
|
import path33 from "path";
|
|
6713
|
-
import
|
|
6875
|
+
import fs23 from "fs/promises";
|
|
6714
6876
|
async function collectImpact(root, candidates) {
|
|
6715
6877
|
const targets2 = /* @__PURE__ */ new Set();
|
|
6716
6878
|
let sourceFiles;
|
|
6717
6879
|
for (const candidate of sanitizeRepoPaths(candidates)) {
|
|
6718
|
-
const stat = await
|
|
6880
|
+
const stat = await fs23.stat(path33.join(root, candidate)).catch(() => null);
|
|
6719
6881
|
if (stat?.isDirectory()) {
|
|
6720
6882
|
sourceFiles ??= await (await createFileAccess(root)).list();
|
|
6721
6883
|
for (const file of sourceFiles) {
|
|
@@ -6937,13 +7099,13 @@ __export(observations_exports, {
|
|
|
6937
7099
|
observeActivation: () => observeActivation,
|
|
6938
7100
|
readObservation: () => readObservation
|
|
6939
7101
|
});
|
|
6940
|
-
import
|
|
6941
|
-
import { z as
|
|
6942
|
-
function observationPath(
|
|
6943
|
-
return `${
|
|
7102
|
+
import fs24 from "fs/promises";
|
|
7103
|
+
import { z as z25 } from "zod";
|
|
7104
|
+
function observationPath(directory2, host, revision, version = executingVersion()) {
|
|
7105
|
+
return `${directory2}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
6944
7106
|
}
|
|
6945
|
-
async function readObservation(root,
|
|
6946
|
-
const raw = await readStoreJson(root, observationPath(
|
|
7107
|
+
async function readObservation(root, directory2, host, revision, version = executingVersion()) {
|
|
7108
|
+
const raw = await readStoreJson(root, observationPath(directory2, host, revision, version));
|
|
6947
7109
|
if (raw === null) return null;
|
|
6948
7110
|
const record = observationSchema.parse(raw);
|
|
6949
7111
|
if (record.root !== root || record.host !== host || record.revision !== revision || record.masonVersion !== version) throw new Error("Activation receipt belongs to another installation.");
|
|
@@ -6960,12 +7122,12 @@ async function observeActivation(dir, event, options = {}) {
|
|
|
6960
7122
|
return inactiveSetupMessage();
|
|
6961
7123
|
}
|
|
6962
7124
|
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];
|
|
6963
|
-
const ws = capturedDirectory ? { root: await
|
|
6964
|
-
if (ws.root !== await
|
|
7125
|
+
const ws = capturedDirectory ? { root: await fs24.realpath(dir), directory: capturedDirectory } : await workspace(dir);
|
|
7126
|
+
if (ws.root !== await fs24.realpath(process.env.MASON_SETUP_ROOT)) return null;
|
|
6965
7127
|
const { setup } = await effectiveSetup(ws.root);
|
|
6966
7128
|
if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
|
|
6967
|
-
const
|
|
6968
|
-
await withLock(ws.root,
|
|
7129
|
+
const directory2 = ws.directory + "/activation";
|
|
7130
|
+
await withLock(ws.root, directory2, async () => {
|
|
6969
7131
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6970
7132
|
const record = await readObservation(ws.root, ws.directory, host, revision) ?? {
|
|
6971
7133
|
version: 1,
|
|
@@ -7009,19 +7171,19 @@ var init_observations = __esm({
|
|
|
7009
7171
|
init_storage();
|
|
7010
7172
|
init_model();
|
|
7011
7173
|
init_launcher();
|
|
7012
|
-
observationSchema =
|
|
7013
|
-
version:
|
|
7014
|
-
masonVersion:
|
|
7015
|
-
root:
|
|
7016
|
-
revision:
|
|
7017
|
-
host:
|
|
7018
|
-
contextCalls:
|
|
7019
|
-
lastContextAt:
|
|
7020
|
-
sessions:
|
|
7021
|
-
events:
|
|
7022
|
-
at:
|
|
7023
|
-
verificationStatus:
|
|
7024
|
-
reportPath:
|
|
7174
|
+
observationSchema = z25.object({
|
|
7175
|
+
version: z25.literal(1),
|
|
7176
|
+
masonVersion: z25.string(),
|
|
7177
|
+
root: z25.string(),
|
|
7178
|
+
revision: z25.string(),
|
|
7179
|
+
host: z25.enum(["codex", "claude"]),
|
|
7180
|
+
contextCalls: z25.number().int().nonnegative(),
|
|
7181
|
+
lastContextAt: z25.string().optional(),
|
|
7182
|
+
sessions: z25.record(z25.object({
|
|
7183
|
+
events: z25.array(z25.enum(events)),
|
|
7184
|
+
at: z25.string(),
|
|
7185
|
+
verificationStatus: z25.string().optional(),
|
|
7186
|
+
reportPath: z25.string().optional()
|
|
7025
7187
|
}))
|
|
7026
7188
|
});
|
|
7027
7189
|
}
|
|
@@ -7048,7 +7210,7 @@ async function setupStatus(dir) {
|
|
|
7048
7210
|
version: 1,
|
|
7049
7211
|
status: pending.length ? "incomplete" : "not-configured",
|
|
7050
7212
|
hosts: {},
|
|
7051
|
-
next: pending.length ? "Setup did not finish. Rerun mason
|
|
7213
|
+
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."
|
|
7052
7214
|
};
|
|
7053
7215
|
}
|
|
7054
7216
|
const hosts = {};
|
|
@@ -7082,7 +7244,7 @@ async function setupStatus(dir) {
|
|
|
7082
7244
|
if (!mcp || !hooks || !instructionsCurrent || !configured) pending.push("Rerun setup to reconcile project configuration; inspect any disabled host settings.");
|
|
7083
7245
|
if (!observation?.contextCalls) pending.push("Start a new assistant session and request task context through Mason's get_context tool.");
|
|
7084
7246
|
if (!complete) pending.push("Review/trust the host configuration, then complete a normal task to observe the full hook lifecycle.");
|
|
7085
|
-
if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason
|
|
7247
|
+
if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason check and inspect its diagnostics.");
|
|
7086
7248
|
const healthy = installed.available && mcp && hooks && instructionsCurrent && configured && automation.status !== "unavailable";
|
|
7087
7249
|
hosts[host] = {
|
|
7088
7250
|
status: healthy ? complete && observation?.contextCalls ? "active" : "pending" : "attention",
|
|
@@ -7216,6 +7378,11 @@ async function setupProject(dir, options = {}) {
|
|
|
7216
7378
|
options.progress?.step("Checking configuration and retained findings");
|
|
7217
7379
|
const checked = await automate(ws.root, { event: "turn_start" });
|
|
7218
7380
|
const configured = await inspectHostConfig(ws.root, host, void 0);
|
|
7381
|
+
const stats = await configureUsefulness(ws.root, true, { onlyIfUnset: true }).catch(() => ({
|
|
7382
|
+
enabled: null,
|
|
7383
|
+
storage: ".mason/local/usefulness",
|
|
7384
|
+
note: "Local stats unavailable. Inspect mason stats --json, or use mason stats --disable to reset the preference. Existing observations were retained."
|
|
7385
|
+
}));
|
|
7219
7386
|
await writeStoreJson(ws.root, receiptPath, {
|
|
7220
7387
|
version: 1,
|
|
7221
7388
|
host,
|
|
@@ -7238,6 +7405,7 @@ async function setupProject(dir, options = {}) {
|
|
|
7238
7405
|
findings,
|
|
7239
7406
|
reportPath: checked.report.reportPath,
|
|
7240
7407
|
activation: await setupStatus(ws.root),
|
|
7408
|
+
stats,
|
|
7241
7409
|
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."
|
|
7242
7410
|
};
|
|
7243
7411
|
});
|
|
@@ -7249,18 +7417,20 @@ function summarizeSetup(result) {
|
|
|
7249
7417
|
" MCP server and lifecycle hooks use mason from PATH.",
|
|
7250
7418
|
" Project instructions updated; original audit evidence retained.",
|
|
7251
7419
|
` ${result.changedFiles.length} files changed.`,
|
|
7420
|
+
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.",
|
|
7252
7421
|
`Initial audit: ${audit.status}${"counts" in audit ? `; ${audit.counts.issues} issues, ${audit.counts.advisories + audit.counts.suppressedAdvisories} advisories` : ""}.`,
|
|
7253
7422
|
..."issues" in audit ? audit.issues.slice(0, 3).map((f) => " " + f.message) : [],
|
|
7254
7423
|
`Original evidence: ${result.initialReportPath}`,
|
|
7255
7424
|
"Activation: " + result.activation.status + ".",
|
|
7256
7425
|
result.next,
|
|
7257
7426
|
"If the assistant cannot find mason, restart the desktop app so it picks up PATH.",
|
|
7258
|
-
"After the task finishes, run mason
|
|
7427
|
+
"After the task finishes, run mason status. Configuration alone does not establish activation."
|
|
7259
7428
|
].join("\n");
|
|
7260
7429
|
}
|
|
7261
7430
|
var init_setup = __esm({
|
|
7262
7431
|
"src/setup/setup.ts"() {
|
|
7263
7432
|
"use strict";
|
|
7433
|
+
init_usefulness();
|
|
7264
7434
|
init_evidence();
|
|
7265
7435
|
init_store();
|
|
7266
7436
|
init_runtime();
|
|
@@ -7398,7 +7568,7 @@ __export(config_exports2, {
|
|
|
7398
7568
|
saveConfluenceConfig: () => saveConfluenceConfig,
|
|
7399
7569
|
validateProvider: () => validateProvider
|
|
7400
7570
|
});
|
|
7401
|
-
import
|
|
7571
|
+
import fs25 from "fs/promises";
|
|
7402
7572
|
import path34 from "path";
|
|
7403
7573
|
import os3 from "os";
|
|
7404
7574
|
import { execFile as execFile9 } from "child_process";
|
|
@@ -7411,15 +7581,15 @@ function configFile() {
|
|
|
7411
7581
|
}
|
|
7412
7582
|
async function loadConfig() {
|
|
7413
7583
|
try {
|
|
7414
|
-
const raw = await
|
|
7584
|
+
const raw = await fs25.readFile(configFile(), "utf-8");
|
|
7415
7585
|
return JSON.parse(raw);
|
|
7416
7586
|
} catch {
|
|
7417
7587
|
return null;
|
|
7418
7588
|
}
|
|
7419
7589
|
}
|
|
7420
7590
|
async function saveConfig(config) {
|
|
7421
|
-
await
|
|
7422
|
-
await
|
|
7591
|
+
await fs25.mkdir(configDir(), { recursive: true });
|
|
7592
|
+
await fs25.writeFile(configFile(), JSON.stringify(config, null, 2), "utf-8");
|
|
7423
7593
|
}
|
|
7424
7594
|
function getDefaultModel(provider) {
|
|
7425
7595
|
return DEFAULT_MODELS[provider];
|
|
@@ -7579,7 +7749,7 @@ var init_renderer = __esm({
|
|
|
7579
7749
|
});
|
|
7580
7750
|
|
|
7581
7751
|
// src/confluence/diff.ts
|
|
7582
|
-
import
|
|
7752
|
+
import fs26 from "fs/promises";
|
|
7583
7753
|
import path35 from "path";
|
|
7584
7754
|
import { createHash as createHash6 } from "crypto";
|
|
7585
7755
|
function hashDescription(description) {
|
|
@@ -7593,7 +7763,7 @@ function syncStatePath(rootDir) {
|
|
|
7593
7763
|
}
|
|
7594
7764
|
async function loadSyncState(rootDir) {
|
|
7595
7765
|
try {
|
|
7596
|
-
const raw = await
|
|
7766
|
+
const raw = await fs26.readFile(syncStatePath(rootDir), "utf-8");
|
|
7597
7767
|
const parsed = JSON.parse(raw);
|
|
7598
7768
|
if (parsed.version !== 2) return null;
|
|
7599
7769
|
return parsed;
|
|
@@ -7602,8 +7772,8 @@ async function loadSyncState(rootDir) {
|
|
|
7602
7772
|
}
|
|
7603
7773
|
}
|
|
7604
7774
|
async function saveSyncState(rootDir, state) {
|
|
7605
|
-
await
|
|
7606
|
-
await
|
|
7775
|
+
await fs26.mkdir(syncStateDir(rootDir), { recursive: true });
|
|
7776
|
+
await fs26.writeFile(
|
|
7607
7777
|
syncStatePath(rootDir),
|
|
7608
7778
|
JSON.stringify(state, null, 2),
|
|
7609
7779
|
"utf-8"
|
|
@@ -8156,12 +8326,12 @@ init_advisory_review();
|
|
|
8156
8326
|
init_decisions();
|
|
8157
8327
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8158
8328
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8159
|
-
import { z as
|
|
8329
|
+
import { z as z26 } from "zod";
|
|
8160
8330
|
|
|
8161
8331
|
// src/mcp/tools.ts
|
|
8162
8332
|
init_repair();
|
|
8163
8333
|
init_advisory_review();
|
|
8164
|
-
import
|
|
8334
|
+
import fs27 from "fs/promises";
|
|
8165
8335
|
import path36 from "path";
|
|
8166
8336
|
import { execFile as execFile11 } from "child_process";
|
|
8167
8337
|
import { promisify as promisify11 } from "util";
|
|
@@ -9135,7 +9305,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
9135
9305
|
const present = [];
|
|
9136
9306
|
for (const file of buildFiles) {
|
|
9137
9307
|
try {
|
|
9138
|
-
await
|
|
9308
|
+
await fs27.access(path36.join(rootDir, file));
|
|
9139
9309
|
present.push(file);
|
|
9140
9310
|
} catch {
|
|
9141
9311
|
}
|
|
@@ -10027,7 +10197,7 @@ function createMcpServer() {
|
|
|
10027
10197
|
const server = new McpServer(
|
|
10028
10198
|
{
|
|
10029
10199
|
name: "mason",
|
|
10030
|
-
version: "0.
|
|
10200
|
+
version: "0.20.2"
|
|
10031
10201
|
},
|
|
10032
10202
|
{
|
|
10033
10203
|
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.'
|
|
@@ -10037,11 +10207,11 @@ function createMcpServer() {
|
|
|
10037
10207
|
"mason_init",
|
|
10038
10208
|
"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.",
|
|
10039
10209
|
{
|
|
10040
|
-
dir:
|
|
10041
|
-
mode:
|
|
10042
|
-
host:
|
|
10043
|
-
base:
|
|
10044
|
-
evidence:
|
|
10210
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10211
|
+
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."),
|
|
10212
|
+
host: z26.enum(["codex", "claude"]).optional().describe("Assistant to configure in setup mode; inferred only when unambiguous."),
|
|
10213
|
+
base: z26.string().optional().describe("Git ref for committed-diff review. Defaults to the first available main branch ref."),
|
|
10214
|
+
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.")
|
|
10045
10215
|
},
|
|
10046
10216
|
async ({ dir, mode, host, base, evidence }) => {
|
|
10047
10217
|
const result = await masonInit(dir, { mode, host, base, evidence });
|
|
@@ -10052,8 +10222,8 @@ function createMcpServer() {
|
|
|
10052
10222
|
"mason_automation",
|
|
10053
10223
|
"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.",
|
|
10054
10224
|
{
|
|
10055
|
-
dir:
|
|
10056
|
-
action:
|
|
10225
|
+
dir: z26.string().describe("Absolute path to the project directory"),
|
|
10226
|
+
action: z26.enum(["status", "check"]).describe("Inspect configuration and receipts, or capture/resume and verify original audit evidence.")
|
|
10057
10227
|
},
|
|
10058
10228
|
async ({ dir, action }) => ({ content: [{ type: "text", text: await masonAutomation(dir, action) }] })
|
|
10059
10229
|
);
|
|
@@ -10061,10 +10231,10 @@ function createMcpServer() {
|
|
|
10061
10231
|
"mason_repair",
|
|
10062
10232
|
"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.",
|
|
10063
10233
|
{
|
|
10064
|
-
dir:
|
|
10065
|
-
action:
|
|
10066
|
-
baselinePath:
|
|
10067
|
-
checks:
|
|
10234
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10235
|
+
action: z26.enum(["prepare", "verify"]),
|
|
10236
|
+
baselinePath: z26.string().optional().describe("Original baseline path returned by prepare; required for verify."),
|
|
10237
|
+
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.")
|
|
10068
10238
|
},
|
|
10069
10239
|
async ({ dir, action, baselinePath, checks }) => {
|
|
10070
10240
|
const result = await masonRepair(dir, { action, baselinePath, checks });
|
|
@@ -10074,15 +10244,15 @@ function createMcpServer() {
|
|
|
10074
10244
|
server.tool(
|
|
10075
10245
|
"review_advisory",
|
|
10076
10246
|
"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.",
|
|
10077
|
-
{ dir:
|
|
10247
|
+
{ dir: z26.string().describe("Absolute path to the project root"), ...advisoryReviewRequest.shape },
|
|
10078
10248
|
async ({ dir, ...request }) => ({ content: [{ type: "text", text: await masonReviewAdvisory(dir, request) }] })
|
|
10079
10249
|
);
|
|
10080
10250
|
server.tool(
|
|
10081
10251
|
"mason_complete_init",
|
|
10082
10252
|
"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.",
|
|
10083
10253
|
{
|
|
10084
|
-
dir:
|
|
10085
|
-
confluenceConfigured:
|
|
10254
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10255
|
+
confluenceConfigured: z26.boolean().optional().describe("Set the Confluence setup status; omit to preserve the existing value")
|
|
10086
10256
|
},
|
|
10087
10257
|
async ({ dir, confluenceConfigured }) => {
|
|
10088
10258
|
const result = await masonCompleteInit(dir, { confluenceConfigured });
|
|
@@ -10093,11 +10263,11 @@ function createMcpServer() {
|
|
|
10093
10263
|
"mason_set_confluence",
|
|
10094
10264
|
"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.",
|
|
10095
10265
|
{
|
|
10096
|
-
baseUrl:
|
|
10097
|
-
email:
|
|
10098
|
-
apiToken:
|
|
10099
|
-
spaceKey:
|
|
10100
|
-
parentPageId:
|
|
10266
|
+
baseUrl: z26.string().describe("Confluence base URL. Accepts `acme`, `acme.atlassian.net`, or `https://acme.atlassian.net` (normalized automatically)."),
|
|
10267
|
+
email: z26.string().describe("User's Atlassian account email"),
|
|
10268
|
+
apiToken: z26.string().describe("API token from id.atlassian.com/manage-profile/security/api-tokens"),
|
|
10269
|
+
spaceKey: z26.string().optional().describe("Confluence space key. Omit on the first call to list available spaces."),
|
|
10270
|
+
parentPageId: z26.string().optional().describe("Optional parent page ID under which Mason's index page is created")
|
|
10101
10271
|
},
|
|
10102
10272
|
async ({ baseUrl, email, apiToken, spaceKey, parentPageId }) => {
|
|
10103
10273
|
const result = await masonSetConfluence({
|
|
@@ -10114,7 +10284,7 @@ function createMcpServer() {
|
|
|
10114
10284
|
"full_analysis",
|
|
10115
10285
|
"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.",
|
|
10116
10286
|
{
|
|
10117
|
-
dir:
|
|
10287
|
+
dir: z26.string().describe("Absolute path to the project root directory")
|
|
10118
10288
|
},
|
|
10119
10289
|
async ({ dir }) => {
|
|
10120
10290
|
const result = await fullAnalysis(dir);
|
|
@@ -10127,7 +10297,7 @@ function createMcpServer() {
|
|
|
10127
10297
|
"analyze_project",
|
|
10128
10298
|
"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.",
|
|
10129
10299
|
{
|
|
10130
|
-
dir:
|
|
10300
|
+
dir: z26.string().describe("Absolute path to the project root directory")
|
|
10131
10301
|
},
|
|
10132
10302
|
async ({ dir }) => {
|
|
10133
10303
|
const result = await analyzeProject(dir);
|
|
@@ -10140,8 +10310,8 @@ function createMcpServer() {
|
|
|
10140
10310
|
"get_code_samples",
|
|
10141
10311
|
"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.",
|
|
10142
10312
|
{
|
|
10143
|
-
dir:
|
|
10144
|
-
count:
|
|
10313
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10314
|
+
count: z26.number().optional().default(15).describe("Maximum number of files to sample (default: 15)")
|
|
10145
10315
|
},
|
|
10146
10316
|
async ({ dir, count: count4 }) => {
|
|
10147
10317
|
const result = await getCodeSamples(dir, count4);
|
|
@@ -10154,7 +10324,7 @@ function createMcpServer() {
|
|
|
10154
10324
|
"get_snapshot",
|
|
10155
10325
|
"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.",
|
|
10156
10326
|
{
|
|
10157
|
-
dir:
|
|
10327
|
+
dir: z26.string().describe("Absolute path to the project root directory")
|
|
10158
10328
|
},
|
|
10159
10329
|
async ({ dir }) => {
|
|
10160
10330
|
const result = await getSnapshot(dir);
|
|
@@ -10167,9 +10337,9 @@ function createMcpServer() {
|
|
|
10167
10337
|
"get_context",
|
|
10168
10338
|
"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.",
|
|
10169
10339
|
{
|
|
10170
|
-
dir:
|
|
10171
|
-
task:
|
|
10172
|
-
files:
|
|
10340
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10341
|
+
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"),
|
|
10342
|
+
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.")
|
|
10173
10343
|
},
|
|
10174
10344
|
async ({ dir, task, files }) => {
|
|
10175
10345
|
const result = await getContext(dir, task, files);
|
|
@@ -10185,10 +10355,10 @@ function createMcpServer() {
|
|
|
10185
10355
|
"generate_snapshot_batch",
|
|
10186
10356
|
"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.",
|
|
10187
10357
|
{
|
|
10188
|
-
dir:
|
|
10189
|
-
offset:
|
|
10190
|
-
batchSize:
|
|
10191
|
-
files:
|
|
10358
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10359
|
+
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."),
|
|
10360
|
+
batchSize: z26.number().int().optional().describe("Files per batch. Defaults to 50."),
|
|
10361
|
+
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.")
|
|
10192
10362
|
},
|
|
10193
10363
|
async ({ dir, offset, batchSize, files }) => {
|
|
10194
10364
|
const result = await generateSnapshotBatch(dir, offset, batchSize, files);
|
|
@@ -10201,23 +10371,23 @@ function createMcpServer() {
|
|
|
10201
10371
|
"save_partial_snapshot",
|
|
10202
10372
|
"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.",
|
|
10203
10373
|
{
|
|
10204
|
-
dir:
|
|
10205
|
-
batchId:
|
|
10206
|
-
offset:
|
|
10207
|
-
features:
|
|
10208
|
-
|
|
10209
|
-
description:
|
|
10210
|
-
files:
|
|
10211
|
-
tests:
|
|
10212
|
-
type:
|
|
10374
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10375
|
+
batchId: z26.string().describe("The `batchId` returned by `generate_snapshot_batch`."),
|
|
10376
|
+
offset: z26.number().int().describe("The `offset` returned by `generate_snapshot_batch`. Used to order partials in the reduce step."),
|
|
10377
|
+
features: z26.record(
|
|
10378
|
+
z26.object({
|
|
10379
|
+
description: z26.string(),
|
|
10380
|
+
files: z26.array(z26.string()),
|
|
10381
|
+
tests: z26.array(z26.string()).optional(),
|
|
10382
|
+
type: z26.enum(["capability", "infrastructure"]).optional().describe(
|
|
10213
10383
|
'"capability" (user-facing functionality) or "infrastructure" (internal plumbing with no end user \u2014 DI/service wiring, config, logging, adapters). Defaults to "capability".'
|
|
10214
10384
|
)
|
|
10215
10385
|
})
|
|
10216
10386
|
).describe("Partial features for this batch only \u2014 files outside the batch will be added by other partials."),
|
|
10217
|
-
flows:
|
|
10218
|
-
|
|
10219
|
-
description:
|
|
10220
|
-
chain:
|
|
10387
|
+
flows: z26.record(
|
|
10388
|
+
z26.object({
|
|
10389
|
+
description: z26.string(),
|
|
10390
|
+
chain: z26.array(z26.string())
|
|
10221
10391
|
})
|
|
10222
10392
|
).describe("Partial flows whose entire chain is in this batch. Cross-batch flows are reconstructed in reduce.")
|
|
10223
10393
|
},
|
|
@@ -10232,7 +10402,7 @@ function createMcpServer() {
|
|
|
10232
10402
|
"reduce_snapshot",
|
|
10233
10403
|
"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).",
|
|
10234
10404
|
{
|
|
10235
|
-
dir:
|
|
10405
|
+
dir: z26.string().describe("Absolute path to the project root directory")
|
|
10236
10406
|
},
|
|
10237
10407
|
async ({ dir }) => {
|
|
10238
10408
|
const result = await reduceSnapshot(dir);
|
|
@@ -10245,25 +10415,25 @@ function createMcpServer() {
|
|
|
10245
10415
|
"save_snapshot",
|
|
10246
10416
|
"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.",
|
|
10247
10417
|
{
|
|
10248
|
-
dir:
|
|
10249
|
-
features:
|
|
10250
|
-
|
|
10251
|
-
description:
|
|
10252
|
-
files:
|
|
10253
|
-
tests:
|
|
10254
|
-
type:
|
|
10418
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10419
|
+
features: z26.record(
|
|
10420
|
+
z26.object({
|
|
10421
|
+
description: z26.string().describe("One-line description of the feature"),
|
|
10422
|
+
files: z26.array(z26.string()).describe("File paths that implement this feature"),
|
|
10423
|
+
tests: z26.array(z26.string()).optional().describe("Test file paths for this feature"),
|
|
10424
|
+
type: z26.enum(["capability", "infrastructure"]).optional().describe(
|
|
10255
10425
|
'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".'
|
|
10256
10426
|
)
|
|
10257
10427
|
})
|
|
10258
10428
|
).describe("Map of feature names to their implementing files"),
|
|
10259
|
-
flows:
|
|
10260
|
-
|
|
10261
|
-
description:
|
|
10262
|
-
chain:
|
|
10429
|
+
flows: z26.record(
|
|
10430
|
+
z26.object({
|
|
10431
|
+
description: z26.string().describe("One-line description of the flow"),
|
|
10432
|
+
chain: z26.array(z26.string()).describe("Ordered list of file paths showing data/call flow")
|
|
10263
10433
|
})
|
|
10264
10434
|
).describe("Map of flow names to ordered file chains"),
|
|
10265
|
-
removeFeatures:
|
|
10266
|
-
removeFlows:
|
|
10435
|
+
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."),
|
|
10436
|
+
removeFlows: z26.array(z26.string()).optional().describe("Flow names to delete from the existing map. Applied before merging; only meaningful on incremental saves.")
|
|
10267
10437
|
},
|
|
10268
10438
|
async ({ dir, features, flows, removeFeatures, removeFlows }) => {
|
|
10269
10439
|
const result = await saveSnapshotData(
|
|
@@ -10282,17 +10452,17 @@ function createMcpServer() {
|
|
|
10282
10452
|
"save_decision",
|
|
10283
10453
|
"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.",
|
|
10284
10454
|
{
|
|
10285
|
-
dir:
|
|
10455
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10286
10456
|
title: decisionTitleSchema.describe(`Short, specific headline (max ${TITLE_MAX_CHARS} characters). Generates a stable id on creation; revising the title keeps the existing id.`),
|
|
10287
10457
|
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.`),
|
|
10288
|
-
category:
|
|
10289
|
-
files:
|
|
10290
|
-
id:
|
|
10291
|
-
supersedes:
|
|
10458
|
+
category: z26.enum(["decision", "gotcha", "deprecation", "convention"]),
|
|
10459
|
+
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."),
|
|
10460
|
+
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."),
|
|
10461
|
+
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."),
|
|
10292
10462
|
owner: attributionSchema.shape.owner.describe("Responsible person or team, when known. Null clears it. Required for acceptance."),
|
|
10293
10463
|
sources: attributionSchema.shape.sources.describe("Known PR, issue, incident, discussion, or document references. Omit to preserve; [] clears. At least one is required for acceptance."),
|
|
10294
10464
|
actor: attributionSchema.shape.actor.describe("Known person or agent recording this revision. Omit if unknown; do not infer from Git identity."),
|
|
10295
|
-
force:
|
|
10465
|
+
force: z26.boolean().optional().describe("Save even when a near-duplicate was detected")
|
|
10296
10466
|
},
|
|
10297
10467
|
async ({ dir, title, body, category, files, id, supersedes, force, owner, sources, actor }) => {
|
|
10298
10468
|
const result = await saveDecision(dir, {
|
|
@@ -10314,12 +10484,12 @@ function createMcpServer() {
|
|
|
10314
10484
|
"review_decision",
|
|
10315
10485
|
"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.",
|
|
10316
10486
|
{
|
|
10317
|
-
dir:
|
|
10318
|
-
id:
|
|
10319
|
-
action:
|
|
10320
|
-
reviewer:
|
|
10321
|
-
note:
|
|
10322
|
-
reviewToken:
|
|
10487
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10488
|
+
id: z26.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Decision id from get_context or save_decision"),
|
|
10489
|
+
action: z26.enum(["prepare", "accept", "reaffirm", "retire"]).optional().default("prepare").describe("Prepare is read-only. Other actions record an explicitly authorized review."),
|
|
10490
|
+
reviewer: z26.string().trim().min(1).max(200).optional().describe("Identity of the actual reviewer; required for a verdict. Never invent one."),
|
|
10491
|
+
note: z26.string().trim().min(1).max(1500).optional().describe("Review rationale; required for a verdict. Cite evidence for the decision."),
|
|
10492
|
+
reviewToken: z26.string().regex(/^[a-f0-9]{64}$/).optional().describe("Token from the prepared review; rejects stale record or code revisions")
|
|
10323
10493
|
},
|
|
10324
10494
|
async ({ dir, ...input }) => {
|
|
10325
10495
|
const result = await reviewDecision2(dir, input);
|
|
@@ -10330,7 +10500,7 @@ function createMcpServer() {
|
|
|
10330
10500
|
"mason_check_drift",
|
|
10331
10501
|
"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.",
|
|
10332
10502
|
{
|
|
10333
|
-
dir:
|
|
10503
|
+
dir: z26.string().describe("Absolute path to the project root directory")
|
|
10334
10504
|
},
|
|
10335
10505
|
async ({ dir }) => {
|
|
10336
10506
|
const result = await checkDrift(dir);
|
|
@@ -10343,8 +10513,8 @@ function createMcpServer() {
|
|
|
10343
10513
|
"verify_snapshot",
|
|
10344
10514
|
"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.",
|
|
10345
10515
|
{
|
|
10346
|
-
dir:
|
|
10347
|
-
sample:
|
|
10516
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10517
|
+
sample: z26.number().int().optional().describe("Entries to sample (default 5)")
|
|
10348
10518
|
},
|
|
10349
10519
|
async ({ dir, sample }) => {
|
|
10350
10520
|
const result = await verifySnapshot(dir, sample);
|
|
@@ -10355,13 +10525,13 @@ function createMcpServer() {
|
|
|
10355
10525
|
"save_verification",
|
|
10356
10526
|
"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.",
|
|
10357
10527
|
{
|
|
10358
|
-
dir:
|
|
10359
|
-
verdicts:
|
|
10360
|
-
|
|
10361
|
-
kind:
|
|
10362
|
-
reviewToken:
|
|
10363
|
-
ok:
|
|
10364
|
-
note:
|
|
10528
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10529
|
+
verdicts: z26.record(
|
|
10530
|
+
z26.object({
|
|
10531
|
+
kind: z26.enum(["feature", "flow"]).optional().describe("Entry kind returned by verify_snapshot; required to record a verdict"),
|
|
10532
|
+
reviewToken: z26.string().optional().describe("Copy the reviewToken from verify_snapshot after inspecting its evidence; required to record a verdict"),
|
|
10533
|
+
ok: z26.boolean(),
|
|
10534
|
+
note: z26.string().optional().describe("One line on what's wrong \u2014 required when ok is false")
|
|
10365
10535
|
})
|
|
10366
10536
|
).describe("Entry name \u2192 verdict, exactly as returned by verify_snapshot")
|
|
10367
10537
|
},
|
|
@@ -10374,8 +10544,8 @@ function createMcpServer() {
|
|
|
10374
10544
|
"get_impact",
|
|
10375
10545
|
"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.",
|
|
10376
10546
|
{
|
|
10377
|
-
dir:
|
|
10378
|
-
files:
|
|
10547
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10548
|
+
files: z26.array(z26.string()).describe("File paths or names to analyze (e.g., ['WeatherRepository.kt'] or ['src/services/auth.ts'])")
|
|
10379
10549
|
},
|
|
10380
10550
|
async ({ dir, files }) => {
|
|
10381
10551
|
const result = await getImpact(dir, files);
|
|
@@ -10388,12 +10558,12 @@ function createMcpServer() {
|
|
|
10388
10558
|
"export_to_confluence",
|
|
10389
10559
|
"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.",
|
|
10390
10560
|
{
|
|
10391
|
-
dir:
|
|
10392
|
-
spaceKey:
|
|
10393
|
-
parentPageId:
|
|
10394
|
-
indexPageTitle:
|
|
10395
|
-
changelogPageTitle:
|
|
10396
|
-
featurePagePrefix:
|
|
10561
|
+
dir: z26.string().describe("Absolute path to the project root directory"),
|
|
10562
|
+
spaceKey: z26.string().optional().describe("Override the configured space key"),
|
|
10563
|
+
parentPageId: z26.string().optional().describe("Override the configured parent page ID"),
|
|
10564
|
+
indexPageTitle: z26.string().optional().describe("Title of the index page (default: 'Mason \u2014 System Map')"),
|
|
10565
|
+
changelogPageTitle: z26.string().optional().describe("Title of the changelog page (default: 'Mason \u2014 Changelog')"),
|
|
10566
|
+
featurePagePrefix: z26.string().optional().describe("Prefix for each feature page title (default: 'Feature: ')")
|
|
10397
10567
|
},
|
|
10398
10568
|
async ({ dir, spaceKey, parentPageId, indexPageTitle, changelogPageTitle, featurePagePrefix }) => {
|
|
10399
10569
|
const result = await exportToConfluenceTool(dir, {
|
|
@@ -10418,32 +10588,32 @@ async function startMcpServer(onClose) {
|
|
|
10418
10588
|
// src/distribution/updates.ts
|
|
10419
10589
|
init_store();
|
|
10420
10590
|
init_storage();
|
|
10421
|
-
import
|
|
10591
|
+
import fs34 from "fs/promises";
|
|
10422
10592
|
import path42 from "path";
|
|
10423
10593
|
import { spawn as spawn3 } from "child_process";
|
|
10424
10594
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
10425
|
-
import { z as
|
|
10595
|
+
import { z as z32 } from "zod";
|
|
10426
10596
|
|
|
10427
10597
|
// src/distribution/state.ts
|
|
10428
10598
|
init_storage();
|
|
10429
10599
|
import os5 from "os";
|
|
10430
10600
|
import path38 from "path";
|
|
10431
10601
|
import { fileURLToPath } from "url";
|
|
10432
|
-
import
|
|
10433
|
-
import { z as
|
|
10602
|
+
import fs29 from "fs/promises";
|
|
10603
|
+
import { z as z28 } from "zod";
|
|
10434
10604
|
|
|
10435
10605
|
// src/distribution/path.ts
|
|
10436
|
-
import
|
|
10606
|
+
import fs28 from "fs/promises";
|
|
10437
10607
|
import os4 from "os";
|
|
10438
10608
|
import path37 from "path";
|
|
10439
10609
|
import { execFile as execFile12 } from "child_process";
|
|
10440
10610
|
import { promisify as promisify12 } from "util";
|
|
10441
10611
|
import { createHash as createHash7, randomUUID as randomUUID7 } from "crypto";
|
|
10442
|
-
import { z as
|
|
10612
|
+
import { z as z27 } from "zod";
|
|
10443
10613
|
var execute2 = promisify12(execFile12);
|
|
10444
|
-
var pathChangeSchema =
|
|
10445
|
-
|
|
10446
|
-
|
|
10614
|
+
var pathChangeSchema = z27.discriminatedUnion("kind", [
|
|
10615
|
+
z27.object({ kind: z27.literal("profile"), file: z27.string(), block: z27.string(), created: z27.boolean() }),
|
|
10616
|
+
z27.object({ kind: z27.literal("windows"), entry: z27.string(), created: z27.boolean() })
|
|
10447
10617
|
]);
|
|
10448
10618
|
var quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
10449
10619
|
async function configurePath(bin, previous, save, options = {}) {
|
|
@@ -10480,7 +10650,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
10480
10650
|
else {
|
|
10481
10651
|
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path37.join(userHome, name));
|
|
10482
10652
|
let login = candidates[2];
|
|
10483
|
-
for (const file of candidates) if (await
|
|
10653
|
+
for (const file of candidates) if (await fs28.lstat(file).then(() => true, (error) => {
|
|
10484
10654
|
if (error.code === "ENOENT") return false;
|
|
10485
10655
|
throw error;
|
|
10486
10656
|
})) {
|
|
@@ -10491,12 +10661,12 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
10491
10661
|
}
|
|
10492
10662
|
const marker = `# >>> mason PATH ${createHash7("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
10493
10663
|
for (const profile of profiles2) {
|
|
10494
|
-
const stat = await
|
|
10664
|
+
const stat = await fs28.lstat(profile).catch((error) => {
|
|
10495
10665
|
if (error.code === "ENOENT") return null;
|
|
10496
10666
|
throw error;
|
|
10497
10667
|
});
|
|
10498
|
-
const file = stat?.isSymbolicLink() ? await
|
|
10499
|
-
const original = await
|
|
10668
|
+
const file = stat?.isSymbolicLink() ? await fs28.realpath(profile) : path37.resolve(profile);
|
|
10669
|
+
const original = await fs28.readFile(file).catch((error) => {
|
|
10500
10670
|
if (error.code === "ENOENT" && !stat) return null;
|
|
10501
10671
|
throw error;
|
|
10502
10672
|
});
|
|
@@ -10511,8 +10681,8 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
10511
10681
|
changes.push({ kind: "profile", file, block, created: original === null });
|
|
10512
10682
|
await save(changes);
|
|
10513
10683
|
}
|
|
10514
|
-
await
|
|
10515
|
-
const handle = await
|
|
10684
|
+
await fs28.mkdir(path37.dirname(file), { recursive: true });
|
|
10685
|
+
const handle = await fs28.open(file, original === null ? "ax+" : "a+", 384);
|
|
10516
10686
|
try {
|
|
10517
10687
|
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
10518
10688
|
await handle.writeFile(block);
|
|
@@ -10563,32 +10733,32 @@ try {
|
|
|
10563
10733
|
}
|
|
10564
10734
|
|
|
10565
10735
|
// src/distribution/state.ts
|
|
10566
|
-
var releaseVersion =
|
|
10567
|
-
var bundleId =
|
|
10568
|
-
var updatePolicySchema =
|
|
10569
|
-
enabled:
|
|
10570
|
-
revision:
|
|
10736
|
+
var releaseVersion = z28.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
|
|
10737
|
+
var bundleId = z28.string().regex(/^[a-f0-9]{24}$/);
|
|
10738
|
+
var updatePolicySchema = z28.object({
|
|
10739
|
+
enabled: z28.boolean(),
|
|
10740
|
+
revision: z28.string(),
|
|
10571
10741
|
pinnedVersion: releaseVersion.optional(),
|
|
10572
|
-
mirror:
|
|
10742
|
+
mirror: z28.string().optional()
|
|
10573
10743
|
});
|
|
10574
|
-
var recordSchema3 =
|
|
10575
|
-
format:
|
|
10576
|
-
home:
|
|
10577
|
-
bin:
|
|
10744
|
+
var recordSchema3 = z28.object({
|
|
10745
|
+
format: z28.literal(1),
|
|
10746
|
+
home: z28.string(),
|
|
10747
|
+
bin: z28.string(),
|
|
10578
10748
|
current: bundleId,
|
|
10579
10749
|
version: releaseVersion,
|
|
10580
|
-
launchers:
|
|
10581
|
-
previousLaunchers:
|
|
10582
|
-
pathChanges:
|
|
10750
|
+
launchers: z28.record(z28.string()),
|
|
10751
|
+
previousLaunchers: z28.record(z28.string()).optional(),
|
|
10752
|
+
pathChanges: z28.array(pathChangeSchema).optional(),
|
|
10583
10753
|
updates: updatePolicySchema.optional(),
|
|
10584
|
-
previous:
|
|
10585
|
-
pending:
|
|
10754
|
+
previous: z28.object({ id: bundleId, version: releaseVersion }).optional(),
|
|
10755
|
+
pending: z28.object({ id: bundleId, version: releaseVersion, policyRevision: z28.string() }).optional()
|
|
10586
10756
|
});
|
|
10587
10757
|
async function readInstallation(home) {
|
|
10588
10758
|
const raw = await readStoreJson(home, "install.json");
|
|
10589
10759
|
if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
10590
10760
|
const record = recordSchema3.parse(raw);
|
|
10591
|
-
if (record.home !== await
|
|
10761
|
+
if (record.home !== await fs29.realpath(home)) throw new Error("Invalid standalone installation receipt.");
|
|
10592
10762
|
return record;
|
|
10593
10763
|
}
|
|
10594
10764
|
function standaloneLocation() {
|
|
@@ -10620,22 +10790,22 @@ function newerStableVersion(candidate, installed) {
|
|
|
10620
10790
|
|
|
10621
10791
|
// src/distribution/bundle.ts
|
|
10622
10792
|
init_storage();
|
|
10623
|
-
import
|
|
10793
|
+
import fs30 from "fs/promises";
|
|
10624
10794
|
import path39 from "path";
|
|
10625
10795
|
import { createHash as createHash8 } from "crypto";
|
|
10626
|
-
import { z as
|
|
10796
|
+
import { z as z29 } from "zod";
|
|
10627
10797
|
var targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
10628
|
-
var digest2 =
|
|
10629
|
-
var bundleSchema =
|
|
10630
|
-
format:
|
|
10631
|
-
version:
|
|
10632
|
-
target:
|
|
10633
|
-
nodeVersion:
|
|
10634
|
-
files:
|
|
10798
|
+
var digest2 = z29.string().regex(/^[a-f0-9]{64}$/);
|
|
10799
|
+
var bundleSchema = z29.object({
|
|
10800
|
+
format: z29.literal(1),
|
|
10801
|
+
version: z29.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
10802
|
+
target: z29.enum(targets),
|
|
10803
|
+
nodeVersion: z29.string(),
|
|
10804
|
+
files: z29.record(digest2)
|
|
10635
10805
|
}).strict();
|
|
10636
10806
|
var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
|
|
10637
10807
|
async function readBundle(root) {
|
|
10638
|
-
const bytes = await
|
|
10808
|
+
const bytes = await fs30.readFile(await storePath(root, "bundle.json"));
|
|
10639
10809
|
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
10640
10810
|
const windows = manifest.target.startsWith("win32-");
|
|
10641
10811
|
for (const file of Object.keys(manifest.files)) {
|
|
@@ -10643,7 +10813,7 @@ async function readBundle(root) {
|
|
|
10643
10813
|
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
10644
10814
|
}
|
|
10645
10815
|
}
|
|
10646
|
-
for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-
|
|
10816
|
+
for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-mcp.js"]) {
|
|
10647
10817
|
if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
|
|
10648
10818
|
}
|
|
10649
10819
|
return { manifest, manifestHash: sha256(bytes) };
|
|
@@ -10654,7 +10824,7 @@ async function verifyBundle(root, expectedHash) {
|
|
|
10654
10824
|
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.");
|
|
10655
10825
|
const files = Object.keys(bundle.manifest.files);
|
|
10656
10826
|
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
10657
|
-
if (sha256(await
|
|
10827
|
+
if (sha256(await fs30.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
10658
10828
|
}));
|
|
10659
10829
|
return bundle;
|
|
10660
10830
|
}
|
|
@@ -10662,21 +10832,21 @@ async function verifyBundle(root, expectedHash) {
|
|
|
10662
10832
|
// src/distribution/runtime.ts
|
|
10663
10833
|
init_store();
|
|
10664
10834
|
init_storage();
|
|
10665
|
-
import
|
|
10835
|
+
import fs31 from "fs/promises";
|
|
10666
10836
|
import { unlinkSync } from "fs";
|
|
10667
10837
|
import os6 from "os";
|
|
10668
10838
|
import path40 from "path";
|
|
10669
10839
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
10670
|
-
import { z as
|
|
10840
|
+
import { z as z30 } from "zod";
|
|
10671
10841
|
|
|
10672
10842
|
// src/distribution/trust.ts
|
|
10673
10843
|
import tls from "tls";
|
|
10674
10844
|
|
|
10675
10845
|
// src/distribution/runtime.ts
|
|
10676
|
-
var leaseSchema =
|
|
10846
|
+
var leaseSchema = z30.object({ pid: z30.number().int().positive(), host: z30.string(), bundle: z30.string().regex(/^[a-f0-9]{24}$/) });
|
|
10677
10847
|
|
|
10678
10848
|
// src/distribution/install.ts
|
|
10679
|
-
import
|
|
10849
|
+
import fs33 from "fs/promises";
|
|
10680
10850
|
import os8 from "os";
|
|
10681
10851
|
import path41 from "path";
|
|
10682
10852
|
import { execFile as execFile13, spawn as spawn2 } from "child_process";
|
|
@@ -10687,14 +10857,14 @@ init_store();
|
|
|
10687
10857
|
|
|
10688
10858
|
// src/distribution/sessions.ts
|
|
10689
10859
|
init_storage();
|
|
10690
|
-
import
|
|
10860
|
+
import fs32 from "fs/promises";
|
|
10691
10861
|
import os7 from "os";
|
|
10692
10862
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
10693
|
-
import { z as
|
|
10694
|
-
var leaseSchema2 =
|
|
10863
|
+
import { z as z31 } from "zod";
|
|
10864
|
+
var leaseSchema2 = z31.object({ pid: z31.number().int().positive(), host: z31.string() });
|
|
10695
10865
|
async function hasRunningMcp(home) {
|
|
10696
|
-
const
|
|
10697
|
-
const files = await
|
|
10866
|
+
const directory2 = await storePath(home, "sessions");
|
|
10867
|
+
const files = await fs32.readdir(directory2).catch((error) => {
|
|
10698
10868
|
if (error.code === "ENOENT") return [];
|
|
10699
10869
|
throw error;
|
|
10700
10870
|
});
|
|
@@ -10712,7 +10882,7 @@ async function hasRunningMcp(home) {
|
|
|
10712
10882
|
} catch (error) {
|
|
10713
10883
|
if (error.code !== "ESRCH") return true;
|
|
10714
10884
|
}
|
|
10715
|
-
await
|
|
10885
|
+
await fs32.rm(await storePath(home, relative), { force: true });
|
|
10716
10886
|
} catch {
|
|
10717
10887
|
return true;
|
|
10718
10888
|
}
|
|
@@ -10723,7 +10893,7 @@ async function registerMcp(home) {
|
|
|
10723
10893
|
const name = `sessions/${randomUUID9()}.json`;
|
|
10724
10894
|
await writeStoreJson(home, name, { pid: process.pid, host: os7.hostname() });
|
|
10725
10895
|
return async () => {
|
|
10726
|
-
await
|
|
10896
|
+
await fs32.rm(await storePath(home, name), { force: true }).catch(() => {
|
|
10727
10897
|
});
|
|
10728
10898
|
};
|
|
10729
10899
|
}
|
|
@@ -10736,10 +10906,10 @@ var launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
|
|
|
10736
10906
|
async function replace(file, content2, mode = 384) {
|
|
10737
10907
|
const temp = file + ".tmp-" + randomUUID10();
|
|
10738
10908
|
try {
|
|
10739
|
-
await
|
|
10740
|
-
await
|
|
10909
|
+
await fs33.writeFile(temp, content2, { mode, flag: "wx" });
|
|
10910
|
+
await fs33.rename(temp, file);
|
|
10741
10911
|
} finally {
|
|
10742
|
-
await
|
|
10912
|
+
await fs33.rm(temp, { force: true });
|
|
10743
10913
|
}
|
|
10744
10914
|
}
|
|
10745
10915
|
function assertUpdatePolicy(record, version, revision) {
|
|
@@ -10751,17 +10921,17 @@ async function installLocked2(home, source, bundle, progress, options, alreadySt
|
|
|
10751
10921
|
const raw = await readStoreJson(home, "install.json");
|
|
10752
10922
|
const previous = raw === null ? null : recordSchema3.parse(raw);
|
|
10753
10923
|
if (options.stageOnly) assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
|
|
10754
|
-
if (!previous) for (const file of await
|
|
10924
|
+
if (!previous) for (const file of await fs33.readdir(home)) {
|
|
10755
10925
|
if (file === ".install-lock") continue;
|
|
10756
|
-
const
|
|
10757
|
-
const stat = await
|
|
10758
|
-
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await
|
|
10926
|
+
const directory2 = path41.join(home, file);
|
|
10927
|
+
const stat = await fs33.lstat(directory2);
|
|
10928
|
+
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs33.readdir(directory2)).length) {
|
|
10759
10929
|
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
10760
10930
|
}
|
|
10761
10931
|
}
|
|
10762
10932
|
const selectedBin = path41.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path41.join(home, "bin") : path41.join(os8.homedir(), ".local/bin")));
|
|
10763
|
-
await
|
|
10764
|
-
const bin = await
|
|
10933
|
+
await fs33.mkdir(selectedBin, { recursive: true });
|
|
10934
|
+
const bin = await fs33.realpath(selectedBin);
|
|
10765
10935
|
if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
|
|
10766
10936
|
const id = bundle.manifestHash.slice(0, 24);
|
|
10767
10937
|
const destination = await storePath(home, "versions/" + id);
|
|
@@ -10793,7 +10963,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
|
|
|
10793
10963
|
` };
|
|
10794
10964
|
for (const [name, content2] of Object.entries(launchers)) {
|
|
10795
10965
|
const file = await storePath(bin, name);
|
|
10796
|
-
const old = await
|
|
10966
|
+
const old = await fs33.readFile(file, "utf8").catch((error) => {
|
|
10797
10967
|
if (error.code === "ENOENT") return null;
|
|
10798
10968
|
throw error;
|
|
10799
10969
|
});
|
|
@@ -10802,7 +10972,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
|
|
|
10802
10972
|
const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
|
|
10803
10973
|
const checkRetired = async (name, expected) => {
|
|
10804
10974
|
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
10805
|
-
const actual = await
|
|
10975
|
+
const actual = await fs33.readFile(await storePath(bin, name), "utf8").catch((error) => {
|
|
10806
10976
|
if (error.code === "ENOENT") return null;
|
|
10807
10977
|
throw error;
|
|
10808
10978
|
});
|
|
@@ -10837,7 +11007,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
|
|
|
10837
11007
|
for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
10838
11008
|
for (const [name, expected] of retired) {
|
|
10839
11009
|
await checkRetired(name, expected);
|
|
10840
|
-
await
|
|
11010
|
+
await fs33.rm(await storePath(bin, name), { force: true });
|
|
10841
11011
|
}
|
|
10842
11012
|
await writeStoreJson(home, "install.json", receipt);
|
|
10843
11013
|
} catch (error) {
|
|
@@ -10856,7 +11026,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
|
|
|
10856
11026
|
return { home, bin, version: bundle.manifest.version, previousVersion: previous?.version, path: pathResult };
|
|
10857
11027
|
}
|
|
10858
11028
|
async function copyBundle(home, source, destination, bundle) {
|
|
10859
|
-
const exists = await
|
|
11029
|
+
const exists = await fs33.lstat(destination).then(() => true, (error) => {
|
|
10860
11030
|
if (error.code === "ENOENT") return false;
|
|
10861
11031
|
throw error;
|
|
10862
11032
|
});
|
|
@@ -10865,16 +11035,16 @@ async function copyBundle(home, source, destination, bundle) {
|
|
|
10865
11035
|
return;
|
|
10866
11036
|
}
|
|
10867
11037
|
const stage = await storePath(home, "versions/.install-" + randomUUID10(), true);
|
|
10868
|
-
await
|
|
11038
|
+
await fs33.mkdir(stage);
|
|
10869
11039
|
try {
|
|
10870
11040
|
for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
|
|
10871
11041
|
const from = await storePath(source, file), to = await storePath(stage, file, true);
|
|
10872
|
-
await
|
|
10873
|
-
await
|
|
11042
|
+
await fs33.copyFile(from, to);
|
|
11043
|
+
await fs33.chmod(to, (await fs33.stat(from)).mode & 511);
|
|
10874
11044
|
}
|
|
10875
11045
|
await verifyBundle(stage, bundle.manifestHash);
|
|
10876
11046
|
try {
|
|
10877
|
-
await
|
|
11047
|
+
await fs33.rename(stage, destination);
|
|
10878
11048
|
} catch (error) {
|
|
10879
11049
|
try {
|
|
10880
11050
|
await verifyBundle(destination, bundle.manifestHash);
|
|
@@ -10883,7 +11053,7 @@ async function copyBundle(home, source, destination, bundle) {
|
|
|
10883
11053
|
}
|
|
10884
11054
|
}
|
|
10885
11055
|
} finally {
|
|
10886
|
-
await
|
|
11056
|
+
await fs33.rm(stage, { recursive: true, force: true });
|
|
10887
11057
|
}
|
|
10888
11058
|
}
|
|
10889
11059
|
async function prepareStandaloneMcp(home) {
|
|
@@ -10921,14 +11091,14 @@ async function prepareStandaloneMcp(home) {
|
|
|
10921
11091
|
// src/distribution/updates.ts
|
|
10922
11092
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
10923
11093
|
var RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
10924
|
-
var releaseListSchema =
|
|
10925
|
-
var updateManifestSchema =
|
|
10926
|
-
format:
|
|
10927
|
-
version:
|
|
10928
|
-
publishedAt:
|
|
10929
|
-
assets:
|
|
11094
|
+
var releaseListSchema = z32.array(z32.object({ tag_name: z32.string(), draft: z32.boolean(), prerelease: z32.boolean(), published_at: z32.string().nullable() }));
|
|
11095
|
+
var updateManifestSchema = z32.object({
|
|
11096
|
+
format: z32.literal(1),
|
|
11097
|
+
version: z32.string().regex(/^\d+\.\d+\.\d+$/),
|
|
11098
|
+
publishedAt: z32.string().datetime(),
|
|
11099
|
+
assets: z32.record(z32.string().regex(/^[a-f0-9]{64}$/))
|
|
10930
11100
|
}).strict();
|
|
10931
|
-
var statusSchema2 =
|
|
11101
|
+
var statusSchema2 = z32.object({ lastAttempt: z32.number(), availableVersion: z32.string().optional(), error: z32.string().optional() });
|
|
10932
11102
|
async function scheduleAutomaticUpdate() {
|
|
10933
11103
|
try {
|
|
10934
11104
|
const { home, record } = await currentInstallation();
|