mason-context 0.17.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/mason-audit.js +304 -222
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-auto.js +339 -253
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-drift.js +54 -22
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +55 -22
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +831 -603
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +63 -30
- package/dist/mason-review.js.map +1 -1
- package/dist/mason.js +372 -286
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/dist/mason.js
CHANGED
|
@@ -36,12 +36,69 @@ var init_paths = __esm({
|
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
// src/utils/profile.ts
|
|
40
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
41
|
+
async function profilePhase(label, run) {
|
|
42
|
+
const phases = profiles.getStore();
|
|
43
|
+
if (!phases) return run();
|
|
44
|
+
const start = performance.now();
|
|
45
|
+
try {
|
|
46
|
+
return await run();
|
|
47
|
+
} finally {
|
|
48
|
+
const timing = phases[label] ??= { calls: 0, totalMs: 0 };
|
|
49
|
+
timing.calls++;
|
|
50
|
+
timing.totalMs += performance.now() - start;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function withProfile(run, emit) {
|
|
54
|
+
const phases = {};
|
|
55
|
+
const start = performance.now();
|
|
56
|
+
return profiles.run(phases, async () => {
|
|
57
|
+
try {
|
|
58
|
+
return await run();
|
|
59
|
+
} finally {
|
|
60
|
+
emit(JSON.stringify({ kind: "mason-profile", version: 1, totalMs: performance.now() - start, phases }));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
var profiles;
|
|
65
|
+
var init_profile = __esm({
|
|
66
|
+
"src/utils/profile.ts"() {
|
|
67
|
+
"use strict";
|
|
68
|
+
profiles = new AsyncLocalStorage();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// src/utils/git-read.ts
|
|
73
|
+
import { execFile } from "child_process";
|
|
74
|
+
import { promisify } from "util";
|
|
75
|
+
function execGit(args2, options) {
|
|
76
|
+
const command2 = args2.find((arg) => !arg.startsWith("-")) ?? "other";
|
|
77
|
+
return profilePhase("git." + (commands.has(command2) ? command2 : "other"), async () => {
|
|
78
|
+
const { input, ...processOptions } = options;
|
|
79
|
+
const operation = execute("git", args2, processOptions);
|
|
80
|
+
if (input !== void 0) {
|
|
81
|
+
operation.child.stdin.on("error", () => {
|
|
82
|
+
});
|
|
83
|
+
operation.child.stdin.end(input);
|
|
84
|
+
}
|
|
85
|
+
return operation;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
var execute, commands;
|
|
89
|
+
var init_git_read = __esm({
|
|
90
|
+
"src/utils/git-read.ts"() {
|
|
91
|
+
"use strict";
|
|
92
|
+
init_profile();
|
|
93
|
+
execute = promisify(execFile);
|
|
94
|
+
commands = /* @__PURE__ */ new Set(["rev-parse", "symbolic-ref", "for-each-ref", "ls-files", "check-ignore", "status", "log", "diff", "ls-tree", "show", "merge-base"]);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
39
98
|
// src/utils/files.ts
|
|
40
99
|
import fs from "fs/promises";
|
|
41
100
|
import { constants } from "fs";
|
|
42
101
|
import path2 from "path";
|
|
43
|
-
import { execFile } from "child_process";
|
|
44
|
-
import { promisify } from "util";
|
|
45
102
|
import fg from "fast-glob";
|
|
46
103
|
function isSensitiveFile(file) {
|
|
47
104
|
return file.split(/[\\/]/).some(
|
|
@@ -95,12 +152,12 @@ async function createFileAccess(rootDir) {
|
|
|
95
152
|
const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
|
|
96
153
|
let gitFiles = null;
|
|
97
154
|
try {
|
|
98
|
-
const { stdout } = await
|
|
155
|
+
const { stdout } = await execGit(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
|
|
99
156
|
gitFiles = new Set(stdout.split("\0").filter(Boolean));
|
|
100
157
|
} catch {
|
|
101
158
|
let inGit = false;
|
|
102
159
|
try {
|
|
103
|
-
await
|
|
160
|
+
await execGit(["rev-parse", "--git-dir"], { cwd: root });
|
|
104
161
|
inGit = true;
|
|
105
162
|
} catch {
|
|
106
163
|
}
|
|
@@ -143,12 +200,12 @@ async function createFileAccess(rootDir) {
|
|
|
143
200
|
}
|
|
144
201
|
return { root, config, list, read };
|
|
145
202
|
}
|
|
146
|
-
var
|
|
203
|
+
var SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
|
|
147
204
|
var init_files = __esm({
|
|
148
205
|
"src/utils/files.ts"() {
|
|
149
206
|
"use strict";
|
|
207
|
+
init_git_read();
|
|
150
208
|
init_paths();
|
|
151
|
-
exec = promisify(execFile);
|
|
152
209
|
SOURCE_EXTENSIONS = ["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs", "vue", "svelte", "kt", "kts", "java", "py", "go", "rs", "swift", "rb", "cs", "cpp", "c", "h", "hpp", "dart", "php"];
|
|
153
210
|
SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(",")}}`;
|
|
154
211
|
SOURCE_IGNORE = [
|
|
@@ -248,18 +305,34 @@ var init_storage = __esm({
|
|
|
248
305
|
}
|
|
249
306
|
});
|
|
250
307
|
|
|
251
|
-
// src/
|
|
308
|
+
// src/utils/store-lock.ts
|
|
252
309
|
import fs3 from "fs/promises";
|
|
253
310
|
import os from "os";
|
|
254
|
-
|
|
255
|
-
|
|
311
|
+
async function lockDiagnostic(file) {
|
|
312
|
+
let reason2;
|
|
256
313
|
try {
|
|
257
|
-
|
|
314
|
+
const owner = JSON.parse(await fs3.readFile(file, "utf8"));
|
|
315
|
+
if (!owner || typeof owner.host !== "string" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
|
|
316
|
+
reason2 = "Lock owner data is incomplete or malformed.";
|
|
317
|
+
} else if (owner.host !== os.hostname()) {
|
|
318
|
+
reason2 = "The lock belongs to another host; its process cannot be checked locally.";
|
|
319
|
+
} else {
|
|
320
|
+
try {
|
|
321
|
+
process.kill(owner.pid, 0);
|
|
322
|
+
reason2 = `Local owner PID ${owner.pid} is still running.`;
|
|
323
|
+
} catch (error) {
|
|
324
|
+
reason2 = error.code === "ESRCH" ? `Local owner PID ${owner.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.pid} could not be checked.`;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
258
327
|
} catch (error) {
|
|
259
|
-
|
|
328
|
+
reason2 = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
|
|
329
|
+
}
|
|
330
|
+
if (await fs3.lstat(file + ".reclaim").then(() => true, () => false)) {
|
|
331
|
+
reason2 += " A recovery guard also needs inspection: " + file + ".reclaim.";
|
|
260
332
|
}
|
|
333
|
+
return reason2 + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
|
|
261
334
|
}
|
|
262
|
-
async function
|
|
335
|
+
async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason store") {
|
|
263
336
|
const file = await storePath(root, directory + "/lock", true);
|
|
264
337
|
const deadline = Date.now() + waitMs;
|
|
265
338
|
let handle;
|
|
@@ -302,7 +375,7 @@ async function withLock(root, directory, run, waitMs = 5e3) {
|
|
|
302
375
|
}
|
|
303
376
|
} catch {
|
|
304
377
|
}
|
|
305
|
-
if (Date.now() >= deadline) throw new Error("
|
|
378
|
+
if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
|
|
306
379
|
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
307
380
|
}
|
|
308
381
|
}
|
|
@@ -313,11 +386,30 @@ async function withLock(root, directory, run, waitMs = 5e3) {
|
|
|
313
386
|
await fs3.unlink(file);
|
|
314
387
|
}
|
|
315
388
|
}
|
|
389
|
+
var init_store_lock = __esm({
|
|
390
|
+
"src/utils/store-lock.ts"() {
|
|
391
|
+
"use strict";
|
|
392
|
+
init_storage();
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// src/automation/store.ts
|
|
397
|
+
import { z } from "zod";
|
|
398
|
+
function parseState(raw) {
|
|
399
|
+
try {
|
|
400
|
+
return stateSchema.parse(raw);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function withLock(root, directory, run, waitMs = 5e3) {
|
|
406
|
+
return withStoreLock(root, directory, run, waitMs, "Automation");
|
|
407
|
+
}
|
|
316
408
|
var hostSchema, events, stateSchema;
|
|
317
409
|
var init_store = __esm({
|
|
318
410
|
"src/automation/store.ts"() {
|
|
319
411
|
"use strict";
|
|
320
|
-
|
|
412
|
+
init_store_lock();
|
|
321
413
|
hostSchema = z.enum(["claude", "codex"]);
|
|
322
414
|
events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
|
|
323
415
|
stateSchema = z.object({
|
|
@@ -346,39 +438,6 @@ var init_store = __esm({
|
|
|
346
438
|
}
|
|
347
439
|
});
|
|
348
440
|
|
|
349
|
-
// src/utils/profile.ts
|
|
350
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
351
|
-
async function profilePhase(label, run) {
|
|
352
|
-
const phases = profiles.getStore();
|
|
353
|
-
if (!phases) return run();
|
|
354
|
-
const start = performance.now();
|
|
355
|
-
try {
|
|
356
|
-
return await run();
|
|
357
|
-
} finally {
|
|
358
|
-
const timing = phases[label] ??= { calls: 0, totalMs: 0 };
|
|
359
|
-
timing.calls++;
|
|
360
|
-
timing.totalMs += performance.now() - start;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
async function withProfile(run, emit) {
|
|
364
|
-
const phases = {};
|
|
365
|
-
const start = performance.now();
|
|
366
|
-
return profiles.run(phases, async () => {
|
|
367
|
-
try {
|
|
368
|
-
return await run();
|
|
369
|
-
} finally {
|
|
370
|
-
emit(JSON.stringify({ kind: "mason-profile", version: 1, totalMs: performance.now() - start, phases }));
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
var profiles;
|
|
375
|
-
var init_profile = __esm({
|
|
376
|
-
"src/utils/profile.ts"() {
|
|
377
|
-
"use strict";
|
|
378
|
-
profiles = new AsyncLocalStorage();
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
|
|
382
441
|
// src/audit/commands.ts
|
|
383
442
|
import path4 from "path";
|
|
384
443
|
function splitCommands(line) {
|
|
@@ -603,14 +662,14 @@ function extractClaims(content2) {
|
|
|
603
662
|
const ignored = computeIgnoredLines(lines);
|
|
604
663
|
const paths = /* @__PURE__ */ new Map();
|
|
605
664
|
const counts = [];
|
|
606
|
-
const
|
|
665
|
+
const commands2 = /* @__PURE__ */ new Map();
|
|
607
666
|
const addPath = (claim) => {
|
|
608
667
|
const key = [claim.relativeTo ?? "scope", claim.path].join(":");
|
|
609
668
|
if (!paths.has(key)) paths.set(key, claim);
|
|
610
669
|
};
|
|
611
670
|
const addCommand = (claim) => {
|
|
612
671
|
const key = JSON.stringify([claim.scriptName, claim.directory, claim.scopeUnknown]);
|
|
613
|
-
if (!
|
|
672
|
+
if (!commands2.has(key)) commands2.set(key, claim);
|
|
614
673
|
};
|
|
615
674
|
const parsed = markdown.parse(lines.map((line, i) => ignored[i] ? "" : line).join("\n"), {});
|
|
616
675
|
for (const token of parsed) {
|
|
@@ -712,7 +771,7 @@ function extractClaims(content2) {
|
|
|
712
771
|
return {
|
|
713
772
|
paths: [...paths.values()],
|
|
714
773
|
counts,
|
|
715
|
-
commands: [...
|
|
774
|
+
commands: [...commands2.values()]
|
|
716
775
|
};
|
|
717
776
|
}
|
|
718
777
|
var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
|
|
@@ -762,9 +821,53 @@ var init_claims = __esm({
|
|
|
762
821
|
}
|
|
763
822
|
});
|
|
764
823
|
|
|
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
|
+
|
|
765
870
|
// src/audit/git.ts
|
|
766
|
-
import { execFile as execFile2 } from "child_process";
|
|
767
|
-
import { promisify as promisify2 } from "util";
|
|
768
871
|
function parseCommitLine(line) {
|
|
769
872
|
const parts = line.split(" ");
|
|
770
873
|
if (parts.length < 3 || !parts[0]) return null;
|
|
@@ -772,8 +875,7 @@ function parseCommitLine(line) {
|
|
|
772
875
|
}
|
|
773
876
|
async function lastCommitOf(resolvedRoot, relPath) {
|
|
774
877
|
try {
|
|
775
|
-
const { stdout } = await
|
|
776
|
-
"git",
|
|
878
|
+
const { stdout } = await inspectionGit(
|
|
777
879
|
["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
778
880
|
{ cwd: resolvedRoot }
|
|
779
881
|
);
|
|
@@ -785,8 +887,7 @@ async function lastCommitOf(resolvedRoot, relPath) {
|
|
|
785
887
|
}
|
|
786
888
|
async function deletingCommitOf(resolvedRoot, relPath) {
|
|
787
889
|
try {
|
|
788
|
-
const { stdout } = await
|
|
789
|
-
"git",
|
|
890
|
+
const { stdout } = await inspectionGit(
|
|
790
891
|
[
|
|
791
892
|
"log",
|
|
792
893
|
"-1",
|
|
@@ -805,8 +906,7 @@ async function deletingCommitOf(resolvedRoot, relPath) {
|
|
|
805
906
|
}
|
|
806
907
|
async function firstCommitOf(resolvedRoot, relPath) {
|
|
807
908
|
try {
|
|
808
|
-
const { stdout } = await
|
|
809
|
-
"git",
|
|
909
|
+
const { stdout } = await inspectionGit(
|
|
810
910
|
["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
811
911
|
{ cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
|
|
812
912
|
);
|
|
@@ -819,8 +919,7 @@ async function firstCommitOf(resolvedRoot, relPath) {
|
|
|
819
919
|
async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
|
|
820
920
|
if (!fromHash || fromHash === "unknown") return null;
|
|
821
921
|
try {
|
|
822
|
-
const { stdout } = await
|
|
823
|
-
"git",
|
|
922
|
+
const { stdout } = await inspectionGit(
|
|
824
923
|
[
|
|
825
924
|
"log",
|
|
826
925
|
`${fromHash}..HEAD`,
|
|
@@ -844,11 +943,11 @@ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
|
|
|
844
943
|
return null;
|
|
845
944
|
}
|
|
846
945
|
}
|
|
847
|
-
var
|
|
946
|
+
var COMMIT_FORMAT;
|
|
848
947
|
var init_git = __esm({
|
|
849
948
|
"src/audit/git.ts"() {
|
|
850
949
|
"use strict";
|
|
851
|
-
|
|
950
|
+
init_inspection();
|
|
852
951
|
COMMIT_FORMAT = "%H%x09%cI%x09%s";
|
|
853
952
|
}
|
|
854
953
|
});
|
|
@@ -856,9 +955,7 @@ var init_git = __esm({
|
|
|
856
955
|
// src/audit/inputs.ts
|
|
857
956
|
import fs4 from "fs/promises";
|
|
858
957
|
import * as nativeFs from "fs";
|
|
859
|
-
import
|
|
860
|
-
import { execFile as execFile3 } from "child_process";
|
|
861
|
-
import { promisify as promisify3 } from "util";
|
|
958
|
+
import path6 from "path";
|
|
862
959
|
import fg2 from "fast-glob";
|
|
863
960
|
async function auditInputPath(root, file) {
|
|
864
961
|
if (file === ".") return fs4.realpath(root);
|
|
@@ -885,7 +982,7 @@ async function auditGlob(root, patterns, options) {
|
|
|
885
982
|
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
886
983
|
const directories = /* @__PURE__ */ new Set();
|
|
887
984
|
const readdir2 = ((directory, opts, callback) => {
|
|
888
|
-
const relative =
|
|
985
|
+
const relative = path6.relative(root, directory).split(path6.sep).join("/") || ".";
|
|
889
986
|
directories.add(relative);
|
|
890
987
|
if (directories.size > MAX_INPUTS) {
|
|
891
988
|
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
@@ -896,7 +993,7 @@ async function auditGlob(root, patterns, options) {
|
|
|
896
993
|
}, callback);
|
|
897
994
|
});
|
|
898
995
|
const lstat2 = ((file, callback) => {
|
|
899
|
-
const relative =
|
|
996
|
+
const relative = path6.relative(root, file).split(path6.sep).join("/") || ".";
|
|
900
997
|
auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
|
|
901
998
|
});
|
|
902
999
|
const stream = fg2.stream(patterns, {
|
|
@@ -933,62 +1030,48 @@ async function auditGlob(root, patterns, options) {
|
|
|
933
1030
|
return matches.sort();
|
|
934
1031
|
}
|
|
935
1032
|
async function gitSourcePaths(root) {
|
|
936
|
-
|
|
937
|
-
"git",
|
|
938
|
-
[
|
|
939
|
-
"ls-files",
|
|
940
|
-
"-z",
|
|
941
|
-
"--cached",
|
|
942
|
-
"--others",
|
|
943
|
-
"--exclude-standard",
|
|
944
|
-
"--",
|
|
945
|
-
...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
|
|
946
|
-
],
|
|
947
|
-
{ cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
|
|
948
|
-
);
|
|
949
|
-
return [...new Set(stdout.split("\0").filter(Boolean))].sort();
|
|
1033
|
+
return auditGitPaths(root, "sources");
|
|
950
1034
|
}
|
|
951
1035
|
async function gitIgnoredPaths(root, files) {
|
|
952
1036
|
if (!files.length) return /* @__PURE__ */ new Set();
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
+
}
|
|
966
1050
|
}
|
|
1051
|
+
return ignored;
|
|
967
1052
|
}
|
|
968
|
-
var
|
|
1053
|
+
var MAX_INPUTS;
|
|
969
1054
|
var init_inputs = __esm({
|
|
970
1055
|
"src/audit/inputs.ts"() {
|
|
971
1056
|
"use strict";
|
|
972
1057
|
init_files();
|
|
973
1058
|
init_storage();
|
|
974
|
-
|
|
1059
|
+
init_git_read();
|
|
1060
|
+
init_inspection();
|
|
975
1061
|
MAX_INPUTS = 1e5;
|
|
976
1062
|
}
|
|
977
1063
|
});
|
|
978
1064
|
|
|
979
1065
|
// src/audit/docs.ts
|
|
980
1066
|
import fs5 from "fs/promises";
|
|
981
|
-
import
|
|
982
|
-
import { execFile as execFile4 } from "child_process";
|
|
983
|
-
import { promisify as promisify4 } from "util";
|
|
1067
|
+
import path7 from "path";
|
|
984
1068
|
import fg3 from "fast-glob";
|
|
985
1069
|
async function dirtyDocs(resolvedRoot, files) {
|
|
986
1070
|
if (!files.length) return /* @__PURE__ */ new Set();
|
|
987
1071
|
try {
|
|
988
1072
|
const dirty = /* @__PURE__ */ new Set();
|
|
989
1073
|
for (let offset = 0; offset < files.length; offset += 256) {
|
|
990
|
-
const { stdout } = await
|
|
991
|
-
"git",
|
|
1074
|
+
const { stdout } = await execGit(
|
|
992
1075
|
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
|
|
993
1076
|
{ cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
994
1077
|
);
|
|
@@ -1008,25 +1091,10 @@ async function dirtyDocs(resolvedRoot, files) {
|
|
|
1008
1091
|
async function discoverDocPaths(root) {
|
|
1009
1092
|
let candidates = [];
|
|
1010
1093
|
try {
|
|
1011
|
-
|
|
1012
|
-
"git",
|
|
1013
|
-
[
|
|
1014
|
-
"ls-files",
|
|
1015
|
-
"-z",
|
|
1016
|
-
"--cached",
|
|
1017
|
-
"--others",
|
|
1018
|
-
"--exclude-standard",
|
|
1019
|
-
"--",
|
|
1020
|
-
":(icase,glob)**/readme.md",
|
|
1021
|
-
":(icase,glob)**/agents.md",
|
|
1022
|
-
":(icase,glob)**/claude.md"
|
|
1023
|
-
],
|
|
1024
|
-
{ cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
1025
|
-
);
|
|
1026
|
-
candidates = stdout.split("\0").filter(Boolean);
|
|
1094
|
+
candidates = await auditGitPaths(root, "documents");
|
|
1027
1095
|
} catch (error) {
|
|
1028
1096
|
try {
|
|
1029
|
-
await
|
|
1097
|
+
await execGit(["rev-parse", "--git-dir"], { cwd: root });
|
|
1030
1098
|
} catch {
|
|
1031
1099
|
return localDocPaths(root);
|
|
1032
1100
|
}
|
|
@@ -1073,14 +1141,14 @@ async function localDocPaths(root) {
|
|
|
1073
1141
|
throw error;
|
|
1074
1142
|
}
|
|
1075
1143
|
for (const entry of entries) if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
|
|
1076
|
-
paths.push(
|
|
1144
|
+
paths.push(path7.posix.join(dir, entry.name));
|
|
1077
1145
|
}
|
|
1078
1146
|
}
|
|
1079
1147
|
return paths;
|
|
1080
1148
|
}
|
|
1081
1149
|
function documentScope(file) {
|
|
1082
|
-
const parent =
|
|
1083
|
-
return
|
|
1150
|
+
const parent = path7.posix.dirname(file);
|
|
1151
|
+
return path7.posix.basename(parent).toLowerCase() === ".claude" ? path7.posix.dirname(parent) : parent;
|
|
1084
1152
|
}
|
|
1085
1153
|
async function discoverDocs(resolvedRoot) {
|
|
1086
1154
|
return profilePhase("audit.documents", () => readDocs(resolvedRoot));
|
|
@@ -1100,7 +1168,7 @@ async function readDocs(resolvedRoot) {
|
|
|
1100
1168
|
path: candidate,
|
|
1101
1169
|
content: content2,
|
|
1102
1170
|
scope: documentScope(candidate),
|
|
1103
|
-
kind: /^readme\.md$/i.test(
|
|
1171
|
+
kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : "instructions",
|
|
1104
1172
|
lineCount: content2.split("\n").length,
|
|
1105
1173
|
lastCommit,
|
|
1106
1174
|
dirty: dirty.has(candidate),
|
|
@@ -1110,16 +1178,17 @@ async function readDocs(resolvedRoot) {
|
|
|
1110
1178
|
}
|
|
1111
1179
|
return docs;
|
|
1112
1180
|
}
|
|
1113
|
-
var
|
|
1181
|
+
var DOC_CANDIDATES;
|
|
1114
1182
|
var init_docs = __esm({
|
|
1115
1183
|
"src/audit/docs.ts"() {
|
|
1116
1184
|
"use strict";
|
|
1185
|
+
init_git_read();
|
|
1117
1186
|
init_profile();
|
|
1118
1187
|
init_claims();
|
|
1119
1188
|
init_git();
|
|
1120
1189
|
init_inputs();
|
|
1121
1190
|
init_files();
|
|
1122
|
-
|
|
1191
|
+
init_inspection();
|
|
1123
1192
|
DOC_CANDIDATES = [
|
|
1124
1193
|
"AGENTS.md",
|
|
1125
1194
|
"CLAUDE.md",
|
|
@@ -1130,14 +1199,14 @@ var init_docs = __esm({
|
|
|
1130
1199
|
|
|
1131
1200
|
// src/audit/scope.ts
|
|
1132
1201
|
import fs6 from "fs/promises";
|
|
1133
|
-
import
|
|
1202
|
+
import path8 from "path";
|
|
1134
1203
|
function scopedPath(directory, value) {
|
|
1135
|
-
if (
|
|
1136
|
-
const resolved =
|
|
1204
|
+
if (path8.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
|
|
1205
|
+
const resolved = path8.posix.normalize(path8.posix.join(directory, value));
|
|
1137
1206
|
return resolved === ".." || resolved.startsWith("../") ? null : resolved;
|
|
1138
1207
|
}
|
|
1139
1208
|
function pathClaimScope(doc, claim) {
|
|
1140
|
-
const directory = claim.relativeTo === "document" ?
|
|
1209
|
+
const directory = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
|
|
1141
1210
|
const local = scopedPath(directory, claim.path);
|
|
1142
1211
|
if (!local) return null;
|
|
1143
1212
|
const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
|
|
@@ -1170,10 +1239,18 @@ var init_scope = __esm({
|
|
|
1170
1239
|
}
|
|
1171
1240
|
});
|
|
1172
1241
|
|
|
1242
|
+
// src/snapshot/lock.ts
|
|
1243
|
+
var init_lock = __esm({
|
|
1244
|
+
"src/snapshot/lock.ts"() {
|
|
1245
|
+
"use strict";
|
|
1246
|
+
init_store_lock();
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1173
1250
|
// src/test-map.ts
|
|
1174
|
-
import
|
|
1251
|
+
import path9 from "path";
|
|
1175
1252
|
async function buildTestMap(dir) {
|
|
1176
|
-
const rootDir =
|
|
1253
|
+
const rootDir = path9.resolve(dir);
|
|
1177
1254
|
const access = await createFileAccess(rootDir);
|
|
1178
1255
|
const testPatterns = [
|
|
1179
1256
|
"**/*.test.*",
|
|
@@ -1194,7 +1271,7 @@ async function buildTestMap(dir) {
|
|
|
1194
1271
|
const sourceByBaseName = /* @__PURE__ */ new Map();
|
|
1195
1272
|
for (const file of sourceFiles) {
|
|
1196
1273
|
if (testFiles.includes(file)) continue;
|
|
1197
|
-
const baseName =
|
|
1274
|
+
const baseName = path9.basename(file).replace(/\.[^.]+$/, "");
|
|
1198
1275
|
const existing = sourceByBaseName.get(baseName) ?? [];
|
|
1199
1276
|
existing.push(file);
|
|
1200
1277
|
sourceByBaseName.set(baseName, existing);
|
|
@@ -1202,7 +1279,7 @@ async function buildTestMap(dir) {
|
|
|
1202
1279
|
const paired = [];
|
|
1203
1280
|
const unmatched = [];
|
|
1204
1281
|
for (const testFile of testFiles) {
|
|
1205
|
-
const testBaseName =
|
|
1282
|
+
const testBaseName = path9.basename(testFile).replace(/\.[^.]+$/, "");
|
|
1206
1283
|
const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
|
|
1207
1284
|
if (!sourceName) {
|
|
1208
1285
|
unmatched.push(testFile);
|
|
@@ -1210,10 +1287,10 @@ async function buildTestMap(dir) {
|
|
|
1210
1287
|
}
|
|
1211
1288
|
const candidates = sourceByBaseName.get(sourceName);
|
|
1212
1289
|
if (candidates && candidates.length > 0) {
|
|
1213
|
-
const testDir =
|
|
1290
|
+
const testDir = path9.dirname(testFile);
|
|
1214
1291
|
const bestMatch = candidates.reduce((best, candidate) => {
|
|
1215
|
-
const candidateDir =
|
|
1216
|
-
const bestDir =
|
|
1292
|
+
const candidateDir = path9.dirname(candidate);
|
|
1293
|
+
const bestDir = path9.dirname(best);
|
|
1217
1294
|
const candidateOverlap = commonSegments(testDir, candidateDir);
|
|
1218
1295
|
const bestOverlap = commonSegments(testDir, bestDir);
|
|
1219
1296
|
return candidateOverlap > bestOverlap ? candidate : best;
|
|
@@ -1247,9 +1324,7 @@ var init_test_map = __esm({
|
|
|
1247
1324
|
});
|
|
1248
1325
|
|
|
1249
1326
|
// src/snapshot/snapshot.ts
|
|
1250
|
-
import
|
|
1251
|
-
import { execFile as execFile5 } from "child_process";
|
|
1252
|
-
import { promisify as promisify5 } from "util";
|
|
1327
|
+
import path10 from "path";
|
|
1253
1328
|
import { z as z2 } from "zod";
|
|
1254
1329
|
async function inspectSnapshot(rootDir) {
|
|
1255
1330
|
try {
|
|
@@ -1265,7 +1340,7 @@ async function inspectSnapshot(rootDir) {
|
|
|
1265
1340
|
}
|
|
1266
1341
|
async function getCurrentGitHash(rootDir) {
|
|
1267
1342
|
try {
|
|
1268
|
-
const { stdout } = await
|
|
1343
|
+
const { stdout } = await execGit(["rev-parse", "HEAD"], {
|
|
1269
1344
|
cwd: rootDir
|
|
1270
1345
|
});
|
|
1271
1346
|
return stdout.trim();
|
|
@@ -1273,21 +1348,23 @@ async function getCurrentGitHash(rootDir) {
|
|
|
1273
1348
|
return "unknown";
|
|
1274
1349
|
}
|
|
1275
1350
|
}
|
|
1276
|
-
var
|
|
1351
|
+
var repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
|
|
1277
1352
|
var init_snapshot = __esm({
|
|
1278
1353
|
"src/snapshot/snapshot.ts"() {
|
|
1279
1354
|
"use strict";
|
|
1355
|
+
init_git_read();
|
|
1280
1356
|
init_files();
|
|
1281
1357
|
init_files();
|
|
1282
1358
|
init_storage();
|
|
1359
|
+
init_lock();
|
|
1283
1360
|
init_paths();
|
|
1284
1361
|
init_test_map();
|
|
1285
|
-
exec4 = promisify5(execFile5);
|
|
1286
1362
|
repoPath = z2.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
|
|
1287
1363
|
verificationFields = {
|
|
1288
1364
|
refreshedHash: z2.string().optional(),
|
|
1289
1365
|
verifiedAt: z2.string().optional(),
|
|
1290
1366
|
verifiedHash: z2.string().optional(),
|
|
1367
|
+
verificationToken: z2.string().optional(),
|
|
1291
1368
|
verificationFailed: z2.boolean().optional(),
|
|
1292
1369
|
verificationNote: z2.string().optional()
|
|
1293
1370
|
};
|
|
@@ -1312,9 +1389,7 @@ var init_snapshot = __esm({
|
|
|
1312
1389
|
|
|
1313
1390
|
// src/drift/drift.ts
|
|
1314
1391
|
import fs7 from "fs/promises";
|
|
1315
|
-
import
|
|
1316
|
-
import { execFile as execFile6 } from "child_process";
|
|
1317
|
-
import { promisify as promisify6 } from "util";
|
|
1392
|
+
import path11 from "path";
|
|
1318
1393
|
function parseChanges(output) {
|
|
1319
1394
|
const fields = output.split("\0");
|
|
1320
1395
|
const changes = [];
|
|
@@ -1335,7 +1410,7 @@ function touchedPaths(changes) {
|
|
|
1335
1410
|
async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
|
|
1336
1411
|
if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-") || !toHash || toHash === "unknown" || toHash.startsWith("-")) return null;
|
|
1337
1412
|
try {
|
|
1338
|
-
const { stdout } = await
|
|
1413
|
+
const { stdout } = await execGit(["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
|
|
1339
1414
|
return parseChanges(stdout);
|
|
1340
1415
|
} catch {
|
|
1341
1416
|
return null;
|
|
@@ -1344,8 +1419,8 @@ async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
|
|
|
1344
1419
|
async function getWorkingTree(resolvedRoot) {
|
|
1345
1420
|
try {
|
|
1346
1421
|
const [diff, untracked] = await Promise.all([
|
|
1347
|
-
|
|
1348
|
-
|
|
1422
|
+
execGit(["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
|
|
1423
|
+
execGit(["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
|
|
1349
1424
|
]);
|
|
1350
1425
|
const untrackedFiles = untracked.stdout.split("\0").filter((f) => f && !f.startsWith(".mason/"));
|
|
1351
1426
|
return { available: true, changedFiles: [.../* @__PURE__ */ new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles])].sort(), untrackedFiles };
|
|
@@ -1353,13 +1428,12 @@ async function getWorkingTree(resolvedRoot) {
|
|
|
1353
1428
|
return { available: false, changedFiles: [], untrackedFiles: [] };
|
|
1354
1429
|
}
|
|
1355
1430
|
}
|
|
1356
|
-
var exec5;
|
|
1357
1431
|
var init_drift = __esm({
|
|
1358
1432
|
"src/drift/drift.ts"() {
|
|
1359
1433
|
"use strict";
|
|
1434
|
+
init_git_read();
|
|
1360
1435
|
init_snapshot();
|
|
1361
1436
|
init_paths();
|
|
1362
|
-
exec5 = promisify6(execFile6);
|
|
1363
1437
|
}
|
|
1364
1438
|
});
|
|
1365
1439
|
|
|
@@ -1380,7 +1454,7 @@ var init_types = __esm({
|
|
|
1380
1454
|
});
|
|
1381
1455
|
|
|
1382
1456
|
// src/audit/checks/deleted-reference.ts
|
|
1383
|
-
import
|
|
1457
|
+
import path12 from "path";
|
|
1384
1458
|
async function checkDeletedReferences(ctx) {
|
|
1385
1459
|
const result = emptyResult();
|
|
1386
1460
|
for (const doc of ctx.docs) {
|
|
@@ -1409,7 +1483,7 @@ async function checkDeletedReferences(ctx) {
|
|
|
1409
1483
|
const candidates = await Promise.all(scope.candidates.map(async (file) => ({
|
|
1410
1484
|
file,
|
|
1411
1485
|
tracked: await lastCommitOf(ctx.root, file),
|
|
1412
|
-
parent: await pathExists(ctx.root,
|
|
1486
|
+
parent: await pathExists(ctx.root, path12.posix.dirname(file))
|
|
1413
1487
|
})));
|
|
1414
1488
|
const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
|
|
1415
1489
|
if (!candidate && claim.relativeTo !== "document") continue;
|
|
@@ -1449,7 +1523,7 @@ var init_deleted_reference = __esm({
|
|
|
1449
1523
|
// src/audit/checks/new-module.ts
|
|
1450
1524
|
import fg4 from "fast-glob";
|
|
1451
1525
|
import fs8 from "fs/promises";
|
|
1452
|
-
import
|
|
1526
|
+
import path13 from "path";
|
|
1453
1527
|
function escapeRegExp(text2) {
|
|
1454
1528
|
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1455
1529
|
}
|
|
@@ -1487,23 +1561,36 @@ async function checkNewModules(ctx) {
|
|
|
1487
1561
|
return result;
|
|
1488
1562
|
}
|
|
1489
1563
|
function moduleDocumentation(docs) {
|
|
1490
|
-
return docs.map((doc) => doc.content + "\n" + (
|
|
1564
|
+
return docs.map((doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")).join("\n");
|
|
1565
|
+
}
|
|
1566
|
+
function moduleCandidates(root, combinedDocs) {
|
|
1567
|
+
return inspectionRead(root, "modules:" + combinedDocs, () => collectModuleCandidates(root, combinedDocs));
|
|
1491
1568
|
}
|
|
1492
|
-
async function
|
|
1569
|
+
async function collectModuleCandidates(root, combinedDocs) {
|
|
1493
1570
|
const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
|
|
1494
1571
|
const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
|
|
1495
1572
|
const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
|
|
1496
|
-
const listSubdirs = async (
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1573
|
+
const listSubdirs = async (parents) => {
|
|
1574
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
1575
|
+
for (let offset = 0; offset < parents.length; offset += 16) {
|
|
1576
|
+
await Promise.all(parents.slice(offset, offset + 16).map(async (dir) => {
|
|
1577
|
+
const entries = await fs8.readdir(await auditInputPath(root, dir), { withFileTypes: true });
|
|
1578
|
+
const dirs = entries.filter((entry) => (entry.isDirectory() || entry.isSymbolicLink() && sources.some((file) => file.startsWith(path13.posix.join(dir, entry.name) + "/"))) && !entry.name.startsWith(".") && !DIR_DENYLIST.has(entry.name)).map((entry) => path13.posix.join(dir, entry.name));
|
|
1579
|
+
byParent.set(dir, dirs);
|
|
1580
|
+
}));
|
|
1581
|
+
}
|
|
1582
|
+
const ignored = await gitIgnoredPaths(root, [...byParent.values()].flat());
|
|
1583
|
+
const result = /* @__PURE__ */ new Map();
|
|
1584
|
+
for (const dir of parents) {
|
|
1585
|
+
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 === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
|
|
1587
|
+
ignore,
|
|
1588
|
+
onlyDirectories: true,
|
|
1589
|
+
label: "Module directory discovery",
|
|
1590
|
+
select: (file) => visible.has(file)
|
|
1591
|
+
})).map((file) => path13.posix.basename(file)));
|
|
1592
|
+
}
|
|
1593
|
+
return result;
|
|
1507
1594
|
};
|
|
1508
1595
|
const countSourceFiles = async (dir) => {
|
|
1509
1596
|
const files = sources.filter((file) => file.startsWith(dir + "/"));
|
|
@@ -1519,14 +1606,16 @@ async function moduleCandidates(root, combinedDocs) {
|
|
|
1519
1606
|
return count4;
|
|
1520
1607
|
};
|
|
1521
1608
|
const candidates = [];
|
|
1522
|
-
|
|
1609
|
+
const topDirs = (await listSubdirs(["."])).get(".");
|
|
1610
|
+
const nested = await listSubdirs(topDirs.filter((dir) => isMentioned(combinedDocs, dir)));
|
|
1611
|
+
for (const topDir of topDirs) {
|
|
1523
1612
|
const topMentioned = isMentioned(combinedDocs, topDir);
|
|
1524
1613
|
if (!topMentioned) {
|
|
1525
1614
|
const count4 = await countSourceFiles(topDir);
|
|
1526
1615
|
if (count4 >= 1) candidates.push({ dir: topDir, sourceFileCount: count4 });
|
|
1527
1616
|
continue;
|
|
1528
1617
|
}
|
|
1529
|
-
const subdirs =
|
|
1618
|
+
const subdirs = nested.get(topDir);
|
|
1530
1619
|
const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
|
|
1531
1620
|
if (mentioned.length < ENUMERATION_THRESHOLD) continue;
|
|
1532
1621
|
for (const sub of subdirs) {
|
|
@@ -1543,6 +1632,7 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
|
|
|
1543
1632
|
var init_new_module = __esm({
|
|
1544
1633
|
"src/audit/checks/new-module.ts"() {
|
|
1545
1634
|
"use strict";
|
|
1635
|
+
init_inspection();
|
|
1546
1636
|
init_files();
|
|
1547
1637
|
init_inputs();
|
|
1548
1638
|
init_git();
|
|
@@ -1572,7 +1662,7 @@ var init_new_module = __esm({
|
|
|
1572
1662
|
});
|
|
1573
1663
|
|
|
1574
1664
|
// src/audit/checks/stale-count.ts
|
|
1575
|
-
import
|
|
1665
|
+
import path14 from "path";
|
|
1576
1666
|
async function countGradleModules(root) {
|
|
1577
1667
|
for (const name of ["settings.gradle.kts", "settings.gradle"]) {
|
|
1578
1668
|
const content2 = await readAuditInput(root, name);
|
|
@@ -1606,7 +1696,7 @@ async function countNpmWorkspaces(root) {
|
|
|
1606
1696
|
return {
|
|
1607
1697
|
actual: matched.length,
|
|
1608
1698
|
countedFrom: "package.json workspaces",
|
|
1609
|
-
members: matched.map((m) =>
|
|
1699
|
+
members: matched.map((m) => path14.dirname(m)).sort()
|
|
1610
1700
|
};
|
|
1611
1701
|
}
|
|
1612
1702
|
}
|
|
@@ -1637,7 +1727,7 @@ async function countNpmWorkspaces(root) {
|
|
|
1637
1727
|
return {
|
|
1638
1728
|
actual: matched.length,
|
|
1639
1729
|
countedFrom: "pnpm-workspace.yaml",
|
|
1640
|
-
members: matched.map((m) =>
|
|
1730
|
+
members: matched.map((m) => path14.dirname(m)).sort()
|
|
1641
1731
|
};
|
|
1642
1732
|
}
|
|
1643
1733
|
}
|
|
@@ -1659,8 +1749,8 @@ async function countCargoCrates(root) {
|
|
|
1659
1749
|
ignore: ["**/target/**"],
|
|
1660
1750
|
label: "Cargo workspace discovery"
|
|
1661
1751
|
});
|
|
1662
|
-
for (const m of matched) members.add(
|
|
1663
|
-
} else if (await readAuditInput(root,
|
|
1752
|
+
for (const m of matched) members.add(path14.dirname(m));
|
|
1753
|
+
} else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
|
|
1664
1754
|
members.add(entry);
|
|
1665
1755
|
}
|
|
1666
1756
|
}
|
|
@@ -1680,7 +1770,7 @@ async function resolveCountSource(root, claim) {
|
|
|
1680
1770
|
}
|
|
1681
1771
|
async function resolveDocCountSource(root, doc, claim) {
|
|
1682
1772
|
const scope = documentScope(doc);
|
|
1683
|
-
const source = await resolveCountSource(
|
|
1773
|
+
const source = await resolveCountSource(path14.join(root, scope), claim);
|
|
1684
1774
|
if (!source || scope === ".") return source;
|
|
1685
1775
|
return {
|
|
1686
1776
|
...source,
|
|
@@ -1732,7 +1822,7 @@ var init_stale_count = __esm({
|
|
|
1732
1822
|
});
|
|
1733
1823
|
|
|
1734
1824
|
// src/audit/checks/dead-command.ts
|
|
1735
|
-
import
|
|
1825
|
+
import path15 from "path";
|
|
1736
1826
|
function scriptsOf(raw) {
|
|
1737
1827
|
try {
|
|
1738
1828
|
const pkg = JSON.parse(raw);
|
|
@@ -1768,7 +1858,7 @@ async function commandInputs(root, docs) {
|
|
|
1768
1858
|
}
|
|
1769
1859
|
let cursor = directory;
|
|
1770
1860
|
while (true) {
|
|
1771
|
-
const manifest =
|
|
1861
|
+
const manifest = path15.posix.join(cursor, "package.json");
|
|
1772
1862
|
scope.candidates.push(manifest);
|
|
1773
1863
|
const raw = await read(manifest);
|
|
1774
1864
|
if (raw !== null) {
|
|
@@ -1779,7 +1869,7 @@ async function commandInputs(root, docs) {
|
|
|
1779
1869
|
break;
|
|
1780
1870
|
}
|
|
1781
1871
|
if (claim.directory !== void 0 || cursor === ".") break;
|
|
1782
|
-
cursor =
|
|
1872
|
+
cursor = path15.posix.dirname(cursor);
|
|
1783
1873
|
}
|
|
1784
1874
|
if (!resolution.manifest && !resolution.problem) {
|
|
1785
1875
|
resolution.problem = `No package.json for the command's scope (${directory}).`;
|
|
@@ -1842,10 +1932,8 @@ var init_dead_command = __esm({
|
|
|
1842
1932
|
});
|
|
1843
1933
|
|
|
1844
1934
|
// src/audit/release-metadata.ts
|
|
1845
|
-
import { execFile as execFile7 } from "child_process";
|
|
1846
|
-
import { promisify as promisify7 } from "util";
|
|
1847
1935
|
async function git(root, args2) {
|
|
1848
|
-
return (await
|
|
1936
|
+
return (await execGit(args2, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
|
|
1849
1937
|
}
|
|
1850
1938
|
function withoutAndroidReleaseValues(text2) {
|
|
1851
1939
|
if (/\/\*|"""|'''/.test(text2) || !/id\s*\(?\s*["']com\.android\.(application|library)["']/.test(text2)) return null;
|
|
@@ -1891,11 +1979,10 @@ async function releaseMetadataOnly(root, commit) {
|
|
|
1891
1979
|
return false;
|
|
1892
1980
|
}
|
|
1893
1981
|
}
|
|
1894
|
-
var exec6;
|
|
1895
1982
|
var init_release_metadata = __esm({
|
|
1896
1983
|
"src/audit/release-metadata.ts"() {
|
|
1897
1984
|
"use strict";
|
|
1898
|
-
|
|
1985
|
+
init_git_read();
|
|
1899
1986
|
}
|
|
1900
1987
|
});
|
|
1901
1988
|
|
|
@@ -1991,7 +2078,7 @@ var init_deps_changed = __esm({
|
|
|
1991
2078
|
});
|
|
1992
2079
|
|
|
1993
2080
|
// src/context/lexical.ts
|
|
1994
|
-
import
|
|
2081
|
+
import path16 from "path";
|
|
1995
2082
|
var init_lexical = __esm({
|
|
1996
2083
|
"src/context/lexical.ts"() {
|
|
1997
2084
|
"use strict";
|
|
@@ -2175,7 +2262,7 @@ var init_provenance = __esm({
|
|
|
2175
2262
|
|
|
2176
2263
|
// src/decisions/decisions.ts
|
|
2177
2264
|
import fs9 from "fs/promises";
|
|
2178
|
-
import
|
|
2265
|
+
import path17 from "path";
|
|
2179
2266
|
import { createHash } from "crypto";
|
|
2180
2267
|
import { z as z4 } from "zod";
|
|
2181
2268
|
async function loadDecisionStore(rootDir) {
|
|
@@ -2226,9 +2313,9 @@ var init_decisions = __esm({
|
|
|
2226
2313
|
});
|
|
2227
2314
|
|
|
2228
2315
|
// src/decisions/drift.ts
|
|
2229
|
-
import
|
|
2316
|
+
import path18 from "path";
|
|
2230
2317
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
2231
|
-
const resolvedRoot =
|
|
2318
|
+
const resolvedRoot = path18.resolve(rootDir);
|
|
2232
2319
|
const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
|
|
2233
2320
|
const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
|
|
2234
2321
|
const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
|
|
@@ -2452,8 +2539,6 @@ var init_findings = __esm({
|
|
|
2452
2539
|
|
|
2453
2540
|
// src/audit/advisory-review.ts
|
|
2454
2541
|
import fs10 from "fs/promises";
|
|
2455
|
-
import { execFile as execFile8 } from "child_process";
|
|
2456
|
-
import { promisify as promisify8 } from "util";
|
|
2457
2542
|
import { z as z6 } from "zod";
|
|
2458
2543
|
async function readRecord(root, id) {
|
|
2459
2544
|
const raw = await readStoreJson(root, recordPath(id));
|
|
@@ -2580,10 +2665,12 @@ async function advisoryReviewInventory(root) {
|
|
|
2580
2665
|
if (names.length > 5e3) throw new Error("Advisory review inventory exceeds 5,000 records.");
|
|
2581
2666
|
return Promise.all(names.filter((name) => name.endsWith(".json")).sort().map(async (name) => [name, await readAuditInput(root, ".mason/reviews/advisories/" + name)]));
|
|
2582
2667
|
}
|
|
2583
|
-
var
|
|
2668
|
+
var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
|
|
2584
2669
|
var init_advisory_review = __esm({
|
|
2585
2670
|
"src/audit/advisory-review.ts"() {
|
|
2586
2671
|
"use strict";
|
|
2672
|
+
init_inspection();
|
|
2673
|
+
init_git_read();
|
|
2587
2674
|
init_findings();
|
|
2588
2675
|
init_deps_changed();
|
|
2589
2676
|
init_inputs();
|
|
@@ -2593,8 +2680,7 @@ var init_advisory_review = __esm({
|
|
|
2593
2680
|
init_store();
|
|
2594
2681
|
init_decisions();
|
|
2595
2682
|
init_drift2();
|
|
2596
|
-
|
|
2597
|
-
git2 = async (root, ...args2) => (await execute2("git", args2, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
2683
|
+
git2 = async (root, ...args2) => (await (["log", "ls-tree", "ls-files", "merge-base"].includes(args2[0]) ? inspectionGit : execGit)(args2, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
2598
2684
|
hashSchema = z6.string().regex(/^[a-f0-9]{64}$/);
|
|
2599
2685
|
commitSchema2 = z6.string().regex(/^[a-f0-9]{40,64}$/);
|
|
2600
2686
|
evidenceSchema2 = z6.object({
|
|
@@ -2638,12 +2724,12 @@ var init_advisory_review = __esm({
|
|
|
2638
2724
|
|
|
2639
2725
|
// src/audit/audit.ts
|
|
2640
2726
|
import fs11 from "fs/promises";
|
|
2641
|
-
import
|
|
2727
|
+
import path19 from "path";
|
|
2642
2728
|
async function computeAudit(rootDir, options = {}) {
|
|
2643
2729
|
return profilePhase("audit.current", () => auditCurrent(rootDir, options));
|
|
2644
2730
|
}
|
|
2645
2731
|
async function auditCurrent(rootDir, options) {
|
|
2646
|
-
const resolvedRoot =
|
|
2732
|
+
const resolvedRoot = path19.resolve(rootDir);
|
|
2647
2733
|
const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
|
|
2648
2734
|
if (docs.length === 0) return null;
|
|
2649
2735
|
const report = {
|
|
@@ -2677,7 +2763,7 @@ async function auditCurrent(rootDir, options) {
|
|
|
2677
2763
|
}
|
|
2678
2764
|
let decisionsPresent = false;
|
|
2679
2765
|
try {
|
|
2680
|
-
await fs11.access(
|
|
2766
|
+
await fs11.access(path19.join(resolvedRoot, ".mason", "decisions"));
|
|
2681
2767
|
decisionsPresent = true;
|
|
2682
2768
|
} catch {
|
|
2683
2769
|
}
|
|
@@ -2719,7 +2805,7 @@ var init_audit = __esm({
|
|
|
2719
2805
|
|
|
2720
2806
|
// src/audit/repair.ts
|
|
2721
2807
|
import fs12 from "fs/promises";
|
|
2722
|
-
import
|
|
2808
|
+
import path20 from "path";
|
|
2723
2809
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2724
2810
|
import { z as z7 } from "zod";
|
|
2725
2811
|
function recheckable(finding) {
|
|
@@ -2754,8 +2840,8 @@ async function readStableAudit(root, checks, options) {
|
|
|
2754
2840
|
}
|
|
2755
2841
|
return report;
|
|
2756
2842
|
}
|
|
2757
|
-
async function withRepairSession(root, options, run, cachedAudit) {
|
|
2758
|
-
const
|
|
2843
|
+
async function withRepairSession(root, options, run, validate, cachedAudit) {
|
|
2844
|
+
const head = await getCurrentGitHash(root);
|
|
2759
2845
|
const audits = /* @__PURE__ */ new Map();
|
|
2760
2846
|
const documents = /* @__PURE__ */ new Map();
|
|
2761
2847
|
const history = /* @__PURE__ */ new Map();
|
|
@@ -2766,9 +2852,9 @@ async function withRepairSession(root, options, run, cachedAudit) {
|
|
|
2766
2852
|
};
|
|
2767
2853
|
const session = {
|
|
2768
2854
|
audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
|
|
2769
|
-
if (cachedAudit && cachedAudit.headHash ===
|
|
2855
|
+
if (cachedAudit && cachedAudit.headHash === head && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
|
|
2770
2856
|
const current = structuredClone(cachedAudit);
|
|
2771
|
-
const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f,
|
|
2857
|
+
const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, head)))).filter((r) => r !== null);
|
|
2772
2858
|
delete current.advisoryReviews;
|
|
2773
2859
|
if (reviews.length) current.advisoryReviews = reviews;
|
|
2774
2860
|
return current;
|
|
@@ -2776,16 +2862,15 @@ async function withRepairSession(root, options, run, cachedAudit) {
|
|
|
2776
2862
|
return computeAudit(root, { ...options, checks });
|
|
2777
2863
|
}),
|
|
2778
2864
|
document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
|
|
2779
|
-
history: (
|
|
2780
|
-
assessment: (dir, finding,
|
|
2865
|
+
history: (head2) => memo(history, head2, () => getChangesWithStatus(root, head2)),
|
|
2866
|
+
assessment: (dir, finding, head2) => memo(assessments, digest([dir, finding, head2]), () => assessAdvisory(dir, finding, head2))
|
|
2781
2867
|
};
|
|
2782
2868
|
const result = await run({
|
|
2783
2869
|
prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
|
|
2784
2870
|
verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
|
|
2785
2871
|
currentAudit: () => session.audit(ALL_CHECKS)
|
|
2786
2872
|
});
|
|
2787
|
-
|
|
2788
|
-
if (digest(before) !== digest(after)) throw new Error("HEAD, context files or advisory review records changed during verification; retry against stable evidence.");
|
|
2873
|
+
await validate();
|
|
2789
2874
|
return result;
|
|
2790
2875
|
}
|
|
2791
2876
|
async function prepareOriginal(rootDir, checks, options, session) {
|
|
@@ -2807,8 +2892,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
|
|
|
2807
2892
|
}
|
|
2808
2893
|
async function loadRepairBaseline(rootDir, baselinePath) {
|
|
2809
2894
|
const root = await fs12.realpath(rootDir);
|
|
2810
|
-
const declaredRoot =
|
|
2811
|
-
const relative =
|
|
2895
|
+
const declaredRoot = path20.resolve(rootDir);
|
|
2896
|
+
const relative = path20.isAbsolute(baselinePath) ? path20.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
|
|
2812
2897
|
const stored = baselineSchema.parse(await readStoreJson(root, relative));
|
|
2813
2898
|
const { digest: savedDigest, ...payload } = stored;
|
|
2814
2899
|
if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
|
|
@@ -2967,14 +3052,12 @@ var init_repair = __esm({
|
|
|
2967
3052
|
|
|
2968
3053
|
// src/automation/evidence.ts
|
|
2969
3054
|
import fs13 from "fs/promises";
|
|
2970
|
-
import
|
|
3055
|
+
import path21 from "path";
|
|
2971
3056
|
import { createHash as createHash3 } from "crypto";
|
|
2972
|
-
import { execFile as execFile9 } from "child_process";
|
|
2973
|
-
import { promisify as promisify9 } from "util";
|
|
2974
3057
|
import fg5 from "fast-glob";
|
|
2975
3058
|
import { z as z8 } from "zod";
|
|
2976
3059
|
async function git3(root, ...args2) {
|
|
2977
|
-
return profilePhase("automation.git", async () => (await
|
|
3060
|
+
return profilePhase("automation.git", async () => (await execGit(args2, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
|
|
2978
3061
|
}
|
|
2979
3062
|
async function workspace(dir) {
|
|
2980
3063
|
const root = await fs13.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
|
|
@@ -2998,7 +3081,7 @@ async function collectInputs(root) {
|
|
|
2998
3081
|
const head = headText.trim();
|
|
2999
3082
|
let shallow = null;
|
|
3000
3083
|
try {
|
|
3001
|
-
shallow = await fs13.readFile(
|
|
3084
|
+
shallow = await fs13.readFile(path21.resolve(root, shallowPath.trim()), "utf8");
|
|
3002
3085
|
} catch (error) {
|
|
3003
3086
|
if (error.code !== "ENOENT") throw error;
|
|
3004
3087
|
}
|
|
@@ -3022,7 +3105,7 @@ async function collectInputs(root) {
|
|
|
3022
3105
|
if (scope?.candidates.some(gitMetadataPath)) continue;
|
|
3023
3106
|
for (const candidate of scope?.candidates ?? []) {
|
|
3024
3107
|
if (optionalMasonPath(candidate)) continue;
|
|
3025
|
-
const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(
|
|
3108
|
+
const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path21.posix.dirname(candidate))]);
|
|
3026
3109
|
claims.push([candidate, filePresent, parentPresent]);
|
|
3027
3110
|
}
|
|
3028
3111
|
}
|
|
@@ -3034,7 +3117,7 @@ async function collectInputs(root) {
|
|
|
3034
3117
|
if (error.code === "ENOENT") return "absent";
|
|
3035
3118
|
throw error;
|
|
3036
3119
|
});
|
|
3037
|
-
const [modules, counts,
|
|
3120
|
+
const [modules, counts, commands2, decisionFiles] = await Promise.all([
|
|
3038
3121
|
combinedDocs ? moduleCandidates(root, combinedDocs) : [],
|
|
3039
3122
|
Promise.all(countClaims.map(({ doc, claim }) => resolveDocCountSource(root, doc, claim))),
|
|
3040
3123
|
commandInputs(root, parsedDocs),
|
|
@@ -3053,7 +3136,7 @@ async function collectInputs(root) {
|
|
|
3053
3136
|
"deleted-reference": hash([common, claims, docStatus]),
|
|
3054
3137
|
"new-module": hash([common, modules]),
|
|
3055
3138
|
"stale-count": hash([common, counts]),
|
|
3056
|
-
"dead-command": hash([common,
|
|
3139
|
+
"dead-command": hash([common, commands2]),
|
|
3057
3140
|
"deps-changed": hash([common, docStatus]),
|
|
3058
3141
|
"decision-anchor-drift": hash([common, decisionPresence, decisions, status, index2])
|
|
3059
3142
|
};
|
|
@@ -3091,10 +3174,11 @@ function checkCache(raw, inputs) {
|
|
|
3091
3174
|
}
|
|
3092
3175
|
};
|
|
3093
3176
|
}
|
|
3094
|
-
var
|
|
3177
|
+
var engineVersion, hash, content, cacheSchema;
|
|
3095
3178
|
var init_evidence = __esm({
|
|
3096
3179
|
"src/automation/evidence.ts"() {
|
|
3097
3180
|
"use strict";
|
|
3181
|
+
init_git_read();
|
|
3098
3182
|
init_profile();
|
|
3099
3183
|
init_docs();
|
|
3100
3184
|
init_scope();
|
|
@@ -3107,8 +3191,7 @@ var init_evidence = __esm({
|
|
|
3107
3191
|
init_dead_command();
|
|
3108
3192
|
init_storage();
|
|
3109
3193
|
init_advisory_review();
|
|
3110
|
-
|
|
3111
|
-
engineVersion = true ? "0.17.3" : "development";
|
|
3194
|
+
engineVersion = true ? "0.18.0" : "development";
|
|
3112
3195
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3113
3196
|
content = readAuditInput;
|
|
3114
3197
|
cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
|
|
@@ -3376,12 +3459,12 @@ async function matchingAnalysis(ws, state, inputs) {
|
|
|
3376
3459
|
return { analysis: a };
|
|
3377
3460
|
}
|
|
3378
3461
|
async function verifyInputs(ws, inputs) {
|
|
3379
|
-
return profilePhase("automation.revalidate", async () => {
|
|
3462
|
+
return profilePhase("automation.revalidate", () => withRepositoryInspection(ws.root, async () => {
|
|
3380
3463
|
const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
|
|
3381
3464
|
if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
|
|
3382
3465
|
throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
|
|
3383
3466
|
}
|
|
3384
|
-
});
|
|
3467
|
+
}));
|
|
3385
3468
|
}
|
|
3386
3469
|
async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
|
|
3387
3470
|
const baselines = [...state.baselines];
|
|
@@ -3401,7 +3484,10 @@ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDia
|
|
|
3401
3484
|
capture: "unknown",
|
|
3402
3485
|
scope: SCOPE
|
|
3403
3486
|
};
|
|
3404
|
-
if (!baselines.length && Object.values(inputs.docs).every((value) => value === null))
|
|
3487
|
+
if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) {
|
|
3488
|
+
await verifyInputs(ws, inputs);
|
|
3489
|
+
return { baselines, report: empty, cache: null };
|
|
3490
|
+
}
|
|
3405
3491
|
if (ws.branch === "detached" && state.latest) {
|
|
3406
3492
|
const previous = await readStoreJson(ws.root, state.latest);
|
|
3407
3493
|
if (!previous?.head || !/^[a-f0-9]{40,64}$/.test(previous.head)) throw new Error("The previous detached checkout evidence is unavailable.");
|
|
@@ -3439,7 +3525,7 @@ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDia
|
|
|
3439
3525
|
}
|
|
3440
3526
|
currentAudit = await repairs.currentAudit();
|
|
3441
3527
|
return verifications2;
|
|
3442
|
-
}, cachedAudit);
|
|
3528
|
+
}, () => verifyInputs(ws, inputs), cachedAudit);
|
|
3443
3529
|
const merged = /* @__PURE__ */ new Map();
|
|
3444
3530
|
for (const verification of verifications) {
|
|
3445
3531
|
for (const finding of verification.findings) {
|
|
@@ -3474,7 +3560,6 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
3474
3560
|
if (previous && previous.completedAt >= startedAt) return { analysis: previous, shared: true };
|
|
3475
3561
|
const originalDigest = await baselineDigest(ws, before);
|
|
3476
3562
|
const computed = await computeAnalysis(ws, before, inputs, event, previous?.currentAudit, diagnostic);
|
|
3477
|
-
await verifyInputs(ws, inputs);
|
|
3478
3563
|
if (originalDigest !== await baselineDigest(ws, before)) throw new Error("Repair baseline was modified during automation; original evidence must be inspected.");
|
|
3479
3564
|
const id = randomUUID4();
|
|
3480
3565
|
const analysis = analysisSchema.parse({
|
|
@@ -3503,7 +3588,7 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
3503
3588
|
async function automate(dir, event) {
|
|
3504
3589
|
const startedAt = Date.now();
|
|
3505
3590
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
3506
|
-
return recordExecution(ws.root, ws.directory, event.event, async () => {
|
|
3591
|
+
return recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
|
|
3507
3592
|
const inputs = await readInputs(ws.root);
|
|
3508
3593
|
const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
|
|
3509
3594
|
if (result.shared) {
|
|
@@ -3576,7 +3661,7 @@ async function automate(dir, event) {
|
|
|
3576
3661
|
await writeStoreJson(ws.root, ws.directory + "/state.json", state);
|
|
3577
3662
|
return { report, message: notify ? summarize(report) : null, continueOnce };
|
|
3578
3663
|
}));
|
|
3579
|
-
});
|
|
3664
|
+
}));
|
|
3580
3665
|
}
|
|
3581
3666
|
async function automationStatus(dir) {
|
|
3582
3667
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
@@ -3614,6 +3699,7 @@ var init_runtime = __esm({
|
|
|
3614
3699
|
"src/automation/runtime.ts"() {
|
|
3615
3700
|
"use strict";
|
|
3616
3701
|
init_profile();
|
|
3702
|
+
init_inspection();
|
|
3617
3703
|
init_execution();
|
|
3618
3704
|
init_repair();
|
|
3619
3705
|
init_types();
|
|
@@ -3659,9 +3745,9 @@ var init_runtime = __esm({
|
|
|
3659
3745
|
|
|
3660
3746
|
// src/automation/hook-root.ts
|
|
3661
3747
|
import fs15 from "fs/promises";
|
|
3662
|
-
import
|
|
3748
|
+
import path22 from "path";
|
|
3663
3749
|
async function repositoryRoot(dir) {
|
|
3664
|
-
if (!
|
|
3750
|
+
if (!path22.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
|
|
3665
3751
|
try {
|
|
3666
3752
|
return await fs15.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
|
|
3667
3753
|
} catch (error) {
|
|
@@ -3745,8 +3831,8 @@ __export(launcher_exports, {
|
|
|
3745
3831
|
mcpCommand: () => mcpCommand,
|
|
3746
3832
|
prepareLaunch: () => prepareLaunch
|
|
3747
3833
|
});
|
|
3748
|
-
import { execFile as
|
|
3749
|
-
import { promisify as
|
|
3834
|
+
import { execFile as execFile2 } from "child_process";
|
|
3835
|
+
import { promisify as promisify2 } from "util";
|
|
3750
3836
|
import fs16 from "fs/promises";
|
|
3751
3837
|
function globalCommand(args2, platform = process.platform) {
|
|
3752
3838
|
return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args2] } : { command: "mason", args: args2 };
|
|
@@ -3754,7 +3840,7 @@ function globalCommand(args2, platform = process.platform) {
|
|
|
3754
3840
|
async function installedCommand() {
|
|
3755
3841
|
const invocation = globalCommand(["internal-integration-version"]);
|
|
3756
3842
|
try {
|
|
3757
|
-
const { stdout } = await
|
|
3843
|
+
const { stdout } = await promisify2(execFile2)(
|
|
3758
3844
|
invocation.command,
|
|
3759
3845
|
invocation.args,
|
|
3760
3846
|
{ timeout: 5e3, maxBuffer: 8192, windowsHide: true }
|
|
@@ -3790,7 +3876,7 @@ var init_launcher = __esm({
|
|
|
3790
3876
|
"use strict";
|
|
3791
3877
|
init_evidence();
|
|
3792
3878
|
init_model();
|
|
3793
|
-
executingVersion = () => true ? "0.
|
|
3879
|
+
executingVersion = () => true ? "0.18.0" : "development";
|
|
3794
3880
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
3795
3881
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
3796
3882
|
}
|
|
@@ -4006,7 +4092,7 @@ __export(files_exports, {
|
|
|
4006
4092
|
readText: () => readText
|
|
4007
4093
|
});
|
|
4008
4094
|
import fs18 from "fs/promises";
|
|
4009
|
-
import
|
|
4095
|
+
import path23 from "path";
|
|
4010
4096
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
4011
4097
|
async function readText(root, file) {
|
|
4012
4098
|
try {
|
|
@@ -4039,7 +4125,7 @@ async function applyEdit(root, edit) {
|
|
|
4039
4125
|
return true;
|
|
4040
4126
|
}
|
|
4041
4127
|
const file = await storePath(root, edit.path, true);
|
|
4042
|
-
const temporary =
|
|
4128
|
+
const temporary = path23.join(path23.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
4043
4129
|
try {
|
|
4044
4130
|
const mode = await fs18.stat(file).then((s) => s.mode & 511, () => 384);
|
|
4045
4131
|
const handle = await fs18.open(temporary, "wx", mode);
|
|
@@ -4159,7 +4245,7 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
|
|
|
4159
4245
|
- Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
|
|
4160
4246
|
- Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` before finishing. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
|
|
4161
4247
|
- Before saving, compare the lesson with matching \`get_context\` records and any pending proposal. If new evidence changes the same lesson's assumptions, scope, or recommended action, revise it with \`save_decision(id: existingId)\`, including when it is accepted. Preserve supported rationale and sources, replace obsolete instructions, and add the new evidence. Create a separate record for a genuinely distinct lesson; skip unchanged restatements. Revision preserves history and leaves the accepted version operative until authorized review.
|
|
4162
|
-
- Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, and failed
|
|
4248
|
+
- Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, stale or unknown map verification requires a fresh evidence review, and a recorded failed verdict means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
|
|
4163
4249
|
- Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
|
|
4164
4250
|
- 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.
|
|
4165
4251
|
- \`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.
|
|
@@ -4484,14 +4570,14 @@ async function planTeardown(root, selected) {
|
|
|
4484
4570
|
const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
|
|
4485
4571
|
const automationBefore = await read(AUTOMATION_PATH);
|
|
4486
4572
|
const automation = automationBefore === null ? null : recordSchema2.parse(JSON.parse(automationBefore));
|
|
4487
|
-
const
|
|
4573
|
+
const commands2 = (host) => new Set([
|
|
4488
4574
|
automation?.hosts[host]?.command,
|
|
4489
4575
|
...ownership.files[configPath(host)]?.hookCommands ?? [],
|
|
4490
4576
|
...knownManagedHookCommands(host),
|
|
4491
4577
|
hookConfig(host, hookCommand(host)).hooks.SessionStart[0].hooks[0].command,
|
|
4492
4578
|
hookConfig(host).hooks.SessionStart[0].hooks[0].command
|
|
4493
4579
|
].filter((command2) => !!command2));
|
|
4494
|
-
const possibleHook = (command2, host) =>
|
|
4580
|
+
const possibleHook = (command2, host) => commands2(host).has(command2) || /\bmason(?:-auto|-hook)?\b/.test(command2);
|
|
4495
4581
|
const add = (file, before, after, empty) => {
|
|
4496
4582
|
edits.push({ path: file, before, after: ownership.files[file]?.created && (empty || ownership.files[file].initialHash === hash(before)) ? null : after });
|
|
4497
4583
|
};
|
|
@@ -4555,12 +4641,12 @@ async function planTeardown(root, selected) {
|
|
|
4555
4641
|
for (const [event, groups] of Object.entries(config.hooks ?? {})) {
|
|
4556
4642
|
const known = ownership.files[hookFile]?.hooks?.[event];
|
|
4557
4643
|
const handlers = groups.flatMap((group) => group.hooks);
|
|
4558
|
-
if (known && !handlers.some((handler) => handler.type === "command" && handler.command &&
|
|
4644
|
+
if (known && !handlers.some((handler) => handler.type === "command" && handler.command && commands2(host).has(handler.command)) && handlers.some((handler) => !known.includes(hash(handler)))) {
|
|
4559
4645
|
diagnostics.push({ path: hookFile, message: "A recorded Mason hook is missing beside a new or edited handler (" + event + "); inspect ownership manually." });
|
|
4560
4646
|
}
|
|
4561
4647
|
const kept = groups.flatMap((group) => {
|
|
4562
4648
|
const handlers2 = group.hooks.filter((handler) => {
|
|
4563
|
-
if (handler.type === "command" && handler.command &&
|
|
4649
|
+
if (handler.type === "command" && handler.command && commands2(host).has(handler.command)) {
|
|
4564
4650
|
changed = true;
|
|
4565
4651
|
return false;
|
|
4566
4652
|
}
|
|
@@ -4692,13 +4778,13 @@ var init_teardown = __esm({
|
|
|
4692
4778
|
});
|
|
4693
4779
|
|
|
4694
4780
|
// src/review/cochange.ts
|
|
4695
|
-
import { execFile as
|
|
4696
|
-
import { promisify as
|
|
4781
|
+
import { execFile as execFile3 } from "child_process";
|
|
4782
|
+
import { promisify as promisify3 } from "util";
|
|
4697
4783
|
async function buildMatrix(resolvedRoot) {
|
|
4698
4784
|
try {
|
|
4699
|
-
const { stdout: shallow } = await
|
|
4785
|
+
const { stdout: shallow } = await exec("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
|
|
4700
4786
|
if (shallow.trim() === "true") return null;
|
|
4701
|
-
const { stdout } = await
|
|
4787
|
+
const { stdout } = await exec(
|
|
4702
4788
|
"git",
|
|
4703
4789
|
["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
|
|
4704
4790
|
{ cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
|
|
@@ -4754,11 +4840,11 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
|
|
|
4754
4840
|
findings.sort((a, b) => b.rate - a.rate || b.sharedCommits - a.sharedCommits);
|
|
4755
4841
|
return findings;
|
|
4756
4842
|
}
|
|
4757
|
-
var
|
|
4843
|
+
var exec, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
|
|
4758
4844
|
var init_cochange = __esm({
|
|
4759
4845
|
"src/review/cochange.ts"() {
|
|
4760
4846
|
"use strict";
|
|
4761
|
-
|
|
4847
|
+
exec = promisify3(execFile3);
|
|
4762
4848
|
HISTORY_COMMITS = 1500;
|
|
4763
4849
|
MIN_FILE_COMMITS = 5;
|
|
4764
4850
|
MIN_SHARED_COMMITS = 4;
|
|
@@ -5066,10 +5152,10 @@ var init_check = __esm({
|
|
|
5066
5152
|
});
|
|
5067
5153
|
|
|
5068
5154
|
// src/review/evidence.ts
|
|
5069
|
-
import
|
|
5155
|
+
import path24 from "path";
|
|
5070
5156
|
import { z as z22 } from "zod";
|
|
5071
5157
|
function relativeArtifact(root, file) {
|
|
5072
|
-
const relative =
|
|
5158
|
+
const relative = path24.isAbsolute(file) ? path24.relative(root, file) : file;
|
|
5073
5159
|
const normalized = normalizeRepoPath(relative);
|
|
5074
5160
|
if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
|
|
5075
5161
|
return normalized;
|
|
@@ -5166,7 +5252,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
5166
5252
|
const report = await readStoreJson(root, file);
|
|
5167
5253
|
if (report === null) throw new Error(`Report artifact is missing: ${file}`);
|
|
5168
5254
|
const sourceRoot = check.sourceRoot ?? root;
|
|
5169
|
-
if (!
|
|
5255
|
+
if (!path24.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
|
|
5170
5256
|
const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
|
|
5171
5257
|
result.outcome = parsed.outcome;
|
|
5172
5258
|
result.counts = parsed.counts;
|
|
@@ -5288,12 +5374,12 @@ var init_evidence2 = __esm({
|
|
|
5288
5374
|
|
|
5289
5375
|
// src/review/review.ts
|
|
5290
5376
|
import fs19 from "fs/promises";
|
|
5291
|
-
import
|
|
5292
|
-
import { execFile as
|
|
5293
|
-
import { promisify as
|
|
5377
|
+
import path25 from "path";
|
|
5378
|
+
import { execFile as execFile4 } from "child_process";
|
|
5379
|
+
import { promisify as promisify4 } from "util";
|
|
5294
5380
|
async function resolveMergeBase(resolvedRoot, base, head) {
|
|
5295
5381
|
try {
|
|
5296
|
-
const { stdout } = await
|
|
5382
|
+
const { stdout } = await exec2("git", ["merge-base", base, head], {
|
|
5297
5383
|
cwd: resolvedRoot
|
|
5298
5384
|
});
|
|
5299
5385
|
return stdout.trim() || null;
|
|
@@ -5304,7 +5390,7 @@ async function resolveMergeBase(resolvedRoot, base, head) {
|
|
|
5304
5390
|
async function defaultBase(resolvedRoot) {
|
|
5305
5391
|
for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
|
|
5306
5392
|
try {
|
|
5307
|
-
await
|
|
5393
|
+
await exec2("git", ["rev-parse", "--verify", "--quiet", ref], {
|
|
5308
5394
|
cwd: resolvedRoot
|
|
5309
5395
|
});
|
|
5310
5396
|
return ref;
|
|
@@ -5317,7 +5403,7 @@ function anchorsTouched(record, changedFiles) {
|
|
|
5317
5403
|
return matchingPaths(record.files, changedFiles);
|
|
5318
5404
|
}
|
|
5319
5405
|
async function computeReview(rootDir, base, options = {}) {
|
|
5320
|
-
const resolvedRoot =
|
|
5406
|
+
const resolvedRoot = path25.resolve(rootDir);
|
|
5321
5407
|
const head = await getCurrentGitHash(resolvedRoot);
|
|
5322
5408
|
if (head === "unknown") return null;
|
|
5323
5409
|
const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
|
|
@@ -5367,7 +5453,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5367
5453
|
analyzed,
|
|
5368
5454
|
async (relPath) => {
|
|
5369
5455
|
try {
|
|
5370
|
-
await fs19.access(
|
|
5456
|
+
await fs19.access(path25.join(resolvedRoot, relPath));
|
|
5371
5457
|
return true;
|
|
5372
5458
|
} catch {
|
|
5373
5459
|
return false;
|
|
@@ -5400,7 +5486,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5400
5486
|
}
|
|
5401
5487
|
return finalize();
|
|
5402
5488
|
}
|
|
5403
|
-
var
|
|
5489
|
+
var exec2, MAX_ANALYZED_FILES;
|
|
5404
5490
|
var init_review = __esm({
|
|
5405
5491
|
"src/review/review.ts"() {
|
|
5406
5492
|
"use strict";
|
|
@@ -5412,7 +5498,7 @@ var init_review = __esm({
|
|
|
5412
5498
|
init_cochange();
|
|
5413
5499
|
init_evidence2();
|
|
5414
5500
|
init_snapshot();
|
|
5415
|
-
|
|
5501
|
+
exec2 = promisify4(execFile4);
|
|
5416
5502
|
MAX_ANALYZED_FILES = 50;
|
|
5417
5503
|
}
|
|
5418
5504
|
});
|
|
@@ -5747,7 +5833,7 @@ var init_setup = __esm({
|
|
|
5747
5833
|
|
|
5748
5834
|
// src/distribution/bundle.ts
|
|
5749
5835
|
import fs20 from "fs/promises";
|
|
5750
|
-
import
|
|
5836
|
+
import path26 from "path";
|
|
5751
5837
|
import { createHash as createHash4 } from "crypto";
|
|
5752
5838
|
import { z as z23 } from "zod";
|
|
5753
5839
|
async function readBundle(root) {
|
|
@@ -5755,7 +5841,7 @@ async function readBundle(root) {
|
|
|
5755
5841
|
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
5756
5842
|
const windows = manifest.target.startsWith("win32-");
|
|
5757
5843
|
for (const file of Object.keys(manifest.files)) {
|
|
5758
|
-
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") ||
|
|
5844
|
+
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path26.isAbsolute(file)) {
|
|
5759
5845
|
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
5760
5846
|
}
|
|
5761
5847
|
}
|
|
@@ -5795,16 +5881,16 @@ var init_bundle = __esm({
|
|
|
5795
5881
|
// src/distribution/path.ts
|
|
5796
5882
|
import fs21 from "fs/promises";
|
|
5797
5883
|
import os3 from "os";
|
|
5798
|
-
import
|
|
5799
|
-
import { execFile as
|
|
5800
|
-
import { promisify as
|
|
5884
|
+
import path27 from "path";
|
|
5885
|
+
import { execFile as execFile5 } from "child_process";
|
|
5886
|
+
import { promisify as promisify5 } from "util";
|
|
5801
5887
|
import { createHash as createHash5, randomUUID as randomUUID7 } from "crypto";
|
|
5802
5888
|
import { z as z24 } from "zod";
|
|
5803
5889
|
async function configurePath(bin, previous, save, options = {}) {
|
|
5804
5890
|
const env = options.env ?? process.env, platform = options.platform ?? process.platform;
|
|
5805
5891
|
const changes = [...previous];
|
|
5806
5892
|
const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
|
|
5807
|
-
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) =>
|
|
5893
|
+
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path27.posix.isAbsolute(entry) && path27.posix.resolve(entry) === path27.posix.resolve(bin));
|
|
5808
5894
|
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";
|
|
5809
5895
|
const message2 = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
|
|
5810
5896
|
const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
|
|
@@ -5824,15 +5910,15 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5824
5910
|
}
|
|
5825
5911
|
return { status: "configured", changes, message: message2 };
|
|
5826
5912
|
}
|
|
5827
|
-
const shell =
|
|
5913
|
+
const shell = path27.basename(env.SHELL ?? os3.userInfo().shell ?? "");
|
|
5828
5914
|
const userHome = options.userHome ?? os3.homedir();
|
|
5829
5915
|
let profiles2;
|
|
5830
5916
|
if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
|
|
5831
|
-
if (env.MASON_PROFILE) profiles2 = [
|
|
5832
|
-
else if (shell === "zsh") profiles2 = [
|
|
5833
|
-
else if (shell === "sh") profiles2 = [
|
|
5917
|
+
if (env.MASON_PROFILE) profiles2 = [path27.resolve(env.MASON_PROFILE)];
|
|
5918
|
+
else if (shell === "zsh") profiles2 = [path27.join(env.ZDOTDIR || userHome, ".zshrc")];
|
|
5919
|
+
else if (shell === "sh") profiles2 = [path27.join(userHome, ".profile")];
|
|
5834
5920
|
else {
|
|
5835
|
-
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) =>
|
|
5921
|
+
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path27.join(userHome, name));
|
|
5836
5922
|
let login = candidates[2];
|
|
5837
5923
|
for (const file of candidates) if (await fs21.lstat(file).then(() => true, (error) => {
|
|
5838
5924
|
if (error.code === "ENOENT") return false;
|
|
@@ -5841,7 +5927,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5841
5927
|
login = file;
|
|
5842
5928
|
break;
|
|
5843
5929
|
}
|
|
5844
|
-
profiles2 = [
|
|
5930
|
+
profiles2 = [path27.join(userHome, ".bashrc"), login];
|
|
5845
5931
|
}
|
|
5846
5932
|
const marker = `# >>> mason PATH ${createHash5("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
5847
5933
|
for (const profile of profiles2) {
|
|
@@ -5849,7 +5935,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5849
5935
|
if (error.code === "ENOENT") return null;
|
|
5850
5936
|
throw error;
|
|
5851
5937
|
});
|
|
5852
|
-
const file = stat?.isSymbolicLink() ? await fs21.realpath(profile) :
|
|
5938
|
+
const file = stat?.isSymbolicLink() ? await fs21.realpath(profile) : path27.resolve(profile);
|
|
5853
5939
|
const original = await fs21.readFile(file).catch((error) => {
|
|
5854
5940
|
if (error.code === "ENOENT" && !stat) return null;
|
|
5855
5941
|
throw error;
|
|
@@ -5865,7 +5951,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5865
5951
|
changes.push({ kind: "profile", file, block, created: original === null });
|
|
5866
5952
|
await save(changes);
|
|
5867
5953
|
}
|
|
5868
|
-
await fs21.mkdir(
|
|
5954
|
+
await fs21.mkdir(path27.dirname(file), { recursive: true });
|
|
5869
5955
|
const handle = await fs21.open(file, original === null ? "ax+" : "a+", 384);
|
|
5870
5956
|
try {
|
|
5871
5957
|
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
@@ -5953,20 +6039,20 @@ try {
|
|
|
5953
6039
|
@{ value = $next; kind = $kind } | ConvertTo-Json -Compress
|
|
5954
6040
|
} finally { $key.Dispose() }
|
|
5955
6041
|
`;
|
|
5956
|
-
const { stdout } = await
|
|
6042
|
+
const { stdout } = await execute2("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { windowsHide: true, timeout: 15e3, maxBuffer: 1024 * 1024 });
|
|
5957
6043
|
return JSON.parse(stdout.replace(/^\uFEFF/, "").trim());
|
|
5958
6044
|
}
|
|
5959
|
-
var
|
|
6045
|
+
var execute2, pathChangeSchema, quote, normalizeWindowsEntry, hasWindowsEntry;
|
|
5960
6046
|
var init_path = __esm({
|
|
5961
6047
|
"src/distribution/path.ts"() {
|
|
5962
6048
|
"use strict";
|
|
5963
|
-
|
|
6049
|
+
execute2 = promisify5(execFile5);
|
|
5964
6050
|
pathChangeSchema = z24.discriminatedUnion("kind", [
|
|
5965
6051
|
z24.object({ kind: z24.literal("profile"), file: z24.string(), block: z24.string(), created: z24.boolean() }),
|
|
5966
6052
|
z24.object({ kind: z24.literal("windows"), entry: z24.string(), created: z24.boolean() })
|
|
5967
6053
|
]);
|
|
5968
6054
|
quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
5969
|
-
normalizeWindowsEntry = (value) =>
|
|
6055
|
+
normalizeWindowsEntry = (value) => path27.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
|
|
5970
6056
|
hasWindowsEntry = (value, entry, env = process.env) => {
|
|
5971
6057
|
const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
|
|
5972
6058
|
const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
|
|
@@ -5986,7 +6072,7 @@ __export(install_exports2, {
|
|
|
5986
6072
|
});
|
|
5987
6073
|
import fs22 from "fs/promises";
|
|
5988
6074
|
import os4 from "os";
|
|
5989
|
-
import
|
|
6075
|
+
import path28 from "path";
|
|
5990
6076
|
import { fileURLToPath } from "url";
|
|
5991
6077
|
import { spawn } from "child_process";
|
|
5992
6078
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
@@ -6003,16 +6089,16 @@ async function replace(file, content2, mode = 384) {
|
|
|
6003
6089
|
async function installStandalone(source, progress2) {
|
|
6004
6090
|
progress2?.step("Checking installation files and location");
|
|
6005
6091
|
const bundle = await verifyBundle(source);
|
|
6006
|
-
const selectedHome =
|
|
6092
|
+
const selectedHome = path28.resolve(defaultHome());
|
|
6007
6093
|
await fs22.mkdir(selectedHome, { recursive: true });
|
|
6008
6094
|
const home = await fs22.realpath(selectedHome);
|
|
6009
|
-
const selectedBin =
|
|
6095
|
+
const selectedBin = path28.resolve(process.env.MASON_BIN_DIR ?? (process.platform === "win32" ? path28.join(home, "bin") : path28.join(os4.homedir(), ".local/bin")));
|
|
6010
6096
|
return withLock(home, ".install-lock", async () => {
|
|
6011
6097
|
const raw = await readStoreJson(home, "install.json");
|
|
6012
6098
|
const previous = raw === null ? null : recordSchema3.parse(raw);
|
|
6013
6099
|
if (!previous) for (const file of await fs22.readdir(home)) {
|
|
6014
6100
|
if (file === ".install-lock") continue;
|
|
6015
|
-
const directory =
|
|
6101
|
+
const directory = path28.join(home, file);
|
|
6016
6102
|
const stat = await fs22.lstat(directory);
|
|
6017
6103
|
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs22.readdir(directory)).length) {
|
|
6018
6104
|
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
@@ -6030,10 +6116,10 @@ $previousToken = $env:MASON_UNINSTALL_TOKEN
|
|
|
6030
6116
|
$token = [Guid]::NewGuid().ToString()
|
|
6031
6117
|
$env:MASON_UNINSTALL_TOKEN = $token
|
|
6032
6118
|
try {
|
|
6033
|
-
& ${psQuote(
|
|
6119
|
+
& ${psQuote(path28.join(destination, "node.exe"))} ${psQuote(path28.join(destination, "app/dist/mason.js"))} @args
|
|
6034
6120
|
$code = $LASTEXITCODE
|
|
6035
6121
|
if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
|
|
6036
|
-
if ([IO.File]::ReadAllText(${psQuote(
|
|
6122
|
+
if ([IO.File]::ReadAllText(${psQuote(path28.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
|
|
6037
6123
|
Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
|
|
6038
6124
|
Write-Output ${psQuote("Removed standalone installation: " + home)}
|
|
6039
6125
|
}
|
|
@@ -6042,7 +6128,7 @@ try {
|
|
|
6042
6128
|
finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
|
|
6043
6129
|
`
|
|
6044
6130
|
} : { mason: `#!/bin/sh
|
|
6045
|
-
exec ${shQuote(
|
|
6131
|
+
exec ${shQuote(path28.join(destination, "node"))} ${shQuote(path28.join(destination, "app/dist/mason.js"))} "$@"
|
|
6046
6132
|
` };
|
|
6047
6133
|
for (const [name, content2] of Object.entries(launchers)) {
|
|
6048
6134
|
const file = await storePath(bin, name);
|
|
@@ -6098,12 +6184,12 @@ exec ${shQuote(path27.join(destination, "node"))} ${shQuote(path27.join(destinat
|
|
|
6098
6184
|
});
|
|
6099
6185
|
}
|
|
6100
6186
|
async function currentInstallation() {
|
|
6101
|
-
const app =
|
|
6102
|
-
const bundle =
|
|
6187
|
+
const app = path28.dirname(path28.dirname(fileURLToPath(import.meta.url)));
|
|
6188
|
+
const bundle = path28.dirname(app), home = path28.dirname(path28.dirname(bundle));
|
|
6103
6189
|
const raw = await readStoreJson(home, "install.json");
|
|
6104
6190
|
if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
6105
6191
|
const record = recordSchema3.parse(raw);
|
|
6106
|
-
if (record.home !== await fs22.realpath(home) ||
|
|
6192
|
+
if (record.home !== await fs22.realpath(home) || path28.dirname(bundle) !== path28.join(home, "versions")) throw new Error("Invalid standalone installation receipt.");
|
|
6107
6193
|
return { home, bundle, record };
|
|
6108
6194
|
}
|
|
6109
6195
|
async function upgradeStandalone(version, progress2) {
|
|
@@ -6112,7 +6198,7 @@ async function upgradeStandalone(version, progress2) {
|
|
|
6112
6198
|
const { home, bundle, record } = await currentInstallation();
|
|
6113
6199
|
await verifyBundle(bundle);
|
|
6114
6200
|
const windows = process.platform === "win32";
|
|
6115
|
-
const script =
|
|
6201
|
+
const script = path28.join(bundle, windows ? "install.ps1" : "install.sh");
|
|
6116
6202
|
progress2?.stop();
|
|
6117
6203
|
return new Promise((resolve, reject) => {
|
|
6118
6204
|
const child = spawn(
|
|
@@ -6169,7 +6255,7 @@ var init_install2 = __esm({
|
|
|
6169
6255
|
psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
|
|
6170
6256
|
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';
|
|
6171
6257
|
launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
|
|
6172
|
-
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ?
|
|
6258
|
+
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path28.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path28.join(os4.homedir(), ".local/share/mason"));
|
|
6173
6259
|
}
|
|
6174
6260
|
});
|
|
6175
6261
|
|
|
@@ -6366,7 +6452,7 @@ var inputArgs = process.argv.slice(2);
|
|
|
6366
6452
|
var managedLaunch = inputArgs[0] === "--setup-host";
|
|
6367
6453
|
var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
|
|
6368
6454
|
var [command, ...args] = inputArgs;
|
|
6369
|
-
var usage = `Mason ${"0.
|
|
6455
|
+
var usage = `Mason ${"0.18.0"}
|
|
6370
6456
|
|
|
6371
6457
|
Usage: mason <command> [options]
|
|
6372
6458
|
|
|
@@ -6392,9 +6478,9 @@ try {
|
|
|
6392
6478
|
await prepareLaunch2(setupHost, { allowInactive: command === "mcp" });
|
|
6393
6479
|
}
|
|
6394
6480
|
}
|
|
6395
|
-
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.
|
|
6481
|
+
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.18.0" }));
|
|
6396
6482
|
else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
|
|
6397
|
-
else if (["--version", "-v"].includes(command)) console.log("0.
|
|
6483
|
+
else if (["--version", "-v"].includes(command)) console.log("0.18.0");
|
|
6398
6484
|
else if (command === "internal-install") {
|
|
6399
6485
|
if (args.length) throw new Error("internal-install takes no arguments.");
|
|
6400
6486
|
const { installStandalone: installStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|