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-mcp.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/audit/commands.ts
|
|
35
61
|
import path from "path";
|
|
36
62
|
function splitCommands(line) {
|
|
@@ -255,14 +281,14 @@ function extractClaims(content2) {
|
|
|
255
281
|
const ignored = computeIgnoredLines(lines);
|
|
256
282
|
const paths = /* @__PURE__ */ new Map();
|
|
257
283
|
const counts = [];
|
|
258
|
-
const
|
|
284
|
+
const commands2 = /* @__PURE__ */ new Map();
|
|
259
285
|
const addPath = (claim) => {
|
|
260
286
|
const key = [claim.relativeTo ?? "scope", claim.path].join(":");
|
|
261
287
|
if (!paths.has(key)) paths.set(key, claim);
|
|
262
288
|
};
|
|
263
289
|
const addCommand = (claim) => {
|
|
264
290
|
const key = JSON.stringify([claim.scriptName, claim.directory, claim.scopeUnknown]);
|
|
265
|
-
if (!
|
|
291
|
+
if (!commands2.has(key)) commands2.set(key, claim);
|
|
266
292
|
};
|
|
267
293
|
const parsed = markdown.parse(lines.map((line, i) => ignored[i] ? "" : line).join("\n"), {});
|
|
268
294
|
for (const token of parsed) {
|
|
@@ -364,7 +390,7 @@ function extractClaims(content2) {
|
|
|
364
390
|
return {
|
|
365
391
|
paths: [...paths.values()],
|
|
366
392
|
counts,
|
|
367
|
-
commands: [...
|
|
393
|
+
commands: [...commands2.values()]
|
|
368
394
|
};
|
|
369
395
|
}
|
|
370
396
|
var ROOT_FILE_NAMES, SHELL_FENCE_INFOS, markdown, COUNT_RE, COUNT_DENYLIST_RE, IGNORE_LINE, IGNORE_START, IGNORE_END;
|
|
@@ -414,97 +440,6 @@ var init_claims = __esm({
|
|
|
414
440
|
}
|
|
415
441
|
});
|
|
416
442
|
|
|
417
|
-
// src/audit/git.ts
|
|
418
|
-
import { execFile } from "child_process";
|
|
419
|
-
import { promisify } from "util";
|
|
420
|
-
function parseCommitLine(line) {
|
|
421
|
-
const parts = line.split(" ");
|
|
422
|
-
if (parts.length < 3 || !parts[0]) return null;
|
|
423
|
-
return { hash: parts[0], date: parts[1], subject: parts.slice(2).join(" ") };
|
|
424
|
-
}
|
|
425
|
-
async function lastCommitOf(resolvedRoot, relPath) {
|
|
426
|
-
try {
|
|
427
|
-
const { stdout } = await exec(
|
|
428
|
-
"git",
|
|
429
|
-
["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
430
|
-
{ cwd: resolvedRoot }
|
|
431
|
-
);
|
|
432
|
-
const line = stdout.trim().split("\n")[0];
|
|
433
|
-
return line ? parseCommitLine(line) : null;
|
|
434
|
-
} catch {
|
|
435
|
-
return null;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
async function deletingCommitOf(resolvedRoot, relPath) {
|
|
439
|
-
try {
|
|
440
|
-
const { stdout } = await exec(
|
|
441
|
-
"git",
|
|
442
|
-
[
|
|
443
|
-
"log",
|
|
444
|
-
"-1",
|
|
445
|
-
"--diff-filter=D",
|
|
446
|
-
`--format=${COMMIT_FORMAT}`,
|
|
447
|
-
"--",
|
|
448
|
-
`:(literal)${relPath}`
|
|
449
|
-
],
|
|
450
|
-
{ cwd: resolvedRoot }
|
|
451
|
-
);
|
|
452
|
-
const line = stdout.trim().split("\n")[0];
|
|
453
|
-
return line ? parseCommitLine(line) : null;
|
|
454
|
-
} catch {
|
|
455
|
-
return null;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
async function firstCommitOf(resolvedRoot, relPath) {
|
|
459
|
-
try {
|
|
460
|
-
const { stdout } = await exec(
|
|
461
|
-
"git",
|
|
462
|
-
["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
463
|
-
{ cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
|
|
464
|
-
);
|
|
465
|
-
const line = stdout.trim().split("\n")[0];
|
|
466
|
-
return line ? parseCommitLine(line) : null;
|
|
467
|
-
} catch {
|
|
468
|
-
return null;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
|
|
472
|
-
if (!fromHash || fromHash === "unknown") return null;
|
|
473
|
-
try {
|
|
474
|
-
const { stdout } = await exec(
|
|
475
|
-
"git",
|
|
476
|
-
[
|
|
477
|
-
"log",
|
|
478
|
-
`${fromHash}..HEAD`,
|
|
479
|
-
`--format=%x01${COMMIT_FORMAT}`,
|
|
480
|
-
"--name-only",
|
|
481
|
-
"--",
|
|
482
|
-
...pathspecs
|
|
483
|
-
],
|
|
484
|
-
{ cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
|
|
485
|
-
);
|
|
486
|
-
const commits = [];
|
|
487
|
-
for (const block of stdout.split("")) {
|
|
488
|
-
if (!block.trim()) continue;
|
|
489
|
-
const lines = block.split("\n").filter((l) => l.trim().length > 0);
|
|
490
|
-
const ref = parseCommitLine(lines[0]);
|
|
491
|
-
if (!ref) continue;
|
|
492
|
-
commits.push({ ...ref, files: lines.slice(1).map((l) => l.trim()) });
|
|
493
|
-
}
|
|
494
|
-
return { commits, total: commits.length };
|
|
495
|
-
} catch {
|
|
496
|
-
return null;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
var exec, COMMIT_FORMAT;
|
|
500
|
-
var init_git = __esm({
|
|
501
|
-
"src/audit/git.ts"() {
|
|
502
|
-
"use strict";
|
|
503
|
-
exec = promisify(execFile);
|
|
504
|
-
COMMIT_FORMAT = "%H%x09%cI%x09%s";
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
|
|
508
443
|
// src/utils/paths.ts
|
|
509
444
|
import path2 from "path";
|
|
510
445
|
function normalizeRepoPath(value) {
|
|
@@ -539,8 +474,6 @@ var init_paths = __esm({
|
|
|
539
474
|
import fs from "fs/promises";
|
|
540
475
|
import { constants } from "fs";
|
|
541
476
|
import path3 from "path";
|
|
542
|
-
import { execFile as execFile2 } from "child_process";
|
|
543
|
-
import { promisify as promisify2 } from "util";
|
|
544
477
|
import fg from "fast-glob";
|
|
545
478
|
function isSensitiveFile(file) {
|
|
546
479
|
return file.split(/[\\/]/).some(
|
|
@@ -594,12 +527,12 @@ async function createFileAccess(rootDir) {
|
|
|
594
527
|
const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
|
|
595
528
|
let gitFiles = null;
|
|
596
529
|
try {
|
|
597
|
-
const { stdout } = await
|
|
530
|
+
const { stdout } = await execGit(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
|
|
598
531
|
gitFiles = new Set(stdout.split("\0").filter(Boolean));
|
|
599
532
|
} catch {
|
|
600
533
|
let inGit = false;
|
|
601
534
|
try {
|
|
602
|
-
await
|
|
535
|
+
await execGit(["rev-parse", "--git-dir"], { cwd: root });
|
|
603
536
|
inGit = true;
|
|
604
537
|
} catch {
|
|
605
538
|
}
|
|
@@ -642,12 +575,12 @@ async function createFileAccess(rootDir) {
|
|
|
642
575
|
}
|
|
643
576
|
return { root, config, list, read };
|
|
644
577
|
}
|
|
645
|
-
var
|
|
578
|
+
var SOURCE_EXTENSIONS, SOURCE_GLOB, SOURCE_IGNORE, MAX_SOURCE_BYTES;
|
|
646
579
|
var init_files = __esm({
|
|
647
580
|
"src/utils/files.ts"() {
|
|
648
581
|
"use strict";
|
|
582
|
+
init_git_read();
|
|
649
583
|
init_paths();
|
|
650
|
-
exec2 = promisify2(execFile2);
|
|
651
584
|
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"];
|
|
652
585
|
SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(",")}}`;
|
|
653
586
|
SOURCE_IGNORE = [
|
|
@@ -677,9 +610,140 @@ var init_files = __esm({
|
|
|
677
610
|
}
|
|
678
611
|
});
|
|
679
612
|
|
|
613
|
+
// src/audit/inspection.ts
|
|
614
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
615
|
+
import path4 from "path";
|
|
616
|
+
function withRepositoryInspection(root, run) {
|
|
617
|
+
return inspections.run({ root: path4.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
|
|
618
|
+
}
|
|
619
|
+
function inspectionRead(root, key, read) {
|
|
620
|
+
const inspection = inspections.getStore();
|
|
621
|
+
if (!inspection || inspection.root !== path4.resolve(root)) return read();
|
|
622
|
+
if (!inspection.reads.has(key)) {
|
|
623
|
+
const pending = read();
|
|
624
|
+
inspection.reads.set(key, pending);
|
|
625
|
+
void pending.catch(() => {
|
|
626
|
+
if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
return inspection.reads.get(key);
|
|
630
|
+
}
|
|
631
|
+
function inspectionGit(args, options) {
|
|
632
|
+
return inspectionRead(options.cwd, "git:" + JSON.stringify([args, options]), () => execGit(args, options));
|
|
633
|
+
}
|
|
634
|
+
async function auditGitPaths(root, kind) {
|
|
635
|
+
const shared = inspections.getStore()?.root === path4.resolve(root);
|
|
636
|
+
const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
|
|
637
|
+
const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
|
|
638
|
+
const { stdout } = await inspectionGit(
|
|
639
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
|
|
640
|
+
{ cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
|
|
641
|
+
);
|
|
642
|
+
const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? /^(readme|agents|claude)\.md$/i.test(path4.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
|
|
643
|
+
if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
|
|
644
|
+
return paths.sort();
|
|
645
|
+
}
|
|
646
|
+
var inspections, sourceSpecs, docSpecs, sourceSuffixes;
|
|
647
|
+
var init_inspection = __esm({
|
|
648
|
+
"src/audit/inspection.ts"() {
|
|
649
|
+
"use strict";
|
|
650
|
+
init_git_read();
|
|
651
|
+
init_files();
|
|
652
|
+
inspections = new AsyncLocalStorage2();
|
|
653
|
+
sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
|
|
654
|
+
docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
|
|
655
|
+
sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// src/audit/git.ts
|
|
660
|
+
function parseCommitLine(line) {
|
|
661
|
+
const parts = line.split(" ");
|
|
662
|
+
if (parts.length < 3 || !parts[0]) return null;
|
|
663
|
+
return { hash: parts[0], date: parts[1], subject: parts.slice(2).join(" ") };
|
|
664
|
+
}
|
|
665
|
+
async function lastCommitOf(resolvedRoot, relPath) {
|
|
666
|
+
try {
|
|
667
|
+
const { stdout } = await inspectionGit(
|
|
668
|
+
["log", "-1", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
669
|
+
{ cwd: resolvedRoot }
|
|
670
|
+
);
|
|
671
|
+
const line = stdout.trim().split("\n")[0];
|
|
672
|
+
return line ? parseCommitLine(line) : null;
|
|
673
|
+
} catch {
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async function deletingCommitOf(resolvedRoot, relPath) {
|
|
678
|
+
try {
|
|
679
|
+
const { stdout } = await inspectionGit(
|
|
680
|
+
[
|
|
681
|
+
"log",
|
|
682
|
+
"-1",
|
|
683
|
+
"--diff-filter=D",
|
|
684
|
+
`--format=${COMMIT_FORMAT}`,
|
|
685
|
+
"--",
|
|
686
|
+
`:(literal)${relPath}`
|
|
687
|
+
],
|
|
688
|
+
{ cwd: resolvedRoot }
|
|
689
|
+
);
|
|
690
|
+
const line = stdout.trim().split("\n")[0];
|
|
691
|
+
return line ? parseCommitLine(line) : null;
|
|
692
|
+
} catch {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async function firstCommitOf(resolvedRoot, relPath) {
|
|
697
|
+
try {
|
|
698
|
+
const { stdout } = await inspectionGit(
|
|
699
|
+
["log", "--reverse", `--format=${COMMIT_FORMAT}`, "--", `:(literal)${relPath}`],
|
|
700
|
+
{ cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
|
|
701
|
+
);
|
|
702
|
+
const line = stdout.trim().split("\n")[0];
|
|
703
|
+
return line ? parseCommitLine(line) : null;
|
|
704
|
+
} catch {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
|
|
709
|
+
if (!fromHash || fromHash === "unknown") return null;
|
|
710
|
+
try {
|
|
711
|
+
const { stdout } = await inspectionGit(
|
|
712
|
+
[
|
|
713
|
+
"log",
|
|
714
|
+
`${fromHash}..HEAD`,
|
|
715
|
+
`--format=%x01${COMMIT_FORMAT}`,
|
|
716
|
+
"--name-only",
|
|
717
|
+
"--",
|
|
718
|
+
...pathspecs
|
|
719
|
+
],
|
|
720
|
+
{ cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }
|
|
721
|
+
);
|
|
722
|
+
const commits = [];
|
|
723
|
+
for (const block of stdout.split("")) {
|
|
724
|
+
if (!block.trim()) continue;
|
|
725
|
+
const lines = block.split("\n").filter((l) => l.trim().length > 0);
|
|
726
|
+
const ref = parseCommitLine(lines[0]);
|
|
727
|
+
if (!ref) continue;
|
|
728
|
+
commits.push({ ...ref, files: lines.slice(1).map((l) => l.trim()) });
|
|
729
|
+
}
|
|
730
|
+
return { commits, total: commits.length };
|
|
731
|
+
} catch {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
var COMMIT_FORMAT;
|
|
736
|
+
var init_git = __esm({
|
|
737
|
+
"src/audit/git.ts"() {
|
|
738
|
+
"use strict";
|
|
739
|
+
init_inspection();
|
|
740
|
+
COMMIT_FORMAT = "%H%x09%cI%x09%s";
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
|
|
680
744
|
// src/utils/storage.ts
|
|
681
745
|
import fs2 from "fs/promises";
|
|
682
|
-
import
|
|
746
|
+
import path5 from "path";
|
|
683
747
|
import { randomUUID } from "crypto";
|
|
684
748
|
async function storePath(root, relative, createParents = false) {
|
|
685
749
|
const normalized = normalizeRepoPath(relative);
|
|
@@ -687,7 +751,7 @@ async function storePath(root, relative, createParents = false) {
|
|
|
687
751
|
let current = await fs2.realpath(root);
|
|
688
752
|
const parts = normalized.split("/");
|
|
689
753
|
for (let i = 0; i < parts.length; i++) {
|
|
690
|
-
current =
|
|
754
|
+
current = path5.join(current, parts[i]);
|
|
691
755
|
let stat;
|
|
692
756
|
try {
|
|
693
757
|
stat = await fs2.lstat(current);
|
|
@@ -725,7 +789,7 @@ async function writeStoreJson(root, relative, value) {
|
|
|
725
789
|
throw new Error(`Mason store ${relative} exceeds 10 MiB`);
|
|
726
790
|
}
|
|
727
791
|
const file = await storePath(root, relative, true);
|
|
728
|
-
const temporary =
|
|
792
|
+
const temporary = path5.join(path5.dirname(file), `.${path5.basename(file)}.${randomUUID()}.tmp`);
|
|
729
793
|
try {
|
|
730
794
|
const handle = await fs2.open(temporary, "wx", 384);
|
|
731
795
|
try {
|
|
@@ -750,9 +814,7 @@ var init_storage = __esm({
|
|
|
750
814
|
// src/audit/inputs.ts
|
|
751
815
|
import fs3 from "fs/promises";
|
|
752
816
|
import * as nativeFs from "fs";
|
|
753
|
-
import
|
|
754
|
-
import { execFile as execFile3 } from "child_process";
|
|
755
|
-
import { promisify as promisify3 } from "util";
|
|
817
|
+
import path6 from "path";
|
|
756
818
|
import fg2 from "fast-glob";
|
|
757
819
|
async function auditInputPath(root, file) {
|
|
758
820
|
if (file === ".") return fs3.realpath(root);
|
|
@@ -779,7 +841,7 @@ async function auditGlob(root, patterns, options) {
|
|
|
779
841
|
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
780
842
|
const directories = /* @__PURE__ */ new Set();
|
|
781
843
|
const readdir2 = ((directory, opts, callback) => {
|
|
782
|
-
const relative =
|
|
844
|
+
const relative = path6.relative(root, directory).split(path6.sep).join("/") || ".";
|
|
783
845
|
directories.add(relative);
|
|
784
846
|
if (directories.size > MAX_INPUTS) {
|
|
785
847
|
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
@@ -790,7 +852,7 @@ async function auditGlob(root, patterns, options) {
|
|
|
790
852
|
}, callback);
|
|
791
853
|
});
|
|
792
854
|
const lstat2 = ((file, callback) => {
|
|
793
|
-
const relative =
|
|
855
|
+
const relative = path6.relative(root, file).split(path6.sep).join("/") || ".";
|
|
794
856
|
auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
|
|
795
857
|
});
|
|
796
858
|
const stream = fg2.stream(patterns, {
|
|
@@ -827,62 +889,48 @@ async function auditGlob(root, patterns, options) {
|
|
|
827
889
|
return matches.sort();
|
|
828
890
|
}
|
|
829
891
|
async function gitSourcePaths(root) {
|
|
830
|
-
|
|
831
|
-
"git",
|
|
832
|
-
[
|
|
833
|
-
"ls-files",
|
|
834
|
-
"-z",
|
|
835
|
-
"--cached",
|
|
836
|
-
"--others",
|
|
837
|
-
"--exclude-standard",
|
|
838
|
-
"--",
|
|
839
|
-
...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
|
|
840
|
-
],
|
|
841
|
-
{ cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
|
|
842
|
-
);
|
|
843
|
-
return [...new Set(stdout.split("\0").filter(Boolean))].sort();
|
|
892
|
+
return auditGitPaths(root, "sources");
|
|
844
893
|
}
|
|
845
894
|
async function gitIgnoredPaths(root, files) {
|
|
846
895
|
if (!files.length) return /* @__PURE__ */ new Set();
|
|
847
|
-
const
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
896
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
897
|
+
for (let offset = 0; offset < files.length; offset += 1e3) {
|
|
898
|
+
try {
|
|
899
|
+
const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
|
|
900
|
+
cwd: root,
|
|
901
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
902
|
+
timeout: 1e4,
|
|
903
|
+
input: files.slice(offset, offset + 1e3).join("\0") + "\0"
|
|
904
|
+
});
|
|
905
|
+
for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
|
|
906
|
+
} catch (error) {
|
|
907
|
+
if (error.code !== 1) throw error;
|
|
908
|
+
}
|
|
860
909
|
}
|
|
910
|
+
return ignored;
|
|
861
911
|
}
|
|
862
|
-
var
|
|
912
|
+
var MAX_INPUTS;
|
|
863
913
|
var init_inputs = __esm({
|
|
864
914
|
"src/audit/inputs.ts"() {
|
|
865
915
|
"use strict";
|
|
866
916
|
init_files();
|
|
867
917
|
init_storage();
|
|
868
|
-
|
|
918
|
+
init_git_read();
|
|
919
|
+
init_inspection();
|
|
869
920
|
MAX_INPUTS = 1e5;
|
|
870
921
|
}
|
|
871
922
|
});
|
|
872
923
|
|
|
873
924
|
// src/audit/docs.ts
|
|
874
925
|
import fs4 from "fs/promises";
|
|
875
|
-
import
|
|
876
|
-
import { execFile as execFile4 } from "child_process";
|
|
877
|
-
import { promisify as promisify4 } from "util";
|
|
926
|
+
import path7 from "path";
|
|
878
927
|
import fg3 from "fast-glob";
|
|
879
928
|
async function dirtyDocs(resolvedRoot, files) {
|
|
880
929
|
if (!files.length) return /* @__PURE__ */ new Set();
|
|
881
930
|
try {
|
|
882
931
|
const dirty = /* @__PURE__ */ new Set();
|
|
883
932
|
for (let offset = 0; offset < files.length; offset += 256) {
|
|
884
|
-
const { stdout } = await
|
|
885
|
-
"git",
|
|
933
|
+
const { stdout } = await execGit(
|
|
886
934
|
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
|
|
887
935
|
{ cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
888
936
|
);
|
|
@@ -902,25 +950,10 @@ async function dirtyDocs(resolvedRoot, files) {
|
|
|
902
950
|
async function discoverDocPaths(root) {
|
|
903
951
|
let candidates = [];
|
|
904
952
|
try {
|
|
905
|
-
|
|
906
|
-
"git",
|
|
907
|
-
[
|
|
908
|
-
"ls-files",
|
|
909
|
-
"-z",
|
|
910
|
-
"--cached",
|
|
911
|
-
"--others",
|
|
912
|
-
"--exclude-standard",
|
|
913
|
-
"--",
|
|
914
|
-
":(icase,glob)**/readme.md",
|
|
915
|
-
":(icase,glob)**/agents.md",
|
|
916
|
-
":(icase,glob)**/claude.md"
|
|
917
|
-
],
|
|
918
|
-
{ cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
919
|
-
);
|
|
920
|
-
candidates = stdout.split("\0").filter(Boolean);
|
|
953
|
+
candidates = await auditGitPaths(root, "documents");
|
|
921
954
|
} catch (error) {
|
|
922
955
|
try {
|
|
923
|
-
await
|
|
956
|
+
await execGit(["rev-parse", "--git-dir"], { cwd: root });
|
|
924
957
|
} catch {
|
|
925
958
|
return localDocPaths(root);
|
|
926
959
|
}
|
|
@@ -967,14 +1000,14 @@ async function localDocPaths(root) {
|
|
|
967
1000
|
throw error;
|
|
968
1001
|
}
|
|
969
1002
|
for (const entry of entries) if (/^(?:agents|claude|readme)\.md$/i.test(entry.name)) {
|
|
970
|
-
paths.push(
|
|
1003
|
+
paths.push(path7.posix.join(dir, entry.name));
|
|
971
1004
|
}
|
|
972
1005
|
}
|
|
973
1006
|
return paths;
|
|
974
1007
|
}
|
|
975
1008
|
function documentScope(file) {
|
|
976
|
-
const parent =
|
|
977
|
-
return
|
|
1009
|
+
const parent = path7.posix.dirname(file);
|
|
1010
|
+
return path7.posix.basename(parent).toLowerCase() === ".claude" ? path7.posix.dirname(parent) : parent;
|
|
978
1011
|
}
|
|
979
1012
|
async function discoverDocs(resolvedRoot) {
|
|
980
1013
|
return profilePhase("audit.documents", () => readDocs(resolvedRoot));
|
|
@@ -994,7 +1027,7 @@ async function readDocs(resolvedRoot) {
|
|
|
994
1027
|
path: candidate,
|
|
995
1028
|
content: content2,
|
|
996
1029
|
scope: documentScope(candidate),
|
|
997
|
-
kind: /^readme\.md$/i.test(
|
|
1030
|
+
kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : "instructions",
|
|
998
1031
|
lineCount: content2.split("\n").length,
|
|
999
1032
|
lastCommit,
|
|
1000
1033
|
dirty: dirty.has(candidate),
|
|
@@ -1004,16 +1037,17 @@ async function readDocs(resolvedRoot) {
|
|
|
1004
1037
|
}
|
|
1005
1038
|
return docs;
|
|
1006
1039
|
}
|
|
1007
|
-
var
|
|
1040
|
+
var DOC_CANDIDATES;
|
|
1008
1041
|
var init_docs = __esm({
|
|
1009
1042
|
"src/audit/docs.ts"() {
|
|
1010
1043
|
"use strict";
|
|
1044
|
+
init_git_read();
|
|
1011
1045
|
init_profile();
|
|
1012
1046
|
init_claims();
|
|
1013
1047
|
init_git();
|
|
1014
1048
|
init_inputs();
|
|
1015
1049
|
init_files();
|
|
1016
|
-
|
|
1050
|
+
init_inspection();
|
|
1017
1051
|
DOC_CANDIDATES = [
|
|
1018
1052
|
"AGENTS.md",
|
|
1019
1053
|
"CLAUDE.md",
|
|
@@ -1024,14 +1058,14 @@ var init_docs = __esm({
|
|
|
1024
1058
|
|
|
1025
1059
|
// src/audit/scope.ts
|
|
1026
1060
|
import fs5 from "fs/promises";
|
|
1027
|
-
import
|
|
1061
|
+
import path8 from "path";
|
|
1028
1062
|
function scopedPath(directory, value) {
|
|
1029
|
-
if (
|
|
1030
|
-
const resolved =
|
|
1063
|
+
if (path8.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
|
|
1064
|
+
const resolved = path8.posix.normalize(path8.posix.join(directory, value));
|
|
1031
1065
|
return resolved === ".." || resolved.startsWith("../") ? null : resolved;
|
|
1032
1066
|
}
|
|
1033
1067
|
function pathClaimScope(doc, claim) {
|
|
1034
|
-
const directory = claim.relativeTo === "document" ?
|
|
1068
|
+
const directory = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
|
|
1035
1069
|
const local = scopedPath(directory, claim.path);
|
|
1036
1070
|
if (!local) return null;
|
|
1037
1071
|
const candidates = claim.relativeTo === "document" || directory === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
|
|
@@ -1064,14 +1098,113 @@ var init_scope = __esm({
|
|
|
1064
1098
|
}
|
|
1065
1099
|
});
|
|
1066
1100
|
|
|
1101
|
+
// src/utils/store-lock.ts
|
|
1102
|
+
import fs6 from "fs/promises";
|
|
1103
|
+
import os from "os";
|
|
1104
|
+
async function lockDiagnostic(file) {
|
|
1105
|
+
let reason2;
|
|
1106
|
+
try {
|
|
1107
|
+
const owner = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
1108
|
+
if (!owner || typeof owner.host !== "string" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
|
|
1109
|
+
reason2 = "Lock owner data is incomplete or malformed.";
|
|
1110
|
+
} else if (owner.host !== os.hostname()) {
|
|
1111
|
+
reason2 = "The lock belongs to another host; its process cannot be checked locally.";
|
|
1112
|
+
} else {
|
|
1113
|
+
try {
|
|
1114
|
+
process.kill(owner.pid, 0);
|
|
1115
|
+
reason2 = `Local owner PID ${owner.pid} is still running.`;
|
|
1116
|
+
} catch (error) {
|
|
1117
|
+
reason2 = error.code === "ESRCH" ? `Local owner PID ${owner.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.pid} could not be checked.`;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
reason2 = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
|
|
1122
|
+
}
|
|
1123
|
+
if (await fs6.lstat(file + ".reclaim").then(() => true, () => false)) {
|
|
1124
|
+
reason2 += " A recovery guard also needs inspection: " + file + ".reclaim.";
|
|
1125
|
+
}
|
|
1126
|
+
return reason2 + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
|
|
1127
|
+
}
|
|
1128
|
+
async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason store") {
|
|
1129
|
+
const file = await storePath(root, directory + "/lock", true);
|
|
1130
|
+
const deadline = Date.now() + waitMs;
|
|
1131
|
+
let handle;
|
|
1132
|
+
while (!handle) {
|
|
1133
|
+
try {
|
|
1134
|
+
handle = await fs6.open(file, "wx", 384);
|
|
1135
|
+
try {
|
|
1136
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
await handle.close().catch(() => {
|
|
1139
|
+
});
|
|
1140
|
+
handle = void 0;
|
|
1141
|
+
await fs6.rm(file, { force: true }).catch(() => {
|
|
1142
|
+
});
|
|
1143
|
+
throw error;
|
|
1144
|
+
}
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
if (error.code !== "EEXIST") throw error;
|
|
1147
|
+
try {
|
|
1148
|
+
const owner = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
1149
|
+
if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
|
|
1150
|
+
try {
|
|
1151
|
+
process.kill(owner.pid, 0);
|
|
1152
|
+
} catch (probe) {
|
|
1153
|
+
if (probe.code === "ESRCH") {
|
|
1154
|
+
const reclaim = file + ".reclaim";
|
|
1155
|
+
let guard;
|
|
1156
|
+
try {
|
|
1157
|
+
guard = await fs6.open(reclaim, "wx", 384);
|
|
1158
|
+
const current = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
1159
|
+
if (current.pid === owner.pid && current.host === owner.host) await fs6.unlink(file);
|
|
1160
|
+
} finally {
|
|
1161
|
+
if (guard) {
|
|
1162
|
+
await guard.close();
|
|
1163
|
+
await fs6.rm(reclaim, { force: true });
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
} catch {
|
|
1170
|
+
}
|
|
1171
|
+
if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
|
|
1172
|
+
await new Promise((resolve2) => setTimeout(resolve2, 40));
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
try {
|
|
1176
|
+
return await run();
|
|
1177
|
+
} finally {
|
|
1178
|
+
await handle.close();
|
|
1179
|
+
await fs6.unlink(file);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
var init_store_lock = __esm({
|
|
1183
|
+
"src/utils/store-lock.ts"() {
|
|
1184
|
+
"use strict";
|
|
1185
|
+
init_storage();
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
// src/snapshot/lock.ts
|
|
1190
|
+
function withSnapshotWrite(root, run, waitMs = 5e3) {
|
|
1191
|
+
return withStoreLock(root, ".mason/local/snapshot-write", run, waitMs, "Snapshot store");
|
|
1192
|
+
}
|
|
1193
|
+
var init_lock = __esm({
|
|
1194
|
+
"src/snapshot/lock.ts"() {
|
|
1195
|
+
"use strict";
|
|
1196
|
+
init_store_lock();
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1067
1200
|
// src/test-map.ts
|
|
1068
1201
|
var test_map_exports = {};
|
|
1069
1202
|
__export(test_map_exports, {
|
|
1070
1203
|
buildTestMap: () => buildTestMap
|
|
1071
1204
|
});
|
|
1072
|
-
import
|
|
1205
|
+
import path9 from "path";
|
|
1073
1206
|
async function buildTestMap(dir) {
|
|
1074
|
-
const rootDir =
|
|
1207
|
+
const rootDir = path9.resolve(dir);
|
|
1075
1208
|
const access = await createFileAccess(rootDir);
|
|
1076
1209
|
const testPatterns = [
|
|
1077
1210
|
"**/*.test.*",
|
|
@@ -1092,7 +1225,7 @@ async function buildTestMap(dir) {
|
|
|
1092
1225
|
const sourceByBaseName = /* @__PURE__ */ new Map();
|
|
1093
1226
|
for (const file of sourceFiles) {
|
|
1094
1227
|
if (testFiles.includes(file)) continue;
|
|
1095
|
-
const baseName =
|
|
1228
|
+
const baseName = path9.basename(file).replace(/\.[^.]+$/, "");
|
|
1096
1229
|
const existing = sourceByBaseName.get(baseName) ?? [];
|
|
1097
1230
|
existing.push(file);
|
|
1098
1231
|
sourceByBaseName.set(baseName, existing);
|
|
@@ -1100,7 +1233,7 @@ async function buildTestMap(dir) {
|
|
|
1100
1233
|
const paired = [];
|
|
1101
1234
|
const unmatched = [];
|
|
1102
1235
|
for (const testFile of testFiles) {
|
|
1103
|
-
const testBaseName =
|
|
1236
|
+
const testBaseName = path9.basename(testFile).replace(/\.[^.]+$/, "");
|
|
1104
1237
|
const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
|
|
1105
1238
|
if (!sourceName) {
|
|
1106
1239
|
unmatched.push(testFile);
|
|
@@ -1108,10 +1241,10 @@ async function buildTestMap(dir) {
|
|
|
1108
1241
|
}
|
|
1109
1242
|
const candidates = sourceByBaseName.get(sourceName);
|
|
1110
1243
|
if (candidates && candidates.length > 0) {
|
|
1111
|
-
const testDir =
|
|
1244
|
+
const testDir = path9.dirname(testFile);
|
|
1112
1245
|
const bestMatch = candidates.reduce((best, candidate) => {
|
|
1113
|
-
const candidateDir =
|
|
1114
|
-
const bestDir =
|
|
1246
|
+
const candidateDir = path9.dirname(candidate);
|
|
1247
|
+
const bestDir = path9.dirname(best);
|
|
1115
1248
|
const candidateOverlap = commonSegments(testDir, candidateDir);
|
|
1116
1249
|
const bestOverlap = commonSegments(testDir, bestDir);
|
|
1117
1250
|
return candidateOverlap > bestOverlap ? candidate : best;
|
|
@@ -1145,9 +1278,7 @@ var init_test_map = __esm({
|
|
|
1145
1278
|
});
|
|
1146
1279
|
|
|
1147
1280
|
// src/snapshot/snapshot.ts
|
|
1148
|
-
import
|
|
1149
|
-
import { execFile as execFile5 } from "child_process";
|
|
1150
|
-
import { promisify as promisify5 } from "util";
|
|
1281
|
+
import path10 from "path";
|
|
1151
1282
|
import { z } from "zod";
|
|
1152
1283
|
function normalizeFeatureType(value) {
|
|
1153
1284
|
return value === "infrastructure" ? "infrastructure" : "capability";
|
|
@@ -1171,12 +1302,19 @@ async function inspectSnapshot(rootDir) {
|
|
|
1171
1302
|
}] };
|
|
1172
1303
|
}
|
|
1173
1304
|
}
|
|
1174
|
-
async function
|
|
1175
|
-
|
|
1305
|
+
async function updateSnapshot(rootDir, update) {
|
|
1306
|
+
return withSnapshotWrite(rootDir, async () => {
|
|
1307
|
+
const change = await update(await loadSnapshot(rootDir));
|
|
1308
|
+
if (change.snapshot) {
|
|
1309
|
+
await writeStoreJson(rootDir, ".mason/snapshot.json", snapshotSchema.parse(change.snapshot));
|
|
1310
|
+
await change.afterSave?.();
|
|
1311
|
+
}
|
|
1312
|
+
return change.result;
|
|
1313
|
+
});
|
|
1176
1314
|
}
|
|
1177
1315
|
async function getCurrentGitHash(rootDir) {
|
|
1178
1316
|
try {
|
|
1179
|
-
const { stdout } = await
|
|
1317
|
+
const { stdout } = await execGit(["rev-parse", "HEAD"], {
|
|
1180
1318
|
cwd: rootDir
|
|
1181
1319
|
});
|
|
1182
1320
|
return stdout.trim();
|
|
@@ -1188,7 +1326,7 @@ async function listSourceFiles(resolvedRoot) {
|
|
|
1188
1326
|
return (await createFileAccess(resolvedRoot)).list();
|
|
1189
1327
|
}
|
|
1190
1328
|
async function prepareSnapshotBatch(rootDir, offset, batchSize = DEFAULT_BATCH_SIZE, scopeFiles) {
|
|
1191
|
-
const resolvedRoot =
|
|
1329
|
+
const resolvedRoot = path10.resolve(rootDir);
|
|
1192
1330
|
const access = await createFileAccess(resolvedRoot);
|
|
1193
1331
|
let allFiles = await access.list();
|
|
1194
1332
|
if (scopeFiles) {
|
|
@@ -1237,21 +1375,23 @@ async function prepareSnapshotBatch(rootDir, offset, batchSize = DEFAULT_BATCH_S
|
|
|
1237
1375
|
testPairs
|
|
1238
1376
|
};
|
|
1239
1377
|
}
|
|
1240
|
-
var
|
|
1378
|
+
var repoPath, verificationFields, featureSchema, flowSchema, snapshotSchema, DEFAULT_BATCH_SIZE, SKELETON_CHARS, DEEP_SAMPLE_CHARS, DEEP_SAMPLES_PER_BATCH;
|
|
1241
1379
|
var init_snapshot = __esm({
|
|
1242
1380
|
"src/snapshot/snapshot.ts"() {
|
|
1243
1381
|
"use strict";
|
|
1382
|
+
init_git_read();
|
|
1244
1383
|
init_files();
|
|
1245
1384
|
init_files();
|
|
1246
1385
|
init_storage();
|
|
1386
|
+
init_lock();
|
|
1247
1387
|
init_paths();
|
|
1248
1388
|
init_test_map();
|
|
1249
|
-
exec4 = promisify5(execFile5);
|
|
1250
1389
|
repoPath = z.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
|
|
1251
1390
|
verificationFields = {
|
|
1252
1391
|
refreshedHash: z.string().optional(),
|
|
1253
1392
|
verifiedAt: z.string().optional(),
|
|
1254
1393
|
verifiedHash: z.string().optional(),
|
|
1394
|
+
verificationToken: z.string().optional(),
|
|
1255
1395
|
verificationFailed: z.boolean().optional(),
|
|
1256
1396
|
verificationNote: z.string().optional()
|
|
1257
1397
|
};
|
|
@@ -1279,10 +1419,8 @@ var init_snapshot = __esm({
|
|
|
1279
1419
|
});
|
|
1280
1420
|
|
|
1281
1421
|
// src/drift/drift.ts
|
|
1282
|
-
import
|
|
1283
|
-
import
|
|
1284
|
-
import { execFile as execFile6 } from "child_process";
|
|
1285
|
-
import { promisify as promisify6 } from "util";
|
|
1422
|
+
import fs7 from "fs/promises";
|
|
1423
|
+
import path11 from "path";
|
|
1286
1424
|
function parseChanges(output) {
|
|
1287
1425
|
const fields = output.split("\0");
|
|
1288
1426
|
const changes = [];
|
|
@@ -1303,7 +1441,7 @@ function touchedPaths(changes) {
|
|
|
1303
1441
|
async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
|
|
1304
1442
|
if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-") || !toHash || toHash === "unknown" || toHash.startsWith("-")) return null;
|
|
1305
1443
|
try {
|
|
1306
|
-
const { stdout } = await
|
|
1444
|
+
const { stdout } = await execGit(["diff", "--name-status", "-z", "-M", fromHash, toHash, "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 });
|
|
1307
1445
|
return parseChanges(stdout);
|
|
1308
1446
|
} catch {
|
|
1309
1447
|
return null;
|
|
@@ -1312,8 +1450,8 @@ async function getChangesWithStatus(resolvedRoot, fromHash, toHash = "HEAD") {
|
|
|
1312
1450
|
async function getWorkingTree(resolvedRoot) {
|
|
1313
1451
|
try {
|
|
1314
1452
|
const [diff, untracked] = await Promise.all([
|
|
1315
|
-
|
|
1316
|
-
|
|
1453
|
+
execGit(["diff", "--name-status", "-z", "-M", "HEAD", "--"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 }),
|
|
1454
|
+
execGit(["ls-files", "-z", "--others", "--exclude-standard"], { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 })
|
|
1317
1455
|
]);
|
|
1318
1456
|
const untrackedFiles = untracked.stdout.split("\0").filter((f) => f && !f.startsWith(".mason/"));
|
|
1319
1457
|
return { available: true, changedFiles: [.../* @__PURE__ */ new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles])].sort(), untrackedFiles };
|
|
@@ -1324,8 +1462,7 @@ async function getWorkingTree(resolvedRoot) {
|
|
|
1324
1462
|
async function countCommitsBehind(resolvedRoot, fromHash) {
|
|
1325
1463
|
if (!fromHash || fromHash === "unknown" || fromHash.startsWith("-")) return null;
|
|
1326
1464
|
try {
|
|
1327
|
-
const { stdout } = await
|
|
1328
|
-
"git",
|
|
1465
|
+
const { stdout } = await execGit(
|
|
1329
1466
|
["rev-list", "--count", `${fromHash}..HEAD`],
|
|
1330
1467
|
{ cwd: resolvedRoot }
|
|
1331
1468
|
);
|
|
@@ -1350,7 +1487,7 @@ async function findGhostFiles(resolvedRoot, mappedFiles) {
|
|
|
1350
1487
|
const ghosts = [];
|
|
1351
1488
|
for (const file of mappedFiles) {
|
|
1352
1489
|
try {
|
|
1353
|
-
await
|
|
1490
|
+
await fs7.access(path11.join(resolvedRoot, file));
|
|
1354
1491
|
} catch {
|
|
1355
1492
|
ghosts.push(file);
|
|
1356
1493
|
}
|
|
@@ -1358,7 +1495,7 @@ async function findGhostFiles(resolvedRoot, mappedFiles) {
|
|
|
1358
1495
|
return ghosts.sort();
|
|
1359
1496
|
}
|
|
1360
1497
|
async function computeDrift(rootDir) {
|
|
1361
|
-
const root =
|
|
1498
|
+
const root = path11.resolve(rootDir);
|
|
1362
1499
|
const snapshot = await loadSnapshot(root);
|
|
1363
1500
|
if (!snapshot) return null;
|
|
1364
1501
|
const [headHash, workingTree] = await Promise.all([getCurrentGitHash(root), getWorkingTree(root)]);
|
|
@@ -1415,7 +1552,7 @@ async function computeDrift(rootDir) {
|
|
|
1415
1552
|
const sourceFiles = new Set(await listSourceFiles(root));
|
|
1416
1553
|
let committedFiles = /* @__PURE__ */ new Set();
|
|
1417
1554
|
try {
|
|
1418
|
-
const { stdout } = await
|
|
1555
|
+
const { stdout } = await execGit(["ls-tree", "-r", "--name-only", "-z", "HEAD"], { cwd: root, maxBuffer: 50 * 1024 * 1024 });
|
|
1419
1556
|
committedFiles = new Set(stdout.split("\0").filter(Boolean));
|
|
1420
1557
|
} catch {
|
|
1421
1558
|
report.historyAvailable = false;
|
|
@@ -1435,13 +1572,13 @@ async function computeDrift(rootDir) {
|
|
|
1435
1572
|
else report.recommendation = changedMapped.size >= FULL_REBUILD_MIN_CHANGED_MAPPED_FILES && changedMapped.size / Math.max(1, mappedFiles.size) > FULL_REBUILD_FRACTION ? "full-rebuild" : "incremental";
|
|
1436
1573
|
return report;
|
|
1437
1574
|
}
|
|
1438
|
-
var
|
|
1575
|
+
var FULL_REBUILD_FRACTION, FULL_REBUILD_MIN_CHANGED_MAPPED_FILES;
|
|
1439
1576
|
var init_drift = __esm({
|
|
1440
1577
|
"src/drift/drift.ts"() {
|
|
1441
1578
|
"use strict";
|
|
1579
|
+
init_git_read();
|
|
1442
1580
|
init_snapshot();
|
|
1443
1581
|
init_paths();
|
|
1444
|
-
exec5 = promisify6(execFile6);
|
|
1445
1582
|
FULL_REBUILD_FRACTION = 0.4;
|
|
1446
1583
|
FULL_REBUILD_MIN_CHANGED_MAPPED_FILES = 10;
|
|
1447
1584
|
}
|
|
@@ -1464,7 +1601,7 @@ var init_types = __esm({
|
|
|
1464
1601
|
});
|
|
1465
1602
|
|
|
1466
1603
|
// src/audit/checks/deleted-reference.ts
|
|
1467
|
-
import
|
|
1604
|
+
import path12 from "path";
|
|
1468
1605
|
async function checkDeletedReferences(ctx) {
|
|
1469
1606
|
const result = emptyResult();
|
|
1470
1607
|
for (const doc of ctx.docs) {
|
|
@@ -1493,7 +1630,7 @@ async function checkDeletedReferences(ctx) {
|
|
|
1493
1630
|
const candidates = await Promise.all(scope.candidates.map(async (file) => ({
|
|
1494
1631
|
file,
|
|
1495
1632
|
tracked: await lastCommitOf(ctx.root, file),
|
|
1496
|
-
parent: await pathExists(ctx.root,
|
|
1633
|
+
parent: await pathExists(ctx.root, path12.posix.dirname(file))
|
|
1497
1634
|
})));
|
|
1498
1635
|
const candidate = candidates.find((c) => c.tracked || renames.has(c.file)) ?? candidates.find((c) => c.parent);
|
|
1499
1636
|
if (!candidate && claim.relativeTo !== "document") continue;
|
|
@@ -1532,8 +1669,8 @@ var init_deleted_reference = __esm({
|
|
|
1532
1669
|
|
|
1533
1670
|
// src/audit/checks/new-module.ts
|
|
1534
1671
|
import fg4 from "fast-glob";
|
|
1535
|
-
import
|
|
1536
|
-
import
|
|
1672
|
+
import fs8 from "fs/promises";
|
|
1673
|
+
import path13 from "path";
|
|
1537
1674
|
function escapeRegExp(text2) {
|
|
1538
1675
|
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1539
1676
|
}
|
|
@@ -1571,23 +1708,36 @@ async function checkNewModules(ctx) {
|
|
|
1571
1708
|
return result;
|
|
1572
1709
|
}
|
|
1573
1710
|
function moduleDocumentation(docs) {
|
|
1574
|
-
return docs.map((doc) => doc.content + "\n" + (
|
|
1711
|
+
return docs.map((doc) => doc.content + "\n" + (path13.posix.dirname(doc.path) === "." ? "" : path13.posix.dirname(doc.path) + "/")).join("\n");
|
|
1575
1712
|
}
|
|
1576
|
-
|
|
1713
|
+
function moduleCandidates(root, combinedDocs) {
|
|
1714
|
+
return inspectionRead(root, "modules:" + combinedDocs, () => collectModuleCandidates(root, combinedDocs));
|
|
1715
|
+
}
|
|
1716
|
+
async function collectModuleCandidates(root, combinedDocs) {
|
|
1577
1717
|
const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
|
|
1578
1718
|
const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
|
|
1579
1719
|
const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
|
|
1580
|
-
const listSubdirs = async (
|
|
1581
|
-
const
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1720
|
+
const listSubdirs = async (parents) => {
|
|
1721
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
1722
|
+
for (let offset = 0; offset < parents.length; offset += 16) {
|
|
1723
|
+
await Promise.all(parents.slice(offset, offset + 16).map(async (dir) => {
|
|
1724
|
+
const entries = await fs8.readdir(await auditInputPath(root, dir), { withFileTypes: true });
|
|
1725
|
+
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));
|
|
1726
|
+
byParent.set(dir, dirs);
|
|
1727
|
+
}));
|
|
1728
|
+
}
|
|
1729
|
+
const ignored = await gitIgnoredPaths(root, [...byParent.values()].flat());
|
|
1730
|
+
const result = /* @__PURE__ */ new Map();
|
|
1731
|
+
for (const dir of parents) {
|
|
1732
|
+
const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
|
|
1733
|
+
result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg4.escapePath(dir)}/*`, {
|
|
1734
|
+
ignore,
|
|
1735
|
+
onlyDirectories: true,
|
|
1736
|
+
label: "Module directory discovery",
|
|
1737
|
+
select: (file) => visible.has(file)
|
|
1738
|
+
})).map((file) => path13.posix.basename(file)));
|
|
1739
|
+
}
|
|
1740
|
+
return result;
|
|
1591
1741
|
};
|
|
1592
1742
|
const countSourceFiles = async (dir) => {
|
|
1593
1743
|
const files = sources.filter((file) => file.startsWith(dir + "/"));
|
|
@@ -1603,14 +1753,16 @@ async function moduleCandidates(root, combinedDocs) {
|
|
|
1603
1753
|
return count4;
|
|
1604
1754
|
};
|
|
1605
1755
|
const candidates = [];
|
|
1606
|
-
|
|
1756
|
+
const topDirs = (await listSubdirs(["."])).get(".");
|
|
1757
|
+
const nested = await listSubdirs(topDirs.filter((dir) => isMentioned(combinedDocs, dir)));
|
|
1758
|
+
for (const topDir of topDirs) {
|
|
1607
1759
|
const topMentioned = isMentioned(combinedDocs, topDir);
|
|
1608
1760
|
if (!topMentioned) {
|
|
1609
1761
|
const count4 = await countSourceFiles(topDir);
|
|
1610
1762
|
if (count4 >= 1) candidates.push({ dir: topDir, sourceFileCount: count4 });
|
|
1611
1763
|
continue;
|
|
1612
1764
|
}
|
|
1613
|
-
const subdirs =
|
|
1765
|
+
const subdirs = nested.get(topDir);
|
|
1614
1766
|
const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
|
|
1615
1767
|
if (mentioned.length < ENUMERATION_THRESHOLD) continue;
|
|
1616
1768
|
for (const sub of subdirs) {
|
|
@@ -1627,6 +1779,7 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
|
|
|
1627
1779
|
var init_new_module = __esm({
|
|
1628
1780
|
"src/audit/checks/new-module.ts"() {
|
|
1629
1781
|
"use strict";
|
|
1782
|
+
init_inspection();
|
|
1630
1783
|
init_files();
|
|
1631
1784
|
init_inputs();
|
|
1632
1785
|
init_git();
|
|
@@ -1656,7 +1809,7 @@ var init_new_module = __esm({
|
|
|
1656
1809
|
});
|
|
1657
1810
|
|
|
1658
1811
|
// src/audit/checks/stale-count.ts
|
|
1659
|
-
import
|
|
1812
|
+
import path14 from "path";
|
|
1660
1813
|
async function countGradleModules(root) {
|
|
1661
1814
|
for (const name of ["settings.gradle.kts", "settings.gradle"]) {
|
|
1662
1815
|
const content2 = await readAuditInput(root, name);
|
|
@@ -1690,7 +1843,7 @@ async function countNpmWorkspaces(root) {
|
|
|
1690
1843
|
return {
|
|
1691
1844
|
actual: matched.length,
|
|
1692
1845
|
countedFrom: "package.json workspaces",
|
|
1693
|
-
members: matched.map((m) =>
|
|
1846
|
+
members: matched.map((m) => path14.dirname(m)).sort()
|
|
1694
1847
|
};
|
|
1695
1848
|
}
|
|
1696
1849
|
}
|
|
@@ -1721,7 +1874,7 @@ async function countNpmWorkspaces(root) {
|
|
|
1721
1874
|
return {
|
|
1722
1875
|
actual: matched.length,
|
|
1723
1876
|
countedFrom: "pnpm-workspace.yaml",
|
|
1724
|
-
members: matched.map((m) =>
|
|
1877
|
+
members: matched.map((m) => path14.dirname(m)).sort()
|
|
1725
1878
|
};
|
|
1726
1879
|
}
|
|
1727
1880
|
}
|
|
@@ -1743,8 +1896,8 @@ async function countCargoCrates(root) {
|
|
|
1743
1896
|
ignore: ["**/target/**"],
|
|
1744
1897
|
label: "Cargo workspace discovery"
|
|
1745
1898
|
});
|
|
1746
|
-
for (const m of matched) members.add(
|
|
1747
|
-
} else if (await readAuditInput(root,
|
|
1899
|
+
for (const m of matched) members.add(path14.dirname(m));
|
|
1900
|
+
} else if (await readAuditInput(root, path14.posix.join(entry, "Cargo.toml")) !== null) {
|
|
1748
1901
|
members.add(entry);
|
|
1749
1902
|
}
|
|
1750
1903
|
}
|
|
@@ -1764,7 +1917,7 @@ async function resolveCountSource(root, claim) {
|
|
|
1764
1917
|
}
|
|
1765
1918
|
async function resolveDocCountSource(root, doc, claim) {
|
|
1766
1919
|
const scope = documentScope(doc);
|
|
1767
|
-
const source = await resolveCountSource(
|
|
1920
|
+
const source = await resolveCountSource(path14.join(root, scope), claim);
|
|
1768
1921
|
if (!source || scope === ".") return source;
|
|
1769
1922
|
return {
|
|
1770
1923
|
...source,
|
|
@@ -1816,7 +1969,7 @@ var init_stale_count = __esm({
|
|
|
1816
1969
|
});
|
|
1817
1970
|
|
|
1818
1971
|
// src/audit/checks/dead-command.ts
|
|
1819
|
-
import
|
|
1972
|
+
import path15 from "path";
|
|
1820
1973
|
function scriptsOf(raw) {
|
|
1821
1974
|
try {
|
|
1822
1975
|
const pkg = JSON.parse(raw);
|
|
@@ -1852,7 +2005,7 @@ async function commandInputs(root, docs) {
|
|
|
1852
2005
|
}
|
|
1853
2006
|
let cursor = directory;
|
|
1854
2007
|
while (true) {
|
|
1855
|
-
const manifest =
|
|
2008
|
+
const manifest = path15.posix.join(cursor, "package.json");
|
|
1856
2009
|
scope.candidates.push(manifest);
|
|
1857
2010
|
const raw = await read(manifest);
|
|
1858
2011
|
if (raw !== null) {
|
|
@@ -1863,7 +2016,7 @@ async function commandInputs(root, docs) {
|
|
|
1863
2016
|
break;
|
|
1864
2017
|
}
|
|
1865
2018
|
if (claim.directory !== void 0 || cursor === ".") break;
|
|
1866
|
-
cursor =
|
|
2019
|
+
cursor = path15.posix.dirname(cursor);
|
|
1867
2020
|
}
|
|
1868
2021
|
if (!resolution.manifest && !resolution.problem) {
|
|
1869
2022
|
resolution.problem = `No package.json for the command's scope (${directory}).`;
|
|
@@ -1926,10 +2079,8 @@ var init_dead_command = __esm({
|
|
|
1926
2079
|
});
|
|
1927
2080
|
|
|
1928
2081
|
// src/audit/release-metadata.ts
|
|
1929
|
-
import { execFile as execFile7 } from "child_process";
|
|
1930
|
-
import { promisify as promisify7 } from "util";
|
|
1931
2082
|
async function git(root, args) {
|
|
1932
|
-
return (await
|
|
2083
|
+
return (await execGit(args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
|
|
1933
2084
|
}
|
|
1934
2085
|
function withoutAndroidReleaseValues(text2) {
|
|
1935
2086
|
if (/\/\*|"""|'''/.test(text2) || !/id\s*\(?\s*["']com\.android\.(application|library)["']/.test(text2)) return null;
|
|
@@ -1975,11 +2126,10 @@ async function releaseMetadataOnly(root, commit) {
|
|
|
1975
2126
|
return false;
|
|
1976
2127
|
}
|
|
1977
2128
|
}
|
|
1978
|
-
var exec6;
|
|
1979
2129
|
var init_release_metadata = __esm({
|
|
1980
2130
|
"src/audit/release-metadata.ts"() {
|
|
1981
2131
|
"use strict";
|
|
1982
|
-
|
|
2132
|
+
init_git_read();
|
|
1983
2133
|
}
|
|
1984
2134
|
});
|
|
1985
2135
|
|
|
@@ -2075,7 +2225,7 @@ var init_deps_changed = __esm({
|
|
|
2075
2225
|
});
|
|
2076
2226
|
|
|
2077
2227
|
// src/context/lexical.ts
|
|
2078
|
-
import
|
|
2228
|
+
import path16 from "path";
|
|
2079
2229
|
function tokenize(text2) {
|
|
2080
2230
|
return text2.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2 && !STOPWORDS.has(t));
|
|
2081
2231
|
}
|
|
@@ -2088,7 +2238,7 @@ function tokenSet(text2) {
|
|
|
2088
2238
|
function scoreEntry(taskTokens, entry) {
|
|
2089
2239
|
const nameTokens = tokenSet(entry.name);
|
|
2090
2240
|
const descTokens = tokenSet(entry.description);
|
|
2091
|
-
const fileTokens = tokenSet(entry.files.map((f) =>
|
|
2241
|
+
const fileTokens = tokenSet(entry.files.map((f) => path16.basename(f)).join(" "));
|
|
2092
2242
|
let score = 0;
|
|
2093
2243
|
for (const token of taskTokens) {
|
|
2094
2244
|
if (nameTokens.has(token)) score += 3;
|
|
@@ -2194,7 +2344,9 @@ function assessTrust(entry, freshness) {
|
|
|
2194
2344
|
}
|
|
2195
2345
|
function trustHint(states) {
|
|
2196
2346
|
const parts = [];
|
|
2197
|
-
if (states.some((s) => s.verification === "failed")) parts.push("Verification failed for returned entries; do not rely on those descriptions until corrected.");
|
|
2347
|
+
if (states.some((s) => s.verification === "failed" || s.recordedVerdict === "failed")) parts.push("Verification failed for returned entries; do not rely on those descriptions until corrected.");
|
|
2348
|
+
if (states.some((s) => s.verification === "stale")) parts.push("Recorded verification evidence changed; use verify_snapshot and review the current evidence before reusing those verdicts.");
|
|
2349
|
+
if (states.some((s) => s.verification === "unknown")) parts.push("Recorded verification evidence cannot be confirmed; inspect the files and obtain a fresh review before relying on those verdicts.");
|
|
2198
2350
|
if (states.some((s) => s.freshness === "unknown")) parts.push("Freshness is unknown for some returned entries; inspect their files before relying on them.");
|
|
2199
2351
|
if (states.some((s) => s.freshness === "changed")) parts.push("Some returned entries have changed files, including possible local edits; verify against the current code.");
|
|
2200
2352
|
if (!parts.length) parts.push("No changes detected in the returned anchors. This does not prove the descriptions are correct.");
|
|
@@ -2419,8 +2571,8 @@ __export(decisions_exports, {
|
|
|
2419
2571
|
upsertDecision: () => upsertDecision,
|
|
2420
2572
|
withDecisionWrite: () => withDecisionWrite
|
|
2421
2573
|
});
|
|
2422
|
-
import
|
|
2423
|
-
import
|
|
2574
|
+
import fs9 from "fs/promises";
|
|
2575
|
+
import path17 from "path";
|
|
2424
2576
|
import { createHash } from "crypto";
|
|
2425
2577
|
import { z as z3 } from "zod";
|
|
2426
2578
|
async function loadDecisionStore(rootDir) {
|
|
@@ -2428,7 +2580,7 @@ async function loadDecisionStore(rootDir) {
|
|
|
2428
2580
|
const diagnostics = [];
|
|
2429
2581
|
let entries;
|
|
2430
2582
|
try {
|
|
2431
|
-
entries = await
|
|
2583
|
+
entries = await fs9.readdir(await storePath(rootDir, ".mason/decisions"));
|
|
2432
2584
|
} catch (error) {
|
|
2433
2585
|
if (error.code !== "ENOENT") diagnostics.push({ path: ".mason/decisions", message: String(error) });
|
|
2434
2586
|
return { records, diagnostics };
|
|
@@ -2487,7 +2639,7 @@ async function withDecisionWrite(root, operation) {
|
|
|
2487
2639
|
const lockPath = await storePath(root, ".mason/decisions/.write-lock", true);
|
|
2488
2640
|
let lock;
|
|
2489
2641
|
try {
|
|
2490
|
-
lock = await
|
|
2642
|
+
lock = await fs9.open(lockPath, "wx", 384);
|
|
2491
2643
|
} catch (error) {
|
|
2492
2644
|
if (error.code === "EEXIST") return { status: "error", error: "Decision store is locked by another write. Retry after it finishes; an abandoned .mason/decisions/.write-lock must be removed only after confirming no writer is running." };
|
|
2493
2645
|
throw error;
|
|
@@ -2496,7 +2648,7 @@ async function withDecisionWrite(root, operation) {
|
|
|
2496
2648
|
return await operation();
|
|
2497
2649
|
} finally {
|
|
2498
2650
|
await lock.close();
|
|
2499
|
-
await
|
|
2651
|
+
await fs9.unlink(lockPath);
|
|
2500
2652
|
}
|
|
2501
2653
|
}
|
|
2502
2654
|
async function upsertDecision(rootDir, input) {
|
|
@@ -2518,7 +2670,7 @@ async function upsertDecision(rootDir, input) {
|
|
|
2518
2670
|
if (input.files && files.length < input.files.length) warnings.push("some anchor paths were outside the repo or duplicated and were dropped");
|
|
2519
2671
|
for (const file of files) {
|
|
2520
2672
|
try {
|
|
2521
|
-
await
|
|
2673
|
+
await fs9.access(path17.join(rootDir, file));
|
|
2522
2674
|
} catch {
|
|
2523
2675
|
warnings.push(`anchor file does not exist on disk: ${file}`);
|
|
2524
2676
|
}
|
|
@@ -2643,9 +2795,9 @@ var init_decisions = __esm({
|
|
|
2643
2795
|
});
|
|
2644
2796
|
|
|
2645
2797
|
// src/decisions/drift.ts
|
|
2646
|
-
import
|
|
2798
|
+
import path18 from "path";
|
|
2647
2799
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
2648
|
-
const resolvedRoot =
|
|
2800
|
+
const resolvedRoot = path18.resolve(rootDir);
|
|
2649
2801
|
const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
|
|
2650
2802
|
const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
|
|
2651
2803
|
const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
|
|
@@ -2868,8 +3020,6 @@ var init_findings = __esm({
|
|
|
2868
3020
|
});
|
|
2869
3021
|
|
|
2870
3022
|
// src/automation/store.ts
|
|
2871
|
-
import fs9 from "fs/promises";
|
|
2872
|
-
import os from "os";
|
|
2873
3023
|
import { z as z5 } from "zod";
|
|
2874
3024
|
function parseState(raw) {
|
|
2875
3025
|
try {
|
|
@@ -2878,65 +3028,14 @@ function parseState(raw) {
|
|
|
2878
3028
|
throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
|
|
2879
3029
|
}
|
|
2880
3030
|
}
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
const deadline = Date.now() + waitMs;
|
|
2884
|
-
let handle;
|
|
2885
|
-
while (!handle) {
|
|
2886
|
-
try {
|
|
2887
|
-
handle = await fs9.open(file, "wx", 384);
|
|
2888
|
-
try {
|
|
2889
|
-
await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
|
|
2890
|
-
} catch (error) {
|
|
2891
|
-
await handle.close().catch(() => {
|
|
2892
|
-
});
|
|
2893
|
-
handle = void 0;
|
|
2894
|
-
await fs9.rm(file, { force: true }).catch(() => {
|
|
2895
|
-
});
|
|
2896
|
-
throw error;
|
|
2897
|
-
}
|
|
2898
|
-
} catch (error) {
|
|
2899
|
-
if (error.code !== "EEXIST") throw error;
|
|
2900
|
-
try {
|
|
2901
|
-
const owner = JSON.parse(await fs9.readFile(file, "utf8"));
|
|
2902
|
-
if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
|
|
2903
|
-
try {
|
|
2904
|
-
process.kill(owner.pid, 0);
|
|
2905
|
-
} catch (probe) {
|
|
2906
|
-
if (probe.code === "ESRCH") {
|
|
2907
|
-
const reclaim = file + ".reclaim";
|
|
2908
|
-
let guard;
|
|
2909
|
-
try {
|
|
2910
|
-
guard = await fs9.open(reclaim, "wx", 384);
|
|
2911
|
-
const current = JSON.parse(await fs9.readFile(file, "utf8"));
|
|
2912
|
-
if (current.pid === owner.pid && current.host === owner.host) await fs9.unlink(file);
|
|
2913
|
-
} finally {
|
|
2914
|
-
if (guard) {
|
|
2915
|
-
await guard.close();
|
|
2916
|
-
await fs9.rm(reclaim, { force: true });
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
} catch {
|
|
2923
|
-
}
|
|
2924
|
-
if (Date.now() >= deadline) throw new Error("Automation is busy or its lock needs inspection: " + file);
|
|
2925
|
-
await new Promise((resolve2) => setTimeout(resolve2, 40));
|
|
2926
|
-
}
|
|
2927
|
-
}
|
|
2928
|
-
try {
|
|
2929
|
-
return await run();
|
|
2930
|
-
} finally {
|
|
2931
|
-
await handle.close();
|
|
2932
|
-
await fs9.unlink(file);
|
|
2933
|
-
}
|
|
3031
|
+
function withLock(root, directory, run, waitMs = 5e3) {
|
|
3032
|
+
return withStoreLock(root, directory, run, waitMs, "Automation");
|
|
2934
3033
|
}
|
|
2935
3034
|
var hostSchema, events, stateSchema;
|
|
2936
3035
|
var init_store = __esm({
|
|
2937
3036
|
"src/automation/store.ts"() {
|
|
2938
3037
|
"use strict";
|
|
2939
|
-
|
|
3038
|
+
init_store_lock();
|
|
2940
3039
|
hostSchema = z5.enum(["claude", "codex"]);
|
|
2941
3040
|
events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
|
|
2942
3041
|
stateSchema = z5.object({
|
|
@@ -2967,8 +3066,6 @@ var init_store = __esm({
|
|
|
2967
3066
|
|
|
2968
3067
|
// src/audit/advisory-review.ts
|
|
2969
3068
|
import fs10 from "fs/promises";
|
|
2970
|
-
import { execFile as execFile8 } from "child_process";
|
|
2971
|
-
import { promisify as promisify8 } from "util";
|
|
2972
3069
|
import { z as z6 } from "zod";
|
|
2973
3070
|
async function readRecord(root, id) {
|
|
2974
3071
|
const raw = await readStoreJson(root, recordPath(id));
|
|
@@ -3163,10 +3260,12 @@ async function reviewAdvisory(rootDir, input) {
|
|
|
3163
3260
|
};
|
|
3164
3261
|
});
|
|
3165
3262
|
}
|
|
3166
|
-
var
|
|
3263
|
+
var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
|
|
3167
3264
|
var init_advisory_review = __esm({
|
|
3168
3265
|
"src/audit/advisory-review.ts"() {
|
|
3169
3266
|
"use strict";
|
|
3267
|
+
init_inspection();
|
|
3268
|
+
init_git_read();
|
|
3170
3269
|
init_findings();
|
|
3171
3270
|
init_deps_changed();
|
|
3172
3271
|
init_inputs();
|
|
@@ -3176,8 +3275,7 @@ var init_advisory_review = __esm({
|
|
|
3176
3275
|
init_store();
|
|
3177
3276
|
init_decisions();
|
|
3178
3277
|
init_drift2();
|
|
3179
|
-
|
|
3180
|
-
git2 = async (root, ...args) => (await execute2("git", args, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
3278
|
+
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;
|
|
3181
3279
|
hashSchema = z6.string().regex(/^[a-f0-9]{64}$/);
|
|
3182
3280
|
commitSchema2 = z6.string().regex(/^[a-f0-9]{40,64}$/);
|
|
3183
3281
|
evidenceSchema2 = z6.object({
|
|
@@ -3221,12 +3319,12 @@ var init_advisory_review = __esm({
|
|
|
3221
3319
|
|
|
3222
3320
|
// src/audit/audit.ts
|
|
3223
3321
|
import fs11 from "fs/promises";
|
|
3224
|
-
import
|
|
3322
|
+
import path19 from "path";
|
|
3225
3323
|
async function computeAudit(rootDir, options = {}) {
|
|
3226
3324
|
return profilePhase("audit.current", () => auditCurrent(rootDir, options));
|
|
3227
3325
|
}
|
|
3228
3326
|
async function auditCurrent(rootDir, options) {
|
|
3229
|
-
const resolvedRoot =
|
|
3327
|
+
const resolvedRoot = path19.resolve(rootDir);
|
|
3230
3328
|
const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
|
|
3231
3329
|
if (docs.length === 0) return null;
|
|
3232
3330
|
const report = {
|
|
@@ -3260,7 +3358,7 @@ async function auditCurrent(rootDir, options) {
|
|
|
3260
3358
|
}
|
|
3261
3359
|
let decisionsPresent = false;
|
|
3262
3360
|
try {
|
|
3263
|
-
await fs11.access(
|
|
3361
|
+
await fs11.access(path19.join(resolvedRoot, ".mason", "decisions"));
|
|
3264
3362
|
decisionsPresent = true;
|
|
3265
3363
|
} catch {
|
|
3266
3364
|
}
|
|
@@ -3315,7 +3413,7 @@ __export(repair_exports, {
|
|
|
3315
3413
|
withRepairSession: () => withRepairSession
|
|
3316
3414
|
});
|
|
3317
3415
|
import fs12 from "fs/promises";
|
|
3318
|
-
import
|
|
3416
|
+
import path20 from "path";
|
|
3319
3417
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3320
3418
|
import { z as z7 } from "zod";
|
|
3321
3419
|
function recheckable(finding) {
|
|
@@ -3353,8 +3451,8 @@ async function readStableAudit(root, checks, options) {
|
|
|
3353
3451
|
async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
|
|
3354
3452
|
return prepareOriginal(rootDir, checks, options);
|
|
3355
3453
|
}
|
|
3356
|
-
async function withRepairSession(root, options, run, cachedAudit) {
|
|
3357
|
-
const
|
|
3454
|
+
async function withRepairSession(root, options, run, validate, cachedAudit) {
|
|
3455
|
+
const head = await getCurrentGitHash(root);
|
|
3358
3456
|
const audits = /* @__PURE__ */ new Map();
|
|
3359
3457
|
const documents = /* @__PURE__ */ new Map();
|
|
3360
3458
|
const history = /* @__PURE__ */ new Map();
|
|
@@ -3365,9 +3463,9 @@ async function withRepairSession(root, options, run, cachedAudit) {
|
|
|
3365
3463
|
};
|
|
3366
3464
|
const session = {
|
|
3367
3465
|
audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
|
|
3368
|
-
if (cachedAudit && cachedAudit.headHash ===
|
|
3466
|
+
if (cachedAudit && cachedAudit.headHash === head && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
|
|
3369
3467
|
const current = structuredClone(cachedAudit);
|
|
3370
|
-
const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f,
|
|
3468
|
+
const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, head)))).filter((r) => r !== null);
|
|
3371
3469
|
delete current.advisoryReviews;
|
|
3372
3470
|
if (reviews.length) current.advisoryReviews = reviews;
|
|
3373
3471
|
return current;
|
|
@@ -3375,16 +3473,15 @@ async function withRepairSession(root, options, run, cachedAudit) {
|
|
|
3375
3473
|
return computeAudit(root, { ...options, checks });
|
|
3376
3474
|
}),
|
|
3377
3475
|
document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
|
|
3378
|
-
history: (
|
|
3379
|
-
assessment: (dir, finding,
|
|
3476
|
+
history: (head2) => memo(history, head2, () => getChangesWithStatus(root, head2)),
|
|
3477
|
+
assessment: (dir, finding, head2) => memo(assessments, digest([dir, finding, head2]), () => assessAdvisory(dir, finding, head2))
|
|
3380
3478
|
};
|
|
3381
3479
|
const result = await run({
|
|
3382
3480
|
prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
|
|
3383
3481
|
verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
|
|
3384
3482
|
currentAudit: () => session.audit(ALL_CHECKS)
|
|
3385
3483
|
});
|
|
3386
|
-
|
|
3387
|
-
if (digest(before) !== digest(after)) throw new Error("HEAD, context files or advisory review records changed during verification; retry against stable evidence.");
|
|
3484
|
+
await validate();
|
|
3388
3485
|
return result;
|
|
3389
3486
|
}
|
|
3390
3487
|
async function prepareOriginal(rootDir, checks, options, session) {
|
|
@@ -3406,8 +3503,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
|
|
|
3406
3503
|
}
|
|
3407
3504
|
async function loadRepairBaseline(rootDir, baselinePath) {
|
|
3408
3505
|
const root = await fs12.realpath(rootDir);
|
|
3409
|
-
const declaredRoot =
|
|
3410
|
-
const relative =
|
|
3506
|
+
const declaredRoot = path20.resolve(rootDir);
|
|
3507
|
+
const relative = path20.isAbsolute(baselinePath) ? path20.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
|
|
3411
3508
|
const stored = baselineSchema.parse(await readStoreJson(root, relative));
|
|
3412
3509
|
const { digest: savedDigest, ...payload } = stored;
|
|
3413
3510
|
if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
|
|
@@ -3582,14 +3679,12 @@ var init_repair = __esm({
|
|
|
3582
3679
|
|
|
3583
3680
|
// src/automation/evidence.ts
|
|
3584
3681
|
import fs13 from "fs/promises";
|
|
3585
|
-
import
|
|
3682
|
+
import path21 from "path";
|
|
3586
3683
|
import { createHash as createHash3 } from "crypto";
|
|
3587
|
-
import { execFile as execFile9 } from "child_process";
|
|
3588
|
-
import { promisify as promisify9 } from "util";
|
|
3589
3684
|
import fg5 from "fast-glob";
|
|
3590
3685
|
import { z as z8 } from "zod";
|
|
3591
3686
|
async function git3(root, ...args) {
|
|
3592
|
-
return profilePhase("automation.git", async () => (await
|
|
3687
|
+
return profilePhase("automation.git", async () => (await execGit(args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
|
|
3593
3688
|
}
|
|
3594
3689
|
async function workspace(dir) {
|
|
3595
3690
|
const root = await fs13.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
|
|
@@ -3613,7 +3708,7 @@ async function collectInputs(root) {
|
|
|
3613
3708
|
const head = headText.trim();
|
|
3614
3709
|
let shallow = null;
|
|
3615
3710
|
try {
|
|
3616
|
-
shallow = await fs13.readFile(
|
|
3711
|
+
shallow = await fs13.readFile(path21.resolve(root, shallowPath.trim()), "utf8");
|
|
3617
3712
|
} catch (error) {
|
|
3618
3713
|
if (error.code !== "ENOENT") throw error;
|
|
3619
3714
|
}
|
|
@@ -3637,7 +3732,7 @@ async function collectInputs(root) {
|
|
|
3637
3732
|
if (scope?.candidates.some(gitMetadataPath)) continue;
|
|
3638
3733
|
for (const candidate of scope?.candidates ?? []) {
|
|
3639
3734
|
if (optionalMasonPath(candidate)) continue;
|
|
3640
|
-
const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(
|
|
3735
|
+
const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path21.posix.dirname(candidate))]);
|
|
3641
3736
|
claims.push([candidate, filePresent, parentPresent]);
|
|
3642
3737
|
}
|
|
3643
3738
|
}
|
|
@@ -3649,7 +3744,7 @@ async function collectInputs(root) {
|
|
|
3649
3744
|
if (error.code === "ENOENT") return "absent";
|
|
3650
3745
|
throw error;
|
|
3651
3746
|
});
|
|
3652
|
-
const [modules, counts,
|
|
3747
|
+
const [modules, counts, commands2, decisionFiles] = await Promise.all([
|
|
3653
3748
|
combinedDocs ? moduleCandidates(root, combinedDocs) : [],
|
|
3654
3749
|
Promise.all(countClaims.map(({ doc, claim }) => resolveDocCountSource(root, doc, claim))),
|
|
3655
3750
|
commandInputs(root, parsedDocs),
|
|
@@ -3668,7 +3763,7 @@ async function collectInputs(root) {
|
|
|
3668
3763
|
"deleted-reference": hash([common, claims, docStatus]),
|
|
3669
3764
|
"new-module": hash([common, modules]),
|
|
3670
3765
|
"stale-count": hash([common, counts]),
|
|
3671
|
-
"dead-command": hash([common,
|
|
3766
|
+
"dead-command": hash([common, commands2]),
|
|
3672
3767
|
"deps-changed": hash([common, docStatus]),
|
|
3673
3768
|
"decision-anchor-drift": hash([common, decisionPresence, decisions, status, index2])
|
|
3674
3769
|
};
|
|
@@ -3706,10 +3801,11 @@ function checkCache(raw, inputs) {
|
|
|
3706
3801
|
}
|
|
3707
3802
|
};
|
|
3708
3803
|
}
|
|
3709
|
-
var
|
|
3804
|
+
var engineVersion, hash, content, cacheSchema;
|
|
3710
3805
|
var init_evidence = __esm({
|
|
3711
3806
|
"src/automation/evidence.ts"() {
|
|
3712
3807
|
"use strict";
|
|
3808
|
+
init_git_read();
|
|
3713
3809
|
init_profile();
|
|
3714
3810
|
init_docs();
|
|
3715
3811
|
init_scope();
|
|
@@ -3722,8 +3818,7 @@ var init_evidence = __esm({
|
|
|
3722
3818
|
init_dead_command();
|
|
3723
3819
|
init_storage();
|
|
3724
3820
|
init_advisory_review();
|
|
3725
|
-
|
|
3726
|
-
engineVersion = true ? "0.17.3" : "development";
|
|
3821
|
+
engineVersion = true ? "0.18.0" : "development";
|
|
3727
3822
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3728
3823
|
content = readAuditInput;
|
|
3729
3824
|
cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
|
|
@@ -3783,8 +3878,8 @@ var init_model = __esm({
|
|
|
3783
3878
|
});
|
|
3784
3879
|
|
|
3785
3880
|
// src/setup/launcher.ts
|
|
3786
|
-
import { execFile as
|
|
3787
|
-
import { promisify as
|
|
3881
|
+
import { execFile as execFile2 } from "child_process";
|
|
3882
|
+
import { promisify as promisify2 } from "util";
|
|
3788
3883
|
import fs14 from "fs/promises";
|
|
3789
3884
|
function globalCommand(args, platform = process.platform) {
|
|
3790
3885
|
return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args] } : { command: "mason", args };
|
|
@@ -3792,7 +3887,7 @@ function globalCommand(args, platform = process.platform) {
|
|
|
3792
3887
|
async function installedCommand() {
|
|
3793
3888
|
const invocation = globalCommand(["internal-integration-version"]);
|
|
3794
3889
|
try {
|
|
3795
|
-
const { stdout } = await
|
|
3890
|
+
const { stdout } = await promisify2(execFile2)(
|
|
3796
3891
|
invocation.command,
|
|
3797
3892
|
invocation.args,
|
|
3798
3893
|
{ timeout: 5e3, maxBuffer: 8192, windowsHide: true }
|
|
@@ -3815,12 +3910,124 @@ var init_launcher = __esm({
|
|
|
3815
3910
|
"use strict";
|
|
3816
3911
|
init_evidence();
|
|
3817
3912
|
init_model();
|
|
3818
|
-
executingVersion = () => true ? "0.
|
|
3913
|
+
executingVersion = () => true ? "0.18.0" : "development";
|
|
3819
3914
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
3820
3915
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
3821
3916
|
}
|
|
3822
3917
|
});
|
|
3823
3918
|
|
|
3919
|
+
// src/snapshot/review.ts
|
|
3920
|
+
import { createHash as createHash5 } from "crypto";
|
|
3921
|
+
function snapshotEntryContent(entry) {
|
|
3922
|
+
return {
|
|
3923
|
+
description: entry.description,
|
|
3924
|
+
files: "files" in entry ? entry.files : void 0,
|
|
3925
|
+
chain: "chain" in entry ? entry.chain : void 0,
|
|
3926
|
+
tests: "files" in entry ? entry.tests : void 0,
|
|
3927
|
+
type: "files" in entry ? normalizeFeatureType(entry.type) : void 0
|
|
3928
|
+
};
|
|
3929
|
+
}
|
|
3930
|
+
function reviewToken(kind, name, entry, evidence) {
|
|
3931
|
+
return createHash5("sha256").update(JSON.stringify({
|
|
3932
|
+
version: 1,
|
|
3933
|
+
kind,
|
|
3934
|
+
name,
|
|
3935
|
+
entry: snapshotEntryContent(entry),
|
|
3936
|
+
evidence
|
|
3937
|
+
})).digest("hex");
|
|
3938
|
+
}
|
|
3939
|
+
async function prepareSnapshotReview(access, kind, name, entry) {
|
|
3940
|
+
const files = "files" in entry ? entry.files : entry.chain;
|
|
3941
|
+
const evidence = [];
|
|
3942
|
+
const skeletons = [];
|
|
3943
|
+
for (const file of files.slice(0, VERIFY_MAX_FILES_PER_ENTRY)) {
|
|
3944
|
+
const full = await access.read(file);
|
|
3945
|
+
evidence.push({ path: file, hash: full ? sourceHash(full.content) : null });
|
|
3946
|
+
skeletons.push(full ? { path: full.path, content: full.content.slice(0, VERIFY_SKELETON_CHARS) } : { path: file, missing: true });
|
|
3947
|
+
}
|
|
3948
|
+
return { reviewToken: reviewToken(kind, name, entry, evidence), skeletons, truncated: files.length > VERIFY_MAX_FILES_PER_ENTRY };
|
|
3949
|
+
}
|
|
3950
|
+
function createSnapshotEvidenceReader(access) {
|
|
3951
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
3952
|
+
return async (kind, name, entry) => {
|
|
3953
|
+
const files = "files" in entry ? entry.files : entry.chain;
|
|
3954
|
+
const evidence = [];
|
|
3955
|
+
for (const file of files.slice(0, VERIFY_MAX_FILES_PER_ENTRY)) {
|
|
3956
|
+
let hash2 = hashes.get(file);
|
|
3957
|
+
if (!hash2) {
|
|
3958
|
+
hash2 = access.read(file).then((full) => full ? sourceHash(full.content) : null);
|
|
3959
|
+
hashes.set(file, hash2);
|
|
3960
|
+
}
|
|
3961
|
+
evidence.push({ path: file, hash: await hash2 });
|
|
3962
|
+
}
|
|
3963
|
+
return {
|
|
3964
|
+
reviewToken: reviewToken(kind, name, entry, evidence),
|
|
3965
|
+
evidenceAvailable: evidence.length > 0 && evidence.every((file) => file.hash !== null)
|
|
3966
|
+
};
|
|
3967
|
+
};
|
|
3968
|
+
}
|
|
3969
|
+
var VERIFY_MAX_FILES_PER_ENTRY, VERIFY_SKELETON_CHARS, sourceHash;
|
|
3970
|
+
var init_review = __esm({
|
|
3971
|
+
"src/snapshot/review.ts"() {
|
|
3972
|
+
"use strict";
|
|
3973
|
+
init_snapshot();
|
|
3974
|
+
VERIFY_MAX_FILES_PER_ENTRY = 8;
|
|
3975
|
+
VERIFY_SKELETON_CHARS = 500;
|
|
3976
|
+
sourceHash = (content2) => createHash5("sha256").update(content2).digest("hex");
|
|
3977
|
+
}
|
|
3978
|
+
});
|
|
3979
|
+
|
|
3980
|
+
// src/snapshot/trust.ts
|
|
3981
|
+
function createSnapshotTrustReader(root) {
|
|
3982
|
+
let reader;
|
|
3983
|
+
return async (kind, name, entry, freshness) => {
|
|
3984
|
+
const trust = assessTrust(entry, freshness);
|
|
3985
|
+
const verdict = entry.verificationFailed ? "failed" : entry.verifiedAt ? "passed" : void 0;
|
|
3986
|
+
if (!verdict) return trust;
|
|
3987
|
+
trust.recordedVerdict = verdict;
|
|
3988
|
+
const unknown = (reason2) => {
|
|
3989
|
+
trust.verification = "unknown";
|
|
3990
|
+
trust.reasons.push(reason2);
|
|
3991
|
+
return trust;
|
|
3992
|
+
};
|
|
3993
|
+
if (!entry.verificationToken || !/^[a-f0-9]{64}$/.test(entry.verificationToken)) return unknown("The recorded verdict has no supported evidence token; obtain a fresh verify_snapshot review.");
|
|
3994
|
+
try {
|
|
3995
|
+
reader ??= createFileAccess(root).then(createSnapshotEvidenceReader).catch(() => null);
|
|
3996
|
+
const readReview = await reader;
|
|
3997
|
+
if (!readReview) return unknown("Verification source evidence could not be read under the current file policy.");
|
|
3998
|
+
const review = await readReview(kind, name, entry);
|
|
3999
|
+
if (!review.evidenceAvailable) {
|
|
4000
|
+
return unknown("Verification source evidence is missing, excluded, unreadable, or empty; the recorded verdict cannot be confirmed.");
|
|
4001
|
+
}
|
|
4002
|
+
if (review.reviewToken !== entry.verificationToken) {
|
|
4003
|
+
trust.verification = "stale";
|
|
4004
|
+
trust.reasons.push("The entry or sampled source contents differ from the recorded verification evidence; obtain a fresh review.");
|
|
4005
|
+
}
|
|
4006
|
+
return trust;
|
|
4007
|
+
} catch {
|
|
4008
|
+
return unknown("Verification source evidence could not be read under the current file policy.");
|
|
4009
|
+
}
|
|
4010
|
+
};
|
|
4011
|
+
}
|
|
4012
|
+
async function readSnapshotTrustIndex(readTrust, snapshot, drift) {
|
|
4013
|
+
const features = /* @__PURE__ */ Object.create(null), flows = /* @__PURE__ */ Object.create(null);
|
|
4014
|
+
for (const [name, entry] of Object.entries(snapshot.features)) {
|
|
4015
|
+
features[name] = await readTrust("feature", name, entry, drift?.featureFreshness?.[name] ?? "unknown");
|
|
4016
|
+
}
|
|
4017
|
+
for (const [name, entry] of Object.entries(snapshot.flows)) {
|
|
4018
|
+
flows[name] = await readTrust("flow", name, entry, drift?.flowFreshness?.[name] ?? "unknown");
|
|
4019
|
+
}
|
|
4020
|
+
return { features, flows };
|
|
4021
|
+
}
|
|
4022
|
+
var init_trust2 = __esm({
|
|
4023
|
+
"src/snapshot/trust.ts"() {
|
|
4024
|
+
"use strict";
|
|
4025
|
+
init_trust();
|
|
4026
|
+
init_files();
|
|
4027
|
+
init_review();
|
|
4028
|
+
}
|
|
4029
|
+
});
|
|
4030
|
+
|
|
3824
4031
|
// src/setup/files.ts
|
|
3825
4032
|
var files_exports = {};
|
|
3826
4033
|
__export(files_exports, {
|
|
@@ -3829,7 +4036,7 @@ __export(files_exports, {
|
|
|
3829
4036
|
readText: () => readText
|
|
3830
4037
|
});
|
|
3831
4038
|
import fs17 from "fs/promises";
|
|
3832
|
-
import
|
|
4039
|
+
import path25 from "path";
|
|
3833
4040
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3834
4041
|
async function readText(root, file) {
|
|
3835
4042
|
try {
|
|
@@ -3862,7 +4069,7 @@ async function applyEdit(root, edit) {
|
|
|
3862
4069
|
return true;
|
|
3863
4070
|
}
|
|
3864
4071
|
const file = await storePath(root, edit.path, true);
|
|
3865
|
-
const temporary =
|
|
4072
|
+
const temporary = path25.join(path25.dirname(file), ".mason-setup-" + randomUUID3() + ".tmp");
|
|
3866
4073
|
try {
|
|
3867
4074
|
const mode = await fs17.stat(file).then((s) => s.mode & 511, () => 384);
|
|
3868
4075
|
const handle = await fs17.open(temporary, "wx", mode);
|
|
@@ -4164,12 +4371,12 @@ async function matchingAnalysis(ws, state, inputs) {
|
|
|
4164
4371
|
return { analysis: a };
|
|
4165
4372
|
}
|
|
4166
4373
|
async function verifyInputs(ws, inputs) {
|
|
4167
|
-
return profilePhase("automation.revalidate", async () => {
|
|
4374
|
+
return profilePhase("automation.revalidate", () => withRepositoryInspection(ws.root, async () => {
|
|
4168
4375
|
const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
|
|
4169
4376
|
if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
|
|
4170
4377
|
throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
|
|
4171
4378
|
}
|
|
4172
|
-
});
|
|
4379
|
+
}));
|
|
4173
4380
|
}
|
|
4174
4381
|
async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
|
|
4175
4382
|
const baselines = [...state.baselines];
|
|
@@ -4189,7 +4396,10 @@ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDia
|
|
|
4189
4396
|
capture: "unknown",
|
|
4190
4397
|
scope: SCOPE
|
|
4191
4398
|
};
|
|
4192
|
-
if (!baselines.length && Object.values(inputs.docs).every((value) => value === null))
|
|
4399
|
+
if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) {
|
|
4400
|
+
await verifyInputs(ws, inputs);
|
|
4401
|
+
return { baselines, report: empty, cache: null };
|
|
4402
|
+
}
|
|
4193
4403
|
if (ws.branch === "detached" && state.latest) {
|
|
4194
4404
|
const previous = await readStoreJson(ws.root, state.latest);
|
|
4195
4405
|
if (!previous?.head || !/^[a-f0-9]{40,64}$/.test(previous.head)) throw new Error("The previous detached checkout evidence is unavailable.");
|
|
@@ -4227,7 +4437,7 @@ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDia
|
|
|
4227
4437
|
}
|
|
4228
4438
|
currentAudit = await repairs.currentAudit();
|
|
4229
4439
|
return verifications2;
|
|
4230
|
-
}, cachedAudit);
|
|
4440
|
+
}, () => verifyInputs(ws, inputs), cachedAudit);
|
|
4231
4441
|
const merged = /* @__PURE__ */ new Map();
|
|
4232
4442
|
for (const verification of verifications) {
|
|
4233
4443
|
for (const finding of verification.findings) {
|
|
@@ -4262,7 +4472,6 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
4262
4472
|
if (previous && previous.completedAt >= startedAt) return { analysis: previous, shared: true };
|
|
4263
4473
|
const originalDigest = await baselineDigest(ws, before);
|
|
4264
4474
|
const computed = await computeAnalysis(ws, before, inputs, event, previous?.currentAudit, diagnostic);
|
|
4265
|
-
await verifyInputs(ws, inputs);
|
|
4266
4475
|
if (originalDigest !== await baselineDigest(ws, before)) throw new Error("Repair baseline was modified during automation; original evidence must be inspected.");
|
|
4267
4476
|
const id = randomUUID5();
|
|
4268
4477
|
const analysis = analysisSchema.parse({
|
|
@@ -4291,7 +4500,7 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
4291
4500
|
async function automate(dir, event) {
|
|
4292
4501
|
const startedAt = Date.now();
|
|
4293
4502
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
4294
|
-
return recordExecution(ws.root, ws.directory, event.event, async () => {
|
|
4503
|
+
return recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
|
|
4295
4504
|
const inputs = await readInputs(ws.root);
|
|
4296
4505
|
const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
|
|
4297
4506
|
if (result.shared) {
|
|
@@ -4364,7 +4573,7 @@ async function automate(dir, event) {
|
|
|
4364
4573
|
await writeStoreJson(ws.root, ws.directory + "/state.json", state);
|
|
4365
4574
|
return { report, message: notify ? summarize(report) : null, continueOnce };
|
|
4366
4575
|
}));
|
|
4367
|
-
});
|
|
4576
|
+
}));
|
|
4368
4577
|
}
|
|
4369
4578
|
async function automationStatus(dir) {
|
|
4370
4579
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
@@ -4402,6 +4611,7 @@ var init_runtime = __esm({
|
|
|
4402
4611
|
"src/automation/runtime.ts"() {
|
|
4403
4612
|
"use strict";
|
|
4404
4613
|
init_profile();
|
|
4614
|
+
init_inspection();
|
|
4405
4615
|
init_execution();
|
|
4406
4616
|
init_repair();
|
|
4407
4617
|
init_types();
|
|
@@ -4447,7 +4657,7 @@ var init_runtime = __esm({
|
|
|
4447
4657
|
|
|
4448
4658
|
// src/automation/hook-root.ts
|
|
4449
4659
|
import fs19 from "fs/promises";
|
|
4450
|
-
import
|
|
4660
|
+
import path26 from "path";
|
|
4451
4661
|
var init_hook_root = __esm({
|
|
4452
4662
|
"src/automation/hook-root.ts"() {
|
|
4453
4663
|
"use strict";
|
|
@@ -4852,7 +5062,7 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
|
|
|
4852
5062
|
- Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
|
|
4853
5063
|
- Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` before finishing. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
|
|
4854
5064
|
- Before saving, compare the lesson with matching \`get_context\` records and any pending proposal. If new evidence changes the same lesson's assumptions, scope, or recommended action, revise it with \`save_decision(id: existingId)\`, including when it is accepted. Preserve supported rationale and sources, replace obsolete instructions, and add the new evidence. Create a separate record for a genuinely distinct lesson; skip unchanged restatements. Revision preserves history and leaves the accepted version operative until authorized review.
|
|
4855
|
-
- Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, and failed
|
|
5065
|
+
- Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, stale or unknown map verification requires a fresh evidence review, and a recorded failed verdict means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
|
|
4856
5066
|
- Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
|
|
4857
5067
|
- For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
|
|
4858
5068
|
- \`mason_init\` returns documentation audit and committed-diff review results, plus a short setup guide. Pass \`evidence\` with local CI manifest paths to include test and analysis results; the CLI equivalent is \`mason-review --evidence <manifest>\`. State skipped, unavailable, stale, or unknown checks explicitly. Related accepted decisions identify review context, not proven violations.
|
|
@@ -4916,13 +5126,13 @@ If a build is interrupted, its partials remain in \`.mason/partial-snapshots/\`.
|
|
|
4916
5126
|
});
|
|
4917
5127
|
|
|
4918
5128
|
// src/review/cochange.ts
|
|
4919
|
-
import { execFile as
|
|
4920
|
-
import { promisify as
|
|
5129
|
+
import { execFile as execFile6 } from "child_process";
|
|
5130
|
+
import { promisify as promisify6 } from "util";
|
|
4921
5131
|
async function buildMatrix(resolvedRoot) {
|
|
4922
5132
|
try {
|
|
4923
|
-
const { stdout: shallow } = await
|
|
5133
|
+
const { stdout: shallow } = await exec4("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
|
|
4924
5134
|
if (shallow.trim() === "true") return null;
|
|
4925
|
-
const { stdout } = await
|
|
5135
|
+
const { stdout } = await exec4(
|
|
4926
5136
|
"git",
|
|
4927
5137
|
["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
|
|
4928
5138
|
{ cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
|
|
@@ -4978,11 +5188,11 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
|
|
|
4978
5188
|
findings.sort((a, b) => b.rate - a.rate || b.sharedCommits - a.sharedCommits);
|
|
4979
5189
|
return findings;
|
|
4980
5190
|
}
|
|
4981
|
-
var
|
|
5191
|
+
var exec4, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_RATE, MAX_COMMIT_FILES;
|
|
4982
5192
|
var init_cochange = __esm({
|
|
4983
5193
|
"src/review/cochange.ts"() {
|
|
4984
5194
|
"use strict";
|
|
4985
|
-
|
|
5195
|
+
exec4 = promisify6(execFile6);
|
|
4986
5196
|
HISTORY_COMMITS = 1500;
|
|
4987
5197
|
MIN_FILE_COMMITS = 5;
|
|
4988
5198
|
MIN_SHARED_COMMITS = 4;
|
|
@@ -5290,10 +5500,10 @@ var init_check = __esm({
|
|
|
5290
5500
|
});
|
|
5291
5501
|
|
|
5292
5502
|
// src/review/evidence.ts
|
|
5293
|
-
import
|
|
5503
|
+
import path27 from "path";
|
|
5294
5504
|
import { z as z22 } from "zod";
|
|
5295
5505
|
function relativeArtifact(root, file) {
|
|
5296
|
-
const relative =
|
|
5506
|
+
const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
|
|
5297
5507
|
const normalized = normalizeRepoPath(relative);
|
|
5298
5508
|
if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
|
|
5299
5509
|
return normalized;
|
|
@@ -5390,7 +5600,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
5390
5600
|
const report = await readStoreJson(root, file);
|
|
5391
5601
|
if (report === null) throw new Error(`Report artifact is missing: ${file}`);
|
|
5392
5602
|
const sourceRoot = check.sourceRoot ?? root;
|
|
5393
|
-
if (!
|
|
5603
|
+
if (!path27.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
|
|
5394
5604
|
const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
|
|
5395
5605
|
result.outcome = parsed.outcome;
|
|
5396
5606
|
result.counts = parsed.counts;
|
|
@@ -5512,12 +5722,12 @@ var init_evidence2 = __esm({
|
|
|
5512
5722
|
|
|
5513
5723
|
// src/review/review.ts
|
|
5514
5724
|
import fs20 from "fs/promises";
|
|
5515
|
-
import
|
|
5516
|
-
import { execFile as
|
|
5517
|
-
import { promisify as
|
|
5725
|
+
import path28 from "path";
|
|
5726
|
+
import { execFile as execFile7 } from "child_process";
|
|
5727
|
+
import { promisify as promisify7 } from "util";
|
|
5518
5728
|
async function resolveMergeBase(resolvedRoot, base, head) {
|
|
5519
5729
|
try {
|
|
5520
|
-
const { stdout } = await
|
|
5730
|
+
const { stdout } = await exec5("git", ["merge-base", base, head], {
|
|
5521
5731
|
cwd: resolvedRoot
|
|
5522
5732
|
});
|
|
5523
5733
|
return stdout.trim() || null;
|
|
@@ -5528,7 +5738,7 @@ async function resolveMergeBase(resolvedRoot, base, head) {
|
|
|
5528
5738
|
async function defaultBase(resolvedRoot) {
|
|
5529
5739
|
for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
|
|
5530
5740
|
try {
|
|
5531
|
-
await
|
|
5741
|
+
await exec5("git", ["rev-parse", "--verify", "--quiet", ref], {
|
|
5532
5742
|
cwd: resolvedRoot
|
|
5533
5743
|
});
|
|
5534
5744
|
return ref;
|
|
@@ -5541,7 +5751,7 @@ function anchorsTouched(record, changedFiles) {
|
|
|
5541
5751
|
return matchingPaths(record.files, changedFiles);
|
|
5542
5752
|
}
|
|
5543
5753
|
async function computeReview(rootDir, base, options = {}) {
|
|
5544
|
-
const resolvedRoot =
|
|
5754
|
+
const resolvedRoot = path28.resolve(rootDir);
|
|
5545
5755
|
const head = await getCurrentGitHash(resolvedRoot);
|
|
5546
5756
|
if (head === "unknown") return null;
|
|
5547
5757
|
const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
|
|
@@ -5591,7 +5801,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5591
5801
|
analyzed,
|
|
5592
5802
|
async (relPath) => {
|
|
5593
5803
|
try {
|
|
5594
|
-
await fs20.access(
|
|
5804
|
+
await fs20.access(path28.join(resolvedRoot, relPath));
|
|
5595
5805
|
return true;
|
|
5596
5806
|
} catch {
|
|
5597
5807
|
return false;
|
|
@@ -5624,8 +5834,8 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5624
5834
|
}
|
|
5625
5835
|
return finalize();
|
|
5626
5836
|
}
|
|
5627
|
-
var
|
|
5628
|
-
var
|
|
5837
|
+
var exec5, MAX_ANALYZED_FILES;
|
|
5838
|
+
var init_review2 = __esm({
|
|
5629
5839
|
"src/review/review.ts"() {
|
|
5630
5840
|
"use strict";
|
|
5631
5841
|
init_drift();
|
|
@@ -5636,7 +5846,7 @@ var init_review = __esm({
|
|
|
5636
5846
|
init_cochange();
|
|
5637
5847
|
init_evidence2();
|
|
5638
5848
|
init_snapshot();
|
|
5639
|
-
|
|
5849
|
+
exec5 = promisify7(execFile7);
|
|
5640
5850
|
MAX_ANALYZED_FILES = 50;
|
|
5641
5851
|
}
|
|
5642
5852
|
});
|
|
@@ -5714,7 +5924,7 @@ var init_onboarding = __esm({
|
|
|
5714
5924
|
"src/mcp/onboarding.ts"() {
|
|
5715
5925
|
"use strict";
|
|
5716
5926
|
init_audit();
|
|
5717
|
-
|
|
5927
|
+
init_review2();
|
|
5718
5928
|
init_drift();
|
|
5719
5929
|
init_snapshot();
|
|
5720
5930
|
init_provenance();
|
|
@@ -5726,7 +5936,7 @@ var init_onboarding = __esm({
|
|
|
5726
5936
|
});
|
|
5727
5937
|
|
|
5728
5938
|
// src/impact/references.ts
|
|
5729
|
-
import
|
|
5939
|
+
import path29 from "path";
|
|
5730
5940
|
function templateEnd(content2, start) {
|
|
5731
5941
|
let i = start + 1, interpolation = 0;
|
|
5732
5942
|
while (i < content2.length) {
|
|
@@ -5852,10 +6062,10 @@ function jvmImports(content2) {
|
|
|
5852
6062
|
}
|
|
5853
6063
|
function resolveImport(file, specifier, available) {
|
|
5854
6064
|
if (!specifier.startsWith("./") && !specifier.startsWith("../")) return null;
|
|
5855
|
-
const relative =
|
|
6065
|
+
const relative = path29.posix.normalize(path29.posix.join(path29.posix.dirname(file), specifier));
|
|
5856
6066
|
if (relative.startsWith("../")) return null;
|
|
5857
6067
|
if (available.has(relative)) return relative;
|
|
5858
|
-
const ext =
|
|
6068
|
+
const ext = path29.posix.extname(relative);
|
|
5859
6069
|
const replacements = { ".js": [".ts", ".tsx"], ".mjs": [".mts"], ".cjs": [".cts"] };
|
|
5860
6070
|
const candidates = ext ? (replacements[ext] ?? []).map((e) => relative.slice(0, -ext.length) + e) : jsExtensions.flatMap((e) => [relative + e, relative + "/index" + e]);
|
|
5861
6071
|
const found = candidates.filter((f) => available.has(f));
|
|
@@ -5867,13 +6077,13 @@ function mentionsPath(content2, target) {
|
|
|
5867
6077
|
function createReferenceMatcher(targets, available, targetContents = /* @__PURE__ */ new Map()) {
|
|
5868
6078
|
const counts = /* @__PURE__ */ new Map();
|
|
5869
6079
|
for (const file of available) {
|
|
5870
|
-
const name =
|
|
6080
|
+
const name = path29.posix.basename(file, path29.posix.extname(file));
|
|
5871
6081
|
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
5872
6082
|
}
|
|
5873
6083
|
const jvmTargets = new Map([...targetContents].filter(([file]) => /\.(?:kt|kts|java)$/.test(file)).map(([file, content2]) => [file, declaredSymbols(content2)]));
|
|
5874
6084
|
return (source) => {
|
|
5875
6085
|
if (targets.includes(source.path)) return [];
|
|
5876
|
-
const resolved = new Set(jsExtensions.includes(
|
|
6086
|
+
const resolved = new Set(jsExtensions.includes(path29.posix.extname(source.path)) ? imports(source.content).map((s) => resolveImport(source.path, s, available)).filter(Boolean) : []);
|
|
5877
6087
|
if (/\.(?:kt|kts|java)$/.test(source.path)) {
|
|
5878
6088
|
const imported = jvmImports(source.content);
|
|
5879
6089
|
for (const [target, symbols] of jvmTargets) if (symbols.some((symbol) => imported.some((name) => name === symbol || name.startsWith(symbol + ".")))) resolved.add(target);
|
|
@@ -5889,7 +6099,7 @@ function createReferenceMatcher(targets, available, targetContents = /* @__PURE_
|
|
|
5889
6099
|
add("explicit-path", target);
|
|
5890
6100
|
continue;
|
|
5891
6101
|
}
|
|
5892
|
-
const ext =
|
|
6102
|
+
const ext = path29.posix.extname(target), name = path29.posix.basename(target, ext);
|
|
5893
6103
|
if (!/\.(?:[cm]?[jt]sx?|py|kt|java|go|rs|swift|rb|cs|cpp|c|h|hpp|dart|php)$/.test(ext) || name.startsWith(".") || name.length < 3 || genericNames.has(name.toLowerCase()) || counts.get(name) !== 1) continue;
|
|
5894
6104
|
if (word(name).test(source.content)) {
|
|
5895
6105
|
add("name-candidate", name);
|
|
@@ -5918,11 +6128,11 @@ var impact_exports = {};
|
|
|
5918
6128
|
__export(impact_exports, {
|
|
5919
6129
|
analyzeImpact: () => analyzeImpact
|
|
5920
6130
|
});
|
|
5921
|
-
import
|
|
5922
|
-
import { execFile as
|
|
5923
|
-
import { promisify as
|
|
6131
|
+
import path30 from "path";
|
|
6132
|
+
import { execFile as execFile8 } from "child_process";
|
|
6133
|
+
import { promisify as promisify8 } from "util";
|
|
5924
6134
|
async function analyzeImpact(rootDir, targetFiles) {
|
|
5925
|
-
const resolvedRoot =
|
|
6135
|
+
const resolvedRoot = path30.resolve(rootDir);
|
|
5926
6136
|
const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
|
|
5927
6137
|
const [cochange, references, tests] = await Promise.all([
|
|
5928
6138
|
getCochangeFiles(resolvedRoot, resolvedTargets),
|
|
@@ -5965,7 +6175,7 @@ async function getCochangeFiles(rootDir, targetFiles) {
|
|
|
5965
6175
|
let totalTargetCommits = 0;
|
|
5966
6176
|
for (const targetFile of targetFiles) {
|
|
5967
6177
|
try {
|
|
5968
|
-
const { stdout: commitLog } = await
|
|
6178
|
+
const { stdout: commitLog } = await exec6(
|
|
5969
6179
|
"git",
|
|
5970
6180
|
["log", "--format=%H", "-n", "500", "--", targetFile],
|
|
5971
6181
|
{ cwd: rootDir, maxBuffer: 5e6 }
|
|
@@ -5975,7 +6185,7 @@ async function getCochangeFiles(rootDir, targetFiles) {
|
|
|
5975
6185
|
if (commits.length === 0) continue;
|
|
5976
6186
|
for (const commit of commits) {
|
|
5977
6187
|
try {
|
|
5978
|
-
const { stdout: filesInCommit } = await
|
|
6188
|
+
const { stdout: filesInCommit } = await exec6(
|
|
5979
6189
|
"git",
|
|
5980
6190
|
["diff-tree", "--no-commit-id", "--name-only", "-r", commit],
|
|
5981
6191
|
{ cwd: rootDir }
|
|
@@ -6036,9 +6246,9 @@ async function getRelatedTests(rootDir, targetFiles) {
|
|
|
6036
6246
|
const testFiles = await (await createFileAccess(rootDir)).list(testPatterns);
|
|
6037
6247
|
const results = [];
|
|
6038
6248
|
for (const target of targetFiles) {
|
|
6039
|
-
const targetBaseName =
|
|
6249
|
+
const targetBaseName = path30.basename(target).replace(/\.[^.]+$/, "");
|
|
6040
6250
|
for (const testFile of testFiles) {
|
|
6041
|
-
const testBaseName =
|
|
6251
|
+
const testBaseName = path30.basename(testFile).replace(/\.[^.]+$/, "");
|
|
6042
6252
|
const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
|
|
6043
6253
|
if (sourceName === targetBaseName) {
|
|
6044
6254
|
results.push({
|
|
@@ -6050,14 +6260,14 @@ async function getRelatedTests(rootDir, targetFiles) {
|
|
|
6050
6260
|
}
|
|
6051
6261
|
return results;
|
|
6052
6262
|
}
|
|
6053
|
-
var
|
|
6263
|
+
var exec6;
|
|
6054
6264
|
var init_impact = __esm({
|
|
6055
6265
|
"src/impact/impact.ts"() {
|
|
6056
6266
|
"use strict";
|
|
6057
6267
|
init_files();
|
|
6058
6268
|
init_paths();
|
|
6059
6269
|
init_references();
|
|
6060
|
-
|
|
6270
|
+
exec6 = promisify8(execFile8);
|
|
6061
6271
|
}
|
|
6062
6272
|
});
|
|
6063
6273
|
|
|
@@ -6066,13 +6276,13 @@ var assemble_exports = {};
|
|
|
6066
6276
|
__export(assemble_exports, {
|
|
6067
6277
|
assembleContext: () => assembleContext
|
|
6068
6278
|
});
|
|
6069
|
-
import
|
|
6279
|
+
import path31 from "path";
|
|
6070
6280
|
import fs21 from "fs/promises";
|
|
6071
6281
|
async function collectImpact(root, candidates) {
|
|
6072
6282
|
const targets = /* @__PURE__ */ new Set();
|
|
6073
6283
|
let sourceFiles;
|
|
6074
6284
|
for (const candidate of sanitizeRepoPaths(candidates)) {
|
|
6075
|
-
const stat = await fs21.stat(
|
|
6285
|
+
const stat = await fs21.stat(path31.join(root, candidate)).catch(() => null);
|
|
6076
6286
|
if (stat?.isDirectory()) {
|
|
6077
6287
|
sourceFiles ??= await (await createFileAccess(root)).list();
|
|
6078
6288
|
for (const file of sourceFiles) {
|
|
@@ -6098,7 +6308,7 @@ async function collectImpact(root, candidates) {
|
|
|
6098
6308
|
};
|
|
6099
6309
|
}
|
|
6100
6310
|
async function assembleContext(rootDir, task, files) {
|
|
6101
|
-
const resolvedRoot =
|
|
6311
|
+
const resolvedRoot = path31.resolve(rootDir);
|
|
6102
6312
|
const mapState = await inspectSnapshot(resolvedRoot);
|
|
6103
6313
|
const snapshot = mapState.snapshot;
|
|
6104
6314
|
const store = await loadDecisionStore(resolvedRoot);
|
|
@@ -6130,6 +6340,7 @@ async function assembleContext(rootDir, task, files) {
|
|
|
6130
6340
|
};
|
|
6131
6341
|
}
|
|
6132
6342
|
const drift = await computeDrift(resolvedRoot);
|
|
6343
|
+
const readTrust = createSnapshotTrustReader(resolvedRoot);
|
|
6133
6344
|
const featureScores = Object.entries(snapshot.features).map(([name, feat]) => ({
|
|
6134
6345
|
name,
|
|
6135
6346
|
feat,
|
|
@@ -6156,14 +6367,7 @@ async function assembleContext(rootDir, task, files) {
|
|
|
6156
6367
|
Object.assign(bundle, await collectImpact(resolvedRoot, [...anchorFiles, ...Object.values(decisions).flatMap((d) => [...d.files, ...d.pendingProposal?.files ?? []])]));
|
|
6157
6368
|
bundle.diagnostics = store.diagnostics;
|
|
6158
6369
|
bundle.freshness = drift;
|
|
6159
|
-
bundle.trust =
|
|
6160
|
-
features: Object.fromEntries(Object.entries(snapshot.features).map(
|
|
6161
|
-
([name, entry]) => [name, assessTrust(entry, drift?.featureFreshness?.[name] ?? "unknown")]
|
|
6162
|
-
)),
|
|
6163
|
-
flows: Object.fromEntries(Object.entries(snapshot.flows).map(
|
|
6164
|
-
([name, entry]) => [name, assessTrust(entry, drift?.flowFreshness?.[name] ?? "unknown")]
|
|
6165
|
-
))
|
|
6166
|
-
};
|
|
6370
|
+
bundle.trust = await readSnapshotTrustIndex(readTrust, snapshot, drift);
|
|
6167
6371
|
bundle.hint += " " + trustHint([
|
|
6168
6372
|
...Object.values(bundle.trust.features),
|
|
6169
6373
|
...Object.values(bundle.trust.flows),
|
|
@@ -6176,7 +6380,7 @@ async function assembleContext(rootDir, task, files) {
|
|
|
6176
6380
|
const features = {};
|
|
6177
6381
|
const staleMatches = [];
|
|
6178
6382
|
for (const { name, feat, score } of featureScores) {
|
|
6179
|
-
const trust =
|
|
6383
|
+
const trust = await readTrust("feature", name, feat, drift?.featureFreshness?.[name] ?? "unknown");
|
|
6180
6384
|
const stale2 = trust.freshness !== "current";
|
|
6181
6385
|
if (stale2) staleMatches.push(name);
|
|
6182
6386
|
features[name] = {
|
|
@@ -6191,7 +6395,7 @@ async function assembleContext(rootDir, task, files) {
|
|
|
6191
6395
|
}
|
|
6192
6396
|
const flows = {};
|
|
6193
6397
|
for (const { name, flow, score } of flowScores) {
|
|
6194
|
-
const trust =
|
|
6398
|
+
const trust = await readTrust("flow", name, flow, drift?.flowFreshness?.[name] ?? "unknown");
|
|
6195
6399
|
const stale2 = trust.freshness !== "current";
|
|
6196
6400
|
if (stale2) staleMatches.push(name);
|
|
6197
6401
|
flows[name] = {
|
|
@@ -6276,6 +6480,7 @@ var init_assemble = __esm({
|
|
|
6276
6480
|
"use strict";
|
|
6277
6481
|
init_snapshot();
|
|
6278
6482
|
init_files();
|
|
6483
|
+
init_trust2();
|
|
6279
6484
|
init_drift();
|
|
6280
6485
|
init_impact();
|
|
6281
6486
|
init_lexical();
|
|
@@ -6641,8 +6846,8 @@ __export(client_exports, {
|
|
|
6641
6846
|
function createConfluenceClient(config, fetchFn = fetch) {
|
|
6642
6847
|
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
6643
6848
|
const auth = "Basic " + Buffer.from(`${config.email}:${config.apiToken}`).toString("base64");
|
|
6644
|
-
async function call(method,
|
|
6645
|
-
const res = await fetchFn(`${baseUrl}${
|
|
6849
|
+
async function call(method, path35, body) {
|
|
6850
|
+
const res = await fetchFn(`${baseUrl}${path35}`, {
|
|
6646
6851
|
method,
|
|
6647
6852
|
headers: {
|
|
6648
6853
|
Authorization: auth,
|
|
@@ -6654,7 +6859,7 @@ function createConfluenceClient(config, fetchFn = fetch) {
|
|
|
6654
6859
|
if (!res.ok) {
|
|
6655
6860
|
const text2 = await res.text();
|
|
6656
6861
|
throw new Error(
|
|
6657
|
-
`Confluence ${method} ${
|
|
6862
|
+
`Confluence ${method} ${path35} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
|
|
6658
6863
|
);
|
|
6659
6864
|
}
|
|
6660
6865
|
if (res.status === 204) return null;
|
|
@@ -6756,15 +6961,15 @@ __export(config_exports2, {
|
|
|
6756
6961
|
validateProvider: () => validateProvider
|
|
6757
6962
|
});
|
|
6758
6963
|
import fs23 from "fs/promises";
|
|
6759
|
-
import
|
|
6964
|
+
import path32 from "path";
|
|
6760
6965
|
import os3 from "os";
|
|
6761
|
-
import { execFile as
|
|
6762
|
-
import { promisify as
|
|
6966
|
+
import { execFile as execFile9 } from "child_process";
|
|
6967
|
+
import { promisify as promisify9 } from "util";
|
|
6763
6968
|
function configDir() {
|
|
6764
|
-
return
|
|
6969
|
+
return path32.join(os3.homedir(), ".mason");
|
|
6765
6970
|
}
|
|
6766
6971
|
function configFile() {
|
|
6767
|
-
return
|
|
6972
|
+
return path32.join(configDir(), "config.json");
|
|
6768
6973
|
}
|
|
6769
6974
|
async function loadConfig() {
|
|
6770
6975
|
try {
|
|
@@ -6794,7 +6999,7 @@ async function detectCLI(provider) {
|
|
|
6794
6999
|
const cliName = provider === "claude" ? "claude" : provider === "gemini" ? "gemini" : provider === "ollama" ? "ollama" : null;
|
|
6795
7000
|
if (!cliName) return { available: false };
|
|
6796
7001
|
try {
|
|
6797
|
-
const { stdout } = await
|
|
7002
|
+
const { stdout } = await exec7(cliName, ["--version"]);
|
|
6798
7003
|
return { available: true, version: stdout.trim() };
|
|
6799
7004
|
} catch {
|
|
6800
7005
|
return { available: false };
|
|
@@ -6811,11 +7016,11 @@ async function loadConfluenceConfig() {
|
|
|
6811
7016
|
const config = await loadConfig();
|
|
6812
7017
|
return config?.confluence ?? null;
|
|
6813
7018
|
}
|
|
6814
|
-
var
|
|
7019
|
+
var exec7, DEFAULT_MODELS;
|
|
6815
7020
|
var init_config2 = __esm({
|
|
6816
7021
|
"src/llm/config.ts"() {
|
|
6817
7022
|
"use strict";
|
|
6818
|
-
|
|
7023
|
+
exec7 = promisify9(execFile9);
|
|
6819
7024
|
DEFAULT_MODELS = {
|
|
6820
7025
|
claude: "claude-sonnet-4-20250514",
|
|
6821
7026
|
gemini: "gemini-2.5-flash",
|
|
@@ -6937,16 +7142,16 @@ var init_renderer = __esm({
|
|
|
6937
7142
|
|
|
6938
7143
|
// src/confluence/diff.ts
|
|
6939
7144
|
import fs24 from "fs/promises";
|
|
6940
|
-
import
|
|
6941
|
-
import { createHash as
|
|
7145
|
+
import path33 from "path";
|
|
7146
|
+
import { createHash as createHash6 } from "crypto";
|
|
6942
7147
|
function hashDescription(description) {
|
|
6943
|
-
return
|
|
7148
|
+
return createHash6("sha256").update(description, "utf8").digest("hex");
|
|
6944
7149
|
}
|
|
6945
7150
|
function syncStateDir(rootDir) {
|
|
6946
|
-
return
|
|
7151
|
+
return path33.join(rootDir, ".mason");
|
|
6947
7152
|
}
|
|
6948
7153
|
function syncStatePath(rootDir) {
|
|
6949
|
-
return
|
|
7154
|
+
return path33.join(syncStateDir(rootDir), "confluence-sync.json");
|
|
6950
7155
|
}
|
|
6951
7156
|
async function loadSyncState(rootDir) {
|
|
6952
7157
|
try {
|
|
@@ -7014,8 +7219,8 @@ var init_diff = __esm({
|
|
|
7014
7219
|
});
|
|
7015
7220
|
|
|
7016
7221
|
// src/llm/providers.ts
|
|
7017
|
-
import { execFile as
|
|
7018
|
-
import { promisify as
|
|
7222
|
+
import { execFile as execFile10, spawn } from "child_process";
|
|
7223
|
+
import { promisify as promisify10 } from "util";
|
|
7019
7224
|
async function callLLM(config, userMessage, systemPrompt) {
|
|
7020
7225
|
const model = config.model ?? getDefaultModel(config.provider);
|
|
7021
7226
|
const system = systemPrompt ?? CLAUDE_MD_SYSTEM_PROMPT;
|
|
@@ -7172,12 +7377,12 @@ async function callOpenAIAPI(apiKey, model, system, userMessage) {
|
|
|
7172
7377
|
});
|
|
7173
7378
|
return response.choices[0]?.message?.content ?? "";
|
|
7174
7379
|
}
|
|
7175
|
-
var
|
|
7380
|
+
var exec8, CLAUDE_MD_SYSTEM_PROMPT;
|
|
7176
7381
|
var init_providers = __esm({
|
|
7177
7382
|
"src/llm/providers.ts"() {
|
|
7178
7383
|
"use strict";
|
|
7179
7384
|
init_config2();
|
|
7180
|
-
|
|
7385
|
+
exec8 = promisify10(execFile10);
|
|
7181
7386
|
CLAUDE_MD_SYSTEM_PROMPT = `You are Mason, a context engineering tool. You've been given a comprehensive analysis of a codebase including:
|
|
7182
7387
|
- Git history stats (commit patterns, frequently changed files, stale directories)
|
|
7183
7388
|
- Project structure (directory layout, file counts by type)
|
|
@@ -7519,20 +7724,20 @@ import { z as z24 } from "zod";
|
|
|
7519
7724
|
init_repair();
|
|
7520
7725
|
init_advisory_review();
|
|
7521
7726
|
import fs25 from "fs/promises";
|
|
7522
|
-
import
|
|
7523
|
-
import { execFile as
|
|
7524
|
-
import { promisify as
|
|
7727
|
+
import path34 from "path";
|
|
7728
|
+
import { execFile as execFile11 } from "child_process";
|
|
7729
|
+
import { promisify as promisify11 } from "util";
|
|
7525
7730
|
|
|
7526
7731
|
// src/audit/cli.ts
|
|
7527
7732
|
init_audit();
|
|
7528
7733
|
init_types();
|
|
7529
7734
|
init_repair();
|
|
7530
7735
|
init_findings();
|
|
7531
|
-
import
|
|
7736
|
+
import path23 from "path";
|
|
7532
7737
|
|
|
7533
7738
|
// src/audit/advisory-cli.ts
|
|
7534
7739
|
init_advisory_review();
|
|
7535
|
-
import
|
|
7740
|
+
import path22 from "path";
|
|
7536
7741
|
import { stripVTControlCharacters } from "util";
|
|
7537
7742
|
|
|
7538
7743
|
// src/audit/cli.ts
|
|
@@ -7625,8 +7830,8 @@ function formatFixPrompt(report, baselinePath) {
|
|
|
7625
7830
|
}
|
|
7626
7831
|
|
|
7627
7832
|
// src/analyzers/git-history.ts
|
|
7628
|
-
import { execFile as
|
|
7629
|
-
import { promisify as
|
|
7833
|
+
import { execFile as execFile3 } from "child_process";
|
|
7834
|
+
import { promisify as promisify3 } from "util";
|
|
7630
7835
|
|
|
7631
7836
|
// src/analyzers/base.ts
|
|
7632
7837
|
import fs15 from "fs/promises";
|
|
@@ -7663,7 +7868,7 @@ var BaseAnalyzer = class {
|
|
|
7663
7868
|
};
|
|
7664
7869
|
|
|
7665
7870
|
// src/analyzers/git-history.ts
|
|
7666
|
-
var
|
|
7871
|
+
var exec = promisify3(execFile3);
|
|
7667
7872
|
var GitHistoryAnalyzer = class extends BaseAnalyzer {
|
|
7668
7873
|
name = "git-history";
|
|
7669
7874
|
async analyze(context) {
|
|
@@ -7684,7 +7889,7 @@ var GitHistoryAnalyzer = class extends BaseAnalyzer {
|
|
|
7684
7889
|
}
|
|
7685
7890
|
async git(args, cwd) {
|
|
7686
7891
|
try {
|
|
7687
|
-
const { stdout } = await
|
|
7892
|
+
const { stdout } = await exec("git", args, { cwd, maxBuffer: 1e7 });
|
|
7688
7893
|
return stdout.trim();
|
|
7689
7894
|
} catch {
|
|
7690
7895
|
return "";
|
|
@@ -7833,12 +8038,10 @@ async function runAll(context) {
|
|
|
7833
8038
|
}
|
|
7834
8039
|
|
|
7835
8040
|
// src/utils/git.ts
|
|
7836
|
-
|
|
7837
|
-
import { promisify as promisify12 } from "util";
|
|
7838
|
-
var exec9 = promisify12(execFile12);
|
|
8041
|
+
init_git_read();
|
|
7839
8042
|
async function isGitRepo(dir) {
|
|
7840
8043
|
try {
|
|
7841
|
-
await
|
|
8044
|
+
await execGit(["rev-parse", "--git-dir"], { cwd: dir });
|
|
7842
8045
|
return true;
|
|
7843
8046
|
} catch {
|
|
7844
8047
|
return false;
|
|
@@ -7847,10 +8050,10 @@ async function isGitRepo(dir) {
|
|
|
7847
8050
|
|
|
7848
8051
|
// src/mcp/sampler.ts
|
|
7849
8052
|
init_files();
|
|
7850
|
-
import
|
|
7851
|
-
import { execFile as
|
|
7852
|
-
import { promisify as
|
|
7853
|
-
var
|
|
8053
|
+
import path24 from "path";
|
|
8054
|
+
import { execFile as execFile4 } from "child_process";
|
|
8055
|
+
import { promisify as promisify4 } from "util";
|
|
8056
|
+
var exec2 = promisify4(execFile4);
|
|
7854
8057
|
var CONFIG_FILES = [
|
|
7855
8058
|
// Build & project config
|
|
7856
8059
|
"package.json",
|
|
@@ -7955,8 +8158,8 @@ async function sampleFiles(rootDir, maxFiles = 25) {
|
|
|
7955
8158
|
const projectConfig = access.config;
|
|
7956
8159
|
for (const filePath of projectConfig.alwaysInclude ?? []) {
|
|
7957
8160
|
if (selected.size >= maxFiles) break;
|
|
7958
|
-
const resolvedPath =
|
|
7959
|
-
if (!resolvedPath.startsWith(
|
|
8161
|
+
const resolvedPath = path24.resolve(rootDir, filePath);
|
|
8162
|
+
if (!resolvedPath.startsWith(path24.resolve(rootDir))) continue;
|
|
7960
8163
|
selected.set(filePath, "always-include (project config)");
|
|
7961
8164
|
}
|
|
7962
8165
|
let configCount = 0;
|
|
@@ -8012,7 +8215,7 @@ async function sampleFiles(rootDir, maxFiles = 25) {
|
|
|
8012
8215
|
}
|
|
8013
8216
|
}
|
|
8014
8217
|
try {
|
|
8015
|
-
const { stdout } = await
|
|
8218
|
+
const { stdout } = await exec2(
|
|
8016
8219
|
"git",
|
|
8017
8220
|
["log", "--since=3 months ago", "--format=", "--name-only"],
|
|
8018
8221
|
{ cwd: rootDir, maxBuffer: 5e6 }
|
|
@@ -8022,7 +8225,7 @@ async function sampleFiles(rootDir, maxFiles = 25) {
|
|
|
8022
8225
|
if (!line) continue;
|
|
8023
8226
|
if (line.includes("node_modules") || line.includes("/build/") || line.includes(".gradle") || line.includes("/generated/"))
|
|
8024
8227
|
continue;
|
|
8025
|
-
const ext =
|
|
8228
|
+
const ext = path24.extname(line).slice(1);
|
|
8026
8229
|
if (!SOURCE_EXTENSIONS.includes(ext)) continue;
|
|
8027
8230
|
fileCounts.set(line, (fileCounts.get(line) ?? 0) + 1);
|
|
8028
8231
|
}
|
|
@@ -8142,10 +8345,10 @@ init_drift();
|
|
|
8142
8345
|
init_paths();
|
|
8143
8346
|
init_files();
|
|
8144
8347
|
import { createHash as createHash4 } from "crypto";
|
|
8145
|
-
import { execFile as
|
|
8146
|
-
import { promisify as
|
|
8348
|
+
import { execFile as execFile5 } from "child_process";
|
|
8349
|
+
import { promisify as promisify5 } from "util";
|
|
8147
8350
|
import { z as z10 } from "zod";
|
|
8148
|
-
var
|
|
8351
|
+
var exec3 = promisify5(execFile5);
|
|
8149
8352
|
var requestSchema = z10.object({
|
|
8150
8353
|
id: z10.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
8151
8354
|
action: z10.enum(["prepare", "accept", "reaffirm", "retire"]).default("prepare"),
|
|
@@ -8168,8 +8371,8 @@ async function reviewState(root, record) {
|
|
|
8168
8371
|
changedFiles: touchedPaths(committed),
|
|
8169
8372
|
localChanges: matchingPaths(anchors, workingTree.changedFiles)
|
|
8170
8373
|
};
|
|
8171
|
-
const
|
|
8172
|
-
return { reviewToken, evidence, committed, workingTreeAvailable: workingTree.available };
|
|
8374
|
+
const reviewToken2 = createHash4("sha256").update(JSON.stringify({ record, evidence, workingTreeAvailable: workingTree.available })).digest("hex");
|
|
8375
|
+
return { reviewToken: reviewToken2, evidence, committed, workingTreeAvailable: workingTree.available };
|
|
8173
8376
|
}
|
|
8174
8377
|
async function previews(root, record, state) {
|
|
8175
8378
|
const access = await createFileAccess(root);
|
|
@@ -8200,7 +8403,7 @@ async function previews(root, record, state) {
|
|
|
8200
8403
|
let diffUnavailable;
|
|
8201
8404
|
if (state.evidence.historyAvailable && diffPaths.length) {
|
|
8202
8405
|
try {
|
|
8203
|
-
const { stdout } = await
|
|
8406
|
+
const { stdout } = await exec3("git", ["--literal-pathspecs", "diff", "--no-ext-diff", "--no-textconv", "--no-color", "--no-renames", "-U3", record.refreshedHash, state.evidence.headHash, "--", ...diffPaths], { cwd: root, maxBuffer: 1024 * 1024 });
|
|
8204
8407
|
diff = stdout.slice(0, 16e3);
|
|
8205
8408
|
if (stdout.length > diff.length) diffUnavailable = "Diff preview was truncated; inspect the full diff before reviewing.";
|
|
8206
8409
|
} catch {
|
|
@@ -8275,7 +8478,9 @@ async function reviewDecision(root, input) {
|
|
|
8275
8478
|
// src/mcp/tools.ts
|
|
8276
8479
|
init_drift2();
|
|
8277
8480
|
init_snapshot();
|
|
8481
|
+
init_trust2();
|
|
8278
8482
|
init_drift();
|
|
8483
|
+
init_review();
|
|
8279
8484
|
|
|
8280
8485
|
// src/snapshot/prompt.ts
|
|
8281
8486
|
var BATCH_SYSTEM_PROMPT = `You are Mason, building one piece of a larger concept-to-files map via a Map-Reduce pattern.
|
|
@@ -8372,6 +8577,7 @@ ${JSON.stringify({ partials }, null, 2)}`;
|
|
|
8372
8577
|
}
|
|
8373
8578
|
|
|
8374
8579
|
// src/snapshot/partials.ts
|
|
8580
|
+
init_lock();
|
|
8375
8581
|
init_storage();
|
|
8376
8582
|
init_snapshot();
|
|
8377
8583
|
import fs16 from "fs/promises";
|
|
@@ -8387,7 +8593,7 @@ var partialSchema = z11.object({
|
|
|
8387
8593
|
var scopeSchema2 = z11.object({ files: z11.array(z11.string()), savedAt: z11.string() });
|
|
8388
8594
|
async function savePartial(rootDir, partial) {
|
|
8389
8595
|
if (!/^[a-zA-Z0-9_-]+$/.test(partial.batchId)) throw new Error(`Invalid batchId: ${partial.batchId}`);
|
|
8390
|
-
await writeStoreJson(rootDir, `${DIRECTORY}/${partial.batchId}.json`, partialSchema.parse(partial));
|
|
8596
|
+
await withSnapshotWrite(rootDir, () => writeStoreJson(rootDir, `${DIRECTORY}/${partial.batchId}.json`, partialSchema.parse(partial)));
|
|
8391
8597
|
}
|
|
8392
8598
|
async function loadAllPartials(rootDir) {
|
|
8393
8599
|
let entries;
|
|
@@ -8407,14 +8613,16 @@ async function loadAllPartials(rootDir) {
|
|
|
8407
8613
|
return partials.sort((a, b) => a.offset - b.offset);
|
|
8408
8614
|
}
|
|
8409
8615
|
async function saveScope(rootDir, files) {
|
|
8410
|
-
await writeStoreJson(rootDir, `${DIRECTORY}/scope.json`, { files, savedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8616
|
+
await withSnapshotWrite(rootDir, () => writeStoreJson(rootDir, `${DIRECTORY}/scope.json`, { files, savedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
8411
8617
|
}
|
|
8412
8618
|
async function loadScope(rootDir) {
|
|
8413
8619
|
const raw = await readStoreJson(rootDir, `${DIRECTORY}/scope.json`);
|
|
8414
8620
|
return raw === null ? null : scopeSchema2.parse(raw).files;
|
|
8415
8621
|
}
|
|
8416
8622
|
async function clearScope(rootDir) {
|
|
8417
|
-
await
|
|
8623
|
+
await withSnapshotWrite(rootDir, async () => {
|
|
8624
|
+
await fs16.rm(await storePath(rootDir, `${DIRECTORY}/scope.json`), { force: true });
|
|
8625
|
+
});
|
|
8418
8626
|
}
|
|
8419
8627
|
async function clearAllPartials(rootDir) {
|
|
8420
8628
|
await fs16.rm(await storePath(rootDir, DIRECTORY), { recursive: true, force: true });
|
|
@@ -8426,7 +8634,7 @@ function batchIdFor(offset) {
|
|
|
8426
8634
|
// src/mcp/tools.ts
|
|
8427
8635
|
init_init();
|
|
8428
8636
|
init_onboarding();
|
|
8429
|
-
var
|
|
8637
|
+
var exec9 = promisify11(execFile11);
|
|
8430
8638
|
async function buildContext(dir) {
|
|
8431
8639
|
return {
|
|
8432
8640
|
rootDir: dir,
|
|
@@ -8434,7 +8642,7 @@ async function buildContext(dir) {
|
|
|
8434
8642
|
};
|
|
8435
8643
|
}
|
|
8436
8644
|
async function analyzeProject(dir) {
|
|
8437
|
-
const rootDir =
|
|
8645
|
+
const rootDir = path34.resolve(dir);
|
|
8438
8646
|
const context = await buildContext(rootDir);
|
|
8439
8647
|
const results = await runAll(context);
|
|
8440
8648
|
const projectSnapshot = await detectProjectSnapshot(rootDir);
|
|
@@ -8489,7 +8697,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
8489
8697
|
const present = [];
|
|
8490
8698
|
for (const file of buildFiles) {
|
|
8491
8699
|
try {
|
|
8492
|
-
await fs25.access(
|
|
8700
|
+
await fs25.access(path34.join(rootDir, file));
|
|
8493
8701
|
present.push(file);
|
|
8494
8702
|
} catch {
|
|
8495
8703
|
}
|
|
@@ -8531,7 +8739,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
8531
8739
|
const sourceFiles = await access.list();
|
|
8532
8740
|
const fileCounts = {};
|
|
8533
8741
|
for (const file of sourceFiles) {
|
|
8534
|
-
const ext =
|
|
8742
|
+
const ext = path34.extname(file).slice(1);
|
|
8535
8743
|
fileCounts[ext] = (fileCounts[ext] ?? 0) + 1;
|
|
8536
8744
|
}
|
|
8537
8745
|
return {
|
|
@@ -8542,7 +8750,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
8542
8750
|
};
|
|
8543
8751
|
}
|
|
8544
8752
|
async function getCodeSamples(dir, count4 = 15) {
|
|
8545
|
-
const rootDir =
|
|
8753
|
+
const rootDir = path34.resolve(dir);
|
|
8546
8754
|
const samples = await sampleFiles(rootDir, count4);
|
|
8547
8755
|
const output = {
|
|
8548
8756
|
note: "These are previews (first ~60 lines). Read the file directly with your own tools to see it in full.",
|
|
@@ -8585,7 +8793,7 @@ async function unmappedContextResponse(rootDir) {
|
|
|
8585
8793
|
});
|
|
8586
8794
|
}
|
|
8587
8795
|
async function getProjectStructure(dir) {
|
|
8588
|
-
const rootDir =
|
|
8796
|
+
const rootDir = path34.resolve(dir);
|
|
8589
8797
|
const allFiles = await (await createFileAccess(rootDir)).list("**/*");
|
|
8590
8798
|
const dirInfo = /* @__PURE__ */ new Map();
|
|
8591
8799
|
for (const file of allFiles) {
|
|
@@ -8597,7 +8805,7 @@ async function getProjectStructure(dir) {
|
|
|
8597
8805
|
}
|
|
8598
8806
|
const info = dirInfo.get(dirPath);
|
|
8599
8807
|
info.fileCount++;
|
|
8600
|
-
const ext =
|
|
8808
|
+
const ext = path34.extname(file).slice(1);
|
|
8601
8809
|
if (ext) {
|
|
8602
8810
|
info.extensions.set(ext, (info.extensions.get(ext) ?? 0) + 1);
|
|
8603
8811
|
}
|
|
@@ -8642,7 +8850,7 @@ async function buildChangedFilePreviews(rootDir, changedFiles) {
|
|
|
8642
8850
|
return previews2;
|
|
8643
8851
|
}
|
|
8644
8852
|
async function getSnapshot(dir) {
|
|
8645
|
-
const rootDir =
|
|
8853
|
+
const rootDir = path34.resolve(dir);
|
|
8646
8854
|
const snapshot = await loadSnapshot(rootDir);
|
|
8647
8855
|
if (!snapshot) {
|
|
8648
8856
|
return unmappedContextResponse(rootDir);
|
|
@@ -8680,9 +8888,9 @@ async function getSnapshot(dir) {
|
|
|
8680
8888
|
const store = await loadDecisionStore2(rootDir);
|
|
8681
8889
|
const decisionRecords = store.records;
|
|
8682
8890
|
const decisionDrift = await computeDecisionDrift(rootDir, decisionRecords);
|
|
8891
|
+
const readTrust = createSnapshotTrustReader(rootDir);
|
|
8683
8892
|
const trust = {
|
|
8684
|
-
|
|
8685
|
-
flows: Object.fromEntries(Object.entries(snapshot.flows).map(([name, entry]) => [name, assessTrust(entry, drift?.flowFreshness?.[name] ?? "unknown")])),
|
|
8893
|
+
...await readSnapshotTrustIndex(readTrust, snapshot, drift),
|
|
8686
8894
|
decisions: Object.fromEntries(decisionRecords.filter((d) => d.status === "active").map((d) => [d.id, decisionTrust(effectiveDecision(d), decisionDrift.freshness?.[d.id] ?? "unknown")]))
|
|
8687
8895
|
};
|
|
8688
8896
|
output.trust = trust;
|
|
@@ -8743,7 +8951,7 @@ function driftHint(report) {
|
|
|
8743
8951
|
return "Read the changed files under staleFeatures/staleFlows, update those entries (fold unmappedFiles into the right features), and call save_snapshot with only the affected entries \u2014 unchanged entries are preserved. Drop ghostFiles from any entries that reference them, and delete features/flows that no longer exist via save_snapshot's removeFeatures/removeFlows.";
|
|
8744
8952
|
}
|
|
8745
8953
|
async function checkDrift(dir) {
|
|
8746
|
-
const rootDir =
|
|
8954
|
+
const rootDir = path34.resolve(dir);
|
|
8747
8955
|
const report = await computeDrift(rootDir);
|
|
8748
8956
|
if (!report) {
|
|
8749
8957
|
return JSON.stringify({
|
|
@@ -8774,7 +8982,7 @@ async function checkDrift(dir) {
|
|
|
8774
8982
|
return JSON.stringify({ exists: true, ...report, verification, hint });
|
|
8775
8983
|
}
|
|
8776
8984
|
async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_SIZE, files) {
|
|
8777
|
-
const rootDir =
|
|
8985
|
+
const rootDir = path34.resolve(dir);
|
|
8778
8986
|
const scoped = files !== void 0 && files.length > 0;
|
|
8779
8987
|
const scopeFiles = scoped ? sanitizePaths(rootDir, files) : void 0;
|
|
8780
8988
|
const batch = await prepareSnapshotBatch(dir, offset, batchSize, scopeFiles);
|
|
@@ -8821,7 +9029,7 @@ async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_
|
|
|
8821
9029
|
);
|
|
8822
9030
|
}
|
|
8823
9031
|
async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
|
|
8824
|
-
const rootDir =
|
|
9032
|
+
const rootDir = path34.resolve(dir);
|
|
8825
9033
|
for (const feat of Object.values(features)) {
|
|
8826
9034
|
feat.files = sanitizePaths(rootDir, feat.files);
|
|
8827
9035
|
if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
|
|
@@ -8850,7 +9058,7 @@ async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
|
|
|
8850
9058
|
);
|
|
8851
9059
|
}
|
|
8852
9060
|
async function reduceSnapshot(dir) {
|
|
8853
|
-
const rootDir =
|
|
9061
|
+
const rootDir = path34.resolve(dir);
|
|
8854
9062
|
const partials = await loadAllPartials(rootDir);
|
|
8855
9063
|
if (partials.length === 0) {
|
|
8856
9064
|
return JSON.stringify(
|
|
@@ -8912,7 +9120,7 @@ async function reduceSnapshot(dir) {
|
|
|
8912
9120
|
);
|
|
8913
9121
|
}
|
|
8914
9122
|
async function fullAnalysis(dir) {
|
|
8915
|
-
const rootDir =
|
|
9123
|
+
const rootDir = path34.resolve(dir);
|
|
8916
9124
|
const [analysis, structure, samples, testMap, snapshot] = await Promise.all([
|
|
8917
9125
|
analyzeProject(dir),
|
|
8918
9126
|
getProjectStructure(dir),
|
|
@@ -8941,100 +9149,94 @@ function sanitizePaths(rootDir, files) {
|
|
|
8941
9149
|
return sanitizeRepoPaths(files);
|
|
8942
9150
|
}
|
|
8943
9151
|
async function saveSnapshotData(dir, features, flows, removeFeatures = [], removeFlows = []) {
|
|
8944
|
-
const rootDir =
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
9152
|
+
const rootDir = path34.resolve(dir);
|
|
9153
|
+
return updateSnapshot(rootDir, async (previous) => {
|
|
9154
|
+
const gitHash = await getCurrentGitHash(rootDir);
|
|
9155
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9156
|
+
for (const feat of Object.values(features)) {
|
|
9157
|
+
feat.files = sanitizePaths(rootDir, feat.files);
|
|
9158
|
+
if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
|
|
9159
|
+
feat.type = normalizeFeatureType(feat.type);
|
|
9160
|
+
}
|
|
9161
|
+
for (const flow of Object.values(flows)) {
|
|
9162
|
+
flow.chain = sanitizePaths(rootDir, flow.chain);
|
|
9163
|
+
}
|
|
9164
|
+
const partials = await loadAllPartials(rootDir);
|
|
9165
|
+
const replaceMode = partials.length > 0;
|
|
9166
|
+
const existing = replaceMode ? null : previous;
|
|
9167
|
+
const preserveVerification = (next, old) => {
|
|
9168
|
+
delete next.verifiedAt;
|
|
9169
|
+
delete next.verifiedHash;
|
|
9170
|
+
delete next.verificationToken;
|
|
9171
|
+
delete next.verificationFailed;
|
|
9172
|
+
delete next.verificationNote;
|
|
9173
|
+
if (!old) return;
|
|
9174
|
+
if (JSON.stringify(snapshotEntryContent(next)) !== JSON.stringify(snapshotEntryContent(old))) return;
|
|
9175
|
+
next.verificationToken = old.verificationToken;
|
|
9176
|
+
next.verifiedAt = old.verifiedAt;
|
|
9177
|
+
next.verifiedHash = old.verifiedHash;
|
|
9178
|
+
next.verificationFailed = old.verificationFailed;
|
|
9179
|
+
next.verificationNote = old.verificationNote;
|
|
9180
|
+
};
|
|
9181
|
+
for (const [name, entry] of Object.entries(features)) preserveVerification(entry, previous?.features[name]);
|
|
9182
|
+
for (const [name, entry] of Object.entries(flows)) preserveVerification(entry, previous?.flows[name]);
|
|
9183
|
+
if (existing) {
|
|
9184
|
+
if (existing.gitHash !== "unknown") {
|
|
9185
|
+
for (const feat of Object.values(existing.features)) {
|
|
9186
|
+
feat.refreshedHash ??= existing.gitHash;
|
|
9187
|
+
}
|
|
9188
|
+
for (const flow of Object.values(existing.flows)) {
|
|
9189
|
+
flow.refreshedHash ??= existing.gitHash;
|
|
9190
|
+
}
|
|
8980
9191
|
}
|
|
8981
|
-
|
|
8982
|
-
|
|
9192
|
+
const removedFeatures = removeFeatures.filter(
|
|
9193
|
+
(name) => name in existing.features
|
|
9194
|
+
);
|
|
9195
|
+
const removedFlows = removeFlows.filter((name) => name in existing.flows);
|
|
9196
|
+
for (const name of removedFeatures) delete existing.features[name];
|
|
9197
|
+
for (const name of removedFlows) delete existing.flows[name];
|
|
9198
|
+
if (gitHash !== "unknown") {
|
|
9199
|
+
for (const feat of Object.values(features)) feat.refreshedHash = gitHash;
|
|
9200
|
+
for (const flow of Object.values(flows)) flow.refreshedHash = gitHash;
|
|
8983
9201
|
}
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
version: 2,
|
|
9012
|
-
createdAt: now,
|
|
9013
|
-
updatedAt: now,
|
|
9014
|
-
gitHash,
|
|
9015
|
-
features,
|
|
9016
|
-
flows
|
|
9017
|
-
};
|
|
9018
|
-
await saveSnapshot(rootDir, snapshot);
|
|
9019
|
-
await clearAllPartials(rootDir);
|
|
9020
|
-
return JSON.stringify({
|
|
9021
|
-
status: replaceMode ? "replaced" : "created",
|
|
9022
|
-
mode: replaceMode ? "replaced-from-partials" : "fresh",
|
|
9023
|
-
features: Object.keys(features).length,
|
|
9024
|
-
flows: Object.keys(flows).length
|
|
9202
|
+
existing.features = { ...existing.features, ...features };
|
|
9203
|
+
existing.flows = { ...existing.flows, ...flows };
|
|
9204
|
+
existing.updatedAt = now;
|
|
9205
|
+
existing.gitHash = gitHash;
|
|
9206
|
+
return { snapshot: existing, afterSave: () => clearAllPartials(rootDir), result: JSON.stringify({
|
|
9207
|
+
status: "updated",
|
|
9208
|
+
mode: "merged",
|
|
9209
|
+
features: Object.keys(existing.features).length,
|
|
9210
|
+
flows: Object.keys(existing.flows).length,
|
|
9211
|
+
removedFeatures: removedFeatures.length,
|
|
9212
|
+
removedFlows: removedFlows.length
|
|
9213
|
+
}) };
|
|
9214
|
+
}
|
|
9215
|
+
const snapshot = {
|
|
9216
|
+
version: 2,
|
|
9217
|
+
createdAt: now,
|
|
9218
|
+
updatedAt: now,
|
|
9219
|
+
gitHash,
|
|
9220
|
+
features,
|
|
9221
|
+
flows
|
|
9222
|
+
};
|
|
9223
|
+
return { snapshot, afterSave: () => clearAllPartials(rootDir), result: JSON.stringify({
|
|
9224
|
+
status: replaceMode ? "replaced" : "created",
|
|
9225
|
+
mode: replaceMode ? "replaced-from-partials" : "fresh",
|
|
9226
|
+
features: Object.keys(features).length,
|
|
9227
|
+
flows: Object.keys(flows).length
|
|
9228
|
+
}) };
|
|
9025
9229
|
});
|
|
9026
9230
|
}
|
|
9027
9231
|
async function getImpact(dir, files) {
|
|
9028
|
-
const rootDir =
|
|
9232
|
+
const rootDir = path34.resolve(dir);
|
|
9029
9233
|
const { analyzeImpact: analyzeImpact2 } = await Promise.resolve().then(() => (init_impact(), impact_exports));
|
|
9030
9234
|
const result = await analyzeImpact2(rootDir, files);
|
|
9031
9235
|
return JSON.stringify(result, null, 2);
|
|
9032
9236
|
}
|
|
9033
9237
|
var VERIFY_DEFAULT_SAMPLE = 5;
|
|
9034
|
-
var VERIFY_MAX_FILES_PER_ENTRY = 8;
|
|
9035
|
-
var VERIFY_SKELETON_CHARS = 500;
|
|
9036
9238
|
async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
|
|
9037
|
-
const rootDir =
|
|
9239
|
+
const rootDir = path34.resolve(dir);
|
|
9038
9240
|
const snapshot = await loadSnapshot(rootDir);
|
|
9039
9241
|
if (!snapshot) {
|
|
9040
9242
|
return JSON.stringify({
|
|
@@ -9068,25 +9270,13 @@ async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
|
|
|
9068
9270
|
const picked = entries.slice(0, Math.max(1, sample));
|
|
9069
9271
|
const toVerify = [];
|
|
9070
9272
|
for (const entry of picked) {
|
|
9071
|
-
const
|
|
9072
|
-
for (const filePath of entry.files.slice(0, VERIFY_MAX_FILES_PER_ENTRY)) {
|
|
9073
|
-
const full = await access.read(filePath);
|
|
9074
|
-
if (full) {
|
|
9075
|
-
skeletons.push({
|
|
9076
|
-
path: full.path,
|
|
9077
|
-
content: full.content.slice(0, VERIFY_SKELETON_CHARS)
|
|
9078
|
-
});
|
|
9079
|
-
} else {
|
|
9080
|
-
skeletons.push({ path: filePath, missing: true });
|
|
9081
|
-
}
|
|
9082
|
-
}
|
|
9273
|
+
const stored = entry.kind === "feature" ? snapshot.features[entry.name] : snapshot.flows[entry.name];
|
|
9083
9274
|
toVerify.push({
|
|
9084
9275
|
name: entry.name,
|
|
9085
9276
|
kind: entry.kind,
|
|
9086
9277
|
description: entry.description,
|
|
9087
9278
|
lastVerified: entry.verifiedAt ?? "never",
|
|
9088
|
-
|
|
9089
|
-
truncated: entry.files.length > VERIFY_MAX_FILES_PER_ENTRY
|
|
9279
|
+
...await prepareSnapshotReview(access, entry.kind, entry.name, stored)
|
|
9090
9280
|
});
|
|
9091
9281
|
}
|
|
9092
9282
|
const neverVerified = entries.filter((e) => !e.verifiedAt).length;
|
|
@@ -9095,58 +9285,94 @@ async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
|
|
|
9095
9285
|
totalEntries: entries.length,
|
|
9096
9286
|
neverVerified,
|
|
9097
9287
|
entries: toVerify,
|
|
9098
|
-
instructions: '
|
|
9288
|
+
instructions: 'Judge whether the files implement each claimed feature/flow, then call save_verification with verdicts: {"<entry name>": {"kind": "<feature|flow from the entry>", "reviewToken": "<token from the entry>", "ok": true|false, "note": "<required when ok is false>"}}. Tokens cover the entry and full contents of the previewed files (at most 8), including unavailable-file markers; previews themselves are truncated. This is a spot-check, not proof of unshown files. A conflict requires verify_snapshot and a fresh review; never reuse a verdict for changed content. If a feature and flow share a name, submit their verdicts in separate calls.'
|
|
9099
9289
|
});
|
|
9100
9290
|
}
|
|
9101
9291
|
async function saveVerification(dir, verdicts) {
|
|
9102
|
-
const rootDir =
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9292
|
+
const rootDir = path34.resolve(dir);
|
|
9293
|
+
return updateSnapshot(rootDir, async (snapshot) => {
|
|
9294
|
+
if (!snapshot) {
|
|
9295
|
+
return { snapshot: null, result: JSON.stringify({ exists: false, stamped: [], hint: "No concept map exists. Build it and run verify_snapshot before recording verdicts." }) };
|
|
9296
|
+
}
|
|
9297
|
+
const stamped = [], unknown = [], failed = [];
|
|
9298
|
+
const conflicts = [], reviewRequired = [], invalid = [];
|
|
9299
|
+
const candidates = [];
|
|
9300
|
+
let access;
|
|
9301
|
+
for (const [name, verdict] of Object.entries(verdicts)) {
|
|
9302
|
+
if (!verdict.kind || !["feature", "flow"].includes(verdict.kind) || !/^[a-f0-9]{64}$/.test(verdict.reviewToken ?? "")) {
|
|
9303
|
+
reviewRequired.push(name);
|
|
9304
|
+
continue;
|
|
9305
|
+
}
|
|
9306
|
+
if (typeof verdict.ok !== "boolean" || !verdict.ok && !verdict.note?.trim()) {
|
|
9307
|
+
invalid.push(name);
|
|
9308
|
+
continue;
|
|
9309
|
+
}
|
|
9310
|
+
const collection = verdict.kind === "feature" ? snapshot.features : snapshot.flows;
|
|
9311
|
+
if (!Object.hasOwn(collection, name)) {
|
|
9312
|
+
unknown.push(name);
|
|
9313
|
+
conflicts.push(name);
|
|
9314
|
+
continue;
|
|
9315
|
+
}
|
|
9316
|
+
const entry = collection[name];
|
|
9317
|
+
access ??= await createFileAccess(rootDir);
|
|
9318
|
+
const current = await prepareSnapshotReview(access, verdict.kind, name, entry);
|
|
9319
|
+
if (current.reviewToken !== verdict.reviewToken) conflicts.push(name);
|
|
9320
|
+
else candidates.push({ name, verdict, entry });
|
|
9321
|
+
}
|
|
9322
|
+
const verifiedHash = candidates.length ? await getCurrentGitHash(rootDir) : "unknown";
|
|
9323
|
+
const freshAccess = candidates.length ? await createFileAccess(rootDir) : null;
|
|
9324
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9325
|
+
for (const { name, verdict, entry } of candidates) {
|
|
9326
|
+
const current = await prepareSnapshotReview(freshAccess, verdict.kind, name, entry);
|
|
9327
|
+
if (current.reviewToken !== verdict.reviewToken) {
|
|
9328
|
+
conflicts.push(name);
|
|
9329
|
+
continue;
|
|
9330
|
+
}
|
|
9331
|
+
entry.verifiedAt = now;
|
|
9332
|
+
entry.verifiedHash = verifiedHash;
|
|
9333
|
+
entry.verificationToken = verdict.reviewToken;
|
|
9334
|
+
if (verdict.ok) {
|
|
9335
|
+
delete entry.verificationFailed;
|
|
9336
|
+
delete entry.verificationNote;
|
|
9337
|
+
} else {
|
|
9338
|
+
entry.verificationFailed = true;
|
|
9339
|
+
entry.verificationNote = verdict.note.trim();
|
|
9340
|
+
failed.push(name);
|
|
9341
|
+
}
|
|
9342
|
+
stamped.push(name);
|
|
9343
|
+
}
|
|
9344
|
+
const rejected = conflicts.length + reviewRequired.length + invalid.length;
|
|
9345
|
+
const status = rejected ? stamped.length ? "partial" : conflicts.length ? "conflict" : reviewRequired.length ? "review_required" : "invalid" : stamped.length ? "saved" : "unchanged";
|
|
9346
|
+
const hints = [];
|
|
9347
|
+
if (conflicts.length) hints.push(`Entries [${conflicts.join(", ")}] changed or were deleted after review. Run verify_snapshot and review current evidence before retrying.`);
|
|
9348
|
+
if (reviewRequired.length) hints.push(`Entries [${reviewRequired.join(", ")}] need kind and reviewToken from verify_snapshot. Inspect that evidence before submitting a verdict.`);
|
|
9349
|
+
if (invalid.length) hints.push(`Entries [${invalid.join(", ")}] need a boolean ok and a non-empty note when ok is false.`);
|
|
9350
|
+
if (failed.length) hints.push(`Entries [${failed.join(", ")}] are mis-mapped. Re-map them: read their actual files, correct the entries, and call save_snapshot with only those entries (plus removeFeatures/removeFlows if a concept no longer exists). Then run verify_snapshot for fresh review tokens.`);
|
|
9351
|
+
if (!hints.length) hints.push(stamped.length ? "All submitted entries verified against the reviewed evidence. Re-run verify_snapshot periodically." : "No verdicts submitted; nothing changed.");
|
|
9352
|
+
if (stamped.length) snapshot.updatedAt = now;
|
|
9353
|
+
return { snapshot: stamped.length ? snapshot : null, result: JSON.stringify({
|
|
9354
|
+
status,
|
|
9355
|
+
stamped,
|
|
9356
|
+
unknown,
|
|
9357
|
+
failed,
|
|
9358
|
+
conflicts,
|
|
9359
|
+
reviewRequired,
|
|
9360
|
+
invalid,
|
|
9361
|
+
hint: hints.join(" ")
|
|
9362
|
+
}) };
|
|
9137
9363
|
});
|
|
9138
9364
|
}
|
|
9139
9365
|
async function saveDecision(dir, input) {
|
|
9140
|
-
const rootDir =
|
|
9366
|
+
const rootDir = path34.resolve(dir);
|
|
9141
9367
|
const { upsertDecision: upsertDecision2 } = await Promise.resolve().then(() => (init_decisions(), decisions_exports));
|
|
9142
9368
|
const result = await upsertDecision2(rootDir, input);
|
|
9143
9369
|
return JSON.stringify(result);
|
|
9144
9370
|
}
|
|
9145
9371
|
async function reviewDecision2(dir, input) {
|
|
9146
|
-
return JSON.stringify(await reviewDecision(
|
|
9372
|
+
return JSON.stringify(await reviewDecision(path34.resolve(dir), input));
|
|
9147
9373
|
}
|
|
9148
9374
|
async function getContext(dir, task, files) {
|
|
9149
|
-
const rootDir =
|
|
9375
|
+
const rootDir = path34.resolve(dir);
|
|
9150
9376
|
const { assembleContext: assembleContext2 } = await Promise.resolve().then(() => (init_assemble(), assemble_exports));
|
|
9151
9377
|
const bundle = await assembleContext2(rootDir, task, files);
|
|
9152
9378
|
return JSON.stringify(bundle);
|
|
@@ -9195,7 +9421,7 @@ async function masonInit(dir, options = {}) {
|
|
|
9195
9421
|
return JSON.stringify(await setupProject2(dir, options), null, 2);
|
|
9196
9422
|
}
|
|
9197
9423
|
if (options.host) throw new Error("host applies only to mode: setup.");
|
|
9198
|
-
const rootDir =
|
|
9424
|
+
const rootDir = path34.resolve(dir);
|
|
9199
9425
|
const marker = await loadProjectMarker(rootDir);
|
|
9200
9426
|
const { loadSetup: loadSetup2 } = await Promise.resolve().then(() => (init_model(), model_exports));
|
|
9201
9427
|
const setup = await loadSetup2(rootDir);
|
|
@@ -9215,7 +9441,7 @@ async function masonInit(dir, options = {}) {
|
|
|
9215
9441
|
);
|
|
9216
9442
|
}
|
|
9217
9443
|
async function masonCompleteInit(dir, options = {}) {
|
|
9218
|
-
const rootDir =
|
|
9444
|
+
const rootDir = path34.resolve(dir);
|
|
9219
9445
|
const existing = await loadProjectMarker(rootDir);
|
|
9220
9446
|
const features = await loadProjectFeatures(rootDir);
|
|
9221
9447
|
const marker = {
|
|
@@ -9324,7 +9550,7 @@ async function masonSetConfluence(input) {
|
|
|
9324
9550
|
);
|
|
9325
9551
|
}
|
|
9326
9552
|
async function exportToConfluenceTool(dir, overrides) {
|
|
9327
|
-
const rootDir =
|
|
9553
|
+
const rootDir = path34.resolve(dir);
|
|
9328
9554
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports2));
|
|
9329
9555
|
const { exportToConfluence: exportToConfluence2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
9330
9556
|
const config = await loadConfig2();
|
|
@@ -9363,10 +9589,10 @@ function createMcpServer() {
|
|
|
9363
9589
|
const server = new McpServer(
|
|
9364
9590
|
{
|
|
9365
9591
|
name: "mason",
|
|
9366
|
-
version: "0.
|
|
9592
|
+
version: "0.18.0"
|
|
9367
9593
|
},
|
|
9368
9594
|
{
|
|
9369
|
-
instructions: (setupNotice ? setupNotice + "\n\n" : "") + 'Mason retrieves recorded decisions, file impact, and optional feature/flow maps. Use get_context with the task and known files, and get_impact before editing. Save learned rationale and constraints with save_decision; review and commit the local records through the project workflow. These tools work without initialization or a concept map. Consult trust and diagnostics: changed or unknown freshness needs source inspection,
|
|
9595
|
+
instructions: (setupNotice ? setupNotice + "\n\n" : "") + 'Mason retrieves recorded decisions, file impact, and optional feature/flow maps. Use get_context with the task and known files, and get_impact before editing. Save learned rationale and constraints with save_decision; review and commit the local records through the project workflow. These tools work without initialization or a concept map. Consult trust and diagnostics: changed or unknown freshness needs source inspection, stale or unknown map verification needs a fresh review, recorded failed verdicts need correction, and proposals are suggestions and legacy records are unreviewed. Accepted decisions are recorded team constraints, still subject to freshness checks. Use review_decision to prepare code evidence and record only authorized acceptance, reaffirmation, or retirement. Never invent a reviewer or treat these recorded identities as authenticated approval. mason_init returns documentation audit and committed-diff review findings with a quickstart guide. Use mode: "map" only when a full architecture map is requested. A missing map is not a setup failure; use decisions and source evidence. get_snapshot provides architecture navigation when a map is available. The mason-audit and mason-review CLIs also work without setup.'
|
|
9370
9596
|
}
|
|
9371
9597
|
);
|
|
9372
9598
|
server.tool(
|
|
@@ -9488,7 +9714,7 @@ function createMcpServer() {
|
|
|
9488
9714
|
);
|
|
9489
9715
|
server.tool(
|
|
9490
9716
|
"get_snapshot",
|
|
9491
|
-
"Return the optional feature-to-file architecture map with drift and trust evidence. If no map exists, returns exists:false plus project structure, Git signals, and test pairs. Decision capture, get_context, and get_impact still work. No initialization required.",
|
|
9717
|
+
"Return the optional feature-to-file architecture map with drift and trust evidence. Map verification is rechecked against sampled source contents; stale or unknown verification requires a fresh review, and recordedVerdict preserves the historical verdict. If no map exists, returns exists:false plus project structure, Git signals, and test pairs. Decision capture, get_context, and get_impact still work. No initialization required.",
|
|
9492
9718
|
{
|
|
9493
9719
|
dir: z24.string().describe("Absolute path to the project root directory")
|
|
9494
9720
|
},
|
|
@@ -9501,7 +9727,7 @@ function createMcpServer() {
|
|
|
9501
9727
|
);
|
|
9502
9728
|
server.tool(
|
|
9503
9729
|
"get_context",
|
|
9504
|
-
"Assemble task context: matching decisions with rationale, approval, owner, sources, last review, and freshness, plus related tests, file impact, and any available map entries. Proposals are suggestions; legacy records are unreviewed; accepted decisions are constraints subject to freshness. No initialization or map required. Pass task and optional files. map.status and diagnostics preserve missing or invalid knowledge. Impact covers up to three unique files, expanding directory anchors.",
|
|
9730
|
+
"Assemble task context: matching decisions with rationale, approval, owner, sources, last review, and freshness, plus related tests, file impact, and any available map entries. Map verification is rechecked against sampled source contents; stale or unknown verification requires a fresh review, and recordedVerdict preserves the historical verdict. Proposals are suggestions; legacy records are unreviewed; accepted decisions are constraints subject to freshness. No initialization or map required. Pass task and optional files. map.status and diagnostics preserve missing or invalid knowledge. Impact covers up to three unique files, expanding directory anchors.",
|
|
9505
9731
|
{
|
|
9506
9732
|
dir: z24.string().describe("Absolute path to the project root directory"),
|
|
9507
9733
|
task: z24.string().describe("The task, bug, or change request in natural language \u2014 e.g. 'add rate limiting to the API client' or a ticket description"),
|
|
@@ -9677,7 +9903,7 @@ function createMcpServer() {
|
|
|
9677
9903
|
);
|
|
9678
9904
|
server.tool(
|
|
9679
9905
|
"verify_snapshot",
|
|
9680
|
-
"Spot-check the concept map's CORRECTNESS (drift checks freshness; this checks entries were right to begin with). Returns a sample of entries \u2014 always the never-verified and least-recently-verified first \u2014 with skeletons of their claimed files, for you to judge whether the files actually implement what the entry claims. Report verdicts back via save_verification. Run periodically, or after an automated refresh wrote entries no human reviewed.",
|
|
9906
|
+
"Spot-check the concept map's CORRECTNESS (drift checks freshness; this checks entries were right to begin with). Returns a sample of entries \u2014 always the never-verified and least-recently-verified first \u2014 with skeletons of their claimed files, for you to judge whether the files actually implement what the entry claims. Report verdicts back via save_verification with each entry\u2019s kind and reviewToken. Tokens bind the entry and bounded source evidence; changed evidence requires a fresh review. Run periodically, or after an automated refresh wrote entries no human reviewed.",
|
|
9681
9907
|
{
|
|
9682
9908
|
dir: z24.string().describe("Absolute path to the project root directory"),
|
|
9683
9909
|
sample: z24.number().int().optional().describe("Entries to sample (default 5)")
|
|
@@ -9689,11 +9915,13 @@ function createMcpServer() {
|
|
|
9689
9915
|
);
|
|
9690
9916
|
server.tool(
|
|
9691
9917
|
"save_verification",
|
|
9692
|
-
"Record verify_snapshot verdicts. Entries judged ok are stamped verifiedAt; failures are flagged verificationFailed with your note and surface in get_context, get_snapshot, and mason_check_drift until corrected. Verdict notes are required for failures.",
|
|
9918
|
+
"Record verify_snapshot verdicts using each entry\u2019s kind and reviewToken. Missing tokens request a fresh review; changed or deleted entries return conflicts without being stamped. Entries judged ok are stamped verifiedAt; failures are flagged verificationFailed with your note and surface in get_context, get_snapshot, and mason_check_drift until corrected. Verdict notes are required for failures.",
|
|
9693
9919
|
{
|
|
9694
9920
|
dir: z24.string().describe("Absolute path to the project root directory"),
|
|
9695
9921
|
verdicts: z24.record(
|
|
9696
9922
|
z24.object({
|
|
9923
|
+
kind: z24.enum(["feature", "flow"]).optional().describe("Entry kind returned by verify_snapshot; required to record a verdict"),
|
|
9924
|
+
reviewToken: z24.string().optional().describe("Copy the reviewToken from verify_snapshot after inspecting its evidence; required to record a verdict"),
|
|
9697
9925
|
ok: z24.boolean(),
|
|
9698
9926
|
note: z24.string().optional().describe("One line on what's wrong \u2014 required when ok is false")
|
|
9699
9927
|
})
|