mason-context 0.19.2 → 0.20.1

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.js CHANGED
@@ -10,33 +10,6 @@ var __export = (target, all) => {
10
10
  __defProp(target, name, { get: all[name], enumerable: true });
11
11
  };
12
12
 
13
- // src/utils/paths.ts
14
- import path from "path";
15
- function normalizeRepoPath(value) {
16
- const slash = value.replace(/\\/g, "/");
17
- if (!slash || slash.includes("\0") || path.posix.isAbsolute(slash) || /^[A-Za-z]:/.test(slash)) return null;
18
- if (slash.split("/").includes("..")) return null;
19
- const normalized = path.posix.normalize(slash).replace(/\/$/, "");
20
- return normalized === "." ? null : normalized;
21
- }
22
- function isWithinRoot(root, candidate) {
23
- const relative = path.relative(root, candidate);
24
- return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
25
- }
26
- function anchorMatches(anchor, file) {
27
- const a = normalizeRepoPath(anchor);
28
- const f = normalizeRepoPath(file);
29
- return a !== null && f !== null && (a === f || f.startsWith(`${a}/`));
30
- }
31
- function matchingPaths(anchors, files) {
32
- return [...new Set(files)].filter((file) => anchors.some((anchor) => anchorMatches(anchor, file)));
33
- }
34
- var init_paths = __esm({
35
- "src/utils/paths.ts"() {
36
- "use strict";
37
- }
38
- });
39
-
40
13
  // src/utils/profile.ts
41
14
  import { AsyncLocalStorage } from "async_hooks";
42
15
  async function profilePhase(label, run) {
@@ -96,6 +69,33 @@ var init_git_read = __esm({
96
69
  }
97
70
  });
98
71
 
72
+ // src/utils/paths.ts
73
+ import path from "path";
74
+ function normalizeRepoPath(value) {
75
+ const slash = value.replace(/\\/g, "/");
76
+ if (!slash || slash.includes("\0") || path.posix.isAbsolute(slash) || /^[A-Za-z]:/.test(slash)) return null;
77
+ if (slash.split("/").includes("..")) return null;
78
+ const normalized = path.posix.normalize(slash).replace(/\/$/, "");
79
+ return normalized === "." ? null : normalized;
80
+ }
81
+ function isWithinRoot(root, candidate) {
82
+ const relative = path.relative(root, candidate);
83
+ return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
84
+ }
85
+ function anchorMatches(anchor, file) {
86
+ const a = normalizeRepoPath(anchor);
87
+ const f = normalizeRepoPath(file);
88
+ return a !== null && f !== null && (a === f || f.startsWith(`${a}/`));
89
+ }
90
+ function matchingPaths(anchors, files) {
91
+ return [...new Set(files)].filter((file) => anchors.some((anchor) => anchorMatches(anchor, file)));
92
+ }
93
+ var init_paths = __esm({
94
+ "src/utils/paths.ts"() {
95
+ "use strict";
96
+ }
97
+ });
98
+
99
99
  // src/audit/types.ts
100
100
  var types_exports = {};
101
101
  __export(types_exports, {
@@ -146,9 +146,9 @@ async function readBoundedFile(file, maxBytes) {
146
146
  async function loadProjectConfig(root) {
147
147
  try {
148
148
  const canonicalRoot = await fs.realpath(root);
149
- const configPath2 = await fs.realpath(path2.join(root, ".mason/config.json"));
150
- if (!isWithinRoot(canonicalRoot, configPath2)) throw new Error("Project configuration resolves outside the repository");
151
- const raw = await readBoundedFile(configPath2, 64 * 1024);
149
+ const configPath3 = await fs.realpath(path2.join(root, ".mason/config.json"));
150
+ if (!isWithinRoot(canonicalRoot, configPath3)) throw new Error("Project configuration resolves outside the repository");
151
+ const raw = await readBoundedFile(configPath3, 64 * 1024);
152
152
  if (raw === null) throw new Error("Project configuration is not a regular file or exceeds 64 KiB");
153
153
  const value = JSON.parse(raw);
154
154
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected a configuration object");
@@ -312,391 +312,38 @@ async function readStoreJson(root, relative) {
312
312
  const raw = await readBoundedFile(file, 10 * 1024 * 1024);
313
313
  if (raw === null) throw new Error("file is not regular or exceeds 10 MiB");
314
314
  const parsed = JSON.parse(raw);
315
- if (parsed === null) throw new Error("expected a JSON object, received null");
316
- return parsed;
317
- } catch (error) {
318
- if (error.code === "ENOENT") return null;
319
- throw new Error(`Invalid Mason store ${relative}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
320
- }
321
- }
322
- async function writeStoreJson(root, relative, value) {
323
- const payload = JSON.stringify(value, null, 2) + "\n";
324
- if (Buffer.byteLength(payload) > 10 * 1024 * 1024) {
325
- throw new Error(`Mason store ${relative} exceeds 10 MiB`);
326
- }
327
- const file = await storePath(root, relative, true);
328
- const temporary = path3.join(path3.dirname(file), `.${path3.basename(file)}.${randomUUID()}.tmp`);
329
- try {
330
- const handle = await fs2.open(temporary, "wx", 384);
331
- try {
332
- await handle.writeFile(payload, "utf8");
333
- await handle.sync();
334
- } finally {
335
- await handle.close();
336
- }
337
- await fs2.rename(temporary, file);
338
- } finally {
339
- await fs2.rm(temporary, { force: true });
340
- }
341
- }
342
- var init_storage = __esm({
343
- "src/utils/storage.ts"() {
344
- "use strict";
345
- init_paths();
346
- init_files();
347
- }
348
- });
349
-
350
- // src/utils/store-lock.ts
351
- import fs3 from "fs/promises";
352
- import os from "os";
353
- async function lockDiagnostic(file) {
354
- let reason2;
355
- try {
356
- const owner = JSON.parse(await fs3.readFile(file, "utf8"));
357
- if (!owner || typeof owner.host !== "string" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
358
- reason2 = "Lock owner data is incomplete or malformed.";
359
- } else if (owner.host !== os.hostname()) {
360
- reason2 = "The lock belongs to another host; its process cannot be checked locally.";
361
- } else {
362
- try {
363
- process.kill(owner.pid, 0);
364
- reason2 = `Local owner PID ${owner.pid} is still running.`;
365
- } catch (error) {
366
- reason2 = error.code === "ESRCH" ? `Local owner PID ${owner.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.pid} could not be checked.`;
367
- }
368
- }
369
- } catch (error) {
370
- reason2 = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
371
- }
372
- if (await fs3.lstat(file + ".reclaim").then(() => true, () => false)) {
373
- reason2 += " A recovery guard also needs inspection: " + file + ".reclaim.";
374
- }
375
- return reason2 + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
376
- }
377
- async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason store") {
378
- const file = await storePath(root, directory + "/lock", true);
379
- const deadline = Date.now() + waitMs;
380
- let handle;
381
- while (!handle) {
382
- try {
383
- handle = await fs3.open(file, "wx", 384);
384
- try {
385
- await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
386
- } catch (error) {
387
- await handle.close().catch(() => {
388
- });
389
- handle = void 0;
390
- await fs3.rm(file, { force: true }).catch(() => {
391
- });
392
- throw error;
393
- }
394
- } catch (error) {
395
- if (error.code !== "EEXIST") throw error;
396
- try {
397
- const owner = JSON.parse(await fs3.readFile(file, "utf8"));
398
- if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
399
- try {
400
- process.kill(owner.pid, 0);
401
- } catch (probe) {
402
- if (probe.code === "ESRCH") {
403
- const reclaim = file + ".reclaim";
404
- let guard;
405
- try {
406
- guard = await fs3.open(reclaim, "wx", 384);
407
- const current = JSON.parse(await fs3.readFile(file, "utf8"));
408
- if (current.pid === owner.pid && current.host === owner.host) await fs3.unlink(file);
409
- } finally {
410
- if (guard) {
411
- await guard.close();
412
- await fs3.rm(reclaim, { force: true });
413
- }
414
- }
415
- }
416
- }
417
- }
418
- } catch {
419
- }
420
- if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
421
- await new Promise((resolve) => setTimeout(resolve, 40));
422
- }
423
- }
424
- try {
425
- return await run();
426
- } finally {
427
- await handle.close();
428
- await fs3.unlink(file);
429
- }
430
- }
431
- var init_store_lock = __esm({
432
- "src/utils/store-lock.ts"() {
433
- "use strict";
434
- init_storage();
435
- }
436
- });
437
-
438
- // src/audit/findings.ts
439
- import { createHash } from "crypto";
440
- import { z } from "zod";
441
- function findingId(finding) {
442
- const e = finding.evidence;
443
- let key;
444
- switch (e.kind) {
445
- case "missing-path":
446
- key = e.resolvedPath && e.resolvedPath !== e.claimed ? [e.claimed, e.resolvedPath] : e.claimed;
447
- break;
448
- case "unmentioned-dir":
449
- key = e.dir;
450
- break;
451
- case "count-mismatch":
452
- key = [e.unit.replace(/s$/, ""), e.countedFrom];
453
- break;
454
- case "missing-script":
455
- key = e.scope && e.scope.directory !== "." ? [e.scriptName, e.scope.directory] : e.scriptName;
456
- break;
457
- case "doc-behind-manifests":
458
- key = null;
459
- break;
460
- case "decision-anchor":
461
- key = [e.decisionId, e.provenance?.revision, e.provenance?.approval];
462
- break;
463
- }
464
- return digest([finding.type, finding.anchor.doc, key]);
465
- }
466
- var digest, checkSchema, commitSchema, anchorSchema, count, scopeSchema, evidenceSchema, findingSchema, issueSchema, advisorySchema, checkResultSchema;
467
- var init_findings = __esm({
468
- "src/audit/findings.ts"() {
469
- "use strict";
470
- digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
471
- checkSchema = z.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"]);
472
- commitSchema = z.object({ hash: z.string().regex(/^[a-f0-9]{40,64}$/), date: z.string(), subject: z.string() });
473
- anchorSchema = z.object({ doc: z.string(), line: z.number().int().positive().nullable(), excerpt: z.string().nullable() });
474
- count = z.number().int().nonnegative();
475
- scopeSchema = z.object({
476
- basis: z.enum(["repository", "document", "document-link", "explicit"]),
477
- directory: z.string(),
478
- candidates: z.array(z.string())
479
- });
480
- evidenceSchema = z.discriminatedUnion("kind", [
481
- z.object({
482
- kind: z.literal("missing-path"),
483
- claimed: z.string(),
484
- renamedTo: z.string().nullable(),
485
- deletedInCommit: commitSchema.nullable(),
486
- everTracked: z.boolean(),
487
- parentDirExists: z.boolean(),
488
- resolvedPath: z.string().optional(),
489
- scope: scopeSchema.optional()
490
- }),
491
- z.object({
492
- kind: z.literal("unmentioned-dir"),
493
- dir: z.string(),
494
- sourceFileCount: count,
495
- firstCommit: commitSchema.nullable(),
496
- checkedDocs: z.array(z.string())
497
- }),
498
- z.object({
499
- kind: z.literal("count-mismatch"),
500
- claimed: count,
501
- actual: count,
502
- unit: z.string(),
503
- countedFrom: z.string(),
504
- members: z.array(z.string())
505
- }),
506
- z.object({
507
- kind: z.literal("missing-script"),
508
- scriptName: z.string(),
509
- invocation: z.string(),
510
- manifestsChecked: z.array(z.string()),
511
- availableScripts: z.array(z.string()),
512
- scope: scopeSchema.optional()
513
- }),
514
- z.object({
515
- kind: z.literal("doc-behind-manifests"),
516
- docLastCommit: commitSchema,
517
- manifestCommits: z.array(commitSchema.extend({ files: z.array(z.string()) })),
518
- totalCommits: count,
519
- matches: z.array(z.object({ dependency: z.string(), manifest: z.string(), before: z.string().nullable(), after: z.string().nullable(), line: z.number().int().positive(), excerpt: z.string() })).optional(),
520
- matchingIncomplete: z.boolean().optional()
521
- }),
522
- z.object({
523
- kind: z.literal("decision-anchor"),
524
- decisionId: z.string(),
525
- title: z.string(),
526
- changedFiles: z.array(z.string()),
527
- refreshedHash: z.string(),
528
- provenance: z.object({}).passthrough().optional()
529
- })
530
- ]);
531
- findingSchema = z.object({ message: z.string(), anchor: anchorSchema, evidence: evidenceSchema });
532
- issueSchema = findingSchema.extend({
533
- type: z.enum(["deleted-reference", "new-module", "stale-count", "dead-command"]),
534
- confidence: z.enum(["certain", "likely"])
535
- });
536
- advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z.enum(["recheck", "informational"]).optional() });
537
- checkResultSchema = z.object({
538
- issues: z.array(issueSchema),
539
- advisories: z.array(advisorySchema),
540
- suppressedAdvisories: z.array(advisorySchema).optional(),
541
- skipped: z.array(z.object({ check: z.string(), reason: z.string(), doc: z.string().optional() }))
542
- });
543
- }
544
- });
545
-
546
- // src/automation/notifications.ts
547
- import { z as z2 } from "zod";
548
- function condition(finding) {
549
- const source = finding.current ?? finding.original;
550
- const e = source.evidence;
551
- const id = e.kind === "decision-anchor" ? digest([source.type, source.anchor.doc, e.decisionId]) : finding.id;
552
- const result = { level: "confidence" in source ? 2 : 1, facts: [] };
553
- switch (e.kind) {
554
- case "count-mismatch":
555
- result.magnitude = Math.abs(e.actual - e.claimed);
556
- break;
557
- case "unmentioned-dir":
558
- result.magnitude = e.sourceFileCount;
559
- break;
560
- case "decision-anchor":
561
- result.facts = [...e.changedFiles].sort();
562
- break;
563
- case "doc-behind-manifests":
564
- result.facts = (e.matches ?? []).map((m) => JSON.stringify([m.manifest, m.dependency, m.after])).sort();
565
- break;
566
- }
567
- return { id, condition: result };
568
- }
569
- function notificationConditions(findings) {
570
- const active = findings.filter((f) => f.status !== "resolved");
571
- const currentIds = new Set(active.filter((f) => f.current).map((f) => condition(f).id));
572
- const result = {};
573
- for (const f of active) {
574
- const value = condition(f);
575
- if (!f.current && currentIds.has(value.id)) continue;
576
- const previous = result[value.id];
577
- result[value.id] = previous ? {
578
- level: Math.max(previous.level, value.condition.level),
579
- facts: [.../* @__PURE__ */ new Set([...previous.facts, ...value.condition.facts])].sort(),
580
- ...previous.magnitude !== void 0 || value.condition.magnitude !== void 0 ? { magnitude: Math.max(previous.magnitude ?? 0, value.condition.magnitude ?? 0) } : {}
581
- } : value.condition;
582
- }
583
- return result;
584
- }
585
- function createNotificationState(findings, head) {
586
- return { version: 1, baselineHead: head, baseline: notificationConditions(findings), delivered: {}, cleared: [] };
587
- }
588
- function worsened(current, previous) {
589
- return !previous || current.level > previous.level || (current.magnitude ?? 0) > (previous.magnitude ?? 0) || current.facts.some((fact) => !previous.facts.includes(fact));
590
- }
591
- function completionFindings(state, findings) {
592
- const current = notificationConditions(findings);
593
- const cleared = new Set(state.cleared);
594
- const resolvedIds = new Set(findings.filter((f) => f.status === "resolved").map((f) => condition(f).id));
595
- for (const id of Object.keys(state.baseline)) if (resolvedIds.has(id) && !current[id]) cleared.add(id);
596
- state.cleared = [...cleared];
597
- const selected = [];
598
- const selectedIds = /* @__PURE__ */ new Set();
599
- const delivered = { ...state.delivered };
600
- for (const finding of findings) {
601
- const value = condition(finding);
602
- const previous = state.delivered[value.id];
603
- value.condition = current[value.id] ?? value.condition;
604
- if (finding.status === "unverified" || !finding.current && finding.status !== "resolved") {
605
- continue;
606
- }
607
- if (finding.status === "resolved") {
608
- if (!current[value.id]) delete delivered[value.id];
609
- continue;
610
- }
611
- const eligible = cleared.has(value.id) || worsened(value.condition, state.baseline[value.id]);
612
- if (!eligible) continue;
613
- if (!selectedIds.has(value.id) && worsened(value.condition, previous)) {
614
- selectedIds.add(value.id);
615
- const source = finding.current;
616
- selected.push(source.evidence.kind === "decision-anchor" ? { ...finding, current: { ...source, evidence: { ...source.evidence, changedFiles: value.condition.facts } } } : finding);
617
- if (selected.length <= MAX_COMPLETION_FINDINGS) delivered[value.id] = value.condition;
618
- }
619
- }
620
- state.delivered = delivered;
621
- return selected;
622
- }
623
- function completionSummary(findings, reportPath) {
624
- if (!findings.length) return null;
625
- const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
626
- return [
627
- `Mason: ${findings.length} new or worsened finding(s) since the retained baseline.`,
628
- ...findings.slice(0, MAX_COMPLETION_FINDINGS).map((f) => {
629
- const source = f.current ?? f.original;
630
- 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;
631
- return `[${f.status === "unresolved" ? "issue" : "advisory"}] ${safe(source.anchor.doc)}: ${safe(detail)}`;
632
- }),
633
- ...findings.length > MAX_COMPLETION_FINDINGS ? [`${findings.length - MAX_COMPLETION_FINDINGS} more in the report; pending later feedback.`] : [],
634
- `Evidence: ${reportPath}. Explicit audit: mason_automation(action: "check").`,
635
- "Inspect findings within the authorized work. Concurrent changes may also contribute; this summary does not establish who introduced them."
636
- ].join("\n");
637
- }
638
- var conditionSchema, MAX_COMPLETION_FINDINGS, notificationStateSchema;
639
- var init_notifications = __esm({
640
- "src/automation/notifications.ts"() {
641
- "use strict";
642
- init_findings();
643
- conditionSchema = z2.object({
644
- level: z2.number(),
645
- facts: z2.array(z2.string()),
646
- magnitude: z2.number().optional()
647
- });
648
- MAX_COMPLETION_FINDINGS = 4;
649
- notificationStateSchema = z2.object({
650
- version: z2.literal(1),
651
- baselineHead: z2.string(),
652
- baseline: z2.record(conditionSchema),
653
- delivered: z2.record(conditionSchema),
654
- cleared: z2.array(z2.string())
655
- });
656
- }
657
- });
658
-
659
- // src/automation/store.ts
660
- import { z as z3 } from "zod";
661
- function parseState(raw) {
662
- try {
663
- return stateSchema.parse(raw);
315
+ if (parsed === null) throw new Error("expected a JSON object, received null");
316
+ return parsed;
664
317
  } catch (error) {
665
- throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
318
+ if (error.code === "ENOENT") return null;
319
+ throw new Error(`Invalid Mason store ${relative}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
666
320
  }
667
321
  }
668
- function withLock(root, directory, run, waitMs = 5e3) {
669
- return withStoreLock(root, directory, run, waitMs, "Automation");
322
+ async function writeStoreJson(root, relative, value) {
323
+ const payload = JSON.stringify(value, null, 2) + "\n";
324
+ if (Buffer.byteLength(payload) > 10 * 1024 * 1024) {
325
+ throw new Error(`Mason store ${relative} exceeds 10 MiB`);
326
+ }
327
+ const file = await storePath(root, relative, true);
328
+ const temporary = path3.join(path3.dirname(file), `.${path3.basename(file)}.${randomUUID()}.tmp`);
329
+ try {
330
+ const handle = await fs2.open(temporary, "wx", 384);
331
+ try {
332
+ await handle.writeFile(payload, "utf8");
333
+ await handle.sync();
334
+ } finally {
335
+ await handle.close();
336
+ }
337
+ await fs2.rename(temporary, file);
338
+ } finally {
339
+ await fs2.rm(temporary, { force: true });
340
+ }
670
341
  }
671
- var hostSchema, events, stateSchema;
672
- var init_store = __esm({
673
- "src/automation/store.ts"() {
342
+ var init_storage = __esm({
343
+ "src/utils/storage.ts"() {
674
344
  "use strict";
675
- init_store_lock();
676
- init_notifications();
677
- hostSchema = z3.enum(["claude", "codex"]);
678
- events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
679
- stateSchema = z3.object({
680
- version: z3.literal(1),
681
- root: z3.string(),
682
- gitDir: z3.string(),
683
- branch: z3.string(),
684
- baselines: z3.array(z3.object({ path: z3.string(), at: z3.string(), event: z3.string(), fingerprint: z3.string() })).max(128),
685
- sessions: z3.record(z3.object({
686
- host: hostSchema,
687
- lastUsed: z3.string(),
688
- notifiedDiagnostics: z3.array(z3.string()).default([]),
689
- pending: z3.record(z3.string()),
690
- coverageGaps: z3.array(z3.string()),
691
- events: z3.record(z3.object({ at: z3.string(), count: z3.number().int().positive() }))
692
- })),
693
- updatedAt: z3.string(),
694
- fingerprint: z3.string().nullable(),
695
- latest: z3.string().nullable(),
696
- notifications: notificationStateSchema.optional(),
697
- agentNotifications: notificationStateSchema.optional(),
698
- analysis: z3.object({ id: z3.string(), fingerprint: z3.string(), completedAt: z3.number(), path: z3.string() }).optional()
699
- });
345
+ init_paths();
346
+ init_files();
700
347
  }
701
348
  });
702
349
 
@@ -752,12 +399,12 @@ var init_inspection = __esm({
752
399
  });
753
400
 
754
401
  // src/audit/inputs.ts
755
- import fs4 from "fs/promises";
402
+ import fs3 from "fs/promises";
756
403
  import * as nativeFs from "fs";
757
404
  import path5 from "path";
758
405
  import fg3 from "fast-glob";
759
406
  async function auditInputPath(root, file) {
760
- if (file === ".") return fs4.realpath(root);
407
+ if (file === ".") return fs3.realpath(root);
761
408
  try {
762
409
  return await storePath(root, file);
763
410
  } catch (error) {
@@ -780,15 +427,15 @@ async function readAuditInput(root, file) {
780
427
  async function auditGlob(root, patterns, options) {
781
428
  if (Array.isArray(patterns) && !patterns.length) return [];
782
429
  const directories = /* @__PURE__ */ new Set();
783
- const readdir2 = ((directory, opts, callback) => {
784
- const relative = path5.relative(root, directory).split(path5.sep).join("/") || ".";
430
+ const readdir2 = ((directory2, opts, callback) => {
431
+ const relative = path5.relative(root, directory2).split(path5.sep).join("/") || ".";
785
432
  directories.add(relative);
786
433
  if (directories.size > MAX_INPUTS) {
787
434
  callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
788
435
  return;
789
436
  }
790
437
  auditInputPath(root, relative).then(() => {
791
- nativeFs.readdir(directory, opts, callback);
438
+ nativeFs.readdir(directory2, opts, callback);
792
439
  }, callback);
793
440
  });
794
441
  const lstat2 = ((file, callback) => {
@@ -918,25 +565,25 @@ function commandClaims(line, lineNo, cwd, shell = false) {
918
565
  if (text2.startsWith("#") || /^(?:echo|printf)\b/.test(text2)) continue;
919
566
  const cd = text2.match(/^cd\s+(.+)$/);
920
567
  if (cd) {
921
- const directory = literal(cd[1]);
922
- cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path6.posix.join(cwd ?? ".", directory);
568
+ const directory2 = literal(cd[1]);
569
+ cwd = directory2 === null || cwd === null || /\s/.test(directory2) && !/^["']/.test(cd[1]) ? null : path6.posix.join(cwd ?? ".", directory2);
923
570
  continue;
924
571
  }
925
572
  for (const match of text2.matchAll(COMMAND)) {
926
573
  const flags = match[2].trim();
927
574
  const selector = flags.match(/^(?:--prefix|--dir|-C)(?:\s+|=)(.+)$/);
928
- let directory = cwd;
575
+ let directory2 = cwd;
929
576
  let scopeUnknown;
930
577
  if (shell && match.index !== 0 || match[3].startsWith("-")) scopeUnknown = "Unsupported shell wrapper or options before the script.";
931
578
  if (selector) {
932
579
  const selected = literal(selector[1]);
933
- directory = selected === null || cwd === null ? null : path6.posix.join(cwd ?? ".", selected);
580
+ directory2 = selected === null || cwd === null ? null : path6.posix.join(cwd ?? ".", selected);
934
581
  } else if (flags) scopeUnknown = "Unsupported package-manager options before run: " + flags;
935
582
  const tail = text2.slice(match.index + match[0].length).split(/\s--(?:\s|$)/)[0];
936
583
  if (/(?:^|\s)(?:--workspace(?:s)?|-w|--filter|-F|--prefix|--dir|-C|--if-present)(?:\s|=|$)/.test(tail)) {
937
584
  scopeUnknown = "Package selection or conditional options after the script need review.";
938
585
  }
939
- if (/[|()]/.test(text2) || /\b(?:pushd|popd|if|for|while)\b/.test(text2) || directory === null) {
586
+ if (/[|()]/.test(text2) || /\b(?:pushd|popd|if|for|while)\b/.test(text2) || directory2 === null) {
940
587
  scopeUnknown = "The command's working directory or shell control flow could not be resolved.";
941
588
  }
942
589
  claims.push({
@@ -944,7 +591,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
944
591
  invocation: match[0],
945
592
  excerpt: match[0],
946
593
  line: lineNo,
947
- ...directory !== void 0 && directory !== null ? { directory } : {},
594
+ ...directory2 !== void 0 && directory2 !== null ? { directory: directory2 } : {},
948
595
  ...scopeUnknown ? { scopeUnknown } : {}
949
596
  });
950
597
  }
@@ -1351,7 +998,7 @@ var init_git = __esm({
1351
998
  });
1352
999
 
1353
1000
  // src/audit/docs.ts
1354
- import fs5 from "fs/promises";
1001
+ import fs4 from "fs/promises";
1355
1002
  import path7 from "path";
1356
1003
  import fg5 from "fast-glob";
1357
1004
  async function dirtyDocs(resolvedRoot, files) {
@@ -1423,7 +1070,7 @@ async function localDocPaths(root) {
1423
1070
  for (const dir of [".", ".claude"]) {
1424
1071
  let entries;
1425
1072
  try {
1426
- entries = await fs5.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1073
+ entries = await fs4.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1427
1074
  } catch (error) {
1428
1075
  if (error.code === "ENOENT") continue;
1429
1076
  throw error;
@@ -1487,27 +1134,27 @@ var init_docs = __esm({
1487
1134
  });
1488
1135
 
1489
1136
  // src/audit/scope.ts
1490
- import fs6 from "fs/promises";
1137
+ import fs5 from "fs/promises";
1491
1138
  import path8 from "path";
1492
- function scopedPath(directory, value) {
1139
+ function scopedPath(directory2, value) {
1493
1140
  if (path8.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
1494
- const resolved = path8.posix.normalize(path8.posix.join(directory, value));
1141
+ const resolved = path8.posix.normalize(path8.posix.join(directory2, value));
1495
1142
  return resolved === ".." || resolved.startsWith("../") ? null : resolved;
1496
1143
  }
1497
1144
  function pathClaimScope(doc, claim) {
1498
- const directory = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
1499
- const local = scopedPath(directory, claim.path);
1145
+ const directory2 = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
1146
+ const local = scopedPath(directory2, claim.path);
1500
1147
  if (!local) return null;
1501
- const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
1148
+ const candidates = claim.relativeTo === "document" || directory2 === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
1502
1149
  return {
1503
- basis: claim.relativeTo === "document" ? "document-link" : directory === "." ? "repository" : "document",
1504
- directory,
1150
+ basis: claim.relativeTo === "document" ? "document-link" : directory2 === "." ? "repository" : "document",
1151
+ directory: directory2,
1505
1152
  candidates
1506
1153
  };
1507
1154
  }
1508
1155
  async function pathExists(root, file) {
1509
1156
  try {
1510
- await fs6.access(await auditInputPath(root, file));
1157
+ await fs5.access(await auditInputPath(root, file));
1511
1158
  return true;
1512
1159
  } catch (error) {
1513
1160
  if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return false;
@@ -1528,6 +1175,100 @@ var init_scope = __esm({
1528
1175
  }
1529
1176
  });
1530
1177
 
1178
+ // src/utils/store-lock.ts
1179
+ import fs6 from "fs/promises";
1180
+ import os from "os";
1181
+ async function lockDiagnostic(file) {
1182
+ let reason2;
1183
+ try {
1184
+ const owner = JSON.parse(await fs6.readFile(file, "utf8"));
1185
+ if (!owner || typeof owner.host !== "string" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
1186
+ reason2 = "Lock owner data is incomplete or malformed.";
1187
+ } else if (owner.host !== os.hostname()) {
1188
+ reason2 = "The lock belongs to another host; its process cannot be checked locally.";
1189
+ } else {
1190
+ try {
1191
+ process.kill(owner.pid, 0);
1192
+ reason2 = `Local owner PID ${owner.pid} is still running.`;
1193
+ } catch (error) {
1194
+ reason2 = error.code === "ESRCH" ? `Local owner PID ${owner.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.pid} could not be checked.`;
1195
+ }
1196
+ }
1197
+ } catch (error) {
1198
+ reason2 = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
1199
+ }
1200
+ if (await fs6.lstat(file + ".reclaim").then(() => true, () => false)) {
1201
+ reason2 += " A recovery guard also needs inspection: " + file + ".reclaim.";
1202
+ }
1203
+ return reason2 + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
1204
+ }
1205
+ async function withStoreLock(root, directory2, run, waitMs = 5e3, label = "Mason store") {
1206
+ const file = await storePath(root, directory2 + "/lock", true);
1207
+ const deadline = Date.now() + waitMs;
1208
+ let handle;
1209
+ while (!handle) {
1210
+ try {
1211
+ handle = await fs6.open(file, "wx", 384);
1212
+ } catch (error) {
1213
+ if (process.platform === "win32" && error.code === "EPERM") {
1214
+ if (Date.now() >= deadline) throw error;
1215
+ await new Promise((resolve) => setTimeout(resolve, 40));
1216
+ continue;
1217
+ }
1218
+ if (error.code !== "EEXIST") throw error;
1219
+ try {
1220
+ const owner = JSON.parse(await fs6.readFile(file, "utf8"));
1221
+ if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
1222
+ try {
1223
+ process.kill(owner.pid, 0);
1224
+ } catch (probe) {
1225
+ if (probe.code === "ESRCH") {
1226
+ const reclaim = file + ".reclaim";
1227
+ let guard;
1228
+ try {
1229
+ guard = await fs6.open(reclaim, "wx", 384);
1230
+ const current = JSON.parse(await fs6.readFile(file, "utf8"));
1231
+ if (current.pid === owner.pid && current.host === owner.host) await fs6.unlink(file);
1232
+ } finally {
1233
+ if (guard) {
1234
+ await guard.close();
1235
+ await fs6.rm(reclaim, { force: true });
1236
+ }
1237
+ }
1238
+ }
1239
+ }
1240
+ }
1241
+ } catch {
1242
+ }
1243
+ if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
1244
+ await new Promise((resolve) => setTimeout(resolve, 40));
1245
+ }
1246
+ if (handle) {
1247
+ try {
1248
+ await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
1249
+ } catch (error) {
1250
+ await handle.close().catch(() => {
1251
+ });
1252
+ await fs6.rm(file, { force: true }).catch(() => {
1253
+ });
1254
+ throw error;
1255
+ }
1256
+ }
1257
+ }
1258
+ try {
1259
+ return await run();
1260
+ } finally {
1261
+ await handle.close();
1262
+ await fs6.unlink(file);
1263
+ }
1264
+ }
1265
+ var init_store_lock = __esm({
1266
+ "src/utils/store-lock.ts"() {
1267
+ "use strict";
1268
+ init_storage();
1269
+ }
1270
+ });
1271
+
1531
1272
  // src/snapshot/lock.ts
1532
1273
  var init_lock = __esm({
1533
1274
  "src/snapshot/lock.ts"() {
@@ -1614,7 +1355,7 @@ var init_test_map = __esm({
1614
1355
 
1615
1356
  // src/snapshot/snapshot.ts
1616
1357
  import path10 from "path";
1617
- import { z as z4 } from "zod";
1358
+ import { z } from "zod";
1618
1359
  async function inspectSnapshot(rootDir) {
1619
1360
  try {
1620
1361
  const raw = await readStoreJson(rootDir, ".mason/snapshot.json");
@@ -1648,30 +1389,30 @@ var init_snapshot = __esm({
1648
1389
  init_lock();
1649
1390
  init_paths();
1650
1391
  init_test_map();
1651
- repoPath = z4.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
1392
+ repoPath = z.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
1652
1393
  verificationFields = {
1653
- refreshedHash: z4.string().optional(),
1654
- verifiedAt: z4.string().optional(),
1655
- verifiedHash: z4.string().optional(),
1656
- verificationToken: z4.string().optional(),
1657
- verificationFailed: z4.boolean().optional(),
1658
- verificationNote: z4.string().optional()
1394
+ refreshedHash: z.string().optional(),
1395
+ verifiedAt: z.string().optional(),
1396
+ verifiedHash: z.string().optional(),
1397
+ verificationToken: z.string().optional(),
1398
+ verificationFailed: z.boolean().optional(),
1399
+ verificationNote: z.string().optional()
1659
1400
  };
1660
- featureSchema = z4.object({
1661
- description: z4.string(),
1662
- files: z4.array(repoPath),
1663
- tests: z4.array(repoPath).optional(),
1664
- type: z4.enum(["capability", "infrastructure"]).optional(),
1401
+ featureSchema = z.object({
1402
+ description: z.string(),
1403
+ files: z.array(repoPath),
1404
+ tests: z.array(repoPath).optional(),
1405
+ type: z.enum(["capability", "infrastructure"]).optional(),
1665
1406
  ...verificationFields
1666
1407
  }).passthrough();
1667
- flowSchema = z4.object({ description: z4.string(), chain: z4.array(repoPath), ...verificationFields }).passthrough();
1668
- snapshotSchema = z4.object({
1669
- version: z4.literal(2),
1670
- createdAt: z4.string(),
1671
- updatedAt: z4.string(),
1672
- gitHash: z4.string(),
1673
- features: z4.record(featureSchema),
1674
- flows: z4.record(flowSchema)
1408
+ flowSchema = z.object({ description: z.string(), chain: z.array(repoPath), ...verificationFields }).passthrough();
1409
+ snapshotSchema = z.object({
1410
+ version: z.literal(2),
1411
+ createdAt: z.string(),
1412
+ updatedAt: z.string(),
1413
+ gitHash: z.string(),
1414
+ features: z.record(featureSchema),
1415
+ flows: z.record(flowSchema)
1675
1416
  }).passthrough();
1676
1417
  }
1677
1418
  });
@@ -2117,19 +1858,19 @@ async function commandInputs(root, docs) {
2117
1858
  const resolutions = [];
2118
1859
  for (const doc of docs) for (const claim of doc.claims.commands) {
2119
1860
  const base = documentScope(doc.path);
2120
- const directory = scopedPath(base, claim.directory ?? ".");
1861
+ const directory2 = scopedPath(base, claim.directory ?? ".");
2121
1862
  const scope = {
2122
1863
  basis: claim.directory === void 0 ? "document" : "explicit",
2123
- directory: directory ?? base,
1864
+ directory: directory2 ?? base,
2124
1865
  candidates: []
2125
1866
  };
2126
1867
  const resolution = { doc: doc.path, claim, scope, manifest: null, scripts: [], elsewhere: [] };
2127
1868
  resolutions.push(resolution);
2128
- if (claim.scopeUnknown || directory === null) {
1869
+ if (claim.scopeUnknown || directory2 === null) {
2129
1870
  resolution.problem = claim.scopeUnknown ?? "The command's directory leaves the repository.";
2130
1871
  continue;
2131
1872
  }
2132
- let cursor = directory;
1873
+ let cursor = directory2;
2133
1874
  while (true) {
2134
1875
  const manifest = path15.posix.join(cursor, "package.json");
2135
1876
  scope.candidates.push(manifest);
@@ -2145,7 +1886,7 @@ async function commandInputs(root, docs) {
2145
1886
  cursor = path15.posix.dirname(cursor);
2146
1887
  }
2147
1888
  if (!resolution.manifest && !resolution.problem) {
2148
- resolution.problem = `No package.json for the command's scope (${directory}).`;
1889
+ resolution.problem = `No package.json for the command's scope (${directory2}).`;
2149
1890
  }
2150
1891
  if (resolution.problem || resolution.scripts.includes(claim.scriptName) || claim.directory !== void 0) continue;
2151
1892
  inventory ??= await commandManifests(root);
@@ -2483,7 +2224,7 @@ var init_trust = __esm({
2483
2224
  });
2484
2225
 
2485
2226
  // src/decisions/provenance.ts
2486
- import { z as z5 } from "zod";
2227
+ import { z as z2 } from "zod";
2487
2228
  function decisionContent(record) {
2488
2229
  return {
2489
2230
  title: record.title,
@@ -2547,67 +2288,67 @@ var init_provenance = __esm({
2547
2288
  "use strict";
2548
2289
  init_paths();
2549
2290
  init_trust();
2550
- text = (max) => z5.string().trim().min(1).max(max);
2551
- decisionSourceSchema = z5.object({
2552
- kind: z5.enum(["pull_request", "issue", "incident", "discussion", "document", "other"]),
2291
+ text = (max) => z2.string().trim().min(1).max(max);
2292
+ decisionSourceSchema = z2.object({
2293
+ kind: z2.enum(["pull_request", "issue", "incident", "discussion", "document", "other"]),
2553
2294
  reference: text(1e3),
2554
2295
  note: text(500).optional()
2555
2296
  }).strict();
2556
- attributionSchema = z5.object({
2297
+ attributionSchema = z2.object({
2557
2298
  owner: text(200).nullable().optional(),
2558
- sources: z5.array(decisionSourceSchema).max(20).optional(),
2299
+ sources: z2.array(decisionSourceSchema).max(20).optional(),
2559
2300
  actor: text(200).optional()
2560
2301
  });
2561
- contentSchema = z5.object({
2562
- title: z5.string().min(1),
2563
- body: z5.string().min(1),
2564
- category: z5.enum(["decision", "gotcha", "deprecation", "convention"]),
2565
- files: z5.array(z5.string().refine((f) => normalizeRepoPath(f) !== null)),
2302
+ contentSchema = z2.object({
2303
+ title: z2.string().min(1),
2304
+ body: z2.string().min(1),
2305
+ category: z2.enum(["decision", "gotcha", "deprecation", "convention"]),
2306
+ files: z2.array(z2.string().refine((f) => normalizeRepoPath(f) !== null)),
2566
2307
  owner: text(200).optional(),
2567
- sources: z5.array(decisionSourceSchema).max(20)
2308
+ sources: z2.array(decisionSourceSchema).max(20)
2568
2309
  });
2569
- approvalSchema = z5.enum(["unreviewed", "proposed", "accepted"]);
2570
- statusSchema = z5.enum(["active", "superseded", "retired"]);
2571
- reviewEvidenceSchema = z5.object({
2572
- baseHash: z5.string(),
2573
- headHash: z5.string(),
2574
- historyAvailable: z5.boolean(),
2575
- changedFiles: z5.array(z5.string()),
2576
- localChanges: z5.array(z5.string())
2310
+ approvalSchema = z2.enum(["unreviewed", "proposed", "accepted"]);
2311
+ statusSchema = z2.enum(["active", "superseded", "retired"]);
2312
+ reviewEvidenceSchema = z2.object({
2313
+ baseHash: z2.string(),
2314
+ headHash: z2.string(),
2315
+ historyAvailable: z2.boolean(),
2316
+ changedFiles: z2.array(z2.string()),
2317
+ localChanges: z2.array(z2.string())
2577
2318
  });
2578
- eventSchema = z5.object({
2579
- kind: z5.enum(["imported", "created", "revised", "accepted", "reaffirmed", "retired", "superseded"]),
2580
- at: z5.string().datetime(),
2319
+ eventSchema = z2.object({
2320
+ kind: z2.enum(["imported", "created", "revised", "accepted", "reaffirmed", "retired", "superseded"]),
2321
+ at: z2.string().datetime(),
2581
2322
  actor: text(200).optional(),
2582
2323
  note: text(1500).optional(),
2583
- revision: z5.number().int().positive(),
2324
+ revision: z2.number().int().positive(),
2584
2325
  content: contentSchema,
2585
2326
  approval: approvalSchema,
2586
2327
  status: statusSchema,
2587
- refreshedHash: z5.string(),
2328
+ refreshedHash: z2.string(),
2588
2329
  evidence: reviewEvidenceSchema.optional()
2589
2330
  });
2590
- legacySchema = z5.object({
2591
- version: z5.literal(1),
2592
- id: z5.string().regex(/^[a-zA-Z0-9_-]+$/),
2593
- title: z5.string().min(1),
2594
- body: z5.string().min(1),
2331
+ legacySchema = z2.object({
2332
+ version: z2.literal(1),
2333
+ id: z2.string().regex(/^[a-zA-Z0-9_-]+$/),
2334
+ title: z2.string().min(1),
2335
+ body: z2.string().min(1),
2595
2336
  category: contentSchema.shape.category,
2596
2337
  files: contentSchema.shape.files,
2597
- createdAt: z5.string(),
2598
- updatedAt: z5.string(),
2599
- refreshedHash: z5.string(),
2600
- status: z5.enum(["active", "superseded"]),
2601
- supersededBy: z5.string().optional()
2338
+ createdAt: z2.string(),
2339
+ updatedAt: z2.string(),
2340
+ refreshedHash: z2.string(),
2341
+ status: z2.enum(["active", "superseded"]),
2342
+ supersededBy: z2.string().optional()
2602
2343
  }).passthrough();
2603
2344
  currentSchema = legacySchema.extend({
2604
- version: z5.literal(2),
2345
+ version: z2.literal(2),
2605
2346
  status: statusSchema,
2606
2347
  approval: approvalSchema,
2607
- revision: z5.number().int().positive(),
2348
+ revision: z2.number().int().positive(),
2608
2349
  owner: text(200).optional(),
2609
- sources: z5.array(decisionSourceSchema).max(20),
2610
- history: z5.array(eventSchema).min(1)
2350
+ sources: z2.array(decisionSourceSchema).max(20),
2351
+ history: z2.array(eventSchema).min(1)
2611
2352
  }).superRefine((record, ctx) => {
2612
2353
  const invalid = (message2) => ctx.addIssue({ code: "custom", message: message2 });
2613
2354
  const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
@@ -2637,15 +2378,15 @@ var init_provenance = __esm({
2637
2378
  }
2638
2379
  if (!previous || !same(previous.content, decisionContent(record)) || previous.approval !== record.approval || previous.status !== record.status || previous.revision !== record.revision || previous.refreshedHash !== record.refreshedHash) invalid("Decision does not match the final history event");
2639
2380
  });
2640
- decisionSchema = z5.union([legacySchema, currentSchema]);
2381
+ decisionSchema = z2.union([legacySchema, currentSchema]);
2641
2382
  }
2642
2383
  });
2643
2384
 
2644
2385
  // src/decisions/decisions.ts
2645
2386
  import fs9 from "fs/promises";
2646
2387
  import path18 from "path";
2647
- import { createHash as createHash2 } from "crypto";
2648
- import { z as z6 } from "zod";
2388
+ import { createHash } from "crypto";
2389
+ import { z as z3 } from "zod";
2649
2390
  async function loadDecisionStore(rootDir) {
2650
2391
  const records = [];
2651
2392
  const diagnostics = [];
@@ -2681,7 +2422,7 @@ var init_decisions = __esm({
2681
2422
  TITLE_MAX_CHARS = 80;
2682
2423
  BODY_RECOMMENDED_CHARS = 1500;
2683
2424
  BODY_MAX_CHARS = 2500;
2684
- boundedText = (field, max, hint) => z6.string({
2425
+ boundedText = (field, max, hint) => z3.string({
2685
2426
  errorMap: (issue, ctx) => ({ message: issue.code === "too_big" ? `${field} has ${ctx.data.length} characters; maximum is ${max} (${ctx.data.length - max} over). ${hint}` : ctx.defaultError })
2686
2427
  }).trim().min(1, `${field} must be non-empty`).max(max);
2687
2428
  decisionTitleSchema = boundedText("title", TITLE_MAX_CHARS, "Tighten it to a specific headline.");
@@ -2777,38 +2518,303 @@ async function checkDecisionAnchors(ctx) {
2777
2518
  }
2778
2519
  return result;
2779
2520
  }
2780
- var init_decision_anchor = __esm({
2781
- "src/audit/checks/decision-anchor.ts"() {
2521
+ var init_decision_anchor = __esm({
2522
+ "src/audit/checks/decision-anchor.ts"() {
2523
+ "use strict";
2524
+ init_drift2();
2525
+ init_decisions();
2526
+ init_provenance();
2527
+ init_checks();
2528
+ }
2529
+ });
2530
+
2531
+ // src/audit/checks/index.ts
2532
+ function emptyResult() {
2533
+ return { issues: [], advisories: [], skipped: [] };
2534
+ }
2535
+ var CHECKS;
2536
+ var init_checks = __esm({
2537
+ "src/audit/checks/index.ts"() {
2538
+ "use strict";
2539
+ init_deleted_reference();
2540
+ init_new_module();
2541
+ init_stale_count();
2542
+ init_dead_command();
2543
+ init_deps_changed();
2544
+ init_decision_anchor();
2545
+ CHECKS = {
2546
+ "deleted-reference": checkDeletedReferences,
2547
+ "new-module": checkNewModules,
2548
+ "stale-count": checkStaleCounts,
2549
+ "dead-command": checkDeadCommands,
2550
+ "deps-changed": checkDepsChanged,
2551
+ "decision-anchor-drift": checkDecisionAnchors
2552
+ };
2553
+ }
2554
+ });
2555
+
2556
+ // src/audit/findings.ts
2557
+ import { createHash as createHash2 } from "crypto";
2558
+ import { z as z4 } from "zod";
2559
+ function findingId(finding) {
2560
+ const e = finding.evidence;
2561
+ let key;
2562
+ switch (e.kind) {
2563
+ case "missing-path":
2564
+ key = e.resolvedPath && e.resolvedPath !== e.claimed ? [e.claimed, e.resolvedPath] : e.claimed;
2565
+ break;
2566
+ case "unmentioned-dir":
2567
+ key = e.dir;
2568
+ break;
2569
+ case "count-mismatch":
2570
+ key = [e.unit.replace(/s$/, ""), e.countedFrom];
2571
+ break;
2572
+ case "missing-script":
2573
+ key = e.scope && e.scope.directory !== "." ? [e.scriptName, e.scope.directory] : e.scriptName;
2574
+ break;
2575
+ case "doc-behind-manifests":
2576
+ key = null;
2577
+ break;
2578
+ case "decision-anchor":
2579
+ key = [e.decisionId, e.provenance?.revision, e.provenance?.approval];
2580
+ break;
2581
+ }
2582
+ return digest([finding.type, finding.anchor.doc, key]);
2583
+ }
2584
+ var digest, checkSchema, commitSchema, anchorSchema, count, scopeSchema, evidenceSchema, findingSchema, issueSchema, advisorySchema, checkResultSchema;
2585
+ var init_findings = __esm({
2586
+ "src/audit/findings.ts"() {
2587
+ "use strict";
2588
+ digest = (value) => createHash2("sha256").update(JSON.stringify(value)).digest("hex");
2589
+ checkSchema = z4.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"]);
2590
+ commitSchema = z4.object({ hash: z4.string().regex(/^[a-f0-9]{40,64}$/), date: z4.string(), subject: z4.string() });
2591
+ anchorSchema = z4.object({ doc: z4.string(), line: z4.number().int().positive().nullable(), excerpt: z4.string().nullable() });
2592
+ count = z4.number().int().nonnegative();
2593
+ scopeSchema = z4.object({
2594
+ basis: z4.enum(["repository", "document", "document-link", "explicit"]),
2595
+ directory: z4.string(),
2596
+ candidates: z4.array(z4.string())
2597
+ });
2598
+ evidenceSchema = z4.discriminatedUnion("kind", [
2599
+ z4.object({
2600
+ kind: z4.literal("missing-path"),
2601
+ claimed: z4.string(),
2602
+ renamedTo: z4.string().nullable(),
2603
+ deletedInCommit: commitSchema.nullable(),
2604
+ everTracked: z4.boolean(),
2605
+ parentDirExists: z4.boolean(),
2606
+ resolvedPath: z4.string().optional(),
2607
+ scope: scopeSchema.optional()
2608
+ }),
2609
+ z4.object({
2610
+ kind: z4.literal("unmentioned-dir"),
2611
+ dir: z4.string(),
2612
+ sourceFileCount: count,
2613
+ firstCommit: commitSchema.nullable(),
2614
+ checkedDocs: z4.array(z4.string())
2615
+ }),
2616
+ z4.object({
2617
+ kind: z4.literal("count-mismatch"),
2618
+ claimed: count,
2619
+ actual: count,
2620
+ unit: z4.string(),
2621
+ countedFrom: z4.string(),
2622
+ members: z4.array(z4.string())
2623
+ }),
2624
+ z4.object({
2625
+ kind: z4.literal("missing-script"),
2626
+ scriptName: z4.string(),
2627
+ invocation: z4.string(),
2628
+ manifestsChecked: z4.array(z4.string()),
2629
+ availableScripts: z4.array(z4.string()),
2630
+ scope: scopeSchema.optional()
2631
+ }),
2632
+ z4.object({
2633
+ kind: z4.literal("doc-behind-manifests"),
2634
+ docLastCommit: commitSchema,
2635
+ manifestCommits: z4.array(commitSchema.extend({ files: z4.array(z4.string()) })),
2636
+ totalCommits: count,
2637
+ 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(),
2638
+ matchingIncomplete: z4.boolean().optional()
2639
+ }),
2640
+ z4.object({
2641
+ kind: z4.literal("decision-anchor"),
2642
+ decisionId: z4.string(),
2643
+ title: z4.string(),
2644
+ changedFiles: z4.array(z4.string()),
2645
+ refreshedHash: z4.string(),
2646
+ provenance: z4.object({}).passthrough().optional()
2647
+ })
2648
+ ]);
2649
+ findingSchema = z4.object({ message: z4.string(), anchor: anchorSchema, evidence: evidenceSchema });
2650
+ issueSchema = findingSchema.extend({
2651
+ type: z4.enum(["deleted-reference", "new-module", "stale-count", "dead-command"]),
2652
+ confidence: z4.enum(["certain", "likely"])
2653
+ });
2654
+ advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z4.enum(["recheck", "informational"]).optional() });
2655
+ checkResultSchema = z4.object({
2656
+ issues: z4.array(issueSchema),
2657
+ advisories: z4.array(advisorySchema),
2658
+ suppressedAdvisories: z4.array(advisorySchema).optional(),
2659
+ skipped: z4.array(z4.object({ check: z4.string(), reason: z4.string(), doc: z4.string().optional() }))
2660
+ });
2661
+ }
2662
+ });
2663
+
2664
+ // src/automation/notifications.ts
2665
+ import { z as z5 } from "zod";
2666
+ function condition(finding) {
2667
+ const source = finding.current ?? finding.original;
2668
+ const e = source.evidence;
2669
+ const id = e.kind === "decision-anchor" ? digest([source.type, source.anchor.doc, e.decisionId]) : finding.id;
2670
+ const result = { level: "confidence" in source ? 2 : 1, facts: [] };
2671
+ switch (e.kind) {
2672
+ case "count-mismatch":
2673
+ result.magnitude = Math.abs(e.actual - e.claimed);
2674
+ break;
2675
+ case "unmentioned-dir":
2676
+ result.magnitude = e.sourceFileCount;
2677
+ break;
2678
+ case "decision-anchor":
2679
+ result.facts = [...e.changedFiles].sort();
2680
+ break;
2681
+ case "doc-behind-manifests":
2682
+ result.facts = (e.matches ?? []).map((m) => JSON.stringify([m.manifest, m.dependency, m.after])).sort();
2683
+ break;
2684
+ }
2685
+ return { id, condition: result };
2686
+ }
2687
+ function notificationConditions(findings) {
2688
+ const active = findings.filter((f) => f.status !== "resolved");
2689
+ const currentIds = new Set(active.filter((f) => f.current).map((f) => condition(f).id));
2690
+ const result = {};
2691
+ for (const f of active) {
2692
+ const value = condition(f);
2693
+ if (!f.current && currentIds.has(value.id)) continue;
2694
+ const previous = result[value.id];
2695
+ result[value.id] = previous ? {
2696
+ level: Math.max(previous.level, value.condition.level),
2697
+ facts: [.../* @__PURE__ */ new Set([...previous.facts, ...value.condition.facts])].sort(),
2698
+ ...previous.magnitude !== void 0 || value.condition.magnitude !== void 0 ? { magnitude: Math.max(previous.magnitude ?? 0, value.condition.magnitude ?? 0) } : {}
2699
+ } : value.condition;
2700
+ }
2701
+ return result;
2702
+ }
2703
+ function createNotificationState(findings, head) {
2704
+ return { version: 1, baselineHead: head, baseline: notificationConditions(findings), delivered: {}, cleared: [] };
2705
+ }
2706
+ function worsened(current, previous) {
2707
+ return !previous || current.level > previous.level || (current.magnitude ?? 0) > (previous.magnitude ?? 0) || current.facts.some((fact) => !previous.facts.includes(fact));
2708
+ }
2709
+ function completionFindings(state, findings) {
2710
+ const current = notificationConditions(findings);
2711
+ const cleared = new Set(state.cleared);
2712
+ const resolvedIds = new Set(findings.filter((f) => f.status === "resolved").map((f) => condition(f).id));
2713
+ for (const id of Object.keys(state.baseline)) if (resolvedIds.has(id) && !current[id]) cleared.add(id);
2714
+ state.cleared = [...cleared];
2715
+ const selected = [];
2716
+ const selectedIds = /* @__PURE__ */ new Set();
2717
+ const delivered = { ...state.delivered };
2718
+ for (const finding of findings) {
2719
+ const value = condition(finding);
2720
+ const previous = state.delivered[value.id];
2721
+ value.condition = current[value.id] ?? value.condition;
2722
+ if (finding.status === "unverified" || !finding.current && finding.status !== "resolved") {
2723
+ continue;
2724
+ }
2725
+ if (finding.status === "resolved") {
2726
+ if (!current[value.id]) delete delivered[value.id];
2727
+ continue;
2728
+ }
2729
+ const eligible = cleared.has(value.id) || worsened(value.condition, state.baseline[value.id]);
2730
+ if (!eligible) continue;
2731
+ if (!selectedIds.has(value.id) && worsened(value.condition, previous)) {
2732
+ selectedIds.add(value.id);
2733
+ const source = finding.current;
2734
+ selected.push(source.evidence.kind === "decision-anchor" ? { ...finding, current: { ...source, evidence: { ...source.evidence, changedFiles: value.condition.facts } } } : finding);
2735
+ if (selected.length <= MAX_COMPLETION_FINDINGS) delivered[value.id] = value.condition;
2736
+ }
2737
+ }
2738
+ state.delivered = delivered;
2739
+ return selected;
2740
+ }
2741
+ function completionSummary(findings, reportPath) {
2742
+ if (!findings.length) return null;
2743
+ const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
2744
+ return [
2745
+ `Mason: ${findings.length} new or worsened finding(s) since the retained baseline.`,
2746
+ ...findings.slice(0, MAX_COMPLETION_FINDINGS).map((f) => {
2747
+ const source = f.current ?? f.original;
2748
+ 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;
2749
+ return `[${f.status === "unresolved" ? "issue" : "advisory"}] ${safe(source.anchor.doc)}: ${safe(detail)}`;
2750
+ }),
2751
+ ...findings.length > MAX_COMPLETION_FINDINGS ? [`${findings.length - MAX_COMPLETION_FINDINGS} more in the report; pending later feedback.`] : [],
2752
+ `Evidence: ${reportPath}. Explicit audit: mason_automation(action: "check").`,
2753
+ "Inspect findings within the authorized work. Concurrent changes may also contribute; this summary does not establish who introduced them."
2754
+ ].join("\n");
2755
+ }
2756
+ var conditionSchema, MAX_COMPLETION_FINDINGS, notificationStateSchema;
2757
+ var init_notifications = __esm({
2758
+ "src/automation/notifications.ts"() {
2782
2759
  "use strict";
2783
- init_drift2();
2784
- init_decisions();
2785
- init_provenance();
2786
- init_checks();
2760
+ init_findings();
2761
+ conditionSchema = z5.object({
2762
+ level: z5.number(),
2763
+ facts: z5.array(z5.string()),
2764
+ magnitude: z5.number().optional()
2765
+ });
2766
+ MAX_COMPLETION_FINDINGS = 4;
2767
+ notificationStateSchema = z5.object({
2768
+ version: z5.literal(1),
2769
+ baselineHead: z5.string(),
2770
+ baseline: z5.record(conditionSchema),
2771
+ delivered: z5.record(conditionSchema),
2772
+ cleared: z5.array(z5.string())
2773
+ });
2787
2774
  }
2788
2775
  });
2789
2776
 
2790
- // src/audit/checks/index.ts
2791
- function emptyResult() {
2792
- return { issues: [], advisories: [], skipped: [] };
2777
+ // src/automation/store.ts
2778
+ import { z as z6 } from "zod";
2779
+ function parseState(raw) {
2780
+ try {
2781
+ return stateSchema.parse(raw);
2782
+ } catch (error) {
2783
+ throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
2784
+ }
2793
2785
  }
2794
- var CHECKS;
2795
- var init_checks = __esm({
2796
- "src/audit/checks/index.ts"() {
2786
+ function withLock(root, directory2, run, waitMs = 5e3) {
2787
+ return withStoreLock(root, directory2, run, waitMs, "Automation");
2788
+ }
2789
+ var hostSchema, events, stateSchema;
2790
+ var init_store = __esm({
2791
+ "src/automation/store.ts"() {
2797
2792
  "use strict";
2798
- init_deleted_reference();
2799
- init_new_module();
2800
- init_stale_count();
2801
- init_dead_command();
2802
- init_deps_changed();
2803
- init_decision_anchor();
2804
- CHECKS = {
2805
- "deleted-reference": checkDeletedReferences,
2806
- "new-module": checkNewModules,
2807
- "stale-count": checkStaleCounts,
2808
- "dead-command": checkDeadCommands,
2809
- "deps-changed": checkDepsChanged,
2810
- "decision-anchor-drift": checkDecisionAnchors
2811
- };
2793
+ init_store_lock();
2794
+ init_notifications();
2795
+ hostSchema = z6.enum(["claude", "codex"]);
2796
+ events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
2797
+ stateSchema = z6.object({
2798
+ version: z6.literal(1),
2799
+ root: z6.string(),
2800
+ gitDir: z6.string(),
2801
+ branch: z6.string(),
2802
+ baselines: z6.array(z6.object({ path: z6.string(), at: z6.string(), event: z6.string(), fingerprint: z6.string() })).max(128),
2803
+ sessions: z6.record(z6.object({
2804
+ host: hostSchema,
2805
+ lastUsed: z6.string(),
2806
+ notifiedDiagnostics: z6.array(z6.string()).default([]),
2807
+ pending: z6.record(z6.string()),
2808
+ coverageGaps: z6.array(z6.string()),
2809
+ events: z6.record(z6.object({ at: z6.string(), count: z6.number().int().positive() }))
2810
+ })),
2811
+ updatedAt: z6.string(),
2812
+ fingerprint: z6.string().nullable(),
2813
+ latest: z6.string().nullable(),
2814
+ notifications: notificationStateSchema.optional(),
2815
+ agentNotifications: notificationStateSchema.optional(),
2816
+ analysis: z6.object({ id: z6.string(), fingerprint: z6.string(), completedAt: z6.number(), path: z6.string() }).optional()
2817
+ });
2812
2818
  }
2813
2819
  });
2814
2820
 
@@ -3497,18 +3503,248 @@ var init_evidence = __esm({
3497
3503
  init_dead_command();
3498
3504
  init_storage();
3499
3505
  init_advisory_review();
3500
- engineVersion = true ? "0.19.2" : "development";
3506
+ engineVersion = true ? "0.20.1" : "development";
3501
3507
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
3502
3508
  content = readAuditInput;
3503
3509
  cacheSchema = z9.object({ version: z9.literal(1), entries: z9.record(z9.object({ key: z9.string(), result: checkResultSchema })), digest: z9.string() });
3504
3510
  }
3505
3511
  });
3506
3512
 
3513
+ // src/automation/usefulness.ts
3514
+ import fs14 from "fs/promises";
3515
+ import { z as z10 } from "zod";
3516
+ async function enabled(root) {
3517
+ const raw = await readStoreJson(root, configPath);
3518
+ return raw !== null && configSchema.parse(raw).enabled;
3519
+ }
3520
+ async function configureUsefulness(dir, value, options = {}) {
3521
+ const ws = await workspace(dir);
3522
+ const configured = await withStoreLock(ws.root, directory, async () => {
3523
+ if (options.onlyIfUnset) {
3524
+ const existing = await readStoreJson(ws.root, configPath);
3525
+ if (existing !== null) return configSchema.parse(existing).enabled;
3526
+ }
3527
+ const ignore = await storePath(ws.root, directory + "/.gitignore", true);
3528
+ try {
3529
+ await fs14.writeFile(ignore, "*\n", { flag: "wx", mode: 384 });
3530
+ } catch (error) {
3531
+ if (error.code !== "EEXIST") throw error;
3532
+ }
3533
+ await writeStoreJson(ws.root, configPath, { version: 1, enabled: value });
3534
+ return value;
3535
+ });
3536
+ return { enabled: configured, storage: directory, note: "Local only. Disabling retains prior observations." };
3537
+ }
3538
+ async function observeUsefulness(root, event, report, agent, completion, elapsedMs, observedAt = (/* @__PURE__ */ new Date()).toISOString()) {
3539
+ if (!event.host || !event.sessionId || !await enabled(root)) return;
3540
+ const ws = await workspace(root);
3541
+ await withStoreLock(ws.root, directory, async () => {
3542
+ if (!await enabled(root)) return;
3543
+ const raw = await readStoreJson(root, statePath(ws));
3544
+ const state = raw === null ? empty() : stateSchema2.parse(raw);
3545
+ const now = observedAt;
3546
+ const key = hash([event.host, event.sessionId]);
3547
+ const session = state.sessions[key] ??= {
3548
+ host: event.host,
3549
+ updatedAt: now,
3550
+ checks: 0,
3551
+ checkMs: 0,
3552
+ findings: {},
3553
+ decisions: [],
3554
+ retrievalObserved: false,
3555
+ receiptIds: [],
3556
+ reportPath: report.reportPath,
3557
+ truncated: false
3558
+ };
3559
+ const newer = now >= session.updatedAt;
3560
+ if (newer) {
3561
+ session.updatedAt = now;
3562
+ session.reportPath = report.reportPath;
3563
+ }
3564
+ session.checks++;
3565
+ session.checkMs += Math.max(0, elapsedMs);
3566
+ if (newer) {
3567
+ if (event.event === "task_end") session.endedAt = now;
3568
+ else delete session.endedAt;
3569
+ }
3570
+ let latestFindings = [];
3571
+ if (!newer && [...agent.slice(0, 4), ...completion.slice(0, 4)].some((f) => !session.findings[f.id])) {
3572
+ try {
3573
+ if (!session.reportPath.startsWith(ws.directory + "/checks/")) throw new Error("Unexpected report path");
3574
+ latestFindings = z10.object({ findings: z10.array(z10.object({ id: z10.string(), status: findingSchema2.shape.status })) }).parse(await readStoreJson(root, session.reportPath)).findings;
3575
+ } catch {
3576
+ }
3577
+ }
3578
+ for (const [channel, findings] of [["agent", agent], ["completion", completion]]) {
3579
+ for (const finding of findings.slice(0, 4)) {
3580
+ if (!session.findings[finding.id] && Object.keys(session.findings).length >= 200) {
3581
+ session.truncated = true;
3582
+ continue;
3583
+ }
3584
+ const saved = session.findings[finding.id] ??= {
3585
+ type: finding.original.type,
3586
+ channels: [],
3587
+ status: newer ? finding.status : latestFindings.find((f) => f.id === finding.id)?.status ?? "unknown",
3588
+ deliveredAt: now,
3589
+ observedAt: newer ? now : session.updatedAt
3590
+ };
3591
+ if (!saved.channels.includes(channel)) saved.channels.push(channel);
3592
+ }
3593
+ }
3594
+ for (const [id, saved] of Object.entries(session.findings)) {
3595
+ if (now < saved.observedAt) continue;
3596
+ const current = report.findings.find((f) => f.id === id);
3597
+ saved.status = current?.status ?? "unknown";
3598
+ saved.observedAt = now;
3599
+ }
3600
+ if (event.retrievedDecisionIds !== void 0 && event.toolId) {
3601
+ const receipt = hash([event.toolId]);
3602
+ if (!session.receiptIds.includes(receipt)) {
3603
+ session.retrievalObserved = true;
3604
+ const decisions = [.../* @__PURE__ */ new Set([...session.decisions, ...event.retrievedDecisionIds.map((id) => hash([id]))])];
3605
+ if (decisions.length > 200) session.truncated = true;
3606
+ session.decisions = decisions.slice(0, 200);
3607
+ session.receiptIds = [...session.receiptIds, receipt].slice(-200);
3608
+ }
3609
+ }
3610
+ const keys = Object.keys(state.sessions).sort((a, b) => state.sessions[b].updatedAt.localeCompare(state.sessions[a].updatedAt));
3611
+ for (const key2 of keys.slice(50)) delete state.sessions[key2];
3612
+ await writeStoreJson(root, statePath(ws), state);
3613
+ });
3614
+ }
3615
+ async function rateUsefulness(dir, sessionId, findingId2, rating) {
3616
+ const ws = await workspace(dir);
3617
+ return withStoreLock(ws.root, directory, async () => {
3618
+ const state = stateSchema2.parse(await readStoreJson(ws.root, statePath(ws)));
3619
+ const finding = state.sessions[sessionId]?.findings[findingId2];
3620
+ if (!finding) throw new Error("Unknown retained session or finding; inspect mason stats --json.");
3621
+ finding.rating = usefulnessRating.parse(rating);
3622
+ await writeStoreJson(ws.root, statePath(ws), state);
3623
+ return { session: sessionId, finding: findingId2, rating };
3624
+ });
3625
+ }
3626
+ async function usefulnessStatus(dir, sessionId) {
3627
+ const ws = await workspace(dir);
3628
+ const raw = await readStoreJson(ws.root, statePath(ws));
3629
+ const state = raw === null ? empty() : stateSchema2.parse(raw);
3630
+ if (sessionId !== void 0 && !Object.hasOwn(state.sessions, sessionId)) throw new Error("Unknown retained session; inspect mason stats --json for session IDs.");
3631
+ const sessions = Object.entries(state.sessions).filter(([id]) => sessionId === void 0 || id === sessionId).sort(([, a], [, b]) => b.updatedAt.localeCompare(a.updatedAt)).map(([id, s]) => ({
3632
+ id,
3633
+ ...s,
3634
+ receiptIds: void 0,
3635
+ delivered: Object.keys(s.findings).length,
3636
+ resolved: Object.values(s.findings).filter((f) => f.status === "resolved").length,
3637
+ unknown: Object.values(s.findings).filter((f) => f.status === "unknown" || f.status === "unverified").length,
3638
+ open: Object.values(s.findings).filter((f) => f.status === "unresolved" || f.status === "review-required").length,
3639
+ ratings: Object.fromEntries(usefulnessRating.options.map((rating) => [rating, Object.values(s.findings).filter((f) => f.rating === rating).length])),
3640
+ deferred: Object.values(s.findings).filter((f) => f.rating === "deferred").length,
3641
+ retrievedDecisions: s.retrievalObserved ? s.decisions.length : null,
3642
+ application: "unknown",
3643
+ modelTokens: null
3644
+ }));
3645
+ const sum = (pick) => sessions.reduce((total, s) => total + pick(s), 0);
3646
+ const retrievalObservedSessions = sessions.filter((s) => s.retrievalObserved).length;
3647
+ return {
3648
+ version: 1,
3649
+ enabled: await enabled(ws.root),
3650
+ branch: ws.branch,
3651
+ sessionId: sessionId ?? null,
3652
+ summary: {
3653
+ sessions: sessions.length,
3654
+ delivered: sum((s) => s.delivered),
3655
+ resolved: sum((s) => s.resolved),
3656
+ open: sum((s) => s.open),
3657
+ unknown: sum((s) => s.unknown),
3658
+ ratings: Object.fromEntries(usefulnessRating.options.map((rating) => [rating, sum((s) => s.ratings[rating])])),
3659
+ retrievedDecisions: retrievalObservedSessions ? sum((s) => s.retrievedDecisions ?? 0) : null,
3660
+ retrievalObservedSessions,
3661
+ checks: sum((s) => s.checks),
3662
+ checkMs: sum((s) => s.checkMs),
3663
+ truncatedSessions: sessions.filter((s) => s.truncated).length,
3664
+ application: "unknown",
3665
+ modelTokens: null
3666
+ },
3667
+ note: "Counts sum distinct findings and returned records within each retained session; the same item may appear in multiple sessions. Status reflects the last observed check, not a fresh verification. Up to 50 sessions and 200 findings/records per session are retained. Resolution after a notice does not establish causation. Ratings are supplied feedback, not authenticated judgments. Lesson application, later-session reuse and model-token cost are unknown.",
3668
+ sessions
3669
+ };
3670
+ }
3671
+ function summarizeUsefulness(result) {
3672
+ const s = result.summary;
3673
+ return [
3674
+ `Mason stats \u2014 tracking ${result.enabled ? "enabled" : "disabled"} (local only)`,
3675
+ `${s.sessions} retained session(s)${result.sessionId ? " selected" : " in this branch"}. Counts are summed per session.`,
3676
+ `Findings: ${s.delivered} delivered; ${s.resolved} observed resolved; ${s.open} open; ${s.unknown} unknown.`,
3677
+ `Feedback: ${s.ratings.helpful} helpful; ${s.ratings["already-knew"]} already knew; ${s.ratings.irrelevant} irrelevant; ${s.ratings.deferred} deferred.`,
3678
+ `Decision records returned: ${s.retrievedDecisions ?? "unknown"} (${s.retrievalObservedSessions}/${s.sessions} sessions with retrieval receipts); application unknown.`,
3679
+ `Automation: ${(s.checkMs / 1e3).toFixed(2)} seconds across ${s.checks} observations; model-token cost unknown.`,
3680
+ ...s.truncatedSessions ? [`${s.truncatedSessions} session(s) have truncated observations.`] : [],
3681
+ ...result.sessionId ? result.sessions.flatMap((session) => [
3682
+ `Session ${session.id} (${session.host}) \u2014 ${session.endedAt ? "last observed stop " + session.endedAt : "no final stop observed"}`,
3683
+ `Evidence: ${session.reportPath}`,
3684
+ ...Object.entries(session.findings).map(([id, f]) => ` ${id}: ${f.type}; ${f.status}; ${f.channels.join("/")}${f.rating ? "; rated " + f.rating : ""}`)
3685
+ ]) : [s.sessions ? "Use --json for session IDs, then --session <id> for details." : "No retained sessions observed."],
3686
+ "Last observed checks only. Resolution after a notice does not prove Mason caused it."
3687
+ ].join("\n");
3688
+ }
3689
+ function retrievedDecisions(tool, response) {
3690
+ if (!/(?:^|__)mason__get_context$/.test(tool ?? "")) return void 0;
3691
+ try {
3692
+ let value = typeof response === "string" ? JSON.parse(response) : response;
3693
+ if (value?.isError) return void 0;
3694
+ if (Array.isArray(value?.content)) {
3695
+ const text2 = value.content.find((item) => item?.type === "text" && typeof item.text === "string")?.text;
3696
+ if (!text2) return void 0;
3697
+ value = JSON.parse(text2);
3698
+ }
3699
+ if (!value || typeof value.decisions !== "object" || !value.decisions || Array.isArray(value.decisions)) return void 0;
3700
+ return Object.keys(value.decisions).filter((id) => /^[a-zA-Z0-9_-]{1,200}$/.test(id)).slice(0, 200);
3701
+ } catch {
3702
+ return void 0;
3703
+ }
3704
+ }
3705
+ var directory, configPath, configSchema, usefulnessRating, findingSchema2, sessionSchema, stateSchema2, empty, statePath;
3706
+ var init_usefulness = __esm({
3707
+ "src/automation/usefulness.ts"() {
3708
+ "use strict";
3709
+ init_evidence();
3710
+ init_storage();
3711
+ init_store_lock();
3712
+ directory = ".mason/local/usefulness";
3713
+ configPath = directory + "/config.json";
3714
+ configSchema = z10.object({ version: z10.literal(1), enabled: z10.boolean() });
3715
+ usefulnessRating = z10.enum(["helpful", "already-knew", "irrelevant", "deferred"]);
3716
+ findingSchema2 = z10.object({
3717
+ type: z10.string(),
3718
+ channels: z10.array(z10.enum(["agent", "completion"])),
3719
+ status: z10.enum(["resolved", "unresolved", "review-required", "unverified", "unknown"]),
3720
+ deliveredAt: z10.string(),
3721
+ observedAt: z10.string(),
3722
+ rating: usefulnessRating.optional()
3723
+ });
3724
+ sessionSchema = z10.object({
3725
+ host: z10.string(),
3726
+ updatedAt: z10.string(),
3727
+ endedAt: z10.string().optional(),
3728
+ checks: z10.number(),
3729
+ checkMs: z10.number(),
3730
+ findings: z10.record(findingSchema2),
3731
+ decisions: z10.array(z10.string()),
3732
+ retrievalObserved: z10.boolean(),
3733
+ receiptIds: z10.array(z10.string()),
3734
+ reportPath: z10.string(),
3735
+ truncated: z10.boolean()
3736
+ });
3737
+ stateSchema2 = z10.object({ version: z10.literal(1), sessions: z10.record(sessionSchema) });
3738
+ empty = () => ({ version: 1, sessions: {} });
3739
+ statePath = (ws) => `${directory}/${hash([ws.root, ws.gitDir, ws.branch]).slice(0, 24)}.json`;
3740
+ }
3741
+ });
3742
+
3507
3743
  // src/automation/execution.ts
3508
3744
  import os2 from "os";
3509
- import fs14 from "fs/promises";
3745
+ import fs15 from "fs/promises";
3510
3746
  import { randomUUID as randomUUID3 } from "crypto";
3511
- import { z as z10 } from "zod";
3747
+ import { z as z11 } from "zod";
3512
3748
  function parseExecution(raw) {
3513
3749
  try {
3514
3750
  return executionSchema.parse(raw);
@@ -3526,15 +3762,15 @@ function automationFailure(error) {
3526
3762
  codes.add(String(cause.code));
3527
3763
  cause = cause.cause;
3528
3764
  }
3529
- 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 z10.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";
3765
+ 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 z11.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";
3530
3766
  return { code, message: message2, retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code), receiptRecorded: false };
3531
3767
  }
3532
3768
  function failureMessage(error) {
3533
3769
  const failure = automationFailure(error);
3534
3770
  return `Mason automation unavailable [${failure.code}]; evidence capture/verification was not established. ${failure.message}` + (failure.receiptRecorded ? "" : " No durable failure receipt was recorded.");
3535
3771
  }
3536
- async function failureNotifications(root, directory) {
3537
- const raw = await readStoreJson(root, directory + "/notifications.json");
3772
+ async function failureNotifications(root, directory2) {
3773
+ const raw = await readStoreJson(root, directory2 + "/notifications.json");
3538
3774
  return raw === null ? { version: 1, sessions: {} } : notificationsSchema.parse(raw);
3539
3775
  }
3540
3776
  async function hookFailureMessage(dir, host, sessionId, error) {
@@ -3564,12 +3800,12 @@ async function hookFailureMessage(dir, host, sessionId, error) {
3564
3800
  return failureMessage(error);
3565
3801
  }
3566
3802
  }
3567
- async function refreshRunning(root, directory, attempts) {
3803
+ async function refreshRunning(root, directory2, attempts) {
3568
3804
  for (const attempt of attempts) {
3569
3805
  if (attempt.status !== "running") continue;
3570
3806
  let alive = false;
3571
3807
  try {
3572
- const owner = await readStoreJson(root, attempt.lease ? leasePath(directory, attempt.id) : directory + "/lock");
3808
+ const owner = await readStoreJson(root, attempt.lease ? leasePath(directory2, attempt.id) : directory2 + "/lock");
3573
3809
  if (attempt.host === os2.hostname() && owner?.pid === attempt.pid && owner.host === attempt.host) {
3574
3810
  try {
3575
3811
  process.kill(attempt.pid, 0);
@@ -3583,12 +3819,12 @@ async function refreshRunning(root, directory, attempts) {
3583
3819
  if (!alive) attempt.status = "unknown";
3584
3820
  }
3585
3821
  }
3586
- async function editExecution(root, directory, edit) {
3587
- return withLock(root, directory + "/execution-lock", async () => {
3588
- const file = directory + "/execution.json";
3822
+ async function editExecution(root, directory2, edit) {
3823
+ return withLock(root, directory2 + "/execution-lock", async () => {
3824
+ const file = directory2 + "/execution.json";
3589
3825
  const raw = await readStoreJson(root, file);
3590
3826
  const log = raw === null ? { version: 1, attempts: [], discardedAttempts: 0 } : parseExecution(raw);
3591
- await refreshRunning(root, directory, log.attempts);
3827
+ await refreshRunning(root, directory2, log.attempts);
3592
3828
  edit(log);
3593
3829
  const finished = log.attempts.filter((a) => a.status !== "running");
3594
3830
  const discard = new Set(finished.slice(0, Math.max(0, finished.length - 32)).map((a) => a.id));
@@ -3597,7 +3833,7 @@ async function editExecution(root, directory, edit) {
3597
3833
  await writeStoreJson(root, file, log);
3598
3834
  });
3599
3835
  }
3600
- async function recordExecution(root, directory, event, run) {
3836
+ async function recordExecution(root, directory2, event, run) {
3601
3837
  const started = performance.now();
3602
3838
  const attempt = {
3603
3839
  id: randomUUID3(),
@@ -3608,15 +3844,15 @@ async function recordExecution(root, directory, event, run) {
3608
3844
  status: "running",
3609
3845
  lease: true
3610
3846
  };
3611
- const owner = leasePath(directory, attempt.id);
3612
- const finish = async (changes) => editExecution(root, directory, (log) => {
3847
+ const owner = leasePath(directory2, attempt.id);
3848
+ const finish = async (changes) => editExecution(root, directory2, (log) => {
3613
3849
  const current = log.attempts.find((a) => a.id === attempt.id);
3614
3850
  if (!current) throw new Error("Automation execution receipt disappeared; completion could not be recorded.");
3615
3851
  Object.assign(current, changes, { finishedAt: (/* @__PURE__ */ new Date()).toISOString(), durationMs: performance.now() - started });
3616
3852
  });
3617
3853
  await writeStoreJson(root, owner, { pid: attempt.pid, host: attempt.host });
3618
3854
  try {
3619
- await editExecution(root, directory, (log) => {
3855
+ await editExecution(root, directory2, (log) => {
3620
3856
  if (log.attempts.filter((a) => a.status === "running").length >= 128) throw new Error("Too many active automation executions; existing receipts were retained.");
3621
3857
  log.attempts.push(attempt);
3622
3858
  });
@@ -3634,15 +3870,15 @@ async function recordExecution(root, directory, event, run) {
3634
3870
  throw Object.assign(new Error(failure.message, { cause: error }), { failure });
3635
3871
  }
3636
3872
  } finally {
3637
- await fs14.rm(await storePath(root, owner), { force: true }).catch(() => {
3873
+ await fs15.rm(await storePath(root, owner), { force: true }).catch(() => {
3638
3874
  });
3639
3875
  }
3640
3876
  }
3641
- async function executionStatus(root, directory) {
3642
- const raw = await readStoreJson(root, directory + "/execution.json");
3877
+ async function executionStatus(root, directory2) {
3878
+ const raw = await readStoreJson(root, directory2 + "/execution.json");
3643
3879
  if (raw === null) return { status: "not-observed", attempts: [] };
3644
3880
  const log = parseExecution(raw);
3645
- await refreshRunning(root, directory, log.attempts);
3881
+ await refreshRunning(root, directory2, log.attempts);
3646
3882
  const completed = [...log.attempts].reverse().find((a) => a.status === "completed");
3647
3883
  const outstanding = [...log.attempts].reverse().find((a) => ["failed", "unknown"].includes(a.status) && (!completed || (a.finishedAt ?? a.startedAt) >= completed.startedAt));
3648
3884
  const status = log.attempts.some((a) => a.status === "running") ? "running" : outstanding?.status ?? log.attempts.at(-1)?.status ?? "not-observed";
@@ -3655,35 +3891,35 @@ var init_execution = __esm({
3655
3891
  init_store();
3656
3892
  init_evidence();
3657
3893
  init_storage();
3658
- failureSchema = z10.object({
3659
- code: z10.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
3660
- message: z10.string(),
3661
- retryable: z10.boolean(),
3662
- receiptRecorded: z10.boolean()
3894
+ failureSchema = z11.object({
3895
+ code: z11.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
3896
+ message: z11.string(),
3897
+ retryable: z11.boolean(),
3898
+ receiptRecorded: z11.boolean()
3663
3899
  });
3664
- attemptSchema = z10.object({
3665
- id: z10.string().regex(/^[a-zA-Z0-9_-]+$/),
3666
- lease: z10.literal(true).optional(),
3667
- event: z10.string(),
3668
- startedAt: z10.string(),
3669
- finishedAt: z10.string().optional(),
3670
- durationMs: z10.number().nonnegative().optional(),
3671
- pid: z10.number().int().positive(),
3672
- host: z10.string(),
3673
- status: z10.enum(["running", "completed", "failed", "unknown"]),
3674
- verificationStatus: z10.string().optional(),
3675
- reportPath: z10.string().optional(),
3900
+ attemptSchema = z11.object({
3901
+ id: z11.string().regex(/^[a-zA-Z0-9_-]+$/),
3902
+ lease: z11.literal(true).optional(),
3903
+ event: z11.string(),
3904
+ startedAt: z11.string(),
3905
+ finishedAt: z11.string().optional(),
3906
+ durationMs: z11.number().nonnegative().optional(),
3907
+ pid: z11.number().int().positive(),
3908
+ host: z11.string(),
3909
+ status: z11.enum(["running", "completed", "failed", "unknown"]),
3910
+ verificationStatus: z11.string().optional(),
3911
+ reportPath: z11.string().optional(),
3676
3912
  failure: failureSchema.optional()
3677
3913
  });
3678
- executionSchema = z10.object({ version: z10.literal(1), attempts: z10.array(attemptSchema).max(160), discardedAttempts: z10.number().int().nonnegative().default(0) });
3679
- notificationsSchema = z10.object({ version: z10.literal(1), sessions: z10.record(z10.object({
3680
- signature: z10.string(),
3914
+ executionSchema = z11.object({ version: z11.literal(1), attempts: z11.array(attemptSchema).max(160), discardedAttempts: z11.number().int().nonnegative().default(0) });
3915
+ notificationsSchema = z11.object({ version: z11.literal(1), sessions: z11.record(z11.object({
3916
+ signature: z11.string(),
3681
3917
  code: failureSchema.shape.code,
3682
- message: z10.string(),
3683
- count: z10.number().int().positive(),
3684
- lastAt: z10.string()
3918
+ message: z11.string(),
3919
+ count: z11.number().int().positive(),
3920
+ lastAt: z11.string()
3685
3921
  })) });
3686
- leasePath = (directory, id) => `${directory}/executions/${id}.json`;
3922
+ leasePath = (directory2, id) => `${directory2}/executions/${id}.json`;
3687
3923
  }
3688
3924
  });
3689
3925
 
@@ -3751,12 +3987,12 @@ var init_pending = __esm({
3751
3987
 
3752
3988
  // src/automation/runtime.ts
3753
3989
  import { randomUUID as randomUUID4 } from "crypto";
3754
- import { z as z11 } from "zod";
3990
+ import { z as z12 } from "zod";
3755
3991
  async function observeReadOnlyTool(dir, event) {
3756
3992
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
3757
3993
  await withLock(ws.root, ws.directory, async () => {
3758
- const statePath = ws.directory + "/state.json";
3759
- const raw = await readStoreJson(ws.root, statePath);
3994
+ const statePath2 = ws.directory + "/state.json";
3995
+ const raw = await readStoreJson(ws.root, statePath2);
3760
3996
  if (raw === null) return;
3761
3997
  const state = parseState(raw);
3762
3998
  if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) {
@@ -3767,7 +4003,7 @@ async function observeReadOnlyTool(dir, event) {
3767
4003
  const now = (/* @__PURE__ */ new Date()).toISOString();
3768
4004
  session.lastUsed = now;
3769
4005
  session.events[event.event] = { at: now, count: (session.events[event.event]?.count ?? 0) + 1 };
3770
- await writeStoreJson(ws.root, statePath, state);
4006
+ await writeStoreJson(ws.root, statePath2, state);
3771
4007
  });
3772
4008
  return { root: ws.root, directory: ws.directory };
3773
4009
  }
@@ -3782,7 +4018,7 @@ function summarize(report) {
3782
4018
  }),
3783
4019
  ...open.length > 4 ? [`${open.length - 4} more findings in the report.`] : [],
3784
4020
  ...report.diagnostics.slice(0, 2).map(cleanText),
3785
- `Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason-auto check.`,
4021
+ `Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason check.`,
3786
4022
  "Keep original evidence. Address findings relevant to the authorized task; report unrelated findings with a brief scope reason and suggested follow-up. Use review_advisory for authorized assessments; otherwise leave advisories open without approving them."
3787
4023
  ].join("\n");
3788
4024
  }
@@ -3841,7 +4077,7 @@ async function verifyInputs(ws, inputs) {
3841
4077
  async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
3842
4078
  const baselines = [...state.baselines];
3843
4079
  const now = (/* @__PURE__ */ new Date()).toISOString();
3844
- const empty = {
4080
+ const empty2 = {
3845
4081
  version: 1,
3846
4082
  status: "unavailable",
3847
4083
  root: ws.root,
@@ -3858,7 +4094,7 @@ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDia
3858
4094
  };
3859
4095
  if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) {
3860
4096
  await verifyInputs(ws, inputs);
3861
- return { baselines, report: empty, cache: null };
4097
+ return { baselines, report: empty2, cache: null };
3862
4098
  }
3863
4099
  if (ws.branch === "detached" && state.latest) {
3864
4100
  const previous = await readStoreJson(ws.root, state.latest);
@@ -3960,7 +4196,7 @@ async function analyze(ws, inputs, event, startedAt) {
3960
4196
  async function automate(dir, event) {
3961
4197
  const startedAt = Date.now();
3962
4198
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
3963
- return recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
4199
+ const output = await recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
3964
4200
  const inputs = await readInputs(ws.root);
3965
4201
  const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
3966
4202
  if (result.shared) {
@@ -3977,7 +4213,7 @@ async function automate(dir, event) {
3977
4213
  let baseline = { findings: report.findings, head: report.head };
3978
4214
  if (state.latest) {
3979
4215
  try {
3980
- const previous = z11.object({ findings: z11.array(repairFindingSchema), head: z11.string() }).safeParse(await readStoreJson(ws.root, state.latest));
4216
+ const previous = z12.object({ findings: z12.array(repairFindingSchema), head: z12.string() }).safeParse(await readStoreJson(ws.root, state.latest));
3981
4217
  if (!previous.success) throw new Error("Unsupported saved report");
3982
4218
  baseline = previous.data;
3983
4219
  } catch {
@@ -4045,9 +4281,15 @@ async function automate(dir, event) {
4045
4281
  state.latest = report.reportPath;
4046
4282
  if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
4047
4283
  await writeStoreJson(ws.root, ws.directory + "/state.json", state);
4048
- return { report, message: message2 };
4284
+ return { report, message: message2, observedAt: now, delivery: { agent: feedback.slice(0, 4), completion: selected.slice(0, 4) } };
4049
4285
  }));
4050
4286
  }));
4287
+ try {
4288
+ await observeUsefulness(ws.root, event, output.report, output.delivery.agent, output.delivery.completion, Date.now() - startedAt, output.observedAt);
4289
+ } catch {
4290
+ if (event.event === "task_end") output.message = [output.message, "Mason: local usefulness tracking unavailable; inspect mason stats --json."].filter(Boolean).join("\n");
4291
+ }
4292
+ return output;
4051
4293
  }
4052
4294
  async function automationStatus(dir) {
4053
4295
  const { pendingKnowledge: pendingKnowledge2 } = await Promise.resolve().then(() => (init_pending(), pending_exports));
@@ -4088,6 +4330,7 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
4088
4330
  var init_runtime = __esm({
4089
4331
  "src/automation/runtime.ts"() {
4090
4332
  "use strict";
4333
+ init_usefulness();
4091
4334
  init_notifications();
4092
4335
  init_profile();
4093
4336
  init_inspection();
@@ -4100,34 +4343,34 @@ var init_runtime = __esm({
4100
4343
  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.";
4101
4344
  priority = { resolved: 0, "review-required": 1, unresolved: 2, unverified: 3 };
4102
4345
  cleanText = (text2) => text2.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
4103
- analysisSchema = z11.object({
4104
- id: z11.string(),
4105
- fingerprint: z11.string(),
4106
- completedAt: z11.number(),
4107
- baselineDigest: z11.string(),
4346
+ analysisSchema = z12.object({
4347
+ id: z12.string(),
4348
+ fingerprint: z12.string(),
4349
+ completedAt: z12.number(),
4350
+ baselineDigest: z12.string(),
4108
4351
  currentAudit: reportSchema.nullable().optional(),
4109
- report: z11.object({
4110
- version: z11.literal(1),
4111
- status: z11.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
4112
- root: z11.string(),
4113
- branch: z11.string(),
4114
- head: z11.string(),
4115
- baselinePaths: z11.array(z11.string()),
4116
- reportPath: z11.string(),
4117
- findings: z11.array(repairFindingSchema),
4118
- diagnostics: z11.array(z11.string()),
4119
- checks: z11.object({
4120
- ran: z11.array(z11.string()),
4121
- reused: z11.array(z11.string()),
4122
- skipped: z11.array(z11.object({ check: z11.string(), reason: z11.string(), doc: z11.string().optional() })),
4123
- auditReused: z11.boolean().optional()
4352
+ report: z12.object({
4353
+ version: z12.literal(1),
4354
+ status: z12.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
4355
+ root: z12.string(),
4356
+ branch: z12.string(),
4357
+ head: z12.string(),
4358
+ baselinePaths: z12.array(z12.string()),
4359
+ reportPath: z12.string(),
4360
+ findings: z12.array(repairFindingSchema),
4361
+ diagnostics: z12.array(z12.string()),
4362
+ checks: z12.object({
4363
+ ran: z12.array(z12.string()),
4364
+ reused: z12.array(z12.string()),
4365
+ skipped: z12.array(z12.object({ check: z12.string(), reason: z12.string(), doc: z12.string().optional() })),
4366
+ auditReused: z12.boolean().optional()
4124
4367
  }),
4125
- counts: z11.object({ resolved: z11.number(), unresolved: z11.number(), "review-required": z11.number(), unverified: z11.number() }),
4126
- capture: z11.enum(["observed", "unknown"]),
4127
- scope: z11.string()
4368
+ counts: z12.object({ resolved: z12.number(), unresolved: z12.number(), "review-required": z12.number(), unverified: z12.number() }),
4369
+ capture: z12.enum(["observed", "unknown"]),
4370
+ scope: z12.string()
4128
4371
  })
4129
4372
  });
4130
- sharedSchema = z11.object({ analysis: analysisSchema, digest: z11.string() });
4373
+ sharedSchema = z12.object({ analysis: analysisSchema, digest: z12.string() });
4131
4374
  baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
4132
4375
  state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
4133
4376
  )));
@@ -4135,12 +4378,12 @@ var init_runtime = __esm({
4135
4378
  });
4136
4379
 
4137
4380
  // src/automation/hook-root.ts
4138
- import fs15 from "fs/promises";
4381
+ import fs16 from "fs/promises";
4139
4382
  import path23 from "path";
4140
4383
  async function repositoryRoot(dir) {
4141
4384
  if (!path23.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
4142
4385
  try {
4143
- return await fs15.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
4386
+ return await fs16.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
4144
4387
  } catch (error) {
4145
4388
  if (error.code === "ENOENT" || /not a git repository/.test(String(error.stderr))) return null;
4146
4389
  throw error;
@@ -4168,8 +4411,8 @@ var init_hook_root = __esm({
4168
4411
  });
4169
4412
 
4170
4413
  // src/setup/model.ts
4171
- import { z as z12 } from "zod";
4172
- import fs16 from "fs/promises";
4414
+ import { z as z13 } from "zod";
4415
+ import fs17 from "fs/promises";
4173
4416
  import path24 from "path";
4174
4417
  async function loadSetup(root) {
4175
4418
  const raw = await readStoreJson(root, SETUP_PATH);
@@ -4179,20 +4422,20 @@ async function loadSetup(root) {
4179
4422
  async function effectiveSetup(root) {
4180
4423
  const local = await loadSetup(root);
4181
4424
  if (local) return { root, setup: local };
4182
- const common = await fs16.realpath(path24.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
4183
- const own = await fs16.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
4425
+ const common = await fs17.realpath(path24.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
4426
+ const own = await fs17.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
4184
4427
  if (common === own) return { root, setup: null };
4185
4428
  const primary = (await git3(root, "worktree", "list", "--porcelain", "-z")).split("\0\0")[0].split("\0");
4186
4429
  const candidate = primary.find((field) => field.startsWith("worktree "))?.slice(9);
4187
4430
  if (!candidate || primary.includes("bare")) return { root, setup: null };
4188
4431
  let inheritedRoot;
4189
4432
  try {
4190
- inheritedRoot = await fs16.realpath((await git3(candidate, "rev-parse", "--show-toplevel")).trim());
4433
+ inheritedRoot = await fs17.realpath((await git3(candidate, "rev-parse", "--show-toplevel")).trim());
4191
4434
  } catch (error) {
4192
4435
  if (/must be run in a work tree/.test(String(error.stderr))) return { root, setup: null };
4193
4436
  throw error;
4194
4437
  }
4195
- const inheritedCommon = await fs16.realpath(path24.resolve(
4438
+ const inheritedCommon = await fs17.realpath(path24.resolve(
4196
4439
  inheritedRoot,
4197
4440
  (await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim()
4198
4441
  ));
@@ -4202,8 +4445,8 @@ async function effectiveSetup(root) {
4202
4445
  if (inactive && setup) for (const host of inactiveHostsSchema.parse(inactive).hosts) delete setup.hosts[host];
4203
4446
  return { root: inheritedRoot, setup };
4204
4447
  }
4205
- async function loadSetupReceipt(root, directory, host) {
4206
- const raw = await readStoreJson(root, directory + "/setup-" + host + ".json");
4448
+ async function loadSetupReceipt(root, directory2, host) {
4449
+ const raw = await readStoreJson(root, directory2 + "/setup-" + host + ".json");
4207
4450
  if (raw === null) return null;
4208
4451
  const receipt = receiptSchema.parse(raw);
4209
4452
  if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
@@ -4215,29 +4458,29 @@ var init_model = __esm({
4215
4458
  "use strict";
4216
4459
  init_evidence();
4217
4460
  init_storage();
4218
- setupHostSchema = z12.object({
4219
- configuredVersion: z12.string(),
4220
- revision: z12.string().uuid(),
4221
- fingerprint: z12.string(),
4222
- mcpFingerprint: z12.string(),
4223
- instructions: z12.array(z12.string())
4461
+ setupHostSchema = z13.object({
4462
+ configuredVersion: z13.string(),
4463
+ revision: z13.string().uuid(),
4464
+ fingerprint: z13.string(),
4465
+ mcpFingerprint: z13.string(),
4466
+ instructions: z13.array(z13.string())
4224
4467
  });
4225
- setupSchema = z12.object({ version: z12.literal(2), hosts: z12.object({
4468
+ setupSchema = z13.object({ version: z13.literal(2), hosts: z13.object({
4226
4469
  codex: setupHostSchema.optional(),
4227
4470
  claude: setupHostSchema.optional()
4228
4471
  }) });
4229
4472
  SETUP_PATH = ".mason/local/setup.json";
4230
4473
  INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
4231
- inactiveHostsSchema = z12.object({ version: z12.literal(1), hosts: z12.array(z12.enum(["claude", "codex"])) });
4232
- receiptSchema = z12.object({
4233
- version: z12.literal(1),
4234
- host: z12.enum(["codex", "claude"]),
4235
- status: z12.enum(["installing", "configured"]),
4236
- initialReportPath: z12.string(),
4237
- initialBaselinePaths: z12.array(z12.string()),
4238
- root: z12.string(),
4239
- revision: z12.string().uuid(),
4240
- configuredAt: z12.string().optional()
4474
+ inactiveHostsSchema = z13.object({ version: z13.literal(1), hosts: z13.array(z13.enum(["claude", "codex"])) });
4475
+ receiptSchema = z13.object({
4476
+ version: z13.literal(1),
4477
+ host: z13.enum(["codex", "claude"]),
4478
+ status: z13.enum(["installing", "configured"]),
4479
+ initialReportPath: z13.string(),
4480
+ initialBaselinePaths: z13.array(z13.string()),
4481
+ root: z13.string(),
4482
+ revision: z13.string().uuid(),
4483
+ configuredAt: z13.string().optional()
4241
4484
  });
4242
4485
  }
4243
4486
  });
@@ -4255,7 +4498,7 @@ __export(launcher_exports, {
4255
4498
  });
4256
4499
  import { execFile as execFile2 } from "child_process";
4257
4500
  import { promisify as promisify2 } from "util";
4258
- import fs17 from "fs/promises";
4501
+ import fs18 from "fs/promises";
4259
4502
  function globalCommand(args2, platform = process.platform) {
4260
4503
  return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args2] } : { command: "mason", args: args2 };
4261
4504
  }
@@ -4275,7 +4518,7 @@ async function installedCommand() {
4275
4518
  }
4276
4519
  }
4277
4520
  async function prepareLaunch(host, options = {}) {
4278
- const root = await fs17.realpath((await git3(options.dir ?? process.cwd(), "rev-parse", "--show-toplevel")).trim());
4521
+ const root = await fs18.realpath((await git3(options.dir ?? process.cwd(), "rev-parse", "--show-toplevel")).trim());
4279
4522
  const entry = (await effectiveSetup(root)).setup?.hosts[host];
4280
4523
  process.env.MASON_SETUP_ROOT = root;
4281
4524
  process.env.MASON_SETUP_HOST = host;
@@ -4298,7 +4541,7 @@ var init_launcher = __esm({
4298
4541
  "use strict";
4299
4542
  init_evidence();
4300
4543
  init_model();
4301
- executingVersion = () => true ? "0.19.2" : "development";
4544
+ executingVersion = () => true ? "0.20.1" : "development";
4302
4545
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
4303
4546
  hookCommand = (host) => `mason --setup-host ${host} auto`;
4304
4547
  }
@@ -4311,13 +4554,13 @@ __export(observations_exports, {
4311
4554
  observeActivation: () => observeActivation,
4312
4555
  readObservation: () => readObservation
4313
4556
  });
4314
- import fs18 from "fs/promises";
4315
- import { z as z13 } from "zod";
4316
- function observationPath(directory, host, revision, version = executingVersion()) {
4317
- return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
4557
+ import fs19 from "fs/promises";
4558
+ import { z as z14 } from "zod";
4559
+ function observationPath(directory2, host, revision, version = executingVersion()) {
4560
+ return `${directory2}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
4318
4561
  }
4319
- async function readObservation(root, directory, host, revision, version = executingVersion()) {
4320
- const raw = await readStoreJson(root, observationPath(directory, host, revision, version));
4562
+ async function readObservation(root, directory2, host, revision, version = executingVersion()) {
4563
+ const raw = await readStoreJson(root, observationPath(directory2, host, revision, version));
4321
4564
  if (raw === null) return null;
4322
4565
  const record = observationSchema.parse(raw);
4323
4566
  if (record.root !== root || record.host !== host || record.revision !== revision || record.masonVersion !== version) throw new Error("Activation receipt belongs to another installation.");
@@ -4334,12 +4577,12 @@ async function observeActivation(dir, event, options = {}) {
4334
4577
  return inactiveSetupMessage();
4335
4578
  }
4336
4579
  const capturedDirectory = options.directory?.match(/^\.mason\/reports\/automation\/[a-f0-9]{24}$/)?.[0] ?? options.reportPath?.match(/^(\.mason\/reports\/automation\/[a-f0-9]{24})\/checks\//)?.[1];
4337
- const ws = capturedDirectory ? { root: await fs18.realpath(dir), directory: capturedDirectory } : await workspace(dir);
4338
- if (ws.root !== await fs18.realpath(process.env.MASON_SETUP_ROOT)) return null;
4580
+ const ws = capturedDirectory ? { root: await fs19.realpath(dir), directory: capturedDirectory } : await workspace(dir);
4581
+ if (ws.root !== await fs19.realpath(process.env.MASON_SETUP_ROOT)) return null;
4339
4582
  const { setup } = await effectiveSetup(ws.root);
4340
4583
  if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
4341
- const directory = ws.directory + "/activation";
4342
- await withLock(ws.root, directory, async () => {
4584
+ const directory2 = ws.directory + "/activation";
4585
+ await withLock(ws.root, directory2, async () => {
4343
4586
  const now = (/* @__PURE__ */ new Date()).toISOString();
4344
4587
  const record = await readObservation(ws.root, ws.directory, host, revision) ?? {
4345
4588
  version: 1,
@@ -4383,26 +4626,26 @@ var init_observations = __esm({
4383
4626
  init_storage();
4384
4627
  init_model();
4385
4628
  init_launcher();
4386
- observationSchema = z13.object({
4387
- version: z13.literal(1),
4388
- masonVersion: z13.string(),
4389
- root: z13.string(),
4390
- revision: z13.string(),
4391
- host: z13.enum(["codex", "claude"]),
4392
- contextCalls: z13.number().int().nonnegative(),
4393
- lastContextAt: z13.string().optional(),
4394
- sessions: z13.record(z13.object({
4395
- events: z13.array(z13.enum(events)),
4396
- at: z13.string(),
4397
- verificationStatus: z13.string().optional(),
4398
- reportPath: z13.string().optional()
4629
+ observationSchema = z14.object({
4630
+ version: z14.literal(1),
4631
+ masonVersion: z14.string(),
4632
+ root: z14.string(),
4633
+ revision: z14.string(),
4634
+ host: z14.enum(["codex", "claude"]),
4635
+ contextCalls: z14.number().int().nonnegative(),
4636
+ lastContextAt: z14.string().optional(),
4637
+ sessions: z14.record(z14.object({
4638
+ events: z14.array(z14.enum(events)),
4639
+ at: z14.string(),
4640
+ verificationStatus: z14.string().optional(),
4641
+ reportPath: z14.string().optional()
4399
4642
  }))
4400
4643
  });
4401
4644
  }
4402
4645
  });
4403
4646
 
4404
4647
  // src/automation/adapters.ts
4405
- import { z as z14 } from "zod";
4648
+ import { z as z15 } from "zod";
4406
4649
  function normalizeHook(host, raw) {
4407
4650
  hostSchema.parse(host);
4408
4651
  const input = inputSchema.parse(raw);
@@ -4412,7 +4655,8 @@ function normalizeHook(host, raw) {
4412
4655
  host,
4413
4656
  sessionId: input.session_id,
4414
4657
  toolId: input.tool_use_id,
4415
- mutating: !!input.tool_name && !readOnly
4658
+ mutating: !!input.tool_name && !readOnly,
4659
+ ...input.hook_event_name === "PostToolUse" ? { retrievedDecisionIds: retrievedDecisions(input.tool_name, input.tool_response) } : {}
4416
4660
  } };
4417
4661
  }
4418
4662
  async function runAutomationHook(host, stdin, options = {}) {
@@ -4469,7 +4713,7 @@ function knownManagedHookCommands(host) {
4469
4713
  function compatibleManagedHookCommands(host, platform = process.platform) {
4470
4714
  return [managedHookCommand(host, platform), `mason --setup-host ${host} auto hook --host ${host}`];
4471
4715
  }
4472
- function hookConfig(host, command2 = "npx --no-install --package mason-context mason-auto") {
4716
+ function hookConfig(host, command2 = "npx --no-install --package mason-context mason auto") {
4473
4717
  const handler = { type: "command", command: command2 === `mason --setup-host ${host} auto` ? managedHookCommand(host) : command2 + " hook --host " + host, timeout: 30 };
4474
4718
  return { hooks: Object.fromEntries(HOOK_EVENTS.map((name) => [
4475
4719
  name,
@@ -4480,19 +4724,21 @@ var inputSchema, lifecycle, HOOK_EVENTS;
4480
4724
  var init_adapters = __esm({
4481
4725
  "src/automation/adapters.ts"() {
4482
4726
  "use strict";
4727
+ init_usefulness();
4483
4728
  init_execution();
4484
4729
  init_runtime();
4485
4730
  init_store();
4486
4731
  init_hook_root();
4487
- inputSchema = z14.object({
4488
- cwd: z14.string().min(1),
4489
- session_id: z14.string().min(1).max(500),
4490
- hook_event_name: z14.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
4491
- tool_name: z14.string().optional(),
4492
- tool_use_id: z14.string().max(500).optional(),
4493
- tool_input: z14.unknown().optional(),
4494
- stop_hook_active: z14.boolean().optional(),
4495
- permission_mode: z14.string().optional()
4732
+ inputSchema = z15.object({
4733
+ cwd: z15.string().min(1),
4734
+ session_id: z15.string().min(1).max(500),
4735
+ hook_event_name: z15.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
4736
+ tool_name: z15.string().optional(),
4737
+ tool_use_id: z15.string().max(500).optional(),
4738
+ tool_response: z15.unknown().optional(),
4739
+ tool_input: z15.unknown().optional(),
4740
+ stop_hook_active: z15.boolean().optional(),
4741
+ permission_mode: z15.string().optional()
4496
4742
  });
4497
4743
  lifecycle = {
4498
4744
  SessionStart: "session_start",
@@ -4512,7 +4758,7 @@ __export(files_exports, {
4512
4758
  managedBlock: () => managedBlock,
4513
4759
  readText: () => readText
4514
4760
  });
4515
- import fs19 from "fs/promises";
4761
+ import fs20 from "fs/promises";
4516
4762
  import path25 from "path";
4517
4763
  import { randomUUID as randomUUID5 } from "crypto";
4518
4764
  async function readText(root, file) {
@@ -4542,14 +4788,14 @@ async function applyEdit(root, edit) {
4542
4788
  if (edit.after === null) {
4543
4789
  const file2 = await storePath(root, edit.path);
4544
4790
  if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
4545
- await fs19.unlink(file2);
4791
+ await fs20.unlink(file2);
4546
4792
  return true;
4547
4793
  }
4548
4794
  const file = await storePath(root, edit.path, true);
4549
4795
  const temporary = path25.join(path25.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
4550
4796
  try {
4551
- const mode = await fs19.stat(file).then((s) => s.mode & 511, () => 384);
4552
- const handle = await fs19.open(temporary, "wx", mode);
4797
+ const mode = await fs20.stat(file).then((s) => s.mode & 511, () => 384);
4798
+ const handle = await fs20.open(temporary, "wx", mode);
4553
4799
  try {
4554
4800
  await handle.writeFile(edit.after, "utf8");
4555
4801
  await handle.sync();
@@ -4557,10 +4803,10 @@ async function applyEdit(root, edit) {
4557
4803
  await handle.close();
4558
4804
  }
4559
4805
  if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
4560
- await fs19.rename(temporary, file);
4806
+ await fs20.rename(temporary, file);
4561
4807
  return true;
4562
4808
  } finally {
4563
- await fs19.rm(temporary, { force: true });
4809
+ await fs20.rm(temporary, { force: true });
4564
4810
  }
4565
4811
  }
4566
4812
  var init_files2 = __esm({
@@ -4581,7 +4827,7 @@ __export(ownership_exports, {
4581
4827
  ownershipEdit: () => ownershipEdit,
4582
4828
  ownershipSchema: () => ownershipSchema
4583
4829
  });
4584
- import { z as z15 } from "zod";
4830
+ import { z as z16 } from "zod";
4585
4831
  import { parse } from "smol-toml";
4586
4832
  function blockSpan(text2, start, end) {
4587
4833
  const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
@@ -4632,30 +4878,30 @@ var init_ownership = __esm({
4632
4878
  ["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
4633
4879
  ["# mason:mcp:start", "# mason:mcp:end"]
4634
4880
  ];
4635
- fileSchema = z15.object({
4636
- created: z15.boolean(),
4637
- initialHash: z15.string(),
4638
- blocks: z15.record(z15.string()),
4639
- mcpHash: z15.string().optional(),
4640
- hookCommands: z15.array(z15.string()).optional(),
4641
- hooks: z15.record(z15.array(z15.string())).optional()
4881
+ fileSchema = z16.object({
4882
+ created: z16.boolean(),
4883
+ initialHash: z16.string(),
4884
+ blocks: z16.record(z16.string()),
4885
+ mcpHash: z16.string().optional(),
4886
+ hookCommands: z16.array(z16.string()).optional(),
4887
+ hooks: z16.record(z16.array(z16.string())).optional()
4642
4888
  });
4643
- ownershipSchema = z15.object({ version: z15.literal(1), files: z15.record(fileSchema) });
4889
+ ownershipSchema = z16.object({ version: z16.literal(1), files: z16.record(fileSchema) });
4644
4890
  }
4645
4891
  });
4646
4892
 
4647
4893
  // src/mcp/init.ts
4648
- import { z as z16 } from "zod";
4894
+ import { z as z17 } from "zod";
4649
4895
  var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_PLAYBOOK, MAP_PLAYBOOK;
4650
4896
  var init_init = __esm({
4651
4897
  "src/mcp/init.ts"() {
4652
4898
  "use strict";
4653
4899
  init_storage();
4654
4900
  init_files2();
4655
- markerSchema = z16.object({
4656
- version: z16.literal(1),
4657
- initializedAt: z16.string(),
4658
- features: z16.object({ confluence: z16.boolean().optional() }).passthrough().optional()
4901
+ markerSchema = z17.object({
4902
+ version: z17.literal(1),
4903
+ initializedAt: z17.string(),
4904
+ features: z17.object({ confluence: z17.boolean().optional() }).passthrough().optional()
4659
4905
  }).passthrough();
4660
4906
  CLAUDE_MD_SECTION = `<!-- mason:start -->
4661
4907
  ## Mason project knowledge
@@ -4677,13 +4923,13 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
4677
4923
 
4678
4924
  - 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.
4679
4925
 
4680
- - 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.
4926
+ - When Mason automation is installed, use \`mason_automation(action: "status")\` to inspect configured hooks and observed events, and \`mason_automation(action: "check")\` to resume its retained repair evidence. CLI fallback: \`mason status\` / \`mason check\`. Preserve existing baselines across sessions. Automatic checks do not authorize unrelated repairs or approve advisories.
4681
4927
 
4682
4928
  Inspect source for what the retrieved context does not answer.
4683
4929
  <!-- mason:end -->`;
4684
4930
  ASSISTANT_SETUP = `UNIFIED SETUP
4685
- When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. This uses the same engine as mason-auto setup: retain the initial audit before instruction edits, use the installed mason command on PATH, merge project MCP configuration and hooks, update marker-delimited instructions and Git ignore rules, and record setup. A concept map is optional.
4686
- If the user only requested inspection or review, report findings without running setup. The setup result distinguishes configured from observed activation. Explain the host's native trust step and request a new session; never trust hooks on the user's behalf or claim activation from generated configuration alone. After a normal task finishes, mason_automation(action: "status") or mason-auto status reports observed context use and hook events. Existing findings remain reviewable; setup does not approve advisories or manufacture decisions.
4931
+ When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. This uses the same engine as mason setup: retain the initial audit before instruction edits, use the installed mason command on PATH, merge project MCP configuration and hooks, update marker-delimited instructions and Git ignore rules, and record setup. A concept map is optional.
4932
+ If the user only requested inspection or review, report findings without running setup. The setup result distinguishes configured from observed activation. Explain the host's native trust step and request a new session; never trust hooks on the user's behalf or claim activation from generated configuration alone. After a normal task finishes, mason_automation(action: "status") or mason status reports observed context use and hook events. Existing findings remain reviewable; setup does not approve advisories or manufacture decisions.
4687
4933
 
4688
4934
  The managed project guidance is:
4689
4935
  ${CLAUDE_MD_SECTION}`;
@@ -4744,7 +4990,7 @@ __export(config_exports, {
4744
4990
  });
4745
4991
  import { isDeepStrictEqual } from "util";
4746
4992
  import { parse as parse2, stringify } from "smol-toml";
4747
- import { z as z17 } from "zod";
4993
+ import { z as z18 } from "zod";
4748
4994
  async function instructionEdits(root, host) {
4749
4995
  const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
4750
4996
  const existing = found.find((f) => f.text !== null);
@@ -4803,7 +5049,7 @@ async function mcpEdit(root, host) {
4803
5049
  return { path: file, before, after };
4804
5050
  }
4805
5051
  async function hookEdits(root, host) {
4806
- const file = configPath(host), before = await readText(root, file);
5052
+ const file = configPath2(host), before = await readText(root, file);
4807
5053
  const { planAutomationInstall: planAutomationInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
4808
5054
  const plan = await planAutomationInstall2(root, host, hookCommand(host));
4809
5055
  return [
@@ -4831,7 +5077,7 @@ async function inspectHostConfig(root, host, plannedMcp) {
4831
5077
  const text2 = plannedMcp ?? await readText(root, mcpPath(host));
4832
5078
  const config = host === "codex" ? parse2(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
4833
5079
  const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
4834
- const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
5080
+ const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath2(host)) ?? "{}"));
4835
5081
  const expected = hookConfig(host, hookCommand(host));
4836
5082
  const compatible = compatibleManagedHookCommands(host);
4837
5083
  const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
@@ -4855,7 +5101,7 @@ var init_config = __esm({
4855
5101
  init_install();
4856
5102
  init_files2();
4857
5103
  init_launcher();
4858
- object = z17.record(z17.unknown());
5104
+ object = z18.record(z18.unknown());
4859
5105
  mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
4860
5106
  TOML_START = "# mason:mcp:start";
4861
5107
  TOML_END = "# mason:mcp:end";
@@ -4869,13 +5115,13 @@ var install_exports = {};
4869
5115
  __export(install_exports, {
4870
5116
  AUTOMATION_PATH: () => AUTOMATION_PATH,
4871
5117
  automationConfigSchema: () => automationConfigSchema,
4872
- configPath: () => configPath,
5118
+ configPath: () => configPath2,
4873
5119
  installAutomation: () => installAutomation,
4874
5120
  installedAutomation: () => installedAutomation,
4875
5121
  planAutomationInstall: () => planAutomationInstall,
4876
5122
  recordSchema: () => recordSchema2
4877
5123
  });
4878
- import { z as z18 } from "zod";
5124
+ import { z as z19 } from "zod";
4879
5125
  async function installationRecord(root) {
4880
5126
  return recordSchema2.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
4881
5127
  }
@@ -4884,7 +5130,7 @@ async function installAutomation(dir, host, command2) {
4884
5130
  return withLock(ws.root, ".mason/reports/setup-lock", () => installLocked(ws.root, host, command2));
4885
5131
  }
4886
5132
  async function planAutomationInstall(root, host, command2) {
4887
- const file = configPath(host);
5133
+ const file = configPath2(host);
4888
5134
  const before = await readText(root, file);
4889
5135
  const existing = automationConfigSchema.parse(JSON.parse(before ?? "{}"));
4890
5136
  const record = await installationRecord(root);
@@ -4924,7 +5170,7 @@ async function installLocked(root, host, command2) {
4924
5170
  status: "configured",
4925
5171
  command: newCommand,
4926
5172
  events: HOOK_EVENTS,
4927
- next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason-auto status reports observed events separately from configuration." : "Start a new Claude Code session. mason-auto status reports observed events separately from configuration.",
5173
+ next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason status reports observed events separately from configuration." : "Start a new Claude Code session. mason status reports observed events separately from configuration.",
4928
5174
  note: "Install mason-context in the project before using the default command. Ignore .mason/reports/ to keep local evidence out of commits. Hooks preserve evidence and suggest scoped repairs; they do not approve edits or decisions."
4929
5175
  };
4930
5176
  }
@@ -4935,10 +5181,10 @@ async function installedAutomation(dir) {
4935
5181
  for (const host of ["claude", "codex"]) {
4936
5182
  const expected = record.hosts[host];
4937
5183
  if (!expected) continue;
4938
- const current = automationConfigSchema.parse(await readStoreJson(ws.root, configPath(host)) ?? {});
5184
+ const current = automationConfigSchema.parse(await readStoreJson(ws.root, configPath2(host)) ?? {});
4939
5185
  const configuredEvents = HOOK_EVENTS.filter((event) => current.hooks?.[event]?.some((group) => group.hooks.some((handler) => handler.type === "command" && handler.command === expected.command)));
4940
5186
  result[host] = {
4941
- configPath: configPath(host),
5187
+ configPath: configPath2(host),
4942
5188
  configuredEvents,
4943
5189
  status: current.disableAllHooks === true ? "disabled" : configuredEvents.length === HOOK_EVENTS.length ? "configured" : "incomplete",
4944
5190
  runtime: "Host version, trust, policy, and tool coverage still determine execution; inspect observed events."
@@ -4946,7 +5192,7 @@ async function installedAutomation(dir) {
4946
5192
  }
4947
5193
  return result;
4948
5194
  }
4949
- var groupSchema, automationConfigSchema, recordSchema2, AUTOMATION_PATH, configPath;
5195
+ var groupSchema, automationConfigSchema, recordSchema2, AUTOMATION_PATH, configPath2;
4950
5196
  var init_install = __esm({
4951
5197
  "src/automation/install.ts"() {
4952
5198
  "use strict";
@@ -4956,11 +5202,11 @@ var init_install = __esm({
4956
5202
  init_store();
4957
5203
  init_files2();
4958
5204
  init_ownership();
4959
- groupSchema = z18.object({ hooks: z18.array(z18.object({ type: z18.string(), command: z18.string().optional() }).passthrough()) }).passthrough();
4960
- automationConfigSchema = z18.object({ hooks: z18.record(z18.array(groupSchema)).optional() }).passthrough();
4961
- recordSchema2 = z18.object({ version: z18.literal(1), hosts: z18.record(z18.object({ command: z18.string() })) });
5205
+ groupSchema = z19.object({ hooks: z19.array(z19.object({ type: z19.string(), command: z19.string().optional() }).passthrough()) }).passthrough();
5206
+ automationConfigSchema = z19.object({ hooks: z19.record(z19.array(groupSchema)).optional() }).passthrough();
5207
+ recordSchema2 = z19.object({ version: z19.literal(1), hosts: z19.record(z19.object({ command: z19.string() })) });
4962
5208
  AUTOMATION_PATH = ".mason/local/automation.json";
4963
- configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
5209
+ configPath2 = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
4964
5210
  }
4965
5211
  });
4966
5212
 
@@ -4971,11 +5217,11 @@ __export(teardown_exports, {
4971
5217
  teardownProject: () => teardownProject
4972
5218
  });
4973
5219
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
4974
- import fs20 from "fs/promises";
5220
+ import fs21 from "fs/promises";
4975
5221
  import path26 from "path";
4976
5222
  import { parse as parse3, stringify as stringify2 } from "smol-toml";
4977
5223
  import fg8 from "fast-glob";
4978
- import { z as z19 } from "zod";
5224
+ import { z as z20 } from "zod";
4979
5225
  async function planTeardown(root, selected) {
4980
5226
  const edits = [], diagnostics = [];
4981
5227
  const inputs = /* @__PURE__ */ new Map();
@@ -4994,7 +5240,7 @@ async function planTeardown(root, selected) {
4994
5240
  const ownership = ownershipBefore === null ? { version: 1, files: {} } : ownershipSchema.parse(JSON.parse(ownershipBefore));
4995
5241
  const setupBefore = await read(SETUP_PATH);
4996
5242
  const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
4997
- const linked = (await workspace(root)).gitDir !== await fs20.realpath(
5243
+ const linked = (await workspace(root)).gitDir !== await fs21.realpath(
4998
5244
  path26.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
4999
5245
  );
5000
5246
  const inactiveBefore = linked && !setup ? await read(INACTIVE_HOSTS_PATH) : null;
@@ -5003,16 +5249,16 @@ async function planTeardown(root, selected) {
5003
5249
  const automation = automationBefore === null ? null : recordSchema2.parse(JSON.parse(automationBefore));
5004
5250
  const commands2 = (host) => new Set([
5005
5251
  automation?.hosts[host]?.command,
5006
- ...ownership.files[configPath(host)]?.hookCommands ?? [],
5252
+ ...ownership.files[configPath2(host)]?.hookCommands ?? [],
5007
5253
  ...knownManagedHookCommands(host),
5008
5254
  hookConfig(host, hookCommand(host)).hooks.SessionStart[0].hooks[0].command,
5009
5255
  hookConfig(host).hooks.SessionStart[0].hooks[0].command
5010
5256
  ].filter((command2) => !!command2));
5011
5257
  const possibleHook = (command2, host) => commands2(host).has(command2) || /\bmason(?:-auto|-hook)?\b/.test(command2);
5012
- const add = (file, before, after, empty) => {
5013
- edits.push({ path: file, before, after: ownership.files[file]?.created && (empty || ownership.files[file].initialHash === hash(before)) ? null : after });
5258
+ const add = (file, before, after, empty2) => {
5259
+ edits.push({ path: file, before, after: ownership.files[file]?.created && (empty2 || ownership.files[file].initialHash === hash(before)) ? null : after });
5014
5260
  };
5015
- let remainingHost = hosts.some((host) => !selected.includes(host) && (setup?.hosts[host] || automation?.hosts[host] || ownership.files[mcpPath(host)] || ownership.files[configPath(host)]));
5261
+ let remainingHost = hosts.some((host) => !selected.includes(host) && (setup?.hosts[host] || automation?.hosts[host] || ownership.files[mcpPath(host)] || ownership.files[configPath2(host)]));
5016
5262
  for (const host of hosts.filter((host2) => !selected.includes(host2))) {
5017
5263
  await attempt(mcpPath(host), async () => {
5018
5264
  const text2 = await read(mcpPath(host));
@@ -5020,8 +5266,8 @@ async function planTeardown(root, selected) {
5020
5266
  const servers = object2.parse((host === "codex" ? config.mcp_servers : config.mcpServers) ?? {});
5021
5267
  remainingHost ||= servers.mason !== void 0;
5022
5268
  });
5023
- await attempt(configPath(host), async () => {
5024
- const config = automationConfigSchema.parse(JSON.parse(await read(configPath(host)) ?? "{}"));
5269
+ await attempt(configPath2(host), async () => {
5270
+ const config = automationConfigSchema.parse(JSON.parse(await read(configPath2(host)) ?? "{}"));
5025
5271
  remainingHost ||= Object.values(config.hooks ?? {}).some((groups) => groups.some((group) => group.hooks.some((handler) => handler.command && possibleHook(handler.command, host))));
5026
5272
  });
5027
5273
  }
@@ -5063,7 +5309,7 @@ async function planTeardown(root, selected) {
5063
5309
  if (!isDeepStrictEqual2(parsed, desired)) throw new Error("Removing the marked block would change other TOML settings; retained for manual cleanup.");
5064
5310
  add(mcpFile, before, after, !after.trim());
5065
5311
  });
5066
- const hookFile = configPath(host);
5312
+ const hookFile = configPath2(host);
5067
5313
  await attempt(hookFile, async () => {
5068
5314
  const before = await read(hookFile);
5069
5315
  if (before === null) return;
@@ -5131,7 +5377,7 @@ async function planTeardown(root, selected) {
5131
5377
  if (!remainingHost) await attempt(".mason/local/project.json", async () => {
5132
5378
  const before = await read(".mason/local/project.json");
5133
5379
  if (before === null) return;
5134
- z19.object({ version: z19.literal(1), initializedAt: z19.string() }).strict().parse(JSON.parse(before));
5380
+ z20.object({ version: z20.literal(1), initializedAt: z20.string() }).strict().parse(JSON.parse(before));
5135
5381
  cleanup.push({ path: ".mason/local/project.json", before, after: null });
5136
5382
  });
5137
5383
  if (!diagnostics.length) {
@@ -5139,7 +5385,7 @@ async function planTeardown(root, selected) {
5139
5385
  if (setup) delete setup.hosts[host];
5140
5386
  if (automation) delete automation.hosts[host];
5141
5387
  delete ownership.files[mcpPath(host)];
5142
- delete ownership.files[configPath(host)];
5388
+ delete ownership.files[configPath2(host)];
5143
5389
  }
5144
5390
  if (!remainingHost) for (const file of DOC_CANDIDATES) delete ownership.files[file];
5145
5391
  if (setupBefore !== null) cleanup.push({
@@ -5212,7 +5458,7 @@ var init_teardown = __esm({
5212
5458
  init_model();
5213
5459
  init_ownership();
5214
5460
  hosts = ["codex", "claude"];
5215
- object2 = z19.record(z19.unknown());
5461
+ object2 = z20.record(z20.unknown());
5216
5462
  json = (value) => JSON.stringify(value, null, 2) + "\n";
5217
5463
  }
5218
5464
  });
@@ -5339,7 +5585,7 @@ var init_types2 = __esm({
5339
5585
  });
5340
5586
 
5341
5587
  // src/review/evidence/vitest.ts
5342
- import { z as z20 } from "zod";
5588
+ import { z as z21 } from "zod";
5343
5589
  function parseVitest(raw, sourceRoot) {
5344
5590
  const report = schema.parse(raw);
5345
5591
  const findings = [], diagnostics = [];
@@ -5388,24 +5634,24 @@ var init_vitest = __esm({
5388
5634
  "use strict";
5389
5635
  init_paths2();
5390
5636
  init_types2();
5391
- count3 = z20.number().int().nonnegative();
5392
- schema = z20.object({
5393
- success: z20.boolean(),
5637
+ count3 = z21.number().int().nonnegative();
5638
+ schema = z21.object({
5639
+ success: z21.boolean(),
5394
5640
  numTotalTests: count3,
5395
5641
  numPassedTests: count3,
5396
5642
  numFailedTests: count3,
5397
5643
  numPendingTests: count3,
5398
5644
  numTodoTests: count3,
5399
5645
  numFailedTestSuites: count3,
5400
- testResults: z20.array(z20.object({
5401
- name: z20.string().min(1),
5402
- status: z20.enum(["passed", "failed"]),
5403
- message: z20.string().optional(),
5404
- assertionResults: z20.array(z20.object({
5405
- fullName: z20.string(),
5406
- status: z20.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
5407
- failureMessages: z20.array(z20.string()).nullable().optional(),
5408
- location: z20.object({ line: count3, column: count3 }).nullable().optional()
5646
+ testResults: z21.array(z21.object({
5647
+ name: z21.string().min(1),
5648
+ status: z21.enum(["passed", "failed"]),
5649
+ message: z21.string().optional(),
5650
+ assertionResults: z21.array(z21.object({
5651
+ fullName: z21.string(),
5652
+ status: z21.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
5653
+ failureMessages: z21.array(z21.string()).nullable().optional(),
5654
+ location: z21.object({ line: count3, column: count3 }).nullable().optional()
5409
5655
  }))
5410
5656
  }))
5411
5657
  });
@@ -5413,7 +5659,7 @@ var init_vitest = __esm({
5413
5659
  });
5414
5660
 
5415
5661
  // src/review/evidence/sarif.ts
5416
- import { z as z21 } from "zod";
5662
+ import { z as z22 } from "zod";
5417
5663
  function parseSarif(raw, sourceRoot) {
5418
5664
  const report = schema2.parse(raw), findings = [], diagnostics = [];
5419
5665
  let active = 0, suppressed = 0, absent = 0, unresolved = 0;
@@ -5504,29 +5750,29 @@ var init_sarif = __esm({
5504
5750
  "use strict";
5505
5751
  init_paths2();
5506
5752
  init_types2();
5507
- index = z21.number().int().nonnegative();
5508
- artifact = z21.object({ uri: z21.string().optional(), uriBaseId: z21.string().optional(), index: index.optional() });
5509
- message = z21.object({ text: z21.string().optional(), markdown: z21.string().optional(), id: z21.string().optional(), arguments: z21.array(z21.string()).optional() });
5510
- level = z21.enum(["error", "warning", "note", "none"]);
5511
- location = z21.object({ physicalLocation: z21.object({ artifactLocation: artifact.optional(), region: z21.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
5512
- schema2 = z21.object({
5513
- version: z21.literal("2.1.0"),
5514
- runs: z21.array(z21.object({
5515
- 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() }) }),
5516
- invocations: z21.array(z21.object({ executionSuccessful: z21.boolean(), commandLine: z21.string().optional(), toolExecutionNotifications: z21.array(z21.object({ level: level.optional(), message })).optional() })).optional(),
5517
- versionControlProvenance: z21.array(z21.object({ revisionId: z21.string().optional() })).optional(),
5518
- originalUriBaseIds: z21.record(artifact).optional(),
5519
- artifacts: z21.array(z21.object({ location: artifact.optional() })).optional(),
5520
- results: z21.array(z21.object({
5521
- ruleId: z21.string().optional(),
5753
+ index = z22.number().int().nonnegative();
5754
+ artifact = z22.object({ uri: z22.string().optional(), uriBaseId: z22.string().optional(), index: index.optional() });
5755
+ message = z22.object({ text: z22.string().optional(), markdown: z22.string().optional(), id: z22.string().optional(), arguments: z22.array(z22.string()).optional() });
5756
+ level = z22.enum(["error", "warning", "note", "none"]);
5757
+ location = z22.object({ physicalLocation: z22.object({ artifactLocation: artifact.optional(), region: z22.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
5758
+ schema2 = z22.object({
5759
+ version: z22.literal("2.1.0"),
5760
+ runs: z22.array(z22.object({
5761
+ tool: z22.object({ driver: z22.object({ name: z22.string().min(1), rules: z22.array(z22.object({ id: z22.string(), defaultConfiguration: z22.object({ level: level.optional() }).optional(), messageStrings: z22.record(message).optional() })).optional(), globalMessageStrings: z22.record(message).optional() }) }),
5762
+ invocations: z22.array(z22.object({ executionSuccessful: z22.boolean(), commandLine: z22.string().optional(), toolExecutionNotifications: z22.array(z22.object({ level: level.optional(), message })).optional() })).optional(),
5763
+ versionControlProvenance: z22.array(z22.object({ revisionId: z22.string().optional() })).optional(),
5764
+ originalUriBaseIds: z22.record(artifact).optional(),
5765
+ artifacts: z22.array(z22.object({ location: artifact.optional() })).optional(),
5766
+ results: z22.array(z22.object({
5767
+ ruleId: z22.string().optional(),
5522
5768
  ruleIndex: index.optional(),
5523
5769
  message,
5524
5770
  level: level.optional(),
5525
- kind: z21.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
5526
- baselineState: z21.enum(["new", "unchanged", "updated", "absent"]).optional(),
5527
- suppressions: z21.array(z21.object({ kind: z21.enum(["inSource", "external"]), status: z21.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
5528
- locations: z21.array(location).optional(),
5529
- relatedLocations: z21.array(location).optional()
5771
+ kind: z22.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
5772
+ baselineState: z22.enum(["new", "unchanged", "updated", "absent"]).optional(),
5773
+ suppressions: z22.array(z22.object({ kind: z22.enum(["inSource", "external"]), status: z22.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
5774
+ locations: z22.array(location).optional(),
5775
+ relatedLocations: z22.array(location).optional()
5530
5776
  })).optional()
5531
5777
  }))
5532
5778
  });
@@ -5534,7 +5780,7 @@ var init_sarif = __esm({
5534
5780
  });
5535
5781
 
5536
5782
  // src/review/evidence/check.ts
5537
- import { z as z22 } from "zod";
5783
+ import { z as z23 } from "zod";
5538
5784
  function parseCheck(raw, sourceRoot) {
5539
5785
  const report = schema3.parse(raw);
5540
5786
  const diagnostics = [], findings = [];
@@ -5574,17 +5820,17 @@ var init_check = __esm({
5574
5820
  "use strict";
5575
5821
  init_paths2();
5576
5822
  init_types2();
5577
- schema3 = z22.object({
5578
- version: z22.literal(1),
5579
- commit: z22.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
5580
- results: z22.array(z22.object({
5581
- name: z22.string().min(1).max(1e3),
5582
- status: z22.enum(["passed", "failed", "skipped"]),
5583
- message: z22.string().max(1e5).optional(),
5584
- locations: z22.array(z22.object({
5585
- file: z22.string().min(1).max(4e3),
5586
- line: z22.number().int().positive().optional(),
5587
- column: z22.number().int().positive().optional()
5823
+ schema3 = z23.object({
5824
+ version: z23.literal(1),
5825
+ commit: z23.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
5826
+ results: z23.array(z23.object({
5827
+ name: z23.string().min(1).max(1e3),
5828
+ status: z23.enum(["passed", "failed", "skipped"]),
5829
+ message: z23.string().max(1e5).optional(),
5830
+ locations: z23.array(z23.object({
5831
+ file: z23.string().min(1).max(4e3),
5832
+ line: z23.number().int().positive().optional(),
5833
+ column: z23.number().int().positive().optional()
5588
5834
  })).max(100).default([])
5589
5835
  })).max(1e5)
5590
5836
  });
@@ -5593,7 +5839,7 @@ var init_check = __esm({
5593
5839
 
5594
5840
  // src/review/evidence.ts
5595
5841
  import path27 from "path";
5596
- import { z as z23 } from "zod";
5842
+ import { z as z24 } from "zod";
5597
5843
  function relativeArtifact(root, file) {
5598
5844
  const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
5599
5845
  const normalized = normalizeRepoPath(relative);
@@ -5634,7 +5880,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5634
5880
  let manifest, raw;
5635
5881
  try {
5636
5882
  manifest = relativeArtifact(root, supplied);
5637
- raw = z23.object({ version: z23.literal(1), checks: z23.array(z23.unknown()) }).parse(await readStoreJson(root, manifest));
5883
+ raw = z24.object({ version: z24.literal(1), checks: z24.array(z24.unknown()) }).parse(await readStoreJson(root, manifest));
5638
5884
  } catch (error) {
5639
5885
  output.diagnostics.push(`${supplied}: ${String(error)}`);
5640
5886
  continue;
@@ -5789,31 +6035,31 @@ var init_evidence2 = __esm({
5789
6035
  init_sarif();
5790
6036
  init_check();
5791
6037
  init_types2();
5792
- checkSchema2 = z23.object({
5793
- id: z23.string().min(1).max(100),
5794
- kind: z23.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
5795
- tool: z23.string().min(1).max(200),
5796
- command: z23.string().min(1).max(2e3),
5797
- commit: z23.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
5798
- workingTreeClean: z23.boolean().optional(),
5799
- source: z23.string().max(2e3).optional(),
5800
- sourceRoot: z23.string().min(1).max(4e3).optional(),
5801
- status: z23.enum(["completed", "skipped", "unavailable"]).default("completed"),
5802
- reason: z23.string().min(1).max(2e3).optional(),
5803
- exitCode: z23.number().int().nullable().optional(),
5804
- environment: z23.object({
5805
- toolVersion: z23.string().min(1).max(200),
5806
- runtime: z23.string().min(1).max(200).optional(),
5807
- platform: z23.string().min(1).max(200).optional(),
5808
- configuration: z23.array(z23.string().min(1).max(4e3)).max(100).optional()
6038
+ checkSchema2 = z24.object({
6039
+ id: z24.string().min(1).max(100),
6040
+ kind: z24.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
6041
+ tool: z24.string().min(1).max(200),
6042
+ command: z24.string().min(1).max(2e3),
6043
+ commit: z24.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
6044
+ workingTreeClean: z24.boolean().optional(),
6045
+ source: z24.string().max(2e3).optional(),
6046
+ sourceRoot: z24.string().min(1).max(4e3).optional(),
6047
+ status: z24.enum(["completed", "skipped", "unavailable"]).default("completed"),
6048
+ reason: z24.string().min(1).max(2e3).optional(),
6049
+ exitCode: z24.number().int().nullable().optional(),
6050
+ environment: z24.object({
6051
+ toolVersion: z24.string().min(1).max(200),
6052
+ runtime: z24.string().min(1).max(200).optional(),
6053
+ platform: z24.string().min(1).max(200).optional(),
6054
+ configuration: z24.array(z24.string().min(1).max(4e3)).max(100).optional()
5809
6055
  }).optional(),
5810
- report: z23.object({ format: z23.enum(["vitest-json", "sarif", "mason-check-json"]), path: z23.string().min(1).max(4e3) }).optional()
6056
+ report: z24.object({ format: z24.enum(["vitest-json", "sarif", "mason-check-json"]), path: z24.string().min(1).max(4e3) }).optional()
5811
6057
  });
5812
6058
  }
5813
6059
  });
5814
6060
 
5815
6061
  // src/review/review.ts
5816
- import fs21 from "fs/promises";
6062
+ import fs22 from "fs/promises";
5817
6063
  import path28 from "path";
5818
6064
  import { execFile as execFile4 } from "child_process";
5819
6065
  import { promisify as promisify4 } from "util";
@@ -5893,7 +6139,7 @@ async function computeReview(rootDir, base, options = {}) {
5893
6139
  analyzed,
5894
6140
  async (relPath) => {
5895
6141
  try {
5896
- await fs21.access(path28.join(resolvedRoot, relPath));
6142
+ await fs22.access(path28.join(resolvedRoot, relPath));
5897
6143
  return true;
5898
6144
  } catch {
5899
6145
  return false;
@@ -6048,7 +6294,7 @@ async function setupStatus(dir) {
6048
6294
  version: 1,
6049
6295
  status: pending.length ? "incomplete" : "not-configured",
6050
6296
  hosts: {},
6051
- next: pending.length ? "Setup did not finish. Rerun mason-auto setup --host " + pending[0] + " to resume using the retained original evidence." : "Project hooks are inactive until local setup. Run mason setup --host codex or --host claude."
6297
+ next: pending.length ? "Setup did not finish. Rerun mason setup --host " + pending[0] + " to resume using the retained original evidence." : "Project hooks are inactive until local setup. Run mason setup --host codex or --host claude."
6052
6298
  };
6053
6299
  }
6054
6300
  const hosts2 = {};
@@ -6082,7 +6328,7 @@ async function setupStatus(dir) {
6082
6328
  if (!mcp || !hooks || !instructionsCurrent || !configured) pending.push("Rerun setup to reconcile project configuration; inspect any disabled host settings.");
6083
6329
  if (!observation?.contextCalls) pending.push("Start a new assistant session and request task context through Mason's get_context tool.");
6084
6330
  if (!complete) pending.push("Review/trust the host configuration, then complete a normal task to observe the full hook lifecycle.");
6085
- if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason-auto check and inspect its diagnostics.");
6331
+ if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason check and inspect its diagnostics.");
6086
6332
  const healthy = installed.available && mcp && hooks && instructionsCurrent && configured && automation.status !== "unavailable";
6087
6333
  hosts2[host] = {
6088
6334
  status: healthy ? complete && observation?.contextCalls ? "active" : "pending" : "attention",
@@ -6216,6 +6462,11 @@ async function setupProject(dir, options = {}) {
6216
6462
  options.progress?.step("Checking configuration and retained findings");
6217
6463
  const checked = await automate(ws.root, { event: "turn_start" });
6218
6464
  const configured = await inspectHostConfig(ws.root, host, void 0);
6465
+ const stats = await configureUsefulness(ws.root, true, { onlyIfUnset: true }).catch(() => ({
6466
+ enabled: null,
6467
+ storage: ".mason/local/usefulness",
6468
+ note: "Local stats unavailable. Inspect mason stats --json, or use mason stats --disable to reset the preference. Existing observations were retained."
6469
+ }));
6219
6470
  await writeStoreJson(ws.root, receiptPath, {
6220
6471
  version: 1,
6221
6472
  host,
@@ -6238,6 +6489,7 @@ async function setupProject(dir, options = {}) {
6238
6489
  findings,
6239
6490
  reportPath: checked.report.reportPath,
6240
6491
  activation: await setupStatus(ws.root),
6492
+ stats,
6241
6493
  next: configured.disabled || configured.mcpDisabled ? "Mason hooks or MCP are disabled in project configuration. Review that setting before activation." : host === "codex" ? "Review/trust this project's MCP configuration and hooks in Codex (/hooks in the CLI), then start a new session and give it a normal task." : "Approve the project MCP server in Claude Code, then start a new session and give it a normal task."
6242
6494
  };
6243
6495
  });
@@ -6249,18 +6501,20 @@ function summarizeSetup(result) {
6249
6501
  " MCP server and lifecycle hooks use mason from PATH.",
6250
6502
  " Project instructions updated; original audit evidence retained.",
6251
6503
  ` ${result.changedFiles.length} files changed.`,
6504
+ result.stats.enabled === null ? result.stats.note : result.stats.enabled ? "Local stats enabled: metadata stays in .mason/local/usefulness/; nothing is uploaded. View with mason stats; disable with mason stats --disable." : "Local stats remain disabled by your saved preference. Enable with mason stats --enable.",
6252
6505
  `Initial audit: ${audit.status}${"counts" in audit ? `; ${audit.counts.issues} issues, ${audit.counts.advisories + audit.counts.suppressedAdvisories} advisories` : ""}.`,
6253
6506
  ..."issues" in audit ? audit.issues.slice(0, 3).map((f) => " " + f.message) : [],
6254
6507
  `Original evidence: ${result.initialReportPath}`,
6255
6508
  "Activation: " + result.activation.status + ".",
6256
6509
  result.next,
6257
6510
  "If the assistant cannot find mason, restart the desktop app so it picks up PATH.",
6258
- "After the task finishes, run mason-auto status. Configuration alone does not establish activation."
6511
+ "After the task finishes, run mason status. Configuration alone does not establish activation."
6259
6512
  ].join("\n");
6260
6513
  }
6261
6514
  var init_setup = __esm({
6262
6515
  "src/setup/setup.ts"() {
6263
6516
  "use strict";
6517
+ init_usefulness();
6264
6518
  init_evidence();
6265
6519
  init_store();
6266
6520
  init_runtime();
@@ -6277,13 +6531,13 @@ var init_setup = __esm({
6277
6531
  });
6278
6532
 
6279
6533
  // src/distribution/path.ts
6280
- import fs22 from "fs/promises";
6534
+ import fs23 from "fs/promises";
6281
6535
  import os3 from "os";
6282
6536
  import path29 from "path";
6283
6537
  import { execFile as execFile5 } from "child_process";
6284
6538
  import { promisify as promisify5 } from "util";
6285
6539
  import { createHash as createHash4, randomUUID as randomUUID7 } from "crypto";
6286
- import { z as z24 } from "zod";
6540
+ import { z as z25 } from "zod";
6287
6541
  async function configurePath(bin, previous, save, options = {}) {
6288
6542
  const env = options.env ?? process.env, platform = options.platform ?? process.platform;
6289
6543
  const changes = [...previous];
@@ -6318,7 +6572,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
6318
6572
  else {
6319
6573
  const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path29.join(userHome, name));
6320
6574
  let login = candidates[2];
6321
- for (const file of candidates) if (await fs22.lstat(file).then(() => true, (error) => {
6575
+ for (const file of candidates) if (await fs23.lstat(file).then(() => true, (error) => {
6322
6576
  if (error.code === "ENOENT") return false;
6323
6577
  throw error;
6324
6578
  })) {
@@ -6329,12 +6583,12 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
6329
6583
  }
6330
6584
  const marker = `# >>> mason PATH ${createHash4("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
6331
6585
  for (const profile of profiles2) {
6332
- const stat = await fs22.lstat(profile).catch((error) => {
6586
+ const stat = await fs23.lstat(profile).catch((error) => {
6333
6587
  if (error.code === "ENOENT") return null;
6334
6588
  throw error;
6335
6589
  });
6336
- const file = stat?.isSymbolicLink() ? await fs22.realpath(profile) : path29.resolve(profile);
6337
- const original = await fs22.readFile(file).catch((error) => {
6590
+ const file = stat?.isSymbolicLink() ? await fs23.realpath(profile) : path29.resolve(profile);
6591
+ const original = await fs23.readFile(file).catch((error) => {
6338
6592
  if (error.code === "ENOENT" && !stat) return null;
6339
6593
  throw error;
6340
6594
  });
@@ -6349,8 +6603,8 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
6349
6603
  changes.push({ kind: "profile", file, block, created: original === null });
6350
6604
  await save(changes);
6351
6605
  }
6352
- await fs22.mkdir(path29.dirname(file), { recursive: true });
6353
- const handle = await fs22.open(file, original === null ? "ax+" : "a+", 384);
6606
+ await fs23.mkdir(path29.dirname(file), { recursive: true });
6607
+ const handle = await fs23.open(file, original === null ? "ax+" : "a+", 384);
6354
6608
  try {
6355
6609
  if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
6356
6610
  await handle.writeFile(block);
@@ -6374,13 +6628,13 @@ async function removePath(changes) {
6374
6628
  await windowsPath("remove", change.entry, current, change.created);
6375
6629
  continue;
6376
6630
  }
6377
- const stat = await fs22.lstat(change.file).catch((error) => {
6631
+ const stat = await fs23.lstat(change.file).catch((error) => {
6378
6632
  if (error.code === "ENOENT") return null;
6379
6633
  throw error;
6380
6634
  });
6381
6635
  if (!stat) continue;
6382
6636
  if (!stat.isFile()) throw new Error("profile type changed");
6383
- const bytes = await fs22.readFile(change.file), text2 = bytes.toString("utf8");
6637
+ const bytes = await fs23.readFile(change.file), text2 = bytes.toString("utf8");
6384
6638
  if (!Buffer.from(text2).equals(bytes)) throw new Error("profile encoding changed");
6385
6639
  const index2 = text2.indexOf(change.block);
6386
6640
  if (index2 < 0 || text2.indexOf(change.block, index2 + change.block.length) >= 0) throw new Error("PATH block was edited or removed");
@@ -6390,7 +6644,7 @@ async function removePath(changes) {
6390
6644
  const temporary = change.file + ".mason-" + randomUUID7();
6391
6645
  try {
6392
6646
  if (stat.nlink > 1) throw new Error("profile has multiple hard links");
6393
- const handle = await fs22.open(temporary, "wx", stat.mode & 511);
6647
+ const handle = await fs23.open(temporary, "wx", stat.mode & 511);
6394
6648
  try {
6395
6649
  await handle.writeFile(next);
6396
6650
  await handle.chmod(stat.mode & 511);
@@ -6399,11 +6653,11 @@ async function removePath(changes) {
6399
6653
  } finally {
6400
6654
  await handle.close();
6401
6655
  }
6402
- const current = await fs22.lstat(change.file);
6403
- if (!current.isFile() || current.ino !== stat.ino || !(await fs22.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
6404
- await fs22.rename(temporary, change.file);
6656
+ const current = await fs23.lstat(change.file);
6657
+ if (!current.isFile() || current.ino !== stat.ino || !(await fs23.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
6658
+ await fs23.rename(temporary, change.file);
6405
6659
  } finally {
6406
- await fs22.rm(temporary, { force: true });
6660
+ await fs23.rm(temporary, { force: true });
6407
6661
  }
6408
6662
  } catch (error) {
6409
6663
  warnings.push(`Retained PATH setting ${change.kind === "profile" ? change.file : change.entry}: ${error instanceof Error ? error.message : String(error)}`);
@@ -6445,9 +6699,9 @@ var init_path = __esm({
6445
6699
  "src/distribution/path.ts"() {
6446
6700
  "use strict";
6447
6701
  execute2 = promisify5(execFile5);
6448
- pathChangeSchema = z24.discriminatedUnion("kind", [
6449
- z24.object({ kind: z24.literal("profile"), file: z24.string(), block: z24.string(), created: z24.boolean() }),
6450
- z24.object({ kind: z24.literal("windows"), entry: z24.string(), created: z24.boolean() })
6702
+ pathChangeSchema = z25.discriminatedUnion("kind", [
6703
+ z25.object({ kind: z25.literal("profile"), file: z25.string(), block: z25.string(), created: z25.boolean() }),
6704
+ z25.object({ kind: z25.literal("windows"), entry: z25.string(), created: z25.boolean() })
6451
6705
  ]);
6452
6706
  quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
6453
6707
  normalizeWindowsEntry = (value) => path29.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
@@ -6475,13 +6729,13 @@ __export(state_exports, {
6475
6729
  import os4 from "os";
6476
6730
  import path30 from "path";
6477
6731
  import { fileURLToPath } from "url";
6478
- import fs23 from "fs/promises";
6479
- import { z as z25 } from "zod";
6732
+ import fs24 from "fs/promises";
6733
+ import { z as z26 } from "zod";
6480
6734
  async function readInstallation(home) {
6481
6735
  const raw = await readStoreJson(home, "install.json");
6482
6736
  if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
6483
6737
  const record = recordSchema3.parse(raw);
6484
- if (record.home !== await fs23.realpath(home)) throw new Error("Invalid standalone installation receipt.");
6738
+ if (record.home !== await fs24.realpath(home)) throw new Error("Invalid standalone installation receipt.");
6485
6739
  return record;
6486
6740
  }
6487
6741
  function standaloneLocation() {
@@ -6516,38 +6770,38 @@ var init_state = __esm({
6516
6770
  "use strict";
6517
6771
  init_storage();
6518
6772
  init_path();
6519
- releaseVersion = z25.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
6520
- bundleId = z25.string().regex(/^[a-f0-9]{24}$/);
6521
- updatePolicySchema = z25.object({
6522
- enabled: z25.boolean(),
6523
- revision: z25.string(),
6773
+ releaseVersion = z26.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
6774
+ bundleId = z26.string().regex(/^[a-f0-9]{24}$/);
6775
+ updatePolicySchema = z26.object({
6776
+ enabled: z26.boolean(),
6777
+ revision: z26.string(),
6524
6778
  pinnedVersion: releaseVersion.optional(),
6525
- mirror: z25.string().optional()
6779
+ mirror: z26.string().optional()
6526
6780
  });
6527
- recordSchema3 = z25.object({
6528
- format: z25.literal(1),
6529
- home: z25.string(),
6530
- bin: z25.string(),
6781
+ recordSchema3 = z26.object({
6782
+ format: z26.literal(1),
6783
+ home: z26.string(),
6784
+ bin: z26.string(),
6531
6785
  current: bundleId,
6532
6786
  version: releaseVersion,
6533
- launchers: z25.record(z25.string()),
6534
- previousLaunchers: z25.record(z25.string()).optional(),
6535
- pathChanges: z25.array(pathChangeSchema).optional(),
6787
+ launchers: z26.record(z26.string()),
6788
+ previousLaunchers: z26.record(z26.string()).optional(),
6789
+ pathChanges: z26.array(pathChangeSchema).optional(),
6536
6790
  updates: updatePolicySchema.optional(),
6537
- previous: z25.object({ id: bundleId, version: releaseVersion }).optional(),
6538
- pending: z25.object({ id: bundleId, version: releaseVersion, policyRevision: z25.string() }).optional()
6791
+ previous: z26.object({ id: bundleId, version: releaseVersion }).optional(),
6792
+ pending: z26.object({ id: bundleId, version: releaseVersion, policyRevision: z26.string() }).optional()
6539
6793
  });
6540
6794
  defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path30.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path30.join(os4.homedir(), ".local/share/mason"));
6541
6795
  }
6542
6796
  });
6543
6797
 
6544
6798
  // src/distribution/bundle.ts
6545
- import fs24 from "fs/promises";
6799
+ import fs25 from "fs/promises";
6546
6800
  import path31 from "path";
6547
6801
  import { createHash as createHash5 } from "crypto";
6548
- import { z as z26 } from "zod";
6802
+ import { z as z27 } from "zod";
6549
6803
  async function readBundle(root) {
6550
- const bytes = await fs24.readFile(await storePath(root, "bundle.json"));
6804
+ const bytes = await fs25.readFile(await storePath(root, "bundle.json"));
6551
6805
  const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
6552
6806
  const windows = manifest.target.startsWith("win32-");
6553
6807
  for (const file of Object.keys(manifest.files)) {
@@ -6555,7 +6809,7 @@ async function readBundle(root) {
6555
6809
  throw new Error("Unsafe path in Mason bundle: " + file);
6556
6810
  }
6557
6811
  }
6558
- for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-auto.js", "app/dist/mason-mcp.js"]) {
6812
+ for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-mcp.js"]) {
6559
6813
  if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
6560
6814
  }
6561
6815
  return { manifest, manifestHash: sha256(bytes) };
@@ -6566,7 +6820,7 @@ async function verifyBundle(root, expectedHash) {
6566
6820
  if (bundle.manifest.target !== `${process.platform}-${process.arch}`) throw new Error("Mason bundle is for another platform. Install this platform's bundle and rerun setup.");
6567
6821
  const files = Object.keys(bundle.manifest.files);
6568
6822
  for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
6569
- if (sha256(await fs24.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
6823
+ if (sha256(await fs25.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
6570
6824
  }));
6571
6825
  return bundle;
6572
6826
  }
@@ -6576,13 +6830,13 @@ var init_bundle = __esm({
6576
6830
  "use strict";
6577
6831
  init_storage();
6578
6832
  targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
6579
- digest2 = z26.string().regex(/^[a-f0-9]{64}$/);
6580
- bundleSchema = z26.object({
6581
- format: z26.literal(1),
6582
- version: z26.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
6583
- target: z26.enum(targets),
6584
- nodeVersion: z26.string(),
6585
- files: z26.record(digest2)
6833
+ digest2 = z27.string().regex(/^[a-f0-9]{64}$/);
6834
+ bundleSchema = z27.object({
6835
+ format: z27.literal(1),
6836
+ version: z27.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
6837
+ target: z27.enum(targets),
6838
+ nodeVersion: z27.string(),
6839
+ files: z27.record(digest2)
6586
6840
  }).strict();
6587
6841
  sha256 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
6588
6842
  }
@@ -6605,27 +6859,27 @@ var init_trust2 = __esm({
6605
6859
  });
6606
6860
 
6607
6861
  // src/distribution/runtime.ts
6608
- import fs25 from "fs/promises";
6862
+ import fs26 from "fs/promises";
6609
6863
  import { unlinkSync } from "fs";
6610
6864
  import os5 from "os";
6611
6865
  import path32 from "path";
6612
6866
  import { randomUUID as randomUUID8 } from "crypto";
6613
- import { z as z27 } from "zod";
6867
+ import { z as z28 } from "zod";
6614
6868
  async function pruneUnusedVersions(home) {
6615
6869
  const versions = await storePath(home, "versions");
6616
6870
  const candidates = [];
6617
6871
  const initial = await readInstallation(home);
6618
6872
  const initialKeep = /* @__PURE__ */ new Set([initial.current, initial.previous?.id, initial.pending?.id]);
6619
- for (const name of await fs25.readdir(versions)) {
6873
+ for (const name of await fs26.readdir(versions)) {
6620
6874
  const retired = /^\.retired-([a-f0-9]{24})-[a-f0-9-]+$/.exec(name);
6621
6875
  const id = retired?.[1] ?? name;
6622
6876
  if (!/^[a-f0-9]{24}$/.test(id) || initialKeep.has(id)) continue;
6623
6877
  try {
6624
- const directory = await storePath(home, `versions/${name}`);
6625
- await fs25.access(await storePath(directory, "app/dist/mason-runtime.js"));
6626
- const bundle = await verifyBundle(directory);
6878
+ const directory2 = await storePath(home, `versions/${name}`);
6879
+ await fs26.access(await storePath(directory2, "app/dist/mason-runtime.js"));
6880
+ const bundle = await verifyBundle(directory2);
6627
6881
  if (bundle.manifestHash.slice(0, 24) === id && bundle.manifest.files["app/dist/mason-runtime.js"])
6628
- candidates.push({ id, directory, retired: !!retired });
6882
+ candidates.push({ id, directory: directory2, retired: !!retired });
6629
6883
  } catch {
6630
6884
  }
6631
6885
  }
@@ -6634,8 +6888,8 @@ async function pruneUnusedVersions(home) {
6634
6888
  await withLock(home, ".install-lock", async () => {
6635
6889
  const record = await readInstallation(home);
6636
6890
  const keep = /* @__PURE__ */ new Set([record.current, record.previous?.id, record.pending?.id]);
6637
- const directory = await storePath(home, "runtimes");
6638
- const leases = await fs25.readdir(directory).catch((error) => {
6891
+ const directory2 = await storePath(home, "runtimes");
6892
+ const leases = await fs26.readdir(directory2).catch((error) => {
6639
6893
  if (error.code === "ENOENT") return [];
6640
6894
  throw error;
6641
6895
  });
@@ -6651,7 +6905,7 @@ async function pruneUnusedVersions(home) {
6651
6905
  keep.add(lease.bundle);
6652
6906
  } catch (error) {
6653
6907
  if (error.code !== "ESRCH") return;
6654
- await fs25.rm(await storePath(home, `runtimes/${name}`), { force: true });
6908
+ await fs26.rm(await storePath(home, `runtimes/${name}`), { force: true });
6655
6909
  }
6656
6910
  } catch {
6657
6911
  return;
@@ -6661,13 +6915,13 @@ async function pruneUnusedVersions(home) {
6661
6915
  if (keep.has(candidate.id)) continue;
6662
6916
  try {
6663
6917
  const retired = candidate.retired ? candidate.directory : await storePath(home, `versions/.retired-${candidate.id}-${randomUUID8()}`);
6664
- if (!candidate.retired) await fs25.rename(candidate.directory, retired);
6918
+ if (!candidate.retired) await fs26.rename(candidate.directory, retired);
6665
6919
  garbage.push(retired);
6666
6920
  } catch {
6667
6921
  }
6668
6922
  }
6669
6923
  });
6670
- for (const directory of garbage) await fs25.rm(directory, { recursive: true }).catch(() => {
6924
+ for (const directory2 of garbage) await fs26.rm(directory2, { recursive: true }).catch(() => {
6671
6925
  });
6672
6926
  }
6673
6927
  var leaseSchema;
@@ -6679,18 +6933,18 @@ var init_runtime2 = __esm({
6679
6933
  init_state();
6680
6934
  init_bundle();
6681
6935
  init_trust2();
6682
- leaseSchema = z27.object({ pid: z27.number().int().positive(), host: z27.string(), bundle: z27.string().regex(/^[a-f0-9]{24}$/) });
6936
+ leaseSchema = z28.object({ pid: z28.number().int().positive(), host: z28.string(), bundle: z28.string().regex(/^[a-f0-9]{24}$/) });
6683
6937
  }
6684
6938
  });
6685
6939
 
6686
6940
  // src/distribution/sessions.ts
6687
- import fs26 from "fs/promises";
6941
+ import fs27 from "fs/promises";
6688
6942
  import os6 from "os";
6689
6943
  import { randomUUID as randomUUID9 } from "crypto";
6690
- import { z as z28 } from "zod";
6944
+ import { z as z29 } from "zod";
6691
6945
  async function hasRunningMcp(home) {
6692
- const directory = await storePath(home, "sessions");
6693
- const files = await fs26.readdir(directory).catch((error) => {
6946
+ const directory2 = await storePath(home, "sessions");
6947
+ const files = await fs27.readdir(directory2).catch((error) => {
6694
6948
  if (error.code === "ENOENT") return [];
6695
6949
  throw error;
6696
6950
  });
@@ -6708,7 +6962,7 @@ async function hasRunningMcp(home) {
6708
6962
  } catch (error) {
6709
6963
  if (error.code !== "ESRCH") return true;
6710
6964
  }
6711
- await fs26.rm(await storePath(home, relative), { force: true });
6965
+ await fs27.rm(await storePath(home, relative), { force: true });
6712
6966
  } catch {
6713
6967
  return true;
6714
6968
  }
@@ -6719,7 +6973,7 @@ async function registerMcp(home) {
6719
6973
  const name = `sessions/${randomUUID9()}.json`;
6720
6974
  await writeStoreJson(home, name, { pid: process.pid, host: os6.hostname() });
6721
6975
  return async () => {
6722
- await fs26.rm(await storePath(home, name), { force: true }).catch(() => {
6976
+ await fs27.rm(await storePath(home, name), { force: true }).catch(() => {
6723
6977
  });
6724
6978
  };
6725
6979
  }
@@ -6728,7 +6982,7 @@ var init_sessions = __esm({
6728
6982
  "src/distribution/sessions.ts"() {
6729
6983
  "use strict";
6730
6984
  init_storage();
6731
- leaseSchema2 = z28.object({ pid: z28.number().int().positive(), host: z28.string() });
6985
+ leaseSchema2 = z29.object({ pid: z29.number().int().positive(), host: z29.string() });
6732
6986
  }
6733
6987
  });
6734
6988
 
@@ -6743,7 +6997,7 @@ __export(install_exports2, {
6743
6997
  uninstallStandalone: () => uninstallStandalone,
6744
6998
  upgradeStandalone: () => upgradeStandalone
6745
6999
  });
6746
- import fs27 from "fs/promises";
7000
+ import fs28 from "fs/promises";
6747
7001
  import os7 from "os";
6748
7002
  import path33 from "path";
6749
7003
  import { execFile as execFile6, spawn } from "child_process";
@@ -6752,18 +7006,18 @@ import { randomUUID as randomUUID10 } from "crypto";
6752
7006
  async function replace(file, content2, mode = 384) {
6753
7007
  const temp = file + ".tmp-" + randomUUID10();
6754
7008
  try {
6755
- await fs27.writeFile(temp, content2, { mode, flag: "wx" });
6756
- await fs27.rename(temp, file);
7009
+ await fs28.writeFile(temp, content2, { mode, flag: "wx" });
7010
+ await fs28.rename(temp, file);
6757
7011
  } finally {
6758
- await fs27.rm(temp, { force: true });
7012
+ await fs28.rm(temp, { force: true });
6759
7013
  }
6760
7014
  }
6761
7015
  async function installStandalone(source, progress2, options = {}) {
6762
7016
  progress2?.step("Checking installation files and location");
6763
7017
  const bundle = await verifyBundle(source);
6764
7018
  const selectedHome = path33.resolve(defaultHome());
6765
- if (!options.stageOnly) await fs27.mkdir(selectedHome, { recursive: true });
6766
- const home = await fs27.realpath(selectedHome);
7019
+ if (!options.stageOnly) await fs28.mkdir(selectedHome, { recursive: true });
7020
+ const home = await fs28.realpath(selectedHome);
6767
7021
  if (options.stageOnly) {
6768
7022
  assertUpdatePolicy(await readInstallation(home), bundle.manifest.version, options.policyRevision);
6769
7023
  const destination = await storePath(home, "versions/" + bundle.manifestHash.slice(0, 24));
@@ -6786,17 +7040,17 @@ async function installLocked2(home, source, bundle, progress2, options, alreadyS
6786
7040
  const raw = await readStoreJson(home, "install.json");
6787
7041
  const previous = raw === null ? null : recordSchema3.parse(raw);
6788
7042
  if (options.stageOnly) assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
6789
- if (!previous) for (const file of await fs27.readdir(home)) {
7043
+ if (!previous) for (const file of await fs28.readdir(home)) {
6790
7044
  if (file === ".install-lock") continue;
6791
- const directory = path33.join(home, file);
6792
- const stat = await fs27.lstat(directory);
6793
- if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs27.readdir(directory)).length) {
7045
+ const directory2 = path33.join(home, file);
7046
+ const stat = await fs28.lstat(directory2);
7047
+ if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs28.readdir(directory2)).length) {
6794
7048
  throw new Error("Mason installation directory is not empty and has no ownership receipt.");
6795
7049
  }
6796
7050
  }
6797
7051
  const selectedBin = path33.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path33.join(home, "bin") : path33.join(os7.homedir(), ".local/bin")));
6798
- await fs27.mkdir(selectedBin, { recursive: true });
6799
- const bin = await fs27.realpath(selectedBin);
7052
+ await fs28.mkdir(selectedBin, { recursive: true });
7053
+ const bin = await fs28.realpath(selectedBin);
6800
7054
  if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
6801
7055
  const id = bundle.manifestHash.slice(0, 24);
6802
7056
  const destination = await storePath(home, "versions/" + id);
@@ -6828,7 +7082,7 @@ exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destinat
6828
7082
  ` };
6829
7083
  for (const [name, content2] of Object.entries(launchers)) {
6830
7084
  const file = await storePath(bin, name);
6831
- const old = await fs27.readFile(file, "utf8").catch((error) => {
7085
+ const old = await fs28.readFile(file, "utf8").catch((error) => {
6832
7086
  if (error.code === "ENOENT") return null;
6833
7087
  throw error;
6834
7088
  });
@@ -6837,7 +7091,7 @@ exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destinat
6837
7091
  const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
6838
7092
  const checkRetired = async (name, expected) => {
6839
7093
  if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
6840
- const actual = await fs27.readFile(await storePath(bin, name), "utf8").catch((error) => {
7094
+ const actual = await fs28.readFile(await storePath(bin, name), "utf8").catch((error) => {
6841
7095
  if (error.code === "ENOENT") return null;
6842
7096
  throw error;
6843
7097
  });
@@ -6872,7 +7126,7 @@ exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destinat
6872
7126
  for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
6873
7127
  for (const [name, expected] of retired) {
6874
7128
  await checkRetired(name, expected);
6875
- await fs27.rm(await storePath(bin, name), { force: true });
7129
+ await fs28.rm(await storePath(bin, name), { force: true });
6876
7130
  }
6877
7131
  await writeStoreJson(home, "install.json", receipt);
6878
7132
  } catch (error) {
@@ -6891,7 +7145,7 @@ exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destinat
6891
7145
  return { home, bin, version: bundle.manifest.version, previousVersion: previous?.version, path: pathResult };
6892
7146
  }
6893
7147
  async function copyBundle(home, source, destination, bundle) {
6894
- const exists = await fs27.lstat(destination).then(() => true, (error) => {
7148
+ const exists = await fs28.lstat(destination).then(() => true, (error) => {
6895
7149
  if (error.code === "ENOENT") return false;
6896
7150
  throw error;
6897
7151
  });
@@ -6900,16 +7154,16 @@ async function copyBundle(home, source, destination, bundle) {
6900
7154
  return;
6901
7155
  }
6902
7156
  const stage = await storePath(home, "versions/.install-" + randomUUID10(), true);
6903
- await fs27.mkdir(stage);
7157
+ await fs28.mkdir(stage);
6904
7158
  try {
6905
7159
  for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
6906
7160
  const from = await storePath(source, file), to = await storePath(stage, file, true);
6907
- await fs27.copyFile(from, to);
6908
- await fs27.chmod(to, (await fs27.stat(from)).mode & 511);
7161
+ await fs28.copyFile(from, to);
7162
+ await fs28.chmod(to, (await fs28.stat(from)).mode & 511);
6909
7163
  }
6910
7164
  await verifyBundle(stage, bundle.manifestHash);
6911
7165
  try {
6912
- await fs27.rename(stage, destination);
7166
+ await fs28.rename(stage, destination);
6913
7167
  } catch (error) {
6914
7168
  try {
6915
7169
  await verifyBundle(destination, bundle.manifestHash);
@@ -6918,7 +7172,7 @@ async function copyBundle(home, source, destination, bundle) {
6918
7172
  }
6919
7173
  }
6920
7174
  } finally {
6921
- await fs27.rm(stage, { recursive: true, force: true });
7175
+ await fs28.rm(stage, { recursive: true, force: true });
6922
7176
  }
6923
7177
  }
6924
7178
  async function prepareStandaloneMcp(home) {
@@ -7037,7 +7291,7 @@ async function uninstallStandalone() {
7037
7291
  for (const [name, expected] of Object.entries(record.launchers)) {
7038
7292
  if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
7039
7293
  const file = await storePath(record.bin, name);
7040
- const actual = await fs27.readFile(file, "utf8").catch((error) => {
7294
+ const actual = await fs28.readFile(file, "utf8").catch((error) => {
7041
7295
  if (error.code === "ENOENT") return null;
7042
7296
  throw error;
7043
7297
  });
@@ -7045,9 +7299,9 @@ async function uninstallStandalone() {
7045
7299
  }
7046
7300
  for (const warning of await removePath(record.pathChanges ?? [])) console.error(warning);
7047
7301
  if (process.platform === "win32") await replace(await storePath(home, ".uninstall-request.txt", true), token);
7048
- for (const name of Object.keys(record.launchers)) await fs27.rm(await storePath(record.bin, name), { force: true });
7302
+ for (const name of Object.keys(record.launchers)) await fs28.rm(await storePath(record.bin, name), { force: true });
7049
7303
  });
7050
- if (process.platform !== "win32") await fs27.rm(home, { recursive: true, force: true });
7304
+ if (process.platform !== "win32") await fs28.rm(home, { recursive: true, force: true });
7051
7305
  return home;
7052
7306
  }
7053
7307
  var shQuote, psQuote, WINDOWS_BATCH_LAUNCHER, launcherNames;
@@ -7082,11 +7336,11 @@ __export(updates_exports, {
7082
7336
  updateSettings: () => updateSettings,
7083
7337
  updateStatus: () => updateStatus
7084
7338
  });
7085
- import fs28 from "fs/promises";
7339
+ import fs29 from "fs/promises";
7086
7340
  import path34 from "path";
7087
7341
  import { spawn as spawn2 } from "child_process";
7088
7342
  import { randomUUID as randomUUID11 } from "crypto";
7089
- import { z as z29 } from "zod";
7343
+ import { z as z30 } from "zod";
7090
7344
  async function boundedDownload(url) {
7091
7345
  const response = await fetch(url, { signal: AbortSignal.timeout(15e3), redirect: "follow" });
7092
7346
  if (!response.ok || !response.body) throw new Error(`Update metadata request failed (HTTP ${response.status}).`);
@@ -7189,7 +7443,7 @@ async function runAutomaticUpdate(home, dependencies = { fetchRelease: fetchVeri
7189
7443
  } catch (error) {
7190
7444
  status.error = error instanceof Error ? error.message : String(error);
7191
7445
  }
7192
- if (await fs28.access(path34.join(home, "install.json")).then(() => true, () => false)) await writeStoreJson(home, "update-status.json", status);
7446
+ if (await fs29.access(path34.join(home, "install.json")).then(() => true, () => false)) await writeStoreJson(home, "update-status.json", status);
7193
7447
  }, 0);
7194
7448
  }
7195
7449
  async function scheduleAutomaticUpdate() {
@@ -7299,14 +7553,14 @@ var init_updates = __esm({
7299
7553
  CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7300
7554
  RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
7301
7555
  releaseBase = "https://github.com/adrianczuczka/mason/releases";
7302
- releaseListSchema = z29.array(z29.object({ tag_name: z29.string(), draft: z29.boolean(), prerelease: z29.boolean(), published_at: z29.string().nullable() }));
7303
- updateManifestSchema = z29.object({
7304
- format: z29.literal(1),
7305
- version: z29.string().regex(/^\d+\.\d+\.\d+$/),
7306
- publishedAt: z29.string().datetime(),
7307
- assets: z29.record(z29.string().regex(/^[a-f0-9]{64}$/))
7556
+ releaseListSchema = z30.array(z30.object({ tag_name: z30.string(), draft: z30.boolean(), prerelease: z30.boolean(), published_at: z30.string().nullable() }));
7557
+ updateManifestSchema = z30.object({
7558
+ format: z30.literal(1),
7559
+ version: z30.string().regex(/^\d+\.\d+\.\d+$/),
7560
+ publishedAt: z30.string().datetime(),
7561
+ assets: z30.record(z30.string().regex(/^[a-f0-9]{64}$/))
7308
7562
  }).strict();
7309
- statusSchema2 = z29.object({ lastAttempt: z29.number(), availableVersion: z29.string().optional(), error: z29.string().optional() });
7563
+ statusSchema2 = z30.object({ lastAttempt: z30.number(), availableVersion: z30.string().optional(), error: z30.string().optional() });
7310
7564
  }
7311
7565
  });
7312
7566
 
@@ -7314,6 +7568,7 @@ var init_updates = __esm({
7314
7568
  import { fileURLToPath as fileURLToPath2 } from "url";
7315
7569
 
7316
7570
  // src/automation/cli.ts
7571
+ init_usefulness();
7317
7572
  init_execution();
7318
7573
  init_runtime();
7319
7574
  init_adapters();
@@ -7385,13 +7640,27 @@ Keeps shared knowledge, repair evidence, ignore rules and the Mason installation
7385
7640
  Edited or ambiguous entries are retained for manual cleanup. Restart assistants
7386
7641
  after teardown. Run mason setup --host codex or --host claude to reconnect.
7387
7642
  Exit 0: complete/no-op. Exit 2: cleanup needs attention (also in dry runs).`;
7388
- var USAGE = `Usage: mason-auto <setup|teardown|install|config|status|check|hook> [options]
7643
+ var STATS_USAGE = `Usage: mason stats [options]
7644
+
7645
+ Show aggregate local observations, or inspect one session.
7646
+
7647
+ --enable | --disable Enable/disable collection (setup enables it)
7648
+ --json Include session/finding IDs and evidence paths
7649
+ --dir <path> Project directory (defaults to cwd)
7650
+ --session <id> Filter to one retained session
7651
+ --session <id> --finding <id> --rating helpful|already-knew|irrelevant|deferred
7652
+ Supply feedback without changing audit status
7653
+
7654
+ No uploads. Observed resolution does not establish causation.`;
7655
+ var USAGE = `Usage: mason auto <setup|teardown|install|config|status|check|hook|stats> [options]
7389
7656
 
7390
7657
  setup [--host claude|codex] Connect MCP, instructions and hooks to mason on PATH; retain the initial audit
7391
7658
  teardown [--host claude|codex] Disconnect one host or all project hosts; retain knowledge and evidence
7392
7659
  install --host claude|codex Merge lifecycle hooks into this project's host config
7393
7660
  config --host claude|codex Print the host config without writing
7394
7661
  status Read configured hooks and observed runtime events
7662
+ stats Aggregate local observations; --enable/--disable to configure
7663
+ Rate with --session <id> --finding <id> --rating helpful|already-knew|irrelevant|deferred
7395
7664
  check Capture/resume and verify retained audit evidence
7396
7665
  hook --host claude|codex Handle host JSON on stdin
7397
7666
 
@@ -7412,7 +7681,12 @@ var parseCli = (argv) => parseArgs({ args: argv, allowPositionals: true, options
7412
7681
  json: { type: "boolean" },
7413
7682
  help: { type: "boolean", short: "h" },
7414
7683
  "dry-run": { type: "boolean" },
7415
- profile: { type: "boolean" }
7684
+ profile: { type: "boolean" },
7685
+ enable: { type: "boolean" },
7686
+ disable: { type: "boolean" },
7687
+ session: { type: "string" },
7688
+ finding: { type: "string" },
7689
+ rating: { type: "string" }
7416
7690
  } });
7417
7691
  function isHookCommand(argv) {
7418
7692
  try {
@@ -7432,12 +7706,14 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
7432
7706
  try {
7433
7707
  const { values, positionals } = parseCli(argv);
7434
7708
  if (values.help || !positionals.length) {
7435
- io.out(positionals[0] === "teardown" ? TEARDOWN_USAGE : USAGE);
7709
+ io.out(positionals[0] === "teardown" ? TEARDOWN_USAGE : positionals[0] === "stats" ? STATS_USAGE : USAGE);
7436
7710
  return 0;
7437
7711
  }
7438
7712
  if (positionals.length !== 1) throw new Error("Expected one command.");
7439
7713
  [action] = positionals;
7440
7714
  if (values.profile && !["check", "hook"].includes(action)) throw new Error("--profile applies only to check/hook.");
7715
+ const trackingOptions = [values.enable, values.disable, values.session, values.finding, values.rating].some((v) => v !== void 0);
7716
+ if (trackingOptions && action !== "stats") throw new Error("Tracking options apply only to stats.");
7441
7717
  const measured = (run) => values.profile ? withProfile(run, io.err) : run();
7442
7718
  const dir = values.dir ?? process.cwd();
7443
7719
  if (values["dry-run"] !== void 0 && action !== "teardown") throw new Error("--dry-run applies only to teardown.");
@@ -7474,6 +7750,21 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
7474
7750
  return 0;
7475
7751
  }
7476
7752
  if (values.host || values.command) throw new Error("--host and --command apply only to install/config/hook.");
7753
+ if (action === "stats") {
7754
+ const configuring = values.enable !== void 0 || values.disable !== void 0;
7755
+ const rating = values.finding !== void 0 || values.rating !== void 0;
7756
+ if (values.enable !== void 0 && values.disable !== void 0 || configuring && (rating || values.session !== void 0)) throw new Error("Choose tracking configuration or session inspection/feedback separately.");
7757
+ if (rating && (!values.session || !values.finding || !values.rating)) throw new Error("Rating requires --session, --finding, and --rating.");
7758
+ if (configuring) {
7759
+ const result2 = await configureUsefulness(dir, !!values.enable);
7760
+ io.out(values.json ? JSON.stringify(result2, null, 2) : `Local stats ${result2.enabled ? "enabled" : "disabled"}. Existing observations retained. View with mason stats.`);
7761
+ return 0;
7762
+ }
7763
+ if (rating) await rateUsefulness(dir, values.session, values.finding, usefulnessRating.parse(values.rating));
7764
+ const result = await usefulnessStatus(dir, values.session);
7765
+ io.out(values.json ? JSON.stringify(result, null, 2) : summarizeUsefulness(result));
7766
+ return 0;
7767
+ }
7477
7768
  if (action === "status") {
7478
7769
  const { setupStatus: setupStatus2, summarizeActivation: summarizeActivation2 } = await Promise.resolve().then(() => (init_status(), status_exports));
7479
7770
  const setup = await setupStatus2(dir);
@@ -7503,13 +7794,15 @@ var inputArgs = process.argv.slice(2);
7503
7794
  var managedLaunch = inputArgs[0] === "--setup-host";
7504
7795
  var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
7505
7796
  var [command, ...args] = inputArgs;
7506
- var usage = `Mason ${"0.19.2"}
7797
+ var usage = `Mason ${"0.20.1"}
7507
7798
 
7508
7799
  Usage: mason <command> [options]
7509
7800
 
7510
7801
  setup --host codex|claude Connect this project to your assistant
7511
7802
  teardown [--host codex|claude] Disconnect project integrations; retain knowledge
7512
7803
  status Show configuration, observed use and verification
7804
+ stats Show local stats and feedback
7805
+ auto Configure and run lifecycle hooks
7513
7806
  check Resume and verify retained documentation findings
7514
7807
  audit Audit project instructions
7515
7808
  review --base <ref> Review committed changes
@@ -7521,7 +7814,7 @@ Usage: mason <command> [options]
7521
7814
  rollback Restore and pin the previous standalone version
7522
7815
  uninstall Remove that installation; retain project data
7523
7816
 
7524
- Use mason <command> --help for options. npm users can keep using mason-auto,
7817
+ Use mason <command> --help for options. npm users can also use
7525
7818
  mason-audit, mason-review, mason-drift, mason-hook and mason-mcp.`;
7526
7819
  var progress;
7527
7820
  try {
@@ -7532,9 +7825,9 @@ try {
7532
7825
  await prepareLaunch2(setupHost, { allowInactive: command === "mcp" });
7533
7826
  }
7534
7827
  }
7535
- if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.19.2" }));
7828
+ if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.20.1" }));
7536
7829
  else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
7537
- else if (["--version", "-v"].includes(command)) console.log("0.19.2");
7830
+ else if (["--version", "-v"].includes(command)) console.log("0.20.1");
7538
7831
  else if (command === "internal-update") {
7539
7832
  if (args.length) throw new Error("internal-update takes no arguments.");
7540
7833
  const { currentInstallation: currentInstallation2 } = await Promise.resolve().then(() => (init_state(), state_exports));
@@ -7603,7 +7896,7 @@ Automatic updates: ${status.effective ? "enabled" : "inactive"} (preference: ${s
7603
7896
  process.exitCode = await upgradeStandalone2(args[0], progress);
7604
7897
  } else console.log((process.platform === "win32" ? "Finishing installation cleanup after runtime exit: " : "Removed standalone installation: ") + await uninstallStandalone2() + ". Project configuration and knowledge were retained. Reinstall Mason to use its integrations.");
7605
7898
  }
7606
- } else if (["setup", "teardown", "status", "check", "auto"].includes(command)) {
7899
+ } else if (["setup", "teardown", "status", "check", "stats", "auto"].includes(command)) {
7607
7900
  const forwarded = command === "auto" ? args : [command, ...args];
7608
7901
  let input = "";
7609
7902
  if (isHookCommand(forwarded) && !process.stdin.isTTY) for await (const chunk of process.stdin) {