mason-context 0.17.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/mason-audit.js +14 -4
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-auto.js +155 -89
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-mcp.js +2656 -2635
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason.js +216 -165
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/dist/mason.js
CHANGED
|
@@ -844,7 +844,7 @@ async function readAuditInput(root, file) {
|
|
|
844
844
|
if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
|
|
845
845
|
return value;
|
|
846
846
|
} catch (error) {
|
|
847
|
-
if (error.code
|
|
847
|
+
if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return null;
|
|
848
848
|
throw error;
|
|
849
849
|
}
|
|
850
850
|
}
|
|
@@ -1094,15 +1094,17 @@ function pathClaimScope(doc, claim) {
|
|
|
1094
1094
|
};
|
|
1095
1095
|
}
|
|
1096
1096
|
async function pathExists(root, file) {
|
|
1097
|
-
const absolute = await auditInputPath(root, file);
|
|
1098
1097
|
try {
|
|
1099
|
-
await fs6.access(
|
|
1098
|
+
await fs6.access(await auditInputPath(root, file));
|
|
1100
1099
|
return true;
|
|
1101
1100
|
} catch (error) {
|
|
1102
|
-
if (error.code
|
|
1101
|
+
if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return false;
|
|
1103
1102
|
throw error;
|
|
1104
1103
|
}
|
|
1105
1104
|
}
|
|
1105
|
+
function gitMetadataPath(file) {
|
|
1106
|
+
return file.split("/").includes(".git");
|
|
1107
|
+
}
|
|
1106
1108
|
function optionalMasonPath(file) {
|
|
1107
1109
|
return file.split("/").includes(".mason");
|
|
1108
1110
|
}
|
|
@@ -1341,6 +1343,14 @@ async function checkDeletedReferences(ctx) {
|
|
|
1341
1343
|
continue;
|
|
1342
1344
|
}
|
|
1343
1345
|
if (scope.candidates.some(optionalMasonPath)) continue;
|
|
1346
|
+
if (scope.candidates.some(gitMetadataPath)) {
|
|
1347
|
+
result.skipped.push({
|
|
1348
|
+
check: "deleted-reference",
|
|
1349
|
+
doc: doc.path,
|
|
1350
|
+
reason: `Reference ${claim.path} names Git metadata, whose layout varies between checkouts; it was not verified as a repository file.`
|
|
1351
|
+
});
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1344
1354
|
if ((await Promise.all(scope.candidates.map((file) => pathExists(ctx.root, file)))).some(Boolean)) continue;
|
|
1345
1355
|
const candidates = await Promise.all(scope.candidates.map(async (file) => ({
|
|
1346
1356
|
file,
|
|
@@ -2860,14 +2870,6 @@ var init_repair = __esm({
|
|
|
2860
2870
|
});
|
|
2861
2871
|
|
|
2862
2872
|
// src/automation/evidence.ts
|
|
2863
|
-
var evidence_exports = {};
|
|
2864
|
-
__export(evidence_exports, {
|
|
2865
|
-
checkCache: () => checkCache,
|
|
2866
|
-
git: () => git3,
|
|
2867
|
-
hash: () => hash,
|
|
2868
|
-
readInputs: () => readInputs,
|
|
2869
|
-
workspace: () => workspace
|
|
2870
|
-
});
|
|
2871
2873
|
import fs13 from "fs/promises";
|
|
2872
2874
|
import path20 from "path";
|
|
2873
2875
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -2913,6 +2915,7 @@ async function readInputs(root) {
|
|
|
2913
2915
|
docContents.push([file, text2]);
|
|
2914
2916
|
for (const claim of text2 ? extractClaims(text2).paths : []) {
|
|
2915
2917
|
const scope = pathClaimScope(file, claim);
|
|
2918
|
+
if (scope?.candidates.some(gitMetadataPath)) continue;
|
|
2916
2919
|
for (const candidate of scope?.candidates ?? []) {
|
|
2917
2920
|
if (optionalMasonPath(candidate)) continue;
|
|
2918
2921
|
claims.push([candidate, await pathExists(root, candidate), await pathExists(root, path20.posix.dirname(candidate))]);
|
|
@@ -2993,7 +2996,7 @@ var init_evidence = __esm({
|
|
|
2993
2996
|
init_storage();
|
|
2994
2997
|
init_advisory_review();
|
|
2995
2998
|
exec7 = promisify9(execFile9);
|
|
2996
|
-
engineVersion = true ? "0.17.
|
|
2999
|
+
engineVersion = true ? "0.17.1" : "development";
|
|
2997
3000
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
2998
3001
|
content = readAuditInput;
|
|
2999
3002
|
cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
|
|
@@ -3514,6 +3517,39 @@ var init_runtime = __esm({
|
|
|
3514
3517
|
}
|
|
3515
3518
|
});
|
|
3516
3519
|
|
|
3520
|
+
// src/automation/hook-root.ts
|
|
3521
|
+
import fs15 from "fs/promises";
|
|
3522
|
+
import path21 from "path";
|
|
3523
|
+
async function repositoryRoot(dir) {
|
|
3524
|
+
if (!path21.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
|
|
3525
|
+
try {
|
|
3526
|
+
return await fs15.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
|
|
3527
|
+
} catch (error) {
|
|
3528
|
+
if (error.code === "ENOENT" || /not a git repository/.test(String(error.stderr))) return null;
|
|
3529
|
+
throw error;
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
async function resolveHookRoot(host, cwd, options = {}) {
|
|
3533
|
+
const current = await repositoryRoot(cwd);
|
|
3534
|
+
if (current) return current;
|
|
3535
|
+
const projectDir = options.projectDir ?? (host === "claude" ? process.env.CLAUDE_PROJECT_DIR : void 0);
|
|
3536
|
+
const projectRoot = projectDir ? await repositoryRoot(projectDir) : null;
|
|
3537
|
+
if (projectDir && !projectRoot) throw new Error("Hook input is outside Git and the host project is no longer a Git repository. Restore its history before running mason check.");
|
|
3538
|
+
const launchRoot = await repositoryRoot(options.launchCwd ?? process.cwd());
|
|
3539
|
+
const roots = [...new Set([launchRoot, projectRoot].filter((root) => root !== null))];
|
|
3540
|
+
if (!roots.length) throw new Error("Hook input is outside a Git repository and no project root is available. Return to the project and run mason check.");
|
|
3541
|
+
if (roots.length > 1) throw new Error("Hook input is outside a Git repository and project roots disagree. Return to the intended checkout and run mason check; verification was not established.");
|
|
3542
|
+
const worktrees = (await git3(roots[0], "worktree", "list", "--porcelain", "-z")).split("\0").filter((field) => field.startsWith("worktree "));
|
|
3543
|
+
if (worktrees.length !== 1) throw new Error("Hook input is outside a Git repository with multiple worktrees. Return to the intended worktree and run mason check; the active checkout is unknown.");
|
|
3544
|
+
return roots[0];
|
|
3545
|
+
}
|
|
3546
|
+
var init_hook_root = __esm({
|
|
3547
|
+
"src/automation/hook-root.ts"() {
|
|
3548
|
+
"use strict";
|
|
3549
|
+
init_evidence();
|
|
3550
|
+
}
|
|
3551
|
+
});
|
|
3552
|
+
|
|
3517
3553
|
// src/setup/model.ts
|
|
3518
3554
|
import { z as z11 } from "zod";
|
|
3519
3555
|
async function loadSetup(root) {
|
|
@@ -3564,13 +3600,14 @@ __export(launcher_exports, {
|
|
|
3564
3600
|
executingVersion: () => executingVersion,
|
|
3565
3601
|
globalCommand: () => globalCommand,
|
|
3566
3602
|
hookCommand: () => hookCommand,
|
|
3603
|
+
inactiveSetupMessage: () => inactiveSetupMessage,
|
|
3567
3604
|
installedCommand: () => installedCommand,
|
|
3568
3605
|
mcpCommand: () => mcpCommand,
|
|
3569
3606
|
prepareLaunch: () => prepareLaunch
|
|
3570
3607
|
});
|
|
3571
3608
|
import { execFile as execFile10 } from "child_process";
|
|
3572
3609
|
import { promisify as promisify10 } from "util";
|
|
3573
|
-
import
|
|
3610
|
+
import fs16 from "fs/promises";
|
|
3574
3611
|
function globalCommand(args2, platform = process.platform) {
|
|
3575
3612
|
return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args2] } : { command: "mason", args: args2 };
|
|
3576
3613
|
}
|
|
@@ -3590,24 +3627,30 @@ async function installedCommand() {
|
|
|
3590
3627
|
}
|
|
3591
3628
|
}
|
|
3592
3629
|
async function prepareLaunch(host, options = {}) {
|
|
3593
|
-
const root = await
|
|
3630
|
+
const root = await fs16.realpath((await git3(options.dir ?? process.cwd(), "rev-parse", "--show-toplevel")).trim());
|
|
3594
3631
|
const entry = (await loadSetup(root))?.hosts[host];
|
|
3632
|
+
process.env.MASON_SETUP_ROOT = root;
|
|
3633
|
+
process.env.MASON_SETUP_HOST = host;
|
|
3634
|
+
delete process.env.MASON_SETUP_REVISION;
|
|
3595
3635
|
if (!entry) {
|
|
3596
3636
|
if (options.allowInactive) return false;
|
|
3597
3637
|
throw new Error(`Mason is not configured locally for ${host}. Run mason setup --host ${host} in this checkout.`);
|
|
3598
3638
|
}
|
|
3599
|
-
process.env.MASON_SETUP_ROOT = root;
|
|
3600
|
-
process.env.MASON_SETUP_HOST = host;
|
|
3601
3639
|
process.env.MASON_SETUP_REVISION = entry.revision;
|
|
3602
3640
|
return true;
|
|
3603
3641
|
}
|
|
3642
|
+
function inactiveSetupMessage() {
|
|
3643
|
+
const host = process.env.MASON_SETUP_HOST;
|
|
3644
|
+
if (!process.env.MASON_SETUP_ROOT || process.env.MASON_SETUP_REVISION || host !== "claude" && host !== "codex") return null;
|
|
3645
|
+
return `Mason MCP is available, but automatic hooks are inactive in ${process.env.MASON_SETUP_ROOT}. Run mason setup --host ${host} in that checkout, review the host configuration, then restart the assistant. Setup has not been performed automatically.`;
|
|
3646
|
+
}
|
|
3604
3647
|
var executingVersion, mcpCommand, hookCommand;
|
|
3605
3648
|
var init_launcher = __esm({
|
|
3606
3649
|
"src/setup/launcher.ts"() {
|
|
3607
3650
|
"use strict";
|
|
3608
3651
|
init_evidence();
|
|
3609
3652
|
init_model();
|
|
3610
|
-
executingVersion = () => true ? "0.17.
|
|
3653
|
+
executingVersion = () => true ? "0.17.1" : "development";
|
|
3611
3654
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
3612
3655
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
3613
3656
|
}
|
|
@@ -3620,7 +3663,7 @@ __export(observations_exports, {
|
|
|
3620
3663
|
observeActivation: () => observeActivation,
|
|
3621
3664
|
readObservation: () => readObservation
|
|
3622
3665
|
});
|
|
3623
|
-
import
|
|
3666
|
+
import fs17 from "fs/promises";
|
|
3624
3667
|
import { z as z12 } from "zod";
|
|
3625
3668
|
function observationPath(directory, host, revision, version = executingVersion()) {
|
|
3626
3669
|
return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
@@ -3634,11 +3677,17 @@ async function readObservation(root, directory, host, revision, version = execut
|
|
|
3634
3677
|
}
|
|
3635
3678
|
async function observeActivation(dir, event, options = {}) {
|
|
3636
3679
|
const host = process.env.MASON_SETUP_HOST, revision = process.env.MASON_SETUP_REVISION;
|
|
3637
|
-
if (host !== "codex" && host !== "claude" || !
|
|
3680
|
+
if (host !== "codex" && host !== "claude" || !process.env.MASON_SETUP_ROOT) return null;
|
|
3638
3681
|
try {
|
|
3682
|
+
if (!revision) {
|
|
3683
|
+
if ((await loadSetup(process.env.MASON_SETUP_ROOT))?.hosts[host]) {
|
|
3684
|
+
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.";
|
|
3685
|
+
}
|
|
3686
|
+
return inactiveSetupMessage();
|
|
3687
|
+
}
|
|
3639
3688
|
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];
|
|
3640
|
-
const ws = capturedDirectory ? { root: await
|
|
3641
|
-
if (ws.root !== await
|
|
3689
|
+
const ws = capturedDirectory ? { root: await fs17.realpath(dir), directory: capturedDirectory } : await workspace(dir);
|
|
3690
|
+
if (ws.root !== await fs17.realpath(process.env.MASON_SETUP_ROOT)) return null;
|
|
3642
3691
|
const setup = await loadSetup(ws.root);
|
|
3643
3692
|
if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
|
|
3644
3693
|
const directory = ws.directory + "/activation";
|
|
@@ -3719,7 +3768,7 @@ function normalizeHook(host, raw) {
|
|
|
3719
3768
|
stopHookActive: input.stop_hook_active || input.permission_mode === "plan"
|
|
3720
3769
|
} };
|
|
3721
3770
|
}
|
|
3722
|
-
async function runAutomationHook(host, stdin) {
|
|
3771
|
+
async function runAutomationHook(host, stdin, options = {}) {
|
|
3723
3772
|
let name = "";
|
|
3724
3773
|
let hook;
|
|
3725
3774
|
try {
|
|
@@ -3733,6 +3782,11 @@ async function runAutomationHook(host, stdin) {
|
|
|
3733
3782
|
const input = normalizeHook(host, raw);
|
|
3734
3783
|
hook = input;
|
|
3735
3784
|
name = input.name;
|
|
3785
|
+
if (options.managed) {
|
|
3786
|
+
input.cwd = await resolveHookRoot(host, input.cwd);
|
|
3787
|
+
const { prepareLaunch: prepareLaunch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
|
|
3788
|
+
if (!await prepareLaunch2(host, { dir: input.cwd, allowInactive: true })) return null;
|
|
3789
|
+
}
|
|
3736
3790
|
if (["before_tool", "after_tool"].includes(input.event.event) && input.readOnly) {
|
|
3737
3791
|
const observed = await observeReadOnlyTool(input.cwd, input.event);
|
|
3738
3792
|
const { observeActivation: observeActivation3 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
|
|
@@ -3765,6 +3819,9 @@ function managedHookCommand(host, platform = process.platform) {
|
|
|
3765
3819
|
function knownManagedHookCommands(host) {
|
|
3766
3820
|
return [managedHookCommand(host, "linux"), managedHookCommand(host, "win32"), `mason --setup-host ${host} auto hook --host ${host}`];
|
|
3767
3821
|
}
|
|
3822
|
+
function compatibleManagedHookCommands(host, platform = process.platform) {
|
|
3823
|
+
return [managedHookCommand(host, platform), `mason --setup-host ${host} auto hook --host ${host}`];
|
|
3824
|
+
}
|
|
3768
3825
|
function hookConfig(host, command2 = "npx --no-install --package mason-context mason-auto") {
|
|
3769
3826
|
const handler = { type: "command", command: command2 === `mason --setup-host ${host} auto` ? managedHookCommand(host) : command2 + " hook --host " + host, timeout: 30 };
|
|
3770
3827
|
return { hooks: Object.fromEntries(HOOK_EVENTS.map((name) => [
|
|
@@ -3779,6 +3836,7 @@ var init_adapters = __esm({
|
|
|
3779
3836
|
init_execution();
|
|
3780
3837
|
init_runtime();
|
|
3781
3838
|
init_store();
|
|
3839
|
+
init_hook_root();
|
|
3782
3840
|
inputSchema = z13.object({
|
|
3783
3841
|
cwd: z13.string().min(1),
|
|
3784
3842
|
session_id: z13.string().min(1).max(500),
|
|
@@ -3807,8 +3865,8 @@ __export(files_exports, {
|
|
|
3807
3865
|
managedBlock: () => managedBlock,
|
|
3808
3866
|
readText: () => readText
|
|
3809
3867
|
});
|
|
3810
|
-
import
|
|
3811
|
-
import
|
|
3868
|
+
import fs18 from "fs/promises";
|
|
3869
|
+
import path22 from "path";
|
|
3812
3870
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
3813
3871
|
async function readText(root, file) {
|
|
3814
3872
|
try {
|
|
@@ -3837,14 +3895,14 @@ async function applyEdit(root, edit) {
|
|
|
3837
3895
|
if (edit.after === null) {
|
|
3838
3896
|
const file2 = await storePath(root, edit.path);
|
|
3839
3897
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
|
|
3840
|
-
await
|
|
3898
|
+
await fs18.unlink(file2);
|
|
3841
3899
|
return true;
|
|
3842
3900
|
}
|
|
3843
3901
|
const file = await storePath(root, edit.path, true);
|
|
3844
|
-
const temporary =
|
|
3902
|
+
const temporary = path22.join(path22.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
3845
3903
|
try {
|
|
3846
|
-
const mode = await
|
|
3847
|
-
const handle = await
|
|
3904
|
+
const mode = await fs18.stat(file).then((s) => s.mode & 511, () => 384);
|
|
3905
|
+
const handle = await fs18.open(temporary, "wx", mode);
|
|
3848
3906
|
try {
|
|
3849
3907
|
await handle.writeFile(edit.after, "utf8");
|
|
3850
3908
|
await handle.sync();
|
|
@@ -3852,10 +3910,10 @@ async function applyEdit(root, edit) {
|
|
|
3852
3910
|
await handle.close();
|
|
3853
3911
|
}
|
|
3854
3912
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
|
|
3855
|
-
await
|
|
3913
|
+
await fs18.rename(temporary, file);
|
|
3856
3914
|
return true;
|
|
3857
3915
|
} finally {
|
|
3858
|
-
await
|
|
3916
|
+
await fs18.rm(temporary, { force: true });
|
|
3859
3917
|
}
|
|
3860
3918
|
}
|
|
3861
3919
|
var init_files2 = __esm({
|
|
@@ -4024,29 +4082,6 @@ If a build is interrupted, its partials remain in \`.mason/partial-snapshots/\`.
|
|
|
4024
4082
|
}
|
|
4025
4083
|
});
|
|
4026
4084
|
|
|
4027
|
-
// src/utils/git.ts
|
|
4028
|
-
var git_exports = {};
|
|
4029
|
-
__export(git_exports, {
|
|
4030
|
-
isGitRepo: () => isGitRepo
|
|
4031
|
-
});
|
|
4032
|
-
import { execFile as execFile11 } from "child_process";
|
|
4033
|
-
import { promisify as promisify11 } from "util";
|
|
4034
|
-
async function isGitRepo(dir) {
|
|
4035
|
-
try {
|
|
4036
|
-
await exec8("git", ["rev-parse", "--git-dir"], { cwd: dir });
|
|
4037
|
-
return true;
|
|
4038
|
-
} catch {
|
|
4039
|
-
return false;
|
|
4040
|
-
}
|
|
4041
|
-
}
|
|
4042
|
-
var exec8;
|
|
4043
|
-
var init_git2 = __esm({
|
|
4044
|
-
"src/utils/git.ts"() {
|
|
4045
|
-
"use strict";
|
|
4046
|
-
exec8 = promisify11(execFile11);
|
|
4047
|
-
}
|
|
4048
|
-
});
|
|
4049
|
-
|
|
4050
4085
|
// src/setup/config.ts
|
|
4051
4086
|
var config_exports = {};
|
|
4052
4087
|
__export(config_exports, {
|
|
@@ -4091,7 +4126,7 @@ async function instructionEdits(root, host) {
|
|
|
4091
4126
|
function managedMcp(previous, host) {
|
|
4092
4127
|
const options = previous === void 0 ? {} : object.parse(previous);
|
|
4093
4128
|
for (const key of ["command", "args", "url", "type", "headers", "http_headers", "env_http_headers", "bearer_token_env_var"]) delete options[key];
|
|
4094
|
-
return { ...options, ...mcpCommand(host) };
|
|
4129
|
+
return { ...options, ...host === "claude" ? { type: "stdio" } : {}, ...mcpCommand(host) };
|
|
4095
4130
|
}
|
|
4096
4131
|
async function mcpEdit(root, host) {
|
|
4097
4132
|
const file = mcpPath(host), before = await readText(root, file);
|
|
@@ -4128,17 +4163,9 @@ async function hookEdits(root, host) {
|
|
|
4128
4163
|
}
|
|
4129
4164
|
async function ancillaryEdits(root) {
|
|
4130
4165
|
const ignore = await readText(root, ".gitignore");
|
|
4131
|
-
const { git: git4 } = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
|
|
4132
|
-
const { isGitRepo: isGitRepo2 } = await Promise.resolve().then(() => (init_git2(), git_exports));
|
|
4133
|
-
let parentIgnored = false;
|
|
4134
|
-
try {
|
|
4135
|
-
if (await isGitRepo2(root)) parentIgnored = !!(await git4(root, "check-ignore", "--no-index", ".mason")).trim();
|
|
4136
|
-
} catch (error) {
|
|
4137
|
-
if (error.code !== 1) throw error;
|
|
4138
|
-
}
|
|
4139
|
-
const retainParentRule = parentIgnored || !!ignore?.replace(/\r\n/g, "\n").includes("# mason:ignore:start\n!/.mason/\n/.mason/*");
|
|
4140
4166
|
const rules = [
|
|
4141
|
-
|
|
4167
|
+
"!/.mason/",
|
|
4168
|
+
"/.mason/*",
|
|
4142
4169
|
"!/.mason/decisions/",
|
|
4143
4170
|
"!/.mason/decisions/**",
|
|
4144
4171
|
"!/.mason/reviews/",
|
|
@@ -4156,13 +4183,14 @@ async function inspectHostConfig(root, host, plannedMcp) {
|
|
|
4156
4183
|
const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
|
|
4157
4184
|
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath(host)) ?? "{}"));
|
|
4158
4185
|
const expected = hookConfig(host, hookCommand(host));
|
|
4186
|
+
const compatible = compatibleManagedHookCommands(host);
|
|
4159
4187
|
const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
|
|
4160
4188
|
return {
|
|
4161
4189
|
mcp,
|
|
4162
4190
|
mcpDisabled: mcp?.enabled === false,
|
|
4163
4191
|
hooks: Object.fromEntries(Object.keys(expected.hooks).map((event) => [
|
|
4164
4192
|
event,
|
|
4165
|
-
(hooks.hooks?.[event] ?? []).flatMap((group) => group.hooks.filter((handler) => handler.command
|
|
4193
|
+
(hooks.hooks?.[event] ?? []).flatMap((group) => group.hooks.filter((handler) => compatible.includes(handler.command ?? "")).map((handler) => ({ ...group, hooks: [handler] })) ?? [])
|
|
4166
4194
|
])),
|
|
4167
4195
|
disabled: hooks.disableAllHooks === true || config.features?.hooks === false || config.features?.codex_hooks === false
|
|
4168
4196
|
};
|
|
@@ -4352,8 +4380,12 @@ async function planTeardown(root, selected) {
|
|
|
4352
4380
|
if (host === "codex" && BLOCKS[2].some((marker) => before.includes(marker))) throw new Error("The marked Mason MCP entry was removed or renamed; inspect the retained block manually.");
|
|
4353
4381
|
return;
|
|
4354
4382
|
}
|
|
4355
|
-
const
|
|
4356
|
-
|
|
4383
|
+
const ownedHash = ownership.files[mcpFile]?.mcpHash;
|
|
4384
|
+
const expected = ownedHash ? [ownedHash] : [
|
|
4385
|
+
hash(mcpCommand(host)),
|
|
4386
|
+
...host === "claude" ? [hash({ type: "stdio", ...mcpCommand(host) })] : []
|
|
4387
|
+
];
|
|
4388
|
+
if (!expected.includes(hash(servers.mason))) throw new Error("The Mason MCP entry was edited or its ownership is uncertain; retained for manual cleanup.");
|
|
4357
4389
|
delete servers.mason;
|
|
4358
4390
|
const desired = { ...config, [key]: servers };
|
|
4359
4391
|
if (!Object.keys(servers).length) delete desired[key];
|
|
@@ -4520,13 +4552,13 @@ var init_teardown = __esm({
|
|
|
4520
4552
|
});
|
|
4521
4553
|
|
|
4522
4554
|
// src/review/cochange.ts
|
|
4523
|
-
import { execFile as
|
|
4524
|
-
import { promisify as
|
|
4555
|
+
import { execFile as execFile11 } from "child_process";
|
|
4556
|
+
import { promisify as promisify11 } from "util";
|
|
4525
4557
|
async function buildMatrix(resolvedRoot) {
|
|
4526
4558
|
try {
|
|
4527
|
-
const { stdout: shallow } = await
|
|
4559
|
+
const { stdout: shallow } = await exec8("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
|
|
4528
4560
|
if (shallow.trim() === "true") return null;
|
|
4529
|
-
const { stdout } = await
|
|
4561
|
+
const { stdout } = await exec8(
|
|
4530
4562
|
"git",
|
|
4531
4563
|
["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
|
|
4532
4564
|
{ cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
|
|
@@ -4582,11 +4614,11 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
|
|
|
4582
4614
|
findings.sort((a, b) => b.rate - a.rate || b.sharedCommits - a.sharedCommits);
|
|
4583
4615
|
return findings;
|
|
4584
4616
|
}
|
|
4585
|
-
var
|
|
4617
|
+
var exec8, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
|
|
4586
4618
|
var init_cochange = __esm({
|
|
4587
4619
|
"src/review/cochange.ts"() {
|
|
4588
4620
|
"use strict";
|
|
4589
|
-
|
|
4621
|
+
exec8 = promisify11(execFile11);
|
|
4590
4622
|
HISTORY_COMMITS = 1500;
|
|
4591
4623
|
MIN_FILE_COMMITS = 5;
|
|
4592
4624
|
MIN_SHARED_COMMITS = 4;
|
|
@@ -4894,10 +4926,10 @@ var init_check = __esm({
|
|
|
4894
4926
|
});
|
|
4895
4927
|
|
|
4896
4928
|
// src/review/evidence.ts
|
|
4897
|
-
import
|
|
4929
|
+
import path23 from "path";
|
|
4898
4930
|
import { z as z22 } from "zod";
|
|
4899
4931
|
function relativeArtifact(root, file) {
|
|
4900
|
-
const relative =
|
|
4932
|
+
const relative = path23.isAbsolute(file) ? path23.relative(root, file) : file;
|
|
4901
4933
|
const normalized = normalizeRepoPath(relative);
|
|
4902
4934
|
if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
|
|
4903
4935
|
return normalized;
|
|
@@ -4994,7 +5026,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
4994
5026
|
const report = await readStoreJson(root, file);
|
|
4995
5027
|
if (report === null) throw new Error(`Report artifact is missing: ${file}`);
|
|
4996
5028
|
const sourceRoot = check.sourceRoot ?? root;
|
|
4997
|
-
if (!
|
|
5029
|
+
if (!path23.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
|
|
4998
5030
|
const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
|
|
4999
5031
|
result.outcome = parsed.outcome;
|
|
5000
5032
|
result.counts = parsed.counts;
|
|
@@ -5115,13 +5147,13 @@ var init_evidence2 = __esm({
|
|
|
5115
5147
|
});
|
|
5116
5148
|
|
|
5117
5149
|
// src/review/review.ts
|
|
5118
|
-
import
|
|
5119
|
-
import
|
|
5120
|
-
import { execFile as
|
|
5121
|
-
import { promisify as
|
|
5150
|
+
import fs19 from "fs/promises";
|
|
5151
|
+
import path24 from "path";
|
|
5152
|
+
import { execFile as execFile12 } from "child_process";
|
|
5153
|
+
import { promisify as promisify12 } from "util";
|
|
5122
5154
|
async function resolveMergeBase(resolvedRoot, base, head) {
|
|
5123
5155
|
try {
|
|
5124
|
-
const { stdout } = await
|
|
5156
|
+
const { stdout } = await exec9("git", ["merge-base", base, head], {
|
|
5125
5157
|
cwd: resolvedRoot
|
|
5126
5158
|
});
|
|
5127
5159
|
return stdout.trim() || null;
|
|
@@ -5132,7 +5164,7 @@ async function resolveMergeBase(resolvedRoot, base, head) {
|
|
|
5132
5164
|
async function defaultBase(resolvedRoot) {
|
|
5133
5165
|
for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
|
|
5134
5166
|
try {
|
|
5135
|
-
await
|
|
5167
|
+
await exec9("git", ["rev-parse", "--verify", "--quiet", ref], {
|
|
5136
5168
|
cwd: resolvedRoot
|
|
5137
5169
|
});
|
|
5138
5170
|
return ref;
|
|
@@ -5145,7 +5177,7 @@ function anchorsTouched(record, changedFiles) {
|
|
|
5145
5177
|
return matchingPaths(record.files, changedFiles);
|
|
5146
5178
|
}
|
|
5147
5179
|
async function computeReview(rootDir, base, options = {}) {
|
|
5148
|
-
const resolvedRoot =
|
|
5180
|
+
const resolvedRoot = path24.resolve(rootDir);
|
|
5149
5181
|
const head = await getCurrentGitHash(resolvedRoot);
|
|
5150
5182
|
if (head === "unknown") return null;
|
|
5151
5183
|
const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
|
|
@@ -5195,7 +5227,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5195
5227
|
analyzed,
|
|
5196
5228
|
async (relPath) => {
|
|
5197
5229
|
try {
|
|
5198
|
-
await
|
|
5230
|
+
await fs19.access(path24.join(resolvedRoot, relPath));
|
|
5199
5231
|
return true;
|
|
5200
5232
|
} catch {
|
|
5201
5233
|
return false;
|
|
@@ -5228,7 +5260,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5228
5260
|
}
|
|
5229
5261
|
return finalize();
|
|
5230
5262
|
}
|
|
5231
|
-
var
|
|
5263
|
+
var exec9, MAX_ANALYZED_FILES;
|
|
5232
5264
|
var init_review = __esm({
|
|
5233
5265
|
"src/review/review.ts"() {
|
|
5234
5266
|
"use strict";
|
|
@@ -5240,7 +5272,7 @@ var init_review = __esm({
|
|
|
5240
5272
|
init_cochange();
|
|
5241
5273
|
init_evidence2();
|
|
5242
5274
|
init_snapshot();
|
|
5243
|
-
|
|
5275
|
+
exec9 = promisify12(execFile12);
|
|
5244
5276
|
MAX_ANALYZED_FILES = 50;
|
|
5245
5277
|
}
|
|
5246
5278
|
});
|
|
@@ -5362,7 +5394,16 @@ async function setupStatus(dir) {
|
|
|
5362
5394
|
const instructionsCurrent = instructions.every((edit) => edit.before === edit.after);
|
|
5363
5395
|
const config = await inspectHostConfig(ws.root, host, void 0);
|
|
5364
5396
|
const mcp = !config.mcpDisabled && hash(config.mcp) === entry.mcpFingerprint && isDeepStrictEqual3({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host));
|
|
5365
|
-
const
|
|
5397
|
+
const expectedHooks = hookConfig(host, hookCommand(host)).hooks;
|
|
5398
|
+
const currentHooks = isDeepStrictEqual3(config.hooks, expectedHooks);
|
|
5399
|
+
const compatibleHooks = Object.fromEntries(Object.entries(config.hooks).map(([event, groups]) => [
|
|
5400
|
+
event,
|
|
5401
|
+
groups.map((group) => ({ ...group, hooks: group.hooks.map((handler) => ({
|
|
5402
|
+
...handler,
|
|
5403
|
+
command: expectedHooks.SessionStart[0].hooks[0].command
|
|
5404
|
+
})) }))
|
|
5405
|
+
]));
|
|
5406
|
+
const hooks = isDeepStrictEqual3(compatibleHooks, expectedHooks) && !config.disabled;
|
|
5366
5407
|
const local = await loadSetupReceipt(ws.root, ws.directory, host);
|
|
5367
5408
|
const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
|
|
5368
5409
|
const observation = await readObservation(ws.root, ws.directory, host, entry.revision, installed.version ?? "unavailable");
|
|
@@ -5381,11 +5422,12 @@ async function setupStatus(dir) {
|
|
|
5381
5422
|
runtime: installed.available ? "global (" + installed.version + ")" : "unavailable-on-path",
|
|
5382
5423
|
mcp: mcp ? "configured" : "changed",
|
|
5383
5424
|
instructions: instructionsCurrent ? "current" : "changed",
|
|
5384
|
-
hookConfiguration: hooks ? "configured" : "disabled-or-changed",
|
|
5425
|
+
hookConfiguration: hooks ? currentHooks ? "configured" : "compatible" : "disabled-or-changed",
|
|
5385
5426
|
observedEvents,
|
|
5386
5427
|
contextCalls: observation?.contextCalls ?? 0,
|
|
5387
5428
|
verificationStatus: automation.status === "current" ? automation.verificationStatus ?? "unavailable" : "unavailable",
|
|
5388
|
-
pending
|
|
5429
|
+
pending,
|
|
5430
|
+
updates: hooks && !currentHooks ? ["A newer hook template is available. Run mason setup --host " + host + " to update it when convenient; review and commit the configuration changes."] : []
|
|
5389
5431
|
};
|
|
5390
5432
|
}
|
|
5391
5433
|
const statuses = Object.values(hosts2).map((host) => host.status);
|
|
@@ -5410,6 +5452,7 @@ function summarizeActivation(status) {
|
|
|
5410
5452
|
` Task context requests: ${state.contextCalls}; verification: ${state.verificationStatus}.`,
|
|
5411
5453
|
...state.pending.map((message2) => " Next: " + message2)
|
|
5412
5454
|
);
|
|
5455
|
+
lines.push(...state.updates.map((message2) => " Optional: " + message2));
|
|
5413
5456
|
}
|
|
5414
5457
|
if ("decisionRecords" in status) lines.push(`Decision records: ${status.decisionRecords}.`);
|
|
5415
5458
|
if (status.next) lines.push(status.next);
|
|
@@ -5558,16 +5601,16 @@ var init_setup = __esm({
|
|
|
5558
5601
|
});
|
|
5559
5602
|
|
|
5560
5603
|
// src/distribution/bundle.ts
|
|
5561
|
-
import
|
|
5562
|
-
import
|
|
5604
|
+
import fs20 from "fs/promises";
|
|
5605
|
+
import path25 from "path";
|
|
5563
5606
|
import { createHash as createHash4 } from "crypto";
|
|
5564
5607
|
import { z as z23 } from "zod";
|
|
5565
5608
|
async function readBundle(root) {
|
|
5566
|
-
const bytes = await
|
|
5609
|
+
const bytes = await fs20.readFile(await storePath(root, "bundle.json"));
|
|
5567
5610
|
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
5568
5611
|
const windows = manifest.target.startsWith("win32-");
|
|
5569
5612
|
for (const file of Object.keys(manifest.files)) {
|
|
5570
|
-
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") ||
|
|
5613
|
+
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path25.isAbsolute(file)) {
|
|
5571
5614
|
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
5572
5615
|
}
|
|
5573
5616
|
}
|
|
@@ -5582,7 +5625,7 @@ async function verifyBundle(root, expectedHash) {
|
|
|
5582
5625
|
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.");
|
|
5583
5626
|
const files = Object.keys(bundle.manifest.files);
|
|
5584
5627
|
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
5585
|
-
if (sha256(await
|
|
5628
|
+
if (sha256(await fs20.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
5586
5629
|
}));
|
|
5587
5630
|
return bundle;
|
|
5588
5631
|
}
|
|
@@ -5605,18 +5648,18 @@ var init_bundle = __esm({
|
|
|
5605
5648
|
});
|
|
5606
5649
|
|
|
5607
5650
|
// src/distribution/path.ts
|
|
5608
|
-
import
|
|
5651
|
+
import fs21 from "fs/promises";
|
|
5609
5652
|
import os3 from "os";
|
|
5610
|
-
import
|
|
5611
|
-
import { execFile as
|
|
5612
|
-
import { promisify as
|
|
5653
|
+
import path26 from "path";
|
|
5654
|
+
import { execFile as execFile13 } from "child_process";
|
|
5655
|
+
import { promisify as promisify13 } from "util";
|
|
5613
5656
|
import { createHash as createHash5, randomUUID as randomUUID7 } from "crypto";
|
|
5614
5657
|
import { z as z24 } from "zod";
|
|
5615
5658
|
async function configurePath(bin, previous, save, options = {}) {
|
|
5616
5659
|
const env = options.env ?? process.env, platform = options.platform ?? process.platform;
|
|
5617
5660
|
const changes = [...previous];
|
|
5618
5661
|
const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
|
|
5619
|
-
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) =>
|
|
5662
|
+
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path26.posix.isAbsolute(entry) && path26.posix.resolve(entry) === path26.posix.resolve(bin));
|
|
5620
5663
|
const message2 = alreadyOnPath ? "Run: mason setup --host codex" : "Open a new terminal, then run: mason setup --host codex";
|
|
5621
5664
|
const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
|
|
5622
5665
|
${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --host codex`}` });
|
|
@@ -5634,33 +5677,33 @@ ${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --h
|
|
|
5634
5677
|
}
|
|
5635
5678
|
return { status: "configured", changes, message: message2 };
|
|
5636
5679
|
}
|
|
5637
|
-
const shell =
|
|
5680
|
+
const shell = path26.basename(env.SHELL ?? os3.userInfo().shell ?? "");
|
|
5638
5681
|
const userHome = options.userHome ?? os3.homedir();
|
|
5639
5682
|
let profiles;
|
|
5640
5683
|
if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
|
|
5641
|
-
if (env.MASON_PROFILE) profiles = [
|
|
5642
|
-
else if (shell === "zsh") profiles = [
|
|
5643
|
-
else if (shell === "sh") profiles = [
|
|
5684
|
+
if (env.MASON_PROFILE) profiles = [path26.resolve(env.MASON_PROFILE)];
|
|
5685
|
+
else if (shell === "zsh") profiles = [path26.join(env.ZDOTDIR || userHome, ".zshrc")];
|
|
5686
|
+
else if (shell === "sh") profiles = [path26.join(userHome, ".profile")];
|
|
5644
5687
|
else {
|
|
5645
|
-
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) =>
|
|
5688
|
+
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path26.join(userHome, name));
|
|
5646
5689
|
let login = candidates[2];
|
|
5647
|
-
for (const file of candidates) if (await
|
|
5690
|
+
for (const file of candidates) if (await fs21.lstat(file).then(() => true, (error) => {
|
|
5648
5691
|
if (error.code === "ENOENT") return false;
|
|
5649
5692
|
throw error;
|
|
5650
5693
|
})) {
|
|
5651
5694
|
login = file;
|
|
5652
5695
|
break;
|
|
5653
5696
|
}
|
|
5654
|
-
profiles = [
|
|
5697
|
+
profiles = [path26.join(userHome, ".bashrc"), login];
|
|
5655
5698
|
}
|
|
5656
5699
|
const marker = `# >>> mason PATH ${createHash5("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
5657
5700
|
for (const profile of profiles) {
|
|
5658
|
-
const stat = await
|
|
5701
|
+
const stat = await fs21.lstat(profile).catch((error) => {
|
|
5659
5702
|
if (error.code === "ENOENT") return null;
|
|
5660
5703
|
throw error;
|
|
5661
5704
|
});
|
|
5662
|
-
const file = stat?.isSymbolicLink() ? await
|
|
5663
|
-
const original = await
|
|
5705
|
+
const file = stat?.isSymbolicLink() ? await fs21.realpath(profile) : path26.resolve(profile);
|
|
5706
|
+
const original = await fs21.readFile(file).catch((error) => {
|
|
5664
5707
|
if (error.code === "ENOENT" && !stat) return null;
|
|
5665
5708
|
throw error;
|
|
5666
5709
|
});
|
|
@@ -5675,8 +5718,8 @@ ${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --h
|
|
|
5675
5718
|
changes.push({ kind: "profile", file, block, created: original === null });
|
|
5676
5719
|
await save(changes);
|
|
5677
5720
|
}
|
|
5678
|
-
await
|
|
5679
|
-
const handle = await
|
|
5721
|
+
await fs21.mkdir(path26.dirname(file), { recursive: true });
|
|
5722
|
+
const handle = await fs21.open(file, original === null ? "ax+" : "a+", 384);
|
|
5680
5723
|
try {
|
|
5681
5724
|
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
5682
5725
|
await handle.writeFile(block);
|
|
@@ -5700,13 +5743,13 @@ async function removePath(changes) {
|
|
|
5700
5743
|
await windowsPath("remove", change.entry, current, change.created);
|
|
5701
5744
|
continue;
|
|
5702
5745
|
}
|
|
5703
|
-
const stat = await
|
|
5746
|
+
const stat = await fs21.lstat(change.file).catch((error) => {
|
|
5704
5747
|
if (error.code === "ENOENT") return null;
|
|
5705
5748
|
throw error;
|
|
5706
5749
|
});
|
|
5707
5750
|
if (!stat) continue;
|
|
5708
5751
|
if (!stat.isFile()) throw new Error("profile type changed");
|
|
5709
|
-
const bytes = await
|
|
5752
|
+
const bytes = await fs21.readFile(change.file), text2 = bytes.toString("utf8");
|
|
5710
5753
|
if (!Buffer.from(text2).equals(bytes)) throw new Error("profile encoding changed");
|
|
5711
5754
|
const index2 = text2.indexOf(change.block);
|
|
5712
5755
|
if (index2 < 0 || text2.indexOf(change.block, index2 + change.block.length) >= 0) throw new Error("PATH block was edited or removed");
|
|
@@ -5716,7 +5759,7 @@ async function removePath(changes) {
|
|
|
5716
5759
|
const temporary = change.file + ".mason-" + randomUUID7();
|
|
5717
5760
|
try {
|
|
5718
5761
|
if (stat.nlink > 1) throw new Error("profile has multiple hard links");
|
|
5719
|
-
const handle = await
|
|
5762
|
+
const handle = await fs21.open(temporary, "wx", stat.mode & 511);
|
|
5720
5763
|
try {
|
|
5721
5764
|
await handle.writeFile(next);
|
|
5722
5765
|
await handle.chmod(stat.mode & 511);
|
|
@@ -5725,11 +5768,11 @@ async function removePath(changes) {
|
|
|
5725
5768
|
} finally {
|
|
5726
5769
|
await handle.close();
|
|
5727
5770
|
}
|
|
5728
|
-
const current = await
|
|
5729
|
-
if (!current.isFile() || current.ino !== stat.ino || !(await
|
|
5730
|
-
await
|
|
5771
|
+
const current = await fs21.lstat(change.file);
|
|
5772
|
+
if (!current.isFile() || current.ino !== stat.ino || !(await fs21.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
|
|
5773
|
+
await fs21.rename(temporary, change.file);
|
|
5731
5774
|
} finally {
|
|
5732
|
-
await
|
|
5775
|
+
await fs21.rm(temporary, { force: true });
|
|
5733
5776
|
}
|
|
5734
5777
|
} catch (error) {
|
|
5735
5778
|
warnings.push(`Retained PATH setting ${change.kind === "profile" ? change.file : change.entry}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -5770,13 +5813,13 @@ var execute3, pathChangeSchema, quote, normalizeWindowsEntry, hasWindowsEntry;
|
|
|
5770
5813
|
var init_path = __esm({
|
|
5771
5814
|
"src/distribution/path.ts"() {
|
|
5772
5815
|
"use strict";
|
|
5773
|
-
execute3 =
|
|
5816
|
+
execute3 = promisify13(execFile13);
|
|
5774
5817
|
pathChangeSchema = z24.discriminatedUnion("kind", [
|
|
5775
5818
|
z24.object({ kind: z24.literal("profile"), file: z24.string(), block: z24.string(), created: z24.boolean() }),
|
|
5776
5819
|
z24.object({ kind: z24.literal("windows"), entry: z24.string(), created: z24.boolean() })
|
|
5777
5820
|
]);
|
|
5778
5821
|
quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
5779
|
-
normalizeWindowsEntry = (value) =>
|
|
5822
|
+
normalizeWindowsEntry = (value) => path26.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
|
|
5780
5823
|
hasWindowsEntry = (value, entry, env = process.env) => {
|
|
5781
5824
|
const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
|
|
5782
5825
|
const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
|
|
@@ -5794,9 +5837,9 @@ __export(install_exports2, {
|
|
|
5794
5837
|
uninstallStandalone: () => uninstallStandalone,
|
|
5795
5838
|
upgradeStandalone: () => upgradeStandalone
|
|
5796
5839
|
});
|
|
5797
|
-
import
|
|
5840
|
+
import fs22 from "fs/promises";
|
|
5798
5841
|
import os4 from "os";
|
|
5799
|
-
import
|
|
5842
|
+
import path27 from "path";
|
|
5800
5843
|
import { fileURLToPath } from "url";
|
|
5801
5844
|
import { spawn } from "child_process";
|
|
5802
5845
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
@@ -5804,31 +5847,31 @@ import { z as z25 } from "zod";
|
|
|
5804
5847
|
async function replace(file, content2, mode = 384) {
|
|
5805
5848
|
const temp = file + ".tmp-" + randomUUID8();
|
|
5806
5849
|
try {
|
|
5807
|
-
await
|
|
5808
|
-
await
|
|
5850
|
+
await fs22.writeFile(temp, content2, { mode, flag: "wx" });
|
|
5851
|
+
await fs22.rename(temp, file);
|
|
5809
5852
|
} finally {
|
|
5810
|
-
await
|
|
5853
|
+
await fs22.rm(temp, { force: true });
|
|
5811
5854
|
}
|
|
5812
5855
|
}
|
|
5813
5856
|
async function installStandalone(source) {
|
|
5814
5857
|
const bundle = await verifyBundle(source);
|
|
5815
|
-
const selectedHome =
|
|
5816
|
-
await
|
|
5817
|
-
const home = await
|
|
5818
|
-
const selectedBin =
|
|
5858
|
+
const selectedHome = path27.resolve(defaultHome());
|
|
5859
|
+
await fs22.mkdir(selectedHome, { recursive: true });
|
|
5860
|
+
const home = await fs22.realpath(selectedHome);
|
|
5861
|
+
const selectedBin = path27.resolve(process.env.MASON_BIN_DIR ?? (process.platform === "win32" ? path27.join(home, "bin") : path27.join(os4.homedir(), ".local/bin")));
|
|
5819
5862
|
return withLock(home, ".install-lock", async () => {
|
|
5820
5863
|
const raw = await readStoreJson(home, "install.json");
|
|
5821
5864
|
const previous = raw === null ? null : recordSchema3.parse(raw);
|
|
5822
|
-
if (!previous) for (const file of await
|
|
5865
|
+
if (!previous) for (const file of await fs22.readdir(home)) {
|
|
5823
5866
|
if (file === ".install-lock") continue;
|
|
5824
|
-
const directory =
|
|
5825
|
-
const stat = await
|
|
5826
|
-
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await
|
|
5867
|
+
const directory = path27.join(home, file);
|
|
5868
|
+
const stat = await fs22.lstat(directory);
|
|
5869
|
+
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs22.readdir(directory)).length) {
|
|
5827
5870
|
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
5828
5871
|
}
|
|
5829
5872
|
}
|
|
5830
|
-
await
|
|
5831
|
-
const bin = await
|
|
5873
|
+
await fs22.mkdir(selectedBin, { recursive: true });
|
|
5874
|
+
const bin = await fs22.realpath(selectedBin);
|
|
5832
5875
|
if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
|
|
5833
5876
|
const id = bundle.manifestHash.slice(0, 24);
|
|
5834
5877
|
const destination = await storePath(home, "versions/" + id);
|
|
@@ -5839,10 +5882,10 @@ $previousToken = $env:MASON_UNINSTALL_TOKEN
|
|
|
5839
5882
|
$token = [Guid]::NewGuid().ToString()
|
|
5840
5883
|
$env:MASON_UNINSTALL_TOKEN = $token
|
|
5841
5884
|
try {
|
|
5842
|
-
& ${psQuote(
|
|
5885
|
+
& ${psQuote(path27.join(destination, "node.exe"))} ${psQuote(path27.join(destination, "app/dist/mason.js"))} @args
|
|
5843
5886
|
$code = $LASTEXITCODE
|
|
5844
5887
|
if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
|
|
5845
|
-
if ([IO.File]::ReadAllText(${psQuote(
|
|
5888
|
+
if ([IO.File]::ReadAllText(${psQuote(path27.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
|
|
5846
5889
|
Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
|
|
5847
5890
|
Write-Output ${psQuote("Removed standalone installation: " + home)}
|
|
5848
5891
|
}
|
|
@@ -5851,11 +5894,11 @@ try {
|
|
|
5851
5894
|
finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
|
|
5852
5895
|
`
|
|
5853
5896
|
} : { mason: `#!/bin/sh
|
|
5854
|
-
exec ${shQuote(
|
|
5897
|
+
exec ${shQuote(path27.join(destination, "node"))} ${shQuote(path27.join(destination, "app/dist/mason.js"))} "$@"
|
|
5855
5898
|
` };
|
|
5856
5899
|
for (const [name, content2] of Object.entries(launchers)) {
|
|
5857
5900
|
const file = await storePath(bin, name);
|
|
5858
|
-
const old = await
|
|
5901
|
+
const old = await fs22.readFile(file, "utf8").catch((error) => {
|
|
5859
5902
|
if (error.code === "ENOENT") return null;
|
|
5860
5903
|
throw error;
|
|
5861
5904
|
});
|
|
@@ -5864,7 +5907,7 @@ exec ${shQuote(path26.join(destination, "node"))} ${shQuote(path26.join(destinat
|
|
|
5864
5907
|
const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
|
|
5865
5908
|
const checkRetired = async (name, expected) => {
|
|
5866
5909
|
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
5867
|
-
const actual = await
|
|
5910
|
+
const actual = await fs22.readFile(await storePath(bin, name), "utf8").catch((error) => {
|
|
5868
5911
|
if (error.code === "ENOENT") return null;
|
|
5869
5912
|
throw error;
|
|
5870
5913
|
});
|
|
@@ -5874,28 +5917,28 @@ exec ${shQuote(path26.join(destination, "node"))} ${shQuote(path26.join(destinat
|
|
|
5874
5917
|
const receipt = { format: 1, home, bin, current: id, version: bundle.manifest.version, launchers, pathChanges: previous?.pathChanges ?? [] };
|
|
5875
5918
|
await writeStoreJson(home, "install.json", { ...receipt, previousLaunchers: previous?.previousLaunchers ?? previous?.launchers });
|
|
5876
5919
|
const stage = await storePath(home, "versions/.install-" + randomUUID8(), true);
|
|
5877
|
-
await
|
|
5920
|
+
await fs22.mkdir(stage);
|
|
5878
5921
|
try {
|
|
5879
5922
|
for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
|
|
5880
5923
|
const from = await storePath(source, file), to = await storePath(stage, file, true);
|
|
5881
|
-
await
|
|
5882
|
-
await
|
|
5924
|
+
await fs22.copyFile(from, to);
|
|
5925
|
+
await fs22.chmod(to, (await fs22.stat(from)).mode & 511);
|
|
5883
5926
|
}
|
|
5884
5927
|
await verifyBundle(stage, bundle.manifestHash);
|
|
5885
|
-
const exists = await
|
|
5928
|
+
const exists = await fs22.lstat(destination).then(() => true, (error) => {
|
|
5886
5929
|
if (error.code === "ENOENT") return false;
|
|
5887
5930
|
throw error;
|
|
5888
5931
|
});
|
|
5889
5932
|
if (exists) await verifyBundle(destination, bundle.manifestHash);
|
|
5890
|
-
else await
|
|
5933
|
+
else await fs22.rename(stage, destination);
|
|
5891
5934
|
for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
5892
5935
|
for (const [name, expected] of retired) {
|
|
5893
5936
|
await checkRetired(name, expected);
|
|
5894
|
-
await
|
|
5937
|
+
await fs22.rm(await storePath(bin, name), { force: true });
|
|
5895
5938
|
}
|
|
5896
5939
|
await writeStoreJson(home, "install.json", receipt);
|
|
5897
5940
|
} finally {
|
|
5898
|
-
await
|
|
5941
|
+
await fs22.rm(stage, { recursive: true, force: true });
|
|
5899
5942
|
}
|
|
5900
5943
|
const pathResult = await configurePath(bin, receipt.pathChanges, async (changes) => {
|
|
5901
5944
|
receipt.pathChanges = changes;
|
|
@@ -5905,12 +5948,12 @@ exec ${shQuote(path26.join(destination, "node"))} ${shQuote(path26.join(destinat
|
|
|
5905
5948
|
});
|
|
5906
5949
|
}
|
|
5907
5950
|
async function currentInstallation() {
|
|
5908
|
-
const app =
|
|
5909
|
-
const bundle =
|
|
5951
|
+
const app = path27.dirname(path27.dirname(fileURLToPath(import.meta.url)));
|
|
5952
|
+
const bundle = path27.dirname(app), home = path27.dirname(path27.dirname(bundle));
|
|
5910
5953
|
const raw = await readStoreJson(home, "install.json");
|
|
5911
5954
|
if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
5912
5955
|
const record = recordSchema3.parse(raw);
|
|
5913
|
-
if (record.home !== await
|
|
5956
|
+
if (record.home !== await fs22.realpath(home) || path27.dirname(bundle) !== path27.join(home, "versions")) throw new Error("Invalid standalone installation receipt.");
|
|
5914
5957
|
return { home, bundle, record };
|
|
5915
5958
|
}
|
|
5916
5959
|
async function upgradeStandalone(version) {
|
|
@@ -5918,7 +5961,7 @@ async function upgradeStandalone(version) {
|
|
|
5918
5961
|
const { home, bundle, record } = await currentInstallation();
|
|
5919
5962
|
await verifyBundle(bundle);
|
|
5920
5963
|
const windows = process.platform === "win32";
|
|
5921
|
-
const script =
|
|
5964
|
+
const script = path27.join(bundle, windows ? "install.ps1" : "install.sh");
|
|
5922
5965
|
return new Promise((resolve, reject) => {
|
|
5923
5966
|
const child = spawn(
|
|
5924
5967
|
windows ? "powershell.exe" : "sh",
|
|
@@ -5939,7 +5982,7 @@ async function uninstallStandalone() {
|
|
|
5939
5982
|
for (const [name, expected] of Object.entries(record.launchers)) {
|
|
5940
5983
|
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
5941
5984
|
const file = await storePath(record.bin, name);
|
|
5942
|
-
const actual = await
|
|
5985
|
+
const actual = await fs22.readFile(file, "utf8").catch((error) => {
|
|
5943
5986
|
if (error.code === "ENOENT") return null;
|
|
5944
5987
|
throw error;
|
|
5945
5988
|
});
|
|
@@ -5947,9 +5990,9 @@ async function uninstallStandalone() {
|
|
|
5947
5990
|
}
|
|
5948
5991
|
for (const warning of await removePath(record.pathChanges ?? [])) console.error(warning);
|
|
5949
5992
|
if (process.platform === "win32") await replace(await storePath(home, ".uninstall-request.txt", true), token);
|
|
5950
|
-
for (const name of Object.keys(record.launchers)) await
|
|
5993
|
+
for (const name of Object.keys(record.launchers)) await fs22.rm(await storePath(record.bin, name), { force: true });
|
|
5951
5994
|
});
|
|
5952
|
-
if (process.platform !== "win32") await
|
|
5995
|
+
if (process.platform !== "win32") await fs22.rm(home, { recursive: true, force: true });
|
|
5953
5996
|
return home;
|
|
5954
5997
|
}
|
|
5955
5998
|
var recordSchema3, shQuote, psQuote, WINDOWS_BATCH_LAUNCHER, launcherNames, defaultHome;
|
|
@@ -5974,7 +6017,7 @@ var init_install2 = __esm({
|
|
|
5974
6017
|
psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
|
|
5975
6018
|
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';
|
|
5976
6019
|
launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
|
|
5977
|
-
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ?
|
|
6020
|
+
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path27.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path27.join(os4.homedir(), ".local/share/mason"));
|
|
5978
6021
|
}
|
|
5979
6022
|
});
|
|
5980
6023
|
|
|
@@ -6039,7 +6082,7 @@ function isHookCommand(argv) {
|
|
|
6039
6082
|
async function runAutomationCli(argv, stdin = "", io = {
|
|
6040
6083
|
out: (s) => process.stdout.write(s + "\n"),
|
|
6041
6084
|
err: (s) => process.stderr.write(s + "\n")
|
|
6042
|
-
}) {
|
|
6085
|
+
}, options = {}) {
|
|
6043
6086
|
let action = "";
|
|
6044
6087
|
try {
|
|
6045
6088
|
const { values, positionals } = parseCli(argv);
|
|
@@ -6059,7 +6102,9 @@ async function runAutomationCli(argv, stdin = "", io = {
|
|
|
6059
6102
|
return result.status === "complete" ? 0 : 2;
|
|
6060
6103
|
}
|
|
6061
6104
|
if (action === "hook") {
|
|
6062
|
-
const
|
|
6105
|
+
const host = hostSchema.parse(values.host);
|
|
6106
|
+
if (options.managedHost && options.managedHost !== host) throw new Error("Managed hook --host does not match its launcher.");
|
|
6107
|
+
const output = await runAutomationHook(host, stdin, { managed: !!options.managedHost });
|
|
6063
6108
|
if (output) io.out(JSON.stringify(output));
|
|
6064
6109
|
return 0;
|
|
6065
6110
|
}
|
|
@@ -6104,7 +6149,7 @@ var inputArgs = process.argv.slice(2);
|
|
|
6104
6149
|
var managedLaunch = inputArgs[0] === "--setup-host";
|
|
6105
6150
|
var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
|
|
6106
6151
|
var [command, ...args] = inputArgs;
|
|
6107
|
-
var usage = `Mason ${"0.17.
|
|
6152
|
+
var usage = `Mason ${"0.17.1"}
|
|
6108
6153
|
|
|
6109
6154
|
Usage: mason <command> [options]
|
|
6110
6155
|
|
|
@@ -6124,13 +6169,14 @@ mason-audit, mason-review, mason-drift, mason-hook and mason-mcp.`;
|
|
|
6124
6169
|
try {
|
|
6125
6170
|
if (managedLaunch) {
|
|
6126
6171
|
if (!setupHost || !["codex", "claude"].includes(setupHost) || !["mcp", "auto"].includes(command)) throw new Error("Invalid setup invocation.");
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6172
|
+
if (!(command === "auto" && isHookCommand(args))) {
|
|
6173
|
+
const { prepareLaunch: prepareLaunch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
|
|
6174
|
+
await prepareLaunch2(setupHost, { allowInactive: command === "mcp" });
|
|
6175
|
+
}
|
|
6130
6176
|
}
|
|
6131
|
-
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.17.
|
|
6177
|
+
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.17.1" }));
|
|
6132
6178
|
else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
|
|
6133
|
-
else if (["--version", "-v"].includes(command)) console.log("0.17.
|
|
6179
|
+
else if (["--version", "-v"].includes(command)) console.log("0.17.1");
|
|
6134
6180
|
else if (command === "internal-install") {
|
|
6135
6181
|
if (args.length) throw new Error("internal-install takes no arguments.");
|
|
6136
6182
|
const { installStandalone: installStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
@@ -6153,7 +6199,12 @@ ${installed.path.message}`);
|
|
|
6153
6199
|
input += chunk.toString();
|
|
6154
6200
|
if (Buffer.byteLength(input) > 1024 * 1024) break;
|
|
6155
6201
|
}
|
|
6156
|
-
process.exitCode = await runAutomationCli(
|
|
6202
|
+
process.exitCode = await runAutomationCli(
|
|
6203
|
+
forwarded,
|
|
6204
|
+
input,
|
|
6205
|
+
void 0,
|
|
6206
|
+
{ managedHost: managedLaunch ? setupHost : void 0 }
|
|
6207
|
+
);
|
|
6157
6208
|
} else if (["audit", "review", "drift", "hook", "mcp"].includes(command)) {
|
|
6158
6209
|
const binary = new URL(`./mason-${command}.js`, import.meta.url);
|
|
6159
6210
|
process.argv = [process.execPath, fileURLToPath2(binary), ...args];
|