mason-context 0.17.2 → 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/dist/mason-mcp.js CHANGED
@@ -9,6 +9,28 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/utils/profile.ts
13
+ import { AsyncLocalStorage } from "async_hooks";
14
+ async function profilePhase(label, run) {
15
+ const phases = profiles.getStore();
16
+ if (!phases) return run();
17
+ const start = performance.now();
18
+ try {
19
+ return await run();
20
+ } finally {
21
+ const timing = phases[label] ??= { calls: 0, totalMs: 0 };
22
+ timing.calls++;
23
+ timing.totalMs += performance.now() - start;
24
+ }
25
+ }
26
+ var profiles;
27
+ var init_profile = __esm({
28
+ "src/utils/profile.ts"() {
29
+ "use strict";
30
+ profiles = new AsyncLocalStorage();
31
+ }
32
+ });
33
+
12
34
  // src/audit/commands.ts
13
35
  import path from "path";
14
36
  function splitCommands(line) {
@@ -854,16 +876,27 @@ import path6 from "path";
854
876
  import { execFile as execFile4 } from "child_process";
855
877
  import { promisify as promisify4 } from "util";
856
878
  import fg3 from "fast-glob";
857
- async function isDirty(resolvedRoot, relPath) {
879
+ async function dirtyDocs(resolvedRoot, files) {
880
+ if (!files.length) return /* @__PURE__ */ new Set();
858
881
  try {
859
- const { stdout } = await exec3(
860
- "git",
861
- ["status", "--porcelain", "--", `:(literal)${relPath}`],
862
- { cwd: resolvedRoot }
863
- );
864
- return stdout.trim().length > 0;
882
+ const dirty = /* @__PURE__ */ new Set();
883
+ for (let offset = 0; offset < files.length; offset += 256) {
884
+ const { stdout } = await exec3(
885
+ "git",
886
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...files.slice(offset, offset + 256).map((file) => `:(literal)${file}`)],
887
+ { cwd: resolvedRoot, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }
888
+ );
889
+ const entries = stdout.split("\0");
890
+ for (let i = 0; i < entries.length; i++) {
891
+ if (!entries[i]) continue;
892
+ const status = entries[i].slice(0, 2);
893
+ dirty.add(entries[i].slice(3));
894
+ if (/[RC]/.test(status) && entries[i + 1]) dirty.add(entries[++i]);
895
+ }
896
+ }
897
+ return dirty;
865
898
  } catch {
866
- return true;
899
+ return new Set(files);
867
900
  }
868
901
  }
869
902
  async function discoverDocPaths(root) {
@@ -944,21 +977,30 @@ function documentScope(file) {
944
977
  return path6.posix.basename(parent).toLowerCase() === ".claude" ? path6.posix.dirname(parent) : parent;
945
978
  }
946
979
  async function discoverDocs(resolvedRoot) {
980
+ return profilePhase("audit.documents", () => readDocs(resolvedRoot));
981
+ }
982
+ async function readDocs(resolvedRoot) {
947
983
  const docs = [];
948
- for (const candidate of await discoverDocPaths(resolvedRoot)) {
949
- const content2 = await readAuditInput(resolvedRoot, candidate);
950
- if (content2 === null) throw new Error("Document disappeared during discovery: " + candidate);
951
- const [lastCommit, dirty] = await Promise.all([lastCommitOf(resolvedRoot, candidate), isDirty(resolvedRoot, candidate)]);
952
- docs.push({
953
- path: candidate,
954
- content: content2,
955
- scope: documentScope(candidate),
956
- kind: /^readme\.md$/i.test(path6.posix.basename(candidate)) ? "readme" : "instructions",
957
- lineCount: content2.split("\n").length,
958
- lastCommit,
959
- dirty,
960
- claims: extractClaims(content2)
961
- });
984
+ const files = await discoverDocPaths(resolvedRoot);
985
+ const dirty = await dirtyDocs(resolvedRoot, files);
986
+ for (let offset = 0; offset < files.length; offset += 4) {
987
+ docs.push(...await Promise.all(files.slice(offset, offset + 4).map(async (candidate) => {
988
+ const [content2, lastCommit] = await Promise.all([
989
+ readAuditInput(resolvedRoot, candidate),
990
+ profilePhase("audit.document-history", () => lastCommitOf(resolvedRoot, candidate))
991
+ ]);
992
+ if (content2 === null) throw new Error("Document disappeared during discovery: " + candidate);
993
+ return {
994
+ path: candidate,
995
+ content: content2,
996
+ scope: documentScope(candidate),
997
+ kind: /^readme\.md$/i.test(path6.posix.basename(candidate)) ? "readme" : "instructions",
998
+ lineCount: content2.split("\n").length,
999
+ lastCommit,
1000
+ dirty: dirty.has(candidate),
1001
+ claims: extractClaims(content2)
1002
+ };
1003
+ })));
962
1004
  }
963
1005
  return docs;
964
1006
  }
@@ -966,6 +1008,7 @@ var exec3, DOC_CANDIDATES;
966
1008
  var init_docs = __esm({
967
1009
  "src/audit/docs.ts"() {
968
1010
  "use strict";
1011
+ init_profile();
969
1012
  init_claims();
970
1013
  init_git();
971
1014
  init_inputs();
@@ -3180,6 +3223,9 @@ var init_advisory_review = __esm({
3180
3223
  import fs11 from "fs/promises";
3181
3224
  import path18 from "path";
3182
3225
  async function computeAudit(rootDir, options = {}) {
3226
+ return profilePhase("audit.current", () => auditCurrent(rootDir, options));
3227
+ }
3228
+ async function auditCurrent(rootDir, options) {
3183
3229
  const resolvedRoot = path18.resolve(rootDir);
3184
3230
  const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
3185
3231
  if (docs.length === 0) return null;
@@ -3206,11 +3252,11 @@ async function computeAudit(rootDir, options = {}) {
3206
3252
  };
3207
3253
  if (!report.gitAvailable) return report;
3208
3254
  const changesSinceDoc = /* @__PURE__ */ new Map();
3255
+ const changesByCommit = /* @__PURE__ */ new Map();
3209
3256
  for (const doc of docs) {
3210
- changesSinceDoc.set(
3211
- doc.path,
3212
- doc.lastCommit ? await getChangesWithStatus(resolvedRoot, doc.lastCommit.hash) : null
3213
- );
3257
+ const hash2 = doc.lastCommit?.hash;
3258
+ if (hash2 && !changesByCommit.has(hash2)) changesByCommit.set(hash2, await getChangesWithStatus(resolvedRoot, hash2));
3259
+ changesSinceDoc.set(doc.path, hash2 ? changesByCommit.get(hash2) : null);
3214
3260
  }
3215
3261
  let decisionsPresent = false;
3216
3262
  try {
@@ -3244,6 +3290,7 @@ async function computeAudit(rootDir, options = {}) {
3244
3290
  var init_audit = __esm({
3245
3291
  "src/audit/audit.ts"() {
3246
3292
  "use strict";
3293
+ init_profile();
3247
3294
  init_drift();
3248
3295
  init_snapshot();
3249
3296
  init_docs();
@@ -3263,7 +3310,9 @@ __export(repair_exports, {
3263
3310
  prepareRepair: () => prepareRepair,
3264
3311
  repairExitCode: () => repairExitCode,
3265
3312
  repairFindingSchema: () => repairFindingSchema,
3266
- verifyRepair: () => verifyRepair
3313
+ reportSchema: () => reportSchema,
3314
+ verifyRepair: () => verifyRepair,
3315
+ withRepairSession: () => withRepairSession
3267
3316
  });
3268
3317
  import fs12 from "fs/promises";
3269
3318
  import path19 from "path";
@@ -3290,6 +3339,9 @@ async function docState(root) {
3290
3339
  return digest(docs);
3291
3340
  }
3292
3341
  async function stableAudit(root, checks, options = {}) {
3342
+ return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
3343
+ }
3344
+ async function readStableAudit(root, checks, options) {
3293
3345
  const head = await getCurrentGitHash(root);
3294
3346
  const before = await docState(root);
3295
3347
  const report = await computeAudit(root, { ...options, checks });
@@ -3299,9 +3351,46 @@ async function stableAudit(root, checks, options = {}) {
3299
3351
  return report;
3300
3352
  }
3301
3353
  async function prepareRepair(rootDir, checks = ALL_CHECKS, options = {}) {
3354
+ return prepareOriginal(rootDir, checks, options);
3355
+ }
3356
+ async function withRepairSession(root, options, run, cachedAudit) {
3357
+ const before = await Promise.all([getCurrentGitHash(root), docState(root), advisoryReviewInventory(root).then(digest)]);
3358
+ const audits = /* @__PURE__ */ new Map();
3359
+ const documents = /* @__PURE__ */ new Map();
3360
+ const history = /* @__PURE__ */ new Map();
3361
+ const assessments = /* @__PURE__ */ new Map();
3362
+ const memo = (cache, key, read) => {
3363
+ if (!cache.has(key)) cache.set(key, read());
3364
+ return cache.get(key);
3365
+ };
3366
+ const session = {
3367
+ audit: (checks) => memo(audits, [...checks].sort().join(","), async () => {
3368
+ if (cachedAudit && cachedAudit.headHash === before[0] && cachedAudit.root === root && digest([...checks].sort()) === digest([...cachedAudit.checksRun ?? []].sort())) {
3369
+ const current = structuredClone(cachedAudit);
3370
+ const reviews = (await Promise.all(allFindings(current).map((f) => assessAdvisory(root, f, before[0])))).filter((r) => r !== null);
3371
+ delete current.advisoryReviews;
3372
+ if (reviews.length) current.advisoryReviews = reviews;
3373
+ return current;
3374
+ }
3375
+ return computeAudit(root, { ...options, checks });
3376
+ }),
3377
+ document: (file) => memo(documents, file, async () => readBoundedFile(await storePath(root, file), 10 * 1024 * 1024)),
3378
+ history: (head) => memo(history, head, () => getChangesWithStatus(root, head)),
3379
+ assessment: (dir, finding, head) => memo(assessments, digest([dir, finding, head]), () => assessAdvisory(dir, finding, head))
3380
+ };
3381
+ const result = await run({
3382
+ prepare: () => prepareOriginal(root, ALL_CHECKS, options, session),
3383
+ verify: (baseline) => profilePhase("repair.verify", () => verifyOriginal(root, baseline, options, session)),
3384
+ currentAudit: () => session.audit(ALL_CHECKS)
3385
+ });
3386
+ const after = await Promise.all([getCurrentGitHash(root), docState(root), advisoryReviewInventory(root).then(digest)]);
3387
+ if (digest(before) !== digest(after)) throw new Error("HEAD, context files or advisory review records changed during verification; retry against stable evidence.");
3388
+ return result;
3389
+ }
3390
+ async function prepareOriginal(rootDir, checks, options, session) {
3302
3391
  const root = await fs12.realpath(rootDir);
3303
3392
  const selected = z7.array(checkSchema).nonempty().parse(checks);
3304
- const report = await stableAudit(root, selected, options);
3393
+ const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
3305
3394
  if (!report) throw new Error("No context files found to prepare a repair.");
3306
3395
  if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
3307
3396
  const storedReport = reportSchema.parse(report);
@@ -3327,23 +3416,26 @@ async function loadRepairBaseline(rootDir, baselinePath) {
3327
3416
  return { root, relative, original };
3328
3417
  }
3329
3418
  async function verifyRepair(rootDir, baselinePath, options = {}) {
3419
+ return profilePhase("repair.verify", () => verifyOriginal(rootDir, baselinePath, options));
3420
+ }
3421
+ async function verifyOriginal(rootDir, baselinePath, options, session) {
3330
3422
  const { root, relative, original } = await loadRepairBaseline(rootDir, baselinePath);
3331
3423
  const diagnostics = [];
3332
3424
  let reviewDigest;
3333
3425
  let current = null;
3334
3426
  try {
3335
- reviewDigest = digest(await advisoryReviewInventory(root));
3336
- current = await stableAudit(root, original.checksRun, options);
3427
+ if (!session) reviewDigest = digest(await advisoryReviewInventory(root));
3428
+ current = await (session ? session.audit(original.checksRun) : stableAudit(root, original.checksRun, options));
3337
3429
  if (!current) diagnostics.push("No context files remain available to audit.");
3338
3430
  else if (!current.gitAvailable) diagnostics.push("Git history is unavailable.");
3339
3431
  for (const doc of original.docs) {
3340
3432
  if (!allFindings(original).some((f) => f.anchor.doc === doc.path) || !current?.docs.some((d) => d.path === doc.path)) continue;
3341
- const content2 = await readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024);
3433
+ const content2 = await (session ? session.document(doc.path) : readBoundedFile(await storePath(root, doc.path), 10 * 1024 * 1024));
3342
3434
  if (content2 === null || !content2.trim()) {
3343
3435
  diagnostics.push("Original context file " + doc.path + " is empty or unreadable; losing its claims does not verify a repair.");
3344
3436
  }
3345
3437
  }
3346
- if (await getChangesWithStatus(root, original.headHash) === null) {
3438
+ if (await (session ? session.history(original.headHash) : getChangesWithStatus(root, original.headHash)) === null) {
3347
3439
  diagnostics.push("The original audit commit is unavailable; repair history cannot be verified.");
3348
3440
  }
3349
3441
  } catch (error) {
@@ -3368,7 +3460,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
3368
3460
  if (!current.checksRun?.includes(finding.type) || skipped.length) {
3369
3461
  return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
3370
3462
  }
3371
- const review = await assessAdvisory(root, now ?? finding, original.headHash);
3463
+ const review = await (session?.assessment ?? assessAdvisory)(root, now ?? finding, original.headHash);
3372
3464
  if (review) {
3373
3465
  if (review.status === "unverified") return { ...base, review, status: "unverified", reason: review.reason };
3374
3466
  if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
@@ -3383,7 +3475,7 @@ async function verifyRepair(rootDir, baselinePath, options = {}) {
3383
3475
  }
3384
3476
  return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
3385
3477
  }));
3386
- try {
3478
+ if (!session) try {
3387
3479
  if (reviewDigest !== digest(await advisoryReviewInventory(root)) || current?.headHash !== await getCurrentGitHash(root)) {
3388
3480
  diagnostics.push("HEAD or advisory review records changed during verification; repeat against stable evidence.");
3389
3481
  for (const finding of findings) {
@@ -3436,6 +3528,7 @@ var count2, reportSchema, baselineSchema, repairFindingSchema;
3436
3528
  var init_repair = __esm({
3437
3529
  "src/audit/repair.ts"() {
3438
3530
  "use strict";
3531
+ init_profile();
3439
3532
  init_audit();
3440
3533
  init_docs();
3441
3534
  init_snapshot();
@@ -3496,20 +3589,20 @@ import { promisify as promisify9 } from "util";
3496
3589
  import fg5 from "fast-glob";
3497
3590
  import { z as z8 } from "zod";
3498
3591
  async function git3(root, ...args) {
3499
- return (await exec7("git", args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout;
3592
+ return profilePhase("automation.git", async () => (await exec7("git", args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
3500
3593
  }
3501
3594
  async function workspace(dir) {
3502
3595
  const root = await fs13.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
3503
- const gitDir = await fs13.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
3504
- let branch;
3505
- try {
3506
- branch = (await git3(root, "symbolic-ref", "--quiet", "HEAD")).trim();
3507
- } catch {
3508
- branch = "detached";
3509
- }
3596
+ const [gitDir, branch] = await Promise.all([
3597
+ git3(root, "rev-parse", "--absolute-git-dir").then((value) => fs13.realpath(value.trim())),
3598
+ git3(root, "symbolic-ref", "--quiet", "HEAD").then((value) => value.trim(), () => "detached")
3599
+ ]);
3510
3600
  return { root, gitDir, branch, directory: ".mason/reports/automation/" + hash([root, gitDir, branch]).slice(0, 24) };
3511
3601
  }
3512
3602
  async function readInputs(root) {
3603
+ return profilePhase("automation.inputs", () => collectInputs(root));
3604
+ }
3605
+ async function collectInputs(root) {
3513
3606
  const docPaths = await discoverDocPaths(root);
3514
3607
  const [headText, docStatus, shallowPath, replacements] = await Promise.all([
3515
3608
  git3(root, "rev-parse", "HEAD"),
@@ -3526,21 +3619,29 @@ async function readInputs(root) {
3526
3619
  }
3527
3620
  const docs = {};
3528
3621
  const docContents = [];
3622
+ const parsedDocs = [];
3529
3623
  const claims = [];
3624
+ const presence = /* @__PURE__ */ new Map();
3625
+ const exists = (file) => {
3626
+ if (!presence.has(file)) presence.set(file, pathExists(root, file));
3627
+ return presence.get(file);
3628
+ };
3530
3629
  for (const file of docPaths) {
3531
3630
  const text2 = await content(root, file);
3532
3631
  docs[file] = text2 === null ? null : hash(text2);
3533
3632
  docContents.push([file, text2]);
3534
- for (const claim of text2 ? extractClaims(text2).paths : []) {
3633
+ const parsed = { path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") };
3634
+ parsedDocs.push(parsed);
3635
+ for (const claim of parsed.claims.paths) {
3535
3636
  const scope = pathClaimScope(file, claim);
3536
3637
  if (scope?.candidates.some(gitMetadataPath)) continue;
3537
3638
  for (const candidate of scope?.candidates ?? []) {
3538
3639
  if (optionalMasonPath(candidate)) continue;
3539
- claims.push([candidate, await pathExists(root, candidate), await pathExists(root, path20.posix.dirname(candidate))]);
3640
+ const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path20.posix.dirname(candidate))]);
3641
+ claims.push([candidate, filePresent, parentPresent]);
3540
3642
  }
3541
3643
  }
3542
3644
  }
3543
- const parsedDocs = docContents.map(([file, text2]) => ({ path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") }));
3544
3645
  const combinedDocs = moduleDocumentation(parsedDocs);
3545
3646
  const countClaims = parsedDocs.flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
3546
3647
  const decisionDirectory = await storePath(root, ".mason/decisions");
@@ -3562,7 +3663,7 @@ async function readInputs(root) {
3562
3663
  git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
3563
3664
  git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
3564
3665
  ]) : ["", ""];
3565
- const common = [4, engineVersion, head, shallow, replacements, docContents];
3666
+ const common = [5, engineVersion, head, shallow, replacements, docContents];
3566
3667
  const keys = {
3567
3668
  "deleted-reference": hash([common, claims, docStatus]),
3568
3669
  "new-module": hash([common, modules]),
@@ -3587,21 +3688,29 @@ function checkCache(raw, inputs) {
3587
3688
  reused.add(name);
3588
3689
  return structuredClone(entries[name].result);
3589
3690
  }
3590
- const result = await CHECKS[name](ctx);
3691
+ const result = await profilePhase("check." + name, () => CHECKS[name](ctx));
3591
3692
  ran.add(name);
3592
3693
  if (!result.skipped.length) entries[name] = { key: inputs.keys[name], result };
3593
3694
  else delete entries[name];
3594
3695
  return result;
3595
3696
  } };
3596
- return { options, ran, reused, diagnostic, serialize: () => {
3597
- const canonical = cacheSchema.shape.entries.parse(entries);
3598
- return { version: 1, entries: canonical, digest: hash(canonical) };
3599
- } };
3697
+ return {
3698
+ options,
3699
+ ran,
3700
+ reused,
3701
+ diagnostic,
3702
+ has: (name) => entries[name]?.key === inputs.keys[name] && !entries[name].result.skipped.length,
3703
+ serialize: () => {
3704
+ const canonical = cacheSchema.shape.entries.parse(entries);
3705
+ return { version: 1, entries: canonical, digest: hash(canonical) };
3706
+ }
3707
+ };
3600
3708
  }
3601
3709
  var exec7, engineVersion, hash, content, cacheSchema;
3602
3710
  var init_evidence = __esm({
3603
3711
  "src/automation/evidence.ts"() {
3604
3712
  "use strict";
3713
+ init_profile();
3605
3714
  init_docs();
3606
3715
  init_scope();
3607
3716
  init_claims();
@@ -3614,7 +3723,7 @@ var init_evidence = __esm({
3614
3723
  init_storage();
3615
3724
  init_advisory_review();
3616
3725
  exec7 = promisify9(execFile9);
3617
- engineVersion = true ? "0.17.2" : "development";
3726
+ engineVersion = true ? "0.17.3" : "development";
3618
3727
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
3619
3728
  content = readAuditInput;
3620
3729
  cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
@@ -3706,7 +3815,7 @@ var init_launcher = __esm({
3706
3815
  "use strict";
3707
3816
  init_evidence();
3708
3817
  init_model();
3709
- executingVersion = () => true ? "0.17.2" : "development";
3818
+ executingVersion = () => true ? "0.17.3" : "development";
3710
3819
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3711
3820
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3712
3821
  }
@@ -3981,7 +4090,7 @@ __export(runtime_exports, {
3981
4090
  import { randomUUID as randomUUID5 } from "crypto";
3982
4091
  import { z as z13 } from "zod";
3983
4092
  async function observeReadOnlyTool(dir, event) {
3984
- const ws = await workspace(dir);
4093
+ const ws = await profilePhase("automation.workspace", () => workspace(dir));
3985
4094
  await withLock(ws.root, ws.directory, async () => {
3986
4095
  const statePath = ws.directory + "/state.json";
3987
4096
  const raw = await readStoreJson(ws.root, statePath);
@@ -4028,24 +4137,41 @@ async function loadState(ws) {
4028
4137
  }
4029
4138
  return state;
4030
4139
  }
4031
- async function sharedAnalysis(ws, state, inputs, startedAt) {
4140
+ async function matchingAnalysis(ws, state, inputs) {
4032
4141
  const pointer = state.analysis;
4033
- if (!pointer || pointer.fingerprint !== inputs.fingerprint || pointer.completedAt < startedAt) return null;
4034
- const stored = sharedSchema.parse(await readStoreJson(ws.root, pointer.path));
4142
+ if (!pointer || pointer.fingerprint !== inputs.fingerprint) return { analysis: null };
4143
+ if (pointer.path !== ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json") {
4144
+ throw new Error("Invalid automation analysis cache path; inspect the retained state.");
4145
+ }
4146
+ const invalid = { analysis: null, diagnostic: "Discarded an invalid current-audit cache; audit preparation and original verification were repeated." };
4147
+ let raw;
4148
+ try {
4149
+ raw = await readStoreJson(ws.root, pointer.path);
4150
+ } catch (error) {
4151
+ const cause = error.cause;
4152
+ if (cause instanceof SyntaxError || cause instanceof Error && cause.message === "expected a JSON object, received null") return invalid;
4153
+ throw error;
4154
+ }
4155
+ if (raw === null) return { analysis: null };
4156
+ const parsed = sharedSchema.safeParse(raw);
4157
+ if (!parsed.success) return invalid;
4158
+ const stored = parsed.data;
4035
4159
  const a = stored.analysis;
4036
4160
  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))) {
4037
- throw new Error("Invalid shared automation evidence; run a new check.");
4161
+ return invalid;
4038
4162
  }
4039
4163
  if (a.baselineDigest !== await baselineDigest(ws, state)) throw new Error("Repair baseline was modified; use the original baseline.");
4040
- return a;
4164
+ return { analysis: a };
4041
4165
  }
4042
4166
  async function verifyInputs(ws, inputs) {
4043
- const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
4044
- if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
4045
- throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
4046
- }
4167
+ return profilePhase("automation.revalidate", async () => {
4168
+ const [after, currentWs] = await Promise.all([readInputs(ws.root), workspace(ws.root)]);
4169
+ if (after.fingerprint !== inputs.fingerprint || currentWs.directory !== ws.directory) {
4170
+ throw new Error("Repository inputs or branch changed during automation; no current verification was recorded. Retry on a stable checkout.");
4171
+ }
4172
+ });
4047
4173
  }
4048
- async function computeAnalysis(ws, state, inputs, event) {
4174
+ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
4049
4175
  const baselines = [...state.baselines];
4050
4176
  const now = (/* @__PURE__ */ new Date()).toISOString();
4051
4177
  const empty = {
@@ -4074,7 +4200,7 @@ async function computeAnalysis(ws, state, inputs, event) {
4074
4200
  }
4075
4201
  }
4076
4202
  let cached = null;
4077
- const diagnostics = [];
4203
+ const diagnostics = cacheDiagnostic ? [cacheDiagnostic] : [];
4078
4204
  try {
4079
4205
  cached = await readStoreJson(ws.root, ws.directory + "/cache.json");
4080
4206
  } catch {
@@ -4082,19 +4208,26 @@ async function computeAnalysis(ws, state, inputs, event) {
4082
4208
  }
4083
4209
  const cache = checkCache(cached, inputs);
4084
4210
  if (cache.diagnostic) diagnostics.push(cache.diagnostic);
4085
- const saveBaseline = async () => {
4086
- if (baselines.length >= 128) throw new Error("128 retained baselines need review; automatic capture stopped without discarding original evidence.");
4087
- const prepared = await prepareRepair(ws.root, ALL_CHECKS, cache.options);
4088
- baselines.push({ path: prepared.baselinePath, at: now, event: event.event, fingerprint: inputs.fingerprint });
4089
- };
4090
- if (!baselines.length) await saveBaseline();
4091
- const verifications = [];
4092
- for (const baseline of baselines) verifications.push(await verifyRepair(ws.root, baseline.path, cache.options));
4093
- const known = new Set(verifications.flatMap((v) => v.findings.map((f) => f.id)));
4094
- if (verifications.some((v) => v.newFindings.some((f) => !known.has(findingId(f))))) {
4095
- await saveBaseline();
4096
- verifications.push(await verifyRepair(ws.root, baselines.at(-1).path, cache.options));
4097
- }
4211
+ 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;
4212
+ if (cachedAudit) for (const name of ALL_CHECKS) cache.reused.add(name);
4213
+ let currentAudit = null;
4214
+ const verifications = await withRepairSession(ws.root, cache.options, async (repairs) => {
4215
+ const saveBaseline = async () => {
4216
+ if (baselines.length >= 128) throw new Error("128 retained baselines need review; automatic capture stopped without discarding original evidence.");
4217
+ const prepared = await repairs.prepare();
4218
+ baselines.push({ path: prepared.baselinePath, at: now, event: event.event, fingerprint: inputs.fingerprint });
4219
+ };
4220
+ if (!baselines.length) await saveBaseline();
4221
+ const verifications2 = [];
4222
+ for (const baseline of baselines) verifications2.push(await repairs.verify(baseline.path));
4223
+ const known = new Set(verifications2.flatMap((v) => v.findings.map((f) => f.id)));
4224
+ if (verifications2.some((v) => v.newFindings.some((f) => !known.has(findingId(f))))) {
4225
+ await saveBaseline();
4226
+ verifications2.push(await repairs.verify(baselines.at(-1).path));
4227
+ }
4228
+ currentAudit = await repairs.currentAudit();
4229
+ return verifications2;
4230
+ }, cachedAudit);
4098
4231
  const merged = /* @__PURE__ */ new Map();
4099
4232
  for (const verification of verifications) {
4100
4233
  for (const finding of verification.findings) {
@@ -4115,20 +4248,20 @@ async function computeAnalysis(ws, state, inputs, event) {
4115
4248
  reportPath: "",
4116
4249
  findings: [...merged.values()],
4117
4250
  diagnostics: [...new Set(diagnostics)],
4118
- checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [] },
4251
+ checks: { ran: [...cache.ran], reused: [...cache.reused].filter((name) => !cache.ran.has(name)), skipped: verifications.at(-1).currentAudit?.skippedChecks ?? [], ...cachedAudit ? { auditReused: true } : {} },
4119
4252
  counts,
4120
4253
  capture: "unknown",
4121
4254
  scope: SCOPE
4122
4255
  };
4123
- return { baselines, report, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
4256
+ return { baselines, report, currentAudit, cache: cache.ran.size || cached === null || cache.diagnostic ? cache.serialize() : null };
4124
4257
  }
4125
4258
  async function analyze(ws, inputs, event, startedAt) {
4126
4259
  return withLock(ws.root, ws.directory + "/analysis/" + inputs.fingerprint, async () => {
4127
4260
  const before = await loadState(ws);
4128
- const shared = await sharedAnalysis(ws, before, inputs, startedAt);
4129
- if (shared) return { analysis: shared, shared: true };
4261
+ const { analysis: previous, diagnostic } = await matchingAnalysis(ws, before, inputs);
4262
+ if (previous && previous.completedAt >= startedAt) return { analysis: previous, shared: true };
4130
4263
  const originalDigest = await baselineDigest(ws, before);
4131
- const computed = await computeAnalysis(ws, before, inputs, event);
4264
+ const computed = await computeAnalysis(ws, before, inputs, event, previous?.currentAudit, diagnostic);
4132
4265
  await verifyInputs(ws, inputs);
4133
4266
  if (originalDigest !== await baselineDigest(ws, before)) throw new Error("Repair baseline was modified during automation; original evidence must be inspected.");
4134
4267
  const id = randomUUID5();
@@ -4137,7 +4270,8 @@ async function analyze(ws, inputs, event, startedAt) {
4137
4270
  fingerprint: inputs.fingerprint,
4138
4271
  completedAt: Date.now(),
4139
4272
  baselineDigest: await baselineDigest(ws, { ...before, baselines: computed.baselines }),
4140
- report: computed.report
4273
+ report: computed.report,
4274
+ currentAudit: computed.currentAudit
4141
4275
  });
4142
4276
  const analysisPath = ws.directory + "/checks/analysis-" + inputs.fingerprint + ".json";
4143
4277
  await withLock(ws.root, ws.directory, async () => {
@@ -4156,17 +4290,17 @@ async function analyze(ws, inputs, event, startedAt) {
4156
4290
  }
4157
4291
  async function automate(dir, event) {
4158
4292
  const startedAt = Date.now();
4159
- const ws = await workspace(dir);
4293
+ const ws = await profilePhase("automation.workspace", () => workspace(dir));
4160
4294
  return recordExecution(ws.root, ws.directory, event.event, async () => {
4161
4295
  const inputs = await readInputs(ws.root);
4162
- const result = await analyze(ws, inputs, event, startedAt);
4296
+ const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
4163
4297
  if (result.shared) {
4164
4298
  await verifyInputs(ws, inputs);
4165
4299
  if (result.analysis.baselineDigest !== await baselineDigest(ws, await loadState(ws))) {
4166
4300
  throw new Error("Repair baseline was modified during shared automation; original evidence must be inspected.");
4167
4301
  }
4168
4302
  }
4169
- return withLock(ws.root, ws.directory, async () => {
4303
+ return profilePhase("automation.publish", () => withLock(ws.root, ws.directory, async () => {
4170
4304
  const state = await loadState(ws);
4171
4305
  if (state.analysis?.id !== result.analysis.id) throw new Error("Automation evidence changed during publication; retry against the current inputs.");
4172
4306
  const report = structuredClone(result.analysis.report);
@@ -4229,11 +4363,11 @@ async function automate(dir, event) {
4229
4363
  if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
4230
4364
  await writeStoreJson(ws.root, ws.directory + "/state.json", state);
4231
4365
  return { report, message: notify ? summarize(report) : null, continueOnce };
4232
- });
4366
+ }));
4233
4367
  });
4234
4368
  }
4235
4369
  async function automationStatus(dir) {
4236
- const ws = await workspace(dir);
4370
+ const ws = await profilePhase("automation.workspace", () => workspace(dir));
4237
4371
  const execution = await executionStatus(ws.root, ws.directory);
4238
4372
  const notifications = await failureNotifications(ws.root, ws.directory);
4239
4373
  const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
@@ -4267,6 +4401,7 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
4267
4401
  var init_runtime = __esm({
4268
4402
  "src/automation/runtime.ts"() {
4269
4403
  "use strict";
4404
+ init_profile();
4270
4405
  init_execution();
4271
4406
  init_repair();
4272
4407
  init_types();
@@ -4281,6 +4416,7 @@ var init_runtime = __esm({
4281
4416
  fingerprint: z13.string(),
4282
4417
  completedAt: z13.number(),
4283
4418
  baselineDigest: z13.string(),
4419
+ currentAudit: reportSchema.nullable().optional(),
4284
4420
  report: z13.object({
4285
4421
  version: z13.literal(1),
4286
4422
  status: z13.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
@@ -4294,7 +4430,8 @@ var init_runtime = __esm({
4294
4430
  checks: z13.object({
4295
4431
  ran: z13.array(z13.string()),
4296
4432
  reused: z13.array(z13.string()),
4297
- skipped: z13.array(z13.object({ check: z13.string(), reason: z13.string(), doc: z13.string().optional() }))
4433
+ skipped: z13.array(z13.object({ check: z13.string(), reason: z13.string(), doc: z13.string().optional() })),
4434
+ auditReused: z13.boolean().optional()
4298
4435
  }),
4299
4436
  counts: z13.object({ resolved: z13.number(), unresolved: z13.number(), "review-required": z13.number(), unverified: z13.number() }),
4300
4437
  capture: z13.enum(["observed", "unknown"]),
@@ -4302,9 +4439,9 @@ var init_runtime = __esm({
4302
4439
  })
4303
4440
  });
4304
4441
  sharedSchema = z13.object({ analysis: analysisSchema, digest: z13.string() });
4305
- baselineDigest = async (ws, state) => hash(await Promise.all(
4442
+ baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
4306
4443
  state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
4307
- ));
4444
+ )));
4308
4445
  }
4309
4446
  });
4310
4447
 
@@ -9226,7 +9363,7 @@ function createMcpServer() {
9226
9363
  const server = new McpServer(
9227
9364
  {
9228
9365
  name: "mason",
9229
- version: "0.17.2"
9366
+ version: "0.17.3"
9230
9367
  },
9231
9368
  {
9232
9369
  instructions: (setupNotice ? setupNotice + "\n\n" : "") + 'Mason retrieves recorded decisions, file impact, and optional feature/flow maps. Use get_context with the task and known files, and get_impact before editing. Save learned rationale and constraints with save_decision; review and commit the local records through the project workflow. These tools work without initialization or a concept map. Consult trust and diagnostics: changed or unknown freshness needs source inspection, failed verification needs correction, and proposals are suggestions and legacy records are unreviewed. Accepted decisions are recorded team constraints, still subject to freshness checks. Use review_decision to prepare code evidence and record only authorized acceptance, reaffirmation, or retirement. Never invent a reviewer or treat these recorded identities as authenticated approval. mason_init returns documentation audit and committed-diff review findings with a quickstart guide. Use mode: "map" only when a full architecture map is requested. A missing map is not a setup failure; use decisions and source evidence. get_snapshot provides architecture navigation when a map is available. The mason-audit and mason-review CLIs also work without setup.'