mason-context 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mason-mcp.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ if (!import.meta.url.endsWith("/mason-runtime.js")) await import("./mason-runtime.js").then(m => m.registerRuntime());
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
5
  var __esm = (fn, res) => function __init() {
@@ -61,19 +62,19 @@ var init_git_read = __esm({
61
62
  import path from "path";
62
63
  function splitCommands(line) {
63
64
  const parts = [];
64
- let quote = "", start = 0;
65
+ let quote2 = "", start = 0;
65
66
  for (let i = 0; i < line.length; i++) {
66
67
  const char = line[i];
67
68
  if (char === "\\") {
68
69
  i++;
69
70
  continue;
70
71
  }
71
- if (quote) {
72
- if (char === quote) quote = "";
72
+ if (quote2) {
73
+ if (char === quote2) quote2 = "";
73
74
  continue;
74
75
  }
75
76
  if (char === "'" || char === '"') {
76
- quote = char;
77
+ quote2 = char;
77
78
  continue;
78
79
  }
79
80
  if (char === ";" || line.slice(i, i + 2) === "&&") {
@@ -2134,6 +2135,10 @@ var init_release_metadata = __esm({
2134
2135
  });
2135
2136
 
2136
2137
  // src/audit/checks/deps-changed.ts
2138
+ function hasDependencyContent(content2) {
2139
+ const text2 = content2.replace(/<!-- mason:start -->[\s\S]*?<!-- mason:end -->/g, "").replace(/<!--[\s\S]*?-->/g, "");
2140
+ return /\b(?:dependenc(?:y|ies)|librar(?:y|ies)|frameworks?|tech(?:nology)? stack|prerequisites|requirements)\b/i.test(text2) || /\b(?:package\.json|build\.gradle(?:\.kts)?|settings\.gradle(?:\.kts)?|libs\.versions\.toml|Cargo\.toml|go\.mod|pyproject\.toml|requirements\.txt|Gemfile|composer\.json)\b/i.test(text2) || /\b(?:node(?:\.js)?|npm|pnpm|yarn|bun|deno|python|ruby|rust|go|java|jdk|kotlin|gradle|swift|php|react|vue|angular|compose|ktor|room)\s*(?:version\s*)?[`*:=>~^v\s-]*\d+(?:\.\d+)*\b/i.test(text2) || /\b(?:npm|pnpm|yarn|pip3?|cargo|gem|composer)\s+(?:install|add|require)\b/i.test(text2);
2141
+ }
2137
2142
  function manifestPathspecs(doc) {
2138
2143
  const scope = documentScope(doc);
2139
2144
  if (scope === ".") return MANIFEST_PATHSPECS;
@@ -2160,6 +2165,19 @@ async function checkDepsChanged(ctx) {
2160
2165
  reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
2161
2166
  });
2162
2167
  }
2168
+ let content2 = doc.content;
2169
+ if (doc.dirty) {
2170
+ try {
2171
+ content2 = (await inspectionGit(
2172
+ ["show", `${doc.lastCommit.hash}:${doc.path}`],
2173
+ { cwd: ctx.root, maxBuffer: 10 * 1024 * 1024, timeout: 1e4 }
2174
+ )).stdout;
2175
+ } catch {
2176
+ result.skipped.push({ check: "deps-changed", doc: doc.path, reason: `${doc.path}: committed dependency content is unavailable` });
2177
+ continue;
2178
+ }
2179
+ }
2180
+ if (!hasDependencyContent(content2)) continue;
2163
2181
  const range = await commitsTouchingSince(
2164
2182
  ctx.root,
2165
2183
  doc.lastCommit.hash,
@@ -2205,6 +2223,7 @@ var init_deps_changed = __esm({
2205
2223
  init_docs();
2206
2224
  init_release_metadata();
2207
2225
  init_git();
2226
+ init_inspection();
2208
2227
  init_checks();
2209
2228
  MANIFEST_COMMITS_CAP = 10;
2210
2229
  MANIFEST_PATHSPECS = [
@@ -3567,7 +3586,7 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
3567
3586
  return {
3568
3587
  ...base,
3569
3588
  status: "review-required",
3570
- reason: "An audit cannot establish that this advisory was reviewed. Retain its original evidence and report a separate assessment; editing or committing the doc is not approval."
3589
+ reason: (now ? "The current check still reports this advisory. " : finding.type === "deps-changed" ? "The current check no longer reports this condition; the historical advisory remains awaiting assessment. " : "The current check no longer reports the original finding; retained evidence still requires assessment. ") + "An audit cannot establish that this advisory was reviewed. Retain its original evidence and report a separate assessment; editing or committing the doc is not approval."
3571
3590
  };
3572
3591
  }
3573
3592
  return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
@@ -3758,7 +3777,7 @@ async function collectInputs(root) {
3758
3777
  git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
3759
3778
  git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
3760
3779
  ]) : ["", ""];
3761
- const common = [5, engineVersion, head, shallow, replacements, docContents];
3780
+ const common = [6, engineVersion, head, shallow, replacements, docContents];
3762
3781
  const keys = {
3763
3782
  "deleted-reference": hash([common, claims, docStatus]),
3764
3783
  "new-module": hash([common, modules]),
@@ -3818,7 +3837,7 @@ var init_evidence = __esm({
3818
3837
  init_dead_command();
3819
3838
  init_storage();
3820
3839
  init_advisory_review();
3821
- engineVersion = true ? "0.18.0" : "development";
3840
+ engineVersion = true ? "0.19.0" : "development";
3822
3841
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
3823
3842
  content = readAuditInput;
3824
3843
  cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
@@ -3828,18 +3847,49 @@ var init_evidence = __esm({
3828
3847
  // src/setup/model.ts
3829
3848
  var model_exports = {};
3830
3849
  __export(model_exports, {
3850
+ INACTIVE_HOSTS_PATH: () => INACTIVE_HOSTS_PATH,
3831
3851
  SETUP_PATH: () => SETUP_PATH,
3852
+ effectiveSetup: () => effectiveSetup,
3853
+ inactiveHostsSchema: () => inactiveHostsSchema,
3832
3854
  loadSetup: () => loadSetup,
3833
3855
  loadSetupReceipt: () => loadSetupReceipt,
3834
3856
  setupHostSchema: () => setupHostSchema,
3835
3857
  setupSchema: () => setupSchema
3836
3858
  });
3837
3859
  import { z as z9 } from "zod";
3860
+ import fs14 from "fs/promises";
3861
+ import path22 from "path";
3838
3862
  async function loadSetup(root) {
3839
3863
  const raw = await readStoreJson(root, SETUP_PATH);
3840
3864
  if (raw === null) return null;
3841
3865
  return setupSchema.parse(raw);
3842
3866
  }
3867
+ async function effectiveSetup(root) {
3868
+ const local = await loadSetup(root);
3869
+ if (local) return { root, setup: local };
3870
+ const common = await fs14.realpath(path22.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
3871
+ const own = await fs14.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
3872
+ if (common === own) return { root, setup: null };
3873
+ const primary = (await git3(root, "worktree", "list", "--porcelain", "-z")).split("\0\0")[0].split("\0");
3874
+ const candidate = primary.find((field) => field.startsWith("worktree "))?.slice(9);
3875
+ if (!candidate || primary.includes("bare")) return { root, setup: null };
3876
+ let inheritedRoot;
3877
+ try {
3878
+ inheritedRoot = await fs14.realpath((await git3(candidate, "rev-parse", "--show-toplevel")).trim());
3879
+ } catch (error) {
3880
+ if (/must be run in a work tree/.test(String(error.stderr))) return { root, setup: null };
3881
+ throw error;
3882
+ }
3883
+ const inheritedCommon = await fs14.realpath(path22.resolve(
3884
+ inheritedRoot,
3885
+ (await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim()
3886
+ ));
3887
+ if (inheritedCommon !== common) throw new Error("Linked worktree setup does not belong to the same Git repository.");
3888
+ const setup = await loadSetup(inheritedRoot);
3889
+ const inactive = await readStoreJson(root, INACTIVE_HOSTS_PATH);
3890
+ if (inactive && setup) for (const host of inactiveHostsSchema.parse(inactive).hosts) delete setup.hosts[host];
3891
+ return { root: inheritedRoot, setup };
3892
+ }
3843
3893
  async function loadSetupReceipt(root, directory, host) {
3844
3894
  const raw = await readStoreJson(root, directory + "/setup-" + host + ".json");
3845
3895
  if (raw === null) return null;
@@ -3847,10 +3897,11 @@ async function loadSetupReceipt(root, directory, host) {
3847
3897
  if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
3848
3898
  return receipt;
3849
3899
  }
3850
- var setupHostSchema, setupSchema, SETUP_PATH, receiptSchema;
3900
+ var setupHostSchema, setupSchema, SETUP_PATH, INACTIVE_HOSTS_PATH, inactiveHostsSchema, receiptSchema;
3851
3901
  var init_model = __esm({
3852
3902
  "src/setup/model.ts"() {
3853
3903
  "use strict";
3904
+ init_evidence();
3854
3905
  init_storage();
3855
3906
  setupHostSchema = z9.object({
3856
3907
  configuredVersion: z9.string(),
@@ -3864,6 +3915,8 @@ var init_model = __esm({
3864
3915
  claude: setupHostSchema.optional()
3865
3916
  }) });
3866
3917
  SETUP_PATH = ".mason/local/setup.json";
3918
+ INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
3919
+ inactiveHostsSchema = z9.object({ version: z9.literal(1), hosts: z9.array(z9.enum(["claude", "codex"])) });
3867
3920
  receiptSchema = z9.object({
3868
3921
  version: z9.literal(1),
3869
3922
  host: z9.enum(["codex", "claude"]),
@@ -3880,7 +3933,7 @@ var init_model = __esm({
3880
3933
  // src/setup/launcher.ts
3881
3934
  import { execFile as execFile2 } from "child_process";
3882
3935
  import { promisify as promisify2 } from "util";
3883
- import fs14 from "fs/promises";
3936
+ import fs15 from "fs/promises";
3884
3937
  function globalCommand(args, platform = process.platform) {
3885
3938
  return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args] } : { command: "mason", args };
3886
3939
  }
@@ -3910,7 +3963,7 @@ var init_launcher = __esm({
3910
3963
  "use strict";
3911
3964
  init_evidence();
3912
3965
  init_model();
3913
- executingVersion = () => true ? "0.18.0" : "development";
3966
+ executingVersion = () => true ? "0.19.0" : "development";
3914
3967
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3915
3968
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3916
3969
  }
@@ -4035,8 +4088,8 @@ __export(files_exports, {
4035
4088
  managedBlock: () => managedBlock,
4036
4089
  readText: () => readText
4037
4090
  });
4038
- import fs17 from "fs/promises";
4039
- import path25 from "path";
4091
+ import fs18 from "fs/promises";
4092
+ import path26 from "path";
4040
4093
  import { randomUUID as randomUUID3 } from "crypto";
4041
4094
  async function readText(root, file) {
4042
4095
  try {
@@ -4065,14 +4118,14 @@ async function applyEdit(root, edit) {
4065
4118
  if (edit.after === null) {
4066
4119
  const file2 = await storePath(root, edit.path);
4067
4120
  if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
4068
- await fs17.unlink(file2);
4121
+ await fs18.unlink(file2);
4069
4122
  return true;
4070
4123
  }
4071
4124
  const file = await storePath(root, edit.path, true);
4072
- const temporary = path25.join(path25.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
4125
+ const temporary = path26.join(path26.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
4073
4126
  try {
4074
- const mode = await fs17.stat(file).then((s) => s.mode & 511, () => 384);
4075
- const handle = await fs17.open(temporary, "wx", mode);
4127
+ const mode = await fs18.stat(file).then((s) => s.mode & 511, () => 384);
4128
+ const handle = await fs18.open(temporary, "wx", mode);
4076
4129
  try {
4077
4130
  await handle.writeFile(edit.after, "utf8");
4078
4131
  await handle.sync();
@@ -4080,10 +4133,10 @@ async function applyEdit(root, edit) {
4080
4133
  await handle.close();
4081
4134
  }
4082
4135
  if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
4083
- await fs17.rename(temporary, file);
4136
+ await fs18.rename(temporary, file);
4084
4137
  return true;
4085
4138
  } finally {
4086
- await fs17.rm(temporary, { force: true });
4139
+ await fs18.rm(temporary, { force: true });
4087
4140
  }
4088
4141
  }
4089
4142
  var init_files2 = __esm({
@@ -4105,7 +4158,7 @@ __export(execution_exports, {
4105
4158
  recordExecution: () => recordExecution
4106
4159
  });
4107
4160
  import os2 from "os";
4108
- import fs18 from "fs/promises";
4161
+ import fs19 from "fs/promises";
4109
4162
  import { randomUUID as randomUUID4 } from "crypto";
4110
4163
  import { z as z12 } from "zod";
4111
4164
  function parseExecution(raw) {
@@ -4233,7 +4286,7 @@ async function recordExecution(root, directory, event, run) {
4233
4286
  throw Object.assign(new Error(failure.message, { cause: error }), { failure });
4234
4287
  }
4235
4288
  } finally {
4236
- await fs18.rm(await storePath(root, owner), { force: true }).catch(() => {
4289
+ await fs19.rm(await storePath(root, owner), { force: true }).catch(() => {
4237
4290
  });
4238
4291
  }
4239
4292
  }
@@ -4317,13 +4370,17 @@ async function observeReadOnlyTool(dir, event) {
4317
4370
  }
4318
4371
  function summarize(report) {
4319
4372
  const open = report.findings.filter((f) => f.status !== "resolved");
4373
+ const onlyReview = report.status === "incomplete" && !report.diagnostics.length && !report.counts.unresolved && !report.counts.unverified && report.counts["review-required"] > 0;
4320
4374
  return [
4321
- `Mason: ${report.status}; ${report.counts.unresolved} unresolved, ${report.counts["review-required"]} need review, ${report.counts.unverified} unverified.`,
4322
- ...open.slice(0, 4).map((f) => `[${f.status}] ${cleanText(f.original.anchor.doc)}: ${cleanText(f.original.message)}`),
4375
+ onlyReview ? `Mason: ${report.counts["review-required"]} advisor${report.counts["review-required"] === 1 ? "y" : "ies"} awaiting review; no unresolved issues.` : `Mason: ${report.status}; ${report.counts.unresolved} unresolved, ${report.counts["review-required"]} need review, ${report.counts.unverified} unverified.`,
4376
+ ...open.slice(0, 4).map((f) => {
4377
+ const detail = f.current ? f.current.message : f.status === "review-required" ? `${f.original.type}: current check no longer reports ${f.original.type === "deps-changed" ? "this condition" : "the original finding"}; historical evidence awaits assessment.` : `Original evidence: ${f.original.message}. ${f.reason}`;
4378
+ return `[${f.status}] ${cleanText(f.original.anchor.doc)}: ${cleanText(detail)}`;
4379
+ }),
4323
4380
  ...open.length > 4 ? [`${open.length - 4} more findings in the report.`] : [],
4324
4381
  ...report.diagnostics.slice(0, 2).map(cleanText),
4325
4382
  `Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason-auto check.`,
4326
- "Keep original evidence. Address findings relevant to the authorized task; report unrelated findings and unresolved advisories without approving them."
4383
+ "Keep original evidence. Address findings relevant to the authorized task; report unrelated findings with a brief scope reason and suggested follow-up. Use review_advisory for authorized assessments; otherwise leave advisories open without approving them."
4327
4384
  ].join("\n");
4328
4385
  }
4329
4386
  async function loadState(ws) {
@@ -4547,7 +4604,7 @@ async function automate(dir, event) {
4547
4604
  if (event.event === "after_tool" && event.mutating) {
4548
4605
  session.mutationObserved = true;
4549
4606
  if (!event.toolId || !session.pending[event.toolId]) {
4550
- const gap = "A tool completed without an observed matching pre-tool capture; pre-edit coverage is unknown.";
4607
+ const gap = "A tool completed without an observed matching pre-tool capture in this checkout; pre-edit coverage is unknown. The tool may have changed checkouts; captures are kept separately for each worktree.";
4551
4608
  if (!session.coverageGaps.includes(gap)) session.coverageGaps.push(gap);
4552
4609
  }
4553
4610
  if (event.toolId) delete session.pending[event.toolId];
@@ -4656,8 +4713,8 @@ var init_runtime = __esm({
4656
4713
  });
4657
4714
 
4658
4715
  // src/automation/hook-root.ts
4659
- import fs19 from "fs/promises";
4660
- import path26 from "path";
4716
+ import fs20 from "fs/promises";
4717
+ import path27 from "path";
4661
4718
  var init_hook_root = __esm({
4662
4719
  "src/automation/hook-root.ts"() {
4663
4720
  "use strict";
@@ -5500,10 +5557,10 @@ var init_check = __esm({
5500
5557
  });
5501
5558
 
5502
5559
  // src/review/evidence.ts
5503
- import path27 from "path";
5560
+ import path28 from "path";
5504
5561
  import { z as z22 } from "zod";
5505
5562
  function relativeArtifact(root, file) {
5506
- const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
5563
+ const relative = path28.isAbsolute(file) ? path28.relative(root, file) : file;
5507
5564
  const normalized = normalizeRepoPath(relative);
5508
5565
  if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
5509
5566
  return normalized;
@@ -5600,7 +5657,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5600
5657
  const report = await readStoreJson(root, file);
5601
5658
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
5602
5659
  const sourceRoot = check.sourceRoot ?? root;
5603
- if (!path27.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5660
+ if (!path28.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5604
5661
  const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
5605
5662
  result.outcome = parsed.outcome;
5606
5663
  result.counts = parsed.counts;
@@ -5721,8 +5778,8 @@ var init_evidence2 = __esm({
5721
5778
  });
5722
5779
 
5723
5780
  // src/review/review.ts
5724
- import fs20 from "fs/promises";
5725
- import path28 from "path";
5781
+ import fs21 from "fs/promises";
5782
+ import path29 from "path";
5726
5783
  import { execFile as execFile7 } from "child_process";
5727
5784
  import { promisify as promisify7 } from "util";
5728
5785
  async function resolveMergeBase(resolvedRoot, base, head) {
@@ -5751,7 +5808,7 @@ function anchorsTouched(record, changedFiles) {
5751
5808
  return matchingPaths(record.files, changedFiles);
5752
5809
  }
5753
5810
  async function computeReview(rootDir, base, options = {}) {
5754
- const resolvedRoot = path28.resolve(rootDir);
5811
+ const resolvedRoot = path29.resolve(rootDir);
5755
5812
  const head = await getCurrentGitHash(resolvedRoot);
5756
5813
  if (head === "unknown") return null;
5757
5814
  const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
@@ -5801,7 +5858,7 @@ async function computeReview(rootDir, base, options = {}) {
5801
5858
  analyzed,
5802
5859
  async (relPath) => {
5803
5860
  try {
5804
- await fs20.access(path28.join(resolvedRoot, relPath));
5861
+ await fs21.access(path29.join(resolvedRoot, relPath));
5805
5862
  return true;
5806
5863
  } catch {
5807
5864
  return false;
@@ -5936,7 +5993,7 @@ var init_onboarding = __esm({
5936
5993
  });
5937
5994
 
5938
5995
  // src/impact/references.ts
5939
- import path29 from "path";
5996
+ import path30 from "path";
5940
5997
  function templateEnd(content2, start) {
5941
5998
  let i = start + 1, interpolation = 0;
5942
5999
  while (i < content2.length) {
@@ -5958,10 +6015,10 @@ function templateEnd(content2, start) {
5958
6015
  continue;
5959
6016
  }
5960
6017
  if (c === '"' || c === "'") {
5961
- const quote = c;
6018
+ const quote2 = c;
5962
6019
  for (i++; i < content2.length; i++) {
5963
6020
  if (content2[i] === "\\") i++;
5964
- else if (content2[i] === quote) {
6021
+ else if (content2[i] === quote2) {
5965
6022
  i++;
5966
6023
  break;
5967
6024
  }
@@ -6062,10 +6119,10 @@ function jvmImports(content2) {
6062
6119
  }
6063
6120
  function resolveImport(file, specifier, available) {
6064
6121
  if (!specifier.startsWith("./") && !specifier.startsWith("../")) return null;
6065
- const relative = path29.posix.normalize(path29.posix.join(path29.posix.dirname(file), specifier));
6122
+ const relative = path30.posix.normalize(path30.posix.join(path30.posix.dirname(file), specifier));
6066
6123
  if (relative.startsWith("../")) return null;
6067
6124
  if (available.has(relative)) return relative;
6068
- const ext = path29.posix.extname(relative);
6125
+ const ext = path30.posix.extname(relative);
6069
6126
  const replacements = { ".js": [".ts", ".tsx"], ".mjs": [".mts"], ".cjs": [".cts"] };
6070
6127
  const candidates = ext ? (replacements[ext] ?? []).map((e) => relative.slice(0, -ext.length) + e) : jsExtensions.flatMap((e) => [relative + e, relative + "/index" + e]);
6071
6128
  const found = candidates.filter((f) => available.has(f));
@@ -6074,23 +6131,23 @@ function resolveImport(file, specifier, available) {
6074
6131
  function mentionsPath(content2, target) {
6075
6132
  return new RegExp(`(?<![\\w./\\\\-])(?:\\./)?${escape(target)}(?![\\w./\\\\-])`).test(content2);
6076
6133
  }
6077
- function createReferenceMatcher(targets, available, targetContents = /* @__PURE__ */ new Map()) {
6134
+ function createReferenceMatcher(targets2, available, targetContents = /* @__PURE__ */ new Map()) {
6078
6135
  const counts = /* @__PURE__ */ new Map();
6079
6136
  for (const file of available) {
6080
- const name = path29.posix.basename(file, path29.posix.extname(file));
6137
+ const name = path30.posix.basename(file, path30.posix.extname(file));
6081
6138
  counts.set(name, (counts.get(name) ?? 0) + 1);
6082
6139
  }
6083
6140
  const jvmTargets = new Map([...targetContents].filter(([file]) => /\.(?:kt|kts|java)$/.test(file)).map(([file, content2]) => [file, declaredSymbols(content2)]));
6084
6141
  return (source) => {
6085
- if (targets.includes(source.path)) return [];
6086
- const resolved = new Set(jsExtensions.includes(path29.posix.extname(source.path)) ? imports(source.content).map((s) => resolveImport(source.path, s, available)).filter(Boolean) : []);
6142
+ if (targets2.includes(source.path)) return [];
6143
+ const resolved = new Set(jsExtensions.includes(path30.posix.extname(source.path)) ? imports(source.content).map((s) => resolveImport(source.path, s, available)).filter(Boolean) : []);
6087
6144
  if (/\.(?:kt|kts|java)$/.test(source.path)) {
6088
6145
  const imported = jvmImports(source.content);
6089
6146
  for (const [target, symbols] of jvmTargets) if (symbols.some((symbol) => imported.some((name) => name === symbol || name.startsWith(symbol + ".")))) resolved.add(target);
6090
6147
  }
6091
6148
  const groups = /* @__PURE__ */ new Map();
6092
6149
  const add = (evidence, match) => groups.set(evidence, [...groups.get(evidence) ?? [], match]);
6093
- for (const target of targets) {
6150
+ for (const target of targets2) {
6094
6151
  if (resolved.has(target)) {
6095
6152
  add("resolved-import", target);
6096
6153
  continue;
@@ -6099,7 +6156,7 @@ function createReferenceMatcher(targets, available, targetContents = /* @__PURE_
6099
6156
  add("explicit-path", target);
6100
6157
  continue;
6101
6158
  }
6102
- const ext = path29.posix.extname(target), name = path29.posix.basename(target, ext);
6159
+ const ext = path30.posix.extname(target), name = path30.posix.basename(target, ext);
6103
6160
  if (!/\.(?:[cm]?[jt]sx?|py|kt|java|go|rs|swift|rb|cs|cpp|c|h|hpp|dart|php)$/.test(ext) || name.startsWith(".") || name.length < 3 || genericNames.has(name.toLowerCase()) || counts.get(name) !== 1) continue;
6104
6161
  if (word(name).test(source.content)) {
6105
6162
  add("name-candidate", name);
@@ -6128,11 +6185,11 @@ var impact_exports = {};
6128
6185
  __export(impact_exports, {
6129
6186
  analyzeImpact: () => analyzeImpact
6130
6187
  });
6131
- import path30 from "path";
6188
+ import path31 from "path";
6132
6189
  import { execFile as execFile8 } from "child_process";
6133
6190
  import { promisify as promisify8 } from "util";
6134
6191
  async function analyzeImpact(rootDir, targetFiles) {
6135
- const resolvedRoot = path30.resolve(rootDir);
6192
+ const resolvedRoot = path31.resolve(rootDir);
6136
6193
  const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
6137
6194
  const [cochange, references, tests] = await Promise.all([
6138
6195
  getCochangeFiles(resolvedRoot, resolvedTargets),
@@ -6146,10 +6203,10 @@ async function analyzeImpact(rootDir, targetFiles) {
6146
6203
  tests
6147
6204
  };
6148
6205
  }
6149
- async function resolveTargetFiles(rootDir, targets) {
6206
+ async function resolveTargetFiles(rootDir, targets2) {
6150
6207
  const resolved = [];
6151
6208
  const access = await createFileAccess(rootDir);
6152
- for (const target of targets) {
6209
+ for (const target of targets2) {
6153
6210
  if (target.includes("/")) {
6154
6211
  const normalized = normalizeRepoPath(target);
6155
6212
  if (normalized) resolved.push(normalized);
@@ -6246,9 +6303,9 @@ async function getRelatedTests(rootDir, targetFiles) {
6246
6303
  const testFiles = await (await createFileAccess(rootDir)).list(testPatterns);
6247
6304
  const results = [];
6248
6305
  for (const target of targetFiles) {
6249
- const targetBaseName = path30.basename(target).replace(/\.[^.]+$/, "");
6306
+ const targetBaseName = path31.basename(target).replace(/\.[^.]+$/, "");
6250
6307
  for (const testFile of testFiles) {
6251
- const testBaseName = path30.basename(testFile).replace(/\.[^.]+$/, "");
6308
+ const testBaseName = path31.basename(testFile).replace(/\.[^.]+$/, "");
6252
6309
  const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
6253
6310
  if (sourceName === targetBaseName) {
6254
6311
  results.push({
@@ -6276,26 +6333,26 @@ var assemble_exports = {};
6276
6333
  __export(assemble_exports, {
6277
6334
  assembleContext: () => assembleContext
6278
6335
  });
6279
- import path31 from "path";
6280
- import fs21 from "fs/promises";
6336
+ import path32 from "path";
6337
+ import fs22 from "fs/promises";
6281
6338
  async function collectImpact(root, candidates) {
6282
- const targets = /* @__PURE__ */ new Set();
6339
+ const targets2 = /* @__PURE__ */ new Set();
6283
6340
  let sourceFiles;
6284
6341
  for (const candidate of sanitizeRepoPaths(candidates)) {
6285
- const stat = await fs21.stat(path31.join(root, candidate)).catch(() => null);
6342
+ const stat = await fs22.stat(path32.join(root, candidate)).catch(() => null);
6286
6343
  if (stat?.isDirectory()) {
6287
6344
  sourceFiles ??= await (await createFileAccess(root)).list();
6288
6345
  for (const file of sourceFiles) {
6289
- if (anchorMatches(candidate, file)) targets.add(file);
6290
- if (targets.size >= MAX_IMPACT_TARGETS) break;
6346
+ if (anchorMatches(candidate, file)) targets2.add(file);
6347
+ if (targets2.size >= MAX_IMPACT_TARGETS) break;
6291
6348
  }
6292
6349
  } else {
6293
- targets.add(candidate);
6350
+ targets2.add(candidate);
6294
6351
  }
6295
- if (targets.size >= MAX_IMPACT_TARGETS) break;
6352
+ if (targets2.size >= MAX_IMPACT_TARGETS) break;
6296
6353
  }
6297
- if (!targets.size) return { impact: null, relatedTests: [] };
6298
- const result = await analyzeImpact(root, [...targets]);
6354
+ if (!targets2.size) return { impact: null, relatedTests: [] };
6355
+ const result = await analyzeImpact(root, [...targets2]);
6299
6356
  const referenceCandidatesOmitted = result.references.filter((r) => r.evidence === "name-candidate").length;
6300
6357
  return {
6301
6358
  impact: {
@@ -6308,7 +6365,7 @@ async function collectImpact(root, candidates) {
6308
6365
  };
6309
6366
  }
6310
6367
  async function assembleContext(rootDir, task, files) {
6311
- const resolvedRoot = path31.resolve(rootDir);
6368
+ const resolvedRoot = path32.resolve(rootDir);
6312
6369
  const mapState = await inspectSnapshot(resolvedRoot);
6313
6370
  const snapshot = mapState.snapshot;
6314
6371
  const store = await loadDecisionStore(resolvedRoot);
@@ -6504,7 +6561,7 @@ __export(observations_exports, {
6504
6561
  observeActivation: () => observeActivation,
6505
6562
  readObservation: () => readObservation
6506
6563
  });
6507
- import fs22 from "fs/promises";
6564
+ import fs23 from "fs/promises";
6508
6565
  import { z as z23 } from "zod";
6509
6566
  function observationPath(directory, host, revision, version = executingVersion()) {
6510
6567
  return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
@@ -6521,15 +6578,15 @@ async function observeActivation(dir, event, options = {}) {
6521
6578
  if (host !== "codex" && host !== "claude" || !process.env.MASON_SETUP_ROOT) return null;
6522
6579
  try {
6523
6580
  if (!revision) {
6524
- if ((await loadSetup(process.env.MASON_SETUP_ROOT))?.hosts[host]) {
6581
+ if ((await effectiveSetup(process.env.MASON_SETUP_ROOT)).setup?.hosts[host]) {
6525
6582
  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.";
6526
6583
  }
6527
6584
  return inactiveSetupMessage();
6528
6585
  }
6529
6586
  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];
6530
- const ws = capturedDirectory ? { root: await fs22.realpath(dir), directory: capturedDirectory } : await workspace(dir);
6531
- if (ws.root !== await fs22.realpath(process.env.MASON_SETUP_ROOT)) return null;
6532
- const setup = await loadSetup(ws.root);
6587
+ const ws = capturedDirectory ? { root: await fs23.realpath(dir), directory: capturedDirectory } : await workspace(dir);
6588
+ if (ws.root !== await fs23.realpath(process.env.MASON_SETUP_ROOT)) return null;
6589
+ const { setup } = await effectiveSetup(ws.root);
6533
6590
  if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
6534
6591
  const directory = ws.directory + "/activation";
6535
6592
  await withLock(ws.root, directory, async () => {
@@ -6603,8 +6660,9 @@ __export(status_exports, {
6603
6660
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
6604
6661
  async function setupStatus(dir) {
6605
6662
  const ws = await workspace(dir);
6606
- const setup = await loadSetup(ws.root);
6607
- if (!setup) {
6663
+ const activation = await effectiveSetup(ws.root);
6664
+ const setup = activation.setup;
6665
+ if (!setup || !Object.keys(setup.hosts).length) {
6608
6666
  const partial = await Promise.all(["codex", "claude"].map(async (host) => ({
6609
6667
  host,
6610
6668
  receipt: await loadSetupReceipt(ws.root, ws.directory, host)
@@ -6638,7 +6696,7 @@ async function setupStatus(dir) {
6638
6696
  ]));
6639
6697
  const hooks = isDeepStrictEqual2(compatibleHooks, expectedHooks) && !config.disabled;
6640
6698
  const local = await loadSetupReceipt(ws.root, ws.directory, host);
6641
- const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
6699
+ const configured = activation.root !== ws.root || local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
6642
6700
  const observation = await readObservation(ws.root, ws.directory, host, entry.revision, installed.version ?? "unavailable");
6643
6701
  const sessions = Object.values(observation?.sessions ?? {}).sort((a, b) => b.at.localeCompare(a.at));
6644
6702
  const complete = sessions.find((session) => events.every((event) => session.events.includes(event)));
@@ -6670,6 +6728,7 @@ async function setupStatus(dir) {
6670
6728
  status: statuses.length && statuses.every((status) => status === "active") ? "active" : statuses.includes("attention") ? "attention" : "pending",
6671
6729
  root: ws.root,
6672
6730
  hosts,
6731
+ activationRoot: activation.root,
6673
6732
  decisionRecords: decisions.records.length,
6674
6733
  diagnostics: decisions.diagnostics,
6675
6734
  scope: "Activation receipts record observed use after setup, not complete interception, correct repairs, or measured usefulness. Host trust and higher-priority settings may prevent execution. Receipts are local to this worktree and branch."
@@ -6677,6 +6736,9 @@ async function setupStatus(dir) {
6677
6736
  }
6678
6737
  function summarizeActivation(status) {
6679
6738
  const lines = ["Mason setup: " + status.status + "."];
6739
+ if (status.activationRoot && status.activationRoot !== status.root) {
6740
+ lines.push("Activation inherited from " + status.activationRoot + "; evidence belongs to this worktree.");
6741
+ }
6680
6742
  for (const [host, state] of Object.entries(status.hosts)) {
6681
6743
  lines.push(
6682
6744
  `${host}: ${state.status}`,
@@ -6846,8 +6908,8 @@ __export(client_exports, {
6846
6908
  function createConfluenceClient(config, fetchFn = fetch) {
6847
6909
  const baseUrl = config.baseUrl.replace(/\/+$/, "");
6848
6910
  const auth = "Basic " + Buffer.from(`${config.email}:${config.apiToken}`).toString("base64");
6849
- async function call(method, path35, body) {
6850
- const res = await fetchFn(`${baseUrl}${path35}`, {
6911
+ async function call(method, path42, body) {
6912
+ const res = await fetchFn(`${baseUrl}${path42}`, {
6851
6913
  method,
6852
6914
  headers: {
6853
6915
  Authorization: auth,
@@ -6859,7 +6921,7 @@ function createConfluenceClient(config, fetchFn = fetch) {
6859
6921
  if (!res.ok) {
6860
6922
  const text2 = await res.text();
6861
6923
  throw new Error(
6862
- `Confluence ${method} ${path35} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
6924
+ `Confluence ${method} ${path42} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
6863
6925
  );
6864
6926
  }
6865
6927
  if (res.status === 204) return null;
@@ -6960,28 +7022,28 @@ __export(config_exports2, {
6960
7022
  saveConfluenceConfig: () => saveConfluenceConfig,
6961
7023
  validateProvider: () => validateProvider
6962
7024
  });
6963
- import fs23 from "fs/promises";
6964
- import path32 from "path";
7025
+ import fs24 from "fs/promises";
7026
+ import path33 from "path";
6965
7027
  import os3 from "os";
6966
7028
  import { execFile as execFile9 } from "child_process";
6967
7029
  import { promisify as promisify9 } from "util";
6968
7030
  function configDir() {
6969
- return path32.join(os3.homedir(), ".mason");
7031
+ return path33.join(os3.homedir(), ".mason");
6970
7032
  }
6971
7033
  function configFile() {
6972
- return path32.join(configDir(), "config.json");
7034
+ return path33.join(configDir(), "config.json");
6973
7035
  }
6974
7036
  async function loadConfig() {
6975
7037
  try {
6976
- const raw = await fs23.readFile(configFile(), "utf-8");
7038
+ const raw = await fs24.readFile(configFile(), "utf-8");
6977
7039
  return JSON.parse(raw);
6978
7040
  } catch {
6979
7041
  return null;
6980
7042
  }
6981
7043
  }
6982
7044
  async function saveConfig(config) {
6983
- await fs23.mkdir(configDir(), { recursive: true });
6984
- await fs23.writeFile(configFile(), JSON.stringify(config, null, 2), "utf-8");
7045
+ await fs24.mkdir(configDir(), { recursive: true });
7046
+ await fs24.writeFile(configFile(), JSON.stringify(config, null, 2), "utf-8");
6985
7047
  }
6986
7048
  function getDefaultModel(provider) {
6987
7049
  return DEFAULT_MODELS[provider];
@@ -7141,21 +7203,21 @@ var init_renderer = __esm({
7141
7203
  });
7142
7204
 
7143
7205
  // src/confluence/diff.ts
7144
- import fs24 from "fs/promises";
7145
- import path33 from "path";
7206
+ import fs25 from "fs/promises";
7207
+ import path34 from "path";
7146
7208
  import { createHash as createHash6 } from "crypto";
7147
7209
  function hashDescription(description) {
7148
7210
  return createHash6("sha256").update(description, "utf8").digest("hex");
7149
7211
  }
7150
7212
  function syncStateDir(rootDir) {
7151
- return path33.join(rootDir, ".mason");
7213
+ return path34.join(rootDir, ".mason");
7152
7214
  }
7153
7215
  function syncStatePath(rootDir) {
7154
- return path33.join(syncStateDir(rootDir), "confluence-sync.json");
7216
+ return path34.join(syncStateDir(rootDir), "confluence-sync.json");
7155
7217
  }
7156
7218
  async function loadSyncState(rootDir) {
7157
7219
  try {
7158
- const raw = await fs24.readFile(syncStatePath(rootDir), "utf-8");
7220
+ const raw = await fs25.readFile(syncStatePath(rootDir), "utf-8");
7159
7221
  const parsed = JSON.parse(raw);
7160
7222
  if (parsed.version !== 2) return null;
7161
7223
  return parsed;
@@ -7164,8 +7226,8 @@ async function loadSyncState(rootDir) {
7164
7226
  }
7165
7227
  }
7166
7228
  async function saveSyncState(rootDir, state) {
7167
- await fs24.mkdir(syncStateDir(rootDir), { recursive: true });
7168
- await fs24.writeFile(
7229
+ await fs25.mkdir(syncStateDir(rootDir), { recursive: true });
7230
+ await fs25.writeFile(
7169
7231
  syncStatePath(rootDir),
7170
7232
  JSON.stringify(state, null, 2),
7171
7233
  "utf-8"
@@ -7723,8 +7785,8 @@ import { z as z24 } from "zod";
7723
7785
  // src/mcp/tools.ts
7724
7786
  init_repair();
7725
7787
  init_advisory_review();
7726
- import fs25 from "fs/promises";
7727
- import path34 from "path";
7788
+ import fs26 from "fs/promises";
7789
+ import path35 from "path";
7728
7790
  import { execFile as execFile11 } from "child_process";
7729
7791
  import { promisify as promisify11 } from "util";
7730
7792
 
@@ -7733,11 +7795,11 @@ init_audit();
7733
7795
  init_types();
7734
7796
  init_repair();
7735
7797
  init_findings();
7736
- import path23 from "path";
7798
+ import path24 from "path";
7737
7799
 
7738
7800
  // src/audit/advisory-cli.ts
7739
7801
  init_advisory_review();
7740
- import path22 from "path";
7802
+ import path23 from "path";
7741
7803
  import { stripVTControlCharacters } from "util";
7742
7804
 
7743
7805
  // src/audit/cli.ts
@@ -7834,7 +7896,7 @@ import { execFile as execFile3 } from "child_process";
7834
7896
  import { promisify as promisify3 } from "util";
7835
7897
 
7836
7898
  // src/analyzers/base.ts
7837
- import fs15 from "fs/promises";
7899
+ import fs16 from "fs/promises";
7838
7900
  import fg6 from "fast-glob";
7839
7901
  var BaseAnalyzer = class {
7840
7902
  async findFiles(patterns, root) {
@@ -7845,7 +7907,7 @@ var BaseAnalyzer = class {
7845
7907
  });
7846
7908
  }
7847
7909
  async readFile(filePath) {
7848
- return fs15.readFile(filePath, "utf-8");
7910
+ return fs16.readFile(filePath, "utf-8");
7849
7911
  }
7850
7912
  createFinding(partial) {
7851
7913
  return {
@@ -8050,7 +8112,7 @@ async function isGitRepo(dir) {
8050
8112
 
8051
8113
  // src/mcp/sampler.ts
8052
8114
  init_files();
8053
- import path24 from "path";
8115
+ import path25 from "path";
8054
8116
  import { execFile as execFile4 } from "child_process";
8055
8117
  import { promisify as promisify4 } from "util";
8056
8118
  var exec2 = promisify4(execFile4);
@@ -8158,8 +8220,8 @@ async function sampleFiles(rootDir, maxFiles = 25) {
8158
8220
  const projectConfig = access.config;
8159
8221
  for (const filePath of projectConfig.alwaysInclude ?? []) {
8160
8222
  if (selected.size >= maxFiles) break;
8161
- const resolvedPath = path24.resolve(rootDir, filePath);
8162
- if (!resolvedPath.startsWith(path24.resolve(rootDir))) continue;
8223
+ const resolvedPath = path25.resolve(rootDir, filePath);
8224
+ if (!resolvedPath.startsWith(path25.resolve(rootDir))) continue;
8163
8225
  selected.set(filePath, "always-include (project config)");
8164
8226
  }
8165
8227
  let configCount = 0;
@@ -8225,7 +8287,7 @@ async function sampleFiles(rootDir, maxFiles = 25) {
8225
8287
  if (!line) continue;
8226
8288
  if (line.includes("node_modules") || line.includes("/build/") || line.includes(".gradle") || line.includes("/generated/"))
8227
8289
  continue;
8228
- const ext = path24.extname(line).slice(1);
8290
+ const ext = path25.extname(line).slice(1);
8229
8291
  if (!SOURCE_EXTENSIONS.includes(ext)) continue;
8230
8292
  fileCounts.set(line, (fileCounts.get(line) ?? 0) + 1);
8231
8293
  }
@@ -8580,7 +8642,7 @@ ${JSON.stringify({ partials }, null, 2)}`;
8580
8642
  init_lock();
8581
8643
  init_storage();
8582
8644
  init_snapshot();
8583
- import fs16 from "fs/promises";
8645
+ import fs17 from "fs/promises";
8584
8646
  import { z as z11 } from "zod";
8585
8647
  var DIRECTORY = ".mason/partial-snapshots";
8586
8648
  var partialSchema = z11.object({
@@ -8598,7 +8660,7 @@ async function savePartial(rootDir, partial) {
8598
8660
  async function loadAllPartials(rootDir) {
8599
8661
  let entries;
8600
8662
  try {
8601
- entries = await fs16.readdir(await storePath(rootDir, DIRECTORY));
8663
+ entries = await fs17.readdir(await storePath(rootDir, DIRECTORY));
8602
8664
  } catch (error) {
8603
8665
  if (error.code === "ENOENT") return [];
8604
8666
  throw error;
@@ -8621,11 +8683,11 @@ async function loadScope(rootDir) {
8621
8683
  }
8622
8684
  async function clearScope(rootDir) {
8623
8685
  await withSnapshotWrite(rootDir, async () => {
8624
- await fs16.rm(await storePath(rootDir, `${DIRECTORY}/scope.json`), { force: true });
8686
+ await fs17.rm(await storePath(rootDir, `${DIRECTORY}/scope.json`), { force: true });
8625
8687
  });
8626
8688
  }
8627
8689
  async function clearAllPartials(rootDir) {
8628
- await fs16.rm(await storePath(rootDir, DIRECTORY), { recursive: true, force: true });
8690
+ await fs17.rm(await storePath(rootDir, DIRECTORY), { recursive: true, force: true });
8629
8691
  }
8630
8692
  function batchIdFor(offset) {
8631
8693
  return `batch-${String(offset).padStart(6, "0")}`;
@@ -8642,7 +8704,7 @@ async function buildContext(dir) {
8642
8704
  };
8643
8705
  }
8644
8706
  async function analyzeProject(dir) {
8645
- const rootDir = path34.resolve(dir);
8707
+ const rootDir = path35.resolve(dir);
8646
8708
  const context = await buildContext(rootDir);
8647
8709
  const results = await runAll(context);
8648
8710
  const projectSnapshot = await detectProjectSnapshot(rootDir);
@@ -8697,7 +8759,7 @@ async function detectProjectSnapshot(rootDir) {
8697
8759
  const present = [];
8698
8760
  for (const file of buildFiles) {
8699
8761
  try {
8700
- await fs25.access(path34.join(rootDir, file));
8762
+ await fs26.access(path35.join(rootDir, file));
8701
8763
  present.push(file);
8702
8764
  } catch {
8703
8765
  }
@@ -8739,7 +8801,7 @@ async function detectProjectSnapshot(rootDir) {
8739
8801
  const sourceFiles = await access.list();
8740
8802
  const fileCounts = {};
8741
8803
  for (const file of sourceFiles) {
8742
- const ext = path34.extname(file).slice(1);
8804
+ const ext = path35.extname(file).slice(1);
8743
8805
  fileCounts[ext] = (fileCounts[ext] ?? 0) + 1;
8744
8806
  }
8745
8807
  return {
@@ -8750,7 +8812,7 @@ async function detectProjectSnapshot(rootDir) {
8750
8812
  };
8751
8813
  }
8752
8814
  async function getCodeSamples(dir, count4 = 15) {
8753
- const rootDir = path34.resolve(dir);
8815
+ const rootDir = path35.resolve(dir);
8754
8816
  const samples = await sampleFiles(rootDir, count4);
8755
8817
  const output = {
8756
8818
  note: "These are previews (first ~60 lines). Read the file directly with your own tools to see it in full.",
@@ -8793,7 +8855,7 @@ async function unmappedContextResponse(rootDir) {
8793
8855
  });
8794
8856
  }
8795
8857
  async function getProjectStructure(dir) {
8796
- const rootDir = path34.resolve(dir);
8858
+ const rootDir = path35.resolve(dir);
8797
8859
  const allFiles = await (await createFileAccess(rootDir)).list("**/*");
8798
8860
  const dirInfo = /* @__PURE__ */ new Map();
8799
8861
  for (const file of allFiles) {
@@ -8805,7 +8867,7 @@ async function getProjectStructure(dir) {
8805
8867
  }
8806
8868
  const info = dirInfo.get(dirPath);
8807
8869
  info.fileCount++;
8808
- const ext = path34.extname(file).slice(1);
8870
+ const ext = path35.extname(file).slice(1);
8809
8871
  if (ext) {
8810
8872
  info.extensions.set(ext, (info.extensions.get(ext) ?? 0) + 1);
8811
8873
  }
@@ -8850,7 +8912,7 @@ async function buildChangedFilePreviews(rootDir, changedFiles) {
8850
8912
  return previews2;
8851
8913
  }
8852
8914
  async function getSnapshot(dir) {
8853
- const rootDir = path34.resolve(dir);
8915
+ const rootDir = path35.resolve(dir);
8854
8916
  const snapshot = await loadSnapshot(rootDir);
8855
8917
  if (!snapshot) {
8856
8918
  return unmappedContextResponse(rootDir);
@@ -8951,7 +9013,7 @@ function driftHint(report) {
8951
9013
  return "Read the changed files under staleFeatures/staleFlows, update those entries (fold unmappedFiles into the right features), and call save_snapshot with only the affected entries \u2014 unchanged entries are preserved. Drop ghostFiles from any entries that reference them, and delete features/flows that no longer exist via save_snapshot's removeFeatures/removeFlows.";
8952
9014
  }
8953
9015
  async function checkDrift(dir) {
8954
- const rootDir = path34.resolve(dir);
9016
+ const rootDir = path35.resolve(dir);
8955
9017
  const report = await computeDrift(rootDir);
8956
9018
  if (!report) {
8957
9019
  return JSON.stringify({
@@ -8982,7 +9044,7 @@ async function checkDrift(dir) {
8982
9044
  return JSON.stringify({ exists: true, ...report, verification, hint });
8983
9045
  }
8984
9046
  async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_SIZE, files) {
8985
- const rootDir = path34.resolve(dir);
9047
+ const rootDir = path35.resolve(dir);
8986
9048
  const scoped = files !== void 0 && files.length > 0;
8987
9049
  const scopeFiles = scoped ? sanitizePaths(rootDir, files) : void 0;
8988
9050
  const batch = await prepareSnapshotBatch(dir, offset, batchSize, scopeFiles);
@@ -9029,7 +9091,7 @@ async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_
9029
9091
  );
9030
9092
  }
9031
9093
  async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
9032
- const rootDir = path34.resolve(dir);
9094
+ const rootDir = path35.resolve(dir);
9033
9095
  for (const feat of Object.values(features)) {
9034
9096
  feat.files = sanitizePaths(rootDir, feat.files);
9035
9097
  if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
@@ -9058,7 +9120,7 @@ async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
9058
9120
  );
9059
9121
  }
9060
9122
  async function reduceSnapshot(dir) {
9061
- const rootDir = path34.resolve(dir);
9123
+ const rootDir = path35.resolve(dir);
9062
9124
  const partials = await loadAllPartials(rootDir);
9063
9125
  if (partials.length === 0) {
9064
9126
  return JSON.stringify(
@@ -9120,7 +9182,7 @@ async function reduceSnapshot(dir) {
9120
9182
  );
9121
9183
  }
9122
9184
  async function fullAnalysis(dir) {
9123
- const rootDir = path34.resolve(dir);
9185
+ const rootDir = path35.resolve(dir);
9124
9186
  const [analysis, structure, samples, testMap, snapshot] = await Promise.all([
9125
9187
  analyzeProject(dir),
9126
9188
  getProjectStructure(dir),
@@ -9149,7 +9211,7 @@ function sanitizePaths(rootDir, files) {
9149
9211
  return sanitizeRepoPaths(files);
9150
9212
  }
9151
9213
  async function saveSnapshotData(dir, features, flows, removeFeatures = [], removeFlows = []) {
9152
- const rootDir = path34.resolve(dir);
9214
+ const rootDir = path35.resolve(dir);
9153
9215
  return updateSnapshot(rootDir, async (previous) => {
9154
9216
  const gitHash = await getCurrentGitHash(rootDir);
9155
9217
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -9229,14 +9291,14 @@ async function saveSnapshotData(dir, features, flows, removeFeatures = [], remov
9229
9291
  });
9230
9292
  }
9231
9293
  async function getImpact(dir, files) {
9232
- const rootDir = path34.resolve(dir);
9294
+ const rootDir = path35.resolve(dir);
9233
9295
  const { analyzeImpact: analyzeImpact2 } = await Promise.resolve().then(() => (init_impact(), impact_exports));
9234
9296
  const result = await analyzeImpact2(rootDir, files);
9235
9297
  return JSON.stringify(result, null, 2);
9236
9298
  }
9237
9299
  var VERIFY_DEFAULT_SAMPLE = 5;
9238
9300
  async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
9239
- const rootDir = path34.resolve(dir);
9301
+ const rootDir = path35.resolve(dir);
9240
9302
  const snapshot = await loadSnapshot(rootDir);
9241
9303
  if (!snapshot) {
9242
9304
  return JSON.stringify({
@@ -9289,7 +9351,7 @@ async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
9289
9351
  });
9290
9352
  }
9291
9353
  async function saveVerification(dir, verdicts) {
9292
- const rootDir = path34.resolve(dir);
9354
+ const rootDir = path35.resolve(dir);
9293
9355
  return updateSnapshot(rootDir, async (snapshot) => {
9294
9356
  if (!snapshot) {
9295
9357
  return { snapshot: null, result: JSON.stringify({ exists: false, stamped: [], hint: "No concept map exists. Build it and run verify_snapshot before recording verdicts." }) };
@@ -9363,16 +9425,16 @@ async function saveVerification(dir, verdicts) {
9363
9425
  });
9364
9426
  }
9365
9427
  async function saveDecision(dir, input) {
9366
- const rootDir = path34.resolve(dir);
9428
+ const rootDir = path35.resolve(dir);
9367
9429
  const { upsertDecision: upsertDecision2 } = await Promise.resolve().then(() => (init_decisions(), decisions_exports));
9368
9430
  const result = await upsertDecision2(rootDir, input);
9369
9431
  return JSON.stringify(result);
9370
9432
  }
9371
9433
  async function reviewDecision2(dir, input) {
9372
- return JSON.stringify(await reviewDecision(path34.resolve(dir), input));
9434
+ return JSON.stringify(await reviewDecision(path35.resolve(dir), input));
9373
9435
  }
9374
9436
  async function getContext(dir, task, files) {
9375
- const rootDir = path34.resolve(dir);
9437
+ const rootDir = path35.resolve(dir);
9376
9438
  const { assembleContext: assembleContext2 } = await Promise.resolve().then(() => (init_assemble(), assemble_exports));
9377
9439
  const bundle = await assembleContext2(rootDir, task, files);
9378
9440
  return JSON.stringify(bundle);
@@ -9421,7 +9483,7 @@ async function masonInit(dir, options = {}) {
9421
9483
  return JSON.stringify(await setupProject2(dir, options), null, 2);
9422
9484
  }
9423
9485
  if (options.host) throw new Error("host applies only to mode: setup.");
9424
- const rootDir = path34.resolve(dir);
9486
+ const rootDir = path35.resolve(dir);
9425
9487
  const marker = await loadProjectMarker(rootDir);
9426
9488
  const { loadSetup: loadSetup2 } = await Promise.resolve().then(() => (init_model(), model_exports));
9427
9489
  const setup = await loadSetup2(rootDir);
@@ -9441,7 +9503,7 @@ async function masonInit(dir, options = {}) {
9441
9503
  );
9442
9504
  }
9443
9505
  async function masonCompleteInit(dir, options = {}) {
9444
- const rootDir = path34.resolve(dir);
9506
+ const rootDir = path35.resolve(dir);
9445
9507
  const existing = await loadProjectMarker(rootDir);
9446
9508
  const features = await loadProjectFeatures(rootDir);
9447
9509
  const marker = {
@@ -9550,7 +9612,7 @@ async function masonSetConfluence(input) {
9550
9612
  );
9551
9613
  }
9552
9614
  async function exportToConfluenceTool(dir, overrides) {
9553
- const rootDir = path34.resolve(dir);
9615
+ const rootDir = path35.resolve(dir);
9554
9616
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports2));
9555
9617
  const { exportToConfluence: exportToConfluence2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
9556
9618
  const config = await loadConfig2();
@@ -9589,7 +9651,7 @@ function createMcpServer() {
9589
9651
  const server = new McpServer(
9590
9652
  {
9591
9653
  name: "mason",
9592
- version: "0.18.0"
9654
+ version: "0.19.0"
9593
9655
  },
9594
9656
  {
9595
9657
  instructions: (setupNotice ? setupNotice + "\n\n" : "") + 'Mason retrieves recorded decisions, file impact, and optional feature/flow maps. Use get_context with the task and known files, and get_impact before editing. Save learned rationale and constraints with save_decision; review and commit the local records through the project workflow. These tools work without initialization or a concept map. Consult trust and diagnostics: changed or unknown freshness needs source inspection, stale or unknown map verification needs a fresh review, recorded failed verdicts need correction, and proposals are suggestions and legacy records are unreviewed. Accepted decisions are recorded team constraints, still subject to freshness checks. Use review_decision to prepare code evidence and record only authorized acceptance, reaffirmation, or retirement. Never invent a reviewer or treat these recorded identities as authenticated approval. mason_init returns documentation audit and committed-diff review findings with a quickstart guide. Use mode: "map" only when a full architecture map is requested. A missing map is not a setup failure; use decisions and source evidence. get_snapshot provides architecture navigation when a map is available. The mason-audit and mason-review CLIs also work without setup.'
@@ -9970,14 +10032,592 @@ function createMcpServer() {
9970
10032
  );
9971
10033
  return server;
9972
10034
  }
9973
- async function startMcpServer() {
10035
+ async function startMcpServer(onClose) {
9974
10036
  const server = createMcpServer();
10037
+ if (onClose) server.server.onclose = onClose;
9975
10038
  const transport = new StdioServerTransport();
9976
10039
  await server.connect(transport);
9977
10040
  }
9978
10041
 
10042
+ // src/distribution/updates.ts
10043
+ init_store();
10044
+ init_storage();
10045
+ import fs33 from "fs/promises";
10046
+ import path41 from "path";
10047
+ import { spawn as spawn3 } from "child_process";
10048
+ import { randomUUID as randomUUID11 } from "crypto";
10049
+ import { z as z30 } from "zod";
10050
+
10051
+ // src/distribution/state.ts
10052
+ init_storage();
10053
+ import os5 from "os";
10054
+ import path37 from "path";
10055
+ import { fileURLToPath } from "url";
10056
+ import fs28 from "fs/promises";
10057
+ import { z as z26 } from "zod";
10058
+
10059
+ // src/distribution/path.ts
10060
+ import fs27 from "fs/promises";
10061
+ import os4 from "os";
10062
+ import path36 from "path";
10063
+ import { execFile as execFile12 } from "child_process";
10064
+ import { promisify as promisify12 } from "util";
10065
+ import { createHash as createHash7, randomUUID as randomUUID7 } from "crypto";
10066
+ import { z as z25 } from "zod";
10067
+ var execute2 = promisify12(execFile12);
10068
+ var pathChangeSchema = z25.discriminatedUnion("kind", [
10069
+ z25.object({ kind: z25.literal("profile"), file: z25.string(), block: z25.string(), created: z25.boolean() }),
10070
+ z25.object({ kind: z25.literal("windows"), entry: z25.string(), created: z25.boolean() })
10071
+ ]);
10072
+ var quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
10073
+ async function configurePath(bin, previous, save, options = {}) {
10074
+ const env = options.env ?? process.env, platform = options.platform ?? process.platform;
10075
+ const changes = [...previous];
10076
+ const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
10077
+ const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path36.posix.isAbsolute(entry) && path36.posix.resolve(entry) === path36.posix.resolve(bin));
10078
+ 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";
10079
+ const message2 = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
10080
+ const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
10081
+ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
10082
+ `}${setupHint}` });
10083
+ if (env.MASON_NO_MODIFY_PATH === "1") return manual("Automatic PATH setup is disabled (MASON_NO_MODIFY_PATH=1).");
10084
+ if (/[\r\n\0]/.test(bin) || bin.includes(platform === "win32" ? ";" : ":")) return manual("This installation path cannot be represented safely in PATH.");
10085
+ try {
10086
+ if (platform === "win32") {
10087
+ const current = await windowsPath("read");
10088
+ if (!hasWindowsEntry(current.value, bin)) {
10089
+ if (!changes.some((c) => c.kind === "windows" && c.entry === bin)) {
10090
+ changes.push({ kind: "windows", entry: bin, created: current.value === null });
10091
+ await save(changes);
10092
+ }
10093
+ await windowsPath("add", bin, current);
10094
+ }
10095
+ return { status: "configured", changes, message: message2 };
10096
+ }
10097
+ const shell = path36.basename(env.SHELL ?? os4.userInfo().shell ?? "");
10098
+ const userHome = options.userHome ?? os4.homedir();
10099
+ let profiles2;
10100
+ if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
10101
+ if (env.MASON_PROFILE) profiles2 = [path36.resolve(env.MASON_PROFILE)];
10102
+ else if (shell === "zsh") profiles2 = [path36.join(env.ZDOTDIR || userHome, ".zshrc")];
10103
+ else if (shell === "sh") profiles2 = [path36.join(userHome, ".profile")];
10104
+ else {
10105
+ const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path36.join(userHome, name));
10106
+ let login = candidates[2];
10107
+ for (const file of candidates) if (await fs27.lstat(file).then(() => true, (error) => {
10108
+ if (error.code === "ENOENT") return false;
10109
+ throw error;
10110
+ })) {
10111
+ login = file;
10112
+ break;
10113
+ }
10114
+ profiles2 = [path36.join(userHome, ".bashrc"), login];
10115
+ }
10116
+ const marker = `# >>> mason PATH ${createHash7("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
10117
+ for (const profile of profiles2) {
10118
+ const stat = await fs27.lstat(profile).catch((error) => {
10119
+ if (error.code === "ENOENT") return null;
10120
+ throw error;
10121
+ });
10122
+ const file = stat?.isSymbolicLink() ? await fs27.realpath(profile) : path36.resolve(profile);
10123
+ const original = await fs27.readFile(file).catch((error) => {
10124
+ if (error.code === "ENOENT" && !stat) return null;
10125
+ throw error;
10126
+ });
10127
+ if (original && !Buffer.from(original.toString("utf8")).equals(original)) return manual(`Shell profile is not UTF-8: ${profile}.`);
10128
+ const text2 = original?.toString("utf8") ?? "";
10129
+ const owned = changes.find((c) => c.kind === "profile" && c.file === file);
10130
+ if (owned && text2.includes(owned.block)) continue;
10131
+ if (text2.includes(marker) || owned && text2.includes("# <<< mason PATH")) return manual(`Mason's PATH block was edited; retained ${profile}.`);
10132
+ const newline = text2.includes("\r\n") ? "\r\n" : "\n";
10133
+ const block = owned?.block ?? ("\n" + marker + '\ncase ":${PATH-}:" in\n *:' + quote(bin) + ":*) ;;\n *) export PATH=" + quote(bin) + ':"${PATH-}" ;;\nesac\n# <<< mason PATH <<<\n').replaceAll("\n", newline);
10134
+ if (!owned) {
10135
+ changes.push({ kind: "profile", file, block, created: original === null });
10136
+ await save(changes);
10137
+ }
10138
+ await fs27.mkdir(path36.dirname(file), { recursive: true });
10139
+ const handle = await fs27.open(file, original === null ? "ax+" : "a+", 384);
10140
+ try {
10141
+ if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
10142
+ await handle.writeFile(block);
10143
+ await handle.sync();
10144
+ } finally {
10145
+ await handle.close();
10146
+ }
10147
+ }
10148
+ return { status: "configured", changes, message: message2 };
10149
+ } catch (error) {
10150
+ return manual(`Could not configure PATH: ${error instanceof Error ? error.message : String(error)}`);
10151
+ }
10152
+ }
10153
+ var normalizeWindowsEntry = (value) => path36.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
10154
+ var hasWindowsEntry = (value, entry, env = process.env) => {
10155
+ const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
10156
+ const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
10157
+ return (value ?? "").split(";").some((part) => normalizeWindowsEntry(expand(part)) === normalizeWindowsEntry(entry));
10158
+ };
10159
+ async function windowsPath(action, entry = "", expected, removeEmpty = false) {
10160
+ const payload = Buffer.from(JSON.stringify({ action, entry, expected, removeEmpty })).toString("base64");
10161
+ const script = `$ErrorActionPreference = 'Stop'
10162
+ [Console]::OutputEncoding = New-Object Text.UTF8Encoding($false)
10163
+ $p = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${payload}')) | ConvertFrom-Json
10164
+ $key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment')
10165
+ try {
10166
+ $value = $key.GetValue('Path', $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
10167
+ $kind = if ($null -eq $value) { $null } else { $key.GetValueKind('Path').ToString() }
10168
+ if ($null -ne $kind -and $kind -notin @('String','ExpandString')) { throw 'User PATH is not a string.' }
10169
+ if ($p.action -eq 'read') { @{ value = $value; kind = $kind } | ConvertTo-Json -Compress; exit 0 }
10170
+ if ($p.expected -and ($value -cne $p.expected.value -or $kind -cne $p.expected.kind)) { throw 'User PATH changed concurrently; retry the operation.' }
10171
+ $parts = if ($null -eq $value -or $value -eq '') { @() } else { @($value.Split(';')) }
10172
+ if ($p.action -eq 'add') { $next = (@($p.entry) + $parts) -join ';' }
10173
+ else { $next = (@($parts | Where-Object { $_ -cne $p.entry })) -join ';' }
10174
+ if ($next -cne $value) {
10175
+ $writeKind = if ($kind) { [Microsoft.Win32.RegistryValueKind]$kind } else { [Microsoft.Win32.RegistryValueKind]::ExpandString }
10176
+ if ($p.action -eq 'remove' -and $p.removeEmpty -and $next -eq '') { $key.DeleteValue('Path', $false) }
10177
+ else { $key.SetValue('Path', $next, $writeKind) }
10178
+ Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class MasonEnvironment { [DllImport("user32.dll", CharSet=CharSet.Unicode, SetLastError=true)] public static extern IntPtr SendMessageTimeout(IntPtr h, uint m, UIntPtr w, string l, uint f, uint t, out UIntPtr r); }'
10179
+ $result = [UIntPtr]::Zero
10180
+ [void][MasonEnvironment]::SendMessageTimeout([IntPtr]0xffff, 0x1a, [UIntPtr]::Zero, 'Environment', 2, 1000, [ref]$result)
10181
+ }
10182
+ @{ value = $next; kind = $kind } | ConvertTo-Json -Compress
10183
+ } finally { $key.Dispose() }
10184
+ `;
10185
+ const { stdout } = await execute2("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { windowsHide: true, timeout: 15e3, maxBuffer: 1024 * 1024 });
10186
+ return JSON.parse(stdout.replace(/^\uFEFF/, "").trim());
10187
+ }
10188
+
10189
+ // src/distribution/state.ts
10190
+ var releaseVersion = z26.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
10191
+ var bundleId = z26.string().regex(/^[a-f0-9]{24}$/);
10192
+ var updatePolicySchema = z26.object({
10193
+ enabled: z26.boolean(),
10194
+ revision: z26.string(),
10195
+ pinnedVersion: releaseVersion.optional(),
10196
+ mirror: z26.string().optional()
10197
+ });
10198
+ var recordSchema3 = z26.object({
10199
+ format: z26.literal(1),
10200
+ home: z26.string(),
10201
+ bin: z26.string(),
10202
+ current: bundleId,
10203
+ version: releaseVersion,
10204
+ launchers: z26.record(z26.string()),
10205
+ previousLaunchers: z26.record(z26.string()).optional(),
10206
+ pathChanges: z26.array(pathChangeSchema).optional(),
10207
+ updates: updatePolicySchema.optional(),
10208
+ previous: z26.object({ id: bundleId, version: releaseVersion }).optional(),
10209
+ pending: z26.object({ id: bundleId, version: releaseVersion, policyRevision: z26.string() }).optional()
10210
+ });
10211
+ async function readInstallation(home) {
10212
+ const raw = await readStoreJson(home, "install.json");
10213
+ if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
10214
+ const record = recordSchema3.parse(raw);
10215
+ if (record.home !== await fs28.realpath(home)) throw new Error("Invalid standalone installation receipt.");
10216
+ return record;
10217
+ }
10218
+ function standaloneLocation() {
10219
+ const bundle = path37.resolve(path37.dirname(fileURLToPath(import.meta.url)), "../..");
10220
+ const home = path37.dirname(path37.dirname(bundle));
10221
+ if (path37.dirname(bundle) !== path37.join(home, "versions") || !/^[a-f0-9]{24}$/.test(path37.basename(bundle))) {
10222
+ return null;
10223
+ }
10224
+ return { home, bundle };
10225
+ }
10226
+ async function currentInstallation() {
10227
+ const location2 = standaloneLocation();
10228
+ if (!location2) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
10229
+ const { home, bundle } = location2;
10230
+ const record = await readInstallation(home);
10231
+ return { home, bundle, record };
10232
+ }
10233
+ function automaticUpdatesAllowed(record, env = process.env) {
10234
+ return record.updates?.enabled === true && !record.updates.pinnedVersion && !record.updates.mirror && !env.CI && env.MASON_NO_AUTO_UPDATE !== "1" && !env.MASON_RELEASE_BASE && !env.MASON_VERSION;
10235
+ }
10236
+ function newerStableVersion(candidate, installed) {
10237
+ if (!/^\d+\.\d+\.\d+$/.test(candidate)) return false;
10238
+ const a = candidate.split(".").map(BigInt), b = installed.split("-")[0].split(".").map(BigInt);
10239
+ for (let i = 0; i < 3; i++) {
10240
+ if (a[i] !== b[i]) return a[i] > b[i];
10241
+ }
10242
+ return installed.includes("-");
10243
+ }
10244
+
10245
+ // src/distribution/bundle.ts
10246
+ init_storage();
10247
+ import fs29 from "fs/promises";
10248
+ import path38 from "path";
10249
+ import { createHash as createHash8 } from "crypto";
10250
+ import { z as z27 } from "zod";
10251
+ var targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
10252
+ var digest2 = z27.string().regex(/^[a-f0-9]{64}$/);
10253
+ var bundleSchema = z27.object({
10254
+ format: z27.literal(1),
10255
+ version: z27.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
10256
+ target: z27.enum(targets),
10257
+ nodeVersion: z27.string(),
10258
+ files: z27.record(digest2)
10259
+ }).strict();
10260
+ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
10261
+ async function readBundle(root) {
10262
+ const bytes = await fs29.readFile(await storePath(root, "bundle.json"));
10263
+ const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
10264
+ const windows = manifest.target.startsWith("win32-");
10265
+ for (const file of Object.keys(manifest.files)) {
10266
+ if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path38.isAbsolute(file)) {
10267
+ throw new Error("Unsafe path in Mason bundle: " + file);
10268
+ }
10269
+ }
10270
+ 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"]) {
10271
+ if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
10272
+ }
10273
+ return { manifest, manifestHash: sha256(bytes) };
10274
+ }
10275
+ async function verifyBundle(root, expectedHash) {
10276
+ const bundle = await readBundle(root);
10277
+ if (expectedHash && bundle.manifestHash !== expectedHash) throw new Error("Mason bundle manifest changed.");
10278
+ 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.");
10279
+ const files = Object.keys(bundle.manifest.files);
10280
+ for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
10281
+ if (sha256(await fs29.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
10282
+ }));
10283
+ return bundle;
10284
+ }
10285
+
10286
+ // src/distribution/runtime.ts
10287
+ init_store();
10288
+ init_storage();
10289
+ import fs30 from "fs/promises";
10290
+ import { unlinkSync } from "fs";
10291
+ import os6 from "os";
10292
+ import path39 from "path";
10293
+ import { randomUUID as randomUUID8 } from "crypto";
10294
+ import { z as z28 } from "zod";
10295
+ var leaseSchema = z28.object({ pid: z28.number().int().positive(), host: z28.string(), bundle: z28.string().regex(/^[a-f0-9]{24}$/) });
10296
+
10297
+ // src/distribution/install.ts
10298
+ import fs32 from "fs/promises";
10299
+ import os8 from "os";
10300
+ import path40 from "path";
10301
+ import { execFile as execFile13, spawn as spawn2 } from "child_process";
10302
+ import { promisify as promisify13 } from "util";
10303
+ import { randomUUID as randomUUID10 } from "crypto";
10304
+ init_storage();
10305
+ init_store();
10306
+
10307
+ // src/distribution/sessions.ts
10308
+ init_storage();
10309
+ import fs31 from "fs/promises";
10310
+ import os7 from "os";
10311
+ import { randomUUID as randomUUID9 } from "crypto";
10312
+ import { z as z29 } from "zod";
10313
+ var leaseSchema2 = z29.object({ pid: z29.number().int().positive(), host: z29.string() });
10314
+ async function hasRunningMcp(home) {
10315
+ const directory = await storePath(home, "sessions");
10316
+ const files = await fs31.readdir(directory).catch((error) => {
10317
+ if (error.code === "ENOENT") return [];
10318
+ throw error;
10319
+ });
10320
+ for (const name of files) {
10321
+ if (!/^[a-f0-9-]+\.json$/.test(name)) return true;
10322
+ const relative = "sessions/" + name;
10323
+ try {
10324
+ const raw = await readStoreJson(home, relative);
10325
+ if (raw === null) continue;
10326
+ const lease = leaseSchema2.parse(raw);
10327
+ if (lease.host !== os7.hostname()) return true;
10328
+ try {
10329
+ process.kill(lease.pid, 0);
10330
+ return true;
10331
+ } catch (error) {
10332
+ if (error.code !== "ESRCH") return true;
10333
+ }
10334
+ await fs31.rm(await storePath(home, relative), { force: true });
10335
+ } catch {
10336
+ return true;
10337
+ }
10338
+ }
10339
+ return false;
10340
+ }
10341
+ async function registerMcp(home) {
10342
+ const name = `sessions/${randomUUID9()}.json`;
10343
+ await writeStoreJson(home, name, { pid: process.pid, host: os7.hostname() });
10344
+ return async () => {
10345
+ await fs31.rm(await storePath(home, name), { force: true }).catch(() => {
10346
+ });
10347
+ };
10348
+ }
10349
+
10350
+ // src/distribution/install.ts
10351
+ var shQuote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
10352
+ var psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
10353
+ var 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';
10354
+ var launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
10355
+ async function replace(file, content2, mode = 384) {
10356
+ const temp = file + ".tmp-" + randomUUID10();
10357
+ try {
10358
+ await fs32.writeFile(temp, content2, { mode, flag: "wx" });
10359
+ await fs32.rename(temp, file);
10360
+ } finally {
10361
+ await fs32.rm(temp, { force: true });
10362
+ }
10363
+ }
10364
+ function assertUpdatePolicy(record, version, revision) {
10365
+ if (!record || !automaticUpdatesAllowed(record, { ...process.env, MASON_VERSION: "" }) || record.updates?.revision !== revision || !newerStableVersion(version, record.version)) {
10366
+ throw new Error("Update policy or installed version changed; download was not activated.");
10367
+ }
10368
+ }
10369
+ async function installLocked2(home, source, bundle, progress, options, alreadyStaged = false) {
10370
+ const raw = await readStoreJson(home, "install.json");
10371
+ const previous = raw === null ? null : recordSchema3.parse(raw);
10372
+ if (options.stageOnly) assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
10373
+ if (!previous) for (const file of await fs32.readdir(home)) {
10374
+ if (file === ".install-lock") continue;
10375
+ const directory = path40.join(home, file);
10376
+ const stat = await fs32.lstat(directory);
10377
+ if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs32.readdir(directory)).length) {
10378
+ throw new Error("Mason installation directory is not empty and has no ownership receipt.");
10379
+ }
10380
+ }
10381
+ const selectedBin = path40.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path40.join(home, "bin") : path40.join(os8.homedir(), ".local/bin")));
10382
+ await fs32.mkdir(selectedBin, { recursive: true });
10383
+ const bin = await fs32.realpath(selectedBin);
10384
+ if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
10385
+ const id = bundle.manifestHash.slice(0, 24);
10386
+ const destination = await storePath(home, "versions/" + id);
10387
+ if (options.stageOnly) {
10388
+ const receipt2 = { ...previous, pending: { id, version: bundle.manifest.version, policyRevision: options.policyRevision } };
10389
+ await writeStoreJson(home, "install.json", receipt2);
10390
+ return { home, bin, version: bundle.manifest.version, previousVersion: previous.version, path: { message: "Update staged for a future MCP launch." } };
10391
+ }
10392
+ const launchers = process.platform === "win32" ? {
10393
+ "mason.cmd": WINDOWS_BATCH_LAUNCHER,
10394
+ "mason-launcher.ps1": `$ErrorActionPreference = 'Stop'
10395
+ $previousToken = $env:MASON_UNINSTALL_TOKEN
10396
+ $token = [Guid]::NewGuid().ToString()
10397
+ $env:MASON_UNINSTALL_TOKEN = $token
10398
+ try {
10399
+ & ${psQuote(path40.join(destination, "node.exe"))} ${psQuote(path40.join(destination, "app/dist/mason.js"))} @args
10400
+ $code = $LASTEXITCODE
10401
+ if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
10402
+ if ([IO.File]::ReadAllText(${psQuote(path40.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
10403
+ Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
10404
+ Write-Output ${psQuote("Removed standalone installation: " + home)}
10405
+ }
10406
+ exit $code
10407
+ } catch { [Console]::Error.WriteLine($_.Exception.Message); exit 2 }
10408
+ finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
10409
+ `
10410
+ } : { mason: `#!/bin/sh
10411
+ exec ${shQuote(path40.join(destination, "node"))} ${shQuote(path40.join(destination, "app/dist/mason.js"))} "$@"
10412
+ ` };
10413
+ for (const [name, content2] of Object.entries(launchers)) {
10414
+ const file = await storePath(bin, name);
10415
+ const old = await fs32.readFile(file, "utf8").catch((error) => {
10416
+ if (error.code === "ENOENT") return null;
10417
+ throw error;
10418
+ });
10419
+ if (old !== null && old !== previous?.launchers[name] && old !== previous?.previousLaunchers?.[name] && old !== content2) throw new Error("Refusing to replace an unrelated or edited launcher: " + file);
10420
+ }
10421
+ const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
10422
+ const checkRetired = async (name, expected) => {
10423
+ if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
10424
+ const actual = await fs32.readFile(await storePath(bin, name), "utf8").catch((error) => {
10425
+ if (error.code === "ENOENT") return null;
10426
+ throw error;
10427
+ });
10428
+ if (actual !== null && actual !== expected) throw new Error("Retired launcher was edited; retained: " + name);
10429
+ };
10430
+ for (const [name, expected] of retired) await checkRetired(name, expected);
10431
+ progress?.step(`Installing Mason ${bundle.manifest.version}`);
10432
+ const updates = options.preservePolicy && previous?.updates ? { ...previous.updates } : {
10433
+ ...previous?.updates ?? { enabled: !previous && !process.env.CI && process.env.MASON_NO_AUTO_UPDATE !== "1" },
10434
+ revision: randomUUID10(),
10435
+ pinnedVersion: process.env.MASON_VERSION?.replace(/^v/, "") || void 0,
10436
+ mirror: process.env.MASON_RELEASE_BASE || previous?.updates?.mirror
10437
+ };
10438
+ if (options.rollback) {
10439
+ updates.pinnedVersion = bundle.manifest.version;
10440
+ updates.revision = randomUUID10();
10441
+ }
10442
+ const receipt = {
10443
+ format: 1,
10444
+ home,
10445
+ bin,
10446
+ current: id,
10447
+ version: bundle.manifest.version,
10448
+ launchers,
10449
+ pathChanges: previous?.pathChanges ?? [],
10450
+ updates,
10451
+ previous: previous && previous.current !== id ? { id: previous.current, version: previous.version } : previous?.previous
10452
+ };
10453
+ await writeStoreJson(home, "install.json", previous ? { ...previous, previousLaunchers: launchers } : receipt);
10454
+ try {
10455
+ if (!alreadyStaged) await copyBundle(home, source, destination, bundle);
10456
+ for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
10457
+ for (const [name, expected] of retired) {
10458
+ await checkRetired(name, expected);
10459
+ await fs32.rm(await storePath(bin, name), { force: true });
10460
+ }
10461
+ await writeStoreJson(home, "install.json", receipt);
10462
+ } catch (error) {
10463
+ if (previous) {
10464
+ for (const [name, content2] of Object.entries(previous.launchers)) await replace(await storePath(bin, name, true), content2, 493);
10465
+ await writeStoreJson(home, "install.json", previous);
10466
+ }
10467
+ throw error;
10468
+ }
10469
+ if (options.preservePolicy) return { home, bin, version: receipt.version, previousVersion: previous?.version, path: { message: "Installation selected." } };
10470
+ progress?.step("Checking terminal command availability");
10471
+ const pathResult = await configurePath(bin, receipt.pathChanges ?? [], async (changes) => {
10472
+ receipt.pathChanges = changes;
10473
+ await writeStoreJson(home, "install.json", receipt);
10474
+ });
10475
+ return { home, bin, version: bundle.manifest.version, previousVersion: previous?.version, path: pathResult };
10476
+ }
10477
+ async function copyBundle(home, source, destination, bundle) {
10478
+ const exists = await fs32.lstat(destination).then(() => true, (error) => {
10479
+ if (error.code === "ENOENT") return false;
10480
+ throw error;
10481
+ });
10482
+ if (exists) {
10483
+ await verifyBundle(destination, bundle.manifestHash);
10484
+ return;
10485
+ }
10486
+ const stage = await storePath(home, "versions/.install-" + randomUUID10(), true);
10487
+ await fs32.mkdir(stage);
10488
+ try {
10489
+ for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
10490
+ const from = await storePath(source, file), to = await storePath(stage, file, true);
10491
+ await fs32.copyFile(from, to);
10492
+ await fs32.chmod(to, (await fs32.stat(from)).mode & 511);
10493
+ }
10494
+ await verifyBundle(stage, bundle.manifestHash);
10495
+ try {
10496
+ await fs32.rename(stage, destination);
10497
+ } catch (error) {
10498
+ try {
10499
+ await verifyBundle(destination, bundle.manifestHash);
10500
+ } catch {
10501
+ throw error;
10502
+ }
10503
+ }
10504
+ } finally {
10505
+ await fs32.rm(stage, { recursive: true, force: true });
10506
+ }
10507
+ }
10508
+ async function prepareStandaloneMcp(home) {
10509
+ const before = await readInstallation(home), candidate = before.pending;
10510
+ let verified, verificationError;
10511
+ if (candidate && automaticUpdatesAllowed(before) && !await hasRunningMcp(home)) {
10512
+ try {
10513
+ verified = await verifyBundle(await storePath(home, "versions/" + candidate.id));
10514
+ if (verified.manifestHash.slice(0, 24) !== candidate.id || verified.manifest.version !== candidate.version) throw new Error("Staged update changed.");
10515
+ } catch (error) {
10516
+ verificationError = error;
10517
+ }
10518
+ }
10519
+ return withLock(home, ".install-lock", async () => {
10520
+ let record = await readInstallation(home);
10521
+ const pending = record.pending;
10522
+ if (pending && candidate?.id === pending.id && (verified || verificationError) && automaticUpdatesAllowed(record) && pending.policyRevision === record.updates?.revision && newerStableVersion(pending.version, record.version) && !await hasRunningMcp(home)) {
10523
+ try {
10524
+ if (verificationError) throw verificationError;
10525
+ const source = await storePath(home, "versions/" + pending.id);
10526
+ await installLocked2(home, source, verified, void 0, { preservePolicy: true }, true);
10527
+ record = await readInstallation(home);
10528
+ } catch (error) {
10529
+ record = await readInstallation(home);
10530
+ delete record.pending;
10531
+ await writeStoreJson(home, "install.json", record);
10532
+ await writeStoreJson(home, "update-status.json", { lastAttempt: Date.now(), error: String(error) });
10533
+ }
10534
+ }
10535
+ const release = await registerMcp(home);
10536
+ return { bundle: await storePath(home, "versions/" + record.current), release };
10537
+ });
10538
+ }
10539
+
10540
+ // src/distribution/updates.ts
10541
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
10542
+ var RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
10543
+ var releaseListSchema = z30.array(z30.object({ tag_name: z30.string(), draft: z30.boolean(), prerelease: z30.boolean(), published_at: z30.string().nullable() }));
10544
+ var updateManifestSchema = z30.object({
10545
+ format: z30.literal(1),
10546
+ version: z30.string().regex(/^\d+\.\d+\.\d+$/),
10547
+ publishedAt: z30.string().datetime(),
10548
+ assets: z30.record(z30.string().regex(/^[a-f0-9]{64}$/))
10549
+ }).strict();
10550
+ var statusSchema2 = z30.object({ lastAttempt: z30.number(), availableVersion: z30.string().optional(), error: z30.string().optional() });
10551
+ async function scheduleAutomaticUpdate() {
10552
+ try {
10553
+ const { home, record } = await currentInstallation();
10554
+ if (!automaticUpdatesAllowed(record)) return;
10555
+ const status = statusSchema2.safeParse(await readStoreJson(home, "update-status.json"));
10556
+ const now = Date.now();
10557
+ if (status.success && status.data.lastAttempt <= now && now - status.data.lastAttempt < CHECK_INTERVAL_MS) return;
10558
+ const bundle = await storePath(home, "versions/" + record.current);
10559
+ const child = spawn3(
10560
+ path41.join(bundle, process.platform === "win32" ? "node.exe" : "node"),
10561
+ [path41.join(bundle, "app/dist/mason.js"), "internal-update"],
10562
+ { cwd: home, env: process.env, stdio: "ignore", detached: true, windowsHide: true }
10563
+ );
10564
+ child.on("error", () => {
10565
+ });
10566
+ child.unref();
10567
+ } catch {
10568
+ }
10569
+ }
10570
+ async function startMcpWithUpdates(start) {
10571
+ const current = standaloneLocation();
10572
+ if (!current) {
10573
+ await start();
10574
+ return;
10575
+ }
10576
+ const session = await prepareStandaloneMcp(current.home);
10577
+ if (session.bundle !== current.bundle) {
10578
+ const child = spawn3(
10579
+ path41.join(session.bundle, process.platform === "win32" ? "node.exe" : "node"),
10580
+ [path41.join(session.bundle, "app/dist/mason-mcp.js"), ...process.argv.slice(2)],
10581
+ { stdio: "inherit", windowsHide: true }
10582
+ );
10583
+ const interrupt = () => {
10584
+ child.kill("SIGINT");
10585
+ }, terminate = () => {
10586
+ child.kill("SIGTERM");
10587
+ };
10588
+ process.on("SIGINT", interrupt);
10589
+ process.on("SIGTERM", terminate);
10590
+ try {
10591
+ process.exitCode = await new Promise((resolve2, reject) => {
10592
+ child.on("error", reject);
10593
+ child.on("exit", (code) => resolve2(code ?? 1));
10594
+ });
10595
+ } finally {
10596
+ process.off("SIGINT", interrupt);
10597
+ process.off("SIGTERM", terminate);
10598
+ await session.release();
10599
+ }
10600
+ return;
10601
+ }
10602
+ const timer = setInterval(() => {
10603
+ void scheduleAutomaticUpdate();
10604
+ }, 60 * 60 * 1e3);
10605
+ timer.unref();
10606
+ const stop = () => {
10607
+ clearInterval(timer);
10608
+ void session?.release();
10609
+ };
10610
+ try {
10611
+ await start(stop);
10612
+ void scheduleAutomaticUpdate();
10613
+ } catch (error) {
10614
+ stop();
10615
+ throw error;
10616
+ }
10617
+ }
10618
+
9979
10619
  // bin/mason-mcp.ts
9980
- startMcpServer().catch((err) => {
10620
+ startMcpWithUpdates(startMcpServer).catch((err) => {
9981
10621
  process.stderr.write(`Mason MCP server error: ${err}
9982
10622
  `);
9983
10623
  process.exit(1);