mason-context 0.17.0 → 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mason.js CHANGED
@@ -844,7 +844,7 @@ async function readAuditInput(root, file) {
844
844
  if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
845
845
  return value;
846
846
  } catch (error) {
847
- if (error.code === "ENOENT") return null;
847
+ if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return null;
848
848
  throw error;
849
849
  }
850
850
  }
@@ -1094,15 +1094,17 @@ function pathClaimScope(doc, claim) {
1094
1094
  };
1095
1095
  }
1096
1096
  async function pathExists(root, file) {
1097
- const absolute = await auditInputPath(root, file);
1098
1097
  try {
1099
- await fs6.access(absolute);
1098
+ await fs6.access(await auditInputPath(root, file));
1100
1099
  return true;
1101
1100
  } catch (error) {
1102
- if (error.code === "ENOENT") return false;
1101
+ if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return false;
1103
1102
  throw error;
1104
1103
  }
1105
1104
  }
1105
+ function gitMetadataPath(file) {
1106
+ return file.split("/").includes(".git");
1107
+ }
1106
1108
  function optionalMasonPath(file) {
1107
1109
  return file.split("/").includes(".mason");
1108
1110
  }
@@ -1341,6 +1343,14 @@ async function checkDeletedReferences(ctx) {
1341
1343
  continue;
1342
1344
  }
1343
1345
  if (scope.candidates.some(optionalMasonPath)) continue;
1346
+ if (scope.candidates.some(gitMetadataPath)) {
1347
+ result.skipped.push({
1348
+ check: "deleted-reference",
1349
+ doc: doc.path,
1350
+ reason: `Reference ${claim.path} names Git metadata, whose layout varies between checkouts; it was not verified as a repository file.`
1351
+ });
1352
+ continue;
1353
+ }
1344
1354
  if ((await Promise.all(scope.candidates.map((file) => pathExists(ctx.root, file)))).some(Boolean)) continue;
1345
1355
  const candidates = await Promise.all(scope.candidates.map(async (file) => ({
1346
1356
  file,
@@ -2860,14 +2870,6 @@ var init_repair = __esm({
2860
2870
  });
2861
2871
 
2862
2872
  // src/automation/evidence.ts
2863
- var evidence_exports = {};
2864
- __export(evidence_exports, {
2865
- checkCache: () => checkCache,
2866
- git: () => git3,
2867
- hash: () => hash,
2868
- readInputs: () => readInputs,
2869
- workspace: () => workspace
2870
- });
2871
2873
  import fs13 from "fs/promises";
2872
2874
  import path20 from "path";
2873
2875
  import { createHash as createHash3 } from "crypto";
@@ -2913,6 +2915,7 @@ async function readInputs(root) {
2913
2915
  docContents.push([file, text2]);
2914
2916
  for (const claim of text2 ? extractClaims(text2).paths : []) {
2915
2917
  const scope = pathClaimScope(file, claim);
2918
+ if (scope?.candidates.some(gitMetadataPath)) continue;
2916
2919
  for (const candidate of scope?.candidates ?? []) {
2917
2920
  if (optionalMasonPath(candidate)) continue;
2918
2921
  claims.push([candidate, await pathExists(root, candidate), await pathExists(root, path20.posix.dirname(candidate))]);
@@ -2993,7 +2996,7 @@ var init_evidence = __esm({
2993
2996
  init_storage();
2994
2997
  init_advisory_review();
2995
2998
  exec7 = promisify9(execFile9);
2996
- engineVersion = true ? "0.17.0" : "development";
2999
+ engineVersion = true ? "0.17.2" : "development";
2997
3000
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
2998
3001
  content = readAuditInput;
2999
3002
  cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
@@ -3514,6 +3517,39 @@ var init_runtime = __esm({
3514
3517
  }
3515
3518
  });
3516
3519
 
3520
+ // src/automation/hook-root.ts
3521
+ import fs15 from "fs/promises";
3522
+ import path21 from "path";
3523
+ async function repositoryRoot(dir) {
3524
+ if (!path21.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
3525
+ try {
3526
+ return await fs15.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
3527
+ } catch (error) {
3528
+ if (error.code === "ENOENT" || /not a git repository/.test(String(error.stderr))) return null;
3529
+ throw error;
3530
+ }
3531
+ }
3532
+ async function resolveHookRoot(host, cwd, options = {}) {
3533
+ const current = await repositoryRoot(cwd);
3534
+ if (current) return current;
3535
+ const projectDir = options.projectDir ?? (host === "claude" ? process.env.CLAUDE_PROJECT_DIR : void 0);
3536
+ const projectRoot = projectDir ? await repositoryRoot(projectDir) : null;
3537
+ if (projectDir && !projectRoot) throw new Error("Hook input is outside Git and the host project is no longer a Git repository. Restore its history before running mason check.");
3538
+ const launchRoot = await repositoryRoot(options.launchCwd ?? process.cwd());
3539
+ const roots = [...new Set([launchRoot, projectRoot].filter((root) => root !== null))];
3540
+ if (!roots.length) throw new Error("Hook input is outside a Git repository and no project root is available. Return to the project and run mason check.");
3541
+ if (roots.length > 1) throw new Error("Hook input is outside a Git repository and project roots disagree. Return to the intended checkout and run mason check; verification was not established.");
3542
+ const worktrees = (await git3(roots[0], "worktree", "list", "--porcelain", "-z")).split("\0").filter((field) => field.startsWith("worktree "));
3543
+ if (worktrees.length !== 1) throw new Error("Hook input is outside a Git repository with multiple worktrees. Return to the intended worktree and run mason check; the active checkout is unknown.");
3544
+ return roots[0];
3545
+ }
3546
+ var init_hook_root = __esm({
3547
+ "src/automation/hook-root.ts"() {
3548
+ "use strict";
3549
+ init_evidence();
3550
+ }
3551
+ });
3552
+
3517
3553
  // src/setup/model.ts
3518
3554
  import { z as z11 } from "zod";
3519
3555
  async function loadSetup(root) {
@@ -3564,13 +3600,14 @@ __export(launcher_exports, {
3564
3600
  executingVersion: () => executingVersion,
3565
3601
  globalCommand: () => globalCommand,
3566
3602
  hookCommand: () => hookCommand,
3603
+ inactiveSetupMessage: () => inactiveSetupMessage,
3567
3604
  installedCommand: () => installedCommand,
3568
3605
  mcpCommand: () => mcpCommand,
3569
3606
  prepareLaunch: () => prepareLaunch
3570
3607
  });
3571
3608
  import { execFile as execFile10 } from "child_process";
3572
3609
  import { promisify as promisify10 } from "util";
3573
- import fs15 from "fs/promises";
3610
+ import fs16 from "fs/promises";
3574
3611
  function globalCommand(args2, platform = process.platform) {
3575
3612
  return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args2] } : { command: "mason", args: args2 };
3576
3613
  }
@@ -3590,24 +3627,30 @@ async function installedCommand() {
3590
3627
  }
3591
3628
  }
3592
3629
  async function prepareLaunch(host, options = {}) {
3593
- const root = await fs15.realpath((await git3(process.cwd(), "rev-parse", "--show-toplevel")).trim());
3630
+ const root = await fs16.realpath((await git3(options.dir ?? process.cwd(), "rev-parse", "--show-toplevel")).trim());
3594
3631
  const entry = (await loadSetup(root))?.hosts[host];
3632
+ process.env.MASON_SETUP_ROOT = root;
3633
+ process.env.MASON_SETUP_HOST = host;
3634
+ delete process.env.MASON_SETUP_REVISION;
3595
3635
  if (!entry) {
3596
3636
  if (options.allowInactive) return false;
3597
3637
  throw new Error(`Mason is not configured locally for ${host}. Run mason setup --host ${host} in this checkout.`);
3598
3638
  }
3599
- process.env.MASON_SETUP_ROOT = root;
3600
- process.env.MASON_SETUP_HOST = host;
3601
3639
  process.env.MASON_SETUP_REVISION = entry.revision;
3602
3640
  return true;
3603
3641
  }
3642
+ function inactiveSetupMessage() {
3643
+ const host = process.env.MASON_SETUP_HOST;
3644
+ if (!process.env.MASON_SETUP_ROOT || process.env.MASON_SETUP_REVISION || host !== "claude" && host !== "codex") return null;
3645
+ return `Mason MCP is available, but automatic hooks are inactive in ${process.env.MASON_SETUP_ROOT}. Run mason setup --host ${host} in that checkout, review the host configuration, then restart the assistant. Setup has not been performed automatically.`;
3646
+ }
3604
3647
  var executingVersion, mcpCommand, hookCommand;
3605
3648
  var init_launcher = __esm({
3606
3649
  "src/setup/launcher.ts"() {
3607
3650
  "use strict";
3608
3651
  init_evidence();
3609
3652
  init_model();
3610
- executingVersion = () => true ? "0.17.0" : "development";
3653
+ executingVersion = () => true ? "0.17.2" : "development";
3611
3654
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3612
3655
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3613
3656
  }
@@ -3620,7 +3663,7 @@ __export(observations_exports, {
3620
3663
  observeActivation: () => observeActivation,
3621
3664
  readObservation: () => readObservation
3622
3665
  });
3623
- import fs16 from "fs/promises";
3666
+ import fs17 from "fs/promises";
3624
3667
  import { z as z12 } from "zod";
3625
3668
  function observationPath(directory, host, revision, version = executingVersion()) {
3626
3669
  return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
@@ -3634,11 +3677,17 @@ async function readObservation(root, directory, host, revision, version = execut
3634
3677
  }
3635
3678
  async function observeActivation(dir, event, options = {}) {
3636
3679
  const host = process.env.MASON_SETUP_HOST, revision = process.env.MASON_SETUP_REVISION;
3637
- if (host !== "codex" && host !== "claude" || !revision || !process.env.MASON_SETUP_ROOT) return null;
3680
+ if (host !== "codex" && host !== "claude" || !process.env.MASON_SETUP_ROOT) return null;
3638
3681
  try {
3682
+ if (!revision) {
3683
+ if ((await loadSetup(process.env.MASON_SETUP_ROOT))?.hosts[host]) {
3684
+ return "Mason setup is now present. Restart the assistant to connect this MCP session to the configured integration; activation has not been established by this session.";
3685
+ }
3686
+ return inactiveSetupMessage();
3687
+ }
3639
3688
  const capturedDirectory = options.directory?.match(/^\.mason\/reports\/automation\/[a-f0-9]{24}$/)?.[0] ?? options.reportPath?.match(/^(\.mason\/reports\/automation\/[a-f0-9]{24})\/checks\//)?.[1];
3640
- const ws = capturedDirectory ? { root: await fs16.realpath(dir), directory: capturedDirectory } : await workspace(dir);
3641
- if (ws.root !== await fs16.realpath(process.env.MASON_SETUP_ROOT)) return null;
3689
+ const ws = capturedDirectory ? { root: await fs17.realpath(dir), directory: capturedDirectory } : await workspace(dir);
3690
+ if (ws.root !== await fs17.realpath(process.env.MASON_SETUP_ROOT)) return null;
3642
3691
  const setup = await loadSetup(ws.root);
3643
3692
  if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
3644
3693
  const directory = ws.directory + "/activation";
@@ -3719,7 +3768,7 @@ function normalizeHook(host, raw) {
3719
3768
  stopHookActive: input.stop_hook_active || input.permission_mode === "plan"
3720
3769
  } };
3721
3770
  }
3722
- async function runAutomationHook(host, stdin) {
3771
+ async function runAutomationHook(host, stdin, options = {}) {
3723
3772
  let name = "";
3724
3773
  let hook;
3725
3774
  try {
@@ -3733,6 +3782,11 @@ async function runAutomationHook(host, stdin) {
3733
3782
  const input = normalizeHook(host, raw);
3734
3783
  hook = input;
3735
3784
  name = input.name;
3785
+ if (options.managed) {
3786
+ input.cwd = await resolveHookRoot(host, input.cwd);
3787
+ const { prepareLaunch: prepareLaunch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
3788
+ if (!await prepareLaunch2(host, { dir: input.cwd, allowInactive: true })) return null;
3789
+ }
3736
3790
  if (["before_tool", "after_tool"].includes(input.event.event) && input.readOnly) {
3737
3791
  const observed = await observeReadOnlyTool(input.cwd, input.event);
3738
3792
  const { observeActivation: observeActivation3 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
@@ -3765,6 +3819,9 @@ function managedHookCommand(host, platform = process.platform) {
3765
3819
  function knownManagedHookCommands(host) {
3766
3820
  return [managedHookCommand(host, "linux"), managedHookCommand(host, "win32"), `mason --setup-host ${host} auto hook --host ${host}`];
3767
3821
  }
3822
+ function compatibleManagedHookCommands(host, platform = process.platform) {
3823
+ return [managedHookCommand(host, platform), `mason --setup-host ${host} auto hook --host ${host}`];
3824
+ }
3768
3825
  function hookConfig(host, command2 = "npx --no-install --package mason-context mason-auto") {
3769
3826
  const handler = { type: "command", command: command2 === `mason --setup-host ${host} auto` ? managedHookCommand(host) : command2 + " hook --host " + host, timeout: 30 };
3770
3827
  return { hooks: Object.fromEntries(HOOK_EVENTS.map((name) => [
@@ -3779,6 +3836,7 @@ var init_adapters = __esm({
3779
3836
  init_execution();
3780
3837
  init_runtime();
3781
3838
  init_store();
3839
+ init_hook_root();
3782
3840
  inputSchema = z13.object({
3783
3841
  cwd: z13.string().min(1),
3784
3842
  session_id: z13.string().min(1).max(500),
@@ -3807,8 +3865,8 @@ __export(files_exports, {
3807
3865
  managedBlock: () => managedBlock,
3808
3866
  readText: () => readText
3809
3867
  });
3810
- import fs17 from "fs/promises";
3811
- import path21 from "path";
3868
+ import fs18 from "fs/promises";
3869
+ import path22 from "path";
3812
3870
  import { randomUUID as randomUUID5 } from "crypto";
3813
3871
  async function readText(root, file) {
3814
3872
  try {
@@ -3837,14 +3895,14 @@ async function applyEdit(root, edit) {
3837
3895
  if (edit.after === null) {
3838
3896
  const file2 = await storePath(root, edit.path);
3839
3897
  if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
3840
- await fs17.unlink(file2);
3898
+ await fs18.unlink(file2);
3841
3899
  return true;
3842
3900
  }
3843
3901
  const file = await storePath(root, edit.path, true);
3844
- const temporary = path21.join(path21.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
3902
+ const temporary = path22.join(path22.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
3845
3903
  try {
3846
- const mode = await fs17.stat(file).then((s) => s.mode & 511, () => 384);
3847
- const handle = await fs17.open(temporary, "wx", mode);
3904
+ const mode = await fs18.stat(file).then((s) => s.mode & 511, () => 384);
3905
+ const handle = await fs18.open(temporary, "wx", mode);
3848
3906
  try {
3849
3907
  await handle.writeFile(edit.after, "utf8");
3850
3908
  await handle.sync();
@@ -3852,10 +3910,10 @@ async function applyEdit(root, edit) {
3852
3910
  await handle.close();
3853
3911
  }
3854
3912
  if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
3855
- await fs17.rename(temporary, file);
3913
+ await fs18.rename(temporary, file);
3856
3914
  return true;
3857
3915
  } finally {
3858
- await fs17.rm(temporary, { force: true });
3916
+ await fs18.rm(temporary, { force: true });
3859
3917
  }
3860
3918
  }
3861
3919
  var init_files2 = __esm({
@@ -4024,29 +4082,6 @@ If a build is interrupted, its partials remain in \`.mason/partial-snapshots/\`.
4024
4082
  }
4025
4083
  });
4026
4084
 
4027
- // src/utils/git.ts
4028
- var git_exports = {};
4029
- __export(git_exports, {
4030
- isGitRepo: () => isGitRepo
4031
- });
4032
- import { execFile as execFile11 } from "child_process";
4033
- import { promisify as promisify11 } from "util";
4034
- async function isGitRepo(dir) {
4035
- try {
4036
- await exec8("git", ["rev-parse", "--git-dir"], { cwd: dir });
4037
- return true;
4038
- } catch {
4039
- return false;
4040
- }
4041
- }
4042
- var exec8;
4043
- var init_git2 = __esm({
4044
- "src/utils/git.ts"() {
4045
- "use strict";
4046
- exec8 = promisify11(execFile11);
4047
- }
4048
- });
4049
-
4050
4085
  // src/setup/config.ts
4051
4086
  var config_exports = {};
4052
4087
  __export(config_exports, {
@@ -4091,7 +4126,7 @@ async function instructionEdits(root, host) {
4091
4126
  function managedMcp(previous, host) {
4092
4127
  const options = previous === void 0 ? {} : object.parse(previous);
4093
4128
  for (const key of ["command", "args", "url", "type", "headers", "http_headers", "env_http_headers", "bearer_token_env_var"]) delete options[key];
4094
- return { ...options, ...mcpCommand(host) };
4129
+ return { ...options, ...host === "claude" ? { type: "stdio" } : {}, ...mcpCommand(host) };
4095
4130
  }
4096
4131
  async function mcpEdit(root, host) {
4097
4132
  const file = mcpPath(host), before = await readText(root, file);
@@ -4128,17 +4163,9 @@ async function hookEdits(root, host) {
4128
4163
  }
4129
4164
  async function ancillaryEdits(root) {
4130
4165
  const ignore = await readText(root, ".gitignore");
4131
- const { git: git4 } = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
4132
- const { isGitRepo: isGitRepo2 } = await Promise.resolve().then(() => (init_git2(), git_exports));
4133
- let parentIgnored = false;
4134
- try {
4135
- if (await isGitRepo2(root)) parentIgnored = !!(await git4(root, "check-ignore", "--no-index", ".mason")).trim();
4136
- } catch (error) {
4137
- if (error.code !== 1) throw error;
4138
- }
4139
- const retainParentRule = parentIgnored || !!ignore?.replace(/\r\n/g, "\n").includes("# mason:ignore:start\n!/.mason/\n/.mason/*");
4140
4166
  const rules = [
4141
- ...retainParentRule ? ["!/.mason/", "/.mason/*"] : [],
4167
+ "!/.mason/",
4168
+ "/.mason/*",
4142
4169
  "!/.mason/decisions/",
4143
4170
  "!/.mason/decisions/**",
4144
4171
  "!/.mason/reviews/",
@@ -4156,13 +4183,14 @@ async function inspectHostConfig(root, host, plannedMcp) {
4156
4183
  const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
4157
4184
  const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
4158
4185
  const expected = hookConfig(host, hookCommand(host));
4186
+ const compatible = compatibleManagedHookCommands(host);
4159
4187
  const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
4160
4188
  return {
4161
4189
  mcp,
4162
4190
  mcpDisabled: mcp?.enabled === false,
4163
4191
  hooks: Object.fromEntries(Object.keys(expected.hooks).map((event) => [
4164
4192
  event,
4165
- (hooks.hooks?.[event] ?? []).flatMap((group) => group.hooks.filter((handler) => handler.command === expected.hooks.SessionStart[0].hooks[0].command).map((handler) => ({ ...group, hooks: [handler] })) ?? [])
4193
+ (hooks.hooks?.[event] ?? []).flatMap((group) => group.hooks.filter((handler) => compatible.includes(handler.command ?? "")).map((handler) => ({ ...group, hooks: [handler] })) ?? [])
4166
4194
  ])),
4167
4195
  disabled: hooks.disableAllHooks === true || config.features?.hooks === false || config.features?.codex_hooks === false
4168
4196
  };
@@ -4352,8 +4380,12 @@ async function planTeardown(root, selected) {
4352
4380
  if (host === "codex" && BLOCKS[2].some((marker) => before.includes(marker))) throw new Error("The marked Mason MCP entry was removed or renamed; inspect the retained block manually.");
4353
4381
  return;
4354
4382
  }
4355
- const expected = ownership.files[mcpFile]?.mcpHash ?? hash(mcpCommand(host));
4356
- if (hash(servers.mason) !== expected) throw new Error("The Mason MCP entry was edited or its ownership is uncertain; retained for manual cleanup.");
4383
+ const ownedHash = ownership.files[mcpFile]?.mcpHash;
4384
+ const expected = ownedHash ? [ownedHash] : [
4385
+ hash(mcpCommand(host)),
4386
+ ...host === "claude" ? [hash({ type: "stdio", ...mcpCommand(host) })] : []
4387
+ ];
4388
+ if (!expected.includes(hash(servers.mason))) throw new Error("The Mason MCP entry was edited or its ownership is uncertain; retained for manual cleanup.");
4357
4389
  delete servers.mason;
4358
4390
  const desired = { ...config, [key]: servers };
4359
4391
  if (!Object.keys(servers).length) delete desired[key];
@@ -4520,13 +4552,13 @@ var init_teardown = __esm({
4520
4552
  });
4521
4553
 
4522
4554
  // src/review/cochange.ts
4523
- import { execFile as execFile12 } from "child_process";
4524
- import { promisify as promisify12 } from "util";
4555
+ import { execFile as execFile11 } from "child_process";
4556
+ import { promisify as promisify11 } from "util";
4525
4557
  async function buildMatrix(resolvedRoot) {
4526
4558
  try {
4527
- const { stdout: shallow } = await exec9("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
4559
+ const { stdout: shallow } = await exec8("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
4528
4560
  if (shallow.trim() === "true") return null;
4529
- const { stdout } = await exec9(
4561
+ const { stdout } = await exec8(
4530
4562
  "git",
4531
4563
  ["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
4532
4564
  { cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
@@ -4582,11 +4614,11 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
4582
4614
  findings.sort((a, b) => b.rate - a.rate || b.sharedCommits - a.sharedCommits);
4583
4615
  return findings;
4584
4616
  }
4585
- var exec9, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
4617
+ var exec8, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
4586
4618
  var init_cochange = __esm({
4587
4619
  "src/review/cochange.ts"() {
4588
4620
  "use strict";
4589
- exec9 = promisify12(execFile12);
4621
+ exec8 = promisify11(execFile11);
4590
4622
  HISTORY_COMMITS = 1500;
4591
4623
  MIN_FILE_COMMITS = 5;
4592
4624
  MIN_SHARED_COMMITS = 4;
@@ -4894,10 +4926,10 @@ var init_check = __esm({
4894
4926
  });
4895
4927
 
4896
4928
  // src/review/evidence.ts
4897
- import path22 from "path";
4929
+ import path23 from "path";
4898
4930
  import { z as z22 } from "zod";
4899
4931
  function relativeArtifact(root, file) {
4900
- const relative = path22.isAbsolute(file) ? path22.relative(root, file) : file;
4932
+ const relative = path23.isAbsolute(file) ? path23.relative(root, file) : file;
4901
4933
  const normalized = normalizeRepoPath(relative);
4902
4934
  if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
4903
4935
  return normalized;
@@ -4994,7 +5026,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
4994
5026
  const report = await readStoreJson(root, file);
4995
5027
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
4996
5028
  const sourceRoot = check.sourceRoot ?? root;
4997
- if (!path22.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5029
+ if (!path23.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
4998
5030
  const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
4999
5031
  result.outcome = parsed.outcome;
5000
5032
  result.counts = parsed.counts;
@@ -5115,13 +5147,13 @@ var init_evidence2 = __esm({
5115
5147
  });
5116
5148
 
5117
5149
  // src/review/review.ts
5118
- import fs18 from "fs/promises";
5119
- import path23 from "path";
5120
- import { execFile as execFile13 } from "child_process";
5121
- import { promisify as promisify13 } from "util";
5150
+ import fs19 from "fs/promises";
5151
+ import path24 from "path";
5152
+ import { execFile as execFile12 } from "child_process";
5153
+ import { promisify as promisify12 } from "util";
5122
5154
  async function resolveMergeBase(resolvedRoot, base, head) {
5123
5155
  try {
5124
- const { stdout } = await exec10("git", ["merge-base", base, head], {
5156
+ const { stdout } = await exec9("git", ["merge-base", base, head], {
5125
5157
  cwd: resolvedRoot
5126
5158
  });
5127
5159
  return stdout.trim() || null;
@@ -5132,7 +5164,7 @@ async function resolveMergeBase(resolvedRoot, base, head) {
5132
5164
  async function defaultBase(resolvedRoot) {
5133
5165
  for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
5134
5166
  try {
5135
- await exec10("git", ["rev-parse", "--verify", "--quiet", ref], {
5167
+ await exec9("git", ["rev-parse", "--verify", "--quiet", ref], {
5136
5168
  cwd: resolvedRoot
5137
5169
  });
5138
5170
  return ref;
@@ -5145,7 +5177,7 @@ function anchorsTouched(record, changedFiles) {
5145
5177
  return matchingPaths(record.files, changedFiles);
5146
5178
  }
5147
5179
  async function computeReview(rootDir, base, options = {}) {
5148
- const resolvedRoot = path23.resolve(rootDir);
5180
+ const resolvedRoot = path24.resolve(rootDir);
5149
5181
  const head = await getCurrentGitHash(resolvedRoot);
5150
5182
  if (head === "unknown") return null;
5151
5183
  const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
@@ -5195,7 +5227,7 @@ async function computeReview(rootDir, base, options = {}) {
5195
5227
  analyzed,
5196
5228
  async (relPath) => {
5197
5229
  try {
5198
- await fs18.access(path23.join(resolvedRoot, relPath));
5230
+ await fs19.access(path24.join(resolvedRoot, relPath));
5199
5231
  return true;
5200
5232
  } catch {
5201
5233
  return false;
@@ -5228,7 +5260,7 @@ async function computeReview(rootDir, base, options = {}) {
5228
5260
  }
5229
5261
  return finalize();
5230
5262
  }
5231
- var exec10, MAX_ANALYZED_FILES;
5263
+ var exec9, MAX_ANALYZED_FILES;
5232
5264
  var init_review = __esm({
5233
5265
  "src/review/review.ts"() {
5234
5266
  "use strict";
@@ -5240,7 +5272,7 @@ var init_review = __esm({
5240
5272
  init_cochange();
5241
5273
  init_evidence2();
5242
5274
  init_snapshot();
5243
- exec10 = promisify13(execFile13);
5275
+ exec9 = promisify12(execFile12);
5244
5276
  MAX_ANALYZED_FILES = 50;
5245
5277
  }
5246
5278
  });
@@ -5362,7 +5394,16 @@ async function setupStatus(dir) {
5362
5394
  const instructionsCurrent = instructions.every((edit) => edit.before === edit.after);
5363
5395
  const config = await inspectHostConfig(ws.root, host, void 0);
5364
5396
  const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual3({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host));
5365
- const hooks = isDeepStrictEqual3(config.hooks, hookConfig(host, hookCommand(host)).hooks) && !config.disabled;
5397
+ const expectedHooks = hookConfig(host, hookCommand(host)).hooks;
5398
+ const currentHooks = isDeepStrictEqual3(config.hooks, expectedHooks);
5399
+ const compatibleHooks = Object.fromEntries(Object.entries(config.hooks).map(([event, groups]) => [
5400
+ event,
5401
+ groups.map((group) => ({ ...group, hooks: group.hooks.map((handler) => ({
5402
+ ...handler,
5403
+ command: expectedHooks.SessionStart[0].hooks[0].command
5404
+ })) }))
5405
+ ]));
5406
+ const hooks = isDeepStrictEqual3(compatibleHooks, expectedHooks) && !config.disabled;
5366
5407
  const local = await loadSetupReceipt(ws.root, ws.directory, host);
5367
5408
  const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
5368
5409
  const observation = await readObservation(ws.root, ws.directory, host, entry.revision, installed.version ?? "unavailable");
@@ -5381,11 +5422,12 @@ async function setupStatus(dir) {
5381
5422
  runtime: installed.available ? "global (" + installed.version + ")" : "unavailable-on-path",
5382
5423
  mcp: mcp ? "configured" : "changed",
5383
5424
  instructions: instructionsCurrent ? "current" : "changed",
5384
- hookConfiguration: hooks ? "configured" : "disabled-or-changed",
5425
+ hookConfiguration: hooks ? currentHooks ? "configured" : "compatible" : "disabled-or-changed",
5385
5426
  observedEvents,
5386
5427
  contextCalls: observation?.contextCalls ?? 0,
5387
5428
  verificationStatus: automation.status === "current" ? automation.verificationStatus ?? "unavailable" : "unavailable",
5388
- pending
5429
+ pending,
5430
+ updates: hooks && !currentHooks ? ["A newer hook template is available. Run mason setup --host " + host + " to update it when convenient; review and commit the configuration changes."] : []
5389
5431
  };
5390
5432
  }
5391
5433
  const statuses = Object.values(hosts2).map((host) => host.status);
@@ -5410,6 +5452,7 @@ function summarizeActivation(status) {
5410
5452
  ` Task context requests: ${state.contextCalls}; verification: ${state.verificationStatus}.`,
5411
5453
  ...state.pending.map((message2) => " Next: " + message2)
5412
5454
  );
5455
+ lines.push(...state.updates.map((message2) => " Optional: " + message2));
5413
5456
  }
5414
5457
  if ("decisionRecords" in status) lines.push(`Decision records: ${status.decisionRecords}.`);
5415
5458
  if (status.next) lines.push(status.next);
@@ -5448,6 +5491,7 @@ async function selectHost(root, explicit) {
5448
5491
  throw new Error("Choose the assistant for setup with --host codex or --host claude.");
5449
5492
  }
5450
5493
  async function setupProject(dir, options = {}) {
5494
+ options.progress?.step("Checking project configuration");
5451
5495
  const ws = await workspace(dir);
5452
5496
  const host = await selectHost(ws.root, options.host);
5453
5497
  return withLock(ws.root, ".mason/reports/setup-lock", async () => {
@@ -5476,11 +5520,14 @@ async function setupProject(dir, options = {}) {
5476
5520
  const revision = !changed && previous ? previous.revision : randomUUID6();
5477
5521
  const setup = existing ?? { version: 2, hosts: {} };
5478
5522
  setup.hosts[host] = { configuredVersion: installed.version, revision, fingerprint, mcpFingerprint, instructions: instructions.map((e) => e.path) };
5523
+ options.progress?.step("Auditing documentation and preserving original findings");
5479
5524
  const initial = await automate(ws.root, { event: "turn_start" });
5525
+ options.progress?.step("Reviewing project context and committed changes");
5480
5526
  const findings = await inspectOnboarding(ws.root, options.base, options.evidence);
5481
5527
  const receiptPath = ws.directory + "/setup-" + host + ".json";
5482
5528
  const initialReportPath = previousReceipt?.initialReportPath ?? initial.report.reportPath;
5483
5529
  const initialBaselinePaths = previousReceipt?.initialBaselinePaths ?? initial.report.baselinePaths;
5530
+ options.progress?.step(`Configuring ${host === "codex" ? "Codex" : "Claude Code"} integration`);
5484
5531
  await writeStoreJson(ws.root, receiptPath, {
5485
5532
  version: 1,
5486
5533
  host,
@@ -5495,6 +5542,7 @@ async function setupProject(dir, options = {}) {
5495
5542
  for (const edit of edits) if (await applyEdit(ws.root, edit)) changedFiles.push(edit.path);
5496
5543
  if (await applyEdit(ws.root, { path: SETUP_PATH, before: setupBefore, after: JSON.stringify(setup, null, 2) + "\n" })) changedFiles.push(SETUP_PATH);
5497
5544
  if (await applyEdit(ws.root, await completedHookOwnership(ws.root, hooks[0].path, configuredHook)) && !changedFiles.includes(ownership.path)) changedFiles.push(ownership.path);
5545
+ options.progress?.step("Checking configuration and retained findings");
5498
5546
  const checked = await automate(ws.root, { event: "turn_start" });
5499
5547
  const configured = await inspectHostConfig(ws.root, host, void 0);
5500
5548
  await writeStoreJson(ws.root, receiptPath, {
@@ -5558,16 +5606,16 @@ var init_setup = __esm({
5558
5606
  });
5559
5607
 
5560
5608
  // src/distribution/bundle.ts
5561
- import fs19 from "fs/promises";
5562
- import path24 from "path";
5609
+ import fs20 from "fs/promises";
5610
+ import path25 from "path";
5563
5611
  import { createHash as createHash4 } from "crypto";
5564
5612
  import { z as z23 } from "zod";
5565
5613
  async function readBundle(root) {
5566
- const bytes = await fs19.readFile(await storePath(root, "bundle.json"));
5614
+ const bytes = await fs20.readFile(await storePath(root, "bundle.json"));
5567
5615
  const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
5568
5616
  const windows = manifest.target.startsWith("win32-");
5569
5617
  for (const file of Object.keys(manifest.files)) {
5570
- if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path24.isAbsolute(file)) {
5618
+ if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path25.isAbsolute(file)) {
5571
5619
  throw new Error("Unsafe path in Mason bundle: " + file);
5572
5620
  }
5573
5621
  }
@@ -5582,7 +5630,7 @@ async function verifyBundle(root, expectedHash) {
5582
5630
  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.");
5583
5631
  const files = Object.keys(bundle.manifest.files);
5584
5632
  for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
5585
- if (sha256(await fs19.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
5633
+ if (sha256(await fs20.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
5586
5634
  }));
5587
5635
  return bundle;
5588
5636
  }
@@ -5605,21 +5653,23 @@ var init_bundle = __esm({
5605
5653
  });
5606
5654
 
5607
5655
  // src/distribution/path.ts
5608
- import fs20 from "fs/promises";
5656
+ import fs21 from "fs/promises";
5609
5657
  import os3 from "os";
5610
- import path25 from "path";
5611
- import { execFile as execFile14 } from "child_process";
5612
- import { promisify as promisify14 } from "util";
5658
+ import path26 from "path";
5659
+ import { execFile as execFile13 } from "child_process";
5660
+ import { promisify as promisify13 } from "util";
5613
5661
  import { createHash as createHash5, randomUUID as randomUUID7 } from "crypto";
5614
5662
  import { z as z24 } from "zod";
5615
5663
  async function configurePath(bin, previous, save, options = {}) {
5616
5664
  const env = options.env ?? process.env, platform = options.platform ?? process.platform;
5617
5665
  const changes = [...previous];
5618
5666
  const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
5619
- const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path25.posix.isAbsolute(entry) && path25.posix.resolve(entry) === path25.posix.resolve(bin));
5620
- const message2 = alreadyOnPath ? "Run: mason setup --host codex" : "Open a new terminal, then run: mason setup --host codex";
5667
+ const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path26.posix.isAbsolute(entry) && path26.posix.resolve(entry) === path26.posix.resolve(bin));
5668
+ const setupHint = "To connect a project, run one of these inside its directory:\n Codex: mason setup --host codex\n Claude Code: mason setup --host claude";
5669
+ const message2 = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
5621
5670
  const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
5622
- ${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --host codex`}` });
5671
+ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
5672
+ `}${setupHint}` });
5623
5673
  if (env.MASON_NO_MODIFY_PATH === "1") return manual("Automatic PATH setup is disabled (MASON_NO_MODIFY_PATH=1).");
5624
5674
  if (/[\r\n\0]/.test(bin) || bin.includes(platform === "win32" ? ";" : ":")) return manual("This installation path cannot be represented safely in PATH.");
5625
5675
  try {
@@ -5634,33 +5684,33 @@ ${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --h
5634
5684
  }
5635
5685
  return { status: "configured", changes, message: message2 };
5636
5686
  }
5637
- const shell = path25.basename(env.SHELL ?? os3.userInfo().shell ?? "");
5687
+ const shell = path26.basename(env.SHELL ?? os3.userInfo().shell ?? "");
5638
5688
  const userHome = options.userHome ?? os3.homedir();
5639
5689
  let profiles;
5640
5690
  if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
5641
- if (env.MASON_PROFILE) profiles = [path25.resolve(env.MASON_PROFILE)];
5642
- else if (shell === "zsh") profiles = [path25.join(env.ZDOTDIR || userHome, ".zshrc")];
5643
- else if (shell === "sh") profiles = [path25.join(userHome, ".profile")];
5691
+ if (env.MASON_PROFILE) profiles = [path26.resolve(env.MASON_PROFILE)];
5692
+ else if (shell === "zsh") profiles = [path26.join(env.ZDOTDIR || userHome, ".zshrc")];
5693
+ else if (shell === "sh") profiles = [path26.join(userHome, ".profile")];
5644
5694
  else {
5645
- const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path25.join(userHome, name));
5695
+ const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path26.join(userHome, name));
5646
5696
  let login = candidates[2];
5647
- for (const file of candidates) if (await fs20.lstat(file).then(() => true, (error) => {
5697
+ for (const file of candidates) if (await fs21.lstat(file).then(() => true, (error) => {
5648
5698
  if (error.code === "ENOENT") return false;
5649
5699
  throw error;
5650
5700
  })) {
5651
5701
  login = file;
5652
5702
  break;
5653
5703
  }
5654
- profiles = [path25.join(userHome, ".bashrc"), login];
5704
+ profiles = [path26.join(userHome, ".bashrc"), login];
5655
5705
  }
5656
5706
  const marker = `# >>> mason PATH ${createHash5("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
5657
5707
  for (const profile of profiles) {
5658
- const stat = await fs20.lstat(profile).catch((error) => {
5708
+ const stat = await fs21.lstat(profile).catch((error) => {
5659
5709
  if (error.code === "ENOENT") return null;
5660
5710
  throw error;
5661
5711
  });
5662
- const file = stat?.isSymbolicLink() ? await fs20.realpath(profile) : path25.resolve(profile);
5663
- const original = await fs20.readFile(file).catch((error) => {
5712
+ const file = stat?.isSymbolicLink() ? await fs21.realpath(profile) : path26.resolve(profile);
5713
+ const original = await fs21.readFile(file).catch((error) => {
5664
5714
  if (error.code === "ENOENT" && !stat) return null;
5665
5715
  throw error;
5666
5716
  });
@@ -5675,8 +5725,8 @@ ${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --h
5675
5725
  changes.push({ kind: "profile", file, block, created: original === null });
5676
5726
  await save(changes);
5677
5727
  }
5678
- await fs20.mkdir(path25.dirname(file), { recursive: true });
5679
- const handle = await fs20.open(file, original === null ? "ax+" : "a+", 384);
5728
+ await fs21.mkdir(path26.dirname(file), { recursive: true });
5729
+ const handle = await fs21.open(file, original === null ? "ax+" : "a+", 384);
5680
5730
  try {
5681
5731
  if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
5682
5732
  await handle.writeFile(block);
@@ -5700,13 +5750,13 @@ async function removePath(changes) {
5700
5750
  await windowsPath("remove", change.entry, current, change.created);
5701
5751
  continue;
5702
5752
  }
5703
- const stat = await fs20.lstat(change.file).catch((error) => {
5753
+ const stat = await fs21.lstat(change.file).catch((error) => {
5704
5754
  if (error.code === "ENOENT") return null;
5705
5755
  throw error;
5706
5756
  });
5707
5757
  if (!stat) continue;
5708
5758
  if (!stat.isFile()) throw new Error("profile type changed");
5709
- const bytes = await fs20.readFile(change.file), text2 = bytes.toString("utf8");
5759
+ const bytes = await fs21.readFile(change.file), text2 = bytes.toString("utf8");
5710
5760
  if (!Buffer.from(text2).equals(bytes)) throw new Error("profile encoding changed");
5711
5761
  const index2 = text2.indexOf(change.block);
5712
5762
  if (index2 < 0 || text2.indexOf(change.block, index2 + change.block.length) >= 0) throw new Error("PATH block was edited or removed");
@@ -5716,7 +5766,7 @@ async function removePath(changes) {
5716
5766
  const temporary = change.file + ".mason-" + randomUUID7();
5717
5767
  try {
5718
5768
  if (stat.nlink > 1) throw new Error("profile has multiple hard links");
5719
- const handle = await fs20.open(temporary, "wx", stat.mode & 511);
5769
+ const handle = await fs21.open(temporary, "wx", stat.mode & 511);
5720
5770
  try {
5721
5771
  await handle.writeFile(next);
5722
5772
  await handle.chmod(stat.mode & 511);
@@ -5725,11 +5775,11 @@ async function removePath(changes) {
5725
5775
  } finally {
5726
5776
  await handle.close();
5727
5777
  }
5728
- const current = await fs20.lstat(change.file);
5729
- if (!current.isFile() || current.ino !== stat.ino || !(await fs20.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
5730
- await fs20.rename(temporary, change.file);
5778
+ const current = await fs21.lstat(change.file);
5779
+ if (!current.isFile() || current.ino !== stat.ino || !(await fs21.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
5780
+ await fs21.rename(temporary, change.file);
5731
5781
  } finally {
5732
- await fs20.rm(temporary, { force: true });
5782
+ await fs21.rm(temporary, { force: true });
5733
5783
  }
5734
5784
  } catch (error) {
5735
5785
  warnings.push(`Retained PATH setting ${change.kind === "profile" ? change.file : change.entry}: ${error instanceof Error ? error.message : String(error)}`);
@@ -5770,13 +5820,13 @@ var execute3, pathChangeSchema, quote, normalizeWindowsEntry, hasWindowsEntry;
5770
5820
  var init_path = __esm({
5771
5821
  "src/distribution/path.ts"() {
5772
5822
  "use strict";
5773
- execute3 = promisify14(execFile14);
5823
+ execute3 = promisify13(execFile13);
5774
5824
  pathChangeSchema = z24.discriminatedUnion("kind", [
5775
5825
  z24.object({ kind: z24.literal("profile"), file: z24.string(), block: z24.string(), created: z24.boolean() }),
5776
5826
  z24.object({ kind: z24.literal("windows"), entry: z24.string(), created: z24.boolean() })
5777
5827
  ]);
5778
5828
  quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
5779
- normalizeWindowsEntry = (value) => path25.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
5829
+ normalizeWindowsEntry = (value) => path26.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
5780
5830
  hasWindowsEntry = (value, entry, env = process.env) => {
5781
5831
  const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
5782
5832
  const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
@@ -5794,9 +5844,9 @@ __export(install_exports2, {
5794
5844
  uninstallStandalone: () => uninstallStandalone,
5795
5845
  upgradeStandalone: () => upgradeStandalone
5796
5846
  });
5797
- import fs21 from "fs/promises";
5847
+ import fs22 from "fs/promises";
5798
5848
  import os4 from "os";
5799
- import path26 from "path";
5849
+ import path27 from "path";
5800
5850
  import { fileURLToPath } from "url";
5801
5851
  import { spawn } from "child_process";
5802
5852
  import { randomUUID as randomUUID8 } from "crypto";
@@ -5804,31 +5854,32 @@ import { z as z25 } from "zod";
5804
5854
  async function replace(file, content2, mode = 384) {
5805
5855
  const temp = file + ".tmp-" + randomUUID8();
5806
5856
  try {
5807
- await fs21.writeFile(temp, content2, { mode, flag: "wx" });
5808
- await fs21.rename(temp, file);
5857
+ await fs22.writeFile(temp, content2, { mode, flag: "wx" });
5858
+ await fs22.rename(temp, file);
5809
5859
  } finally {
5810
- await fs21.rm(temp, { force: true });
5860
+ await fs22.rm(temp, { force: true });
5811
5861
  }
5812
5862
  }
5813
- async function installStandalone(source) {
5863
+ async function installStandalone(source, progress2) {
5864
+ progress2?.step("Checking installation files and location");
5814
5865
  const bundle = await verifyBundle(source);
5815
- const selectedHome = path26.resolve(defaultHome());
5816
- await fs21.mkdir(selectedHome, { recursive: true });
5817
- const home = await fs21.realpath(selectedHome);
5818
- const selectedBin = path26.resolve(process.env.MASON_BIN_DIR ?? (process.platform === "win32" ? path26.join(home, "bin") : path26.join(os4.homedir(), ".local/bin")));
5866
+ const selectedHome = path27.resolve(defaultHome());
5867
+ await fs22.mkdir(selectedHome, { recursive: true });
5868
+ const home = await fs22.realpath(selectedHome);
5869
+ const selectedBin = path27.resolve(process.env.MASON_BIN_DIR ?? (process.platform === "win32" ? path27.join(home, "bin") : path27.join(os4.homedir(), ".local/bin")));
5819
5870
  return withLock(home, ".install-lock", async () => {
5820
5871
  const raw = await readStoreJson(home, "install.json");
5821
5872
  const previous = raw === null ? null : recordSchema3.parse(raw);
5822
- if (!previous) for (const file of await fs21.readdir(home)) {
5873
+ if (!previous) for (const file of await fs22.readdir(home)) {
5823
5874
  if (file === ".install-lock") continue;
5824
- const directory = path26.join(home, file);
5825
- const stat = await fs21.lstat(directory);
5826
- if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs21.readdir(directory)).length) {
5875
+ const directory = path27.join(home, file);
5876
+ const stat = await fs22.lstat(directory);
5877
+ if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs22.readdir(directory)).length) {
5827
5878
  throw new Error("Mason installation directory is not empty and has no ownership receipt.");
5828
5879
  }
5829
5880
  }
5830
- await fs21.mkdir(selectedBin, { recursive: true });
5831
- const bin = await fs21.realpath(selectedBin);
5881
+ await fs22.mkdir(selectedBin, { recursive: true });
5882
+ const bin = await fs22.realpath(selectedBin);
5832
5883
  if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
5833
5884
  const id = bundle.manifestHash.slice(0, 24);
5834
5885
  const destination = await storePath(home, "versions/" + id);
@@ -5839,10 +5890,10 @@ $previousToken = $env:MASON_UNINSTALL_TOKEN
5839
5890
  $token = [Guid]::NewGuid().ToString()
5840
5891
  $env:MASON_UNINSTALL_TOKEN = $token
5841
5892
  try {
5842
- & ${psQuote(path26.join(destination, "node.exe"))} ${psQuote(path26.join(destination, "app/dist/mason.js"))} @args
5893
+ & ${psQuote(path27.join(destination, "node.exe"))} ${psQuote(path27.join(destination, "app/dist/mason.js"))} @args
5843
5894
  $code = $LASTEXITCODE
5844
5895
  if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
5845
- if ([IO.File]::ReadAllText(${psQuote(path26.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
5896
+ if ([IO.File]::ReadAllText(${psQuote(path27.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
5846
5897
  Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
5847
5898
  Write-Output ${psQuote("Removed standalone installation: " + home)}
5848
5899
  }
@@ -5851,11 +5902,11 @@ try {
5851
5902
  finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
5852
5903
  `
5853
5904
  } : { mason: `#!/bin/sh
5854
- exec ${shQuote(path26.join(destination, "node"))} ${shQuote(path26.join(destination, "app/dist/mason.js"))} "$@"
5905
+ exec ${shQuote(path27.join(destination, "node"))} ${shQuote(path27.join(destination, "app/dist/mason.js"))} "$@"
5855
5906
  ` };
5856
5907
  for (const [name, content2] of Object.entries(launchers)) {
5857
5908
  const file = await storePath(bin, name);
5858
- const old = await fs21.readFile(file, "utf8").catch((error) => {
5909
+ const old = await fs22.readFile(file, "utf8").catch((error) => {
5859
5910
  if (error.code === "ENOENT") return null;
5860
5911
  throw error;
5861
5912
  });
@@ -5864,61 +5915,65 @@ exec ${shQuote(path26.join(destination, "node"))} ${shQuote(path26.join(destinat
5864
5915
  const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
5865
5916
  const checkRetired = async (name, expected) => {
5866
5917
  if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
5867
- const actual = await fs21.readFile(await storePath(bin, name), "utf8").catch((error) => {
5918
+ const actual = await fs22.readFile(await storePath(bin, name), "utf8").catch((error) => {
5868
5919
  if (error.code === "ENOENT") return null;
5869
5920
  throw error;
5870
5921
  });
5871
5922
  if (actual !== null && actual !== expected) throw new Error("Retired launcher was edited; retained: " + name);
5872
5923
  };
5873
5924
  for (const [name, expected] of retired) await checkRetired(name, expected);
5925
+ progress2?.step(`Installing Mason ${bundle.manifest.version}`);
5874
5926
  const receipt = { format: 1, home, bin, current: id, version: bundle.manifest.version, launchers, pathChanges: previous?.pathChanges ?? [] };
5875
5927
  await writeStoreJson(home, "install.json", { ...receipt, previousLaunchers: previous?.previousLaunchers ?? previous?.launchers });
5876
5928
  const stage = await storePath(home, "versions/.install-" + randomUUID8(), true);
5877
- await fs21.mkdir(stage);
5929
+ await fs22.mkdir(stage);
5878
5930
  try {
5879
5931
  for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
5880
5932
  const from = await storePath(source, file), to = await storePath(stage, file, true);
5881
- await fs21.copyFile(from, to);
5882
- await fs21.chmod(to, (await fs21.stat(from)).mode & 511);
5933
+ await fs22.copyFile(from, to);
5934
+ await fs22.chmod(to, (await fs22.stat(from)).mode & 511);
5883
5935
  }
5884
5936
  await verifyBundle(stage, bundle.manifestHash);
5885
- const exists = await fs21.lstat(destination).then(() => true, (error) => {
5937
+ const exists = await fs22.lstat(destination).then(() => true, (error) => {
5886
5938
  if (error.code === "ENOENT") return false;
5887
5939
  throw error;
5888
5940
  });
5889
5941
  if (exists) await verifyBundle(destination, bundle.manifestHash);
5890
- else await fs21.rename(stage, destination);
5942
+ else await fs22.rename(stage, destination);
5891
5943
  for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
5892
5944
  for (const [name, expected] of retired) {
5893
5945
  await checkRetired(name, expected);
5894
- await fs21.rm(await storePath(bin, name), { force: true });
5946
+ await fs22.rm(await storePath(bin, name), { force: true });
5895
5947
  }
5896
5948
  await writeStoreJson(home, "install.json", receipt);
5897
5949
  } finally {
5898
- await fs21.rm(stage, { recursive: true, force: true });
5950
+ await fs22.rm(stage, { recursive: true, force: true });
5899
5951
  }
5952
+ progress2?.step("Checking terminal command availability");
5900
5953
  const pathResult = await configurePath(bin, receipt.pathChanges, async (changes) => {
5901
5954
  receipt.pathChanges = changes;
5902
5955
  await writeStoreJson(home, "install.json", receipt);
5903
5956
  });
5904
- return { home, bin, version: bundle.manifest.version, path: pathResult };
5957
+ return { home, bin, version: bundle.manifest.version, previousVersion: previous?.version, path: pathResult };
5905
5958
  });
5906
5959
  }
5907
5960
  async function currentInstallation() {
5908
- const app = path26.dirname(path26.dirname(fileURLToPath(import.meta.url)));
5909
- const bundle = path26.dirname(app), home = path26.dirname(path26.dirname(bundle));
5961
+ const app = path27.dirname(path27.dirname(fileURLToPath(import.meta.url)));
5962
+ const bundle = path27.dirname(app), home = path27.dirname(path27.dirname(bundle));
5910
5963
  const raw = await readStoreJson(home, "install.json");
5911
5964
  if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
5912
5965
  const record = recordSchema3.parse(raw);
5913
- if (record.home !== await fs21.realpath(home) || path26.dirname(bundle) !== path26.join(home, "versions")) throw new Error("Invalid standalone installation receipt.");
5966
+ if (record.home !== await fs22.realpath(home) || path27.dirname(bundle) !== path27.join(home, "versions")) throw new Error("Invalid standalone installation receipt.");
5914
5967
  return { home, bundle, record };
5915
5968
  }
5916
- async function upgradeStandalone(version) {
5969
+ async function upgradeStandalone(version, progress2) {
5917
5970
  if (version && !/^v?\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(version)) throw new Error("Expected a release version such as 0.14.0.");
5971
+ progress2?.step("Checking current installation");
5918
5972
  const { home, bundle, record } = await currentInstallation();
5919
5973
  await verifyBundle(bundle);
5920
5974
  const windows = process.platform === "win32";
5921
- const script = path26.join(bundle, windows ? "install.ps1" : "install.sh");
5975
+ const script = path27.join(bundle, windows ? "install.ps1" : "install.sh");
5976
+ progress2?.stop();
5922
5977
  return new Promise((resolve, reject) => {
5923
5978
  const child = spawn(
5924
5979
  windows ? "powershell.exe" : "sh",
@@ -5939,7 +5994,7 @@ async function uninstallStandalone() {
5939
5994
  for (const [name, expected] of Object.entries(record.launchers)) {
5940
5995
  if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
5941
5996
  const file = await storePath(record.bin, name);
5942
- const actual = await fs21.readFile(file, "utf8").catch((error) => {
5997
+ const actual = await fs22.readFile(file, "utf8").catch((error) => {
5943
5998
  if (error.code === "ENOENT") return null;
5944
5999
  throw error;
5945
6000
  });
@@ -5947,9 +6002,9 @@ async function uninstallStandalone() {
5947
6002
  }
5948
6003
  for (const warning of await removePath(record.pathChanges ?? [])) console.error(warning);
5949
6004
  if (process.platform === "win32") await replace(await storePath(home, ".uninstall-request.txt", true), token);
5950
- for (const name of Object.keys(record.launchers)) await fs21.rm(await storePath(record.bin, name), { force: true });
6005
+ for (const name of Object.keys(record.launchers)) await fs22.rm(await storePath(record.bin, name), { force: true });
5951
6006
  });
5952
- if (process.platform !== "win32") await fs21.rm(home, { recursive: true, force: true });
6007
+ if (process.platform !== "win32") await fs22.rm(home, { recursive: true, force: true });
5953
6008
  return home;
5954
6009
  }
5955
6010
  var recordSchema3, shQuote, psQuote, WINDOWS_BATCH_LAUNCHER, launcherNames, defaultHome;
@@ -5974,7 +6029,7 @@ var init_install2 = __esm({
5974
6029
  psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
5975
6030
  WINDOWS_BATCH_LAUNCHER = '@echo off\r\ngoto #_mason_handoff_# 2>NUL || title %COMSPEC% & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0mason-launcher.ps1" %*\r\n';
5976
6031
  launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
5977
- defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path26.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path26.join(os4.homedir(), ".local/share/mason"));
6032
+ defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path27.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path27.join(os4.homedir(), ".local/share/mason"));
5978
6033
  }
5979
6034
  });
5980
6035
 
@@ -5988,6 +6043,57 @@ init_adapters();
5988
6043
  init_install();
5989
6044
  init_store();
5990
6045
  import { parseArgs } from "util";
6046
+
6047
+ // src/utils/progress.ts
6048
+ function createProgress(options = {}) {
6049
+ const write = options.write ?? ((text2) => {
6050
+ process.stderr.write(text2);
6051
+ });
6052
+ const interactive = options.interactive ?? (!!process.stderr.isTTY && process.env.TERM !== "dumb" && !process.env.CI);
6053
+ const columns = options.columns ?? (() => process.stderr.columns || 80);
6054
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
6055
+ let message2 = "", started = 0, frame = 0;
6056
+ let timer;
6057
+ const elapsed = () => {
6058
+ const seconds = Math.floor((Date.now() - started) / 1e3);
6059
+ return seconds ? ` (${seconds}s)` : "";
6060
+ };
6061
+ const render = (prefix) => {
6062
+ const text2 = `${prefix} ${message2}${elapsed()}`;
6063
+ const width = Math.max(1, columns() - 1);
6064
+ write("\r\x1B[2K" + (text2.length > width ? text2.slice(0, width - 1) + "\u2026" : text2));
6065
+ };
6066
+ const stop = (success = true) => {
6067
+ if (timer) clearInterval(timer);
6068
+ timer = void 0;
6069
+ if (message2 && interactive) {
6070
+ render(success ? "\u2713" : "\u2717");
6071
+ write("\n");
6072
+ }
6073
+ message2 = "";
6074
+ };
6075
+ return {
6076
+ step(next) {
6077
+ stop();
6078
+ message2 = next.replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
6079
+ started = Date.now();
6080
+ frame = 0;
6081
+ if (!interactive) {
6082
+ write(`Mason: ${message2}\u2026
6083
+ `);
6084
+ return;
6085
+ }
6086
+ render(frames[frame]);
6087
+ timer = setInterval(() => {
6088
+ render(frames[++frame % frames.length]);
6089
+ }, 100);
6090
+ timer.unref();
6091
+ },
6092
+ stop
6093
+ };
6094
+ }
6095
+
6096
+ // src/automation/cli.ts
5991
6097
  var TEARDOWN_USAGE = `Usage: mason teardown [options]
5992
6098
 
5993
6099
  Disconnect Mason from this Git project. Defaults to all project hosts.
@@ -6036,11 +6142,13 @@ function isHookCommand(argv) {
6036
6142
  return false;
6037
6143
  }
6038
6144
  }
6039
- async function runAutomationCli(argv, stdin = "", io = {
6145
+ var defaultIo = {
6040
6146
  out: (s) => process.stdout.write(s + "\n"),
6041
6147
  err: (s) => process.stderr.write(s + "\n")
6042
- }) {
6148
+ };
6149
+ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {}) {
6043
6150
  let action = "";
6151
+ let progress2;
6044
6152
  try {
6045
6153
  const { values, positionals } = parseCli(argv);
6046
6154
  if (values.help || !positionals.length) {
@@ -6059,14 +6167,22 @@ async function runAutomationCli(argv, stdin = "", io = {
6059
6167
  return result.status === "complete" ? 0 : 2;
6060
6168
  }
6061
6169
  if (action === "hook") {
6062
- const output = await runAutomationHook(hostSchema.parse(values.host), stdin);
6170
+ const host = hostSchema.parse(values.host);
6171
+ if (options.managedHost && options.managedHost !== host) throw new Error("Managed hook --host does not match its launcher.");
6172
+ const output = await runAutomationHook(host, stdin, { managed: !!options.managedHost });
6063
6173
  if (output) io.out(JSON.stringify(output));
6064
6174
  return 0;
6065
6175
  }
6066
6176
  if (action === "setup") {
6067
6177
  if (values.command) throw new Error("--command applies to install/config, not managed setup.");
6068
6178
  const { setupProject: setupProject2, summarizeSetup: summarizeSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
6069
- const result = await setupProject2(dir, { host: values.host ? hostSchema.parse(values.host) : void 0 });
6179
+ const host = values.host ? hostSchema.parse(values.host) : void 0;
6180
+ if (!values.json) progress2 = createProgress(io === defaultIo ? {} : {
6181
+ write: (text2) => io.err(text2.replace(/\n$/, "")),
6182
+ interactive: false
6183
+ });
6184
+ const result = await setupProject2(dir, { host, progress: progress2 });
6185
+ progress2?.stop();
6070
6186
  io.out(values.json ? JSON.stringify(result, null, 2) : summarizeSetup2(result));
6071
6187
  return 0;
6072
6188
  }
@@ -6088,6 +6204,7 @@ async function runAutomationCli(argv, stdin = "", io = {
6088
6204
  io.out(values.json ? JSON.stringify(report, null, 2) : summarize(report));
6089
6205
  return report.status === "verified" ? 0 : report.status === "issues-remain" ? 1 : 2;
6090
6206
  } catch (error) {
6207
+ progress2?.stop(false);
6091
6208
  const message2 = action === "setup" ? "Mason setup incomplete: " + automationFailure(error).message + ". Rerun the same setup command to resume; retained audit evidence is preserved." : action === "teardown" ? "Mason teardown incomplete: " + automationFailure(error).message + ". Inspect the reported input, then rerun teardown; knowledge and repair evidence are retained." : failureMessage(error);
6092
6209
  if (action === "hook" || isHookCommand(argv)) {
6093
6210
  io.out(JSON.stringify({ systemMessage: message2 }));
@@ -6104,7 +6221,7 @@ var inputArgs = process.argv.slice(2);
6104
6221
  var managedLaunch = inputArgs[0] === "--setup-host";
6105
6222
  var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
6106
6223
  var [command, ...args] = inputArgs;
6107
- var usage = `Mason ${"0.17.0"}
6224
+ var usage = `Mason ${"0.17.2"}
6108
6225
 
6109
6226
  Usage: mason <command> [options]
6110
6227
 
@@ -6121,30 +6238,38 @@ Usage: mason <command> [options]
6121
6238
 
6122
6239
  Use mason <command> --help for options. npm users can keep using mason-auto,
6123
6240
  mason-audit, mason-review, mason-drift, mason-hook and mason-mcp.`;
6241
+ var progress;
6124
6242
  try {
6125
6243
  if (managedLaunch) {
6126
6244
  if (!setupHost || !["codex", "claude"].includes(setupHost) || !["mcp", "auto"].includes(command)) throw new Error("Invalid setup invocation.");
6127
- const { prepareLaunch: prepareLaunch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
6128
- const active = await prepareLaunch2(setupHost, { allowInactive: command === "auto" && isHookCommand(args) });
6129
- if (!active) process.exit(0);
6245
+ if (!(command === "auto" && isHookCommand(args))) {
6246
+ const { prepareLaunch: prepareLaunch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
6247
+ await prepareLaunch2(setupHost, { allowInactive: command === "mcp" });
6248
+ }
6130
6249
  }
6131
- if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.17.0" }));
6250
+ if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.17.2" }));
6132
6251
  else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
6133
- else if (["--version", "-v"].includes(command)) console.log("0.17.0");
6252
+ else if (["--version", "-v"].includes(command)) console.log("0.17.2");
6134
6253
  else if (command === "internal-install") {
6135
6254
  if (args.length) throw new Error("internal-install takes no arguments.");
6136
6255
  const { installStandalone: installStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
6137
6256
  const bundleRoot = fileURLToPath2(new URL("../..", import.meta.url));
6138
- const installed = await installStandalone2(bundleRoot);
6139
- console.log(`Installed Mason ${installed.version} in ${installed.home}.
6257
+ progress = createProgress();
6258
+ const installed = await installStandalone2(bundleRoot, progress);
6259
+ progress.stop();
6260
+ const outcome = installed.previousVersion && installed.previousVersion !== installed.version ? `Updated Mason ${installed.previousVersion} \u2192 ${installed.version}` : `Installed Mason ${installed.version}`;
6261
+ console.log(`${outcome} in ${installed.home}.
6140
6262
  ${installed.path.message}`);
6141
6263
  } else if (command === "upgrade" || command === "uninstall") {
6142
6264
  if (args.includes("--help")) console.log(command === "upgrade" ? "Usage: mason upgrade [version]. Configured projects use the upgraded command on their next launch. Restart running assistants." : "Usage: mason uninstall. Removes the standalone user installation; project configuration and knowledge are retained. Run mason teardown in each project first to disconnect its integrations.");
6143
6265
  else {
6144
6266
  const { upgradeStandalone: upgradeStandalone2, uninstallStandalone: uninstallStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
6145
6267
  if (args.length > (command === "upgrade" ? 1 : 0)) throw new Error("Unexpected arguments.");
6146
- if (command === "upgrade") process.exitCode = await upgradeStandalone2(args[0]);
6147
- else console.log((process.platform === "win32" ? "Finishing installation cleanup after runtime exit: " : "Removed standalone installation: ") + await uninstallStandalone2() + ". Project configuration and knowledge were retained. Reinstall Mason to use its integrations.");
6268
+ if (command === "upgrade") {
6269
+ progress = createProgress();
6270
+ process.exitCode = await upgradeStandalone2(args[0], progress);
6271
+ if (process.exitCode === 0) console.log("Restart running assistants to use this installation. Existing project integrations use mason from PATH.");
6272
+ } else console.log((process.platform === "win32" ? "Finishing installation cleanup after runtime exit: " : "Removed standalone installation: ") + await uninstallStandalone2() + ". Project configuration and knowledge were retained. Reinstall Mason to use its integrations.");
6148
6273
  }
6149
6274
  } else if (["setup", "teardown", "status", "check", "auto"].includes(command)) {
6150
6275
  const forwarded = command === "auto" ? args : [command, ...args];
@@ -6153,13 +6278,19 @@ ${installed.path.message}`);
6153
6278
  input += chunk.toString();
6154
6279
  if (Buffer.byteLength(input) > 1024 * 1024) break;
6155
6280
  }
6156
- process.exitCode = await runAutomationCli(forwarded, input);
6281
+ process.exitCode = await runAutomationCli(
6282
+ forwarded,
6283
+ input,
6284
+ void 0,
6285
+ { managedHost: managedLaunch ? setupHost : void 0 }
6286
+ );
6157
6287
  } else if (["audit", "review", "drift", "hook", "mcp"].includes(command)) {
6158
6288
  const binary = new URL(`./mason-${command}.js`, import.meta.url);
6159
6289
  process.argv = [process.execPath, fileURLToPath2(binary), ...args];
6160
6290
  await import(binary.href);
6161
6291
  } else throw new Error("Unknown command: " + command + ". Run mason --help.");
6162
6292
  } catch (error) {
6293
+ progress?.stop(false);
6163
6294
  const message2 = "Mason: " + (error instanceof Error ? error.message : String(error));
6164
6295
  if (command === "auto" && isHookCommand(args)) {
6165
6296
  console.log(JSON.stringify({ systemMessage: message2 + " Verification was not established." }));