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-audit.js
CHANGED
|
@@ -9,6 +9,54 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/utils/profile.ts
|
|
13
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
14
|
+
async function profilePhase(label, run) {
|
|
15
|
+
const phases = profiles.getStore();
|
|
16
|
+
if (!phases) return run();
|
|
17
|
+
const start = performance.now();
|
|
18
|
+
try {
|
|
19
|
+
return await run();
|
|
20
|
+
} finally {
|
|
21
|
+
const timing = phases[label] ??= { calls: 0, totalMs: 0 };
|
|
22
|
+
timing.calls++;
|
|
23
|
+
timing.totalMs += performance.now() - start;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
var profiles;
|
|
27
|
+
var init_profile = __esm({
|
|
28
|
+
"src/utils/profile.ts"() {
|
|
29
|
+
"use strict";
|
|
30
|
+
profiles = new AsyncLocalStorage();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// src/utils/git-read.ts
|
|
35
|
+
import { execFile } from "child_process";
|
|
36
|
+
import { promisify } from "util";
|
|
37
|
+
function execGit(args, options) {
|
|
38
|
+
const command = args.find((arg) => !arg.startsWith("-")) ?? "other";
|
|
39
|
+
return profilePhase("git." + (commands.has(command) ? command : "other"), async () => {
|
|
40
|
+
const { input, ...processOptions } = options;
|
|
41
|
+
const operation = execute("git", args, processOptions);
|
|
42
|
+
if (input !== void 0) {
|
|
43
|
+
operation.child.stdin.on("error", () => {
|
|
44
|
+
});
|
|
45
|
+
operation.child.stdin.end(input);
|
|
46
|
+
}
|
|
47
|
+
return operation;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
var execute, commands;
|
|
51
|
+
var init_git_read = __esm({
|
|
52
|
+
"src/utils/git-read.ts"() {
|
|
53
|
+
"use strict";
|
|
54
|
+
init_profile();
|
|
55
|
+
execute = promisify(execFile);
|
|
56
|
+
commands = /* @__PURE__ */ new Set(["rev-parse", "symbolic-ref", "for-each-ref", "ls-files", "check-ignore", "status", "log", "diff", "ls-tree", "show", "merge-base"]);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
12
60
|
// src/utils/paths.ts
|
|
13
61
|
import path from "path";
|
|
14
62
|
function normalizeRepoPath(value) {
|
|
@@ -40,8 +88,6 @@ var init_paths = __esm({
|
|
|
40
88
|
import fs from "fs/promises";
|
|
41
89
|
import { constants } from "fs";
|
|
42
90
|
import path2 from "path";
|
|
43
|
-
import { execFile } from "child_process";
|
|
44
|
-
import { promisify } from "util";
|
|
45
91
|
import fg from "fast-glob";
|
|
46
92
|
async function readBoundedFile(file, maxBytes) {
|
|
47
93
|
const handle = await fs.open(file, constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW);
|
|
@@ -83,12 +129,12 @@ async function loadProjectConfig(root) {
|
|
|
83
129
|
throw new Error(`Cannot apply project file policy: ${error instanceof Error ? error.message : String(error)}`);
|
|
84
130
|
}
|
|
85
131
|
}
|
|
86
|
-
var
|
|
132
|
+
var SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
|
|
87
133
|
var init_files = __esm({
|
|
88
134
|
"src/utils/files.ts"() {
|
|
89
135
|
"use strict";
|
|
136
|
+
init_git_read();
|
|
90
137
|
init_paths();
|
|
91
|
-
exec = promisify(execFile);
|
|
92
138
|
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"];
|
|
93
139
|
SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(",")}}`;
|
|
94
140
|
SOURCE_IGNORE = [
|
|
@@ -199,12 +245,10 @@ var init_test_map = __esm({
|
|
|
199
245
|
|
|
200
246
|
// src/snapshot/snapshot.ts
|
|
201
247
|
import path5 from "path";
|
|
202
|
-
import { execFile as execFile2 } from "child_process";
|
|
203
|
-
import { promisify as promisify2 } from "util";
|
|
204
248
|
import { z } from "zod";
|
|
205
249
|
async function getCurrentGitHash(rootDir) {
|
|
206
250
|
try {
|
|
207
|
-
const { stdout } = await
|
|
251
|
+
const { stdout } = await execGit(["rev-parse", "HEAD"], {
|
|
208
252
|
cwd: rootDir
|
|
209
253
|
});
|
|
210
254
|
return stdout.trim();
|
|
@@ -212,16 +256,16 @@ async function getCurrentGitHash(rootDir) {
|
|
|
212
256
|
return "unknown";
|
|
213
257
|
}
|
|
214
258
|
}
|
|
215
|
-
var
|
|
259
|
+
var repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
|
|
216
260
|
var init_snapshot = __esm({
|
|
217
261
|
"src/snapshot/snapshot.ts"() {
|
|
218
262
|
"use strict";
|
|
263
|
+
init_git_read();
|
|
219
264
|
init_files();
|
|
220
265
|
init_files();
|
|
221
266
|
init_storage();
|
|
222
267
|
init_paths();
|
|
223
268
|
init_test_map();
|
|
224
|
-
exec2 = promisify2(execFile2);
|
|
225
269
|
repoPath = z.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
|
|
226
270
|
verificationFields = {
|
|
227
271
|
refreshedHash: z.string().optional(),
|
|
@@ -252,8 +296,6 @@ var init_snapshot = __esm({
|
|
|
252
296
|
// src/drift/drift.ts
|
|
253
297
|
import fs3 from "fs/promises";
|
|
254
298
|
import path6 from "path";
|
|
255
|
-
import { execFile as execFile3 } from "child_process";
|
|
256
|
-
import { promisify as promisify3 } from "util";
|
|
257
299
|
function parseChanges(output) {
|
|
258
300
|
const fields = output.split("\0");
|
|
259
301
|
const changes = [];
|
|
@@ -274,7 +316,7 @@ function touchedPaths(changes) {
|
|
|
274
316
|
async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
|
|
275
317
|
if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-") || !toHash || toHash === "unknown" || toHash.startsWith("-")) return null;
|
|
276
318
|
try {
|
|
277
|
-
const { stdout } = await
|
|
319
|
+
const { stdout } = await execGit(["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
|
|
278
320
|
return parseChanges(stdout);
|
|
279
321
|
} catch {
|
|
280
322
|
return null;
|
|
@@ -283,8 +325,8 @@ async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
|
|
|
283
325
|
async function getWorkingTree(resolvedRoot) {
|
|
284
326
|
try {
|
|
285
327
|
const [diff, untracked] = await Promise.all([
|
|
286
|
-
|
|
287
|
-
|
|
328
|
+
execGit(["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
|
|
329
|
+
execGit(["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
|
|
288
330
|
]);
|
|
289
331
|
const untrackedFiles = untracked.stdout.split("\0").filter((f) => f && !f.startsWith(".mason/"));
|
|
290
332
|
return { available: true, changedFiles: [.../* @__PURE__ */ new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles])].sort(), untrackedFiles };
|
|
@@ -292,13 +334,12 @@ async function getWorkingTree(resolvedRoot) {
|
|
|
292
334
|
return { available: false, changedFiles: [], untrackedFiles: [] };
|
|
293
335
|
}
|
|
294
336
|
}
|
|
295
|
-
var exec3;
|
|
296
337
|
var init_drift = __esm({
|
|
297
338
|
"src/drift/drift.ts"() {
|
|
298
339
|
"use strict";
|
|
340
|
+
init_git_read();
|
|
299
341
|
init_snapshot();
|
|
300
342
|
init_paths();
|
|
301
|
-
exec3 = promisify3(execFile3);
|
|
302
343
|
}
|
|
303
344
|
});
|
|
304
345
|
|
|
@@ -526,14 +567,14 @@ function extractClaims(content) {
|
|
|
526
567
|
const ignored = computeIgnoredLines(lines);
|
|
527
568
|
const paths = /* @__PURE__ */ new Map();
|
|
528
569
|
const counts = [];
|
|
529
|
-
const
|
|
570
|
+
const commands2 = /* @__PURE__ */ new Map();
|
|
530
571
|
const addPath = (claim) => {
|
|
531
572
|
const key = [claim.relativeTo ?? "scope", claim.path].join(":");
|
|
532
573
|
if (!paths.has(key)) paths.set(key, claim);
|
|
533
574
|
};
|
|
534
575
|
const addCommand = (claim) => {
|
|
535
576
|
const key = JSON.stringify([claim.scriptName, claim.directory, claim.scopeUnknown]);
|
|
536
|
-
if (!
|
|
577
|
+
if (!commands2.has(key)) commands2.set(key, claim);
|
|
537
578
|
};
|
|
538
579
|
const parsed = markdown.parse(lines.map((line, i) => ignored[i] ? "" : line).join("\n"), {});
|
|
539
580
|
for (const token of parsed) {
|
|
@@ -635,7 +676,7 @@ function extractClaims(content) {
|
|
|
635
676
|
return {
|
|
636
677
|
paths: [...paths.values()],
|
|
637
678
|
counts,
|
|
638
|
-
commands: [...
|
|
679
|
+
commands: [...commands2.values()]
|
|
639
680
|
};
|
|
640
681
|
}
|
|
641
682
|
var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
|
|
@@ -685,9 +726,50 @@ var init_claims = __esm({
|
|
|
685
726
|
}
|
|
686
727
|
});
|
|
687
728
|
|
|
729
|
+
// src/audit/inspection.ts
|
|
730
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
731
|
+
import path8 from "path";
|
|
732
|
+
function inspectionRead(root, key, read) {
|
|
733
|
+
const inspection = inspections.getStore();
|
|
734
|
+
if (!inspection || inspection.root !== path8.resolve(root)) return read();
|
|
735
|
+
if (!inspection.reads.has(key)) {
|
|
736
|
+
const pending = read();
|
|
737
|
+
inspection.reads.set(key, pending);
|
|
738
|
+
void pending.catch(() => {
|
|
739
|
+
if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
return inspection.reads.get(key);
|
|
743
|
+
}
|
|
744
|
+
function inspectionGit(args, options) {
|
|
745
|
+
return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
|
|
746
|
+
}
|
|
747
|
+
async function auditGitPaths(root, kind) {
|
|
748
|
+
const shared = inspections.getStore()?.root === path8.resolve(root);
|
|
749
|
+
const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
|
|
750
|
+
const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
|
|
751
|
+
const { stdout } = await inspectionGit(
|
|
752
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
|
|
753
|
+
{ cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
|
|
754
|
+
);
|
|
755
|
+
const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? /^(readme|agents|claude)\.md$/i.test(path8.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
|
|
756
|
+
if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
|
|
757
|
+
return paths.sort();
|
|
758
|
+
}
|
|
759
|
+
var inspections, sourceSpecs, docSpecs, sourceSuffixes;
|
|
760
|
+
var init_inspection = __esm({
|
|
761
|
+
"src/audit/inspection.ts"() {
|
|
762
|
+
"use strict";
|
|
763
|
+
init_git_read();
|
|
764
|
+
init_files();
|
|
765
|
+
inspections = new AsyncLocalStorage2();
|
|
766
|
+
sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
|
|
767
|
+
docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
|
|
768
|
+
sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
|
|
688
772
|
// src/audit/git.ts
|
|
689
|
-
import { execFile as execFile4 } from "child_process";
|
|
690
|
-
import { promisify as promisify4 } from "util";
|
|
691
773
|
function parseCommitLine(line) {
|
|
692
774
|
const parts = line.split(" ");
|
|
693
775
|
if (parts.length < 3 || !parts[0]) return null;
|
|
@@ -695,8 +777,7 @@ function parseCommitLine(line) {
|
|
|
695
777
|
}
|
|
696
778
|
async function lastCommitOf(resolvedRoot, relPath) {
|
|
697
779
|
try {
|
|
698
|
-
const { stdout } = await
|
|
699
|
-
"git",
|
|
780
|
+
const { stdout } = await inspectionGit(
|
|
700
781
|
["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
701
782
|
{ cwd: resolvedRoot }
|
|
702
783
|
);
|
|
@@ -708,8 +789,7 @@ async function lastCommitOf(resolvedRoot, relPath) {
|
|
|
708
789
|
}
|
|
709
790
|
async function deletingCommitOf(resolvedRoot, relPath) {
|
|
710
791
|
try {
|
|
711
|
-
const { stdout } = await
|
|
712
|
-
"git",
|
|
792
|
+
const { stdout } = await inspectionGit(
|
|
713
793
|
[
|
|
714
794
|
"log",
|
|
715
795
|
"-1",
|
|
@@ -728,8 +808,7 @@ async function deletingCommitOf(resolvedRoot, relPath) {
|
|
|
728
808
|
}
|
|
729
809
|
async function firstCommitOf(resolvedRoot, relPath) {
|
|
730
810
|
try {
|
|
731
|
-
const { stdout } = await
|
|
732
|
-
"git",
|
|
811
|
+
const { stdout } = await inspectionGit(
|
|
733
812
|
["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
734
813
|
{ cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
|
|
735
814
|
);
|
|
@@ -742,8 +821,7 @@ async function firstCommitOf(resolvedRoot, relPath) {
|
|
|
742
821
|
async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
|
|
743
822
|
if (!fromHash || fromHash === "unknown") return null;
|
|
744
823
|
try {
|
|
745
|
-
const { stdout } = await
|
|
746
|
-
"git",
|
|
824
|
+
const { stdout } = await inspectionGit(
|
|
747
825
|
[
|
|
748
826
|
"log",
|
|
749
827
|
`${fromHash}..HEAD`,
|
|
@@ -767,11 +845,11 @@ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
|
|
|
767
845
|
return null;
|
|
768
846
|
}
|
|
769
847
|
}
|
|
770
|
-
var
|
|
848
|
+
var COMMIT_FORMAT;
|
|
771
849
|
var init_git = __esm({
|
|
772
850
|
"src/audit/git.ts"() {
|
|
773
851
|
"use strict";
|
|
774
|
-
|
|
852
|
+
init_inspection();
|
|
775
853
|
COMMIT_FORMAT = "%H%x09%cI%x09%s";
|
|
776
854
|
}
|
|
777
855
|
});
|
|
@@ -779,9 +857,7 @@ var init_git = __esm({
|
|
|
779
857
|
// src/audit/inputs.ts
|
|
780
858
|
import fs4 from "fs/promises";
|
|
781
859
|
import * as nativeFs from "fs";
|
|
782
|
-
import
|
|
783
|
-
import { execFile as execFile5 } from "child_process";
|
|
784
|
-
import { promisify as promisify5 } from "util";
|
|
860
|
+
import path9 from "path";
|
|
785
861
|
import fg2 from "fast-glob";
|
|
786
862
|
async function auditInputPath(root, file) {
|
|
787
863
|
if (file === ".") return fs4.realpath(root);
|
|
@@ -808,7 +884,7 @@ async function auditGlob(root, patterns, options) {
|
|
|
808
884
|
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
809
885
|
const directories = /* @__PURE__ */ new Set();
|
|
810
886
|
const readdir2 = ((directory, opts, callback) => {
|
|
811
|
-
const relative =
|
|
887
|
+
const relative = path9.relative(root, directory).split(path9.sep).join("/") || ".";
|
|
812
888
|
directories.add(relative);
|
|
813
889
|
if (directories.size > MAX_INPUTS) {
|
|
814
890
|
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
@@ -819,7 +895,7 @@ async function auditGlob(root, patterns, options) {
|
|
|
819
895
|
}, callback);
|
|
820
896
|
});
|
|
821
897
|
const lstat2 = ((file, callback) => {
|
|
822
|
-
const relative =
|
|
898
|
+
const relative = path9.relative(root, file).split(path9.sep).join("/") || ".";
|
|
823
899
|
auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
|
|
824
900
|
});
|
|
825
901
|
const stream = fg2.stream(patterns, {
|
|
@@ -856,89 +932,71 @@ async function auditGlob(root, patterns, options) {
|
|
|
856
932
|
return matches.sort();
|
|
857
933
|
}
|
|
858
934
|
async function gitSourcePaths(root) {
|
|
859
|
-
|
|
860
|
-
"git",
|
|
861
|
-
[
|
|
862
|
-
"ls-files",
|
|
863
|
-
"-z",
|
|
864
|
-
"--cached",
|
|
865
|
-
"--others",
|
|
866
|
-
"--exclude-standard",
|
|
867
|
-
"--",
|
|
868
|
-
...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
|
|
869
|
-
],
|
|
870
|
-
{ cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
|
|
871
|
-
);
|
|
872
|
-
return [...new Set(stdout.split("\0").filter(Boolean))].sort();
|
|
935
|
+
return auditGitPaths(root, "sources");
|
|
873
936
|
}
|
|
874
937
|
async function gitIgnoredPaths(root, files) {
|
|
875
938
|
if (!files.length) return /* @__PURE__ */ new Set();
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
939
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
940
|
+
for (let offset = 0; offset < files.length; offset += 1e3) {
|
|
941
|
+
try {
|
|
942
|
+
const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
|
|
943
|
+
cwd: root,
|
|
944
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
945
|
+
timeout: 1e4,
|
|
946
|
+
input: files.slice(offset, offset + 1e3).join("\0") + "\0"
|
|
947
|
+
});
|
|
948
|
+
for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
|
|
949
|
+
} catch (error) {
|
|
950
|
+
if (error.code !== 1) throw error;
|
|
951
|
+
}
|
|
889
952
|
}
|
|
953
|
+
return ignored;
|
|
890
954
|
}
|
|
891
|
-
var
|
|
955
|
+
var MAX_INPUTS;
|
|
892
956
|
var init_inputs = __esm({
|
|
893
957
|
"src/audit/inputs.ts"() {
|
|
894
958
|
"use strict";
|
|
895
959
|
init_files();
|
|
896
960
|
init_storage();
|
|
897
|
-
|
|
961
|
+
init_git_read();
|
|
962
|
+
init_inspection();
|
|
898
963
|
MAX_INPUTS = 1e5;
|
|
899
964
|
}
|
|
900
965
|
});
|
|
901
966
|
|
|
902
967
|
// src/audit/docs.ts
|
|
903
968
|
import fs5 from "fs/promises";
|
|
904
|
-
import
|
|
905
|
-
import { execFile as execFile6 } from "child_process";
|
|
906
|
-
import { promisify as promisify6 } from "util";
|
|
969
|
+
import path10 from "path";
|
|
907
970
|
import fg3 from "fast-glob";
|
|
908
|
-
async function
|
|
971
|
+
async function dirtyDocs(resolvedRoot, files) {
|
|
972
|
+
if (!files.length) return /* @__PURE__ */ new Set();
|
|
909
973
|
try {
|
|
910
|
-
const
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
974
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
975
|
+
for (let offset = 0; offset < files.length; offset += 256) {
|
|
976
|
+
const { stdout } = await execGit(
|
|
977
|
+
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
|
|
978
|
+
{ cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
979
|
+
);
|
|
980
|
+
const entries = stdout.split("\0");
|
|
981
|
+
for (let i = 0; i < entries.length; i++) {
|
|
982
|
+
if (!entries[i]) continue;
|
|
983
|
+
const status = entries[i].slice(0, 2);
|
|
984
|
+
dirty.add(entries[i].slice(3));
|
|
985
|
+
if (/[RC]/.test(status) && entries[i + 1]) dirty.add(entries[++i]);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return dirty;
|
|
916
989
|
} catch {
|
|
917
|
-
return
|
|
990
|
+
return new Set(files);
|
|
918
991
|
}
|
|
919
992
|
}
|
|
920
993
|
async function discoverDocPaths(root) {
|
|
921
994
|
let candidates = [];
|
|
922
995
|
try {
|
|
923
|
-
|
|
924
|
-
"git",
|
|
925
|
-
[
|
|
926
|
-
"ls-files",
|
|
927
|
-
"-z",
|
|
928
|
-
"--cached",
|
|
929
|
-
"--others",
|
|
930
|
-
"--exclude-standard",
|
|
931
|
-
"--",
|
|
932
|
-
":(icase,glob)**/readme.md",
|
|
933
|
-
":(icase,glob)**/agents.md",
|
|
934
|
-
":(icase,glob)**/claude.md"
|
|
935
|
-
],
|
|
936
|
-
{ cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
937
|
-
);
|
|
938
|
-
candidates = stdout.split("\0").filter(Boolean);
|
|
996
|
+
candidates = await auditGitPaths(root, "documents");
|
|
939
997
|
} catch (error) {
|
|
940
998
|
try {
|
|
941
|
-
await
|
|
999
|
+
await execGit(["rev-parse", "--git-dir"], { cwd: root });
|
|
942
1000
|
} catch {
|
|
943
1001
|
return localDocPaths(root);
|
|
944
1002
|
}
|
|
@@ -985,43 +1043,54 @@ async function localDocPaths(root) {
|
|
|
985
1043
|
throw error;
|
|
986
1044
|
}
|
|
987
1045
|
for (const entry of entries) if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
|
|
988
|
-
paths.push(
|
|
1046
|
+
paths.push(path10.posix.join(dir, entry.name));
|
|
989
1047
|
}
|
|
990
1048
|
}
|
|
991
1049
|
return paths;
|
|
992
1050
|
}
|
|
993
1051
|
function documentScope(file) {
|
|
994
|
-
const parent =
|
|
995
|
-
return
|
|
1052
|
+
const parent = path10.posix.dirname(file);
|
|
1053
|
+
return path10.posix.basename(parent).toLowerCase() === ".claude" ? path10.posix.dirname(parent) : parent;
|
|
996
1054
|
}
|
|
997
1055
|
async function discoverDocs(resolvedRoot) {
|
|
1056
|
+
return profilePhase("audit.documents", () => readDocs(resolvedRoot));
|
|
1057
|
+
}
|
|
1058
|
+
async function readDocs(resolvedRoot) {
|
|
998
1059
|
const docs = [];
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1060
|
+
const files = await discoverDocPaths(resolvedRoot);
|
|
1061
|
+
const dirty = await dirtyDocs(resolvedRoot, files);
|
|
1062
|
+
for (let offset = 0; offset < files.length; offset += 4) {
|
|
1063
|
+
docs.push(...await Promise.all(files.slice(offset, offset + 4).map(async (candidate) => {
|
|
1064
|
+
const [content, lastCommit] = await Promise.all([
|
|
1065
|
+
readAuditInput(resolvedRoot, candidate),
|
|
1066
|
+
profilePhase("audit.document-history", () => lastCommitOf(resolvedRoot, candidate))
|
|
1067
|
+
]);
|
|
1068
|
+
if (content === null) throw new Error("Document disappeared during discovery: " + candidate);
|
|
1069
|
+
return {
|
|
1070
|
+
path: candidate,
|
|
1071
|
+
content,
|
|
1072
|
+
scope: documentScope(candidate),
|
|
1073
|
+
kind: /^readme\.md$/i.test(path10.posix.basename(candidate)) ? "readme" : "instructions",
|
|
1074
|
+
lineCount: content.split("\n").length,
|
|
1075
|
+
lastCommit,
|
|
1076
|
+
dirty: dirty.has(candidate),
|
|
1077
|
+
claims: extractClaims(content)
|
|
1078
|
+
};
|
|
1079
|
+
})));
|
|
1013
1080
|
}
|
|
1014
1081
|
return docs;
|
|
1015
1082
|
}
|
|
1016
|
-
var
|
|
1083
|
+
var DOC_CANDIDATES;
|
|
1017
1084
|
var init_docs = __esm({
|
|
1018
1085
|
"src/audit/docs.ts"() {
|
|
1019
1086
|
"use strict";
|
|
1087
|
+
init_git_read();
|
|
1088
|
+
init_profile();
|
|
1020
1089
|
init_claims();
|
|
1021
1090
|
init_git();
|
|
1022
1091
|
init_inputs();
|
|
1023
1092
|
init_files();
|
|
1024
|
-
|
|
1093
|
+
init_inspection();
|
|
1025
1094
|
DOC_CANDIDATES = [
|
|
1026
1095
|
"AGENTS.md",
|
|
1027
1096
|
"CLAUDE.md",
|
|
@@ -1048,14 +1117,14 @@ var init_types = __esm({
|
|
|
1048
1117
|
|
|
1049
1118
|
// src/audit/scope.ts
|
|
1050
1119
|
import fs6 from "fs/promises";
|
|
1051
|
-
import
|
|
1120
|
+
import path11 from "path";
|
|
1052
1121
|
function scopedPath(directory, value) {
|
|
1053
|
-
if (
|
|
1054
|
-
const resolved =
|
|
1122
|
+
if (path11.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
|
|
1123
|
+
const resolved = path11.posix.normalize(path11.posix.join(directory, value));
|
|
1055
1124
|
return resolved === ".." || resolved.startsWith("../") ? null : resolved;
|
|
1056
1125
|
}
|
|
1057
1126
|
function pathClaimScope(doc, claim) {
|
|
1058
|
-
const directory = claim.relativeTo === "document" ?
|
|
1127
|
+
const directory = claim.relativeTo === "document" ? path11.posix.dirname(doc) : documentScope(doc);
|
|
1059
1128
|
const local = scopedPath(directory, claim.path);
|
|
1060
1129
|
if (!local) return null;
|
|
1061
1130
|
const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
|
|
@@ -1089,7 +1158,7 @@ var init_scope = __esm({
|
|
|
1089
1158
|
});
|
|
1090
1159
|
|
|
1091
1160
|
// src/audit/checks/deleted-reference.ts
|
|
1092
|
-
import
|
|
1161
|
+
import path12 from "path";
|
|
1093
1162
|
async function checkDeletedReferences(ctx) {
|
|
1094
1163
|
const result = emptyResult();
|
|
1095
1164
|
for (const doc of ctx.docs) {
|
|
@@ -1118,7 +1187,7 @@ async function checkDeletedReferences(ctx) {
|
|
|
1118
1187
|
const candidates = await Promise.all(scope.candidates.map(async (file) => ({
|
|
1119
1188
|
file,
|
|
1120
1189
|
tracked: await lastCommitOf(ctx.root, file),
|
|
1121
|
-
parent: await pathExists(ctx.root,
|
|
1190
|
+
parent: await pathExists(ctx.root, path12.posix.dirname(file))
|
|
1122
1191
|
})));
|
|
1123
1192
|
const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
|
|
1124
1193
|
if (!candidate && claim.relativeTo !== "document") continue;
|
|
@@ -1158,7 +1227,7 @@ var init_deleted_reference = __esm({
|
|
|
1158
1227
|
// src/audit/checks/new-module.ts
|
|
1159
1228
|
import fg4 from "fast-glob";
|
|
1160
1229
|
import fs7 from "fs/promises";
|
|
1161
|
-
import
|
|
1230
|
+
import path13 from "path";
|
|
1162
1231
|
function escapeRegExp(text2) {
|
|
1163
1232
|
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1164
1233
|
}
|
|
@@ -1196,23 +1265,36 @@ async function checkNewModules(ctx) {
|
|
|
1196
1265
|
return result;
|
|
1197
1266
|
}
|
|
1198
1267
|
function moduleDocumentation(docs) {
|
|
1199
|
-
return docs.map((doc) => doc.content + "\n" + (
|
|
1268
|
+
return docs.map((doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")).join("\n");
|
|
1269
|
+
}
|
|
1270
|
+
function moduleCandidates(root, combinedDocs) {
|
|
1271
|
+
return inspectionRead(root, "modules:" + combinedDocs, () => collectModuleCandidates(root, combinedDocs));
|
|
1200
1272
|
}
|
|
1201
|
-
async function
|
|
1273
|
+
async function collectModuleCandidates(root, combinedDocs) {
|
|
1202
1274
|
const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
|
|
1203
1275
|
const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
|
|
1204
1276
|
const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
|
|
1205
|
-
const listSubdirs = async (
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1277
|
+
const listSubdirs = async (parents) => {
|
|
1278
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
1279
|
+
for (let offset = 0; offset < parents.length; offset += 16) {
|
|
1280
|
+
await Promise.all(parents.slice(offset, offset + 16).map(async (dir) => {
|
|
1281
|
+
const entries = await fs7.readdir(await auditInputPath(root, dir), { withFileTypes: true });
|
|
1282
|
+
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));
|
|
1283
|
+
byParent.set(dir, dirs);
|
|
1284
|
+
}));
|
|
1285
|
+
}
|
|
1286
|
+
const ignored = await gitIgnoredPaths(root, [...byParent.values()].flat());
|
|
1287
|
+
const result = /* @__PURE__ */ new Map();
|
|
1288
|
+
for (const dir of parents) {
|
|
1289
|
+
const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
|
|
1290
|
+
result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
|
|
1291
|
+
ignore,
|
|
1292
|
+
onlyDirectories: true,
|
|
1293
|
+
label: "Module directory discovery",
|
|
1294
|
+
select: (file) => visible.has(file)
|
|
1295
|
+
})).map((file) => path13.posix.basename(file)));
|
|
1296
|
+
}
|
|
1297
|
+
return result;
|
|
1216
1298
|
};
|
|
1217
1299
|
const countSourceFiles = async (dir) => {
|
|
1218
1300
|
const files = sources.filter((file) => file.startsWith(dir + "/"));
|
|
@@ -1228,14 +1310,16 @@ async function moduleCandidates(root, combinedDocs) {
|
|
|
1228
1310
|
return count3;
|
|
1229
1311
|
};
|
|
1230
1312
|
const candidates = [];
|
|
1231
|
-
|
|
1313
|
+
const topDirs = (await listSubdirs(["."])).get(".");
|
|
1314
|
+
const nested = await listSubdirs(topDirs.filter((dir) => isMentioned(combinedDocs, dir)));
|
|
1315
|
+
for (const topDir of topDirs) {
|
|
1232
1316
|
const topMentioned = isMentioned(combinedDocs, topDir);
|
|
1233
1317
|
if (!topMentioned) {
|
|
1234
1318
|
const count3 = await countSourceFiles(topDir);
|
|
1235
1319
|
if (count3 >= 1) candidates.push({ dir: topDir, sourceFileCount: count3 });
|
|
1236
1320
|
continue;
|
|
1237
1321
|
}
|
|
1238
|
-
const subdirs =
|
|
1322
|
+
const subdirs = nested.get(topDir);
|
|
1239
1323
|
const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
|
|
1240
1324
|
if (mentioned.length < ENUMERATION_THRESHOLD) continue;
|
|
1241
1325
|
for (const sub of subdirs) {
|
|
@@ -1252,6 +1336,7 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
|
|
|
1252
1336
|
var init_new_module = __esm({
|
|
1253
1337
|
"src/audit/checks/new-module.ts"() {
|
|
1254
1338
|
"use strict";
|
|
1339
|
+
init_inspection();
|
|
1255
1340
|
init_files();
|
|
1256
1341
|
init_inputs();
|
|
1257
1342
|
init_git();
|
|
@@ -1281,7 +1366,7 @@ var init_new_module = __esm({
|
|
|
1281
1366
|
});
|
|
1282
1367
|
|
|
1283
1368
|
// src/audit/checks/stale-count.ts
|
|
1284
|
-
import
|
|
1369
|
+
import path14 from "path";
|
|
1285
1370
|
async function countGradleModules(root) {
|
|
1286
1371
|
for (const name of ["settings.gradle.kts", "settings.gradle"]) {
|
|
1287
1372
|
const content = await readAuditInput(root, name);
|
|
@@ -1315,7 +1400,7 @@ async function countNpmWorkspaces(root) {
|
|
|
1315
1400
|
return {
|
|
1316
1401
|
actual: matched.length,
|
|
1317
1402
|
countedFrom: "package.json workspaces",
|
|
1318
|
-
members: matched.map((m) =>
|
|
1403
|
+
members: matched.map((m) => path14.dirname(m)).sort()
|
|
1319
1404
|
};
|
|
1320
1405
|
}
|
|
1321
1406
|
}
|
|
@@ -1346,7 +1431,7 @@ async function countNpmWorkspaces(root) {
|
|
|
1346
1431
|
return {
|
|
1347
1432
|
actual: matched.length,
|
|
1348
1433
|
countedFrom: "pnpm-workspace.yaml",
|
|
1349
|
-
members: matched.map((m) =>
|
|
1434
|
+
members: matched.map((m) => path14.dirname(m)).sort()
|
|
1350
1435
|
};
|
|
1351
1436
|
}
|
|
1352
1437
|
}
|
|
@@ -1368,8 +1453,8 @@ async function countCargoCrates(root) {
|
|
|
1368
1453
|
ignore: ["**/target/**"],
|
|
1369
1454
|
label: "Cargo workspace discovery"
|
|
1370
1455
|
});
|
|
1371
|
-
for (const m of matched) members.add(
|
|
1372
|
-
} else if (await readAuditInput(root,
|
|
1456
|
+
for (const m of matched) members.add(path14.dirname(m));
|
|
1457
|
+
} else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
|
|
1373
1458
|
members.add(entry);
|
|
1374
1459
|
}
|
|
1375
1460
|
}
|
|
@@ -1389,7 +1474,7 @@ async function resolveCountSource(root, claim) {
|
|
|
1389
1474
|
}
|
|
1390
1475
|
async function resolveDocCountSource(root, doc, claim) {
|
|
1391
1476
|
const scope = documentScope(doc);
|
|
1392
|
-
const source = await resolveCountSource(
|
|
1477
|
+
const source = await resolveCountSource(path14.join(root, scope), claim);
|
|
1393
1478
|
if (!source || scope === ".") return source;
|
|
1394
1479
|
return {
|
|
1395
1480
|
...source,
|
|
@@ -1441,7 +1526,7 @@ var init_stale_count = __esm({
|
|
|
1441
1526
|
});
|
|
1442
1527
|
|
|
1443
1528
|
// src/audit/checks/dead-command.ts
|
|
1444
|
-
import
|
|
1529
|
+
import path15 from "path";
|
|
1445
1530
|
function scriptsOf(raw) {
|
|
1446
1531
|
try {
|
|
1447
1532
|
const pkg = JSON.parse(raw);
|
|
@@ -1477,7 +1562,7 @@ async function commandInputs(root, docs) {
|
|
|
1477
1562
|
}
|
|
1478
1563
|
let cursor = directory;
|
|
1479
1564
|
while (true) {
|
|
1480
|
-
const manifest =
|
|
1565
|
+
const manifest = path15.posix.join(cursor, "package.json");
|
|
1481
1566
|
scope.candidates.push(manifest);
|
|
1482
1567
|
const raw = await read(manifest);
|
|
1483
1568
|
if (raw !== null) {
|
|
@@ -1488,7 +1573,7 @@ async function commandInputs(root, docs) {
|
|
|
1488
1573
|
break;
|
|
1489
1574
|
}
|
|
1490
1575
|
if (claim.directory !== void 0 || cursor === ".") break;
|
|
1491
|
-
cursor =
|
|
1576
|
+
cursor = path15.posix.dirname(cursor);
|
|
1492
1577
|
}
|
|
1493
1578
|
if (!resolution.manifest && !resolution.problem) {
|
|
1494
1579
|
resolution.problem = `No package.json for the command's scope (${directory}).`;
|
|
@@ -1551,10 +1636,8 @@ var init_dead_command = __esm({
|
|
|
1551
1636
|
});
|
|
1552
1637
|
|
|
1553
1638
|
// src/audit/release-metadata.ts
|
|
1554
|
-
import { execFile as execFile7 } from "child_process";
|
|
1555
|
-
import { promisify as promisify7 } from "util";
|
|
1556
1639
|
async function git(root, args) {
|
|
1557
|
-
return (await
|
|
1640
|
+
return (await execGit(args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
|
|
1558
1641
|
}
|
|
1559
1642
|
function withoutAndroidReleaseValues(text2) {
|
|
1560
1643
|
if (/\/\*|"""|'''/.test(text2) || !/id\s*\(?\s*["']com\.android\.(application|library)["']/.test(text2)) return null;
|
|
@@ -1600,11 +1683,10 @@ async function releaseMetadataOnly(root, commit) {
|
|
|
1600
1683
|
return false;
|
|
1601
1684
|
}
|
|
1602
1685
|
}
|
|
1603
|
-
var exec6;
|
|
1604
1686
|
var init_release_metadata = __esm({
|
|
1605
1687
|
"src/audit/release-metadata.ts"() {
|
|
1606
1688
|
"use strict";
|
|
1607
|
-
|
|
1689
|
+
init_git_read();
|
|
1608
1690
|
}
|
|
1609
1691
|
});
|
|
1610
1692
|
|
|
@@ -1700,7 +1782,7 @@ var init_deps_changed = __esm({
|
|
|
1700
1782
|
});
|
|
1701
1783
|
|
|
1702
1784
|
// src/context/lexical.ts
|
|
1703
|
-
import
|
|
1785
|
+
import path16 from "path";
|
|
1704
1786
|
var init_lexical = __esm({
|
|
1705
1787
|
"src/context/lexical.ts"() {
|
|
1706
1788
|
"use strict";
|
|
@@ -1861,7 +1943,7 @@ var init_provenance = __esm({
|
|
|
1861
1943
|
|
|
1862
1944
|
// src/decisions/decisions.ts
|
|
1863
1945
|
import fs8 from "fs/promises";
|
|
1864
|
-
import
|
|
1946
|
+
import path17 from "path";
|
|
1865
1947
|
import { createHash } from "crypto";
|
|
1866
1948
|
import { z as z3 } from "zod";
|
|
1867
1949
|
async function loadDecisionStore(rootDir) {
|
|
@@ -1912,9 +1994,9 @@ var init_decisions = __esm({
|
|
|
1912
1994
|
});
|
|
1913
1995
|
|
|
1914
1996
|
// src/decisions/drift.ts
|
|
1915
|
-
import
|
|
1997
|
+
import path18 from "path";
|
|
1916
1998
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
1917
|
-
const resolvedRoot =
|
|
1999
|
+
const resolvedRoot = path18.resolve(rootDir);
|
|
1918
2000
|
const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
|
|
1919
2001
|
const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
|
|
1920
2002
|
const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
|
|
@@ -2236,10 +2318,12 @@ __export(repair_exports, {
|
|
|
2236
2318
|
prepareRepair: () => prepareRepair,
|
|
2237
2319
|
repairExitCode: () => repairExitCode,
|
|
2238
2320
|
repairFindingSchema: () => repairFindingSchema,
|
|
2239
|
-
|
|
2321
|
+
reportSchema: () => reportSchema,
|
|
2322
|
+
verifyRepair: () => verifyRepair,
|
|
2323
|
+
withRepairSession: () => withRepairSession
|
|
2240
2324
|
});
|
|
2241
2325
|
import fs10 from "fs/promises";
|
|
2242
|
-
import
|
|
2326
|
+
import path19 from "path";
|
|
2243
2327
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2244
2328
|
import { z as z6 } from "zod";
|
|
2245
2329
|
function recheckable(finding) {
|
|
@@ -2263,6 +2347,9 @@ async function docState(root) {
|
|
|
2263
2347
|
return digest(docs);
|
|
2264
2348
|
}
|
|
2265
2349
|
async function stableAudit(root, checks, options = {}) {
|
|
2350
|
+
return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
|
|
2351
|
+
}
|
|
2352
|
+
async function readStableAudit(root, checks, options) {
|
|
2266
2353
|
const head = await getCurrentGitHash(root);
|
|
2267
2354
|
const before = await docState(root);
|
|
2268
2355
|
const report = await computeAudit(root, { ...options, checks });
|
|
@@ -2272,9 +2359,45 @@ async function stableAudit(root, checks, options = {}) {
|
|
|
2272
2359
|
return report;
|
|
2273
2360
|
}
|
|
2274
2361
|
async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
|
|
2362
|
+
return prepareOriginal(rootDir, checks, options);
|
|
2363
|
+
}
|
|
2364
|
+
async function withRepairSession(root, options, run, validate, cachedAudit) {
|
|
2365
|
+
const head = await getCurrentGitHash(root);
|
|
2366
|
+
const audits = /* @__PURE__ */ new Map();
|
|
2367
|
+
const documents = /* @__PURE__ */ new Map();
|
|
2368
|
+
const history = /* @__PURE__ */ new Map();
|
|
2369
|
+
const assessments = /* @__PURE__ */ new Map();
|
|
2370
|
+
const memo = (cache, key, read) => {
|
|
2371
|
+
if (!cache.has(key)) cache.set(key, read());
|
|
2372
|
+
return cache.get(key);
|
|
2373
|
+
};
|
|
2374
|
+
const session = {
|
|
2375
|
+
audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
|
|
2376
|
+
if (cachedAudit && cachedAudit.headHash === head && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
|
|
2377
|
+
const current = structuredClone(cachedAudit);
|
|
2378
|
+
const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, head)))).filter((r) => r !== null);
|
|
2379
|
+
delete current.advisoryReviews;
|
|
2380
|
+
if (reviews.length) current.advisoryReviews = reviews;
|
|
2381
|
+
return current;
|
|
2382
|
+
}
|
|
2383
|
+
return computeAudit(root, { ...options, checks });
|
|
2384
|
+
}),
|
|
2385
|
+
document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
|
|
2386
|
+
history: (head2) => memo(history, head2, () => getChangesWithStatus(root, head2)),
|
|
2387
|
+
assessment: (dir, finding, head2) => memo(assessments, digest([dir, finding, head2]), () => assessAdvisory(dir, finding, head2))
|
|
2388
|
+
};
|
|
2389
|
+
const result = await run({
|
|
2390
|
+
prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
|
|
2391
|
+
verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
|
|
2392
|
+
currentAudit: () => session.audit(ALL_CHECKS)
|
|
2393
|
+
});
|
|
2394
|
+
await validate();
|
|
2395
|
+
return result;
|
|
2396
|
+
}
|
|
2397
|
+
async function prepareOriginal(rootDir, checks, options, session) {
|
|
2275
2398
|
const root = await fs10.realpath(rootDir);
|
|
2276
2399
|
const selected = z6.array(checkSchema).nonempty().parse(checks);
|
|
2277
|
-
const report = await stableAudit(root, selected, options);
|
|
2400
|
+
const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
|
|
2278
2401
|
if (!report) throw new Error("No context files found to prepare a repair.");
|
|
2279
2402
|
if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
|
|
2280
2403
|
const storedReport = reportSchema.parse(report);
|
|
@@ -2290,8 +2413,8 @@ async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
|
|
|
2290
2413
|
}
|
|
2291
2414
|
async function loadRepairBaseline(rootDir, baselinePath) {
|
|
2292
2415
|
const root = await fs10.realpath(rootDir);
|
|
2293
|
-
const declaredRoot =
|
|
2294
|
-
const relative =
|
|
2416
|
+
const declaredRoot = path19.resolve(rootDir);
|
|
2417
|
+
const relative = path19.isAbsolute(baselinePath) ? path19.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
|
|
2295
2418
|
const stored = baselineSchema.parse(await readStoreJson(root, relative));
|
|
2296
2419
|
const { digest: savedDigest, ...payload } = stored;
|
|
2297
2420
|
if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
|
|
@@ -2300,23 +2423,26 @@ async function loadRepairBaseline(rootDir, baselinePath) {
|
|
|
2300
2423
|
return { root, relative, original };
|
|
2301
2424
|
}
|
|
2302
2425
|
async function verifyRepair(rootDir, baselinePath, options = {}) {
|
|
2426
|
+
return profilePhase("repair.verify", () => verifyOriginal(rootDir, baselinePath, options));
|
|
2427
|
+
}
|
|
2428
|
+
async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
2303
2429
|
const { root, relative, original } = await loadRepairBaseline(rootDir, baselinePath);
|
|
2304
2430
|
const diagnostics = [];
|
|
2305
2431
|
let reviewDigest;
|
|
2306
2432
|
let current = null;
|
|
2307
2433
|
try {
|
|
2308
|
-
reviewDigest = digest(await advisoryReviewInventory(root));
|
|
2309
|
-
current = await stableAudit(root, original.checksRun, options);
|
|
2434
|
+
if (!session) reviewDigest = digest(await advisoryReviewInventory(root));
|
|
2435
|
+
current = await (session ? session.audit(original.checksRun) : stableAudit(root, original.checksRun, options));
|
|
2310
2436
|
if (!current) diagnostics.push("No context files remain available to audit.");
|
|
2311
2437
|
else if (!current.gitAvailable) diagnostics.push("Git history is unavailable.");
|
|
2312
2438
|
for (const doc of original.docs) {
|
|
2313
2439
|
if (!allFindings(original).some((f) => f.anchor.doc === doc.path) || !current?.docs.some((d) => d.path === doc.path)) continue;
|
|
2314
|
-
const content = await readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024);
|
|
2440
|
+
const content = await (session ? session.document(doc.path) : readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024));
|
|
2315
2441
|
if (content === null || !content.trim()) {
|
|
2316
2442
|
diagnostics.push("Original context file " + doc.path + " is empty or unreadable; losing its claims does not verify a repair.");
|
|
2317
2443
|
}
|
|
2318
2444
|
}
|
|
2319
|
-
if (await getChangesWithStatus(root, original.headHash) === null) {
|
|
2445
|
+
if (await (session ? session.history(original.headHash) : getChangesWithStatus(root, original.headHash)) === null) {
|
|
2320
2446
|
diagnostics.push("The original audit commit is unavailable; repair history cannot be verified.");
|
|
2321
2447
|
}
|
|
2322
2448
|
} catch (error) {
|
|
@@ -2341,7 +2467,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
|
|
|
2341
2467
|
if (!current.checksRun?.includes(finding.type) || skipped.length) {
|
|
2342
2468
|
return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
|
|
2343
2469
|
}
|
|
2344
|
-
const review = await assessAdvisory(root, now ?? finding, original.headHash);
|
|
2470
|
+
const review = await (session?.assessment ?? assessAdvisory)(root, now ?? finding, original.headHash);
|
|
2345
2471
|
if (review) {
|
|
2346
2472
|
if (review.status === "unverified") return { ...base, review, status: "unverified", reason: review.reason };
|
|
2347
2473
|
if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
|
|
@@ -2356,7 +2482,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
|
|
|
2356
2482
|
}
|
|
2357
2483
|
return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
|
|
2358
2484
|
}));
|
|
2359
|
-
try {
|
|
2485
|
+
if (!session) try {
|
|
2360
2486
|
if (reviewDigest !== digest(await advisoryReviewInventory(root)) || current?.headHash !== await getCurrentGitHash(root)) {
|
|
2361
2487
|
diagnostics.push("HEAD or advisory review records changed during verification; repeat against stable evidence.");
|
|
2362
2488
|
for (const finding of findings) {
|
|
@@ -2409,6 +2535,7 @@ var count2, reportSchema, baselineSchema, repairFindingSchema;
|
|
|
2409
2535
|
var init_repair = __esm({
|
|
2410
2536
|
"src/audit/repair.ts"() {
|
|
2411
2537
|
"use strict";
|
|
2538
|
+
init_profile();
|
|
2412
2539
|
init_audit();
|
|
2413
2540
|
init_docs();
|
|
2414
2541
|
init_snapshot();
|
|
@@ -2462,8 +2589,6 @@ var init_repair = __esm({
|
|
|
2462
2589
|
|
|
2463
2590
|
// src/audit/advisory-review.ts
|
|
2464
2591
|
import fs11 from "fs/promises";
|
|
2465
|
-
import { execFile as execFile8 } from "child_process";
|
|
2466
|
-
import { promisify as promisify8 } from "util";
|
|
2467
2592
|
import { z as z7 } from "zod";
|
|
2468
2593
|
async function readRecord(root, id) {
|
|
2469
2594
|
const raw = await readStoreJson(root, recordPath(id));
|
|
@@ -2658,10 +2783,12 @@ async function reviewAdvisory(rootDir, input) {
|
|
|
2658
2783
|
};
|
|
2659
2784
|
});
|
|
2660
2785
|
}
|
|
2661
|
-
var
|
|
2786
|
+
var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
|
|
2662
2787
|
var init_advisory_review = __esm({
|
|
2663
2788
|
"src/audit/advisory-review.ts"() {
|
|
2664
2789
|
"use strict";
|
|
2790
|
+
init_inspection();
|
|
2791
|
+
init_git_read();
|
|
2665
2792
|
init_findings();
|
|
2666
2793
|
init_deps_changed();
|
|
2667
2794
|
init_inputs();
|
|
@@ -2671,8 +2798,7 @@ var init_advisory_review = __esm({
|
|
|
2671
2798
|
init_store();
|
|
2672
2799
|
init_decisions();
|
|
2673
2800
|
init_drift2();
|
|
2674
|
-
|
|
2675
|
-
git2 = async (root, ...args) => (await execute2("git", args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
2801
|
+
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;
|
|
2676
2802
|
hashSchema = z7.string().regex(/^[a-f0-9]{64}$/);
|
|
2677
2803
|
commitSchema2 = z7.string().regex(/^[a-f0-9]{40,64}$/);
|
|
2678
2804
|
evidenceSchema2 = z7.object({
|
|
@@ -2716,9 +2842,12 @@ var init_advisory_review = __esm({
|
|
|
2716
2842
|
|
|
2717
2843
|
// src/audit/audit.ts
|
|
2718
2844
|
import fs12 from "fs/promises";
|
|
2719
|
-
import
|
|
2845
|
+
import path20 from "path";
|
|
2720
2846
|
async function computeAudit(rootDir, options = {}) {
|
|
2721
|
-
|
|
2847
|
+
return profilePhase("audit.current", () => auditCurrent(rootDir, options));
|
|
2848
|
+
}
|
|
2849
|
+
async function auditCurrent(rootDir, options) {
|
|
2850
|
+
const resolvedRoot = path20.resolve(rootDir);
|
|
2722
2851
|
const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
|
|
2723
2852
|
if (docs.length === 0) return null;
|
|
2724
2853
|
const report = {
|
|
@@ -2744,15 +2873,15 @@ async function computeAudit(rootDir, options = {}) {
|
|
|
2744
2873
|
};
|
|
2745
2874
|
if (!report.gitAvailable) return report;
|
|
2746
2875
|
const changesSinceDoc = /* @__PURE__ */ new Map();
|
|
2876
|
+
const changesByCommit = /* @__PURE__ */ new Map();
|
|
2747
2877
|
for (const doc of docs) {
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
);
|
|
2878
|
+
const hash = doc.lastCommit?.hash;
|
|
2879
|
+
if (hash && !changesByCommit.has(hash)) changesByCommit.set(hash, await getChangesWithStatus(resolvedRoot, hash));
|
|
2880
|
+
changesSinceDoc.set(doc.path, hash ? changesByCommit.get(hash) : null);
|
|
2752
2881
|
}
|
|
2753
2882
|
let decisionsPresent = false;
|
|
2754
2883
|
try {
|
|
2755
|
-
await fs12.access(
|
|
2884
|
+
await fs12.access(path20.join(resolvedRoot, ".mason", "decisions"));
|
|
2756
2885
|
decisionsPresent = true;
|
|
2757
2886
|
} catch {
|
|
2758
2887
|
}
|
|
@@ -2782,6 +2911,7 @@ async function computeAudit(rootDir, options = {}) {
|
|
|
2782
2911
|
var init_audit = __esm({
|
|
2783
2912
|
"src/audit/audit.ts"() {
|
|
2784
2913
|
"use strict";
|
|
2914
|
+
init_profile();
|
|
2785
2915
|
init_drift();
|
|
2786
2916
|
init_snapshot();
|
|
2787
2917
|
init_docs();
|
|
@@ -2796,11 +2926,11 @@ init_audit();
|
|
|
2796
2926
|
init_types();
|
|
2797
2927
|
init_repair();
|
|
2798
2928
|
init_findings();
|
|
2799
|
-
import
|
|
2929
|
+
import path22 from "path";
|
|
2800
2930
|
|
|
2801
2931
|
// src/audit/advisory-cli.ts
|
|
2802
2932
|
init_advisory_review();
|
|
2803
|
-
import
|
|
2933
|
+
import path21 from "path";
|
|
2804
2934
|
import { stripVTControlCharacters } from "util";
|
|
2805
2935
|
var USAGE = `Usage: mason audit review --baseline <path> --finding <id> [options]
|
|
2806
2936
|
|
|
@@ -2846,7 +2976,7 @@ async function runAdvisoryCli(args, io = { out: (text2) => console.log(text2), e
|
|
|
2846
2976
|
note: flags["--note"],
|
|
2847
2977
|
reviewToken: flags["--token"]
|
|
2848
2978
|
};
|
|
2849
|
-
const result = await reviewAdvisory(
|
|
2979
|
+
const result = await reviewAdvisory(path21.resolve(flags["--dir"] ?? process.cwd()), input);
|
|
2850
2980
|
if (json) io.out(JSON.stringify(result, null, 2));
|
|
2851
2981
|
else {
|
|
2852
2982
|
const lines = [`Advisory review: ${result.status}.`];
|
|
@@ -3079,7 +3209,7 @@ async function runAuditCli(argv, io = {
|
|
|
3079
3209
|
io.out(USAGE2);
|
|
3080
3210
|
return 0;
|
|
3081
3211
|
}
|
|
3082
|
-
const rootDir =
|
|
3212
|
+
const rootDir = path22.resolve(args.dir);
|
|
3083
3213
|
if (args.baseline || args.prepareRepair) {
|
|
3084
3214
|
try {
|
|
3085
3215
|
if (args.baseline) {
|