mason-context 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +2 -2
- package/dist/mason-audit.js +214 -69
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-auto.js +702 -766
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-mcp.js +2505 -2542
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason.js +876 -867
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/dist/mason.js
CHANGED
|
@@ -262,15 +262,15 @@ function parseExecution(raw) {
|
|
|
262
262
|
function automationFailure(error) {
|
|
263
263
|
const recorded = failureSchema.safeParse(error?.failure);
|
|
264
264
|
if (recorded.success) return recorded.data;
|
|
265
|
-
const
|
|
265
|
+
const message2 = (error instanceof Error ? error.message : String(error)).replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 700);
|
|
266
266
|
const codes = /* @__PURE__ */ new Set();
|
|
267
267
|
let cause = error;
|
|
268
268
|
for (let i = 0; cause && i < 8; i++) {
|
|
269
269
|
codes.add(String(cause.code));
|
|
270
270
|
cause = cause.cause;
|
|
271
271
|
}
|
|
272
|
-
const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(
|
|
273
|
-
return { code, message:
|
|
272
|
+
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 z.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";
|
|
273
|
+
return { code, message: message2, retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code), receiptRecorded: false };
|
|
274
274
|
}
|
|
275
275
|
function failureMessage(error) {
|
|
276
276
|
const failure = automationFailure(error);
|
|
@@ -1105,9 +1105,133 @@ var init_deleted_reference = __esm({
|
|
|
1105
1105
|
}
|
|
1106
1106
|
});
|
|
1107
1107
|
|
|
1108
|
-
// src/audit/
|
|
1109
|
-
import
|
|
1108
|
+
// src/audit/inputs.ts
|
|
1109
|
+
import fs6 from "fs/promises";
|
|
1110
|
+
import * as nativeFs from "fs";
|
|
1110
1111
|
import path9 from "path";
|
|
1112
|
+
import { execFile as execFile6 } from "child_process";
|
|
1113
|
+
import { promisify as promisify6 } from "util";
|
|
1114
|
+
import fg2 from "fast-glob";
|
|
1115
|
+
async function auditInputPath(root, file) {
|
|
1116
|
+
if (file === ".") return fs6.realpath(root);
|
|
1117
|
+
try {
|
|
1118
|
+
return await storePath(root, file);
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
if (error instanceof Error && error.message.startsWith("Symlink")) {
|
|
1121
|
+
throw new Error(`Audit input contains a symbolic link: ${file}. Its evidence could not be verified.`, { cause: error });
|
|
1122
|
+
}
|
|
1123
|
+
throw error;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
async function readAuditInput(root, file) {
|
|
1127
|
+
try {
|
|
1128
|
+
const value = await readBoundedFile(await auditInputPath(root, file), 10 * 1024 * 1024);
|
|
1129
|
+
if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
|
|
1130
|
+
return value;
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
if (error.code === "ENOENT") return null;
|
|
1133
|
+
throw error;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
async function auditGlob(root, patterns, options) {
|
|
1137
|
+
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
1138
|
+
const directories = /* @__PURE__ */ new Set();
|
|
1139
|
+
const readdir2 = ((directory, opts, callback) => {
|
|
1140
|
+
const relative = path9.relative(root, directory).split(path9.sep).join("/") || ".";
|
|
1141
|
+
directories.add(relative);
|
|
1142
|
+
if (directories.size > MAX_INPUTS) {
|
|
1143
|
+
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
auditInputPath(root, relative).then(() => {
|
|
1147
|
+
nativeFs.readdir(directory, opts, callback);
|
|
1148
|
+
}, callback);
|
|
1149
|
+
});
|
|
1150
|
+
const lstat2 = ((file, callback) => {
|
|
1151
|
+
const relative = path9.relative(root, file).split(path9.sep).join("/") || ".";
|
|
1152
|
+
auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
|
|
1153
|
+
});
|
|
1154
|
+
const stream = fg2.stream(patterns, {
|
|
1155
|
+
cwd: root,
|
|
1156
|
+
ignore: options.ignore,
|
|
1157
|
+
onlyFiles: false,
|
|
1158
|
+
onlyDirectories: options.onlyDirectories,
|
|
1159
|
+
objectMode: true,
|
|
1160
|
+
followSymbolicLinks: options.followSymbolicLinks ?? true,
|
|
1161
|
+
fs: { readdir: readdir2, lstat: lstat2 },
|
|
1162
|
+
concurrency: 16
|
|
1163
|
+
});
|
|
1164
|
+
const matches = [];
|
|
1165
|
+
try {
|
|
1166
|
+
for await (const value of stream) {
|
|
1167
|
+
const entry = value;
|
|
1168
|
+
const file = entry.path;
|
|
1169
|
+
if (options.select && !options.select(file)) continue;
|
|
1170
|
+
await auditInputPath(root, file);
|
|
1171
|
+
if (!options.onlyDirectories && !entry.dirent.isFile()) continue;
|
|
1172
|
+
matches.push(file);
|
|
1173
|
+
if (matches.length > MAX_INPUTS) {
|
|
1174
|
+
const largest = [...matches.reduce((counts, item) => {
|
|
1175
|
+
const dir = item.split("/")[0];
|
|
1176
|
+
counts.set(dir, (counts.get(dir) ?? 0) + 1);
|
|
1177
|
+
return counts;
|
|
1178
|
+
}, /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
1179
|
+
throw new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count3]) => `${dir} (${count3})`).join(", ")}. Narrow this check's inputs.`);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
} finally {
|
|
1183
|
+
stream.destroy();
|
|
1184
|
+
}
|
|
1185
|
+
return matches.sort();
|
|
1186
|
+
}
|
|
1187
|
+
async function gitSourcePaths(root) {
|
|
1188
|
+
const { stdout } = await execute(
|
|
1189
|
+
"git",
|
|
1190
|
+
[
|
|
1191
|
+
"ls-files",
|
|
1192
|
+
"-z",
|
|
1193
|
+
"--cached",
|
|
1194
|
+
"--others",
|
|
1195
|
+
"--exclude-standard",
|
|
1196
|
+
"--",
|
|
1197
|
+
...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
|
|
1198
|
+
],
|
|
1199
|
+
{ cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
|
|
1200
|
+
);
|
|
1201
|
+
return [...new Set(stdout.split("\0").filter(Boolean))].sort();
|
|
1202
|
+
}
|
|
1203
|
+
async function gitIgnoredPaths(root, files) {
|
|
1204
|
+
if (!files.length) return /* @__PURE__ */ new Set();
|
|
1205
|
+
const operation = execute(
|
|
1206
|
+
"git",
|
|
1207
|
+
["check-ignore", "-z", "--stdin"],
|
|
1208
|
+
{ cwd: root, maxBuffer: 4 * 1024 * 1024, timeout: 1e4 }
|
|
1209
|
+
);
|
|
1210
|
+
operation.child.stdin.on("error", () => {
|
|
1211
|
+
});
|
|
1212
|
+
operation.child.stdin.end(files.join("\0") + "\0");
|
|
1213
|
+
try {
|
|
1214
|
+
return new Set((await operation).stdout.split("\0").filter(Boolean));
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
if (error.code === 1) return /* @__PURE__ */ new Set();
|
|
1217
|
+
throw error;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
var execute, MAX_INPUTS;
|
|
1221
|
+
var init_inputs = __esm({
|
|
1222
|
+
"src/audit/inputs.ts"() {
|
|
1223
|
+
"use strict";
|
|
1224
|
+
init_files();
|
|
1225
|
+
init_storage();
|
|
1226
|
+
execute = promisify6(execFile6);
|
|
1227
|
+
MAX_INPUTS = 1e5;
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
// src/audit/checks/new-module.ts
|
|
1232
|
+
import fg3 from "fast-glob";
|
|
1233
|
+
import fs7 from "fs/promises";
|
|
1234
|
+
import path10 from "path";
|
|
1111
1235
|
function escapeRegExp(text2) {
|
|
1112
1236
|
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1113
1237
|
}
|
|
@@ -1118,22 +1242,6 @@ function isMentioned(combinedDocs, name) {
|
|
|
1118
1242
|
);
|
|
1119
1243
|
return re.test(combinedDocs);
|
|
1120
1244
|
}
|
|
1121
|
-
async function listSubdirs(absDir) {
|
|
1122
|
-
const dirs = await fg2("*", {
|
|
1123
|
-
cwd: absDir,
|
|
1124
|
-
onlyDirectories: true,
|
|
1125
|
-
suppressErrors: true
|
|
1126
|
-
});
|
|
1127
|
-
return dirs.filter((d) => !DIR_DENYLIST.has(d)).sort();
|
|
1128
|
-
}
|
|
1129
|
-
async function countSourceFiles(absDir) {
|
|
1130
|
-
const files = await fg2(SOURCE_GLOB, {
|
|
1131
|
-
cwd: absDir,
|
|
1132
|
-
ignore: SOURCE_IGNORE,
|
|
1133
|
-
suppressErrors: true
|
|
1134
|
-
});
|
|
1135
|
-
return files.length;
|
|
1136
|
-
}
|
|
1137
1245
|
async function checkNewModules(ctx) {
|
|
1138
1246
|
const result = emptyResult();
|
|
1139
1247
|
if (ctx.docs.length === 0) return result;
|
|
@@ -1161,21 +1269,48 @@ async function checkNewModules(ctx) {
|
|
|
1161
1269
|
return result;
|
|
1162
1270
|
}
|
|
1163
1271
|
async function moduleCandidates(root, combinedDocs) {
|
|
1272
|
+
const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
|
|
1273
|
+
const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
|
|
1274
|
+
const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
|
|
1275
|
+
const listSubdirs = async (dir) => {
|
|
1276
|
+
const entries = await fs7.readdir(await auditInputPath(root, dir), { withFileTypes: true });
|
|
1277
|
+
const dirs = entries.filter((entry) => (entry.isDirectory() || entry.isSymbolicLink()) && !entry.name.startsWith(".") && !DIR_DENYLIST.has(entry.name)).map((entry) => path10.posix.join(dir, entry.name));
|
|
1278
|
+
const ignored = await gitIgnoredPaths(root, dirs);
|
|
1279
|
+
const visible = new Set(dirs.filter((dir2) => !ignored.has(dir2) || sources.some((file) => file.startsWith(dir2 + "/"))));
|
|
1280
|
+
return (await auditGlob(root, dir === "." ? "*" : `${fg3.escapePath(dir)}/*`, {
|
|
1281
|
+
ignore,
|
|
1282
|
+
onlyDirectories: true,
|
|
1283
|
+
label: "Module directory discovery",
|
|
1284
|
+
select: (file) => visible.has(file)
|
|
1285
|
+
})).map((file) => path10.posix.basename(file));
|
|
1286
|
+
};
|
|
1287
|
+
const countSourceFiles = async (dir) => {
|
|
1288
|
+
const files = sources.filter((file) => file.startsWith(dir + "/"));
|
|
1289
|
+
let count3 = 0;
|
|
1290
|
+
for (let offset = 0; offset < files.length; offset += 256) {
|
|
1291
|
+
count3 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) => fg3.escapePath(file)), {
|
|
1292
|
+
ignore,
|
|
1293
|
+
followSymbolicLinks: false,
|
|
1294
|
+
label: `Source discovery in ${dir}`
|
|
1295
|
+
})).length;
|
|
1296
|
+
if (count3 > 1e5) throw new Error(`Source discovery in ${dir} exceeds 100,000 relevant files. Exclude generated source with .mason/config.json ignore patterns or narrow this audit's checks.`);
|
|
1297
|
+
}
|
|
1298
|
+
return count3;
|
|
1299
|
+
};
|
|
1164
1300
|
const candidates = [];
|
|
1165
|
-
for (const topDir of await listSubdirs(
|
|
1166
|
-
const absTop = path9.join(root, topDir);
|
|
1301
|
+
for (const topDir of await listSubdirs(".")) {
|
|
1167
1302
|
const topMentioned = isMentioned(combinedDocs, topDir);
|
|
1168
1303
|
if (!topMentioned) {
|
|
1169
|
-
const count3 = await countSourceFiles(
|
|
1304
|
+
const count3 = await countSourceFiles(topDir);
|
|
1170
1305
|
if (count3 >= 1) candidates.push({ dir: topDir, sourceFileCount: count3 });
|
|
1171
1306
|
continue;
|
|
1172
1307
|
}
|
|
1173
|
-
const subdirs = await listSubdirs(
|
|
1308
|
+
const subdirs = await listSubdirs(topDir);
|
|
1174
1309
|
const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
|
|
1175
1310
|
if (mentioned.length < ENUMERATION_THRESHOLD) continue;
|
|
1176
1311
|
for (const sub of subdirs) {
|
|
1177
1312
|
if (isMentioned(combinedDocs, sub)) continue;
|
|
1178
|
-
const count3 = await countSourceFiles(
|
|
1313
|
+
const count3 = await countSourceFiles(`${topDir}/${sub}`);
|
|
1179
1314
|
if (count3 >= SECOND_LEVEL_MIN_SOURCE_FILES) {
|
|
1180
1315
|
candidates.push({ dir: `${topDir}/${sub}`, sourceFileCount: count3 });
|
|
1181
1316
|
}
|
|
@@ -1187,7 +1322,8 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
|
|
|
1187
1322
|
var init_new_module = __esm({
|
|
1188
1323
|
"src/audit/checks/new-module.ts"() {
|
|
1189
1324
|
"use strict";
|
|
1190
|
-
|
|
1325
|
+
init_files();
|
|
1326
|
+
init_inputs();
|
|
1191
1327
|
init_git();
|
|
1192
1328
|
init_checks();
|
|
1193
1329
|
DIR_DENYLIST = /* @__PURE__ */ new Set([
|
|
@@ -1215,19 +1351,10 @@ var init_new_module = __esm({
|
|
|
1215
1351
|
});
|
|
1216
1352
|
|
|
1217
1353
|
// src/audit/checks/stale-count.ts
|
|
1218
|
-
import
|
|
1219
|
-
import path10 from "path";
|
|
1220
|
-
import fg3 from "fast-glob";
|
|
1221
|
-
async function readIfExists(absPath) {
|
|
1222
|
-
try {
|
|
1223
|
-
return await fs6.readFile(absPath, "utf-8");
|
|
1224
|
-
} catch {
|
|
1225
|
-
return null;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1354
|
+
import path11 from "path";
|
|
1228
1355
|
async function countGradleModules(root) {
|
|
1229
1356
|
for (const name of ["settings.gradle.kts", "settings.gradle"]) {
|
|
1230
|
-
const content2 = await
|
|
1357
|
+
const content2 = await readAuditInput(root, name);
|
|
1231
1358
|
if (content2 === null) continue;
|
|
1232
1359
|
const members = [];
|
|
1233
1360
|
for (const call of content2.matchAll(/include\s*\(([^)]*)\)/g)) {
|
|
@@ -1241,26 +1368,28 @@ async function countGradleModules(root) {
|
|
|
1241
1368
|
return null;
|
|
1242
1369
|
}
|
|
1243
1370
|
async function countNpmWorkspaces(root) {
|
|
1244
|
-
const pkgRaw = await
|
|
1371
|
+
const pkgRaw = await readAuditInput(root, "package.json");
|
|
1245
1372
|
if (pkgRaw !== null) {
|
|
1373
|
+
let globs = [];
|
|
1246
1374
|
try {
|
|
1247
1375
|
const pkg = JSON.parse(pkgRaw);
|
|
1248
|
-
|
|
1249
|
-
if (globs.length > 0) {
|
|
1250
|
-
const matched = await fg3(
|
|
1251
|
-
globs.map((g) => `${g.replace(/\/+$/, "")}/package.json`),
|
|
1252
|
-
{ cwd: root, ignore: ["**/node_modules/**"] }
|
|
1253
|
-
);
|
|
1254
|
-
return {
|
|
1255
|
-
actual: matched.length,
|
|
1256
|
-
countedFrom: "package.json workspaces",
|
|
1257
|
-
members: matched.map((m) => path10.dirname(m)).sort()
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1376
|
+
globs = Array.isArray(pkg.workspaces) ? pkg.workspaces : Array.isArray(pkg.workspaces?.packages) ? pkg.workspaces.packages : [];
|
|
1260
1377
|
} catch {
|
|
1261
1378
|
}
|
|
1379
|
+
if (globs.length > 0) {
|
|
1380
|
+
const matched = await auditGlob(
|
|
1381
|
+
root,
|
|
1382
|
+
globs.map((g) => `${g.replace(/\/+$/, "")}/package.json`),
|
|
1383
|
+
{ ignore: ["**/node_modules/**"], label: "npm workspace discovery" }
|
|
1384
|
+
);
|
|
1385
|
+
return {
|
|
1386
|
+
actual: matched.length,
|
|
1387
|
+
countedFrom: "package.json workspaces",
|
|
1388
|
+
members: matched.map((m) => path11.dirname(m)).sort()
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1262
1391
|
}
|
|
1263
|
-
const pnpmRaw = await
|
|
1392
|
+
const pnpmRaw = await readAuditInput(root, "pnpm-workspace.yaml");
|
|
1264
1393
|
if (pnpmRaw !== null) {
|
|
1265
1394
|
const globs = [];
|
|
1266
1395
|
let inPackages = false;
|
|
@@ -1279,21 +1408,22 @@ async function countNpmWorkspaces(root) {
|
|
|
1279
1408
|
}
|
|
1280
1409
|
}
|
|
1281
1410
|
if (globs.length > 0) {
|
|
1282
|
-
const matched = await
|
|
1411
|
+
const matched = await auditGlob(
|
|
1412
|
+
root,
|
|
1283
1413
|
globs.map((g) => `${g.replace(/\/+$/, "")}/package.json`),
|
|
1284
|
-
{
|
|
1414
|
+
{ ignore: ["**/node_modules/**"], label: "pnpm workspace discovery" }
|
|
1285
1415
|
);
|
|
1286
1416
|
return {
|
|
1287
1417
|
actual: matched.length,
|
|
1288
1418
|
countedFrom: "pnpm-workspace.yaml",
|
|
1289
|
-
members: matched.map((m) =>
|
|
1419
|
+
members: matched.map((m) => path11.dirname(m)).sort()
|
|
1290
1420
|
};
|
|
1291
1421
|
}
|
|
1292
1422
|
}
|
|
1293
1423
|
return null;
|
|
1294
1424
|
}
|
|
1295
1425
|
async function countCargoCrates(root) {
|
|
1296
|
-
const content2 = await
|
|
1426
|
+
const content2 = await readAuditInput(root, "Cargo.toml");
|
|
1297
1427
|
if (content2 === null) return null;
|
|
1298
1428
|
const membersBlock = content2.match(/members\s*=\s*\[([\s\S]*?)\]/);
|
|
1299
1429
|
if (!membersBlock) return null;
|
|
@@ -1304,12 +1434,12 @@ async function countCargoCrates(root) {
|
|
|
1304
1434
|
const members = /* @__PURE__ */ new Set();
|
|
1305
1435
|
for (const entry of entries) {
|
|
1306
1436
|
if (/[*?[\]{}]/.test(entry)) {
|
|
1307
|
-
const matched = await
|
|
1308
|
-
|
|
1309
|
-
|
|
1437
|
+
const matched = await auditGlob(root, `${entry.replace(/\/+$/, "")}/Cargo.toml`, {
|
|
1438
|
+
ignore: ["**/target/**"],
|
|
1439
|
+
label: "Cargo workspace discovery"
|
|
1310
1440
|
});
|
|
1311
|
-
for (const m of matched) members.add(
|
|
1312
|
-
} else if (await
|
|
1441
|
+
for (const m of matched) members.add(path11.dirname(m));
|
|
1442
|
+
} else if (await readAuditInput(root, path11.posix.join(entry, "Cargo.toml")) !== null) {
|
|
1313
1443
|
members.add(entry);
|
|
1314
1444
|
}
|
|
1315
1445
|
}
|
|
@@ -1363,18 +1493,18 @@ var MEMBERS_CAP;
|
|
|
1363
1493
|
var init_stale_count = __esm({
|
|
1364
1494
|
"src/audit/checks/stale-count.ts"() {
|
|
1365
1495
|
"use strict";
|
|
1496
|
+
init_inputs();
|
|
1366
1497
|
init_checks();
|
|
1367
1498
|
MEMBERS_CAP = 50;
|
|
1368
1499
|
}
|
|
1369
1500
|
});
|
|
1370
1501
|
|
|
1371
1502
|
// src/audit/checks/dead-command.ts
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
async function scriptsOf(absManifest) {
|
|
1503
|
+
async function scriptsOf(root, manifest) {
|
|
1504
|
+
const raw = await readAuditInput(root, manifest);
|
|
1505
|
+
if (raw === null) return null;
|
|
1376
1506
|
try {
|
|
1377
|
-
const pkg = JSON.parse(
|
|
1507
|
+
const pkg = JSON.parse(raw);
|
|
1378
1508
|
return pkg && typeof pkg.scripts === "object" && pkg.scripts !== null ? Object.keys(pkg.scripts) : [];
|
|
1379
1509
|
} catch {
|
|
1380
1510
|
return null;
|
|
@@ -1386,7 +1516,7 @@ async function checkDeadCommands(ctx) {
|
|
|
1386
1516
|
(doc) => doc.claims.commands.map((claim) => ({ doc, claim }))
|
|
1387
1517
|
);
|
|
1388
1518
|
if (commandClaims.length === 0) return result;
|
|
1389
|
-
const rootScripts = await scriptsOf(
|
|
1519
|
+
const rootScripts = await scriptsOf(ctx.root, "package.json");
|
|
1390
1520
|
if (rootScripts === null) {
|
|
1391
1521
|
result.skipped.push({
|
|
1392
1522
|
check: "dead-command",
|
|
@@ -1403,7 +1533,7 @@ async function checkDeadCommands(ctx) {
|
|
|
1403
1533
|
const manifests = await commandManifests(ctx.root);
|
|
1404
1534
|
manifestsChecked = ["package.json", ...manifests.sort()];
|
|
1405
1535
|
for (const manifest of manifests) {
|
|
1406
|
-
const scripts = await scriptsOf(
|
|
1536
|
+
const scripts = await scriptsOf(ctx.root, manifest);
|
|
1407
1537
|
for (const name of scripts ?? []) workspaceScripts.add(name);
|
|
1408
1538
|
}
|
|
1409
1539
|
return workspaceScripts;
|
|
@@ -1429,23 +1559,24 @@ async function checkDeadCommands(ctx) {
|
|
|
1429
1559
|
return result;
|
|
1430
1560
|
}
|
|
1431
1561
|
function commandManifests(root) {
|
|
1432
|
-
return
|
|
1433
|
-
|
|
1434
|
-
|
|
1562
|
+
return auditGlob(root, "**/package.json", {
|
|
1563
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", ".mason/reports/**", "package.json"],
|
|
1564
|
+
label: "Command manifest discovery"
|
|
1435
1565
|
});
|
|
1436
1566
|
}
|
|
1437
1567
|
var AVAILABLE_SCRIPTS_CAP;
|
|
1438
1568
|
var init_dead_command = __esm({
|
|
1439
1569
|
"src/audit/checks/dead-command.ts"() {
|
|
1440
1570
|
"use strict";
|
|
1571
|
+
init_inputs();
|
|
1441
1572
|
init_checks();
|
|
1442
1573
|
AVAILABLE_SCRIPTS_CAP = 30;
|
|
1443
1574
|
}
|
|
1444
1575
|
});
|
|
1445
1576
|
|
|
1446
1577
|
// src/audit/release-metadata.ts
|
|
1447
|
-
import { execFile as
|
|
1448
|
-
import { promisify as
|
|
1578
|
+
import { execFile as execFile7 } from "child_process";
|
|
1579
|
+
import { promisify as promisify7 } from "util";
|
|
1449
1580
|
async function git(root, args2) {
|
|
1450
1581
|
return (await exec6("git", args2, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
|
|
1451
1582
|
}
|
|
@@ -1497,7 +1628,7 @@ var exec6;
|
|
|
1497
1628
|
var init_release_metadata = __esm({
|
|
1498
1629
|
"src/audit/release-metadata.ts"() {
|
|
1499
1630
|
"use strict";
|
|
1500
|
-
exec6 =
|
|
1631
|
+
exec6 = promisify7(execFile7);
|
|
1501
1632
|
}
|
|
1502
1633
|
});
|
|
1503
1634
|
|
|
@@ -1736,7 +1867,7 @@ var init_provenance = __esm({
|
|
|
1736
1867
|
sources: z3.array(decisionSourceSchema).max(20),
|
|
1737
1868
|
history: z3.array(eventSchema).min(1)
|
|
1738
1869
|
}).superRefine((record, ctx) => {
|
|
1739
|
-
const invalid = (
|
|
1870
|
+
const invalid = (message2) => ctx.addIssue({ code: "custom", message: message2 });
|
|
1740
1871
|
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
1741
1872
|
let previous;
|
|
1742
1873
|
for (const event of record.history) {
|
|
@@ -2262,9 +2393,9 @@ __export(evidence_exports, {
|
|
|
2262
2393
|
import fs11 from "fs/promises";
|
|
2263
2394
|
import path17 from "path";
|
|
2264
2395
|
import { createHash as createHash3 } from "crypto";
|
|
2265
|
-
import { execFile as
|
|
2266
|
-
import { promisify as
|
|
2267
|
-
import
|
|
2396
|
+
import { execFile as execFile8 } from "child_process";
|
|
2397
|
+
import { promisify as promisify8 } from "util";
|
|
2398
|
+
import fg4 from "fast-glob";
|
|
2268
2399
|
import { z as z5 } from "zod";
|
|
2269
2400
|
async function git2(root, ...args2) {
|
|
2270
2401
|
return (await exec7("git", args2, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout;
|
|
@@ -2280,34 +2411,13 @@ async function workspace(dir) {
|
|
|
2280
2411
|
}
|
|
2281
2412
|
return { root, gitDir, branch, directory: ".mason/reports/automation/" + hash([root, gitDir, branch]).slice(0, 24) };
|
|
2282
2413
|
}
|
|
2283
|
-
async function content(root, file) {
|
|
2284
|
-
try {
|
|
2285
|
-
const value = await readBoundedFile(await storePath(root, file), 10 * 1024 * 1024);
|
|
2286
|
-
if (value === null) throw new Error("Unreadable or oversized audit input: " + file);
|
|
2287
|
-
return value;
|
|
2288
|
-
} catch (error) {
|
|
2289
|
-
if (error.code === "ENOENT") return null;
|
|
2290
|
-
throw error;
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
2414
|
async function readInputs(root) {
|
|
2294
|
-
const [headText, docStatus,
|
|
2415
|
+
const [headText, docStatus, shallowPath, replacements] = await Promise.all([
|
|
2295
2416
|
git2(root, "rev-parse", "HEAD"),
|
|
2296
2417
|
git2(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...DOC_CANDIDATES),
|
|
2297
|
-
fg5("**/*", {
|
|
2298
|
-
cwd: root,
|
|
2299
|
-
dot: true,
|
|
2300
|
-
onlyFiles: false,
|
|
2301
|
-
followSymbolicLinks: false,
|
|
2302
|
-
objectMode: true,
|
|
2303
|
-
ignore: SOURCE_IGNORE
|
|
2304
|
-
}),
|
|
2305
2418
|
git2(root, "rev-parse", "--git-path", "shallow"),
|
|
2306
2419
|
git2(root, "for-each-ref", "--format=%(refname) %(objectname)", "refs/replace")
|
|
2307
2420
|
]);
|
|
2308
|
-
const entries = inventory.filter((f) => !internal(f.path) && f.path !== ".git").sort((a, b) => a.path.localeCompare(b.path));
|
|
2309
|
-
const files = entries.map((f) => f.path);
|
|
2310
|
-
if (files.length > 1e5) throw new Error("Automation input inventory exceeds 100,000 paths; use an explicit scoped audit.");
|
|
2311
2421
|
const head = headText.trim();
|
|
2312
2422
|
let shallow = null;
|
|
2313
2423
|
try {
|
|
@@ -2328,9 +2438,19 @@ async function readInputs(root) {
|
|
|
2328
2438
|
claims.push([claim.path, await exists2(claim.path), await exists2(path17.dirname(claim.path))]);
|
|
2329
2439
|
}
|
|
2330
2440
|
}
|
|
2331
|
-
if (entries.some((f) => f.dirent.isSymbolicLink())) throw new Error("Automation inventory contains a symbolic link; use an explicit audit to inspect its scope. No cached verification was recorded.");
|
|
2332
2441
|
const combinedDocs = docContents.map(([, text2]) => text2 ?? "").join("\n");
|
|
2333
2442
|
const countClaims = docContents.flatMap(([, text2]) => text2 ? extractClaims(text2).counts : []);
|
|
2443
|
+
const commandClaims = docContents.flatMap(([, text2]) => text2 ? extractClaims(text2).commands : []);
|
|
2444
|
+
const rootPackage = await content(root, "package.json");
|
|
2445
|
+
let rootScripts = null;
|
|
2446
|
+
if (rootPackage !== null) {
|
|
2447
|
+
try {
|
|
2448
|
+
const pkg = JSON.parse(rootPackage);
|
|
2449
|
+
rootScripts = pkg?.scripts && typeof pkg.scripts === "object" ? Object.keys(pkg.scripts) : [];
|
|
2450
|
+
} catch {
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
const needsWorkspaceScripts = rootScripts !== null && commandClaims.some((claim) => !rootScripts.includes(claim.scriptName));
|
|
2334
2454
|
const decisionDirectory = await storePath(root, ".mason/decisions");
|
|
2335
2455
|
const decisionPresence = await fs11.lstat(decisionDirectory).then((stat) => stat.isDirectory() ? "directory" : "file", (error) => {
|
|
2336
2456
|
if (error.code === "ENOENT") return "absent";
|
|
@@ -2339,16 +2459,19 @@ async function readInputs(root) {
|
|
|
2339
2459
|
const [modules, counts, manifests, decisionFiles] = await Promise.all([
|
|
2340
2460
|
combinedDocs ? moduleCandidates(root, combinedDocs) : [],
|
|
2341
2461
|
Promise.all(countClaims.map((claim) => resolveCountSource(root, claim))),
|
|
2342
|
-
commandManifests(root),
|
|
2343
|
-
|
|
2462
|
+
needsWorkspaceScripts ? commandManifests(root) : [],
|
|
2463
|
+
fg4(".mason/decisions/*.json", { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false })
|
|
2344
2464
|
]);
|
|
2345
|
-
const packages =
|
|
2346
|
-
const decisions = await Promise.all(decisionFiles.sort().map(async (file) =>
|
|
2465
|
+
const packages = [["package.json", rootPackage], ...await Promise.all(manifests.sort().map(async (file) => [file, await content(root, file)]))];
|
|
2466
|
+
const decisions = await Promise.all(decisionFiles.sort().map(async (file) => {
|
|
2467
|
+
await storePath(root, file);
|
|
2468
|
+
return [file, await content(root, file)];
|
|
2469
|
+
}));
|
|
2347
2470
|
const [status, index2] = decisions.length ? await Promise.all([
|
|
2348
2471
|
git2(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
|
|
2349
2472
|
git2(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
|
|
2350
2473
|
]) : ["", ""];
|
|
2351
|
-
const common = [
|
|
2474
|
+
const common = [3, engineVersion, head, shallow, replacements, docContents];
|
|
2352
2475
|
const keys = {
|
|
2353
2476
|
"deleted-reference": hash([common, claims, docStatus]),
|
|
2354
2477
|
"new-module": hash([common, modules]),
|
|
@@ -2384,7 +2507,7 @@ function checkCache(raw, inputs) {
|
|
|
2384
2507
|
return { version: 1, entries: canonical, digest: hash(canonical) };
|
|
2385
2508
|
} };
|
|
2386
2509
|
}
|
|
2387
|
-
var exec7, engineVersion, hash, internal, cacheSchema;
|
|
2510
|
+
var exec7, engineVersion, hash, content, internal, cacheSchema;
|
|
2388
2511
|
var init_evidence = __esm({
|
|
2389
2512
|
"src/automation/evidence.ts"() {
|
|
2390
2513
|
"use strict";
|
|
@@ -2392,14 +2515,15 @@ var init_evidence = __esm({
|
|
|
2392
2515
|
init_claims();
|
|
2393
2516
|
init_repair();
|
|
2394
2517
|
init_checks();
|
|
2395
|
-
|
|
2518
|
+
init_inputs();
|
|
2396
2519
|
init_new_module();
|
|
2397
2520
|
init_stale_count();
|
|
2398
2521
|
init_dead_command();
|
|
2399
2522
|
init_storage();
|
|
2400
|
-
exec7 =
|
|
2401
|
-
engineVersion = true ? "0.
|
|
2523
|
+
exec7 = promisify8(execFile8);
|
|
2524
|
+
engineVersion = true ? "0.15.0" : "development";
|
|
2402
2525
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
2526
|
+
content = readAuditInput;
|
|
2403
2527
|
internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
|
|
2404
2528
|
cacheSchema = z5.object({ version: z5.literal(1), entries: z5.record(z5.object({ key: z5.string(), result: checkResultSchema })), digest: z5.string() });
|
|
2405
2529
|
}
|
|
@@ -2728,7 +2852,7 @@ var init_runtime = __esm({
|
|
|
2728
2852
|
// src/setup/model.ts
|
|
2729
2853
|
import { z as z7 } from "zod";
|
|
2730
2854
|
async function loadSetup(root) {
|
|
2731
|
-
const raw = await readStoreJson(root,
|
|
2855
|
+
const raw = await readStoreJson(root, SETUP_PATH);
|
|
2732
2856
|
if (raw === null) return null;
|
|
2733
2857
|
return setupSchema.parse(raw);
|
|
2734
2858
|
}
|
|
@@ -2739,28 +2863,23 @@ async function loadSetupReceipt(root, directory, host) {
|
|
|
2739
2863
|
if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
|
|
2740
2864
|
return receipt;
|
|
2741
2865
|
}
|
|
2742
|
-
var
|
|
2866
|
+
var setupHostSchema, setupSchema, SETUP_PATH, receiptSchema;
|
|
2743
2867
|
var init_model = __esm({
|
|
2744
2868
|
"src/setup/model.ts"() {
|
|
2745
2869
|
"use strict";
|
|
2746
2870
|
init_storage();
|
|
2747
|
-
runtimeSchema = z7.object({
|
|
2748
|
-
id: z7.string().regex(/^[a-f0-9]{24}$/),
|
|
2749
|
-
version: z7.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
2750
|
-
hashes: z7.record(z7.string().regex(/^[a-f0-9]{64}$/)),
|
|
2751
|
-
bundle: z7.object({ target: z7.string(), manifestHash: z7.string().regex(/^[a-f0-9]{64}$/) }).optional()
|
|
2752
|
-
});
|
|
2753
2871
|
setupHostSchema = z7.object({
|
|
2754
|
-
|
|
2872
|
+
configuredVersion: z7.string(),
|
|
2755
2873
|
revision: z7.string().uuid(),
|
|
2756
2874
|
fingerprint: z7.string(),
|
|
2757
2875
|
mcpFingerprint: z7.string(),
|
|
2758
2876
|
instructions: z7.array(z7.string())
|
|
2759
2877
|
});
|
|
2760
|
-
setupSchema = z7.object({ version: z7.literal(
|
|
2878
|
+
setupSchema = z7.object({ version: z7.literal(2), hosts: z7.object({
|
|
2761
2879
|
codex: setupHostSchema.optional(),
|
|
2762
2880
|
claude: setupHostSchema.optional()
|
|
2763
2881
|
}) });
|
|
2882
|
+
SETUP_PATH = ".mason/local/setup.json";
|
|
2764
2883
|
receiptSchema = z7.object({
|
|
2765
2884
|
version: z7.literal(1),
|
|
2766
2885
|
host: z7.enum(["codex", "claude"]),
|
|
@@ -2774,6 +2893,56 @@ var init_model = __esm({
|
|
|
2774
2893
|
}
|
|
2775
2894
|
});
|
|
2776
2895
|
|
|
2896
|
+
// src/setup/launcher.ts
|
|
2897
|
+
var launcher_exports = {};
|
|
2898
|
+
__export(launcher_exports, {
|
|
2899
|
+
executingVersion: () => executingVersion,
|
|
2900
|
+
globalCommand: () => globalCommand,
|
|
2901
|
+
hookCommand: () => hookCommand,
|
|
2902
|
+
installedCommand: () => installedCommand,
|
|
2903
|
+
mcpCommand: () => mcpCommand,
|
|
2904
|
+
prepareLaunch: () => prepareLaunch
|
|
2905
|
+
});
|
|
2906
|
+
import { execFile as execFile9 } from "child_process";
|
|
2907
|
+
import { promisify as promisify9 } from "util";
|
|
2908
|
+
function globalCommand(args2, platform = process.platform) {
|
|
2909
|
+
return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args2] } : { command: "mason", args: args2 };
|
|
2910
|
+
}
|
|
2911
|
+
async function installedCommand() {
|
|
2912
|
+
const invocation = globalCommand(["internal-integration-version"]);
|
|
2913
|
+
try {
|
|
2914
|
+
const { stdout } = await promisify9(execFile9)(
|
|
2915
|
+
invocation.command,
|
|
2916
|
+
invocation.args,
|
|
2917
|
+
{ timeout: 5e3, maxBuffer: 8192, windowsHide: true }
|
|
2918
|
+
);
|
|
2919
|
+
const result = JSON.parse(stdout);
|
|
2920
|
+
if (result.protocol !== 1 || typeof result.version !== "string") throw new Error("Unsupported Mason integration protocol.");
|
|
2921
|
+
return { available: true, version: result.version, message: null };
|
|
2922
|
+
} catch {
|
|
2923
|
+
return { available: false, version: null, message: "An up-to-date mason command is not available on this process's PATH. Install or upgrade Mason, then reopen your terminal or desktop assistant and rerun mason setup." };
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
async function prepareLaunch(host) {
|
|
2927
|
+
const ws = await workspace(process.cwd());
|
|
2928
|
+
const entry = (await loadSetup(ws.root))?.hosts[host];
|
|
2929
|
+
if (!entry) throw new Error(`Mason is not configured locally for ${host}. Run mason setup --host ${host} in this checkout.`);
|
|
2930
|
+
process.env.MASON_SETUP_ROOT = ws.root;
|
|
2931
|
+
process.env.MASON_SETUP_HOST = host;
|
|
2932
|
+
process.env.MASON_SETUP_REVISION = entry.revision;
|
|
2933
|
+
}
|
|
2934
|
+
var executingVersion, mcpCommand, hookCommand;
|
|
2935
|
+
var init_launcher = __esm({
|
|
2936
|
+
"src/setup/launcher.ts"() {
|
|
2937
|
+
"use strict";
|
|
2938
|
+
init_evidence();
|
|
2939
|
+
init_model();
|
|
2940
|
+
executingVersion = () => true ? "0.15.0" : "development";
|
|
2941
|
+
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
2942
|
+
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
2943
|
+
}
|
|
2944
|
+
});
|
|
2945
|
+
|
|
2777
2946
|
// src/setup/observations.ts
|
|
2778
2947
|
var observations_exports = {};
|
|
2779
2948
|
__export(observations_exports, {
|
|
@@ -2783,14 +2952,14 @@ __export(observations_exports, {
|
|
|
2783
2952
|
});
|
|
2784
2953
|
import fs13 from "fs/promises";
|
|
2785
2954
|
import { z as z8 } from "zod";
|
|
2786
|
-
function observationPath(directory, host, revision) {
|
|
2787
|
-
return `${directory}/activation/${host}-${revision}.json`;
|
|
2955
|
+
function observationPath(directory, host, revision, version = executingVersion()) {
|
|
2956
|
+
return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
2788
2957
|
}
|
|
2789
|
-
async function readObservation(root, directory, host, revision) {
|
|
2790
|
-
const raw = await readStoreJson(root, observationPath(directory, host, revision));
|
|
2958
|
+
async function readObservation(root, directory, host, revision, version = executingVersion()) {
|
|
2959
|
+
const raw = await readStoreJson(root, observationPath(directory, host, revision, version));
|
|
2791
2960
|
if (raw === null) return null;
|
|
2792
2961
|
const record = observationSchema.parse(raw);
|
|
2793
|
-
if (record.root !== root || record.host !== host || record.revision !== revision) throw new Error("Activation receipt belongs to another installation.");
|
|
2962
|
+
if (record.root !== root || record.host !== host || record.revision !== revision || record.masonVersion !== version) throw new Error("Activation receipt belongs to another installation.");
|
|
2794
2963
|
return record;
|
|
2795
2964
|
}
|
|
2796
2965
|
async function observeActivation(dir, event, options = {}) {
|
|
@@ -2807,6 +2976,7 @@ async function observeActivation(dir, event, options = {}) {
|
|
|
2807
2976
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2808
2977
|
const record = await readObservation(ws.root, ws.directory, host, revision) ?? {
|
|
2809
2978
|
version: 1,
|
|
2979
|
+
masonVersion: executingVersion(),
|
|
2810
2980
|
root: ws.root,
|
|
2811
2981
|
revision,
|
|
2812
2982
|
host,
|
|
@@ -2845,8 +3015,10 @@ var init_observations = __esm({
|
|
|
2845
3015
|
init_store();
|
|
2846
3016
|
init_storage();
|
|
2847
3017
|
init_model();
|
|
3018
|
+
init_launcher();
|
|
2848
3019
|
observationSchema = z8.object({
|
|
2849
3020
|
version: z8.literal(1),
|
|
3021
|
+
masonVersion: z8.string(),
|
|
2850
3022
|
root: z8.string(),
|
|
2851
3023
|
revision: z8.string(),
|
|
2852
3024
|
host: z8.enum(["codex", "claude"]),
|
|
@@ -2897,8 +3069,8 @@ async function runAutomationHook(host, stdin) {
|
|
|
2897
3069
|
}
|
|
2898
3070
|
return { hookSpecificOutput: { hookEventName: name, additionalContext: result.message } };
|
|
2899
3071
|
} catch (error) {
|
|
2900
|
-
const
|
|
2901
|
-
return { systemMessage:
|
|
3072
|
+
const message2 = failureMessage(error);
|
|
3073
|
+
return { systemMessage: message2, ...["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse"].includes(name) ? { hookSpecificOutput: { hookEventName: name, additionalContext: message2 } } : {} };
|
|
2902
3074
|
}
|
|
2903
3075
|
}
|
|
2904
3076
|
function hookConfig(host, command2 = "npx --no-install --package mason-context mason-auto") {
|
|
@@ -2936,106 +3108,78 @@ var init_adapters = __esm({
|
|
|
2936
3108
|
}
|
|
2937
3109
|
});
|
|
2938
3110
|
|
|
2939
|
-
// src/
|
|
2940
|
-
var
|
|
2941
|
-
__export(
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
installedAutomation: () => installedAutomation,
|
|
2946
|
-
planAutomationInstall: () => planAutomationInstall
|
|
3111
|
+
// src/setup/files.ts
|
|
3112
|
+
var files_exports = {};
|
|
3113
|
+
__export(files_exports, {
|
|
3114
|
+
applyEdit: () => applyEdit,
|
|
3115
|
+
managedBlock: () => managedBlock,
|
|
3116
|
+
readText: () => readText
|
|
2947
3117
|
});
|
|
2948
|
-
import
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
const previous = record.hosts[host]?.command;
|
|
2960
|
-
const hooks = existing.hooks ?? {};
|
|
2961
|
-
for (const event of HOOK_EVENTS) {
|
|
2962
|
-
hooks[event] = (hooks[event] ?? []).map((group) => ({
|
|
2963
|
-
...group,
|
|
2964
|
-
hooks: group.hooks.filter((handler) => !(handler.type === "command" && typeof handler.command === "string" && (handler.command === previous || handler.command === newCommand)))
|
|
2965
|
-
})).filter((group) => group.hooks.length);
|
|
2966
|
-
hooks[event].push(...desired.hooks[event]);
|
|
3118
|
+
import fs14 from "fs/promises";
|
|
3119
|
+
import path18 from "path";
|
|
3120
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
3121
|
+
async function readText(root, file) {
|
|
3122
|
+
try {
|
|
3123
|
+
const text2 = await readBoundedFile(await storePath(root, file), 2 * 1024 * 1024);
|
|
3124
|
+
if (text2 === null) throw new Error("Setup input is not a regular file or exceeds 2 MiB: " + file);
|
|
3125
|
+
return text2;
|
|
3126
|
+
} catch (error) {
|
|
3127
|
+
if (error.code === "ENOENT") return null;
|
|
3128
|
+
throw error;
|
|
2967
3129
|
}
|
|
2968
|
-
record.hosts[host] = { command: newCommand };
|
|
2969
|
-
return { file, config: { ...existing, hooks }, record, newCommand };
|
|
2970
3130
|
}
|
|
2971
|
-
|
|
2972
|
-
const
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
command: newCommand,
|
|
2981
|
-
events: HOOK_EVENTS,
|
|
2982
|
-
next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason-auto status reports observed events separately from configuration." : "Start a new Claude Code session. mason-auto status reports observed events separately from configuration.",
|
|
2983
|
-
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."
|
|
2984
|
-
};
|
|
3131
|
+
function managedBlock(text2, start, end, body) {
|
|
3132
|
+
const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
|
|
3133
|
+
if (starts !== ends || starts > 1 || starts === 1 && text2.indexOf(end) < text2.indexOf(start)) {
|
|
3134
|
+
throw new Error("Ambiguous Mason instruction/configuration markers; repair the marked block before setup.");
|
|
3135
|
+
}
|
|
3136
|
+
const eol = text2.includes("\r\n") ? "\r\n" : "\n";
|
|
3137
|
+
const block = [start, body.replace(/\r?\n/g, eol), end].join(eol);
|
|
3138
|
+
if (starts) return text2.slice(0, text2.indexOf(start)) + block + text2.slice(text2.indexOf(end) + end.length);
|
|
3139
|
+
return text2 + (text2.length && !text2.endsWith("\n") ? eol : "") + (text2.length ? eol : "") + block + eol;
|
|
2985
3140
|
}
|
|
2986
|
-
async function
|
|
2987
|
-
const
|
|
2988
|
-
|
|
2989
|
-
if (
|
|
2990
|
-
const
|
|
2991
|
-
const
|
|
2992
|
-
|
|
2993
|
-
const
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3141
|
+
async function applyEdit(root, edit) {
|
|
3142
|
+
const current = await readText(root, edit.path);
|
|
3143
|
+
if (current === edit.after) return false;
|
|
3144
|
+
if (current !== edit.before) throw new Error("Setup input changed during installation: " + edit.path + ". Rerun setup to resume.");
|
|
3145
|
+
const file = await storePath(root, edit.path, true);
|
|
3146
|
+
const temporary = path18.join(path18.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
3147
|
+
try {
|
|
3148
|
+
const mode = await fs14.stat(file).then((s) => s.mode & 511, () => 384);
|
|
3149
|
+
const handle = await fs14.open(temporary, "wx", mode);
|
|
3150
|
+
try {
|
|
3151
|
+
await handle.writeFile(edit.after, "utf8");
|
|
3152
|
+
await handle.sync();
|
|
3153
|
+
} finally {
|
|
3154
|
+
await handle.close();
|
|
3155
|
+
}
|
|
3156
|
+
if (await readText(root, edit.path) !== edit.before) throw new Error("Setup input changed during installation: " + edit.path);
|
|
3157
|
+
await fs14.rename(temporary, file);
|
|
3158
|
+
return true;
|
|
3159
|
+
} finally {
|
|
3160
|
+
await fs14.rm(temporary, { force: true });
|
|
3003
3161
|
}
|
|
3004
|
-
return result;
|
|
3005
3162
|
}
|
|
3006
|
-
var
|
|
3007
|
-
|
|
3008
|
-
"src/automation/install.ts"() {
|
|
3163
|
+
var init_files2 = __esm({
|
|
3164
|
+
"src/setup/files.ts"() {
|
|
3009
3165
|
"use strict";
|
|
3166
|
+
init_files();
|
|
3010
3167
|
init_storage();
|
|
3011
|
-
init_evidence();
|
|
3012
|
-
init_adapters();
|
|
3013
|
-
init_store();
|
|
3014
|
-
groupSchema = z10.object({ hooks: z10.array(z10.object({ type: z10.string(), command: z10.string().optional() }).passthrough()) }).passthrough();
|
|
3015
|
-
automationConfigSchema = z10.object({ hooks: z10.record(z10.array(groupSchema)).optional() }).passthrough();
|
|
3016
|
-
recordSchema = z10.object({ version: z10.literal(1), hosts: z10.record(z10.object({ command: z10.string() })) });
|
|
3017
|
-
configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
|
|
3018
3168
|
}
|
|
3019
3169
|
});
|
|
3020
3170
|
|
|
3021
3171
|
// src/mcp/init.ts
|
|
3022
|
-
import { z as
|
|
3023
|
-
async function loadProjectMarker(rootDir) {
|
|
3024
|
-
const raw = await readStoreJson(rootDir, ".mason/project.json");
|
|
3025
|
-
return raw === null ? null : markerSchema.parse(raw);
|
|
3026
|
-
}
|
|
3027
|
-
async function saveProjectMarker(rootDir, marker) {
|
|
3028
|
-
await writeStoreJson(rootDir, ".mason/project.json", markerSchema.parse(marker));
|
|
3029
|
-
}
|
|
3172
|
+
import { z as z10 } from "zod";
|
|
3030
3173
|
var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_PLAYBOOK, MAP_PLAYBOOK;
|
|
3031
3174
|
var init_init = __esm({
|
|
3032
3175
|
"src/mcp/init.ts"() {
|
|
3033
3176
|
"use strict";
|
|
3034
3177
|
init_storage();
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3178
|
+
init_files2();
|
|
3179
|
+
markerSchema = z10.object({
|
|
3180
|
+
version: z10.literal(1),
|
|
3181
|
+
initializedAt: z10.string(),
|
|
3182
|
+
features: z10.object({ confluence: z10.boolean().optional() }).passthrough().optional()
|
|
3039
3183
|
}).passthrough();
|
|
3040
3184
|
CLAUDE_MD_SECTION = `<!-- mason:start -->
|
|
3041
3185
|
## Mason project knowledge
|
|
@@ -3057,7 +3201,7 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
|
|
|
3057
3201
|
Inspect source for what the retrieved context does not answer.
|
|
3058
3202
|
<!-- mason:end -->`;
|
|
3059
3203
|
ASSISTANT_SETUP = `UNIFIED SETUP
|
|
3060
|
-
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-auto setup: retain the initial audit before instruction edits,
|
|
3204
|
+
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-auto 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.
|
|
3061
3205
|
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-auto status reports observed context use and hook events. Existing findings remain reviewable; setup does not approve advisories or manufacture decisions.
|
|
3062
3206
|
|
|
3063
3207
|
The managed project guidance is:
|
|
@@ -3065,7 +3209,7 @@ ${CLAUDE_MD_SECTION}`;
|
|
|
3065
3209
|
QUICKSTART_PLAYBOOK = `Start with the audit and review results included in this response. No concept map is required.
|
|
3066
3210
|
|
|
3067
3211
|
1. Explain the actionable findings with their source evidence. Separate audit issues, advisories, and skipped checks. The review covers committed changes from the merge base to HEAD; workingTree paths are not included in that review. An unavailable or empty check is not proof that the project is correct. Use the CLI for full output if a summary is truncated.
|
|
3068
|
-
2. Address findings within the user's requested scope. Setup alone authorizes
|
|
3212
|
+
2. Address findings within the user's requested scope. Setup alone authorizes Mason host configuration, hooks, instructions, and ignore rules; rewriting existing project claims requires repair scope. If repair is authorized, call \`mason_repair(dir, action: "prepare")\` before editing; it saves the full original findings even when this summary is truncated. Inspect relevant source, make grounded edits, then call \`mason_repair(dir, action: "verify", baselinePath)\` with that same baseline, including after any final doc commit. Report resolved, unresolved, review-required, unverified, and new findings. Keep suppressed advisories visible even when setup has already dirtied a doc. Do not invent a decision just to populate the store.
|
|
3069
3213
|
3. When the task reveals a real lesson or constraint, call \`save_decision\` with title, body, category, anchors, and known owner/source/actor information. Missing attribution can be added later. The tool writes a local proposal; editing it preserves revision history and requires a new acceptance, while any earlier accepted revision remains operative. An unchanged save never refreshes its evidence. When decision review is requested, \`review_decision\` prepares the record and code evidence before any authorized verdict. Review and commit records through the normal workflow. Retrieve it on the next relevant task with \`get_context(dir, task, files)\`.
|
|
3070
3214
|
|
|
3071
3215
|
${ASSISTANT_SETUP}
|
|
@@ -3107,34 +3251,283 @@ If a build is interrupted, its partials remain in \`.mason/partial-snapshots/\`.
|
|
|
3107
3251
|
}
|
|
3108
3252
|
});
|
|
3109
3253
|
|
|
3110
|
-
// src/
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3254
|
+
// src/utils/git.ts
|
|
3255
|
+
var git_exports = {};
|
|
3256
|
+
__export(git_exports, {
|
|
3257
|
+
isGitRepo: () => isGitRepo
|
|
3258
|
+
});
|
|
3259
|
+
import { execFile as execFile10 } from "child_process";
|
|
3260
|
+
import { promisify as promisify10 } from "util";
|
|
3261
|
+
async function isGitRepo(dir) {
|
|
3114
3262
|
try {
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3263
|
+
await exec8("git", ["rev-parse", "--git-dir"], { cwd: dir });
|
|
3264
|
+
return true;
|
|
3265
|
+
} catch {
|
|
3266
|
+
return false;
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
var exec8;
|
|
3270
|
+
var init_git2 = __esm({
|
|
3271
|
+
"src/utils/git.ts"() {
|
|
3272
|
+
"use strict";
|
|
3273
|
+
exec8 = promisify10(execFile10);
|
|
3274
|
+
}
|
|
3275
|
+
});
|
|
3276
|
+
|
|
3277
|
+
// src/setup/config.ts
|
|
3278
|
+
var config_exports = {};
|
|
3279
|
+
__export(config_exports, {
|
|
3280
|
+
ancillaryEdits: () => ancillaryEdits,
|
|
3281
|
+
hookEdits: () => hookEdits,
|
|
3282
|
+
inspectHostConfig: () => inspectHostConfig,
|
|
3283
|
+
instructionEdits: () => instructionEdits,
|
|
3284
|
+
mcpEdit: () => mcpEdit,
|
|
3285
|
+
mcpPath: () => mcpPath
|
|
3286
|
+
});
|
|
3287
|
+
import { isDeepStrictEqual } from "util";
|
|
3288
|
+
import { parse, stringify } from "smol-toml";
|
|
3289
|
+
import { z as z11 } from "zod";
|
|
3290
|
+
async function instructionEdits(root, host) {
|
|
3291
|
+
const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
|
|
3292
|
+
const existing = found.find((f) => f.text !== null);
|
|
3293
|
+
const primary = host === "codex" && found[0].text === null ? { path: "AGENTS.md", text: null } : existing ?? { path: "AGENTS.md", text: null };
|
|
3294
|
+
const originalGuidance = primary.text === null && existing ? "Follow the existing project conventions in " + existing.path + ".\n" : "";
|
|
3295
|
+
const edits = [{
|
|
3296
|
+
path: primary.path,
|
|
3297
|
+
before: primary.text,
|
|
3298
|
+
after: managedBlock(primary.text ?? originalGuidance, "<!-- mason:start -->", "<!-- mason:end -->", CLAUDE_MD_SECTION.split("\n").slice(1, -1).join("\n"))
|
|
3299
|
+
}];
|
|
3300
|
+
if (primary.path === "AGENTS.md") {
|
|
3301
|
+
const secondary = found.filter((f) => f.path !== "AGENTS.md" && f.text !== null);
|
|
3302
|
+
if (host === "claude" && !secondary.length) secondary.push({ path: "CLAUDE.md", text: null });
|
|
3303
|
+
for (const file of secondary) {
|
|
3304
|
+
const text2 = file.text ?? "";
|
|
3305
|
+
const legacy = text2.includes("<!-- mason:start -->") || text2.includes("<!-- mason:end -->");
|
|
3306
|
+
const imported = "@" + (file.path.startsWith(".claude/") ? "../AGENTS.md" : "AGENTS.md");
|
|
3307
|
+
if (text2.trim() === imported) continue;
|
|
3308
|
+
const pointer = "Mason project knowledge and shared project instructions:\n" + imported;
|
|
3309
|
+
edits.push({
|
|
3310
|
+
path: file.path,
|
|
3311
|
+
before: file.text,
|
|
3312
|
+
after: managedBlock(text2, legacy ? "<!-- mason:start -->" : POINTER_START, legacy ? "<!-- mason:end -->" : POINTER_END, pointer)
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
return edits;
|
|
3317
|
+
}
|
|
3318
|
+
function managedMcp(previous, host) {
|
|
3319
|
+
const options = previous === void 0 ? {} : object.parse(previous);
|
|
3320
|
+
for (const key of ["command", "args", "url", "type", "headers", "http_headers", "env_http_headers", "bearer_token_env_var"]) delete options[key];
|
|
3321
|
+
return { ...options, ...mcpCommand(host) };
|
|
3322
|
+
}
|
|
3323
|
+
async function mcpEdit(root, host) {
|
|
3324
|
+
const file = mcpPath(host), before = await readText(root, file);
|
|
3325
|
+
if (host === "claude") {
|
|
3326
|
+
const config2 = before === null ? {} : object.parse(JSON.parse(before));
|
|
3327
|
+
const servers2 = object.parse(config2.mcpServers ?? {});
|
|
3328
|
+
return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host) } }, null, 2) + "\n" };
|
|
3329
|
+
}
|
|
3330
|
+
const config = parse(before ?? "");
|
|
3331
|
+
const managed = (before ?? "").includes(TOML_START);
|
|
3332
|
+
const servers = config.mcp_servers;
|
|
3333
|
+
const desired = managedMcp(servers?.mason, host);
|
|
3334
|
+
let base = before ?? "";
|
|
3335
|
+
if (servers?.mason && !managed) {
|
|
3336
|
+
const headers = [...base.matchAll(/^\s*\[\[?.+?\]\]?[^\S\r\n]*(?:#.*)?$/gm)];
|
|
3337
|
+
const spans = headers.flatMap((header, index2) => /^\s*\[mcp_servers\.mason(?:\.[A-Za-z0-9_-]+)*\]/.test(header[0]) ? [{ start: header.index, end: headers[index2 + 1]?.index ?? base.length }] : []);
|
|
3338
|
+
if (!spans.length) throw new Error("Cannot safely migrate the existing Mason MCP TOML entry. Use an explicit [mcp_servers.mason] table and rerun setup; the file was retained.");
|
|
3339
|
+
for (const span of spans.reverse()) base = base.slice(0, span.start) + base.slice(span.end);
|
|
3340
|
+
}
|
|
3341
|
+
const after = managedBlock(base, TOML_START, TOML_END, stringify({ mcp_servers: { mason: desired } }).trimEnd());
|
|
3342
|
+
const parsed = parse(after);
|
|
3343
|
+
const expected = { ...config, mcp_servers: { ...servers ?? {}, mason: desired } };
|
|
3344
|
+
if (!isDeepStrictEqual(parsed, expected)) throw new Error("Could not safely configure the Mason MCP entry without changing other settings.");
|
|
3345
|
+
return { path: file, before, after };
|
|
3346
|
+
}
|
|
3347
|
+
async function hookEdits(root, host) {
|
|
3348
|
+
const file = configPath(host), before = await readText(root, file);
|
|
3349
|
+
const { planAutomationInstall: planAutomationInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
3350
|
+
const plan = await planAutomationInstall2(root, host, hookCommand(host));
|
|
3351
|
+
return [
|
|
3352
|
+
{ path: file, before, after: JSON.stringify(plan.config, null, 2) + "\n" },
|
|
3353
|
+
{ path: AUTOMATION_PATH, before: await readText(root, AUTOMATION_PATH), after: JSON.stringify(plan.record, null, 2) + "\n" }
|
|
3354
|
+
];
|
|
3355
|
+
}
|
|
3356
|
+
async function ancillaryEdits(root) {
|
|
3357
|
+
const ignore = await readText(root, ".gitignore");
|
|
3358
|
+
const { git: git3 } = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
|
|
3359
|
+
const { isGitRepo: isGitRepo2 } = await Promise.resolve().then(() => (init_git2(), git_exports));
|
|
3360
|
+
let parentIgnored = false;
|
|
3361
|
+
try {
|
|
3362
|
+
if (await isGitRepo2(root)) parentIgnored = !!(await git3(root, "check-ignore", "--no-index", ".mason")).trim();
|
|
3363
|
+
} catch (error) {
|
|
3364
|
+
if (error.code !== 1) throw error;
|
|
3365
|
+
}
|
|
3366
|
+
const retainParentRule = parentIgnored || !!ignore?.replace(/\r\n/g, "\n").includes("# mason:ignore:start\n!/.mason/\n/.mason/*");
|
|
3367
|
+
const rules = [
|
|
3368
|
+
...retainParentRule ? ["!/.mason/", "/.mason/*"] : [],
|
|
3369
|
+
"!/.mason/decisions/",
|
|
3370
|
+
"!/.mason/decisions/**",
|
|
3371
|
+
"!/.mason/config.json",
|
|
3372
|
+
"!/.mason/snapshot.json",
|
|
3373
|
+
"/.mason/local/",
|
|
3374
|
+
"/.mason/reports/"
|
|
3375
|
+
].join("\n");
|
|
3376
|
+
return [{ path: ".gitignore", before: ignore, after: managedBlock(ignore ?? "", "# mason:ignore:start", "# mason:ignore:end", rules) }];
|
|
3377
|
+
}
|
|
3378
|
+
async function inspectHostConfig(root, host, plannedMcp) {
|
|
3379
|
+
const text2 = plannedMcp ?? await readText(root, mcpPath(host));
|
|
3380
|
+
const config = host === "codex" ? parse(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
|
|
3381
|
+
const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
|
|
3382
|
+
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
|
|
3383
|
+
const expected = hookConfig(host, hookCommand(host));
|
|
3384
|
+
const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
|
|
3385
|
+
return {
|
|
3386
|
+
mcp,
|
|
3387
|
+
mcpDisabled: mcp?.enabled === false,
|
|
3388
|
+
hooks: Object.fromEntries(Object.keys(expected.hooks).map((event) => [
|
|
3389
|
+
event,
|
|
3390
|
+
(hooks.hooks?.[event] ?? []).flatMap((group) => group.hooks.filter((handler) => handler.command === expected.hooks.SessionStart[0].hooks[0].command).map((handler) => ({ ...group, hooks: [handler] })) ?? [])
|
|
3391
|
+
])),
|
|
3392
|
+
disabled: hooks.disableAllHooks === true || config.features?.hooks === false || config.features?.codex_hooks === false
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
var object, mcpPath, TOML_START, TOML_END, POINTER_START, POINTER_END;
|
|
3396
|
+
var init_config = __esm({
|
|
3397
|
+
"src/setup/config.ts"() {
|
|
3398
|
+
"use strict";
|
|
3399
|
+
init_init();
|
|
3400
|
+
init_docs();
|
|
3401
|
+
init_adapters();
|
|
3402
|
+
init_install();
|
|
3403
|
+
init_files2();
|
|
3404
|
+
init_launcher();
|
|
3405
|
+
object = z11.record(z11.unknown());
|
|
3406
|
+
mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
3407
|
+
TOML_START = "# mason:mcp:start";
|
|
3408
|
+
TOML_END = "# mason:mcp:end";
|
|
3409
|
+
POINTER_START = "<!-- mason:agents:start -->";
|
|
3410
|
+
POINTER_END = "<!-- mason:agents:end -->";
|
|
3411
|
+
}
|
|
3412
|
+
});
|
|
3413
|
+
|
|
3414
|
+
// src/automation/install.ts
|
|
3415
|
+
var install_exports = {};
|
|
3416
|
+
__export(install_exports, {
|
|
3417
|
+
AUTOMATION_PATH: () => AUTOMATION_PATH,
|
|
3418
|
+
automationConfigSchema: () => automationConfigSchema,
|
|
3419
|
+
configPath: () => configPath,
|
|
3420
|
+
installAutomation: () => installAutomation,
|
|
3421
|
+
installedAutomation: () => installedAutomation,
|
|
3422
|
+
planAutomationInstall: () => planAutomationInstall
|
|
3423
|
+
});
|
|
3424
|
+
import { z as z12 } from "zod";
|
|
3425
|
+
async function installationRecord(root) {
|
|
3426
|
+
return recordSchema.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
|
|
3427
|
+
}
|
|
3428
|
+
async function installAutomation(dir, host, command2) {
|
|
3429
|
+
const ws = await workspace(dir);
|
|
3430
|
+
return withLock(ws.root, ".mason/reports/automation-install", () => installLocked(ws.root, host, command2));
|
|
3431
|
+
}
|
|
3432
|
+
async function planAutomationInstall(root, host, command2) {
|
|
3433
|
+
const file = configPath(host);
|
|
3434
|
+
const existing = automationConfigSchema.parse(await readStoreJson(root, file) ?? {});
|
|
3435
|
+
const record = await installationRecord(root);
|
|
3436
|
+
const desired = hookConfig(host, command2);
|
|
3437
|
+
const newCommand = desired.hooks.SessionStart[0].hooks[0].command;
|
|
3438
|
+
const previous = record.hosts[host]?.command;
|
|
3439
|
+
const owned = /* @__PURE__ */ new Set([previous, newCommand]);
|
|
3440
|
+
const hooks = existing.hooks ?? {};
|
|
3441
|
+
for (const event of HOOK_EVENTS) {
|
|
3442
|
+
hooks[event] = (hooks[event] ?? []).map((group) => ({
|
|
3443
|
+
...group,
|
|
3444
|
+
hooks: group.hooks.filter((handler) => !(handler.type === "command" && typeof handler.command === "string" && owned.has(handler.command)))
|
|
3445
|
+
})).filter((group) => group.hooks.length);
|
|
3446
|
+
hooks[event].push(...desired.hooks[event]);
|
|
3447
|
+
}
|
|
3448
|
+
record.hosts[host] = { command: newCommand };
|
|
3449
|
+
return { file, config: { ...existing, hooks }, record, newCommand };
|
|
3450
|
+
}
|
|
3451
|
+
async function installLocked(root, host, command2) {
|
|
3452
|
+
const { file, config, record, newCommand } = await planAutomationInstall(root, host, command2);
|
|
3453
|
+
const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3454
|
+
const { applyEdit: applyEdit2 } = await Promise.resolve().then(() => (init_files2(), files_exports));
|
|
3455
|
+
for (const edit of await ancillaryEdits2(root)) await applyEdit2(root, edit);
|
|
3456
|
+
await writeStoreJson(root, file, config);
|
|
3457
|
+
await writeStoreJson(root, AUTOMATION_PATH, record);
|
|
3458
|
+
return {
|
|
3459
|
+
version: 1,
|
|
3460
|
+
host,
|
|
3461
|
+
configPath: file,
|
|
3462
|
+
status: "configured",
|
|
3463
|
+
command: newCommand,
|
|
3464
|
+
events: HOOK_EVENTS,
|
|
3465
|
+
next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason-auto status reports observed events separately from configuration." : "Start a new Claude Code session. mason-auto status reports observed events separately from configuration.",
|
|
3466
|
+
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."
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
async function installedAutomation(dir) {
|
|
3470
|
+
const ws = await workspace(dir);
|
|
3471
|
+
const record = await installationRecord(ws.root);
|
|
3472
|
+
const result = {};
|
|
3473
|
+
for (const host of ["claude", "codex"]) {
|
|
3474
|
+
const expected = record.hosts[host];
|
|
3475
|
+
if (!expected) continue;
|
|
3476
|
+
const current = automationConfigSchema.parse(await readStoreJson(ws.root, configPath(host)) ?? {});
|
|
3477
|
+
const configuredEvents = HOOK_EVENTS.filter((event) => current.hooks?.[event]?.some((group) => group.hooks.some((handler) => handler.type === "command" && handler.command === expected.command)));
|
|
3478
|
+
result[host] = {
|
|
3479
|
+
configPath: configPath(host),
|
|
3480
|
+
configuredEvents,
|
|
3481
|
+
status: current.disableAllHooks === true ? "disabled" : configuredEvents.length === HOOK_EVENTS.length ? "configured" : "incomplete",
|
|
3482
|
+
runtime: "Host version, trust, policy, and tool coverage still determine execution; inspect observed events."
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3485
|
+
return result;
|
|
3486
|
+
}
|
|
3487
|
+
var groupSchema, automationConfigSchema, recordSchema, AUTOMATION_PATH, configPath;
|
|
3488
|
+
var init_install = __esm({
|
|
3489
|
+
"src/automation/install.ts"() {
|
|
3490
|
+
"use strict";
|
|
3491
|
+
init_storage();
|
|
3492
|
+
init_evidence();
|
|
3493
|
+
init_adapters();
|
|
3494
|
+
init_store();
|
|
3495
|
+
groupSchema = z12.object({ hooks: z12.array(z12.object({ type: z12.string(), command: z12.string().optional() }).passthrough()) }).passthrough();
|
|
3496
|
+
automationConfigSchema = z12.object({ hooks: z12.record(z12.array(groupSchema)).optional() }).passthrough();
|
|
3497
|
+
recordSchema = z12.object({ version: z12.literal(1), hosts: z12.record(z12.object({ command: z12.string() })) });
|
|
3498
|
+
AUTOMATION_PATH = ".mason/local/automation.json";
|
|
3499
|
+
configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
|
|
3500
|
+
}
|
|
3501
|
+
});
|
|
3502
|
+
|
|
3503
|
+
// src/review/cochange.ts
|
|
3504
|
+
import { execFile as execFile11 } from "child_process";
|
|
3505
|
+
import { promisify as promisify11 } from "util";
|
|
3506
|
+
async function buildMatrix(resolvedRoot) {
|
|
3507
|
+
try {
|
|
3508
|
+
const { stdout: shallow } = await exec9("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
|
|
3509
|
+
if (shallow.trim() === "true") return null;
|
|
3510
|
+
const { stdout } = await exec9(
|
|
3511
|
+
"git",
|
|
3512
|
+
["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
|
|
3513
|
+
{ cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
|
|
3514
|
+
);
|
|
3515
|
+
const commitsByFile = /* @__PURE__ */ new Map();
|
|
3516
|
+
const blocks = stdout.split("");
|
|
3517
|
+
let index2 = 0;
|
|
3518
|
+
for (const block of blocks) {
|
|
3519
|
+
const files = block.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith(".mason/"));
|
|
3520
|
+
if (files.length === 0 || files.length > MAX_COMMIT_FILES) continue;
|
|
3521
|
+
for (const file of files) {
|
|
3522
|
+
let set = commitsByFile.get(file);
|
|
3523
|
+
if (!set) {
|
|
3524
|
+
set = /* @__PURE__ */ new Set();
|
|
3525
|
+
commitsByFile.set(file, set);
|
|
3526
|
+
}
|
|
3527
|
+
set.add(index2);
|
|
3528
|
+
}
|
|
3529
|
+
index2++;
|
|
3530
|
+
}
|
|
3138
3531
|
return { commitsByFile, totalCommits: index2 };
|
|
3139
3532
|
} catch {
|
|
3140
3533
|
return null;
|
|
@@ -3170,11 +3563,11 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
|
|
|
3170
3563
|
findings.sort((a, b) => b.rate - a.rate || b.sharedCommits - a.sharedCommits);
|
|
3171
3564
|
return findings;
|
|
3172
3565
|
}
|
|
3173
|
-
var
|
|
3566
|
+
var exec9, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
|
|
3174
3567
|
var init_cochange = __esm({
|
|
3175
3568
|
"src/review/cochange.ts"() {
|
|
3176
3569
|
"use strict";
|
|
3177
|
-
|
|
3570
|
+
exec9 = promisify11(execFile11);
|
|
3178
3571
|
HISTORY_COMMITS = 1500;
|
|
3179
3572
|
MIN_FILE_COMMITS = 5;
|
|
3180
3573
|
MIN_SHARED_COMMITS = 4;
|
|
@@ -3217,8 +3610,8 @@ var init_paths2 = __esm({
|
|
|
3217
3610
|
});
|
|
3218
3611
|
|
|
3219
3612
|
// src/review/evidence/types.ts
|
|
3220
|
-
function messagePreview(
|
|
3221
|
-
return { message:
|
|
3613
|
+
function messagePreview(message2) {
|
|
3614
|
+
return { message: message2.slice(0, 4e3), ...message2.length > 4e3 ? { truncated: true } : {} };
|
|
3222
3615
|
}
|
|
3223
3616
|
var MAX_FINDINGS;
|
|
3224
3617
|
var init_types2 = __esm({
|
|
@@ -3229,7 +3622,7 @@ var init_types2 = __esm({
|
|
|
3229
3622
|
});
|
|
3230
3623
|
|
|
3231
3624
|
// src/review/evidence/vitest.ts
|
|
3232
|
-
import { z as
|
|
3625
|
+
import { z as z13 } from "zod";
|
|
3233
3626
|
function parseVitest(raw, sourceRoot) {
|
|
3234
3627
|
const report = schema.parse(raw);
|
|
3235
3628
|
const findings = [], diagnostics = [];
|
|
@@ -3278,24 +3671,24 @@ var init_vitest = __esm({
|
|
|
3278
3671
|
"use strict";
|
|
3279
3672
|
init_paths2();
|
|
3280
3673
|
init_types2();
|
|
3281
|
-
count2 =
|
|
3282
|
-
schema =
|
|
3283
|
-
success:
|
|
3674
|
+
count2 = z13.number().int().nonnegative();
|
|
3675
|
+
schema = z13.object({
|
|
3676
|
+
success: z13.boolean(),
|
|
3284
3677
|
numTotalTests: count2,
|
|
3285
3678
|
numPassedTests: count2,
|
|
3286
3679
|
numFailedTests: count2,
|
|
3287
3680
|
numPendingTests: count2,
|
|
3288
3681
|
numTodoTests: count2,
|
|
3289
3682
|
numFailedTestSuites: count2,
|
|
3290
|
-
testResults:
|
|
3291
|
-
name:
|
|
3292
|
-
status:
|
|
3293
|
-
message:
|
|
3294
|
-
assertionResults:
|
|
3295
|
-
fullName:
|
|
3296
|
-
status:
|
|
3297
|
-
failureMessages:
|
|
3298
|
-
location:
|
|
3683
|
+
testResults: z13.array(z13.object({
|
|
3684
|
+
name: z13.string().min(1),
|
|
3685
|
+
status: z13.enum(["passed", "failed"]),
|
|
3686
|
+
message: z13.string().optional(),
|
|
3687
|
+
assertionResults: z13.array(z13.object({
|
|
3688
|
+
fullName: z13.string(),
|
|
3689
|
+
status: z13.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
|
|
3690
|
+
failureMessages: z13.array(z13.string()).nullable().optional(),
|
|
3691
|
+
location: z13.object({ line: count2, column: count2 }).nullable().optional()
|
|
3299
3692
|
}))
|
|
3300
3693
|
}))
|
|
3301
3694
|
});
|
|
@@ -3303,7 +3696,7 @@ var init_vitest = __esm({
|
|
|
3303
3696
|
});
|
|
3304
3697
|
|
|
3305
3698
|
// src/review/evidence/sarif.ts
|
|
3306
|
-
import { z as
|
|
3699
|
+
import { z as z14 } from "zod";
|
|
3307
3700
|
function parseSarif(raw, sourceRoot) {
|
|
3308
3701
|
const report = schema2.parse(raw), findings = [], diagnostics = [];
|
|
3309
3702
|
let active = 0, suppressed = 0, absent = 0, unresolved = 0;
|
|
@@ -3394,29 +3787,29 @@ var init_sarif = __esm({
|
|
|
3394
3787
|
"use strict";
|
|
3395
3788
|
init_paths2();
|
|
3396
3789
|
init_types2();
|
|
3397
|
-
index =
|
|
3398
|
-
artifact =
|
|
3399
|
-
message =
|
|
3400
|
-
level =
|
|
3401
|
-
location =
|
|
3402
|
-
schema2 =
|
|
3403
|
-
version:
|
|
3404
|
-
runs:
|
|
3405
|
-
tool:
|
|
3406
|
-
invocations:
|
|
3407
|
-
versionControlProvenance:
|
|
3408
|
-
originalUriBaseIds:
|
|
3409
|
-
artifacts:
|
|
3410
|
-
results:
|
|
3411
|
-
ruleId:
|
|
3790
|
+
index = z14.number().int().nonnegative();
|
|
3791
|
+
artifact = z14.object({ uri: z14.string().optional(), uriBaseId: z14.string().optional(), index: index.optional() });
|
|
3792
|
+
message = z14.object({ text: z14.string().optional(), markdown: z14.string().optional(), id: z14.string().optional(), arguments: z14.array(z14.string()).optional() });
|
|
3793
|
+
level = z14.enum(["error", "warning", "note", "none"]);
|
|
3794
|
+
location = z14.object({ physicalLocation: z14.object({ artifactLocation: artifact.optional(), region: z14.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
|
|
3795
|
+
schema2 = z14.object({
|
|
3796
|
+
version: z14.literal("2.1.0"),
|
|
3797
|
+
runs: z14.array(z14.object({
|
|
3798
|
+
tool: z14.object({ driver: z14.object({ name: z14.string().min(1), rules: z14.array(z14.object({ id: z14.string(), defaultConfiguration: z14.object({ level: level.optional() }).optional(), messageStrings: z14.record(message).optional() })).optional(), globalMessageStrings: z14.record(message).optional() }) }),
|
|
3799
|
+
invocations: z14.array(z14.object({ executionSuccessful: z14.boolean(), commandLine: z14.string().optional(), toolExecutionNotifications: z14.array(z14.object({ level: level.optional(), message })).optional() })).optional(),
|
|
3800
|
+
versionControlProvenance: z14.array(z14.object({ revisionId: z14.string().optional() })).optional(),
|
|
3801
|
+
originalUriBaseIds: z14.record(artifact).optional(),
|
|
3802
|
+
artifacts: z14.array(z14.object({ location: artifact.optional() })).optional(),
|
|
3803
|
+
results: z14.array(z14.object({
|
|
3804
|
+
ruleId: z14.string().optional(),
|
|
3412
3805
|
ruleIndex: index.optional(),
|
|
3413
3806
|
message,
|
|
3414
3807
|
level: level.optional(),
|
|
3415
|
-
kind:
|
|
3416
|
-
baselineState:
|
|
3417
|
-
suppressions:
|
|
3418
|
-
locations:
|
|
3419
|
-
relatedLocations:
|
|
3808
|
+
kind: z14.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
|
|
3809
|
+
baselineState: z14.enum(["new", "unchanged", "updated", "absent"]).optional(),
|
|
3810
|
+
suppressions: z14.array(z14.object({ kind: z14.enum(["inSource", "external"]), status: z14.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
|
|
3811
|
+
locations: z14.array(location).optional(),
|
|
3812
|
+
relatedLocations: z14.array(location).optional()
|
|
3420
3813
|
})).optional()
|
|
3421
3814
|
}))
|
|
3422
3815
|
});
|
|
@@ -3424,10 +3817,10 @@ var init_sarif = __esm({
|
|
|
3424
3817
|
});
|
|
3425
3818
|
|
|
3426
3819
|
// src/review/evidence.ts
|
|
3427
|
-
import
|
|
3428
|
-
import { z as
|
|
3820
|
+
import path19 from "path";
|
|
3821
|
+
import { z as z15 } from "zod";
|
|
3429
3822
|
function relativeArtifact(root, file) {
|
|
3430
|
-
const relative =
|
|
3823
|
+
const relative = path19.isAbsolute(file) ? path19.relative(root, file) : file;
|
|
3431
3824
|
const normalized = normalizeRepoPath(relative);
|
|
3432
3825
|
if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
|
|
3433
3826
|
return normalized;
|
|
@@ -3466,7 +3859,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
3466
3859
|
let manifest, raw;
|
|
3467
3860
|
try {
|
|
3468
3861
|
manifest = relativeArtifact(root, supplied);
|
|
3469
|
-
raw =
|
|
3862
|
+
raw = z15.object({ version: z15.literal(1), checks: z15.array(z15.unknown()) }).parse(await readStoreJson(root, manifest));
|
|
3470
3863
|
} catch (error) {
|
|
3471
3864
|
output.diagnostics.push(`${supplied}: ${String(error)}`);
|
|
3472
3865
|
continue;
|
|
@@ -3523,7 +3916,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
3523
3916
|
const report = await readStoreJson(root, file);
|
|
3524
3917
|
if (report === null) throw new Error(`Report artifact is missing: ${file}`);
|
|
3525
3918
|
const sourceRoot = check.sourceRoot ?? root;
|
|
3526
|
-
if (!
|
|
3919
|
+
if (!path19.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
|
|
3527
3920
|
const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : parseSarif(report, sourceRoot);
|
|
3528
3921
|
result.outcome = parsed.outcome;
|
|
3529
3922
|
result.counts = parsed.counts;
|
|
@@ -3619,31 +4012,31 @@ var init_evidence2 = __esm({
|
|
|
3619
4012
|
init_vitest();
|
|
3620
4013
|
init_sarif();
|
|
3621
4014
|
init_types2();
|
|
3622
|
-
checkSchema2 =
|
|
3623
|
-
id:
|
|
3624
|
-
kind:
|
|
3625
|
-
tool:
|
|
3626
|
-
command:
|
|
3627
|
-
commit:
|
|
3628
|
-
workingTreeClean:
|
|
3629
|
-
source:
|
|
3630
|
-
sourceRoot:
|
|
3631
|
-
status:
|
|
3632
|
-
reason:
|
|
3633
|
-
exitCode:
|
|
3634
|
-
report:
|
|
4015
|
+
checkSchema2 = z15.object({
|
|
4016
|
+
id: z15.string().min(1).max(100),
|
|
4017
|
+
kind: z15.enum(["tests", "static-analysis", "security", "complexity", "duplication"]),
|
|
4018
|
+
tool: z15.string().min(1).max(200),
|
|
4019
|
+
command: z15.string().min(1).max(2e3),
|
|
4020
|
+
commit: z15.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
|
|
4021
|
+
workingTreeClean: z15.boolean().optional(),
|
|
4022
|
+
source: z15.string().max(2e3).optional(),
|
|
4023
|
+
sourceRoot: z15.string().min(1).max(4e3).optional(),
|
|
4024
|
+
status: z15.enum(["completed", "skipped", "unavailable"]).default("completed"),
|
|
4025
|
+
reason: z15.string().min(1).max(2e3).optional(),
|
|
4026
|
+
exitCode: z15.number().int().nullable().optional(),
|
|
4027
|
+
report: z15.object({ format: z15.enum(["vitest-json", "sarif"]), path: z15.string().min(1).max(4e3) }).optional()
|
|
3635
4028
|
});
|
|
3636
4029
|
}
|
|
3637
4030
|
});
|
|
3638
4031
|
|
|
3639
4032
|
// src/review/review.ts
|
|
3640
|
-
import
|
|
3641
|
-
import
|
|
3642
|
-
import { execFile as
|
|
3643
|
-
import { promisify as
|
|
4033
|
+
import fs15 from "fs/promises";
|
|
4034
|
+
import path20 from "path";
|
|
4035
|
+
import { execFile as execFile12 } from "child_process";
|
|
4036
|
+
import { promisify as promisify12 } from "util";
|
|
3644
4037
|
async function resolveMergeBase(resolvedRoot, base, head) {
|
|
3645
4038
|
try {
|
|
3646
|
-
const { stdout } = await
|
|
4039
|
+
const { stdout } = await exec10("git", ["merge-base", base, head], {
|
|
3647
4040
|
cwd: resolvedRoot
|
|
3648
4041
|
});
|
|
3649
4042
|
return stdout.trim() || null;
|
|
@@ -3654,7 +4047,7 @@ async function resolveMergeBase(resolvedRoot, base, head) {
|
|
|
3654
4047
|
async function defaultBase(resolvedRoot) {
|
|
3655
4048
|
for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
|
|
3656
4049
|
try {
|
|
3657
|
-
await
|
|
4050
|
+
await exec10("git", ["rev-parse", "--verify", "--quiet", ref], {
|
|
3658
4051
|
cwd: resolvedRoot
|
|
3659
4052
|
});
|
|
3660
4053
|
return ref;
|
|
@@ -3667,7 +4060,7 @@ function anchorsTouched(record, changedFiles) {
|
|
|
3667
4060
|
return matchingPaths(record.files, changedFiles);
|
|
3668
4061
|
}
|
|
3669
4062
|
async function computeReview(rootDir, base, options = {}) {
|
|
3670
|
-
const resolvedRoot =
|
|
4063
|
+
const resolvedRoot = path20.resolve(rootDir);
|
|
3671
4064
|
const head = await getCurrentGitHash(resolvedRoot);
|
|
3672
4065
|
if (head === "unknown") return null;
|
|
3673
4066
|
const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
|
|
@@ -3717,7 +4110,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
3717
4110
|
analyzed,
|
|
3718
4111
|
async (relPath) => {
|
|
3719
4112
|
try {
|
|
3720
|
-
await
|
|
4113
|
+
await fs15.access(path20.join(resolvedRoot, relPath));
|
|
3721
4114
|
return true;
|
|
3722
4115
|
} catch {
|
|
3723
4116
|
return false;
|
|
@@ -3750,7 +4143,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
3750
4143
|
}
|
|
3751
4144
|
return finalize();
|
|
3752
4145
|
}
|
|
3753
|
-
var
|
|
4146
|
+
var exec10, MAX_ANALYZED_FILES;
|
|
3754
4147
|
var init_review = __esm({
|
|
3755
4148
|
"src/review/review.ts"() {
|
|
3756
4149
|
"use strict";
|
|
@@ -3762,7 +4155,7 @@ var init_review = __esm({
|
|
|
3762
4155
|
init_cochange();
|
|
3763
4156
|
init_evidence2();
|
|
3764
4157
|
init_snapshot();
|
|
3765
|
-
|
|
4158
|
+
exec10 = promisify12(execFile12);
|
|
3766
4159
|
MAX_ANALYZED_FILES = 50;
|
|
3767
4160
|
}
|
|
3768
4161
|
});
|
|
@@ -3851,437 +4244,6 @@ var init_onboarding = __esm({
|
|
|
3851
4244
|
}
|
|
3852
4245
|
});
|
|
3853
4246
|
|
|
3854
|
-
// src/setup/files.ts
|
|
3855
|
-
import fs15 from "fs/promises";
|
|
3856
|
-
import path20 from "path";
|
|
3857
|
-
import { randomUUID as randomUUID5 } from "crypto";
|
|
3858
|
-
async function readText(root, file) {
|
|
3859
|
-
try {
|
|
3860
|
-
const text2 = await readBoundedFile(await storePath(root, file), 2 * 1024 * 1024);
|
|
3861
|
-
if (text2 === null) throw new Error("Setup input is not a regular file or exceeds 2 MiB: " + file);
|
|
3862
|
-
return text2;
|
|
3863
|
-
} catch (error) {
|
|
3864
|
-
if (error.code === "ENOENT") return null;
|
|
3865
|
-
throw error;
|
|
3866
|
-
}
|
|
3867
|
-
}
|
|
3868
|
-
function managedBlock(text2, start, end, body) {
|
|
3869
|
-
const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
|
|
3870
|
-
if (starts !== ends || starts > 1 || starts === 1 && text2.indexOf(end) < text2.indexOf(start)) {
|
|
3871
|
-
throw new Error("Ambiguous Mason instruction/configuration markers; repair the marked block before setup.");
|
|
3872
|
-
}
|
|
3873
|
-
const eol = text2.includes("\r\n") ? "\r\n" : "\n";
|
|
3874
|
-
const block = [start, body.replace(/\r?\n/g, eol), end].join(eol);
|
|
3875
|
-
if (starts) return text2.slice(0, text2.indexOf(start)) + block + text2.slice(text2.indexOf(end) + end.length);
|
|
3876
|
-
return text2 + (text2.length && !text2.endsWith("\n") ? eol : "") + (text2.length ? eol : "") + block + eol;
|
|
3877
|
-
}
|
|
3878
|
-
async function applyEdit(root, edit) {
|
|
3879
|
-
const current = await readText(root, edit.path);
|
|
3880
|
-
if (current === edit.after) return false;
|
|
3881
|
-
if (current !== edit.before) throw new Error("Setup input changed during installation: " + edit.path + ". Rerun setup to resume.");
|
|
3882
|
-
const file = await storePath(root, edit.path, true);
|
|
3883
|
-
const temporary = path20.join(path20.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
3884
|
-
try {
|
|
3885
|
-
const mode = await fs15.stat(file).then((s) => s.mode & 511, () => 384);
|
|
3886
|
-
const handle = await fs15.open(temporary, "wx", mode);
|
|
3887
|
-
try {
|
|
3888
|
-
await handle.writeFile(edit.after, "utf8");
|
|
3889
|
-
await handle.sync();
|
|
3890
|
-
} finally {
|
|
3891
|
-
await handle.close();
|
|
3892
|
-
}
|
|
3893
|
-
if (await readText(root, edit.path) !== edit.before) throw new Error("Setup input changed during installation: " + edit.path);
|
|
3894
|
-
await fs15.rename(temporary, file);
|
|
3895
|
-
return true;
|
|
3896
|
-
} finally {
|
|
3897
|
-
await fs15.rm(temporary, { force: true });
|
|
3898
|
-
}
|
|
3899
|
-
}
|
|
3900
|
-
var init_files2 = __esm({
|
|
3901
|
-
"src/setup/files.ts"() {
|
|
3902
|
-
"use strict";
|
|
3903
|
-
init_files();
|
|
3904
|
-
init_storage();
|
|
3905
|
-
}
|
|
3906
|
-
});
|
|
3907
|
-
|
|
3908
|
-
// src/setup/launcher.ts
|
|
3909
|
-
var BOOTSTRAP, POWERSHELL_BOOTSTRAP, mcpCommand, hookCommand, SHELL_LAUNCHER, POWERSHELL_LAUNCHER, LAUNCHER;
|
|
3910
|
-
var init_launcher = __esm({
|
|
3911
|
-
"src/setup/launcher.ts"() {
|
|
3912
|
-
"use strict";
|
|
3913
|
-
BOOTSTRAP = "require(require('node:path').join(require('node:child_process').execFileSync('git',['rev-parse','--show-toplevel'],{encoding:'utf8'}).trim(),'.mason','run.cjs'))";
|
|
3914
|
-
POWERSHELL_BOOTSTRAP = "& (Join-Path (git rev-parse --show-toplevel) '.mason/run.ps1')";
|
|
3915
|
-
mcpCommand = (host, runtime) => !runtime?.bundle ? { command: "node", args: ["-e", BOOTSTRAP, "--", host, "mcp"] } : process.platform === "win32" ? { command: "powershell.exe", args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", POWERSHELL_BOOTSTRAP, host, "mcp"] } : { command: "sh", args: ["-c", 'exec sh "$(git rev-parse --show-toplevel)/.mason/run.sh" "$@"', "mason", host, "mcp"] };
|
|
3916
|
-
hookCommand = (host, runtime) => !runtime?.bundle ? `node -e "${BOOTSTRAP}" -- ${host} auto` : process.platform === "win32" ? `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "${POWERSHELL_BOOTSTRAP} ${host} auto"` : `sh "$(git rev-parse --show-toplevel)/.mason/run.sh" ${host} auto`;
|
|
3917
|
-
SHELL_LAUNCHER = `#!/bin/sh
|
|
3918
|
-
set -eu
|
|
3919
|
-
advisory=0
|
|
3920
|
-
if [ "\${2-}" = auto ] && [ "\${3-}" = hook ]; then advisory=1; fi
|
|
3921
|
-
fail() {
|
|
3922
|
-
if [ "$advisory" -eq 1 ]; then printf '%s\\n' '{"systemMessage":"Mason runtime unavailable. Rerun mason setup for this checkout; verification was not established."}'; exit 0; fi
|
|
3923
|
-
echo 'Mason runtime unavailable. Rerun mason setup for this checkout.' >&2; exit 2
|
|
3924
|
-
}
|
|
3925
|
-
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) || fail
|
|
3926
|
-
case "\${1-}" in codex|claude) ;; *) fail ;; esac
|
|
3927
|
-
pointer="$root/.mason/runtime/$1.txt"
|
|
3928
|
-
[ -f "$pointer" ] || fail
|
|
3929
|
-
runtime=$(cat "$pointer") || fail
|
|
3930
|
-
case "$runtime" in ''|*[!0-9a-f]*) fail ;; esac
|
|
3931
|
-
[ "\${#runtime}" -eq 24 ] || fail
|
|
3932
|
-
[ -x "$root/.mason/runtime/$runtime/node" ] || fail
|
|
3933
|
-
if [ "$advisory" -eq 1 ]; then
|
|
3934
|
-
"$root/.mason/runtime/$runtime/node" "$root/.mason/run.cjs" "$@" || fail
|
|
3935
|
-
else
|
|
3936
|
-
exec "$root/.mason/runtime/$runtime/node" "$root/.mason/run.cjs" "$@"
|
|
3937
|
-
fi
|
|
3938
|
-
`;
|
|
3939
|
-
POWERSHELL_LAUNCHER = `# Managed by mason setup; no system Node installation is used.
|
|
3940
|
-
$ErrorActionPreference = 'Stop'
|
|
3941
|
-
$advisory = $args.Count -ge 3 -and $args[1] -eq 'auto' -and $args[2] -eq 'hook'
|
|
3942
|
-
try {
|
|
3943
|
-
if ($args.Count -lt 2 -or $args[0] -notin @('codex','claude')) { throw 'Invalid Mason host.' }
|
|
3944
|
-
$runtime = (Get-Content -LiteralPath (Join-Path $PSScriptRoot ('runtime/' + $args[0] + '.txt')) -Raw).Trim()
|
|
3945
|
-
if ($runtime -cnotmatch '^[a-f0-9]{24}$') { throw 'Invalid Mason runtime pointer.' }
|
|
3946
|
-
& (Join-Path $PSScriptRoot ('runtime/' + $runtime + '/node.exe')) (Join-Path $PSScriptRoot 'run.cjs') @args
|
|
3947
|
-
if ($LASTEXITCODE -ne 0) { throw 'Mason runtime failed.' }
|
|
3948
|
-
} catch {
|
|
3949
|
-
if ($advisory) { Write-Output '{"systemMessage":"Mason runtime unavailable. Rerun mason setup for this checkout; verification was not established."}'; exit 0 }
|
|
3950
|
-
[Console]::Error.WriteLine($_.Exception.Message); exit 2
|
|
3951
|
-
}
|
|
3952
|
-
`;
|
|
3953
|
-
LAUNCHER = `// Managed by mason-auto setup. Rerun setup to install this checkout's pinned runtime.
|
|
3954
|
-
const fs = require('node:fs');
|
|
3955
|
-
const path = require('node:path');
|
|
3956
|
-
const {pathToFileURL} = require('node:url');
|
|
3957
|
-
const launchArgs = process.argv.slice(process.argv[1] && path.resolve(process.argv[1]) === __filename ? 2 : 1);
|
|
3958
|
-
(async () => {
|
|
3959
|
-
const root = path.dirname(__dirname);
|
|
3960
|
-
const args = launchArgs;
|
|
3961
|
-
const [host, command, ...rest] = args;
|
|
3962
|
-
if (!['codex','claude'].includes(host) || !['auto','mcp'].includes(command)) throw new Error('Invalid Mason launcher arguments.');
|
|
3963
|
-
const setup = JSON.parse(fs.readFileSync(path.join(__dirname, 'setup.json'), 'utf8'));
|
|
3964
|
-
const entry = setup.hosts?.[host];
|
|
3965
|
-
if (setup.version !== 1 || !entry || !/^[a-f0-9]{24}$/.test(entry.runtime?.id)) throw new Error('Mason is not configured for this host. Rerun mason-auto setup.');
|
|
3966
|
-
const base = path.join(__dirname, 'runtime', entry.runtime.id);
|
|
3967
|
-
if (entry.runtime.bundle) {
|
|
3968
|
-
const node = process.platform === 'win32' ? 'node.exe' : 'node';
|
|
3969
|
-
if (fs.realpathSync(process.execPath) !== fs.realpathSync(path.join(base, node))) throw new Error('Mason runtime pointer differs from the configured version. Rerun setup.');
|
|
3970
|
-
}
|
|
3971
|
-
const binary = entry.runtime.bundle ? path.join(base, 'app', 'dist', 'mason-' + command + '.js')
|
|
3972
|
-
: path.join(base, 'node_modules', 'mason-context', 'dist', 'mason-' + command + '.js');
|
|
3973
|
-
if (!fs.existsSync(binary)) throw new Error('The pinned Mason runtime is missing in this checkout. Rerun mason-auto setup --host ' + host + '.');
|
|
3974
|
-
process.env.MASON_SETUP_ROOT = root;
|
|
3975
|
-
process.env.MASON_SETUP_HOST = host;
|
|
3976
|
-
process.env.MASON_SETUP_REVISION = entry.revision;
|
|
3977
|
-
process.argv = [process.execPath, binary, ...rest];
|
|
3978
|
-
await import(pathToFileURL(binary).href);
|
|
3979
|
-
})().catch(error => {
|
|
3980
|
-
const message = 'Mason setup unavailable: ' + error.message;
|
|
3981
|
-
if (launchArgs[1] === 'auto' && launchArgs[2] === 'hook') { console.log(JSON.stringify({systemMessage: message})); process.exitCode = 0; }
|
|
3982
|
-
else { console.error(message); process.exitCode = 2; }
|
|
3983
|
-
});
|
|
3984
|
-
`;
|
|
3985
|
-
}
|
|
3986
|
-
});
|
|
3987
|
-
|
|
3988
|
-
// src/setup/config.ts
|
|
3989
|
-
import { isDeepStrictEqual } from "util";
|
|
3990
|
-
import { parse, stringify } from "smol-toml";
|
|
3991
|
-
import { z as z15 } from "zod";
|
|
3992
|
-
async function instructionEdits(root, host) {
|
|
3993
|
-
const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
|
|
3994
|
-
const existing = found.find((f) => f.text !== null);
|
|
3995
|
-
const primary = host === "codex" && found[0].text === null ? { path: "AGENTS.md", text: null } : existing ?? { path: "AGENTS.md", text: null };
|
|
3996
|
-
const originalGuidance = primary.text === null && existing ? "Follow the existing project conventions in " + existing.path + ".\n" : "";
|
|
3997
|
-
const edits = [{
|
|
3998
|
-
path: primary.path,
|
|
3999
|
-
before: primary.text,
|
|
4000
|
-
after: managedBlock(primary.text ?? originalGuidance, "<!-- mason:start -->", "<!-- mason:end -->", CLAUDE_MD_SECTION.split("\n").slice(1, -1).join("\n"))
|
|
4001
|
-
}];
|
|
4002
|
-
if (primary.path === "AGENTS.md") {
|
|
4003
|
-
const secondary = found.filter((f) => f.path !== "AGENTS.md" && f.text !== null);
|
|
4004
|
-
if (host === "claude" && !secondary.length) secondary.push({ path: "CLAUDE.md", text: null });
|
|
4005
|
-
for (const file of secondary) {
|
|
4006
|
-
const text2 = file.text ?? "";
|
|
4007
|
-
const legacy = text2.includes("<!-- mason:start -->") || text2.includes("<!-- mason:end -->");
|
|
4008
|
-
const imported = "@" + (file.path.startsWith(".claude/") ? "../AGENTS.md" : "AGENTS.md");
|
|
4009
|
-
if (text2.trim() === imported) continue;
|
|
4010
|
-
const pointer = "Mason project knowledge and shared project instructions:\n" + imported;
|
|
4011
|
-
edits.push({
|
|
4012
|
-
path: file.path,
|
|
4013
|
-
before: file.text,
|
|
4014
|
-
after: managedBlock(text2, legacy ? "<!-- mason:start -->" : POINTER_START, legacy ? "<!-- mason:end -->" : POINTER_END, pointer)
|
|
4015
|
-
});
|
|
4016
|
-
}
|
|
4017
|
-
}
|
|
4018
|
-
return edits;
|
|
4019
|
-
}
|
|
4020
|
-
function managedMcp(previous, host, runtime) {
|
|
4021
|
-
const options = previous === void 0 ? {} : object.parse(previous);
|
|
4022
|
-
for (const key of ["command", "args", "url", "type", "headers", "http_headers", "env_http_headers", "bearer_token_env_var"]) delete options[key];
|
|
4023
|
-
return { ...options, ...mcpCommand(host, runtime) };
|
|
4024
|
-
}
|
|
4025
|
-
async function mcpEdit(root, host, runtime) {
|
|
4026
|
-
const file = mcpPath(host), before = await readText(root, file);
|
|
4027
|
-
if (host === "claude") {
|
|
4028
|
-
const config2 = before === null ? {} : object.parse(JSON.parse(before));
|
|
4029
|
-
const servers2 = object.parse(config2.mcpServers ?? {});
|
|
4030
|
-
return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host, runtime) } }, null, 2) + "\n" };
|
|
4031
|
-
}
|
|
4032
|
-
const config = parse(before ?? "");
|
|
4033
|
-
const managed = (before ?? "").includes(TOML_START);
|
|
4034
|
-
const servers = config.mcp_servers;
|
|
4035
|
-
const desired = managedMcp(servers?.mason, host, runtime);
|
|
4036
|
-
let base = before ?? "";
|
|
4037
|
-
if (servers?.mason && !managed) {
|
|
4038
|
-
const headers = [...base.matchAll(/^\s*\[\[?.+?\]\]?[^\S\r\n]*(?:#.*)?$/gm)];
|
|
4039
|
-
const spans = headers.flatMap((header, index2) => /^\s*\[mcp_servers\.mason(?:\.[A-Za-z0-9_-]+)*\]/.test(header[0]) ? [{ start: header.index, end: headers[index2 + 1]?.index ?? base.length }] : []);
|
|
4040
|
-
if (!spans.length) throw new Error("Cannot safely migrate the existing Mason MCP TOML entry. Use an explicit [mcp_servers.mason] table and rerun setup; the file was retained.");
|
|
4041
|
-
for (const span of spans.reverse()) base = base.slice(0, span.start) + base.slice(span.end);
|
|
4042
|
-
}
|
|
4043
|
-
const after = managedBlock(base, TOML_START, TOML_END, stringify({ mcp_servers: { mason: desired } }).trimEnd());
|
|
4044
|
-
const parsed = parse(after);
|
|
4045
|
-
const expected = { ...config, mcp_servers: { ...servers ?? {}, mason: desired } };
|
|
4046
|
-
if (!isDeepStrictEqual(parsed, expected)) throw new Error("Could not safely configure the Mason MCP entry without changing other settings.");
|
|
4047
|
-
return { path: file, before, after };
|
|
4048
|
-
}
|
|
4049
|
-
async function hookEdits(root, host, runtime) {
|
|
4050
|
-
const file = configPath(host), before = await readText(root, file);
|
|
4051
|
-
const { planAutomationInstall: planAutomationInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
4052
|
-
const plan = await planAutomationInstall2(root, host, hookCommand(host, runtime));
|
|
4053
|
-
return [
|
|
4054
|
-
{ path: file, before, after: JSON.stringify(plan.config, null, 2) + "\n" },
|
|
4055
|
-
{ path: ".mason/automation.json", before: await readText(root, ".mason/automation.json"), after: JSON.stringify(plan.record, null, 2) + "\n" }
|
|
4056
|
-
];
|
|
4057
|
-
}
|
|
4058
|
-
async function ancillaryEdits(root) {
|
|
4059
|
-
const ignore = await readText(root, ".gitignore");
|
|
4060
|
-
const { git: git3 } = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
|
|
4061
|
-
let parentIgnored = false;
|
|
4062
|
-
try {
|
|
4063
|
-
parentIgnored = !!(await git3(root, "check-ignore", "--no-index", ".mason")).trim();
|
|
4064
|
-
} catch (error) {
|
|
4065
|
-
if (error.code !== 1) throw error;
|
|
4066
|
-
}
|
|
4067
|
-
const retainParentRule = parentIgnored || !!ignore?.replace(/\r\n/g, "\n").includes("# mason:ignore:start\n!/.mason/\n/.mason/*");
|
|
4068
|
-
const rules = [...retainParentRule ? ["!/.mason/", "/.mason/*"] : [], "!/.mason/decisions/", "!/.mason/decisions/**", "!/.mason/setup.json", "!/.mason/automation.json", "!/.mason/project.json", "!/.mason/run.cjs", "!/.mason/run.sh", "!/.mason/run.ps1", "/.mason/reports/", "/.mason/runtime/"].join("\n");
|
|
4069
|
-
return [
|
|
4070
|
-
{ path: ".gitignore", before: ignore, after: managedBlock(ignore ?? "", "# mason:ignore:start", "# mason:ignore:end", rules) },
|
|
4071
|
-
{ path: ".mason/run.cjs", before: await readText(root, ".mason/run.cjs"), after: LAUNCHER },
|
|
4072
|
-
{ path: ".mason/run.sh", before: await readText(root, ".mason/run.sh"), after: SHELL_LAUNCHER },
|
|
4073
|
-
{ path: ".mason/run.ps1", before: await readText(root, ".mason/run.ps1"), after: POWERSHELL_LAUNCHER }
|
|
4074
|
-
];
|
|
4075
|
-
}
|
|
4076
|
-
async function inspectHostConfig(root, host, plannedMcp, runtime) {
|
|
4077
|
-
const text2 = plannedMcp ?? await readText(root, mcpPath(host));
|
|
4078
|
-
const config = host === "codex" ? parse(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
|
|
4079
|
-
const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
|
|
4080
|
-
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
|
|
4081
|
-
const expected = hookConfig(host, hookCommand(host, runtime));
|
|
4082
|
-
const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
|
|
4083
|
-
return {
|
|
4084
|
-
mcp,
|
|
4085
|
-
mcpDisabled: mcp?.enabled === false,
|
|
4086
|
-
hooks: Object.fromEntries(Object.keys(expected.hooks).map((event) => [
|
|
4087
|
-
event,
|
|
4088
|
-
(hooks.hooks?.[event] ?? []).flatMap((group) => group.hooks.filter((handler) => handler.command === expected.hooks.SessionStart[0].hooks[0].command).map((handler) => ({ ...group, hooks: [handler] })) ?? [])
|
|
4089
|
-
])),
|
|
4090
|
-
disabled: hooks.disableAllHooks === true || config.features?.hooks === false || config.features?.codex_hooks === false
|
|
4091
|
-
};
|
|
4092
|
-
}
|
|
4093
|
-
var object, mcpPath, TOML_START, TOML_END, POINTER_START, POINTER_END;
|
|
4094
|
-
var init_config = __esm({
|
|
4095
|
-
"src/setup/config.ts"() {
|
|
4096
|
-
"use strict";
|
|
4097
|
-
init_init();
|
|
4098
|
-
init_docs();
|
|
4099
|
-
init_adapters();
|
|
4100
|
-
init_install();
|
|
4101
|
-
init_files2();
|
|
4102
|
-
init_launcher();
|
|
4103
|
-
object = z15.record(z15.unknown());
|
|
4104
|
-
mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
4105
|
-
TOML_START = "# mason:mcp:start";
|
|
4106
|
-
TOML_END = "# mason:mcp:end";
|
|
4107
|
-
POINTER_START = "<!-- mason:agents:start -->";
|
|
4108
|
-
POINTER_END = "<!-- mason:agents:end -->";
|
|
4109
|
-
}
|
|
4110
|
-
});
|
|
4111
|
-
|
|
4112
|
-
// src/distribution/bundle.ts
|
|
4113
|
-
import fs16 from "fs/promises";
|
|
4114
|
-
import path21 from "path";
|
|
4115
|
-
import { createHash as createHash4 } from "crypto";
|
|
4116
|
-
import { z as z16 } from "zod";
|
|
4117
|
-
async function readBundle(root) {
|
|
4118
|
-
const bytes = await fs16.readFile(await storePath(root, "bundle.json"));
|
|
4119
|
-
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
4120
|
-
const windows = manifest.target.startsWith("win32-");
|
|
4121
|
-
for (const file of Object.keys(manifest.files)) {
|
|
4122
|
-
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path21.isAbsolute(file)) {
|
|
4123
|
-
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
4124
|
-
}
|
|
4125
|
-
}
|
|
4126
|
-
for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-auto.js", "app/dist/mason-mcp.js"]) {
|
|
4127
|
-
if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
|
|
4128
|
-
}
|
|
4129
|
-
return { manifest, manifestHash: sha256(bytes) };
|
|
4130
|
-
}
|
|
4131
|
-
async function verifyBundle(root, expectedHash) {
|
|
4132
|
-
const bundle = await readBundle(root);
|
|
4133
|
-
if (expectedHash && bundle.manifestHash !== expectedHash) throw new Error("Mason bundle manifest changed.");
|
|
4134
|
-
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.");
|
|
4135
|
-
const files = Object.keys(bundle.manifest.files);
|
|
4136
|
-
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
4137
|
-
if (sha256(await fs16.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
4138
|
-
}));
|
|
4139
|
-
return bundle;
|
|
4140
|
-
}
|
|
4141
|
-
var targets, digest2, bundleSchema, sha256;
|
|
4142
|
-
var init_bundle = __esm({
|
|
4143
|
-
"src/distribution/bundle.ts"() {
|
|
4144
|
-
"use strict";
|
|
4145
|
-
init_storage();
|
|
4146
|
-
targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
4147
|
-
digest2 = z16.string().regex(/^[a-f0-9]{64}$/);
|
|
4148
|
-
bundleSchema = z16.object({
|
|
4149
|
-
format: z16.literal(1),
|
|
4150
|
-
version: z16.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
4151
|
-
target: z16.enum(targets),
|
|
4152
|
-
nodeVersion: z16.string(),
|
|
4153
|
-
files: z16.record(digest2)
|
|
4154
|
-
}).strict();
|
|
4155
|
-
sha256 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
|
|
4156
|
-
}
|
|
4157
|
-
});
|
|
4158
|
-
|
|
4159
|
-
// src/setup/runtime.ts
|
|
4160
|
-
var runtime_exports = {};
|
|
4161
|
-
__export(runtime_exports, {
|
|
4162
|
-
installRuntime: () => installRuntime,
|
|
4163
|
-
sourceRuntime: () => sourceRuntime,
|
|
4164
|
-
verifyRuntime: () => verifyRuntime
|
|
4165
|
-
});
|
|
4166
|
-
import fs17 from "fs/promises";
|
|
4167
|
-
import path22 from "path";
|
|
4168
|
-
import { fileURLToPath } from "url";
|
|
4169
|
-
import { randomUUID as randomUUID6, createHash as createHash5 } from "crypto";
|
|
4170
|
-
import { execFile as execFile10 } from "child_process";
|
|
4171
|
-
import { promisify as promisify10 } from "util";
|
|
4172
|
-
async function packageRoot() {
|
|
4173
|
-
let directory = path22.dirname(fileURLToPath(import.meta.url));
|
|
4174
|
-
for (let i = 0; i < 8; i++) {
|
|
4175
|
-
try {
|
|
4176
|
-
const pkg = JSON.parse(await fs17.readFile(path22.join(directory, "package.json"), "utf8"));
|
|
4177
|
-
if (pkg.name === "mason-context") return directory;
|
|
4178
|
-
} catch {
|
|
4179
|
-
}
|
|
4180
|
-
directory = path22.dirname(directory);
|
|
4181
|
-
}
|
|
4182
|
-
throw new Error("Cannot locate the executing Mason distribution. Reinstall Mason and rerun setup.");
|
|
4183
|
-
}
|
|
4184
|
-
async function sourceRuntime() {
|
|
4185
|
-
const source = await packageRoot();
|
|
4186
|
-
const pkg = JSON.parse(await fs17.readFile(path22.join(source, "package.json"), "utf8"));
|
|
4187
|
-
const hashes = Object.fromEntries(await Promise.all(BINARIES.map(async (file) => [file, checksum(await fs17.readFile(path22.join(source, file)))])));
|
|
4188
|
-
const bundleRoot = path22.dirname(source);
|
|
4189
|
-
const bundleExists = await fs17.lstat(path22.join(bundleRoot, "bundle.json")).then(() => true, (error) => {
|
|
4190
|
-
if (error.code === "ENOENT") return false;
|
|
4191
|
-
throw error;
|
|
4192
|
-
});
|
|
4193
|
-
if (bundleExists) {
|
|
4194
|
-
const { manifest, manifestHash } = await verifyBundle(bundleRoot);
|
|
4195
|
-
if (manifest.version !== pkg.version) throw new Error("Mason package and bundle versions differ.");
|
|
4196
|
-
return { source, bundleRoot, runtime: runtimeSchema.parse({
|
|
4197
|
-
id: manifestHash.slice(0, 24),
|
|
4198
|
-
version: pkg.version,
|
|
4199
|
-
hashes,
|
|
4200
|
-
bundle: { target: manifest.target, manifestHash }
|
|
4201
|
-
}) };
|
|
4202
|
-
}
|
|
4203
|
-
return { source, runtime: runtimeSchema.parse({ id: hash([pkg.version, pkg.dependencies, hashes]).slice(0, 24), version: pkg.version, hashes }) };
|
|
4204
|
-
}
|
|
4205
|
-
async function verifyRuntime(root, runtime) {
|
|
4206
|
-
try {
|
|
4207
|
-
const saved = await readStoreJson(root, `.mason/runtime/${runtime.id}/receipt.json`);
|
|
4208
|
-
if (JSON.stringify(saved) !== JSON.stringify(runtime)) return false;
|
|
4209
|
-
if (runtime.bundle) {
|
|
4210
|
-
const base2 = await storePath(root, `.mason/runtime/${runtime.id}`);
|
|
4211
|
-
const { manifest } = await verifyBundle(base2, runtime.bundle.manifestHash);
|
|
4212
|
-
if (manifest.version !== runtime.version || manifest.target !== runtime.bundle.target) return false;
|
|
4213
|
-
for (const file of BINARIES) if (manifest.files["app/" + file] !== runtime.hashes[file]) return false;
|
|
4214
|
-
return true;
|
|
4215
|
-
}
|
|
4216
|
-
const base = `.mason/runtime/${runtime.id}/node_modules/mason-context/`;
|
|
4217
|
-
const pkg = await readStoreJson(root, base + "package.json");
|
|
4218
|
-
if (pkg?.name !== "mason-context" || pkg.version !== runtime.version) return false;
|
|
4219
|
-
for (const file of BINARIES) {
|
|
4220
|
-
if (checksum(await fs17.readFile(await storePath(root, base + file))) !== runtime.hashes[file]) return false;
|
|
4221
|
-
}
|
|
4222
|
-
return true;
|
|
4223
|
-
} catch {
|
|
4224
|
-
return false;
|
|
4225
|
-
}
|
|
4226
|
-
}
|
|
4227
|
-
async function installRuntime(root, selected) {
|
|
4228
|
-
const { runtime, source } = selected;
|
|
4229
|
-
if (await verifyRuntime(root, runtime)) return runtime;
|
|
4230
|
-
const relative = `.mason/runtime/${runtime.id}`;
|
|
4231
|
-
const target = await storePath(root, relative, true);
|
|
4232
|
-
const stage = await storePath(root, ".mason/runtime/.install-" + randomUUID6(), true);
|
|
4233
|
-
await fs17.mkdir(stage);
|
|
4234
|
-
try {
|
|
4235
|
-
if (runtime.bundle) {
|
|
4236
|
-
if (!selected.bundleRoot) throw new Error("Standalone bundle source is missing.");
|
|
4237
|
-
const { manifest } = await verifyBundle(selected.bundleRoot, runtime.bundle.manifestHash);
|
|
4238
|
-
for (const file of ["bundle.json", ...Object.keys(manifest.files)]) {
|
|
4239
|
-
const from = await storePath(selected.bundleRoot, file);
|
|
4240
|
-
const to = await storePath(stage, file, true);
|
|
4241
|
-
await fs17.copyFile(from, to);
|
|
4242
|
-
await fs17.chmod(to, (await fs17.stat(from)).mode & 511);
|
|
4243
|
-
}
|
|
4244
|
-
await verifyBundle(stage, runtime.bundle.manifestHash);
|
|
4245
|
-
} else {
|
|
4246
|
-
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
4247
|
-
const options = { timeout: 12e4, maxBuffer: 2 * 1024 * 1024, windowsHide: true };
|
|
4248
|
-
const packed = JSON.parse((await exec10(npm, ["pack", "--ignore-scripts", "--json", "--pack-destination", stage], { ...options, cwd: source })).stdout);
|
|
4249
|
-
const filename = packed[0]?.filename;
|
|
4250
|
-
if (typeof filename !== "string" || path22.basename(filename) !== filename) throw new Error("npm did not produce a Mason package archive.");
|
|
4251
|
-
await fs17.rename(path22.join(stage, filename), path22.join(stage, "mason.tgz"));
|
|
4252
|
-
await fs17.writeFile(path22.join(stage, "package.json"), JSON.stringify({ name: "mason-project-runtime", private: true, version: "1.0.0" }));
|
|
4253
|
-
await exec10(npm, ["install", "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund", "--save-exact", "./mason.tgz"], { ...options, cwd: stage });
|
|
4254
|
-
for (const file of BINARIES) {
|
|
4255
|
-
if (checksum(await fs17.readFile(path22.join(stage, "node_modules/mason-context", file))) !== runtime.hashes[file]) throw new Error("Installed Mason binary differs from the selected distribution.");
|
|
4256
|
-
}
|
|
4257
|
-
}
|
|
4258
|
-
await fs17.writeFile(path22.join(stage, "receipt.json"), JSON.stringify(runtime, null, 2) + "\n");
|
|
4259
|
-
try {
|
|
4260
|
-
await fs17.rename(target, target + ".previous-" + randomUUID6());
|
|
4261
|
-
} catch (error) {
|
|
4262
|
-
if (error.code !== "ENOENT") throw error;
|
|
4263
|
-
}
|
|
4264
|
-
await fs17.rename(stage, target);
|
|
4265
|
-
if (!await verifyRuntime(root, runtime)) throw new Error("The installed Mason runtime could not be verified.");
|
|
4266
|
-
return runtime;
|
|
4267
|
-
} finally {
|
|
4268
|
-
await fs17.rm(stage, { recursive: true, force: true });
|
|
4269
|
-
}
|
|
4270
|
-
}
|
|
4271
|
-
var exec10, checksum, BINARIES;
|
|
4272
|
-
var init_runtime2 = __esm({
|
|
4273
|
-
"src/setup/runtime.ts"() {
|
|
4274
|
-
"use strict";
|
|
4275
|
-
init_model();
|
|
4276
|
-
init_storage();
|
|
4277
|
-
init_evidence();
|
|
4278
|
-
init_bundle();
|
|
4279
|
-
exec10 = promisify10(execFile10);
|
|
4280
|
-
checksum = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
4281
|
-
BINARIES = ["dist/mason-auto.js", "dist/mason-mcp.js"];
|
|
4282
|
-
}
|
|
4283
|
-
});
|
|
4284
|
-
|
|
4285
4247
|
// src/setup/status.ts
|
|
4286
4248
|
var status_exports = {};
|
|
4287
4249
|
__export(status_exports, {
|
|
@@ -4306,34 +4268,32 @@ async function setupStatus(dir) {
|
|
|
4306
4268
|
};
|
|
4307
4269
|
}
|
|
4308
4270
|
const hosts = {};
|
|
4309
|
-
const
|
|
4271
|
+
const installed = await installedCommand();
|
|
4310
4272
|
const automation = await automationStatus(ws.root);
|
|
4311
4273
|
for (const host of ["codex", "claude"]) {
|
|
4312
4274
|
const entry = setup.hosts[host];
|
|
4313
4275
|
if (!entry) continue;
|
|
4314
|
-
const launcherCurrent = commonLauncherCurrent && (!entry.runtime.bundle || await readText(ws.root, ".mason/run.sh") === SHELL_LAUNCHER && await readText(ws.root, ".mason/run.ps1") === POWERSHELL_LAUNCHER && await readText(ws.root, `.mason/runtime/${host}.txt`) === entry.runtime.id + "\n");
|
|
4315
4276
|
const instructions = await instructionEdits(ws.root, host);
|
|
4316
4277
|
const instructionsCurrent = instructions.every((edit) => edit.before === edit.after);
|
|
4317
|
-
const
|
|
4318
|
-
const
|
|
4319
|
-
const
|
|
4320
|
-
const hooks = isDeepStrictEqual2(config.hooks, hookConfig(host, hookCommand(host, entry.runtime)).hooks) && !config.disabled;
|
|
4278
|
+
const config = await inspectHostConfig(ws.root, host, void 0);
|
|
4279
|
+
const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual2({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host));
|
|
4280
|
+
const hooks = isDeepStrictEqual2(config.hooks, hookConfig(host, hookCommand(host)).hooks) && !config.disabled;
|
|
4321
4281
|
const local = await loadSetupReceipt(ws.root, ws.directory, host);
|
|
4322
4282
|
const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
|
|
4323
|
-
const observation = await readObservation(ws.root, ws.directory, host, entry.revision);
|
|
4283
|
+
const observation = await readObservation(ws.root, ws.directory, host, entry.revision, installed.version ?? "unavailable");
|
|
4324
4284
|
const sessions = Object.values(observation?.sessions ?? {}).sort((a, b) => b.at.localeCompare(a.at));
|
|
4325
4285
|
const complete = sessions.find((session) => events.every((event) => session.events.includes(event)));
|
|
4326
4286
|
const observedEvents = complete?.events ?? sessions[0]?.events ?? [];
|
|
4327
4287
|
const pending = [];
|
|
4328
|
-
if (!installed
|
|
4288
|
+
if (!installed.available) pending.push(installed.message);
|
|
4329
4289
|
if (!mcp || !hooks || !instructionsCurrent || !configured) pending.push("Rerun setup to reconcile project configuration; inspect any disabled host settings.");
|
|
4330
4290
|
if (!observation?.contextCalls) pending.push("Start a new assistant session and request task context through Mason's get_context tool.");
|
|
4331
4291
|
if (!complete) pending.push("Review/trust the host configuration, then complete a normal task to observe the full hook lifecycle.");
|
|
4332
4292
|
if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason-auto check and inspect its diagnostics.");
|
|
4333
|
-
const healthy = installed &&
|
|
4293
|
+
const healthy = installed.available && mcp && hooks && instructionsCurrent && configured && automation.status !== "unavailable";
|
|
4334
4294
|
hosts[host] = {
|
|
4335
4295
|
status: healthy ? complete && observation?.contextCalls ? "active" : "pending" : "attention",
|
|
4336
|
-
runtime: installed
|
|
4296
|
+
runtime: installed.available ? "global (" + installed.version + ")" : "unavailable-on-path",
|
|
4337
4297
|
mcp: mcp ? "configured" : "changed",
|
|
4338
4298
|
instructions: instructionsCurrent ? "current" : "changed",
|
|
4339
4299
|
hookConfiguration: hooks ? "configured" : "disabled-or-changed",
|
|
@@ -4363,7 +4323,7 @@ function summarizeActivation(status) {
|
|
|
4363
4323
|
` Runtime: ${state.runtime}; MCP: ${state.mcp}; instructions: ${state.instructions}.`,
|
|
4364
4324
|
` Hooks: ${state.hookConfiguration}; observed: ${state.observedEvents.join(", ") || "none"}.`,
|
|
4365
4325
|
` Task context requests: ${state.contextCalls}; verification: ${state.verificationStatus}.`,
|
|
4366
|
-
...state.pending.map((
|
|
4326
|
+
...state.pending.map((message2) => " Next: " + message2)
|
|
4367
4327
|
);
|
|
4368
4328
|
}
|
|
4369
4329
|
if ("decisionRecords" in status) lines.push(`Decision records: ${status.decisionRecords}.`);
|
|
@@ -4379,10 +4339,8 @@ var init_status = __esm({
|
|
|
4379
4339
|
init_decisions();
|
|
4380
4340
|
init_adapters();
|
|
4381
4341
|
init_model();
|
|
4382
|
-
init_runtime2();
|
|
4383
4342
|
init_config();
|
|
4384
4343
|
init_launcher();
|
|
4385
|
-
init_files2();
|
|
4386
4344
|
init_observations();
|
|
4387
4345
|
}
|
|
4388
4346
|
});
|
|
@@ -4394,7 +4352,7 @@ __export(setup_exports, {
|
|
|
4394
4352
|
setupProject: () => setupProject,
|
|
4395
4353
|
summarizeSetup: () => summarizeSetup
|
|
4396
4354
|
});
|
|
4397
|
-
import { randomUUID as
|
|
4355
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
4398
4356
|
async function selectHost(root, explicit) {
|
|
4399
4357
|
if (explicit) return explicit;
|
|
4400
4358
|
if (process.env.MASON_SETUP_HOST === "codex" || process.env.MASON_SETUP_HOST === "claude") return process.env.MASON_SETUP_HOST;
|
|
@@ -4410,28 +4368,27 @@ async function setupProject(dir, options = {}) {
|
|
|
4410
4368
|
return withLock(ws.root, ".mason/reports/setup-lock", async () => {
|
|
4411
4369
|
const existing = await loadSetup(ws.root);
|
|
4412
4370
|
const previousReceipt = await loadSetupReceipt(ws.root, ws.directory, host);
|
|
4413
|
-
const
|
|
4414
|
-
|
|
4371
|
+
const installed = await installedCommand();
|
|
4372
|
+
if (!installed.available) throw new Error(installed.message);
|
|
4415
4373
|
const instructions = await instructionEdits(ws.root, host);
|
|
4416
|
-
const mcp = await mcpEdit(ws.root, host
|
|
4417
|
-
const hooks = await hookEdits(ws.root, host
|
|
4374
|
+
const mcp = await mcpEdit(ws.root, host);
|
|
4375
|
+
const hooks = await hookEdits(ws.root, host);
|
|
4418
4376
|
const ancillary = await ancillaryEdits(ws.root);
|
|
4419
4377
|
const edits = [...ancillary, ...instructions, mcp, ...hooks];
|
|
4420
|
-
const setupBefore = await readText(ws.root,
|
|
4421
|
-
const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after
|
|
4422
|
-
const desiredHooks = hookConfig(host, hookCommand(host
|
|
4378
|
+
const setupBefore = await readText(ws.root, SETUP_PATH);
|
|
4379
|
+
const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after)).mcp);
|
|
4380
|
+
const desiredHooks = hookConfig(host, hookCommand(host)).hooks;
|
|
4423
4381
|
const fingerprint = hash({
|
|
4424
|
-
|
|
4425
|
-
launcher: LAUNCHER,
|
|
4382
|
+
command: "mason",
|
|
4426
4383
|
mcp: mcpFingerprint,
|
|
4427
4384
|
hooks: desiredHooks,
|
|
4428
4385
|
instructions: instructions.map((edit) => edit.path)
|
|
4429
4386
|
});
|
|
4430
4387
|
const previous = existing?.hosts[host];
|
|
4431
4388
|
const changed = edits.some((edit) => edit.before !== edit.after) || previous?.fingerprint !== fingerprint;
|
|
4432
|
-
const revision = !changed && previous ? previous.revision :
|
|
4433
|
-
const setup = existing ?? { version:
|
|
4434
|
-
setup.hosts[host] = {
|
|
4389
|
+
const revision = !changed && previous ? previous.revision : randomUUID6();
|
|
4390
|
+
const setup = existing ?? { version: 2, hosts: {} };
|
|
4391
|
+
setup.hosts[host] = { configuredVersion: installed.version, revision, fingerprint, mcpFingerprint, instructions: instructions.map((e) => e.path) };
|
|
4435
4392
|
const initial = await automate(ws.root, { event: "turn_start" });
|
|
4436
4393
|
const findings = await inspectOnboarding(ws.root, options.base, options.evidence);
|
|
4437
4394
|
const receiptPath = ws.directory + "/setup-" + host + ".json";
|
|
@@ -4446,20 +4403,11 @@ async function setupProject(dir, options = {}) {
|
|
|
4446
4403
|
root: ws.root,
|
|
4447
4404
|
revision
|
|
4448
4405
|
});
|
|
4449
|
-
await installRuntime(ws.root, selected);
|
|
4450
|
-
if (selected.runtime.bundle) {
|
|
4451
|
-
const pointer = `.mason/runtime/${host}.txt`;
|
|
4452
|
-
await applyEdit(ws.root, { path: pointer, before: await readText(ws.root, pointer), after: selected.runtime.id + "\n" });
|
|
4453
|
-
}
|
|
4454
4406
|
const changedFiles = [];
|
|
4455
4407
|
for (const edit of edits) if (await applyEdit(ws.root, edit)) changedFiles.push(edit.path);
|
|
4456
|
-
if (await applyEdit(ws.root, { path:
|
|
4457
|
-
if (!marker) {
|
|
4458
|
-
await saveProjectMarker(ws.root, { version: 1, initializedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4459
|
-
changedFiles.push(".mason/project.json");
|
|
4460
|
-
}
|
|
4408
|
+
if (await applyEdit(ws.root, { path: SETUP_PATH, before: setupBefore, after: JSON.stringify(setup, null, 2) + "\n" })) changedFiles.push(SETUP_PATH);
|
|
4461
4409
|
const checked = await automate(ws.root, { event: "turn_start" });
|
|
4462
|
-
const configured = await inspectHostConfig(ws.root, host, void 0
|
|
4410
|
+
const configured = await inspectHostConfig(ws.root, host, void 0);
|
|
4463
4411
|
await writeStoreJson(ws.root, receiptPath, {
|
|
4464
4412
|
version: 1,
|
|
4465
4413
|
host,
|
|
@@ -4476,7 +4424,7 @@ async function setupProject(dir, options = {}) {
|
|
|
4476
4424
|
status: "configured",
|
|
4477
4425
|
host,
|
|
4478
4426
|
root: ws.root,
|
|
4479
|
-
runtime:
|
|
4427
|
+
runtime: { kind: "global", command: "mason", version: installed.version },
|
|
4480
4428
|
changedFiles,
|
|
4481
4429
|
initialReportPath,
|
|
4482
4430
|
findings,
|
|
@@ -4490,14 +4438,15 @@ function summarizeSetup(result) {
|
|
|
4490
4438
|
const audit = result.findings.audit;
|
|
4491
4439
|
return [
|
|
4492
4440
|
`Mason configured for ${result.host}.`,
|
|
4493
|
-
"
|
|
4441
|
+
" MCP server and lifecycle hooks use mason from PATH.",
|
|
4494
4442
|
" Project instructions updated; original audit evidence retained.",
|
|
4495
|
-
` ${result.changedFiles.length}
|
|
4443
|
+
` ${result.changedFiles.length} files changed.`,
|
|
4496
4444
|
`Initial audit: ${audit.status}${"counts" in audit ? `; ${audit.counts.issues} issues, ${audit.counts.advisories + audit.counts.suppressedAdvisories} advisories` : ""}.`,
|
|
4497
4445
|
..."issues" in audit ? audit.issues.slice(0, 3).map((f) => " " + f.message) : [],
|
|
4498
4446
|
`Original evidence: ${result.initialReportPath}`,
|
|
4499
4447
|
"Activation: " + result.activation.status + ".",
|
|
4500
4448
|
result.next,
|
|
4449
|
+
"If the assistant cannot find mason, restart the desktop app so it picks up PATH.",
|
|
4501
4450
|
"After the task finishes, run mason-auto status. Configuration alone does not establish activation."
|
|
4502
4451
|
].join("\n");
|
|
4503
4452
|
}
|
|
@@ -4507,7 +4456,6 @@ var init_setup = __esm({
|
|
|
4507
4456
|
init_evidence();
|
|
4508
4457
|
init_store();
|
|
4509
4458
|
init_runtime();
|
|
4510
|
-
init_init();
|
|
4511
4459
|
init_onboarding();
|
|
4512
4460
|
init_storage();
|
|
4513
4461
|
init_files2();
|
|
@@ -4515,24 +4463,73 @@ var init_setup = __esm({
|
|
|
4515
4463
|
init_launcher();
|
|
4516
4464
|
init_adapters();
|
|
4517
4465
|
init_model();
|
|
4518
|
-
init_runtime2();
|
|
4519
4466
|
init_status();
|
|
4520
4467
|
}
|
|
4521
4468
|
});
|
|
4522
4469
|
|
|
4470
|
+
// src/distribution/bundle.ts
|
|
4471
|
+
import fs16 from "fs/promises";
|
|
4472
|
+
import path21 from "path";
|
|
4473
|
+
import { createHash as createHash4 } from "crypto";
|
|
4474
|
+
import { z as z16 } from "zod";
|
|
4475
|
+
async function readBundle(root) {
|
|
4476
|
+
const bytes = await fs16.readFile(await storePath(root, "bundle.json"));
|
|
4477
|
+
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
4478
|
+
const windows = manifest.target.startsWith("win32-");
|
|
4479
|
+
for (const file of Object.keys(manifest.files)) {
|
|
4480
|
+
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path21.isAbsolute(file)) {
|
|
4481
|
+
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-auto.js", "app/dist/mason-mcp.js"]) {
|
|
4485
|
+
if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
|
|
4486
|
+
}
|
|
4487
|
+
return { manifest, manifestHash: sha256(bytes) };
|
|
4488
|
+
}
|
|
4489
|
+
async function verifyBundle(root, expectedHash) {
|
|
4490
|
+
const bundle = await readBundle(root);
|
|
4491
|
+
if (expectedHash && bundle.manifestHash !== expectedHash) throw new Error("Mason bundle manifest changed.");
|
|
4492
|
+
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.");
|
|
4493
|
+
const files = Object.keys(bundle.manifest.files);
|
|
4494
|
+
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
4495
|
+
if (sha256(await fs16.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
4496
|
+
}));
|
|
4497
|
+
return bundle;
|
|
4498
|
+
}
|
|
4499
|
+
var targets, digest2, bundleSchema, sha256;
|
|
4500
|
+
var init_bundle = __esm({
|
|
4501
|
+
"src/distribution/bundle.ts"() {
|
|
4502
|
+
"use strict";
|
|
4503
|
+
init_storage();
|
|
4504
|
+
targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
4505
|
+
digest2 = z16.string().regex(/^[a-f0-9]{64}$/);
|
|
4506
|
+
bundleSchema = z16.object({
|
|
4507
|
+
format: z16.literal(1),
|
|
4508
|
+
version: z16.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
4509
|
+
target: z16.enum(targets),
|
|
4510
|
+
nodeVersion: z16.string(),
|
|
4511
|
+
files: z16.record(digest2)
|
|
4512
|
+
}).strict();
|
|
4513
|
+
sha256 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
|
|
4514
|
+
}
|
|
4515
|
+
});
|
|
4516
|
+
|
|
4523
4517
|
// src/distribution/path.ts
|
|
4524
|
-
import
|
|
4518
|
+
import fs17 from "fs/promises";
|
|
4525
4519
|
import os3 from "os";
|
|
4526
|
-
import
|
|
4527
|
-
import { execFile as
|
|
4528
|
-
import { promisify as
|
|
4529
|
-
import { createHash as
|
|
4520
|
+
import path22 from "path";
|
|
4521
|
+
import { execFile as execFile13 } from "child_process";
|
|
4522
|
+
import { promisify as promisify13 } from "util";
|
|
4523
|
+
import { createHash as createHash5, randomUUID as randomUUID7 } from "crypto";
|
|
4530
4524
|
import { z as z17 } from "zod";
|
|
4531
4525
|
async function configurePath(bin, previous, save, options = {}) {
|
|
4532
4526
|
const env = options.env ?? process.env, platform = options.platform ?? process.platform;
|
|
4533
4527
|
const changes = [...previous];
|
|
4528
|
+
const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
|
|
4529
|
+
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path22.posix.isAbsolute(entry) && path22.posix.resolve(entry) === path22.posix.resolve(bin));
|
|
4530
|
+
const message2 = alreadyOnPath ? "Run: mason setup --host codex" : "Open a new terminal, then run: mason setup --host codex";
|
|
4534
4531
|
const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
|
|
4535
|
-
Add ${bin} to your PATH, then run: mason setup --host codex` });
|
|
4532
|
+
${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --host codex`}` });
|
|
4536
4533
|
if (env.MASON_NO_MODIFY_PATH === "1") return manual("Automatic PATH setup is disabled (MASON_NO_MODIFY_PATH=1).");
|
|
4537
4534
|
if (/[\r\n\0]/.test(bin) || bin.includes(platform === "win32" ? ";" : ":")) return manual("This installation path cannot be represented safely in PATH.");
|
|
4538
4535
|
try {
|
|
@@ -4547,33 +4544,33 @@ Add ${bin} to your PATH, then run: mason setup --host codex` });
|
|
|
4547
4544
|
}
|
|
4548
4545
|
return { status: "configured", changes, message: message2 };
|
|
4549
4546
|
}
|
|
4550
|
-
const shell =
|
|
4547
|
+
const shell = path22.basename(env.SHELL ?? os3.userInfo().shell ?? "");
|
|
4551
4548
|
const userHome = options.userHome ?? os3.homedir();
|
|
4552
4549
|
let profiles;
|
|
4553
4550
|
if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
|
|
4554
|
-
if (env.MASON_PROFILE) profiles = [
|
|
4555
|
-
else if (shell === "zsh") profiles = [
|
|
4556
|
-
else if (shell === "sh") profiles = [
|
|
4551
|
+
if (env.MASON_PROFILE) profiles = [path22.resolve(env.MASON_PROFILE)];
|
|
4552
|
+
else if (shell === "zsh") profiles = [path22.join(env.ZDOTDIR || userHome, ".zshrc")];
|
|
4553
|
+
else if (shell === "sh") profiles = [path22.join(userHome, ".profile")];
|
|
4557
4554
|
else {
|
|
4558
|
-
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) =>
|
|
4555
|
+
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path22.join(userHome, name));
|
|
4559
4556
|
let login = candidates[2];
|
|
4560
|
-
for (const file of candidates) if (await
|
|
4557
|
+
for (const file of candidates) if (await fs17.lstat(file).then(() => true, (error) => {
|
|
4561
4558
|
if (error.code === "ENOENT") return false;
|
|
4562
4559
|
throw error;
|
|
4563
4560
|
})) {
|
|
4564
4561
|
login = file;
|
|
4565
4562
|
break;
|
|
4566
4563
|
}
|
|
4567
|
-
profiles = [
|
|
4564
|
+
profiles = [path22.join(userHome, ".bashrc"), login];
|
|
4568
4565
|
}
|
|
4569
|
-
const marker = `# >>> mason PATH ${
|
|
4566
|
+
const marker = `# >>> mason PATH ${createHash5("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
4570
4567
|
for (const profile of profiles) {
|
|
4571
|
-
const stat = await
|
|
4568
|
+
const stat = await fs17.lstat(profile).catch((error) => {
|
|
4572
4569
|
if (error.code === "ENOENT") return null;
|
|
4573
4570
|
throw error;
|
|
4574
4571
|
});
|
|
4575
|
-
const file = stat?.isSymbolicLink() ? await
|
|
4576
|
-
const original = await
|
|
4572
|
+
const file = stat?.isSymbolicLink() ? await fs17.realpath(profile) : path22.resolve(profile);
|
|
4573
|
+
const original = await fs17.readFile(file).catch((error) => {
|
|
4577
4574
|
if (error.code === "ENOENT" && !stat) return null;
|
|
4578
4575
|
throw error;
|
|
4579
4576
|
});
|
|
@@ -4588,8 +4585,8 @@ Add ${bin} to your PATH, then run: mason setup --host codex` });
|
|
|
4588
4585
|
changes.push({ kind: "profile", file, block, created: original === null });
|
|
4589
4586
|
await save(changes);
|
|
4590
4587
|
}
|
|
4591
|
-
await
|
|
4592
|
-
const handle = await
|
|
4588
|
+
await fs17.mkdir(path22.dirname(file), { recursive: true });
|
|
4589
|
+
const handle = await fs17.open(file, original === null ? "ax+" : "a+", 384);
|
|
4593
4590
|
try {
|
|
4594
4591
|
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
4595
4592
|
await handle.writeFile(block);
|
|
@@ -4613,23 +4610,23 @@ async function removePath(changes) {
|
|
|
4613
4610
|
await windowsPath("remove", change.entry, current, change.created);
|
|
4614
4611
|
continue;
|
|
4615
4612
|
}
|
|
4616
|
-
const stat = await
|
|
4613
|
+
const stat = await fs17.lstat(change.file).catch((error) => {
|
|
4617
4614
|
if (error.code === "ENOENT") return null;
|
|
4618
4615
|
throw error;
|
|
4619
4616
|
});
|
|
4620
4617
|
if (!stat) continue;
|
|
4621
4618
|
if (!stat.isFile()) throw new Error("profile type changed");
|
|
4622
|
-
const bytes = await
|
|
4619
|
+
const bytes = await fs17.readFile(change.file), text2 = bytes.toString("utf8");
|
|
4623
4620
|
if (!Buffer.from(text2).equals(bytes)) throw new Error("profile encoding changed");
|
|
4624
4621
|
const index2 = text2.indexOf(change.block);
|
|
4625
4622
|
if (index2 < 0 || text2.indexOf(change.block, index2 + change.block.length) >= 0) throw new Error("PATH block was edited or removed");
|
|
4626
4623
|
const prefix = text2.slice(0, index2), suffix = text2.slice(index2 + change.block.length);
|
|
4627
4624
|
const separator = prefix && suffix && !prefix.endsWith("\n") ? change.block.startsWith("\r\n") ? "\r\n" : "\n" : "";
|
|
4628
4625
|
const next = Buffer.from(prefix + separator + suffix);
|
|
4629
|
-
const temporary = change.file + ".mason-" +
|
|
4626
|
+
const temporary = change.file + ".mason-" + randomUUID7();
|
|
4630
4627
|
try {
|
|
4631
4628
|
if (stat.nlink > 1) throw new Error("profile has multiple hard links");
|
|
4632
|
-
const handle = await
|
|
4629
|
+
const handle = await fs17.open(temporary, "wx", stat.mode & 511);
|
|
4633
4630
|
try {
|
|
4634
4631
|
await handle.writeFile(next);
|
|
4635
4632
|
await handle.chmod(stat.mode & 511);
|
|
@@ -4638,11 +4635,11 @@ async function removePath(changes) {
|
|
|
4638
4635
|
} finally {
|
|
4639
4636
|
await handle.close();
|
|
4640
4637
|
}
|
|
4641
|
-
const current = await
|
|
4642
|
-
if (!current.isFile() || current.ino !== stat.ino || !(await
|
|
4643
|
-
await
|
|
4638
|
+
const current = await fs17.lstat(change.file);
|
|
4639
|
+
if (!current.isFile() || current.ino !== stat.ino || !(await fs17.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
|
|
4640
|
+
await fs17.rename(temporary, change.file);
|
|
4644
4641
|
} finally {
|
|
4645
|
-
await
|
|
4642
|
+
await fs17.rm(temporary, { force: true });
|
|
4646
4643
|
}
|
|
4647
4644
|
} catch (error) {
|
|
4648
4645
|
warnings.push(`Retained PATH setting ${change.kind === "profile" ? change.file : change.entry}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -4676,21 +4673,20 @@ try {
|
|
|
4676
4673
|
@{ value = $next; kind = $kind } | ConvertTo-Json -Compress
|
|
4677
4674
|
} finally { $key.Dispose() }
|
|
4678
4675
|
`;
|
|
4679
|
-
const { stdout } = await
|
|
4676
|
+
const { stdout } = await execute2("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { windowsHide: true, timeout: 15e3, maxBuffer: 1024 * 1024 });
|
|
4680
4677
|
return JSON.parse(stdout.replace(/^\uFEFF/, "").trim());
|
|
4681
4678
|
}
|
|
4682
|
-
var
|
|
4679
|
+
var execute2, pathChangeSchema, quote, normalizeWindowsEntry, hasWindowsEntry;
|
|
4683
4680
|
var init_path = __esm({
|
|
4684
4681
|
"src/distribution/path.ts"() {
|
|
4685
4682
|
"use strict";
|
|
4686
|
-
|
|
4683
|
+
execute2 = promisify13(execFile13);
|
|
4687
4684
|
pathChangeSchema = z17.discriminatedUnion("kind", [
|
|
4688
4685
|
z17.object({ kind: z17.literal("profile"), file: z17.string(), block: z17.string(), created: z17.boolean() }),
|
|
4689
4686
|
z17.object({ kind: z17.literal("windows"), entry: z17.string(), created: z17.boolean() })
|
|
4690
4687
|
]);
|
|
4691
4688
|
quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
4692
|
-
|
|
4693
|
-
normalizeWindowsEntry = (value) => path23.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
|
|
4689
|
+
normalizeWindowsEntry = (value) => path22.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
|
|
4694
4690
|
hasWindowsEntry = (value, entry, env = process.env) => {
|
|
4695
4691
|
const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
|
|
4696
4692
|
const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
|
|
@@ -4708,41 +4704,41 @@ __export(install_exports2, {
|
|
|
4708
4704
|
uninstallStandalone: () => uninstallStandalone,
|
|
4709
4705
|
upgradeStandalone: () => upgradeStandalone
|
|
4710
4706
|
});
|
|
4711
|
-
import
|
|
4707
|
+
import fs18 from "fs/promises";
|
|
4712
4708
|
import os4 from "os";
|
|
4713
|
-
import
|
|
4714
|
-
import { fileURLToPath
|
|
4709
|
+
import path23 from "path";
|
|
4710
|
+
import { fileURLToPath } from "url";
|
|
4715
4711
|
import { spawn } from "child_process";
|
|
4716
|
-
import { randomUUID as
|
|
4712
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
4717
4713
|
import { z as z18 } from "zod";
|
|
4718
4714
|
async function replace(file, content2, mode = 384) {
|
|
4719
|
-
const temp = file + ".tmp-" +
|
|
4715
|
+
const temp = file + ".tmp-" + randomUUID8();
|
|
4720
4716
|
try {
|
|
4721
|
-
await
|
|
4722
|
-
await
|
|
4717
|
+
await fs18.writeFile(temp, content2, { mode, flag: "wx" });
|
|
4718
|
+
await fs18.rename(temp, file);
|
|
4723
4719
|
} finally {
|
|
4724
|
-
await
|
|
4720
|
+
await fs18.rm(temp, { force: true });
|
|
4725
4721
|
}
|
|
4726
4722
|
}
|
|
4727
4723
|
async function installStandalone(source) {
|
|
4728
4724
|
const bundle = await verifyBundle(source);
|
|
4729
|
-
const selectedHome =
|
|
4730
|
-
await
|
|
4731
|
-
const home = await
|
|
4732
|
-
const selectedBin =
|
|
4725
|
+
const selectedHome = path23.resolve(defaultHome());
|
|
4726
|
+
await fs18.mkdir(selectedHome, { recursive: true });
|
|
4727
|
+
const home = await fs18.realpath(selectedHome);
|
|
4728
|
+
const selectedBin = path23.resolve(process.env.MASON_BIN_DIR ?? (process.platform === "win32" ? path23.join(home, "bin") : path23.join(os4.homedir(), ".local/bin")));
|
|
4733
4729
|
return withLock(home, ".install-lock", async () => {
|
|
4734
4730
|
const raw = await readStoreJson(home, "install.json");
|
|
4735
4731
|
const previous = raw === null ? null : recordSchema2.parse(raw);
|
|
4736
|
-
if (!previous) for (const file of await
|
|
4732
|
+
if (!previous) for (const file of await fs18.readdir(home)) {
|
|
4737
4733
|
if (file === ".install-lock") continue;
|
|
4738
|
-
const directory =
|
|
4739
|
-
const stat = await
|
|
4740
|
-
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await
|
|
4734
|
+
const directory = path23.join(home, file);
|
|
4735
|
+
const stat = await fs18.lstat(directory);
|
|
4736
|
+
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs18.readdir(directory)).length) {
|
|
4741
4737
|
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
4742
4738
|
}
|
|
4743
4739
|
}
|
|
4744
|
-
await
|
|
4745
|
-
const bin = await
|
|
4740
|
+
await fs18.mkdir(selectedBin, { recursive: true });
|
|
4741
|
+
const bin = await fs18.realpath(selectedBin);
|
|
4746
4742
|
if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
|
|
4747
4743
|
const id = bundle.manifestHash.slice(0, 24);
|
|
4748
4744
|
const destination = await storePath(home, "versions/" + id);
|
|
@@ -4753,10 +4749,10 @@ $previousToken = $env:MASON_UNINSTALL_TOKEN
|
|
|
4753
4749
|
$token = [Guid]::NewGuid().ToString()
|
|
4754
4750
|
$env:MASON_UNINSTALL_TOKEN = $token
|
|
4755
4751
|
try {
|
|
4756
|
-
& ${psQuote(
|
|
4752
|
+
& ${psQuote(path23.join(destination, "node.exe"))} ${psQuote(path23.join(destination, "app/dist/mason.js"))} @args
|
|
4757
4753
|
$code = $LASTEXITCODE
|
|
4758
4754
|
if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
|
|
4759
|
-
if ([IO.File]::ReadAllText(${psQuote(
|
|
4755
|
+
if ([IO.File]::ReadAllText(${psQuote(path23.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
|
|
4760
4756
|
Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
|
|
4761
4757
|
Write-Output ${psQuote("Removed standalone installation: " + home)}
|
|
4762
4758
|
}
|
|
@@ -4765,11 +4761,11 @@ try {
|
|
|
4765
4761
|
finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
|
|
4766
4762
|
`
|
|
4767
4763
|
} : { mason: `#!/bin/sh
|
|
4768
|
-
exec ${shQuote(
|
|
4764
|
+
exec ${shQuote(path23.join(destination, "node"))} ${shQuote(path23.join(destination, "app/dist/mason.js"))} "$@"
|
|
4769
4765
|
` };
|
|
4770
4766
|
for (const [name, content2] of Object.entries(launchers)) {
|
|
4771
4767
|
const file = await storePath(bin, name);
|
|
4772
|
-
const old = await
|
|
4768
|
+
const old = await fs18.readFile(file, "utf8").catch((error) => {
|
|
4773
4769
|
if (error.code === "ENOENT") return null;
|
|
4774
4770
|
throw error;
|
|
4775
4771
|
});
|
|
@@ -4778,7 +4774,7 @@ exec ${shQuote(path24.join(destination, "node"))} ${shQuote(path24.join(destinat
|
|
|
4778
4774
|
const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
|
|
4779
4775
|
const checkRetired = async (name, expected) => {
|
|
4780
4776
|
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
4781
|
-
const actual = await
|
|
4777
|
+
const actual = await fs18.readFile(await storePath(bin, name), "utf8").catch((error) => {
|
|
4782
4778
|
if (error.code === "ENOENT") return null;
|
|
4783
4779
|
throw error;
|
|
4784
4780
|
});
|
|
@@ -4787,29 +4783,29 @@ exec ${shQuote(path24.join(destination, "node"))} ${shQuote(path24.join(destinat
|
|
|
4787
4783
|
for (const [name, expected] of retired) await checkRetired(name, expected);
|
|
4788
4784
|
const receipt = { format: 1, home, bin, current: id, version: bundle.manifest.version, launchers, pathChanges: previous?.pathChanges ?? [] };
|
|
4789
4785
|
await writeStoreJson(home, "install.json", { ...receipt, previousLaunchers: previous?.previousLaunchers ?? previous?.launchers });
|
|
4790
|
-
const stage = await storePath(home, "versions/.install-" +
|
|
4791
|
-
await
|
|
4786
|
+
const stage = await storePath(home, "versions/.install-" + randomUUID8(), true);
|
|
4787
|
+
await fs18.mkdir(stage);
|
|
4792
4788
|
try {
|
|
4793
4789
|
for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
|
|
4794
4790
|
const from = await storePath(source, file), to = await storePath(stage, file, true);
|
|
4795
|
-
await
|
|
4796
|
-
await
|
|
4791
|
+
await fs18.copyFile(from, to);
|
|
4792
|
+
await fs18.chmod(to, (await fs18.stat(from)).mode & 511);
|
|
4797
4793
|
}
|
|
4798
4794
|
await verifyBundle(stage, bundle.manifestHash);
|
|
4799
|
-
const exists2 = await
|
|
4795
|
+
const exists2 = await fs18.lstat(destination).then(() => true, (error) => {
|
|
4800
4796
|
if (error.code === "ENOENT") return false;
|
|
4801
4797
|
throw error;
|
|
4802
4798
|
});
|
|
4803
4799
|
if (exists2) await verifyBundle(destination, bundle.manifestHash);
|
|
4804
|
-
else await
|
|
4800
|
+
else await fs18.rename(stage, destination);
|
|
4805
4801
|
for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
4806
4802
|
for (const [name, expected] of retired) {
|
|
4807
4803
|
await checkRetired(name, expected);
|
|
4808
|
-
await
|
|
4804
|
+
await fs18.rm(await storePath(bin, name), { force: true });
|
|
4809
4805
|
}
|
|
4810
4806
|
await writeStoreJson(home, "install.json", receipt);
|
|
4811
4807
|
} finally {
|
|
4812
|
-
await
|
|
4808
|
+
await fs18.rm(stage, { recursive: true, force: true });
|
|
4813
4809
|
}
|
|
4814
4810
|
const pathResult = await configurePath(bin, receipt.pathChanges, async (changes) => {
|
|
4815
4811
|
receipt.pathChanges = changes;
|
|
@@ -4819,12 +4815,12 @@ exec ${shQuote(path24.join(destination, "node"))} ${shQuote(path24.join(destinat
|
|
|
4819
4815
|
});
|
|
4820
4816
|
}
|
|
4821
4817
|
async function currentInstallation() {
|
|
4822
|
-
const app =
|
|
4823
|
-
const bundle =
|
|
4818
|
+
const app = path23.dirname(path23.dirname(fileURLToPath(import.meta.url)));
|
|
4819
|
+
const bundle = path23.dirname(app), home = path23.dirname(path23.dirname(bundle));
|
|
4824
4820
|
const raw = await readStoreJson(home, "install.json");
|
|
4825
4821
|
if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
4826
4822
|
const record = recordSchema2.parse(raw);
|
|
4827
|
-
if (record.home !== await
|
|
4823
|
+
if (record.home !== await fs18.realpath(home) || path23.dirname(bundle) !== path23.join(home, "versions")) throw new Error("Invalid standalone installation receipt.");
|
|
4828
4824
|
return { home, bundle, record };
|
|
4829
4825
|
}
|
|
4830
4826
|
async function upgradeStandalone(version) {
|
|
@@ -4832,7 +4828,7 @@ async function upgradeStandalone(version) {
|
|
|
4832
4828
|
const { home, bundle, record } = await currentInstallation();
|
|
4833
4829
|
await verifyBundle(bundle);
|
|
4834
4830
|
const windows = process.platform === "win32";
|
|
4835
|
-
const script =
|
|
4831
|
+
const script = path23.join(bundle, windows ? "install.ps1" : "install.sh");
|
|
4836
4832
|
return new Promise((resolve, reject) => {
|
|
4837
4833
|
const child = spawn(
|
|
4838
4834
|
windows ? "powershell.exe" : "sh",
|
|
@@ -4853,7 +4849,7 @@ async function uninstallStandalone() {
|
|
|
4853
4849
|
for (const [name, expected] of Object.entries(record.launchers)) {
|
|
4854
4850
|
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
4855
4851
|
const file = await storePath(record.bin, name);
|
|
4856
|
-
const actual = await
|
|
4852
|
+
const actual = await fs18.readFile(file, "utf8").catch((error) => {
|
|
4857
4853
|
if (error.code === "ENOENT") return null;
|
|
4858
4854
|
throw error;
|
|
4859
4855
|
});
|
|
@@ -4861,9 +4857,9 @@ async function uninstallStandalone() {
|
|
|
4861
4857
|
}
|
|
4862
4858
|
for (const warning of await removePath(record.pathChanges ?? [])) console.error(warning);
|
|
4863
4859
|
if (process.platform === "win32") await replace(await storePath(home, ".uninstall-request.txt", true), token);
|
|
4864
|
-
for (const name of Object.keys(record.launchers)) await
|
|
4860
|
+
for (const name of Object.keys(record.launchers)) await fs18.rm(await storePath(record.bin, name), { force: true });
|
|
4865
4861
|
});
|
|
4866
|
-
if (process.platform !== "win32") await
|
|
4862
|
+
if (process.platform !== "win32") await fs18.rm(home, { recursive: true, force: true });
|
|
4867
4863
|
return home;
|
|
4868
4864
|
}
|
|
4869
4865
|
var recordSchema2, shQuote, psQuote, WINDOWS_BATCH_LAUNCHER, launcherNames, defaultHome;
|
|
@@ -4888,12 +4884,12 @@ var init_install2 = __esm({
|
|
|
4888
4884
|
psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
|
|
4889
4885
|
WINDOWS_BATCH_LAUNCHER = '@echo off\r\ngoto #_mason_handoff_# 2>NUL || title %COMSPEC% & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0mason-launcher.ps1" %*\r\n';
|
|
4890
4886
|
launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
|
|
4891
|
-
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ?
|
|
4887
|
+
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path23.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path23.join(os4.homedir(), ".local/share/mason"));
|
|
4892
4888
|
}
|
|
4893
4889
|
});
|
|
4894
4890
|
|
|
4895
4891
|
// bin/mason.ts
|
|
4896
|
-
import { fileURLToPath as
|
|
4892
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4897
4893
|
|
|
4898
4894
|
// src/automation/cli.ts
|
|
4899
4895
|
init_execution();
|
|
@@ -4904,7 +4900,7 @@ init_store();
|
|
|
4904
4900
|
import { parseArgs } from "util";
|
|
4905
4901
|
var USAGE = `Usage: mason-auto <setup|install|config|status|check|hook> [options]
|
|
4906
4902
|
|
|
4907
|
-
setup [--host claude|codex]
|
|
4903
|
+
setup [--host claude|codex] Connect MCP, instructions and hooks to mason on PATH; retain the initial audit
|
|
4908
4904
|
install --host claude|codex Merge lifecycle hooks into this project's host config
|
|
4909
4905
|
config --host claude|codex Print the host config without writing
|
|
4910
4906
|
status Read configured hooks and observed runtime events
|
|
@@ -4977,20 +4973,23 @@ async function runAutomationCli(argv, stdin = "", io = {
|
|
|
4977
4973
|
io.out(values.json ? JSON.stringify(report, null, 2) : summarize(report));
|
|
4978
4974
|
return report.status === "verified" ? 0 : report.status === "issues-remain" ? 1 : 2;
|
|
4979
4975
|
} catch (error) {
|
|
4980
|
-
const
|
|
4976
|
+
const message2 = action === "setup" ? "Mason setup incomplete: " + automationFailure(error).message + ". Rerun the same setup command to resume; retained audit evidence is preserved." : failureMessage(error);
|
|
4981
4977
|
if (action === "hook" || isHookCommand(argv)) {
|
|
4982
|
-
io.out(JSON.stringify({ systemMessage:
|
|
4978
|
+
io.out(JSON.stringify({ systemMessage: message2 }));
|
|
4983
4979
|
return 0;
|
|
4984
4980
|
}
|
|
4985
4981
|
if (argv.includes("--json")) io.out(JSON.stringify({ version: 1, ...action === "setup" ? { action, status: "incomplete", next: "Rerun the same setup command to resume." } : { status: "unavailable" }, failure: automationFailure(error) }));
|
|
4986
|
-
else io.err(
|
|
4982
|
+
else io.err(message2);
|
|
4987
4983
|
return 2;
|
|
4988
4984
|
}
|
|
4989
4985
|
}
|
|
4990
4986
|
|
|
4991
4987
|
// bin/mason.ts
|
|
4992
|
-
var
|
|
4993
|
-
var
|
|
4988
|
+
var inputArgs = process.argv.slice(2);
|
|
4989
|
+
var managedLaunch = inputArgs[0] === "--setup-host";
|
|
4990
|
+
var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
|
|
4991
|
+
var [command, ...args] = inputArgs;
|
|
4992
|
+
var usage = `Mason ${"0.15.0"}
|
|
4994
4993
|
|
|
4995
4994
|
Usage: mason <command> [options]
|
|
4996
4995
|
|
|
@@ -5007,24 +5006,28 @@ Usage: mason <command> [options]
|
|
|
5007
5006
|
Use mason <command> --help for options. npm users can keep using mason-auto,
|
|
5008
5007
|
mason-audit, mason-review, mason-drift, mason-hook and mason-mcp.`;
|
|
5009
5008
|
try {
|
|
5010
|
-
if (
|
|
5011
|
-
|
|
5009
|
+
if (managedLaunch) {
|
|
5010
|
+
if (!setupHost || !["codex", "claude"].includes(setupHost) || !["mcp", "auto"].includes(command)) throw new Error("Invalid setup invocation.");
|
|
5011
|
+
const { prepareLaunch: prepareLaunch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
|
|
5012
|
+
await prepareLaunch2(setupHost);
|
|
5013
|
+
}
|
|
5014
|
+
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.15.0" }));
|
|
5015
|
+
else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
|
|
5016
|
+
else if (["--version", "-v"].includes(command)) console.log("0.15.0");
|
|
5012
5017
|
else if (command === "internal-install") {
|
|
5013
5018
|
if (args.length) throw new Error("internal-install takes no arguments.");
|
|
5014
|
-
const { sourceRuntime: sourceRuntime2 } = await Promise.resolve().then(() => (init_runtime2(), runtime_exports));
|
|
5015
|
-
const selected = await sourceRuntime2();
|
|
5016
|
-
if (!selected.bundleRoot) throw new Error("This command requires a standalone bundle.");
|
|
5017
5019
|
const { installStandalone: installStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
5018
|
-
const
|
|
5020
|
+
const bundleRoot = fileURLToPath2(new URL("../..", import.meta.url));
|
|
5021
|
+
const installed = await installStandalone2(bundleRoot);
|
|
5019
5022
|
console.log(`Installed Mason ${installed.version} in ${installed.home}.
|
|
5020
5023
|
${installed.path.message}`);
|
|
5021
5024
|
} else if (command === "upgrade" || command === "uninstall") {
|
|
5022
|
-
if (args.includes("--help")) console.log(command === "upgrade" ? "Usage: mason upgrade [version].
|
|
5025
|
+
if (args.includes("--help")) console.log(command === "upgrade" ? "Usage: mason upgrade [version]. Configured projects use the upgraded command on their next launch. Restart running assistants." : "Usage: mason uninstall. Removes the standalone user installation; project configuration and knowledge are retained; integrations need Mason reinstalled.");
|
|
5023
5026
|
else {
|
|
5024
5027
|
const { upgradeStandalone: upgradeStandalone2, uninstallStandalone: uninstallStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
5025
5028
|
if (args.length > (command === "upgrade" ? 1 : 0)) throw new Error("Unexpected arguments.");
|
|
5026
5029
|
if (command === "upgrade") process.exitCode = await upgradeStandalone2(args[0]);
|
|
5027
|
-
else console.log((process.platform === "win32" ? "Finishing installation cleanup after runtime exit: " : "Removed standalone installation: ") + await uninstallStandalone2() + ". Project
|
|
5030
|
+
else console.log((process.platform === "win32" ? "Finishing installation cleanup after runtime exit: " : "Removed standalone installation: ") + await uninstallStandalone2() + ". Project configuration and knowledge were retained. Reinstall Mason to use its integrations.");
|
|
5028
5031
|
}
|
|
5029
5032
|
} else if (["setup", "status", "check", "auto"].includes(command)) {
|
|
5030
5033
|
const forwarded = command === "auto" ? args : [command, ...args];
|
|
@@ -5036,11 +5039,17 @@ ${installed.path.message}`);
|
|
|
5036
5039
|
process.exitCode = await runAutomationCli(forwarded, input);
|
|
5037
5040
|
} else if (["audit", "review", "drift", "hook", "mcp"].includes(command)) {
|
|
5038
5041
|
const binary = new URL(`./mason-${command}.js`, import.meta.url);
|
|
5039
|
-
process.argv = [process.execPath,
|
|
5042
|
+
process.argv = [process.execPath, fileURLToPath2(binary), ...args];
|
|
5040
5043
|
await import(binary.href);
|
|
5041
5044
|
} else throw new Error("Unknown command: " + command + ". Run mason --help.");
|
|
5042
5045
|
} catch (error) {
|
|
5043
|
-
|
|
5044
|
-
|
|
5046
|
+
const message2 = "Mason: " + (error instanceof Error ? error.message : String(error));
|
|
5047
|
+
if (command === "auto" && isHookCommand(args)) {
|
|
5048
|
+
console.log(JSON.stringify({ systemMessage: message2 + " Verification was not established." }));
|
|
5049
|
+
process.exitCode = 0;
|
|
5050
|
+
} else {
|
|
5051
|
+
console.error(message2);
|
|
5052
|
+
process.exitCode = 2;
|
|
5053
|
+
}
|
|
5045
5054
|
}
|
|
5046
5055
|
//# sourceMappingURL=mason.js.map
|