mason-context 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -1
- package/README.md +39 -546
- package/dist/mason-auto.js +185 -52
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-mcp.js +351 -218
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason.js +4842 -15
- package/dist/mason.js.map +1 -1
- package/package.json +4 -2
package/dist/mason-mcp.js
CHANGED
|
@@ -3504,10 +3504,10 @@ async function analyzeImpact(rootDir, targetFiles) {
|
|
|
3504
3504
|
tests
|
|
3505
3505
|
};
|
|
3506
3506
|
}
|
|
3507
|
-
async function resolveTargetFiles(rootDir,
|
|
3507
|
+
async function resolveTargetFiles(rootDir, targets2) {
|
|
3508
3508
|
const resolved = [];
|
|
3509
3509
|
const access = await createFileAccess(rootDir);
|
|
3510
|
-
for (const target of
|
|
3510
|
+
for (const target of targets2) {
|
|
3511
3511
|
if (target.includes("/")) {
|
|
3512
3512
|
const normalized = normalizeRepoPath(target);
|
|
3513
3513
|
if (normalized) resolved.push(normalized);
|
|
@@ -3667,23 +3667,23 @@ __export(assemble_exports, {
|
|
|
3667
3667
|
import path22 from "path";
|
|
3668
3668
|
import fs14 from "fs/promises";
|
|
3669
3669
|
async function collectImpact(root, candidates) {
|
|
3670
|
-
const
|
|
3670
|
+
const targets2 = /* @__PURE__ */ new Set();
|
|
3671
3671
|
let sourceFiles;
|
|
3672
3672
|
for (const candidate of sanitizeRepoPaths(candidates)) {
|
|
3673
3673
|
const stat = await fs14.stat(path22.join(root, candidate)).catch(() => null);
|
|
3674
3674
|
if (stat?.isDirectory()) {
|
|
3675
3675
|
sourceFiles ??= await (await createFileAccess(root)).list();
|
|
3676
3676
|
for (const file of sourceFiles) {
|
|
3677
|
-
if (anchorMatches(candidate, file))
|
|
3678
|
-
if (
|
|
3677
|
+
if (anchorMatches(candidate, file)) targets2.add(file);
|
|
3678
|
+
if (targets2.size >= MAX_IMPACT_TARGETS) break;
|
|
3679
3679
|
}
|
|
3680
3680
|
} else {
|
|
3681
|
-
|
|
3681
|
+
targets2.add(candidate);
|
|
3682
3682
|
}
|
|
3683
|
-
if (
|
|
3683
|
+
if (targets2.size >= MAX_IMPACT_TARGETS) break;
|
|
3684
3684
|
}
|
|
3685
|
-
if (!
|
|
3686
|
-
const result = await analyzeImpact(root, [...
|
|
3685
|
+
if (!targets2.size) return { impact: null, relatedTests: [] };
|
|
3686
|
+
const result = await analyzeImpact(root, [...targets2]);
|
|
3687
3687
|
return {
|
|
3688
3688
|
impact: { targets: result.targetFiles, cochange: result.cochange, references: result.references.slice(0, 10) },
|
|
3689
3689
|
relatedTests: [...new Set(result.tests.map((t) => t.file))]
|
|
@@ -4163,7 +4163,7 @@ var init_evidence2 = __esm({
|
|
|
4163
4163
|
init_dead_command();
|
|
4164
4164
|
init_storage();
|
|
4165
4165
|
exec14 = promisify14(execFile14);
|
|
4166
|
-
engineVersion = true ? "0.
|
|
4166
|
+
engineVersion = true ? "0.14.0" : "development";
|
|
4167
4167
|
hash = (value) => createHash4("sha256").update(JSON.stringify(value)).digest("hex");
|
|
4168
4168
|
internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
|
|
4169
4169
|
cacheSchema = z11.object({ version: z11.literal(1), entries: z11.record(z11.object({ key: z11.string(), result: checkResultSchema })), digest: z11.string() });
|
|
@@ -4630,7 +4630,8 @@ var init_model = __esm({
|
|
|
4630
4630
|
runtimeSchema = z15.object({
|
|
4631
4631
|
id: z15.string().regex(/^[a-f0-9]{24}$/),
|
|
4632
4632
|
version: z15.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
4633
|
-
hashes: z15.record(z15.string().regex(/^[a-f0-9]{64}$/))
|
|
4633
|
+
hashes: z15.record(z15.string().regex(/^[a-f0-9]{64}$/)),
|
|
4634
|
+
bundle: z15.object({ target: z15.string(), manifestHash: z15.string().regex(/^[a-f0-9]{64}$/) }).optional()
|
|
4634
4635
|
});
|
|
4635
4636
|
setupHostSchema = z15.object({
|
|
4636
4637
|
runtime: runtimeSchema,
|
|
@@ -4656,40 +4657,109 @@ var init_model = __esm({
|
|
|
4656
4657
|
}
|
|
4657
4658
|
});
|
|
4658
4659
|
|
|
4659
|
-
// src/
|
|
4660
|
+
// src/distribution/bundle.ts
|
|
4660
4661
|
import fs17 from "fs/promises";
|
|
4661
4662
|
import path24 from "path";
|
|
4663
|
+
import { createHash as createHash5 } from "crypto";
|
|
4664
|
+
import { z as z16 } from "zod";
|
|
4665
|
+
async function readBundle(root) {
|
|
4666
|
+
const bytes = await fs17.readFile(await storePath(root, "bundle.json"));
|
|
4667
|
+
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
4668
|
+
const windows = manifest.target.startsWith("win32-");
|
|
4669
|
+
for (const file of Object.keys(manifest.files)) {
|
|
4670
|
+
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path24.isAbsolute(file)) {
|
|
4671
|
+
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-auto.js", "app/dist/mason-mcp.js"]) {
|
|
4675
|
+
if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
|
|
4676
|
+
}
|
|
4677
|
+
return { manifest, manifestHash: sha256(bytes) };
|
|
4678
|
+
}
|
|
4679
|
+
async function verifyBundle(root, expectedHash) {
|
|
4680
|
+
const bundle = await readBundle(root);
|
|
4681
|
+
if (expectedHash && bundle.manifestHash !== expectedHash) throw new Error("Mason bundle manifest changed.");
|
|
4682
|
+
if (bundle.manifest.target !== `${process.platform}-${process.arch}`) throw new Error("Mason bundle is for another platform. Install this platform's bundle and rerun setup.");
|
|
4683
|
+
const files = Object.keys(bundle.manifest.files);
|
|
4684
|
+
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
4685
|
+
if (sha256(await fs17.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
4686
|
+
}));
|
|
4687
|
+
return bundle;
|
|
4688
|
+
}
|
|
4689
|
+
var targets, digest2, bundleSchema, sha256;
|
|
4690
|
+
var init_bundle = __esm({
|
|
4691
|
+
"src/distribution/bundle.ts"() {
|
|
4692
|
+
"use strict";
|
|
4693
|
+
init_storage();
|
|
4694
|
+
targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
4695
|
+
digest2 = z16.string().regex(/^[a-f0-9]{64}$/);
|
|
4696
|
+
bundleSchema = z16.object({
|
|
4697
|
+
format: z16.literal(1),
|
|
4698
|
+
version: z16.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
4699
|
+
target: z16.enum(targets),
|
|
4700
|
+
nodeVersion: z16.string(),
|
|
4701
|
+
files: z16.record(digest2)
|
|
4702
|
+
}).strict();
|
|
4703
|
+
sha256 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
4704
|
+
}
|
|
4705
|
+
});
|
|
4706
|
+
|
|
4707
|
+
// src/setup/runtime.ts
|
|
4708
|
+
import fs18 from "fs/promises";
|
|
4709
|
+
import path25 from "path";
|
|
4662
4710
|
import { fileURLToPath } from "url";
|
|
4663
|
-
import { randomUUID as randomUUID5, createHash as
|
|
4711
|
+
import { randomUUID as randomUUID5, createHash as createHash6 } from "crypto";
|
|
4664
4712
|
import { execFile as execFile15 } from "child_process";
|
|
4665
4713
|
import { promisify as promisify15 } from "util";
|
|
4666
4714
|
async function packageRoot() {
|
|
4667
|
-
let directory =
|
|
4715
|
+
let directory = path25.dirname(fileURLToPath(import.meta.url));
|
|
4668
4716
|
for (let i = 0; i < 8; i++) {
|
|
4669
4717
|
try {
|
|
4670
|
-
const pkg = JSON.parse(await
|
|
4718
|
+
const pkg = JSON.parse(await fs18.readFile(path25.join(directory, "package.json"), "utf8"));
|
|
4671
4719
|
if (pkg.name === "mason-context") return directory;
|
|
4672
4720
|
} catch {
|
|
4673
4721
|
}
|
|
4674
|
-
directory =
|
|
4722
|
+
directory = path25.dirname(directory);
|
|
4675
4723
|
}
|
|
4676
4724
|
throw new Error("Cannot locate the executing Mason distribution. Reinstall Mason and rerun setup.");
|
|
4677
4725
|
}
|
|
4678
4726
|
async function sourceRuntime() {
|
|
4679
4727
|
const source = await packageRoot();
|
|
4680
|
-
const pkg = JSON.parse(await
|
|
4681
|
-
const hashes = Object.fromEntries(await Promise.all(BINARIES.map(async (file) => [file, checksum(await
|
|
4728
|
+
const pkg = JSON.parse(await fs18.readFile(path25.join(source, "package.json"), "utf8"));
|
|
4729
|
+
const hashes = Object.fromEntries(await Promise.all(BINARIES.map(async (file) => [file, checksum(await fs18.readFile(path25.join(source, file)))])));
|
|
4730
|
+
const bundleRoot = path25.dirname(source);
|
|
4731
|
+
const bundleExists = await fs18.lstat(path25.join(bundleRoot, "bundle.json")).then(() => true, (error) => {
|
|
4732
|
+
if (error.code === "ENOENT") return false;
|
|
4733
|
+
throw error;
|
|
4734
|
+
});
|
|
4735
|
+
if (bundleExists) {
|
|
4736
|
+
const { manifest, manifestHash } = await verifyBundle(bundleRoot);
|
|
4737
|
+
if (manifest.version !== pkg.version) throw new Error("Mason package and bundle versions differ.");
|
|
4738
|
+
return { source, bundleRoot, runtime: runtimeSchema.parse({
|
|
4739
|
+
id: manifestHash.slice(0, 24),
|
|
4740
|
+
version: pkg.version,
|
|
4741
|
+
hashes,
|
|
4742
|
+
bundle: { target: manifest.target, manifestHash }
|
|
4743
|
+
}) };
|
|
4744
|
+
}
|
|
4682
4745
|
return { source, runtime: runtimeSchema.parse({ id: hash([pkg.version, pkg.dependencies, hashes]).slice(0, 24), version: pkg.version, hashes }) };
|
|
4683
4746
|
}
|
|
4684
4747
|
async function verifyRuntime(root, runtime) {
|
|
4685
4748
|
try {
|
|
4686
4749
|
const saved = await readStoreJson(root, `.mason/runtime/${runtime.id}/receipt.json`);
|
|
4687
4750
|
if (JSON.stringify(saved) !== JSON.stringify(runtime)) return false;
|
|
4751
|
+
if (runtime.bundle) {
|
|
4752
|
+
const base2 = await storePath(root, `.mason/runtime/${runtime.id}`);
|
|
4753
|
+
const { manifest } = await verifyBundle(base2, runtime.bundle.manifestHash);
|
|
4754
|
+
if (manifest.version !== runtime.version || manifest.target !== runtime.bundle.target) return false;
|
|
4755
|
+
for (const file of BINARIES) if (manifest.files["app/" + file] !== runtime.hashes[file]) return false;
|
|
4756
|
+
return true;
|
|
4757
|
+
}
|
|
4688
4758
|
const base = `.mason/runtime/${runtime.id}/node_modules/mason-context/`;
|
|
4689
4759
|
const pkg = await readStoreJson(root, base + "package.json");
|
|
4690
4760
|
if (pkg?.name !== "mason-context" || pkg.version !== runtime.version) return false;
|
|
4691
4761
|
for (const file of BINARIES) {
|
|
4692
|
-
if (checksum(await
|
|
4762
|
+
if (checksum(await fs18.readFile(await storePath(root, base + file))) !== runtime.hashes[file]) return false;
|
|
4693
4763
|
}
|
|
4694
4764
|
return true;
|
|
4695
4765
|
} catch {
|
|
@@ -4702,30 +4772,42 @@ async function installRuntime(root, selected) {
|
|
|
4702
4772
|
const relative = `.mason/runtime/${runtime.id}`;
|
|
4703
4773
|
const target = await storePath(root, relative, true);
|
|
4704
4774
|
const stage = await storePath(root, ".mason/runtime/.install-" + randomUUID5(), true);
|
|
4705
|
-
await
|
|
4775
|
+
await fs18.mkdir(stage);
|
|
4706
4776
|
try {
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4777
|
+
if (runtime.bundle) {
|
|
4778
|
+
if (!selected.bundleRoot) throw new Error("Standalone bundle source is missing.");
|
|
4779
|
+
const { manifest } = await verifyBundle(selected.bundleRoot, runtime.bundle.manifestHash);
|
|
4780
|
+
for (const file of ["bundle.json", ...Object.keys(manifest.files)]) {
|
|
4781
|
+
const from = await storePath(selected.bundleRoot, file);
|
|
4782
|
+
const to = await storePath(stage, file, true);
|
|
4783
|
+
await fs18.copyFile(from, to);
|
|
4784
|
+
await fs18.chmod(to, (await fs18.stat(from)).mode & 511);
|
|
4785
|
+
}
|
|
4786
|
+
await verifyBundle(stage, runtime.bundle.manifestHash);
|
|
4787
|
+
} else {
|
|
4788
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
4789
|
+
const options = { timeout: 12e4, maxBuffer: 2 * 1024 * 1024, windowsHide: true };
|
|
4790
|
+
const packed = JSON.parse((await exec15(npm, ["pack", "--ignore-scripts", "--json", "--pack-destination", stage], { ...options, cwd: source })).stdout);
|
|
4791
|
+
const filename = packed[0]?.filename;
|
|
4792
|
+
if (typeof filename !== "string" || path25.basename(filename) !== filename) throw new Error("npm did not produce a Mason package archive.");
|
|
4793
|
+
await fs18.rename(path25.join(stage, filename), path25.join(stage, "mason.tgz"));
|
|
4794
|
+
await fs18.writeFile(path25.join(stage, "package.json"), JSON.stringify({ name: "mason-project-runtime", private: true, version: "1.0.0" }));
|
|
4795
|
+
await exec15(npm, ["install", "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund", "--save-exact", "./mason.tgz"], { ...options, cwd: stage });
|
|
4796
|
+
for (const file of BINARIES) {
|
|
4797
|
+
if (checksum(await fs18.readFile(path25.join(stage, "node_modules/mason-context", file))) !== runtime.hashes[file]) throw new Error("Installed Mason binary differs from the selected distribution.");
|
|
4798
|
+
}
|
|
4717
4799
|
}
|
|
4718
|
-
await
|
|
4800
|
+
await fs18.writeFile(path25.join(stage, "receipt.json"), JSON.stringify(runtime, null, 2) + "\n");
|
|
4719
4801
|
try {
|
|
4720
|
-
await
|
|
4802
|
+
await fs18.rename(target, target + ".previous-" + randomUUID5());
|
|
4721
4803
|
} catch (error) {
|
|
4722
4804
|
if (error.code !== "ENOENT") throw error;
|
|
4723
4805
|
}
|
|
4724
|
-
await
|
|
4806
|
+
await fs18.rename(stage, target);
|
|
4725
4807
|
if (!await verifyRuntime(root, runtime)) throw new Error("The installed Mason runtime could not be verified.");
|
|
4726
4808
|
return runtime;
|
|
4727
4809
|
} finally {
|
|
4728
|
-
await
|
|
4810
|
+
await fs18.rm(stage, { recursive: true, force: true });
|
|
4729
4811
|
}
|
|
4730
4812
|
}
|
|
4731
4813
|
var exec15, checksum, BINARIES;
|
|
@@ -4735,15 +4817,16 @@ var init_runtime2 = __esm({
|
|
|
4735
4817
|
init_model();
|
|
4736
4818
|
init_storage();
|
|
4737
4819
|
init_evidence2();
|
|
4820
|
+
init_bundle();
|
|
4738
4821
|
exec15 = promisify15(execFile15);
|
|
4739
|
-
checksum = (bytes) =>
|
|
4822
|
+
checksum = (bytes) => createHash6("sha256").update(bytes).digest("hex");
|
|
4740
4823
|
BINARIES = ["dist/mason-auto.js", "dist/mason-mcp.js"];
|
|
4741
4824
|
}
|
|
4742
4825
|
});
|
|
4743
4826
|
|
|
4744
4827
|
// src/setup/files.ts
|
|
4745
|
-
import
|
|
4746
|
-
import
|
|
4828
|
+
import fs19 from "fs/promises";
|
|
4829
|
+
import path26 from "path";
|
|
4747
4830
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
4748
4831
|
async function readText(root, file) {
|
|
4749
4832
|
try {
|
|
@@ -4770,10 +4853,10 @@ async function applyEdit(root, edit) {
|
|
|
4770
4853
|
if (current === edit.after) return false;
|
|
4771
4854
|
if (current !== edit.before) throw new Error("Setup input changed during installation: " + edit.path + ". Rerun setup to resume.");
|
|
4772
4855
|
const file = await storePath(root, edit.path, true);
|
|
4773
|
-
const temporary =
|
|
4856
|
+
const temporary = path26.join(path26.dirname(file), ".mason-setup-" + randomUUID6() + ".tmp");
|
|
4774
4857
|
try {
|
|
4775
|
-
const mode = await
|
|
4776
|
-
const handle = await
|
|
4858
|
+
const mode = await fs19.stat(file).then((s) => s.mode & 511, () => 384);
|
|
4859
|
+
const handle = await fs19.open(temporary, "wx", mode);
|
|
4777
4860
|
try {
|
|
4778
4861
|
await handle.writeFile(edit.after, "utf8");
|
|
4779
4862
|
await handle.sync();
|
|
@@ -4781,10 +4864,10 @@ async function applyEdit(root, edit) {
|
|
|
4781
4864
|
await handle.close();
|
|
4782
4865
|
}
|
|
4783
4866
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Setup input changed during installation: " + edit.path);
|
|
4784
|
-
await
|
|
4867
|
+
await fs19.rename(temporary, file);
|
|
4785
4868
|
return true;
|
|
4786
4869
|
} finally {
|
|
4787
|
-
await
|
|
4870
|
+
await fs19.rm(temporary, { force: true });
|
|
4788
4871
|
}
|
|
4789
4872
|
}
|
|
4790
4873
|
var init_files2 = __esm({
|
|
@@ -4796,18 +4879,55 @@ var init_files2 = __esm({
|
|
|
4796
4879
|
});
|
|
4797
4880
|
|
|
4798
4881
|
// src/setup/launcher.ts
|
|
4799
|
-
var BOOTSTRAP, mcpCommand, hookCommand, LAUNCHER;
|
|
4882
|
+
var BOOTSTRAP, POWERSHELL_BOOTSTRAP, mcpCommand, hookCommand, SHELL_LAUNCHER, POWERSHELL_LAUNCHER, LAUNCHER;
|
|
4800
4883
|
var init_launcher = __esm({
|
|
4801
4884
|
"src/setup/launcher.ts"() {
|
|
4802
4885
|
"use strict";
|
|
4803
4886
|
BOOTSTRAP = "require(require('node:path').join(require('node:child_process').execFileSync('git',['rev-parse','--show-toplevel'],{encoding:'utf8'}).trim(),'.mason','run.cjs'))";
|
|
4804
|
-
|
|
4805
|
-
|
|
4887
|
+
POWERSHELL_BOOTSTRAP = "& (Join-Path (git rev-parse --show-toplevel) '.mason/run.ps1')";
|
|
4888
|
+
mcpCommand = (host, runtime) => !runtime?.bundle ? { command: "node", args: ["-e", BOOTSTRAP, "--", host, "mcp"] } : process.platform === "win32" ? { command: "powershell.exe", args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", POWERSHELL_BOOTSTRAP, host, "mcp"] } : { command: "sh", args: ["-c", 'exec sh "$(git rev-parse --show-toplevel)/.mason/run.sh" "$@"', "mason", host, "mcp"] };
|
|
4889
|
+
hookCommand = (host, runtime) => !runtime?.bundle ? `node -e "${BOOTSTRAP}" -- ${host} auto` : process.platform === "win32" ? `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "${POWERSHELL_BOOTSTRAP} ${host} auto"` : `sh "$(git rev-parse --show-toplevel)/.mason/run.sh" ${host} auto`;
|
|
4890
|
+
SHELL_LAUNCHER = `#!/bin/sh
|
|
4891
|
+
set -eu
|
|
4892
|
+
advisory=0
|
|
4893
|
+
if [ "\${2-}" = auto ] && [ "\${3-}" = hook ]; then advisory=1; fi
|
|
4894
|
+
fail() {
|
|
4895
|
+
if [ "$advisory" -eq 1 ]; then printf '%s\\n' '{"systemMessage":"Mason runtime unavailable. Rerun mason setup for this checkout; verification was not established."}'; exit 0; fi
|
|
4896
|
+
echo 'Mason runtime unavailable. Rerun mason setup for this checkout.' >&2; exit 2
|
|
4897
|
+
}
|
|
4898
|
+
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) || fail
|
|
4899
|
+
case "\${1-}" in codex|claude) ;; *) fail ;; esac
|
|
4900
|
+
pointer="$root/.mason/runtime/$1.txt"
|
|
4901
|
+
[ -f "$pointer" ] || fail
|
|
4902
|
+
runtime=$(cat "$pointer") || fail
|
|
4903
|
+
case "$runtime" in ''|*[!0-9a-f]*) fail ;; esac
|
|
4904
|
+
[ "\${#runtime}" -eq 24 ] || fail
|
|
4905
|
+
[ -x "$root/.mason/runtime/$runtime/node" ] || fail
|
|
4906
|
+
if [ "$advisory" -eq 1 ]; then
|
|
4907
|
+
"$root/.mason/runtime/$runtime/node" "$root/.mason/run.cjs" "$@" || fail
|
|
4908
|
+
else
|
|
4909
|
+
exec "$root/.mason/runtime/$runtime/node" "$root/.mason/run.cjs" "$@"
|
|
4910
|
+
fi
|
|
4911
|
+
`;
|
|
4912
|
+
POWERSHELL_LAUNCHER = `# Managed by mason setup; no system Node installation is used.
|
|
4913
|
+
$ErrorActionPreference = 'Stop'
|
|
4914
|
+
$advisory = $args.Count -ge 3 -and $args[1] -eq 'auto' -and $args[2] -eq 'hook'
|
|
4915
|
+
try {
|
|
4916
|
+
if ($args.Count -lt 2 -or $args[0] -notin @('codex','claude')) { throw 'Invalid Mason host.' }
|
|
4917
|
+
$runtime = (Get-Content -LiteralPath (Join-Path $PSScriptRoot ('runtime/' + $args[0] + '.txt')) -Raw).Trim()
|
|
4918
|
+
if ($runtime -cnotmatch '^[a-f0-9]{24}$') { throw 'Invalid Mason runtime pointer.' }
|
|
4919
|
+
& (Join-Path $PSScriptRoot ('runtime/' + $runtime + '/node.exe')) (Join-Path $PSScriptRoot 'run.cjs') @args
|
|
4920
|
+
if ($LASTEXITCODE -ne 0) { throw 'Mason runtime failed.' }
|
|
4921
|
+
} catch {
|
|
4922
|
+
if ($advisory) { Write-Output '{"systemMessage":"Mason runtime unavailable. Rerun mason setup for this checkout; verification was not established."}'; exit 0 }
|
|
4923
|
+
[Console]::Error.WriteLine($_.Exception.Message); exit 2
|
|
4924
|
+
}
|
|
4925
|
+
`;
|
|
4806
4926
|
LAUNCHER = `// Managed by mason-auto setup. Rerun setup to install this checkout's pinned runtime.
|
|
4807
4927
|
const fs = require('node:fs');
|
|
4808
4928
|
const path = require('node:path');
|
|
4809
4929
|
const {pathToFileURL} = require('node:url');
|
|
4810
|
-
const launchArgs = process.argv.slice(1);
|
|
4930
|
+
const launchArgs = process.argv.slice(process.argv[1] && path.resolve(process.argv[1]) === __filename ? 2 : 1);
|
|
4811
4931
|
(async () => {
|
|
4812
4932
|
const root = path.dirname(__dirname);
|
|
4813
4933
|
const args = launchArgs;
|
|
@@ -4816,7 +4936,13 @@ const launchArgs = process.argv.slice(1);
|
|
|
4816
4936
|
const setup = JSON.parse(fs.readFileSync(path.join(__dirname, 'setup.json'), 'utf8'));
|
|
4817
4937
|
const entry = setup.hosts?.[host];
|
|
4818
4938
|
if (setup.version !== 1 || !entry || !/^[a-f0-9]{24}$/.test(entry.runtime?.id)) throw new Error('Mason is not configured for this host. Rerun mason-auto setup.');
|
|
4819
|
-
const
|
|
4939
|
+
const base = path.join(__dirname, 'runtime', entry.runtime.id);
|
|
4940
|
+
if (entry.runtime.bundle) {
|
|
4941
|
+
const node = process.platform === 'win32' ? 'node.exe' : 'node';
|
|
4942
|
+
if (fs.realpathSync(process.execPath) !== fs.realpathSync(path.join(base, node))) throw new Error('Mason runtime pointer differs from the configured version. Rerun setup.');
|
|
4943
|
+
}
|
|
4944
|
+
const binary = entry.runtime.bundle ? path.join(base, 'app', 'dist', 'mason-' + command + '.js')
|
|
4945
|
+
: path.join(base, 'node_modules', 'mason-context', 'dist', 'mason-' + command + '.js');
|
|
4820
4946
|
if (!fs.existsSync(binary)) throw new Error('The pinned Mason runtime is missing in this checkout. Rerun mason-auto setup --host ' + host + '.');
|
|
4821
4947
|
process.env.MASON_SETUP_ROOT = root;
|
|
4822
4948
|
process.env.MASON_SETUP_HOST = host;
|
|
@@ -4835,7 +4961,7 @@ const launchArgs = process.argv.slice(1);
|
|
|
4835
4961
|
// src/setup/config.ts
|
|
4836
4962
|
import { isDeepStrictEqual } from "util";
|
|
4837
4963
|
import { parse, stringify } from "smol-toml";
|
|
4838
|
-
import { z as
|
|
4964
|
+
import { z as z17 } from "zod";
|
|
4839
4965
|
async function instructionEdits(root, host) {
|
|
4840
4966
|
const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
|
|
4841
4967
|
const existing = found.find((f) => f.text !== null);
|
|
@@ -4864,22 +4990,22 @@ async function instructionEdits(root, host) {
|
|
|
4864
4990
|
}
|
|
4865
4991
|
return edits;
|
|
4866
4992
|
}
|
|
4867
|
-
function managedMcp(previous, host) {
|
|
4993
|
+
function managedMcp(previous, host, runtime) {
|
|
4868
4994
|
const options = previous === void 0 ? {} : object.parse(previous);
|
|
4869
4995
|
for (const key of ["command", "args", "url", "type", "headers", "http_headers", "env_http_headers", "bearer_token_env_var"]) delete options[key];
|
|
4870
|
-
return { ...options, ...mcpCommand(host) };
|
|
4996
|
+
return { ...options, ...mcpCommand(host, runtime) };
|
|
4871
4997
|
}
|
|
4872
|
-
async function mcpEdit(root, host) {
|
|
4998
|
+
async function mcpEdit(root, host, runtime) {
|
|
4873
4999
|
const file = mcpPath(host), before = await readText(root, file);
|
|
4874
5000
|
if (host === "claude") {
|
|
4875
5001
|
const config2 = before === null ? {} : object.parse(JSON.parse(before));
|
|
4876
5002
|
const servers2 = object.parse(config2.mcpServers ?? {});
|
|
4877
|
-
return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host) } }, null, 2) + "\n" };
|
|
5003
|
+
return { path: file, before, after: JSON.stringify({ ...config2, mcpServers: { ...servers2, mason: managedMcp(servers2.mason, host, runtime) } }, null, 2) + "\n" };
|
|
4878
5004
|
}
|
|
4879
5005
|
const config = parse(before ?? "");
|
|
4880
5006
|
const managed = (before ?? "").includes(TOML_START);
|
|
4881
5007
|
const servers = config.mcp_servers;
|
|
4882
|
-
const desired = managedMcp(servers?.mason, host);
|
|
5008
|
+
const desired = managedMcp(servers?.mason, host, runtime);
|
|
4883
5009
|
let base = before ?? "";
|
|
4884
5010
|
if (servers?.mason && !managed) {
|
|
4885
5011
|
const headers = [...base.matchAll(/^\s*\[\[?.+?\]\]?[^\S\r\n]*(?:#.*)?$/gm)];
|
|
@@ -4893,10 +5019,10 @@ async function mcpEdit(root, host) {
|
|
|
4893
5019
|
if (!isDeepStrictEqual(parsed, expected)) throw new Error("Could not safely configure the Mason MCP entry without changing other settings.");
|
|
4894
5020
|
return { path: file, before, after };
|
|
4895
5021
|
}
|
|
4896
|
-
async function hookEdits(root, host) {
|
|
5022
|
+
async function hookEdits(root, host, runtime) {
|
|
4897
5023
|
const file = configPath(host), before = await readText(root, file);
|
|
4898
5024
|
const { planAutomationInstall: planAutomationInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
4899
|
-
const plan = await planAutomationInstall2(root, host, hookCommand(host));
|
|
5025
|
+
const plan = await planAutomationInstall2(root, host, hookCommand(host, runtime));
|
|
4900
5026
|
return [
|
|
4901
5027
|
{ path: file, before, after: JSON.stringify(plan.config, null, 2) + "\n" },
|
|
4902
5028
|
{ path: ".mason/automation.json", before: await readText(root, ".mason/automation.json"), after: JSON.stringify(plan.record, null, 2) + "\n" }
|
|
@@ -4912,18 +5038,20 @@ async function ancillaryEdits(root) {
|
|
|
4912
5038
|
if (error.code !== 1) throw error;
|
|
4913
5039
|
}
|
|
4914
5040
|
const retainParentRule = parentIgnored || !!ignore?.replace(/\r\n/g, "\n").includes("# mason:ignore:start\n!/.mason/\n/.mason/*");
|
|
4915
|
-
const rules = [...retainParentRule ? ["!/.mason/", "/.mason/*"] : [], "!/.mason/decisions/", "!/.mason/decisions/**", "!/.mason/setup.json", "!/.mason/automation.json", "!/.mason/project.json", "!/.mason/run.cjs", "/.mason/reports/", "/.mason/runtime/"].join("\n");
|
|
5041
|
+
const rules = [...retainParentRule ? ["!/.mason/", "/.mason/*"] : [], "!/.mason/decisions/", "!/.mason/decisions/**", "!/.mason/setup.json", "!/.mason/automation.json", "!/.mason/project.json", "!/.mason/run.cjs", "!/.mason/run.sh", "!/.mason/run.ps1", "/.mason/reports/", "/.mason/runtime/"].join("\n");
|
|
4916
5042
|
return [
|
|
4917
5043
|
{ path: ".gitignore", before: ignore, after: managedBlock(ignore ?? "", "# mason:ignore:start", "# mason:ignore:end", rules) },
|
|
4918
|
-
{ path: ".mason/run.cjs", before: await readText(root, ".mason/run.cjs"), after: LAUNCHER }
|
|
5044
|
+
{ path: ".mason/run.cjs", before: await readText(root, ".mason/run.cjs"), after: LAUNCHER },
|
|
5045
|
+
{ path: ".mason/run.sh", before: await readText(root, ".mason/run.sh"), after: SHELL_LAUNCHER },
|
|
5046
|
+
{ path: ".mason/run.ps1", before: await readText(root, ".mason/run.ps1"), after: POWERSHELL_LAUNCHER }
|
|
4919
5047
|
];
|
|
4920
5048
|
}
|
|
4921
|
-
async function inspectHostConfig(root, host, plannedMcp) {
|
|
5049
|
+
async function inspectHostConfig(root, host, plannedMcp, runtime) {
|
|
4922
5050
|
const text2 = plannedMcp ?? await readText(root, mcpPath(host));
|
|
4923
5051
|
const config = host === "codex" ? parse(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
|
|
4924
5052
|
const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
|
|
4925
5053
|
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
|
|
4926
|
-
const expected = hookConfig(host, hookCommand(host));
|
|
5054
|
+
const expected = hookConfig(host, hookCommand(host, runtime));
|
|
4927
5055
|
const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
|
|
4928
5056
|
return {
|
|
4929
5057
|
mcp,
|
|
@@ -4945,7 +5073,7 @@ var init_config = __esm({
|
|
|
4945
5073
|
init_install();
|
|
4946
5074
|
init_files2();
|
|
4947
5075
|
init_launcher();
|
|
4948
|
-
object =
|
|
5076
|
+
object = z17.record(z17.unknown());
|
|
4949
5077
|
mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
4950
5078
|
TOML_START = "# mason:mcp:start";
|
|
4951
5079
|
TOML_END = "# mason:mcp:end";
|
|
@@ -4961,8 +5089,8 @@ __export(observations_exports, {
|
|
|
4961
5089
|
observeActivation: () => observeActivation,
|
|
4962
5090
|
readObservation: () => readObservation
|
|
4963
5091
|
});
|
|
4964
|
-
import
|
|
4965
|
-
import { z as
|
|
5092
|
+
import fs20 from "fs/promises";
|
|
5093
|
+
import { z as z18 } from "zod";
|
|
4966
5094
|
function observationPath(directory, host, revision) {
|
|
4967
5095
|
return `${directory}/activation/${host}-${revision}.json`;
|
|
4968
5096
|
}
|
|
@@ -4978,8 +5106,8 @@ async function observeActivation(dir, event, options = {}) {
|
|
|
4978
5106
|
if (host !== "codex" && host !== "claude" || !revision || !process.env.MASON_SETUP_ROOT) return null;
|
|
4979
5107
|
try {
|
|
4980
5108
|
const capturedDirectory = options.reportPath?.match(/^(\.mason\/reports\/automation\/[a-f0-9]{24})\/checks\//)?.[1];
|
|
4981
|
-
const ws = capturedDirectory ? { root: await
|
|
4982
|
-
if (ws.root !== await
|
|
5109
|
+
const ws = capturedDirectory ? { root: await fs20.realpath(dir), directory: capturedDirectory } : await workspace(dir);
|
|
5110
|
+
if (ws.root !== await fs20.realpath(process.env.MASON_SETUP_ROOT)) return null;
|
|
4983
5111
|
const setup = await loadSetup(ws.root);
|
|
4984
5112
|
if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
|
|
4985
5113
|
const directory = ws.directory + "/activation";
|
|
@@ -5025,18 +5153,18 @@ var init_observations = __esm({
|
|
|
5025
5153
|
init_store();
|
|
5026
5154
|
init_storage();
|
|
5027
5155
|
init_model();
|
|
5028
|
-
observationSchema =
|
|
5029
|
-
version:
|
|
5030
|
-
root:
|
|
5031
|
-
revision:
|
|
5032
|
-
host:
|
|
5033
|
-
contextCalls:
|
|
5034
|
-
lastContextAt:
|
|
5035
|
-
sessions:
|
|
5036
|
-
events:
|
|
5037
|
-
at:
|
|
5038
|
-
verificationStatus:
|
|
5039
|
-
reportPath:
|
|
5156
|
+
observationSchema = z18.object({
|
|
5157
|
+
version: z18.literal(1),
|
|
5158
|
+
root: z18.string(),
|
|
5159
|
+
revision: z18.string(),
|
|
5160
|
+
host: z18.enum(["codex", "claude"]),
|
|
5161
|
+
contextCalls: z18.number().int().nonnegative(),
|
|
5162
|
+
lastContextAt: z18.string().optional(),
|
|
5163
|
+
sessions: z18.record(z18.object({
|
|
5164
|
+
events: z18.array(z18.enum(events)),
|
|
5165
|
+
at: z18.string(),
|
|
5166
|
+
verificationStatus: z18.string().optional(),
|
|
5167
|
+
reportPath: z18.string().optional()
|
|
5040
5168
|
}))
|
|
5041
5169
|
});
|
|
5042
5170
|
}
|
|
@@ -5066,17 +5194,18 @@ async function setupStatus(dir) {
|
|
|
5066
5194
|
};
|
|
5067
5195
|
}
|
|
5068
5196
|
const hosts = {};
|
|
5069
|
-
const
|
|
5197
|
+
const commonLauncherCurrent = await readText(ws.root, ".mason/run.cjs") === LAUNCHER;
|
|
5070
5198
|
const automation = await automationStatus(ws.root);
|
|
5071
5199
|
for (const host of ["codex", "claude"]) {
|
|
5072
5200
|
const entry = setup.hosts[host];
|
|
5073
5201
|
if (!entry) continue;
|
|
5202
|
+
const launcherCurrent = commonLauncherCurrent && (!entry.runtime.bundle || await readText(ws.root, ".mason/run.sh") === SHELL_LAUNCHER && await readText(ws.root, ".mason/run.ps1") === POWERSHELL_LAUNCHER && await readText(ws.root, `.mason/runtime/${host}.txt`) === entry.runtime.id + "\n");
|
|
5074
5203
|
const instructions = await instructionEdits(ws.root, host);
|
|
5075
5204
|
const instructionsCurrent = instructions.every((edit) => edit.before === edit.after);
|
|
5076
5205
|
const installed = await verifyRuntime(ws.root, entry.runtime);
|
|
5077
|
-
const config = await inspectHostConfig(ws.root, host);
|
|
5078
|
-
const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual2({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host));
|
|
5079
|
-
const hooks = isDeepStrictEqual2(config.hooks, hookConfig(host, hookCommand(host)).hooks) && !config.disabled;
|
|
5206
|
+
const config = await inspectHostConfig(ws.root, host, void 0, entry.runtime);
|
|
5207
|
+
const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual2({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host, entry.runtime));
|
|
5208
|
+
const hooks = isDeepStrictEqual2(config.hooks, hookConfig(host, hookCommand(host, entry.runtime)).hooks) && !config.disabled;
|
|
5080
5209
|
const local = await loadSetupReceipt(ws.root, ws.directory, host);
|
|
5081
5210
|
const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
|
|
5082
5211
|
const observation = await readObservation(ws.root, ws.directory, host, entry.revision);
|
|
@@ -5170,15 +5299,15 @@ async function setupProject(dir, options = {}) {
|
|
|
5170
5299
|
const existing = await loadSetup(ws.root);
|
|
5171
5300
|
const previousReceipt = await loadSetupReceipt(ws.root, ws.directory, host);
|
|
5172
5301
|
const marker = await loadProjectMarker(ws.root);
|
|
5302
|
+
const selected = await sourceRuntime();
|
|
5173
5303
|
const instructions = await instructionEdits(ws.root, host);
|
|
5174
|
-
const mcp = await mcpEdit(ws.root, host);
|
|
5175
|
-
const hooks = await hookEdits(ws.root, host);
|
|
5304
|
+
const mcp = await mcpEdit(ws.root, host, selected.runtime);
|
|
5305
|
+
const hooks = await hookEdits(ws.root, host, selected.runtime);
|
|
5176
5306
|
const ancillary = await ancillaryEdits(ws.root);
|
|
5177
5307
|
const edits = [...ancillary, ...instructions, mcp, ...hooks];
|
|
5178
5308
|
const setupBefore = await readText(ws.root, ".mason/setup.json");
|
|
5179
|
-
const
|
|
5180
|
-
const
|
|
5181
|
-
const desiredHooks = hookConfig(host, hookCommand(host)).hooks;
|
|
5309
|
+
const mcpFingerprint = hash((await inspectHostConfig(ws.root, host, mcp.after, selected.runtime)).mcp);
|
|
5310
|
+
const desiredHooks = hookConfig(host, hookCommand(host, selected.runtime)).hooks;
|
|
5182
5311
|
const fingerprint = hash({
|
|
5183
5312
|
runtime: selected.runtime,
|
|
5184
5313
|
launcher: LAUNCHER,
|
|
@@ -5206,6 +5335,10 @@ async function setupProject(dir, options = {}) {
|
|
|
5206
5335
|
revision
|
|
5207
5336
|
});
|
|
5208
5337
|
await installRuntime(ws.root, selected);
|
|
5338
|
+
if (selected.runtime.bundle) {
|
|
5339
|
+
const pointer = `.mason/runtime/${host}.txt`;
|
|
5340
|
+
await applyEdit(ws.root, { path: pointer, before: await readText(ws.root, pointer), after: selected.runtime.id + "\n" });
|
|
5341
|
+
}
|
|
5209
5342
|
const changedFiles = [];
|
|
5210
5343
|
for (const edit of edits) if (await applyEdit(ws.root, edit)) changedFiles.push(edit.path);
|
|
5211
5344
|
if (await applyEdit(ws.root, { path: ".mason/setup.json", before: setupBefore, after: JSON.stringify(setup, null, 2) + "\n" })) changedFiles.push(".mason/setup.json");
|
|
@@ -5214,7 +5347,7 @@ async function setupProject(dir, options = {}) {
|
|
|
5214
5347
|
changedFiles.push(".mason/project.json");
|
|
5215
5348
|
}
|
|
5216
5349
|
const checked = await automate(ws.root, { event: "turn_start" });
|
|
5217
|
-
const configured = await inspectHostConfig(ws.root, host);
|
|
5350
|
+
const configured = await inspectHostConfig(ws.root, host, void 0, selected.runtime);
|
|
5218
5351
|
await writeStoreJson(ws.root, receiptPath, {
|
|
5219
5352
|
version: 1,
|
|
5220
5353
|
host,
|
|
@@ -5283,8 +5416,8 @@ __export(client_exports, {
|
|
|
5283
5416
|
function createConfluenceClient(config, fetchFn = fetch) {
|
|
5284
5417
|
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
5285
5418
|
const auth = "Basic " + Buffer.from(`${config.email}:${config.apiToken}`).toString("base64");
|
|
5286
|
-
async function call(method,
|
|
5287
|
-
const res = await fetchFn(`${baseUrl}${
|
|
5419
|
+
async function call(method, path30, body) {
|
|
5420
|
+
const res = await fetchFn(`${baseUrl}${path30}`, {
|
|
5288
5421
|
method,
|
|
5289
5422
|
headers: {
|
|
5290
5423
|
Authorization: auth,
|
|
@@ -5296,7 +5429,7 @@ function createConfluenceClient(config, fetchFn = fetch) {
|
|
|
5296
5429
|
if (!res.ok) {
|
|
5297
5430
|
const text2 = await res.text();
|
|
5298
5431
|
throw new Error(
|
|
5299
|
-
`Confluence ${method} ${
|
|
5432
|
+
`Confluence ${method} ${path30} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
|
|
5300
5433
|
);
|
|
5301
5434
|
}
|
|
5302
5435
|
if (res.status === 204) return null;
|
|
@@ -5397,28 +5530,28 @@ __export(config_exports, {
|
|
|
5397
5530
|
saveConfluenceConfig: () => saveConfluenceConfig,
|
|
5398
5531
|
validateProvider: () => validateProvider
|
|
5399
5532
|
});
|
|
5400
|
-
import
|
|
5401
|
-
import
|
|
5533
|
+
import fs21 from "fs/promises";
|
|
5534
|
+
import path27 from "path";
|
|
5402
5535
|
import os3 from "os";
|
|
5403
5536
|
import { execFile as execFile16 } from "child_process";
|
|
5404
5537
|
import { promisify as promisify16 } from "util";
|
|
5405
5538
|
function configDir() {
|
|
5406
|
-
return
|
|
5539
|
+
return path27.join(os3.homedir(), ".mason");
|
|
5407
5540
|
}
|
|
5408
5541
|
function configFile() {
|
|
5409
|
-
return
|
|
5542
|
+
return path27.join(configDir(), "config.json");
|
|
5410
5543
|
}
|
|
5411
5544
|
async function loadConfig() {
|
|
5412
5545
|
try {
|
|
5413
|
-
const raw = await
|
|
5546
|
+
const raw = await fs21.readFile(configFile(), "utf-8");
|
|
5414
5547
|
return JSON.parse(raw);
|
|
5415
5548
|
} catch {
|
|
5416
5549
|
return null;
|
|
5417
5550
|
}
|
|
5418
5551
|
}
|
|
5419
5552
|
async function saveConfig(config) {
|
|
5420
|
-
await
|
|
5421
|
-
await
|
|
5553
|
+
await fs21.mkdir(configDir(), { recursive: true });
|
|
5554
|
+
await fs21.writeFile(configFile(), JSON.stringify(config, null, 2), "utf-8");
|
|
5422
5555
|
}
|
|
5423
5556
|
function getDefaultModel(provider) {
|
|
5424
5557
|
return DEFAULT_MODELS[provider];
|
|
@@ -5578,21 +5711,21 @@ var init_renderer = __esm({
|
|
|
5578
5711
|
});
|
|
5579
5712
|
|
|
5580
5713
|
// src/confluence/diff.ts
|
|
5581
|
-
import
|
|
5582
|
-
import
|
|
5583
|
-
import { createHash as
|
|
5714
|
+
import fs22 from "fs/promises";
|
|
5715
|
+
import path28 from "path";
|
|
5716
|
+
import { createHash as createHash7 } from "crypto";
|
|
5584
5717
|
function hashDescription(description) {
|
|
5585
|
-
return
|
|
5718
|
+
return createHash7("sha256").update(description, "utf8").digest("hex");
|
|
5586
5719
|
}
|
|
5587
5720
|
function syncStateDir(rootDir) {
|
|
5588
|
-
return
|
|
5721
|
+
return path28.join(rootDir, ".mason");
|
|
5589
5722
|
}
|
|
5590
5723
|
function syncStatePath(rootDir) {
|
|
5591
|
-
return
|
|
5724
|
+
return path28.join(syncStateDir(rootDir), "confluence-sync.json");
|
|
5592
5725
|
}
|
|
5593
5726
|
async function loadSyncState(rootDir) {
|
|
5594
5727
|
try {
|
|
5595
|
-
const raw = await
|
|
5728
|
+
const raw = await fs22.readFile(syncStatePath(rootDir), "utf-8");
|
|
5596
5729
|
const parsed = JSON.parse(raw);
|
|
5597
5730
|
if (parsed.version !== 2) return null;
|
|
5598
5731
|
return parsed;
|
|
@@ -5601,8 +5734,8 @@ async function loadSyncState(rootDir) {
|
|
|
5601
5734
|
}
|
|
5602
5735
|
}
|
|
5603
5736
|
async function saveSyncState(rootDir, state) {
|
|
5604
|
-
await
|
|
5605
|
-
await
|
|
5737
|
+
await fs22.mkdir(syncStateDir(rootDir), { recursive: true });
|
|
5738
|
+
await fs22.writeFile(
|
|
5606
5739
|
syncStatePath(rootDir),
|
|
5607
5740
|
JSON.stringify(state, null, 2),
|
|
5608
5741
|
"utf-8"
|
|
@@ -6152,12 +6285,12 @@ var init_sync = __esm({
|
|
|
6152
6285
|
init_provenance();
|
|
6153
6286
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6154
6287
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6155
|
-
import { z as
|
|
6288
|
+
import { z as z19 } from "zod";
|
|
6156
6289
|
|
|
6157
6290
|
// src/mcp/tools.ts
|
|
6158
6291
|
init_repair();
|
|
6159
|
-
import
|
|
6160
|
-
import
|
|
6292
|
+
import fs23 from "fs/promises";
|
|
6293
|
+
import path29 from "path";
|
|
6161
6294
|
import { execFile as execFile18 } from "child_process";
|
|
6162
6295
|
import { promisify as promisify18 } from "util";
|
|
6163
6296
|
|
|
@@ -7062,7 +7195,7 @@ async function buildContext(dir) {
|
|
|
7062
7195
|
};
|
|
7063
7196
|
}
|
|
7064
7197
|
async function analyzeProject(dir) {
|
|
7065
|
-
const rootDir =
|
|
7198
|
+
const rootDir = path29.resolve(dir);
|
|
7066
7199
|
const context = await buildContext(rootDir);
|
|
7067
7200
|
const results = await runAll(context);
|
|
7068
7201
|
const projectSnapshot = await detectProjectSnapshot(rootDir);
|
|
@@ -7117,7 +7250,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
7117
7250
|
const present = [];
|
|
7118
7251
|
for (const file of buildFiles) {
|
|
7119
7252
|
try {
|
|
7120
|
-
await
|
|
7253
|
+
await fs23.access(path29.join(rootDir, file));
|
|
7121
7254
|
present.push(file);
|
|
7122
7255
|
} catch {
|
|
7123
7256
|
}
|
|
@@ -7159,7 +7292,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
7159
7292
|
const sourceFiles = await access.list();
|
|
7160
7293
|
const fileCounts = {};
|
|
7161
7294
|
for (const file of sourceFiles) {
|
|
7162
|
-
const ext =
|
|
7295
|
+
const ext = path29.extname(file).slice(1);
|
|
7163
7296
|
fileCounts[ext] = (fileCounts[ext] ?? 0) + 1;
|
|
7164
7297
|
}
|
|
7165
7298
|
return {
|
|
@@ -7170,7 +7303,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
7170
7303
|
};
|
|
7171
7304
|
}
|
|
7172
7305
|
async function getCodeSamples(dir, count3 = 15) {
|
|
7173
|
-
const rootDir =
|
|
7306
|
+
const rootDir = path29.resolve(dir);
|
|
7174
7307
|
const samples = await sampleFiles(rootDir, count3);
|
|
7175
7308
|
const output = {
|
|
7176
7309
|
note: "These are previews (first ~60 lines). Read the file directly with your own tools to see it in full.",
|
|
@@ -7213,7 +7346,7 @@ async function unmappedContextResponse(rootDir) {
|
|
|
7213
7346
|
});
|
|
7214
7347
|
}
|
|
7215
7348
|
async function getProjectStructure(dir) {
|
|
7216
|
-
const rootDir =
|
|
7349
|
+
const rootDir = path29.resolve(dir);
|
|
7217
7350
|
const allFiles = await (await createFileAccess(rootDir)).list("**/*");
|
|
7218
7351
|
const dirInfo = /* @__PURE__ */ new Map();
|
|
7219
7352
|
for (const file of allFiles) {
|
|
@@ -7225,7 +7358,7 @@ async function getProjectStructure(dir) {
|
|
|
7225
7358
|
}
|
|
7226
7359
|
const info = dirInfo.get(dirPath);
|
|
7227
7360
|
info.fileCount++;
|
|
7228
|
-
const ext =
|
|
7361
|
+
const ext = path29.extname(file).slice(1);
|
|
7229
7362
|
if (ext) {
|
|
7230
7363
|
info.extensions.set(ext, (info.extensions.get(ext) ?? 0) + 1);
|
|
7231
7364
|
}
|
|
@@ -7270,7 +7403,7 @@ async function buildChangedFilePreviews(rootDir, changedFiles) {
|
|
|
7270
7403
|
return previews2;
|
|
7271
7404
|
}
|
|
7272
7405
|
async function getSnapshot(dir) {
|
|
7273
|
-
const rootDir =
|
|
7406
|
+
const rootDir = path29.resolve(dir);
|
|
7274
7407
|
const snapshot = await loadSnapshot(rootDir);
|
|
7275
7408
|
if (!snapshot) {
|
|
7276
7409
|
return unmappedContextResponse(rootDir);
|
|
@@ -7371,7 +7504,7 @@ function driftHint(report) {
|
|
|
7371
7504
|
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.";
|
|
7372
7505
|
}
|
|
7373
7506
|
async function checkDrift(dir) {
|
|
7374
|
-
const rootDir =
|
|
7507
|
+
const rootDir = path29.resolve(dir);
|
|
7375
7508
|
const report = await computeDrift(rootDir);
|
|
7376
7509
|
if (!report) {
|
|
7377
7510
|
return JSON.stringify({
|
|
@@ -7402,7 +7535,7 @@ async function checkDrift(dir) {
|
|
|
7402
7535
|
return JSON.stringify({ exists: true, ...report, verification, hint });
|
|
7403
7536
|
}
|
|
7404
7537
|
async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_SIZE, files) {
|
|
7405
|
-
const rootDir =
|
|
7538
|
+
const rootDir = path29.resolve(dir);
|
|
7406
7539
|
const scoped = files !== void 0 && files.length > 0;
|
|
7407
7540
|
const scopeFiles = scoped ? sanitizePaths(rootDir, files) : void 0;
|
|
7408
7541
|
const batch = await prepareSnapshotBatch(dir, offset, batchSize, scopeFiles);
|
|
@@ -7449,7 +7582,7 @@ async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_
|
|
|
7449
7582
|
);
|
|
7450
7583
|
}
|
|
7451
7584
|
async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
|
|
7452
|
-
const rootDir =
|
|
7585
|
+
const rootDir = path29.resolve(dir);
|
|
7453
7586
|
for (const feat of Object.values(features)) {
|
|
7454
7587
|
feat.files = sanitizePaths(rootDir, feat.files);
|
|
7455
7588
|
if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
|
|
@@ -7478,7 +7611,7 @@ async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
|
|
|
7478
7611
|
);
|
|
7479
7612
|
}
|
|
7480
7613
|
async function reduceSnapshot(dir) {
|
|
7481
|
-
const rootDir =
|
|
7614
|
+
const rootDir = path29.resolve(dir);
|
|
7482
7615
|
const partials = await loadAllPartials(rootDir);
|
|
7483
7616
|
if (partials.length === 0) {
|
|
7484
7617
|
return JSON.stringify(
|
|
@@ -7540,7 +7673,7 @@ async function reduceSnapshot(dir) {
|
|
|
7540
7673
|
);
|
|
7541
7674
|
}
|
|
7542
7675
|
async function fullAnalysis(dir) {
|
|
7543
|
-
const rootDir =
|
|
7676
|
+
const rootDir = path29.resolve(dir);
|
|
7544
7677
|
const [analysis, structure, samples, testMap, snapshot] = await Promise.all([
|
|
7545
7678
|
analyzeProject(dir),
|
|
7546
7679
|
getProjectStructure(dir),
|
|
@@ -7569,7 +7702,7 @@ function sanitizePaths(rootDir, files) {
|
|
|
7569
7702
|
return sanitizeRepoPaths(files);
|
|
7570
7703
|
}
|
|
7571
7704
|
async function saveSnapshotData(dir, features, flows, removeFeatures = [], removeFlows = []) {
|
|
7572
|
-
const rootDir =
|
|
7705
|
+
const rootDir = path29.resolve(dir);
|
|
7573
7706
|
const gitHash = await getCurrentGitHash(rootDir);
|
|
7574
7707
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7575
7708
|
for (const feat of Object.values(features)) {
|
|
@@ -7653,7 +7786,7 @@ async function saveSnapshotData(dir, features, flows, removeFeatures = [], remov
|
|
|
7653
7786
|
});
|
|
7654
7787
|
}
|
|
7655
7788
|
async function getImpact(dir, files) {
|
|
7656
|
-
const rootDir =
|
|
7789
|
+
const rootDir = path29.resolve(dir);
|
|
7657
7790
|
const { analyzeImpact: analyzeImpact2 } = await Promise.resolve().then(() => (init_impact(), impact_exports));
|
|
7658
7791
|
const result = await analyzeImpact2(rootDir, files);
|
|
7659
7792
|
return JSON.stringify(result, null, 2);
|
|
@@ -7662,7 +7795,7 @@ var VERIFY_DEFAULT_SAMPLE = 5;
|
|
|
7662
7795
|
var VERIFY_MAX_FILES_PER_ENTRY = 8;
|
|
7663
7796
|
var VERIFY_SKELETON_CHARS = 500;
|
|
7664
7797
|
async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
|
|
7665
|
-
const rootDir =
|
|
7798
|
+
const rootDir = path29.resolve(dir);
|
|
7666
7799
|
const snapshot = await loadSnapshot(rootDir);
|
|
7667
7800
|
if (!snapshot) {
|
|
7668
7801
|
return JSON.stringify({
|
|
@@ -7727,7 +7860,7 @@ async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
|
|
|
7727
7860
|
});
|
|
7728
7861
|
}
|
|
7729
7862
|
async function saveVerification(dir, verdicts) {
|
|
7730
|
-
const rootDir =
|
|
7863
|
+
const rootDir = path29.resolve(dir);
|
|
7731
7864
|
const snapshot = await loadSnapshot(rootDir);
|
|
7732
7865
|
if (!snapshot) {
|
|
7733
7866
|
return JSON.stringify({ exists: false, hint: "No concept map exists." });
|
|
@@ -7765,16 +7898,16 @@ async function saveVerification(dir, verdicts) {
|
|
|
7765
7898
|
});
|
|
7766
7899
|
}
|
|
7767
7900
|
async function saveDecision(dir, input) {
|
|
7768
|
-
const rootDir =
|
|
7901
|
+
const rootDir = path29.resolve(dir);
|
|
7769
7902
|
const { upsertDecision: upsertDecision2 } = await Promise.resolve().then(() => (init_decisions(), decisions_exports));
|
|
7770
7903
|
const result = await upsertDecision2(rootDir, input);
|
|
7771
7904
|
return JSON.stringify(result);
|
|
7772
7905
|
}
|
|
7773
7906
|
async function reviewDecision2(dir, input) {
|
|
7774
|
-
return JSON.stringify(await reviewDecision(
|
|
7907
|
+
return JSON.stringify(await reviewDecision(path29.resolve(dir), input));
|
|
7775
7908
|
}
|
|
7776
7909
|
async function getContext(dir, task, files) {
|
|
7777
|
-
const rootDir =
|
|
7910
|
+
const rootDir = path29.resolve(dir);
|
|
7778
7911
|
const { assembleContext: assembleContext2 } = await Promise.resolve().then(() => (init_assemble(), assemble_exports));
|
|
7779
7912
|
const bundle = await assembleContext2(rootDir, task, files);
|
|
7780
7913
|
return JSON.stringify(bundle);
|
|
@@ -7816,7 +7949,7 @@ async function masonInit(dir, options = {}) {
|
|
|
7816
7949
|
return JSON.stringify(await setupProject2(dir, options), null, 2);
|
|
7817
7950
|
}
|
|
7818
7951
|
if (options.host) throw new Error("host applies only to mode: setup.");
|
|
7819
|
-
const rootDir =
|
|
7952
|
+
const rootDir = path29.resolve(dir);
|
|
7820
7953
|
const marker = await loadProjectMarker(rootDir);
|
|
7821
7954
|
const mode = options.mode ?? "quickstart";
|
|
7822
7955
|
const findings = await inspectOnboarding(rootDir, options.base, options.evidence);
|
|
@@ -7834,7 +7967,7 @@ async function masonInit(dir, options = {}) {
|
|
|
7834
7967
|
);
|
|
7835
7968
|
}
|
|
7836
7969
|
async function masonCompleteInit(dir, options = {}) {
|
|
7837
|
-
const rootDir =
|
|
7970
|
+
const rootDir = path29.resolve(dir);
|
|
7838
7971
|
const existing = await loadProjectMarker(rootDir);
|
|
7839
7972
|
const marker = {
|
|
7840
7973
|
version: 1,
|
|
@@ -7942,7 +8075,7 @@ async function masonSetConfluence(input) {
|
|
|
7942
8075
|
);
|
|
7943
8076
|
}
|
|
7944
8077
|
async function exportToConfluenceTool(dir, overrides) {
|
|
7945
|
-
const rootDir =
|
|
8078
|
+
const rootDir = path29.resolve(dir);
|
|
7946
8079
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
7947
8080
|
const { exportToConfluence: exportToConfluence2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
7948
8081
|
const config = await loadConfig2();
|
|
@@ -7980,7 +8113,7 @@ function createMcpServer() {
|
|
|
7980
8113
|
const server = new McpServer(
|
|
7981
8114
|
{
|
|
7982
8115
|
name: "mason",
|
|
7983
|
-
version: "0.
|
|
8116
|
+
version: "0.14.0"
|
|
7984
8117
|
},
|
|
7985
8118
|
{
|
|
7986
8119
|
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.'
|
|
@@ -7990,11 +8123,11 @@ function createMcpServer() {
|
|
|
7990
8123
|
"mason_init",
|
|
7991
8124
|
"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 installs a pinned project runtime, MCP configuration, instructions and lifecycle hooks 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.",
|
|
7992
8125
|
{
|
|
7993
|
-
dir:
|
|
7994
|
-
mode:
|
|
7995
|
-
host:
|
|
7996
|
-
base:
|
|
7997
|
-
evidence:
|
|
8126
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8127
|
+
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."),
|
|
8128
|
+
host: z19.enum(["codex", "claude"]).optional().describe("Assistant to configure in setup mode; inferred only when unambiguous."),
|
|
8129
|
+
base: z19.string().optional().describe("Git ref for committed-diff review. Defaults to the first available main branch ref."),
|
|
8130
|
+
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.")
|
|
7998
8131
|
},
|
|
7999
8132
|
async ({ dir, mode, host, base, evidence }) => {
|
|
8000
8133
|
const result = await masonInit(dir, { mode, host, base, evidence });
|
|
@@ -8005,8 +8138,8 @@ function createMcpServer() {
|
|
|
8005
8138
|
"mason_automation",
|
|
8006
8139
|
"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.",
|
|
8007
8140
|
{
|
|
8008
|
-
dir:
|
|
8009
|
-
action:
|
|
8141
|
+
dir: z19.string().describe("Absolute path to the project directory"),
|
|
8142
|
+
action: z19.enum(["status", "check"]).describe("Inspect configuration and receipts, or capture/resume and verify original audit evidence.")
|
|
8010
8143
|
},
|
|
8011
8144
|
async ({ dir, action }) => ({ content: [{ type: "text", text: await masonAutomation(dir, action) }] })
|
|
8012
8145
|
);
|
|
@@ -8014,10 +8147,10 @@ function createMcpServer() {
|
|
|
8014
8147
|
"mason_repair",
|
|
8015
8148
|
"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.",
|
|
8016
8149
|
{
|
|
8017
|
-
dir:
|
|
8018
|
-
action:
|
|
8019
|
-
baselinePath:
|
|
8020
|
-
checks:
|
|
8150
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8151
|
+
action: z19.enum(["prepare", "verify"]),
|
|
8152
|
+
baselinePath: z19.string().optional().describe("Original baseline path returned by prepare; required for verify."),
|
|
8153
|
+
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.")
|
|
8021
8154
|
},
|
|
8022
8155
|
async ({ dir, action, baselinePath, checks }) => {
|
|
8023
8156
|
const result = await masonRepair(dir, { action, baselinePath, checks });
|
|
@@ -8028,8 +8161,8 @@ function createMcpServer() {
|
|
|
8028
8161
|
"mason_complete_init",
|
|
8029
8162
|
"Record completion of assistant instruction setup in .mason/project.json. Other tools work without this marker. Repeated calls preserve the original setup time and existing settings; pass confluenceConfigured only to change that setting.",
|
|
8030
8163
|
{
|
|
8031
|
-
dir:
|
|
8032
|
-
confluenceConfigured:
|
|
8164
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8165
|
+
confluenceConfigured: z19.boolean().optional().describe("Set the Confluence setup status; omit to preserve the existing value")
|
|
8033
8166
|
},
|
|
8034
8167
|
async ({ dir, confluenceConfigured }) => {
|
|
8035
8168
|
const result = await masonCompleteInit(dir, { confluenceConfigured });
|
|
@@ -8040,11 +8173,11 @@ function createMcpServer() {
|
|
|
8040
8173
|
"mason_set_confluence",
|
|
8041
8174
|
"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.",
|
|
8042
8175
|
{
|
|
8043
|
-
baseUrl:
|
|
8044
|
-
email:
|
|
8045
|
-
apiToken:
|
|
8046
|
-
spaceKey:
|
|
8047
|
-
parentPageId:
|
|
8176
|
+
baseUrl: z19.string().describe("Confluence base URL. Accepts `acme`, `acme.atlassian.net`, or `https://acme.atlassian.net` (normalized automatically)."),
|
|
8177
|
+
email: z19.string().describe("User's Atlassian account email"),
|
|
8178
|
+
apiToken: z19.string().describe("API token from id.atlassian.com/manage-profile/security/api-tokens"),
|
|
8179
|
+
spaceKey: z19.string().optional().describe("Confluence space key. Omit on the first call to list available spaces."),
|
|
8180
|
+
parentPageId: z19.string().optional().describe("Optional parent page ID under which Mason's index page is created")
|
|
8048
8181
|
},
|
|
8049
8182
|
async ({ baseUrl, email, apiToken, spaceKey, parentPageId }) => {
|
|
8050
8183
|
const result = await masonSetConfluence({
|
|
@@ -8061,7 +8194,7 @@ function createMcpServer() {
|
|
|
8061
8194
|
"full_analysis",
|
|
8062
8195
|
"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.",
|
|
8063
8196
|
{
|
|
8064
|
-
dir:
|
|
8197
|
+
dir: z19.string().describe("Absolute path to the project root directory")
|
|
8065
8198
|
},
|
|
8066
8199
|
async ({ dir }) => {
|
|
8067
8200
|
const result = await fullAnalysis(dir);
|
|
@@ -8074,7 +8207,7 @@ function createMcpServer() {
|
|
|
8074
8207
|
"analyze_project",
|
|
8075
8208
|
"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.",
|
|
8076
8209
|
{
|
|
8077
|
-
dir:
|
|
8210
|
+
dir: z19.string().describe("Absolute path to the project root directory")
|
|
8078
8211
|
},
|
|
8079
8212
|
async ({ dir }) => {
|
|
8080
8213
|
const result = await analyzeProject(dir);
|
|
@@ -8087,8 +8220,8 @@ function createMcpServer() {
|
|
|
8087
8220
|
"get_code_samples",
|
|
8088
8221
|
"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.",
|
|
8089
8222
|
{
|
|
8090
|
-
dir:
|
|
8091
|
-
count:
|
|
8223
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8224
|
+
count: z19.number().optional().default(15).describe("Maximum number of files to sample (default: 15)")
|
|
8092
8225
|
},
|
|
8093
8226
|
async ({ dir, count: count3 }) => {
|
|
8094
8227
|
const result = await getCodeSamples(dir, count3);
|
|
@@ -8101,7 +8234,7 @@ function createMcpServer() {
|
|
|
8101
8234
|
"get_snapshot",
|
|
8102
8235
|
"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.",
|
|
8103
8236
|
{
|
|
8104
|
-
dir:
|
|
8237
|
+
dir: z19.string().describe("Absolute path to the project root directory")
|
|
8105
8238
|
},
|
|
8106
8239
|
async ({ dir }) => {
|
|
8107
8240
|
const result = await getSnapshot(dir);
|
|
@@ -8114,9 +8247,9 @@ function createMcpServer() {
|
|
|
8114
8247
|
"get_context",
|
|
8115
8248
|
"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.",
|
|
8116
8249
|
{
|
|
8117
|
-
dir:
|
|
8118
|
-
task:
|
|
8119
|
-
files:
|
|
8250
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8251
|
+
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"),
|
|
8252
|
+
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.")
|
|
8120
8253
|
},
|
|
8121
8254
|
async ({ dir, task, files }) => {
|
|
8122
8255
|
const result = await getContext(dir, task, files);
|
|
@@ -8132,10 +8265,10 @@ function createMcpServer() {
|
|
|
8132
8265
|
"generate_snapshot_batch",
|
|
8133
8266
|
"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.",
|
|
8134
8267
|
{
|
|
8135
|
-
dir:
|
|
8136
|
-
offset:
|
|
8137
|
-
batchSize:
|
|
8138
|
-
files:
|
|
8268
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8269
|
+
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."),
|
|
8270
|
+
batchSize: z19.number().int().optional().describe("Files per batch. Defaults to 50."),
|
|
8271
|
+
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.")
|
|
8139
8272
|
},
|
|
8140
8273
|
async ({ dir, offset, batchSize, files }) => {
|
|
8141
8274
|
const result = await generateSnapshotBatch(dir, offset, batchSize, files);
|
|
@@ -8148,23 +8281,23 @@ function createMcpServer() {
|
|
|
8148
8281
|
"save_partial_snapshot",
|
|
8149
8282
|
"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.",
|
|
8150
8283
|
{
|
|
8151
|
-
dir:
|
|
8152
|
-
batchId:
|
|
8153
|
-
offset:
|
|
8154
|
-
features:
|
|
8155
|
-
|
|
8156
|
-
description:
|
|
8157
|
-
files:
|
|
8158
|
-
tests:
|
|
8159
|
-
type:
|
|
8284
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8285
|
+
batchId: z19.string().describe("The `batchId` returned by `generate_snapshot_batch`."),
|
|
8286
|
+
offset: z19.number().int().describe("The `offset` returned by `generate_snapshot_batch`. Used to order partials in the reduce step."),
|
|
8287
|
+
features: z19.record(
|
|
8288
|
+
z19.object({
|
|
8289
|
+
description: z19.string(),
|
|
8290
|
+
files: z19.array(z19.string()),
|
|
8291
|
+
tests: z19.array(z19.string()).optional(),
|
|
8292
|
+
type: z19.enum(["capability", "infrastructure"]).optional().describe(
|
|
8160
8293
|
'"capability" (user-facing functionality) or "infrastructure" (internal plumbing with no end user \u2014 DI/service wiring, config, logging, adapters). Defaults to "capability".'
|
|
8161
8294
|
)
|
|
8162
8295
|
})
|
|
8163
8296
|
).describe("Partial features for this batch only \u2014 files outside the batch will be added by other partials."),
|
|
8164
|
-
flows:
|
|
8165
|
-
|
|
8166
|
-
description:
|
|
8167
|
-
chain:
|
|
8297
|
+
flows: z19.record(
|
|
8298
|
+
z19.object({
|
|
8299
|
+
description: z19.string(),
|
|
8300
|
+
chain: z19.array(z19.string())
|
|
8168
8301
|
})
|
|
8169
8302
|
).describe("Partial flows whose entire chain is in this batch. Cross-batch flows are reconstructed in reduce.")
|
|
8170
8303
|
},
|
|
@@ -8179,7 +8312,7 @@ function createMcpServer() {
|
|
|
8179
8312
|
"reduce_snapshot",
|
|
8180
8313
|
"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).",
|
|
8181
8314
|
{
|
|
8182
|
-
dir:
|
|
8315
|
+
dir: z19.string().describe("Absolute path to the project root directory")
|
|
8183
8316
|
},
|
|
8184
8317
|
async ({ dir }) => {
|
|
8185
8318
|
const result = await reduceSnapshot(dir);
|
|
@@ -8192,25 +8325,25 @@ function createMcpServer() {
|
|
|
8192
8325
|
"save_snapshot",
|
|
8193
8326
|
"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.",
|
|
8194
8327
|
{
|
|
8195
|
-
dir:
|
|
8196
|
-
features:
|
|
8197
|
-
|
|
8198
|
-
description:
|
|
8199
|
-
files:
|
|
8200
|
-
tests:
|
|
8201
|
-
type:
|
|
8328
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8329
|
+
features: z19.record(
|
|
8330
|
+
z19.object({
|
|
8331
|
+
description: z19.string().describe("One-line description of the feature"),
|
|
8332
|
+
files: z19.array(z19.string()).describe("File paths that implement this feature"),
|
|
8333
|
+
tests: z19.array(z19.string()).optional().describe("Test file paths for this feature"),
|
|
8334
|
+
type: z19.enum(["capability", "infrastructure"]).optional().describe(
|
|
8202
8335
|
'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".'
|
|
8203
8336
|
)
|
|
8204
8337
|
})
|
|
8205
8338
|
).describe("Map of feature names to their implementing files"),
|
|
8206
|
-
flows:
|
|
8207
|
-
|
|
8208
|
-
description:
|
|
8209
|
-
chain:
|
|
8339
|
+
flows: z19.record(
|
|
8340
|
+
z19.object({
|
|
8341
|
+
description: z19.string().describe("One-line description of the flow"),
|
|
8342
|
+
chain: z19.array(z19.string()).describe("Ordered list of file paths showing data/call flow")
|
|
8210
8343
|
})
|
|
8211
8344
|
).describe("Map of flow names to ordered file chains"),
|
|
8212
|
-
removeFeatures:
|
|
8213
|
-
removeFlows:
|
|
8345
|
+
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."),
|
|
8346
|
+
removeFlows: z19.array(z19.string()).optional().describe("Flow names to delete from the existing map. Applied before merging; only meaningful on incremental saves.")
|
|
8214
8347
|
},
|
|
8215
8348
|
async ({ dir, features, flows, removeFeatures, removeFlows }) => {
|
|
8216
8349
|
const result = await saveSnapshotData(
|
|
@@ -8229,17 +8362,17 @@ function createMcpServer() {
|
|
|
8229
8362
|
"save_decision",
|
|
8230
8363
|
"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.",
|
|
8231
8364
|
{
|
|
8232
|
-
dir:
|
|
8233
|
-
title:
|
|
8234
|
-
body:
|
|
8235
|
-
category:
|
|
8236
|
-
files:
|
|
8237
|
-
id:
|
|
8238
|
-
supersedes:
|
|
8365
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8366
|
+
title: z19.string().max(80).describe("Short, specific headline \u2014 becomes the stable record id"),
|
|
8367
|
+
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."),
|
|
8368
|
+
category: z19.enum(["decision", "gotcha", "deprecation", "convention"]),
|
|
8369
|
+
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."),
|
|
8370
|
+
id: z19.string().optional().describe("Existing id to revise. Changed content becomes a proposal; unchanged content leaves the review and freshness untouched."),
|
|
8371
|
+
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."),
|
|
8239
8372
|
owner: attributionSchema.shape.owner.describe("Responsible person or team, when known. Null clears it. Required for acceptance."),
|
|
8240
8373
|
sources: attributionSchema.shape.sources.describe("Known PR, issue, incident, discussion, or document references. Omit to preserve; [] clears. At least one is required for acceptance."),
|
|
8241
8374
|
actor: attributionSchema.shape.actor.describe("Known person or agent recording this revision. Omit if unknown; do not infer from Git identity."),
|
|
8242
|
-
force:
|
|
8375
|
+
force: z19.boolean().optional().describe("Save even when a near-duplicate was detected")
|
|
8243
8376
|
},
|
|
8244
8377
|
async ({ dir, title, body, category, files, id, supersedes, force, owner, sources, actor }) => {
|
|
8245
8378
|
const result = await saveDecision(dir, {
|
|
@@ -8261,12 +8394,12 @@ function createMcpServer() {
|
|
|
8261
8394
|
"review_decision",
|
|
8262
8395
|
"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.",
|
|
8263
8396
|
{
|
|
8264
|
-
dir:
|
|
8265
|
-
id:
|
|
8266
|
-
action:
|
|
8267
|
-
reviewer:
|
|
8268
|
-
note:
|
|
8269
|
-
reviewToken:
|
|
8397
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8398
|
+
id: z19.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Decision id from get_context or save_decision"),
|
|
8399
|
+
action: z19.enum(["prepare", "accept", "reaffirm", "retire"]).optional().default("prepare").describe("Prepare is read-only. Other actions record an explicitly authorized review."),
|
|
8400
|
+
reviewer: z19.string().trim().min(1).max(200).optional().describe("Identity of the actual reviewer; required for a verdict. Never invent one."),
|
|
8401
|
+
note: z19.string().trim().min(1).max(1500).optional().describe("Review rationale; required for a verdict. Cite evidence for the decision."),
|
|
8402
|
+
reviewToken: z19.string().regex(/^[a-f0-9]{64}$/).optional().describe("Token from the prepared review; rejects stale record or code revisions")
|
|
8270
8403
|
},
|
|
8271
8404
|
async ({ dir, ...input }) => {
|
|
8272
8405
|
const result = await reviewDecision2(dir, input);
|
|
@@ -8277,7 +8410,7 @@ function createMcpServer() {
|
|
|
8277
8410
|
"mason_check_drift",
|
|
8278
8411
|
"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.",
|
|
8279
8412
|
{
|
|
8280
|
-
dir:
|
|
8413
|
+
dir: z19.string().describe("Absolute path to the project root directory")
|
|
8281
8414
|
},
|
|
8282
8415
|
async ({ dir }) => {
|
|
8283
8416
|
const result = await checkDrift(dir);
|
|
@@ -8290,8 +8423,8 @@ function createMcpServer() {
|
|
|
8290
8423
|
"verify_snapshot",
|
|
8291
8424
|
"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.",
|
|
8292
8425
|
{
|
|
8293
|
-
dir:
|
|
8294
|
-
sample:
|
|
8426
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8427
|
+
sample: z19.number().int().optional().describe("Entries to sample (default 5)")
|
|
8295
8428
|
},
|
|
8296
8429
|
async ({ dir, sample }) => {
|
|
8297
8430
|
const result = await verifySnapshot(dir, sample);
|
|
@@ -8302,11 +8435,11 @@ function createMcpServer() {
|
|
|
8302
8435
|
"save_verification",
|
|
8303
8436
|
"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.",
|
|
8304
8437
|
{
|
|
8305
|
-
dir:
|
|
8306
|
-
verdicts:
|
|
8307
|
-
|
|
8308
|
-
ok:
|
|
8309
|
-
note:
|
|
8438
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8439
|
+
verdicts: z19.record(
|
|
8440
|
+
z19.object({
|
|
8441
|
+
ok: z19.boolean(),
|
|
8442
|
+
note: z19.string().optional().describe("One line on what's wrong \u2014 required when ok is false")
|
|
8310
8443
|
})
|
|
8311
8444
|
).describe("Entry name \u2192 verdict, exactly as returned by verify_snapshot")
|
|
8312
8445
|
},
|
|
@@ -8319,8 +8452,8 @@ function createMcpServer() {
|
|
|
8319
8452
|
"get_impact",
|
|
8320
8453
|
"Trace the impact of changing files: historical co-change partners, references, and related tests. Deterministic, read-only, and usable without initialization, saved decisions, or a concept map.",
|
|
8321
8454
|
{
|
|
8322
|
-
dir:
|
|
8323
|
-
files:
|
|
8455
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8456
|
+
files: z19.array(z19.string()).describe("File paths or names to analyze (e.g., ['WeatherRepository.kt'] or ['src/services/auth.ts'])")
|
|
8324
8457
|
},
|
|
8325
8458
|
async ({ dir, files }) => {
|
|
8326
8459
|
const result = await getImpact(dir, files);
|
|
@@ -8333,12 +8466,12 @@ function createMcpServer() {
|
|
|
8333
8466
|
"export_to_confluence",
|
|
8334
8467
|
"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.",
|
|
8335
8468
|
{
|
|
8336
|
-
dir:
|
|
8337
|
-
spaceKey:
|
|
8338
|
-
parentPageId:
|
|
8339
|
-
indexPageTitle:
|
|
8340
|
-
changelogPageTitle:
|
|
8341
|
-
featurePagePrefix:
|
|
8469
|
+
dir: z19.string().describe("Absolute path to the project root directory"),
|
|
8470
|
+
spaceKey: z19.string().optional().describe("Override the configured space key"),
|
|
8471
|
+
parentPageId: z19.string().optional().describe("Override the configured parent page ID"),
|
|
8472
|
+
indexPageTitle: z19.string().optional().describe("Title of the index page (default: 'Mason \u2014 System Map')"),
|
|
8473
|
+
changelogPageTitle: z19.string().optional().describe("Title of the changelog page (default: 'Mason \u2014 Changelog')"),
|
|
8474
|
+
featurePagePrefix: z19.string().optional().describe("Prefix for each feature page title (default: 'Feature: ')")
|
|
8342
8475
|
},
|
|
8343
8476
|
async ({ dir, spaceKey, parentPageId, indexPageTitle, changelogPageTitle, featurePagePrefix }) => {
|
|
8344
8477
|
const result = await exportToConfluenceTool(dir, {
|