mason-context 0.19.2 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mason-mcp.js CHANGED
@@ -138,9 +138,9 @@ async function readBoundedFile(file, maxBytes) {
138
138
  async function loadProjectConfig(root) {
139
139
  try {
140
140
  const canonicalRoot = await fs.realpath(root);
141
- const configPath2 = await fs.realpath(path2.join(root, ".mason/config.json"));
142
- if (!isWithinRoot(canonicalRoot, configPath2)) throw new Error("Project configuration resolves outside the repository");
143
- const raw = await readBoundedFile(configPath2, 64 * 1024);
141
+ const configPath3 = await fs.realpath(path2.join(root, ".mason/config.json"));
142
+ if (!isWithinRoot(canonicalRoot, configPath3)) throw new Error("Project configuration resolves outside the repository");
143
+ const raw = await readBoundedFile(configPath3, 64 * 1024);
144
144
  if (raw === null) throw new Error("Project configuration is not a regular file or exceeds 64 KiB");
145
145
  const value = JSON.parse(raw);
146
146
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected a configuration object");
@@ -419,15 +419,15 @@ async function readAuditInput(root, file) {
419
419
  async function auditGlob(root, patterns, options) {
420
420
  if (Array.isArray(patterns) && !patterns.length) return [];
421
421
  const directories = /* @__PURE__ */ new Set();
422
- const readdir2 = ((directory, opts, callback) => {
423
- const relative = path5.relative(root, directory).split(path5.sep).join("/") || ".";
422
+ const readdir2 = ((directory2, opts, callback) => {
423
+ const relative = path5.relative(root, directory2).split(path5.sep).join("/") || ".";
424
424
  directories.add(relative);
425
425
  if (directories.size > MAX_INPUTS) {
426
426
  callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
427
427
  return;
428
428
  }
429
429
  auditInputPath(root, relative).then(() => {
430
- nativeFs.readdir(directory, opts, callback);
430
+ nativeFs.readdir(directory2, opts, callback);
431
431
  }, callback);
432
432
  });
433
433
  const lstat2 = ((file, callback) => {
@@ -557,25 +557,25 @@ function commandClaims(line, lineNo, cwd, shell = false) {
557
557
  if (text2.startsWith("#") || /^(?:echo|printf)\b/.test(text2)) continue;
558
558
  const cd = text2.match(/^cd\s+(.+)$/);
559
559
  if (cd) {
560
- const directory = literal(cd[1]);
561
- cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path6.posix.join(cwd ?? ".", directory);
560
+ const directory2 = literal(cd[1]);
561
+ cwd = directory2 === null || cwd === null || /\s/.test(directory2) && !/^["']/.test(cd[1]) ? null : path6.posix.join(cwd ?? ".", directory2);
562
562
  continue;
563
563
  }
564
564
  for (const match of text2.matchAll(COMMAND)) {
565
565
  const flags = match[2].trim();
566
566
  const selector = flags.match(/^(?:--prefix|--dir|-C)(?:\s+|=)(.+)$/);
567
- let directory = cwd;
567
+ let directory2 = cwd;
568
568
  let scopeUnknown;
569
569
  if (shell && match.index !== 0 || match[3].startsWith("-")) scopeUnknown = "Unsupported shell wrapper or options before the script.";
570
570
  if (selector) {
571
571
  const selected = literal(selector[1]);
572
- directory = selected === null || cwd === null ? null : path6.posix.join(cwd ?? ".", selected);
572
+ directory2 = selected === null || cwd === null ? null : path6.posix.join(cwd ?? ".", selected);
573
573
  } else if (flags) scopeUnknown = "Unsupported package-manager options before run: " + flags;
574
574
  const tail = text2.slice(match.index + match[0].length).split(/\s--(?:\s|$)/)[0];
575
575
  if (/(?:^|\s)(?:--workspace(?:s)?|-w|--filter|-F|--prefix|--dir|-C|--if-present)(?:\s|=|$)/.test(tail)) {
576
576
  scopeUnknown = "Package selection or conditional options after the script need review.";
577
577
  }
578
- if (/[|()]/.test(text2) || /\b(?:pushd|popd|if|for|while)\b/.test(text2) || directory === null) {
578
+ if (/[|()]/.test(text2) || /\b(?:pushd|popd|if|for|while)\b/.test(text2) || directory2 === null) {
579
579
  scopeUnknown = "The command's working directory or shell control flow could not be resolved.";
580
580
  }
581
581
  claims.push({
@@ -583,7 +583,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
583
583
  invocation: match[0],
584
584
  excerpt: match[0],
585
585
  line: lineNo,
586
- ...directory !== void 0 && directory !== null ? { directory } : {},
586
+ ...directory2 !== void 0 && directory2 !== null ? { directory: directory2 } : {},
587
587
  ...scopeUnknown ? { scopeUnknown } : {}
588
588
  });
589
589
  }
@@ -1128,19 +1128,19 @@ var init_docs = __esm({
1128
1128
  // src/audit/scope.ts
1129
1129
  import fs5 from "fs/promises";
1130
1130
  import path8 from "path";
1131
- function scopedPath(directory, value) {
1131
+ function scopedPath(directory2, value) {
1132
1132
  if (path8.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
1133
- const resolved = path8.posix.normalize(path8.posix.join(directory, value));
1133
+ const resolved = path8.posix.normalize(path8.posix.join(directory2, value));
1134
1134
  return resolved === ".." || resolved.startsWith("../") ? null : resolved;
1135
1135
  }
1136
1136
  function pathClaimScope(doc, claim) {
1137
- const directory = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
1138
- const local = scopedPath(directory, claim.path);
1137
+ const directory2 = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
1138
+ const local = scopedPath(directory2, claim.path);
1139
1139
  if (!local) return null;
1140
- const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
1140
+ const candidates = claim.relativeTo === "document" || directory2 === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
1141
1141
  return {
1142
- basis: claim.relativeTo === "document" ? "document-link" : directory === "." ? "repository" : "document",
1143
- directory,
1142
+ basis: claim.relativeTo === "document" ? "document-link" : directory2 === "." ? "repository" : "document",
1143
+ directory: directory2,
1144
1144
  candidates
1145
1145
  };
1146
1146
  }
@@ -1194,24 +1194,19 @@ async function lockDiagnostic(file) {
1194
1194
  }
1195
1195
  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.";
1196
1196
  }
1197
- async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason store") {
1198
- const file = await storePath(root, directory + "/lock", true);
1197
+ async function withStoreLock(root, directory2, run, waitMs = 5e3, label = "Mason store") {
1198
+ const file = await storePath(root, directory2 + "/lock", true);
1199
1199
  const deadline = Date.now() + waitMs;
1200
1200
  let handle;
1201
1201
  while (!handle) {
1202
1202
  try {
1203
1203
  handle = await fs6.open(file, "wx", 384);
1204
- try {
1205
- await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
1206
- } catch (error) {
1207
- await handle.close().catch(() => {
1208
- });
1209
- handle = void 0;
1210
- await fs6.rm(file, { force: true }).catch(() => {
1211
- });
1212
- throw error;
1213
- }
1214
1204
  } catch (error) {
1205
+ if (process.platform === "win32" && error.code === "EPERM") {
1206
+ if (Date.now() >= deadline) throw error;
1207
+ await new Promise((resolve2) => setTimeout(resolve2, 40));
1208
+ continue;
1209
+ }
1215
1210
  if (error.code !== "EEXIST") throw error;
1216
1211
  try {
1217
1212
  const owner = JSON.parse(await fs6.readFile(file, "utf8"));
@@ -1240,6 +1235,17 @@ async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason
1240
1235
  if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
1241
1236
  await new Promise((resolve2) => setTimeout(resolve2, 40));
1242
1237
  }
1238
+ if (handle) {
1239
+ try {
1240
+ await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
1241
+ } catch (error) {
1242
+ await handle.close().catch(() => {
1243
+ });
1244
+ await fs6.rm(file, { force: true }).catch(() => {
1245
+ });
1246
+ throw error;
1247
+ }
1248
+ }
1243
1249
  }
1244
1250
  try {
1245
1251
  return await run();
@@ -2044,19 +2050,19 @@ async function commandInputs(root, docs) {
2044
2050
  const resolutions = [];
2045
2051
  for (const doc of docs) for (const claim of doc.claims.commands) {
2046
2052
  const base = documentScope(doc.path);
2047
- const directory = scopedPath(base, claim.directory ?? ".");
2053
+ const directory2 = scopedPath(base, claim.directory ?? ".");
2048
2054
  const scope = {
2049
2055
  basis: claim.directory === void 0 ? "document" : "explicit",
2050
- directory: directory ?? base,
2056
+ directory: directory2 ?? base,
2051
2057
  candidates: []
2052
2058
  };
2053
2059
  const resolution = { doc: doc.path, claim, scope, manifest: null, scripts: [], elsewhere: [] };
2054
2060
  resolutions.push(resolution);
2055
- if (claim.scopeUnknown || directory === null) {
2061
+ if (claim.scopeUnknown || directory2 === null) {
2056
2062
  resolution.problem = claim.scopeUnknown ?? "The command's directory leaves the repository.";
2057
2063
  continue;
2058
2064
  }
2059
- let cursor = directory;
2065
+ let cursor = directory2;
2060
2066
  while (true) {
2061
2067
  const manifest = path15.posix.join(cursor, "package.json");
2062
2068
  scope.candidates.push(manifest);
@@ -2072,7 +2078,7 @@ async function commandInputs(root, docs) {
2072
2078
  cursor = path15.posix.dirname(cursor);
2073
2079
  }
2074
2080
  if (!resolution.manifest && !resolution.problem) {
2075
- resolution.problem = `No package.json for the command's scope (${directory}).`;
2081
+ resolution.problem = `No package.json for the command's scope (${directory2}).`;
2076
2082
  }
2077
2083
  if (resolution.problem || resolution.scripts.includes(claim.scriptName) || claim.directory !== void 0) continue;
2078
2084
  inventory ??= await commandManifests(root);
@@ -3266,8 +3272,15 @@ function completionSummary(findings, reportPath) {
3266
3272
  `Mason: ${findings.length} new or worsened finding(s) since the retained baseline.`,
3267
3273
  ...findings.slice(0, MAX_COMPLETION_FINDINGS).map((f) => {
3268
3274
  const source = f.current ?? f.original;
3269
- const detail = source.evidence.kind === "decision-anchor" ? `Changes to ${source.evidence.changedFiles.join(", ")} may affect decision "${source.evidence.title}". Inspect the diff if relevant; acceptance is not required.` : source.message;
3270
- return `[${f.status === "unresolved" ? "issue" : "advisory"}] ${safe(source.anchor.doc)}: ${safe(detail)}`;
3275
+ if (source.evidence.kind === "decision-anchor") {
3276
+ const evidence = source.evidence;
3277
+ return [
3278
+ `[advisory] Decision "${safe(evidence.title)}" may no longer hold: ${evidence.changedFiles.length} anchored file(s) changed.`,
3279
+ `Inspect the decision and diff with review_decision(action: "prepare", id: ${JSON.stringify(evidence.decisionId)}); acceptance is not required.`,
3280
+ `Changed files: ${safe(evidence.changedFiles.join(", "))}`
3281
+ ].join("\n");
3282
+ }
3283
+ return `[${f.status === "unresolved" ? "issue" : "advisory"}] ${safe(source.anchor.doc)}: ${safe(source.message)}`;
3271
3284
  }),
3272
3285
  ...findings.length > MAX_COMPLETION_FINDINGS ? [`${findings.length - MAX_COMPLETION_FINDINGS} more in the report; pending later feedback.`] : [],
3273
3286
  `Evidence: ${reportPath}. Explicit audit: mason_automation(action: "check").`,
@@ -3304,8 +3317,8 @@ function parseState(raw) {
3304
3317
  throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
3305
3318
  }
3306
3319
  }
3307
- function withLock(root, directory, run, waitMs = 5e3) {
3308
- return withStoreLock(root, directory, run, waitMs, "Automation");
3320
+ function withLock(root, directory2, run, waitMs = 5e3) {
3321
+ return withStoreLock(root, directory2, run, waitMs, "Automation");
3309
3322
  }
3310
3323
  var hostSchema, events, stateSchema;
3311
3324
  var init_store = __esm({
@@ -4129,7 +4142,7 @@ var init_evidence = __esm({
4129
4142
  init_dead_command();
4130
4143
  init_storage();
4131
4144
  init_advisory_review();
4132
- engineVersion = true ? "0.19.2" : "development";
4145
+ engineVersion = true ? "0.20.2" : "development";
4133
4146
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
4134
4147
  content = readAuditInput;
4135
4148
  cacheSchema = z9.object({ version: z9.literal(1), entries: z9.record(z9.object({ key: z9.string(), result: checkResultSchema })), digest: z9.string() });
@@ -4182,8 +4195,8 @@ async function effectiveSetup(root) {
4182
4195
  if (inactive && setup) for (const host of inactiveHostsSchema.parse(inactive).hosts) delete setup.hosts[host];
4183
4196
  return { root: inheritedRoot, setup };
4184
4197
  }
4185
- async function loadSetupReceipt(root, directory, host) {
4186
- const raw = await readStoreJson(root, directory + "/setup-" + host + ".json");
4198
+ async function loadSetupReceipt(root, directory2, host) {
4199
+ const raw = await readStoreJson(root, directory2 + "/setup-" + host + ".json");
4187
4200
  if (raw === null) return null;
4188
4201
  const receipt = receiptSchema.parse(raw);
4189
4202
  if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
@@ -4255,7 +4268,7 @@ var init_launcher = __esm({
4255
4268
  "use strict";
4256
4269
  init_evidence();
4257
4270
  init_model();
4258
- executingVersion = () => true ? "0.19.2" : "development";
4271
+ executingVersion = () => true ? "0.20.2" : "development";
4259
4272
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
4260
4273
  hookCommand = (host) => `mason --setup-host ${host} auto`;
4261
4274
  }
@@ -4439,6 +4452,146 @@ var init_files2 = __esm({
4439
4452
  }
4440
4453
  });
4441
4454
 
4455
+ // src/automation/usefulness.ts
4456
+ import fs19 from "fs/promises";
4457
+ import { z as z13 } from "zod";
4458
+ async function enabled(root) {
4459
+ const raw = await readStoreJson(root, configPath);
4460
+ return raw !== null && configSchema.parse(raw).enabled;
4461
+ }
4462
+ async function configureUsefulness(dir, value, options = {}) {
4463
+ const ws = await workspace(dir);
4464
+ const configured = await withStoreLock(ws.root, directory, async () => {
4465
+ if (options.onlyIfUnset) {
4466
+ const existing = await readStoreJson(ws.root, configPath);
4467
+ if (existing !== null) return configSchema.parse(existing).enabled;
4468
+ }
4469
+ const ignore = await storePath(ws.root, directory + "/.gitignore", true);
4470
+ try {
4471
+ await fs19.writeFile(ignore, "*\n", { flag: "wx", mode: 384 });
4472
+ } catch (error) {
4473
+ if (error.code !== "EEXIST") throw error;
4474
+ }
4475
+ await writeStoreJson(ws.root, configPath, { version: 1, enabled: value });
4476
+ return value;
4477
+ });
4478
+ return { enabled: configured, storage: directory, note: "Local only. Disabling retains prior observations." };
4479
+ }
4480
+ async function observeUsefulness(root, event, report, agent, completion, elapsedMs, observedAt = (/* @__PURE__ */ new Date()).toISOString()) {
4481
+ if (!event.host || !event.sessionId || !await enabled(root)) return;
4482
+ const ws = await workspace(root);
4483
+ await withStoreLock(ws.root, directory, async () => {
4484
+ if (!await enabled(root)) return;
4485
+ const raw = await readStoreJson(root, statePath(ws));
4486
+ const state = raw === null ? empty() : stateSchema2.parse(raw);
4487
+ const now = observedAt;
4488
+ const key = hash([event.host, event.sessionId]);
4489
+ const session = state.sessions[key] ??= {
4490
+ host: event.host,
4491
+ updatedAt: now,
4492
+ checks: 0,
4493
+ checkMs: 0,
4494
+ findings: {},
4495
+ decisions: [],
4496
+ retrievalObserved: false,
4497
+ receiptIds: [],
4498
+ reportPath: report.reportPath,
4499
+ truncated: false
4500
+ };
4501
+ const newer = now >= session.updatedAt;
4502
+ if (newer) {
4503
+ session.updatedAt = now;
4504
+ session.reportPath = report.reportPath;
4505
+ }
4506
+ session.checks++;
4507
+ session.checkMs += Math.max(0, elapsedMs);
4508
+ if (newer) {
4509
+ if (event.event === "task_end") session.endedAt = now;
4510
+ else delete session.endedAt;
4511
+ }
4512
+ let latestFindings = [];
4513
+ if (!newer && [...agent.slice(0, 4), ...completion.slice(0, 4)].some((f) => !session.findings[f.id])) {
4514
+ try {
4515
+ if (!session.reportPath.startsWith(ws.directory + "/checks/")) throw new Error("Unexpected report path");
4516
+ latestFindings = z13.object({ findings: z13.array(z13.object({ id: z13.string(), status: findingSchema2.shape.status })) }).parse(await readStoreJson(root, session.reportPath)).findings;
4517
+ } catch {
4518
+ }
4519
+ }
4520
+ for (const [channel, findings] of [["agent", agent], ["completion", completion]]) {
4521
+ for (const finding of findings.slice(0, 4)) {
4522
+ if (!session.findings[finding.id] && Object.keys(session.findings).length >= 200) {
4523
+ session.truncated = true;
4524
+ continue;
4525
+ }
4526
+ const saved = session.findings[finding.id] ??= {
4527
+ type: finding.original.type,
4528
+ channels: [],
4529
+ status: newer ? finding.status : latestFindings.find((f) => f.id === finding.id)?.status ?? "unknown",
4530
+ deliveredAt: now,
4531
+ observedAt: newer ? now : session.updatedAt
4532
+ };
4533
+ if (!saved.channels.includes(channel)) saved.channels.push(channel);
4534
+ }
4535
+ }
4536
+ for (const [id, saved] of Object.entries(session.findings)) {
4537
+ if (now < saved.observedAt) continue;
4538
+ const current = report.findings.find((f) => f.id === id);
4539
+ saved.status = current?.status ?? "unknown";
4540
+ saved.observedAt = now;
4541
+ }
4542
+ if (event.retrievedDecisionIds !== void 0 && event.toolId) {
4543
+ const receipt = hash([event.toolId]);
4544
+ if (!session.receiptIds.includes(receipt)) {
4545
+ session.retrievalObserved = true;
4546
+ const decisions = [.../* @__PURE__ */ new Set([...session.decisions, ...event.retrievedDecisionIds.map((id) => hash([id]))])];
4547
+ if (decisions.length > 200) session.truncated = true;
4548
+ session.decisions = decisions.slice(0, 200);
4549
+ session.receiptIds = [...session.receiptIds, receipt].slice(-200);
4550
+ }
4551
+ }
4552
+ const keys = Object.keys(state.sessions).sort((a, b) => state.sessions[b].updatedAt.localeCompare(state.sessions[a].updatedAt));
4553
+ for (const key2 of keys.slice(50)) delete state.sessions[key2];
4554
+ await writeStoreJson(root, statePath(ws), state);
4555
+ });
4556
+ }
4557
+ var directory, configPath, configSchema, usefulnessRating, findingSchema2, sessionSchema, stateSchema2, empty, statePath;
4558
+ var init_usefulness = __esm({
4559
+ "src/automation/usefulness.ts"() {
4560
+ "use strict";
4561
+ init_evidence();
4562
+ init_storage();
4563
+ init_store_lock();
4564
+ directory = ".mason/local/usefulness";
4565
+ configPath = directory + "/config.json";
4566
+ configSchema = z13.object({ version: z13.literal(1), enabled: z13.boolean() });
4567
+ usefulnessRating = z13.enum(["helpful", "already-knew", "irrelevant", "deferred"]);
4568
+ findingSchema2 = z13.object({
4569
+ type: z13.string(),
4570
+ channels: z13.array(z13.enum(["agent", "completion"])),
4571
+ status: z13.enum(["resolved", "unresolved", "review-required", "unverified", "unknown"]),
4572
+ deliveredAt: z13.string(),
4573
+ observedAt: z13.string(),
4574
+ rating: usefulnessRating.optional()
4575
+ });
4576
+ sessionSchema = z13.object({
4577
+ host: z13.string(),
4578
+ updatedAt: z13.string(),
4579
+ endedAt: z13.string().optional(),
4580
+ checks: z13.number(),
4581
+ checkMs: z13.number(),
4582
+ findings: z13.record(findingSchema2),
4583
+ decisions: z13.array(z13.string()),
4584
+ retrievalObserved: z13.boolean(),
4585
+ receiptIds: z13.array(z13.string()),
4586
+ reportPath: z13.string(),
4587
+ truncated: z13.boolean()
4588
+ });
4589
+ stateSchema2 = z13.object({ version: z13.literal(1), sessions: z13.record(sessionSchema) });
4590
+ empty = () => ({ version: 1, sessions: {} });
4591
+ statePath = (ws) => `${directory}/${hash([ws.root, ws.gitDir, ws.branch]).slice(0, 24)}.json`;
4592
+ }
4593
+ });
4594
+
4442
4595
  // src/automation/execution.ts
4443
4596
  var execution_exports = {};
4444
4597
  __export(execution_exports, {
@@ -4450,9 +4603,9 @@ __export(execution_exports, {
4450
4603
  recordExecution: () => recordExecution
4451
4604
  });
4452
4605
  import os2 from "os";
4453
- import fs19 from "fs/promises";
4606
+ import fs20 from "fs/promises";
4454
4607
  import { randomUUID as randomUUID4 } from "crypto";
4455
- import { z as z13 } from "zod";
4608
+ import { z as z14 } from "zod";
4456
4609
  function parseExecution(raw) {
4457
4610
  try {
4458
4611
  return executionSchema.parse(raw);
@@ -4470,15 +4623,15 @@ function automationFailure(error) {
4470
4623
  codes.add(String(cause.code));
4471
4624
  cause = cause.cause;
4472
4625
  }
4473
- const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(message2) ? "inputs-changed" : /Automation is busy/.test(message2) ? "busy" : /not a git repository|unknown revision|bad revision|different history|unreachable/.test(message2) ? "history-unavailable" : error instanceof z13.ZodError || error instanceof SyntaxError || /Hook input|Expected one command|Unknown automation command|--host/.test(message2) ? "invalid-input" : /store|baseline|modified|symbolic link|Symlink|automation state|state belongs/.test(message2) ? "invalid-evidence" : [...codes].some((c) => /^E[A-Z]+$/.test(c)) ? "io-error" : "internal";
4626
+ const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(message2) ? "inputs-changed" : /Automation is busy/.test(message2) ? "busy" : /not a git repository|unknown revision|bad revision|different history|unreachable/.test(message2) ? "history-unavailable" : error instanceof z14.ZodError || error instanceof SyntaxError || /Hook input|Expected one command|Unknown automation command|--host/.test(message2) ? "invalid-input" : /store|baseline|modified|symbolic link|Symlink|automation state|state belongs/.test(message2) ? "invalid-evidence" : [...codes].some((c) => /^E[A-Z]+$/.test(c)) ? "io-error" : "internal";
4474
4627
  return { code, message: message2, retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code), receiptRecorded: false };
4475
4628
  }
4476
4629
  function failureMessage(error) {
4477
4630
  const failure = automationFailure(error);
4478
4631
  return `Mason automation unavailable [${failure.code}]; evidence capture/verification was not established. ${failure.message}` + (failure.receiptRecorded ? "" : " No durable failure receipt was recorded.");
4479
4632
  }
4480
- async function failureNotifications(root, directory) {
4481
- const raw = await readStoreJson(root, directory + "/notifications.json");
4633
+ async function failureNotifications(root, directory2) {
4634
+ const raw = await readStoreJson(root, directory2 + "/notifications.json");
4482
4635
  return raw === null ? { version: 1, sessions: {} } : notificationsSchema.parse(raw);
4483
4636
  }
4484
4637
  async function hookFailureMessage(dir, host, sessionId, error) {
@@ -4508,12 +4661,12 @@ async function hookFailureMessage(dir, host, sessionId, error) {
4508
4661
  return failureMessage(error);
4509
4662
  }
4510
4663
  }
4511
- async function refreshRunning(root, directory, attempts) {
4664
+ async function refreshRunning(root, directory2, attempts) {
4512
4665
  for (const attempt of attempts) {
4513
4666
  if (attempt.status !== "running") continue;
4514
4667
  let alive = false;
4515
4668
  try {
4516
- const owner = await readStoreJson(root, attempt.lease ? leasePath(directory, attempt.id) : directory + "/lock");
4669
+ const owner = await readStoreJson(root, attempt.lease ? leasePath(directory2, attempt.id) : directory2 + "/lock");
4517
4670
  if (attempt.host === os2.hostname() && owner?.pid === attempt.pid && owner.host === attempt.host) {
4518
4671
  try {
4519
4672
  process.kill(attempt.pid, 0);
@@ -4527,12 +4680,12 @@ async function refreshRunning(root, directory, attempts) {
4527
4680
  if (!alive) attempt.status = "unknown";
4528
4681
  }
4529
4682
  }
4530
- async function editExecution(root, directory, edit) {
4531
- return withLock(root, directory + "/execution-lock", async () => {
4532
- const file = directory + "/execution.json";
4683
+ async function editExecution(root, directory2, edit) {
4684
+ return withLock(root, directory2 + "/execution-lock", async () => {
4685
+ const file = directory2 + "/execution.json";
4533
4686
  const raw = await readStoreJson(root, file);
4534
4687
  const log = raw === null ? { version: 1, attempts: [], discardedAttempts: 0 } : parseExecution(raw);
4535
- await refreshRunning(root, directory, log.attempts);
4688
+ await refreshRunning(root, directory2, log.attempts);
4536
4689
  edit(log);
4537
4690
  const finished = log.attempts.filter((a) => a.status !== "running");
4538
4691
  const discard = new Set(finished.slice(0, Math.max(0, finished.length - 32)).map((a) => a.id));
@@ -4541,7 +4694,7 @@ async function editExecution(root, directory, edit) {
4541
4694
  await writeStoreJson(root, file, log);
4542
4695
  });
4543
4696
  }
4544
- async function recordExecution(root, directory, event, run) {
4697
+ async function recordExecution(root, directory2, event, run) {
4545
4698
  const started = performance.now();
4546
4699
  const attempt = {
4547
4700
  id: randomUUID4(),
@@ -4552,15 +4705,15 @@ async function recordExecution(root, directory, event, run) {
4552
4705
  status: "running",
4553
4706
  lease: true
4554
4707
  };
4555
- const owner = leasePath(directory, attempt.id);
4556
- const finish = async (changes) => editExecution(root, directory, (log) => {
4708
+ const owner = leasePath(directory2, attempt.id);
4709
+ const finish = async (changes) => editExecution(root, directory2, (log) => {
4557
4710
  const current = log.attempts.find((a) => a.id === attempt.id);
4558
4711
  if (!current) throw new Error("Automation execution receipt disappeared; completion could not be recorded.");
4559
4712
  Object.assign(current, changes, { finishedAt: (/* @__PURE__ */ new Date()).toISOString(), durationMs: performance.now() - started });
4560
4713
  });
4561
4714
  await writeStoreJson(root, owner, { pid: attempt.pid, host: attempt.host });
4562
4715
  try {
4563
- await editExecution(root, directory, (log) => {
4716
+ await editExecution(root, directory2, (log) => {
4564
4717
  if (log.attempts.filter((a) => a.status === "running").length >= 128) throw new Error("Too many active automation executions; existing receipts were retained.");
4565
4718
  log.attempts.push(attempt);
4566
4719
  });
@@ -4578,15 +4731,15 @@ async function recordExecution(root, directory, event, run) {
4578
4731
  throw Object.assign(new Error(failure.message, { cause: error }), { failure });
4579
4732
  }
4580
4733
  } finally {
4581
- await fs19.rm(await storePath(root, owner), { force: true }).catch(() => {
4734
+ await fs20.rm(await storePath(root, owner), { force: true }).catch(() => {
4582
4735
  });
4583
4736
  }
4584
4737
  }
4585
- async function executionStatus(root, directory) {
4586
- const raw = await readStoreJson(root, directory + "/execution.json");
4738
+ async function executionStatus(root, directory2) {
4739
+ const raw = await readStoreJson(root, directory2 + "/execution.json");
4587
4740
  if (raw === null) return { status: "not-observed", attempts: [] };
4588
4741
  const log = parseExecution(raw);
4589
- await refreshRunning(root, directory, log.attempts);
4742
+ await refreshRunning(root, directory2, log.attempts);
4590
4743
  const completed = [...log.attempts].reverse().find((a) => a.status === "completed");
4591
4744
  const outstanding = [...log.attempts].reverse().find((a) => ["failed", "unknown"].includes(a.status) && (!completed || (a.finishedAt ?? a.startedAt) >= completed.startedAt));
4592
4745
  const status = log.attempts.some((a) => a.status === "running") ? "running" : outstanding?.status ?? log.attempts.at(-1)?.status ?? "not-observed";
@@ -4599,35 +4752,35 @@ var init_execution = __esm({
4599
4752
  init_store();
4600
4753
  init_evidence();
4601
4754
  init_storage();
4602
- failureSchema = z13.object({
4603
- code: z13.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
4604
- message: z13.string(),
4605
- retryable: z13.boolean(),
4606
- receiptRecorded: z13.boolean()
4755
+ failureSchema = z14.object({
4756
+ code: z14.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
4757
+ message: z14.string(),
4758
+ retryable: z14.boolean(),
4759
+ receiptRecorded: z14.boolean()
4607
4760
  });
4608
- attemptSchema = z13.object({
4609
- id: z13.string().regex(/^[a-zA-Z0-9_-]+$/),
4610
- lease: z13.literal(true).optional(),
4611
- event: z13.string(),
4612
- startedAt: z13.string(),
4613
- finishedAt: z13.string().optional(),
4614
- durationMs: z13.number().nonnegative().optional(),
4615
- pid: z13.number().int().positive(),
4616
- host: z13.string(),
4617
- status: z13.enum(["running", "completed", "failed", "unknown"]),
4618
- verificationStatus: z13.string().optional(),
4619
- reportPath: z13.string().optional(),
4761
+ attemptSchema = z14.object({
4762
+ id: z14.string().regex(/^[a-zA-Z0-9_-]+$/),
4763
+ lease: z14.literal(true).optional(),
4764
+ event: z14.string(),
4765
+ startedAt: z14.string(),
4766
+ finishedAt: z14.string().optional(),
4767
+ durationMs: z14.number().nonnegative().optional(),
4768
+ pid: z14.number().int().positive(),
4769
+ host: z14.string(),
4770
+ status: z14.enum(["running", "completed", "failed", "unknown"]),
4771
+ verificationStatus: z14.string().optional(),
4772
+ reportPath: z14.string().optional(),
4620
4773
  failure: failureSchema.optional()
4621
4774
  });
4622
- executionSchema = z13.object({ version: z13.literal(1), attempts: z13.array(attemptSchema).max(160), discardedAttempts: z13.number().int().nonnegative().default(0) });
4623
- notificationsSchema = z13.object({ version: z13.literal(1), sessions: z13.record(z13.object({
4624
- signature: z13.string(),
4775
+ executionSchema = z14.object({ version: z14.literal(1), attempts: z14.array(attemptSchema).max(160), discardedAttempts: z14.number().int().nonnegative().default(0) });
4776
+ notificationsSchema = z14.object({ version: z14.literal(1), sessions: z14.record(z14.object({
4777
+ signature: z14.string(),
4625
4778
  code: failureSchema.shape.code,
4626
- message: z13.string(),
4627
- count: z13.number().int().positive(),
4628
- lastAt: z13.string()
4779
+ message: z14.string(),
4780
+ count: z14.number().int().positive(),
4781
+ lastAt: z14.string()
4629
4782
  })) });
4630
- leasePath = (directory, id) => `${directory}/executions/${id}.json`;
4783
+ leasePath = (directory2, id) => `${directory2}/executions/${id}.json`;
4631
4784
  }
4632
4785
  });
4633
4786
 
@@ -4702,12 +4855,12 @@ __export(runtime_exports, {
4702
4855
  summarize: () => summarize
4703
4856
  });
4704
4857
  import { randomUUID as randomUUID5 } from "crypto";
4705
- import { z as z14 } from "zod";
4858
+ import { z as z15 } from "zod";
4706
4859
  async function observeReadOnlyTool(dir, event) {
4707
4860
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
4708
4861
  await withLock(ws.root, ws.directory, async () => {
4709
- const statePath = ws.directory + "/state.json";
4710
- const raw = await readStoreJson(ws.root, statePath);
4862
+ const statePath2 = ws.directory + "/state.json";
4863
+ const raw = await readStoreJson(ws.root, statePath2);
4711
4864
  if (raw === null) return;
4712
4865
  const state = parseState(raw);
4713
4866
  if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) {
@@ -4718,7 +4871,7 @@ async function observeReadOnlyTool(dir, event) {
4718
4871
  const now = (/* @__PURE__ */ new Date()).toISOString();
4719
4872
  session.lastUsed = now;
4720
4873
  session.events[event.event] = { at: now, count: (session.events[event.event]?.count ?? 0) + 1 };
4721
- await writeStoreJson(ws.root, statePath, state);
4874
+ await writeStoreJson(ws.root, statePath2, state);
4722
4875
  });
4723
4876
  return { root: ws.root, directory: ws.directory };
4724
4877
  }
@@ -4733,7 +4886,7 @@ function summarize(report) {
4733
4886
  }),
4734
4887
  ...open.length > 4 ? [`${open.length - 4} more findings in the report.`] : [],
4735
4888
  ...report.diagnostics.slice(0, 2).map(cleanText),
4736
- `Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason-auto check.`,
4889
+ `Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason check.`,
4737
4890
  "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."
4738
4891
  ].join("\n");
4739
4892
  }
@@ -4792,7 +4945,7 @@ async function verifyInputs(ws, inputs) {
4792
4945
  async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
4793
4946
  const baselines = [...state.baselines];
4794
4947
  const now = (/* @__PURE__ */ new Date()).toISOString();
4795
- const empty = {
4948
+ const empty2 = {
4796
4949
  version: 1,
4797
4950
  status: "unavailable",
4798
4951
  root: ws.root,
@@ -4809,7 +4962,7 @@ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDia
4809
4962
  };
4810
4963
  if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) {
4811
4964
  await verifyInputs(ws, inputs);
4812
- return { baselines, report: empty, cache: null };
4965
+ return { baselines, report: empty2, cache: null };
4813
4966
  }
4814
4967
  if (ws.branch === "detached" && state.latest) {
4815
4968
  const previous = await readStoreJson(ws.root, state.latest);
@@ -4911,7 +5064,7 @@ async function analyze(ws, inputs, event, startedAt) {
4911
5064
  async function automate(dir, event) {
4912
5065
  const startedAt = Date.now();
4913
5066
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
4914
- return recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
5067
+ const output = await recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
4915
5068
  const inputs = await readInputs(ws.root);
4916
5069
  const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
4917
5070
  if (result.shared) {
@@ -4928,7 +5081,7 @@ async function automate(dir, event) {
4928
5081
  let baseline = { findings: report.findings, head: report.head };
4929
5082
  if (state.latest) {
4930
5083
  try {
4931
- const previous = z14.object({ findings: z14.array(repairFindingSchema), head: z14.string() }).safeParse(await readStoreJson(ws.root, state.latest));
5084
+ const previous = z15.object({ findings: z15.array(repairFindingSchema), head: z15.string() }).safeParse(await readStoreJson(ws.root, state.latest));
4932
5085
  if (!previous.success) throw new Error("Unsupported saved report");
4933
5086
  baseline = previous.data;
4934
5087
  } catch {
@@ -4996,9 +5149,15 @@ async function automate(dir, event) {
4996
5149
  state.latest = report.reportPath;
4997
5150
  if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
4998
5151
  await writeStoreJson(ws.root, ws.directory + "/state.json", state);
4999
- return { report, message: message2 };
5152
+ return { report, message: message2, observedAt: now, delivery: { agent: feedback.slice(0, 4), completion: selected.slice(0, 4) } };
5000
5153
  }));
5001
5154
  }));
5155
+ try {
5156
+ await observeUsefulness(ws.root, event, output.report, output.delivery.agent, output.delivery.completion, Date.now() - startedAt, output.observedAt);
5157
+ } catch {
5158
+ if (event.event === "task_end") output.message = [output.message, "Mason: local usefulness tracking unavailable; inspect mason stats --json."].filter(Boolean).join("\n");
5159
+ }
5160
+ return output;
5002
5161
  }
5003
5162
  async function automationStatus(dir) {
5004
5163
  const { pendingKnowledge: pendingKnowledge2 } = await Promise.resolve().then(() => (init_pending(), pending_exports));
@@ -5039,6 +5198,7 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
5039
5198
  var init_runtime = __esm({
5040
5199
  "src/automation/runtime.ts"() {
5041
5200
  "use strict";
5201
+ init_usefulness();
5042
5202
  init_notifications();
5043
5203
  init_profile();
5044
5204
  init_inspection();
@@ -5051,34 +5211,34 @@ var init_runtime = __esm({
5051
5211
  SCOPE = "Documentation audit evidence only. Hook receipts show observed events, not complete interception. Resolved findings no longer fail their checks or have a recorded assessment covering current evidence. Repair and review only within the user's task authorization.";
5052
5212
  priority = { resolved: 0, "review-required": 1, unresolved: 2, unverified: 3 };
5053
5213
  cleanText = (text2) => text2.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
5054
- analysisSchema = z14.object({
5055
- id: z14.string(),
5056
- fingerprint: z14.string(),
5057
- completedAt: z14.number(),
5058
- baselineDigest: z14.string(),
5214
+ analysisSchema = z15.object({
5215
+ id: z15.string(),
5216
+ fingerprint: z15.string(),
5217
+ completedAt: z15.number(),
5218
+ baselineDigest: z15.string(),
5059
5219
  currentAudit: reportSchema.nullable().optional(),
5060
- report: z14.object({
5061
- version: z14.literal(1),
5062
- status: z14.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
5063
- root: z14.string(),
5064
- branch: z14.string(),
5065
- head: z14.string(),
5066
- baselinePaths: z14.array(z14.string()),
5067
- reportPath: z14.string(),
5068
- findings: z14.array(repairFindingSchema),
5069
- diagnostics: z14.array(z14.string()),
5070
- checks: z14.object({
5071
- ran: z14.array(z14.string()),
5072
- reused: z14.array(z14.string()),
5073
- skipped: z14.array(z14.object({ check: z14.string(), reason: z14.string(), doc: z14.string().optional() })),
5074
- auditReused: z14.boolean().optional()
5220
+ report: z15.object({
5221
+ version: z15.literal(1),
5222
+ status: z15.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
5223
+ root: z15.string(),
5224
+ branch: z15.string(),
5225
+ head: z15.string(),
5226
+ baselinePaths: z15.array(z15.string()),
5227
+ reportPath: z15.string(),
5228
+ findings: z15.array(repairFindingSchema),
5229
+ diagnostics: z15.array(z15.string()),
5230
+ checks: z15.object({
5231
+ ran: z15.array(z15.string()),
5232
+ reused: z15.array(z15.string()),
5233
+ skipped: z15.array(z15.object({ check: z15.string(), reason: z15.string(), doc: z15.string().optional() })),
5234
+ auditReused: z15.boolean().optional()
5075
5235
  }),
5076
- counts: z14.object({ resolved: z14.number(), unresolved: z14.number(), "review-required": z14.number(), unverified: z14.number() }),
5077
- capture: z14.enum(["observed", "unknown"]),
5078
- scope: z14.string()
5236
+ counts: z15.object({ resolved: z15.number(), unresolved: z15.number(), "review-required": z15.number(), unverified: z15.number() }),
5237
+ capture: z15.enum(["observed", "unknown"]),
5238
+ scope: z15.string()
5079
5239
  })
5080
5240
  });
5081
- sharedSchema = z14.object({ analysis: analysisSchema, digest: z14.string() });
5241
+ sharedSchema = z15.object({ analysis: analysisSchema, digest: z15.string() });
5082
5242
  baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
5083
5243
  state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
5084
5244
  )));
@@ -5086,7 +5246,7 @@ var init_runtime = __esm({
5086
5246
  });
5087
5247
 
5088
5248
  // src/automation/hook-root.ts
5089
- import fs20 from "fs/promises";
5249
+ import fs21 from "fs/promises";
5090
5250
  import path28 from "path";
5091
5251
  var init_hook_root = __esm({
5092
5252
  "src/automation/hook-root.ts"() {
@@ -5096,7 +5256,7 @@ var init_hook_root = __esm({
5096
5256
  });
5097
5257
 
5098
5258
  // src/automation/adapters.ts
5099
- import { z as z15 } from "zod";
5259
+ import { z as z16 } from "zod";
5100
5260
  function managedHookCommand(host, platform = process.platform) {
5101
5261
  const command = `mason --setup-host ${host} auto hook --host ${host}`;
5102
5262
  return platform === "win32" ? `cmd.exe /d /s /c "where mason >nul 2>nul & if errorlevel 1 (exit /b 0) else (${command})"` : `if command -v mason >/dev/null 2>&1; then ${command}; fi`;
@@ -5107,7 +5267,7 @@ function knownManagedHookCommands(host) {
5107
5267
  function compatibleManagedHookCommands(host, platform = process.platform) {
5108
5268
  return [managedHookCommand(host, platform), `mason --setup-host ${host} auto hook --host ${host}`];
5109
5269
  }
5110
- function hookConfig(host, command = "npx --no-install --package mason-context mason-auto") {
5270
+ function hookConfig(host, command = "npx --no-install --package mason-context mason auto") {
5111
5271
  const handler = { type: "command", command: command === `mason --setup-host ${host} auto` ? managedHookCommand(host) : command + " hook --host " + host, timeout: 30 };
5112
5272
  return { hooks: Object.fromEntries(HOOK_EVENTS.map((name) => [
5113
5273
  name,
@@ -5118,19 +5278,21 @@ var inputSchema, HOOK_EVENTS;
5118
5278
  var init_adapters = __esm({
5119
5279
  "src/automation/adapters.ts"() {
5120
5280
  "use strict";
5281
+ init_usefulness();
5121
5282
  init_execution();
5122
5283
  init_runtime();
5123
5284
  init_store();
5124
5285
  init_hook_root();
5125
- inputSchema = z15.object({
5126
- cwd: z15.string().min(1),
5127
- session_id: z15.string().min(1).max(500),
5128
- hook_event_name: z15.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
5129
- tool_name: z15.string().optional(),
5130
- tool_use_id: z15.string().max(500).optional(),
5131
- tool_input: z15.unknown().optional(),
5132
- stop_hook_active: z15.boolean().optional(),
5133
- permission_mode: z15.string().optional()
5286
+ inputSchema = z16.object({
5287
+ cwd: z16.string().min(1),
5288
+ session_id: z16.string().min(1).max(500),
5289
+ hook_event_name: z16.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
5290
+ tool_name: z16.string().optional(),
5291
+ tool_use_id: z16.string().max(500).optional(),
5292
+ tool_response: z16.unknown().optional(),
5293
+ tool_input: z16.unknown().optional(),
5294
+ stop_hook_active: z16.boolean().optional(),
5295
+ permission_mode: z16.string().optional()
5134
5296
  });
5135
5297
  HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"];
5136
5298
  }
@@ -5146,7 +5308,7 @@ __export(ownership_exports, {
5146
5308
  ownershipEdit: () => ownershipEdit,
5147
5309
  ownershipSchema: () => ownershipSchema
5148
5310
  });
5149
- import { z as z16 } from "zod";
5311
+ import { z as z17 } from "zod";
5150
5312
  import { parse } from "smol-toml";
5151
5313
  function blockSpan(text2, start, end) {
5152
5314
  const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
@@ -5197,15 +5359,15 @@ var init_ownership = __esm({
5197
5359
  ["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
5198
5360
  ["# mason:mcp:start", "# mason:mcp:end"]
5199
5361
  ];
5200
- fileSchema = z16.object({
5201
- created: z16.boolean(),
5202
- initialHash: z16.string(),
5203
- blocks: z16.record(z16.string()),
5204
- mcpHash: z16.string().optional(),
5205
- hookCommands: z16.array(z16.string()).optional(),
5206
- hooks: z16.record(z16.array(z16.string())).optional()
5362
+ fileSchema = z17.object({
5363
+ created: z17.boolean(),
5364
+ initialHash: z17.string(),
5365
+ blocks: z17.record(z17.string()),
5366
+ mcpHash: z17.string().optional(),
5367
+ hookCommands: z17.array(z17.string()).optional(),
5368
+ hooks: z17.record(z17.array(z17.string())).optional()
5207
5369
  });
5208
- ownershipSchema = z16.object({ version: z16.literal(1), files: z16.record(fileSchema) });
5370
+ ownershipSchema = z17.object({ version: z17.literal(1), files: z17.record(fileSchema) });
5209
5371
  }
5210
5372
  });
5211
5373
 
@@ -5214,13 +5376,13 @@ var install_exports = {};
5214
5376
  __export(install_exports, {
5215
5377
  AUTOMATION_PATH: () => AUTOMATION_PATH,
5216
5378
  automationConfigSchema: () => automationConfigSchema,
5217
- configPath: () => configPath,
5379
+ configPath: () => configPath2,
5218
5380
  installAutomation: () => installAutomation,
5219
5381
  installedAutomation: () => installedAutomation,
5220
5382
  planAutomationInstall: () => planAutomationInstall,
5221
5383
  recordSchema: () => recordSchema2
5222
5384
  });
5223
- import { z as z17 } from "zod";
5385
+ import { z as z18 } from "zod";
5224
5386
  async function installationRecord(root) {
5225
5387
  return recordSchema2.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
5226
5388
  }
@@ -5229,7 +5391,7 @@ async function installAutomation(dir, host, command) {
5229
5391
  return withLock(ws.root, ".mason/reports/setup-lock", () => installLocked(ws.root, host, command));
5230
5392
  }
5231
5393
  async function planAutomationInstall(root, host, command) {
5232
- const file = configPath(host);
5394
+ const file = configPath2(host);
5233
5395
  const before = await readText(root, file);
5234
5396
  const existing = automationConfigSchema.parse(JSON.parse(before ?? "{}"));
5235
5397
  const record = await installationRecord(root);
@@ -5269,7 +5431,7 @@ async function installLocked(root, host, command) {
5269
5431
  status: "configured",
5270
5432
  command: newCommand,
5271
5433
  events: HOOK_EVENTS,
5272
- next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason-auto status reports observed events separately from configuration." : "Start a new Claude Code session. mason-auto status reports observed events separately from configuration.",
5434
+ next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason status reports observed events separately from configuration." : "Start a new Claude Code session. mason status reports observed events separately from configuration.",
5273
5435
  note: "Install mason-context in the project before using the default command. Ignore .mason/reports/ to keep local evidence out of commits. Hooks preserve evidence and suggest scoped repairs; they do not approve edits or decisions."
5274
5436
  };
5275
5437
  }
@@ -5280,10 +5442,10 @@ async function installedAutomation(dir) {
5280
5442
  for (const host of ["claude", "codex"]) {
5281
5443
  const expected = record.hosts[host];
5282
5444
  if (!expected) continue;
5283
- const current = automationConfigSchema.parse(await readStoreJson(ws.root, configPath(host)) ?? {});
5445
+ const current = automationConfigSchema.parse(await readStoreJson(ws.root, configPath2(host)) ?? {});
5284
5446
  const configuredEvents = HOOK_EVENTS.filter((event) => current.hooks?.[event]?.some((group) => group.hooks.some((handler) => handler.type === "command" && handler.command === expected.command)));
5285
5447
  result[host] = {
5286
- configPath: configPath(host),
5448
+ configPath: configPath2(host),
5287
5449
  configuredEvents,
5288
5450
  status: current.disableAllHooks === true ? "disabled" : configuredEvents.length === HOOK_EVENTS.length ? "configured" : "incomplete",
5289
5451
  runtime: "Host version, trust, policy, and tool coverage still determine execution; inspect observed events."
@@ -5291,7 +5453,7 @@ async function installedAutomation(dir) {
5291
5453
  }
5292
5454
  return result;
5293
5455
  }
5294
- var groupSchema, automationConfigSchema, recordSchema2, AUTOMATION_PATH, configPath;
5456
+ var groupSchema, automationConfigSchema, recordSchema2, AUTOMATION_PATH, configPath2;
5295
5457
  var init_install = __esm({
5296
5458
  "src/automation/install.ts"() {
5297
5459
  "use strict";
@@ -5301,11 +5463,11 @@ var init_install = __esm({
5301
5463
  init_store();
5302
5464
  init_files2();
5303
5465
  init_ownership();
5304
- groupSchema = z17.object({ hooks: z17.array(z17.object({ type: z17.string(), command: z17.string().optional() }).passthrough()) }).passthrough();
5305
- automationConfigSchema = z17.object({ hooks: z17.record(z17.array(groupSchema)).optional() }).passthrough();
5306
- recordSchema2 = z17.object({ version: z17.literal(1), hosts: z17.record(z17.object({ command: z17.string() })) });
5466
+ groupSchema = z18.object({ hooks: z18.array(z18.object({ type: z18.string(), command: z18.string().optional() }).passthrough()) }).passthrough();
5467
+ automationConfigSchema = z18.object({ hooks: z18.record(z18.array(groupSchema)).optional() }).passthrough();
5468
+ recordSchema2 = z18.object({ version: z18.literal(1), hosts: z18.record(z18.object({ command: z18.string() })) });
5307
5469
  AUTOMATION_PATH = ".mason/local/automation.json";
5308
- configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
5470
+ configPath2 = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
5309
5471
  }
5310
5472
  });
5311
5473
 
@@ -5321,7 +5483,7 @@ __export(config_exports, {
5321
5483
  });
5322
5484
  import { isDeepStrictEqual } from "util";
5323
5485
  import { parse as parse2, stringify } from "smol-toml";
5324
- import { z as z18 } from "zod";
5486
+ import { z as z19 } from "zod";
5325
5487
  async function instructionEdits(root, host) {
5326
5488
  const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
5327
5489
  const existing = found.find((f) => f.text !== null);
@@ -5380,7 +5542,7 @@ async function mcpEdit(root, host) {
5380
5542
  return { path: file, before, after };
5381
5543
  }
5382
5544
  async function hookEdits(root, host) {
5383
- const file = configPath(host), before = await readText(root, file);
5545
+ const file = configPath2(host), before = await readText(root, file);
5384
5546
  const { planAutomationInstall: planAutomationInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
5385
5547
  const plan = await planAutomationInstall2(root, host, hookCommand(host));
5386
5548
  return [
@@ -5408,7 +5570,7 @@ async function inspectHostConfig(root, host, plannedMcp) {
5408
5570
  const text2 = plannedMcp ?? await readText(root, mcpPath(host));
5409
5571
  const config = host === "codex" ? parse2(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
5410
5572
  const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
5411
- const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
5573
+ const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath2(host)) ?? "{}"));
5412
5574
  const expected = hookConfig(host, hookCommand(host));
5413
5575
  const compatible = compatibleManagedHookCommands(host);
5414
5576
  const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
@@ -5432,7 +5594,7 @@ var init_config = __esm({
5432
5594
  init_install();
5433
5595
  init_files2();
5434
5596
  init_launcher();
5435
- object = z18.record(z18.unknown());
5597
+ object = z19.record(z19.unknown());
5436
5598
  mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
5437
5599
  TOML_START = "# mason:mcp:start";
5438
5600
  TOML_END = "# mason:mcp:end";
@@ -5442,7 +5604,7 @@ var init_config = __esm({
5442
5604
  });
5443
5605
 
5444
5606
  // src/mcp/init.ts
5445
- import { z as z19 } from "zod";
5607
+ import { z as z20 } from "zod";
5446
5608
  async function loadProjectMarker(rootDir) {
5447
5609
  const raw = await readStoreJson(rootDir, ".mason/local/project.json");
5448
5610
  if (raw === null) return null;
@@ -5450,8 +5612,8 @@ async function loadProjectMarker(rootDir) {
5450
5612
  return { ...marker, features: await loadProjectFeatures(rootDir) };
5451
5613
  }
5452
5614
  async function loadProjectFeatures(rootDir) {
5453
- const config = z19.record(z19.unknown()).parse(await readStoreJson(rootDir, ".mason/config.json") ?? {});
5454
- return z19.object({ confluence: z19.boolean().optional() }).passthrough().parse(config.features ?? {});
5615
+ const config = z20.record(z20.unknown()).parse(await readStoreJson(rootDir, ".mason/config.json") ?? {});
5616
+ return z20.object({ confluence: z20.boolean().optional() }).passthrough().parse(config.features ?? {});
5455
5617
  }
5456
5618
  async function saveProjectMarker(rootDir, marker) {
5457
5619
  const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
@@ -5462,10 +5624,10 @@ async function projectMarkerEdits(rootDir, marker) {
5462
5624
  const file = ".mason/local/project.json";
5463
5625
  const edits = [{ path: file, before: await readText(rootDir, file), after: JSON.stringify(local, null, 2) + "\n" }];
5464
5626
  if (features && Object.keys(features).length) {
5465
- const configPath2 = ".mason/config.json", before = await readText(rootDir, configPath2);
5466
- const config = z19.record(z19.unknown()).parse(JSON.parse(before ?? "{}"));
5467
- config.features = { ...z19.record(z19.unknown()).parse(config.features ?? {}), ...features };
5468
- edits.push({ path: configPath2, before, after: JSON.stringify(config, null, 2) + "\n" });
5627
+ const configPath3 = ".mason/config.json", before = await readText(rootDir, configPath3);
5628
+ const config = z20.record(z20.unknown()).parse(JSON.parse(before ?? "{}"));
5629
+ config.features = { ...z20.record(z20.unknown()).parse(config.features ?? {}), ...features };
5630
+ edits.push({ path: configPath3, before, after: JSON.stringify(config, null, 2) + "\n" });
5469
5631
  }
5470
5632
  return edits;
5471
5633
  }
@@ -5478,10 +5640,10 @@ var init_init = __esm({
5478
5640
  "use strict";
5479
5641
  init_storage();
5480
5642
  init_files2();
5481
- markerSchema = z19.object({
5482
- version: z19.literal(1),
5483
- initializedAt: z19.string(),
5484
- features: z19.object({ confluence: z19.boolean().optional() }).passthrough().optional()
5643
+ markerSchema = z20.object({
5644
+ version: z20.literal(1),
5645
+ initializedAt: z20.string(),
5646
+ features: z20.object({ confluence: z20.boolean().optional() }).passthrough().optional()
5485
5647
  }).passthrough();
5486
5648
  CLAUDE_MD_SECTION = `<!-- mason:start -->
5487
5649
  ## Mason project knowledge
@@ -5503,13 +5665,13 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
5503
5665
 
5504
5666
  - For a dependency advisory, \`review_advisory(action: "dismiss", baselinePath, findingId, reasonCode, note)\` records a one-step dismissal without reviewer or token. Use \`no-dependency-claims\` for a document with no dependency claims (reopens on document content changes), or \`unrelated-manifest-change\` for reviewed changes that do not affect its guidance (reopens on document or manifest changes). Keep the relevant scope committed; commit the dismissal to share it. This never approves a decision.
5505
5667
 
5506
- - When Mason automation is installed, use \`mason_automation(action: "status")\` to inspect configured hooks and observed events, and \`mason_automation(action: "check")\` to resume its retained repair evidence. CLI fallback: \`mason-auto status\` / \`mason-auto check\`. Preserve existing baselines across sessions. Automatic checks do not authorize unrelated repairs or approve advisories.
5668
+ - When Mason automation is installed, use \`mason_automation(action: "status")\` to inspect configured hooks and observed events, and \`mason_automation(action: "check")\` to resume its retained repair evidence. CLI fallback: \`mason status\` / \`mason check\`. Preserve existing baselines across sessions. Automatic checks do not authorize unrelated repairs or approve advisories.
5507
5669
 
5508
5670
  Inspect source for what the retrieved context does not answer.
5509
5671
  <!-- mason:end -->`;
5510
5672
  ASSISTANT_SETUP = `UNIFIED SETUP
5511
- When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. This uses the same engine as mason-auto setup: retain the initial audit before instruction edits, use the installed mason command on PATH, merge project MCP configuration and hooks, update marker-delimited instructions and Git ignore rules, and record setup. A concept map is optional.
5512
- If the user only requested inspection or review, report findings without running setup. The setup result distinguishes configured from observed activation. Explain the host's native trust step and request a new session; never trust hooks on the user's behalf or claim activation from generated configuration alone. After a normal task finishes, mason_automation(action: "status") or mason-auto status reports observed context use and hook events. Existing findings remain reviewable; setup does not approve advisories or manufacture decisions.
5673
+ When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. This uses the same engine as mason setup: retain the initial audit before instruction edits, use the installed mason command on PATH, merge project MCP configuration and hooks, update marker-delimited instructions and Git ignore rules, and record setup. A concept map is optional.
5674
+ If the user only requested inspection or review, report findings without running setup. The setup result distinguishes configured from observed activation. Explain the host's native trust step and request a new session; never trust hooks on the user's behalf or claim activation from generated configuration alone. After a normal task finishes, mason_automation(action: "status") or mason status reports observed context use and hook events. Existing findings remain reviewable; setup does not approve advisories or manufacture decisions.
5513
5675
 
5514
5676
  The managed project guidance is:
5515
5677
  ${CLAUDE_MD_SECTION}`;
@@ -5680,7 +5842,7 @@ var init_types2 = __esm({
5680
5842
  });
5681
5843
 
5682
5844
  // src/review/evidence/vitest.ts
5683
- import { z as z20 } from "zod";
5845
+ import { z as z21 } from "zod";
5684
5846
  function parseVitest(raw, sourceRoot) {
5685
5847
  const report = schema.parse(raw);
5686
5848
  const findings = [], diagnostics = [];
@@ -5729,24 +5891,24 @@ var init_vitest = __esm({
5729
5891
  "use strict";
5730
5892
  init_paths2();
5731
5893
  init_types2();
5732
- count3 = z20.number().int().nonnegative();
5733
- schema = z20.object({
5734
- success: z20.boolean(),
5894
+ count3 = z21.number().int().nonnegative();
5895
+ schema = z21.object({
5896
+ success: z21.boolean(),
5735
5897
  numTotalTests: count3,
5736
5898
  numPassedTests: count3,
5737
5899
  numFailedTests: count3,
5738
5900
  numPendingTests: count3,
5739
5901
  numTodoTests: count3,
5740
5902
  numFailedTestSuites: count3,
5741
- testResults: z20.array(z20.object({
5742
- name: z20.string().min(1),
5743
- status: z20.enum(["passed", "failed"]),
5744
- message: z20.string().optional(),
5745
- assertionResults: z20.array(z20.object({
5746
- fullName: z20.string(),
5747
- status: z20.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
5748
- failureMessages: z20.array(z20.string()).nullable().optional(),
5749
- location: z20.object({ line: count3, column: count3 }).nullable().optional()
5903
+ testResults: z21.array(z21.object({
5904
+ name: z21.string().min(1),
5905
+ status: z21.enum(["passed", "failed"]),
5906
+ message: z21.string().optional(),
5907
+ assertionResults: z21.array(z21.object({
5908
+ fullName: z21.string(),
5909
+ status: z21.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
5910
+ failureMessages: z21.array(z21.string()).nullable().optional(),
5911
+ location: z21.object({ line: count3, column: count3 }).nullable().optional()
5750
5912
  }))
5751
5913
  }))
5752
5914
  });
@@ -5754,7 +5916,7 @@ var init_vitest = __esm({
5754
5916
  });
5755
5917
 
5756
5918
  // src/review/evidence/sarif.ts
5757
- import { z as z21 } from "zod";
5919
+ import { z as z22 } from "zod";
5758
5920
  function parseSarif(raw, sourceRoot) {
5759
5921
  const report = schema2.parse(raw), findings = [], diagnostics = [];
5760
5922
  let active = 0, suppressed = 0, absent = 0, unresolved = 0;
@@ -5845,29 +6007,29 @@ var init_sarif = __esm({
5845
6007
  "use strict";
5846
6008
  init_paths2();
5847
6009
  init_types2();
5848
- index = z21.number().int().nonnegative();
5849
- artifact = z21.object({ uri: z21.string().optional(), uriBaseId: z21.string().optional(), index: index.optional() });
5850
- message = z21.object({ text: z21.string().optional(), markdown: z21.string().optional(), id: z21.string().optional(), arguments: z21.array(z21.string()).optional() });
5851
- level = z21.enum(["error", "warning", "note", "none"]);
5852
- location = z21.object({ physicalLocation: z21.object({ artifactLocation: artifact.optional(), region: z21.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
5853
- schema2 = z21.object({
5854
- version: z21.literal("2.1.0"),
5855
- runs: z21.array(z21.object({
5856
- tool: z21.object({ driver: z21.object({ name: z21.string().min(1), rules: z21.array(z21.object({ id: z21.string(), defaultConfiguration: z21.object({ level: level.optional() }).optional(), messageStrings: z21.record(message).optional() })).optional(), globalMessageStrings: z21.record(message).optional() }) }),
5857
- invocations: z21.array(z21.object({ executionSuccessful: z21.boolean(), commandLine: z21.string().optional(), toolExecutionNotifications: z21.array(z21.object({ level: level.optional(), message })).optional() })).optional(),
5858
- versionControlProvenance: z21.array(z21.object({ revisionId: z21.string().optional() })).optional(),
5859
- originalUriBaseIds: z21.record(artifact).optional(),
5860
- artifacts: z21.array(z21.object({ location: artifact.optional() })).optional(),
5861
- results: z21.array(z21.object({
5862
- ruleId: z21.string().optional(),
6010
+ index = z22.number().int().nonnegative();
6011
+ artifact = z22.object({ uri: z22.string().optional(), uriBaseId: z22.string().optional(), index: index.optional() });
6012
+ message = z22.object({ text: z22.string().optional(), markdown: z22.string().optional(), id: z22.string().optional(), arguments: z22.array(z22.string()).optional() });
6013
+ level = z22.enum(["error", "warning", "note", "none"]);
6014
+ location = z22.object({ physicalLocation: z22.object({ artifactLocation: artifact.optional(), region: z22.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
6015
+ schema2 = z22.object({
6016
+ version: z22.literal("2.1.0"),
6017
+ runs: z22.array(z22.object({
6018
+ tool: z22.object({ driver: z22.object({ name: z22.string().min(1), rules: z22.array(z22.object({ id: z22.string(), defaultConfiguration: z22.object({ level: level.optional() }).optional(), messageStrings: z22.record(message).optional() })).optional(), globalMessageStrings: z22.record(message).optional() }) }),
6019
+ invocations: z22.array(z22.object({ executionSuccessful: z22.boolean(), commandLine: z22.string().optional(), toolExecutionNotifications: z22.array(z22.object({ level: level.optional(), message })).optional() })).optional(),
6020
+ versionControlProvenance: z22.array(z22.object({ revisionId: z22.string().optional() })).optional(),
6021
+ originalUriBaseIds: z22.record(artifact).optional(),
6022
+ artifacts: z22.array(z22.object({ location: artifact.optional() })).optional(),
6023
+ results: z22.array(z22.object({
6024
+ ruleId: z22.string().optional(),
5863
6025
  ruleIndex: index.optional(),
5864
6026
  message,
5865
6027
  level: level.optional(),
5866
- kind: z21.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
5867
- baselineState: z21.enum(["new", "unchanged", "updated", "absent"]).optional(),
5868
- suppressions: z21.array(z21.object({ kind: z21.enum(["inSource", "external"]), status: z21.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
5869
- locations: z21.array(location).optional(),
5870
- relatedLocations: z21.array(location).optional()
6028
+ kind: z22.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
6029
+ baselineState: z22.enum(["new", "unchanged", "updated", "absent"]).optional(),
6030
+ suppressions: z22.array(z22.object({ kind: z22.enum(["inSource", "external"]), status: z22.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
6031
+ locations: z22.array(location).optional(),
6032
+ relatedLocations: z22.array(location).optional()
5871
6033
  })).optional()
5872
6034
  }))
5873
6035
  });
@@ -5875,7 +6037,7 @@ var init_sarif = __esm({
5875
6037
  });
5876
6038
 
5877
6039
  // src/review/evidence/check.ts
5878
- import { z as z22 } from "zod";
6040
+ import { z as z23 } from "zod";
5879
6041
  function parseCheck(raw, sourceRoot) {
5880
6042
  const report = schema3.parse(raw);
5881
6043
  const diagnostics = [], findings = [];
@@ -5915,17 +6077,17 @@ var init_check = __esm({
5915
6077
  "use strict";
5916
6078
  init_paths2();
5917
6079
  init_types2();
5918
- schema3 = z22.object({
5919
- version: z22.literal(1),
5920
- commit: z22.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
5921
- results: z22.array(z22.object({
5922
- name: z22.string().min(1).max(1e3),
5923
- status: z22.enum(["passed", "failed", "skipped"]),
5924
- message: z22.string().max(1e5).optional(),
5925
- locations: z22.array(z22.object({
5926
- file: z22.string().min(1).max(4e3),
5927
- line: z22.number().int().positive().optional(),
5928
- column: z22.number().int().positive().optional()
6080
+ schema3 = z23.object({
6081
+ version: z23.literal(1),
6082
+ commit: z23.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
6083
+ results: z23.array(z23.object({
6084
+ name: z23.string().min(1).max(1e3),
6085
+ status: z23.enum(["passed", "failed", "skipped"]),
6086
+ message: z23.string().max(1e5).optional(),
6087
+ locations: z23.array(z23.object({
6088
+ file: z23.string().min(1).max(4e3),
6089
+ line: z23.number().int().positive().optional(),
6090
+ column: z23.number().int().positive().optional()
5929
6091
  })).max(100).default([])
5930
6092
  })).max(1e5)
5931
6093
  });
@@ -5934,7 +6096,7 @@ var init_check = __esm({
5934
6096
 
5935
6097
  // src/review/evidence.ts
5936
6098
  import path29 from "path";
5937
- import { z as z23 } from "zod";
6099
+ import { z as z24 } from "zod";
5938
6100
  function relativeArtifact(root, file) {
5939
6101
  const relative = path29.isAbsolute(file) ? path29.relative(root, file) : file;
5940
6102
  const normalized = normalizeRepoPath(relative);
@@ -5975,7 +6137,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5975
6137
  let manifest, raw;
5976
6138
  try {
5977
6139
  manifest = relativeArtifact(root, supplied);
5978
- raw = z23.object({ version: z23.literal(1), checks: z23.array(z23.unknown()) }).parse(await readStoreJson(root, manifest));
6140
+ raw = z24.object({ version: z24.literal(1), checks: z24.array(z24.unknown()) }).parse(await readStoreJson(root, manifest));
5979
6141
  } catch (error) {
5980
6142
  output.diagnostics.push(`${supplied}: ${String(error)}`);
5981
6143
  continue;
@@ -6130,31 +6292,31 @@ var init_evidence2 = __esm({
6130
6292
  init_sarif();
6131
6293
  init_check();
6132
6294
  init_types2();
6133
- checkSchema2 = z23.object({
6134
- id: z23.string().min(1).max(100),
6135
- kind: z23.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
6136
- tool: z23.string().min(1).max(200),
6137
- command: z23.string().min(1).max(2e3),
6138
- commit: z23.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
6139
- workingTreeClean: z23.boolean().optional(),
6140
- source: z23.string().max(2e3).optional(),
6141
- sourceRoot: z23.string().min(1).max(4e3).optional(),
6142
- status: z23.enum(["completed", "skipped", "unavailable"]).default("completed"),
6143
- reason: z23.string().min(1).max(2e3).optional(),
6144
- exitCode: z23.number().int().nullable().optional(),
6145
- environment: z23.object({
6146
- toolVersion: z23.string().min(1).max(200),
6147
- runtime: z23.string().min(1).max(200).optional(),
6148
- platform: z23.string().min(1).max(200).optional(),
6149
- configuration: z23.array(z23.string().min(1).max(4e3)).max(100).optional()
6295
+ checkSchema2 = z24.object({
6296
+ id: z24.string().min(1).max(100),
6297
+ kind: z24.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
6298
+ tool: z24.string().min(1).max(200),
6299
+ command: z24.string().min(1).max(2e3),
6300
+ commit: z24.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
6301
+ workingTreeClean: z24.boolean().optional(),
6302
+ source: z24.string().max(2e3).optional(),
6303
+ sourceRoot: z24.string().min(1).max(4e3).optional(),
6304
+ status: z24.enum(["completed", "skipped", "unavailable"]).default("completed"),
6305
+ reason: z24.string().min(1).max(2e3).optional(),
6306
+ exitCode: z24.number().int().nullable().optional(),
6307
+ environment: z24.object({
6308
+ toolVersion: z24.string().min(1).max(200),
6309
+ runtime: z24.string().min(1).max(200).optional(),
6310
+ platform: z24.string().min(1).max(200).optional(),
6311
+ configuration: z24.array(z24.string().min(1).max(4e3)).max(100).optional()
6150
6312
  }).optional(),
6151
- report: z23.object({ format: z23.enum(["vitest-json", "sarif", "mason-check-json"]), path: z23.string().min(1).max(4e3) }).optional()
6313
+ report: z24.object({ format: z24.enum(["vitest-json", "sarif", "mason-check-json"]), path: z24.string().min(1).max(4e3) }).optional()
6152
6314
  });
6153
6315
  }
6154
6316
  });
6155
6317
 
6156
6318
  // src/review/review.ts
6157
- import fs21 from "fs/promises";
6319
+ import fs22 from "fs/promises";
6158
6320
  import path30 from "path";
6159
6321
  import { execFile as execFile7 } from "child_process";
6160
6322
  import { promisify as promisify7 } from "util";
@@ -6234,7 +6396,7 @@ async function computeReview(rootDir, base, options = {}) {
6234
6396
  analyzed,
6235
6397
  async (relPath) => {
6236
6398
  try {
6237
- await fs21.access(path30.join(resolvedRoot, relPath));
6399
+ await fs22.access(path30.join(resolvedRoot, relPath));
6238
6400
  return true;
6239
6401
  } catch {
6240
6402
  return false;
@@ -6710,12 +6872,12 @@ __export(assemble_exports, {
6710
6872
  assembleContext: () => assembleContext
6711
6873
  });
6712
6874
  import path33 from "path";
6713
- import fs22 from "fs/promises";
6875
+ import fs23 from "fs/promises";
6714
6876
  async function collectImpact(root, candidates) {
6715
6877
  const targets2 = /* @__PURE__ */ new Set();
6716
6878
  let sourceFiles;
6717
6879
  for (const candidate of sanitizeRepoPaths(candidates)) {
6718
- const stat = await fs22.stat(path33.join(root, candidate)).catch(() => null);
6880
+ const stat = await fs23.stat(path33.join(root, candidate)).catch(() => null);
6719
6881
  if (stat?.isDirectory()) {
6720
6882
  sourceFiles ??= await (await createFileAccess(root)).list();
6721
6883
  for (const file of sourceFiles) {
@@ -6937,13 +7099,13 @@ __export(observations_exports, {
6937
7099
  observeActivation: () => observeActivation,
6938
7100
  readObservation: () => readObservation
6939
7101
  });
6940
- import fs23 from "fs/promises";
6941
- import { z as z24 } from "zod";
6942
- function observationPath(directory, host, revision, version = executingVersion()) {
6943
- return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
7102
+ import fs24 from "fs/promises";
7103
+ import { z as z25 } from "zod";
7104
+ function observationPath(directory2, host, revision, version = executingVersion()) {
7105
+ return `${directory2}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
6944
7106
  }
6945
- async function readObservation(root, directory, host, revision, version = executingVersion()) {
6946
- const raw = await readStoreJson(root, observationPath(directory, host, revision, version));
7107
+ async function readObservation(root, directory2, host, revision, version = executingVersion()) {
7108
+ const raw = await readStoreJson(root, observationPath(directory2, host, revision, version));
6947
7109
  if (raw === null) return null;
6948
7110
  const record = observationSchema.parse(raw);
6949
7111
  if (record.root !== root || record.host !== host || record.revision !== revision || record.masonVersion !== version) throw new Error("Activation receipt belongs to another installation.");
@@ -6960,12 +7122,12 @@ async function observeActivation(dir, event, options = {}) {
6960
7122
  return inactiveSetupMessage();
6961
7123
  }
6962
7124
  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];
6963
- const ws = capturedDirectory ? { root: await fs23.realpath(dir), directory: capturedDirectory } : await workspace(dir);
6964
- if (ws.root !== await fs23.realpath(process.env.MASON_SETUP_ROOT)) return null;
7125
+ const ws = capturedDirectory ? { root: await fs24.realpath(dir), directory: capturedDirectory } : await workspace(dir);
7126
+ if (ws.root !== await fs24.realpath(process.env.MASON_SETUP_ROOT)) return null;
6965
7127
  const { setup } = await effectiveSetup(ws.root);
6966
7128
  if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
6967
- const directory = ws.directory + "/activation";
6968
- await withLock(ws.root, directory, async () => {
7129
+ const directory2 = ws.directory + "/activation";
7130
+ await withLock(ws.root, directory2, async () => {
6969
7131
  const now = (/* @__PURE__ */ new Date()).toISOString();
6970
7132
  const record = await readObservation(ws.root, ws.directory, host, revision) ?? {
6971
7133
  version: 1,
@@ -7009,19 +7171,19 @@ var init_observations = __esm({
7009
7171
  init_storage();
7010
7172
  init_model();
7011
7173
  init_launcher();
7012
- observationSchema = z24.object({
7013
- version: z24.literal(1),
7014
- masonVersion: z24.string(),
7015
- root: z24.string(),
7016
- revision: z24.string(),
7017
- host: z24.enum(["codex", "claude"]),
7018
- contextCalls: z24.number().int().nonnegative(),
7019
- lastContextAt: z24.string().optional(),
7020
- sessions: z24.record(z24.object({
7021
- events: z24.array(z24.enum(events)),
7022
- at: z24.string(),
7023
- verificationStatus: z24.string().optional(),
7024
- reportPath: z24.string().optional()
7174
+ observationSchema = z25.object({
7175
+ version: z25.literal(1),
7176
+ masonVersion: z25.string(),
7177
+ root: z25.string(),
7178
+ revision: z25.string(),
7179
+ host: z25.enum(["codex", "claude"]),
7180
+ contextCalls: z25.number().int().nonnegative(),
7181
+ lastContextAt: z25.string().optional(),
7182
+ sessions: z25.record(z25.object({
7183
+ events: z25.array(z25.enum(events)),
7184
+ at: z25.string(),
7185
+ verificationStatus: z25.string().optional(),
7186
+ reportPath: z25.string().optional()
7025
7187
  }))
7026
7188
  });
7027
7189
  }
@@ -7048,7 +7210,7 @@ async function setupStatus(dir) {
7048
7210
  version: 1,
7049
7211
  status: pending.length ? "incomplete" : "not-configured",
7050
7212
  hosts: {},
7051
- next: pending.length ? "Setup did not finish. Rerun mason-auto setup --host " + pending[0] + " to resume using the retained original evidence." : "Project hooks are inactive until local setup. Run mason setup --host codex or --host claude."
7213
+ next: pending.length ? "Setup did not finish. Rerun mason setup --host " + pending[0] + " to resume using the retained original evidence." : "Project hooks are inactive until local setup. Run mason setup --host codex or --host claude."
7052
7214
  };
7053
7215
  }
7054
7216
  const hosts = {};
@@ -7082,7 +7244,7 @@ async function setupStatus(dir) {
7082
7244
  if (!mcp || !hooks || !instructionsCurrent || !configured) pending.push("Rerun setup to reconcile project configuration; inspect any disabled host settings.");
7083
7245
  if (!observation?.contextCalls) pending.push("Start a new assistant session and request task context through Mason's get_context tool.");
7084
7246
  if (!complete) pending.push("Review/trust the host configuration, then complete a normal task to observe the full hook lifecycle.");
7085
- if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason-auto check and inspect its diagnostics.");
7247
+ if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason check and inspect its diagnostics.");
7086
7248
  const healthy = installed.available && mcp && hooks && instructionsCurrent && configured && automation.status !== "unavailable";
7087
7249
  hosts[host] = {
7088
7250
  status: healthy ? complete && observation?.contextCalls ? "active" : "pending" : "attention",
@@ -7216,6 +7378,11 @@ async function setupProject(dir, options = {}) {
7216
7378
  options.progress?.step("Checking configuration and retained findings");
7217
7379
  const checked = await automate(ws.root, { event: "turn_start" });
7218
7380
  const configured = await inspectHostConfig(ws.root, host, void 0);
7381
+ const stats = await configureUsefulness(ws.root, true, { onlyIfUnset: true }).catch(() => ({
7382
+ enabled: null,
7383
+ storage: ".mason/local/usefulness",
7384
+ note: "Local stats unavailable. Inspect mason stats --json, or use mason stats --disable to reset the preference. Existing observations were retained."
7385
+ }));
7219
7386
  await writeStoreJson(ws.root, receiptPath, {
7220
7387
  version: 1,
7221
7388
  host,
@@ -7238,6 +7405,7 @@ async function setupProject(dir, options = {}) {
7238
7405
  findings,
7239
7406
  reportPath: checked.report.reportPath,
7240
7407
  activation: await setupStatus(ws.root),
7408
+ stats,
7241
7409
  next: configured.disabled || configured.mcpDisabled ? "Mason hooks or MCP are disabled in project configuration. Review that setting before activation." : host === "codex" ? "Review/trust this project's MCP configuration and hooks in Codex (/hooks in the CLI), then start a new session and give it a normal task." : "Approve the project MCP server in Claude Code, then start a new session and give it a normal task."
7242
7410
  };
7243
7411
  });
@@ -7249,18 +7417,20 @@ function summarizeSetup(result) {
7249
7417
  " MCP server and lifecycle hooks use mason from PATH.",
7250
7418
  " Project instructions updated; original audit evidence retained.",
7251
7419
  ` ${result.changedFiles.length} files changed.`,
7420
+ result.stats.enabled === null ? result.stats.note : result.stats.enabled ? "Local stats enabled: metadata stays in .mason/local/usefulness/; nothing is uploaded. View with mason stats; disable with mason stats --disable." : "Local stats remain disabled by your saved preference. Enable with mason stats --enable.",
7252
7421
  `Initial audit: ${audit.status}${"counts" in audit ? `; ${audit.counts.issues} issues, ${audit.counts.advisories + audit.counts.suppressedAdvisories} advisories` : ""}.`,
7253
7422
  ..."issues" in audit ? audit.issues.slice(0, 3).map((f) => " " + f.message) : [],
7254
7423
  `Original evidence: ${result.initialReportPath}`,
7255
7424
  "Activation: " + result.activation.status + ".",
7256
7425
  result.next,
7257
7426
  "If the assistant cannot find mason, restart the desktop app so it picks up PATH.",
7258
- "After the task finishes, run mason-auto status. Configuration alone does not establish activation."
7427
+ "After the task finishes, run mason status. Configuration alone does not establish activation."
7259
7428
  ].join("\n");
7260
7429
  }
7261
7430
  var init_setup = __esm({
7262
7431
  "src/setup/setup.ts"() {
7263
7432
  "use strict";
7433
+ init_usefulness();
7264
7434
  init_evidence();
7265
7435
  init_store();
7266
7436
  init_runtime();
@@ -7398,7 +7568,7 @@ __export(config_exports2, {
7398
7568
  saveConfluenceConfig: () => saveConfluenceConfig,
7399
7569
  validateProvider: () => validateProvider
7400
7570
  });
7401
- import fs24 from "fs/promises";
7571
+ import fs25 from "fs/promises";
7402
7572
  import path34 from "path";
7403
7573
  import os3 from "os";
7404
7574
  import { execFile as execFile9 } from "child_process";
@@ -7411,15 +7581,15 @@ function configFile() {
7411
7581
  }
7412
7582
  async function loadConfig() {
7413
7583
  try {
7414
- const raw = await fs24.readFile(configFile(), "utf-8");
7584
+ const raw = await fs25.readFile(configFile(), "utf-8");
7415
7585
  return JSON.parse(raw);
7416
7586
  } catch {
7417
7587
  return null;
7418
7588
  }
7419
7589
  }
7420
7590
  async function saveConfig(config) {
7421
- await fs24.mkdir(configDir(), { recursive: true });
7422
- await fs24.writeFile(configFile(), JSON.stringify(config, null, 2), "utf-8");
7591
+ await fs25.mkdir(configDir(), { recursive: true });
7592
+ await fs25.writeFile(configFile(), JSON.stringify(config, null, 2), "utf-8");
7423
7593
  }
7424
7594
  function getDefaultModel(provider) {
7425
7595
  return DEFAULT_MODELS[provider];
@@ -7579,7 +7749,7 @@ var init_renderer = __esm({
7579
7749
  });
7580
7750
 
7581
7751
  // src/confluence/diff.ts
7582
- import fs25 from "fs/promises";
7752
+ import fs26 from "fs/promises";
7583
7753
  import path35 from "path";
7584
7754
  import { createHash as createHash6 } from "crypto";
7585
7755
  function hashDescription(description) {
@@ -7593,7 +7763,7 @@ function syncStatePath(rootDir) {
7593
7763
  }
7594
7764
  async function loadSyncState(rootDir) {
7595
7765
  try {
7596
- const raw = await fs25.readFile(syncStatePath(rootDir), "utf-8");
7766
+ const raw = await fs26.readFile(syncStatePath(rootDir), "utf-8");
7597
7767
  const parsed = JSON.parse(raw);
7598
7768
  if (parsed.version !== 2) return null;
7599
7769
  return parsed;
@@ -7602,8 +7772,8 @@ async function loadSyncState(rootDir) {
7602
7772
  }
7603
7773
  }
7604
7774
  async function saveSyncState(rootDir, state) {
7605
- await fs25.mkdir(syncStateDir(rootDir), { recursive: true });
7606
- await fs25.writeFile(
7775
+ await fs26.mkdir(syncStateDir(rootDir), { recursive: true });
7776
+ await fs26.writeFile(
7607
7777
  syncStatePath(rootDir),
7608
7778
  JSON.stringify(state, null, 2),
7609
7779
  "utf-8"
@@ -8156,12 +8326,12 @@ init_advisory_review();
8156
8326
  init_decisions();
8157
8327
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8158
8328
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8159
- import { z as z25 } from "zod";
8329
+ import { z as z26 } from "zod";
8160
8330
 
8161
8331
  // src/mcp/tools.ts
8162
8332
  init_repair();
8163
8333
  init_advisory_review();
8164
- import fs26 from "fs/promises";
8334
+ import fs27 from "fs/promises";
8165
8335
  import path36 from "path";
8166
8336
  import { execFile as execFile11 } from "child_process";
8167
8337
  import { promisify as promisify11 } from "util";
@@ -9135,7 +9305,7 @@ async function detectProjectSnapshot(rootDir) {
9135
9305
  const present = [];
9136
9306
  for (const file of buildFiles) {
9137
9307
  try {
9138
- await fs26.access(path36.join(rootDir, file));
9308
+ await fs27.access(path36.join(rootDir, file));
9139
9309
  present.push(file);
9140
9310
  } catch {
9141
9311
  }
@@ -10027,7 +10197,7 @@ function createMcpServer() {
10027
10197
  const server = new McpServer(
10028
10198
  {
10029
10199
  name: "mason",
10030
- version: "0.19.2"
10200
+ version: "0.20.2"
10031
10201
  },
10032
10202
  {
10033
10203
  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 as soon as evidence supports them, without waiting for implementation or merge; 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.'
@@ -10037,11 +10207,11 @@ function createMcpServer() {
10037
10207
  "mason_init",
10038
10208
  "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.",
10039
10209
  {
10040
- dir: z25.string().describe("Absolute path to the project root directory"),
10041
- mode: z25.enum(["quickstart", "map", "setup"]).optional().default("quickstart").describe("Quickstart inspects without edits; map requests an architecture build; setup installs the shared onboarding flow."),
10042
- host: z25.enum(["codex", "claude"]).optional().describe("Assistant to configure in setup mode; inferred only when unambiguous."),
10043
- base: z25.string().optional().describe("Git ref for committed-diff review. Defaults to the first available main branch ref."),
10044
- evidence: z25.array(z25.string()).max(10).optional().describe("Repository-local CI evidence manifests to include in the review. Imports Vitest JSON, SARIF, and native-validator mason-check-json results without executing check commands.")
10210
+ dir: z26.string().describe("Absolute path to the project root directory"),
10211
+ mode: z26.enum(["quickstart", "map", "setup"]).optional().default("quickstart").describe("Quickstart inspects without edits; map requests an architecture build; setup installs the shared onboarding flow."),
10212
+ host: z26.enum(["codex", "claude"]).optional().describe("Assistant to configure in setup mode; inferred only when unambiguous."),
10213
+ base: z26.string().optional().describe("Git ref for committed-diff review. Defaults to the first available main branch ref."),
10214
+ evidence: z26.array(z26.string()).max(10).optional().describe("Repository-local CI evidence manifests to include in the review. Imports Vitest JSON, SARIF, and native-validator mason-check-json results without executing check commands.")
10045
10215
  },
10046
10216
  async ({ dir, mode, host, base, evidence }) => {
10047
10217
  const result = await masonInit(dir, { mode, host, base, evidence });
@@ -10052,8 +10222,8 @@ function createMcpServer() {
10052
10222
  "mason_automation",
10053
10223
  "Inspect installed automation, observed host events, pending decision records and the notification baseline, or check all retained documentation findings across sessions. status is read-only; check saves local baselines and verification reports without editing source, approving advisories or consuming pending completion notices. Returns concise results with a full report path. Works without a map.",
10054
10224
  {
10055
- dir: z25.string().describe("Absolute path to the project directory"),
10056
- action: z25.enum(["status", "check"]).describe("Inspect configuration and receipts, or capture/resume and verify original audit evidence.")
10225
+ dir: z26.string().describe("Absolute path to the project directory"),
10226
+ action: z26.enum(["status", "check"]).describe("Inspect configuration and receipts, or capture/resume and verify original audit evidence.")
10057
10227
  },
10058
10228
  async ({ dir, action }) => ({ content: [{ type: "text", text: await masonAutomation(dir, action) }] })
10059
10229
  );
@@ -10061,10 +10231,10 @@ function createMcpServer() {
10061
10231
  "mason_repair",
10062
10232
  "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.",
10063
10233
  {
10064
- dir: z25.string().describe("Absolute path to the project root directory"),
10065
- action: z25.enum(["prepare", "verify"]),
10066
- baselinePath: z25.string().optional().describe("Original baseline path returned by prepare; required for verify."),
10067
- checks: z25.array(z25.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.")
10234
+ dir: z26.string().describe("Absolute path to the project root directory"),
10235
+ action: z26.enum(["prepare", "verify"]),
10236
+ baselinePath: z26.string().optional().describe("Original baseline path returned by prepare; required for verify."),
10237
+ checks: z26.array(z26.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.")
10068
10238
  },
10069
10239
  async ({ dir, action, baselinePath, checks }) => {
10070
10240
  const result = await masonRepair(dir, { action, baselinePath, checks });
@@ -10074,15 +10244,15 @@ function createMcpServer() {
10074
10244
  server.tool(
10075
10245
  "review_advisory",
10076
10246
  "Prepare an assessment of an original repair advisory and its exact repository scope, then record authorized addressed, inapplicable or deferred outcomes with reviewToken, reviewer and note. Relevant changes reopen reviews; unrelated commits preserve them. Records belong in Git. Decision findings route to review_decision; this tool never approves decisions. Recorded identities are assertions, not authenticated approvals. For dependency advisories, action dismiss with reasonCode and note records a one-step dismissal without a token or reviewer. no-dependency-claims reopens only on document content changes; unrelated-manifest-change also tracks scoped manifests.",
10077
- { dir: z25.string().describe("Absolute path to the project root"), ...advisoryReviewRequest.shape },
10247
+ { dir: z26.string().describe("Absolute path to the project root"), ...advisoryReviewRequest.shape },
10078
10248
  async ({ dir, ...request }) => ({ content: [{ type: "text", text: await masonReviewAdvisory(dir, request) }] })
10079
10249
  );
10080
10250
  server.tool(
10081
10251
  "mason_complete_init",
10082
10252
  "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.",
10083
10253
  {
10084
- dir: z25.string().describe("Absolute path to the project root directory"),
10085
- confluenceConfigured: z25.boolean().optional().describe("Set the Confluence setup status; omit to preserve the existing value")
10254
+ dir: z26.string().describe("Absolute path to the project root directory"),
10255
+ confluenceConfigured: z26.boolean().optional().describe("Set the Confluence setup status; omit to preserve the existing value")
10086
10256
  },
10087
10257
  async ({ dir, confluenceConfigured }) => {
10088
10258
  const result = await masonCompleteInit(dir, { confluenceConfigured });
@@ -10093,11 +10263,11 @@ function createMcpServer() {
10093
10263
  "mason_set_confluence",
10094
10264
  "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.",
10095
10265
  {
10096
- baseUrl: z25.string().describe("Confluence base URL. Accepts `acme`, `acme.atlassian.net`, or `https://acme.atlassian.net` (normalized automatically)."),
10097
- email: z25.string().describe("User's Atlassian account email"),
10098
- apiToken: z25.string().describe("API token from id.atlassian.com/manage-profile/security/api-tokens"),
10099
- spaceKey: z25.string().optional().describe("Confluence space key. Omit on the first call to list available spaces."),
10100
- parentPageId: z25.string().optional().describe("Optional parent page ID under which Mason's index page is created")
10266
+ baseUrl: z26.string().describe("Confluence base URL. Accepts `acme`, `acme.atlassian.net`, or `https://acme.atlassian.net` (normalized automatically)."),
10267
+ email: z26.string().describe("User's Atlassian account email"),
10268
+ apiToken: z26.string().describe("API token from id.atlassian.com/manage-profile/security/api-tokens"),
10269
+ spaceKey: z26.string().optional().describe("Confluence space key. Omit on the first call to list available spaces."),
10270
+ parentPageId: z26.string().optional().describe("Optional parent page ID under which Mason's index page is created")
10101
10271
  },
10102
10272
  async ({ baseUrl, email, apiToken, spaceKey, parentPageId }) => {
10103
10273
  const result = await masonSetConfluence({
@@ -10114,7 +10284,7 @@ function createMcpServer() {
10114
10284
  "full_analysis",
10115
10285
  "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.",
10116
10286
  {
10117
- dir: z25.string().describe("Absolute path to the project root directory")
10287
+ dir: z26.string().describe("Absolute path to the project root directory")
10118
10288
  },
10119
10289
  async ({ dir }) => {
10120
10290
  const result = await fullAnalysis(dir);
@@ -10127,7 +10297,7 @@ function createMcpServer() {
10127
10297
  "analyze_project",
10128
10298
  "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.",
10129
10299
  {
10130
- dir: z25.string().describe("Absolute path to the project root directory")
10300
+ dir: z26.string().describe("Absolute path to the project root directory")
10131
10301
  },
10132
10302
  async ({ dir }) => {
10133
10303
  const result = await analyzeProject(dir);
@@ -10140,8 +10310,8 @@ function createMcpServer() {
10140
10310
  "get_code_samples",
10141
10311
  "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.",
10142
10312
  {
10143
- dir: z25.string().describe("Absolute path to the project root directory"),
10144
- count: z25.number().optional().default(15).describe("Maximum number of files to sample (default: 15)")
10313
+ dir: z26.string().describe("Absolute path to the project root directory"),
10314
+ count: z26.number().optional().default(15).describe("Maximum number of files to sample (default: 15)")
10145
10315
  },
10146
10316
  async ({ dir, count: count4 }) => {
10147
10317
  const result = await getCodeSamples(dir, count4);
@@ -10154,7 +10324,7 @@ function createMcpServer() {
10154
10324
  "get_snapshot",
10155
10325
  "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.",
10156
10326
  {
10157
- dir: z25.string().describe("Absolute path to the project root directory")
10327
+ dir: z26.string().describe("Absolute path to the project root directory")
10158
10328
  },
10159
10329
  async ({ dir }) => {
10160
10330
  const result = await getSnapshot(dir);
@@ -10167,9 +10337,9 @@ function createMcpServer() {
10167
10337
  "get_context",
10168
10338
  "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.",
10169
10339
  {
10170
- dir: z25.string().describe("Absolute path to the project root directory"),
10171
- task: z25.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"),
10172
- files: z25.array(z25.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.")
10340
+ dir: z26.string().describe("Absolute path to the project root directory"),
10341
+ task: z26.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"),
10342
+ files: z26.array(z26.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.")
10173
10343
  },
10174
10344
  async ({ dir, task, files }) => {
10175
10345
  const result = await getContext(dir, task, files);
@@ -10185,10 +10355,10 @@ function createMcpServer() {
10185
10355
  "generate_snapshot_batch",
10186
10356
  "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.",
10187
10357
  {
10188
- dir: z25.string().describe("Absolute path to the project root directory"),
10189
- offset: z25.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."),
10190
- batchSize: z25.number().int().optional().describe("Files per batch. Defaults to 50."),
10191
- files: z25.array(z25.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.")
10358
+ dir: z26.string().describe("Absolute path to the project root directory"),
10359
+ offset: z26.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."),
10360
+ batchSize: z26.number().int().optional().describe("Files per batch. Defaults to 50."),
10361
+ files: z26.array(z26.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.")
10192
10362
  },
10193
10363
  async ({ dir, offset, batchSize, files }) => {
10194
10364
  const result = await generateSnapshotBatch(dir, offset, batchSize, files);
@@ -10201,23 +10371,23 @@ function createMcpServer() {
10201
10371
  "save_partial_snapshot",
10202
10372
  "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.",
10203
10373
  {
10204
- dir: z25.string().describe("Absolute path to the project root directory"),
10205
- batchId: z25.string().describe("The `batchId` returned by `generate_snapshot_batch`."),
10206
- offset: z25.number().int().describe("The `offset` returned by `generate_snapshot_batch`. Used to order partials in the reduce step."),
10207
- features: z25.record(
10208
- z25.object({
10209
- description: z25.string(),
10210
- files: z25.array(z25.string()),
10211
- tests: z25.array(z25.string()).optional(),
10212
- type: z25.enum(["capability", "infrastructure"]).optional().describe(
10374
+ dir: z26.string().describe("Absolute path to the project root directory"),
10375
+ batchId: z26.string().describe("The `batchId` returned by `generate_snapshot_batch`."),
10376
+ offset: z26.number().int().describe("The `offset` returned by `generate_snapshot_batch`. Used to order partials in the reduce step."),
10377
+ features: z26.record(
10378
+ z26.object({
10379
+ description: z26.string(),
10380
+ files: z26.array(z26.string()),
10381
+ tests: z26.array(z26.string()).optional(),
10382
+ type: z26.enum(["capability", "infrastructure"]).optional().describe(
10213
10383
  '"capability" (user-facing functionality) or "infrastructure" (internal plumbing with no end user \u2014 DI/service wiring, config, logging, adapters). Defaults to "capability".'
10214
10384
  )
10215
10385
  })
10216
10386
  ).describe("Partial features for this batch only \u2014 files outside the batch will be added by other partials."),
10217
- flows: z25.record(
10218
- z25.object({
10219
- description: z25.string(),
10220
- chain: z25.array(z25.string())
10387
+ flows: z26.record(
10388
+ z26.object({
10389
+ description: z26.string(),
10390
+ chain: z26.array(z26.string())
10221
10391
  })
10222
10392
  ).describe("Partial flows whose entire chain is in this batch. Cross-batch flows are reconstructed in reduce.")
10223
10393
  },
@@ -10232,7 +10402,7 @@ function createMcpServer() {
10232
10402
  "reduce_snapshot",
10233
10403
  "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).",
10234
10404
  {
10235
- dir: z25.string().describe("Absolute path to the project root directory")
10405
+ dir: z26.string().describe("Absolute path to the project root directory")
10236
10406
  },
10237
10407
  async ({ dir }) => {
10238
10408
  const result = await reduceSnapshot(dir);
@@ -10245,25 +10415,25 @@ function createMcpServer() {
10245
10415
  "save_snapshot",
10246
10416
  "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.",
10247
10417
  {
10248
- dir: z25.string().describe("Absolute path to the project root directory"),
10249
- features: z25.record(
10250
- z25.object({
10251
- description: z25.string().describe("One-line description of the feature"),
10252
- files: z25.array(z25.string()).describe("File paths that implement this feature"),
10253
- tests: z25.array(z25.string()).optional().describe("Test file paths for this feature"),
10254
- type: z25.enum(["capability", "infrastructure"]).optional().describe(
10418
+ dir: z26.string().describe("Absolute path to the project root directory"),
10419
+ features: z26.record(
10420
+ z26.object({
10421
+ description: z26.string().describe("One-line description of the feature"),
10422
+ files: z26.array(z26.string()).describe("File paths that implement this feature"),
10423
+ tests: z26.array(z26.string()).optional().describe("Test file paths for this feature"),
10424
+ type: z26.enum(["capability", "infrastructure"]).optional().describe(
10255
10425
  '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".'
10256
10426
  )
10257
10427
  })
10258
10428
  ).describe("Map of feature names to their implementing files"),
10259
- flows: z25.record(
10260
- z25.object({
10261
- description: z25.string().describe("One-line description of the flow"),
10262
- chain: z25.array(z25.string()).describe("Ordered list of file paths showing data/call flow")
10429
+ flows: z26.record(
10430
+ z26.object({
10431
+ description: z26.string().describe("One-line description of the flow"),
10432
+ chain: z26.array(z26.string()).describe("Ordered list of file paths showing data/call flow")
10263
10433
  })
10264
10434
  ).describe("Map of flow names to ordered file chains"),
10265
- removeFeatures: z25.array(z25.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."),
10266
- removeFlows: z25.array(z25.string()).optional().describe("Flow names to delete from the existing map. Applied before merging; only meaningful on incremental saves.")
10435
+ removeFeatures: z26.array(z26.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."),
10436
+ removeFlows: z26.array(z26.string()).optional().describe("Flow names to delete from the existing map. Applied before merging; only meaningful on incremental saves.")
10267
10437
  },
10268
10438
  async ({ dir, features, flows, removeFeatures, removeFlows }) => {
10269
10439
  const result = await saveSnapshotData(
@@ -10282,17 +10452,17 @@ function createMcpServer() {
10282
10452
  "save_decision",
10283
10453
  "Capture or revise a decision proposal as soon as the lesson is supported by evidence, without waiting for implementation or merge. Keep unimplemented remedies tentative. Include rationale, anchors, optional owner, sources, and a known actor. Compare matching get_context records and pending proposals before saving. When new evidence changes the same lesson's assumptions, scope, or recommended action, pass its existing id, including for accepted records. Preserve supported rationale and sources and replace obsolete instructions. Create a new record for a genuinely distinct lesson; skip unchanged restatements. 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.",
10284
10454
  {
10285
- dir: z25.string().describe("Absolute path to the project root directory"),
10455
+ dir: z26.string().describe("Absolute path to the project root directory"),
10286
10456
  title: decisionTitleSchema.describe(`Short, specific headline (max ${TITLE_MAX_CHARS} characters). Generates a stable id on creation; revising the title keeps the existing id.`),
10287
10457
  body: decisionBodySchema.describe(`The knowledge itself: what was tried/decided, why, and what to avoid. Must contain information NOT derivable by reading the code. Aim for ${BODY_RECOMMENDED_CHARS} characters or fewer; up to ${BODY_MAX_CHARS} is accepted with a warning above the target. Matching context and hooks include the full body. Preserve exceptions; use sources for supporting references.`),
10288
- category: z25.enum(["decision", "gotcha", "deprecation", "convention"]),
10289
- files: z25.array(z25.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."),
10290
- id: z25.string().optional().describe("Pass the matching record's existing id when evidence changes that lesson's assumptions, scope, or recommended action, including for accepted records. Changed content becomes a proposal while the accepted version remains operative; unchanged content leaves review and freshness untouched."),
10291
- supersedes: z25.string().optional().describe("Id of an unreviewed record or proposal with no accepted revision to replace. Operative accepted decisions require separate review and retirement."),
10458
+ category: z26.enum(["decision", "gotcha", "deprecation", "convention"]),
10459
+ files: z26.array(z26.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."),
10460
+ id: z26.string().optional().describe("Pass the matching record's existing id when evidence changes that lesson's assumptions, scope, or recommended action, including for accepted records. Changed content becomes a proposal while the accepted version remains operative; unchanged content leaves review and freshness untouched."),
10461
+ supersedes: z26.string().optional().describe("Id of an unreviewed record or proposal with no accepted revision to replace. Operative accepted decisions require separate review and retirement."),
10292
10462
  owner: attributionSchema.shape.owner.describe("Responsible person or team, when known. Null clears it. Required for acceptance."),
10293
10463
  sources: attributionSchema.shape.sources.describe("Known PR, issue, incident, discussion, or document references. Omit to preserve; [] clears. At least one is required for acceptance."),
10294
10464
  actor: attributionSchema.shape.actor.describe("Known person or agent recording this revision. Omit if unknown; do not infer from Git identity."),
10295
- force: z25.boolean().optional().describe("Save even when a near-duplicate was detected")
10465
+ force: z26.boolean().optional().describe("Save even when a near-duplicate was detected")
10296
10466
  },
10297
10467
  async ({ dir, title, body, category, files, id, supersedes, force, owner, sources, actor }) => {
10298
10468
  const result = await saveDecision(dir, {
@@ -10314,12 +10484,12 @@ function createMcpServer() {
10314
10484
  "review_decision",
10315
10485
  "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.",
10316
10486
  {
10317
- dir: z25.string().describe("Absolute path to the project root directory"),
10318
- id: z25.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Decision id from get_context or save_decision"),
10319
- action: z25.enum(["prepare", "accept", "reaffirm", "retire"]).optional().default("prepare").describe("Prepare is read-only. Other actions record an explicitly authorized review."),
10320
- reviewer: z25.string().trim().min(1).max(200).optional().describe("Identity of the actual reviewer; required for a verdict. Never invent one."),
10321
- note: z25.string().trim().min(1).max(1500).optional().describe("Review rationale; required for a verdict. Cite evidence for the decision."),
10322
- reviewToken: z25.string().regex(/^[a-f0-9]{64}$/).optional().describe("Token from the prepared review; rejects stale record or code revisions")
10487
+ dir: z26.string().describe("Absolute path to the project root directory"),
10488
+ id: z26.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Decision id from get_context or save_decision"),
10489
+ action: z26.enum(["prepare", "accept", "reaffirm", "retire"]).optional().default("prepare").describe("Prepare is read-only. Other actions record an explicitly authorized review."),
10490
+ reviewer: z26.string().trim().min(1).max(200).optional().describe("Identity of the actual reviewer; required for a verdict. Never invent one."),
10491
+ note: z26.string().trim().min(1).max(1500).optional().describe("Review rationale; required for a verdict. Cite evidence for the decision."),
10492
+ reviewToken: z26.string().regex(/^[a-f0-9]{64}$/).optional().describe("Token from the prepared review; rejects stale record or code revisions")
10323
10493
  },
10324
10494
  async ({ dir, ...input }) => {
10325
10495
  const result = await reviewDecision2(dir, input);
@@ -10330,7 +10500,7 @@ function createMcpServer() {
10330
10500
  "mason_check_drift",
10331
10501
  "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.",
10332
10502
  {
10333
- dir: z25.string().describe("Absolute path to the project root directory")
10503
+ dir: z26.string().describe("Absolute path to the project root directory")
10334
10504
  },
10335
10505
  async ({ dir }) => {
10336
10506
  const result = await checkDrift(dir);
@@ -10343,8 +10513,8 @@ function createMcpServer() {
10343
10513
  "verify_snapshot",
10344
10514
  "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.",
10345
10515
  {
10346
- dir: z25.string().describe("Absolute path to the project root directory"),
10347
- sample: z25.number().int().optional().describe("Entries to sample (default 5)")
10516
+ dir: z26.string().describe("Absolute path to the project root directory"),
10517
+ sample: z26.number().int().optional().describe("Entries to sample (default 5)")
10348
10518
  },
10349
10519
  async ({ dir, sample }) => {
10350
10520
  const result = await verifySnapshot(dir, sample);
@@ -10355,13 +10525,13 @@ function createMcpServer() {
10355
10525
  "save_verification",
10356
10526
  "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.",
10357
10527
  {
10358
- dir: z25.string().describe("Absolute path to the project root directory"),
10359
- verdicts: z25.record(
10360
- z25.object({
10361
- kind: z25.enum(["feature", "flow"]).optional().describe("Entry kind returned by verify_snapshot; required to record a verdict"),
10362
- reviewToken: z25.string().optional().describe("Copy the reviewToken from verify_snapshot after inspecting its evidence; required to record a verdict"),
10363
- ok: z25.boolean(),
10364
- note: z25.string().optional().describe("One line on what's wrong \u2014 required when ok is false")
10528
+ dir: z26.string().describe("Absolute path to the project root directory"),
10529
+ verdicts: z26.record(
10530
+ z26.object({
10531
+ kind: z26.enum(["feature", "flow"]).optional().describe("Entry kind returned by verify_snapshot; required to record a verdict"),
10532
+ reviewToken: z26.string().optional().describe("Copy the reviewToken from verify_snapshot after inspecting its evidence; required to record a verdict"),
10533
+ ok: z26.boolean(),
10534
+ note: z26.string().optional().describe("One line on what's wrong \u2014 required when ok is false")
10365
10535
  })
10366
10536
  ).describe("Entry name \u2192 verdict, exactly as returned by verify_snapshot")
10367
10537
  },
@@ -10374,8 +10544,8 @@ function createMcpServer() {
10374
10544
  "get_impact",
10375
10545
  "Trace historical co-change partners, related tests, and references with evidence: resolved imports, explicit paths, or uncertain name candidates. Candidates are not proven dependencies. Deterministic and read-only; no initialization or concept map required.",
10376
10546
  {
10377
- dir: z25.string().describe("Absolute path to the project root directory"),
10378
- files: z25.array(z25.string()).describe("File paths or names to analyze (e.g., ['WeatherRepository.kt'] or ['src/services/auth.ts'])")
10547
+ dir: z26.string().describe("Absolute path to the project root directory"),
10548
+ files: z26.array(z26.string()).describe("File paths or names to analyze (e.g., ['WeatherRepository.kt'] or ['src/services/auth.ts'])")
10379
10549
  },
10380
10550
  async ({ dir, files }) => {
10381
10551
  const result = await getImpact(dir, files);
@@ -10388,12 +10558,12 @@ function createMcpServer() {
10388
10558
  "export_to_confluence",
10389
10559
  "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.",
10390
10560
  {
10391
- dir: z25.string().describe("Absolute path to the project root directory"),
10392
- spaceKey: z25.string().optional().describe("Override the configured space key"),
10393
- parentPageId: z25.string().optional().describe("Override the configured parent page ID"),
10394
- indexPageTitle: z25.string().optional().describe("Title of the index page (default: 'Mason \u2014 System Map')"),
10395
- changelogPageTitle: z25.string().optional().describe("Title of the changelog page (default: 'Mason \u2014 Changelog')"),
10396
- featurePagePrefix: z25.string().optional().describe("Prefix for each feature page title (default: 'Feature: ')")
10561
+ dir: z26.string().describe("Absolute path to the project root directory"),
10562
+ spaceKey: z26.string().optional().describe("Override the configured space key"),
10563
+ parentPageId: z26.string().optional().describe("Override the configured parent page ID"),
10564
+ indexPageTitle: z26.string().optional().describe("Title of the index page (default: 'Mason \u2014 System Map')"),
10565
+ changelogPageTitle: z26.string().optional().describe("Title of the changelog page (default: 'Mason \u2014 Changelog')"),
10566
+ featurePagePrefix: z26.string().optional().describe("Prefix for each feature page title (default: 'Feature: ')")
10397
10567
  },
10398
10568
  async ({ dir, spaceKey, parentPageId, indexPageTitle, changelogPageTitle, featurePagePrefix }) => {
10399
10569
  const result = await exportToConfluenceTool(dir, {
@@ -10418,32 +10588,32 @@ async function startMcpServer(onClose) {
10418
10588
  // src/distribution/updates.ts
10419
10589
  init_store();
10420
10590
  init_storage();
10421
- import fs33 from "fs/promises";
10591
+ import fs34 from "fs/promises";
10422
10592
  import path42 from "path";
10423
10593
  import { spawn as spawn3 } from "child_process";
10424
10594
  import { randomUUID as randomUUID11 } from "crypto";
10425
- import { z as z31 } from "zod";
10595
+ import { z as z32 } from "zod";
10426
10596
 
10427
10597
  // src/distribution/state.ts
10428
10598
  init_storage();
10429
10599
  import os5 from "os";
10430
10600
  import path38 from "path";
10431
10601
  import { fileURLToPath } from "url";
10432
- import fs28 from "fs/promises";
10433
- import { z as z27 } from "zod";
10602
+ import fs29 from "fs/promises";
10603
+ import { z as z28 } from "zod";
10434
10604
 
10435
10605
  // src/distribution/path.ts
10436
- import fs27 from "fs/promises";
10606
+ import fs28 from "fs/promises";
10437
10607
  import os4 from "os";
10438
10608
  import path37 from "path";
10439
10609
  import { execFile as execFile12 } from "child_process";
10440
10610
  import { promisify as promisify12 } from "util";
10441
10611
  import { createHash as createHash7, randomUUID as randomUUID7 } from "crypto";
10442
- import { z as z26 } from "zod";
10612
+ import { z as z27 } from "zod";
10443
10613
  var execute2 = promisify12(execFile12);
10444
- var pathChangeSchema = z26.discriminatedUnion("kind", [
10445
- z26.object({ kind: z26.literal("profile"), file: z26.string(), block: z26.string(), created: z26.boolean() }),
10446
- z26.object({ kind: z26.literal("windows"), entry: z26.string(), created: z26.boolean() })
10614
+ var pathChangeSchema = z27.discriminatedUnion("kind", [
10615
+ z27.object({ kind: z27.literal("profile"), file: z27.string(), block: z27.string(), created: z27.boolean() }),
10616
+ z27.object({ kind: z27.literal("windows"), entry: z27.string(), created: z27.boolean() })
10447
10617
  ]);
10448
10618
  var quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
10449
10619
  async function configurePath(bin, previous, save, options = {}) {
@@ -10480,7 +10650,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
10480
10650
  else {
10481
10651
  const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path37.join(userHome, name));
10482
10652
  let login = candidates[2];
10483
- for (const file of candidates) if (await fs27.lstat(file).then(() => true, (error) => {
10653
+ for (const file of candidates) if (await fs28.lstat(file).then(() => true, (error) => {
10484
10654
  if (error.code === "ENOENT") return false;
10485
10655
  throw error;
10486
10656
  })) {
@@ -10491,12 +10661,12 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
10491
10661
  }
10492
10662
  const marker = `# >>> mason PATH ${createHash7("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
10493
10663
  for (const profile of profiles2) {
10494
- const stat = await fs27.lstat(profile).catch((error) => {
10664
+ const stat = await fs28.lstat(profile).catch((error) => {
10495
10665
  if (error.code === "ENOENT") return null;
10496
10666
  throw error;
10497
10667
  });
10498
- const file = stat?.isSymbolicLink() ? await fs27.realpath(profile) : path37.resolve(profile);
10499
- const original = await fs27.readFile(file).catch((error) => {
10668
+ const file = stat?.isSymbolicLink() ? await fs28.realpath(profile) : path37.resolve(profile);
10669
+ const original = await fs28.readFile(file).catch((error) => {
10500
10670
  if (error.code === "ENOENT" && !stat) return null;
10501
10671
  throw error;
10502
10672
  });
@@ -10511,8 +10681,8 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
10511
10681
  changes.push({ kind: "profile", file, block, created: original === null });
10512
10682
  await save(changes);
10513
10683
  }
10514
- await fs27.mkdir(path37.dirname(file), { recursive: true });
10515
- const handle = await fs27.open(file, original === null ? "ax+" : "a+", 384);
10684
+ await fs28.mkdir(path37.dirname(file), { recursive: true });
10685
+ const handle = await fs28.open(file, original === null ? "ax+" : "a+", 384);
10516
10686
  try {
10517
10687
  if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
10518
10688
  await handle.writeFile(block);
@@ -10563,32 +10733,32 @@ try {
10563
10733
  }
10564
10734
 
10565
10735
  // src/distribution/state.ts
10566
- var releaseVersion = z27.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
10567
- var bundleId = z27.string().regex(/^[a-f0-9]{24}$/);
10568
- var updatePolicySchema = z27.object({
10569
- enabled: z27.boolean(),
10570
- revision: z27.string(),
10736
+ var releaseVersion = z28.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
10737
+ var bundleId = z28.string().regex(/^[a-f0-9]{24}$/);
10738
+ var updatePolicySchema = z28.object({
10739
+ enabled: z28.boolean(),
10740
+ revision: z28.string(),
10571
10741
  pinnedVersion: releaseVersion.optional(),
10572
- mirror: z27.string().optional()
10742
+ mirror: z28.string().optional()
10573
10743
  });
10574
- var recordSchema3 = z27.object({
10575
- format: z27.literal(1),
10576
- home: z27.string(),
10577
- bin: z27.string(),
10744
+ var recordSchema3 = z28.object({
10745
+ format: z28.literal(1),
10746
+ home: z28.string(),
10747
+ bin: z28.string(),
10578
10748
  current: bundleId,
10579
10749
  version: releaseVersion,
10580
- launchers: z27.record(z27.string()),
10581
- previousLaunchers: z27.record(z27.string()).optional(),
10582
- pathChanges: z27.array(pathChangeSchema).optional(),
10750
+ launchers: z28.record(z28.string()),
10751
+ previousLaunchers: z28.record(z28.string()).optional(),
10752
+ pathChanges: z28.array(pathChangeSchema).optional(),
10583
10753
  updates: updatePolicySchema.optional(),
10584
- previous: z27.object({ id: bundleId, version: releaseVersion }).optional(),
10585
- pending: z27.object({ id: bundleId, version: releaseVersion, policyRevision: z27.string() }).optional()
10754
+ previous: z28.object({ id: bundleId, version: releaseVersion }).optional(),
10755
+ pending: z28.object({ id: bundleId, version: releaseVersion, policyRevision: z28.string() }).optional()
10586
10756
  });
10587
10757
  async function readInstallation(home) {
10588
10758
  const raw = await readStoreJson(home, "install.json");
10589
10759
  if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
10590
10760
  const record = recordSchema3.parse(raw);
10591
- if (record.home !== await fs28.realpath(home)) throw new Error("Invalid standalone installation receipt.");
10761
+ if (record.home !== await fs29.realpath(home)) throw new Error("Invalid standalone installation receipt.");
10592
10762
  return record;
10593
10763
  }
10594
10764
  function standaloneLocation() {
@@ -10620,22 +10790,22 @@ function newerStableVersion(candidate, installed) {
10620
10790
 
10621
10791
  // src/distribution/bundle.ts
10622
10792
  init_storage();
10623
- import fs29 from "fs/promises";
10793
+ import fs30 from "fs/promises";
10624
10794
  import path39 from "path";
10625
10795
  import { createHash as createHash8 } from "crypto";
10626
- import { z as z28 } from "zod";
10796
+ import { z as z29 } from "zod";
10627
10797
  var targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
10628
- var digest2 = z28.string().regex(/^[a-f0-9]{64}$/);
10629
- var bundleSchema = z28.object({
10630
- format: z28.literal(1),
10631
- version: z28.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
10632
- target: z28.enum(targets),
10633
- nodeVersion: z28.string(),
10634
- files: z28.record(digest2)
10798
+ var digest2 = z29.string().regex(/^[a-f0-9]{64}$/);
10799
+ var bundleSchema = z29.object({
10800
+ format: z29.literal(1),
10801
+ version: z29.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
10802
+ target: z29.enum(targets),
10803
+ nodeVersion: z29.string(),
10804
+ files: z29.record(digest2)
10635
10805
  }).strict();
10636
10806
  var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
10637
10807
  async function readBundle(root) {
10638
- const bytes = await fs29.readFile(await storePath(root, "bundle.json"));
10808
+ const bytes = await fs30.readFile(await storePath(root, "bundle.json"));
10639
10809
  const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
10640
10810
  const windows = manifest.target.startsWith("win32-");
10641
10811
  for (const file of Object.keys(manifest.files)) {
@@ -10643,7 +10813,7 @@ async function readBundle(root) {
10643
10813
  throw new Error("Unsafe path in Mason bundle: " + file);
10644
10814
  }
10645
10815
  }
10646
- 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"]) {
10816
+ for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-mcp.js"]) {
10647
10817
  if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
10648
10818
  }
10649
10819
  return { manifest, manifestHash: sha256(bytes) };
@@ -10654,7 +10824,7 @@ async function verifyBundle(root, expectedHash) {
10654
10824
  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.");
10655
10825
  const files = Object.keys(bundle.manifest.files);
10656
10826
  for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
10657
- if (sha256(await fs29.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
10827
+ if (sha256(await fs30.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
10658
10828
  }));
10659
10829
  return bundle;
10660
10830
  }
@@ -10662,21 +10832,21 @@ async function verifyBundle(root, expectedHash) {
10662
10832
  // src/distribution/runtime.ts
10663
10833
  init_store();
10664
10834
  init_storage();
10665
- import fs30 from "fs/promises";
10835
+ import fs31 from "fs/promises";
10666
10836
  import { unlinkSync } from "fs";
10667
10837
  import os6 from "os";
10668
10838
  import path40 from "path";
10669
10839
  import { randomUUID as randomUUID8 } from "crypto";
10670
- import { z as z29 } from "zod";
10840
+ import { z as z30 } from "zod";
10671
10841
 
10672
10842
  // src/distribution/trust.ts
10673
10843
  import tls from "tls";
10674
10844
 
10675
10845
  // src/distribution/runtime.ts
10676
- var leaseSchema = z29.object({ pid: z29.number().int().positive(), host: z29.string(), bundle: z29.string().regex(/^[a-f0-9]{24}$/) });
10846
+ var leaseSchema = z30.object({ pid: z30.number().int().positive(), host: z30.string(), bundle: z30.string().regex(/^[a-f0-9]{24}$/) });
10677
10847
 
10678
10848
  // src/distribution/install.ts
10679
- import fs32 from "fs/promises";
10849
+ import fs33 from "fs/promises";
10680
10850
  import os8 from "os";
10681
10851
  import path41 from "path";
10682
10852
  import { execFile as execFile13, spawn as spawn2 } from "child_process";
@@ -10687,14 +10857,14 @@ init_store();
10687
10857
 
10688
10858
  // src/distribution/sessions.ts
10689
10859
  init_storage();
10690
- import fs31 from "fs/promises";
10860
+ import fs32 from "fs/promises";
10691
10861
  import os7 from "os";
10692
10862
  import { randomUUID as randomUUID9 } from "crypto";
10693
- import { z as z30 } from "zod";
10694
- var leaseSchema2 = z30.object({ pid: z30.number().int().positive(), host: z30.string() });
10863
+ import { z as z31 } from "zod";
10864
+ var leaseSchema2 = z31.object({ pid: z31.number().int().positive(), host: z31.string() });
10695
10865
  async function hasRunningMcp(home) {
10696
- const directory = await storePath(home, "sessions");
10697
- const files = await fs31.readdir(directory).catch((error) => {
10866
+ const directory2 = await storePath(home, "sessions");
10867
+ const files = await fs32.readdir(directory2).catch((error) => {
10698
10868
  if (error.code === "ENOENT") return [];
10699
10869
  throw error;
10700
10870
  });
@@ -10712,7 +10882,7 @@ async function hasRunningMcp(home) {
10712
10882
  } catch (error) {
10713
10883
  if (error.code !== "ESRCH") return true;
10714
10884
  }
10715
- await fs31.rm(await storePath(home, relative), { force: true });
10885
+ await fs32.rm(await storePath(home, relative), { force: true });
10716
10886
  } catch {
10717
10887
  return true;
10718
10888
  }
@@ -10723,7 +10893,7 @@ async function registerMcp(home) {
10723
10893
  const name = `sessions/${randomUUID9()}.json`;
10724
10894
  await writeStoreJson(home, name, { pid: process.pid, host: os7.hostname() });
10725
10895
  return async () => {
10726
- await fs31.rm(await storePath(home, name), { force: true }).catch(() => {
10896
+ await fs32.rm(await storePath(home, name), { force: true }).catch(() => {
10727
10897
  });
10728
10898
  };
10729
10899
  }
@@ -10736,10 +10906,10 @@ var launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
10736
10906
  async function replace(file, content2, mode = 384) {
10737
10907
  const temp = file + ".tmp-" + randomUUID10();
10738
10908
  try {
10739
- await fs32.writeFile(temp, content2, { mode, flag: "wx" });
10740
- await fs32.rename(temp, file);
10909
+ await fs33.writeFile(temp, content2, { mode, flag: "wx" });
10910
+ await fs33.rename(temp, file);
10741
10911
  } finally {
10742
- await fs32.rm(temp, { force: true });
10912
+ await fs33.rm(temp, { force: true });
10743
10913
  }
10744
10914
  }
10745
10915
  function assertUpdatePolicy(record, version, revision) {
@@ -10751,17 +10921,17 @@ async function installLocked2(home, source, bundle, progress, options, alreadySt
10751
10921
  const raw = await readStoreJson(home, "install.json");
10752
10922
  const previous = raw === null ? null : recordSchema3.parse(raw);
10753
10923
  if (options.stageOnly) assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
10754
- if (!previous) for (const file of await fs32.readdir(home)) {
10924
+ if (!previous) for (const file of await fs33.readdir(home)) {
10755
10925
  if (file === ".install-lock") continue;
10756
- const directory = path41.join(home, file);
10757
- const stat = await fs32.lstat(directory);
10758
- if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs32.readdir(directory)).length) {
10926
+ const directory2 = path41.join(home, file);
10927
+ const stat = await fs33.lstat(directory2);
10928
+ if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs33.readdir(directory2)).length) {
10759
10929
  throw new Error("Mason installation directory is not empty and has no ownership receipt.");
10760
10930
  }
10761
10931
  }
10762
10932
  const selectedBin = path41.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path41.join(home, "bin") : path41.join(os8.homedir(), ".local/bin")));
10763
- await fs32.mkdir(selectedBin, { recursive: true });
10764
- const bin = await fs32.realpath(selectedBin);
10933
+ await fs33.mkdir(selectedBin, { recursive: true });
10934
+ const bin = await fs33.realpath(selectedBin);
10765
10935
  if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
10766
10936
  const id = bundle.manifestHash.slice(0, 24);
10767
10937
  const destination = await storePath(home, "versions/" + id);
@@ -10793,7 +10963,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
10793
10963
  ` };
10794
10964
  for (const [name, content2] of Object.entries(launchers)) {
10795
10965
  const file = await storePath(bin, name);
10796
- const old = await fs32.readFile(file, "utf8").catch((error) => {
10966
+ const old = await fs33.readFile(file, "utf8").catch((error) => {
10797
10967
  if (error.code === "ENOENT") return null;
10798
10968
  throw error;
10799
10969
  });
@@ -10802,7 +10972,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
10802
10972
  const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
10803
10973
  const checkRetired = async (name, expected) => {
10804
10974
  if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
10805
- const actual = await fs32.readFile(await storePath(bin, name), "utf8").catch((error) => {
10975
+ const actual = await fs33.readFile(await storePath(bin, name), "utf8").catch((error) => {
10806
10976
  if (error.code === "ENOENT") return null;
10807
10977
  throw error;
10808
10978
  });
@@ -10837,7 +11007,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
10837
11007
  for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
10838
11008
  for (const [name, expected] of retired) {
10839
11009
  await checkRetired(name, expected);
10840
- await fs32.rm(await storePath(bin, name), { force: true });
11010
+ await fs33.rm(await storePath(bin, name), { force: true });
10841
11011
  }
10842
11012
  await writeStoreJson(home, "install.json", receipt);
10843
11013
  } catch (error) {
@@ -10856,7 +11026,7 @@ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destinat
10856
11026
  return { home, bin, version: bundle.manifest.version, previousVersion: previous?.version, path: pathResult };
10857
11027
  }
10858
11028
  async function copyBundle(home, source, destination, bundle) {
10859
- const exists = await fs32.lstat(destination).then(() => true, (error) => {
11029
+ const exists = await fs33.lstat(destination).then(() => true, (error) => {
10860
11030
  if (error.code === "ENOENT") return false;
10861
11031
  throw error;
10862
11032
  });
@@ -10865,16 +11035,16 @@ async function copyBundle(home, source, destination, bundle) {
10865
11035
  return;
10866
11036
  }
10867
11037
  const stage = await storePath(home, "versions/.install-" + randomUUID10(), true);
10868
- await fs32.mkdir(stage);
11038
+ await fs33.mkdir(stage);
10869
11039
  try {
10870
11040
  for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
10871
11041
  const from = await storePath(source, file), to = await storePath(stage, file, true);
10872
- await fs32.copyFile(from, to);
10873
- await fs32.chmod(to, (await fs32.stat(from)).mode & 511);
11042
+ await fs33.copyFile(from, to);
11043
+ await fs33.chmod(to, (await fs33.stat(from)).mode & 511);
10874
11044
  }
10875
11045
  await verifyBundle(stage, bundle.manifestHash);
10876
11046
  try {
10877
- await fs32.rename(stage, destination);
11047
+ await fs33.rename(stage, destination);
10878
11048
  } catch (error) {
10879
11049
  try {
10880
11050
  await verifyBundle(destination, bundle.manifestHash);
@@ -10883,7 +11053,7 @@ async function copyBundle(home, source, destination, bundle) {
10883
11053
  }
10884
11054
  }
10885
11055
  } finally {
10886
- await fs32.rm(stage, { recursive: true, force: true });
11056
+ await fs33.rm(stage, { recursive: true, force: true });
10887
11057
  }
10888
11058
  }
10889
11059
  async function prepareStandaloneMcp(home) {
@@ -10921,14 +11091,14 @@ async function prepareStandaloneMcp(home) {
10921
11091
  // src/distribution/updates.ts
10922
11092
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
10923
11093
  var RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
10924
- var releaseListSchema = z31.array(z31.object({ tag_name: z31.string(), draft: z31.boolean(), prerelease: z31.boolean(), published_at: z31.string().nullable() }));
10925
- var updateManifestSchema = z31.object({
10926
- format: z31.literal(1),
10927
- version: z31.string().regex(/^\d+\.\d+\.\d+$/),
10928
- publishedAt: z31.string().datetime(),
10929
- assets: z31.record(z31.string().regex(/^[a-f0-9]{64}$/))
11094
+ var releaseListSchema = z32.array(z32.object({ tag_name: z32.string(), draft: z32.boolean(), prerelease: z32.boolean(), published_at: z32.string().nullable() }));
11095
+ var updateManifestSchema = z32.object({
11096
+ format: z32.literal(1),
11097
+ version: z32.string().regex(/^\d+\.\d+\.\d+$/),
11098
+ publishedAt: z32.string().datetime(),
11099
+ assets: z32.record(z32.string().regex(/^[a-f0-9]{64}$/))
10930
11100
  }).strict();
10931
- var statusSchema2 = z31.object({ lastAttempt: z31.number(), availableVersion: z31.string().optional(), error: z31.string().optional() });
11101
+ var statusSchema2 = z32.object({ lastAttempt: z32.number(), availableVersion: z32.string().optional(), error: z32.string().optional() });
10932
11102
  async function scheduleAutomaticUpdate() {
10933
11103
  try {
10934
11104
  const { home, record } = await currentInstallation();