mason-context 0.13.0 → 0.14.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.
@@ -2398,7 +2398,7 @@ var init_evidence = __esm({
2398
2398
  init_dead_command();
2399
2399
  init_storage();
2400
2400
  exec7 = promisify7(execFile7);
2401
- engineVersion = true ? "0.13.0" : "development";
2401
+ engineVersion = true ? "0.14.0" : "development";
2402
2402
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
2403
2403
  internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
2404
2404
  cacheSchema = z5.object({ version: z5.literal(1), entries: z5.record(z5.object({ key: z5.string(), result: checkResultSchema })), digest: z5.string() });
@@ -2747,7 +2747,8 @@ var init_model = __esm({
2747
2747
  runtimeSchema = z7.object({
2748
2748
  id: z7.string().regex(/^[a-f0-9]{24}$/),
2749
2749
  version: z7.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
2750
- hashes: z7.record(z7.string().regex(/^[a-f0-9]{64}$/))
2750
+ hashes: z7.record(z7.string().regex(/^[a-f0-9]{64}$/)),
2751
+ bundle: z7.object({ target: z7.string(), manifestHash: z7.string().regex(/^[a-f0-9]{64}$/) }).optional()
2751
2752
  });
2752
2753
  setupHostSchema = z7.object({
2753
2754
  runtime: runtimeSchema,
@@ -3905,18 +3906,55 @@ var init_files2 = __esm({
3905
3906
  });
3906
3907
 
3907
3908
  // src/setup/launcher.ts
3908
- var BOOTSTRAP, mcpCommand, hookCommand, LAUNCHER;
3909
+ var BOOTSTRAP, POWERSHELL_BOOTSTRAP, mcpCommand, hookCommand, SHELL_LAUNCHER, POWERSHELL_LAUNCHER, LAUNCHER;
3909
3910
  var init_launcher = __esm({
3910
3911
  "src/setup/launcher.ts"() {
3911
3912
  "use strict";
3912
3913
  BOOTSTRAP = "require(require('node:path').join(require('node:child_process').execFileSync('git',['rev-parse','--show-toplevel'],{encoding:'utf8'}).trim(),'.mason','run.cjs'))";
3913
- mcpCommand = (host) => ({ command: "node", args: ["-e", BOOTSTRAP, "--", host, "mcp"] });
3914
- hookCommand = (host) => `node -e "${BOOTSTRAP}" -- ${host} auto`;
3914
+ POWERSHELL_BOOTSTRAP = "& (Join-Path (git rev-parse --show-toplevel) '.mason/run.ps1')";
3915
+ mcpCommand = (host, runtime) => !runtime?.bundle ? { command: "node", args: ["-e", BOOTSTRAP, "--", host, "mcp"] } : process.platform === "win32" ? { command: "powershell.exe", args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", POWERSHELL_BOOTSTRAP, host, "mcp"] } : { command: "sh", args: ["-c", 'exec sh "$(git rev-parse --show-toplevel)/.mason/run.sh" "$@"', "mason", host, "mcp"] };
3916
+ hookCommand = (host, runtime) => !runtime?.bundle ? `node -e "${BOOTSTRAP}" -- ${host} auto` : process.platform === "win32" ? `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "${POWERSHELL_BOOTSTRAP} ${host} auto"` : `sh "$(git rev-parse --show-toplevel)/.mason/run.sh" ${host} auto`;
3917
+ SHELL_LAUNCHER = `#!/bin/sh
3918
+ set -eu
3919
+ advisory=0
3920
+ if [ "\${2-}" = auto ] && [ "\${3-}" = hook ]; then advisory=1; fi
3921
+ fail() {
3922
+ if [ "$advisory" -eq 1 ]; then printf '%s\\n' '{"systemMessage":"Mason runtime unavailable. Rerun mason setup for this checkout; verification was not established."}'; exit 0; fi
3923
+ echo 'Mason runtime unavailable. Rerun mason setup for this checkout.' >&2; exit 2
3924
+ }
3925
+ root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) || fail
3926
+ case "\${1-}" in codex|claude) ;; *) fail ;; esac
3927
+ pointer="$root/.mason/runtime/$1.txt"
3928
+ [ -f "$pointer" ] || fail
3929
+ runtime=$(cat "$pointer") || fail
3930
+ case "$runtime" in ''|*[!0-9a-f]*) fail ;; esac
3931
+ [ "\${#runtime}" -eq 24 ] || fail
3932
+ [ -x "$root/.mason/runtime/$runtime/node" ] || fail
3933
+ if [ "$advisory" -eq 1 ]; then
3934
+ "$root/.mason/runtime/$runtime/node" "$root/.mason/run.cjs" "$@" || fail
3935
+ else
3936
+ exec "$root/.mason/runtime/$runtime/node" "$root/.mason/run.cjs" "$@"
3937
+ fi
3938
+ `;
3939
+ POWERSHELL_LAUNCHER = `# Managed by mason setup; no system Node installation is used.
3940
+ $ErrorActionPreference = 'Stop'
3941
+ $advisory = $args.Count -ge 3 -and $args[1] -eq 'auto' -and $args[2] -eq 'hook'
3942
+ try {
3943
+ if ($args.Count -lt 2 -or $args[0] -notin @('codex','claude')) { throw 'Invalid Mason host.' }
3944
+ $runtime = (Get-Content -LiteralPath (Join-Path $PSScriptRoot ('runtime/' + $args[0] + '.txt')) -Raw).Trim()
3945
+ if ($runtime -cnotmatch '^[a-f0-9]{24}$') { throw 'Invalid Mason runtime pointer.' }
3946
+ & (Join-Path $PSScriptRoot ('runtime/' + $runtime + '/node.exe')) (Join-Path $PSScriptRoot 'run.cjs') @args
3947
+ if ($LASTEXITCODE -ne 0) { throw 'Mason runtime failed.' }
3948
+ } catch {
3949
+ if ($advisory) { Write-Output '{"systemMessage":"Mason runtime unavailable. Rerun mason setup for this checkout; verification was not established."}'; exit 0 }
3950
+ [Console]::Error.WriteLine($_.Exception.Message); exit 2
3951
+ }
3952
+ `;
3915
3953
  LAUNCHER = `// Managed by mason-auto setup. Rerun setup to install this checkout's pinned runtime.
3916
3954
  const fs = require('node:fs');
3917
3955
  const path = require('node:path');
3918
3956
  const {pathToFileURL} = require('node:url');
3919
- const launchArgs = process.argv.slice(1);
3957
+ const launchArgs = process.argv.slice(process.argv[1] && path.resolve(process.argv[1]) === __filename ? 2 : 1);
3920
3958
  (async () => {
3921
3959
  const root = path.dirname(__dirname);
3922
3960
  const args = launchArgs;
@@ -3925,7 +3963,13 @@ const launchArgs = process.argv.slice(1);
3925
3963
  const setup = JSON.parse(fs.readFileSync(path.join(__dirname, 'setup.json'), 'utf8'));
3926
3964
  const entry = setup.hosts?.[host];
3927
3965
  if (setup.version !== 1 || !entry || !/^[a-f0-9]{24}$/.test(entry.runtime?.id)) throw new Error('Mason is not configured for this host. Rerun mason-auto setup.');
3928
- const binary = path.join(__dirname, 'runtime', entry.runtime.id, 'node_modules', 'mason-context', 'dist', 'mason-' + command + '.js');
3966
+ const base = path.join(__dirname, 'runtime', entry.runtime.id);
3967
+ if (entry.runtime.bundle) {
3968
+ const node = process.platform === 'win32' ? 'node.exe' : 'node';
3969
+ if (fs.realpathSync(process.execPath) !== fs.realpathSync(path.join(base, node))) throw new Error('Mason runtime pointer differs from the configured version. Rerun setup.');
3970
+ }
3971
+ const binary = entry.runtime.bundle ? path.join(base, 'app', 'dist', 'mason-' + command + '.js')
3972
+ : path.join(base, 'node_modules', 'mason-context', 'dist', 'mason-' + command + '.js');
3929
3973
  if (!fs.existsSync(binary)) throw new Error('The pinned Mason runtime is missing in this checkout. Rerun mason-auto setup --host ' + host + '.');
3930
3974
  process.env.MASON_SETUP_ROOT = root;
3931
3975
  process.env.MASON_SETUP_HOST = host;
@@ -3973,22 +4017,22 @@ async function instructionEdits(root, host) {
3973
4017
  }
3974
4018
  return edits;
3975
4019
  }
3976
- function managedMcp(previous, host) {
4020
+ function managedMcp(previous, host, runtime) {
3977
4021
  const options = previous === void 0 ? {} : object.parse(previous);
3978
4022
  for (const key of ["command", "args", "url", "type", "headers", "http_headers", "env_http_headers", "bearer_token_env_var"]) delete options[key];
3979
- return { ...options, ...mcpCommand(host) };
4023
+ return { ...options, ...mcpCommand(host, runtime) };
3980
4024
  }
3981
- async function mcpEdit(root, host) {
4025
+ async function mcpEdit(root, host, runtime) {
3982
4026
  const file = mcpPath(host), before = await readText(root, file);
3983
4027
  if (host === "claude") {
3984
4028
  const config2 = before === null ? {} : object.parse(JSON.parse(before));
3985
4029
  const servers2 = object.parse(config2.mcpServers ?? {});
3986
- return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host) } }, null, 2) + "\n" };
4030
+ return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host, runtime) } }, null, 2) + "\n" };
3987
4031
  }
3988
4032
  const config = parse(before ?? "");
3989
4033
  const managed = (before ?? "").includes(TOML_START);
3990
4034
  const servers = config.mcp_servers;
3991
- const desired = managedMcp(servers?.mason, host);
4035
+ const desired = managedMcp(servers?.mason, host, runtime);
3992
4036
  let base = before ?? "";
3993
4037
  if (servers?.mason && !managed) {
3994
4038
  const headers = [...base.matchAll(/^\s*\[\[?.+?\]\]?[^\S\r\n]*(?:#.*)?$/gm)];
@@ -4002,10 +4046,10 @@ async function mcpEdit(root, host) {
4002
4046
  if (!isDeepStrictEqual(parsed, expected)) throw new Error("Could not safely configure the Mason MCP entry without changing other settings.");
4003
4047
  return { path: file, before, after };
4004
4048
  }
4005
- async function hookEdits(root, host) {
4049
+ async function hookEdits(root, host, runtime) {
4006
4050
  const file = configPath(host), before = await readText(root, file);
4007
4051
  const { planAutomationInstall: planAutomationInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
4008
- const plan = await planAutomationInstall2(root, host, hookCommand(host));
4052
+ const plan = await planAutomationInstall2(root, host, hookCommand(host, runtime));
4009
4053
  return [
4010
4054
  { path: file, before, after: JSON.stringify(plan.config, null, 2) + "\n" },
4011
4055
  { path: ".mason/automation.json", before: await readText(root, ".mason/automation.json"), after: JSON.stringify(plan.record, null, 2) + "\n" }
@@ -4021,18 +4065,20 @@ async function ancillaryEdits(root) {
4021
4065
  if (error.code !== 1) throw error;
4022
4066
  }
4023
4067
  const retainParentRule = parentIgnored || !!ignore?.replace(/\r\n/g, "\n").includes("# mason:ignore:start\n!/.mason/\n/.mason/*");
4024
- const rules = [...retainParentRule ? ["!/.mason/", "/.mason/*"] : [], "!/.mason/decisions/", "!/.mason/decisions/**", "!/.mason/setup.json", "!/.mason/automation.json", "!/.mason/project.json", "!/.mason/run.cjs", "/.mason/reports/", "/.mason/runtime/"].join("\n");
4068
+ const rules = [...retainParentRule ? ["!/.mason/", "/.mason/*"] : [], "!/.mason/decisions/", "!/.mason/decisions/**", "!/.mason/setup.json", "!/.mason/automation.json", "!/.mason/project.json", "!/.mason/run.cjs", "!/.mason/run.sh", "!/.mason/run.ps1", "/.mason/reports/", "/.mason/runtime/"].join("\n");
4025
4069
  return [
4026
4070
  { path: ".gitignore", before: ignore, after: managedBlock(ignore ?? "", "# mason:ignore:start", "# mason:ignore:end", rules) },
4027
- { path: ".mason/run.cjs", before: await readText(root, ".mason/run.cjs"), after: LAUNCHER }
4071
+ { path: ".mason/run.cjs", before: await readText(root, ".mason/run.cjs"), after: LAUNCHER },
4072
+ { path: ".mason/run.sh", before: await readText(root, ".mason/run.sh"), after: SHELL_LAUNCHER },
4073
+ { path: ".mason/run.ps1", before: await readText(root, ".mason/run.ps1"), after: POWERSHELL_LAUNCHER }
4028
4074
  ];
4029
4075
  }
4030
- async function inspectHostConfig(root, host, plannedMcp) {
4076
+ async function inspectHostConfig(root, host, plannedMcp, runtime) {
4031
4077
  const text2 = plannedMcp ?? await readText(root, mcpPath(host));
4032
4078
  const config = host === "codex" ? parse(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
4033
4079
  const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
4034
4080
  const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
4035
- const expected = hookConfig(host, hookCommand(host));
4081
+ const expected = hookConfig(host, hookCommand(host, runtime));
4036
4082
  const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
4037
4083
  return {
4038
4084
  mcp,
@@ -4063,40 +4109,109 @@ var init_config = __esm({
4063
4109
  }
4064
4110
  });
4065
4111
 
4066
- // src/setup/runtime.ts
4112
+ // src/distribution/bundle.ts
4067
4113
  import fs16 from "fs/promises";
4068
4114
  import path21 from "path";
4115
+ import { createHash as createHash4 } from "crypto";
4116
+ import { z as z16 } from "zod";
4117
+ async function readBundle(root) {
4118
+ const bytes = await fs16.readFile(await storePath(root, "bundle.json"));
4119
+ const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
4120
+ const windows = manifest.target.startsWith("win32-");
4121
+ for (const file of Object.keys(manifest.files)) {
4122
+ if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path21.isAbsolute(file)) {
4123
+ throw new Error("Unsafe path in Mason bundle: " + file);
4124
+ }
4125
+ }
4126
+ for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-auto.js", "app/dist/mason-mcp.js"]) {
4127
+ if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
4128
+ }
4129
+ return { manifest, manifestHash: sha256(bytes) };
4130
+ }
4131
+ async function verifyBundle(root, expectedHash) {
4132
+ const bundle = await readBundle(root);
4133
+ if (expectedHash && bundle.manifestHash !== expectedHash) throw new Error("Mason bundle manifest changed.");
4134
+ if (bundle.manifest.target !== `${process.platform}-${process.arch}`) throw new Error("Mason bundle is for another platform. Install this platform's bundle and rerun setup.");
4135
+ const files = Object.keys(bundle.manifest.files);
4136
+ for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
4137
+ if (sha256(await fs16.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
4138
+ }));
4139
+ return bundle;
4140
+ }
4141
+ var targets, digest2, bundleSchema, sha256;
4142
+ var init_bundle = __esm({
4143
+ "src/distribution/bundle.ts"() {
4144
+ "use strict";
4145
+ init_storage();
4146
+ targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
4147
+ digest2 = z16.string().regex(/^[a-f0-9]{64}$/);
4148
+ bundleSchema = z16.object({
4149
+ format: z16.literal(1),
4150
+ version: z16.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
4151
+ target: z16.enum(targets),
4152
+ nodeVersion: z16.string(),
4153
+ files: z16.record(digest2)
4154
+ }).strict();
4155
+ sha256 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
4156
+ }
4157
+ });
4158
+
4159
+ // src/setup/runtime.ts
4160
+ import fs17 from "fs/promises";
4161
+ import path22 from "path";
4069
4162
  import { fileURLToPath } from "url";
4070
- import { randomUUID as randomUUID6, createHash as createHash4 } from "crypto";
4163
+ import { randomUUID as randomUUID6, createHash as createHash5 } from "crypto";
4071
4164
  import { execFile as execFile10 } from "child_process";
4072
4165
  import { promisify as promisify10 } from "util";
4073
4166
  async function packageRoot() {
4074
- let directory = path21.dirname(fileURLToPath(import.meta.url));
4167
+ let directory = path22.dirname(fileURLToPath(import.meta.url));
4075
4168
  for (let i = 0; i < 8; i++) {
4076
4169
  try {
4077
- const pkg = JSON.parse(await fs16.readFile(path21.join(directory, "package.json"), "utf8"));
4170
+ const pkg = JSON.parse(await fs17.readFile(path22.join(directory, "package.json"), "utf8"));
4078
4171
  if (pkg.name === "mason-context") return directory;
4079
4172
  } catch {
4080
4173
  }
4081
- directory = path21.dirname(directory);
4174
+ directory = path22.dirname(directory);
4082
4175
  }
4083
4176
  throw new Error("Cannot locate the executing Mason distribution. Reinstall Mason and rerun setup.");
4084
4177
  }
4085
4178
  async function sourceRuntime() {
4086
4179
  const source = await packageRoot();
4087
- const pkg = JSON.parse(await fs16.readFile(path21.join(source, "package.json"), "utf8"));
4088
- const hashes = Object.fromEntries(await Promise.all(BINARIES.map(async (file) => [file, checksum(await fs16.readFile(path21.join(source, file)))])));
4180
+ const pkg = JSON.parse(await fs17.readFile(path22.join(source, "package.json"), "utf8"));
4181
+ const hashes = Object.fromEntries(await Promise.all(BINARIES.map(async (file) => [file, checksum(await fs17.readFile(path22.join(source, file)))])));
4182
+ const bundleRoot = path22.dirname(source);
4183
+ const bundleExists = await fs17.lstat(path22.join(bundleRoot, "bundle.json")).then(() => true, (error) => {
4184
+ if (error.code === "ENOENT") return false;
4185
+ throw error;
4186
+ });
4187
+ if (bundleExists) {
4188
+ const { manifest, manifestHash } = await verifyBundle(bundleRoot);
4189
+ if (manifest.version !== pkg.version) throw new Error("Mason package and bundle versions differ.");
4190
+ return { source, bundleRoot, runtime: runtimeSchema.parse({
4191
+ id: manifestHash.slice(0, 24),
4192
+ version: pkg.version,
4193
+ hashes,
4194
+ bundle: { target: manifest.target, manifestHash }
4195
+ }) };
4196
+ }
4089
4197
  return { source, runtime: runtimeSchema.parse({ id: hash([pkg.version, pkg.dependencies, hashes]).slice(0, 24), version: pkg.version, hashes }) };
4090
4198
  }
4091
4199
  async function verifyRuntime(root, runtime) {
4092
4200
  try {
4093
4201
  const saved = await readStoreJson(root, `.mason/runtime/${runtime.id}/receipt.json`);
4094
4202
  if (JSON.stringify(saved) !== JSON.stringify(runtime)) return false;
4203
+ if (runtime.bundle) {
4204
+ const base2 = await storePath(root, `.mason/runtime/${runtime.id}`);
4205
+ const { manifest } = await verifyBundle(base2, runtime.bundle.manifestHash);
4206
+ if (manifest.version !== runtime.version || manifest.target !== runtime.bundle.target) return false;
4207
+ for (const file of BINARIES) if (manifest.files["app/" + file] !== runtime.hashes[file]) return false;
4208
+ return true;
4209
+ }
4095
4210
  const base = `.mason/runtime/${runtime.id}/node_modules/mason-context/`;
4096
4211
  const pkg = await readStoreJson(root, base + "package.json");
4097
4212
  if (pkg?.name !== "mason-context" || pkg.version !== runtime.version) return false;
4098
4213
  for (const file of BINARIES) {
4099
- if (checksum(await fs16.readFile(await storePath(root, base + file))) !== runtime.hashes[file]) return false;
4214
+ if (checksum(await fs17.readFile(await storePath(root, base + file))) !== runtime.hashes[file]) return false;
4100
4215
  }
4101
4216
  return true;
4102
4217
  } catch {
@@ -4109,30 +4224,42 @@ async function installRuntime(root, selected) {
4109
4224
  const relative = `.mason/runtime/${runtime.id}`;
4110
4225
  const target = await storePath(root, relative, true);
4111
4226
  const stage = await storePath(root, ".mason/runtime/.install-" + randomUUID6(), true);
4112
- await fs16.mkdir(stage);
4227
+ await fs17.mkdir(stage);
4113
4228
  try {
4114
- const npm = process.platform === "win32" ? "npm.cmd" : "npm";
4115
- const options = { timeout: 12e4, maxBuffer: 2 * 1024 * 1024, windowsHide: true };
4116
- const packed = JSON.parse((await exec10(npm, ["pack", "--ignore-scripts", "--json", "--pack-destination", stage], { ...options, cwd: source })).stdout);
4117
- const filename = packed[0]?.filename;
4118
- if (typeof filename !== "string" || path21.basename(filename) !== filename) throw new Error("npm did not produce a Mason package archive.");
4119
- await fs16.rename(path21.join(stage, filename), path21.join(stage, "mason.tgz"));
4120
- await fs16.writeFile(path21.join(stage, "package.json"), JSON.stringify({ name: "mason-project-runtime", private: true, version: "1.0.0" }));
4121
- await exec10(npm, ["install", "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund", "--save-exact", "./mason.tgz"], { ...options, cwd: stage });
4122
- for (const file of BINARIES) {
4123
- if (checksum(await fs16.readFile(path21.join(stage, "node_modules/mason-context", file))) !== runtime.hashes[file]) throw new Error("Installed Mason binary differs from the selected distribution.");
4229
+ if (runtime.bundle) {
4230
+ if (!selected.bundleRoot) throw new Error("Standalone bundle source is missing.");
4231
+ const { manifest } = await verifyBundle(selected.bundleRoot, runtime.bundle.manifestHash);
4232
+ for (const file of ["bundle.json", ...Object.keys(manifest.files)]) {
4233
+ const from = await storePath(selected.bundleRoot, file);
4234
+ const to = await storePath(stage, file, true);
4235
+ await fs17.copyFile(from, to);
4236
+ await fs17.chmod(to, (await fs17.stat(from)).mode & 511);
4237
+ }
4238
+ await verifyBundle(stage, runtime.bundle.manifestHash);
4239
+ } else {
4240
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
4241
+ const options = { timeout: 12e4, maxBuffer: 2 * 1024 * 1024, windowsHide: true };
4242
+ const packed = JSON.parse((await exec10(npm, ["pack", "--ignore-scripts", "--json", "--pack-destination", stage], { ...options, cwd: source })).stdout);
4243
+ const filename = packed[0]?.filename;
4244
+ if (typeof filename !== "string" || path22.basename(filename) !== filename) throw new Error("npm did not produce a Mason package archive.");
4245
+ await fs17.rename(path22.join(stage, filename), path22.join(stage, "mason.tgz"));
4246
+ await fs17.writeFile(path22.join(stage, "package.json"), JSON.stringify({ name: "mason-project-runtime", private: true, version: "1.0.0" }));
4247
+ await exec10(npm, ["install", "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund", "--save-exact", "./mason.tgz"], { ...options, cwd: stage });
4248
+ for (const file of BINARIES) {
4249
+ 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.");
4250
+ }
4124
4251
  }
4125
- await fs16.writeFile(path21.join(stage, "receipt.json"), JSON.stringify(runtime, null, 2) + "\n");
4252
+ await fs17.writeFile(path22.join(stage, "receipt.json"), JSON.stringify(runtime, null, 2) + "\n");
4126
4253
  try {
4127
- await fs16.rename(target, target + ".previous-" + randomUUID6());
4254
+ await fs17.rename(target, target + ".previous-" + randomUUID6());
4128
4255
  } catch (error) {
4129
4256
  if (error.code !== "ENOENT") throw error;
4130
4257
  }
4131
- await fs16.rename(stage, target);
4258
+ await fs17.rename(stage, target);
4132
4259
  if (!await verifyRuntime(root, runtime)) throw new Error("The installed Mason runtime could not be verified.");
4133
4260
  return runtime;
4134
4261
  } finally {
4135
- await fs16.rm(stage, { recursive: true, force: true });
4262
+ await fs17.rm(stage, { recursive: true, force: true });
4136
4263
  }
4137
4264
  }
4138
4265
  var exec10, checksum, BINARIES;
@@ -4142,8 +4269,9 @@ var init_runtime2 = __esm({
4142
4269
  init_model();
4143
4270
  init_storage();
4144
4271
  init_evidence();
4272
+ init_bundle();
4145
4273
  exec10 = promisify10(execFile10);
4146
- checksum = (bytes) => createHash4("sha256").update(bytes).digest("hex");
4274
+ checksum = (bytes) => createHash5("sha256").update(bytes).digest("hex");
4147
4275
  BINARIES = ["dist/mason-auto.js", "dist/mason-mcp.js"];
4148
4276
  }
4149
4277
  });
@@ -4172,17 +4300,18 @@ async function setupStatus(dir) {
4172
4300
  };
4173
4301
  }
4174
4302
  const hosts = {};
4175
- const launcherCurrent = await readText(ws.root, ".mason/run.cjs") === LAUNCHER;
4303
+ const commonLauncherCurrent = await readText(ws.root, ".mason/run.cjs") === LAUNCHER;
4176
4304
  const automation = await automationStatus(ws.root);
4177
4305
  for (const host of ["codex", "claude"]) {
4178
4306
  const entry = setup.hosts[host];
4179
4307
  if (!entry) continue;
4308
+ 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");
4180
4309
  const instructions = await instructionEdits(ws.root, host);
4181
4310
  const instructionsCurrent = instructions.every((edit) => edit.before === edit.after);
4182
4311
  const installed = await verifyRuntime(ws.root, entry.runtime);
4183
- const config = await inspectHostConfig(ws.root, host);
4184
- const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual2({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host));
4185
- const hooks = isDeepStrictEqual2(config.hooks, hookConfig(host, hookCommand(host)).hooks) && !config.disabled;
4312
+ const config = await inspectHostConfig(ws.root, host, void 0, entry.runtime);
4313
+ const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual2({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host, entry.runtime));
4314
+ const hooks = isDeepStrictEqual2(config.hooks, hookConfig(host, hookCommand(host, entry.runtime)).hooks) && !config.disabled;
4186
4315
  const local = await loadSetupReceipt(ws.root, ws.directory, host);
4187
4316
  const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
4188
4317
  const observation = await readObservation(ws.root, ws.directory, host, entry.revision);
@@ -4276,15 +4405,15 @@ async function setupProject(dir, options = {}) {
4276
4405
  const existing = await loadSetup(ws.root);
4277
4406
  const previousReceipt = await loadSetupReceipt(ws.root, ws.directory, host);
4278
4407
  const marker = await loadProjectMarker(ws.root);
4408
+ const selected = await sourceRuntime();
4279
4409
  const instructions = await instructionEdits(ws.root, host);
4280
- const mcp = await mcpEdit(ws.root, host);
4281
- const hooks = await hookEdits(ws.root, host);
4410
+ const mcp = await mcpEdit(ws.root, host, selected.runtime);
4411
+ const hooks = await hookEdits(ws.root, host, selected.runtime);
4282
4412
  const ancillary = await ancillaryEdits(ws.root);
4283
4413
  const edits = [...ancillary, ...instructions, mcp, ...hooks];
4284
4414
  const setupBefore = await readText(ws.root, ".mason/setup.json");
4285
- const selected = await sourceRuntime();
4286
- const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after)).mcp);
4287
- const desiredHooks = hookConfig(host, hookCommand(host)).hooks;
4415
+ const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after, selected.runtime)).mcp);
4416
+ const desiredHooks = hookConfig(host, hookCommand(host, selected.runtime)).hooks;
4288
4417
  const fingerprint = hash({
4289
4418
  runtime: selected.runtime,
4290
4419
  launcher: LAUNCHER,
@@ -4312,6 +4441,10 @@ async function setupProject(dir, options = {}) {
4312
4441
  revision
4313
4442
  });
4314
4443
  await installRuntime(ws.root, selected);
4444
+ if (selected.runtime.bundle) {
4445
+ const pointer = `.mason/runtime/${host}.txt`;
4446
+ await applyEdit(ws.root, { path: pointer, before: await readText(ws.root, pointer), after: selected.runtime.id + "\n" });
4447
+ }
4315
4448
  const changedFiles = [];
4316
4449
  for (const edit of edits) if (await applyEdit(ws.root, edit)) changedFiles.push(edit.path);
4317
4450
  if (await applyEdit(ws.root, { path: ".mason/setup.json", before: setupBefore, after: JSON.stringify(setup, null, 2) + "\n" })) changedFiles.push(".mason/setup.json");
@@ -4320,7 +4453,7 @@ async function setupProject(dir, options = {}) {
4320
4453
  changedFiles.push(".mason/project.json");
4321
4454
  }
4322
4455
  const checked = await automate(ws.root, { event: "turn_start" });
4323
- const configured = await inspectHostConfig(ws.root, host);
4456
+ const configured = await inspectHostConfig(ws.root, host, void 0, selected.runtime);
4324
4457
  await writeStoreJson(ws.root, receiptPath, {
4325
4458
  version: 1,
4326
4459
  host,