mason-context 0.19.0 → 0.19.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
@@ -58,8 +58,471 @@ var init_git_read = __esm({
58
58
  }
59
59
  });
60
60
 
61
+ // src/utils/paths.ts
62
+ import path from "path";
63
+ function normalizeRepoPath(value) {
64
+ const slash = value.replace(/\\/g, "/");
65
+ if (!slash || slash.includes("\0") || path.posix.isAbsolute(slash) || /^[A-Za-z]:/.test(slash)) return null;
66
+ if (slash.split("/").includes("..")) return null;
67
+ const normalized = path.posix.normalize(slash).replace(/\/$/, "");
68
+ return normalized === "." ? null : normalized;
69
+ }
70
+ function sanitizeRepoPaths(files) {
71
+ return [...new Set(files.map(normalizeRepoPath).filter((p) => p !== null))];
72
+ }
73
+ function isWithinRoot(root, candidate) {
74
+ const relative = path.relative(root, candidate);
75
+ return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
76
+ }
77
+ function anchorMatches(anchor, file) {
78
+ const a = normalizeRepoPath(anchor);
79
+ const f = normalizeRepoPath(file);
80
+ return a !== null && f !== null && (a === f || f.startsWith(`${a}/`));
81
+ }
82
+ function matchingPaths(anchors, files) {
83
+ return [...new Set(files)].filter((file) => anchors.some((anchor) => anchorMatches(anchor, file)));
84
+ }
85
+ var init_paths = __esm({
86
+ "src/utils/paths.ts"() {
87
+ "use strict";
88
+ }
89
+ });
90
+
91
+ // src/audit/types.ts
92
+ var types_exports = {};
93
+ __export(types_exports, {
94
+ ALL_CHECKS: () => ALL_CHECKS
95
+ });
96
+ var ALL_CHECKS;
97
+ var init_types = __esm({
98
+ "src/audit/types.ts"() {
99
+ "use strict";
100
+ ALL_CHECKS = [
101
+ "deleted-reference",
102
+ "new-module",
103
+ "stale-count",
104
+ "dead-command",
105
+ "deps-changed",
106
+ "decision-anchor-drift"
107
+ ];
108
+ }
109
+ });
110
+
111
+ // src/utils/files.ts
112
+ import fs from "fs/promises";
113
+ import { constants } from "fs";
114
+ import path2 from "path";
115
+ import fg from "fast-glob";
116
+ function isSensitiveFile(file) {
117
+ return file.split(/[\\/]/).some(
118
+ (part) => /^(?:\.env(?:\..*)?|id_rsa.*|id_ed25519.*)$|\.(?:pem|key|p12|pfx|jks|keystore)$|credentials\.|secret|^local\.properties$/i.test(part)
119
+ );
120
+ }
121
+ async function readBoundedFile(file, maxBytes) {
122
+ const handle = await fs.open(file, constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW);
123
+ try {
124
+ const stat = await handle.stat();
125
+ if (!stat.isFile() || stat.size > maxBytes) return null;
126
+ const buffer = Buffer.alloc(Math.min(maxBytes + 1, stat.size + 1));
127
+ let bytes = 0;
128
+ while (bytes < buffer.length) {
129
+ const result = await handle.read(buffer, bytes, buffer.length - bytes, null);
130
+ if (result.bytesRead === 0) break;
131
+ bytes += result.bytesRead;
132
+ }
133
+ return bytes === buffer.length ? null : buffer.subarray(0, bytes).toString("utf8");
134
+ } finally {
135
+ await handle.close();
136
+ }
137
+ }
138
+ async function loadProjectConfig(root) {
139
+ try {
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);
144
+ if (raw === null) throw new Error("Project configuration is not a regular file or exceeds 64 KiB");
145
+ const value = JSON.parse(raw);
146
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected a configuration object");
147
+ const config = {};
148
+ for (const key of ["patterns", "alwaysInclude", "ignore"]) {
149
+ if (value[key] === void 0) continue;
150
+ if (!Array.isArray(value[key]) || !value[key].every((s) => typeof s === "string")) {
151
+ throw new Error(`Configuration ${key} must be an array of strings`);
152
+ }
153
+ config[key] = value[key];
154
+ }
155
+ if (value.audit !== void 0) {
156
+ const audit = value.audit;
157
+ if (!audit || typeof audit !== "object" || Array.isArray(audit)) throw new Error("Configuration audit must be an object");
158
+ const patterns = (raw2) => {
159
+ if (!Array.isArray(raw2) || raw2.length > 100 || !raw2.every((s) => typeof s === "string" && normalizeRepoPath(s) === s && !s.startsWith("!") && !/[\x00-\x1f]/.test(s))) {
160
+ throw new Error("Audit patterns must be at most 100 repository-relative globs without traversal or negation");
161
+ }
162
+ return raw2;
163
+ };
164
+ config.audit = {};
165
+ if (audit.include !== void 0) config.audit.include = patterns(audit.include);
166
+ if (audit.exclude !== void 0) {
167
+ if (!audit.exclude || typeof audit.exclude !== "object" || Array.isArray(audit.exclude)) throw new Error("audit.exclude must map check names to patterns");
168
+ const { ALL_CHECKS: ALL_CHECKS2 } = await Promise.resolve().then(() => (init_types(), types_exports));
169
+ config.audit.exclude = {};
170
+ for (const [check, globs] of Object.entries(audit.exclude)) {
171
+ if (!ALL_CHECKS2.includes(check)) throw new Error("Unknown audit check: " + check);
172
+ config.audit.exclude[check] = patterns(globs);
173
+ }
174
+ }
175
+ }
176
+ return config;
177
+ } catch (error) {
178
+ if (error.code === "ENOENT") return {};
179
+ throw new Error(`Cannot apply project file policy: ${error instanceof Error ? error.message : String(error)}`);
180
+ }
181
+ }
182
+ async function createFileAccess(rootDir) {
183
+ const root = path2.resolve(rootDir);
184
+ const canonicalRoot = await fs.realpath(root).catch(() => root);
185
+ const config = await loadProjectConfig(root);
186
+ const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
187
+ let gitFiles = null;
188
+ try {
189
+ const { stdout } = await execGit(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
190
+ gitFiles = new Set(stdout.split("\0").filter(Boolean));
191
+ } catch {
192
+ let inGit = false;
193
+ try {
194
+ await execGit(["rev-parse", "--git-dir"], { cwd: root });
195
+ inGit = true;
196
+ } catch {
197
+ }
198
+ if (inGit) throw new Error("Cannot enumerate Git files safely");
199
+ }
200
+ async function resolve2(file) {
201
+ const relative = normalizeRepoPath(file);
202
+ if (!relative || isSensitiveFile(relative) || gitFiles && !gitFiles.has(relative)) return null;
203
+ const candidate = path2.join(root, relative);
204
+ try {
205
+ const real = await fs.realpath(candidate);
206
+ if (!isWithinRoot(canonicalRoot, real) || isSensitiveFile(path2.relative(canonicalRoot, real))) return null;
207
+ const stat = await fs.stat(real);
208
+ if (!stat.isFile() || stat.size > MAX_SOURCE_BYTES) return null;
209
+ if (gitFiles && !gitFiles.has(path2.relative(canonicalRoot, real).split(path2.sep).join("/"))) return null;
210
+ return real;
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+ async function list(patterns = SOURCE_GLOB, options = {}) {
216
+ const found = await fg(patterns, { cwd: root, ignore, followSymbolicLinks: false, ...options });
217
+ const safe = await Promise.all(found.map(async (f) => await resolve2(f) ? f : null));
218
+ return safe.filter((f) => f !== null).sort();
219
+ }
220
+ async function read(file) {
221
+ const relative = normalizeRepoPath(file);
222
+ if (!relative) return null;
223
+ const real = await resolve2(relative);
224
+ if (!real) return null;
225
+ for (const rel of /* @__PURE__ */ new Set([relative, path2.relative(canonicalRoot, real).split(path2.sep).join("/")])) {
226
+ if (!(await fg(fg.escapePath(rel), { cwd: root, ignore, dot: true })).length) return null;
227
+ }
228
+ try {
229
+ const content2 = await readBoundedFile(real, MAX_SOURCE_BYTES);
230
+ return content2 === null ? null : { path: relative, content: content2, totalLines: content2.split("\n").length };
231
+ } catch {
232
+ return null;
233
+ }
234
+ }
235
+ return { root, config, list, read };
236
+ }
237
+ var SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
238
+ var init_files = __esm({
239
+ "src/utils/files.ts"() {
240
+ "use strict";
241
+ init_git_read();
242
+ init_paths();
243
+ SOURCE_EXTENSIONS = ["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs", "vue", "svelte", "kt", "kts", "java", "py", "go", "rs", "swift", "rb", "cs", "cpp", "c", "h", "hpp", "dart", "php"];
244
+ SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(",")}}`;
245
+ SOURCE_IGNORE = [
246
+ "**/node_modules/**",
247
+ "**/dist/**",
248
+ "**/build/**",
249
+ "**/.gradle/**",
250
+ "**/target/**",
251
+ "**/.git/**",
252
+ "**/.mason/**",
253
+ "**/vendor/**",
254
+ "**/__pycache__/**",
255
+ "**/venv/**",
256
+ "**/.venv/**",
257
+ "**/*.min.*",
258
+ "**/*.map",
259
+ "**/*.lock",
260
+ "**/generated/**",
261
+ "**/*.generated.*",
262
+ "**/R.java",
263
+ "**/BuildConfig.java",
264
+ "**/package-lock.json",
265
+ "**/yarn.lock",
266
+ "**/pnpm-lock.yaml"
267
+ ];
268
+ MAX_SOURCE_BYTES = 1024 * 1024;
269
+ }
270
+ });
271
+
272
+ // src/utils/storage.ts
273
+ import fs2 from "fs/promises";
274
+ import path3 from "path";
275
+ import { randomUUID } from "crypto";
276
+ async function storePath(root, relative, createParents = false) {
277
+ const normalized = normalizeRepoPath(relative);
278
+ if (!normalized) throw new Error(`Invalid store path: ${relative}`);
279
+ let current = await fs2.realpath(root);
280
+ const parts = normalized.split("/");
281
+ for (let i = 0; i < parts.length; i++) {
282
+ current = path3.join(current, parts[i]);
283
+ let stat;
284
+ try {
285
+ stat = await fs2.lstat(current);
286
+ } catch (error) {
287
+ if (error.code !== "ENOENT") throw error;
288
+ if (createParents && i < parts.length - 1) {
289
+ try {
290
+ await fs2.mkdir(current);
291
+ } catch (mkdirError) {
292
+ if (mkdirError.code !== "EEXIST") throw mkdirError;
293
+ }
294
+ stat = await fs2.lstat(current);
295
+ }
296
+ }
297
+ if (stat?.isSymbolicLink()) throw new Error(`Symlink in store path: ${relative}`);
298
+ }
299
+ return current;
300
+ }
301
+ async function readStoreJson(root, relative) {
302
+ try {
303
+ const file = await storePath(root, relative);
304
+ const raw = await readBoundedFile(file, 10 * 1024 * 1024);
305
+ if (raw === null) throw new Error("file is not regular or exceeds 10 MiB");
306
+ const parsed = JSON.parse(raw);
307
+ if (parsed === null) throw new Error("expected a JSON object, received null");
308
+ return parsed;
309
+ } catch (error) {
310
+ if (error.code === "ENOENT") return null;
311
+ throw new Error(`Invalid Mason store ${relative}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
312
+ }
313
+ }
314
+ async function writeStoreJson(root, relative, value) {
315
+ const payload = JSON.stringify(value, null, 2) + "\n";
316
+ if (Buffer.byteLength(payload) > 10 * 1024 * 1024) {
317
+ throw new Error(`Mason store ${relative} exceeds 10 MiB`);
318
+ }
319
+ const file = await storePath(root, relative, true);
320
+ const temporary = path3.join(path3.dirname(file), `.${path3.basename(file)}.${randomUUID()}.tmp`);
321
+ try {
322
+ const handle = await fs2.open(temporary, "wx", 384);
323
+ try {
324
+ await handle.writeFile(payload, "utf8");
325
+ await handle.sync();
326
+ } finally {
327
+ await handle.close();
328
+ }
329
+ await fs2.rename(temporary, file);
330
+ } finally {
331
+ await fs2.rm(temporary, { force: true });
332
+ }
333
+ }
334
+ var init_storage = __esm({
335
+ "src/utils/storage.ts"() {
336
+ "use strict";
337
+ init_paths();
338
+ init_files();
339
+ }
340
+ });
341
+
342
+ // src/audit/inspection.ts
343
+ import fg2 from "fast-glob";
344
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
345
+ import path4 from "path";
346
+ function withRepositoryInspection(root, run) {
347
+ return inspections.run({ root: path4.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
348
+ }
349
+ function inspectionRead(root, key, read) {
350
+ const inspection = inspections.getStore();
351
+ if (!inspection || inspection.root !== path4.resolve(root)) return read();
352
+ if (!inspection.reads.has(key)) {
353
+ const pending = read();
354
+ inspection.reads.set(key, pending);
355
+ void pending.catch(() => {
356
+ if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
357
+ });
358
+ }
359
+ return inspection.reads.get(key);
360
+ }
361
+ function inspectionGit(args, options) {
362
+ return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
363
+ }
364
+ async function auditGitPaths(root, kind) {
365
+ const custom = (await loadProjectConfig(root)).audit?.include ?? [];
366
+ const shared = inspections.getStore()?.root === path4.resolve(root) && !custom.length;
367
+ const expanded = fg2.generateTasks(custom).flatMap((task) => task.positive);
368
+ if (expanded.length > 1e3) throw new Error("Audit include patterns expand to more than 1,000 globs");
369
+ const documentSpecs = [...docSpecs, ...expanded.map((pattern) => `:(glob)${pattern}`)];
370
+ const specs = shared ? [...documentSpecs, ...sourceSpecs] : kind === "documents" ? documentSpecs : sourceSpecs;
371
+ const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
372
+ const { stdout } = await inspectionGit(
373
+ ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
374
+ { cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
375
+ );
376
+ const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? custom.length > 0 || /^(readme|agents|claude)\.md$/i.test(path4.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
377
+ if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
378
+ return paths.sort();
379
+ }
380
+ var inspections, sourceSpecs, docSpecs, sourceSuffixes;
381
+ var init_inspection = __esm({
382
+ "src/audit/inspection.ts"() {
383
+ "use strict";
384
+ init_git_read();
385
+ init_files();
386
+ inspections = new AsyncLocalStorage2();
387
+ sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
388
+ docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
389
+ sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
390
+ }
391
+ });
392
+
393
+ // src/audit/inputs.ts
394
+ import fs3 from "fs/promises";
395
+ import * as nativeFs from "fs";
396
+ import path5 from "path";
397
+ import fg3 from "fast-glob";
398
+ async function auditInputPath(root, file) {
399
+ if (file === ".") return fs3.realpath(root);
400
+ try {
401
+ return await storePath(root, file);
402
+ } catch (error) {
403
+ if (error instanceof Error && error.message.startsWith("Symlink")) {
404
+ throw new Error(`Audit input contains a symbolic link: ${file}. Its evidence could not be verified.`, { cause: error });
405
+ }
406
+ throw error;
407
+ }
408
+ }
409
+ async function readAuditInput(root, file) {
410
+ try {
411
+ const value = await readBoundedFile(await auditInputPath(root, file), 10 * 1024 * 1024);
412
+ if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
413
+ return value;
414
+ } catch (error) {
415
+ if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return null;
416
+ throw error;
417
+ }
418
+ }
419
+ async function auditGlob(root, patterns, options) {
420
+ if (Array.isArray(patterns) && !patterns.length) return [];
421
+ const directories = /* @__PURE__ */ new Set();
422
+ const readdir2 = ((directory, opts, callback) => {
423
+ const relative = path5.relative(root, directory).split(path5.sep).join("/") || ".";
424
+ directories.add(relative);
425
+ if (directories.size > MAX_INPUTS) {
426
+ callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
427
+ return;
428
+ }
429
+ auditInputPath(root, relative).then(() => {
430
+ nativeFs.readdir(directory, opts, callback);
431
+ }, callback);
432
+ });
433
+ const lstat2 = ((file, callback) => {
434
+ const relative = path5.relative(root, file).split(path5.sep).join("/") || ".";
435
+ auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
436
+ });
437
+ const stream = fg3.stream(patterns, {
438
+ cwd: root,
439
+ ignore: options.ignore,
440
+ onlyFiles: false,
441
+ onlyDirectories: options.onlyDirectories,
442
+ objectMode: true,
443
+ followSymbolicLinks: options.followSymbolicLinks ?? true,
444
+ fs: { readdir: readdir2, lstat: lstat2 },
445
+ concurrency: 16
446
+ });
447
+ const matches = [];
448
+ try {
449
+ for await (const value of stream) {
450
+ const entry = value;
451
+ const file = entry.path;
452
+ if (options.select && !options.select(file)) continue;
453
+ await auditInputPath(root, file);
454
+ if (!options.onlyDirectories && !entry.dirent.isFile()) continue;
455
+ matches.push(file);
456
+ if (matches.length > MAX_INPUTS) {
457
+ const largest = [...matches.reduce((counts, item) => {
458
+ const dir = item.split("/")[0];
459
+ counts.set(dir, (counts.get(dir) ?? 0) + 1);
460
+ return counts;
461
+ }, /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1]).slice(0, 3);
462
+ throw new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count4]) => `${dir} (${count4})`).join(", ")}. Narrow this check's inputs.`);
463
+ }
464
+ }
465
+ } finally {
466
+ stream.destroy();
467
+ }
468
+ return matches.sort();
469
+ }
470
+ async function gitSourcePaths(root) {
471
+ return auditGitPaths(root, "sources");
472
+ }
473
+ async function gitIgnoredPaths(root, files) {
474
+ if (!files.length) return /* @__PURE__ */ new Set();
475
+ const ignored = /* @__PURE__ */ new Set();
476
+ for (let offset = 0; offset < files.length; offset += 1e3) {
477
+ try {
478
+ const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
479
+ cwd: root,
480
+ maxBuffer: 4 * 1024 * 1024,
481
+ timeout: 1e4,
482
+ input: files.slice(offset, offset + 1e3).join("\0") + "\0"
483
+ });
484
+ for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
485
+ } catch (error) {
486
+ if (error.code !== 1) throw error;
487
+ }
488
+ }
489
+ return ignored;
490
+ }
491
+ var MAX_INPUTS;
492
+ var init_inputs = __esm({
493
+ "src/audit/inputs.ts"() {
494
+ "use strict";
495
+ init_files();
496
+ init_storage();
497
+ init_git_read();
498
+ init_inspection();
499
+ MAX_INPUTS = 1e5;
500
+ }
501
+ });
502
+
503
+ // src/audit/policy.ts
504
+ import fg4 from "fast-glob";
505
+ async function includedCheckPaths(root, paths, exclude) {
506
+ if (!exclude.length) return new Set(paths);
507
+ const included = /* @__PURE__ */ new Set();
508
+ for (let offset = 0; offset < paths.length; offset += 256) {
509
+ for (const file of await auditGlob(root, paths.slice(offset, offset + 256).map((file2) => fg4.escapePath(file2)), {
510
+ ignore: exclude,
511
+ followSymbolicLinks: false,
512
+ label: "Audit check exclusions"
513
+ })) included.add(file);
514
+ }
515
+ return included;
516
+ }
517
+ var init_policy = __esm({
518
+ "src/audit/policy.ts"() {
519
+ "use strict";
520
+ init_inputs();
521
+ }
522
+ });
523
+
61
524
  // src/audit/commands.ts
62
- import path from "path";
525
+ import path6 from "path";
63
526
  function splitCommands(line) {
64
527
  const parts = [];
65
528
  let quote2 = "", start = 0;
@@ -95,7 +558,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
95
558
  const cd = text2.match(/^cd\s+(.+)$/);
96
559
  if (cd) {
97
560
  const directory = literal(cd[1]);
98
- cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path.posix.join(cwd ?? ".", directory);
561
+ cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path6.posix.join(cwd ?? ".", directory);
99
562
  continue;
100
563
  }
101
564
  for (const match of text2.matchAll(COMMAND)) {
@@ -106,7 +569,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
106
569
  if (shell && match.index !== 0 || match[3].startsWith("-")) scopeUnknown = "Unsupported shell wrapper or options before the script.";
107
570
  if (selector) {
108
571
  const selected = literal(selector[1]);
109
- directory = selected === null || cwd === null ? null : path.posix.join(cwd ?? ".", selected);
572
+ directory = selected === null || cwd === null ? null : path6.posix.join(cwd ?? ".", selected);
110
573
  } else if (flags) scopeUnknown = "Unsupported package-manager options before run: " + flags;
111
574
  const tail = text2.slice(match.index + match[0].length).split(/\s--(?:\s|$)/)[0];
112
575
  if (/(?:^|\s)(?:--workspace(?:s)?|-w|--filter|-F|--prefix|--dir|-C|--if-present)(?:\s|=|$)/.test(tail)) {
@@ -136,7 +599,7 @@ var init_commands = __esm({
136
599
  COMMAND = new RegExp(`\\b(npm|pnpm|yarn)((?:[ \\t]+${TOKEN})*?)[ \\t]+run[ \\t]+([A-Za-z0-9:_.-]+)`, "g");
137
600
  literal = (value) => {
138
601
  const unquoted = value.replace(/^(["'])(.*)\1$/, "$2");
139
- return /[$`*?\[\]{}~\\\x00-\x1f]/.test(unquoted) || path.posix.isAbsolute(unquoted) ? null : unquoted;
602
+ return /[$`*?\[\]{}~\\\x00-\x1f]/.test(unquoted) || path6.posix.isAbsolute(unquoted) ? null : unquoted;
140
603
  };
141
604
  }
142
605
  });
@@ -382,278 +845,62 @@ function extractClaims(content2) {
382
845
  excerpt: m[0]
383
846
  });
384
847
  }
385
- for (const match of line.matchAll(/`([^`]+)`/g)) {
386
- for (const claim of commandClaims(match[1], lineNo, void 0, true).claims) addCommand(claim);
387
- }
388
- for (const claim of commandClaims(line.replace(/`[^`]+`/g, ""), lineNo).claims) addCommand(claim);
389
- }
390
- if (inFence) processBlock();
391
- return {
392
- paths: [...paths.values()],
393
- counts,
394
- commands: [...commands2.values()]
395
- };
396
- }
397
- var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
398
- var init_claims = __esm({
399
- "src/audit/claims.ts"() {
400
- "use strict";
401
- init_commands();
402
- init_tree();
403
- ROOT_FILE_NAMES = /* @__PURE__ */ new Set([
404
- "package.json",
405
- "package-lock.json",
406
- "pnpm-workspace.yaml",
407
- "tsconfig.json",
408
- "tsup.config.ts",
409
- "vitest.config.ts",
410
- "Makefile",
411
- "Dockerfile",
412
- "docker-compose.yml",
413
- "Cargo.toml",
414
- "go.mod",
415
- "go.sum",
416
- "pyproject.toml",
417
- "requirements.txt",
418
- "Gemfile",
419
- "composer.json",
420
- "settings.gradle.kts",
421
- "settings.gradle",
422
- "build.gradle.kts",
423
- "build.gradle",
424
- "manifest.json",
425
- "server.json",
426
- "README.md",
427
- "CHANGELOG.md",
428
- "LICENSE",
429
- "CLAUDE.md",
430
- "AGENTS.md",
431
- ".gitignore",
432
- ".env.example"
433
- ]);
434
- SHELL_FENCE_INFOS = /* @__PURE__ */ new Set(["", "bash", "sh", "shell", "console", "zsh"]);
435
- markdown = new MarkdownIt("commonmark", { html: false });
436
- COUNT_RE = /(\d+)\s+(modules?|packages?|workspaces?|crates?)\b/gi;
437
- COUNT_DENYLIST_RE = /^\s*(manager|registr|lock|json)/i;
438
- IGNORE_LINE = "<!-- mason:ignore -->";
439
- IGNORE_START = "<!-- mason:ignore-start -->";
440
- IGNORE_END = "<!-- mason:ignore-end -->";
441
- }
442
- });
443
-
444
- // src/utils/paths.ts
445
- import path2 from "path";
446
- function normalizeRepoPath(value) {
447
- const slash = value.replace(/\\/g, "/");
448
- if (!slash || slash.includes("\0") || path2.posix.isAbsolute(slash) || /^[A-Za-z]:/.test(slash)) return null;
449
- if (slash.split("/").includes("..")) return null;
450
- const normalized = path2.posix.normalize(slash).replace(/\/$/, "");
451
- return normalized === "." ? null : normalized;
452
- }
453
- function sanitizeRepoPaths(files) {
454
- return [...new Set(files.map(normalizeRepoPath).filter((p) => p !== null))];
455
- }
456
- function isWithinRoot(root, candidate) {
457
- const relative = path2.relative(root, candidate);
458
- return relative !== ".." && !relative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative);
459
- }
460
- function anchorMatches(anchor, file) {
461
- const a = normalizeRepoPath(anchor);
462
- const f = normalizeRepoPath(file);
463
- return a !== null && f !== null && (a === f || f.startsWith(`${a}/`));
464
- }
465
- function matchingPaths(anchors, files) {
466
- return [...new Set(files)].filter((file) => anchors.some((anchor) => anchorMatches(anchor, file)));
467
- }
468
- var init_paths = __esm({
469
- "src/utils/paths.ts"() {
470
- "use strict";
471
- }
472
- });
473
-
474
- // src/utils/files.ts
475
- import fs from "fs/promises";
476
- import { constants } from "fs";
477
- import path3 from "path";
478
- import fg from "fast-glob";
479
- function isSensitiveFile(file) {
480
- return file.split(/[\\/]/).some(
481
- (part) => /^(?:\.env(?:\..*)?|id_rsa.*|id_ed25519.*)$|\.(?:pem|key|p12|pfx|jks|keystore)$|credentials\.|secret|^local\.properties$/i.test(part)
482
- );
483
- }
484
- async function readBoundedFile(file, maxBytes) {
485
- const handle = await fs.open(file, constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW);
486
- try {
487
- const stat = await handle.stat();
488
- if (!stat.isFile() || stat.size > maxBytes) return null;
489
- const buffer = Buffer.alloc(Math.min(maxBytes + 1, stat.size + 1));
490
- let bytes = 0;
491
- while (bytes < buffer.length) {
492
- const result = await handle.read(buffer, bytes, buffer.length - bytes, null);
493
- if (result.bytesRead === 0) break;
494
- bytes += result.bytesRead;
495
- }
496
- return bytes === buffer.length ? null : buffer.subarray(0, bytes).toString("utf8");
497
- } finally {
498
- await handle.close();
499
- }
500
- }
501
- async function loadProjectConfig(root) {
502
- try {
503
- const canonicalRoot = await fs.realpath(root);
504
- const configPath2 = await fs.realpath(path3.join(root, ".mason/config.json"));
505
- if (!isWithinRoot(canonicalRoot, configPath2)) throw new Error("Project configuration resolves outside the repository");
506
- const raw = await readBoundedFile(configPath2, 64 * 1024);
507
- if (raw === null) throw new Error("Project configuration is not a regular file or exceeds 64 KiB");
508
- const value = JSON.parse(raw);
509
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected a configuration object");
510
- const config = {};
511
- for (const key of ["patterns", "alwaysInclude", "ignore"]) {
512
- if (value[key] === void 0) continue;
513
- if (!Array.isArray(value[key]) || !value[key].every((s) => typeof s === "string")) {
514
- throw new Error(`Configuration ${key} must be an array of strings`);
515
- }
516
- config[key] = value[key];
517
- }
518
- return config;
519
- } catch (error) {
520
- if (error.code === "ENOENT") return {};
521
- throw new Error(`Cannot apply project file policy: ${error instanceof Error ? error.message : String(error)}`);
522
- }
523
- }
524
- async function createFileAccess(rootDir) {
525
- const root = path3.resolve(rootDir);
526
- const canonicalRoot = await fs.realpath(root).catch(() => root);
527
- const config = await loadProjectConfig(root);
528
- const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
529
- let gitFiles = null;
530
- try {
531
- const { stdout } = await execGit(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
532
- gitFiles = new Set(stdout.split("\0").filter(Boolean));
533
- } catch {
534
- let inGit = false;
535
- try {
536
- await execGit(["rev-parse", "--git-dir"], { cwd: root });
537
- inGit = true;
538
- } catch {
539
- }
540
- if (inGit) throw new Error("Cannot enumerate Git files safely");
541
- }
542
- async function resolve2(file) {
543
- const relative = normalizeRepoPath(file);
544
- if (!relative || isSensitiveFile(relative) || gitFiles && !gitFiles.has(relative)) return null;
545
- const candidate = path3.join(root, relative);
546
- try {
547
- const real = await fs.realpath(candidate);
548
- if (!isWithinRoot(canonicalRoot, real) || isSensitiveFile(path3.relative(canonicalRoot, real))) return null;
549
- const stat = await fs.stat(real);
550
- if (!stat.isFile() || stat.size > MAX_SOURCE_BYTES) return null;
551
- if (gitFiles && !gitFiles.has(path3.relative(canonicalRoot, real).split(path3.sep).join("/"))) return null;
552
- return real;
553
- } catch {
554
- return null;
555
- }
556
- }
557
- async function list(patterns = SOURCE_GLOB, options = {}) {
558
- const found = await fg(patterns, { cwd: root, ignore, followSymbolicLinks: false, ...options });
559
- const safe = await Promise.all(found.map(async (f) => await resolve2(f) ? f : null));
560
- return safe.filter((f) => f !== null).sort();
561
- }
562
- async function read(file) {
563
- const relative = normalizeRepoPath(file);
564
- if (!relative) return null;
565
- const real = await resolve2(relative);
566
- if (!real) return null;
567
- for (const rel of /* @__PURE__ */ new Set([relative, path3.relative(canonicalRoot, real).split(path3.sep).join("/")])) {
568
- if (!(await fg(fg.escapePath(rel), { cwd: root, ignore, dot: true })).length) return null;
569
- }
570
- try {
571
- const content2 = await readBoundedFile(real, MAX_SOURCE_BYTES);
572
- return content2 === null ? null : { path: relative, content: content2, totalLines: content2.split("\n").length };
573
- } catch {
574
- return null;
575
- }
576
- }
577
- return { root, config, list, read };
578
- }
579
- var SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
580
- var init_files = __esm({
581
- "src/utils/files.ts"() {
582
- "use strict";
583
- init_git_read();
584
- init_paths();
585
- SOURCE_EXTENSIONS = ["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs", "vue", "svelte", "kt", "kts", "java", "py", "go", "rs", "swift", "rb", "cs", "cpp", "c", "h", "hpp", "dart", "php"];
586
- SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(",")}}`;
587
- SOURCE_IGNORE = [
588
- "**/node_modules/**",
589
- "**/dist/**",
590
- "**/build/**",
591
- "**/.gradle/**",
592
- "**/target/**",
593
- "**/.git/**",
594
- "**/.mason/**",
595
- "**/vendor/**",
596
- "**/__pycache__/**",
597
- "**/venv/**",
598
- "**/.venv/**",
599
- "**/*.min.*",
600
- "**/*.map",
601
- "**/*.lock",
602
- "**/generated/**",
603
- "**/*.generated.*",
604
- "**/R.java",
605
- "**/BuildConfig.java",
606
- "**/package-lock.json",
607
- "**/yarn.lock",
608
- "**/pnpm-lock.yaml"
609
- ];
610
- MAX_SOURCE_BYTES = 1024 * 1024;
611
- }
612
- });
613
-
614
- // src/audit/inspection.ts
615
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
616
- import path4 from "path";
617
- function withRepositoryInspection(root, run) {
618
- return inspections.run({ root: path4.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
619
- }
620
- function inspectionRead(root, key, read) {
621
- const inspection = inspections.getStore();
622
- if (!inspection || inspection.root !== path4.resolve(root)) return read();
623
- if (!inspection.reads.has(key)) {
624
- const pending = read();
625
- inspection.reads.set(key, pending);
626
- void pending.catch(() => {
627
- if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
628
- });
848
+ for (const match of line.matchAll(/`([^`]+)`/g)) {
849
+ for (const claim of commandClaims(match[1], lineNo, void 0, true).claims) addCommand(claim);
850
+ }
851
+ for (const claim of commandClaims(line.replace(/`[^`]+`/g, ""), lineNo).claims) addCommand(claim);
629
852
  }
630
- return inspection.reads.get(key);
631
- }
632
- function inspectionGit(args, options) {
633
- return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
634
- }
635
- async function auditGitPaths(root, kind) {
636
- const shared = inspections.getStore()?.root === path4.resolve(root);
637
- const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
638
- const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
639
- const { stdout } = await inspectionGit(
640
- ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
641
- { cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
642
- );
643
- const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? /^(readme|agents|claude)\.md$/i.test(path4.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
644
- if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
645
- return paths.sort();
853
+ if (inFence) processBlock();
854
+ return {
855
+ paths: [...paths.values()],
856
+ counts,
857
+ commands: [...commands2.values()]
858
+ };
646
859
  }
647
- var inspections, sourceSpecs, docSpecs, sourceSuffixes;
648
- var init_inspection = __esm({
649
- "src/audit/inspection.ts"() {
860
+ var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
861
+ var init_claims = __esm({
862
+ "src/audit/claims.ts"() {
650
863
  "use strict";
651
- init_git_read();
652
- init_files();
653
- inspections = new AsyncLocalStorage2();
654
- sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
655
- docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
656
- sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
864
+ init_commands();
865
+ init_tree();
866
+ ROOT_FILE_NAMES = /* @__PURE__ */ new Set([
867
+ "package.json",
868
+ "package-lock.json",
869
+ "pnpm-workspace.yaml",
870
+ "tsconfig.json",
871
+ "tsup.config.ts",
872
+ "vitest.config.ts",
873
+ "Makefile",
874
+ "Dockerfile",
875
+ "docker-compose.yml",
876
+ "Cargo.toml",
877
+ "go.mod",
878
+ "go.sum",
879
+ "pyproject.toml",
880
+ "requirements.txt",
881
+ "Gemfile",
882
+ "composer.json",
883
+ "settings.gradle.kts",
884
+ "settings.gradle",
885
+ "build.gradle.kts",
886
+ "build.gradle",
887
+ "manifest.json",
888
+ "server.json",
889
+ "README.md",
890
+ "CHANGELOG.md",
891
+ "LICENSE",
892
+ "CLAUDE.md",
893
+ "AGENTS.md",
894
+ ".gitignore",
895
+ ".env.example"
896
+ ]);
897
+ SHELL_FENCE_INFOS = /* @__PURE__ */ new Set(["", "bash", "sh", "shell", "console", "zsh"]);
898
+ markdown = new MarkdownIt("commonmark", { html: false });
899
+ COUNT_RE = /(\d+)\s+(modules?|packages?|workspaces?|crates?)\b/gi;
900
+ COUNT_DENYLIST_RE = /^\s*(manager|registr|lock|json)/i;
901
+ IGNORE_LINE = "<!-- mason:ignore -->";
902
+ IGNORE_START = "<!-- mason:ignore-start -->";
903
+ IGNORE_END = "<!-- mason:ignore-end -->";
657
904
  }
658
905
  });
659
906
 
@@ -720,212 +967,32 @@ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
720
967
  ],
721
968
  { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
722
969
  );
723
- const commits = [];
724
- for (const block of stdout.split("")) {
725
- if (!block.trim()) continue;
726
- const lines = block.split("\n").filter((l) => l.trim().length > 0);
727
- const ref = parseCommitLine(lines[0]);
728
- if (!ref) continue;
729
- commits.push({ ...ref, files: lines.slice(1).map((l) => l.trim()) });
730
- }
731
- return { commits, total: commits.length };
732
- } catch {
733
- return null;
734
- }
735
- }
736
- var COMMIT_FORMAT;
737
- var init_git = __esm({
738
- "src/audit/git.ts"() {
739
- "use strict";
740
- init_inspection();
741
- COMMIT_FORMAT = "%H%x09%cI%x09%s";
742
- }
743
- });
744
-
745
- // src/utils/storage.ts
746
- import fs2 from "fs/promises";
747
- import path5 from "path";
748
- import { randomUUID } from "crypto";
749
- async function storePath(root, relative, createParents = false) {
750
- const normalized = normalizeRepoPath(relative);
751
- if (!normalized) throw new Error(`Invalid store path: ${relative}`);
752
- let current = await fs2.realpath(root);
753
- const parts = normalized.split("/");
754
- for (let i = 0; i < parts.length; i++) {
755
- current = path5.join(current, parts[i]);
756
- let stat;
757
- try {
758
- stat = await fs2.lstat(current);
759
- } catch (error) {
760
- if (error.code !== "ENOENT") throw error;
761
- if (createParents && i < parts.length - 1) {
762
- try {
763
- await fs2.mkdir(current);
764
- } catch (mkdirError) {
765
- if (mkdirError.code !== "EEXIST") throw mkdirError;
766
- }
767
- stat = await fs2.lstat(current);
768
- }
769
- }
770
- if (stat?.isSymbolicLink()) throw new Error(`Symlink in store path: ${relative}`);
771
- }
772
- return current;
773
- }
774
- async function readStoreJson(root, relative) {
775
- try {
776
- const file = await storePath(root, relative);
777
- const raw = await readBoundedFile(file, 10 * 1024 * 1024);
778
- if (raw === null) throw new Error("file is not regular or exceeds 10 MiB");
779
- const parsed = JSON.parse(raw);
780
- if (parsed === null) throw new Error("expected a JSON object, received null");
781
- return parsed;
782
- } catch (error) {
783
- if (error.code === "ENOENT") return null;
784
- throw new Error(`Invalid Mason store ${relative}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
785
- }
786
- }
787
- async function writeStoreJson(root, relative, value) {
788
- const payload = JSON.stringify(value, null, 2) + "\n";
789
- if (Buffer.byteLength(payload) > 10 * 1024 * 1024) {
790
- throw new Error(`Mason store ${relative} exceeds 10 MiB`);
791
- }
792
- const file = await storePath(root, relative, true);
793
- const temporary = path5.join(path5.dirname(file), `.${path5.basename(file)}.${randomUUID()}.tmp`);
794
- try {
795
- const handle = await fs2.open(temporary, "wx", 384);
796
- try {
797
- await handle.writeFile(payload, "utf8");
798
- await handle.sync();
799
- } finally {
800
- await handle.close();
801
- }
802
- await fs2.rename(temporary, file);
803
- } finally {
804
- await fs2.rm(temporary, { force: true });
805
- }
806
- }
807
- var init_storage = __esm({
808
- "src/utils/storage.ts"() {
809
- "use strict";
810
- init_paths();
811
- init_files();
812
- }
813
- });
814
-
815
- // src/audit/inputs.ts
816
- import fs3 from "fs/promises";
817
- import * as nativeFs from "fs";
818
- import path6 from "path";
819
- import fg2 from "fast-glob";
820
- async function auditInputPath(root, file) {
821
- if (file === ".") return fs3.realpath(root);
822
- try {
823
- return await storePath(root, file);
824
- } catch (error) {
825
- if (error instanceof Error && error.message.startsWith("Symlink")) {
826
- throw new Error(`Audit input contains a symbolic link: ${file}. Its evidence could not be verified.`, { cause: error });
827
- }
828
- throw error;
829
- }
830
- }
831
- async function readAuditInput(root, file) {
832
- try {
833
- const value = await readBoundedFile(await auditInputPath(root, file), 10 * 1024 * 1024);
834
- if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
835
- return value;
836
- } catch (error) {
837
- if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return null;
838
- throw error;
839
- }
840
- }
841
- async function auditGlob(root, patterns, options) {
842
- if (Array.isArray(patterns) && !patterns.length) return [];
843
- const directories = /* @__PURE__ */ new Set();
844
- const readdir2 = ((directory, opts, callback) => {
845
- const relative = path6.relative(root, directory).split(path6.sep).join("/") || ".";
846
- directories.add(relative);
847
- if (directories.size > MAX_INPUTS) {
848
- callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
849
- return;
850
- }
851
- auditInputPath(root, relative).then(() => {
852
- nativeFs.readdir(directory, opts, callback);
853
- }, callback);
854
- });
855
- const lstat2 = ((file, callback) => {
856
- const relative = path6.relative(root, file).split(path6.sep).join("/") || ".";
857
- auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
858
- });
859
- const stream = fg2.stream(patterns, {
860
- cwd: root,
861
- ignore: options.ignore,
862
- onlyFiles: false,
863
- onlyDirectories: options.onlyDirectories,
864
- objectMode: true,
865
- followSymbolicLinks: options.followSymbolicLinks ?? true,
866
- fs: { readdir: readdir2, lstat: lstat2 },
867
- concurrency: 16
868
- });
869
- const matches = [];
870
- try {
871
- for await (const value of stream) {
872
- const entry = value;
873
- const file = entry.path;
874
- if (options.select && !options.select(file)) continue;
875
- await auditInputPath(root, file);
876
- if (!options.onlyDirectories && !entry.dirent.isFile()) continue;
877
- matches.push(file);
878
- if (matches.length > MAX_INPUTS) {
879
- const largest = [...matches.reduce((counts, item) => {
880
- const dir = item.split("/")[0];
881
- counts.set(dir, (counts.get(dir) ?? 0) + 1);
882
- return counts;
883
- }, /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1]).slice(0, 3);
884
- throw new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count4]) => `${dir} (${count4})`).join(", ")}. Narrow this check's inputs.`);
885
- }
886
- }
887
- } finally {
888
- stream.destroy();
889
- }
890
- return matches.sort();
891
- }
892
- async function gitSourcePaths(root) {
893
- return auditGitPaths(root, "sources");
894
- }
895
- async function gitIgnoredPaths(root, files) {
896
- if (!files.length) return /* @__PURE__ */ new Set();
897
- const ignored = /* @__PURE__ */ new Set();
898
- for (let offset = 0; offset < files.length; offset += 1e3) {
899
- try {
900
- const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
901
- cwd: root,
902
- maxBuffer: 4 * 1024 * 1024,
903
- timeout: 1e4,
904
- input: files.slice(offset, offset + 1e3).join("\0") + "\0"
905
- });
906
- for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
907
- } catch (error) {
908
- if (error.code !== 1) throw error;
970
+ const commits = [];
971
+ for (const block of stdout.split("")) {
972
+ if (!block.trim()) continue;
973
+ const lines = block.split("\n").filter((l) => l.trim().length > 0);
974
+ const ref = parseCommitLine(lines[0]);
975
+ if (!ref) continue;
976
+ commits.push({ ...ref, files: lines.slice(1).map((l) => l.trim()) });
909
977
  }
978
+ return { commits, total: commits.length };
979
+ } catch {
980
+ return null;
910
981
  }
911
- return ignored;
912
982
  }
913
- var MAX_INPUTS;
914
- var init_inputs = __esm({
915
- "src/audit/inputs.ts"() {
983
+ var COMMIT_FORMAT;
984
+ var init_git = __esm({
985
+ "src/audit/git.ts"() {
916
986
  "use strict";
917
- init_files();
918
- init_storage();
919
- init_git_read();
920
987
  init_inspection();
921
- MAX_INPUTS = 1e5;
988
+ COMMIT_FORMAT = "%H%x09%cI%x09%s";
922
989
  }
923
990
  });
924
991
 
925
992
  // src/audit/docs.ts
926
993
  import fs4 from "fs/promises";
927
994
  import path7 from "path";
928
- import fg3 from "fast-glob";
995
+ import fg5 from "fast-glob";
929
996
  async function dirtyDocs(resolvedRoot, files) {
930
997
  if (!files.length) return /* @__PURE__ */ new Set();
931
998
  try {
@@ -977,7 +1044,7 @@ async function discoverDocPaths(root) {
977
1044
  const paths = [];
978
1045
  const unique = [...new Set(candidates)].sort();
979
1046
  for (let offset = 0; offset < unique.length; offset += 256) {
980
- paths.push(...await auditGlob(root, unique.slice(offset, offset + 256).map((p) => fg3.escapePath(p)), {
1047
+ paths.push(...await auditGlob(root, unique.slice(offset, offset + 256).map((p) => fg5.escapePath(p)), {
981
1048
  ignore: excluded,
982
1049
  followSymbolicLinks: false,
983
1050
  label: "Documentation discovery"
@@ -1007,6 +1074,7 @@ async function localDocPaths(root) {
1007
1074
  return paths;
1008
1075
  }
1009
1076
  function documentScope(file) {
1077
+ if (!/^(readme|agents|claude)\.md$/i.test(path7.posix.basename(file))) return ".";
1010
1078
  const parent = path7.posix.dirname(file);
1011
1079
  return path7.posix.basename(parent).toLowerCase() === ".claude" ? path7.posix.dirname(parent) : parent;
1012
1080
  }
@@ -1028,7 +1096,7 @@ async function readDocs(resolvedRoot) {
1028
1096
  path: candidate,
1029
1097
  content: content2,
1030
1098
  scope: documentScope(candidate),
1031
- kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : "instructions",
1099
+ kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : /^(agents|claude)\.md$/i.test(path7.posix.basename(candidate)) ? "instructions" : "guide",
1032
1100
  lineCount: content2.split("\n").length,
1033
1101
  lastCommit,
1034
1102
  dirty: dirty.has(candidate),
@@ -1585,22 +1653,6 @@ var init_drift = __esm({
1585
1653
  }
1586
1654
  });
1587
1655
 
1588
- // src/audit/types.ts
1589
- var ALL_CHECKS;
1590
- var init_types = __esm({
1591
- "src/audit/types.ts"() {
1592
- "use strict";
1593
- ALL_CHECKS = [
1594
- "deleted-reference",
1595
- "new-module",
1596
- "stale-count",
1597
- "dead-command",
1598
- "deps-changed",
1599
- "decision-anchor-drift"
1600
- ];
1601
- }
1602
- });
1603
-
1604
1656
  // src/audit/checks/deleted-reference.ts
1605
1657
  import path12 from "path";
1606
1658
  async function checkDeletedReferences(ctx) {
@@ -1669,7 +1721,7 @@ var init_deleted_reference = __esm({
1669
1721
  });
1670
1722
 
1671
1723
  // src/audit/checks/new-module.ts
1672
- import fg4 from "fast-glob";
1724
+ import fg6 from "fast-glob";
1673
1725
  import fs8 from "fs/promises";
1674
1726
  import path13 from "path";
1675
1727
  function escapeRegExp(text2) {
@@ -1731,7 +1783,7 @@ async function collectModuleCandidates(root, combinedDocs) {
1731
1783
  const result = /* @__PURE__ */ new Map();
1732
1784
  for (const dir of parents) {
1733
1785
  const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
1734
- result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
1786
+ result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg6.escapePath(dir)}/*`, {
1735
1787
  ignore,
1736
1788
  onlyDirectories: true,
1737
1789
  label: "Module directory discovery",
@@ -1744,7 +1796,7 @@ async function collectModuleCandidates(root, combinedDocs) {
1744
1796
  const files = sources.filter((file) => file.startsWith(dir + "/"));
1745
1797
  let count4 = 0;
1746
1798
  for (let offset = 0; offset < files.length; offset += 256) {
1747
- count4 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) => fg4.escapePath(file)), {
1799
+ count4 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) => fg6.escapePath(file)), {
1748
1800
  ignore,
1749
1801
  followSymbolicLinks: false,
1750
1802
  label: `Source discovery in ${dir}`
@@ -2079,6 +2131,87 @@ var init_dead_command = __esm({
2079
2131
  }
2080
2132
  });
2081
2133
 
2134
+ // src/audit/dependency-claims.ts
2135
+ import path16 from "path";
2136
+ import { parse as parseToml } from "smol-toml";
2137
+ function dependencyText(content2) {
2138
+ return content2.replace(/<!-- mason:start -->[\s\S]*?<!-- mason:end -->/g, (text2) => text2.replace(/[^\n]/g, " ")).replace(/<!--[\s\S]*?-->/g, (text2) => text2.replace(/[^\n]/g, " "));
2139
+ }
2140
+ function declarations(file, source) {
2141
+ const result = /* @__PURE__ */ new Map();
2142
+ if (!source) return result;
2143
+ if (path16.posix.basename(file) === "package.json") {
2144
+ const json = JSON.parse(source);
2145
+ for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies", "engines"]) {
2146
+ for (const [name, value] of Object.entries(json[section] ?? {})) {
2147
+ if (typeof value === "string") result.set(name, [...result.has(name) ? [result.get(name)] : [], value].join("; "));
2148
+ }
2149
+ }
2150
+ } else if (file.endsWith("libs.versions.toml")) {
2151
+ const catalog = parseToml(source);
2152
+ for (const section of ["libraries", "plugins"]) for (const [alias, entry] of Object.entries(catalog[section] ?? {})) {
2153
+ if (typeof entry === "string") {
2154
+ const parts = entry.split(":");
2155
+ if (parts.length >= 3) result.set(parts.slice(0, -1).join(":"), parts.at(-1));
2156
+ } else if (entry && typeof entry === "object") {
2157
+ const name = entry.module ?? (entry.group && entry.name ? `${entry.group}:${entry.name}` : entry.id);
2158
+ const version = typeof entry.version === "string" ? entry.version : catalog.versions?.[entry.version?.ref];
2159
+ if (typeof name === "string") result.set(name, typeof version === "string" ? version : "unspecified");
2160
+ }
2161
+ }
2162
+ } else if (/build\.gradle(?:\.kts)?$/.test(file)) {
2163
+ for (const match of source.matchAll(/["']([\w.-]+:[\w.-]+):([^"'\s]+)["']/g)) result.set(match[1], match[2]);
2164
+ for (const match of source.matchAll(/\bid\s*\(?\s*["']([\w.-]+)["']\s*\)?\s*version\s*["']([^"']+)["']/g)) result.set(match[1], match[2]);
2165
+ }
2166
+ return result;
2167
+ }
2168
+ async function matchDependencyChanges(root, from, head, files, content2) {
2169
+ const matches = [];
2170
+ const lines = dependencyText(content2).split("\n");
2171
+ let incomplete = files.length > 64;
2172
+ for (const file of files.slice(0, 64)) {
2173
+ if (!/(?:^|\/)(?:package\.json|libs\.versions\.toml|build\.gradle(?:\.kts)?)$/.test(file)) {
2174
+ incomplete = true;
2175
+ continue;
2176
+ }
2177
+ try {
2178
+ const read = async (revision) => {
2179
+ const { stdout: entry } = await inspectionGit(["ls-tree", revision, "--", `:(literal)${file}`], { cwd: root, timeout: 1e4 });
2180
+ if (!entry) return "";
2181
+ if (!entry.startsWith("100644 ") && !entry.startsWith("100755 ")) throw new Error("Unsupported manifest mode");
2182
+ return (await inspectionGit(["show", `${revision}:${file}`], { cwd: root, maxBuffer: 1024 * 1024, timeout: 1e4 })).stdout;
2183
+ };
2184
+ const [before, after] = await Promise.all([read(from), read(head)]);
2185
+ const old = declarations(file, before), next = declarations(file, after);
2186
+ for (const dependency of /* @__PURE__ */ new Set([...old.keys(), ...next.keys()])) {
2187
+ if (old.get(dependency) === next.get(dependency)) continue;
2188
+ const escaped = dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2189
+ const mention = new RegExp(`(^|[^a-zA-Z0-9_@./-])${escaped}(?![a-zA-Z0-9_/-]|\\.[a-zA-Z0-9_])`, "i");
2190
+ const line = lines.findIndex((text2) => mention.test(text2));
2191
+ if (line < 0) continue;
2192
+ matches.push({
2193
+ dependency,
2194
+ manifest: file,
2195
+ before: old.get(dependency) ?? null,
2196
+ after: next.get(dependency) ?? null,
2197
+ line: line + 1,
2198
+ excerpt: lines[line].trim().slice(0, 500)
2199
+ });
2200
+ if (matches.length >= 100) return { matches, incomplete: true };
2201
+ }
2202
+ } catch {
2203
+ incomplete = true;
2204
+ }
2205
+ }
2206
+ return { matches, incomplete };
2207
+ }
2208
+ var init_dependency_claims = __esm({
2209
+ "src/audit/dependency-claims.ts"() {
2210
+ "use strict";
2211
+ init_inspection();
2212
+ }
2213
+ });
2214
+
2082
2215
  // src/audit/release-metadata.ts
2083
2216
  async function git(root, args) {
2084
2217
  return (await execGit(args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
@@ -2158,13 +2291,6 @@ async function checkDepsChanged(ctx) {
2158
2291
  });
2159
2292
  continue;
2160
2293
  }
2161
- if (doc.dirty) {
2162
- result.skipped.push({
2163
- check: "deps-changed",
2164
- doc: doc.path,
2165
- reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
2166
- });
2167
- }
2168
2294
  let content2 = doc.content;
2169
2295
  if (doc.dirty) {
2170
2296
  try {
@@ -2177,7 +2303,6 @@ async function checkDepsChanged(ctx) {
2177
2303
  continue;
2178
2304
  }
2179
2305
  }
2180
- if (!hasDependencyContent(content2)) continue;
2181
2306
  const range = await commitsTouchingSince(
2182
2307
  ctx.root,
2183
2308
  doc.lastCommit.hash,
@@ -2201,16 +2326,32 @@ async function checkDepsChanged(ctx) {
2201
2326
  range.commits = relevant;
2202
2327
  range.total = relevant.length;
2203
2328
  if (range.total === 0) continue;
2204
- const latest = range.commits[0];
2329
+ const relevance = await matchDependencyChanges(
2330
+ ctx.root,
2331
+ doc.lastCommit.hash,
2332
+ ctx.headHash,
2333
+ [...new Set(range.commits.flatMap((commit) => commit.files))],
2334
+ content2
2335
+ );
2336
+ if (!relevance.matches.length && !hasDependencyContent(content2)) continue;
2337
+ const match = relevance.matches[0];
2338
+ if (doc.dirty && match) result.skipped.push({
2339
+ check: "deps-changed",
2340
+ doc: doc.path,
2341
+ reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
2342
+ });
2205
2343
  (doc.dirty ? result.suppressedAdvisories : result.advisories).push({
2206
2344
  type: "deps-changed",
2207
- message: `dependency manifests touched by ${range.total} commit${range.total === 1 ? "" : "s"} since ${doc.path} was last committed (latest: ${latest.hash.slice(0, 7)} "${latest.subject}")`,
2208
- anchor: { doc: doc.path, line: null, excerpt: null },
2345
+ ...match ? {} : { resolution: "informational" },
2346
+ message: match ? `${match.dependency} changed from ${match.before ?? "absent"} to ${match.after ?? "removed"} in ${match.manifest}; review ${doc.path}:${match.line}: ${match.excerpt}${relevance.matches.length > 1 ? ` (${relevance.matches.length - 1} more matches in evidence)` : ""}` : `Dependency manifests changed after the document baseline ${doc.lastCommit.hash.slice(0, 7)} (${doc.lastCommit.date.slice(0, 10)}); no specific affected passage was established. Informational only.${relevance.incomplete ? " Some manifest declarations could not be matched." : ""}`,
2347
+ anchor: { doc: doc.path, line: match?.line ?? null, excerpt: match?.excerpt ?? null },
2209
2348
  evidence: {
2210
2349
  kind: "doc-behind-manifests",
2211
2350
  docLastCommit: doc.lastCommit,
2212
2351
  manifestCommits: range.commits.slice(0, MANIFEST_COMMITS_CAP),
2213
- totalCommits: range.total
2352
+ totalCommits: range.total,
2353
+ matches: relevance.matches,
2354
+ matchingIncomplete: relevance.incomplete
2214
2355
  }
2215
2356
  });
2216
2357
  }
@@ -2220,6 +2361,7 @@ var MANIFEST_COMMITS_CAP, MANIFEST_PATHSPECS;
2220
2361
  var init_deps_changed = __esm({
2221
2362
  "src/audit/checks/deps-changed.ts"() {
2222
2363
  "use strict";
2364
+ init_dependency_claims();
2223
2365
  init_docs();
2224
2366
  init_release_metadata();
2225
2367
  init_git();
@@ -2244,7 +2386,7 @@ var init_deps_changed = __esm({
2244
2386
  });
2245
2387
 
2246
2388
  // src/context/lexical.ts
2247
- import path16 from "path";
2389
+ import path17 from "path";
2248
2390
  function tokenize(text2) {
2249
2391
  return text2.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2 && !STOPWORDS.has(t));
2250
2392
  }
@@ -2257,7 +2399,7 @@ function tokenSet(text2) {
2257
2399
  function scoreEntry(taskTokens, entry) {
2258
2400
  const nameTokens = tokenSet(entry.name);
2259
2401
  const descTokens = tokenSet(entry.description);
2260
- const fileTokens = tokenSet(entry.files.map((f) => path16.basename(f)).join(" "));
2402
+ const fileTokens = tokenSet(entry.files.map((f) => path17.basename(f)).join(" "));
2261
2403
  let score = 0;
2262
2404
  for (const token of taskTokens) {
2263
2405
  if (nameTokens.has(token)) score += 3;
@@ -2591,7 +2733,7 @@ __export(decisions_exports, {
2591
2733
  withDecisionWrite: () => withDecisionWrite
2592
2734
  });
2593
2735
  import fs9 from "fs/promises";
2594
- import path17 from "path";
2736
+ import path18 from "path";
2595
2737
  import { createHash } from "crypto";
2596
2738
  import { z as z3 } from "zod";
2597
2739
  async function loadDecisionStore(rootDir) {
@@ -2689,7 +2831,7 @@ async function upsertDecision(rootDir, input) {
2689
2831
  if (input.files && files.length < input.files.length) warnings.push("some anchor paths were outside the repo or duplicated and were dropped");
2690
2832
  for (const file of files) {
2691
2833
  try {
2692
- await fs9.access(path17.join(rootDir, file));
2834
+ await fs9.access(path18.join(rootDir, file));
2693
2835
  } catch {
2694
2836
  warnings.push(`anchor file does not exist on disk: ${file}`);
2695
2837
  }
@@ -2814,9 +2956,9 @@ var init_decisions = __esm({
2814
2956
  });
2815
2957
 
2816
2958
  // src/decisions/drift.ts
2817
- import path18 from "path";
2959
+ import path19 from "path";
2818
2960
  async function computeDecisionDrift(rootDir, decisions) {
2819
- const resolvedRoot = path18.resolve(rootDir);
2961
+ const resolvedRoot = path19.resolve(rootDir);
2820
2962
  const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
2821
2963
  const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
2822
2964
  const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
@@ -3012,7 +3154,9 @@ var init_findings = __esm({
3012
3154
  kind: z4.literal("doc-behind-manifests"),
3013
3155
  docLastCommit: commitSchema,
3014
3156
  manifestCommits: z4.array(commitSchema.extend({ files: z4.array(z4.string()) })),
3015
- totalCommits: count
3157
+ totalCommits: count,
3158
+ matches: z4.array(z4.object({ dependency: z4.string(), manifest: z4.string(), before: z4.string().nullable(), after: z4.string().nullable(), line: z4.number().int().positive(), excerpt: z4.string() })).optional(),
3159
+ matchingIncomplete: z4.boolean().optional()
3016
3160
  }),
3017
3161
  z4.object({
3018
3162
  kind: z4.literal("decision-anchor"),
@@ -3028,7 +3172,7 @@ var init_findings = __esm({
3028
3172
  type: z4.enum(["deleted-reference", "new-module", "stale-count", "dead-command"]),
3029
3173
  confidence: z4.enum(["certain", "likely"])
3030
3174
  });
3031
- advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z4.literal("recheck").optional() });
3175
+ advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z4.enum(["recheck", "informational"]).optional() });
3032
3176
  checkResultSchema = z4.object({
3033
3177
  issues: z4.array(issueSchema),
3034
3178
  advisories: z4.array(advisorySchema),
@@ -3038,8 +3182,121 @@ var init_findings = __esm({
3038
3182
  }
3039
3183
  });
3040
3184
 
3041
- // src/automation/store.ts
3185
+ // src/automation/notifications.ts
3042
3186
  import { z as z5 } from "zod";
3187
+ function condition(finding) {
3188
+ const source = finding.current ?? finding.original;
3189
+ const e = source.evidence;
3190
+ const id = e.kind === "decision-anchor" ? digest([source.type, source.anchor.doc, e.decisionId]) : finding.id;
3191
+ const result = { level: "confidence" in source ? 2 : 1, facts: [] };
3192
+ switch (e.kind) {
3193
+ case "count-mismatch":
3194
+ result.magnitude = Math.abs(e.actual - e.claimed);
3195
+ break;
3196
+ case "unmentioned-dir":
3197
+ result.magnitude = e.sourceFileCount;
3198
+ break;
3199
+ case "decision-anchor":
3200
+ result.facts = [...e.changedFiles].sort();
3201
+ break;
3202
+ case "doc-behind-manifests":
3203
+ result.facts = (e.matches ?? []).map((m) => JSON.stringify([m.manifest, m.dependency, m.after])).sort();
3204
+ break;
3205
+ }
3206
+ return { id, condition: result };
3207
+ }
3208
+ function notificationConditions(findings) {
3209
+ const active = findings.filter((f) => f.status !== "resolved");
3210
+ const currentIds = new Set(active.filter((f) => f.current).map((f) => condition(f).id));
3211
+ const result = {};
3212
+ for (const f of active) {
3213
+ const value = condition(f);
3214
+ if (!f.current && currentIds.has(value.id)) continue;
3215
+ const previous = result[value.id];
3216
+ result[value.id] = previous ? {
3217
+ level: Math.max(previous.level, value.condition.level),
3218
+ facts: [.../* @__PURE__ */ new Set([...previous.facts, ...value.condition.facts])].sort(),
3219
+ ...previous.magnitude !== void 0 || value.condition.magnitude !== void 0 ? { magnitude: Math.max(previous.magnitude ?? 0, value.condition.magnitude ?? 0) } : {}
3220
+ } : value.condition;
3221
+ }
3222
+ return result;
3223
+ }
3224
+ function createNotificationState(findings, head) {
3225
+ return { version: 1, baselineHead: head, baseline: notificationConditions(findings), delivered: {}, cleared: [] };
3226
+ }
3227
+ function worsened(current, previous) {
3228
+ return !previous || current.level > previous.level || (current.magnitude ?? 0) > (previous.magnitude ?? 0) || current.facts.some((fact) => !previous.facts.includes(fact));
3229
+ }
3230
+ function completionFindings(state, findings) {
3231
+ const current = notificationConditions(findings);
3232
+ const cleared = new Set(state.cleared);
3233
+ const resolvedIds = new Set(findings.filter((f) => f.status === "resolved").map((f) => condition(f).id));
3234
+ for (const id of Object.keys(state.baseline)) if (resolvedIds.has(id) && !current[id]) cleared.add(id);
3235
+ state.cleared = [...cleared];
3236
+ const selected = [];
3237
+ const selectedIds = /* @__PURE__ */ new Set();
3238
+ const delivered = { ...state.delivered };
3239
+ for (const finding of findings) {
3240
+ const value = condition(finding);
3241
+ const previous = state.delivered[value.id];
3242
+ value.condition = current[value.id] ?? value.condition;
3243
+ if (finding.status === "unverified" || !finding.current && finding.status !== "resolved") {
3244
+ continue;
3245
+ }
3246
+ if (finding.status === "resolved") {
3247
+ if (!current[value.id]) delete delivered[value.id];
3248
+ continue;
3249
+ }
3250
+ const eligible = cleared.has(value.id) || worsened(value.condition, state.baseline[value.id]);
3251
+ if (!eligible) continue;
3252
+ if (!selectedIds.has(value.id) && worsened(value.condition, previous)) {
3253
+ selectedIds.add(value.id);
3254
+ const source = finding.current;
3255
+ selected.push(source.evidence.kind === "decision-anchor" ? { ...finding, current: { ...source, evidence: { ...source.evidence, changedFiles: value.condition.facts } } } : finding);
3256
+ if (selected.length <= MAX_COMPLETION_FINDINGS) delivered[value.id] = value.condition;
3257
+ }
3258
+ }
3259
+ state.delivered = delivered;
3260
+ return selected;
3261
+ }
3262
+ function completionSummary(findings, reportPath) {
3263
+ if (!findings.length) return null;
3264
+ const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
3265
+ return [
3266
+ `Mason: ${findings.length} new or worsened finding(s) since the retained baseline.`,
3267
+ ...findings.slice(0, MAX_COMPLETION_FINDINGS).map((f) => {
3268
+ 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)}`;
3271
+ }),
3272
+ ...findings.length > MAX_COMPLETION_FINDINGS ? [`${findings.length - MAX_COMPLETION_FINDINGS} more in the report; pending later feedback.`] : [],
3273
+ `Evidence: ${reportPath}. Explicit audit: mason_automation(action: "check").`,
3274
+ "Inspect findings within the authorized work. Concurrent changes may also contribute; this summary does not establish who introduced them."
3275
+ ].join("\n");
3276
+ }
3277
+ var conditionSchema, MAX_COMPLETION_FINDINGS, notificationStateSchema;
3278
+ var init_notifications = __esm({
3279
+ "src/automation/notifications.ts"() {
3280
+ "use strict";
3281
+ init_findings();
3282
+ conditionSchema = z5.object({
3283
+ level: z5.number(),
3284
+ facts: z5.array(z5.string()),
3285
+ magnitude: z5.number().optional()
3286
+ });
3287
+ MAX_COMPLETION_FINDINGS = 4;
3288
+ notificationStateSchema = z5.object({
3289
+ version: z5.literal(1),
3290
+ baselineHead: z5.string(),
3291
+ baseline: z5.record(conditionSchema),
3292
+ delivered: z5.record(conditionSchema),
3293
+ cleared: z5.array(z5.string())
3294
+ });
3295
+ }
3296
+ });
3297
+
3298
+ // src/automation/store.ts
3299
+ import { z as z6 } from "zod";
3043
3300
  function parseState(raw) {
3044
3301
  try {
3045
3302
  return stateSchema.parse(raw);
@@ -3055,50 +3312,53 @@ var init_store = __esm({
3055
3312
  "src/automation/store.ts"() {
3056
3313
  "use strict";
3057
3314
  init_store_lock();
3058
- hostSchema = z5.enum(["claude", "codex"]);
3315
+ init_notifications();
3316
+ hostSchema = z6.enum(["claude", "codex"]);
3059
3317
  events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
3060
- stateSchema = z5.object({
3061
- version: z5.literal(1),
3062
- root: z5.string(),
3063
- gitDir: z5.string(),
3064
- branch: z5.string(),
3065
- baselines: z5.array(z5.object({ path: z5.string(), at: z5.string(), event: z5.string(), fingerprint: z5.string() })).max(128),
3066
- sessions: z5.record(z5.object({
3318
+ stateSchema = z6.object({
3319
+ version: z6.literal(1),
3320
+ root: z6.string(),
3321
+ gitDir: z6.string(),
3322
+ branch: z6.string(),
3323
+ baselines: z6.array(z6.object({ path: z6.string(), at: z6.string(), event: z6.string(), fingerprint: z6.string() })).max(128),
3324
+ sessions: z6.record(z6.object({
3067
3325
  host: hostSchema,
3068
- seen: z5.string().nullable(),
3069
- continued: z5.boolean(),
3070
- initialIssues: z5.array(z5.string()),
3071
- initialDocs: z5.record(z5.string().nullable()),
3072
- lastUsed: z5.string(),
3073
- mutationObserved: z5.boolean(),
3074
- pending: z5.record(z5.string()),
3075
- coverageGaps: z5.array(z5.string()),
3076
- events: z5.record(z5.object({ at: z5.string(), count: z5.number().int().positive() }))
3326
+ lastUsed: z6.string(),
3327
+ notifiedDiagnostics: z6.array(z6.string()).default([]),
3328
+ pending: z6.record(z6.string()),
3329
+ coverageGaps: z6.array(z6.string()),
3330
+ events: z6.record(z6.object({ at: z6.string(), count: z6.number().int().positive() }))
3077
3331
  })),
3078
- updatedAt: z5.string(),
3079
- fingerprint: z5.string().nullable(),
3080
- latest: z5.string().nullable(),
3081
- analysis: z5.object({ id: z5.string(), fingerprint: z5.string(), completedAt: z5.number(), path: z5.string() }).optional()
3332
+ updatedAt: z6.string(),
3333
+ fingerprint: z6.string().nullable(),
3334
+ latest: z6.string().nullable(),
3335
+ notifications: notificationStateSchema.optional(),
3336
+ agentNotifications: notificationStateSchema.optional(),
3337
+ analysis: z6.object({ id: z6.string(), fingerprint: z6.string(), completedAt: z6.number(), path: z6.string() }).optional()
3082
3338
  });
3083
3339
  }
3084
3340
  });
3085
3341
 
3086
3342
  // src/audit/advisory-review.ts
3087
3343
  import fs10 from "fs/promises";
3088
- import { z as z6 } from "zod";
3344
+ import { z as z7 } from "zod";
3089
3345
  async function readRecord(root, id) {
3090
3346
  const raw = await readStoreJson(root, recordPath(id));
3091
3347
  if (raw === null) return null;
3092
3348
  const record = recordSchema.parse(raw);
3093
3349
  const { digest: stored, ...payload } = record;
3094
- if (record.id !== id || digest(payload) !== stored || record.events.some((event) => findingId(event.finding) !== id || event.evidence.fingerprint !== digest([event.evidence.scope, event.evidence.tree, event.evidence.lastScopeCommit, event.evidence.paths]))) {
3350
+ if (record.id !== id || digest(payload) !== stored || record.events.some((event) => findingId(event.finding) !== id || (event.outcome === "dismissed" ? !event.reasonCode || event.finding.type !== "deps-changed" : !event.reviewer || !!event.reasonCode) || event.evidence.fingerprint !== evidenceFingerprint(event.evidence, event.reasonCode))) {
3095
3351
  throw new Error("Advisory review record is inconsistent or modified; inspect its history.");
3096
3352
  }
3097
3353
  return record;
3098
3354
  }
3099
- function scopes(finding) {
3355
+ function evidenceFingerprint(evidence, reason2) {
3356
+ return digest([evidence.scope, evidence.tree, ...reason2 === "no-dependency-claims" ? [] : [evidence.lastScopeCommit], evidence.paths]);
3357
+ }
3358
+ function scopes(finding, reason2) {
3100
3359
  const files = [finding.anchor.doc], e = finding.evidence;
3101
3360
  if (e.kind === "decision-anchor") throw new Error("Use review_decision for decision acceptance, reaffirmation or retirement.");
3361
+ if (reason2 === "no-dependency-claims" && e.kind === "doc-behind-manifests") return [`:(literal)${finding.anchor.doc}`];
3102
3362
  if (e.kind === "doc-behind-manifests") return [`:(literal)${finding.anchor.doc}`, ...manifestPathspecs(finding.anchor.doc)];
3103
3363
  if (e.kind === "missing-path") files.push(...e.scope?.candidates ?? [e.resolvedPath ?? e.claimed]);
3104
3364
  else if (e.kind === "missing-script") files.push(...e.manifestsChecked, ...e.scope?.candidates ?? []);
@@ -3109,9 +3369,9 @@ function scopes(finding) {
3109
3369
  return `:(literal)${file}`;
3110
3370
  }).sort();
3111
3371
  }
3112
- async function reviewEvidence(root, finding) {
3372
+ async function reviewEvidence(root, finding, reason2) {
3113
3373
  if (normalizeRepoPath(finding.anchor.doc) !== finding.anchor.doc) throw new Error("Invalid advisory document path.");
3114
- const scope = scopes(finding);
3374
+ const scope = scopes(finding, reason2);
3115
3375
  const head = (await git2(root, "rev-parse", "HEAD")).trim();
3116
3376
  const tracked = (await git2(root, "ls-files", "--cached", "-z", "--", ...scope)).split("\0").filter(Boolean);
3117
3377
  if (tracked.length > 2e4) throw new Error("Advisory scope exceeds 20,000 tracked files.");
@@ -3137,7 +3397,7 @@ async function reviewEvidence(root, finding) {
3137
3397
  if (!tracked.includes(file) && await pathExists(root, file)) dirty = true;
3138
3398
  }
3139
3399
  }
3140
- const fingerprint = digest([scope, tree, lastScopeCommit, paths]);
3400
+ const fingerprint = evidenceFingerprint({ head, scope, tree, lastScopeCommit, paths, fingerprint: "" }, reason2);
3141
3401
  if ((await git2(root, "rev-parse", "HEAD")).trim() !== head) throw new Error("HEAD changed while collecting review evidence.");
3142
3402
  return {
3143
3403
  ...evidenceSchema2.parse({ head, scope, tree, lastScopeCommit, paths, fingerprint }),
@@ -3184,7 +3444,7 @@ async function assessAdvisory(root, finding, baselineHead) {
3184
3444
  const record = await readRecord(root, id);
3185
3445
  if (!record) return null;
3186
3446
  const event = record.events.at(-1);
3187
- const current = await reviewEvidence(root, event.finding);
3447
+ const current = await reviewEvidence(root, event.finding, event.reasonCode);
3188
3448
  await git2(root, "merge-base", "--is-ancestor", event.evidence.head, current.head);
3189
3449
  const base = {
3190
3450
  id,
@@ -3194,7 +3454,7 @@ async function assessAdvisory(root, finding, baselineHead) {
3194
3454
  outcome: event.outcome,
3195
3455
  reviewedHead: event.evidence.head
3196
3456
  };
3197
- if (current.dirty || current.fingerprint !== event.evidence.fingerprint || digest(scopes(finding)) !== digest(event.evidence.scope)) return { ...base, status: "reopened", reason: "Relevant document or repository evidence changed after the assessment; prepare a new review." };
3457
+ if (current.dirty || current.fingerprint !== event.evidence.fingerprint || digest(scopes(finding, event.reasonCode)) !== digest(event.evidence.scope)) return { ...base, status: "reopened", reason: "Relevant document or repository evidence changed after the assessment; prepare a new review." };
3198
3458
  return { ...base, status: event.outcome === "deferred" ? "deferred" : "current", reason: event.outcome === "deferred" ? "The review was deferred and remains outstanding." : "An explicit assessment covers the current committed scope. This does not approve an engineering decision or certify correctness." };
3199
3459
  } catch (error) {
3200
3460
  return { id, status: "unverified", reason: error instanceof Error ? error.message : String(error) };
@@ -3222,9 +3482,13 @@ async function reviewAdvisory(rootDir, input) {
3222
3482
  decisionId: finding.evidence.decisionId,
3223
3483
  hint: "Use review_decision to prepare evidence and record authorized acceptance, reaffirmation or retirement, then verify this original repair baseline. An advisory assessment cannot approve a decision."
3224
3484
  };
3485
+ if (request.action === "dismiss") {
3486
+ if (finding.type !== "deps-changed" || finding.evidence.kind !== "doc-behind-manifests") throw new Error("One-step dismissal is only available for dependency advisories.");
3487
+ if (!request.reasonCode || !request.note) throw new Error("Dismissal requires reasonCode and note; it does not require a reviewer or prepared token.");
3488
+ } else if (request.reasonCode) throw new Error("reasonCode is only used with dismiss.");
3225
3489
  const prepare = async () => {
3226
3490
  const record = await readRecord(root, request.findingId);
3227
- const evidence = await reviewEvidence(root, finding);
3491
+ const evidence = await reviewEvidence(root, finding, request.reasonCode);
3228
3492
  await git2(root, "merge-base", "--is-ancestor", baseline.original.headHash, evidence.head);
3229
3493
  const token = digest({ baseline: baseline.original, finding, evidence, record });
3230
3494
  return { record, evidence, token };
@@ -3253,10 +3517,10 @@ async function reviewAdvisory(rootDir, input) {
3253
3517
  hint: "Inspect the original finding, scoped document and relevant code changes. Record addressed, inapplicable or deferred only when the user or cited project review authorizes that assessment. Supply the actual reviewer, reason in note and reviewToken. Relevant edits must be committed first. Deferral remains outstanding. Review identities are recorded assertions, not authenticated approvals."
3254
3518
  };
3255
3519
  }
3256
- if (!request.reviewer || !request.note || !request.reviewToken) throw new Error("Prepare first; recording an assessment requires reviewToken, reviewer and note.");
3520
+ if (request.action !== "dismiss" && (!request.reviewer || !request.note || !request.reviewToken)) throw new Error("Prepare first; recording an assessment requires reviewToken, reviewer and note.");
3257
3521
  return withLock(root, ".mason/reports/advisory-review-locks/" + request.findingId, async () => {
3258
3522
  const state = await prepare();
3259
- if (state.token !== request.reviewToken) throw new Error("Review conflict: evidence or review history changed since preparation. Prepare and inspect a new review.");
3523
+ if (request.action !== "dismiss" && state.token !== request.reviewToken) throw new Error("Review conflict: evidence or review history changed since preparation. Prepare and inspect a new review.");
3260
3524
  if (state.evidence.dirty) throw new Error("Commit relevant document and repository edits before recording the assessment. Unrelated local work may remain.");
3261
3525
  if ((state.record?.events.length ?? 0) >= 200) throw new Error("Review history limit reached; existing events were retained.");
3262
3526
  const { dirty, documentDigest, ...evidence } = state.evidence;
@@ -3264,7 +3528,8 @@ async function reviewAdvisory(rootDir, input) {
3264
3528
  at: (/* @__PURE__ */ new Date()).toISOString(),
3265
3529
  reviewer: request.reviewer,
3266
3530
  note: request.note,
3267
- outcome: request.action,
3531
+ outcome: request.action === "dismiss" ? "dismissed" : request.action,
3532
+ reasonCode: request.reasonCode,
3268
3533
  finding,
3269
3534
  evidence
3270
3535
  });
@@ -3275,11 +3540,11 @@ async function reviewAdvisory(rootDir, input) {
3275
3540
  status: "recorded",
3276
3541
  recordPath: recordPath(request.findingId),
3277
3542
  event,
3278
- hint: "Review and commit this assessment record through the normal project workflow, then verify the original repair baseline. Relevant changes reopen the assessment; deferral remains outstanding."
3543
+ hint: request.action === "dismiss" ? "Dismissal saved and effective locally. Commit the record to share it. No-dependency-claims dismissals reopen on document content changes; unrelated-manifest-change dismissals reopen on scoped document or manifest changes. This does not approve a decision." : "Review and commit this assessment record through the normal project workflow, then verify the original repair baseline. Relevant changes reopen the assessment; deferral remains outstanding."
3279
3544
  };
3280
3545
  });
3281
3546
  }
3282
- var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
3547
+ var git2, hashSchema, commitSchema2, evidenceSchema2, dismissalReasonSchema, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
3283
3548
  var init_advisory_review = __esm({
3284
3549
  "src/audit/advisory-review.ts"() {
3285
3550
  "use strict";
@@ -3295,39 +3560,42 @@ var init_advisory_review = __esm({
3295
3560
  init_decisions();
3296
3561
  init_drift2();
3297
3562
  git2 = async (root, ...args) => (await (["log", "ls-tree", "ls-files", "merge-base"].includes(args[0]) ? inspectionGit : execGit)(args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
3298
- hashSchema = z6.string().regex(/^[a-f0-9]{64}$/);
3299
- commitSchema2 = z6.string().regex(/^[a-f0-9]{40,64}$/);
3300
- evidenceSchema2 = z6.object({
3563
+ hashSchema = z7.string().regex(/^[a-f0-9]{64}$/);
3564
+ commitSchema2 = z7.string().regex(/^[a-f0-9]{40,64}$/);
3565
+ evidenceSchema2 = z7.object({
3301
3566
  head: commitSchema2,
3302
3567
  fingerprint: hashSchema,
3303
- scope: z6.array(z6.string()).min(1).max(1e3),
3304
- tree: z6.string().max(2 * 1024 * 1024),
3568
+ scope: z7.array(z7.string()).min(1).max(1e3),
3569
+ tree: z7.string().max(2 * 1024 * 1024),
3305
3570
  lastScopeCommit: commitSchema2,
3306
- paths: z6.array(z6.object({ path: z6.string(), exists: z6.boolean() })).max(1e3)
3571
+ paths: z7.array(z7.object({ path: z7.string(), exists: z7.boolean() })).max(1e3)
3307
3572
  });
3308
- eventSchema2 = z6.object({
3309
- at: z6.string().datetime(),
3310
- reviewer: z6.string().trim().min(1).max(200),
3311
- note: z6.string().trim().min(1).max(2500),
3312
- outcome: z6.enum(["addressed", "inapplicable", "deferred"]),
3573
+ dismissalReasonSchema = z7.enum(["no-dependency-claims", "unrelated-manifest-change"]);
3574
+ eventSchema2 = z7.object({
3575
+ at: z7.string().datetime(),
3576
+ reviewer: z7.string().trim().min(1).max(200).optional(),
3577
+ note: z7.string().trim().min(1).max(2500),
3578
+ outcome: z7.enum(["addressed", "inapplicable", "deferred", "dismissed"]),
3579
+ reasonCode: dismissalReasonSchema.optional(),
3313
3580
  finding: advisorySchema.refine((finding) => finding.evidence.kind !== "decision-anchor", "Decision findings use decision reviews"),
3314
3581
  evidence: evidenceSchema2
3315
3582
  });
3316
- recordSchema = z6.object({ version: z6.literal(1), id: hashSchema, events: z6.array(eventSchema2).min(1).max(200), digest: hashSchema });
3317
- reviewSummarySchema = z6.object({
3583
+ recordSchema = z7.object({ version: z7.literal(1), id: hashSchema, events: z7.array(eventSchema2).min(1).max(200), digest: hashSchema });
3584
+ reviewSummarySchema = z7.object({
3318
3585
  id: hashSchema,
3319
- status: z6.enum(["current", "reopened", "deferred", "unverified"]),
3320
- reason: z6.string(),
3321
- recordPath: z6.string().optional(),
3322
- reviewer: z6.string().optional(),
3323
- note: z6.string().optional(),
3324
- outcome: z6.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired"]).optional(),
3325
- reviewedHead: z6.string().optional()
3586
+ status: z7.enum(["current", "reopened", "deferred", "unverified"]),
3587
+ reason: z7.string(),
3588
+ recordPath: z7.string().optional(),
3589
+ reviewer: z7.string().optional(),
3590
+ note: z7.string().optional(),
3591
+ outcome: z7.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired", "dismissed"]).optional(),
3592
+ reviewedHead: z7.string().optional()
3326
3593
  });
3327
- advisoryReviewRequest = z6.object({
3328
- baselinePath: z6.string().min(1),
3594
+ advisoryReviewRequest = z7.object({
3595
+ baselinePath: z7.string().min(1),
3329
3596
  findingId: hashSchema,
3330
- action: z6.enum(["prepare", "addressed", "inapplicable", "deferred"]).default("prepare"),
3597
+ action: z7.enum(["prepare", "addressed", "inapplicable", "deferred", "dismiss"]).default("prepare"),
3598
+ reasonCode: dismissalReasonSchema.optional(),
3331
3599
  reviewer: eventSchema2.shape.reviewer.optional(),
3332
3600
  note: eventSchema2.shape.note.optional(),
3333
3601
  reviewToken: hashSchema.optional()
@@ -3338,12 +3606,12 @@ var init_advisory_review = __esm({
3338
3606
 
3339
3607
  // src/audit/audit.ts
3340
3608
  import fs11 from "fs/promises";
3341
- import path19 from "path";
3609
+ import path20 from "path";
3342
3610
  async function computeAudit(rootDir, options = {}) {
3343
3611
  return profilePhase("audit.current", () => auditCurrent(rootDir, options));
3344
3612
  }
3345
3613
  async function auditCurrent(rootDir, options) {
3346
- const resolvedRoot = path19.resolve(rootDir);
3614
+ const resolvedRoot = path20.resolve(rootDir);
3347
3615
  const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
3348
3616
  if (docs.length === 0) return null;
3349
3617
  const report = {
@@ -3377,7 +3645,7 @@ async function auditCurrent(rootDir, options) {
3377
3645
  }
3378
3646
  let decisionsPresent = false;
3379
3647
  try {
3380
- await fs11.access(path19.join(resolvedRoot, ".mason", "decisions"));
3648
+ await fs11.access(path20.join(resolvedRoot, ".mason", "decisions"));
3381
3649
  decisionsPresent = true;
3382
3650
  } catch {
3383
3651
  }
@@ -3389,13 +3657,21 @@ async function auditCurrent(rootDir, options) {
3389
3657
  changesSinceDoc,
3390
3658
  decisionsPresent
3391
3659
  };
3660
+ const policy = (await loadProjectConfig(resolvedRoot)).audit;
3392
3661
  const selected = options.checks ?? ALL_CHECKS;
3393
3662
  for (const name of ALL_CHECKS) {
3394
3663
  if (!selected.includes(name)) continue;
3395
- const { issues, advisories, suppressedAdvisories, skipped } = await (options.runCheck ? options.runCheck(name, ctx) : CHECKS[name](ctx));
3664
+ const exclusions = policy?.exclude?.[name] ?? [];
3665
+ const included = await includedCheckPaths(resolvedRoot, docs.map((doc) => doc.path), exclusions);
3666
+ const excluded = docs.filter((doc) => !included.has(doc.path)).map((doc) => ({ check: name, doc: doc.path }));
3667
+ if (excluded.length) (report.excludedChecks ??= []).push(...excluded);
3668
+ const scoped = { ...ctx, docs: docs.filter((doc) => included.has(doc.path)) };
3669
+ const { issues, advisories, suppressedAdvisories, skipped } = await (options.runCheck ? options.runCheck(name, scoped) : CHECKS[name](scoped));
3396
3670
  report.checksRun.push(name);
3397
3671
  report.issues.push(...issues);
3398
- report.advisories.push(...advisories);
3672
+ const advisoryPaths = await includedCheckPaths(resolvedRoot, [...new Set(advisories.map((f) => f.anchor.doc))], exclusions);
3673
+ report.advisories.push(...advisories.filter((f) => advisoryPaths.has(f.anchor.doc)));
3674
+ for (const f of advisories) if (!advisoryPaths.has(f.anchor.doc)) (report.excludedChecks ??= []).push({ check: name, doc: f.anchor.doc });
3399
3675
  report.suppressedAdvisories.push(...suppressedAdvisories ?? []);
3400
3676
  report.skippedChecks.push(...skipped);
3401
3677
  }
@@ -3407,6 +3683,8 @@ async function auditCurrent(rootDir, options) {
3407
3683
  var init_audit = __esm({
3408
3684
  "src/audit/audit.ts"() {
3409
3685
  "use strict";
3686
+ init_files();
3687
+ init_policy();
3410
3688
  init_profile();
3411
3689
  init_drift();
3412
3690
  init_snapshot();
@@ -3432,9 +3710,9 @@ __export(repair_exports, {
3432
3710
  withRepairSession: () => withRepairSession
3433
3711
  });
3434
3712
  import fs12 from "fs/promises";
3435
- import path20 from "path";
3713
+ import path21 from "path";
3436
3714
  import { randomUUID as randomUUID2 } from "crypto";
3437
- import { z as z7 } from "zod";
3715
+ import { z as z8 } from "zod";
3438
3716
  function recheckable(finding) {
3439
3717
  return !("confidence" in finding) && finding.resolution === "recheck" && ["deleted-reference", "dead-command", "new-module"].includes(finding.type) && ["missing-path", "missing-script", "unmentioned-dir"].includes(finding.evidence.kind);
3440
3718
  }
@@ -3453,7 +3731,7 @@ async function docState(root) {
3453
3731
  docs.push([doc, null]);
3454
3732
  }
3455
3733
  }
3456
- return digest(docs);
3734
+ return digest([docs, (await loadProjectConfig(root)).audit]);
3457
3735
  }
3458
3736
  async function stableAudit(root, checks, options = {}) {
3459
3737
  return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
@@ -3505,7 +3783,7 @@ async function withRepairSession(root, options, run, validate, cachedAudit) {
3505
3783
  }
3506
3784
  async function prepareOriginal(rootDir, checks, options, session) {
3507
3785
  const root = await fs12.realpath(rootDir);
3508
- const selected = z7.array(checkSchema).nonempty().parse(checks);
3786
+ const selected = z8.array(checkSchema).nonempty().parse(checks);
3509
3787
  const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
3510
3788
  if (!report) throw new Error("No context files found to prepare a repair.");
3511
3789
  if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
@@ -3522,8 +3800,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
3522
3800
  }
3523
3801
  async function loadRepairBaseline(rootDir, baselinePath) {
3524
3802
  const root = await fs12.realpath(rootDir);
3525
- const declaredRoot = path20.resolve(rootDir);
3526
- const relative = path20.isAbsolute(baselinePath) ? path20.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
3803
+ const declaredRoot = path21.resolve(rootDir);
3804
+ const relative = path21.isAbsolute(baselinePath) ? path21.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
3527
3805
  const stored = baselineSchema.parse(await readStoreJson(root, relative));
3528
3806
  const { digest: savedDigest, ...payload } = stored;
3529
3807
  if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
@@ -3572,6 +3850,9 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
3572
3850
  if (now && ("confidence" in finding || "confidence" in now)) {
3573
3851
  return { ...base, status: "unresolved", reason: "The original check still reports this claim." };
3574
3852
  }
3853
+ if (current.excludedChecks?.some((excluded) => excluded.check === finding.type && excluded.doc === finding.anchor.doc)) {
3854
+ return { ...base, status: "resolved", reason: "Excluded by project audit policy; this is not a correctness assessment. Original evidence is retained." };
3855
+ }
3575
3856
  const skipped = current.skippedChecks.filter((s) => s.check === finding.type && (!s.doc || s.doc === finding.anchor.doc));
3576
3857
  if (!current.checksRun?.includes(finding.type) || skipped.length) {
3577
3858
  return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
@@ -3582,6 +3863,9 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
3582
3863
  if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
3583
3864
  return { ...base, review, status: "review-required", reason: review.reason };
3584
3865
  }
3866
+ if (!("confidence" in finding) && finding.evidence.kind === "doc-behind-manifests" && !finding.evidence.matches?.length && (!now || !("confidence" in now) && now.resolution === "informational")) {
3867
+ return { ...base, status: "resolved", reason: "Manifest recency alone establishes no affected passage. Retained as information; no assessment is required." };
3868
+ }
3585
3869
  if (!("confidence" in finding) && (!recheckable(finding) || now)) {
3586
3870
  return {
3587
3871
  ...base,
@@ -3610,7 +3894,7 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
3610
3894
  const reviewedIds = new Set(current?.advisoryReviews?.filter((review) => review.status === "current").map((review) => review.id));
3611
3895
  const counts = { resolved: 0, unresolved: 0, "review-required": 0, unverified: 0 };
3612
3896
  for (const f of findings) counts[f.status]++;
3613
- const incomplete = diagnostics.length > 0 || counts.unverified > 0 || counts["review-required"] > 0 || (current?.skippedChecks.length ?? 0) > 0 || newFindings.some((f) => !("confidence" in f) && !reviewedIds.has(findingId(f)));
3897
+ const incomplete = diagnostics.length > 0 || counts.unverified > 0 || counts["review-required"] > 0 || (current?.skippedChecks.length ?? 0) > 0 || newFindings.some((f) => !("confidence" in f) && f.resolution !== "informational" && !reviewedIds.has(findingId(f)));
3614
3898
  const issuesRemain = counts.unresolved > 0 || newFindings.some((f) => "confidence" in f);
3615
3899
  return {
3616
3900
  version: 1,
@@ -3633,7 +3917,7 @@ function repairExitCode(report) {
3633
3917
  function formatRepairSummary(report) {
3634
3918
  return [
3635
3919
  "Repair verification: " + report.status + ". Baseline: " + report.baselinePath,
3636
- ...report.findings.map((f) => " [" + f.status + "] " + f.original.type + " " + f.original.anchor.doc + ": " + f.original.message + "\n " + f.reason + "\n findingId: " + f.id),
3920
+ ...report.findings.map((f) => " [" + f.status + "] " + f.original.type + " " + f.original.anchor.doc + ": " + (f.current?.message ?? "Historical evidence at " + report.baselineHead.slice(0, 7) + ": " + f.original.message) + "\n " + f.reason + "\n findingId: " + f.id),
3637
3921
  ...report.newFindings.map((f) => " [new] " + f.type + " " + f.anchor.doc + ": " + f.message),
3638
3922
  ...report.diagnostics.map((d) => " [unverified] " + d),
3639
3923
  ...(report.currentAudit?.skippedChecks ?? []).map((s) => " [skipped] " + s.check + ": " + s.reason),
@@ -3656,41 +3940,42 @@ var init_repair = __esm({
3656
3940
  init_findings();
3657
3941
  init_advisory_review();
3658
3942
  init_findings();
3659
- count2 = z7.number().int().nonnegative();
3660
- reportSchema = z7.object({
3661
- version: z7.literal(1),
3662
- root: z7.string(),
3663
- gitAvailable: z7.literal(true),
3943
+ count2 = z8.number().int().nonnegative();
3944
+ reportSchema = z8.object({
3945
+ version: z8.literal(1),
3946
+ root: z8.string(),
3947
+ gitAvailable: z8.literal(true),
3664
3948
  headHash: commitSchema.shape.hash,
3665
- checksRun: z7.array(checkSchema).nonempty(),
3666
- docs: z7.array(z7.object({
3667
- path: z7.string().refine((value) => normalizeRepoPath(value) === value),
3668
- scope: z7.string().optional(),
3669
- kind: z7.enum(["instructions", "readme"]).optional(),
3949
+ checksRun: z8.array(checkSchema).nonempty(),
3950
+ docs: z8.array(z8.object({
3951
+ path: z8.string().refine((value) => normalizeRepoPath(value) === value),
3952
+ scope: z8.string().optional(),
3953
+ kind: z8.enum(["instructions", "readme", "guide"]).optional(),
3670
3954
  lastCommit: commitSchema.nullable(),
3671
- dirty: z7.boolean(),
3955
+ dirty: z8.boolean(),
3672
3956
  lineCount: count2
3673
3957
  })).nonempty(),
3674
- decisionsChecked: z7.boolean(),
3675
- clean: z7.boolean(),
3676
- issues: z7.array(issueSchema),
3677
- advisories: z7.array(advisorySchema),
3678
- suppressedAdvisories: z7.array(advisorySchema).optional(),
3679
- skippedChecks: z7.array(z7.object({ check: z7.string(), reason: z7.string(), doc: z7.string().optional() }))
3958
+ decisionsChecked: z8.boolean(),
3959
+ clean: z8.boolean(),
3960
+ excludedChecks: z8.array(z8.object({ check: checkSchema, doc: z8.string() })).optional(),
3961
+ issues: z8.array(issueSchema),
3962
+ advisories: z8.array(advisorySchema),
3963
+ suppressedAdvisories: z8.array(advisorySchema).optional(),
3964
+ skippedChecks: z8.array(z8.object({ check: z8.string(), reason: z8.string(), doc: z8.string().optional() }))
3680
3965
  });
3681
- baselineSchema = z7.object({
3682
- kind: z7.literal("mason-audit-repair"),
3683
- version: z7.literal(1),
3684
- createdAt: z7.string().datetime(),
3966
+ baselineSchema = z8.object({
3967
+ kind: z8.literal("mason-audit-repair"),
3968
+ version: z8.literal(1),
3969
+ createdAt: z8.string().datetime(),
3685
3970
  report: reportSchema,
3686
- digest: z7.string().regex(/^[a-f0-9]{64}$/)
3971
+ digest: z8.string().regex(/^[a-f0-9]{64}$/)
3687
3972
  });
3688
- repairFindingSchema = z7.object({
3689
- id: z7.string(),
3690
- original: z7.union([issueSchema, advisorySchema]),
3691
- status: z7.enum(["resolved", "unresolved", "review-required", "unverified"]),
3692
- reason: z7.string(),
3693
- current: z7.union([issueSchema, advisorySchema]).optional(),
3973
+ repairFindingSchema = z8.object({
3974
+ id: z8.string(),
3975
+ original: z8.union([issueSchema, advisorySchema]),
3976
+ status: z8.enum(["resolved", "unresolved", "review-required", "unverified"]),
3977
+ reason: z8.string(),
3978
+ current: z8.union([issueSchema, advisorySchema]).optional(),
3694
3979
  review: reviewSummarySchema.optional()
3695
3980
  });
3696
3981
  }
@@ -3698,10 +3983,10 @@ var init_repair = __esm({
3698
3983
 
3699
3984
  // src/automation/evidence.ts
3700
3985
  import fs13 from "fs/promises";
3701
- import path21 from "path";
3986
+ import path22 from "path";
3702
3987
  import { createHash as createHash3 } from "crypto";
3703
- import fg5 from "fast-glob";
3704
- import { z as z8 } from "zod";
3988
+ import fg7 from "fast-glob";
3989
+ import { z as z9 } from "zod";
3705
3990
  async function git3(root, ...args) {
3706
3991
  return profilePhase("automation.git", async () => (await execGit(args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
3707
3992
  }
@@ -3718,6 +4003,11 @@ async function readInputs(root) {
3718
4003
  }
3719
4004
  async function collectInputs(root) {
3720
4005
  const docPaths = await discoverDocPaths(root);
4006
+ const auditPolicy = (await loadProjectConfig(root)).audit;
4007
+ const allowed = /* @__PURE__ */ new Map();
4008
+ for (const name of ["deleted-reference", "new-module", "stale-count", "dead-command"]) {
4009
+ allowed.set(name, await includedCheckPaths(root, docPaths, auditPolicy?.exclude?.[name] ?? []));
4010
+ }
3721
4011
  const [headText, docStatus, shallowPath, replacements] = await Promise.all([
3722
4012
  git3(root, "rev-parse", "HEAD"),
3723
4013
  docPaths.length ? git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...docPaths.map((p) => `:(literal)${p}`)) : "",
@@ -3727,7 +4017,7 @@ async function collectInputs(root) {
3727
4017
  const head = headText.trim();
3728
4018
  let shallow = null;
3729
4019
  try {
3730
- shallow = await fs13.readFile(path21.resolve(root, shallowPath.trim()), "utf8");
4020
+ shallow = await fs13.readFile(path22.resolve(root, shallowPath.trim()), "utf8");
3731
4021
  } catch (error) {
3732
4022
  if (error.code !== "ENOENT") throw error;
3733
4023
  }
@@ -3746,18 +4036,18 @@ async function collectInputs(root) {
3746
4036
  docContents.push([file, text2]);
3747
4037
  const parsed = { path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") };
3748
4038
  parsedDocs.push(parsed);
3749
- for (const claim of parsed.claims.paths) {
4039
+ for (const claim of allowed.get("deleted-reference").has(file) ? parsed.claims.paths : []) {
3750
4040
  const scope = pathClaimScope(file, claim);
3751
4041
  if (scope?.candidates.some(gitMetadataPath)) continue;
3752
4042
  for (const candidate of scope?.candidates ?? []) {
3753
4043
  if (optionalMasonPath(candidate)) continue;
3754
- const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path21.posix.dirname(candidate))]);
4044
+ const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path22.posix.dirname(candidate))]);
3755
4045
  claims.push([candidate, filePresent, parentPresent]);
3756
4046
  }
3757
4047
  }
3758
4048
  }
3759
- const combinedDocs = moduleDocumentation(parsedDocs);
3760
- const countClaims = parsedDocs.flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
4049
+ const combinedDocs = moduleDocumentation(parsedDocs.filter((doc) => allowed.get("new-module").has(doc.path)));
4050
+ const countClaims = parsedDocs.filter((doc) => allowed.get("stale-count").has(doc.path)).flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
3761
4051
  const decisionDirectory = await storePath(root, ".mason/decisions");
3762
4052
  const decisionPresence = await fs13.lstat(decisionDirectory).then((stat) => stat.isDirectory() ? "directory" : "file", (error) => {
3763
4053
  if (error.code === "ENOENT") return "absent";
@@ -3766,8 +4056,8 @@ async function collectInputs(root) {
3766
4056
  const [modules, counts, commands2, decisionFiles] = await Promise.all([
3767
4057
  combinedDocs ? moduleCandidates(root, combinedDocs) : [],
3768
4058
  Promise.all(countClaims.map(({ doc, claim }) => resolveDocCountSource(root, doc, claim))),
3769
- commandInputs(root, parsedDocs),
3770
- fg5(".mason/decisions/*.json", { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false })
4059
+ commandInputs(root, parsedDocs.filter((doc) => allowed.get("dead-command").has(doc.path))),
4060
+ fg7(".mason/decisions/*.json", { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false })
3771
4061
  ]);
3772
4062
  const decisions = await Promise.all(decisionFiles.sort().map(async (file) => {
3773
4063
  await storePath(root, file);
@@ -3777,7 +4067,7 @@ async function collectInputs(root) {
3777
4067
  git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
3778
4068
  git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
3779
4069
  ]) : ["", ""];
3780
- const common = [6, engineVersion, head, shallow, replacements, docContents];
4070
+ const common = [7, engineVersion, head, shallow, replacements, docContents, auditPolicy];
3781
4071
  const keys = {
3782
4072
  "deleted-reference": hash([common, claims, docStatus]),
3783
4073
  "new-module": hash([common, modules]),
@@ -3824,6 +4114,8 @@ var engineVersion, hash, content, cacheSchema;
3824
4114
  var init_evidence = __esm({
3825
4115
  "src/automation/evidence.ts"() {
3826
4116
  "use strict";
4117
+ init_policy();
4118
+ init_files();
3827
4119
  init_git_read();
3828
4120
  init_profile();
3829
4121
  init_docs();
@@ -3837,10 +4129,10 @@ var init_evidence = __esm({
3837
4129
  init_dead_command();
3838
4130
  init_storage();
3839
4131
  init_advisory_review();
3840
- engineVersion = true ? "0.19.0" : "development";
4132
+ engineVersion = true ? "0.19.2" : "development";
3841
4133
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
3842
4134
  content = readAuditInput;
3843
- cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
4135
+ cacheSchema = z9.object({ version: z9.literal(1), entries: z9.record(z9.object({ key: z9.string(), result: checkResultSchema })), digest: z9.string() });
3844
4136
  }
3845
4137
  });
3846
4138
 
@@ -3856,9 +4148,9 @@ __export(model_exports, {
3856
4148
  setupHostSchema: () => setupHostSchema,
3857
4149
  setupSchema: () => setupSchema
3858
4150
  });
3859
- import { z as z9 } from "zod";
4151
+ import { z as z10 } from "zod";
3860
4152
  import fs14 from "fs/promises";
3861
- import path22 from "path";
4153
+ import path23 from "path";
3862
4154
  async function loadSetup(root) {
3863
4155
  const raw = await readStoreJson(root, SETUP_PATH);
3864
4156
  if (raw === null) return null;
@@ -3867,7 +4159,7 @@ async function loadSetup(root) {
3867
4159
  async function effectiveSetup(root) {
3868
4160
  const local = await loadSetup(root);
3869
4161
  if (local) return { root, setup: local };
3870
- const common = await fs14.realpath(path22.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
4162
+ const common = await fs14.realpath(path23.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
3871
4163
  const own = await fs14.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
3872
4164
  if (common === own) return { root, setup: null };
3873
4165
  const primary = (await git3(root, "worktree", "list", "--porcelain", "-z")).split("\0\0")[0].split("\0");
@@ -3880,7 +4172,7 @@ async function effectiveSetup(root) {
3880
4172
  if (/must be run in a work tree/.test(String(error.stderr))) return { root, setup: null };
3881
4173
  throw error;
3882
4174
  }
3883
- const inheritedCommon = await fs14.realpath(path22.resolve(
4175
+ const inheritedCommon = await fs14.realpath(path23.resolve(
3884
4176
  inheritedRoot,
3885
4177
  (await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim()
3886
4178
  ));
@@ -3903,29 +4195,29 @@ var init_model = __esm({
3903
4195
  "use strict";
3904
4196
  init_evidence();
3905
4197
  init_storage();
3906
- setupHostSchema = z9.object({
3907
- configuredVersion: z9.string(),
3908
- revision: z9.string().uuid(),
3909
- fingerprint: z9.string(),
3910
- mcpFingerprint: z9.string(),
3911
- instructions: z9.array(z9.string())
4198
+ setupHostSchema = z10.object({
4199
+ configuredVersion: z10.string(),
4200
+ revision: z10.string().uuid(),
4201
+ fingerprint: z10.string(),
4202
+ mcpFingerprint: z10.string(),
4203
+ instructions: z10.array(z10.string())
3912
4204
  });
3913
- setupSchema = z9.object({ version: z9.literal(2), hosts: z9.object({
4205
+ setupSchema = z10.object({ version: z10.literal(2), hosts: z10.object({
3914
4206
  codex: setupHostSchema.optional(),
3915
4207
  claude: setupHostSchema.optional()
3916
4208
  }) });
3917
4209
  SETUP_PATH = ".mason/local/setup.json";
3918
4210
  INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
3919
- inactiveHostsSchema = z9.object({ version: z9.literal(1), hosts: z9.array(z9.enum(["claude", "codex"])) });
3920
- receiptSchema = z9.object({
3921
- version: z9.literal(1),
3922
- host: z9.enum(["codex", "claude"]),
3923
- status: z9.enum(["installing", "configured"]),
3924
- initialReportPath: z9.string(),
3925
- initialBaselinePaths: z9.array(z9.string()),
3926
- root: z9.string(),
3927
- revision: z9.string().uuid(),
3928
- configuredAt: z9.string().optional()
4211
+ inactiveHostsSchema = z10.object({ version: z10.literal(1), hosts: z10.array(z10.enum(["claude", "codex"])) });
4212
+ receiptSchema = z10.object({
4213
+ version: z10.literal(1),
4214
+ host: z10.enum(["codex", "claude"]),
4215
+ status: z10.enum(["installing", "configured"]),
4216
+ initialReportPath: z10.string(),
4217
+ initialBaselinePaths: z10.array(z10.string()),
4218
+ root: z10.string(),
4219
+ revision: z10.string().uuid(),
4220
+ configuredAt: z10.string().optional()
3929
4221
  });
3930
4222
  }
3931
4223
  });
@@ -3963,7 +4255,7 @@ var init_launcher = __esm({
3963
4255
  "use strict";
3964
4256
  init_evidence();
3965
4257
  init_model();
3966
- executingVersion = () => true ? "0.19.0" : "development";
4258
+ executingVersion = () => true ? "0.19.2" : "development";
3967
4259
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3968
4260
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3969
4261
  }
@@ -4089,7 +4381,7 @@ __export(files_exports, {
4089
4381
  readText: () => readText
4090
4382
  });
4091
4383
  import fs18 from "fs/promises";
4092
- import path26 from "path";
4384
+ import path27 from "path";
4093
4385
  import { randomUUID as randomUUID3 } from "crypto";
4094
4386
  async function readText(root, file) {
4095
4387
  try {
@@ -4122,7 +4414,7 @@ async function applyEdit(root, edit) {
4122
4414
  return true;
4123
4415
  }
4124
4416
  const file = await storePath(root, edit.path, true);
4125
- const temporary = path26.join(path26.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
4417
+ const temporary = path27.join(path27.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
4126
4418
  try {
4127
4419
  const mode = await fs18.stat(file).then((s) => s.mode & 511, () => 384);
4128
4420
  const handle = await fs18.open(temporary, "wx", mode);
@@ -4160,7 +4452,7 @@ __export(execution_exports, {
4160
4452
  import os2 from "os";
4161
4453
  import fs19 from "fs/promises";
4162
4454
  import { randomUUID as randomUUID4 } from "crypto";
4163
- import { z as z12 } from "zod";
4455
+ import { z as z13 } from "zod";
4164
4456
  function parseExecution(raw) {
4165
4457
  try {
4166
4458
  return executionSchema.parse(raw);
@@ -4178,7 +4470,7 @@ function automationFailure(error) {
4178
4470
  codes.add(String(cause.code));
4179
4471
  cause = cause.cause;
4180
4472
  }
4181
- 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 z12.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";
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";
4182
4474
  return { code, message: message2, retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code), receiptRecorded: false };
4183
4475
  }
4184
4476
  function failureMessage(error) {
@@ -4307,38 +4599,100 @@ var init_execution = __esm({
4307
4599
  init_store();
4308
4600
  init_evidence();
4309
4601
  init_storage();
4310
- failureSchema = z12.object({
4311
- code: z12.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
4312
- message: z12.string(),
4313
- retryable: z12.boolean(),
4314
- receiptRecorded: z12.boolean()
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()
4315
4607
  });
4316
- attemptSchema = z12.object({
4317
- id: z12.string().regex(/^[a-zA-Z0-9_-]+$/),
4318
- lease: z12.literal(true).optional(),
4319
- event: z12.string(),
4320
- startedAt: z12.string(),
4321
- finishedAt: z12.string().optional(),
4322
- durationMs: z12.number().nonnegative().optional(),
4323
- pid: z12.number().int().positive(),
4324
- host: z12.string(),
4325
- status: z12.enum(["running", "completed", "failed", "unknown"]),
4326
- verificationStatus: z12.string().optional(),
4327
- reportPath: z12.string().optional(),
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(),
4328
4620
  failure: failureSchema.optional()
4329
4621
  });
4330
- executionSchema = z12.object({ version: z12.literal(1), attempts: z12.array(attemptSchema).max(160), discardedAttempts: z12.number().int().nonnegative().default(0) });
4331
- notificationsSchema = z12.object({ version: z12.literal(1), sessions: z12.record(z12.object({
4332
- signature: z12.string(),
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(),
4333
4625
  code: failureSchema.shape.code,
4334
- message: z12.string(),
4335
- count: z12.number().int().positive(),
4336
- lastAt: z12.string()
4626
+ message: z13.string(),
4627
+ count: z13.number().int().positive(),
4628
+ lastAt: z13.string()
4337
4629
  })) });
4338
4630
  leasePath = (directory, id) => `${directory}/executions/${id}.json`;
4339
4631
  }
4340
4632
  });
4341
4633
 
4634
+ // src/decisions/pending.ts
4635
+ var pending_exports = {};
4636
+ __export(pending_exports, {
4637
+ pendingKnowledge: () => pendingKnowledge
4638
+ });
4639
+ async function pendingKnowledge(root, now = Date.now()) {
4640
+ try {
4641
+ const [store, status] = await Promise.all([
4642
+ loadDecisionStore(root),
4643
+ execGit(
4644
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".mason/decisions"],
4645
+ { cwd: root, timeout: 1e4, maxBuffer: 1024 * 1024 }
4646
+ )
4647
+ ]);
4648
+ const changed = /* @__PURE__ */ new Set();
4649
+ const entries = status.stdout.split("\0");
4650
+ for (let i = 0; i < entries.length; i++) {
4651
+ const entry = entries[i];
4652
+ if (!entry) continue;
4653
+ const file = entry.slice(3);
4654
+ if (/^\.mason\/decisions\/[^/]+\.json$/.test(file)) changed.add(file);
4655
+ if (/[RC]/.test(entry.slice(0, 2))) i++;
4656
+ }
4657
+ const records = new Map(store.records.map((record) => [`.mason/decisions/${record.id}.json`, record]));
4658
+ const age = (at) => {
4659
+ const time = at ? Date.parse(at) : NaN;
4660
+ return Number.isFinite(time) ? `${Math.max(0, Math.floor((now - time) / 864e5))} days` : "unknown age";
4661
+ };
4662
+ const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ");
4663
+ const list = (items) => items.slice(0, 5).map(safe).join("; ") + (items.length > 5 ? `; ${items.length - 5} more` : "");
4664
+ const proposals = store.records.filter((record) => record.status === "active" && decisionApproval(record) === "proposed").map((record) => {
4665
+ const history = record.version === 2 ? record.history : [];
4666
+ let lastReview = -1;
4667
+ history.forEach((event, index2) => {
4668
+ if (event.kind === "accepted" || event.kind === "reaffirmed") lastReview = index2;
4669
+ });
4670
+ const since = history.slice(lastReview + 1).find((event) => event.kind === "created" || event.kind === "revised")?.at ?? record.createdAt;
4671
+ return { record, since };
4672
+ }).sort((a, b) => {
4673
+ const timestamp = (value) => Number.isFinite(Date.parse(value)) ? Date.parse(value) : Infinity;
4674
+ return timestamp(a.since) - timestamp(b.since) || a.record.id.localeCompare(b.record.id);
4675
+ });
4676
+ const lines = [];
4677
+ if (changed.size) lines.push(`${changed.size} uncommitted decision record(s): ${list([...changed].sort().map((file) => `${file} (saved-record age: ${age(records.get(file)?.updatedAt)})`))}.`);
4678
+ if (proposals.length) lines.push(`${proposals.length} proposal(s) awaiting review (oldest first): ${list(proposals.map(({ record, since }) => `.mason/decisions/${record.id}.json (${age(since)})`))}.`);
4679
+ if (store.diagnostics.length) lines.push(`${store.diagnostics.length} decision record(s) could not be read; pending knowledge summary is incomplete.`);
4680
+ return lines.length ? `Mason pending knowledge:
4681
+ ${lines.join("\n")}
4682
+ Informational only; review and commit through the normal project workflow. No approval is implied.` : null;
4683
+ } catch {
4684
+ return "Mason pending knowledge: unavailable; decision records or Git status could not be read.";
4685
+ }
4686
+ }
4687
+ var init_pending = __esm({
4688
+ "src/decisions/pending.ts"() {
4689
+ "use strict";
4690
+ init_git_read();
4691
+ init_decisions();
4692
+ init_provenance();
4693
+ }
4694
+ });
4695
+
4342
4696
  // src/automation/runtime.ts
4343
4697
  var runtime_exports = {};
4344
4698
  __export(runtime_exports, {
@@ -4348,7 +4702,7 @@ __export(runtime_exports, {
4348
4702
  summarize: () => summarize
4349
4703
  });
4350
4704
  import { randomUUID as randomUUID5 } from "crypto";
4351
- import { z as z13 } from "zod";
4705
+ import { z as z14 } from "zod";
4352
4706
  async function observeReadOnlyTool(dir, event) {
4353
4707
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
4354
4708
  await withLock(ws.root, ws.directory, async () => {
@@ -4570,6 +4924,19 @@ async function automate(dir, event) {
4570
4924
  const state = await loadState(ws);
4571
4925
  if (state.analysis?.id !== result.analysis.id) throw new Error("Automation evidence changed during publication; retry against the current inputs.");
4572
4926
  const report = structuredClone(result.analysis.report);
4927
+ if (!state.notifications) {
4928
+ let baseline = { findings: report.findings, head: report.head };
4929
+ if (state.latest) {
4930
+ try {
4931
+ const previous = z14.object({ findings: z14.array(repairFindingSchema), head: z14.string() }).safeParse(await readStoreJson(ws.root, state.latest));
4932
+ if (!previous.success) throw new Error("Unsupported saved report");
4933
+ baseline = previous.data;
4934
+ } catch {
4935
+ report.diagnostics.push("The saved notification baseline could not be read; initialized from the current audit. Original repair evidence is retained. Run an explicit check for all outstanding findings.");
4936
+ }
4937
+ }
4938
+ state.notifications = createNotificationState(baseline.findings, baseline.head);
4939
+ }
4573
4940
  if (result.shared) report.checks = { ...report.checks, ran: [], reused: [.../* @__PURE__ */ new Set([...report.checks.ran, ...report.checks.reused])], shared: true };
4574
4941
  report.reportPath = ws.directory + "/checks/" + randomUUID5() + ".json";
4575
4942
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4582,12 +4949,8 @@ async function automate(dir, event) {
4582
4949
  if (Object.keys(state.sessions).length >= 128) throw new Error("Too many sessions with unfinished tool calls; original evidence was retained.");
4583
4950
  state.sessions[key] = {
4584
4951
  host: event.host,
4585
- seen: null,
4586
- continued: false,
4587
- initialIssues: report.findings.filter((f) => f.status === "unresolved").map((f) => f.id),
4588
- initialDocs: inputs.docs,
4952
+ notifiedDiagnostics: [],
4589
4953
  lastUsed: now,
4590
- mutationObserved: false,
4591
4954
  pending: {},
4592
4955
  coverageGaps: [],
4593
4956
  events: {}
@@ -4602,7 +4965,6 @@ async function automate(dir, event) {
4602
4965
  session.pending[event.toolId] = inputs.fingerprint;
4603
4966
  }
4604
4967
  if (event.event === "after_tool" && event.mutating) {
4605
- session.mutationObserved = true;
4606
4968
  if (!event.toolId || !session.pending[event.toolId]) {
4607
4969
  const gap = "A tool completed without an observed matching pre-tool capture in this checkout; pre-edit coverage is unknown. The tool may have changed checkouts; captures are kept separately for each worktree.";
4608
4970
  if (!session.coverageGaps.includes(gap)) session.coverageGaps.push(gap);
@@ -4613,31 +4975,39 @@ async function automate(dir, event) {
4613
4975
  if (session.coverageGaps.length && report.status !== "unavailable") report.status = "incomplete";
4614
4976
  report.capture = report.status !== "unavailable" && !session.coverageGaps.length && (session.events.session_start || session.events.before_tool) ? "observed" : "unknown";
4615
4977
  }
4616
- const signature = hash([report.status, report.findings, report.diagnostics, report.checks.skipped]);
4617
- const relevant = report.findings.some((f) => f.status === "unresolved" && session && (!session.initialIssues.includes(f.id) || session.initialDocs[f.original.anchor.doc] !== inputs.docs[f.original.anchor.doc]));
4618
- const continueOnce = event.event === "task_end" && !!session?.mutationObserved && relevant && !session.continued && !event.stopHookActive;
4619
- const notify = !session || newSession || signature !== session.seen || continueOnce;
4620
- if (session) {
4621
- session.seen = signature;
4622
- if (continueOnce) session.continued = true;
4623
- }
4624
- const persistReport = !state.latest || state.fingerprint !== inputs.fingerprint || notify || event.event === "task_end";
4978
+ const selected = session && event.event === "task_end" ? completionFindings(state.notifications, report.findings) : [];
4979
+ state.agentNotifications ??= { ...structuredClone(state.notifications), delivered: {} };
4980
+ const feedback = session && ["turn_start", "after_tool"].includes(event.event) ? completionFindings(state.agentNotifications, report.findings) : [];
4981
+ const diagnostics = session ? report.diagnostics.filter((d) => !session.notifiedDiagnostics.includes(d)).slice(0, 2) : [];
4982
+ if (session) session.notifiedDiagnostics = [.../* @__PURE__ */ new Set([
4983
+ ...session.notifiedDiagnostics.filter((d) => report.diagnostics.includes(d)),
4984
+ ...diagnostics
4985
+ ])];
4986
+ const message2 = !session ? summarize(report) : [
4987
+ completionSummary(selected, report.reportPath),
4988
+ completionSummary(feedback, report.reportPath),
4989
+ ...diagnostics.length ? [`Mason: verification limited. ${diagnostics.slice(0, 2).map(cleanText).join(" ")} Evidence: ${report.reportPath}.`] : []
4990
+ ].filter(Boolean).join("\n") || null;
4991
+ const notify = message2 !== null;
4992
+ const persistReport = !state.latest || state.fingerprint !== inputs.fingerprint || notify || newSession || event.event === "task_end";
4625
4993
  if (!persistReport) report.reportPath = state.latest;
4626
4994
  state.updatedAt = now;
4627
4995
  state.fingerprint = inputs.fingerprint;
4628
4996
  state.latest = report.reportPath;
4629
4997
  if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
4630
4998
  await writeStoreJson(ws.root, ws.directory + "/state.json", state);
4631
- return { report, message: notify ? summarize(report) : null, continueOnce };
4999
+ return { report, message: message2 };
4632
5000
  }));
4633
5001
  }));
4634
5002
  }
4635
5003
  async function automationStatus(dir) {
5004
+ const { pendingKnowledge: pendingKnowledge2 } = await Promise.resolve().then(() => (init_pending(), pending_exports));
4636
5005
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
5006
+ const knowledge = await pendingKnowledge2(ws.root);
4637
5007
  const execution = await executionStatus(ws.root, ws.directory);
4638
5008
  const notifications = await failureNotifications(ws.root, ws.directory);
4639
5009
  const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
4640
- if (raw === null) return { version: 1, status: execution.status === "not-observed" ? "not-observed" : "unavailable", root: ws.root, branch: ws.branch, baselinePaths: [], hosts: {}, execution, notifications };
5010
+ if (raw === null) return { version: 1, status: execution.status === "not-observed" ? "not-observed" : "unavailable", root: ws.root, branch: ws.branch, baselinePaths: [], hosts: {}, execution, notifications, knowledge };
4641
5011
  const state = parseState(raw);
4642
5012
  if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) throw new Error("Automation state belongs to another workspace.");
4643
5013
  const inputs = await readInputs(ws.root);
@@ -4660,6 +5030,8 @@ async function automationStatus(dir) {
4660
5030
  hosts,
4661
5031
  execution,
4662
5032
  notifications,
5033
+ knowledge,
5034
+ notificationBaselineHead: state.notifications?.baselineHead ?? null,
4663
5035
  note: "Observed events do not prove all tool paths are intercepted. Run check to verify the retained evidence."
4664
5036
  };
4665
5037
  }
@@ -4667,6 +5039,7 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
4667
5039
  var init_runtime = __esm({
4668
5040
  "src/automation/runtime.ts"() {
4669
5041
  "use strict";
5042
+ init_notifications();
4670
5043
  init_profile();
4671
5044
  init_inspection();
4672
5045
  init_execution();
@@ -4678,34 +5051,34 @@ var init_runtime = __esm({
4678
5051
  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.";
4679
5052
  priority = { resolved: 0, "review-required": 1, unresolved: 2, unverified: 3 };
4680
5053
  cleanText = (text2) => text2.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
4681
- analysisSchema = z13.object({
4682
- id: z13.string(),
4683
- fingerprint: z13.string(),
4684
- completedAt: z13.number(),
4685
- baselineDigest: z13.string(),
5054
+ analysisSchema = z14.object({
5055
+ id: z14.string(),
5056
+ fingerprint: z14.string(),
5057
+ completedAt: z14.number(),
5058
+ baselineDigest: z14.string(),
4686
5059
  currentAudit: reportSchema.nullable().optional(),
4687
- report: z13.object({
4688
- version: z13.literal(1),
4689
- status: z13.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
4690
- root: z13.string(),
4691
- branch: z13.string(),
4692
- head: z13.string(),
4693
- baselinePaths: z13.array(z13.string()),
4694
- reportPath: z13.string(),
4695
- findings: z13.array(repairFindingSchema),
4696
- diagnostics: z13.array(z13.string()),
4697
- checks: z13.object({
4698
- ran: z13.array(z13.string()),
4699
- reused: z13.array(z13.string()),
4700
- skipped: z13.array(z13.object({ check: z13.string(), reason: z13.string(), doc: z13.string().optional() })),
4701
- auditReused: z13.boolean().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()
4702
5075
  }),
4703
- counts: z13.object({ resolved: z13.number(), unresolved: z13.number(), "review-required": z13.number(), unverified: z13.number() }),
4704
- capture: z13.enum(["observed", "unknown"]),
4705
- scope: z13.string()
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()
4706
5079
  })
4707
5080
  });
4708
- sharedSchema = z13.object({ analysis: analysisSchema, digest: z13.string() });
5081
+ sharedSchema = z14.object({ analysis: analysisSchema, digest: z14.string() });
4709
5082
  baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
4710
5083
  state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
4711
5084
  )));
@@ -4714,7 +5087,7 @@ var init_runtime = __esm({
4714
5087
 
4715
5088
  // src/automation/hook-root.ts
4716
5089
  import fs20 from "fs/promises";
4717
- import path27 from "path";
5090
+ import path28 from "path";
4718
5091
  var init_hook_root = __esm({
4719
5092
  "src/automation/hook-root.ts"() {
4720
5093
  "use strict";
@@ -4723,7 +5096,7 @@ var init_hook_root = __esm({
4723
5096
  });
4724
5097
 
4725
5098
  // src/automation/adapters.ts
4726
- import { z as z14 } from "zod";
5099
+ import { z as z15 } from "zod";
4727
5100
  function managedHookCommand(host, platform = process.platform) {
4728
5101
  const command = `mason --setup-host ${host} auto hook --host ${host}`;
4729
5102
  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`;
@@ -4749,15 +5122,15 @@ var init_adapters = __esm({
4749
5122
  init_runtime();
4750
5123
  init_store();
4751
5124
  init_hook_root();
4752
- inputSchema = z14.object({
4753
- cwd: z14.string().min(1),
4754
- session_id: z14.string().min(1).max(500),
4755
- hook_event_name: z14.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
4756
- tool_name: z14.string().optional(),
4757
- tool_use_id: z14.string().max(500).optional(),
4758
- tool_input: z14.unknown().optional(),
4759
- stop_hook_active: z14.boolean().optional(),
4760
- permission_mode: z14.string().optional()
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()
4761
5134
  });
4762
5135
  HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"];
4763
5136
  }
@@ -4773,7 +5146,7 @@ __export(ownership_exports, {
4773
5146
  ownershipEdit: () => ownershipEdit,
4774
5147
  ownershipSchema: () => ownershipSchema
4775
5148
  });
4776
- import { z as z15 } from "zod";
5149
+ import { z as z16 } from "zod";
4777
5150
  import { parse } from "smol-toml";
4778
5151
  function blockSpan(text2, start, end) {
4779
5152
  const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
@@ -4824,15 +5197,15 @@ var init_ownership = __esm({
4824
5197
  ["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
4825
5198
  ["# mason:mcp:start", "# mason:mcp:end"]
4826
5199
  ];
4827
- fileSchema = z15.object({
4828
- created: z15.boolean(),
4829
- initialHash: z15.string(),
4830
- blocks: z15.record(z15.string()),
4831
- mcpHash: z15.string().optional(),
4832
- hookCommands: z15.array(z15.string()).optional(),
4833
- hooks: z15.record(z15.array(z15.string())).optional()
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()
4834
5207
  });
4835
- ownershipSchema = z15.object({ version: z15.literal(1), files: z15.record(fileSchema) });
5208
+ ownershipSchema = z16.object({ version: z16.literal(1), files: z16.record(fileSchema) });
4836
5209
  }
4837
5210
  });
4838
5211
 
@@ -4847,7 +5220,7 @@ __export(install_exports, {
4847
5220
  planAutomationInstall: () => planAutomationInstall,
4848
5221
  recordSchema: () => recordSchema2
4849
5222
  });
4850
- import { z as z16 } from "zod";
5223
+ import { z as z17 } from "zod";
4851
5224
  async function installationRecord(root) {
4852
5225
  return recordSchema2.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
4853
5226
  }
@@ -4928,9 +5301,9 @@ var init_install = __esm({
4928
5301
  init_store();
4929
5302
  init_files2();
4930
5303
  init_ownership();
4931
- groupSchema = z16.object({ hooks: z16.array(z16.object({ type: z16.string(), command: z16.string().optional() }).passthrough()) }).passthrough();
4932
- automationConfigSchema = z16.object({ hooks: z16.record(z16.array(groupSchema)).optional() }).passthrough();
4933
- recordSchema2 = z16.object({ version: z16.literal(1), hosts: z16.record(z16.object({ command: z16.string() })) });
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() })) });
4934
5307
  AUTOMATION_PATH = ".mason/local/automation.json";
4935
5308
  configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
4936
5309
  }
@@ -4948,7 +5321,7 @@ __export(config_exports, {
4948
5321
  });
4949
5322
  import { isDeepStrictEqual } from "util";
4950
5323
  import { parse as parse2, stringify } from "smol-toml";
4951
- import { z as z17 } from "zod";
5324
+ import { z as z18 } from "zod";
4952
5325
  async function instructionEdits(root, host) {
4953
5326
  const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
4954
5327
  const existing = found.find((f) => f.text !== null);
@@ -5059,7 +5432,7 @@ var init_config = __esm({
5059
5432
  init_install();
5060
5433
  init_files2();
5061
5434
  init_launcher();
5062
- object = z17.record(z17.unknown());
5435
+ object = z18.record(z18.unknown());
5063
5436
  mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
5064
5437
  TOML_START = "# mason:mcp:start";
5065
5438
  TOML_END = "# mason:mcp:end";
@@ -5069,7 +5442,7 @@ var init_config = __esm({
5069
5442
  });
5070
5443
 
5071
5444
  // src/mcp/init.ts
5072
- import { z as z18 } from "zod";
5445
+ import { z as z19 } from "zod";
5073
5446
  async function loadProjectMarker(rootDir) {
5074
5447
  const raw = await readStoreJson(rootDir, ".mason/local/project.json");
5075
5448
  if (raw === null) return null;
@@ -5077,8 +5450,8 @@ async function loadProjectMarker(rootDir) {
5077
5450
  return { ...marker, features: await loadProjectFeatures(rootDir) };
5078
5451
  }
5079
5452
  async function loadProjectFeatures(rootDir) {
5080
- const config = z18.record(z18.unknown()).parse(await readStoreJson(rootDir, ".mason/config.json") ?? {});
5081
- return z18.object({ confluence: z18.boolean().optional() }).passthrough().parse(config.features ?? {});
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 ?? {});
5082
5455
  }
5083
5456
  async function saveProjectMarker(rootDir, marker) {
5084
5457
  const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
@@ -5090,8 +5463,8 @@ async function projectMarkerEdits(rootDir, marker) {
5090
5463
  const edits = [{ path: file, before: await readText(rootDir, file), after: JSON.stringify(local, null, 2) + "\n" }];
5091
5464
  if (features && Object.keys(features).length) {
5092
5465
  const configPath2 = ".mason/config.json", before = await readText(rootDir, configPath2);
5093
- const config = z18.record(z18.unknown()).parse(JSON.parse(before ?? "{}"));
5094
- config.features = { ...z18.record(z18.unknown()).parse(config.features ?? {}), ...features };
5466
+ const config = z19.record(z19.unknown()).parse(JSON.parse(before ?? "{}"));
5467
+ config.features = { ...z19.record(z19.unknown()).parse(config.features ?? {}), ...features };
5095
5468
  edits.push({ path: configPath2, before, after: JSON.stringify(config, null, 2) + "\n" });
5096
5469
  }
5097
5470
  return edits;
@@ -5105,10 +5478,10 @@ var init_init = __esm({
5105
5478
  "use strict";
5106
5479
  init_storage();
5107
5480
  init_files2();
5108
- markerSchema = z18.object({
5109
- version: z18.literal(1),
5110
- initializedAt: z18.string(),
5111
- features: z18.object({ confluence: z18.boolean().optional() }).passthrough().optional()
5481
+ markerSchema = z19.object({
5482
+ version: z19.literal(1),
5483
+ initializedAt: z19.string(),
5484
+ features: z19.object({ confluence: z19.boolean().optional() }).passthrough().optional()
5112
5485
  }).passthrough();
5113
5486
  CLAUDE_MD_SECTION = `<!-- mason:start -->
5114
5487
  ## Mason project knowledge
@@ -5117,16 +5490,19 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
5117
5490
 
5118
5491
  - Task, bug, change request, or investigation of why the project works this way \u2192 \`get_context\` with the task text and known files: matching decisions, related tests, impact, and any available map entries.
5119
5492
  - Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
5120
- - Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` before finishing. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
5493
+ - Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` as soon as the lesson is supported by evidence; do not wait for implementation, task completion, or merge. Record the established observation now and identify unimplemented remedies as tentative. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
5121
5494
  - Before saving, compare the lesson with matching \`get_context\` records and any pending proposal. If new evidence changes the same lesson's assumptions, scope, or recommended action, revise it with \`save_decision(id: existingId)\`, including when it is accepted. Preserve supported rationale and sources, replace obsolete instructions, and add the new evidence. Create a separate record for a genuinely distinct lesson; skip unchanged restatements. Revision preserves history and leaves the accepted version operative until authorized review.
5122
5495
  - Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, stale or unknown map verification requires a fresh evidence review, and a recorded failed verdict means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
5496
+ - Asked to review pending project knowledge \u2192 \`mason_automation(action: "status")\` lists pending proposals and uncommitted records. For ordinary tasks, inspect matching proposals through \`get_context\`; do not request acceptance merely because a proposal exists.
5123
5497
  - Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
5124
5498
  - For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
5125
5499
  - \`mason_init\` returns documentation audit and committed-diff review results, plus a short setup guide. Pass \`evidence\` with local CI manifest paths to include test and analysis results; the CLI equivalent is \`mason-review --evidence <manifest>\`. State skipped, unavailable, stale, or unknown checks explicitly. Related accepted decisions identify review context, not proven violations.
5126
5500
 
5127
- - When documentation repair is authorized, use \`mason_repair(action: "prepare")\` before edits, keep its baselinePath, and use \`mason_repair(action: "verify", baselinePath)\` after edits and any final doc commit. Report every original finding's outcome and any new findings. Suppressed advisories remain unresolved; editing a doc does not approve it.
5501
+ - When documentation repair is authorized, use \`mason_repair(action: "prepare")\` before edits, keep its baselinePath, and use \`mason_repair(action: "verify", baselinePath)\` after edits and any final doc commit. Report every original finding's outcome and any new findings. Specific suppressed advisories remain unresolved; editing a doc does not approve it. Informational dependency notices require no review.
5128
5502
  - When advisory assessment is authorized, use \`review_advisory(action: "prepare", baselinePath, findingId)\` to inspect original evidence and history. Record \`addressed\`, \`inapplicable\`, or \`deferred\` with the actual reviewer, reason in note, and returned reviewToken only after relevant edits are committed. Commit the assessment and verify the same baseline again. Relevant evidence changes reopen it; deferral stays outstanding. Decision findings use \`review_decision\`; advisory closure never approves a decision.
5129
5503
 
5504
+ - 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
+
5130
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.
5131
5507
 
5132
5508
  Inspect source for what the retrieved context does not answer.
@@ -5141,7 +5517,7 @@ ${CLAUDE_MD_SECTION}`;
5141
5517
 
5142
5518
  1. Explain the actionable findings with their source evidence. Separate audit issues, advisories, and skipped checks. The review covers committed changes from the merge base to HEAD; workingTree paths are not included in that review. An unavailable or empty check is not proof that the project is correct. Use the CLI for full output if a summary is truncated.
5143
5519
  2. Address findings within the user's requested scope. Setup alone authorizes Mason host configuration, hooks, instructions, and ignore rules; rewriting existing project claims requires repair scope. If repair is authorized, call \`mason_repair(dir, action: "prepare")\` before editing; it saves the full original findings even when this summary is truncated. Inspect relevant source, make grounded edits, then call \`mason_repair(dir, action: "verify", baselinePath)\` with that same baseline, including after any final doc commit. Report resolved, unresolved, review-required, unverified, and new findings. Keep suppressed advisories visible even when setup has already dirtied a doc. Do not invent a decision just to populate the store.
5144
- 3. When the task reveals a real lesson or constraint, compare matching \`get_context\` records first. If its assumptions, scope, or recommended action changed, revise that record using \`save_decision(id: existingId)\`; create a separate record for a genuinely distinct lesson and skip unchanged restatements. Include title, body, category, anchors, and known owner/source/actor information. Preserve supported rationale and sources, replace obsolete instructions, and add new evidence. Missing attribution can be added later. The tool writes a local proposal; editing it preserves revision history and requires a new acceptance, while any earlier accepted revision remains operative. An unchanged save never refreshes its evidence. When decision review is requested, \`review_decision\` prepares the record and code evidence before any authorized verdict. Review and commit records through the normal workflow. Retrieve it on the next relevant task with \`get_context(dir, task, files)\`.
5520
+ 3. Capture a supported lesson as soon as it is established; do not wait for implementation, task completion, or merge. Keep unimplemented remedies tentative. When the task reveals a real lesson or constraint, compare matching \`get_context\` records first. If its assumptions, scope, or recommended action changed, revise that record using \`save_decision(id: existingId)\`; create a separate record for a genuinely distinct lesson and skip unchanged restatements. Include title, body, category, anchors, and known owner/source/actor information. Preserve supported rationale and sources, replace obsolete instructions, and add new evidence. Missing attribution can be added later. The tool writes a local proposal; editing it preserves revision history and requires a new acceptance, while any earlier accepted revision remains operative. An unchanged save never refreshes its evidence. When decision review is requested, \`review_decision\` prepares the record and code evidence before any authorized verdict. Review and commit records through the normal workflow. Retrieve it on the next relevant task with \`get_context(dir, task, files)\`.
5145
5521
 
5146
5522
  ${ASSISTANT_SETUP}
5147
5523
 
@@ -5304,7 +5680,7 @@ var init_types2 = __esm({
5304
5680
  });
5305
5681
 
5306
5682
  // src/review/evidence/vitest.ts
5307
- import { z as z19 } from "zod";
5683
+ import { z as z20 } from "zod";
5308
5684
  function parseVitest(raw, sourceRoot) {
5309
5685
  const report = schema.parse(raw);
5310
5686
  const findings = [], diagnostics = [];
@@ -5353,24 +5729,24 @@ var init_vitest = __esm({
5353
5729
  "use strict";
5354
5730
  init_paths2();
5355
5731
  init_types2();
5356
- count3 = z19.number().int().nonnegative();
5357
- schema = z19.object({
5358
- success: z19.boolean(),
5732
+ count3 = z20.number().int().nonnegative();
5733
+ schema = z20.object({
5734
+ success: z20.boolean(),
5359
5735
  numTotalTests: count3,
5360
5736
  numPassedTests: count3,
5361
5737
  numFailedTests: count3,
5362
5738
  numPendingTests: count3,
5363
5739
  numTodoTests: count3,
5364
5740
  numFailedTestSuites: count3,
5365
- testResults: z19.array(z19.object({
5366
- name: z19.string().min(1),
5367
- status: z19.enum(["passed", "failed"]),
5368
- message: z19.string().optional(),
5369
- assertionResults: z19.array(z19.object({
5370
- fullName: z19.string(),
5371
- status: z19.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
5372
- failureMessages: z19.array(z19.string()).nullable().optional(),
5373
- location: z19.object({ line: count3, column: count3 }).nullable().optional()
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()
5374
5750
  }))
5375
5751
  }))
5376
5752
  });
@@ -5378,7 +5754,7 @@ var init_vitest = __esm({
5378
5754
  });
5379
5755
 
5380
5756
  // src/review/evidence/sarif.ts
5381
- import { z as z20 } from "zod";
5757
+ import { z as z21 } from "zod";
5382
5758
  function parseSarif(raw, sourceRoot) {
5383
5759
  const report = schema2.parse(raw), findings = [], diagnostics = [];
5384
5760
  let active = 0, suppressed = 0, absent = 0, unresolved = 0;
@@ -5469,29 +5845,29 @@ var init_sarif = __esm({
5469
5845
  "use strict";
5470
5846
  init_paths2();
5471
5847
  init_types2();
5472
- index = z20.number().int().nonnegative();
5473
- artifact = z20.object({ uri: z20.string().optional(), uriBaseId: z20.string().optional(), index: index.optional() });
5474
- message = z20.object({ text: z20.string().optional(), markdown: z20.string().optional(), id: z20.string().optional(), arguments: z20.array(z20.string()).optional() });
5475
- level = z20.enum(["error", "warning", "note", "none"]);
5476
- location = z20.object({ physicalLocation: z20.object({ artifactLocation: artifact.optional(), region: z20.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
5477
- schema2 = z20.object({
5478
- version: z20.literal("2.1.0"),
5479
- runs: z20.array(z20.object({
5480
- tool: z20.object({ driver: z20.object({ name: z20.string().min(1), rules: z20.array(z20.object({ id: z20.string(), defaultConfiguration: z20.object({ level: level.optional() }).optional(), messageStrings: z20.record(message).optional() })).optional(), globalMessageStrings: z20.record(message).optional() }) }),
5481
- invocations: z20.array(z20.object({ executionSuccessful: z20.boolean(), commandLine: z20.string().optional(), toolExecutionNotifications: z20.array(z20.object({ level: level.optional(), message })).optional() })).optional(),
5482
- versionControlProvenance: z20.array(z20.object({ revisionId: z20.string().optional() })).optional(),
5483
- originalUriBaseIds: z20.record(artifact).optional(),
5484
- artifacts: z20.array(z20.object({ location: artifact.optional() })).optional(),
5485
- results: z20.array(z20.object({
5486
- ruleId: z20.string().optional(),
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(),
5487
5863
  ruleIndex: index.optional(),
5488
5864
  message,
5489
5865
  level: level.optional(),
5490
- kind: z20.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
5491
- baselineState: z20.enum(["new", "unchanged", "updated", "absent"]).optional(),
5492
- suppressions: z20.array(z20.object({ kind: z20.enum(["inSource", "external"]), status: z20.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
5493
- locations: z20.array(location).optional(),
5494
- relatedLocations: z20.array(location).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()
5495
5871
  })).optional()
5496
5872
  }))
5497
5873
  });
@@ -5499,7 +5875,7 @@ var init_sarif = __esm({
5499
5875
  });
5500
5876
 
5501
5877
  // src/review/evidence/check.ts
5502
- import { z as z21 } from "zod";
5878
+ import { z as z22 } from "zod";
5503
5879
  function parseCheck(raw, sourceRoot) {
5504
5880
  const report = schema3.parse(raw);
5505
5881
  const diagnostics = [], findings = [];
@@ -5539,17 +5915,17 @@ var init_check = __esm({
5539
5915
  "use strict";
5540
5916
  init_paths2();
5541
5917
  init_types2();
5542
- schema3 = z21.object({
5543
- version: z21.literal(1),
5544
- commit: z21.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
5545
- results: z21.array(z21.object({
5546
- name: z21.string().min(1).max(1e3),
5547
- status: z21.enum(["passed", "failed", "skipped"]),
5548
- message: z21.string().max(1e5).optional(),
5549
- locations: z21.array(z21.object({
5550
- file: z21.string().min(1).max(4e3),
5551
- line: z21.number().int().positive().optional(),
5552
- column: z21.number().int().positive().optional()
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()
5553
5929
  })).max(100).default([])
5554
5930
  })).max(1e5)
5555
5931
  });
@@ -5557,10 +5933,10 @@ var init_check = __esm({
5557
5933
  });
5558
5934
 
5559
5935
  // src/review/evidence.ts
5560
- import path28 from "path";
5561
- import { z as z22 } from "zod";
5936
+ import path29 from "path";
5937
+ import { z as z23 } from "zod";
5562
5938
  function relativeArtifact(root, file) {
5563
- const relative = path28.isAbsolute(file) ? path28.relative(root, file) : file;
5939
+ const relative = path29.isAbsolute(file) ? path29.relative(root, file) : file;
5564
5940
  const normalized = normalizeRepoPath(relative);
5565
5941
  if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
5566
5942
  return normalized;
@@ -5599,7 +5975,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5599
5975
  let manifest, raw;
5600
5976
  try {
5601
5977
  manifest = relativeArtifact(root, supplied);
5602
- raw = z22.object({ version: z22.literal(1), checks: z22.array(z22.unknown()) }).parse(await readStoreJson(root, manifest));
5978
+ raw = z23.object({ version: z23.literal(1), checks: z23.array(z23.unknown()) }).parse(await readStoreJson(root, manifest));
5603
5979
  } catch (error) {
5604
5980
  output.diagnostics.push(`${supplied}: ${String(error)}`);
5605
5981
  continue;
@@ -5657,7 +6033,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5657
6033
  const report = await readStoreJson(root, file);
5658
6034
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
5659
6035
  const sourceRoot = check.sourceRoot ?? root;
5660
- if (!path28.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
6036
+ if (!path29.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
5661
6037
  const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
5662
6038
  result.outcome = parsed.outcome;
5663
6039
  result.counts = parsed.counts;
@@ -5754,32 +6130,32 @@ var init_evidence2 = __esm({
5754
6130
  init_sarif();
5755
6131
  init_check();
5756
6132
  init_types2();
5757
- checkSchema2 = z22.object({
5758
- id: z22.string().min(1).max(100),
5759
- kind: z22.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
5760
- tool: z22.string().min(1).max(200),
5761
- command: z22.string().min(1).max(2e3),
5762
- commit: z22.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
5763
- workingTreeClean: z22.boolean().optional(),
5764
- source: z22.string().max(2e3).optional(),
5765
- sourceRoot: z22.string().min(1).max(4e3).optional(),
5766
- status: z22.enum(["completed", "skipped", "unavailable"]).default("completed"),
5767
- reason: z22.string().min(1).max(2e3).optional(),
5768
- exitCode: z22.number().int().nullable().optional(),
5769
- environment: z22.object({
5770
- toolVersion: z22.string().min(1).max(200),
5771
- runtime: z22.string().min(1).max(200).optional(),
5772
- platform: z22.string().min(1).max(200).optional(),
5773
- configuration: z22.array(z22.string().min(1).max(4e3)).max(100).optional()
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()
5774
6150
  }).optional(),
5775
- report: z22.object({ format: z22.enum(["vitest-json", "sarif", "mason-check-json"]), path: z22.string().min(1).max(4e3) }).optional()
6151
+ report: z23.object({ format: z23.enum(["vitest-json", "sarif", "mason-check-json"]), path: z23.string().min(1).max(4e3) }).optional()
5776
6152
  });
5777
6153
  }
5778
6154
  });
5779
6155
 
5780
6156
  // src/review/review.ts
5781
6157
  import fs21 from "fs/promises";
5782
- import path29 from "path";
6158
+ import path30 from "path";
5783
6159
  import { execFile as execFile7 } from "child_process";
5784
6160
  import { promisify as promisify7 } from "util";
5785
6161
  async function resolveMergeBase(resolvedRoot, base, head) {
@@ -5808,7 +6184,7 @@ function anchorsTouched(record, changedFiles) {
5808
6184
  return matchingPaths(record.files, changedFiles);
5809
6185
  }
5810
6186
  async function computeReview(rootDir, base, options = {}) {
5811
- const resolvedRoot = path29.resolve(rootDir);
6187
+ const resolvedRoot = path30.resolve(rootDir);
5812
6188
  const head = await getCurrentGitHash(resolvedRoot);
5813
6189
  if (head === "unknown") return null;
5814
6190
  const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
@@ -5858,7 +6234,7 @@ async function computeReview(rootDir, base, options = {}) {
5858
6234
  analyzed,
5859
6235
  async (relPath) => {
5860
6236
  try {
5861
- await fs21.access(path29.join(resolvedRoot, relPath));
6237
+ await fs21.access(path30.join(resolvedRoot, relPath));
5862
6238
  return true;
5863
6239
  } catch {
5864
6240
  return false;
@@ -5993,7 +6369,7 @@ var init_onboarding = __esm({
5993
6369
  });
5994
6370
 
5995
6371
  // src/impact/references.ts
5996
- import path30 from "path";
6372
+ import path31 from "path";
5997
6373
  function templateEnd(content2, start) {
5998
6374
  let i = start + 1, interpolation = 0;
5999
6375
  while (i < content2.length) {
@@ -6119,10 +6495,10 @@ function jvmImports(content2) {
6119
6495
  }
6120
6496
  function resolveImport(file, specifier, available) {
6121
6497
  if (!specifier.startsWith("./") && !specifier.startsWith("../")) return null;
6122
- const relative = path30.posix.normalize(path30.posix.join(path30.posix.dirname(file), specifier));
6498
+ const relative = path31.posix.normalize(path31.posix.join(path31.posix.dirname(file), specifier));
6123
6499
  if (relative.startsWith("../")) return null;
6124
6500
  if (available.has(relative)) return relative;
6125
- const ext = path30.posix.extname(relative);
6501
+ const ext = path31.posix.extname(relative);
6126
6502
  const replacements = { ".js": [".ts", ".tsx"], ".mjs": [".mts"], ".cjs": [".cts"] };
6127
6503
  const candidates = ext ? (replacements[ext] ?? []).map((e) => relative.slice(0, -ext.length) + e) : jsExtensions.flatMap((e) => [relative + e, relative + "/index" + e]);
6128
6504
  const found = candidates.filter((f) => available.has(f));
@@ -6134,13 +6510,13 @@ function mentionsPath(content2, target) {
6134
6510
  function createReferenceMatcher(targets2, available, targetContents = /* @__PURE__ */ new Map()) {
6135
6511
  const counts = /* @__PURE__ */ new Map();
6136
6512
  for (const file of available) {
6137
- const name = path30.posix.basename(file, path30.posix.extname(file));
6513
+ const name = path31.posix.basename(file, path31.posix.extname(file));
6138
6514
  counts.set(name, (counts.get(name) ?? 0) + 1);
6139
6515
  }
6140
6516
  const jvmTargets = new Map([...targetContents].filter(([file]) => /\.(?:kt|kts|java)$/.test(file)).map(([file, content2]) => [file, declaredSymbols(content2)]));
6141
6517
  return (source) => {
6142
6518
  if (targets2.includes(source.path)) return [];
6143
- const resolved = new Set(jsExtensions.includes(path30.posix.extname(source.path)) ? imports(source.content).map((s) => resolveImport(source.path, s, available)).filter(Boolean) : []);
6519
+ const resolved = new Set(jsExtensions.includes(path31.posix.extname(source.path)) ? imports(source.content).map((s) => resolveImport(source.path, s, available)).filter(Boolean) : []);
6144
6520
  if (/\.(?:kt|kts|java)$/.test(source.path)) {
6145
6521
  const imported = jvmImports(source.content);
6146
6522
  for (const [target, symbols] of jvmTargets) if (symbols.some((symbol) => imported.some((name) => name === symbol || name.startsWith(symbol + ".")))) resolved.add(target);
@@ -6156,7 +6532,7 @@ function createReferenceMatcher(targets2, available, targetContents = /* @__PURE
6156
6532
  add("explicit-path", target);
6157
6533
  continue;
6158
6534
  }
6159
- const ext = path30.posix.extname(target), name = path30.posix.basename(target, ext);
6535
+ const ext = path31.posix.extname(target), name = path31.posix.basename(target, ext);
6160
6536
  if (!/\.(?:[cm]?[jt]sx?|py|kt|java|go|rs|swift|rb|cs|cpp|c|h|hpp|dart|php)$/.test(ext) || name.startsWith(".") || name.length < 3 || genericNames.has(name.toLowerCase()) || counts.get(name) !== 1) continue;
6161
6537
  if (word(name).test(source.content)) {
6162
6538
  add("name-candidate", name);
@@ -6185,11 +6561,11 @@ var impact_exports = {};
6185
6561
  __export(impact_exports, {
6186
6562
  analyzeImpact: () => analyzeImpact
6187
6563
  });
6188
- import path31 from "path";
6564
+ import path32 from "path";
6189
6565
  import { execFile as execFile8 } from "child_process";
6190
6566
  import { promisify as promisify8 } from "util";
6191
6567
  async function analyzeImpact(rootDir, targetFiles) {
6192
- const resolvedRoot = path31.resolve(rootDir);
6568
+ const resolvedRoot = path32.resolve(rootDir);
6193
6569
  const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
6194
6570
  const [cochange, references, tests] = await Promise.all([
6195
6571
  getCochangeFiles(resolvedRoot, resolvedTargets),
@@ -6303,9 +6679,9 @@ async function getRelatedTests(rootDir, targetFiles) {
6303
6679
  const testFiles = await (await createFileAccess(rootDir)).list(testPatterns);
6304
6680
  const results = [];
6305
6681
  for (const target of targetFiles) {
6306
- const targetBaseName = path31.basename(target).replace(/\.[^.]+$/, "");
6682
+ const targetBaseName = path32.basename(target).replace(/\.[^.]+$/, "");
6307
6683
  for (const testFile of testFiles) {
6308
- const testBaseName = path31.basename(testFile).replace(/\.[^.]+$/, "");
6684
+ const testBaseName = path32.basename(testFile).replace(/\.[^.]+$/, "");
6309
6685
  const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
6310
6686
  if (sourceName === targetBaseName) {
6311
6687
  results.push({
@@ -6333,13 +6709,13 @@ var assemble_exports = {};
6333
6709
  __export(assemble_exports, {
6334
6710
  assembleContext: () => assembleContext
6335
6711
  });
6336
- import path32 from "path";
6712
+ import path33 from "path";
6337
6713
  import fs22 from "fs/promises";
6338
6714
  async function collectImpact(root, candidates) {
6339
6715
  const targets2 = /* @__PURE__ */ new Set();
6340
6716
  let sourceFiles;
6341
6717
  for (const candidate of sanitizeRepoPaths(candidates)) {
6342
- const stat = await fs22.stat(path32.join(root, candidate)).catch(() => null);
6718
+ const stat = await fs22.stat(path33.join(root, candidate)).catch(() => null);
6343
6719
  if (stat?.isDirectory()) {
6344
6720
  sourceFiles ??= await (await createFileAccess(root)).list();
6345
6721
  for (const file of sourceFiles) {
@@ -6365,7 +6741,7 @@ async function collectImpact(root, candidates) {
6365
6741
  };
6366
6742
  }
6367
6743
  async function assembleContext(rootDir, task, files) {
6368
- const resolvedRoot = path32.resolve(rootDir);
6744
+ const resolvedRoot = path33.resolve(rootDir);
6369
6745
  const mapState = await inspectSnapshot(resolvedRoot);
6370
6746
  const snapshot = mapState.snapshot;
6371
6747
  const store = await loadDecisionStore(resolvedRoot);
@@ -6562,7 +6938,7 @@ __export(observations_exports, {
6562
6938
  readObservation: () => readObservation
6563
6939
  });
6564
6940
  import fs23 from "fs/promises";
6565
- import { z as z23 } from "zod";
6941
+ import { z as z24 } from "zod";
6566
6942
  function observationPath(directory, host, revision, version = executingVersion()) {
6567
6943
  return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
6568
6944
  }
@@ -6633,19 +7009,19 @@ var init_observations = __esm({
6633
7009
  init_storage();
6634
7010
  init_model();
6635
7011
  init_launcher();
6636
- observationSchema = z23.object({
6637
- version: z23.literal(1),
6638
- masonVersion: z23.string(),
6639
- root: z23.string(),
6640
- revision: z23.string(),
6641
- host: z23.enum(["codex", "claude"]),
6642
- contextCalls: z23.number().int().nonnegative(),
6643
- lastContextAt: z23.string().optional(),
6644
- sessions: z23.record(z23.object({
6645
- events: z23.array(z23.enum(events)),
6646
- at: z23.string(),
6647
- verificationStatus: z23.string().optional(),
6648
- reportPath: z23.string().optional()
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()
6649
7025
  }))
6650
7026
  });
6651
7027
  }
@@ -6908,8 +7284,8 @@ __export(client_exports, {
6908
7284
  function createConfluenceClient(config, fetchFn = fetch) {
6909
7285
  const baseUrl = config.baseUrl.replace(/\/+$/, "");
6910
7286
  const auth = "Basic " + Buffer.from(`${config.email}:${config.apiToken}`).toString("base64");
6911
- async function call(method, path42, body) {
6912
- const res = await fetchFn(`${baseUrl}${path42}`, {
7287
+ async function call(method, path43, body) {
7288
+ const res = await fetchFn(`${baseUrl}${path43}`, {
6913
7289
  method,
6914
7290
  headers: {
6915
7291
  Authorization: auth,
@@ -6921,7 +7297,7 @@ function createConfluenceClient(config, fetchFn = fetch) {
6921
7297
  if (!res.ok) {
6922
7298
  const text2 = await res.text();
6923
7299
  throw new Error(
6924
- `Confluence ${method} ${path42} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
7300
+ `Confluence ${method} ${path43} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
6925
7301
  );
6926
7302
  }
6927
7303
  if (res.status === 204) return null;
@@ -7023,15 +7399,15 @@ __export(config_exports2, {
7023
7399
  validateProvider: () => validateProvider
7024
7400
  });
7025
7401
  import fs24 from "fs/promises";
7026
- import path33 from "path";
7402
+ import path34 from "path";
7027
7403
  import os3 from "os";
7028
7404
  import { execFile as execFile9 } from "child_process";
7029
7405
  import { promisify as promisify9 } from "util";
7030
7406
  function configDir() {
7031
- return path33.join(os3.homedir(), ".mason");
7407
+ return path34.join(os3.homedir(), ".mason");
7032
7408
  }
7033
7409
  function configFile() {
7034
- return path33.join(configDir(), "config.json");
7410
+ return path34.join(configDir(), "config.json");
7035
7411
  }
7036
7412
  async function loadConfig() {
7037
7413
  try {
@@ -7204,16 +7580,16 @@ var init_renderer = __esm({
7204
7580
 
7205
7581
  // src/confluence/diff.ts
7206
7582
  import fs25 from "fs/promises";
7207
- import path34 from "path";
7583
+ import path35 from "path";
7208
7584
  import { createHash as createHash6 } from "crypto";
7209
7585
  function hashDescription(description) {
7210
7586
  return createHash6("sha256").update(description, "utf8").digest("hex");
7211
7587
  }
7212
7588
  function syncStateDir(rootDir) {
7213
- return path34.join(rootDir, ".mason");
7589
+ return path35.join(rootDir, ".mason");
7214
7590
  }
7215
7591
  function syncStatePath(rootDir) {
7216
- return path34.join(syncStateDir(rootDir), "confluence-sync.json");
7592
+ return path35.join(syncStateDir(rootDir), "confluence-sync.json");
7217
7593
  }
7218
7594
  async function loadSyncState(rootDir) {
7219
7595
  try {
@@ -7780,13 +8156,13 @@ init_advisory_review();
7780
8156
  init_decisions();
7781
8157
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7782
8158
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7783
- import { z as z24 } from "zod";
8159
+ import { z as z25 } from "zod";
7784
8160
 
7785
8161
  // src/mcp/tools.ts
7786
8162
  init_repair();
7787
8163
  init_advisory_review();
7788
8164
  import fs26 from "fs/promises";
7789
- import path35 from "path";
8165
+ import path36 from "path";
7790
8166
  import { execFile as execFile11 } from "child_process";
7791
8167
  import { promisify as promisify11 } from "util";
7792
8168
 
@@ -7795,11 +8171,11 @@ init_audit();
7795
8171
  init_types();
7796
8172
  init_repair();
7797
8173
  init_findings();
7798
- import path24 from "path";
8174
+ import path25 from "path";
7799
8175
 
7800
8176
  // src/audit/advisory-cli.ts
7801
8177
  init_advisory_review();
7802
- import path23 from "path";
8178
+ import path24 from "path";
7803
8179
  import { stripVTControlCharacters } from "util";
7804
8180
 
7805
8181
  // src/audit/cli.ts
@@ -7866,7 +8242,7 @@ function formatFixPrompt(report, baselinePath) {
7866
8242
  "- new-module: review whether the directory needs documenting; an omission alone does not establish a defect. Preserve scope and describe only what you verified."
7867
8243
  );
7868
8244
  lines.push(
7869
- "- Historical ADVISORIES require a separate authorized assessment using review_advisory with the baselinePath and findingId from verification. Prepare and inspect evidence first; record addressed, inapplicable or deferred with the actual reviewer, note and reviewToken. Decision findings use review_decision. Candidates marked resolution: recheck can be rerun to see whether the condition remains; this does not establish semantic approval. Report what remains unknown."
8245
+ "- Specific historical ADVISORIES require a separate authorized assessment using review_advisory with the baselinePath and findingId from verification. Prepare and inspect evidence first; record addressed, inapplicable or deferred with the actual reviewer, note and reviewToken. Dependency notices marked resolution: informational require no review. Dependency advisories may be dismissed with action: dismiss, reasonCode (no-dependency-claims or unrelated-manifest-change), and note, without a reviewer or token. Decision findings use review_decision. Candidates marked resolution: recheck can be rerun to see whether the condition remains; this does not establish semantic approval. Report what remains unknown."
7870
8246
  );
7871
8247
  lines.push("");
7872
8248
  lines.push("AUDIT REPORT (current context files and repository evidence, including local edits):");
@@ -7897,10 +8273,10 @@ import { promisify as promisify3 } from "util";
7897
8273
 
7898
8274
  // src/analyzers/base.ts
7899
8275
  import fs16 from "fs/promises";
7900
- import fg6 from "fast-glob";
8276
+ import fg8 from "fast-glob";
7901
8277
  var BaseAnalyzer = class {
7902
8278
  async findFiles(patterns, root) {
7903
- return fg6(patterns, {
8279
+ return fg8(patterns, {
7904
8280
  cwd: root,
7905
8281
  ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
7906
8282
  absolute: true
@@ -8112,7 +8488,7 @@ async function isGitRepo(dir) {
8112
8488
 
8113
8489
  // src/mcp/sampler.ts
8114
8490
  init_files();
8115
- import path25 from "path";
8491
+ import path26 from "path";
8116
8492
  import { execFile as execFile4 } from "child_process";
8117
8493
  import { promisify as promisify4 } from "util";
8118
8494
  var exec2 = promisify4(execFile4);
@@ -8220,8 +8596,8 @@ async function sampleFiles(rootDir, maxFiles = 25) {
8220
8596
  const projectConfig = access.config;
8221
8597
  for (const filePath of projectConfig.alwaysInclude ?? []) {
8222
8598
  if (selected.size >= maxFiles) break;
8223
- const resolvedPath = path25.resolve(rootDir, filePath);
8224
- if (!resolvedPath.startsWith(path25.resolve(rootDir))) continue;
8599
+ const resolvedPath = path26.resolve(rootDir, filePath);
8600
+ if (!resolvedPath.startsWith(path26.resolve(rootDir))) continue;
8225
8601
  selected.set(filePath, "always-include (project config)");
8226
8602
  }
8227
8603
  let configCount = 0;
@@ -8287,7 +8663,7 @@ async function sampleFiles(rootDir, maxFiles = 25) {
8287
8663
  if (!line) continue;
8288
8664
  if (line.includes("node_modules") || line.includes("/build/") || line.includes(".gradle") || line.includes("/generated/"))
8289
8665
  continue;
8290
- const ext = path25.extname(line).slice(1);
8666
+ const ext = path26.extname(line).slice(1);
8291
8667
  if (!SOURCE_EXTENSIONS.includes(ext)) continue;
8292
8668
  fileCounts.set(line, (fileCounts.get(line) ?? 0) + 1);
8293
8669
  }
@@ -8409,14 +8785,14 @@ init_files();
8409
8785
  import { createHash as createHash4 } from "crypto";
8410
8786
  import { execFile as execFile5 } from "child_process";
8411
8787
  import { promisify as promisify5 } from "util";
8412
- import { z as z10 } from "zod";
8788
+ import { z as z11 } from "zod";
8413
8789
  var exec3 = promisify5(execFile5);
8414
- var requestSchema = z10.object({
8415
- id: z10.string().regex(/^[a-zA-Z0-9_-]+$/),
8416
- action: z10.enum(["prepare", "accept", "reaffirm", "retire"]).default("prepare"),
8417
- reviewer: z10.string().trim().min(1).max(200).optional(),
8418
- note: z10.string().trim().min(1).max(1500).optional(),
8419
- reviewToken: z10.string().regex(/^[a-f0-9]{64}$/).optional()
8790
+ var requestSchema = z11.object({
8791
+ id: z11.string().regex(/^[a-zA-Z0-9_-]+$/),
8792
+ action: z11.enum(["prepare", "accept", "reaffirm", "retire"]).default("prepare"),
8793
+ reviewer: z11.string().trim().min(1).max(200).optional(),
8794
+ note: z11.string().trim().min(1).max(1500).optional(),
8795
+ reviewToken: z11.string().regex(/^[a-f0-9]{64}$/).optional()
8420
8796
  });
8421
8797
  async function reviewState(root, record) {
8422
8798
  const [headHash, workingTree, changes] = await Promise.all([
@@ -8643,16 +9019,16 @@ init_lock();
8643
9019
  init_storage();
8644
9020
  init_snapshot();
8645
9021
  import fs17 from "fs/promises";
8646
- import { z as z11 } from "zod";
9022
+ import { z as z12 } from "zod";
8647
9023
  var DIRECTORY = ".mason/partial-snapshots";
8648
- var partialSchema = z11.object({
8649
- batchId: z11.string().regex(/^[a-zA-Z0-9_-]+$/),
8650
- offset: z11.number().int().nonnegative(),
8651
- features: z11.record(featureSchema),
8652
- flows: z11.record(flowSchema),
8653
- savedAt: z11.string()
9024
+ var partialSchema = z12.object({
9025
+ batchId: z12.string().regex(/^[a-zA-Z0-9_-]+$/),
9026
+ offset: z12.number().int().nonnegative(),
9027
+ features: z12.record(featureSchema),
9028
+ flows: z12.record(flowSchema),
9029
+ savedAt: z12.string()
8654
9030
  });
8655
- var scopeSchema2 = z11.object({ files: z11.array(z11.string()), savedAt: z11.string() });
9031
+ var scopeSchema2 = z12.object({ files: z12.array(z12.string()), savedAt: z12.string() });
8656
9032
  async function savePartial(rootDir, partial) {
8657
9033
  if (!/^[a-zA-Z0-9_-]+$/.test(partial.batchId)) throw new Error(`Invalid batchId: ${partial.batchId}`);
8658
9034
  await withSnapshotWrite(rootDir, () => writeStoreJson(rootDir, `${DIRECTORY}/${partial.batchId}.json`, partialSchema.parse(partial)));
@@ -8704,7 +9080,7 @@ async function buildContext(dir) {
8704
9080
  };
8705
9081
  }
8706
9082
  async function analyzeProject(dir) {
8707
- const rootDir = path35.resolve(dir);
9083
+ const rootDir = path36.resolve(dir);
8708
9084
  const context = await buildContext(rootDir);
8709
9085
  const results = await runAll(context);
8710
9086
  const projectSnapshot = await detectProjectSnapshot(rootDir);
@@ -8759,7 +9135,7 @@ async function detectProjectSnapshot(rootDir) {
8759
9135
  const present = [];
8760
9136
  for (const file of buildFiles) {
8761
9137
  try {
8762
- await fs26.access(path35.join(rootDir, file));
9138
+ await fs26.access(path36.join(rootDir, file));
8763
9139
  present.push(file);
8764
9140
  } catch {
8765
9141
  }
@@ -8801,7 +9177,7 @@ async function detectProjectSnapshot(rootDir) {
8801
9177
  const sourceFiles = await access.list();
8802
9178
  const fileCounts = {};
8803
9179
  for (const file of sourceFiles) {
8804
- const ext = path35.extname(file).slice(1);
9180
+ const ext = path36.extname(file).slice(1);
8805
9181
  fileCounts[ext] = (fileCounts[ext] ?? 0) + 1;
8806
9182
  }
8807
9183
  return {
@@ -8812,7 +9188,7 @@ async function detectProjectSnapshot(rootDir) {
8812
9188
  };
8813
9189
  }
8814
9190
  async function getCodeSamples(dir, count4 = 15) {
8815
- const rootDir = path35.resolve(dir);
9191
+ const rootDir = path36.resolve(dir);
8816
9192
  const samples = await sampleFiles(rootDir, count4);
8817
9193
  const output = {
8818
9194
  note: "These are previews (first ~60 lines). Read the file directly with your own tools to see it in full.",
@@ -8855,7 +9231,7 @@ async function unmappedContextResponse(rootDir) {
8855
9231
  });
8856
9232
  }
8857
9233
  async function getProjectStructure(dir) {
8858
- const rootDir = path35.resolve(dir);
9234
+ const rootDir = path36.resolve(dir);
8859
9235
  const allFiles = await (await createFileAccess(rootDir)).list("**/*");
8860
9236
  const dirInfo = /* @__PURE__ */ new Map();
8861
9237
  for (const file of allFiles) {
@@ -8867,7 +9243,7 @@ async function getProjectStructure(dir) {
8867
9243
  }
8868
9244
  const info = dirInfo.get(dirPath);
8869
9245
  info.fileCount++;
8870
- const ext = path35.extname(file).slice(1);
9246
+ const ext = path36.extname(file).slice(1);
8871
9247
  if (ext) {
8872
9248
  info.extensions.set(ext, (info.extensions.get(ext) ?? 0) + 1);
8873
9249
  }
@@ -8912,7 +9288,7 @@ async function buildChangedFilePreviews(rootDir, changedFiles) {
8912
9288
  return previews2;
8913
9289
  }
8914
9290
  async function getSnapshot(dir) {
8915
- const rootDir = path35.resolve(dir);
9291
+ const rootDir = path36.resolve(dir);
8916
9292
  const snapshot = await loadSnapshot(rootDir);
8917
9293
  if (!snapshot) {
8918
9294
  return unmappedContextResponse(rootDir);
@@ -9013,7 +9389,7 @@ function driftHint(report) {
9013
9389
  return "Read the changed files under staleFeatures/staleFlows, update those entries (fold unmappedFiles into the right features), and call save_snapshot with only the affected entries \u2014 unchanged entries are preserved. Drop ghostFiles from any entries that reference them, and delete features/flows that no longer exist via save_snapshot's removeFeatures/removeFlows.";
9014
9390
  }
9015
9391
  async function checkDrift(dir) {
9016
- const rootDir = path35.resolve(dir);
9392
+ const rootDir = path36.resolve(dir);
9017
9393
  const report = await computeDrift(rootDir);
9018
9394
  if (!report) {
9019
9395
  return JSON.stringify({
@@ -9044,7 +9420,7 @@ async function checkDrift(dir) {
9044
9420
  return JSON.stringify({ exists: true, ...report, verification, hint });
9045
9421
  }
9046
9422
  async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_SIZE, files) {
9047
- const rootDir = path35.resolve(dir);
9423
+ const rootDir = path36.resolve(dir);
9048
9424
  const scoped = files !== void 0 && files.length > 0;
9049
9425
  const scopeFiles = scoped ? sanitizePaths(rootDir, files) : void 0;
9050
9426
  const batch = await prepareSnapshotBatch(dir, offset, batchSize, scopeFiles);
@@ -9091,7 +9467,7 @@ async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_
9091
9467
  );
9092
9468
  }
9093
9469
  async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
9094
- const rootDir = path35.resolve(dir);
9470
+ const rootDir = path36.resolve(dir);
9095
9471
  for (const feat of Object.values(features)) {
9096
9472
  feat.files = sanitizePaths(rootDir, feat.files);
9097
9473
  if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
@@ -9120,7 +9496,7 @@ async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
9120
9496
  );
9121
9497
  }
9122
9498
  async function reduceSnapshot(dir) {
9123
- const rootDir = path35.resolve(dir);
9499
+ const rootDir = path36.resolve(dir);
9124
9500
  const partials = await loadAllPartials(rootDir);
9125
9501
  if (partials.length === 0) {
9126
9502
  return JSON.stringify(
@@ -9182,7 +9558,7 @@ async function reduceSnapshot(dir) {
9182
9558
  );
9183
9559
  }
9184
9560
  async function fullAnalysis(dir) {
9185
- const rootDir = path35.resolve(dir);
9561
+ const rootDir = path36.resolve(dir);
9186
9562
  const [analysis, structure, samples, testMap, snapshot] = await Promise.all([
9187
9563
  analyzeProject(dir),
9188
9564
  getProjectStructure(dir),
@@ -9211,7 +9587,7 @@ function sanitizePaths(rootDir, files) {
9211
9587
  return sanitizeRepoPaths(files);
9212
9588
  }
9213
9589
  async function saveSnapshotData(dir, features, flows, removeFeatures = [], removeFlows = []) {
9214
- const rootDir = path35.resolve(dir);
9590
+ const rootDir = path36.resolve(dir);
9215
9591
  return updateSnapshot(rootDir, async (previous) => {
9216
9592
  const gitHash = await getCurrentGitHash(rootDir);
9217
9593
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -9291,14 +9667,14 @@ async function saveSnapshotData(dir, features, flows, removeFeatures = [], remov
9291
9667
  });
9292
9668
  }
9293
9669
  async function getImpact(dir, files) {
9294
- const rootDir = path35.resolve(dir);
9670
+ const rootDir = path36.resolve(dir);
9295
9671
  const { analyzeImpact: analyzeImpact2 } = await Promise.resolve().then(() => (init_impact(), impact_exports));
9296
9672
  const result = await analyzeImpact2(rootDir, files);
9297
9673
  return JSON.stringify(result, null, 2);
9298
9674
  }
9299
9675
  var VERIFY_DEFAULT_SAMPLE = 5;
9300
9676
  async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
9301
- const rootDir = path35.resolve(dir);
9677
+ const rootDir = path36.resolve(dir);
9302
9678
  const snapshot = await loadSnapshot(rootDir);
9303
9679
  if (!snapshot) {
9304
9680
  return JSON.stringify({
@@ -9351,7 +9727,7 @@ async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
9351
9727
  });
9352
9728
  }
9353
9729
  async function saveVerification(dir, verdicts) {
9354
- const rootDir = path35.resolve(dir);
9730
+ const rootDir = path36.resolve(dir);
9355
9731
  return updateSnapshot(rootDir, async (snapshot) => {
9356
9732
  if (!snapshot) {
9357
9733
  return { snapshot: null, result: JSON.stringify({ exists: false, stamped: [], hint: "No concept map exists. Build it and run verify_snapshot before recording verdicts." }) };
@@ -9425,16 +9801,16 @@ async function saveVerification(dir, verdicts) {
9425
9801
  });
9426
9802
  }
9427
9803
  async function saveDecision(dir, input) {
9428
- const rootDir = path35.resolve(dir);
9804
+ const rootDir = path36.resolve(dir);
9429
9805
  const { upsertDecision: upsertDecision2 } = await Promise.resolve().then(() => (init_decisions(), decisions_exports));
9430
9806
  const result = await upsertDecision2(rootDir, input);
9431
9807
  return JSON.stringify(result);
9432
9808
  }
9433
9809
  async function reviewDecision2(dir, input) {
9434
- return JSON.stringify(await reviewDecision(path35.resolve(dir), input));
9810
+ return JSON.stringify(await reviewDecision(path36.resolve(dir), input));
9435
9811
  }
9436
9812
  async function getContext(dir, task, files) {
9437
- const rootDir = path35.resolve(dir);
9813
+ const rootDir = path36.resolve(dir);
9438
9814
  const { assembleContext: assembleContext2 } = await Promise.resolve().then(() => (init_assemble(), assemble_exports));
9439
9815
  const bundle = await assembleContext2(rootDir, task, files);
9440
9816
  return JSON.stringify(bundle);
@@ -9483,7 +9859,7 @@ async function masonInit(dir, options = {}) {
9483
9859
  return JSON.stringify(await setupProject2(dir, options), null, 2);
9484
9860
  }
9485
9861
  if (options.host) throw new Error("host applies only to mode: setup.");
9486
- const rootDir = path35.resolve(dir);
9862
+ const rootDir = path36.resolve(dir);
9487
9863
  const marker = await loadProjectMarker(rootDir);
9488
9864
  const { loadSetup: loadSetup2 } = await Promise.resolve().then(() => (init_model(), model_exports));
9489
9865
  const setup = await loadSetup2(rootDir);
@@ -9503,7 +9879,7 @@ async function masonInit(dir, options = {}) {
9503
9879
  );
9504
9880
  }
9505
9881
  async function masonCompleteInit(dir, options = {}) {
9506
- const rootDir = path35.resolve(dir);
9882
+ const rootDir = path36.resolve(dir);
9507
9883
  const existing = await loadProjectMarker(rootDir);
9508
9884
  const features = await loadProjectFeatures(rootDir);
9509
9885
  const marker = {
@@ -9612,7 +9988,7 @@ async function masonSetConfluence(input) {
9612
9988
  );
9613
9989
  }
9614
9990
  async function exportToConfluenceTool(dir, overrides) {
9615
- const rootDir = path35.resolve(dir);
9991
+ const rootDir = path36.resolve(dir);
9616
9992
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports2));
9617
9993
  const { exportToConfluence: exportToConfluence2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
9618
9994
  const config = await loadConfig2();
@@ -9651,21 +10027,21 @@ function createMcpServer() {
9651
10027
  const server = new McpServer(
9652
10028
  {
9653
10029
  name: "mason",
9654
- version: "0.19.0"
10030
+ version: "0.19.2"
9655
10031
  },
9656
10032
  {
9657
- instructions: (setupNotice ? setupNotice + "\n\n" : "") + 'Mason retrieves recorded decisions, file impact, and optional feature/flow maps. Use get_context with the task and known files, and get_impact before editing. Save learned rationale and constraints with save_decision; review and commit the local records through the project workflow. These tools work without initialization or a concept map. Consult trust and diagnostics: changed or unknown freshness needs source inspection, stale or unknown map verification needs a fresh review, recorded failed verdicts need correction, and proposals are suggestions and legacy records are unreviewed. Accepted decisions are recorded team constraints, still subject to freshness checks. Use review_decision to prepare code evidence and record only authorized acceptance, reaffirmation, or retirement. Never invent a reviewer or treat these recorded identities as authenticated approval. mason_init returns documentation audit and committed-diff review findings with a quickstart guide. Use mode: "map" only when a full architecture map is requested. A missing map is not a setup failure; use decisions and source evidence. get_snapshot provides architecture navigation when a map is available. The mason-audit and mason-review CLIs also work without setup.'
10033
+ 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.'
9658
10034
  }
9659
10035
  );
9660
10036
  server.tool(
9661
10037
  "mason_init",
9662
10038
  "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.",
9663
10039
  {
9664
- dir: z24.string().describe("Absolute path to the project root directory"),
9665
- mode: z24.enum(["quickstart", "map", "setup"]).optional().default("quickstart").describe("Quickstart inspects without edits; map requests an architecture build; setup installs the shared onboarding flow."),
9666
- host: z24.enum(["codex", "claude"]).optional().describe("Assistant to configure in setup mode; inferred only when unambiguous."),
9667
- base: z24.string().optional().describe("Git ref for committed-diff review. Defaults to the first available main branch ref."),
9668
- evidence: z24.array(z24.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.")
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.")
9669
10045
  },
9670
10046
  async ({ dir, mode, host, base, evidence }) => {
9671
10047
  const result = await masonInit(dir, { mode, host, base, evidence });
@@ -9674,10 +10050,10 @@ function createMcpServer() {
9674
10050
  );
9675
10051
  server.tool(
9676
10052
  "mason_automation",
9677
- "Inspect installed automation and observed host events, or resume/check retained documentation repair evidence across sessions. status is read-only; check saves local baselines and verification reports without editing source or approving advisories. Returns concise results with a full report path. Works without a map.",
10053
+ "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.",
9678
10054
  {
9679
- dir: z24.string().describe("Absolute path to the project directory"),
9680
- action: z24.enum(["status", "check"]).describe("Inspect configuration and receipts, or capture/resume and verify original audit evidence.")
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.")
9681
10057
  },
9682
10058
  async ({ dir, action }) => ({ content: [{ type: "text", text: await masonAutomation(dir, action) }] })
9683
10059
  );
@@ -9685,10 +10061,10 @@ function createMcpServer() {
9685
10061
  "mason_repair",
9686
10062
  "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.",
9687
10063
  {
9688
- dir: z24.string().describe("Absolute path to the project root directory"),
9689
- action: z24.enum(["prepare", "verify"]),
9690
- baselinePath: z24.string().optional().describe("Original baseline path returned by prepare; required for verify."),
9691
- checks: z24.array(z24.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.")
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.")
9692
10068
  },
9693
10069
  async ({ dir, action, baselinePath, checks }) => {
9694
10070
  const result = await masonRepair(dir, { action, baselinePath, checks });
@@ -9697,16 +10073,16 @@ function createMcpServer() {
9697
10073
  );
9698
10074
  server.tool(
9699
10075
  "review_advisory",
9700
- "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.",
9701
- { dir: z24.string().describe("Absolute path to the project root"), ...advisoryReviewRequest.shape },
10076
+ "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 },
9702
10078
  async ({ dir, ...request }) => ({ content: [{ type: "text", text: await masonReviewAdvisory(dir, request) }] })
9703
10079
  );
9704
10080
  server.tool(
9705
10081
  "mason_complete_init",
9706
10082
  "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.",
9707
10083
  {
9708
- dir: z24.string().describe("Absolute path to the project root directory"),
9709
- confluenceConfigured: z24.boolean().optional().describe("Set the Confluence setup status; omit to preserve the existing value")
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")
9710
10086
  },
9711
10087
  async ({ dir, confluenceConfigured }) => {
9712
10088
  const result = await masonCompleteInit(dir, { confluenceConfigured });
@@ -9717,11 +10093,11 @@ function createMcpServer() {
9717
10093
  "mason_set_confluence",
9718
10094
  "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.",
9719
10095
  {
9720
- baseUrl: z24.string().describe("Confluence base URL. Accepts `acme`, `acme.atlassian.net`, or `https://acme.atlassian.net` (normalized automatically)."),
9721
- email: z24.string().describe("User's Atlassian account email"),
9722
- apiToken: z24.string().describe("API token from id.atlassian.com/manage-profile/security/api-tokens"),
9723
- spaceKey: z24.string().optional().describe("Confluence space key. Omit on the first call to list available spaces."),
9724
- parentPageId: z24.string().optional().describe("Optional parent page ID under which Mason's index page is created")
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")
9725
10101
  },
9726
10102
  async ({ baseUrl, email, apiToken, spaceKey, parentPageId }) => {
9727
10103
  const result = await masonSetConfluence({
@@ -9738,7 +10114,7 @@ function createMcpServer() {
9738
10114
  "full_analysis",
9739
10115
  "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.",
9740
10116
  {
9741
- dir: z24.string().describe("Absolute path to the project root directory")
10117
+ dir: z25.string().describe("Absolute path to the project root directory")
9742
10118
  },
9743
10119
  async ({ dir }) => {
9744
10120
  const result = await fullAnalysis(dir);
@@ -9751,7 +10127,7 @@ function createMcpServer() {
9751
10127
  "analyze_project",
9752
10128
  "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.",
9753
10129
  {
9754
- dir: z24.string().describe("Absolute path to the project root directory")
10130
+ dir: z25.string().describe("Absolute path to the project root directory")
9755
10131
  },
9756
10132
  async ({ dir }) => {
9757
10133
  const result = await analyzeProject(dir);
@@ -9764,8 +10140,8 @@ function createMcpServer() {
9764
10140
  "get_code_samples",
9765
10141
  "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.",
9766
10142
  {
9767
- dir: z24.string().describe("Absolute path to the project root directory"),
9768
- count: z24.number().optional().default(15).describe("Maximum number of files to sample (default: 15)")
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)")
9769
10145
  },
9770
10146
  async ({ dir, count: count4 }) => {
9771
10147
  const result = await getCodeSamples(dir, count4);
@@ -9778,7 +10154,7 @@ function createMcpServer() {
9778
10154
  "get_snapshot",
9779
10155
  "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.",
9780
10156
  {
9781
- dir: z24.string().describe("Absolute path to the project root directory")
10157
+ dir: z25.string().describe("Absolute path to the project root directory")
9782
10158
  },
9783
10159
  async ({ dir }) => {
9784
10160
  const result = await getSnapshot(dir);
@@ -9791,9 +10167,9 @@ function createMcpServer() {
9791
10167
  "get_context",
9792
10168
  "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.",
9793
10169
  {
9794
- dir: z24.string().describe("Absolute path to the project root directory"),
9795
- task: z24.string().describe("The task, bug, or change request in natural language \u2014 e.g. 'add rate limiting to the API client' or a ticket description"),
9796
- files: z24.array(z24.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.")
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.")
9797
10173
  },
9798
10174
  async ({ dir, task, files }) => {
9799
10175
  const result = await getContext(dir, task, files);
@@ -9809,10 +10185,10 @@ function createMcpServer() {
9809
10185
  "generate_snapshot_batch",
9810
10186
  "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.",
9811
10187
  {
9812
- dir: z24.string().describe("Absolute path to the project root directory"),
9813
- offset: z24.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."),
9814
- batchSize: z24.number().int().optional().describe("Files per batch. Defaults to 50."),
9815
- files: z24.array(z24.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.")
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.")
9816
10192
  },
9817
10193
  async ({ dir, offset, batchSize, files }) => {
9818
10194
  const result = await generateSnapshotBatch(dir, offset, batchSize, files);
@@ -9825,23 +10201,23 @@ function createMcpServer() {
9825
10201
  "save_partial_snapshot",
9826
10202
  "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.",
9827
10203
  {
9828
- dir: z24.string().describe("Absolute path to the project root directory"),
9829
- batchId: z24.string().describe("The `batchId` returned by `generate_snapshot_batch`."),
9830
- offset: z24.number().int().describe("The `offset` returned by `generate_snapshot_batch`. Used to order partials in the reduce step."),
9831
- features: z24.record(
9832
- z24.object({
9833
- description: z24.string(),
9834
- files: z24.array(z24.string()),
9835
- tests: z24.array(z24.string()).optional(),
9836
- type: z24.enum(["capability", "infrastructure"]).optional().describe(
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(
9837
10213
  '"capability" (user-facing functionality) or "infrastructure" (internal plumbing with no end user \u2014 DI/service wiring, config, logging, adapters). Defaults to "capability".'
9838
10214
  )
9839
10215
  })
9840
10216
  ).describe("Partial features for this batch only \u2014 files outside the batch will be added by other partials."),
9841
- flows: z24.record(
9842
- z24.object({
9843
- description: z24.string(),
9844
- chain: z24.array(z24.string())
10217
+ flows: z25.record(
10218
+ z25.object({
10219
+ description: z25.string(),
10220
+ chain: z25.array(z25.string())
9845
10221
  })
9846
10222
  ).describe("Partial flows whose entire chain is in this batch. Cross-batch flows are reconstructed in reduce.")
9847
10223
  },
@@ -9856,7 +10232,7 @@ function createMcpServer() {
9856
10232
  "reduce_snapshot",
9857
10233
  "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).",
9858
10234
  {
9859
- dir: z24.string().describe("Absolute path to the project root directory")
10235
+ dir: z25.string().describe("Absolute path to the project root directory")
9860
10236
  },
9861
10237
  async ({ dir }) => {
9862
10238
  const result = await reduceSnapshot(dir);
@@ -9869,25 +10245,25 @@ function createMcpServer() {
9869
10245
  "save_snapshot",
9870
10246
  "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.",
9871
10247
  {
9872
- dir: z24.string().describe("Absolute path to the project root directory"),
9873
- features: z24.record(
9874
- z24.object({
9875
- description: z24.string().describe("One-line description of the feature"),
9876
- files: z24.array(z24.string()).describe("File paths that implement this feature"),
9877
- tests: z24.array(z24.string()).optional().describe("Test file paths for this feature"),
9878
- type: z24.enum(["capability", "infrastructure"]).optional().describe(
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(
9879
10255
  '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".'
9880
10256
  )
9881
10257
  })
9882
10258
  ).describe("Map of feature names to their implementing files"),
9883
- flows: z24.record(
9884
- z24.object({
9885
- description: z24.string().describe("One-line description of the flow"),
9886
- chain: z24.array(z24.string()).describe("Ordered list of file paths showing data/call flow")
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")
9887
10263
  })
9888
10264
  ).describe("Map of flow names to ordered file chains"),
9889
- removeFeatures: z24.array(z24.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."),
9890
- removeFlows: z24.array(z24.string()).optional().describe("Flow names to delete from the existing map. Applied before merging; only meaningful on incremental saves.")
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.")
9891
10267
  },
9892
10268
  async ({ dir, features, flows, removeFeatures, removeFlows }) => {
9893
10269
  const result = await saveSnapshotData(
@@ -9904,19 +10280,19 @@ function createMcpServer() {
9904
10280
  );
9905
10281
  server.tool(
9906
10282
  "save_decision",
9907
- "Capture or revise a decision proposal with 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.",
10283
+ "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.",
9908
10284
  {
9909
- dir: z24.string().describe("Absolute path to the project root directory"),
10285
+ dir: z25.string().describe("Absolute path to the project root directory"),
9910
10286
  title: decisionTitleSchema.describe(`Short, specific headline (max ${TITLE_MAX_CHARS} characters). Generates a stable id on creation; revising the title keeps the existing id.`),
9911
10287
  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.`),
9912
- category: z24.enum(["decision", "gotcha", "deprecation", "convention"]),
9913
- files: z24.array(z24.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."),
9914
- id: z24.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."),
9915
- supersedes: z24.string().optional().describe("Id of an unreviewed record or proposal with no accepted revision to replace. Operative accepted decisions require separate review and retirement."),
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."),
9916
10292
  owner: attributionSchema.shape.owner.describe("Responsible person or team, when known. Null clears it. Required for acceptance."),
9917
10293
  sources: attributionSchema.shape.sources.describe("Known PR, issue, incident, discussion, or document references. Omit to preserve; [] clears. At least one is required for acceptance."),
9918
10294
  actor: attributionSchema.shape.actor.describe("Known person or agent recording this revision. Omit if unknown; do not infer from Git identity."),
9919
- force: z24.boolean().optional().describe("Save even when a near-duplicate was detected")
10295
+ force: z25.boolean().optional().describe("Save even when a near-duplicate was detected")
9920
10296
  },
9921
10297
  async ({ dir, title, body, category, files, id, supersedes, force, owner, sources, actor }) => {
9922
10298
  const result = await saveDecision(dir, {
@@ -9938,12 +10314,12 @@ function createMcpServer() {
9938
10314
  "review_decision",
9939
10315
  "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.",
9940
10316
  {
9941
- dir: z24.string().describe("Absolute path to the project root directory"),
9942
- id: z24.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Decision id from get_context or save_decision"),
9943
- action: z24.enum(["prepare", "accept", "reaffirm", "retire"]).optional().default("prepare").describe("Prepare is read-only. Other actions record an explicitly authorized review."),
9944
- reviewer: z24.string().trim().min(1).max(200).optional().describe("Identity of the actual reviewer; required for a verdict. Never invent one."),
9945
- note: z24.string().trim().min(1).max(1500).optional().describe("Review rationale; required for a verdict. Cite evidence for the decision."),
9946
- reviewToken: z24.string().regex(/^[a-f0-9]{64}$/).optional().describe("Token from the prepared review; rejects stale record or code revisions")
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")
9947
10323
  },
9948
10324
  async ({ dir, ...input }) => {
9949
10325
  const result = await reviewDecision2(dir, input);
@@ -9954,7 +10330,7 @@ function createMcpServer() {
9954
10330
  "mason_check_drift",
9955
10331
  "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.",
9956
10332
  {
9957
- dir: z24.string().describe("Absolute path to the project root directory")
10333
+ dir: z25.string().describe("Absolute path to the project root directory")
9958
10334
  },
9959
10335
  async ({ dir }) => {
9960
10336
  const result = await checkDrift(dir);
@@ -9967,8 +10343,8 @@ function createMcpServer() {
9967
10343
  "verify_snapshot",
9968
10344
  "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.",
9969
10345
  {
9970
- dir: z24.string().describe("Absolute path to the project root directory"),
9971
- sample: z24.number().int().optional().describe("Entries to sample (default 5)")
10346
+ dir: z25.string().describe("Absolute path to the project root directory"),
10347
+ sample: z25.number().int().optional().describe("Entries to sample (default 5)")
9972
10348
  },
9973
10349
  async ({ dir, sample }) => {
9974
10350
  const result = await verifySnapshot(dir, sample);
@@ -9979,13 +10355,13 @@ function createMcpServer() {
9979
10355
  "save_verification",
9980
10356
  "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.",
9981
10357
  {
9982
- dir: z24.string().describe("Absolute path to the project root directory"),
9983
- verdicts: z24.record(
9984
- z24.object({
9985
- kind: z24.enum(["feature", "flow"]).optional().describe("Entry kind returned by verify_snapshot; required to record a verdict"),
9986
- reviewToken: z24.string().optional().describe("Copy the reviewToken from verify_snapshot after inspecting its evidence; required to record a verdict"),
9987
- ok: z24.boolean(),
9988
- note: z24.string().optional().describe("One line on what's wrong \u2014 required when ok is false")
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")
9989
10365
  })
9990
10366
  ).describe("Entry name \u2192 verdict, exactly as returned by verify_snapshot")
9991
10367
  },
@@ -9998,8 +10374,8 @@ function createMcpServer() {
9998
10374
  "get_impact",
9999
10375
  "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.",
10000
10376
  {
10001
- dir: z24.string().describe("Absolute path to the project root directory"),
10002
- files: z24.array(z24.string()).describe("File paths or names to analyze (e.g., ['WeatherRepository.kt'] or ['src/services/auth.ts'])")
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'])")
10003
10379
  },
10004
10380
  async ({ dir, files }) => {
10005
10381
  const result = await getImpact(dir, files);
@@ -10012,12 +10388,12 @@ function createMcpServer() {
10012
10388
  "export_to_confluence",
10013
10389
  "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.",
10014
10390
  {
10015
- dir: z24.string().describe("Absolute path to the project root directory"),
10016
- spaceKey: z24.string().optional().describe("Override the configured space key"),
10017
- parentPageId: z24.string().optional().describe("Override the configured parent page ID"),
10018
- indexPageTitle: z24.string().optional().describe("Title of the index page (default: 'Mason \u2014 System Map')"),
10019
- changelogPageTitle: z24.string().optional().describe("Title of the changelog page (default: 'Mason \u2014 Changelog')"),
10020
- featurePagePrefix: z24.string().optional().describe("Prefix for each feature page title (default: 'Feature: ')")
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: ')")
10021
10397
  },
10022
10398
  async ({ dir, spaceKey, parentPageId, indexPageTitle, changelogPageTitle, featurePagePrefix }) => {
10023
10399
  const result = await exportToConfluenceTool(dir, {
@@ -10043,38 +10419,38 @@ async function startMcpServer(onClose) {
10043
10419
  init_store();
10044
10420
  init_storage();
10045
10421
  import fs33 from "fs/promises";
10046
- import path41 from "path";
10422
+ import path42 from "path";
10047
10423
  import { spawn as spawn3 } from "child_process";
10048
10424
  import { randomUUID as randomUUID11 } from "crypto";
10049
- import { z as z30 } from "zod";
10425
+ import { z as z31 } from "zod";
10050
10426
 
10051
10427
  // src/distribution/state.ts
10052
10428
  init_storage();
10053
10429
  import os5 from "os";
10054
- import path37 from "path";
10430
+ import path38 from "path";
10055
10431
  import { fileURLToPath } from "url";
10056
10432
  import fs28 from "fs/promises";
10057
- import { z as z26 } from "zod";
10433
+ import { z as z27 } from "zod";
10058
10434
 
10059
10435
  // src/distribution/path.ts
10060
10436
  import fs27 from "fs/promises";
10061
10437
  import os4 from "os";
10062
- import path36 from "path";
10438
+ import path37 from "path";
10063
10439
  import { execFile as execFile12 } from "child_process";
10064
10440
  import { promisify as promisify12 } from "util";
10065
10441
  import { createHash as createHash7, randomUUID as randomUUID7 } from "crypto";
10066
- import { z as z25 } from "zod";
10442
+ import { z as z26 } from "zod";
10067
10443
  var execute2 = promisify12(execFile12);
10068
- var pathChangeSchema = z25.discriminatedUnion("kind", [
10069
- z25.object({ kind: z25.literal("profile"), file: z25.string(), block: z25.string(), created: z25.boolean() }),
10070
- z25.object({ kind: z25.literal("windows"), entry: z25.string(), created: z25.boolean() })
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() })
10071
10447
  ]);
10072
10448
  var quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
10073
10449
  async function configurePath(bin, previous, save, options = {}) {
10074
10450
  const env = options.env ?? process.env, platform = options.platform ?? process.platform;
10075
10451
  const changes = [...previous];
10076
10452
  const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
10077
- const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path36.posix.isAbsolute(entry) && path36.posix.resolve(entry) === path36.posix.resolve(bin));
10453
+ const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path37.posix.isAbsolute(entry) && path37.posix.resolve(entry) === path37.posix.resolve(bin));
10078
10454
  const setupHint = "To connect a project, run one of these inside its directory:\n Codex: mason setup --host codex\n Claude Code: mason setup --host claude";
10079
10455
  const message2 = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
10080
10456
  const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
@@ -10094,15 +10470,15 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
10094
10470
  }
10095
10471
  return { status: "configured", changes, message: message2 };
10096
10472
  }
10097
- const shell = path36.basename(env.SHELL ?? os4.userInfo().shell ?? "");
10473
+ const shell = path37.basename(env.SHELL ?? os4.userInfo().shell ?? "");
10098
10474
  const userHome = options.userHome ?? os4.homedir();
10099
10475
  let profiles2;
10100
10476
  if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
10101
- if (env.MASON_PROFILE) profiles2 = [path36.resolve(env.MASON_PROFILE)];
10102
- else if (shell === "zsh") profiles2 = [path36.join(env.ZDOTDIR || userHome, ".zshrc")];
10103
- else if (shell === "sh") profiles2 = [path36.join(userHome, ".profile")];
10477
+ if (env.MASON_PROFILE) profiles2 = [path37.resolve(env.MASON_PROFILE)];
10478
+ else if (shell === "zsh") profiles2 = [path37.join(env.ZDOTDIR || userHome, ".zshrc")];
10479
+ else if (shell === "sh") profiles2 = [path37.join(userHome, ".profile")];
10104
10480
  else {
10105
- const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path36.join(userHome, name));
10481
+ const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path37.join(userHome, name));
10106
10482
  let login = candidates[2];
10107
10483
  for (const file of candidates) if (await fs27.lstat(file).then(() => true, (error) => {
10108
10484
  if (error.code === "ENOENT") return false;
@@ -10111,7 +10487,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
10111
10487
  login = file;
10112
10488
  break;
10113
10489
  }
10114
- profiles2 = [path36.join(userHome, ".bashrc"), login];
10490
+ profiles2 = [path37.join(userHome, ".bashrc"), login];
10115
10491
  }
10116
10492
  const marker = `# >>> mason PATH ${createHash7("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
10117
10493
  for (const profile of profiles2) {
@@ -10119,7 +10495,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
10119
10495
  if (error.code === "ENOENT") return null;
10120
10496
  throw error;
10121
10497
  });
10122
- const file = stat?.isSymbolicLink() ? await fs27.realpath(profile) : path36.resolve(profile);
10498
+ const file = stat?.isSymbolicLink() ? await fs27.realpath(profile) : path37.resolve(profile);
10123
10499
  const original = await fs27.readFile(file).catch((error) => {
10124
10500
  if (error.code === "ENOENT" && !stat) return null;
10125
10501
  throw error;
@@ -10135,7 +10511,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
10135
10511
  changes.push({ kind: "profile", file, block, created: original === null });
10136
10512
  await save(changes);
10137
10513
  }
10138
- await fs27.mkdir(path36.dirname(file), { recursive: true });
10514
+ await fs27.mkdir(path37.dirname(file), { recursive: true });
10139
10515
  const handle = await fs27.open(file, original === null ? "ax+" : "a+", 384);
10140
10516
  try {
10141
10517
  if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
@@ -10150,7 +10526,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
10150
10526
  return manual(`Could not configure PATH: ${error instanceof Error ? error.message : String(error)}`);
10151
10527
  }
10152
10528
  }
10153
- var normalizeWindowsEntry = (value) => path36.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
10529
+ var normalizeWindowsEntry = (value) => path37.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
10154
10530
  var hasWindowsEntry = (value, entry, env = process.env) => {
10155
10531
  const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
10156
10532
  const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
@@ -10187,26 +10563,26 @@ try {
10187
10563
  }
10188
10564
 
10189
10565
  // src/distribution/state.ts
10190
- var releaseVersion = z26.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
10191
- var bundleId = z26.string().regex(/^[a-f0-9]{24}$/);
10192
- var updatePolicySchema = z26.object({
10193
- enabled: z26.boolean(),
10194
- revision: z26.string(),
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(),
10195
10571
  pinnedVersion: releaseVersion.optional(),
10196
- mirror: z26.string().optional()
10572
+ mirror: z27.string().optional()
10197
10573
  });
10198
- var recordSchema3 = z26.object({
10199
- format: z26.literal(1),
10200
- home: z26.string(),
10201
- bin: z26.string(),
10574
+ var recordSchema3 = z27.object({
10575
+ format: z27.literal(1),
10576
+ home: z27.string(),
10577
+ bin: z27.string(),
10202
10578
  current: bundleId,
10203
10579
  version: releaseVersion,
10204
- launchers: z26.record(z26.string()),
10205
- previousLaunchers: z26.record(z26.string()).optional(),
10206
- pathChanges: z26.array(pathChangeSchema).optional(),
10580
+ launchers: z27.record(z27.string()),
10581
+ previousLaunchers: z27.record(z27.string()).optional(),
10582
+ pathChanges: z27.array(pathChangeSchema).optional(),
10207
10583
  updates: updatePolicySchema.optional(),
10208
- previous: z26.object({ id: bundleId, version: releaseVersion }).optional(),
10209
- pending: z26.object({ id: bundleId, version: releaseVersion, policyRevision: z26.string() }).optional()
10584
+ previous: z27.object({ id: bundleId, version: releaseVersion }).optional(),
10585
+ pending: z27.object({ id: bundleId, version: releaseVersion, policyRevision: z27.string() }).optional()
10210
10586
  });
10211
10587
  async function readInstallation(home) {
10212
10588
  const raw = await readStoreJson(home, "install.json");
@@ -10216,9 +10592,9 @@ async function readInstallation(home) {
10216
10592
  return record;
10217
10593
  }
10218
10594
  function standaloneLocation() {
10219
- const bundle = path37.resolve(path37.dirname(fileURLToPath(import.meta.url)), "../..");
10220
- const home = path37.dirname(path37.dirname(bundle));
10221
- if (path37.dirname(bundle) !== path37.join(home, "versions") || !/^[a-f0-9]{24}$/.test(path37.basename(bundle))) {
10595
+ const bundle = path38.resolve(path38.dirname(fileURLToPath(import.meta.url)), "../..");
10596
+ const home = path38.dirname(path38.dirname(bundle));
10597
+ if (path38.dirname(bundle) !== path38.join(home, "versions") || !/^[a-f0-9]{24}$/.test(path38.basename(bundle))) {
10222
10598
  return null;
10223
10599
  }
10224
10600
  return { home, bundle };
@@ -10245,17 +10621,17 @@ function newerStableVersion(candidate, installed) {
10245
10621
  // src/distribution/bundle.ts
10246
10622
  init_storage();
10247
10623
  import fs29 from "fs/promises";
10248
- import path38 from "path";
10624
+ import path39 from "path";
10249
10625
  import { createHash as createHash8 } from "crypto";
10250
- import { z as z27 } from "zod";
10626
+ import { z as z28 } from "zod";
10251
10627
  var targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
10252
- var digest2 = z27.string().regex(/^[a-f0-9]{64}$/);
10253
- var bundleSchema = z27.object({
10254
- format: z27.literal(1),
10255
- version: z27.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
10256
- target: z27.enum(targets),
10257
- nodeVersion: z27.string(),
10258
- files: z27.record(digest2)
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)
10259
10635
  }).strict();
10260
10636
  var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
10261
10637
  async function readBundle(root) {
@@ -10263,7 +10639,7 @@ async function readBundle(root) {
10263
10639
  const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
10264
10640
  const windows = manifest.target.startsWith("win32-");
10265
10641
  for (const file of Object.keys(manifest.files)) {
10266
- if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path38.isAbsolute(file)) {
10642
+ if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path39.isAbsolute(file)) {
10267
10643
  throw new Error("Unsafe path in Mason bundle: " + file);
10268
10644
  }
10269
10645
  }
@@ -10289,15 +10665,20 @@ init_storage();
10289
10665
  import fs30 from "fs/promises";
10290
10666
  import { unlinkSync } from "fs";
10291
10667
  import os6 from "os";
10292
- import path39 from "path";
10668
+ import path40 from "path";
10293
10669
  import { randomUUID as randomUUID8 } from "crypto";
10294
- import { z as z28 } from "zod";
10295
- var leaseSchema = z28.object({ pid: z28.number().int().positive(), host: z28.string(), bundle: z28.string().regex(/^[a-f0-9]{24}$/) });
10670
+ import { z as z29 } from "zod";
10671
+
10672
+ // src/distribution/trust.ts
10673
+ import tls from "tls";
10674
+
10675
+ // 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}$/) });
10296
10677
 
10297
10678
  // src/distribution/install.ts
10298
10679
  import fs32 from "fs/promises";
10299
10680
  import os8 from "os";
10300
- import path40 from "path";
10681
+ import path41 from "path";
10301
10682
  import { execFile as execFile13, spawn as spawn2 } from "child_process";
10302
10683
  import { promisify as promisify13 } from "util";
10303
10684
  import { randomUUID as randomUUID10 } from "crypto";
@@ -10309,8 +10690,8 @@ init_storage();
10309
10690
  import fs31 from "fs/promises";
10310
10691
  import os7 from "os";
10311
10692
  import { randomUUID as randomUUID9 } from "crypto";
10312
- import { z as z29 } from "zod";
10313
- var leaseSchema2 = z29.object({ pid: z29.number().int().positive(), host: z29.string() });
10693
+ import { z as z30 } from "zod";
10694
+ var leaseSchema2 = z30.object({ pid: z30.number().int().positive(), host: z30.string() });
10314
10695
  async function hasRunningMcp(home) {
10315
10696
  const directory = await storePath(home, "sessions");
10316
10697
  const files = await fs31.readdir(directory).catch((error) => {
@@ -10372,13 +10753,13 @@ async function installLocked2(home, source, bundle, progress, options, alreadySt
10372
10753
  if (options.stageOnly) assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
10373
10754
  if (!previous) for (const file of await fs32.readdir(home)) {
10374
10755
  if (file === ".install-lock") continue;
10375
- const directory = path40.join(home, file);
10756
+ const directory = path41.join(home, file);
10376
10757
  const stat = await fs32.lstat(directory);
10377
10758
  if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs32.readdir(directory)).length) {
10378
10759
  throw new Error("Mason installation directory is not empty and has no ownership receipt.");
10379
10760
  }
10380
10761
  }
10381
- const selectedBin = path40.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path40.join(home, "bin") : path40.join(os8.homedir(), ".local/bin")));
10762
+ const selectedBin = path41.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path41.join(home, "bin") : path41.join(os8.homedir(), ".local/bin")));
10382
10763
  await fs32.mkdir(selectedBin, { recursive: true });
10383
10764
  const bin = await fs32.realpath(selectedBin);
10384
10765
  if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
@@ -10396,10 +10777,10 @@ $previousToken = $env:MASON_UNINSTALL_TOKEN
10396
10777
  $token = [Guid]::NewGuid().ToString()
10397
10778
  $env:MASON_UNINSTALL_TOKEN = $token
10398
10779
  try {
10399
- & ${psQuote(path40.join(destination, "node.exe"))} ${psQuote(path40.join(destination, "app/dist/mason.js"))} @args
10780
+ & ${psQuote(path41.join(destination, "node.exe"))} ${psQuote(path41.join(destination, "app/dist/mason.js"))} @args
10400
10781
  $code = $LASTEXITCODE
10401
10782
  if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
10402
- if ([IO.File]::ReadAllText(${psQuote(path40.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
10783
+ if ([IO.File]::ReadAllText(${psQuote(path41.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
10403
10784
  Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
10404
10785
  Write-Output ${psQuote("Removed standalone installation: " + home)}
10405
10786
  }
@@ -10408,7 +10789,7 @@ try {
10408
10789
  finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
10409
10790
  `
10410
10791
  } : { mason: `#!/bin/sh
10411
- exec ${shQuote(path40.join(destination, "node"))} ${shQuote(path40.join(destination, "app/dist/mason.js"))} "$@"
10792
+ exec ${shQuote(path41.join(destination, "node"))} ${shQuote(path41.join(destination, "app/dist/mason.js"))} "$@"
10412
10793
  ` };
10413
10794
  for (const [name, content2] of Object.entries(launchers)) {
10414
10795
  const file = await storePath(bin, name);
@@ -10540,14 +10921,14 @@ async function prepareStandaloneMcp(home) {
10540
10921
  // src/distribution/updates.ts
10541
10922
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
10542
10923
  var RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
10543
- var releaseListSchema = z30.array(z30.object({ tag_name: z30.string(), draft: z30.boolean(), prerelease: z30.boolean(), published_at: z30.string().nullable() }));
10544
- var updateManifestSchema = z30.object({
10545
- format: z30.literal(1),
10546
- version: z30.string().regex(/^\d+\.\d+\.\d+$/),
10547
- publishedAt: z30.string().datetime(),
10548
- assets: z30.record(z30.string().regex(/^[a-f0-9]{64}$/))
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}$/))
10549
10930
  }).strict();
10550
- var statusSchema2 = z30.object({ lastAttempt: z30.number(), availableVersion: z30.string().optional(), error: z30.string().optional() });
10931
+ var statusSchema2 = z31.object({ lastAttempt: z31.number(), availableVersion: z31.string().optional(), error: z31.string().optional() });
10551
10932
  async function scheduleAutomaticUpdate() {
10552
10933
  try {
10553
10934
  const { home, record } = await currentInstallation();
@@ -10557,8 +10938,8 @@ async function scheduleAutomaticUpdate() {
10557
10938
  if (status.success && status.data.lastAttempt <= now && now - status.data.lastAttempt < CHECK_INTERVAL_MS) return;
10558
10939
  const bundle = await storePath(home, "versions/" + record.current);
10559
10940
  const child = spawn3(
10560
- path41.join(bundle, process.platform === "win32" ? "node.exe" : "node"),
10561
- [path41.join(bundle, "app/dist/mason.js"), "internal-update"],
10941
+ path42.join(bundle, process.platform === "win32" ? "node.exe" : "node"),
10942
+ [path42.join(bundle, "app/dist/mason.js"), "internal-update"],
10562
10943
  { cwd: home, env: process.env, stdio: "ignore", detached: true, windowsHide: true }
10563
10944
  );
10564
10945
  child.on("error", () => {
@@ -10576,8 +10957,8 @@ async function startMcpWithUpdates(start) {
10576
10957
  const session = await prepareStandaloneMcp(current.home);
10577
10958
  if (session.bundle !== current.bundle) {
10578
10959
  const child = spawn3(
10579
- path41.join(session.bundle, process.platform === "win32" ? "node.exe" : "node"),
10580
- [path41.join(session.bundle, "app/dist/mason-mcp.js"), ...process.argv.slice(2)],
10960
+ path42.join(session.bundle, process.platform === "win32" ? "node.exe" : "node"),
10961
+ [path42.join(session.bundle, "app/dist/mason-mcp.js"), ...process.argv.slice(2)],
10581
10962
  { stdio: "inherit", windowsHide: true }
10582
10963
  );
10583
10964
  const interrupt = () => {