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 +19 -0
- package/README.md +10 -5
- package/dist/mason-audit.js +89 -73
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-drift.js +52 -38
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +60 -30
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +2767 -5123
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +130 -100
- package/dist/mason-review.js.map +1 -1
- package/dist/mason-runtime.js +1 -0
- package/dist/mason-runtime.js.map +1 -1
- package/dist/mason.js +1055 -1703
- package/dist/mason.js.map +1 -1
- package/package.json +1 -4
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
|
-
###
|
|
5
|
+
### Preserve why your code was built this way.
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Record the reason · Review the lesson · Surface applicable guidance · Reconsider it as code changes**
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/mason-context)
|
|
10
10
|
[](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
|
|
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
|
-
|
|
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
|
|
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)
|
package/dist/mason-audit.js
CHANGED
|
@@ -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
|
|
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
|
|
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 :
|
|
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 :
|
|
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) ||
|
|
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
|
|
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(
|
|
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(
|
|
1478
|
-
const parent =
|
|
1479
|
-
return
|
|
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(
|
|
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
|
|
1519
|
+
import path9 from "path";
|
|
1532
1520
|
function scopedPath(directory, value) {
|
|
1533
|
-
if (
|
|
1521
|
+
if (path9.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value))
|
|
1534
1522
|
return null;
|
|
1535
|
-
const resolved =
|
|
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" ?
|
|
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
|
|
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,
|
|
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
|
|
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" + (
|
|
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(
|
|
1711
|
+
(file) => file.startsWith(path11.posix.join(dir, entry.name) + "/")
|
|
1724
1712
|
)) && !entry.name.startsWith(".") && !DIR_DENYLIST.has(entry.name)
|
|
1725
|
-
).map((entry) =>
|
|
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) =>
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
1916
|
-
} else if (await readAuditInput(root,
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
2914
|
+
import path17 from "path";
|
|
2913
2915
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
2914
|
-
const resolvedRoot =
|
|
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
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
3421
|
-
const relative =
|
|
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(
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
4205
|
+
import path21 from "path";
|
|
4190
4206
|
|
|
4191
4207
|
// src/audit/advisory-cli.ts
|
|
4192
4208
|
init_advisory_review();
|
|
4193
|
-
import
|
|
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(
|
|
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,
|
|
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
|
|
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,
|
|
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 =
|
|
4533
|
+
const rootDir = path21.resolve(args.dir);
|
|
4518
4534
|
if (args.baseline || args.prepareRepair) {
|
|
4519
4535
|
try {
|
|
4520
4536
|
if (args.baseline) {
|