mason-context 0.14.0 → 0.14.2

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
@@ -1394,9 +1394,133 @@ var init_deleted_reference = __esm({
1394
1394
  }
1395
1395
  });
1396
1396
 
1397
- // src/audit/checks/new-module.ts
1398
- import fg2 from "fast-glob";
1397
+ // src/audit/inputs.ts
1398
+ import fs6 from "fs/promises";
1399
+ import * as nativeFs from "fs";
1399
1400
  import path9 from "path";
1401
+ import { execFile as execFile6 } from "child_process";
1402
+ import { promisify as promisify6 } from "util";
1403
+ import fg2 from "fast-glob";
1404
+ async function auditInputPath(root, file) {
1405
+ if (file === ".") return fs6.realpath(root);
1406
+ try {
1407
+ return await storePath(root, file);
1408
+ } catch (error) {
1409
+ if (error instanceof Error && error.message.startsWith("Symlink")) {
1410
+ throw new Error(`Audit input contains a symbolic link: ${file}. Its evidence could not be verified.`, { cause: error });
1411
+ }
1412
+ throw error;
1413
+ }
1414
+ }
1415
+ async function readAuditInput(root, file) {
1416
+ try {
1417
+ const value = await readBoundedFile(await auditInputPath(root, file), 10 * 1024 * 1024);
1418
+ if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
1419
+ return value;
1420
+ } catch (error) {
1421
+ if (error.code === "ENOENT") return null;
1422
+ throw error;
1423
+ }
1424
+ }
1425
+ async function auditGlob(root, patterns, options) {
1426
+ if (Array.isArray(patterns) && !patterns.length) return [];
1427
+ const directories = /* @__PURE__ */ new Set();
1428
+ const readdir2 = ((directory, opts, callback) => {
1429
+ const relative = path9.relative(root, directory).split(path9.sep).join("/") || ".";
1430
+ directories.add(relative);
1431
+ if (directories.size > MAX_INPUTS) {
1432
+ callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
1433
+ return;
1434
+ }
1435
+ auditInputPath(root, relative).then(() => {
1436
+ nativeFs.readdir(directory, opts, callback);
1437
+ }, callback);
1438
+ });
1439
+ const lstat2 = ((file, callback) => {
1440
+ const relative = path9.relative(root, file).split(path9.sep).join("/") || ".";
1441
+ auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
1442
+ });
1443
+ const stream = fg2.stream(patterns, {
1444
+ cwd: root,
1445
+ ignore: options.ignore,
1446
+ onlyFiles: false,
1447
+ onlyDirectories: options.onlyDirectories,
1448
+ objectMode: true,
1449
+ followSymbolicLinks: options.followSymbolicLinks ?? true,
1450
+ fs: { readdir: readdir2, lstat: lstat2 },
1451
+ concurrency: 16
1452
+ });
1453
+ const matches = [];
1454
+ try {
1455
+ for await (const value of stream) {
1456
+ const entry = value;
1457
+ const file = entry.path;
1458
+ if (options.select && !options.select(file)) continue;
1459
+ await auditInputPath(root, file);
1460
+ if (!options.onlyDirectories && !entry.dirent.isFile()) continue;
1461
+ matches.push(file);
1462
+ if (matches.length > MAX_INPUTS) {
1463
+ const largest = [...matches.reduce((counts, item) => {
1464
+ const dir = item.split("/")[0];
1465
+ counts.set(dir, (counts.get(dir) ?? 0) + 1);
1466
+ return counts;
1467
+ }, /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1]).slice(0, 3);
1468
+ throw new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count3]) => `${dir} (${count3})`).join(", ")}. Narrow this check's inputs.`);
1469
+ }
1470
+ }
1471
+ } finally {
1472
+ stream.destroy();
1473
+ }
1474
+ return matches.sort();
1475
+ }
1476
+ async function gitSourcePaths(root) {
1477
+ const { stdout } = await execute(
1478
+ "git",
1479
+ [
1480
+ "ls-files",
1481
+ "-z",
1482
+ "--cached",
1483
+ "--others",
1484
+ "--exclude-standard",
1485
+ "--",
1486
+ ...SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`)
1487
+ ],
1488
+ { cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 1e4 }
1489
+ );
1490
+ return [...new Set(stdout.split("\0").filter(Boolean))].sort();
1491
+ }
1492
+ async function gitIgnoredPaths(root, files) {
1493
+ if (!files.length) return /* @__PURE__ */ new Set();
1494
+ const operation = execute(
1495
+ "git",
1496
+ ["check-ignore", "-z", "--stdin"],
1497
+ { cwd: root, maxBuffer: 4 * 1024 * 1024, timeout: 1e4 }
1498
+ );
1499
+ operation.child.stdin.on("error", () => {
1500
+ });
1501
+ operation.child.stdin.end(files.join("\0") + "\0");
1502
+ try {
1503
+ return new Set((await operation).stdout.split("\0").filter(Boolean));
1504
+ } catch (error) {
1505
+ if (error.code === 1) return /* @__PURE__ */ new Set();
1506
+ throw error;
1507
+ }
1508
+ }
1509
+ var execute, MAX_INPUTS;
1510
+ var init_inputs = __esm({
1511
+ "src/audit/inputs.ts"() {
1512
+ "use strict";
1513
+ init_files();
1514
+ init_storage();
1515
+ execute = promisify6(execFile6);
1516
+ MAX_INPUTS = 1e5;
1517
+ }
1518
+ });
1519
+
1520
+ // src/audit/checks/new-module.ts
1521
+ import fg3 from "fast-glob";
1522
+ import fs7 from "fs/promises";
1523
+ import path10 from "path";
1400
1524
  function escapeRegExp(text2) {
1401
1525
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1402
1526
  }
@@ -1407,22 +1531,6 @@ function isMentioned(combinedDocs, name) {
1407
1531
  );
1408
1532
  return re.test(combinedDocs);
1409
1533
  }
1410
- async function listSubdirs(absDir) {
1411
- const dirs = await fg2("*", {
1412
- cwd: absDir,
1413
- onlyDirectories: true,
1414
- suppressErrors: true
1415
- });
1416
- return dirs.filter((d) => !DIR_DENYLIST.has(d)).sort();
1417
- }
1418
- async function countSourceFiles(absDir) {
1419
- const files = await fg2(SOURCE_GLOB, {
1420
- cwd: absDir,
1421
- ignore: SOURCE_IGNORE,
1422
- suppressErrors: true
1423
- });
1424
- return files.length;
1425
- }
1426
1534
  async function checkNewModules(ctx) {
1427
1535
  const result = emptyResult();
1428
1536
  if (ctx.docs.length === 0) return result;
@@ -1450,21 +1558,48 @@ async function checkNewModules(ctx) {
1450
1558
  return result;
1451
1559
  }
1452
1560
  async function moduleCandidates(root, combinedDocs) {
1561
+ const [config, sourcePaths] = await Promise.all([loadProjectConfig(root), gitSourcePaths(root)]);
1562
+ const ignore = [...SOURCE_IGNORE, ...config.ignore ?? []];
1563
+ const sources = sourcePaths.filter((file) => !file.split("/").some((part) => part.startsWith(".")));
1564
+ const listSubdirs = async (dir) => {
1565
+ const entries = await fs7.readdir(await auditInputPath(root, dir), { withFileTypes: true });
1566
+ const dirs = entries.filter((entry) => (entry.isDirectory() || entry.isSymbolicLink()) && !entry.name.startsWith(".") && !DIR_DENYLIST.has(entry.name)).map((entry) => path10.posix.join(dir, entry.name));
1567
+ const ignored = await gitIgnoredPaths(root, dirs);
1568
+ const visible = new Set(dirs.filter((dir2) => !ignored.has(dir2) || sources.some((file) => file.startsWith(dir2 + "/"))));
1569
+ return (await auditGlob(root, dir === "." ? "*" : `${fg3.escapePath(dir)}/*`, {
1570
+ ignore,
1571
+ onlyDirectories: true,
1572
+ label: "Module directory discovery",
1573
+ select: (file) => visible.has(file)
1574
+ })).map((file) => path10.posix.basename(file));
1575
+ };
1576
+ const countSourceFiles = async (dir) => {
1577
+ const files = sources.filter((file) => file.startsWith(dir + "/"));
1578
+ let count3 = 0;
1579
+ for (let offset = 0; offset < files.length; offset += 256) {
1580
+ count3 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) => fg3.escapePath(file)), {
1581
+ ignore,
1582
+ followSymbolicLinks: false,
1583
+ label: `Source discovery in ${dir}`
1584
+ })).length;
1585
+ if (count3 > 1e5) throw new Error(`Source discovery in ${dir} exceeds 100,000 relevant files. Exclude generated source with .mason/config.json ignore patterns or narrow this audit's checks.`);
1586
+ }
1587
+ return count3;
1588
+ };
1453
1589
  const candidates = [];
1454
- for (const topDir of await listSubdirs(root)) {
1455
- const absTop = path9.join(root, topDir);
1590
+ for (const topDir of await listSubdirs(".")) {
1456
1591
  const topMentioned = isMentioned(combinedDocs, topDir);
1457
1592
  if (!topMentioned) {
1458
- const count3 = await countSourceFiles(absTop);
1593
+ const count3 = await countSourceFiles(topDir);
1459
1594
  if (count3 >= 1) candidates.push({ dir: topDir, sourceFileCount: count3 });
1460
1595
  continue;
1461
1596
  }
1462
- const subdirs = await listSubdirs(absTop);
1597
+ const subdirs = await listSubdirs(topDir);
1463
1598
  const mentioned = subdirs.filter((s) => isMentioned(combinedDocs, s));
1464
1599
  if (mentioned.length < ENUMERATION_THRESHOLD) continue;
1465
1600
  for (const sub of subdirs) {
1466
1601
  if (isMentioned(combinedDocs, sub)) continue;
1467
- const count3 = await countSourceFiles(path9.join(absTop, sub));
1602
+ const count3 = await countSourceFiles(`${topDir}/${sub}`);
1468
1603
  if (count3 >= SECOND_LEVEL_MIN_SOURCE_FILES) {
1469
1604
  candidates.push({ dir: `${topDir}/${sub}`, sourceFileCount: count3 });
1470
1605
  }
@@ -1476,7 +1611,8 @@ var DIR_DENYLIST, SECOND_LEVEL_MIN_SOURCE_FILES, ENUMERATION_THRESHOLD;
1476
1611
  var init_new_module = __esm({
1477
1612
  "src/audit/checks/new-module.ts"() {
1478
1613
  "use strict";
1479
- init_snapshot();
1614
+ init_files();
1615
+ init_inputs();
1480
1616
  init_git();
1481
1617
  init_checks();
1482
1618
  DIR_DENYLIST = /* @__PURE__ */ new Set([
@@ -1504,19 +1640,10 @@ var init_new_module = __esm({
1504
1640
  });
1505
1641
 
1506
1642
  // src/audit/checks/stale-count.ts
1507
- import fs6 from "fs/promises";
1508
- import path10 from "path";
1509
- import fg3 from "fast-glob";
1510
- async function readIfExists(absPath) {
1511
- try {
1512
- return await fs6.readFile(absPath, "utf-8");
1513
- } catch {
1514
- return null;
1515
- }
1516
- }
1643
+ import path11 from "path";
1517
1644
  async function countGradleModules(root) {
1518
1645
  for (const name of ["settings.gradle.kts", "settings.gradle"]) {
1519
- const content2 = await readIfExists(path10.join(root, name));
1646
+ const content2 = await readAuditInput(root, name);
1520
1647
  if (content2 === null) continue;
1521
1648
  const members = [];
1522
1649
  for (const call of content2.matchAll(/include\s*\(([^)]*)\)/g)) {
@@ -1530,26 +1657,28 @@ async function countGradleModules(root) {
1530
1657
  return null;
1531
1658
  }
1532
1659
  async function countNpmWorkspaces(root) {
1533
- const pkgRaw = await readIfExists(path10.join(root, "package.json"));
1660
+ const pkgRaw = await readAuditInput(root, "package.json");
1534
1661
  if (pkgRaw !== null) {
1662
+ let globs = [];
1535
1663
  try {
1536
1664
  const pkg = JSON.parse(pkgRaw);
1537
- const globs = Array.isArray(pkg.workspaces) ? pkg.workspaces : Array.isArray(pkg.workspaces?.packages) ? pkg.workspaces.packages : [];
1538
- if (globs.length > 0) {
1539
- const matched = await fg3(
1540
- globs.map((g) => `${g.replace(/\/+$/, "")}/package.json`),
1541
- { cwd: root, ignore: ["**/node_modules/**"] }
1542
- );
1543
- return {
1544
- actual: matched.length,
1545
- countedFrom: "package.json workspaces",
1546
- members: matched.map((m) => path10.dirname(m)).sort()
1547
- };
1548
- }
1665
+ globs = Array.isArray(pkg.workspaces) ? pkg.workspaces : Array.isArray(pkg.workspaces?.packages) ? pkg.workspaces.packages : [];
1549
1666
  } catch {
1550
1667
  }
1668
+ if (globs.length > 0) {
1669
+ const matched = await auditGlob(
1670
+ root,
1671
+ globs.map((g) => `${g.replace(/\/+$/, "")}/package.json`),
1672
+ { ignore: ["**/node_modules/**"], label: "npm workspace discovery" }
1673
+ );
1674
+ return {
1675
+ actual: matched.length,
1676
+ countedFrom: "package.json workspaces",
1677
+ members: matched.map((m) => path11.dirname(m)).sort()
1678
+ };
1679
+ }
1551
1680
  }
1552
- const pnpmRaw = await readIfExists(path10.join(root, "pnpm-workspace.yaml"));
1681
+ const pnpmRaw = await readAuditInput(root, "pnpm-workspace.yaml");
1553
1682
  if (pnpmRaw !== null) {
1554
1683
  const globs = [];
1555
1684
  let inPackages = false;
@@ -1568,21 +1697,22 @@ async function countNpmWorkspaces(root) {
1568
1697
  }
1569
1698
  }
1570
1699
  if (globs.length > 0) {
1571
- const matched = await fg3(
1700
+ const matched = await auditGlob(
1701
+ root,
1572
1702
  globs.map((g) => `${g.replace(/\/+$/, "")}/package.json`),
1573
- { cwd: root, ignore: ["**/node_modules/**"] }
1703
+ { ignore: ["**/node_modules/**"], label: "pnpm workspace discovery" }
1574
1704
  );
1575
1705
  return {
1576
1706
  actual: matched.length,
1577
1707
  countedFrom: "pnpm-workspace.yaml",
1578
- members: matched.map((m) => path10.dirname(m)).sort()
1708
+ members: matched.map((m) => path11.dirname(m)).sort()
1579
1709
  };
1580
1710
  }
1581
1711
  }
1582
1712
  return null;
1583
1713
  }
1584
1714
  async function countCargoCrates(root) {
1585
- const content2 = await readIfExists(path10.join(root, "Cargo.toml"));
1715
+ const content2 = await readAuditInput(root, "Cargo.toml");
1586
1716
  if (content2 === null) return null;
1587
1717
  const membersBlock = content2.match(/members\s*=\s*\[([\s\S]*?)\]/);
1588
1718
  if (!membersBlock) return null;
@@ -1593,12 +1723,12 @@ async function countCargoCrates(root) {
1593
1723
  const members = /* @__PURE__ */ new Set();
1594
1724
  for (const entry of entries) {
1595
1725
  if (/[*?[\]{}]/.test(entry)) {
1596
- const matched = await fg3(`${entry.replace(/\/+$/, "")}/Cargo.toml`, {
1597
- cwd: root,
1598
- ignore: ["**/target/**"]
1726
+ const matched = await auditGlob(root, `${entry.replace(/\/+$/, "")}/Cargo.toml`, {
1727
+ ignore: ["**/target/**"],
1728
+ label: "Cargo workspace discovery"
1599
1729
  });
1600
- for (const m of matched) members.add(path10.dirname(m));
1601
- } else if (await readIfExists(path10.join(root, entry, "Cargo.toml")) !== null) {
1730
+ for (const m of matched) members.add(path11.dirname(m));
1731
+ } else if (await readAuditInput(root, path11.posix.join(entry, "Cargo.toml")) !== null) {
1602
1732
  members.add(entry);
1603
1733
  }
1604
1734
  }
@@ -1652,18 +1782,18 @@ var MEMBERS_CAP;
1652
1782
  var init_stale_count = __esm({
1653
1783
  "src/audit/checks/stale-count.ts"() {
1654
1784
  "use strict";
1785
+ init_inputs();
1655
1786
  init_checks();
1656
1787
  MEMBERS_CAP = 50;
1657
1788
  }
1658
1789
  });
1659
1790
 
1660
1791
  // src/audit/checks/dead-command.ts
1661
- import fs7 from "fs/promises";
1662
- import path11 from "path";
1663
- import fg4 from "fast-glob";
1664
- async function scriptsOf(absManifest) {
1792
+ async function scriptsOf(root, manifest) {
1793
+ const raw = await readAuditInput(root, manifest);
1794
+ if (raw === null) return null;
1665
1795
  try {
1666
- const pkg = JSON.parse(await fs7.readFile(absManifest, "utf-8"));
1796
+ const pkg = JSON.parse(raw);
1667
1797
  return pkg && typeof pkg.scripts === "object" && pkg.scripts !== null ? Object.keys(pkg.scripts) : [];
1668
1798
  } catch {
1669
1799
  return null;
@@ -1675,7 +1805,7 @@ async function checkDeadCommands(ctx) {
1675
1805
  (doc) => doc.claims.commands.map((claim) => ({ doc, claim }))
1676
1806
  );
1677
1807
  if (commandClaims.length === 0) return result;
1678
- const rootScripts = await scriptsOf(path11.join(ctx.root, "package.json"));
1808
+ const rootScripts = await scriptsOf(ctx.root, "package.json");
1679
1809
  if (rootScripts === null) {
1680
1810
  result.skipped.push({
1681
1811
  check: "dead-command",
@@ -1692,7 +1822,7 @@ async function checkDeadCommands(ctx) {
1692
1822
  const manifests = await commandManifests(ctx.root);
1693
1823
  manifestsChecked = ["package.json", ...manifests.sort()];
1694
1824
  for (const manifest of manifests) {
1695
- const scripts = await scriptsOf(path11.join(ctx.root, manifest));
1825
+ const scripts = await scriptsOf(ctx.root, manifest);
1696
1826
  for (const name of scripts ?? []) workspaceScripts.add(name);
1697
1827
  }
1698
1828
  return workspaceScripts;
@@ -1718,23 +1848,24 @@ async function checkDeadCommands(ctx) {
1718
1848
  return result;
1719
1849
  }
1720
1850
  function commandManifests(root) {
1721
- return fg4("**/package.json", {
1722
- cwd: root,
1723
- ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", ".mason/reports/**", "package.json"]
1851
+ return auditGlob(root, "**/package.json", {
1852
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", ".mason/reports/**", "package.json"],
1853
+ label: "Command manifest discovery"
1724
1854
  });
1725
1855
  }
1726
1856
  var AVAILABLE_SCRIPTS_CAP;
1727
1857
  var init_dead_command = __esm({
1728
1858
  "src/audit/checks/dead-command.ts"() {
1729
1859
  "use strict";
1860
+ init_inputs();
1730
1861
  init_checks();
1731
1862
  AVAILABLE_SCRIPTS_CAP = 30;
1732
1863
  }
1733
1864
  });
1734
1865
 
1735
1866
  // src/audit/release-metadata.ts
1736
- import { execFile as execFile6 } from "child_process";
1737
- import { promisify as promisify6 } from "util";
1867
+ import { execFile as execFile7 } from "child_process";
1868
+ import { promisify as promisify7 } from "util";
1738
1869
  async function git(root, args) {
1739
1870
  return (await exec6("git", args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1740
1871
  }
@@ -1786,7 +1917,7 @@ var exec6;
1786
1917
  var init_release_metadata = __esm({
1787
1918
  "src/audit/release-metadata.ts"() {
1788
1919
  "use strict";
1789
- exec6 = promisify6(execFile6);
1920
+ exec6 = promisify7(execFile7);
1790
1921
  }
1791
1922
  });
1792
1923
 
@@ -2738,8 +2869,8 @@ If a build is interrupted, its partials remain in \`.mason/partial-snapshots/\`.
2738
2869
  });
2739
2870
 
2740
2871
  // src/review/cochange.ts
2741
- import { execFile as execFile11 } from "child_process";
2742
- import { promisify as promisify11 } from "util";
2872
+ import { execFile as execFile12 } from "child_process";
2873
+ import { promisify as promisify12 } from "util";
2743
2874
  async function buildMatrix(resolvedRoot) {
2744
2875
  try {
2745
2876
  const { stdout: shallow } = await exec11("git", ["rev-parse", "--is-shallow-repository"], { cwd: resolvedRoot });
@@ -2804,7 +2935,7 @@ var exec11, HISTORY_COMMITS, MIN_FILE_COMMITS, MIN_SHARED_COMMITS, MIN_COCHANGE_
2804
2935
  var init_cochange = __esm({
2805
2936
  "src/review/cochange.ts"() {
2806
2937
  "use strict";
2807
- exec11 = promisify11(execFile11);
2938
+ exec11 = promisify12(execFile12);
2808
2939
  HISTORY_COMMITS = 1500;
2809
2940
  MIN_FILE_COMMITS = 5;
2810
2941
  MIN_SHARED_COMMITS = 4;
@@ -3269,8 +3400,8 @@ var init_evidence = __esm({
3269
3400
  // src/review/review.ts
3270
3401
  import fs13 from "fs/promises";
3271
3402
  import path20 from "path";
3272
- import { execFile as execFile12 } from "child_process";
3273
- import { promisify as promisify12 } from "util";
3403
+ import { execFile as execFile13 } from "child_process";
3404
+ import { promisify as promisify13 } from "util";
3274
3405
  async function resolveMergeBase(resolvedRoot, base, head) {
3275
3406
  try {
3276
3407
  const { stdout } = await exec12("git", ["merge-base", base, head], {
@@ -3392,7 +3523,7 @@ var init_review = __esm({
3392
3523
  init_cochange();
3393
3524
  init_evidence();
3394
3525
  init_snapshot();
3395
- exec12 = promisify12(execFile12);
3526
+ exec12 = promisify13(execFile13);
3396
3527
  MAX_ANALYZED_FILES = 50;
3397
3528
  }
3398
3529
  });
@@ -3487,8 +3618,8 @@ __export(impact_exports, {
3487
3618
  analyzeImpact: () => analyzeImpact
3488
3619
  });
3489
3620
  import path21 from "path";
3490
- import { execFile as execFile13 } from "child_process";
3491
- import { promisify as promisify13 } from "util";
3621
+ import { execFile as execFile14 } from "child_process";
3622
+ import { promisify as promisify14 } from "util";
3492
3623
  async function analyzeImpact(rootDir, targetFiles) {
3493
3624
  const resolvedRoot = path21.resolve(rootDir);
3494
3625
  const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
@@ -3655,7 +3786,7 @@ var init_impact = __esm({
3655
3786
  "use strict";
3656
3787
  init_files();
3657
3788
  init_paths();
3658
- exec13 = promisify13(execFile13);
3789
+ exec13 = promisify14(execFile14);
3659
3790
  }
3660
3791
  });
3661
3792
 
@@ -4027,9 +4158,9 @@ __export(evidence_exports, {
4027
4158
  import fs15 from "fs/promises";
4028
4159
  import path23 from "path";
4029
4160
  import { createHash as createHash4 } from "crypto";
4030
- import { execFile as execFile14 } from "child_process";
4031
- import { promisify as promisify14 } from "util";
4032
- import fg6 from "fast-glob";
4161
+ import { execFile as execFile15 } from "child_process";
4162
+ import { promisify as promisify15 } from "util";
4163
+ import fg5 from "fast-glob";
4033
4164
  import { z as z11 } from "zod";
4034
4165
  async function git2(root, ...args) {
4035
4166
  return (await exec14("git", args, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout;
@@ -4045,34 +4176,13 @@ async function workspace(dir) {
4045
4176
  }
4046
4177
  return { root, gitDir, branch, directory: ".mason/reports/automation/" + hash([root, gitDir, branch]).slice(0, 24) };
4047
4178
  }
4048
- async function content(root, file) {
4049
- try {
4050
- const value = await readBoundedFile(await storePath(root, file), 10 * 1024 * 1024);
4051
- if (value === null) throw new Error("Unreadable or oversized audit input: " + file);
4052
- return value;
4053
- } catch (error) {
4054
- if (error.code === "ENOENT") return null;
4055
- throw error;
4056
- }
4057
- }
4058
4179
  async function readInputs(root) {
4059
- const [headText, docStatus, inventory, shallowPath, replacements] = await Promise.all([
4180
+ const [headText, docStatus, shallowPath, replacements] = await Promise.all([
4060
4181
  git2(root, "rev-parse", "HEAD"),
4061
4182
  git2(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...DOC_CANDIDATES),
4062
- fg6("**/*", {
4063
- cwd: root,
4064
- dot: true,
4065
- onlyFiles: false,
4066
- followSymbolicLinks: false,
4067
- objectMode: true,
4068
- ignore: SOURCE_IGNORE
4069
- }),
4070
4183
  git2(root, "rev-parse", "--git-path", "shallow"),
4071
4184
  git2(root, "for-each-ref", "--format=%(refname) %(objectname)", "refs/replace")
4072
4185
  ]);
4073
- const entries = inventory.filter((f) => !internal(f.path) && f.path !== ".git").sort((a, b) => a.path.localeCompare(b.path));
4074
- const files = entries.map((f) => f.path);
4075
- if (files.length > 1e5) throw new Error("Automation input inventory exceeds 100,000 paths; use an explicit scoped audit.");
4076
4186
  const head = headText.trim();
4077
4187
  let shallow = null;
4078
4188
  try {
@@ -4093,9 +4203,19 @@ async function readInputs(root) {
4093
4203
  claims.push([claim.path, await exists2(claim.path), await exists2(path23.dirname(claim.path))]);
4094
4204
  }
4095
4205
  }
4096
- if (entries.some((f) => f.dirent.isSymbolicLink())) throw new Error("Automation inventory contains a symbolic link; use an explicit audit to inspect its scope. No cached verification was recorded.");
4097
4206
  const combinedDocs = docContents.map(([, text2]) => text2 ?? "").join("\n");
4098
4207
  const countClaims = docContents.flatMap(([, text2]) => text2 ? extractClaims(text2).counts : []);
4208
+ const commandClaims = docContents.flatMap(([, text2]) => text2 ? extractClaims(text2).commands : []);
4209
+ const rootPackage = await content(root, "package.json");
4210
+ let rootScripts = null;
4211
+ if (rootPackage !== null) {
4212
+ try {
4213
+ const pkg = JSON.parse(rootPackage);
4214
+ rootScripts = pkg?.scripts && typeof pkg.scripts === "object" ? Object.keys(pkg.scripts) : [];
4215
+ } catch {
4216
+ }
4217
+ }
4218
+ const needsWorkspaceScripts = rootScripts !== null && commandClaims.some((claim) => !rootScripts.includes(claim.scriptName));
4099
4219
  const decisionDirectory = await storePath(root, ".mason/decisions");
4100
4220
  const decisionPresence = await fs15.lstat(decisionDirectory).then((stat) => stat.isDirectory() ? "directory" : "file", (error) => {
4101
4221
  if (error.code === "ENOENT") return "absent";
@@ -4104,16 +4224,19 @@ async function readInputs(root) {
4104
4224
  const [modules, counts, manifests, decisionFiles] = await Promise.all([
4105
4225
  combinedDocs ? moduleCandidates(root, combinedDocs) : [],
4106
4226
  Promise.all(countClaims.map((claim) => resolveCountSource(root, claim))),
4107
- commandManifests(root),
4108
- fg6(".mason/decisions/*.json", { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false })
4227
+ needsWorkspaceScripts ? commandManifests(root) : [],
4228
+ fg5(".mason/decisions/*.json", { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false })
4109
4229
  ]);
4110
- const packages = await Promise.all(["package.json", ...manifests.sort()].map(async (file) => [file, await content(root, file)]));
4111
- const decisions = await Promise.all(decisionFiles.sort().map(async (file) => [file, await content(root, file)]));
4230
+ const packages = [["package.json", rootPackage], ...await Promise.all(manifests.sort().map(async (file) => [file, await content(root, file)]))];
4231
+ const decisions = await Promise.all(decisionFiles.sort().map(async (file) => {
4232
+ await storePath(root, file);
4233
+ return [file, await content(root, file)];
4234
+ }));
4112
4235
  const [status, index2] = decisions.length ? await Promise.all([
4113
4236
  git2(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
4114
4237
  git2(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
4115
4238
  ]) : ["", ""];
4116
- const common = [2, engineVersion, head, shallow, replacements, docContents];
4239
+ const common = [3, engineVersion, head, shallow, replacements, docContents];
4117
4240
  const keys = {
4118
4241
  "deleted-reference": hash([common, claims, docStatus]),
4119
4242
  "new-module": hash([common, modules]),
@@ -4149,7 +4272,7 @@ function checkCache(raw, inputs) {
4149
4272
  return { version: 1, entries: canonical, digest: hash(canonical) };
4150
4273
  } };
4151
4274
  }
4152
- var exec14, engineVersion, hash, internal, cacheSchema;
4275
+ var exec14, engineVersion, hash, content, internal, cacheSchema;
4153
4276
  var init_evidence2 = __esm({
4154
4277
  "src/automation/evidence.ts"() {
4155
4278
  "use strict";
@@ -4157,14 +4280,15 @@ var init_evidence2 = __esm({
4157
4280
  init_claims();
4158
4281
  init_repair();
4159
4282
  init_checks();
4160
- init_files();
4283
+ init_inputs();
4161
4284
  init_new_module();
4162
4285
  init_stale_count();
4163
4286
  init_dead_command();
4164
4287
  init_storage();
4165
- exec14 = promisify14(execFile14);
4166
- engineVersion = true ? "0.14.0" : "development";
4288
+ exec14 = promisify15(execFile15);
4289
+ engineVersion = true ? "0.14.2" : "development";
4167
4290
  hash = (value) => createHash4("sha256").update(JSON.stringify(value)).digest("hex");
4291
+ content = readAuditInput;
4168
4292
  internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
4169
4293
  cacheSchema = z11.object({ version: z11.literal(1), entries: z11.record(z11.object({ key: z11.string(), result: checkResultSchema })), digest: z11.string() });
4170
4294
  }
@@ -4709,8 +4833,8 @@ import fs18 from "fs/promises";
4709
4833
  import path25 from "path";
4710
4834
  import { fileURLToPath } from "url";
4711
4835
  import { randomUUID as randomUUID5, createHash as createHash6 } from "crypto";
4712
- import { execFile as execFile15 } from "child_process";
4713
- import { promisify as promisify15 } from "util";
4836
+ import { execFile as execFile16 } from "child_process";
4837
+ import { promisify as promisify16 } from "util";
4714
4838
  async function packageRoot() {
4715
4839
  let directory = path25.dirname(fileURLToPath(import.meta.url));
4716
4840
  for (let i = 0; i < 8; i++) {
@@ -4818,7 +4942,7 @@ var init_runtime2 = __esm({
4818
4942
  init_storage();
4819
4943
  init_evidence2();
4820
4944
  init_bundle();
4821
- exec15 = promisify15(execFile15);
4945
+ exec15 = promisify16(execFile16);
4822
4946
  checksum = (bytes) => createHash6("sha256").update(bytes).digest("hex");
4823
4947
  BINARIES = ["dist/mason-auto.js", "dist/mason-mcp.js"];
4824
4948
  }
@@ -5533,8 +5657,8 @@ __export(config_exports, {
5533
5657
  import fs21 from "fs/promises";
5534
5658
  import path27 from "path";
5535
5659
  import os3 from "os";
5536
- import { execFile as execFile16 } from "child_process";
5537
- import { promisify as promisify16 } from "util";
5660
+ import { execFile as execFile17 } from "child_process";
5661
+ import { promisify as promisify17 } from "util";
5538
5662
  function configDir() {
5539
5663
  return path27.join(os3.homedir(), ".mason");
5540
5664
  }
@@ -5590,7 +5714,7 @@ var exec16, DEFAULT_MODELS;
5590
5714
  var init_config2 = __esm({
5591
5715
  "src/llm/config.ts"() {
5592
5716
  "use strict";
5593
- exec16 = promisify16(execFile16);
5717
+ exec16 = promisify17(execFile17);
5594
5718
  DEFAULT_MODELS = {
5595
5719
  claude: "claude-sonnet-4-20250514",
5596
5720
  gemini: "gemini-2.5-flash",
@@ -5789,8 +5913,8 @@ var init_diff = __esm({
5789
5913
  });
5790
5914
 
5791
5915
  // src/llm/providers.ts
5792
- import { execFile as execFile17, spawn } from "child_process";
5793
- import { promisify as promisify17 } from "util";
5916
+ import { execFile as execFile18, spawn } from "child_process";
5917
+ import { promisify as promisify18 } from "util";
5794
5918
  async function callLLM(config, userMessage, systemPrompt) {
5795
5919
  const model = config.model ?? getDefaultModel(config.provider);
5796
5920
  const system = systemPrompt ?? CLAUDE_MD_SYSTEM_PROMPT;
@@ -5952,7 +6076,7 @@ var init_providers = __esm({
5952
6076
  "src/llm/providers.ts"() {
5953
6077
  "use strict";
5954
6078
  init_config2();
5955
- exec17 = promisify17(execFile17);
6079
+ exec17 = promisify18(execFile18);
5956
6080
  CLAUDE_MD_SYSTEM_PROMPT = `You are Mason, a context engineering tool. You've been given a comprehensive analysis of a codebase including:
5957
6081
  - Git history stats (commit patterns, frequently changed files, stale directories)
5958
6082
  - Project structure (directory layout, file counts by type)
@@ -6291,8 +6415,8 @@ import { z as z19 } from "zod";
6291
6415
  init_repair();
6292
6416
  import fs23 from "fs/promises";
6293
6417
  import path29 from "path";
6294
- import { execFile as execFile18 } from "child_process";
6295
- import { promisify as promisify18 } from "util";
6418
+ import { execFile as execFile19 } from "child_process";
6419
+ import { promisify as promisify19 } from "util";
6296
6420
 
6297
6421
  // src/audit/cli.ts
6298
6422
  init_audit();
@@ -6386,15 +6510,15 @@ function formatFixPrompt(report, baselinePath) {
6386
6510
  }
6387
6511
 
6388
6512
  // src/analyzers/git-history.ts
6389
- import { execFile as execFile7 } from "child_process";
6390
- import { promisify as promisify7 } from "util";
6513
+ import { execFile as execFile8 } from "child_process";
6514
+ import { promisify as promisify8 } from "util";
6391
6515
 
6392
6516
  // src/analyzers/base.ts
6393
6517
  import fs11 from "fs/promises";
6394
- import fg5 from "fast-glob";
6518
+ import fg4 from "fast-glob";
6395
6519
  var BaseAnalyzer = class {
6396
6520
  async findFiles(patterns, root) {
6397
- return fg5(patterns, {
6521
+ return fg4(patterns, {
6398
6522
  cwd: root,
6399
6523
  ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
6400
6524
  absolute: true
@@ -6424,7 +6548,7 @@ var BaseAnalyzer = class {
6424
6548
  };
6425
6549
 
6426
6550
  // src/analyzers/git-history.ts
6427
- var exec7 = promisify7(execFile7);
6551
+ var exec7 = promisify8(execFile8);
6428
6552
  var GitHistoryAnalyzer = class extends BaseAnalyzer {
6429
6553
  name = "git-history";
6430
6554
  async analyze(context) {
@@ -6594,9 +6718,9 @@ async function runAll(context) {
6594
6718
  }
6595
6719
 
6596
6720
  // src/utils/git.ts
6597
- import { execFile as execFile8 } from "child_process";
6598
- import { promisify as promisify8 } from "util";
6599
- var exec8 = promisify8(execFile8);
6721
+ import { execFile as execFile9 } from "child_process";
6722
+ import { promisify as promisify9 } from "util";
6723
+ var exec8 = promisify9(execFile9);
6600
6724
  async function isGitRepo(dir) {
6601
6725
  try {
6602
6726
  await exec8("git", ["rev-parse", "--git-dir"], { cwd: dir });
@@ -6609,9 +6733,9 @@ async function isGitRepo(dir) {
6609
6733
  // src/mcp/sampler.ts
6610
6734
  init_files();
6611
6735
  import path18 from "path";
6612
- import { execFile as execFile9 } from "child_process";
6613
- import { promisify as promisify9 } from "util";
6614
- var exec9 = promisify9(execFile9);
6736
+ import { execFile as execFile10 } from "child_process";
6737
+ import { promisify as promisify10 } from "util";
6738
+ var exec9 = promisify10(execFile10);
6615
6739
  var CONFIG_FILES = [
6616
6740
  // Build & project config
6617
6741
  "package.json",
@@ -6903,10 +7027,10 @@ init_drift();
6903
7027
  init_paths();
6904
7028
  init_files();
6905
7029
  import { createHash as createHash3 } from "crypto";
6906
- import { execFile as execFile10 } from "child_process";
6907
- import { promisify as promisify10 } from "util";
7030
+ import { execFile as execFile11 } from "child_process";
7031
+ import { promisify as promisify11 } from "util";
6908
7032
  import { z as z4 } from "zod";
6909
- var exec10 = promisify10(execFile10);
7033
+ var exec10 = promisify11(execFile11);
6910
7034
  var requestSchema = z4.object({
6911
7035
  id: z4.string().regex(/^[a-zA-Z0-9_-]+$/),
6912
7036
  action: z4.enum(["prepare", "accept", "reaffirm", "retire"]).default("prepare"),
@@ -7187,7 +7311,7 @@ function batchIdFor(offset) {
7187
7311
  // src/mcp/tools.ts
7188
7312
  init_init();
7189
7313
  init_onboarding();
7190
- var exec18 = promisify18(execFile18);
7314
+ var exec18 = promisify19(execFile19);
7191
7315
  async function buildContext(dir) {
7192
7316
  return {
7193
7317
  rootDir: dir,
@@ -8113,7 +8237,7 @@ function createMcpServer() {
8113
8237
  const server = new McpServer(
8114
8238
  {
8115
8239
  name: "mason",
8116
- version: "0.14.0"
8240
+ version: "0.14.2"
8117
8241
  },
8118
8242
  {
8119
8243
  instructions: '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.'