mason-context 0.18.0 → 0.19.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 +16 -0
- package/README.md +2 -0
- package/dist/mason-audit.js +449 -251
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-auto.js +522 -270
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-drift.js +51 -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 +1486 -664
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +51 -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 +1377 -485
- package/dist/mason.js.map +1 -1
- package/package.json +2 -1
package/dist/mason.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() {
|
|
@@ -95,6 +96,26 @@ var init_git_read = __esm({
|
|
|
95
96
|
}
|
|
96
97
|
});
|
|
97
98
|
|
|
99
|
+
// src/audit/types.ts
|
|
100
|
+
var types_exports = {};
|
|
101
|
+
__export(types_exports, {
|
|
102
|
+
ALL_CHECKS: () => ALL_CHECKS
|
|
103
|
+
});
|
|
104
|
+
var ALL_CHECKS;
|
|
105
|
+
var init_types = __esm({
|
|
106
|
+
"src/audit/types.ts"() {
|
|
107
|
+
"use strict";
|
|
108
|
+
ALL_CHECKS = [
|
|
109
|
+
"deleted-reference",
|
|
110
|
+
"new-module",
|
|
111
|
+
"stale-count",
|
|
112
|
+
"dead-command",
|
|
113
|
+
"deps-changed",
|
|
114
|
+
"decision-anchor-drift"
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
98
119
|
// src/utils/files.ts
|
|
99
120
|
import fs from "fs/promises";
|
|
100
121
|
import { constants } from "fs";
|
|
@@ -139,6 +160,27 @@ async function loadProjectConfig(root) {
|
|
|
139
160
|
}
|
|
140
161
|
config[key] = value[key];
|
|
141
162
|
}
|
|
163
|
+
if (value.audit !== void 0) {
|
|
164
|
+
const audit = value.audit;
|
|
165
|
+
if (!audit || typeof audit !== "object" || Array.isArray(audit)) throw new Error("Configuration audit must be an object");
|
|
166
|
+
const patterns = (raw2) => {
|
|
167
|
+
if (!Array.isArray(raw2) || raw2.length > 100 || !raw2.every((s) => typeof s === "string" && normalizeRepoPath(s) === s && !s.startsWith("!") && !/[\x00-\x1f]/.test(s))) {
|
|
168
|
+
throw new Error("Audit patterns must be at most 100 repository-relative globs without traversal or negation");
|
|
169
|
+
}
|
|
170
|
+
return raw2;
|
|
171
|
+
};
|
|
172
|
+
config.audit = {};
|
|
173
|
+
if (audit.include !== void 0) config.audit.include = patterns(audit.include);
|
|
174
|
+
if (audit.exclude !== void 0) {
|
|
175
|
+
if (!audit.exclude || typeof audit.exclude !== "object" || Array.isArray(audit.exclude)) throw new Error("audit.exclude must map check names to patterns");
|
|
176
|
+
const { ALL_CHECKS: ALL_CHECKS2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
177
|
+
config.audit.exclude = {};
|
|
178
|
+
for (const [check, globs] of Object.entries(audit.exclude)) {
|
|
179
|
+
if (!ALL_CHECKS2.includes(check)) throw new Error("Unknown audit check: " + check);
|
|
180
|
+
config.audit.exclude[check] = patterns(globs);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
142
184
|
return config;
|
|
143
185
|
} catch (error) {
|
|
144
186
|
if (error.code === "ENOENT") return {};
|
|
@@ -438,8 +480,190 @@ var init_store = __esm({
|
|
|
438
480
|
}
|
|
439
481
|
});
|
|
440
482
|
|
|
441
|
-
// src/audit/
|
|
483
|
+
// src/audit/inspection.ts
|
|
484
|
+
import fg2 from "fast-glob";
|
|
485
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
442
486
|
import path4 from "path";
|
|
487
|
+
function withRepositoryInspection(root, run) {
|
|
488
|
+
return inspections.run({ root: path4.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
|
|
489
|
+
}
|
|
490
|
+
function inspectionRead(root, key, read) {
|
|
491
|
+
const inspection = inspections.getStore();
|
|
492
|
+
if (!inspection || inspection.root !== path4.resolve(root)) return read();
|
|
493
|
+
if (!inspection.reads.has(key)) {
|
|
494
|
+
const pending = read();
|
|
495
|
+
inspection.reads.set(key, pending);
|
|
496
|
+
void pending.catch(() => {
|
|
497
|
+
if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return inspection.reads.get(key);
|
|
501
|
+
}
|
|
502
|
+
function inspectionGit(args2, options) {
|
|
503
|
+
return inspectionRead(options.cwd, "git:" + JSON.stringify([args2, options]), () => execGit(args2, options));
|
|
504
|
+
}
|
|
505
|
+
async function auditGitPaths(root, kind) {
|
|
506
|
+
const custom = (await loadProjectConfig(root)).audit?.include ?? [];
|
|
507
|
+
const shared = inspections.getStore()?.root === path4.resolve(root) && !custom.length;
|
|
508
|
+
const expanded = fg2.generateTasks(custom).flatMap((task) => task.positive);
|
|
509
|
+
if (expanded.length > 1e3) throw new Error("Audit include patterns expand to more than 1,000 globs");
|
|
510
|
+
const documentSpecs = [...docSpecs, ...expanded.map((pattern) => `:(glob)${pattern}`)];
|
|
511
|
+
const specs = shared ? [...documentSpecs, ...sourceSpecs] : kind === "documents" ? documentSpecs : sourceSpecs;
|
|
512
|
+
const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
|
|
513
|
+
const { stdout } = await inspectionGit(
|
|
514
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
|
|
515
|
+
{ cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
|
|
516
|
+
);
|
|
517
|
+
const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? custom.length > 0 || /^(readme|agents|claude)\.md$/i.test(path4.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
|
|
518
|
+
if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
|
|
519
|
+
return paths.sort();
|
|
520
|
+
}
|
|
521
|
+
var inspections, sourceSpecs, docSpecs, sourceSuffixes;
|
|
522
|
+
var init_inspection = __esm({
|
|
523
|
+
"src/audit/inspection.ts"() {
|
|
524
|
+
"use strict";
|
|
525
|
+
init_git_read();
|
|
526
|
+
init_files();
|
|
527
|
+
inspections = new AsyncLocalStorage2();
|
|
528
|
+
sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
|
|
529
|
+
docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
|
|
530
|
+
sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// src/audit/inputs.ts
|
|
535
|
+
import fs4 from "fs/promises";
|
|
536
|
+
import * as nativeFs from "fs";
|
|
537
|
+
import path5 from "path";
|
|
538
|
+
import fg3 from "fast-glob";
|
|
539
|
+
async function auditInputPath(root, file) {
|
|
540
|
+
if (file === ".") return fs4.realpath(root);
|
|
541
|
+
try {
|
|
542
|
+
return await storePath(root, file);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
if (error instanceof Error && error.message.startsWith("Symlink")) {
|
|
545
|
+
throw new Error(`Audit input contains a symbolic link: ${file}. Its evidence could not be verified.`, { cause: error });
|
|
546
|
+
}
|
|
547
|
+
throw error;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
async function readAuditInput(root, file) {
|
|
551
|
+
try {
|
|
552
|
+
const value = await readBoundedFile(await auditInputPath(root, file), 10 * 1024 * 1024);
|
|
553
|
+
if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
|
|
554
|
+
return value;
|
|
555
|
+
} catch (error) {
|
|
556
|
+
if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return null;
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
async function auditGlob(root, patterns, options) {
|
|
561
|
+
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
562
|
+
const directories = /* @__PURE__ */ new Set();
|
|
563
|
+
const readdir2 = ((directory, opts, callback) => {
|
|
564
|
+
const relative = path5.relative(root, directory).split(path5.sep).join("/") || ".";
|
|
565
|
+
directories.add(relative);
|
|
566
|
+
if (directories.size > MAX_INPUTS) {
|
|
567
|
+
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
auditInputPath(root, relative).then(() => {
|
|
571
|
+
nativeFs.readdir(directory, opts, callback);
|
|
572
|
+
}, callback);
|
|
573
|
+
});
|
|
574
|
+
const lstat2 = ((file, callback) => {
|
|
575
|
+
const relative = path5.relative(root, file).split(path5.sep).join("/") || ".";
|
|
576
|
+
auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
|
|
577
|
+
});
|
|
578
|
+
const stream = fg3.stream(patterns, {
|
|
579
|
+
cwd: root,
|
|
580
|
+
ignore: options.ignore,
|
|
581
|
+
onlyFiles: false,
|
|
582
|
+
onlyDirectories: options.onlyDirectories,
|
|
583
|
+
objectMode: true,
|
|
584
|
+
followSymbolicLinks: options.followSymbolicLinks ?? true,
|
|
585
|
+
fs: { readdir: readdir2, lstat: lstat2 },
|
|
586
|
+
concurrency: 16
|
|
587
|
+
});
|
|
588
|
+
const matches = [];
|
|
589
|
+
try {
|
|
590
|
+
for await (const value of stream) {
|
|
591
|
+
const entry = value;
|
|
592
|
+
const file = entry.path;
|
|
593
|
+
if (options.select && !options.select(file)) continue;
|
|
594
|
+
await auditInputPath(root, file);
|
|
595
|
+
if (!options.onlyDirectories && !entry.dirent.isFile()) continue;
|
|
596
|
+
matches.push(file);
|
|
597
|
+
if (matches.length > MAX_INPUTS) {
|
|
598
|
+
const largest = [...matches.reduce((counts, item) => {
|
|
599
|
+
const dir = item.split("/")[0];
|
|
600
|
+
counts.set(dir, (counts.get(dir) ?? 0) + 1);
|
|
601
|
+
return counts;
|
|
602
|
+
}, /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
603
|
+
throw new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count4]) => `${dir} (${count4})`).join(", ")}. Narrow this check's inputs.`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
} finally {
|
|
607
|
+
stream.destroy();
|
|
608
|
+
}
|
|
609
|
+
return matches.sort();
|
|
610
|
+
}
|
|
611
|
+
async function gitSourcePaths(root) {
|
|
612
|
+
return auditGitPaths(root, "sources");
|
|
613
|
+
}
|
|
614
|
+
async function gitIgnoredPaths(root, files) {
|
|
615
|
+
if (!files.length) return /* @__PURE__ */ new Set();
|
|
616
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
617
|
+
for (let offset = 0; offset < files.length; offset += 1e3) {
|
|
618
|
+
try {
|
|
619
|
+
const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
|
|
620
|
+
cwd: root,
|
|
621
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
622
|
+
timeout: 1e4,
|
|
623
|
+
input: files.slice(offset, offset + 1e3).join("\0") + "\0"
|
|
624
|
+
});
|
|
625
|
+
for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
|
|
626
|
+
} catch (error) {
|
|
627
|
+
if (error.code !== 1) throw error;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return ignored;
|
|
631
|
+
}
|
|
632
|
+
var MAX_INPUTS;
|
|
633
|
+
var init_inputs = __esm({
|
|
634
|
+
"src/audit/inputs.ts"() {
|
|
635
|
+
"use strict";
|
|
636
|
+
init_files();
|
|
637
|
+
init_storage();
|
|
638
|
+
init_git_read();
|
|
639
|
+
init_inspection();
|
|
640
|
+
MAX_INPUTS = 1e5;
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// src/audit/policy.ts
|
|
645
|
+
import fg4 from "fast-glob";
|
|
646
|
+
async function includedCheckPaths(root, paths, exclude) {
|
|
647
|
+
if (!exclude.length) return new Set(paths);
|
|
648
|
+
const included = /* @__PURE__ */ new Set();
|
|
649
|
+
for (let offset = 0; offset < paths.length; offset += 256) {
|
|
650
|
+
for (const file of await auditGlob(root, paths.slice(offset, offset + 256).map((file2) => fg4.escapePath(file2)), {
|
|
651
|
+
ignore: exclude,
|
|
652
|
+
followSymbolicLinks: false,
|
|
653
|
+
label: "Audit check exclusions"
|
|
654
|
+
})) included.add(file);
|
|
655
|
+
}
|
|
656
|
+
return included;
|
|
657
|
+
}
|
|
658
|
+
var init_policy = __esm({
|
|
659
|
+
"src/audit/policy.ts"() {
|
|
660
|
+
"use strict";
|
|
661
|
+
init_inputs();
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// src/audit/commands.ts
|
|
666
|
+
import path6 from "path";
|
|
443
667
|
function splitCommands(line) {
|
|
444
668
|
const parts = [];
|
|
445
669
|
let quote2 = "", start = 0;
|
|
@@ -475,7 +699,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
|
|
|
475
699
|
const cd = text2.match(/^cd\s+(.+)$/);
|
|
476
700
|
if (cd) {
|
|
477
701
|
const directory = literal(cd[1]);
|
|
478
|
-
cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null :
|
|
702
|
+
cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path6.posix.join(cwd ?? ".", directory);
|
|
479
703
|
continue;
|
|
480
704
|
}
|
|
481
705
|
for (const match of text2.matchAll(COMMAND)) {
|
|
@@ -486,7 +710,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
|
|
|
486
710
|
if (shell && match.index !== 0 || match[3].startsWith("-")) scopeUnknown = "Unsupported shell wrapper or options before the script.";
|
|
487
711
|
if (selector) {
|
|
488
712
|
const selected = literal(selector[1]);
|
|
489
|
-
directory = selected === null || cwd === null ? null :
|
|
713
|
+
directory = selected === null || cwd === null ? null : path6.posix.join(cwd ?? ".", selected);
|
|
490
714
|
} else if (flags) scopeUnknown = "Unsupported package-manager options before run: " + flags;
|
|
491
715
|
const tail = text2.slice(match.index + match[0].length).split(/\s--(?:\s|$)/)[0];
|
|
492
716
|
if (/(?:^|\s)(?:--workspace(?:s)?|-w|--filter|-F|--prefix|--dir|-C|--if-present)(?:\s|=|$)/.test(tail)) {
|
|
@@ -516,7 +740,7 @@ var init_commands = __esm({
|
|
|
516
740
|
COMMAND = new RegExp(`\\b(npm|pnpm|yarn)((?:[ \\t]+${TOKEN})*?)[ \\t]+run[ \\t]+([A-Za-z0-9:_.-]+)`, "g");
|
|
517
741
|
literal = (value) => {
|
|
518
742
|
const unquoted = value.replace(/^(["'])(.*)\1$/, "$2");
|
|
519
|
-
return /[$`*?\[\]{}~\\\x00-\x1f]/.test(unquoted) ||
|
|
743
|
+
return /[$`*?\[\]{}~\\\x00-\x1f]/.test(unquoted) || path6.posix.isAbsolute(unquoted) ? null : unquoted;
|
|
520
744
|
};
|
|
521
745
|
}
|
|
522
746
|
});
|
|
@@ -821,52 +1045,6 @@ var init_claims = __esm({
|
|
|
821
1045
|
}
|
|
822
1046
|
});
|
|
823
1047
|
|
|
824
|
-
// src/audit/inspection.ts
|
|
825
|
-
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
826
|
-
import path5 from "path";
|
|
827
|
-
function withRepositoryInspection(root, run) {
|
|
828
|
-
return inspections.run({ root: path5.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
|
|
829
|
-
}
|
|
830
|
-
function inspectionRead(root, key, read) {
|
|
831
|
-
const inspection = inspections.getStore();
|
|
832
|
-
if (!inspection || inspection.root !== path5.resolve(root)) return read();
|
|
833
|
-
if (!inspection.reads.has(key)) {
|
|
834
|
-
const pending = read();
|
|
835
|
-
inspection.reads.set(key, pending);
|
|
836
|
-
void pending.catch(() => {
|
|
837
|
-
if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
|
|
838
|
-
});
|
|
839
|
-
}
|
|
840
|
-
return inspection.reads.get(key);
|
|
841
|
-
}
|
|
842
|
-
function inspectionGit(args2, options) {
|
|
843
|
-
return inspectionRead(options.cwd, "git:" + JSON.stringify([args2, options]), () => execGit(args2, options));
|
|
844
|
-
}
|
|
845
|
-
async function auditGitPaths(root, kind) {
|
|
846
|
-
const shared = inspections.getStore()?.root === path5.resolve(root);
|
|
847
|
-
const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
|
|
848
|
-
const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
|
|
849
|
-
const { stdout } = await inspectionGit(
|
|
850
|
-
["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
|
|
851
|
-
{ cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
|
|
852
|
-
);
|
|
853
|
-
const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? /^(readme|agents|claude)\.md$/i.test(path5.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
|
|
854
|
-
if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
|
|
855
|
-
return paths.sort();
|
|
856
|
-
}
|
|
857
|
-
var inspections, sourceSpecs, docSpecs, sourceSuffixes;
|
|
858
|
-
var init_inspection = __esm({
|
|
859
|
-
"src/audit/inspection.ts"() {
|
|
860
|
-
"use strict";
|
|
861
|
-
init_git_read();
|
|
862
|
-
init_files();
|
|
863
|
-
inspections = new AsyncLocalStorage2();
|
|
864
|
-
sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
|
|
865
|
-
docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
|
|
866
|
-
sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
|
|
867
|
-
}
|
|
868
|
-
});
|
|
869
|
-
|
|
870
1048
|
// src/audit/git.ts
|
|
871
1049
|
function parseCommitLine(line) {
|
|
872
1050
|
const parts = line.split(" ");
|
|
@@ -952,136 +1130,26 @@ var init_git = __esm({
|
|
|
952
1130
|
}
|
|
953
1131
|
});
|
|
954
1132
|
|
|
955
|
-
// src/audit/
|
|
956
|
-
import
|
|
957
|
-
import
|
|
958
|
-
import
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
if (file === ".") return fs4.realpath(root);
|
|
1133
|
+
// src/audit/docs.ts
|
|
1134
|
+
import fs5 from "fs/promises";
|
|
1135
|
+
import path7 from "path";
|
|
1136
|
+
import fg5 from "fast-glob";
|
|
1137
|
+
async function dirtyDocs(resolvedRoot, files) {
|
|
1138
|
+
if (!files.length) return /* @__PURE__ */ new Set();
|
|
962
1139
|
try {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
} catch (error) {
|
|
977
|
-
if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return null;
|
|
978
|
-
throw error;
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
async function auditGlob(root, patterns, options) {
|
|
982
|
-
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
983
|
-
const directories = /* @__PURE__ */ new Set();
|
|
984
|
-
const readdir2 = ((directory, opts, callback) => {
|
|
985
|
-
const relative = path6.relative(root, directory).split(path6.sep).join("/") || ".";
|
|
986
|
-
directories.add(relative);
|
|
987
|
-
if (directories.size > MAX_INPUTS) {
|
|
988
|
-
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
989
|
-
return;
|
|
990
|
-
}
|
|
991
|
-
auditInputPath(root, relative).then(() => {
|
|
992
|
-
nativeFs.readdir(directory, opts, callback);
|
|
993
|
-
}, callback);
|
|
994
|
-
});
|
|
995
|
-
const lstat2 = ((file, callback) => {
|
|
996
|
-
const relative = path6.relative(root, file).split(path6.sep).join("/") || ".";
|
|
997
|
-
auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
|
|
998
|
-
});
|
|
999
|
-
const stream = fg2.stream(patterns, {
|
|
1000
|
-
cwd: root,
|
|
1001
|
-
ignore: options.ignore,
|
|
1002
|
-
onlyFiles: false,
|
|
1003
|
-
onlyDirectories: options.onlyDirectories,
|
|
1004
|
-
objectMode: true,
|
|
1005
|
-
followSymbolicLinks: options.followSymbolicLinks ?? true,
|
|
1006
|
-
fs: { readdir: readdir2, lstat: lstat2 },
|
|
1007
|
-
concurrency: 16
|
|
1008
|
-
});
|
|
1009
|
-
const matches = [];
|
|
1010
|
-
try {
|
|
1011
|
-
for await (const value of stream) {
|
|
1012
|
-
const entry = value;
|
|
1013
|
-
const file = entry.path;
|
|
1014
|
-
if (options.select && !options.select(file)) continue;
|
|
1015
|
-
await auditInputPath(root, file);
|
|
1016
|
-
if (!options.onlyDirectories && !entry.dirent.isFile()) continue;
|
|
1017
|
-
matches.push(file);
|
|
1018
|
-
if (matches.length > MAX_INPUTS) {
|
|
1019
|
-
const largest = [...matches.reduce((counts, item) => {
|
|
1020
|
-
const dir = item.split("/")[0];
|
|
1021
|
-
counts.set(dir, (counts.get(dir) ?? 0) + 1);
|
|
1022
|
-
return counts;
|
|
1023
|
-
}, /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
1024
|
-
throw new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count4]) => `${dir} (${count4})`).join(", ")}. Narrow this check's inputs.`);
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
} finally {
|
|
1028
|
-
stream.destroy();
|
|
1029
|
-
}
|
|
1030
|
-
return matches.sort();
|
|
1031
|
-
}
|
|
1032
|
-
async function gitSourcePaths(root) {
|
|
1033
|
-
return auditGitPaths(root, "sources");
|
|
1034
|
-
}
|
|
1035
|
-
async function gitIgnoredPaths(root, files) {
|
|
1036
|
-
if (!files.length) return /* @__PURE__ */ new Set();
|
|
1037
|
-
const ignored = /* @__PURE__ */ new Set();
|
|
1038
|
-
for (let offset = 0; offset < files.length; offset += 1e3) {
|
|
1039
|
-
try {
|
|
1040
|
-
const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
|
|
1041
|
-
cwd: root,
|
|
1042
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
1043
|
-
timeout: 1e4,
|
|
1044
|
-
input: files.slice(offset, offset + 1e3).join("\0") + "\0"
|
|
1045
|
-
});
|
|
1046
|
-
for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
|
|
1047
|
-
} catch (error) {
|
|
1048
|
-
if (error.code !== 1) throw error;
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
return ignored;
|
|
1052
|
-
}
|
|
1053
|
-
var MAX_INPUTS;
|
|
1054
|
-
var init_inputs = __esm({
|
|
1055
|
-
"src/audit/inputs.ts"() {
|
|
1056
|
-
"use strict";
|
|
1057
|
-
init_files();
|
|
1058
|
-
init_storage();
|
|
1059
|
-
init_git_read();
|
|
1060
|
-
init_inspection();
|
|
1061
|
-
MAX_INPUTS = 1e5;
|
|
1062
|
-
}
|
|
1063
|
-
});
|
|
1064
|
-
|
|
1065
|
-
// src/audit/docs.ts
|
|
1066
|
-
import fs5 from "fs/promises";
|
|
1067
|
-
import path7 from "path";
|
|
1068
|
-
import fg3 from "fast-glob";
|
|
1069
|
-
async function dirtyDocs(resolvedRoot, files) {
|
|
1070
|
-
if (!files.length) return /* @__PURE__ */ new Set();
|
|
1071
|
-
try {
|
|
1072
|
-
const dirty = /* @__PURE__ */ new Set();
|
|
1073
|
-
for (let offset = 0; offset < files.length; offset += 256) {
|
|
1074
|
-
const { stdout } = await execGit(
|
|
1075
|
-
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
|
|
1076
|
-
{ cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
1077
|
-
);
|
|
1078
|
-
const entries = stdout.split("\0");
|
|
1079
|
-
for (let i = 0; i < entries.length; i++) {
|
|
1080
|
-
if (!entries[i]) continue;
|
|
1081
|
-
const status = entries[i].slice(0, 2);
|
|
1082
|
-
dirty.add(entries[i].slice(3));
|
|
1083
|
-
if (/[RC]/.test(status) && entries[i + 1]) dirty.add(entries[++i]);
|
|
1084
|
-
}
|
|
1140
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
1141
|
+
for (let offset = 0; offset < files.length; offset += 256) {
|
|
1142
|
+
const { stdout } = await execGit(
|
|
1143
|
+
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
|
|
1144
|
+
{ cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
1145
|
+
);
|
|
1146
|
+
const entries = stdout.split("\0");
|
|
1147
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1148
|
+
if (!entries[i]) continue;
|
|
1149
|
+
const status = entries[i].slice(0, 2);
|
|
1150
|
+
dirty.add(entries[i].slice(3));
|
|
1151
|
+
if (/[RC]/.test(status) && entries[i + 1]) dirty.add(entries[++i]);
|
|
1152
|
+
}
|
|
1085
1153
|
}
|
|
1086
1154
|
return dirty;
|
|
1087
1155
|
} catch {
|
|
@@ -1117,7 +1185,7 @@ async function discoverDocPaths(root) {
|
|
|
1117
1185
|
const paths = [];
|
|
1118
1186
|
const unique = [...new Set(candidates)].sort();
|
|
1119
1187
|
for (let offset = 0; offset < unique.length; offset += 256) {
|
|
1120
|
-
paths.push(...await auditGlob(root, unique.slice(offset, offset + 256).map((p) =>
|
|
1188
|
+
paths.push(...await auditGlob(root, unique.slice(offset, offset + 256).map((p) => fg5.escapePath(p)), {
|
|
1121
1189
|
ignore: excluded,
|
|
1122
1190
|
followSymbolicLinks: false,
|
|
1123
1191
|
label: "Documentation discovery"
|
|
@@ -1147,6 +1215,7 @@ async function localDocPaths(root) {
|
|
|
1147
1215
|
return paths;
|
|
1148
1216
|
}
|
|
1149
1217
|
function documentScope(file) {
|
|
1218
|
+
if (!/^(readme|agents|claude)\.md$/i.test(path7.posix.basename(file))) return ".";
|
|
1150
1219
|
const parent = path7.posix.dirname(file);
|
|
1151
1220
|
return path7.posix.basename(parent).toLowerCase() === ".claude" ? path7.posix.dirname(parent) : parent;
|
|
1152
1221
|
}
|
|
@@ -1168,7 +1237,7 @@ async function readDocs(resolvedRoot) {
|
|
|
1168
1237
|
path: candidate,
|
|
1169
1238
|
content: content2,
|
|
1170
1239
|
scope: documentScope(candidate),
|
|
1171
|
-
kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : "instructions",
|
|
1240
|
+
kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : /^(agents|claude)\.md$/i.test(path7.posix.basename(candidate)) ? "instructions" : "guide",
|
|
1172
1241
|
lineCount: content2.split("\n").length,
|
|
1173
1242
|
lastCommit,
|
|
1174
1243
|
dirty: dirty.has(candidate),
|
|
@@ -1437,22 +1506,6 @@ var init_drift = __esm({
|
|
|
1437
1506
|
}
|
|
1438
1507
|
});
|
|
1439
1508
|
|
|
1440
|
-
// src/audit/types.ts
|
|
1441
|
-
var ALL_CHECKS;
|
|
1442
|
-
var init_types = __esm({
|
|
1443
|
-
"src/audit/types.ts"() {
|
|
1444
|
-
"use strict";
|
|
1445
|
-
ALL_CHECKS = [
|
|
1446
|
-
"deleted-reference",
|
|
1447
|
-
"new-module",
|
|
1448
|
-
"stale-count",
|
|
1449
|
-
"dead-command",
|
|
1450
|
-
"deps-changed",
|
|
1451
|
-
"decision-anchor-drift"
|
|
1452
|
-
];
|
|
1453
|
-
}
|
|
1454
|
-
});
|
|
1455
|
-
|
|
1456
1509
|
// src/audit/checks/deleted-reference.ts
|
|
1457
1510
|
import path12 from "path";
|
|
1458
1511
|
async function checkDeletedReferences(ctx) {
|
|
@@ -1521,7 +1574,7 @@ var init_deleted_reference = __esm({
|
|
|
1521
1574
|
});
|
|
1522
1575
|
|
|
1523
1576
|
// src/audit/checks/new-module.ts
|
|
1524
|
-
import
|
|
1577
|
+
import fg6 from "fast-glob";
|
|
1525
1578
|
import fs8 from "fs/promises";
|
|
1526
1579
|
import path13 from "path";
|
|
1527
1580
|
function escapeRegExp(text2) {
|
|
@@ -1583,7 +1636,7 @@ async function collectModuleCandidates(root, combinedDocs) {
|
|
|
1583
1636
|
const result = /* @__PURE__ */ new Map();
|
|
1584
1637
|
for (const dir of parents) {
|
|
1585
1638
|
const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
|
|
1586
|
-
result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${
|
|
1639
|
+
result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg6.escapePath(dir)}/*`, {
|
|
1587
1640
|
ignore,
|
|
1588
1641
|
onlyDirectories: true,
|
|
1589
1642
|
label: "Module directory discovery",
|
|
@@ -1596,7 +1649,7 @@ async function collectModuleCandidates(root, combinedDocs) {
|
|
|
1596
1649
|
const files = sources.filter((file) => file.startsWith(dir + "/"));
|
|
1597
1650
|
let count4 = 0;
|
|
1598
1651
|
for (let offset = 0; offset < files.length; offset += 256) {
|
|
1599
|
-
count4 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) =>
|
|
1652
|
+
count4 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) => fg6.escapePath(file)), {
|
|
1600
1653
|
ignore,
|
|
1601
1654
|
followSymbolicLinks: false,
|
|
1602
1655
|
label: `Source discovery in ${dir}`
|
|
@@ -1931,6 +1984,87 @@ var init_dead_command = __esm({
|
|
|
1931
1984
|
}
|
|
1932
1985
|
});
|
|
1933
1986
|
|
|
1987
|
+
// src/audit/dependency-claims.ts
|
|
1988
|
+
import path16 from "path";
|
|
1989
|
+
import { parse as parseToml } from "smol-toml";
|
|
1990
|
+
function dependencyText(content2) {
|
|
1991
|
+
return content2.replace(/<!-- mason:start -->[\s\S]*?<!-- mason:end -->/g, (text2) => text2.replace(/[^\n]/g, " ")).replace(/<!--[\s\S]*?-->/g, (text2) => text2.replace(/[^\n]/g, " "));
|
|
1992
|
+
}
|
|
1993
|
+
function declarations(file, source) {
|
|
1994
|
+
const result = /* @__PURE__ */ new Map();
|
|
1995
|
+
if (!source) return result;
|
|
1996
|
+
if (path16.posix.basename(file) === "package.json") {
|
|
1997
|
+
const json2 = JSON.parse(source);
|
|
1998
|
+
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies", "engines"]) {
|
|
1999
|
+
for (const [name, value] of Object.entries(json2[section] ?? {})) {
|
|
2000
|
+
if (typeof value === "string") result.set(name, [...result.has(name) ? [result.get(name)] : [], value].join("; "));
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
} else if (file.endsWith("libs.versions.toml")) {
|
|
2004
|
+
const catalog = parseToml(source);
|
|
2005
|
+
for (const section of ["libraries", "plugins"]) for (const [alias, entry] of Object.entries(catalog[section] ?? {})) {
|
|
2006
|
+
if (typeof entry === "string") {
|
|
2007
|
+
const parts = entry.split(":");
|
|
2008
|
+
if (parts.length >= 3) result.set(parts.slice(0, -1).join(":"), parts.at(-1));
|
|
2009
|
+
} else if (entry && typeof entry === "object") {
|
|
2010
|
+
const name = entry.module ?? (entry.group && entry.name ? `${entry.group}:${entry.name}` : entry.id);
|
|
2011
|
+
const version = typeof entry.version === "string" ? entry.version : catalog.versions?.[entry.version?.ref];
|
|
2012
|
+
if (typeof name === "string") result.set(name, typeof version === "string" ? version : "unspecified");
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
} else if (/build\.gradle(?:\.kts)?$/.test(file)) {
|
|
2016
|
+
for (const match of source.matchAll(/["']([\w.-]+:[\w.-]+):([^"'\s]+)["']/g)) result.set(match[1], match[2]);
|
|
2017
|
+
for (const match of source.matchAll(/\bid\s*\(?\s*["']([\w.-]+)["']\s*\)?\s*version\s*["']([^"']+)["']/g)) result.set(match[1], match[2]);
|
|
2018
|
+
}
|
|
2019
|
+
return result;
|
|
2020
|
+
}
|
|
2021
|
+
async function matchDependencyChanges(root, from, head, files, content2) {
|
|
2022
|
+
const matches = [];
|
|
2023
|
+
const lines = dependencyText(content2).split("\n");
|
|
2024
|
+
let incomplete = files.length > 64;
|
|
2025
|
+
for (const file of files.slice(0, 64)) {
|
|
2026
|
+
if (!/(?:^|\/)(?:package\.json|libs\.versions\.toml|build\.gradle(?:\.kts)?)$/.test(file)) {
|
|
2027
|
+
incomplete = true;
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
try {
|
|
2031
|
+
const read = async (revision) => {
|
|
2032
|
+
const { stdout: entry } = await inspectionGit(["ls-tree", revision, "--", `:(literal)${file}`], { cwd: root, timeout: 1e4 });
|
|
2033
|
+
if (!entry) return "";
|
|
2034
|
+
if (!entry.startsWith("100644 ") && !entry.startsWith("100755 ")) throw new Error("Unsupported manifest mode");
|
|
2035
|
+
return (await inspectionGit(["show", `${revision}:${file}`], { cwd: root, maxBuffer: 1024 * 1024, timeout: 1e4 })).stdout;
|
|
2036
|
+
};
|
|
2037
|
+
const [before, after] = await Promise.all([read(from), read(head)]);
|
|
2038
|
+
const old = declarations(file, before), next = declarations(file, after);
|
|
2039
|
+
for (const dependency of /* @__PURE__ */ new Set([...old.keys(), ...next.keys()])) {
|
|
2040
|
+
if (old.get(dependency) === next.get(dependency)) continue;
|
|
2041
|
+
const escaped = dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2042
|
+
const mention = new RegExp(`(^|[^a-zA-Z0-9_@./-])${escaped}(?![a-zA-Z0-9_/-]|\\.[a-zA-Z0-9_])`, "i");
|
|
2043
|
+
const line = lines.findIndex((text2) => mention.test(text2));
|
|
2044
|
+
if (line < 0) continue;
|
|
2045
|
+
matches.push({
|
|
2046
|
+
dependency,
|
|
2047
|
+
manifest: file,
|
|
2048
|
+
before: old.get(dependency) ?? null,
|
|
2049
|
+
after: next.get(dependency) ?? null,
|
|
2050
|
+
line: line + 1,
|
|
2051
|
+
excerpt: lines[line].trim().slice(0, 500)
|
|
2052
|
+
});
|
|
2053
|
+
if (matches.length >= 100) return { matches, incomplete: true };
|
|
2054
|
+
}
|
|
2055
|
+
} catch {
|
|
2056
|
+
incomplete = true;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return { matches, incomplete };
|
|
2060
|
+
}
|
|
2061
|
+
var init_dependency_claims = __esm({
|
|
2062
|
+
"src/audit/dependency-claims.ts"() {
|
|
2063
|
+
"use strict";
|
|
2064
|
+
init_inspection();
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
|
|
1934
2068
|
// src/audit/release-metadata.ts
|
|
1935
2069
|
async function git(root, args2) {
|
|
1936
2070
|
return (await execGit(args2, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
|
|
@@ -1987,6 +2121,10 @@ var init_release_metadata = __esm({
|
|
|
1987
2121
|
});
|
|
1988
2122
|
|
|
1989
2123
|
// src/audit/checks/deps-changed.ts
|
|
2124
|
+
function hasDependencyContent(content2) {
|
|
2125
|
+
const text2 = content2.replace(/<!-- mason:start -->[\s\S]*?<!-- mason:end -->/g, "").replace(/<!--[\s\S]*?-->/g, "");
|
|
2126
|
+
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);
|
|
2127
|
+
}
|
|
1990
2128
|
function manifestPathspecs(doc) {
|
|
1991
2129
|
const scope = documentScope(doc);
|
|
1992
2130
|
if (scope === ".") return MANIFEST_PATHSPECS;
|
|
@@ -2006,12 +2144,17 @@ async function checkDepsChanged(ctx) {
|
|
|
2006
2144
|
});
|
|
2007
2145
|
continue;
|
|
2008
2146
|
}
|
|
2147
|
+
let content2 = doc.content;
|
|
2009
2148
|
if (doc.dirty) {
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2149
|
+
try {
|
|
2150
|
+
content2 = (await inspectionGit(
|
|
2151
|
+
["show", `${doc.lastCommit.hash}:${doc.path}`],
|
|
2152
|
+
{ cwd: ctx.root, maxBuffer: 10 * 1024 * 1024, timeout: 1e4 }
|
|
2153
|
+
)).stdout;
|
|
2154
|
+
} catch {
|
|
2155
|
+
result.skipped.push({ check: "deps-changed", doc: doc.path, reason: `${doc.path}: committed dependency content is unavailable` });
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2015
2158
|
}
|
|
2016
2159
|
const range = await commitsTouchingSince(
|
|
2017
2160
|
ctx.root,
|
|
@@ -2036,16 +2179,32 @@ async function checkDepsChanged(ctx) {
|
|
|
2036
2179
|
range.commits = relevant;
|
|
2037
2180
|
range.total = relevant.length;
|
|
2038
2181
|
if (range.total === 0) continue;
|
|
2039
|
-
const
|
|
2182
|
+
const relevance = await matchDependencyChanges(
|
|
2183
|
+
ctx.root,
|
|
2184
|
+
doc.lastCommit.hash,
|
|
2185
|
+
ctx.headHash,
|
|
2186
|
+
[...new Set(range.commits.flatMap((commit) => commit.files))],
|
|
2187
|
+
content2
|
|
2188
|
+
);
|
|
2189
|
+
if (!relevance.matches.length && !hasDependencyContent(content2)) continue;
|
|
2190
|
+
const match = relevance.matches[0];
|
|
2191
|
+
if (doc.dirty && match) result.skipped.push({
|
|
2192
|
+
check: "deps-changed",
|
|
2193
|
+
doc: doc.path,
|
|
2194
|
+
reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
|
|
2195
|
+
});
|
|
2040
2196
|
(doc.dirty ? result.suppressedAdvisories : result.advisories).push({
|
|
2041
2197
|
type: "deps-changed",
|
|
2042
|
-
|
|
2043
|
-
|
|
2198
|
+
...match ? {} : { resolution: "informational" },
|
|
2199
|
+
message: match ? `${match.dependency} changed from ${match.before ?? "absent"} to ${match.after ?? "removed"} in ${match.manifest}; review ${doc.path}:${match.line}: ${match.excerpt}${relevance.matches.length > 1 ? ` (${relevance.matches.length - 1} more matches in evidence)` : ""}` : `Dependency manifests changed after the document baseline ${doc.lastCommit.hash.slice(0, 7)} (${doc.lastCommit.date.slice(0, 10)}); no specific affected passage was established. Informational only.${relevance.incomplete ? " Some manifest declarations could not be matched." : ""}`,
|
|
2200
|
+
anchor: { doc: doc.path, line: match?.line ?? null, excerpt: match?.excerpt ?? null },
|
|
2044
2201
|
evidence: {
|
|
2045
2202
|
kind: "doc-behind-manifests",
|
|
2046
2203
|
docLastCommit: doc.lastCommit,
|
|
2047
2204
|
manifestCommits: range.commits.slice(0, MANIFEST_COMMITS_CAP),
|
|
2048
|
-
totalCommits: range.total
|
|
2205
|
+
totalCommits: range.total,
|
|
2206
|
+
matches: relevance.matches,
|
|
2207
|
+
matchingIncomplete: relevance.incomplete
|
|
2049
2208
|
}
|
|
2050
2209
|
});
|
|
2051
2210
|
}
|
|
@@ -2055,9 +2214,11 @@ var MANIFEST_COMMITS_CAP, MANIFEST_PATHSPECS;
|
|
|
2055
2214
|
var init_deps_changed = __esm({
|
|
2056
2215
|
"src/audit/checks/deps-changed.ts"() {
|
|
2057
2216
|
"use strict";
|
|
2217
|
+
init_dependency_claims();
|
|
2058
2218
|
init_docs();
|
|
2059
2219
|
init_release_metadata();
|
|
2060
2220
|
init_git();
|
|
2221
|
+
init_inspection();
|
|
2061
2222
|
init_checks();
|
|
2062
2223
|
MANIFEST_COMMITS_CAP = 10;
|
|
2063
2224
|
MANIFEST_PATHSPECS = [
|
|
@@ -2078,7 +2239,7 @@ var init_deps_changed = __esm({
|
|
|
2078
2239
|
});
|
|
2079
2240
|
|
|
2080
2241
|
// src/context/lexical.ts
|
|
2081
|
-
import
|
|
2242
|
+
import path17 from "path";
|
|
2082
2243
|
var init_lexical = __esm({
|
|
2083
2244
|
"src/context/lexical.ts"() {
|
|
2084
2245
|
"use strict";
|
|
@@ -2262,7 +2423,7 @@ var init_provenance = __esm({
|
|
|
2262
2423
|
|
|
2263
2424
|
// src/decisions/decisions.ts
|
|
2264
2425
|
import fs9 from "fs/promises";
|
|
2265
|
-
import
|
|
2426
|
+
import path18 from "path";
|
|
2266
2427
|
import { createHash } from "crypto";
|
|
2267
2428
|
import { z as z4 } from "zod";
|
|
2268
2429
|
async function loadDecisionStore(rootDir) {
|
|
@@ -2313,9 +2474,9 @@ var init_decisions = __esm({
|
|
|
2313
2474
|
});
|
|
2314
2475
|
|
|
2315
2476
|
// src/decisions/drift.ts
|
|
2316
|
-
import
|
|
2477
|
+
import path19 from "path";
|
|
2317
2478
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
2318
|
-
const resolvedRoot =
|
|
2479
|
+
const resolvedRoot = path19.resolve(rootDir);
|
|
2319
2480
|
const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
|
|
2320
2481
|
const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
|
|
2321
2482
|
const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
|
|
@@ -2511,7 +2672,9 @@ var init_findings = __esm({
|
|
|
2511
2672
|
kind: z5.literal("doc-behind-manifests"),
|
|
2512
2673
|
docLastCommit: commitSchema,
|
|
2513
2674
|
manifestCommits: z5.array(commitSchema.extend({ files: z5.array(z5.string()) })),
|
|
2514
|
-
totalCommits: count
|
|
2675
|
+
totalCommits: count,
|
|
2676
|
+
matches: z5.array(z5.object({ dependency: z5.string(), manifest: z5.string(), before: z5.string().nullable(), after: z5.string().nullable(), line: z5.number().int().positive(), excerpt: z5.string() })).optional(),
|
|
2677
|
+
matchingIncomplete: z5.boolean().optional()
|
|
2515
2678
|
}),
|
|
2516
2679
|
z5.object({
|
|
2517
2680
|
kind: z5.literal("decision-anchor"),
|
|
@@ -2527,7 +2690,7 @@ var init_findings = __esm({
|
|
|
2527
2690
|
type: z5.enum(["deleted-reference", "new-module", "stale-count", "dead-command"]),
|
|
2528
2691
|
confidence: z5.enum(["certain", "likely"])
|
|
2529
2692
|
});
|
|
2530
|
-
advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z5.
|
|
2693
|
+
advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z5.enum(["recheck", "informational"]).optional() });
|
|
2531
2694
|
checkResultSchema = z5.object({
|
|
2532
2695
|
issues: z5.array(issueSchema),
|
|
2533
2696
|
advisories: z5.array(advisorySchema),
|
|
@@ -2545,14 +2708,18 @@ async function readRecord(root, id) {
|
|
|
2545
2708
|
if (raw === null) return null;
|
|
2546
2709
|
const record = recordSchema.parse(raw);
|
|
2547
2710
|
const { digest: stored, ...payload } = record;
|
|
2548
|
-
if (record.id !== id || digest(payload) !== stored || record.events.some((event) => findingId(event.finding) !== id || event.
|
|
2711
|
+
if (record.id !== id || digest(payload) !== stored || record.events.some((event) => findingId(event.finding) !== id || (event.outcome === "dismissed" ? !event.reasonCode || event.finding.type !== "deps-changed" : !event.reviewer || !!event.reasonCode) || event.evidence.fingerprint !== evidenceFingerprint(event.evidence, event.reasonCode))) {
|
|
2549
2712
|
throw new Error("Advisory review record is inconsistent or modified; inspect its history.");
|
|
2550
2713
|
}
|
|
2551
2714
|
return record;
|
|
2552
2715
|
}
|
|
2553
|
-
function
|
|
2716
|
+
function evidenceFingerprint(evidence, reason2) {
|
|
2717
|
+
return digest([evidence.scope, evidence.tree, ...reason2 === "no-dependency-claims" ? [] : [evidence.lastScopeCommit], evidence.paths]);
|
|
2718
|
+
}
|
|
2719
|
+
function scopes(finding, reason2) {
|
|
2554
2720
|
const files = [finding.anchor.doc], e = finding.evidence;
|
|
2555
2721
|
if (e.kind === "decision-anchor") throw new Error("Use review_decision for decision acceptance, reaffirmation or retirement.");
|
|
2722
|
+
if (reason2 === "no-dependency-claims" && e.kind === "doc-behind-manifests") return [`:(literal)${finding.anchor.doc}`];
|
|
2556
2723
|
if (e.kind === "doc-behind-manifests") return [`:(literal)${finding.anchor.doc}`, ...manifestPathspecs(finding.anchor.doc)];
|
|
2557
2724
|
if (e.kind === "missing-path") files.push(...e.scope?.candidates ?? [e.resolvedPath ?? e.claimed]);
|
|
2558
2725
|
else if (e.kind === "missing-script") files.push(...e.manifestsChecked, ...e.scope?.candidates ?? []);
|
|
@@ -2563,9 +2730,9 @@ function scopes(finding) {
|
|
|
2563
2730
|
return `:(literal)${file}`;
|
|
2564
2731
|
}).sort();
|
|
2565
2732
|
}
|
|
2566
|
-
async function reviewEvidence(root, finding) {
|
|
2733
|
+
async function reviewEvidence(root, finding, reason2) {
|
|
2567
2734
|
if (normalizeRepoPath(finding.anchor.doc) !== finding.anchor.doc) throw new Error("Invalid advisory document path.");
|
|
2568
|
-
const scope = scopes(finding);
|
|
2735
|
+
const scope = scopes(finding, reason2);
|
|
2569
2736
|
const head = (await git2(root, "rev-parse", "HEAD")).trim();
|
|
2570
2737
|
const tracked = (await git2(root, "ls-files", "--cached", "-z", "--", ...scope)).split("\0").filter(Boolean);
|
|
2571
2738
|
if (tracked.length > 2e4) throw new Error("Advisory scope exceeds 20,000 tracked files.");
|
|
@@ -2591,7 +2758,7 @@ async function reviewEvidence(root, finding) {
|
|
|
2591
2758
|
if (!tracked.includes(file) && await pathExists(root, file)) dirty = true;
|
|
2592
2759
|
}
|
|
2593
2760
|
}
|
|
2594
|
-
const fingerprint =
|
|
2761
|
+
const fingerprint = evidenceFingerprint({ head, scope, tree, lastScopeCommit, paths, fingerprint: "" }, reason2);
|
|
2595
2762
|
if ((await git2(root, "rev-parse", "HEAD")).trim() !== head) throw new Error("HEAD changed while collecting review evidence.");
|
|
2596
2763
|
return {
|
|
2597
2764
|
...evidenceSchema2.parse({ head, scope, tree, lastScopeCommit, paths, fingerprint }),
|
|
@@ -2638,7 +2805,7 @@ async function assessAdvisory(root, finding, baselineHead) {
|
|
|
2638
2805
|
const record = await readRecord(root, id);
|
|
2639
2806
|
if (!record) return null;
|
|
2640
2807
|
const event = record.events.at(-1);
|
|
2641
|
-
const current = await reviewEvidence(root, event.finding);
|
|
2808
|
+
const current = await reviewEvidence(root, event.finding, event.reasonCode);
|
|
2642
2809
|
await git2(root, "merge-base", "--is-ancestor", event.evidence.head, current.head);
|
|
2643
2810
|
const base = {
|
|
2644
2811
|
id,
|
|
@@ -2648,7 +2815,7 @@ async function assessAdvisory(root, finding, baselineHead) {
|
|
|
2648
2815
|
outcome: event.outcome,
|
|
2649
2816
|
reviewedHead: event.evidence.head
|
|
2650
2817
|
};
|
|
2651
|
-
if (current.dirty || current.fingerprint !== event.evidence.fingerprint || digest(scopes(finding)) !== digest(event.evidence.scope)) return { ...base, status: "reopened", reason: "Relevant document or repository evidence changed after the assessment; prepare a new review." };
|
|
2818
|
+
if (current.dirty || current.fingerprint !== event.evidence.fingerprint || digest(scopes(finding, event.reasonCode)) !== digest(event.evidence.scope)) return { ...base, status: "reopened", reason: "Relevant document or repository evidence changed after the assessment; prepare a new review." };
|
|
2652
2819
|
return { ...base, status: event.outcome === "deferred" ? "deferred" : "current", reason: event.outcome === "deferred" ? "The review was deferred and remains outstanding." : "An explicit assessment covers the current committed scope. This does not approve an engineering decision or certify correctness." };
|
|
2653
2820
|
} catch (error) {
|
|
2654
2821
|
return { id, status: "unverified", reason: error instanceof Error ? error.message : String(error) };
|
|
@@ -2665,7 +2832,7 @@ async function advisoryReviewInventory(root) {
|
|
|
2665
2832
|
if (names.length > 5e3) throw new Error("Advisory review inventory exceeds 5,000 records.");
|
|
2666
2833
|
return Promise.all(names.filter((name) => name.endsWith(".json")).sort().map(async (name) => [name, await readAuditInput(root, ".mason/reviews/advisories/" + name)]));
|
|
2667
2834
|
}
|
|
2668
|
-
var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
|
|
2835
|
+
var git2, hashSchema, commitSchema2, evidenceSchema2, dismissalReasonSchema, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
|
|
2669
2836
|
var init_advisory_review = __esm({
|
|
2670
2837
|
"src/audit/advisory-review.ts"() {
|
|
2671
2838
|
"use strict";
|
|
@@ -2691,11 +2858,13 @@ var init_advisory_review = __esm({
|
|
|
2691
2858
|
lastScopeCommit: commitSchema2,
|
|
2692
2859
|
paths: z6.array(z6.object({ path: z6.string(), exists: z6.boolean() })).max(1e3)
|
|
2693
2860
|
});
|
|
2861
|
+
dismissalReasonSchema = z6.enum(["no-dependency-claims", "unrelated-manifest-change"]);
|
|
2694
2862
|
eventSchema2 = z6.object({
|
|
2695
2863
|
at: z6.string().datetime(),
|
|
2696
|
-
reviewer: z6.string().trim().min(1).max(200),
|
|
2864
|
+
reviewer: z6.string().trim().min(1).max(200).optional(),
|
|
2697
2865
|
note: z6.string().trim().min(1).max(2500),
|
|
2698
|
-
outcome: z6.enum(["addressed", "inapplicable", "deferred"]),
|
|
2866
|
+
outcome: z6.enum(["addressed", "inapplicable", "deferred", "dismissed"]),
|
|
2867
|
+
reasonCode: dismissalReasonSchema.optional(),
|
|
2699
2868
|
finding: advisorySchema.refine((finding) => finding.evidence.kind !== "decision-anchor", "Decision findings use decision reviews"),
|
|
2700
2869
|
evidence: evidenceSchema2
|
|
2701
2870
|
});
|
|
@@ -2707,13 +2876,14 @@ var init_advisory_review = __esm({
|
|
|
2707
2876
|
recordPath: z6.string().optional(),
|
|
2708
2877
|
reviewer: z6.string().optional(),
|
|
2709
2878
|
note: z6.string().optional(),
|
|
2710
|
-
outcome: z6.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired"]).optional(),
|
|
2879
|
+
outcome: z6.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired", "dismissed"]).optional(),
|
|
2711
2880
|
reviewedHead: z6.string().optional()
|
|
2712
2881
|
});
|
|
2713
2882
|
advisoryReviewRequest = z6.object({
|
|
2714
2883
|
baselinePath: z6.string().min(1),
|
|
2715
2884
|
findingId: hashSchema,
|
|
2716
|
-
action: z6.enum(["prepare", "addressed", "inapplicable", "deferred"]).default("prepare"),
|
|
2885
|
+
action: z6.enum(["prepare", "addressed", "inapplicable", "deferred", "dismiss"]).default("prepare"),
|
|
2886
|
+
reasonCode: dismissalReasonSchema.optional(),
|
|
2717
2887
|
reviewer: eventSchema2.shape.reviewer.optional(),
|
|
2718
2888
|
note: eventSchema2.shape.note.optional(),
|
|
2719
2889
|
reviewToken: hashSchema.optional()
|
|
@@ -2724,12 +2894,12 @@ var init_advisory_review = __esm({
|
|
|
2724
2894
|
|
|
2725
2895
|
// src/audit/audit.ts
|
|
2726
2896
|
import fs11 from "fs/promises";
|
|
2727
|
-
import
|
|
2897
|
+
import path20 from "path";
|
|
2728
2898
|
async function computeAudit(rootDir, options = {}) {
|
|
2729
2899
|
return profilePhase("audit.current", () => auditCurrent(rootDir, options));
|
|
2730
2900
|
}
|
|
2731
2901
|
async function auditCurrent(rootDir, options) {
|
|
2732
|
-
const resolvedRoot =
|
|
2902
|
+
const resolvedRoot = path20.resolve(rootDir);
|
|
2733
2903
|
const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
|
|
2734
2904
|
if (docs.length === 0) return null;
|
|
2735
2905
|
const report = {
|
|
@@ -2763,7 +2933,7 @@ async function auditCurrent(rootDir, options) {
|
|
|
2763
2933
|
}
|
|
2764
2934
|
let decisionsPresent = false;
|
|
2765
2935
|
try {
|
|
2766
|
-
await fs11.access(
|
|
2936
|
+
await fs11.access(path20.join(resolvedRoot, ".mason", "decisions"));
|
|
2767
2937
|
decisionsPresent = true;
|
|
2768
2938
|
} catch {
|
|
2769
2939
|
}
|
|
@@ -2775,13 +2945,21 @@ async function auditCurrent(rootDir, options) {
|
|
|
2775
2945
|
changesSinceDoc,
|
|
2776
2946
|
decisionsPresent
|
|
2777
2947
|
};
|
|
2948
|
+
const policy = (await loadProjectConfig(resolvedRoot)).audit;
|
|
2778
2949
|
const selected = options.checks ?? ALL_CHECKS;
|
|
2779
2950
|
for (const name of ALL_CHECKS) {
|
|
2780
2951
|
if (!selected.includes(name)) continue;
|
|
2781
|
-
const
|
|
2952
|
+
const exclusions = policy?.exclude?.[name] ?? [];
|
|
2953
|
+
const included = await includedCheckPaths(resolvedRoot, docs.map((doc) => doc.path), exclusions);
|
|
2954
|
+
const excluded = docs.filter((doc) => !included.has(doc.path)).map((doc) => ({ check: name, doc: doc.path }));
|
|
2955
|
+
if (excluded.length) (report.excludedChecks ??= []).push(...excluded);
|
|
2956
|
+
const scoped = { ...ctx, docs: docs.filter((doc) => included.has(doc.path)) };
|
|
2957
|
+
const { issues, advisories, suppressedAdvisories, skipped } = await (options.runCheck ? options.runCheck(name, scoped) : CHECKS[name](scoped));
|
|
2782
2958
|
report.checksRun.push(name);
|
|
2783
2959
|
report.issues.push(...issues);
|
|
2784
|
-
|
|
2960
|
+
const advisoryPaths = await includedCheckPaths(resolvedRoot, [...new Set(advisories.map((f) => f.anchor.doc))], exclusions);
|
|
2961
|
+
report.advisories.push(...advisories.filter((f) => advisoryPaths.has(f.anchor.doc)));
|
|
2962
|
+
for (const f of advisories) if (!advisoryPaths.has(f.anchor.doc)) (report.excludedChecks ??= []).push({ check: name, doc: f.anchor.doc });
|
|
2785
2963
|
report.suppressedAdvisories.push(...suppressedAdvisories ?? []);
|
|
2786
2964
|
report.skippedChecks.push(...skipped);
|
|
2787
2965
|
}
|
|
@@ -2793,6 +2971,8 @@ async function auditCurrent(rootDir, options) {
|
|
|
2793
2971
|
var init_audit = __esm({
|
|
2794
2972
|
"src/audit/audit.ts"() {
|
|
2795
2973
|
"use strict";
|
|
2974
|
+
init_files();
|
|
2975
|
+
init_policy();
|
|
2796
2976
|
init_profile();
|
|
2797
2977
|
init_drift();
|
|
2798
2978
|
init_snapshot();
|
|
@@ -2805,7 +2985,7 @@ var init_audit = __esm({
|
|
|
2805
2985
|
|
|
2806
2986
|
// src/audit/repair.ts
|
|
2807
2987
|
import fs12 from "fs/promises";
|
|
2808
|
-
import
|
|
2988
|
+
import path21 from "path";
|
|
2809
2989
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2810
2990
|
import { z as z7 } from "zod";
|
|
2811
2991
|
function recheckable(finding) {
|
|
@@ -2826,7 +3006,7 @@ async function docState(root) {
|
|
|
2826
3006
|
docs.push([doc, null]);
|
|
2827
3007
|
}
|
|
2828
3008
|
}
|
|
2829
|
-
return digest(docs);
|
|
3009
|
+
return digest([docs, (await loadProjectConfig(root)).audit]);
|
|
2830
3010
|
}
|
|
2831
3011
|
async function stableAudit(root, checks, options = {}) {
|
|
2832
3012
|
return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
|
|
@@ -2892,8 +3072,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
|
|
|
2892
3072
|
}
|
|
2893
3073
|
async function loadRepairBaseline(rootDir, baselinePath) {
|
|
2894
3074
|
const root = await fs12.realpath(rootDir);
|
|
2895
|
-
const declaredRoot =
|
|
2896
|
-
const relative =
|
|
3075
|
+
const declaredRoot = path21.resolve(rootDir);
|
|
3076
|
+
const relative = path21.isAbsolute(baselinePath) ? path21.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
|
|
2897
3077
|
const stored = baselineSchema.parse(await readStoreJson(root, relative));
|
|
2898
3078
|
const { digest: savedDigest, ...payload } = stored;
|
|
2899
3079
|
if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
|
|
@@ -2939,6 +3119,9 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
|
2939
3119
|
if (now && ("confidence" in finding || "confidence" in now)) {
|
|
2940
3120
|
return { ...base, status: "unresolved", reason: "The original check still reports this claim." };
|
|
2941
3121
|
}
|
|
3122
|
+
if (current.excludedChecks?.some((excluded) => excluded.check === finding.type && excluded.doc === finding.anchor.doc)) {
|
|
3123
|
+
return { ...base, status: "resolved", reason: "Excluded by project audit policy; this is not a correctness assessment. Original evidence is retained." };
|
|
3124
|
+
}
|
|
2942
3125
|
const skipped = current.skippedChecks.filter((s) => s.check === finding.type && (!s.doc || s.doc === finding.anchor.doc));
|
|
2943
3126
|
if (!current.checksRun?.includes(finding.type) || skipped.length) {
|
|
2944
3127
|
return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
|
|
@@ -2949,11 +3132,14 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
|
2949
3132
|
if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
|
|
2950
3133
|
return { ...base, review, status: "review-required", reason: review.reason };
|
|
2951
3134
|
}
|
|
3135
|
+
if (!("confidence" in finding) && finding.evidence.kind === "doc-behind-manifests" && !finding.evidence.matches?.length && (!now || !("confidence" in now) && now.resolution === "informational")) {
|
|
3136
|
+
return { ...base, status: "resolved", reason: "Manifest recency alone establishes no affected passage. Retained as information; no assessment is required." };
|
|
3137
|
+
}
|
|
2952
3138
|
if (!("confidence" in finding) && (!recheckable(finding) || now)) {
|
|
2953
3139
|
return {
|
|
2954
3140
|
...base,
|
|
2955
3141
|
status: "review-required",
|
|
2956
|
-
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."
|
|
3142
|
+
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."
|
|
2957
3143
|
};
|
|
2958
3144
|
}
|
|
2959
3145
|
return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
|
|
@@ -2977,7 +3163,7 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
|
2977
3163
|
const reviewedIds = new Set(current?.advisoryReviews?.filter((review) => review.status === "current").map((review) => review.id));
|
|
2978
3164
|
const counts = { resolved: 0, unresolved: 0, "review-required": 0, unverified: 0 };
|
|
2979
3165
|
for (const f of findings) counts[f.status]++;
|
|
2980
|
-
const incomplete = diagnostics.length > 0 || counts.unverified > 0 || counts["review-required"] > 0 || (current?.skippedChecks.length ?? 0) > 0 || newFindings.some((f) => !("confidence" in f) && !reviewedIds.has(findingId(f)));
|
|
3166
|
+
const incomplete = diagnostics.length > 0 || counts.unverified > 0 || counts["review-required"] > 0 || (current?.skippedChecks.length ?? 0) > 0 || newFindings.some((f) => !("confidence" in f) && f.resolution !== "informational" && !reviewedIds.has(findingId(f)));
|
|
2981
3167
|
const issuesRemain = counts.unresolved > 0 || newFindings.some((f) => "confidence" in f);
|
|
2982
3168
|
return {
|
|
2983
3169
|
version: 1,
|
|
@@ -3020,13 +3206,14 @@ var init_repair = __esm({
|
|
|
3020
3206
|
docs: z7.array(z7.object({
|
|
3021
3207
|
path: z7.string().refine((value) => normalizeRepoPath(value) === value),
|
|
3022
3208
|
scope: z7.string().optional(),
|
|
3023
|
-
kind: z7.enum(["instructions", "readme"]).optional(),
|
|
3209
|
+
kind: z7.enum(["instructions", "readme", "guide"]).optional(),
|
|
3024
3210
|
lastCommit: commitSchema.nullable(),
|
|
3025
3211
|
dirty: z7.boolean(),
|
|
3026
3212
|
lineCount: count2
|
|
3027
3213
|
})).nonempty(),
|
|
3028
3214
|
decisionsChecked: z7.boolean(),
|
|
3029
3215
|
clean: z7.boolean(),
|
|
3216
|
+
excludedChecks: z7.array(z7.object({ check: checkSchema, doc: z7.string() })).optional(),
|
|
3030
3217
|
issues: z7.array(issueSchema),
|
|
3031
3218
|
advisories: z7.array(advisorySchema),
|
|
3032
3219
|
suppressedAdvisories: z7.array(advisorySchema).optional(),
|
|
@@ -3052,9 +3239,9 @@ var init_repair = __esm({
|
|
|
3052
3239
|
|
|
3053
3240
|
// src/automation/evidence.ts
|
|
3054
3241
|
import fs13 from "fs/promises";
|
|
3055
|
-
import
|
|
3242
|
+
import path22 from "path";
|
|
3056
3243
|
import { createHash as createHash3 } from "crypto";
|
|
3057
|
-
import
|
|
3244
|
+
import fg7 from "fast-glob";
|
|
3058
3245
|
import { z as z8 } from "zod";
|
|
3059
3246
|
async function git3(root, ...args2) {
|
|
3060
3247
|
return profilePhase("automation.git", async () => (await execGit(args2, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
|
|
@@ -3072,6 +3259,11 @@ async function readInputs(root) {
|
|
|
3072
3259
|
}
|
|
3073
3260
|
async function collectInputs(root) {
|
|
3074
3261
|
const docPaths = await discoverDocPaths(root);
|
|
3262
|
+
const auditPolicy = (await loadProjectConfig(root)).audit;
|
|
3263
|
+
const allowed = /* @__PURE__ */ new Map();
|
|
3264
|
+
for (const name of ["deleted-reference", "new-module", "stale-count", "dead-command"]) {
|
|
3265
|
+
allowed.set(name, await includedCheckPaths(root, docPaths, auditPolicy?.exclude?.[name] ?? []));
|
|
3266
|
+
}
|
|
3075
3267
|
const [headText, docStatus, shallowPath, replacements] = await Promise.all([
|
|
3076
3268
|
git3(root, "rev-parse", "HEAD"),
|
|
3077
3269
|
docPaths.length ? git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...docPaths.map((p) => `:(literal)${p}`)) : "",
|
|
@@ -3081,7 +3273,7 @@ async function collectInputs(root) {
|
|
|
3081
3273
|
const head = headText.trim();
|
|
3082
3274
|
let shallow = null;
|
|
3083
3275
|
try {
|
|
3084
|
-
shallow = await fs13.readFile(
|
|
3276
|
+
shallow = await fs13.readFile(path22.resolve(root, shallowPath.trim()), "utf8");
|
|
3085
3277
|
} catch (error) {
|
|
3086
3278
|
if (error.code !== "ENOENT") throw error;
|
|
3087
3279
|
}
|
|
@@ -3100,18 +3292,18 @@ async function collectInputs(root) {
|
|
|
3100
3292
|
docContents.push([file, text2]);
|
|
3101
3293
|
const parsed = { path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") };
|
|
3102
3294
|
parsedDocs.push(parsed);
|
|
3103
|
-
for (const claim of parsed.claims.paths) {
|
|
3295
|
+
for (const claim of allowed.get("deleted-reference").has(file) ? parsed.claims.paths : []) {
|
|
3104
3296
|
const scope = pathClaimScope(file, claim);
|
|
3105
3297
|
if (scope?.candidates.some(gitMetadataPath)) continue;
|
|
3106
3298
|
for (const candidate of scope?.candidates ?? []) {
|
|
3107
3299
|
if (optionalMasonPath(candidate)) continue;
|
|
3108
|
-
const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(
|
|
3300
|
+
const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path22.posix.dirname(candidate))]);
|
|
3109
3301
|
claims.push([candidate, filePresent, parentPresent]);
|
|
3110
3302
|
}
|
|
3111
3303
|
}
|
|
3112
3304
|
}
|
|
3113
|
-
const combinedDocs = moduleDocumentation(parsedDocs);
|
|
3114
|
-
const countClaims = parsedDocs.flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
|
|
3305
|
+
const combinedDocs = moduleDocumentation(parsedDocs.filter((doc) => allowed.get("new-module").has(doc.path)));
|
|
3306
|
+
const countClaims = parsedDocs.filter((doc) => allowed.get("stale-count").has(doc.path)).flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
|
|
3115
3307
|
const decisionDirectory = await storePath(root, ".mason/decisions");
|
|
3116
3308
|
const decisionPresence = await fs13.lstat(decisionDirectory).then((stat) => stat.isDirectory() ? "directory" : "file", (error) => {
|
|
3117
3309
|
if (error.code === "ENOENT") return "absent";
|
|
@@ -3120,8 +3312,8 @@ async function collectInputs(root) {
|
|
|
3120
3312
|
const [modules, counts, commands2, decisionFiles] = await Promise.all([
|
|
3121
3313
|
combinedDocs ? moduleCandidates(root, combinedDocs) : [],
|
|
3122
3314
|
Promise.all(countClaims.map(({ doc, claim }) => resolveDocCountSource(root, doc, claim))),
|
|
3123
|
-
commandInputs(root, parsedDocs),
|
|
3124
|
-
|
|
3315
|
+
commandInputs(root, parsedDocs.filter((doc) => allowed.get("dead-command").has(doc.path))),
|
|
3316
|
+
fg7(".mason/decisions/*.json", { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false })
|
|
3125
3317
|
]);
|
|
3126
3318
|
const decisions = await Promise.all(decisionFiles.sort().map(async (file) => {
|
|
3127
3319
|
await storePath(root, file);
|
|
@@ -3131,7 +3323,7 @@ async function collectInputs(root) {
|
|
|
3131
3323
|
git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
|
|
3132
3324
|
git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
|
|
3133
3325
|
]) : ["", ""];
|
|
3134
|
-
const common = [
|
|
3326
|
+
const common = [7, engineVersion, head, shallow, replacements, docContents, auditPolicy];
|
|
3135
3327
|
const keys = {
|
|
3136
3328
|
"deleted-reference": hash([common, claims, docStatus]),
|
|
3137
3329
|
"new-module": hash([common, modules]),
|
|
@@ -3178,6 +3370,8 @@ var engineVersion, hash, content, cacheSchema;
|
|
|
3178
3370
|
var init_evidence = __esm({
|
|
3179
3371
|
"src/automation/evidence.ts"() {
|
|
3180
3372
|
"use strict";
|
|
3373
|
+
init_policy();
|
|
3374
|
+
init_files();
|
|
3181
3375
|
init_git_read();
|
|
3182
3376
|
init_profile();
|
|
3183
3377
|
init_docs();
|
|
@@ -3191,7 +3385,7 @@ var init_evidence = __esm({
|
|
|
3191
3385
|
init_dead_command();
|
|
3192
3386
|
init_storage();
|
|
3193
3387
|
init_advisory_review();
|
|
3194
|
-
engineVersion = true ? "0.
|
|
3388
|
+
engineVersion = true ? "0.19.1" : "development";
|
|
3195
3389
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3196
3390
|
content = readAuditInput;
|
|
3197
3391
|
cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
|
|
@@ -3405,13 +3599,17 @@ async function observeReadOnlyTool(dir, event) {
|
|
|
3405
3599
|
}
|
|
3406
3600
|
function summarize(report) {
|
|
3407
3601
|
const open = report.findings.filter((f) => f.status !== "resolved");
|
|
3602
|
+
const onlyReview = report.status === "incomplete" && !report.diagnostics.length && !report.counts.unresolved && !report.counts.unverified && report.counts["review-required"] > 0;
|
|
3408
3603
|
return [
|
|
3409
|
-
`Mason: ${report.status}; ${report.counts.unresolved} unresolved, ${report.counts["review-required"]} need review, ${report.counts.unverified} unverified.`,
|
|
3410
|
-
...open.slice(0, 4).map((f) =>
|
|
3604
|
+
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.`,
|
|
3605
|
+
...open.slice(0, 4).map((f) => {
|
|
3606
|
+
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}`;
|
|
3607
|
+
return `[${f.status}] ${cleanText(f.original.anchor.doc)}: ${cleanText(detail)}`;
|
|
3608
|
+
}),
|
|
3411
3609
|
...open.length > 4 ? [`${open.length - 4} more findings in the report.`] : [],
|
|
3412
3610
|
...report.diagnostics.slice(0, 2).map(cleanText),
|
|
3413
3611
|
`Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason-auto check.`,
|
|
3414
|
-
"Keep original evidence. Address findings relevant to the authorized task; report unrelated findings and
|
|
3612
|
+
"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."
|
|
3415
3613
|
].join("\n");
|
|
3416
3614
|
}
|
|
3417
3615
|
async function loadState(ws) {
|
|
@@ -3635,7 +3833,7 @@ async function automate(dir, event) {
|
|
|
3635
3833
|
if (event.event === "after_tool" && event.mutating) {
|
|
3636
3834
|
session.mutationObserved = true;
|
|
3637
3835
|
if (!event.toolId || !session.pending[event.toolId]) {
|
|
3638
|
-
const gap = "A tool completed without an observed matching pre-tool capture; pre-edit coverage is unknown.";
|
|
3836
|
+
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.";
|
|
3639
3837
|
if (!session.coverageGaps.includes(gap)) session.coverageGaps.push(gap);
|
|
3640
3838
|
}
|
|
3641
3839
|
if (event.toolId) delete session.pending[event.toolId];
|
|
@@ -3745,9 +3943,9 @@ var init_runtime = __esm({
|
|
|
3745
3943
|
|
|
3746
3944
|
// src/automation/hook-root.ts
|
|
3747
3945
|
import fs15 from "fs/promises";
|
|
3748
|
-
import
|
|
3946
|
+
import path23 from "path";
|
|
3749
3947
|
async function repositoryRoot(dir) {
|
|
3750
|
-
if (!
|
|
3948
|
+
if (!path23.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
|
|
3751
3949
|
try {
|
|
3752
3950
|
return await fs15.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
|
|
3753
3951
|
} catch (error) {
|
|
@@ -3778,11 +3976,39 @@ var init_hook_root = __esm({
|
|
|
3778
3976
|
|
|
3779
3977
|
// src/setup/model.ts
|
|
3780
3978
|
import { z as z11 } from "zod";
|
|
3979
|
+
import fs16 from "fs/promises";
|
|
3980
|
+
import path24 from "path";
|
|
3781
3981
|
async function loadSetup(root) {
|
|
3782
3982
|
const raw = await readStoreJson(root, SETUP_PATH);
|
|
3783
3983
|
if (raw === null) return null;
|
|
3784
3984
|
return setupSchema.parse(raw);
|
|
3785
3985
|
}
|
|
3986
|
+
async function effectiveSetup(root) {
|
|
3987
|
+
const local = await loadSetup(root);
|
|
3988
|
+
if (local) return { root, setup: local };
|
|
3989
|
+
const common = await fs16.realpath(path24.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
|
|
3990
|
+
const own = await fs16.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
|
|
3991
|
+
if (common === own) return { root, setup: null };
|
|
3992
|
+
const primary = (await git3(root, "worktree", "list", "--porcelain", "-z")).split("\0\0")[0].split("\0");
|
|
3993
|
+
const candidate = primary.find((field) => field.startsWith("worktree "))?.slice(9);
|
|
3994
|
+
if (!candidate || primary.includes("bare")) return { root, setup: null };
|
|
3995
|
+
let inheritedRoot;
|
|
3996
|
+
try {
|
|
3997
|
+
inheritedRoot = await fs16.realpath((await git3(candidate, "rev-parse", "--show-toplevel")).trim());
|
|
3998
|
+
} catch (error) {
|
|
3999
|
+
if (/must be run in a work tree/.test(String(error.stderr))) return { root, setup: null };
|
|
4000
|
+
throw error;
|
|
4001
|
+
}
|
|
4002
|
+
const inheritedCommon = await fs16.realpath(path24.resolve(
|
|
4003
|
+
inheritedRoot,
|
|
4004
|
+
(await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim()
|
|
4005
|
+
));
|
|
4006
|
+
if (inheritedCommon !== common) throw new Error("Linked worktree setup does not belong to the same Git repository.");
|
|
4007
|
+
const setup = await loadSetup(inheritedRoot);
|
|
4008
|
+
const inactive = await readStoreJson(root, INACTIVE_HOSTS_PATH);
|
|
4009
|
+
if (inactive && setup) for (const host of inactiveHostsSchema.parse(inactive).hosts) delete setup.hosts[host];
|
|
4010
|
+
return { root: inheritedRoot, setup };
|
|
4011
|
+
}
|
|
3786
4012
|
async function loadSetupReceipt(root, directory, host) {
|
|
3787
4013
|
const raw = await readStoreJson(root, directory + "/setup-" + host + ".json");
|
|
3788
4014
|
if (raw === null) return null;
|
|
@@ -3790,10 +4016,11 @@ async function loadSetupReceipt(root, directory, host) {
|
|
|
3790
4016
|
if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
|
|
3791
4017
|
return receipt;
|
|
3792
4018
|
}
|
|
3793
|
-
var setupHostSchema, setupSchema, SETUP_PATH, receiptSchema;
|
|
4019
|
+
var setupHostSchema, setupSchema, SETUP_PATH, INACTIVE_HOSTS_PATH, inactiveHostsSchema, receiptSchema;
|
|
3794
4020
|
var init_model = __esm({
|
|
3795
4021
|
"src/setup/model.ts"() {
|
|
3796
4022
|
"use strict";
|
|
4023
|
+
init_evidence();
|
|
3797
4024
|
init_storage();
|
|
3798
4025
|
setupHostSchema = z11.object({
|
|
3799
4026
|
configuredVersion: z11.string(),
|
|
@@ -3807,6 +4034,8 @@ var init_model = __esm({
|
|
|
3807
4034
|
claude: setupHostSchema.optional()
|
|
3808
4035
|
}) });
|
|
3809
4036
|
SETUP_PATH = ".mason/local/setup.json";
|
|
4037
|
+
INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
|
|
4038
|
+
inactiveHostsSchema = z11.object({ version: z11.literal(1), hosts: z11.array(z11.enum(["claude", "codex"])) });
|
|
3810
4039
|
receiptSchema = z11.object({
|
|
3811
4040
|
version: z11.literal(1),
|
|
3812
4041
|
host: z11.enum(["codex", "claude"]),
|
|
@@ -3833,7 +4062,7 @@ __export(launcher_exports, {
|
|
|
3833
4062
|
});
|
|
3834
4063
|
import { execFile as execFile2 } from "child_process";
|
|
3835
4064
|
import { promisify as promisify2 } from "util";
|
|
3836
|
-
import
|
|
4065
|
+
import fs17 from "fs/promises";
|
|
3837
4066
|
function globalCommand(args2, platform = process.platform) {
|
|
3838
4067
|
return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args2] } : { command: "mason", args: args2 };
|
|
3839
4068
|
}
|
|
@@ -3853,8 +4082,8 @@ async function installedCommand() {
|
|
|
3853
4082
|
}
|
|
3854
4083
|
}
|
|
3855
4084
|
async function prepareLaunch(host, options = {}) {
|
|
3856
|
-
const root = await
|
|
3857
|
-
const entry = (await
|
|
4085
|
+
const root = await fs17.realpath((await git3(options.dir ?? process.cwd(), "rev-parse", "--show-toplevel")).trim());
|
|
4086
|
+
const entry = (await effectiveSetup(root)).setup?.hosts[host];
|
|
3858
4087
|
process.env.MASON_SETUP_ROOT = root;
|
|
3859
4088
|
process.env.MASON_SETUP_HOST = host;
|
|
3860
4089
|
delete process.env.MASON_SETUP_REVISION;
|
|
@@ -3876,7 +4105,7 @@ var init_launcher = __esm({
|
|
|
3876
4105
|
"use strict";
|
|
3877
4106
|
init_evidence();
|
|
3878
4107
|
init_model();
|
|
3879
|
-
executingVersion = () => true ? "0.
|
|
4108
|
+
executingVersion = () => true ? "0.19.1" : "development";
|
|
3880
4109
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
3881
4110
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
3882
4111
|
}
|
|
@@ -3889,7 +4118,7 @@ __export(observations_exports, {
|
|
|
3889
4118
|
observeActivation: () => observeActivation,
|
|
3890
4119
|
readObservation: () => readObservation
|
|
3891
4120
|
});
|
|
3892
|
-
import
|
|
4121
|
+
import fs18 from "fs/promises";
|
|
3893
4122
|
import { z as z12 } from "zod";
|
|
3894
4123
|
function observationPath(directory, host, revision, version = executingVersion()) {
|
|
3895
4124
|
return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
@@ -3906,15 +4135,15 @@ async function observeActivation(dir, event, options = {}) {
|
|
|
3906
4135
|
if (host !== "codex" && host !== "claude" || !process.env.MASON_SETUP_ROOT) return null;
|
|
3907
4136
|
try {
|
|
3908
4137
|
if (!revision) {
|
|
3909
|
-
if ((await
|
|
4138
|
+
if ((await effectiveSetup(process.env.MASON_SETUP_ROOT)).setup?.hosts[host]) {
|
|
3910
4139
|
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.";
|
|
3911
4140
|
}
|
|
3912
4141
|
return inactiveSetupMessage();
|
|
3913
4142
|
}
|
|
3914
4143
|
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];
|
|
3915
|
-
const ws = capturedDirectory ? { root: await
|
|
3916
|
-
if (ws.root !== await
|
|
3917
|
-
const setup = await
|
|
4144
|
+
const ws = capturedDirectory ? { root: await fs18.realpath(dir), directory: capturedDirectory } : await workspace(dir);
|
|
4145
|
+
if (ws.root !== await fs18.realpath(process.env.MASON_SETUP_ROOT)) return null;
|
|
4146
|
+
const { setup } = await effectiveSetup(ws.root);
|
|
3918
4147
|
if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
|
|
3919
4148
|
const directory = ws.directory + "/activation";
|
|
3920
4149
|
await withLock(ws.root, directory, async () => {
|
|
@@ -4091,8 +4320,8 @@ __export(files_exports, {
|
|
|
4091
4320
|
managedBlock: () => managedBlock,
|
|
4092
4321
|
readText: () => readText
|
|
4093
4322
|
});
|
|
4094
|
-
import
|
|
4095
|
-
import
|
|
4323
|
+
import fs19 from "fs/promises";
|
|
4324
|
+
import path25 from "path";
|
|
4096
4325
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
4097
4326
|
async function readText(root, file) {
|
|
4098
4327
|
try {
|
|
@@ -4121,14 +4350,14 @@ async function applyEdit(root, edit) {
|
|
|
4121
4350
|
if (edit.after === null) {
|
|
4122
4351
|
const file2 = await storePath(root, edit.path);
|
|
4123
4352
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
|
|
4124
|
-
await
|
|
4353
|
+
await fs19.unlink(file2);
|
|
4125
4354
|
return true;
|
|
4126
4355
|
}
|
|
4127
4356
|
const file = await storePath(root, edit.path, true);
|
|
4128
|
-
const temporary =
|
|
4357
|
+
const temporary = path25.join(path25.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
4129
4358
|
try {
|
|
4130
|
-
const mode = await
|
|
4131
|
-
const handle = await
|
|
4359
|
+
const mode = await fs19.stat(file).then((s) => s.mode & 511, () => 384);
|
|
4360
|
+
const handle = await fs19.open(temporary, "wx", mode);
|
|
4132
4361
|
try {
|
|
4133
4362
|
await handle.writeFile(edit.after, "utf8");
|
|
4134
4363
|
await handle.sync();
|
|
@@ -4136,10 +4365,10 @@ async function applyEdit(root, edit) {
|
|
|
4136
4365
|
await handle.close();
|
|
4137
4366
|
}
|
|
4138
4367
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
|
|
4139
|
-
await
|
|
4368
|
+
await fs19.rename(temporary, file);
|
|
4140
4369
|
return true;
|
|
4141
4370
|
} finally {
|
|
4142
|
-
await
|
|
4371
|
+
await fs19.rm(temporary, { force: true });
|
|
4143
4372
|
}
|
|
4144
4373
|
}
|
|
4145
4374
|
var init_files2 = __esm({
|
|
@@ -4250,9 +4479,11 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
|
|
|
4250
4479
|
- For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
|
|
4251
4480
|
- \`mason_init\` returns documentation audit and committed-diff review results, plus a short setup guide. Pass \`evidence\` with local CI manifest paths to include test and analysis results; the CLI equivalent is \`mason-review --evidence <manifest>\`. State skipped, unavailable, stale, or unknown checks explicitly. Related accepted decisions identify review context, not proven violations.
|
|
4252
4481
|
|
|
4253
|
-
- When documentation repair is authorized, use \`mason_repair(action: "prepare")\` before edits, keep its baselinePath, and use \`mason_repair(action: "verify", baselinePath)\` after edits and any final doc commit. Report every original finding's outcome and any new findings.
|
|
4482
|
+
- When documentation repair is authorized, use \`mason_repair(action: "prepare")\` before edits, keep its baselinePath, and use \`mason_repair(action: "verify", baselinePath)\` after edits and any final doc commit. Report every original finding's outcome and any new findings. Specific suppressed advisories remain unresolved; editing a doc does not approve it. Informational dependency notices require no review.
|
|
4254
4483
|
- When advisory assessment is authorized, use \`review_advisory(action: "prepare", baselinePath, findingId)\` to inspect original evidence and history. Record \`addressed\`, \`inapplicable\`, or \`deferred\` with the actual reviewer, reason in note, and returned reviewToken only after relevant edits are committed. Commit the assessment and verify the same baseline again. Relevant evidence changes reopen it; deferral stays outstanding. Decision findings use \`review_decision\`; advisory closure never approves a decision.
|
|
4255
4484
|
|
|
4485
|
+
- For a dependency advisory, \`review_advisory(action: "dismiss", baselinePath, findingId, reasonCode, note)\` records a one-step dismissal without reviewer or token. Use \`no-dependency-claims\` for a document with no dependency claims (reopens on document content changes), or \`unrelated-manifest-change\` for reviewed changes that do not affect its guidance (reopens on document or manifest changes). Keep the relevant scope committed; commit the dismissal to share it. This never approves a decision.
|
|
4486
|
+
|
|
4256
4487
|
- When Mason automation is installed, use \`mason_automation(action: "status")\` to inspect configured hooks and observed events, and \`mason_automation(action: "check")\` to resume its retained repair evidence. CLI fallback: \`mason-auto status\` / \`mason-auto check\`. Preserve existing baselines across sessions. Automatic checks do not authorize unrelated repairs or approve advisories.
|
|
4257
4488
|
|
|
4258
4489
|
Inspect source for what the retrieved context does not answer.
|
|
@@ -4547,8 +4778,10 @@ __export(teardown_exports, {
|
|
|
4547
4778
|
teardownProject: () => teardownProject
|
|
4548
4779
|
});
|
|
4549
4780
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
4781
|
+
import fs20 from "fs/promises";
|
|
4782
|
+
import path26 from "path";
|
|
4550
4783
|
import { parse as parse3, stringify as stringify2 } from "smol-toml";
|
|
4551
|
-
import
|
|
4784
|
+
import fg8 from "fast-glob";
|
|
4552
4785
|
import { z as z18 } from "zod";
|
|
4553
4786
|
async function planTeardown(root, selected) {
|
|
4554
4787
|
const edits = [], diagnostics = [];
|
|
@@ -4568,6 +4801,11 @@ async function planTeardown(root, selected) {
|
|
|
4568
4801
|
const ownership = ownershipBefore === null ? { version: 1, files: {} } : ownershipSchema.parse(JSON.parse(ownershipBefore));
|
|
4569
4802
|
const setupBefore = await read(SETUP_PATH);
|
|
4570
4803
|
const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
|
|
4804
|
+
const linked = (await workspace(root)).gitDir !== await fs20.realpath(
|
|
4805
|
+
path26.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
|
|
4806
|
+
);
|
|
4807
|
+
const inactiveBefore = linked && !setup ? await read(INACTIVE_HOSTS_PATH) : null;
|
|
4808
|
+
const inactive = inactiveBefore === null ? { version: 1, hosts: [] } : inactiveHostsSchema.parse(JSON.parse(inactiveBefore));
|
|
4571
4809
|
const automationBefore = await read(AUTOMATION_PATH);
|
|
4572
4810
|
const automation = automationBefore === null ? null : recordSchema2.parse(JSON.parse(automationBefore));
|
|
4573
4811
|
const commands2 = (host) => new Set([
|
|
@@ -4687,7 +4925,7 @@ async function planTeardown(root, selected) {
|
|
|
4687
4925
|
if (diagnostics.length) edits.splice(instructionEditStart);
|
|
4688
4926
|
if (!diagnostics.length) {
|
|
4689
4927
|
const cleanup = [];
|
|
4690
|
-
const receiptFiles = await
|
|
4928
|
+
const receiptFiles = await fg8(
|
|
4691
4929
|
selected.map((host) => ".mason/reports/automation/*/setup-" + host + ".json"),
|
|
4692
4930
|
{ cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false }
|
|
4693
4931
|
);
|
|
@@ -4711,7 +4949,16 @@ async function planTeardown(root, selected) {
|
|
|
4711
4949
|
delete ownership.files[configPath(host)];
|
|
4712
4950
|
}
|
|
4713
4951
|
if (!remainingHost) for (const file of DOC_CANDIDATES) delete ownership.files[file];
|
|
4714
|
-
if (setupBefore !== null) cleanup.push({
|
|
4952
|
+
if (setupBefore !== null) cleanup.push({
|
|
4953
|
+
path: SETUP_PATH,
|
|
4954
|
+
before: setupBefore,
|
|
4955
|
+
after: linked || Object.keys(setup.hosts).length ? json(setup) : null
|
|
4956
|
+
});
|
|
4957
|
+
else if (linked) cleanup.push({
|
|
4958
|
+
path: INACTIVE_HOSTS_PATH,
|
|
4959
|
+
before: inactiveBefore,
|
|
4960
|
+
after: json({ version: 1, hosts: [.../* @__PURE__ */ new Set([...inactive.hosts, ...selected])].sort() })
|
|
4961
|
+
});
|
|
4715
4962
|
if (automationBefore !== null) cleanup.push({ path: AUTOMATION_PATH, before: automationBefore, after: Object.keys(automation.hosts).length ? json(automation) : null });
|
|
4716
4963
|
if (ownershipBefore !== null) cleanup.push({ path: OWNERSHIP_PATH, before: ownershipBefore, after: Object.keys(ownership.files).length ? json(ownership) : null });
|
|
4717
4964
|
edits.push(...cleanup);
|
|
@@ -5152,10 +5399,10 @@ var init_check = __esm({
|
|
|
5152
5399
|
});
|
|
5153
5400
|
|
|
5154
5401
|
// src/review/evidence.ts
|
|
5155
|
-
import
|
|
5402
|
+
import path27 from "path";
|
|
5156
5403
|
import { z as z22 } from "zod";
|
|
5157
5404
|
function relativeArtifact(root, file) {
|
|
5158
|
-
const relative =
|
|
5405
|
+
const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
|
|
5159
5406
|
const normalized = normalizeRepoPath(relative);
|
|
5160
5407
|
if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
|
|
5161
5408
|
return normalized;
|
|
@@ -5252,7 +5499,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
5252
5499
|
const report = await readStoreJson(root, file);
|
|
5253
5500
|
if (report === null) throw new Error(`Report artifact is missing: ${file}`);
|
|
5254
5501
|
const sourceRoot = check.sourceRoot ?? root;
|
|
5255
|
-
if (!
|
|
5502
|
+
if (!path27.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
|
|
5256
5503
|
const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
|
|
5257
5504
|
result.outcome = parsed.outcome;
|
|
5258
5505
|
result.counts = parsed.counts;
|
|
@@ -5373,8 +5620,8 @@ var init_evidence2 = __esm({
|
|
|
5373
5620
|
});
|
|
5374
5621
|
|
|
5375
5622
|
// src/review/review.ts
|
|
5376
|
-
import
|
|
5377
|
-
import
|
|
5623
|
+
import fs21 from "fs/promises";
|
|
5624
|
+
import path28 from "path";
|
|
5378
5625
|
import { execFile as execFile4 } from "child_process";
|
|
5379
5626
|
import { promisify as promisify4 } from "util";
|
|
5380
5627
|
async function resolveMergeBase(resolvedRoot, base, head) {
|
|
@@ -5403,7 +5650,7 @@ function anchorsTouched(record, changedFiles) {
|
|
|
5403
5650
|
return matchingPaths(record.files, changedFiles);
|
|
5404
5651
|
}
|
|
5405
5652
|
async function computeReview(rootDir, base, options = {}) {
|
|
5406
|
-
const resolvedRoot =
|
|
5653
|
+
const resolvedRoot = path28.resolve(rootDir);
|
|
5407
5654
|
const head = await getCurrentGitHash(resolvedRoot);
|
|
5408
5655
|
if (head === "unknown") return null;
|
|
5409
5656
|
const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
|
|
@@ -5453,7 +5700,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5453
5700
|
analyzed,
|
|
5454
5701
|
async (relPath) => {
|
|
5455
5702
|
try {
|
|
5456
|
-
await
|
|
5703
|
+
await fs21.access(path28.join(resolvedRoot, relPath));
|
|
5457
5704
|
return true;
|
|
5458
5705
|
} catch {
|
|
5459
5706
|
return false;
|
|
@@ -5596,8 +5843,9 @@ __export(status_exports, {
|
|
|
5596
5843
|
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
5597
5844
|
async function setupStatus(dir) {
|
|
5598
5845
|
const ws = await workspace(dir);
|
|
5599
|
-
const
|
|
5600
|
-
|
|
5846
|
+
const activation = await effectiveSetup(ws.root);
|
|
5847
|
+
const setup = activation.setup;
|
|
5848
|
+
if (!setup || !Object.keys(setup.hosts).length) {
|
|
5601
5849
|
const partial = await Promise.all(["codex", "claude"].map(async (host) => ({
|
|
5602
5850
|
host,
|
|
5603
5851
|
receipt: await loadSetupReceipt(ws.root, ws.directory, host)
|
|
@@ -5631,7 +5879,7 @@ async function setupStatus(dir) {
|
|
|
5631
5879
|
]));
|
|
5632
5880
|
const hooks = isDeepStrictEqual3(compatibleHooks, expectedHooks) && !config.disabled;
|
|
5633
5881
|
const local = await loadSetupReceipt(ws.root, ws.directory, host);
|
|
5634
|
-
const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
|
|
5882
|
+
const configured = activation.root !== ws.root || local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
|
|
5635
5883
|
const observation = await readObservation(ws.root, ws.directory, host, entry.revision, installed.version ?? "unavailable");
|
|
5636
5884
|
const sessions = Object.values(observation?.sessions ?? {}).sort((a, b) => b.at.localeCompare(a.at));
|
|
5637
5885
|
const complete = sessions.find((session) => events.every((event) => session.events.includes(event)));
|
|
@@ -5663,6 +5911,7 @@ async function setupStatus(dir) {
|
|
|
5663
5911
|
status: statuses.length && statuses.every((status) => status === "active") ? "active" : statuses.includes("attention") ? "attention" : "pending",
|
|
5664
5912
|
root: ws.root,
|
|
5665
5913
|
hosts: hosts2,
|
|
5914
|
+
activationRoot: activation.root,
|
|
5666
5915
|
decisionRecords: decisions.records.length,
|
|
5667
5916
|
diagnostics: decisions.diagnostics,
|
|
5668
5917
|
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."
|
|
@@ -5670,6 +5919,9 @@ async function setupStatus(dir) {
|
|
|
5670
5919
|
}
|
|
5671
5920
|
function summarizeActivation(status) {
|
|
5672
5921
|
const lines = ["Mason setup: " + status.status + "."];
|
|
5922
|
+
if (status.activationRoot && status.activationRoot !== status.root) {
|
|
5923
|
+
lines.push("Activation inherited from " + status.activationRoot + "; evidence belongs to this worktree.");
|
|
5924
|
+
}
|
|
5673
5925
|
for (const [host, state] of Object.entries(status.hosts)) {
|
|
5674
5926
|
lines.push(
|
|
5675
5927
|
`${host}: ${state.status}`,
|
|
@@ -5831,66 +6083,19 @@ var init_setup = __esm({
|
|
|
5831
6083
|
}
|
|
5832
6084
|
});
|
|
5833
6085
|
|
|
5834
|
-
// src/distribution/bundle.ts
|
|
5835
|
-
import fs20 from "fs/promises";
|
|
5836
|
-
import path26 from "path";
|
|
5837
|
-
import { createHash as createHash4 } from "crypto";
|
|
5838
|
-
import { z as z23 } from "zod";
|
|
5839
|
-
async function readBundle(root) {
|
|
5840
|
-
const bytes = await fs20.readFile(await storePath(root, "bundle.json"));
|
|
5841
|
-
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
5842
|
-
const windows = manifest.target.startsWith("win32-");
|
|
5843
|
-
for (const file of Object.keys(manifest.files)) {
|
|
5844
|
-
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path26.isAbsolute(file)) {
|
|
5845
|
-
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
5846
|
-
}
|
|
5847
|
-
}
|
|
5848
|
-
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"]) {
|
|
5849
|
-
if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
|
|
5850
|
-
}
|
|
5851
|
-
return { manifest, manifestHash: sha256(bytes) };
|
|
5852
|
-
}
|
|
5853
|
-
async function verifyBundle(root, expectedHash) {
|
|
5854
|
-
const bundle = await readBundle(root);
|
|
5855
|
-
if (expectedHash && bundle.manifestHash !== expectedHash) throw new Error("Mason bundle manifest changed.");
|
|
5856
|
-
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.");
|
|
5857
|
-
const files = Object.keys(bundle.manifest.files);
|
|
5858
|
-
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
5859
|
-
if (sha256(await fs20.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
5860
|
-
}));
|
|
5861
|
-
return bundle;
|
|
5862
|
-
}
|
|
5863
|
-
var targets, digest2, bundleSchema, sha256;
|
|
5864
|
-
var init_bundle = __esm({
|
|
5865
|
-
"src/distribution/bundle.ts"() {
|
|
5866
|
-
"use strict";
|
|
5867
|
-
init_storage();
|
|
5868
|
-
targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
5869
|
-
digest2 = z23.string().regex(/^[a-f0-9]{64}$/);
|
|
5870
|
-
bundleSchema = z23.object({
|
|
5871
|
-
format: z23.literal(1),
|
|
5872
|
-
version: z23.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
5873
|
-
target: z23.enum(targets),
|
|
5874
|
-
nodeVersion: z23.string(),
|
|
5875
|
-
files: z23.record(digest2)
|
|
5876
|
-
}).strict();
|
|
5877
|
-
sha256 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
|
|
5878
|
-
}
|
|
5879
|
-
});
|
|
5880
|
-
|
|
5881
6086
|
// src/distribution/path.ts
|
|
5882
|
-
import
|
|
6087
|
+
import fs22 from "fs/promises";
|
|
5883
6088
|
import os3 from "os";
|
|
5884
|
-
import
|
|
6089
|
+
import path29 from "path";
|
|
5885
6090
|
import { execFile as execFile5 } from "child_process";
|
|
5886
6091
|
import { promisify as promisify5 } from "util";
|
|
5887
|
-
import { createHash as
|
|
5888
|
-
import { z as
|
|
6092
|
+
import { createHash as createHash4, randomUUID as randomUUID7 } from "crypto";
|
|
6093
|
+
import { z as z23 } from "zod";
|
|
5889
6094
|
async function configurePath(bin, previous, save, options = {}) {
|
|
5890
6095
|
const env = options.env ?? process.env, platform = options.platform ?? process.platform;
|
|
5891
6096
|
const changes = [...previous];
|
|
5892
6097
|
const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
|
|
5893
|
-
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) =>
|
|
6098
|
+
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path29.posix.isAbsolute(entry) && path29.posix.resolve(entry) === path29.posix.resolve(bin));
|
|
5894
6099
|
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";
|
|
5895
6100
|
const message2 = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
|
|
5896
6101
|
const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
|
|
@@ -5910,33 +6115,33 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5910
6115
|
}
|
|
5911
6116
|
return { status: "configured", changes, message: message2 };
|
|
5912
6117
|
}
|
|
5913
|
-
const shell =
|
|
6118
|
+
const shell = path29.basename(env.SHELL ?? os3.userInfo().shell ?? "");
|
|
5914
6119
|
const userHome = options.userHome ?? os3.homedir();
|
|
5915
6120
|
let profiles2;
|
|
5916
6121
|
if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
|
|
5917
|
-
if (env.MASON_PROFILE) profiles2 = [
|
|
5918
|
-
else if (shell === "zsh") profiles2 = [
|
|
5919
|
-
else if (shell === "sh") profiles2 = [
|
|
6122
|
+
if (env.MASON_PROFILE) profiles2 = [path29.resolve(env.MASON_PROFILE)];
|
|
6123
|
+
else if (shell === "zsh") profiles2 = [path29.join(env.ZDOTDIR || userHome, ".zshrc")];
|
|
6124
|
+
else if (shell === "sh") profiles2 = [path29.join(userHome, ".profile")];
|
|
5920
6125
|
else {
|
|
5921
|
-
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) =>
|
|
6126
|
+
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path29.join(userHome, name));
|
|
5922
6127
|
let login = candidates[2];
|
|
5923
|
-
for (const file of candidates) if (await
|
|
6128
|
+
for (const file of candidates) if (await fs22.lstat(file).then(() => true, (error) => {
|
|
5924
6129
|
if (error.code === "ENOENT") return false;
|
|
5925
6130
|
throw error;
|
|
5926
6131
|
})) {
|
|
5927
6132
|
login = file;
|
|
5928
6133
|
break;
|
|
5929
6134
|
}
|
|
5930
|
-
profiles2 = [
|
|
6135
|
+
profiles2 = [path29.join(userHome, ".bashrc"), login];
|
|
5931
6136
|
}
|
|
5932
|
-
const marker = `# >>> mason PATH ${
|
|
6137
|
+
const marker = `# >>> mason PATH ${createHash4("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
5933
6138
|
for (const profile of profiles2) {
|
|
5934
|
-
const stat = await
|
|
6139
|
+
const stat = await fs22.lstat(profile).catch((error) => {
|
|
5935
6140
|
if (error.code === "ENOENT") return null;
|
|
5936
6141
|
throw error;
|
|
5937
6142
|
});
|
|
5938
|
-
const file = stat?.isSymbolicLink() ? await
|
|
5939
|
-
const original = await
|
|
6143
|
+
const file = stat?.isSymbolicLink() ? await fs22.realpath(profile) : path29.resolve(profile);
|
|
6144
|
+
const original = await fs22.readFile(file).catch((error) => {
|
|
5940
6145
|
if (error.code === "ENOENT" && !stat) return null;
|
|
5941
6146
|
throw error;
|
|
5942
6147
|
});
|
|
@@ -5951,8 +6156,8 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5951
6156
|
changes.push({ kind: "profile", file, block, created: original === null });
|
|
5952
6157
|
await save(changes);
|
|
5953
6158
|
}
|
|
5954
|
-
await
|
|
5955
|
-
const handle = await
|
|
6159
|
+
await fs22.mkdir(path29.dirname(file), { recursive: true });
|
|
6160
|
+
const handle = await fs22.open(file, original === null ? "ax+" : "a+", 384);
|
|
5956
6161
|
try {
|
|
5957
6162
|
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
5958
6163
|
await handle.writeFile(block);
|
|
@@ -5976,13 +6181,13 @@ async function removePath(changes) {
|
|
|
5976
6181
|
await windowsPath("remove", change.entry, current, change.created);
|
|
5977
6182
|
continue;
|
|
5978
6183
|
}
|
|
5979
|
-
const stat = await
|
|
6184
|
+
const stat = await fs22.lstat(change.file).catch((error) => {
|
|
5980
6185
|
if (error.code === "ENOENT") return null;
|
|
5981
6186
|
throw error;
|
|
5982
6187
|
});
|
|
5983
6188
|
if (!stat) continue;
|
|
5984
6189
|
if (!stat.isFile()) throw new Error("profile type changed");
|
|
5985
|
-
const bytes = await
|
|
6190
|
+
const bytes = await fs22.readFile(change.file), text2 = bytes.toString("utf8");
|
|
5986
6191
|
if (!Buffer.from(text2).equals(bytes)) throw new Error("profile encoding changed");
|
|
5987
6192
|
const index2 = text2.indexOf(change.block);
|
|
5988
6193
|
if (index2 < 0 || text2.indexOf(change.block, index2 + change.block.length) >= 0) throw new Error("PATH block was edited or removed");
|
|
@@ -5992,7 +6197,7 @@ async function removePath(changes) {
|
|
|
5992
6197
|
const temporary = change.file + ".mason-" + randomUUID7();
|
|
5993
6198
|
try {
|
|
5994
6199
|
if (stat.nlink > 1) throw new Error("profile has multiple hard links");
|
|
5995
|
-
const handle = await
|
|
6200
|
+
const handle = await fs22.open(temporary, "wx", stat.mode & 511);
|
|
5996
6201
|
try {
|
|
5997
6202
|
await handle.writeFile(next);
|
|
5998
6203
|
await handle.chmod(stat.mode & 511);
|
|
@@ -6001,11 +6206,11 @@ async function removePath(changes) {
|
|
|
6001
6206
|
} finally {
|
|
6002
6207
|
await handle.close();
|
|
6003
6208
|
}
|
|
6004
|
-
const current = await
|
|
6005
|
-
if (!current.isFile() || current.ino !== stat.ino || !(await
|
|
6006
|
-
await
|
|
6209
|
+
const current = await fs22.lstat(change.file);
|
|
6210
|
+
if (!current.isFile() || current.ino !== stat.ino || !(await fs22.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
|
|
6211
|
+
await fs22.rename(temporary, change.file);
|
|
6007
6212
|
} finally {
|
|
6008
|
-
await
|
|
6213
|
+
await fs22.rm(temporary, { force: true });
|
|
6009
6214
|
}
|
|
6010
6215
|
} catch (error) {
|
|
6011
6216
|
warnings.push(`Retained PATH setting ${change.kind === "profile" ? change.file : change.entry}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -6047,12 +6252,12 @@ var init_path = __esm({
|
|
|
6047
6252
|
"src/distribution/path.ts"() {
|
|
6048
6253
|
"use strict";
|
|
6049
6254
|
execute2 = promisify5(execFile5);
|
|
6050
|
-
pathChangeSchema =
|
|
6051
|
-
|
|
6052
|
-
|
|
6255
|
+
pathChangeSchema = z23.discriminatedUnion("kind", [
|
|
6256
|
+
z23.object({ kind: z23.literal("profile"), file: z23.string(), block: z23.string(), created: z23.boolean() }),
|
|
6257
|
+
z23.object({ kind: z23.literal("windows"), entry: z23.string(), created: z23.boolean() })
|
|
6053
6258
|
]);
|
|
6054
6259
|
quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
6055
|
-
normalizeWindowsEntry = (value) =>
|
|
6260
|
+
normalizeWindowsEntry = (value) => path29.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
|
|
6056
6261
|
hasWindowsEntry = (value, entry, env = process.env) => {
|
|
6057
6262
|
const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
|
|
6058
6263
|
const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
|
|
@@ -6061,65 +6266,346 @@ var init_path = __esm({
|
|
|
6061
6266
|
}
|
|
6062
6267
|
});
|
|
6063
6268
|
|
|
6269
|
+
// src/distribution/state.ts
|
|
6270
|
+
var state_exports = {};
|
|
6271
|
+
__export(state_exports, {
|
|
6272
|
+
automaticUpdatesAllowed: () => automaticUpdatesAllowed,
|
|
6273
|
+
currentInstallation: () => currentInstallation,
|
|
6274
|
+
defaultHome: () => defaultHome,
|
|
6275
|
+
newerStableVersion: () => newerStableVersion,
|
|
6276
|
+
readInstallation: () => readInstallation,
|
|
6277
|
+
recordSchema: () => recordSchema3,
|
|
6278
|
+
releaseVersion: () => releaseVersion,
|
|
6279
|
+
standaloneLocation: () => standaloneLocation,
|
|
6280
|
+
updatePolicySchema: () => updatePolicySchema
|
|
6281
|
+
});
|
|
6282
|
+
import os4 from "os";
|
|
6283
|
+
import path30 from "path";
|
|
6284
|
+
import { fileURLToPath } from "url";
|
|
6285
|
+
import fs23 from "fs/promises";
|
|
6286
|
+
import { z as z24 } from "zod";
|
|
6287
|
+
async function readInstallation(home) {
|
|
6288
|
+
const raw = await readStoreJson(home, "install.json");
|
|
6289
|
+
if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
6290
|
+
const record = recordSchema3.parse(raw);
|
|
6291
|
+
if (record.home !== await fs23.realpath(home)) throw new Error("Invalid standalone installation receipt.");
|
|
6292
|
+
return record;
|
|
6293
|
+
}
|
|
6294
|
+
function standaloneLocation() {
|
|
6295
|
+
const bundle = path30.resolve(path30.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
6296
|
+
const home = path30.dirname(path30.dirname(bundle));
|
|
6297
|
+
if (path30.dirname(bundle) !== path30.join(home, "versions") || !/^[a-f0-9]{24}$/.test(path30.basename(bundle))) {
|
|
6298
|
+
return null;
|
|
6299
|
+
}
|
|
6300
|
+
return { home, bundle };
|
|
6301
|
+
}
|
|
6302
|
+
async function currentInstallation() {
|
|
6303
|
+
const location2 = standaloneLocation();
|
|
6304
|
+
if (!location2) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
6305
|
+
const { home, bundle } = location2;
|
|
6306
|
+
const record = await readInstallation(home);
|
|
6307
|
+
return { home, bundle, record };
|
|
6308
|
+
}
|
|
6309
|
+
function automaticUpdatesAllowed(record, env = process.env) {
|
|
6310
|
+
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;
|
|
6311
|
+
}
|
|
6312
|
+
function newerStableVersion(candidate, installed) {
|
|
6313
|
+
if (!/^\d+\.\d+\.\d+$/.test(candidate)) return false;
|
|
6314
|
+
const a = candidate.split(".").map(BigInt), b = installed.split("-")[0].split(".").map(BigInt);
|
|
6315
|
+
for (let i = 0; i < 3; i++) {
|
|
6316
|
+
if (a[i] !== b[i]) return a[i] > b[i];
|
|
6317
|
+
}
|
|
6318
|
+
return installed.includes("-");
|
|
6319
|
+
}
|
|
6320
|
+
var releaseVersion, bundleId, updatePolicySchema, recordSchema3, defaultHome;
|
|
6321
|
+
var init_state = __esm({
|
|
6322
|
+
"src/distribution/state.ts"() {
|
|
6323
|
+
"use strict";
|
|
6324
|
+
init_storage();
|
|
6325
|
+
init_path();
|
|
6326
|
+
releaseVersion = z24.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
|
|
6327
|
+
bundleId = z24.string().regex(/^[a-f0-9]{24}$/);
|
|
6328
|
+
updatePolicySchema = z24.object({
|
|
6329
|
+
enabled: z24.boolean(),
|
|
6330
|
+
revision: z24.string(),
|
|
6331
|
+
pinnedVersion: releaseVersion.optional(),
|
|
6332
|
+
mirror: z24.string().optional()
|
|
6333
|
+
});
|
|
6334
|
+
recordSchema3 = z24.object({
|
|
6335
|
+
format: z24.literal(1),
|
|
6336
|
+
home: z24.string(),
|
|
6337
|
+
bin: z24.string(),
|
|
6338
|
+
current: bundleId,
|
|
6339
|
+
version: releaseVersion,
|
|
6340
|
+
launchers: z24.record(z24.string()),
|
|
6341
|
+
previousLaunchers: z24.record(z24.string()).optional(),
|
|
6342
|
+
pathChanges: z24.array(pathChangeSchema).optional(),
|
|
6343
|
+
updates: updatePolicySchema.optional(),
|
|
6344
|
+
previous: z24.object({ id: bundleId, version: releaseVersion }).optional(),
|
|
6345
|
+
pending: z24.object({ id: bundleId, version: releaseVersion, policyRevision: z24.string() }).optional()
|
|
6346
|
+
});
|
|
6347
|
+
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path30.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path30.join(os4.homedir(), ".local/share/mason"));
|
|
6348
|
+
}
|
|
6349
|
+
});
|
|
6350
|
+
|
|
6351
|
+
// src/distribution/bundle.ts
|
|
6352
|
+
import fs24 from "fs/promises";
|
|
6353
|
+
import path31 from "path";
|
|
6354
|
+
import { createHash as createHash5 } from "crypto";
|
|
6355
|
+
import { z as z25 } from "zod";
|
|
6356
|
+
async function readBundle(root) {
|
|
6357
|
+
const bytes = await fs24.readFile(await storePath(root, "bundle.json"));
|
|
6358
|
+
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
6359
|
+
const windows = manifest.target.startsWith("win32-");
|
|
6360
|
+
for (const file of Object.keys(manifest.files)) {
|
|
6361
|
+
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path31.isAbsolute(file)) {
|
|
6362
|
+
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
6363
|
+
}
|
|
6364
|
+
}
|
|
6365
|
+
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"]) {
|
|
6366
|
+
if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
|
|
6367
|
+
}
|
|
6368
|
+
return { manifest, manifestHash: sha256(bytes) };
|
|
6369
|
+
}
|
|
6370
|
+
async function verifyBundle(root, expectedHash) {
|
|
6371
|
+
const bundle = await readBundle(root);
|
|
6372
|
+
if (expectedHash && bundle.manifestHash !== expectedHash) throw new Error("Mason bundle manifest changed.");
|
|
6373
|
+
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.");
|
|
6374
|
+
const files = Object.keys(bundle.manifest.files);
|
|
6375
|
+
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
6376
|
+
if (sha256(await fs24.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
6377
|
+
}));
|
|
6378
|
+
return bundle;
|
|
6379
|
+
}
|
|
6380
|
+
var targets, digest2, bundleSchema, sha256;
|
|
6381
|
+
var init_bundle = __esm({
|
|
6382
|
+
"src/distribution/bundle.ts"() {
|
|
6383
|
+
"use strict";
|
|
6384
|
+
init_storage();
|
|
6385
|
+
targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
6386
|
+
digest2 = z25.string().regex(/^[a-f0-9]{64}$/);
|
|
6387
|
+
bundleSchema = z25.object({
|
|
6388
|
+
format: z25.literal(1),
|
|
6389
|
+
version: z25.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
6390
|
+
target: z25.enum(targets),
|
|
6391
|
+
nodeVersion: z25.string(),
|
|
6392
|
+
files: z25.record(digest2)
|
|
6393
|
+
}).strict();
|
|
6394
|
+
sha256 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
6395
|
+
}
|
|
6396
|
+
});
|
|
6397
|
+
|
|
6398
|
+
// src/distribution/runtime.ts
|
|
6399
|
+
import fs25 from "fs/promises";
|
|
6400
|
+
import { unlinkSync } from "fs";
|
|
6401
|
+
import os5 from "os";
|
|
6402
|
+
import path32 from "path";
|
|
6403
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
6404
|
+
import { z as z26 } from "zod";
|
|
6405
|
+
async function pruneUnusedVersions(home) {
|
|
6406
|
+
const versions = await storePath(home, "versions");
|
|
6407
|
+
const candidates = [];
|
|
6408
|
+
const initial = await readInstallation(home);
|
|
6409
|
+
const initialKeep = /* @__PURE__ */ new Set([initial.current, initial.previous?.id, initial.pending?.id]);
|
|
6410
|
+
for (const name of await fs25.readdir(versions)) {
|
|
6411
|
+
const retired = /^\.retired-([a-f0-9]{24})-[a-f0-9-]+$/.exec(name);
|
|
6412
|
+
const id = retired?.[1] ?? name;
|
|
6413
|
+
if (!/^[a-f0-9]{24}$/.test(id) || initialKeep.has(id)) continue;
|
|
6414
|
+
try {
|
|
6415
|
+
const directory = await storePath(home, `versions/${name}`);
|
|
6416
|
+
await fs25.access(await storePath(directory, "app/dist/mason-runtime.js"));
|
|
6417
|
+
const bundle = await verifyBundle(directory);
|
|
6418
|
+
if (bundle.manifestHash.slice(0, 24) === id && bundle.manifest.files["app/dist/mason-runtime.js"])
|
|
6419
|
+
candidates.push({ id, directory, retired: !!retired });
|
|
6420
|
+
} catch {
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
if (!candidates.length) return;
|
|
6424
|
+
const garbage = [];
|
|
6425
|
+
await withLock(home, ".install-lock", async () => {
|
|
6426
|
+
const record = await readInstallation(home);
|
|
6427
|
+
const keep = /* @__PURE__ */ new Set([record.current, record.previous?.id, record.pending?.id]);
|
|
6428
|
+
const directory = await storePath(home, "runtimes");
|
|
6429
|
+
const leases = await fs25.readdir(directory).catch((error) => {
|
|
6430
|
+
if (error.code === "ENOENT") return [];
|
|
6431
|
+
throw error;
|
|
6432
|
+
});
|
|
6433
|
+
for (const name of leases) {
|
|
6434
|
+
if (!/^[a-f0-9-]+\.json$/.test(name)) return;
|
|
6435
|
+
try {
|
|
6436
|
+
const raw = await readStoreJson(home, `runtimes/${name}`);
|
|
6437
|
+
if (raw === null) continue;
|
|
6438
|
+
const lease = leaseSchema.parse(raw);
|
|
6439
|
+
if (lease.host !== os5.hostname()) return;
|
|
6440
|
+
try {
|
|
6441
|
+
process.kill(lease.pid, 0);
|
|
6442
|
+
keep.add(lease.bundle);
|
|
6443
|
+
} catch (error) {
|
|
6444
|
+
if (error.code !== "ESRCH") return;
|
|
6445
|
+
await fs25.rm(await storePath(home, `runtimes/${name}`), { force: true });
|
|
6446
|
+
}
|
|
6447
|
+
} catch {
|
|
6448
|
+
return;
|
|
6449
|
+
}
|
|
6450
|
+
}
|
|
6451
|
+
for (const candidate of candidates) {
|
|
6452
|
+
if (keep.has(candidate.id)) continue;
|
|
6453
|
+
try {
|
|
6454
|
+
const retired = candidate.retired ? candidate.directory : await storePath(home, `versions/.retired-${candidate.id}-${randomUUID8()}`);
|
|
6455
|
+
if (!candidate.retired) await fs25.rename(candidate.directory, retired);
|
|
6456
|
+
garbage.push(retired);
|
|
6457
|
+
} catch {
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
});
|
|
6461
|
+
for (const directory of garbage) await fs25.rm(directory, { recursive: true }).catch(() => {
|
|
6462
|
+
});
|
|
6463
|
+
}
|
|
6464
|
+
var leaseSchema;
|
|
6465
|
+
var init_runtime2 = __esm({
|
|
6466
|
+
"src/distribution/runtime.ts"() {
|
|
6467
|
+
"use strict";
|
|
6468
|
+
init_store();
|
|
6469
|
+
init_storage();
|
|
6470
|
+
init_state();
|
|
6471
|
+
init_bundle();
|
|
6472
|
+
leaseSchema = z26.object({ pid: z26.number().int().positive(), host: z26.string(), bundle: z26.string().regex(/^[a-f0-9]{24}$/) });
|
|
6473
|
+
}
|
|
6474
|
+
});
|
|
6475
|
+
|
|
6476
|
+
// src/distribution/sessions.ts
|
|
6477
|
+
import fs26 from "fs/promises";
|
|
6478
|
+
import os6 from "os";
|
|
6479
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
6480
|
+
import { z as z27 } from "zod";
|
|
6481
|
+
async function hasRunningMcp(home) {
|
|
6482
|
+
const directory = await storePath(home, "sessions");
|
|
6483
|
+
const files = await fs26.readdir(directory).catch((error) => {
|
|
6484
|
+
if (error.code === "ENOENT") return [];
|
|
6485
|
+
throw error;
|
|
6486
|
+
});
|
|
6487
|
+
for (const name of files) {
|
|
6488
|
+
if (!/^[a-f0-9-]+\.json$/.test(name)) return true;
|
|
6489
|
+
const relative = "sessions/" + name;
|
|
6490
|
+
try {
|
|
6491
|
+
const raw = await readStoreJson(home, relative);
|
|
6492
|
+
if (raw === null) continue;
|
|
6493
|
+
const lease = leaseSchema2.parse(raw);
|
|
6494
|
+
if (lease.host !== os6.hostname()) return true;
|
|
6495
|
+
try {
|
|
6496
|
+
process.kill(lease.pid, 0);
|
|
6497
|
+
return true;
|
|
6498
|
+
} catch (error) {
|
|
6499
|
+
if (error.code !== "ESRCH") return true;
|
|
6500
|
+
}
|
|
6501
|
+
await fs26.rm(await storePath(home, relative), { force: true });
|
|
6502
|
+
} catch {
|
|
6503
|
+
return true;
|
|
6504
|
+
}
|
|
6505
|
+
}
|
|
6506
|
+
return false;
|
|
6507
|
+
}
|
|
6508
|
+
async function registerMcp(home) {
|
|
6509
|
+
const name = `sessions/${randomUUID9()}.json`;
|
|
6510
|
+
await writeStoreJson(home, name, { pid: process.pid, host: os6.hostname() });
|
|
6511
|
+
return async () => {
|
|
6512
|
+
await fs26.rm(await storePath(home, name), { force: true }).catch(() => {
|
|
6513
|
+
});
|
|
6514
|
+
};
|
|
6515
|
+
}
|
|
6516
|
+
var leaseSchema2;
|
|
6517
|
+
var init_sessions = __esm({
|
|
6518
|
+
"src/distribution/sessions.ts"() {
|
|
6519
|
+
"use strict";
|
|
6520
|
+
init_storage();
|
|
6521
|
+
leaseSchema2 = z27.object({ pid: z27.number().int().positive(), host: z27.string() });
|
|
6522
|
+
}
|
|
6523
|
+
});
|
|
6524
|
+
|
|
6064
6525
|
// src/distribution/install.ts
|
|
6065
6526
|
var install_exports2 = {};
|
|
6066
6527
|
__export(install_exports2, {
|
|
6067
6528
|
WINDOWS_BATCH_LAUNCHER: () => WINDOWS_BATCH_LAUNCHER,
|
|
6068
6529
|
defaultHome: () => defaultHome,
|
|
6069
6530
|
installStandalone: () => installStandalone,
|
|
6531
|
+
prepareStandaloneMcp: () => prepareStandaloneMcp,
|
|
6532
|
+
rollbackStandalone: () => rollbackStandalone,
|
|
6070
6533
|
uninstallStandalone: () => uninstallStandalone,
|
|
6071
6534
|
upgradeStandalone: () => upgradeStandalone
|
|
6072
6535
|
});
|
|
6073
|
-
import
|
|
6074
|
-
import
|
|
6075
|
-
import
|
|
6076
|
-
import {
|
|
6077
|
-
import {
|
|
6078
|
-
import { randomUUID as
|
|
6079
|
-
import { z as z25 } from "zod";
|
|
6536
|
+
import fs27 from "fs/promises";
|
|
6537
|
+
import os7 from "os";
|
|
6538
|
+
import path33 from "path";
|
|
6539
|
+
import { execFile as execFile6, spawn } from "child_process";
|
|
6540
|
+
import { promisify as promisify6 } from "util";
|
|
6541
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
6080
6542
|
async function replace(file, content2, mode = 384) {
|
|
6081
|
-
const temp = file + ".tmp-" +
|
|
6543
|
+
const temp = file + ".tmp-" + randomUUID10();
|
|
6082
6544
|
try {
|
|
6083
|
-
await
|
|
6084
|
-
await
|
|
6545
|
+
await fs27.writeFile(temp, content2, { mode, flag: "wx" });
|
|
6546
|
+
await fs27.rename(temp, file);
|
|
6085
6547
|
} finally {
|
|
6086
|
-
await
|
|
6548
|
+
await fs27.rm(temp, { force: true });
|
|
6087
6549
|
}
|
|
6088
6550
|
}
|
|
6089
|
-
async function installStandalone(source, progress2) {
|
|
6551
|
+
async function installStandalone(source, progress2, options = {}) {
|
|
6090
6552
|
progress2?.step("Checking installation files and location");
|
|
6091
6553
|
const bundle = await verifyBundle(source);
|
|
6092
|
-
const selectedHome =
|
|
6093
|
-
await
|
|
6094
|
-
const home = await
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
const
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6554
|
+
const selectedHome = path33.resolve(defaultHome());
|
|
6555
|
+
if (!options.stageOnly) await fs27.mkdir(selectedHome, { recursive: true });
|
|
6556
|
+
const home = await fs27.realpath(selectedHome);
|
|
6557
|
+
if (options.stageOnly) {
|
|
6558
|
+
assertUpdatePolicy(await readInstallation(home), bundle.manifest.version, options.policyRevision);
|
|
6559
|
+
const destination = await storePath(home, "versions/" + bundle.manifestHash.slice(0, 24));
|
|
6560
|
+
await copyBundle(home, source, destination, bundle);
|
|
6561
|
+
const { stdout } = await promisify6(execFile6)(
|
|
6562
|
+
path33.join(destination, process.platform === "win32" ? "node.exe" : "node"),
|
|
6563
|
+
[path33.join(destination, "app/dist/mason.js"), "--version"],
|
|
6564
|
+
{ timeout: 3e4, windowsHide: true, env: { ...process.env, MASON_NO_AUTO_UPDATE: "1" } }
|
|
6565
|
+
);
|
|
6566
|
+
if (stdout.trim() !== bundle.manifest.version) throw new Error("Downloaded Mason failed its version health check.");
|
|
6567
|
+
}
|
|
6568
|
+
return withLock(home, ".install-lock", () => installLocked2(home, source, bundle, progress2, options));
|
|
6569
|
+
}
|
|
6570
|
+
function assertUpdatePolicy(record, version, revision) {
|
|
6571
|
+
if (!record || !automaticUpdatesAllowed(record, { ...process.env, MASON_VERSION: "" }) || record.updates?.revision !== revision || !newerStableVersion(version, record.version)) {
|
|
6572
|
+
throw new Error("Update policy or installed version changed; download was not activated.");
|
|
6573
|
+
}
|
|
6574
|
+
}
|
|
6575
|
+
async function installLocked2(home, source, bundle, progress2, options, alreadyStaged = false) {
|
|
6576
|
+
const raw = await readStoreJson(home, "install.json");
|
|
6577
|
+
const previous = raw === null ? null : recordSchema3.parse(raw);
|
|
6578
|
+
if (options.stageOnly) assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
|
|
6579
|
+
if (!previous) for (const file of await fs27.readdir(home)) {
|
|
6580
|
+
if (file === ".install-lock") continue;
|
|
6581
|
+
const directory = path33.join(home, file);
|
|
6582
|
+
const stat = await fs27.lstat(directory);
|
|
6583
|
+
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs27.readdir(directory)).length) {
|
|
6584
|
+
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
6585
|
+
}
|
|
6586
|
+
}
|
|
6587
|
+
const selectedBin = path33.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path33.join(home, "bin") : path33.join(os7.homedir(), ".local/bin")));
|
|
6588
|
+
await fs27.mkdir(selectedBin, { recursive: true });
|
|
6589
|
+
const bin = await fs27.realpath(selectedBin);
|
|
6590
|
+
if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
|
|
6591
|
+
const id = bundle.manifestHash.slice(0, 24);
|
|
6592
|
+
const destination = await storePath(home, "versions/" + id);
|
|
6593
|
+
if (options.stageOnly) {
|
|
6594
|
+
const receipt2 = { ...previous, pending: { id, version: bundle.manifest.version, policyRevision: options.policyRevision } };
|
|
6595
|
+
await writeStoreJson(home, "install.json", receipt2);
|
|
6596
|
+
return { home, bin, version: bundle.manifest.version, previousVersion: previous.version, path: { message: "Update staged for a future MCP launch." } };
|
|
6597
|
+
}
|
|
6598
|
+
const launchers = process.platform === "win32" ? {
|
|
6599
|
+
"mason.cmd": WINDOWS_BATCH_LAUNCHER,
|
|
6600
|
+
"mason-launcher.ps1": `$ErrorActionPreference = 'Stop'
|
|
6115
6601
|
$previousToken = $env:MASON_UNINSTALL_TOKEN
|
|
6116
6602
|
$token = [Guid]::NewGuid().ToString()
|
|
6117
6603
|
$env:MASON_UNINSTALL_TOKEN = $token
|
|
6118
6604
|
try {
|
|
6119
|
-
& ${psQuote(
|
|
6605
|
+
& ${psQuote(path33.join(destination, "node.exe"))} ${psQuote(path33.join(destination, "app/dist/mason.js"))} @args
|
|
6120
6606
|
$code = $LASTEXITCODE
|
|
6121
6607
|
if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
|
|
6122
|
-
if ([IO.File]::ReadAllText(${psQuote(
|
|
6608
|
+
if ([IO.File]::ReadAllText(${psQuote(path33.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
|
|
6123
6609
|
Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
|
|
6124
6610
|
Write-Output ${psQuote("Removed standalone installation: " + home)}
|
|
6125
6611
|
}
|
|
@@ -6127,88 +6613,207 @@ try {
|
|
|
6127
6613
|
} catch { [Console]::Error.WriteLine($_.Exception.Message); exit 2 }
|
|
6128
6614
|
finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
|
|
6129
6615
|
`
|
|
6130
|
-
|
|
6131
|
-
exec ${shQuote(
|
|
6616
|
+
} : { mason: `#!/bin/sh
|
|
6617
|
+
exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destination, "app/dist/mason.js"))} "$@"
|
|
6132
6618
|
` };
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6619
|
+
for (const [name, content2] of Object.entries(launchers)) {
|
|
6620
|
+
const file = await storePath(bin, name);
|
|
6621
|
+
const old = await fs27.readFile(file, "utf8").catch((error) => {
|
|
6622
|
+
if (error.code === "ENOENT") return null;
|
|
6623
|
+
throw error;
|
|
6624
|
+
});
|
|
6625
|
+
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);
|
|
6626
|
+
}
|
|
6627
|
+
const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
|
|
6628
|
+
const checkRetired = async (name, expected) => {
|
|
6629
|
+
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
6630
|
+
const actual = await fs27.readFile(await storePath(bin, name), "utf8").catch((error) => {
|
|
6631
|
+
if (error.code === "ENOENT") return null;
|
|
6632
|
+
throw error;
|
|
6633
|
+
});
|
|
6634
|
+
if (actual !== null && actual !== expected) throw new Error("Retired launcher was edited; retained: " + name);
|
|
6635
|
+
};
|
|
6636
|
+
for (const [name, expected] of retired) await checkRetired(name, expected);
|
|
6637
|
+
progress2?.step(`Installing Mason ${bundle.manifest.version}`);
|
|
6638
|
+
const updates = options.preservePolicy && previous?.updates ? { ...previous.updates } : {
|
|
6639
|
+
...previous?.updates ?? { enabled: !previous && !process.env.CI && process.env.MASON_NO_AUTO_UPDATE !== "1" },
|
|
6640
|
+
revision: randomUUID10(),
|
|
6641
|
+
pinnedVersion: process.env.MASON_VERSION?.replace(/^v/, "") || void 0,
|
|
6642
|
+
mirror: process.env.MASON_RELEASE_BASE || previous?.updates?.mirror
|
|
6643
|
+
};
|
|
6644
|
+
if (options.rollback) {
|
|
6645
|
+
updates.pinnedVersion = bundle.manifest.version;
|
|
6646
|
+
updates.revision = randomUUID10();
|
|
6647
|
+
}
|
|
6648
|
+
const receipt = {
|
|
6649
|
+
format: 1,
|
|
6650
|
+
home,
|
|
6651
|
+
bin,
|
|
6652
|
+
current: id,
|
|
6653
|
+
version: bundle.manifest.version,
|
|
6654
|
+
launchers,
|
|
6655
|
+
pathChanges: previous?.pathChanges ?? [],
|
|
6656
|
+
updates,
|
|
6657
|
+
previous: previous && previous.current !== id ? { id: previous.current, version: previous.version } : previous?.previous
|
|
6658
|
+
};
|
|
6659
|
+
await writeStoreJson(home, "install.json", previous ? { ...previous, previousLaunchers: launchers } : receipt);
|
|
6660
|
+
try {
|
|
6661
|
+
if (!alreadyStaged) await copyBundle(home, source, destination, bundle);
|
|
6662
|
+
for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
6663
|
+
for (const [name, expected] of retired) {
|
|
6664
|
+
await checkRetired(name, expected);
|
|
6665
|
+
await fs27.rm(await storePath(bin, name), { force: true });
|
|
6140
6666
|
}
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
const
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
await writeStoreJson(home, "install.json",
|
|
6154
|
-
|
|
6155
|
-
|
|
6667
|
+
await writeStoreJson(home, "install.json", receipt);
|
|
6668
|
+
} catch (error) {
|
|
6669
|
+
if (previous) {
|
|
6670
|
+
for (const [name, content2] of Object.entries(previous.launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
6671
|
+
await writeStoreJson(home, "install.json", previous);
|
|
6672
|
+
}
|
|
6673
|
+
throw error;
|
|
6674
|
+
}
|
|
6675
|
+
if (options.preservePolicy) return { home, bin, version: receipt.version, previousVersion: previous?.version, path: { message: "Installation selected." } };
|
|
6676
|
+
progress2?.step("Checking terminal command availability");
|
|
6677
|
+
const pathResult = await configurePath(bin, receipt.pathChanges ?? [], async (changes) => {
|
|
6678
|
+
receipt.pathChanges = changes;
|
|
6679
|
+
await writeStoreJson(home, "install.json", receipt);
|
|
6680
|
+
});
|
|
6681
|
+
return { home, bin, version: bundle.manifest.version, previousVersion: previous?.version, path: pathResult };
|
|
6682
|
+
}
|
|
6683
|
+
async function copyBundle(home, source, destination, bundle) {
|
|
6684
|
+
const exists = await fs27.lstat(destination).then(() => true, (error) => {
|
|
6685
|
+
if (error.code === "ENOENT") return false;
|
|
6686
|
+
throw error;
|
|
6687
|
+
});
|
|
6688
|
+
if (exists) {
|
|
6689
|
+
await verifyBundle(destination, bundle.manifestHash);
|
|
6690
|
+
return;
|
|
6691
|
+
}
|
|
6692
|
+
const stage = await storePath(home, "versions/.install-" + randomUUID10(), true);
|
|
6693
|
+
await fs27.mkdir(stage);
|
|
6694
|
+
try {
|
|
6695
|
+
for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
|
|
6696
|
+
const from = await storePath(source, file), to = await storePath(stage, file, true);
|
|
6697
|
+
await fs27.copyFile(from, to);
|
|
6698
|
+
await fs27.chmod(to, (await fs27.stat(from)).mode & 511);
|
|
6699
|
+
}
|
|
6700
|
+
await verifyBundle(stage, bundle.manifestHash);
|
|
6156
6701
|
try {
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
await
|
|
6161
|
-
}
|
|
6162
|
-
await verifyBundle(stage, bundle.manifestHash);
|
|
6163
|
-
const exists = await fs22.lstat(destination).then(() => true, (error) => {
|
|
6164
|
-
if (error.code === "ENOENT") return false;
|
|
6702
|
+
await fs27.rename(stage, destination);
|
|
6703
|
+
} catch (error) {
|
|
6704
|
+
try {
|
|
6705
|
+
await verifyBundle(destination, bundle.manifestHash);
|
|
6706
|
+
} catch {
|
|
6165
6707
|
throw error;
|
|
6166
|
-
});
|
|
6167
|
-
if (exists) await verifyBundle(destination, bundle.manifestHash);
|
|
6168
|
-
else await fs22.rename(stage, destination);
|
|
6169
|
-
for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
6170
|
-
for (const [name, expected] of retired) {
|
|
6171
|
-
await checkRetired(name, expected);
|
|
6172
|
-
await fs22.rm(await storePath(bin, name), { force: true });
|
|
6173
6708
|
}
|
|
6174
|
-
await writeStoreJson(home, "install.json", receipt);
|
|
6175
|
-
} finally {
|
|
6176
|
-
await fs22.rm(stage, { recursive: true, force: true });
|
|
6177
6709
|
}
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6710
|
+
} finally {
|
|
6711
|
+
await fs27.rm(stage, { recursive: true, force: true });
|
|
6712
|
+
}
|
|
6713
|
+
}
|
|
6714
|
+
async function prepareStandaloneMcp(home) {
|
|
6715
|
+
const before = await readInstallation(home), candidate = before.pending;
|
|
6716
|
+
let verified, verificationError;
|
|
6717
|
+
if (candidate && automaticUpdatesAllowed(before) && !await hasRunningMcp(home)) {
|
|
6718
|
+
try {
|
|
6719
|
+
verified = await verifyBundle(await storePath(home, "versions/" + candidate.id));
|
|
6720
|
+
if (verified.manifestHash.slice(0, 24) !== candidate.id || verified.manifest.version !== candidate.version) throw new Error("Staged update changed.");
|
|
6721
|
+
} catch (error) {
|
|
6722
|
+
verificationError = error;
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
return withLock(home, ".install-lock", async () => {
|
|
6726
|
+
let record = await readInstallation(home);
|
|
6727
|
+
const pending = record.pending;
|
|
6728
|
+
if (pending && candidate?.id === pending.id && (verified || verificationError) && automaticUpdatesAllowed(record) && pending.policyRevision === record.updates?.revision && newerStableVersion(pending.version, record.version) && !await hasRunningMcp(home)) {
|
|
6729
|
+
try {
|
|
6730
|
+
if (verificationError) throw verificationError;
|
|
6731
|
+
const source = await storePath(home, "versions/" + pending.id);
|
|
6732
|
+
await installLocked2(home, source, verified, void 0, { preservePolicy: true }, true);
|
|
6733
|
+
record = await readInstallation(home);
|
|
6734
|
+
} catch (error) {
|
|
6735
|
+
record = await readInstallation(home);
|
|
6736
|
+
delete record.pending;
|
|
6737
|
+
await writeStoreJson(home, "install.json", record);
|
|
6738
|
+
await writeStoreJson(home, "update-status.json", { lastAttempt: Date.now(), error: String(error) });
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
const release = await registerMcp(home);
|
|
6742
|
+
return { bundle: await storePath(home, "versions/" + record.current), release };
|
|
6184
6743
|
});
|
|
6185
6744
|
}
|
|
6186
|
-
async function
|
|
6187
|
-
const
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6745
|
+
async function rollbackStandalone() {
|
|
6746
|
+
const { home } = await currentInstallation();
|
|
6747
|
+
return withLock(home, ".install-lock", async () => {
|
|
6748
|
+
const record = await readInstallation(home);
|
|
6749
|
+
if (!record.previous) throw new Error("No previous installation is available.");
|
|
6750
|
+
if (await hasRunningMcp(home)) throw new Error("Close running Mason MCP sessions before rolling back.");
|
|
6751
|
+
const source = await storePath(home, "versions/" + record.previous.id);
|
|
6752
|
+
const bundle = await verifyBundle(source);
|
|
6753
|
+
if (bundle.manifestHash.slice(0, 24) !== record.previous.id || bundle.manifest.version !== record.previous.version) throw new Error("Previous installation changed.");
|
|
6754
|
+
return installLocked2(home, source, bundle, void 0, { preservePolicy: true, rollback: true });
|
|
6755
|
+
});
|
|
6194
6756
|
}
|
|
6195
6757
|
async function upgradeStandalone(version, progress2) {
|
|
6196
6758
|
if (version && !/^v?\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(version)) throw new Error("Expected a release version such as 0.14.0.");
|
|
6197
6759
|
progress2?.step("Checking current installation");
|
|
6198
6760
|
const { home, bundle, record } = await currentInstallation();
|
|
6199
6761
|
await verifyBundle(bundle);
|
|
6762
|
+
const mirror = process.env.MASON_RELEASE_BASE || record.updates?.mirror || "";
|
|
6763
|
+
if (mirror && !version) throw new Error("Specify a version when upgrading from a company mirror; no public latest-version lookup was made.");
|
|
6764
|
+
let selected = version?.replace(/^v/, "");
|
|
6765
|
+
if (!selected) {
|
|
6766
|
+
progress2?.step("Checking the latest release");
|
|
6767
|
+
const response = await fetch("https://github.com/adrianczuczka/mason/releases/latest", {
|
|
6768
|
+
method: "HEAD",
|
|
6769
|
+
redirect: "follow",
|
|
6770
|
+
signal: AbortSignal.timeout(3e4)
|
|
6771
|
+
});
|
|
6772
|
+
if (!response.ok) throw new Error(`Latest release request failed (HTTP ${response.status}).`);
|
|
6773
|
+
selected = new URL(response.url).pathname.split("/").pop()?.replace(/^v/, "");
|
|
6774
|
+
if (!selected || !/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(selected)) throw new Error("Invalid Mason release version.");
|
|
6775
|
+
}
|
|
6776
|
+
const alreadyInstalled = await withLock(home, ".install-lock", async () => {
|
|
6777
|
+
const current = await readInstallation(home);
|
|
6778
|
+
if (current.version !== selected) return false;
|
|
6779
|
+
await verifyBundle(await storePath(home, "versions/" + current.current));
|
|
6780
|
+
current.updates = {
|
|
6781
|
+
...current.updates ?? { enabled: false },
|
|
6782
|
+
revision: randomUUID10(),
|
|
6783
|
+
pinnedVersion: version ? selected : void 0,
|
|
6784
|
+
mirror: mirror || void 0
|
|
6785
|
+
};
|
|
6786
|
+
delete current.pending;
|
|
6787
|
+
await writeStoreJson(home, "install.json", current);
|
|
6788
|
+
return true;
|
|
6789
|
+
});
|
|
6790
|
+
if (alreadyInstalled) {
|
|
6791
|
+
progress2?.stop();
|
|
6792
|
+
console.log(`Mason ${selected} is already installed.`);
|
|
6793
|
+
return 0;
|
|
6794
|
+
}
|
|
6200
6795
|
const windows = process.platform === "win32";
|
|
6201
|
-
const script =
|
|
6796
|
+
const script = path33.join(bundle, windows ? "install.ps1" : "install.sh");
|
|
6202
6797
|
progress2?.stop();
|
|
6203
|
-
|
|
6798
|
+
const code = await new Promise((resolve, reject) => {
|
|
6204
6799
|
const child = spawn(
|
|
6205
6800
|
windows ? "powershell.exe" : "sh",
|
|
6206
|
-
windows ? ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script] : [script],
|
|
6207
|
-
{ stdio: "inherit", windowsHide: true, env: {
|
|
6801
|
+
windows ? ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script, selected] : [script, selected],
|
|
6802
|
+
{ stdio: "inherit", windowsHide: true, env: {
|
|
6803
|
+
...process.env,
|
|
6804
|
+
MASON_HOME: home,
|
|
6805
|
+
MASON_BIN_DIR: record.bin,
|
|
6806
|
+
MASON_RELEASE_BASE: process.env.MASON_RELEASE_BASE || record.updates?.mirror || "",
|
|
6807
|
+
MASON_VERSION: version ?? "",
|
|
6808
|
+
MASON_UPDATE_POLICY_REVISION: "",
|
|
6809
|
+
MASON_EXPECTED_SHA256: ""
|
|
6810
|
+
} }
|
|
6208
6811
|
);
|
|
6209
6812
|
child.on("error", reject);
|
|
6210
|
-
child.on("exit", (
|
|
6813
|
+
child.on("exit", (code2) => resolve(code2 ?? 2));
|
|
6211
6814
|
});
|
|
6815
|
+
if (code === 0) console.log("Restart running assistants to use this installation. Existing project integrations use mason from PATH.");
|
|
6816
|
+
return code;
|
|
6212
6817
|
}
|
|
6213
6818
|
async function uninstallStandalone() {
|
|
6214
6819
|
const { home, record } = await currentInstallation();
|
|
@@ -6220,7 +6825,7 @@ async function uninstallStandalone() {
|
|
|
6220
6825
|
for (const [name, expected] of Object.entries(record.launchers)) {
|
|
6221
6826
|
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
6222
6827
|
const file = await storePath(record.bin, name);
|
|
6223
|
-
const actual = await
|
|
6828
|
+
const actual = await fs27.readFile(file, "utf8").catch((error) => {
|
|
6224
6829
|
if (error.code === "ENOENT") return null;
|
|
6225
6830
|
throw error;
|
|
6226
6831
|
});
|
|
@@ -6228,12 +6833,12 @@ async function uninstallStandalone() {
|
|
|
6228
6833
|
}
|
|
6229
6834
|
for (const warning of await removePath(record.pathChanges ?? [])) console.error(warning);
|
|
6230
6835
|
if (process.platform === "win32") await replace(await storePath(home, ".uninstall-request.txt", true), token);
|
|
6231
|
-
for (const name of Object.keys(record.launchers)) await
|
|
6836
|
+
for (const name of Object.keys(record.launchers)) await fs27.rm(await storePath(record.bin, name), { force: true });
|
|
6232
6837
|
});
|
|
6233
|
-
if (process.platform !== "win32") await
|
|
6838
|
+
if (process.platform !== "win32") await fs27.rm(home, { recursive: true, force: true });
|
|
6234
6839
|
return home;
|
|
6235
6840
|
}
|
|
6236
|
-
var
|
|
6841
|
+
var shQuote, psQuote, WINDOWS_BATCH_LAUNCHER, launcherNames;
|
|
6237
6842
|
var init_install2 = __esm({
|
|
6238
6843
|
"src/distribution/install.ts"() {
|
|
6239
6844
|
"use strict";
|
|
@@ -6241,21 +6846,254 @@ var init_install2 = __esm({
|
|
|
6241
6846
|
init_storage();
|
|
6242
6847
|
init_store();
|
|
6243
6848
|
init_path();
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
bin: z25.string(),
|
|
6248
|
-
current: z25.string().regex(/^[a-f0-9]{24}$/),
|
|
6249
|
-
version: z25.string(),
|
|
6250
|
-
launchers: z25.record(z25.string()),
|
|
6251
|
-
previousLaunchers: z25.record(z25.string()).optional(),
|
|
6252
|
-
pathChanges: z25.array(pathChangeSchema).optional()
|
|
6253
|
-
});
|
|
6849
|
+
init_state();
|
|
6850
|
+
init_sessions();
|
|
6851
|
+
init_state();
|
|
6254
6852
|
shQuote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
6255
6853
|
psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
|
|
6256
6854
|
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';
|
|
6257
6855
|
launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
|
|
6258
|
-
|
|
6856
|
+
}
|
|
6857
|
+
});
|
|
6858
|
+
|
|
6859
|
+
// src/distribution/updates.ts
|
|
6860
|
+
var updates_exports = {};
|
|
6861
|
+
__export(updates_exports, {
|
|
6862
|
+
CHECK_INTERVAL_MS: () => CHECK_INTERVAL_MS,
|
|
6863
|
+
RELEASE_AGE_MS: () => RELEASE_AGE_MS,
|
|
6864
|
+
fetchVerifiedRelease: () => fetchVerifiedRelease,
|
|
6865
|
+
runAutomaticUpdate: () => runAutomaticUpdate,
|
|
6866
|
+
scheduleAutomaticUpdate: () => scheduleAutomaticUpdate,
|
|
6867
|
+
startMcpWithUpdates: () => startMcpWithUpdates,
|
|
6868
|
+
updateManifestSchema: () => updateManifestSchema,
|
|
6869
|
+
updateSettings: () => updateSettings,
|
|
6870
|
+
updateStatus: () => updateStatus
|
|
6871
|
+
});
|
|
6872
|
+
import fs28 from "fs/promises";
|
|
6873
|
+
import path34 from "path";
|
|
6874
|
+
import { spawn as spawn2 } from "child_process";
|
|
6875
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
6876
|
+
import { z as z28 } from "zod";
|
|
6877
|
+
async function boundedDownload(url) {
|
|
6878
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(15e3), redirect: "follow" });
|
|
6879
|
+
if (!response.ok || !response.body) throw new Error(`Update metadata request failed (HTTP ${response.status}).`);
|
|
6880
|
+
if (Number(response.headers.get("content-length")) > 1024 * 1024) throw new Error("Update metadata is too large.");
|
|
6881
|
+
const reader = response.body.getReader(), chunks = [];
|
|
6882
|
+
let size = 0;
|
|
6883
|
+
try {
|
|
6884
|
+
for (; ; ) {
|
|
6885
|
+
const { value, done } = await reader.read();
|
|
6886
|
+
if (done) break;
|
|
6887
|
+
size += value.length;
|
|
6888
|
+
if (size > 1024 * 1024) throw new Error("Update metadata is too large.");
|
|
6889
|
+
chunks.push(value);
|
|
6890
|
+
}
|
|
6891
|
+
} finally {
|
|
6892
|
+
await reader.cancel();
|
|
6893
|
+
}
|
|
6894
|
+
return Buffer.concat(chunks);
|
|
6895
|
+
}
|
|
6896
|
+
async function fetchVerifiedRelease(home, now = Date.now()) {
|
|
6897
|
+
const releases = releaseListSchema.parse(JSON.parse((await boundedDownload("https://api.github.com/repos/adrianczuczka/mason/releases?per_page=30")).toString("utf8")));
|
|
6898
|
+
const installed = (await readInstallation(home)).version;
|
|
6899
|
+
let selected;
|
|
6900
|
+
for (const release of releases) {
|
|
6901
|
+
const version = release.tag_name.replace(/^v/, "");
|
|
6902
|
+
if (release.tag_name !== "v" + version || release.draft || release.prerelease || !release.published_at || !(now - Date.parse(release.published_at) >= RELEASE_AGE_MS) || !newerStableVersion(version, installed)) continue;
|
|
6903
|
+
if (!selected || newerStableVersion(version, selected)) selected = version;
|
|
6904
|
+
}
|
|
6905
|
+
if (!selected) return null;
|
|
6906
|
+
const bytes = await boundedDownload(`${releaseBase}/download/v${selected}/update.json`);
|
|
6907
|
+
const manifest = updateManifestSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
6908
|
+
if (manifest.version !== selected) throw new Error("Signed manifest does not match the selected release.");
|
|
6909
|
+
const signature = JSON.parse((await boundedDownload(`${releaseBase}/download/v${manifest.version}/update.sigstore.json`)).toString("utf8"));
|
|
6910
|
+
if (!signature?.messageSignature || signature.dsseEnvelope) throw new Error("Update metadata requires a detached message signature.");
|
|
6911
|
+
const { verify } = await import("sigstore");
|
|
6912
|
+
await verify(signature, bytes, {
|
|
6913
|
+
certificateIdentityURI: `https://github.com/adrianczuczka/mason/.github/workflows/publish.yml@refs/tags/v${manifest.version}`,
|
|
6914
|
+
certificateIssuer: "https://token.actions.githubusercontent.com",
|
|
6915
|
+
tufCachePath: await storePath(home, "sigstore-cache"),
|
|
6916
|
+
timeout: 1e4,
|
|
6917
|
+
retry: 0,
|
|
6918
|
+
ctLogThreshold: 1,
|
|
6919
|
+
tlogThreshold: 1
|
|
6920
|
+
});
|
|
6921
|
+
return manifest;
|
|
6922
|
+
}
|
|
6923
|
+
async function runInstaller(home, version, digest3, revision) {
|
|
6924
|
+
const record = await readInstallation(home);
|
|
6925
|
+
const bundle = await storePath(home, "versions/" + record.current);
|
|
6926
|
+
await verifyBundle(bundle);
|
|
6927
|
+
const windows = process.platform === "win32", script = path34.join(bundle, windows ? "install.ps1" : "install.sh");
|
|
6928
|
+
await new Promise((resolve, reject) => {
|
|
6929
|
+
const child = spawn2(windows ? "powershell.exe" : "sh", windows ? ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script] : [script], {
|
|
6930
|
+
cwd: home,
|
|
6931
|
+
windowsHide: true,
|
|
6932
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
6933
|
+
timeout: 10 * 60 * 1e3,
|
|
6934
|
+
env: {
|
|
6935
|
+
...process.env,
|
|
6936
|
+
MASON_HOME: home,
|
|
6937
|
+
MASON_BIN_DIR: record.bin,
|
|
6938
|
+
MASON_VERSION: version,
|
|
6939
|
+
MASON_RELEASE_BASE: "",
|
|
6940
|
+
MASON_EXPECTED_SHA256: digest3,
|
|
6941
|
+
MASON_UPDATE_POLICY_REVISION: revision
|
|
6942
|
+
}
|
|
6943
|
+
});
|
|
6944
|
+
let diagnostic = "";
|
|
6945
|
+
child.stderr.on("data", (bytes) => {
|
|
6946
|
+
diagnostic = (diagnostic + bytes).slice(-4e3);
|
|
6947
|
+
});
|
|
6948
|
+
child.on("error", reject);
|
|
6949
|
+
child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`Update download failed (${code}): ${diagnostic.trim()}`)));
|
|
6950
|
+
});
|
|
6951
|
+
}
|
|
6952
|
+
async function runAutomaticUpdate(home, dependencies = { fetchRelease: fetchVerifiedRelease, install: runInstaller }, now = Date.now()) {
|
|
6953
|
+
await withLock(home, ".update-lock", async () => {
|
|
6954
|
+
const record = await readInstallation(home);
|
|
6955
|
+
if (!automaticUpdatesAllowed(record)) return;
|
|
6956
|
+
const old = statusSchema2.safeParse(await readStoreJson(home, "update-status.json"));
|
|
6957
|
+
if (old.success && now - old.data.lastAttempt < CHECK_INTERVAL_MS && old.data.lastAttempt <= now) return;
|
|
6958
|
+
const status = { lastAttempt: now };
|
|
6959
|
+
await writeStoreJson(home, "update-status.json", status);
|
|
6960
|
+
try {
|
|
6961
|
+
await pruneUnusedVersions(home).catch(() => {
|
|
6962
|
+
});
|
|
6963
|
+
const release = await dependencies.fetchRelease(home);
|
|
6964
|
+
if (!release) return;
|
|
6965
|
+
status.availableVersion = release.version;
|
|
6966
|
+
const age = now - Date.parse(release.publishedAt);
|
|
6967
|
+
if (age >= RELEASE_AGE_MS && newerStableVersion(release.version, record.version) && (!record.pending || newerStableVersion(release.version, record.pending.version))) {
|
|
6968
|
+
const asset = `mason-${process.platform}-${process.arch}.${process.platform === "win32" ? "zip" : "tar.gz"}`;
|
|
6969
|
+
const digest3 = release.assets[asset];
|
|
6970
|
+
if (!digest3) throw new Error("Signed release has no archive for this platform.");
|
|
6971
|
+
const fresh = await readInstallation(home);
|
|
6972
|
+
if (automaticUpdatesAllowed(fresh) && fresh.updates?.revision === record.updates.revision && newerStableVersion(release.version, fresh.version)) {
|
|
6973
|
+
await dependencies.install(home, release.version, digest3, record.updates.revision);
|
|
6974
|
+
}
|
|
6975
|
+
}
|
|
6976
|
+
} catch (error) {
|
|
6977
|
+
status.error = error instanceof Error ? error.message : String(error);
|
|
6978
|
+
}
|
|
6979
|
+
if (await fs28.access(path34.join(home, "install.json")).then(() => true, () => false)) await writeStoreJson(home, "update-status.json", status);
|
|
6980
|
+
}, 0);
|
|
6981
|
+
}
|
|
6982
|
+
async function scheduleAutomaticUpdate() {
|
|
6983
|
+
try {
|
|
6984
|
+
const { home, record } = await currentInstallation();
|
|
6985
|
+
if (!automaticUpdatesAllowed(record)) return;
|
|
6986
|
+
const status = statusSchema2.safeParse(await readStoreJson(home, "update-status.json"));
|
|
6987
|
+
const now = Date.now();
|
|
6988
|
+
if (status.success && status.data.lastAttempt <= now && now - status.data.lastAttempt < CHECK_INTERVAL_MS) return;
|
|
6989
|
+
const bundle = await storePath(home, "versions/" + record.current);
|
|
6990
|
+
const child = spawn2(
|
|
6991
|
+
path34.join(bundle, process.platform === "win32" ? "node.exe" : "node"),
|
|
6992
|
+
[path34.join(bundle, "app/dist/mason.js"), "internal-update"],
|
|
6993
|
+
{ cwd: home, env: process.env, stdio: "ignore", detached: true, windowsHide: true }
|
|
6994
|
+
);
|
|
6995
|
+
child.on("error", () => {
|
|
6996
|
+
});
|
|
6997
|
+
child.unref();
|
|
6998
|
+
} catch {
|
|
6999
|
+
}
|
|
7000
|
+
}
|
|
7001
|
+
async function updateSettings(home, action) {
|
|
7002
|
+
return withLock(home, ".install-lock", async () => {
|
|
7003
|
+
const record = await readInstallation(home);
|
|
7004
|
+
const updates = { ...record.updates ?? { enabled: false }, revision: randomUUID11() };
|
|
7005
|
+
if (action === "enable") updates.enabled = true;
|
|
7006
|
+
if (action === "disable") updates.enabled = false;
|
|
7007
|
+
if (action === "pin") updates.pinnedVersion = record.version;
|
|
7008
|
+
if (action === "unpin") delete updates.pinnedVersion;
|
|
7009
|
+
record.updates = updates;
|
|
7010
|
+
delete record.pending;
|
|
7011
|
+
await writeStoreJson(home, "install.json", record);
|
|
7012
|
+
return record;
|
|
7013
|
+
});
|
|
7014
|
+
}
|
|
7015
|
+
async function updateStatus(home) {
|
|
7016
|
+
const record = await readInstallation(home);
|
|
7017
|
+
const lastCheck = statusSchema2.parse(await readStoreJson(home, "update-status.json") ?? { lastAttempt: 0 });
|
|
7018
|
+
return {
|
|
7019
|
+
version: record.version,
|
|
7020
|
+
enabled: record.updates?.enabled ?? false,
|
|
7021
|
+
effective: automaticUpdatesAllowed(record),
|
|
7022
|
+
pinnedVersion: record.updates?.pinnedVersion ?? null,
|
|
7023
|
+
mirror: record.updates?.mirror ?? null,
|
|
7024
|
+
pendingVersion: record.pending?.version ?? null,
|
|
7025
|
+
lastCheck: lastCheck.lastAttempt ? lastCheck : null
|
|
7026
|
+
};
|
|
7027
|
+
}
|
|
7028
|
+
async function startMcpWithUpdates(start) {
|
|
7029
|
+
const current = standaloneLocation();
|
|
7030
|
+
if (!current) {
|
|
7031
|
+
await start();
|
|
7032
|
+
return;
|
|
7033
|
+
}
|
|
7034
|
+
const session = await prepareStandaloneMcp(current.home);
|
|
7035
|
+
if (session.bundle !== current.bundle) {
|
|
7036
|
+
const child = spawn2(
|
|
7037
|
+
path34.join(session.bundle, process.platform === "win32" ? "node.exe" : "node"),
|
|
7038
|
+
[path34.join(session.bundle, "app/dist/mason-mcp.js"), ...process.argv.slice(2)],
|
|
7039
|
+
{ stdio: "inherit", windowsHide: true }
|
|
7040
|
+
);
|
|
7041
|
+
const interrupt = () => {
|
|
7042
|
+
child.kill("SIGINT");
|
|
7043
|
+
}, terminate = () => {
|
|
7044
|
+
child.kill("SIGTERM");
|
|
7045
|
+
};
|
|
7046
|
+
process.on("SIGINT", interrupt);
|
|
7047
|
+
process.on("SIGTERM", terminate);
|
|
7048
|
+
try {
|
|
7049
|
+
process.exitCode = await new Promise((resolve, reject) => {
|
|
7050
|
+
child.on("error", reject);
|
|
7051
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
7052
|
+
});
|
|
7053
|
+
} finally {
|
|
7054
|
+
process.off("SIGINT", interrupt);
|
|
7055
|
+
process.off("SIGTERM", terminate);
|
|
7056
|
+
await session.release();
|
|
7057
|
+
}
|
|
7058
|
+
return;
|
|
7059
|
+
}
|
|
7060
|
+
const timer = setInterval(() => {
|
|
7061
|
+
void scheduleAutomaticUpdate();
|
|
7062
|
+
}, 60 * 60 * 1e3);
|
|
7063
|
+
timer.unref();
|
|
7064
|
+
const stop = () => {
|
|
7065
|
+
clearInterval(timer);
|
|
7066
|
+
void session?.release();
|
|
7067
|
+
};
|
|
7068
|
+
try {
|
|
7069
|
+
await start(stop);
|
|
7070
|
+
void scheduleAutomaticUpdate();
|
|
7071
|
+
} catch (error) {
|
|
7072
|
+
stop();
|
|
7073
|
+
throw error;
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
var CHECK_INTERVAL_MS, RELEASE_AGE_MS, releaseBase, releaseListSchema, updateManifestSchema, statusSchema2;
|
|
7077
|
+
var init_updates = __esm({
|
|
7078
|
+
"src/distribution/updates.ts"() {
|
|
7079
|
+
"use strict";
|
|
7080
|
+
init_store();
|
|
7081
|
+
init_storage();
|
|
7082
|
+
init_state();
|
|
7083
|
+
init_bundle();
|
|
7084
|
+
init_runtime2();
|
|
7085
|
+
init_install2();
|
|
7086
|
+
CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
7087
|
+
RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
7088
|
+
releaseBase = "https://github.com/adrianczuczka/mason/releases";
|
|
7089
|
+
releaseListSchema = z28.array(z28.object({ tag_name: z28.string(), draft: z28.boolean(), prerelease: z28.boolean(), published_at: z28.string().nullable() }));
|
|
7090
|
+
updateManifestSchema = z28.object({
|
|
7091
|
+
format: z28.literal(1),
|
|
7092
|
+
version: z28.string().regex(/^\d+\.\d+\.\d+$/),
|
|
7093
|
+
publishedAt: z28.string().datetime(),
|
|
7094
|
+
assets: z28.record(z28.string().regex(/^[a-f0-9]{64}$/))
|
|
7095
|
+
}).strict();
|
|
7096
|
+
statusSchema2 = z28.object({ lastAttempt: z28.number(), availableVersion: z28.string().optional(), error: z28.string().optional() });
|
|
6259
7097
|
}
|
|
6260
7098
|
});
|
|
6261
7099
|
|
|
@@ -6452,7 +7290,7 @@ var inputArgs = process.argv.slice(2);
|
|
|
6452
7290
|
var managedLaunch = inputArgs[0] === "--setup-host";
|
|
6453
7291
|
var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
|
|
6454
7292
|
var [command, ...args] = inputArgs;
|
|
6455
|
-
var usage = `Mason ${"0.
|
|
7293
|
+
var usage = `Mason ${"0.19.1"}
|
|
6456
7294
|
|
|
6457
7295
|
Usage: mason <command> [options]
|
|
6458
7296
|
|
|
@@ -6465,6 +7303,9 @@ Usage: mason <command> [options]
|
|
|
6465
7303
|
drift Check an optional architecture map
|
|
6466
7304
|
mcp Run the MCP server (stdio)
|
|
6467
7305
|
upgrade [version] Upgrade the standalone user installation
|
|
7306
|
+
updates [status|enable|disable|pin|unpin]
|
|
7307
|
+
Manage automatic standalone updates
|
|
7308
|
+
rollback Restore and pin the previous standalone version
|
|
6468
7309
|
uninstall Remove that installation; retain project data
|
|
6469
7310
|
|
|
6470
7311
|
Use mason <command> --help for options. npm users can keep using mason-auto,
|
|
@@ -6478,19 +7319,67 @@ try {
|
|
|
6478
7319
|
await prepareLaunch2(setupHost, { allowInactive: command === "mcp" });
|
|
6479
7320
|
}
|
|
6480
7321
|
}
|
|
6481
|
-
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.
|
|
7322
|
+
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.19.1" }));
|
|
6482
7323
|
else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
|
|
6483
|
-
else if (["--version", "-v"].includes(command)) console.log("0.
|
|
6484
|
-
else if (command === "internal-
|
|
7324
|
+
else if (["--version", "-v"].includes(command)) console.log("0.19.1");
|
|
7325
|
+
else if (command === "internal-update") {
|
|
7326
|
+
if (args.length) throw new Error("internal-update takes no arguments.");
|
|
7327
|
+
const { currentInstallation: currentInstallation2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
7328
|
+
const { runAutomaticUpdate: runAutomaticUpdate2 } = await Promise.resolve().then(() => (init_updates(), updates_exports));
|
|
7329
|
+
await runAutomaticUpdate2((await currentInstallation2()).home);
|
|
7330
|
+
} else if (command === "internal-install") {
|
|
6485
7331
|
if (args.length) throw new Error("internal-install takes no arguments.");
|
|
6486
7332
|
const { installStandalone: installStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
6487
7333
|
const bundleRoot = fileURLToPath2(new URL("../..", import.meta.url));
|
|
6488
7334
|
progress = createProgress();
|
|
6489
|
-
const
|
|
7335
|
+
const policyRevision = process.env.MASON_UPDATE_POLICY_REVISION;
|
|
7336
|
+
if (policyRevision && !/^[a-f0-9]{64}$/.test(process.env.MASON_EXPECTED_SHA256 ?? "")) throw new Error("Automatic staging requires a verified release checksum.");
|
|
7337
|
+
const installed = await installStandalone2(bundleRoot, progress, { stageOnly: !!policyRevision, policyRevision });
|
|
6490
7338
|
progress.stop();
|
|
6491
7339
|
const outcome = installed.previousVersion && installed.previousVersion !== installed.version ? `Updated Mason ${installed.previousVersion} \u2192 ${installed.version}` : `Installed Mason ${installed.version}`;
|
|
6492
|
-
console.log(
|
|
6493
|
-
|
|
7340
|
+
if (policyRevision) console.log(`Staged Mason ${installed.version}. It will activate at a future MCP launch after existing servers exit.`);
|
|
7341
|
+
else {
|
|
7342
|
+
const { updateStatus: updateStatus2 } = await Promise.resolve().then(() => (init_updates(), updates_exports));
|
|
7343
|
+
const status = await updateStatus2(installed.home);
|
|
7344
|
+
const policy = status.pinnedVersion ? `pinned to ${status.pinnedVersion}` : status.mirror ? "managed by your mirror" : status.enabled ? "enabled" : "disabled";
|
|
7345
|
+
console.log(`${outcome} in ${installed.home}.
|
|
7346
|
+
${installed.path.message}
|
|
7347
|
+
Automatic updates: ${policy}. Manage with mason updates.`);
|
|
7348
|
+
}
|
|
7349
|
+
} else if (command === "updates") {
|
|
7350
|
+
if (args.includes("--help")) console.log("Usage: mason updates [status|enable|disable|pin|unpin] [--json]. Pin holds the installed version; mason upgrade <version> installs and pins a specific release. Environment and mirror restrictions still apply.");
|
|
7351
|
+
else {
|
|
7352
|
+
const action = args.filter((arg) => arg !== "--json")[0] ?? "status";
|
|
7353
|
+
if (args.filter((arg) => arg !== "--json").length > 1 || !["status", "enable", "disable", "pin", "unpin"].includes(action)) throw new Error("Usage: mason updates [status|enable|disable|pin|unpin] [--json]");
|
|
7354
|
+
const { currentInstallation: currentInstallation2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
7355
|
+
const { updateSettings: updateSettings2, updateStatus: updateStatus2, scheduleAutomaticUpdate: scheduleAutomaticUpdate2 } = await Promise.resolve().then(() => (init_updates(), updates_exports));
|
|
7356
|
+
const { home } = await currentInstallation2();
|
|
7357
|
+
if (action !== "status") await updateSettings2(home, action);
|
|
7358
|
+
const status = await updateStatus2(home);
|
|
7359
|
+
if (args.includes("--json")) console.log(JSON.stringify(status));
|
|
7360
|
+
else {
|
|
7361
|
+
console.log(`Mason ${status.version}
|
|
7362
|
+
Automatic updates: ${status.effective ? "enabled" : "inactive"} (preference: ${status.enabled ? "enabled" : "disabled"}).`);
|
|
7363
|
+
if (status.pinnedVersion) console.log(`Pinned to ${status.pinnedVersion}. Run mason updates unpin to follow stable releases.`);
|
|
7364
|
+
if (status.mirror) console.log("Company mirror installation: automatic public updates are disabled.");
|
|
7365
|
+
if (!status.effective && status.enabled && !status.pinnedVersion && !status.mirror) console.log("Suppressed by CI, MASON_NO_AUTO_UPDATE, MASON_VERSION, or MASON_RELEASE_BASE.");
|
|
7366
|
+
if (status.pendingVersion) console.log(`Downloaded ${status.pendingVersion}; activates on a future MCP launch after existing servers exit.`);
|
|
7367
|
+
if (status.lastCheck) {
|
|
7368
|
+
console.log(`Last check: ${new Date(status.lastCheck.lastAttempt).toISOString()}`);
|
|
7369
|
+
if (status.lastCheck.availableVersion) console.log(`Available stable release: ${status.lastCheck.availableVersion}`);
|
|
7370
|
+
if (status.lastCheck.error) console.log(`Last update problem: ${status.lastCheck.error}`);
|
|
7371
|
+
}
|
|
7372
|
+
}
|
|
7373
|
+
if (action === "enable" || action === "unpin") void scheduleAutomaticUpdate2();
|
|
7374
|
+
}
|
|
7375
|
+
} else if (command === "rollback") {
|
|
7376
|
+
if (args.includes("--help")) console.log("Usage: mason rollback. Close running Mason MCP servers, then restore and pin the previous installed version.");
|
|
7377
|
+
else {
|
|
7378
|
+
if (args.length) throw new Error("rollback takes no arguments.");
|
|
7379
|
+
const { rollbackStandalone: rollbackStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
7380
|
+
const restored = await rollbackStandalone2();
|
|
7381
|
+
console.log(`Restored Mason ${restored.version} and pinned it. Run mason updates unpin to resume automatic updates.`);
|
|
7382
|
+
}
|
|
6494
7383
|
} else if (command === "upgrade" || command === "uninstall") {
|
|
6495
7384
|
if (args.includes("--help")) console.log(command === "upgrade" ? "Usage: mason upgrade [version]. Configured projects use the upgraded command on their next launch. Restart running assistants." : "Usage: mason uninstall. Removes the standalone user installation; project configuration and knowledge are retained. Run mason teardown in each project first to disconnect its integrations.");
|
|
6496
7385
|
else {
|
|
@@ -6499,7 +7388,6 @@ ${installed.path.message}`);
|
|
|
6499
7388
|
if (command === "upgrade") {
|
|
6500
7389
|
progress = createProgress();
|
|
6501
7390
|
process.exitCode = await upgradeStandalone2(args[0], progress);
|
|
6502
|
-
if (process.exitCode === 0) console.log("Restart running assistants to use this installation. Existing project integrations use mason from PATH.");
|
|
6503
7391
|
} else console.log((process.platform === "win32" ? "Finishing installation cleanup after runtime exit: " : "Removed standalone installation: ") + await uninstallStandalone2() + ". Project configuration and knowledge were retained. Reinstall Mason to use its integrations.");
|
|
6504
7392
|
}
|
|
6505
7393
|
} else if (["setup", "teardown", "status", "check", "auto"].includes(command)) {
|
|
@@ -6515,6 +7403,10 @@ ${installed.path.message}`);
|
|
|
6515
7403
|
void 0,
|
|
6516
7404
|
{ managedHost: managedLaunch ? setupHost : void 0 }
|
|
6517
7405
|
);
|
|
7406
|
+
if (command === "setup" && process.exitCode === 0 && !args.includes("--help")) {
|
|
7407
|
+
const { scheduleAutomaticUpdate: scheduleAutomaticUpdate2 } = await Promise.resolve().then(() => (init_updates(), updates_exports));
|
|
7408
|
+
void scheduleAutomaticUpdate2();
|
|
7409
|
+
}
|
|
6518
7410
|
} else if (["audit", "review", "drift", "hook", "mcp"].includes(command)) {
|
|
6519
7411
|
const binary = new URL(`./mason-${command}.js`, import.meta.url);
|
|
6520
7412
|
process.argv = [process.execPath, fileURLToPath2(binary), ...args];
|