mason-context 0.22.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/mason-audit.js +74 -26
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-drift.js +42 -19
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +41 -19
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +699 -173
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +54 -29
- package/dist/mason-review.js.map +1 -1
- package/dist/mason-runtime.js +12 -6
- package/dist/mason-runtime.js.map +1 -1
- package/dist/mason.js +722 -148
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.23.1 — 2026-10-21
|
|
6
|
+
|
|
7
|
+
- Wait up to 60 seconds for the transparency log when signing the standalone update manifest. With the 5-second default, a slow log write timed out after succeeding and the retry failed with `409 entry already exists`, so the 0.23.0 release produced no GitHub release downloads.
|
|
8
|
+
- Stop publishing to Smithery on release: Smithery now rejects server updates (`403 Updating servers has been removed`). Releases continue to npm, the official MCP Registry and GitHub release downloads; the existing Smithery listing is no longer updated.
|
|
9
|
+
|
|
10
|
+
## 0.23.0 — 2026-10-21
|
|
11
|
+
|
|
12
|
+
- `save_decision` guidance asks for the constrained functions, types, or settings to be named in backticks, so drift notices can judge relevance. Anchors remain whole files or directories, and anchored changes still require inspection; symbol anchors and automatic clearing of trivial drift were considered and not adopted.
|
|
13
|
+
- Attribute decision drift on a branch to the branch or to the default branch it builds on. Previously a rebase made every decision the default branch had changed look like this checkout's unchecked work. Drift in anchors this branch touched is reported as before; drift that arrived only from the default branch is listed once per session, never blocks Stop, and is reported as usual on the default branch. `review_decision(prepare)` reports the same split in `previews.attribution`. Explicit audits still report all drift.
|
|
14
|
+
- `review_decision(action: "prepare")` previews source, diff and anchor commits since the latest inspection still valid for the record and scope, instead of since the original evidence baseline, and reports the commit used in `previews.diffSince`. Earlier changes were already inspected, so long-lived records no longer return months of truncated history. Pass `diffFrom: "capture"` for the full history. Responses include the latest two inspections and count older ones; the record file keeps them all. Review evidence, tokens and verdict requirements are unchanged.
|
|
15
|
+
- Stop failed, denied and interrupted tool calls from filling the automation store. Previously every new session failed once 128 sessions held unfinished calls, or once `state.json` passed 10 MiB, until the file was edited by hand. Claude configurations now register `PostToolUseFailure`; run `mason init --host claude` to add it, as older configurations stay compatible without it. Pending captures are dropped when the turn ends or after 24 idle hours. Sessions are evicted least recently used even with unfinished calls. `state.json` is written compactly and shrunk instead of failing when oversized, including files left by earlier versions. `mason status --json` reports unfinished tool calls, and an attempt still in flight no longer makes status `unavailable`.
|
|
16
|
+
- Show the changed lines in decision drift notices, including the Claude Stop review, so most irrelevant edits can be dismissed without another tool call. Hunks are diffed against the exact captured content of the last inspection or save. Each notice reports code identifiers named by the decision (backticked names, calls, camelCase or snake_case) that the changed lines or their enclosing declarations mention, or `Low relevance` when none are; decisions naming no identifiers are not assessed. Relevance is a name match only: findings, inspection requirements and Stop blocking are unchanged.
|
|
17
|
+
- Merge decision records automatically when branches each append inspections or review history, instead of conflicting inside the JSON on every rebase of a stacked branch. `mason init` adds a `.gitattributes` entry for `.mason/decisions/*.json`; `mason setup` configures the local `mason-decision` Git merge driver, and `mason status` reports a committed attribute without it. Two different changes to the same field still conflict, and Git's ordinary text conflict is used whenever Mason refuses a merge or is not on PATH. Run `mason init --host <host>` to add the attribute, commit `.gitattributes`, then run `mason setup` in each checkout.
|
|
18
|
+
- Leave Git-ignored files beneath a directory anchor out of its capture instead of marking the capture incomplete, so local setup files no longer block `review_decision` inspection or make the anchor's freshness unknown. Anchors naming only ignored content still stay unknown. An incomplete capture now leaves only its own decision's earlier drift findings unverified instead of every decision's.
|
|
19
|
+
|
|
5
20
|
## 0.22.0 — 2026-10-12
|
|
6
21
|
|
|
7
22
|
- 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`.
|
package/dist/mason-audit.js
CHANGED
|
@@ -405,11 +405,12 @@ async function storePath(root, relative, createParents = false) {
|
|
|
405
405
|
}
|
|
406
406
|
return current;
|
|
407
407
|
}
|
|
408
|
-
async function readStoreJson(root, relative) {
|
|
408
|
+
async function readStoreJson(root, relative, maxBytes = STORE_MAX_BYTES) {
|
|
409
409
|
try {
|
|
410
410
|
const file = await storePath(root, relative);
|
|
411
|
-
const raw = await readBoundedFile(file,
|
|
412
|
-
if (raw === null)
|
|
411
|
+
const raw = await readBoundedFile(file, maxBytes);
|
|
412
|
+
if (raw === null)
|
|
413
|
+
throw new Error(`file is not regular or exceeds ${maxBytes / 1024 / 1024} MiB`);
|
|
413
414
|
const parsed = JSON.parse(raw);
|
|
414
415
|
if (parsed === null) throw new Error("expected a JSON object, received null");
|
|
415
416
|
return parsed;
|
|
@@ -421,9 +422,9 @@ async function readStoreJson(root, relative) {
|
|
|
421
422
|
);
|
|
422
423
|
}
|
|
423
424
|
}
|
|
424
|
-
async function writeStoreJson(root, relative, value) {
|
|
425
|
-
const payload = JSON.stringify(value, null, 2) + "\n";
|
|
426
|
-
if (Buffer.byteLength(payload) >
|
|
425
|
+
async function writeStoreJson(root, relative, value, options = {}) {
|
|
426
|
+
const payload = JSON.stringify(value, null, options.compact ? void 0 : 2) + "\n";
|
|
427
|
+
if (Buffer.byteLength(payload) > STORE_MAX_BYTES) {
|
|
427
428
|
throw new Error(`Mason store ${relative} exceeds 10 MiB`);
|
|
428
429
|
}
|
|
429
430
|
const file = await storePath(root, relative, true);
|
|
@@ -441,11 +442,13 @@ async function writeStoreJson(root, relative, value) {
|
|
|
441
442
|
await fs2.rm(temporary, { force: true });
|
|
442
443
|
}
|
|
443
444
|
}
|
|
445
|
+
var STORE_MAX_BYTES;
|
|
444
446
|
var init_storage = __esm({
|
|
445
447
|
"src/utils/storage.ts"() {
|
|
446
448
|
"use strict";
|
|
447
449
|
init_paths();
|
|
448
450
|
init_files();
|
|
451
|
+
STORE_MAX_BYTES = 10 * 1024 * 1024;
|
|
449
452
|
}
|
|
450
453
|
});
|
|
451
454
|
|
|
@@ -2458,7 +2461,12 @@ async function captureAnchorScopes(root, scopes2) {
|
|
|
2458
2461
|
);
|
|
2459
2462
|
const inventory = access.inventory();
|
|
2460
2463
|
if (!inventory) throw new Error("Git anchor inventory unavailable");
|
|
2461
|
-
const
|
|
2464
|
+
const visible = indexMatchingPaths(
|
|
2465
|
+
union.map((anchor) => [anchor]),
|
|
2466
|
+
inventory
|
|
2467
|
+
);
|
|
2468
|
+
const hidden = new Set(union.filter((_, index) => !visible[index].length));
|
|
2469
|
+
const ignored = hidden.size ? (await inspectionGit(
|
|
2462
2470
|
[
|
|
2463
2471
|
"ls-files",
|
|
2464
2472
|
"-z",
|
|
@@ -2466,13 +2474,11 @@ async function captureAnchorScopes(root, scopes2) {
|
|
|
2466
2474
|
"--ignored",
|
|
2467
2475
|
"--exclude-standard",
|
|
2468
2476
|
"--",
|
|
2469
|
-
...
|
|
2477
|
+
...[...hidden].map((anchor) => `:(literal)${anchor}`)
|
|
2470
2478
|
],
|
|
2471
2479
|
{ cwd: root, maxBuffer: 8 * 1024 * 1024 }
|
|
2472
|
-
);
|
|
2473
|
-
const files = [
|
|
2474
|
-
.../* @__PURE__ */ new Set([...inventory, ...ignored.stdout.split("\0").filter(Boolean)])
|
|
2475
|
-
].sort();
|
|
2480
|
+
)).stdout.split("\0").filter(Boolean) : [];
|
|
2481
|
+
const files = [.../* @__PURE__ */ new Set([...inventory, ...ignored])].sort();
|
|
2476
2482
|
const patterns = await Promise.all(
|
|
2477
2483
|
union.map(async (anchor) => {
|
|
2478
2484
|
const literal2 = fg7.escapePath(anchor);
|
|
@@ -2488,7 +2494,7 @@ async function captureAnchorScopes(root, scopes2) {
|
|
|
2488
2494
|
const filesByScope = new Map(
|
|
2489
2495
|
scopes2.map((scope, index) => [JSON.stringify([...new Set(scope)].sort()), indexed[index]])
|
|
2490
2496
|
);
|
|
2491
|
-
return { filesByScope, eligible };
|
|
2497
|
+
return { filesByScope, eligible, hidden, ignored: new Set(ignored) };
|
|
2492
2498
|
};
|
|
2493
2499
|
return Promise.all(
|
|
2494
2500
|
scopes2.map((scope) => {
|
|
@@ -2500,8 +2506,10 @@ async function captureAnchorScopes(root, scopes2) {
|
|
|
2500
2506
|
const capture = { anchors, files: {}, complete: true };
|
|
2501
2507
|
if (!anchors.length) return capture;
|
|
2502
2508
|
try {
|
|
2503
|
-
const { eligible, filesByScope } = await (context ??= prepareContext());
|
|
2504
|
-
const files = filesByScope.get(JSON.stringify(anchors))
|
|
2509
|
+
const { eligible, filesByScope, hidden, ignored } = await (context ??= prepareContext());
|
|
2510
|
+
const files = filesByScope.get(JSON.stringify(anchors)).filter(
|
|
2511
|
+
(file) => !ignored.has(file) || anchors.some((anchor) => hidden.has(anchor) && anchorMatches(anchor, file))
|
|
2512
|
+
);
|
|
2505
2513
|
if (files.length > 2e3) return { ...capture, complete: false };
|
|
2506
2514
|
for (const file of files) {
|
|
2507
2515
|
const evidence = await inspectionRead(
|
|
@@ -2910,6 +2918,35 @@ var init_decisions = __esm({
|
|
|
2910
2918
|
}
|
|
2911
2919
|
});
|
|
2912
2920
|
|
|
2921
|
+
// src/decisions/excerpts.ts
|
|
2922
|
+
import { createHash as createHash4 } from "crypto";
|
|
2923
|
+
import { execFile as execFile2 } from "child_process";
|
|
2924
|
+
import { promisify as promisify2 } from "util";
|
|
2925
|
+
function driftBaseline(record) {
|
|
2926
|
+
const effective = effectiveDecision(record);
|
|
2927
|
+
const inspection = latestDecisionInspection(
|
|
2928
|
+
record,
|
|
2929
|
+
decisionApproval(effective) === "accepted" ? "operative" : "all"
|
|
2930
|
+
);
|
|
2931
|
+
return {
|
|
2932
|
+
record: effective,
|
|
2933
|
+
capture: inspection?.capture ?? (effective.version === 2 ? effective.capture : void 0),
|
|
2934
|
+
commits: [inspection?.headHash, effective.refreshedHash].filter(
|
|
2935
|
+
(hash) => !!hash && /^[a-f0-9]{40,64}$/.test(hash)
|
|
2936
|
+
)
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
var exec;
|
|
2940
|
+
var init_excerpts = __esm({
|
|
2941
|
+
"src/decisions/excerpts.ts"() {
|
|
2942
|
+
"use strict";
|
|
2943
|
+
init_files();
|
|
2944
|
+
init_paths();
|
|
2945
|
+
init_provenance();
|
|
2946
|
+
exec = promisify2(execFile2);
|
|
2947
|
+
}
|
|
2948
|
+
});
|
|
2949
|
+
|
|
2913
2950
|
// src/decisions/drift.ts
|
|
2914
2951
|
import path17 from "path";
|
|
2915
2952
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
@@ -2974,15 +3011,11 @@ async function computeDecisionDrift(rootDir, decisions) {
|
|
|
2974
3011
|
};
|
|
2975
3012
|
for (const record of store.records) {
|
|
2976
3013
|
if (record.status !== "active") continue;
|
|
2977
|
-
const
|
|
2978
|
-
const
|
|
2979
|
-
record,
|
|
2980
|
-
decisionApproval(effective) === "accepted" ? "operative" : "all"
|
|
2981
|
-
);
|
|
2982
|
-
const state = await inspect(effective, inspection?.capture);
|
|
3014
|
+
const baseline = driftBaseline(record);
|
|
3015
|
+
const state = await inspect(baseline.record, baseline.capture);
|
|
2983
3016
|
report.freshness[record.id] = state.freshness;
|
|
2984
3017
|
if (state.changedFiles.length) report.staleDecisions[record.id] = state.changedFiles;
|
|
2985
|
-
if (
|
|
3018
|
+
if (baseline.record !== record)
|
|
2986
3019
|
(report.pendingProposals ??= {})[record.id] = await inspect(
|
|
2987
3020
|
record,
|
|
2988
3021
|
latestDecisionInspection(record)?.capture
|
|
@@ -2998,13 +3031,14 @@ var init_drift2 = __esm({
|
|
|
2998
3031
|
init_paths();
|
|
2999
3032
|
init_snapshot();
|
|
3000
3033
|
init_decisions();
|
|
3034
|
+
init_excerpts();
|
|
3001
3035
|
init_provenance();
|
|
3002
3036
|
}
|
|
3003
3037
|
});
|
|
3004
3038
|
|
|
3005
3039
|
// src/audit/checks/decision-anchor.ts
|
|
3006
3040
|
async function checkDecisionAnchors(ctx) {
|
|
3007
|
-
const result = emptyResult();
|
|
3041
|
+
const result = { ...emptyResult(), incompleteDecisions: [] };
|
|
3008
3042
|
if (!ctx.decisionsPresent) return result;
|
|
3009
3043
|
const store = await loadDecisionStore(ctx.root);
|
|
3010
3044
|
const records = store.records;
|
|
@@ -3036,6 +3070,7 @@ async function checkDecisionAnchors(ctx) {
|
|
|
3036
3070
|
check: "decision-anchor-drift",
|
|
3037
3071
|
reason: `Anchor evidence for decision ${record.id} is incomplete or unreadable`
|
|
3038
3072
|
});
|
|
3073
|
+
result.incompleteDecisions.push(record.id);
|
|
3039
3074
|
continue;
|
|
3040
3075
|
}
|
|
3041
3076
|
if (!changedFiles.length) continue;
|
|
@@ -3097,7 +3132,7 @@ var init_checks = __esm({
|
|
|
3097
3132
|
});
|
|
3098
3133
|
|
|
3099
3134
|
// src/audit/findings.ts
|
|
3100
|
-
import { createHash as
|
|
3135
|
+
import { createHash as createHash5 } from "crypto";
|
|
3101
3136
|
import { z as z6 } from "zod";
|
|
3102
3137
|
function findingId(finding) {
|
|
3103
3138
|
const e = finding.evidence;
|
|
@@ -3128,7 +3163,7 @@ var digest, checkSchema, commitSchema, anchorSchema, count, scopeSchema, evidenc
|
|
|
3128
3163
|
var init_findings = __esm({
|
|
3129
3164
|
"src/audit/findings.ts"() {
|
|
3130
3165
|
"use strict";
|
|
3131
|
-
digest = (value) =>
|
|
3166
|
+
digest = (value) => createHash5("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3132
3167
|
checkSchema = z6.enum([
|
|
3133
3168
|
"deleted-reference",
|
|
3134
3169
|
"new-module",
|
|
@@ -3235,6 +3270,13 @@ var init_findings = __esm({
|
|
|
3235
3270
|
}
|
|
3236
3271
|
});
|
|
3237
3272
|
|
|
3273
|
+
// src/automation/pending-tools.ts
|
|
3274
|
+
var init_pending_tools = __esm({
|
|
3275
|
+
"src/automation/pending-tools.ts"() {
|
|
3276
|
+
"use strict";
|
|
3277
|
+
}
|
|
3278
|
+
});
|
|
3279
|
+
|
|
3238
3280
|
// src/automation/stop-review.ts
|
|
3239
3281
|
import { z as z7 } from "zod";
|
|
3240
3282
|
var evidenceSchema2, stopReviewSchema;
|
|
@@ -3243,6 +3285,7 @@ var init_stop_review = __esm({
|
|
|
3243
3285
|
"use strict";
|
|
3244
3286
|
init_anchors();
|
|
3245
3287
|
init_findings();
|
|
3288
|
+
init_pending_tools();
|
|
3246
3289
|
evidenceSchema2 = z7.record(z7.object({ digest: z7.string(), capture: anchorCaptureSchema }));
|
|
3247
3290
|
stopReviewSchema = z7.object({
|
|
3248
3291
|
pending: z7.record(z7.object({ head: z7.string(), evidence: evidenceSchema2 })),
|
|
@@ -3279,13 +3322,14 @@ import { z as z9 } from "zod";
|
|
|
3279
3322
|
function withLock(root, directory, run, waitMs = 5e3) {
|
|
3280
3323
|
return withStoreLock(root, directory, run, waitMs, "Automation");
|
|
3281
3324
|
}
|
|
3282
|
-
var hostSchema, stateSchema;
|
|
3325
|
+
var hostSchema, stateSchema, IDLE_PENDING_MS, STATE_READ_MAX_BYTES;
|
|
3283
3326
|
var init_store = __esm({
|
|
3284
3327
|
"src/automation/store.ts"() {
|
|
3285
3328
|
"use strict";
|
|
3286
3329
|
init_store_lock();
|
|
3287
3330
|
init_stop_review();
|
|
3288
3331
|
init_notifications();
|
|
3332
|
+
init_storage();
|
|
3289
3333
|
hostSchema = z9.enum(["claude", "codex"]);
|
|
3290
3334
|
stateSchema = z9.object({
|
|
3291
3335
|
version: z9.literal(1),
|
|
@@ -3300,6 +3344,8 @@ var init_store = __esm({
|
|
|
3300
3344
|
host: hostSchema,
|
|
3301
3345
|
lastUsed: z9.string(),
|
|
3302
3346
|
notifiedDiagnostics: z9.array(z9.string()).default([]),
|
|
3347
|
+
/** Digest of the upstream drift notice already shown to this session. */
|
|
3348
|
+
upstreamNotice: z9.string().optional(),
|
|
3303
3349
|
pending: z9.record(z9.string()),
|
|
3304
3350
|
stopReview: stopReviewSchema.optional(),
|
|
3305
3351
|
coverageGaps: z9.array(z9.string()),
|
|
@@ -3314,6 +3360,8 @@ var init_store = __esm({
|
|
|
3314
3360
|
agentNotifications: notificationStateSchema.optional(),
|
|
3315
3361
|
analysis: z9.object({ id: z9.string(), fingerprint: z9.string(), completedAt: z9.number(), path: z9.string() }).optional()
|
|
3316
3362
|
});
|
|
3363
|
+
IDLE_PENDING_MS = 24 * 60 * 60 * 1e3;
|
|
3364
|
+
STATE_READ_MAX_BYTES = 64 * 1024 * 1024;
|
|
3317
3365
|
}
|
|
3318
3366
|
});
|
|
3319
3367
|
|