mason-context 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.14.2" : "development";
2524
+ engineVersion = true ? "0.15.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/");
@@ -2852,7 +2852,7 @@ var init_runtime = __esm({
2852
2852
  // src/setup/model.ts
2853
2853
  import { z as z7 } from "zod";
2854
2854
  async function loadSetup(root) {
2855
- const raw = await readStoreJson(root, ".mason/setup.json");
2855
+ const raw = await readStoreJson(root, SETUP_PATH);
2856
2856
  if (raw === null) return null;
2857
2857
  return setupSchema.parse(raw);
2858
2858
  }
@@ -2863,28 +2863,23 @@ async function loadSetupReceipt(root, directory, host) {
2863
2863
  if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
2864
2864
  return receipt;
2865
2865
  }
2866
- var runtimeSchema, setupHostSchema, setupSchema, receiptSchema;
2866
+ var setupHostSchema, setupSchema, SETUP_PATH, receiptSchema;
2867
2867
  var init_model = __esm({
2868
2868
  "src/setup/model.ts"() {
2869
2869
  "use strict";
2870
2870
  init_storage();
2871
- runtimeSchema = z7.object({
2872
- id: z7.string().regex(/^[a-f0-9]{24}$/),
2873
- version: z7.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
2874
- hashes: z7.record(z7.string().regex(/^[a-f0-9]{64}$/)),
2875
- bundle: z7.object({ target: z7.string(), manifestHash: z7.string().regex(/^[a-f0-9]{64}$/) }).optional()
2876
- });
2877
2871
  setupHostSchema = z7.object({
2878
- runtime: runtimeSchema,
2872
+ configuredVersion: z7.string(),
2879
2873
  revision: z7.string().uuid(),
2880
2874
  fingerprint: z7.string(),
2881
2875
  mcpFingerprint: z7.string(),
2882
2876
  instructions: z7.array(z7.string())
2883
2877
  });
2884
- setupSchema = z7.object({ version: z7.literal(1), hosts: z7.object({
2878
+ setupSchema = z7.object({ version: z7.literal(2), hosts: z7.object({
2885
2879
  codex: setupHostSchema.optional(),
2886
2880
  claude: setupHostSchema.optional()
2887
2881
  }) });
2882
+ SETUP_PATH = ".mason/local/setup.json";
2888
2883
  receiptSchema = z7.object({
2889
2884
  version: z7.literal(1),
2890
2885
  host: z7.enum(["codex", "claude"]),
@@ -2898,6 +2893,39 @@ var init_model = __esm({
2898
2893
  }
2899
2894
  });
2900
2895
 
2896
+ // src/setup/launcher.ts
2897
+ import { execFile as execFile9 } from "child_process";
2898
+ import { promisify as promisify9 } from "util";
2899
+ function globalCommand(args, platform = process.platform) {
2900
+ return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args] } : { command: "mason", args };
2901
+ }
2902
+ async function installedCommand() {
2903
+ const invocation = globalCommand(["internal-integration-version"]);
2904
+ try {
2905
+ const { stdout } = await promisify9(execFile9)(
2906
+ invocation.command,
2907
+ invocation.args,
2908
+ { timeout: 5e3, maxBuffer: 8192, windowsHide: true }
2909
+ );
2910
+ const result = JSON.parse(stdout);
2911
+ if (result.protocol !== 1 || typeof result.version !== "string") throw new Error("Unsupported Mason integration protocol.");
2912
+ return { available: true, version: result.version, message: null };
2913
+ } catch {
2914
+ return { available: false, version: null, message: "An up-to-date mason command is not available on this process's PATH. Install or upgrade Mason, then reopen your terminal or desktop assistant and rerun mason setup." };
2915
+ }
2916
+ }
2917
+ var executingVersion, mcpCommand, hookCommand;
2918
+ var init_launcher = __esm({
2919
+ "src/setup/launcher.ts"() {
2920
+ "use strict";
2921
+ init_evidence();
2922
+ init_model();
2923
+ executingVersion = () => true ? "0.15.0" : "development";
2924
+ mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
2925
+ hookCommand = (host) => `mason --setup-host ${host} auto`;
2926
+ }
2927
+ });
2928
+
2901
2929
  // src/setup/observations.ts
2902
2930
  var observations_exports = {};
2903
2931
  __export(observations_exports, {
@@ -2907,14 +2935,14 @@ __export(observations_exports, {
2907
2935
  });
2908
2936
  import fs13 from "fs/promises";
2909
2937
  import { z as z8 } from "zod";
2910
- function observationPath(directory, host, revision) {
2911
- return `${directory}/activation/${host}-${revision}.json`;
2938
+ function observationPath(directory, host, revision, version = executingVersion()) {
2939
+ return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
2912
2940
  }
2913
- async function readObservation(root, directory, host, revision) {
2914
- const raw = await readStoreJson(root, observationPath(directory, host, revision));
2941
+ async function readObservation(root, directory, host, revision, version = executingVersion()) {
2942
+ const raw = await readStoreJson(root, observationPath(directory, host, revision, version));
2915
2943
  if (raw === null) return null;
2916
2944
  const record = observationSchema.parse(raw);
2917
- if (record.root !== root || record.host !== host || record.revision !== revision) throw new Error("Activation receipt belongs to another installation.");
2945
+ if (record.root !== root || record.host !== host || record.revision !== revision || record.masonVersion !== version) throw new Error("Activation receipt belongs to another installation.");
2918
2946
  return record;
2919
2947
  }
2920
2948
  async function observeActivation(dir, event, options = {}) {
@@ -2931,6 +2959,7 @@ async function observeActivation(dir, event, options = {}) {
2931
2959
  const now = (/* @__PURE__ */ new Date()).toISOString();
2932
2960
  const record = await readObservation(ws.root, ws.directory, host, revision) ?? {
2933
2961
  version: 1,
2962
+ masonVersion: executingVersion(),
2934
2963
  root: ws.root,
2935
2964
  revision,
2936
2965
  host,
@@ -2969,8 +2998,10 @@ var init_observations = __esm({
2969
2998
  init_store();
2970
2999
  init_storage();
2971
3000
  init_model();
3001
+ init_launcher();
2972
3002
  observationSchema = z8.object({
2973
3003
  version: z8.literal(1),
3004
+ masonVersion: z8.string(),
2974
3005
  root: z8.string(),
2975
3006
  revision: z8.string(),
2976
3007
  host: z8.enum(["codex", "claude"]),
@@ -3060,106 +3091,78 @@ var init_adapters = __esm({
3060
3091
  }
3061
3092
  });
3062
3093
 
3063
- // src/automation/install.ts
3064
- var install_exports = {};
3065
- __export(install_exports, {
3066
- automationConfigSchema: () => automationConfigSchema,
3067
- configPath: () => configPath,
3068
- installAutomation: () => installAutomation,
3069
- installedAutomation: () => installedAutomation,
3070
- planAutomationInstall: () => planAutomationInstall
3094
+ // src/setup/files.ts
3095
+ var files_exports = {};
3096
+ __export(files_exports, {
3097
+ applyEdit: () => applyEdit,
3098
+ managedBlock: () => managedBlock,
3099
+ readText: () => readText
3071
3100
  });
3072
- import { z as z10 } from "zod";
3073
- async function installAutomation(dir, host, command) {
3074
- const ws = await workspace(dir);
3075
- return withLock(ws.root, ".mason/reports/automation-install", () => installLocked(ws.root, host, command));
3076
- }
3077
- async function planAutomationInstall(root, host, command) {
3078
- const file = configPath(host);
3079
- const existing = automationConfigSchema.parse(await readStoreJson(root, file) ?? {});
3080
- const record = recordSchema.parse(await readStoreJson(root, ".mason/automation.json") ?? { version: 1, hosts: {} });
3081
- const desired = hookConfig(host, command);
3082
- const newCommand = desired.hooks.SessionStart[0].hooks[0].command;
3083
- const previous = record.hosts[host]?.command;
3084
- const hooks = existing.hooks ?? {};
3085
- for (const event of HOOK_EVENTS) {
3086
- hooks[event] = (hooks[event] ?? []).map((group) => ({
3087
- ...group,
3088
- hooks: group.hooks.filter((handler) => !(handler.type === "command" && typeof handler.command === "string" && (handler.command === previous || handler.command === newCommand)))
3089
- })).filter((group) => group.hooks.length);
3090
- hooks[event].push(...desired.hooks[event]);
3101
+ import fs14 from "fs/promises";
3102
+ import path18 from "path";
3103
+ import { randomUUID as randomUUID5 } from "crypto";
3104
+ async function readText(root, file) {
3105
+ try {
3106
+ const text2 = await readBoundedFile(await storePath(root, file), 2 * 1024 * 1024);
3107
+ if (text2 === null) throw new Error("Setup input is not a regular file or exceeds 2 MiB: " + file);
3108
+ return text2;
3109
+ } catch (error) {
3110
+ if (error.code === "ENOENT") return null;
3111
+ throw error;
3091
3112
  }
3092
- record.hosts[host] = { command: newCommand };
3093
- return { file, config: { ...existing, hooks }, record, newCommand };
3094
3113
  }
3095
- async function installLocked(root, host, command) {
3096
- const { file, config, record, newCommand } = await planAutomationInstall(root, host, command);
3097
- await writeStoreJson(root, file, config);
3098
- await writeStoreJson(root, ".mason/automation.json", record);
3099
- return {
3100
- version: 1,
3101
- host,
3102
- configPath: file,
3103
- status: "configured",
3104
- command: newCommand,
3105
- events: HOOK_EVENTS,
3106
- next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason-auto status reports observed events separately from configuration." : "Start a new Claude Code session. mason-auto status reports observed events separately from configuration.",
3107
- note: "Install mason-context in the project before using the default command. Ignore .mason/reports/ to keep local evidence out of commits. Hooks preserve evidence and suggest scoped repairs; they do not approve edits or decisions."
3108
- };
3114
+ function managedBlock(text2, start, end, body) {
3115
+ const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
3116
+ if (starts !== ends || starts > 1 || starts === 1 && text2.indexOf(end) < text2.indexOf(start)) {
3117
+ throw new Error("Ambiguous Mason instruction/configuration markers; repair the marked block before setup.");
3118
+ }
3119
+ const eol = text2.includes("\r\n") ? "\r\n" : "\n";
3120
+ const block = [start, body.replace(/\r?\n/g, eol), end].join(eol);
3121
+ if (starts) return text2.slice(0, text2.indexOf(start)) + block + text2.slice(text2.indexOf(end) + end.length);
3122
+ return text2 + (text2.length && !text2.endsWith("\n") ? eol : "") + (text2.length ? eol : "") + block + eol;
3109
3123
  }
3110
- async function installedAutomation(dir) {
3111
- const ws = await workspace(dir);
3112
- const raw = await readStoreJson(ws.root, ".mason/automation.json");
3113
- if (raw === null) return {};
3114
- const record = recordSchema.parse(raw);
3115
- const result = {};
3116
- for (const host of ["claude", "codex"]) {
3117
- const expected = record.hosts[host];
3118
- if (!expected) continue;
3119
- const current = automationConfigSchema.parse(await readStoreJson(ws.root, configPath(host)) ?? {});
3120
- const configuredEvents = HOOK_EVENTS.filter((event) => current.hooks?.[event]?.some((group) => group.hooks.some((handler) => handler.type === "command" && handler.command === expected.command)));
3121
- result[host] = {
3122
- configPath: configPath(host),
3123
- configuredEvents,
3124
- status: current.disableAllHooks === true ? "disabled" : configuredEvents.length === HOOK_EVENTS.length ? "configured" : "incomplete",
3125
- runtime: "Host version, trust, policy, and tool coverage still determine execution; inspect observed events."
3126
- };
3124
+ async function applyEdit(root, edit) {
3125
+ const current = await readText(root, edit.path);
3126
+ if (current === edit.after) return false;
3127
+ if (current !== edit.before) throw new Error("Setup input changed during installation: " + edit.path + ". Rerun setup to resume.");
3128
+ const file = await storePath(root, edit.path, true);
3129
+ const temporary = path18.join(path18.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
3130
+ try {
3131
+ const mode = await fs14.stat(file).then((s) => s.mode & 511, () => 384);
3132
+ const handle = await fs14.open(temporary, "wx", mode);
3133
+ try {
3134
+ await handle.writeFile(edit.after, "utf8");
3135
+ await handle.sync();
3136
+ } finally {
3137
+ await handle.close();
3138
+ }
3139
+ if (await readText(root, edit.path) !== edit.before) throw new Error("Setup input changed during installation: " + edit.path);
3140
+ await fs14.rename(temporary, file);
3141
+ return true;
3142
+ } finally {
3143
+ await fs14.rm(temporary, { force: true });
3127
3144
  }
3128
- return result;
3129
3145
  }
3130
- var groupSchema, automationConfigSchema, recordSchema, configPath;
3131
- var init_install = __esm({
3132
- "src/automation/install.ts"() {
3146
+ var init_files2 = __esm({
3147
+ "src/setup/files.ts"() {
3133
3148
  "use strict";
3149
+ init_files();
3134
3150
  init_storage();
3135
- init_evidence();
3136
- init_adapters();
3137
- init_store();
3138
- groupSchema = z10.object({ hooks: z10.array(z10.object({ type: z10.string(), command: z10.string().optional() }).passthrough()) }).passthrough();
3139
- automationConfigSchema = z10.object({ hooks: z10.record(z10.array(groupSchema)).optional() }).passthrough();
3140
- recordSchema = z10.object({ version: z10.literal(1), hosts: z10.record(z10.object({ command: z10.string() })) });
3141
- configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
3142
3151
  }
3143
3152
  });
3144
3153
 
3145
3154
  // src/mcp/init.ts
3146
- import { z as z11 } from "zod";
3147
- async function loadProjectMarker(rootDir) {
3148
- const raw = await readStoreJson(rootDir, ".mason/project.json");
3149
- return raw === null ? null : markerSchema.parse(raw);
3150
- }
3151
- async function saveProjectMarker(rootDir, marker) {
3152
- await writeStoreJson(rootDir, ".mason/project.json", markerSchema.parse(marker));
3153
- }
3155
+ import { z as z10 } from "zod";
3154
3156
  var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_PLAYBOOK, MAP_PLAYBOOK;
3155
3157
  var init_init = __esm({
3156
3158
  "src/mcp/init.ts"() {
3157
3159
  "use strict";
3158
3160
  init_storage();
3159
- markerSchema = z11.object({
3160
- version: z11.literal(1),
3161
- initializedAt: z11.string(),
3162
- features: z11.object({ confluence: z11.boolean().optional() }).optional()
3161
+ init_files2();
3162
+ markerSchema = z10.object({
3163
+ version: z10.literal(1),
3164
+ initializedAt: z10.string(),
3165
+ features: z10.object({ confluence: z10.boolean().optional() }).passthrough().optional()
3163
3166
  }).passthrough();
3164
3167
  CLAUDE_MD_SECTION = `<!-- mason:start -->
3165
3168
  ## Mason project knowledge
@@ -3181,7 +3184,7 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
3181
3184
  Inspect source for what the retrieved context does not answer.
3182
3185
  <!-- mason:end -->`;
3183
3186
  ASSISTANT_SETUP = `UNIFIED SETUP
3184
- When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. This uses the same engine as mason-auto setup: retain the initial audit before instruction edits, install a pinned private runtime, merge project MCP configuration and hooks, update marker-delimited instructions and Git ignore rules, and record setup. A concept map is optional.
3187
+ When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. This uses the same engine as mason-auto setup: retain the initial audit before instruction edits, use the installed mason command on PATH, merge project MCP configuration and hooks, update marker-delimited instructions and Git ignore rules, and record setup. A concept map is optional.
3185
3188
  If the user only requested inspection or review, report findings without running setup. The setup result distinguishes configured from observed activation. Explain the host's native trust step and request a new session; never trust hooks on the user's behalf or claim activation from generated configuration alone. After a normal task finishes, mason_automation(action: "status") or mason-auto status reports observed context use and hook events. Existing findings remain reviewable; setup does not approve advisories or manufacture decisions.
3186
3189
 
3187
3190
  The managed project guidance is:
@@ -3189,7 +3192,7 @@ ${CLAUDE_MD_SECTION}`;
3189
3192
  QUICKSTART_PLAYBOOK = `Start with the audit and review results included in this response. No concept map is required.
3190
3193
 
3191
3194
  1. Explain the actionable findings with their source evidence. Separate audit issues, advisories, and skipped checks. The review covers committed changes from the merge base to HEAD; workingTree paths are not included in that review. An unavailable or empty check is not proof that the project is correct. Use the CLI for full output if a summary is truncated.
3192
- 2. Address findings within the user's requested scope. Setup alone authorizes the Mason runtime, host configuration, hooks, instructions, and ignore rules; rewriting existing project claims requires repair scope. If repair is authorized, call \`mason_repair(dir, action: "prepare")\` before editing; it saves the full original findings even when this summary is truncated. Inspect relevant source, make grounded edits, then call \`mason_repair(dir, action: "verify", baselinePath)\` with that same baseline, including after any final doc commit. Report resolved, unresolved, review-required, unverified, and new findings. Keep suppressed advisories visible even when setup has already dirtied a doc. Do not invent a decision just to populate the store.
3195
+ 2. Address findings within the user's requested scope. Setup alone authorizes Mason host configuration, hooks, instructions, and ignore rules; rewriting existing project claims requires repair scope. If repair is authorized, call \`mason_repair(dir, action: "prepare")\` before editing; it saves the full original findings even when this summary is truncated. Inspect relevant source, make grounded edits, then call \`mason_repair(dir, action: "verify", baselinePath)\` with that same baseline, including after any final doc commit. Report resolved, unresolved, review-required, unverified, and new findings. Keep suppressed advisories visible even when setup has already dirtied a doc. Do not invent a decision just to populate the store.
3193
3196
  3. When the task reveals a real lesson or constraint, call \`save_decision\` with title, body, category, anchors, and known owner/source/actor information. Missing attribution can be added later. The tool writes a local proposal; editing it preserves revision history and requires a new acceptance, while any earlier accepted revision remains operative. An unchanged save never refreshes its evidence. When decision review is requested, \`review_decision\` prepares the record and code evidence before any authorized verdict. Review and commit records through the normal workflow. Retrieve it on the next relevant task with \`get_context(dir, task, files)\`.
3194
3197
 
3195
3198
  ${ASSISTANT_SETUP}
@@ -3231,14 +3234,263 @@ If a build is interrupted, its partials remain in \`.mason/partial-snapshots/\`.
3231
3234
  }
3232
3235
  });
3233
3236
 
3237
+ // src/utils/git.ts
3238
+ var git_exports = {};
3239
+ __export(git_exports, {
3240
+ isGitRepo: () => isGitRepo
3241
+ });
3242
+ import { execFile as execFile10 } from "child_process";
3243
+ import { promisify as promisify10 } from "util";
3244
+ async function isGitRepo(dir) {
3245
+ try {
3246
+ await exec8("git", ["rev-parse", "--git-dir"], { cwd: dir });
3247
+ return true;
3248
+ } catch {
3249
+ return false;
3250
+ }
3251
+ }
3252
+ var exec8;
3253
+ var init_git2 = __esm({
3254
+ "src/utils/git.ts"() {
3255
+ "use strict";
3256
+ exec8 = promisify10(execFile10);
3257
+ }
3258
+ });
3259
+
3260
+ // src/setup/config.ts
3261
+ var config_exports = {};
3262
+ __export(config_exports, {
3263
+ ancillaryEdits: () => ancillaryEdits,
3264
+ hookEdits: () => hookEdits,
3265
+ inspectHostConfig: () => inspectHostConfig,
3266
+ instructionEdits: () => instructionEdits,
3267
+ mcpEdit: () => mcpEdit,
3268
+ mcpPath: () => mcpPath
3269
+ });
3270
+ import { isDeepStrictEqual } from "util";
3271
+ import { parse, stringify } from "smol-toml";
3272
+ import { z as z11 } from "zod";
3273
+ async function instructionEdits(root, host) {
3274
+ const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
3275
+ const existing = found.find((f) => f.text !== null);
3276
+ const primary = host === "codex" && found[0].text === null ? { path: "AGENTS.md", text: null } : existing ?? { path: "AGENTS.md", text: null };
3277
+ const originalGuidance = primary.text === null && existing ? "Follow the existing project conventions in " + existing.path + ".\n" : "";
3278
+ const edits = [{
3279
+ path: primary.path,
3280
+ before: primary.text,
3281
+ after: managedBlock(primary.text ?? originalGuidance, "<!-- mason:start -->", "<!-- mason:end -->", CLAUDE_MD_SECTION.split("\n").slice(1, -1).join("\n"))
3282
+ }];
3283
+ if (primary.path === "AGENTS.md") {
3284
+ const secondary = found.filter((f) => f.path !== "AGENTS.md" && f.text !== null);
3285
+ if (host === "claude" && !secondary.length) secondary.push({ path: "CLAUDE.md", text: null });
3286
+ for (const file of secondary) {
3287
+ const text2 = file.text ?? "";
3288
+ const legacy = text2.includes("<!-- mason:start -->") || text2.includes("<!-- mason:end -->");
3289
+ const imported = "@" + (file.path.startsWith(".claude/") ? "../AGENTS.md" : "AGENTS.md");
3290
+ if (text2.trim() === imported) continue;
3291
+ const pointer = "Mason project knowledge and shared project instructions:\n" + imported;
3292
+ edits.push({
3293
+ path: file.path,
3294
+ before: file.text,
3295
+ after: managedBlock(text2, legacy ? "<!-- mason:start -->" : POINTER_START, legacy ? "<!-- mason:end -->" : POINTER_END, pointer)
3296
+ });
3297
+ }
3298
+ }
3299
+ return edits;
3300
+ }
3301
+ function managedMcp(previous, host) {
3302
+ const options = previous === void 0 ? {} : object.parse(previous);
3303
+ for (const key of ["command", "args", "url", "type", "headers", "http_headers", "env_http_headers", "bearer_token_env_var"]) delete options[key];
3304
+ return { ...options, ...mcpCommand(host) };
3305
+ }
3306
+ async function mcpEdit(root, host) {
3307
+ const file = mcpPath(host), before = await readText(root, file);
3308
+ if (host === "claude") {
3309
+ const config2 = before === null ? {} : object.parse(JSON.parse(before));
3310
+ const servers2 = object.parse(config2.mcpServers ?? {});
3311
+ return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host) } }, null, 2) + "\n" };
3312
+ }
3313
+ const config = parse(before ?? "");
3314
+ const managed = (before ?? "").includes(TOML_START);
3315
+ const servers = config.mcp_servers;
3316
+ const desired = managedMcp(servers?.mason, host);
3317
+ let base = before ?? "";
3318
+ if (servers?.mason && !managed) {
3319
+ const headers = [...base.matchAll(/^\s*\[\[?.+?\]\]?[^\S\r\n]*(?:#.*)?$/gm)];
3320
+ const spans = headers.flatMap((header, index2) => /^\s*\[mcp_servers\.mason(?:\.[A-Za-z0-9_-]+)*\]/.test(header[0]) ? [{ start: header.index, end: headers[index2 + 1]?.index ?? base.length }] : []);
3321
+ if (!spans.length) throw new Error("Cannot safely migrate the existing Mason MCP TOML entry. Use an explicit [mcp_servers.mason] table and rerun setup; the file was retained.");
3322
+ for (const span of spans.reverse()) base = base.slice(0, span.start) + base.slice(span.end);
3323
+ }
3324
+ const after = managedBlock(base, TOML_START, TOML_END, stringify({ mcp_servers: { mason: desired } }).trimEnd());
3325
+ const parsed = parse(after);
3326
+ const expected = { ...config, mcp_servers: { ...servers ?? {}, mason: desired } };
3327
+ if (!isDeepStrictEqual(parsed, expected)) throw new Error("Could not safely configure the Mason MCP entry without changing other settings.");
3328
+ return { path: file, before, after };
3329
+ }
3330
+ async function hookEdits(root, host) {
3331
+ const file = configPath(host), before = await readText(root, file);
3332
+ const { planAutomationInstall: planAutomationInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
3333
+ const plan = await planAutomationInstall2(root, host, hookCommand(host));
3334
+ return [
3335
+ { path: file, before, after: JSON.stringify(plan.config, null, 2) + "\n" },
3336
+ { path: AUTOMATION_PATH, before: await readText(root, AUTOMATION_PATH), after: JSON.stringify(plan.record, null, 2) + "\n" }
3337
+ ];
3338
+ }
3339
+ async function ancillaryEdits(root) {
3340
+ const ignore = await readText(root, ".gitignore");
3341
+ const { git: git3 } = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
3342
+ const { isGitRepo: isGitRepo2 } = await Promise.resolve().then(() => (init_git2(), git_exports));
3343
+ let parentIgnored = false;
3344
+ try {
3345
+ if (await isGitRepo2(root)) parentIgnored = !!(await git3(root, "check-ignore", "--no-index", ".mason")).trim();
3346
+ } catch (error) {
3347
+ if (error.code !== 1) throw error;
3348
+ }
3349
+ const retainParentRule = parentIgnored || !!ignore?.replace(/\r\n/g, "\n").includes("# mason:ignore:start\n!/.mason/\n/.mason/*");
3350
+ const rules = [
3351
+ ...retainParentRule ? ["!/.mason/", "/.mason/*"] : [],
3352
+ "!/.mason/decisions/",
3353
+ "!/.mason/decisions/**",
3354
+ "!/.mason/config.json",
3355
+ "!/.mason/snapshot.json",
3356
+ "/.mason/local/",
3357
+ "/.mason/reports/"
3358
+ ].join("\n");
3359
+ return [{ path: ".gitignore", before: ignore, after: managedBlock(ignore ?? "", "# mason:ignore:start", "# mason:ignore:end", rules) }];
3360
+ }
3361
+ async function inspectHostConfig(root, host, plannedMcp) {
3362
+ const text2 = plannedMcp ?? await readText(root, mcpPath(host));
3363
+ const config = host === "codex" ? parse(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
3364
+ const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
3365
+ const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
3366
+ const expected = hookConfig(host, hookCommand(host));
3367
+ const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
3368
+ return {
3369
+ mcp,
3370
+ mcpDisabled: mcp?.enabled === false,
3371
+ hooks: Object.fromEntries(Object.keys(expected.hooks).map((event) => [
3372
+ event,
3373
+ (hooks.hooks?.[event] ?? []).flatMap((group) => group.hooks.filter((handler) => handler.command === expected.hooks.SessionStart[0].hooks[0].command).map((handler) => ({ ...group, hooks: [handler] })) ?? [])
3374
+ ])),
3375
+ disabled: hooks.disableAllHooks === true || config.features?.hooks === false || config.features?.codex_hooks === false
3376
+ };
3377
+ }
3378
+ var object, mcpPath, TOML_START, TOML_END, POINTER_START, POINTER_END;
3379
+ var init_config = __esm({
3380
+ "src/setup/config.ts"() {
3381
+ "use strict";
3382
+ init_init();
3383
+ init_docs();
3384
+ init_adapters();
3385
+ init_install();
3386
+ init_files2();
3387
+ init_launcher();
3388
+ object = z11.record(z11.unknown());
3389
+ mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
3390
+ TOML_START = "# mason:mcp:start";
3391
+ TOML_END = "# mason:mcp:end";
3392
+ POINTER_START = "<!-- mason:agents:start -->";
3393
+ POINTER_END = "<!-- mason:agents:end -->";
3394
+ }
3395
+ });
3396
+
3397
+ // src/automation/install.ts
3398
+ var install_exports = {};
3399
+ __export(install_exports, {
3400
+ AUTOMATION_PATH: () => AUTOMATION_PATH,
3401
+ automationConfigSchema: () => automationConfigSchema,
3402
+ configPath: () => configPath,
3403
+ installAutomation: () => installAutomation,
3404
+ installedAutomation: () => installedAutomation,
3405
+ planAutomationInstall: () => planAutomationInstall
3406
+ });
3407
+ import { z as z12 } from "zod";
3408
+ async function installationRecord(root) {
3409
+ return recordSchema.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
3410
+ }
3411
+ async function installAutomation(dir, host, command) {
3412
+ const ws = await workspace(dir);
3413
+ return withLock(ws.root, ".mason/reports/automation-install", () => installLocked(ws.root, host, command));
3414
+ }
3415
+ async function planAutomationInstall(root, host, command) {
3416
+ const file = configPath(host);
3417
+ const existing = automationConfigSchema.parse(await readStoreJson(root, file) ?? {});
3418
+ const record = await installationRecord(root);
3419
+ const desired = hookConfig(host, command);
3420
+ const newCommand = desired.hooks.SessionStart[0].hooks[0].command;
3421
+ const previous = record.hosts[host]?.command;
3422
+ const owned = /* @__PURE__ */ new Set([previous, newCommand]);
3423
+ const hooks = existing.hooks ?? {};
3424
+ for (const event of HOOK_EVENTS) {
3425
+ hooks[event] = (hooks[event] ?? []).map((group) => ({
3426
+ ...group,
3427
+ hooks: group.hooks.filter((handler) => !(handler.type === "command" && typeof handler.command === "string" && owned.has(handler.command)))
3428
+ })).filter((group) => group.hooks.length);
3429
+ hooks[event].push(...desired.hooks[event]);
3430
+ }
3431
+ record.hosts[host] = { command: newCommand };
3432
+ return { file, config: { ...existing, hooks }, record, newCommand };
3433
+ }
3434
+ async function installLocked(root, host, command) {
3435
+ const { file, config, record, newCommand } = await planAutomationInstall(root, host, command);
3436
+ const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3437
+ const { applyEdit: applyEdit2 } = await Promise.resolve().then(() => (init_files2(), files_exports));
3438
+ for (const edit of await ancillaryEdits2(root)) await applyEdit2(root, edit);
3439
+ await writeStoreJson(root, file, config);
3440
+ await writeStoreJson(root, AUTOMATION_PATH, record);
3441
+ return {
3442
+ version: 1,
3443
+ host,
3444
+ configPath: file,
3445
+ status: "configured",
3446
+ command: newCommand,
3447
+ events: HOOK_EVENTS,
3448
+ next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason-auto status reports observed events separately from configuration." : "Start a new Claude Code session. mason-auto status reports observed events separately from configuration.",
3449
+ note: "Install mason-context in the project before using the default command. Ignore .mason/reports/ to keep local evidence out of commits. Hooks preserve evidence and suggest scoped repairs; they do not approve edits or decisions."
3450
+ };
3451
+ }
3452
+ async function installedAutomation(dir) {
3453
+ const ws = await workspace(dir);
3454
+ const record = await installationRecord(ws.root);
3455
+ const result = {};
3456
+ for (const host of ["claude", "codex"]) {
3457
+ const expected = record.hosts[host];
3458
+ if (!expected) continue;
3459
+ const current = automationConfigSchema.parse(await readStoreJson(ws.root, configPath(host)) ?? {});
3460
+ const configuredEvents = HOOK_EVENTS.filter((event) => current.hooks?.[event]?.some((group) => group.hooks.some((handler) => handler.type === "command" && handler.command === expected.command)));
3461
+ result[host] = {
3462
+ configPath: configPath(host),
3463
+ configuredEvents,
3464
+ status: current.disableAllHooks === true ? "disabled" : configuredEvents.length === HOOK_EVENTS.length ? "configured" : "incomplete",
3465
+ runtime: "Host version, trust, policy, and tool coverage still determine execution; inspect observed events."
3466
+ };
3467
+ }
3468
+ return result;
3469
+ }
3470
+ var groupSchema, automationConfigSchema, recordSchema, AUTOMATION_PATH, configPath;
3471
+ var init_install = __esm({
3472
+ "src/automation/install.ts"() {
3473
+ "use strict";
3474
+ init_storage();
3475
+ init_evidence();
3476
+ init_adapters();
3477
+ init_store();
3478
+ groupSchema = z12.object({ hooks: z12.array(z12.object({ type: z12.string(), command: z12.string().optional() }).passthrough()) }).passthrough();
3479
+ automationConfigSchema = z12.object({ hooks: z12.record(z12.array(groupSchema)).optional() }).passthrough();
3480
+ recordSchema = z12.object({ version: z12.literal(1), hosts: z12.record(z12.object({ command: z12.string() })) });
3481
+ AUTOMATION_PATH = ".mason/local/automation.json";
3482
+ configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
3483
+ }
3484
+ });
3485
+
3234
3486
  // src/review/cochange.ts
3235
- import { execFile as execFile9 } from "child_process";
3236
- import { promisify as promisify9 } from "util";
3487
+ import { execFile as execFile11 } from "child_process";
3488
+ import { promisify as promisify11 } from "util";
3237
3489
  async function buildMatrix(resolvedRoot) {
3238
3490
  try {
3239
- const { stdout: shallow } = await exec8("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
3491
+ const { stdout: shallow } = await exec9("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
3240
3492
  if (shallow.trim() === "true") return null;
3241
- const { stdout } = await exec8(
3493
+ const { stdout } = await exec9(
3242
3494
  "git",
3243
3495
  ["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
3244
3496
  { cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
@@ -3294,11 +3546,11 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
3294
3546
  findings.sort((a, b) => b.rate - a.rate || b.sharedCommits - a.sharedCommits);
3295
3547
  return findings;
3296
3548
  }
3297
- var exec8, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
3549
+ var exec9, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
3298
3550
  var init_cochange = __esm({
3299
3551
  "src/review/cochange.ts"() {
3300
3552
  "use strict";
3301
- exec8 = promisify9(execFile9);
3553
+ exec9 = promisify11(execFile11);
3302
3554
  HISTORY_COMMITS = 1500;
3303
3555
  MIN_FILE_COMMITS = 5;
3304
3556
  MIN_SHARED_COMMITS = 4;
@@ -3353,7 +3605,7 @@ var init_types2 = __esm({
3353
3605
  });
3354
3606
 
3355
3607
  // src/review/evidence/vitest.ts
3356
- import { z as z12 } from "zod";
3608
+ import { z as z13 } from "zod";
3357
3609
  function parseVitest(raw, sourceRoot) {
3358
3610
  const report = schema.parse(raw);
3359
3611
  const findings = [], diagnostics = [];
@@ -3402,24 +3654,24 @@ var init_vitest = __esm({
3402
3654
  "use strict";
3403
3655
  init_paths2();
3404
3656
  init_types2();
3405
- count2 = z12.number().int().nonnegative();
3406
- schema = z12.object({
3407
- success: z12.boolean(),
3657
+ count2 = z13.number().int().nonnegative();
3658
+ schema = z13.object({
3659
+ success: z13.boolean(),
3408
3660
  numTotalTests: count2,
3409
3661
  numPassedTests: count2,
3410
3662
  numFailedTests: count2,
3411
3663
  numPendingTests: count2,
3412
3664
  numTodoTests: count2,
3413
3665
  numFailedTestSuites: count2,
3414
- testResults: z12.array(z12.object({
3415
- name: z12.string().min(1),
3416
- status: z12.enum(["passed", "failed"]),
3417
- message: z12.string().optional(),
3418
- assertionResults: z12.array(z12.object({
3419
- fullName: z12.string(),
3420
- status: z12.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
3421
- failureMessages: z12.array(z12.string()).nullable().optional(),
3422
- location: z12.object({ line: count2, column: count2 }).nullable().optional()
3666
+ testResults: z13.array(z13.object({
3667
+ name: z13.string().min(1),
3668
+ status: z13.enum(["passed", "failed"]),
3669
+ message: z13.string().optional(),
3670
+ assertionResults: z13.array(z13.object({
3671
+ fullName: z13.string(),
3672
+ status: z13.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
3673
+ failureMessages: z13.array(z13.string()).nullable().optional(),
3674
+ location: z13.object({ line: count2, column: count2 }).nullable().optional()
3423
3675
  }))
3424
3676
  }))
3425
3677
  });
@@ -3427,7 +3679,7 @@ var init_vitest = __esm({
3427
3679
  });
3428
3680
 
3429
3681
  // src/review/evidence/sarif.ts
3430
- import { z as z13 } from "zod";
3682
+ import { z as z14 } from "zod";
3431
3683
  function parseSarif(raw, sourceRoot) {
3432
3684
  const report = schema2.parse(raw), findings = [], diagnostics = [];
3433
3685
  let active = 0, suppressed = 0, absent = 0, unresolved = 0;
@@ -3518,29 +3770,29 @@ var init_sarif = __esm({
3518
3770
  "use strict";
3519
3771
  init_paths2();
3520
3772
  init_types2();
3521
- index = z13.number().int().nonnegative();
3522
- artifact = z13.object({ uri: z13.string().optional(), uriBaseId: z13.string().optional(), index: index.optional() });
3523
- message = z13.object({ text: z13.string().optional(), markdown: z13.string().optional(), id: z13.string().optional(), arguments: z13.array(z13.string()).optional() });
3524
- level = z13.enum(["error", "warning", "note", "none"]);
3525
- location = z13.object({ physicalLocation: z13.object({ artifactLocation: artifact.optional(), region: z13.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
3526
- schema2 = z13.object({
3527
- version: z13.literal("2.1.0"),
3528
- runs: z13.array(z13.object({
3529
- tool: z13.object({ driver: z13.object({ name: z13.string().min(1), rules: z13.array(z13.object({ id: z13.string(), defaultConfiguration: z13.object({ level: level.optional() }).optional(), messageStrings: z13.record(message).optional() })).optional(), globalMessageStrings: z13.record(message).optional() }) }),
3530
- invocations: z13.array(z13.object({ executionSuccessful: z13.boolean(), commandLine: z13.string().optional(), toolExecutionNotifications: z13.array(z13.object({ level: level.optional(), message })).optional() })).optional(),
3531
- versionControlProvenance: z13.array(z13.object({ revisionId: z13.string().optional() })).optional(),
3532
- originalUriBaseIds: z13.record(artifact).optional(),
3533
- artifacts: z13.array(z13.object({ location: artifact.optional() })).optional(),
3534
- results: z13.array(z13.object({
3535
- ruleId: z13.string().optional(),
3773
+ index = z14.number().int().nonnegative();
3774
+ artifact = z14.object({ uri: z14.string().optional(), uriBaseId: z14.string().optional(), index: index.optional() });
3775
+ message = z14.object({ text: z14.string().optional(), markdown: z14.string().optional(), id: z14.string().optional(), arguments: z14.array(z14.string()).optional() });
3776
+ level = z14.enum(["error", "warning", "note", "none"]);
3777
+ location = z14.object({ physicalLocation: z14.object({ artifactLocation: artifact.optional(), region: z14.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
3778
+ schema2 = z14.object({
3779
+ version: z14.literal("2.1.0"),
3780
+ runs: z14.array(z14.object({
3781
+ tool: z14.object({ driver: z14.object({ name: z14.string().min(1), rules: z14.array(z14.object({ id: z14.string(), defaultConfiguration: z14.object({ level: level.optional() }).optional(), messageStrings: z14.record(message).optional() })).optional(), globalMessageStrings: z14.record(message).optional() }) }),
3782
+ invocations: z14.array(z14.object({ executionSuccessful: z14.boolean(), commandLine: z14.string().optional(), toolExecutionNotifications: z14.array(z14.object({ level: level.optional(), message })).optional() })).optional(),
3783
+ versionControlProvenance: z14.array(z14.object({ revisionId: z14.string().optional() })).optional(),
3784
+ originalUriBaseIds: z14.record(artifact).optional(),
3785
+ artifacts: z14.array(z14.object({ location: artifact.optional() })).optional(),
3786
+ results: z14.array(z14.object({
3787
+ ruleId: z14.string().optional(),
3536
3788
  ruleIndex: index.optional(),
3537
3789
  message,
3538
3790
  level: level.optional(),
3539
- kind: z13.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
3540
- baselineState: z13.enum(["new", "unchanged", "updated", "absent"]).optional(),
3541
- suppressions: z13.array(z13.object({ kind: z13.enum(["inSource", "external"]), status: z13.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
3542
- locations: z13.array(location).optional(),
3543
- relatedLocations: z13.array(location).optional()
3791
+ kind: z14.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
3792
+ baselineState: z14.enum(["new", "unchanged", "updated", "absent"]).optional(),
3793
+ suppressions: z14.array(z14.object({ kind: z14.enum(["inSource", "external"]), status: z14.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
3794
+ locations: z14.array(location).optional(),
3795
+ relatedLocations: z14.array(location).optional()
3544
3796
  })).optional()
3545
3797
  }))
3546
3798
  });
@@ -3548,10 +3800,10 @@ var init_sarif = __esm({
3548
3800
  });
3549
3801
 
3550
3802
  // src/review/evidence.ts
3551
- import path18 from "path";
3552
- import { z as z14 } from "zod";
3803
+ import path19 from "path";
3804
+ import { z as z15 } from "zod";
3553
3805
  function relativeArtifact(root, file) {
3554
- const relative = path18.isAbsolute(file) ? path18.relative(root, file) : file;
3806
+ const relative = path19.isAbsolute(file) ? path19.relative(root, file) : file;
3555
3807
  const normalized = normalizeRepoPath(relative);
3556
3808
  if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
3557
3809
  return normalized;
@@ -3590,7 +3842,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
3590
3842
  let manifest, raw;
3591
3843
  try {
3592
3844
  manifest = relativeArtifact(root, supplied);
3593
- raw = z14.object({ version: z14.literal(1), checks: z14.array(z14.unknown()) }).parse(await readStoreJson(root, manifest));
3845
+ raw = z15.object({ version: z15.literal(1), checks: z15.array(z15.unknown()) }).parse(await readStoreJson(root, manifest));
3594
3846
  } catch (error) {
3595
3847
  output.diagnostics.push(`${supplied}: ${String(error)}`);
3596
3848
  continue;
@@ -3647,7 +3899,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
3647
3899
  const report = await readStoreJson(root, file);
3648
3900
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
3649
3901
  const sourceRoot = check.sourceRoot ?? root;
3650
- if (!path18.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
3902
+ if (!path19.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
3651
3903
  const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : parseSarif(report, sourceRoot);
3652
3904
  result.outcome = parsed.outcome;
3653
3905
  result.counts = parsed.counts;
@@ -3743,31 +3995,31 @@ var init_evidence2 = __esm({
3743
3995
  init_vitest();
3744
3996
  init_sarif();
3745
3997
  init_types2();
3746
- checkSchema2 = z14.object({
3747
- id: z14.string().min(1).max(100),
3748
- kind: z14.enum(["tests", "static-analysis", "security", "complexity", "duplication"]),
3749
- tool: z14.string().min(1).max(200),
3750
- command: z14.string().min(1).max(2e3),
3751
- commit: z14.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
3752
- workingTreeClean: z14.boolean().optional(),
3753
- source: z14.string().max(2e3).optional(),
3754
- sourceRoot: z14.string().min(1).max(4e3).optional(),
3755
- status: z14.enum(["completed", "skipped", "unavailable"]).default("completed"),
3756
- reason: z14.string().min(1).max(2e3).optional(),
3757
- exitCode: z14.number().int().nullable().optional(),
3758
- report: z14.object({ format: z14.enum(["vitest-json", "sarif"]), path: z14.string().min(1).max(4e3) }).optional()
3998
+ checkSchema2 = z15.object({
3999
+ id: z15.string().min(1).max(100),
4000
+ kind: z15.enum(["tests", "static-analysis", "security", "complexity", "duplication"]),
4001
+ tool: z15.string().min(1).max(200),
4002
+ command: z15.string().min(1).max(2e3),
4003
+ commit: z15.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
4004
+ workingTreeClean: z15.boolean().optional(),
4005
+ source: z15.string().max(2e3).optional(),
4006
+ sourceRoot: z15.string().min(1).max(4e3).optional(),
4007
+ status: z15.enum(["completed", "skipped", "unavailable"]).default("completed"),
4008
+ reason: z15.string().min(1).max(2e3).optional(),
4009
+ exitCode: z15.number().int().nullable().optional(),
4010
+ report: z15.object({ format: z15.enum(["vitest-json", "sarif"]), path: z15.string().min(1).max(4e3) }).optional()
3759
4011
  });
3760
4012
  }
3761
4013
  });
3762
4014
 
3763
4015
  // src/review/review.ts
3764
- import fs14 from "fs/promises";
3765
- import path19 from "path";
3766
- import { execFile as execFile10 } from "child_process";
3767
- import { promisify as promisify10 } from "util";
4016
+ import fs15 from "fs/promises";
4017
+ import path20 from "path";
4018
+ import { execFile as execFile12 } from "child_process";
4019
+ import { promisify as promisify12 } from "util";
3768
4020
  async function resolveMergeBase(resolvedRoot, base, head) {
3769
4021
  try {
3770
- const { stdout } = await exec9("git", ["merge-base", base, head], {
4022
+ const { stdout } = await exec10("git", ["merge-base", base, head], {
3771
4023
  cwd: resolvedRoot
3772
4024
  });
3773
4025
  return stdout.trim() || null;
@@ -3778,7 +4030,7 @@ async function resolveMergeBase(resolvedRoot, base, head) {
3778
4030
  async function defaultBase(resolvedRoot) {
3779
4031
  for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
3780
4032
  try {
3781
- await exec9("git", ["rev-parse", "--verify", "--quiet", ref], {
4033
+ await exec10("git", ["rev-parse", "--verify", "--quiet", ref], {
3782
4034
  cwd: resolvedRoot
3783
4035
  });
3784
4036
  return ref;
@@ -3791,7 +4043,7 @@ function anchorsTouched(record, changedFiles) {
3791
4043
  return matchingPaths(record.files, changedFiles);
3792
4044
  }
3793
4045
  async function computeReview(rootDir, base, options = {}) {
3794
- const resolvedRoot = path19.resolve(rootDir);
4046
+ const resolvedRoot = path20.resolve(rootDir);
3795
4047
  const head = await getCurrentGitHash(resolvedRoot);
3796
4048
  if (head === "unknown") return null;
3797
4049
  const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
@@ -3841,7 +4093,7 @@ async function computeReview(rootDir, base, options = {}) {
3841
4093
  analyzed,
3842
4094
  async (relPath) => {
3843
4095
  try {
3844
- await fs14.access(path19.join(resolvedRoot, relPath));
4096
+ await fs15.access(path20.join(resolvedRoot, relPath));
3845
4097
  return true;
3846
4098
  } catch {
3847
4099
  return false;
@@ -3874,7 +4126,7 @@ async function computeReview(rootDir, base, options = {}) {
3874
4126
  }
3875
4127
  return finalize();
3876
4128
  }
3877
- var exec9, MAX_ANALYZED_FILES;
4129
+ var exec10, MAX_ANALYZED_FILES;
3878
4130
  var init_review = __esm({
3879
4131
  "src/review/review.ts"() {
3880
4132
  "use strict";
@@ -3886,7 +4138,7 @@ var init_review = __esm({
3886
4138
  init_cochange();
3887
4139
  init_evidence2();
3888
4140
  init_snapshot();
3889
- exec9 = promisify10(execFile10);
4141
+ exec10 = promisify12(execFile12);
3890
4142
  MAX_ANALYZED_FILES = 50;
3891
4143
  }
3892
4144
  });
@@ -3975,431 +4227,6 @@ var init_onboarding = __esm({
3975
4227
  }
3976
4228
  });
3977
4229
 
3978
- // src/setup/files.ts
3979
- import fs15 from "fs/promises";
3980
- import path20 from "path";
3981
- import { randomUUID as randomUUID5 } from "crypto";
3982
- async function readText(root, file) {
3983
- try {
3984
- const text2 = await readBoundedFile(await storePath(root, file), 2 * 1024 * 1024);
3985
- if (text2 === null) throw new Error("Setup input is not a regular file or exceeds 2 MiB: " + file);
3986
- return text2;
3987
- } catch (error) {
3988
- if (error.code === "ENOENT") return null;
3989
- throw error;
3990
- }
3991
- }
3992
- function managedBlock(text2, start, end, body) {
3993
- const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
3994
- if (starts !== ends || starts > 1 || starts === 1 && text2.indexOf(end) < text2.indexOf(start)) {
3995
- throw new Error("Ambiguous Mason instruction/configuration markers; repair the marked block before setup.");
3996
- }
3997
- const eol = text2.includes("\r\n") ? "\r\n" : "\n";
3998
- const block = [start, body.replace(/\r?\n/g, eol), end].join(eol);
3999
- if (starts) return text2.slice(0, text2.indexOf(start)) + block + text2.slice(text2.indexOf(end) + end.length);
4000
- return text2 + (text2.length && !text2.endsWith("\n") ? eol : "") + (text2.length ? eol : "") + block + eol;
4001
- }
4002
- async function applyEdit(root, edit) {
4003
- const current = await readText(root, edit.path);
4004
- if (current === edit.after) return false;
4005
- if (current !== edit.before) throw new Error("Setup input changed during installation: " + edit.path + ". Rerun setup to resume.");
4006
- const file = await storePath(root, edit.path, true);
4007
- const temporary = path20.join(path20.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
4008
- try {
4009
- const mode = await fs15.stat(file).then((s) => s.mode & 511, () => 384);
4010
- const handle = await fs15.open(temporary, "wx", mode);
4011
- try {
4012
- await handle.writeFile(edit.after, "utf8");
4013
- await handle.sync();
4014
- } finally {
4015
- await handle.close();
4016
- }
4017
- if (await readText(root, edit.path) !== edit.before) throw new Error("Setup input changed during installation: " + edit.path);
4018
- await fs15.rename(temporary, file);
4019
- return true;
4020
- } finally {
4021
- await fs15.rm(temporary, { force: true });
4022
- }
4023
- }
4024
- var init_files2 = __esm({
4025
- "src/setup/files.ts"() {
4026
- "use strict";
4027
- init_files();
4028
- init_storage();
4029
- }
4030
- });
4031
-
4032
- // src/setup/launcher.ts
4033
- var BOOTSTRAP, POWERSHELL_BOOTSTRAP, mcpCommand, hookCommand, SHELL_LAUNCHER, POWERSHELL_LAUNCHER, LAUNCHER;
4034
- var init_launcher = __esm({
4035
- "src/setup/launcher.ts"() {
4036
- "use strict";
4037
- BOOTSTRAP = "require(require('node:path').join(require('node:child_process').execFileSync('git',['rev-parse','--show-toplevel'],{encoding:'utf8'}).trim(),'.mason','run.cjs'))";
4038
- POWERSHELL_BOOTSTRAP = "& (Join-Path (git rev-parse --show-toplevel) '.mason/run.ps1')";
4039
- mcpCommand = (host, runtime) => !runtime?.bundle ? { command: "node", args: ["-e", BOOTSTRAP, "--", host, "mcp"] } : process.platform === "win32" ? { command: "powershell.exe", args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", POWERSHELL_BOOTSTRAP, host, "mcp"] } : { command: "sh", args: ["-c", 'exec sh "$(git rev-parse --show-toplevel)/.mason/run.sh" "$@"', "mason", host, "mcp"] };
4040
- hookCommand = (host, runtime) => !runtime?.bundle ? `node -e "${BOOTSTRAP}" -- ${host} auto` : process.platform === "win32" ? `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "${POWERSHELL_BOOTSTRAP} ${host} auto"` : `sh "$(git rev-parse --show-toplevel)/.mason/run.sh" ${host} auto`;
4041
- SHELL_LAUNCHER = `#!/bin/sh
4042
- set -eu
4043
- advisory=0
4044
- if [ "\${2-}" = auto ] && [ "\${3-}" = hook ]; then advisory=1; fi
4045
- fail() {
4046
- if [ "$advisory" -eq 1 ]; then printf '%s\\n' '{"systemMessage":"Mason runtime unavailable. Rerun mason setup for this checkout; verification was not established."}'; exit 0; fi
4047
- echo 'Mason runtime unavailable. Rerun mason setup for this checkout.' >&2; exit 2
4048
- }
4049
- root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) || fail
4050
- case "\${1-}" in codex|claude) ;; *) fail ;; esac
4051
- pointer="$root/.mason/runtime/$1.txt"
4052
- [ -f "$pointer" ] || fail
4053
- runtime=$(cat "$pointer") || fail
4054
- case "$runtime" in ''|*[!0-9a-f]*) fail ;; esac
4055
- [ "\${#runtime}" -eq 24 ] || fail
4056
- [ -x "$root/.mason/runtime/$runtime/node" ] || fail
4057
- if [ "$advisory" -eq 1 ]; then
4058
- "$root/.mason/runtime/$runtime/node" "$root/.mason/run.cjs" "$@" || fail
4059
- else
4060
- exec "$root/.mason/runtime/$runtime/node" "$root/.mason/run.cjs" "$@"
4061
- fi
4062
- `;
4063
- POWERSHELL_LAUNCHER = `# Managed by mason setup; no system Node installation is used.
4064
- $ErrorActionPreference = 'Stop'
4065
- $advisory = $args.Count -ge 3 -and $args[1] -eq 'auto' -and $args[2] -eq 'hook'
4066
- try {
4067
- if ($args.Count -lt 2 -or $args[0] -notin @('codex','claude')) { throw 'Invalid Mason host.' }
4068
- $runtime = (Get-Content -LiteralPath (Join-Path $PSScriptRoot ('runtime/' + $args[0] + '.txt')) -Raw).Trim()
4069
- if ($runtime -cnotmatch '^[a-f0-9]{24}$') { throw 'Invalid Mason runtime pointer.' }
4070
- & (Join-Path $PSScriptRoot ('runtime/' + $runtime + '/node.exe')) (Join-Path $PSScriptRoot 'run.cjs') @args
4071
- if ($LASTEXITCODE -ne 0) { throw 'Mason runtime failed.' }
4072
- } catch {
4073
- if ($advisory) { Write-Output '{"systemMessage":"Mason runtime unavailable. Rerun mason setup for this checkout; verification was not established."}'; exit 0 }
4074
- [Console]::Error.WriteLine($_.Exception.Message); exit 2
4075
- }
4076
- `;
4077
- LAUNCHER = `// Managed by mason-auto setup. Rerun setup to install this checkout's pinned runtime.
4078
- const fs = require('node:fs');
4079
- const path = require('node:path');
4080
- const {pathToFileURL} = require('node:url');
4081
- const launchArgs = process.argv.slice(process.argv[1] && path.resolve(process.argv[1]) === __filename ? 2 : 1);
4082
- (async () => {
4083
- const root = path.dirname(__dirname);
4084
- const args = launchArgs;
4085
- const [host, command, ...rest] = args;
4086
- if (!['codex','claude'].includes(host) || !['auto','mcp'].includes(command)) throw new Error('Invalid Mason launcher arguments.');
4087
- const setup = JSON.parse(fs.readFileSync(path.join(__dirname, 'setup.json'), 'utf8'));
4088
- const entry = setup.hosts?.[host];
4089
- if (setup.version !== 1 || !entry || !/^[a-f0-9]{24}$/.test(entry.runtime?.id)) throw new Error('Mason is not configured for this host. Rerun mason-auto setup.');
4090
- const base = path.join(__dirname, 'runtime', entry.runtime.id);
4091
- if (entry.runtime.bundle) {
4092
- const node = process.platform === 'win32' ? 'node.exe' : 'node';
4093
- if (fs.realpathSync(process.execPath) !== fs.realpathSync(path.join(base, node))) throw new Error('Mason runtime pointer differs from the configured version. Rerun setup.');
4094
- }
4095
- const binary = entry.runtime.bundle ? path.join(base, 'app', 'dist', 'mason-' + command + '.js')
4096
- : path.join(base, 'node_modules', 'mason-context', 'dist', 'mason-' + command + '.js');
4097
- if (!fs.existsSync(binary)) throw new Error('The pinned Mason runtime is missing in this checkout. Rerun mason-auto setup --host ' + host + '.');
4098
- process.env.MASON_SETUP_ROOT = root;
4099
- process.env.MASON_SETUP_HOST = host;
4100
- process.env.MASON_SETUP_REVISION = entry.revision;
4101
- process.argv = [process.execPath, binary, ...rest];
4102
- await import(pathToFileURL(binary).href);
4103
- })().catch(error => {
4104
- const message = 'Mason setup unavailable: ' + error.message;
4105
- if (launchArgs[1] === 'auto' && launchArgs[2] === 'hook') { console.log(JSON.stringify({systemMessage: message})); process.exitCode = 0; }
4106
- else { console.error(message); process.exitCode = 2; }
4107
- });
4108
- `;
4109
- }
4110
- });
4111
-
4112
- // src/setup/config.ts
4113
- import { isDeepStrictEqual } from "util";
4114
- import { parse, stringify } from "smol-toml";
4115
- import { z as z15 } from "zod";
4116
- async function instructionEdits(root, host) {
4117
- const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
4118
- const existing = found.find((f) => f.text !== null);
4119
- const primary = host === "codex" && found[0].text === null ? { path: "AGENTS.md", text: null } : existing ?? { path: "AGENTS.md", text: null };
4120
- const originalGuidance = primary.text === null && existing ? "Follow the existing project conventions in " + existing.path + ".\n" : "";
4121
- const edits = [{
4122
- path: primary.path,
4123
- before: primary.text,
4124
- after: managedBlock(primary.text ?? originalGuidance, "<!-- mason:start -->", "<!-- mason:end -->", CLAUDE_MD_SECTION.split("\n").slice(1, -1).join("\n"))
4125
- }];
4126
- if (primary.path === "AGENTS.md") {
4127
- const secondary = found.filter((f) => f.path !== "AGENTS.md" && f.text !== null);
4128
- if (host === "claude" && !secondary.length) secondary.push({ path: "CLAUDE.md", text: null });
4129
- for (const file of secondary) {
4130
- const text2 = file.text ?? "";
4131
- const legacy = text2.includes("<!-- mason:start -->") || text2.includes("<!-- mason:end -->");
4132
- const imported = "@" + (file.path.startsWith(".claude/") ? "../AGENTS.md" : "AGENTS.md");
4133
- if (text2.trim() === imported) continue;
4134
- const pointer = "Mason project knowledge and shared project instructions:\n" + imported;
4135
- edits.push({
4136
- path: file.path,
4137
- before: file.text,
4138
- after: managedBlock(text2, legacy ? "<!-- mason:start -->" : POINTER_START, legacy ? "<!-- mason:end -->" : POINTER_END, pointer)
4139
- });
4140
- }
4141
- }
4142
- return edits;
4143
- }
4144
- function managedMcp(previous, host, runtime) {
4145
- const options = previous === void 0 ? {} : object.parse(previous);
4146
- for (const key of ["command", "args", "url", "type", "headers", "http_headers", "env_http_headers", "bearer_token_env_var"]) delete options[key];
4147
- return { ...options, ...mcpCommand(host, runtime) };
4148
- }
4149
- async function mcpEdit(root, host, runtime) {
4150
- const file = mcpPath(host), before = await readText(root, file);
4151
- if (host === "claude") {
4152
- const config2 = before === null ? {} : object.parse(JSON.parse(before));
4153
- const servers2 = object.parse(config2.mcpServers ?? {});
4154
- return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host, runtime) } }, null, 2) + "\n" };
4155
- }
4156
- const config = parse(before ?? "");
4157
- const managed = (before ?? "").includes(TOML_START);
4158
- const servers = config.mcp_servers;
4159
- const desired = managedMcp(servers?.mason, host, runtime);
4160
- let base = before ?? "";
4161
- if (servers?.mason && !managed) {
4162
- const headers = [...base.matchAll(/^\s*\[\[?.+?\]\]?[^\S\r\n]*(?:#.*)?$/gm)];
4163
- const spans = headers.flatMap((header, index2) => /^\s*\[mcp_servers\.mason(?:\.[A-Za-z0-9_-]+)*\]/.test(header[0]) ? [{ start: header.index, end: headers[index2 + 1]?.index ?? base.length }] : []);
4164
- if (!spans.length) throw new Error("Cannot safely migrate the existing Mason MCP TOML entry. Use an explicit [mcp_servers.mason] table and rerun setup; the file was retained.");
4165
- for (const span of spans.reverse()) base = base.slice(0, span.start) + base.slice(span.end);
4166
- }
4167
- const after = managedBlock(base, TOML_START, TOML_END, stringify({ mcp_servers: { mason: desired } }).trimEnd());
4168
- const parsed = parse(after);
4169
- const expected = { ...config, mcp_servers: { ...servers ?? {}, mason: desired } };
4170
- if (!isDeepStrictEqual(parsed, expected)) throw new Error("Could not safely configure the Mason MCP entry without changing other settings.");
4171
- return { path: file, before, after };
4172
- }
4173
- async function hookEdits(root, host, runtime) {
4174
- const file = configPath(host), before = await readText(root, file);
4175
- const { planAutomationInstall: planAutomationInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
4176
- const plan = await planAutomationInstall2(root, host, hookCommand(host, runtime));
4177
- return [
4178
- { path: file, before, after: JSON.stringify(plan.config, null, 2) + "\n" },
4179
- { path: ".mason/automation.json", before: await readText(root, ".mason/automation.json"), after: JSON.stringify(plan.record, null, 2) + "\n" }
4180
- ];
4181
- }
4182
- async function ancillaryEdits(root) {
4183
- const ignore = await readText(root, ".gitignore");
4184
- const { git: git3 } = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
4185
- let parentIgnored = false;
4186
- try {
4187
- parentIgnored = !!(await git3(root, "check-ignore", "--no-index", ".mason")).trim();
4188
- } catch (error) {
4189
- if (error.code !== 1) throw error;
4190
- }
4191
- const retainParentRule = parentIgnored || !!ignore?.replace(/\r\n/g, "\n").includes("# mason:ignore:start\n!/.mason/\n/.mason/*");
4192
- const rules = [...retainParentRule ? ["!/.mason/", "/.mason/*"] : [], "!/.mason/decisions/", "!/.mason/decisions/**", "!/.mason/setup.json", "!/.mason/automation.json", "!/.mason/project.json", "!/.mason/run.cjs", "!/.mason/run.sh", "!/.mason/run.ps1", "/.mason/reports/", "/.mason/runtime/"].join("\n");
4193
- return [
4194
- { path: ".gitignore", before: ignore, after: managedBlock(ignore ?? "", "# mason:ignore:start", "# mason:ignore:end", rules) },
4195
- { path: ".mason/run.cjs", before: await readText(root, ".mason/run.cjs"), after: LAUNCHER },
4196
- { path: ".mason/run.sh", before: await readText(root, ".mason/run.sh"), after: SHELL_LAUNCHER },
4197
- { path: ".mason/run.ps1", before: await readText(root, ".mason/run.ps1"), after: POWERSHELL_LAUNCHER }
4198
- ];
4199
- }
4200
- async function inspectHostConfig(root, host, plannedMcp, runtime) {
4201
- const text2 = plannedMcp ?? await readText(root, mcpPath(host));
4202
- const config = host === "codex" ? parse(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
4203
- const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
4204
- const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
4205
- const expected = hookConfig(host, hookCommand(host, runtime));
4206
- const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
4207
- return {
4208
- mcp,
4209
- mcpDisabled: mcp?.enabled === false,
4210
- hooks: Object.fromEntries(Object.keys(expected.hooks).map((event) => [
4211
- event,
4212
- (hooks.hooks?.[event] ?? []).flatMap((group) => group.hooks.filter((handler) => handler.command === expected.hooks.SessionStart[0].hooks[0].command).map((handler) => ({ ...group, hooks: [handler] })) ?? [])
4213
- ])),
4214
- disabled: hooks.disableAllHooks === true || config.features?.hooks === false || config.features?.codex_hooks === false
4215
- };
4216
- }
4217
- var object, mcpPath, TOML_START, TOML_END, POINTER_START, POINTER_END;
4218
- var init_config = __esm({
4219
- "src/setup/config.ts"() {
4220
- "use strict";
4221
- init_init();
4222
- init_docs();
4223
- init_adapters();
4224
- init_install();
4225
- init_files2();
4226
- init_launcher();
4227
- object = z15.record(z15.unknown());
4228
- mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
4229
- TOML_START = "# mason:mcp:start";
4230
- TOML_END = "# mason:mcp:end";
4231
- POINTER_START = "<!-- mason:agents:start -->";
4232
- POINTER_END = "<!-- mason:agents:end -->";
4233
- }
4234
- });
4235
-
4236
- // src/distribution/bundle.ts
4237
- import fs16 from "fs/promises";
4238
- import path21 from "path";
4239
- import { createHash as createHash4 } from "crypto";
4240
- import { z as z16 } from "zod";
4241
- async function readBundle(root) {
4242
- const bytes = await fs16.readFile(await storePath(root, "bundle.json"));
4243
- const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
4244
- const windows = manifest.target.startsWith("win32-");
4245
- for (const file of Object.keys(manifest.files)) {
4246
- if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path21.isAbsolute(file)) {
4247
- throw new Error("Unsafe path in Mason bundle: " + file);
4248
- }
4249
- }
4250
- for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-auto.js", "app/dist/mason-mcp.js"]) {
4251
- if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
4252
- }
4253
- return { manifest, manifestHash: sha256(bytes) };
4254
- }
4255
- async function verifyBundle(root, expectedHash) {
4256
- const bundle = await readBundle(root);
4257
- if (expectedHash && bundle.manifestHash !== expectedHash) throw new Error("Mason bundle manifest changed.");
4258
- if (bundle.manifest.target !== `${process.platform}-${process.arch}`) throw new Error("Mason bundle is for another platform. Install this platform's bundle and rerun setup.");
4259
- const files = Object.keys(bundle.manifest.files);
4260
- for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
4261
- if (sha256(await fs16.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
4262
- }));
4263
- return bundle;
4264
- }
4265
- var targets, digest2, bundleSchema, sha256;
4266
- var init_bundle = __esm({
4267
- "src/distribution/bundle.ts"() {
4268
- "use strict";
4269
- init_storage();
4270
- targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
4271
- digest2 = z16.string().regex(/^[a-f0-9]{64}$/);
4272
- bundleSchema = z16.object({
4273
- format: z16.literal(1),
4274
- version: z16.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
4275
- target: z16.enum(targets),
4276
- nodeVersion: z16.string(),
4277
- files: z16.record(digest2)
4278
- }).strict();
4279
- sha256 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
4280
- }
4281
- });
4282
-
4283
- // src/setup/runtime.ts
4284
- import fs17 from "fs/promises";
4285
- import path22 from "path";
4286
- import { fileURLToPath } from "url";
4287
- import { randomUUID as randomUUID6, createHash as createHash5 } from "crypto";
4288
- import { execFile as execFile11 } from "child_process";
4289
- import { promisify as promisify11 } from "util";
4290
- async function packageRoot() {
4291
- let directory = path22.dirname(fileURLToPath(import.meta.url));
4292
- for (let i = 0; i < 8; i++) {
4293
- try {
4294
- const pkg = JSON.parse(await fs17.readFile(path22.join(directory, "package.json"), "utf8"));
4295
- if (pkg.name === "mason-context") return directory;
4296
- } catch {
4297
- }
4298
- directory = path22.dirname(directory);
4299
- }
4300
- throw new Error("Cannot locate the executing Mason distribution. Reinstall Mason and rerun setup.");
4301
- }
4302
- async function sourceRuntime() {
4303
- const source = await packageRoot();
4304
- const pkg = JSON.parse(await fs17.readFile(path22.join(source, "package.json"), "utf8"));
4305
- const hashes = Object.fromEntries(await Promise.all(BINARIES.map(async (file) => [file, checksum(await fs17.readFile(path22.join(source, file)))])));
4306
- const bundleRoot = path22.dirname(source);
4307
- const bundleExists = await fs17.lstat(path22.join(bundleRoot, "bundle.json")).then(() => true, (error) => {
4308
- if (error.code === "ENOENT") return false;
4309
- throw error;
4310
- });
4311
- if (bundleExists) {
4312
- const { manifest, manifestHash } = await verifyBundle(bundleRoot);
4313
- if (manifest.version !== pkg.version) throw new Error("Mason package and bundle versions differ.");
4314
- return { source, bundleRoot, runtime: runtimeSchema.parse({
4315
- id: manifestHash.slice(0, 24),
4316
- version: pkg.version,
4317
- hashes,
4318
- bundle: { target: manifest.target, manifestHash }
4319
- }) };
4320
- }
4321
- return { source, runtime: runtimeSchema.parse({ id: hash([pkg.version, pkg.dependencies, hashes]).slice(0, 24), version: pkg.version, hashes }) };
4322
- }
4323
- async function verifyRuntime(root, runtime) {
4324
- try {
4325
- const saved = await readStoreJson(root, `.mason/runtime/${runtime.id}/receipt.json`);
4326
- if (JSON.stringify(saved) !== JSON.stringify(runtime)) return false;
4327
- if (runtime.bundle) {
4328
- const base2 = await storePath(root, `.mason/runtime/${runtime.id}`);
4329
- const { manifest } = await verifyBundle(base2, runtime.bundle.manifestHash);
4330
- if (manifest.version !== runtime.version || manifest.target !== runtime.bundle.target) return false;
4331
- for (const file of BINARIES) if (manifest.files["app/" + file] !== runtime.hashes[file]) return false;
4332
- return true;
4333
- }
4334
- const base = `.mason/runtime/${runtime.id}/node_modules/mason-context/`;
4335
- const pkg = await readStoreJson(root, base + "package.json");
4336
- if (pkg?.name !== "mason-context" || pkg.version !== runtime.version) return false;
4337
- for (const file of BINARIES) {
4338
- if (checksum(await fs17.readFile(await storePath(root, base + file))) !== runtime.hashes[file]) return false;
4339
- }
4340
- return true;
4341
- } catch {
4342
- return false;
4343
- }
4344
- }
4345
- async function installRuntime(root, selected) {
4346
- const { runtime, source } = selected;
4347
- if (await verifyRuntime(root, runtime)) return runtime;
4348
- const relative = `.mason/runtime/${runtime.id}`;
4349
- const target = await storePath(root, relative, true);
4350
- const stage = await storePath(root, ".mason/runtime/.install-" + randomUUID6(), true);
4351
- await fs17.mkdir(stage);
4352
- try {
4353
- if (runtime.bundle) {
4354
- if (!selected.bundleRoot) throw new Error("Standalone bundle source is missing.");
4355
- const { manifest } = await verifyBundle(selected.bundleRoot, runtime.bundle.manifestHash);
4356
- for (const file of ["bundle.json", ...Object.keys(manifest.files)]) {
4357
- const from = await storePath(selected.bundleRoot, file);
4358
- const to = await storePath(stage, file, true);
4359
- await fs17.copyFile(from, to);
4360
- await fs17.chmod(to, (await fs17.stat(from)).mode & 511);
4361
- }
4362
- await verifyBundle(stage, runtime.bundle.manifestHash);
4363
- } else {
4364
- const npm = process.platform === "win32" ? "npm.cmd" : "npm";
4365
- const options = { timeout: 12e4, maxBuffer: 2 * 1024 * 1024, windowsHide: true };
4366
- const packed = JSON.parse((await exec10(npm, ["pack", "--ignore-scripts", "--json", "--pack-destination", stage], { ...options, cwd: source })).stdout);
4367
- const filename = packed[0]?.filename;
4368
- if (typeof filename !== "string" || path22.basename(filename) !== filename) throw new Error("npm did not produce a Mason package archive.");
4369
- await fs17.rename(path22.join(stage, filename), path22.join(stage, "mason.tgz"));
4370
- await fs17.writeFile(path22.join(stage, "package.json"), JSON.stringify({ name: "mason-project-runtime", private: true, version: "1.0.0" }));
4371
- await exec10(npm, ["install", "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund", "--save-exact", "./mason.tgz"], { ...options, cwd: stage });
4372
- for (const file of BINARIES) {
4373
- if (checksum(await fs17.readFile(path22.join(stage, "node_modules/mason-context", file))) !== runtime.hashes[file]) throw new Error("Installed Mason binary differs from the selected distribution.");
4374
- }
4375
- }
4376
- await fs17.writeFile(path22.join(stage, "receipt.json"), JSON.stringify(runtime, null, 2) + "\n");
4377
- try {
4378
- await fs17.rename(target, target + ".previous-" + randomUUID6());
4379
- } catch (error) {
4380
- if (error.code !== "ENOENT") throw error;
4381
- }
4382
- await fs17.rename(stage, target);
4383
- if (!await verifyRuntime(root, runtime)) throw new Error("The installed Mason runtime could not be verified.");
4384
- return runtime;
4385
- } finally {
4386
- await fs17.rm(stage, { recursive: true, force: true });
4387
- }
4388
- }
4389
- var exec10, checksum, BINARIES;
4390
- var init_runtime2 = __esm({
4391
- "src/setup/runtime.ts"() {
4392
- "use strict";
4393
- init_model();
4394
- init_storage();
4395
- init_evidence();
4396
- init_bundle();
4397
- exec10 = promisify11(execFile11);
4398
- checksum = (bytes) => createHash5("sha256").update(bytes).digest("hex");
4399
- BINARIES = ["dist/mason-auto.js", "dist/mason-mcp.js"];
4400
- }
4401
- });
4402
-
4403
4230
  // src/setup/status.ts
4404
4231
  var status_exports = {};
4405
4232
  __export(status_exports, {
@@ -4424,34 +4251,32 @@ async function setupStatus(dir) {
4424
4251
  };
4425
4252
  }
4426
4253
  const hosts = {};
4427
- const commonLauncherCurrent = await readText(ws.root, ".mason/run.cjs") === LAUNCHER;
4254
+ const installed = await installedCommand();
4428
4255
  const automation = await automationStatus(ws.root);
4429
4256
  for (const host of ["codex", "claude"]) {
4430
4257
  const entry = setup.hosts[host];
4431
4258
  if (!entry) continue;
4432
- const launcherCurrent = commonLauncherCurrent && (!entry.runtime.bundle || await readText(ws.root, ".mason/run.sh") === SHELL_LAUNCHER && await readText(ws.root, ".mason/run.ps1") === POWERSHELL_LAUNCHER && await readText(ws.root, `.mason/runtime/${host}.txt`) === entry.runtime.id + "\n");
4433
4259
  const instructions = await instructionEdits(ws.root, host);
4434
4260
  const instructionsCurrent = instructions.every((edit) => edit.before === edit.after);
4435
- const installed = await verifyRuntime(ws.root, entry.runtime);
4436
- const config = await inspectHostConfig(ws.root, host, void 0, entry.runtime);
4437
- const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual2({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host, entry.runtime));
4438
- const hooks = isDeepStrictEqual2(config.hooks, hookConfig(host, hookCommand(host, entry.runtime)).hooks) && !config.disabled;
4261
+ const config = await inspectHostConfig(ws.root, host, void 0);
4262
+ const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual2({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host));
4263
+ const hooks = isDeepStrictEqual2(config.hooks, hookConfig(host, hookCommand(host)).hooks) && !config.disabled;
4439
4264
  const local = await loadSetupReceipt(ws.root, ws.directory, host);
4440
4265
  const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
4441
- const observation = await readObservation(ws.root, ws.directory, host, entry.revision);
4266
+ const observation = await readObservation(ws.root, ws.directory, host, entry.revision, installed.version ?? "unavailable");
4442
4267
  const sessions = Object.values(observation?.sessions ?? {}).sort((a, b) => b.at.localeCompare(a.at));
4443
4268
  const complete = sessions.find((session) => events.every((event) => session.events.includes(event)));
4444
4269
  const observedEvents = complete?.events ?? sessions[0]?.events ?? [];
4445
4270
  const pending = [];
4446
- if (!installed || !launcherCurrent) pending.push("Install this checkout's pinned runtime by rerunning setup.");
4271
+ if (!installed.available) pending.push(installed.message);
4447
4272
  if (!mcp || !hooks || !instructionsCurrent || !configured) pending.push("Rerun setup to reconcile project configuration; inspect any disabled host settings.");
4448
4273
  if (!observation?.contextCalls) pending.push("Start a new assistant session and request task context through Mason's get_context tool.");
4449
4274
  if (!complete) pending.push("Review/trust the host configuration, then complete a normal task to observe the full hook lifecycle.");
4450
4275
  if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason-auto check and inspect its diagnostics.");
4451
- const healthy = installed && launcherCurrent && mcp && hooks && instructionsCurrent && configured && automation.status !== "unavailable";
4276
+ const healthy = installed.available && mcp && hooks && instructionsCurrent && configured && automation.status !== "unavailable";
4452
4277
  hosts[host] = {
4453
4278
  status: healthy ? complete && observation?.contextCalls ? "active" : "pending" : "attention",
4454
- runtime: installed && launcherCurrent ? "installed" : "missing-or-changed",
4279
+ runtime: installed.available ? "global (" + installed.version + ")" : "unavailable-on-path",
4455
4280
  mcp: mcp ? "configured" : "changed",
4456
4281
  instructions: instructionsCurrent ? "current" : "changed",
4457
4282
  hookConfiguration: hooks ? "configured" : "disabled-or-changed",
@@ -4497,10 +4322,8 @@ var init_status = __esm({
4497
4322
  init_decisions();
4498
4323
  init_adapters();
4499
4324
  init_model();
4500
- init_runtime2();
4501
4325
  init_config();
4502
4326
  init_launcher();
4503
- init_files2();
4504
4327
  init_observations();
4505
4328
  }
4506
4329
  });
@@ -4512,7 +4335,7 @@ __export(setup_exports, {
4512
4335
  setupProject: () => setupProject,
4513
4336
  summarizeSetup: () => summarizeSetup
4514
4337
  });
4515
- import { randomUUID as randomUUID7 } from "crypto";
4338
+ import { randomUUID as randomUUID6 } from "crypto";
4516
4339
  async function selectHost(root, explicit) {
4517
4340
  if (explicit) return explicit;
4518
4341
  if (process.env.MASON_SETUP_HOST === "codex" || process.env.MASON_SETUP_HOST === "claude") return process.env.MASON_SETUP_HOST;
@@ -4528,28 +4351,27 @@ async function setupProject(dir, options = {}) {
4528
4351
  return withLock(ws.root, ".mason/reports/setup-lock", async () => {
4529
4352
  const existing = await loadSetup(ws.root);
4530
4353
  const previousReceipt = await loadSetupReceipt(ws.root, ws.directory, host);
4531
- const marker = await loadProjectMarker(ws.root);
4532
- const selected = await sourceRuntime();
4354
+ const installed = await installedCommand();
4355
+ if (!installed.available) throw new Error(installed.message);
4533
4356
  const instructions = await instructionEdits(ws.root, host);
4534
- const mcp = await mcpEdit(ws.root, host, selected.runtime);
4535
- const hooks = await hookEdits(ws.root, host, selected.runtime);
4357
+ const mcp = await mcpEdit(ws.root, host);
4358
+ const hooks = await hookEdits(ws.root, host);
4536
4359
  const ancillary = await ancillaryEdits(ws.root);
4537
4360
  const edits = [...ancillary, ...instructions, mcp, ...hooks];
4538
- const setupBefore = await readText(ws.root, ".mason/setup.json");
4539
- const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after, selected.runtime)).mcp);
4540
- const desiredHooks = hookConfig(host, hookCommand(host, selected.runtime)).hooks;
4361
+ const setupBefore = await readText(ws.root, SETUP_PATH);
4362
+ const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after)).mcp);
4363
+ const desiredHooks = hookConfig(host, hookCommand(host)).hooks;
4541
4364
  const fingerprint = hash({
4542
- runtime: selected.runtime,
4543
- launcher: LAUNCHER,
4365
+ command: "mason",
4544
4366
  mcp: mcpFingerprint,
4545
4367
  hooks: desiredHooks,
4546
4368
  instructions: instructions.map((edit) => edit.path)
4547
4369
  });
4548
4370
  const previous = existing?.hosts[host];
4549
4371
  const changed = edits.some((edit) => edit.before !== edit.after) || previous?.fingerprint !== fingerprint;
4550
- const revision = !changed && previous ? previous.revision : randomUUID7();
4551
- const setup = existing ?? { version: 1, hosts: {} };
4552
- setup.hosts[host] = { runtime: selected.runtime, revision, fingerprint, mcpFingerprint, instructions: instructions.map((e) => e.path) };
4372
+ const revision = !changed && previous ? previous.revision : randomUUID6();
4373
+ const setup = existing ?? { version: 2, hosts: {} };
4374
+ setup.hosts[host] = { configuredVersion: installed.version, revision, fingerprint, mcpFingerprint, instructions: instructions.map((e) => e.path) };
4553
4375
  const initial = await automate(ws.root, { event: "turn_start" });
4554
4376
  const findings = await inspectOnboarding(ws.root, options.base, options.evidence);
4555
4377
  const receiptPath = ws.directory + "/setup-" + host + ".json";
@@ -4564,20 +4386,11 @@ async function setupProject(dir, options = {}) {
4564
4386
  root: ws.root,
4565
4387
  revision
4566
4388
  });
4567
- await installRuntime(ws.root, selected);
4568
- if (selected.runtime.bundle) {
4569
- const pointer = `.mason/runtime/${host}.txt`;
4570
- await applyEdit(ws.root, { path: pointer, before: await readText(ws.root, pointer), after: selected.runtime.id + "\n" });
4571
- }
4572
4389
  const changedFiles = [];
4573
4390
  for (const edit of edits) if (await applyEdit(ws.root, edit)) changedFiles.push(edit.path);
4574
- if (await applyEdit(ws.root, { path: ".mason/setup.json", before: setupBefore, after: JSON.stringify(setup, null, 2) + "\n" })) changedFiles.push(".mason/setup.json");
4575
- if (!marker) {
4576
- await saveProjectMarker(ws.root, { version: 1, initializedAt: (/* @__PURE__ */ new Date()).toISOString() });
4577
- changedFiles.push(".mason/project.json");
4578
- }
4391
+ if (await applyEdit(ws.root, { path: SETUP_PATH, before: setupBefore, after: JSON.stringify(setup, null, 2) + "\n" })) changedFiles.push(SETUP_PATH);
4579
4392
  const checked = await automate(ws.root, { event: "turn_start" });
4580
- const configured = await inspectHostConfig(ws.root, host, void 0, selected.runtime);
4393
+ const configured = await inspectHostConfig(ws.root, host, void 0);
4581
4394
  await writeStoreJson(ws.root, receiptPath, {
4582
4395
  version: 1,
4583
4396
  host,
@@ -4594,7 +4407,7 @@ async function setupProject(dir, options = {}) {
4594
4407
  status: "configured",
4595
4408
  host,
4596
4409
  root: ws.root,
4597
- runtime: selected.runtime,
4410
+ runtime: { kind: "global", command: "mason", version: installed.version },
4598
4411
  changedFiles,
4599
4412
  initialReportPath,
4600
4413
  findings,
@@ -4608,14 +4421,15 @@ function summarizeSetup(result) {
4608
4421
  const audit = result.findings.audit;
4609
4422
  return [
4610
4423
  `Mason configured for ${result.host}.`,
4611
- " Runtime installed; MCP server and lifecycle hooks configured.",
4424
+ " MCP server and lifecycle hooks use mason from PATH.",
4612
4425
  " Project instructions updated; original audit evidence retained.",
4613
- ` ${result.changedFiles.length} shared files changed.`,
4426
+ ` ${result.changedFiles.length} files changed.`,
4614
4427
  `Initial audit: ${audit.status}${"counts" in audit ? `; ${audit.counts.issues} issues, ${audit.counts.advisories + audit.counts.suppressedAdvisories} advisories` : ""}.`,
4615
4428
  ..."issues" in audit ? audit.issues.slice(0, 3).map((f) => " " + f.message) : [],
4616
4429
  `Original evidence: ${result.initialReportPath}`,
4617
4430
  "Activation: " + result.activation.status + ".",
4618
4431
  result.next,
4432
+ "If the assistant cannot find mason, restart the desktop app so it picks up PATH.",
4619
4433
  "After the task finishes, run mason-auto status. Configuration alone does not establish activation."
4620
4434
  ].join("\n");
4621
4435
  }
@@ -4625,7 +4439,6 @@ var init_setup = __esm({
4625
4439
  init_evidence();
4626
4440
  init_store();
4627
4441
  init_runtime();
4628
- init_init();
4629
4442
  init_onboarding();
4630
4443
  init_storage();
4631
4444
  init_files2();
@@ -4633,7 +4446,6 @@ var init_setup = __esm({
4633
4446
  init_launcher();
4634
4447
  init_adapters();
4635
4448
  init_model();
4636
- init_runtime2();
4637
4449
  init_status();
4638
4450
  }
4639
4451
  });
@@ -4647,7 +4459,7 @@ init_store();
4647
4459
  import { parseArgs } from "util";
4648
4460
  var USAGE = `Usage: mason-auto <setup|install|config|status|check|hook> [options]
4649
4461
 
4650
- setup [--host claude|codex] Install a pinned runtime, MCP, instructions and hooks; retain the initial audit
4462
+ setup [--host claude|codex] Connect MCP, instructions and hooks to mason on PATH; retain the initial audit
4651
4463
  install --host claude|codex Merge lifecycle hooks into this project's host config
4652
4464
  config --host claude|codex Print the host config without writing
4653
4465
  status Read configured hooks and observed runtime events