mason-context 0.17.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/mason-audit.js +304 -222
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-auto.js +339 -253
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-drift.js +54 -22
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +55 -22
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +831 -603
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +63 -30
- package/dist/mason-review.js.map +1 -1
- package/dist/mason.js +372 -286
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/dist/mason-audit.js
CHANGED
|
@@ -31,6 +31,32 @@ var init_profile = __esm({
|
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
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
|
+
|
|
34
60
|
// src/utils/paths.ts
|
|
35
61
|
import path from "path";
|
|
36
62
|
function normalizeRepoPath(value) {
|
|
@@ -62,8 +88,6 @@ var init_paths = __esm({
|
|
|
62
88
|
import fs from "fs/promises";
|
|
63
89
|
import { constants } from "fs";
|
|
64
90
|
import path2 from "path";
|
|
65
|
-
import { execFile } from "child_process";
|
|
66
|
-
import { promisify } from "util";
|
|
67
91
|
import fg from "fast-glob";
|
|
68
92
|
async function readBoundedFile(file, maxBytes) {
|
|
69
93
|
const handle = await fs.open(file, constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW);
|
|
@@ -105,12 +129,12 @@ async function loadProjectConfig(root) {
|
|
|
105
129
|
throw new Error(`Cannot apply project file policy: ${error instanceof Error ? error.message : String(error)}`);
|
|
106
130
|
}
|
|
107
131
|
}
|
|
108
|
-
var
|
|
132
|
+
var SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
|
|
109
133
|
var init_files = __esm({
|
|
110
134
|
"src/utils/files.ts"() {
|
|
111
135
|
"use strict";
|
|
136
|
+
init_git_read();
|
|
112
137
|
init_paths();
|
|
113
|
-
exec = promisify(execFile);
|
|
114
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"];
|
|
115
139
|
SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(",")}}`;
|
|
116
140
|
SOURCE_IGNORE = [
|
|
@@ -210,6 +234,102 @@ var init_storage = __esm({
|
|
|
210
234
|
}
|
|
211
235
|
});
|
|
212
236
|
|
|
237
|
+
// src/utils/store-lock.ts
|
|
238
|
+
import fs3 from "fs/promises";
|
|
239
|
+
import os from "os";
|
|
240
|
+
async function lockDiagnostic(file) {
|
|
241
|
+
let reason;
|
|
242
|
+
try {
|
|
243
|
+
const owner = JSON.parse(await fs3.readFile(file, "utf8"));
|
|
244
|
+
if (!owner || typeof owner.host !== "string" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
|
|
245
|
+
reason = "Lock owner data is incomplete or malformed.";
|
|
246
|
+
} else if (owner.host !== os.hostname()) {
|
|
247
|
+
reason = "The lock belongs to another host; its process cannot be checked locally.";
|
|
248
|
+
} else {
|
|
249
|
+
try {
|
|
250
|
+
process.kill(owner.pid, 0);
|
|
251
|
+
reason = `Local owner PID ${owner.pid} is still running.`;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
reason = error.code === "ESRCH" ? `Local owner PID ${owner.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.pid} could not be checked.`;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} catch (error) {
|
|
257
|
+
reason = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
|
|
258
|
+
}
|
|
259
|
+
if (await fs3.lstat(file + ".reclaim").then(() => true, () => false)) {
|
|
260
|
+
reason += " A recovery guard also needs inspection: " + file + ".reclaim.";
|
|
261
|
+
}
|
|
262
|
+
return reason + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
|
|
263
|
+
}
|
|
264
|
+
async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason store") {
|
|
265
|
+
const file = await storePath(root, directory + "/lock", true);
|
|
266
|
+
const deadline = Date.now() + waitMs;
|
|
267
|
+
let handle;
|
|
268
|
+
while (!handle) {
|
|
269
|
+
try {
|
|
270
|
+
handle = await fs3.open(file, "wx", 384);
|
|
271
|
+
try {
|
|
272
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
|
|
273
|
+
} catch (error) {
|
|
274
|
+
await handle.close().catch(() => {
|
|
275
|
+
});
|
|
276
|
+
handle = void 0;
|
|
277
|
+
await fs3.rm(file, { force: true }).catch(() => {
|
|
278
|
+
});
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (error.code !== "EEXIST") throw error;
|
|
283
|
+
try {
|
|
284
|
+
const owner = JSON.parse(await fs3.readFile(file, "utf8"));
|
|
285
|
+
if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
|
|
286
|
+
try {
|
|
287
|
+
process.kill(owner.pid, 0);
|
|
288
|
+
} catch (probe) {
|
|
289
|
+
if (probe.code === "ESRCH") {
|
|
290
|
+
const reclaim = file + ".reclaim";
|
|
291
|
+
let guard;
|
|
292
|
+
try {
|
|
293
|
+
guard = await fs3.open(reclaim, "wx", 384);
|
|
294
|
+
const current = JSON.parse(await fs3.readFile(file, "utf8"));
|
|
295
|
+
if (current.pid === owner.pid && current.host === owner.host) await fs3.unlink(file);
|
|
296
|
+
} finally {
|
|
297
|
+
if (guard) {
|
|
298
|
+
await guard.close();
|
|
299
|
+
await fs3.rm(reclaim, { force: true });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
|
|
308
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
return await run();
|
|
313
|
+
} finally {
|
|
314
|
+
await handle.close();
|
|
315
|
+
await fs3.unlink(file);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
var init_store_lock = __esm({
|
|
319
|
+
"src/utils/store-lock.ts"() {
|
|
320
|
+
"use strict";
|
|
321
|
+
init_storage();
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// src/snapshot/lock.ts
|
|
326
|
+
var init_lock = __esm({
|
|
327
|
+
"src/snapshot/lock.ts"() {
|
|
328
|
+
"use strict";
|
|
329
|
+
init_store_lock();
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
213
333
|
// src/test-map.ts
|
|
214
334
|
import path4 from "path";
|
|
215
335
|
var init_test_map = __esm({
|
|
@@ -221,12 +341,10 @@ var init_test_map = __esm({
|
|
|
221
341
|
|
|
222
342
|
// src/snapshot/snapshot.ts
|
|
223
343
|
import path5 from "path";
|
|
224
|
-
import { execFile as execFile2 } from "child_process";
|
|
225
|
-
import { promisify as promisify2 } from "util";
|
|
226
344
|
import { z } from "zod";
|
|
227
345
|
async function getCurrentGitHash(rootDir) {
|
|
228
346
|
try {
|
|
229
|
-
const { stdout } = await
|
|
347
|
+
const { stdout } = await execGit(["rev-parse", "HEAD"], {
|
|
230
348
|
cwd: rootDir
|
|
231
349
|
});
|
|
232
350
|
return stdout.trim();
|
|
@@ -234,21 +352,23 @@ async function getCurrentGitHash(rootDir) {
|
|
|
234
352
|
return "unknown";
|
|
235
353
|
}
|
|
236
354
|
}
|
|
237
|
-
var
|
|
355
|
+
var repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema;
|
|
238
356
|
var init_snapshot = __esm({
|
|
239
357
|
"src/snapshot/snapshot.ts"() {
|
|
240
358
|
"use strict";
|
|
359
|
+
init_git_read();
|
|
241
360
|
init_files();
|
|
242
361
|
init_files();
|
|
243
362
|
init_storage();
|
|
363
|
+
init_lock();
|
|
244
364
|
init_paths();
|
|
245
365
|
init_test_map();
|
|
246
|
-
exec2 = promisify2(execFile2);
|
|
247
366
|
repoPath = z.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
|
|
248
367
|
verificationFields = {
|
|
249
368
|
refreshedHash: z.string().optional(),
|
|
250
369
|
verifiedAt: z.string().optional(),
|
|
251
370
|
verifiedHash: z.string().optional(),
|
|
371
|
+
verificationToken: z.string().optional(),
|
|
252
372
|
verificationFailed: z.boolean().optional(),
|
|
253
373
|
verificationNote: z.string().optional()
|
|
254
374
|
};
|
|
@@ -272,10 +392,8 @@ var init_snapshot = __esm({
|
|
|
272
392
|
});
|
|
273
393
|
|
|
274
394
|
// src/drift/drift.ts
|
|
275
|
-
import
|
|
395
|
+
import fs4 from "fs/promises";
|
|
276
396
|
import path6 from "path";
|
|
277
|
-
import { execFile as execFile3 } from "child_process";
|
|
278
|
-
import { promisify as promisify3 } from "util";
|
|
279
397
|
function parseChanges(output) {
|
|
280
398
|
const fields = output.split("\0");
|
|
281
399
|
const changes = [];
|
|
@@ -296,7 +414,7 @@ function touchedPaths(changes) {
|
|
|
296
414
|
async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
|
|
297
415
|
if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-") || !toHash || toHash === "unknown" || toHash.startsWith("-")) return null;
|
|
298
416
|
try {
|
|
299
|
-
const { stdout } = await
|
|
417
|
+
const { stdout } = await execGit(["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
|
|
300
418
|
return parseChanges(stdout);
|
|
301
419
|
} catch {
|
|
302
420
|
return null;
|
|
@@ -305,8 +423,8 @@ async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
|
|
|
305
423
|
async function getWorkingTree(resolvedRoot) {
|
|
306
424
|
try {
|
|
307
425
|
const [diff, untracked] = await Promise.all([
|
|
308
|
-
|
|
309
|
-
|
|
426
|
+
execGit(["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
|
|
427
|
+
execGit(["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
|
|
310
428
|
]);
|
|
311
429
|
const untrackedFiles = untracked.stdout.split("\0").filter((f) => f && !f.startsWith(".mason/"));
|
|
312
430
|
return { available: true, changedFiles: [.../* @__PURE__ */ new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles])].sort(), untrackedFiles };
|
|
@@ -314,13 +432,12 @@ async function getWorkingTree(resolvedRoot) {
|
|
|
314
432
|
return { available: false, changedFiles: [], untrackedFiles: [] };
|
|
315
433
|
}
|
|
316
434
|
}
|
|
317
|
-
var exec3;
|
|
318
435
|
var init_drift = __esm({
|
|
319
436
|
"src/drift/drift.ts"() {
|
|
320
437
|
"use strict";
|
|
438
|
+
init_git_read();
|
|
321
439
|
init_snapshot();
|
|
322
440
|
init_paths();
|
|
323
|
-
exec3 = promisify3(execFile3);
|
|
324
441
|
}
|
|
325
442
|
});
|
|
326
443
|
|
|
@@ -548,14 +665,14 @@ function extractClaims(content) {
|
|
|
548
665
|
const ignored = computeIgnoredLines(lines);
|
|
549
666
|
const paths = /* @__PURE__ */ new Map();
|
|
550
667
|
const counts = [];
|
|
551
|
-
const
|
|
668
|
+
const commands2 = /* @__PURE__ */ new Map();
|
|
552
669
|
const addPath = (claim) => {
|
|
553
670
|
const key = [claim.relativeTo ?? "scope", claim.path].join(":");
|
|
554
671
|
if (!paths.has(key)) paths.set(key, claim);
|
|
555
672
|
};
|
|
556
673
|
const addCommand = (claim) => {
|
|
557
674
|
const key = JSON.stringify([claim.scriptName, claim.directory, claim.scopeUnknown]);
|
|
558
|
-
if (!
|
|
675
|
+
if (!commands2.has(key)) commands2.set(key, claim);
|
|
559
676
|
};
|
|
560
677
|
const parsed = markdown.parse(lines.map((line, i) => ignored[i] ? "" : line).join("\n"), {});
|
|
561
678
|
for (const token of parsed) {
|
|
@@ -657,7 +774,7 @@ function extractClaims(content) {
|
|
|
657
774
|
return {
|
|
658
775
|
paths: [...paths.values()],
|
|
659
776
|
counts,
|
|
660
|
-
commands: [...
|
|
777
|
+
commands: [...commands2.values()]
|
|
661
778
|
};
|
|
662
779
|
}
|
|
663
780
|
var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
|
|
@@ -707,9 +824,50 @@ var init_claims = __esm({
|
|
|
707
824
|
}
|
|
708
825
|
});
|
|
709
826
|
|
|
827
|
+
// src/audit/inspection.ts
|
|
828
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
829
|
+
import path8 from "path";
|
|
830
|
+
function inspectionRead(root, key, read) {
|
|
831
|
+
const inspection = inspections.getStore();
|
|
832
|
+
if (!inspection || inspection.root !== path8.resolve(root)) return read();
|
|
833
|
+
if (!inspection.reads.has(key)) {
|
|
834
|
+
const pending = read();
|
|
835
|
+
inspection.reads.set(key, pending);
|
|
836
|
+
void pending.catch(() => {
|
|
837
|
+
if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
return inspection.reads.get(key);
|
|
841
|
+
}
|
|
842
|
+
function inspectionGit(args, options) {
|
|
843
|
+
return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
|
|
844
|
+
}
|
|
845
|
+
async function auditGitPaths(root, kind) {
|
|
846
|
+
const shared = inspections.getStore()?.root === path8.resolve(root);
|
|
847
|
+
const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
|
|
848
|
+
const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
|
|
849
|
+
const { stdout } = await inspectionGit(
|
|
850
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
|
|
851
|
+
{ cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
|
|
852
|
+
);
|
|
853
|
+
const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? /^(readme|agents|claude)\.md$/i.test(path8.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
|
|
854
|
+
if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
|
|
855
|
+
return paths.sort();
|
|
856
|
+
}
|
|
857
|
+
var inspections, sourceSpecs, docSpecs, sourceSuffixes;
|
|
858
|
+
var init_inspection = __esm({
|
|
859
|
+
"src/audit/inspection.ts"() {
|
|
860
|
+
"use strict";
|
|
861
|
+
init_git_read();
|
|
862
|
+
init_files();
|
|
863
|
+
inspections = new AsyncLocalStorage2();
|
|
864
|
+
sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
|
|
865
|
+
docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
|
|
866
|
+
sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
|
|
710
870
|
// src/audit/git.ts
|
|
711
|
-
import { execFile as execFile4 } from "child_process";
|
|
712
|
-
import { promisify as promisify4 } from "util";
|
|
713
871
|
function parseCommitLine(line) {
|
|
714
872
|
const parts = line.split(" ");
|
|
715
873
|
if (parts.length < 3 || !parts[0]) return null;
|
|
@@ -717,8 +875,7 @@ function parseCommitLine(line) {
|
|
|
717
875
|
}
|
|
718
876
|
async function lastCommitOf(resolvedRoot, relPath) {
|
|
719
877
|
try {
|
|
720
|
-
const { stdout } = await
|
|
721
|
-
"git",
|
|
878
|
+
const { stdout } = await inspectionGit(
|
|
722
879
|
["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
723
880
|
{ cwd: resolvedRoot }
|
|
724
881
|
);
|
|
@@ -730,8 +887,7 @@ async function lastCommitOf(resolvedRoot, relPath) {
|
|
|
730
887
|
}
|
|
731
888
|
async function deletingCommitOf(resolvedRoot, relPath) {
|
|
732
889
|
try {
|
|
733
|
-
const { stdout } = await
|
|
734
|
-
"git",
|
|
890
|
+
const { stdout } = await inspectionGit(
|
|
735
891
|
[
|
|
736
892
|
"log",
|
|
737
893
|
"-1",
|
|
@@ -750,8 +906,7 @@ async function deletingCommitOf(resolvedRoot, relPath) {
|
|
|
750
906
|
}
|
|
751
907
|
async function firstCommitOf(resolvedRoot, relPath) {
|
|
752
908
|
try {
|
|
753
|
-
const { stdout } = await
|
|
754
|
-
"git",
|
|
909
|
+
const { stdout } = await inspectionGit(
|
|
755
910
|
["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
756
911
|
{ cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
|
|
757
912
|
);
|
|
@@ -764,8 +919,7 @@ async function firstCommitOf(resolvedRoot, relPath) {
|
|
|
764
919
|
async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
|
|
765
920
|
if (!fromHash || fromHash === "unknown") return null;
|
|
766
921
|
try {
|
|
767
|
-
const { stdout } = await
|
|
768
|
-
"git",
|
|
922
|
+
const { stdout } = await inspectionGit(
|
|
769
923
|
[
|
|
770
924
|
"log",
|
|
771
925
|
`${fromHash}..HEAD`,
|
|
@@ -789,24 +943,22 @@ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
|
|
|
789
943
|
return null;
|
|
790
944
|
}
|
|
791
945
|
}
|
|
792
|
-
var
|
|
946
|
+
var COMMIT_FORMAT;
|
|
793
947
|
var init_git = __esm({
|
|
794
948
|
"src/audit/git.ts"() {
|
|
795
949
|
"use strict";
|
|
796
|
-
|
|
950
|
+
init_inspection();
|
|
797
951
|
COMMIT_FORMAT = "%H%x09%cI%x09%s";
|
|
798
952
|
}
|
|
799
953
|
});
|
|
800
954
|
|
|
801
955
|
// src/audit/inputs.ts
|
|
802
|
-
import
|
|
956
|
+
import fs5 from "fs/promises";
|
|
803
957
|
import * as nativeFs from "fs";
|
|
804
|
-
import
|
|
805
|
-
import { execFile as execFile5 } from "child_process";
|
|
806
|
-
import { promisify as promisify5 } from "util";
|
|
958
|
+
import path9 from "path";
|
|
807
959
|
import fg2 from "fast-glob";
|
|
808
960
|
async function auditInputPath(root, file) {
|
|
809
|
-
if (file === ".") return
|
|
961
|
+
if (file === ".") return fs5.realpath(root);
|
|
810
962
|
try {
|
|
811
963
|
return await storePath(root, file);
|
|
812
964
|
} catch (error) {
|
|
@@ -830,7 +982,7 @@ async function auditGlob(root, patterns, options) {
|
|
|
830
982
|
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
831
983
|
const directories = /* @__PURE__ */ new Set();
|
|
832
984
|
const readdir2 = ((directory, opts, callback) => {
|
|
833
|
-
const relative =
|
|
985
|
+
const relative = path9.relative(root, directory).split(path9.sep).join("/") || ".";
|
|
834
986
|
directories.add(relative);
|
|
835
987
|
if (directories.size > MAX_INPUTS) {
|
|
836
988
|
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
@@ -841,7 +993,7 @@ async function auditGlob(root, patterns, options) {
|
|
|
841
993
|
}, callback);
|
|
842
994
|
});
|
|
843
995
|
const lstat2 = ((file, callback) => {
|
|
844
|
-
const relative =
|
|
996
|
+
const relative = path9.relative(root, file).split(path9.sep).join("/") || ".";
|
|
845
997
|
auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
|
|
846
998
|
});
|
|
847
999
|
const stream = fg2.stream(patterns, {
|
|
@@ -878,62 +1030,48 @@ async function auditGlob(root, patterns, options) {
|
|
|
878
1030
|
return matches.sort();
|
|
879
1031
|
}
|
|
880
1032
|
async function gitSourcePaths(root) {
|
|
881
|
-
|
|
882
|
-
"git",
|
|
883
|
-
[
|
|
884
|
-
"ls-files",
|
|
885
|
-
"-z",
|
|
886
|
-
"--cached",
|
|
887
|
-
"--others",
|
|
888
|
-
"--exclude-standard",
|
|
889
|
-
"--",
|
|
890
|
-
...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
|
|
891
|
-
],
|
|
892
|
-
{ cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
|
|
893
|
-
);
|
|
894
|
-
return [...new Set(stdout.split("\0").filter(Boolean))].sort();
|
|
1033
|
+
return auditGitPaths(root, "sources");
|
|
895
1034
|
}
|
|
896
1035
|
async function gitIgnoredPaths(root, files) {
|
|
897
1036
|
if (!files.length) return /* @__PURE__ */ new Set();
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1037
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
1038
|
+
for (let offset = 0; offset < files.length; offset += 1e3) {
|
|
1039
|
+
try {
|
|
1040
|
+
const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
|
|
1041
|
+
cwd: root,
|
|
1042
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
1043
|
+
timeout: 1e4,
|
|
1044
|
+
input: files.slice(offset, offset + 1e3).join("\0") + "\0"
|
|
1045
|
+
});
|
|
1046
|
+
for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
if (error.code !== 1) throw error;
|
|
1049
|
+
}
|
|
911
1050
|
}
|
|
1051
|
+
return ignored;
|
|
912
1052
|
}
|
|
913
|
-
var
|
|
1053
|
+
var MAX_INPUTS;
|
|
914
1054
|
var init_inputs = __esm({
|
|
915
1055
|
"src/audit/inputs.ts"() {
|
|
916
1056
|
"use strict";
|
|
917
1057
|
init_files();
|
|
918
1058
|
init_storage();
|
|
919
|
-
|
|
1059
|
+
init_git_read();
|
|
1060
|
+
init_inspection();
|
|
920
1061
|
MAX_INPUTS = 1e5;
|
|
921
1062
|
}
|
|
922
1063
|
});
|
|
923
1064
|
|
|
924
1065
|
// src/audit/docs.ts
|
|
925
|
-
import
|
|
926
|
-
import
|
|
927
|
-
import { execFile as execFile6 } from "child_process";
|
|
928
|
-
import { promisify as promisify6 } from "util";
|
|
1066
|
+
import fs6 from "fs/promises";
|
|
1067
|
+
import path10 from "path";
|
|
929
1068
|
import fg3 from "fast-glob";
|
|
930
1069
|
async function dirtyDocs(resolvedRoot, files) {
|
|
931
1070
|
if (!files.length) return /* @__PURE__ */ new Set();
|
|
932
1071
|
try {
|
|
933
1072
|
const dirty = /* @__PURE__ */ new Set();
|
|
934
1073
|
for (let offset = 0; offset < files.length; offset += 256) {
|
|
935
|
-
const { stdout } = await
|
|
936
|
-
"git",
|
|
1074
|
+
const { stdout } = await execGit(
|
|
937
1075
|
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
|
|
938
1076
|
{ cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
939
1077
|
);
|
|
@@ -953,25 +1091,10 @@ async function dirtyDocs(resolvedRoot, files) {
|
|
|
953
1091
|
async function discoverDocPaths(root) {
|
|
954
1092
|
let candidates = [];
|
|
955
1093
|
try {
|
|
956
|
-
|
|
957
|
-
"git",
|
|
958
|
-
[
|
|
959
|
-
"ls-files",
|
|
960
|
-
"-z",
|
|
961
|
-
"--cached",
|
|
962
|
-
"--others",
|
|
963
|
-
"--exclude-standard",
|
|
964
|
-
"--",
|
|
965
|
-
":(icase,glob)**/readme.md",
|
|
966
|
-
":(icase,glob)**/agents.md",
|
|
967
|
-
":(icase,glob)**/claude.md"
|
|
968
|
-
],
|
|
969
|
-
{ cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
970
|
-
);
|
|
971
|
-
candidates = stdout.split("\0").filter(Boolean);
|
|
1094
|
+
candidates = await auditGitPaths(root, "documents");
|
|
972
1095
|
} catch (error) {
|
|
973
1096
|
try {
|
|
974
|
-
await
|
|
1097
|
+
await execGit(["rev-parse", "--git-dir"], { cwd: root });
|
|
975
1098
|
} catch {
|
|
976
1099
|
return localDocPaths(root);
|
|
977
1100
|
}
|
|
@@ -1012,20 +1135,20 @@ async function localDocPaths(root) {
|
|
|
1012
1135
|
for (const dir of [".", ".claude"]) {
|
|
1013
1136
|
let entries;
|
|
1014
1137
|
try {
|
|
1015
|
-
entries = await
|
|
1138
|
+
entries = await fs6.readdir(await auditInputPath(root, dir), { withFileTypes: true });
|
|
1016
1139
|
} catch (error) {
|
|
1017
1140
|
if (error.code === "ENOENT") continue;
|
|
1018
1141
|
throw error;
|
|
1019
1142
|
}
|
|
1020
1143
|
for (const entry of entries) if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
|
|
1021
|
-
paths.push(
|
|
1144
|
+
paths.push(path10.posix.join(dir, entry.name));
|
|
1022
1145
|
}
|
|
1023
1146
|
}
|
|
1024
1147
|
return paths;
|
|
1025
1148
|
}
|
|
1026
1149
|
function documentScope(file) {
|
|
1027
|
-
const parent =
|
|
1028
|
-
return
|
|
1150
|
+
const parent = path10.posix.dirname(file);
|
|
1151
|
+
return path10.posix.basename(parent).toLowerCase() === ".claude" ? path10.posix.dirname(parent) : parent;
|
|
1029
1152
|
}
|
|
1030
1153
|
async function discoverDocs(resolvedRoot) {
|
|
1031
1154
|
return profilePhase("audit.documents", () => readDocs(resolvedRoot));
|
|
@@ -1045,7 +1168,7 @@ async function readDocs(resolvedRoot) {
|
|
|
1045
1168
|
path: candidate,
|
|
1046
1169
|
content,
|
|
1047
1170
|
scope: documentScope(candidate),
|
|
1048
|
-
kind: /^readme\.md$/i.test(
|
|
1171
|
+
kind: /^readme\.md$/i.test(path10.posix.basename(candidate)) ? "readme" : "instructions",
|
|
1049
1172
|
lineCount: content.split("\n").length,
|
|
1050
1173
|
lastCommit,
|
|
1051
1174
|
dirty: dirty.has(candidate),
|
|
@@ -1055,16 +1178,17 @@ async function readDocs(resolvedRoot) {
|
|
|
1055
1178
|
}
|
|
1056
1179
|
return docs;
|
|
1057
1180
|
}
|
|
1058
|
-
var
|
|
1181
|
+
var DOC_CANDIDATES;
|
|
1059
1182
|
var init_docs = __esm({
|
|
1060
1183
|
"src/audit/docs.ts"() {
|
|
1061
1184
|
"use strict";
|
|
1185
|
+
init_git_read();
|
|
1062
1186
|
init_profile();
|
|
1063
1187
|
init_claims();
|
|
1064
1188
|
init_git();
|
|
1065
1189
|
init_inputs();
|
|
1066
1190
|
init_files();
|
|
1067
|
-
|
|
1191
|
+
init_inspection();
|
|
1068
1192
|
DOC_CANDIDATES = [
|
|
1069
1193
|
"AGENTS.md",
|
|
1070
1194
|
"CLAUDE.md",
|
|
@@ -1090,15 +1214,15 @@ var init_types = __esm({
|
|
|
1090
1214
|
});
|
|
1091
1215
|
|
|
1092
1216
|
// src/audit/scope.ts
|
|
1093
|
-
import
|
|
1094
|
-
import
|
|
1217
|
+
import fs7 from "fs/promises";
|
|
1218
|
+
import path11 from "path";
|
|
1095
1219
|
function scopedPath(directory, value) {
|
|
1096
|
-
if (
|
|
1097
|
-
const resolved =
|
|
1220
|
+
if (path11.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
|
|
1221
|
+
const resolved = path11.posix.normalize(path11.posix.join(directory, value));
|
|
1098
1222
|
return resolved === ".." || resolved.startsWith("../") ? null : resolved;
|
|
1099
1223
|
}
|
|
1100
1224
|
function pathClaimScope(doc, claim) {
|
|
1101
|
-
const directory = claim.relativeTo === "document" ?
|
|
1225
|
+
const directory = claim.relativeTo === "document" ? path11.posix.dirname(doc) : documentScope(doc);
|
|
1102
1226
|
const local = scopedPath(directory, claim.path);
|
|
1103
1227
|
if (!local) return null;
|
|
1104
1228
|
const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
|
|
@@ -1110,7 +1234,7 @@ function pathClaimScope(doc, claim) {
|
|
|
1110
1234
|
}
|
|
1111
1235
|
async function pathExists(root, file) {
|
|
1112
1236
|
try {
|
|
1113
|
-
await
|
|
1237
|
+
await fs7.access(await auditInputPath(root, file));
|
|
1114
1238
|
return true;
|
|
1115
1239
|
} catch (error) {
|
|
1116
1240
|
if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return false;
|
|
@@ -1132,7 +1256,7 @@ var init_scope = __esm({
|
|
|
1132
1256
|
});
|
|
1133
1257
|
|
|
1134
1258
|
// src/audit/checks/deleted-reference.ts
|
|
1135
|
-
import
|
|
1259
|
+
import path12 from "path";
|
|
1136
1260
|
async function checkDeletedReferences(ctx) {
|
|
1137
1261
|
const result = emptyResult();
|
|
1138
1262
|
for (const doc of ctx.docs) {
|
|
@@ -1161,7 +1285,7 @@ async function checkDeletedReferences(ctx) {
|
|
|
1161
1285
|
const candidates = await Promise.all(scope.candidates.map(async (file) => ({
|
|
1162
1286
|
file,
|
|
1163
1287
|
tracked: await lastCommitOf(ctx.root, file),
|
|
1164
|
-
parent: await pathExists(ctx.root,
|
|
1288
|
+
parent: await pathExists(ctx.root, path12.posix.dirname(file))
|
|
1165
1289
|
})));
|
|
1166
1290
|
const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
|
|
1167
1291
|
if (!candidate && claim.relativeTo !== "document") continue;
|
|
@@ -1200,8 +1324,8 @@ var init_deleted_reference = __esm({
|
|
|
1200
1324
|
|
|
1201
1325
|
// src/audit/checks/new-module.ts
|
|
1202
1326
|
import fg4 from "fast-glob";
|
|
1203
|
-
import
|
|
1204
|
-
import
|
|
1327
|
+
import fs8 from "fs/promises";
|
|
1328
|
+
import path13 from "path";
|
|
1205
1329
|
function escapeRegExp(text2) {
|
|
1206
1330
|
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1207
1331
|
}
|
|
@@ -1239,23 +1363,36 @@ async function checkNewModules(ctx) {
|
|
|
1239
1363
|
return result;
|
|
1240
1364
|
}
|
|
1241
1365
|
function moduleDocumentation(docs) {
|
|
1242
|
-
return docs.map((doc) => doc.content + "\n" + (
|
|
1366
|
+
return docs.map((doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")).join("\n");
|
|
1243
1367
|
}
|
|
1244
|
-
|
|
1368
|
+
function moduleCandidates(root, combinedDocs) {
|
|
1369
|
+
return inspectionRead(root, "modules:" + combinedDocs, () => collectModuleCandidates(root, combinedDocs));
|
|
1370
|
+
}
|
|
1371
|
+
async function collectModuleCandidates(root, combinedDocs) {
|
|
1245
1372
|
const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
|
|
1246
1373
|
const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
|
|
1247
1374
|
const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
|
|
1248
|
-
const listSubdirs = async (
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1375
|
+
const listSubdirs = async (parents) => {
|
|
1376
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
1377
|
+
for (let offset = 0; offset < parents.length; offset += 16) {
|
|
1378
|
+
await Promise.all(parents.slice(offset, offset + 16).map(async (dir) => {
|
|
1379
|
+
const entries = await fs8.readdir(await auditInputPath(root, dir), { withFileTypes: true });
|
|
1380
|
+
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));
|
|
1381
|
+
byParent.set(dir, dirs);
|
|
1382
|
+
}));
|
|
1383
|
+
}
|
|
1384
|
+
const ignored = await gitIgnoredPaths(root, [...byParent.values()].flat());
|
|
1385
|
+
const result = /* @__PURE__ */ new Map();
|
|
1386
|
+
for (const dir of parents) {
|
|
1387
|
+
const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
|
|
1388
|
+
result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
|
|
1389
|
+
ignore,
|
|
1390
|
+
onlyDirectories: true,
|
|
1391
|
+
label: "Module directory discovery",
|
|
1392
|
+
select: (file) => visible.has(file)
|
|
1393
|
+
})).map((file) => path13.posix.basename(file)));
|
|
1394
|
+
}
|
|
1395
|
+
return result;
|
|
1259
1396
|
};
|
|
1260
1397
|
const countSourceFiles = async (dir) => {
|
|
1261
1398
|
const files = sources.filter((file) => file.startsWith(dir + "/"));
|
|
@@ -1271,14 +1408,16 @@ async function moduleCandidates(root, combinedDocs) {
|
|
|
1271
1408
|
return count3;
|
|
1272
1409
|
};
|
|
1273
1410
|
const candidates = [];
|
|
1274
|
-
|
|
1411
|
+
const topDirs = (await listSubdirs(["."])).get(".");
|
|
1412
|
+
const nested = await listSubdirs(topDirs.filter((dir) => isMentioned(combinedDocs, dir)));
|
|
1413
|
+
for (const topDir of topDirs) {
|
|
1275
1414
|
const topMentioned = isMentioned(combinedDocs, topDir);
|
|
1276
1415
|
if (!topMentioned) {
|
|
1277
1416
|
const count3 = await countSourceFiles(topDir);
|
|
1278
1417
|
if (count3 >= 1) candidates.push({ dir: topDir, sourceFileCount: count3 });
|
|
1279
1418
|
continue;
|
|
1280
1419
|
}
|
|
1281
|
-
const subdirs =
|
|
1420
|
+
const subdirs = nested.get(topDir);
|
|
1282
1421
|
const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
|
|
1283
1422
|
if (mentioned.length < ENUMERATION_THRESHOLD) continue;
|
|
1284
1423
|
for (const sub of subdirs) {
|
|
@@ -1295,6 +1434,7 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
|
|
|
1295
1434
|
var init_new_module = __esm({
|
|
1296
1435
|
"src/audit/checks/new-module.ts"() {
|
|
1297
1436
|
"use strict";
|
|
1437
|
+
init_inspection();
|
|
1298
1438
|
init_files();
|
|
1299
1439
|
init_inputs();
|
|
1300
1440
|
init_git();
|
|
@@ -1324,7 +1464,7 @@ var init_new_module = __esm({
|
|
|
1324
1464
|
});
|
|
1325
1465
|
|
|
1326
1466
|
// src/audit/checks/stale-count.ts
|
|
1327
|
-
import
|
|
1467
|
+
import path14 from "path";
|
|
1328
1468
|
async function countGradleModules(root) {
|
|
1329
1469
|
for (const name of ["settings.gradle.kts", "settings.gradle"]) {
|
|
1330
1470
|
const content = await readAuditInput(root, name);
|
|
@@ -1358,7 +1498,7 @@ async function countNpmWorkspaces(root) {
|
|
|
1358
1498
|
return {
|
|
1359
1499
|
actual: matched.length,
|
|
1360
1500
|
countedFrom: "package.json workspaces",
|
|
1361
|
-
members: matched.map((m) =>
|
|
1501
|
+
members: matched.map((m) => path14.dirname(m)).sort()
|
|
1362
1502
|
};
|
|
1363
1503
|
}
|
|
1364
1504
|
}
|
|
@@ -1389,7 +1529,7 @@ async function countNpmWorkspaces(root) {
|
|
|
1389
1529
|
return {
|
|
1390
1530
|
actual: matched.length,
|
|
1391
1531
|
countedFrom: "pnpm-workspace.yaml",
|
|
1392
|
-
members: matched.map((m) =>
|
|
1532
|
+
members: matched.map((m) => path14.dirname(m)).sort()
|
|
1393
1533
|
};
|
|
1394
1534
|
}
|
|
1395
1535
|
}
|
|
@@ -1411,8 +1551,8 @@ async function countCargoCrates(root) {
|
|
|
1411
1551
|
ignore: ["**/target/**"],
|
|
1412
1552
|
label: "Cargo workspace discovery"
|
|
1413
1553
|
});
|
|
1414
|
-
for (const m of matched) members.add(
|
|
1415
|
-
} else if (await readAuditInput(root,
|
|
1554
|
+
for (const m of matched) members.add(path14.dirname(m));
|
|
1555
|
+
} else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
|
|
1416
1556
|
members.add(entry);
|
|
1417
1557
|
}
|
|
1418
1558
|
}
|
|
@@ -1432,7 +1572,7 @@ async function resolveCountSource(root, claim) {
|
|
|
1432
1572
|
}
|
|
1433
1573
|
async function resolveDocCountSource(root, doc, claim) {
|
|
1434
1574
|
const scope = documentScope(doc);
|
|
1435
|
-
const source = await resolveCountSource(
|
|
1575
|
+
const source = await resolveCountSource(path14.join(root, scope), claim);
|
|
1436
1576
|
if (!source || scope === ".") return source;
|
|
1437
1577
|
return {
|
|
1438
1578
|
...source,
|
|
@@ -1484,7 +1624,7 @@ var init_stale_count = __esm({
|
|
|
1484
1624
|
});
|
|
1485
1625
|
|
|
1486
1626
|
// src/audit/checks/dead-command.ts
|
|
1487
|
-
import
|
|
1627
|
+
import path15 from "path";
|
|
1488
1628
|
function scriptsOf(raw) {
|
|
1489
1629
|
try {
|
|
1490
1630
|
const pkg = JSON.parse(raw);
|
|
@@ -1520,7 +1660,7 @@ async function commandInputs(root, docs) {
|
|
|
1520
1660
|
}
|
|
1521
1661
|
let cursor = directory;
|
|
1522
1662
|
while (true) {
|
|
1523
|
-
const manifest =
|
|
1663
|
+
const manifest = path15.posix.join(cursor, "package.json");
|
|
1524
1664
|
scope.candidates.push(manifest);
|
|
1525
1665
|
const raw = await read(manifest);
|
|
1526
1666
|
if (raw !== null) {
|
|
@@ -1531,7 +1671,7 @@ async function commandInputs(root, docs) {
|
|
|
1531
1671
|
break;
|
|
1532
1672
|
}
|
|
1533
1673
|
if (claim.directory !== void 0 || cursor === ".") break;
|
|
1534
|
-
cursor =
|
|
1674
|
+
cursor = path15.posix.dirname(cursor);
|
|
1535
1675
|
}
|
|
1536
1676
|
if (!resolution.manifest && !resolution.problem) {
|
|
1537
1677
|
resolution.problem = `No package.json for the command's scope (${directory}).`;
|
|
@@ -1594,10 +1734,8 @@ var init_dead_command = __esm({
|
|
|
1594
1734
|
});
|
|
1595
1735
|
|
|
1596
1736
|
// src/audit/release-metadata.ts
|
|
1597
|
-
import { execFile as execFile7 } from "child_process";
|
|
1598
|
-
import { promisify as promisify7 } from "util";
|
|
1599
1737
|
async function git(root, args) {
|
|
1600
|
-
return (await
|
|
1738
|
+
return (await execGit(args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
|
|
1601
1739
|
}
|
|
1602
1740
|
function withoutAndroidReleaseValues(text2) {
|
|
1603
1741
|
if (/\/\*|"""|'''/.test(text2) || !/id\s*\(?\s*["']com\.android\.(application|library)["']/.test(text2)) return null;
|
|
@@ -1643,11 +1781,10 @@ async function releaseMetadataOnly(root, commit) {
|
|
|
1643
1781
|
return false;
|
|
1644
1782
|
}
|
|
1645
1783
|
}
|
|
1646
|
-
var exec6;
|
|
1647
1784
|
var init_release_metadata = __esm({
|
|
1648
1785
|
"src/audit/release-metadata.ts"() {
|
|
1649
1786
|
"use strict";
|
|
1650
|
-
|
|
1787
|
+
init_git_read();
|
|
1651
1788
|
}
|
|
1652
1789
|
});
|
|
1653
1790
|
|
|
@@ -1743,7 +1880,7 @@ var init_deps_changed = __esm({
|
|
|
1743
1880
|
});
|
|
1744
1881
|
|
|
1745
1882
|
// src/context/lexical.ts
|
|
1746
|
-
import
|
|
1883
|
+
import path16 from "path";
|
|
1747
1884
|
var init_lexical = __esm({
|
|
1748
1885
|
"src/context/lexical.ts"() {
|
|
1749
1886
|
"use strict";
|
|
@@ -1903,8 +2040,8 @@ var init_provenance = __esm({
|
|
|
1903
2040
|
});
|
|
1904
2041
|
|
|
1905
2042
|
// src/decisions/decisions.ts
|
|
1906
|
-
import
|
|
1907
|
-
import
|
|
2043
|
+
import fs9 from "fs/promises";
|
|
2044
|
+
import path17 from "path";
|
|
1908
2045
|
import { createHash } from "crypto";
|
|
1909
2046
|
import { z as z3 } from "zod";
|
|
1910
2047
|
async function loadDecisionStore(rootDir) {
|
|
@@ -1912,7 +2049,7 @@ async function loadDecisionStore(rootDir) {
|
|
|
1912
2049
|
const diagnostics = [];
|
|
1913
2050
|
let entries;
|
|
1914
2051
|
try {
|
|
1915
|
-
entries = await
|
|
2052
|
+
entries = await fs9.readdir(await storePath(rootDir, ".mason/decisions"));
|
|
1916
2053
|
} catch (error) {
|
|
1917
2054
|
if (error.code !== "ENOENT") diagnostics.push({ path: ".mason/decisions", message: String(error) });
|
|
1918
2055
|
return { records, diagnostics };
|
|
@@ -1955,9 +2092,9 @@ var init_decisions = __esm({
|
|
|
1955
2092
|
});
|
|
1956
2093
|
|
|
1957
2094
|
// src/decisions/drift.ts
|
|
1958
|
-
import
|
|
2095
|
+
import path18 from "path";
|
|
1959
2096
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
1960
|
-
const resolvedRoot =
|
|
2097
|
+
const resolvedRoot = path18.resolve(rootDir);
|
|
1961
2098
|
const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
|
|
1962
2099
|
const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
|
|
1963
2100
|
const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
|
|
@@ -2180,68 +2317,15 @@ var init_findings = __esm({
|
|
|
2180
2317
|
});
|
|
2181
2318
|
|
|
2182
2319
|
// src/automation/store.ts
|
|
2183
|
-
import fs9 from "fs/promises";
|
|
2184
|
-
import os from "os";
|
|
2185
2320
|
import { z as z5 } from "zod";
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
const deadline = Date.now() + waitMs;
|
|
2189
|
-
let handle;
|
|
2190
|
-
while (!handle) {
|
|
2191
|
-
try {
|
|
2192
|
-
handle = await fs9.open(file, "wx", 384);
|
|
2193
|
-
try {
|
|
2194
|
-
await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
|
|
2195
|
-
} catch (error) {
|
|
2196
|
-
await handle.close().catch(() => {
|
|
2197
|
-
});
|
|
2198
|
-
handle = void 0;
|
|
2199
|
-
await fs9.rm(file, { force: true }).catch(() => {
|
|
2200
|
-
});
|
|
2201
|
-
throw error;
|
|
2202
|
-
}
|
|
2203
|
-
} catch (error) {
|
|
2204
|
-
if (error.code !== "EEXIST") throw error;
|
|
2205
|
-
try {
|
|
2206
|
-
const owner = JSON.parse(await fs9.readFile(file, "utf8"));
|
|
2207
|
-
if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
|
|
2208
|
-
try {
|
|
2209
|
-
process.kill(owner.pid, 0);
|
|
2210
|
-
} catch (probe) {
|
|
2211
|
-
if (probe.code === "ESRCH") {
|
|
2212
|
-
const reclaim = file + ".reclaim";
|
|
2213
|
-
let guard;
|
|
2214
|
-
try {
|
|
2215
|
-
guard = await fs9.open(reclaim, "wx", 384);
|
|
2216
|
-
const current = JSON.parse(await fs9.readFile(file, "utf8"));
|
|
2217
|
-
if (current.pid === owner.pid && current.host === owner.host) await fs9.unlink(file);
|
|
2218
|
-
} finally {
|
|
2219
|
-
if (guard) {
|
|
2220
|
-
await guard.close();
|
|
2221
|
-
await fs9.rm(reclaim, { force: true });
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
} catch {
|
|
2228
|
-
}
|
|
2229
|
-
if (Date.now() >= deadline) throw new Error("Automation is busy or its lock needs inspection: " + file);
|
|
2230
|
-
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
try {
|
|
2234
|
-
return await run();
|
|
2235
|
-
} finally {
|
|
2236
|
-
await handle.close();
|
|
2237
|
-
await fs9.unlink(file);
|
|
2238
|
-
}
|
|
2321
|
+
function withLock(root, directory, run, waitMs = 5e3) {
|
|
2322
|
+
return withStoreLock(root, directory, run, waitMs, "Automation");
|
|
2239
2323
|
}
|
|
2240
2324
|
var hostSchema, stateSchema;
|
|
2241
2325
|
var init_store = __esm({
|
|
2242
2326
|
"src/automation/store.ts"() {
|
|
2243
2327
|
"use strict";
|
|
2244
|
-
|
|
2328
|
+
init_store_lock();
|
|
2245
2329
|
hostSchema = z5.enum(["claude", "codex"]);
|
|
2246
2330
|
stateSchema = z5.object({
|
|
2247
2331
|
version: z5.literal(1),
|
|
@@ -2284,7 +2368,7 @@ __export(repair_exports, {
|
|
|
2284
2368
|
withRepairSession: () => withRepairSession
|
|
2285
2369
|
});
|
|
2286
2370
|
import fs10 from "fs/promises";
|
|
2287
|
-
import
|
|
2371
|
+
import path19 from "path";
|
|
2288
2372
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2289
2373
|
import { z as z6 } from "zod";
|
|
2290
2374
|
function recheckable(finding) {
|
|
@@ -2322,8 +2406,8 @@ async function readStableAudit(root, checks, options) {
|
|
|
2322
2406
|
async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
|
|
2323
2407
|
return prepareOriginal(rootDir, checks, options);
|
|
2324
2408
|
}
|
|
2325
|
-
async function withRepairSession(root, options, run, cachedAudit) {
|
|
2326
|
-
const
|
|
2409
|
+
async function withRepairSession(root, options, run, validate, cachedAudit) {
|
|
2410
|
+
const head = await getCurrentGitHash(root);
|
|
2327
2411
|
const audits = /* @__PURE__ */ new Map();
|
|
2328
2412
|
const documents = /* @__PURE__ */ new Map();
|
|
2329
2413
|
const history = /* @__PURE__ */ new Map();
|
|
@@ -2334,9 +2418,9 @@ async function withRepairSession(root, options, run, cachedAudit) {
|
|
|
2334
2418
|
};
|
|
2335
2419
|
const session = {
|
|
2336
2420
|
audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
|
|
2337
|
-
if (cachedAudit && cachedAudit.headHash ===
|
|
2421
|
+
if (cachedAudit && cachedAudit.headHash === head && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
|
|
2338
2422
|
const current = structuredClone(cachedAudit);
|
|
2339
|
-
const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f,
|
|
2423
|
+
const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, head)))).filter((r) => r !== null);
|
|
2340
2424
|
delete current.advisoryReviews;
|
|
2341
2425
|
if (reviews.length) current.advisoryReviews = reviews;
|
|
2342
2426
|
return current;
|
|
@@ -2344,16 +2428,15 @@ async function withRepairSession(root, options, run, cachedAudit) {
|
|
|
2344
2428
|
return computeAudit(root, { ...options, checks });
|
|
2345
2429
|
}),
|
|
2346
2430
|
document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
|
|
2347
|
-
history: (
|
|
2348
|
-
assessment: (dir, finding,
|
|
2431
|
+
history: (head2) => memo(history, head2, () => getChangesWithStatus(root, head2)),
|
|
2432
|
+
assessment: (dir, finding, head2) => memo(assessments, digest([dir, finding, head2]), () => assessAdvisory(dir, finding, head2))
|
|
2349
2433
|
};
|
|
2350
2434
|
const result = await run({
|
|
2351
2435
|
prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
|
|
2352
2436
|
verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
|
|
2353
2437
|
currentAudit: () => session.audit(ALL_CHECKS)
|
|
2354
2438
|
});
|
|
2355
|
-
|
|
2356
|
-
if (digest(before) !== digest(after)) throw new Error("HEAD, context files or advisory review records changed during verification; retry against stable evidence.");
|
|
2439
|
+
await validate();
|
|
2357
2440
|
return result;
|
|
2358
2441
|
}
|
|
2359
2442
|
async function prepareOriginal(rootDir, checks, options, session) {
|
|
@@ -2375,8 +2458,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
|
|
|
2375
2458
|
}
|
|
2376
2459
|
async function loadRepairBaseline(rootDir, baselinePath) {
|
|
2377
2460
|
const root = await fs10.realpath(rootDir);
|
|
2378
|
-
const declaredRoot =
|
|
2379
|
-
const relative =
|
|
2461
|
+
const declaredRoot = path19.resolve(rootDir);
|
|
2462
|
+
const relative = path19.isAbsolute(baselinePath) ? path19.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
|
|
2380
2463
|
const stored = baselineSchema.parse(await readStoreJson(root, relative));
|
|
2381
2464
|
const { digest: savedDigest, ...payload } = stored;
|
|
2382
2465
|
if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
|
|
@@ -2551,8 +2634,6 @@ var init_repair = __esm({
|
|
|
2551
2634
|
|
|
2552
2635
|
// src/audit/advisory-review.ts
|
|
2553
2636
|
import fs11 from "fs/promises";
|
|
2554
|
-
import { execFile as execFile8 } from "child_process";
|
|
2555
|
-
import { promisify as promisify8 } from "util";
|
|
2556
2637
|
import { z as z7 } from "zod";
|
|
2557
2638
|
async function readRecord(root, id) {
|
|
2558
2639
|
const raw = await readStoreJson(root, recordPath(id));
|
|
@@ -2747,10 +2828,12 @@ async function reviewAdvisory(rootDir, input) {
|
|
|
2747
2828
|
};
|
|
2748
2829
|
});
|
|
2749
2830
|
}
|
|
2750
|
-
var
|
|
2831
|
+
var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
|
|
2751
2832
|
var init_advisory_review = __esm({
|
|
2752
2833
|
"src/audit/advisory-review.ts"() {
|
|
2753
2834
|
"use strict";
|
|
2835
|
+
init_inspection();
|
|
2836
|
+
init_git_read();
|
|
2754
2837
|
init_findings();
|
|
2755
2838
|
init_deps_changed();
|
|
2756
2839
|
init_inputs();
|
|
@@ -2760,8 +2843,7 @@ var init_advisory_review = __esm({
|
|
|
2760
2843
|
init_store();
|
|
2761
2844
|
init_decisions();
|
|
2762
2845
|
init_drift2();
|
|
2763
|
-
|
|
2764
|
-
git2 = async (root, ...args) => (await execute2("git", args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
2846
|
+
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;
|
|
2765
2847
|
hashSchema = z7.string().regex(/^[a-f0-9]{64}$/);
|
|
2766
2848
|
commitSchema2 = z7.string().regex(/^[a-f0-9]{40,64}$/);
|
|
2767
2849
|
evidenceSchema2 = z7.object({
|
|
@@ -2805,12 +2887,12 @@ var init_advisory_review = __esm({
|
|
|
2805
2887
|
|
|
2806
2888
|
// src/audit/audit.ts
|
|
2807
2889
|
import fs12 from "fs/promises";
|
|
2808
|
-
import
|
|
2890
|
+
import path20 from "path";
|
|
2809
2891
|
async function computeAudit(rootDir, options = {}) {
|
|
2810
2892
|
return profilePhase("audit.current", () => auditCurrent(rootDir, options));
|
|
2811
2893
|
}
|
|
2812
2894
|
async function auditCurrent(rootDir, options) {
|
|
2813
|
-
const resolvedRoot =
|
|
2895
|
+
const resolvedRoot = path20.resolve(rootDir);
|
|
2814
2896
|
const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
|
|
2815
2897
|
if (docs.length === 0) return null;
|
|
2816
2898
|
const report = {
|
|
@@ -2844,7 +2926,7 @@ async function auditCurrent(rootDir, options) {
|
|
|
2844
2926
|
}
|
|
2845
2927
|
let decisionsPresent = false;
|
|
2846
2928
|
try {
|
|
2847
|
-
await fs12.access(
|
|
2929
|
+
await fs12.access(path20.join(resolvedRoot, ".mason", "decisions"));
|
|
2848
2930
|
decisionsPresent = true;
|
|
2849
2931
|
} catch {
|
|
2850
2932
|
}
|
|
@@ -2889,11 +2971,11 @@ init_audit();
|
|
|
2889
2971
|
init_types();
|
|
2890
2972
|
init_repair();
|
|
2891
2973
|
init_findings();
|
|
2892
|
-
import
|
|
2974
|
+
import path22 from "path";
|
|
2893
2975
|
|
|
2894
2976
|
// src/audit/advisory-cli.ts
|
|
2895
2977
|
init_advisory_review();
|
|
2896
|
-
import
|
|
2978
|
+
import path21 from "path";
|
|
2897
2979
|
import { stripVTControlCharacters } from "util";
|
|
2898
2980
|
var USAGE = `Usage: mason audit review --baseline <path> --finding <id> [options]
|
|
2899
2981
|
|
|
@@ -2939,7 +3021,7 @@ async function runAdvisoryCli(args, io = { out: (text2) => console.log(text2), e
|
|
|
2939
3021
|
note: flags["--note"],
|
|
2940
3022
|
reviewToken: flags["--token"]
|
|
2941
3023
|
};
|
|
2942
|
-
const result = await reviewAdvisory(
|
|
3024
|
+
const result = await reviewAdvisory(path21.resolve(flags["--dir"] ?? process.cwd()), input);
|
|
2943
3025
|
if (json) io.out(JSON.stringify(result, null, 2));
|
|
2944
3026
|
else {
|
|
2945
3027
|
const lines = [`Advisory review: ${result.status}.`];
|
|
@@ -3172,7 +3254,7 @@ async function runAuditCli(argv, io = {
|
|
|
3172
3254
|
io.out(USAGE2);
|
|
3173
3255
|
return 0;
|
|
3174
3256
|
}
|
|
3175
|
-
const rootDir =
|
|
3257
|
+
const rootDir = path22.resolve(args.dir);
|
|
3176
3258
|
if (args.baseline || args.prepareRepair) {
|
|
3177
3259
|
try {
|
|
3178
3260
|
if (args.baseline) {
|