mason-context 0.19.1 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -435,8 +435,229 @@ var init_store_lock = __esm({
435
435
  }
436
436
  });
437
437
 
438
- // src/automation/store.ts
438
+ // src/audit/findings.ts
439
+ import { createHash } from "crypto";
439
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";
440
661
  function parseState(raw) {
441
662
  try {
442
663
  return stateSchema.parse(raw);
@@ -452,30 +673,29 @@ var init_store = __esm({
452
673
  "src/automation/store.ts"() {
453
674
  "use strict";
454
675
  init_store_lock();
455
- hostSchema = z.enum(["claude", "codex"]);
676
+ init_notifications();
677
+ hostSchema = z3.enum(["claude", "codex"]);
456
678
  events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
457
- stateSchema = z.object({
458
- version: z.literal(1),
459
- root: z.string(),
460
- gitDir: z.string(),
461
- branch: z.string(),
462
- baselines: z.array(z.object({ path: z.string(), at: z.string(), event: z.string(), fingerprint: z.string() })).max(128),
463
- sessions: z.record(z.object({
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({
464
686
  host: hostSchema,
465
- seen: z.string().nullable(),
466
- continued: z.boolean(),
467
- initialIssues: z.array(z.string()),
468
- initialDocs: z.record(z.string().nullable()),
469
- lastUsed: z.string(),
470
- mutationObserved: z.boolean(),
471
- pending: z.record(z.string()),
472
- coverageGaps: z.array(z.string()),
473
- events: z.record(z.object({ at: z.string(), count: z.number().int().positive() }))
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() }))
474
692
  })),
475
- updatedAt: z.string(),
476
- fingerprint: z.string().nullable(),
477
- latest: z.string().nullable(),
478
- analysis: z.object({ id: z.string(), fingerprint: z.string(), completedAt: z.number(), path: z.string() }).optional()
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()
479
699
  });
480
700
  }
481
701
  });
@@ -1394,7 +1614,7 @@ var init_test_map = __esm({
1394
1614
 
1395
1615
  // src/snapshot/snapshot.ts
1396
1616
  import path10 from "path";
1397
- import { z as z2 } from "zod";
1617
+ import { z as z4 } from "zod";
1398
1618
  async function inspectSnapshot(rootDir) {
1399
1619
  try {
1400
1620
  const raw = await readStoreJson(rootDir, ".mason/snapshot.json");
@@ -1428,30 +1648,30 @@ var init_snapshot = __esm({
1428
1648
  init_lock();
1429
1649
  init_paths();
1430
1650
  init_test_map();
1431
- repoPath = z2.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
1651
+ repoPath = z4.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
1432
1652
  verificationFields = {
1433
- refreshedHash: z2.string().optional(),
1434
- verifiedAt: z2.string().optional(),
1435
- verifiedHash: z2.string().optional(),
1436
- verificationToken: z2.string().optional(),
1437
- verificationFailed: z2.boolean().optional(),
1438
- verificationNote: z2.string().optional()
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()
1439
1659
  };
1440
- featureSchema = z2.object({
1441
- description: z2.string(),
1442
- files: z2.array(repoPath),
1443
- tests: z2.array(repoPath).optional(),
1444
- type: z2.enum(["capability", "infrastructure"]).optional(),
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(),
1445
1665
  ...verificationFields
1446
1666
  }).passthrough();
1447
- flowSchema = z2.object({ description: z2.string(), chain: z2.array(repoPath), ...verificationFields }).passthrough();
1448
- snapshotSchema = z2.object({
1449
- version: z2.literal(2),
1450
- createdAt: z2.string(),
1451
- updatedAt: z2.string(),
1452
- gitHash: z2.string(),
1453
- features: z2.record(featureSchema),
1454
- flows: z2.record(flowSchema)
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)
1455
1675
  }).passthrough();
1456
1676
  }
1457
1677
  });
@@ -2263,7 +2483,7 @@ var init_trust = __esm({
2263
2483
  });
2264
2484
 
2265
2485
  // src/decisions/provenance.ts
2266
- import { z as z3 } from "zod";
2486
+ import { z as z5 } from "zod";
2267
2487
  function decisionContent(record) {
2268
2488
  return {
2269
2489
  title: record.title,
@@ -2327,67 +2547,67 @@ var init_provenance = __esm({
2327
2547
  "use strict";
2328
2548
  init_paths();
2329
2549
  init_trust();
2330
- text = (max) => z3.string().trim().min(1).max(max);
2331
- decisionSourceSchema = z3.object({
2332
- kind: z3.enum(["pull_request", "issue", "incident", "discussion", "document", "other"]),
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"]),
2333
2553
  reference: text(1e3),
2334
2554
  note: text(500).optional()
2335
2555
  }).strict();
2336
- attributionSchema = z3.object({
2556
+ attributionSchema = z5.object({
2337
2557
  owner: text(200).nullable().optional(),
2338
- sources: z3.array(decisionSourceSchema).max(20).optional(),
2558
+ sources: z5.array(decisionSourceSchema).max(20).optional(),
2339
2559
  actor: text(200).optional()
2340
2560
  });
2341
- contentSchema = z3.object({
2342
- title: z3.string().min(1),
2343
- body: z3.string().min(1),
2344
- category: z3.enum(["decision", "gotcha", "deprecation", "convention"]),
2345
- files: z3.array(z3.string().refine((f) => normalizeRepoPath(f) !== null)),
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)),
2346
2566
  owner: text(200).optional(),
2347
- sources: z3.array(decisionSourceSchema).max(20)
2567
+ sources: z5.array(decisionSourceSchema).max(20)
2348
2568
  });
2349
- approvalSchema = z3.enum(["unreviewed", "proposed", "accepted"]);
2350
- statusSchema = z3.enum(["active", "superseded", "retired"]);
2351
- reviewEvidenceSchema = z3.object({
2352
- baseHash: z3.string(),
2353
- headHash: z3.string(),
2354
- historyAvailable: z3.boolean(),
2355
- changedFiles: z3.array(z3.string()),
2356
- localChanges: z3.array(z3.string())
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())
2357
2577
  });
2358
- eventSchema = z3.object({
2359
- kind: z3.enum(["imported", "created", "revised", "accepted", "reaffirmed", "retired", "superseded"]),
2360
- at: z3.string().datetime(),
2578
+ eventSchema = z5.object({
2579
+ kind: z5.enum(["imported", "created", "revised", "accepted", "reaffirmed", "retired", "superseded"]),
2580
+ at: z5.string().datetime(),
2361
2581
  actor: text(200).optional(),
2362
2582
  note: text(1500).optional(),
2363
- revision: z3.number().int().positive(),
2583
+ revision: z5.number().int().positive(),
2364
2584
  content: contentSchema,
2365
2585
  approval: approvalSchema,
2366
2586
  status: statusSchema,
2367
- refreshedHash: z3.string(),
2587
+ refreshedHash: z5.string(),
2368
2588
  evidence: reviewEvidenceSchema.optional()
2369
2589
  });
2370
- legacySchema = z3.object({
2371
- version: z3.literal(1),
2372
- id: z3.string().regex(/^[a-zA-Z0-9_-]+$/),
2373
- title: z3.string().min(1),
2374
- body: z3.string().min(1),
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),
2375
2595
  category: contentSchema.shape.category,
2376
2596
  files: contentSchema.shape.files,
2377
- createdAt: z3.string(),
2378
- updatedAt: z3.string(),
2379
- refreshedHash: z3.string(),
2380
- status: z3.enum(["active", "superseded"]),
2381
- supersededBy: z3.string().optional()
2597
+ createdAt: z5.string(),
2598
+ updatedAt: z5.string(),
2599
+ refreshedHash: z5.string(),
2600
+ status: z5.enum(["active", "superseded"]),
2601
+ supersededBy: z5.string().optional()
2382
2602
  }).passthrough();
2383
2603
  currentSchema = legacySchema.extend({
2384
- version: z3.literal(2),
2604
+ version: z5.literal(2),
2385
2605
  status: statusSchema,
2386
2606
  approval: approvalSchema,
2387
- revision: z3.number().int().positive(),
2607
+ revision: z5.number().int().positive(),
2388
2608
  owner: text(200).optional(),
2389
- sources: z3.array(decisionSourceSchema).max(20),
2390
- history: z3.array(eventSchema).min(1)
2609
+ sources: z5.array(decisionSourceSchema).max(20),
2610
+ history: z5.array(eventSchema).min(1)
2391
2611
  }).superRefine((record, ctx) => {
2392
2612
  const invalid = (message2) => ctx.addIssue({ code: "custom", message: message2 });
2393
2613
  const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
@@ -2417,15 +2637,15 @@ var init_provenance = __esm({
2417
2637
  }
2418
2638
  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");
2419
2639
  });
2420
- decisionSchema = z3.union([legacySchema, currentSchema]);
2640
+ decisionSchema = z5.union([legacySchema, currentSchema]);
2421
2641
  }
2422
2642
  });
2423
2643
 
2424
2644
  // src/decisions/decisions.ts
2425
2645
  import fs9 from "fs/promises";
2426
2646
  import path18 from "path";
2427
- import { createHash } from "crypto";
2428
- import { z as z4 } from "zod";
2647
+ import { createHash as createHash2 } from "crypto";
2648
+ import { z as z6 } from "zod";
2429
2649
  async function loadDecisionStore(rootDir) {
2430
2650
  const records = [];
2431
2651
  const diagnostics = [];
@@ -2461,7 +2681,7 @@ var init_decisions = __esm({
2461
2681
  TITLE_MAX_CHARS = 80;
2462
2682
  BODY_RECOMMENDED_CHARS = 1500;
2463
2683
  BODY_MAX_CHARS = 2500;
2464
- boundedText = (field, max, hint) => z4.string({
2684
+ boundedText = (field, max, hint) => z6.string({
2465
2685
  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 })
2466
2686
  }).trim().min(1, `${field} must be non-empty`).max(max);
2467
2687
  decisionTitleSchema = boundedText("title", TITLE_MAX_CHARS, "Tighten it to a specific headline.");
@@ -2592,117 +2812,9 @@ var init_checks = __esm({
2592
2812
  }
2593
2813
  });
2594
2814
 
2595
- // src/audit/findings.ts
2596
- import { createHash as createHash2 } from "crypto";
2597
- import { z as z5 } from "zod";
2598
- function findingId(finding) {
2599
- const e = finding.evidence;
2600
- let key;
2601
- switch (e.kind) {
2602
- case "missing-path":
2603
- key = e.resolvedPath && e.resolvedPath !== e.claimed ? [e.claimed, e.resolvedPath] : e.claimed;
2604
- break;
2605
- case "unmentioned-dir":
2606
- key = e.dir;
2607
- break;
2608
- case "count-mismatch":
2609
- key = [e.unit.replace(/s$/, ""), e.countedFrom];
2610
- break;
2611
- case "missing-script":
2612
- key = e.scope && e.scope.directory !== "." ? [e.scriptName, e.scope.directory] : e.scriptName;
2613
- break;
2614
- case "doc-behind-manifests":
2615
- key = null;
2616
- break;
2617
- case "decision-anchor":
2618
- key = [e.decisionId, e.provenance?.revision, e.provenance?.approval];
2619
- break;
2620
- }
2621
- return digest([finding.type, finding.anchor.doc, key]);
2622
- }
2623
- var digest, checkSchema, commitSchema, anchorSchema, count, scopeSchema, evidenceSchema, findingSchema, issueSchema, advisorySchema, checkResultSchema;
2624
- var init_findings = __esm({
2625
- "src/audit/findings.ts"() {
2626
- "use strict";
2627
- digest = (value) => createHash2("sha256").update(JSON.stringify(value)).digest("hex");
2628
- checkSchema = z5.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"]);
2629
- commitSchema = z5.object({ hash: z5.string().regex(/^[a-f0-9]{40,64}$/), date: z5.string(), subject: z5.string() });
2630
- anchorSchema = z5.object({ doc: z5.string(), line: z5.number().int().positive().nullable(), excerpt: z5.string().nullable() });
2631
- count = z5.number().int().nonnegative();
2632
- scopeSchema = z5.object({
2633
- basis: z5.enum(["repository", "document", "document-link", "explicit"]),
2634
- directory: z5.string(),
2635
- candidates: z5.array(z5.string())
2636
- });
2637
- evidenceSchema = z5.discriminatedUnion("kind", [
2638
- z5.object({
2639
- kind: z5.literal("missing-path"),
2640
- claimed: z5.string(),
2641
- renamedTo: z5.string().nullable(),
2642
- deletedInCommit: commitSchema.nullable(),
2643
- everTracked: z5.boolean(),
2644
- parentDirExists: z5.boolean(),
2645
- resolvedPath: z5.string().optional(),
2646
- scope: scopeSchema.optional()
2647
- }),
2648
- z5.object({
2649
- kind: z5.literal("unmentioned-dir"),
2650
- dir: z5.string(),
2651
- sourceFileCount: count,
2652
- firstCommit: commitSchema.nullable(),
2653
- checkedDocs: z5.array(z5.string())
2654
- }),
2655
- z5.object({
2656
- kind: z5.literal("count-mismatch"),
2657
- claimed: count,
2658
- actual: count,
2659
- unit: z5.string(),
2660
- countedFrom: z5.string(),
2661
- members: z5.array(z5.string())
2662
- }),
2663
- z5.object({
2664
- kind: z5.literal("missing-script"),
2665
- scriptName: z5.string(),
2666
- invocation: z5.string(),
2667
- manifestsChecked: z5.array(z5.string()),
2668
- availableScripts: z5.array(z5.string()),
2669
- scope: scopeSchema.optional()
2670
- }),
2671
- z5.object({
2672
- kind: z5.literal("doc-behind-manifests"),
2673
- docLastCommit: commitSchema,
2674
- manifestCommits: z5.array(commitSchema.extend({ files: z5.array(z5.string()) })),
2675
- totalCommits: count,
2676
- matches: z5.array(z5.object({ dependency: z5.string(), manifest: z5.string(), before: z5.string().nullable(), after: z5.string().nullable(), line: z5.number().int().positive(), excerpt: z5.string() })).optional(),
2677
- matchingIncomplete: z5.boolean().optional()
2678
- }),
2679
- z5.object({
2680
- kind: z5.literal("decision-anchor"),
2681
- decisionId: z5.string(),
2682
- title: z5.string(),
2683
- changedFiles: z5.array(z5.string()),
2684
- refreshedHash: z5.string(),
2685
- provenance: z5.object({}).passthrough().optional()
2686
- })
2687
- ]);
2688
- findingSchema = z5.object({ message: z5.string(), anchor: anchorSchema, evidence: evidenceSchema });
2689
- issueSchema = findingSchema.extend({
2690
- type: z5.enum(["deleted-reference", "new-module", "stale-count", "dead-command"]),
2691
- confidence: z5.enum(["certain", "likely"])
2692
- });
2693
- advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z5.enum(["recheck", "informational"]).optional() });
2694
- checkResultSchema = z5.object({
2695
- issues: z5.array(issueSchema),
2696
- advisories: z5.array(advisorySchema),
2697
- suppressedAdvisories: z5.array(advisorySchema).optional(),
2698
- skipped: z5.array(z5.object({ check: z5.string(), reason: z5.string(), doc: z5.string().optional() }))
2699
- });
2700
- }
2701
- });
2702
-
2703
2815
  // src/audit/advisory-review.ts
2704
2816
  import fs10 from "fs/promises";
2705
- import { z as z6 } from "zod";
2817
+ import { z as z7 } from "zod";
2706
2818
  async function readRecord(root, id) {
2707
2819
  const raw = await readStoreJson(root, recordPath(id));
2708
2820
  if (raw === null) return null;
@@ -2848,41 +2960,41 @@ var init_advisory_review = __esm({
2848
2960
  init_decisions();
2849
2961
  init_drift2();
2850
2962
  git2 = async (root, ...args) => (await (["log", "ls-tree", "ls-files", "merge-base"].includes(args[0]) ? inspectionGit : execGit)(args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
2851
- hashSchema = z6.string().regex(/^[a-f0-9]{64}$/);
2852
- commitSchema2 = z6.string().regex(/^[a-f0-9]{40,64}$/);
2853
- evidenceSchema2 = z6.object({
2963
+ hashSchema = z7.string().regex(/^[a-f0-9]{64}$/);
2964
+ commitSchema2 = z7.string().regex(/^[a-f0-9]{40,64}$/);
2965
+ evidenceSchema2 = z7.object({
2854
2966
  head: commitSchema2,
2855
2967
  fingerprint: hashSchema,
2856
- scope: z6.array(z6.string()).min(1).max(1e3),
2857
- tree: z6.string().max(2 * 1024 * 1024),
2968
+ scope: z7.array(z7.string()).min(1).max(1e3),
2969
+ tree: z7.string().max(2 * 1024 * 1024),
2858
2970
  lastScopeCommit: commitSchema2,
2859
- paths: z6.array(z6.object({ path: z6.string(), exists: z6.boolean() })).max(1e3)
2971
+ paths: z7.array(z7.object({ path: z7.string(), exists: z7.boolean() })).max(1e3)
2860
2972
  });
2861
- dismissalReasonSchema = z6.enum(["no-dependency-claims", "unrelated-manifest-change"]);
2862
- eventSchema2 = z6.object({
2863
- at: z6.string().datetime(),
2864
- reviewer: z6.string().trim().min(1).max(200).optional(),
2865
- note: z6.string().trim().min(1).max(2500),
2866
- outcome: z6.enum(["addressed", "inapplicable", "deferred", "dismissed"]),
2973
+ dismissalReasonSchema = z7.enum(["no-dependency-claims", "unrelated-manifest-change"]);
2974
+ eventSchema2 = z7.object({
2975
+ at: z7.string().datetime(),
2976
+ reviewer: z7.string().trim().min(1).max(200).optional(),
2977
+ note: z7.string().trim().min(1).max(2500),
2978
+ outcome: z7.enum(["addressed", "inapplicable", "deferred", "dismissed"]),
2867
2979
  reasonCode: dismissalReasonSchema.optional(),
2868
2980
  finding: advisorySchema.refine((finding) => finding.evidence.kind !== "decision-anchor", "Decision findings use decision reviews"),
2869
2981
  evidence: evidenceSchema2
2870
2982
  });
2871
- recordSchema = z6.object({ version: z6.literal(1), id: hashSchema, events: z6.array(eventSchema2).min(1).max(200), digest: hashSchema });
2872
- reviewSummarySchema = z6.object({
2983
+ recordSchema = z7.object({ version: z7.literal(1), id: hashSchema, events: z7.array(eventSchema2).min(1).max(200), digest: hashSchema });
2984
+ reviewSummarySchema = z7.object({
2873
2985
  id: hashSchema,
2874
- status: z6.enum(["current", "reopened", "deferred", "unverified"]),
2875
- reason: z6.string(),
2876
- recordPath: z6.string().optional(),
2877
- reviewer: z6.string().optional(),
2878
- note: z6.string().optional(),
2879
- outcome: z6.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired", "dismissed"]).optional(),
2880
- reviewedHead: z6.string().optional()
2986
+ status: z7.enum(["current", "reopened", "deferred", "unverified"]),
2987
+ reason: z7.string(),
2988
+ recordPath: z7.string().optional(),
2989
+ reviewer: z7.string().optional(),
2990
+ note: z7.string().optional(),
2991
+ outcome: z7.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired", "dismissed"]).optional(),
2992
+ reviewedHead: z7.string().optional()
2881
2993
  });
2882
- advisoryReviewRequest = z6.object({
2883
- baselinePath: z6.string().min(1),
2994
+ advisoryReviewRequest = z7.object({
2995
+ baselinePath: z7.string().min(1),
2884
2996
  findingId: hashSchema,
2885
- action: z6.enum(["prepare", "addressed", "inapplicable", "deferred", "dismiss"]).default("prepare"),
2997
+ action: z7.enum(["prepare", "addressed", "inapplicable", "deferred", "dismiss"]).default("prepare"),
2886
2998
  reasonCode: dismissalReasonSchema.optional(),
2887
2999
  reviewer: eventSchema2.shape.reviewer.optional(),
2888
3000
  note: eventSchema2.shape.note.optional(),
@@ -2987,7 +3099,7 @@ var init_audit = __esm({
2987
3099
  import fs12 from "fs/promises";
2988
3100
  import path21 from "path";
2989
3101
  import { randomUUID as randomUUID2 } from "crypto";
2990
- import { z as z7 } from "zod";
3102
+ import { z as z8 } from "zod";
2991
3103
  function recheckable(finding) {
2992
3104
  return !("confidence" in finding) && finding.resolution === "recheck" && ["deleted-reference", "dead-command", "new-module"].includes(finding.type) && ["missing-path", "missing-script", "unmentioned-dir"].includes(finding.evidence.kind);
2993
3105
  }
@@ -3055,7 +3167,7 @@ async function withRepairSession(root, options, run, validate, cachedAudit) {
3055
3167
  }
3056
3168
  async function prepareOriginal(rootDir, checks, options, session) {
3057
3169
  const root = await fs12.realpath(rootDir);
3058
- const selected = z7.array(checkSchema).nonempty().parse(checks);
3170
+ const selected = z8.array(checkSchema).nonempty().parse(checks);
3059
3171
  const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
3060
3172
  if (!report) throw new Error("No context files found to prepare a repair.");
3061
3173
  if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
@@ -3196,42 +3308,42 @@ var init_repair = __esm({
3196
3308
  init_findings();
3197
3309
  init_advisory_review();
3198
3310
  init_findings();
3199
- count2 = z7.number().int().nonnegative();
3200
- reportSchema = z7.object({
3201
- version: z7.literal(1),
3202
- root: z7.string(),
3203
- gitAvailable: z7.literal(true),
3311
+ count2 = z8.number().int().nonnegative();
3312
+ reportSchema = z8.object({
3313
+ version: z8.literal(1),
3314
+ root: z8.string(),
3315
+ gitAvailable: z8.literal(true),
3204
3316
  headHash: commitSchema.shape.hash,
3205
- checksRun: z7.array(checkSchema).nonempty(),
3206
- docs: z7.array(z7.object({
3207
- path: z7.string().refine((value) => normalizeRepoPath(value) === value),
3208
- scope: z7.string().optional(),
3209
- kind: z7.enum(["instructions", "readme", "guide"]).optional(),
3317
+ checksRun: z8.array(checkSchema).nonempty(),
3318
+ docs: z8.array(z8.object({
3319
+ path: z8.string().refine((value) => normalizeRepoPath(value) === value),
3320
+ scope: z8.string().optional(),
3321
+ kind: z8.enum(["instructions", "readme", "guide"]).optional(),
3210
3322
  lastCommit: commitSchema.nullable(),
3211
- dirty: z7.boolean(),
3323
+ dirty: z8.boolean(),
3212
3324
  lineCount: count2
3213
3325
  })).nonempty(),
3214
- decisionsChecked: z7.boolean(),
3215
- clean: z7.boolean(),
3216
- excludedChecks: z7.array(z7.object({ check: checkSchema, doc: z7.string() })).optional(),
3217
- issues: z7.array(issueSchema),
3218
- advisories: z7.array(advisorySchema),
3219
- suppressedAdvisories: z7.array(advisorySchema).optional(),
3220
- skippedChecks: z7.array(z7.object({ check: z7.string(), reason: z7.string(), doc: z7.string().optional() }))
3326
+ decisionsChecked: z8.boolean(),
3327
+ clean: z8.boolean(),
3328
+ excludedChecks: z8.array(z8.object({ check: checkSchema, doc: z8.string() })).optional(),
3329
+ issues: z8.array(issueSchema),
3330
+ advisories: z8.array(advisorySchema),
3331
+ suppressedAdvisories: z8.array(advisorySchema).optional(),
3332
+ skippedChecks: z8.array(z8.object({ check: z8.string(), reason: z8.string(), doc: z8.string().optional() }))
3221
3333
  });
3222
- baselineSchema = z7.object({
3223
- kind: z7.literal("mason-audit-repair"),
3224
- version: z7.literal(1),
3225
- createdAt: z7.string().datetime(),
3334
+ baselineSchema = z8.object({
3335
+ kind: z8.literal("mason-audit-repair"),
3336
+ version: z8.literal(1),
3337
+ createdAt: z8.string().datetime(),
3226
3338
  report: reportSchema,
3227
- digest: z7.string().regex(/^[a-f0-9]{64}$/)
3339
+ digest: z8.string().regex(/^[a-f0-9]{64}$/)
3228
3340
  });
3229
- repairFindingSchema = z7.object({
3230
- id: z7.string(),
3231
- original: z7.union([issueSchema, advisorySchema]),
3232
- status: z7.enum(["resolved", "unresolved", "review-required", "unverified"]),
3233
- reason: z7.string(),
3234
- current: z7.union([issueSchema, advisorySchema]).optional(),
3341
+ repairFindingSchema = z8.object({
3342
+ id: z8.string(),
3343
+ original: z8.union([issueSchema, advisorySchema]),
3344
+ status: z8.enum(["resolved", "unresolved", "review-required", "unverified"]),
3345
+ reason: z8.string(),
3346
+ current: z8.union([issueSchema, advisorySchema]).optional(),
3235
3347
  review: reviewSummarySchema.optional()
3236
3348
  });
3237
3349
  }
@@ -3242,7 +3354,7 @@ import fs13 from "fs/promises";
3242
3354
  import path22 from "path";
3243
3355
  import { createHash as createHash3 } from "crypto";
3244
3356
  import fg7 from "fast-glob";
3245
- import { z as z8 } from "zod";
3357
+ import { z as z9 } from "zod";
3246
3358
  async function git3(root, ...args) {
3247
3359
  return profilePhase("automation.git", async () => (await execGit(args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
3248
3360
  }
@@ -3385,10 +3497,10 @@ var init_evidence = __esm({
3385
3497
  init_dead_command();
3386
3498
  init_storage();
3387
3499
  init_advisory_review();
3388
- engineVersion = true ? "0.19.1" : "development";
3500
+ engineVersion = true ? "0.19.2" : "development";
3389
3501
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
3390
3502
  content = readAuditInput;
3391
- cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
3503
+ cacheSchema = z9.object({ version: z9.literal(1), entries: z9.record(z9.object({ key: z9.string(), result: checkResultSchema })), digest: z9.string() });
3392
3504
  }
3393
3505
  });
3394
3506
 
@@ -3396,7 +3508,7 @@ var init_evidence = __esm({
3396
3508
  import os2 from "os";
3397
3509
  import fs14 from "fs/promises";
3398
3510
  import { randomUUID as randomUUID3 } from "crypto";
3399
- import { z as z9 } from "zod";
3511
+ import { z as z10 } from "zod";
3400
3512
  function parseExecution(raw) {
3401
3513
  try {
3402
3514
  return executionSchema.parse(raw);
@@ -3414,7 +3526,7 @@ function automationFailure(error) {
3414
3526
  codes.add(String(cause.code));
3415
3527
  cause = cause.cause;
3416
3528
  }
3417
- 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 z9.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";
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";
3418
3530
  return { code, message: message2, retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code), receiptRecorded: false };
3419
3531
  }
3420
3532
  function failureMessage(error) {
@@ -3543,41 +3655,103 @@ var init_execution = __esm({
3543
3655
  init_store();
3544
3656
  init_evidence();
3545
3657
  init_storage();
3546
- failureSchema = z9.object({
3547
- code: z9.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
3548
- message: z9.string(),
3549
- retryable: z9.boolean(),
3550
- receiptRecorded: z9.boolean()
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()
3551
3663
  });
3552
- attemptSchema = z9.object({
3553
- id: z9.string().regex(/^[a-zA-Z0-9_-]+$/),
3554
- lease: z9.literal(true).optional(),
3555
- event: z9.string(),
3556
- startedAt: z9.string(),
3557
- finishedAt: z9.string().optional(),
3558
- durationMs: z9.number().nonnegative().optional(),
3559
- pid: z9.number().int().positive(),
3560
- host: z9.string(),
3561
- status: z9.enum(["running", "completed", "failed", "unknown"]),
3562
- verificationStatus: z9.string().optional(),
3563
- reportPath: z9.string().optional(),
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(),
3564
3676
  failure: failureSchema.optional()
3565
3677
  });
3566
- executionSchema = z9.object({ version: z9.literal(1), attempts: z9.array(attemptSchema).max(160), discardedAttempts: z9.number().int().nonnegative().default(0) });
3567
- notificationsSchema = z9.object({ version: z9.literal(1), sessions: z9.record(z9.object({
3568
- signature: z9.string(),
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(),
3569
3681
  code: failureSchema.shape.code,
3570
- message: z9.string(),
3571
- count: z9.number().int().positive(),
3572
- lastAt: z9.string()
3682
+ message: z10.string(),
3683
+ count: z10.number().int().positive(),
3684
+ lastAt: z10.string()
3573
3685
  })) });
3574
3686
  leasePath = (directory, id) => `${directory}/executions/${id}.json`;
3575
3687
  }
3576
3688
  });
3577
3689
 
3690
+ // src/decisions/pending.ts
3691
+ var pending_exports = {};
3692
+ __export(pending_exports, {
3693
+ pendingKnowledge: () => pendingKnowledge
3694
+ });
3695
+ async function pendingKnowledge(root, now = Date.now()) {
3696
+ try {
3697
+ const [store, status] = await Promise.all([
3698
+ loadDecisionStore(root),
3699
+ execGit(
3700
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".mason/decisions"],
3701
+ { cwd: root, timeout: 1e4, maxBuffer: 1024 * 1024 }
3702
+ )
3703
+ ]);
3704
+ const changed = /* @__PURE__ */ new Set();
3705
+ const entries = status.stdout.split("\0");
3706
+ for (let i = 0; i < entries.length; i++) {
3707
+ const entry = entries[i];
3708
+ if (!entry) continue;
3709
+ const file = entry.slice(3);
3710
+ if (/^\.mason\/decisions\/[^/]+\.json$/.test(file)) changed.add(file);
3711
+ if (/[RC]/.test(entry.slice(0, 2))) i++;
3712
+ }
3713
+ const records = new Map(store.records.map((record) => [`.mason/decisions/${record.id}.json`, record]));
3714
+ const age = (at) => {
3715
+ const time = at ? Date.parse(at) : NaN;
3716
+ return Number.isFinite(time) ? `${Math.max(0, Math.floor((now - time) / 864e5))} days` : "unknown age";
3717
+ };
3718
+ const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ");
3719
+ const list = (items) => items.slice(0, 5).map(safe).join("; ") + (items.length > 5 ? `; ${items.length - 5} more` : "");
3720
+ const proposals = store.records.filter((record) => record.status === "active" && decisionApproval(record) === "proposed").map((record) => {
3721
+ const history = record.version === 2 ? record.history : [];
3722
+ let lastReview = -1;
3723
+ history.forEach((event, index2) => {
3724
+ if (event.kind === "accepted" || event.kind === "reaffirmed") lastReview = index2;
3725
+ });
3726
+ const since = history.slice(lastReview + 1).find((event) => event.kind === "created" || event.kind === "revised")?.at ?? record.createdAt;
3727
+ return { record, since };
3728
+ }).sort((a, b) => {
3729
+ const timestamp = (value) => Number.isFinite(Date.parse(value)) ? Date.parse(value) : Infinity;
3730
+ return timestamp(a.since) - timestamp(b.since) || a.record.id.localeCompare(b.record.id);
3731
+ });
3732
+ const lines = [];
3733
+ if (changed.size) lines.push(`${changed.size} uncommitted decision record(s): ${list([...changed].sort().map((file) => `${file} (saved-record age: ${age(records.get(file)?.updatedAt)})`))}.`);
3734
+ if (proposals.length) lines.push(`${proposals.length} proposal(s) awaiting review (oldest first): ${list(proposals.map(({ record, since }) => `.mason/decisions/${record.id}.json (${age(since)})`))}.`);
3735
+ if (store.diagnostics.length) lines.push(`${store.diagnostics.length} decision record(s) could not be read; pending knowledge summary is incomplete.`);
3736
+ return lines.length ? `Mason pending knowledge:
3737
+ ${lines.join("\n")}
3738
+ Informational only; review and commit through the normal project workflow. No approval is implied.` : null;
3739
+ } catch {
3740
+ return "Mason pending knowledge: unavailable; decision records or Git status could not be read.";
3741
+ }
3742
+ }
3743
+ var init_pending = __esm({
3744
+ "src/decisions/pending.ts"() {
3745
+ "use strict";
3746
+ init_git_read();
3747
+ init_decisions();
3748
+ init_provenance();
3749
+ }
3750
+ });
3751
+
3578
3752
  // src/automation/runtime.ts
3579
3753
  import { randomUUID as randomUUID4 } from "crypto";
3580
- import { z as z10 } from "zod";
3754
+ import { z as z11 } from "zod";
3581
3755
  async function observeReadOnlyTool(dir, event) {
3582
3756
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
3583
3757
  await withLock(ws.root, ws.directory, async () => {
@@ -3799,6 +3973,19 @@ async function automate(dir, event) {
3799
3973
  const state = await loadState(ws);
3800
3974
  if (state.analysis?.id !== result.analysis.id) throw new Error("Automation evidence changed during publication; retry against the current inputs.");
3801
3975
  const report = structuredClone(result.analysis.report);
3976
+ if (!state.notifications) {
3977
+ let baseline = { findings: report.findings, head: report.head };
3978
+ if (state.latest) {
3979
+ try {
3980
+ const previous = z11.object({ findings: z11.array(repairFindingSchema), head: z11.string() }).safeParse(await readStoreJson(ws.root, state.latest));
3981
+ if (!previous.success) throw new Error("Unsupported saved report");
3982
+ baseline = previous.data;
3983
+ } catch {
3984
+ report.diagnostics.push("The saved notification baseline could not be read; initialized from the current audit. Original repair evidence is retained. Run an explicit check for all outstanding findings.");
3985
+ }
3986
+ }
3987
+ state.notifications = createNotificationState(baseline.findings, baseline.head);
3988
+ }
3802
3989
  if (result.shared) report.checks = { ...report.checks, ran: [], reused: [.../* @__PURE__ */ new Set([...report.checks.ran, ...report.checks.reused])], shared: true };
3803
3990
  report.reportPath = ws.directory + "/checks/" + randomUUID4() + ".json";
3804
3991
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -3811,12 +3998,8 @@ async function automate(dir, event) {
3811
3998
  if (Object.keys(state.sessions).length >= 128) throw new Error("Too many sessions with unfinished tool calls; original evidence was retained.");
3812
3999
  state.sessions[key] = {
3813
4000
  host: event.host,
3814
- seen: null,
3815
- continued: false,
3816
- initialIssues: report.findings.filter((f) => f.status === "unresolved").map((f) => f.id),
3817
- initialDocs: inputs.docs,
4001
+ notifiedDiagnostics: [],
3818
4002
  lastUsed: now,
3819
- mutationObserved: false,
3820
4003
  pending: {},
3821
4004
  coverageGaps: [],
3822
4005
  events: {}
@@ -3831,7 +4014,6 @@ async function automate(dir, event) {
3831
4014
  session.pending[event.toolId] = inputs.fingerprint;
3832
4015
  }
3833
4016
  if (event.event === "after_tool" && event.mutating) {
3834
- session.mutationObserved = true;
3835
4017
  if (!event.toolId || !session.pending[event.toolId]) {
3836
4018
  const gap = "A tool completed without an observed matching pre-tool capture in this checkout; pre-edit coverage is unknown. The tool may have changed checkouts; captures are kept separately for each worktree.";
3837
4019
  if (!session.coverageGaps.includes(gap)) session.coverageGaps.push(gap);
@@ -3842,31 +4024,39 @@ async function automate(dir, event) {
3842
4024
  if (session.coverageGaps.length && report.status !== "unavailable") report.status = "incomplete";
3843
4025
  report.capture = report.status !== "unavailable" && !session.coverageGaps.length && (session.events.session_start || session.events.before_tool) ? "observed" : "unknown";
3844
4026
  }
3845
- const signature = hash([report.status, report.findings, report.diagnostics, report.checks.skipped]);
3846
- const relevant = report.findings.some((f) => f.status === "unresolved" && session && (!session.initialIssues.includes(f.id) || session.initialDocs[f.original.anchor.doc] !== inputs.docs[f.original.anchor.doc]));
3847
- const continueOnce = event.event === "task_end" && !!session?.mutationObserved && relevant && !session.continued && !event.stopHookActive;
3848
- const notify = !session || newSession || signature !== session.seen || continueOnce;
3849
- if (session) {
3850
- session.seen = signature;
3851
- if (continueOnce) session.continued = true;
3852
- }
3853
- const persistReport = !state.latest || state.fingerprint !== inputs.fingerprint || notify || event.event === "task_end";
4027
+ const selected = session && event.event === "task_end" ? completionFindings(state.notifications, report.findings) : [];
4028
+ state.agentNotifications ??= { ...structuredClone(state.notifications), delivered: {} };
4029
+ const feedback = session && ["turn_start", "after_tool"].includes(event.event) ? completionFindings(state.agentNotifications, report.findings) : [];
4030
+ const diagnostics = session ? report.diagnostics.filter((d) => !session.notifiedDiagnostics.includes(d)).slice(0, 2) : [];
4031
+ if (session) session.notifiedDiagnostics = [.../* @__PURE__ */ new Set([
4032
+ ...session.notifiedDiagnostics.filter((d) => report.diagnostics.includes(d)),
4033
+ ...diagnostics
4034
+ ])];
4035
+ const message2 = !session ? summarize(report) : [
4036
+ completionSummary(selected, report.reportPath),
4037
+ completionSummary(feedback, report.reportPath),
4038
+ ...diagnostics.length ? [`Mason: verification limited. ${diagnostics.slice(0, 2).map(cleanText).join(" ")} Evidence: ${report.reportPath}.`] : []
4039
+ ].filter(Boolean).join("\n") || null;
4040
+ const notify = message2 !== null;
4041
+ const persistReport = !state.latest || state.fingerprint !== inputs.fingerprint || notify || newSession || event.event === "task_end";
3854
4042
  if (!persistReport) report.reportPath = state.latest;
3855
4043
  state.updatedAt = now;
3856
4044
  state.fingerprint = inputs.fingerprint;
3857
4045
  state.latest = report.reportPath;
3858
4046
  if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
3859
4047
  await writeStoreJson(ws.root, ws.directory + "/state.json", state);
3860
- return { report, message: notify ? summarize(report) : null, continueOnce };
4048
+ return { report, message: message2 };
3861
4049
  }));
3862
4050
  }));
3863
4051
  }
3864
4052
  async function automationStatus(dir) {
4053
+ const { pendingKnowledge: pendingKnowledge2 } = await Promise.resolve().then(() => (init_pending(), pending_exports));
3865
4054
  const ws = await profilePhase("automation.workspace", () => workspace(dir));
4055
+ const knowledge = await pendingKnowledge2(ws.root);
3866
4056
  const execution = await executionStatus(ws.root, ws.directory);
3867
4057
  const notifications = await failureNotifications(ws.root, ws.directory);
3868
4058
  const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
3869
- if (raw === null) return { version: 1, status: execution.status === "not-observed" ? "not-observed" : "unavailable", root: ws.root, branch: ws.branch, baselinePaths: [], hosts: {}, execution, notifications };
4059
+ if (raw === null) return { version: 1, status: execution.status === "not-observed" ? "not-observed" : "unavailable", root: ws.root, branch: ws.branch, baselinePaths: [], hosts: {}, execution, notifications, knowledge };
3870
4060
  const state = parseState(raw);
3871
4061
  if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) throw new Error("Automation state belongs to another workspace.");
3872
4062
  const inputs = await readInputs(ws.root);
@@ -3889,6 +4079,8 @@ async function automationStatus(dir) {
3889
4079
  hosts: hosts2,
3890
4080
  execution,
3891
4081
  notifications,
4082
+ knowledge,
4083
+ notificationBaselineHead: state.notifications?.baselineHead ?? null,
3892
4084
  note: "Observed events do not prove all tool paths are intercepted. Run check to verify the retained evidence."
3893
4085
  };
3894
4086
  }
@@ -3896,6 +4088,7 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
3896
4088
  var init_runtime = __esm({
3897
4089
  "src/automation/runtime.ts"() {
3898
4090
  "use strict";
4091
+ init_notifications();
3899
4092
  init_profile();
3900
4093
  init_inspection();
3901
4094
  init_execution();
@@ -3907,34 +4100,34 @@ var init_runtime = __esm({
3907
4100
  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.";
3908
4101
  priority = { resolved: 0, "review-required": 1, unresolved: 2, unverified: 3 };
3909
4102
  cleanText = (text2) => text2.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
3910
- analysisSchema = z10.object({
3911
- id: z10.string(),
3912
- fingerprint: z10.string(),
3913
- completedAt: z10.number(),
3914
- baselineDigest: z10.string(),
4103
+ analysisSchema = z11.object({
4104
+ id: z11.string(),
4105
+ fingerprint: z11.string(),
4106
+ completedAt: z11.number(),
4107
+ baselineDigest: z11.string(),
3915
4108
  currentAudit: reportSchema.nullable().optional(),
3916
- report: z10.object({
3917
- version: z10.literal(1),
3918
- status: z10.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
3919
- root: z10.string(),
3920
- branch: z10.string(),
3921
- head: z10.string(),
3922
- baselinePaths: z10.array(z10.string()),
3923
- reportPath: z10.string(),
3924
- findings: z10.array(repairFindingSchema),
3925
- diagnostics: z10.array(z10.string()),
3926
- checks: z10.object({
3927
- ran: z10.array(z10.string()),
3928
- reused: z10.array(z10.string()),
3929
- skipped: z10.array(z10.object({ check: z10.string(), reason: z10.string(), doc: z10.string().optional() })),
3930
- auditReused: z10.boolean().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()
3931
4124
  }),
3932
- counts: z10.object({ resolved: z10.number(), unresolved: z10.number(), "review-required": z10.number(), unverified: z10.number() }),
3933
- capture: z10.enum(["observed", "unknown"]),
3934
- scope: z10.string()
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()
3935
4128
  })
3936
4129
  });
3937
- sharedSchema = z10.object({ analysis: analysisSchema, digest: z10.string() });
4130
+ sharedSchema = z11.object({ analysis: analysisSchema, digest: z11.string() });
3938
4131
  baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
3939
4132
  state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
3940
4133
  )));
@@ -3975,7 +4168,7 @@ var init_hook_root = __esm({
3975
4168
  });
3976
4169
 
3977
4170
  // src/setup/model.ts
3978
- import { z as z11 } from "zod";
4171
+ import { z as z12 } from "zod";
3979
4172
  import fs16 from "fs/promises";
3980
4173
  import path24 from "path";
3981
4174
  async function loadSetup(root) {
@@ -4022,29 +4215,29 @@ var init_model = __esm({
4022
4215
  "use strict";
4023
4216
  init_evidence();
4024
4217
  init_storage();
4025
- setupHostSchema = z11.object({
4026
- configuredVersion: z11.string(),
4027
- revision: z11.string().uuid(),
4028
- fingerprint: z11.string(),
4029
- mcpFingerprint: z11.string(),
4030
- instructions: z11.array(z11.string())
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())
4031
4224
  });
4032
- setupSchema = z11.object({ version: z11.literal(2), hosts: z11.object({
4225
+ setupSchema = z12.object({ version: z12.literal(2), hosts: z12.object({
4033
4226
  codex: setupHostSchema.optional(),
4034
4227
  claude: setupHostSchema.optional()
4035
4228
  }) });
4036
4229
  SETUP_PATH = ".mason/local/setup.json";
4037
4230
  INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
4038
- inactiveHostsSchema = z11.object({ version: z11.literal(1), hosts: z11.array(z11.enum(["claude", "codex"])) });
4039
- receiptSchema = z11.object({
4040
- version: z11.literal(1),
4041
- host: z11.enum(["codex", "claude"]),
4042
- status: z11.enum(["installing", "configured"]),
4043
- initialReportPath: z11.string(),
4044
- initialBaselinePaths: z11.array(z11.string()),
4045
- root: z11.string(),
4046
- revision: z11.string().uuid(),
4047
- configuredAt: z11.string().optional()
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()
4048
4241
  });
4049
4242
  }
4050
4243
  });
@@ -4105,7 +4298,7 @@ var init_launcher = __esm({
4105
4298
  "use strict";
4106
4299
  init_evidence();
4107
4300
  init_model();
4108
- executingVersion = () => true ? "0.19.1" : "development";
4301
+ executingVersion = () => true ? "0.19.2" : "development";
4109
4302
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
4110
4303
  hookCommand = (host) => `mason --setup-host ${host} auto`;
4111
4304
  }
@@ -4119,7 +4312,7 @@ __export(observations_exports, {
4119
4312
  readObservation: () => readObservation
4120
4313
  });
4121
4314
  import fs18 from "fs/promises";
4122
- import { z as z12 } from "zod";
4315
+ import { z as z13 } from "zod";
4123
4316
  function observationPath(directory, host, revision, version = executingVersion()) {
4124
4317
  return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
4125
4318
  }
@@ -4190,26 +4383,26 @@ var init_observations = __esm({
4190
4383
  init_storage();
4191
4384
  init_model();
4192
4385
  init_launcher();
4193
- observationSchema = z12.object({
4194
- version: z12.literal(1),
4195
- masonVersion: z12.string(),
4196
- root: z12.string(),
4197
- revision: z12.string(),
4198
- host: z12.enum(["codex", "claude"]),
4199
- contextCalls: z12.number().int().nonnegative(),
4200
- lastContextAt: z12.string().optional(),
4201
- sessions: z12.record(z12.object({
4202
- events: z12.array(z12.enum(events)),
4203
- at: z12.string(),
4204
- verificationStatus: z12.string().optional(),
4205
- reportPath: z12.string().optional()
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()
4206
4399
  }))
4207
4400
  });
4208
4401
  }
4209
4402
  });
4210
4403
 
4211
4404
  // src/automation/adapters.ts
4212
- import { z as z13 } from "zod";
4405
+ import { z as z14 } from "zod";
4213
4406
  function normalizeHook(host, raw) {
4214
4407
  hostSchema.parse(host);
4215
4408
  const input2 = inputSchema.parse(raw);
@@ -4219,8 +4412,7 @@ function normalizeHook(host, raw) {
4219
4412
  host,
4220
4413
  sessionId: input2.session_id,
4221
4414
  toolId: input2.tool_use_id,
4222
- mutating: !!input2.tool_name && !readOnly,
4223
- stopHookActive: input2.stop_hook_active || input2.permission_mode === "plan"
4415
+ mutating: !!input2.tool_name && !readOnly
4224
4416
  } };
4225
4417
  }
4226
4418
  async function runAutomationHook(host, stdin, options = {}) {
@@ -4258,7 +4450,7 @@ async function runAutomationHook(host, stdin, options = {}) {
4258
4450
  if (warning) result.message = [result.message, warning].filter(Boolean).join("\n");
4259
4451
  if (!result.message) return null;
4260
4452
  if (name === "Stop") {
4261
- return result.continueOnce ? { decision: "block", reason: result.message } : { systemMessage: result.message };
4453
+ return { systemMessage: result.message };
4262
4454
  }
4263
4455
  return { hookSpecificOutput: { hookEventName: name, additionalContext: result.message } };
4264
4456
  } catch (error) {
@@ -4292,15 +4484,15 @@ var init_adapters = __esm({
4292
4484
  init_runtime();
4293
4485
  init_store();
4294
4486
  init_hook_root();
4295
- inputSchema = z13.object({
4296
- cwd: z13.string().min(1),
4297
- session_id: z13.string().min(1).max(500),
4298
- hook_event_name: z13.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
4299
- tool_name: z13.string().optional(),
4300
- tool_use_id: z13.string().max(500).optional(),
4301
- tool_input: z13.unknown().optional(),
4302
- stop_hook_active: z13.boolean().optional(),
4303
- permission_mode: z13.string().optional()
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()
4304
4496
  });
4305
4497
  lifecycle = {
4306
4498
  SessionStart: "session_start",
@@ -4389,7 +4581,7 @@ __export(ownership_exports, {
4389
4581
  ownershipEdit: () => ownershipEdit,
4390
4582
  ownershipSchema: () => ownershipSchema
4391
4583
  });
4392
- import { z as z14 } from "zod";
4584
+ import { z as z15 } from "zod";
4393
4585
  import { parse } from "smol-toml";
4394
4586
  function blockSpan(text2, start, end) {
4395
4587
  const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
@@ -4440,30 +4632,30 @@ var init_ownership = __esm({
4440
4632
  ["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
4441
4633
  ["# mason:mcp:start", "# mason:mcp:end"]
4442
4634
  ];
4443
- fileSchema = z14.object({
4444
- created: z14.boolean(),
4445
- initialHash: z14.string(),
4446
- blocks: z14.record(z14.string()),
4447
- mcpHash: z14.string().optional(),
4448
- hookCommands: z14.array(z14.string()).optional(),
4449
- hooks: z14.record(z14.array(z14.string())).optional()
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()
4450
4642
  });
4451
- ownershipSchema = z14.object({ version: z14.literal(1), files: z14.record(fileSchema) });
4643
+ ownershipSchema = z15.object({ version: z15.literal(1), files: z15.record(fileSchema) });
4452
4644
  }
4453
4645
  });
4454
4646
 
4455
4647
  // src/mcp/init.ts
4456
- import { z as z15 } from "zod";
4648
+ import { z as z16 } from "zod";
4457
4649
  var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_PLAYBOOK, MAP_PLAYBOOK;
4458
4650
  var init_init = __esm({
4459
4651
  "src/mcp/init.ts"() {
4460
4652
  "use strict";
4461
4653
  init_storage();
4462
4654
  init_files2();
4463
- markerSchema = z15.object({
4464
- version: z15.literal(1),
4465
- initializedAt: z15.string(),
4466
- features: z15.object({ confluence: z15.boolean().optional() }).passthrough().optional()
4655
+ markerSchema = z16.object({
4656
+ version: z16.literal(1),
4657
+ initializedAt: z16.string(),
4658
+ features: z16.object({ confluence: z16.boolean().optional() }).passthrough().optional()
4467
4659
  }).passthrough();
4468
4660
  CLAUDE_MD_SECTION = `<!-- mason:start -->
4469
4661
  ## Mason project knowledge
@@ -4472,9 +4664,10 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
4472
4664
 
4473
4665
  - Task, bug, change request, or investigation of why the project works this way \u2192 \`get_context\` with the task text and known files: matching decisions, related tests, impact, and any available map entries.
4474
4666
  - Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
4475
- - Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` before finishing. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
4667
+ - Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` as soon as the lesson is supported by evidence; do not wait for implementation, task completion, or merge. Record the established observation now and identify unimplemented remedies as tentative. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
4476
4668
  - Before saving, compare the lesson with matching \`get_context\` records and any pending proposal. If new evidence changes the same lesson's assumptions, scope, or recommended action, revise it with \`save_decision(id: existingId)\`, including when it is accepted. Preserve supported rationale and sources, replace obsolete instructions, and add the new evidence. Create a separate record for a genuinely distinct lesson; skip unchanged restatements. Revision preserves history and leaves the accepted version operative until authorized review.
4477
4669
  - Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, stale or unknown map verification requires a fresh evidence review, and a recorded failed verdict means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
4670
+ - Asked to review pending project knowledge \u2192 \`mason_automation(action: "status")\` lists pending proposals and uncommitted records. For ordinary tasks, inspect matching proposals through \`get_context\`; do not request acceptance merely because a proposal exists.
4478
4671
  - Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
4479
4672
  - For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
4480
4673
  - \`mason_init\` returns documentation audit and committed-diff review results, plus a short setup guide. Pass \`evidence\` with local CI manifest paths to include test and analysis results; the CLI equivalent is \`mason-review --evidence <manifest>\`. State skipped, unavailable, stale, or unknown checks explicitly. Related accepted decisions identify review context, not proven violations.
@@ -4498,7 +4691,7 @@ ${CLAUDE_MD_SECTION}`;
4498
4691
 
4499
4692
  1. Explain the actionable findings with their source evidence. Separate audit issues, advisories, and skipped checks. The review covers committed changes from the merge base to HEAD; workingTree paths are not included in that review. An unavailable or empty check is not proof that the project is correct. Use the CLI for full output if a summary is truncated.
4500
4693
  2. Address findings within the user's requested scope. Setup alone authorizes Mason host configuration, hooks, instructions, and ignore rules; rewriting existing project claims requires repair scope. If repair is authorized, call \`mason_repair(dir, action: "prepare")\` before editing; it saves the full original findings even when this summary is truncated. Inspect relevant source, make grounded edits, then call \`mason_repair(dir, action: "verify", baselinePath)\` with that same baseline, including after any final doc commit. Report resolved, unresolved, review-required, unverified, and new findings. Keep suppressed advisories visible even when setup has already dirtied a doc. Do not invent a decision just to populate the store.
4501
- 3. When the task reveals a real lesson or constraint, compare matching \`get_context\` records first. If its assumptions, scope, or recommended action changed, revise that record using \`save_decision(id: existingId)\`; create a separate record for a genuinely distinct lesson and skip unchanged restatements. Include title, body, category, anchors, and known owner/source/actor information. Preserve supported rationale and sources, replace obsolete instructions, and add new evidence. Missing attribution can be added later. The tool writes a local proposal; editing it preserves revision history and requires a new acceptance, while any earlier accepted revision remains operative. An unchanged save never refreshes its evidence. When decision review is requested, \`review_decision\` prepares the record and code evidence before any authorized verdict. Review and commit records through the normal workflow. Retrieve it on the next relevant task with \`get_context(dir, task, files)\`.
4694
+ 3. Capture a supported lesson as soon as it is established; do not wait for implementation, task completion, or merge. Keep unimplemented remedies tentative. When the task reveals a real lesson or constraint, compare matching \`get_context\` records first. If its assumptions, scope, or recommended action changed, revise that record using \`save_decision(id: existingId)\`; create a separate record for a genuinely distinct lesson and skip unchanged restatements. Include title, body, category, anchors, and known owner/source/actor information. Preserve supported rationale and sources, replace obsolete instructions, and add new evidence. Missing attribution can be added later. The tool writes a local proposal; editing it preserves revision history and requires a new acceptance, while any earlier accepted revision remains operative. An unchanged save never refreshes its evidence. When decision review is requested, \`review_decision\` prepares the record and code evidence before any authorized verdict. Review and commit records through the normal workflow. Retrieve it on the next relevant task with \`get_context(dir, task, files)\`.
4502
4695
 
4503
4696
  ${ASSISTANT_SETUP}
4504
4697
 
@@ -4551,7 +4744,7 @@ __export(config_exports, {
4551
4744
  });
4552
4745
  import { isDeepStrictEqual } from "util";
4553
4746
  import { parse as parse2, stringify } from "smol-toml";
4554
- import { z as z16 } from "zod";
4747
+ import { z as z17 } from "zod";
4555
4748
  async function instructionEdits(root, host) {
4556
4749
  const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
4557
4750
  const existing = found.find((f) => f.text !== null);
@@ -4662,7 +4855,7 @@ var init_config = __esm({
4662
4855
  init_install();
4663
4856
  init_files2();
4664
4857
  init_launcher();
4665
- object = z16.record(z16.unknown());
4858
+ object = z17.record(z17.unknown());
4666
4859
  mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
4667
4860
  TOML_START = "# mason:mcp:start";
4668
4861
  TOML_END = "# mason:mcp:end";
@@ -4682,7 +4875,7 @@ __export(install_exports, {
4682
4875
  planAutomationInstall: () => planAutomationInstall,
4683
4876
  recordSchema: () => recordSchema2
4684
4877
  });
4685
- import { z as z17 } from "zod";
4878
+ import { z as z18 } from "zod";
4686
4879
  async function installationRecord(root) {
4687
4880
  return recordSchema2.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
4688
4881
  }
@@ -4763,9 +4956,9 @@ var init_install = __esm({
4763
4956
  init_store();
4764
4957
  init_files2();
4765
4958
  init_ownership();
4766
- groupSchema = z17.object({ hooks: z17.array(z17.object({ type: z17.string(), command: z17.string().optional() }).passthrough()) }).passthrough();
4767
- automationConfigSchema = z17.object({ hooks: z17.record(z17.array(groupSchema)).optional() }).passthrough();
4768
- recordSchema2 = z17.object({ version: z17.literal(1), hosts: z17.record(z17.object({ command: z17.string() })) });
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() })) });
4769
4962
  AUTOMATION_PATH = ".mason/local/automation.json";
4770
4963
  configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
4771
4964
  }
@@ -4782,7 +4975,7 @@ import fs20 from "fs/promises";
4782
4975
  import path26 from "path";
4783
4976
  import { parse as parse3, stringify as stringify2 } from "smol-toml";
4784
4977
  import fg8 from "fast-glob";
4785
- import { z as z18 } from "zod";
4978
+ import { z as z19 } from "zod";
4786
4979
  async function planTeardown(root, selected) {
4787
4980
  const edits = [], diagnostics = [];
4788
4981
  const inputs = /* @__PURE__ */ new Map();
@@ -4938,7 +5131,7 @@ async function planTeardown(root, selected) {
4938
5131
  if (!remainingHost) await attempt(".mason/local/project.json", async () => {
4939
5132
  const before = await read(".mason/local/project.json");
4940
5133
  if (before === null) return;
4941
- z18.object({ version: z18.literal(1), initializedAt: z18.string() }).strict().parse(JSON.parse(before));
5134
+ z19.object({ version: z19.literal(1), initializedAt: z19.string() }).strict().parse(JSON.parse(before));
4942
5135
  cleanup.push({ path: ".mason/local/project.json", before, after: null });
4943
5136
  });
4944
5137
  if (!diagnostics.length) {
@@ -5019,7 +5212,7 @@ var init_teardown = __esm({
5019
5212
  init_model();
5020
5213
  init_ownership();
5021
5214
  hosts = ["codex", "claude"];
5022
- object2 = z18.record(z18.unknown());
5215
+ object2 = z19.record(z19.unknown());
5023
5216
  json = (value) => JSON.stringify(value, null, 2) + "\n";
5024
5217
  }
5025
5218
  });
@@ -5146,7 +5339,7 @@ var init_types2 = __esm({
5146
5339
  });
5147
5340
 
5148
5341
  // src/review/evidence/vitest.ts
5149
- import { z as z19 } from "zod";
5342
+ import { z as z20 } from "zod";
5150
5343
  function parseVitest(raw, sourceRoot) {
5151
5344
  const report = schema.parse(raw);
5152
5345
  const findings = [], diagnostics = [];
@@ -5195,24 +5388,24 @@ var init_vitest = __esm({
5195
5388
  "use strict";
5196
5389
  init_paths2();
5197
5390
  init_types2();
5198
- count3 = z19.number().int().nonnegative();
5199
- schema = z19.object({
5200
- success: z19.boolean(),
5391
+ count3 = z20.number().int().nonnegative();
5392
+ schema = z20.object({
5393
+ success: z20.boolean(),
5201
5394
  numTotalTests: count3,
5202
5395
  numPassedTests: count3,
5203
5396
  numFailedTests: count3,
5204
5397
  numPendingTests: count3,
5205
5398
  numTodoTests: count3,
5206
5399
  numFailedTestSuites: count3,
5207
- testResults: z19.array(z19.object({
5208
- name: z19.string().min(1),
5209
- status: z19.enum(["passed", "failed"]),
5210
- message: z19.string().optional(),
5211
- assertionResults: z19.array(z19.object({
5212
- fullName: z19.string(),
5213
- status: z19.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
5214
- failureMessages: z19.array(z19.string()).nullable().optional(),
5215
- location: z19.object({ line: count3, column: count3 }).nullable().optional()
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()
5216
5409
  }))
5217
5410
  }))
5218
5411
  });
@@ -5220,7 +5413,7 @@ var init_vitest = __esm({
5220
5413
  });
5221
5414
 
5222
5415
  // src/review/evidence/sarif.ts
5223
- import { z as z20 } from "zod";
5416
+ import { z as z21 } from "zod";
5224
5417
  function parseSarif(raw, sourceRoot) {
5225
5418
  const report = schema2.parse(raw), findings = [], diagnostics = [];
5226
5419
  let active = 0, suppressed = 0, absent = 0, unresolved = 0;
@@ -5311,29 +5504,29 @@ var init_sarif = __esm({
5311
5504
  "use strict";
5312
5505
  init_paths2();
5313
5506
  init_types2();
5314
- index = z20.number().int().nonnegative();
5315
- artifact = z20.object({ uri: z20.string().optional(), uriBaseId: z20.string().optional(), index: index.optional() });
5316
- message = z20.object({ text: z20.string().optional(), markdown: z20.string().optional(), id: z20.string().optional(), arguments: z20.array(z20.string()).optional() });
5317
- level = z20.enum(["error", "warning", "note", "none"]);
5318
- location = z20.object({ physicalLocation: z20.object({ artifactLocation: artifact.optional(), region: z20.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
5319
- schema2 = z20.object({
5320
- version: z20.literal("2.1.0"),
5321
- runs: z20.array(z20.object({
5322
- tool: z20.object({ driver: z20.object({ name: z20.string().min(1), rules: z20.array(z20.object({ id: z20.string(), defaultConfiguration: z20.object({ level: level.optional() }).optional(), messageStrings: z20.record(message).optional() })).optional(), globalMessageStrings: z20.record(message).optional() }) }),
5323
- invocations: z20.array(z20.object({ executionSuccessful: z20.boolean(), commandLine: z20.string().optional(), toolExecutionNotifications: z20.array(z20.object({ level: level.optional(), message })).optional() })).optional(),
5324
- versionControlProvenance: z20.array(z20.object({ revisionId: z20.string().optional() })).optional(),
5325
- originalUriBaseIds: z20.record(artifact).optional(),
5326
- artifacts: z20.array(z20.object({ location: artifact.optional() })).optional(),
5327
- results: z20.array(z20.object({
5328
- ruleId: z20.string().optional(),
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(),
5329
5522
  ruleIndex: index.optional(),
5330
5523
  message,
5331
5524
  level: level.optional(),
5332
- kind: z20.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
5333
- baselineState: z20.enum(["new", "unchanged", "updated", "absent"]).optional(),
5334
- suppressions: z20.array(z20.object({ kind: z20.enum(["inSource", "external"]), status: z20.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
5335
- locations: z20.array(location).optional(),
5336
- relatedLocations: z20.array(location).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()
5337
5530
  })).optional()
5338
5531
  }))
5339
5532
  });
@@ -5341,7 +5534,7 @@ var init_sarif = __esm({
5341
5534
  });
5342
5535
 
5343
5536
  // src/review/evidence/check.ts
5344
- import { z as z21 } from "zod";
5537
+ import { z as z22 } from "zod";
5345
5538
  function parseCheck(raw, sourceRoot) {
5346
5539
  const report = schema3.parse(raw);
5347
5540
  const diagnostics = [], findings = [];
@@ -5381,17 +5574,17 @@ var init_check = __esm({
5381
5574
  "use strict";
5382
5575
  init_paths2();
5383
5576
  init_types2();
5384
- schema3 = z21.object({
5385
- version: z21.literal(1),
5386
- commit: z21.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
5387
- results: z21.array(z21.object({
5388
- name: z21.string().min(1).max(1e3),
5389
- status: z21.enum(["passed", "failed", "skipped"]),
5390
- message: z21.string().max(1e5).optional(),
5391
- locations: z21.array(z21.object({
5392
- file: z21.string().min(1).max(4e3),
5393
- line: z21.number().int().positive().optional(),
5394
- column: z21.number().int().positive().optional()
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()
5395
5588
  })).max(100).default([])
5396
5589
  })).max(1e5)
5397
5590
  });
@@ -5400,7 +5593,7 @@ var init_check = __esm({
5400
5593
 
5401
5594
  // src/review/evidence.ts
5402
5595
  import path27 from "path";
5403
- import { z as z22 } from "zod";
5596
+ import { z as z23 } from "zod";
5404
5597
  function relativeArtifact(root, file) {
5405
5598
  const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
5406
5599
  const normalized = normalizeRepoPath(relative);
@@ -5441,7 +5634,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
5441
5634
  let manifest, raw;
5442
5635
  try {
5443
5636
  manifest = relativeArtifact(root, supplied);
5444
- raw = z22.object({ version: z22.literal(1), checks: z22.array(z22.unknown()) }).parse(await readStoreJson(root, manifest));
5637
+ raw = z23.object({ version: z23.literal(1), checks: z23.array(z23.unknown()) }).parse(await readStoreJson(root, manifest));
5445
5638
  } catch (error) {
5446
5639
  output.diagnostics.push(`${supplied}: ${String(error)}`);
5447
5640
  continue;
@@ -5596,25 +5789,25 @@ var init_evidence2 = __esm({
5596
5789
  init_sarif();
5597
5790
  init_check();
5598
5791
  init_types2();
5599
- checkSchema2 = z22.object({
5600
- id: z22.string().min(1).max(100),
5601
- kind: z22.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
5602
- tool: z22.string().min(1).max(200),
5603
- command: z22.string().min(1).max(2e3),
5604
- commit: z22.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
5605
- workingTreeClean: z22.boolean().optional(),
5606
- source: z22.string().max(2e3).optional(),
5607
- sourceRoot: z22.string().min(1).max(4e3).optional(),
5608
- status: z22.enum(["completed", "skipped", "unavailable"]).default("completed"),
5609
- reason: z22.string().min(1).max(2e3).optional(),
5610
- exitCode: z22.number().int().nullable().optional(),
5611
- environment: z22.object({
5612
- toolVersion: z22.string().min(1).max(200),
5613
- runtime: z22.string().min(1).max(200).optional(),
5614
- platform: z22.string().min(1).max(200).optional(),
5615
- configuration: z22.array(z22.string().min(1).max(4e3)).max(100).optional()
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()
5616
5809
  }).optional(),
5617
- report: z22.object({ format: z22.enum(["vitest-json", "sarif", "mason-check-json"]), path: z22.string().min(1).max(4e3) }).optional()
5810
+ report: z23.object({ format: z23.enum(["vitest-json", "sarif", "mason-check-json"]), path: z23.string().min(1).max(4e3) }).optional()
5618
5811
  });
5619
5812
  }
5620
5813
  });
@@ -6248,7 +6441,7 @@ async function runAutomationCli(argv2, stdin = "", io = defaultIo, options = {})
6248
6441
  const { setupStatus: setupStatus2, summarizeActivation: summarizeActivation2 } = await Promise.resolve().then(() => (init_status(), status_exports));
6249
6442
  const setup = await setupStatus2(dir);
6250
6443
  const result = { ...await automationStatus(dir), configured: await installedAutomation(dir), setup };
6251
- io.out(values.json || !process.stdout.isTTY ? JSON.stringify(result, null, 2) : summarizeActivation2(setup));
6444
+ io.out(values.json || !process.stdout.isTTY ? JSON.stringify(result, null, 2) : [summarizeActivation2(setup), result.knowledge].filter(Boolean).join("\n\n"));
6252
6445
  return 0;
6253
6446
  }
6254
6447
  if (action !== "check") throw new Error("Unknown automation command: " + action);