mason-context 0.16.1 → 0.16.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.
- package/CHANGELOG.md +7 -0
- package/README.md +3 -0
- package/dist/mason-auto.js +61 -21
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-mcp.js +52 -24
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason.js +116 -71
- package/dist/mason.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.2 — 2026-09-09
|
|
4
|
+
|
|
5
|
+
- Guard generated hooks when Mason is absent, and keep a checkout without local host setup quiet. Preserve active failure diagnostics and strict MCP startup; setup/status/teardown recognize the generated guard variants without project wrapper scripts.
|
|
6
|
+
- Observe known read-only tools without rescanning audit inputs or advancing verification. Keep before/after capture for shell and unknown tools. Add a reproducible full-CLI hook benchmark and publish its measured scope and limits.
|
|
7
|
+
- Document hook inputs, retained data, local versus optional networked features, MCP installation, and company mirrors. Require an explicit version with a custom release mirror to avoid public version discovery.
|
|
8
|
+
- Exercise core CLI/MCP and hook paths with denied network operations, raw-input retention markers, inactive/invalid setup, and changed evidence before potentially mutating tools. On macOS, apply OS network denial to the tested process tree too.
|
|
9
|
+
|
|
3
10
|
## 0.16.1 — 2026-09-08
|
|
4
11
|
|
|
5
12
|
- Resolve references using literal import paths and declared Kotlin/Java package symbols. Separate explicit path mentions and uncertain name candidates; suppress config/document-name collisions and keep weak candidates out of compact task context with an omission count.
|
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
Works through MCP, with automatic documentation checks for **Codex** and **Claude Code**. No concept map required.
|
|
14
14
|
|
|
15
|
+
Core checks run locally with no model calls or telemetry. Results become context for your existing assistant. [Data, network access, and team installation](docs/data-and-network.md) covers storage, optional networked features, and internal mirrors.
|
|
16
|
+
|
|
15
17
|
## Get started
|
|
16
18
|
|
|
17
19
|
Install on macOS or Linux:
|
|
@@ -74,6 +76,7 @@ Earlier read-only decision-retrieval evaluations scored **9.0/10 with Mason vs 7
|
|
|
74
76
|
## Documentation
|
|
75
77
|
|
|
76
78
|
- [Setup, hooks, and upgrades](docs/setup.md)
|
|
79
|
+
- [Data and network behavior](docs/data-and-network.md) · [Hook performance](docs/hook-performance.md)
|
|
77
80
|
- [Audits, repair verification, and CI evidence](docs/checks.md)
|
|
78
81
|
- [Decisions, tools, and optional architecture maps](docs/reference.md)
|
|
79
82
|
- [Roadmap](ROADMAP.md) · [Changelog](CHANGELOG.md) · [MIT license](LICENSE)
|
package/dist/mason-auto.js
CHANGED
|
@@ -2521,7 +2521,7 @@ var init_evidence = __esm({
|
|
|
2521
2521
|
init_dead_command();
|
|
2522
2522
|
init_storage();
|
|
2523
2523
|
exec7 = promisify8(execFile8);
|
|
2524
|
-
engineVersion = true ? "0.16.
|
|
2524
|
+
engineVersion = true ? "0.16.2" : "development";
|
|
2525
2525
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
2526
2526
|
content = readAuditInput;
|
|
2527
2527
|
internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
|
|
@@ -2628,6 +2628,25 @@ var init_store = __esm({
|
|
|
2628
2628
|
|
|
2629
2629
|
// src/automation/runtime.ts
|
|
2630
2630
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
2631
|
+
async function observeReadOnlyTool(dir, event) {
|
|
2632
|
+
const ws = await workspace(dir);
|
|
2633
|
+
await withLock(ws.root, ws.directory, async () => {
|
|
2634
|
+
const statePath = ws.directory + "/state.json";
|
|
2635
|
+
const raw = await readStoreJson(ws.root, statePath);
|
|
2636
|
+
if (raw === null) return;
|
|
2637
|
+
const state = parseState(raw);
|
|
2638
|
+
if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) {
|
|
2639
|
+
throw new Error("Automation state belongs to another branch or worktree; original evidence was retained.");
|
|
2640
|
+
}
|
|
2641
|
+
const session = event.host && event.sessionId ? state.sessions[hash([event.host, event.sessionId])] : null;
|
|
2642
|
+
if (!session) return;
|
|
2643
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2644
|
+
session.lastUsed = now;
|
|
2645
|
+
session.events[event.event] = { at: now, count: (session.events[event.event]?.count ?? 0) + 1 };
|
|
2646
|
+
await writeStoreJson(ws.root, statePath, state);
|
|
2647
|
+
});
|
|
2648
|
+
return { root: ws.root, directory: ws.directory };
|
|
2649
|
+
}
|
|
2631
2650
|
function summarize(report) {
|
|
2632
2651
|
const open = report.findings.filter((f) => f.status !== "resolved");
|
|
2633
2652
|
return [
|
|
@@ -2896,6 +2915,7 @@ var init_model = __esm({
|
|
|
2896
2915
|
// src/setup/launcher.ts
|
|
2897
2916
|
import { execFile as execFile9 } from "child_process";
|
|
2898
2917
|
import { promisify as promisify9 } from "util";
|
|
2918
|
+
import fs13 from "fs/promises";
|
|
2899
2919
|
function globalCommand(args, platform = process.platform) {
|
|
2900
2920
|
return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args] } : { command: "mason", args };
|
|
2901
2921
|
}
|
|
@@ -2920,7 +2940,7 @@ var init_launcher = __esm({
|
|
|
2920
2940
|
"use strict";
|
|
2921
2941
|
init_evidence();
|
|
2922
2942
|
init_model();
|
|
2923
|
-
executingVersion = () => true ? "0.16.
|
|
2943
|
+
executingVersion = () => true ? "0.16.2" : "development";
|
|
2924
2944
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
2925
2945
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
2926
2946
|
}
|
|
@@ -2933,7 +2953,7 @@ __export(observations_exports, {
|
|
|
2933
2953
|
observeActivation: () => observeActivation,
|
|
2934
2954
|
readObservation: () => readObservation
|
|
2935
2955
|
});
|
|
2936
|
-
import
|
|
2956
|
+
import fs14 from "fs/promises";
|
|
2937
2957
|
import { z as z8 } from "zod";
|
|
2938
2958
|
function observationPath(directory, host, revision, version = executingVersion()) {
|
|
2939
2959
|
return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
@@ -2949,9 +2969,9 @@ async function observeActivation(dir, event, options = {}) {
|
|
|
2949
2969
|
const host = process.env.MASON_SETUP_HOST, revision = process.env.MASON_SETUP_REVISION;
|
|
2950
2970
|
if (host !== "codex" && host !== "claude" || !revision || !process.env.MASON_SETUP_ROOT) return null;
|
|
2951
2971
|
try {
|
|
2952
|
-
const capturedDirectory = options.reportPath?.match(/^(\.mason\/reports\/automation\/[a-f0-9]{24})\/checks\//)?.[1];
|
|
2953
|
-
const ws = capturedDirectory ? { root: await
|
|
2954
|
-
if (ws.root !== await
|
|
2972
|
+
const capturedDirectory = options.directory?.match(/^\.mason\/reports\/automation\/[a-f0-9]{24}$/)?.[0] ?? options.reportPath?.match(/^(\.mason\/reports\/automation\/[a-f0-9]{24})\/checks\//)?.[1];
|
|
2973
|
+
const ws = capturedDirectory ? { root: await fs14.realpath(dir), directory: capturedDirectory } : await workspace(dir);
|
|
2974
|
+
if (ws.root !== await fs14.realpath(process.env.MASON_SETUP_ROOT)) return null;
|
|
2955
2975
|
const setup = await loadSetup(ws.root);
|
|
2956
2976
|
if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
|
|
2957
2977
|
const directory = ws.directory + "/activation";
|
|
@@ -3022,13 +3042,13 @@ import { z as z9 } from "zod";
|
|
|
3022
3042
|
function normalizeHook(host, raw) {
|
|
3023
3043
|
hostSchema.parse(host);
|
|
3024
3044
|
const input2 = inputSchema.parse(raw);
|
|
3025
|
-
const readOnly = host === "claude" ? /^(Read|Glob|Grep|WebSearch|WebFetch)$/ : /^(read_file|list_dir|grep_files)
|
|
3026
|
-
return { cwd: input2.cwd, name: input2.hook_event_name, event: {
|
|
3045
|
+
const readOnly = (host === "claude" ? /^(Read|Glob|Grep|WebSearch|WebFetch)$/ : /^(read_file|list_dir|grep_files|Read|Glob|Grep)$/).test(input2.tool_name ?? "");
|
|
3046
|
+
return { cwd: input2.cwd, name: input2.hook_event_name, readOnly, event: {
|
|
3027
3047
|
event: lifecycle[input2.hook_event_name],
|
|
3028
3048
|
host,
|
|
3029
3049
|
sessionId: input2.session_id,
|
|
3030
3050
|
toolId: input2.tool_use_id,
|
|
3031
|
-
mutating: !!input2.tool_name && !readOnly
|
|
3051
|
+
mutating: !!input2.tool_name && !readOnly,
|
|
3032
3052
|
stopHookActive: input2.stop_hook_active || input2.permission_mode === "plan"
|
|
3033
3053
|
} };
|
|
3034
3054
|
}
|
|
@@ -3036,8 +3056,20 @@ async function runAutomationHook(host, stdin) {
|
|
|
3036
3056
|
let name = "";
|
|
3037
3057
|
try {
|
|
3038
3058
|
if (Buffer.byteLength(stdin) > 1024 * 1024) throw new Error("Hook input exceeds 1 MiB.");
|
|
3039
|
-
|
|
3059
|
+
let raw;
|
|
3060
|
+
try {
|
|
3061
|
+
raw = JSON.parse(stdin);
|
|
3062
|
+
} catch {
|
|
3063
|
+
throw new Error("Hook input is not valid JSON.");
|
|
3064
|
+
}
|
|
3065
|
+
const input2 = normalizeHook(host, raw);
|
|
3040
3066
|
name = input2.name;
|
|
3067
|
+
if (["before_tool", "after_tool"].includes(input2.event.event) && input2.readOnly) {
|
|
3068
|
+
const observed = await observeReadOnlyTool(input2.cwd, input2.event);
|
|
3069
|
+
const { observeActivation: observeActivation3 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
|
|
3070
|
+
const warning2 = await observeActivation3(observed.root, input2.event.event, { sessionId: input2.event.sessionId, directory: observed.directory });
|
|
3071
|
+
return warning2 ? { systemMessage: warning2 } : null;
|
|
3072
|
+
}
|
|
3041
3073
|
const result = await automate(input2.cwd, input2.event);
|
|
3042
3074
|
const { observeActivation: observeActivation2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
|
|
3043
3075
|
const warning = await observeActivation2(result.report.root, input2.event.event, {
|
|
@@ -3056,8 +3088,15 @@ async function runAutomationHook(host, stdin) {
|
|
|
3056
3088
|
return { systemMessage: message2, ...["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse"].includes(name) ? { hookSpecificOutput: { hookEventName: name, additionalContext: message2 } } : {} };
|
|
3057
3089
|
}
|
|
3058
3090
|
}
|
|
3091
|
+
function managedHookCommand(host, platform = process.platform) {
|
|
3092
|
+
const command = `mason --setup-host ${host} auto hook --host ${host}`;
|
|
3093
|
+
return platform === "win32" ? `cmd.exe /d /s /c "where mason >nul 2>nul & if errorlevel 1 (exit /b 0) else (${command})"` : `if command -v mason >/dev/null 2>&1; then ${command}; fi`;
|
|
3094
|
+
}
|
|
3095
|
+
function knownManagedHookCommands(host) {
|
|
3096
|
+
return [managedHookCommand(host, "linux"), managedHookCommand(host, "win32"), `mason --setup-host ${host} auto hook --host ${host}`];
|
|
3097
|
+
}
|
|
3059
3098
|
function hookConfig(host, command = "npx --no-install --package mason-context mason-auto") {
|
|
3060
|
-
const handler = { type: "command", command: command + " hook --host " + host, timeout: 30 };
|
|
3099
|
+
const handler = { type: "command", command: command === `mason --setup-host ${host} auto` ? managedHookCommand(host) : command + " hook --host " + host, timeout: 30 };
|
|
3061
3100
|
return { hooks: Object.fromEntries(HOOK_EVENTS.map((name) => [
|
|
3062
3101
|
name,
|
|
3063
3102
|
[{ ...["PreToolUse", "PostToolUse"].includes(name) ? { matcher: ".*" } : {}, hooks: [{ ...handler }] }]
|
|
@@ -3098,7 +3137,7 @@ __export(files_exports, {
|
|
|
3098
3137
|
managedBlock: () => managedBlock,
|
|
3099
3138
|
readText: () => readText
|
|
3100
3139
|
});
|
|
3101
|
-
import
|
|
3140
|
+
import fs15 from "fs/promises";
|
|
3102
3141
|
import path18 from "path";
|
|
3103
3142
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
3104
3143
|
async function readText(root, file) {
|
|
@@ -3128,14 +3167,14 @@ async function applyEdit(root, edit) {
|
|
|
3128
3167
|
if (edit.after === null) {
|
|
3129
3168
|
const file2 = await storePath(root, edit.path);
|
|
3130
3169
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
|
|
3131
|
-
await
|
|
3170
|
+
await fs15.unlink(file2);
|
|
3132
3171
|
return true;
|
|
3133
3172
|
}
|
|
3134
3173
|
const file = await storePath(root, edit.path, true);
|
|
3135
3174
|
const temporary = path18.join(path18.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
3136
3175
|
try {
|
|
3137
|
-
const mode = await
|
|
3138
|
-
const handle = await
|
|
3176
|
+
const mode = await fs15.stat(file).then((s) => s.mode & 511, () => 384);
|
|
3177
|
+
const handle = await fs15.open(temporary, "wx", mode);
|
|
3139
3178
|
try {
|
|
3140
3179
|
await handle.writeFile(edit.after, "utf8");
|
|
3141
3180
|
await handle.sync();
|
|
@@ -3143,10 +3182,10 @@ async function applyEdit(root, edit) {
|
|
|
3143
3182
|
await handle.close();
|
|
3144
3183
|
}
|
|
3145
3184
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
|
|
3146
|
-
await
|
|
3185
|
+
await fs15.rename(temporary, file);
|
|
3147
3186
|
return true;
|
|
3148
3187
|
} finally {
|
|
3149
|
-
await
|
|
3188
|
+
await fs15.rm(temporary, { force: true });
|
|
3150
3189
|
}
|
|
3151
3190
|
}
|
|
3152
3191
|
var init_files2 = __esm({
|
|
@@ -3502,7 +3541,7 @@ async function planAutomationInstall(root, host, command) {
|
|
|
3502
3541
|
const previous = record.hosts[host]?.command;
|
|
3503
3542
|
const ownershipText = await readText(root, OWNERSHIP_PATH);
|
|
3504
3543
|
const pending = ownershipText === null ? [] : ownershipSchema.parse(JSON.parse(ownershipText)).files[file]?.hookCommands ?? [];
|
|
3505
|
-
const owned = /* @__PURE__ */ new Set([previous, ...pending, newCommand]);
|
|
3544
|
+
const owned = /* @__PURE__ */ new Set([previous, ...pending, newCommand, ...knownManagedHookCommands(host)]);
|
|
3506
3545
|
const hooks = existing.hooks ?? {};
|
|
3507
3546
|
for (const event of HOOK_EVENTS) {
|
|
3508
3547
|
hooks[event] = (hooks[event] ?? []).map((group) => ({
|
|
@@ -3606,6 +3645,7 @@ async function planTeardown(root, selected) {
|
|
|
3606
3645
|
const commands = (host) => new Set([
|
|
3607
3646
|
automation?.hosts[host]?.command,
|
|
3608
3647
|
...ownership.files[configPath(host)]?.hookCommands ?? [],
|
|
3648
|
+
...knownManagedHookCommands(host),
|
|
3609
3649
|
hookConfig(host, hookCommand(host)).hooks.SessionStart[0].hooks[0].command,
|
|
3610
3650
|
hookConfig(host).hooks.SessionStart[0].hooks[0].command
|
|
3611
3651
|
].filter((command) => !!command));
|
|
@@ -4335,7 +4375,7 @@ var init_evidence2 = __esm({
|
|
|
4335
4375
|
});
|
|
4336
4376
|
|
|
4337
4377
|
// src/review/review.ts
|
|
4338
|
-
import
|
|
4378
|
+
import fs16 from "fs/promises";
|
|
4339
4379
|
import path20 from "path";
|
|
4340
4380
|
import { execFile as execFile12 } from "child_process";
|
|
4341
4381
|
import { promisify as promisify12 } from "util";
|
|
@@ -4415,7 +4455,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
4415
4455
|
analyzed,
|
|
4416
4456
|
async (relPath) => {
|
|
4417
4457
|
try {
|
|
4418
|
-
await
|
|
4458
|
+
await fs16.access(path20.join(resolvedRoot, relPath));
|
|
4419
4459
|
return true;
|
|
4420
4460
|
} catch {
|
|
4421
4461
|
return false;
|
|
@@ -4569,7 +4609,7 @@ async function setupStatus(dir) {
|
|
|
4569
4609
|
version: 1,
|
|
4570
4610
|
status: pending.length ? "incomplete" : "not-configured",
|
|
4571
4611
|
hosts: {},
|
|
4572
|
-
next: pending.length ? "Setup did not finish. Rerun mason-auto setup --host " + pending[0] + " to resume using the retained original evidence." : "Run mason
|
|
4612
|
+
next: pending.length ? "Setup did not finish. Rerun mason-auto setup --host " + pending[0] + " to resume using the retained original evidence." : "Project hooks are inactive until local setup. Run mason setup --host codex or --host claude."
|
|
4573
4613
|
};
|
|
4574
4614
|
}
|
|
4575
4615
|
const hosts2 = {};
|