@massu/core 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -118,13 +118,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
118
118
  name: parsed.project.name,
119
119
  root: projectRoot
120
120
  },
121
+ // Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
122
+ // `framework.python`) survive into the consumer-visible Config. Then override
123
+ // the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
124
+ // variant-resolution `pickVariant` (install-commands.ts) cannot see the
125
+ // top-level passthrough language blocks.
121
126
  framework: {
122
- type: fw.type,
127
+ ...fw,
123
128
  router,
124
129
  orm,
125
- ui,
126
- primary: fw.primary,
127
- languages: fw.languages
130
+ ui
128
131
  },
129
132
  paths: parsed.paths,
130
133
  toolPrefix: parsed.toolPrefix,
@@ -414,6 +417,7 @@ var init_config = __esm({
414
417
  PathsConfigSchema = z.object({
415
418
  source: z.string().default("src"),
416
419
  aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
420
+ monorepo_roots: z.array(z.string()).optional(),
417
421
  routers: z.string().optional(),
418
422
  routerRoot: z.string().optional(),
419
423
  pages: z.string().optional(),
@@ -1680,15 +1684,30 @@ var init_memory_file_ingest = __esm({
1680
1684
  // src/commands/install-commands.ts
1681
1685
  var install_commands_exports = {};
1682
1686
  __export(install_commands_exports, {
1687
+ hashContent: () => hashContent,
1683
1688
  installAll: () => installAll,
1684
1689
  installCommands: () => installCommands,
1690
+ loadManifest: () => loadManifest,
1691
+ pickVariant: () => pickVariant,
1685
1692
  resolveAssetDir: () => resolveAssetDir,
1686
1693
  resolveCommandsDir: () => resolveCommandsDir,
1687
- runInstallCommands: () => runInstallCommands
1694
+ runInstallCommands: () => runInstallCommands,
1695
+ runWithManifest: () => runWithManifest,
1696
+ saveManifest: () => saveManifest,
1697
+ syncDirectory: () => syncDirectory
1688
1698
  });
1689
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync2, readdirSync as readdirSync2, statSync } from "fs";
1690
- import { resolve as resolve3, dirname as dirname3 } from "path";
1699
+ import {
1700
+ existsSync as existsSync4,
1701
+ readFileSync as readFileSync3,
1702
+ writeFileSync,
1703
+ mkdirSync as mkdirSync2,
1704
+ readdirSync as readdirSync2,
1705
+ statSync,
1706
+ renameSync
1707
+ } from "fs";
1708
+ import { resolve as resolve3, dirname as dirname3, join as join2 } from "path";
1691
1709
  import { fileURLToPath } from "url";
1710
+ import { createHash } from "crypto";
1692
1711
  function resolveAssetDir(assetName) {
1693
1712
  const cwd = process.cwd();
1694
1713
  const nodeModulesPath = resolve3(cwd, "node_modules/@massu/core", assetName);
@@ -1708,42 +1727,189 @@ function resolveAssetDir(assetName) {
1708
1727
  function resolveCommandsDir() {
1709
1728
  return resolveAssetDir("commands");
1710
1729
  }
1711
- function syncDirectory(sourceDir, targetDir) {
1712
- const stats = { installed: 0, updated: 0, skipped: 0 };
1730
+ function hashContent(content) {
1731
+ return createHash("sha256").update(content, "utf-8").digest("hex");
1732
+ }
1733
+ function loadManifest(claudeDir) {
1734
+ const path = resolve3(claudeDir, MANIFEST_RELPATH);
1735
+ if (!existsSync4(path)) {
1736
+ return emptyManifest();
1737
+ }
1738
+ try {
1739
+ const raw = readFileSync3(path, "utf-8");
1740
+ const parsed = JSON.parse(raw);
1741
+ if (!parsed || typeof parsed !== "object" || !parsed.entries) {
1742
+ return emptyManifest();
1743
+ }
1744
+ return parsed;
1745
+ } catch {
1746
+ return emptyManifest();
1747
+ }
1748
+ }
1749
+ function saveManifest(claudeDir, manifest) {
1750
+ const dir = resolve3(claudeDir, ".massu");
1751
+ if (!existsSync4(dir)) {
1752
+ mkdirSync2(dir, { recursive: true });
1753
+ }
1754
+ const finalPath = resolve3(dir, "install-manifest.json");
1755
+ const tempPath = finalPath + ".tmp";
1756
+ manifest.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
1757
+ writeFileSync(tempPath, JSON.stringify(manifest, null, 2), "utf-8");
1758
+ renameSync(tempPath, finalPath);
1759
+ }
1760
+ function emptyManifest() {
1761
+ return {
1762
+ version: MANIFEST_VERSION,
1763
+ generatedBy: "@massu/core",
1764
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1765
+ entries: {}
1766
+ };
1767
+ }
1768
+ function runWithManifest(claudeDir, fn) {
1769
+ const manifest = loadManifest(claudeDir);
1770
+ const result = fn(manifest);
1771
+ saveManifest(claudeDir, manifest);
1772
+ return result;
1773
+ }
1774
+ function pickVariant(baseName, sourceDir, framework) {
1775
+ const candidates = [];
1776
+ const primary = framework.primary ?? framework.type;
1777
+ if (primary && primary !== "multi") {
1778
+ candidates.push(primary);
1779
+ }
1780
+ if (framework.languages) {
1781
+ for (const lang of Object.keys(framework.languages)) {
1782
+ const entry = framework.languages[lang];
1783
+ if (entry && typeof entry.framework === "string" && entry.framework.length > 0) {
1784
+ if (!candidates.includes(lang)) {
1785
+ candidates.push(lang);
1786
+ }
1787
+ }
1788
+ }
1789
+ }
1790
+ const passthrough = framework;
1791
+ for (const lang of PASSTHROUGH_LANG_KEYS) {
1792
+ if (candidates.includes(lang)) continue;
1793
+ const block = passthrough[lang];
1794
+ if (block && typeof block === "object") {
1795
+ const fw = block.framework;
1796
+ if (typeof fw === "string" && fw.length > 0) {
1797
+ candidates.push(lang);
1798
+ }
1799
+ }
1800
+ }
1801
+ for (const cand of candidates) {
1802
+ const path = resolve3(sourceDir, `${baseName}.${cand}.md`);
1803
+ if (existsSync4(path)) {
1804
+ return { kind: "hit", suffix: `.${cand}` };
1805
+ }
1806
+ }
1807
+ const defaultPath = resolve3(sourceDir, `${baseName}.md`);
1808
+ if (existsSync4(defaultPath)) {
1809
+ return { kind: "hit", suffix: "" };
1810
+ }
1811
+ if (framework.type === "multi" && !framework.primary) {
1812
+ process.stderr.write(
1813
+ "massu: warning - framework.type=multi but framework.primary is undefined; falling back to default templates\n"
1814
+ );
1815
+ return { kind: "fallback", reason: "multi-without-primary" };
1816
+ }
1817
+ return { kind: "miss" };
1818
+ }
1819
+ function isVariantFilename(entry) {
1820
+ return /^[^.]+\.[^.]+\.md$/.test(entry);
1821
+ }
1822
+ function syncDirectory(sourceDir, targetDir, framework, manifest, manifestKeyPrefix, topLevel = true) {
1823
+ const stats = { installed: 0, updated: 0, skipped: 0, kept: 0 };
1713
1824
  if (!existsSync4(targetDir)) {
1714
1825
  mkdirSync2(targetDir, { recursive: true });
1715
1826
  }
1716
1827
  const entries = readdirSync2(sourceDir);
1717
1828
  for (const entry of entries) {
1718
1829
  const sourcePath = resolve3(sourceDir, entry);
1719
- const targetPath = resolve3(targetDir, entry);
1720
1830
  const entryStat = statSync(sourcePath);
1721
1831
  if (entryStat.isDirectory()) {
1722
- const subStats = syncDirectory(sourcePath, targetPath);
1832
+ const subTargetDir = resolve3(targetDir, entry);
1833
+ const subPrefix = manifestKeyPrefix === "" ? entry : `${manifestKeyPrefix}/${entry}`;
1834
+ const subStats = syncDirectory(
1835
+ sourcePath,
1836
+ subTargetDir,
1837
+ framework,
1838
+ manifest,
1839
+ subPrefix,
1840
+ false
1841
+ );
1723
1842
  stats.installed += subStats.installed;
1724
1843
  stats.updated += subStats.updated;
1725
1844
  stats.skipped += subStats.skipped;
1726
- } else if (entry.endsWith(".md")) {
1727
- const sourceContent = readFileSync3(sourcePath, "utf-8");
1728
- if (existsSync4(targetPath)) {
1729
- const existingContent = readFileSync3(targetPath, "utf-8");
1730
- if (existingContent === sourceContent) {
1731
- stats.skipped++;
1732
- continue;
1733
- }
1734
- writeFileSync(targetPath, sourceContent, "utf-8");
1735
- stats.updated++;
1736
- } else {
1737
- writeFileSync(targetPath, sourceContent, "utf-8");
1738
- stats.installed++;
1845
+ stats.kept += subStats.kept;
1846
+ continue;
1847
+ }
1848
+ if (!entry.endsWith(".md")) continue;
1849
+ let sourceFilename = entry;
1850
+ let baseName = entry.slice(0, -".md".length);
1851
+ if (topLevel) {
1852
+ if (isVariantFilename(entry)) continue;
1853
+ const choice = pickVariant(baseName, sourceDir, framework);
1854
+ if (choice.kind === "miss") {
1855
+ continue;
1739
1856
  }
1857
+ const suffix = choice.kind === "hit" ? choice.suffix : "";
1858
+ sourceFilename = suffix === "" ? `${baseName}.md` : `${baseName}${suffix}.md`;
1859
+ }
1860
+ const resolvedSourcePath = resolve3(sourceDir, sourceFilename);
1861
+ if (!existsSync4(resolvedSourcePath)) {
1862
+ continue;
1863
+ }
1864
+ const targetFilename = topLevel ? `${baseName}.md` : entry;
1865
+ const targetPath = resolve3(targetDir, targetFilename);
1866
+ const sourceContent = readFileSync3(resolvedSourcePath, "utf-8");
1867
+ const sourceHash = hashContent(sourceContent);
1868
+ const manifestKey = manifestKeyPrefix === "" ? targetFilename : `${manifestKeyPrefix}/${targetFilename}`;
1869
+ const lastInstalledHash = manifest.entries[manifestKey];
1870
+ if (existsSync4(targetPath)) {
1871
+ const existingContent = readFileSync3(targetPath, "utf-8");
1872
+ const existingHash = hashContent(existingContent);
1873
+ if (existingHash === sourceHash) {
1874
+ manifest.entries[manifestKey] = sourceHash;
1875
+ stats.skipped++;
1876
+ continue;
1877
+ }
1878
+ if (lastInstalledHash === void 0) {
1879
+ manifest.entries[manifestKey] = existingHash;
1880
+ process.stderr.write(
1881
+ `First-install heuristic: keeping existing ${targetPath} (differs from upstream).
1882
+ To accept upstream: rm ${targetPath} && npx massu install-commands
1883
+ `
1884
+ );
1885
+ stats.kept++;
1886
+ continue;
1887
+ }
1888
+ if (existingHash !== lastInstalledHash) {
1889
+ process.stderr.write(
1890
+ `${targetFilename} has local edits - kept your version.
1891
+ To accept upstream: rm ${targetPath} && npx massu install-commands
1892
+ To diff: diff ${targetPath} <(npx massu show-template ${baseName})
1893
+ `
1894
+ );
1895
+ stats.kept++;
1896
+ continue;
1897
+ }
1898
+ writeFileSync(targetPath, sourceContent, "utf-8");
1899
+ manifest.entries[manifestKey] = sourceHash;
1900
+ stats.updated++;
1901
+ } else {
1902
+ writeFileSync(targetPath, sourceContent, "utf-8");
1903
+ manifest.entries[manifestKey] = sourceHash;
1904
+ stats.installed++;
1740
1905
  }
1741
1906
  }
1742
1907
  return stats;
1743
1908
  }
1744
1909
  function installCommands(projectRoot) {
1745
1910
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
1746
- const targetDir = resolve3(projectRoot, claudeDirName, "commands");
1911
+ const claudeDir = resolve3(projectRoot, claudeDirName);
1912
+ const targetDir = resolve3(claudeDir, "commands");
1747
1913
  if (!existsSync4(targetDir)) {
1748
1914
  mkdirSync2(targetDir, { recursive: true });
1749
1915
  }
@@ -1751,9 +1917,13 @@ function installCommands(projectRoot) {
1751
1917
  if (!sourceDir) {
1752
1918
  console.error(" ERROR: Could not find massu commands directory.");
1753
1919
  console.error(" Try reinstalling: npm install @massu/core");
1754
- return { installed: 0, updated: 0, skipped: 0, commandsDir: targetDir };
1920
+ return { installed: 0, updated: 0, skipped: 0, kept: 0, commandsDir: targetDir };
1755
1921
  }
1756
- const stats = syncDirectory(sourceDir, targetDir);
1922
+ const framework = getConfig().framework;
1923
+ const stats = runWithManifest(
1924
+ claudeDir,
1925
+ (manifest) => syncDirectory(sourceDir, targetDir, framework, manifest, "commands", true)
1926
+ );
1757
1927
  return { ...stats, commandsDir: targetDir };
1758
1928
  }
1759
1929
  function installAll(projectRoot) {
@@ -1763,19 +1933,36 @@ function installAll(projectRoot) {
1763
1933
  let totalInstalled = 0;
1764
1934
  let totalUpdated = 0;
1765
1935
  let totalSkipped = 0;
1766
- for (const assetType of ASSET_TYPES) {
1767
- const sourceDir = resolveAssetDir(assetType.name);
1768
- if (!sourceDir) {
1769
- continue;
1936
+ let totalKept = 0;
1937
+ const framework = getConfig().framework;
1938
+ runWithManifest(claudeDir, (manifest) => {
1939
+ for (const assetType of ASSET_TYPES) {
1940
+ const sourceDir = resolveAssetDir(assetType.name);
1941
+ if (!sourceDir) continue;
1942
+ const targetDir = resolve3(claudeDir, assetType.targetSubdir);
1943
+ const stats = syncDirectory(
1944
+ sourceDir,
1945
+ targetDir,
1946
+ framework,
1947
+ manifest,
1948
+ assetType.targetSubdir,
1949
+ true
1950
+ );
1951
+ assets[assetType.name] = stats;
1952
+ totalInstalled += stats.installed;
1953
+ totalUpdated += stats.updated;
1954
+ totalSkipped += stats.skipped;
1955
+ totalKept += stats.kept;
1770
1956
  }
1771
- const targetDir = resolve3(claudeDir, assetType.targetSubdir);
1772
- const stats = syncDirectory(sourceDir, targetDir);
1773
- assets[assetType.name] = stats;
1774
- totalInstalled += stats.installed;
1775
- totalUpdated += stats.updated;
1776
- totalSkipped += stats.skipped;
1777
- }
1778
- return { assets, totalInstalled, totalUpdated, totalSkipped, claudeDir };
1957
+ });
1958
+ return {
1959
+ assets,
1960
+ totalInstalled,
1961
+ totalUpdated,
1962
+ totalSkipped,
1963
+ totalKept,
1964
+ claudeDir
1965
+ };
1779
1966
  }
1780
1967
  async function runInstallCommands() {
1781
1968
  const projectRoot = process.cwd();
@@ -1789,23 +1976,29 @@ async function runInstallCommands() {
1789
1976
  if (!stats) {
1790
1977
  continue;
1791
1978
  }
1792
- const total = stats.installed + stats.updated + stats.skipped;
1979
+ const total = stats.installed + stats.updated + stats.skipped + stats.kept;
1793
1980
  if (total === 0) continue;
1794
1981
  const parts = [];
1795
1982
  if (stats.installed > 0) parts.push(`${stats.installed} new`);
1796
1983
  if (stats.updated > 0) parts.push(`${stats.updated} updated`);
1797
1984
  if (stats.skipped > 0) parts.push(`${stats.skipped} current`);
1985
+ if (stats.kept > 0) parts.push(`${stats.kept} kept (local edits)`);
1798
1986
  const description = assetType.description;
1799
1987
  console.log(` ${description}: ${parts.join(", ")} (${total} total)`);
1800
1988
  }
1801
- const grandTotal = result.totalInstalled + result.totalUpdated + result.totalSkipped;
1989
+ const grandTotal = result.totalInstalled + result.totalUpdated + result.totalSkipped + result.totalKept;
1802
1990
  console.log("");
1803
1991
  console.log(` ${grandTotal} total files synced to ${result.claudeDir}`);
1992
+ if (result.totalKept > 0) {
1993
+ console.log(
1994
+ ` ${result.totalKept} file(s) had local edits and were preserved (see stderr above).`
1995
+ );
1996
+ }
1804
1997
  console.log("");
1805
1998
  console.log(" Restart your Claude Code session to use them.");
1806
1999
  console.log("");
1807
2000
  }
1808
- var __filename, __dirname, ASSET_TYPES;
2001
+ var __filename, __dirname, ASSET_TYPES, MANIFEST_VERSION, MANIFEST_RELPATH, PASSTHROUGH_LANG_KEYS;
1809
2002
  var init_install_commands = __esm({
1810
2003
  "src/commands/install-commands.ts"() {
1811
2004
  "use strict";
@@ -1819,6 +2012,16 @@ var init_install_commands = __esm({
1819
2012
  { name: "protocols", targetSubdir: "protocols", description: "protocol files" },
1820
2013
  { name: "reference", targetSubdir: "reference", description: "reference files" }
1821
2014
  ];
2015
+ MANIFEST_VERSION = 1;
2016
+ MANIFEST_RELPATH = join2(".massu", "install-manifest.json");
2017
+ PASSTHROUGH_LANG_KEYS = [
2018
+ "typescript",
2019
+ "javascript",
2020
+ "python",
2021
+ "swift",
2022
+ "rust",
2023
+ "go"
2024
+ ];
1822
2025
  }
1823
2026
  });
1824
2027
 
@@ -7797,41 +8000,41 @@ var require_queue = __commonJS({
7797
8000
  queue.drained = drained;
7798
8001
  return queue;
7799
8002
  function push(value) {
7800
- var p19 = new Promise(function(resolve25, reject) {
8003
+ var p19 = new Promise(function(resolve26, reject) {
7801
8004
  pushCb(value, function(err, result) {
7802
8005
  if (err) {
7803
8006
  reject(err);
7804
8007
  return;
7805
8008
  }
7806
- resolve25(result);
8009
+ resolve26(result);
7807
8010
  });
7808
8011
  });
7809
8012
  p19.catch(noop);
7810
8013
  return p19;
7811
8014
  }
7812
8015
  function unshift(value) {
7813
- var p19 = new Promise(function(resolve25, reject) {
8016
+ var p19 = new Promise(function(resolve26, reject) {
7814
8017
  unshiftCb(value, function(err, result) {
7815
8018
  if (err) {
7816
8019
  reject(err);
7817
8020
  return;
7818
8021
  }
7819
- resolve25(result);
8022
+ resolve26(result);
7820
8023
  });
7821
8024
  });
7822
8025
  p19.catch(noop);
7823
8026
  return p19;
7824
8027
  }
7825
8028
  function drained() {
7826
- var p19 = new Promise(function(resolve25) {
8029
+ var p19 = new Promise(function(resolve26) {
7827
8030
  process.nextTick(function() {
7828
8031
  if (queue.idle()) {
7829
- resolve25();
8032
+ resolve26();
7830
8033
  } else {
7831
8034
  var previousDrain = queue.drain;
7832
8035
  queue.drain = function() {
7833
8036
  if (typeof previousDrain === "function") previousDrain();
7834
- resolve25();
8037
+ resolve26();
7835
8038
  queue.drain = previousDrain;
7836
8039
  };
7837
8040
  }
@@ -8317,9 +8520,9 @@ var require_stream3 = __commonJS({
8317
8520
  });
8318
8521
  }
8319
8522
  _getStat(filepath) {
8320
- return new Promise((resolve25, reject) => {
8523
+ return new Promise((resolve26, reject) => {
8321
8524
  this._stat(filepath, this._fsStatSettings, (error, stats) => {
8322
- return error === null ? resolve25(stats) : reject(error);
8525
+ return error === null ? resolve26(stats) : reject(error);
8323
8526
  });
8324
8527
  });
8325
8528
  }
@@ -8343,10 +8546,10 @@ var require_async5 = __commonJS({
8343
8546
  this._readerStream = new stream_1.default(this._settings);
8344
8547
  }
8345
8548
  dynamic(root, options) {
8346
- return new Promise((resolve25, reject) => {
8549
+ return new Promise((resolve26, reject) => {
8347
8550
  this._walkAsync(root, options, (error, entries) => {
8348
8551
  if (error === null) {
8349
- resolve25(entries);
8552
+ resolve26(entries);
8350
8553
  } else {
8351
8554
  reject(error);
8352
8555
  }
@@ -8356,10 +8559,10 @@ var require_async5 = __commonJS({
8356
8559
  async static(patterns, options) {
8357
8560
  const entries = [];
8358
8561
  const stream = this._readerStream.static(patterns, options);
8359
- return new Promise((resolve25, reject) => {
8562
+ return new Promise((resolve26, reject) => {
8360
8563
  stream.once("error", reject);
8361
8564
  stream.on("data", (entry) => entries.push(entry));
8362
- stream.once("end", () => resolve25(entries));
8565
+ stream.once("end", () => resolve26(entries));
8363
8566
  });
8364
8567
  }
8365
8568
  };
@@ -9022,6 +9225,13 @@ import { resolve as resolve4 } from "path";
9022
9225
  function extsFor(language) {
9023
9226
  return EXTENSIONS[language] ?? [];
9024
9227
  }
9228
+ function extsWithFallback(language, fallbackTsForJs) {
9229
+ const base = extsFor(language);
9230
+ if (language === "javascript" && fallbackTsForJs) {
9231
+ return [...base, "ts", "tsx"];
9232
+ }
9233
+ return base;
9234
+ }
9025
9235
  function isTestPath(language, path) {
9026
9236
  const segments = path.split("/");
9027
9237
  for (const seg of segments) {
@@ -9043,10 +9253,11 @@ function isInsideRoot(root, candidate) {
9043
9253
  return false;
9044
9254
  }
9045
9255
  }
9046
- function detectSourceDirs(projectRoot, languages) {
9256
+ function detectSourceDirs(projectRoot, languages, opts) {
9257
+ const fallbackTsForJs = opts?.fallbackTsForJs ?? false;
9047
9258
  const out = {};
9048
9259
  for (const lang of languages) {
9049
- const exts = extsFor(lang);
9260
+ const exts = extsWithFallback(lang, fallbackTsForJs);
9050
9261
  if (exts.length === 0) continue;
9051
9262
  const patterns = exts.map((e2) => `**/*.${e2}`);
9052
9263
  let files;
@@ -9644,8 +9855,9 @@ async function runDetection(projectRoot, overrides) {
9644
9855
  const languages = Array.from(
9645
9856
  new Set(pkg.manifests.map((m3) => m3.language))
9646
9857
  );
9858
+ const fallbackTsForJs = languages.includes("javascript") && !languages.includes("typescript");
9647
9859
  const [sourceDirs, monorepo] = await Promise.all([
9648
- Promise.resolve(detectSourceDirs(projectRoot, languages)),
9860
+ Promise.resolve(detectSourceDirs(projectRoot, languages, { fallbackTsForJs })),
9649
9861
  Promise.resolve(detectMonorepo(projectRoot))
9650
9862
  ]);
9651
9863
  const domains = inferDomains(projectRoot, monorepo, sourceDirs);
@@ -9687,7 +9899,7 @@ var init_detect = __esm({
9687
9899
  });
9688
9900
 
9689
9901
  // src/detect/drift.ts
9690
- import { createHash } from "crypto";
9902
+ import { createHash as createHash2 } from "crypto";
9691
9903
  function summarizeDetection(det) {
9692
9904
  const languages = Array.from(new Set(det.manifests.map((m3) => m3.language))).sort();
9693
9905
  const frameworks = {};
@@ -9718,7 +9930,7 @@ function summarizeDetection(det) {
9718
9930
  function computeFingerprint(det) {
9719
9931
  const data = summarizeDetection(det);
9720
9932
  const stable = JSON.stringify(data, Object.keys(data).sort());
9721
- return createHash("sha256").update(stable).digest("hex");
9933
+ return createHash2("sha256").update(stable).digest("hex");
9722
9934
  }
9723
9935
  function stringOf(v3) {
9724
9936
  if (typeof v3 === "string") return v3;
@@ -10876,7 +11088,7 @@ __export(init_exports, {
10876
11088
  validateWrittenConfig: () => validateWrittenConfig,
10877
11089
  writeConfigAtomic: () => writeConfigAtomic
10878
11090
  });
10879
- import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync6, renameSync, rmSync, statSync as statSync4, chmodSync } from "fs";
11091
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync6, renameSync as renameSync2, rmSync, statSync as statSync4, chmodSync } from "fs";
10880
11092
  import { resolve as resolve5, basename as basename2, dirname as dirname4 } from "path";
10881
11093
  import { fileURLToPath as fileURLToPath2 } from "url";
10882
11094
  import { homedir as homedir2 } from "os";
@@ -10978,6 +11190,9 @@ function detectPython(projectRoot) {
10978
11190
  return result;
10979
11191
  }
10980
11192
  function generateConfig(projectRoot, framework) {
11193
+ console.warn(
11194
+ "[@massu/core] generateConfig() is deprecated since 1.2.1 \u2014 use buildConfigFromDetection instead. It cannot produce valid configs for monorepos."
11195
+ );
10981
11196
  const configPath = resolve5(projectRoot, "massu.config.yaml");
10982
11197
  if (existsSync8(configPath)) {
10983
11198
  return false;
@@ -11028,6 +11243,20 @@ ${yamlStringify(config)}`;
11028
11243
  writeFileSync2(configPath, yamlContent, "utf-8");
11029
11244
  return true;
11030
11245
  }
11246
+ function monorepoCommonRoot(packages) {
11247
+ const roots = monorepoDistinctRoots(packages);
11248
+ return roots.length === 1 ? roots[0] : ".";
11249
+ }
11250
+ function monorepoDistinctRoots(packages) {
11251
+ const set = /* @__PURE__ */ new Set();
11252
+ for (const p19 of packages) {
11253
+ const parts = p19.path.split("/");
11254
+ if (parts.length > 1 && parts[0] !== "" && parts[0] !== ".") {
11255
+ set.add(parts[0]);
11256
+ }
11257
+ }
11258
+ return [...set].sort();
11259
+ }
11031
11260
  function buildConfigFromDetection(opts) {
11032
11261
  const { projectRoot, detection } = opts;
11033
11262
  if (!detection) {
@@ -11072,6 +11301,16 @@ function buildConfigFromDetection(opts) {
11072
11301
  const primaryDirs = detection.sourceDirs[primary]?.source_dirs ?? [];
11073
11302
  if (primaryDirs.length > 0) {
11074
11303
  pathsSource = primaryDirs[0];
11304
+ } else if (detection.monorepo.type !== "single" && detection.monorepo.packages.length > 0) {
11305
+ pathsSource = monorepoCommonRoot(detection.monorepo.packages);
11306
+ }
11307
+ }
11308
+ let monorepoRoots;
11309
+ if (detection.monorepo.type !== "single") {
11310
+ if (detection.monorepo.packages.length > 0) {
11311
+ monorepoRoots = monorepoDistinctRoots(detection.monorepo.packages);
11312
+ } else if (pathsSource !== "src" && pathsSource !== ".") {
11313
+ monorepoRoots = [pathsSource];
11075
11314
  }
11076
11315
  }
11077
11316
  const verification = {};
@@ -11108,6 +11347,13 @@ function buildConfigFromDetection(opts) {
11108
11347
  if (Object.keys(languageEntries).length > 0) {
11109
11348
  frameworkBlock.languages = languageEntries;
11110
11349
  }
11350
+ const pathsBlock = {
11351
+ source: pathsSource,
11352
+ aliases: { "@": pathsSource }
11353
+ };
11354
+ if (monorepoRoots && monorepoRoots.length > 0) {
11355
+ pathsBlock.monorepo_roots = monorepoRoots;
11356
+ }
11111
11357
  const config = {
11112
11358
  schema_version: 2,
11113
11359
  project: {
@@ -11115,10 +11361,7 @@ function buildConfigFromDetection(opts) {
11115
11361
  root: "auto"
11116
11362
  },
11117
11363
  framework: frameworkBlock,
11118
- paths: {
11119
- source: pathsSource,
11120
- aliases: { "@": pathsSource }
11121
- },
11364
+ paths: pathsBlock,
11122
11365
  toolPrefix: "massu",
11123
11366
  domains,
11124
11367
  rules: []
@@ -11167,7 +11410,7 @@ function writeConfigAtomic(configPath, content) {
11167
11410
  if (parsed === null || typeof parsed !== "object") {
11168
11411
  throw new Error("Generated config is not a valid YAML object");
11169
11412
  }
11170
- renameSync(tmpPath, configPath);
11413
+ renameSync2(tmpPath, configPath);
11171
11414
  if (existingMode !== void 0) {
11172
11415
  try {
11173
11416
  chmodSync(configPath, existingMode);
@@ -11225,6 +11468,15 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
11225
11468
  }
11226
11469
  }
11227
11470
  }
11471
+ const mRoots = cfg.paths.monorepo_roots;
11472
+ if (Array.isArray(mRoots)) {
11473
+ for (const r2 of mRoots) {
11474
+ if (typeof r2 !== "string" || r2 === ".") continue;
11475
+ if (!existsSync8(resolve5(projectRoot, r2))) {
11476
+ return `paths.monorepo_roots '${r2}' does not exist on disk`;
11477
+ }
11478
+ }
11479
+ }
11228
11480
  }
11229
11481
  } catch (err) {
11230
11482
  return err instanceof Error ? err.message : String(err);
@@ -11736,7 +11988,7 @@ var init_init = __esm({
11736
11988
  });
11737
11989
 
11738
11990
  // src/license.ts
11739
- import { createHash as createHash2 } from "crypto";
11991
+ import { createHash as createHash3 } from "crypto";
11740
11992
  function tierLevel(tier) {
11741
11993
  return TIER_LEVELS[tier] ?? 0;
11742
11994
  }
@@ -11761,7 +12013,7 @@ function annotateToolDefinitions(defs) {
11761
12013
  });
11762
12014
  }
11763
12015
  async function validateLicense(apiKey) {
11764
- const keyHash = createHash2("sha256").update(apiKey).digest("hex");
12016
+ const keyHash = createHash3("sha256").update(apiKey).digest("hex");
11765
12017
  const memDb = getMemoryDb();
11766
12018
  try {
11767
12019
  const cached = memDb.prepare(
@@ -11822,7 +12074,7 @@ async function validateLicense(apiKey) {
11822
12074
  }
11823
12075
  }
11824
12076
  function updateLicenseCache(apiKey, tier, validUntil, features = []) {
11825
- const keyHash = createHash2("sha256").update(apiKey).digest("hex");
12077
+ const keyHash = createHash3("sha256").update(apiKey).digest("hex");
11826
12078
  const memDb = getMemoryDb();
11827
12079
  try {
11828
12080
  memDb.prepare(`
@@ -12438,13 +12690,64 @@ var init_install_hooks = __esm({
12438
12690
  }
12439
12691
  });
12440
12692
 
12693
+ // src/commands/show-template.ts
12694
+ var show_template_exports = {};
12695
+ __export(show_template_exports, {
12696
+ runShowTemplate: () => runShowTemplate
12697
+ });
12698
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
12699
+ import { resolve as resolve7 } from "path";
12700
+ function normalizeBaseName(input) {
12701
+ return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
12702
+ }
12703
+ async function runShowTemplate(args2) {
12704
+ const rawName = args2[0];
12705
+ if (!rawName) {
12706
+ process.stderr.write("massu: show-template requires a template name\n");
12707
+ process.stderr.write(" usage: massu show-template <name>\n");
12708
+ process.exit(1);
12709
+ return;
12710
+ }
12711
+ const baseName = normalizeBaseName(rawName);
12712
+ const sourceDir = resolveAssetDir("commands");
12713
+ if (!sourceDir) {
12714
+ process.stderr.write("massu: could not locate the bundled commands directory\n");
12715
+ process.exit(1);
12716
+ return;
12717
+ }
12718
+ const framework = getConfig().framework;
12719
+ const choice = pickVariant(baseName, sourceDir, framework);
12720
+ if (choice.kind === "miss") {
12721
+ process.stderr.write(`massu: no template named "${baseName}" found
12722
+ `);
12723
+ process.exit(1);
12724
+ return;
12725
+ }
12726
+ const suffix = choice.kind === "hit" ? choice.suffix : "";
12727
+ const file = suffix === "" ? resolve7(sourceDir, `${baseName}.md`) : resolve7(sourceDir, `${baseName}${suffix}.md`);
12728
+ if (!existsSync10(file)) {
12729
+ process.stderr.write(`massu: resolved template "${file}" no longer exists
12730
+ `);
12731
+ process.exit(1);
12732
+ return;
12733
+ }
12734
+ process.stdout.write(readFileSync8(file, "utf-8"));
12735
+ }
12736
+ var init_show_template = __esm({
12737
+ "src/commands/show-template.ts"() {
12738
+ "use strict";
12739
+ init_config();
12740
+ init_install_commands();
12741
+ }
12742
+ });
12743
+
12441
12744
  // src/db.ts
12442
12745
  import Database2 from "better-sqlite3";
12443
12746
  import { dirname as dirname6, join as join6 } from "path";
12444
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, readdirSync as readdirSync8, statSync as statSync5 } from "fs";
12747
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, readdirSync as readdirSync8, statSync as statSync5 } from "fs";
12445
12748
  function getCodeGraphDb() {
12446
12749
  const dbPath = getResolvedPaths().codegraphDbPath;
12447
- if (!existsSync10(dbPath)) {
12750
+ if (!existsSync11(dbPath)) {
12448
12751
  throw new Error(`CodeGraph database not found at ${dbPath}. Run 'npx @colbymchenry/codegraph sync' first.`);
12449
12752
  }
12450
12753
  const db = new Database2(dbPath, { readonly: true });
@@ -12454,7 +12757,7 @@ function getCodeGraphDb() {
12454
12757
  function getDataDb() {
12455
12758
  const dbPath = getResolvedPaths().dataDbPath;
12456
12759
  const dir = dirname6(dbPath);
12457
- if (!existsSync10(dir)) {
12760
+ if (!existsSync11(dir)) {
12458
12761
  mkdirSync4(dir, { recursive: true });
12459
12762
  }
12460
12763
  const db = new Database2(dbPath);
@@ -12752,10 +13055,10 @@ var init_db = __esm({
12752
13055
  });
12753
13056
 
12754
13057
  // src/security-utils.ts
12755
- import { resolve as resolve7, normalize } from "path";
13058
+ import { resolve as resolve8, normalize } from "path";
12756
13059
  function ensureWithinRoot(filePath, projectRoot) {
12757
- const resolvedRoot = resolve7(projectRoot);
12758
- const resolvedPath = resolve7(resolvedRoot, filePath);
13060
+ const resolvedRoot = resolve8(projectRoot);
13061
+ const resolvedPath = resolve8(resolvedRoot, filePath);
12759
13062
  const normalizedPath = normalize(resolvedPath);
12760
13063
  const normalizedRoot = normalize(resolvedRoot);
12761
13064
  if (!normalizedPath.startsWith(normalizedRoot + "/") && normalizedPath !== normalizedRoot) {
@@ -12828,8 +13131,8 @@ var init_rules = __esm({
12828
13131
  });
12829
13132
 
12830
13133
  // src/import-resolver.ts
12831
- import { readFileSync as readFileSync8, existsSync as existsSync11, statSync as statSync6 } from "fs";
12832
- import { resolve as resolve8, dirname as dirname7, join as join7 } from "path";
13134
+ import { readFileSync as readFileSync9, existsSync as existsSync12, statSync as statSync6 } from "fs";
13135
+ import { resolve as resolve9, dirname as dirname7, join as join7 } from "path";
12833
13136
  function parseImports(source) {
12834
13137
  const imports = [];
12835
13138
  const lines = source.split("\n");
@@ -12885,23 +13188,23 @@ function resolveImportPath(specifier, fromFile) {
12885
13188
  let basePath;
12886
13189
  if (specifier.startsWith("@/")) {
12887
13190
  const paths = getResolvedPaths();
12888
- basePath = resolve8(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
13191
+ basePath = resolve9(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
12889
13192
  } else {
12890
- basePath = resolve8(dirname7(fromFile), specifier);
13193
+ basePath = resolve9(dirname7(fromFile), specifier);
12891
13194
  }
12892
- if (existsSync11(basePath) && !isDirectory(basePath)) {
13195
+ if (existsSync12(basePath) && !isDirectory(basePath)) {
12893
13196
  return toRelative(basePath);
12894
13197
  }
12895
13198
  const resolvedPaths = getResolvedPaths();
12896
13199
  for (const ext of resolvedPaths.extensions) {
12897
13200
  const withExt = basePath + ext;
12898
- if (existsSync11(withExt)) {
13201
+ if (existsSync12(withExt)) {
12899
13202
  return toRelative(withExt);
12900
13203
  }
12901
13204
  }
12902
13205
  for (const indexFile of resolvedPaths.indexFiles) {
12903
13206
  const indexPath = join7(basePath, indexFile);
12904
- if (existsSync11(indexPath)) {
13207
+ if (existsSync12(indexPath)) {
12905
13208
  return toRelative(indexPath);
12906
13209
  }
12907
13210
  }
@@ -12937,11 +13240,11 @@ function buildImportIndex(dataDb2, codegraphDb2) {
12937
13240
  const batchSize = 500;
12938
13241
  let batch = [];
12939
13242
  for (const file of files) {
12940
- const absPath = ensureWithinRoot(resolve8(projectRoot, file.path), projectRoot);
12941
- if (!existsSync11(absPath)) continue;
13243
+ const absPath = ensureWithinRoot(resolve9(projectRoot, file.path), projectRoot);
13244
+ if (!existsSync12(absPath)) continue;
12942
13245
  let source;
12943
13246
  try {
12944
- source = readFileSync8(absPath, "utf-8");
13247
+ source = readFileSync9(absPath, "utf-8");
12945
13248
  } catch {
12946
13249
  continue;
12947
13250
  }
@@ -12977,15 +13280,15 @@ var init_import_resolver = __esm({
12977
13280
  });
12978
13281
 
12979
13282
  // src/trpc-index.ts
12980
- import { readFileSync as readFileSync9, existsSync as existsSync12, readdirSync as readdirSync9 } from "fs";
12981
- import { resolve as resolve9, join as join8 } from "path";
13283
+ import { readFileSync as readFileSync10, existsSync as existsSync13, readdirSync as readdirSync9 } from "fs";
13284
+ import { resolve as resolve10, join as join8 } from "path";
12982
13285
  function parseRootRouter() {
12983
13286
  const paths = getResolvedPaths();
12984
13287
  const rootPath = paths.rootRouterPath;
12985
- if (!existsSync12(rootPath)) {
13288
+ if (!existsSync13(rootPath)) {
12986
13289
  throw new Error(`Root router not found at ${rootPath}`);
12987
13290
  }
12988
- const source = readFileSync9(rootPath, "utf-8");
13291
+ const source = readFileSync10(rootPath, "utf-8");
12989
13292
  const mappings = [];
12990
13293
  const importMap = /* @__PURE__ */ new Map();
12991
13294
  const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
@@ -12993,16 +13296,16 @@ function parseRootRouter() {
12993
13296
  while ((match = importRegex.exec(source)) !== null) {
12994
13297
  const variable = match[1];
12995
13298
  let filePath = match[2];
12996
- const fullPath = resolve9(paths.routersDir, filePath);
13299
+ const fullPath = resolve10(paths.routersDir, filePath);
12997
13300
  for (const ext of [".ts", ".tsx", ""]) {
12998
13301
  const candidate = fullPath + ext;
12999
13302
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
13000
- if (existsSync12(candidate)) {
13303
+ if (existsSync13(candidate)) {
13001
13304
  filePath = routersRelPath + "/" + filePath + ext;
13002
13305
  break;
13003
13306
  }
13004
13307
  const indexCandidate = join8(fullPath, "index.ts");
13005
- if (existsSync12(indexCandidate)) {
13308
+ if (existsSync13(indexCandidate)) {
13006
13309
  filePath = routersRelPath + "/" + filePath + "/index.ts";
13007
13310
  break;
13008
13311
  }
@@ -13021,9 +13324,9 @@ function parseRootRouter() {
13021
13324
  return mappings;
13022
13325
  }
13023
13326
  function extractProcedures(routerFilePath) {
13024
- const absPath = resolve9(getProjectRoot(), routerFilePath);
13025
- if (!existsSync12(absPath)) return [];
13026
- const source = readFileSync9(absPath, "utf-8");
13327
+ const absPath = resolve10(getProjectRoot(), routerFilePath);
13328
+ if (!existsSync13(absPath)) return [];
13329
+ const source = readFileSync10(absPath, "utf-8");
13027
13330
  const procedures = [];
13028
13331
  const seen = /* @__PURE__ */ new Set();
13029
13332
  const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
@@ -13046,13 +13349,13 @@ function findUICallSites(routerKey, procedureName) {
13046
13349
  const root = getProjectRoot();
13047
13350
  const src = config.paths.source;
13048
13351
  const searchDirs = [
13049
- resolve9(root, config.paths.pages ?? src + "/app"),
13050
- resolve9(root, config.paths.components ?? src + "/components"),
13051
- resolve9(root, config.paths.hooks ?? src + "/hooks")
13352
+ resolve10(root, config.paths.pages ?? src + "/app"),
13353
+ resolve10(root, config.paths.components ?? src + "/components"),
13354
+ resolve10(root, config.paths.hooks ?? src + "/hooks")
13052
13355
  ];
13053
13356
  const searchPattern = `api.${routerKey}.${procedureName}`;
13054
13357
  for (const dir of searchDirs) {
13055
- if (!existsSync12(dir)) continue;
13358
+ if (!existsSync13(dir)) continue;
13056
13359
  searchDirectory(dir, searchPattern, callSites);
13057
13360
  }
13058
13361
  return callSites;
@@ -13066,7 +13369,7 @@ function searchDirectory(dir, pattern, results) {
13066
13369
  searchDirectory(fullPath, pattern, results);
13067
13370
  } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
13068
13371
  try {
13069
- const source = readFileSync9(fullPath, "utf-8");
13372
+ const source = readFileSync10(fullPath, "utf-8");
13070
13373
  const lines = source.split("\n");
13071
13374
  for (let i = 0; i < lines.length; i++) {
13072
13375
  if (lines[i].includes(pattern)) {
@@ -13129,8 +13432,8 @@ var init_trpc_index = __esm({
13129
13432
  });
13130
13433
 
13131
13434
  // src/page-deps.ts
13132
- import { readFileSync as readFileSync10, existsSync as existsSync13 } from "fs";
13133
- import { resolve as resolve10 } from "path";
13435
+ import { readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
13436
+ import { resolve as resolve11 } from "path";
13134
13437
  function deriveRoute(pageFile) {
13135
13438
  let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
13136
13439
  return route || "/";
@@ -13168,10 +13471,10 @@ function findRouterCalls(files) {
13168
13471
  const routers = /* @__PURE__ */ new Set();
13169
13472
  const projectRoot = getProjectRoot();
13170
13473
  for (const file of files) {
13171
- const absPath = ensureWithinRoot(resolve10(projectRoot, file), projectRoot);
13172
- if (!existsSync13(absPath)) continue;
13474
+ const absPath = ensureWithinRoot(resolve11(projectRoot, file), projectRoot);
13475
+ if (!existsSync14(absPath)) continue;
13173
13476
  try {
13174
- const source = readFileSync10(absPath, "utf-8");
13477
+ const source = readFileSync11(absPath, "utf-8");
13175
13478
  const apiCallRegex = /api\.(\w+)\.\w+/g;
13176
13479
  let match;
13177
13480
  while ((match = apiCallRegex.exec(source)) !== null) {
@@ -13189,10 +13492,10 @@ function findTablesFromRouters(routerNames, dataDb2) {
13189
13492
  "SELECT DISTINCT router_file FROM massu_trpc_procedures WHERE router_name = ?"
13190
13493
  ).all(routerName);
13191
13494
  for (const proc of procs) {
13192
- const absPath = ensureWithinRoot(resolve10(getProjectRoot(), proc.router_file), getProjectRoot());
13193
- if (!existsSync13(absPath)) continue;
13495
+ const absPath = ensureWithinRoot(resolve11(getProjectRoot(), proc.router_file), getProjectRoot());
13496
+ if (!existsSync14(absPath)) continue;
13194
13497
  try {
13195
- const source = readFileSync10(absPath, "utf-8");
13498
+ const source = readFileSync11(absPath, "utf-8");
13196
13499
  const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
13197
13500
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
13198
13501
  const tableRegex = new RegExp(regexStr + "\\.", "g");
@@ -13461,14 +13764,14 @@ var init_domains = __esm({
13461
13764
  });
13462
13765
 
13463
13766
  // src/schema-mapper.ts
13464
- import { readFileSync as readFileSync11, existsSync as existsSync14, readdirSync as readdirSync10 } from "fs";
13767
+ import { readFileSync as readFileSync12, existsSync as existsSync15, readdirSync as readdirSync10 } from "fs";
13465
13768
  import { join as join9 } from "path";
13466
13769
  function parsePrismaSchema() {
13467
13770
  const schemaPath = getResolvedPaths().prismaSchemaPath;
13468
- if (!existsSync14(schemaPath)) {
13771
+ if (!existsSync15(schemaPath)) {
13469
13772
  throw new Error(`Prisma schema not found at ${schemaPath}`);
13470
13773
  }
13471
- const source = readFileSync11(schemaPath, "utf-8");
13774
+ const source = readFileSync12(schemaPath, "utf-8");
13472
13775
  const models = [];
13473
13776
  const sourceLines = source.split("\n");
13474
13777
  let i = 0;
@@ -13526,7 +13829,7 @@ function toSnakeCase(str) {
13526
13829
  function findColumnUsageInRouters(tableName) {
13527
13830
  const usage = /* @__PURE__ */ new Map();
13528
13831
  const routersDir = getResolvedPaths().routersDir;
13529
- if (!existsSync14(routersDir)) return usage;
13832
+ if (!existsSync15(routersDir)) return usage;
13530
13833
  scanDirectory(routersDir, tableName, usage);
13531
13834
  return usage;
13532
13835
  }
@@ -13543,7 +13846,7 @@ function scanDirectory(dir, tableName, usage) {
13543
13846
  }
13544
13847
  function scanFile(absPath, tableName, usage) {
13545
13848
  try {
13546
- const source = readFileSync11(absPath, "utf-8");
13849
+ const source = readFileSync12(absPath, "utf-8");
13547
13850
  if (!source.includes(tableName)) return;
13548
13851
  const relPath = absPath.slice(getProjectRoot().length + 1);
13549
13852
  const lines = source.split("\n");
@@ -13588,7 +13891,7 @@ function detectMismatches(models) {
13588
13891
  }
13589
13892
  function findFilesUsingColumn(dir, column, tableName) {
13590
13893
  const result = [];
13591
- if (!existsSync14(dir)) return result;
13894
+ if (!existsSync15(dir)) return result;
13592
13895
  const entries = readdirSync10(dir, { withFileTypes: true });
13593
13896
  for (const entry of entries) {
13594
13897
  const fullPath = join9(dir, entry.name);
@@ -13596,7 +13899,7 @@ function findFilesUsingColumn(dir, column, tableName) {
13596
13899
  result.push(...findFilesUsingColumn(fullPath, column, tableName));
13597
13900
  } else if (entry.name.endsWith(".ts")) {
13598
13901
  try {
13599
- const source = readFileSync11(fullPath, "utf-8");
13902
+ const source = readFileSync12(fullPath, "utf-8");
13600
13903
  if (source.includes(tableName) && source.includes(column)) {
13601
13904
  result.push(fullPath.slice(getProjectRoot().length + 1));
13602
13905
  }
@@ -13741,12 +14044,12 @@ var init_import_parser = __esm({
13741
14044
  });
13742
14045
 
13743
14046
  // src/python/import-resolver.ts
13744
- import { readFileSync as readFileSync12, existsSync as existsSync15, readdirSync as readdirSync11 } from "fs";
13745
- import { resolve as resolve12, join as join10, relative as relative4, dirname as dirname8 } from "path";
14047
+ import { readFileSync as readFileSync13, existsSync as existsSync16, readdirSync as readdirSync11 } from "fs";
14048
+ import { resolve as resolve13, join as join10, relative as relative4, dirname as dirname8 } from "path";
13746
14049
  function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
13747
14050
  const projectRoot = getProjectRoot();
13748
14051
  if (level > 0) {
13749
- let baseDir = dirname8(resolve12(projectRoot, fromFile));
14052
+ let baseDir = dirname8(resolve13(projectRoot, fromFile));
13750
14053
  for (let i = 1; i < level; i++) {
13751
14054
  baseDir = dirname8(baseDir);
13752
14055
  }
@@ -13758,17 +14061,17 @@ function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
13758
14061
  return tryResolvePythonPath(baseDir, projectRoot);
13759
14062
  }
13760
14063
  const parts = module.split(".");
13761
- const candidate = join10(resolve12(projectRoot, pythonRoot), ...parts);
14064
+ const candidate = join10(resolve13(projectRoot, pythonRoot), ...parts);
13762
14065
  return tryResolvePythonPath(candidate, projectRoot);
13763
14066
  }
13764
14067
  function tryResolvePythonPath(basePath, projectRoot) {
13765
- if (existsSync15(basePath + ".py")) {
14068
+ if (existsSync16(basePath + ".py")) {
13766
14069
  return relative4(projectRoot, basePath + ".py");
13767
14070
  }
13768
- if (existsSync15(join10(basePath, "__init__.py"))) {
14071
+ if (existsSync16(join10(basePath, "__init__.py"))) {
13769
14072
  return relative4(projectRoot, join10(basePath, "__init__.py"));
13770
14073
  }
13771
- if (basePath.endsWith(".py") && existsSync15(basePath)) {
14074
+ if (basePath.endsWith(".py") && existsSync16(basePath)) {
13772
14075
  return relative4(projectRoot, basePath);
13773
14076
  }
13774
14077
  return null;
@@ -13791,7 +14094,7 @@ function walkPythonFiles(dir, excludeDirs) {
13791
14094
  }
13792
14095
  function buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
13793
14096
  const projectRoot = getProjectRoot();
13794
- const absRoot = resolve12(projectRoot, pythonRoot);
14097
+ const absRoot = resolve13(projectRoot, pythonRoot);
13795
14098
  dataDb2.exec("DELETE FROM massu_py_imports");
13796
14099
  const insertStmt = dataDb2.prepare(
13797
14100
  "INSERT INTO massu_py_imports (source_file, target_file, import_type, imported_names, line) VALUES (?, ?, ?, ?, ?)"
@@ -13808,7 +14111,7 @@ function buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__
13808
14111
  const relFile = relative4(projectRoot, absFile);
13809
14112
  let source;
13810
14113
  try {
13811
- source = readFileSync12(absFile, "utf-8");
14114
+ source = readFileSync13(absFile, "utf-8");
13812
14115
  } catch {
13813
14116
  continue;
13814
14117
  }
@@ -14074,7 +14377,7 @@ var init_route_parser = __esm({
14074
14377
  });
14075
14378
 
14076
14379
  // src/python/route-indexer.ts
14077
- import { readFileSync as readFileSync13, readdirSync as readdirSync12 } from "fs";
14380
+ import { readFileSync as readFileSync14, readdirSync as readdirSync12 } from "fs";
14078
14381
  import { join as join11, relative as relative5 } from "path";
14079
14382
  function walkPyFiles(dir, excludeDirs) {
14080
14383
  const files = [];
@@ -14107,7 +14410,7 @@ function buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
14107
14410
  const relFile = relative5(projectRoot, absFile);
14108
14411
  let source;
14109
14412
  try {
14110
- source = readFileSync13(absFile, "utf-8");
14413
+ source = readFileSync14(absFile, "utf-8");
14111
14414
  } catch {
14112
14415
  continue;
14113
14416
  }
@@ -14318,7 +14621,7 @@ var init_model_parser = __esm({
14318
14621
  });
14319
14622
 
14320
14623
  // src/python/model-indexer.ts
14321
- import { readFileSync as readFileSync14, readdirSync as readdirSync13 } from "fs";
14624
+ import { readFileSync as readFileSync15, readdirSync as readdirSync13 } from "fs";
14322
14625
  import { join as join12, relative as relative6 } from "path";
14323
14626
  function walkPyFiles2(dir, excludeDirs) {
14324
14627
  const files = [];
@@ -14354,7 +14657,7 @@ function buildPythonModelIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
14354
14657
  const relFile = relative6(projectRoot, absFile);
14355
14658
  let source;
14356
14659
  try {
14357
- source = readFileSync14(absFile, "utf-8");
14660
+ source = readFileSync15(absFile, "utf-8");
14358
14661
  } catch {
14359
14662
  continue;
14360
14663
  }
@@ -14614,7 +14917,7 @@ var init_migration_parser = __esm({
14614
14917
  });
14615
14918
 
14616
14919
  // src/python/migration-indexer.ts
14617
- import { readFileSync as readFileSync15, readdirSync as readdirSync14 } from "fs";
14920
+ import { readFileSync as readFileSync16, readdirSync as readdirSync14 } from "fs";
14618
14921
  import { join as join13, relative as relative7 } from "path";
14619
14922
  function buildPythonMigrationIndex(dataDb2, alembicDir) {
14620
14923
  const projectRoot = getProjectRoot();
@@ -14640,7 +14943,7 @@ function buildPythonMigrationIndex(dataDb2, alembicDir) {
14640
14943
  for (const absFile of files) {
14641
14944
  let source;
14642
14945
  try {
14643
- source = readFileSync15(absFile, "utf-8");
14946
+ source = readFileSync16(absFile, "utf-8");
14644
14947
  } catch {
14645
14948
  continue;
14646
14949
  }
@@ -14674,7 +14977,7 @@ var init_migration_indexer = __esm({
14674
14977
  });
14675
14978
 
14676
14979
  // src/python/coupling-detector.ts
14677
- import { readFileSync as readFileSync16, readdirSync as readdirSync15 } from "fs";
14980
+ import { readFileSync as readFileSync17, readdirSync as readdirSync15 } from "fs";
14678
14981
  import { join as join14, relative as relative8 } from "path";
14679
14982
  function buildPythonCouplingIndex(dataDb2) {
14680
14983
  const projectRoot = getProjectRoot();
@@ -14711,7 +15014,7 @@ function buildPythonCouplingIndex(dataDb2) {
14711
15014
  const relFile = relative8(projectRoot, absFile);
14712
15015
  let source;
14713
15016
  try {
14714
- source = readFileSync16(absFile, "utf-8");
15017
+ source = readFileSync17(absFile, "utf-8");
14715
15018
  } catch {
14716
15019
  continue;
14717
15020
  }
@@ -15116,8 +15419,8 @@ var init_memory_tools = __esm({
15116
15419
  });
15117
15420
 
15118
15421
  // src/docs-tools.ts
15119
- import { readFileSync as readFileSync17, existsSync as existsSync16 } from "fs";
15120
- import { resolve as resolve13, basename as basename3 } from "path";
15422
+ import { readFileSync as readFileSync18, existsSync as existsSync17 } from "fs";
15423
+ import { resolve as resolve14, basename as basename3 } from "path";
15121
15424
  function p3(baseName) {
15122
15425
  return `${getConfig().toolPrefix}_${baseName}`;
15123
15426
  }
@@ -15172,10 +15475,10 @@ function handleDocsToolCall(name, args2) {
15172
15475
  }
15173
15476
  function loadDocsMap() {
15174
15477
  const mapPath = getResolvedPaths().docsMapPath;
15175
- if (!existsSync16(mapPath)) {
15478
+ if (!existsSync17(mapPath)) {
15176
15479
  throw new Error(`docs-map.json not found at ${mapPath}`);
15177
15480
  }
15178
- return JSON.parse(readFileSync17(mapPath, "utf-8"));
15481
+ return JSON.parse(readFileSync18(mapPath, "utf-8"));
15179
15482
  }
15180
15483
  function matchesPattern(filePath, pattern) {
15181
15484
  const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
@@ -15245,13 +15548,13 @@ function extractFrontmatter(content) {
15245
15548
  }
15246
15549
  function extractProcedureNames(routerPath) {
15247
15550
  const root = getProjectRoot();
15248
- const absPath = ensureWithinRoot(resolve13(getResolvedPaths().srcDir, "..", routerPath), root);
15249
- if (!existsSync16(absPath)) {
15250
- const altPath = ensureWithinRoot(resolve13(getResolvedPaths().srcDir, "../server/api/routers", basename3(routerPath)), root);
15251
- if (!existsSync16(altPath)) return [];
15252
- return extractProcedureNamesFromContent(readFileSync17(altPath, "utf-8"));
15551
+ const absPath = ensureWithinRoot(resolve14(getResolvedPaths().srcDir, "..", routerPath), root);
15552
+ if (!existsSync17(absPath)) {
15553
+ const altPath = ensureWithinRoot(resolve14(getResolvedPaths().srcDir, "../server/api/routers", basename3(routerPath)), root);
15554
+ if (!existsSync17(altPath)) return [];
15555
+ return extractProcedureNamesFromContent(readFileSync18(altPath, "utf-8"));
15253
15556
  }
15254
- return extractProcedureNamesFromContent(readFileSync17(absPath, "utf-8"));
15557
+ return extractProcedureNamesFromContent(readFileSync18(absPath, "utf-8"));
15255
15558
  }
15256
15559
  function extractProcedureNamesFromContent(content) {
15257
15560
  const procRegex = /\.(?:query|mutation)\s*\(/g;
@@ -15291,8 +15594,8 @@ function handleDocsAudit(args2) {
15291
15594
  for (const [mappingId, triggeringFiles] of affectedMappings) {
15292
15595
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
15293
15596
  if (!mapping) continue;
15294
- const helpPagePath = ensureWithinRoot(resolve13(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
15295
- if (!existsSync16(helpPagePath)) {
15597
+ const helpPagePath = ensureWithinRoot(resolve14(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
15598
+ if (!existsSync17(helpPagePath)) {
15296
15599
  results.push({
15297
15600
  helpPage: mapping.helpPage,
15298
15601
  mappingId,
@@ -15304,7 +15607,7 @@ function handleDocsAudit(args2) {
15304
15607
  });
15305
15608
  continue;
15306
15609
  }
15307
- const content = readFileSync17(helpPagePath, "utf-8");
15610
+ const content = readFileSync18(helpPagePath, "utf-8");
15308
15611
  const sections = extractSections(content);
15309
15612
  const frontmatter = extractFrontmatter(content);
15310
15613
  const staleReasons = [];
@@ -15344,9 +15647,9 @@ function handleDocsAudit(args2) {
15344
15647
  });
15345
15648
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
15346
15649
  if (parentId === mappingId) {
15347
- const guidePath = ensureWithinRoot(resolve13(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
15348
- if (existsSync16(guidePath)) {
15349
- const guideContent = readFileSync17(guidePath, "utf-8");
15650
+ const guidePath = ensureWithinRoot(resolve14(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
15651
+ if (existsSync17(guidePath)) {
15652
+ const guideContent = readFileSync18(guidePath, "utf-8");
15350
15653
  const guideFrontmatter = extractFrontmatter(guideContent);
15351
15654
  if (!guideFrontmatter?.lastVerified || status === "STALE") {
15352
15655
  results.push({
@@ -15379,14 +15682,14 @@ function handleDocsCoverage(args2) {
15379
15682
  const gaps = [];
15380
15683
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
15381
15684
  for (const mapping of mappings) {
15382
- const helpPagePath = ensureWithinRoot(resolve13(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
15383
- const exists = existsSync16(helpPagePath);
15685
+ const helpPagePath = ensureWithinRoot(resolve14(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
15686
+ const exists = existsSync17(helpPagePath);
15384
15687
  let hasContent = false;
15385
15688
  let lineCount = 0;
15386
15689
  let lastVerified = null;
15387
15690
  let status = null;
15388
15691
  if (exists) {
15389
- const content = readFileSync17(helpPagePath, "utf-8");
15692
+ const content = readFileSync18(helpPagePath, "utf-8");
15390
15693
  lineCount = content.split("\n").length;
15391
15694
  hasContent = lineCount > 10;
15392
15695
  const frontmatter = extractFrontmatter(content);
@@ -15727,8 +16030,8 @@ var init_observability_tools = __esm({
15727
16030
  });
15728
16031
 
15729
16032
  // src/sentinel-db.ts
15730
- import { existsSync as existsSync17 } from "fs";
15731
- import { resolve as resolve14 } from "path";
16033
+ import { existsSync as existsSync18 } from "fs";
16034
+ import { resolve as resolve15 } from "path";
15732
16035
  function parsePortalScope(raw) {
15733
16036
  if (!raw) return [];
15734
16037
  try {
@@ -15964,23 +16267,23 @@ function validateFeatures(db, domainFilter) {
15964
16267
  const missingProcedures = [];
15965
16268
  const missingPages = [];
15966
16269
  for (const comp of components) {
15967
- const absPath = resolve14(PROJECT_ROOT, comp.component_file);
15968
- if (!existsSync17(absPath)) {
16270
+ const absPath = resolve15(PROJECT_ROOT, comp.component_file);
16271
+ if (!existsSync18(absPath)) {
15969
16272
  missingComponents.push(comp.component_file);
15970
16273
  }
15971
16274
  }
15972
16275
  for (const proc of procedures) {
15973
- const routerPath = resolve14(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
15974
- if (!existsSync17(routerPath)) {
16276
+ const routerPath = resolve15(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
16277
+ if (!existsSync18(routerPath)) {
15975
16278
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
15976
16279
  }
15977
16280
  }
15978
16281
  for (const page of pages) {
15979
16282
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
15980
- const absPath = resolve14(PROJECT_ROOT, routeToPath);
15981
- if (page.page_route.startsWith("/") && !existsSync17(absPath)) {
15982
- const altPath = resolve14(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
15983
- if (!existsSync17(altPath)) {
16283
+ const absPath = resolve15(PROJECT_ROOT, routeToPath);
16284
+ if (page.page_route.startsWith("/") && !existsSync18(absPath)) {
16285
+ const altPath = resolve15(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
16286
+ if (!existsSync18(altPath)) {
15984
16287
  missingPages.push(page.page_route);
15985
16288
  }
15986
16289
  }
@@ -16504,8 +16807,8 @@ var init_sentinel_tools = __esm({
16504
16807
  });
16505
16808
 
16506
16809
  // src/sentinel-scanner.ts
16507
- import { readFileSync as readFileSync18, existsSync as existsSync18, readdirSync as readdirSync16, statSync as statSync7 } from "fs";
16508
- import { resolve as resolve15, join as join15, basename as basename4, dirname as dirname9, relative as relative9 } from "path";
16810
+ import { readFileSync as readFileSync19, existsSync as existsSync19, readdirSync as readdirSync16, statSync as statSync7 } from "fs";
16811
+ import { resolve as resolve16, join as join15, basename as basename4, dirname as dirname9, relative as relative9 } from "path";
16509
16812
  function inferDomain(filePath) {
16510
16813
  const domains = getConfig().domains;
16511
16814
  const path = filePath.toLowerCase();
@@ -16634,8 +16937,8 @@ function scanComponentExports(dataDb2) {
16634
16937
  const projectRoot = getProjectRoot();
16635
16938
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
16636
16939
  const componentDirs = [];
16637
- const basePath = resolve15(projectRoot, componentsBase);
16638
- if (existsSync18(basePath)) {
16940
+ const basePath = resolve16(projectRoot, componentsBase);
16941
+ if (existsSync19(basePath)) {
16639
16942
  try {
16640
16943
  const entries = readdirSync16(basePath, { withFileTypes: true });
16641
16944
  for (const entry of entries) {
@@ -16647,12 +16950,12 @@ function scanComponentExports(dataDb2) {
16647
16950
  }
16648
16951
  }
16649
16952
  for (const dir of componentDirs) {
16650
- const absDir = resolve15(projectRoot, dir);
16651
- if (!existsSync18(absDir)) continue;
16953
+ const absDir = resolve16(projectRoot, dir);
16954
+ if (!existsSync19(absDir)) continue;
16652
16955
  const files = walkDir(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
16653
16956
  for (const file of files) {
16654
16957
  const relPath = relative9(projectRoot, file);
16655
- const source = readFileSync18(file, "utf-8");
16958
+ const source = readFileSync19(file, "utf-8");
16656
16959
  const annotations = parseFeatureAnnotations(source);
16657
16960
  if (annotations.length > 0) {
16658
16961
  for (const ann of annotations) {
@@ -17691,7 +17994,7 @@ var init_audit_trail = __esm({
17691
17994
  });
17692
17995
 
17693
17996
  // src/validation-engine.ts
17694
- import { existsSync as existsSync19, readFileSync as readFileSync19 } from "fs";
17997
+ import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
17695
17998
  function p10(baseName) {
17696
17999
  return `${getConfig().toolPrefix}_${baseName}`;
17697
18000
  }
@@ -17722,7 +18025,7 @@ function validateFile(filePath, projectRoot) {
17722
18025
  });
17723
18026
  return checks;
17724
18027
  }
17725
- if (!existsSync19(absPath)) {
18028
+ if (!existsSync20(absPath)) {
17726
18029
  checks.push({
17727
18030
  name: "file_exists",
17728
18031
  severity: "error",
@@ -17731,7 +18034,7 @@ function validateFile(filePath, projectRoot) {
17731
18034
  });
17732
18035
  return checks;
17733
18036
  }
17734
- const source = readFileSync19(absPath, "utf-8");
18037
+ const source = readFileSync20(absPath, "utf-8");
17735
18038
  const lines = source.split("\n");
17736
18039
  if (activeChecks.rule_compliance !== false) {
17737
18040
  for (const ruleSet of config.rules) {
@@ -18176,7 +18479,7 @@ var init_adr_generator = __esm({
18176
18479
  });
18177
18480
 
18178
18481
  // src/security-scorer.ts
18179
- import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
18482
+ import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
18180
18483
  function p12(baseName) {
18181
18484
  return `${getConfig().toolPrefix}_${baseName}`;
18182
18485
  }
@@ -18200,12 +18503,12 @@ function scoreFileSecurity(filePath, projectRoot) {
18200
18503
  }]
18201
18504
  };
18202
18505
  }
18203
- if (!existsSync20(absPath)) {
18506
+ if (!existsSync21(absPath)) {
18204
18507
  return { riskScore: 0, findings: [] };
18205
18508
  }
18206
18509
  let source;
18207
18510
  try {
18208
- source = readFileSync20(absPath, "utf-8");
18511
+ source = readFileSync21(absPath, "utf-8");
18209
18512
  } catch {
18210
18513
  return { riskScore: 0, findings: [] };
18211
18514
  }
@@ -18494,8 +18797,8 @@ var init_security_scorer = __esm({
18494
18797
  });
18495
18798
 
18496
18799
  // src/dependency-scorer.ts
18497
- import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
18498
- import { resolve as resolve16 } from "path";
18800
+ import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
18801
+ import { resolve as resolve17 } from "path";
18499
18802
  function p13(baseName) {
18500
18803
  return `${getConfig().toolPrefix}_${baseName}`;
18501
18804
  }
@@ -18527,10 +18830,10 @@ function calculateDepRisk(factors) {
18527
18830
  return Math.min(100, risk);
18528
18831
  }
18529
18832
  function getInstalledPackages(projectRoot) {
18530
- const pkgPath = resolve16(projectRoot, "package.json");
18531
- if (!existsSync21(pkgPath)) return /* @__PURE__ */ new Map();
18833
+ const pkgPath = resolve17(projectRoot, "package.json");
18834
+ if (!existsSync22(pkgPath)) return /* @__PURE__ */ new Map();
18532
18835
  try {
18533
- const pkg = JSON.parse(readFileSync21(pkgPath, "utf-8"));
18836
+ const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
18534
18837
  const packages = /* @__PURE__ */ new Map();
18535
18838
  for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
18536
18839
  packages.set(name, version);
@@ -19132,9 +19435,9 @@ var init_regression_detector = __esm({
19132
19435
  });
19133
19436
 
19134
19437
  // src/knowledge-indexer.ts
19135
- import { createHash as createHash3 } from "crypto";
19136
- import { readFileSync as readFileSync22, readdirSync as readdirSync17, statSync as statSync8, existsSync as existsSync22 } from "fs";
19137
- import { resolve as resolve17, relative as relative10, basename as basename5, extname } from "path";
19438
+ import { createHash as createHash4 } from "crypto";
19439
+ import { readFileSync as readFileSync23, readdirSync as readdirSync17, statSync as statSync8, existsSync as existsSync23 } from "fs";
19440
+ import { resolve as resolve18, relative as relative10, basename as basename5, extname } from "path";
19138
19441
  function getKnowledgePaths() {
19139
19442
  const resolved = getResolvedPaths();
19140
19443
  const config = getConfig();
@@ -19162,7 +19465,7 @@ function discoverMarkdownFiles(baseDir) {
19162
19465
  try {
19163
19466
  const entries = readdirSync17(dir, { withFileTypes: true });
19164
19467
  for (const entry of entries) {
19165
- const fullPath = resolve17(dir, entry.name);
19468
+ const fullPath = resolve18(dir, entry.name);
19166
19469
  if (entry.isDirectory()) {
19167
19470
  if (entry.name === "archive" && dir.includes("session-state")) continue;
19168
19471
  if (entry.name === "archive" && dir.includes("status")) continue;
@@ -19219,8 +19522,8 @@ function categorizeFile(filePath) {
19219
19522
  if (knownCategories.includes(firstDir)) return firstDir;
19220
19523
  return "root";
19221
19524
  }
19222
- function hashContent(content) {
19223
- return createHash3("sha256").update(content).digest("hex");
19525
+ function hashContent2(content) {
19526
+ return createHash4("sha256").update(content).digest("hex");
19224
19527
  }
19225
19528
  function parseCRTable(content) {
19226
19529
  const rules = [];
@@ -19440,11 +19743,11 @@ function indexAllKnowledge(db) {
19440
19743
  files.push(...memFiles);
19441
19744
  } catch {
19442
19745
  }
19443
- if (existsSync22(paths.plansDir)) {
19746
+ if (existsSync23(paths.plansDir)) {
19444
19747
  const planFiles = discoverMarkdownFiles(paths.plansDir);
19445
19748
  files.push(...planFiles);
19446
19749
  }
19447
- if (existsSync22(paths.docsDir)) {
19750
+ if (existsSync23(paths.docsDir)) {
19448
19751
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
19449
19752
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
19450
19753
  files.push(...docsFiles);
@@ -19487,9 +19790,9 @@ function indexAllKnowledge(db) {
19487
19790
  } catch {
19488
19791
  }
19489
19792
  for (const filePath of files) {
19490
- if (!existsSync22(filePath)) continue;
19491
- const content = readFileSync22(filePath, "utf-8");
19492
- const hash = hashContent(content);
19793
+ if (!existsSync23(filePath)) continue;
19794
+ const content = readFileSync23(filePath, "utf-8");
19795
+ const hash = hashContent2(content);
19493
19796
  const relPath = filePath.startsWith(paths.claudeDir) ? relative10(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative10(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative10(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative10(paths.memoryDir, filePath)}` : basename5(filePath);
19494
19797
  const category = categorizeFile(filePath);
19495
19798
  const title = extractTitle(content, filePath);
@@ -19608,10 +19911,10 @@ function isKnowledgeStale(db) {
19608
19911
  files.push(...discoverMarkdownFiles(paths.memoryDir));
19609
19912
  } catch {
19610
19913
  }
19611
- if (existsSync22(paths.plansDir)) {
19914
+ if (existsSync23(paths.plansDir)) {
19612
19915
  files.push(...discoverMarkdownFiles(paths.plansDir));
19613
19916
  }
19614
- if (existsSync22(paths.docsDir)) {
19917
+ if (existsSync23(paths.docsDir)) {
19615
19918
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
19616
19919
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
19617
19920
  files.push(...docsFiles);
@@ -19640,8 +19943,8 @@ var init_knowledge_indexer = __esm({
19640
19943
  });
19641
19944
 
19642
19945
  // src/knowledge-tools.ts
19643
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync3, appendFileSync, readdirSync as readdirSync18 } from "fs";
19644
- import { resolve as resolve18, basename as basename6 } from "path";
19946
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync3, appendFileSync, readdirSync as readdirSync18 } from "fs";
19947
+ import { resolve as resolve19, basename as basename6 } from "path";
19645
19948
  function p16(baseName) {
19646
19949
  return `${getConfig().toolPrefix}_${baseName}`;
19647
19950
  }
@@ -20378,7 +20681,7 @@ function handleCorrect(db, args2) {
20378
20681
  if (!wrong || !correction || !rule) {
20379
20682
  return text15("Error: wrong, correction, and rule are all required.");
20380
20683
  }
20381
- const correctionsPath = resolve18(getResolvedPaths().memoryDir, "corrections.md");
20684
+ const correctionsPath = resolve19(getResolvedPaths().memoryDir, "corrections.md");
20382
20685
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
20383
20686
  const title = rule.slice(0, 60);
20384
20687
  const entry = `
@@ -20391,7 +20694,7 @@ ${crRule ? `- **CR**: ${crRule}
20391
20694
  `;
20392
20695
  let existing = "";
20393
20696
  try {
20394
- existing = readFileSync23(correctionsPath, "utf-8");
20697
+ existing = readFileSync24(correctionsPath, "utf-8");
20395
20698
  } catch {
20396
20699
  }
20397
20700
  const archiveIdx = existing.indexOf("## Archived");
@@ -20730,11 +21033,11 @@ var init_knowledge_tools = __esm({
20730
21033
  // src/knowledge-db.ts
20731
21034
  import Database3 from "better-sqlite3";
20732
21035
  import { dirname as dirname10 } from "path";
20733
- import { existsSync as existsSync24, mkdirSync as mkdirSync5 } from "fs";
21036
+ import { existsSync as existsSync25, mkdirSync as mkdirSync5 } from "fs";
20734
21037
  function getKnowledgeDb() {
20735
21038
  const dbPath = getResolvedPaths().knowledgeDbPath;
20736
21039
  const dir = dirname10(dbPath);
20737
- if (!existsSync24(dir)) {
21040
+ if (!existsSync25(dir)) {
20738
21041
  mkdirSync5(dir, { recursive: true });
20739
21042
  }
20740
21043
  const db = new Database3(dbPath);
@@ -21468,8 +21771,8 @@ var init_python_tools = __esm({
21468
21771
  });
21469
21772
 
21470
21773
  // src/tools.ts
21471
- import { readFileSync as readFileSync24, existsSync as existsSync25 } from "fs";
21472
- import { resolve as resolve19, basename as basename7 } from "path";
21774
+ import { readFileSync as readFileSync25, existsSync as existsSync26 } from "fs";
21775
+ import { resolve as resolve20, basename as basename7 } from "path";
21473
21776
  function prefix2() {
21474
21777
  return getConfig().toolPrefix;
21475
21778
  }
@@ -21504,7 +21807,7 @@ function ensureIndexes(dataDb2, codegraphDb2, force = false) {
21504
21807
  if (config.python?.root) {
21505
21808
  const pythonRoot = config.python.root;
21506
21809
  const excludeDirs = config.python.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
21507
- if (force || isPythonDataStale(dataDb2, resolve19(getProjectRoot(), pythonRoot))) {
21810
+ if (force || isPythonDataStale(dataDb2, resolve20(getProjectRoot(), pythonRoot))) {
21508
21811
  const pyImports = buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs);
21509
21812
  results.push(`Python imports: ${pyImports}`);
21510
21813
  const pyRoutes = buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs);
@@ -21903,9 +22206,9 @@ function handleContext(file, dataDb2, codegraphDb2) {
21903
22206
  try {
21904
22207
  const resolvedPaths = getResolvedPaths();
21905
22208
  const root = getProjectRoot();
21906
- const absFilePath = ensureWithinRoot(resolve19(resolvedPaths.srcDir, "..", file), root);
21907
- if (existsSync25(absFilePath)) {
21908
- const fileContent = readFileSync24(absFilePath, "utf-8").slice(0, 3e3);
22209
+ const absFilePath = ensureWithinRoot(resolve20(resolvedPaths.srcDir, "..", file), root);
22210
+ if (existsSync26(absFilePath)) {
22211
+ const fileContent = readFileSync25(absFilePath, "utf-8").slice(0, 3e3);
21909
22212
  const keywords = [];
21910
22213
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
21911
22214
  if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
@@ -22329,11 +22632,11 @@ function handleSchema(args2) {
22329
22632
  lines.push("Checking all column references against Prisma schema...");
22330
22633
  lines.push("");
22331
22634
  const projectRoot = getProjectRoot();
22332
- const absPath = ensureWithinRoot(resolve19(projectRoot, file), projectRoot);
22333
- if (!existsSync25(absPath)) {
22635
+ const absPath = ensureWithinRoot(resolve20(projectRoot, file), projectRoot);
22636
+ if (!existsSync26(absPath)) {
22334
22637
  return text17(`File not found: ${file}`);
22335
22638
  }
22336
- const source = readFileSync24(absPath, "utf-8");
22639
+ const source = readFileSync25(absPath, "utf-8");
22337
22640
  const config = getConfig();
22338
22641
  const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
22339
22642
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
@@ -22411,8 +22714,8 @@ var init_tools = __esm({
22411
22714
 
22412
22715
  // src/server.ts
22413
22716
  var server_exports = {};
22414
- import { readFileSync as readFileSync25 } from "fs";
22415
- import { resolve as resolve20, dirname as dirname11 } from "path";
22717
+ import { readFileSync as readFileSync26 } from "fs";
22718
+ import { resolve as resolve21, dirname as dirname11 } from "path";
22416
22719
  import { fileURLToPath as fileURLToPath4 } from "url";
22417
22720
  function getDb() {
22418
22721
  if (!codegraphDb) codegraphDb = getCodeGraphDb();
@@ -22507,7 +22810,7 @@ var init_server = __esm({
22507
22810
  __dirname4 = dirname11(fileURLToPath4(import.meta.url));
22508
22811
  PKG_VERSION = (() => {
22509
22812
  try {
22510
- const pkg = JSON.parse(readFileSync25(resolve20(__dirname4, "..", "package.json"), "utf-8"));
22813
+ const pkg = JSON.parse(readFileSync26(resolve21(__dirname4, "..", "package.json"), "utf-8"));
22511
22814
  return pkg.version ?? "0.0.0";
22512
22815
  } catch {
22513
22816
  return "0.0.0";
@@ -22619,8 +22922,8 @@ __export(config_refresh_exports, {
22619
22922
  mergeRefresh: () => mergeRefresh,
22620
22923
  runConfigRefresh: () => runConfigRefresh
22621
22924
  });
22622
- import { existsSync as existsSync26, readFileSync as readFileSync26 } from "fs";
22623
- import { resolve as resolve21 } from "path";
22925
+ import { existsSync as existsSync27, readFileSync as readFileSync27 } from "fs";
22926
+ import { resolve as resolve22 } from "path";
22624
22927
  import { parse as parseYaml5 } from "yaml";
22625
22928
  function flatten(obj, prefix3 = "") {
22626
22929
  const out = {};
@@ -22748,17 +23051,17 @@ function renderDiff(diff) {
22748
23051
  }
22749
23052
  async function runConfigRefresh(opts = {}) {
22750
23053
  const cwd = opts.cwd ?? process.cwd();
22751
- const configPath = resolve21(cwd, "massu.config.yaml");
23054
+ const configPath = resolve22(cwd, "massu.config.yaml");
22752
23055
  const log = opts.silent ? () => {
22753
23056
  } : (s) => process.stdout.write(s);
22754
- if (!existsSync26(configPath)) {
23057
+ if (!existsSync27(configPath)) {
22755
23058
  const message = "massu.config.yaml not found. Run: npx massu init";
22756
23059
  if (!opts.silent) process.stderr.write(message + "\n");
22757
23060
  return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
22758
23061
  }
22759
23062
  let existing;
22760
23063
  try {
22761
- const content = readFileSync26(configPath, "utf-8");
23064
+ const content = readFileSync27(configPath, "utf-8");
22762
23065
  const parsed = parseYaml5(content);
22763
23066
  if (!parsed || typeof parsed !== "object") {
22764
23067
  throw new Error("config is not a YAML object");
@@ -22958,13 +23261,21 @@ function migrateV1ToV2(v1Config, detection) {
22958
23261
  let pathsSource = typeof v1Paths.source === "string" ? v1Paths.source : "src";
22959
23262
  if (pathsSource === "src" && primary) {
22960
23263
  const primaryDirs = detection.sourceDirs[primary]?.source_dirs ?? [];
22961
- if (primaryDirs.length > 0) pathsSource = primaryDirs[0];
23264
+ if (primaryDirs.length > 0) {
23265
+ pathsSource = primaryDirs[0];
23266
+ } else if (detection.monorepo?.type !== void 0 && detection.monorepo.type !== "single" && detection.monorepo.packages.length > 0) {
23267
+ pathsSource = monorepoCommonRootMigrate(detection.monorepo.packages);
23268
+ }
22962
23269
  }
22963
23270
  const aliases = v1Paths.aliases && typeof v1Paths.aliases === "object" ? v1Paths.aliases : { "@": pathsSource };
22964
23271
  const paths = {
22965
23272
  source: pathsSource,
22966
23273
  aliases
22967
23274
  };
23275
+ if (detection.monorepo?.type !== void 0 && detection.monorepo.type !== "single" && detection.monorepo.packages.length > 0 && !("monorepo_roots" in v1Paths)) {
23276
+ const roots = monorepoDistinctRootsMigrate(detection.monorepo.packages);
23277
+ if (roots.length > 0) paths.monorepo_roots = roots;
23278
+ }
22968
23279
  for (const k3 of ["routers", "routerRoot", "pages", "middleware", "schema", "components", "hooks"]) {
22969
23280
  if (typeof v1Paths[k3] === "string") paths[k3] = v1Paths[k3];
22970
23281
  }
@@ -23035,6 +23346,20 @@ function migrateV1ToV2(v1Config, detection) {
23035
23346
  }
23036
23347
  return v22;
23037
23348
  }
23349
+ function monorepoCommonRootMigrate(packages) {
23350
+ const roots = monorepoDistinctRootsMigrate(packages);
23351
+ return roots.length === 1 ? roots[0] : ".";
23352
+ }
23353
+ function monorepoDistinctRootsMigrate(packages) {
23354
+ const set = /* @__PURE__ */ new Set();
23355
+ for (const p19 of packages) {
23356
+ const parts = p19.path.split("/");
23357
+ if (parts.length > 1 && parts[0] !== "" && parts[0] !== ".") {
23358
+ set.add(parts[0]);
23359
+ }
23360
+ }
23361
+ return [...set].sort();
23362
+ }
23038
23363
  var PRESERVED_FIELDS2;
23039
23364
  var init_migrate = __esm({
23040
23365
  "src/detect/migrate.ts"() {
@@ -23066,19 +23391,19 @@ var config_upgrade_exports = {};
23066
23391
  __export(config_upgrade_exports, {
23067
23392
  runConfigUpgrade: () => runConfigUpgrade
23068
23393
  });
23069
- import { existsSync as existsSync27, readFileSync as readFileSync27, writeFileSync as writeFileSync4, copyFileSync, unlinkSync } from "fs";
23070
- import { resolve as resolve22 } from "path";
23394
+ import { existsSync as existsSync28, readFileSync as readFileSync28, writeFileSync as writeFileSync4, copyFileSync, unlinkSync } from "fs";
23395
+ import { resolve as resolve23 } from "path";
23071
23396
  import { parse as parseYaml6 } from "yaml";
23072
23397
  async function runConfigUpgrade(opts = {}) {
23073
23398
  const cwd = opts.cwd ?? process.cwd();
23074
- const configPath = resolve22(cwd, "massu.config.yaml");
23399
+ const configPath = resolve23(cwd, "massu.config.yaml");
23075
23400
  const bakPath = `${configPath}.bak`;
23076
23401
  const log = opts.silent ? () => {
23077
23402
  } : (s) => process.stdout.write(s);
23078
23403
  const err = opts.silent ? () => {
23079
23404
  } : (s) => process.stderr.write(s);
23080
23405
  if (opts.rollback) {
23081
- if (!existsSync27(bakPath)) {
23406
+ if (!existsSync28(bakPath)) {
23082
23407
  const message = `No backup found at ${bakPath}`;
23083
23408
  err(message + "\n");
23084
23409
  return { exitCode: 1, action: "none", message };
@@ -23094,14 +23419,14 @@ async function runConfigUpgrade(opts = {}) {
23094
23419
  return { exitCode: 2, action: "none", message };
23095
23420
  }
23096
23421
  }
23097
- if (!existsSync27(configPath)) {
23422
+ if (!existsSync28(configPath)) {
23098
23423
  const message = "massu.config.yaml not found. Run: npx massu init";
23099
23424
  err(message + "\n");
23100
23425
  return { exitCode: 1, action: "none", message };
23101
23426
  }
23102
23427
  let existing;
23103
23428
  try {
23104
- const content = readFileSync27(configPath, "utf-8");
23429
+ const content = readFileSync28(configPath, "utf-8");
23105
23430
  const parsed = parseYaml6(content);
23106
23431
  if (!parsed || typeof parsed !== "object") {
23107
23432
  throw new Error("config is not a YAML object");
@@ -23124,7 +23449,7 @@ async function runConfigUpgrade(opts = {}) {
23124
23449
  fingerprint: computeFingerprint(detection)
23125
23450
  };
23126
23451
  try {
23127
- const original = readFileSync27(configPath, "utf-8");
23452
+ const original = readFileSync28(configPath, "utf-8");
23128
23453
  writeFileSync4(bakPath, original, "utf-8");
23129
23454
  } catch (e2) {
23130
23455
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
@@ -23158,8 +23483,8 @@ var config_check_drift_exports = {};
23158
23483
  __export(config_check_drift_exports, {
23159
23484
  runConfigCheckDrift: () => runConfigCheckDrift
23160
23485
  });
23161
- import { existsSync as existsSync28, readFileSync as readFileSync28 } from "fs";
23162
- import { resolve as resolve23 } from "path";
23486
+ import { existsSync as existsSync29, readFileSync as readFileSync29 } from "fs";
23487
+ import { resolve as resolve24 } from "path";
23163
23488
  import { parse as parseYaml7 } from "yaml";
23164
23489
  function renderChanges(changes) {
23165
23490
  if (changes.length === 0) return "(none)\n";
@@ -23167,12 +23492,12 @@ function renderChanges(changes) {
23167
23492
  }
23168
23493
  async function runConfigCheckDrift(opts = {}) {
23169
23494
  const cwd = opts.cwd ?? process.cwd();
23170
- const configPath = resolve23(cwd, "massu.config.yaml");
23495
+ const configPath = resolve24(cwd, "massu.config.yaml");
23171
23496
  const log = opts.silent ? () => {
23172
23497
  } : (s) => process.stdout.write(s);
23173
23498
  const err = opts.silent ? () => {
23174
23499
  } : (s) => process.stderr.write(s);
23175
- if (!existsSync28(configPath)) {
23500
+ if (!existsSync29(configPath)) {
23176
23501
  const message = "massu.config.yaml not found. Run: npx massu init";
23177
23502
  err(message + "\n");
23178
23503
  return {
@@ -23186,7 +23511,7 @@ async function runConfigCheckDrift(opts = {}) {
23186
23511
  }
23187
23512
  let config;
23188
23513
  try {
23189
- const content = readFileSync28(configPath, "utf-8");
23514
+ const content = readFileSync29(configPath, "utf-8");
23190
23515
  const parsed = parseYaml7(content);
23191
23516
  if (!parsed || typeof parsed !== "object") {
23192
23517
  throw new Error("config is not a YAML object");
@@ -23253,8 +23578,8 @@ var init_config_check_drift = __esm({
23253
23578
  });
23254
23579
 
23255
23580
  // src/cli.ts
23256
- import { readFileSync as readFileSync29 } from "fs";
23257
- import { resolve as resolve24, dirname as dirname12 } from "path";
23581
+ import { readFileSync as readFileSync30 } from "fs";
23582
+ import { resolve as resolve25, dirname as dirname12 } from "path";
23258
23583
  import { fileURLToPath as fileURLToPath5 } from "url";
23259
23584
  var __filename4 = fileURLToPath5(import.meta.url);
23260
23585
  var __dirname5 = dirname12(__filename4);
@@ -23282,6 +23607,11 @@ async function main() {
23282
23607
  await runInstallCommands2();
23283
23608
  break;
23284
23609
  }
23610
+ case "show-template": {
23611
+ const { runShowTemplate: runShowTemplate2 } = await Promise.resolve().then(() => (init_show_template(), show_template_exports));
23612
+ await runShowTemplate2(args.slice(1));
23613
+ break;
23614
+ }
23285
23615
  case "validate-config": {
23286
23616
  const { runValidateConfig: runValidateConfig2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
23287
23617
  await runValidateConfig2();
@@ -23368,6 +23698,7 @@ Commands:
23368
23698
  doctor Check installation health
23369
23699
  install-hooks Install/update Claude Code hooks
23370
23700
  install-commands Install/update slash commands
23701
+ show-template Print the resolved variant of a bundled template (e.g. for diffs)
23371
23702
  validate-config Validate massu.config.yaml (alias: config validate)
23372
23703
  config <sub> Config lifecycle: refresh | validate | upgrade | doctor | check-drift
23373
23704
 
@@ -23406,7 +23737,7 @@ Examples:
23406
23737
  }
23407
23738
  function printVersion() {
23408
23739
  try {
23409
- const pkg = JSON.parse(readFileSync29(resolve24(__dirname5, "../package.json"), "utf-8"));
23740
+ const pkg = JSON.parse(readFileSync30(resolve25(__dirname5, "../package.json"), "utf-8"));
23410
23741
  console.log(`massu v${pkg.version}`);
23411
23742
  } catch {
23412
23743
  console.log("massu v0.1.0");