mason-context 0.15.0 → 0.16.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 +12 -0
- package/README.md +1 -1
- package/dist/mason-auto.js +445 -93
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-mcp.js +560 -301
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason.js +473 -120
- package/dist/mason.js.map +1 -1
- package/package.json +2 -1
package/dist/mason-mcp.js
CHANGED
|
@@ -2832,7 +2832,13 @@ function managedBlock(text2, start, end, body) {
|
|
|
2832
2832
|
async function applyEdit(root, edit) {
|
|
2833
2833
|
const current = await readText(root, edit.path);
|
|
2834
2834
|
if (current === edit.after) return false;
|
|
2835
|
-
if (current !== edit.before) throw new Error("
|
|
2835
|
+
if (current !== edit.before) throw new Error("Integration input changed during update: " + edit.path + ". Rerun the requested command to resume.");
|
|
2836
|
+
if (edit.after === null) {
|
|
2837
|
+
const file2 = await storePath(root, edit.path);
|
|
2838
|
+
if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
|
|
2839
|
+
await fs13.unlink(file2);
|
|
2840
|
+
return true;
|
|
2841
|
+
}
|
|
2836
2842
|
const file = await storePath(root, edit.path, true);
|
|
2837
2843
|
const temporary = path19.join(path19.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
|
|
2838
2844
|
try {
|
|
@@ -2844,7 +2850,7 @@ async function applyEdit(root, edit) {
|
|
|
2844
2850
|
} finally {
|
|
2845
2851
|
await handle.close();
|
|
2846
2852
|
}
|
|
2847
|
-
if (await readText(root, edit.path) !== edit.before) throw new Error("
|
|
2853
|
+
if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
|
|
2848
2854
|
await fs13.rename(temporary, file);
|
|
2849
2855
|
return true;
|
|
2850
2856
|
} finally {
|
|
@@ -3130,7 +3136,7 @@ var init_evidence = __esm({
|
|
|
3130
3136
|
init_dead_command();
|
|
3131
3137
|
init_storage();
|
|
3132
3138
|
exec11 = promisify12(execFile12);
|
|
3133
|
-
engineVersion = true ? "0.
|
|
3139
|
+
engineVersion = true ? "0.16.1" : "development";
|
|
3134
3140
|
hash = (value) => createHash4("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3135
3141
|
content = readAuditInput;
|
|
3136
3142
|
internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
|
|
@@ -3494,6 +3500,79 @@ var init_adapters = __esm({
|
|
|
3494
3500
|
}
|
|
3495
3501
|
});
|
|
3496
3502
|
|
|
3503
|
+
// src/setup/ownership.ts
|
|
3504
|
+
var ownership_exports = {};
|
|
3505
|
+
__export(ownership_exports, {
|
|
3506
|
+
BLOCKS: () => BLOCKS,
|
|
3507
|
+
OWNERSHIP_PATH: () => OWNERSHIP_PATH,
|
|
3508
|
+
blockSpan: () => blockSpan,
|
|
3509
|
+
completedHookOwnership: () => completedHookOwnership,
|
|
3510
|
+
ownershipEdit: () => ownershipEdit,
|
|
3511
|
+
ownershipSchema: () => ownershipSchema
|
|
3512
|
+
});
|
|
3513
|
+
import { z as z10 } from "zod";
|
|
3514
|
+
import { parse } from "smol-toml";
|
|
3515
|
+
function blockSpan(text2, start, end) {
|
|
3516
|
+
const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
|
|
3517
|
+
if (!starts && !ends) return null;
|
|
3518
|
+
const from = text2.indexOf(start), to = text2.indexOf(end) + end.length;
|
|
3519
|
+
if (starts !== 1 || ends !== 1 || to <= from) throw new Error("Ambiguous Mason markers; inspect the marked block manually.");
|
|
3520
|
+
return { from, to, text: text2.slice(from, to) };
|
|
3521
|
+
}
|
|
3522
|
+
async function ownershipEdit(root, edits, hookCommands = {}) {
|
|
3523
|
+
const before = await readText(root, OWNERSHIP_PATH);
|
|
3524
|
+
const record = before === null ? { version: 1, files: {} } : ownershipSchema.parse(JSON.parse(before));
|
|
3525
|
+
for (const edit of edits) {
|
|
3526
|
+
const previous = record.files[edit.path];
|
|
3527
|
+
const entry = {
|
|
3528
|
+
created: edit.before === null || (previous?.created ?? false),
|
|
3529
|
+
initialHash: edit.before === null || previous?.initialHash === hash(edit.before) ? hash(edit.after) : previous?.initialHash ?? hash(edit.after),
|
|
3530
|
+
blocks: Object.fromEntries(BLOCKS.flatMap(([start, end]) => {
|
|
3531
|
+
const block = blockSpan(edit.after, start, end);
|
|
3532
|
+
return block ? [[start, hash(block.text)]] : [];
|
|
3533
|
+
}))
|
|
3534
|
+
};
|
|
3535
|
+
const mcp = edit.path === ".codex/config.toml" ? parse(edit.after).mcp_servers?.mason : edit.path === ".mcp.json" ? JSON.parse(edit.after).mcpServers?.mason : void 0;
|
|
3536
|
+
const hooks = edit.path === ".codex/hooks.json" || edit.path === ".claude/settings.json" ? JSON.parse(edit.after).hooks : void 0;
|
|
3537
|
+
record.files[edit.path] = {
|
|
3538
|
+
...entry,
|
|
3539
|
+
...mcp === void 0 ? {} : { mcpHash: hash(mcp) },
|
|
3540
|
+
...hookCommands[edit.path] ? { hookCommands: [.../* @__PURE__ */ new Set([...previous?.hookCommands ?? [], hookCommands[edit.path]])] } : {},
|
|
3541
|
+
...hooks === void 0 ? {} : { hooks: Object.fromEntries(Object.entries(hooks).map(([event, groups]) => [event, groups.flatMap((group) => group.hooks.map(hash))])) }
|
|
3542
|
+
};
|
|
3543
|
+
}
|
|
3544
|
+
return { path: OWNERSHIP_PATH, before, after: JSON.stringify(record, null, 2) + "\n" };
|
|
3545
|
+
}
|
|
3546
|
+
async function completedHookOwnership(root, file, command) {
|
|
3547
|
+
const before = await readText(root, OWNERSHIP_PATH);
|
|
3548
|
+
const record = ownershipSchema.parse(JSON.parse(before ?? "null"));
|
|
3549
|
+
record.files[file].hookCommands = [command];
|
|
3550
|
+
return { path: OWNERSHIP_PATH, before, after: JSON.stringify(record, null, 2) + "\n" };
|
|
3551
|
+
}
|
|
3552
|
+
var OWNERSHIP_PATH, BLOCKS, fileSchema, ownershipSchema;
|
|
3553
|
+
var init_ownership = __esm({
|
|
3554
|
+
"src/setup/ownership.ts"() {
|
|
3555
|
+
"use strict";
|
|
3556
|
+
init_evidence();
|
|
3557
|
+
init_files2();
|
|
3558
|
+
OWNERSHIP_PATH = ".mason/local/integration.json";
|
|
3559
|
+
BLOCKS = [
|
|
3560
|
+
["<!-- mason:start -->", "<!-- mason:end -->"],
|
|
3561
|
+
["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
|
|
3562
|
+
["# mason:mcp:start", "# mason:mcp:end"]
|
|
3563
|
+
];
|
|
3564
|
+
fileSchema = z10.object({
|
|
3565
|
+
created: z10.boolean(),
|
|
3566
|
+
initialHash: z10.string(),
|
|
3567
|
+
blocks: z10.record(z10.string()),
|
|
3568
|
+
mcpHash: z10.string().optional(),
|
|
3569
|
+
hookCommands: z10.array(z10.string()).optional(),
|
|
3570
|
+
hooks: z10.record(z10.array(z10.string())).optional()
|
|
3571
|
+
});
|
|
3572
|
+
ownershipSchema = z10.object({ version: z10.literal(1), files: z10.record(fileSchema) });
|
|
3573
|
+
}
|
|
3574
|
+
});
|
|
3575
|
+
|
|
3497
3576
|
// src/automation/install.ts
|
|
3498
3577
|
var install_exports = {};
|
|
3499
3578
|
__export(install_exports, {
|
|
@@ -3502,24 +3581,28 @@ __export(install_exports, {
|
|
|
3502
3581
|
configPath: () => configPath,
|
|
3503
3582
|
installAutomation: () => installAutomation,
|
|
3504
3583
|
installedAutomation: () => installedAutomation,
|
|
3505
|
-
planAutomationInstall: () => planAutomationInstall
|
|
3584
|
+
planAutomationInstall: () => planAutomationInstall,
|
|
3585
|
+
recordSchema: () => recordSchema
|
|
3506
3586
|
});
|
|
3507
|
-
import { z as
|
|
3587
|
+
import { z as z11 } from "zod";
|
|
3508
3588
|
async function installationRecord(root) {
|
|
3509
3589
|
return recordSchema.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
|
|
3510
3590
|
}
|
|
3511
3591
|
async function installAutomation(dir, host, command) {
|
|
3512
3592
|
const ws = await workspace(dir);
|
|
3513
|
-
return withLock(ws.root, ".mason/reports/
|
|
3593
|
+
return withLock(ws.root, ".mason/reports/setup-lock", () => installLocked(ws.root, host, command));
|
|
3514
3594
|
}
|
|
3515
3595
|
async function planAutomationInstall(root, host, command) {
|
|
3516
3596
|
const file = configPath(host);
|
|
3517
|
-
const
|
|
3597
|
+
const before = await readText(root, file);
|
|
3598
|
+
const existing = automationConfigSchema.parse(JSON.parse(before ?? "{}"));
|
|
3518
3599
|
const record = await installationRecord(root);
|
|
3519
3600
|
const desired = hookConfig(host, command);
|
|
3520
3601
|
const newCommand = desired.hooks.SessionStart[0].hooks[0].command;
|
|
3521
3602
|
const previous = record.hosts[host]?.command;
|
|
3522
|
-
const
|
|
3603
|
+
const ownershipText = await readText(root, OWNERSHIP_PATH);
|
|
3604
|
+
const pending = ownershipText === null ? [] : ownershipSchema.parse(JSON.parse(ownershipText)).files[file]?.hookCommands ?? [];
|
|
3605
|
+
const owned = /* @__PURE__ */ new Set([previous, ...pending, newCommand]);
|
|
3523
3606
|
const hooks = existing.hooks ?? {};
|
|
3524
3607
|
for (const event of HOOK_EVENTS) {
|
|
3525
3608
|
hooks[event] = (hooks[event] ?? []).map((group) => ({
|
|
@@ -3529,15 +3612,20 @@ async function planAutomationInstall(root, host, command) {
|
|
|
3529
3612
|
hooks[event].push(...desired.hooks[event]);
|
|
3530
3613
|
}
|
|
3531
3614
|
record.hosts[host] = { command: newCommand };
|
|
3532
|
-
return { file, config: { ...existing, hooks }, record, newCommand };
|
|
3615
|
+
return { file, before, config: { ...existing, hooks }, record, newCommand };
|
|
3533
3616
|
}
|
|
3534
3617
|
async function installLocked(root, host, command) {
|
|
3535
|
-
const { file, config, record, newCommand } = await planAutomationInstall(root, host, command);
|
|
3618
|
+
const { file, before, config, record, newCommand } = await planAutomationInstall(root, host, command);
|
|
3536
3619
|
const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3537
3620
|
const { applyEdit: applyEdit2 } = await Promise.resolve().then(() => (init_files2(), files_exports));
|
|
3538
|
-
|
|
3539
|
-
|
|
3621
|
+
const { ownershipEdit: ownershipEdit2, completedHookOwnership: completedHookOwnership2 } = await Promise.resolve().then(() => (init_ownership(), ownership_exports));
|
|
3622
|
+
const edit = { path: file, before, after: JSON.stringify(config, null, 2) + "\n" };
|
|
3623
|
+
const ancillary = await ancillaryEdits2(root);
|
|
3624
|
+
await applyEdit2(root, await ownershipEdit2(root, [edit], { [file]: newCommand }));
|
|
3625
|
+
for (const edit2 of ancillary) await applyEdit2(root, edit2);
|
|
3626
|
+
await applyEdit2(root, edit);
|
|
3540
3627
|
await writeStoreJson(root, AUTOMATION_PATH, record);
|
|
3628
|
+
await applyEdit2(root, await completedHookOwnership2(root, file, newCommand));
|
|
3541
3629
|
return {
|
|
3542
3630
|
version: 1,
|
|
3543
3631
|
host,
|
|
@@ -3575,9 +3663,11 @@ var init_install = __esm({
|
|
|
3575
3663
|
init_evidence();
|
|
3576
3664
|
init_adapters();
|
|
3577
3665
|
init_store();
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3666
|
+
init_files2();
|
|
3667
|
+
init_ownership();
|
|
3668
|
+
groupSchema = z11.object({ hooks: z11.array(z11.object({ type: z11.string(), command: z11.string().optional() }).passthrough()) }).passthrough();
|
|
3669
|
+
automationConfigSchema = z11.object({ hooks: z11.record(z11.array(groupSchema)).optional() }).passthrough();
|
|
3670
|
+
recordSchema = z11.object({ version: z11.literal(1), hosts: z11.record(z11.object({ command: z11.string() })) });
|
|
3581
3671
|
AUTOMATION_PATH = ".mason/local/automation.json";
|
|
3582
3672
|
configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
|
|
3583
3673
|
}
|
|
@@ -3592,7 +3682,7 @@ __export(model_exports, {
|
|
|
3592
3682
|
setupHostSchema: () => setupHostSchema,
|
|
3593
3683
|
setupSchema: () => setupSchema
|
|
3594
3684
|
});
|
|
3595
|
-
import { z as
|
|
3685
|
+
import { z as z12 } from "zod";
|
|
3596
3686
|
async function loadSetup(root) {
|
|
3597
3687
|
const raw = await readStoreJson(root, SETUP_PATH);
|
|
3598
3688
|
if (raw === null) return null;
|
|
@@ -3610,27 +3700,27 @@ var init_model = __esm({
|
|
|
3610
3700
|
"src/setup/model.ts"() {
|
|
3611
3701
|
"use strict";
|
|
3612
3702
|
init_storage();
|
|
3613
|
-
setupHostSchema =
|
|
3614
|
-
configuredVersion:
|
|
3615
|
-
revision:
|
|
3616
|
-
fingerprint:
|
|
3617
|
-
mcpFingerprint:
|
|
3618
|
-
instructions:
|
|
3703
|
+
setupHostSchema = z12.object({
|
|
3704
|
+
configuredVersion: z12.string(),
|
|
3705
|
+
revision: z12.string().uuid(),
|
|
3706
|
+
fingerprint: z12.string(),
|
|
3707
|
+
mcpFingerprint: z12.string(),
|
|
3708
|
+
instructions: z12.array(z12.string())
|
|
3619
3709
|
});
|
|
3620
|
-
setupSchema =
|
|
3710
|
+
setupSchema = z12.object({ version: z12.literal(2), hosts: z12.object({
|
|
3621
3711
|
codex: setupHostSchema.optional(),
|
|
3622
3712
|
claude: setupHostSchema.optional()
|
|
3623
3713
|
}) });
|
|
3624
3714
|
SETUP_PATH = ".mason/local/setup.json";
|
|
3625
|
-
receiptSchema =
|
|
3626
|
-
version:
|
|
3627
|
-
host:
|
|
3628
|
-
status:
|
|
3629
|
-
initialReportPath:
|
|
3630
|
-
initialBaselinePaths:
|
|
3631
|
-
root:
|
|
3632
|
-
revision:
|
|
3633
|
-
configuredAt:
|
|
3715
|
+
receiptSchema = z12.object({
|
|
3716
|
+
version: z12.literal(1),
|
|
3717
|
+
host: z12.enum(["codex", "claude"]),
|
|
3718
|
+
status: z12.enum(["installing", "configured"]),
|
|
3719
|
+
initialReportPath: z12.string(),
|
|
3720
|
+
initialBaselinePaths: z12.array(z12.string()),
|
|
3721
|
+
root: z12.string(),
|
|
3722
|
+
revision: z12.string().uuid(),
|
|
3723
|
+
configuredAt: z12.string().optional()
|
|
3634
3724
|
});
|
|
3635
3725
|
}
|
|
3636
3726
|
});
|
|
@@ -3662,7 +3752,7 @@ var init_launcher = __esm({
|
|
|
3662
3752
|
"use strict";
|
|
3663
3753
|
init_evidence();
|
|
3664
3754
|
init_model();
|
|
3665
|
-
executingVersion = () => true ? "0.
|
|
3755
|
+
executingVersion = () => true ? "0.16.1" : "development";
|
|
3666
3756
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
3667
3757
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
3668
3758
|
}
|
|
@@ -3679,8 +3769,8 @@ __export(config_exports, {
|
|
|
3679
3769
|
mcpPath: () => mcpPath
|
|
3680
3770
|
});
|
|
3681
3771
|
import { isDeepStrictEqual } from "util";
|
|
3682
|
-
import { parse, stringify } from "smol-toml";
|
|
3683
|
-
import { z as
|
|
3772
|
+
import { parse as parse2, stringify } from "smol-toml";
|
|
3773
|
+
import { z as z13 } from "zod";
|
|
3684
3774
|
async function instructionEdits(root, host) {
|
|
3685
3775
|
const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
|
|
3686
3776
|
const existing = found.find((f) => f.text !== null);
|
|
@@ -3721,7 +3811,7 @@ async function mcpEdit(root, host) {
|
|
|
3721
3811
|
const servers2 = object.parse(config2.mcpServers ?? {});
|
|
3722
3812
|
return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host) } }, null, 2) + "\n" };
|
|
3723
3813
|
}
|
|
3724
|
-
const config =
|
|
3814
|
+
const config = parse2(before ?? "");
|
|
3725
3815
|
const managed = (before ?? "").includes(TOML_START);
|
|
3726
3816
|
const servers = config.mcp_servers;
|
|
3727
3817
|
const desired = managedMcp(servers?.mason, host);
|
|
@@ -3733,7 +3823,7 @@ async function mcpEdit(root, host) {
|
|
|
3733
3823
|
for (const span of spans.reverse()) base = base.slice(0, span.start) + base.slice(span.end);
|
|
3734
3824
|
}
|
|
3735
3825
|
const after = managedBlock(base, TOML_START, TOML_END, stringify({ mcp_servers: { mason: desired } }).trimEnd());
|
|
3736
|
-
const parsed =
|
|
3826
|
+
const parsed = parse2(after);
|
|
3737
3827
|
const expected = { ...config, mcp_servers: { ...servers ?? {}, mason: desired } };
|
|
3738
3828
|
if (!isDeepStrictEqual(parsed, expected)) throw new Error("Could not safely configure the Mason MCP entry without changing other settings.");
|
|
3739
3829
|
return { path: file, before, after };
|
|
@@ -3771,7 +3861,7 @@ async function ancillaryEdits(root) {
|
|
|
3771
3861
|
}
|
|
3772
3862
|
async function inspectHostConfig(root, host, plannedMcp) {
|
|
3773
3863
|
const text2 = plannedMcp ?? await readText(root, mcpPath(host));
|
|
3774
|
-
const config = host === "codex" ?
|
|
3864
|
+
const config = host === "codex" ? parse2(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
|
|
3775
3865
|
const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
|
|
3776
3866
|
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
|
|
3777
3867
|
const expected = hookConfig(host, hookCommand(host));
|
|
@@ -3796,7 +3886,7 @@ var init_config = __esm({
|
|
|
3796
3886
|
init_install();
|
|
3797
3887
|
init_files2();
|
|
3798
3888
|
init_launcher();
|
|
3799
|
-
object =
|
|
3889
|
+
object = z13.record(z13.unknown());
|
|
3800
3890
|
mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
3801
3891
|
TOML_START = "# mason:mcp:start";
|
|
3802
3892
|
TOML_END = "# mason:mcp:end";
|
|
@@ -3806,7 +3896,7 @@ var init_config = __esm({
|
|
|
3806
3896
|
});
|
|
3807
3897
|
|
|
3808
3898
|
// src/mcp/init.ts
|
|
3809
|
-
import { z as
|
|
3899
|
+
import { z as z14 } from "zod";
|
|
3810
3900
|
async function loadProjectMarker(rootDir) {
|
|
3811
3901
|
const raw = await readStoreJson(rootDir, ".mason/local/project.json");
|
|
3812
3902
|
if (raw === null) return null;
|
|
@@ -3814,8 +3904,8 @@ async function loadProjectMarker(rootDir) {
|
|
|
3814
3904
|
return { ...marker, features: await loadProjectFeatures(rootDir) };
|
|
3815
3905
|
}
|
|
3816
3906
|
async function loadProjectFeatures(rootDir) {
|
|
3817
|
-
const config =
|
|
3818
|
-
return
|
|
3907
|
+
const config = z14.record(z14.unknown()).parse(await readStoreJson(rootDir, ".mason/config.json") ?? {});
|
|
3908
|
+
return z14.object({ confluence: z14.boolean().optional() }).passthrough().parse(config.features ?? {});
|
|
3819
3909
|
}
|
|
3820
3910
|
async function saveProjectMarker(rootDir, marker) {
|
|
3821
3911
|
const { ancillaryEdits: ancillaryEdits2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
@@ -3827,8 +3917,8 @@ async function projectMarkerEdits(rootDir, marker) {
|
|
|
3827
3917
|
const edits = [{ path: file, before: await readText(rootDir, file), after: JSON.stringify(local, null, 2) + "\n" }];
|
|
3828
3918
|
if (features && Object.keys(features).length) {
|
|
3829
3919
|
const configPath2 = ".mason/config.json", before = await readText(rootDir, configPath2);
|
|
3830
|
-
const config =
|
|
3831
|
-
config.features = { ...
|
|
3920
|
+
const config = z14.record(z14.unknown()).parse(JSON.parse(before ?? "{}"));
|
|
3921
|
+
config.features = { ...z14.record(z14.unknown()).parse(config.features ?? {}), ...features };
|
|
3832
3922
|
edits.push({ path: configPath2, before, after: JSON.stringify(config, null, 2) + "\n" });
|
|
3833
3923
|
}
|
|
3834
3924
|
return edits;
|
|
@@ -3842,19 +3932,19 @@ var init_init = __esm({
|
|
|
3842
3932
|
"use strict";
|
|
3843
3933
|
init_storage();
|
|
3844
3934
|
init_files2();
|
|
3845
|
-
markerSchema =
|
|
3846
|
-
version:
|
|
3847
|
-
initializedAt:
|
|
3848
|
-
features:
|
|
3935
|
+
markerSchema = z14.object({
|
|
3936
|
+
version: z14.literal(1),
|
|
3937
|
+
initializedAt: z14.string(),
|
|
3938
|
+
features: z14.object({ confluence: z14.boolean().optional() }).passthrough().optional()
|
|
3849
3939
|
}).passthrough();
|
|
3850
3940
|
CLAUDE_MD_SECTION = `<!-- mason:start -->
|
|
3851
3941
|
## Mason project knowledge
|
|
3852
3942
|
|
|
3853
3943
|
Mason provides recorded decisions and file impact over MCP. A concept map is optional.
|
|
3854
3944
|
|
|
3855
|
-
- Task, bug,
|
|
3945
|
+
- Task, bug, change request, or investigation of why the project works this way \u2192 \`get_context\` with the task text and known files: matching decisions, related tests, impact, and any available map entries.
|
|
3856
3946
|
- Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
|
|
3857
|
-
-
|
|
3947
|
+
- Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` before finishing. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
|
|
3858
3948
|
- Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, and failed verification means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
|
|
3859
3949
|
- Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
|
|
3860
3950
|
- For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
|
|
@@ -4039,7 +4129,7 @@ var init_types2 = __esm({
|
|
|
4039
4129
|
});
|
|
4040
4130
|
|
|
4041
4131
|
// src/review/evidence/vitest.ts
|
|
4042
|
-
import { z as
|
|
4132
|
+
import { z as z15 } from "zod";
|
|
4043
4133
|
function parseVitest(raw, sourceRoot) {
|
|
4044
4134
|
const report = schema.parse(raw);
|
|
4045
4135
|
const findings = [], diagnostics = [];
|
|
@@ -4088,24 +4178,24 @@ var init_vitest = __esm({
|
|
|
4088
4178
|
"use strict";
|
|
4089
4179
|
init_paths2();
|
|
4090
4180
|
init_types2();
|
|
4091
|
-
count2 =
|
|
4092
|
-
schema =
|
|
4093
|
-
success:
|
|
4181
|
+
count2 = z15.number().int().nonnegative();
|
|
4182
|
+
schema = z15.object({
|
|
4183
|
+
success: z15.boolean(),
|
|
4094
4184
|
numTotalTests: count2,
|
|
4095
4185
|
numPassedTests: count2,
|
|
4096
4186
|
numFailedTests: count2,
|
|
4097
4187
|
numPendingTests: count2,
|
|
4098
4188
|
numTodoTests: count2,
|
|
4099
4189
|
numFailedTestSuites: count2,
|
|
4100
|
-
testResults:
|
|
4101
|
-
name:
|
|
4102
|
-
status:
|
|
4103
|
-
message:
|
|
4104
|
-
assertionResults:
|
|
4105
|
-
fullName:
|
|
4106
|
-
status:
|
|
4107
|
-
failureMessages:
|
|
4108
|
-
location:
|
|
4190
|
+
testResults: z15.array(z15.object({
|
|
4191
|
+
name: z15.string().min(1),
|
|
4192
|
+
status: z15.enum(["passed", "failed"]),
|
|
4193
|
+
message: z15.string().optional(),
|
|
4194
|
+
assertionResults: z15.array(z15.object({
|
|
4195
|
+
fullName: z15.string(),
|
|
4196
|
+
status: z15.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
|
|
4197
|
+
failureMessages: z15.array(z15.string()).nullable().optional(),
|
|
4198
|
+
location: z15.object({ line: count2, column: count2 }).nullable().optional()
|
|
4109
4199
|
}))
|
|
4110
4200
|
}))
|
|
4111
4201
|
});
|
|
@@ -4113,7 +4203,7 @@ var init_vitest = __esm({
|
|
|
4113
4203
|
});
|
|
4114
4204
|
|
|
4115
4205
|
// src/review/evidence/sarif.ts
|
|
4116
|
-
import { z as
|
|
4206
|
+
import { z as z16 } from "zod";
|
|
4117
4207
|
function parseSarif(raw, sourceRoot) {
|
|
4118
4208
|
const report = schema2.parse(raw), findings = [], diagnostics = [];
|
|
4119
4209
|
let active = 0, suppressed = 0, absent = 0, unresolved = 0;
|
|
@@ -4204,29 +4294,29 @@ var init_sarif = __esm({
|
|
|
4204
4294
|
"use strict";
|
|
4205
4295
|
init_paths2();
|
|
4206
4296
|
init_types2();
|
|
4207
|
-
index =
|
|
4208
|
-
artifact =
|
|
4209
|
-
message =
|
|
4210
|
-
level =
|
|
4211
|
-
location =
|
|
4212
|
-
schema2 =
|
|
4213
|
-
version:
|
|
4214
|
-
runs:
|
|
4215
|
-
tool:
|
|
4216
|
-
invocations:
|
|
4217
|
-
versionControlProvenance:
|
|
4218
|
-
originalUriBaseIds:
|
|
4219
|
-
artifacts:
|
|
4220
|
-
results:
|
|
4221
|
-
ruleId:
|
|
4297
|
+
index = z16.number().int().nonnegative();
|
|
4298
|
+
artifact = z16.object({ uri: z16.string().optional(), uriBaseId: z16.string().optional(), index: index.optional() });
|
|
4299
|
+
message = z16.object({ text: z16.string().optional(), markdown: z16.string().optional(), id: z16.string().optional(), arguments: z16.array(z16.string()).optional() });
|
|
4300
|
+
level = z16.enum(["error", "warning", "note", "none"]);
|
|
4301
|
+
location = z16.object({ physicalLocation: z16.object({ artifactLocation: artifact.optional(), region: z16.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
|
|
4302
|
+
schema2 = z16.object({
|
|
4303
|
+
version: z16.literal("2.1.0"),
|
|
4304
|
+
runs: z16.array(z16.object({
|
|
4305
|
+
tool: z16.object({ driver: z16.object({ name: z16.string().min(1), rules: z16.array(z16.object({ id: z16.string(), defaultConfiguration: z16.object({ level: level.optional() }).optional(), messageStrings: z16.record(message).optional() })).optional(), globalMessageStrings: z16.record(message).optional() }) }),
|
|
4306
|
+
invocations: z16.array(z16.object({ executionSuccessful: z16.boolean(), commandLine: z16.string().optional(), toolExecutionNotifications: z16.array(z16.object({ level: level.optional(), message })).optional() })).optional(),
|
|
4307
|
+
versionControlProvenance: z16.array(z16.object({ revisionId: z16.string().optional() })).optional(),
|
|
4308
|
+
originalUriBaseIds: z16.record(artifact).optional(),
|
|
4309
|
+
artifacts: z16.array(z16.object({ location: artifact.optional() })).optional(),
|
|
4310
|
+
results: z16.array(z16.object({
|
|
4311
|
+
ruleId: z16.string().optional(),
|
|
4222
4312
|
ruleIndex: index.optional(),
|
|
4223
4313
|
message,
|
|
4224
4314
|
level: level.optional(),
|
|
4225
|
-
kind:
|
|
4226
|
-
baselineState:
|
|
4227
|
-
suppressions:
|
|
4228
|
-
locations:
|
|
4229
|
-
relatedLocations:
|
|
4315
|
+
kind: z16.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
|
|
4316
|
+
baselineState: z16.enum(["new", "unchanged", "updated", "absent"]).optional(),
|
|
4317
|
+
suppressions: z16.array(z16.object({ kind: z16.enum(["inSource", "external"]), status: z16.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
|
|
4318
|
+
locations: z16.array(location).optional(),
|
|
4319
|
+
relatedLocations: z16.array(location).optional()
|
|
4230
4320
|
})).optional()
|
|
4231
4321
|
}))
|
|
4232
4322
|
});
|
|
@@ -4235,7 +4325,7 @@ var init_sarif = __esm({
|
|
|
4235
4325
|
|
|
4236
4326
|
// src/review/evidence.ts
|
|
4237
4327
|
import path21 from "path";
|
|
4238
|
-
import { z as
|
|
4328
|
+
import { z as z17 } from "zod";
|
|
4239
4329
|
function relativeArtifact(root, file) {
|
|
4240
4330
|
const relative = path21.isAbsolute(file) ? path21.relative(root, file) : file;
|
|
4241
4331
|
const normalized = normalizeRepoPath(relative);
|
|
@@ -4276,7 +4366,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
4276
4366
|
let manifest, raw;
|
|
4277
4367
|
try {
|
|
4278
4368
|
manifest = relativeArtifact(root, supplied);
|
|
4279
|
-
raw =
|
|
4369
|
+
raw = z17.object({ version: z17.literal(1), checks: z17.array(z17.unknown()) }).parse(await readStoreJson(root, manifest));
|
|
4280
4370
|
} catch (error) {
|
|
4281
4371
|
output.diagnostics.push(`${supplied}: ${String(error)}`);
|
|
4282
4372
|
continue;
|
|
@@ -4429,19 +4519,19 @@ var init_evidence2 = __esm({
|
|
|
4429
4519
|
init_vitest();
|
|
4430
4520
|
init_sarif();
|
|
4431
4521
|
init_types2();
|
|
4432
|
-
checkSchema2 =
|
|
4433
|
-
id:
|
|
4434
|
-
kind:
|
|
4435
|
-
tool:
|
|
4436
|
-
command:
|
|
4437
|
-
commit:
|
|
4438
|
-
workingTreeClean:
|
|
4439
|
-
source:
|
|
4440
|
-
sourceRoot:
|
|
4441
|
-
status:
|
|
4442
|
-
reason:
|
|
4443
|
-
exitCode:
|
|
4444
|
-
report:
|
|
4522
|
+
checkSchema2 = z17.object({
|
|
4523
|
+
id: z17.string().min(1).max(100),
|
|
4524
|
+
kind: z17.enum(["tests", "static-analysis", "security", "complexity", "duplication"]),
|
|
4525
|
+
tool: z17.string().min(1).max(200),
|
|
4526
|
+
command: z17.string().min(1).max(2e3),
|
|
4527
|
+
commit: z17.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
|
|
4528
|
+
workingTreeClean: z17.boolean().optional(),
|
|
4529
|
+
source: z17.string().max(2e3).optional(),
|
|
4530
|
+
sourceRoot: z17.string().min(1).max(4e3).optional(),
|
|
4531
|
+
status: z17.enum(["completed", "skipped", "unavailable"]).default("completed"),
|
|
4532
|
+
reason: z17.string().min(1).max(2e3).optional(),
|
|
4533
|
+
exitCode: z17.number().int().nullable().optional(),
|
|
4534
|
+
report: z17.object({ format: z17.enum(["vitest-json", "sarif"]), path: z17.string().min(1).max(4e3) }).optional()
|
|
4445
4535
|
});
|
|
4446
4536
|
}
|
|
4447
4537
|
});
|
|
@@ -4661,16 +4751,204 @@ var init_onboarding = __esm({
|
|
|
4661
4751
|
}
|
|
4662
4752
|
});
|
|
4663
4753
|
|
|
4754
|
+
// src/impact/references.ts
|
|
4755
|
+
import path23 from "path";
|
|
4756
|
+
function templateEnd(content2, start) {
|
|
4757
|
+
let i = start + 1, interpolation = 0;
|
|
4758
|
+
while (i < content2.length) {
|
|
4759
|
+
const c = content2[i];
|
|
4760
|
+
if (c === "\\") {
|
|
4761
|
+
i += 2;
|
|
4762
|
+
continue;
|
|
4763
|
+
}
|
|
4764
|
+
if (!interpolation) {
|
|
4765
|
+
if (c === "`") return i + 1;
|
|
4766
|
+
if (c === "$" && content2[i + 1] === "{") {
|
|
4767
|
+
interpolation = 1;
|
|
4768
|
+
i += 2;
|
|
4769
|
+
continue;
|
|
4770
|
+
}
|
|
4771
|
+
} else {
|
|
4772
|
+
if (c === "`") {
|
|
4773
|
+
i = templateEnd(content2, i);
|
|
4774
|
+
continue;
|
|
4775
|
+
}
|
|
4776
|
+
if (c === '"' || c === "'") {
|
|
4777
|
+
const quote = c;
|
|
4778
|
+
for (i++; i < content2.length; i++) {
|
|
4779
|
+
if (content2[i] === "\\") i++;
|
|
4780
|
+
else if (content2[i] === quote) {
|
|
4781
|
+
i++;
|
|
4782
|
+
break;
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4785
|
+
continue;
|
|
4786
|
+
}
|
|
4787
|
+
if (content2.startsWith("//", i)) {
|
|
4788
|
+
const end = content2.indexOf("\n", i + 2);
|
|
4789
|
+
i = end < 0 ? content2.length : end;
|
|
4790
|
+
continue;
|
|
4791
|
+
}
|
|
4792
|
+
if (content2.startsWith("/*", i)) {
|
|
4793
|
+
const end = content2.indexOf("*/", i + 2);
|
|
4794
|
+
i = end < 0 ? content2.length : end + 2;
|
|
4795
|
+
continue;
|
|
4796
|
+
}
|
|
4797
|
+
if (c === "{") interpolation++;
|
|
4798
|
+
if (c === "}") interpolation--;
|
|
4799
|
+
}
|
|
4800
|
+
i++;
|
|
4801
|
+
}
|
|
4802
|
+
return content2.length;
|
|
4803
|
+
}
|
|
4804
|
+
function tokens(content2) {
|
|
4805
|
+
const result = [];
|
|
4806
|
+
const re = /\/\/[^\n]*|\/\*[\s\S]*?(?:\*\/|$)|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|[\w$]+|[^\s]/g;
|
|
4807
|
+
let match;
|
|
4808
|
+
while (match = re.exec(content2)) {
|
|
4809
|
+
const value = match[0];
|
|
4810
|
+
if (value.startsWith("//") || value.startsWith("/*")) continue;
|
|
4811
|
+
if (/^["'`]/.test(value)) {
|
|
4812
|
+
if (value[0] === "`") {
|
|
4813
|
+
re.lastIndex = templateEnd(content2, match.index);
|
|
4814
|
+
result.push({ value: "<opaque>" });
|
|
4815
|
+
continue;
|
|
4816
|
+
}
|
|
4817
|
+
result.push({ value: value.slice(1, -1), string: !value.includes("\\") && value[0] !== "`" });
|
|
4818
|
+
if (!result.at(-1).string) result[result.length - 1] = { value: "<opaque>" };
|
|
4819
|
+
} else result.push({ value });
|
|
4820
|
+
}
|
|
4821
|
+
return result;
|
|
4822
|
+
}
|
|
4823
|
+
function imports(content2) {
|
|
4824
|
+
const ts = tokens(content2), found = [];
|
|
4825
|
+
for (let i = 0; i < ts.length; i++) {
|
|
4826
|
+
const t = ts[i];
|
|
4827
|
+
if (t.string || ts[i - 1]?.value === ".") continue;
|
|
4828
|
+
if (["require", "import"].includes(t.value) && ts[i + 1]?.value === "(" && ts[i + 2]?.string && ts[i + 3]?.value === ")") {
|
|
4829
|
+
found.push(ts[i + 2].value);
|
|
4830
|
+
}
|
|
4831
|
+
if (!["import", "export"].includes(t.value) || ts[i + 1]?.value === "(") continue;
|
|
4832
|
+
if (t.value === "import" && ts[i + 1]?.string) {
|
|
4833
|
+
found.push(ts[i + 1].value);
|
|
4834
|
+
continue;
|
|
4835
|
+
}
|
|
4836
|
+
for (let j = i + 1; j < Math.min(ts.length, i + 150); j++) {
|
|
4837
|
+
if ([";", "=", "import", "export"].includes(ts[j].value) && !ts[j].string) break;
|
|
4838
|
+
if (ts[j].value === "from" && !ts[j].string && ts[j + 1]?.string) {
|
|
4839
|
+
found.push(ts[j + 1].value);
|
|
4840
|
+
break;
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
return found;
|
|
4845
|
+
}
|
|
4846
|
+
function qualifiedName(ts, start) {
|
|
4847
|
+
let name = "";
|
|
4848
|
+
for (let i = start; i < ts.length; i++) {
|
|
4849
|
+
if (ts[i].string || !/^[A-Za-z_$][\w$]*$/.test(ts[i].value)) break;
|
|
4850
|
+
name += ts[i].value;
|
|
4851
|
+
if (ts[i + 1]?.value !== "." || !/^[A-Za-z_$][\w$]*$/.test(ts[i + 2]?.value ?? "")) break;
|
|
4852
|
+
name += ".";
|
|
4853
|
+
i++;
|
|
4854
|
+
}
|
|
4855
|
+
return name;
|
|
4856
|
+
}
|
|
4857
|
+
function declaredSymbols(content2) {
|
|
4858
|
+
const ts = tokens(content2);
|
|
4859
|
+
const packageAt = ts.findIndex((t) => !t.string && t.value === "package");
|
|
4860
|
+
if (packageAt < 0) return [];
|
|
4861
|
+
const pkg = qualifiedName(ts, packageAt + 1), names = [];
|
|
4862
|
+
let depth = 0;
|
|
4863
|
+
for (let i = 0; i < ts.length; i++) {
|
|
4864
|
+
if (ts[i].string) continue;
|
|
4865
|
+
if (ts[i].value === "{") depth++;
|
|
4866
|
+
if (ts[i].value === "}") depth--;
|
|
4867
|
+
if (depth === 0 && ["class", "interface", "object", "enum", "typealias", "fun"].includes(ts[i].value) && /^[A-Za-z_$][\w$]*$/.test(ts[i + 1]?.value ?? "") && ts[i + 1].value !== "class") names.push(pkg + "." + ts[i + 1].value);
|
|
4868
|
+
}
|
|
4869
|
+
return names;
|
|
4870
|
+
}
|
|
4871
|
+
function jvmImports(content2) {
|
|
4872
|
+
const ts = tokens(content2), result = [];
|
|
4873
|
+
for (let i = 0; i < ts.length; i++) if (!ts[i].string && ts[i].value === "import") {
|
|
4874
|
+
const name = qualifiedName(ts, i + (ts[i + 1]?.value === "static" ? 2 : 1));
|
|
4875
|
+
if (name) result.push(name);
|
|
4876
|
+
}
|
|
4877
|
+
return result;
|
|
4878
|
+
}
|
|
4879
|
+
function resolveImport(file, specifier, available) {
|
|
4880
|
+
if (!specifier.startsWith("./") && !specifier.startsWith("../")) return null;
|
|
4881
|
+
const relative = path23.posix.normalize(path23.posix.join(path23.posix.dirname(file), specifier));
|
|
4882
|
+
if (relative.startsWith("../")) return null;
|
|
4883
|
+
if (available.has(relative)) return relative;
|
|
4884
|
+
const ext = path23.posix.extname(relative);
|
|
4885
|
+
const replacements = { ".js": [".ts", ".tsx"], ".mjs": [".mts"], ".cjs": [".cts"] };
|
|
4886
|
+
const candidates = ext ? (replacements[ext] ?? []).map((e) => relative.slice(0, -ext.length) + e) : jsExtensions.flatMap((e) => [relative + e, relative + "/index" + e]);
|
|
4887
|
+
const found = candidates.filter((f) => available.has(f));
|
|
4888
|
+
return found.length === 1 ? found[0] : null;
|
|
4889
|
+
}
|
|
4890
|
+
function mentionsPath(content2, target) {
|
|
4891
|
+
return new RegExp(`(?<![\\w./\\\\-])(?:\\./)?${escape(target)}(?![\\w./\\\\-])`).test(content2);
|
|
4892
|
+
}
|
|
4893
|
+
function createReferenceMatcher(targets, available, targetContents = /* @__PURE__ */ new Map()) {
|
|
4894
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4895
|
+
for (const file of available) {
|
|
4896
|
+
const name = path23.posix.basename(file, path23.posix.extname(file));
|
|
4897
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
4898
|
+
}
|
|
4899
|
+
const jvmTargets = new Map([...targetContents].filter(([file]) => /\.(?:kt|kts|java)$/.test(file)).map(([file, content2]) => [file, declaredSymbols(content2)]));
|
|
4900
|
+
return (source) => {
|
|
4901
|
+
if (targets.includes(source.path)) return [];
|
|
4902
|
+
const resolved = new Set(jsExtensions.includes(path23.posix.extname(source.path)) ? imports(source.content).map((s) => resolveImport(source.path, s, available)).filter(Boolean) : []);
|
|
4903
|
+
if (/\.(?:kt|kts|java)$/.test(source.path)) {
|
|
4904
|
+
const imported = jvmImports(source.content);
|
|
4905
|
+
for (const [target, symbols] of jvmTargets) if (symbols.some((symbol) => imported.some((name) => name === symbol || name.startsWith(symbol + ".")))) resolved.add(target);
|
|
4906
|
+
}
|
|
4907
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4908
|
+
const add = (evidence, match) => groups.set(evidence, [...groups.get(evidence) ?? [], match]);
|
|
4909
|
+
for (const target of targets) {
|
|
4910
|
+
if (resolved.has(target)) {
|
|
4911
|
+
add("resolved-import", target);
|
|
4912
|
+
continue;
|
|
4913
|
+
}
|
|
4914
|
+
if (mentionsPath(source.content, target)) {
|
|
4915
|
+
add("explicit-path", target);
|
|
4916
|
+
continue;
|
|
4917
|
+
}
|
|
4918
|
+
const ext = path23.posix.extname(target), name = path23.posix.basename(target, ext);
|
|
4919
|
+
if (!/\.(?:[cm]?[jt]sx?|py|kt|java|go|rs|swift|rb|cs|cpp|c|h|hpp|dart|php)$/.test(ext) || name.startsWith(".") || name.length < 3 || genericNames.has(name.toLowerCase()) || counts.get(name) !== 1) continue;
|
|
4920
|
+
if (word(name).test(source.content)) {
|
|
4921
|
+
add("name-candidate", name);
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
return [...groups].map(([evidence, matches]) => ({ file: source.path, matches, kind: evidence === "resolved-import" ? "import" : "mention", evidence }));
|
|
4925
|
+
};
|
|
4926
|
+
}
|
|
4927
|
+
function sortReferences(result) {
|
|
4928
|
+
const rank = { "resolved-import": 0, "explicit-path": 1, "name-candidate": 2 };
|
|
4929
|
+
return result.sort((a, b) => rank[a.evidence] - rank[b.evidence] || b.matches.length - a.matches.length || a.file.localeCompare(b.file));
|
|
4930
|
+
}
|
|
4931
|
+
var jsExtensions, genericNames, escape, word;
|
|
4932
|
+
var init_references = __esm({
|
|
4933
|
+
"src/impact/references.ts"() {
|
|
4934
|
+
"use strict";
|
|
4935
|
+
jsExtensions = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"];
|
|
4936
|
+
genericNames = /* @__PURE__ */ new Set(["index", "main", "app", "init", "config", "settings", "types", "utils", "test", "tests"]);
|
|
4937
|
+
escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4938
|
+
word = (s) => new RegExp(`(?<![\\w$])${escape(s)}(?![\\w$])`);
|
|
4939
|
+
}
|
|
4940
|
+
});
|
|
4941
|
+
|
|
4664
4942
|
// src/impact/impact.ts
|
|
4665
4943
|
var impact_exports = {};
|
|
4666
4944
|
__export(impact_exports, {
|
|
4667
4945
|
analyzeImpact: () => analyzeImpact
|
|
4668
4946
|
});
|
|
4669
|
-
import
|
|
4947
|
+
import path24 from "path";
|
|
4670
4948
|
import { execFile as execFile16 } from "child_process";
|
|
4671
4949
|
import { promisify as promisify16 } from "util";
|
|
4672
4950
|
async function analyzeImpact(rootDir, targetFiles) {
|
|
4673
|
-
const resolvedRoot =
|
|
4951
|
+
const resolvedRoot = path24.resolve(rootDir);
|
|
4674
4952
|
const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
|
|
4675
4953
|
const [cochange, references, tests] = await Promise.all([
|
|
4676
4954
|
getCochangeFiles(resolvedRoot, resolvedTargets),
|
|
@@ -4747,52 +5025,24 @@ async function getCochangeFiles(rootDir, targetFiles) {
|
|
|
4747
5025
|
})).filter((e) => e.cochangeRate >= 0.3 || e.sharedCommits >= 3).sort((a, b) => b.cochangeRate - a.cochangeRate).slice(0, 20);
|
|
4748
5026
|
}
|
|
4749
5027
|
async function getReferences(rootDir, targetFiles) {
|
|
4750
|
-
const searchNames = /* @__PURE__ */ new Set();
|
|
4751
|
-
for (const target of targetFiles) {
|
|
4752
|
-
const basename = path23.basename(target).replace(/\.[^.]+$/, "");
|
|
4753
|
-
searchNames.add(basename);
|
|
4754
|
-
}
|
|
4755
5028
|
const access = await createFileAccess(rootDir);
|
|
4756
5029
|
const allSourceFiles = await access.list(SOURCE_GLOB);
|
|
4757
|
-
const
|
|
4758
|
-
const
|
|
4759
|
-
const
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
batch.map(async (file) => {
|
|
4766
|
-
try {
|
|
4767
|
-
const full = await access.read(file);
|
|
4768
|
-
if (!full) return;
|
|
4769
|
-
const content2 = full.content;
|
|
4770
|
-
const lines = content2.split("\n");
|
|
4771
|
-
for (const name of searchNames) {
|
|
4772
|
-
const regex = new RegExp(`\\b${escapeRegex(name)}\\b`);
|
|
4773
|
-
if (!regex.test(content2)) continue;
|
|
4774
|
-
if (!results.has(file)) {
|
|
4775
|
-
results.set(file, { matches: /* @__PURE__ */ new Set(), isImport: false });
|
|
4776
|
-
}
|
|
4777
|
-
const entry = results.get(file);
|
|
4778
|
-
entry.matches.add(name);
|
|
4779
|
-
if (!entry.isImport && lines.some((l) => regex.test(l) && importLine.test(l))) {
|
|
4780
|
-
entry.isImport = true;
|
|
4781
|
-
}
|
|
4782
|
-
}
|
|
4783
|
-
} catch {
|
|
4784
|
-
}
|
|
4785
|
-
})
|
|
4786
|
-
);
|
|
5030
|
+
const available = new Set(allSourceFiles);
|
|
5031
|
+
const targetContents = /* @__PURE__ */ new Map();
|
|
5032
|
+
for (const target of targetFiles) {
|
|
5033
|
+
const source = await access.read(target);
|
|
5034
|
+
if (source) {
|
|
5035
|
+
available.add(target);
|
|
5036
|
+
targetContents.set(target, source.content);
|
|
5037
|
+
}
|
|
4787
5038
|
}
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
});
|
|
5039
|
+
const match = createReferenceMatcher(targetFiles, available, targetContents);
|
|
5040
|
+
const references = [];
|
|
5041
|
+
for (let i = 0; i < allSourceFiles.length; i += 50) {
|
|
5042
|
+
const batch = await Promise.all(allSourceFiles.slice(i, i + 50).map((file) => access.read(file)));
|
|
5043
|
+
for (const source of batch) if (source) references.push(...match(source));
|
|
5044
|
+
}
|
|
5045
|
+
return sortReferences(references);
|
|
4796
5046
|
}
|
|
4797
5047
|
async function getRelatedTests(rootDir, targetFiles) {
|
|
4798
5048
|
const testPatterns = [
|
|
@@ -4812,9 +5062,9 @@ async function getRelatedTests(rootDir, targetFiles) {
|
|
|
4812
5062
|
const testFiles = await (await createFileAccess(rootDir)).list(testPatterns);
|
|
4813
5063
|
const results = [];
|
|
4814
5064
|
for (const target of targetFiles) {
|
|
4815
|
-
const targetBaseName =
|
|
5065
|
+
const targetBaseName = path24.basename(target).replace(/\.[^.]+$/, "");
|
|
4816
5066
|
for (const testFile of testFiles) {
|
|
4817
|
-
const testBaseName =
|
|
5067
|
+
const testBaseName = path24.basename(testFile).replace(/\.[^.]+$/, "");
|
|
4818
5068
|
const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
|
|
4819
5069
|
if (sourceName === targetBaseName) {
|
|
4820
5070
|
results.push({
|
|
@@ -4826,15 +5076,13 @@ async function getRelatedTests(rootDir, targetFiles) {
|
|
|
4826
5076
|
}
|
|
4827
5077
|
return results;
|
|
4828
5078
|
}
|
|
4829
|
-
function escapeRegex(str) {
|
|
4830
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4831
|
-
}
|
|
4832
5079
|
var exec14;
|
|
4833
5080
|
var init_impact = __esm({
|
|
4834
5081
|
"src/impact/impact.ts"() {
|
|
4835
5082
|
"use strict";
|
|
4836
5083
|
init_files();
|
|
4837
5084
|
init_paths();
|
|
5085
|
+
init_references();
|
|
4838
5086
|
exec14 = promisify16(execFile16);
|
|
4839
5087
|
}
|
|
4840
5088
|
});
|
|
@@ -4844,13 +5092,13 @@ var assemble_exports = {};
|
|
|
4844
5092
|
__export(assemble_exports, {
|
|
4845
5093
|
assembleContext: () => assembleContext
|
|
4846
5094
|
});
|
|
4847
|
-
import
|
|
5095
|
+
import path25 from "path";
|
|
4848
5096
|
import fs17 from "fs/promises";
|
|
4849
5097
|
async function collectImpact(root, candidates) {
|
|
4850
5098
|
const targets = /* @__PURE__ */ new Set();
|
|
4851
5099
|
let sourceFiles;
|
|
4852
5100
|
for (const candidate of sanitizeRepoPaths(candidates)) {
|
|
4853
|
-
const stat = await fs17.stat(
|
|
5101
|
+
const stat = await fs17.stat(path25.join(root, candidate)).catch(() => null);
|
|
4854
5102
|
if (stat?.isDirectory()) {
|
|
4855
5103
|
sourceFiles ??= await (await createFileAccess(root)).list();
|
|
4856
5104
|
for (const file of sourceFiles) {
|
|
@@ -4864,13 +5112,19 @@ async function collectImpact(root, candidates) {
|
|
|
4864
5112
|
}
|
|
4865
5113
|
if (!targets.size) return { impact: null, relatedTests: [] };
|
|
4866
5114
|
const result = await analyzeImpact(root, [...targets]);
|
|
5115
|
+
const referenceCandidatesOmitted = result.references.filter((r) => r.evidence === "name-candidate").length;
|
|
4867
5116
|
return {
|
|
4868
|
-
impact: {
|
|
5117
|
+
impact: {
|
|
5118
|
+
targets: result.targetFiles,
|
|
5119
|
+
cochange: result.cochange,
|
|
5120
|
+
references: result.references.filter((r) => r.evidence !== "name-candidate").slice(0, 10),
|
|
5121
|
+
...referenceCandidatesOmitted ? { referenceCandidatesOmitted } : {}
|
|
5122
|
+
},
|
|
4869
5123
|
relatedTests: [...new Set(result.tests.map((t) => t.file))]
|
|
4870
5124
|
};
|
|
4871
5125
|
}
|
|
4872
5126
|
async function assembleContext(rootDir, task, files) {
|
|
4873
|
-
const resolvedRoot =
|
|
5127
|
+
const resolvedRoot = path25.resolve(rootDir);
|
|
4874
5128
|
const mapState = await inspectSnapshot(resolvedRoot);
|
|
4875
5129
|
const snapshot = mapState.snapshot;
|
|
4876
5130
|
const store = await loadDecisionStore(resolvedRoot);
|
|
@@ -5072,7 +5326,7 @@ __export(observations_exports, {
|
|
|
5072
5326
|
readObservation: () => readObservation
|
|
5073
5327
|
});
|
|
5074
5328
|
import fs18 from "fs/promises";
|
|
5075
|
-
import { z as
|
|
5329
|
+
import { z as z18 } from "zod";
|
|
5076
5330
|
function observationPath(directory, host, revision, version = executingVersion()) {
|
|
5077
5331
|
return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
5078
5332
|
}
|
|
@@ -5137,19 +5391,19 @@ var init_observations = __esm({
|
|
|
5137
5391
|
init_storage();
|
|
5138
5392
|
init_model();
|
|
5139
5393
|
init_launcher();
|
|
5140
|
-
observationSchema =
|
|
5141
|
-
version:
|
|
5142
|
-
masonVersion:
|
|
5143
|
-
root:
|
|
5144
|
-
revision:
|
|
5145
|
-
host:
|
|
5146
|
-
contextCalls:
|
|
5147
|
-
lastContextAt:
|
|
5148
|
-
sessions:
|
|
5149
|
-
events:
|
|
5150
|
-
at:
|
|
5151
|
-
verificationStatus:
|
|
5152
|
-
reportPath:
|
|
5394
|
+
observationSchema = z18.object({
|
|
5395
|
+
version: z18.literal(1),
|
|
5396
|
+
masonVersion: z18.string(),
|
|
5397
|
+
root: z18.string(),
|
|
5398
|
+
revision: z18.string(),
|
|
5399
|
+
host: z18.enum(["codex", "claude"]),
|
|
5400
|
+
contextCalls: z18.number().int().nonnegative(),
|
|
5401
|
+
lastContextAt: z18.string().optional(),
|
|
5402
|
+
sessions: z18.record(z18.object({
|
|
5403
|
+
events: z18.array(z18.enum(events)),
|
|
5404
|
+
at: z18.string(),
|
|
5405
|
+
verificationStatus: z18.string().optional(),
|
|
5406
|
+
reportPath: z18.string().optional()
|
|
5153
5407
|
}))
|
|
5154
5408
|
});
|
|
5155
5409
|
}
|
|
@@ -5289,6 +5543,8 @@ async function setupProject(dir, options = {}) {
|
|
|
5289
5543
|
const setupBefore = await readText(ws.root, SETUP_PATH);
|
|
5290
5544
|
const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after)).mcp);
|
|
5291
5545
|
const desiredHooks = hookConfig(host, hookCommand(host)).hooks;
|
|
5546
|
+
const configuredHook = desiredHooks.SessionStart[0].hooks[0].command;
|
|
5547
|
+
const ownership = await ownershipEdit(ws.root, [...instructions, mcp, hooks[0]], { [hooks[0].path]: configuredHook });
|
|
5292
5548
|
const fingerprint = hash({
|
|
5293
5549
|
command: "mason",
|
|
5294
5550
|
mcp: mcpFingerprint,
|
|
@@ -5315,8 +5571,10 @@ async function setupProject(dir, options = {}) {
|
|
|
5315
5571
|
revision
|
|
5316
5572
|
});
|
|
5317
5573
|
const changedFiles = [];
|
|
5574
|
+
if (await applyEdit(ws.root, ownership)) changedFiles.push(ownership.path);
|
|
5318
5575
|
for (const edit of edits) if (await applyEdit(ws.root, edit)) changedFiles.push(edit.path);
|
|
5319
5576
|
if (await applyEdit(ws.root, { path: SETUP_PATH, before: setupBefore, after: JSON.stringify(setup, null, 2) + "\n" })) changedFiles.push(SETUP_PATH);
|
|
5577
|
+
if (await applyEdit(ws.root, await completedHookOwnership(ws.root, hooks[0].path, configuredHook)) && !changedFiles.includes(ownership.path)) changedFiles.push(ownership.path);
|
|
5320
5578
|
const checked = await automate(ws.root, { event: "turn_start" });
|
|
5321
5579
|
const configured = await inspectHostConfig(ws.root, host, void 0);
|
|
5322
5580
|
await writeStoreJson(ws.root, receiptPath, {
|
|
@@ -5375,6 +5633,7 @@ var init_setup = __esm({
|
|
|
5375
5633
|
init_adapters();
|
|
5376
5634
|
init_model();
|
|
5377
5635
|
init_status();
|
|
5636
|
+
init_ownership();
|
|
5378
5637
|
}
|
|
5379
5638
|
});
|
|
5380
5639
|
|
|
@@ -5386,8 +5645,8 @@ __export(client_exports, {
|
|
|
5386
5645
|
function createConfluenceClient(config, fetchFn = fetch) {
|
|
5387
5646
|
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
5388
5647
|
const auth = "Basic " + Buffer.from(`${config.email}:${config.apiToken}`).toString("base64");
|
|
5389
|
-
async function call(method,
|
|
5390
|
-
const res = await fetchFn(`${baseUrl}${
|
|
5648
|
+
async function call(method, path29, body) {
|
|
5649
|
+
const res = await fetchFn(`${baseUrl}${path29}`, {
|
|
5391
5650
|
method,
|
|
5392
5651
|
headers: {
|
|
5393
5652
|
Authorization: auth,
|
|
@@ -5399,7 +5658,7 @@ function createConfluenceClient(config, fetchFn = fetch) {
|
|
|
5399
5658
|
if (!res.ok) {
|
|
5400
5659
|
const text2 = await res.text();
|
|
5401
5660
|
throw new Error(
|
|
5402
|
-
`Confluence ${method} ${
|
|
5661
|
+
`Confluence ${method} ${path29} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
|
|
5403
5662
|
);
|
|
5404
5663
|
}
|
|
5405
5664
|
if (res.status === 204) return null;
|
|
@@ -5501,15 +5760,15 @@ __export(config_exports2, {
|
|
|
5501
5760
|
validateProvider: () => validateProvider
|
|
5502
5761
|
});
|
|
5503
5762
|
import fs19 from "fs/promises";
|
|
5504
|
-
import
|
|
5763
|
+
import path26 from "path";
|
|
5505
5764
|
import os3 from "os";
|
|
5506
5765
|
import { execFile as execFile17 } from "child_process";
|
|
5507
5766
|
import { promisify as promisify17 } from "util";
|
|
5508
5767
|
function configDir() {
|
|
5509
|
-
return
|
|
5768
|
+
return path26.join(os3.homedir(), ".mason");
|
|
5510
5769
|
}
|
|
5511
5770
|
function configFile() {
|
|
5512
|
-
return
|
|
5771
|
+
return path26.join(configDir(), "config.json");
|
|
5513
5772
|
}
|
|
5514
5773
|
async function loadConfig() {
|
|
5515
5774
|
try {
|
|
@@ -5589,21 +5848,21 @@ var init_url = __esm({
|
|
|
5589
5848
|
});
|
|
5590
5849
|
|
|
5591
5850
|
// src/confluence/renderer.ts
|
|
5592
|
-
function
|
|
5851
|
+
function escape2(value) {
|
|
5593
5852
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5594
5853
|
}
|
|
5595
5854
|
function infoPanel(text2) {
|
|
5596
|
-
return `<ac:structured-macro ac:name="info"><ac:rich-text-body><p>${
|
|
5855
|
+
return `<ac:structured-macro ac:name="info"><ac:rich-text-body><p>${escape2(text2)}</p></ac:rich-text-body></ac:structured-macro>`;
|
|
5597
5856
|
}
|
|
5598
5857
|
function featurePageTitle(prefix, name) {
|
|
5599
5858
|
return `${prefix}${name}`;
|
|
5600
5859
|
}
|
|
5601
5860
|
function renderFeaturePage(options) {
|
|
5602
|
-
const overviewBody = `<h2>What it does</h2><p>${
|
|
5861
|
+
const overviewBody = `<h2>What it does</h2><p>${escape2(options.productDescription)}</p>`;
|
|
5603
5862
|
const flowsBody = options.flowDescriptions.length ? `<h2>How it fits in</h2><ul>` + options.flowDescriptions.map(
|
|
5604
|
-
(f) => `<li><strong>${
|
|
5863
|
+
(f) => `<li><strong>${escape2(f.name)}</strong> \u2014 ${escape2(f.description)}</li>`
|
|
5605
5864
|
).join("") + `</ul>` : "";
|
|
5606
|
-
const navBody = `<p><ac:link><ri:page ri:content-title="${
|
|
5865
|
+
const navBody = `<p><ac:link><ri:page ri:content-title="${escape2(options.indexPageTitle)}"/><ac:plain-text-link-body><![CDATA[Back to ${options.indexPageTitle}]]></ac:plain-text-link-body></ac:link></p>`;
|
|
5607
5866
|
const footer = infoPanel(
|
|
5608
5867
|
`Generated from code by Mason. This page is overwritten on each sync \u2014 edit the code, not the page.`
|
|
5609
5868
|
);
|
|
@@ -5619,7 +5878,7 @@ function renderIndexPage(options) {
|
|
|
5619
5878
|
}
|
|
5620
5879
|
const list = `<h2>Features</h2><ul>` + options.featureTitles.map((name) => {
|
|
5621
5880
|
const pageTitle = featurePageTitle(options.featurePrefix, name);
|
|
5622
|
-
return `<li><ac:link><ri:page ri:content-title="${
|
|
5881
|
+
return `<li><ac:link><ri:page ri:content-title="${escape2(pageTitle)}"/><ac:plain-text-link-body><![CDATA[${name}]]></ac:plain-text-link-body></ac:link></li>`;
|
|
5623
5882
|
}).join("") + `</ul>`;
|
|
5624
5883
|
const banner = infoPanel(
|
|
5625
5884
|
`Generated from code by Mason. Maintained automatically \u2014 edit the code, not this page.`
|
|
@@ -5630,33 +5889,33 @@ function renderChangelogSection(section) {
|
|
|
5630
5889
|
const segments = [];
|
|
5631
5890
|
if (section.addedFeatures.length) {
|
|
5632
5891
|
segments.push(
|
|
5633
|
-
`<p><strong>Added features:</strong> ${section.addedFeatures.map(
|
|
5892
|
+
`<p><strong>Added features:</strong> ${section.addedFeatures.map(escape2).join(", ")}</p>`
|
|
5634
5893
|
);
|
|
5635
5894
|
}
|
|
5636
5895
|
if (section.removedFeatures.length) {
|
|
5637
5896
|
segments.push(
|
|
5638
|
-
`<p><strong>Removed features:</strong> ${section.removedFeatures.map(
|
|
5897
|
+
`<p><strong>Removed features:</strong> ${section.removedFeatures.map(escape2).join(", ")}</p>`
|
|
5639
5898
|
);
|
|
5640
5899
|
}
|
|
5641
5900
|
if (section.changedFeatures.length) {
|
|
5642
5901
|
segments.push(
|
|
5643
|
-
`<p><strong>Updated features:</strong> ${section.changedFeatures.map(
|
|
5902
|
+
`<p><strong>Updated features:</strong> ${section.changedFeatures.map(escape2).join(", ")}</p>`
|
|
5644
5903
|
);
|
|
5645
5904
|
}
|
|
5646
5905
|
if (section.addedFlows.length) {
|
|
5647
5906
|
segments.push(
|
|
5648
|
-
`<p><strong>Added flows:</strong> ${section.addedFlows.map(
|
|
5907
|
+
`<p><strong>Added flows:</strong> ${section.addedFlows.map(escape2).join(", ")}</p>`
|
|
5649
5908
|
);
|
|
5650
5909
|
}
|
|
5651
5910
|
if (section.removedFlows.length) {
|
|
5652
5911
|
segments.push(
|
|
5653
|
-
`<p><strong>Removed flows:</strong> ${section.removedFlows.map(
|
|
5912
|
+
`<p><strong>Removed flows:</strong> ${section.removedFlows.map(escape2).join(", ")}</p>`
|
|
5654
5913
|
);
|
|
5655
5914
|
}
|
|
5656
5915
|
if (segments.length === 0) {
|
|
5657
5916
|
segments.push(`<p><em>No meaningful changes detected.</em></p>`);
|
|
5658
5917
|
}
|
|
5659
|
-
return `<h3>${
|
|
5918
|
+
return `<h3>${escape2(section.syncedAt)}</h3>` + segments.join("");
|
|
5660
5919
|
}
|
|
5661
5920
|
function renderChangelogPage(sections) {
|
|
5662
5921
|
if (sections.length === 0) {
|
|
@@ -5682,16 +5941,16 @@ var init_renderer = __esm({
|
|
|
5682
5941
|
|
|
5683
5942
|
// src/confluence/diff.ts
|
|
5684
5943
|
import fs20 from "fs/promises";
|
|
5685
|
-
import
|
|
5944
|
+
import path27 from "path";
|
|
5686
5945
|
import { createHash as createHash5 } from "crypto";
|
|
5687
5946
|
function hashDescription(description) {
|
|
5688
5947
|
return createHash5("sha256").update(description, "utf8").digest("hex");
|
|
5689
5948
|
}
|
|
5690
5949
|
function syncStateDir(rootDir) {
|
|
5691
|
-
return
|
|
5950
|
+
return path27.join(rootDir, ".mason");
|
|
5692
5951
|
}
|
|
5693
5952
|
function syncStatePath(rootDir) {
|
|
5694
|
-
return
|
|
5953
|
+
return path27.join(syncStateDir(rootDir), "confluence-sync.json");
|
|
5695
5954
|
}
|
|
5696
5955
|
async function loadSyncState(rootDir) {
|
|
5697
5956
|
try {
|
|
@@ -6255,12 +6514,12 @@ var init_sync = __esm({
|
|
|
6255
6514
|
init_provenance();
|
|
6256
6515
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6257
6516
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6258
|
-
import { z as
|
|
6517
|
+
import { z as z19 } from "zod";
|
|
6259
6518
|
|
|
6260
6519
|
// src/mcp/tools.ts
|
|
6261
6520
|
init_repair();
|
|
6262
6521
|
import fs21 from "fs/promises";
|
|
6263
|
-
import
|
|
6522
|
+
import path28 from "path";
|
|
6264
6523
|
import { execFile as execFile19 } from "child_process";
|
|
6265
6524
|
import { promisify as promisify19 } from "util";
|
|
6266
6525
|
|
|
@@ -7155,7 +7414,7 @@ async function buildContext(dir) {
|
|
|
7155
7414
|
};
|
|
7156
7415
|
}
|
|
7157
7416
|
async function analyzeProject(dir) {
|
|
7158
|
-
const rootDir =
|
|
7417
|
+
const rootDir = path28.resolve(dir);
|
|
7159
7418
|
const context = await buildContext(rootDir);
|
|
7160
7419
|
const results = await runAll(context);
|
|
7161
7420
|
const projectSnapshot = await detectProjectSnapshot(rootDir);
|
|
@@ -7210,7 +7469,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
7210
7469
|
const present = [];
|
|
7211
7470
|
for (const file of buildFiles) {
|
|
7212
7471
|
try {
|
|
7213
|
-
await fs21.access(
|
|
7472
|
+
await fs21.access(path28.join(rootDir, file));
|
|
7214
7473
|
present.push(file);
|
|
7215
7474
|
} catch {
|
|
7216
7475
|
}
|
|
@@ -7252,7 +7511,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
7252
7511
|
const sourceFiles = await access.list();
|
|
7253
7512
|
const fileCounts = {};
|
|
7254
7513
|
for (const file of sourceFiles) {
|
|
7255
|
-
const ext =
|
|
7514
|
+
const ext = path28.extname(file).slice(1);
|
|
7256
7515
|
fileCounts[ext] = (fileCounts[ext] ?? 0) + 1;
|
|
7257
7516
|
}
|
|
7258
7517
|
return {
|
|
@@ -7263,7 +7522,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
7263
7522
|
};
|
|
7264
7523
|
}
|
|
7265
7524
|
async function getCodeSamples(dir, count3 = 15) {
|
|
7266
|
-
const rootDir =
|
|
7525
|
+
const rootDir = path28.resolve(dir);
|
|
7267
7526
|
const samples = await sampleFiles(rootDir, count3);
|
|
7268
7527
|
const output = {
|
|
7269
7528
|
note: "These are previews (first ~60 lines). Read the file directly with your own tools to see it in full.",
|
|
@@ -7306,7 +7565,7 @@ async function unmappedContextResponse(rootDir) {
|
|
|
7306
7565
|
});
|
|
7307
7566
|
}
|
|
7308
7567
|
async function getProjectStructure(dir) {
|
|
7309
|
-
const rootDir =
|
|
7568
|
+
const rootDir = path28.resolve(dir);
|
|
7310
7569
|
const allFiles = await (await createFileAccess(rootDir)).list("**/*");
|
|
7311
7570
|
const dirInfo = /* @__PURE__ */ new Map();
|
|
7312
7571
|
for (const file of allFiles) {
|
|
@@ -7318,7 +7577,7 @@ async function getProjectStructure(dir) {
|
|
|
7318
7577
|
}
|
|
7319
7578
|
const info = dirInfo.get(dirPath);
|
|
7320
7579
|
info.fileCount++;
|
|
7321
|
-
const ext =
|
|
7580
|
+
const ext = path28.extname(file).slice(1);
|
|
7322
7581
|
if (ext) {
|
|
7323
7582
|
info.extensions.set(ext, (info.extensions.get(ext) ?? 0) + 1);
|
|
7324
7583
|
}
|
|
@@ -7363,7 +7622,7 @@ async function buildChangedFilePreviews(rootDir, changedFiles) {
|
|
|
7363
7622
|
return previews2;
|
|
7364
7623
|
}
|
|
7365
7624
|
async function getSnapshot(dir) {
|
|
7366
|
-
const rootDir =
|
|
7625
|
+
const rootDir = path28.resolve(dir);
|
|
7367
7626
|
const snapshot = await loadSnapshot(rootDir);
|
|
7368
7627
|
if (!snapshot) {
|
|
7369
7628
|
return unmappedContextResponse(rootDir);
|
|
@@ -7464,7 +7723,7 @@ function driftHint(report) {
|
|
|
7464
7723
|
return "Read the changed files under staleFeatures/staleFlows, update those entries (fold unmappedFiles into the right features), and call save_snapshot with only the affected entries \u2014 unchanged entries are preserved. Drop ghostFiles from any entries that reference them, and delete features/flows that no longer exist via save_snapshot's removeFeatures/removeFlows.";
|
|
7465
7724
|
}
|
|
7466
7725
|
async function checkDrift(dir) {
|
|
7467
|
-
const rootDir =
|
|
7726
|
+
const rootDir = path28.resolve(dir);
|
|
7468
7727
|
const report = await computeDrift(rootDir);
|
|
7469
7728
|
if (!report) {
|
|
7470
7729
|
return JSON.stringify({
|
|
@@ -7495,7 +7754,7 @@ async function checkDrift(dir) {
|
|
|
7495
7754
|
return JSON.stringify({ exists: true, ...report, verification, hint });
|
|
7496
7755
|
}
|
|
7497
7756
|
async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_SIZE, files) {
|
|
7498
|
-
const rootDir =
|
|
7757
|
+
const rootDir = path28.resolve(dir);
|
|
7499
7758
|
const scoped = files !== void 0 && files.length > 0;
|
|
7500
7759
|
const scopeFiles = scoped ? sanitizePaths(rootDir, files) : void 0;
|
|
7501
7760
|
const batch = await prepareSnapshotBatch(dir, offset, batchSize, scopeFiles);
|
|
@@ -7542,7 +7801,7 @@ async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_
|
|
|
7542
7801
|
);
|
|
7543
7802
|
}
|
|
7544
7803
|
async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
|
|
7545
|
-
const rootDir =
|
|
7804
|
+
const rootDir = path28.resolve(dir);
|
|
7546
7805
|
for (const feat of Object.values(features)) {
|
|
7547
7806
|
feat.files = sanitizePaths(rootDir, feat.files);
|
|
7548
7807
|
if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
|
|
@@ -7571,7 +7830,7 @@ async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
|
|
|
7571
7830
|
);
|
|
7572
7831
|
}
|
|
7573
7832
|
async function reduceSnapshot(dir) {
|
|
7574
|
-
const rootDir =
|
|
7833
|
+
const rootDir = path28.resolve(dir);
|
|
7575
7834
|
const partials = await loadAllPartials(rootDir);
|
|
7576
7835
|
if (partials.length === 0) {
|
|
7577
7836
|
return JSON.stringify(
|
|
@@ -7633,7 +7892,7 @@ async function reduceSnapshot(dir) {
|
|
|
7633
7892
|
);
|
|
7634
7893
|
}
|
|
7635
7894
|
async function fullAnalysis(dir) {
|
|
7636
|
-
const rootDir =
|
|
7895
|
+
const rootDir = path28.resolve(dir);
|
|
7637
7896
|
const [analysis, structure, samples, testMap, snapshot] = await Promise.all([
|
|
7638
7897
|
analyzeProject(dir),
|
|
7639
7898
|
getProjectStructure(dir),
|
|
@@ -7662,7 +7921,7 @@ function sanitizePaths(rootDir, files) {
|
|
|
7662
7921
|
return sanitizeRepoPaths(files);
|
|
7663
7922
|
}
|
|
7664
7923
|
async function saveSnapshotData(dir, features, flows, removeFeatures = [], removeFlows = []) {
|
|
7665
|
-
const rootDir =
|
|
7924
|
+
const rootDir = path28.resolve(dir);
|
|
7666
7925
|
const gitHash = await getCurrentGitHash(rootDir);
|
|
7667
7926
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7668
7927
|
for (const feat of Object.values(features)) {
|
|
@@ -7746,7 +8005,7 @@ async function saveSnapshotData(dir, features, flows, removeFeatures = [], remov
|
|
|
7746
8005
|
});
|
|
7747
8006
|
}
|
|
7748
8007
|
async function getImpact(dir, files) {
|
|
7749
|
-
const rootDir =
|
|
8008
|
+
const rootDir = path28.resolve(dir);
|
|
7750
8009
|
const { analyzeImpact: analyzeImpact2 } = await Promise.resolve().then(() => (init_impact(), impact_exports));
|
|
7751
8010
|
const result = await analyzeImpact2(rootDir, files);
|
|
7752
8011
|
return JSON.stringify(result, null, 2);
|
|
@@ -7755,7 +8014,7 @@ var VERIFY_DEFAULT_SAMPLE = 5;
|
|
|
7755
8014
|
var VERIFY_MAX_FILES_PER_ENTRY = 8;
|
|
7756
8015
|
var VERIFY_SKELETON_CHARS = 500;
|
|
7757
8016
|
async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
|
|
7758
|
-
const rootDir =
|
|
8017
|
+
const rootDir = path28.resolve(dir);
|
|
7759
8018
|
const snapshot = await loadSnapshot(rootDir);
|
|
7760
8019
|
if (!snapshot) {
|
|
7761
8020
|
return JSON.stringify({
|
|
@@ -7820,7 +8079,7 @@ async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
|
|
|
7820
8079
|
});
|
|
7821
8080
|
}
|
|
7822
8081
|
async function saveVerification(dir, verdicts) {
|
|
7823
|
-
const rootDir =
|
|
8082
|
+
const rootDir = path28.resolve(dir);
|
|
7824
8083
|
const snapshot = await loadSnapshot(rootDir);
|
|
7825
8084
|
if (!snapshot) {
|
|
7826
8085
|
return JSON.stringify({ exists: false, hint: "No concept map exists." });
|
|
@@ -7858,16 +8117,16 @@ async function saveVerification(dir, verdicts) {
|
|
|
7858
8117
|
});
|
|
7859
8118
|
}
|
|
7860
8119
|
async function saveDecision(dir, input) {
|
|
7861
|
-
const rootDir =
|
|
8120
|
+
const rootDir = path28.resolve(dir);
|
|
7862
8121
|
const { upsertDecision: upsertDecision2 } = await Promise.resolve().then(() => (init_decisions(), decisions_exports));
|
|
7863
8122
|
const result = await upsertDecision2(rootDir, input);
|
|
7864
8123
|
return JSON.stringify(result);
|
|
7865
8124
|
}
|
|
7866
8125
|
async function reviewDecision2(dir, input) {
|
|
7867
|
-
return JSON.stringify(await reviewDecision(
|
|
8126
|
+
return JSON.stringify(await reviewDecision(path28.resolve(dir), input));
|
|
7868
8127
|
}
|
|
7869
8128
|
async function getContext(dir, task, files) {
|
|
7870
|
-
const rootDir =
|
|
8129
|
+
const rootDir = path28.resolve(dir);
|
|
7871
8130
|
const { assembleContext: assembleContext2 } = await Promise.resolve().then(() => (init_assemble(), assemble_exports));
|
|
7872
8131
|
const bundle = await assembleContext2(rootDir, task, files);
|
|
7873
8132
|
return JSON.stringify(bundle);
|
|
@@ -7909,7 +8168,7 @@ async function masonInit(dir, options = {}) {
|
|
|
7909
8168
|
return JSON.stringify(await setupProject2(dir, options), null, 2);
|
|
7910
8169
|
}
|
|
7911
8170
|
if (options.host) throw new Error("host applies only to mode: setup.");
|
|
7912
|
-
const rootDir =
|
|
8171
|
+
const rootDir = path28.resolve(dir);
|
|
7913
8172
|
const marker = await loadProjectMarker(rootDir);
|
|
7914
8173
|
const { loadSetup: loadSetup2 } = await Promise.resolve().then(() => (init_model(), model_exports));
|
|
7915
8174
|
const setup = await loadSetup2(rootDir);
|
|
@@ -7929,7 +8188,7 @@ async function masonInit(dir, options = {}) {
|
|
|
7929
8188
|
);
|
|
7930
8189
|
}
|
|
7931
8190
|
async function masonCompleteInit(dir, options = {}) {
|
|
7932
|
-
const rootDir =
|
|
8191
|
+
const rootDir = path28.resolve(dir);
|
|
7933
8192
|
const existing = await loadProjectMarker(rootDir);
|
|
7934
8193
|
const features = await loadProjectFeatures(rootDir);
|
|
7935
8194
|
const marker = {
|
|
@@ -8038,7 +8297,7 @@ async function masonSetConfluence(input) {
|
|
|
8038
8297
|
);
|
|
8039
8298
|
}
|
|
8040
8299
|
async function exportToConfluenceTool(dir, overrides) {
|
|
8041
|
-
const rootDir =
|
|
8300
|
+
const rootDir = path28.resolve(dir);
|
|
8042
8301
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports2));
|
|
8043
8302
|
const { exportToConfluence: exportToConfluence2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
8044
8303
|
const config = await loadConfig2();
|
|
@@ -8076,7 +8335,7 @@ function createMcpServer() {
|
|
|
8076
8335
|
const server = new McpServer(
|
|
8077
8336
|
{
|
|
8078
8337
|
name: "mason",
|
|
8079
|
-
version: "0.
|
|
8338
|
+
version: "0.16.1"
|
|
8080
8339
|
},
|
|
8081
8340
|
{
|
|
8082
8341
|
instructions: 'Mason retrieves recorded decisions, file impact, and optional feature/flow maps. Use get_context with the task and known files, and get_impact before editing. Save learned rationale and constraints with save_decision; review and commit the local records through the project workflow. These tools work without initialization or a concept map. Consult trust and diagnostics: changed or unknown freshness needs source inspection, failed verification needs correction, and proposals are suggestions and legacy records are unreviewed. Accepted decisions are recorded team constraints, still subject to freshness checks. Use review_decision to prepare code evidence and record only authorized acceptance, reaffirmation, or retirement. Never invent a reviewer or treat these recorded identities as authenticated approval. mason_init returns documentation audit and committed-diff review findings with a quickstart guide. Use mode: "map" only when a full architecture map is requested. A missing map is not a setup failure; use decisions and source evidence. get_snapshot provides architecture navigation when a map is available. The mason-audit and mason-review CLIs also work without setup.'
|
|
@@ -8086,11 +8345,11 @@ function createMcpServer() {
|
|
|
8086
8345
|
"mason_init",
|
|
8087
8346
|
"Inspect this project now: returns documentation audit findings, committed-diff review findings, decision/map status, and a quickstart playbook. Quickstart and map modes are read-only and deterministic. Explicit mode: setup configures MCP, instructions and lifecycle hooks to use mason on PATH while retaining original audit evidence; use it only when the user requests setup. Optional host selects codex or claude. Optional base selects the review comparison; evidence imports CI manifests with check outcomes, commit freshness, and links to changed files and accepted decisions. mode: map returns the full Map-Reduce build workflow. Repeat calls refresh findings even after setup.",
|
|
8088
8347
|
{
|
|
8089
|
-
dir:
|
|
8090
|
-
mode:
|
|
8091
|
-
host:
|
|
8092
|
-
base:
|
|
8093
|
-
evidence:
|
|
8348
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8349
|
+
mode: z19.enum(["quickstart", "map", "setup"]).optional().default("quickstart").describe("Quickstart inspects without edits; map requests an architecture build; setup installs the shared onboarding flow."),
|
|
8350
|
+
host: z19.enum(["codex", "claude"]).optional().describe("Assistant to configure in setup mode; inferred only when unambiguous."),
|
|
8351
|
+
base: z19.string().optional().describe("Git ref for committed-diff review. Defaults to the first available main branch ref."),
|
|
8352
|
+
evidence: z19.array(z19.string()).max(10).optional().describe("Repository-local CI evidence manifests to include in the review. Imports Vitest JSON and SARIF without executing check commands.")
|
|
8094
8353
|
},
|
|
8095
8354
|
async ({ dir, mode, host, base, evidence }) => {
|
|
8096
8355
|
const result = await masonInit(dir, { mode, host, base, evidence });
|
|
@@ -8101,8 +8360,8 @@ function createMcpServer() {
|
|
|
8101
8360
|
"mason_automation",
|
|
8102
8361
|
"Inspect installed automation and observed host events, or resume/check retained documentation repair evidence across sessions. status is read-only; check saves local baselines and verification reports without editing source or approving advisories. Returns concise results with a full report path. Works without a map.",
|
|
8103
8362
|
{
|
|
8104
|
-
dir:
|
|
8105
|
-
action:
|
|
8363
|
+
dir: z19.string().describe("Absolute path to the project directory"),
|
|
8364
|
+
action: z19.enum(["status", "check"]).describe("Inspect configuration and receipts, or capture/resume and verify original audit evidence.")
|
|
8106
8365
|
},
|
|
8107
8366
|
async ({ dir, action }) => ({ content: [{ type: "text", text: await masonAutomation(dir, action) }] })
|
|
8108
8367
|
);
|
|
@@ -8110,10 +8369,10 @@ function createMcpServer() {
|
|
|
8110
8369
|
"mason_repair",
|
|
8111
8370
|
"Track documentation repairs against original audit evidence. prepare saves a local baseline and returns a scoped work order; verify reads that baseline and reports resolved, unresolved, review-required, unverified, and new findings. Suppressed advisories remain unresolved. Does not edit documentation or approve decisions. No map required.",
|
|
8112
8371
|
{
|
|
8113
|
-
dir:
|
|
8114
|
-
action:
|
|
8115
|
-
baselinePath:
|
|
8116
|
-
checks:
|
|
8372
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8373
|
+
action: z19.enum(["prepare", "verify"]),
|
|
8374
|
+
baselinePath: z19.string().optional().describe("Original baseline path returned by prepare; required for verify."),
|
|
8375
|
+
checks: z19.array(z19.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"])).min(1).optional().describe("Optional audit check subset for prepare. Verification always uses the original checks.")
|
|
8117
8376
|
},
|
|
8118
8377
|
async ({ dir, action, baselinePath, checks }) => {
|
|
8119
8378
|
const result = await masonRepair(dir, { action, baselinePath, checks });
|
|
@@ -8124,8 +8383,8 @@ function createMcpServer() {
|
|
|
8124
8383
|
"mason_complete_init",
|
|
8125
8384
|
"Record assistant instruction setup locally in ignored .mason/local/project.json, with feature settings in shared .mason/config.json. Other tools work without this marker. Repeated calls preserve the original setup time and existing settings; pass confluenceConfigured only to change that setting.",
|
|
8126
8385
|
{
|
|
8127
|
-
dir:
|
|
8128
|
-
confluenceConfigured:
|
|
8386
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8387
|
+
confluenceConfigured: z19.boolean().optional().describe("Set the Confluence setup status; omit to preserve the existing value")
|
|
8129
8388
|
},
|
|
8130
8389
|
async ({ dir, confluenceConfigured }) => {
|
|
8131
8390
|
const result = await masonCompleteInit(dir, { confluenceConfigured });
|
|
@@ -8136,11 +8395,11 @@ function createMcpServer() {
|
|
|
8136
8395
|
"mason_set_confluence",
|
|
8137
8396
|
"Configure Confluence credentials. Two-step flow: (1) call without `spaceKey` to validate the credentials and receive a list of available spaces \u2014 relay them to the user. (2) call again with the same `baseUrl`/`email`/`apiToken` plus the chosen `spaceKey` to persist. Credentials are stored in `~/.mason/config.json`. Warn the user that the API token will be visible in chat history before they paste it.",
|
|
8138
8397
|
{
|
|
8139
|
-
baseUrl:
|
|
8140
|
-
email:
|
|
8141
|
-
apiToken:
|
|
8142
|
-
spaceKey:
|
|
8143
|
-
parentPageId:
|
|
8398
|
+
baseUrl: z19.string().describe("Confluence base URL. Accepts `acme`, `acme.atlassian.net`, or `https://acme.atlassian.net` (normalized automatically)."),
|
|
8399
|
+
email: z19.string().describe("User's Atlassian account email"),
|
|
8400
|
+
apiToken: z19.string().describe("API token from id.atlassian.com/manage-profile/security/api-tokens"),
|
|
8401
|
+
spaceKey: z19.string().optional().describe("Confluence space key. Omit on the first call to list available spaces."),
|
|
8402
|
+
parentPageId: z19.string().optional().describe("Optional parent page ID under which Mason's index page is created")
|
|
8144
8403
|
},
|
|
8145
8404
|
async ({ baseUrl, email, apiToken, spaceKey, parentPageId }) => {
|
|
8146
8405
|
const result = await masonSetConfluence({
|
|
@@ -8157,7 +8416,7 @@ function createMcpServer() {
|
|
|
8157
8416
|
"full_analysis",
|
|
8158
8417
|
"One-shot orientation for a project WITHOUT a concept map (get_snapshot returned exists:false). Returns git history stats, project structure with file counts, curated code sample previews (~60 lines each), and test-to-source mapping. On a mapped project, prefer get_snapshot \u2014 it is cheaper and answers feature/architecture questions directly.",
|
|
8159
8418
|
{
|
|
8160
|
-
dir:
|
|
8419
|
+
dir: z19.string().describe("Absolute path to the project root directory")
|
|
8161
8420
|
},
|
|
8162
8421
|
async ({ dir }) => {
|
|
8163
8422
|
const result = await fullAnalysis(dir);
|
|
@@ -8170,7 +8429,7 @@ function createMcpServer() {
|
|
|
8170
8429
|
"analyze_project",
|
|
8171
8430
|
"Run git history analysis on a codebase. Returns commit convention patterns, stale directories, and frequently changed files. These are aggregate stats across hundreds of commits that would be expensive to compute manually.",
|
|
8172
8431
|
{
|
|
8173
|
-
dir:
|
|
8432
|
+
dir: z19.string().describe("Absolute path to the project root directory")
|
|
8174
8433
|
},
|
|
8175
8434
|
async ({ dir }) => {
|
|
8176
8435
|
const result = await analyzeProject(dir);
|
|
@@ -8183,8 +8442,8 @@ function createMcpServer() {
|
|
|
8183
8442
|
"get_code_samples",
|
|
8184
8443
|
"Get previews (first ~60 lines) of representative source files from the codebase. Includes entry points, config files, hot files (frequently changed), test examples, and one file per directory for breadth. Read files natively for full content.",
|
|
8185
8444
|
{
|
|
8186
|
-
dir:
|
|
8187
|
-
count:
|
|
8445
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8446
|
+
count: z19.number().optional().default(15).describe("Maximum number of files to sample (default: 15)")
|
|
8188
8447
|
},
|
|
8189
8448
|
async ({ dir, count: count3 }) => {
|
|
8190
8449
|
const result = await getCodeSamples(dir, count3);
|
|
@@ -8197,7 +8456,7 @@ function createMcpServer() {
|
|
|
8197
8456
|
"get_snapshot",
|
|
8198
8457
|
"Return the optional feature-to-file architecture map with drift and trust evidence. If no map exists, returns exists:false plus project structure, Git signals, and test pairs. Decision capture, get_context, and get_impact still work. No initialization required.",
|
|
8199
8458
|
{
|
|
8200
|
-
dir:
|
|
8459
|
+
dir: z19.string().describe("Absolute path to the project root directory")
|
|
8201
8460
|
},
|
|
8202
8461
|
async ({ dir }) => {
|
|
8203
8462
|
const result = await getSnapshot(dir);
|
|
@@ -8210,9 +8469,9 @@ function createMcpServer() {
|
|
|
8210
8469
|
"get_context",
|
|
8211
8470
|
"Assemble task context: matching decisions with rationale, approval, owner, sources, last review, and freshness, plus related tests, file impact, and any available map entries. Proposals are suggestions; legacy records are unreviewed; accepted decisions are constraints subject to freshness. No initialization or map required. Pass task and optional files. map.status and diagnostics preserve missing or invalid knowledge. Impact covers up to three unique files, expanding directory anchors.",
|
|
8212
8471
|
{
|
|
8213
|
-
dir:
|
|
8214
|
-
task:
|
|
8215
|
-
files:
|
|
8472
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8473
|
+
task: z19.string().describe("The task, bug, or change request in natural language \u2014 e.g. 'add rate limiting to the API client' or a ticket description"),
|
|
8474
|
+
files: z19.array(z19.string()).optional().describe("Optional file paths already known to be involved (e.g. from a diff or stack trace). Entries containing them are boosted above pure text matches.")
|
|
8216
8475
|
},
|
|
8217
8476
|
async ({ dir, task, files }) => {
|
|
8218
8477
|
const result = await getContext(dir, task, files);
|
|
@@ -8228,10 +8487,10 @@ function createMcpServer() {
|
|
|
8228
8487
|
"generate_snapshot_batch",
|
|
8229
8488
|
"Map step of the concept-map build. Returns one batch of source files (skeletons of every file in the batch plus a few deeper-read bodies for grounding), along with a system prompt instructing you to derive features and flows for ONLY this batch. Call repeatedly with the returned `nextOffset` until it is null, calling `save_partial_snapshot` between each call. Use product-natural feature names so partials merge cleanly in the reduce step.",
|
|
8230
8489
|
{
|
|
8231
|
-
dir:
|
|
8232
|
-
offset:
|
|
8233
|
-
batchSize:
|
|
8234
|
-
files:
|
|
8490
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8491
|
+
offset: z19.number().int().optional().describe("0-indexed file offset to start the batch at. Omit on the first call; pass the `nextOffset` from the previous response for subsequent calls."),
|
|
8492
|
+
batchSize: z19.number().int().optional().describe("Files per batch. Defaults to 50."),
|
|
8493
|
+
files: z19.array(z19.string()).optional().describe("Scope the batch walk to this explicit file list \u2014 e.g. the drift set from mason_check_drift (changedFiles + unmappedFiles). Pass the SAME list on every batch call of one refresh run. Triggers refresh mode: reduce_snapshot will merge the partials into the existing map instead of rebuilding it.")
|
|
8235
8494
|
},
|
|
8236
8495
|
async ({ dir, offset, batchSize, files }) => {
|
|
8237
8496
|
const result = await generateSnapshotBatch(dir, offset, batchSize, files);
|
|
@@ -8244,23 +8503,23 @@ function createMcpServer() {
|
|
|
8244
8503
|
"save_partial_snapshot",
|
|
8245
8504
|
"Persist the partial concept map you derived for one batch. Call this once per batch, with the `batchId` from the `generate_snapshot_batch` response. Partials accumulate in `.mason/partial-snapshots/` and are merged in the reduce step.",
|
|
8246
8505
|
{
|
|
8247
|
-
dir:
|
|
8248
|
-
batchId:
|
|
8249
|
-
offset:
|
|
8250
|
-
features:
|
|
8251
|
-
|
|
8252
|
-
description:
|
|
8253
|
-
files:
|
|
8254
|
-
tests:
|
|
8255
|
-
type:
|
|
8506
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8507
|
+
batchId: z19.string().describe("The `batchId` returned by `generate_snapshot_batch`."),
|
|
8508
|
+
offset: z19.number().int().describe("The `offset` returned by `generate_snapshot_batch`. Used to order partials in the reduce step."),
|
|
8509
|
+
features: z19.record(
|
|
8510
|
+
z19.object({
|
|
8511
|
+
description: z19.string(),
|
|
8512
|
+
files: z19.array(z19.string()),
|
|
8513
|
+
tests: z19.array(z19.string()).optional(),
|
|
8514
|
+
type: z19.enum(["capability", "infrastructure"]).optional().describe(
|
|
8256
8515
|
'"capability" (user-facing functionality) or "infrastructure" (internal plumbing with no end user \u2014 DI/service wiring, config, logging, adapters). Defaults to "capability".'
|
|
8257
8516
|
)
|
|
8258
8517
|
})
|
|
8259
8518
|
).describe("Partial features for this batch only \u2014 files outside the batch will be added by other partials."),
|
|
8260
|
-
flows:
|
|
8261
|
-
|
|
8262
|
-
description:
|
|
8263
|
-
chain:
|
|
8519
|
+
flows: z19.record(
|
|
8520
|
+
z19.object({
|
|
8521
|
+
description: z19.string(),
|
|
8522
|
+
chain: z19.array(z19.string())
|
|
8264
8523
|
})
|
|
8265
8524
|
).describe("Partial flows whose entire chain is in this batch. Cross-batch flows are reconstructed in reduce.")
|
|
8266
8525
|
},
|
|
@@ -8275,7 +8534,7 @@ function createMcpServer() {
|
|
|
8275
8534
|
"reduce_snapshot",
|
|
8276
8535
|
"Reduce step of the concept-map build. Returns every partial snapshot plus a system prompt asking you to merge them into one coherent project-wide map. Resolve platform variants into single product features, dedupe near-duplicates, and ensure no file is dropped. After producing the unified map, call `save_snapshot` to persist it (this also clears the partials).",
|
|
8277
8536
|
{
|
|
8278
|
-
dir:
|
|
8537
|
+
dir: z19.string().describe("Absolute path to the project root directory")
|
|
8279
8538
|
},
|
|
8280
8539
|
async ({ dir }) => {
|
|
8281
8540
|
const result = await reduceSnapshot(dir);
|
|
@@ -8288,25 +8547,25 @@ function createMcpServer() {
|
|
|
8288
8547
|
"save_snapshot",
|
|
8289
8548
|
"Save a concept-to-files map as a persistent project snapshot. Maps feature names and data flows to the files that implement them. Persists across conversations \u2014 future sessions can call get_snapshot to instantly find relevant files. No API key needed \u2014 you are the LLM generating the map.",
|
|
8290
8549
|
{
|
|
8291
|
-
dir:
|
|
8292
|
-
features:
|
|
8293
|
-
|
|
8294
|
-
description:
|
|
8295
|
-
files:
|
|
8296
|
-
tests:
|
|
8297
|
-
type:
|
|
8550
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8551
|
+
features: z19.record(
|
|
8552
|
+
z19.object({
|
|
8553
|
+
description: z19.string().describe("One-line description of the feature"),
|
|
8554
|
+
files: z19.array(z19.string()).describe("File paths that implement this feature"),
|
|
8555
|
+
tests: z19.array(z19.string()).optional().describe("Test file paths for this feature"),
|
|
8556
|
+
type: z19.enum(["capability", "infrastructure"]).optional().describe(
|
|
8298
8557
|
'Classification: "capability" for user-facing functionality, "infrastructure" for internal plumbing with no end user (DI/service wiring, config, logging, adapters). Capabilities are published to Confluence; infrastructure stays in the AI concept map only. Defaults to "capability".'
|
|
8299
8558
|
)
|
|
8300
8559
|
})
|
|
8301
8560
|
).describe("Map of feature names to their implementing files"),
|
|
8302
|
-
flows:
|
|
8303
|
-
|
|
8304
|
-
description:
|
|
8305
|
-
chain:
|
|
8561
|
+
flows: z19.record(
|
|
8562
|
+
z19.object({
|
|
8563
|
+
description: z19.string().describe("One-line description of the flow"),
|
|
8564
|
+
chain: z19.array(z19.string()).describe("Ordered list of file paths showing data/call flow")
|
|
8306
8565
|
})
|
|
8307
8566
|
).describe("Map of flow names to ordered file chains"),
|
|
8308
|
-
removeFeatures:
|
|
8309
|
-
removeFlows:
|
|
8567
|
+
removeFeatures: z19.array(z19.string()).optional().describe("Feature names to delete from the existing map \u2014 for features that were renamed or no longer exist. Applied before merging; only meaningful on incremental saves."),
|
|
8568
|
+
removeFlows: z19.array(z19.string()).optional().describe("Flow names to delete from the existing map. Applied before merging; only meaningful on incremental saves.")
|
|
8310
8569
|
},
|
|
8311
8570
|
async ({ dir, features, flows, removeFeatures, removeFlows }) => {
|
|
8312
8571
|
const result = await saveSnapshotData(
|
|
@@ -8325,17 +8584,17 @@ function createMcpServer() {
|
|
|
8325
8584
|
"save_decision",
|
|
8326
8585
|
"Capture or revise a decision proposal with rationale, anchors, optional owner, sources, and a known actor. No setup or map required. Writes a local record and preserves content history. Changes create a pending proposal while the last accepted revision remains operative; unchanged content does not re-verify or refresh it. Use review_decision for authorized acceptance or reaffirmation. A proposal cannot supersede a record with an operative accepted revision; review its replacement and retire the original separately.",
|
|
8327
8586
|
{
|
|
8328
|
-
dir:
|
|
8329
|
-
title:
|
|
8330
|
-
body:
|
|
8331
|
-
category:
|
|
8332
|
-
files:
|
|
8333
|
-
id:
|
|
8334
|
-
supersedes:
|
|
8587
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8588
|
+
title: z19.string().max(80).describe("Short, specific headline \u2014 becomes the stable record id"),
|
|
8589
|
+
body: z19.string().max(1500).describe("The knowledge itself: what was tried/decided, why, and what to avoid. Must contain information NOT derivable by reading the code."),
|
|
8590
|
+
category: z19.enum(["decision", "gotcha", "deprecation", "convention"]),
|
|
8591
|
+
files: z19.array(z19.string()).optional().describe("Repo-relative files or directory prefixes this applies to. Matching is shared by retrieval, hooks, review, and drift checking; changes flag the decision for re-verification."),
|
|
8592
|
+
id: z19.string().optional().describe("Existing id to revise. Changed content becomes a proposal; unchanged content leaves the review and freshness untouched."),
|
|
8593
|
+
supersedes: z19.string().optional().describe("Id of an unreviewed record or proposal with no accepted revision to replace. Operative accepted decisions require separate review and retirement."),
|
|
8335
8594
|
owner: attributionSchema.shape.owner.describe("Responsible person or team, when known. Null clears it. Required for acceptance."),
|
|
8336
8595
|
sources: attributionSchema.shape.sources.describe("Known PR, issue, incident, discussion, or document references. Omit to preserve; [] clears. At least one is required for acceptance."),
|
|
8337
8596
|
actor: attributionSchema.shape.actor.describe("Known person or agent recording this revision. Omit if unknown; do not infer from Git identity."),
|
|
8338
|
-
force:
|
|
8597
|
+
force: z19.boolean().optional().describe("Save even when a near-duplicate was detected")
|
|
8339
8598
|
},
|
|
8340
8599
|
async ({ dir, title, body, category, files, id, supersedes, force, owner, sources, actor }) => {
|
|
8341
8600
|
const result = await saveDecision(dir, {
|
|
@@ -8357,12 +8616,12 @@ function createMcpServer() {
|
|
|
8357
8616
|
"review_decision",
|
|
8358
8617
|
"Prepare a decision review: returns the full record and history, any operative accepted revision, provenance, changes and previews for both sets of anchors, and a reviewToken. Then record accept, reaffirm, or retire with that token, the authorized reviewer, and a reason. Acceptance replaces the operative revision; retirement withdraws the entire record including its proposal. Acceptance requires owner, source, readable Git HEAD, and committed anchor changes. Changed records or code invalidate the token. Identities and approvals are recorded assertions for normal PR review, not authenticated proof.",
|
|
8359
8618
|
{
|
|
8360
|
-
dir:
|
|
8361
|
-
id:
|
|
8362
|
-
action:
|
|
8363
|
-
reviewer:
|
|
8364
|
-
note:
|
|
8365
|
-
reviewToken:
|
|
8619
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8620
|
+
id: z19.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Decision id from get_context or save_decision"),
|
|
8621
|
+
action: z19.enum(["prepare", "accept", "reaffirm", "retire"]).optional().default("prepare").describe("Prepare is read-only. Other actions record an explicitly authorized review."),
|
|
8622
|
+
reviewer: z19.string().trim().min(1).max(200).optional().describe("Identity of the actual reviewer; required for a verdict. Never invent one."),
|
|
8623
|
+
note: z19.string().trim().min(1).max(1500).optional().describe("Review rationale; required for a verdict. Cite evidence for the decision."),
|
|
8624
|
+
reviewToken: z19.string().regex(/^[a-f0-9]{64}$/).optional().describe("Token from the prepared review; rejects stale record or code revisions")
|
|
8366
8625
|
},
|
|
8367
8626
|
async ({ dir, ...input }) => {
|
|
8368
8627
|
const result = await reviewDecision2(dir, input);
|
|
@@ -8373,7 +8632,7 @@ function createMcpServer() {
|
|
|
8373
8632
|
"mason_check_drift",
|
|
8374
8633
|
"Check how far the concept map has drifted from HEAD. Deterministic (git + filesystem, no LLM). Returns which features/flows are stale and the changed files behind them, new source files not yet mapped, ghost files (mapped but deleted), renames, and a `recommendation`: `up-to-date` (nothing to do), `incremental` (update just the stale entries via save_snapshot), or `full-rebuild` (re-run the Map-Reduce build). Call this before trusting the map in a long session, or periodically to keep the map and any synced wikis fresh.",
|
|
8375
8634
|
{
|
|
8376
|
-
dir:
|
|
8635
|
+
dir: z19.string().describe("Absolute path to the project root directory")
|
|
8377
8636
|
},
|
|
8378
8637
|
async ({ dir }) => {
|
|
8379
8638
|
const result = await checkDrift(dir);
|
|
@@ -8386,8 +8645,8 @@ function createMcpServer() {
|
|
|
8386
8645
|
"verify_snapshot",
|
|
8387
8646
|
"Spot-check the concept map's CORRECTNESS (drift checks freshness; this checks entries were right to begin with). Returns a sample of entries \u2014 always the never-verified and least-recently-verified first \u2014 with skeletons of their claimed files, for you to judge whether the files actually implement what the entry claims. Report verdicts back via save_verification. Run periodically, or after an automated refresh wrote entries no human reviewed.",
|
|
8388
8647
|
{
|
|
8389
|
-
dir:
|
|
8390
|
-
sample:
|
|
8648
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8649
|
+
sample: z19.number().int().optional().describe("Entries to sample (default 5)")
|
|
8391
8650
|
},
|
|
8392
8651
|
async ({ dir, sample }) => {
|
|
8393
8652
|
const result = await verifySnapshot(dir, sample);
|
|
@@ -8398,11 +8657,11 @@ function createMcpServer() {
|
|
|
8398
8657
|
"save_verification",
|
|
8399
8658
|
"Record verify_snapshot verdicts. Entries judged ok are stamped verifiedAt; failures are flagged verificationFailed with your note and surface in get_context, get_snapshot, and mason_check_drift until corrected. Verdict notes are required for failures.",
|
|
8400
8659
|
{
|
|
8401
|
-
dir:
|
|
8402
|
-
verdicts:
|
|
8403
|
-
|
|
8404
|
-
ok:
|
|
8405
|
-
note:
|
|
8660
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8661
|
+
verdicts: z19.record(
|
|
8662
|
+
z19.object({
|
|
8663
|
+
ok: z19.boolean(),
|
|
8664
|
+
note: z19.string().optional().describe("One line on what's wrong \u2014 required when ok is false")
|
|
8406
8665
|
})
|
|
8407
8666
|
).describe("Entry name \u2192 verdict, exactly as returned by verify_snapshot")
|
|
8408
8667
|
},
|
|
@@ -8413,10 +8672,10 @@ function createMcpServer() {
|
|
|
8413
8672
|
);
|
|
8414
8673
|
server.tool(
|
|
8415
8674
|
"get_impact",
|
|
8416
|
-
"Trace
|
|
8675
|
+
"Trace historical co-change partners, related tests, and references with evidence: resolved imports, explicit paths, or uncertain name candidates. Candidates are not proven dependencies. Deterministic and read-only; no initialization or concept map required.",
|
|
8417
8676
|
{
|
|
8418
|
-
dir:
|
|
8419
|
-
files:
|
|
8677
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8678
|
+
files: z19.array(z19.string()).describe("File paths or names to analyze (e.g., ['WeatherRepository.kt'] or ['src/services/auth.ts'])")
|
|
8420
8679
|
},
|
|
8421
8680
|
async ({ dir, files }) => {
|
|
8422
8681
|
const result = await getImpact(dir, files);
|
|
@@ -8429,12 +8688,12 @@ function createMcpServer() {
|
|
|
8429
8688
|
"export_to_confluence",
|
|
8430
8689
|
"Sync the project's concept map to Confluence as product-readable wiki pages: an index page, one page per feature (PM-language descriptions, no file paths), and a changelog page. Mason replaces managed page bodies; manual edits to those bodies are overwritten. Requires `mason_set_confluence` to have been called first.",
|
|
8431
8690
|
{
|
|
8432
|
-
dir:
|
|
8433
|
-
spaceKey:
|
|
8434
|
-
parentPageId:
|
|
8435
|
-
indexPageTitle:
|
|
8436
|
-
changelogPageTitle:
|
|
8437
|
-
featurePagePrefix:
|
|
8691
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8692
|
+
spaceKey: z19.string().optional().describe("Override the configured space key"),
|
|
8693
|
+
parentPageId: z19.string().optional().describe("Override the configured parent page ID"),
|
|
8694
|
+
indexPageTitle: z19.string().optional().describe("Title of the index page (default: 'Mason \u2014 System Map')"),
|
|
8695
|
+
changelogPageTitle: z19.string().optional().describe("Title of the changelog page (default: 'Mason \u2014 Changelog')"),
|
|
8696
|
+
featurePagePrefix: z19.string().optional().describe("Prefix for each feature page title (default: 'Feature: ')")
|
|
8438
8697
|
},
|
|
8439
8698
|
async ({ dir, spaceKey, parentPageId, indexPageTitle, changelogPageTitle, featurePagePrefix }) => {
|
|
8440
8699
|
const result = await exportToConfluenceTool(dir, {
|