mason-context 0.15.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/mason-auto.js +445 -93
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-mcp.js +560 -301
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason.js +473 -120
- package/dist/mason.js.map +1 -1
- package/package.json +2 -1
package/dist/mason-auto.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.1" : "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
|
};
|
|
@@ -2920,7 +2920,7 @@ var init_launcher = __esm({
|
|
|
2920
2920
|
"use strict";
|
|
2921
2921
|
init_evidence();
|
|
2922
2922
|
init_model();
|
|
2923
|
-
executingVersion = () => true ? "0.
|
|
2923
|
+
executingVersion = () => true ? "0.16.1" : "development";
|
|
2924
2924
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
2925
2925
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
2926
2926
|
}
|
|
@@ -3124,7 +3124,13 @@ function managedBlock(text2, start, end, body) {
|
|
|
3124
3124
|
async function applyEdit(root, edit) {
|
|
3125
3125
|
const current = await readText(root, edit.path);
|
|
3126
3126
|
if (current === edit.after) return false;
|
|
3127
|
-
if (current !== edit.before) throw new Error("
|
|
3127
|
+
if (current !== edit.before) throw new Error("Integration input changed during update: " + edit.path + ". Rerun the requested command to resume.");
|
|
3128
|
+
if (edit.after === null) {
|
|
3129
|
+
const file2 = await storePath(root, edit.path);
|
|
3130
|
+
if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
|
|
3131
|
+
await fs14.unlink(file2);
|
|
3132
|
+
return true;
|
|
3133
|
+
}
|
|
3128
3134
|
const file = await storePath(root, edit.path, true);
|
|
3129
3135
|
const temporary = path18.join(path18.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
3130
3136
|
try {
|
|
@@ -3136,7 +3142,7 @@ async function applyEdit(root, edit) {
|
|
|
3136
3142
|
} finally {
|
|
3137
3143
|
await handle.close();
|
|
3138
3144
|
}
|
|
3139
|
-
if (await readText(root, edit.path) !== edit.before) throw new Error("
|
|
3145
|
+
if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
|
|
3140
3146
|
await fs14.rename(temporary, file);
|
|
3141
3147
|
return true;
|
|
3142
3148
|
} finally {
|
|
@@ -3151,27 +3157,100 @@ var init_files2 = __esm({
|
|
|
3151
3157
|
}
|
|
3152
3158
|
});
|
|
3153
3159
|
|
|
3154
|
-
// src/
|
|
3160
|
+
// src/setup/ownership.ts
|
|
3161
|
+
var ownership_exports = {};
|
|
3162
|
+
__export(ownership_exports, {
|
|
3163
|
+
BLOCKS: () => BLOCKS,
|
|
3164
|
+
OWNERSHIP_PATH: () => OWNERSHIP_PATH,
|
|
3165
|
+
blockSpan: () => blockSpan,
|
|
3166
|
+
completedHookOwnership: () => completedHookOwnership,
|
|
3167
|
+
ownershipEdit: () => ownershipEdit,
|
|
3168
|
+
ownershipSchema: () => ownershipSchema
|
|
3169
|
+
});
|
|
3155
3170
|
import { z as z10 } from "zod";
|
|
3171
|
+
import { parse } from "smol-toml";
|
|
3172
|
+
function blockSpan(text2, start, end) {
|
|
3173
|
+
const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
|
|
3174
|
+
if (!starts && !ends) return null;
|
|
3175
|
+
const from = text2.indexOf(start), to = text2.indexOf(end) + end.length;
|
|
3176
|
+
if (starts !== 1 || ends !== 1 || to <= from) throw new Error("Ambiguous Mason markers; inspect the marked block manually.");
|
|
3177
|
+
return { from, to, text: text2.slice(from, to) };
|
|
3178
|
+
}
|
|
3179
|
+
async function ownershipEdit(root, edits, hookCommands = {}) {
|
|
3180
|
+
const before = await readText(root, OWNERSHIP_PATH);
|
|
3181
|
+
const record = before === null ? { version: 1, files: {} } : ownershipSchema.parse(JSON.parse(before));
|
|
3182
|
+
for (const edit of edits) {
|
|
3183
|
+
const previous = record.files[edit.path];
|
|
3184
|
+
const entry = {
|
|
3185
|
+
created: edit.before === null || (previous?.created ?? false),
|
|
3186
|
+
initialHash: edit.before === null || previous?.initialHash === hash(edit.before) ? hash(edit.after) : previous?.initialHash ?? hash(edit.after),
|
|
3187
|
+
blocks: Object.fromEntries(BLOCKS.flatMap(([start, end]) => {
|
|
3188
|
+
const block = blockSpan(edit.after, start, end);
|
|
3189
|
+
return block ? [[start, hash(block.text)]] : [];
|
|
3190
|
+
}))
|
|
3191
|
+
};
|
|
3192
|
+
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;
|
|
3193
|
+
const hooks = edit.path === ".codex/hooks.json" || edit.path === ".claude/settings.json" ? JSON.parse(edit.after).hooks : void 0;
|
|
3194
|
+
record.files[edit.path] = {
|
|
3195
|
+
...entry,
|
|
3196
|
+
...mcp === void 0 ? {} : { mcpHash: hash(mcp) },
|
|
3197
|
+
...hookCommands[edit.path] ? { hookCommands: [.../* @__PURE__ */ new Set([...previous?.hookCommands ?? [], hookCommands[edit.path]])] } : {},
|
|
3198
|
+
...hooks === void 0 ? {} : { hooks: Object.fromEntries(Object.entries(hooks).map(([event, groups]) => [event, groups.flatMap((group) => group.hooks.map(hash))])) }
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3201
|
+
return { path: OWNERSHIP_PATH, before, after: JSON.stringify(record, null, 2) + "\n" };
|
|
3202
|
+
}
|
|
3203
|
+
async function completedHookOwnership(root, file, command) {
|
|
3204
|
+
const before = await readText(root, OWNERSHIP_PATH);
|
|
3205
|
+
const record = ownershipSchema.parse(JSON.parse(before ?? "null"));
|
|
3206
|
+
record.files[file].hookCommands = [command];
|
|
3207
|
+
return { path: OWNERSHIP_PATH, before, after: JSON.stringify(record, null, 2) + "\n" };
|
|
3208
|
+
}
|
|
3209
|
+
var OWNERSHIP_PATH, BLOCKS, fileSchema, ownershipSchema;
|
|
3210
|
+
var init_ownership = __esm({
|
|
3211
|
+
"src/setup/ownership.ts"() {
|
|
3212
|
+
"use strict";
|
|
3213
|
+
init_evidence();
|
|
3214
|
+
init_files2();
|
|
3215
|
+
OWNERSHIP_PATH = ".mason/local/integration.json";
|
|
3216
|
+
BLOCKS = [
|
|
3217
|
+
["<!-- mason:start -->", "<!-- mason:end -->"],
|
|
3218
|
+
["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
|
|
3219
|
+
["# mason:mcp:start", "# mason:mcp:end"]
|
|
3220
|
+
];
|
|
3221
|
+
fileSchema = z10.object({
|
|
3222
|
+
created: z10.boolean(),
|
|
3223
|
+
initialHash: z10.string(),
|
|
3224
|
+
blocks: z10.record(z10.string()),
|
|
3225
|
+
mcpHash: z10.string().optional(),
|
|
3226
|
+
hookCommands: z10.array(z10.string()).optional(),
|
|
3227
|
+
hooks: z10.record(z10.array(z10.string())).optional()
|
|
3228
|
+
});
|
|
3229
|
+
ownershipSchema = z10.object({ version: z10.literal(1), files: z10.record(fileSchema) });
|
|
3230
|
+
}
|
|
3231
|
+
});
|
|
3232
|
+
|
|
3233
|
+
// src/mcp/init.ts
|
|
3234
|
+
import { z as z11 } from "zod";
|
|
3156
3235
|
var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_PLAYBOOK, MAP_PLAYBOOK;
|
|
3157
3236
|
var init_init = __esm({
|
|
3158
3237
|
"src/mcp/init.ts"() {
|
|
3159
3238
|
"use strict";
|
|
3160
3239
|
init_storage();
|
|
3161
3240
|
init_files2();
|
|
3162
|
-
markerSchema =
|
|
3163
|
-
version:
|
|
3164
|
-
initializedAt:
|
|
3165
|
-
features:
|
|
3241
|
+
markerSchema = z11.object({
|
|
3242
|
+
version: z11.literal(1),
|
|
3243
|
+
initializedAt: z11.string(),
|
|
3244
|
+
features: z11.object({ confluence: z11.boolean().optional() }).passthrough().optional()
|
|
3166
3245
|
}).passthrough();
|
|
3167
3246
|
CLAUDE_MD_SECTION = `<!-- mason:start -->
|
|
3168
3247
|
## Mason project knowledge
|
|
3169
3248
|
|
|
3170
3249
|
Mason provides recorded decisions and file impact over MCP. A concept map is optional.
|
|
3171
3250
|
|
|
3172
|
-
- Task, bug,
|
|
3251
|
+
- Task, bug, change request, or investigation of why the project works this way \u2192 \`get_context\` with the task text and known files: matching decisions, related tests, impact, and any available map entries.
|
|
3173
3252
|
- Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
|
|
3174
|
-
-
|
|
3253
|
+
- Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` before finishing. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
|
|
3175
3254
|
- Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, and failed verification means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
|
|
3176
3255
|
- Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
|
|
3177
3256
|
- For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
|
|
@@ -3268,8 +3347,8 @@ __export(config_exports, {
|
|
|
3268
3347
|
mcpPath: () => mcpPath
|
|
3269
3348
|
});
|
|
3270
3349
|
import { isDeepStrictEqual } from "util";
|
|
3271
|
-
import { parse, stringify } from "smol-toml";
|
|
3272
|
-
import { z as
|
|
3350
|
+
import { parse as parse2, stringify } from "smol-toml";
|
|
3351
|
+
import { z as z12 } from "zod";
|
|
3273
3352
|
async function instructionEdits(root, host) {
|
|
3274
3353
|
const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
|
|
3275
3354
|
const existing = found.find((f) => f.text !== null);
|
|
@@ -3310,7 +3389,7 @@ async function mcpEdit(root, host) {
|
|
|
3310
3389
|
const servers2 = object.parse(config2.mcpServers ?? {});
|
|
3311
3390
|
return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host) } }, null, 2) + "\n" };
|
|
3312
3391
|
}
|
|
3313
|
-
const config =
|
|
3392
|
+
const config = parse2(before ?? "");
|
|
3314
3393
|
const managed = (before ?? "").includes(TOML_START);
|
|
3315
3394
|
const servers = config.mcp_servers;
|
|
3316
3395
|
const desired = managedMcp(servers?.mason, host);
|
|
@@ -3322,7 +3401,7 @@ async function mcpEdit(root, host) {
|
|
|
3322
3401
|
for (const span of spans.reverse()) base = base.slice(0, span.start) + base.slice(span.end);
|
|
3323
3402
|
}
|
|
3324
3403
|
const after = managedBlock(base, TOML_START, TOML_END, stringify({ mcp_servers: { mason: desired } }).trimEnd());
|
|
3325
|
-
const parsed =
|
|
3404
|
+
const parsed = parse2(after);
|
|
3326
3405
|
const expected = { ...config, mcp_servers: { ...servers ?? {}, mason: desired } };
|
|
3327
3406
|
if (!isDeepStrictEqual(parsed, expected)) throw new Error("Could not safely configure the Mason MCP entry without changing other settings.");
|
|
3328
3407
|
return { path: file, before, after };
|
|
@@ -3360,7 +3439,7 @@ async function ancillaryEdits(root) {
|
|
|
3360
3439
|
}
|
|
3361
3440
|
async function inspectHostConfig(root, host, plannedMcp) {
|
|
3362
3441
|
const text2 = plannedMcp ?? await readText(root, mcpPath(host));
|
|
3363
|
-
const config = host === "codex" ?
|
|
3442
|
+
const config = host === "codex" ? parse2(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
|
|
3364
3443
|
const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
|
|
3365
3444
|
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
|
|
3366
3445
|
const expected = hookConfig(host, hookCommand(host));
|
|
@@ -3385,7 +3464,7 @@ var init_config = __esm({
|
|
|
3385
3464
|
init_install();
|
|
3386
3465
|
init_files2();
|
|
3387
3466
|
init_launcher();
|
|
3388
|
-
object =
|
|
3467
|
+
object = z12.record(z12.unknown());
|
|
3389
3468
|
mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
3390
3469
|
TOML_START = "# mason:mcp:start";
|
|
3391
3470
|
TOML_END = "# mason:mcp:end";
|
|
@@ -3402,24 +3481,28 @@ __export(install_exports, {
|
|
|
3402
3481
|
configPath: () => configPath,
|
|
3403
3482
|
installAutomation: () => installAutomation,
|
|
3404
3483
|
installedAutomation: () => installedAutomation,
|
|
3405
|
-
planAutomationInstall: () => planAutomationInstall
|
|
3484
|
+
planAutomationInstall: () => planAutomationInstall,
|
|
3485
|
+
recordSchema: () => recordSchema
|
|
3406
3486
|
});
|
|
3407
|
-
import { z as
|
|
3487
|
+
import { z as z13 } from "zod";
|
|
3408
3488
|
async function installationRecord(root) {
|
|
3409
3489
|
return recordSchema.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
|
|
3410
3490
|
}
|
|
3411
3491
|
async function installAutomation(dir, host, command) {
|
|
3412
3492
|
const ws = await workspace(dir);
|
|
3413
|
-
return withLock(ws.root, ".mason/reports/
|
|
3493
|
+
return withLock(ws.root, ".mason/reports/setup-lock", () => installLocked(ws.root, host, command));
|
|
3414
3494
|
}
|
|
3415
3495
|
async function planAutomationInstall(root, host, command) {
|
|
3416
3496
|
const file = configPath(host);
|
|
3417
|
-
const
|
|
3497
|
+
const before = await readText(root, file);
|
|
3498
|
+
const existing = automationConfigSchema.parse(JSON.parse(before ?? "{}"));
|
|
3418
3499
|
const record = await installationRecord(root);
|
|
3419
3500
|
const desired = hookConfig(host, command);
|
|
3420
3501
|
const newCommand = desired.hooks.SessionStart[0].hooks[0].command;
|
|
3421
3502
|
const previous = record.hosts[host]?.command;
|
|
3422
|
-
const
|
|
3503
|
+
const ownershipText = await readText(root, OWNERSHIP_PATH);
|
|
3504
|
+
const pending = ownershipText === null ? [] : ownershipSchema.parse(JSON.parse(ownershipText)).files[file]?.hookCommands ?? [];
|
|
3505
|
+
const owned = /* @__PURE__ */ new Set([previous, ...pending, newCommand]);
|
|
3423
3506
|
const hooks = existing.hooks ?? {};
|
|
3424
3507
|
for (const event of HOOK_EVENTS) {
|
|
3425
3508
|
hooks[event] = (hooks[event] ?? []).map((group) => ({
|
|
@@ -3429,15 +3512,20 @@ async function planAutomationInstall(root, host, command) {
|
|
|
3429
3512
|
hooks[event].push(...desired.hooks[event]);
|
|
3430
3513
|
}
|
|
3431
3514
|
record.hosts[host] = { command: newCommand };
|
|
3432
|
-
return { file, config: { ...existing, hooks }, record, newCommand };
|
|
3515
|
+
return { file, before, config: { ...existing, hooks }, record, newCommand };
|
|
3433
3516
|
}
|
|
3434
3517
|
async function installLocked(root, host, command) {
|
|
3435
|
-
const { file, config, record, newCommand } = await planAutomationInstall(root, host, command);
|
|
3518
|
+
const { file, before, config, record, newCommand } = await planAutomationInstall(root, host, command);
|
|
3436
3519
|
const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3437
3520
|
const { applyEdit: applyEdit2 } = await Promise.resolve().then(() => (init_files2(), files_exports));
|
|
3438
|
-
|
|
3439
|
-
|
|
3521
|
+
const { ownershipEdit: ownershipEdit2, completedHookOwnership: completedHookOwnership2 } = await Promise.resolve().then(() => (init_ownership(), ownership_exports));
|
|
3522
|
+
const edit = { path: file, before, after: JSON.stringify(config, null, 2) + "\n" };
|
|
3523
|
+
const ancillary = await ancillaryEdits2(root);
|
|
3524
|
+
await applyEdit2(root, await ownershipEdit2(root, [edit], { [file]: newCommand }));
|
|
3525
|
+
for (const edit2 of ancillary) await applyEdit2(root, edit2);
|
|
3526
|
+
await applyEdit2(root, edit);
|
|
3440
3527
|
await writeStoreJson(root, AUTOMATION_PATH, record);
|
|
3528
|
+
await applyEdit2(root, await completedHookOwnership2(root, file, newCommand));
|
|
3441
3529
|
return {
|
|
3442
3530
|
version: 1,
|
|
3443
3531
|
host,
|
|
@@ -3475,14 +3563,248 @@ var init_install = __esm({
|
|
|
3475
3563
|
init_evidence();
|
|
3476
3564
|
init_adapters();
|
|
3477
3565
|
init_store();
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3566
|
+
init_files2();
|
|
3567
|
+
init_ownership();
|
|
3568
|
+
groupSchema = z13.object({ hooks: z13.array(z13.object({ type: z13.string(), command: z13.string().optional() }).passthrough()) }).passthrough();
|
|
3569
|
+
automationConfigSchema = z13.object({ hooks: z13.record(z13.array(groupSchema)).optional() }).passthrough();
|
|
3570
|
+
recordSchema = z13.object({ version: z13.literal(1), hosts: z13.record(z13.object({ command: z13.string() })) });
|
|
3481
3571
|
AUTOMATION_PATH = ".mason/local/automation.json";
|
|
3482
3572
|
configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
|
|
3483
3573
|
}
|
|
3484
3574
|
});
|
|
3485
3575
|
|
|
3576
|
+
// src/setup/teardown.ts
|
|
3577
|
+
var teardown_exports = {};
|
|
3578
|
+
__export(teardown_exports, {
|
|
3579
|
+
summarizeTeardown: () => summarizeTeardown,
|
|
3580
|
+
teardownProject: () => teardownProject
|
|
3581
|
+
});
|
|
3582
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
3583
|
+
import { parse as parse3, stringify as stringify2 } from "smol-toml";
|
|
3584
|
+
import fg5 from "fast-glob";
|
|
3585
|
+
import { z as z14 } from "zod";
|
|
3586
|
+
async function planTeardown(root, selected) {
|
|
3587
|
+
const edits = [], diagnostics = [];
|
|
3588
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
3589
|
+
const read = async (file) => {
|
|
3590
|
+
if (!inputs.has(file)) inputs.set(file, await readText(root, file));
|
|
3591
|
+
return inputs.get(file);
|
|
3592
|
+
};
|
|
3593
|
+
const attempt = async (file, action) => {
|
|
3594
|
+
try {
|
|
3595
|
+
await action();
|
|
3596
|
+
} catch (error) {
|
|
3597
|
+
diagnostics.push({ path: file, message: error instanceof Error ? error.message : String(error) });
|
|
3598
|
+
}
|
|
3599
|
+
};
|
|
3600
|
+
const ownershipBefore = await read(OWNERSHIP_PATH);
|
|
3601
|
+
const ownership = ownershipBefore === null ? { version: 1, files: {} } : ownershipSchema.parse(JSON.parse(ownershipBefore));
|
|
3602
|
+
const setupBefore = await read(SETUP_PATH);
|
|
3603
|
+
const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
|
|
3604
|
+
const automationBefore = await read(AUTOMATION_PATH);
|
|
3605
|
+
const automation = automationBefore === null ? null : recordSchema.parse(JSON.parse(automationBefore));
|
|
3606
|
+
const commands = (host) => new Set([
|
|
3607
|
+
automation?.hosts[host]?.command,
|
|
3608
|
+
...ownership.files[configPath(host)]?.hookCommands ?? [],
|
|
3609
|
+
hookConfig(host, hookCommand(host)).hooks.SessionStart[0].hooks[0].command,
|
|
3610
|
+
hookConfig(host).hooks.SessionStart[0].hooks[0].command
|
|
3611
|
+
].filter((command) => !!command));
|
|
3612
|
+
const possibleHook = (command, host) => commands(host).has(command) || /\bmason(?:-auto|-hook)?\b/.test(command);
|
|
3613
|
+
const add = (file, before, after, empty) => {
|
|
3614
|
+
edits.push({ path: file, before, after: ownership.files[file]?.created && (empty || ownership.files[file].initialHash === hash(before)) ? null : after });
|
|
3615
|
+
};
|
|
3616
|
+
let remainingHost = hosts.some((host) => !selected.includes(host) && (setup?.hosts[host] || automation?.hosts[host] || ownership.files[mcpPath(host)] || ownership.files[configPath(host)]));
|
|
3617
|
+
for (const host of hosts.filter((host2) => !selected.includes(host2))) {
|
|
3618
|
+
await attempt(mcpPath(host), async () => {
|
|
3619
|
+
const text2 = await read(mcpPath(host));
|
|
3620
|
+
const config = host === "codex" ? parse3(text2 ?? "") : object2.parse(JSON.parse(text2 ?? "{}"));
|
|
3621
|
+
const servers = object2.parse((host === "codex" ? config.mcp_servers : config.mcpServers) ?? {});
|
|
3622
|
+
remainingHost ||= servers.mason !== void 0;
|
|
3623
|
+
});
|
|
3624
|
+
await attempt(configPath(host), async () => {
|
|
3625
|
+
const config = automationConfigSchema.parse(JSON.parse(await read(configPath(host)) ?? "{}"));
|
|
3626
|
+
remainingHost ||= Object.values(config.hooks ?? {}).some((groups) => groups.some((group) => group.hooks.some((handler) => handler.command && possibleHook(handler.command, host))));
|
|
3627
|
+
});
|
|
3628
|
+
}
|
|
3629
|
+
for (const host of selected) {
|
|
3630
|
+
const mcpFile = mcpPath(host);
|
|
3631
|
+
await attempt(mcpFile, async () => {
|
|
3632
|
+
const before = await read(mcpFile);
|
|
3633
|
+
if (before === null) return;
|
|
3634
|
+
const config = host === "codex" ? parse3(before) : object2.parse(JSON.parse(before));
|
|
3635
|
+
const key = host === "codex" ? "mcp_servers" : "mcpServers";
|
|
3636
|
+
const servers = object2.parse(config[key] ?? {});
|
|
3637
|
+
if (servers.mason === void 0) {
|
|
3638
|
+
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.");
|
|
3639
|
+
return;
|
|
3640
|
+
}
|
|
3641
|
+
const expected = ownership.files[mcpFile]?.mcpHash ?? hash(mcpCommand(host));
|
|
3642
|
+
if (hash(servers.mason) !== expected) throw new Error("The Mason MCP entry was edited or its ownership is uncertain; retained for manual cleanup.");
|
|
3643
|
+
delete servers.mason;
|
|
3644
|
+
const desired = { ...config, [key]: servers };
|
|
3645
|
+
if (!Object.keys(servers).length) delete desired[key];
|
|
3646
|
+
if (host === "claude") {
|
|
3647
|
+
add(mcpFile, before, json(desired), !Object.keys(desired).length);
|
|
3648
|
+
return;
|
|
3649
|
+
}
|
|
3650
|
+
const [start, end] = BLOCKS[2], block = blockSpan(before, start, end);
|
|
3651
|
+
if (!block) throw new Error("Mason MCP markers are missing; retained for manual cleanup.");
|
|
3652
|
+
const expectedBlock = ownership.files[mcpFile]?.blocks[start];
|
|
3653
|
+
const canonicalBlock = managedBlock("", start, end, stringify2({ mcp_servers: { mason: mcpCommand(host) } }).trimEnd()).trimEnd();
|
|
3654
|
+
if (expectedBlock ? hash(block.text) !== expectedBlock : block.text.replace(/\r\n/g, "\n") !== canonicalBlock) {
|
|
3655
|
+
throw new Error("The Mason MCP block was edited; retained for manual cleanup.");
|
|
3656
|
+
}
|
|
3657
|
+
const after = before.slice(0, block.from) + before.slice(block.to).replace(/^\r?\n/, "");
|
|
3658
|
+
const parsed = parse3(after);
|
|
3659
|
+
if (!Object.keys(servers).length && parsed[key] !== void 0) desired[key] = {};
|
|
3660
|
+
if (!isDeepStrictEqual2(parsed, desired)) throw new Error("Removing the marked block would change other TOML settings; retained for manual cleanup.");
|
|
3661
|
+
add(mcpFile, before, after, !after.trim());
|
|
3662
|
+
});
|
|
3663
|
+
const hookFile = configPath(host);
|
|
3664
|
+
await attempt(hookFile, async () => {
|
|
3665
|
+
const before = await read(hookFile);
|
|
3666
|
+
if (before === null) return;
|
|
3667
|
+
const config = automationConfigSchema.parse(JSON.parse(before));
|
|
3668
|
+
let changed = false;
|
|
3669
|
+
for (const [event, groups] of Object.entries(config.hooks ?? {})) {
|
|
3670
|
+
const known = ownership.files[hookFile]?.hooks?.[event];
|
|
3671
|
+
const handlers = groups.flatMap((group) => group.hooks);
|
|
3672
|
+
if (known && !handlers.some((handler) => handler.type === "command" && handler.command && commands(host).has(handler.command)) && handlers.some((handler) => !known.includes(hash(handler)))) {
|
|
3673
|
+
diagnostics.push({ path: hookFile, message: "A recorded Mason hook is missing beside a new or edited handler (" + event + "); inspect ownership manually." });
|
|
3674
|
+
}
|
|
3675
|
+
const kept = groups.flatMap((group) => {
|
|
3676
|
+
const handlers2 = group.hooks.filter((handler) => {
|
|
3677
|
+
if (handler.type === "command" && handler.command && commands(host).has(handler.command)) {
|
|
3678
|
+
changed = true;
|
|
3679
|
+
return false;
|
|
3680
|
+
}
|
|
3681
|
+
if (handler.command && possibleHook(handler.command, host)) diagnostics.push({
|
|
3682
|
+
path: hookFile,
|
|
3683
|
+
message: "An unrecognized Mason hook was retained for manual cleanup (" + event + ")."
|
|
3684
|
+
});
|
|
3685
|
+
return true;
|
|
3686
|
+
});
|
|
3687
|
+
return handlers2.length === group.hooks.length ? [group] : handlers2.length ? [{ ...group, hooks: handlers2 }] : [];
|
|
3688
|
+
});
|
|
3689
|
+
if (kept.length || !groups.length) config.hooks[event] = kept;
|
|
3690
|
+
else delete config.hooks[event];
|
|
3691
|
+
}
|
|
3692
|
+
if (!changed) return;
|
|
3693
|
+
if (!Object.keys(config.hooks ?? {}).length) delete config.hooks;
|
|
3694
|
+
add(hookFile, before, json(config), !Object.keys(config).length);
|
|
3695
|
+
});
|
|
3696
|
+
}
|
|
3697
|
+
const instructionEditStart = edits.length;
|
|
3698
|
+
if (!remainingHost && !diagnostics.length) for (const file of DOC_CANDIDATES) await attempt(file, async () => {
|
|
3699
|
+
const before = await read(file);
|
|
3700
|
+
if (before === null) return;
|
|
3701
|
+
let after = before;
|
|
3702
|
+
for (const [start, end] of BLOCKS.slice(0, 2)) {
|
|
3703
|
+
const block = blockSpan(after, start, end);
|
|
3704
|
+
if (!block) continue;
|
|
3705
|
+
const pointer = "Mason project knowledge and shared project instructions:\n@" + (file.startsWith(".claude/") ? "../AGENTS.md" : "AGENTS.md");
|
|
3706
|
+
const canonical = [CLAUDE_MD_SECTION, managedBlock("", start, end, pointer).trimEnd()];
|
|
3707
|
+
const expected = ownership.files[file]?.blocks[start];
|
|
3708
|
+
if (expected ? hash(block.text) !== expected : !canonical.includes(block.text.replace(/\r\n/g, "\n"))) {
|
|
3709
|
+
throw new Error("The Mason instruction block was edited or is unrecognized; retained for manual cleanup.");
|
|
3710
|
+
}
|
|
3711
|
+
after = after.slice(0, block.from) + after.slice(block.to).replace(/^\r?\n/, "");
|
|
3712
|
+
}
|
|
3713
|
+
if (after !== before) add(file, before, after, !after.trim());
|
|
3714
|
+
});
|
|
3715
|
+
if (diagnostics.length) edits.splice(instructionEditStart);
|
|
3716
|
+
if (!diagnostics.length) {
|
|
3717
|
+
const cleanup = [];
|
|
3718
|
+
const receiptFiles = await fg5(
|
|
3719
|
+
selected.map((host) => ".mason/reports/automation/*/setup-" + host + ".json"),
|
|
3720
|
+
{ cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false }
|
|
3721
|
+
);
|
|
3722
|
+
if (receiptFiles.length > 1e3) throw new Error("Too many setup receipts; inspect local setup history before teardown.");
|
|
3723
|
+
for (const file of receiptFiles.sort()) await attempt(file, async () => {
|
|
3724
|
+
const host = file.endsWith("setup-codex.json") ? "codex" : "claude";
|
|
3725
|
+
await loadSetupReceipt(root, file.slice(0, file.lastIndexOf("/")), host);
|
|
3726
|
+
cleanup.push({ path: file, before: await read(file), after: null });
|
|
3727
|
+
});
|
|
3728
|
+
if (!remainingHost) await attempt(".mason/local/project.json", async () => {
|
|
3729
|
+
const before = await read(".mason/local/project.json");
|
|
3730
|
+
if (before === null) return;
|
|
3731
|
+
z14.object({ version: z14.literal(1), initializedAt: z14.string() }).strict().parse(JSON.parse(before));
|
|
3732
|
+
cleanup.push({ path: ".mason/local/project.json", before, after: null });
|
|
3733
|
+
});
|
|
3734
|
+
if (!diagnostics.length) {
|
|
3735
|
+
for (const host of selected) {
|
|
3736
|
+
if (setup) delete setup.hosts[host];
|
|
3737
|
+
if (automation) delete automation.hosts[host];
|
|
3738
|
+
delete ownership.files[mcpPath(host)];
|
|
3739
|
+
delete ownership.files[configPath(host)];
|
|
3740
|
+
}
|
|
3741
|
+
if (!remainingHost) for (const file of DOC_CANDIDATES) delete ownership.files[file];
|
|
3742
|
+
if (setupBefore !== null) cleanup.push({ path: SETUP_PATH, before: setupBefore, after: Object.keys(setup.hosts).length ? json(setup) : null });
|
|
3743
|
+
if (automationBefore !== null) cleanup.push({ path: AUTOMATION_PATH, before: automationBefore, after: Object.keys(automation.hosts).length ? json(automation) : null });
|
|
3744
|
+
if (ownershipBefore !== null) cleanup.push({ path: OWNERSHIP_PATH, before: ownershipBefore, after: Object.keys(ownership.files).length ? json(ownership) : null });
|
|
3745
|
+
edits.push(...cleanup);
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
return { edits: edits.filter((edit) => edit.before !== edit.after), diagnostics, remainingHost, inputs };
|
|
3749
|
+
}
|
|
3750
|
+
async function teardownProject(dir, options = {}) {
|
|
3751
|
+
const { root } = await workspace(dir);
|
|
3752
|
+
const selected = options.host ? [options.host] : [...hosts];
|
|
3753
|
+
const run = async (apply) => {
|
|
3754
|
+
const plan = await planTeardown(root, selected);
|
|
3755
|
+
if (apply) {
|
|
3756
|
+
for (const [file, before] of plan.inputs) if (await readText(root, file) !== before) throw new Error("Input changed during teardown: " + file + ". Rerun teardown.");
|
|
3757
|
+
for (const edit of plan.edits) await applyEdit(root, edit);
|
|
3758
|
+
}
|
|
3759
|
+
return {
|
|
3760
|
+
version: 1,
|
|
3761
|
+
action: "teardown",
|
|
3762
|
+
status: plan.diagnostics.length ? "incomplete" : "complete",
|
|
3763
|
+
root,
|
|
3764
|
+
hosts: selected,
|
|
3765
|
+
dryRun: !!options.dryRun,
|
|
3766
|
+
changes: plan.edits.map((edit) => ({ path: edit.path, action: edit.after === null ? "delete" : "update" })),
|
|
3767
|
+
diagnostics: plan.diagnostics,
|
|
3768
|
+
sharedInstructionsRetained: plan.remainingHost || !!plan.diagnostics.length,
|
|
3769
|
+
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."
|
|
3770
|
+
};
|
|
3771
|
+
};
|
|
3772
|
+
const preview = await run(false);
|
|
3773
|
+
if (options.dryRun || !preview.changes.length) return preview;
|
|
3774
|
+
return withLock(root, ".mason/reports/setup-lock", () => run(true));
|
|
3775
|
+
}
|
|
3776
|
+
function summarizeTeardown(result) {
|
|
3777
|
+
return [
|
|
3778
|
+
`Mason teardown${result.dryRun ? " preview" : ""}: ${result.status}.`,
|
|
3779
|
+
...result.changes.map((change) => ` ${result.dryRun ? "Would " : ""}${change.action}: ${change.path}`),
|
|
3780
|
+
...result.diagnostics.map((item) => ` Retained ${item.path}: ${item.message}`),
|
|
3781
|
+
...!result.changes.length ? ["No changes."] : [],
|
|
3782
|
+
"Shared knowledge, repair evidence, ignore rules and the installed Mason command are retained.",
|
|
3783
|
+
...result.sharedInstructionsRetained ? ["Shared instruction cleanup is deferred while another host or incomplete removal needs inspection."] : [],
|
|
3784
|
+
...result.dryRun ? ["Preview only; no files were changed."] : [result.next]
|
|
3785
|
+
].join("\n");
|
|
3786
|
+
}
|
|
3787
|
+
var hosts, object2, json;
|
|
3788
|
+
var init_teardown = __esm({
|
|
3789
|
+
"src/setup/teardown.ts"() {
|
|
3790
|
+
"use strict";
|
|
3791
|
+
init_evidence();
|
|
3792
|
+
init_store();
|
|
3793
|
+
init_install();
|
|
3794
|
+
init_adapters();
|
|
3795
|
+
init_docs();
|
|
3796
|
+
init_init();
|
|
3797
|
+
init_files2();
|
|
3798
|
+
init_config();
|
|
3799
|
+
init_launcher();
|
|
3800
|
+
init_model();
|
|
3801
|
+
init_ownership();
|
|
3802
|
+
hosts = ["codex", "claude"];
|
|
3803
|
+
object2 = z14.record(z14.unknown());
|
|
3804
|
+
json = (value) => JSON.stringify(value, null, 2) + "\n";
|
|
3805
|
+
}
|
|
3806
|
+
});
|
|
3807
|
+
|
|
3486
3808
|
// src/review/cochange.ts
|
|
3487
3809
|
import { execFile as execFile11 } from "child_process";
|
|
3488
3810
|
import { promisify as promisify11 } from "util";
|
|
@@ -3605,7 +3927,7 @@ var init_types2 = __esm({
|
|
|
3605
3927
|
});
|
|
3606
3928
|
|
|
3607
3929
|
// src/review/evidence/vitest.ts
|
|
3608
|
-
import { z as
|
|
3930
|
+
import { z as z15 } from "zod";
|
|
3609
3931
|
function parseVitest(raw, sourceRoot) {
|
|
3610
3932
|
const report = schema.parse(raw);
|
|
3611
3933
|
const findings = [], diagnostics = [];
|
|
@@ -3654,24 +3976,24 @@ var init_vitest = __esm({
|
|
|
3654
3976
|
"use strict";
|
|
3655
3977
|
init_paths2();
|
|
3656
3978
|
init_types2();
|
|
3657
|
-
count2 =
|
|
3658
|
-
schema =
|
|
3659
|
-
success:
|
|
3979
|
+
count2 = z15.number().int().nonnegative();
|
|
3980
|
+
schema = z15.object({
|
|
3981
|
+
success: z15.boolean(),
|
|
3660
3982
|
numTotalTests: count2,
|
|
3661
3983
|
numPassedTests: count2,
|
|
3662
3984
|
numFailedTests: count2,
|
|
3663
3985
|
numPendingTests: count2,
|
|
3664
3986
|
numTodoTests: count2,
|
|
3665
3987
|
numFailedTestSuites: count2,
|
|
3666
|
-
testResults:
|
|
3667
|
-
name:
|
|
3668
|
-
status:
|
|
3669
|
-
message:
|
|
3670
|
-
assertionResults:
|
|
3671
|
-
fullName:
|
|
3672
|
-
status:
|
|
3673
|
-
failureMessages:
|
|
3674
|
-
location:
|
|
3988
|
+
testResults: z15.array(z15.object({
|
|
3989
|
+
name: z15.string().min(1),
|
|
3990
|
+
status: z15.enum(["passed", "failed"]),
|
|
3991
|
+
message: z15.string().optional(),
|
|
3992
|
+
assertionResults: z15.array(z15.object({
|
|
3993
|
+
fullName: z15.string(),
|
|
3994
|
+
status: z15.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
|
|
3995
|
+
failureMessages: z15.array(z15.string()).nullable().optional(),
|
|
3996
|
+
location: z15.object({ line: count2, column: count2 }).nullable().optional()
|
|
3675
3997
|
}))
|
|
3676
3998
|
}))
|
|
3677
3999
|
});
|
|
@@ -3679,7 +4001,7 @@ var init_vitest = __esm({
|
|
|
3679
4001
|
});
|
|
3680
4002
|
|
|
3681
4003
|
// src/review/evidence/sarif.ts
|
|
3682
|
-
import { z as
|
|
4004
|
+
import { z as z16 } from "zod";
|
|
3683
4005
|
function parseSarif(raw, sourceRoot) {
|
|
3684
4006
|
const report = schema2.parse(raw), findings = [], diagnostics = [];
|
|
3685
4007
|
let active = 0, suppressed = 0, absent = 0, unresolved = 0;
|
|
@@ -3770,29 +4092,29 @@ var init_sarif = __esm({
|
|
|
3770
4092
|
"use strict";
|
|
3771
4093
|
init_paths2();
|
|
3772
4094
|
init_types2();
|
|
3773
|
-
index =
|
|
3774
|
-
artifact =
|
|
3775
|
-
message =
|
|
3776
|
-
level =
|
|
3777
|
-
location =
|
|
3778
|
-
schema2 =
|
|
3779
|
-
version:
|
|
3780
|
-
runs:
|
|
3781
|
-
tool:
|
|
3782
|
-
invocations:
|
|
3783
|
-
versionControlProvenance:
|
|
3784
|
-
originalUriBaseIds:
|
|
3785
|
-
artifacts:
|
|
3786
|
-
results:
|
|
3787
|
-
ruleId:
|
|
4095
|
+
index = z16.number().int().nonnegative();
|
|
4096
|
+
artifact = z16.object({ uri: z16.string().optional(), uriBaseId: z16.string().optional(), index: index.optional() });
|
|
4097
|
+
message = z16.object({ text: z16.string().optional(), markdown: z16.string().optional(), id: z16.string().optional(), arguments: z16.array(z16.string()).optional() });
|
|
4098
|
+
level = z16.enum(["error", "warning", "note", "none"]);
|
|
4099
|
+
location = z16.object({ physicalLocation: z16.object({ artifactLocation: artifact.optional(), region: z16.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
|
|
4100
|
+
schema2 = z16.object({
|
|
4101
|
+
version: z16.literal("2.1.0"),
|
|
4102
|
+
runs: z16.array(z16.object({
|
|
4103
|
+
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() }) }),
|
|
4104
|
+
invocations: z16.array(z16.object({ executionSuccessful: z16.boolean(), commandLine: z16.string().optional(), toolExecutionNotifications: z16.array(z16.object({ level: level.optional(), message })).optional() })).optional(),
|
|
4105
|
+
versionControlProvenance: z16.array(z16.object({ revisionId: z16.string().optional() })).optional(),
|
|
4106
|
+
originalUriBaseIds: z16.record(artifact).optional(),
|
|
4107
|
+
artifacts: z16.array(z16.object({ location: artifact.optional() })).optional(),
|
|
4108
|
+
results: z16.array(z16.object({
|
|
4109
|
+
ruleId: z16.string().optional(),
|
|
3788
4110
|
ruleIndex: index.optional(),
|
|
3789
4111
|
message,
|
|
3790
4112
|
level: level.optional(),
|
|
3791
|
-
kind:
|
|
3792
|
-
baselineState:
|
|
3793
|
-
suppressions:
|
|
3794
|
-
locations:
|
|
3795
|
-
relatedLocations:
|
|
4113
|
+
kind: z16.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
|
|
4114
|
+
baselineState: z16.enum(["new", "unchanged", "updated", "absent"]).optional(),
|
|
4115
|
+
suppressions: z16.array(z16.object({ kind: z16.enum(["inSource", "external"]), status: z16.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
|
|
4116
|
+
locations: z16.array(location).optional(),
|
|
4117
|
+
relatedLocations: z16.array(location).optional()
|
|
3796
4118
|
})).optional()
|
|
3797
4119
|
}))
|
|
3798
4120
|
});
|
|
@@ -3801,7 +4123,7 @@ var init_sarif = __esm({
|
|
|
3801
4123
|
|
|
3802
4124
|
// src/review/evidence.ts
|
|
3803
4125
|
import path19 from "path";
|
|
3804
|
-
import { z as
|
|
4126
|
+
import { z as z17 } from "zod";
|
|
3805
4127
|
function relativeArtifact(root, file) {
|
|
3806
4128
|
const relative = path19.isAbsolute(file) ? path19.relative(root, file) : file;
|
|
3807
4129
|
const normalized = normalizeRepoPath(relative);
|
|
@@ -3842,7 +4164,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
3842
4164
|
let manifest, raw;
|
|
3843
4165
|
try {
|
|
3844
4166
|
manifest = relativeArtifact(root, supplied);
|
|
3845
|
-
raw =
|
|
4167
|
+
raw = z17.object({ version: z17.literal(1), checks: z17.array(z17.unknown()) }).parse(await readStoreJson(root, manifest));
|
|
3846
4168
|
} catch (error) {
|
|
3847
4169
|
output.diagnostics.push(`${supplied}: ${String(error)}`);
|
|
3848
4170
|
continue;
|
|
@@ -3995,19 +4317,19 @@ var init_evidence2 = __esm({
|
|
|
3995
4317
|
init_vitest();
|
|
3996
4318
|
init_sarif();
|
|
3997
4319
|
init_types2();
|
|
3998
|
-
checkSchema2 =
|
|
3999
|
-
id:
|
|
4000
|
-
kind:
|
|
4001
|
-
tool:
|
|
4002
|
-
command:
|
|
4003
|
-
commit:
|
|
4004
|
-
workingTreeClean:
|
|
4005
|
-
source:
|
|
4006
|
-
sourceRoot:
|
|
4007
|
-
status:
|
|
4008
|
-
reason:
|
|
4009
|
-
exitCode:
|
|
4010
|
-
report:
|
|
4320
|
+
checkSchema2 = z17.object({
|
|
4321
|
+
id: z17.string().min(1).max(100),
|
|
4322
|
+
kind: z17.enum(["tests", "static-analysis", "security", "complexity", "duplication"]),
|
|
4323
|
+
tool: z17.string().min(1).max(200),
|
|
4324
|
+
command: z17.string().min(1).max(2e3),
|
|
4325
|
+
commit: z17.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
|
|
4326
|
+
workingTreeClean: z17.boolean().optional(),
|
|
4327
|
+
source: z17.string().max(2e3).optional(),
|
|
4328
|
+
sourceRoot: z17.string().min(1).max(4e3).optional(),
|
|
4329
|
+
status: z17.enum(["completed", "skipped", "unavailable"]).default("completed"),
|
|
4330
|
+
reason: z17.string().min(1).max(2e3).optional(),
|
|
4331
|
+
exitCode: z17.number().int().nullable().optional(),
|
|
4332
|
+
report: z17.object({ format: z17.enum(["vitest-json", "sarif"]), path: z17.string().min(1).max(4e3) }).optional()
|
|
4011
4333
|
});
|
|
4012
4334
|
}
|
|
4013
4335
|
});
|
|
@@ -4233,7 +4555,7 @@ __export(status_exports, {
|
|
|
4233
4555
|
setupStatus: () => setupStatus,
|
|
4234
4556
|
summarizeActivation: () => summarizeActivation
|
|
4235
4557
|
});
|
|
4236
|
-
import { isDeepStrictEqual as
|
|
4558
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
4237
4559
|
async function setupStatus(dir) {
|
|
4238
4560
|
const ws = await workspace(dir);
|
|
4239
4561
|
const setup = await loadSetup(ws.root);
|
|
@@ -4250,7 +4572,7 @@ async function setupStatus(dir) {
|
|
|
4250
4572
|
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."
|
|
4251
4573
|
};
|
|
4252
4574
|
}
|
|
4253
|
-
const
|
|
4575
|
+
const hosts2 = {};
|
|
4254
4576
|
const installed = await installedCommand();
|
|
4255
4577
|
const automation = await automationStatus(ws.root);
|
|
4256
4578
|
for (const host of ["codex", "claude"]) {
|
|
@@ -4259,8 +4581,8 @@ async function setupStatus(dir) {
|
|
|
4259
4581
|
const instructions = await instructionEdits(ws.root, host);
|
|
4260
4582
|
const instructionsCurrent = instructions.every((edit) => edit.before === edit.after);
|
|
4261
4583
|
const config = await inspectHostConfig(ws.root, host, void 0);
|
|
4262
|
-
const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint &&
|
|
4263
|
-
const hooks =
|
|
4584
|
+
const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual3({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host));
|
|
4585
|
+
const hooks = isDeepStrictEqual3(config.hooks, hookConfig(host, hookCommand(host)).hooks) && !config.disabled;
|
|
4264
4586
|
const local = await loadSetupReceipt(ws.root, ws.directory, host);
|
|
4265
4587
|
const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
|
|
4266
4588
|
const observation = await readObservation(ws.root, ws.directory, host, entry.revision, installed.version ?? "unavailable");
|
|
@@ -4274,7 +4596,7 @@ async function setupStatus(dir) {
|
|
|
4274
4596
|
if (!complete) pending.push("Review/trust the host configuration, then complete a normal task to observe the full hook lifecycle.");
|
|
4275
4597
|
if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason-auto check and inspect its diagnostics.");
|
|
4276
4598
|
const healthy = installed.available && mcp && hooks && instructionsCurrent && configured && automation.status !== "unavailable";
|
|
4277
|
-
|
|
4599
|
+
hosts2[host] = {
|
|
4278
4600
|
status: healthy ? complete && observation?.contextCalls ? "active" : "pending" : "attention",
|
|
4279
4601
|
runtime: installed.available ? "global (" + installed.version + ")" : "unavailable-on-path",
|
|
4280
4602
|
mcp: mcp ? "configured" : "changed",
|
|
@@ -4286,13 +4608,13 @@ async function setupStatus(dir) {
|
|
|
4286
4608
|
pending
|
|
4287
4609
|
};
|
|
4288
4610
|
}
|
|
4289
|
-
const statuses = Object.values(
|
|
4611
|
+
const statuses = Object.values(hosts2).map((host) => host.status);
|
|
4290
4612
|
const decisions = await loadDecisionStore(ws.root);
|
|
4291
4613
|
return {
|
|
4292
4614
|
version: 1,
|
|
4293
4615
|
status: statuses.length && statuses.every((status) => status === "active") ? "active" : statuses.includes("attention") ? "attention" : "pending",
|
|
4294
4616
|
root: ws.root,
|
|
4295
|
-
hosts,
|
|
4617
|
+
hosts: hosts2,
|
|
4296
4618
|
decisionRecords: decisions.records.length,
|
|
4297
4619
|
diagnostics: decisions.diagnostics,
|
|
4298
4620
|
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."
|
|
@@ -4361,6 +4683,8 @@ async function setupProject(dir, options = {}) {
|
|
|
4361
4683
|
const setupBefore = await readText(ws.root, SETUP_PATH);
|
|
4362
4684
|
const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after)).mcp);
|
|
4363
4685
|
const desiredHooks = hookConfig(host, hookCommand(host)).hooks;
|
|
4686
|
+
const configuredHook = desiredHooks.SessionStart[0].hooks[0].command;
|
|
4687
|
+
const ownership = await ownershipEdit(ws.root, [...instructions, mcp, hooks[0]], { [hooks[0].path]: configuredHook });
|
|
4364
4688
|
const fingerprint = hash({
|
|
4365
4689
|
command: "mason",
|
|
4366
4690
|
mcp: mcpFingerprint,
|
|
@@ -4387,8 +4711,10 @@ async function setupProject(dir, options = {}) {
|
|
|
4387
4711
|
revision
|
|
4388
4712
|
});
|
|
4389
4713
|
const changedFiles = [];
|
|
4714
|
+
if (await applyEdit(ws.root, ownership)) changedFiles.push(ownership.path);
|
|
4390
4715
|
for (const edit of edits) if (await applyEdit(ws.root, edit)) changedFiles.push(edit.path);
|
|
4391
4716
|
if (await applyEdit(ws.root, { path: SETUP_PATH, before: setupBefore, after: JSON.stringify(setup, null, 2) + "\n" })) changedFiles.push(SETUP_PATH);
|
|
4717
|
+
if (await applyEdit(ws.root, await completedHookOwnership(ws.root, hooks[0].path, configuredHook)) && !changedFiles.includes(ownership.path)) changedFiles.push(ownership.path);
|
|
4392
4718
|
const checked = await automate(ws.root, { event: "turn_start" });
|
|
4393
4719
|
const configured = await inspectHostConfig(ws.root, host, void 0);
|
|
4394
4720
|
await writeStoreJson(ws.root, receiptPath, {
|
|
@@ -4447,6 +4773,7 @@ var init_setup = __esm({
|
|
|
4447
4773
|
init_adapters();
|
|
4448
4774
|
init_model();
|
|
4449
4775
|
init_status();
|
|
4776
|
+
init_ownership();
|
|
4450
4777
|
}
|
|
4451
4778
|
});
|
|
4452
4779
|
|
|
@@ -4457,9 +4784,23 @@ init_adapters();
|
|
|
4457
4784
|
init_install();
|
|
4458
4785
|
init_store();
|
|
4459
4786
|
import { parseArgs } from "util";
|
|
4460
|
-
var
|
|
4787
|
+
var TEARDOWN_USAGE = `Usage: mason teardown [options]
|
|
4788
|
+
|
|
4789
|
+
Disconnect Mason from this Git project. Defaults to all project hosts.
|
|
4790
|
+
|
|
4791
|
+
--host codex|claude Disconnect only this host
|
|
4792
|
+
--dir <path> Project directory (defaults to cwd; subdirectories work)
|
|
4793
|
+
--dry-run Preview changes without writing
|
|
4794
|
+
--json Machine-readable changes and diagnostics
|
|
4795
|
+
|
|
4796
|
+
Keeps shared knowledge, repair evidence, ignore rules and the Mason installation.
|
|
4797
|
+
Edited or ambiguous entries are retained for manual cleanup. Restart assistants
|
|
4798
|
+
after teardown. Run mason setup --host codex or --host claude to reconnect.
|
|
4799
|
+
Exit 0: complete/no-op. Exit 2: cleanup needs attention (also in dry runs).`;
|
|
4800
|
+
var USAGE = `Usage: mason-auto <setup|teardown|install|config|status|check|hook> [options]
|
|
4461
4801
|
|
|
4462
4802
|
setup [--host claude|codex] Connect MCP, instructions and hooks to mason on PATH; retain the initial audit
|
|
4803
|
+
teardown [--host claude|codex] Disconnect one host or all project hosts; retain knowledge and evidence
|
|
4463
4804
|
install --host claude|codex Merge lifecycle hooks into this project's host config
|
|
4464
4805
|
config --host claude|codex Print the host config without writing
|
|
4465
4806
|
status Read configured hooks and observed runtime events
|
|
@@ -4468,9 +4809,11 @@ var USAGE = `Usage: mason-auto <setup|install|config|status|check|hook> [options
|
|
|
4468
4809
|
|
|
4469
4810
|
--dir <path> Project directory (defaults to cwd)
|
|
4470
4811
|
--command <prefix> Installed executable prefix for install/config
|
|
4812
|
+
--dry-run Preview teardown without writing
|
|
4471
4813
|
--json Machine-readable output (status also uses JSON when piped)
|
|
4472
4814
|
|
|
4473
4815
|
check exits 0 for verified checks, 1 for issues, 2 for incomplete/unavailable.
|
|
4816
|
+
teardown exits 0 for complete/no-op, 2 when cleanup needs attention (also in dry runs).
|
|
4474
4817
|
Hooks are advisory and exit 0; a failed capture is reported explicitly.
|
|
4475
4818
|
Local evidence is written under .mason/reports/. No LLM calls or source edits.`;
|
|
4476
4819
|
var parseCli = (argv2) => parseArgs({ args: argv2, allowPositionals: true, options: {
|
|
@@ -4478,7 +4821,8 @@ var parseCli = (argv2) => parseArgs({ args: argv2, allowPositionals: true, optio
|
|
|
4478
4821
|
host: { type: "string" },
|
|
4479
4822
|
command: { type: "string" },
|
|
4480
4823
|
json: { type: "boolean" },
|
|
4481
|
-
help: { type: "boolean", short: "h" }
|
|
4824
|
+
help: { type: "boolean", short: "h" },
|
|
4825
|
+
"dry-run": { type: "boolean" }
|
|
4482
4826
|
} });
|
|
4483
4827
|
function isHookCommand(argv2) {
|
|
4484
4828
|
try {
|
|
@@ -4496,12 +4840,20 @@ async function runAutomationCli(argv2, stdin = "", io = {
|
|
|
4496
4840
|
try {
|
|
4497
4841
|
const { values, positionals } = parseCli(argv2);
|
|
4498
4842
|
if (values.help || !positionals.length) {
|
|
4499
|
-
io.out(USAGE);
|
|
4843
|
+
io.out(positionals[0] === "teardown" ? TEARDOWN_USAGE : USAGE);
|
|
4500
4844
|
return 0;
|
|
4501
4845
|
}
|
|
4502
4846
|
if (positionals.length !== 1) throw new Error("Expected one command.");
|
|
4503
4847
|
[action] = positionals;
|
|
4504
4848
|
const dir = values.dir ?? process.cwd();
|
|
4849
|
+
if (values["dry-run"] !== void 0 && action !== "teardown") throw new Error("--dry-run applies only to teardown.");
|
|
4850
|
+
if (action === "teardown") {
|
|
4851
|
+
if (values.command !== void 0) throw new Error("--command applies only to install/config.");
|
|
4852
|
+
const { teardownProject: teardownProject2, summarizeTeardown: summarizeTeardown2 } = await Promise.resolve().then(() => (init_teardown(), teardown_exports));
|
|
4853
|
+
const result = await teardownProject2(dir, { host: values.host === void 0 ? void 0 : hostSchema.parse(values.host), dryRun: values["dry-run"] });
|
|
4854
|
+
io.out(values.json ? JSON.stringify(result, null, 2) : summarizeTeardown2(result));
|
|
4855
|
+
return result.status === "complete" ? 0 : 2;
|
|
4856
|
+
}
|
|
4505
4857
|
if (action === "hook") {
|
|
4506
4858
|
const output = await runAutomationHook(hostSchema.parse(values.host), stdin);
|
|
4507
4859
|
if (output) io.out(JSON.stringify(output));
|
|
@@ -4532,12 +4884,12 @@ async function runAutomationCli(argv2, stdin = "", io = {
|
|
|
4532
4884
|
io.out(values.json ? JSON.stringify(report, null, 2) : summarize(report));
|
|
4533
4885
|
return report.status === "verified" ? 0 : report.status === "issues-remain" ? 1 : 2;
|
|
4534
4886
|
} catch (error) {
|
|
4535
|
-
const message2 = action === "setup" ? "Mason setup incomplete: " + automationFailure(error).message + ". Rerun the same setup command to resume; retained audit evidence is preserved." : failureMessage(error);
|
|
4887
|
+
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);
|
|
4536
4888
|
if (action === "hook" || isHookCommand(argv2)) {
|
|
4537
4889
|
io.out(JSON.stringify({ systemMessage: message2 }));
|
|
4538
4890
|
return 0;
|
|
4539
4891
|
}
|
|
4540
|
-
if (argv2.includes("--json")) io.out(JSON.stringify({ version: 1, ...
|
|
4892
|
+
if (argv2.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) }));
|
|
4541
4893
|
else io.err(message2);
|
|
4542
4894
|
return 2;
|
|
4543
4895
|
}
|