@massu/core 1.2.1 → 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,
@@ -1681,15 +1684,30 @@ var init_memory_file_ingest = __esm({
1681
1684
  // src/commands/install-commands.ts
1682
1685
  var install_commands_exports = {};
1683
1686
  __export(install_commands_exports, {
1687
+ hashContent: () => hashContent,
1684
1688
  installAll: () => installAll,
1685
1689
  installCommands: () => installCommands,
1690
+ loadManifest: () => loadManifest,
1691
+ pickVariant: () => pickVariant,
1686
1692
  resolveAssetDir: () => resolveAssetDir,
1687
1693
  resolveCommandsDir: () => resolveCommandsDir,
1688
- runInstallCommands: () => runInstallCommands
1694
+ runInstallCommands: () => runInstallCommands,
1695
+ runWithManifest: () => runWithManifest,
1696
+ saveManifest: () => saveManifest,
1697
+ syncDirectory: () => syncDirectory
1689
1698
  });
1690
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync2, readdirSync as readdirSync2, statSync } from "fs";
1691
- 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";
1692
1709
  import { fileURLToPath } from "url";
1710
+ import { createHash } from "crypto";
1693
1711
  function resolveAssetDir(assetName) {
1694
1712
  const cwd = process.cwd();
1695
1713
  const nodeModulesPath = resolve3(cwd, "node_modules/@massu/core", assetName);
@@ -1709,42 +1727,189 @@ function resolveAssetDir(assetName) {
1709
1727
  function resolveCommandsDir() {
1710
1728
  return resolveAssetDir("commands");
1711
1729
  }
1712
- function syncDirectory(sourceDir, targetDir) {
1713
- 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 };
1714
1824
  if (!existsSync4(targetDir)) {
1715
1825
  mkdirSync2(targetDir, { recursive: true });
1716
1826
  }
1717
1827
  const entries = readdirSync2(sourceDir);
1718
1828
  for (const entry of entries) {
1719
1829
  const sourcePath = resolve3(sourceDir, entry);
1720
- const targetPath = resolve3(targetDir, entry);
1721
1830
  const entryStat = statSync(sourcePath);
1722
1831
  if (entryStat.isDirectory()) {
1723
- 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
+ );
1724
1842
  stats.installed += subStats.installed;
1725
1843
  stats.updated += subStats.updated;
1726
1844
  stats.skipped += subStats.skipped;
1727
- } else if (entry.endsWith(".md")) {
1728
- const sourceContent = readFileSync3(sourcePath, "utf-8");
1729
- if (existsSync4(targetPath)) {
1730
- const existingContent = readFileSync3(targetPath, "utf-8");
1731
- if (existingContent === sourceContent) {
1732
- stats.skipped++;
1733
- continue;
1734
- }
1735
- writeFileSync(targetPath, sourceContent, "utf-8");
1736
- stats.updated++;
1737
- } else {
1738
- writeFileSync(targetPath, sourceContent, "utf-8");
1739
- 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;
1740
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++;
1741
1905
  }
1742
1906
  }
1743
1907
  return stats;
1744
1908
  }
1745
1909
  function installCommands(projectRoot) {
1746
1910
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
1747
- const targetDir = resolve3(projectRoot, claudeDirName, "commands");
1911
+ const claudeDir = resolve3(projectRoot, claudeDirName);
1912
+ const targetDir = resolve3(claudeDir, "commands");
1748
1913
  if (!existsSync4(targetDir)) {
1749
1914
  mkdirSync2(targetDir, { recursive: true });
1750
1915
  }
@@ -1752,9 +1917,13 @@ function installCommands(projectRoot) {
1752
1917
  if (!sourceDir) {
1753
1918
  console.error(" ERROR: Could not find massu commands directory.");
1754
1919
  console.error(" Try reinstalling: npm install @massu/core");
1755
- return { installed: 0, updated: 0, skipped: 0, commandsDir: targetDir };
1920
+ return { installed: 0, updated: 0, skipped: 0, kept: 0, commandsDir: targetDir };
1756
1921
  }
1757
- 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
+ );
1758
1927
  return { ...stats, commandsDir: targetDir };
1759
1928
  }
1760
1929
  function installAll(projectRoot) {
@@ -1764,19 +1933,36 @@ function installAll(projectRoot) {
1764
1933
  let totalInstalled = 0;
1765
1934
  let totalUpdated = 0;
1766
1935
  let totalSkipped = 0;
1767
- for (const assetType of ASSET_TYPES) {
1768
- const sourceDir = resolveAssetDir(assetType.name);
1769
- if (!sourceDir) {
1770
- 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;
1771
1956
  }
1772
- const targetDir = resolve3(claudeDir, assetType.targetSubdir);
1773
- const stats = syncDirectory(sourceDir, targetDir);
1774
- assets[assetType.name] = stats;
1775
- totalInstalled += stats.installed;
1776
- totalUpdated += stats.updated;
1777
- totalSkipped += stats.skipped;
1778
- }
1779
- return { assets, totalInstalled, totalUpdated, totalSkipped, claudeDir };
1957
+ });
1958
+ return {
1959
+ assets,
1960
+ totalInstalled,
1961
+ totalUpdated,
1962
+ totalSkipped,
1963
+ totalKept,
1964
+ claudeDir
1965
+ };
1780
1966
  }
1781
1967
  async function runInstallCommands() {
1782
1968
  const projectRoot = process.cwd();
@@ -1790,23 +1976,29 @@ async function runInstallCommands() {
1790
1976
  if (!stats) {
1791
1977
  continue;
1792
1978
  }
1793
- const total = stats.installed + stats.updated + stats.skipped;
1979
+ const total = stats.installed + stats.updated + stats.skipped + stats.kept;
1794
1980
  if (total === 0) continue;
1795
1981
  const parts = [];
1796
1982
  if (stats.installed > 0) parts.push(`${stats.installed} new`);
1797
1983
  if (stats.updated > 0) parts.push(`${stats.updated} updated`);
1798
1984
  if (stats.skipped > 0) parts.push(`${stats.skipped} current`);
1985
+ if (stats.kept > 0) parts.push(`${stats.kept} kept (local edits)`);
1799
1986
  const description = assetType.description;
1800
1987
  console.log(` ${description}: ${parts.join(", ")} (${total} total)`);
1801
1988
  }
1802
- const grandTotal = result.totalInstalled + result.totalUpdated + result.totalSkipped;
1989
+ const grandTotal = result.totalInstalled + result.totalUpdated + result.totalSkipped + result.totalKept;
1803
1990
  console.log("");
1804
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
+ }
1805
1997
  console.log("");
1806
1998
  console.log(" Restart your Claude Code session to use them.");
1807
1999
  console.log("");
1808
2000
  }
1809
- var __filename, __dirname, ASSET_TYPES;
2001
+ var __filename, __dirname, ASSET_TYPES, MANIFEST_VERSION, MANIFEST_RELPATH, PASSTHROUGH_LANG_KEYS;
1810
2002
  var init_install_commands = __esm({
1811
2003
  "src/commands/install-commands.ts"() {
1812
2004
  "use strict";
@@ -1820,6 +2012,16 @@ var init_install_commands = __esm({
1820
2012
  { name: "protocols", targetSubdir: "protocols", description: "protocol files" },
1821
2013
  { name: "reference", targetSubdir: "reference", description: "reference files" }
1822
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
+ ];
1823
2025
  }
1824
2026
  });
1825
2027
 
@@ -7798,41 +8000,41 @@ var require_queue = __commonJS({
7798
8000
  queue.drained = drained;
7799
8001
  return queue;
7800
8002
  function push(value) {
7801
- var p19 = new Promise(function(resolve25, reject) {
8003
+ var p19 = new Promise(function(resolve26, reject) {
7802
8004
  pushCb(value, function(err, result) {
7803
8005
  if (err) {
7804
8006
  reject(err);
7805
8007
  return;
7806
8008
  }
7807
- resolve25(result);
8009
+ resolve26(result);
7808
8010
  });
7809
8011
  });
7810
8012
  p19.catch(noop);
7811
8013
  return p19;
7812
8014
  }
7813
8015
  function unshift(value) {
7814
- var p19 = new Promise(function(resolve25, reject) {
8016
+ var p19 = new Promise(function(resolve26, reject) {
7815
8017
  unshiftCb(value, function(err, result) {
7816
8018
  if (err) {
7817
8019
  reject(err);
7818
8020
  return;
7819
8021
  }
7820
- resolve25(result);
8022
+ resolve26(result);
7821
8023
  });
7822
8024
  });
7823
8025
  p19.catch(noop);
7824
8026
  return p19;
7825
8027
  }
7826
8028
  function drained() {
7827
- var p19 = new Promise(function(resolve25) {
8029
+ var p19 = new Promise(function(resolve26) {
7828
8030
  process.nextTick(function() {
7829
8031
  if (queue.idle()) {
7830
- resolve25();
8032
+ resolve26();
7831
8033
  } else {
7832
8034
  var previousDrain = queue.drain;
7833
8035
  queue.drain = function() {
7834
8036
  if (typeof previousDrain === "function") previousDrain();
7835
- resolve25();
8037
+ resolve26();
7836
8038
  queue.drain = previousDrain;
7837
8039
  };
7838
8040
  }
@@ -8318,9 +8520,9 @@ var require_stream3 = __commonJS({
8318
8520
  });
8319
8521
  }
8320
8522
  _getStat(filepath) {
8321
- return new Promise((resolve25, reject) => {
8523
+ return new Promise((resolve26, reject) => {
8322
8524
  this._stat(filepath, this._fsStatSettings, (error, stats) => {
8323
- return error === null ? resolve25(stats) : reject(error);
8525
+ return error === null ? resolve26(stats) : reject(error);
8324
8526
  });
8325
8527
  });
8326
8528
  }
@@ -8344,10 +8546,10 @@ var require_async5 = __commonJS({
8344
8546
  this._readerStream = new stream_1.default(this._settings);
8345
8547
  }
8346
8548
  dynamic(root, options) {
8347
- return new Promise((resolve25, reject) => {
8549
+ return new Promise((resolve26, reject) => {
8348
8550
  this._walkAsync(root, options, (error, entries) => {
8349
8551
  if (error === null) {
8350
- resolve25(entries);
8552
+ resolve26(entries);
8351
8553
  } else {
8352
8554
  reject(error);
8353
8555
  }
@@ -8357,10 +8559,10 @@ var require_async5 = __commonJS({
8357
8559
  async static(patterns, options) {
8358
8560
  const entries = [];
8359
8561
  const stream = this._readerStream.static(patterns, options);
8360
- return new Promise((resolve25, reject) => {
8562
+ return new Promise((resolve26, reject) => {
8361
8563
  stream.once("error", reject);
8362
8564
  stream.on("data", (entry) => entries.push(entry));
8363
- stream.once("end", () => resolve25(entries));
8565
+ stream.once("end", () => resolve26(entries));
8364
8566
  });
8365
8567
  }
8366
8568
  };
@@ -9697,7 +9899,7 @@ var init_detect = __esm({
9697
9899
  });
9698
9900
 
9699
9901
  // src/detect/drift.ts
9700
- import { createHash } from "crypto";
9902
+ import { createHash as createHash2 } from "crypto";
9701
9903
  function summarizeDetection(det) {
9702
9904
  const languages = Array.from(new Set(det.manifests.map((m3) => m3.language))).sort();
9703
9905
  const frameworks = {};
@@ -9728,7 +9930,7 @@ function summarizeDetection(det) {
9728
9930
  function computeFingerprint(det) {
9729
9931
  const data = summarizeDetection(det);
9730
9932
  const stable = JSON.stringify(data, Object.keys(data).sort());
9731
- return createHash("sha256").update(stable).digest("hex");
9933
+ return createHash2("sha256").update(stable).digest("hex");
9732
9934
  }
9733
9935
  function stringOf(v3) {
9734
9936
  if (typeof v3 === "string") return v3;
@@ -10886,7 +11088,7 @@ __export(init_exports, {
10886
11088
  validateWrittenConfig: () => validateWrittenConfig,
10887
11089
  writeConfigAtomic: () => writeConfigAtomic
10888
11090
  });
10889
- 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";
10890
11092
  import { resolve as resolve5, basename as basename2, dirname as dirname4 } from "path";
10891
11093
  import { fileURLToPath as fileURLToPath2 } from "url";
10892
11094
  import { homedir as homedir2 } from "os";
@@ -11208,7 +11410,7 @@ function writeConfigAtomic(configPath, content) {
11208
11410
  if (parsed === null || typeof parsed !== "object") {
11209
11411
  throw new Error("Generated config is not a valid YAML object");
11210
11412
  }
11211
- renameSync(tmpPath, configPath);
11413
+ renameSync2(tmpPath, configPath);
11212
11414
  if (existingMode !== void 0) {
11213
11415
  try {
11214
11416
  chmodSync(configPath, existingMode);
@@ -11786,7 +11988,7 @@ var init_init = __esm({
11786
11988
  });
11787
11989
 
11788
11990
  // src/license.ts
11789
- import { createHash as createHash2 } from "crypto";
11991
+ import { createHash as createHash3 } from "crypto";
11790
11992
  function tierLevel(tier) {
11791
11993
  return TIER_LEVELS[tier] ?? 0;
11792
11994
  }
@@ -11811,7 +12013,7 @@ function annotateToolDefinitions(defs) {
11811
12013
  });
11812
12014
  }
11813
12015
  async function validateLicense(apiKey) {
11814
- const keyHash = createHash2("sha256").update(apiKey).digest("hex");
12016
+ const keyHash = createHash3("sha256").update(apiKey).digest("hex");
11815
12017
  const memDb = getMemoryDb();
11816
12018
  try {
11817
12019
  const cached = memDb.prepare(
@@ -11872,7 +12074,7 @@ async function validateLicense(apiKey) {
11872
12074
  }
11873
12075
  }
11874
12076
  function updateLicenseCache(apiKey, tier, validUntil, features = []) {
11875
- const keyHash = createHash2("sha256").update(apiKey).digest("hex");
12077
+ const keyHash = createHash3("sha256").update(apiKey).digest("hex");
11876
12078
  const memDb = getMemoryDb();
11877
12079
  try {
11878
12080
  memDb.prepare(`
@@ -12488,13 +12690,64 @@ var init_install_hooks = __esm({
12488
12690
  }
12489
12691
  });
12490
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
+
12491
12744
  // src/db.ts
12492
12745
  import Database2 from "better-sqlite3";
12493
12746
  import { dirname as dirname6, join as join6 } from "path";
12494
- 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";
12495
12748
  function getCodeGraphDb() {
12496
12749
  const dbPath = getResolvedPaths().codegraphDbPath;
12497
- if (!existsSync10(dbPath)) {
12750
+ if (!existsSync11(dbPath)) {
12498
12751
  throw new Error(`CodeGraph database not found at ${dbPath}. Run 'npx @colbymchenry/codegraph sync' first.`);
12499
12752
  }
12500
12753
  const db = new Database2(dbPath, { readonly: true });
@@ -12504,7 +12757,7 @@ function getCodeGraphDb() {
12504
12757
  function getDataDb() {
12505
12758
  const dbPath = getResolvedPaths().dataDbPath;
12506
12759
  const dir = dirname6(dbPath);
12507
- if (!existsSync10(dir)) {
12760
+ if (!existsSync11(dir)) {
12508
12761
  mkdirSync4(dir, { recursive: true });
12509
12762
  }
12510
12763
  const db = new Database2(dbPath);
@@ -12802,10 +13055,10 @@ var init_db = __esm({
12802
13055
  });
12803
13056
 
12804
13057
  // src/security-utils.ts
12805
- import { resolve as resolve7, normalize } from "path";
13058
+ import { resolve as resolve8, normalize } from "path";
12806
13059
  function ensureWithinRoot(filePath, projectRoot) {
12807
- const resolvedRoot = resolve7(projectRoot);
12808
- const resolvedPath = resolve7(resolvedRoot, filePath);
13060
+ const resolvedRoot = resolve8(projectRoot);
13061
+ const resolvedPath = resolve8(resolvedRoot, filePath);
12809
13062
  const normalizedPath = normalize(resolvedPath);
12810
13063
  const normalizedRoot = normalize(resolvedRoot);
12811
13064
  if (!normalizedPath.startsWith(normalizedRoot + "/") && normalizedPath !== normalizedRoot) {
@@ -12878,8 +13131,8 @@ var init_rules = __esm({
12878
13131
  });
12879
13132
 
12880
13133
  // src/import-resolver.ts
12881
- import { readFileSync as readFileSync8, existsSync as existsSync11, statSync as statSync6 } from "fs";
12882
- 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";
12883
13136
  function parseImports(source) {
12884
13137
  const imports = [];
12885
13138
  const lines = source.split("\n");
@@ -12935,23 +13188,23 @@ function resolveImportPath(specifier, fromFile) {
12935
13188
  let basePath;
12936
13189
  if (specifier.startsWith("@/")) {
12937
13190
  const paths = getResolvedPaths();
12938
- basePath = resolve8(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
13191
+ basePath = resolve9(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
12939
13192
  } else {
12940
- basePath = resolve8(dirname7(fromFile), specifier);
13193
+ basePath = resolve9(dirname7(fromFile), specifier);
12941
13194
  }
12942
- if (existsSync11(basePath) && !isDirectory(basePath)) {
13195
+ if (existsSync12(basePath) && !isDirectory(basePath)) {
12943
13196
  return toRelative(basePath);
12944
13197
  }
12945
13198
  const resolvedPaths = getResolvedPaths();
12946
13199
  for (const ext of resolvedPaths.extensions) {
12947
13200
  const withExt = basePath + ext;
12948
- if (existsSync11(withExt)) {
13201
+ if (existsSync12(withExt)) {
12949
13202
  return toRelative(withExt);
12950
13203
  }
12951
13204
  }
12952
13205
  for (const indexFile of resolvedPaths.indexFiles) {
12953
13206
  const indexPath = join7(basePath, indexFile);
12954
- if (existsSync11(indexPath)) {
13207
+ if (existsSync12(indexPath)) {
12955
13208
  return toRelative(indexPath);
12956
13209
  }
12957
13210
  }
@@ -12987,11 +13240,11 @@ function buildImportIndex(dataDb2, codegraphDb2) {
12987
13240
  const batchSize = 500;
12988
13241
  let batch = [];
12989
13242
  for (const file of files) {
12990
- const absPath = ensureWithinRoot(resolve8(projectRoot, file.path), projectRoot);
12991
- if (!existsSync11(absPath)) continue;
13243
+ const absPath = ensureWithinRoot(resolve9(projectRoot, file.path), projectRoot);
13244
+ if (!existsSync12(absPath)) continue;
12992
13245
  let source;
12993
13246
  try {
12994
- source = readFileSync8(absPath, "utf-8");
13247
+ source = readFileSync9(absPath, "utf-8");
12995
13248
  } catch {
12996
13249
  continue;
12997
13250
  }
@@ -13027,15 +13280,15 @@ var init_import_resolver = __esm({
13027
13280
  });
13028
13281
 
13029
13282
  // src/trpc-index.ts
13030
- import { readFileSync as readFileSync9, existsSync as existsSync12, readdirSync as readdirSync9 } from "fs";
13031
- 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";
13032
13285
  function parseRootRouter() {
13033
13286
  const paths = getResolvedPaths();
13034
13287
  const rootPath = paths.rootRouterPath;
13035
- if (!existsSync12(rootPath)) {
13288
+ if (!existsSync13(rootPath)) {
13036
13289
  throw new Error(`Root router not found at ${rootPath}`);
13037
13290
  }
13038
- const source = readFileSync9(rootPath, "utf-8");
13291
+ const source = readFileSync10(rootPath, "utf-8");
13039
13292
  const mappings = [];
13040
13293
  const importMap = /* @__PURE__ */ new Map();
13041
13294
  const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
@@ -13043,16 +13296,16 @@ function parseRootRouter() {
13043
13296
  while ((match = importRegex.exec(source)) !== null) {
13044
13297
  const variable = match[1];
13045
13298
  let filePath = match[2];
13046
- const fullPath = resolve9(paths.routersDir, filePath);
13299
+ const fullPath = resolve10(paths.routersDir, filePath);
13047
13300
  for (const ext of [".ts", ".tsx", ""]) {
13048
13301
  const candidate = fullPath + ext;
13049
13302
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
13050
- if (existsSync12(candidate)) {
13303
+ if (existsSync13(candidate)) {
13051
13304
  filePath = routersRelPath + "/" + filePath + ext;
13052
13305
  break;
13053
13306
  }
13054
13307
  const indexCandidate = join8(fullPath, "index.ts");
13055
- if (existsSync12(indexCandidate)) {
13308
+ if (existsSync13(indexCandidate)) {
13056
13309
  filePath = routersRelPath + "/" + filePath + "/index.ts";
13057
13310
  break;
13058
13311
  }
@@ -13071,9 +13324,9 @@ function parseRootRouter() {
13071
13324
  return mappings;
13072
13325
  }
13073
13326
  function extractProcedures(routerFilePath) {
13074
- const absPath = resolve9(getProjectRoot(), routerFilePath);
13075
- if (!existsSync12(absPath)) return [];
13076
- const source = readFileSync9(absPath, "utf-8");
13327
+ const absPath = resolve10(getProjectRoot(), routerFilePath);
13328
+ if (!existsSync13(absPath)) return [];
13329
+ const source = readFileSync10(absPath, "utf-8");
13077
13330
  const procedures = [];
13078
13331
  const seen = /* @__PURE__ */ new Set();
13079
13332
  const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
@@ -13096,13 +13349,13 @@ function findUICallSites(routerKey, procedureName) {
13096
13349
  const root = getProjectRoot();
13097
13350
  const src = config.paths.source;
13098
13351
  const searchDirs = [
13099
- resolve9(root, config.paths.pages ?? src + "/app"),
13100
- resolve9(root, config.paths.components ?? src + "/components"),
13101
- 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")
13102
13355
  ];
13103
13356
  const searchPattern = `api.${routerKey}.${procedureName}`;
13104
13357
  for (const dir of searchDirs) {
13105
- if (!existsSync12(dir)) continue;
13358
+ if (!existsSync13(dir)) continue;
13106
13359
  searchDirectory(dir, searchPattern, callSites);
13107
13360
  }
13108
13361
  return callSites;
@@ -13116,7 +13369,7 @@ function searchDirectory(dir, pattern, results) {
13116
13369
  searchDirectory(fullPath, pattern, results);
13117
13370
  } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
13118
13371
  try {
13119
- const source = readFileSync9(fullPath, "utf-8");
13372
+ const source = readFileSync10(fullPath, "utf-8");
13120
13373
  const lines = source.split("\n");
13121
13374
  for (let i = 0; i < lines.length; i++) {
13122
13375
  if (lines[i].includes(pattern)) {
@@ -13179,8 +13432,8 @@ var init_trpc_index = __esm({
13179
13432
  });
13180
13433
 
13181
13434
  // src/page-deps.ts
13182
- import { readFileSync as readFileSync10, existsSync as existsSync13 } from "fs";
13183
- import { resolve as resolve10 } from "path";
13435
+ import { readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
13436
+ import { resolve as resolve11 } from "path";
13184
13437
  function deriveRoute(pageFile) {
13185
13438
  let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
13186
13439
  return route || "/";
@@ -13218,10 +13471,10 @@ function findRouterCalls(files) {
13218
13471
  const routers = /* @__PURE__ */ new Set();
13219
13472
  const projectRoot = getProjectRoot();
13220
13473
  for (const file of files) {
13221
- const absPath = ensureWithinRoot(resolve10(projectRoot, file), projectRoot);
13222
- if (!existsSync13(absPath)) continue;
13474
+ const absPath = ensureWithinRoot(resolve11(projectRoot, file), projectRoot);
13475
+ if (!existsSync14(absPath)) continue;
13223
13476
  try {
13224
- const source = readFileSync10(absPath, "utf-8");
13477
+ const source = readFileSync11(absPath, "utf-8");
13225
13478
  const apiCallRegex = /api\.(\w+)\.\w+/g;
13226
13479
  let match;
13227
13480
  while ((match = apiCallRegex.exec(source)) !== null) {
@@ -13239,10 +13492,10 @@ function findTablesFromRouters(routerNames, dataDb2) {
13239
13492
  "SELECT DISTINCT router_file FROM massu_trpc_procedures WHERE router_name = ?"
13240
13493
  ).all(routerName);
13241
13494
  for (const proc of procs) {
13242
- const absPath = ensureWithinRoot(resolve10(getProjectRoot(), proc.router_file), getProjectRoot());
13243
- if (!existsSync13(absPath)) continue;
13495
+ const absPath = ensureWithinRoot(resolve11(getProjectRoot(), proc.router_file), getProjectRoot());
13496
+ if (!existsSync14(absPath)) continue;
13244
13497
  try {
13245
- const source = readFileSync10(absPath, "utf-8");
13498
+ const source = readFileSync11(absPath, "utf-8");
13246
13499
  const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
13247
13500
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
13248
13501
  const tableRegex = new RegExp(regexStr + "\\.", "g");
@@ -13511,14 +13764,14 @@ var init_domains = __esm({
13511
13764
  });
13512
13765
 
13513
13766
  // src/schema-mapper.ts
13514
- 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";
13515
13768
  import { join as join9 } from "path";
13516
13769
  function parsePrismaSchema() {
13517
13770
  const schemaPath = getResolvedPaths().prismaSchemaPath;
13518
- if (!existsSync14(schemaPath)) {
13771
+ if (!existsSync15(schemaPath)) {
13519
13772
  throw new Error(`Prisma schema not found at ${schemaPath}`);
13520
13773
  }
13521
- const source = readFileSync11(schemaPath, "utf-8");
13774
+ const source = readFileSync12(schemaPath, "utf-8");
13522
13775
  const models = [];
13523
13776
  const sourceLines = source.split("\n");
13524
13777
  let i = 0;
@@ -13576,7 +13829,7 @@ function toSnakeCase(str) {
13576
13829
  function findColumnUsageInRouters(tableName) {
13577
13830
  const usage = /* @__PURE__ */ new Map();
13578
13831
  const routersDir = getResolvedPaths().routersDir;
13579
- if (!existsSync14(routersDir)) return usage;
13832
+ if (!existsSync15(routersDir)) return usage;
13580
13833
  scanDirectory(routersDir, tableName, usage);
13581
13834
  return usage;
13582
13835
  }
@@ -13593,7 +13846,7 @@ function scanDirectory(dir, tableName, usage) {
13593
13846
  }
13594
13847
  function scanFile(absPath, tableName, usage) {
13595
13848
  try {
13596
- const source = readFileSync11(absPath, "utf-8");
13849
+ const source = readFileSync12(absPath, "utf-8");
13597
13850
  if (!source.includes(tableName)) return;
13598
13851
  const relPath = absPath.slice(getProjectRoot().length + 1);
13599
13852
  const lines = source.split("\n");
@@ -13638,7 +13891,7 @@ function detectMismatches(models) {
13638
13891
  }
13639
13892
  function findFilesUsingColumn(dir, column, tableName) {
13640
13893
  const result = [];
13641
- if (!existsSync14(dir)) return result;
13894
+ if (!existsSync15(dir)) return result;
13642
13895
  const entries = readdirSync10(dir, { withFileTypes: true });
13643
13896
  for (const entry of entries) {
13644
13897
  const fullPath = join9(dir, entry.name);
@@ -13646,7 +13899,7 @@ function findFilesUsingColumn(dir, column, tableName) {
13646
13899
  result.push(...findFilesUsingColumn(fullPath, column, tableName));
13647
13900
  } else if (entry.name.endsWith(".ts")) {
13648
13901
  try {
13649
- const source = readFileSync11(fullPath, "utf-8");
13902
+ const source = readFileSync12(fullPath, "utf-8");
13650
13903
  if (source.includes(tableName) && source.includes(column)) {
13651
13904
  result.push(fullPath.slice(getProjectRoot().length + 1));
13652
13905
  }
@@ -13791,12 +14044,12 @@ var init_import_parser = __esm({
13791
14044
  });
13792
14045
 
13793
14046
  // src/python/import-resolver.ts
13794
- import { readFileSync as readFileSync12, existsSync as existsSync15, readdirSync as readdirSync11 } from "fs";
13795
- 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";
13796
14049
  function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
13797
14050
  const projectRoot = getProjectRoot();
13798
14051
  if (level > 0) {
13799
- let baseDir = dirname8(resolve12(projectRoot, fromFile));
14052
+ let baseDir = dirname8(resolve13(projectRoot, fromFile));
13800
14053
  for (let i = 1; i < level; i++) {
13801
14054
  baseDir = dirname8(baseDir);
13802
14055
  }
@@ -13808,17 +14061,17 @@ function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
13808
14061
  return tryResolvePythonPath(baseDir, projectRoot);
13809
14062
  }
13810
14063
  const parts = module.split(".");
13811
- const candidate = join10(resolve12(projectRoot, pythonRoot), ...parts);
14064
+ const candidate = join10(resolve13(projectRoot, pythonRoot), ...parts);
13812
14065
  return tryResolvePythonPath(candidate, projectRoot);
13813
14066
  }
13814
14067
  function tryResolvePythonPath(basePath, projectRoot) {
13815
- if (existsSync15(basePath + ".py")) {
14068
+ if (existsSync16(basePath + ".py")) {
13816
14069
  return relative4(projectRoot, basePath + ".py");
13817
14070
  }
13818
- if (existsSync15(join10(basePath, "__init__.py"))) {
14071
+ if (existsSync16(join10(basePath, "__init__.py"))) {
13819
14072
  return relative4(projectRoot, join10(basePath, "__init__.py"));
13820
14073
  }
13821
- if (basePath.endsWith(".py") && existsSync15(basePath)) {
14074
+ if (basePath.endsWith(".py") && existsSync16(basePath)) {
13822
14075
  return relative4(projectRoot, basePath);
13823
14076
  }
13824
14077
  return null;
@@ -13841,7 +14094,7 @@ function walkPythonFiles(dir, excludeDirs) {
13841
14094
  }
13842
14095
  function buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
13843
14096
  const projectRoot = getProjectRoot();
13844
- const absRoot = resolve12(projectRoot, pythonRoot);
14097
+ const absRoot = resolve13(projectRoot, pythonRoot);
13845
14098
  dataDb2.exec("DELETE FROM massu_py_imports");
13846
14099
  const insertStmt = dataDb2.prepare(
13847
14100
  "INSERT INTO massu_py_imports (source_file, target_file, import_type, imported_names, line) VALUES (?, ?, ?, ?, ?)"
@@ -13858,7 +14111,7 @@ function buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__
13858
14111
  const relFile = relative4(projectRoot, absFile);
13859
14112
  let source;
13860
14113
  try {
13861
- source = readFileSync12(absFile, "utf-8");
14114
+ source = readFileSync13(absFile, "utf-8");
13862
14115
  } catch {
13863
14116
  continue;
13864
14117
  }
@@ -14124,7 +14377,7 @@ var init_route_parser = __esm({
14124
14377
  });
14125
14378
 
14126
14379
  // src/python/route-indexer.ts
14127
- import { readFileSync as readFileSync13, readdirSync as readdirSync12 } from "fs";
14380
+ import { readFileSync as readFileSync14, readdirSync as readdirSync12 } from "fs";
14128
14381
  import { join as join11, relative as relative5 } from "path";
14129
14382
  function walkPyFiles(dir, excludeDirs) {
14130
14383
  const files = [];
@@ -14157,7 +14410,7 @@ function buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
14157
14410
  const relFile = relative5(projectRoot, absFile);
14158
14411
  let source;
14159
14412
  try {
14160
- source = readFileSync13(absFile, "utf-8");
14413
+ source = readFileSync14(absFile, "utf-8");
14161
14414
  } catch {
14162
14415
  continue;
14163
14416
  }
@@ -14368,7 +14621,7 @@ var init_model_parser = __esm({
14368
14621
  });
14369
14622
 
14370
14623
  // src/python/model-indexer.ts
14371
- import { readFileSync as readFileSync14, readdirSync as readdirSync13 } from "fs";
14624
+ import { readFileSync as readFileSync15, readdirSync as readdirSync13 } from "fs";
14372
14625
  import { join as join12, relative as relative6 } from "path";
14373
14626
  function walkPyFiles2(dir, excludeDirs) {
14374
14627
  const files = [];
@@ -14404,7 +14657,7 @@ function buildPythonModelIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
14404
14657
  const relFile = relative6(projectRoot, absFile);
14405
14658
  let source;
14406
14659
  try {
14407
- source = readFileSync14(absFile, "utf-8");
14660
+ source = readFileSync15(absFile, "utf-8");
14408
14661
  } catch {
14409
14662
  continue;
14410
14663
  }
@@ -14664,7 +14917,7 @@ var init_migration_parser = __esm({
14664
14917
  });
14665
14918
 
14666
14919
  // src/python/migration-indexer.ts
14667
- import { readFileSync as readFileSync15, readdirSync as readdirSync14 } from "fs";
14920
+ import { readFileSync as readFileSync16, readdirSync as readdirSync14 } from "fs";
14668
14921
  import { join as join13, relative as relative7 } from "path";
14669
14922
  function buildPythonMigrationIndex(dataDb2, alembicDir) {
14670
14923
  const projectRoot = getProjectRoot();
@@ -14690,7 +14943,7 @@ function buildPythonMigrationIndex(dataDb2, alembicDir) {
14690
14943
  for (const absFile of files) {
14691
14944
  let source;
14692
14945
  try {
14693
- source = readFileSync15(absFile, "utf-8");
14946
+ source = readFileSync16(absFile, "utf-8");
14694
14947
  } catch {
14695
14948
  continue;
14696
14949
  }
@@ -14724,7 +14977,7 @@ var init_migration_indexer = __esm({
14724
14977
  });
14725
14978
 
14726
14979
  // src/python/coupling-detector.ts
14727
- import { readFileSync as readFileSync16, readdirSync as readdirSync15 } from "fs";
14980
+ import { readFileSync as readFileSync17, readdirSync as readdirSync15 } from "fs";
14728
14981
  import { join as join14, relative as relative8 } from "path";
14729
14982
  function buildPythonCouplingIndex(dataDb2) {
14730
14983
  const projectRoot = getProjectRoot();
@@ -14761,7 +15014,7 @@ function buildPythonCouplingIndex(dataDb2) {
14761
15014
  const relFile = relative8(projectRoot, absFile);
14762
15015
  let source;
14763
15016
  try {
14764
- source = readFileSync16(absFile, "utf-8");
15017
+ source = readFileSync17(absFile, "utf-8");
14765
15018
  } catch {
14766
15019
  continue;
14767
15020
  }
@@ -15166,8 +15419,8 @@ var init_memory_tools = __esm({
15166
15419
  });
15167
15420
 
15168
15421
  // src/docs-tools.ts
15169
- import { readFileSync as readFileSync17, existsSync as existsSync16 } from "fs";
15170
- 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";
15171
15424
  function p3(baseName) {
15172
15425
  return `${getConfig().toolPrefix}_${baseName}`;
15173
15426
  }
@@ -15222,10 +15475,10 @@ function handleDocsToolCall(name, args2) {
15222
15475
  }
15223
15476
  function loadDocsMap() {
15224
15477
  const mapPath = getResolvedPaths().docsMapPath;
15225
- if (!existsSync16(mapPath)) {
15478
+ if (!existsSync17(mapPath)) {
15226
15479
  throw new Error(`docs-map.json not found at ${mapPath}`);
15227
15480
  }
15228
- return JSON.parse(readFileSync17(mapPath, "utf-8"));
15481
+ return JSON.parse(readFileSync18(mapPath, "utf-8"));
15229
15482
  }
15230
15483
  function matchesPattern(filePath, pattern) {
15231
15484
  const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
@@ -15295,13 +15548,13 @@ function extractFrontmatter(content) {
15295
15548
  }
15296
15549
  function extractProcedureNames(routerPath) {
15297
15550
  const root = getProjectRoot();
15298
- const absPath = ensureWithinRoot(resolve13(getResolvedPaths().srcDir, "..", routerPath), root);
15299
- if (!existsSync16(absPath)) {
15300
- const altPath = ensureWithinRoot(resolve13(getResolvedPaths().srcDir, "../server/api/routers", basename3(routerPath)), root);
15301
- if (!existsSync16(altPath)) return [];
15302
- 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"));
15303
15556
  }
15304
- return extractProcedureNamesFromContent(readFileSync17(absPath, "utf-8"));
15557
+ return extractProcedureNamesFromContent(readFileSync18(absPath, "utf-8"));
15305
15558
  }
15306
15559
  function extractProcedureNamesFromContent(content) {
15307
15560
  const procRegex = /\.(?:query|mutation)\s*\(/g;
@@ -15341,8 +15594,8 @@ function handleDocsAudit(args2) {
15341
15594
  for (const [mappingId, triggeringFiles] of affectedMappings) {
15342
15595
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
15343
15596
  if (!mapping) continue;
15344
- const helpPagePath = ensureWithinRoot(resolve13(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
15345
- if (!existsSync16(helpPagePath)) {
15597
+ const helpPagePath = ensureWithinRoot(resolve14(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
15598
+ if (!existsSync17(helpPagePath)) {
15346
15599
  results.push({
15347
15600
  helpPage: mapping.helpPage,
15348
15601
  mappingId,
@@ -15354,7 +15607,7 @@ function handleDocsAudit(args2) {
15354
15607
  });
15355
15608
  continue;
15356
15609
  }
15357
- const content = readFileSync17(helpPagePath, "utf-8");
15610
+ const content = readFileSync18(helpPagePath, "utf-8");
15358
15611
  const sections = extractSections(content);
15359
15612
  const frontmatter = extractFrontmatter(content);
15360
15613
  const staleReasons = [];
@@ -15394,9 +15647,9 @@ function handleDocsAudit(args2) {
15394
15647
  });
15395
15648
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
15396
15649
  if (parentId === mappingId) {
15397
- const guidePath = ensureWithinRoot(resolve13(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
15398
- if (existsSync16(guidePath)) {
15399
- 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");
15400
15653
  const guideFrontmatter = extractFrontmatter(guideContent);
15401
15654
  if (!guideFrontmatter?.lastVerified || status === "STALE") {
15402
15655
  results.push({
@@ -15429,14 +15682,14 @@ function handleDocsCoverage(args2) {
15429
15682
  const gaps = [];
15430
15683
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
15431
15684
  for (const mapping of mappings) {
15432
- const helpPagePath = ensureWithinRoot(resolve13(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
15433
- const exists = existsSync16(helpPagePath);
15685
+ const helpPagePath = ensureWithinRoot(resolve14(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
15686
+ const exists = existsSync17(helpPagePath);
15434
15687
  let hasContent = false;
15435
15688
  let lineCount = 0;
15436
15689
  let lastVerified = null;
15437
15690
  let status = null;
15438
15691
  if (exists) {
15439
- const content = readFileSync17(helpPagePath, "utf-8");
15692
+ const content = readFileSync18(helpPagePath, "utf-8");
15440
15693
  lineCount = content.split("\n").length;
15441
15694
  hasContent = lineCount > 10;
15442
15695
  const frontmatter = extractFrontmatter(content);
@@ -15777,8 +16030,8 @@ var init_observability_tools = __esm({
15777
16030
  });
15778
16031
 
15779
16032
  // src/sentinel-db.ts
15780
- import { existsSync as existsSync17 } from "fs";
15781
- import { resolve as resolve14 } from "path";
16033
+ import { existsSync as existsSync18 } from "fs";
16034
+ import { resolve as resolve15 } from "path";
15782
16035
  function parsePortalScope(raw) {
15783
16036
  if (!raw) return [];
15784
16037
  try {
@@ -16014,23 +16267,23 @@ function validateFeatures(db, domainFilter) {
16014
16267
  const missingProcedures = [];
16015
16268
  const missingPages = [];
16016
16269
  for (const comp of components) {
16017
- const absPath = resolve14(PROJECT_ROOT, comp.component_file);
16018
- if (!existsSync17(absPath)) {
16270
+ const absPath = resolve15(PROJECT_ROOT, comp.component_file);
16271
+ if (!existsSync18(absPath)) {
16019
16272
  missingComponents.push(comp.component_file);
16020
16273
  }
16021
16274
  }
16022
16275
  for (const proc of procedures) {
16023
- const routerPath = resolve14(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
16024
- if (!existsSync17(routerPath)) {
16276
+ const routerPath = resolve15(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
16277
+ if (!existsSync18(routerPath)) {
16025
16278
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
16026
16279
  }
16027
16280
  }
16028
16281
  for (const page of pages) {
16029
16282
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
16030
- const absPath = resolve14(PROJECT_ROOT, routeToPath);
16031
- if (page.page_route.startsWith("/") && !existsSync17(absPath)) {
16032
- const altPath = resolve14(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
16033
- 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)) {
16034
16287
  missingPages.push(page.page_route);
16035
16288
  }
16036
16289
  }
@@ -16554,8 +16807,8 @@ var init_sentinel_tools = __esm({
16554
16807
  });
16555
16808
 
16556
16809
  // src/sentinel-scanner.ts
16557
- import { readFileSync as readFileSync18, existsSync as existsSync18, readdirSync as readdirSync16, statSync as statSync7 } from "fs";
16558
- 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";
16559
16812
  function inferDomain(filePath) {
16560
16813
  const domains = getConfig().domains;
16561
16814
  const path = filePath.toLowerCase();
@@ -16684,8 +16937,8 @@ function scanComponentExports(dataDb2) {
16684
16937
  const projectRoot = getProjectRoot();
16685
16938
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
16686
16939
  const componentDirs = [];
16687
- const basePath = resolve15(projectRoot, componentsBase);
16688
- if (existsSync18(basePath)) {
16940
+ const basePath = resolve16(projectRoot, componentsBase);
16941
+ if (existsSync19(basePath)) {
16689
16942
  try {
16690
16943
  const entries = readdirSync16(basePath, { withFileTypes: true });
16691
16944
  for (const entry of entries) {
@@ -16697,12 +16950,12 @@ function scanComponentExports(dataDb2) {
16697
16950
  }
16698
16951
  }
16699
16952
  for (const dir of componentDirs) {
16700
- const absDir = resolve15(projectRoot, dir);
16701
- if (!existsSync18(absDir)) continue;
16953
+ const absDir = resolve16(projectRoot, dir);
16954
+ if (!existsSync19(absDir)) continue;
16702
16955
  const files = walkDir(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
16703
16956
  for (const file of files) {
16704
16957
  const relPath = relative9(projectRoot, file);
16705
- const source = readFileSync18(file, "utf-8");
16958
+ const source = readFileSync19(file, "utf-8");
16706
16959
  const annotations = parseFeatureAnnotations(source);
16707
16960
  if (annotations.length > 0) {
16708
16961
  for (const ann of annotations) {
@@ -17741,7 +17994,7 @@ var init_audit_trail = __esm({
17741
17994
  });
17742
17995
 
17743
17996
  // src/validation-engine.ts
17744
- import { existsSync as existsSync19, readFileSync as readFileSync19 } from "fs";
17997
+ import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
17745
17998
  function p10(baseName) {
17746
17999
  return `${getConfig().toolPrefix}_${baseName}`;
17747
18000
  }
@@ -17772,7 +18025,7 @@ function validateFile(filePath, projectRoot) {
17772
18025
  });
17773
18026
  return checks;
17774
18027
  }
17775
- if (!existsSync19(absPath)) {
18028
+ if (!existsSync20(absPath)) {
17776
18029
  checks.push({
17777
18030
  name: "file_exists",
17778
18031
  severity: "error",
@@ -17781,7 +18034,7 @@ function validateFile(filePath, projectRoot) {
17781
18034
  });
17782
18035
  return checks;
17783
18036
  }
17784
- const source = readFileSync19(absPath, "utf-8");
18037
+ const source = readFileSync20(absPath, "utf-8");
17785
18038
  const lines = source.split("\n");
17786
18039
  if (activeChecks.rule_compliance !== false) {
17787
18040
  for (const ruleSet of config.rules) {
@@ -18226,7 +18479,7 @@ var init_adr_generator = __esm({
18226
18479
  });
18227
18480
 
18228
18481
  // src/security-scorer.ts
18229
- import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
18482
+ import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
18230
18483
  function p12(baseName) {
18231
18484
  return `${getConfig().toolPrefix}_${baseName}`;
18232
18485
  }
@@ -18250,12 +18503,12 @@ function scoreFileSecurity(filePath, projectRoot) {
18250
18503
  }]
18251
18504
  };
18252
18505
  }
18253
- if (!existsSync20(absPath)) {
18506
+ if (!existsSync21(absPath)) {
18254
18507
  return { riskScore: 0, findings: [] };
18255
18508
  }
18256
18509
  let source;
18257
18510
  try {
18258
- source = readFileSync20(absPath, "utf-8");
18511
+ source = readFileSync21(absPath, "utf-8");
18259
18512
  } catch {
18260
18513
  return { riskScore: 0, findings: [] };
18261
18514
  }
@@ -18544,8 +18797,8 @@ var init_security_scorer = __esm({
18544
18797
  });
18545
18798
 
18546
18799
  // src/dependency-scorer.ts
18547
- import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
18548
- import { resolve as resolve16 } from "path";
18800
+ import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
18801
+ import { resolve as resolve17 } from "path";
18549
18802
  function p13(baseName) {
18550
18803
  return `${getConfig().toolPrefix}_${baseName}`;
18551
18804
  }
@@ -18577,10 +18830,10 @@ function calculateDepRisk(factors) {
18577
18830
  return Math.min(100, risk);
18578
18831
  }
18579
18832
  function getInstalledPackages(projectRoot) {
18580
- const pkgPath = resolve16(projectRoot, "package.json");
18581
- if (!existsSync21(pkgPath)) return /* @__PURE__ */ new Map();
18833
+ const pkgPath = resolve17(projectRoot, "package.json");
18834
+ if (!existsSync22(pkgPath)) return /* @__PURE__ */ new Map();
18582
18835
  try {
18583
- const pkg = JSON.parse(readFileSync21(pkgPath, "utf-8"));
18836
+ const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
18584
18837
  const packages = /* @__PURE__ */ new Map();
18585
18838
  for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
18586
18839
  packages.set(name, version);
@@ -19182,9 +19435,9 @@ var init_regression_detector = __esm({
19182
19435
  });
19183
19436
 
19184
19437
  // src/knowledge-indexer.ts
19185
- import { createHash as createHash3 } from "crypto";
19186
- import { readFileSync as readFileSync22, readdirSync as readdirSync17, statSync as statSync8, existsSync as existsSync22 } from "fs";
19187
- 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";
19188
19441
  function getKnowledgePaths() {
19189
19442
  const resolved = getResolvedPaths();
19190
19443
  const config = getConfig();
@@ -19212,7 +19465,7 @@ function discoverMarkdownFiles(baseDir) {
19212
19465
  try {
19213
19466
  const entries = readdirSync17(dir, { withFileTypes: true });
19214
19467
  for (const entry of entries) {
19215
- const fullPath = resolve17(dir, entry.name);
19468
+ const fullPath = resolve18(dir, entry.name);
19216
19469
  if (entry.isDirectory()) {
19217
19470
  if (entry.name === "archive" && dir.includes("session-state")) continue;
19218
19471
  if (entry.name === "archive" && dir.includes("status")) continue;
@@ -19269,8 +19522,8 @@ function categorizeFile(filePath) {
19269
19522
  if (knownCategories.includes(firstDir)) return firstDir;
19270
19523
  return "root";
19271
19524
  }
19272
- function hashContent(content) {
19273
- return createHash3("sha256").update(content).digest("hex");
19525
+ function hashContent2(content) {
19526
+ return createHash4("sha256").update(content).digest("hex");
19274
19527
  }
19275
19528
  function parseCRTable(content) {
19276
19529
  const rules = [];
@@ -19490,11 +19743,11 @@ function indexAllKnowledge(db) {
19490
19743
  files.push(...memFiles);
19491
19744
  } catch {
19492
19745
  }
19493
- if (existsSync22(paths.plansDir)) {
19746
+ if (existsSync23(paths.plansDir)) {
19494
19747
  const planFiles = discoverMarkdownFiles(paths.plansDir);
19495
19748
  files.push(...planFiles);
19496
19749
  }
19497
- if (existsSync22(paths.docsDir)) {
19750
+ if (existsSync23(paths.docsDir)) {
19498
19751
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
19499
19752
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
19500
19753
  files.push(...docsFiles);
@@ -19537,9 +19790,9 @@ function indexAllKnowledge(db) {
19537
19790
  } catch {
19538
19791
  }
19539
19792
  for (const filePath of files) {
19540
- if (!existsSync22(filePath)) continue;
19541
- const content = readFileSync22(filePath, "utf-8");
19542
- const hash = hashContent(content);
19793
+ if (!existsSync23(filePath)) continue;
19794
+ const content = readFileSync23(filePath, "utf-8");
19795
+ const hash = hashContent2(content);
19543
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);
19544
19797
  const category = categorizeFile(filePath);
19545
19798
  const title = extractTitle(content, filePath);
@@ -19658,10 +19911,10 @@ function isKnowledgeStale(db) {
19658
19911
  files.push(...discoverMarkdownFiles(paths.memoryDir));
19659
19912
  } catch {
19660
19913
  }
19661
- if (existsSync22(paths.plansDir)) {
19914
+ if (existsSync23(paths.plansDir)) {
19662
19915
  files.push(...discoverMarkdownFiles(paths.plansDir));
19663
19916
  }
19664
- if (existsSync22(paths.docsDir)) {
19917
+ if (existsSync23(paths.docsDir)) {
19665
19918
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
19666
19919
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
19667
19920
  files.push(...docsFiles);
@@ -19690,8 +19943,8 @@ var init_knowledge_indexer = __esm({
19690
19943
  });
19691
19944
 
19692
19945
  // src/knowledge-tools.ts
19693
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync3, appendFileSync, readdirSync as readdirSync18 } from "fs";
19694
- 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";
19695
19948
  function p16(baseName) {
19696
19949
  return `${getConfig().toolPrefix}_${baseName}`;
19697
19950
  }
@@ -20428,7 +20681,7 @@ function handleCorrect(db, args2) {
20428
20681
  if (!wrong || !correction || !rule) {
20429
20682
  return text15("Error: wrong, correction, and rule are all required.");
20430
20683
  }
20431
- const correctionsPath = resolve18(getResolvedPaths().memoryDir, "corrections.md");
20684
+ const correctionsPath = resolve19(getResolvedPaths().memoryDir, "corrections.md");
20432
20685
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
20433
20686
  const title = rule.slice(0, 60);
20434
20687
  const entry = `
@@ -20441,7 +20694,7 @@ ${crRule ? `- **CR**: ${crRule}
20441
20694
  `;
20442
20695
  let existing = "";
20443
20696
  try {
20444
- existing = readFileSync23(correctionsPath, "utf-8");
20697
+ existing = readFileSync24(correctionsPath, "utf-8");
20445
20698
  } catch {
20446
20699
  }
20447
20700
  const archiveIdx = existing.indexOf("## Archived");
@@ -20780,11 +21033,11 @@ var init_knowledge_tools = __esm({
20780
21033
  // src/knowledge-db.ts
20781
21034
  import Database3 from "better-sqlite3";
20782
21035
  import { dirname as dirname10 } from "path";
20783
- import { existsSync as existsSync24, mkdirSync as mkdirSync5 } from "fs";
21036
+ import { existsSync as existsSync25, mkdirSync as mkdirSync5 } from "fs";
20784
21037
  function getKnowledgeDb() {
20785
21038
  const dbPath = getResolvedPaths().knowledgeDbPath;
20786
21039
  const dir = dirname10(dbPath);
20787
- if (!existsSync24(dir)) {
21040
+ if (!existsSync25(dir)) {
20788
21041
  mkdirSync5(dir, { recursive: true });
20789
21042
  }
20790
21043
  const db = new Database3(dbPath);
@@ -21518,8 +21771,8 @@ var init_python_tools = __esm({
21518
21771
  });
21519
21772
 
21520
21773
  // src/tools.ts
21521
- import { readFileSync as readFileSync24, existsSync as existsSync25 } from "fs";
21522
- 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";
21523
21776
  function prefix2() {
21524
21777
  return getConfig().toolPrefix;
21525
21778
  }
@@ -21554,7 +21807,7 @@ function ensureIndexes(dataDb2, codegraphDb2, force = false) {
21554
21807
  if (config.python?.root) {
21555
21808
  const pythonRoot = config.python.root;
21556
21809
  const excludeDirs = config.python.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
21557
- if (force || isPythonDataStale(dataDb2, resolve19(getProjectRoot(), pythonRoot))) {
21810
+ if (force || isPythonDataStale(dataDb2, resolve20(getProjectRoot(), pythonRoot))) {
21558
21811
  const pyImports = buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs);
21559
21812
  results.push(`Python imports: ${pyImports}`);
21560
21813
  const pyRoutes = buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs);
@@ -21953,9 +22206,9 @@ function handleContext(file, dataDb2, codegraphDb2) {
21953
22206
  try {
21954
22207
  const resolvedPaths = getResolvedPaths();
21955
22208
  const root = getProjectRoot();
21956
- const absFilePath = ensureWithinRoot(resolve19(resolvedPaths.srcDir, "..", file), root);
21957
- if (existsSync25(absFilePath)) {
21958
- 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);
21959
22212
  const keywords = [];
21960
22213
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
21961
22214
  if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
@@ -22379,11 +22632,11 @@ function handleSchema(args2) {
22379
22632
  lines.push("Checking all column references against Prisma schema...");
22380
22633
  lines.push("");
22381
22634
  const projectRoot = getProjectRoot();
22382
- const absPath = ensureWithinRoot(resolve19(projectRoot, file), projectRoot);
22383
- if (!existsSync25(absPath)) {
22635
+ const absPath = ensureWithinRoot(resolve20(projectRoot, file), projectRoot);
22636
+ if (!existsSync26(absPath)) {
22384
22637
  return text17(`File not found: ${file}`);
22385
22638
  }
22386
- const source = readFileSync24(absPath, "utf-8");
22639
+ const source = readFileSync25(absPath, "utf-8");
22387
22640
  const config = getConfig();
22388
22641
  const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
22389
22642
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
@@ -22461,8 +22714,8 @@ var init_tools = __esm({
22461
22714
 
22462
22715
  // src/server.ts
22463
22716
  var server_exports = {};
22464
- import { readFileSync as readFileSync25 } from "fs";
22465
- 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";
22466
22719
  import { fileURLToPath as fileURLToPath4 } from "url";
22467
22720
  function getDb() {
22468
22721
  if (!codegraphDb) codegraphDb = getCodeGraphDb();
@@ -22557,7 +22810,7 @@ var init_server = __esm({
22557
22810
  __dirname4 = dirname11(fileURLToPath4(import.meta.url));
22558
22811
  PKG_VERSION = (() => {
22559
22812
  try {
22560
- const pkg = JSON.parse(readFileSync25(resolve20(__dirname4, "..", "package.json"), "utf-8"));
22813
+ const pkg = JSON.parse(readFileSync26(resolve21(__dirname4, "..", "package.json"), "utf-8"));
22561
22814
  return pkg.version ?? "0.0.0";
22562
22815
  } catch {
22563
22816
  return "0.0.0";
@@ -22669,8 +22922,8 @@ __export(config_refresh_exports, {
22669
22922
  mergeRefresh: () => mergeRefresh,
22670
22923
  runConfigRefresh: () => runConfigRefresh
22671
22924
  });
22672
- import { existsSync as existsSync26, readFileSync as readFileSync26 } from "fs";
22673
- import { resolve as resolve21 } from "path";
22925
+ import { existsSync as existsSync27, readFileSync as readFileSync27 } from "fs";
22926
+ import { resolve as resolve22 } from "path";
22674
22927
  import { parse as parseYaml5 } from "yaml";
22675
22928
  function flatten(obj, prefix3 = "") {
22676
22929
  const out = {};
@@ -22798,17 +23051,17 @@ function renderDiff(diff) {
22798
23051
  }
22799
23052
  async function runConfigRefresh(opts = {}) {
22800
23053
  const cwd = opts.cwd ?? process.cwd();
22801
- const configPath = resolve21(cwd, "massu.config.yaml");
23054
+ const configPath = resolve22(cwd, "massu.config.yaml");
22802
23055
  const log = opts.silent ? () => {
22803
23056
  } : (s) => process.stdout.write(s);
22804
- if (!existsSync26(configPath)) {
23057
+ if (!existsSync27(configPath)) {
22805
23058
  const message = "massu.config.yaml not found. Run: npx massu init";
22806
23059
  if (!opts.silent) process.stderr.write(message + "\n");
22807
23060
  return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
22808
23061
  }
22809
23062
  let existing;
22810
23063
  try {
22811
- const content = readFileSync26(configPath, "utf-8");
23064
+ const content = readFileSync27(configPath, "utf-8");
22812
23065
  const parsed = parseYaml5(content);
22813
23066
  if (!parsed || typeof parsed !== "object") {
22814
23067
  throw new Error("config is not a YAML object");
@@ -23138,19 +23391,19 @@ var config_upgrade_exports = {};
23138
23391
  __export(config_upgrade_exports, {
23139
23392
  runConfigUpgrade: () => runConfigUpgrade
23140
23393
  });
23141
- import { existsSync as existsSync27, readFileSync as readFileSync27, writeFileSync as writeFileSync4, copyFileSync, unlinkSync } from "fs";
23142
- 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";
23143
23396
  import { parse as parseYaml6 } from "yaml";
23144
23397
  async function runConfigUpgrade(opts = {}) {
23145
23398
  const cwd = opts.cwd ?? process.cwd();
23146
- const configPath = resolve22(cwd, "massu.config.yaml");
23399
+ const configPath = resolve23(cwd, "massu.config.yaml");
23147
23400
  const bakPath = `${configPath}.bak`;
23148
23401
  const log = opts.silent ? () => {
23149
23402
  } : (s) => process.stdout.write(s);
23150
23403
  const err = opts.silent ? () => {
23151
23404
  } : (s) => process.stderr.write(s);
23152
23405
  if (opts.rollback) {
23153
- if (!existsSync27(bakPath)) {
23406
+ if (!existsSync28(bakPath)) {
23154
23407
  const message = `No backup found at ${bakPath}`;
23155
23408
  err(message + "\n");
23156
23409
  return { exitCode: 1, action: "none", message };
@@ -23166,14 +23419,14 @@ async function runConfigUpgrade(opts = {}) {
23166
23419
  return { exitCode: 2, action: "none", message };
23167
23420
  }
23168
23421
  }
23169
- if (!existsSync27(configPath)) {
23422
+ if (!existsSync28(configPath)) {
23170
23423
  const message = "massu.config.yaml not found. Run: npx massu init";
23171
23424
  err(message + "\n");
23172
23425
  return { exitCode: 1, action: "none", message };
23173
23426
  }
23174
23427
  let existing;
23175
23428
  try {
23176
- const content = readFileSync27(configPath, "utf-8");
23429
+ const content = readFileSync28(configPath, "utf-8");
23177
23430
  const parsed = parseYaml6(content);
23178
23431
  if (!parsed || typeof parsed !== "object") {
23179
23432
  throw new Error("config is not a YAML object");
@@ -23196,7 +23449,7 @@ async function runConfigUpgrade(opts = {}) {
23196
23449
  fingerprint: computeFingerprint(detection)
23197
23450
  };
23198
23451
  try {
23199
- const original = readFileSync27(configPath, "utf-8");
23452
+ const original = readFileSync28(configPath, "utf-8");
23200
23453
  writeFileSync4(bakPath, original, "utf-8");
23201
23454
  } catch (e2) {
23202
23455
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
@@ -23230,8 +23483,8 @@ var config_check_drift_exports = {};
23230
23483
  __export(config_check_drift_exports, {
23231
23484
  runConfigCheckDrift: () => runConfigCheckDrift
23232
23485
  });
23233
- import { existsSync as existsSync28, readFileSync as readFileSync28 } from "fs";
23234
- import { resolve as resolve23 } from "path";
23486
+ import { existsSync as existsSync29, readFileSync as readFileSync29 } from "fs";
23487
+ import { resolve as resolve24 } from "path";
23235
23488
  import { parse as parseYaml7 } from "yaml";
23236
23489
  function renderChanges(changes) {
23237
23490
  if (changes.length === 0) return "(none)\n";
@@ -23239,12 +23492,12 @@ function renderChanges(changes) {
23239
23492
  }
23240
23493
  async function runConfigCheckDrift(opts = {}) {
23241
23494
  const cwd = opts.cwd ?? process.cwd();
23242
- const configPath = resolve23(cwd, "massu.config.yaml");
23495
+ const configPath = resolve24(cwd, "massu.config.yaml");
23243
23496
  const log = opts.silent ? () => {
23244
23497
  } : (s) => process.stdout.write(s);
23245
23498
  const err = opts.silent ? () => {
23246
23499
  } : (s) => process.stderr.write(s);
23247
- if (!existsSync28(configPath)) {
23500
+ if (!existsSync29(configPath)) {
23248
23501
  const message = "massu.config.yaml not found. Run: npx massu init";
23249
23502
  err(message + "\n");
23250
23503
  return {
@@ -23258,7 +23511,7 @@ async function runConfigCheckDrift(opts = {}) {
23258
23511
  }
23259
23512
  let config;
23260
23513
  try {
23261
- const content = readFileSync28(configPath, "utf-8");
23514
+ const content = readFileSync29(configPath, "utf-8");
23262
23515
  const parsed = parseYaml7(content);
23263
23516
  if (!parsed || typeof parsed !== "object") {
23264
23517
  throw new Error("config is not a YAML object");
@@ -23325,8 +23578,8 @@ var init_config_check_drift = __esm({
23325
23578
  });
23326
23579
 
23327
23580
  // src/cli.ts
23328
- import { readFileSync as readFileSync29 } from "fs";
23329
- 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";
23330
23583
  import { fileURLToPath as fileURLToPath5 } from "url";
23331
23584
  var __filename4 = fileURLToPath5(import.meta.url);
23332
23585
  var __dirname5 = dirname12(__filename4);
@@ -23354,6 +23607,11 @@ async function main() {
23354
23607
  await runInstallCommands2();
23355
23608
  break;
23356
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
+ }
23357
23615
  case "validate-config": {
23358
23616
  const { runValidateConfig: runValidateConfig2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
23359
23617
  await runValidateConfig2();
@@ -23440,6 +23698,7 @@ Commands:
23440
23698
  doctor Check installation health
23441
23699
  install-hooks Install/update Claude Code hooks
23442
23700
  install-commands Install/update slash commands
23701
+ show-template Print the resolved variant of a bundled template (e.g. for diffs)
23443
23702
  validate-config Validate massu.config.yaml (alias: config validate)
23444
23703
  config <sub> Config lifecycle: refresh | validate | upgrade | doctor | check-drift
23445
23704
 
@@ -23478,7 +23737,7 @@ Examples:
23478
23737
  }
23479
23738
  function printVersion() {
23480
23739
  try {
23481
- const pkg = JSON.parse(readFileSync29(resolve24(__dirname5, "../package.json"), "utf-8"));
23740
+ const pkg = JSON.parse(readFileSync30(resolve25(__dirname5, "../package.json"), "utf-8"));
23482
23741
  console.log(`massu v${pkg.version}`);
23483
23742
  } catch {
23484
23743
  console.log("massu v0.1.0");