mason-context 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.22.0 — 2026-10-12
6
+
7
+ - Separate CLI scopes: `mason init` prepares shared repository integration without activating it; `mason setup` activates compatible existing configuration using local state only. `mason teardown` now disconnects locally; use `--project` explicitly for shared removal. Initialization and teardown support read-only `--dry-run` previews. Compatible older instructions, supported legacy hooks and JSON formatting do not force shared rewrites; status reports optional template updates separately. MCP exposes matching `init`/`setup` modes. Setup no longer triggers background update checks. Migrate scripts that used setup to generate configuration to `mason init` followed by `mason setup`; scripts removing shared configuration must add `teardown --project`.
8
+
9
+ - Simplify project integration to MCP, reasoning instructions and decision hooks, prepared by `mason init` and activated locally by `mason setup`. Remove automatic documentation audits, committed-diff review, repair capture and statistics enrollment. Setup results expose a configuration receipt instead of audit findings/report paths; legacy receipt pointers and existing audit artifacts remain intact. Statistics are opt-in through `mason stats --enable`; existing preferences are unchanged. Run `mason init --host <host>` after upgrading to refresh the shorter shared project guidance, review and commit those changes, then run `mason setup --host <host>` locally.
10
+
11
+ - Separate lifecycle decision checks from documentation-repair automation. Hooks check current decision anchors without discovering document claims, creating repair baselines, or verifying old audit findings. Preserve paired edit observation, source-inspection and human-review boundaries, Stop deduplication, guidance delivery and explicit audit checks. Reuse the event/receipt store with a separate guidance report pointer; status and setup output label verification scope. Old baselines and assessments stay available to explicit checks.
12
+
13
+ - Consolidate documentation repair and advisory assessment on the existing CLI: remove the duplicate `mason_repair` and `review_advisory` MCP tools. Use `mason audit --prepare-repair`, `mason audit --verify-repair <baselinePath>`, and `mason audit review`. Update project instructions, work orders and hook notices to point to these commands. Run `mason init --host <host>` after upgrading to refresh shared instructions. Existing baselines, review records, hooks and decision review semantics remain supported.
14
+
15
+ - Remove managed architecture-map creation (`generate_snapshot_batch`, `save_partial_snapshot`, `reduce_snapshot`, and `mason_init` map mode) and general orientation (`full_analysis`, `analyze_project`, `get_code_samples`). Remove batch prompts, partial-store management, the orientation analyzer and sampler. Keep existing map reading, direct saving, drift and verification; direct saves merge supported updates and no longer consume or clear old partials. Missing maps return a short decision-context pointer. Refresh instructions use the agent's source tools and direct saving. Keep 0.21.0 for the old managed build workflow and run `mason init --host <host>` after upgrading to refresh shared project instructions.
16
+
17
+ - Remove Confluence publishing (`mason_set_confluence`, `export_to_confluence`) and its standalone LLM provider/configuration layer to focus maintenance on engineering reasoning. Remove the Anthropic/OpenAI SDK dependencies. Existing wiki pages, legacy feature settings, and user credentials are left untouched; Mason no longer reads or updates them. `mason_complete_init` no longer accepts `confluenceConfigured` or advertises Confluence status. Users needing wiki publishing should keep 0.21.0 until they migrate that workflow to a separate publisher.
18
+
19
+ - Deliver file-scoped decision knowledge through standard Claude hooks after `Read` and before `Edit`/`Write`, reusing the existing injector and session deduplication. Confine delivery to the active checkout, retain approval/freshness labels and audit diagnostics, and preserve the standalone PostToolUse contract. Shell commands and Codex remain outside direct lesson injection.
20
+ - Flatten hook commands to `mason install`, `mason config`, and `mason hook --host claude|codex`. Generate shorter hook handlers and migrate existing configurations without duplicates. Retain `mason auto …` aliases and the standalone decision injector for compatibility.
21
+
3
22
  ## 0.21.0 — 2026-10-10
4
23
 
5
24
  - Continue Claude once at Stop when outstanding decision drift overlaps observed session anchor edits. Require source inspection, preserve human approval boundaries, and keep repeat Stops and Codex advisory. Add local `mason auto config --stop-block off|drift` controls and status reporting. Include uncommitted anchor changes in drift findings for accepted and legacy decisions using Git baselines.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  # Mason
4
4
 
5
- ### Your agents are creating tech debt. Mason helps you prevent it.
5
+ ### Preserve why your code was built this way.
6
6
 
7
- **Remember why decisions were made · Catch outdated guidance · Find what else needs updating**
7
+ **Record the reason · Review the lesson · Surface applicable guidance · Reconsider it as code changes**
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/mason-context?style=flat-square&labelColor=334155&color=2563eb)](https://www.npmjs.com/package/mason-context)
10
10
  [![CI](https://img.shields.io/github/actions/workflow/status/adrianczuczka/mason/ci.yml?branch=main&style=flat-square&labelColor=334155&label=CI)](https://github.com/adrianczuczka/mason/actions/workflows/ci.yml)
@@ -18,6 +18,8 @@
18
18
 
19
19
  </div>
20
20
 
21
+ Mason preserves reviewed engineering reasoning with your repository, so future maintainers and coding agents can retrieve the evidence behind consequential decisions. Record workarounds, rejected approaches and constraints while their reasons are available; revisit them when the underlying code changes.
22
+
21
23
  ## Get started
22
24
 
23
25
  Install on macOS or Linux:
@@ -39,11 +41,12 @@ New standard standalone installs update automatically in the background. Updates
39
41
  Run this in your Git repository (open a new terminal if the installer asks):
40
42
 
41
43
  ```bash
42
- mason setup --host codex
44
+ mason init --host codex # Maintainer: prepare shared configuration for review/commit
45
+ mason setup --host codex # Each developer: activate locally
43
46
  # For Claude Code, use --host claude.
44
47
  ```
45
48
 
46
- Setup connects MCP, hooks, and project instructions. Review your host's trust settings, start a new session, and give your agent a normal task.
49
+ If the repository already has compatible Mason configuration, run only `mason setup`. Initialization prepares MCP, hooks and project instructions; setup leaves shared files untouched. Review your host's trust settings, start a new session, and give your agent a normal task.
47
50
 
48
51
  Check that Mason is being used:
49
52
 
@@ -71,7 +74,7 @@ The audit checks claims in README and agent instruction files throughout the rep
71
74
  | Old decisions lose their context. | Retrieving recorded rationale and review status, and flagging changes to the code they apply to. |
72
75
  | A repair gets interrupted. | Retaining the original findings and verifying them through the final documentation commit. |
73
76
 
74
- After resolving an incident or settling a constraint, ask your agent to record the reason with Mason. Later tasks can retrieve it. Proposals and accepted decisions stay distinct.
77
+ After resolving an incident or settling a constraint, ask your agent to record a sourced proposal with Mason. Review it before accepting it as project guidance. Later tasks can retrieve the operative accepted revision while proposed replacements remain distinct, and changes to referenced code can prompt inspection, revision or retirement.
75
78
 
76
79
  Mason complements tests, linters, and code review. Findings are evidence to inspect; unavailable checks stay explicit.
77
80
 
@@ -79,6 +82,8 @@ Mason complements tests, linters, and code review. Findings are evidence to insp
79
82
 
80
83
  Earlier read-only decision-retrieval evaluations scored **9.0/10 with Mason vs 7.0/10 without**. The initial ten-task patch comparison tied at **10/10 for both**. Improved patch outcomes remain to be demonstrated. [Results, methodology, and limitations](docs/benchmarks.md)
81
84
 
85
+ Maintained notes also succeeded in the reuse trials. Recent hook experiments verified supported file delivery, with additional context cost and no demonstrated correctness gain; direct delivery for ordinary Bash workflows remains open. The current focus is useful preservation and reuse of engineering reasoning during ordinary work, with capture and maintenance effort that feels worthwhile. Preventing repeat mistakes is one intended benefit; comparative superiority remains unestablished. [Positioning and validation plan](ROADMAP.md#historical-context-focus--2026-10-12) · [Current evidence](ROADMAP.md#current-evidence-and-boundaries)
86
+
82
87
  ## Documentation
83
88
 
84
89
  - [Setup, hooks, and upgrades](docs/setup.md)
@@ -775,17 +775,7 @@ var init_lock = __esm({
775
775
  }
776
776
  });
777
777
 
778
- // src/test-map.ts
779
- import path6 from "path";
780
- var init_test_map = __esm({
781
- "src/test-map.ts"() {
782
- "use strict";
783
- init_files();
784
- }
785
- });
786
-
787
778
  // src/snapshot/snapshot.ts
788
- import path7 from "path";
789
779
  import { z as z2 } from "zod";
790
780
  async function getCurrentGitHash(rootDir) {
791
781
  try {
@@ -803,11 +793,9 @@ var init_snapshot = __esm({
803
793
  "use strict";
804
794
  init_git_read();
805
795
  init_files();
806
- init_files();
807
796
  init_storage();
808
797
  init_lock();
809
798
  init_paths();
810
- init_test_map();
811
799
  repoPath = z2.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
812
800
  verificationFields = {
813
801
  refreshedHash: z2.string().optional(),
@@ -838,7 +826,7 @@ var init_snapshot = __esm({
838
826
 
839
827
  // src/drift/drift.ts
840
828
  import fs5 from "fs/promises";
841
- import path8 from "path";
829
+ import path6 from "path";
842
830
  function parseChanges(output) {
843
831
  const fields = output.split("\0");
844
832
  const changes = [];
@@ -906,7 +894,7 @@ var init_drift = __esm({
906
894
  });
907
895
 
908
896
  // src/audit/commands.ts
909
- import path9 from "path";
897
+ import path7 from "path";
910
898
  function splitCommands(line) {
911
899
  const parts = [];
912
900
  let quote = "", start = 0;
@@ -942,7 +930,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
942
930
  const cd = text2.match(/^cd\s+(.+)$/);
943
931
  if (cd) {
944
932
  const directory = literal(cd[1]);
945
- cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path9.posix.join(cwd ?? ".", directory);
933
+ cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path7.posix.join(cwd ?? ".", directory);
946
934
  continue;
947
935
  }
948
936
  for (const match of text2.matchAll(COMMAND)) {
@@ -954,7 +942,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
954
942
  scopeUnknown = "Unsupported shell wrapper or options before the script.";
955
943
  if (selector) {
956
944
  const selected = literal(selector[1]);
957
- directory = selected === null || cwd === null ? null : path9.posix.join(cwd ?? ".", selected);
945
+ directory = selected === null || cwd === null ? null : path7.posix.join(cwd ?? ".", selected);
958
946
  } else if (flags) scopeUnknown = "Unsupported package-manager options before run: " + flags;
959
947
  const tail = text2.slice(match.index + match[0].length).split(/\s--(?:\s|$)/)[0];
960
948
  if (/(?:^|\s)(?:--workspace(?:s)?|-w|--filter|-F|--prefix|--dir|-C|--if-present)(?:\s|=|$)/.test(
@@ -989,7 +977,7 @@ var init_commands = __esm({
989
977
  );
990
978
  literal = (value) => {
991
979
  const unquoted = value.replace(/^(["'])(.*)\1$/, "$2");
992
- return /[$`*?[\]{}~\\\x00-\x1f]/.test(unquoted) || path9.posix.isAbsolute(unquoted) ? null : unquoted;
980
+ return /[$`*?[\]{}~\\\x00-\x1f]/.test(unquoted) || path7.posix.isAbsolute(unquoted) ? null : unquoted;
993
981
  };
994
982
  }
995
983
  });
@@ -1372,7 +1360,7 @@ var init_git = __esm({
1372
1360
 
1373
1361
  // src/audit/docs.ts
1374
1362
  import fs6 from "fs/promises";
1375
- import path10 from "path";
1363
+ import path8 from "path";
1376
1364
  import fg5 from "fast-glob";
1377
1365
  async function dirtyDocs(resolvedRoot, files) {
1378
1366
  if (!files.length) return /* @__PURE__ */ new Set();
@@ -1468,15 +1456,15 @@ async function localDocPaths(root) {
1468
1456
  }
1469
1457
  for (const entry of entries)
1470
1458
  if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
1471
- paths.push(path10.posix.join(dir, entry.name));
1459
+ paths.push(path8.posix.join(dir, entry.name));
1472
1460
  }
1473
1461
  }
1474
1462
  return paths;
1475
1463
  }
1476
1464
  function documentScope(file) {
1477
- if (!/^(readme|agents|claude)\.md$/i.test(path10.posix.basename(file))) return ".";
1478
- const parent = path10.posix.dirname(file);
1479
- return path10.posix.basename(parent).toLowerCase() === ".claude" ? path10.posix.dirname(parent) : parent;
1465
+ if (!/^(readme|agents|claude)\.md$/i.test(path8.posix.basename(file))) return ".";
1466
+ const parent = path8.posix.dirname(file);
1467
+ return path8.posix.basename(parent).toLowerCase() === ".claude" ? path8.posix.dirname(parent) : parent;
1480
1468
  }
1481
1469
  async function discoverDocs(resolvedRoot) {
1482
1470
  return profilePhase("audit.documents", () => readDocs(resolvedRoot));
@@ -1499,7 +1487,7 @@ async function readDocs(resolvedRoot) {
1499
1487
  path: candidate,
1500
1488
  content,
1501
1489
  scope: documentScope(candidate),
1502
- kind: /^readme\.md$/i.test(path10.posix.basename(candidate)) ? "readme" : /^(agents|claude)\.md$/i.test(path10.posix.basename(candidate)) ? "instructions" : "guide",
1490
+ kind: /^readme\.md$/i.test(path8.posix.basename(candidate)) ? "readme" : /^(agents|claude)\.md$/i.test(path8.posix.basename(candidate)) ? "instructions" : "guide",
1503
1491
  lineCount: content.split("\n").length,
1504
1492
  lastCommit,
1505
1493
  dirty: dirty.has(candidate),
@@ -1528,15 +1516,15 @@ var init_docs = __esm({
1528
1516
 
1529
1517
  // src/audit/scope.ts
1530
1518
  import fs7 from "fs/promises";
1531
- import path11 from "path";
1519
+ import path9 from "path";
1532
1520
  function scopedPath(directory, value) {
1533
- if (path11.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value))
1521
+ if (path9.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value))
1534
1522
  return null;
1535
- const resolved = path11.posix.normalize(path11.posix.join(directory, value));
1523
+ const resolved = path9.posix.normalize(path9.posix.join(directory, value));
1536
1524
  return resolved === ".." || resolved.startsWith("../") ? null : resolved;
1537
1525
  }
1538
1526
  function pathClaimScope(doc, claim) {
1539
- const directory = claim.relativeTo === "document" ? path11.posix.dirname(doc) : documentScope(doc);
1527
+ const directory = claim.relativeTo === "document" ? path9.posix.dirname(doc) : documentScope(doc);
1540
1528
  const local = scopedPath(directory, claim.path);
1541
1529
  if (!local) return null;
1542
1530
  const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
@@ -1570,7 +1558,7 @@ var init_scope = __esm({
1570
1558
  });
1571
1559
 
1572
1560
  // src/audit/checks/deleted-reference.ts
1573
- import path12 from "path";
1561
+ import path10 from "path";
1574
1562
  async function checkDeletedReferences(ctx) {
1575
1563
  const result = emptyResult();
1576
1564
  for (const doc of ctx.docs) {
@@ -1605,7 +1593,7 @@ async function checkDeletedReferences(ctx) {
1605
1593
  scope.candidates.map(async (file) => ({
1606
1594
  file,
1607
1595
  tracked: await lastCommitOf(ctx.root, file),
1608
- parent: await pathExists(ctx.root, path12.posix.dirname(file))
1596
+ parent: await pathExists(ctx.root, path10.posix.dirname(file))
1609
1597
  }))
1610
1598
  );
1611
1599
  const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
@@ -1658,7 +1646,7 @@ var init_deleted_reference = __esm({
1658
1646
  // src/audit/checks/new-module.ts
1659
1647
  import fg6 from "fast-glob";
1660
1648
  import fs8 from "fs/promises";
1661
- import path13 from "path";
1649
+ import path11 from "path";
1662
1650
  function escapeRegExp(text2) {
1663
1651
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1664
1652
  }
@@ -1694,7 +1682,7 @@ async function checkNewModules(ctx) {
1694
1682
  }
1695
1683
  function moduleDocumentation(docs) {
1696
1684
  return docs.map(
1697
- (doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")
1685
+ (doc) => doc.content + "\n" + (path11.posix.dirname(doc.path) === "." ? "" : path11.posix.dirname(doc.path) + "/")
1698
1686
  ).join("\n");
1699
1687
  }
1700
1688
  function moduleCandidates(root, combinedDocs) {
@@ -1720,9 +1708,9 @@ async function collectModuleCandidates(root, combinedDocs) {
1720
1708
  });
1721
1709
  const dirs = entries.filter(
1722
1710
  (entry) => (entry.isDirectory() || entry.isSymbolicLink() && sources.some(
1723
- (file) => file.startsWith(path13.posix.join(dir, entry.name) + "/")
1711
+ (file) => file.startsWith(path11.posix.join(dir, entry.name) + "/")
1724
1712
  )) && !entry.name.startsWith(".") && !DIR_DENYLIST.has(entry.name)
1725
- ).map((entry) => path13.posix.join(dir, entry.name));
1713
+ ).map((entry) => path11.posix.join(dir, entry.name));
1726
1714
  byParent.set(dir, dirs);
1727
1715
  })
1728
1716
  );
@@ -1742,7 +1730,7 @@ async function collectModuleCandidates(root, combinedDocs) {
1742
1730
  onlyDirectories: true,
1743
1731
  label: "Module directory discovery",
1744
1732
  select: (file) => visible.has(file)
1745
- })).map((file) => path13.posix.basename(file))
1733
+ })).map((file) => path11.posix.basename(file))
1746
1734
  );
1747
1735
  }
1748
1736
  return result;
@@ -1824,7 +1812,7 @@ var init_new_module = __esm({
1824
1812
  });
1825
1813
 
1826
1814
  // src/audit/checks/stale-count.ts
1827
- import path14 from "path";
1815
+ import path12 from "path";
1828
1816
  async function countGradleModules(root) {
1829
1817
  for (const name of ["settings.gradle.kts", "settings.gradle"]) {
1830
1818
  const content = await readAuditInput(root, name);
@@ -1861,7 +1849,7 @@ async function countNpmWorkspaces(root) {
1861
1849
  return {
1862
1850
  actual: matched.length,
1863
1851
  countedFrom: "package.json workspaces",
1864
- members: matched.map((m) => path14.dirname(m)).sort()
1852
+ members: matched.map((m) => path12.dirname(m)).sort()
1865
1853
  };
1866
1854
  }
1867
1855
  }
@@ -1892,7 +1880,7 @@ async function countNpmWorkspaces(root) {
1892
1880
  return {
1893
1881
  actual: matched.length,
1894
1882
  countedFrom: "pnpm-workspace.yaml",
1895
- members: matched.map((m) => path14.dirname(m)).sort()
1883
+ members: matched.map((m) => path12.dirname(m)).sort()
1896
1884
  };
1897
1885
  }
1898
1886
  }
@@ -1912,8 +1900,8 @@ async function countCargoCrates(root) {
1912
1900
  ignore: ["**/target/**"],
1913
1901
  label: "Cargo workspace discovery"
1914
1902
  });
1915
- for (const m of matched) members.add(path14.dirname(m));
1916
- } else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
1903
+ for (const m of matched) members.add(path12.dirname(m));
1904
+ } else if (await readAuditInput(root, path12.posix.join(entry, "Cargo.toml")) !== null) {
1917
1905
  members.add(entry);
1918
1906
  }
1919
1907
  }
@@ -1933,7 +1921,7 @@ async function resolveCountSource(root, claim) {
1933
1921
  }
1934
1922
  async function resolveDocCountSource(root, doc, claim) {
1935
1923
  const scope = documentScope(doc);
1936
- const source = await resolveCountSource(path14.join(root, scope), claim);
1924
+ const source = await resolveCountSource(path12.join(root, scope), claim);
1937
1925
  if (!source || scope === ".") return source;
1938
1926
  return {
1939
1927
  ...source,
@@ -1986,7 +1974,7 @@ var init_stale_count = __esm({
1986
1974
  });
1987
1975
 
1988
1976
  // src/audit/checks/dead-command.ts
1989
- import path15 from "path";
1977
+ import path13 from "path";
1990
1978
  function scriptsOf(raw) {
1991
1979
  try {
1992
1980
  const pkg = JSON.parse(raw);
@@ -2030,7 +2018,7 @@ async function commandInputs(root, docs) {
2030
2018
  }
2031
2019
  let cursor = directory;
2032
2020
  while (true) {
2033
- const manifest = path15.posix.join(cursor, "package.json");
2021
+ const manifest = path13.posix.join(cursor, "package.json");
2034
2022
  scope.candidates.push(manifest);
2035
2023
  const raw = await read(manifest);
2036
2024
  if (raw !== null) {
@@ -2041,7 +2029,7 @@ async function commandInputs(root, docs) {
2041
2029
  break;
2042
2030
  }
2043
2031
  if (claim.directory !== void 0 || cursor === ".") break;
2044
- cursor = path15.posix.dirname(cursor);
2032
+ cursor = path13.posix.dirname(cursor);
2045
2033
  }
2046
2034
  if (!resolution.manifest && !resolution.problem) {
2047
2035
  resolution.problem = `No package.json for the command's scope (${directory}).`;
@@ -2127,7 +2115,7 @@ var init_dead_command = __esm({
2127
2115
  });
2128
2116
 
2129
2117
  // src/audit/dependency-claims.ts
2130
- import path16 from "path";
2118
+ import path14 from "path";
2131
2119
  import { parse as parseToml } from "smol-toml";
2132
2120
  function dependencyText(content) {
2133
2121
  return content.replace(
@@ -2138,7 +2126,7 @@ function dependencyText(content) {
2138
2126
  function declarations(file, source) {
2139
2127
  const result = /* @__PURE__ */ new Map();
2140
2128
  if (!source) return result;
2141
- if (path16.posix.basename(file) === "package.json") {
2129
+ if (path14.posix.basename(file) === "package.json") {
2142
2130
  const json = JSON.parse(source);
2143
2131
  if (!isRecord(json)) throw new Error("Invalid package manifest");
2144
2132
  for (const section of [
@@ -2591,7 +2579,7 @@ var init_anchors = __esm({
2591
2579
  });
2592
2580
 
2593
2581
  // src/context/lexical.ts
2594
- import path17 from "path";
2582
+ import path15 from "path";
2595
2583
  var init_lexical = __esm({
2596
2584
  "src/context/lexical.ts"() {
2597
2585
  "use strict";
@@ -2660,8 +2648,19 @@ function decisionProvenance(record, freshness = "unknown") {
2660
2648
  } : null
2661
2649
  };
2662
2650
  }
2663
- function decisionInspectionDigest(record) {
2651
+ function decisionInspectionDigest(record, scope = "all") {
2664
2652
  const effective = effectiveDecision(record);
2653
+ if (scope === "operative")
2654
+ return createHash2("sha256").update(
2655
+ JSON.stringify([
2656
+ "operative",
2657
+ record.status,
2658
+ decisionApproval(effective),
2659
+ effective.version === 2 ? effective.revision : 0,
2660
+ decisionContent(effective),
2661
+ effective.refreshedHash
2662
+ ])
2663
+ ).digest("hex");
2665
2664
  return createHash2("sha256").update(
2666
2665
  JSON.stringify([
2667
2666
  record.status,
@@ -2673,8 +2672,10 @@ function decisionInspectionDigest(record) {
2673
2672
  ])
2674
2673
  ).digest("hex");
2675
2674
  }
2676
- function latestDecisionInspection(record) {
2677
- return record.version === 2 ? record.inspections?.filter((i) => i.decisionDigest === decisionInspectionDigest(record)).at(-1) : void 0;
2675
+ function latestDecisionInspection(record, scope = "all") {
2676
+ return record.version === 2 ? record.inspections?.filter(
2677
+ (i) => (scope === "operative" || i.scope !== "operative") && i.decisionDigest === decisionInspectionDigest(record, i.scope ?? "all")
2678
+ ).at(-1) : void 0;
2678
2679
  }
2679
2680
  var text, decisionSourceSchema, attributionSchema, contentSchema, approvalSchema, statusSchema, reviewEvidenceSchema, eventSchema, legacySchema, currentSchema, decisionSchema;
2680
2681
  var init_provenance = __esm({
@@ -2760,6 +2761,7 @@ var init_provenance = __esm({
2760
2761
  inspector: text(200),
2761
2762
  note: text(1500),
2762
2763
  outcome: z4.literal("no_contradiction_found"),
2764
+ scope: z4.literal("operative").optional(),
2763
2765
  decisionDigest: z4.string().regex(/^[a-f0-9]{64}$/),
2764
2766
  headHash: z4.string(),
2765
2767
  capture: anchorCaptureSchema
@@ -2816,7 +2818,7 @@ var init_provenance = __esm({
2816
2818
 
2817
2819
  // src/decisions/decisions.ts
2818
2820
  import fs10 from "fs/promises";
2819
- import path18 from "path";
2821
+ import path16 from "path";
2820
2822
  import { createHash as createHash3 } from "crypto";
2821
2823
  import { z as z5 } from "zod";
2822
2824
  async function readDecisionInputs(rootDir) {
@@ -2909,9 +2911,9 @@ var init_decisions = __esm({
2909
2911
  });
2910
2912
 
2911
2913
  // src/decisions/drift.ts
2912
- import path19 from "path";
2914
+ import path17 from "path";
2913
2915
  async function computeDecisionDrift(rootDir, decisions) {
2914
- const resolvedRoot = path19.resolve(rootDir);
2916
+ const resolvedRoot = path17.resolve(rootDir);
2915
2917
  const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
2916
2918
  const report = {
2917
2919
  historyAvailable: true,
@@ -2926,11 +2928,15 @@ async function computeDecisionDrift(rootDir, decisions) {
2926
2928
  ]);
2927
2929
  const changesByHash = /* @__PURE__ */ new Map();
2928
2930
  const baselines = store.records.filter((record) => record.status === "active").flatMap((record) => {
2929
- const inspection = latestDecisionInspection(record);
2930
- if (inspection) return [inspection.capture];
2931
- return [.../* @__PURE__ */ new Set([effectiveDecision(record), record])].flatMap(
2932
- (candidate) => candidate.version === 2 && candidate.capture ? [candidate.capture] : []
2933
- );
2931
+ const effective = effectiveDecision(record);
2932
+ return [.../* @__PURE__ */ new Set([effective, record])].flatMap((candidate) => {
2933
+ const inspection = latestDecisionInspection(
2934
+ record,
2935
+ candidate === effective && decisionApproval(effective) === "accepted" ? "operative" : "all"
2936
+ );
2937
+ const capture = inspection?.capture ?? (candidate.version === 2 ? candidate.capture : void 0);
2938
+ return capture ? [capture] : [];
2939
+ });
2934
2940
  });
2935
2941
  const captures = await captureAnchorScopes(
2936
2942
  resolvedRoot,
@@ -2969,12 +2975,18 @@ async function computeDecisionDrift(rootDir, decisions) {
2969
2975
  for (const record of store.records) {
2970
2976
  if (record.status !== "active") continue;
2971
2977
  const effective = effectiveDecision(record);
2972
- const inspection = latestDecisionInspection(record);
2978
+ const inspection = latestDecisionInspection(
2979
+ record,
2980
+ decisionApproval(effective) === "accepted" ? "operative" : "all"
2981
+ );
2973
2982
  const state = await inspect(effective, inspection?.capture);
2974
2983
  report.freshness[record.id] = state.freshness;
2975
2984
  if (state.changedFiles.length) report.staleDecisions[record.id] = state.changedFiles;
2976
2985
  if (effective !== record)
2977
- (report.pendingProposals ??= {})[record.id] = await inspect(record, inspection?.capture);
2986
+ (report.pendingProposals ??= {})[record.id] = await inspect(
2987
+ record,
2988
+ latestDecisionInspection(record)?.capture
2989
+ );
2978
2990
  }
2979
2991
  return report;
2980
2992
  }
@@ -3297,6 +3309,7 @@ var init_store = __esm({
3297
3309
  updatedAt: z9.string(),
3298
3310
  fingerprint: z9.string().nullable(),
3299
3311
  latest: z9.string().nullable(),
3312
+ guidance: z9.object({ fingerprint: z9.string(), path: z9.string() }).optional(),
3300
3313
  notifications: notificationStateSchema.optional(),
3301
3314
  agentNotifications: notificationStateSchema.optional(),
3302
3315
  analysis: z9.object({ id: z9.string(), fingerprint: z9.string(), completedAt: z9.number(), path: z9.string() }).optional()
@@ -3319,7 +3332,7 @@ __export(repair_exports, {
3319
3332
  withRepairSession: () => withRepairSession
3320
3333
  });
3321
3334
  import fs11 from "fs/promises";
3322
- import path20 from "path";
3335
+ import path18 from "path";
3323
3336
  import { randomUUID as randomUUID2 } from "crypto";
3324
3337
  import { z as z10 } from "zod";
3325
3338
  function recheckable(finding) {
@@ -3417,8 +3430,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
3417
3430
  }
3418
3431
  async function loadRepairBaseline(rootDir, baselinePath) {
3419
3432
  const root = await fs11.realpath(rootDir);
3420
- const declaredRoot = path20.resolve(rootDir);
3421
- const relative = path20.isAbsolute(baselinePath) ? path20.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
3433
+ const declaredRoot = path18.resolve(rootDir);
3434
+ const relative = path18.isAbsolute(baselinePath) ? path18.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
3422
3435
  const stored = baselineSchema.parse(await readStoreJson(root, relative));
3423
3436
  const { digest: savedDigest, ...payload } = stored;
3424
3437
  if (digest(payload) !== savedDigest)
@@ -3779,7 +3792,10 @@ async function decisionAssessment(root, finding, baselineHead) {
3779
3792
  );
3780
3793
  const record = store.records.find((record2) => record2.id === evidence.decisionId);
3781
3794
  if (!record || record.version !== 2) return null;
3782
- const inspection = latestDecisionInspection(record);
3795
+ const inspection = latestDecisionInspection(
3796
+ record,
3797
+ evidence.provenance?.approval === "accepted" ? "operative" : "all"
3798
+ );
3783
3799
  if (inspection && record.status === "active") {
3784
3800
  const capture = await captureAnchors(root, inspection.capture.anchors);
3785
3801
  let ancestor = true;
@@ -4074,12 +4090,12 @@ var init_advisory_review = __esm({
4074
4090
 
4075
4091
  // src/audit/audit.ts
4076
4092
  import fs13 from "fs/promises";
4077
- import path21 from "path";
4093
+ import path19 from "path";
4078
4094
  async function computeAudit(rootDir, options = {}) {
4079
4095
  return profilePhase("audit.current", () => auditCurrent(rootDir, options));
4080
4096
  }
4081
4097
  async function auditCurrent(rootDir, options) {
4082
- const resolvedRoot = path21.resolve(rootDir);
4098
+ const resolvedRoot = path19.resolve(rootDir);
4083
4099
  const [docs, headHash] = await Promise.all([
4084
4100
  discoverDocs(resolvedRoot),
4085
4101
  getCurrentGitHash(resolvedRoot)
@@ -4117,7 +4133,7 @@ async function auditCurrent(rootDir, options) {
4117
4133
  }
4118
4134
  let decisionsPresent = false;
4119
4135
  try {
4120
- await fs13.access(path21.join(resolvedRoot, ".mason", "decisions"));
4136
+ await fs13.access(path19.join(resolvedRoot, ".mason", "decisions"));
4121
4137
  decisionsPresent = true;
4122
4138
  } catch {
4123
4139
  }
@@ -4186,11 +4202,11 @@ init_audit();
4186
4202
  init_types();
4187
4203
  init_repair();
4188
4204
  init_findings();
4189
- import path23 from "path";
4205
+ import path21 from "path";
4190
4206
 
4191
4207
  // src/audit/advisory-cli.ts
4192
4208
  init_advisory_review();
4193
- import path22 from "path";
4209
+ import path20 from "path";
4194
4210
  import { stripVTControlCharacters } from "util";
4195
4211
  var USAGE = `Usage: mason audit review --baseline <path> --finding <id> [options]
4196
4212
 
@@ -4250,7 +4266,7 @@ async function runAdvisoryCli(args, io = { out: (text2) => console.log(text2), e
4250
4266
  reviewToken: flags["--token"],
4251
4267
  reasonCode: flags["--reason"]
4252
4268
  };
4253
- const result = await reviewAdvisory(path22.resolve(flags["--dir"] ?? process.cwd()), input);
4269
+ const result = await reviewAdvisory(path20.resolve(flags["--dir"] ?? process.cwd()), input);
4254
4270
  if (json) io.out(JSON.stringify(result, null, 2));
4255
4271
  else {
4256
4272
  const lines = [`Advisory review: ${result.status}.`];
@@ -4438,7 +4454,7 @@ function formatFixPrompt(report, baselinePath) {
4438
4454
  lines.push("");
4439
4455
  lines.push("RULES:");
4440
4456
  lines.push(
4441
- baselinePath ? `- Preserve the original repair baseline: ${JSON.stringify(baselinePath)}. Do not replace it after editing.` : "- Before the first edit, call mason_repair with action: prepare, or run mason-audit --prepare-repair --json with the same --dir and --checks. Keep the returned baselinePath through verification."
4457
+ baselinePath ? `- Preserve the original repair baseline: ${JSON.stringify(baselinePath)}. Do not replace it after editing.` : "- Before the first edit, run mason audit --prepare-repair --json with the same --dir and --checks. Keep the returned baselinePath through verification."
4442
4458
  );
4443
4459
  lines.push(
4444
4460
  `- Edit ONLY these files: ${flaggedDocs.join(", ") || "none (advisory review only)"}. Bring the docs into agreement with verified source evidence; do not change source code or configs to silence findings.`
@@ -4461,7 +4477,7 @@ function formatFixPrompt(report, baselinePath) {
4461
4477
  "- new-module: review whether the directory needs documenting; an omission alone does not establish a defect. Preserve scope and describe only what you verified."
4462
4478
  );
4463
4479
  lines.push(
4464
- "- Specific historical ADVISORIES require a separate authorized assessment using review_advisory with the baselinePath and findingId from verification. Prepare and inspect evidence first; record addressed, inapplicable or deferred with the actual reviewer, note and reviewToken. Dependency notices marked resolution: informational require no review. Dependency advisories may be dismissed with action: dismiss, reasonCode (no-dependency-claims or unrelated-manifest-change), and note, without a reviewer or token. Decision findings use review_decision. Candidates marked resolution: recheck can be rerun to see whether the condition remains; this does not establish semantic approval. Report what remains unknown."
4480
+ "- Specific historical ADVISORIES require a separate authorized assessment using mason audit review with --baseline and --finding from verification. Prepare and inspect evidence first; record addressed, inapplicable or deferred with the actual reviewer, note and reviewToken. Dependency notices marked resolution: informational require no review. Dependency advisories may be dismissed with --outcome dismiss, --reason (no-dependency-claims or unrelated-manifest-change), and --note, without a reviewer or token. Decision findings use review_decision. Candidates marked resolution: recheck can be rerun to see whether the condition remains; this does not establish semantic approval. Report what remains unknown."
4465
4481
  );
4466
4482
  lines.push("");
4467
4483
  lines.push(
@@ -4483,7 +4499,7 @@ function formatFixPrompt(report, baselinePath) {
4483
4499
  );
4484
4500
  lines.push("");
4485
4501
  lines.push(
4486
- "After edits, call mason_repair with action: verify and the original baselinePath, or mason-audit --verify-repair <baselinePath> --dir <project>. Repeat against the same baseline after any final documentation commit. Summarize resolved, unresolved, review-required, unverified, and new findings with their evidence. Do not report a suppressed or unavailable check as fixed. This audit covers the listed documents and supported claims; application behavior and code examples need their own validation."
4502
+ "After edits, run mason audit --verify-repair <baselinePath> --dir <project>. Repeat against the same baseline after any final documentation commit. Summarize resolved, unresolved, review-required, unverified, and new findings with their evidence. Do not report a suppressed or unavailable check as fixed. This audit covers the listed documents and supported claims; application behavior and code examples need their own validation."
4487
4503
  );
4488
4504
  return lines.join("\n");
4489
4505
  }
@@ -4514,7 +4530,7 @@ async function runAuditCli(argv, io = {
4514
4530
  io.out(USAGE2);
4515
4531
  return 0;
4516
4532
  }
4517
- const rootDir = path23.resolve(args.dir);
4533
+ const rootDir = path21.resolve(args.dir);
4518
4534
  if (args.baseline || args.prepareRepair) {
4519
4535
  try {
4520
4536
  if (args.baseline) {