mason-context 0.15.0 → 0.16.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 +6 -0
- package/README.md +1 -1
- package/dist/mason-auto.js +443 -91
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-mcp.js +295 -200
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason.js +471 -118
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/dist/mason.js
CHANGED
|
@@ -2521,7 +2521,7 @@ var init_evidence = __esm({
|
|
|
2521
2521
|
init_dead_command();
|
|
2522
2522
|
init_storage();
|
|
2523
2523
|
exec7 = promisify8(execFile8);
|
|
2524
|
-
engineVersion = true ? "0.
|
|
2524
|
+
engineVersion = true ? "0.16.0" : "development";
|
|
2525
2525
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
2526
2526
|
content = readAuditInput;
|
|
2527
2527
|
internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
|
|
@@ -2813,9 +2813,9 @@ async function automationStatus(dir) {
|
|
|
2813
2813
|
if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) throw new Error("Automation state belongs to another workspace.");
|
|
2814
2814
|
const inputs = await readInputs(ws.root);
|
|
2815
2815
|
const latest = state.latest ? await readStoreJson(ws.root, state.latest) : null;
|
|
2816
|
-
const
|
|
2816
|
+
const hosts2 = {};
|
|
2817
2817
|
for (const session of Object.values(state.sessions)) {
|
|
2818
|
-
const host =
|
|
2818
|
+
const host = hosts2[session.host] ??= { sessions: 0, observedEvents: [] };
|
|
2819
2819
|
host.sessions++;
|
|
2820
2820
|
host.observedEvents = [.../* @__PURE__ */ new Set([...host.observedEvents, ...Object.keys(session.events)])];
|
|
2821
2821
|
}
|
|
@@ -2828,7 +2828,7 @@ async function automationStatus(dir) {
|
|
|
2828
2828
|
baselinePaths: state.baselines.map((b) => b.path),
|
|
2829
2829
|
reportPath: state.latest,
|
|
2830
2830
|
verificationStatus: unfinished ? "unavailable" : latest?.status ?? "unavailable",
|
|
2831
|
-
hosts,
|
|
2831
|
+
hosts: hosts2,
|
|
2832
2832
|
execution,
|
|
2833
2833
|
note: "Observed events do not prove all tool paths are intercepted. Run check to verify the retained evidence."
|
|
2834
2834
|
};
|
|
@@ -2937,7 +2937,7 @@ var init_launcher = __esm({
|
|
|
2937
2937
|
"use strict";
|
|
2938
2938
|
init_evidence();
|
|
2939
2939
|
init_model();
|
|
2940
|
-
executingVersion = () => true ? "0.
|
|
2940
|
+
executingVersion = () => true ? "0.16.0" : "development";
|
|
2941
2941
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
2942
2942
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
2943
2943
|
}
|
|
@@ -3141,7 +3141,13 @@ function managedBlock(text2, start, end, body) {
|
|
|
3141
3141
|
async function applyEdit(root, edit) {
|
|
3142
3142
|
const current = await readText(root, edit.path);
|
|
3143
3143
|
if (current === edit.after) return false;
|
|
3144
|
-
if (current !== edit.before) throw new Error("
|
|
3144
|
+
if (current !== edit.before) throw new Error("Integration input changed during update: " + edit.path + ". Rerun the requested command to resume.");
|
|
3145
|
+
if (edit.after === null) {
|
|
3146
|
+
const file2 = await storePath(root, edit.path);
|
|
3147
|
+
if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
|
|
3148
|
+
await fs14.unlink(file2);
|
|
3149
|
+
return true;
|
|
3150
|
+
}
|
|
3145
3151
|
const file = await storePath(root, edit.path, true);
|
|
3146
3152
|
const temporary = path18.join(path18.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
3147
3153
|
try {
|
|
@@ -3153,7 +3159,7 @@ async function applyEdit(root, edit) {
|
|
|
3153
3159
|
} finally {
|
|
3154
3160
|
await handle.close();
|
|
3155
3161
|
}
|
|
3156
|
-
if (await readText(root, edit.path) !== edit.before) throw new Error("
|
|
3162
|
+
if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
|
|
3157
3163
|
await fs14.rename(temporary, file);
|
|
3158
3164
|
return true;
|
|
3159
3165
|
} finally {
|
|
@@ -3168,18 +3174,91 @@ var init_files2 = __esm({
|
|
|
3168
3174
|
}
|
|
3169
3175
|
});
|
|
3170
3176
|
|
|
3171
|
-
// src/
|
|
3177
|
+
// src/setup/ownership.ts
|
|
3178
|
+
var ownership_exports = {};
|
|
3179
|
+
__export(ownership_exports, {
|
|
3180
|
+
BLOCKS: () => BLOCKS,
|
|
3181
|
+
OWNERSHIP_PATH: () => OWNERSHIP_PATH,
|
|
3182
|
+
blockSpan: () => blockSpan,
|
|
3183
|
+
completedHookOwnership: () => completedHookOwnership,
|
|
3184
|
+
ownershipEdit: () => ownershipEdit,
|
|
3185
|
+
ownershipSchema: () => ownershipSchema
|
|
3186
|
+
});
|
|
3172
3187
|
import { z as z10 } from "zod";
|
|
3188
|
+
import { parse } from "smol-toml";
|
|
3189
|
+
function blockSpan(text2, start, end) {
|
|
3190
|
+
const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
|
|
3191
|
+
if (!starts && !ends) return null;
|
|
3192
|
+
const from = text2.indexOf(start), to = text2.indexOf(end) + end.length;
|
|
3193
|
+
if (starts !== 1 || ends !== 1 || to <= from) throw new Error("Ambiguous Mason markers; inspect the marked block manually.");
|
|
3194
|
+
return { from, to, text: text2.slice(from, to) };
|
|
3195
|
+
}
|
|
3196
|
+
async function ownershipEdit(root, edits, hookCommands = {}) {
|
|
3197
|
+
const before = await readText(root, OWNERSHIP_PATH);
|
|
3198
|
+
const record = before === null ? { version: 1, files: {} } : ownershipSchema.parse(JSON.parse(before));
|
|
3199
|
+
for (const edit of edits) {
|
|
3200
|
+
const previous = record.files[edit.path];
|
|
3201
|
+
const entry = {
|
|
3202
|
+
created: edit.before === null || (previous?.created ?? false),
|
|
3203
|
+
initialHash: edit.before === null || previous?.initialHash === hash(edit.before) ? hash(edit.after) : previous?.initialHash ?? hash(edit.after),
|
|
3204
|
+
blocks: Object.fromEntries(BLOCKS.flatMap(([start, end]) => {
|
|
3205
|
+
const block = blockSpan(edit.after, start, end);
|
|
3206
|
+
return block ? [[start, hash(block.text)]] : [];
|
|
3207
|
+
}))
|
|
3208
|
+
};
|
|
3209
|
+
const mcp = edit.path === ".codex/config.toml" ? parse(edit.after).mcp_servers?.mason : edit.path === ".mcp.json" ? JSON.parse(edit.after).mcpServers?.mason : void 0;
|
|
3210
|
+
const hooks = edit.path === ".codex/hooks.json" || edit.path === ".claude/settings.json" ? JSON.parse(edit.after).hooks : void 0;
|
|
3211
|
+
record.files[edit.path] = {
|
|
3212
|
+
...entry,
|
|
3213
|
+
...mcp === void 0 ? {} : { mcpHash: hash(mcp) },
|
|
3214
|
+
...hookCommands[edit.path] ? { hookCommands: [.../* @__PURE__ */ new Set([...previous?.hookCommands ?? [], hookCommands[edit.path]])] } : {},
|
|
3215
|
+
...hooks === void 0 ? {} : { hooks: Object.fromEntries(Object.entries(hooks).map(([event, groups]) => [event, groups.flatMap((group) => group.hooks.map(hash))])) }
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
return { path: OWNERSHIP_PATH, before, after: JSON.stringify(record, null, 2) + "\n" };
|
|
3219
|
+
}
|
|
3220
|
+
async function completedHookOwnership(root, file, command2) {
|
|
3221
|
+
const before = await readText(root, OWNERSHIP_PATH);
|
|
3222
|
+
const record = ownershipSchema.parse(JSON.parse(before ?? "null"));
|
|
3223
|
+
record.files[file].hookCommands = [command2];
|
|
3224
|
+
return { path: OWNERSHIP_PATH, before, after: JSON.stringify(record, null, 2) + "\n" };
|
|
3225
|
+
}
|
|
3226
|
+
var OWNERSHIP_PATH, BLOCKS, fileSchema, ownershipSchema;
|
|
3227
|
+
var init_ownership = __esm({
|
|
3228
|
+
"src/setup/ownership.ts"() {
|
|
3229
|
+
"use strict";
|
|
3230
|
+
init_evidence();
|
|
3231
|
+
init_files2();
|
|
3232
|
+
OWNERSHIP_PATH = ".mason/local/integration.json";
|
|
3233
|
+
BLOCKS = [
|
|
3234
|
+
["<!-- mason:start -->", "<!-- mason:end -->"],
|
|
3235
|
+
["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
|
|
3236
|
+
["# mason:mcp:start", "# mason:mcp:end"]
|
|
3237
|
+
];
|
|
3238
|
+
fileSchema = z10.object({
|
|
3239
|
+
created: z10.boolean(),
|
|
3240
|
+
initialHash: z10.string(),
|
|
3241
|
+
blocks: z10.record(z10.string()),
|
|
3242
|
+
mcpHash: z10.string().optional(),
|
|
3243
|
+
hookCommands: z10.array(z10.string()).optional(),
|
|
3244
|
+
hooks: z10.record(z10.array(z10.string())).optional()
|
|
3245
|
+
});
|
|
3246
|
+
ownershipSchema = z10.object({ version: z10.literal(1), files: z10.record(fileSchema) });
|
|
3247
|
+
}
|
|
3248
|
+
});
|
|
3249
|
+
|
|
3250
|
+
// src/mcp/init.ts
|
|
3251
|
+
import { z as z11 } from "zod";
|
|
3173
3252
|
var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_PLAYBOOK, MAP_PLAYBOOK;
|
|
3174
3253
|
var init_init = __esm({
|
|
3175
3254
|
"src/mcp/init.ts"() {
|
|
3176
3255
|
"use strict";
|
|
3177
3256
|
init_storage();
|
|
3178
3257
|
init_files2();
|
|
3179
|
-
markerSchema =
|
|
3180
|
-
version:
|
|
3181
|
-
initializedAt:
|
|
3182
|
-
features:
|
|
3258
|
+
markerSchema = z11.object({
|
|
3259
|
+
version: z11.literal(1),
|
|
3260
|
+
initializedAt: z11.string(),
|
|
3261
|
+
features: z11.object({ confluence: z11.boolean().optional() }).passthrough().optional()
|
|
3183
3262
|
}).passthrough();
|
|
3184
3263
|
CLAUDE_MD_SECTION = `<!-- mason:start -->
|
|
3185
3264
|
## Mason project knowledge
|
|
@@ -3285,8 +3364,8 @@ __export(config_exports, {
|
|
|
3285
3364
|
mcpPath: () => mcpPath
|
|
3286
3365
|
});
|
|
3287
3366
|
import { isDeepStrictEqual } from "util";
|
|
3288
|
-
import { parse, stringify } from "smol-toml";
|
|
3289
|
-
import { z as
|
|
3367
|
+
import { parse as parse2, stringify } from "smol-toml";
|
|
3368
|
+
import { z as z12 } from "zod";
|
|
3290
3369
|
async function instructionEdits(root, host) {
|
|
3291
3370
|
const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
|
|
3292
3371
|
const existing = found.find((f) => f.text !== null);
|
|
@@ -3327,7 +3406,7 @@ async function mcpEdit(root, host) {
|
|
|
3327
3406
|
const servers2 = object.parse(config2.mcpServers ?? {});
|
|
3328
3407
|
return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host) } }, null, 2) + "\n" };
|
|
3329
3408
|
}
|
|
3330
|
-
const config =
|
|
3409
|
+
const config = parse2(before ?? "");
|
|
3331
3410
|
const managed = (before ?? "").includes(TOML_START);
|
|
3332
3411
|
const servers = config.mcp_servers;
|
|
3333
3412
|
const desired = managedMcp(servers?.mason, host);
|
|
@@ -3339,7 +3418,7 @@ async function mcpEdit(root, host) {
|
|
|
3339
3418
|
for (const span of spans.reverse()) base = base.slice(0, span.start) + base.slice(span.end);
|
|
3340
3419
|
}
|
|
3341
3420
|
const after = managedBlock(base, TOML_START, TOML_END, stringify({ mcp_servers: { mason: desired } }).trimEnd());
|
|
3342
|
-
const parsed =
|
|
3421
|
+
const parsed = parse2(after);
|
|
3343
3422
|
const expected = { ...config, mcp_servers: { ...servers ?? {}, mason: desired } };
|
|
3344
3423
|
if (!isDeepStrictEqual(parsed, expected)) throw new Error("Could not safely configure the Mason MCP entry without changing other settings.");
|
|
3345
3424
|
return { path: file, before, after };
|
|
@@ -3377,7 +3456,7 @@ async function ancillaryEdits(root) {
|
|
|
3377
3456
|
}
|
|
3378
3457
|
async function inspectHostConfig(root, host, plannedMcp) {
|
|
3379
3458
|
const text2 = plannedMcp ?? await readText(root, mcpPath(host));
|
|
3380
|
-
const config = host === "codex" ?
|
|
3459
|
+
const config = host === "codex" ? parse2(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
|
|
3381
3460
|
const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
|
|
3382
3461
|
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
|
|
3383
3462
|
const expected = hookConfig(host, hookCommand(host));
|
|
@@ -3402,7 +3481,7 @@ var init_config = __esm({
|
|
|
3402
3481
|
init_install();
|
|
3403
3482
|
init_files2();
|
|
3404
3483
|
init_launcher();
|
|
3405
|
-
object =
|
|
3484
|
+
object = z12.record(z12.unknown());
|
|
3406
3485
|
mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
3407
3486
|
TOML_START = "# mason:mcp:start";
|
|
3408
3487
|
TOML_END = "# mason:mcp:end";
|
|
@@ -3419,24 +3498,28 @@ __export(install_exports, {
|
|
|
3419
3498
|
configPath: () => configPath,
|
|
3420
3499
|
installAutomation: () => installAutomation,
|
|
3421
3500
|
installedAutomation: () => installedAutomation,
|
|
3422
|
-
planAutomationInstall: () => planAutomationInstall
|
|
3501
|
+
planAutomationInstall: () => planAutomationInstall,
|
|
3502
|
+
recordSchema: () => recordSchema
|
|
3423
3503
|
});
|
|
3424
|
-
import { z as
|
|
3504
|
+
import { z as z13 } from "zod";
|
|
3425
3505
|
async function installationRecord(root) {
|
|
3426
3506
|
return recordSchema.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
|
|
3427
3507
|
}
|
|
3428
3508
|
async function installAutomation(dir, host, command2) {
|
|
3429
3509
|
const ws = await workspace(dir);
|
|
3430
|
-
return withLock(ws.root, ".mason/reports/
|
|
3510
|
+
return withLock(ws.root, ".mason/reports/setup-lock", () => installLocked(ws.root, host, command2));
|
|
3431
3511
|
}
|
|
3432
3512
|
async function planAutomationInstall(root, host, command2) {
|
|
3433
3513
|
const file = configPath(host);
|
|
3434
|
-
const
|
|
3514
|
+
const before = await readText(root, file);
|
|
3515
|
+
const existing = automationConfigSchema.parse(JSON.parse(before ?? "{}"));
|
|
3435
3516
|
const record = await installationRecord(root);
|
|
3436
3517
|
const desired = hookConfig(host, command2);
|
|
3437
3518
|
const newCommand = desired.hooks.SessionStart[0].hooks[0].command;
|
|
3438
3519
|
const previous = record.hosts[host]?.command;
|
|
3439
|
-
const
|
|
3520
|
+
const ownershipText = await readText(root, OWNERSHIP_PATH);
|
|
3521
|
+
const pending = ownershipText === null ? [] : ownershipSchema.parse(JSON.parse(ownershipText)).files[file]?.hookCommands ?? [];
|
|
3522
|
+
const owned = /* @__PURE__ */ new Set([previous, ...pending, newCommand]);
|
|
3440
3523
|
const hooks = existing.hooks ?? {};
|
|
3441
3524
|
for (const event of HOOK_EVENTS) {
|
|
3442
3525
|
hooks[event] = (hooks[event] ?? []).map((group) => ({
|
|
@@ -3446,15 +3529,20 @@ async function planAutomationInstall(root, host, command2) {
|
|
|
3446
3529
|
hooks[event].push(...desired.hooks[event]);
|
|
3447
3530
|
}
|
|
3448
3531
|
record.hosts[host] = { command: newCommand };
|
|
3449
|
-
return { file, config: { ...existing, hooks }, record, newCommand };
|
|
3532
|
+
return { file, before, config: { ...existing, hooks }, record, newCommand };
|
|
3450
3533
|
}
|
|
3451
3534
|
async function installLocked(root, host, command2) {
|
|
3452
|
-
const { file, config, record, newCommand } = await planAutomationInstall(root, host, command2);
|
|
3535
|
+
const { file, before, config, record, newCommand } = await planAutomationInstall(root, host, command2);
|
|
3453
3536
|
const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3454
3537
|
const { applyEdit: applyEdit2 } = await Promise.resolve().then(() => (init_files2(), files_exports));
|
|
3455
|
-
|
|
3456
|
-
|
|
3538
|
+
const { ownershipEdit: ownershipEdit2, completedHookOwnership: completedHookOwnership2 } = await Promise.resolve().then(() => (init_ownership(), ownership_exports));
|
|
3539
|
+
const edit = { path: file, before, after: JSON.stringify(config, null, 2) + "\n" };
|
|
3540
|
+
const ancillary = await ancillaryEdits2(root);
|
|
3541
|
+
await applyEdit2(root, await ownershipEdit2(root, [edit], { [file]: newCommand }));
|
|
3542
|
+
for (const edit2 of ancillary) await applyEdit2(root, edit2);
|
|
3543
|
+
await applyEdit2(root, edit);
|
|
3457
3544
|
await writeStoreJson(root, AUTOMATION_PATH, record);
|
|
3545
|
+
await applyEdit2(root, await completedHookOwnership2(root, file, newCommand));
|
|
3458
3546
|
return {
|
|
3459
3547
|
version: 1,
|
|
3460
3548
|
host,
|
|
@@ -3492,14 +3580,248 @@ var init_install = __esm({
|
|
|
3492
3580
|
init_evidence();
|
|
3493
3581
|
init_adapters();
|
|
3494
3582
|
init_store();
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3583
|
+
init_files2();
|
|
3584
|
+
init_ownership();
|
|
3585
|
+
groupSchema = z13.object({ hooks: z13.array(z13.object({ type: z13.string(), command: z13.string().optional() }).passthrough()) }).passthrough();
|
|
3586
|
+
automationConfigSchema = z13.object({ hooks: z13.record(z13.array(groupSchema)).optional() }).passthrough();
|
|
3587
|
+
recordSchema = z13.object({ version: z13.literal(1), hosts: z13.record(z13.object({ command: z13.string() })) });
|
|
3498
3588
|
AUTOMATION_PATH = ".mason/local/automation.json";
|
|
3499
3589
|
configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
|
|
3500
3590
|
}
|
|
3501
3591
|
});
|
|
3502
3592
|
|
|
3593
|
+
// src/setup/teardown.ts
|
|
3594
|
+
var teardown_exports = {};
|
|
3595
|
+
__export(teardown_exports, {
|
|
3596
|
+
summarizeTeardown: () => summarizeTeardown,
|
|
3597
|
+
teardownProject: () => teardownProject
|
|
3598
|
+
});
|
|
3599
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
3600
|
+
import { parse as parse3, stringify as stringify2 } from "smol-toml";
|
|
3601
|
+
import fg5 from "fast-glob";
|
|
3602
|
+
import { z as z14 } from "zod";
|
|
3603
|
+
async function planTeardown(root, selected) {
|
|
3604
|
+
const edits = [], diagnostics = [];
|
|
3605
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
3606
|
+
const read = async (file) => {
|
|
3607
|
+
if (!inputs.has(file)) inputs.set(file, await readText(root, file));
|
|
3608
|
+
return inputs.get(file);
|
|
3609
|
+
};
|
|
3610
|
+
const attempt = async (file, action) => {
|
|
3611
|
+
try {
|
|
3612
|
+
await action();
|
|
3613
|
+
} catch (error) {
|
|
3614
|
+
diagnostics.push({ path: file, message: error instanceof Error ? error.message : String(error) });
|
|
3615
|
+
}
|
|
3616
|
+
};
|
|
3617
|
+
const ownershipBefore = await read(OWNERSHIP_PATH);
|
|
3618
|
+
const ownership = ownershipBefore === null ? { version: 1, files: {} } : ownershipSchema.parse(JSON.parse(ownershipBefore));
|
|
3619
|
+
const setupBefore = await read(SETUP_PATH);
|
|
3620
|
+
const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
|
|
3621
|
+
const automationBefore = await read(AUTOMATION_PATH);
|
|
3622
|
+
const automation = automationBefore === null ? null : recordSchema.parse(JSON.parse(automationBefore));
|
|
3623
|
+
const commands = (host) => new Set([
|
|
3624
|
+
automation?.hosts[host]?.command,
|
|
3625
|
+
...ownership.files[configPath(host)]?.hookCommands ?? [],
|
|
3626
|
+
hookConfig(host, hookCommand(host)).hooks.SessionStart[0].hooks[0].command,
|
|
3627
|
+
hookConfig(host).hooks.SessionStart[0].hooks[0].command
|
|
3628
|
+
].filter((command2) => !!command2));
|
|
3629
|
+
const possibleHook = (command2, host) => commands(host).has(command2) || /\bmason(?:-auto|-hook)?\b/.test(command2);
|
|
3630
|
+
const add = (file, before, after, empty) => {
|
|
3631
|
+
edits.push({ path: file, before, after: ownership.files[file]?.created && (empty || ownership.files[file].initialHash === hash(before)) ? null : after });
|
|
3632
|
+
};
|
|
3633
|
+
let remainingHost = hosts.some((host) => !selected.includes(host) && (setup?.hosts[host] || automation?.hosts[host] || ownership.files[mcpPath(host)] || ownership.files[configPath(host)]));
|
|
3634
|
+
for (const host of hosts.filter((host2) => !selected.includes(host2))) {
|
|
3635
|
+
await attempt(mcpPath(host), async () => {
|
|
3636
|
+
const text2 = await read(mcpPath(host));
|
|
3637
|
+
const config = host === "codex" ? parse3(text2 ?? "") : object2.parse(JSON.parse(text2 ?? "{}"));
|
|
3638
|
+
const servers = object2.parse((host === "codex" ? config.mcp_servers : config.mcpServers) ?? {});
|
|
3639
|
+
remainingHost ||= servers.mason !== void 0;
|
|
3640
|
+
});
|
|
3641
|
+
await attempt(configPath(host), async () => {
|
|
3642
|
+
const config = automationConfigSchema.parse(JSON.parse(await read(configPath(host)) ?? "{}"));
|
|
3643
|
+
remainingHost ||= Object.values(config.hooks ?? {}).some((groups) => groups.some((group) => group.hooks.some((handler) => handler.command && possibleHook(handler.command, host))));
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3646
|
+
for (const host of selected) {
|
|
3647
|
+
const mcpFile = mcpPath(host);
|
|
3648
|
+
await attempt(mcpFile, async () => {
|
|
3649
|
+
const before = await read(mcpFile);
|
|
3650
|
+
if (before === null) return;
|
|
3651
|
+
const config = host === "codex" ? parse3(before) : object2.parse(JSON.parse(before));
|
|
3652
|
+
const key = host === "codex" ? "mcp_servers" : "mcpServers";
|
|
3653
|
+
const servers = object2.parse(config[key] ?? {});
|
|
3654
|
+
if (servers.mason === void 0) {
|
|
3655
|
+
if (host === "codex" && BLOCKS[2].some((marker) => before.includes(marker))) throw new Error("The marked Mason MCP entry was removed or renamed; inspect the retained block manually.");
|
|
3656
|
+
return;
|
|
3657
|
+
}
|
|
3658
|
+
const expected = ownership.files[mcpFile]?.mcpHash ?? hash(mcpCommand(host));
|
|
3659
|
+
if (hash(servers.mason) !== expected) throw new Error("The Mason MCP entry was edited or its ownership is uncertain; retained for manual cleanup.");
|
|
3660
|
+
delete servers.mason;
|
|
3661
|
+
const desired = { ...config, [key]: servers };
|
|
3662
|
+
if (!Object.keys(servers).length) delete desired[key];
|
|
3663
|
+
if (host === "claude") {
|
|
3664
|
+
add(mcpFile, before, json(desired), !Object.keys(desired).length);
|
|
3665
|
+
return;
|
|
3666
|
+
}
|
|
3667
|
+
const [start, end] = BLOCKS[2], block = blockSpan(before, start, end);
|
|
3668
|
+
if (!block) throw new Error("Mason MCP markers are missing; retained for manual cleanup.");
|
|
3669
|
+
const expectedBlock = ownership.files[mcpFile]?.blocks[start];
|
|
3670
|
+
const canonicalBlock = managedBlock("", start, end, stringify2({ mcp_servers: { mason: mcpCommand(host) } }).trimEnd()).trimEnd();
|
|
3671
|
+
if (expectedBlock ? hash(block.text) !== expectedBlock : block.text.replace(/\r\n/g, "\n") !== canonicalBlock) {
|
|
3672
|
+
throw new Error("The Mason MCP block was edited; retained for manual cleanup.");
|
|
3673
|
+
}
|
|
3674
|
+
const after = before.slice(0, block.from) + before.slice(block.to).replace(/^\r?\n/, "");
|
|
3675
|
+
const parsed = parse3(after);
|
|
3676
|
+
if (!Object.keys(servers).length && parsed[key] !== void 0) desired[key] = {};
|
|
3677
|
+
if (!isDeepStrictEqual2(parsed, desired)) throw new Error("Removing the marked block would change other TOML settings; retained for manual cleanup.");
|
|
3678
|
+
add(mcpFile, before, after, !after.trim());
|
|
3679
|
+
});
|
|
3680
|
+
const hookFile = configPath(host);
|
|
3681
|
+
await attempt(hookFile, async () => {
|
|
3682
|
+
const before = await read(hookFile);
|
|
3683
|
+
if (before === null) return;
|
|
3684
|
+
const config = automationConfigSchema.parse(JSON.parse(before));
|
|
3685
|
+
let changed = false;
|
|
3686
|
+
for (const [event, groups] of Object.entries(config.hooks ?? {})) {
|
|
3687
|
+
const known = ownership.files[hookFile]?.hooks?.[event];
|
|
3688
|
+
const handlers = groups.flatMap((group) => group.hooks);
|
|
3689
|
+
if (known && !handlers.some((handler) => handler.type === "command" && handler.command && commands(host).has(handler.command)) && handlers.some((handler) => !known.includes(hash(handler)))) {
|
|
3690
|
+
diagnostics.push({ path: hookFile, message: "A recorded Mason hook is missing beside a new or edited handler (" + event + "); inspect ownership manually." });
|
|
3691
|
+
}
|
|
3692
|
+
const kept = groups.flatMap((group) => {
|
|
3693
|
+
const handlers2 = group.hooks.filter((handler) => {
|
|
3694
|
+
if (handler.type === "command" && handler.command && commands(host).has(handler.command)) {
|
|
3695
|
+
changed = true;
|
|
3696
|
+
return false;
|
|
3697
|
+
}
|
|
3698
|
+
if (handler.command && possibleHook(handler.command, host)) diagnostics.push({
|
|
3699
|
+
path: hookFile,
|
|
3700
|
+
message: "An unrecognized Mason hook was retained for manual cleanup (" + event + ")."
|
|
3701
|
+
});
|
|
3702
|
+
return true;
|
|
3703
|
+
});
|
|
3704
|
+
return handlers2.length === group.hooks.length ? [group] : handlers2.length ? [{ ...group, hooks: handlers2 }] : [];
|
|
3705
|
+
});
|
|
3706
|
+
if (kept.length || !groups.length) config.hooks[event] = kept;
|
|
3707
|
+
else delete config.hooks[event];
|
|
3708
|
+
}
|
|
3709
|
+
if (!changed) return;
|
|
3710
|
+
if (!Object.keys(config.hooks ?? {}).length) delete config.hooks;
|
|
3711
|
+
add(hookFile, before, json(config), !Object.keys(config).length);
|
|
3712
|
+
});
|
|
3713
|
+
}
|
|
3714
|
+
const instructionEditStart = edits.length;
|
|
3715
|
+
if (!remainingHost && !diagnostics.length) for (const file of DOC_CANDIDATES) await attempt(file, async () => {
|
|
3716
|
+
const before = await read(file);
|
|
3717
|
+
if (before === null) return;
|
|
3718
|
+
let after = before;
|
|
3719
|
+
for (const [start, end] of BLOCKS.slice(0, 2)) {
|
|
3720
|
+
const block = blockSpan(after, start, end);
|
|
3721
|
+
if (!block) continue;
|
|
3722
|
+
const pointer = "Mason project knowledge and shared project instructions:\n@" + (file.startsWith(".claude/") ? "../AGENTS.md" : "AGENTS.md");
|
|
3723
|
+
const canonical = [CLAUDE_MD_SECTION, managedBlock("", start, end, pointer).trimEnd()];
|
|
3724
|
+
const expected = ownership.files[file]?.blocks[start];
|
|
3725
|
+
if (expected ? hash(block.text) !== expected : !canonical.includes(block.text.replace(/\r\n/g, "\n"))) {
|
|
3726
|
+
throw new Error("The Mason instruction block was edited or is unrecognized; retained for manual cleanup.");
|
|
3727
|
+
}
|
|
3728
|
+
after = after.slice(0, block.from) + after.slice(block.to).replace(/^\r?\n/, "");
|
|
3729
|
+
}
|
|
3730
|
+
if (after !== before) add(file, before, after, !after.trim());
|
|
3731
|
+
});
|
|
3732
|
+
if (diagnostics.length) edits.splice(instructionEditStart);
|
|
3733
|
+
if (!diagnostics.length) {
|
|
3734
|
+
const cleanup = [];
|
|
3735
|
+
const receiptFiles = await fg5(
|
|
3736
|
+
selected.map((host) => ".mason/reports/automation/*/setup-" + host + ".json"),
|
|
3737
|
+
{ cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false }
|
|
3738
|
+
);
|
|
3739
|
+
if (receiptFiles.length > 1e3) throw new Error("Too many setup receipts; inspect local setup history before teardown.");
|
|
3740
|
+
for (const file of receiptFiles.sort()) await attempt(file, async () => {
|
|
3741
|
+
const host = file.endsWith("setup-codex.json") ? "codex" : "claude";
|
|
3742
|
+
await loadSetupReceipt(root, file.slice(0, file.lastIndexOf("/")), host);
|
|
3743
|
+
cleanup.push({ path: file, before: await read(file), after: null });
|
|
3744
|
+
});
|
|
3745
|
+
if (!remainingHost) await attempt(".mason/local/project.json", async () => {
|
|
3746
|
+
const before = await read(".mason/local/project.json");
|
|
3747
|
+
if (before === null) return;
|
|
3748
|
+
z14.object({ version: z14.literal(1), initializedAt: z14.string() }).strict().parse(JSON.parse(before));
|
|
3749
|
+
cleanup.push({ path: ".mason/local/project.json", before, after: null });
|
|
3750
|
+
});
|
|
3751
|
+
if (!diagnostics.length) {
|
|
3752
|
+
for (const host of selected) {
|
|
3753
|
+
if (setup) delete setup.hosts[host];
|
|
3754
|
+
if (automation) delete automation.hosts[host];
|
|
3755
|
+
delete ownership.files[mcpPath(host)];
|
|
3756
|
+
delete ownership.files[configPath(host)];
|
|
3757
|
+
}
|
|
3758
|
+
if (!remainingHost) for (const file of DOC_CANDIDATES) delete ownership.files[file];
|
|
3759
|
+
if (setupBefore !== null) cleanup.push({ path: SETUP_PATH, before: setupBefore, after: Object.keys(setup.hosts).length ? json(setup) : null });
|
|
3760
|
+
if (automationBefore !== null) cleanup.push({ path: AUTOMATION_PATH, before: automationBefore, after: Object.keys(automation.hosts).length ? json(automation) : null });
|
|
3761
|
+
if (ownershipBefore !== null) cleanup.push({ path: OWNERSHIP_PATH, before: ownershipBefore, after: Object.keys(ownership.files).length ? json(ownership) : null });
|
|
3762
|
+
edits.push(...cleanup);
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
return { edits: edits.filter((edit) => edit.before !== edit.after), diagnostics, remainingHost, inputs };
|
|
3766
|
+
}
|
|
3767
|
+
async function teardownProject(dir, options = {}) {
|
|
3768
|
+
const { root } = await workspace(dir);
|
|
3769
|
+
const selected = options.host ? [options.host] : [...hosts];
|
|
3770
|
+
const run = async (apply) => {
|
|
3771
|
+
const plan = await planTeardown(root, selected);
|
|
3772
|
+
if (apply) {
|
|
3773
|
+
for (const [file, before] of plan.inputs) if (await readText(root, file) !== before) throw new Error("Input changed during teardown: " + file + ". Rerun teardown.");
|
|
3774
|
+
for (const edit of plan.edits) await applyEdit(root, edit);
|
|
3775
|
+
}
|
|
3776
|
+
return {
|
|
3777
|
+
version: 1,
|
|
3778
|
+
action: "teardown",
|
|
3779
|
+
status: plan.diagnostics.length ? "incomplete" : "complete",
|
|
3780
|
+
root,
|
|
3781
|
+
hosts: selected,
|
|
3782
|
+
dryRun: !!options.dryRun,
|
|
3783
|
+
changes: plan.edits.map((edit) => ({ path: edit.path, action: edit.after === null ? "delete" : "update" })),
|
|
3784
|
+
diagnostics: plan.diagnostics,
|
|
3785
|
+
sharedInstructionsRetained: plan.remainingHost || !!plan.diagnostics.length,
|
|
3786
|
+
next: plan.diagnostics.length ? "Inspect the retained entries, remove or correct them, then rerun teardown." : "Restart running assistants to unload the project integration. Run mason setup --host codex or --host claude to reconnect."
|
|
3787
|
+
};
|
|
3788
|
+
};
|
|
3789
|
+
const preview = await run(false);
|
|
3790
|
+
if (options.dryRun || !preview.changes.length) return preview;
|
|
3791
|
+
return withLock(root, ".mason/reports/setup-lock", () => run(true));
|
|
3792
|
+
}
|
|
3793
|
+
function summarizeTeardown(result) {
|
|
3794
|
+
return [
|
|
3795
|
+
`Mason teardown${result.dryRun ? " preview" : ""}: ${result.status}.`,
|
|
3796
|
+
...result.changes.map((change) => ` ${result.dryRun ? "Would " : ""}${change.action}: ${change.path}`),
|
|
3797
|
+
...result.diagnostics.map((item) => ` Retained ${item.path}: ${item.message}`),
|
|
3798
|
+
...!result.changes.length ? ["No changes."] : [],
|
|
3799
|
+
"Shared knowledge, repair evidence, ignore rules and the installed Mason command are retained.",
|
|
3800
|
+
...result.sharedInstructionsRetained ? ["Shared instruction cleanup is deferred while another host or incomplete removal needs inspection."] : [],
|
|
3801
|
+
...result.dryRun ? ["Preview only; no files were changed."] : [result.next]
|
|
3802
|
+
].join("\n");
|
|
3803
|
+
}
|
|
3804
|
+
var hosts, object2, json;
|
|
3805
|
+
var init_teardown = __esm({
|
|
3806
|
+
"src/setup/teardown.ts"() {
|
|
3807
|
+
"use strict";
|
|
3808
|
+
init_evidence();
|
|
3809
|
+
init_store();
|
|
3810
|
+
init_install();
|
|
3811
|
+
init_adapters();
|
|
3812
|
+
init_docs();
|
|
3813
|
+
init_init();
|
|
3814
|
+
init_files2();
|
|
3815
|
+
init_config();
|
|
3816
|
+
init_launcher();
|
|
3817
|
+
init_model();
|
|
3818
|
+
init_ownership();
|
|
3819
|
+
hosts = ["codex", "claude"];
|
|
3820
|
+
object2 = z14.record(z14.unknown());
|
|
3821
|
+
json = (value) => JSON.stringify(value, null, 2) + "\n";
|
|
3822
|
+
}
|
|
3823
|
+
});
|
|
3824
|
+
|
|
3503
3825
|
// src/review/cochange.ts
|
|
3504
3826
|
import { execFile as execFile11 } from "child_process";
|
|
3505
3827
|
import { promisify as promisify11 } from "util";
|
|
@@ -3622,7 +3944,7 @@ var init_types2 = __esm({
|
|
|
3622
3944
|
});
|
|
3623
3945
|
|
|
3624
3946
|
// src/review/evidence/vitest.ts
|
|
3625
|
-
import { z as
|
|
3947
|
+
import { z as z15 } from "zod";
|
|
3626
3948
|
function parseVitest(raw, sourceRoot) {
|
|
3627
3949
|
const report = schema.parse(raw);
|
|
3628
3950
|
const findings = [], diagnostics = [];
|
|
@@ -3671,24 +3993,24 @@ var init_vitest = __esm({
|
|
|
3671
3993
|
"use strict";
|
|
3672
3994
|
init_paths2();
|
|
3673
3995
|
init_types2();
|
|
3674
|
-
count2 =
|
|
3675
|
-
schema =
|
|
3676
|
-
success:
|
|
3996
|
+
count2 = z15.number().int().nonnegative();
|
|
3997
|
+
schema = z15.object({
|
|
3998
|
+
success: z15.boolean(),
|
|
3677
3999
|
numTotalTests: count2,
|
|
3678
4000
|
numPassedTests: count2,
|
|
3679
4001
|
numFailedTests: count2,
|
|
3680
4002
|
numPendingTests: count2,
|
|
3681
4003
|
numTodoTests: count2,
|
|
3682
4004
|
numFailedTestSuites: count2,
|
|
3683
|
-
testResults:
|
|
3684
|
-
name:
|
|
3685
|
-
status:
|
|
3686
|
-
message:
|
|
3687
|
-
assertionResults:
|
|
3688
|
-
fullName:
|
|
3689
|
-
status:
|
|
3690
|
-
failureMessages:
|
|
3691
|
-
location:
|
|
4005
|
+
testResults: z15.array(z15.object({
|
|
4006
|
+
name: z15.string().min(1),
|
|
4007
|
+
status: z15.enum(["passed", "failed"]),
|
|
4008
|
+
message: z15.string().optional(),
|
|
4009
|
+
assertionResults: z15.array(z15.object({
|
|
4010
|
+
fullName: z15.string(),
|
|
4011
|
+
status: z15.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
|
|
4012
|
+
failureMessages: z15.array(z15.string()).nullable().optional(),
|
|
4013
|
+
location: z15.object({ line: count2, column: count2 }).nullable().optional()
|
|
3692
4014
|
}))
|
|
3693
4015
|
}))
|
|
3694
4016
|
});
|
|
@@ -3696,7 +4018,7 @@ var init_vitest = __esm({
|
|
|
3696
4018
|
});
|
|
3697
4019
|
|
|
3698
4020
|
// src/review/evidence/sarif.ts
|
|
3699
|
-
import { z as
|
|
4021
|
+
import { z as z16 } from "zod";
|
|
3700
4022
|
function parseSarif(raw, sourceRoot) {
|
|
3701
4023
|
const report = schema2.parse(raw), findings = [], diagnostics = [];
|
|
3702
4024
|
let active = 0, suppressed = 0, absent = 0, unresolved = 0;
|
|
@@ -3787,29 +4109,29 @@ var init_sarif = __esm({
|
|
|
3787
4109
|
"use strict";
|
|
3788
4110
|
init_paths2();
|
|
3789
4111
|
init_types2();
|
|
3790
|
-
index =
|
|
3791
|
-
artifact =
|
|
3792
|
-
message =
|
|
3793
|
-
level =
|
|
3794
|
-
location =
|
|
3795
|
-
schema2 =
|
|
3796
|
-
version:
|
|
3797
|
-
runs:
|
|
3798
|
-
tool:
|
|
3799
|
-
invocations:
|
|
3800
|
-
versionControlProvenance:
|
|
3801
|
-
originalUriBaseIds:
|
|
3802
|
-
artifacts:
|
|
3803
|
-
results:
|
|
3804
|
-
ruleId:
|
|
4112
|
+
index = z16.number().int().nonnegative();
|
|
4113
|
+
artifact = z16.object({ uri: z16.string().optional(), uriBaseId: z16.string().optional(), index: index.optional() });
|
|
4114
|
+
message = z16.object({ text: z16.string().optional(), markdown: z16.string().optional(), id: z16.string().optional(), arguments: z16.array(z16.string()).optional() });
|
|
4115
|
+
level = z16.enum(["error", "warning", "note", "none"]);
|
|
4116
|
+
location = z16.object({ physicalLocation: z16.object({ artifactLocation: artifact.optional(), region: z16.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
|
|
4117
|
+
schema2 = z16.object({
|
|
4118
|
+
version: z16.literal("2.1.0"),
|
|
4119
|
+
runs: z16.array(z16.object({
|
|
4120
|
+
tool: z16.object({ driver: z16.object({ name: z16.string().min(1), rules: z16.array(z16.object({ id: z16.string(), defaultConfiguration: z16.object({ level: level.optional() }).optional(), messageStrings: z16.record(message).optional() })).optional(), globalMessageStrings: z16.record(message).optional() }) }),
|
|
4121
|
+
invocations: z16.array(z16.object({ executionSuccessful: z16.boolean(), commandLine: z16.string().optional(), toolExecutionNotifications: z16.array(z16.object({ level: level.optional(), message })).optional() })).optional(),
|
|
4122
|
+
versionControlProvenance: z16.array(z16.object({ revisionId: z16.string().optional() })).optional(),
|
|
4123
|
+
originalUriBaseIds: z16.record(artifact).optional(),
|
|
4124
|
+
artifacts: z16.array(z16.object({ location: artifact.optional() })).optional(),
|
|
4125
|
+
results: z16.array(z16.object({
|
|
4126
|
+
ruleId: z16.string().optional(),
|
|
3805
4127
|
ruleIndex: index.optional(),
|
|
3806
4128
|
message,
|
|
3807
4129
|
level: level.optional(),
|
|
3808
|
-
kind:
|
|
3809
|
-
baselineState:
|
|
3810
|
-
suppressions:
|
|
3811
|
-
locations:
|
|
3812
|
-
relatedLocations:
|
|
4130
|
+
kind: z16.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
|
|
4131
|
+
baselineState: z16.enum(["new", "unchanged", "updated", "absent"]).optional(),
|
|
4132
|
+
suppressions: z16.array(z16.object({ kind: z16.enum(["inSource", "external"]), status: z16.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
|
|
4133
|
+
locations: z16.array(location).optional(),
|
|
4134
|
+
relatedLocations: z16.array(location).optional()
|
|
3813
4135
|
})).optional()
|
|
3814
4136
|
}))
|
|
3815
4137
|
});
|
|
@@ -3818,7 +4140,7 @@ var init_sarif = __esm({
|
|
|
3818
4140
|
|
|
3819
4141
|
// src/review/evidence.ts
|
|
3820
4142
|
import path19 from "path";
|
|
3821
|
-
import { z as
|
|
4143
|
+
import { z as z17 } from "zod";
|
|
3822
4144
|
function relativeArtifact(root, file) {
|
|
3823
4145
|
const relative = path19.isAbsolute(file) ? path19.relative(root, file) : file;
|
|
3824
4146
|
const normalized = normalizeRepoPath(relative);
|
|
@@ -3859,7 +4181,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
3859
4181
|
let manifest, raw;
|
|
3860
4182
|
try {
|
|
3861
4183
|
manifest = relativeArtifact(root, supplied);
|
|
3862
|
-
raw =
|
|
4184
|
+
raw = z17.object({ version: z17.literal(1), checks: z17.array(z17.unknown()) }).parse(await readStoreJson(root, manifest));
|
|
3863
4185
|
} catch (error) {
|
|
3864
4186
|
output.diagnostics.push(`${supplied}: ${String(error)}`);
|
|
3865
4187
|
continue;
|
|
@@ -4012,19 +4334,19 @@ var init_evidence2 = __esm({
|
|
|
4012
4334
|
init_vitest();
|
|
4013
4335
|
init_sarif();
|
|
4014
4336
|
init_types2();
|
|
4015
|
-
checkSchema2 =
|
|
4016
|
-
id:
|
|
4017
|
-
kind:
|
|
4018
|
-
tool:
|
|
4019
|
-
command:
|
|
4020
|
-
commit:
|
|
4021
|
-
workingTreeClean:
|
|
4022
|
-
source:
|
|
4023
|
-
sourceRoot:
|
|
4024
|
-
status:
|
|
4025
|
-
reason:
|
|
4026
|
-
exitCode:
|
|
4027
|
-
report:
|
|
4337
|
+
checkSchema2 = z17.object({
|
|
4338
|
+
id: z17.string().min(1).max(100),
|
|
4339
|
+
kind: z17.enum(["tests", "static-analysis", "security", "complexity", "duplication"]),
|
|
4340
|
+
tool: z17.string().min(1).max(200),
|
|
4341
|
+
command: z17.string().min(1).max(2e3),
|
|
4342
|
+
commit: z17.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
|
|
4343
|
+
workingTreeClean: z17.boolean().optional(),
|
|
4344
|
+
source: z17.string().max(2e3).optional(),
|
|
4345
|
+
sourceRoot: z17.string().min(1).max(4e3).optional(),
|
|
4346
|
+
status: z17.enum(["completed", "skipped", "unavailable"]).default("completed"),
|
|
4347
|
+
reason: z17.string().min(1).max(2e3).optional(),
|
|
4348
|
+
exitCode: z17.number().int().nullable().optional(),
|
|
4349
|
+
report: z17.object({ format: z17.enum(["vitest-json", "sarif"]), path: z17.string().min(1).max(4e3) }).optional()
|
|
4028
4350
|
});
|
|
4029
4351
|
}
|
|
4030
4352
|
});
|
|
@@ -4250,7 +4572,7 @@ __export(status_exports, {
|
|
|
4250
4572
|
setupStatus: () => setupStatus,
|
|
4251
4573
|
summarizeActivation: () => summarizeActivation
|
|
4252
4574
|
});
|
|
4253
|
-
import { isDeepStrictEqual as
|
|
4575
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
4254
4576
|
async function setupStatus(dir) {
|
|
4255
4577
|
const ws = await workspace(dir);
|
|
4256
4578
|
const setup = await loadSetup(ws.root);
|
|
@@ -4267,7 +4589,7 @@ async function setupStatus(dir) {
|
|
|
4267
4589
|
next: pending.length ? "Setup did not finish. Rerun mason-auto setup --host " + pending[0] + " to resume using the retained original evidence." : "Run mason-auto setup --host codex or --host claude."
|
|
4268
4590
|
};
|
|
4269
4591
|
}
|
|
4270
|
-
const
|
|
4592
|
+
const hosts2 = {};
|
|
4271
4593
|
const installed = await installedCommand();
|
|
4272
4594
|
const automation = await automationStatus(ws.root);
|
|
4273
4595
|
for (const host of ["codex", "claude"]) {
|
|
@@ -4276,8 +4598,8 @@ async function setupStatus(dir) {
|
|
|
4276
4598
|
const instructions = await instructionEdits(ws.root, host);
|
|
4277
4599
|
const instructionsCurrent = instructions.every((edit) => edit.before === edit.after);
|
|
4278
4600
|
const config = await inspectHostConfig(ws.root, host, void 0);
|
|
4279
|
-
const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint &&
|
|
4280
|
-
const hooks =
|
|
4601
|
+
const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual3({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host));
|
|
4602
|
+
const hooks = isDeepStrictEqual3(config.hooks, hookConfig(host, hookCommand(host)).hooks) && !config.disabled;
|
|
4281
4603
|
const local = await loadSetupReceipt(ws.root, ws.directory, host);
|
|
4282
4604
|
const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
|
|
4283
4605
|
const observation = await readObservation(ws.root, ws.directory, host, entry.revision, installed.version ?? "unavailable");
|
|
@@ -4291,7 +4613,7 @@ async function setupStatus(dir) {
|
|
|
4291
4613
|
if (!complete) pending.push("Review/trust the host configuration, then complete a normal task to observe the full hook lifecycle.");
|
|
4292
4614
|
if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason-auto check and inspect its diagnostics.");
|
|
4293
4615
|
const healthy = installed.available && mcp && hooks && instructionsCurrent && configured && automation.status !== "unavailable";
|
|
4294
|
-
|
|
4616
|
+
hosts2[host] = {
|
|
4295
4617
|
status: healthy ? complete && observation?.contextCalls ? "active" : "pending" : "attention",
|
|
4296
4618
|
runtime: installed.available ? "global (" + installed.version + ")" : "unavailable-on-path",
|
|
4297
4619
|
mcp: mcp ? "configured" : "changed",
|
|
@@ -4303,13 +4625,13 @@ async function setupStatus(dir) {
|
|
|
4303
4625
|
pending
|
|
4304
4626
|
};
|
|
4305
4627
|
}
|
|
4306
|
-
const statuses = Object.values(
|
|
4628
|
+
const statuses = Object.values(hosts2).map((host) => host.status);
|
|
4307
4629
|
const decisions = await loadDecisionStore(ws.root);
|
|
4308
4630
|
return {
|
|
4309
4631
|
version: 1,
|
|
4310
4632
|
status: statuses.length && statuses.every((status) => status === "active") ? "active" : statuses.includes("attention") ? "attention" : "pending",
|
|
4311
4633
|
root: ws.root,
|
|
4312
|
-
hosts,
|
|
4634
|
+
hosts: hosts2,
|
|
4313
4635
|
decisionRecords: decisions.records.length,
|
|
4314
4636
|
diagnostics: decisions.diagnostics,
|
|
4315
4637
|
scope: "Activation receipts record observed use after setup, not complete interception, correct repairs, or measured usefulness. Host trust and higher-priority settings may prevent execution. Receipts are local to this worktree and branch."
|
|
@@ -4378,6 +4700,8 @@ async function setupProject(dir, options = {}) {
|
|
|
4378
4700
|
const setupBefore = await readText(ws.root, SETUP_PATH);
|
|
4379
4701
|
const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after)).mcp);
|
|
4380
4702
|
const desiredHooks = hookConfig(host, hookCommand(host)).hooks;
|
|
4703
|
+
const configuredHook = desiredHooks.SessionStart[0].hooks[0].command;
|
|
4704
|
+
const ownership = await ownershipEdit(ws.root, [...instructions, mcp, hooks[0]], { [hooks[0].path]: configuredHook });
|
|
4381
4705
|
const fingerprint = hash({
|
|
4382
4706
|
command: "mason",
|
|
4383
4707
|
mcp: mcpFingerprint,
|
|
@@ -4404,8 +4728,10 @@ async function setupProject(dir, options = {}) {
|
|
|
4404
4728
|
revision
|
|
4405
4729
|
});
|
|
4406
4730
|
const changedFiles = [];
|
|
4731
|
+
if (await applyEdit(ws.root, ownership)) changedFiles.push(ownership.path);
|
|
4407
4732
|
for (const edit of edits) if (await applyEdit(ws.root, edit)) changedFiles.push(edit.path);
|
|
4408
4733
|
if (await applyEdit(ws.root, { path: SETUP_PATH, before: setupBefore, after: JSON.stringify(setup, null, 2) + "\n" })) changedFiles.push(SETUP_PATH);
|
|
4734
|
+
if (await applyEdit(ws.root, await completedHookOwnership(ws.root, hooks[0].path, configuredHook)) && !changedFiles.includes(ownership.path)) changedFiles.push(ownership.path);
|
|
4409
4735
|
const checked = await automate(ws.root, { event: "turn_start" });
|
|
4410
4736
|
const configured = await inspectHostConfig(ws.root, host, void 0);
|
|
4411
4737
|
await writeStoreJson(ws.root, receiptPath, {
|
|
@@ -4464,6 +4790,7 @@ var init_setup = __esm({
|
|
|
4464
4790
|
init_adapters();
|
|
4465
4791
|
init_model();
|
|
4466
4792
|
init_status();
|
|
4793
|
+
init_ownership();
|
|
4467
4794
|
}
|
|
4468
4795
|
});
|
|
4469
4796
|
|
|
@@ -4471,7 +4798,7 @@ var init_setup = __esm({
|
|
|
4471
4798
|
import fs16 from "fs/promises";
|
|
4472
4799
|
import path21 from "path";
|
|
4473
4800
|
import { createHash as createHash4 } from "crypto";
|
|
4474
|
-
import { z as
|
|
4801
|
+
import { z as z18 } from "zod";
|
|
4475
4802
|
async function readBundle(root) {
|
|
4476
4803
|
const bytes = await fs16.readFile(await storePath(root, "bundle.json"));
|
|
4477
4804
|
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
@@ -4502,13 +4829,13 @@ var init_bundle = __esm({
|
|
|
4502
4829
|
"use strict";
|
|
4503
4830
|
init_storage();
|
|
4504
4831
|
targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
4505
|
-
digest2 =
|
|
4506
|
-
bundleSchema =
|
|
4507
|
-
format:
|
|
4508
|
-
version:
|
|
4509
|
-
target:
|
|
4510
|
-
nodeVersion:
|
|
4511
|
-
files:
|
|
4832
|
+
digest2 = z18.string().regex(/^[a-f0-9]{64}$/);
|
|
4833
|
+
bundleSchema = z18.object({
|
|
4834
|
+
format: z18.literal(1),
|
|
4835
|
+
version: z18.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
4836
|
+
target: z18.enum(targets),
|
|
4837
|
+
nodeVersion: z18.string(),
|
|
4838
|
+
files: z18.record(digest2)
|
|
4512
4839
|
}).strict();
|
|
4513
4840
|
sha256 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
|
|
4514
4841
|
}
|
|
@@ -4521,7 +4848,7 @@ import path22 from "path";
|
|
|
4521
4848
|
import { execFile as execFile13 } from "child_process";
|
|
4522
4849
|
import { promisify as promisify13 } from "util";
|
|
4523
4850
|
import { createHash as createHash5, randomUUID as randomUUID7 } from "crypto";
|
|
4524
|
-
import { z as
|
|
4851
|
+
import { z as z19 } from "zod";
|
|
4525
4852
|
async function configurePath(bin, previous, save, options = {}) {
|
|
4526
4853
|
const env = options.env ?? process.env, platform = options.platform ?? process.platform;
|
|
4527
4854
|
const changes = [...previous];
|
|
@@ -4681,9 +5008,9 @@ var init_path = __esm({
|
|
|
4681
5008
|
"src/distribution/path.ts"() {
|
|
4682
5009
|
"use strict";
|
|
4683
5010
|
execute2 = promisify13(execFile13);
|
|
4684
|
-
pathChangeSchema =
|
|
4685
|
-
|
|
4686
|
-
|
|
5011
|
+
pathChangeSchema = z19.discriminatedUnion("kind", [
|
|
5012
|
+
z19.object({ kind: z19.literal("profile"), file: z19.string(), block: z19.string(), created: z19.boolean() }),
|
|
5013
|
+
z19.object({ kind: z19.literal("windows"), entry: z19.string(), created: z19.boolean() })
|
|
4687
5014
|
]);
|
|
4688
5015
|
quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
4689
5016
|
normalizeWindowsEntry = (value) => path22.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
|
|
@@ -4710,7 +5037,7 @@ import path23 from "path";
|
|
|
4710
5037
|
import { fileURLToPath } from "url";
|
|
4711
5038
|
import { spawn } from "child_process";
|
|
4712
5039
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
4713
|
-
import { z as
|
|
5040
|
+
import { z as z20 } from "zod";
|
|
4714
5041
|
async function replace(file, content2, mode = 384) {
|
|
4715
5042
|
const temp = file + ".tmp-" + randomUUID8();
|
|
4716
5043
|
try {
|
|
@@ -4870,15 +5197,15 @@ var init_install2 = __esm({
|
|
|
4870
5197
|
init_storage();
|
|
4871
5198
|
init_store();
|
|
4872
5199
|
init_path();
|
|
4873
|
-
recordSchema2 =
|
|
4874
|
-
format:
|
|
4875
|
-
home:
|
|
4876
|
-
bin:
|
|
4877
|
-
current:
|
|
4878
|
-
version:
|
|
4879
|
-
launchers:
|
|
4880
|
-
previousLaunchers:
|
|
4881
|
-
pathChanges:
|
|
5200
|
+
recordSchema2 = z20.object({
|
|
5201
|
+
format: z20.literal(1),
|
|
5202
|
+
home: z20.string(),
|
|
5203
|
+
bin: z20.string(),
|
|
5204
|
+
current: z20.string().regex(/^[a-f0-9]{24}$/),
|
|
5205
|
+
version: z20.string(),
|
|
5206
|
+
launchers: z20.record(z20.string()),
|
|
5207
|
+
previousLaunchers: z20.record(z20.string()).optional(),
|
|
5208
|
+
pathChanges: z20.array(pathChangeSchema).optional()
|
|
4882
5209
|
});
|
|
4883
5210
|
shQuote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
4884
5211
|
psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
|
|
@@ -4898,9 +5225,23 @@ init_adapters();
|
|
|
4898
5225
|
init_install();
|
|
4899
5226
|
init_store();
|
|
4900
5227
|
import { parseArgs } from "util";
|
|
4901
|
-
var
|
|
5228
|
+
var TEARDOWN_USAGE = `Usage: mason teardown [options]
|
|
5229
|
+
|
|
5230
|
+
Disconnect Mason from this Git project. Defaults to all project hosts.
|
|
5231
|
+
|
|
5232
|
+
--host codex|claude Disconnect only this host
|
|
5233
|
+
--dir <path> Project directory (defaults to cwd; subdirectories work)
|
|
5234
|
+
--dry-run Preview changes without writing
|
|
5235
|
+
--json Machine-readable changes and diagnostics
|
|
5236
|
+
|
|
5237
|
+
Keeps shared knowledge, repair evidence, ignore rules and the Mason installation.
|
|
5238
|
+
Edited or ambiguous entries are retained for manual cleanup. Restart assistants
|
|
5239
|
+
after teardown. Run mason setup --host codex or --host claude to reconnect.
|
|
5240
|
+
Exit 0: complete/no-op. Exit 2: cleanup needs attention (also in dry runs).`;
|
|
5241
|
+
var USAGE = `Usage: mason-auto <setup|teardown|install|config|status|check|hook> [options]
|
|
4902
5242
|
|
|
4903
5243
|
setup [--host claude|codex] Connect MCP, instructions and hooks to mason on PATH; retain the initial audit
|
|
5244
|
+
teardown [--host claude|codex] Disconnect one host or all project hosts; retain knowledge and evidence
|
|
4904
5245
|
install --host claude|codex Merge lifecycle hooks into this project's host config
|
|
4905
5246
|
config --host claude|codex Print the host config without writing
|
|
4906
5247
|
status Read configured hooks and observed runtime events
|
|
@@ -4909,9 +5250,11 @@ var USAGE = `Usage: mason-auto <setup|install|config|status|check|hook> [options
|
|
|
4909
5250
|
|
|
4910
5251
|
--dir <path> Project directory (defaults to cwd)
|
|
4911
5252
|
--command <prefix> Installed executable prefix for install/config
|
|
5253
|
+
--dry-run Preview teardown without writing
|
|
4912
5254
|
--json Machine-readable output (status also uses JSON when piped)
|
|
4913
5255
|
|
|
4914
5256
|
check exits 0 for verified checks, 1 for issues, 2 for incomplete/unavailable.
|
|
5257
|
+
teardown exits 0 for complete/no-op, 2 when cleanup needs attention (also in dry runs).
|
|
4915
5258
|
Hooks are advisory and exit 0; a failed capture is reported explicitly.
|
|
4916
5259
|
Local evidence is written under .mason/reports/. No LLM calls or source edits.`;
|
|
4917
5260
|
var parseCli = (argv) => parseArgs({ args: argv, allowPositionals: true, options: {
|
|
@@ -4919,7 +5262,8 @@ var parseCli = (argv) => parseArgs({ args: argv, allowPositionals: true, options
|
|
|
4919
5262
|
host: { type: "string" },
|
|
4920
5263
|
command: { type: "string" },
|
|
4921
5264
|
json: { type: "boolean" },
|
|
4922
|
-
help: { type: "boolean", short: "h" }
|
|
5265
|
+
help: { type: "boolean", short: "h" },
|
|
5266
|
+
"dry-run": { type: "boolean" }
|
|
4923
5267
|
} });
|
|
4924
5268
|
function isHookCommand(argv) {
|
|
4925
5269
|
try {
|
|
@@ -4937,12 +5281,20 @@ async function runAutomationCli(argv, stdin = "", io = {
|
|
|
4937
5281
|
try {
|
|
4938
5282
|
const { values, positionals } = parseCli(argv);
|
|
4939
5283
|
if (values.help || !positionals.length) {
|
|
4940
|
-
io.out(USAGE);
|
|
5284
|
+
io.out(positionals[0] === "teardown" ? TEARDOWN_USAGE : USAGE);
|
|
4941
5285
|
return 0;
|
|
4942
5286
|
}
|
|
4943
5287
|
if (positionals.length !== 1) throw new Error("Expected one command.");
|
|
4944
5288
|
[action] = positionals;
|
|
4945
5289
|
const dir = values.dir ?? process.cwd();
|
|
5290
|
+
if (values["dry-run"] !== void 0 && action !== "teardown") throw new Error("--dry-run applies only to teardown.");
|
|
5291
|
+
if (action === "teardown") {
|
|
5292
|
+
if (values.command !== void 0) throw new Error("--command applies only to install/config.");
|
|
5293
|
+
const { teardownProject: teardownProject2, summarizeTeardown: summarizeTeardown2 } = await Promise.resolve().then(() => (init_teardown(), teardown_exports));
|
|
5294
|
+
const result = await teardownProject2(dir, { host: values.host === void 0 ? void 0 : hostSchema.parse(values.host), dryRun: values["dry-run"] });
|
|
5295
|
+
io.out(values.json ? JSON.stringify(result, null, 2) : summarizeTeardown2(result));
|
|
5296
|
+
return result.status === "complete" ? 0 : 2;
|
|
5297
|
+
}
|
|
4946
5298
|
if (action === "hook") {
|
|
4947
5299
|
const output = await runAutomationHook(hostSchema.parse(values.host), stdin);
|
|
4948
5300
|
if (output) io.out(JSON.stringify(output));
|
|
@@ -4973,12 +5325,12 @@ async function runAutomationCli(argv, stdin = "", io = {
|
|
|
4973
5325
|
io.out(values.json ? JSON.stringify(report, null, 2) : summarize(report));
|
|
4974
5326
|
return report.status === "verified" ? 0 : report.status === "issues-remain" ? 1 : 2;
|
|
4975
5327
|
} catch (error) {
|
|
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);
|
|
5328
|
+
const message2 = action === "setup" ? "Mason setup incomplete: " + automationFailure(error).message + ". Rerun the same setup command to resume; retained audit evidence is preserved." : action === "teardown" ? "Mason teardown incomplete: " + automationFailure(error).message + ". Inspect the reported input, then rerun teardown; knowledge and repair evidence are retained." : failureMessage(error);
|
|
4977
5329
|
if (action === "hook" || isHookCommand(argv)) {
|
|
4978
5330
|
io.out(JSON.stringify({ systemMessage: message2 }));
|
|
4979
5331
|
return 0;
|
|
4980
5332
|
}
|
|
4981
|
-
if (argv.includes("--json")) io.out(JSON.stringify({ version: 1, ...
|
|
5333
|
+
if (argv.includes("--json")) io.out(JSON.stringify({ version: 1, ...["setup", "teardown"].includes(action) ? { action, status: "incomplete", next: "Inspect the reported input, then rerun the same command to resume." } : { status: "unavailable" }, failure: automationFailure(error) }));
|
|
4982
5334
|
else io.err(message2);
|
|
4983
5335
|
return 2;
|
|
4984
5336
|
}
|
|
@@ -4989,11 +5341,12 @@ var inputArgs = process.argv.slice(2);
|
|
|
4989
5341
|
var managedLaunch = inputArgs[0] === "--setup-host";
|
|
4990
5342
|
var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
|
|
4991
5343
|
var [command, ...args] = inputArgs;
|
|
4992
|
-
var usage = `Mason ${"0.
|
|
5344
|
+
var usage = `Mason ${"0.16.0"}
|
|
4993
5345
|
|
|
4994
5346
|
Usage: mason <command> [options]
|
|
4995
5347
|
|
|
4996
5348
|
setup --host codex|claude Connect this project to your assistant
|
|
5349
|
+
teardown [--host codex|claude] Disconnect project integrations; retain knowledge
|
|
4997
5350
|
status Show configuration, observed use and verification
|
|
4998
5351
|
check Resume and verify retained documentation findings
|
|
4999
5352
|
audit Audit project instructions
|
|
@@ -5011,9 +5364,9 @@ try {
|
|
|
5011
5364
|
const { prepareLaunch: prepareLaunch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
|
|
5012
5365
|
await prepareLaunch2(setupHost);
|
|
5013
5366
|
}
|
|
5014
|
-
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.
|
|
5367
|
+
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.16.0" }));
|
|
5015
5368
|
else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
|
|
5016
|
-
else if (["--version", "-v"].includes(command)) console.log("0.
|
|
5369
|
+
else if (["--version", "-v"].includes(command)) console.log("0.16.0");
|
|
5017
5370
|
else if (command === "internal-install") {
|
|
5018
5371
|
if (args.length) throw new Error("internal-install takes no arguments.");
|
|
5019
5372
|
const { installStandalone: installStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
@@ -5022,14 +5375,14 @@ try {
|
|
|
5022
5375
|
console.log(`Installed Mason ${installed.version} in ${installed.home}.
|
|
5023
5376
|
${installed.path.message}`);
|
|
5024
5377
|
} else if (command === "upgrade" || command === "uninstall") {
|
|
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
|
|
5378
|
+
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. Run mason teardown in each project first to disconnect its integrations.");
|
|
5026
5379
|
else {
|
|
5027
5380
|
const { upgradeStandalone: upgradeStandalone2, uninstallStandalone: uninstallStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
5028
5381
|
if (args.length > (command === "upgrade" ? 1 : 0)) throw new Error("Unexpected arguments.");
|
|
5029
5382
|
if (command === "upgrade") process.exitCode = await upgradeStandalone2(args[0]);
|
|
5030
5383
|
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.");
|
|
5031
5384
|
}
|
|
5032
|
-
} else if (["setup", "status", "check", "auto"].includes(command)) {
|
|
5385
|
+
} else if (["setup", "teardown", "status", "check", "auto"].includes(command)) {
|
|
5033
5386
|
const forwarded = command === "auto" ? args : [command, ...args];
|
|
5034
5387
|
let input = "";
|
|
5035
5388
|
if (isHookCommand(forwarded) && !process.stdin.isTTY) for await (const chunk of process.stdin) {
|