mason-context 0.17.1 → 0.17.3
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 +16 -0
- package/dist/mason-audit.js +127 -34
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-auto.js +309 -99
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-mcp.js +235 -93
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason.js +342 -117
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/dist/mason.js
CHANGED
|
@@ -346,6 +346,39 @@ var init_store = __esm({
|
|
|
346
346
|
}
|
|
347
347
|
});
|
|
348
348
|
|
|
349
|
+
// src/utils/profile.ts
|
|
350
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
351
|
+
async function profilePhase(label, run) {
|
|
352
|
+
const phases = profiles.getStore();
|
|
353
|
+
if (!phases) return run();
|
|
354
|
+
const start = performance.now();
|
|
355
|
+
try {
|
|
356
|
+
return await run();
|
|
357
|
+
} finally {
|
|
358
|
+
const timing = phases[label] ??= { calls: 0, totalMs: 0 };
|
|
359
|
+
timing.calls++;
|
|
360
|
+
timing.totalMs += performance.now() - start;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async function withProfile(run, emit) {
|
|
364
|
+
const phases = {};
|
|
365
|
+
const start = performance.now();
|
|
366
|
+
return profiles.run(phases, async () => {
|
|
367
|
+
try {
|
|
368
|
+
return await run();
|
|
369
|
+
} finally {
|
|
370
|
+
emit(JSON.stringify({ kind: "mason-profile", version: 1, totalMs: performance.now() - start, phases }));
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
var profiles;
|
|
375
|
+
var init_profile = __esm({
|
|
376
|
+
"src/utils/profile.ts"() {
|
|
377
|
+
"use strict";
|
|
378
|
+
profiles = new AsyncLocalStorage();
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
349
382
|
// src/audit/commands.ts
|
|
350
383
|
import path4 from "path";
|
|
351
384
|
function splitCommands(line) {
|
|
@@ -949,16 +982,27 @@ import path6 from "path";
|
|
|
949
982
|
import { execFile as execFile4 } from "child_process";
|
|
950
983
|
import { promisify as promisify4 } from "util";
|
|
951
984
|
import fg3 from "fast-glob";
|
|
952
|
-
async function
|
|
985
|
+
async function dirtyDocs(resolvedRoot, files) {
|
|
986
|
+
if (!files.length) return /* @__PURE__ */ new Set();
|
|
953
987
|
try {
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
988
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
989
|
+
for (let offset = 0; offset < files.length; offset += 256) {
|
|
990
|
+
const { stdout } = await exec3(
|
|
991
|
+
"git",
|
|
992
|
+
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
|
|
993
|
+
{ cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
|
|
994
|
+
);
|
|
995
|
+
const entries = stdout.split("\0");
|
|
996
|
+
for (let i = 0; i < entries.length; i++) {
|
|
997
|
+
if (!entries[i]) continue;
|
|
998
|
+
const status = entries[i].slice(0, 2);
|
|
999
|
+
dirty.add(entries[i].slice(3));
|
|
1000
|
+
if (/[RC]/.test(status) && entries[i + 1]) dirty.add(entries[++i]);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return dirty;
|
|
960
1004
|
} catch {
|
|
961
|
-
return
|
|
1005
|
+
return new Set(files);
|
|
962
1006
|
}
|
|
963
1007
|
}
|
|
964
1008
|
async function discoverDocPaths(root) {
|
|
@@ -1039,21 +1083,30 @@ function documentScope(file) {
|
|
|
1039
1083
|
return path6.posix.basename(parent).toLowerCase() === ".claude" ? path6.posix.dirname(parent) : parent;
|
|
1040
1084
|
}
|
|
1041
1085
|
async function discoverDocs(resolvedRoot) {
|
|
1086
|
+
return profilePhase("audit.documents", () => readDocs(resolvedRoot));
|
|
1087
|
+
}
|
|
1088
|
+
async function readDocs(resolvedRoot) {
|
|
1042
1089
|
const docs = [];
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1090
|
+
const files = await discoverDocPaths(resolvedRoot);
|
|
1091
|
+
const dirty = await dirtyDocs(resolvedRoot, files);
|
|
1092
|
+
for (let offset = 0; offset < files.length; offset += 4) {
|
|
1093
|
+
docs.push(...await Promise.all(files.slice(offset, offset + 4).map(async (candidate) => {
|
|
1094
|
+
const [content2, lastCommit] = await Promise.all([
|
|
1095
|
+
readAuditInput(resolvedRoot, candidate),
|
|
1096
|
+
profilePhase("audit.document-history", () => lastCommitOf(resolvedRoot, candidate))
|
|
1097
|
+
]);
|
|
1098
|
+
if (content2 === null) throw new Error("Document disappeared during discovery: " + candidate);
|
|
1099
|
+
return {
|
|
1100
|
+
path: candidate,
|
|
1101
|
+
content: content2,
|
|
1102
|
+
scope: documentScope(candidate),
|
|
1103
|
+
kind: /^readme\.md$/i.test(path6.posix.basename(candidate)) ? "readme" : "instructions",
|
|
1104
|
+
lineCount: content2.split("\n").length,
|
|
1105
|
+
lastCommit,
|
|
1106
|
+
dirty: dirty.has(candidate),
|
|
1107
|
+
claims: extractClaims(content2)
|
|
1108
|
+
};
|
|
1109
|
+
})));
|
|
1057
1110
|
}
|
|
1058
1111
|
return docs;
|
|
1059
1112
|
}
|
|
@@ -1061,6 +1114,7 @@ var exec3, DOC_CANDIDATES;
|
|
|
1061
1114
|
var init_docs = __esm({
|
|
1062
1115
|
"src/audit/docs.ts"() {
|
|
1063
1116
|
"use strict";
|
|
1117
|
+
init_profile();
|
|
1064
1118
|
init_claims();
|
|
1065
1119
|
init_git();
|
|
1066
1120
|
init_inputs();
|
|
@@ -2586,6 +2640,9 @@ var init_advisory_review = __esm({
|
|
|
2586
2640
|
import fs11 from "fs/promises";
|
|
2587
2641
|
import path18 from "path";
|
|
2588
2642
|
async function computeAudit(rootDir, options = {}) {
|
|
2643
|
+
return profilePhase("audit.current", () => auditCurrent(rootDir, options));
|
|
2644
|
+
}
|
|
2645
|
+
async function auditCurrent(rootDir, options) {
|
|
2589
2646
|
const resolvedRoot = path18.resolve(rootDir);
|
|
2590
2647
|
const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
|
|
2591
2648
|
if (docs.length === 0) return null;
|
|
@@ -2612,11 +2669,11 @@ async function computeAudit(rootDir, options = {}) {
|
|
|
2612
2669
|
};
|
|
2613
2670
|
if (!report.gitAvailable) return report;
|
|
2614
2671
|
const changesSinceDoc = /* @__PURE__ */ new Map();
|
|
2672
|
+
const changesByCommit = /* @__PURE__ */ new Map();
|
|
2615
2673
|
for (const doc of docs) {
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
);
|
|
2674
|
+
const hash2 = doc.lastCommit?.hash;
|
|
2675
|
+
if (hash2 && !changesByCommit.has(hash2)) changesByCommit.set(hash2, await getChangesWithStatus(resolvedRoot, hash2));
|
|
2676
|
+
changesSinceDoc.set(doc.path, hash2 ? changesByCommit.get(hash2) : null);
|
|
2620
2677
|
}
|
|
2621
2678
|
let decisionsPresent = false;
|
|
2622
2679
|
try {
|
|
@@ -2650,6 +2707,7 @@ async function computeAudit(rootDir, options = {}) {
|
|
|
2650
2707
|
var init_audit = __esm({
|
|
2651
2708
|
"src/audit/audit.ts"() {
|
|
2652
2709
|
"use strict";
|
|
2710
|
+
init_profile();
|
|
2653
2711
|
init_drift();
|
|
2654
2712
|
init_snapshot();
|
|
2655
2713
|
init_docs();
|
|
@@ -2685,6 +2743,9 @@ async function docState(root) {
|
|
|
2685
2743
|
return digest(docs);
|
|
2686
2744
|
}
|
|
2687
2745
|
async function stableAudit(root, checks, options = {}) {
|
|
2746
|
+
return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
|
|
2747
|
+
}
|
|
2748
|
+
async function readStableAudit(root, checks, options) {
|
|
2688
2749
|
const head = await getCurrentGitHash(root);
|
|
2689
2750
|
const before = await docState(root);
|
|
2690
2751
|
const report = await computeAudit(root, { ...options, checks });
|
|
@@ -2693,10 +2754,44 @@ async function stableAudit(root, checks, options = {}) {
|
|
|
2693
2754
|
}
|
|
2694
2755
|
return report;
|
|
2695
2756
|
}
|
|
2696
|
-
async function
|
|
2757
|
+
async function withRepairSession(root, options, run, cachedAudit) {
|
|
2758
|
+
const before = await Promise.all([getCurrentGitHash(root), docState(root), advisoryReviewInventory(root).then(digest)]);
|
|
2759
|
+
const audits = /* @__PURE__ */ new Map();
|
|
2760
|
+
const documents = /* @__PURE__ */ new Map();
|
|
2761
|
+
const history = /* @__PURE__ */ new Map();
|
|
2762
|
+
const assessments = /* @__PURE__ */ new Map();
|
|
2763
|
+
const memo = (cache, key, read) => {
|
|
2764
|
+
if (!cache.has(key)) cache.set(key, read());
|
|
2765
|
+
return cache.get(key);
|
|
2766
|
+
};
|
|
2767
|
+
const session = {
|
|
2768
|
+
audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
|
|
2769
|
+
if (cachedAudit && cachedAudit.headHash === before[0] && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
|
|
2770
|
+
const current = structuredClone(cachedAudit);
|
|
2771
|
+
const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, before[0])))).filter((r) => r !== null);
|
|
2772
|
+
delete current.advisoryReviews;
|
|
2773
|
+
if (reviews.length) current.advisoryReviews = reviews;
|
|
2774
|
+
return current;
|
|
2775
|
+
}
|
|
2776
|
+
return computeAudit(root, { ...options, checks });
|
|
2777
|
+
}),
|
|
2778
|
+
document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
|
|
2779
|
+
history: (head) => memo(history, head, () => getChangesWithStatus(root, head)),
|
|
2780
|
+
assessment: (dir, finding, head) => memo(assessments, digest([dir, finding, head]), () => assessAdvisory(dir, finding, head))
|
|
2781
|
+
};
|
|
2782
|
+
const result = await run({
|
|
2783
|
+
prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
|
|
2784
|
+
verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
|
|
2785
|
+
currentAudit: () => session.audit(ALL_CHECKS)
|
|
2786
|
+
});
|
|
2787
|
+
const after = await Promise.all([getCurrentGitHash(root), docState(root), advisoryReviewInventory(root).then(digest)]);
|
|
2788
|
+
if (digest(before) !== digest(after)) throw new Error("HEAD, context files or advisory review records changed during verification; retry against stable evidence.");
|
|
2789
|
+
return result;
|
|
2790
|
+
}
|
|
2791
|
+
async function prepareOriginal(rootDir, checks, options, session) {
|
|
2697
2792
|
const root = await fs12.realpath(rootDir);
|
|
2698
2793
|
const selected = z7.array(checkSchema).nonempty().parse(checks);
|
|
2699
|
-
const report = await stableAudit(root, selected, options);
|
|
2794
|
+
const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
|
|
2700
2795
|
if (!report) throw new Error("No context files found to prepare a repair.");
|
|
2701
2796
|
if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
|
|
2702
2797
|
const storedReport = reportSchema.parse(report);
|
|
@@ -2721,24 +2816,24 @@ async function loadRepairBaseline(rootDir, baselinePath) {
|
|
|
2721
2816
|
const original = stored.report;
|
|
2722
2817
|
return { root, relative, original };
|
|
2723
2818
|
}
|
|
2724
|
-
async function
|
|
2819
|
+
async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
2725
2820
|
const { root, relative, original } = await loadRepairBaseline(rootDir, baselinePath);
|
|
2726
2821
|
const diagnostics = [];
|
|
2727
2822
|
let reviewDigest;
|
|
2728
2823
|
let current = null;
|
|
2729
2824
|
try {
|
|
2730
|
-
reviewDigest = digest(await advisoryReviewInventory(root));
|
|
2731
|
-
current = await stableAudit(root, original.checksRun, options);
|
|
2825
|
+
if (!session) reviewDigest = digest(await advisoryReviewInventory(root));
|
|
2826
|
+
current = await (session ? session.audit(original.checksRun) : stableAudit(root, original.checksRun, options));
|
|
2732
2827
|
if (!current) diagnostics.push("No context files remain available to audit.");
|
|
2733
2828
|
else if (!current.gitAvailable) diagnostics.push("Git history is unavailable.");
|
|
2734
2829
|
for (const doc of original.docs) {
|
|
2735
2830
|
if (!allFindings(original).some((f) => f.anchor.doc === doc.path) || !current?.docs.some((d) => d.path === doc.path)) continue;
|
|
2736
|
-
const content2 = await readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024);
|
|
2831
|
+
const content2 = await (session ? session.document(doc.path) : readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024));
|
|
2737
2832
|
if (content2 === null || !content2.trim()) {
|
|
2738
2833
|
diagnostics.push("Original context file " + doc.path + " is empty or unreadable; losing its claims does not verify a repair.");
|
|
2739
2834
|
}
|
|
2740
2835
|
}
|
|
2741
|
-
if (await getChangesWithStatus(root, original.headHash) === null) {
|
|
2836
|
+
if (await (session ? session.history(original.headHash) : getChangesWithStatus(root, original.headHash)) === null) {
|
|
2742
2837
|
diagnostics.push("The original audit commit is unavailable; repair history cannot be verified.");
|
|
2743
2838
|
}
|
|
2744
2839
|
} catch (error) {
|
|
@@ -2763,7 +2858,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
|
|
|
2763
2858
|
if (!current.checksRun?.includes(finding.type) || skipped.length) {
|
|
2764
2859
|
return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
|
|
2765
2860
|
}
|
|
2766
|
-
const review = await assessAdvisory(root, now ?? finding, original.headHash);
|
|
2861
|
+
const review = await (session?.assessment ?? assessAdvisory)(root, now ?? finding, original.headHash);
|
|
2767
2862
|
if (review) {
|
|
2768
2863
|
if (review.status === "unverified") return { ...base, review, status: "unverified", reason: review.reason };
|
|
2769
2864
|
if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
|
|
@@ -2778,7 +2873,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
|
|
|
2778
2873
|
}
|
|
2779
2874
|
return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
|
|
2780
2875
|
}));
|
|
2781
|
-
try {
|
|
2876
|
+
if (!session) try {
|
|
2782
2877
|
if (reviewDigest !== digest(await advisoryReviewInventory(root)) || current?.headHash !== await getCurrentGitHash(root)) {
|
|
2783
2878
|
diagnostics.push("HEAD or advisory review records changed during verification; repeat against stable evidence.");
|
|
2784
2879
|
for (const finding of findings) {
|
|
@@ -2818,6 +2913,7 @@ var count2, reportSchema, baselineSchema, repairFindingSchema;
|
|
|
2818
2913
|
var init_repair = __esm({
|
|
2819
2914
|
"src/audit/repair.ts"() {
|
|
2820
2915
|
"use strict";
|
|
2916
|
+
init_profile();
|
|
2821
2917
|
init_audit();
|
|
2822
2918
|
init_docs();
|
|
2823
2919
|
init_snapshot();
|
|
@@ -2878,20 +2974,20 @@ import { promisify as promisify9 } from "util";
|
|
|
2878
2974
|
import fg5 from "fast-glob";
|
|
2879
2975
|
import { z as z8 } from "zod";
|
|
2880
2976
|
async function git3(root, ...args2) {
|
|
2881
|
-
return (await exec7("git", args2, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout;
|
|
2977
|
+
return profilePhase("automation.git", async () => (await exec7("git", args2, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
|
|
2882
2978
|
}
|
|
2883
2979
|
async function workspace(dir) {
|
|
2884
2980
|
const root = await fs13.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
|
|
2885
|
-
const gitDir = await
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
} catch {
|
|
2890
|
-
branch = "detached";
|
|
2891
|
-
}
|
|
2981
|
+
const [gitDir, branch] = await Promise.all([
|
|
2982
|
+
git3(root, "rev-parse", "--absolute-git-dir").then((value) => fs13.realpath(value.trim())),
|
|
2983
|
+
git3(root, "symbolic-ref", "--quiet", "HEAD").then((value) => value.trim(), () => "detached")
|
|
2984
|
+
]);
|
|
2892
2985
|
return { root, gitDir, branch, directory: ".mason/reports/automation/" + hash([root, gitDir, branch]).slice(0, 24) };
|
|
2893
2986
|
}
|
|
2894
2987
|
async function readInputs(root) {
|
|
2988
|
+
return profilePhase("automation.inputs", () => collectInputs(root));
|
|
2989
|
+
}
|
|
2990
|
+
async function collectInputs(root) {
|
|
2895
2991
|
const docPaths = await discoverDocPaths(root);
|
|
2896
2992
|
const [headText, docStatus, shallowPath, replacements] = await Promise.all([
|
|
2897
2993
|
git3(root, "rev-parse", "HEAD"),
|
|
@@ -2908,21 +3004,29 @@ async function readInputs(root) {
|
|
|
2908
3004
|
}
|
|
2909
3005
|
const docs = {};
|
|
2910
3006
|
const docContents = [];
|
|
3007
|
+
const parsedDocs = [];
|
|
2911
3008
|
const claims = [];
|
|
3009
|
+
const presence = /* @__PURE__ */ new Map();
|
|
3010
|
+
const exists = (file) => {
|
|
3011
|
+
if (!presence.has(file)) presence.set(file, pathExists(root, file));
|
|
3012
|
+
return presence.get(file);
|
|
3013
|
+
};
|
|
2912
3014
|
for (const file of docPaths) {
|
|
2913
3015
|
const text2 = await content(root, file);
|
|
2914
3016
|
docs[file] = text2 === null ? null : hash(text2);
|
|
2915
3017
|
docContents.push([file, text2]);
|
|
2916
|
-
|
|
3018
|
+
const parsed = { path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") };
|
|
3019
|
+
parsedDocs.push(parsed);
|
|
3020
|
+
for (const claim of parsed.claims.paths) {
|
|
2917
3021
|
const scope = pathClaimScope(file, claim);
|
|
2918
3022
|
if (scope?.candidates.some(gitMetadataPath)) continue;
|
|
2919
3023
|
for (const candidate of scope?.candidates ?? []) {
|
|
2920
3024
|
if (optionalMasonPath(candidate)) continue;
|
|
2921
|
-
|
|
3025
|
+
const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path20.posix.dirname(candidate))]);
|
|
3026
|
+
claims.push([candidate, filePresent, parentPresent]);
|
|
2922
3027
|
}
|
|
2923
3028
|
}
|
|
2924
3029
|
}
|
|
2925
|
-
const parsedDocs = docContents.map(([file, text2]) => ({ path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") }));
|
|
2926
3030
|
const combinedDocs = moduleDocumentation(parsedDocs);
|
|
2927
3031
|
const countClaims = parsedDocs.flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
|
|
2928
3032
|
const decisionDirectory = await storePath(root, ".mason/decisions");
|
|
@@ -2944,7 +3048,7 @@ async function readInputs(root) {
|
|
|
2944
3048
|
git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
|
|
2945
3049
|
git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
|
|
2946
3050
|
]) : ["", ""];
|
|
2947
|
-
const common = [
|
|
3051
|
+
const common = [5, engineVersion, head, shallow, replacements, docContents];
|
|
2948
3052
|
const keys = {
|
|
2949
3053
|
"deleted-reference": hash([common, claims, docStatus]),
|
|
2950
3054
|
"new-module": hash([common, modules]),
|
|
@@ -2969,21 +3073,29 @@ function checkCache(raw, inputs) {
|
|
|
2969
3073
|
reused.add(name);
|
|
2970
3074
|
return structuredClone(entries[name].result);
|
|
2971
3075
|
}
|
|
2972
|
-
const result = await CHECKS[name](ctx);
|
|
3076
|
+
const result = await profilePhase("check." + name, () => CHECKS[name](ctx));
|
|
2973
3077
|
ran.add(name);
|
|
2974
3078
|
if (!result.skipped.length) entries[name] = { key: inputs.keys[name], result };
|
|
2975
3079
|
else delete entries[name];
|
|
2976
3080
|
return result;
|
|
2977
3081
|
} };
|
|
2978
|
-
return {
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3082
|
+
return {
|
|
3083
|
+
options,
|
|
3084
|
+
ran,
|
|
3085
|
+
reused,
|
|
3086
|
+
diagnostic,
|
|
3087
|
+
has: (name) => entries[name]?.key === inputs.keys[name] && !entries[name].result.skipped.length,
|
|
3088
|
+
serialize: () => {
|
|
3089
|
+
const canonical = cacheSchema.shape.entries.parse(entries);
|
|
3090
|
+
return { version: 1, entries: canonical, digest: hash(canonical) };
|
|
3091
|
+
}
|
|
3092
|
+
};
|
|
2982
3093
|
}
|
|
2983
3094
|
var exec7, engineVersion, hash, content, cacheSchema;
|
|
2984
3095
|
var init_evidence = __esm({
|
|
2985
3096
|
"src/automation/evidence.ts"() {
|
|
2986
3097
|
"use strict";
|
|
3098
|
+
init_profile();
|
|
2987
3099
|
init_docs();
|
|
2988
3100
|
init_scope();
|
|
2989
3101
|
init_claims();
|
|
@@ -2996,7 +3108,7 @@ var init_evidence = __esm({
|
|
|
2996
3108
|
init_storage();
|
|
2997
3109
|
init_advisory_review();
|
|
2998
3110
|
exec7 = promisify9(execFile9);
|
|
2999
|
-
engineVersion = true ? "0.17.
|
|
3111
|
+
engineVersion = true ? "0.17.3" : "development";
|
|
3000
3112
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3001
3113
|
content = readAuditInput;
|
|
3002
3114
|
cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
|
|
@@ -3190,7 +3302,7 @@ var init_execution = __esm({
|
|
|
3190
3302
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3191
3303
|
import { z as z10 } from "zod";
|
|
3192
3304
|
async function observeReadOnlyTool(dir, event) {
|
|
3193
|
-
const ws = await workspace(dir);
|
|
3305
|
+
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
3194
3306
|
await withLock(ws.root, ws.directory, async () => {
|
|
3195
3307
|
const statePath = ws.directory + "/state.json";
|
|
3196
3308
|
const raw = await readStoreJson(ws.root, statePath);
|
|
@@ -3237,24 +3349,41 @@ async function loadState(ws) {
|
|
|
3237
3349
|
}
|
|
3238
3350
|
return state;
|
|
3239
3351
|
}
|
|
3240
|
-
async function
|
|
3352
|
+
async function matchingAnalysis(ws, state, inputs) {
|
|
3241
3353
|
const pointer = state.analysis;
|
|
3242
|
-
if (!pointer || pointer.fingerprint !== inputs.fingerprint
|
|
3243
|
-
|
|
3354
|
+
if (!pointer || pointer.fingerprint !== inputs.fingerprint) return { analysis: null };
|
|
3355
|
+
if (pointer.path !== ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json") {
|
|
3356
|
+
throw new Error("Invalid automation analysis cache path; inspect the retained state.");
|
|
3357
|
+
}
|
|
3358
|
+
const invalid = { analysis: null, diagnostic: "Discarded an invalid current-audit cache; audit preparation and original verification were repeated." };
|
|
3359
|
+
let raw;
|
|
3360
|
+
try {
|
|
3361
|
+
raw = await readStoreJson(ws.root, pointer.path);
|
|
3362
|
+
} catch (error) {
|
|
3363
|
+
const cause = error.cause;
|
|
3364
|
+
if (cause instanceof SyntaxError || cause instanceof Error && cause.message === "expected a JSON object, received null") return invalid;
|
|
3365
|
+
throw error;
|
|
3366
|
+
}
|
|
3367
|
+
if (raw === null) return { analysis: null };
|
|
3368
|
+
const parsed = sharedSchema.safeParse(raw);
|
|
3369
|
+
if (!parsed.success) return invalid;
|
|
3370
|
+
const stored = parsed.data;
|
|
3244
3371
|
const a = stored.analysis;
|
|
3245
3372
|
if (stored.digest !== hash(a) || a.id !== pointer.id || a.fingerprint !== inputs.fingerprint || a.completedAt !== pointer.completedAt || a.report.root !== ws.root || a.report.branch !== ws.branch || a.report.head !== inputs.head || hash(a.report.baselinePaths) !== hash(state.baselines.map((b) => b.path))) {
|
|
3246
|
-
|
|
3373
|
+
return invalid;
|
|
3247
3374
|
}
|
|
3248
3375
|
if (a.baselineDigest !== await baselineDigest(ws, state)) throw new Error("Repair baseline was modified; use the original baseline.");
|
|
3249
|
-
return a;
|
|
3376
|
+
return { analysis: a };
|
|
3250
3377
|
}
|
|
3251
3378
|
async function verifyInputs(ws, inputs) {
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3379
|
+
return profilePhase("automation.revalidate", async () => {
|
|
3380
|
+
const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
|
|
3381
|
+
if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
|
|
3382
|
+
throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
|
|
3383
|
+
}
|
|
3384
|
+
});
|
|
3256
3385
|
}
|
|
3257
|
-
async function computeAnalysis(ws, state, inputs, event) {
|
|
3386
|
+
async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
|
|
3258
3387
|
const baselines = [...state.baselines];
|
|
3259
3388
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3260
3389
|
const empty = {
|
|
@@ -3283,7 +3412,7 @@ async function computeAnalysis(ws, state, inputs, event) {
|
|
|
3283
3412
|
}
|
|
3284
3413
|
}
|
|
3285
3414
|
let cached = null;
|
|
3286
|
-
const diagnostics = [];
|
|
3415
|
+
const diagnostics = cacheDiagnostic ? [cacheDiagnostic] : [];
|
|
3287
3416
|
try {
|
|
3288
3417
|
cached = await readStoreJson(ws.root, ws.directory + "/cache.json");
|
|
3289
3418
|
} catch {
|
|
@@ -3291,19 +3420,26 @@ async function computeAnalysis(ws, state, inputs, event) {
|
|
|
3291
3420
|
}
|
|
3292
3421
|
const cache = checkCache(cached, inputs);
|
|
3293
3422
|
if (cache.diagnostic) diagnostics.push(cache.diagnostic);
|
|
3294
|
-
const
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3423
|
+
const cachedAudit = previousAudit?.gitAvailable && previousAudit.headHash === inputs.head && previousAudit.root === ws.root && !previousAudit.skippedChecks.length && ALL_CHECKS.every((name) => previousAudit.checksRun?.includes(name) && cache.has(name)) ? previousAudit : void 0;
|
|
3424
|
+
if (cachedAudit) for (const name of ALL_CHECKS) cache.reused.add(name);
|
|
3425
|
+
let currentAudit = null;
|
|
3426
|
+
const verifications = await withRepairSession(ws.root, cache.options, async (repairs) => {
|
|
3427
|
+
const saveBaseline = async () => {
|
|
3428
|
+
if (baselines.length >= 128) throw new Error("128 retained baselines need review; automatic capture stopped without discarding original evidence.");
|
|
3429
|
+
const prepared = await repairs.prepare();
|
|
3430
|
+
baselines.push({ path: prepared.baselinePath, at: now, event: event.event, fingerprint: inputs.fingerprint });
|
|
3431
|
+
};
|
|
3432
|
+
if (!baselines.length) await saveBaseline();
|
|
3433
|
+
const verifications2 = [];
|
|
3434
|
+
for (const baseline of baselines) verifications2.push(await repairs.verify(baseline.path));
|
|
3435
|
+
const known = new Set(verifications2.flatMap((v) => v.findings.map((f) => f.id)));
|
|
3436
|
+
if (verifications2.some((v) => v.newFindings.some((f) => !known.has(findingId(f))))) {
|
|
3437
|
+
await saveBaseline();
|
|
3438
|
+
verifications2.push(await repairs.verify(baselines.at(-1).path));
|
|
3439
|
+
}
|
|
3440
|
+
currentAudit = await repairs.currentAudit();
|
|
3441
|
+
return verifications2;
|
|
3442
|
+
}, cachedAudit);
|
|
3307
3443
|
const merged = /* @__PURE__ */ new Map();
|
|
3308
3444
|
for (const verification of verifications) {
|
|
3309
3445
|
for (const finding of verification.findings) {
|
|
@@ -3324,20 +3460,20 @@ async function computeAnalysis(ws, state, inputs, event) {
|
|
|
3324
3460
|
reportPath: "",
|
|
3325
3461
|
findings: [...merged.values()],
|
|
3326
3462
|
diagnostics: [...new Set(diagnostics)],
|
|
3327
|
-
checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [] },
|
|
3463
|
+
checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [], ...cachedAudit ? { auditReused: true } : {} },
|
|
3328
3464
|
counts,
|
|
3329
3465
|
capture: "unknown",
|
|
3330
3466
|
scope: SCOPE
|
|
3331
3467
|
};
|
|
3332
|
-
return { baselines, report, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
|
|
3468
|
+
return { baselines, report, currentAudit, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
|
|
3333
3469
|
}
|
|
3334
3470
|
async function analyze(ws, inputs, event, startedAt) {
|
|
3335
3471
|
return withLock(ws.root, ws.directory + "/analysis/" + inputs.fingerprint, async () => {
|
|
3336
3472
|
const before = await loadState(ws);
|
|
3337
|
-
const
|
|
3338
|
-
if (
|
|
3473
|
+
const { analysis: previous, diagnostic } = await matchingAnalysis(ws, before, inputs);
|
|
3474
|
+
if (previous && previous.completedAt >= startedAt) return { analysis: previous, shared: true };
|
|
3339
3475
|
const originalDigest = await baselineDigest(ws, before);
|
|
3340
|
-
const computed = await computeAnalysis(ws, before, inputs, event);
|
|
3476
|
+
const computed = await computeAnalysis(ws, before, inputs, event, previous?.currentAudit, diagnostic);
|
|
3341
3477
|
await verifyInputs(ws, inputs);
|
|
3342
3478
|
if (originalDigest !== await baselineDigest(ws, before)) throw new Error("Repair baseline was modified during automation; original evidence must be inspected.");
|
|
3343
3479
|
const id = randomUUID4();
|
|
@@ -3346,7 +3482,8 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
3346
3482
|
fingerprint: inputs.fingerprint,
|
|
3347
3483
|
completedAt: Date.now(),
|
|
3348
3484
|
baselineDigest: await baselineDigest(ws, { ...before, baselines: computed.baselines }),
|
|
3349
|
-
report: computed.report
|
|
3485
|
+
report: computed.report,
|
|
3486
|
+
currentAudit: computed.currentAudit
|
|
3350
3487
|
});
|
|
3351
3488
|
const analysisPath = ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json";
|
|
3352
3489
|
await withLock(ws.root, ws.directory, async () => {
|
|
@@ -3365,17 +3502,17 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
3365
3502
|
}
|
|
3366
3503
|
async function automate(dir, event) {
|
|
3367
3504
|
const startedAt = Date.now();
|
|
3368
|
-
const ws = await workspace(dir);
|
|
3505
|
+
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
3369
3506
|
return recordExecution(ws.root, ws.directory, event.event, async () => {
|
|
3370
3507
|
const inputs = await readInputs(ws.root);
|
|
3371
|
-
const result = await analyze(ws, inputs, event, startedAt);
|
|
3508
|
+
const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
|
|
3372
3509
|
if (result.shared) {
|
|
3373
3510
|
await verifyInputs(ws, inputs);
|
|
3374
3511
|
if (result.analysis.baselineDigest !== await baselineDigest(ws, await loadState(ws))) {
|
|
3375
3512
|
throw new Error("Repair baseline was modified during shared automation; original evidence must be inspected.");
|
|
3376
3513
|
}
|
|
3377
3514
|
}
|
|
3378
|
-
return withLock(ws.root, ws.directory, async () => {
|
|
3515
|
+
return profilePhase("automation.publish", () => withLock(ws.root, ws.directory, async () => {
|
|
3379
3516
|
const state = await loadState(ws);
|
|
3380
3517
|
if (state.analysis?.id !== result.analysis.id) throw new Error("Automation evidence changed during publication; retry against the current inputs.");
|
|
3381
3518
|
const report = structuredClone(result.analysis.report);
|
|
@@ -3438,11 +3575,11 @@ async function automate(dir, event) {
|
|
|
3438
3575
|
if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
|
|
3439
3576
|
await writeStoreJson(ws.root, ws.directory + "/state.json", state);
|
|
3440
3577
|
return { report, message: notify ? summarize(report) : null, continueOnce };
|
|
3441
|
-
});
|
|
3578
|
+
}));
|
|
3442
3579
|
});
|
|
3443
3580
|
}
|
|
3444
3581
|
async function automationStatus(dir) {
|
|
3445
|
-
const ws = await workspace(dir);
|
|
3582
|
+
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
3446
3583
|
const execution = await executionStatus(ws.root, ws.directory);
|
|
3447
3584
|
const notifications = await failureNotifications(ws.root, ws.directory);
|
|
3448
3585
|
const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
|
|
@@ -3476,6 +3613,7 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
|
|
|
3476
3613
|
var init_runtime = __esm({
|
|
3477
3614
|
"src/automation/runtime.ts"() {
|
|
3478
3615
|
"use strict";
|
|
3616
|
+
init_profile();
|
|
3479
3617
|
init_execution();
|
|
3480
3618
|
init_repair();
|
|
3481
3619
|
init_types();
|
|
@@ -3490,6 +3628,7 @@ var init_runtime = __esm({
|
|
|
3490
3628
|
fingerprint: z10.string(),
|
|
3491
3629
|
completedAt: z10.number(),
|
|
3492
3630
|
baselineDigest: z10.string(),
|
|
3631
|
+
currentAudit: reportSchema.nullable().optional(),
|
|
3493
3632
|
report: z10.object({
|
|
3494
3633
|
version: z10.literal(1),
|
|
3495
3634
|
status: z10.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
|
|
@@ -3503,7 +3642,8 @@ var init_runtime = __esm({
|
|
|
3503
3642
|
checks: z10.object({
|
|
3504
3643
|
ran: z10.array(z10.string()),
|
|
3505
3644
|
reused: z10.array(z10.string()),
|
|
3506
|
-
skipped: z10.array(z10.object({ check: z10.string(), reason: z10.string(), doc: z10.string().optional() }))
|
|
3645
|
+
skipped: z10.array(z10.object({ check: z10.string(), reason: z10.string(), doc: z10.string().optional() })),
|
|
3646
|
+
auditReused: z10.boolean().optional()
|
|
3507
3647
|
}),
|
|
3508
3648
|
counts: z10.object({ resolved: z10.number(), unresolved: z10.number(), "review-required": z10.number(), unverified: z10.number() }),
|
|
3509
3649
|
capture: z10.enum(["observed", "unknown"]),
|
|
@@ -3511,9 +3651,9 @@ var init_runtime = __esm({
|
|
|
3511
3651
|
})
|
|
3512
3652
|
});
|
|
3513
3653
|
sharedSchema = z10.object({ analysis: analysisSchema, digest: z10.string() });
|
|
3514
|
-
baselineDigest = async (ws, state) => hash(await Promise.all(
|
|
3654
|
+
baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
|
|
3515
3655
|
state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
|
|
3516
|
-
));
|
|
3656
|
+
)));
|
|
3517
3657
|
}
|
|
3518
3658
|
});
|
|
3519
3659
|
|
|
@@ -3650,7 +3790,7 @@ var init_launcher = __esm({
|
|
|
3650
3790
|
"use strict";
|
|
3651
3791
|
init_evidence();
|
|
3652
3792
|
init_model();
|
|
3653
|
-
executingVersion = () => true ? "0.17.
|
|
3793
|
+
executingVersion = () => true ? "0.17.3" : "development";
|
|
3654
3794
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
3655
3795
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
3656
3796
|
}
|
|
@@ -5491,6 +5631,7 @@ async function selectHost(root, explicit) {
|
|
|
5491
5631
|
throw new Error("Choose the assistant for setup with --host codex or --host claude.");
|
|
5492
5632
|
}
|
|
5493
5633
|
async function setupProject(dir, options = {}) {
|
|
5634
|
+
options.progress?.step("Checking project configuration");
|
|
5494
5635
|
const ws = await workspace(dir);
|
|
5495
5636
|
const host = await selectHost(ws.root, options.host);
|
|
5496
5637
|
return withLock(ws.root, ".mason/reports/setup-lock", async () => {
|
|
@@ -5519,11 +5660,14 @@ async function setupProject(dir, options = {}) {
|
|
|
5519
5660
|
const revision = !changed && previous ? previous.revision : randomUUID6();
|
|
5520
5661
|
const setup = existing ?? { version: 2, hosts: {} };
|
|
5521
5662
|
setup.hosts[host] = { configuredVersion: installed.version, revision, fingerprint, mcpFingerprint, instructions: instructions.map((e) => e.path) };
|
|
5663
|
+
options.progress?.step("Auditing documentation and preserving original findings");
|
|
5522
5664
|
const initial = await automate(ws.root, { event: "turn_start" });
|
|
5665
|
+
options.progress?.step("Reviewing project context and committed changes");
|
|
5523
5666
|
const findings = await inspectOnboarding(ws.root, options.base, options.evidence);
|
|
5524
5667
|
const receiptPath = ws.directory + "/setup-" + host + ".json";
|
|
5525
5668
|
const initialReportPath = previousReceipt?.initialReportPath ?? initial.report.reportPath;
|
|
5526
5669
|
const initialBaselinePaths = previousReceipt?.initialBaselinePaths ?? initial.report.baselinePaths;
|
|
5670
|
+
options.progress?.step(`Configuring ${host === "codex" ? "Codex" : "Claude Code"} integration`);
|
|
5527
5671
|
await writeStoreJson(ws.root, receiptPath, {
|
|
5528
5672
|
version: 1,
|
|
5529
5673
|
host,
|
|
@@ -5538,6 +5682,7 @@ async function setupProject(dir, options = {}) {
|
|
|
5538
5682
|
for (const edit of edits) if (await applyEdit(ws.root, edit)) changedFiles.push(edit.path);
|
|
5539
5683
|
if (await applyEdit(ws.root, { path: SETUP_PATH, before: setupBefore, after: JSON.stringify(setup, null, 2) + "\n" })) changedFiles.push(SETUP_PATH);
|
|
5540
5684
|
if (await applyEdit(ws.root, await completedHookOwnership(ws.root, hooks[0].path, configuredHook)) && !changedFiles.includes(ownership.path)) changedFiles.push(ownership.path);
|
|
5685
|
+
options.progress?.step("Checking configuration and retained findings");
|
|
5541
5686
|
const checked = await automate(ws.root, { event: "turn_start" });
|
|
5542
5687
|
const configured = await inspectHostConfig(ws.root, host, void 0);
|
|
5543
5688
|
await writeStoreJson(ws.root, receiptPath, {
|
|
@@ -5660,9 +5805,11 @@ async function configurePath(bin, previous, save, options = {}) {
|
|
|
5660
5805
|
const changes = [...previous];
|
|
5661
5806
|
const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
|
|
5662
5807
|
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path26.posix.isAbsolute(entry) && path26.posix.resolve(entry) === path26.posix.resolve(bin));
|
|
5663
|
-
const
|
|
5808
|
+
const setupHint = "To connect a project, run one of these inside its directory:\n Codex: mason setup --host codex\n Claude Code: mason setup --host claude";
|
|
5809
|
+
const message2 = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
|
|
5664
5810
|
const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
|
|
5665
|
-
${alreadyOnPath ?
|
|
5811
|
+
${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
5812
|
+
`}${setupHint}` });
|
|
5666
5813
|
if (env.MASON_NO_MODIFY_PATH === "1") return manual("Automatic PATH setup is disabled (MASON_NO_MODIFY_PATH=1).");
|
|
5667
5814
|
if (/[\r\n\0]/.test(bin) || bin.includes(platform === "win32" ? ";" : ":")) return manual("This installation path cannot be represented safely in PATH.");
|
|
5668
5815
|
try {
|
|
@@ -5679,11 +5826,11 @@ ${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --h
|
|
|
5679
5826
|
}
|
|
5680
5827
|
const shell = path26.basename(env.SHELL ?? os3.userInfo().shell ?? "");
|
|
5681
5828
|
const userHome = options.userHome ?? os3.homedir();
|
|
5682
|
-
let
|
|
5829
|
+
let profiles2;
|
|
5683
5830
|
if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
|
|
5684
|
-
if (env.MASON_PROFILE)
|
|
5685
|
-
else if (shell === "zsh")
|
|
5686
|
-
else if (shell === "sh")
|
|
5831
|
+
if (env.MASON_PROFILE) profiles2 = [path26.resolve(env.MASON_PROFILE)];
|
|
5832
|
+
else if (shell === "zsh") profiles2 = [path26.join(env.ZDOTDIR || userHome, ".zshrc")];
|
|
5833
|
+
else if (shell === "sh") profiles2 = [path26.join(userHome, ".profile")];
|
|
5687
5834
|
else {
|
|
5688
5835
|
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path26.join(userHome, name));
|
|
5689
5836
|
let login = candidates[2];
|
|
@@ -5694,10 +5841,10 @@ ${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --h
|
|
|
5694
5841
|
login = file;
|
|
5695
5842
|
break;
|
|
5696
5843
|
}
|
|
5697
|
-
|
|
5844
|
+
profiles2 = [path26.join(userHome, ".bashrc"), login];
|
|
5698
5845
|
}
|
|
5699
5846
|
const marker = `# >>> mason PATH ${createHash5("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
5700
|
-
for (const profile of
|
|
5847
|
+
for (const profile of profiles2) {
|
|
5701
5848
|
const stat = await fs21.lstat(profile).catch((error) => {
|
|
5702
5849
|
if (error.code === "ENOENT") return null;
|
|
5703
5850
|
throw error;
|
|
@@ -5853,7 +6000,8 @@ async function replace(file, content2, mode = 384) {
|
|
|
5853
6000
|
await fs22.rm(temp, { force: true });
|
|
5854
6001
|
}
|
|
5855
6002
|
}
|
|
5856
|
-
async function installStandalone(source) {
|
|
6003
|
+
async function installStandalone(source, progress2) {
|
|
6004
|
+
progress2?.step("Checking installation files and location");
|
|
5857
6005
|
const bundle = await verifyBundle(source);
|
|
5858
6006
|
const selectedHome = path27.resolve(defaultHome());
|
|
5859
6007
|
await fs22.mkdir(selectedHome, { recursive: true });
|
|
@@ -5914,6 +6062,7 @@ exec ${shQuote(path27.join(destination, "node"))} ${shQuote(path27.join(destinat
|
|
|
5914
6062
|
if (actual !== null && actual !== expected) throw new Error("Retired launcher was edited; retained: " + name);
|
|
5915
6063
|
};
|
|
5916
6064
|
for (const [name, expected] of retired) await checkRetired(name, expected);
|
|
6065
|
+
progress2?.step(`Installing Mason ${bundle.manifest.version}`);
|
|
5917
6066
|
const receipt = { format: 1, home, bin, current: id, version: bundle.manifest.version, launchers, pathChanges: previous?.pathChanges ?? [] };
|
|
5918
6067
|
await writeStoreJson(home, "install.json", { ...receipt, previousLaunchers: previous?.previousLaunchers ?? previous?.launchers });
|
|
5919
6068
|
const stage = await storePath(home, "versions/.install-" + randomUUID8(), true);
|
|
@@ -5940,11 +6089,12 @@ exec ${shQuote(path27.join(destination, "node"))} ${shQuote(path27.join(destinat
|
|
|
5940
6089
|
} finally {
|
|
5941
6090
|
await fs22.rm(stage, { recursive: true, force: true });
|
|
5942
6091
|
}
|
|
6092
|
+
progress2?.step("Checking terminal command availability");
|
|
5943
6093
|
const pathResult = await configurePath(bin, receipt.pathChanges, async (changes) => {
|
|
5944
6094
|
receipt.pathChanges = changes;
|
|
5945
6095
|
await writeStoreJson(home, "install.json", receipt);
|
|
5946
6096
|
});
|
|
5947
|
-
return { home, bin, version: bundle.manifest.version, path: pathResult };
|
|
6097
|
+
return { home, bin, version: bundle.manifest.version, previousVersion: previous?.version, path: pathResult };
|
|
5948
6098
|
});
|
|
5949
6099
|
}
|
|
5950
6100
|
async function currentInstallation() {
|
|
@@ -5956,12 +6106,14 @@ async function currentInstallation() {
|
|
|
5956
6106
|
if (record.home !== await fs22.realpath(home) || path27.dirname(bundle) !== path27.join(home, "versions")) throw new Error("Invalid standalone installation receipt.");
|
|
5957
6107
|
return { home, bundle, record };
|
|
5958
6108
|
}
|
|
5959
|
-
async function upgradeStandalone(version) {
|
|
6109
|
+
async function upgradeStandalone(version, progress2) {
|
|
5960
6110
|
if (version && !/^v?\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(version)) throw new Error("Expected a release version such as 0.14.0.");
|
|
6111
|
+
progress2?.step("Checking current installation");
|
|
5961
6112
|
const { home, bundle, record } = await currentInstallation();
|
|
5962
6113
|
await verifyBundle(bundle);
|
|
5963
6114
|
const windows = process.platform === "win32";
|
|
5964
6115
|
const script = path27.join(bundle, windows ? "install.ps1" : "install.sh");
|
|
6116
|
+
progress2?.stop();
|
|
5965
6117
|
return new Promise((resolve, reject) => {
|
|
5966
6118
|
const child = spawn(
|
|
5967
6119
|
windows ? "powershell.exe" : "sh",
|
|
@@ -6031,6 +6183,58 @@ init_adapters();
|
|
|
6031
6183
|
init_install();
|
|
6032
6184
|
init_store();
|
|
6033
6185
|
import { parseArgs } from "util";
|
|
6186
|
+
|
|
6187
|
+
// src/utils/progress.ts
|
|
6188
|
+
function createProgress(options = {}) {
|
|
6189
|
+
const write = options.write ?? ((text2) => {
|
|
6190
|
+
process.stderr.write(text2);
|
|
6191
|
+
});
|
|
6192
|
+
const interactive = options.interactive ?? (!!process.stderr.isTTY && process.env.TERM !== "dumb" && !process.env.CI);
|
|
6193
|
+
const columns = options.columns ?? (() => process.stderr.columns || 80);
|
|
6194
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
6195
|
+
let message2 = "", started = 0, frame = 0;
|
|
6196
|
+
let timer;
|
|
6197
|
+
const elapsed = () => {
|
|
6198
|
+
const seconds = Math.floor((Date.now() - started) / 1e3);
|
|
6199
|
+
return seconds ? ` (${seconds}s)` : "";
|
|
6200
|
+
};
|
|
6201
|
+
const render = (prefix) => {
|
|
6202
|
+
const text2 = `${prefix} ${message2}${elapsed()}`;
|
|
6203
|
+
const width = Math.max(1, columns() - 1);
|
|
6204
|
+
write("\r\x1B[2K" + (text2.length > width ? text2.slice(0, width - 1) + "\u2026" : text2));
|
|
6205
|
+
};
|
|
6206
|
+
const stop = (success = true) => {
|
|
6207
|
+
if (timer) clearInterval(timer);
|
|
6208
|
+
timer = void 0;
|
|
6209
|
+
if (message2 && interactive) {
|
|
6210
|
+
render(success ? "\u2713" : "\u2717");
|
|
6211
|
+
write("\n");
|
|
6212
|
+
}
|
|
6213
|
+
message2 = "";
|
|
6214
|
+
};
|
|
6215
|
+
return {
|
|
6216
|
+
step(next) {
|
|
6217
|
+
stop();
|
|
6218
|
+
message2 = next.replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
|
|
6219
|
+
started = Date.now();
|
|
6220
|
+
frame = 0;
|
|
6221
|
+
if (!interactive) {
|
|
6222
|
+
write(`Mason: ${message2}\u2026
|
|
6223
|
+
`);
|
|
6224
|
+
return;
|
|
6225
|
+
}
|
|
6226
|
+
render(frames[frame]);
|
|
6227
|
+
timer = setInterval(() => {
|
|
6228
|
+
render(frames[++frame % frames.length]);
|
|
6229
|
+
}, 100);
|
|
6230
|
+
timer.unref();
|
|
6231
|
+
},
|
|
6232
|
+
stop
|
|
6233
|
+
};
|
|
6234
|
+
}
|
|
6235
|
+
|
|
6236
|
+
// src/automation/cli.ts
|
|
6237
|
+
init_profile();
|
|
6034
6238
|
var TEARDOWN_USAGE = `Usage: mason teardown [options]
|
|
6035
6239
|
|
|
6036
6240
|
Disconnect Mason from this Git project. Defaults to all project hosts.
|
|
@@ -6058,6 +6262,7 @@ var USAGE = `Usage: mason-auto <setup|teardown|install|config|status|check|hook>
|
|
|
6058
6262
|
--command <prefix> Installed executable prefix for install/config
|
|
6059
6263
|
--dry-run Preview teardown without writing
|
|
6060
6264
|
--json Machine-readable output (status also uses JSON when piped)
|
|
6265
|
+
--profile Local phase timings on stderr for check/hook; no file contents
|
|
6061
6266
|
|
|
6062
6267
|
check exits 0 for verified checks, 1 for issues, 2 for incomplete/unavailable.
|
|
6063
6268
|
teardown exits 0 for complete/no-op, 2 when cleanup needs attention (also in dry runs).
|
|
@@ -6069,7 +6274,8 @@ var parseCli = (argv) => parseArgs({ args: argv, allowPositionals: true, options
|
|
|
6069
6274
|
command: { type: "string" },
|
|
6070
6275
|
json: { type: "boolean" },
|
|
6071
6276
|
help: { type: "boolean", short: "h" },
|
|
6072
|
-
"dry-run": { type: "boolean" }
|
|
6277
|
+
"dry-run": { type: "boolean" },
|
|
6278
|
+
profile: { type: "boolean" }
|
|
6073
6279
|
} });
|
|
6074
6280
|
function isHookCommand(argv) {
|
|
6075
6281
|
try {
|
|
@@ -6079,11 +6285,13 @@ function isHookCommand(argv) {
|
|
|
6079
6285
|
return false;
|
|
6080
6286
|
}
|
|
6081
6287
|
}
|
|
6082
|
-
|
|
6288
|
+
var defaultIo = {
|
|
6083
6289
|
out: (s) => process.stdout.write(s + "\n"),
|
|
6084
6290
|
err: (s) => process.stderr.write(s + "\n")
|
|
6085
|
-
}
|
|
6291
|
+
};
|
|
6292
|
+
async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {}) {
|
|
6086
6293
|
let action = "";
|
|
6294
|
+
let progress2;
|
|
6087
6295
|
try {
|
|
6088
6296
|
const { values, positionals } = parseCli(argv);
|
|
6089
6297
|
if (values.help || !positionals.length) {
|
|
@@ -6092,6 +6300,8 @@ async function runAutomationCli(argv, stdin = "", io = {
|
|
|
6092
6300
|
}
|
|
6093
6301
|
if (positionals.length !== 1) throw new Error("Expected one command.");
|
|
6094
6302
|
[action] = positionals;
|
|
6303
|
+
if (values.profile && !["check", "hook"].includes(action)) throw new Error("--profile applies only to check/hook.");
|
|
6304
|
+
const measured = (run) => values.profile ? withProfile(run, io.err) : run();
|
|
6095
6305
|
const dir = values.dir ?? process.cwd();
|
|
6096
6306
|
if (values["dry-run"] !== void 0 && action !== "teardown") throw new Error("--dry-run applies only to teardown.");
|
|
6097
6307
|
if (action === "teardown") {
|
|
@@ -6104,14 +6314,20 @@ async function runAutomationCli(argv, stdin = "", io = {
|
|
|
6104
6314
|
if (action === "hook") {
|
|
6105
6315
|
const host = hostSchema.parse(values.host);
|
|
6106
6316
|
if (options.managedHost && options.managedHost !== host) throw new Error("Managed hook --host does not match its launcher.");
|
|
6107
|
-
const output = await runAutomationHook(host, stdin, { managed: !!options.managedHost });
|
|
6317
|
+
const output = await measured(() => runAutomationHook(host, stdin, { managed: !!options.managedHost }));
|
|
6108
6318
|
if (output) io.out(JSON.stringify(output));
|
|
6109
6319
|
return 0;
|
|
6110
6320
|
}
|
|
6111
6321
|
if (action === "setup") {
|
|
6112
6322
|
if (values.command) throw new Error("--command applies to install/config, not managed setup.");
|
|
6113
6323
|
const { setupProject: setupProject2, summarizeSetup: summarizeSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
6114
|
-
const
|
|
6324
|
+
const host = values.host ? hostSchema.parse(values.host) : void 0;
|
|
6325
|
+
if (!values.json) progress2 = createProgress(io === defaultIo ? {} : {
|
|
6326
|
+
write: (text2) => io.err(text2.replace(/\n$/, "")),
|
|
6327
|
+
interactive: false
|
|
6328
|
+
});
|
|
6329
|
+
const result = await setupProject2(dir, { host, progress: progress2 });
|
|
6330
|
+
progress2?.stop();
|
|
6115
6331
|
io.out(values.json ? JSON.stringify(result, null, 2) : summarizeSetup2(result));
|
|
6116
6332
|
return 0;
|
|
6117
6333
|
}
|
|
@@ -6129,10 +6345,11 @@ async function runAutomationCli(argv, stdin = "", io = {
|
|
|
6129
6345
|
return 0;
|
|
6130
6346
|
}
|
|
6131
6347
|
if (action !== "check") throw new Error("Unknown automation command: " + action);
|
|
6132
|
-
const { report } = await automate(dir, { event: "task_end" });
|
|
6348
|
+
const { report } = await measured(() => automate(dir, { event: "task_end" }));
|
|
6133
6349
|
io.out(values.json ? JSON.stringify(report, null, 2) : summarize(report));
|
|
6134
6350
|
return report.status === "verified" ? 0 : report.status === "issues-remain" ? 1 : 2;
|
|
6135
6351
|
} catch (error) {
|
|
6352
|
+
progress2?.stop(false);
|
|
6136
6353
|
const message2 = action === "setup" ? "Mason setup incomplete: " + automationFailure(error).message + ". Rerun the same setup command to resume; retained audit evidence is preserved." : action === "teardown" ? "Mason teardown incomplete: " + automationFailure(error).message + ". Inspect the reported input, then rerun teardown; knowledge and repair evidence are retained." : failureMessage(error);
|
|
6137
6354
|
if (action === "hook" || isHookCommand(argv)) {
|
|
6138
6355
|
io.out(JSON.stringify({ systemMessage: message2 }));
|
|
@@ -6149,7 +6366,7 @@ var inputArgs = process.argv.slice(2);
|
|
|
6149
6366
|
var managedLaunch = inputArgs[0] === "--setup-host";
|
|
6150
6367
|
var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
|
|
6151
6368
|
var [command, ...args] = inputArgs;
|
|
6152
|
-
var usage = `Mason ${"0.17.
|
|
6369
|
+
var usage = `Mason ${"0.17.3"}
|
|
6153
6370
|
|
|
6154
6371
|
Usage: mason <command> [options]
|
|
6155
6372
|
|
|
@@ -6166,6 +6383,7 @@ Usage: mason <command> [options]
|
|
|
6166
6383
|
|
|
6167
6384
|
Use mason <command> --help for options. npm users can keep using mason-auto,
|
|
6168
6385
|
mason-audit, mason-review, mason-drift, mason-hook and mason-mcp.`;
|
|
6386
|
+
var progress;
|
|
6169
6387
|
try {
|
|
6170
6388
|
if (managedLaunch) {
|
|
6171
6389
|
if (!setupHost || !["codex", "claude"].includes(setupHost) || !["mcp", "auto"].includes(command)) throw new Error("Invalid setup invocation.");
|
|
@@ -6174,23 +6392,29 @@ try {
|
|
|
6174
6392
|
await prepareLaunch2(setupHost, { allowInactive: command === "mcp" });
|
|
6175
6393
|
}
|
|
6176
6394
|
}
|
|
6177
|
-
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.17.
|
|
6395
|
+
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.17.3" }));
|
|
6178
6396
|
else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
|
|
6179
|
-
else if (["--version", "-v"].includes(command)) console.log("0.17.
|
|
6397
|
+
else if (["--version", "-v"].includes(command)) console.log("0.17.3");
|
|
6180
6398
|
else if (command === "internal-install") {
|
|
6181
6399
|
if (args.length) throw new Error("internal-install takes no arguments.");
|
|
6182
6400
|
const { installStandalone: installStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
6183
6401
|
const bundleRoot = fileURLToPath2(new URL("../..", import.meta.url));
|
|
6184
|
-
|
|
6185
|
-
|
|
6402
|
+
progress = createProgress();
|
|
6403
|
+
const installed = await installStandalone2(bundleRoot, progress);
|
|
6404
|
+
progress.stop();
|
|
6405
|
+
const outcome = installed.previousVersion && installed.previousVersion !== installed.version ? `Updated Mason ${installed.previousVersion} \u2192 ${installed.version}` : `Installed Mason ${installed.version}`;
|
|
6406
|
+
console.log(`${outcome} in ${installed.home}.
|
|
6186
6407
|
${installed.path.message}`);
|
|
6187
6408
|
} else if (command === "upgrade" || command === "uninstall") {
|
|
6188
6409
|
if (args.includes("--help")) console.log(command === "upgrade" ? "Usage: mason upgrade [version]. Configured projects use the upgraded command on their next launch. Restart running assistants." : "Usage: mason uninstall. Removes the standalone user installation; project configuration and knowledge are retained. Run mason teardown in each project first to disconnect its integrations.");
|
|
6189
6410
|
else {
|
|
6190
6411
|
const { upgradeStandalone: upgradeStandalone2, uninstallStandalone: uninstallStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
6191
6412
|
if (args.length > (command === "upgrade" ? 1 : 0)) throw new Error("Unexpected arguments.");
|
|
6192
|
-
if (command === "upgrade")
|
|
6193
|
-
|
|
6413
|
+
if (command === "upgrade") {
|
|
6414
|
+
progress = createProgress();
|
|
6415
|
+
process.exitCode = await upgradeStandalone2(args[0], progress);
|
|
6416
|
+
if (process.exitCode === 0) console.log("Restart running assistants to use this installation. Existing project integrations use mason from PATH.");
|
|
6417
|
+
} else console.log((process.platform === "win32" ? "Finishing installation cleanup after runtime exit: " : "Removed standalone installation: ") + await uninstallStandalone2() + ". Project configuration and knowledge were retained. Reinstall Mason to use its integrations.");
|
|
6194
6418
|
}
|
|
6195
6419
|
} else if (["setup", "teardown", "status", "check", "auto"].includes(command)) {
|
|
6196
6420
|
const forwarded = command === "auto" ? args : [command, ...args];
|
|
@@ -6211,6 +6435,7 @@ ${installed.path.message}`);
|
|
|
6211
6435
|
await import(binary.href);
|
|
6212
6436
|
} else throw new Error("Unknown command: " + command + ". Run mason --help.");
|
|
6213
6437
|
} catch (error) {
|
|
6438
|
+
progress?.stop(false);
|
|
6214
6439
|
const message2 = "Mason: " + (error instanceof Error ? error.message : String(error));
|
|
6215
6440
|
if (command === "auto" && isHookCommand(args)) {
|
|
6216
6441
|
console.log(JSON.stringify({ systemMessage: message2 + " Verification was not established." }));
|