mason-context 0.17.4 → 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) === "&&") {
@@ -1098,6 +1099,105 @@ var init_scope = __esm({
1098
1099
  }
1099
1100
  });
1100
1101
 
1102
+ // src/utils/store-lock.ts
1103
+ import fs6 from "fs/promises";
1104
+ import os from "os";
1105
+ async function lockDiagnostic(file) {
1106
+ let reason2;
1107
+ try {
1108
+ const owner = JSON.parse(await fs6.readFile(file, "utf8"));
1109
+ if (!owner || typeof owner.host !== "string" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
1110
+ reason2 = "Lock owner data is incomplete or malformed.";
1111
+ } else if (owner.host !== os.hostname()) {
1112
+ reason2 = "The lock belongs to another host; its process cannot be checked locally.";
1113
+ } else {
1114
+ try {
1115
+ process.kill(owner.pid, 0);
1116
+ reason2 = `Local owner PID ${owner.pid} is still running.`;
1117
+ } catch (error) {
1118
+ reason2 = error.code === "ESRCH" ? `Local owner PID ${owner.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.pid} could not be checked.`;
1119
+ }
1120
+ }
1121
+ } catch (error) {
1122
+ reason2 = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
1123
+ }
1124
+ if (await fs6.lstat(file + ".reclaim").then(() => true, () => false)) {
1125
+ reason2 += " A recovery guard also needs inspection: " + file + ".reclaim.";
1126
+ }
1127
+ return reason2 + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
1128
+ }
1129
+ async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason store") {
1130
+ const file = await storePath(root, directory + "/lock", true);
1131
+ const deadline = Date.now() + waitMs;
1132
+ let handle;
1133
+ while (!handle) {
1134
+ try {
1135
+ handle = await fs6.open(file, "wx", 384);
1136
+ try {
1137
+ await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
1138
+ } catch (error) {
1139
+ await handle.close().catch(() => {
1140
+ });
1141
+ handle = void 0;
1142
+ await fs6.rm(file, { force: true }).catch(() => {
1143
+ });
1144
+ throw error;
1145
+ }
1146
+ } catch (error) {
1147
+ if (error.code !== "EEXIST") throw error;
1148
+ try {
1149
+ const owner = JSON.parse(await fs6.readFile(file, "utf8"));
1150
+ if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
1151
+ try {
1152
+ process.kill(owner.pid, 0);
1153
+ } catch (probe) {
1154
+ if (probe.code === "ESRCH") {
1155
+ const reclaim = file + ".reclaim";
1156
+ let guard;
1157
+ try {
1158
+ guard = await fs6.open(reclaim, "wx", 384);
1159
+ const current = JSON.parse(await fs6.readFile(file, "utf8"));
1160
+ if (current.pid === owner.pid && current.host === owner.host) await fs6.unlink(file);
1161
+ } finally {
1162
+ if (guard) {
1163
+ await guard.close();
1164
+ await fs6.rm(reclaim, { force: true });
1165
+ }
1166
+ }
1167
+ }
1168
+ }
1169
+ }
1170
+ } catch {
1171
+ }
1172
+ if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
1173
+ await new Promise((resolve2) => setTimeout(resolve2, 40));
1174
+ }
1175
+ }
1176
+ try {
1177
+ return await run();
1178
+ } finally {
1179
+ await handle.close();
1180
+ await fs6.unlink(file);
1181
+ }
1182
+ }
1183
+ var init_store_lock = __esm({
1184
+ "src/utils/store-lock.ts"() {
1185
+ "use strict";
1186
+ init_storage();
1187
+ }
1188
+ });
1189
+
1190
+ // src/snapshot/lock.ts
1191
+ function withSnapshotWrite(root, run, waitMs = 5e3) {
1192
+ return withStoreLock(root, ".mason/local/snapshot-write", run, waitMs, "Snapshot store");
1193
+ }
1194
+ var init_lock = __esm({
1195
+ "src/snapshot/lock.ts"() {
1196
+ "use strict";
1197
+ init_store_lock();
1198
+ }
1199
+ });
1200
+
1101
1201
  // src/test-map.ts
1102
1202
  var test_map_exports = {};
1103
1203
  __export(test_map_exports, {
@@ -1203,8 +1303,15 @@ async function inspectSnapshot(rootDir) {
1203
1303
  }] };
1204
1304
  }
1205
1305
  }
1206
- async function saveSnapshot(rootDir, snapshot) {
1207
- await writeStoreJson(rootDir, ".mason/snapshot.json", snapshotSchema.parse(snapshot));
1306
+ async function updateSnapshot(rootDir, update) {
1307
+ return withSnapshotWrite(rootDir, async () => {
1308
+ const change = await update(await loadSnapshot(rootDir));
1309
+ if (change.snapshot) {
1310
+ await writeStoreJson(rootDir, ".mason/snapshot.json", snapshotSchema.parse(change.snapshot));
1311
+ await change.afterSave?.();
1312
+ }
1313
+ return change.result;
1314
+ });
1208
1315
  }
1209
1316
  async function getCurrentGitHash(rootDir) {
1210
1317
  try {
@@ -1277,6 +1384,7 @@ var init_snapshot = __esm({
1277
1384
  init_files();
1278
1385
  init_files();
1279
1386
  init_storage();
1387
+ init_lock();
1280
1388
  init_paths();
1281
1389
  init_test_map();
1282
1390
  repoPath = z.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
@@ -1284,6 +1392,7 @@ var init_snapshot = __esm({
1284
1392
  refreshedHash: z.string().optional(),
1285
1393
  verifiedAt: z.string().optional(),
1286
1394
  verifiedHash: z.string().optional(),
1395
+ verificationToken: z.string().optional(),
1287
1396
  verificationFailed: z.boolean().optional(),
1288
1397
  verificationNote: z.string().optional()
1289
1398
  };
@@ -1311,7 +1420,7 @@ var init_snapshot = __esm({
1311
1420
  });
1312
1421
 
1313
1422
  // src/drift/drift.ts
1314
- import fs6 from "fs/promises";
1423
+ import fs7 from "fs/promises";
1315
1424
  import path11 from "path";
1316
1425
  function parseChanges(output) {
1317
1426
  const fields = output.split("\0");
@@ -1379,7 +1488,7 @@ async function findGhostFiles(resolvedRoot, mappedFiles) {
1379
1488
  const ghosts = [];
1380
1489
  for (const file of mappedFiles) {
1381
1490
  try {
1382
- await fs6.access(path11.join(resolvedRoot, file));
1491
+ await fs7.access(path11.join(resolvedRoot, file));
1383
1492
  } catch {
1384
1493
  ghosts.push(file);
1385
1494
  }
@@ -1561,7 +1670,7 @@ var init_deleted_reference = __esm({
1561
1670
 
1562
1671
  // src/audit/checks/new-module.ts
1563
1672
  import fg4 from "fast-glob";
1564
- import fs7 from "fs/promises";
1673
+ import fs8 from "fs/promises";
1565
1674
  import path13 from "path";
1566
1675
  function escapeRegExp(text2) {
1567
1676
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -1613,7 +1722,7 @@ async function collectModuleCandidates(root, combinedDocs) {
1613
1722
  const byParent = /* @__PURE__ */ new Map();
1614
1723
  for (let offset = 0; offset < parents.length; offset += 16) {
1615
1724
  await Promise.all(parents.slice(offset, offset + 16).map(async (dir) => {
1616
- const entries = await fs7.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1725
+ const entries = await fs8.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1617
1726
  const dirs = entries.filter((entry) => (entry.isDirectory() || entry.isSymbolicLink() && sources.some((file) => file.startsWith(path13.posix.join(dir, entry.name) + "/"))) && !entry.name.startsWith(".") && !DIR_DENYLIST.has(entry.name)).map((entry) => path13.posix.join(dir, entry.name));
1618
1727
  byParent.set(dir, dirs);
1619
1728
  }));
@@ -2026,6 +2135,10 @@ var init_release_metadata = __esm({
2026
2135
  });
2027
2136
 
2028
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
+ }
2029
2142
  function manifestPathspecs(doc) {
2030
2143
  const scope = documentScope(doc);
2031
2144
  if (scope === ".") return MANIFEST_PATHSPECS;
@@ -2052,6 +2165,19 @@ async function checkDepsChanged(ctx) {
2052
2165
  reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
2053
2166
  });
2054
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;
2055
2181
  const range = await commitsTouchingSince(
2056
2182
  ctx.root,
2057
2183
  doc.lastCommit.hash,
@@ -2097,6 +2223,7 @@ var init_deps_changed = __esm({
2097
2223
  init_docs();
2098
2224
  init_release_metadata();
2099
2225
  init_git();
2226
+ init_inspection();
2100
2227
  init_checks();
2101
2228
  MANIFEST_COMMITS_CAP = 10;
2102
2229
  MANIFEST_PATHSPECS = [
@@ -2236,7 +2363,9 @@ function assessTrust(entry, freshness) {
2236
2363
  }
2237
2364
  function trustHint(states) {
2238
2365
  const parts = [];
2239
- if (states.some((s) => s.verification === "failed")) parts.push("Verification failed for returned entries; do not rely on those descriptions until corrected.");
2366
+ if (states.some((s) => s.verification === "failed" || s.recordedVerdict === "failed")) parts.push("Verification failed for returned entries; do not rely on those descriptions until corrected.");
2367
+ if (states.some((s) => s.verification === "stale")) parts.push("Recorded verification evidence changed; use verify_snapshot and review the current evidence before reusing those verdicts.");
2368
+ if (states.some((s) => s.verification === "unknown")) parts.push("Recorded verification evidence cannot be confirmed; inspect the files and obtain a fresh review before relying on those verdicts.");
2240
2369
  if (states.some((s) => s.freshness === "unknown")) parts.push("Freshness is unknown for some returned entries; inspect their files before relying on them.");
2241
2370
  if (states.some((s) => s.freshness === "changed")) parts.push("Some returned entries have changed files, including possible local edits; verify against the current code.");
2242
2371
  if (!parts.length) parts.push("No changes detected in the returned anchors. This does not prove the descriptions are correct.");
@@ -2461,7 +2590,7 @@ __export(decisions_exports, {
2461
2590
  upsertDecision: () => upsertDecision,
2462
2591
  withDecisionWrite: () => withDecisionWrite
2463
2592
  });
2464
- import fs8 from "fs/promises";
2593
+ import fs9 from "fs/promises";
2465
2594
  import path17 from "path";
2466
2595
  import { createHash } from "crypto";
2467
2596
  import { z as z3 } from "zod";
@@ -2470,7 +2599,7 @@ async function loadDecisionStore(rootDir) {
2470
2599
  const diagnostics = [];
2471
2600
  let entries;
2472
2601
  try {
2473
- entries = await fs8.readdir(await storePath(rootDir, ".mason/decisions"));
2602
+ entries = await fs9.readdir(await storePath(rootDir, ".mason/decisions"));
2474
2603
  } catch (error) {
2475
2604
  if (error.code !== "ENOENT") diagnostics.push({ path: ".mason/decisions", message: String(error) });
2476
2605
  return { records, diagnostics };
@@ -2529,7 +2658,7 @@ async function withDecisionWrite(root, operation) {
2529
2658
  const lockPath = await storePath(root, ".mason/decisions/.write-lock", true);
2530
2659
  let lock;
2531
2660
  try {
2532
- lock = await fs8.open(lockPath, "wx", 384);
2661
+ lock = await fs9.open(lockPath, "wx", 384);
2533
2662
  } catch (error) {
2534
2663
  if (error.code === "EEXIST") return { status: "error", error: "Decision store is locked by another write. Retry after it finishes; an abandoned .mason/decisions/.write-lock must be removed only after confirming no writer is running." };
2535
2664
  throw error;
@@ -2538,7 +2667,7 @@ async function withDecisionWrite(root, operation) {
2538
2667
  return await operation();
2539
2668
  } finally {
2540
2669
  await lock.close();
2541
- await fs8.unlink(lockPath);
2670
+ await fs9.unlink(lockPath);
2542
2671
  }
2543
2672
  }
2544
2673
  async function upsertDecision(rootDir, input) {
@@ -2560,7 +2689,7 @@ async function upsertDecision(rootDir, input) {
2560
2689
  if (input.files && files.length < input.files.length) warnings.push("some anchor paths were outside the repo or duplicated and were dropped");
2561
2690
  for (const file of files) {
2562
2691
  try {
2563
- await fs8.access(path17.join(rootDir, file));
2692
+ await fs9.access(path17.join(rootDir, file));
2564
2693
  } catch {
2565
2694
  warnings.push(`anchor file does not exist on disk: ${file}`);
2566
2695
  }
@@ -2910,8 +3039,6 @@ var init_findings = __esm({
2910
3039
  });
2911
3040
 
2912
3041
  // src/automation/store.ts
2913
- import fs9 from "fs/promises";
2914
- import os from "os";
2915
3042
  import { z as z5 } from "zod";
2916
3043
  function parseState(raw) {
2917
3044
  try {
@@ -2920,65 +3047,14 @@ function parseState(raw) {
2920
3047
  throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
2921
3048
  }
2922
3049
  }
2923
- async function withLock(root, directory, run, waitMs = 5e3) {
2924
- const file = await storePath(root, directory + "/lock", true);
2925
- const deadline = Date.now() + waitMs;
2926
- let handle;
2927
- while (!handle) {
2928
- try {
2929
- handle = await fs9.open(file, "wx", 384);
2930
- try {
2931
- await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
2932
- } catch (error) {
2933
- await handle.close().catch(() => {
2934
- });
2935
- handle = void 0;
2936
- await fs9.rm(file, { force: true }).catch(() => {
2937
- });
2938
- throw error;
2939
- }
2940
- } catch (error) {
2941
- if (error.code !== "EEXIST") throw error;
2942
- try {
2943
- const owner = JSON.parse(await fs9.readFile(file, "utf8"));
2944
- if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
2945
- try {
2946
- process.kill(owner.pid, 0);
2947
- } catch (probe) {
2948
- if (probe.code === "ESRCH") {
2949
- const reclaim = file + ".reclaim";
2950
- let guard;
2951
- try {
2952
- guard = await fs9.open(reclaim, "wx", 384);
2953
- const current = JSON.parse(await fs9.readFile(file, "utf8"));
2954
- if (current.pid === owner.pid && current.host === owner.host) await fs9.unlink(file);
2955
- } finally {
2956
- if (guard) {
2957
- await guard.close();
2958
- await fs9.rm(reclaim, { force: true });
2959
- }
2960
- }
2961
- }
2962
- }
2963
- }
2964
- } catch {
2965
- }
2966
- if (Date.now() >= deadline) throw new Error("Automation is busy or its lock needs inspection: " + file);
2967
- await new Promise((resolve2) => setTimeout(resolve2, 40));
2968
- }
2969
- }
2970
- try {
2971
- return await run();
2972
- } finally {
2973
- await handle.close();
2974
- await fs9.unlink(file);
2975
- }
3050
+ function withLock(root, directory, run, waitMs = 5e3) {
3051
+ return withStoreLock(root, directory, run, waitMs, "Automation");
2976
3052
  }
2977
3053
  var hostSchema, events, stateSchema;
2978
3054
  var init_store = __esm({
2979
3055
  "src/automation/store.ts"() {
2980
3056
  "use strict";
2981
- init_storage();
3057
+ init_store_lock();
2982
3058
  hostSchema = z5.enum(["claude", "codex"]);
2983
3059
  events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
2984
3060
  stateSchema = z5.object({
@@ -3510,7 +3586,7 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
3510
3586
  return {
3511
3587
  ...base,
3512
3588
  status: "review-required",
3513
- 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."
3514
3590
  };
3515
3591
  }
3516
3592
  return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
@@ -3701,7 +3777,7 @@ async function collectInputs(root) {
3701
3777
  git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
3702
3778
  git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
3703
3779
  ]) : ["", ""];
3704
- const common = [5, engineVersion, head, shallow, replacements, docContents];
3780
+ const common = [6, engineVersion, head, shallow, replacements, docContents];
3705
3781
  const keys = {
3706
3782
  "deleted-reference": hash([common, claims, docStatus]),
3707
3783
  "new-module": hash([common, modules]),
@@ -3761,7 +3837,7 @@ var init_evidence = __esm({
3761
3837
  init_dead_command();
3762
3838
  init_storage();
3763
3839
  init_advisory_review();
3764
- engineVersion = true ? "0.17.4" : "development";
3840
+ engineVersion = true ? "0.19.0" : "development";
3765
3841
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
3766
3842
  content = readAuditInput;
3767
3843
  cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
@@ -3771,18 +3847,49 @@ var init_evidence = __esm({
3771
3847
  // src/setup/model.ts
3772
3848
  var model_exports = {};
3773
3849
  __export(model_exports, {
3850
+ INACTIVE_HOSTS_PATH: () => INACTIVE_HOSTS_PATH,
3774
3851
  SETUP_PATH: () => SETUP_PATH,
3852
+ effectiveSetup: () => effectiveSetup,
3853
+ inactiveHostsSchema: () => inactiveHostsSchema,
3775
3854
  loadSetup: () => loadSetup,
3776
3855
  loadSetupReceipt: () => loadSetupReceipt,
3777
3856
  setupHostSchema: () => setupHostSchema,
3778
3857
  setupSchema: () => setupSchema
3779
3858
  });
3780
3859
  import { z as z9 } from "zod";
3860
+ import fs14 from "fs/promises";
3861
+ import path22 from "path";
3781
3862
  async function loadSetup(root) {
3782
3863
  const raw = await readStoreJson(root, SETUP_PATH);
3783
3864
  if (raw === null) return null;
3784
3865
  return setupSchema.parse(raw);
3785
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
+ }
3786
3893
  async function loadSetupReceipt(root, directory, host) {
3787
3894
  const raw = await readStoreJson(root, directory + "/setup-" + host + ".json");
3788
3895
  if (raw === null) return null;
@@ -3790,10 +3897,11 @@ async function loadSetupReceipt(root, directory, host) {
3790
3897
  if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
3791
3898
  return receipt;
3792
3899
  }
3793
- var setupHostSchema, setupSchema, SETUP_PATH, receiptSchema;
3900
+ var setupHostSchema, setupSchema, SETUP_PATH, INACTIVE_HOSTS_PATH, inactiveHostsSchema, receiptSchema;
3794
3901
  var init_model = __esm({
3795
3902
  "src/setup/model.ts"() {
3796
3903
  "use strict";
3904
+ init_evidence();
3797
3905
  init_storage();
3798
3906
  setupHostSchema = z9.object({
3799
3907
  configuredVersion: z9.string(),
@@ -3807,6 +3915,8 @@ var init_model = __esm({
3807
3915
  claude: setupHostSchema.optional()
3808
3916
  }) });
3809
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"])) });
3810
3920
  receiptSchema = z9.object({
3811
3921
  version: z9.literal(1),
3812
3922
  host: z9.enum(["codex", "claude"]),
@@ -3823,7 +3933,7 @@ var init_model = __esm({
3823
3933
  // src/setup/launcher.ts
3824
3934
  import { execFile as execFile2 } from "child_process";
3825
3935
  import { promisify as promisify2 } from "util";
3826
- import fs14 from "fs/promises";
3936
+ import fs15 from "fs/promises";
3827
3937
  function globalCommand(args, platform = process.platform) {
3828
3938
  return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args] } : { command: "mason", args };
3829
3939
  }
@@ -3853,12 +3963,124 @@ var init_launcher = __esm({
3853
3963
  "use strict";
3854
3964
  init_evidence();
3855
3965
  init_model();
3856
- executingVersion = () => true ? "0.17.4" : "development";
3966
+ executingVersion = () => true ? "0.19.0" : "development";
3857
3967
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3858
3968
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3859
3969
  }
3860
3970
  });
3861
3971
 
3972
+ // src/snapshot/review.ts
3973
+ import { createHash as createHash5 } from "crypto";
3974
+ function snapshotEntryContent(entry) {
3975
+ return {
3976
+ description: entry.description,
3977
+ files: "files" in entry ? entry.files : void 0,
3978
+ chain: "chain" in entry ? entry.chain : void 0,
3979
+ tests: "files" in entry ? entry.tests : void 0,
3980
+ type: "files" in entry ? normalizeFeatureType(entry.type) : void 0
3981
+ };
3982
+ }
3983
+ function reviewToken(kind, name, entry, evidence) {
3984
+ return createHash5("sha256").update(JSON.stringify({
3985
+ version: 1,
3986
+ kind,
3987
+ name,
3988
+ entry: snapshotEntryContent(entry),
3989
+ evidence
3990
+ })).digest("hex");
3991
+ }
3992
+ async function prepareSnapshotReview(access, kind, name, entry) {
3993
+ const files = "files" in entry ? entry.files : entry.chain;
3994
+ const evidence = [];
3995
+ const skeletons = [];
3996
+ for (const file of files.slice(0, VERIFY_MAX_FILES_PER_ENTRY)) {
3997
+ const full = await access.read(file);
3998
+ evidence.push({ path: file, hash: full ? sourceHash(full.content) : null });
3999
+ skeletons.push(full ? { path: full.path, content: full.content.slice(0, VERIFY_SKELETON_CHARS) } : { path: file, missing: true });
4000
+ }
4001
+ return { reviewToken: reviewToken(kind, name, entry, evidence), skeletons, truncated: files.length > VERIFY_MAX_FILES_PER_ENTRY };
4002
+ }
4003
+ function createSnapshotEvidenceReader(access) {
4004
+ const hashes = /* @__PURE__ */ new Map();
4005
+ return async (kind, name, entry) => {
4006
+ const files = "files" in entry ? entry.files : entry.chain;
4007
+ const evidence = [];
4008
+ for (const file of files.slice(0, VERIFY_MAX_FILES_PER_ENTRY)) {
4009
+ let hash2 = hashes.get(file);
4010
+ if (!hash2) {
4011
+ hash2 = access.read(file).then((full) => full ? sourceHash(full.content) : null);
4012
+ hashes.set(file, hash2);
4013
+ }
4014
+ evidence.push({ path: file, hash: await hash2 });
4015
+ }
4016
+ return {
4017
+ reviewToken: reviewToken(kind, name, entry, evidence),
4018
+ evidenceAvailable: evidence.length > 0 && evidence.every((file) => file.hash !== null)
4019
+ };
4020
+ };
4021
+ }
4022
+ var VERIFY_MAX_FILES_PER_ENTRY, VERIFY_SKELETON_CHARS, sourceHash;
4023
+ var init_review = __esm({
4024
+ "src/snapshot/review.ts"() {
4025
+ "use strict";
4026
+ init_snapshot();
4027
+ VERIFY_MAX_FILES_PER_ENTRY = 8;
4028
+ VERIFY_SKELETON_CHARS = 500;
4029
+ sourceHash = (content2) => createHash5("sha256").update(content2).digest("hex");
4030
+ }
4031
+ });
4032
+
4033
+ // src/snapshot/trust.ts
4034
+ function createSnapshotTrustReader(root) {
4035
+ let reader;
4036
+ return async (kind, name, entry, freshness) => {
4037
+ const trust = assessTrust(entry, freshness);
4038
+ const verdict = entry.verificationFailed ? "failed" : entry.verifiedAt ? "passed" : void 0;
4039
+ if (!verdict) return trust;
4040
+ trust.recordedVerdict = verdict;
4041
+ const unknown = (reason2) => {
4042
+ trust.verification = "unknown";
4043
+ trust.reasons.push(reason2);
4044
+ return trust;
4045
+ };
4046
+ if (!entry.verificationToken || !/^[a-f0-9]{64}$/.test(entry.verificationToken)) return unknown("The recorded verdict has no supported evidence token; obtain a fresh verify_snapshot review.");
4047
+ try {
4048
+ reader ??= createFileAccess(root).then(createSnapshotEvidenceReader).catch(() => null);
4049
+ const readReview = await reader;
4050
+ if (!readReview) return unknown("Verification source evidence could not be read under the current file policy.");
4051
+ const review = await readReview(kind, name, entry);
4052
+ if (!review.evidenceAvailable) {
4053
+ return unknown("Verification source evidence is missing, excluded, unreadable, or empty; the recorded verdict cannot be confirmed.");
4054
+ }
4055
+ if (review.reviewToken !== entry.verificationToken) {
4056
+ trust.verification = "stale";
4057
+ trust.reasons.push("The entry or sampled source contents differ from the recorded verification evidence; obtain a fresh review.");
4058
+ }
4059
+ return trust;
4060
+ } catch {
4061
+ return unknown("Verification source evidence could not be read under the current file policy.");
4062
+ }
4063
+ };
4064
+ }
4065
+ async function readSnapshotTrustIndex(readTrust, snapshot, drift) {
4066
+ const features = /* @__PURE__ */ Object.create(null), flows = /* @__PURE__ */ Object.create(null);
4067
+ for (const [name, entry] of Object.entries(snapshot.features)) {
4068
+ features[name] = await readTrust("feature", name, entry, drift?.featureFreshness?.[name] ?? "unknown");
4069
+ }
4070
+ for (const [name, entry] of Object.entries(snapshot.flows)) {
4071
+ flows[name] = await readTrust("flow", name, entry, drift?.flowFreshness?.[name] ?? "unknown");
4072
+ }
4073
+ return { features, flows };
4074
+ }
4075
+ var init_trust2 = __esm({
4076
+ "src/snapshot/trust.ts"() {
4077
+ "use strict";
4078
+ init_trust();
4079
+ init_files();
4080
+ init_review();
4081
+ }
4082
+ });
4083
+
3862
4084
  // src/setup/files.ts
3863
4085
  var files_exports = {};
3864
4086
  __export(files_exports, {
@@ -3866,8 +4088,8 @@ __export(files_exports, {
3866
4088
  managedBlock: () => managedBlock,
3867
4089
  readText: () => readText
3868
4090
  });
3869
- import fs17 from "fs/promises";
3870
- import path25 from "path";
4091
+ import fs18 from "fs/promises";
4092
+ import path26 from "path";
3871
4093
  import { randomUUID as randomUUID3 } from "crypto";
3872
4094
  async function readText(root, file) {
3873
4095
  try {
@@ -3896,14 +4118,14 @@ async function applyEdit(root, edit) {
3896
4118
  if (edit.after === null) {
3897
4119
  const file2 = await storePath(root, edit.path);
3898
4120
  if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
3899
- await fs17.unlink(file2);
4121
+ await fs18.unlink(file2);
3900
4122
  return true;
3901
4123
  }
3902
4124
  const file = await storePath(root, edit.path, true);
3903
- const temporary = path25.join(path25.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
4125
+ const temporary = path26.join(path26.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
3904
4126
  try {
3905
- const mode = await fs17.stat(file).then((s) => s.mode & 511, () => 384);
3906
- 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);
3907
4129
  try {
3908
4130
  await handle.writeFile(edit.after, "utf8");
3909
4131
  await handle.sync();
@@ -3911,10 +4133,10 @@ async function applyEdit(root, edit) {
3911
4133
  await handle.close();
3912
4134
  }
3913
4135
  if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
3914
- await fs17.rename(temporary, file);
4136
+ await fs18.rename(temporary, file);
3915
4137
  return true;
3916
4138
  } finally {
3917
- await fs17.rm(temporary, { force: true });
4139
+ await fs18.rm(temporary, { force: true });
3918
4140
  }
3919
4141
  }
3920
4142
  var init_files2 = __esm({
@@ -3936,7 +4158,7 @@ __export(execution_exports, {
3936
4158
  recordExecution: () => recordExecution
3937
4159
  });
3938
4160
  import os2 from "os";
3939
- import fs18 from "fs/promises";
4161
+ import fs19 from "fs/promises";
3940
4162
  import { randomUUID as randomUUID4 } from "crypto";
3941
4163
  import { z as z12 } from "zod";
3942
4164
  function parseExecution(raw) {
@@ -4064,7 +4286,7 @@ async function recordExecution(root, directory, event, run) {
4064
4286
  throw Object.assign(new Error(failure.message, { cause: error }), { failure });
4065
4287
  }
4066
4288
  } finally {
4067
- await fs18.rm(await storePath(root, owner), { force: true }).catch(() => {
4289
+ await fs19.rm(await storePath(root, owner), { force: true }).catch(() => {
4068
4290
  });
4069
4291
  }
4070
4292
  }
@@ -4148,13 +4370,17 @@ async function observeReadOnlyTool(dir, event) {
4148
4370
  }
4149
4371
  function summarize(report) {
4150
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;
4151
4374
  return [
4152
- `Mason: ${report.status}; ${report.counts.unresolved} unresolved, ${report.counts["review-required"]} need review, ${report.counts.unverified} unverified.`,
4153
- ...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
+ }),
4154
4380
  ...open.length > 4 ? [`${open.length - 4} more findings in the report.`] : [],
4155
4381
  ...report.diagnostics.slice(0, 2).map(cleanText),
4156
4382
  `Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason-auto check.`,
4157
- "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."
4158
4384
  ].join("\n");
4159
4385
  }
4160
4386
  async function loadState(ws) {
@@ -4378,7 +4604,7 @@ async function automate(dir, event) {
4378
4604
  if (event.event === "after_tool" && event.mutating) {
4379
4605
  session.mutationObserved = true;
4380
4606
  if (!event.toolId || !session.pending[event.toolId]) {
4381
- 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.";
4382
4608
  if (!session.coverageGaps.includes(gap)) session.coverageGaps.push(gap);
4383
4609
  }
4384
4610
  if (event.toolId) delete session.pending[event.toolId];
@@ -4487,8 +4713,8 @@ var init_runtime = __esm({
4487
4713
  });
4488
4714
 
4489
4715
  // src/automation/hook-root.ts
4490
- import fs19 from "fs/promises";
4491
- import path26 from "path";
4716
+ import fs20 from "fs/promises";
4717
+ import path27 from "path";
4492
4718
  var init_hook_root = __esm({
4493
4719
  "src/automation/hook-root.ts"() {
4494
4720
  "use strict";
@@ -4893,7 +5119,7 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
4893
5119
  - Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
4894
5120
  - Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` before finishing. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
4895
5121
  - Before saving, compare the lesson with matching \`get_context\` records and any pending proposal. If new evidence changes the same lesson's assumptions, scope, or recommended action, revise it with \`save_decision(id: existingId)\`, including when it is accepted. Preserve supported rationale and sources, replace obsolete instructions, and add the new evidence. Create a separate record for a genuinely distinct lesson; skip unchanged restatements. Revision preserves history and leaves the accepted version operative until authorized review.
4896
- - Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, and failed verification means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
5122
+ - Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, stale or unknown map verification requires a fresh evidence review, and a recorded failed verdict means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
4897
5123
  - Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
4898
5124
  - For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
4899
5125
  - \`mason_init\` returns documentation audit and committed-diff review results, plus a short setup guide. Pass \`evidence\` with local CI manifest paths to include test and analysis results; the CLI equivalent is \`mason-review --evidence <manifest>\`. State skipped, unavailable, stale, or unknown checks explicitly. Related accepted decisions identify review context, not proven violations.
@@ -5331,10 +5557,10 @@ var init_check = __esm({
5331
5557
  });
5332
5558
 
5333
5559
  // src/review/evidence.ts
5334
- import path27 from "path";
5560
+ import path28 from "path";
5335
5561
  import { z as z22 } from "zod";
5336
5562
  function relativeArtifact(root, file) {
5337
- const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
5563
+ const relative = path28.isAbsolute(file) ? path28.relative(root, file) : file;
5338
5564
  const normalized = normalizeRepoPath(relative);
5339
5565
  if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
5340
5566
  return normalized;
@@ -5431,7 +5657,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5431
5657
  const report = await readStoreJson(root, file);
5432
5658
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
5433
5659
  const sourceRoot = check.sourceRoot ?? root;
5434
- 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.");
5435
5661
  const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
5436
5662
  result.outcome = parsed.outcome;
5437
5663
  result.counts = parsed.counts;
@@ -5552,8 +5778,8 @@ var init_evidence2 = __esm({
5552
5778
  });
5553
5779
 
5554
5780
  // src/review/review.ts
5555
- import fs20 from "fs/promises";
5556
- import path28 from "path";
5781
+ import fs21 from "fs/promises";
5782
+ import path29 from "path";
5557
5783
  import { execFile as execFile7 } from "child_process";
5558
5784
  import { promisify as promisify7 } from "util";
5559
5785
  async function resolveMergeBase(resolvedRoot, base, head) {
@@ -5582,7 +5808,7 @@ function anchorsTouched(record, changedFiles) {
5582
5808
  return matchingPaths(record.files, changedFiles);
5583
5809
  }
5584
5810
  async function computeReview(rootDir, base, options = {}) {
5585
- const resolvedRoot = path28.resolve(rootDir);
5811
+ const resolvedRoot = path29.resolve(rootDir);
5586
5812
  const head = await getCurrentGitHash(resolvedRoot);
5587
5813
  if (head === "unknown") return null;
5588
5814
  const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
@@ -5632,7 +5858,7 @@ async function computeReview(rootDir, base, options = {}) {
5632
5858
  analyzed,
5633
5859
  async (relPath) => {
5634
5860
  try {
5635
- await fs20.access(path28.join(resolvedRoot, relPath));
5861
+ await fs21.access(path29.join(resolvedRoot, relPath));
5636
5862
  return true;
5637
5863
  } catch {
5638
5864
  return false;
@@ -5666,7 +5892,7 @@ async function computeReview(rootDir, base, options = {}) {
5666
5892
  return finalize();
5667
5893
  }
5668
5894
  var exec5, MAX_ANALYZED_FILES;
5669
- var init_review = __esm({
5895
+ var init_review2 = __esm({
5670
5896
  "src/review/review.ts"() {
5671
5897
  "use strict";
5672
5898
  init_drift();
@@ -5755,7 +5981,7 @@ var init_onboarding = __esm({
5755
5981
  "src/mcp/onboarding.ts"() {
5756
5982
  "use strict";
5757
5983
  init_audit();
5758
- init_review();
5984
+ init_review2();
5759
5985
  init_drift();
5760
5986
  init_snapshot();
5761
5987
  init_provenance();
@@ -5767,7 +5993,7 @@ var init_onboarding = __esm({
5767
5993
  });
5768
5994
 
5769
5995
  // src/impact/references.ts
5770
- import path29 from "path";
5996
+ import path30 from "path";
5771
5997
  function templateEnd(content2, start) {
5772
5998
  let i = start + 1, interpolation = 0;
5773
5999
  while (i < content2.length) {
@@ -5789,10 +6015,10 @@ function templateEnd(content2, start) {
5789
6015
  continue;
5790
6016
  }
5791
6017
  if (c === '"' || c === "'") {
5792
- const quote = c;
6018
+ const quote2 = c;
5793
6019
  for (i++; i < content2.length; i++) {
5794
6020
  if (content2[i] === "\\") i++;
5795
- else if (content2[i] === quote) {
6021
+ else if (content2[i] === quote2) {
5796
6022
  i++;
5797
6023
  break;
5798
6024
  }
@@ -5893,10 +6119,10 @@ function jvmImports(content2) {
5893
6119
  }
5894
6120
  function resolveImport(file, specifier, available) {
5895
6121
  if (!specifier.startsWith("./") && !specifier.startsWith("../")) return null;
5896
- 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));
5897
6123
  if (relative.startsWith("../")) return null;
5898
6124
  if (available.has(relative)) return relative;
5899
- const ext = path29.posix.extname(relative);
6125
+ const ext = path30.posix.extname(relative);
5900
6126
  const replacements = { ".js": [".ts", ".tsx"], ".mjs": [".mts"], ".cjs": [".cts"] };
5901
6127
  const candidates = ext ? (replacements[ext] ?? []).map((e) => relative.slice(0, -ext.length) + e) : jsExtensions.flatMap((e) => [relative + e, relative + "/index" + e]);
5902
6128
  const found = candidates.filter((f) => available.has(f));
@@ -5905,23 +6131,23 @@ function resolveImport(file, specifier, available) {
5905
6131
  function mentionsPath(content2, target) {
5906
6132
  return new RegExp(`(?<![\\w./\\\\-])(?:\\./)?${escape(target)}(?![\\w./\\\\-])`).test(content2);
5907
6133
  }
5908
- function createReferenceMatcher(targets, available, targetContents = /* @__PURE__ */ new Map()) {
6134
+ function createReferenceMatcher(targets2, available, targetContents = /* @__PURE__ */ new Map()) {
5909
6135
  const counts = /* @__PURE__ */ new Map();
5910
6136
  for (const file of available) {
5911
- const name = path29.posix.basename(file, path29.posix.extname(file));
6137
+ const name = path30.posix.basename(file, path30.posix.extname(file));
5912
6138
  counts.set(name, (counts.get(name) ?? 0) + 1);
5913
6139
  }
5914
6140
  const jvmTargets = new Map([...targetContents].filter(([file]) => /\.(?:kt|kts|java)$/.test(file)).map(([file, content2]) => [file, declaredSymbols(content2)]));
5915
6141
  return (source) => {
5916
- if (targets.includes(source.path)) return [];
5917
- 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) : []);
5918
6144
  if (/\.(?:kt|kts|java)$/.test(source.path)) {
5919
6145
  const imported = jvmImports(source.content);
5920
6146
  for (const [target, symbols] of jvmTargets) if (symbols.some((symbol) => imported.some((name) => name === symbol || name.startsWith(symbol + ".")))) resolved.add(target);
5921
6147
  }
5922
6148
  const groups = /* @__PURE__ */ new Map();
5923
6149
  const add = (evidence, match) => groups.set(evidence, [...groups.get(evidence) ?? [], match]);
5924
- for (const target of targets) {
6150
+ for (const target of targets2) {
5925
6151
  if (resolved.has(target)) {
5926
6152
  add("resolved-import", target);
5927
6153
  continue;
@@ -5930,7 +6156,7 @@ function createReferenceMatcher(targets, available, targetContents = /* @__PURE_
5930
6156
  add("explicit-path", target);
5931
6157
  continue;
5932
6158
  }
5933
- 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);
5934
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;
5935
6161
  if (word(name).test(source.content)) {
5936
6162
  add("name-candidate", name);
@@ -5959,11 +6185,11 @@ var impact_exports = {};
5959
6185
  __export(impact_exports, {
5960
6186
  analyzeImpact: () => analyzeImpact
5961
6187
  });
5962
- import path30 from "path";
6188
+ import path31 from "path";
5963
6189
  import { execFile as execFile8 } from "child_process";
5964
6190
  import { promisify as promisify8 } from "util";
5965
6191
  async function analyzeImpact(rootDir, targetFiles) {
5966
- const resolvedRoot = path30.resolve(rootDir);
6192
+ const resolvedRoot = path31.resolve(rootDir);
5967
6193
  const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
5968
6194
  const [cochange, references, tests] = await Promise.all([
5969
6195
  getCochangeFiles(resolvedRoot, resolvedTargets),
@@ -5977,10 +6203,10 @@ async function analyzeImpact(rootDir, targetFiles) {
5977
6203
  tests
5978
6204
  };
5979
6205
  }
5980
- async function resolveTargetFiles(rootDir, targets) {
6206
+ async function resolveTargetFiles(rootDir, targets2) {
5981
6207
  const resolved = [];
5982
6208
  const access = await createFileAccess(rootDir);
5983
- for (const target of targets) {
6209
+ for (const target of targets2) {
5984
6210
  if (target.includes("/")) {
5985
6211
  const normalized = normalizeRepoPath(target);
5986
6212
  if (normalized) resolved.push(normalized);
@@ -6077,9 +6303,9 @@ async function getRelatedTests(rootDir, targetFiles) {
6077
6303
  const testFiles = await (await createFileAccess(rootDir)).list(testPatterns);
6078
6304
  const results = [];
6079
6305
  for (const target of targetFiles) {
6080
- const targetBaseName = path30.basename(target).replace(/\.[^.]+$/, "");
6306
+ const targetBaseName = path31.basename(target).replace(/\.[^.]+$/, "");
6081
6307
  for (const testFile of testFiles) {
6082
- const testBaseName = path30.basename(testFile).replace(/\.[^.]+$/, "");
6308
+ const testBaseName = path31.basename(testFile).replace(/\.[^.]+$/, "");
6083
6309
  const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
6084
6310
  if (sourceName === targetBaseName) {
6085
6311
  results.push({
@@ -6107,26 +6333,26 @@ var assemble_exports = {};
6107
6333
  __export(assemble_exports, {
6108
6334
  assembleContext: () => assembleContext
6109
6335
  });
6110
- import path31 from "path";
6111
- import fs21 from "fs/promises";
6336
+ import path32 from "path";
6337
+ import fs22 from "fs/promises";
6112
6338
  async function collectImpact(root, candidates) {
6113
- const targets = /* @__PURE__ */ new Set();
6339
+ const targets2 = /* @__PURE__ */ new Set();
6114
6340
  let sourceFiles;
6115
6341
  for (const candidate of sanitizeRepoPaths(candidates)) {
6116
- const stat = await fs21.stat(path31.join(root, candidate)).catch(() => null);
6342
+ const stat = await fs22.stat(path32.join(root, candidate)).catch(() => null);
6117
6343
  if (stat?.isDirectory()) {
6118
6344
  sourceFiles ??= await (await createFileAccess(root)).list();
6119
6345
  for (const file of sourceFiles) {
6120
- if (anchorMatches(candidate, file)) targets.add(file);
6121
- if (targets.size >= MAX_IMPACT_TARGETS) break;
6346
+ if (anchorMatches(candidate, file)) targets2.add(file);
6347
+ if (targets2.size >= MAX_IMPACT_TARGETS) break;
6122
6348
  }
6123
6349
  } else {
6124
- targets.add(candidate);
6350
+ targets2.add(candidate);
6125
6351
  }
6126
- if (targets.size >= MAX_IMPACT_TARGETS) break;
6352
+ if (targets2.size >= MAX_IMPACT_TARGETS) break;
6127
6353
  }
6128
- if (!targets.size) return { impact: null, relatedTests: [] };
6129
- const result = await analyzeImpact(root, [...targets]);
6354
+ if (!targets2.size) return { impact: null, relatedTests: [] };
6355
+ const result = await analyzeImpact(root, [...targets2]);
6130
6356
  const referenceCandidatesOmitted = result.references.filter((r) => r.evidence === "name-candidate").length;
6131
6357
  return {
6132
6358
  impact: {
@@ -6139,7 +6365,7 @@ async function collectImpact(root, candidates) {
6139
6365
  };
6140
6366
  }
6141
6367
  async function assembleContext(rootDir, task, files) {
6142
- const resolvedRoot = path31.resolve(rootDir);
6368
+ const resolvedRoot = path32.resolve(rootDir);
6143
6369
  const mapState = await inspectSnapshot(resolvedRoot);
6144
6370
  const snapshot = mapState.snapshot;
6145
6371
  const store = await loadDecisionStore(resolvedRoot);
@@ -6171,6 +6397,7 @@ async function assembleContext(rootDir, task, files) {
6171
6397
  };
6172
6398
  }
6173
6399
  const drift = await computeDrift(resolvedRoot);
6400
+ const readTrust = createSnapshotTrustReader(resolvedRoot);
6174
6401
  const featureScores = Object.entries(snapshot.features).map(([name, feat]) => ({
6175
6402
  name,
6176
6403
  feat,
@@ -6197,14 +6424,7 @@ async function assembleContext(rootDir, task, files) {
6197
6424
  Object.assign(bundle, await collectImpact(resolvedRoot, [...anchorFiles, ...Object.values(decisions).flatMap((d) => [...d.files, ...d.pendingProposal?.files ?? []])]));
6198
6425
  bundle.diagnostics = store.diagnostics;
6199
6426
  bundle.freshness = drift;
6200
- bundle.trust = {
6201
- features: Object.fromEntries(Object.entries(snapshot.features).map(
6202
- ([name, entry]) => [name, assessTrust(entry, drift?.featureFreshness?.[name] ?? "unknown")]
6203
- )),
6204
- flows: Object.fromEntries(Object.entries(snapshot.flows).map(
6205
- ([name, entry]) => [name, assessTrust(entry, drift?.flowFreshness?.[name] ?? "unknown")]
6206
- ))
6207
- };
6427
+ bundle.trust = await readSnapshotTrustIndex(readTrust, snapshot, drift);
6208
6428
  bundle.hint += " " + trustHint([
6209
6429
  ...Object.values(bundle.trust.features),
6210
6430
  ...Object.values(bundle.trust.flows),
@@ -6217,7 +6437,7 @@ async function assembleContext(rootDir, task, files) {
6217
6437
  const features = {};
6218
6438
  const staleMatches = [];
6219
6439
  for (const { name, feat, score } of featureScores) {
6220
- const trust = assessTrust(feat, drift?.featureFreshness?.[name] ?? "unknown");
6440
+ const trust = await readTrust("feature", name, feat, drift?.featureFreshness?.[name] ?? "unknown");
6221
6441
  const stale2 = trust.freshness !== "current";
6222
6442
  if (stale2) staleMatches.push(name);
6223
6443
  features[name] = {
@@ -6232,7 +6452,7 @@ async function assembleContext(rootDir, task, files) {
6232
6452
  }
6233
6453
  const flows = {};
6234
6454
  for (const { name, flow, score } of flowScores) {
6235
- const trust = assessTrust(flow, drift?.flowFreshness?.[name] ?? "unknown");
6455
+ const trust = await readTrust("flow", name, flow, drift?.flowFreshness?.[name] ?? "unknown");
6236
6456
  const stale2 = trust.freshness !== "current";
6237
6457
  if (stale2) staleMatches.push(name);
6238
6458
  flows[name] = {
@@ -6317,6 +6537,7 @@ var init_assemble = __esm({
6317
6537
  "use strict";
6318
6538
  init_snapshot();
6319
6539
  init_files();
6540
+ init_trust2();
6320
6541
  init_drift();
6321
6542
  init_impact();
6322
6543
  init_lexical();
@@ -6340,7 +6561,7 @@ __export(observations_exports, {
6340
6561
  observeActivation: () => observeActivation,
6341
6562
  readObservation: () => readObservation
6342
6563
  });
6343
- import fs22 from "fs/promises";
6564
+ import fs23 from "fs/promises";
6344
6565
  import { z as z23 } from "zod";
6345
6566
  function observationPath(directory, host, revision, version = executingVersion()) {
6346
6567
  return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
@@ -6357,15 +6578,15 @@ async function observeActivation(dir, event, options = {}) {
6357
6578
  if (host !== "codex" && host !== "claude" || !process.env.MASON_SETUP_ROOT) return null;
6358
6579
  try {
6359
6580
  if (!revision) {
6360
- if ((await loadSetup(process.env.MASON_SETUP_ROOT))?.hosts[host]) {
6581
+ if ((await effectiveSetup(process.env.MASON_SETUP_ROOT)).setup?.hosts[host]) {
6361
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.";
6362
6583
  }
6363
6584
  return inactiveSetupMessage();
6364
6585
  }
6365
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];
6366
- const ws = capturedDirectory ? { root: await fs22.realpath(dir), directory: capturedDirectory } : await workspace(dir);
6367
- if (ws.root !== await fs22.realpath(process.env.MASON_SETUP_ROOT)) return null;
6368
- 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);
6369
6590
  if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
6370
6591
  const directory = ws.directory + "/activation";
6371
6592
  await withLock(ws.root, directory, async () => {
@@ -6439,8 +6660,9 @@ __export(status_exports, {
6439
6660
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
6440
6661
  async function setupStatus(dir) {
6441
6662
  const ws = await workspace(dir);
6442
- const setup = await loadSetup(ws.root);
6443
- if (!setup) {
6663
+ const activation = await effectiveSetup(ws.root);
6664
+ const setup = activation.setup;
6665
+ if (!setup || !Object.keys(setup.hosts).length) {
6444
6666
  const partial = await Promise.all(["codex", "claude"].map(async (host) => ({
6445
6667
  host,
6446
6668
  receipt: await loadSetupReceipt(ws.root, ws.directory, host)
@@ -6474,7 +6696,7 @@ async function setupStatus(dir) {
6474
6696
  ]));
6475
6697
  const hooks = isDeepStrictEqual2(compatibleHooks, expectedHooks) && !config.disabled;
6476
6698
  const local = await loadSetupReceipt(ws.root, ws.directory, host);
6477
- 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;
6478
6700
  const observation = await readObservation(ws.root, ws.directory, host, entry.revision, installed.version ?? "unavailable");
6479
6701
  const sessions = Object.values(observation?.sessions ?? {}).sort((a, b) => b.at.localeCompare(a.at));
6480
6702
  const complete = sessions.find((session) => events.every((event) => session.events.includes(event)));
@@ -6506,6 +6728,7 @@ async function setupStatus(dir) {
6506
6728
  status: statuses.length && statuses.every((status) => status === "active") ? "active" : statuses.includes("attention") ? "attention" : "pending",
6507
6729
  root: ws.root,
6508
6730
  hosts,
6731
+ activationRoot: activation.root,
6509
6732
  decisionRecords: decisions.records.length,
6510
6733
  diagnostics: decisions.diagnostics,
6511
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."
@@ -6513,6 +6736,9 @@ async function setupStatus(dir) {
6513
6736
  }
6514
6737
  function summarizeActivation(status) {
6515
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
+ }
6516
6742
  for (const [host, state] of Object.entries(status.hosts)) {
6517
6743
  lines.push(
6518
6744
  `${host}: ${state.status}`,
@@ -6682,8 +6908,8 @@ __export(client_exports, {
6682
6908
  function createConfluenceClient(config, fetchFn = fetch) {
6683
6909
  const baseUrl = config.baseUrl.replace(/\/+$/, "");
6684
6910
  const auth = "Basic " + Buffer.from(`${config.email}:${config.apiToken}`).toString("base64");
6685
- async function call(method, path35, body) {
6686
- const res = await fetchFn(`${baseUrl}${path35}`, {
6911
+ async function call(method, path42, body) {
6912
+ const res = await fetchFn(`${baseUrl}${path42}`, {
6687
6913
  method,
6688
6914
  headers: {
6689
6915
  Authorization: auth,
@@ -6695,7 +6921,7 @@ function createConfluenceClient(config, fetchFn = fetch) {
6695
6921
  if (!res.ok) {
6696
6922
  const text2 = await res.text();
6697
6923
  throw new Error(
6698
- `Confluence ${method} ${path35} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
6924
+ `Confluence ${method} ${path42} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
6699
6925
  );
6700
6926
  }
6701
6927
  if (res.status === 204) return null;
@@ -6796,28 +7022,28 @@ __export(config_exports2, {
6796
7022
  saveConfluenceConfig: () => saveConfluenceConfig,
6797
7023
  validateProvider: () => validateProvider
6798
7024
  });
6799
- import fs23 from "fs/promises";
6800
- import path32 from "path";
7025
+ import fs24 from "fs/promises";
7026
+ import path33 from "path";
6801
7027
  import os3 from "os";
6802
7028
  import { execFile as execFile9 } from "child_process";
6803
7029
  import { promisify as promisify9 } from "util";
6804
7030
  function configDir() {
6805
- return path32.join(os3.homedir(), ".mason");
7031
+ return path33.join(os3.homedir(), ".mason");
6806
7032
  }
6807
7033
  function configFile() {
6808
- return path32.join(configDir(), "config.json");
7034
+ return path33.join(configDir(), "config.json");
6809
7035
  }
6810
7036
  async function loadConfig() {
6811
7037
  try {
6812
- const raw = await fs23.readFile(configFile(), "utf-8");
7038
+ const raw = await fs24.readFile(configFile(), "utf-8");
6813
7039
  return JSON.parse(raw);
6814
7040
  } catch {
6815
7041
  return null;
6816
7042
  }
6817
7043
  }
6818
7044
  async function saveConfig(config) {
6819
- await fs23.mkdir(configDir(), { recursive: true });
6820
- 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");
6821
7047
  }
6822
7048
  function getDefaultModel(provider) {
6823
7049
  return DEFAULT_MODELS[provider];
@@ -6977,21 +7203,21 @@ var init_renderer = __esm({
6977
7203
  });
6978
7204
 
6979
7205
  // src/confluence/diff.ts
6980
- import fs24 from "fs/promises";
6981
- import path33 from "path";
6982
- import { createHash as createHash5 } from "crypto";
7206
+ import fs25 from "fs/promises";
7207
+ import path34 from "path";
7208
+ import { createHash as createHash6 } from "crypto";
6983
7209
  function hashDescription(description) {
6984
- return createHash5("sha256").update(description, "utf8").digest("hex");
7210
+ return createHash6("sha256").update(description, "utf8").digest("hex");
6985
7211
  }
6986
7212
  function syncStateDir(rootDir) {
6987
- return path33.join(rootDir, ".mason");
7213
+ return path34.join(rootDir, ".mason");
6988
7214
  }
6989
7215
  function syncStatePath(rootDir) {
6990
- return path33.join(syncStateDir(rootDir), "confluence-sync.json");
7216
+ return path34.join(syncStateDir(rootDir), "confluence-sync.json");
6991
7217
  }
6992
7218
  async function loadSyncState(rootDir) {
6993
7219
  try {
6994
- const raw = await fs24.readFile(syncStatePath(rootDir), "utf-8");
7220
+ const raw = await fs25.readFile(syncStatePath(rootDir), "utf-8");
6995
7221
  const parsed = JSON.parse(raw);
6996
7222
  if (parsed.version !== 2) return null;
6997
7223
  return parsed;
@@ -7000,8 +7226,8 @@ async function loadSyncState(rootDir) {
7000
7226
  }
7001
7227
  }
7002
7228
  async function saveSyncState(rootDir, state) {
7003
- await fs24.mkdir(syncStateDir(rootDir), { recursive: true });
7004
- await fs24.writeFile(
7229
+ await fs25.mkdir(syncStateDir(rootDir), { recursive: true });
7230
+ await fs25.writeFile(
7005
7231
  syncStatePath(rootDir),
7006
7232
  JSON.stringify(state, null, 2),
7007
7233
  "utf-8"
@@ -7559,8 +7785,8 @@ import { z as z24 } from "zod";
7559
7785
  // src/mcp/tools.ts
7560
7786
  init_repair();
7561
7787
  init_advisory_review();
7562
- import fs25 from "fs/promises";
7563
- import path34 from "path";
7788
+ import fs26 from "fs/promises";
7789
+ import path35 from "path";
7564
7790
  import { execFile as execFile11 } from "child_process";
7565
7791
  import { promisify as promisify11 } from "util";
7566
7792
 
@@ -7569,11 +7795,11 @@ init_audit();
7569
7795
  init_types();
7570
7796
  init_repair();
7571
7797
  init_findings();
7572
- import path23 from "path";
7798
+ import path24 from "path";
7573
7799
 
7574
7800
  // src/audit/advisory-cli.ts
7575
7801
  init_advisory_review();
7576
- import path22 from "path";
7802
+ import path23 from "path";
7577
7803
  import { stripVTControlCharacters } from "util";
7578
7804
 
7579
7805
  // src/audit/cli.ts
@@ -7670,7 +7896,7 @@ import { execFile as execFile3 } from "child_process";
7670
7896
  import { promisify as promisify3 } from "util";
7671
7897
 
7672
7898
  // src/analyzers/base.ts
7673
- import fs15 from "fs/promises";
7899
+ import fs16 from "fs/promises";
7674
7900
  import fg6 from "fast-glob";
7675
7901
  var BaseAnalyzer = class {
7676
7902
  async findFiles(patterns, root) {
@@ -7681,7 +7907,7 @@ var BaseAnalyzer = class {
7681
7907
  });
7682
7908
  }
7683
7909
  async readFile(filePath) {
7684
- return fs15.readFile(filePath, "utf-8");
7910
+ return fs16.readFile(filePath, "utf-8");
7685
7911
  }
7686
7912
  createFinding(partial) {
7687
7913
  return {
@@ -7886,7 +8112,7 @@ async function isGitRepo(dir) {
7886
8112
 
7887
8113
  // src/mcp/sampler.ts
7888
8114
  init_files();
7889
- import path24 from "path";
8115
+ import path25 from "path";
7890
8116
  import { execFile as execFile4 } from "child_process";
7891
8117
  import { promisify as promisify4 } from "util";
7892
8118
  var exec2 = promisify4(execFile4);
@@ -7994,8 +8220,8 @@ async function sampleFiles(rootDir, maxFiles = 25) {
7994
8220
  const projectConfig = access.config;
7995
8221
  for (const filePath of projectConfig.alwaysInclude ?? []) {
7996
8222
  if (selected.size >= maxFiles) break;
7997
- const resolvedPath = path24.resolve(rootDir, filePath);
7998
- if (!resolvedPath.startsWith(path24.resolve(rootDir))) continue;
8223
+ const resolvedPath = path25.resolve(rootDir, filePath);
8224
+ if (!resolvedPath.startsWith(path25.resolve(rootDir))) continue;
7999
8225
  selected.set(filePath, "always-include (project config)");
8000
8226
  }
8001
8227
  let configCount = 0;
@@ -8061,7 +8287,7 @@ async function sampleFiles(rootDir, maxFiles = 25) {
8061
8287
  if (!line) continue;
8062
8288
  if (line.includes("node_modules") || line.includes("/build/") || line.includes(".gradle") || line.includes("/generated/"))
8063
8289
  continue;
8064
- const ext = path24.extname(line).slice(1);
8290
+ const ext = path25.extname(line).slice(1);
8065
8291
  if (!SOURCE_EXTENSIONS.includes(ext)) continue;
8066
8292
  fileCounts.set(line, (fileCounts.get(line) ?? 0) + 1);
8067
8293
  }
@@ -8207,8 +8433,8 @@ async function reviewState(root, record) {
8207
8433
  changedFiles: touchedPaths(committed),
8208
8434
  localChanges: matchingPaths(anchors, workingTree.changedFiles)
8209
8435
  };
8210
- const reviewToken = createHash4("sha256").update(JSON.stringify({ record, evidence, workingTreeAvailable: workingTree.available })).digest("hex");
8211
- return { reviewToken, evidence, committed, workingTreeAvailable: workingTree.available };
8436
+ const reviewToken2 = createHash4("sha256").update(JSON.stringify({ record, evidence, workingTreeAvailable: workingTree.available })).digest("hex");
8437
+ return { reviewToken: reviewToken2, evidence, committed, workingTreeAvailable: workingTree.available };
8212
8438
  }
8213
8439
  async function previews(root, record, state) {
8214
8440
  const access = await createFileAccess(root);
@@ -8314,7 +8540,9 @@ async function reviewDecision(root, input) {
8314
8540
  // src/mcp/tools.ts
8315
8541
  init_drift2();
8316
8542
  init_snapshot();
8543
+ init_trust2();
8317
8544
  init_drift();
8545
+ init_review();
8318
8546
 
8319
8547
  // src/snapshot/prompt.ts
8320
8548
  var BATCH_SYSTEM_PROMPT = `You are Mason, building one piece of a larger concept-to-files map via a Map-Reduce pattern.
@@ -8411,9 +8639,10 @@ ${JSON.stringify({ partials }, null, 2)}`;
8411
8639
  }
8412
8640
 
8413
8641
  // src/snapshot/partials.ts
8642
+ init_lock();
8414
8643
  init_storage();
8415
8644
  init_snapshot();
8416
- import fs16 from "fs/promises";
8645
+ import fs17 from "fs/promises";
8417
8646
  import { z as z11 } from "zod";
8418
8647
  var DIRECTORY = ".mason/partial-snapshots";
8419
8648
  var partialSchema = z11.object({
@@ -8426,12 +8655,12 @@ var partialSchema = z11.object({
8426
8655
  var scopeSchema2 = z11.object({ files: z11.array(z11.string()), savedAt: z11.string() });
8427
8656
  async function savePartial(rootDir, partial) {
8428
8657
  if (!/^[a-zA-Z0-9_-]+$/.test(partial.batchId)) throw new Error(`Invalid batchId: ${partial.batchId}`);
8429
- await writeStoreJson(rootDir, `${DIRECTORY}/${partial.batchId}.json`, partialSchema.parse(partial));
8658
+ await withSnapshotWrite(rootDir, () => writeStoreJson(rootDir, `${DIRECTORY}/${partial.batchId}.json`, partialSchema.parse(partial)));
8430
8659
  }
8431
8660
  async function loadAllPartials(rootDir) {
8432
8661
  let entries;
8433
8662
  try {
8434
- entries = await fs16.readdir(await storePath(rootDir, DIRECTORY));
8663
+ entries = await fs17.readdir(await storePath(rootDir, DIRECTORY));
8435
8664
  } catch (error) {
8436
8665
  if (error.code === "ENOENT") return [];
8437
8666
  throw error;
@@ -8446,17 +8675,19 @@ async function loadAllPartials(rootDir) {
8446
8675
  return partials.sort((a, b) => a.offset - b.offset);
8447
8676
  }
8448
8677
  async function saveScope(rootDir, files) {
8449
- await writeStoreJson(rootDir, `${DIRECTORY}/scope.json`, { files, savedAt: (/* @__PURE__ */ new Date()).toISOString() });
8678
+ await withSnapshotWrite(rootDir, () => writeStoreJson(rootDir, `${DIRECTORY}/scope.json`, { files, savedAt: (/* @__PURE__ */ new Date()).toISOString() }));
8450
8679
  }
8451
8680
  async function loadScope(rootDir) {
8452
8681
  const raw = await readStoreJson(rootDir, `${DIRECTORY}/scope.json`);
8453
8682
  return raw === null ? null : scopeSchema2.parse(raw).files;
8454
8683
  }
8455
8684
  async function clearScope(rootDir) {
8456
- await fs16.rm(await storePath(rootDir, `${DIRECTORY}/scope.json`), { force: true });
8685
+ await withSnapshotWrite(rootDir, async () => {
8686
+ await fs17.rm(await storePath(rootDir, `${DIRECTORY}/scope.json`), { force: true });
8687
+ });
8457
8688
  }
8458
8689
  async function clearAllPartials(rootDir) {
8459
- await fs16.rm(await storePath(rootDir, DIRECTORY), { recursive: true, force: true });
8690
+ await fs17.rm(await storePath(rootDir, DIRECTORY), { recursive: true, force: true });
8460
8691
  }
8461
8692
  function batchIdFor(offset) {
8462
8693
  return `batch-${String(offset).padStart(6, "0")}`;
@@ -8473,7 +8704,7 @@ async function buildContext(dir) {
8473
8704
  };
8474
8705
  }
8475
8706
  async function analyzeProject(dir) {
8476
- const rootDir = path34.resolve(dir);
8707
+ const rootDir = path35.resolve(dir);
8477
8708
  const context = await buildContext(rootDir);
8478
8709
  const results = await runAll(context);
8479
8710
  const projectSnapshot = await detectProjectSnapshot(rootDir);
@@ -8528,7 +8759,7 @@ async function detectProjectSnapshot(rootDir) {
8528
8759
  const present = [];
8529
8760
  for (const file of buildFiles) {
8530
8761
  try {
8531
- await fs25.access(path34.join(rootDir, file));
8762
+ await fs26.access(path35.join(rootDir, file));
8532
8763
  present.push(file);
8533
8764
  } catch {
8534
8765
  }
@@ -8570,7 +8801,7 @@ async function detectProjectSnapshot(rootDir) {
8570
8801
  const sourceFiles = await access.list();
8571
8802
  const fileCounts = {};
8572
8803
  for (const file of sourceFiles) {
8573
- const ext = path34.extname(file).slice(1);
8804
+ const ext = path35.extname(file).slice(1);
8574
8805
  fileCounts[ext] = (fileCounts[ext] ?? 0) + 1;
8575
8806
  }
8576
8807
  return {
@@ -8581,7 +8812,7 @@ async function detectProjectSnapshot(rootDir) {
8581
8812
  };
8582
8813
  }
8583
8814
  async function getCodeSamples(dir, count4 = 15) {
8584
- const rootDir = path34.resolve(dir);
8815
+ const rootDir = path35.resolve(dir);
8585
8816
  const samples = await sampleFiles(rootDir, count4);
8586
8817
  const output = {
8587
8818
  note: "These are previews (first ~60 lines). Read the file directly with your own tools to see it in full.",
@@ -8624,7 +8855,7 @@ async function unmappedContextResponse(rootDir) {
8624
8855
  });
8625
8856
  }
8626
8857
  async function getProjectStructure(dir) {
8627
- const rootDir = path34.resolve(dir);
8858
+ const rootDir = path35.resolve(dir);
8628
8859
  const allFiles = await (await createFileAccess(rootDir)).list("**/*");
8629
8860
  const dirInfo = /* @__PURE__ */ new Map();
8630
8861
  for (const file of allFiles) {
@@ -8636,7 +8867,7 @@ async function getProjectStructure(dir) {
8636
8867
  }
8637
8868
  const info = dirInfo.get(dirPath);
8638
8869
  info.fileCount++;
8639
- const ext = path34.extname(file).slice(1);
8870
+ const ext = path35.extname(file).slice(1);
8640
8871
  if (ext) {
8641
8872
  info.extensions.set(ext, (info.extensions.get(ext) ?? 0) + 1);
8642
8873
  }
@@ -8681,7 +8912,7 @@ async function buildChangedFilePreviews(rootDir, changedFiles) {
8681
8912
  return previews2;
8682
8913
  }
8683
8914
  async function getSnapshot(dir) {
8684
- const rootDir = path34.resolve(dir);
8915
+ const rootDir = path35.resolve(dir);
8685
8916
  const snapshot = await loadSnapshot(rootDir);
8686
8917
  if (!snapshot) {
8687
8918
  return unmappedContextResponse(rootDir);
@@ -8719,9 +8950,9 @@ async function getSnapshot(dir) {
8719
8950
  const store = await loadDecisionStore2(rootDir);
8720
8951
  const decisionRecords = store.records;
8721
8952
  const decisionDrift = await computeDecisionDrift(rootDir, decisionRecords);
8953
+ const readTrust = createSnapshotTrustReader(rootDir);
8722
8954
  const trust = {
8723
- features: Object.fromEntries(Object.entries(snapshot.features).map(([name, entry]) => [name, assessTrust(entry, drift?.featureFreshness?.[name] ?? "unknown")])),
8724
- flows: Object.fromEntries(Object.entries(snapshot.flows).map(([name, entry]) => [name, assessTrust(entry, drift?.flowFreshness?.[name] ?? "unknown")])),
8955
+ ...await readSnapshotTrustIndex(readTrust, snapshot, drift),
8725
8956
  decisions: Object.fromEntries(decisionRecords.filter((d) => d.status === "active").map((d) => [d.id, decisionTrust(effectiveDecision(d), decisionDrift.freshness?.[d.id] ?? "unknown")]))
8726
8957
  };
8727
8958
  output.trust = trust;
@@ -8782,7 +9013,7 @@ function driftHint(report) {
8782
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.";
8783
9014
  }
8784
9015
  async function checkDrift(dir) {
8785
- const rootDir = path34.resolve(dir);
9016
+ const rootDir = path35.resolve(dir);
8786
9017
  const report = await computeDrift(rootDir);
8787
9018
  if (!report) {
8788
9019
  return JSON.stringify({
@@ -8813,7 +9044,7 @@ async function checkDrift(dir) {
8813
9044
  return JSON.stringify({ exists: true, ...report, verification, hint });
8814
9045
  }
8815
9046
  async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_SIZE, files) {
8816
- const rootDir = path34.resolve(dir);
9047
+ const rootDir = path35.resolve(dir);
8817
9048
  const scoped = files !== void 0 && files.length > 0;
8818
9049
  const scopeFiles = scoped ? sanitizePaths(rootDir, files) : void 0;
8819
9050
  const batch = await prepareSnapshotBatch(dir, offset, batchSize, scopeFiles);
@@ -8860,7 +9091,7 @@ async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_
8860
9091
  );
8861
9092
  }
8862
9093
  async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
8863
- const rootDir = path34.resolve(dir);
9094
+ const rootDir = path35.resolve(dir);
8864
9095
  for (const feat of Object.values(features)) {
8865
9096
  feat.files = sanitizePaths(rootDir, feat.files);
8866
9097
  if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
@@ -8889,7 +9120,7 @@ async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
8889
9120
  );
8890
9121
  }
8891
9122
  async function reduceSnapshot(dir) {
8892
- const rootDir = path34.resolve(dir);
9123
+ const rootDir = path35.resolve(dir);
8893
9124
  const partials = await loadAllPartials(rootDir);
8894
9125
  if (partials.length === 0) {
8895
9126
  return JSON.stringify(
@@ -8951,7 +9182,7 @@ async function reduceSnapshot(dir) {
8951
9182
  );
8952
9183
  }
8953
9184
  async function fullAnalysis(dir) {
8954
- const rootDir = path34.resolve(dir);
9185
+ const rootDir = path35.resolve(dir);
8955
9186
  const [analysis, structure, samples, testMap, snapshot] = await Promise.all([
8956
9187
  analyzeProject(dir),
8957
9188
  getProjectStructure(dir),
@@ -8980,100 +9211,94 @@ function sanitizePaths(rootDir, files) {
8980
9211
  return sanitizeRepoPaths(files);
8981
9212
  }
8982
9213
  async function saveSnapshotData(dir, features, flows, removeFeatures = [], removeFlows = []) {
8983
- const rootDir = path34.resolve(dir);
8984
- const gitHash = await getCurrentGitHash(rootDir);
8985
- const now = (/* @__PURE__ */ new Date()).toISOString();
8986
- for (const feat of Object.values(features)) {
8987
- feat.files = sanitizePaths(rootDir, feat.files);
8988
- if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
8989
- feat.type = normalizeFeatureType(feat.type);
8990
- }
8991
- for (const flow of Object.values(flows)) {
8992
- flow.chain = sanitizePaths(rootDir, flow.chain);
8993
- }
8994
- const partials = await loadAllPartials(rootDir);
8995
- const replaceMode = partials.length > 0;
8996
- const previous = await loadSnapshot(rootDir);
8997
- const existing = replaceMode ? null : previous;
8998
- const preserveVerification = (next, old) => {
8999
- if (!old) return;
9000
- const semantic = (entry) => JSON.stringify({
9001
- description: entry.description,
9002
- files: "files" in entry ? entry.files : void 0,
9003
- chain: "chain" in entry ? entry.chain : void 0,
9004
- tests: "files" in entry ? entry.tests : void 0,
9005
- type: "files" in entry ? normalizeFeatureType(entry.type) : void 0
9006
- });
9007
- if (semantic(next) !== semantic(old)) return;
9008
- next.verifiedAt = old.verifiedAt;
9009
- next.verifiedHash = old.verifiedHash;
9010
- next.verificationFailed = old.verificationFailed;
9011
- next.verificationNote = old.verificationNote;
9012
- };
9013
- for (const [name, entry] of Object.entries(features)) preserveVerification(entry, previous?.features[name]);
9014
- for (const [name, entry] of Object.entries(flows)) preserveVerification(entry, previous?.flows[name]);
9015
- if (existing) {
9016
- if (existing.gitHash !== "unknown") {
9017
- for (const feat of Object.values(existing.features)) {
9018
- feat.refreshedHash ??= existing.gitHash;
9214
+ const rootDir = path35.resolve(dir);
9215
+ return updateSnapshot(rootDir, async (previous) => {
9216
+ const gitHash = await getCurrentGitHash(rootDir);
9217
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9218
+ for (const feat of Object.values(features)) {
9219
+ feat.files = sanitizePaths(rootDir, feat.files);
9220
+ if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
9221
+ feat.type = normalizeFeatureType(feat.type);
9222
+ }
9223
+ for (const flow of Object.values(flows)) {
9224
+ flow.chain = sanitizePaths(rootDir, flow.chain);
9225
+ }
9226
+ const partials = await loadAllPartials(rootDir);
9227
+ const replaceMode = partials.length > 0;
9228
+ const existing = replaceMode ? null : previous;
9229
+ const preserveVerification = (next, old) => {
9230
+ delete next.verifiedAt;
9231
+ delete next.verifiedHash;
9232
+ delete next.verificationToken;
9233
+ delete next.verificationFailed;
9234
+ delete next.verificationNote;
9235
+ if (!old) return;
9236
+ if (JSON.stringify(snapshotEntryContent(next)) !== JSON.stringify(snapshotEntryContent(old))) return;
9237
+ next.verificationToken = old.verificationToken;
9238
+ next.verifiedAt = old.verifiedAt;
9239
+ next.verifiedHash = old.verifiedHash;
9240
+ next.verificationFailed = old.verificationFailed;
9241
+ next.verificationNote = old.verificationNote;
9242
+ };
9243
+ for (const [name, entry] of Object.entries(features)) preserveVerification(entry, previous?.features[name]);
9244
+ for (const [name, entry] of Object.entries(flows)) preserveVerification(entry, previous?.flows[name]);
9245
+ if (existing) {
9246
+ if (existing.gitHash !== "unknown") {
9247
+ for (const feat of Object.values(existing.features)) {
9248
+ feat.refreshedHash ??= existing.gitHash;
9249
+ }
9250
+ for (const flow of Object.values(existing.flows)) {
9251
+ flow.refreshedHash ??= existing.gitHash;
9252
+ }
9019
9253
  }
9020
- for (const flow of Object.values(existing.flows)) {
9021
- flow.refreshedHash ??= existing.gitHash;
9254
+ const removedFeatures = removeFeatures.filter(
9255
+ (name) => name in existing.features
9256
+ );
9257
+ const removedFlows = removeFlows.filter((name) => name in existing.flows);
9258
+ for (const name of removedFeatures) delete existing.features[name];
9259
+ for (const name of removedFlows) delete existing.flows[name];
9260
+ if (gitHash !== "unknown") {
9261
+ for (const feat of Object.values(features)) feat.refreshedHash = gitHash;
9262
+ for (const flow of Object.values(flows)) flow.refreshedHash = gitHash;
9022
9263
  }
9023
- }
9024
- const removedFeatures = removeFeatures.filter(
9025
- (name) => name in existing.features
9026
- );
9027
- const removedFlows = removeFlows.filter((name) => name in existing.flows);
9028
- for (const name of removedFeatures) delete existing.features[name];
9029
- for (const name of removedFlows) delete existing.flows[name];
9030
- if (gitHash !== "unknown") {
9031
- for (const feat of Object.values(features)) feat.refreshedHash = gitHash;
9032
- for (const flow of Object.values(flows)) flow.refreshedHash = gitHash;
9033
- }
9034
- existing.features = { ...existing.features, ...features };
9035
- existing.flows = { ...existing.flows, ...flows };
9036
- existing.updatedAt = now;
9037
- existing.gitHash = gitHash;
9038
- await saveSnapshot(rootDir, existing);
9039
- await clearAllPartials(rootDir);
9040
- return JSON.stringify({
9041
- status: "updated",
9042
- mode: "merged",
9043
- features: Object.keys(existing.features).length,
9044
- flows: Object.keys(existing.flows).length,
9045
- removedFeatures: removedFeatures.length,
9046
- removedFlows: removedFlows.length
9047
- });
9048
- }
9049
- const snapshot = {
9050
- version: 2,
9051
- createdAt: now,
9052
- updatedAt: now,
9053
- gitHash,
9054
- features,
9055
- flows
9056
- };
9057
- await saveSnapshot(rootDir, snapshot);
9058
- await clearAllPartials(rootDir);
9059
- return JSON.stringify({
9060
- status: replaceMode ? "replaced" : "created",
9061
- mode: replaceMode ? "replaced-from-partials" : "fresh",
9062
- features: Object.keys(features).length,
9063
- flows: Object.keys(flows).length
9264
+ existing.features = { ...existing.features, ...features };
9265
+ existing.flows = { ...existing.flows, ...flows };
9266
+ existing.updatedAt = now;
9267
+ existing.gitHash = gitHash;
9268
+ return { snapshot: existing, afterSave: () => clearAllPartials(rootDir), result: JSON.stringify({
9269
+ status: "updated",
9270
+ mode: "merged",
9271
+ features: Object.keys(existing.features).length,
9272
+ flows: Object.keys(existing.flows).length,
9273
+ removedFeatures: removedFeatures.length,
9274
+ removedFlows: removedFlows.length
9275
+ }) };
9276
+ }
9277
+ const snapshot = {
9278
+ version: 2,
9279
+ createdAt: now,
9280
+ updatedAt: now,
9281
+ gitHash,
9282
+ features,
9283
+ flows
9284
+ };
9285
+ return { snapshot, afterSave: () => clearAllPartials(rootDir), result: JSON.stringify({
9286
+ status: replaceMode ? "replaced" : "created",
9287
+ mode: replaceMode ? "replaced-from-partials" : "fresh",
9288
+ features: Object.keys(features).length,
9289
+ flows: Object.keys(flows).length
9290
+ }) };
9064
9291
  });
9065
9292
  }
9066
9293
  async function getImpact(dir, files) {
9067
- const rootDir = path34.resolve(dir);
9294
+ const rootDir = path35.resolve(dir);
9068
9295
  const { analyzeImpact: analyzeImpact2 } = await Promise.resolve().then(() => (init_impact(), impact_exports));
9069
9296
  const result = await analyzeImpact2(rootDir, files);
9070
9297
  return JSON.stringify(result, null, 2);
9071
9298
  }
9072
9299
  var VERIFY_DEFAULT_SAMPLE = 5;
9073
- var VERIFY_MAX_FILES_PER_ENTRY = 8;
9074
- var VERIFY_SKELETON_CHARS = 500;
9075
9300
  async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
9076
- const rootDir = path34.resolve(dir);
9301
+ const rootDir = path35.resolve(dir);
9077
9302
  const snapshot = await loadSnapshot(rootDir);
9078
9303
  if (!snapshot) {
9079
9304
  return JSON.stringify({
@@ -9107,25 +9332,13 @@ async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
9107
9332
  const picked = entries.slice(0, Math.max(1, sample));
9108
9333
  const toVerify = [];
9109
9334
  for (const entry of picked) {
9110
- const skeletons = [];
9111
- for (const filePath of entry.files.slice(0, VERIFY_MAX_FILES_PER_ENTRY)) {
9112
- const full = await access.read(filePath);
9113
- if (full) {
9114
- skeletons.push({
9115
- path: full.path,
9116
- content: full.content.slice(0, VERIFY_SKELETON_CHARS)
9117
- });
9118
- } else {
9119
- skeletons.push({ path: filePath, missing: true });
9120
- }
9121
- }
9335
+ const stored = entry.kind === "feature" ? snapshot.features[entry.name] : snapshot.flows[entry.name];
9122
9336
  toVerify.push({
9123
9337
  name: entry.name,
9124
9338
  kind: entry.kind,
9125
9339
  description: entry.description,
9126
9340
  lastVerified: entry.verifiedAt ?? "never",
9127
- skeletons,
9128
- truncated: entry.files.length > VERIFY_MAX_FILES_PER_ENTRY
9341
+ ...await prepareSnapshotReview(access, entry.kind, entry.name, stored)
9129
9342
  });
9130
9343
  }
9131
9344
  const neverVerified = entries.filter((e) => !e.verifiedAt).length;
@@ -9134,58 +9347,94 @@ async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
9134
9347
  totalEntries: entries.length,
9135
9348
  neverVerified,
9136
9349
  entries: toVerify,
9137
- instructions: 'For each entry, judge from the skeletons whether the listed files actually implement the claimed feature/flow (missing files count against it). Then call save_verification with verdicts: {"<entry name>": {"ok": true|false, "note": "<one line, required when ok is false>"}}. Be skeptical \u2014 a plausible description is not evidence; the files must show it.'
9350
+ instructions: 'Judge whether the files implement each claimed feature/flow, then call save_verification with verdicts: {"<entry name>": {"kind": "<feature|flow from the entry>", "reviewToken": "<token from the entry>", "ok": true|false, "note": "<required when ok is false>"}}. Tokens cover the entry and full contents of the previewed files (at most 8), including unavailable-file markers; previews themselves are truncated. This is a spot-check, not proof of unshown files. A conflict requires verify_snapshot and a fresh review; never reuse a verdict for changed content. If a feature and flow share a name, submit their verdicts in separate calls.'
9138
9351
  });
9139
9352
  }
9140
9353
  async function saveVerification(dir, verdicts) {
9141
- const rootDir = path34.resolve(dir);
9142
- const snapshot = await loadSnapshot(rootDir);
9143
- if (!snapshot) {
9144
- return JSON.stringify({ exists: false, hint: "No concept map exists." });
9145
- }
9146
- const now = (/* @__PURE__ */ new Date()).toISOString();
9147
- const verifiedHash = await getCurrentGitHash(rootDir);
9148
- const stamped = [];
9149
- const unknown = [];
9150
- const failed = [];
9151
- for (const [name, verdict] of Object.entries(verdicts)) {
9152
- const entry = snapshot.features[name] ?? snapshot.flows[name];
9153
- if (!entry) {
9154
- unknown.push(name);
9155
- continue;
9156
- }
9157
- entry.verifiedAt = now;
9158
- entry.verifiedHash = verifiedHash;
9159
- if (verdict.ok) {
9160
- delete entry.verificationFailed;
9161
- delete entry.verificationNote;
9162
- } else {
9163
- entry.verificationFailed = true;
9164
- entry.verificationNote = verdict.note ?? "verification failed";
9165
- failed.push(name);
9166
- }
9167
- stamped.push(name);
9168
- }
9169
- snapshot.updatedAt = now;
9170
- await saveSnapshot(rootDir, snapshot);
9171
- return JSON.stringify({
9172
- stamped,
9173
- unknown,
9174
- failed,
9175
- hint: failed.length > 0 ? `Entries [${failed.join(", ")}] are mis-mapped. Re-map them: read their actual files, correct the entries, and call save_snapshot with only those entries (plus removeFeatures/removeFlows if a concept no longer exists).` : "All sampled entries verified. Re-run verify_snapshot periodically \u2014 it always picks the least-recently-verified entries next."
9354
+ const rootDir = path35.resolve(dir);
9355
+ return updateSnapshot(rootDir, async (snapshot) => {
9356
+ if (!snapshot) {
9357
+ return { snapshot: null, result: JSON.stringify({ exists: false, stamped: [], hint: "No concept map exists. Build it and run verify_snapshot before recording verdicts." }) };
9358
+ }
9359
+ const stamped = [], unknown = [], failed = [];
9360
+ const conflicts = [], reviewRequired = [], invalid = [];
9361
+ const candidates = [];
9362
+ let access;
9363
+ for (const [name, verdict] of Object.entries(verdicts)) {
9364
+ if (!verdict.kind || !["feature", "flow"].includes(verdict.kind) || !/^[a-f0-9]{64}$/.test(verdict.reviewToken ?? "")) {
9365
+ reviewRequired.push(name);
9366
+ continue;
9367
+ }
9368
+ if (typeof verdict.ok !== "boolean" || !verdict.ok && !verdict.note?.trim()) {
9369
+ invalid.push(name);
9370
+ continue;
9371
+ }
9372
+ const collection = verdict.kind === "feature" ? snapshot.features : snapshot.flows;
9373
+ if (!Object.hasOwn(collection, name)) {
9374
+ unknown.push(name);
9375
+ conflicts.push(name);
9376
+ continue;
9377
+ }
9378
+ const entry = collection[name];
9379
+ access ??= await createFileAccess(rootDir);
9380
+ const current = await prepareSnapshotReview(access, verdict.kind, name, entry);
9381
+ if (current.reviewToken !== verdict.reviewToken) conflicts.push(name);
9382
+ else candidates.push({ name, verdict, entry });
9383
+ }
9384
+ const verifiedHash = candidates.length ? await getCurrentGitHash(rootDir) : "unknown";
9385
+ const freshAccess = candidates.length ? await createFileAccess(rootDir) : null;
9386
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9387
+ for (const { name, verdict, entry } of candidates) {
9388
+ const current = await prepareSnapshotReview(freshAccess, verdict.kind, name, entry);
9389
+ if (current.reviewToken !== verdict.reviewToken) {
9390
+ conflicts.push(name);
9391
+ continue;
9392
+ }
9393
+ entry.verifiedAt = now;
9394
+ entry.verifiedHash = verifiedHash;
9395
+ entry.verificationToken = verdict.reviewToken;
9396
+ if (verdict.ok) {
9397
+ delete entry.verificationFailed;
9398
+ delete entry.verificationNote;
9399
+ } else {
9400
+ entry.verificationFailed = true;
9401
+ entry.verificationNote = verdict.note.trim();
9402
+ failed.push(name);
9403
+ }
9404
+ stamped.push(name);
9405
+ }
9406
+ const rejected = conflicts.length + reviewRequired.length + invalid.length;
9407
+ const status = rejected ? stamped.length ? "partial" : conflicts.length ? "conflict" : reviewRequired.length ? "review_required" : "invalid" : stamped.length ? "saved" : "unchanged";
9408
+ const hints = [];
9409
+ if (conflicts.length) hints.push(`Entries [${conflicts.join(", ")}] changed or were deleted after review. Run verify_snapshot and review current evidence before retrying.`);
9410
+ if (reviewRequired.length) hints.push(`Entries [${reviewRequired.join(", ")}] need kind and reviewToken from verify_snapshot. Inspect that evidence before submitting a verdict.`);
9411
+ if (invalid.length) hints.push(`Entries [${invalid.join(", ")}] need a boolean ok and a non-empty note when ok is false.`);
9412
+ if (failed.length) hints.push(`Entries [${failed.join(", ")}] are mis-mapped. Re-map them: read their actual files, correct the entries, and call save_snapshot with only those entries (plus removeFeatures/removeFlows if a concept no longer exists). Then run verify_snapshot for fresh review tokens.`);
9413
+ if (!hints.length) hints.push(stamped.length ? "All submitted entries verified against the reviewed evidence. Re-run verify_snapshot periodically." : "No verdicts submitted; nothing changed.");
9414
+ if (stamped.length) snapshot.updatedAt = now;
9415
+ return { snapshot: stamped.length ? snapshot : null, result: JSON.stringify({
9416
+ status,
9417
+ stamped,
9418
+ unknown,
9419
+ failed,
9420
+ conflicts,
9421
+ reviewRequired,
9422
+ invalid,
9423
+ hint: hints.join(" ")
9424
+ }) };
9176
9425
  });
9177
9426
  }
9178
9427
  async function saveDecision(dir, input) {
9179
- const rootDir = path34.resolve(dir);
9428
+ const rootDir = path35.resolve(dir);
9180
9429
  const { upsertDecision: upsertDecision2 } = await Promise.resolve().then(() => (init_decisions(), decisions_exports));
9181
9430
  const result = await upsertDecision2(rootDir, input);
9182
9431
  return JSON.stringify(result);
9183
9432
  }
9184
9433
  async function reviewDecision2(dir, input) {
9185
- return JSON.stringify(await reviewDecision(path34.resolve(dir), input));
9434
+ return JSON.stringify(await reviewDecision(path35.resolve(dir), input));
9186
9435
  }
9187
9436
  async function getContext(dir, task, files) {
9188
- const rootDir = path34.resolve(dir);
9437
+ const rootDir = path35.resolve(dir);
9189
9438
  const { assembleContext: assembleContext2 } = await Promise.resolve().then(() => (init_assemble(), assemble_exports));
9190
9439
  const bundle = await assembleContext2(rootDir, task, files);
9191
9440
  return JSON.stringify(bundle);
@@ -9234,7 +9483,7 @@ async function masonInit(dir, options = {}) {
9234
9483
  return JSON.stringify(await setupProject2(dir, options), null, 2);
9235
9484
  }
9236
9485
  if (options.host) throw new Error("host applies only to mode: setup.");
9237
- const rootDir = path34.resolve(dir);
9486
+ const rootDir = path35.resolve(dir);
9238
9487
  const marker = await loadProjectMarker(rootDir);
9239
9488
  const { loadSetup: loadSetup2 } = await Promise.resolve().then(() => (init_model(), model_exports));
9240
9489
  const setup = await loadSetup2(rootDir);
@@ -9254,7 +9503,7 @@ async function masonInit(dir, options = {}) {
9254
9503
  );
9255
9504
  }
9256
9505
  async function masonCompleteInit(dir, options = {}) {
9257
- const rootDir = path34.resolve(dir);
9506
+ const rootDir = path35.resolve(dir);
9258
9507
  const existing = await loadProjectMarker(rootDir);
9259
9508
  const features = await loadProjectFeatures(rootDir);
9260
9509
  const marker = {
@@ -9363,7 +9612,7 @@ async function masonSetConfluence(input) {
9363
9612
  );
9364
9613
  }
9365
9614
  async function exportToConfluenceTool(dir, overrides) {
9366
- const rootDir = path34.resolve(dir);
9615
+ const rootDir = path35.resolve(dir);
9367
9616
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports2));
9368
9617
  const { exportToConfluence: exportToConfluence2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
9369
9618
  const config = await loadConfig2();
@@ -9402,10 +9651,10 @@ function createMcpServer() {
9402
9651
  const server = new McpServer(
9403
9652
  {
9404
9653
  name: "mason",
9405
- version: "0.17.4"
9654
+ version: "0.19.0"
9406
9655
  },
9407
9656
  {
9408
- 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, failed verification needs 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.'
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.'
9409
9658
  }
9410
9659
  );
9411
9660
  server.tool(
@@ -9527,7 +9776,7 @@ function createMcpServer() {
9527
9776
  );
9528
9777
  server.tool(
9529
9778
  "get_snapshot",
9530
- "Return the optional feature-to-file architecture map with drift and trust evidence. If no map exists, returns exists:false plus project structure, Git signals, and test pairs. Decision capture, get_context, and get_impact still work. No initialization required.",
9779
+ "Return the optional feature-to-file architecture map with drift and trust evidence. Map verification is rechecked against sampled source contents; stale or unknown verification requires a fresh review, and recordedVerdict preserves the historical verdict. If no map exists, returns exists:false plus project structure, Git signals, and test pairs. Decision capture, get_context, and get_impact still work. No initialization required.",
9531
9780
  {
9532
9781
  dir: z24.string().describe("Absolute path to the project root directory")
9533
9782
  },
@@ -9540,7 +9789,7 @@ function createMcpServer() {
9540
9789
  );
9541
9790
  server.tool(
9542
9791
  "get_context",
9543
- "Assemble task context: matching decisions with rationale, approval, owner, sources, last review, and freshness, plus related tests, file impact, and any available map entries. Proposals are suggestions; legacy records are unreviewed; accepted decisions are constraints subject to freshness. No initialization or map required. Pass task and optional files. map.status and diagnostics preserve missing or invalid knowledge. Impact covers up to three unique files, expanding directory anchors.",
9792
+ "Assemble task context: matching decisions with rationale, approval, owner, sources, last review, and freshness, plus related tests, file impact, and any available map entries. Map verification is rechecked against sampled source contents; stale or unknown verification requires a fresh review, and recordedVerdict preserves the historical verdict. Proposals are suggestions; legacy records are unreviewed; accepted decisions are constraints subject to freshness. No initialization or map required. Pass task and optional files. map.status and diagnostics preserve missing or invalid knowledge. Impact covers up to three unique files, expanding directory anchors.",
9544
9793
  {
9545
9794
  dir: z24.string().describe("Absolute path to the project root directory"),
9546
9795
  task: z24.string().describe("The task, bug, or change request in natural language \u2014 e.g. 'add rate limiting to the API client' or a ticket description"),
@@ -9716,7 +9965,7 @@ function createMcpServer() {
9716
9965
  );
9717
9966
  server.tool(
9718
9967
  "verify_snapshot",
9719
- "Spot-check the concept map's CORRECTNESS (drift checks freshness; this checks entries were right to begin with). Returns a sample of entries \u2014 always the never-verified and least-recently-verified first \u2014 with skeletons of their claimed files, for you to judge whether the files actually implement what the entry claims. Report verdicts back via save_verification. Run periodically, or after an automated refresh wrote entries no human reviewed.",
9968
+ "Spot-check the concept map's CORRECTNESS (drift checks freshness; this checks entries were right to begin with). Returns a sample of entries \u2014 always the never-verified and least-recently-verified first \u2014 with skeletons of their claimed files, for you to judge whether the files actually implement what the entry claims. Report verdicts back via save_verification with each entry\u2019s kind and reviewToken. Tokens bind the entry and bounded source evidence; changed evidence requires a fresh review. Run periodically, or after an automated refresh wrote entries no human reviewed.",
9720
9969
  {
9721
9970
  dir: z24.string().describe("Absolute path to the project root directory"),
9722
9971
  sample: z24.number().int().optional().describe("Entries to sample (default 5)")
@@ -9728,11 +9977,13 @@ function createMcpServer() {
9728
9977
  );
9729
9978
  server.tool(
9730
9979
  "save_verification",
9731
- "Record verify_snapshot verdicts. Entries judged ok are stamped verifiedAt; failures are flagged verificationFailed with your note and surface in get_context, get_snapshot, and mason_check_drift until corrected. Verdict notes are required for failures.",
9980
+ "Record verify_snapshot verdicts using each entry\u2019s kind and reviewToken. Missing tokens request a fresh review; changed or deleted entries return conflicts without being stamped. Entries judged ok are stamped verifiedAt; failures are flagged verificationFailed with your note and surface in get_context, get_snapshot, and mason_check_drift until corrected. Verdict notes are required for failures.",
9732
9981
  {
9733
9982
  dir: z24.string().describe("Absolute path to the project root directory"),
9734
9983
  verdicts: z24.record(
9735
9984
  z24.object({
9985
+ kind: z24.enum(["feature", "flow"]).optional().describe("Entry kind returned by verify_snapshot; required to record a verdict"),
9986
+ reviewToken: z24.string().optional().describe("Copy the reviewToken from verify_snapshot after inspecting its evidence; required to record a verdict"),
9736
9987
  ok: z24.boolean(),
9737
9988
  note: z24.string().optional().describe("One line on what's wrong \u2014 required when ok is false")
9738
9989
  })
@@ -9781,14 +10032,592 @@ function createMcpServer() {
9781
10032
  );
9782
10033
  return server;
9783
10034
  }
9784
- async function startMcpServer() {
10035
+ async function startMcpServer(onClose) {
9785
10036
  const server = createMcpServer();
10037
+ if (onClose) server.server.onclose = onClose;
9786
10038
  const transport = new StdioServerTransport();
9787
10039
  await server.connect(transport);
9788
10040
  }
9789
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
+
9790
10619
  // bin/mason-mcp.ts
9791
- startMcpServer().catch((err) => {
10620
+ startMcpWithUpdates(startMcpServer).catch((err) => {
9792
10621
  process.stderr.write(`Mason MCP server error: ${err}
9793
10622
  `);
9794
10623
  process.exit(1);