mason-context 0.18.0 → 0.19.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 +9 -0
- package/README.md +2 -0
- package/dist/mason-audit.js +20 -1
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-auto.js +111 -36
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-drift.js +1 -0
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +1 -0
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +765 -125
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +1 -0
- package/dist/mason-review.js.map +1 -1
- package/dist/mason-runtime.js +337 -0
- package/dist/mason-runtime.js.map +1 -0
- package/dist/mason.js +910 -229
- package/dist/mason.js.map +1 -1
- package/package.json +2 -1
package/dist/mason-mcp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
if (!import.meta.url.endsWith("/mason-runtime.js")) await import("./mason-runtime.js").then(m => m.registerRuntime());
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
5
|
var __esm = (fn, res) => function __init() {
|
|
@@ -61,19 +62,19 @@ var init_git_read = __esm({
|
|
|
61
62
|
import path from "path";
|
|
62
63
|
function splitCommands(line) {
|
|
63
64
|
const parts = [];
|
|
64
|
-
let
|
|
65
|
+
let quote2 = "", start = 0;
|
|
65
66
|
for (let i = 0; i < line.length; i++) {
|
|
66
67
|
const char = line[i];
|
|
67
68
|
if (char === "\\") {
|
|
68
69
|
i++;
|
|
69
70
|
continue;
|
|
70
71
|
}
|
|
71
|
-
if (
|
|
72
|
-
if (char ===
|
|
72
|
+
if (quote2) {
|
|
73
|
+
if (char === quote2) quote2 = "";
|
|
73
74
|
continue;
|
|
74
75
|
}
|
|
75
76
|
if (char === "'" || char === '"') {
|
|
76
|
-
|
|
77
|
+
quote2 = char;
|
|
77
78
|
continue;
|
|
78
79
|
}
|
|
79
80
|
if (char === ";" || line.slice(i, i + 2) === "&&") {
|
|
@@ -2134,6 +2135,10 @@ var init_release_metadata = __esm({
|
|
|
2134
2135
|
});
|
|
2135
2136
|
|
|
2136
2137
|
// src/audit/checks/deps-changed.ts
|
|
2138
|
+
function hasDependencyContent(content2) {
|
|
2139
|
+
const text2 = content2.replace(/<!-- mason:start -->[\s\S]*?<!-- mason:end -->/g, "").replace(/<!--[\s\S]*?-->/g, "");
|
|
2140
|
+
return /\b(?:dependenc(?:y|ies)|librar(?:y|ies)|frameworks?|tech(?:nology)? stack|prerequisites|requirements)\b/i.test(text2) || /\b(?:package\.json|build\.gradle(?:\.kts)?|settings\.gradle(?:\.kts)?|libs\.versions\.toml|Cargo\.toml|go\.mod|pyproject\.toml|requirements\.txt|Gemfile|composer\.json)\b/i.test(text2) || /\b(?:node(?:\.js)?|npm|pnpm|yarn|bun|deno|python|ruby|rust|go|java|jdk|kotlin|gradle|swift|php|react|vue|angular|compose|ktor|room)\s*(?:version\s*)?[`*:=>~^v\s-]*\d+(?:\.\d+)*\b/i.test(text2) || /\b(?:npm|pnpm|yarn|pip3?|cargo|gem|composer)\s+(?:install|add|require)\b/i.test(text2);
|
|
2141
|
+
}
|
|
2137
2142
|
function manifestPathspecs(doc) {
|
|
2138
2143
|
const scope = documentScope(doc);
|
|
2139
2144
|
if (scope === ".") return MANIFEST_PATHSPECS;
|
|
@@ -2160,6 +2165,19 @@ async function checkDepsChanged(ctx) {
|
|
|
2160
2165
|
reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
|
|
2161
2166
|
});
|
|
2162
2167
|
}
|
|
2168
|
+
let content2 = doc.content;
|
|
2169
|
+
if (doc.dirty) {
|
|
2170
|
+
try {
|
|
2171
|
+
content2 = (await inspectionGit(
|
|
2172
|
+
["show", `${doc.lastCommit.hash}:${doc.path}`],
|
|
2173
|
+
{ cwd: ctx.root, maxBuffer: 10 * 1024 * 1024, timeout: 1e4 }
|
|
2174
|
+
)).stdout;
|
|
2175
|
+
} catch {
|
|
2176
|
+
result.skipped.push({ check: "deps-changed", doc: doc.path, reason: `${doc.path}: committed dependency content is unavailable` });
|
|
2177
|
+
continue;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
if (!hasDependencyContent(content2)) continue;
|
|
2163
2181
|
const range = await commitsTouchingSince(
|
|
2164
2182
|
ctx.root,
|
|
2165
2183
|
doc.lastCommit.hash,
|
|
@@ -2205,6 +2223,7 @@ var init_deps_changed = __esm({
|
|
|
2205
2223
|
init_docs();
|
|
2206
2224
|
init_release_metadata();
|
|
2207
2225
|
init_git();
|
|
2226
|
+
init_inspection();
|
|
2208
2227
|
init_checks();
|
|
2209
2228
|
MANIFEST_COMMITS_CAP = 10;
|
|
2210
2229
|
MANIFEST_PATHSPECS = [
|
|
@@ -3567,7 +3586,7 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
|
3567
3586
|
return {
|
|
3568
3587
|
...base,
|
|
3569
3588
|
status: "review-required",
|
|
3570
|
-
reason: "An audit cannot establish that this advisory was reviewed. Retain its original evidence and report a separate assessment; editing or committing the doc is not approval."
|
|
3589
|
+
reason: (now ? "The current check still reports this advisory. " : finding.type === "deps-changed" ? "The current check no longer reports this condition; the historical advisory remains awaiting assessment. " : "The current check no longer reports the original finding; retained evidence still requires assessment. ") + "An audit cannot establish that this advisory was reviewed. Retain its original evidence and report a separate assessment; editing or committing the doc is not approval."
|
|
3571
3590
|
};
|
|
3572
3591
|
}
|
|
3573
3592
|
return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
|
|
@@ -3758,7 +3777,7 @@ async function collectInputs(root) {
|
|
|
3758
3777
|
git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
|
|
3759
3778
|
git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
|
|
3760
3779
|
]) : ["", ""];
|
|
3761
|
-
const common = [
|
|
3780
|
+
const common = [6, engineVersion, head, shallow, replacements, docContents];
|
|
3762
3781
|
const keys = {
|
|
3763
3782
|
"deleted-reference": hash([common, claims, docStatus]),
|
|
3764
3783
|
"new-module": hash([common, modules]),
|
|
@@ -3818,7 +3837,7 @@ var init_evidence = __esm({
|
|
|
3818
3837
|
init_dead_command();
|
|
3819
3838
|
init_storage();
|
|
3820
3839
|
init_advisory_review();
|
|
3821
|
-
engineVersion = true ? "0.
|
|
3840
|
+
engineVersion = true ? "0.19.0" : "development";
|
|
3822
3841
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3823
3842
|
content = readAuditInput;
|
|
3824
3843
|
cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
|
|
@@ -3828,18 +3847,49 @@ var init_evidence = __esm({
|
|
|
3828
3847
|
// src/setup/model.ts
|
|
3829
3848
|
var model_exports = {};
|
|
3830
3849
|
__export(model_exports, {
|
|
3850
|
+
INACTIVE_HOSTS_PATH: () => INACTIVE_HOSTS_PATH,
|
|
3831
3851
|
SETUP_PATH: () => SETUP_PATH,
|
|
3852
|
+
effectiveSetup: () => effectiveSetup,
|
|
3853
|
+
inactiveHostsSchema: () => inactiveHostsSchema,
|
|
3832
3854
|
loadSetup: () => loadSetup,
|
|
3833
3855
|
loadSetupReceipt: () => loadSetupReceipt,
|
|
3834
3856
|
setupHostSchema: () => setupHostSchema,
|
|
3835
3857
|
setupSchema: () => setupSchema
|
|
3836
3858
|
});
|
|
3837
3859
|
import { z as z9 } from "zod";
|
|
3860
|
+
import fs14 from "fs/promises";
|
|
3861
|
+
import path22 from "path";
|
|
3838
3862
|
async function loadSetup(root) {
|
|
3839
3863
|
const raw = await readStoreJson(root, SETUP_PATH);
|
|
3840
3864
|
if (raw === null) return null;
|
|
3841
3865
|
return setupSchema.parse(raw);
|
|
3842
3866
|
}
|
|
3867
|
+
async function effectiveSetup(root) {
|
|
3868
|
+
const local = await loadSetup(root);
|
|
3869
|
+
if (local) return { root, setup: local };
|
|
3870
|
+
const common = await fs14.realpath(path22.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
|
|
3871
|
+
const own = await fs14.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
|
|
3872
|
+
if (common === own) return { root, setup: null };
|
|
3873
|
+
const primary = (await git3(root, "worktree", "list", "--porcelain", "-z")).split("\0\0")[0].split("\0");
|
|
3874
|
+
const candidate = primary.find((field) => field.startsWith("worktree "))?.slice(9);
|
|
3875
|
+
if (!candidate || primary.includes("bare")) return { root, setup: null };
|
|
3876
|
+
let inheritedRoot;
|
|
3877
|
+
try {
|
|
3878
|
+
inheritedRoot = await fs14.realpath((await git3(candidate, "rev-parse", "--show-toplevel")).trim());
|
|
3879
|
+
} catch (error) {
|
|
3880
|
+
if (/must be run in a work tree/.test(String(error.stderr))) return { root, setup: null };
|
|
3881
|
+
throw error;
|
|
3882
|
+
}
|
|
3883
|
+
const inheritedCommon = await fs14.realpath(path22.resolve(
|
|
3884
|
+
inheritedRoot,
|
|
3885
|
+
(await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim()
|
|
3886
|
+
));
|
|
3887
|
+
if (inheritedCommon !== common) throw new Error("Linked worktree setup does not belong to the same Git repository.");
|
|
3888
|
+
const setup = await loadSetup(inheritedRoot);
|
|
3889
|
+
const inactive = await readStoreJson(root, INACTIVE_HOSTS_PATH);
|
|
3890
|
+
if (inactive && setup) for (const host of inactiveHostsSchema.parse(inactive).hosts) delete setup.hosts[host];
|
|
3891
|
+
return { root: inheritedRoot, setup };
|
|
3892
|
+
}
|
|
3843
3893
|
async function loadSetupReceipt(root, directory, host) {
|
|
3844
3894
|
const raw = await readStoreJson(root, directory + "/setup-" + host + ".json");
|
|
3845
3895
|
if (raw === null) return null;
|
|
@@ -3847,10 +3897,11 @@ async function loadSetupReceipt(root, directory, host) {
|
|
|
3847
3897
|
if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
|
|
3848
3898
|
return receipt;
|
|
3849
3899
|
}
|
|
3850
|
-
var setupHostSchema, setupSchema, SETUP_PATH, receiptSchema;
|
|
3900
|
+
var setupHostSchema, setupSchema, SETUP_PATH, INACTIVE_HOSTS_PATH, inactiveHostsSchema, receiptSchema;
|
|
3851
3901
|
var init_model = __esm({
|
|
3852
3902
|
"src/setup/model.ts"() {
|
|
3853
3903
|
"use strict";
|
|
3904
|
+
init_evidence();
|
|
3854
3905
|
init_storage();
|
|
3855
3906
|
setupHostSchema = z9.object({
|
|
3856
3907
|
configuredVersion: z9.string(),
|
|
@@ -3864,6 +3915,8 @@ var init_model = __esm({
|
|
|
3864
3915
|
claude: setupHostSchema.optional()
|
|
3865
3916
|
}) });
|
|
3866
3917
|
SETUP_PATH = ".mason/local/setup.json";
|
|
3918
|
+
INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
|
|
3919
|
+
inactiveHostsSchema = z9.object({ version: z9.literal(1), hosts: z9.array(z9.enum(["claude", "codex"])) });
|
|
3867
3920
|
receiptSchema = z9.object({
|
|
3868
3921
|
version: z9.literal(1),
|
|
3869
3922
|
host: z9.enum(["codex", "claude"]),
|
|
@@ -3880,7 +3933,7 @@ var init_model = __esm({
|
|
|
3880
3933
|
// src/setup/launcher.ts
|
|
3881
3934
|
import { execFile as execFile2 } from "child_process";
|
|
3882
3935
|
import { promisify as promisify2 } from "util";
|
|
3883
|
-
import
|
|
3936
|
+
import fs15 from "fs/promises";
|
|
3884
3937
|
function globalCommand(args, platform = process.platform) {
|
|
3885
3938
|
return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args] } : { command: "mason", args };
|
|
3886
3939
|
}
|
|
@@ -3910,7 +3963,7 @@ var init_launcher = __esm({
|
|
|
3910
3963
|
"use strict";
|
|
3911
3964
|
init_evidence();
|
|
3912
3965
|
init_model();
|
|
3913
|
-
executingVersion = () => true ? "0.
|
|
3966
|
+
executingVersion = () => true ? "0.19.0" : "development";
|
|
3914
3967
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
3915
3968
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
3916
3969
|
}
|
|
@@ -4035,8 +4088,8 @@ __export(files_exports, {
|
|
|
4035
4088
|
managedBlock: () => managedBlock,
|
|
4036
4089
|
readText: () => readText
|
|
4037
4090
|
});
|
|
4038
|
-
import
|
|
4039
|
-
import
|
|
4091
|
+
import fs18 from "fs/promises";
|
|
4092
|
+
import path26 from "path";
|
|
4040
4093
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4041
4094
|
async function readText(root, file) {
|
|
4042
4095
|
try {
|
|
@@ -4065,14 +4118,14 @@ async function applyEdit(root, edit) {
|
|
|
4065
4118
|
if (edit.after === null) {
|
|
4066
4119
|
const file2 = await storePath(root, edit.path);
|
|
4067
4120
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
|
|
4068
|
-
await
|
|
4121
|
+
await fs18.unlink(file2);
|
|
4069
4122
|
return true;
|
|
4070
4123
|
}
|
|
4071
4124
|
const file = await storePath(root, edit.path, true);
|
|
4072
|
-
const temporary =
|
|
4125
|
+
const temporary = path26.join(path26.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
|
|
4073
4126
|
try {
|
|
4074
|
-
const mode = await
|
|
4075
|
-
const handle = await
|
|
4127
|
+
const mode = await fs18.stat(file).then((s) => s.mode & 511, () => 384);
|
|
4128
|
+
const handle = await fs18.open(temporary, "wx", mode);
|
|
4076
4129
|
try {
|
|
4077
4130
|
await handle.writeFile(edit.after, "utf8");
|
|
4078
4131
|
await handle.sync();
|
|
@@ -4080,10 +4133,10 @@ async function applyEdit(root, edit) {
|
|
|
4080
4133
|
await handle.close();
|
|
4081
4134
|
}
|
|
4082
4135
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
|
|
4083
|
-
await
|
|
4136
|
+
await fs18.rename(temporary, file);
|
|
4084
4137
|
return true;
|
|
4085
4138
|
} finally {
|
|
4086
|
-
await
|
|
4139
|
+
await fs18.rm(temporary, { force: true });
|
|
4087
4140
|
}
|
|
4088
4141
|
}
|
|
4089
4142
|
var init_files2 = __esm({
|
|
@@ -4105,7 +4158,7 @@ __export(execution_exports, {
|
|
|
4105
4158
|
recordExecution: () => recordExecution
|
|
4106
4159
|
});
|
|
4107
4160
|
import os2 from "os";
|
|
4108
|
-
import
|
|
4161
|
+
import fs19 from "fs/promises";
|
|
4109
4162
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4110
4163
|
import { z as z12 } from "zod";
|
|
4111
4164
|
function parseExecution(raw) {
|
|
@@ -4233,7 +4286,7 @@ async function recordExecution(root, directory, event, run) {
|
|
|
4233
4286
|
throw Object.assign(new Error(failure.message, { cause: error }), { failure });
|
|
4234
4287
|
}
|
|
4235
4288
|
} finally {
|
|
4236
|
-
await
|
|
4289
|
+
await fs19.rm(await storePath(root, owner), { force: true }).catch(() => {
|
|
4237
4290
|
});
|
|
4238
4291
|
}
|
|
4239
4292
|
}
|
|
@@ -4317,13 +4370,17 @@ async function observeReadOnlyTool(dir, event) {
|
|
|
4317
4370
|
}
|
|
4318
4371
|
function summarize(report) {
|
|
4319
4372
|
const open = report.findings.filter((f) => f.status !== "resolved");
|
|
4373
|
+
const onlyReview = report.status === "incomplete" && !report.diagnostics.length && !report.counts.unresolved && !report.counts.unverified && report.counts["review-required"] > 0;
|
|
4320
4374
|
return [
|
|
4321
|
-
`Mason: ${report.status}; ${report.counts.unresolved} unresolved, ${report.counts["review-required"]} need review, ${report.counts.unverified} unverified.`,
|
|
4322
|
-
...open.slice(0, 4).map((f) =>
|
|
4375
|
+
onlyReview ? `Mason: ${report.counts["review-required"]} advisor${report.counts["review-required"] === 1 ? "y" : "ies"} awaiting review; no unresolved issues.` : `Mason: ${report.status}; ${report.counts.unresolved} unresolved, ${report.counts["review-required"]} need review, ${report.counts.unverified} unverified.`,
|
|
4376
|
+
...open.slice(0, 4).map((f) => {
|
|
4377
|
+
const detail = f.current ? f.current.message : f.status === "review-required" ? `${f.original.type}: current check no longer reports ${f.original.type === "deps-changed" ? "this condition" : "the original finding"}; historical evidence awaits assessment.` : `Original evidence: ${f.original.message}. ${f.reason}`;
|
|
4378
|
+
return `[${f.status}] ${cleanText(f.original.anchor.doc)}: ${cleanText(detail)}`;
|
|
4379
|
+
}),
|
|
4323
4380
|
...open.length > 4 ? [`${open.length - 4} more findings in the report.`] : [],
|
|
4324
4381
|
...report.diagnostics.slice(0, 2).map(cleanText),
|
|
4325
4382
|
`Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason-auto check.`,
|
|
4326
|
-
"Keep original evidence. Address findings relevant to the authorized task; report unrelated findings and
|
|
4383
|
+
"Keep original evidence. Address findings relevant to the authorized task; report unrelated findings with a brief scope reason and suggested follow-up. Use review_advisory for authorized assessments; otherwise leave advisories open without approving them."
|
|
4327
4384
|
].join("\n");
|
|
4328
4385
|
}
|
|
4329
4386
|
async function loadState(ws) {
|
|
@@ -4547,7 +4604,7 @@ async function automate(dir, event) {
|
|
|
4547
4604
|
if (event.event === "after_tool" && event.mutating) {
|
|
4548
4605
|
session.mutationObserved = true;
|
|
4549
4606
|
if (!event.toolId || !session.pending[event.toolId]) {
|
|
4550
|
-
const gap = "A tool completed without an observed matching pre-tool capture; pre-edit coverage is unknown.";
|
|
4607
|
+
const gap = "A tool completed without an observed matching pre-tool capture in this checkout; pre-edit coverage is unknown. The tool may have changed checkouts; captures are kept separately for each worktree.";
|
|
4551
4608
|
if (!session.coverageGaps.includes(gap)) session.coverageGaps.push(gap);
|
|
4552
4609
|
}
|
|
4553
4610
|
if (event.toolId) delete session.pending[event.toolId];
|
|
@@ -4656,8 +4713,8 @@ var init_runtime = __esm({
|
|
|
4656
4713
|
});
|
|
4657
4714
|
|
|
4658
4715
|
// src/automation/hook-root.ts
|
|
4659
|
-
import
|
|
4660
|
-
import
|
|
4716
|
+
import fs20 from "fs/promises";
|
|
4717
|
+
import path27 from "path";
|
|
4661
4718
|
var init_hook_root = __esm({
|
|
4662
4719
|
"src/automation/hook-root.ts"() {
|
|
4663
4720
|
"use strict";
|
|
@@ -5500,10 +5557,10 @@ var init_check = __esm({
|
|
|
5500
5557
|
});
|
|
5501
5558
|
|
|
5502
5559
|
// src/review/evidence.ts
|
|
5503
|
-
import
|
|
5560
|
+
import path28 from "path";
|
|
5504
5561
|
import { z as z22 } from "zod";
|
|
5505
5562
|
function relativeArtifact(root, file) {
|
|
5506
|
-
const relative =
|
|
5563
|
+
const relative = path28.isAbsolute(file) ? path28.relative(root, file) : file;
|
|
5507
5564
|
const normalized = normalizeRepoPath(relative);
|
|
5508
5565
|
if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
|
|
5509
5566
|
return normalized;
|
|
@@ -5600,7 +5657,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
5600
5657
|
const report = await readStoreJson(root, file);
|
|
5601
5658
|
if (report === null) throw new Error(`Report artifact is missing: ${file}`);
|
|
5602
5659
|
const sourceRoot = check.sourceRoot ?? root;
|
|
5603
|
-
if (!
|
|
5660
|
+
if (!path28.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
|
|
5604
5661
|
const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
|
|
5605
5662
|
result.outcome = parsed.outcome;
|
|
5606
5663
|
result.counts = parsed.counts;
|
|
@@ -5721,8 +5778,8 @@ var init_evidence2 = __esm({
|
|
|
5721
5778
|
});
|
|
5722
5779
|
|
|
5723
5780
|
// src/review/review.ts
|
|
5724
|
-
import
|
|
5725
|
-
import
|
|
5781
|
+
import fs21 from "fs/promises";
|
|
5782
|
+
import path29 from "path";
|
|
5726
5783
|
import { execFile as execFile7 } from "child_process";
|
|
5727
5784
|
import { promisify as promisify7 } from "util";
|
|
5728
5785
|
async function resolveMergeBase(resolvedRoot, base, head) {
|
|
@@ -5751,7 +5808,7 @@ function anchorsTouched(record, changedFiles) {
|
|
|
5751
5808
|
return matchingPaths(record.files, changedFiles);
|
|
5752
5809
|
}
|
|
5753
5810
|
async function computeReview(rootDir, base, options = {}) {
|
|
5754
|
-
const resolvedRoot =
|
|
5811
|
+
const resolvedRoot = path29.resolve(rootDir);
|
|
5755
5812
|
const head = await getCurrentGitHash(resolvedRoot);
|
|
5756
5813
|
if (head === "unknown") return null;
|
|
5757
5814
|
const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
|
|
@@ -5801,7 +5858,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5801
5858
|
analyzed,
|
|
5802
5859
|
async (relPath) => {
|
|
5803
5860
|
try {
|
|
5804
|
-
await
|
|
5861
|
+
await fs21.access(path29.join(resolvedRoot, relPath));
|
|
5805
5862
|
return true;
|
|
5806
5863
|
} catch {
|
|
5807
5864
|
return false;
|
|
@@ -5936,7 +5993,7 @@ var init_onboarding = __esm({
|
|
|
5936
5993
|
});
|
|
5937
5994
|
|
|
5938
5995
|
// src/impact/references.ts
|
|
5939
|
-
import
|
|
5996
|
+
import path30 from "path";
|
|
5940
5997
|
function templateEnd(content2, start) {
|
|
5941
5998
|
let i = start + 1, interpolation = 0;
|
|
5942
5999
|
while (i < content2.length) {
|
|
@@ -5958,10 +6015,10 @@ function templateEnd(content2, start) {
|
|
|
5958
6015
|
continue;
|
|
5959
6016
|
}
|
|
5960
6017
|
if (c === '"' || c === "'") {
|
|
5961
|
-
const
|
|
6018
|
+
const quote2 = c;
|
|
5962
6019
|
for (i++; i < content2.length; i++) {
|
|
5963
6020
|
if (content2[i] === "\\") i++;
|
|
5964
|
-
else if (content2[i] ===
|
|
6021
|
+
else if (content2[i] === quote2) {
|
|
5965
6022
|
i++;
|
|
5966
6023
|
break;
|
|
5967
6024
|
}
|
|
@@ -6062,10 +6119,10 @@ function jvmImports(content2) {
|
|
|
6062
6119
|
}
|
|
6063
6120
|
function resolveImport(file, specifier, available) {
|
|
6064
6121
|
if (!specifier.startsWith("./") && !specifier.startsWith("../")) return null;
|
|
6065
|
-
const relative =
|
|
6122
|
+
const relative = path30.posix.normalize(path30.posix.join(path30.posix.dirname(file), specifier));
|
|
6066
6123
|
if (relative.startsWith("../")) return null;
|
|
6067
6124
|
if (available.has(relative)) return relative;
|
|
6068
|
-
const ext =
|
|
6125
|
+
const ext = path30.posix.extname(relative);
|
|
6069
6126
|
const replacements = { ".js": [".ts", ".tsx"], ".mjs": [".mts"], ".cjs": [".cts"] };
|
|
6070
6127
|
const candidates = ext ? (replacements[ext] ?? []).map((e) => relative.slice(0, -ext.length) + e) : jsExtensions.flatMap((e) => [relative + e, relative + "/index" + e]);
|
|
6071
6128
|
const found = candidates.filter((f) => available.has(f));
|
|
@@ -6074,23 +6131,23 @@ function resolveImport(file, specifier, available) {
|
|
|
6074
6131
|
function mentionsPath(content2, target) {
|
|
6075
6132
|
return new RegExp(`(?<![\\w./\\\\-])(?:\\./)?${escape(target)}(?![\\w./\\\\-])`).test(content2);
|
|
6076
6133
|
}
|
|
6077
|
-
function createReferenceMatcher(
|
|
6134
|
+
function createReferenceMatcher(targets2, available, targetContents = /* @__PURE__ */ new Map()) {
|
|
6078
6135
|
const counts = /* @__PURE__ */ new Map();
|
|
6079
6136
|
for (const file of available) {
|
|
6080
|
-
const name =
|
|
6137
|
+
const name = path30.posix.basename(file, path30.posix.extname(file));
|
|
6081
6138
|
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
6082
6139
|
}
|
|
6083
6140
|
const jvmTargets = new Map([...targetContents].filter(([file]) => /\.(?:kt|kts|java)$/.test(file)).map(([file, content2]) => [file, declaredSymbols(content2)]));
|
|
6084
6141
|
return (source) => {
|
|
6085
|
-
if (
|
|
6086
|
-
const resolved = new Set(jsExtensions.includes(
|
|
6142
|
+
if (targets2.includes(source.path)) return [];
|
|
6143
|
+
const resolved = new Set(jsExtensions.includes(path30.posix.extname(source.path)) ? imports(source.content).map((s) => resolveImport(source.path, s, available)).filter(Boolean) : []);
|
|
6087
6144
|
if (/\.(?:kt|kts|java)$/.test(source.path)) {
|
|
6088
6145
|
const imported = jvmImports(source.content);
|
|
6089
6146
|
for (const [target, symbols] of jvmTargets) if (symbols.some((symbol) => imported.some((name) => name === symbol || name.startsWith(symbol + ".")))) resolved.add(target);
|
|
6090
6147
|
}
|
|
6091
6148
|
const groups = /* @__PURE__ */ new Map();
|
|
6092
6149
|
const add = (evidence, match) => groups.set(evidence, [...groups.get(evidence) ?? [], match]);
|
|
6093
|
-
for (const target of
|
|
6150
|
+
for (const target of targets2) {
|
|
6094
6151
|
if (resolved.has(target)) {
|
|
6095
6152
|
add("resolved-import", target);
|
|
6096
6153
|
continue;
|
|
@@ -6099,7 +6156,7 @@ function createReferenceMatcher(targets, available, targetContents = /* @__PURE_
|
|
|
6099
6156
|
add("explicit-path", target);
|
|
6100
6157
|
continue;
|
|
6101
6158
|
}
|
|
6102
|
-
const ext =
|
|
6159
|
+
const ext = path30.posix.extname(target), name = path30.posix.basename(target, ext);
|
|
6103
6160
|
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;
|
|
6104
6161
|
if (word(name).test(source.content)) {
|
|
6105
6162
|
add("name-candidate", name);
|
|
@@ -6128,11 +6185,11 @@ var impact_exports = {};
|
|
|
6128
6185
|
__export(impact_exports, {
|
|
6129
6186
|
analyzeImpact: () => analyzeImpact
|
|
6130
6187
|
});
|
|
6131
|
-
import
|
|
6188
|
+
import path31 from "path";
|
|
6132
6189
|
import { execFile as execFile8 } from "child_process";
|
|
6133
6190
|
import { promisify as promisify8 } from "util";
|
|
6134
6191
|
async function analyzeImpact(rootDir, targetFiles) {
|
|
6135
|
-
const resolvedRoot =
|
|
6192
|
+
const resolvedRoot = path31.resolve(rootDir);
|
|
6136
6193
|
const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
|
|
6137
6194
|
const [cochange, references, tests] = await Promise.all([
|
|
6138
6195
|
getCochangeFiles(resolvedRoot, resolvedTargets),
|
|
@@ -6146,10 +6203,10 @@ async function analyzeImpact(rootDir, targetFiles) {
|
|
|
6146
6203
|
tests
|
|
6147
6204
|
};
|
|
6148
6205
|
}
|
|
6149
|
-
async function resolveTargetFiles(rootDir,
|
|
6206
|
+
async function resolveTargetFiles(rootDir, targets2) {
|
|
6150
6207
|
const resolved = [];
|
|
6151
6208
|
const access = await createFileAccess(rootDir);
|
|
6152
|
-
for (const target of
|
|
6209
|
+
for (const target of targets2) {
|
|
6153
6210
|
if (target.includes("/")) {
|
|
6154
6211
|
const normalized = normalizeRepoPath(target);
|
|
6155
6212
|
if (normalized) resolved.push(normalized);
|
|
@@ -6246,9 +6303,9 @@ async function getRelatedTests(rootDir, targetFiles) {
|
|
|
6246
6303
|
const testFiles = await (await createFileAccess(rootDir)).list(testPatterns);
|
|
6247
6304
|
const results = [];
|
|
6248
6305
|
for (const target of targetFiles) {
|
|
6249
|
-
const targetBaseName =
|
|
6306
|
+
const targetBaseName = path31.basename(target).replace(/\.[^.]+$/, "");
|
|
6250
6307
|
for (const testFile of testFiles) {
|
|
6251
|
-
const testBaseName =
|
|
6308
|
+
const testBaseName = path31.basename(testFile).replace(/\.[^.]+$/, "");
|
|
6252
6309
|
const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
|
|
6253
6310
|
if (sourceName === targetBaseName) {
|
|
6254
6311
|
results.push({
|
|
@@ -6276,26 +6333,26 @@ var assemble_exports = {};
|
|
|
6276
6333
|
__export(assemble_exports, {
|
|
6277
6334
|
assembleContext: () => assembleContext
|
|
6278
6335
|
});
|
|
6279
|
-
import
|
|
6280
|
-
import
|
|
6336
|
+
import path32 from "path";
|
|
6337
|
+
import fs22 from "fs/promises";
|
|
6281
6338
|
async function collectImpact(root, candidates) {
|
|
6282
|
-
const
|
|
6339
|
+
const targets2 = /* @__PURE__ */ new Set();
|
|
6283
6340
|
let sourceFiles;
|
|
6284
6341
|
for (const candidate of sanitizeRepoPaths(candidates)) {
|
|
6285
|
-
const stat = await
|
|
6342
|
+
const stat = await fs22.stat(path32.join(root, candidate)).catch(() => null);
|
|
6286
6343
|
if (stat?.isDirectory()) {
|
|
6287
6344
|
sourceFiles ??= await (await createFileAccess(root)).list();
|
|
6288
6345
|
for (const file of sourceFiles) {
|
|
6289
|
-
if (anchorMatches(candidate, file))
|
|
6290
|
-
if (
|
|
6346
|
+
if (anchorMatches(candidate, file)) targets2.add(file);
|
|
6347
|
+
if (targets2.size >= MAX_IMPACT_TARGETS) break;
|
|
6291
6348
|
}
|
|
6292
6349
|
} else {
|
|
6293
|
-
|
|
6350
|
+
targets2.add(candidate);
|
|
6294
6351
|
}
|
|
6295
|
-
if (
|
|
6352
|
+
if (targets2.size >= MAX_IMPACT_TARGETS) break;
|
|
6296
6353
|
}
|
|
6297
|
-
if (!
|
|
6298
|
-
const result = await analyzeImpact(root, [...
|
|
6354
|
+
if (!targets2.size) return { impact: null, relatedTests: [] };
|
|
6355
|
+
const result = await analyzeImpact(root, [...targets2]);
|
|
6299
6356
|
const referenceCandidatesOmitted = result.references.filter((r) => r.evidence === "name-candidate").length;
|
|
6300
6357
|
return {
|
|
6301
6358
|
impact: {
|
|
@@ -6308,7 +6365,7 @@ async function collectImpact(root, candidates) {
|
|
|
6308
6365
|
};
|
|
6309
6366
|
}
|
|
6310
6367
|
async function assembleContext(rootDir, task, files) {
|
|
6311
|
-
const resolvedRoot =
|
|
6368
|
+
const resolvedRoot = path32.resolve(rootDir);
|
|
6312
6369
|
const mapState = await inspectSnapshot(resolvedRoot);
|
|
6313
6370
|
const snapshot = mapState.snapshot;
|
|
6314
6371
|
const store = await loadDecisionStore(resolvedRoot);
|
|
@@ -6504,7 +6561,7 @@ __export(observations_exports, {
|
|
|
6504
6561
|
observeActivation: () => observeActivation,
|
|
6505
6562
|
readObservation: () => readObservation
|
|
6506
6563
|
});
|
|
6507
|
-
import
|
|
6564
|
+
import fs23 from "fs/promises";
|
|
6508
6565
|
import { z as z23 } from "zod";
|
|
6509
6566
|
function observationPath(directory, host, revision, version = executingVersion()) {
|
|
6510
6567
|
return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
@@ -6521,15 +6578,15 @@ async function observeActivation(dir, event, options = {}) {
|
|
|
6521
6578
|
if (host !== "codex" && host !== "claude" || !process.env.MASON_SETUP_ROOT) return null;
|
|
6522
6579
|
try {
|
|
6523
6580
|
if (!revision) {
|
|
6524
|
-
if ((await
|
|
6581
|
+
if ((await effectiveSetup(process.env.MASON_SETUP_ROOT)).setup?.hosts[host]) {
|
|
6525
6582
|
return "Mason setup is now present. Restart the assistant to connect this MCP session to the configured integration; activation has not been established by this session.";
|
|
6526
6583
|
}
|
|
6527
6584
|
return inactiveSetupMessage();
|
|
6528
6585
|
}
|
|
6529
6586
|
const capturedDirectory = options.directory?.match(/^\.mason\/reports\/automation\/[a-f0-9]{24}$/)?.[0] ?? options.reportPath?.match(/^(\.mason\/reports\/automation\/[a-f0-9]{24})\/checks\//)?.[1];
|
|
6530
|
-
const ws = capturedDirectory ? { root: await
|
|
6531
|
-
if (ws.root !== await
|
|
6532
|
-
const setup = await
|
|
6587
|
+
const ws = capturedDirectory ? { root: await fs23.realpath(dir), directory: capturedDirectory } : await workspace(dir);
|
|
6588
|
+
if (ws.root !== await fs23.realpath(process.env.MASON_SETUP_ROOT)) return null;
|
|
6589
|
+
const { setup } = await effectiveSetup(ws.root);
|
|
6533
6590
|
if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
|
|
6534
6591
|
const directory = ws.directory + "/activation";
|
|
6535
6592
|
await withLock(ws.root, directory, async () => {
|
|
@@ -6603,8 +6660,9 @@ __export(status_exports, {
|
|
|
6603
6660
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
6604
6661
|
async function setupStatus(dir) {
|
|
6605
6662
|
const ws = await workspace(dir);
|
|
6606
|
-
const
|
|
6607
|
-
|
|
6663
|
+
const activation = await effectiveSetup(ws.root);
|
|
6664
|
+
const setup = activation.setup;
|
|
6665
|
+
if (!setup || !Object.keys(setup.hosts).length) {
|
|
6608
6666
|
const partial = await Promise.all(["codex", "claude"].map(async (host) => ({
|
|
6609
6667
|
host,
|
|
6610
6668
|
receipt: await loadSetupReceipt(ws.root, ws.directory, host)
|
|
@@ -6638,7 +6696,7 @@ async function setupStatus(dir) {
|
|
|
6638
6696
|
]));
|
|
6639
6697
|
const hooks = isDeepStrictEqual2(compatibleHooks, expectedHooks) && !config.disabled;
|
|
6640
6698
|
const local = await loadSetupReceipt(ws.root, ws.directory, host);
|
|
6641
|
-
const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
|
|
6699
|
+
const configured = activation.root !== ws.root || local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
|
|
6642
6700
|
const observation = await readObservation(ws.root, ws.directory, host, entry.revision, installed.version ?? "unavailable");
|
|
6643
6701
|
const sessions = Object.values(observation?.sessions ?? {}).sort((a, b) => b.at.localeCompare(a.at));
|
|
6644
6702
|
const complete = sessions.find((session) => events.every((event) => session.events.includes(event)));
|
|
@@ -6670,6 +6728,7 @@ async function setupStatus(dir) {
|
|
|
6670
6728
|
status: statuses.length && statuses.every((status) => status === "active") ? "active" : statuses.includes("attention") ? "attention" : "pending",
|
|
6671
6729
|
root: ws.root,
|
|
6672
6730
|
hosts,
|
|
6731
|
+
activationRoot: activation.root,
|
|
6673
6732
|
decisionRecords: decisions.records.length,
|
|
6674
6733
|
diagnostics: decisions.diagnostics,
|
|
6675
6734
|
scope: "Activation receipts record observed use after setup, not complete interception, correct repairs, or measured usefulness. Host trust and higher-priority settings may prevent execution. Receipts are local to this worktree and branch."
|
|
@@ -6677,6 +6736,9 @@ async function setupStatus(dir) {
|
|
|
6677
6736
|
}
|
|
6678
6737
|
function summarizeActivation(status) {
|
|
6679
6738
|
const lines = ["Mason setup: " + status.status + "."];
|
|
6739
|
+
if (status.activationRoot && status.activationRoot !== status.root) {
|
|
6740
|
+
lines.push("Activation inherited from " + status.activationRoot + "; evidence belongs to this worktree.");
|
|
6741
|
+
}
|
|
6680
6742
|
for (const [host, state] of Object.entries(status.hosts)) {
|
|
6681
6743
|
lines.push(
|
|
6682
6744
|
`${host}: ${state.status}`,
|
|
@@ -6846,8 +6908,8 @@ __export(client_exports, {
|
|
|
6846
6908
|
function createConfluenceClient(config, fetchFn = fetch) {
|
|
6847
6909
|
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
6848
6910
|
const auth = "Basic " + Buffer.from(`${config.email}:${config.apiToken}`).toString("base64");
|
|
6849
|
-
async function call(method,
|
|
6850
|
-
const res = await fetchFn(`${baseUrl}${
|
|
6911
|
+
async function call(method, path42, body) {
|
|
6912
|
+
const res = await fetchFn(`${baseUrl}${path42}`, {
|
|
6851
6913
|
method,
|
|
6852
6914
|
headers: {
|
|
6853
6915
|
Authorization: auth,
|
|
@@ -6859,7 +6921,7 @@ function createConfluenceClient(config, fetchFn = fetch) {
|
|
|
6859
6921
|
if (!res.ok) {
|
|
6860
6922
|
const text2 = await res.text();
|
|
6861
6923
|
throw new Error(
|
|
6862
|
-
`Confluence ${method} ${
|
|
6924
|
+
`Confluence ${method} ${path42} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
|
|
6863
6925
|
);
|
|
6864
6926
|
}
|
|
6865
6927
|
if (res.status === 204) return null;
|
|
@@ -6960,28 +7022,28 @@ __export(config_exports2, {
|
|
|
6960
7022
|
saveConfluenceConfig: () => saveConfluenceConfig,
|
|
6961
7023
|
validateProvider: () => validateProvider
|
|
6962
7024
|
});
|
|
6963
|
-
import
|
|
6964
|
-
import
|
|
7025
|
+
import fs24 from "fs/promises";
|
|
7026
|
+
import path33 from "path";
|
|
6965
7027
|
import os3 from "os";
|
|
6966
7028
|
import { execFile as execFile9 } from "child_process";
|
|
6967
7029
|
import { promisify as promisify9 } from "util";
|
|
6968
7030
|
function configDir() {
|
|
6969
|
-
return
|
|
7031
|
+
return path33.join(os3.homedir(), ".mason");
|
|
6970
7032
|
}
|
|
6971
7033
|
function configFile() {
|
|
6972
|
-
return
|
|
7034
|
+
return path33.join(configDir(), "config.json");
|
|
6973
7035
|
}
|
|
6974
7036
|
async function loadConfig() {
|
|
6975
7037
|
try {
|
|
6976
|
-
const raw = await
|
|
7038
|
+
const raw = await fs24.readFile(configFile(), "utf-8");
|
|
6977
7039
|
return JSON.parse(raw);
|
|
6978
7040
|
} catch {
|
|
6979
7041
|
return null;
|
|
6980
7042
|
}
|
|
6981
7043
|
}
|
|
6982
7044
|
async function saveConfig(config) {
|
|
6983
|
-
await
|
|
6984
|
-
await
|
|
7045
|
+
await fs24.mkdir(configDir(), { recursive: true });
|
|
7046
|
+
await fs24.writeFile(configFile(), JSON.stringify(config, null, 2), "utf-8");
|
|
6985
7047
|
}
|
|
6986
7048
|
function getDefaultModel(provider) {
|
|
6987
7049
|
return DEFAULT_MODELS[provider];
|
|
@@ -7141,21 +7203,21 @@ var init_renderer = __esm({
|
|
|
7141
7203
|
});
|
|
7142
7204
|
|
|
7143
7205
|
// src/confluence/diff.ts
|
|
7144
|
-
import
|
|
7145
|
-
import
|
|
7206
|
+
import fs25 from "fs/promises";
|
|
7207
|
+
import path34 from "path";
|
|
7146
7208
|
import { createHash as createHash6 } from "crypto";
|
|
7147
7209
|
function hashDescription(description) {
|
|
7148
7210
|
return createHash6("sha256").update(description, "utf8").digest("hex");
|
|
7149
7211
|
}
|
|
7150
7212
|
function syncStateDir(rootDir) {
|
|
7151
|
-
return
|
|
7213
|
+
return path34.join(rootDir, ".mason");
|
|
7152
7214
|
}
|
|
7153
7215
|
function syncStatePath(rootDir) {
|
|
7154
|
-
return
|
|
7216
|
+
return path34.join(syncStateDir(rootDir), "confluence-sync.json");
|
|
7155
7217
|
}
|
|
7156
7218
|
async function loadSyncState(rootDir) {
|
|
7157
7219
|
try {
|
|
7158
|
-
const raw = await
|
|
7220
|
+
const raw = await fs25.readFile(syncStatePath(rootDir), "utf-8");
|
|
7159
7221
|
const parsed = JSON.parse(raw);
|
|
7160
7222
|
if (parsed.version !== 2) return null;
|
|
7161
7223
|
return parsed;
|
|
@@ -7164,8 +7226,8 @@ async function loadSyncState(rootDir) {
|
|
|
7164
7226
|
}
|
|
7165
7227
|
}
|
|
7166
7228
|
async function saveSyncState(rootDir, state) {
|
|
7167
|
-
await
|
|
7168
|
-
await
|
|
7229
|
+
await fs25.mkdir(syncStateDir(rootDir), { recursive: true });
|
|
7230
|
+
await fs25.writeFile(
|
|
7169
7231
|
syncStatePath(rootDir),
|
|
7170
7232
|
JSON.stringify(state, null, 2),
|
|
7171
7233
|
"utf-8"
|
|
@@ -7723,8 +7785,8 @@ import { z as z24 } from "zod";
|
|
|
7723
7785
|
// src/mcp/tools.ts
|
|
7724
7786
|
init_repair();
|
|
7725
7787
|
init_advisory_review();
|
|
7726
|
-
import
|
|
7727
|
-
import
|
|
7788
|
+
import fs26 from "fs/promises";
|
|
7789
|
+
import path35 from "path";
|
|
7728
7790
|
import { execFile as execFile11 } from "child_process";
|
|
7729
7791
|
import { promisify as promisify11 } from "util";
|
|
7730
7792
|
|
|
@@ -7733,11 +7795,11 @@ init_audit();
|
|
|
7733
7795
|
init_types();
|
|
7734
7796
|
init_repair();
|
|
7735
7797
|
init_findings();
|
|
7736
|
-
import
|
|
7798
|
+
import path24 from "path";
|
|
7737
7799
|
|
|
7738
7800
|
// src/audit/advisory-cli.ts
|
|
7739
7801
|
init_advisory_review();
|
|
7740
|
-
import
|
|
7802
|
+
import path23 from "path";
|
|
7741
7803
|
import { stripVTControlCharacters } from "util";
|
|
7742
7804
|
|
|
7743
7805
|
// src/audit/cli.ts
|
|
@@ -7834,7 +7896,7 @@ import { execFile as execFile3 } from "child_process";
|
|
|
7834
7896
|
import { promisify as promisify3 } from "util";
|
|
7835
7897
|
|
|
7836
7898
|
// src/analyzers/base.ts
|
|
7837
|
-
import
|
|
7899
|
+
import fs16 from "fs/promises";
|
|
7838
7900
|
import fg6 from "fast-glob";
|
|
7839
7901
|
var BaseAnalyzer = class {
|
|
7840
7902
|
async findFiles(patterns, root) {
|
|
@@ -7845,7 +7907,7 @@ var BaseAnalyzer = class {
|
|
|
7845
7907
|
});
|
|
7846
7908
|
}
|
|
7847
7909
|
async readFile(filePath) {
|
|
7848
|
-
return
|
|
7910
|
+
return fs16.readFile(filePath, "utf-8");
|
|
7849
7911
|
}
|
|
7850
7912
|
createFinding(partial) {
|
|
7851
7913
|
return {
|
|
@@ -8050,7 +8112,7 @@ async function isGitRepo(dir) {
|
|
|
8050
8112
|
|
|
8051
8113
|
// src/mcp/sampler.ts
|
|
8052
8114
|
init_files();
|
|
8053
|
-
import
|
|
8115
|
+
import path25 from "path";
|
|
8054
8116
|
import { execFile as execFile4 } from "child_process";
|
|
8055
8117
|
import { promisify as promisify4 } from "util";
|
|
8056
8118
|
var exec2 = promisify4(execFile4);
|
|
@@ -8158,8 +8220,8 @@ async function sampleFiles(rootDir, maxFiles = 25) {
|
|
|
8158
8220
|
const projectConfig = access.config;
|
|
8159
8221
|
for (const filePath of projectConfig.alwaysInclude ?? []) {
|
|
8160
8222
|
if (selected.size >= maxFiles) break;
|
|
8161
|
-
const resolvedPath =
|
|
8162
|
-
if (!resolvedPath.startsWith(
|
|
8223
|
+
const resolvedPath = path25.resolve(rootDir, filePath);
|
|
8224
|
+
if (!resolvedPath.startsWith(path25.resolve(rootDir))) continue;
|
|
8163
8225
|
selected.set(filePath, "always-include (project config)");
|
|
8164
8226
|
}
|
|
8165
8227
|
let configCount = 0;
|
|
@@ -8225,7 +8287,7 @@ async function sampleFiles(rootDir, maxFiles = 25) {
|
|
|
8225
8287
|
if (!line) continue;
|
|
8226
8288
|
if (line.includes("node_modules") || line.includes("/build/") || line.includes(".gradle") || line.includes("/generated/"))
|
|
8227
8289
|
continue;
|
|
8228
|
-
const ext =
|
|
8290
|
+
const ext = path25.extname(line).slice(1);
|
|
8229
8291
|
if (!SOURCE_EXTENSIONS.includes(ext)) continue;
|
|
8230
8292
|
fileCounts.set(line, (fileCounts.get(line) ?? 0) + 1);
|
|
8231
8293
|
}
|
|
@@ -8580,7 +8642,7 @@ ${JSON.stringify({ partials }, null, 2)}`;
|
|
|
8580
8642
|
init_lock();
|
|
8581
8643
|
init_storage();
|
|
8582
8644
|
init_snapshot();
|
|
8583
|
-
import
|
|
8645
|
+
import fs17 from "fs/promises";
|
|
8584
8646
|
import { z as z11 } from "zod";
|
|
8585
8647
|
var DIRECTORY = ".mason/partial-snapshots";
|
|
8586
8648
|
var partialSchema = z11.object({
|
|
@@ -8598,7 +8660,7 @@ async function savePartial(rootDir, partial) {
|
|
|
8598
8660
|
async function loadAllPartials(rootDir) {
|
|
8599
8661
|
let entries;
|
|
8600
8662
|
try {
|
|
8601
|
-
entries = await
|
|
8663
|
+
entries = await fs17.readdir(await storePath(rootDir, DIRECTORY));
|
|
8602
8664
|
} catch (error) {
|
|
8603
8665
|
if (error.code === "ENOENT") return [];
|
|
8604
8666
|
throw error;
|
|
@@ -8621,11 +8683,11 @@ async function loadScope(rootDir) {
|
|
|
8621
8683
|
}
|
|
8622
8684
|
async function clearScope(rootDir) {
|
|
8623
8685
|
await withSnapshotWrite(rootDir, async () => {
|
|
8624
|
-
await
|
|
8686
|
+
await fs17.rm(await storePath(rootDir, `${DIRECTORY}/scope.json`), { force: true });
|
|
8625
8687
|
});
|
|
8626
8688
|
}
|
|
8627
8689
|
async function clearAllPartials(rootDir) {
|
|
8628
|
-
await
|
|
8690
|
+
await fs17.rm(await storePath(rootDir, DIRECTORY), { recursive: true, force: true });
|
|
8629
8691
|
}
|
|
8630
8692
|
function batchIdFor(offset) {
|
|
8631
8693
|
return `batch-${String(offset).padStart(6, "0")}`;
|
|
@@ -8642,7 +8704,7 @@ async function buildContext(dir) {
|
|
|
8642
8704
|
};
|
|
8643
8705
|
}
|
|
8644
8706
|
async function analyzeProject(dir) {
|
|
8645
|
-
const rootDir =
|
|
8707
|
+
const rootDir = path35.resolve(dir);
|
|
8646
8708
|
const context = await buildContext(rootDir);
|
|
8647
8709
|
const results = await runAll(context);
|
|
8648
8710
|
const projectSnapshot = await detectProjectSnapshot(rootDir);
|
|
@@ -8697,7 +8759,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
8697
8759
|
const present = [];
|
|
8698
8760
|
for (const file of buildFiles) {
|
|
8699
8761
|
try {
|
|
8700
|
-
await
|
|
8762
|
+
await fs26.access(path35.join(rootDir, file));
|
|
8701
8763
|
present.push(file);
|
|
8702
8764
|
} catch {
|
|
8703
8765
|
}
|
|
@@ -8739,7 +8801,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
8739
8801
|
const sourceFiles = await access.list();
|
|
8740
8802
|
const fileCounts = {};
|
|
8741
8803
|
for (const file of sourceFiles) {
|
|
8742
|
-
const ext =
|
|
8804
|
+
const ext = path35.extname(file).slice(1);
|
|
8743
8805
|
fileCounts[ext] = (fileCounts[ext] ?? 0) + 1;
|
|
8744
8806
|
}
|
|
8745
8807
|
return {
|
|
@@ -8750,7 +8812,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
8750
8812
|
};
|
|
8751
8813
|
}
|
|
8752
8814
|
async function getCodeSamples(dir, count4 = 15) {
|
|
8753
|
-
const rootDir =
|
|
8815
|
+
const rootDir = path35.resolve(dir);
|
|
8754
8816
|
const samples = await sampleFiles(rootDir, count4);
|
|
8755
8817
|
const output = {
|
|
8756
8818
|
note: "These are previews (first ~60 lines). Read the file directly with your own tools to see it in full.",
|
|
@@ -8793,7 +8855,7 @@ async function unmappedContextResponse(rootDir) {
|
|
|
8793
8855
|
});
|
|
8794
8856
|
}
|
|
8795
8857
|
async function getProjectStructure(dir) {
|
|
8796
|
-
const rootDir =
|
|
8858
|
+
const rootDir = path35.resolve(dir);
|
|
8797
8859
|
const allFiles = await (await createFileAccess(rootDir)).list("**/*");
|
|
8798
8860
|
const dirInfo = /* @__PURE__ */ new Map();
|
|
8799
8861
|
for (const file of allFiles) {
|
|
@@ -8805,7 +8867,7 @@ async function getProjectStructure(dir) {
|
|
|
8805
8867
|
}
|
|
8806
8868
|
const info = dirInfo.get(dirPath);
|
|
8807
8869
|
info.fileCount++;
|
|
8808
|
-
const ext =
|
|
8870
|
+
const ext = path35.extname(file).slice(1);
|
|
8809
8871
|
if (ext) {
|
|
8810
8872
|
info.extensions.set(ext, (info.extensions.get(ext) ?? 0) + 1);
|
|
8811
8873
|
}
|
|
@@ -8850,7 +8912,7 @@ async function buildChangedFilePreviews(rootDir, changedFiles) {
|
|
|
8850
8912
|
return previews2;
|
|
8851
8913
|
}
|
|
8852
8914
|
async function getSnapshot(dir) {
|
|
8853
|
-
const rootDir =
|
|
8915
|
+
const rootDir = path35.resolve(dir);
|
|
8854
8916
|
const snapshot = await loadSnapshot(rootDir);
|
|
8855
8917
|
if (!snapshot) {
|
|
8856
8918
|
return unmappedContextResponse(rootDir);
|
|
@@ -8951,7 +9013,7 @@ function driftHint(report) {
|
|
|
8951
9013
|
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.";
|
|
8952
9014
|
}
|
|
8953
9015
|
async function checkDrift(dir) {
|
|
8954
|
-
const rootDir =
|
|
9016
|
+
const rootDir = path35.resolve(dir);
|
|
8955
9017
|
const report = await computeDrift(rootDir);
|
|
8956
9018
|
if (!report) {
|
|
8957
9019
|
return JSON.stringify({
|
|
@@ -8982,7 +9044,7 @@ async function checkDrift(dir) {
|
|
|
8982
9044
|
return JSON.stringify({ exists: true, ...report, verification, hint });
|
|
8983
9045
|
}
|
|
8984
9046
|
async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_SIZE, files) {
|
|
8985
|
-
const rootDir =
|
|
9047
|
+
const rootDir = path35.resolve(dir);
|
|
8986
9048
|
const scoped = files !== void 0 && files.length > 0;
|
|
8987
9049
|
const scopeFiles = scoped ? sanitizePaths(rootDir, files) : void 0;
|
|
8988
9050
|
const batch = await prepareSnapshotBatch(dir, offset, batchSize, scopeFiles);
|
|
@@ -9029,7 +9091,7 @@ async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_
|
|
|
9029
9091
|
);
|
|
9030
9092
|
}
|
|
9031
9093
|
async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
|
|
9032
|
-
const rootDir =
|
|
9094
|
+
const rootDir = path35.resolve(dir);
|
|
9033
9095
|
for (const feat of Object.values(features)) {
|
|
9034
9096
|
feat.files = sanitizePaths(rootDir, feat.files);
|
|
9035
9097
|
if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
|
|
@@ -9058,7 +9120,7 @@ async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
|
|
|
9058
9120
|
);
|
|
9059
9121
|
}
|
|
9060
9122
|
async function reduceSnapshot(dir) {
|
|
9061
|
-
const rootDir =
|
|
9123
|
+
const rootDir = path35.resolve(dir);
|
|
9062
9124
|
const partials = await loadAllPartials(rootDir);
|
|
9063
9125
|
if (partials.length === 0) {
|
|
9064
9126
|
return JSON.stringify(
|
|
@@ -9120,7 +9182,7 @@ async function reduceSnapshot(dir) {
|
|
|
9120
9182
|
);
|
|
9121
9183
|
}
|
|
9122
9184
|
async function fullAnalysis(dir) {
|
|
9123
|
-
const rootDir =
|
|
9185
|
+
const rootDir = path35.resolve(dir);
|
|
9124
9186
|
const [analysis, structure, samples, testMap, snapshot] = await Promise.all([
|
|
9125
9187
|
analyzeProject(dir),
|
|
9126
9188
|
getProjectStructure(dir),
|
|
@@ -9149,7 +9211,7 @@ function sanitizePaths(rootDir, files) {
|
|
|
9149
9211
|
return sanitizeRepoPaths(files);
|
|
9150
9212
|
}
|
|
9151
9213
|
async function saveSnapshotData(dir, features, flows, removeFeatures = [], removeFlows = []) {
|
|
9152
|
-
const rootDir =
|
|
9214
|
+
const rootDir = path35.resolve(dir);
|
|
9153
9215
|
return updateSnapshot(rootDir, async (previous) => {
|
|
9154
9216
|
const gitHash = await getCurrentGitHash(rootDir);
|
|
9155
9217
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9229,14 +9291,14 @@ async function saveSnapshotData(dir, features, flows, removeFeatures = [], remov
|
|
|
9229
9291
|
});
|
|
9230
9292
|
}
|
|
9231
9293
|
async function getImpact(dir, files) {
|
|
9232
|
-
const rootDir =
|
|
9294
|
+
const rootDir = path35.resolve(dir);
|
|
9233
9295
|
const { analyzeImpact: analyzeImpact2 } = await Promise.resolve().then(() => (init_impact(), impact_exports));
|
|
9234
9296
|
const result = await analyzeImpact2(rootDir, files);
|
|
9235
9297
|
return JSON.stringify(result, null, 2);
|
|
9236
9298
|
}
|
|
9237
9299
|
var VERIFY_DEFAULT_SAMPLE = 5;
|
|
9238
9300
|
async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
|
|
9239
|
-
const rootDir =
|
|
9301
|
+
const rootDir = path35.resolve(dir);
|
|
9240
9302
|
const snapshot = await loadSnapshot(rootDir);
|
|
9241
9303
|
if (!snapshot) {
|
|
9242
9304
|
return JSON.stringify({
|
|
@@ -9289,7 +9351,7 @@ async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
|
|
|
9289
9351
|
});
|
|
9290
9352
|
}
|
|
9291
9353
|
async function saveVerification(dir, verdicts) {
|
|
9292
|
-
const rootDir =
|
|
9354
|
+
const rootDir = path35.resolve(dir);
|
|
9293
9355
|
return updateSnapshot(rootDir, async (snapshot) => {
|
|
9294
9356
|
if (!snapshot) {
|
|
9295
9357
|
return { snapshot: null, result: JSON.stringify({ exists: false, stamped: [], hint: "No concept map exists. Build it and run verify_snapshot before recording verdicts." }) };
|
|
@@ -9363,16 +9425,16 @@ async function saveVerification(dir, verdicts) {
|
|
|
9363
9425
|
});
|
|
9364
9426
|
}
|
|
9365
9427
|
async function saveDecision(dir, input) {
|
|
9366
|
-
const rootDir =
|
|
9428
|
+
const rootDir = path35.resolve(dir);
|
|
9367
9429
|
const { upsertDecision: upsertDecision2 } = await Promise.resolve().then(() => (init_decisions(), decisions_exports));
|
|
9368
9430
|
const result = await upsertDecision2(rootDir, input);
|
|
9369
9431
|
return JSON.stringify(result);
|
|
9370
9432
|
}
|
|
9371
9433
|
async function reviewDecision2(dir, input) {
|
|
9372
|
-
return JSON.stringify(await reviewDecision(
|
|
9434
|
+
return JSON.stringify(await reviewDecision(path35.resolve(dir), input));
|
|
9373
9435
|
}
|
|
9374
9436
|
async function getContext(dir, task, files) {
|
|
9375
|
-
const rootDir =
|
|
9437
|
+
const rootDir = path35.resolve(dir);
|
|
9376
9438
|
const { assembleContext: assembleContext2 } = await Promise.resolve().then(() => (init_assemble(), assemble_exports));
|
|
9377
9439
|
const bundle = await assembleContext2(rootDir, task, files);
|
|
9378
9440
|
return JSON.stringify(bundle);
|
|
@@ -9421,7 +9483,7 @@ async function masonInit(dir, options = {}) {
|
|
|
9421
9483
|
return JSON.stringify(await setupProject2(dir, options), null, 2);
|
|
9422
9484
|
}
|
|
9423
9485
|
if (options.host) throw new Error("host applies only to mode: setup.");
|
|
9424
|
-
const rootDir =
|
|
9486
|
+
const rootDir = path35.resolve(dir);
|
|
9425
9487
|
const marker = await loadProjectMarker(rootDir);
|
|
9426
9488
|
const { loadSetup: loadSetup2 } = await Promise.resolve().then(() => (init_model(), model_exports));
|
|
9427
9489
|
const setup = await loadSetup2(rootDir);
|
|
@@ -9441,7 +9503,7 @@ async function masonInit(dir, options = {}) {
|
|
|
9441
9503
|
);
|
|
9442
9504
|
}
|
|
9443
9505
|
async function masonCompleteInit(dir, options = {}) {
|
|
9444
|
-
const rootDir =
|
|
9506
|
+
const rootDir = path35.resolve(dir);
|
|
9445
9507
|
const existing = await loadProjectMarker(rootDir);
|
|
9446
9508
|
const features = await loadProjectFeatures(rootDir);
|
|
9447
9509
|
const marker = {
|
|
@@ -9550,7 +9612,7 @@ async function masonSetConfluence(input) {
|
|
|
9550
9612
|
);
|
|
9551
9613
|
}
|
|
9552
9614
|
async function exportToConfluenceTool(dir, overrides) {
|
|
9553
|
-
const rootDir =
|
|
9615
|
+
const rootDir = path35.resolve(dir);
|
|
9554
9616
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports2));
|
|
9555
9617
|
const { exportToConfluence: exportToConfluence2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
9556
9618
|
const config = await loadConfig2();
|
|
@@ -9589,7 +9651,7 @@ function createMcpServer() {
|
|
|
9589
9651
|
const server = new McpServer(
|
|
9590
9652
|
{
|
|
9591
9653
|
name: "mason",
|
|
9592
|
-
version: "0.
|
|
9654
|
+
version: "0.19.0"
|
|
9593
9655
|
},
|
|
9594
9656
|
{
|
|
9595
9657
|
instructions: (setupNotice ? setupNotice + "\n\n" : "") + '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, stale or unknown map verification needs a fresh review, recorded failed verdicts need 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.'
|
|
@@ -9970,14 +10032,592 @@ function createMcpServer() {
|
|
|
9970
10032
|
);
|
|
9971
10033
|
return server;
|
|
9972
10034
|
}
|
|
9973
|
-
async function startMcpServer() {
|
|
10035
|
+
async function startMcpServer(onClose) {
|
|
9974
10036
|
const server = createMcpServer();
|
|
10037
|
+
if (onClose) server.server.onclose = onClose;
|
|
9975
10038
|
const transport = new StdioServerTransport();
|
|
9976
10039
|
await server.connect(transport);
|
|
9977
10040
|
}
|
|
9978
10041
|
|
|
10042
|
+
// src/distribution/updates.ts
|
|
10043
|
+
init_store();
|
|
10044
|
+
init_storage();
|
|
10045
|
+
import fs33 from "fs/promises";
|
|
10046
|
+
import path41 from "path";
|
|
10047
|
+
import { spawn as spawn3 } from "child_process";
|
|
10048
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
10049
|
+
import { z as z30 } from "zod";
|
|
10050
|
+
|
|
10051
|
+
// src/distribution/state.ts
|
|
10052
|
+
init_storage();
|
|
10053
|
+
import os5 from "os";
|
|
10054
|
+
import path37 from "path";
|
|
10055
|
+
import { fileURLToPath } from "url";
|
|
10056
|
+
import fs28 from "fs/promises";
|
|
10057
|
+
import { z as z26 } from "zod";
|
|
10058
|
+
|
|
10059
|
+
// src/distribution/path.ts
|
|
10060
|
+
import fs27 from "fs/promises";
|
|
10061
|
+
import os4 from "os";
|
|
10062
|
+
import path36 from "path";
|
|
10063
|
+
import { execFile as execFile12 } from "child_process";
|
|
10064
|
+
import { promisify as promisify12 } from "util";
|
|
10065
|
+
import { createHash as createHash7, randomUUID as randomUUID7 } from "crypto";
|
|
10066
|
+
import { z as z25 } from "zod";
|
|
10067
|
+
var execute2 = promisify12(execFile12);
|
|
10068
|
+
var pathChangeSchema = z25.discriminatedUnion("kind", [
|
|
10069
|
+
z25.object({ kind: z25.literal("profile"), file: z25.string(), block: z25.string(), created: z25.boolean() }),
|
|
10070
|
+
z25.object({ kind: z25.literal("windows"), entry: z25.string(), created: z25.boolean() })
|
|
10071
|
+
]);
|
|
10072
|
+
var quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
10073
|
+
async function configurePath(bin, previous, save, options = {}) {
|
|
10074
|
+
const env = options.env ?? process.env, platform = options.platform ?? process.platform;
|
|
10075
|
+
const changes = [...previous];
|
|
10076
|
+
const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
|
|
10077
|
+
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path36.posix.isAbsolute(entry) && path36.posix.resolve(entry) === path36.posix.resolve(bin));
|
|
10078
|
+
const setupHint = "To connect a project, run one of these inside its directory:\n Codex: mason setup --host codex\n Claude Code: mason setup --host claude";
|
|
10079
|
+
const message2 = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
|
|
10080
|
+
const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
|
|
10081
|
+
${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
10082
|
+
`}${setupHint}` });
|
|
10083
|
+
if (env.MASON_NO_MODIFY_PATH === "1") return manual("Automatic PATH setup is disabled (MASON_NO_MODIFY_PATH=1).");
|
|
10084
|
+
if (/[\r\n\0]/.test(bin) || bin.includes(platform === "win32" ? ";" : ":")) return manual("This installation path cannot be represented safely in PATH.");
|
|
10085
|
+
try {
|
|
10086
|
+
if (platform === "win32") {
|
|
10087
|
+
const current = await windowsPath("read");
|
|
10088
|
+
if (!hasWindowsEntry(current.value, bin)) {
|
|
10089
|
+
if (!changes.some((c) => c.kind === "windows" && c.entry === bin)) {
|
|
10090
|
+
changes.push({ kind: "windows", entry: bin, created: current.value === null });
|
|
10091
|
+
await save(changes);
|
|
10092
|
+
}
|
|
10093
|
+
await windowsPath("add", bin, current);
|
|
10094
|
+
}
|
|
10095
|
+
return { status: "configured", changes, message: message2 };
|
|
10096
|
+
}
|
|
10097
|
+
const shell = path36.basename(env.SHELL ?? os4.userInfo().shell ?? "");
|
|
10098
|
+
const userHome = options.userHome ?? os4.homedir();
|
|
10099
|
+
let profiles2;
|
|
10100
|
+
if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
|
|
10101
|
+
if (env.MASON_PROFILE) profiles2 = [path36.resolve(env.MASON_PROFILE)];
|
|
10102
|
+
else if (shell === "zsh") profiles2 = [path36.join(env.ZDOTDIR || userHome, ".zshrc")];
|
|
10103
|
+
else if (shell === "sh") profiles2 = [path36.join(userHome, ".profile")];
|
|
10104
|
+
else {
|
|
10105
|
+
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path36.join(userHome, name));
|
|
10106
|
+
let login = candidates[2];
|
|
10107
|
+
for (const file of candidates) if (await fs27.lstat(file).then(() => true, (error) => {
|
|
10108
|
+
if (error.code === "ENOENT") return false;
|
|
10109
|
+
throw error;
|
|
10110
|
+
})) {
|
|
10111
|
+
login = file;
|
|
10112
|
+
break;
|
|
10113
|
+
}
|
|
10114
|
+
profiles2 = [path36.join(userHome, ".bashrc"), login];
|
|
10115
|
+
}
|
|
10116
|
+
const marker = `# >>> mason PATH ${createHash7("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
10117
|
+
for (const profile of profiles2) {
|
|
10118
|
+
const stat = await fs27.lstat(profile).catch((error) => {
|
|
10119
|
+
if (error.code === "ENOENT") return null;
|
|
10120
|
+
throw error;
|
|
10121
|
+
});
|
|
10122
|
+
const file = stat?.isSymbolicLink() ? await fs27.realpath(profile) : path36.resolve(profile);
|
|
10123
|
+
const original = await fs27.readFile(file).catch((error) => {
|
|
10124
|
+
if (error.code === "ENOENT" && !stat) return null;
|
|
10125
|
+
throw error;
|
|
10126
|
+
});
|
|
10127
|
+
if (original && !Buffer.from(original.toString("utf8")).equals(original)) return manual(`Shell profile is not UTF-8: ${profile}.`);
|
|
10128
|
+
const text2 = original?.toString("utf8") ?? "";
|
|
10129
|
+
const owned = changes.find((c) => c.kind === "profile" && c.file === file);
|
|
10130
|
+
if (owned && text2.includes(owned.block)) continue;
|
|
10131
|
+
if (text2.includes(marker) || owned && text2.includes("# <<< mason PATH")) return manual(`Mason's PATH block was edited; retained ${profile}.`);
|
|
10132
|
+
const newline = text2.includes("\r\n") ? "\r\n" : "\n";
|
|
10133
|
+
const block = owned?.block ?? ("\n" + marker + '\ncase ":${PATH-}:" in\n *:' + quote(bin) + ":*) ;;\n *) export PATH=" + quote(bin) + ':"${PATH-}" ;;\nesac\n# <<< mason PATH <<<\n').replaceAll("\n", newline);
|
|
10134
|
+
if (!owned) {
|
|
10135
|
+
changes.push({ kind: "profile", file, block, created: original === null });
|
|
10136
|
+
await save(changes);
|
|
10137
|
+
}
|
|
10138
|
+
await fs27.mkdir(path36.dirname(file), { recursive: true });
|
|
10139
|
+
const handle = await fs27.open(file, original === null ? "ax+" : "a+", 384);
|
|
10140
|
+
try {
|
|
10141
|
+
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
10142
|
+
await handle.writeFile(block);
|
|
10143
|
+
await handle.sync();
|
|
10144
|
+
} finally {
|
|
10145
|
+
await handle.close();
|
|
10146
|
+
}
|
|
10147
|
+
}
|
|
10148
|
+
return { status: "configured", changes, message: message2 };
|
|
10149
|
+
} catch (error) {
|
|
10150
|
+
return manual(`Could not configure PATH: ${error instanceof Error ? error.message : String(error)}`);
|
|
10151
|
+
}
|
|
10152
|
+
}
|
|
10153
|
+
var normalizeWindowsEntry = (value) => path36.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
|
|
10154
|
+
var hasWindowsEntry = (value, entry, env = process.env) => {
|
|
10155
|
+
const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
|
|
10156
|
+
const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
|
|
10157
|
+
return (value ?? "").split(";").some((part) => normalizeWindowsEntry(expand(part)) === normalizeWindowsEntry(entry));
|
|
10158
|
+
};
|
|
10159
|
+
async function windowsPath(action, entry = "", expected, removeEmpty = false) {
|
|
10160
|
+
const payload = Buffer.from(JSON.stringify({ action, entry, expected, removeEmpty })).toString("base64");
|
|
10161
|
+
const script = `$ErrorActionPreference = 'Stop'
|
|
10162
|
+
[Console]::OutputEncoding = New-Object Text.UTF8Encoding($false)
|
|
10163
|
+
$p = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${payload}')) | ConvertFrom-Json
|
|
10164
|
+
$key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment')
|
|
10165
|
+
try {
|
|
10166
|
+
$value = $key.GetValue('Path', $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
|
|
10167
|
+
$kind = if ($null -eq $value) { $null } else { $key.GetValueKind('Path').ToString() }
|
|
10168
|
+
if ($null -ne $kind -and $kind -notin @('String','ExpandString')) { throw 'User PATH is not a string.' }
|
|
10169
|
+
if ($p.action -eq 'read') { @{ value = $value; kind = $kind } | ConvertTo-Json -Compress; exit 0 }
|
|
10170
|
+
if ($p.expected -and ($value -cne $p.expected.value -or $kind -cne $p.expected.kind)) { throw 'User PATH changed concurrently; retry the operation.' }
|
|
10171
|
+
$parts = if ($null -eq $value -or $value -eq '') { @() } else { @($value.Split(';')) }
|
|
10172
|
+
if ($p.action -eq 'add') { $next = (@($p.entry) + $parts) -join ';' }
|
|
10173
|
+
else { $next = (@($parts | Where-Object { $_ -cne $p.entry })) -join ';' }
|
|
10174
|
+
if ($next -cne $value) {
|
|
10175
|
+
$writeKind = if ($kind) { [Microsoft.Win32.RegistryValueKind]$kind } else { [Microsoft.Win32.RegistryValueKind]::ExpandString }
|
|
10176
|
+
if ($p.action -eq 'remove' -and $p.removeEmpty -and $next -eq '') { $key.DeleteValue('Path', $false) }
|
|
10177
|
+
else { $key.SetValue('Path', $next, $writeKind) }
|
|
10178
|
+
Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class MasonEnvironment { [DllImport("user32.dll", CharSet=CharSet.Unicode, SetLastError=true)] public static extern IntPtr SendMessageTimeout(IntPtr h, uint m, UIntPtr w, string l, uint f, uint t, out UIntPtr r); }'
|
|
10179
|
+
$result = [UIntPtr]::Zero
|
|
10180
|
+
[void][MasonEnvironment]::SendMessageTimeout([IntPtr]0xffff, 0x1a, [UIntPtr]::Zero, 'Environment', 2, 1000, [ref]$result)
|
|
10181
|
+
}
|
|
10182
|
+
@{ value = $next; kind = $kind } | ConvertTo-Json -Compress
|
|
10183
|
+
} finally { $key.Dispose() }
|
|
10184
|
+
`;
|
|
10185
|
+
const { stdout } = await execute2("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { windowsHide: true, timeout: 15e3, maxBuffer: 1024 * 1024 });
|
|
10186
|
+
return JSON.parse(stdout.replace(/^\uFEFF/, "").trim());
|
|
10187
|
+
}
|
|
10188
|
+
|
|
10189
|
+
// src/distribution/state.ts
|
|
10190
|
+
var releaseVersion = z26.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
|
|
10191
|
+
var bundleId = z26.string().regex(/^[a-f0-9]{24}$/);
|
|
10192
|
+
var updatePolicySchema = z26.object({
|
|
10193
|
+
enabled: z26.boolean(),
|
|
10194
|
+
revision: z26.string(),
|
|
10195
|
+
pinnedVersion: releaseVersion.optional(),
|
|
10196
|
+
mirror: z26.string().optional()
|
|
10197
|
+
});
|
|
10198
|
+
var recordSchema3 = z26.object({
|
|
10199
|
+
format: z26.literal(1),
|
|
10200
|
+
home: z26.string(),
|
|
10201
|
+
bin: z26.string(),
|
|
10202
|
+
current: bundleId,
|
|
10203
|
+
version: releaseVersion,
|
|
10204
|
+
launchers: z26.record(z26.string()),
|
|
10205
|
+
previousLaunchers: z26.record(z26.string()).optional(),
|
|
10206
|
+
pathChanges: z26.array(pathChangeSchema).optional(),
|
|
10207
|
+
updates: updatePolicySchema.optional(),
|
|
10208
|
+
previous: z26.object({ id: bundleId, version: releaseVersion }).optional(),
|
|
10209
|
+
pending: z26.object({ id: bundleId, version: releaseVersion, policyRevision: z26.string() }).optional()
|
|
10210
|
+
});
|
|
10211
|
+
async function readInstallation(home) {
|
|
10212
|
+
const raw = await readStoreJson(home, "install.json");
|
|
10213
|
+
if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
10214
|
+
const record = recordSchema3.parse(raw);
|
|
10215
|
+
if (record.home !== await fs28.realpath(home)) throw new Error("Invalid standalone installation receipt.");
|
|
10216
|
+
return record;
|
|
10217
|
+
}
|
|
10218
|
+
function standaloneLocation() {
|
|
10219
|
+
const bundle = path37.resolve(path37.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
10220
|
+
const home = path37.dirname(path37.dirname(bundle));
|
|
10221
|
+
if (path37.dirname(bundle) !== path37.join(home, "versions") || !/^[a-f0-9]{24}$/.test(path37.basename(bundle))) {
|
|
10222
|
+
return null;
|
|
10223
|
+
}
|
|
10224
|
+
return { home, bundle };
|
|
10225
|
+
}
|
|
10226
|
+
async function currentInstallation() {
|
|
10227
|
+
const location2 = standaloneLocation();
|
|
10228
|
+
if (!location2) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
10229
|
+
const { home, bundle } = location2;
|
|
10230
|
+
const record = await readInstallation(home);
|
|
10231
|
+
return { home, bundle, record };
|
|
10232
|
+
}
|
|
10233
|
+
function automaticUpdatesAllowed(record, env = process.env) {
|
|
10234
|
+
return record.updates?.enabled === true && !record.updates.pinnedVersion && !record.updates.mirror && !env.CI && env.MASON_NO_AUTO_UPDATE !== "1" && !env.MASON_RELEASE_BASE && !env.MASON_VERSION;
|
|
10235
|
+
}
|
|
10236
|
+
function newerStableVersion(candidate, installed) {
|
|
10237
|
+
if (!/^\d+\.\d+\.\d+$/.test(candidate)) return false;
|
|
10238
|
+
const a = candidate.split(".").map(BigInt), b = installed.split("-")[0].split(".").map(BigInt);
|
|
10239
|
+
for (let i = 0; i < 3; i++) {
|
|
10240
|
+
if (a[i] !== b[i]) return a[i] > b[i];
|
|
10241
|
+
}
|
|
10242
|
+
return installed.includes("-");
|
|
10243
|
+
}
|
|
10244
|
+
|
|
10245
|
+
// src/distribution/bundle.ts
|
|
10246
|
+
init_storage();
|
|
10247
|
+
import fs29 from "fs/promises";
|
|
10248
|
+
import path38 from "path";
|
|
10249
|
+
import { createHash as createHash8 } from "crypto";
|
|
10250
|
+
import { z as z27 } from "zod";
|
|
10251
|
+
var targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
10252
|
+
var digest2 = z27.string().regex(/^[a-f0-9]{64}$/);
|
|
10253
|
+
var bundleSchema = z27.object({
|
|
10254
|
+
format: z27.literal(1),
|
|
10255
|
+
version: z27.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
10256
|
+
target: z27.enum(targets),
|
|
10257
|
+
nodeVersion: z27.string(),
|
|
10258
|
+
files: z27.record(digest2)
|
|
10259
|
+
}).strict();
|
|
10260
|
+
var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
|
|
10261
|
+
async function readBundle(root) {
|
|
10262
|
+
const bytes = await fs29.readFile(await storePath(root, "bundle.json"));
|
|
10263
|
+
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
10264
|
+
const windows = manifest.target.startsWith("win32-");
|
|
10265
|
+
for (const file of Object.keys(manifest.files)) {
|
|
10266
|
+
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path38.isAbsolute(file)) {
|
|
10267
|
+
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
10268
|
+
}
|
|
10269
|
+
}
|
|
10270
|
+
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"]) {
|
|
10271
|
+
if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
|
|
10272
|
+
}
|
|
10273
|
+
return { manifest, manifestHash: sha256(bytes) };
|
|
10274
|
+
}
|
|
10275
|
+
async function verifyBundle(root, expectedHash) {
|
|
10276
|
+
const bundle = await readBundle(root);
|
|
10277
|
+
if (expectedHash && bundle.manifestHash !== expectedHash) throw new Error("Mason bundle manifest changed.");
|
|
10278
|
+
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.");
|
|
10279
|
+
const files = Object.keys(bundle.manifest.files);
|
|
10280
|
+
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
10281
|
+
if (sha256(await fs29.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
10282
|
+
}));
|
|
10283
|
+
return bundle;
|
|
10284
|
+
}
|
|
10285
|
+
|
|
10286
|
+
// src/distribution/runtime.ts
|
|
10287
|
+
init_store();
|
|
10288
|
+
init_storage();
|
|
10289
|
+
import fs30 from "fs/promises";
|
|
10290
|
+
import { unlinkSync } from "fs";
|
|
10291
|
+
import os6 from "os";
|
|
10292
|
+
import path39 from "path";
|
|
10293
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
10294
|
+
import { z as z28 } from "zod";
|
|
10295
|
+
var leaseSchema = z28.object({ pid: z28.number().int().positive(), host: z28.string(), bundle: z28.string().regex(/^[a-f0-9]{24}$/) });
|
|
10296
|
+
|
|
10297
|
+
// src/distribution/install.ts
|
|
10298
|
+
import fs32 from "fs/promises";
|
|
10299
|
+
import os8 from "os";
|
|
10300
|
+
import path40 from "path";
|
|
10301
|
+
import { execFile as execFile13, spawn as spawn2 } from "child_process";
|
|
10302
|
+
import { promisify as promisify13 } from "util";
|
|
10303
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
10304
|
+
init_storage();
|
|
10305
|
+
init_store();
|
|
10306
|
+
|
|
10307
|
+
// src/distribution/sessions.ts
|
|
10308
|
+
init_storage();
|
|
10309
|
+
import fs31 from "fs/promises";
|
|
10310
|
+
import os7 from "os";
|
|
10311
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
10312
|
+
import { z as z29 } from "zod";
|
|
10313
|
+
var leaseSchema2 = z29.object({ pid: z29.number().int().positive(), host: z29.string() });
|
|
10314
|
+
async function hasRunningMcp(home) {
|
|
10315
|
+
const directory = await storePath(home, "sessions");
|
|
10316
|
+
const files = await fs31.readdir(directory).catch((error) => {
|
|
10317
|
+
if (error.code === "ENOENT") return [];
|
|
10318
|
+
throw error;
|
|
10319
|
+
});
|
|
10320
|
+
for (const name of files) {
|
|
10321
|
+
if (!/^[a-f0-9-]+\.json$/.test(name)) return true;
|
|
10322
|
+
const relative = "sessions/" + name;
|
|
10323
|
+
try {
|
|
10324
|
+
const raw = await readStoreJson(home, relative);
|
|
10325
|
+
if (raw === null) continue;
|
|
10326
|
+
const lease = leaseSchema2.parse(raw);
|
|
10327
|
+
if (lease.host !== os7.hostname()) return true;
|
|
10328
|
+
try {
|
|
10329
|
+
process.kill(lease.pid, 0);
|
|
10330
|
+
return true;
|
|
10331
|
+
} catch (error) {
|
|
10332
|
+
if (error.code !== "ESRCH") return true;
|
|
10333
|
+
}
|
|
10334
|
+
await fs31.rm(await storePath(home, relative), { force: true });
|
|
10335
|
+
} catch {
|
|
10336
|
+
return true;
|
|
10337
|
+
}
|
|
10338
|
+
}
|
|
10339
|
+
return false;
|
|
10340
|
+
}
|
|
10341
|
+
async function registerMcp(home) {
|
|
10342
|
+
const name = `sessions/${randomUUID9()}.json`;
|
|
10343
|
+
await writeStoreJson(home, name, { pid: process.pid, host: os7.hostname() });
|
|
10344
|
+
return async () => {
|
|
10345
|
+
await fs31.rm(await storePath(home, name), { force: true }).catch(() => {
|
|
10346
|
+
});
|
|
10347
|
+
};
|
|
10348
|
+
}
|
|
10349
|
+
|
|
10350
|
+
// src/distribution/install.ts
|
|
10351
|
+
var shQuote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
10352
|
+
var psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
|
|
10353
|
+
var WINDOWS_BATCH_LAUNCHER = '@echo off\r\ngoto #_mason_handoff_# 2>NUL || title %COMSPEC% & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0mason-launcher.ps1" %*\r\n';
|
|
10354
|
+
var launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
|
|
10355
|
+
async function replace(file, content2, mode = 384) {
|
|
10356
|
+
const temp = file + ".tmp-" + randomUUID10();
|
|
10357
|
+
try {
|
|
10358
|
+
await fs32.writeFile(temp, content2, { mode, flag: "wx" });
|
|
10359
|
+
await fs32.rename(temp, file);
|
|
10360
|
+
} finally {
|
|
10361
|
+
await fs32.rm(temp, { force: true });
|
|
10362
|
+
}
|
|
10363
|
+
}
|
|
10364
|
+
function assertUpdatePolicy(record, version, revision) {
|
|
10365
|
+
if (!record || !automaticUpdatesAllowed(record, { ...process.env, MASON_VERSION: "" }) || record.updates?.revision !== revision || !newerStableVersion(version, record.version)) {
|
|
10366
|
+
throw new Error("Update policy or installed version changed; download was not activated.");
|
|
10367
|
+
}
|
|
10368
|
+
}
|
|
10369
|
+
async function installLocked2(home, source, bundle, progress, options, alreadyStaged = false) {
|
|
10370
|
+
const raw = await readStoreJson(home, "install.json");
|
|
10371
|
+
const previous = raw === null ? null : recordSchema3.parse(raw);
|
|
10372
|
+
if (options.stageOnly) assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
|
|
10373
|
+
if (!previous) for (const file of await fs32.readdir(home)) {
|
|
10374
|
+
if (file === ".install-lock") continue;
|
|
10375
|
+
const directory = path40.join(home, file);
|
|
10376
|
+
const stat = await fs32.lstat(directory);
|
|
10377
|
+
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs32.readdir(directory)).length) {
|
|
10378
|
+
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
10379
|
+
}
|
|
10380
|
+
}
|
|
10381
|
+
const selectedBin = path40.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path40.join(home, "bin") : path40.join(os8.homedir(), ".local/bin")));
|
|
10382
|
+
await fs32.mkdir(selectedBin, { recursive: true });
|
|
10383
|
+
const bin = await fs32.realpath(selectedBin);
|
|
10384
|
+
if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
|
|
10385
|
+
const id = bundle.manifestHash.slice(0, 24);
|
|
10386
|
+
const destination = await storePath(home, "versions/" + id);
|
|
10387
|
+
if (options.stageOnly) {
|
|
10388
|
+
const receipt2 = { ...previous, pending: { id, version: bundle.manifest.version, policyRevision: options.policyRevision } };
|
|
10389
|
+
await writeStoreJson(home, "install.json", receipt2);
|
|
10390
|
+
return { home, bin, version: bundle.manifest.version, previousVersion: previous.version, path: { message: "Update staged for a future MCP launch." } };
|
|
10391
|
+
}
|
|
10392
|
+
const launchers = process.platform === "win32" ? {
|
|
10393
|
+
"mason.cmd": WINDOWS_BATCH_LAUNCHER,
|
|
10394
|
+
"mason-launcher.ps1": `$ErrorActionPreference = 'Stop'
|
|
10395
|
+
$previousToken = $env:MASON_UNINSTALL_TOKEN
|
|
10396
|
+
$token = [Guid]::NewGuid().ToString()
|
|
10397
|
+
$env:MASON_UNINSTALL_TOKEN = $token
|
|
10398
|
+
try {
|
|
10399
|
+
& ${psQuote(path40.join(destination, "node.exe"))} ${psQuote(path40.join(destination, "app/dist/mason.js"))} @args
|
|
10400
|
+
$code = $LASTEXITCODE
|
|
10401
|
+
if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
|
|
10402
|
+
if ([IO.File]::ReadAllText(${psQuote(path40.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
|
|
10403
|
+
Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
|
|
10404
|
+
Write-Output ${psQuote("Removed standalone installation: " + home)}
|
|
10405
|
+
}
|
|
10406
|
+
exit $code
|
|
10407
|
+
} catch { [Console]::Error.WriteLine($_.Exception.Message); exit 2 }
|
|
10408
|
+
finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
|
|
10409
|
+
`
|
|
10410
|
+
} : { mason: `#!/bin/sh
|
|
10411
|
+
exec ${shQuote(path40.join(destination, "node"))} ${shQuote(path40.join(destination, "app/dist/mason.js"))} "$@"
|
|
10412
|
+
` };
|
|
10413
|
+
for (const [name, content2] of Object.entries(launchers)) {
|
|
10414
|
+
const file = await storePath(bin, name);
|
|
10415
|
+
const old = await fs32.readFile(file, "utf8").catch((error) => {
|
|
10416
|
+
if (error.code === "ENOENT") return null;
|
|
10417
|
+
throw error;
|
|
10418
|
+
});
|
|
10419
|
+
if (old !== null && old !== previous?.launchers[name] && old !== previous?.previousLaunchers?.[name] && old !== content2) throw new Error("Refusing to replace an unrelated or edited launcher: " + file);
|
|
10420
|
+
}
|
|
10421
|
+
const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
|
|
10422
|
+
const checkRetired = async (name, expected) => {
|
|
10423
|
+
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
10424
|
+
const actual = await fs32.readFile(await storePath(bin, name), "utf8").catch((error) => {
|
|
10425
|
+
if (error.code === "ENOENT") return null;
|
|
10426
|
+
throw error;
|
|
10427
|
+
});
|
|
10428
|
+
if (actual !== null && actual !== expected) throw new Error("Retired launcher was edited; retained: " + name);
|
|
10429
|
+
};
|
|
10430
|
+
for (const [name, expected] of retired) await checkRetired(name, expected);
|
|
10431
|
+
progress?.step(`Installing Mason ${bundle.manifest.version}`);
|
|
10432
|
+
const updates = options.preservePolicy && previous?.updates ? { ...previous.updates } : {
|
|
10433
|
+
...previous?.updates ?? { enabled: !previous && !process.env.CI && process.env.MASON_NO_AUTO_UPDATE !== "1" },
|
|
10434
|
+
revision: randomUUID10(),
|
|
10435
|
+
pinnedVersion: process.env.MASON_VERSION?.replace(/^v/, "") || void 0,
|
|
10436
|
+
mirror: process.env.MASON_RELEASE_BASE || previous?.updates?.mirror
|
|
10437
|
+
};
|
|
10438
|
+
if (options.rollback) {
|
|
10439
|
+
updates.pinnedVersion = bundle.manifest.version;
|
|
10440
|
+
updates.revision = randomUUID10();
|
|
10441
|
+
}
|
|
10442
|
+
const receipt = {
|
|
10443
|
+
format: 1,
|
|
10444
|
+
home,
|
|
10445
|
+
bin,
|
|
10446
|
+
current: id,
|
|
10447
|
+
version: bundle.manifest.version,
|
|
10448
|
+
launchers,
|
|
10449
|
+
pathChanges: previous?.pathChanges ?? [],
|
|
10450
|
+
updates,
|
|
10451
|
+
previous: previous && previous.current !== id ? { id: previous.current, version: previous.version } : previous?.previous
|
|
10452
|
+
};
|
|
10453
|
+
await writeStoreJson(home, "install.json", previous ? { ...previous, previousLaunchers: launchers } : receipt);
|
|
10454
|
+
try {
|
|
10455
|
+
if (!alreadyStaged) await copyBundle(home, source, destination, bundle);
|
|
10456
|
+
for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
10457
|
+
for (const [name, expected] of retired) {
|
|
10458
|
+
await checkRetired(name, expected);
|
|
10459
|
+
await fs32.rm(await storePath(bin, name), { force: true });
|
|
10460
|
+
}
|
|
10461
|
+
await writeStoreJson(home, "install.json", receipt);
|
|
10462
|
+
} catch (error) {
|
|
10463
|
+
if (previous) {
|
|
10464
|
+
for (const [name, content2] of Object.entries(previous.launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
10465
|
+
await writeStoreJson(home, "install.json", previous);
|
|
10466
|
+
}
|
|
10467
|
+
throw error;
|
|
10468
|
+
}
|
|
10469
|
+
if (options.preservePolicy) return { home, bin, version: receipt.version, previousVersion: previous?.version, path: { message: "Installation selected." } };
|
|
10470
|
+
progress?.step("Checking terminal command availability");
|
|
10471
|
+
const pathResult = await configurePath(bin, receipt.pathChanges ?? [], async (changes) => {
|
|
10472
|
+
receipt.pathChanges = changes;
|
|
10473
|
+
await writeStoreJson(home, "install.json", receipt);
|
|
10474
|
+
});
|
|
10475
|
+
return { home, bin, version: bundle.manifest.version, previousVersion: previous?.version, path: pathResult };
|
|
10476
|
+
}
|
|
10477
|
+
async function copyBundle(home, source, destination, bundle) {
|
|
10478
|
+
const exists = await fs32.lstat(destination).then(() => true, (error) => {
|
|
10479
|
+
if (error.code === "ENOENT") return false;
|
|
10480
|
+
throw error;
|
|
10481
|
+
});
|
|
10482
|
+
if (exists) {
|
|
10483
|
+
await verifyBundle(destination, bundle.manifestHash);
|
|
10484
|
+
return;
|
|
10485
|
+
}
|
|
10486
|
+
const stage = await storePath(home, "versions/.install-" + randomUUID10(), true);
|
|
10487
|
+
await fs32.mkdir(stage);
|
|
10488
|
+
try {
|
|
10489
|
+
for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
|
|
10490
|
+
const from = await storePath(source, file), to = await storePath(stage, file, true);
|
|
10491
|
+
await fs32.copyFile(from, to);
|
|
10492
|
+
await fs32.chmod(to, (await fs32.stat(from)).mode & 511);
|
|
10493
|
+
}
|
|
10494
|
+
await verifyBundle(stage, bundle.manifestHash);
|
|
10495
|
+
try {
|
|
10496
|
+
await fs32.rename(stage, destination);
|
|
10497
|
+
} catch (error) {
|
|
10498
|
+
try {
|
|
10499
|
+
await verifyBundle(destination, bundle.manifestHash);
|
|
10500
|
+
} catch {
|
|
10501
|
+
throw error;
|
|
10502
|
+
}
|
|
10503
|
+
}
|
|
10504
|
+
} finally {
|
|
10505
|
+
await fs32.rm(stage, { recursive: true, force: true });
|
|
10506
|
+
}
|
|
10507
|
+
}
|
|
10508
|
+
async function prepareStandaloneMcp(home) {
|
|
10509
|
+
const before = await readInstallation(home), candidate = before.pending;
|
|
10510
|
+
let verified, verificationError;
|
|
10511
|
+
if (candidate && automaticUpdatesAllowed(before) && !await hasRunningMcp(home)) {
|
|
10512
|
+
try {
|
|
10513
|
+
verified = await verifyBundle(await storePath(home, "versions/" + candidate.id));
|
|
10514
|
+
if (verified.manifestHash.slice(0, 24) !== candidate.id || verified.manifest.version !== candidate.version) throw new Error("Staged update changed.");
|
|
10515
|
+
} catch (error) {
|
|
10516
|
+
verificationError = error;
|
|
10517
|
+
}
|
|
10518
|
+
}
|
|
10519
|
+
return withLock(home, ".install-lock", async () => {
|
|
10520
|
+
let record = await readInstallation(home);
|
|
10521
|
+
const pending = record.pending;
|
|
10522
|
+
if (pending && candidate?.id === pending.id && (verified || verificationError) && automaticUpdatesAllowed(record) && pending.policyRevision === record.updates?.revision && newerStableVersion(pending.version, record.version) && !await hasRunningMcp(home)) {
|
|
10523
|
+
try {
|
|
10524
|
+
if (verificationError) throw verificationError;
|
|
10525
|
+
const source = await storePath(home, "versions/" + pending.id);
|
|
10526
|
+
await installLocked2(home, source, verified, void 0, { preservePolicy: true }, true);
|
|
10527
|
+
record = await readInstallation(home);
|
|
10528
|
+
} catch (error) {
|
|
10529
|
+
record = await readInstallation(home);
|
|
10530
|
+
delete record.pending;
|
|
10531
|
+
await writeStoreJson(home, "install.json", record);
|
|
10532
|
+
await writeStoreJson(home, "update-status.json", { lastAttempt: Date.now(), error: String(error) });
|
|
10533
|
+
}
|
|
10534
|
+
}
|
|
10535
|
+
const release = await registerMcp(home);
|
|
10536
|
+
return { bundle: await storePath(home, "versions/" + record.current), release };
|
|
10537
|
+
});
|
|
10538
|
+
}
|
|
10539
|
+
|
|
10540
|
+
// src/distribution/updates.ts
|
|
10541
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
10542
|
+
var RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
10543
|
+
var releaseListSchema = z30.array(z30.object({ tag_name: z30.string(), draft: z30.boolean(), prerelease: z30.boolean(), published_at: z30.string().nullable() }));
|
|
10544
|
+
var updateManifestSchema = z30.object({
|
|
10545
|
+
format: z30.literal(1),
|
|
10546
|
+
version: z30.string().regex(/^\d+\.\d+\.\d+$/),
|
|
10547
|
+
publishedAt: z30.string().datetime(),
|
|
10548
|
+
assets: z30.record(z30.string().regex(/^[a-f0-9]{64}$/))
|
|
10549
|
+
}).strict();
|
|
10550
|
+
var statusSchema2 = z30.object({ lastAttempt: z30.number(), availableVersion: z30.string().optional(), error: z30.string().optional() });
|
|
10551
|
+
async function scheduleAutomaticUpdate() {
|
|
10552
|
+
try {
|
|
10553
|
+
const { home, record } = await currentInstallation();
|
|
10554
|
+
if (!automaticUpdatesAllowed(record)) return;
|
|
10555
|
+
const status = statusSchema2.safeParse(await readStoreJson(home, "update-status.json"));
|
|
10556
|
+
const now = Date.now();
|
|
10557
|
+
if (status.success && status.data.lastAttempt <= now && now - status.data.lastAttempt < CHECK_INTERVAL_MS) return;
|
|
10558
|
+
const bundle = await storePath(home, "versions/" + record.current);
|
|
10559
|
+
const child = spawn3(
|
|
10560
|
+
path41.join(bundle, process.platform === "win32" ? "node.exe" : "node"),
|
|
10561
|
+
[path41.join(bundle, "app/dist/mason.js"), "internal-update"],
|
|
10562
|
+
{ cwd: home, env: process.env, stdio: "ignore", detached: true, windowsHide: true }
|
|
10563
|
+
);
|
|
10564
|
+
child.on("error", () => {
|
|
10565
|
+
});
|
|
10566
|
+
child.unref();
|
|
10567
|
+
} catch {
|
|
10568
|
+
}
|
|
10569
|
+
}
|
|
10570
|
+
async function startMcpWithUpdates(start) {
|
|
10571
|
+
const current = standaloneLocation();
|
|
10572
|
+
if (!current) {
|
|
10573
|
+
await start();
|
|
10574
|
+
return;
|
|
10575
|
+
}
|
|
10576
|
+
const session = await prepareStandaloneMcp(current.home);
|
|
10577
|
+
if (session.bundle !== current.bundle) {
|
|
10578
|
+
const child = spawn3(
|
|
10579
|
+
path41.join(session.bundle, process.platform === "win32" ? "node.exe" : "node"),
|
|
10580
|
+
[path41.join(session.bundle, "app/dist/mason-mcp.js"), ...process.argv.slice(2)],
|
|
10581
|
+
{ stdio: "inherit", windowsHide: true }
|
|
10582
|
+
);
|
|
10583
|
+
const interrupt = () => {
|
|
10584
|
+
child.kill("SIGINT");
|
|
10585
|
+
}, terminate = () => {
|
|
10586
|
+
child.kill("SIGTERM");
|
|
10587
|
+
};
|
|
10588
|
+
process.on("SIGINT", interrupt);
|
|
10589
|
+
process.on("SIGTERM", terminate);
|
|
10590
|
+
try {
|
|
10591
|
+
process.exitCode = await new Promise((resolve2, reject) => {
|
|
10592
|
+
child.on("error", reject);
|
|
10593
|
+
child.on("exit", (code) => resolve2(code ?? 1));
|
|
10594
|
+
});
|
|
10595
|
+
} finally {
|
|
10596
|
+
process.off("SIGINT", interrupt);
|
|
10597
|
+
process.off("SIGTERM", terminate);
|
|
10598
|
+
await session.release();
|
|
10599
|
+
}
|
|
10600
|
+
return;
|
|
10601
|
+
}
|
|
10602
|
+
const timer = setInterval(() => {
|
|
10603
|
+
void scheduleAutomaticUpdate();
|
|
10604
|
+
}, 60 * 60 * 1e3);
|
|
10605
|
+
timer.unref();
|
|
10606
|
+
const stop = () => {
|
|
10607
|
+
clearInterval(timer);
|
|
10608
|
+
void session?.release();
|
|
10609
|
+
};
|
|
10610
|
+
try {
|
|
10611
|
+
await start(stop);
|
|
10612
|
+
void scheduleAutomaticUpdate();
|
|
10613
|
+
} catch (error) {
|
|
10614
|
+
stop();
|
|
10615
|
+
throw error;
|
|
10616
|
+
}
|
|
10617
|
+
}
|
|
10618
|
+
|
|
9979
10619
|
// bin/mason-mcp.ts
|
|
9980
|
-
startMcpServer
|
|
10620
|
+
startMcpWithUpdates(startMcpServer).catch((err) => {
|
|
9981
10621
|
process.stderr.write(`Mason MCP server error: ${err}
|
|
9982
10622
|
`);
|
|
9983
10623
|
process.exit(1);
|