mason-context 0.17.2 → 0.17.4
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 +20 -0
- package/dist/mason-audit.js +309 -179
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-auto.js +475 -289
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-drift.js +45 -18
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +41 -13
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +632 -456
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +53 -25
- package/dist/mason-review.js.map +1 -1
- package/dist/mason.js +510 -324
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/dist/mason-auto.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(args, options) {
|
|
76
|
+
const command = args.find((arg) => !arg.startsWith("-")) ?? "other";
|
|
77
|
+
return profilePhase("git." + (commands.has(command) ? command : "other"), async () => {
|
|
78
|
+
const { input: input2, ...processOptions } = options;
|
|
79
|
+
const operation = execute("git", args, processOptions);
|
|
80
|
+
if (input2 !== void 0) {
|
|
81
|
+
operation.child.stdin.on("error", () => {
|
|
82
|
+
});
|
|
83
|
+
operation.child.stdin.end(input2);
|
|
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 = [
|
|
@@ -570,14 +627,14 @@ function extractClaims(content2) {
|
|
|
570
627
|
const ignored = computeIgnoredLines(lines);
|
|
571
628
|
const paths = /* @__PURE__ */ new Map();
|
|
572
629
|
const counts = [];
|
|
573
|
-
const
|
|
630
|
+
const commands2 = /* @__PURE__ */ new Map();
|
|
574
631
|
const addPath = (claim) => {
|
|
575
632
|
const key = [claim.relativeTo ?? "scope", claim.path].join(":");
|
|
576
633
|
if (!paths.has(key)) paths.set(key, claim);
|
|
577
634
|
};
|
|
578
635
|
const addCommand = (claim) => {
|
|
579
636
|
const key = JSON.stringify([claim.scriptName, claim.directory, claim.scopeUnknown]);
|
|
580
|
-
if (!
|
|
637
|
+
if (!commands2.has(key)) commands2.set(key, claim);
|
|
581
638
|
};
|
|
582
639
|
const parsed = markdown.parse(lines.map((line, i) => ignored[i] ? "" : line).join("\n"), {});
|
|
583
640
|
for (const token of parsed) {
|
|
@@ -679,7 +736,7 @@ function extractClaims(content2) {
|
|
|
679
736
|
return {
|
|
680
737
|
paths: [...paths.values()],
|
|
681
738
|
counts,
|
|
682
|
-
commands: [...
|
|
739
|
+
commands: [...commands2.values()]
|
|
683
740
|
};
|
|
684
741
|
}
|
|
685
742
|
var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
|
|
@@ -729,9 +786,53 @@ var init_claims = __esm({
|
|
|
729
786
|
}
|
|
730
787
|
});
|
|
731
788
|
|
|
789
|
+
// src/audit/inspection.ts
|
|
790
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
791
|
+
import path5 from "path";
|
|
792
|
+
function withRepositoryInspection(root, run) {
|
|
793
|
+
return inspections.run({ root: path5.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
|
|
794
|
+
}
|
|
795
|
+
function inspectionRead(root, key, read) {
|
|
796
|
+
const inspection = inspections.getStore();
|
|
797
|
+
if (!inspection || inspection.root !== path5.resolve(root)) return read();
|
|
798
|
+
if (!inspection.reads.has(key)) {
|
|
799
|
+
const pending = read();
|
|
800
|
+
inspection.reads.set(key, pending);
|
|
801
|
+
void pending.catch(() => {
|
|
802
|
+
if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
return inspection.reads.get(key);
|
|
806
|
+
}
|
|
807
|
+
function inspectionGit(args, options) {
|
|
808
|
+
return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
|
|
809
|
+
}
|
|
810
|
+
async function auditGitPaths(root, kind) {
|
|
811
|
+
const shared = inspections.getStore()?.root === path5.resolve(root);
|
|
812
|
+
const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
|
|
813
|
+
const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
|
|
814
|
+
const { stdout } = await inspectionGit(
|
|
815
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
|
|
816
|
+
{ cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
|
|
817
|
+
);
|
|
818
|
+
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("."))));
|
|
819
|
+
if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
|
|
820
|
+
return paths.sort();
|
|
821
|
+
}
|
|
822
|
+
var inspections, sourceSpecs, docSpecs, sourceSuffixes;
|
|
823
|
+
var init_inspection = __esm({
|
|
824
|
+
"src/audit/inspection.ts"() {
|
|
825
|
+
"use strict";
|
|
826
|
+
init_git_read();
|
|
827
|
+
init_files();
|
|
828
|
+
inspections = new AsyncLocalStorage2();
|
|
829
|
+
sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
|
|
830
|
+
docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
|
|
831
|
+
sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
|
|
732
835
|
// src/audit/git.ts
|
|
733
|
-
import { execFile as execFile2 } from "child_process";
|
|
734
|
-
import { promisify as promisify2 } from "util";
|
|
735
836
|
function parseCommitLine(line) {
|
|
736
837
|
const parts = line.split(" ");
|
|
737
838
|
if (parts.length < 3 || !parts[0]) return null;
|
|
@@ -739,8 +840,7 @@ function parseCommitLine(line) {
|
|
|
739
840
|
}
|
|
740
841
|
async function lastCommitOf(resolvedRoot, relPath) {
|
|
741
842
|
try {
|
|
742
|
-
const { stdout } = await
|
|
743
|
-
"git",
|
|
843
|
+
const { stdout } = await inspectionGit(
|
|
744
844
|
["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
745
845
|
{ cwd: resolvedRoot }
|
|
746
846
|
);
|
|
@@ -752,8 +852,7 @@ async function lastCommitOf(resolvedRoot, relPath) {
|
|
|
752
852
|
}
|
|
753
853
|
async function deletingCommitOf(resolvedRoot, relPath) {
|
|
754
854
|
try {
|
|
755
|
-
const { stdout } = await
|
|
756
|
-
"git",
|
|
855
|
+
const { stdout } = await inspectionGit(
|
|
757
856
|
[
|
|
758
857
|
"log",
|
|
759
858
|
"-1",
|
|
@@ -772,8 +871,7 @@ async function deletingCommitOf(resolvedRoot, relPath) {
|
|
|
772
871
|
}
|
|
773
872
|
async function firstCommitOf(resolvedRoot, relPath) {
|
|
774
873
|
try {
|
|
775
|
-
const { stdout } = await
|
|
776
|
-
"git",
|
|
874
|
+
const { stdout } = await inspectionGit(
|
|
777
875
|
["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
778
876
|
{ cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
|
|
779
877
|
);
|
|
@@ -786,8 +884,7 @@ async function firstCommitOf(resolvedRoot, relPath) {
|
|
|
786
884
|
async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
|
|
787
885
|
if (!fromHash || fromHash === "unknown") return null;
|
|
788
886
|
try {
|
|
789
|
-
const { stdout } = await
|
|
790
|
-
"git",
|
|
887
|
+
const { stdout } = await inspectionGit(
|
|
791
888
|
[
|
|
792
889
|
"log",
|
|
793
890
|
`${fromHash}..HEAD`,
|
|
@@ -811,11 +908,11 @@ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
|
|
|
811
908
|
return null;
|
|
812
909
|
}
|
|
813
910
|
}
|
|
814
|
-
var
|
|
911
|
+
var COMMIT_FORMAT;
|
|
815
912
|
var init_git = __esm({
|
|
816
913
|
"src/audit/git.ts"() {
|
|
817
914
|
"use strict";
|
|
818
|
-
|
|
915
|
+
init_inspection();
|
|
819
916
|
COMMIT_FORMAT = "%H%x09%cI%x09%s";
|
|
820
917
|
}
|
|
821
918
|
});
|
|
@@ -823,9 +920,7 @@ var init_git = __esm({
|
|
|
823
920
|
// src/audit/inputs.ts
|
|
824
921
|
import fs4 from "fs/promises";
|
|
825
922
|
import * as nativeFs from "fs";
|
|
826
|
-
import
|
|
827
|
-
import { execFile as execFile3 } from "child_process";
|
|
828
|
-
import { promisify as promisify3 } from "util";
|
|
923
|
+
import path6 from "path";
|
|
829
924
|
import fg2 from "fast-glob";
|
|
830
925
|
async function auditInputPath(root, file) {
|
|
831
926
|
if (file === ".") return fs4.realpath(root);
|
|
@@ -852,7 +947,7 @@ async function auditGlob(root, patterns, options) {
|
|
|
852
947
|
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
853
948
|
const directories = /* @__PURE__ */ new Set();
|
|
854
949
|
const readdir2 = ((directory, opts, callback) => {
|
|
855
|
-
const relative =
|
|
950
|
+
const relative = path6.relative(root, directory).split(path6.sep).join("/") || ".";
|
|
856
951
|
directories.add(relative);
|
|
857
952
|
if (directories.size > MAX_INPUTS) {
|
|
858
953
|
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
@@ -863,7 +958,7 @@ async function auditGlob(root, patterns, options) {
|
|
|
863
958
|
}, callback);
|
|
864
959
|
});
|
|
865
960
|
const lstat2 = ((file, callback) => {
|
|
866
|
-
const relative =
|
|
961
|
+
const relative = path6.relative(root, file).split(path6.sep).join("/") || ".";
|
|
867
962
|
auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
|
|
868
963
|
});
|
|
869
964
|
const stream = fg2.stream(patterns, {
|
|
@@ -900,89 +995,71 @@ async function auditGlob(root, patterns, options) {
|
|
|
900
995
|
return matches.sort();
|
|
901
996
|
}
|
|
902
997
|
async function gitSourcePaths(root) {
|
|
903
|
-
|
|
904
|
-
"git",
|
|
905
|
-
[
|
|
906
|
-
"ls-files",
|
|
907
|
-
"-z",
|
|
908
|
-
"--cached",
|
|
909
|
-
"--others",
|
|
910
|
-
"--exclude-standard",
|
|
911
|
-
"--",
|
|
912
|
-
...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
|
|
913
|
-
],
|
|
914
|
-
{ cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
|
|
915
|
-
);
|
|
916
|
-
return [...new Set(stdout.split("\0").filter(Boolean))].sort();
|
|
998
|
+
return auditGitPaths(root, "sources");
|
|
917
999
|
}
|
|
918
1000
|
async function gitIgnoredPaths(root, files) {
|
|
919
1001
|
if (!files.length) return /* @__PURE__ */ new Set();
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1002
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
1003
|
+
for (let offset = 0; offset < files.length; offset += 1e3) {
|
|
1004
|
+
try {
|
|
1005
|
+
const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
|
|
1006
|
+
cwd: root,
|
|
1007
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
1008
|
+
timeout: 1e4,
|
|
1009
|
+
input: files.slice(offset, offset + 1e3).join("\0") + "\0"
|
|
1010
|
+
});
|
|
1011
|
+
for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
if (error.code !== 1) throw error;
|
|
1014
|
+
}
|
|
933
1015
|
}
|
|
1016
|
+
return ignored;
|
|
934
1017
|
}
|
|
935
|
-
var
|
|
1018
|
+
var MAX_INPUTS;
|
|
936
1019
|
var init_inputs = __esm({
|
|
937
1020
|
"src/audit/inputs.ts"() {
|
|
938
1021
|
"use strict";
|
|
939
1022
|
init_files();
|
|
940
1023
|
init_storage();
|
|
941
|
-
|
|
1024
|
+
init_git_read();
|
|
1025
|
+
init_inspection();
|
|
942
1026
|
MAX_INPUTS = 1e5;
|
|
943
1027
|
}
|
|
944
1028
|
});
|
|
945
1029
|
|
|
946
1030
|
// src/audit/docs.ts
|
|
947
1031
|
import fs5 from "fs/promises";
|
|
948
|
-
import
|
|
949
|
-
import { execFile as execFile4 } from "child_process";
|
|
950
|
-
import { promisify as promisify4 } from "util";
|
|
1032
|
+
import path7 from "path";
|
|
951
1033
|
import fg3 from "fast-glob";
|
|
952
|
-
async function
|
|
1034
|
+
async function dirtyDocs(resolvedRoot, files) {
|
|
1035
|
+
if (!files.length) return /* @__PURE__ */ new Set();
|
|
953
1036
|
try {
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1037
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
1038
|
+
for (let offset = 0; offset < files.length; offset += 256) {
|
|
1039
|
+
const { stdout } = await execGit(
|
|
1040
|
+
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
|
|
1041
|
+
{ cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
1042
|
+
);
|
|
1043
|
+
const entries = stdout.split("\0");
|
|
1044
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1045
|
+
if (!entries[i]) continue;
|
|
1046
|
+
const status = entries[i].slice(0, 2);
|
|
1047
|
+
dirty.add(entries[i].slice(3));
|
|
1048
|
+
if (/[RC]/.test(status) && entries[i + 1]) dirty.add(entries[++i]);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return dirty;
|
|
960
1052
|
} catch {
|
|
961
|
-
return
|
|
1053
|
+
return new Set(files);
|
|
962
1054
|
}
|
|
963
1055
|
}
|
|
964
1056
|
async function discoverDocPaths(root) {
|
|
965
1057
|
let candidates = [];
|
|
966
1058
|
try {
|
|
967
|
-
|
|
968
|
-
"git",
|
|
969
|
-
[
|
|
970
|
-
"ls-files",
|
|
971
|
-
"-z",
|
|
972
|
-
"--cached",
|
|
973
|
-
"--others",
|
|
974
|
-
"--exclude-standard",
|
|
975
|
-
"--",
|
|
976
|
-
":(icase,glob)**/readme.md",
|
|
977
|
-
":(icase,glob)**/agents.md",
|
|
978
|
-
":(icase,glob)**/claude.md"
|
|
979
|
-
],
|
|
980
|
-
{ cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
981
|
-
);
|
|
982
|
-
candidates = stdout.split("\0").filter(Boolean);
|
|
1059
|
+
candidates = await auditGitPaths(root, "documents");
|
|
983
1060
|
} catch (error) {
|
|
984
1061
|
try {
|
|
985
|
-
await
|
|
1062
|
+
await execGit(["rev-parse", "--git-dir"], { cwd: root });
|
|
986
1063
|
} catch {
|
|
987
1064
|
return localDocPaths(root);
|
|
988
1065
|
}
|
|
@@ -1029,43 +1106,54 @@ async function localDocPaths(root) {
|
|
|
1029
1106
|
throw error;
|
|
1030
1107
|
}
|
|
1031
1108
|
for (const entry of entries) if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
|
|
1032
|
-
paths.push(
|
|
1109
|
+
paths.push(path7.posix.join(dir, entry.name));
|
|
1033
1110
|
}
|
|
1034
1111
|
}
|
|
1035
1112
|
return paths;
|
|
1036
1113
|
}
|
|
1037
1114
|
function documentScope(file) {
|
|
1038
|
-
const parent =
|
|
1039
|
-
return
|
|
1115
|
+
const parent = path7.posix.dirname(file);
|
|
1116
|
+
return path7.posix.basename(parent).toLowerCase() === ".claude" ? path7.posix.dirname(parent) : parent;
|
|
1040
1117
|
}
|
|
1041
1118
|
async function discoverDocs(resolvedRoot) {
|
|
1119
|
+
return profilePhase("audit.documents", () => readDocs(resolvedRoot));
|
|
1120
|
+
}
|
|
1121
|
+
async function readDocs(resolvedRoot) {
|
|
1042
1122
|
const docs = [];
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1123
|
+
const files = await discoverDocPaths(resolvedRoot);
|
|
1124
|
+
const dirty = await dirtyDocs(resolvedRoot, files);
|
|
1125
|
+
for (let offset = 0; offset < files.length; offset += 4) {
|
|
1126
|
+
docs.push(...await Promise.all(files.slice(offset, offset + 4).map(async (candidate) => {
|
|
1127
|
+
const [content2, lastCommit] = await Promise.all([
|
|
1128
|
+
readAuditInput(resolvedRoot, candidate),
|
|
1129
|
+
profilePhase("audit.document-history", () => lastCommitOf(resolvedRoot, candidate))
|
|
1130
|
+
]);
|
|
1131
|
+
if (content2 === null) throw new Error("Document disappeared during discovery: " + candidate);
|
|
1132
|
+
return {
|
|
1133
|
+
path: candidate,
|
|
1134
|
+
content: content2,
|
|
1135
|
+
scope: documentScope(candidate),
|
|
1136
|
+
kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : "instructions",
|
|
1137
|
+
lineCount: content2.split("\n").length,
|
|
1138
|
+
lastCommit,
|
|
1139
|
+
dirty: dirty.has(candidate),
|
|
1140
|
+
claims: extractClaims(content2)
|
|
1141
|
+
};
|
|
1142
|
+
})));
|
|
1057
1143
|
}
|
|
1058
1144
|
return docs;
|
|
1059
1145
|
}
|
|
1060
|
-
var
|
|
1146
|
+
var DOC_CANDIDATES;
|
|
1061
1147
|
var init_docs = __esm({
|
|
1062
1148
|
"src/audit/docs.ts"() {
|
|
1063
1149
|
"use strict";
|
|
1150
|
+
init_git_read();
|
|
1151
|
+
init_profile();
|
|
1064
1152
|
init_claims();
|
|
1065
1153
|
init_git();
|
|
1066
1154
|
init_inputs();
|
|
1067
1155
|
init_files();
|
|
1068
|
-
|
|
1156
|
+
init_inspection();
|
|
1069
1157
|
DOC_CANDIDATES = [
|
|
1070
1158
|
"AGENTS.md",
|
|
1071
1159
|
"CLAUDE.md",
|
|
@@ -1076,14 +1164,14 @@ var init_docs = __esm({
|
|
|
1076
1164
|
|
|
1077
1165
|
// src/audit/scope.ts
|
|
1078
1166
|
import fs6 from "fs/promises";
|
|
1079
|
-
import
|
|
1167
|
+
import path8 from "path";
|
|
1080
1168
|
function scopedPath(directory, value) {
|
|
1081
|
-
if (
|
|
1082
|
-
const resolved =
|
|
1169
|
+
if (path8.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
|
|
1170
|
+
const resolved = path8.posix.normalize(path8.posix.join(directory, value));
|
|
1083
1171
|
return resolved === ".." || resolved.startsWith("../") ? null : resolved;
|
|
1084
1172
|
}
|
|
1085
1173
|
function pathClaimScope(doc, claim) {
|
|
1086
|
-
const directory = claim.relativeTo === "document" ?
|
|
1174
|
+
const directory = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
|
|
1087
1175
|
const local = scopedPath(directory, claim.path);
|
|
1088
1176
|
if (!local) return null;
|
|
1089
1177
|
const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
|
|
@@ -1117,9 +1205,9 @@ var init_scope = __esm({
|
|
|
1117
1205
|
});
|
|
1118
1206
|
|
|
1119
1207
|
// src/test-map.ts
|
|
1120
|
-
import
|
|
1208
|
+
import path9 from "path";
|
|
1121
1209
|
async function buildTestMap(dir) {
|
|
1122
|
-
const rootDir =
|
|
1210
|
+
const rootDir = path9.resolve(dir);
|
|
1123
1211
|
const access = await createFileAccess(rootDir);
|
|
1124
1212
|
const testPatterns = [
|
|
1125
1213
|
"**/*.test.*",
|
|
@@ -1140,7 +1228,7 @@ async function buildTestMap(dir) {
|
|
|
1140
1228
|
const sourceByBaseName = /* @__PURE__ */ new Map();
|
|
1141
1229
|
for (const file of sourceFiles) {
|
|
1142
1230
|
if (testFiles.includes(file)) continue;
|
|
1143
|
-
const baseName =
|
|
1231
|
+
const baseName = path9.basename(file).replace(/\.[^.]+$/, "");
|
|
1144
1232
|
const existing = sourceByBaseName.get(baseName) ?? [];
|
|
1145
1233
|
existing.push(file);
|
|
1146
1234
|
sourceByBaseName.set(baseName, existing);
|
|
@@ -1148,7 +1236,7 @@ async function buildTestMap(dir) {
|
|
|
1148
1236
|
const paired = [];
|
|
1149
1237
|
const unmatched = [];
|
|
1150
1238
|
for (const testFile of testFiles) {
|
|
1151
|
-
const testBaseName =
|
|
1239
|
+
const testBaseName = path9.basename(testFile).replace(/\.[^.]+$/, "");
|
|
1152
1240
|
const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
|
|
1153
1241
|
if (!sourceName) {
|
|
1154
1242
|
unmatched.push(testFile);
|
|
@@ -1156,10 +1244,10 @@ async function buildTestMap(dir) {
|
|
|
1156
1244
|
}
|
|
1157
1245
|
const candidates = sourceByBaseName.get(sourceName);
|
|
1158
1246
|
if (candidates && candidates.length > 0) {
|
|
1159
|
-
const testDir =
|
|
1247
|
+
const testDir = path9.dirname(testFile);
|
|
1160
1248
|
const bestMatch = candidates.reduce((best, candidate) => {
|
|
1161
|
-
const candidateDir =
|
|
1162
|
-
const bestDir =
|
|
1249
|
+
const candidateDir = path9.dirname(candidate);
|
|
1250
|
+
const bestDir = path9.dirname(best);
|
|
1163
1251
|
const candidateOverlap = commonSegments(testDir, candidateDir);
|
|
1164
1252
|
const bestOverlap = commonSegments(testDir, bestDir);
|
|
1165
1253
|
return candidateOverlap > bestOverlap ? candidate : best;
|
|
@@ -1193,9 +1281,7 @@ var init_test_map = __esm({
|
|
|
1193
1281
|
});
|
|
1194
1282
|
|
|
1195
1283
|
// src/snapshot/snapshot.ts
|
|
1196
|
-
import
|
|
1197
|
-
import { execFile as execFile5 } from "child_process";
|
|
1198
|
-
import { promisify as promisify5 } from "util";
|
|
1284
|
+
import path10 from "path";
|
|
1199
1285
|
import { z as z2 } from "zod";
|
|
1200
1286
|
async function inspectSnapshot(rootDir) {
|
|
1201
1287
|
try {
|
|
@@ -1211,7 +1297,7 @@ async function inspectSnapshot(rootDir) {
|
|
|
1211
1297
|
}
|
|
1212
1298
|
async function getCurrentGitHash(rootDir) {
|
|
1213
1299
|
try {
|
|
1214
|
-
const { stdout } = await
|
|
1300
|
+
const { stdout } = await execGit(["rev-parse", "HEAD"], {
|
|
1215
1301
|
cwd: rootDir
|
|
1216
1302
|
});
|
|
1217
1303
|
return stdout.trim();
|
|
@@ -1219,16 +1305,16 @@ async function getCurrentGitHash(rootDir) {
|
|
|
1219
1305
|
return "unknown";
|
|
1220
1306
|
}
|
|
1221
1307
|
}
|
|
1222
|
-
var
|
|
1308
|
+
var repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
|
|
1223
1309
|
var init_snapshot = __esm({
|
|
1224
1310
|
"src/snapshot/snapshot.ts"() {
|
|
1225
1311
|
"use strict";
|
|
1312
|
+
init_git_read();
|
|
1226
1313
|
init_files();
|
|
1227
1314
|
init_files();
|
|
1228
1315
|
init_storage();
|
|
1229
1316
|
init_paths();
|
|
1230
1317
|
init_test_map();
|
|
1231
|
-
exec4 = promisify5(execFile5);
|
|
1232
1318
|
repoPath = z2.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
|
|
1233
1319
|
verificationFields = {
|
|
1234
1320
|
refreshedHash: z2.string().optional(),
|
|
@@ -1258,9 +1344,7 @@ var init_snapshot = __esm({
|
|
|
1258
1344
|
|
|
1259
1345
|
// src/drift/drift.ts
|
|
1260
1346
|
import fs7 from "fs/promises";
|
|
1261
|
-
import
|
|
1262
|
-
import { execFile as execFile6 } from "child_process";
|
|
1263
|
-
import { promisify as promisify6 } from "util";
|
|
1347
|
+
import path11 from "path";
|
|
1264
1348
|
function parseChanges(output) {
|
|
1265
1349
|
const fields = output.split("\0");
|
|
1266
1350
|
const changes = [];
|
|
@@ -1281,7 +1365,7 @@ function touchedPaths(changes) {
|
|
|
1281
1365
|
async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
|
|
1282
1366
|
if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-") || !toHash || toHash === "unknown" || toHash.startsWith("-")) return null;
|
|
1283
1367
|
try {
|
|
1284
|
-
const { stdout } = await
|
|
1368
|
+
const { stdout } = await execGit(["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
|
|
1285
1369
|
return parseChanges(stdout);
|
|
1286
1370
|
} catch {
|
|
1287
1371
|
return null;
|
|
@@ -1290,8 +1374,8 @@ async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
|
|
|
1290
1374
|
async function getWorkingTree(resolvedRoot) {
|
|
1291
1375
|
try {
|
|
1292
1376
|
const [diff, untracked] = await Promise.all([
|
|
1293
|
-
|
|
1294
|
-
|
|
1377
|
+
execGit(["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
|
|
1378
|
+
execGit(["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
|
|
1295
1379
|
]);
|
|
1296
1380
|
const untrackedFiles = untracked.stdout.split("\0").filter((f) => f && !f.startsWith(".mason/"));
|
|
1297
1381
|
return { available: true, changedFiles: [.../* @__PURE__ */ new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles])].sort(), untrackedFiles };
|
|
@@ -1299,13 +1383,12 @@ async function getWorkingTree(resolvedRoot) {
|
|
|
1299
1383
|
return { available: false, changedFiles: [], untrackedFiles: [] };
|
|
1300
1384
|
}
|
|
1301
1385
|
}
|
|
1302
|
-
var exec5;
|
|
1303
1386
|
var init_drift = __esm({
|
|
1304
1387
|
"src/drift/drift.ts"() {
|
|
1305
1388
|
"use strict";
|
|
1389
|
+
init_git_read();
|
|
1306
1390
|
init_snapshot();
|
|
1307
1391
|
init_paths();
|
|
1308
|
-
exec5 = promisify6(execFile6);
|
|
1309
1392
|
}
|
|
1310
1393
|
});
|
|
1311
1394
|
|
|
@@ -1326,7 +1409,7 @@ var init_types = __esm({
|
|
|
1326
1409
|
});
|
|
1327
1410
|
|
|
1328
1411
|
// src/audit/checks/deleted-reference.ts
|
|
1329
|
-
import
|
|
1412
|
+
import path12 from "path";
|
|
1330
1413
|
async function checkDeletedReferences(ctx) {
|
|
1331
1414
|
const result = emptyResult();
|
|
1332
1415
|
for (const doc of ctx.docs) {
|
|
@@ -1355,7 +1438,7 @@ async function checkDeletedReferences(ctx) {
|
|
|
1355
1438
|
const candidates = await Promise.all(scope.candidates.map(async (file) => ({
|
|
1356
1439
|
file,
|
|
1357
1440
|
tracked: await lastCommitOf(ctx.root, file),
|
|
1358
|
-
parent: await pathExists(ctx.root,
|
|
1441
|
+
parent: await pathExists(ctx.root, path12.posix.dirname(file))
|
|
1359
1442
|
})));
|
|
1360
1443
|
const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
|
|
1361
1444
|
if (!candidate && claim.relativeTo !== "document") continue;
|
|
@@ -1395,7 +1478,7 @@ var init_deleted_reference = __esm({
|
|
|
1395
1478
|
// src/audit/checks/new-module.ts
|
|
1396
1479
|
import fg4 from "fast-glob";
|
|
1397
1480
|
import fs8 from "fs/promises";
|
|
1398
|
-
import
|
|
1481
|
+
import path13 from "path";
|
|
1399
1482
|
function escapeRegExp(text2) {
|
|
1400
1483
|
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1401
1484
|
}
|
|
@@ -1433,23 +1516,36 @@ async function checkNewModules(ctx) {
|
|
|
1433
1516
|
return result;
|
|
1434
1517
|
}
|
|
1435
1518
|
function moduleDocumentation(docs) {
|
|
1436
|
-
return docs.map((doc) => doc.content + "\n" + (
|
|
1519
|
+
return docs.map((doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")).join("\n");
|
|
1520
|
+
}
|
|
1521
|
+
function moduleCandidates(root, combinedDocs) {
|
|
1522
|
+
return inspectionRead(root, "modules:" + combinedDocs, () => collectModuleCandidates(root, combinedDocs));
|
|
1437
1523
|
}
|
|
1438
|
-
async function
|
|
1524
|
+
async function collectModuleCandidates(root, combinedDocs) {
|
|
1439
1525
|
const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
|
|
1440
1526
|
const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
|
|
1441
1527
|
const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
|
|
1442
|
-
const listSubdirs = async (
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1528
|
+
const listSubdirs = async (parents) => {
|
|
1529
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
1530
|
+
for (let offset = 0; offset < parents.length; offset += 16) {
|
|
1531
|
+
await Promise.all(parents.slice(offset, offset + 16).map(async (dir) => {
|
|
1532
|
+
const entries = await fs8.readdir(await auditInputPath(root, dir), { withFileTypes: true });
|
|
1533
|
+
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));
|
|
1534
|
+
byParent.set(dir, dirs);
|
|
1535
|
+
}));
|
|
1536
|
+
}
|
|
1537
|
+
const ignored = await gitIgnoredPaths(root, [...byParent.values()].flat());
|
|
1538
|
+
const result = /* @__PURE__ */ new Map();
|
|
1539
|
+
for (const dir of parents) {
|
|
1540
|
+
const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
|
|
1541
|
+
result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
|
|
1542
|
+
ignore,
|
|
1543
|
+
onlyDirectories: true,
|
|
1544
|
+
label: "Module directory discovery",
|
|
1545
|
+
select: (file) => visible.has(file)
|
|
1546
|
+
})).map((file) => path13.posix.basename(file)));
|
|
1547
|
+
}
|
|
1548
|
+
return result;
|
|
1453
1549
|
};
|
|
1454
1550
|
const countSourceFiles = async (dir) => {
|
|
1455
1551
|
const files = sources.filter((file) => file.startsWith(dir + "/"));
|
|
@@ -1465,14 +1561,16 @@ async function moduleCandidates(root, combinedDocs) {
|
|
|
1465
1561
|
return count4;
|
|
1466
1562
|
};
|
|
1467
1563
|
const candidates = [];
|
|
1468
|
-
|
|
1564
|
+
const topDirs = (await listSubdirs(["."])).get(".");
|
|
1565
|
+
const nested = await listSubdirs(topDirs.filter((dir) => isMentioned(combinedDocs, dir)));
|
|
1566
|
+
for (const topDir of topDirs) {
|
|
1469
1567
|
const topMentioned = isMentioned(combinedDocs, topDir);
|
|
1470
1568
|
if (!topMentioned) {
|
|
1471
1569
|
const count4 = await countSourceFiles(topDir);
|
|
1472
1570
|
if (count4 >= 1) candidates.push({ dir: topDir, sourceFileCount: count4 });
|
|
1473
1571
|
continue;
|
|
1474
1572
|
}
|
|
1475
|
-
const subdirs =
|
|
1573
|
+
const subdirs = nested.get(topDir);
|
|
1476
1574
|
const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
|
|
1477
1575
|
if (mentioned.length < ENUMERATION_THRESHOLD) continue;
|
|
1478
1576
|
for (const sub of subdirs) {
|
|
@@ -1489,6 +1587,7 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
|
|
|
1489
1587
|
var init_new_module = __esm({
|
|
1490
1588
|
"src/audit/checks/new-module.ts"() {
|
|
1491
1589
|
"use strict";
|
|
1590
|
+
init_inspection();
|
|
1492
1591
|
init_files();
|
|
1493
1592
|
init_inputs();
|
|
1494
1593
|
init_git();
|
|
@@ -1518,7 +1617,7 @@ var init_new_module = __esm({
|
|
|
1518
1617
|
});
|
|
1519
1618
|
|
|
1520
1619
|
// src/audit/checks/stale-count.ts
|
|
1521
|
-
import
|
|
1620
|
+
import path14 from "path";
|
|
1522
1621
|
async function countGradleModules(root) {
|
|
1523
1622
|
for (const name of ["settings.gradle.kts", "settings.gradle"]) {
|
|
1524
1623
|
const content2 = await readAuditInput(root, name);
|
|
@@ -1552,7 +1651,7 @@ async function countNpmWorkspaces(root) {
|
|
|
1552
1651
|
return {
|
|
1553
1652
|
actual: matched.length,
|
|
1554
1653
|
countedFrom: "package.json workspaces",
|
|
1555
|
-
members: matched.map((m) =>
|
|
1654
|
+
members: matched.map((m) => path14.dirname(m)).sort()
|
|
1556
1655
|
};
|
|
1557
1656
|
}
|
|
1558
1657
|
}
|
|
@@ -1583,7 +1682,7 @@ async function countNpmWorkspaces(root) {
|
|
|
1583
1682
|
return {
|
|
1584
1683
|
actual: matched.length,
|
|
1585
1684
|
countedFrom: "pnpm-workspace.yaml",
|
|
1586
|
-
members: matched.map((m) =>
|
|
1685
|
+
members: matched.map((m) => path14.dirname(m)).sort()
|
|
1587
1686
|
};
|
|
1588
1687
|
}
|
|
1589
1688
|
}
|
|
@@ -1605,8 +1704,8 @@ async function countCargoCrates(root) {
|
|
|
1605
1704
|
ignore: ["**/target/**"],
|
|
1606
1705
|
label: "Cargo workspace discovery"
|
|
1607
1706
|
});
|
|
1608
|
-
for (const m of matched) members.add(
|
|
1609
|
-
} else if (await readAuditInput(root,
|
|
1707
|
+
for (const m of matched) members.add(path14.dirname(m));
|
|
1708
|
+
} else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
|
|
1610
1709
|
members.add(entry);
|
|
1611
1710
|
}
|
|
1612
1711
|
}
|
|
@@ -1626,7 +1725,7 @@ async function resolveCountSource(root, claim) {
|
|
|
1626
1725
|
}
|
|
1627
1726
|
async function resolveDocCountSource(root, doc, claim) {
|
|
1628
1727
|
const scope = documentScope(doc);
|
|
1629
|
-
const source = await resolveCountSource(
|
|
1728
|
+
const source = await resolveCountSource(path14.join(root, scope), claim);
|
|
1630
1729
|
if (!source || scope === ".") return source;
|
|
1631
1730
|
return {
|
|
1632
1731
|
...source,
|
|
@@ -1678,7 +1777,7 @@ var init_stale_count = __esm({
|
|
|
1678
1777
|
});
|
|
1679
1778
|
|
|
1680
1779
|
// src/audit/checks/dead-command.ts
|
|
1681
|
-
import
|
|
1780
|
+
import path15 from "path";
|
|
1682
1781
|
function scriptsOf(raw) {
|
|
1683
1782
|
try {
|
|
1684
1783
|
const pkg = JSON.parse(raw);
|
|
@@ -1714,7 +1813,7 @@ async function commandInputs(root, docs) {
|
|
|
1714
1813
|
}
|
|
1715
1814
|
let cursor = directory;
|
|
1716
1815
|
while (true) {
|
|
1717
|
-
const manifest =
|
|
1816
|
+
const manifest = path15.posix.join(cursor, "package.json");
|
|
1718
1817
|
scope.candidates.push(manifest);
|
|
1719
1818
|
const raw = await read(manifest);
|
|
1720
1819
|
if (raw !== null) {
|
|
@@ -1725,7 +1824,7 @@ async function commandInputs(root, docs) {
|
|
|
1725
1824
|
break;
|
|
1726
1825
|
}
|
|
1727
1826
|
if (claim.directory !== void 0 || cursor === ".") break;
|
|
1728
|
-
cursor =
|
|
1827
|
+
cursor = path15.posix.dirname(cursor);
|
|
1729
1828
|
}
|
|
1730
1829
|
if (!resolution.manifest && !resolution.problem) {
|
|
1731
1830
|
resolution.problem = `No package.json for the command's scope (${directory}).`;
|
|
@@ -1788,10 +1887,8 @@ var init_dead_command = __esm({
|
|
|
1788
1887
|
});
|
|
1789
1888
|
|
|
1790
1889
|
// src/audit/release-metadata.ts
|
|
1791
|
-
import { execFile as execFile7 } from "child_process";
|
|
1792
|
-
import { promisify as promisify7 } from "util";
|
|
1793
1890
|
async function git(root, args) {
|
|
1794
|
-
return (await
|
|
1891
|
+
return (await execGit(args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
|
|
1795
1892
|
}
|
|
1796
1893
|
function withoutAndroidReleaseValues(text2) {
|
|
1797
1894
|
if (/\/\*|"""|'''/.test(text2) || !/id\s*\(?\s*["']com\.android\.(application|library)["']/.test(text2)) return null;
|
|
@@ -1837,11 +1934,10 @@ async function releaseMetadataOnly(root, commit) {
|
|
|
1837
1934
|
return false;
|
|
1838
1935
|
}
|
|
1839
1936
|
}
|
|
1840
|
-
var exec6;
|
|
1841
1937
|
var init_release_metadata = __esm({
|
|
1842
1938
|
"src/audit/release-metadata.ts"() {
|
|
1843
1939
|
"use strict";
|
|
1844
|
-
|
|
1940
|
+
init_git_read();
|
|
1845
1941
|
}
|
|
1846
1942
|
});
|
|
1847
1943
|
|
|
@@ -1937,7 +2033,7 @@ var init_deps_changed = __esm({
|
|
|
1937
2033
|
});
|
|
1938
2034
|
|
|
1939
2035
|
// src/context/lexical.ts
|
|
1940
|
-
import
|
|
2036
|
+
import path16 from "path";
|
|
1941
2037
|
var init_lexical = __esm({
|
|
1942
2038
|
"src/context/lexical.ts"() {
|
|
1943
2039
|
"use strict";
|
|
@@ -2121,7 +2217,7 @@ var init_provenance = __esm({
|
|
|
2121
2217
|
|
|
2122
2218
|
// src/decisions/decisions.ts
|
|
2123
2219
|
import fs9 from "fs/promises";
|
|
2124
|
-
import
|
|
2220
|
+
import path17 from "path";
|
|
2125
2221
|
import { createHash } from "crypto";
|
|
2126
2222
|
import { z as z4 } from "zod";
|
|
2127
2223
|
async function loadDecisionStore(rootDir) {
|
|
@@ -2172,9 +2268,9 @@ var init_decisions = __esm({
|
|
|
2172
2268
|
});
|
|
2173
2269
|
|
|
2174
2270
|
// src/decisions/drift.ts
|
|
2175
|
-
import
|
|
2271
|
+
import path18 from "path";
|
|
2176
2272
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
2177
|
-
const resolvedRoot =
|
|
2273
|
+
const resolvedRoot = path18.resolve(rootDir);
|
|
2178
2274
|
const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
|
|
2179
2275
|
const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
|
|
2180
2276
|
const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
|
|
@@ -2398,8 +2494,6 @@ var init_findings = __esm({
|
|
|
2398
2494
|
|
|
2399
2495
|
// src/audit/advisory-review.ts
|
|
2400
2496
|
import fs10 from "fs/promises";
|
|
2401
|
-
import { execFile as execFile8 } from "child_process";
|
|
2402
|
-
import { promisify as promisify8 } from "util";
|
|
2403
2497
|
import { z as z6 } from "zod";
|
|
2404
2498
|
async function readRecord(root, id) {
|
|
2405
2499
|
const raw = await readStoreJson(root, recordPath(id));
|
|
@@ -2526,10 +2620,12 @@ async function advisoryReviewInventory(root) {
|
|
|
2526
2620
|
if (names.length > 5e3) throw new Error("Advisory review inventory exceeds 5,000 records.");
|
|
2527
2621
|
return Promise.all(names.filter((name) => name.endsWith(".json")).sort().map(async (name) => [name, await readAuditInput(root, ".mason/reviews/advisories/" + name)]));
|
|
2528
2622
|
}
|
|
2529
|
-
var
|
|
2623
|
+
var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
|
|
2530
2624
|
var init_advisory_review = __esm({
|
|
2531
2625
|
"src/audit/advisory-review.ts"() {
|
|
2532
2626
|
"use strict";
|
|
2627
|
+
init_inspection();
|
|
2628
|
+
init_git_read();
|
|
2533
2629
|
init_findings();
|
|
2534
2630
|
init_deps_changed();
|
|
2535
2631
|
init_inputs();
|
|
@@ -2539,8 +2635,7 @@ var init_advisory_review = __esm({
|
|
|
2539
2635
|
init_store();
|
|
2540
2636
|
init_decisions();
|
|
2541
2637
|
init_drift2();
|
|
2542
|
-
|
|
2543
|
-
git2 = async (root, ...args) => (await execute2("git", args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
2638
|
+
git2 = async (root, ...args) => (await (["log", "ls-tree", "ls-files", "merge-base"].includes(args[0]) ? inspectionGit : execGit)(args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
2544
2639
|
hashSchema = z6.string().regex(/^[a-f0-9]{64}$/);
|
|
2545
2640
|
commitSchema2 = z6.string().regex(/^[a-f0-9]{40,64}$/);
|
|
2546
2641
|
evidenceSchema2 = z6.object({
|
|
@@ -2584,9 +2679,12 @@ var init_advisory_review = __esm({
|
|
|
2584
2679
|
|
|
2585
2680
|
// src/audit/audit.ts
|
|
2586
2681
|
import fs11 from "fs/promises";
|
|
2587
|
-
import
|
|
2682
|
+
import path19 from "path";
|
|
2588
2683
|
async function computeAudit(rootDir, options = {}) {
|
|
2589
|
-
|
|
2684
|
+
return profilePhase("audit.current", () => auditCurrent(rootDir, options));
|
|
2685
|
+
}
|
|
2686
|
+
async function auditCurrent(rootDir, options) {
|
|
2687
|
+
const resolvedRoot = path19.resolve(rootDir);
|
|
2590
2688
|
const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
|
|
2591
2689
|
if (docs.length === 0) return null;
|
|
2592
2690
|
const report = {
|
|
@@ -2612,15 +2710,15 @@ async function computeAudit(rootDir, options = {}) {
|
|
|
2612
2710
|
};
|
|
2613
2711
|
if (!report.gitAvailable) return report;
|
|
2614
2712
|
const changesSinceDoc = /* @__PURE__ */ new Map();
|
|
2713
|
+
const changesByCommit = /* @__PURE__ */ new Map();
|
|
2615
2714
|
for (const doc of docs) {
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
);
|
|
2715
|
+
const hash2 = doc.lastCommit?.hash;
|
|
2716
|
+
if (hash2 && !changesByCommit.has(hash2)) changesByCommit.set(hash2, await getChangesWithStatus(resolvedRoot, hash2));
|
|
2717
|
+
changesSinceDoc.set(doc.path, hash2 ? changesByCommit.get(hash2) : null);
|
|
2620
2718
|
}
|
|
2621
2719
|
let decisionsPresent = false;
|
|
2622
2720
|
try {
|
|
2623
|
-
await fs11.access(
|
|
2721
|
+
await fs11.access(path19.join(resolvedRoot, ".mason", "decisions"));
|
|
2624
2722
|
decisionsPresent = true;
|
|
2625
2723
|
} catch {
|
|
2626
2724
|
}
|
|
@@ -2650,6 +2748,7 @@ async function computeAudit(rootDir, options = {}) {
|
|
|
2650
2748
|
var init_audit = __esm({
|
|
2651
2749
|
"src/audit/audit.ts"() {
|
|
2652
2750
|
"use strict";
|
|
2751
|
+
init_profile();
|
|
2653
2752
|
init_drift();
|
|
2654
2753
|
init_snapshot();
|
|
2655
2754
|
init_docs();
|
|
@@ -2661,7 +2760,7 @@ var init_audit = __esm({
|
|
|
2661
2760
|
|
|
2662
2761
|
// src/audit/repair.ts
|
|
2663
2762
|
import fs12 from "fs/promises";
|
|
2664
|
-
import
|
|
2763
|
+
import path20 from "path";
|
|
2665
2764
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2666
2765
|
import { z as z7 } from "zod";
|
|
2667
2766
|
function recheckable(finding) {
|
|
@@ -2685,6 +2784,9 @@ async function docState(root) {
|
|
|
2685
2784
|
return digest(docs);
|
|
2686
2785
|
}
|
|
2687
2786
|
async function stableAudit(root, checks, options = {}) {
|
|
2787
|
+
return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
|
|
2788
|
+
}
|
|
2789
|
+
async function readStableAudit(root, checks, options) {
|
|
2688
2790
|
const head = await getCurrentGitHash(root);
|
|
2689
2791
|
const before = await docState(root);
|
|
2690
2792
|
const report = await computeAudit(root, { ...options, checks });
|
|
@@ -2693,10 +2795,43 @@ async function stableAudit(root, checks, options = {}) {
|
|
|
2693
2795
|
}
|
|
2694
2796
|
return report;
|
|
2695
2797
|
}
|
|
2696
|
-
async function
|
|
2798
|
+
async function withRepairSession(root, options, run, validate, cachedAudit) {
|
|
2799
|
+
const head = await getCurrentGitHash(root);
|
|
2800
|
+
const audits = /* @__PURE__ */ new Map();
|
|
2801
|
+
const documents = /* @__PURE__ */ new Map();
|
|
2802
|
+
const history = /* @__PURE__ */ new Map();
|
|
2803
|
+
const assessments = /* @__PURE__ */ new Map();
|
|
2804
|
+
const memo = (cache, key, read) => {
|
|
2805
|
+
if (!cache.has(key)) cache.set(key, read());
|
|
2806
|
+
return cache.get(key);
|
|
2807
|
+
};
|
|
2808
|
+
const session = {
|
|
2809
|
+
audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
|
|
2810
|
+
if (cachedAudit && cachedAudit.headHash === head && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
|
|
2811
|
+
const current = structuredClone(cachedAudit);
|
|
2812
|
+
const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, head)))).filter((r) => r !== null);
|
|
2813
|
+
delete current.advisoryReviews;
|
|
2814
|
+
if (reviews.length) current.advisoryReviews = reviews;
|
|
2815
|
+
return current;
|
|
2816
|
+
}
|
|
2817
|
+
return computeAudit(root, { ...options, checks });
|
|
2818
|
+
}),
|
|
2819
|
+
document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
|
|
2820
|
+
history: (head2) => memo(history, head2, () => getChangesWithStatus(root, head2)),
|
|
2821
|
+
assessment: (dir, finding, head2) => memo(assessments, digest([dir, finding, head2]), () => assessAdvisory(dir, finding, head2))
|
|
2822
|
+
};
|
|
2823
|
+
const result = await run({
|
|
2824
|
+
prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
|
|
2825
|
+
verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
|
|
2826
|
+
currentAudit: () => session.audit(ALL_CHECKS)
|
|
2827
|
+
});
|
|
2828
|
+
await validate();
|
|
2829
|
+
return result;
|
|
2830
|
+
}
|
|
2831
|
+
async function prepareOriginal(rootDir, checks, options, session) {
|
|
2697
2832
|
const root = await fs12.realpath(rootDir);
|
|
2698
2833
|
const selected = z7.array(checkSchema).nonempty().parse(checks);
|
|
2699
|
-
const report = await stableAudit(root, selected, options);
|
|
2834
|
+
const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
|
|
2700
2835
|
if (!report) throw new Error("No context files found to prepare a repair.");
|
|
2701
2836
|
if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
|
|
2702
2837
|
const storedReport = reportSchema.parse(report);
|
|
@@ -2712,8 +2847,8 @@ async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
|
|
|
2712
2847
|
}
|
|
2713
2848
|
async function loadRepairBaseline(rootDir, baselinePath) {
|
|
2714
2849
|
const root = await fs12.realpath(rootDir);
|
|
2715
|
-
const declaredRoot =
|
|
2716
|
-
const relative =
|
|
2850
|
+
const declaredRoot = path20.resolve(rootDir);
|
|
2851
|
+
const relative = path20.isAbsolute(baselinePath) ? path20.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
|
|
2717
2852
|
const stored = baselineSchema.parse(await readStoreJson(root, relative));
|
|
2718
2853
|
const { digest: savedDigest, ...payload } = stored;
|
|
2719
2854
|
if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
|
|
@@ -2721,24 +2856,24 @@ async function loadRepairBaseline(rootDir, baselinePath) {
|
|
|
2721
2856
|
const original = stored.report;
|
|
2722
2857
|
return { root, relative, original };
|
|
2723
2858
|
}
|
|
2724
|
-
async function
|
|
2859
|
+
async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
2725
2860
|
const { root, relative, original } = await loadRepairBaseline(rootDir, baselinePath);
|
|
2726
2861
|
const diagnostics = [];
|
|
2727
2862
|
let reviewDigest;
|
|
2728
2863
|
let current = null;
|
|
2729
2864
|
try {
|
|
2730
|
-
reviewDigest = digest(await advisoryReviewInventory(root));
|
|
2731
|
-
current = await stableAudit(root, original.checksRun, options);
|
|
2865
|
+
if (!session) reviewDigest = digest(await advisoryReviewInventory(root));
|
|
2866
|
+
current = await (session ? session.audit(original.checksRun) : stableAudit(root, original.checksRun, options));
|
|
2732
2867
|
if (!current) diagnostics.push("No context files remain available to audit.");
|
|
2733
2868
|
else if (!current.gitAvailable) diagnostics.push("Git history is unavailable.");
|
|
2734
2869
|
for (const doc of original.docs) {
|
|
2735
2870
|
if (!allFindings(original).some((f) => f.anchor.doc === doc.path) || !current?.docs.some((d) => d.path === doc.path)) continue;
|
|
2736
|
-
const content2 = await readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024);
|
|
2871
|
+
const content2 = await (session ? session.document(doc.path) : readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024));
|
|
2737
2872
|
if (content2 === null || !content2.trim()) {
|
|
2738
2873
|
diagnostics.push("Original context file " + doc.path + " is empty or unreadable; losing its claims does not verify a repair.");
|
|
2739
2874
|
}
|
|
2740
2875
|
}
|
|
2741
|
-
if (await getChangesWithStatus(root, original.headHash) === null) {
|
|
2876
|
+
if (await (session ? session.history(original.headHash) : getChangesWithStatus(root, original.headHash)) === null) {
|
|
2742
2877
|
diagnostics.push("The original audit commit is unavailable; repair history cannot be verified.");
|
|
2743
2878
|
}
|
|
2744
2879
|
} catch (error) {
|
|
@@ -2763,7 +2898,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
|
|
|
2763
2898
|
if (!current.checksRun?.includes(finding.type) || skipped.length) {
|
|
2764
2899
|
return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
|
|
2765
2900
|
}
|
|
2766
|
-
const review = await assessAdvisory(root, now ?? finding, original.headHash);
|
|
2901
|
+
const review = await (session?.assessment ?? assessAdvisory)(root, now ?? finding, original.headHash);
|
|
2767
2902
|
if (review) {
|
|
2768
2903
|
if (review.status === "unverified") return { ...base, review, status: "unverified", reason: review.reason };
|
|
2769
2904
|
if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
|
|
@@ -2778,7 +2913,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
|
|
|
2778
2913
|
}
|
|
2779
2914
|
return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
|
|
2780
2915
|
}));
|
|
2781
|
-
try {
|
|
2916
|
+
if (!session) try {
|
|
2782
2917
|
if (reviewDigest !== digest(await advisoryReviewInventory(root)) || current?.headHash !== await getCurrentGitHash(root)) {
|
|
2783
2918
|
diagnostics.push("HEAD or advisory review records changed during verification; repeat against stable evidence.");
|
|
2784
2919
|
for (const finding of findings) {
|
|
@@ -2818,6 +2953,7 @@ var count2, reportSchema, baselineSchema, repairFindingSchema;
|
|
|
2818
2953
|
var init_repair = __esm({
|
|
2819
2954
|
"src/audit/repair.ts"() {
|
|
2820
2955
|
"use strict";
|
|
2956
|
+
init_profile();
|
|
2821
2957
|
init_audit();
|
|
2822
2958
|
init_docs();
|
|
2823
2959
|
init_snapshot();
|
|
@@ -2871,27 +3007,25 @@ var init_repair = __esm({
|
|
|
2871
3007
|
|
|
2872
3008
|
// src/automation/evidence.ts
|
|
2873
3009
|
import fs13 from "fs/promises";
|
|
2874
|
-
import
|
|
3010
|
+
import path21 from "path";
|
|
2875
3011
|
import { createHash as createHash3 } from "crypto";
|
|
2876
|
-
import { execFile as execFile9 } from "child_process";
|
|
2877
|
-
import { promisify as promisify9 } from "util";
|
|
2878
3012
|
import fg5 from "fast-glob";
|
|
2879
3013
|
import { z as z8 } from "zod";
|
|
2880
3014
|
async function git3(root, ...args) {
|
|
2881
|
-
return (
|
|
3015
|
+
return profilePhase("automation.git", async () => (await execGit(args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
|
|
2882
3016
|
}
|
|
2883
3017
|
async function workspace(dir) {
|
|
2884
3018
|
const root = await fs13.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
|
|
2885
|
-
const gitDir = await
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
} catch {
|
|
2890
|
-
branch = "detached";
|
|
2891
|
-
}
|
|
3019
|
+
const [gitDir, branch] = await Promise.all([
|
|
3020
|
+
git3(root, "rev-parse", "--absolute-git-dir").then((value) => fs13.realpath(value.trim())),
|
|
3021
|
+
git3(root, "symbolic-ref", "--quiet", "HEAD").then((value) => value.trim(), () => "detached")
|
|
3022
|
+
]);
|
|
2892
3023
|
return { root, gitDir, branch, directory: ".mason/reports/automation/" + hash([root, gitDir, branch]).slice(0, 24) };
|
|
2893
3024
|
}
|
|
2894
3025
|
async function readInputs(root) {
|
|
3026
|
+
return profilePhase("automation.inputs", () => collectInputs(root));
|
|
3027
|
+
}
|
|
3028
|
+
async function collectInputs(root) {
|
|
2895
3029
|
const docPaths = await discoverDocPaths(root);
|
|
2896
3030
|
const [headText, docStatus, shallowPath, replacements] = await Promise.all([
|
|
2897
3031
|
git3(root, "rev-parse", "HEAD"),
|
|
@@ -2902,27 +3036,35 @@ async function readInputs(root) {
|
|
|
2902
3036
|
const head = headText.trim();
|
|
2903
3037
|
let shallow = null;
|
|
2904
3038
|
try {
|
|
2905
|
-
shallow = await fs13.readFile(
|
|
3039
|
+
shallow = await fs13.readFile(path21.resolve(root, shallowPath.trim()), "utf8");
|
|
2906
3040
|
} catch (error) {
|
|
2907
3041
|
if (error.code !== "ENOENT") throw error;
|
|
2908
3042
|
}
|
|
2909
3043
|
const docs = {};
|
|
2910
3044
|
const docContents = [];
|
|
3045
|
+
const parsedDocs = [];
|
|
2911
3046
|
const claims = [];
|
|
3047
|
+
const presence = /* @__PURE__ */ new Map();
|
|
3048
|
+
const exists = (file) => {
|
|
3049
|
+
if (!presence.has(file)) presence.set(file, pathExists(root, file));
|
|
3050
|
+
return presence.get(file);
|
|
3051
|
+
};
|
|
2912
3052
|
for (const file of docPaths) {
|
|
2913
3053
|
const text2 = await content(root, file);
|
|
2914
3054
|
docs[file] = text2 === null ? null : hash(text2);
|
|
2915
3055
|
docContents.push([file, text2]);
|
|
2916
|
-
|
|
3056
|
+
const parsed = { path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") };
|
|
3057
|
+
parsedDocs.push(parsed);
|
|
3058
|
+
for (const claim of parsed.claims.paths) {
|
|
2917
3059
|
const scope = pathClaimScope(file, claim);
|
|
2918
3060
|
if (scope?.candidates.some(gitMetadataPath)) continue;
|
|
2919
3061
|
for (const candidate of scope?.candidates ?? []) {
|
|
2920
3062
|
if (optionalMasonPath(candidate)) continue;
|
|
2921
|
-
|
|
3063
|
+
const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path21.posix.dirname(candidate))]);
|
|
3064
|
+
claims.push([candidate, filePresent, parentPresent]);
|
|
2922
3065
|
}
|
|
2923
3066
|
}
|
|
2924
3067
|
}
|
|
2925
|
-
const parsedDocs = docContents.map(([file, text2]) => ({ path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") }));
|
|
2926
3068
|
const combinedDocs = moduleDocumentation(parsedDocs);
|
|
2927
3069
|
const countClaims = parsedDocs.flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
|
|
2928
3070
|
const decisionDirectory = await storePath(root, ".mason/decisions");
|
|
@@ -2930,7 +3072,7 @@ async function readInputs(root) {
|
|
|
2930
3072
|
if (error.code === "ENOENT") return "absent";
|
|
2931
3073
|
throw error;
|
|
2932
3074
|
});
|
|
2933
|
-
const [modules, counts,
|
|
3075
|
+
const [modules, counts, commands2, decisionFiles] = await Promise.all([
|
|
2934
3076
|
combinedDocs ? moduleCandidates(root, combinedDocs) : [],
|
|
2935
3077
|
Promise.all(countClaims.map(({ doc, claim }) => resolveDocCountSource(root, doc, claim))),
|
|
2936
3078
|
commandInputs(root, parsedDocs),
|
|
@@ -2944,12 +3086,12 @@ async function readInputs(root) {
|
|
|
2944
3086
|
git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
|
|
2945
3087
|
git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
|
|
2946
3088
|
]) : ["", ""];
|
|
2947
|
-
const common = [
|
|
3089
|
+
const common = [5, engineVersion, head, shallow, replacements, docContents];
|
|
2948
3090
|
const keys = {
|
|
2949
3091
|
"deleted-reference": hash([common, claims, docStatus]),
|
|
2950
3092
|
"new-module": hash([common, modules]),
|
|
2951
3093
|
"stale-count": hash([common, counts]),
|
|
2952
|
-
"dead-command": hash([common,
|
|
3094
|
+
"dead-command": hash([common, commands2]),
|
|
2953
3095
|
"deps-changed": hash([common, docStatus]),
|
|
2954
3096
|
"decision-anchor-drift": hash([common, decisionPresence, decisions, status, index2])
|
|
2955
3097
|
};
|
|
@@ -2969,21 +3111,30 @@ function checkCache(raw, inputs) {
|
|
|
2969
3111
|
reused.add(name);
|
|
2970
3112
|
return structuredClone(entries[name].result);
|
|
2971
3113
|
}
|
|
2972
|
-
const result = await CHECKS[name](ctx);
|
|
3114
|
+
const result = await profilePhase("check." + name, () => CHECKS[name](ctx));
|
|
2973
3115
|
ran.add(name);
|
|
2974
3116
|
if (!result.skipped.length) entries[name] = { key: inputs.keys[name], result };
|
|
2975
3117
|
else delete entries[name];
|
|
2976
3118
|
return result;
|
|
2977
3119
|
} };
|
|
2978
|
-
return {
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3120
|
+
return {
|
|
3121
|
+
options,
|
|
3122
|
+
ran,
|
|
3123
|
+
reused,
|
|
3124
|
+
diagnostic,
|
|
3125
|
+
has: (name) => entries[name]?.key === inputs.keys[name] && !entries[name].result.skipped.length,
|
|
3126
|
+
serialize: () => {
|
|
3127
|
+
const canonical = cacheSchema.shape.entries.parse(entries);
|
|
3128
|
+
return { version: 1, entries: canonical, digest: hash(canonical) };
|
|
3129
|
+
}
|
|
3130
|
+
};
|
|
2982
3131
|
}
|
|
2983
|
-
var
|
|
3132
|
+
var engineVersion, hash, content, cacheSchema;
|
|
2984
3133
|
var init_evidence = __esm({
|
|
2985
3134
|
"src/automation/evidence.ts"() {
|
|
2986
3135
|
"use strict";
|
|
3136
|
+
init_git_read();
|
|
3137
|
+
init_profile();
|
|
2987
3138
|
init_docs();
|
|
2988
3139
|
init_scope();
|
|
2989
3140
|
init_claims();
|
|
@@ -2995,8 +3146,7 @@ var init_evidence = __esm({
|
|
|
2995
3146
|
init_dead_command();
|
|
2996
3147
|
init_storage();
|
|
2997
3148
|
init_advisory_review();
|
|
2998
|
-
|
|
2999
|
-
engineVersion = true ? "0.17.2" : "development";
|
|
3149
|
+
engineVersion = true ? "0.17.4" : "development";
|
|
3000
3150
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3001
3151
|
content = readAuditInput;
|
|
3002
3152
|
cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
|
|
@@ -3190,7 +3340,7 @@ var init_execution = __esm({
|
|
|
3190
3340
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3191
3341
|
import { z as z10 } from "zod";
|
|
3192
3342
|
async function observeReadOnlyTool(dir, event) {
|
|
3193
|
-
const ws = await workspace(dir);
|
|
3343
|
+
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
3194
3344
|
await withLock(ws.root, ws.directory, async () => {
|
|
3195
3345
|
const statePath = ws.directory + "/state.json";
|
|
3196
3346
|
const raw = await readStoreJson(ws.root, statePath);
|
|
@@ -3237,24 +3387,41 @@ async function loadState(ws) {
|
|
|
3237
3387
|
}
|
|
3238
3388
|
return state;
|
|
3239
3389
|
}
|
|
3240
|
-
async function
|
|
3390
|
+
async function matchingAnalysis(ws, state, inputs) {
|
|
3241
3391
|
const pointer = state.analysis;
|
|
3242
|
-
if (!pointer || pointer.fingerprint !== inputs.fingerprint
|
|
3243
|
-
|
|
3392
|
+
if (!pointer || pointer.fingerprint !== inputs.fingerprint) return { analysis: null };
|
|
3393
|
+
if (pointer.path !== ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json") {
|
|
3394
|
+
throw new Error("Invalid automation analysis cache path; inspect the retained state.");
|
|
3395
|
+
}
|
|
3396
|
+
const invalid = { analysis: null, diagnostic: "Discarded an invalid current-audit cache; audit preparation and original verification were repeated." };
|
|
3397
|
+
let raw;
|
|
3398
|
+
try {
|
|
3399
|
+
raw = await readStoreJson(ws.root, pointer.path);
|
|
3400
|
+
} catch (error) {
|
|
3401
|
+
const cause = error.cause;
|
|
3402
|
+
if (cause instanceof SyntaxError || cause instanceof Error && cause.message === "expected a JSON object, received null") return invalid;
|
|
3403
|
+
throw error;
|
|
3404
|
+
}
|
|
3405
|
+
if (raw === null) return { analysis: null };
|
|
3406
|
+
const parsed = sharedSchema.safeParse(raw);
|
|
3407
|
+
if (!parsed.success) return invalid;
|
|
3408
|
+
const stored = parsed.data;
|
|
3244
3409
|
const a = stored.analysis;
|
|
3245
3410
|
if (stored.digest !== hash(a) || a.id !== pointer.id || a.fingerprint !== inputs.fingerprint || a.completedAt !== pointer.completedAt || a.report.root !== ws.root || a.report.branch !== ws.branch || a.report.head !== inputs.head || hash(a.report.baselinePaths) !== hash(state.baselines.map((b) => b.path))) {
|
|
3246
|
-
|
|
3411
|
+
return invalid;
|
|
3247
3412
|
}
|
|
3248
3413
|
if (a.baselineDigest !== await baselineDigest(ws, state)) throw new Error("Repair baseline was modified; use the original baseline.");
|
|
3249
|
-
return a;
|
|
3414
|
+
return { analysis: a };
|
|
3250
3415
|
}
|
|
3251
3416
|
async function verifyInputs(ws, inputs) {
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3417
|
+
return profilePhase("automation.revalidate", () => withRepositoryInspection(ws.root, async () => {
|
|
3418
|
+
const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
|
|
3419
|
+
if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
|
|
3420
|
+
throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
|
|
3421
|
+
}
|
|
3422
|
+
}));
|
|
3256
3423
|
}
|
|
3257
|
-
async function computeAnalysis(ws, state, inputs, event) {
|
|
3424
|
+
async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
|
|
3258
3425
|
const baselines = [...state.baselines];
|
|
3259
3426
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3260
3427
|
const empty = {
|
|
@@ -3272,7 +3439,10 @@ async function computeAnalysis(ws, state, inputs, event) {
|
|
|
3272
3439
|
capture: "unknown",
|
|
3273
3440
|
scope: SCOPE
|
|
3274
3441
|
};
|
|
3275
|
-
if (!baselines.length && Object.values(inputs.docs).every((value) => value === null))
|
|
3442
|
+
if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) {
|
|
3443
|
+
await verifyInputs(ws, inputs);
|
|
3444
|
+
return { baselines, report: empty, cache: null };
|
|
3445
|
+
}
|
|
3276
3446
|
if (ws.branch === "detached" && state.latest) {
|
|
3277
3447
|
const previous = await readStoreJson(ws.root, state.latest);
|
|
3278
3448
|
if (!previous?.head || !/^[a-f0-9]{40,64}$/.test(previous.head)) throw new Error("The previous detached checkout evidence is unavailable.");
|
|
@@ -3283,7 +3453,7 @@ async function computeAnalysis(ws, state, inputs, event) {
|
|
|
3283
3453
|
}
|
|
3284
3454
|
}
|
|
3285
3455
|
let cached = null;
|
|
3286
|
-
const diagnostics = [];
|
|
3456
|
+
const diagnostics = cacheDiagnostic ? [cacheDiagnostic] : [];
|
|
3287
3457
|
try {
|
|
3288
3458
|
cached = await readStoreJson(ws.root, ws.directory + "/cache.json");
|
|
3289
3459
|
} catch {
|
|
@@ -3291,19 +3461,26 @@ async function computeAnalysis(ws, state, inputs, event) {
|
|
|
3291
3461
|
}
|
|
3292
3462
|
const cache = checkCache(cached, inputs);
|
|
3293
3463
|
if (cache.diagnostic) diagnostics.push(cache.diagnostic);
|
|
3294
|
-
const
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3464
|
+
const cachedAudit = previousAudit?.gitAvailable && previousAudit.headHash === inputs.head && previousAudit.root === ws.root && !previousAudit.skippedChecks.length && ALL_CHECKS.every((name) => previousAudit.checksRun?.includes(name) && cache.has(name)) ? previousAudit : void 0;
|
|
3465
|
+
if (cachedAudit) for (const name of ALL_CHECKS) cache.reused.add(name);
|
|
3466
|
+
let currentAudit = null;
|
|
3467
|
+
const verifications = await withRepairSession(ws.root, cache.options, async (repairs) => {
|
|
3468
|
+
const saveBaseline = async () => {
|
|
3469
|
+
if (baselines.length >= 128) throw new Error("128 retained baselines need review; automatic capture stopped without discarding original evidence.");
|
|
3470
|
+
const prepared = await repairs.prepare();
|
|
3471
|
+
baselines.push({ path: prepared.baselinePath, at: now, event: event.event, fingerprint: inputs.fingerprint });
|
|
3472
|
+
};
|
|
3473
|
+
if (!baselines.length) await saveBaseline();
|
|
3474
|
+
const verifications2 = [];
|
|
3475
|
+
for (const baseline of baselines) verifications2.push(await repairs.verify(baseline.path));
|
|
3476
|
+
const known = new Set(verifications2.flatMap((v) => v.findings.map((f) => f.id)));
|
|
3477
|
+
if (verifications2.some((v) => v.newFindings.some((f) => !known.has(findingId(f))))) {
|
|
3478
|
+
await saveBaseline();
|
|
3479
|
+
verifications2.push(await repairs.verify(baselines.at(-1).path));
|
|
3480
|
+
}
|
|
3481
|
+
currentAudit = await repairs.currentAudit();
|
|
3482
|
+
return verifications2;
|
|
3483
|
+
}, () => verifyInputs(ws, inputs), cachedAudit);
|
|
3307
3484
|
const merged = /* @__PURE__ */ new Map();
|
|
3308
3485
|
for (const verification of verifications) {
|
|
3309
3486
|
for (const finding of verification.findings) {
|
|
@@ -3324,21 +3501,20 @@ async function computeAnalysis(ws, state, inputs, event) {
|
|
|
3324
3501
|
reportPath: "",
|
|
3325
3502
|
findings: [...merged.values()],
|
|
3326
3503
|
diagnostics: [...new Set(diagnostics)],
|
|
3327
|
-
checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [] },
|
|
3504
|
+
checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [], ...cachedAudit ? { auditReused: true } : {} },
|
|
3328
3505
|
counts,
|
|
3329
3506
|
capture: "unknown",
|
|
3330
3507
|
scope: SCOPE
|
|
3331
3508
|
};
|
|
3332
|
-
return { baselines, report, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
|
|
3509
|
+
return { baselines, report, currentAudit, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
|
|
3333
3510
|
}
|
|
3334
3511
|
async function analyze(ws, inputs, event, startedAt) {
|
|
3335
3512
|
return withLock(ws.root, ws.directory + "/analysis/" + inputs.fingerprint, async () => {
|
|
3336
3513
|
const before = await loadState(ws);
|
|
3337
|
-
const
|
|
3338
|
-
if (
|
|
3514
|
+
const { analysis: previous, diagnostic } = await matchingAnalysis(ws, before, inputs);
|
|
3515
|
+
if (previous && previous.completedAt >= startedAt) return { analysis: previous, shared: true };
|
|
3339
3516
|
const originalDigest = await baselineDigest(ws, before);
|
|
3340
|
-
const computed = await computeAnalysis(ws, before, inputs, event);
|
|
3341
|
-
await verifyInputs(ws, inputs);
|
|
3517
|
+
const computed = await computeAnalysis(ws, before, inputs, event, previous?.currentAudit, diagnostic);
|
|
3342
3518
|
if (originalDigest !== await baselineDigest(ws, before)) throw new Error("Repair baseline was modified during automation; original evidence must be inspected.");
|
|
3343
3519
|
const id = randomUUID4();
|
|
3344
3520
|
const analysis = analysisSchema.parse({
|
|
@@ -3346,7 +3522,8 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
3346
3522
|
fingerprint: inputs.fingerprint,
|
|
3347
3523
|
completedAt: Date.now(),
|
|
3348
3524
|
baselineDigest: await baselineDigest(ws, { ...before, baselines: computed.baselines }),
|
|
3349
|
-
report: computed.report
|
|
3525
|
+
report: computed.report,
|
|
3526
|
+
currentAudit: computed.currentAudit
|
|
3350
3527
|
});
|
|
3351
3528
|
const analysisPath = ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json";
|
|
3352
3529
|
await withLock(ws.root, ws.directory, async () => {
|
|
@@ -3365,17 +3542,17 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
3365
3542
|
}
|
|
3366
3543
|
async function automate(dir, event) {
|
|
3367
3544
|
const startedAt = Date.now();
|
|
3368
|
-
const ws = await workspace(dir);
|
|
3369
|
-
return recordExecution(ws.root, ws.directory, event.event, async () => {
|
|
3545
|
+
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
3546
|
+
return recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
|
|
3370
3547
|
const inputs = await readInputs(ws.root);
|
|
3371
|
-
const result = await analyze(ws, inputs, event, startedAt);
|
|
3548
|
+
const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
|
|
3372
3549
|
if (result.shared) {
|
|
3373
3550
|
await verifyInputs(ws, inputs);
|
|
3374
3551
|
if (result.analysis.baselineDigest !== await baselineDigest(ws, await loadState(ws))) {
|
|
3375
3552
|
throw new Error("Repair baseline was modified during shared automation; original evidence must be inspected.");
|
|
3376
3553
|
}
|
|
3377
3554
|
}
|
|
3378
|
-
return withLock(ws.root, ws.directory, async () => {
|
|
3555
|
+
return profilePhase("automation.publish", () => withLock(ws.root, ws.directory, async () => {
|
|
3379
3556
|
const state = await loadState(ws);
|
|
3380
3557
|
if (state.analysis?.id !== result.analysis.id) throw new Error("Automation evidence changed during publication; retry against the current inputs.");
|
|
3381
3558
|
const report = structuredClone(result.analysis.report);
|
|
@@ -3438,11 +3615,11 @@ async function automate(dir, event) {
|
|
|
3438
3615
|
if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
|
|
3439
3616
|
await writeStoreJson(ws.root, ws.directory + "/state.json", state);
|
|
3440
3617
|
return { report, message: notify ? summarize(report) : null, continueOnce };
|
|
3441
|
-
});
|
|
3442
|
-
});
|
|
3618
|
+
}));
|
|
3619
|
+
}));
|
|
3443
3620
|
}
|
|
3444
3621
|
async function automationStatus(dir) {
|
|
3445
|
-
const ws = await workspace(dir);
|
|
3622
|
+
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
3446
3623
|
const execution = await executionStatus(ws.root, ws.directory);
|
|
3447
3624
|
const notifications = await failureNotifications(ws.root, ws.directory);
|
|
3448
3625
|
const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
|
|
@@ -3476,6 +3653,8 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
|
|
|
3476
3653
|
var init_runtime = __esm({
|
|
3477
3654
|
"src/automation/runtime.ts"() {
|
|
3478
3655
|
"use strict";
|
|
3656
|
+
init_profile();
|
|
3657
|
+
init_inspection();
|
|
3479
3658
|
init_execution();
|
|
3480
3659
|
init_repair();
|
|
3481
3660
|
init_types();
|
|
@@ -3490,6 +3669,7 @@ var init_runtime = __esm({
|
|
|
3490
3669
|
fingerprint: z10.string(),
|
|
3491
3670
|
completedAt: z10.number(),
|
|
3492
3671
|
baselineDigest: z10.string(),
|
|
3672
|
+
currentAudit: reportSchema.nullable().optional(),
|
|
3493
3673
|
report: z10.object({
|
|
3494
3674
|
version: z10.literal(1),
|
|
3495
3675
|
status: z10.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
|
|
@@ -3503,7 +3683,8 @@ var init_runtime = __esm({
|
|
|
3503
3683
|
checks: z10.object({
|
|
3504
3684
|
ran: z10.array(z10.string()),
|
|
3505
3685
|
reused: z10.array(z10.string()),
|
|
3506
|
-
skipped: z10.array(z10.object({ check: z10.string(), reason: z10.string(), doc: z10.string().optional() }))
|
|
3686
|
+
skipped: z10.array(z10.object({ check: z10.string(), reason: z10.string(), doc: z10.string().optional() })),
|
|
3687
|
+
auditReused: z10.boolean().optional()
|
|
3507
3688
|
}),
|
|
3508
3689
|
counts: z10.object({ resolved: z10.number(), unresolved: z10.number(), "review-required": z10.number(), unverified: z10.number() }),
|
|
3509
3690
|
capture: z10.enum(["observed", "unknown"]),
|
|
@@ -3511,17 +3692,17 @@ var init_runtime = __esm({
|
|
|
3511
3692
|
})
|
|
3512
3693
|
});
|
|
3513
3694
|
sharedSchema = z10.object({ analysis: analysisSchema, digest: z10.string() });
|
|
3514
|
-
baselineDigest = async (ws, state) => hash(await Promise.all(
|
|
3695
|
+
baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
|
|
3515
3696
|
state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
|
|
3516
|
-
));
|
|
3697
|
+
)));
|
|
3517
3698
|
}
|
|
3518
3699
|
});
|
|
3519
3700
|
|
|
3520
3701
|
// src/automation/hook-root.ts
|
|
3521
3702
|
import fs15 from "fs/promises";
|
|
3522
|
-
import
|
|
3703
|
+
import path22 from "path";
|
|
3523
3704
|
async function repositoryRoot(dir) {
|
|
3524
|
-
if (!
|
|
3705
|
+
if (!path22.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
|
|
3525
3706
|
try {
|
|
3526
3707
|
return await fs15.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
|
|
3527
3708
|
} catch (error) {
|
|
@@ -3605,8 +3786,8 @@ __export(launcher_exports, {
|
|
|
3605
3786
|
mcpCommand: () => mcpCommand,
|
|
3606
3787
|
prepareLaunch: () => prepareLaunch
|
|
3607
3788
|
});
|
|
3608
|
-
import { execFile as
|
|
3609
|
-
import { promisify as
|
|
3789
|
+
import { execFile as execFile2 } from "child_process";
|
|
3790
|
+
import { promisify as promisify2 } from "util";
|
|
3610
3791
|
import fs16 from "fs/promises";
|
|
3611
3792
|
function globalCommand(args, platform = process.platform) {
|
|
3612
3793
|
return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args] } : { command: "mason", args };
|
|
@@ -3614,7 +3795,7 @@ function globalCommand(args, platform = process.platform) {
|
|
|
3614
3795
|
async function installedCommand() {
|
|
3615
3796
|
const invocation = globalCommand(["internal-integration-version"]);
|
|
3616
3797
|
try {
|
|
3617
|
-
const { stdout } = await
|
|
3798
|
+
const { stdout } = await promisify2(execFile2)(
|
|
3618
3799
|
invocation.command,
|
|
3619
3800
|
invocation.args,
|
|
3620
3801
|
{ timeout: 5e3, maxBuffer: 8192, windowsHide: true }
|
|
@@ -3650,7 +3831,7 @@ var init_launcher = __esm({
|
|
|
3650
3831
|
"use strict";
|
|
3651
3832
|
init_evidence();
|
|
3652
3833
|
init_model();
|
|
3653
|
-
executingVersion = () => true ? "0.17.
|
|
3834
|
+
executingVersion = () => true ? "0.17.4" : "development";
|
|
3654
3835
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
3655
3836
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
3656
3837
|
}
|
|
@@ -3866,7 +4047,7 @@ __export(files_exports, {
|
|
|
3866
4047
|
readText: () => readText
|
|
3867
4048
|
});
|
|
3868
4049
|
import fs18 from "fs/promises";
|
|
3869
|
-
import
|
|
4050
|
+
import path23 from "path";
|
|
3870
4051
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
3871
4052
|
async function readText(root, file) {
|
|
3872
4053
|
try {
|
|
@@ -3899,7 +4080,7 @@ async function applyEdit(root, edit) {
|
|
|
3899
4080
|
return true;
|
|
3900
4081
|
}
|
|
3901
4082
|
const file = await storePath(root, edit.path, true);
|
|
3902
|
-
const temporary =
|
|
4083
|
+
const temporary = path23.join(path23.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
3903
4084
|
try {
|
|
3904
4085
|
const mode = await fs18.stat(file).then((s) => s.mode & 511, () => 384);
|
|
3905
4086
|
const handle = await fs18.open(temporary, "wx", mode);
|
|
@@ -4344,14 +4525,14 @@ async function planTeardown(root, selected) {
|
|
|
4344
4525
|
const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
|
|
4345
4526
|
const automationBefore = await read(AUTOMATION_PATH);
|
|
4346
4527
|
const automation = automationBefore === null ? null : recordSchema2.parse(JSON.parse(automationBefore));
|
|
4347
|
-
const
|
|
4528
|
+
const commands2 = (host) => new Set([
|
|
4348
4529
|
automation?.hosts[host]?.command,
|
|
4349
4530
|
...ownership.files[configPath(host)]?.hookCommands ?? [],
|
|
4350
4531
|
...knownManagedHookCommands(host),
|
|
4351
4532
|
hookConfig(host, hookCommand(host)).hooks.SessionStart[0].hooks[0].command,
|
|
4352
4533
|
hookConfig(host).hooks.SessionStart[0].hooks[0].command
|
|
4353
4534
|
].filter((command) => !!command));
|
|
4354
|
-
const possibleHook = (command, host) =>
|
|
4535
|
+
const possibleHook = (command, host) => commands2(host).has(command) || /\bmason(?:-auto|-hook)?\b/.test(command);
|
|
4355
4536
|
const add = (file, before, after, empty) => {
|
|
4356
4537
|
edits.push({ path: file, before, after: ownership.files[file]?.created && (empty || ownership.files[file].initialHash === hash(before)) ? null : after });
|
|
4357
4538
|
};
|
|
@@ -4415,12 +4596,12 @@ async function planTeardown(root, selected) {
|
|
|
4415
4596
|
for (const [event, groups] of Object.entries(config.hooks ?? {})) {
|
|
4416
4597
|
const known = ownership.files[hookFile]?.hooks?.[event];
|
|
4417
4598
|
const handlers = groups.flatMap((group) => group.hooks);
|
|
4418
|
-
if (known && !handlers.some((handler) => handler.type === "command" && handler.command &&
|
|
4599
|
+
if (known && !handlers.some((handler) => handler.type === "command" && handler.command && commands2(host).has(handler.command)) && handlers.some((handler) => !known.includes(hash(handler)))) {
|
|
4419
4600
|
diagnostics.push({ path: hookFile, message: "A recorded Mason hook is missing beside a new or edited handler (" + event + "); inspect ownership manually." });
|
|
4420
4601
|
}
|
|
4421
4602
|
const kept = groups.flatMap((group) => {
|
|
4422
4603
|
const handlers2 = group.hooks.filter((handler) => {
|
|
4423
|
-
if (handler.type === "command" && handler.command &&
|
|
4604
|
+
if (handler.type === "command" && handler.command && commands2(host).has(handler.command)) {
|
|
4424
4605
|
changed = true;
|
|
4425
4606
|
return false;
|
|
4426
4607
|
}
|
|
@@ -4552,13 +4733,13 @@ var init_teardown = __esm({
|
|
|
4552
4733
|
});
|
|
4553
4734
|
|
|
4554
4735
|
// src/review/cochange.ts
|
|
4555
|
-
import { execFile as
|
|
4556
|
-
import { promisify as
|
|
4736
|
+
import { execFile as execFile3 } from "child_process";
|
|
4737
|
+
import { promisify as promisify3 } from "util";
|
|
4557
4738
|
async function buildMatrix(resolvedRoot) {
|
|
4558
4739
|
try {
|
|
4559
|
-
const { stdout: shallow } = await
|
|
4740
|
+
const { stdout: shallow } = await exec("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
|
|
4560
4741
|
if (shallow.trim() === "true") return null;
|
|
4561
|
-
const { stdout } = await
|
|
4742
|
+
const { stdout } = await exec(
|
|
4562
4743
|
"git",
|
|
4563
4744
|
["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
|
|
4564
4745
|
{ cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
|
|
@@ -4614,11 +4795,11 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
|
|
|
4614
4795
|
findings.sort((a, b) => b.rate - a.rate || b.sharedCommits - a.sharedCommits);
|
|
4615
4796
|
return findings;
|
|
4616
4797
|
}
|
|
4617
|
-
var
|
|
4798
|
+
var exec, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
|
|
4618
4799
|
var init_cochange = __esm({
|
|
4619
4800
|
"src/review/cochange.ts"() {
|
|
4620
4801
|
"use strict";
|
|
4621
|
-
|
|
4802
|
+
exec = promisify3(execFile3);
|
|
4622
4803
|
HISTORY_COMMITS = 1500;
|
|
4623
4804
|
MIN_FILE_COMMITS = 5;
|
|
4624
4805
|
MIN_SHARED_COMMITS = 4;
|
|
@@ -4926,10 +5107,10 @@ var init_check = __esm({
|
|
|
4926
5107
|
});
|
|
4927
5108
|
|
|
4928
5109
|
// src/review/evidence.ts
|
|
4929
|
-
import
|
|
5110
|
+
import path24 from "path";
|
|
4930
5111
|
import { z as z22 } from "zod";
|
|
4931
5112
|
function relativeArtifact(root, file) {
|
|
4932
|
-
const relative =
|
|
5113
|
+
const relative = path24.isAbsolute(file) ? path24.relative(root, file) : file;
|
|
4933
5114
|
const normalized = normalizeRepoPath(relative);
|
|
4934
5115
|
if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
|
|
4935
5116
|
return normalized;
|
|
@@ -5026,7 +5207,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
5026
5207
|
const report = await readStoreJson(root, file);
|
|
5027
5208
|
if (report === null) throw new Error(`Report artifact is missing: ${file}`);
|
|
5028
5209
|
const sourceRoot = check.sourceRoot ?? root;
|
|
5029
|
-
if (!
|
|
5210
|
+
if (!path24.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
|
|
5030
5211
|
const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
|
|
5031
5212
|
result.outcome = parsed.outcome;
|
|
5032
5213
|
result.counts = parsed.counts;
|
|
@@ -5148,12 +5329,12 @@ var init_evidence2 = __esm({
|
|
|
5148
5329
|
|
|
5149
5330
|
// src/review/review.ts
|
|
5150
5331
|
import fs19 from "fs/promises";
|
|
5151
|
-
import
|
|
5152
|
-
import { execFile as
|
|
5153
|
-
import { promisify as
|
|
5332
|
+
import path25 from "path";
|
|
5333
|
+
import { execFile as execFile4 } from "child_process";
|
|
5334
|
+
import { promisify as promisify4 } from "util";
|
|
5154
5335
|
async function resolveMergeBase(resolvedRoot, base, head) {
|
|
5155
5336
|
try {
|
|
5156
|
-
const { stdout } = await
|
|
5337
|
+
const { stdout } = await exec2("git", ["merge-base", base, head], {
|
|
5157
5338
|
cwd: resolvedRoot
|
|
5158
5339
|
});
|
|
5159
5340
|
return stdout.trim() || null;
|
|
@@ -5164,7 +5345,7 @@ async function resolveMergeBase(resolvedRoot, base, head) {
|
|
|
5164
5345
|
async function defaultBase(resolvedRoot) {
|
|
5165
5346
|
for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
|
|
5166
5347
|
try {
|
|
5167
|
-
await
|
|
5348
|
+
await exec2("git", ["rev-parse", "--verify", "--quiet", ref], {
|
|
5168
5349
|
cwd: resolvedRoot
|
|
5169
5350
|
});
|
|
5170
5351
|
return ref;
|
|
@@ -5177,7 +5358,7 @@ function anchorsTouched(record, changedFiles) {
|
|
|
5177
5358
|
return matchingPaths(record.files, changedFiles);
|
|
5178
5359
|
}
|
|
5179
5360
|
async function computeReview(rootDir, base, options = {}) {
|
|
5180
|
-
const resolvedRoot =
|
|
5361
|
+
const resolvedRoot = path25.resolve(rootDir);
|
|
5181
5362
|
const head = await getCurrentGitHash(resolvedRoot);
|
|
5182
5363
|
if (head === "unknown") return null;
|
|
5183
5364
|
const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
|
|
@@ -5227,7 +5408,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5227
5408
|
analyzed,
|
|
5228
5409
|
async (relPath) => {
|
|
5229
5410
|
try {
|
|
5230
|
-
await fs19.access(
|
|
5411
|
+
await fs19.access(path25.join(resolvedRoot, relPath));
|
|
5231
5412
|
return true;
|
|
5232
5413
|
} catch {
|
|
5233
5414
|
return false;
|
|
@@ -5260,7 +5441,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5260
5441
|
}
|
|
5261
5442
|
return finalize();
|
|
5262
5443
|
}
|
|
5263
|
-
var
|
|
5444
|
+
var exec2, MAX_ANALYZED_FILES;
|
|
5264
5445
|
var init_review = __esm({
|
|
5265
5446
|
"src/review/review.ts"() {
|
|
5266
5447
|
"use strict";
|
|
@@ -5272,7 +5453,7 @@ var init_review = __esm({
|
|
|
5272
5453
|
init_cochange();
|
|
5273
5454
|
init_evidence2();
|
|
5274
5455
|
init_snapshot();
|
|
5275
|
-
|
|
5456
|
+
exec2 = promisify4(execFile4);
|
|
5276
5457
|
MAX_ANALYZED_FILES = 50;
|
|
5277
5458
|
}
|
|
5278
5459
|
});
|
|
@@ -5663,6 +5844,7 @@ function createProgress(options = {}) {
|
|
|
5663
5844
|
}
|
|
5664
5845
|
|
|
5665
5846
|
// src/automation/cli.ts
|
|
5847
|
+
init_profile();
|
|
5666
5848
|
var TEARDOWN_USAGE = `Usage: mason teardown [options]
|
|
5667
5849
|
|
|
5668
5850
|
Disconnect Mason from this Git project. Defaults to all project hosts.
|
|
@@ -5690,6 +5872,7 @@ var USAGE = `Usage: mason-auto <setup|teardown|install|config|status|check|hook>
|
|
|
5690
5872
|
--command <prefix> Installed executable prefix for install/config
|
|
5691
5873
|
--dry-run Preview teardown without writing
|
|
5692
5874
|
--json Machine-readable output (status also uses JSON when piped)
|
|
5875
|
+
--profile Local phase timings on stderr for check/hook; no file contents
|
|
5693
5876
|
|
|
5694
5877
|
check exits 0 for verified checks, 1 for issues, 2 for incomplete/unavailable.
|
|
5695
5878
|
teardown exits 0 for complete/no-op, 2 when cleanup needs attention (also in dry runs).
|
|
@@ -5701,7 +5884,8 @@ var parseCli = (argv2) => parseArgs({ args: argv2, allowPositionals: true, optio
|
|
|
5701
5884
|
command: { type: "string" },
|
|
5702
5885
|
json: { type: "boolean" },
|
|
5703
5886
|
help: { type: "boolean", short: "h" },
|
|
5704
|
-
"dry-run": { type: "boolean" }
|
|
5887
|
+
"dry-run": { type: "boolean" },
|
|
5888
|
+
profile: { type: "boolean" }
|
|
5705
5889
|
} });
|
|
5706
5890
|
function isHookCommand(argv2) {
|
|
5707
5891
|
try {
|
|
@@ -5726,6 +5910,8 @@ async function runAutomationCli(argv2, stdin = "", io = defaultIo, options = {})
|
|
|
5726
5910
|
}
|
|
5727
5911
|
if (positionals.length !== 1) throw new Error("Expected one command.");
|
|
5728
5912
|
[action] = positionals;
|
|
5913
|
+
if (values.profile && !["check", "hook"].includes(action)) throw new Error("--profile applies only to check/hook.");
|
|
5914
|
+
const measured = (run) => values.profile ? withProfile(run, io.err) : run();
|
|
5729
5915
|
const dir = values.dir ?? process.cwd();
|
|
5730
5916
|
if (values["dry-run"] !== void 0 && action !== "teardown") throw new Error("--dry-run applies only to teardown.");
|
|
5731
5917
|
if (action === "teardown") {
|
|
@@ -5738,7 +5924,7 @@ async function runAutomationCli(argv2, stdin = "", io = defaultIo, options = {})
|
|
|
5738
5924
|
if (action === "hook") {
|
|
5739
5925
|
const host = hostSchema.parse(values.host);
|
|
5740
5926
|
if (options.managedHost && options.managedHost !== host) throw new Error("Managed hook --host does not match its launcher.");
|
|
5741
|
-
const output = await runAutomationHook(host, stdin, { managed: !!options.managedHost });
|
|
5927
|
+
const output = await measured(() => runAutomationHook(host, stdin, { managed: !!options.managedHost }));
|
|
5742
5928
|
if (output) io.out(JSON.stringify(output));
|
|
5743
5929
|
return 0;
|
|
5744
5930
|
}
|
|
@@ -5769,7 +5955,7 @@ async function runAutomationCli(argv2, stdin = "", io = defaultIo, options = {})
|
|
|
5769
5955
|
return 0;
|
|
5770
5956
|
}
|
|
5771
5957
|
if (action !== "check") throw new Error("Unknown automation command: " + action);
|
|
5772
|
-
const { report } = await automate(dir, { event: "task_end" });
|
|
5958
|
+
const { report } = await measured(() => automate(dir, { event: "task_end" }));
|
|
5773
5959
|
io.out(values.json ? JSON.stringify(report, null, 2) : summarize(report));
|
|
5774
5960
|
return report.status === "verified" ? 0 : report.status === "issues-remain" ? 1 : 2;
|
|
5775
5961
|
} catch (error) {
|