mason-context 0.15.0 → 0.16.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
@@ -2832,7 +2832,13 @@ function managedBlock(text2, start, end, body) {
2832
2832
  async function applyEdit(root, edit) {
2833
2833
  const current = await readText(root, edit.path);
2834
2834
  if (current === edit.after) return false;
2835
- if (current !== edit.before) throw new Error("Setup input changed during installation: " + edit.path + ". Rerun setup to resume.");
2835
+ if (current !== edit.before) throw new Error("Integration input changed during update: " + edit.path + ". Rerun the requested command to resume.");
2836
+ if (edit.after === null) {
2837
+ const file2 = await storePath(root, edit.path);
2838
+ if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
2839
+ await fs13.unlink(file2);
2840
+ return true;
2841
+ }
2836
2842
  const file = await storePath(root, edit.path, true);
2837
2843
  const temporary = path19.join(path19.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
2838
2844
  try {
@@ -2844,7 +2850,7 @@ async function applyEdit(root, edit) {
2844
2850
  } finally {
2845
2851
  await handle.close();
2846
2852
  }
2847
- if (await readText(root, edit.path) !== edit.before) throw new Error("Setup input changed during installation: " + edit.path);
2853
+ if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
2848
2854
  await fs13.rename(temporary, file);
2849
2855
  return true;
2850
2856
  } finally {
@@ -3130,7 +3136,7 @@ var init_evidence = __esm({
3130
3136
  init_dead_command();
3131
3137
  init_storage();
3132
3138
  exec11 = promisify12(execFile12);
3133
- engineVersion = true ? "0.15.0" : "development";
3139
+ engineVersion = true ? "0.16.0" : "development";
3134
3140
  hash = (value) => createHash4("sha256").update(JSON.stringify(value)).digest("hex");
3135
3141
  content = readAuditInput;
3136
3142
  internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
@@ -3494,6 +3500,79 @@ var init_adapters = __esm({
3494
3500
  }
3495
3501
  });
3496
3502
 
3503
+ // src/setup/ownership.ts
3504
+ var ownership_exports = {};
3505
+ __export(ownership_exports, {
3506
+ BLOCKS: () => BLOCKS,
3507
+ OWNERSHIP_PATH: () => OWNERSHIP_PATH,
3508
+ blockSpan: () => blockSpan,
3509
+ completedHookOwnership: () => completedHookOwnership,
3510
+ ownershipEdit: () => ownershipEdit,
3511
+ ownershipSchema: () => ownershipSchema
3512
+ });
3513
+ import { z as z10 } from "zod";
3514
+ import { parse } from "smol-toml";
3515
+ function blockSpan(text2, start, end) {
3516
+ const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
3517
+ if (!starts && !ends) return null;
3518
+ const from = text2.indexOf(start), to = text2.indexOf(end) + end.length;
3519
+ if (starts !== 1 || ends !== 1 || to <= from) throw new Error("Ambiguous Mason markers; inspect the marked block manually.");
3520
+ return { from, to, text: text2.slice(from, to) };
3521
+ }
3522
+ async function ownershipEdit(root, edits, hookCommands = {}) {
3523
+ const before = await readText(root, OWNERSHIP_PATH);
3524
+ const record = before === null ? { version: 1, files: {} } : ownershipSchema.parse(JSON.parse(before));
3525
+ for (const edit of edits) {
3526
+ const previous = record.files[edit.path];
3527
+ const entry = {
3528
+ created: edit.before === null || (previous?.created ?? false),
3529
+ initialHash: edit.before === null || previous?.initialHash === hash(edit.before) ? hash(edit.after) : previous?.initialHash ?? hash(edit.after),
3530
+ blocks: Object.fromEntries(BLOCKS.flatMap(([start, end]) => {
3531
+ const block = blockSpan(edit.after, start, end);
3532
+ return block ? [[start, hash(block.text)]] : [];
3533
+ }))
3534
+ };
3535
+ const mcp = edit.path === ".codex/config.toml" ? parse(edit.after).mcp_servers?.mason : edit.path === ".mcp.json" ? JSON.parse(edit.after).mcpServers?.mason : void 0;
3536
+ const hooks = edit.path === ".codex/hooks.json" || edit.path === ".claude/settings.json" ? JSON.parse(edit.after).hooks : void 0;
3537
+ record.files[edit.path] = {
3538
+ ...entry,
3539
+ ...mcp === void 0 ? {} : { mcpHash: hash(mcp) },
3540
+ ...hookCommands[edit.path] ? { hookCommands: [.../* @__PURE__ */ new Set([...previous?.hookCommands ?? [], hookCommands[edit.path]])] } : {},
3541
+ ...hooks === void 0 ? {} : { hooks: Object.fromEntries(Object.entries(hooks).map(([event, groups]) => [event, groups.flatMap((group) => group.hooks.map(hash))])) }
3542
+ };
3543
+ }
3544
+ return { path: OWNERSHIP_PATH, before, after: JSON.stringify(record, null, 2) + "\n" };
3545
+ }
3546
+ async function completedHookOwnership(root, file, command) {
3547
+ const before = await readText(root, OWNERSHIP_PATH);
3548
+ const record = ownershipSchema.parse(JSON.parse(before ?? "null"));
3549
+ record.files[file].hookCommands = [command];
3550
+ return { path: OWNERSHIP_PATH, before, after: JSON.stringify(record, null, 2) + "\n" };
3551
+ }
3552
+ var OWNERSHIP_PATH, BLOCKS, fileSchema, ownershipSchema;
3553
+ var init_ownership = __esm({
3554
+ "src/setup/ownership.ts"() {
3555
+ "use strict";
3556
+ init_evidence();
3557
+ init_files2();
3558
+ OWNERSHIP_PATH = ".mason/local/integration.json";
3559
+ BLOCKS = [
3560
+ ["<!-- mason:start -->", "<!-- mason:end -->"],
3561
+ ["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
3562
+ ["# mason:mcp:start", "# mason:mcp:end"]
3563
+ ];
3564
+ fileSchema = z10.object({
3565
+ created: z10.boolean(),
3566
+ initialHash: z10.string(),
3567
+ blocks: z10.record(z10.string()),
3568
+ mcpHash: z10.string().optional(),
3569
+ hookCommands: z10.array(z10.string()).optional(),
3570
+ hooks: z10.record(z10.array(z10.string())).optional()
3571
+ });
3572
+ ownershipSchema = z10.object({ version: z10.literal(1), files: z10.record(fileSchema) });
3573
+ }
3574
+ });
3575
+
3497
3576
  // src/automation/install.ts
3498
3577
  var install_exports = {};
3499
3578
  __export(install_exports, {
@@ -3502,24 +3581,28 @@ __export(install_exports, {
3502
3581
  configPath: () => configPath,
3503
3582
  installAutomation: () => installAutomation,
3504
3583
  installedAutomation: () => installedAutomation,
3505
- planAutomationInstall: () => planAutomationInstall
3584
+ planAutomationInstall: () => planAutomationInstall,
3585
+ recordSchema: () => recordSchema
3506
3586
  });
3507
- import { z as z10 } from "zod";
3587
+ import { z as z11 } from "zod";
3508
3588
  async function installationRecord(root) {
3509
3589
  return recordSchema.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
3510
3590
  }
3511
3591
  async function installAutomation(dir, host, command) {
3512
3592
  const ws = await workspace(dir);
3513
- return withLock(ws.root, ".mason/reports/automation-install", () => installLocked(ws.root, host, command));
3593
+ return withLock(ws.root, ".mason/reports/setup-lock", () => installLocked(ws.root, host, command));
3514
3594
  }
3515
3595
  async function planAutomationInstall(root, host, command) {
3516
3596
  const file = configPath(host);
3517
- const existing = automationConfigSchema.parse(await readStoreJson(root, file) ?? {});
3597
+ const before = await readText(root, file);
3598
+ const existing = automationConfigSchema.parse(JSON.parse(before ?? "{}"));
3518
3599
  const record = await installationRecord(root);
3519
3600
  const desired = hookConfig(host, command);
3520
3601
  const newCommand = desired.hooks.SessionStart[0].hooks[0].command;
3521
3602
  const previous = record.hosts[host]?.command;
3522
- const owned = /* @__PURE__ */ new Set([previous, newCommand]);
3603
+ const ownershipText = await readText(root, OWNERSHIP_PATH);
3604
+ const pending = ownershipText === null ? [] : ownershipSchema.parse(JSON.parse(ownershipText)).files[file]?.hookCommands ?? [];
3605
+ const owned = /* @__PURE__ */ new Set([previous, ...pending, newCommand]);
3523
3606
  const hooks = existing.hooks ?? {};
3524
3607
  for (const event of HOOK_EVENTS) {
3525
3608
  hooks[event] = (hooks[event] ?? []).map((group) => ({
@@ -3529,15 +3612,20 @@ async function planAutomationInstall(root, host, command) {
3529
3612
  hooks[event].push(...desired.hooks[event]);
3530
3613
  }
3531
3614
  record.hosts[host] = { command: newCommand };
3532
- return { file, config: { ...existing, hooks }, record, newCommand };
3615
+ return { file, before, config: { ...existing, hooks }, record, newCommand };
3533
3616
  }
3534
3617
  async function installLocked(root, host, command) {
3535
- const { file, config, record, newCommand } = await planAutomationInstall(root, host, command);
3618
+ const { file, before, config, record, newCommand } = await planAutomationInstall(root, host, command);
3536
3619
  const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3537
3620
  const { applyEdit: applyEdit2 } = await Promise.resolve().then(() => (init_files2(), files_exports));
3538
- for (const edit of await ancillaryEdits2(root)) await applyEdit2(root, edit);
3539
- await writeStoreJson(root, file, config);
3621
+ const { ownershipEdit: ownershipEdit2, completedHookOwnership: completedHookOwnership2 } = await Promise.resolve().then(() => (init_ownership(), ownership_exports));
3622
+ const edit = { path: file, before, after: JSON.stringify(config, null, 2) + "\n" };
3623
+ const ancillary = await ancillaryEdits2(root);
3624
+ await applyEdit2(root, await ownershipEdit2(root, [edit], { [file]: newCommand }));
3625
+ for (const edit2 of ancillary) await applyEdit2(root, edit2);
3626
+ await applyEdit2(root, edit);
3540
3627
  await writeStoreJson(root, AUTOMATION_PATH, record);
3628
+ await applyEdit2(root, await completedHookOwnership2(root, file, newCommand));
3541
3629
  return {
3542
3630
  version: 1,
3543
3631
  host,
@@ -3575,9 +3663,11 @@ var init_install = __esm({
3575
3663
  init_evidence();
3576
3664
  init_adapters();
3577
3665
  init_store();
3578
- groupSchema = z10.object({ hooks: z10.array(z10.object({ type: z10.string(), command: z10.string().optional() }).passthrough()) }).passthrough();
3579
- automationConfigSchema = z10.object({ hooks: z10.record(z10.array(groupSchema)).optional() }).passthrough();
3580
- recordSchema = z10.object({ version: z10.literal(1), hosts: z10.record(z10.object({ command: z10.string() })) });
3666
+ init_files2();
3667
+ init_ownership();
3668
+ groupSchema = z11.object({ hooks: z11.array(z11.object({ type: z11.string(), command: z11.string().optional() }).passthrough()) }).passthrough();
3669
+ automationConfigSchema = z11.object({ hooks: z11.record(z11.array(groupSchema)).optional() }).passthrough();
3670
+ recordSchema = z11.object({ version: z11.literal(1), hosts: z11.record(z11.object({ command: z11.string() })) });
3581
3671
  AUTOMATION_PATH = ".mason/local/automation.json";
3582
3672
  configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
3583
3673
  }
@@ -3592,7 +3682,7 @@ __export(model_exports, {
3592
3682
  setupHostSchema: () => setupHostSchema,
3593
3683
  setupSchema: () => setupSchema
3594
3684
  });
3595
- import { z as z11 } from "zod";
3685
+ import { z as z12 } from "zod";
3596
3686
  async function loadSetup(root) {
3597
3687
  const raw = await readStoreJson(root, SETUP_PATH);
3598
3688
  if (raw === null) return null;
@@ -3610,27 +3700,27 @@ var init_model = __esm({
3610
3700
  "src/setup/model.ts"() {
3611
3701
  "use strict";
3612
3702
  init_storage();
3613
- setupHostSchema = z11.object({
3614
- configuredVersion: z11.string(),
3615
- revision: z11.string().uuid(),
3616
- fingerprint: z11.string(),
3617
- mcpFingerprint: z11.string(),
3618
- instructions: z11.array(z11.string())
3703
+ setupHostSchema = z12.object({
3704
+ configuredVersion: z12.string(),
3705
+ revision: z12.string().uuid(),
3706
+ fingerprint: z12.string(),
3707
+ mcpFingerprint: z12.string(),
3708
+ instructions: z12.array(z12.string())
3619
3709
  });
3620
- setupSchema = z11.object({ version: z11.literal(2), hosts: z11.object({
3710
+ setupSchema = z12.object({ version: z12.literal(2), hosts: z12.object({
3621
3711
  codex: setupHostSchema.optional(),
3622
3712
  claude: setupHostSchema.optional()
3623
3713
  }) });
3624
3714
  SETUP_PATH = ".mason/local/setup.json";
3625
- receiptSchema = z11.object({
3626
- version: z11.literal(1),
3627
- host: z11.enum(["codex", "claude"]),
3628
- status: z11.enum(["installing", "configured"]),
3629
- initialReportPath: z11.string(),
3630
- initialBaselinePaths: z11.array(z11.string()),
3631
- root: z11.string(),
3632
- revision: z11.string().uuid(),
3633
- configuredAt: z11.string().optional()
3715
+ receiptSchema = z12.object({
3716
+ version: z12.literal(1),
3717
+ host: z12.enum(["codex", "claude"]),
3718
+ status: z12.enum(["installing", "configured"]),
3719
+ initialReportPath: z12.string(),
3720
+ initialBaselinePaths: z12.array(z12.string()),
3721
+ root: z12.string(),
3722
+ revision: z12.string().uuid(),
3723
+ configuredAt: z12.string().optional()
3634
3724
  });
3635
3725
  }
3636
3726
  });
@@ -3662,7 +3752,7 @@ var init_launcher = __esm({
3662
3752
  "use strict";
3663
3753
  init_evidence();
3664
3754
  init_model();
3665
- executingVersion = () => true ? "0.15.0" : "development";
3755
+ executingVersion = () => true ? "0.16.0" : "development";
3666
3756
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3667
3757
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3668
3758
  }
@@ -3679,8 +3769,8 @@ __export(config_exports, {
3679
3769
  mcpPath: () => mcpPath
3680
3770
  });
3681
3771
  import { isDeepStrictEqual } from "util";
3682
- import { parse, stringify } from "smol-toml";
3683
- import { z as z12 } from "zod";
3772
+ import { parse as parse2, stringify } from "smol-toml";
3773
+ import { z as z13 } from "zod";
3684
3774
  async function instructionEdits(root, host) {
3685
3775
  const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
3686
3776
  const existing = found.find((f) => f.text !== null);
@@ -3721,7 +3811,7 @@ async function mcpEdit(root, host) {
3721
3811
  const servers2 = object.parse(config2.mcpServers ?? {});
3722
3812
  return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host) } }, null, 2) + "\n" };
3723
3813
  }
3724
- const config = parse(before ?? "");
3814
+ const config = parse2(before ?? "");
3725
3815
  const managed = (before ?? "").includes(TOML_START);
3726
3816
  const servers = config.mcp_servers;
3727
3817
  const desired = managedMcp(servers?.mason, host);
@@ -3733,7 +3823,7 @@ async function mcpEdit(root, host) {
3733
3823
  for (const span of spans.reverse()) base = base.slice(0, span.start) + base.slice(span.end);
3734
3824
  }
3735
3825
  const after = managedBlock(base, TOML_START, TOML_END, stringify({ mcp_servers: { mason: desired } }).trimEnd());
3736
- const parsed = parse(after);
3826
+ const parsed = parse2(after);
3737
3827
  const expected = { ...config, mcp_servers: { ...servers ?? {}, mason: desired } };
3738
3828
  if (!isDeepStrictEqual(parsed, expected)) throw new Error("Could not safely configure the Mason MCP entry without changing other settings.");
3739
3829
  return { path: file, before, after };
@@ -3771,7 +3861,7 @@ async function ancillaryEdits(root) {
3771
3861
  }
3772
3862
  async function inspectHostConfig(root, host, plannedMcp) {
3773
3863
  const text2 = plannedMcp ?? await readText(root, mcpPath(host));
3774
- const config = host === "codex" ? parse(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
3864
+ const config = host === "codex" ? parse2(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
3775
3865
  const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
3776
3866
  const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
3777
3867
  const expected = hookConfig(host, hookCommand(host));
@@ -3796,7 +3886,7 @@ var init_config = __esm({
3796
3886
  init_install();
3797
3887
  init_files2();
3798
3888
  init_launcher();
3799
- object = z12.record(z12.unknown());
3889
+ object = z13.record(z13.unknown());
3800
3890
  mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
3801
3891
  TOML_START = "# mason:mcp:start";
3802
3892
  TOML_END = "# mason:mcp:end";
@@ -3806,7 +3896,7 @@ var init_config = __esm({
3806
3896
  });
3807
3897
 
3808
3898
  // src/mcp/init.ts
3809
- import { z as z13 } from "zod";
3899
+ import { z as z14 } from "zod";
3810
3900
  async function loadProjectMarker(rootDir) {
3811
3901
  const raw = await readStoreJson(rootDir, ".mason/local/project.json");
3812
3902
  if (raw === null) return null;
@@ -3814,8 +3904,8 @@ async function loadProjectMarker(rootDir) {
3814
3904
  return { ...marker, features: await loadProjectFeatures(rootDir) };
3815
3905
  }
3816
3906
  async function loadProjectFeatures(rootDir) {
3817
- const config = z13.record(z13.unknown()).parse(await readStoreJson(rootDir, ".mason/config.json") ?? {});
3818
- return z13.object({ confluence: z13.boolean().optional() }).passthrough().parse(config.features ?? {});
3907
+ const config = z14.record(z14.unknown()).parse(await readStoreJson(rootDir, ".mason/config.json") ?? {});
3908
+ return z14.object({ confluence: z14.boolean().optional() }).passthrough().parse(config.features ?? {});
3819
3909
  }
3820
3910
  async function saveProjectMarker(rootDir, marker) {
3821
3911
  const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
@@ -3827,8 +3917,8 @@ async function projectMarkerEdits(rootDir, marker) {
3827
3917
  const edits = [{ path: file, before: await readText(rootDir, file), after: JSON.stringify(local, null, 2) + "\n" }];
3828
3918
  if (features && Object.keys(features).length) {
3829
3919
  const configPath2 = ".mason/config.json", before = await readText(rootDir, configPath2);
3830
- const config = z13.record(z13.unknown()).parse(JSON.parse(before ?? "{}"));
3831
- config.features = { ...z13.record(z13.unknown()).parse(config.features ?? {}), ...features };
3920
+ const config = z14.record(z14.unknown()).parse(JSON.parse(before ?? "{}"));
3921
+ config.features = { ...z14.record(z14.unknown()).parse(config.features ?? {}), ...features };
3832
3922
  edits.push({ path: configPath2, before, after: JSON.stringify(config, null, 2) + "\n" });
3833
3923
  }
3834
3924
  return edits;
@@ -3842,10 +3932,10 @@ var init_init = __esm({
3842
3932
  "use strict";
3843
3933
  init_storage();
3844
3934
  init_files2();
3845
- markerSchema = z13.object({
3846
- version: z13.literal(1),
3847
- initializedAt: z13.string(),
3848
- features: z13.object({ confluence: z13.boolean().optional() }).passthrough().optional()
3935
+ markerSchema = z14.object({
3936
+ version: z14.literal(1),
3937
+ initializedAt: z14.string(),
3938
+ features: z14.object({ confluence: z14.boolean().optional() }).passthrough().optional()
3849
3939
  }).passthrough();
3850
3940
  CLAUDE_MD_SECTION = `<!-- mason:start -->
3851
3941
  ## Mason project knowledge
@@ -4039,7 +4129,7 @@ var init_types2 = __esm({
4039
4129
  });
4040
4130
 
4041
4131
  // src/review/evidence/vitest.ts
4042
- import { z as z14 } from "zod";
4132
+ import { z as z15 } from "zod";
4043
4133
  function parseVitest(raw, sourceRoot) {
4044
4134
  const report = schema.parse(raw);
4045
4135
  const findings = [], diagnostics = [];
@@ -4088,24 +4178,24 @@ var init_vitest = __esm({
4088
4178
  "use strict";
4089
4179
  init_paths2();
4090
4180
  init_types2();
4091
- count2 = z14.number().int().nonnegative();
4092
- schema = z14.object({
4093
- success: z14.boolean(),
4181
+ count2 = z15.number().int().nonnegative();
4182
+ schema = z15.object({
4183
+ success: z15.boolean(),
4094
4184
  numTotalTests: count2,
4095
4185
  numPassedTests: count2,
4096
4186
  numFailedTests: count2,
4097
4187
  numPendingTests: count2,
4098
4188
  numTodoTests: count2,
4099
4189
  numFailedTestSuites: count2,
4100
- testResults: z14.array(z14.object({
4101
- name: z14.string().min(1),
4102
- status: z14.enum(["passed", "failed"]),
4103
- message: z14.string().optional(),
4104
- assertionResults: z14.array(z14.object({
4105
- fullName: z14.string(),
4106
- status: z14.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
4107
- failureMessages: z14.array(z14.string()).nullable().optional(),
4108
- location: z14.object({ line: count2, column: count2 }).nullable().optional()
4190
+ testResults: z15.array(z15.object({
4191
+ name: z15.string().min(1),
4192
+ status: z15.enum(["passed", "failed"]),
4193
+ message: z15.string().optional(),
4194
+ assertionResults: z15.array(z15.object({
4195
+ fullName: z15.string(),
4196
+ status: z15.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
4197
+ failureMessages: z15.array(z15.string()).nullable().optional(),
4198
+ location: z15.object({ line: count2, column: count2 }).nullable().optional()
4109
4199
  }))
4110
4200
  }))
4111
4201
  });
@@ -4113,7 +4203,7 @@ var init_vitest = __esm({
4113
4203
  });
4114
4204
 
4115
4205
  // src/review/evidence/sarif.ts
4116
- import { z as z15 } from "zod";
4206
+ import { z as z16 } from "zod";
4117
4207
  function parseSarif(raw, sourceRoot) {
4118
4208
  const report = schema2.parse(raw), findings = [], diagnostics = [];
4119
4209
  let active = 0, suppressed = 0, absent = 0, unresolved = 0;
@@ -4204,29 +4294,29 @@ var init_sarif = __esm({
4204
4294
  "use strict";
4205
4295
  init_paths2();
4206
4296
  init_types2();
4207
- index = z15.number().int().nonnegative();
4208
- artifact = z15.object({ uri: z15.string().optional(), uriBaseId: z15.string().optional(), index: index.optional() });
4209
- message = z15.object({ text: z15.string().optional(), markdown: z15.string().optional(), id: z15.string().optional(), arguments: z15.array(z15.string()).optional() });
4210
- level = z15.enum(["error", "warning", "note", "none"]);
4211
- location = z15.object({ physicalLocation: z15.object({ artifactLocation: artifact.optional(), region: z15.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
4212
- schema2 = z15.object({
4213
- version: z15.literal("2.1.0"),
4214
- runs: z15.array(z15.object({
4215
- tool: z15.object({ driver: z15.object({ name: z15.string().min(1), rules: z15.array(z15.object({ id: z15.string(), defaultConfiguration: z15.object({ level: level.optional() }).optional(), messageStrings: z15.record(message).optional() })).optional(), globalMessageStrings: z15.record(message).optional() }) }),
4216
- invocations: z15.array(z15.object({ executionSuccessful: z15.boolean(), commandLine: z15.string().optional(), toolExecutionNotifications: z15.array(z15.object({ level: level.optional(), message })).optional() })).optional(),
4217
- versionControlProvenance: z15.array(z15.object({ revisionId: z15.string().optional() })).optional(),
4218
- originalUriBaseIds: z15.record(artifact).optional(),
4219
- artifacts: z15.array(z15.object({ location: artifact.optional() })).optional(),
4220
- results: z15.array(z15.object({
4221
- ruleId: z15.string().optional(),
4297
+ index = z16.number().int().nonnegative();
4298
+ artifact = z16.object({ uri: z16.string().optional(), uriBaseId: z16.string().optional(), index: index.optional() });
4299
+ message = z16.object({ text: z16.string().optional(), markdown: z16.string().optional(), id: z16.string().optional(), arguments: z16.array(z16.string()).optional() });
4300
+ level = z16.enum(["error", "warning", "note", "none"]);
4301
+ location = z16.object({ physicalLocation: z16.object({ artifactLocation: artifact.optional(), region: z16.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
4302
+ schema2 = z16.object({
4303
+ version: z16.literal("2.1.0"),
4304
+ runs: z16.array(z16.object({
4305
+ tool: z16.object({ driver: z16.object({ name: z16.string().min(1), rules: z16.array(z16.object({ id: z16.string(), defaultConfiguration: z16.object({ level: level.optional() }).optional(), messageStrings: z16.record(message).optional() })).optional(), globalMessageStrings: z16.record(message).optional() }) }),
4306
+ invocations: z16.array(z16.object({ executionSuccessful: z16.boolean(), commandLine: z16.string().optional(), toolExecutionNotifications: z16.array(z16.object({ level: level.optional(), message })).optional() })).optional(),
4307
+ versionControlProvenance: z16.array(z16.object({ revisionId: z16.string().optional() })).optional(),
4308
+ originalUriBaseIds: z16.record(artifact).optional(),
4309
+ artifacts: z16.array(z16.object({ location: artifact.optional() })).optional(),
4310
+ results: z16.array(z16.object({
4311
+ ruleId: z16.string().optional(),
4222
4312
  ruleIndex: index.optional(),
4223
4313
  message,
4224
4314
  level: level.optional(),
4225
- kind: z15.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
4226
- baselineState: z15.enum(["new", "unchanged", "updated", "absent"]).optional(),
4227
- suppressions: z15.array(z15.object({ kind: z15.enum(["inSource", "external"]), status: z15.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
4228
- locations: z15.array(location).optional(),
4229
- relatedLocations: z15.array(location).optional()
4315
+ kind: z16.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
4316
+ baselineState: z16.enum(["new", "unchanged", "updated", "absent"]).optional(),
4317
+ suppressions: z16.array(z16.object({ kind: z16.enum(["inSource", "external"]), status: z16.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
4318
+ locations: z16.array(location).optional(),
4319
+ relatedLocations: z16.array(location).optional()
4230
4320
  })).optional()
4231
4321
  }))
4232
4322
  });
@@ -4235,7 +4325,7 @@ var init_sarif = __esm({
4235
4325
 
4236
4326
  // src/review/evidence.ts
4237
4327
  import path21 from "path";
4238
- import { z as z16 } from "zod";
4328
+ import { z as z17 } from "zod";
4239
4329
  function relativeArtifact(root, file) {
4240
4330
  const relative = path21.isAbsolute(file) ? path21.relative(root, file) : file;
4241
4331
  const normalized = normalizeRepoPath(relative);
@@ -4276,7 +4366,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
4276
4366
  let manifest, raw;
4277
4367
  try {
4278
4368
  manifest = relativeArtifact(root, supplied);
4279
- raw = z16.object({ version: z16.literal(1), checks: z16.array(z16.unknown()) }).parse(await readStoreJson(root, manifest));
4369
+ raw = z17.object({ version: z17.literal(1), checks: z17.array(z17.unknown()) }).parse(await readStoreJson(root, manifest));
4280
4370
  } catch (error) {
4281
4371
  output.diagnostics.push(`${supplied}: ${String(error)}`);
4282
4372
  continue;
@@ -4429,19 +4519,19 @@ var init_evidence2 = __esm({
4429
4519
  init_vitest();
4430
4520
  init_sarif();
4431
4521
  init_types2();
4432
- checkSchema2 = z16.object({
4433
- id: z16.string().min(1).max(100),
4434
- kind: z16.enum(["tests", "static-analysis", "security", "complexity", "duplication"]),
4435
- tool: z16.string().min(1).max(200),
4436
- command: z16.string().min(1).max(2e3),
4437
- commit: z16.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
4438
- workingTreeClean: z16.boolean().optional(),
4439
- source: z16.string().max(2e3).optional(),
4440
- sourceRoot: z16.string().min(1).max(4e3).optional(),
4441
- status: z16.enum(["completed", "skipped", "unavailable"]).default("completed"),
4442
- reason: z16.string().min(1).max(2e3).optional(),
4443
- exitCode: z16.number().int().nullable().optional(),
4444
- report: z16.object({ format: z16.enum(["vitest-json", "sarif"]), path: z16.string().min(1).max(4e3) }).optional()
4522
+ checkSchema2 = z17.object({
4523
+ id: z17.string().min(1).max(100),
4524
+ kind: z17.enum(["tests", "static-analysis", "security", "complexity", "duplication"]),
4525
+ tool: z17.string().min(1).max(200),
4526
+ command: z17.string().min(1).max(2e3),
4527
+ commit: z17.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
4528
+ workingTreeClean: z17.boolean().optional(),
4529
+ source: z17.string().max(2e3).optional(),
4530
+ sourceRoot: z17.string().min(1).max(4e3).optional(),
4531
+ status: z17.enum(["completed", "skipped", "unavailable"]).default("completed"),
4532
+ reason: z17.string().min(1).max(2e3).optional(),
4533
+ exitCode: z17.number().int().nullable().optional(),
4534
+ report: z17.object({ format: z17.enum(["vitest-json", "sarif"]), path: z17.string().min(1).max(4e3) }).optional()
4445
4535
  });
4446
4536
  }
4447
4537
  });
@@ -5072,7 +5162,7 @@ __export(observations_exports, {
5072
5162
  readObservation: () => readObservation
5073
5163
  });
5074
5164
  import fs18 from "fs/promises";
5075
- import { z as z17 } from "zod";
5165
+ import { z as z18 } from "zod";
5076
5166
  function observationPath(directory, host, revision, version = executingVersion()) {
5077
5167
  return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
5078
5168
  }
@@ -5137,19 +5227,19 @@ var init_observations = __esm({
5137
5227
  init_storage();
5138
5228
  init_model();
5139
5229
  init_launcher();
5140
- observationSchema = z17.object({
5141
- version: z17.literal(1),
5142
- masonVersion: z17.string(),
5143
- root: z17.string(),
5144
- revision: z17.string(),
5145
- host: z17.enum(["codex", "claude"]),
5146
- contextCalls: z17.number().int().nonnegative(),
5147
- lastContextAt: z17.string().optional(),
5148
- sessions: z17.record(z17.object({
5149
- events: z17.array(z17.enum(events)),
5150
- at: z17.string(),
5151
- verificationStatus: z17.string().optional(),
5152
- reportPath: z17.string().optional()
5230
+ observationSchema = z18.object({
5231
+ version: z18.literal(1),
5232
+ masonVersion: z18.string(),
5233
+ root: z18.string(),
5234
+ revision: z18.string(),
5235
+ host: z18.enum(["codex", "claude"]),
5236
+ contextCalls: z18.number().int().nonnegative(),
5237
+ lastContextAt: z18.string().optional(),
5238
+ sessions: z18.record(z18.object({
5239
+ events: z18.array(z18.enum(events)),
5240
+ at: z18.string(),
5241
+ verificationStatus: z18.string().optional(),
5242
+ reportPath: z18.string().optional()
5153
5243
  }))
5154
5244
  });
5155
5245
  }
@@ -5289,6 +5379,8 @@ async function setupProject(dir, options = {}) {
5289
5379
  const setupBefore = await readText(ws.root, SETUP_PATH);
5290
5380
  const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after)).mcp);
5291
5381
  const desiredHooks = hookConfig(host, hookCommand(host)).hooks;
5382
+ const configuredHook = desiredHooks.SessionStart[0].hooks[0].command;
5383
+ const ownership = await ownershipEdit(ws.root, [...instructions, mcp, hooks[0]], { [hooks[0].path]: configuredHook });
5292
5384
  const fingerprint = hash({
5293
5385
  command: "mason",
5294
5386
  mcp: mcpFingerprint,
@@ -5315,8 +5407,10 @@ async function setupProject(dir, options = {}) {
5315
5407
  revision
5316
5408
  });
5317
5409
  const changedFiles = [];
5410
+ if (await applyEdit(ws.root, ownership)) changedFiles.push(ownership.path);
5318
5411
  for (const edit of edits) if (await applyEdit(ws.root, edit)) changedFiles.push(edit.path);
5319
5412
  if (await applyEdit(ws.root, { path: SETUP_PATH, before: setupBefore, after: JSON.stringify(setup, null, 2) + "\n" })) changedFiles.push(SETUP_PATH);
5413
+ if (await applyEdit(ws.root, await completedHookOwnership(ws.root, hooks[0].path, configuredHook)) && !changedFiles.includes(ownership.path)) changedFiles.push(ownership.path);
5320
5414
  const checked = await automate(ws.root, { event: "turn_start" });
5321
5415
  const configured = await inspectHostConfig(ws.root, host, void 0);
5322
5416
  await writeStoreJson(ws.root, receiptPath, {
@@ -5375,6 +5469,7 @@ var init_setup = __esm({
5375
5469
  init_adapters();
5376
5470
  init_model();
5377
5471
  init_status();
5472
+ init_ownership();
5378
5473
  }
5379
5474
  });
5380
5475
 
@@ -6255,7 +6350,7 @@ var init_sync = __esm({
6255
6350
  init_provenance();
6256
6351
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6257
6352
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6258
- import { z as z18 } from "zod";
6353
+ import { z as z19 } from "zod";
6259
6354
 
6260
6355
  // src/mcp/tools.ts
6261
6356
  init_repair();
@@ -8076,7 +8171,7 @@ function createMcpServer() {
8076
8171
  const server = new McpServer(
8077
8172
  {
8078
8173
  name: "mason",
8079
- version: "0.15.0"
8174
+ version: "0.16.0"
8080
8175
  },
8081
8176
  {
8082
8177
  instructions: '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.'
@@ -8086,11 +8181,11 @@ function createMcpServer() {
8086
8181
  "mason_init",
8087
8182
  "Inspect this project now: returns documentation audit findings, committed-diff review findings, decision/map status, and a quickstart playbook. Quickstart and map modes are read-only and deterministic. Explicit mode: setup configures MCP, instructions and lifecycle hooks to use mason on PATH while retaining original audit evidence; use it only when the user requests setup. Optional host selects codex or claude. Optional base selects the review comparison; evidence imports CI manifests with check outcomes, commit freshness, and links to changed files and accepted decisions. mode: map returns the full Map-Reduce build workflow. Repeat calls refresh findings even after setup.",
8088
8183
  {
8089
- dir: z18.string().describe("Absolute path to the project root directory"),
8090
- mode: z18.enum(["quickstart", "map", "setup"]).optional().default("quickstart").describe("Quickstart inspects without edits; map requests an architecture build; setup installs the shared onboarding flow."),
8091
- host: z18.enum(["codex", "claude"]).optional().describe("Assistant to configure in setup mode; inferred only when unambiguous."),
8092
- base: z18.string().optional().describe("Git ref for committed-diff review. Defaults to the first available main branch ref."),
8093
- evidence: z18.array(z18.string()).max(10).optional().describe("Repository-local CI evidence manifests to include in the review. Imports Vitest JSON and SARIF without executing check commands.")
8184
+ dir: z19.string().describe("Absolute path to the project root directory"),
8185
+ mode: z19.enum(["quickstart", "map", "setup"]).optional().default("quickstart").describe("Quickstart inspects without edits; map requests an architecture build; setup installs the shared onboarding flow."),
8186
+ host: z19.enum(["codex", "claude"]).optional().describe("Assistant to configure in setup mode; inferred only when unambiguous."),
8187
+ base: z19.string().optional().describe("Git ref for committed-diff review. Defaults to the first available main branch ref."),
8188
+ evidence: z19.array(z19.string()).max(10).optional().describe("Repository-local CI evidence manifests to include in the review. Imports Vitest JSON and SARIF without executing check commands.")
8094
8189
  },
8095
8190
  async ({ dir, mode, host, base, evidence }) => {
8096
8191
  const result = await masonInit(dir, { mode, host, base, evidence });
@@ -8101,8 +8196,8 @@ function createMcpServer() {
8101
8196
  "mason_automation",
8102
8197
  "Inspect installed automation and observed host events, or resume/check retained documentation repair evidence across sessions. status is read-only; check saves local baselines and verification reports without editing source or approving advisories. Returns concise results with a full report path. Works without a map.",
8103
8198
  {
8104
- dir: z18.string().describe("Absolute path to the project directory"),
8105
- action: z18.enum(["status", "check"]).describe("Inspect configuration and receipts, or capture/resume and verify original audit evidence.")
8199
+ dir: z19.string().describe("Absolute path to the project directory"),
8200
+ action: z19.enum(["status", "check"]).describe("Inspect configuration and receipts, or capture/resume and verify original audit evidence.")
8106
8201
  },
8107
8202
  async ({ dir, action }) => ({ content: [{ type: "text", text: await masonAutomation(dir, action) }] })
8108
8203
  );
@@ -8110,10 +8205,10 @@ function createMcpServer() {
8110
8205
  "mason_repair",
8111
8206
  "Track documentation repairs against original audit evidence. prepare saves a local baseline and returns a scoped work order; verify reads that baseline and reports resolved, unresolved, review-required, unverified, and new findings. Suppressed advisories remain unresolved. Does not edit documentation or approve decisions. No map required.",
8112
8207
  {
8113
- dir: z18.string().describe("Absolute path to the project root directory"),
8114
- action: z18.enum(["prepare", "verify"]),
8115
- baselinePath: z18.string().optional().describe("Original baseline path returned by prepare; required for verify."),
8116
- checks: z18.array(z18.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"])).min(1).optional().describe("Optional audit check subset for prepare. Verification always uses the original checks.")
8208
+ dir: z19.string().describe("Absolute path to the project root directory"),
8209
+ action: z19.enum(["prepare", "verify"]),
8210
+ baselinePath: z19.string().optional().describe("Original baseline path returned by prepare; required for verify."),
8211
+ checks: z19.array(z19.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"])).min(1).optional().describe("Optional audit check subset for prepare. Verification always uses the original checks.")
8117
8212
  },
8118
8213
  async ({ dir, action, baselinePath, checks }) => {
8119
8214
  const result = await masonRepair(dir, { action, baselinePath, checks });
@@ -8124,8 +8219,8 @@ function createMcpServer() {
8124
8219
  "mason_complete_init",
8125
8220
  "Record assistant instruction setup locally in ignored .mason/local/project.json, with feature settings in shared .mason/config.json. Other tools work without this marker. Repeated calls preserve the original setup time and existing settings; pass confluenceConfigured only to change that setting.",
8126
8221
  {
8127
- dir: z18.string().describe("Absolute path to the project root directory"),
8128
- confluenceConfigured: z18.boolean().optional().describe("Set the Confluence setup status; omit to preserve the existing value")
8222
+ dir: z19.string().describe("Absolute path to the project root directory"),
8223
+ confluenceConfigured: z19.boolean().optional().describe("Set the Confluence setup status; omit to preserve the existing value")
8129
8224
  },
8130
8225
  async ({ dir, confluenceConfigured }) => {
8131
8226
  const result = await masonCompleteInit(dir, { confluenceConfigured });
@@ -8136,11 +8231,11 @@ function createMcpServer() {
8136
8231
  "mason_set_confluence",
8137
8232
  "Configure Confluence credentials. Two-step flow: (1) call without `spaceKey` to validate the credentials and receive a list of available spaces \u2014 relay them to the user. (2) call again with the same `baseUrl`/`email`/`apiToken` plus the chosen `spaceKey` to persist. Credentials are stored in `~/.mason/config.json`. Warn the user that the API token will be visible in chat history before they paste it.",
8138
8233
  {
8139
- baseUrl: z18.string().describe("Confluence base URL. Accepts `acme`, `acme.atlassian.net`, or `https://acme.atlassian.net` (normalized automatically)."),
8140
- email: z18.string().describe("User's Atlassian account email"),
8141
- apiToken: z18.string().describe("API token from id.atlassian.com/manage-profile/security/api-tokens"),
8142
- spaceKey: z18.string().optional().describe("Confluence space key. Omit on the first call to list available spaces."),
8143
- parentPageId: z18.string().optional().describe("Optional parent page ID under which Mason's index page is created")
8234
+ baseUrl: z19.string().describe("Confluence base URL. Accepts `acme`, `acme.atlassian.net`, or `https://acme.atlassian.net` (normalized automatically)."),
8235
+ email: z19.string().describe("User's Atlassian account email"),
8236
+ apiToken: z19.string().describe("API token from id.atlassian.com/manage-profile/security/api-tokens"),
8237
+ spaceKey: z19.string().optional().describe("Confluence space key. Omit on the first call to list available spaces."),
8238
+ parentPageId: z19.string().optional().describe("Optional parent page ID under which Mason's index page is created")
8144
8239
  },
8145
8240
  async ({ baseUrl, email, apiToken, spaceKey, parentPageId }) => {
8146
8241
  const result = await masonSetConfluence({
@@ -8157,7 +8252,7 @@ function createMcpServer() {
8157
8252
  "full_analysis",
8158
8253
  "One-shot orientation for a project WITHOUT a concept map (get_snapshot returned exists:false). Returns git history stats, project structure with file counts, curated code sample previews (~60 lines each), and test-to-source mapping. On a mapped project, prefer get_snapshot \u2014 it is cheaper and answers feature/architecture questions directly.",
8159
8254
  {
8160
- dir: z18.string().describe("Absolute path to the project root directory")
8255
+ dir: z19.string().describe("Absolute path to the project root directory")
8161
8256
  },
8162
8257
  async ({ dir }) => {
8163
8258
  const result = await fullAnalysis(dir);
@@ -8170,7 +8265,7 @@ function createMcpServer() {
8170
8265
  "analyze_project",
8171
8266
  "Run git history analysis on a codebase. Returns commit convention patterns, stale directories, and frequently changed files. These are aggregate stats across hundreds of commits that would be expensive to compute manually.",
8172
8267
  {
8173
- dir: z18.string().describe("Absolute path to the project root directory")
8268
+ dir: z19.string().describe("Absolute path to the project root directory")
8174
8269
  },
8175
8270
  async ({ dir }) => {
8176
8271
  const result = await analyzeProject(dir);
@@ -8183,8 +8278,8 @@ function createMcpServer() {
8183
8278
  "get_code_samples",
8184
8279
  "Get previews (first ~60 lines) of representative source files from the codebase. Includes entry points, config files, hot files (frequently changed), test examples, and one file per directory for breadth. Read files natively for full content.",
8185
8280
  {
8186
- dir: z18.string().describe("Absolute path to the project root directory"),
8187
- count: z18.number().optional().default(15).describe("Maximum number of files to sample (default: 15)")
8281
+ dir: z19.string().describe("Absolute path to the project root directory"),
8282
+ count: z19.number().optional().default(15).describe("Maximum number of files to sample (default: 15)")
8188
8283
  },
8189
8284
  async ({ dir, count: count3 }) => {
8190
8285
  const result = await getCodeSamples(dir, count3);
@@ -8197,7 +8292,7 @@ function createMcpServer() {
8197
8292
  "get_snapshot",
8198
8293
  "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.",
8199
8294
  {
8200
- dir: z18.string().describe("Absolute path to the project root directory")
8295
+ dir: z19.string().describe("Absolute path to the project root directory")
8201
8296
  },
8202
8297
  async ({ dir }) => {
8203
8298
  const result = await getSnapshot(dir);
@@ -8210,9 +8305,9 @@ function createMcpServer() {
8210
8305
  "get_context",
8211
8306
  "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.",
8212
8307
  {
8213
- dir: z18.string().describe("Absolute path to the project root directory"),
8214
- task: z18.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"),
8215
- files: z18.array(z18.string()).optional().describe("Optional file paths already known to be involved (e.g. from a diff or stack trace). Entries containing them are boosted above pure text matches.")
8308
+ dir: z19.string().describe("Absolute path to the project root directory"),
8309
+ task: z19.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"),
8310
+ files: z19.array(z19.string()).optional().describe("Optional file paths already known to be involved (e.g. from a diff or stack trace). Entries containing them are boosted above pure text matches.")
8216
8311
  },
8217
8312
  async ({ dir, task, files }) => {
8218
8313
  const result = await getContext(dir, task, files);
@@ -8228,10 +8323,10 @@ function createMcpServer() {
8228
8323
  "generate_snapshot_batch",
8229
8324
  "Map step of the concept-map build. Returns one batch of source files (skeletons of every file in the batch plus a few deeper-read bodies for grounding), along with a system prompt instructing you to derive features and flows for ONLY this batch. Call repeatedly with the returned `nextOffset` until it is null, calling `save_partial_snapshot` between each call. Use product-natural feature names so partials merge cleanly in the reduce step.",
8230
8325
  {
8231
- dir: z18.string().describe("Absolute path to the project root directory"),
8232
- offset: z18.number().int().optional().describe("0-indexed file offset to start the batch at. Omit on the first call; pass the `nextOffset` from the previous response for subsequent calls."),
8233
- batchSize: z18.number().int().optional().describe("Files per batch. Defaults to 50."),
8234
- files: z18.array(z18.string()).optional().describe("Scope the batch walk to this explicit file list \u2014 e.g. the drift set from mason_check_drift (changedFiles + unmappedFiles). Pass the SAME list on every batch call of one refresh run. Triggers refresh mode: reduce_snapshot will merge the partials into the existing map instead of rebuilding it.")
8326
+ dir: z19.string().describe("Absolute path to the project root directory"),
8327
+ offset: z19.number().int().optional().describe("0-indexed file offset to start the batch at. Omit on the first call; pass the `nextOffset` from the previous response for subsequent calls."),
8328
+ batchSize: z19.number().int().optional().describe("Files per batch. Defaults to 50."),
8329
+ files: z19.array(z19.string()).optional().describe("Scope the batch walk to this explicit file list \u2014 e.g. the drift set from mason_check_drift (changedFiles + unmappedFiles). Pass the SAME list on every batch call of one refresh run. Triggers refresh mode: reduce_snapshot will merge the partials into the existing map instead of rebuilding it.")
8235
8330
  },
8236
8331
  async ({ dir, offset, batchSize, files }) => {
8237
8332
  const result = await generateSnapshotBatch(dir, offset, batchSize, files);
@@ -8244,23 +8339,23 @@ function createMcpServer() {
8244
8339
  "save_partial_snapshot",
8245
8340
  "Persist the partial concept map you derived for one batch. Call this once per batch, with the `batchId` from the `generate_snapshot_batch` response. Partials accumulate in `.mason/partial-snapshots/` and are merged in the reduce step.",
8246
8341
  {
8247
- dir: z18.string().describe("Absolute path to the project root directory"),
8248
- batchId: z18.string().describe("The `batchId` returned by `generate_snapshot_batch`."),
8249
- offset: z18.number().int().describe("The `offset` returned by `generate_snapshot_batch`. Used to order partials in the reduce step."),
8250
- features: z18.record(
8251
- z18.object({
8252
- description: z18.string(),
8253
- files: z18.array(z18.string()),
8254
- tests: z18.array(z18.string()).optional(),
8255
- type: z18.enum(["capability", "infrastructure"]).optional().describe(
8342
+ dir: z19.string().describe("Absolute path to the project root directory"),
8343
+ batchId: z19.string().describe("The `batchId` returned by `generate_snapshot_batch`."),
8344
+ offset: z19.number().int().describe("The `offset` returned by `generate_snapshot_batch`. Used to order partials in the reduce step."),
8345
+ features: z19.record(
8346
+ z19.object({
8347
+ description: z19.string(),
8348
+ files: z19.array(z19.string()),
8349
+ tests: z19.array(z19.string()).optional(),
8350
+ type: z19.enum(["capability", "infrastructure"]).optional().describe(
8256
8351
  '"capability" (user-facing functionality) or "infrastructure" (internal plumbing with no end user \u2014 DI/service wiring, config, logging, adapters). Defaults to "capability".'
8257
8352
  )
8258
8353
  })
8259
8354
  ).describe("Partial features for this batch only \u2014 files outside the batch will be added by other partials."),
8260
- flows: z18.record(
8261
- z18.object({
8262
- description: z18.string(),
8263
- chain: z18.array(z18.string())
8355
+ flows: z19.record(
8356
+ z19.object({
8357
+ description: z19.string(),
8358
+ chain: z19.array(z19.string())
8264
8359
  })
8265
8360
  ).describe("Partial flows whose entire chain is in this batch. Cross-batch flows are reconstructed in reduce.")
8266
8361
  },
@@ -8275,7 +8370,7 @@ function createMcpServer() {
8275
8370
  "reduce_snapshot",
8276
8371
  "Reduce step of the concept-map build. Returns every partial snapshot plus a system prompt asking you to merge them into one coherent project-wide map. Resolve platform variants into single product features, dedupe near-duplicates, and ensure no file is dropped. After producing the unified map, call `save_snapshot` to persist it (this also clears the partials).",
8277
8372
  {
8278
- dir: z18.string().describe("Absolute path to the project root directory")
8373
+ dir: z19.string().describe("Absolute path to the project root directory")
8279
8374
  },
8280
8375
  async ({ dir }) => {
8281
8376
  const result = await reduceSnapshot(dir);
@@ -8288,25 +8383,25 @@ function createMcpServer() {
8288
8383
  "save_snapshot",
8289
8384
  "Save a concept-to-files map as a persistent project snapshot. Maps feature names and data flows to the files that implement them. Persists across conversations \u2014 future sessions can call get_snapshot to instantly find relevant files. No API key needed \u2014 you are the LLM generating the map.",
8290
8385
  {
8291
- dir: z18.string().describe("Absolute path to the project root directory"),
8292
- features: z18.record(
8293
- z18.object({
8294
- description: z18.string().describe("One-line description of the feature"),
8295
- files: z18.array(z18.string()).describe("File paths that implement this feature"),
8296
- tests: z18.array(z18.string()).optional().describe("Test file paths for this feature"),
8297
- type: z18.enum(["capability", "infrastructure"]).optional().describe(
8386
+ dir: z19.string().describe("Absolute path to the project root directory"),
8387
+ features: z19.record(
8388
+ z19.object({
8389
+ description: z19.string().describe("One-line description of the feature"),
8390
+ files: z19.array(z19.string()).describe("File paths that implement this feature"),
8391
+ tests: z19.array(z19.string()).optional().describe("Test file paths for this feature"),
8392
+ type: z19.enum(["capability", "infrastructure"]).optional().describe(
8298
8393
  'Classification: "capability" for user-facing functionality, "infrastructure" for internal plumbing with no end user (DI/service wiring, config, logging, adapters). Capabilities are published to Confluence; infrastructure stays in the AI concept map only. Defaults to "capability".'
8299
8394
  )
8300
8395
  })
8301
8396
  ).describe("Map of feature names to their implementing files"),
8302
- flows: z18.record(
8303
- z18.object({
8304
- description: z18.string().describe("One-line description of the flow"),
8305
- chain: z18.array(z18.string()).describe("Ordered list of file paths showing data/call flow")
8397
+ flows: z19.record(
8398
+ z19.object({
8399
+ description: z19.string().describe("One-line description of the flow"),
8400
+ chain: z19.array(z19.string()).describe("Ordered list of file paths showing data/call flow")
8306
8401
  })
8307
8402
  ).describe("Map of flow names to ordered file chains"),
8308
- removeFeatures: z18.array(z18.string()).optional().describe("Feature names to delete from the existing map \u2014 for features that were renamed or no longer exist. Applied before merging; only meaningful on incremental saves."),
8309
- removeFlows: z18.array(z18.string()).optional().describe("Flow names to delete from the existing map. Applied before merging; only meaningful on incremental saves.")
8403
+ removeFeatures: z19.array(z19.string()).optional().describe("Feature names to delete from the existing map \u2014 for features that were renamed or no longer exist. Applied before merging; only meaningful on incremental saves."),
8404
+ removeFlows: z19.array(z19.string()).optional().describe("Flow names to delete from the existing map. Applied before merging; only meaningful on incremental saves.")
8310
8405
  },
8311
8406
  async ({ dir, features, flows, removeFeatures, removeFlows }) => {
8312
8407
  const result = await saveSnapshotData(
@@ -8325,17 +8420,17 @@ function createMcpServer() {
8325
8420
  "save_decision",
8326
8421
  "Capture or revise a decision proposal with rationale, anchors, optional owner, sources, and a known actor. No setup or map required. Writes a local record and preserves content history. Changes create a pending proposal while the last accepted revision remains operative; unchanged content does not re-verify or refresh it. Use review_decision for authorized acceptance or reaffirmation. A proposal cannot supersede a record with an operative accepted revision; review its replacement and retire the original separately.",
8327
8422
  {
8328
- dir: z18.string().describe("Absolute path to the project root directory"),
8329
- title: z18.string().max(80).describe("Short, specific headline \u2014 becomes the stable record id"),
8330
- body: z18.string().max(1500).describe("The knowledge itself: what was tried/decided, why, and what to avoid. Must contain information NOT derivable by reading the code."),
8331
- category: z18.enum(["decision", "gotcha", "deprecation", "convention"]),
8332
- files: z18.array(z18.string()).optional().describe("Repo-relative files or directory prefixes this applies to. Matching is shared by retrieval, hooks, review, and drift checking; changes flag the decision for re-verification."),
8333
- id: z18.string().optional().describe("Existing id to revise. Changed content becomes a proposal; unchanged content leaves the review and freshness untouched."),
8334
- supersedes: z18.string().optional().describe("Id of an unreviewed record or proposal with no accepted revision to replace. Operative accepted decisions require separate review and retirement."),
8423
+ dir: z19.string().describe("Absolute path to the project root directory"),
8424
+ title: z19.string().max(80).describe("Short, specific headline \u2014 becomes the stable record id"),
8425
+ body: z19.string().max(1500).describe("The knowledge itself: what was tried/decided, why, and what to avoid. Must contain information NOT derivable by reading the code."),
8426
+ category: z19.enum(["decision", "gotcha", "deprecation", "convention"]),
8427
+ files: z19.array(z19.string()).optional().describe("Repo-relative files or directory prefixes this applies to. Matching is shared by retrieval, hooks, review, and drift checking; changes flag the decision for re-verification."),
8428
+ id: z19.string().optional().describe("Existing id to revise. Changed content becomes a proposal; unchanged content leaves the review and freshness untouched."),
8429
+ supersedes: z19.string().optional().describe("Id of an unreviewed record or proposal with no accepted revision to replace. Operative accepted decisions require separate review and retirement."),
8335
8430
  owner: attributionSchema.shape.owner.describe("Responsible person or team, when known. Null clears it. Required for acceptance."),
8336
8431
  sources: attributionSchema.shape.sources.describe("Known PR, issue, incident, discussion, or document references. Omit to preserve; [] clears. At least one is required for acceptance."),
8337
8432
  actor: attributionSchema.shape.actor.describe("Known person or agent recording this revision. Omit if unknown; do not infer from Git identity."),
8338
- force: z18.boolean().optional().describe("Save even when a near-duplicate was detected")
8433
+ force: z19.boolean().optional().describe("Save even when a near-duplicate was detected")
8339
8434
  },
8340
8435
  async ({ dir, title, body, category, files, id, supersedes, force, owner, sources, actor }) => {
8341
8436
  const result = await saveDecision(dir, {
@@ -8357,12 +8452,12 @@ function createMcpServer() {
8357
8452
  "review_decision",
8358
8453
  "Prepare a decision review: returns the full record and history, any operative accepted revision, provenance, changes and previews for both sets of anchors, and a reviewToken. Then record accept, reaffirm, or retire with that token, the authorized reviewer, and a reason. Acceptance replaces the operative revision; retirement withdraws the entire record including its proposal. Acceptance requires owner, source, readable Git HEAD, and committed anchor changes. Changed records or code invalidate the token. Identities and approvals are recorded assertions for normal PR review, not authenticated proof.",
8359
8454
  {
8360
- dir: z18.string().describe("Absolute path to the project root directory"),
8361
- id: z18.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Decision id from get_context or save_decision"),
8362
- action: z18.enum(["prepare", "accept", "reaffirm", "retire"]).optional().default("prepare").describe("Prepare is read-only. Other actions record an explicitly authorized review."),
8363
- reviewer: z18.string().trim().min(1).max(200).optional().describe("Identity of the actual reviewer; required for a verdict. Never invent one."),
8364
- note: z18.string().trim().min(1).max(1500).optional().describe("Review rationale; required for a verdict. Cite evidence for the decision."),
8365
- reviewToken: z18.string().regex(/^[a-f0-9]{64}$/).optional().describe("Token from the prepared review; rejects stale record or code revisions")
8455
+ dir: z19.string().describe("Absolute path to the project root directory"),
8456
+ id: z19.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Decision id from get_context or save_decision"),
8457
+ action: z19.enum(["prepare", "accept", "reaffirm", "retire"]).optional().default("prepare").describe("Prepare is read-only. Other actions record an explicitly authorized review."),
8458
+ reviewer: z19.string().trim().min(1).max(200).optional().describe("Identity of the actual reviewer; required for a verdict. Never invent one."),
8459
+ note: z19.string().trim().min(1).max(1500).optional().describe("Review rationale; required for a verdict. Cite evidence for the decision."),
8460
+ reviewToken: z19.string().regex(/^[a-f0-9]{64}$/).optional().describe("Token from the prepared review; rejects stale record or code revisions")
8366
8461
  },
8367
8462
  async ({ dir, ...input }) => {
8368
8463
  const result = await reviewDecision2(dir, input);
@@ -8373,7 +8468,7 @@ function createMcpServer() {
8373
8468
  "mason_check_drift",
8374
8469
  "Check how far the concept map has drifted from HEAD. Deterministic (git + filesystem, no LLM). Returns which features/flows are stale and the changed files behind them, new source files not yet mapped, ghost files (mapped but deleted), renames, and a `recommendation`: `up-to-date` (nothing to do), `incremental` (update just the stale entries via save_snapshot), or `full-rebuild` (re-run the Map-Reduce build). Call this before trusting the map in a long session, or periodically to keep the map and any synced wikis fresh.",
8375
8470
  {
8376
- dir: z18.string().describe("Absolute path to the project root directory")
8471
+ dir: z19.string().describe("Absolute path to the project root directory")
8377
8472
  },
8378
8473
  async ({ dir }) => {
8379
8474
  const result = await checkDrift(dir);
@@ -8386,8 +8481,8 @@ function createMcpServer() {
8386
8481
  "verify_snapshot",
8387
8482
  "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.",
8388
8483
  {
8389
- dir: z18.string().describe("Absolute path to the project root directory"),
8390
- sample: z18.number().int().optional().describe("Entries to sample (default 5)")
8484
+ dir: z19.string().describe("Absolute path to the project root directory"),
8485
+ sample: z19.number().int().optional().describe("Entries to sample (default 5)")
8391
8486
  },
8392
8487
  async ({ dir, sample }) => {
8393
8488
  const result = await verifySnapshot(dir, sample);
@@ -8398,11 +8493,11 @@ function createMcpServer() {
8398
8493
  "save_verification",
8399
8494
  "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.",
8400
8495
  {
8401
- dir: z18.string().describe("Absolute path to the project root directory"),
8402
- verdicts: z18.record(
8403
- z18.object({
8404
- ok: z18.boolean(),
8405
- note: z18.string().optional().describe("One line on what's wrong \u2014 required when ok is false")
8496
+ dir: z19.string().describe("Absolute path to the project root directory"),
8497
+ verdicts: z19.record(
8498
+ z19.object({
8499
+ ok: z19.boolean(),
8500
+ note: z19.string().optional().describe("One line on what's wrong \u2014 required when ok is false")
8406
8501
  })
8407
8502
  ).describe("Entry name \u2192 verdict, exactly as returned by verify_snapshot")
8408
8503
  },
@@ -8415,8 +8510,8 @@ function createMcpServer() {
8415
8510
  "get_impact",
8416
8511
  "Trace the impact of changing files: historical co-change partners, references, and related tests. Deterministic, read-only, and usable without initialization, saved decisions, or a concept map.",
8417
8512
  {
8418
- dir: z18.string().describe("Absolute path to the project root directory"),
8419
- files: z18.array(z18.string()).describe("File paths or names to analyze (e.g., ['WeatherRepository.kt'] or ['src/services/auth.ts'])")
8513
+ dir: z19.string().describe("Absolute path to the project root directory"),
8514
+ files: z19.array(z19.string()).describe("File paths or names to analyze (e.g., ['WeatherRepository.kt'] or ['src/services/auth.ts'])")
8420
8515
  },
8421
8516
  async ({ dir, files }) => {
8422
8517
  const result = await getImpact(dir, files);
@@ -8429,12 +8524,12 @@ function createMcpServer() {
8429
8524
  "export_to_confluence",
8430
8525
  "Sync the project's concept map to Confluence as product-readable wiki pages: an index page, one page per feature (PM-language descriptions, no file paths), and a changelog page. Mason replaces managed page bodies; manual edits to those bodies are overwritten. Requires `mason_set_confluence` to have been called first.",
8431
8526
  {
8432
- dir: z18.string().describe("Absolute path to the project root directory"),
8433
- spaceKey: z18.string().optional().describe("Override the configured space key"),
8434
- parentPageId: z18.string().optional().describe("Override the configured parent page ID"),
8435
- indexPageTitle: z18.string().optional().describe("Title of the index page (default: 'Mason \u2014 System Map')"),
8436
- changelogPageTitle: z18.string().optional().describe("Title of the changelog page (default: 'Mason \u2014 Changelog')"),
8437
- featurePagePrefix: z18.string().optional().describe("Prefix for each feature page title (default: 'Feature: ')")
8527
+ dir: z19.string().describe("Absolute path to the project root directory"),
8528
+ spaceKey: z19.string().optional().describe("Override the configured space key"),
8529
+ parentPageId: z19.string().optional().describe("Override the configured parent page ID"),
8530
+ indexPageTitle: z19.string().optional().describe("Title of the index page (default: 'Mason \u2014 System Map')"),
8531
+ changelogPageTitle: z19.string().optional().describe("Title of the changelog page (default: 'Mason \u2014 Changelog')"),
8532
+ featurePagePrefix: z19.string().optional().describe("Prefix for each feature page title (default: 'Feature: ')")
8438
8533
  },
8439
8534
  async ({ dir, spaceKey, parentPageId, indexPageTitle, changelogPageTitle, featurePagePrefix }) => {
8440
8535
  const result = await exportToConfluenceTool(dir, {