mason-context 0.22.0 → 0.23.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 +10 -0
- package/dist/mason-audit.js +74 -26
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-drift.js +42 -19
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +41 -19
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +699 -173
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +54 -29
- package/dist/mason-review.js.map +1 -1
- package/dist/mason-runtime.js +12 -6
- package/dist/mason-runtime.js.map +1 -1
- package/dist/mason.js +722 -148
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/dist/mason-mcp.js
CHANGED
|
@@ -483,11 +483,12 @@ async function storePath(root, relative, createParents = false) {
|
|
|
483
483
|
}
|
|
484
484
|
return current;
|
|
485
485
|
}
|
|
486
|
-
async function readStoreJson(root, relative) {
|
|
486
|
+
async function readStoreJson(root, relative, maxBytes = STORE_MAX_BYTES) {
|
|
487
487
|
try {
|
|
488
488
|
const file = await storePath(root, relative);
|
|
489
|
-
const raw = await readBoundedFile(file,
|
|
490
|
-
if (raw === null)
|
|
489
|
+
const raw = await readBoundedFile(file, maxBytes);
|
|
490
|
+
if (raw === null)
|
|
491
|
+
throw new Error(`file is not regular or exceeds ${maxBytes / 1024 / 1024} MiB`);
|
|
491
492
|
const parsed = JSON.parse(raw);
|
|
492
493
|
if (parsed === null) throw new Error("expected a JSON object, received null");
|
|
493
494
|
return parsed;
|
|
@@ -499,9 +500,9 @@ async function readStoreJson(root, relative) {
|
|
|
499
500
|
);
|
|
500
501
|
}
|
|
501
502
|
}
|
|
502
|
-
async function writeStoreJson(root, relative, value) {
|
|
503
|
-
const payload = JSON.stringify(value, null, 2) + "\n";
|
|
504
|
-
if (Buffer.byteLength(payload) >
|
|
503
|
+
async function writeStoreJson(root, relative, value, options = {}) {
|
|
504
|
+
const payload = JSON.stringify(value, null, options.compact ? void 0 : 2) + "\n";
|
|
505
|
+
if (Buffer.byteLength(payload) > STORE_MAX_BYTES) {
|
|
505
506
|
throw new Error(`Mason store ${relative} exceeds 10 MiB`);
|
|
506
507
|
}
|
|
507
508
|
const file = await storePath(root, relative, true);
|
|
@@ -519,11 +520,13 @@ async function writeStoreJson(root, relative, value) {
|
|
|
519
520
|
await fs2.rm(temporary, { force: true });
|
|
520
521
|
}
|
|
521
522
|
}
|
|
523
|
+
var STORE_MAX_BYTES;
|
|
522
524
|
var init_storage = __esm({
|
|
523
525
|
"src/utils/storage.ts"() {
|
|
524
526
|
"use strict";
|
|
525
527
|
init_paths();
|
|
526
528
|
init_files();
|
|
529
|
+
STORE_MAX_BYTES = 10 * 1024 * 1024;
|
|
527
530
|
}
|
|
528
531
|
});
|
|
529
532
|
|
|
@@ -544,7 +547,12 @@ async function captureAnchorScopes(root, scopes2) {
|
|
|
544
547
|
);
|
|
545
548
|
const inventory = access.inventory();
|
|
546
549
|
if (!inventory) throw new Error("Git anchor inventory unavailable");
|
|
547
|
-
const
|
|
550
|
+
const visible = indexMatchingPaths(
|
|
551
|
+
union.map((anchor) => [anchor]),
|
|
552
|
+
inventory
|
|
553
|
+
);
|
|
554
|
+
const hidden = new Set(union.filter((_, index2) => !visible[index2].length));
|
|
555
|
+
const ignored = hidden.size ? (await inspectionGit(
|
|
548
556
|
[
|
|
549
557
|
"ls-files",
|
|
550
558
|
"-z",
|
|
@@ -552,13 +560,11 @@ async function captureAnchorScopes(root, scopes2) {
|
|
|
552
560
|
"--ignored",
|
|
553
561
|
"--exclude-standard",
|
|
554
562
|
"--",
|
|
555
|
-
...
|
|
563
|
+
...[...hidden].map((anchor) => `:(literal)${anchor}`)
|
|
556
564
|
],
|
|
557
565
|
{ cwd: root, maxBuffer: 8 * 1024 * 1024 }
|
|
558
|
-
);
|
|
559
|
-
const files = [
|
|
560
|
-
.../* @__PURE__ */ new Set([...inventory, ...ignored.stdout.split("\0").filter(Boolean)])
|
|
561
|
-
].sort();
|
|
566
|
+
)).stdout.split("\0").filter(Boolean) : [];
|
|
567
|
+
const files = [.../* @__PURE__ */ new Set([...inventory, ...ignored])].sort();
|
|
562
568
|
const patterns = await Promise.all(
|
|
563
569
|
union.map(async (anchor) => {
|
|
564
570
|
const literal2 = fg3.escapePath(anchor);
|
|
@@ -574,7 +580,7 @@ async function captureAnchorScopes(root, scopes2) {
|
|
|
574
580
|
const filesByScope = new Map(
|
|
575
581
|
scopes2.map((scope, index2) => [JSON.stringify([...new Set(scope)].sort()), indexed[index2]])
|
|
576
582
|
);
|
|
577
|
-
return { filesByScope, eligible };
|
|
583
|
+
return { filesByScope, eligible, hidden, ignored: new Set(ignored) };
|
|
578
584
|
};
|
|
579
585
|
return Promise.all(
|
|
580
586
|
scopes2.map((scope) => {
|
|
@@ -586,8 +592,10 @@ async function captureAnchorScopes(root, scopes2) {
|
|
|
586
592
|
const capture = { anchors, files: {}, complete: true };
|
|
587
593
|
if (!anchors.length) return capture;
|
|
588
594
|
try {
|
|
589
|
-
const { eligible, filesByScope } = await (context ??= prepareContext());
|
|
590
|
-
const files = filesByScope.get(JSON.stringify(anchors))
|
|
595
|
+
const { eligible, filesByScope, hidden, ignored } = await (context ??= prepareContext());
|
|
596
|
+
const files = filesByScope.get(JSON.stringify(anchors)).filter(
|
|
597
|
+
(file) => !ignored.has(file) || anchors.some((anchor) => hidden.has(anchor) && anchorMatches(anchor, file))
|
|
598
|
+
);
|
|
591
599
|
if (files.length > 2e3) return { ...capture, complete: false };
|
|
592
600
|
for (const file of files) {
|
|
593
601
|
const evidence = await inspectionRead(
|
|
@@ -3626,6 +3634,195 @@ var init_deps_changed = __esm({
|
|
|
3626
3634
|
}
|
|
3627
3635
|
});
|
|
3628
3636
|
|
|
3637
|
+
// src/decisions/excerpts.ts
|
|
3638
|
+
import { createHash as createHash4 } from "crypto";
|
|
3639
|
+
import { execFile as execFile2 } from "child_process";
|
|
3640
|
+
import { promisify as promisify2 } from "util";
|
|
3641
|
+
function driftBaseline(record) {
|
|
3642
|
+
const effective = effectiveDecision(record);
|
|
3643
|
+
const inspection = latestDecisionInspection(
|
|
3644
|
+
record,
|
|
3645
|
+
decisionApproval(effective) === "accepted" ? "operative" : "all"
|
|
3646
|
+
);
|
|
3647
|
+
return {
|
|
3648
|
+
record: effective,
|
|
3649
|
+
capture: inspection?.capture ?? (effective.version === 2 ? effective.capture : void 0),
|
|
3650
|
+
commits: [inspection?.headHash, effective.refreshedHash].filter(
|
|
3651
|
+
(hash2) => !!hash2 && /^[a-f0-9]{40,64}$/.test(hash2)
|
|
3652
|
+
)
|
|
3653
|
+
};
|
|
3654
|
+
}
|
|
3655
|
+
async function capturedCommit(root, file, hash2, preferred) {
|
|
3656
|
+
if (/[\n\r]/.test(file)) return null;
|
|
3657
|
+
const { stdout } = await exec(
|
|
3658
|
+
"git",
|
|
3659
|
+
["--literal-pathspecs", "log", "-n", String(HISTORY_DEPTH), "--format=%H", "--", file],
|
|
3660
|
+
{ cwd: root, maxBuffer: 1024 * 1024 }
|
|
3661
|
+
);
|
|
3662
|
+
const commits = [.../* @__PURE__ */ new Set([...preferred, ...stdout.split("\n").filter(Boolean)])];
|
|
3663
|
+
const batch = exec("git", ["cat-file", "--batch"], {
|
|
3664
|
+
cwd: root,
|
|
3665
|
+
encoding: "buffer",
|
|
3666
|
+
maxBuffer: 64 * 1024 * 1024
|
|
3667
|
+
});
|
|
3668
|
+
batch.child.stdin.end(commits.map((commit) => `${commit}:${file}
|
|
3669
|
+
`).join(""));
|
|
3670
|
+
const output = (await batch).stdout;
|
|
3671
|
+
let position = 0;
|
|
3672
|
+
for (const commit of commits) {
|
|
3673
|
+
const end = output.indexOf(10, position);
|
|
3674
|
+
if (end < 0) break;
|
|
3675
|
+
const header = output.subarray(position, end).toString("utf8").split(" ");
|
|
3676
|
+
position = end + 1;
|
|
3677
|
+
if (header[1] !== "blob") continue;
|
|
3678
|
+
const size = Number(header[2]);
|
|
3679
|
+
const bytes = output.subarray(position, position + size);
|
|
3680
|
+
position += size + 1;
|
|
3681
|
+
if (createHash4("sha256").update(bytes).digest("hex") === hash2) return commit;
|
|
3682
|
+
}
|
|
3683
|
+
return null;
|
|
3684
|
+
}
|
|
3685
|
+
function namedIdentifiers(text2) {
|
|
3686
|
+
const names = /* @__PURE__ */ new Set();
|
|
3687
|
+
const add = (name) => {
|
|
3688
|
+
if (name.length >= 3) names.add(name);
|
|
3689
|
+
};
|
|
3690
|
+
for (const [, span] of text2.matchAll(/`([^`]+)`/g))
|
|
3691
|
+
for (const [name] of span.matchAll(/[A-Za-z_$][\w$]*(?:-[A-Za-z0-9_$]+)*/g)) add(name);
|
|
3692
|
+
for (const [, name] of text2.matchAll(/([A-Za-z_$][\w$]*)\(/g)) add(name);
|
|
3693
|
+
for (const [name] of text2.matchAll(/[A-Za-z_$][\w$]*/g))
|
|
3694
|
+
if (/[a-z][A-Z]|[_$]/.test(name)) add(name);
|
|
3695
|
+
return [...names];
|
|
3696
|
+
}
|
|
3697
|
+
function codeTokens(code) {
|
|
3698
|
+
const tokens2 = /* @__PURE__ */ new Set();
|
|
3699
|
+
for (const [name] of code.matchAll(/[A-Za-z_$][\w$]*/g)) {
|
|
3700
|
+
tokens2.add(name.toLowerCase());
|
|
3701
|
+
for (const part of name.split(/_+|(?<=[a-z0-9])(?=[A-Z])/))
|
|
3702
|
+
if (part.length >= 3) tokens2.add(part.toLowerCase());
|
|
3703
|
+
}
|
|
3704
|
+
return tokens2;
|
|
3705
|
+
}
|
|
3706
|
+
function parseHunks(diff) {
|
|
3707
|
+
const hunks = [];
|
|
3708
|
+
for (const line of diff.split("\n")) {
|
|
3709
|
+
if (line.startsWith("@@")) hunks.push({ header: line, lines: [] });
|
|
3710
|
+
else if (hunks.length && /^[+-]/.test(line)) hunks.at(-1).lines.push(line);
|
|
3711
|
+
}
|
|
3712
|
+
return hunks;
|
|
3713
|
+
}
|
|
3714
|
+
async function driftExcerpt(root, record, changedFiles) {
|
|
3715
|
+
const baseline = driftBaseline(record);
|
|
3716
|
+
const excerpt = { mentions: [], relevance: "unknown", files: [], omittedFiles: [] };
|
|
3717
|
+
const named = namedIdentifiers(`${baseline.record.title}
|
|
3718
|
+
${baseline.record.body}`);
|
|
3719
|
+
const found = /* @__PURE__ */ new Set();
|
|
3720
|
+
let complete = !!baseline.capture && changedFiles.length <= MAX_DIFFED_FILES;
|
|
3721
|
+
const access = await createFileAccess(root);
|
|
3722
|
+
for (const [index2, file] of changedFiles.slice(0, MAX_DIFFED_FILES).entries()) {
|
|
3723
|
+
let hunks = null;
|
|
3724
|
+
let unavailable;
|
|
3725
|
+
const captured = baseline.capture?.files[file];
|
|
3726
|
+
try {
|
|
3727
|
+
if (!baseline.capture?.anchors.some((anchor) => anchorMatches(anchor, file))) {
|
|
3728
|
+
unavailable = "not in the captured anchor scope";
|
|
3729
|
+
} else if (captured === void 0) {
|
|
3730
|
+
const source = await access.read(file);
|
|
3731
|
+
if (source)
|
|
3732
|
+
hunks = [
|
|
3733
|
+
{
|
|
3734
|
+
header: "(added since capture)",
|
|
3735
|
+
lines: source.content.replace(/\r?\n$/, "").split("\n").map((line) => "+" + line)
|
|
3736
|
+
}
|
|
3737
|
+
];
|
|
3738
|
+
else unavailable = "added since capture but unreadable";
|
|
3739
|
+
} else {
|
|
3740
|
+
const commit = await capturedCommit(root, file, captured, baseline.commits);
|
|
3741
|
+
if (!commit) unavailable = "captured content is not in recent Git history";
|
|
3742
|
+
else {
|
|
3743
|
+
const { stdout } = await exec(
|
|
3744
|
+
"git",
|
|
3745
|
+
[
|
|
3746
|
+
"--literal-pathspecs",
|
|
3747
|
+
"diff",
|
|
3748
|
+
"--no-ext-diff",
|
|
3749
|
+
"--no-textconv",
|
|
3750
|
+
"--no-color",
|
|
3751
|
+
"--no-renames",
|
|
3752
|
+
"-U0",
|
|
3753
|
+
commit,
|
|
3754
|
+
"--",
|
|
3755
|
+
file
|
|
3756
|
+
],
|
|
3757
|
+
{ cwd: root, maxBuffer: 1024 * 1024 }
|
|
3758
|
+
);
|
|
3759
|
+
hunks = parseHunks(stdout);
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
} catch {
|
|
3763
|
+
unavailable = "diff could not be read within its bounds";
|
|
3764
|
+
}
|
|
3765
|
+
if (!hunks?.length) complete = false;
|
|
3766
|
+
const code = (hunks ?? []).map((hunk) => [hunk.header, ...hunk.lines.map((line) => line.slice(1))].join("\n")).join("\n");
|
|
3767
|
+
const tokens2 = codeTokens(code);
|
|
3768
|
+
for (const name of named)
|
|
3769
|
+
if (name.includes("-") ? code.toLowerCase().includes(name.toLowerCase()) : tokens2.has(name.toLowerCase()))
|
|
3770
|
+
found.add(name);
|
|
3771
|
+
if (index2 >= MAX_SHOWN_FILES) excerpt.omittedFiles.push(file);
|
|
3772
|
+
else
|
|
3773
|
+
excerpt.files.push({
|
|
3774
|
+
path: file,
|
|
3775
|
+
hunks: (hunks ?? []).slice(0, MAX_SHOWN_HUNKS).map((hunk) => ({ ...hunk, lines: hunk.lines.slice(0, MAX_HUNK_LINES) })),
|
|
3776
|
+
omittedHunks: Math.max(0, (hunks?.length ?? 0) - MAX_SHOWN_HUNKS),
|
|
3777
|
+
...unavailable ? { unavailable } : {}
|
|
3778
|
+
});
|
|
3779
|
+
}
|
|
3780
|
+
excerpt.omittedFiles.push(...changedFiles.slice(MAX_DIFFED_FILES));
|
|
3781
|
+
excerpt.mentions = [...found].sort();
|
|
3782
|
+
excerpt.relevance = found.size ? "mentioned" : complete && named.length ? "low" : "unknown";
|
|
3783
|
+
if (excerpt.relevance === "unknown")
|
|
3784
|
+
excerpt.unknownReason = named.length ? "incomplete" : "no-identifiers";
|
|
3785
|
+
return excerpt;
|
|
3786
|
+
}
|
|
3787
|
+
function formatDriftExcerpt(title, id, excerpt) {
|
|
3788
|
+
const safe = (value, max = 160) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, max);
|
|
3789
|
+
const lines = [`Changed lines for decision ${JSON.stringify(safe(title, 250))} (${safe(id)}):`];
|
|
3790
|
+
lines.push(
|
|
3791
|
+
excerpt.relevance === "mentioned" ? ` Changed lines mention identifiers named by the decision: ${excerpt.mentions.map((name) => safe(name, 60)).join(", ")}.` : excerpt.relevance === "low" ? " Low relevance: no changed line or enclosing declaration mentions an identifier named by the decision. This is a name match, not evidence of consistency; inspect before recording." : excerpt.unknownReason === "no-identifiers" ? " Relevance not assessed: the decision names no code identifiers (such as `refresh()` or onRefresh) to match against the changes." : " Relevance unknown: some changed content could not be compared with its captured version."
|
|
3792
|
+
);
|
|
3793
|
+
for (const file of excerpt.files) {
|
|
3794
|
+
if (file.unavailable) {
|
|
3795
|
+
lines.push(` ${safe(file.path)}: ${file.unavailable}.`);
|
|
3796
|
+
continue;
|
|
3797
|
+
}
|
|
3798
|
+
for (const hunk of file.hunks)
|
|
3799
|
+
lines.push(
|
|
3800
|
+
` ${safe(file.path)} ${safe(hunk.header, 120)}`,
|
|
3801
|
+
...hunk.lines.map((line) => " " + safe(line))
|
|
3802
|
+
);
|
|
3803
|
+
if (!file.hunks.length) lines.push(` ${safe(file.path)}: changed without text hunks.`);
|
|
3804
|
+
if (file.omittedHunks) lines.push(` ${safe(file.path)}: ${file.omittedHunks} more hunk(s).`);
|
|
3805
|
+
}
|
|
3806
|
+
if (excerpt.omittedFiles.length)
|
|
3807
|
+
lines.push(` ${excerpt.omittedFiles.length} more changed file(s) not shown.`);
|
|
3808
|
+
return lines.join("\n");
|
|
3809
|
+
}
|
|
3810
|
+
var exec, MAX_DIFFED_FILES, MAX_SHOWN_FILES, MAX_SHOWN_HUNKS, MAX_HUNK_LINES, HISTORY_DEPTH;
|
|
3811
|
+
var init_excerpts = __esm({
|
|
3812
|
+
"src/decisions/excerpts.ts"() {
|
|
3813
|
+
"use strict";
|
|
3814
|
+
init_files();
|
|
3815
|
+
init_paths();
|
|
3816
|
+
init_provenance();
|
|
3817
|
+
exec = promisify2(execFile2);
|
|
3818
|
+
MAX_DIFFED_FILES = 8;
|
|
3819
|
+
MAX_SHOWN_FILES = 3;
|
|
3820
|
+
MAX_SHOWN_HUNKS = 2;
|
|
3821
|
+
MAX_HUNK_LINES = 8;
|
|
3822
|
+
HISTORY_DEPTH = 20;
|
|
3823
|
+
}
|
|
3824
|
+
});
|
|
3825
|
+
|
|
3629
3826
|
// src/decisions/drift.ts
|
|
3630
3827
|
import path17 from "path";
|
|
3631
3828
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
@@ -3690,15 +3887,11 @@ async function computeDecisionDrift(rootDir, decisions) {
|
|
|
3690
3887
|
};
|
|
3691
3888
|
for (const record of store.records) {
|
|
3692
3889
|
if (record.status !== "active") continue;
|
|
3693
|
-
const
|
|
3694
|
-
const
|
|
3695
|
-
record,
|
|
3696
|
-
decisionApproval(effective) === "accepted" ? "operative" : "all"
|
|
3697
|
-
);
|
|
3698
|
-
const state = await inspect(effective, inspection?.capture);
|
|
3890
|
+
const baseline = driftBaseline(record);
|
|
3891
|
+
const state = await inspect(baseline.record, baseline.capture);
|
|
3699
3892
|
report.freshness[record.id] = state.freshness;
|
|
3700
3893
|
if (state.changedFiles.length) report.staleDecisions[record.id] = state.changedFiles;
|
|
3701
|
-
if (
|
|
3894
|
+
if (baseline.record !== record)
|
|
3702
3895
|
(report.pendingProposals ??= {})[record.id] = await inspect(
|
|
3703
3896
|
record,
|
|
3704
3897
|
latestDecisionInspection(record)?.capture
|
|
@@ -3714,13 +3907,14 @@ var init_drift2 = __esm({
|
|
|
3714
3907
|
init_paths();
|
|
3715
3908
|
init_snapshot();
|
|
3716
3909
|
init_decisions();
|
|
3910
|
+
init_excerpts();
|
|
3717
3911
|
init_provenance();
|
|
3718
3912
|
}
|
|
3719
3913
|
});
|
|
3720
3914
|
|
|
3721
3915
|
// src/audit/checks/decision-anchor.ts
|
|
3722
3916
|
async function checkDecisionAnchors(ctx) {
|
|
3723
|
-
const result = emptyResult();
|
|
3917
|
+
const result = { ...emptyResult(), incompleteDecisions: [] };
|
|
3724
3918
|
if (!ctx.decisionsPresent) return result;
|
|
3725
3919
|
const store = await loadDecisionStore(ctx.root);
|
|
3726
3920
|
const records = store.records;
|
|
@@ -3752,6 +3946,7 @@ async function checkDecisionAnchors(ctx) {
|
|
|
3752
3946
|
check: "decision-anchor-drift",
|
|
3753
3947
|
reason: `Anchor evidence for decision ${record.id} is incomplete or unreadable`
|
|
3754
3948
|
});
|
|
3949
|
+
result.incompleteDecisions.push(record.id);
|
|
3755
3950
|
continue;
|
|
3756
3951
|
}
|
|
3757
3952
|
if (!changedFiles.length) continue;
|
|
@@ -3813,7 +4008,7 @@ var init_checks = __esm({
|
|
|
3813
4008
|
});
|
|
3814
4009
|
|
|
3815
4010
|
// src/audit/findings.ts
|
|
3816
|
-
import { createHash as
|
|
4011
|
+
import { createHash as createHash5 } from "crypto";
|
|
3817
4012
|
import { z as z6 } from "zod";
|
|
3818
4013
|
function findingId(finding) {
|
|
3819
4014
|
const e = finding.evidence;
|
|
@@ -3844,7 +4039,7 @@ var digest, checkSchema, commitSchema, anchorSchema, count, scopeSchema, evidenc
|
|
|
3844
4039
|
var init_findings = __esm({
|
|
3845
4040
|
"src/audit/findings.ts"() {
|
|
3846
4041
|
"use strict";
|
|
3847
|
-
digest = (value) =>
|
|
4042
|
+
digest = (value) => createHash5("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3848
4043
|
checkSchema = z6.enum([
|
|
3849
4044
|
"deleted-reference",
|
|
3850
4045
|
"new-module",
|
|
@@ -3951,12 +4146,25 @@ var init_findings = __esm({
|
|
|
3951
4146
|
}
|
|
3952
4147
|
});
|
|
3953
4148
|
|
|
4149
|
+
// src/automation/pending-tools.ts
|
|
4150
|
+
function boundPending(pending) {
|
|
4151
|
+
for (const key of Object.keys(pending).slice(0, -PENDING_LIMIT + 1)) delete pending[key];
|
|
4152
|
+
}
|
|
4153
|
+
var PENDING_LIMIT;
|
|
4154
|
+
var init_pending_tools = __esm({
|
|
4155
|
+
"src/automation/pending-tools.ts"() {
|
|
4156
|
+
"use strict";
|
|
4157
|
+
PENDING_LIMIT = 32;
|
|
4158
|
+
}
|
|
4159
|
+
});
|
|
4160
|
+
|
|
3954
4161
|
// src/automation/stop-review.ts
|
|
3955
4162
|
import { z as z7 } from "zod";
|
|
3956
4163
|
function observeDecisionEdits(session, inputs, event) {
|
|
3957
4164
|
if (event.host !== "claude" || !event.mutating || !event.toolId) return;
|
|
3958
4165
|
const state = session.stopReview ??= empty();
|
|
3959
4166
|
if (event.event === "before_tool") {
|
|
4167
|
+
boundPending(state.pending);
|
|
3960
4168
|
state.pending[event.toolId] = { head: inputs.head, evidence: inputs.decisionEvidence };
|
|
3961
4169
|
} else if (event.event === "after_tool") {
|
|
3962
4170
|
const before = state.pending[event.toolId];
|
|
@@ -4013,7 +4221,7 @@ function decisionStopReview(session, inputs, report, reentry) {
|
|
|
4013
4221
|
}
|
|
4014
4222
|
decisions.set(evidence.decisionId, value);
|
|
4015
4223
|
}
|
|
4016
|
-
if (!decisions.size) return { reason: null, block: false };
|
|
4224
|
+
if (!decisions.size) return { reason: null, block: false, decisions: [] };
|
|
4017
4225
|
let fresh = false;
|
|
4018
4226
|
for (const [id, value] of decisions) {
|
|
4019
4227
|
const previous = state.delivered[id];
|
|
@@ -4036,7 +4244,7 @@ function decisionStopReview(session, inputs, report, reentry) {
|
|
|
4036
4244
|
"If contradicted or uncertain, explain the mismatch to the user and request direction. Do not accept, reaffirm, or retire a decision without authorized human review. A revised proposal leaves the accepted revision operative until reviewed.",
|
|
4037
4245
|
`Evidence: ${report.reportPath}. Concurrent changes can contribute; captures do not prove authorship. This reminder does not resolve the finding.`
|
|
4038
4246
|
].join("\n");
|
|
4039
|
-
return { reason: reason2, block: !reentry && fresh };
|
|
4247
|
+
return { reason: reason2, block: !reentry && fresh, decisions: [...decisions.keys()] };
|
|
4040
4248
|
}
|
|
4041
4249
|
var evidenceSchema2, stopReviewSchema, empty;
|
|
4042
4250
|
var init_stop_review = __esm({
|
|
@@ -4044,6 +4252,7 @@ var init_stop_review = __esm({
|
|
|
4044
4252
|
"use strict";
|
|
4045
4253
|
init_anchors();
|
|
4046
4254
|
init_findings();
|
|
4255
|
+
init_pending_tools();
|
|
4047
4256
|
evidenceSchema2 = z7.record(z7.object({ digest: z7.string(), capture: anchorCaptureSchema }));
|
|
4048
4257
|
stopReviewSchema = z7.object({
|
|
4049
4258
|
pending: z7.record(z7.object({ head: z7.string(), evidence: evidenceSchema2 })),
|
|
@@ -4200,16 +4409,41 @@ function parseState(raw) {
|
|
|
4200
4409
|
throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
|
|
4201
4410
|
}
|
|
4202
4411
|
}
|
|
4412
|
+
function clearPendingTools(session) {
|
|
4413
|
+
session.pending = {};
|
|
4414
|
+
if (session.stopReview) session.stopReview.pending = {};
|
|
4415
|
+
}
|
|
4416
|
+
function pruneSessions(state, now, active, limit = SESSION_LIMIT) {
|
|
4417
|
+
for (const session of Object.values(state.sessions))
|
|
4418
|
+
if (now.getTime() - Date.parse(session.lastUsed) > IDLE_PENDING_MS) clearPendingTools(session);
|
|
4419
|
+
const evictable = Object.keys(state.sessions).filter((key) => key !== active).sort((a, b) => state.sessions[a].lastUsed.localeCompare(state.sessions[b].lastUsed));
|
|
4420
|
+
const excess = Object.keys(state.sessions).length - limit;
|
|
4421
|
+
for (const key of evictable.slice(0, Math.max(0, excess))) delete state.sessions[key];
|
|
4422
|
+
}
|
|
4423
|
+
function serializeState(state, active) {
|
|
4424
|
+
let payload = JSON.stringify(state) + "\n";
|
|
4425
|
+
while (Buffer.byteLength(payload) > STORE_MAX_BYTES) {
|
|
4426
|
+
const before = Object.keys(state.sessions).length;
|
|
4427
|
+
pruneSessions(state, /* @__PURE__ */ new Date(), active, before - 1);
|
|
4428
|
+
if (Object.keys(state.sessions).length === before)
|
|
4429
|
+
throw new Error(
|
|
4430
|
+
"Mason automation state exceeds 10 MiB after evicting every other session; inspect the active session in .mason/reports/automation/<id>/state.json. Original evidence was retained."
|
|
4431
|
+
);
|
|
4432
|
+
payload = JSON.stringify(state) + "\n";
|
|
4433
|
+
}
|
|
4434
|
+
return state;
|
|
4435
|
+
}
|
|
4203
4436
|
function withLock(root, directory3, run, waitMs = 5e3) {
|
|
4204
4437
|
return withStoreLock(root, directory3, run, waitMs, "Automation");
|
|
4205
4438
|
}
|
|
4206
|
-
var hostSchema, events, stateSchema;
|
|
4439
|
+
var hostSchema, events, stateSchema, SESSION_LIMIT, IDLE_PENDING_MS, STATE_READ_MAX_BYTES;
|
|
4207
4440
|
var init_store = __esm({
|
|
4208
4441
|
"src/automation/store.ts"() {
|
|
4209
4442
|
"use strict";
|
|
4210
4443
|
init_store_lock();
|
|
4211
4444
|
init_stop_review();
|
|
4212
4445
|
init_notifications();
|
|
4446
|
+
init_storage();
|
|
4213
4447
|
hostSchema = z9.enum(["claude", "codex"]);
|
|
4214
4448
|
events = [
|
|
4215
4449
|
"session_start",
|
|
@@ -4231,6 +4465,8 @@ var init_store = __esm({
|
|
|
4231
4465
|
host: hostSchema,
|
|
4232
4466
|
lastUsed: z9.string(),
|
|
4233
4467
|
notifiedDiagnostics: z9.array(z9.string()).default([]),
|
|
4468
|
+
/** Digest of the upstream drift notice already shown to this session. */
|
|
4469
|
+
upstreamNotice: z9.string().optional(),
|
|
4234
4470
|
pending: z9.record(z9.string()),
|
|
4235
4471
|
stopReview: stopReviewSchema.optional(),
|
|
4236
4472
|
coverageGaps: z9.array(z9.string()),
|
|
@@ -4245,6 +4481,9 @@ var init_store = __esm({
|
|
|
4245
4481
|
agentNotifications: notificationStateSchema.optional(),
|
|
4246
4482
|
analysis: z9.object({ id: z9.string(), fingerprint: z9.string(), completedAt: z9.number(), path: z9.string() }).optional()
|
|
4247
4483
|
});
|
|
4484
|
+
SESSION_LIMIT = 32;
|
|
4485
|
+
IDLE_PENDING_MS = 24 * 60 * 60 * 1e3;
|
|
4486
|
+
STATE_READ_MAX_BYTES = 64 * 1024 * 1024;
|
|
4248
4487
|
}
|
|
4249
4488
|
});
|
|
4250
4489
|
|
|
@@ -4977,9 +5216,18 @@ var init_repair = __esm({
|
|
|
4977
5216
|
});
|
|
4978
5217
|
|
|
4979
5218
|
// src/automation/evidence.ts
|
|
5219
|
+
var evidence_exports = {};
|
|
5220
|
+
__export(evidence_exports, {
|
|
5221
|
+
checkCache: () => checkCache,
|
|
5222
|
+
git: () => git3,
|
|
5223
|
+
hash: () => hash,
|
|
5224
|
+
readGuidanceInputs: () => readGuidanceInputs,
|
|
5225
|
+
readInputs: () => readInputs,
|
|
5226
|
+
workspace: () => workspace
|
|
5227
|
+
});
|
|
4980
5228
|
import fs14 from "fs/promises";
|
|
4981
5229
|
import path20 from "path";
|
|
4982
|
-
import { createHash as
|
|
5230
|
+
import { createHash as createHash6 } from "crypto";
|
|
4983
5231
|
import { z as z12 } from "zod";
|
|
4984
5232
|
async function git3(root, ...args) {
|
|
4985
5233
|
return profilePhase(
|
|
@@ -5246,8 +5494,8 @@ var init_evidence = __esm({
|
|
|
5246
5494
|
init_dead_command();
|
|
5247
5495
|
init_storage();
|
|
5248
5496
|
init_advisory_review();
|
|
5249
|
-
engineVersion = true ? "0.
|
|
5250
|
-
hash = (value) =>
|
|
5497
|
+
engineVersion = true ? "0.23.0" : "development";
|
|
5498
|
+
hash = (value) => createHash6("sha256").update(JSON.stringify(value)).digest("hex");
|
|
5251
5499
|
content = readAuditInput;
|
|
5252
5500
|
cacheSchema = z12.object({
|
|
5253
5501
|
version: z12.literal(1),
|
|
@@ -5358,8 +5606,8 @@ var init_model = __esm({
|
|
|
5358
5606
|
|
|
5359
5607
|
// src/setup/launcher.ts
|
|
5360
5608
|
import { z as z14 } from "zod";
|
|
5361
|
-
import { execFile as
|
|
5362
|
-
import { promisify as
|
|
5609
|
+
import { execFile as execFile3 } from "child_process";
|
|
5610
|
+
import { promisify as promisify3 } from "util";
|
|
5363
5611
|
import fs16 from "fs/promises";
|
|
5364
5612
|
function globalCommand(args, platform = process.platform) {
|
|
5365
5613
|
return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args] } : { command: "mason", args };
|
|
@@ -5367,7 +5615,7 @@ function globalCommand(args, platform = process.platform) {
|
|
|
5367
5615
|
async function installedCommand() {
|
|
5368
5616
|
const invocation = globalCommand(["internal-integration-version"]);
|
|
5369
5617
|
try {
|
|
5370
|
-
const { stdout } = await
|
|
5618
|
+
const { stdout } = await promisify3(execFile3)(invocation.command, invocation.args, {
|
|
5371
5619
|
timeout: 5e3,
|
|
5372
5620
|
maxBuffer: 8192,
|
|
5373
5621
|
windowsHide: true
|
|
@@ -5396,14 +5644,66 @@ var init_launcher = __esm({
|
|
|
5396
5644
|
"use strict";
|
|
5397
5645
|
init_evidence();
|
|
5398
5646
|
init_model();
|
|
5399
|
-
executingVersion = () => true ? "0.
|
|
5647
|
+
executingVersion = () => true ? "0.23.0" : "development";
|
|
5400
5648
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
5401
5649
|
hookCommand = (host) => `mason --setup-host ${host}`;
|
|
5402
5650
|
}
|
|
5403
5651
|
});
|
|
5404
5652
|
|
|
5653
|
+
// src/decisions/attribution.ts
|
|
5654
|
+
async function branchAttribution(root) {
|
|
5655
|
+
const git4 = async (...args) => (await execGit(args, { cwd: root, maxBuffer: 16 * 1024 * 1024 })).stdout.trim();
|
|
5656
|
+
let target = null;
|
|
5657
|
+
for (const ref of TARGETS) {
|
|
5658
|
+
try {
|
|
5659
|
+
await git4("rev-parse", "--verify", "--quiet", `${ref}^{commit}`);
|
|
5660
|
+
target = ref === "refs/remotes/origin/HEAD" ? await git4("symbolic-ref", "--short", ref) : ref.replace(/^refs\/(remotes|heads)\//, "");
|
|
5661
|
+
break;
|
|
5662
|
+
} catch {
|
|
5663
|
+
}
|
|
5664
|
+
}
|
|
5665
|
+
if (!target) return null;
|
|
5666
|
+
const current = await git4("symbolic-ref", "--quiet", "--short", "HEAD").catch(() => "");
|
|
5667
|
+
if (current && (target === current || target.endsWith("/" + current))) return null;
|
|
5668
|
+
let mergeBase, head;
|
|
5669
|
+
try {
|
|
5670
|
+
[mergeBase, head] = await Promise.all([
|
|
5671
|
+
git4("merge-base", "HEAD", target),
|
|
5672
|
+
git4("rev-parse", "HEAD")
|
|
5673
|
+
]);
|
|
5674
|
+
} catch {
|
|
5675
|
+
return null;
|
|
5676
|
+
}
|
|
5677
|
+
if (!mergeBase || mergeBase === head) return null;
|
|
5678
|
+
const [committed, workingTree] = await Promise.all([
|
|
5679
|
+
git4("diff", "--name-only", "--no-renames", "-z", mergeBase, "HEAD"),
|
|
5680
|
+
getWorkingTree(root)
|
|
5681
|
+
]);
|
|
5682
|
+
if (!workingTree.available) return null;
|
|
5683
|
+
return {
|
|
5684
|
+
target,
|
|
5685
|
+
mergeBase,
|
|
5686
|
+
branchFiles: /* @__PURE__ */ new Set([...committed.split("\0").filter(Boolean), ...workingTree.changedFiles])
|
|
5687
|
+
};
|
|
5688
|
+
}
|
|
5689
|
+
var TARGETS;
|
|
5690
|
+
var init_attribution = __esm({
|
|
5691
|
+
"src/decisions/attribution.ts"() {
|
|
5692
|
+
"use strict";
|
|
5693
|
+
init_drift();
|
|
5694
|
+
init_git_read();
|
|
5695
|
+
TARGETS = [
|
|
5696
|
+
"refs/remotes/origin/HEAD",
|
|
5697
|
+
"refs/remotes/origin/main",
|
|
5698
|
+
"refs/remotes/origin/master",
|
|
5699
|
+
"refs/heads/main",
|
|
5700
|
+
"refs/heads/master"
|
|
5701
|
+
];
|
|
5702
|
+
}
|
|
5703
|
+
});
|
|
5704
|
+
|
|
5405
5705
|
// src/snapshot/review.ts
|
|
5406
|
-
import { createHash as
|
|
5706
|
+
import { createHash as createHash8 } from "crypto";
|
|
5407
5707
|
function snapshotEntryContent(entry) {
|
|
5408
5708
|
return {
|
|
5409
5709
|
description: entry.description,
|
|
@@ -5414,7 +5714,7 @@ function snapshotEntryContent(entry) {
|
|
|
5414
5714
|
};
|
|
5415
5715
|
}
|
|
5416
5716
|
function reviewToken(kind, name, entry, evidence) {
|
|
5417
|
-
return
|
|
5717
|
+
return createHash8("sha256").update(
|
|
5418
5718
|
JSON.stringify({
|
|
5419
5719
|
version: 1,
|
|
5420
5720
|
kind,
|
|
@@ -5467,7 +5767,7 @@ var init_review = __esm({
|
|
|
5467
5767
|
init_snapshot();
|
|
5468
5768
|
VERIFY_MAX_FILES_PER_ENTRY = 8;
|
|
5469
5769
|
VERIFY_SKELETON_CHARS = 500;
|
|
5470
|
-
sourceHash = (content2) =>
|
|
5770
|
+
sourceHash = (content2) => createHash8("sha256").update(content2).digest("hex");
|
|
5471
5771
|
}
|
|
5472
5772
|
});
|
|
5473
5773
|
|
|
@@ -5929,8 +6229,9 @@ async function executionStatus(root, directory3) {
|
|
|
5929
6229
|
const outstanding = [...log.attempts].reverse().find(
|
|
5930
6230
|
(a) => ["failed", "unknown"].includes(a.status) && (!completed || (a.finishedAt ?? a.startedAt) >= completed.startedAt)
|
|
5931
6231
|
);
|
|
5932
|
-
const
|
|
5933
|
-
|
|
6232
|
+
const inFlight = log.attempts.some((a) => a.status === "running");
|
|
6233
|
+
const status = outstanding?.status ?? (completed ? "completed" : inFlight ? "running" : log.attempts.at(-1)?.status ?? "not-observed");
|
|
6234
|
+
return { status, inFlight, attempts: log.attempts, discardedAttempts: log.discardedAttempts };
|
|
5934
6235
|
}
|
|
5935
6236
|
var failureSchema, attemptSchema, executionSchema, notificationsSchema, leasePath;
|
|
5936
6237
|
var init_execution = __esm({
|
|
@@ -6103,7 +6404,7 @@ async function observeReadOnlyTool(dir, event) {
|
|
|
6103
6404
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
6104
6405
|
await withLock(ws.root, ws.directory, async () => {
|
|
6105
6406
|
const statePath2 = ws.directory + "/state.json";
|
|
6106
|
-
const raw = await readStoreJson(ws.root, statePath2);
|
|
6407
|
+
const raw = await readStoreJson(ws.root, statePath2, STATE_READ_MAX_BYTES);
|
|
6107
6408
|
if (raw === null) return;
|
|
6108
6409
|
const state = parseState(raw);
|
|
6109
6410
|
if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) {
|
|
@@ -6116,7 +6417,14 @@ async function observeReadOnlyTool(dir, event) {
|
|
|
6116
6417
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6117
6418
|
session.lastUsed = now;
|
|
6118
6419
|
session.events[event.event] = { at: now, count: (session.events[event.event]?.count ?? 0) + 1 };
|
|
6119
|
-
await writeStoreJson(
|
|
6420
|
+
await writeStoreJson(
|
|
6421
|
+
ws.root,
|
|
6422
|
+
statePath2,
|
|
6423
|
+
serializeState(state, hash([event.host, event.sessionId])),
|
|
6424
|
+
{
|
|
6425
|
+
compact: true
|
|
6426
|
+
}
|
|
6427
|
+
);
|
|
6120
6428
|
});
|
|
6121
6429
|
return { root: ws.root, directory: ws.directory };
|
|
6122
6430
|
}
|
|
@@ -6136,7 +6444,7 @@ function summarize(report) {
|
|
|
6136
6444
|
].join("\n");
|
|
6137
6445
|
}
|
|
6138
6446
|
async function loadState(ws) {
|
|
6139
|
-
const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
|
|
6447
|
+
const raw = await readStoreJson(ws.root, ws.directory + "/state.json", STATE_READ_MAX_BYTES);
|
|
6140
6448
|
const state = raw === null ? {
|
|
6141
6449
|
version: 1,
|
|
6142
6450
|
root: ws.root,
|
|
@@ -6368,13 +6676,47 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
6368
6676
|
completedAt: analysis.completedAt,
|
|
6369
6677
|
path: analysisPath
|
|
6370
6678
|
};
|
|
6371
|
-
await writeStoreJson(ws.root, ws.directory + "/state.json", state)
|
|
6679
|
+
await writeStoreJson(ws.root, ws.directory + "/state.json", serializeState(state, null), {
|
|
6680
|
+
compact: true
|
|
6681
|
+
});
|
|
6372
6682
|
});
|
|
6373
6683
|
return { analysis, shared: false };
|
|
6374
6684
|
},
|
|
6375
6685
|
Math.max(0, 25e3 - (Date.now() - startedAt))
|
|
6376
6686
|
);
|
|
6377
6687
|
}
|
|
6688
|
+
function upstreamDriftNotice(drift) {
|
|
6689
|
+
const listed = drift.decisions.slice(0, 5).map((decision) => `${JSON.stringify(cleanText(decision.title))} (${cleanText(decision.id)})`);
|
|
6690
|
+
const more = drift.decisions.length - listed.length;
|
|
6691
|
+
return [
|
|
6692
|
+
`Mason: ${drift.decisions.length} decision(s) have anchors changed only by ${cleanText(drift.target)} since this branch's merge-base, not by this branch: ${listed.join(", ")}${more ? ` and ${more} more` : ""}.`,
|
|
6693
|
+
`They are not this checkout's work and do not block. Inspect them on ${cleanText(drift.target)}, where they are reported as usual; inspections recorded here would ride along with this branch.`
|
|
6694
|
+
].join("\n");
|
|
6695
|
+
}
|
|
6696
|
+
async function driftExcerpts(root, report, ids) {
|
|
6697
|
+
const unique = [...new Set(ids)].slice(0, 4);
|
|
6698
|
+
if (!unique.length) return null;
|
|
6699
|
+
const records = (await loadDecisionStore(root)).records;
|
|
6700
|
+
const sections = [];
|
|
6701
|
+
for (const id of unique) {
|
|
6702
|
+
const record = records.find((candidate) => candidate.id === id);
|
|
6703
|
+
const changed = /* @__PURE__ */ new Set();
|
|
6704
|
+
let title = record?.title ?? id;
|
|
6705
|
+
for (const finding of report.findings) {
|
|
6706
|
+
const evidence = finding.current?.evidence;
|
|
6707
|
+
if (finding.status === "resolved" || evidence?.kind !== "decision-anchor") continue;
|
|
6708
|
+
if (evidence.decisionId !== id) continue;
|
|
6709
|
+
title = evidence.title;
|
|
6710
|
+
for (const file of evidence.changedFiles) changed.add(file);
|
|
6711
|
+
}
|
|
6712
|
+
if (!record || !changed.size) continue;
|
|
6713
|
+
try {
|
|
6714
|
+
sections.push(formatDriftExcerpt(title, id, await driftExcerpt(root, record, [...changed])));
|
|
6715
|
+
} catch {
|
|
6716
|
+
}
|
|
6717
|
+
}
|
|
6718
|
+
return sections.join("\n") || null;
|
|
6719
|
+
}
|
|
6378
6720
|
async function guidanceAnalysis(ws, inputs) {
|
|
6379
6721
|
const checked = await checkDecisionAnchors({
|
|
6380
6722
|
root: ws.root,
|
|
@@ -6383,7 +6725,27 @@ async function guidanceAnalysis(ws, inputs) {
|
|
|
6383
6725
|
changesSinceDoc: /* @__PURE__ */ new Map(),
|
|
6384
6726
|
decisionsPresent: true
|
|
6385
6727
|
});
|
|
6386
|
-
const
|
|
6728
|
+
const attribution = checked.advisories.length ? await branchAttribution(ws.root).catch(() => null) : null;
|
|
6729
|
+
const own = new Set(
|
|
6730
|
+
checked.advisories.flatMap(
|
|
6731
|
+
(advisory) => advisory.evidence.kind === "decision-anchor" && advisory.evidence.changedFiles.some((file) => attribution?.branchFiles.has(file)) ? [advisory.evidence.decisionId] : []
|
|
6732
|
+
)
|
|
6733
|
+
);
|
|
6734
|
+
const upstream = /* @__PURE__ */ new Map();
|
|
6735
|
+
const advisories = checked.advisories.filter((advisory) => {
|
|
6736
|
+
const evidence = advisory.evidence;
|
|
6737
|
+
if (!attribution || evidence.kind !== "decision-anchor" || own.has(evidence.decisionId))
|
|
6738
|
+
return true;
|
|
6739
|
+
const entry = upstream.get(evidence.decisionId) ?? {
|
|
6740
|
+
id: evidence.decisionId,
|
|
6741
|
+
title: evidence.title,
|
|
6742
|
+
changedFiles: []
|
|
6743
|
+
};
|
|
6744
|
+
entry.changedFiles = [.../* @__PURE__ */ new Set([...entry.changedFiles, ...evidence.changedFiles])].sort();
|
|
6745
|
+
upstream.set(evidence.decisionId, entry);
|
|
6746
|
+
return false;
|
|
6747
|
+
});
|
|
6748
|
+
const findings = advisories.map((current) => ({
|
|
6387
6749
|
id: findingId(current),
|
|
6388
6750
|
original: current,
|
|
6389
6751
|
current,
|
|
@@ -6395,13 +6757,16 @@ async function guidanceAnalysis(ws, inputs) {
|
|
|
6395
6757
|
const prior = z19.object({ findings: z19.array(repairFindingSchema) }).parse(await readStoreJson(ws.root, before.guidance.path));
|
|
6396
6758
|
const currentIds = new Set(findings.map((finding) => finding.id));
|
|
6397
6759
|
const records = (await loadDecisionStore(ws.root)).records;
|
|
6760
|
+
const incomplete = new Set(checked.incompleteDecisions);
|
|
6761
|
+
const sharedSkip = checked.skipped.length > checked.incompleteDecisions.length;
|
|
6398
6762
|
for (const previous of prior.findings) {
|
|
6399
6763
|
if (currentIds.has(previous.id) || previous.original.evidence.kind !== "decision-anchor")
|
|
6400
6764
|
continue;
|
|
6401
6765
|
const evidence = previous.original.evidence;
|
|
6766
|
+
if (upstream.has(evidence.decisionId)) continue;
|
|
6402
6767
|
const record = records.find((record2) => record2.id === evidence.decisionId);
|
|
6403
6768
|
const capture = record && inputs.decisionEvidence[record.id]?.capture;
|
|
6404
|
-
const resolved = record?.status === "retired" || !!record && !!capture?.complete && capture.anchors.length > 0 && !
|
|
6769
|
+
const resolved = record?.status === "retired" || !!record && !!capture?.complete && capture.anchors.length > 0 && !sharedSkip && !incomplete.has(record.id);
|
|
6405
6770
|
findings.push({
|
|
6406
6771
|
id: previous.id,
|
|
6407
6772
|
original: previous.original,
|
|
@@ -6426,6 +6791,13 @@ async function guidanceAnalysis(ws, inputs) {
|
|
|
6426
6791
|
baselinePaths: [],
|
|
6427
6792
|
reportPath: "",
|
|
6428
6793
|
findings,
|
|
6794
|
+
...attribution && upstream.size ? {
|
|
6795
|
+
upstreamDrift: {
|
|
6796
|
+
target: attribution.target,
|
|
6797
|
+
mergeBase: attribution.mergeBase,
|
|
6798
|
+
decisions: [...upstream.values()]
|
|
6799
|
+
}
|
|
6800
|
+
} : {},
|
|
6429
6801
|
diagnostics: [
|
|
6430
6802
|
...checked.skipped.map((item) => item.reason),
|
|
6431
6803
|
...findings.filter((finding) => finding.status === "unverified").map((finding) => `Decision ${finding.id}: ${finding.reason}`)
|
|
@@ -6503,17 +6875,13 @@ async function runAutomation(dir, event, guidance) {
|
|
|
6503
6875
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6504
6876
|
const key = event.host && event.sessionId ? hash([event.host, event.sessionId]) : null;
|
|
6505
6877
|
const newSession = key !== null && !state.sessions[key];
|
|
6878
|
+
pruneSessions(
|
|
6879
|
+
state,
|
|
6880
|
+
new Date(now),
|
|
6881
|
+
newSession ? null : key,
|
|
6882
|
+
newSession ? SESSION_LIMIT - 1 : SESSION_LIMIT
|
|
6883
|
+
);
|
|
6506
6884
|
if (key && !state.sessions[key]) {
|
|
6507
|
-
const keys = Object.keys(state.sessions).sort(
|
|
6508
|
-
(a, b) => state.sessions[a].lastUsed.localeCompare(state.sessions[b].lastUsed)
|
|
6509
|
-
);
|
|
6510
|
-
const removable = keys.filter((k) => !Object.keys(state.sessions[k].pending).length);
|
|
6511
|
-
for (const expired of removable.slice(0, Math.max(0, keys.length - 31)))
|
|
6512
|
-
delete state.sessions[expired];
|
|
6513
|
-
if (Object.keys(state.sessions).length >= 128)
|
|
6514
|
-
throw new Error(
|
|
6515
|
-
"Too many sessions with unfinished tool calls; original evidence was retained."
|
|
6516
|
-
);
|
|
6517
6885
|
state.sessions[key] = {
|
|
6518
6886
|
host: event.host,
|
|
6519
6887
|
notifiedDiagnostics: [],
|
|
@@ -6533,8 +6901,7 @@ async function runAutomation(dir, event, guidance) {
|
|
|
6533
6901
|
count: (session.events[event.event]?.count ?? 0) + 1
|
|
6534
6902
|
};
|
|
6535
6903
|
if (event.event === "before_tool" && event.mutating && event.toolId) {
|
|
6536
|
-
|
|
6537
|
-
throw new Error("Too many unfinished tool calls to track pre-edit evidence.");
|
|
6904
|
+
boundPending(session.pending);
|
|
6538
6905
|
session.pending[event.toolId] = inputs.fingerprint;
|
|
6539
6906
|
}
|
|
6540
6907
|
if (event.event === "after_tool" && event.mutating) {
|
|
@@ -6560,11 +6927,23 @@ async function runAutomation(dir, event, guidance) {
|
|
|
6560
6927
|
...diagnostics
|
|
6561
6928
|
])
|
|
6562
6929
|
];
|
|
6563
|
-
const stopReview = session && event.host === "claude" && event.event === "task_end" && config.stopBlock === "drift" ? decisionStopReview(session, inputs, report, !!event.stopHookActive) : { reason: null, block: false };
|
|
6930
|
+
const stopReview = session && event.host === "claude" && event.event === "task_end" && config.stopBlock === "drift" ? decisionStopReview(session, inputs, report, !!event.stopHookActive) : { reason: null, block: false, decisions: [] };
|
|
6931
|
+
const excerpts = session ? await driftExcerpts(ws.root, report, [
|
|
6932
|
+
...stopReview.decisions,
|
|
6933
|
+
...[...selected, ...feedback].flatMap((finding) => {
|
|
6934
|
+
const evidence = (finding.current ?? finding.original).evidence;
|
|
6935
|
+
return evidence.kind === "decision-anchor" ? [evidence.decisionId] : [];
|
|
6936
|
+
})
|
|
6937
|
+
]) : null;
|
|
6938
|
+
const upstream = report.upstreamDrift ? upstreamDriftNotice(report.upstreamDrift) : null;
|
|
6939
|
+
const upstreamNotice = session && upstream && session.upstreamNotice !== hash(upstream) ? upstream : null;
|
|
6940
|
+
if (session) session.upstreamNotice = upstream ? hash(upstream) : void 0;
|
|
6564
6941
|
const message2 = !session ? summarize(report) : [
|
|
6565
6942
|
stopReview.reason,
|
|
6566
6943
|
completionSummary(selected, report.reportPath),
|
|
6567
6944
|
completionSummary(feedback, report.reportPath),
|
|
6945
|
+
excerpts,
|
|
6946
|
+
upstreamNotice,
|
|
6568
6947
|
...diagnostics.length ? [
|
|
6569
6948
|
`Mason: verification limited. ${diagnostics.slice(0, 2).map(cleanText).join(" ")} Evidence: ${report.reportPath}.`
|
|
6570
6949
|
] : []
|
|
@@ -6582,7 +6961,10 @@ async function runAutomation(dir, event, guidance) {
|
|
|
6582
6961
|
state.latest = report.reportPath;
|
|
6583
6962
|
}
|
|
6584
6963
|
if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
|
|
6585
|
-
|
|
6964
|
+
if (session && event.event === "task_end") clearPendingTools(session);
|
|
6965
|
+
await writeStoreJson(ws.root, ws.directory + "/state.json", serializeState(state, key), {
|
|
6966
|
+
compact: true
|
|
6967
|
+
});
|
|
6586
6968
|
return {
|
|
6587
6969
|
report,
|
|
6588
6970
|
message: message2,
|
|
@@ -6620,7 +7002,7 @@ async function automationStatus(dir) {
|
|
|
6620
7002
|
const knowledge = await pendingKnowledge2(ws.root);
|
|
6621
7003
|
const execution = await executionStatus(ws.root, ws.directory);
|
|
6622
7004
|
const notifications = await failureNotifications(ws.root, ws.directory);
|
|
6623
|
-
const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
|
|
7005
|
+
const raw = await readStoreJson(ws.root, ws.directory + "/state.json", STATE_READ_MAX_BYTES);
|
|
6624
7006
|
if (raw === null)
|
|
6625
7007
|
return {
|
|
6626
7008
|
version: 1,
|
|
@@ -6648,7 +7030,10 @@ async function automationStatus(dir) {
|
|
|
6648
7030
|
host.sessions++;
|
|
6649
7031
|
host.observedEvents = [.../* @__PURE__ */ new Set([...host.observedEvents, ...Object.keys(session.events)])];
|
|
6650
7032
|
}
|
|
6651
|
-
const unfinished = ["failed", "unknown"
|
|
7033
|
+
const unfinished = ["failed", "unknown"].includes(execution.status);
|
|
7034
|
+
const pendingSessions = Object.values(state.sessions).filter(
|
|
7035
|
+
(session) => Object.keys(session.pending).length
|
|
7036
|
+
);
|
|
6652
7037
|
return {
|
|
6653
7038
|
version: 1,
|
|
6654
7039
|
stopBlock: config.stopBlock,
|
|
@@ -6661,6 +7046,12 @@ async function automationStatus(dir) {
|
|
|
6661
7046
|
auditReportPath: state.latest,
|
|
6662
7047
|
verificationStatus: unfinished ? "unavailable" : latest?.status ?? "unavailable",
|
|
6663
7048
|
hosts,
|
|
7049
|
+
sessions: Object.keys(state.sessions).length,
|
|
7050
|
+
unfinishedToolCalls: pendingSessions.reduce(
|
|
7051
|
+
(total, session) => total + Object.keys(session.pending).length,
|
|
7052
|
+
0
|
|
7053
|
+
),
|
|
7054
|
+
oldestUnfinishedSessionUsedAt: pendingSessions.map((session) => session.lastUsed).sort()[0] ?? null,
|
|
6664
7055
|
execution,
|
|
6665
7056
|
notifications,
|
|
6666
7057
|
knowledge,
|
|
@@ -6673,6 +7064,8 @@ var init_runtime = __esm({
|
|
|
6673
7064
|
"src/automation/runtime.ts"() {
|
|
6674
7065
|
"use strict";
|
|
6675
7066
|
init_decisions();
|
|
7067
|
+
init_excerpts();
|
|
7068
|
+
init_attribution();
|
|
6676
7069
|
init_decision_anchor();
|
|
6677
7070
|
init_config();
|
|
6678
7071
|
init_stop_review();
|
|
@@ -6685,6 +7078,7 @@ var init_runtime = __esm({
|
|
|
6685
7078
|
init_types();
|
|
6686
7079
|
init_storage();
|
|
6687
7080
|
init_evidence();
|
|
7081
|
+
init_pending_tools();
|
|
6688
7082
|
init_store();
|
|
6689
7083
|
SCOPE = "Documentation audit evidence only. Hook receipts show observed events, not complete interception. Resolved findings no longer fail their checks or have a recorded assessment covering current evidence. Repair and review only within the user's task authorization.";
|
|
6690
7084
|
priority = { resolved: 0, "review-required": 1, unresolved: 2, unverified: 3 };
|
|
@@ -6750,7 +7144,7 @@ import { z as z20 } from "zod";
|
|
|
6750
7144
|
import fs21 from "fs/promises";
|
|
6751
7145
|
import path24 from "path";
|
|
6752
7146
|
import os3 from "os";
|
|
6753
|
-
import { createHash as
|
|
7147
|
+
import { createHash as createHash9 } from "crypto";
|
|
6754
7148
|
var hookStdinSchema;
|
|
6755
7149
|
var init_hook = __esm({
|
|
6756
7150
|
"src/hook/hook.ts"() {
|
|
@@ -6773,6 +7167,9 @@ var init_hook = __esm({
|
|
|
6773
7167
|
|
|
6774
7168
|
// src/automation/adapters.ts
|
|
6775
7169
|
import { z as z21 } from "zod";
|
|
7170
|
+
function hookEvents(host) {
|
|
7171
|
+
return [...HOOK_EVENTS, ...OPTIONAL_HOOK_EVENTS[host]];
|
|
7172
|
+
}
|
|
6776
7173
|
function managedHookCommand(host, platform = process.platform, legacy = false) {
|
|
6777
7174
|
const command = `mason --setup-host ${host}${legacy ? " auto" : ""} hook --host ${host}`;
|
|
6778
7175
|
return platform === "win32" ? `cmd.exe /d /s /c "where mason >nul 2>nul & if errorlevel 1 (exit /b 0) else (${command})"` : `if command -v mason >/dev/null 2>&1; then ${command}; fi`;
|
|
@@ -6805,11 +7202,11 @@ function hookConfig(host, command = "npx --no-install --package mason-context ma
|
|
|
6805
7202
|
};
|
|
6806
7203
|
return {
|
|
6807
7204
|
hooks: Object.fromEntries(
|
|
6808
|
-
|
|
7205
|
+
hookEvents(host).map((name) => [
|
|
6809
7206
|
name,
|
|
6810
7207
|
[
|
|
6811
7208
|
{
|
|
6812
|
-
...["PreToolUse", "PostToolUse"].includes(name) ? { matcher: ".*" } : {},
|
|
7209
|
+
...["PreToolUse", "PostToolUse", "PostToolUseFailure"].includes(name) ? { matcher: ".*" } : {},
|
|
6813
7210
|
hooks: [{ ...handler }]
|
|
6814
7211
|
}
|
|
6815
7212
|
]
|
|
@@ -6817,7 +7214,7 @@ function hookConfig(host, command = "npx --no-install --package mason-context ma
|
|
|
6817
7214
|
)
|
|
6818
7215
|
};
|
|
6819
7216
|
}
|
|
6820
|
-
var inputSchema, HOOK_EVENTS;
|
|
7217
|
+
var inputSchema, HOOK_EVENTS, OPTIONAL_HOOK_EVENTS;
|
|
6821
7218
|
var init_adapters = __esm({
|
|
6822
7219
|
"src/automation/adapters.ts"() {
|
|
6823
7220
|
"use strict";
|
|
@@ -6835,6 +7232,7 @@ var init_adapters = __esm({
|
|
|
6835
7232
|
"UserPromptSubmit",
|
|
6836
7233
|
"PreToolUse",
|
|
6837
7234
|
"PostToolUse",
|
|
7235
|
+
"PostToolUseFailure",
|
|
6838
7236
|
"Stop"
|
|
6839
7237
|
]),
|
|
6840
7238
|
tool_name: z21.string().optional(),
|
|
@@ -6851,6 +7249,10 @@ var init_adapters = __esm({
|
|
|
6851
7249
|
"PostToolUse",
|
|
6852
7250
|
"Stop"
|
|
6853
7251
|
];
|
|
7252
|
+
OPTIONAL_HOOK_EVENTS = {
|
|
7253
|
+
claude: ["PostToolUseFailure"],
|
|
7254
|
+
codex: []
|
|
7255
|
+
};
|
|
6854
7256
|
}
|
|
6855
7257
|
});
|
|
6856
7258
|
|
|
@@ -6983,7 +7385,7 @@ async function planAutomationInstall(root, host, command) {
|
|
|
6983
7385
|
const pending = ownershipText === null ? [] : ownershipSchema.parse(JSON.parse(ownershipText)).files[file]?.hookCommands ?? [];
|
|
6984
7386
|
const owned = /* @__PURE__ */ new Set([previous, ...pending, newCommand, ...knownManagedHookCommands(host)]);
|
|
6985
7387
|
const hooks = existing.hooks ?? {};
|
|
6986
|
-
for (const event of
|
|
7388
|
+
for (const event of hookEvents(host)) {
|
|
6987
7389
|
hooks[event] = (hooks[event] ?? []).map((group) => ({
|
|
6988
7390
|
...group,
|
|
6989
7391
|
hooks: group.hooks.filter(
|
|
@@ -7017,7 +7419,7 @@ async function installLocked(root, host, command) {
|
|
|
7017
7419
|
configPath: file,
|
|
7018
7420
|
status: "configured",
|
|
7019
7421
|
command: newCommand,
|
|
7020
|
-
events:
|
|
7422
|
+
events: hookEvents(host),
|
|
7021
7423
|
next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason status reports observed events separately from configuration." : "Start a new Claude Code session. mason status reports observed events separately from configuration.",
|
|
7022
7424
|
note: "Install mason-context in the project before using the default command. Ignore .mason/reports/ to keep local evidence out of commits. Hooks preserve evidence and suggest scoped repairs; they do not approve edits or decisions."
|
|
7023
7425
|
};
|
|
@@ -7032,7 +7434,7 @@ async function installedAutomation(dir) {
|
|
|
7032
7434
|
const current = automationConfigSchema.parse(
|
|
7033
7435
|
await readStoreJson(ws.root, configPath3(host)) ?? {}
|
|
7034
7436
|
);
|
|
7035
|
-
const configuredEvents =
|
|
7437
|
+
const configuredEvents = hookEvents(host).filter(
|
|
7036
7438
|
(event) => current.hooks?.[event]?.some(
|
|
7037
7439
|
(group) => group.hooks.some(
|
|
7038
7440
|
(handler) => handler.type === "command" && handler.command === expected.command
|
|
@@ -7042,7 +7444,7 @@ async function installedAutomation(dir) {
|
|
|
7042
7444
|
result[host] = {
|
|
7043
7445
|
configPath: configPath3(host),
|
|
7044
7446
|
configuredEvents,
|
|
7045
|
-
status: current.disableAllHooks === true ? "disabled" :
|
|
7447
|
+
status: current.disableAllHooks === true ? "disabled" : HOOK_EVENTS.every((event) => configuredEvents.includes(event)) ? "configured" : "incomplete",
|
|
7046
7448
|
runtime: "Host version, trust, policy, and tool coverage still determine execution; inspect observed events."
|
|
7047
7449
|
};
|
|
7048
7450
|
}
|
|
@@ -7071,16 +7473,33 @@ var init_install = __esm({
|
|
|
7071
7473
|
}
|
|
7072
7474
|
});
|
|
7073
7475
|
|
|
7476
|
+
// src/decisions/merge.ts
|
|
7477
|
+
import fs22 from "fs/promises";
|
|
7478
|
+
var MERGE_DRIVER_NAME, MERGE_ATTRIBUTE, MERGE_DRIVER_COMMAND;
|
|
7479
|
+
var init_merge = __esm({
|
|
7480
|
+
"src/decisions/merge.ts"() {
|
|
7481
|
+
"use strict";
|
|
7482
|
+
init_files();
|
|
7483
|
+
init_provenance();
|
|
7484
|
+
MERGE_DRIVER_NAME = "mason-decision";
|
|
7485
|
+
MERGE_ATTRIBUTE = `.mason/decisions/*.json merge=${MERGE_DRIVER_NAME}`;
|
|
7486
|
+
MERGE_DRIVER_COMMAND = "mason merge-decision %O %A %B %L %P || git merge-file --marker-size=%L -L ours -L base -L theirs %A %O %B";
|
|
7487
|
+
}
|
|
7488
|
+
});
|
|
7489
|
+
|
|
7074
7490
|
// src/setup/config.ts
|
|
7075
7491
|
var config_exports = {};
|
|
7076
7492
|
__export(config_exports, {
|
|
7077
7493
|
ancillaryEdits: () => ancillaryEdits,
|
|
7494
|
+
configureDecisionMerge: () => configureDecisionMerge,
|
|
7495
|
+
decisionMergeStatus: () => decisionMergeStatus,
|
|
7078
7496
|
hookEdits: () => hookEdits,
|
|
7079
7497
|
inspectHostConfig: () => inspectHostConfig,
|
|
7080
7498
|
inspectIntegration: () => inspectIntegration,
|
|
7081
7499
|
instructionEdits: () => instructionEdits,
|
|
7082
7500
|
mcpEdit: () => mcpEdit,
|
|
7083
|
-
mcpPath: () => mcpPath
|
|
7501
|
+
mcpPath: () => mcpPath,
|
|
7502
|
+
mergeDriverSummary: () => mergeDriverSummary
|
|
7084
7503
|
});
|
|
7085
7504
|
import { isDeepStrictEqual } from "util";
|
|
7086
7505
|
import { parse as parse2, stringify } from "smol-toml";
|
|
@@ -7202,6 +7621,7 @@ async function hookEdits(root, host) {
|
|
|
7202
7621
|
}
|
|
7203
7622
|
async function ancillaryEdits(root) {
|
|
7204
7623
|
const ignore = await readText(root, ".gitignore");
|
|
7624
|
+
const attributes = await readText(root, ".gitattributes");
|
|
7205
7625
|
const rules = [
|
|
7206
7626
|
"!/.mason/",
|
|
7207
7627
|
"/.mason/*",
|
|
@@ -7219,9 +7639,45 @@ async function ancillaryEdits(root) {
|
|
|
7219
7639
|
path: ".gitignore",
|
|
7220
7640
|
before: ignore,
|
|
7221
7641
|
after: managedBlock(ignore ?? "", "# mason:ignore:start", "# mason:ignore:end", rules)
|
|
7642
|
+
},
|
|
7643
|
+
// Unconfigured checkouts keep Git's text merge; mason setup enables the driver locally.
|
|
7644
|
+
{
|
|
7645
|
+
path: ".gitattributes",
|
|
7646
|
+
before: attributes,
|
|
7647
|
+
after: managedBlock(
|
|
7648
|
+
attributes ?? "",
|
|
7649
|
+
"# mason:merge:start",
|
|
7650
|
+
"# mason:merge:end",
|
|
7651
|
+
MERGE_ATTRIBUTE
|
|
7652
|
+
)
|
|
7222
7653
|
}
|
|
7223
7654
|
];
|
|
7224
7655
|
}
|
|
7656
|
+
async function decisionMergeStatus(root) {
|
|
7657
|
+
const { git: git4 } = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
|
|
7658
|
+
const attribute = (await git4(root, "check-attr", "merge", "--", ".mason/decisions/record.json")).trim();
|
|
7659
|
+
const driver = await git4(root, "config", "--get", `merge.${MERGE_DRIVER_NAME}.driver`).then(
|
|
7660
|
+
(value) => value.trim(),
|
|
7661
|
+
() => null
|
|
7662
|
+
);
|
|
7663
|
+
return {
|
|
7664
|
+
attribute: attribute.endsWith(`: merge: ${MERGE_DRIVER_NAME}`),
|
|
7665
|
+
driver: driver === null ? "missing" : driver === MERGE_DRIVER_COMMAND ? "configured" : "custom"
|
|
7666
|
+
};
|
|
7667
|
+
}
|
|
7668
|
+
function mergeDriverSummary(state) {
|
|
7669
|
+
const driver = state.driver === "configured" ? "Decision record merge driver: configured." : state.driver === "custom" ? "Decision record merge driver: customized in Git config; Mason left it unchanged." : "Decision record merge driver: not configured; Git text-merges decision records. Run mason setup to enable it.";
|
|
7670
|
+
return state.attribute ? driver : driver + " The .gitattributes entry is missing; a maintainer can add it with mason init.";
|
|
7671
|
+
}
|
|
7672
|
+
async function configureDecisionMerge(root) {
|
|
7673
|
+
const status = await decisionMergeStatus(root);
|
|
7674
|
+
if (status.driver !== "missing") return status;
|
|
7675
|
+
const { git: git4 } = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
|
|
7676
|
+
const section = `merge.${MERGE_DRIVER_NAME}`;
|
|
7677
|
+
await git4(root, "config", "--local", `${section}.name`, "Mason decision record merge");
|
|
7678
|
+
await git4(root, "config", "--local", `${section}.driver`, MERGE_DRIVER_COMMAND);
|
|
7679
|
+
return { ...status, driver: "configured" };
|
|
7680
|
+
}
|
|
7225
7681
|
async function inspectHostConfig(root, host, plannedMcp) {
|
|
7226
7682
|
const text2 = plannedMcp ?? await readText(root, mcpPath(host));
|
|
7227
7683
|
const config = host === "codex" ? parse2(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
|
|
@@ -7272,7 +7728,10 @@ async function inspectIntegration(root, host) {
|
|
|
7272
7728
|
])
|
|
7273
7729
|
);
|
|
7274
7730
|
const mcpCompatible = isDeepStrictEqual({ command: config.mcp?.command, args: config.mcp?.args }, mcpCommand(host)) && (config.mcp?.type === void 0 || config.mcp.type === "stdio");
|
|
7275
|
-
const
|
|
7731
|
+
const required = new Set(HOOK_EVENTS);
|
|
7732
|
+
const hooksCompatible = Object.entries(expectedHooks).every(
|
|
7733
|
+
([event, groups]) => isDeepStrictEqual(normalizedHooks[event], groups) || !required.has(event) && !normalizedHooks[event]?.length
|
|
7734
|
+
);
|
|
7276
7735
|
return {
|
|
7277
7736
|
...config,
|
|
7278
7737
|
instructions,
|
|
@@ -7294,6 +7753,7 @@ var init_config2 = __esm({
|
|
|
7294
7753
|
init_install();
|
|
7295
7754
|
init_files2();
|
|
7296
7755
|
init_launcher();
|
|
7756
|
+
init_merge();
|
|
7297
7757
|
object = z24.record(z24.unknown());
|
|
7298
7758
|
mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
7299
7759
|
TOML_START = "# mason:mcp:start";
|
|
@@ -7618,8 +8078,8 @@ __export(impact_exports, {
|
|
|
7618
8078
|
analyzeImpact: () => analyzeImpact
|
|
7619
8079
|
});
|
|
7620
8080
|
import path29 from "path";
|
|
7621
|
-
import { execFile as
|
|
7622
|
-
import { promisify as
|
|
8081
|
+
import { execFile as execFile7 } from "child_process";
|
|
8082
|
+
import { promisify as promisify7 } from "util";
|
|
7623
8083
|
async function analyzeImpact(rootDir, targetFiles) {
|
|
7624
8084
|
const resolvedRoot = path29.resolve(rootDir);
|
|
7625
8085
|
const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
|
|
@@ -7664,7 +8124,7 @@ async function getCochangeFiles(rootDir, targetFiles) {
|
|
|
7664
8124
|
let totalTargetCommits = 0;
|
|
7665
8125
|
for (const targetFile of targetFiles) {
|
|
7666
8126
|
try {
|
|
7667
|
-
const { stdout: commitLog } = await
|
|
8127
|
+
const { stdout: commitLog } = await exec5(
|
|
7668
8128
|
"git",
|
|
7669
8129
|
["log", "--format=%H", "-n", "500", "--", targetFile],
|
|
7670
8130
|
{ cwd: rootDir, maxBuffer: 5e6 }
|
|
@@ -7674,7 +8134,7 @@ async function getCochangeFiles(rootDir, targetFiles) {
|
|
|
7674
8134
|
if (commits.length === 0) continue;
|
|
7675
8135
|
for (const commit of commits) {
|
|
7676
8136
|
try {
|
|
7677
|
-
const { stdout: filesInCommit } = await
|
|
8137
|
+
const { stdout: filesInCommit } = await exec5(
|
|
7678
8138
|
"git",
|
|
7679
8139
|
["diff-tree", "--no-commit-id", "--name-only", "-r", commit],
|
|
7680
8140
|
{ cwd: rootDir }
|
|
@@ -7751,14 +8211,14 @@ async function getRelatedTests(rootDir, targetFiles) {
|
|
|
7751
8211
|
}
|
|
7752
8212
|
return results;
|
|
7753
8213
|
}
|
|
7754
|
-
var
|
|
8214
|
+
var exec5;
|
|
7755
8215
|
var init_impact = __esm({
|
|
7756
8216
|
"src/impact/impact.ts"() {
|
|
7757
8217
|
"use strict";
|
|
7758
8218
|
init_files();
|
|
7759
8219
|
init_paths();
|
|
7760
8220
|
init_references();
|
|
7761
|
-
|
|
8221
|
+
exec5 = promisify7(execFile7);
|
|
7762
8222
|
}
|
|
7763
8223
|
});
|
|
7764
8224
|
|
|
@@ -7768,12 +8228,12 @@ __export(assemble_exports, {
|
|
|
7768
8228
|
assembleContext: () => assembleContext
|
|
7769
8229
|
});
|
|
7770
8230
|
import path30 from "path";
|
|
7771
|
-
import
|
|
8231
|
+
import fs24 from "fs/promises";
|
|
7772
8232
|
async function collectImpact(root, candidates) {
|
|
7773
8233
|
const targets2 = /* @__PURE__ */ new Set();
|
|
7774
8234
|
let sourceFiles;
|
|
7775
8235
|
for (const candidate of sanitizeRepoPaths(candidates)) {
|
|
7776
|
-
const stat = await
|
|
8236
|
+
const stat = await fs24.stat(path30.join(root, candidate)).catch(() => null);
|
|
7777
8237
|
if (stat?.isDirectory()) {
|
|
7778
8238
|
sourceFiles ??= await (await createFileAccess(root)).list();
|
|
7779
8239
|
for (const file of sourceFiles) {
|
|
@@ -8046,7 +8506,7 @@ __export(observations_exports, {
|
|
|
8046
8506
|
observeActivation: () => observeActivation,
|
|
8047
8507
|
readObservation: () => readObservation
|
|
8048
8508
|
});
|
|
8049
|
-
import
|
|
8509
|
+
import fs25 from "fs/promises";
|
|
8050
8510
|
import { z as z30 } from "zod";
|
|
8051
8511
|
function observationPath(directory3, host, revision, version = executingVersion()) {
|
|
8052
8512
|
return `${directory3}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
@@ -8070,8 +8530,8 @@ async function observeActivation(dir, event, options = {}) {
|
|
|
8070
8530
|
return inactiveSetupMessage();
|
|
8071
8531
|
}
|
|
8072
8532
|
const capturedDirectory = options.directory?.match(/^\.mason\/reports\/automation\/[a-f0-9]{24}$/)?.[0] ?? options.reportPath?.match(/^(\.mason\/reports\/automation\/[a-f0-9]{24})\/checks\//)?.[1];
|
|
8073
|
-
const ws = capturedDirectory ? { root: await
|
|
8074
|
-
if (ws.root !== await
|
|
8533
|
+
const ws = capturedDirectory ? { root: await fs25.realpath(dir), directory: capturedDirectory } : await workspace(dir);
|
|
8534
|
+
if (ws.root !== await fs25.realpath(process.env.MASON_SETUP_ROOT)) return null;
|
|
8075
8535
|
const { setup } = await effectiveSetup(ws.root);
|
|
8076
8536
|
if (setup?.hosts[host]?.revision !== revision)
|
|
8077
8537
|
return "Mason setup changed; restart the assistant to observe the current integration.";
|
|
@@ -8152,6 +8612,7 @@ async function setupStatus(dir) {
|
|
|
8152
8612
|
const ws = await workspace(dir);
|
|
8153
8613
|
const activation = await effectiveSetup(ws.root);
|
|
8154
8614
|
const setup = activation.setup;
|
|
8615
|
+
const mergeDriver = await decisionMergeStatus(ws.root);
|
|
8155
8616
|
if (!setup || !Object.keys(setup.hosts).length) {
|
|
8156
8617
|
const partial = await Promise.all(
|
|
8157
8618
|
["codex", "claude"].map(async (host) => ({
|
|
@@ -8182,6 +8643,7 @@ async function setupStatus(dir) {
|
|
|
8182
8643
|
status: pending.length ? "incomplete" : "not-configured",
|
|
8183
8644
|
hosts: {},
|
|
8184
8645
|
configuration,
|
|
8646
|
+
mergeDriver,
|
|
8185
8647
|
next: pending.length ? "Setup did not finish. Rerun mason setup --host " + pending[0] + " to resume local activation; use mason init if shared configuration needs updating." : "Project hooks are inactive. Run mason setup --host codex or --host claude for compatible shared configuration; a maintainer uses mason init to prepare missing or incompatible configuration."
|
|
8186
8648
|
};
|
|
8187
8649
|
}
|
|
@@ -8256,6 +8718,7 @@ async function setupStatus(dir) {
|
|
|
8256
8718
|
hosts,
|
|
8257
8719
|
activationRoot: activation.root,
|
|
8258
8720
|
decisionRecords: decisions.records.length,
|
|
8721
|
+
mergeDriver,
|
|
8259
8722
|
diagnostics: decisions.diagnostics,
|
|
8260
8723
|
scope: "Activation receipts record observed use after setup, not complete interception, correct repairs, or measured usefulness. Host trust and higher-priority settings may prevent execution. Receipts are local to this worktree and branch."
|
|
8261
8724
|
};
|
|
@@ -8278,6 +8741,8 @@ function summarizeActivation(status) {
|
|
|
8278
8741
|
lines.push(...state.updates.map((message2) => " Optional: " + message2));
|
|
8279
8742
|
}
|
|
8280
8743
|
if ("decisionRecords" in status) lines.push(`Decision records: ${status.decisionRecords}.`);
|
|
8744
|
+
if (status.mergeDriver.attribute || status.mergeDriver.driver !== "missing")
|
|
8745
|
+
lines.push(mergeDriverSummary(status.mergeDriver));
|
|
8281
8746
|
if (status.next) lines.push(status.next);
|
|
8282
8747
|
return lines.join("\n");
|
|
8283
8748
|
}
|
|
@@ -8395,6 +8860,7 @@ async function setupProject(dir, options = {}) {
|
|
|
8395
8860
|
after: JSON.stringify(setup, null, 2) + "\n"
|
|
8396
8861
|
}))
|
|
8397
8862
|
changedFiles.push(SETUP_PATH);
|
|
8863
|
+
const mergeDriver = await configureDecisionMerge(ws.root);
|
|
8398
8864
|
options.progress?.step("Checking agent configuration");
|
|
8399
8865
|
for (const input of sharedInputs)
|
|
8400
8866
|
if (await readText(ws.root, input.path) !== input.before)
|
|
@@ -8418,6 +8884,7 @@ async function setupProject(dir, options = {}) {
|
|
|
8418
8884
|
runtime: { kind: "global", command: "mason", version: installed.version },
|
|
8419
8885
|
changedFiles,
|
|
8420
8886
|
receiptPath,
|
|
8887
|
+
mergeDriver,
|
|
8421
8888
|
activation: await setupStatus(ws.root),
|
|
8422
8889
|
next: configured.disabled || configured.mcpDisabled ? "Mason hooks or MCP are disabled in project configuration. Review that setting before activation." : host === "codex" ? "Review/trust this project's MCP configuration and hooks in Codex (/hooks in the CLI), then start a new session and give it a normal task." : "Approve the project MCP server in Claude Code, then start a new session and give it a normal task."
|
|
8423
8890
|
};
|
|
@@ -8429,6 +8896,7 @@ function summarizeSetup(result) {
|
|
|
8429
8896
|
" MCP server and lifecycle hooks use mason from PATH.",
|
|
8430
8897
|
" Existing shared configuration validated; only local activation state was written.",
|
|
8431
8898
|
` ${result.changedFiles.length} files changed.`,
|
|
8899
|
+
" " + mergeDriverSummary(result.mergeDriver),
|
|
8432
8900
|
"Activation: " + result.activation.status + ".",
|
|
8433
8901
|
result.next,
|
|
8434
8902
|
"If the assistant cannot find mason, restart the desktop app so it picks up PATH.",
|
|
@@ -8526,15 +8994,17 @@ init_snapshot();
|
|
|
8526
8994
|
init_drift();
|
|
8527
8995
|
init_paths();
|
|
8528
8996
|
init_files();
|
|
8529
|
-
|
|
8530
|
-
import {
|
|
8531
|
-
import {
|
|
8997
|
+
init_attribution();
|
|
8998
|
+
import { createHash as createHash7 } from "crypto";
|
|
8999
|
+
import { execFile as execFile4 } from "child_process";
|
|
9000
|
+
import { promisify as promisify4 } from "util";
|
|
8532
9001
|
import { z as z15 } from "zod";
|
|
8533
|
-
var
|
|
9002
|
+
var exec2 = promisify4(execFile4);
|
|
8534
9003
|
var requestSchema = z15.object({
|
|
8535
9004
|
id: z15.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
8536
9005
|
action: z15.enum(["prepare", "inspect", "accept", "reaffirm", "retire"]).default("prepare"),
|
|
8537
9006
|
inspectionScope: z15.enum(["all", "operative"]).default("all"),
|
|
9007
|
+
diffFrom: z15.enum(["lastReview", "capture"]).default("lastReview"),
|
|
8538
9008
|
inspector: z15.string().trim().min(1).max(200).optional(),
|
|
8539
9009
|
reviewer: z15.string().trim().min(1).max(200).optional(),
|
|
8540
9010
|
note: z15.string().trim().min(1).max(1500).optional(),
|
|
@@ -8561,7 +9031,7 @@ async function reviewState(root, record, scope = "all") {
|
|
|
8561
9031
|
localChanges: matchingPaths(anchors, workingTree.changedFiles)
|
|
8562
9032
|
};
|
|
8563
9033
|
const capture = await captureAnchors(root, anchors);
|
|
8564
|
-
const reviewToken2 =
|
|
9034
|
+
const reviewToken2 = createHash7("sha256").update(
|
|
8565
9035
|
JSON.stringify({
|
|
8566
9036
|
record,
|
|
8567
9037
|
evidence,
|
|
@@ -8572,11 +9042,54 @@ async function reviewState(root, record, scope = "all") {
|
|
|
8572
9042
|
).digest("hex");
|
|
8573
9043
|
return { reviewToken: reviewToken2, evidence, capture, committed, workingTreeAvailable: workingTree.available };
|
|
8574
9044
|
}
|
|
8575
|
-
|
|
9045
|
+
var RETURNED_INSPECTIONS = 2;
|
|
9046
|
+
var MAX_PREVIEW_COMMITS = 20;
|
|
9047
|
+
async function diffSince(root, record, scope, diffFrom) {
|
|
9048
|
+
const baseline = (scope === "operative" ? effectiveDecision(record) : record).refreshedHash;
|
|
9049
|
+
const inspection = diffFrom === "lastReview" ? latestDecisionInspection(record, scope) : void 0;
|
|
9050
|
+
if (inspection && /^[a-f0-9]{40,64}$/.test(inspection.headHash)) {
|
|
9051
|
+
try {
|
|
9052
|
+
await exec2("git", ["cat-file", "-e", `${inspection.headHash}^{commit}`], { cwd: root });
|
|
9053
|
+
return { from: "inspection", commit: inspection.headHash, at: inspection.at };
|
|
9054
|
+
} catch {
|
|
9055
|
+
return {
|
|
9056
|
+
from: "baseline",
|
|
9057
|
+
commit: baseline,
|
|
9058
|
+
note: "The latest inspection's commit is not available locally; the diff starts at the evidence baseline."
|
|
9059
|
+
};
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
return { from: "baseline", commit: baseline };
|
|
9063
|
+
}
|
|
9064
|
+
function trimInspections(record) {
|
|
9065
|
+
return record.version === 2 && (record.inspections?.length ?? 0) > RETURNED_INSPECTIONS ? { ...record, inspections: record.inspections.slice(-RETURNED_INSPECTIONS) } : record;
|
|
9066
|
+
}
|
|
9067
|
+
async function previews(root, state, since) {
|
|
8576
9068
|
const access = await createFileAccess(root);
|
|
8577
9069
|
const anchors = state.capture.anchors;
|
|
9070
|
+
const git4 = (args) => exec2("git", ["--literal-pathspecs", ...args, "--", ...anchors], {
|
|
9071
|
+
cwd: root,
|
|
9072
|
+
maxBuffer: 1024 * 1024
|
|
9073
|
+
}).then(({ stdout }) => stdout);
|
|
9074
|
+
const range = state.evidence.headHash === "unknown" ? null : [since.commit, state.evidence.headHash];
|
|
9075
|
+
let changedSince = [];
|
|
9076
|
+
let commits = [];
|
|
9077
|
+
if (range && anchors.length)
|
|
9078
|
+
try {
|
|
9079
|
+
changedSince = (await git4(["diff", "--name-only", "--no-renames", ...range])).split("\n").filter(Boolean);
|
|
9080
|
+
commits = (await git4([
|
|
9081
|
+
"log",
|
|
9082
|
+
"--oneline",
|
|
9083
|
+
"--no-decorate",
|
|
9084
|
+
"-n",
|
|
9085
|
+
String(MAX_PREVIEW_COMMITS + 1),
|
|
9086
|
+
`${range[0]}..${range[1]}`
|
|
9087
|
+
])).split("\n").filter(Boolean);
|
|
9088
|
+
} catch {
|
|
9089
|
+
}
|
|
8578
9090
|
const candidates = [
|
|
8579
9091
|
.../* @__PURE__ */ new Set([
|
|
9092
|
+
...changedSince,
|
|
8580
9093
|
...state.evidence.changedFiles,
|
|
8581
9094
|
...state.evidence.localChanges,
|
|
8582
9095
|
...anchors,
|
|
@@ -8587,7 +9100,6 @@ async function previews(root, record, state) {
|
|
|
8587
9100
|
];
|
|
8588
9101
|
const files = [];
|
|
8589
9102
|
const omittedFiles = [];
|
|
8590
|
-
const diffPaths = [];
|
|
8591
9103
|
for (const file of candidates) {
|
|
8592
9104
|
if (files.length >= 6) {
|
|
8593
9105
|
omittedFiles.push(file);
|
|
@@ -8603,37 +9115,46 @@ async function previews(root, record, state) {
|
|
|
8603
9115
|
preview: source.content.slice(0, 2e3),
|
|
8604
9116
|
totalLines: source.totalLines
|
|
8605
9117
|
});
|
|
8606
|
-
if (state.evidence.changedFiles.includes(file)) diffPaths.push(file);
|
|
8607
9118
|
}
|
|
8608
9119
|
let diff = null;
|
|
8609
9120
|
let diffUnavailable;
|
|
8610
|
-
if (
|
|
9121
|
+
if (range && changedSince.length) {
|
|
8611
9122
|
try {
|
|
8612
|
-
const
|
|
8613
|
-
"
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
"-U3",
|
|
8622
|
-
record.refreshedHash,
|
|
8623
|
-
state.evidence.headHash,
|
|
8624
|
-
"--",
|
|
8625
|
-
...diffPaths
|
|
8626
|
-
],
|
|
8627
|
-
{ cwd: root, maxBuffer: 1024 * 1024 }
|
|
8628
|
-
);
|
|
9123
|
+
const stdout = await git4([
|
|
9124
|
+
"diff",
|
|
9125
|
+
"--no-ext-diff",
|
|
9126
|
+
"--no-textconv",
|
|
9127
|
+
"--no-color",
|
|
9128
|
+
"--no-renames",
|
|
9129
|
+
"-U3",
|
|
9130
|
+
...range
|
|
9131
|
+
]);
|
|
8629
9132
|
diff = stdout.slice(0, 16e3);
|
|
8630
9133
|
if (stdout.length > diff.length)
|
|
8631
9134
|
diffUnavailable = "Diff preview was truncated; inspect the full diff before reviewing.";
|
|
8632
9135
|
} catch {
|
|
8633
9136
|
diffUnavailable = "Diff preview could not be read within its size bound; inspect the diff separately.";
|
|
8634
9137
|
}
|
|
9138
|
+
} else if (range && !changedSince.length && since.commit !== state.evidence.headHash) {
|
|
9139
|
+
try {
|
|
9140
|
+
await exec2("git", ["cat-file", "-e", `${since.commit}^{commit}`], { cwd: root });
|
|
9141
|
+
} catch {
|
|
9142
|
+
diffUnavailable = "The diff base is not available locally; inspect the history separately.";
|
|
9143
|
+
}
|
|
8635
9144
|
}
|
|
9145
|
+
const branch = changedSince.length ? await branchAttribution(root).catch(() => null) : null;
|
|
8636
9146
|
return {
|
|
9147
|
+
diffSince: since,
|
|
9148
|
+
...branch ? {
|
|
9149
|
+
attribution: {
|
|
9150
|
+
target: branch.target,
|
|
9151
|
+
mergeBase: branch.mergeBase,
|
|
9152
|
+
branchFiles: changedSince.filter((file) => branch.branchFiles.has(file)),
|
|
9153
|
+
targetFiles: changedSince.filter((file) => !branch.branchFiles.has(file))
|
|
9154
|
+
}
|
|
9155
|
+
} : {},
|
|
9156
|
+
commits: commits.slice(0, MAX_PREVIEW_COMMITS),
|
|
9157
|
+
...commits.length > MAX_PREVIEW_COMMITS ? { commitsTruncated: true } : {},
|
|
8637
9158
|
files,
|
|
8638
9159
|
diff,
|
|
8639
9160
|
diffUnavailable,
|
|
@@ -8669,10 +9190,12 @@ async function reviewDecision(root, input) {
|
|
|
8669
9190
|
};
|
|
8670
9191
|
const state = await reviewState(root, record, request.inspectionScope);
|
|
8671
9192
|
const operativeDecision = effectiveDecision(record);
|
|
9193
|
+
const inspections2 = record.version === 2 ? record.inspections?.length ?? 0 : 0;
|
|
8672
9194
|
return {
|
|
8673
9195
|
status: "prepared",
|
|
8674
|
-
record,
|
|
8675
|
-
...
|
|
9196
|
+
record: trimInspections(record),
|
|
9197
|
+
...inspections2 > RETURNED_INSPECTIONS ? { omittedInspections: inspections2 - RETURNED_INSPECTIONS } : {},
|
|
9198
|
+
...operativeDecision !== record && request.inspectionScope === "all" ? { operativeDecision: trimInspections(operativeDecision) } : {},
|
|
8676
9199
|
inspectionScope: request.inspectionScope,
|
|
8677
9200
|
...request.inspectionScope === "operative" ? {
|
|
8678
9201
|
inspectionTarget: {
|
|
@@ -8693,8 +9216,8 @@ async function reviewDecision(root, input) {
|
|
|
8693
9216
|
diagnostics,
|
|
8694
9217
|
previews: await previews(
|
|
8695
9218
|
root,
|
|
8696
|
-
|
|
8697
|
-
|
|
9219
|
+
state,
|
|
9220
|
+
await diffSince(root, record, request.inspectionScope, request.diffFrom)
|
|
8698
9221
|
),
|
|
8699
9222
|
hint: request.inspectionScope === "operative" ? "Inspect only the accepted inspectionTarget against its source and diff. Use the same inspectionScope: operative and token with action inspect. This reports no contradiction for that revision only; pending content remains unassessed and visible. Do not attest to the draft or infer human approval. If the accepted guidance is contradicted or uncertain, explain that instead of recording no contradiction." : "Inspect the proposed content, operativeDecision, sources, history, and code changes. A pending proposal leaves the prior accepted revision operative; accepting it replaces that revision, and retirement withdraws the entire decision including its proposal. Evidence covers both revisions' anchors. After inspecting the relevant code, action inspect with inspector, note and reviewToken records no_contradiction_found without human approval; it does not accept or reaffirm the decision. Only record acceptance or reaffirmation when the user or cited team review has authorized it. Supply that reviewer's identity, a reason, and this reviewToken. Do not invent identities or infer agreement from unchanged code. Acceptance and reaffirmation require committed anchor changes; retirement is available independently. Missing old history remains visible in the event even if a reviewer establishes a new baseline at HEAD. Saved reviews are local assertions for normal PR review, not authenticated approvals."
|
|
8700
9223
|
};
|
|
@@ -8850,15 +9373,15 @@ init_provenance();
|
|
|
8850
9373
|
init_decisions();
|
|
8851
9374
|
init_paths();
|
|
8852
9375
|
init_drift2();
|
|
8853
|
-
import
|
|
9376
|
+
import fs23 from "fs/promises";
|
|
8854
9377
|
import path27 from "path";
|
|
8855
|
-
import { execFile as
|
|
8856
|
-
import { promisify as
|
|
9378
|
+
import { execFile as execFile6 } from "child_process";
|
|
9379
|
+
import { promisify as promisify6 } from "util";
|
|
8857
9380
|
|
|
8858
9381
|
// src/review/cochange.ts
|
|
8859
|
-
import { execFile as
|
|
8860
|
-
import { promisify as
|
|
8861
|
-
var
|
|
9382
|
+
import { execFile as execFile5 } from "child_process";
|
|
9383
|
+
import { promisify as promisify5 } from "util";
|
|
9384
|
+
var exec3 = promisify5(execFile5);
|
|
8862
9385
|
var HISTORY_COMMITS = 1500;
|
|
8863
9386
|
var MIN_FILE_COMMITS = 5;
|
|
8864
9387
|
var MIN_SHARED_COMMITS = 4;
|
|
@@ -8866,11 +9389,11 @@ var MIN_COCHANGE_RATE = 0.6;
|
|
|
8866
9389
|
var MAX_COMMIT_FILES = 30;
|
|
8867
9390
|
async function buildMatrix(resolvedRoot) {
|
|
8868
9391
|
try {
|
|
8869
|
-
const { stdout: shallow } = await
|
|
9392
|
+
const { stdout: shallow } = await exec3("git", ["rev-parse", "--is-shallow-repository"], {
|
|
8870
9393
|
cwd: resolvedRoot
|
|
8871
9394
|
});
|
|
8872
9395
|
if (shallow.trim() === "true") return null;
|
|
8873
|
-
const { stdout } = await
|
|
9396
|
+
const { stdout } = await exec3(
|
|
8874
9397
|
"git",
|
|
8875
9398
|
["log", `-n${HISTORY_COMMITS}`, "--format=%x01", "--name-only", "-M"],
|
|
8876
9399
|
{ cwd: resolvedRoot, maxBuffer: 100 * 1024 * 1024 }
|
|
@@ -9642,11 +10165,11 @@ function summarizeEvidence(evidence) {
|
|
|
9642
10165
|
|
|
9643
10166
|
// src/review/review.ts
|
|
9644
10167
|
init_snapshot();
|
|
9645
|
-
var
|
|
10168
|
+
var exec4 = promisify6(execFile6);
|
|
9646
10169
|
var MAX_ANALYZED_FILES = 50;
|
|
9647
10170
|
async function resolveMergeBase(resolvedRoot, base, head) {
|
|
9648
10171
|
try {
|
|
9649
|
-
const { stdout } = await
|
|
10172
|
+
const { stdout } = await exec4("git", ["merge-base", base, head], {
|
|
9650
10173
|
cwd: resolvedRoot
|
|
9651
10174
|
});
|
|
9652
10175
|
return stdout.trim() || null;
|
|
@@ -9657,7 +10180,7 @@ async function resolveMergeBase(resolvedRoot, base, head) {
|
|
|
9657
10180
|
async function defaultBase(resolvedRoot) {
|
|
9658
10181
|
for (const ref of ["origin/HEAD", "origin/main", "origin/master", "main"]) {
|
|
9659
10182
|
try {
|
|
9660
|
-
await
|
|
10183
|
+
await exec4("git", ["rev-parse", "--verify", "--quiet", ref], {
|
|
9661
10184
|
cwd: resolvedRoot
|
|
9662
10185
|
});
|
|
9663
10186
|
return ref;
|
|
@@ -9731,7 +10254,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
9731
10254
|
analyzed,
|
|
9732
10255
|
async (relPath) => {
|
|
9733
10256
|
try {
|
|
9734
|
-
await
|
|
10257
|
+
await fs23.access(path27.join(resolvedRoot, relPath));
|
|
9735
10258
|
return true;
|
|
9736
10259
|
} catch {
|
|
9737
10260
|
return false;
|
|
@@ -10398,7 +10921,7 @@ function createMcpServer() {
|
|
|
10398
10921
|
const server = new McpServer(
|
|
10399
10922
|
{
|
|
10400
10923
|
name: "mason",
|
|
10401
|
-
version: "0.
|
|
10924
|
+
version: "0.23.0"
|
|
10402
10925
|
},
|
|
10403
10926
|
{
|
|
10404
10927
|
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 as soon as evidence supports them, without waiting for implementation or merge; 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 and inspect code evidence without claiming human approval; acceptance, reaffirmation, or retirement require an authorized review. 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. 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."
|
|
@@ -10541,7 +11064,7 @@ function createMcpServer() {
|
|
|
10541
11064
|
`Short, specific headline (max ${TITLE_MAX_CHARS} characters). Generates a stable id on creation; revising the title keeps the existing id.`
|
|
10542
11065
|
),
|
|
10543
11066
|
body: decisionBodySchema.describe(
|
|
10544
|
-
`The knowledge itself: what was tried/decided, why, and what to avoid. Must contain information NOT derivable by reading the code. Aim for ${BODY_RECOMMENDED_CHARS} characters or fewer; up to ${BODY_MAX_CHARS} is accepted with a warning above the target. Matching context and hooks include the full body. Preserve exceptions; use sources for supporting references.`
|
|
11067
|
+
`The knowledge itself: what was tried/decided, why, and what to avoid. Must contain information NOT derivable by reading the code. Aim for ${BODY_RECOMMENDED_CHARS} characters or fewer; up to ${BODY_MAX_CHARS} is accepted with a warning above the target. Matching context and hooks include the full body. Preserve exceptions; use sources for supporting references. Name the functions, types, or settings the decision constrains in backticks, such as \`refresh()\`; drift notices use those names to judge whether a change is relevant.`
|
|
10545
11068
|
),
|
|
10546
11069
|
category: z31.enum(["decision", "gotcha", "deprecation", "convention"]),
|
|
10547
11070
|
files: z31.array(z31.string()).optional().describe(
|
|
@@ -10582,7 +11105,7 @@ function createMcpServer() {
|
|
|
10582
11105
|
);
|
|
10583
11106
|
server.tool(
|
|
10584
11107
|
"review_decision",
|
|
10585
|
-
"Prepare a decision review: returns the
|
|
11108
|
+
"Prepare a decision review: returns the record and history with its latest two inspections (older ones are counted), any operative accepted revision, provenance, changes, and source, diff and commit previews since the latest valid inspection for both sets of anchors, and a reviewToken. For accepted-guidance inspection use inspectionScope operative on both prepare and inspect; pending proposals remain unassessed. Default all scope covers both revisions. After source inspection, record inspect with inspector, note and token to report no contradiction found without human acceptance. For human review, prepare with all scope, then record accept, reaffirm, or retire with its token, the authorized reviewer, and a reason. Acceptance replaces the operative revision; retirement withdraws the entire record including its proposal. Acceptance requires owner, source, readable Git HEAD, and committed anchor changes. Changed records or code invalidate the token. Identities and approvals are recorded assertions for normal PR review, not authenticated proof.",
|
|
10586
11109
|
{
|
|
10587
11110
|
dir: z31.string().describe("Absolute path to the project root directory"),
|
|
10588
11111
|
id: z31.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Decision id from get_context or save_decision"),
|
|
@@ -10592,6 +11115,9 @@ function createMcpServer() {
|
|
|
10592
11115
|
inspectionScope: z31.enum(["all", "operative"]).optional().default("all").describe(
|
|
10593
11116
|
"For prepare/inspect only: all covers the full record and both anchor sets (default); operative covers only an active accepted revision, leaving a pending draft unassessed. Use the same scope when submitting its token."
|
|
10594
11117
|
),
|
|
11118
|
+
diffFrom: z31.enum(["lastReview", "capture"]).optional().default("lastReview").describe(
|
|
11119
|
+
"For prepare only: lastReview previews changes since the latest inspection still valid for this record and scope (default); capture previews the full history since the evidence baseline."
|
|
11120
|
+
),
|
|
10595
11121
|
inspector: z31.string().trim().min(1).max(200).optional().describe(
|
|
10596
11122
|
"Actual agent or person performing inspection; required for inspect. Does not claim human approval."
|
|
10597
11123
|
),
|
|
@@ -10679,7 +11205,7 @@ async function startMcpServer(onClose) {
|
|
|
10679
11205
|
init_validation();
|
|
10680
11206
|
init_store();
|
|
10681
11207
|
init_storage();
|
|
10682
|
-
import
|
|
11208
|
+
import fs32 from "fs/promises";
|
|
10683
11209
|
import path37 from "path";
|
|
10684
11210
|
import { spawn as spawn2 } from "child_process";
|
|
10685
11211
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
@@ -10690,19 +11216,19 @@ init_storage();
|
|
|
10690
11216
|
import os5 from "os";
|
|
10691
11217
|
import path33 from "path";
|
|
10692
11218
|
import { fileURLToPath } from "url";
|
|
10693
|
-
import
|
|
11219
|
+
import fs27 from "fs/promises";
|
|
10694
11220
|
import { z as z33 } from "zod";
|
|
10695
11221
|
|
|
10696
11222
|
// src/distribution/path.ts
|
|
10697
11223
|
init_validation();
|
|
10698
|
-
import
|
|
11224
|
+
import fs26 from "fs/promises";
|
|
10699
11225
|
import os4 from "os";
|
|
10700
11226
|
import path32 from "path";
|
|
10701
|
-
import { execFile as
|
|
10702
|
-
import { promisify as
|
|
10703
|
-
import { createHash as
|
|
11227
|
+
import { execFile as execFile8 } from "child_process";
|
|
11228
|
+
import { promisify as promisify8 } from "util";
|
|
11229
|
+
import { createHash as createHash10, randomUUID as randomUUID7 } from "crypto";
|
|
10704
11230
|
import { z as z32 } from "zod";
|
|
10705
|
-
var execute2 =
|
|
11231
|
+
var execute2 = promisify8(execFile8);
|
|
10706
11232
|
var pathChangeSchema = z32.discriminatedUnion("kind", [
|
|
10707
11233
|
z32.object({
|
|
10708
11234
|
kind: z32.literal("profile"),
|
|
@@ -10759,7 +11285,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
10759
11285
|
);
|
|
10760
11286
|
let login = candidates[2];
|
|
10761
11287
|
for (const file of candidates)
|
|
10762
|
-
if (await
|
|
11288
|
+
if (await fs26.lstat(file).then(
|
|
10763
11289
|
() => true,
|
|
10764
11290
|
(error) => {
|
|
10765
11291
|
if (hasErrorCode(error, "ENOENT")) return false;
|
|
@@ -10771,14 +11297,14 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
10771
11297
|
}
|
|
10772
11298
|
profiles2 = [path32.join(userHome, ".bashrc"), login];
|
|
10773
11299
|
}
|
|
10774
|
-
const marker = `# >>> mason PATH ${
|
|
11300
|
+
const marker = `# >>> mason PATH ${createHash10("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
10775
11301
|
for (const profile of profiles2) {
|
|
10776
|
-
const stat = await
|
|
11302
|
+
const stat = await fs26.lstat(profile).catch((error) => {
|
|
10777
11303
|
if (hasErrorCode(error, "ENOENT")) return null;
|
|
10778
11304
|
throw error;
|
|
10779
11305
|
});
|
|
10780
|
-
const file = stat?.isSymbolicLink() ? await
|
|
10781
|
-
const original = await
|
|
11306
|
+
const file = stat?.isSymbolicLink() ? await fs26.realpath(profile) : path32.resolve(profile);
|
|
11307
|
+
const original = await fs26.readFile(file).catch((error) => {
|
|
10782
11308
|
if (hasErrorCode(error, "ENOENT") && !stat) return null;
|
|
10783
11309
|
throw error;
|
|
10784
11310
|
});
|
|
@@ -10797,8 +11323,8 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
10797
11323
|
changes.push({ kind: "profile", file, block, created: original === null });
|
|
10798
11324
|
await save(changes);
|
|
10799
11325
|
}
|
|
10800
|
-
await
|
|
10801
|
-
const handle = await
|
|
11326
|
+
await fs26.mkdir(path32.dirname(file), { recursive: true });
|
|
11327
|
+
const handle = await fs26.open(file, original === null ? "ax+" : "a+", 384);
|
|
10802
11328
|
try {
|
|
10803
11329
|
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0)))
|
|
10804
11330
|
throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
@@ -10893,7 +11419,7 @@ async function readInstallation(home) {
|
|
|
10893
11419
|
"Run this command from a standalone installation. npm users can upgrade with npm."
|
|
10894
11420
|
);
|
|
10895
11421
|
const record = recordSchema3.parse(raw);
|
|
10896
|
-
if (record.home !== await
|
|
11422
|
+
if (record.home !== await fs27.realpath(home))
|
|
10897
11423
|
throw new Error("Invalid standalone installation receipt.");
|
|
10898
11424
|
return record;
|
|
10899
11425
|
}
|
|
@@ -10929,9 +11455,9 @@ function newerStableVersion(candidate, installed) {
|
|
|
10929
11455
|
|
|
10930
11456
|
// src/distribution/bundle.ts
|
|
10931
11457
|
init_storage();
|
|
10932
|
-
import
|
|
11458
|
+
import fs28 from "fs/promises";
|
|
10933
11459
|
import path34 from "path";
|
|
10934
|
-
import { createHash as
|
|
11460
|
+
import { createHash as createHash11 } from "crypto";
|
|
10935
11461
|
import { z as z34 } from "zod";
|
|
10936
11462
|
var targets = [
|
|
10937
11463
|
"darwin-arm64",
|
|
@@ -10949,9 +11475,9 @@ var bundleSchema = z34.object({
|
|
|
10949
11475
|
nodeVersion: z34.string(),
|
|
10950
11476
|
files: z34.record(digest2)
|
|
10951
11477
|
}).strict();
|
|
10952
|
-
var sha256 = (bytes) =>
|
|
11478
|
+
var sha256 = (bytes) => createHash11("sha256").update(bytes).digest("hex");
|
|
10953
11479
|
async function readBundle(root) {
|
|
10954
|
-
const bytes = await
|
|
11480
|
+
const bytes = await fs28.readFile(await storePath(root, "bundle.json"));
|
|
10955
11481
|
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
10956
11482
|
const windows = manifest.target.startsWith("win32-");
|
|
10957
11483
|
for (const file of Object.keys(manifest.files)) {
|
|
@@ -10981,7 +11507,7 @@ async function verifyBundle(root, expectedHash) {
|
|
|
10981
11507
|
for (let i = 0; i < files.length; i += 8)
|
|
10982
11508
|
await Promise.all(
|
|
10983
11509
|
files.slice(i, i + 8).map(async (file) => {
|
|
10984
|
-
if (sha256(await
|
|
11510
|
+
if (sha256(await fs28.readFile(await storePath(root, file))) !== bundle.manifest.files[file])
|
|
10985
11511
|
throw new Error("Mason bundle checksum mismatch: " + file);
|
|
10986
11512
|
})
|
|
10987
11513
|
);
|
|
@@ -10992,7 +11518,7 @@ async function verifyBundle(root, expectedHash) {
|
|
|
10992
11518
|
init_validation();
|
|
10993
11519
|
init_store();
|
|
10994
11520
|
init_storage();
|
|
10995
|
-
import
|
|
11521
|
+
import fs29 from "fs/promises";
|
|
10996
11522
|
import { unlinkSync } from "fs";
|
|
10997
11523
|
import os6 from "os";
|
|
10998
11524
|
import path35 from "path";
|
|
@@ -11011,11 +11537,11 @@ var leaseSchema = z35.object({
|
|
|
11011
11537
|
|
|
11012
11538
|
// src/distribution/install.ts
|
|
11013
11539
|
init_validation();
|
|
11014
|
-
import
|
|
11540
|
+
import fs31 from "fs/promises";
|
|
11015
11541
|
import os8 from "os";
|
|
11016
11542
|
import path36 from "path";
|
|
11017
|
-
import { execFile as
|
|
11018
|
-
import { promisify as
|
|
11543
|
+
import { execFile as execFile9, spawn } from "child_process";
|
|
11544
|
+
import { promisify as promisify9 } from "util";
|
|
11019
11545
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
11020
11546
|
init_storage();
|
|
11021
11547
|
init_store();
|
|
@@ -11023,14 +11549,14 @@ init_store();
|
|
|
11023
11549
|
// src/distribution/sessions.ts
|
|
11024
11550
|
init_validation();
|
|
11025
11551
|
init_storage();
|
|
11026
|
-
import
|
|
11552
|
+
import fs30 from "fs/promises";
|
|
11027
11553
|
import os7 from "os";
|
|
11028
11554
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
11029
11555
|
import { z as z36 } from "zod";
|
|
11030
11556
|
var leaseSchema2 = z36.object({ pid: z36.number().int().positive(), host: z36.string() });
|
|
11031
11557
|
async function hasRunningMcp(home) {
|
|
11032
11558
|
const directory3 = await storePath(home, "sessions");
|
|
11033
|
-
const files = await
|
|
11559
|
+
const files = await fs30.readdir(directory3).catch((error) => {
|
|
11034
11560
|
if (hasErrorCode(error, "ENOENT")) return [];
|
|
11035
11561
|
throw error;
|
|
11036
11562
|
});
|
|
@@ -11048,7 +11574,7 @@ async function hasRunningMcp(home) {
|
|
|
11048
11574
|
} catch (error) {
|
|
11049
11575
|
if (error.code !== "ESRCH") return true;
|
|
11050
11576
|
}
|
|
11051
|
-
await
|
|
11577
|
+
await fs30.rm(await storePath(home, relative), { force: true });
|
|
11052
11578
|
} catch {
|
|
11053
11579
|
return true;
|
|
11054
11580
|
}
|
|
@@ -11059,7 +11585,7 @@ async function registerMcp(home) {
|
|
|
11059
11585
|
const name = `sessions/${randomUUID9()}.json`;
|
|
11060
11586
|
await writeStoreJson(home, name, { pid: process.pid, host: os7.hostname() });
|
|
11061
11587
|
return async () => {
|
|
11062
|
-
await
|
|
11588
|
+
await fs30.rm(await storePath(home, name), { force: true }).catch(() => {
|
|
11063
11589
|
});
|
|
11064
11590
|
};
|
|
11065
11591
|
}
|
|
@@ -11072,10 +11598,10 @@ var launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
|
|
|
11072
11598
|
async function replace(file, content2, mode = 384) {
|
|
11073
11599
|
const temp = file + ".tmp-" + randomUUID10();
|
|
11074
11600
|
try {
|
|
11075
|
-
await
|
|
11076
|
-
await
|
|
11601
|
+
await fs31.writeFile(temp, content2, { mode, flag: "wx" });
|
|
11602
|
+
await fs31.rename(temp, file);
|
|
11077
11603
|
} finally {
|
|
11078
|
-
await
|
|
11604
|
+
await fs31.rm(temp, { force: true });
|
|
11079
11605
|
}
|
|
11080
11606
|
}
|
|
11081
11607
|
function assertUpdatePolicy(record, version, revision) {
|
|
@@ -11089,19 +11615,19 @@ async function installLocked2(home, source, bundle, progress, options, alreadySt
|
|
|
11089
11615
|
if (options.stageOnly)
|
|
11090
11616
|
assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
|
|
11091
11617
|
if (!previous)
|
|
11092
|
-
for (const file of await
|
|
11618
|
+
for (const file of await fs31.readdir(home)) {
|
|
11093
11619
|
if (file === ".install-lock") continue;
|
|
11094
11620
|
const directory3 = path36.join(home, file);
|
|
11095
|
-
const stat = await
|
|
11096
|
-
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await
|
|
11621
|
+
const stat = await fs31.lstat(directory3);
|
|
11622
|
+
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs31.readdir(directory3)).length) {
|
|
11097
11623
|
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
11098
11624
|
}
|
|
11099
11625
|
}
|
|
11100
11626
|
const selectedBin = path36.resolve(
|
|
11101
11627
|
process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path36.join(home, "bin") : path36.join(os8.homedir(), ".local/bin"))
|
|
11102
11628
|
);
|
|
11103
|
-
await
|
|
11104
|
-
const bin = await
|
|
11629
|
+
await fs31.mkdir(selectedBin, { recursive: true });
|
|
11630
|
+
const bin = await fs31.realpath(selectedBin);
|
|
11105
11631
|
if (previous && (previous.home !== home || previous.bin !== bin))
|
|
11106
11632
|
throw new Error("Installation paths differ from the existing Mason receipt.");
|
|
11107
11633
|
const id = bundle.manifestHash.slice(0, 24);
|
|
@@ -11145,7 +11671,7 @@ exec ${shQuote(path36.join(destination, "node"))} ${shQuote(path36.join(destinat
|
|
|
11145
11671
|
};
|
|
11146
11672
|
for (const [name, content2] of Object.entries(launchers)) {
|
|
11147
11673
|
const file = await storePath(bin, name);
|
|
11148
|
-
const old = await
|
|
11674
|
+
const old = await fs31.readFile(file, "utf8").catch((error) => {
|
|
11149
11675
|
if (hasErrorCode(error, "ENOENT")) return null;
|
|
11150
11676
|
throw error;
|
|
11151
11677
|
});
|
|
@@ -11157,7 +11683,7 @@ exec ${shQuote(path36.join(destination, "node"))} ${shQuote(path36.join(destinat
|
|
|
11157
11683
|
);
|
|
11158
11684
|
const checkRetired = async (name, expected) => {
|
|
11159
11685
|
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
11160
|
-
const actual = await
|
|
11686
|
+
const actual = await fs31.readFile(await storePath(bin, name), "utf8").catch((error) => {
|
|
11161
11687
|
if (hasErrorCode(error, "ENOENT")) return null;
|
|
11162
11688
|
throw error;
|
|
11163
11689
|
});
|
|
@@ -11200,7 +11726,7 @@ exec ${shQuote(path36.join(destination, "node"))} ${shQuote(path36.join(destinat
|
|
|
11200
11726
|
await replace(await storePath(bin, name, true), content2, 493);
|
|
11201
11727
|
for (const [name, expected] of retired) {
|
|
11202
11728
|
await checkRetired(name, expected);
|
|
11203
|
-
await
|
|
11729
|
+
await fs31.rm(await storePath(bin, name), { force: true });
|
|
11204
11730
|
}
|
|
11205
11731
|
await writeStoreJson(home, "install.json", receipt);
|
|
11206
11732
|
} catch (error) {
|
|
@@ -11233,7 +11759,7 @@ exec ${shQuote(path36.join(destination, "node"))} ${shQuote(path36.join(destinat
|
|
|
11233
11759
|
};
|
|
11234
11760
|
}
|
|
11235
11761
|
async function copyBundle(home, source, destination, bundle) {
|
|
11236
|
-
const exists = await
|
|
11762
|
+
const exists = await fs31.lstat(destination).then(
|
|
11237
11763
|
() => true,
|
|
11238
11764
|
(error) => {
|
|
11239
11765
|
if (hasErrorCode(error, "ENOENT")) return false;
|
|
@@ -11245,16 +11771,16 @@ async function copyBundle(home, source, destination, bundle) {
|
|
|
11245
11771
|
return;
|
|
11246
11772
|
}
|
|
11247
11773
|
const stage = await storePath(home, "versions/.install-" + randomUUID10(), true);
|
|
11248
|
-
await
|
|
11774
|
+
await fs31.mkdir(stage);
|
|
11249
11775
|
try {
|
|
11250
11776
|
for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
|
|
11251
11777
|
const from = await storePath(source, file), to = await storePath(stage, file, true);
|
|
11252
|
-
await
|
|
11253
|
-
await
|
|
11778
|
+
await fs31.copyFile(from, to);
|
|
11779
|
+
await fs31.chmod(to, (await fs31.stat(from)).mode & 511);
|
|
11254
11780
|
}
|
|
11255
11781
|
await verifyBundle(stage, bundle.manifestHash);
|
|
11256
11782
|
try {
|
|
11257
|
-
await
|
|
11783
|
+
await fs31.rename(stage, destination);
|
|
11258
11784
|
} catch (error) {
|
|
11259
11785
|
try {
|
|
11260
11786
|
await verifyBundle(destination, bundle.manifestHash);
|
|
@@ -11263,7 +11789,7 @@ async function copyBundle(home, source, destination, bundle) {
|
|
|
11263
11789
|
}
|
|
11264
11790
|
}
|
|
11265
11791
|
} finally {
|
|
11266
|
-
await
|
|
11792
|
+
await fs31.rm(stage, { recursive: true, force: true });
|
|
11267
11793
|
}
|
|
11268
11794
|
}
|
|
11269
11795
|
async function prepareStandaloneMcp(home) {
|