@massu/core 1.5.3 → 1.5.5

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
@@ -1673,16 +1673,16 @@ import { parse as parseYaml2 } from "yaml";
1673
1673
  function ingestMemoryFile(db, sessionId, filePath) {
1674
1674
  if (!existsSync3(filePath)) return "skipped";
1675
1675
  const content = readFileSync2(filePath, "utf-8");
1676
- const basename10 = (filePath.split("/").pop() ?? "").replace(".md", "");
1676
+ const basename11 = (filePath.split("/").pop() ?? "").replace(".md", "");
1677
1677
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
1678
- let name2 = basename10;
1678
+ let name2 = basename11;
1679
1679
  let description = "";
1680
1680
  let type = "discovery";
1681
1681
  let confidence;
1682
1682
  if (frontmatterMatch) {
1683
1683
  try {
1684
1684
  const fm = parseYaml2(frontmatterMatch[1]);
1685
- name2 = fm.name ?? basename10;
1685
+ name2 = fm.name ?? basename11;
1686
1686
  description = fm.description ?? "";
1687
1687
  type = fm.type ?? "discovery";
1688
1688
  confidence = fm.confidence != null ? Number(fm.confidence) : void 0;
@@ -7374,8 +7374,8 @@ var require_pattern = __commonJS({
7374
7374
  }
7375
7375
  exports.endsWithSlashGlobStar = endsWithSlashGlobStar;
7376
7376
  function isAffectDepthOfReadingPattern(pattern) {
7377
- const basename10 = path.basename(pattern);
7378
- return endsWithSlashGlobStar(pattern) || isStaticPattern(basename10);
7377
+ const basename11 = path.basename(pattern);
7378
+ return endsWithSlashGlobStar(pattern) || isStaticPattern(basename11);
7379
7379
  }
7380
7380
  exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
7381
7381
  function expandPatternsWithBraceExpansion(patterns) {
@@ -7420,15 +7420,15 @@ var require_pattern = __commonJS({
7420
7420
  exports.removeDuplicateSlashes = removeDuplicateSlashes;
7421
7421
  function partitionAbsoluteAndRelative(patterns) {
7422
7422
  const absolute = [];
7423
- const relative12 = [];
7423
+ const relative13 = [];
7424
7424
  for (const pattern of patterns) {
7425
7425
  if (isAbsolute3(pattern)) {
7426
7426
  absolute.push(pattern);
7427
7427
  } else {
7428
- relative12.push(pattern);
7428
+ relative13.push(pattern);
7429
7429
  }
7430
7430
  }
7431
- return [absolute, relative12];
7431
+ return [absolute, relative13];
7432
7432
  }
7433
7433
  exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
7434
7434
  function isAbsolute3(pattern) {
@@ -7849,11 +7849,11 @@ var require_out = __commonJS({
7849
7849
  async.read(path, getSettings(optionsOrSettingsOrCallback), callback);
7850
7850
  }
7851
7851
  exports.stat = stat;
7852
- function statSync13(path, optionsOrSettings) {
7852
+ function statSync15(path, optionsOrSettings) {
7853
7853
  const settings = getSettings(optionsOrSettings);
7854
7854
  return sync.read(path, settings);
7855
7855
  }
7856
- exports.statSync = statSync13;
7856
+ exports.statSync = statSync15;
7857
7857
  function getSettings(settingsOrOptions = {}) {
7858
7858
  if (settingsOrOptions instanceof settings_1.default) {
7859
7859
  return settingsOrOptions;
@@ -10623,15 +10623,204 @@ var init_regex_fallback = __esm({
10623
10623
  });
10624
10624
 
10625
10625
  // src/detect/adapters/parse-guard.ts
10626
- var MAX_AST_FILE_BYTES;
10626
+ function isParsableSource(source, sizeBytes) {
10627
+ const bytes = sizeBytes ?? Buffer.byteLength(source, "utf-8");
10628
+ if (bytes > MAX_AST_FILE_BYTES) {
10629
+ return {
10630
+ reason: "size-cap",
10631
+ detail: `${bytes} bytes > ${MAX_AST_FILE_BYTES} cap`
10632
+ };
10633
+ }
10634
+ let depth = 0;
10635
+ let maxDepth = 0;
10636
+ for (let i2 = 0; i2 < source.length; i2++) {
10637
+ const c2 = source.charCodeAt(i2);
10638
+ if (c2 === 0) {
10639
+ return { reason: "control-bytes", detail: "NUL byte at offset " + i2 };
10640
+ }
10641
+ if (c2 === 40 || c2 === 91 || c2 === 123) {
10642
+ depth++;
10643
+ if (depth > maxDepth) maxDepth = depth;
10644
+ if (depth > MAX_AST_PARSE_DEPTH) {
10645
+ return {
10646
+ reason: "depth-cap",
10647
+ detail: `nesting depth exceeded ${MAX_AST_PARSE_DEPTH}`
10648
+ };
10649
+ }
10650
+ } else if (c2 === 41 || c2 === 93 || c2 === 125) {
10651
+ depth = depth > 0 ? depth - 1 : 0;
10652
+ }
10653
+ }
10654
+ return null;
10655
+ }
10656
+ var MAX_AST_FILE_BYTES, MAX_AST_PARSE_DEPTH;
10627
10657
  var init_parse_guard = __esm({
10628
10658
  "src/detect/adapters/parse-guard.ts"() {
10629
10659
  "use strict";
10630
10660
  MAX_AST_FILE_BYTES = 1 * 1024 * 1024;
10661
+ MAX_AST_PARSE_DEPTH = 5e3;
10631
10662
  }
10632
10663
  });
10633
10664
 
10634
10665
  // src/detect/adapters/runner.ts
10666
+ import { basename as basename3, relative as relative4 } from "path";
10667
+ import { existsSync as existsSync9, readdirSync as readdirSync7, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
10668
+ import { join as join7 } from "path";
10669
+ async function runAdapters(adapters, rootDir, signals, options = {}) {
10670
+ const out2 = {
10671
+ byAdapter: {},
10672
+ skipped: [],
10673
+ errored: []
10674
+ };
10675
+ for (const adapter of adapters) {
10676
+ if (out2.byAdapter[adapter.id] || out2.skipped.includes(adapter.id)) {
10677
+ continue;
10678
+ }
10679
+ let matches;
10680
+ try {
10681
+ matches = adapter.matches(signals);
10682
+ } catch (e2) {
10683
+ out2.errored.push({
10684
+ adapterId: adapter.id,
10685
+ error: `matches() threw: ${e2 instanceof Error ? e2.message : String(e2)}`
10686
+ });
10687
+ continue;
10688
+ }
10689
+ if (!matches) {
10690
+ out2.skipped.push(adapter.id);
10691
+ continue;
10692
+ }
10693
+ let files;
10694
+ try {
10695
+ files = options.sampleFiles ? await options.sampleFiles(adapter, rootDir) : [];
10696
+ } catch (e2) {
10697
+ out2.errored.push({
10698
+ adapterId: adapter.id,
10699
+ error: `sampleFiles threw: ${e2 instanceof Error ? e2.message : String(e2)}`
10700
+ });
10701
+ continue;
10702
+ }
10703
+ const safeFiles = [];
10704
+ for (const f2 of files) {
10705
+ const skip = isParsableSource(f2.content, f2.size);
10706
+ if (skip) {
10707
+ process.stderr.write(
10708
+ `[massu/ast] WARN: skipping ${f2.path} for adapter ${adapter.id}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES} bytes. (Phase 3.5 mitigation)
10709
+ `
10710
+ );
10711
+ continue;
10712
+ }
10713
+ safeFiles.push(f2);
10714
+ }
10715
+ files = safeFiles;
10716
+ let result;
10717
+ try {
10718
+ result = await adapter.introspect(files, rootDir);
10719
+ } catch (e2) {
10720
+ out2.errored.push({
10721
+ adapterId: adapter.id,
10722
+ error: `introspect() threw: ${e2 instanceof Error ? e2.message : String(e2)}`
10723
+ });
10724
+ continue;
10725
+ }
10726
+ if (result.confidence === "none") {
10727
+ out2.byAdapter[adapter.id] = {
10728
+ conventions: {},
10729
+ _provenance: {},
10730
+ confidence: "none"
10731
+ };
10732
+ continue;
10733
+ }
10734
+ const conventions = {};
10735
+ const provenanceMap = {};
10736
+ for (const [field, value] of Object.entries(result.conventions)) {
10737
+ if (value === null || value === void 0) continue;
10738
+ if (field in conventions) continue;
10739
+ conventions[field] = value;
10740
+ }
10741
+ for (const p19 of result.provenance) {
10742
+ if (p19.field in provenanceMap) continue;
10743
+ provenanceMap[p19.field] = formatProvenance(p19, rootDir);
10744
+ }
10745
+ const resolved = {
10746
+ conventions,
10747
+ _provenance: provenanceMap,
10748
+ confidence: result.confidence
10749
+ };
10750
+ out2.byAdapter[adapter.id] = resolved;
10751
+ }
10752
+ return out2;
10753
+ }
10754
+ function formatProvenance(p19, rootDir) {
10755
+ const rel = p19.sourceFile.startsWith(rootDir + "/") ? relative4(rootDir, p19.sourceFile) : basename3(p19.sourceFile);
10756
+ return `${rel}:${p19.line} :: ${p19.query}`;
10757
+ }
10758
+ function buildDetectionSignals(rootDir) {
10759
+ const presentDirs = /* @__PURE__ */ new Set();
10760
+ const presentFiles = /* @__PURE__ */ new Set();
10761
+ try {
10762
+ for (const entry of readdirSync7(rootDir)) {
10763
+ if (entry.startsWith(".")) continue;
10764
+ try {
10765
+ const st = statSync5(join7(rootDir, entry));
10766
+ if (st.isDirectory()) presentDirs.add(entry);
10767
+ else if (st.isFile()) presentFiles.add(entry);
10768
+ } catch {
10769
+ }
10770
+ }
10771
+ } catch {
10772
+ }
10773
+ return {
10774
+ packageJson: tryReadJson(join7(rootDir, "package.json")),
10775
+ pyprojectToml: tryReadToml(join7(rootDir, "pyproject.toml")),
10776
+ gemfile: tryReadString(join7(rootDir, "Gemfile")),
10777
+ cargoToml: tryReadToml(join7(rootDir, "Cargo.toml")),
10778
+ goMod: tryReadString(join7(rootDir, "go.mod")),
10779
+ mixExs: tryReadString(join7(rootDir, "mix.exs")),
10780
+ csproj: tryReadFirstCsproj(rootDir, presentFiles),
10781
+ pomXml: tryReadString(join7(rootDir, "pom.xml")),
10782
+ gradleBuild: tryReadGradleBuild(rootDir, presentFiles),
10783
+ presentDirs,
10784
+ presentFiles
10785
+ };
10786
+ }
10787
+ function tryReadFirstCsproj(rootDir, presentFiles) {
10788
+ const csprojNames = [...presentFiles].filter((f2) => f2.endsWith(".csproj")).sort();
10789
+ if (csprojNames.length === 0) return void 0;
10790
+ return tryReadString(join7(rootDir, csprojNames[0]));
10791
+ }
10792
+ function tryReadGradleBuild(rootDir, presentFiles) {
10793
+ if (presentFiles.has("build.gradle.kts")) {
10794
+ return tryReadString(join7(rootDir, "build.gradle.kts"));
10795
+ }
10796
+ if (presentFiles.has("build.gradle")) {
10797
+ return tryReadString(join7(rootDir, "build.gradle"));
10798
+ }
10799
+ return void 0;
10800
+ }
10801
+ function tryReadString(path) {
10802
+ if (!existsSync9(path)) return void 0;
10803
+ try {
10804
+ return readFileSync7(path, "utf-8");
10805
+ } catch {
10806
+ return void 0;
10807
+ }
10808
+ }
10809
+ function tryReadJson(path) {
10810
+ const txt = tryReadString(path);
10811
+ if (!txt) return void 0;
10812
+ try {
10813
+ const parsed = JSON.parse(txt);
10814
+ return typeof parsed === "object" && parsed !== null ? parsed : void 0;
10815
+ } catch {
10816
+ return void 0;
10817
+ }
10818
+ }
10819
+ function tryReadToml(path) {
10820
+ const txt = tryReadString(path);
10821
+ if (!txt) return void 0;
10822
+ return { __raw: txt };
10823
+ }
10635
10824
  var init_runner = __esm({
10636
10825
  "src/detect/adapters/runner.ts"() {
10637
10826
  "use strict";
@@ -14588,22 +14777,408 @@ ${JSON.stringify(symbolNames, null, 2)}`);
14588
14777
  });
14589
14778
 
14590
14779
  // src/detect/adapters/query-helpers.ts
14780
+ function compileQuery(language, source, queryName) {
14781
+ let perLang = queryCache.get(language);
14782
+ if (!perLang) {
14783
+ perLang = /* @__PURE__ */ new Map();
14784
+ queryCache.set(language, perLang);
14785
+ }
14786
+ const cached = perLang.get(source);
14787
+ if (cached) return cached;
14788
+ let q2;
14789
+ try {
14790
+ q2 = new Query(language, source);
14791
+ } catch (e2) {
14792
+ throw new InvalidQueryError(queryName, source, e2);
14793
+ }
14794
+ perLang.set(source, q2);
14795
+ return q2;
14796
+ }
14797
+ function runQuery(parser, source, queryText, queryName, filePath) {
14798
+ const language = parser.language;
14799
+ if (!language) {
14800
+ throw new InvalidQueryError(
14801
+ queryName,
14802
+ queryText,
14803
+ new Error("Parser has no language assigned")
14804
+ );
14805
+ }
14806
+ const query = compileQuery(language, queryText, queryName);
14807
+ const tree = parser.parse(source);
14808
+ if (!tree) return [];
14809
+ let matches;
14810
+ try {
14811
+ matches = query.matches(tree.rootNode);
14812
+ } catch (e2) {
14813
+ throw new InvalidQueryError(queryName, queryText, e2);
14814
+ }
14815
+ const out2 = [];
14816
+ for (const match of matches) {
14817
+ if (!match.captures || match.captures.length === 0) continue;
14818
+ const captures = {};
14819
+ let earliestLine = Number.POSITIVE_INFINITY;
14820
+ for (const cap of match.captures) {
14821
+ const node = cap.node;
14822
+ captures[cap.name] = node.text;
14823
+ if (node.startPosition.row + 1 < earliestLine) {
14824
+ earliestLine = node.startPosition.row + 1;
14825
+ }
14826
+ }
14827
+ out2.push({
14828
+ captures,
14829
+ file: filePath,
14830
+ line: Number.isFinite(earliestLine) ? earliestLine : 1,
14831
+ queryName
14832
+ });
14833
+ }
14834
+ try {
14835
+ tree.delete();
14836
+ } catch {
14837
+ }
14838
+ return out2;
14839
+ }
14840
+ var InvalidQueryError, queryCache;
14591
14841
  var init_query_helpers = __esm({
14592
14842
  "src/detect/adapters/query-helpers.ts"() {
14593
14843
  "use strict";
14594
14844
  init_tree_sitter();
14845
+ InvalidQueryError = class extends Error {
14846
+ queryName;
14847
+ querySource;
14848
+ cause;
14849
+ constructor(queryName, querySource, cause) {
14850
+ const causeMsg = cause instanceof Error ? cause.message : String(cause);
14851
+ super(
14852
+ `[query-helpers] Invalid Tree-sitter query "${queryName}": ${causeMsg}
14853
+ Query source:
14854
+ ${querySource}`
14855
+ );
14856
+ this.name = "InvalidQueryError";
14857
+ this.queryName = queryName;
14858
+ this.querySource = querySource;
14859
+ this.cause = cause;
14860
+ }
14861
+ };
14862
+ queryCache = /* @__PURE__ */ new WeakMap();
14595
14863
  }
14596
14864
  });
14597
14865
 
14598
14866
  // src/detect/adapters/tree-sitter-loader.ts
14867
+ import { createHash as createHash2 } from "crypto";
14868
+ import {
14869
+ mkdirSync as mkdirSync3,
14870
+ readdirSync as readdirSync8,
14871
+ readFileSync as readFileSync8,
14872
+ writeFileSync,
14873
+ renameSync as renameSync2,
14874
+ unlinkSync,
14875
+ lstatSync as lstatSync3,
14876
+ chmodSync,
14877
+ utimesSync
14878
+ } from "fs";
14879
+ import { homedir as homedir2 } from "os";
14880
+ import { dirname as dirname4, join as join8 } from "path";
14881
+ function getCacheDir() {
14882
+ return process.env.MASSU_WASM_CACHE_DIR ?? join8(homedir2(), ".massu", "wasm-cache");
14883
+ }
14884
+ function getCachedPath(language, sha) {
14885
+ return join8(getCacheDir(), `${language}-${sha}.wasm`);
14886
+ }
14887
+ function getCacheRetainCount() {
14888
+ const env = process.env.MASSU_WASM_CACHE_RETAIN;
14889
+ if (env) {
14890
+ const n = Number(env);
14891
+ if (Number.isFinite(n) && n >= 1 && n <= 1024) return Math.floor(n);
14892
+ }
14893
+ return DEFAULT_CACHE_RETAIN_COUNT;
14894
+ }
14895
+ function touchCacheFile(path) {
14896
+ try {
14897
+ const now = /* @__PURE__ */ new Date();
14898
+ utimesSync(path, now, now);
14899
+ } catch {
14900
+ }
14901
+ }
14902
+ function evictBeyondRetainCount(retain = getCacheRetainCount()) {
14903
+ const dir = getCacheDir();
14904
+ let entries;
14905
+ try {
14906
+ entries = readdirSync8(dir);
14907
+ } catch {
14908
+ return;
14909
+ }
14910
+ const candidates = [];
14911
+ for (const name2 of entries) {
14912
+ if (!name2.endsWith(".wasm")) continue;
14913
+ const path = join8(dir, name2);
14914
+ let stat;
14915
+ try {
14916
+ stat = lstatSync3(path);
14917
+ } catch {
14918
+ continue;
14919
+ }
14920
+ if (stat.isSymbolicLink() || !stat.isFile()) {
14921
+ console.error(
14922
+ `[tree-sitter-loader] cache eviction skipped non-regular file: ${path} (possible symlink attack \u2014 see Phase 3.5 finding F-008).`
14923
+ );
14924
+ continue;
14925
+ }
14926
+ candidates.push({ path, mtimeMs: stat.mtimeMs });
14927
+ }
14928
+ if (candidates.length <= retain) return;
14929
+ candidates.sort((a2, b2) => b2.mtimeMs - a2.mtimeMs);
14930
+ for (const victim of candidates.slice(retain)) {
14931
+ try {
14932
+ unlinkSync(victim.path);
14933
+ } catch {
14934
+ }
14935
+ }
14936
+ }
14937
+ function sha256(bytes) {
14938
+ return createHash2("sha256").update(bytes).digest("hex");
14939
+ }
14940
+ async function ensureParserInitialized() {
14941
+ if (parserInitPromise) return parserInitPromise;
14942
+ parserInitPromise = Parser.init();
14943
+ return parserInitPromise;
14944
+ }
14945
+ async function loadGrammar(language, options = {}) {
14946
+ await ensureParserInitialized();
14947
+ const cached = loadedGrammars.get(language);
14948
+ if (cached) return cached;
14949
+ const manifest = options.manifestOverride?.[language] ?? GRAMMAR_MANIFEST[language];
14950
+ if (!manifest) {
14951
+ throw new GrammarUnavailableError(
14952
+ language,
14953
+ new Error(`No manifest entry for language "${language}". v1 supports: ${Object.keys(GRAMMAR_MANIFEST).join(", ")}.`)
14954
+ );
14955
+ }
14956
+ const cachePath = getCachedPath(language, manifest.sha256);
14957
+ let cacheLstat;
14958
+ try {
14959
+ cacheLstat = lstatSync3(cachePath);
14960
+ } catch {
14961
+ cacheLstat = null;
14962
+ }
14963
+ if (cacheLstat) {
14964
+ if (cacheLstat.isSymbolicLink() || !cacheLstat.isFile()) {
14965
+ throw new GrammarCacheSymlinkError(cachePath);
14966
+ }
14967
+ let bytes;
14968
+ try {
14969
+ bytes = readFileSync8(cachePath);
14970
+ } catch (e2) {
14971
+ bytes = new Uint8Array(0);
14972
+ }
14973
+ if (bytes.byteLength > 0) {
14974
+ const actualSha = sha256(bytes);
14975
+ if (actualSha !== manifest.sha256) {
14976
+ throw new GrammarSHAMismatchError(language, manifest.sha256, actualSha);
14977
+ }
14978
+ const lang2 = await Language.load(bytes);
14979
+ loadedGrammars.set(language, lang2);
14980
+ touchCacheFile(cachePath);
14981
+ return lang2;
14982
+ }
14983
+ }
14984
+ if (!/^https:\/\//i.test(manifest.url)) {
14985
+ throw new GrammarUrlNotHttpsError(manifest.url);
14986
+ }
14987
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
14988
+ if (!fetchImpl) {
14989
+ throw new GrammarUnavailableError(
14990
+ language,
14991
+ new Error("No fetch implementation available (Node < 18?)")
14992
+ );
14993
+ }
14994
+ let body2;
14995
+ try {
14996
+ const res = await fetchImpl(manifest.url);
14997
+ if (!res.ok) {
14998
+ throw new Error(`HTTP ${res.status ?? "unknown"} from ${manifest.url}`);
14999
+ }
15000
+ body2 = new Uint8Array(await res.arrayBuffer());
15001
+ } catch (e2) {
15002
+ throw new GrammarUnavailableError(language, e2);
15003
+ }
15004
+ const downloadedSha = sha256(body2);
15005
+ if (downloadedSha !== manifest.sha256) {
15006
+ throw new GrammarSHAMismatchError(language, manifest.sha256, downloadedSha);
15007
+ }
15008
+ try {
15009
+ mkdirSync3(dirname4(cachePath), { recursive: true, mode: 448 });
15010
+ try {
15011
+ chmodSync(dirname4(cachePath), 448);
15012
+ } catch {
15013
+ }
15014
+ const tmpPath = `${cachePath}.tmp.${process.pid}`;
15015
+ writeFileSync(tmpPath, body2, { mode: 384 });
15016
+ try {
15017
+ chmodSync(tmpPath, 384);
15018
+ } catch {
15019
+ }
15020
+ try {
15021
+ renameSync2(tmpPath, cachePath);
15022
+ try {
15023
+ chmodSync(cachePath, 384);
15024
+ } catch {
15025
+ }
15026
+ } catch (e2) {
15027
+ try {
15028
+ unlinkSync(tmpPath);
15029
+ } catch {
15030
+ }
15031
+ throw e2;
15032
+ }
15033
+ evictBeyondRetainCount();
15034
+ } catch (e2) {
15035
+ console.error(
15036
+ `[tree-sitter-loader] cache write failed for ${language}: ${e2 instanceof Error ? e2.message : String(e2)} \u2014 loading directly from memory.`
15037
+ );
15038
+ }
15039
+ const lang = await Language.load(body2);
15040
+ loadedGrammars.set(language, lang);
15041
+ return lang;
15042
+ }
15043
+ var GrammarSHAMismatchError, GrammarUnavailableError, GrammarCacheSymlinkError, GrammarUrlNotHttpsError, GRAMMAR_MANIFEST, DEFAULT_CACHE_RETAIN_COUNT, parserInitPromise, loadedGrammars;
14599
15044
  var init_tree_sitter_loader = __esm({
14600
15045
  "src/detect/adapters/tree-sitter-loader.ts"() {
14601
15046
  "use strict";
14602
15047
  init_tree_sitter();
15048
+ GrammarSHAMismatchError = class extends Error {
15049
+ language;
15050
+ expected;
15051
+ actual;
15052
+ constructor(language, expected, actual) {
15053
+ super(
15054
+ `[tree-sitter-loader] SHA-256 mismatch for grammar "${language}". Expected ${expected}, got ${actual}. REFUSING to load \u2014 see Phase 3.5 audit attack vector #3.`
15055
+ );
15056
+ this.name = "GrammarSHAMismatchError";
15057
+ this.language = language;
15058
+ this.expected = expected;
15059
+ this.actual = actual;
15060
+ }
15061
+ };
15062
+ GrammarUnavailableError = class extends Error {
15063
+ language;
15064
+ cause;
15065
+ constructor(language, cause) {
15066
+ const causeMsg = cause instanceof Error ? cause.message : cause ? String(cause) : "no cached grammar and download failed";
15067
+ super(
15068
+ `[tree-sitter-loader] Grammar for "${language}" is unavailable: ${causeMsg}. Falling back to regex introspection for files in ${language}.`
15069
+ );
15070
+ this.name = "GrammarUnavailableError";
15071
+ this.language = language;
15072
+ this.cause = cause;
15073
+ }
15074
+ };
15075
+ GrammarCacheSymlinkError = class extends Error {
15076
+ cachePath;
15077
+ constructor(cachePath) {
15078
+ super(
15079
+ `[tree-sitter-loader] Refusing to load grammar \u2014 cache path "${cachePath}" is a symlink or non-regular file. (Phase 3.5 finding #3 \u2014 symlink attack vector.)`
15080
+ );
15081
+ this.name = "GrammarCacheSymlinkError";
15082
+ this.cachePath = cachePath;
15083
+ }
15084
+ };
15085
+ GrammarUrlNotHttpsError = class extends Error {
15086
+ url;
15087
+ constructor(url) {
15088
+ super(
15089
+ `[tree-sitter-loader] Refusing to download grammar from non-HTTPS URL: ${url}. Only https:// URLs are accepted. (Phase 3.5 finding #3.)`
15090
+ );
15091
+ this.name = "GrammarUrlNotHttpsError";
15092
+ this.url = url;
15093
+ }
15094
+ };
15095
+ GRAMMAR_MANIFEST = {
15096
+ python: {
15097
+ url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-python.wasm",
15098
+ sha256: "9056d0fb0c337810d019fae350e8167786119da98f0f282aceae7ab89ee8253b",
15099
+ version: "0.1.13"
15100
+ },
15101
+ typescript: {
15102
+ url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-typescript.wasm",
15103
+ sha256: "8515404dceed38e1ed86aa34b09fcf3379fff1b4ff9dd3967bcd6d1eb5ac3d8f",
15104
+ version: "0.1.13"
15105
+ },
15106
+ javascript: {
15107
+ url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-javascript.wasm",
15108
+ sha256: "63812b9e275d26851264734868d27a1656bd44a2ef6eb3e85e6b03728c595ab5",
15109
+ version: "0.1.13"
15110
+ },
15111
+ swift: {
15112
+ url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-swift.wasm",
15113
+ sha256: "41c4fdb2249a3aa6d87eed0d383081ff09725c2248b4977043a43825980ffcc7",
15114
+ version: "0.1.13"
15115
+ },
15116
+ // ----------------------------------------------------------------
15117
+ // Plan 3c Phase 7 expansion (2026-05-07):
15118
+ //
15119
+ // Six additional grammars to support the registry-verified framework
15120
+ // adapters (go-chi, rails, aspnet, spring, ktor, phoenix) plus the
15121
+ // bundled adapters in the same language families (gin/echo/fiber,
15122
+ // sinatra, etc.). All entries use the SAME pinned tree-sitter-wasms
15123
+ // version (0.1.13) as the v1 four to keep the dependency surface
15124
+ // single-source.
15125
+ //
15126
+ // SHA-256s computed 2026-05-07 via:
15127
+ // curl -fsSL <url> | shasum -a 256
15128
+ //
15129
+ // The unpkg filename for C# uses an underscore (`c_sharp`) while the
15130
+ // TreeSitterLanguage identifier uses no separator (`csharp`); the map
15131
+ // key is the type identifier, the URL is the storage path — they do
15132
+ // NOT need to match, the same as how `python` maps to `tree-sitter-
15133
+ // python.wasm`. This is intentional and validated by the manifest
15134
+ // shape test in tree-sitter-loader-manifest.test.ts.
15135
+ // ----------------------------------------------------------------
15136
+ go: {
15137
+ url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-go.wasm",
15138
+ sha256: "9963ca89b616eaf04b08a43bc1fb0f07b85395bec313330851f1f1ead2f755b6",
15139
+ version: "0.1.13"
15140
+ },
15141
+ ruby: {
15142
+ url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-ruby.wasm",
15143
+ sha256: "93a5022855314cdb45458c7bb026a24a0ebc3a5ff6439e542e881f14dfa13a39",
15144
+ version: "0.1.13"
15145
+ },
15146
+ csharp: {
15147
+ url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-c_sharp.wasm",
15148
+ sha256: "6266a7e32d68a3459104d994dc848df15d5672b0ea8e86d327274b694f8e6991",
15149
+ version: "0.1.13"
15150
+ },
15151
+ java: {
15152
+ url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-java.wasm",
15153
+ sha256: "637aac4415fb39a211a4f4292d63c66b5ce9c32fa2cd35464af4f681d91b9a1f",
15154
+ version: "0.1.13"
15155
+ },
15156
+ kotlin: {
15157
+ url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-kotlin.wasm",
15158
+ sha256: "b5cb00c8d06ed0f10f1dbe497205b437809d7e87db1f638721a8cfb30e044449",
15159
+ version: "0.1.13"
15160
+ },
15161
+ elixir: {
15162
+ url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-elixir.wasm",
15163
+ sha256: "82e91b9759ddca30d8978ebbfa8e347b4451b64c931f9ae62112e6db9b8fac20",
15164
+ version: "0.1.13"
15165
+ }
15166
+ };
15167
+ DEFAULT_CACHE_RETAIN_COUNT = 16;
15168
+ parserInitPromise = null;
15169
+ loadedGrammars = /* @__PURE__ */ new Map();
14603
15170
  }
14604
15171
  });
14605
15172
 
14606
15173
  // src/detect/adapters/python-fastapi.ts
15174
+ function extractPrefixBase2(prefix3) {
15175
+ if (!prefix3.startsWith("/")) return null;
15176
+ const stripped = prefix3.replace(/^\/+/, "");
15177
+ const firstSeg = stripped.split("/")[0];
15178
+ if (!firstSeg) return null;
15179
+ return "/" + firstSeg;
15180
+ }
15181
+ var AUTH_DEP_QUERY, API_PREFIX_QUERY, PYTEST_ASYNCIO_QUERY, pythonFastApiAdapter;
14607
15182
  var init_python_fastapi = __esm({
14608
15183
  "src/detect/adapters/python-fastapi.ts"() {
14609
15184
  "use strict";
@@ -14611,43 +15186,1621 @@ var init_python_fastapi = __esm({
14611
15186
  init_query_helpers();
14612
15187
  init_tree_sitter_loader();
14613
15188
  init_parse_guard();
15189
+ AUTH_DEP_QUERY = `
15190
+ (call
15191
+ function: (identifier) @_callee (#eq? @_callee "Depends")
15192
+ arguments: (argument_list
15193
+ (identifier) @auth_dep))
15194
+ `;
15195
+ API_PREFIX_QUERY = `
15196
+ (call
15197
+ function: (identifier) @_callee (#eq? @_callee "APIRouter")
15198
+ arguments: (argument_list
15199
+ (keyword_argument
15200
+ name: (identifier) @_kw (#eq? @_kw "prefix")
15201
+ value: (string) @prefix_value)))
15202
+ `;
15203
+ PYTEST_ASYNCIO_QUERY = `
15204
+ (decorator
15205
+ (attribute
15206
+ object: (attribute
15207
+ object: (identifier) @_pkg (#eq? @_pkg "pytest")
15208
+ attribute: (identifier) @_mark (#eq? @_mark "mark"))
15209
+ attribute: (identifier) @_marker (#eq? @_marker "asyncio"))) @decorator
15210
+ `;
15211
+ pythonFastApiAdapter = {
15212
+ id: "python-fastapi",
15213
+ languages: ["python"],
15214
+ matches(signals) {
15215
+ const pyToml = signals.pyprojectToml;
15216
+ if (pyToml?.__raw && /\bfastapi\b/i.test(pyToml.__raw)) return true;
15217
+ if (signals.presentDirs.has("routers")) return true;
15218
+ if (signals.presentDirs.has("app") && signals.presentFiles.has("main.py")) return true;
15219
+ return false;
15220
+ },
15221
+ async introspect(files, _rootDir) {
15222
+ if (files.length === 0) {
15223
+ return { conventions: {}, provenance: [], confidence: "none" };
15224
+ }
15225
+ let language;
15226
+ try {
15227
+ language = await loadGrammar("python");
15228
+ } catch (e2) {
15229
+ return { conventions: {}, provenance: [], confidence: "none" };
15230
+ }
15231
+ const parser = new Parser();
15232
+ parser.setLanguage(language);
15233
+ const authDeps = /* @__PURE__ */ new Map();
15234
+ const prefixBases = /* @__PURE__ */ new Map();
15235
+ const testAsyncPatterns = /* @__PURE__ */ new Map();
15236
+ try {
15237
+ for (const file of files) {
15238
+ const skip = isParsableSource(file.content, file.size);
15239
+ if (skip) {
15240
+ process.stderr.write(
15241
+ `[massu/ast] WARN: python-fastapi skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
15242
+ `
15243
+ );
15244
+ continue;
15245
+ }
15246
+ try {
15247
+ for (const hit of runQuery(parser, file.content, AUTH_DEP_QUERY, "fastapi-auth-dep", file.path)) {
15248
+ const name2 = hit.captures.auth_dep;
15249
+ if (name2 && !authDeps.has(name2)) {
15250
+ authDeps.set(name2, { line: hit.line, file: file.path });
15251
+ }
15252
+ }
15253
+ for (const hit of runQuery(parser, file.content, API_PREFIX_QUERY, "fastapi-api-prefix", file.path)) {
15254
+ const raw = hit.captures.prefix_value;
15255
+ if (!raw) continue;
15256
+ const literal = raw.replace(/^['"]/, "").replace(/['"]$/, "");
15257
+ const base = extractPrefixBase2(literal);
15258
+ if (base && !prefixBases.has(base)) {
15259
+ prefixBases.set(base, { line: hit.line, file: file.path });
15260
+ }
15261
+ }
15262
+ for (const hit of runQuery(parser, file.content, PYTEST_ASYNCIO_QUERY, "fastapi-pytest-asyncio", file.path)) {
15263
+ const pat = "@pytest.mark.asyncio";
15264
+ if (!testAsyncPatterns.has(pat)) {
15265
+ testAsyncPatterns.set(pat, { line: hit.line, file: file.path });
15266
+ }
15267
+ }
15268
+ } catch (e2) {
15269
+ if (e2 instanceof InvalidQueryError) {
15270
+ throw e2;
15271
+ }
15272
+ continue;
15273
+ }
15274
+ }
15275
+ } finally {
15276
+ try {
15277
+ parser.delete();
15278
+ } catch {
15279
+ }
15280
+ }
15281
+ const conventions = {};
15282
+ const provenance = [];
15283
+ if (authDeps.size === 1) {
15284
+ const [name2, { line, file }] = authDeps.entries().next().value;
15285
+ conventions.auth_dep = name2;
15286
+ provenance.push({ field: "auth_dep", sourceFile: file, line, query: "fastapi-auth-dep" });
15287
+ } else if (authDeps.size >= 2) {
15288
+ const [name2, { line, file }] = authDeps.entries().next().value;
15289
+ conventions.auth_dep = name2;
15290
+ provenance.push({ field: "auth_dep", sourceFile: file, line, query: "fastapi-auth-dep" });
15291
+ }
15292
+ if (prefixBases.size >= 1) {
15293
+ const [base, { line, file }] = prefixBases.entries().next().value;
15294
+ conventions.api_prefix_base = base;
15295
+ provenance.push({ field: "api_prefix_base", sourceFile: file, line, query: "fastapi-api-prefix" });
15296
+ }
15297
+ if (testAsyncPatterns.size >= 1) {
15298
+ const [pat, { line, file }] = testAsyncPatterns.entries().next().value;
15299
+ conventions.test_async_pattern = pat;
15300
+ provenance.push({ field: "test_async_pattern", sourceFile: file, line, query: "fastapi-pytest-asyncio" });
15301
+ }
15302
+ let confidence;
15303
+ if (Object.keys(conventions).length === 0) {
15304
+ confidence = "none";
15305
+ } else if (authDeps.size === 1 || authDeps.size === 0 && prefixBases.size > 0) {
15306
+ confidence = "high";
15307
+ } else if (authDeps.size >= 2) {
15308
+ confidence = "low";
15309
+ } else {
15310
+ confidence = "medium";
15311
+ }
15312
+ return { conventions, provenance, confidence };
15313
+ }
15314
+ };
15315
+ }
15316
+ });
15317
+
15318
+ // src/detect/adapters/python-django.ts
15319
+ var DECORATOR_QUERY, CLASS_BASE_QUERY, URLPATTERNS_QUERY, DJANGO_MIXIN_NAMES, DJANGO_DECORATOR_PATTERNS, pythonDjangoAdapter;
15320
+ var init_python_django = __esm({
15321
+ "src/detect/adapters/python-django.ts"() {
15322
+ "use strict";
15323
+ init_tree_sitter();
15324
+ init_query_helpers();
15325
+ init_tree_sitter_loader();
15326
+ init_parse_guard();
15327
+ DECORATOR_QUERY = `
15328
+ (decorator
15329
+ (identifier) @decorator_name)
15330
+ `;
15331
+ CLASS_BASE_QUERY = `
15332
+ (class_definition
15333
+ name: (identifier) @class_name
15334
+ superclasses: (argument_list
15335
+ (identifier) @base_name))
15336
+ `;
15337
+ URLPATTERNS_QUERY = `
15338
+ (assignment
15339
+ left: (identifier) @_target (#eq? @_target "urlpatterns")
15340
+ right: (list) @urlpatterns_list)
15341
+ `;
15342
+ DJANGO_MIXIN_NAMES = /* @__PURE__ */ new Set([
15343
+ "LoginRequiredMixin",
15344
+ "PermissionRequiredMixin",
15345
+ "UserPassesTestMixin",
15346
+ "StaffuserRequiredMixin"
15347
+ ]);
15348
+ DJANGO_DECORATOR_PATTERNS = [
15349
+ /^login_required$/,
15350
+ /^permission_required$/,
15351
+ /_required$/,
15352
+ /^require_/
15353
+ ];
15354
+ pythonDjangoAdapter = {
15355
+ id: "python-django",
15356
+ languages: ["python"],
15357
+ matches(signals) {
15358
+ if (signals.presentFiles.has("manage.py")) return true;
15359
+ const pyToml = signals.pyprojectToml;
15360
+ if (pyToml?.__raw && /\bdjango\b/i.test(pyToml.__raw)) return true;
15361
+ return false;
15362
+ },
15363
+ async introspect(files, _rootDir) {
15364
+ if (files.length === 0) {
15365
+ return { conventions: {}, provenance: [], confidence: "none" };
15366
+ }
15367
+ let language;
15368
+ try {
15369
+ language = await loadGrammar("python");
15370
+ } catch {
15371
+ return { conventions: {}, provenance: [], confidence: "none" };
15372
+ }
15373
+ const parser = new Parser();
15374
+ parser.setLanguage(language);
15375
+ const decoratorsFound = /* @__PURE__ */ new Map();
15376
+ const mixinsFound = /* @__PURE__ */ new Map();
15377
+ const urlpatternsShape = {
15378
+ value: null,
15379
+ line: 0,
15380
+ file: ""
15381
+ };
15382
+ try {
15383
+ for (const file of files) {
15384
+ const skip = isParsableSource(file.content, file.size);
15385
+ if (skip) {
15386
+ process.stderr.write(
15387
+ `[massu/ast] WARN: python-django skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
15388
+ `
15389
+ );
15390
+ continue;
15391
+ }
15392
+ try {
15393
+ for (const hit of runQuery(parser, file.content, DECORATOR_QUERY, "django-decorator", file.path)) {
15394
+ const name2 = hit.captures.decorator_name;
15395
+ if (!name2) continue;
15396
+ if (DJANGO_DECORATOR_PATTERNS.some((re2) => re2.test(name2)) && !decoratorsFound.has(name2)) {
15397
+ decoratorsFound.set(name2, { line: hit.line, file: file.path });
15398
+ }
15399
+ }
15400
+ for (const hit of runQuery(parser, file.content, CLASS_BASE_QUERY, "django-mixin", file.path)) {
15401
+ const base = hit.captures.base_name;
15402
+ if (base && DJANGO_MIXIN_NAMES.has(base) && !mixinsFound.has(base)) {
15403
+ mixinsFound.set(base, { line: hit.line, file: file.path });
15404
+ }
15405
+ }
15406
+ for (const hit of runQuery(parser, file.content, URLPATTERNS_QUERY, "django-urlpatterns", file.path)) {
15407
+ const listText = hit.captures.urlpatterns_list ?? "";
15408
+ const hasFunctionForm = /\bpath\s*\(/.test(listText) || /\bre_path\s*\(/.test(listText);
15409
+ const hasClassForm = /\.as_view\s*\(/.test(listText);
15410
+ let shape = null;
15411
+ if (hasFunctionForm && hasClassForm) shape = "mixed";
15412
+ else if (hasFunctionForm) shape = "function-views";
15413
+ else if (hasClassForm) shape = "class-views";
15414
+ if (shape && !urlpatternsShape.value) {
15415
+ urlpatternsShape.value = shape;
15416
+ urlpatternsShape.line = hit.line;
15417
+ urlpatternsShape.file = file.path;
15418
+ }
15419
+ }
15420
+ } catch (e2) {
15421
+ if (e2 instanceof InvalidQueryError) throw e2;
15422
+ continue;
15423
+ }
15424
+ }
15425
+ } finally {
15426
+ try {
15427
+ parser.delete();
15428
+ } catch {
15429
+ }
15430
+ }
15431
+ const conventions = {};
15432
+ const provenance = [];
15433
+ if (decoratorsFound.size > 0) {
15434
+ const list = Array.from(decoratorsFound.keys());
15435
+ conventions.decorator_usage = list;
15436
+ const [first, { line, file }] = decoratorsFound.entries().next().value;
15437
+ provenance.push({ field: "decorator_usage", sourceFile: file, line, query: "django-decorator" });
15438
+ conventions.auth_dep = first;
15439
+ provenance.push({ field: "auth_dep", sourceFile: file, line, query: "django-decorator" });
15440
+ }
15441
+ if (mixinsFound.size > 0) {
15442
+ const list = Array.from(mixinsFound.keys());
15443
+ conventions.mixin_classes = list;
15444
+ const [, { line, file }] = mixinsFound.entries().next().value;
15445
+ provenance.push({ field: "mixin_classes", sourceFile: file, line, query: "django-mixin" });
15446
+ }
15447
+ if (urlpatternsShape.value) {
15448
+ conventions.urlpatterns_shape = urlpatternsShape.value;
15449
+ provenance.push({
15450
+ field: "urlpatterns_shape",
15451
+ sourceFile: urlpatternsShape.file,
15452
+ line: urlpatternsShape.line,
15453
+ query: "django-urlpatterns"
15454
+ });
15455
+ }
15456
+ let confidence;
15457
+ if (Object.keys(conventions).length === 0) {
15458
+ confidence = "none";
15459
+ } else if (decoratorsFound.size > 0 || mixinsFound.size > 0) {
15460
+ confidence = "high";
15461
+ } else {
15462
+ confidence = "medium";
15463
+ }
15464
+ return { conventions, provenance, confidence };
15465
+ }
15466
+ };
15467
+ }
15468
+ });
15469
+
15470
+ // src/detect/adapters/nextjs-trpc.ts
15471
+ var ROUTER_BUILDER_QUERY, PROCEDURE_QUERY, KNOWN_BUILDERS, nextjsTrpcAdapter;
15472
+ var init_nextjs_trpc = __esm({
15473
+ "src/detect/adapters/nextjs-trpc.ts"() {
15474
+ "use strict";
15475
+ init_tree_sitter();
15476
+ init_query_helpers();
15477
+ init_tree_sitter_loader();
15478
+ init_parse_guard();
15479
+ ROUTER_BUILDER_QUERY = `
15480
+ (call_expression
15481
+ function: (identifier) @builder_id (#match? @builder_id "^(createTRPCRouter|router)$"))
15482
+
15483
+ (call_expression
15484
+ function: (member_expression
15485
+ object: (identifier) @_obj
15486
+ property: (property_identifier) @_prop (#eq? @_prop "router"))) @member_call
15487
+ `;
15488
+ PROCEDURE_QUERY = `
15489
+ (member_expression
15490
+ object: (identifier) @procedure_id (#match? @procedure_id "Procedure$"))
15491
+
15492
+ (call_expression
15493
+ function: (identifier) @procedure_call (#match? @procedure_call "Procedure$"))
15494
+ `;
15495
+ KNOWN_BUILDERS = /* @__PURE__ */ new Set(["createTRPCRouter", "router"]);
15496
+ nextjsTrpcAdapter = {
15497
+ id: "nextjs-trpc",
15498
+ languages: ["typescript"],
15499
+ matches(signals) {
15500
+ const pkgJson = signals.packageJson;
15501
+ if (pkgJson) {
15502
+ const deps = pkgJson.dependencies;
15503
+ const devDeps = pkgJson.devDependencies;
15504
+ const all = { ...deps ?? {}, ...devDeps ?? {} };
15505
+ if (Object.keys(all).some((k3) => k3.startsWith("@trpc/"))) return true;
15506
+ }
15507
+ if (signals.presentDirs.has("server")) return true;
15508
+ return false;
15509
+ },
15510
+ async introspect(files, _rootDir) {
15511
+ if (files.length === 0) {
15512
+ return { conventions: {}, provenance: [], confidence: "none" };
15513
+ }
15514
+ let language;
15515
+ try {
15516
+ language = await loadGrammar("typescript");
15517
+ } catch {
15518
+ return { conventions: {}, provenance: [], confidence: "none" };
15519
+ }
15520
+ const parser = new Parser();
15521
+ parser.setLanguage(language);
15522
+ const builders = /* @__PURE__ */ new Map();
15523
+ const procedures = /* @__PURE__ */ new Map();
15524
+ try {
15525
+ for (const file of files) {
15526
+ const skip = isParsableSource(file.content, file.size);
15527
+ if (skip) {
15528
+ process.stderr.write(
15529
+ `[massu/ast] WARN: nextjs-trpc skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
15530
+ `
15531
+ );
15532
+ continue;
15533
+ }
15534
+ try {
15535
+ for (const hit of runQuery(parser, file.content, ROUTER_BUILDER_QUERY, "trpc-router-builder", file.path)) {
15536
+ const directId = hit.captures.builder_id;
15537
+ const memberCall = hit.captures.member_call;
15538
+ let label = null;
15539
+ if (directId && KNOWN_BUILDERS.has(directId)) {
15540
+ label = directId;
15541
+ } else if (memberCall) {
15542
+ const m3 = memberCall.match(/([A-Za-z_$][A-Za-z0-9_$]*)\.router/);
15543
+ if (m3) label = `${m3[1]}.router`;
15544
+ }
15545
+ if (label && !builders.has(label)) {
15546
+ builders.set(label, { line: hit.line, file: file.path });
15547
+ }
15548
+ }
15549
+ for (const hit of runQuery(parser, file.content, PROCEDURE_QUERY, "trpc-procedure", file.path)) {
15550
+ const proc = hit.captures.procedure_id ?? hit.captures.procedure_call;
15551
+ if (proc && !procedures.has(proc)) {
15552
+ procedures.set(proc, { line: hit.line, file: file.path });
15553
+ }
15554
+ }
15555
+ } catch (e2) {
15556
+ if (e2 instanceof InvalidQueryError) throw e2;
15557
+ continue;
15558
+ }
15559
+ }
15560
+ } finally {
15561
+ try {
15562
+ parser.delete();
15563
+ } catch {
15564
+ }
15565
+ }
15566
+ const conventions = {};
15567
+ const provenance = [];
15568
+ if (builders.size > 0) {
15569
+ const [name2, { line, file }] = builders.entries().next().value;
15570
+ conventions.trpc_router_builder = name2;
15571
+ provenance.push({ field: "trpc_router_builder", sourceFile: file, line, query: "trpc-router-builder" });
15572
+ }
15573
+ if (procedures.size > 0) {
15574
+ const [name2, { line, file }] = procedures.entries().next().value;
15575
+ conventions.procedure_pattern = name2;
15576
+ provenance.push({ field: "procedure_pattern", sourceFile: file, line, query: "trpc-procedure" });
15577
+ }
15578
+ let confidence;
15579
+ if (Object.keys(conventions).length === 0) {
15580
+ confidence = "none";
15581
+ } else if (builders.size === 1 && procedures.size <= 2) {
15582
+ confidence = "high";
15583
+ } else if (builders.size > 1) {
15584
+ confidence = "low";
15585
+ } else {
15586
+ confidence = "medium";
15587
+ }
15588
+ return { conventions, provenance, confidence };
15589
+ }
15590
+ };
15591
+ }
15592
+ });
15593
+
15594
+ // src/detect/adapters/swift-swiftui.ts
15595
+ var API_CLASS_QUERY, POLICY_QUERY, NAV_QUERY, POLICY_NAMES, swiftSwiftUiAdapter;
15596
+ var init_swift_swiftui = __esm({
15597
+ "src/detect/adapters/swift-swiftui.ts"() {
15598
+ "use strict";
15599
+ init_tree_sitter();
15600
+ init_query_helpers();
15601
+ init_tree_sitter_loader();
15602
+ init_parse_guard();
15603
+ API_CLASS_QUERY = `
15604
+ (simple_identifier) @ident
15605
+ `;
15606
+ POLICY_QUERY = `
15607
+ (navigation_expression
15608
+ suffix: (navigation_suffix
15609
+ (simple_identifier) @policy_name))
15610
+ `;
15611
+ NAV_QUERY = `
15612
+ (simple_identifier) @nav_ident
15613
+ `;
15614
+ POLICY_NAMES = /* @__PURE__ */ new Set([
15615
+ "deviceOwnerAuthentication",
15616
+ "deviceOwnerAuthenticationWithBiometrics"
15617
+ ]);
15618
+ swiftSwiftUiAdapter = {
15619
+ id: "swift-swiftui",
15620
+ languages: ["swift"],
15621
+ matches(signals) {
15622
+ if (signals.presentFiles.has("Package.swift")) return true;
15623
+ for (const dir of signals.presentDirs) {
15624
+ if (dir.endsWith(".xcodeproj") || dir.endsWith(".xcworkspace")) return true;
15625
+ if (dir === "Sources") return true;
15626
+ }
15627
+ for (const file of signals.presentFiles) {
15628
+ if (file.endsWith(".swift")) return true;
15629
+ }
15630
+ return false;
15631
+ },
15632
+ async introspect(files, _rootDir) {
15633
+ if (files.length === 0) {
15634
+ return { conventions: {}, provenance: [], confidence: "none" };
15635
+ }
15636
+ let language;
15637
+ try {
15638
+ language = await loadGrammar("swift");
15639
+ } catch {
15640
+ return { conventions: {}, provenance: [], confidence: "none" };
15641
+ }
15642
+ const parser = new Parser();
15643
+ parser.setLanguage(language);
15644
+ const apiClasses = /* @__PURE__ */ new Map();
15645
+ const policies = /* @__PURE__ */ new Map();
15646
+ const navs = /* @__PURE__ */ new Map();
15647
+ try {
15648
+ for (const file of files) {
15649
+ const skip = isParsableSource(file.content, file.size);
15650
+ if (skip) {
15651
+ process.stderr.write(
15652
+ `[massu/ast] WARN: swift-swiftui skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
15653
+ `
15654
+ );
15655
+ continue;
15656
+ }
15657
+ try {
15658
+ for (const hit of runQuery(parser, file.content, API_CLASS_QUERY, "swift-api-class", file.path)) {
15659
+ const ident = hit.captures.ident;
15660
+ if (ident && /^[A-Z][A-Za-z0-9_]*API$/.test(ident) && !apiClasses.has(ident)) {
15661
+ apiClasses.set(ident, { line: hit.line, file: file.path });
15662
+ }
15663
+ }
15664
+ for (const hit of runQuery(parser, file.content, POLICY_QUERY, "swift-biometric-policy", file.path)) {
15665
+ const name2 = hit.captures.policy_name;
15666
+ if (name2 && POLICY_NAMES.has(name2) && !policies.has(name2)) {
15667
+ policies.set(name2, { line: hit.line, file: file.path });
15668
+ }
15669
+ }
15670
+ for (const hit of runQuery(parser, file.content, NAV_QUERY, "swift-navigation", file.path)) {
15671
+ const ident = hit.captures.nav_ident;
15672
+ if ((ident === "NavigationStack" || ident === "NavigationView") && !navs.has(ident)) {
15673
+ navs.set(ident, { line: hit.line, file: file.path });
15674
+ }
15675
+ }
15676
+ } catch (e2) {
15677
+ if (e2 instanceof InvalidQueryError) throw e2;
15678
+ continue;
15679
+ }
15680
+ }
15681
+ } finally {
15682
+ try {
15683
+ parser.delete();
15684
+ } catch {
15685
+ }
15686
+ }
15687
+ const conventions = {};
15688
+ const provenance = [];
15689
+ if (apiClasses.size > 0) {
15690
+ const [name2, { line, file }] = apiClasses.entries().next().value;
15691
+ conventions.api_client_class = name2;
15692
+ provenance.push({ field: "api_client_class", sourceFile: file, line, query: "swift-api-class" });
15693
+ }
15694
+ if (policies.size > 0) {
15695
+ const [name2, { line, file }] = policies.entries().next().value;
15696
+ conventions.biometric_policy = name2;
15697
+ provenance.push({ field: "biometric_policy", sourceFile: file, line, query: "swift-biometric-policy" });
15698
+ }
15699
+ if (navs.size > 0) {
15700
+ const [name2, { line, file }] = navs.entries().next().value;
15701
+ conventions.navigation_pattern = name2;
15702
+ provenance.push({ field: "navigation_pattern", sourceFile: file, line, query: "swift-navigation" });
15703
+ }
15704
+ let confidence;
15705
+ if (Object.keys(conventions).length === 0) {
15706
+ confidence = "none";
15707
+ } else if (apiClasses.size === 1 && policies.size <= 1) {
15708
+ confidence = "high";
15709
+ } else if (apiClasses.size > 1) {
15710
+ confidence = "low";
15711
+ } else {
15712
+ confidence = "medium";
15713
+ }
15714
+ return { conventions, provenance, confidence };
15715
+ }
15716
+ };
15717
+ }
15718
+ });
15719
+
15720
+ // src/detect/adapters/python-flask.ts
15721
+ function extractPrefixBase3(prefix3) {
15722
+ if (!prefix3.startsWith("/")) return null;
15723
+ const stripped = prefix3.replace(/^\/+/, "");
15724
+ const firstSeg = stripped.split("/")[0];
15725
+ if (!firstSeg) return null;
15726
+ return "/" + firstSeg;
15727
+ }
15728
+ var AUTH_DECORATOR_QUERY, BLUEPRINT_URL_PREFIX_QUERY, APP_FACTORY_QUERY, pythonFlaskAdapter;
15729
+ var init_python_flask = __esm({
15730
+ "src/detect/adapters/python-flask.ts"() {
15731
+ "use strict";
15732
+ init_tree_sitter();
15733
+ init_query_helpers();
15734
+ init_tree_sitter_loader();
15735
+ init_parse_guard();
15736
+ AUTH_DECORATOR_QUERY = `
15737
+ (decorator
15738
+ (identifier) @auth_decorator (#match? @auth_decorator "_required$"))
15739
+ `;
15740
+ BLUEPRINT_URL_PREFIX_QUERY = `
15741
+ (call
15742
+ function: (identifier) @_callee (#eq? @_callee "Blueprint")
15743
+ arguments: (argument_list
15744
+ (keyword_argument
15745
+ name: (identifier) @_kw (#eq? @_kw "url_prefix")
15746
+ value: (string) @url_prefix)))
15747
+ `;
15748
+ APP_FACTORY_QUERY = `
15749
+ (function_definition
15750
+ name: (identifier) @factory_name (#match? @factory_name "^create_")
15751
+ body: (block
15752
+ (expression_statement
15753
+ (assignment
15754
+ right: (call
15755
+ function: (identifier) @_flask_call (#eq? @_flask_call "Flask"))))))
15756
+ `;
15757
+ pythonFlaskAdapter = {
15758
+ id: "python-flask",
15759
+ languages: ["python"],
15760
+ matches(signals) {
15761
+ const pyToml = signals.pyprojectToml;
15762
+ if (pyToml?.__raw && /\bflask\b/i.test(pyToml.__raw)) return true;
15763
+ if (signals.presentDirs.has("app") && signals.presentFiles.has("app.py")) return true;
15764
+ if (signals.presentDirs.has("app") && signals.presentFiles.has("wsgi.py")) return true;
15765
+ return false;
15766
+ },
15767
+ async introspect(files, _rootDir) {
15768
+ if (files.length === 0) {
15769
+ return { conventions: {}, provenance: [], confidence: "none" };
15770
+ }
15771
+ let language;
15772
+ try {
15773
+ language = await loadGrammar("python");
15774
+ } catch (e2) {
15775
+ return { conventions: {}, provenance: [], confidence: "none" };
15776
+ }
15777
+ const parser = new Parser();
15778
+ parser.setLanguage(language);
15779
+ const authDecorators = /* @__PURE__ */ new Map();
15780
+ const urlPrefixes = /* @__PURE__ */ new Map();
15781
+ const appFactories = /* @__PURE__ */ new Map();
15782
+ try {
15783
+ for (const file of files) {
15784
+ const skip = isParsableSource(file.content, file.size);
15785
+ if (skip) {
15786
+ process.stderr.write(
15787
+ `[massu/ast] WARN: python-flask skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
15788
+ `
15789
+ );
15790
+ continue;
15791
+ }
15792
+ try {
15793
+ for (const hit of runQuery(parser, file.content, AUTH_DECORATOR_QUERY, "flask-auth-decorator", file.path)) {
15794
+ const name2 = hit.captures.auth_decorator;
15795
+ if (name2 && !authDecorators.has(name2)) {
15796
+ authDecorators.set(name2, { line: hit.line, file: file.path });
15797
+ }
15798
+ }
15799
+ for (const hit of runQuery(parser, file.content, BLUEPRINT_URL_PREFIX_QUERY, "flask-blueprint-url-prefix", file.path)) {
15800
+ const raw = hit.captures.url_prefix;
15801
+ if (!raw) continue;
15802
+ const literal = raw.replace(/^['"]/, "").replace(/['"]$/, "");
15803
+ const base = extractPrefixBase3(literal);
15804
+ if (base && !urlPrefixes.has(base)) {
15805
+ urlPrefixes.set(base, { line: hit.line, file: file.path });
15806
+ }
15807
+ }
15808
+ for (const hit of runQuery(parser, file.content, APP_FACTORY_QUERY, "flask-app-factory", file.path)) {
15809
+ const name2 = hit.captures.factory_name;
15810
+ if (name2 && !appFactories.has(name2)) {
15811
+ appFactories.set(name2, { line: hit.line, file: file.path });
15812
+ }
15813
+ }
15814
+ } catch (e2) {
15815
+ if (e2 instanceof InvalidQueryError) {
15816
+ throw e2;
15817
+ }
15818
+ continue;
15819
+ }
15820
+ }
15821
+ } finally {
15822
+ try {
15823
+ parser.delete();
15824
+ } catch {
15825
+ }
15826
+ }
15827
+ const conventions = {};
15828
+ const provenance = [];
15829
+ if (authDecorators.size === 1) {
15830
+ const [name2, { line, file }] = authDecorators.entries().next().value;
15831
+ conventions.auth_decorator = name2;
15832
+ provenance.push({ field: "auth_decorator", sourceFile: file, line, query: "flask-auth-decorator" });
15833
+ } else if (authDecorators.size >= 2) {
15834
+ const [name2, { line, file }] = authDecorators.entries().next().value;
15835
+ conventions.auth_decorator = name2;
15836
+ provenance.push({ field: "auth_decorator", sourceFile: file, line, query: "flask-auth-decorator" });
15837
+ }
15838
+ if (urlPrefixes.size >= 1) {
15839
+ const [base, { line, file }] = urlPrefixes.entries().next().value;
15840
+ conventions.blueprint_url_prefix = base;
15841
+ provenance.push({ field: "blueprint_url_prefix", sourceFile: file, line, query: "flask-blueprint-url-prefix" });
15842
+ }
15843
+ if (appFactories.size >= 1) {
15844
+ const [name2, { line, file }] = appFactories.entries().next().value;
15845
+ conventions.app_factory = name2;
15846
+ provenance.push({ field: "app_factory", sourceFile: file, line, query: "flask-app-factory" });
15847
+ }
15848
+ let confidence;
15849
+ if (Object.keys(conventions).length === 0) {
15850
+ confidence = "none";
15851
+ } else if (authDecorators.size === 1) {
15852
+ confidence = "high";
15853
+ } else if (authDecorators.size >= 2) {
15854
+ confidence = "low";
15855
+ } else {
15856
+ confidence = "medium";
15857
+ }
15858
+ return { conventions, provenance, confidence };
15859
+ }
15860
+ };
15861
+ }
15862
+ });
15863
+
15864
+ // src/detect/adapters/go-chi.ts
15865
+ function extractPrefixBase4(prefix3) {
15866
+ if (!prefix3.startsWith("/")) return null;
15867
+ const stripped = prefix3.replace(/^\/+/, "");
15868
+ const firstSeg = stripped.split("/")[0];
15869
+ if (!firstSeg) return null;
15870
+ return "/" + firstSeg;
15871
+ }
15872
+ var ROUTE_METHOD_QUERY, MOUNT_PREFIX_QUERY, MIDDLEWARE_USE_QUERY, goChiAdapter;
15873
+ var init_go_chi = __esm({
15874
+ "src/detect/adapters/go-chi.ts"() {
15875
+ "use strict";
15876
+ init_tree_sitter();
15877
+ init_query_helpers();
15878
+ init_tree_sitter_loader();
15879
+ init_parse_guard();
15880
+ ROUTE_METHOD_QUERY = `
15881
+ (call_expression
15882
+ function: (selector_expression
15883
+ field: (field_identifier) @method (#match? @method "^(Get|Post|Put|Delete|Patch|Head|Options|Connect|Trace)$"))
15884
+ arguments: (argument_list
15885
+ .
15886
+ (interpreted_string_literal) @route_path))
15887
+ `;
15888
+ MOUNT_PREFIX_QUERY = `
15889
+ (call_expression
15890
+ function: (selector_expression
15891
+ field: (field_identifier) @_field (#eq? @_field "Mount"))
15892
+ arguments: (argument_list
15893
+ .
15894
+ (interpreted_string_literal) @mount_path))
15895
+ `;
15896
+ MIDDLEWARE_USE_QUERY = `
15897
+ (call_expression
15898
+ function: (selector_expression
15899
+ field: (field_identifier) @_use (#eq? @_use "Use"))
15900
+ arguments: (argument_list
15901
+ .
15902
+ (selector_expression
15903
+ operand: (identifier) @_pkg (#eq? @_pkg "middleware")
15904
+ field: (field_identifier) @middleware_name)))
15905
+ `;
15906
+ goChiAdapter = {
15907
+ id: "go-chi",
15908
+ languages: ["go"],
15909
+ matches(signals) {
15910
+ if (!signals.goMod) return false;
15911
+ if (/github\.com\/go-chi\/chi/i.test(signals.goMod)) return true;
15912
+ return false;
15913
+ },
15914
+ async introspect(files, _rootDir) {
15915
+ if (files.length === 0) {
15916
+ return { conventions: {}, provenance: [], confidence: "none" };
15917
+ }
15918
+ let language;
15919
+ try {
15920
+ language = await loadGrammar("go");
15921
+ } catch (e2) {
15922
+ return { conventions: {}, provenance: [], confidence: "none" };
15923
+ }
15924
+ const parser = new Parser();
15925
+ parser.setLanguage(language);
15926
+ const routeMethods = /* @__PURE__ */ new Map();
15927
+ const mountBases = /* @__PURE__ */ new Map();
15928
+ const middlewareNames = /* @__PURE__ */ new Map();
15929
+ try {
15930
+ for (const file of files) {
15931
+ const skip = isParsableSource(file.content, file.size);
15932
+ if (skip) {
15933
+ process.stderr.write(
15934
+ `[massu/ast] WARN: go-chi skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
15935
+ `
15936
+ );
15937
+ continue;
15938
+ }
15939
+ try {
15940
+ for (const hit of runQuery(parser, file.content, ROUTE_METHOD_QUERY, "chi-route-method", file.path)) {
15941
+ const method = hit.captures.method;
15942
+ if (method && !routeMethods.has(method)) {
15943
+ routeMethods.set(method, { line: hit.line, file: file.path });
15944
+ }
15945
+ }
15946
+ for (const hit of runQuery(parser, file.content, MOUNT_PREFIX_QUERY, "chi-mount-prefix", file.path)) {
15947
+ const raw = hit.captures.mount_path;
15948
+ if (!raw) continue;
15949
+ const literal = raw.replace(/^["`]/, "").replace(/["`]$/, "");
15950
+ const base = extractPrefixBase4(literal);
15951
+ if (base && !mountBases.has(base)) {
15952
+ mountBases.set(base, { line: hit.line, file: file.path });
15953
+ }
15954
+ }
15955
+ for (const hit of runQuery(parser, file.content, MIDDLEWARE_USE_QUERY, "chi-middleware-use", file.path)) {
15956
+ const name2 = hit.captures.middleware_name;
15957
+ if (name2 && !middlewareNames.has(name2)) {
15958
+ middlewareNames.set(name2, { line: hit.line, file: file.path });
15959
+ }
15960
+ }
15961
+ } catch (e2) {
15962
+ if (e2 instanceof InvalidQueryError) {
15963
+ throw e2;
15964
+ }
15965
+ continue;
15966
+ }
15967
+ }
15968
+ } finally {
15969
+ try {
15970
+ parser.delete();
15971
+ } catch {
15972
+ }
15973
+ }
15974
+ const conventions = {};
15975
+ const provenance = [];
15976
+ if (routeMethods.size === 1) {
15977
+ const [name2, { line, file }] = routeMethods.entries().next().value;
15978
+ conventions.route_method = name2;
15979
+ provenance.push({ field: "route_method", sourceFile: file, line, query: "chi-route-method" });
15980
+ } else if (routeMethods.size >= 2) {
15981
+ const [name2, { line, file }] = routeMethods.entries().next().value;
15982
+ conventions.route_method = name2;
15983
+ provenance.push({ field: "route_method", sourceFile: file, line, query: "chi-route-method" });
15984
+ }
15985
+ if (mountBases.size >= 1) {
15986
+ const [base, { line, file }] = mountBases.entries().next().value;
15987
+ conventions.mount_prefix_base = base;
15988
+ provenance.push({ field: "mount_prefix_base", sourceFile: file, line, query: "chi-mount-prefix" });
15989
+ }
15990
+ if (middlewareNames.size >= 1) {
15991
+ const [name2, { line, file }] = middlewareNames.entries().next().value;
15992
+ conventions.middleware_name = name2;
15993
+ provenance.push({ field: "middleware_name", sourceFile: file, line, query: "chi-middleware-use" });
15994
+ }
15995
+ let confidence;
15996
+ if (Object.keys(conventions).length === 0) {
15997
+ confidence = "none";
15998
+ } else if (routeMethods.size === 1) {
15999
+ confidence = "high";
16000
+ } else if (routeMethods.size >= 2) {
16001
+ confidence = "low";
16002
+ } else {
16003
+ confidence = "medium";
16004
+ }
16005
+ return { conventions, provenance, confidence };
16006
+ }
16007
+ };
16008
+ }
16009
+ });
16010
+
16011
+ // src/detect/adapters/rails.ts
16012
+ function extractRootController(target) {
16013
+ const idx = target.indexOf("#");
16014
+ if (idx <= 0) return null;
16015
+ const controller = target.slice(0, idx).trim();
16016
+ return controller || null;
16017
+ }
16018
+ var ROUTE_METHOD_QUERY2, NAMESPACE_QUERY, ROOT_ROUTE_QUERY, railsAdapter;
16019
+ var init_rails = __esm({
16020
+ "src/detect/adapters/rails.ts"() {
16021
+ "use strict";
16022
+ init_tree_sitter();
16023
+ init_query_helpers();
16024
+ init_tree_sitter_loader();
16025
+ init_parse_guard();
16026
+ ROUTE_METHOD_QUERY2 = `
16027
+ (call
16028
+ method: (identifier) @method (#match? @method "^(get|post|put|patch|delete|options|head)$")
16029
+ arguments: (argument_list
16030
+ .
16031
+ (string) @route_path))
16032
+ `;
16033
+ NAMESPACE_QUERY = `
16034
+ (call
16035
+ method: (identifier) @_method (#eq? @_method "namespace")
16036
+ arguments: (argument_list
16037
+ .
16038
+ [
16039
+ (simple_symbol) @namespace_symbol
16040
+ (string) @namespace_string
16041
+ ]))
16042
+ `;
16043
+ ROOT_ROUTE_QUERY = `
16044
+ (call
16045
+ method: (identifier) @_method (#eq? @_method "root")
16046
+ arguments: (argument_list
16047
+ .
16048
+ (string) @root_target))
16049
+
16050
+ (call
16051
+ method: (identifier) @_method (#eq? @_method "root")
16052
+ arguments: (argument_list
16053
+ (pair
16054
+ key: (hash_key_symbol) @_key (#eq? @_key "to")
16055
+ value: (string) @root_target)))
16056
+ `;
16057
+ railsAdapter = {
16058
+ id: "rails",
16059
+ languages: ["ruby"],
16060
+ matches(signals) {
16061
+ if (!signals.gemfile) return false;
16062
+ return /^\s*gem\s+['"]rails['"]/im.test(signals.gemfile);
16063
+ },
16064
+ async introspect(files, _rootDir) {
16065
+ if (files.length === 0) {
16066
+ return { conventions: {}, provenance: [], confidence: "none" };
16067
+ }
16068
+ let language;
16069
+ try {
16070
+ language = await loadGrammar("ruby");
16071
+ } catch (e2) {
16072
+ return { conventions: {}, provenance: [], confidence: "none" };
16073
+ }
16074
+ const parser = new Parser();
16075
+ parser.setLanguage(language);
16076
+ const routeMethods = /* @__PURE__ */ new Map();
16077
+ const namespaces = /* @__PURE__ */ new Map();
16078
+ const rootControllers = /* @__PURE__ */ new Map();
16079
+ try {
16080
+ for (const file of files) {
16081
+ const skip = isParsableSource(file.content, file.size);
16082
+ if (skip) {
16083
+ process.stderr.write(
16084
+ `[massu/ast] WARN: rails skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
16085
+ `
16086
+ );
16087
+ continue;
16088
+ }
16089
+ try {
16090
+ for (const hit of runQuery(parser, file.content, ROUTE_METHOD_QUERY2, "rails-route-method", file.path)) {
16091
+ const method = hit.captures.method;
16092
+ if (method && !routeMethods.has(method)) {
16093
+ routeMethods.set(method, { line: hit.line, file: file.path });
16094
+ }
16095
+ }
16096
+ for (const hit of runQuery(parser, file.content, NAMESPACE_QUERY, "rails-namespace", file.path)) {
16097
+ const symbolRaw = hit.captures.namespace_symbol;
16098
+ const stringRaw = hit.captures.namespace_string;
16099
+ const name2 = symbolRaw ? symbolRaw.replace(/^:/, "") : stringRaw ? stringRaw.replace(/^['"]/, "").replace(/['"]$/, "") : null;
16100
+ if (!name2) continue;
16101
+ const path = "/" + name2;
16102
+ if (!namespaces.has(path)) {
16103
+ namespaces.set(path, { line: hit.line, file: file.path });
16104
+ }
16105
+ }
16106
+ for (const hit of runQuery(parser, file.content, ROOT_ROUTE_QUERY, "rails-root", file.path)) {
16107
+ const raw = hit.captures.root_target;
16108
+ if (!raw) continue;
16109
+ const literal = raw.replace(/^['"]/, "").replace(/['"]$/, "");
16110
+ const controller = extractRootController(literal);
16111
+ if (controller && !rootControllers.has(controller)) {
16112
+ rootControllers.set(controller, { line: hit.line, file: file.path });
16113
+ }
16114
+ }
16115
+ } catch (e2) {
16116
+ if (e2 instanceof InvalidQueryError) {
16117
+ throw e2;
16118
+ }
16119
+ continue;
16120
+ }
16121
+ }
16122
+ } finally {
16123
+ try {
16124
+ parser.delete();
16125
+ } catch {
16126
+ }
16127
+ }
16128
+ const conventions = {};
16129
+ const provenance = [];
16130
+ if (routeMethods.size === 1) {
16131
+ const [name2, { line, file }] = routeMethods.entries().next().value;
16132
+ conventions.route_method = name2;
16133
+ provenance.push({ field: "route_method", sourceFile: file, line, query: "rails-route-method" });
16134
+ } else if (routeMethods.size >= 2) {
16135
+ const [name2, { line, file }] = routeMethods.entries().next().value;
16136
+ conventions.route_method = name2;
16137
+ provenance.push({ field: "route_method", sourceFile: file, line, query: "rails-route-method" });
16138
+ }
16139
+ if (namespaces.size >= 1) {
16140
+ const [path, { line, file }] = namespaces.entries().next().value;
16141
+ conventions.api_namespace = path;
16142
+ provenance.push({ field: "api_namespace", sourceFile: file, line, query: "rails-namespace" });
16143
+ }
16144
+ if (rootControllers.size >= 1) {
16145
+ const [name2, { line, file }] = rootControllers.entries().next().value;
16146
+ conventions.root_controller = name2;
16147
+ provenance.push({ field: "root_controller", sourceFile: file, line, query: "rails-root" });
16148
+ }
16149
+ let confidence;
16150
+ if (Object.keys(conventions).length === 0) {
16151
+ confidence = "none";
16152
+ } else if (routeMethods.size === 1) {
16153
+ confidence = "high";
16154
+ } else if (routeMethods.size >= 2) {
16155
+ confidence = "low";
16156
+ } else {
16157
+ confidence = "medium";
16158
+ }
16159
+ return { conventions, provenance, confidence };
16160
+ }
16161
+ };
16162
+ }
16163
+ });
16164
+
16165
+ // src/detect/adapters/phoenix.ts
16166
+ function extractPrefixBase5(prefix3) {
16167
+ if (!prefix3.startsWith("/")) return null;
16168
+ const stripped = prefix3.replace(/^\/+/, "");
16169
+ const firstSeg = stripped.split("/")[0];
16170
+ if (!firstSeg) return null;
16171
+ return "/" + firstSeg;
16172
+ }
16173
+ var ROUTE_METHOD_QUERY3, SCOPE_PATH_QUERY, ROUTER_MODULE_QUERY, phoenixAdapter;
16174
+ var init_phoenix = __esm({
16175
+ "src/detect/adapters/phoenix.ts"() {
16176
+ "use strict";
16177
+ init_tree_sitter();
16178
+ init_query_helpers();
16179
+ init_tree_sitter_loader();
16180
+ init_parse_guard();
16181
+ ROUTE_METHOD_QUERY3 = `
16182
+ (call
16183
+ (identifier) @method (#match? @method "^(get|post|put|patch|delete|options|head)$")
16184
+ (arguments
16185
+ .
16186
+ (string) @route_path))
16187
+ `;
16188
+ SCOPE_PATH_QUERY = `
16189
+ (call
16190
+ (identifier) @_method (#eq? @_method "scope")
16191
+ (arguments
16192
+ .
16193
+ (string) @scope_path)
16194
+ (do_block))
16195
+ `;
16196
+ ROUTER_MODULE_QUERY = `
16197
+ (call
16198
+ (identifier) @_method (#eq? @_method "defmodule")
16199
+ (arguments
16200
+ .
16201
+ (alias) @module_name (#match? @module_name "Router$"))
16202
+ (do_block))
16203
+ `;
16204
+ phoenixAdapter = {
16205
+ id: "phoenix",
16206
+ languages: ["elixir"],
16207
+ matches(signals) {
16208
+ if (!signals.mixExs) return false;
16209
+ return /\{\s*:phoenix\b(?!_)/.test(signals.mixExs);
16210
+ },
16211
+ async introspect(files, _rootDir) {
16212
+ if (files.length === 0) {
16213
+ return { conventions: {}, provenance: [], confidence: "none" };
16214
+ }
16215
+ let language;
16216
+ try {
16217
+ language = await loadGrammar("elixir");
16218
+ } catch (e2) {
16219
+ return { conventions: {}, provenance: [], confidence: "none" };
16220
+ }
16221
+ const parser = new Parser();
16222
+ parser.setLanguage(language);
16223
+ const routeMethods = /* @__PURE__ */ new Map();
16224
+ const scopePaths = /* @__PURE__ */ new Map();
16225
+ const routerModules = /* @__PURE__ */ new Map();
16226
+ try {
16227
+ for (const file of files) {
16228
+ const skip = isParsableSource(file.content, file.size);
16229
+ if (skip) {
16230
+ process.stderr.write(
16231
+ `[massu/ast] WARN: phoenix skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
16232
+ `
16233
+ );
16234
+ continue;
16235
+ }
16236
+ try {
16237
+ for (const hit of runQuery(parser, file.content, ROUTE_METHOD_QUERY3, "phoenix-route-method", file.path)) {
16238
+ const method = hit.captures.method;
16239
+ if (method && !routeMethods.has(method)) {
16240
+ routeMethods.set(method, { line: hit.line, file: file.path });
16241
+ }
16242
+ }
16243
+ for (const hit of runQuery(parser, file.content, SCOPE_PATH_QUERY, "phoenix-scope-path", file.path)) {
16244
+ const raw = hit.captures.scope_path;
16245
+ if (!raw) continue;
16246
+ const literal = raw.replace(/^["']/, "").replace(/["']$/, "");
16247
+ const base = extractPrefixBase5(literal);
16248
+ if (base && !scopePaths.has(base)) {
16249
+ scopePaths.set(base, { line: hit.line, file: file.path });
16250
+ }
16251
+ }
16252
+ for (const hit of runQuery(parser, file.content, ROUTER_MODULE_QUERY, "phoenix-router-module", file.path)) {
16253
+ const name2 = hit.captures.module_name;
16254
+ if (name2 && !routerModules.has(name2)) {
16255
+ routerModules.set(name2, { line: hit.line, file: file.path });
16256
+ }
16257
+ }
16258
+ } catch (e2) {
16259
+ if (e2 instanceof InvalidQueryError) {
16260
+ throw e2;
16261
+ }
16262
+ continue;
16263
+ }
16264
+ }
16265
+ } finally {
16266
+ try {
16267
+ parser.delete();
16268
+ } catch {
16269
+ }
16270
+ }
16271
+ const conventions = {};
16272
+ const provenance = [];
16273
+ if (routeMethods.size === 1) {
16274
+ const [name2, { line, file }] = routeMethods.entries().next().value;
16275
+ conventions.route_method = name2;
16276
+ provenance.push({ field: "route_method", sourceFile: file, line, query: "phoenix-route-method" });
16277
+ } else if (routeMethods.size >= 2) {
16278
+ const [name2, { line, file }] = routeMethods.entries().next().value;
16279
+ conventions.route_method = name2;
16280
+ provenance.push({ field: "route_method", sourceFile: file, line, query: "phoenix-route-method" });
16281
+ }
16282
+ if (scopePaths.size >= 1) {
16283
+ const [base, { line, file }] = scopePaths.entries().next().value;
16284
+ conventions.scope_prefix_base = base;
16285
+ provenance.push({ field: "scope_prefix_base", sourceFile: file, line, query: "phoenix-scope-path" });
16286
+ }
16287
+ if (routerModules.size >= 1) {
16288
+ const [name2, { line, file }] = routerModules.entries().next().value;
16289
+ conventions.router_module = name2;
16290
+ provenance.push({ field: "router_module", sourceFile: file, line, query: "phoenix-router-module" });
16291
+ }
16292
+ let confidence;
16293
+ if (Object.keys(conventions).length === 0) {
16294
+ confidence = "none";
16295
+ } else if (routeMethods.size === 1) {
16296
+ confidence = "high";
16297
+ } else if (routeMethods.size >= 2) {
16298
+ confidence = "low";
16299
+ } else {
16300
+ confidence = "medium";
16301
+ }
16302
+ return { conventions, provenance, confidence };
16303
+ }
16304
+ };
14614
16305
  }
14615
16306
  });
14616
16307
 
14617
- // src/detect/adapters/python-django.ts
14618
- var init_python_django = __esm({
14619
- "src/detect/adapters/python-django.ts"() {
16308
+ // src/detect/adapters/aspnet.ts
16309
+ function extractPrefixBase6(prefix3) {
16310
+ const stripped = prefix3.replace(/^\/+/, "");
16311
+ const firstSeg = stripped.split("/")[0];
16312
+ if (!firstSeg) return null;
16313
+ return "/" + firstSeg;
16314
+ }
16315
+ var MAP_VERB_QUERY, HTTP_ATTR_QUERY, ROUTE_ATTR_QUERY, CONTROLLER_CLASS_QUERY, aspnetAdapter;
16316
+ var init_aspnet = __esm({
16317
+ "src/detect/adapters/aspnet.ts"() {
14620
16318
  "use strict";
14621
16319
  init_tree_sitter();
14622
16320
  init_query_helpers();
14623
16321
  init_tree_sitter_loader();
14624
16322
  init_parse_guard();
16323
+ MAP_VERB_QUERY = `
16324
+ (invocation_expression
16325
+ function: (member_access_expression
16326
+ name: (identifier) @method (#match? @method "^Map(Get|Post|Put|Patch|Delete|Head|Options)$"))
16327
+ arguments: (argument_list
16328
+ .
16329
+ (argument (string_literal) @route_path)))
16330
+ `;
16331
+ HTTP_ATTR_QUERY = `
16332
+ (attribute
16333
+ name: (identifier) @attr_name (#match? @attr_name "^Http(Get|Post|Put|Patch|Delete|Head|Options)$"))
16334
+ `;
16335
+ ROUTE_ATTR_QUERY = `
16336
+ (attribute
16337
+ name: (identifier) @_attr_name (#eq? @_attr_name "Route")
16338
+ (attribute_argument_list
16339
+ (attribute_argument (string_literal) @route_template)))
16340
+ `;
16341
+ CONTROLLER_CLASS_QUERY = `
16342
+ (class_declaration
16343
+ name: (identifier) @class_name (#match? @class_name "Controller$"))
16344
+ `;
16345
+ aspnetAdapter = {
16346
+ id: "aspnet",
16347
+ languages: ["csharp"],
16348
+ matches(signals) {
16349
+ if (!signals.csproj) return false;
16350
+ if (/Sdk\s*=\s*["']Microsoft\.NET\.Sdk\.Web["']/i.test(signals.csproj)) return true;
16351
+ if (/Microsoft\.AspNetCore\.App/i.test(signals.csproj)) return true;
16352
+ return false;
16353
+ },
16354
+ async introspect(files, _rootDir) {
16355
+ if (files.length === 0) {
16356
+ return { conventions: {}, provenance: [], confidence: "none" };
16357
+ }
16358
+ let language;
16359
+ try {
16360
+ language = await loadGrammar("csharp");
16361
+ } catch (e2) {
16362
+ return { conventions: {}, provenance: [], confidence: "none" };
16363
+ }
16364
+ const parser = new Parser();
16365
+ parser.setLanguage(language);
16366
+ const routeMethods = /* @__PURE__ */ new Map();
16367
+ const prefixBases = /* @__PURE__ */ new Map();
16368
+ const controllerClasses = /* @__PURE__ */ new Map();
16369
+ try {
16370
+ for (const file of files) {
16371
+ const skip = isParsableSource(file.content, file.size);
16372
+ if (skip) {
16373
+ process.stderr.write(
16374
+ `[massu/ast] WARN: aspnet skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
16375
+ `
16376
+ );
16377
+ continue;
16378
+ }
16379
+ try {
16380
+ for (const hit of runQuery(parser, file.content, MAP_VERB_QUERY, "aspnet-map-verb", file.path)) {
16381
+ const methodRaw = hit.captures.method;
16382
+ if (!methodRaw) continue;
16383
+ const verb = methodRaw.replace(/^Map/, "");
16384
+ if (!routeMethods.has(verb)) {
16385
+ routeMethods.set(verb, { line: hit.line, file: file.path });
16386
+ }
16387
+ const pathRaw = hit.captures.route_path;
16388
+ if (pathRaw) {
16389
+ const literal = pathRaw.replace(/^["']/, "").replace(/["']$/, "");
16390
+ const base = extractPrefixBase6(literal);
16391
+ if (base && !prefixBases.has(base)) {
16392
+ prefixBases.set(base, { line: hit.line, file: file.path });
16393
+ }
16394
+ }
16395
+ }
16396
+ for (const hit of runQuery(parser, file.content, HTTP_ATTR_QUERY, "aspnet-http-attr", file.path)) {
16397
+ const attrRaw = hit.captures.attr_name;
16398
+ if (!attrRaw) continue;
16399
+ const verb = attrRaw.replace(/^Http/, "");
16400
+ if (!routeMethods.has(verb)) {
16401
+ routeMethods.set(verb, { line: hit.line, file: file.path });
16402
+ }
16403
+ }
16404
+ for (const hit of runQuery(parser, file.content, ROUTE_ATTR_QUERY, "aspnet-route-attr", file.path)) {
16405
+ const tplRaw = hit.captures.route_template;
16406
+ if (!tplRaw) continue;
16407
+ const literal = tplRaw.replace(/^["']/, "").replace(/["']$/, "");
16408
+ const base = extractPrefixBase6(literal);
16409
+ if (base && !prefixBases.has(base)) {
16410
+ prefixBases.set(base, { line: hit.line, file: file.path });
16411
+ }
16412
+ }
16413
+ for (const hit of runQuery(parser, file.content, CONTROLLER_CLASS_QUERY, "aspnet-controller-class", file.path)) {
16414
+ const name2 = hit.captures.class_name;
16415
+ if (name2 && !controllerClasses.has(name2)) {
16416
+ controllerClasses.set(name2, { line: hit.line, file: file.path });
16417
+ }
16418
+ }
16419
+ } catch (e2) {
16420
+ if (e2 instanceof InvalidQueryError) {
16421
+ throw e2;
16422
+ }
16423
+ continue;
16424
+ }
16425
+ }
16426
+ } finally {
16427
+ try {
16428
+ parser.delete();
16429
+ } catch {
16430
+ }
16431
+ }
16432
+ const conventions = {};
16433
+ const provenance = [];
16434
+ if (routeMethods.size === 1) {
16435
+ const [name2, { line, file }] = routeMethods.entries().next().value;
16436
+ conventions.route_method = name2;
16437
+ provenance.push({ field: "route_method", sourceFile: file, line, query: "aspnet-map-verb" });
16438
+ } else if (routeMethods.size >= 2) {
16439
+ const [name2, { line, file }] = routeMethods.entries().next().value;
16440
+ conventions.route_method = name2;
16441
+ provenance.push({ field: "route_method", sourceFile: file, line, query: "aspnet-map-verb" });
16442
+ }
16443
+ if (prefixBases.size >= 1) {
16444
+ const [base, { line, file }] = prefixBases.entries().next().value;
16445
+ conventions.route_prefix_base = base;
16446
+ provenance.push({ field: "route_prefix_base", sourceFile: file, line, query: "aspnet-route-prefix" });
16447
+ }
16448
+ if (controllerClasses.size >= 1) {
16449
+ const [name2, { line, file }] = controllerClasses.entries().next().value;
16450
+ conventions.controller_class = name2;
16451
+ provenance.push({ field: "controller_class", sourceFile: file, line, query: "aspnet-controller-class" });
16452
+ }
16453
+ let confidence;
16454
+ if (Object.keys(conventions).length === 0) {
16455
+ confidence = "none";
16456
+ } else if (routeMethods.size === 1) {
16457
+ confidence = "high";
16458
+ } else if (routeMethods.size >= 2) {
16459
+ confidence = "low";
16460
+ } else {
16461
+ confidence = "medium";
16462
+ }
16463
+ return { conventions, provenance, confidence };
16464
+ }
16465
+ };
14625
16466
  }
14626
16467
  });
14627
16468
 
14628
- // src/detect/adapters/nextjs-trpc.ts
14629
- var init_nextjs_trpc = __esm({
14630
- "src/detect/adapters/nextjs-trpc.ts"() {
16469
+ // src/detect/adapters/spring.ts
16470
+ function extractPrefixBase7(prefix3) {
16471
+ const stripped = prefix3.replace(/^\/+/, "");
16472
+ const firstSeg = stripped.split("/")[0];
16473
+ if (!firstSeg) return null;
16474
+ return "/" + firstSeg;
16475
+ }
16476
+ var HTTP_MAPPING_QUERY, HTTP_MAPPING_NO_ARGS_QUERY, REQUEST_MAPPING_QUERY, CONTROLLER_CLASS_QUERY2, springAdapter;
16477
+ var init_spring = __esm({
16478
+ "src/detect/adapters/spring.ts"() {
14631
16479
  "use strict";
14632
16480
  init_tree_sitter();
14633
16481
  init_query_helpers();
14634
16482
  init_tree_sitter_loader();
14635
16483
  init_parse_guard();
16484
+ HTTP_MAPPING_QUERY = `
16485
+ (annotation
16486
+ name: (identifier) @method (#match? @method "^(Get|Post|Put|Patch|Delete|Head|Options)Mapping$")
16487
+ arguments: (annotation_argument_list
16488
+ (string_literal) @route_path))
16489
+ `;
16490
+ HTTP_MAPPING_NO_ARGS_QUERY = `
16491
+ (marker_annotation
16492
+ name: (identifier) @method (#match? @method "^(Get|Post|Put|Patch|Delete|Head|Options)Mapping$"))
16493
+ `;
16494
+ REQUEST_MAPPING_QUERY = `
16495
+ (annotation
16496
+ name: (identifier) @_name (#eq? @_name "RequestMapping")
16497
+ arguments: (annotation_argument_list
16498
+ (string_literal) @route_template))
16499
+ `;
16500
+ CONTROLLER_CLASS_QUERY2 = `
16501
+ (class_declaration
16502
+ (modifiers
16503
+ (marker_annotation
16504
+ name: (identifier) @_anno (#match? @_anno "^(RestController|Controller)$")))
16505
+ name: (identifier) @class_name)
16506
+
16507
+ (class_declaration
16508
+ (modifiers
16509
+ (annotation
16510
+ name: (identifier) @_anno (#match? @_anno "^(RestController|Controller)$")))
16511
+ name: (identifier) @class_name)
16512
+ `;
16513
+ springAdapter = {
16514
+ id: "spring",
16515
+ languages: ["java"],
16516
+ matches(signals) {
16517
+ if (signals.pomXml && /\bspring-boot-starter[\w-]*\b/.test(signals.pomXml)) {
16518
+ return true;
16519
+ }
16520
+ if (signals.gradleBuild && /\bspring-boot-starter[\w-]*\b/.test(signals.gradleBuild)) {
16521
+ return true;
16522
+ }
16523
+ if (signals.pomXml && /\borg\.springframework\b/.test(signals.pomXml)) {
16524
+ return true;
16525
+ }
16526
+ if (signals.gradleBuild && /\borg\.springframework\b/.test(signals.gradleBuild)) {
16527
+ return true;
16528
+ }
16529
+ return false;
16530
+ },
16531
+ async introspect(files, _rootDir) {
16532
+ if (files.length === 0) {
16533
+ return { conventions: {}, provenance: [], confidence: "none" };
16534
+ }
16535
+ let language;
16536
+ try {
16537
+ language = await loadGrammar("java");
16538
+ } catch (e2) {
16539
+ return { conventions: {}, provenance: [], confidence: "none" };
16540
+ }
16541
+ const parser = new Parser();
16542
+ parser.setLanguage(language);
16543
+ const routeMethods = /* @__PURE__ */ new Map();
16544
+ const prefixBases = /* @__PURE__ */ new Map();
16545
+ const controllerClasses = /* @__PURE__ */ new Map();
16546
+ try {
16547
+ for (const file of files) {
16548
+ const skip = isParsableSource(file.content, file.size);
16549
+ if (skip) {
16550
+ process.stderr.write(
16551
+ `[massu/ast] WARN: spring skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
16552
+ `
16553
+ );
16554
+ continue;
16555
+ }
16556
+ try {
16557
+ for (const hit of runQuery(parser, file.content, HTTP_MAPPING_QUERY, "spring-http-mapping", file.path)) {
16558
+ const methodRaw = hit.captures.method;
16559
+ if (!methodRaw) continue;
16560
+ const verb = methodRaw.replace(/Mapping$/, "");
16561
+ if (!routeMethods.has(verb)) {
16562
+ routeMethods.set(verb, { line: hit.line, file: file.path });
16563
+ }
16564
+ }
16565
+ for (const hit of runQuery(parser, file.content, HTTP_MAPPING_NO_ARGS_QUERY, "spring-http-mapping-marker", file.path)) {
16566
+ const methodRaw = hit.captures.method;
16567
+ if (!methodRaw) continue;
16568
+ const verb = methodRaw.replace(/Mapping$/, "");
16569
+ if (!routeMethods.has(verb)) {
16570
+ routeMethods.set(verb, { line: hit.line, file: file.path });
16571
+ }
16572
+ }
16573
+ for (const hit of runQuery(parser, file.content, REQUEST_MAPPING_QUERY, "spring-request-mapping", file.path)) {
16574
+ const tplRaw = hit.captures.route_template;
16575
+ if (!tplRaw) continue;
16576
+ const literal = tplRaw.replace(/^["']/, "").replace(/["']$/, "");
16577
+ const base = extractPrefixBase7(literal);
16578
+ if (base && !prefixBases.has(base)) {
16579
+ prefixBases.set(base, { line: hit.line, file: file.path });
16580
+ }
16581
+ }
16582
+ for (const hit of runQuery(parser, file.content, CONTROLLER_CLASS_QUERY2, "spring-controller-class", file.path)) {
16583
+ const name2 = hit.captures.class_name;
16584
+ if (name2 && !controllerClasses.has(name2)) {
16585
+ controllerClasses.set(name2, { line: hit.line, file: file.path });
16586
+ }
16587
+ }
16588
+ } catch (e2) {
16589
+ if (e2 instanceof InvalidQueryError) {
16590
+ throw e2;
16591
+ }
16592
+ continue;
16593
+ }
16594
+ }
16595
+ } finally {
16596
+ try {
16597
+ parser.delete();
16598
+ } catch {
16599
+ }
16600
+ }
16601
+ const conventions = {};
16602
+ const provenance = [];
16603
+ if (routeMethods.size === 1) {
16604
+ const [name2, { line, file }] = routeMethods.entries().next().value;
16605
+ conventions.route_method = name2;
16606
+ provenance.push({ field: "route_method", sourceFile: file, line, query: "spring-http-mapping" });
16607
+ } else if (routeMethods.size >= 2) {
16608
+ const [name2, { line, file }] = routeMethods.entries().next().value;
16609
+ conventions.route_method = name2;
16610
+ provenance.push({ field: "route_method", sourceFile: file, line, query: "spring-http-mapping" });
16611
+ }
16612
+ if (prefixBases.size >= 1) {
16613
+ const [base, { line, file }] = prefixBases.entries().next().value;
16614
+ conventions.route_prefix_base = base;
16615
+ provenance.push({ field: "route_prefix_base", sourceFile: file, line, query: "spring-request-mapping" });
16616
+ }
16617
+ if (controllerClasses.size >= 1) {
16618
+ const [name2, { line, file }] = controllerClasses.entries().next().value;
16619
+ conventions.controller_class = name2;
16620
+ provenance.push({ field: "controller_class", sourceFile: file, line, query: "spring-controller-class" });
16621
+ }
16622
+ let confidence;
16623
+ if (Object.keys(conventions).length === 0) {
16624
+ confidence = "none";
16625
+ } else if (routeMethods.size === 1) {
16626
+ confidence = "high";
16627
+ } else if (routeMethods.size >= 2) {
16628
+ confidence = "low";
16629
+ } else {
16630
+ confidence = "medium";
16631
+ }
16632
+ return { conventions, provenance, confidence };
16633
+ }
16634
+ };
14636
16635
  }
14637
16636
  });
14638
16637
 
14639
- // src/detect/adapters/swift-swiftui.ts
14640
- var init_swift_swiftui = __esm({
14641
- "src/detect/adapters/swift-swiftui.ts"() {
16638
+ // src/detect/adapters/file-sampler.ts
16639
+ var file_sampler_exports = {};
16640
+ __export(file_sampler_exports, {
16641
+ SAMPLE_EXTENSIONS: () => SAMPLE_EXTENSIONS,
16642
+ SAMPLE_TEST_FILE_PATTERNS: () => SAMPLE_TEST_FILE_PATTERNS,
16643
+ sampleFilesForAdapter: () => sampleFilesForAdapter
16644
+ });
16645
+ import { readdirSync as readdirSync9, readFileSync as readFileSync9, lstatSync as lstatSync4 } from "node:fs";
16646
+ import { join as join9, extname } from "node:path";
16647
+ function sampleFilesForAdapter(adapter, projectRoot, detection, options = {}) {
16648
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
16649
+ const maxFiles = options.maxFilesPerAdapter ?? DEFAULT_MAX_FILES;
16650
+ const out2 = [];
16651
+ const seen = /* @__PURE__ */ new Set();
16652
+ for (const lang of adapter.languages) {
16653
+ if (out2.length >= maxFiles) break;
16654
+ const exts = SAMPLE_EXTENSIONS[lang];
16655
+ if (!exts || exts.length === 0) continue;
16656
+ const testPatterns = SAMPLE_TEST_FILE_PATTERNS[lang] ?? [];
16657
+ const langKey = lang;
16658
+ const langDetection = detection.sourceDirs[langKey];
16659
+ const candidateDirs = [];
16660
+ if (langDetection?.source_dirs && langDetection.source_dirs.length > 0) {
16661
+ candidateDirs.push(...langDetection.source_dirs.map((d2) => join9(projectRoot, d2)));
16662
+ } else {
16663
+ candidateDirs.push(projectRoot);
16664
+ }
16665
+ for (const dir of candidateDirs) {
16666
+ if (out2.length >= maxFiles) break;
16667
+ walkDir(dir, exts, testPatterns, lang, maxDepth, 0, out2, seen, maxFiles);
16668
+ }
16669
+ }
16670
+ return out2;
16671
+ }
16672
+ function walkDir(dir, exts, testPatterns, lang, maxDepth, curDepth, out2, seen, maxFiles) {
16673
+ if (curDepth > maxDepth) return;
16674
+ if (out2.length >= maxFiles) return;
16675
+ let entries;
16676
+ try {
16677
+ entries = readdirSync9(dir);
16678
+ } catch {
16679
+ return;
16680
+ }
16681
+ for (const entry of entries) {
16682
+ if (out2.length >= maxFiles) return;
16683
+ if (entry.startsWith(".")) continue;
16684
+ if (IGNORED_DIRS3.has(entry)) continue;
16685
+ const fullPath = join9(dir, entry);
16686
+ let st;
16687
+ try {
16688
+ st = lstatSync4(fullPath);
16689
+ } catch {
16690
+ continue;
16691
+ }
16692
+ if (st.isSymbolicLink()) continue;
16693
+ if (st.isDirectory()) {
16694
+ walkDir(fullPath, exts, testPatterns, lang, maxDepth, curDepth + 1, out2, seen, maxFiles);
16695
+ continue;
16696
+ }
16697
+ if (!st.isFile()) continue;
16698
+ if (st.size > MAX_AST_FILE_BYTES) continue;
16699
+ const ext = extname(entry).slice(1);
16700
+ if (!exts.includes(ext)) continue;
16701
+ if (testPatterns.some((p19) => p19.test(fullPath))) continue;
16702
+ if (seen.has(fullPath)) continue;
16703
+ seen.add(fullPath);
16704
+ let content;
16705
+ try {
16706
+ content = readFileSync9(fullPath, "utf-8");
16707
+ } catch {
16708
+ continue;
16709
+ }
16710
+ out2.push({
16711
+ path: fullPath,
16712
+ content,
16713
+ language: lang,
16714
+ size: st.size
16715
+ });
16716
+ }
16717
+ }
16718
+ var SAMPLE_EXTENSIONS, SAMPLE_TEST_FILE_PATTERNS, IGNORED_DIRS3, DEFAULT_MAX_DEPTH, DEFAULT_MAX_FILES;
16719
+ var init_file_sampler = __esm({
16720
+ "src/detect/adapters/file-sampler.ts"() {
14642
16721
  "use strict";
14643
- init_tree_sitter();
14644
- init_query_helpers();
14645
- init_tree_sitter_loader();
14646
16722
  init_parse_guard();
16723
+ SAMPLE_EXTENSIONS = {
16724
+ python: ["py"],
16725
+ typescript: ["ts", "tsx"],
16726
+ javascript: ["js", "jsx", "mjs", "cjs"],
16727
+ swift: ["swift"],
16728
+ rust: ["rs"],
16729
+ go: ["go"],
16730
+ ruby: ["rb"],
16731
+ php: ["php"],
16732
+ java: ["java", "kt"],
16733
+ kotlin: ["kt", "kts"],
16734
+ elixir: ["ex", "exs"],
16735
+ erlang: ["erl", "hrl"],
16736
+ csharp: ["cs"],
16737
+ cpp: ["cpp", "cc", "cxx", "h", "hpp"],
16738
+ haskell: ["hs", "lhs"],
16739
+ ocaml: ["ml", "mli"]
16740
+ };
16741
+ SAMPLE_TEST_FILE_PATTERNS = {
16742
+ python: [/_test\.py$/, /test_[^/]*\.py$/, /\/tests?\//],
16743
+ typescript: [/\.test\.tsx?$/, /\.spec\.tsx?$/, /\/__tests__\//],
16744
+ javascript: [/\.test\.[mc]?jsx?$/, /\.spec\.[mc]?jsx?$/, /\/__tests__\//],
16745
+ swift: [/Tests\//],
16746
+ rust: [/tests\/.*\.rs$/],
16747
+ go: [/_test\.go$/],
16748
+ ruby: [/_spec\.rb$/, /_test\.rb$/, /\/spec\//],
16749
+ php: [/Test\.php$/, /\/tests?\//i],
16750
+ java: [/Test[^/]*\.(java|kt)$/, /[^/]*Test\.(java|kt)$/, /\/test\//],
16751
+ kotlin: [/Test[^/]*\.kt$/, /[^/]*Test\.kt$/],
16752
+ elixir: [/_test\.exs$/, /\/test\//],
16753
+ erlang: [/_SUITE\.erl$/],
16754
+ csharp: [/Tests?\.cs$/, /\.Tests?\//],
16755
+ cpp: [/_test\.(cpp|cc)$/i, /\/tests?\//i],
16756
+ haskell: [/Spec\.hs$/, /\/test\//],
16757
+ ocaml: [/_test\.ml$/, /\/test\//]
16758
+ };
16759
+ IGNORED_DIRS3 = /* @__PURE__ */ new Set([
16760
+ "node_modules",
16761
+ ".venv",
16762
+ "venv",
16763
+ "__pycache__",
16764
+ "dist",
16765
+ "build",
16766
+ ".build",
16767
+ "target",
16768
+ ".next",
16769
+ ".nuxt",
16770
+ "coverage",
16771
+ ".git",
16772
+ ".massu",
16773
+ ".turbo",
16774
+ ".cache",
16775
+ ".pytest_cache",
16776
+ ".mypy_cache",
16777
+ "DerivedData",
16778
+ "Pods",
16779
+ "_build",
16780
+ "deps",
16781
+ "priv",
16782
+ "cover",
16783
+ ".elixir_ls",
16784
+ // Elixir/Phoenix
16785
+ "bin",
16786
+ "obj",
16787
+ ".vs",
16788
+ "packages",
16789
+ "publish",
16790
+ "TestResults"
16791
+ // .NET
16792
+ ]);
16793
+ DEFAULT_MAX_DEPTH = 3;
16794
+ DEFAULT_MAX_FILES = 50;
14647
16795
  }
14648
16796
  });
14649
16797
 
14650
16798
  // src/detect/codebase-introspector.ts
16799
+ var codebase_introspector_exports = {};
16800
+ __export(codebase_introspector_exports, {
16801
+ introspect: () => introspect,
16802
+ introspectAsync: () => introspectAsync
16803
+ });
14651
16804
  function introspect(detection, projectRoot) {
14652
16805
  const out2 = {};
14653
16806
  const languages = Array.from(
@@ -14667,6 +16820,35 @@ function introspect(detection, projectRoot) {
14667
16820
  }
14668
16821
  return out2;
14669
16822
  }
16823
+ async function introspectAsync(detection, projectRoot) {
16824
+ const out2 = introspect(detection, projectRoot);
16825
+ const signals = buildDetectionSignals(projectRoot);
16826
+ const { sampleFilesForAdapter: sampleFilesForAdapter2 } = await Promise.resolve().then(() => (init_file_sampler(), file_sampler_exports));
16827
+ let merged;
16828
+ try {
16829
+ merged = await runAdapters(FIRST_PARTY_ADAPTERS, projectRoot, signals, {
16830
+ sampleFiles: async (adapter, root) => {
16831
+ return sampleFilesForAdapter2(adapter, root, detection);
16832
+ }
16833
+ });
16834
+ } catch {
16835
+ return out2;
16836
+ }
16837
+ for (const [adapterId, resolved] of Object.entries(merged.byAdapter)) {
16838
+ if (resolved.confidence === "none") continue;
16839
+ out2[adapterId] = serializeAdapterBlock(resolved);
16840
+ }
16841
+ return out2;
16842
+ }
16843
+ function serializeAdapterBlock(r2) {
16844
+ const block = { ...r2.conventions };
16845
+ if (Object.keys(r2._provenance).length > 0) {
16846
+ block._provenance = r2._provenance;
16847
+ }
16848
+ block._confidence = r2.confidence;
16849
+ return block;
16850
+ }
16851
+ var FIRST_PARTY_ADAPTERS;
14670
16852
  var init_codebase_introspector = __esm({
14671
16853
  "src/detect/codebase-introspector.ts"() {
14672
16854
  "use strict";
@@ -14676,6 +16858,24 @@ var init_codebase_introspector = __esm({
14676
16858
  init_python_django();
14677
16859
  init_nextjs_trpc();
14678
16860
  init_swift_swiftui();
16861
+ init_python_flask();
16862
+ init_go_chi();
16863
+ init_rails();
16864
+ init_phoenix();
16865
+ init_aspnet();
16866
+ init_spring();
16867
+ FIRST_PARTY_ADAPTERS = [
16868
+ pythonFastApiAdapter,
16869
+ pythonDjangoAdapter,
16870
+ pythonFlaskAdapter,
16871
+ nextjsTrpcAdapter,
16872
+ swiftSwiftUiAdapter,
16873
+ goChiAdapter,
16874
+ railsAdapter,
16875
+ phoenixAdapter,
16876
+ aspnetAdapter,
16877
+ springAdapter
16878
+ ];
14679
16879
  }
14680
16880
  });
14681
16881
 
@@ -14741,7 +16941,7 @@ var init_detect = __esm({
14741
16941
  });
14742
16942
 
14743
16943
  // src/detect/drift.ts
14744
- import { createHash as createHash2 } from "crypto";
16944
+ import { createHash as createHash3 } from "crypto";
14745
16945
  function summarizeDetection(det) {
14746
16946
  const languages = Array.from(new Set(det.manifests.map((m3) => m3.language))).sort();
14747
16947
  const frameworks = {};
@@ -14772,7 +16972,7 @@ function summarizeDetection(det) {
14772
16972
  function computeFingerprint(det) {
14773
16973
  const data = summarizeDetection(det);
14774
16974
  const stable = JSON.stringify(data, Object.keys(data).sort());
14775
- return createHash2("sha256").update(stable).digest("hex");
16975
+ return createHash3("sha256").update(stable).digest("hex");
14776
16976
  }
14777
16977
  function stringOf(v3) {
14778
16978
  if (typeof v3 === "string") return v3;
@@ -15931,10 +18131,10 @@ __export(init_exports, {
15931
18131
  validateWrittenConfig: () => validateWrittenConfig,
15932
18132
  writeConfigAtomic: () => writeConfigAtomic
15933
18133
  });
15934
- import { closeSync as closeSync2, existsSync as existsSync9, fsyncSync as fsyncSync2, openSync as openSync2, readFileSync as readFileSync7, writeFileSync, writeSync as writeSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync7, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync5, chmodSync } from "fs";
15935
- import { resolve as resolve6, basename as basename3, dirname as dirname4 } from "path";
18134
+ import { closeSync as closeSync2, existsSync as existsSync10, fsyncSync as fsyncSync2, openSync as openSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync2, writeSync as writeSync2, mkdirSync as mkdirSync4, readdirSync as readdirSync10, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync7, chmodSync as chmodSync2 } from "fs";
18135
+ import { resolve as resolve6, basename as basename4, dirname as dirname5 } from "path";
15936
18136
  import { fileURLToPath as fileURLToPath2 } from "url";
15937
- import { homedir as homedir2 } from "os";
18137
+ import { homedir as homedir3 } from "os";
15938
18138
  import { stringify as yamlStringify, parse as yamlParse } from "yaml";
15939
18139
  function detectFramework(projectRoot) {
15940
18140
  const result = {
@@ -15944,9 +18144,9 @@ function detectFramework(projectRoot) {
15944
18144
  ui: "none"
15945
18145
  };
15946
18146
  const pkgPath = resolve6(projectRoot, "package.json");
15947
- if (!existsSync9(pkgPath)) return result;
18147
+ if (!existsSync10(pkgPath)) return result;
15948
18148
  try {
15949
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
18149
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
15950
18150
  const allDeps = {
15951
18151
  ...pkg.dependencies,
15952
18152
  ...pkg.devDependencies
@@ -15980,7 +18180,7 @@ function detectPython(projectRoot) {
15980
18180
  alembicDir: null
15981
18181
  };
15982
18182
  const markers = ["pyproject.toml", "setup.py", "requirements.txt", "Pipfile"];
15983
- const hasMarker = markers.some((m3) => existsSync9(resolve6(projectRoot, m3)));
18183
+ const hasMarker = markers.some((m3) => existsSync10(resolve6(projectRoot, m3)));
15984
18184
  if (!hasMarker) return result;
15985
18185
  result.detected = true;
15986
18186
  const depFiles = [
@@ -15991,34 +18191,34 @@ function detectPython(projectRoot) {
15991
18191
  ];
15992
18192
  for (const { file } of depFiles) {
15993
18193
  const filePath = resolve6(projectRoot, file);
15994
- if (existsSync9(filePath)) {
18194
+ if (existsSync10(filePath)) {
15995
18195
  try {
15996
- const content = readFileSync7(filePath, "utf-8").toLowerCase();
18196
+ const content = readFileSync10(filePath, "utf-8").toLowerCase();
15997
18197
  if (content.includes("fastapi")) result.hasFastapi = true;
15998
18198
  if (content.includes("sqlalchemy")) result.hasSqlalchemy = true;
15999
18199
  } catch {
16000
18200
  }
16001
18201
  }
16002
18202
  }
16003
- if (existsSync9(resolve6(projectRoot, "alembic.ini"))) {
18203
+ if (existsSync10(resolve6(projectRoot, "alembic.ini"))) {
16004
18204
  result.hasAlembic = true;
16005
- if (existsSync9(resolve6(projectRoot, "alembic"))) {
18205
+ if (existsSync10(resolve6(projectRoot, "alembic"))) {
16006
18206
  result.alembicDir = "alembic";
16007
18207
  }
16008
- } else if (existsSync9(resolve6(projectRoot, "alembic"))) {
18208
+ } else if (existsSync10(resolve6(projectRoot, "alembic"))) {
16009
18209
  result.hasAlembic = true;
16010
18210
  result.alembicDir = "alembic";
16011
18211
  }
16012
18212
  const candidateRoots = ["app", "src", "backend", "api"];
16013
18213
  for (const candidate of candidateRoots) {
16014
18214
  const candidatePath = resolve6(projectRoot, candidate);
16015
- if (existsSync9(candidatePath) && existsSync9(resolve6(candidatePath, "__init__.py"))) {
18215
+ if (existsSync10(candidatePath) && existsSync10(resolve6(candidatePath, "__init__.py"))) {
16016
18216
  result.root = candidate;
16017
18217
  break;
16018
18218
  }
16019
- if (existsSync9(candidatePath)) {
18219
+ if (existsSync10(candidatePath)) {
16020
18220
  try {
16021
- const files = readdirSync7(candidatePath);
18221
+ const files = readdirSync10(candidatePath);
16022
18222
  if (files.some((f2) => f2.endsWith(".py"))) {
16023
18223
  result.root = candidate;
16024
18224
  break;
@@ -16037,10 +18237,10 @@ function generateConfig(projectRoot, framework) {
16037
18237
  "[@massu/core] generateConfig() is deprecated since 1.2.1 \u2014 use buildConfigFromDetection instead. It cannot produce valid configs for monorepos."
16038
18238
  );
16039
18239
  const configPath = resolve6(projectRoot, "massu.config.yaml");
16040
- if (existsSync9(configPath)) {
18240
+ if (existsSync10(configPath)) {
16041
18241
  return false;
16042
18242
  }
16043
- const projectName = basename3(projectRoot);
18243
+ const projectName = basename4(projectRoot);
16044
18244
  const config = {
16045
18245
  project: {
16046
18246
  name: projectName,
@@ -16083,7 +18283,7 @@ function generateConfig(projectRoot, framework) {
16083
18283
  # Documentation: https://massu.ai/docs/getting-started/configuration
16084
18284
 
16085
18285
  ${yamlStringify(config)}`;
16086
- writeFileSync(configPath, yamlContent, "utf-8");
18286
+ writeFileSync2(configPath, yamlContent, "utf-8");
16087
18287
  return true;
16088
18288
  }
16089
18289
  function monorepoCommonRoot(packages) {
@@ -16105,7 +18305,7 @@ function buildConfigFromDetection(opts) {
16105
18305
  if (!detection) {
16106
18306
  throw new Error("buildConfigFromDetection requires a detection result");
16107
18307
  }
16108
- const projectName = opts.projectName ?? basename3(projectRoot);
18308
+ const projectName = opts.projectName ?? basename4(projectRoot);
16109
18309
  const languages = Array.from(
16110
18310
  new Set(detection.manifests.map((m3) => m3.language))
16111
18311
  );
@@ -16226,7 +18426,7 @@ function buildConfigFromDetection(opts) {
16226
18426
  };
16227
18427
  if (pyFw?.framework) pythonBlock.framework = pyFw.framework;
16228
18428
  if (pyFw?.orm) pythonBlock.orm = pyFw.orm;
16229
- if (existsSync9(resolve6(projectRoot, "alembic.ini")) || existsSync9(resolve6(projectRoot, "alembic"))) {
18429
+ if (existsSync10(resolve6(projectRoot, "alembic.ini")) || existsSync10(resolve6(projectRoot, "alembic"))) {
16230
18430
  pythonBlock.alembic_dir = "alembic";
16231
18431
  }
16232
18432
  config.python = pythonBlock;
@@ -16251,10 +18451,10 @@ function applyVariantTemplate(config, templatesDir) {
16251
18451
  }
16252
18452
  if (templateId === null) return config;
16253
18453
  const templatePath = resolve6(templatesDir, templateId, "massu.config.yaml");
16254
- if (!existsSync9(templatePath)) return config;
18454
+ if (!existsSync10(templatePath)) return config;
16255
18455
  let template;
16256
18456
  try {
16257
- template = yamlParse(readFileSync7(templatePath, "utf-8"));
18457
+ template = yamlParse(readFileSync10(templatePath, "utf-8"));
16258
18458
  } catch {
16259
18459
  return config;
16260
18460
  }
@@ -16313,15 +18513,15 @@ ${yamlStringify(config)}`;
16313
18513
  function writeConfigAtomic(configPath, content) {
16314
18514
  const tmpPath = `${configPath}.tmp`;
16315
18515
  let existingMode;
16316
- if (existsSync9(configPath)) {
18516
+ if (existsSync10(configPath)) {
16317
18517
  try {
16318
- existingMode = statSync5(configPath).mode;
18518
+ existingMode = statSync7(configPath).mode;
16319
18519
  } catch {
16320
18520
  existingMode = void 0;
16321
18521
  }
16322
18522
  }
16323
18523
  try {
16324
- mkdirSync3(dirname4(configPath), { recursive: true });
18524
+ mkdirSync4(dirname5(configPath), { recursive: true });
16325
18525
  const fd = openSync2(tmpPath, "w", 420);
16326
18526
  try {
16327
18527
  const buf = Buffer.from(content, "utf-8");
@@ -16334,16 +18534,16 @@ function writeConfigAtomic(configPath, content) {
16334
18534
  if (parsed === null || typeof parsed !== "object") {
16335
18535
  throw new Error("Generated config is not a valid YAML object");
16336
18536
  }
16337
- renameSync2(tmpPath, configPath);
18537
+ renameSync3(tmpPath, configPath);
16338
18538
  if (existingMode !== void 0) {
16339
18539
  try {
16340
- chmodSync(configPath, existingMode);
18540
+ chmodSync2(configPath, existingMode);
16341
18541
  } catch {
16342
18542
  }
16343
18543
  }
16344
18544
  return { validated: true };
16345
18545
  } catch (err2) {
16346
- if (existsSync9(tmpPath)) {
18546
+ if (existsSync10(tmpPath)) {
16347
18547
  try {
16348
18548
  rmSync2(tmpPath, { force: true });
16349
18549
  } catch {
@@ -16354,8 +18554,8 @@ function writeConfigAtomic(configPath, content) {
16354
18554
  }
16355
18555
  function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16356
18556
  try {
16357
- if (!existsSync9(configPath)) return "Config file does not exist after write";
16358
- const content = readFileSync7(configPath, "utf-8");
18557
+ if (!existsSync10(configPath)) return "Config file does not exist after write";
18558
+ const content = readFileSync10(configPath, "utf-8");
16359
18559
  const parsed = yamlParse(content);
16360
18560
  if (parsed === null || typeof parsed !== "object") {
16361
18561
  return "Config is not a valid YAML object";
@@ -16376,7 +18576,7 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16376
18576
  const src = cfg.paths.source;
16377
18577
  if (src && src !== ".") {
16378
18578
  const srcAbs = resolve6(projectRoot, src);
16379
- if (!existsSync9(srcAbs)) {
18579
+ if (!existsSync10(srcAbs)) {
16380
18580
  return `paths.source '${src}' does not exist on disk`;
16381
18581
  }
16382
18582
  }
@@ -16387,7 +18587,7 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16387
18587
  for (const d2 of rawDirs) {
16388
18588
  if (typeof d2 !== "string" || d2 === ".") continue;
16389
18589
  const abs = resolve6(projectRoot, d2);
16390
- if (!existsSync9(abs)) {
18590
+ if (!existsSync10(abs)) {
16391
18591
  return `framework.languages.${lang}.source_dirs '${d2}' does not exist on disk`;
16392
18592
  }
16393
18593
  }
@@ -16396,7 +18596,7 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16396
18596
  if (Array.isArray(mRoots)) {
16397
18597
  for (const r2 of mRoots) {
16398
18598
  if (typeof r2 !== "string" || r2 === ".") continue;
16399
- if (!existsSync9(resolve6(projectRoot, r2))) {
18599
+ if (!existsSync10(resolve6(projectRoot, r2))) {
16400
18600
  return `paths.monorepo_roots '${r2}' does not exist on disk`;
16401
18601
  }
16402
18602
  }
@@ -16442,7 +18642,7 @@ function resolveTemplatesDir() {
16442
18642
  resolve6(__dirname2, "../../../templates")
16443
18643
  ];
16444
18644
  for (const c2 of candidates) {
16445
- if (existsSync9(c2)) return c2;
18645
+ if (existsSync10(c2)) return c2;
16446
18646
  }
16447
18647
  return null;
16448
18648
  }
@@ -16452,13 +18652,13 @@ function copyTemplateConfig(templateName, targetPath, projectName) {
16452
18652
  return { success: false, error: `Templates directory not found (looked in node_modules and dist/src)` };
16453
18653
  }
16454
18654
  const srcPath = resolve6(templatesDir, templateName, "massu.config.yaml");
16455
- if (!existsSync9(srcPath)) {
18655
+ if (!existsSync10(srcPath)) {
16456
18656
  return { success: false, error: `Template '${templateName}' not found at ${srcPath}` };
16457
18657
  }
16458
18658
  try {
16459
- let content = readFileSync7(srcPath, "utf-8");
18659
+ let content = readFileSync10(srcPath, "utf-8");
16460
18660
  content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
16461
- writeFileSync(targetPath, content, "utf-8");
18661
+ writeFileSync2(targetPath, content, "utf-8");
16462
18662
  return { success: true };
16463
18663
  } catch (err2) {
16464
18664
  return { success: false, error: err2 instanceof Error ? err2.message : String(err2) };
@@ -16467,9 +18667,9 @@ function copyTemplateConfig(templateName, targetPath, projectName) {
16467
18667
  function registerMcpServer(projectRoot) {
16468
18668
  const mcpPath = resolve6(projectRoot, ".mcp.json");
16469
18669
  let existing = {};
16470
- if (existsSync9(mcpPath)) {
18670
+ if (existsSync10(mcpPath)) {
16471
18671
  try {
16472
- existing = JSON.parse(readFileSync7(mcpPath, "utf-8"));
18672
+ existing = JSON.parse(readFileSync10(mcpPath, "utf-8"));
16473
18673
  } catch {
16474
18674
  existing = {};
16475
18675
  }
@@ -16484,17 +18684,17 @@ function registerMcpServer(projectRoot) {
16484
18684
  args: ["-y", "@massu/core"]
16485
18685
  };
16486
18686
  existing.mcpServers = servers;
16487
- writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
18687
+ writeFileSync2(mcpPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
16488
18688
  return true;
16489
18689
  }
16490
18690
  function resolveHooksDir() {
16491
18691
  const cwd = process.cwd();
16492
18692
  const nodeModulesPath = resolve6(cwd, "node_modules/@massu/core/dist/hooks");
16493
- if (existsSync9(nodeModulesPath)) {
18693
+ if (existsSync10(nodeModulesPath)) {
16494
18694
  return "node_modules/@massu/core/dist/hooks";
16495
18695
  }
16496
18696
  const localPath = resolve6(__dirname2, "../dist/hooks");
16497
- if (existsSync9(localPath)) {
18697
+ if (existsSync10(localPath)) {
16498
18698
  return localPath;
16499
18699
  }
16500
18700
  return "node_modules/@massu/core/dist/hooks";
@@ -16588,13 +18788,13 @@ function installHooks(projectRoot) {
16588
18788
  }
16589
18789
  const claudeDir = resolve6(projectRoot, claudeDirName);
16590
18790
  const settingsPath = resolve6(claudeDir, "settings.local.json");
16591
- if (!existsSync9(claudeDir)) {
16592
- mkdirSync3(claudeDir, { recursive: true });
18791
+ if (!existsSync10(claudeDir)) {
18792
+ mkdirSync4(claudeDir, { recursive: true });
16593
18793
  }
16594
18794
  let settings = {};
16595
- if (existsSync9(settingsPath)) {
18795
+ if (existsSync10(settingsPath)) {
16596
18796
  try {
16597
- settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
18797
+ settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
16598
18798
  } catch {
16599
18799
  settings = {};
16600
18800
  }
@@ -16608,21 +18808,21 @@ function installHooks(projectRoot) {
16608
18808
  }
16609
18809
  }
16610
18810
  settings.hooks = hooksConfig;
16611
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
18811
+ writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
16612
18812
  return { installed: true, count: hookCount };
16613
18813
  }
16614
18814
  function initMemoryDir(projectRoot) {
16615
18815
  const encodedRoot = "-" + projectRoot.replace(/\//g, "-");
16616
- const memoryDir = resolve6(homedir2(), `.claude/projects/${encodedRoot}/memory`);
18816
+ const memoryDir = resolve6(homedir3(), `.claude/projects/${encodedRoot}/memory`);
16617
18817
  let created = false;
16618
- if (!existsSync9(memoryDir)) {
16619
- mkdirSync3(memoryDir, { recursive: true });
18818
+ if (!existsSync10(memoryDir)) {
18819
+ mkdirSync4(memoryDir, { recursive: true });
16620
18820
  created = true;
16621
18821
  }
16622
18822
  const memoryMdPath = resolve6(memoryDir, "MEMORY.md");
16623
18823
  let memoryMdCreated = false;
16624
- if (!existsSync9(memoryMdPath)) {
16625
- const projectName = basename3(projectRoot);
18824
+ if (!existsSync10(memoryMdPath)) {
18825
+ const projectName = basename4(projectRoot);
16626
18826
  const memoryContent = `# ${projectName} - Massu Memory
16627
18827
 
16628
18828
  ## Key Learnings
@@ -16637,7 +18837,7 @@ function initMemoryDir(projectRoot) {
16637
18837
  ## File Index
16638
18838
  <!-- Significant files and directories -->
16639
18839
  `;
16640
- writeFileSync(memoryMdPath, memoryContent, "utf-8");
18840
+ writeFileSync2(memoryMdPath, memoryContent, "utf-8");
16641
18841
  memoryMdCreated = true;
16642
18842
  }
16643
18843
  return { created, memoryMdCreated };
@@ -16649,6 +18849,7 @@ function parseInitArgs(argv) {
16649
18849
  if (a2 === "--ci") opts.ci = true;
16650
18850
  else if (a2 === "--force") opts.force = true;
16651
18851
  else if (a2 === "--skip-commands") opts.skipCommands = true;
18852
+ else if (a2 === "--no-introspect") opts.skipIntrospect = true;
16652
18853
  else if (a2 === "--help" || a2 === "-h") opts.help = true;
16653
18854
  else if (a2 === "--template") {
16654
18855
  const next = argv[i2 + 1];
@@ -16727,7 +18928,7 @@ async function runInit(argv, overrides) {
16727
18928
  log("========================");
16728
18929
  log("");
16729
18930
  const configPath = resolve6(projectRoot, "massu.config.yaml");
16730
- if (existsSync9(configPath)) {
18931
+ if (existsSync10(configPath)) {
16731
18932
  if (opts.ci && !opts.force) {
16732
18933
  errLog(`error: massu.config.yaml already exists at ${configPath}`);
16733
18934
  errLog(" rerun with --force to overwrite, or remove the file first");
@@ -16746,7 +18947,7 @@ async function runInit(argv, overrides) {
16746
18947
  errLog(`error: unknown template '${opts.template}'. Available: ${TEMPLATE_NAMES.join(", ")}`);
16747
18948
  throw new Error(`Unknown template: ${opts.template}`);
16748
18949
  }
16749
- const projectName = basename3(projectRoot);
18950
+ const projectName = basename4(projectRoot);
16750
18951
  const res = copyTemplateConfig(opts.template, configPath, projectName);
16751
18952
  if (!res.success) {
16752
18953
  errLog(`error: template copy failed: ${res.error}`);
@@ -16805,7 +19006,25 @@ async function runInit(argv, overrides) {
16805
19006
  }
16806
19007
  }
16807
19008
  const baseConfig = buildConfigFromDetection({ projectRoot, detection });
16808
- const config = applyVariantTemplate(baseConfig, resolveTemplatesDir());
19009
+ const withVariant = applyVariantTemplate(baseConfig, resolveTemplatesDir());
19010
+ let config = withVariant;
19011
+ if (!opts.skipIntrospect) {
19012
+ try {
19013
+ const { introspectAsync: introspectAsync2 } = await Promise.resolve().then(() => (init_codebase_introspector(), codebase_introspector_exports));
19014
+ const introspected = await introspectAsync2(detection, projectRoot);
19015
+ const detectedBlocks = {};
19016
+ for (const [key, block] of Object.entries(introspected)) {
19017
+ if (block && typeof block === "object" && "_confidence" in block) {
19018
+ detectedBlocks[key] = block;
19019
+ }
19020
+ }
19021
+ if (Object.keys(detectedBlocks).length > 0) {
19022
+ config = { ...config, detected: detectedBlocks };
19023
+ }
19024
+ } catch (err2) {
19025
+ errLog(`warning: AST adapter introspection failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
19026
+ }
19027
+ }
16809
19028
  const content = renderConfigYaml(config);
16810
19029
  const writeRes = writeConfigAtomic(configPath, content);
16811
19030
  if (!writeRes.validated) {
@@ -16849,7 +19068,7 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
16849
19068
  const stackResolved = !emptyStack && commandStats && (commandStats.installed > 0 || commandStats.updated > 0 || commandStats.kept > 0);
16850
19069
  if (!stackResolved) {
16851
19070
  const placeholderPath = resolve6(cmdResult.claudeDir, "commands", "_massu-needs-stack.md");
16852
- if (!existsSync9(placeholderPath)) {
19071
+ if (!existsSync10(placeholderPath)) {
16853
19072
  const placeholderBody = [
16854
19073
  "# Massu \u2014 stack not yet detected",
16855
19074
  "",
@@ -16870,8 +19089,8 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
16870
19089
  "\u2014 Massu"
16871
19090
  ].join("\n");
16872
19091
  try {
16873
- mkdirSync3(resolve6(cmdResult.claudeDir, "commands"), { recursive: true });
16874
- writeFileSync(placeholderPath, placeholderBody, "utf-8");
19092
+ mkdirSync4(resolve6(cmdResult.claudeDir, "commands"), { recursive: true });
19093
+ writeFileSync2(placeholderPath, placeholderBody, "utf-8");
16875
19094
  log(" Wrote _massu-needs-stack.md placeholder (no stack detected yet)");
16876
19095
  } catch {
16877
19096
  }
@@ -16890,8 +19109,8 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
16890
19109
  (async () => {
16891
19110
  try {
16892
19111
  const encodedRoot = projectRoot.replace(/\//g, "-");
16893
- const memoryDir = resolve6(homedir2(), ".claude", "projects", encodedRoot, "memory");
16894
- const memFiles = existsSync9(memoryDir) ? readdirSync7(memoryDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md") : [];
19112
+ const memoryDir = resolve6(homedir3(), ".claude", "projects", encodedRoot, "memory");
19113
+ const memFiles = existsSync10(memoryDir) ? readdirSync10(memoryDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md") : [];
16895
19114
  if (memFiles.length > 0) {
16896
19115
  const { getMemoryDb: getMemoryDb2 } = await Promise.resolve().then(() => (init_memory_db(), memory_db_exports));
16897
19116
  const db = getMemoryDb2();
@@ -16951,7 +19170,7 @@ var init_init = __esm({
16951
19170
  init_detect();
16952
19171
  init_drift();
16953
19172
  __filename2 = fileURLToPath2(import.meta.url);
16954
- __dirname2 = dirname4(__filename2);
19173
+ __dirname2 = dirname5(__filename2);
16955
19174
  FRAMEWORK_TO_TEMPLATE_ID = {
16956
19175
  rails: "rails",
16957
19176
  phoenix: "phoenix",
@@ -16973,7 +19192,7 @@ var init_init = __esm({
16973
19192
  });
16974
19193
 
16975
19194
  // src/license.ts
16976
- import { createHash as createHash3 } from "crypto";
19195
+ import { createHash as createHash4 } from "crypto";
16977
19196
  function tierLevel(tier) {
16978
19197
  return TIER_LEVELS[tier] ?? 0;
16979
19198
  }
@@ -16998,7 +19217,7 @@ function annotateToolDefinitions(defs) {
16998
19217
  });
16999
19218
  }
17000
19219
  async function validateLicense(apiKey) {
17001
- const keyHash = createHash3("sha256").update(apiKey).digest("hex");
19220
+ const keyHash = createHash4("sha256").update(apiKey).digest("hex");
17002
19221
  const memDb = getMemoryDb();
17003
19222
  try {
17004
19223
  const cached = memDb.prepare(
@@ -17059,7 +19278,7 @@ async function validateLicense(apiKey) {
17059
19278
  }
17060
19279
  }
17061
19280
  function updateLicenseCache(apiKey, tier, validUntil, features = []) {
17062
- const keyHash = createHash3("sha256").update(apiKey).digest("hex");
19281
+ const keyHash = createHash4("sha256").update(apiKey).digest("hex");
17063
19282
  const memDb = getMemoryDb();
17064
19283
  try {
17065
19284
  memDb.prepare(`
@@ -17267,17 +19486,17 @@ __export(doctor_exports, {
17267
19486
  runDoctor: () => runDoctor,
17268
19487
  runValidateConfig: () => runValidateConfig
17269
19488
  });
17270
- import { existsSync as existsSync10, readFileSync as readFileSync8, readdirSync as readdirSync8 } from "fs";
17271
- import { resolve as resolve7, dirname as dirname5 } from "path";
19489
+ import { existsSync as existsSync11, readFileSync as readFileSync11, readdirSync as readdirSync11 } from "fs";
19490
+ import { resolve as resolve7, dirname as dirname6 } from "path";
17272
19491
  import { fileURLToPath as fileURLToPath3 } from "url";
17273
19492
  import { parse as parseYaml4 } from "yaml";
17274
19493
  function checkConfig(projectRoot) {
17275
19494
  const configPath = resolve7(projectRoot, "massu.config.yaml");
17276
- if (!existsSync10(configPath)) {
19495
+ if (!existsSync11(configPath)) {
17277
19496
  return { name: "Configuration", status: "fail", detail: "massu.config.yaml not found. Run: npx massu init" };
17278
19497
  }
17279
19498
  try {
17280
- const content = readFileSync8(configPath, "utf-8");
19499
+ const content = readFileSync11(configPath, "utf-8");
17281
19500
  const parsed = parseYaml4(content);
17282
19501
  if (!parsed || typeof parsed !== "object") {
17283
19502
  return { name: "Configuration", status: "fail", detail: "massu.config.yaml is empty or invalid YAML" };
@@ -17289,11 +19508,11 @@ function checkConfig(projectRoot) {
17289
19508
  }
17290
19509
  function checkMcpServer(projectRoot) {
17291
19510
  const mcpPath = getResolvedPaths().mcpJsonPath;
17292
- if (!existsSync10(mcpPath)) {
19511
+ if (!existsSync11(mcpPath)) {
17293
19512
  return { name: "MCP Server", status: "fail", detail: ".mcp.json not found. Run: npx massu init" };
17294
19513
  }
17295
19514
  try {
17296
- const content = JSON.parse(readFileSync8(mcpPath, "utf-8"));
19515
+ const content = JSON.parse(readFileSync11(mcpPath, "utf-8"));
17297
19516
  const servers = content.mcpServers ?? {};
17298
19517
  if (!servers.massu) {
17299
19518
  return { name: "MCP Server", status: "fail", detail: "massu not registered in .mcp.json. Run: npx massu init" };
@@ -17305,11 +19524,11 @@ function checkMcpServer(projectRoot) {
17305
19524
  }
17306
19525
  function checkHooksConfig(projectRoot) {
17307
19526
  const settingsPath = getResolvedPaths().settingsLocalPath;
17308
- if (!existsSync10(settingsPath)) {
19527
+ if (!existsSync11(settingsPath)) {
17309
19528
  return { name: "Hooks Config", status: "fail", detail: ".claude/settings.local.json not found. Run: npx massu init" };
17310
19529
  }
17311
19530
  try {
17312
- const content = JSON.parse(readFileSync8(settingsPath, "utf-8"));
19531
+ const content = JSON.parse(readFileSync11(settingsPath, "utf-8"));
17313
19532
  if (!content.hooks) {
17314
19533
  return { name: "Hooks Config", status: "fail", detail: "No hooks configured. Run: npx massu install-hooks" };
17315
19534
  }
@@ -17335,9 +19554,9 @@ function checkHooksConfig(projectRoot) {
17335
19554
  function checkHookFiles(projectRoot) {
17336
19555
  const nodeModulesHooksDir = resolve7(projectRoot, "node_modules/@massu/core/dist/hooks");
17337
19556
  let hooksDir = nodeModulesHooksDir;
17338
- if (!existsSync10(nodeModulesHooksDir)) {
19557
+ if (!existsSync11(nodeModulesHooksDir)) {
17339
19558
  const devHooksDir = resolve7(__dirname3, "../../dist/hooks");
17340
- if (existsSync10(devHooksDir)) {
19559
+ if (existsSync11(devHooksDir)) {
17341
19560
  hooksDir = devHooksDir;
17342
19561
  } else {
17343
19562
  return { name: "Hook Files", status: "fail", detail: "Compiled hooks not found. Run: npm install @massu/core" };
@@ -17345,7 +19564,7 @@ function checkHookFiles(projectRoot) {
17345
19564
  }
17346
19565
  const missing = [];
17347
19566
  for (const hookFile of EXPECTED_HOOKS) {
17348
- if (!existsSync10(resolve7(hooksDir, hookFile))) {
19567
+ if (!existsSync11(resolve7(hooksDir, hookFile))) {
17349
19568
  missing.push(hookFile);
17350
19569
  }
17351
19570
  }
@@ -17372,7 +19591,7 @@ function checkNodeVersion() {
17372
19591
  }
17373
19592
  async function checkGitRepo(projectRoot) {
17374
19593
  const gitDir = resolve7(projectRoot, ".git");
17375
- if (!existsSync10(gitDir)) {
19594
+ if (!existsSync11(gitDir)) {
17376
19595
  return { name: "Git Repository", status: "warn", detail: "Not a git repository (optional but recommended)" };
17377
19596
  }
17378
19597
  try {
@@ -17390,7 +19609,7 @@ async function checkGitRepo(projectRoot) {
17390
19609
  }
17391
19610
  function checkKnowledgeDb(projectRoot) {
17392
19611
  const knowledgeDbPath = getResolvedPaths().memoryDbPath;
17393
- if (!existsSync10(knowledgeDbPath)) {
19612
+ if (!existsSync11(knowledgeDbPath)) {
17394
19613
  return {
17395
19614
  name: "Knowledge DB",
17396
19615
  status: "warn",
@@ -17401,7 +19620,7 @@ function checkKnowledgeDb(projectRoot) {
17401
19620
  }
17402
19621
  function checkMemoryDir(_projectRoot2) {
17403
19622
  const memoryDir = getResolvedPaths().memoryDir;
17404
- if (!existsSync10(memoryDir)) {
19623
+ if (!existsSync11(memoryDir)) {
17405
19624
  return {
17406
19625
  name: "Memory Directory",
17407
19626
  status: "warn",
@@ -17412,7 +19631,7 @@ function checkMemoryDir(_projectRoot2) {
17412
19631
  }
17413
19632
  function checkShellHooksWired(_projectRoot2) {
17414
19633
  const settingsPath = getResolvedPaths().settingsLocalPath;
17415
- if (!existsSync10(settingsPath)) {
19634
+ if (!existsSync11(settingsPath)) {
17416
19635
  return {
17417
19636
  name: "Shell Hooks",
17418
19637
  status: "fail",
@@ -17420,7 +19639,7 @@ function checkShellHooksWired(_projectRoot2) {
17420
19639
  };
17421
19640
  }
17422
19641
  try {
17423
- const content = JSON.parse(readFileSync8(settingsPath, "utf-8"));
19642
+ const content = JSON.parse(readFileSync11(settingsPath, "utf-8"));
17424
19643
  const hooks = content.hooks ?? {};
17425
19644
  const hasSessionStart = Array.isArray(hooks.SessionStart) && hooks.SessionStart.length > 0;
17426
19645
  const hasPreToolUse = Array.isArray(hooks.PreToolUse) && hooks.PreToolUse.length > 0;
@@ -17472,7 +19691,7 @@ function checkPythonHealth(projectRoot) {
17472
19691
  const config = getConfig();
17473
19692
  if (!config.python?.root) return null;
17474
19693
  const pythonRoot = resolve7(projectRoot, config.python.root);
17475
- if (!existsSync10(pythonRoot)) {
19694
+ if (!existsSync11(pythonRoot)) {
17476
19695
  return {
17477
19696
  name: "Python",
17478
19697
  status: "fail",
@@ -17486,15 +19705,15 @@ function checkPythonHealth(projectRoot) {
17486
19705
  function scanDir(dir, depth) {
17487
19706
  if (depth > 5) return;
17488
19707
  try {
17489
- const entries = readdirSync8(dir, { withFileTypes: true });
19708
+ const entries = readdirSync11(dir, { withFileTypes: true });
17490
19709
  for (const entry of entries) {
17491
19710
  if (entry.isDirectory()) {
17492
19711
  const excludeDirs = config.python?.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
17493
19712
  if (!excludeDirs.includes(entry.name)) {
17494
19713
  const subdir = resolve7(dir, entry.name);
17495
- if (depth <= 2 && !existsSync10(resolve7(subdir, "__init__.py"))) {
19714
+ if (depth <= 2 && !existsSync11(resolve7(subdir, "__init__.py"))) {
17496
19715
  try {
17497
- const subEntries = readdirSync8(subdir);
19716
+ const subEntries = readdirSync11(subdir);
17498
19717
  if (subEntries.some((f2) => f2.endsWith(".py") && f2 !== "__init__.py")) {
17499
19718
  initPyMissing.push(entry.name);
17500
19719
  }
@@ -17588,14 +19807,14 @@ async function runDoctor() {
17588
19807
  async function runValidateConfig() {
17589
19808
  const projectRoot = process.cwd();
17590
19809
  const configPath = resolve7(projectRoot, "massu.config.yaml");
17591
- if (!existsSync10(configPath)) {
19810
+ if (!existsSync11(configPath)) {
17592
19811
  console.error("Error: massu.config.yaml not found in current directory");
17593
19812
  console.error("Run: npx massu init");
17594
19813
  process.exit(1);
17595
19814
  return;
17596
19815
  }
17597
19816
  try {
17598
- const content = readFileSync8(configPath, "utf-8");
19817
+ const content = readFileSync11(configPath, "utf-8");
17599
19818
  const parsed = parseYaml4(content);
17600
19819
  if (!parsed || typeof parsed !== "object") {
17601
19820
  console.error("Error: massu.config.yaml is empty or not a valid YAML object");
@@ -17634,7 +19853,7 @@ var init_doctor = __esm({
17634
19853
  init_config();
17635
19854
  init_license();
17636
19855
  __filename3 = fileURLToPath3(import.meta.url);
17637
- __dirname3 = dirname5(__filename3);
19856
+ __dirname3 = dirname6(__filename3);
17638
19857
  EXPECTED_HOOKS = [
17639
19858
  "session-start.js",
17640
19859
  "session-end.js",
@@ -17680,7 +19899,7 @@ var show_template_exports = {};
17680
19899
  __export(show_template_exports, {
17681
19900
  runShowTemplate: () => runShowTemplate
17682
19901
  });
17683
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
19902
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
17684
19903
  import { resolve as resolve8 } from "path";
17685
19904
  function normalizeBaseName(input) {
17686
19905
  return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
@@ -17710,13 +19929,13 @@ async function runShowTemplate(args3) {
17710
19929
  }
17711
19930
  const suffix = choice.kind === "hit" ? choice.suffix : "";
17712
19931
  const file = suffix === "" ? resolve8(sourceDir, `${baseName}.md`) : resolve8(sourceDir, `${baseName}${suffix}.md`);
17713
- if (!existsSync11(file)) {
19932
+ if (!existsSync12(file)) {
17714
19933
  process.stderr.write(`massu: resolved template "${file}" no longer exists
17715
19934
  `);
17716
19935
  process.exit(1);
17717
19936
  return;
17718
19937
  }
17719
- process.stdout.write(readFileSync9(file, "utf-8"));
19938
+ process.stdout.write(readFileSync12(file, "utf-8"));
17720
19939
  }
17721
19940
  var init_show_template = __esm({
17722
19941
  "src/commands/show-template.ts"() {
@@ -17769,12 +19988,12 @@ var init_passthrough = __esm({
17769
19988
  });
17770
19989
 
17771
19990
  // src/lib/fileLock.ts
17772
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync10, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "fs";
17773
- import { dirname as dirname6 } from "path";
19991
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync13, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
19992
+ import { dirname as dirname7 } from "path";
17774
19993
  import * as lockfile from "proper-lockfile";
17775
19994
  function readLockHolderPid(lockPath) {
17776
19995
  try {
17777
- const raw = readFileSync10(`${lockPath}.pid`, "utf-8").trim();
19996
+ const raw = readFileSync13(`${lockPath}.pid`, "utf-8").trim();
17778
19997
  const pid = Number.parseInt(raw, 10);
17779
19998
  if (!Number.isFinite(pid) || pid <= 0) return null;
17780
19999
  return pid;
@@ -17793,7 +20012,7 @@ function busyWaitSync(ms) {
17793
20012
  Atomics.wait(view, 0, 0, ms);
17794
20013
  }
17795
20014
  function withFileLockSync(lockPath, fn, opts = {}) {
17796
- mkdirSync4(dirname6(lockPath), { recursive: true });
20015
+ mkdirSync5(dirname7(lockPath), { recursive: true });
17797
20016
  const staleMs = opts.staleMs ?? 3e4;
17798
20017
  const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
17799
20018
  const pollIntervalMs = opts.pollIntervalMs ?? 100;
@@ -17810,7 +20029,7 @@ function withFileLockSync(lockPath, fn, opts = {}) {
17810
20029
  realpath: false
17811
20030
  });
17812
20031
  try {
17813
- writeFileSync2(`${lockPath}.pid`, String(process.pid), "utf-8");
20032
+ writeFileSync3(`${lockPath}.pid`, String(process.pid), "utf-8");
17814
20033
  } catch {
17815
20034
  }
17816
20035
  break;
@@ -17899,7 +20118,7 @@ __export(config_refresh_exports, {
17899
20118
  mergeRefresh: () => mergeRefresh,
17900
20119
  runConfigRefresh: () => runConfigRefresh
17901
20120
  });
17902
- import { existsSync as existsSync12, readFileSync as readFileSync11, rmSync as rmSync4 } from "fs";
20121
+ import { existsSync as existsSync13, readFileSync as readFileSync14, rmSync as rmSync4 } from "fs";
17903
20122
  import { resolve as resolve10 } from "path";
17904
20123
  import { parse as parseYaml5 } from "yaml";
17905
20124
  function flatten(obj, prefix3 = "") {
@@ -18031,14 +20250,14 @@ async function runConfigRefresh(opts = {}) {
18031
20250
  const configPath = resolve10(cwd, "massu.config.yaml");
18032
20251
  const log = opts.silent ? () => {
18033
20252
  } : (s) => process.stdout.write(s);
18034
- if (!existsSync12(configPath)) {
20253
+ if (!existsSync13(configPath)) {
18035
20254
  const message = "massu.config.yaml not found. Run: npx massu init";
18036
20255
  if (!opts.silent) process.stderr.write(message + "\n");
18037
20256
  return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
18038
20257
  }
18039
20258
  let existing;
18040
20259
  try {
18041
- const content = readFileSync11(configPath, "utf-8");
20260
+ const content = readFileSync14(configPath, "utf-8");
18042
20261
  const parsed = parseYaml5(content);
18043
20262
  if (!parsed || typeof parsed !== "object") {
18044
20263
  throw new Error("config is not a YAML object");
@@ -18112,7 +20331,7 @@ async function runConfigRefresh(opts = {}) {
18112
20331
  const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
18113
20332
  if (stackResolved) {
18114
20333
  const placeholderPath = resolve10(installResult.claudeDir, "commands", "_massu-needs-stack.md");
18115
- if (existsSync12(placeholderPath)) {
20334
+ if (existsSync13(placeholderPath)) {
18116
20335
  try {
18117
20336
  rmSync4(placeholderPath, { force: true });
18118
20337
  log("Removed _massu-needs-stack.md (stack now declared).\n");
@@ -18221,14 +20440,14 @@ var init_gitToplevel = __esm({
18221
20440
  });
18222
20441
 
18223
20442
  // src/watch/lockfile-detector.ts
18224
- import { existsSync as existsSync13, statSync as statSync6 } from "fs";
20443
+ import { existsSync as existsSync14, statSync as statSync8 } from "fs";
18225
20444
  import { resolve as resolve11 } from "path";
18226
20445
  function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
18227
20446
  for (const lf of KNOWN_LOCKFILES) {
18228
20447
  const p19 = resolve11(projectRoot, lf);
18229
- if (!existsSync13(p19)) continue;
20448
+ if (!existsSync14(p19)) continue;
18230
20449
  try {
18231
- const stat = statSync6(p19);
20450
+ const stat = statSync8(p19);
18232
20451
  const delta = now - stat.mtimeMs;
18233
20452
  if (delta >= 0 && delta < windowMs) return true;
18234
20453
  } catch {
@@ -18239,7 +20458,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
18239
20458
  function gitMidOperation(projectRoot) {
18240
20459
  const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
18241
20460
  for (const s of sentinels) {
18242
- if (existsSync13(resolve11(projectRoot, ".git", s))) return true;
20461
+ if (existsSync14(resolve11(projectRoot, ".git", s))) return true;
18243
20462
  }
18244
20463
  return false;
18245
20464
  }
@@ -18436,8 +20655,8 @@ var init_paths = __esm({
18436
20655
  });
18437
20656
 
18438
20657
  // src/watch/state.ts
18439
- import { closeSync as closeSync3, existsSync as existsSync14, fsyncSync as fsyncSync3, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync12, renameSync as renameSync3, rmSync as rmSync5, writeFileSync as writeFileSync3, writeSync as writeSync3 } from "fs";
18440
- import { dirname as dirname7, resolve as resolve12 } from "path";
20658
+ import { closeSync as closeSync3, existsSync as existsSync15, fsyncSync as fsyncSync3, mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync15, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync4, writeSync as writeSync3 } from "fs";
20659
+ import { dirname as dirname8, resolve as resolve12 } from "path";
18441
20660
  function watchStatePath(projectRoot) {
18442
20661
  return resolve12(projectRoot, ".massu", "watch-state.json");
18443
20662
  }
@@ -18446,8 +20665,8 @@ function backupStatePath(projectRoot) {
18446
20665
  }
18447
20666
  function readState(projectRoot) {
18448
20667
  const path = watchStatePath(projectRoot);
18449
- if (!existsSync14(path)) return { ...DEFAULT_STATE };
18450
- const content = readFileSync12(path, "utf-8");
20668
+ if (!existsSync15(path)) return { ...DEFAULT_STATE };
20669
+ const content = readFileSync15(path, "utf-8");
18451
20670
  let raw;
18452
20671
  try {
18453
20672
  raw = JSON.parse(content);
@@ -18489,14 +20708,14 @@ function readState(projectRoot) {
18489
20708
  }
18490
20709
  function archiveCorrupt(projectRoot, content) {
18491
20710
  const bak = backupStatePath(projectRoot);
18492
- mkdirSync5(dirname7(bak), { recursive: true });
18493
- writeFileSync3(bak, content, "utf-8");
20711
+ mkdirSync6(dirname8(bak), { recursive: true });
20712
+ writeFileSync4(bak, content, "utf-8");
18494
20713
  }
18495
20714
  function writeStateAtomic(projectRoot, state) {
18496
20715
  const path = watchStatePath(projectRoot);
18497
20716
  writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
18498
20717
  const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
18499
- mkdirSync5(dirname7(path), { recursive: true });
20718
+ mkdirSync6(dirname8(path), { recursive: true });
18500
20719
  let renamed = false;
18501
20720
  try {
18502
20721
  const fd = openSync3(tmp, "w");
@@ -18507,10 +20726,10 @@ function writeStateAtomic(projectRoot, state) {
18507
20726
  } finally {
18508
20727
  closeSync3(fd);
18509
20728
  }
18510
- renameSync3(tmp, path);
20729
+ renameSync4(tmp, path);
18511
20730
  renamed = true;
18512
20731
  } finally {
18513
- if (!renamed && existsSync14(tmp)) {
20732
+ if (!renamed && existsSync15(tmp)) {
18514
20733
  try {
18515
20734
  rmSync5(tmp, { force: true });
18516
20735
  } catch {
@@ -18790,8 +21009,8 @@ __export(watch_exports, {
18790
21009
  runWatch: () => runWatch
18791
21010
  });
18792
21011
  import { spawnSync as spawnSync3 } from "child_process";
18793
- import { basename as basename4, dirname as dirname8, resolve as resolve13 } from "path";
18794
- import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync13 } from "fs";
21012
+ import { basename as basename5, dirname as dirname9, resolve as resolve13 } from "path";
21013
+ import { appendFileSync, existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync16 } from "fs";
18795
21014
  function parseFlags(args3) {
18796
21015
  const out2 = {
18797
21016
  foreground: false,
@@ -18817,7 +21036,7 @@ function parseFlags(args3) {
18817
21036
  function findClaudeBg() {
18818
21037
  const home = process.env.HOME ?? "";
18819
21038
  const fixed = home ? resolve13(home, ".claude", "bin", "claude-bg") : null;
18820
- if (fixed && existsSync15(fixed)) return fixed;
21039
+ if (fixed && existsSync16(fixed)) return fixed;
18821
21040
  const which = spawnSync3("which", ["claude-bg"], { encoding: "utf-8" });
18822
21041
  if (which.status === 0 && which.stdout) {
18823
21042
  const p19 = which.stdout.trim();
@@ -18826,7 +21045,7 @@ function findClaudeBg() {
18826
21045
  return null;
18827
21046
  }
18828
21047
  function watchName(root) {
18829
- return `massu-watch-${basename4(root)}`;
21048
+ return `massu-watch-${basename5(root)}`;
18830
21049
  }
18831
21050
  function printHelp(out2) {
18832
21051
  out2(`
@@ -18878,7 +21097,7 @@ async function runWatch(args3) {
18878
21097
  }
18879
21098
  function runStatus(root) {
18880
21099
  const path = watchStatePath(root);
18881
- if (!existsSync15(path)) {
21100
+ if (!existsSync16(path)) {
18882
21101
  process.stdout.write("massu watch: not running (no state file)\n");
18883
21102
  return { exitCode: 0 };
18884
21103
  }
@@ -19036,18 +21255,18 @@ function refreshLogPath(projectRoot) {
19036
21255
  function appendRefreshLog(projectRoot, event) {
19037
21256
  const path = refreshLogPath(projectRoot);
19038
21257
  try {
19039
- mkdirSync6(dirname8(path), { recursive: true });
21258
+ mkdirSync7(dirname9(path), { recursive: true });
19040
21259
  appendFileSync(path, JSON.stringify(event) + "\n", "utf-8");
19041
21260
  } catch {
19042
21261
  }
19043
21262
  }
19044
21263
  function readRefreshLog(projectRoot, limit = 10, opts = {}) {
19045
21264
  const path = refreshLogPath(projectRoot);
19046
- if (!existsSync15(path)) return [];
21265
+ if (!existsSync16(path)) return [];
19047
21266
  const warn = opts.warn ?? ((s) => {
19048
21267
  process.stderr.write(s);
19049
21268
  });
19050
- const lines = readFileSync13(path, "utf-8").split("\n").filter(Boolean);
21269
+ const lines = readFileSync16(path, "utf-8").split("\n").filter(Boolean);
19051
21270
  const tail = lines.slice(-limit);
19052
21271
  const out2 = [];
19053
21272
  let corrupt = 0;
@@ -19118,28 +21337,28 @@ var init_refresh_log = __esm({
19118
21337
 
19119
21338
  // src/security/atomic-write.ts
19120
21339
  import {
19121
- chmodSync as chmodSync2,
21340
+ chmodSync as chmodSync3,
19122
21341
  closeSync as closeSync4,
19123
- existsSync as existsSync16,
21342
+ existsSync as existsSync17,
19124
21343
  fsyncSync as fsyncSync4,
19125
- mkdirSync as mkdirSync7,
21344
+ mkdirSync as mkdirSync8,
19126
21345
  openSync as openSync4,
19127
- renameSync as renameSync4,
21346
+ renameSync as renameSync5,
19128
21347
  rmSync as rmSync6,
19129
- statSync as statSync7,
21348
+ statSync as statSync9,
19130
21349
  writeSync as writeSync4
19131
21350
  } from "node:fs";
19132
- import { dirname as dirname9 } from "node:path";
21351
+ import { dirname as dirname10 } from "node:path";
19133
21352
  function atomicWrite(path, content, opts = {}) {
19134
21353
  const tmpPath = `${path}.tmp`;
19135
- const parentDir = dirname9(path);
21354
+ const parentDir = dirname10(path);
19136
21355
  try {
19137
- if (!existsSync16(parentDir)) {
21356
+ if (!existsSync17(parentDir)) {
19138
21357
  const mkdirOpts = { recursive: true };
19139
21358
  if (opts.ensureParentDirMode !== void 0) {
19140
21359
  mkdirOpts.mode = opts.ensureParentDirMode;
19141
21360
  }
19142
- mkdirSync7(parentDir, mkdirOpts);
21361
+ mkdirSync8(parentDir, mkdirOpts);
19143
21362
  }
19144
21363
  const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
19145
21364
  const openMode = opts.mode ?? 420;
@@ -19151,12 +21370,12 @@ function atomicWrite(path, content, opts = {}) {
19151
21370
  closeSync4(fd);
19152
21371
  }
19153
21372
  if (opts.mode !== void 0) {
19154
- chmodSync2(tmpPath, opts.mode);
21373
+ chmodSync3(tmpPath, opts.mode);
19155
21374
  }
19156
- renameSync4(tmpPath, path);
21375
+ renameSync5(tmpPath, path);
19157
21376
  return { written: true };
19158
21377
  } catch (err2) {
19159
- if (existsSync16(tmpPath)) {
21378
+ if (existsSync17(tmpPath)) {
19160
21379
  try {
19161
21380
  rmSync6(tmpPath, { force: true });
19162
21381
  } catch {
@@ -21460,9 +23679,9 @@ var init_registry_pubkey_generated = __esm({
21460
23679
  });
21461
23680
 
21462
23681
  // src/security/adapter-verifier.ts
21463
- import { createHash as createHash4 } from "node:crypto";
23682
+ import { createHash as createHash5 } from "node:crypto";
21464
23683
  function sha256Hex(bytes) {
21465
- return createHash4("sha256").update(bytes).digest("hex");
23684
+ return createHash5("sha256").update(bytes).digest("hex");
21466
23685
  }
21467
23686
  function reviver(key, value) {
21468
23687
  if (key === "__proto__" || key === "constructor" || key === "prototype") {
@@ -21724,24 +23943,24 @@ var init_fetcher = __esm({
21724
23943
  });
21725
23944
 
21726
23945
  // src/security/manifest-cache.ts
21727
- import { existsSync as existsSync17, readFileSync as readFileSync14, statSync as statSync8 } from "node:fs";
21728
- import { homedir as homedir3 } from "node:os";
23946
+ import { existsSync as existsSync18, readFileSync as readFileSync17, statSync as statSync10 } from "node:fs";
23947
+ import { homedir as homedir4 } from "node:os";
21729
23948
  import { resolve as resolve14 } from "node:path";
21730
23949
  import { z as z4 } from "zod";
21731
23950
  function defaultCachePaths() {
21732
- const dir = resolve14(homedir3(), ".massu");
23951
+ const dir = resolve14(homedir4(), ".massu");
21733
23952
  return {
21734
23953
  cachePath: resolve14(dir, "adapter-manifest.json"),
21735
23954
  lockPath: resolve14(dir, ".adapter-manifest.lock")
21736
23955
  };
21737
23956
  }
21738
23957
  function loadCachedManifest(paths = defaultCachePaths()) {
21739
- if (!existsSync17(paths.cachePath)) {
23958
+ if (!existsSync18(paths.cachePath)) {
21740
23959
  return { kind: "absent" };
21741
23960
  }
21742
23961
  let raw;
21743
23962
  try {
21744
- const content = readFileSync14(paths.cachePath, "utf-8");
23963
+ const content = readFileSync17(paths.cachePath, "utf-8");
21745
23964
  raw = JSON.parse(content);
21746
23965
  } catch (err2) {
21747
23966
  return {
@@ -21930,10 +24149,10 @@ var init_adapter_origin = __esm({
21930
24149
  });
21931
24150
 
21932
24151
  // src/security/local-fingerprint.ts
21933
- import { existsSync as existsSync18, readFileSync as readFileSync15, lstatSync as lstatSync3 } from "node:fs";
21934
- import { homedir as homedir4 } from "node:os";
24152
+ import { existsSync as existsSync19, readFileSync as readFileSync18, lstatSync as lstatSync5 } from "node:fs";
24153
+ import { homedir as homedir5 } from "node:os";
21935
24154
  import { resolve as resolve15, isAbsolute } from "node:path";
21936
- import { createHash as createHash5 } from "node:crypto";
24155
+ import { createHash as createHash6 } from "node:crypto";
21937
24156
  import { z as z5 } from "zod";
21938
24157
  function computeLocalFingerprint(localPaths, projectRoot) {
21939
24158
  const tuples = [];
@@ -21941,13 +24160,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
21941
24160
  const abs = isAbsolute(p19) ? p19 : resolve15(projectRoot, p19);
21942
24161
  let contentTag;
21943
24162
  try {
21944
- const lst = lstatSync3(abs);
24163
+ const lst = lstatSync5(abs);
21945
24164
  if (lst.isSymbolicLink()) {
21946
24165
  contentTag = "<symlink>";
21947
24166
  } else if (!lst.isFile()) {
21948
24167
  contentTag = "<not-a-file>";
21949
24168
  } else {
21950
- contentTag = createHash5("sha256").update(readFileSync15(abs)).digest("hex");
24169
+ contentTag = createHash6("sha256").update(readFileSync18(abs)).digest("hex");
21951
24170
  }
21952
24171
  } catch {
21953
24172
  contentTag = "<missing>";
@@ -21956,13 +24175,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
21956
24175
  }
21957
24176
  tuples.sort((a2, b2) => a2.path < b2.path ? -1 : a2.path > b2.path ? 1 : 0);
21958
24177
  const canonical = JSON.stringify(tuples);
21959
- return createHash5("sha256").update(canonical).digest("hex");
24178
+ return createHash6("sha256").update(canonical).digest("hex");
21960
24179
  }
21961
24180
  function readFingerprintSentinel(path = FINGERPRINT_PATH) {
21962
- if (!existsSync18(path)) return null;
24181
+ if (!existsSync19(path)) return null;
21963
24182
  let raw;
21964
24183
  try {
21965
- raw = JSON.parse(readFileSync15(path, "utf-8"));
24184
+ raw = JSON.parse(readFileSync18(path, "utf-8"));
21966
24185
  } catch {
21967
24186
  return null;
21968
24187
  }
@@ -22010,7 +24229,7 @@ var init_local_fingerprint = __esm({
22010
24229
  "use strict";
22011
24230
  init_atomic_write();
22012
24231
  init_manifest_schema();
22013
- FINGERPRINT_PATH = resolve15(homedir4(), ".massu", "adapters-local-fingerprint.json");
24232
+ FINGERPRINT_PATH = resolve15(homedir5(), ".massu", "adapters-local-fingerprint.json");
22014
24233
  FingerprintSentinelSchema = z5.object({
22015
24234
  fingerprint: z5.string().regex(/^[0-9a-f]{64}$/),
22016
24235
  source: z5.enum(["cli", "cli-resync"]),
@@ -22026,15 +24245,15 @@ var init_local_fingerprint = __esm({
22026
24245
  });
22027
24246
 
22028
24247
  // src/security/install-tracking.ts
22029
- import { readFileSync as readFileSync16, readdirSync as readdirSync9, lstatSync as lstatSync4, existsSync as existsSync19 } from "node:fs";
22030
- import { join as join7, relative as relative4, sep } from "node:path";
22031
- import { homedir as homedir5 } from "node:os";
24248
+ import { readFileSync as readFileSync19, readdirSync as readdirSync12, lstatSync as lstatSync6, existsSync as existsSync20 } from "node:fs";
24249
+ import { join as join10, relative as relative5, sep } from "node:path";
24250
+ import { homedir as homedir6 } from "node:os";
22032
24251
  import { resolve as resolve16 } from "node:path";
22033
- import { createHash as createHash6 } from "node:crypto";
24252
+ import { createHash as createHash7 } from "node:crypto";
22034
24253
  import { z as z6 } from "zod";
22035
24254
  function containsHiddenDirs(packageDir) {
22036
24255
  for (const hidden of EXCLUDED_DIR_NAMES) {
22037
- if (existsSync19(`${packageDir}/${hidden}`)) {
24256
+ if (existsSync20(`${packageDir}/${hidden}`)) {
22038
24257
  return hidden;
22039
24258
  }
22040
24259
  }
@@ -22046,15 +24265,15 @@ function sha256OfDir(dir, opts = {}) {
22046
24265
  function walk(currentDir) {
22047
24266
  let entries;
22048
24267
  try {
22049
- entries = readdirSync9(currentDir);
24268
+ entries = readdirSync12(currentDir);
22050
24269
  } catch {
22051
24270
  return;
22052
24271
  }
22053
24272
  for (const entry of entries.sort()) {
22054
- const absPath = join7(currentDir, entry);
24273
+ const absPath = join10(currentDir, entry);
22055
24274
  let lst;
22056
24275
  try {
22057
- lst = lstatSync4(absPath);
24276
+ lst = lstatSync6(absPath);
22058
24277
  } catch {
22059
24278
  continue;
22060
24279
  }
@@ -22072,15 +24291,15 @@ function sha256OfDir(dir, opts = {}) {
22072
24291
  `sha256OfDir: file ${absPath} exceeds maxFileBytes (${lst.size} > ${maxFileBytes}); adapter packages should not ship files this large.`
22073
24292
  );
22074
24293
  }
22075
- const rel = relative4(dir, absPath).split(sep).join("/");
24294
+ const rel = relative5(dir, absPath).split(sep).join("/");
22076
24295
  files.push({ relativePath: rel, absPath });
22077
24296
  }
22078
24297
  }
22079
24298
  walk(dir);
22080
24299
  files.sort((a2, b2) => a2.relativePath < b2.relativePath ? -1 : a2.relativePath > b2.relativePath ? 1 : 0);
22081
- const top = createHash6("sha256");
24300
+ const top = createHash7("sha256");
22082
24301
  for (const f2 of files) {
22083
- const fileHash = createHash6("sha256").update(readFileSync16(f2.absPath)).digest("hex");
24302
+ const fileHash = createHash7("sha256").update(readFileSync19(f2.absPath)).digest("hex");
22084
24303
  top.update(f2.relativePath, "utf-8");
22085
24304
  top.update("\0", "utf-8");
22086
24305
  top.update(fileHash, "utf-8");
@@ -22089,10 +24308,10 @@ function sha256OfDir(dir, opts = {}) {
22089
24308
  return top.digest("hex");
22090
24309
  }
22091
24310
  function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
22092
- if (!existsSync19(path)) return {};
24311
+ if (!existsSync20(path)) return {};
22093
24312
  let raw;
22094
24313
  try {
22095
- raw = JSON.parse(readFileSync16(path, "utf-8"));
24314
+ raw = JSON.parse(readFileSync19(path, "utf-8"));
22096
24315
  } catch {
22097
24316
  return {};
22098
24317
  }
@@ -22156,7 +24375,7 @@ var init_install_tracking = __esm({
22156
24375
  "src/security/install-tracking.ts"() {
22157
24376
  "use strict";
22158
24377
  init_atomic_write();
22159
- INSTALLED_MANIFEST_PATH = resolve16(homedir5(), ".massu", "adapter-manifest-installed.json");
24378
+ INSTALLED_MANIFEST_PATH = resolve16(homedir6(), ".massu", "adapter-manifest-installed.json");
22160
24379
  DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
22161
24380
  EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
22162
24381
  InstallEntrySchema = z6.object({
@@ -22179,18 +24398,18 @@ var init_install_tracking = __esm({
22179
24398
  });
22180
24399
 
22181
24400
  // src/detect/adapters/discover.ts
22182
- import { existsSync as existsSync20, readdirSync as readdirSync10, readFileSync as readFileSync17, lstatSync as lstatSync5 } from "node:fs";
24401
+ import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync20, lstatSync as lstatSync7 } from "node:fs";
22183
24402
  import { resolve as resolve17, isAbsolute as isAbsolute2 } from "node:path";
22184
24403
  import { z as z7 } from "zod";
22185
24404
  function walkNodeModules(projectRoot, warnings) {
22186
24405
  const nodeModulesDir = resolve17(projectRoot, "node_modules");
22187
- if (!existsSync20(nodeModulesDir)) {
24406
+ if (!existsSync21(nodeModulesDir)) {
22188
24407
  return [];
22189
24408
  }
22190
24409
  const candidates = [];
22191
24410
  let topLevelEntries;
22192
24411
  try {
22193
- topLevelEntries = readdirSync10(nodeModulesDir);
24412
+ topLevelEntries = readdirSync13(nodeModulesDir);
22194
24413
  } catch (err2) {
22195
24414
  warnings.push(`failed to read node_modules: ${err2 instanceof Error ? err2.message : String(err2)}`);
22196
24415
  return [];
@@ -22200,7 +24419,7 @@ function walkNodeModules(projectRoot, warnings) {
22200
24419
  const entryPath = resolve17(nodeModulesDir, entry);
22201
24420
  let entryStat;
22202
24421
  try {
22203
- entryStat = lstatSync5(entryPath);
24422
+ entryStat = lstatSync7(entryPath);
22204
24423
  } catch {
22205
24424
  continue;
22206
24425
  }
@@ -22214,7 +24433,7 @@ function walkNodeModules(projectRoot, warnings) {
22214
24433
  if (entry.startsWith("@")) {
22215
24434
  let scopedEntries;
22216
24435
  try {
22217
- scopedEntries = readdirSync10(entryPath);
24436
+ scopedEntries = readdirSync13(entryPath);
22218
24437
  } catch {
22219
24438
  continue;
22220
24439
  }
@@ -22222,7 +24441,7 @@ function walkNodeModules(projectRoot, warnings) {
22222
24441
  const subPath = resolve17(entryPath, sub);
22223
24442
  let subStat;
22224
24443
  try {
22225
- subStat = lstatSync5(subPath);
24444
+ subStat = lstatSync7(subPath);
22226
24445
  } catch {
22227
24446
  continue;
22228
24447
  }
@@ -22239,10 +24458,10 @@ function walkNodeModules(projectRoot, warnings) {
22239
24458
  }
22240
24459
  function tryReadAdapterPackage(packageDir, warnings) {
22241
24460
  const pkgJsonPath = resolve17(packageDir, "package.json");
22242
- if (!existsSync20(pkgJsonPath)) return null;
24461
+ if (!existsSync21(pkgJsonPath)) return null;
22243
24462
  let raw;
22244
24463
  try {
22245
- raw = JSON.parse(readFileSync17(pkgJsonPath, "utf-8"));
24464
+ raw = JSON.parse(readFileSync20(pkgJsonPath, "utf-8"));
22246
24465
  } catch (err2) {
22247
24466
  warnings.push(
22248
24467
  `skipping ${packageDir}: package.json parse failed (${err2 instanceof Error ? err2.message : String(err2)})`
@@ -22377,7 +24596,7 @@ function discoverAdapters(opts) {
22377
24596
  for (const localPath of opts.configLocalPaths) {
22378
24597
  if (seenIds.has(localPath)) continue;
22379
24598
  const absPath = isAbsolute2(localPath) ? localPath : resolve17(opts.projectRoot, localPath);
22380
- if (!existsSync20(absPath)) {
24599
+ if (!existsSync21(absPath)) {
22381
24600
  warnings.push(
22382
24601
  `local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
22383
24602
  );
@@ -22458,7 +24677,7 @@ __export(adapters_exports, {
22458
24677
  runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
22459
24678
  runAdaptersSearch: () => runAdaptersSearch
22460
24679
  });
22461
- import { existsSync as existsSync21, readFileSync as readFileSync18 } from "node:fs";
24680
+ import { existsSync as existsSync22, readFileSync as readFileSync21 } from "node:fs";
22462
24681
  import { resolve as resolve18 } from "node:path";
22463
24682
  import { parseDocument } from "yaml";
22464
24683
  async function handleAdaptersSubcommand(args3) {
@@ -22727,7 +24946,7 @@ function mutateLocalArray(mutator, command) {
22727
24946
  return { exitCode: 2 };
22728
24947
  }
22729
24948
  const yamlPath = resolve18(projectRoot, "massu.config.yaml");
22730
- if (!existsSync21(yamlPath)) {
24949
+ if (!existsSync22(yamlPath)) {
22731
24950
  process.stderr.write(
22732
24951
  `${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
22733
24952
  `
@@ -22736,7 +24955,7 @@ function mutateLocalArray(mutator, command) {
22736
24955
  }
22737
24956
  let yamlText;
22738
24957
  try {
22739
- yamlText = readFileSync18(yamlPath, "utf-8");
24958
+ yamlText = readFileSync21(yamlPath, "utf-8");
22740
24959
  } catch (err2) {
22741
24960
  process.stderr.write(`${command}: failed to read ${yamlPath}: ${err2 instanceof Error ? err2.message : String(err2)}
22742
24961
  `);
@@ -22827,7 +25046,7 @@ async function runAdaptersInstall(args3) {
22827
25046
  return { exitCode: 1 };
22828
25047
  }
22829
25048
  const packageDir = resolve18(projectRoot, "node_modules", ...packageName.split("/"));
22830
- if (!existsSync21(packageDir)) {
25049
+ if (!existsSync22(packageDir)) {
22831
25050
  process.stderr.write(
22832
25051
  `install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
22833
25052
  `
@@ -22835,14 +25054,14 @@ async function runAdaptersInstall(args3) {
22835
25054
  return { exitCode: 1 };
22836
25055
  }
22837
25056
  const pkgJsonPath = resolve18(packageDir, "package.json");
22838
- if (!existsSync21(pkgJsonPath)) {
25057
+ if (!existsSync22(pkgJsonPath)) {
22839
25058
  process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
22840
25059
  `);
22841
25060
  return { exitCode: 1 };
22842
25061
  }
22843
25062
  let pkgJson;
22844
25063
  try {
22845
- pkgJson = JSON.parse(readFileSync18(pkgJsonPath, "utf-8"));
25064
+ pkgJson = JSON.parse(readFileSync21(pkgJsonPath, "utf-8"));
22846
25065
  } catch (err2) {
22847
25066
  process.stderr.write(`install: ${packageName} has malformed package.json: ${err2 instanceof Error ? err2.message : String(err2)}
22848
25067
  `);
@@ -22962,7 +25181,7 @@ async function runAdaptersResign(_args) {
22962
25181
  continue;
22963
25182
  }
22964
25183
  const packageDir = resolve18(projectRoot, "node_modules", ...name2.split("/"));
22965
- if (!existsSync21(packageDir)) {
25184
+ if (!existsSync22(packageDir)) {
22966
25185
  removed++;
22967
25186
  warnings.push(`${name2}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
22968
25187
  removeInstalledManifestEntry(name2);
@@ -23049,11 +25268,11 @@ var init_adapters2 = __esm({
23049
25268
 
23050
25269
  // src/db.ts
23051
25270
  import Database2 from "better-sqlite3";
23052
- import { dirname as dirname10, join as join8 } from "path";
23053
- import { existsSync as existsSync22, mkdirSync as mkdirSync8, readdirSync as readdirSync11, statSync as statSync9 } from "fs";
25271
+ import { dirname as dirname11, join as join11 } from "path";
25272
+ import { existsSync as existsSync23, mkdirSync as mkdirSync9, readdirSync as readdirSync14, statSync as statSync11 } from "fs";
23054
25273
  function getCodeGraphDb() {
23055
25274
  const dbPath = getResolvedPaths().codegraphDbPath;
23056
- if (!existsSync22(dbPath)) {
25275
+ if (!existsSync23(dbPath)) {
23057
25276
  throw new Error(`CodeGraph database not found at ${dbPath}. Run 'npx @colbymchenry/codegraph sync' first.`);
23058
25277
  }
23059
25278
  const db = new Database2(dbPath, { readonly: true });
@@ -23062,9 +25281,9 @@ function getCodeGraphDb() {
23062
25281
  }
23063
25282
  function getDataDb() {
23064
25283
  const dbPath = getResolvedPaths().dataDbPath;
23065
- const dir = dirname10(dbPath);
23066
- if (!existsSync22(dir)) {
23067
- mkdirSync8(dir, { recursive: true });
25284
+ const dir = dirname11(dbPath);
25285
+ if (!existsSync23(dir)) {
25286
+ mkdirSync9(dir, { recursive: true });
23068
25287
  }
23069
25288
  const db = new Database2(dbPath);
23070
25289
  db.pragma("journal_mode = WAL");
@@ -23334,14 +25553,14 @@ function isPythonDataStale(dataDb2, pythonRoot) {
23334
25553
  const lastBuildTime = new Date(lastBuild.value).getTime();
23335
25554
  function checkDir(dir) {
23336
25555
  try {
23337
- const entries = readdirSync11(dir, { withFileTypes: true });
25556
+ const entries = readdirSync14(dir, { withFileTypes: true });
23338
25557
  for (const entry of entries) {
23339
- const fullPath = join8(dir, entry.name);
25558
+ const fullPath = join11(dir, entry.name);
23340
25559
  if (entry.isDirectory()) {
23341
25560
  if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
23342
25561
  if (checkDir(fullPath)) return true;
23343
25562
  } else if (entry.name.endsWith(".py")) {
23344
- if (statSync9(fullPath).mtimeMs > lastBuildTime) return true;
25563
+ if (statSync11(fullPath).mtimeMs > lastBuildTime) return true;
23345
25564
  }
23346
25565
  }
23347
25566
  } catch {
@@ -23437,8 +25656,8 @@ var init_rules = __esm({
23437
25656
  });
23438
25657
 
23439
25658
  // src/import-resolver.ts
23440
- import { readFileSync as readFileSync19, existsSync as existsSync23, statSync as statSync10 } from "fs";
23441
- import { resolve as resolve20, dirname as dirname11, join as join9 } from "path";
25659
+ import { readFileSync as readFileSync22, existsSync as existsSync24, statSync as statSync12 } from "fs";
25660
+ import { resolve as resolve20, dirname as dirname12, join as join12 } from "path";
23442
25661
  function parseImports(source) {
23443
25662
  const imports = [];
23444
25663
  const lines = source.split("\n");
@@ -23496,21 +25715,21 @@ function resolveImportPath(specifier, fromFile) {
23496
25715
  const paths = getResolvedPaths();
23497
25716
  basePath = resolve20(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
23498
25717
  } else {
23499
- basePath = resolve20(dirname11(fromFile), specifier);
25718
+ basePath = resolve20(dirname12(fromFile), specifier);
23500
25719
  }
23501
- if (existsSync23(basePath) && !isDirectory(basePath)) {
25720
+ if (existsSync24(basePath) && !isDirectory(basePath)) {
23502
25721
  return toRelative(basePath);
23503
25722
  }
23504
25723
  const resolvedPaths = getResolvedPaths();
23505
25724
  for (const ext of resolvedPaths.extensions) {
23506
25725
  const withExt = basePath + ext;
23507
- if (existsSync23(withExt)) {
25726
+ if (existsSync24(withExt)) {
23508
25727
  return toRelative(withExt);
23509
25728
  }
23510
25729
  }
23511
25730
  for (const indexFile of resolvedPaths.indexFiles) {
23512
- const indexPath = join9(basePath, indexFile);
23513
- if (existsSync23(indexPath)) {
25731
+ const indexPath = join12(basePath, indexFile);
25732
+ if (existsSync24(indexPath)) {
23514
25733
  return toRelative(indexPath);
23515
25734
  }
23516
25735
  }
@@ -23518,7 +25737,7 @@ function resolveImportPath(specifier, fromFile) {
23518
25737
  }
23519
25738
  function isDirectory(path) {
23520
25739
  try {
23521
- return statSync10(path).isDirectory();
25740
+ return statSync12(path).isDirectory();
23522
25741
  } catch {
23523
25742
  return false;
23524
25743
  }
@@ -23547,10 +25766,10 @@ function buildImportIndex(dataDb2, codegraphDb2) {
23547
25766
  let batch = [];
23548
25767
  for (const file of files) {
23549
25768
  const absPath = ensureWithinRoot(resolve20(projectRoot, file.path), projectRoot);
23550
- if (!existsSync23(absPath)) continue;
25769
+ if (!existsSync24(absPath)) continue;
23551
25770
  let source;
23552
25771
  try {
23553
- source = readFileSync19(absPath, "utf-8");
25772
+ source = readFileSync22(absPath, "utf-8");
23554
25773
  } catch {
23555
25774
  continue;
23556
25775
  }
@@ -23586,15 +25805,15 @@ var init_import_resolver = __esm({
23586
25805
  });
23587
25806
 
23588
25807
  // src/trpc-index.ts
23589
- import { readFileSync as readFileSync20, existsSync as existsSync24, readdirSync as readdirSync12 } from "fs";
23590
- import { resolve as resolve21, join as join10 } from "path";
25808
+ import { readFileSync as readFileSync23, existsSync as existsSync25, readdirSync as readdirSync15 } from "fs";
25809
+ import { resolve as resolve21, join as join13 } from "path";
23591
25810
  function parseRootRouter() {
23592
25811
  const paths = getResolvedPaths();
23593
25812
  const rootPath = paths.rootRouterPath;
23594
- if (!existsSync24(rootPath)) {
25813
+ if (!existsSync25(rootPath)) {
23595
25814
  throw new Error(`Root router not found at ${rootPath}`);
23596
25815
  }
23597
- const source = readFileSync20(rootPath, "utf-8");
25816
+ const source = readFileSync23(rootPath, "utf-8");
23598
25817
  const mappings = [];
23599
25818
  const importMap = /* @__PURE__ */ new Map();
23600
25819
  const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
@@ -23606,12 +25825,12 @@ function parseRootRouter() {
23606
25825
  for (const ext of [".ts", ".tsx", ""]) {
23607
25826
  const candidate = fullPath + ext;
23608
25827
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
23609
- if (existsSync24(candidate)) {
25828
+ if (existsSync25(candidate)) {
23610
25829
  filePath = routersRelPath + "/" + filePath + ext;
23611
25830
  break;
23612
25831
  }
23613
- const indexCandidate = join10(fullPath, "index.ts");
23614
- if (existsSync24(indexCandidate)) {
25832
+ const indexCandidate = join13(fullPath, "index.ts");
25833
+ if (existsSync25(indexCandidate)) {
23615
25834
  filePath = routersRelPath + "/" + filePath + "/index.ts";
23616
25835
  break;
23617
25836
  }
@@ -23631,8 +25850,8 @@ function parseRootRouter() {
23631
25850
  }
23632
25851
  function extractProcedures(routerFilePath) {
23633
25852
  const absPath = resolve21(getProjectRoot(), routerFilePath);
23634
- if (!existsSync24(absPath)) return [];
23635
- const source = readFileSync20(absPath, "utf-8");
25853
+ if (!existsSync25(absPath)) return [];
25854
+ const source = readFileSync23(absPath, "utf-8");
23636
25855
  const procedures = [];
23637
25856
  const seen = /* @__PURE__ */ new Set();
23638
25857
  const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
@@ -23661,21 +25880,21 @@ function findUICallSites(routerKey, procedureName) {
23661
25880
  ];
23662
25881
  const searchPattern = `api.${routerKey}.${procedureName}`;
23663
25882
  for (const dir of searchDirs) {
23664
- if (!existsSync24(dir)) continue;
25883
+ if (!existsSync25(dir)) continue;
23665
25884
  searchDirectory(dir, searchPattern, callSites);
23666
25885
  }
23667
25886
  return callSites;
23668
25887
  }
23669
25888
  function searchDirectory(dir, pattern, results) {
23670
- const entries = readdirSync12(dir, { withFileTypes: true });
25889
+ const entries = readdirSync15(dir, { withFileTypes: true });
23671
25890
  for (const entry of entries) {
23672
- const fullPath = join10(dir, entry.name);
25891
+ const fullPath = join13(dir, entry.name);
23673
25892
  if (entry.isDirectory()) {
23674
25893
  if (entry.name === "node_modules" || entry.name === ".next") continue;
23675
25894
  searchDirectory(fullPath, pattern, results);
23676
25895
  } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
23677
25896
  try {
23678
- const source = readFileSync20(fullPath, "utf-8");
25897
+ const source = readFileSync23(fullPath, "utf-8");
23679
25898
  const lines = source.split("\n");
23680
25899
  for (let i2 = 0; i2 < lines.length; i2++) {
23681
25900
  if (lines[i2].includes(pattern)) {
@@ -23738,7 +25957,7 @@ var init_trpc_index = __esm({
23738
25957
  });
23739
25958
 
23740
25959
  // src/page-deps.ts
23741
- import { readFileSync as readFileSync21, existsSync as existsSync25 } from "fs";
25960
+ import { readFileSync as readFileSync24, existsSync as existsSync26 } from "fs";
23742
25961
  import { resolve as resolve22 } from "path";
23743
25962
  function deriveRoute(pageFile) {
23744
25963
  let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
@@ -23778,9 +25997,9 @@ function findRouterCalls(files) {
23778
25997
  const projectRoot = getProjectRoot();
23779
25998
  for (const file of files) {
23780
25999
  const absPath = ensureWithinRoot(resolve22(projectRoot, file), projectRoot);
23781
- if (!existsSync25(absPath)) continue;
26000
+ if (!existsSync26(absPath)) continue;
23782
26001
  try {
23783
- const source = readFileSync21(absPath, "utf-8");
26002
+ const source = readFileSync24(absPath, "utf-8");
23784
26003
  const apiCallRegex = /api\.(\w+)\.\w+/g;
23785
26004
  let match;
23786
26005
  while ((match = apiCallRegex.exec(source)) !== null) {
@@ -23799,9 +26018,9 @@ function findTablesFromRouters(routerNames, dataDb2) {
23799
26018
  ).all(routerName);
23800
26019
  for (const proc of procs) {
23801
26020
  const absPath = ensureWithinRoot(resolve22(getProjectRoot(), proc.router_file), getProjectRoot());
23802
- if (!existsSync25(absPath)) continue;
26021
+ if (!existsSync26(absPath)) continue;
23803
26022
  try {
23804
- const source = readFileSync21(absPath, "utf-8");
26023
+ const source = readFileSync24(absPath, "utf-8");
23805
26024
  const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
23806
26025
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
23807
26026
  const tableRegex = new RegExp(regexStr + "\\.", "g");
@@ -24070,14 +26289,14 @@ var init_domains = __esm({
24070
26289
  });
24071
26290
 
24072
26291
  // src/schema-mapper.ts
24073
- import { readFileSync as readFileSync22, existsSync as existsSync26, readdirSync as readdirSync13 } from "fs";
24074
- import { join as join11 } from "path";
26292
+ import { readFileSync as readFileSync25, existsSync as existsSync27, readdirSync as readdirSync16 } from "fs";
26293
+ import { join as join14 } from "path";
24075
26294
  function parsePrismaSchema() {
24076
26295
  const schemaPath = getResolvedPaths().prismaSchemaPath;
24077
- if (!existsSync26(schemaPath)) {
26296
+ if (!existsSync27(schemaPath)) {
24078
26297
  throw new Error(`Prisma schema not found at ${schemaPath}`);
24079
26298
  }
24080
- const source = readFileSync22(schemaPath, "utf-8");
26299
+ const source = readFileSync25(schemaPath, "utf-8");
24081
26300
  const models = [];
24082
26301
  const sourceLines = source.split("\n");
24083
26302
  let i2 = 0;
@@ -24135,14 +26354,14 @@ function toSnakeCase(str) {
24135
26354
  function findColumnUsageInRouters(tableName) {
24136
26355
  const usage = /* @__PURE__ */ new Map();
24137
26356
  const routersDir = getResolvedPaths().routersDir;
24138
- if (!existsSync26(routersDir)) return usage;
26357
+ if (!existsSync27(routersDir)) return usage;
24139
26358
  scanDirectory(routersDir, tableName, usage);
24140
26359
  return usage;
24141
26360
  }
24142
26361
  function scanDirectory(dir, tableName, usage) {
24143
- const entries = readdirSync13(dir, { withFileTypes: true });
26362
+ const entries = readdirSync16(dir, { withFileTypes: true });
24144
26363
  for (const entry of entries) {
24145
- const fullPath = join11(dir, entry.name);
26364
+ const fullPath = join14(dir, entry.name);
24146
26365
  if (entry.isDirectory()) {
24147
26366
  scanDirectory(fullPath, tableName, usage);
24148
26367
  } else if (entry.name.endsWith(".ts")) {
@@ -24152,7 +26371,7 @@ function scanDirectory(dir, tableName, usage) {
24152
26371
  }
24153
26372
  function scanFile(absPath, tableName, usage) {
24154
26373
  try {
24155
- const source = readFileSync22(absPath, "utf-8");
26374
+ const source = readFileSync25(absPath, "utf-8");
24156
26375
  if (!source.includes(tableName)) return;
24157
26376
  const relPath = absPath.slice(getProjectRoot().length + 1);
24158
26377
  const lines = source.split("\n");
@@ -24197,15 +26416,15 @@ function detectMismatches(models) {
24197
26416
  }
24198
26417
  function findFilesUsingColumn(dir, column, tableName) {
24199
26418
  const result = [];
24200
- if (!existsSync26(dir)) return result;
24201
- const entries = readdirSync13(dir, { withFileTypes: true });
26419
+ if (!existsSync27(dir)) return result;
26420
+ const entries = readdirSync16(dir, { withFileTypes: true });
24202
26421
  for (const entry of entries) {
24203
- const fullPath = join11(dir, entry.name);
26422
+ const fullPath = join14(dir, entry.name);
24204
26423
  if (entry.isDirectory()) {
24205
26424
  result.push(...findFilesUsingColumn(fullPath, column, tableName));
24206
26425
  } else if (entry.name.endsWith(".ts")) {
24207
26426
  try {
24208
- const source = readFileSync22(fullPath, "utf-8");
26427
+ const source = readFileSync25(fullPath, "utf-8");
24209
26428
  if (source.includes(tableName) && source.includes(column)) {
24210
26429
  result.push(fullPath.slice(getProjectRoot().length + 1));
24211
26430
  }
@@ -24350,48 +26569,48 @@ var init_import_parser = __esm({
24350
26569
  });
24351
26570
 
24352
26571
  // src/python/import-resolver.ts
24353
- import { readFileSync as readFileSync23, existsSync as existsSync27, readdirSync as readdirSync14 } from "fs";
24354
- import { resolve as resolve24, join as join12, relative as relative5, dirname as dirname12 } from "path";
26572
+ import { readFileSync as readFileSync26, existsSync as existsSync28, readdirSync as readdirSync17 } from "fs";
26573
+ import { resolve as resolve24, join as join15, relative as relative6, dirname as dirname13 } from "path";
24355
26574
  function resolvePythonModulePath(module2, fromFile, pythonRoot, level) {
24356
26575
  const projectRoot = getProjectRoot();
24357
26576
  if (level > 0) {
24358
- let baseDir = dirname12(resolve24(projectRoot, fromFile));
26577
+ let baseDir = dirname13(resolve24(projectRoot, fromFile));
24359
26578
  for (let i2 = 1; i2 < level; i2++) {
24360
- baseDir = dirname12(baseDir);
26579
+ baseDir = dirname13(baseDir);
24361
26580
  }
24362
26581
  const modulePart = module2.replace(/^\.+/, "");
24363
26582
  if (modulePart) {
24364
26583
  const parts3 = modulePart.split(".");
24365
- return tryResolvePythonPath(join12(baseDir, ...parts3), projectRoot);
26584
+ return tryResolvePythonPath(join15(baseDir, ...parts3), projectRoot);
24366
26585
  }
24367
26586
  return tryResolvePythonPath(baseDir, projectRoot);
24368
26587
  }
24369
26588
  const parts2 = module2.split(".");
24370
- const candidate = join12(resolve24(projectRoot, pythonRoot), ...parts2);
26589
+ const candidate = join15(resolve24(projectRoot, pythonRoot), ...parts2);
24371
26590
  return tryResolvePythonPath(candidate, projectRoot);
24372
26591
  }
24373
26592
  function tryResolvePythonPath(basePath, projectRoot) {
24374
- if (existsSync27(basePath + ".py")) {
24375
- return relative5(projectRoot, basePath + ".py");
26593
+ if (existsSync28(basePath + ".py")) {
26594
+ return relative6(projectRoot, basePath + ".py");
24376
26595
  }
24377
- if (existsSync27(join12(basePath, "__init__.py"))) {
24378
- return relative5(projectRoot, join12(basePath, "__init__.py"));
26596
+ if (existsSync28(join15(basePath, "__init__.py"))) {
26597
+ return relative6(projectRoot, join15(basePath, "__init__.py"));
24379
26598
  }
24380
- if (basePath.endsWith(".py") && existsSync27(basePath)) {
24381
- return relative5(projectRoot, basePath);
26599
+ if (basePath.endsWith(".py") && existsSync28(basePath)) {
26600
+ return relative6(projectRoot, basePath);
24382
26601
  }
24383
26602
  return null;
24384
26603
  }
24385
26604
  function walkPythonFiles(dir, excludeDirs) {
24386
26605
  const files = [];
24387
26606
  try {
24388
- const entries = readdirSync14(dir, { withFileTypes: true });
26607
+ const entries = readdirSync17(dir, { withFileTypes: true });
24389
26608
  for (const entry of entries) {
24390
26609
  if (entry.isDirectory()) {
24391
26610
  if (excludeDirs.includes(entry.name)) continue;
24392
- files.push(...walkPythonFiles(join12(dir, entry.name), excludeDirs));
26611
+ files.push(...walkPythonFiles(join15(dir, entry.name), excludeDirs));
24393
26612
  } else if (entry.name.endsWith(".py")) {
24394
- files.push(join12(dir, entry.name));
26613
+ files.push(join15(dir, entry.name));
24395
26614
  }
24396
26615
  }
24397
26616
  } catch {
@@ -24414,10 +26633,10 @@ function buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__
24414
26633
  });
24415
26634
  const batch = [];
24416
26635
  for (const absFile of files) {
24417
- const relFile = relative5(projectRoot, absFile);
26636
+ const relFile = relative6(projectRoot, absFile);
24418
26637
  let source;
24419
26638
  try {
24420
- source = readFileSync23(absFile, "utf-8");
26639
+ source = readFileSync26(absFile, "utf-8");
24421
26640
  } catch {
24422
26641
  continue;
24423
26642
  }
@@ -24683,18 +26902,18 @@ var init_route_parser = __esm({
24683
26902
  });
24684
26903
 
24685
26904
  // src/python/route-indexer.ts
24686
- import { readFileSync as readFileSync24, readdirSync as readdirSync15 } from "fs";
24687
- import { join as join13, relative as relative6 } from "path";
26905
+ import { readFileSync as readFileSync27, readdirSync as readdirSync18 } from "fs";
26906
+ import { join as join16, relative as relative7 } from "path";
24688
26907
  function walkPyFiles(dir, excludeDirs) {
24689
26908
  const files = [];
24690
26909
  try {
24691
- const entries = readdirSync15(dir, { withFileTypes: true });
26910
+ const entries = readdirSync18(dir, { withFileTypes: true });
24692
26911
  for (const entry of entries) {
24693
26912
  if (entry.isDirectory()) {
24694
26913
  if (excludeDirs.includes(entry.name)) continue;
24695
- files.push(...walkPyFiles(join13(dir, entry.name), excludeDirs));
26914
+ files.push(...walkPyFiles(join16(dir, entry.name), excludeDirs));
24696
26915
  } else if (entry.name.endsWith(".py")) {
24697
- files.push(join13(dir, entry.name));
26916
+ files.push(join16(dir, entry.name));
24698
26917
  }
24699
26918
  }
24700
26919
  } catch {
@@ -24703,7 +26922,7 @@ function walkPyFiles(dir, excludeDirs) {
24703
26922
  }
24704
26923
  function buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
24705
26924
  const projectRoot = getProjectRoot();
24706
- const absRoot = join13(projectRoot, pythonRoot);
26925
+ const absRoot = join16(projectRoot, pythonRoot);
24707
26926
  dataDb2.exec("DELETE FROM massu_py_routes");
24708
26927
  dataDb2.exec("DELETE FROM massu_py_route_callers");
24709
26928
  const insertStmt = dataDb2.prepare(
@@ -24713,10 +26932,10 @@ function buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
24713
26932
  let count = 0;
24714
26933
  dataDb2.transaction(() => {
24715
26934
  for (const absFile of files) {
24716
- const relFile = relative6(projectRoot, absFile);
26935
+ const relFile = relative7(projectRoot, absFile);
24717
26936
  let source;
24718
26937
  try {
24719
- source = readFileSync24(absFile, "utf-8");
26938
+ source = readFileSync27(absFile, "utf-8");
24720
26939
  } catch {
24721
26940
  continue;
24722
26941
  }
@@ -24927,18 +27146,18 @@ var init_model_parser = __esm({
24927
27146
  });
24928
27147
 
24929
27148
  // src/python/model-indexer.ts
24930
- import { readFileSync as readFileSync25, readdirSync as readdirSync16 } from "fs";
24931
- import { join as join14, relative as relative7 } from "path";
27149
+ import { readFileSync as readFileSync28, readdirSync as readdirSync19 } from "fs";
27150
+ import { join as join17, relative as relative8 } from "path";
24932
27151
  function walkPyFiles2(dir, excludeDirs) {
24933
27152
  const files = [];
24934
27153
  try {
24935
- const entries = readdirSync16(dir, { withFileTypes: true });
27154
+ const entries = readdirSync19(dir, { withFileTypes: true });
24936
27155
  for (const entry of entries) {
24937
27156
  if (entry.isDirectory()) {
24938
27157
  if (excludeDirs.includes(entry.name)) continue;
24939
- files.push(...walkPyFiles2(join14(dir, entry.name), excludeDirs));
27158
+ files.push(...walkPyFiles2(join17(dir, entry.name), excludeDirs));
24940
27159
  } else if (entry.name.endsWith(".py")) {
24941
- files.push(join14(dir, entry.name));
27160
+ files.push(join17(dir, entry.name));
24942
27161
  }
24943
27162
  }
24944
27163
  } catch {
@@ -24947,7 +27166,7 @@ function walkPyFiles2(dir, excludeDirs) {
24947
27166
  }
24948
27167
  function buildPythonModelIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
24949
27168
  const projectRoot = getProjectRoot();
24950
- const absRoot = join14(projectRoot, pythonRoot);
27169
+ const absRoot = join17(projectRoot, pythonRoot);
24951
27170
  dataDb2.exec("DELETE FROM massu_py_models");
24952
27171
  dataDb2.exec("DELETE FROM massu_py_fk_edges");
24953
27172
  const insertModel = dataDb2.prepare(
@@ -24960,10 +27179,10 @@ function buildPythonModelIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
24960
27179
  let count = 0;
24961
27180
  dataDb2.transaction(() => {
24962
27181
  for (const absFile of files) {
24963
- const relFile = relative7(projectRoot, absFile);
27182
+ const relFile = relative8(projectRoot, absFile);
24964
27183
  let source;
24965
27184
  try {
24966
- source = readFileSync25(absFile, "utf-8");
27185
+ source = readFileSync28(absFile, "utf-8");
24967
27186
  } catch {
24968
27187
  continue;
24969
27188
  }
@@ -25223,19 +27442,19 @@ var init_migration_parser = __esm({
25223
27442
  });
25224
27443
 
25225
27444
  // src/python/migration-indexer.ts
25226
- import { readFileSync as readFileSync26, readdirSync as readdirSync17 } from "fs";
25227
- import { join as join15, relative as relative8 } from "path";
27445
+ import { readFileSync as readFileSync29, readdirSync as readdirSync20 } from "fs";
27446
+ import { join as join18, relative as relative9 } from "path";
25228
27447
  function buildPythonMigrationIndex(dataDb2, alembicDir) {
25229
27448
  const projectRoot = getProjectRoot();
25230
- const absDir = join15(projectRoot, alembicDir);
27449
+ const absDir = join18(projectRoot, alembicDir);
25231
27450
  dataDb2.exec("DELETE FROM massu_py_migrations");
25232
- const versionsDir = join15(absDir, "versions");
27451
+ const versionsDir = join18(absDir, "versions");
25233
27452
  let files = [];
25234
27453
  try {
25235
- files = readdirSync17(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join15(versionsDir, f2));
27454
+ files = readdirSync20(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join18(versionsDir, f2));
25236
27455
  } catch {
25237
27456
  try {
25238
- files = readdirSync17(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join15(absDir, f2));
27457
+ files = readdirSync20(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join18(absDir, f2));
25239
27458
  } catch {
25240
27459
  }
25241
27460
  }
@@ -25249,7 +27468,7 @@ function buildPythonMigrationIndex(dataDb2, alembicDir) {
25249
27468
  for (const absFile of files) {
25250
27469
  let source;
25251
27470
  try {
25252
- source = readFileSync26(absFile, "utf-8");
27471
+ source = readFileSync29(absFile, "utf-8");
25253
27472
  } catch {
25254
27473
  continue;
25255
27474
  }
@@ -25260,7 +27479,7 @@ function buildPythonMigrationIndex(dataDb2, alembicDir) {
25260
27479
  rows.push({
25261
27480
  revision: parsed.revision,
25262
27481
  downRevision: parsed.downRevision,
25263
- file: relative8(projectRoot, absFile),
27482
+ file: relative9(projectRoot, absFile),
25264
27483
  description: parsed.description,
25265
27484
  operations: JSON.stringify(parsed.operations)
25266
27485
  });
@@ -25283,12 +27502,12 @@ var init_migration_indexer = __esm({
25283
27502
  });
25284
27503
 
25285
27504
  // src/python/coupling-detector.ts
25286
- import { readFileSync as readFileSync27, readdirSync as readdirSync18 } from "fs";
25287
- import { join as join16, relative as relative9 } from "path";
27505
+ import { readFileSync as readFileSync30, readdirSync as readdirSync21 } from "fs";
27506
+ import { join as join19, relative as relative10 } from "path";
25288
27507
  function buildPythonCouplingIndex(dataDb2) {
25289
27508
  const projectRoot = getProjectRoot();
25290
27509
  const config = getConfig();
25291
- const srcDir = join16(projectRoot, config.paths.source);
27510
+ const srcDir = join19(projectRoot, config.paths.source);
25292
27511
  const routes = dataDb2.prepare("SELECT id, method, path FROM massu_py_routes").all();
25293
27512
  if (routes.length === 0) return 0;
25294
27513
  dataDb2.exec("DELETE FROM massu_py_route_callers");
@@ -25317,10 +27536,10 @@ function buildPythonCouplingIndex(dataDb2) {
25317
27536
  ];
25318
27537
  dataDb2.transaction(() => {
25319
27538
  for (const absFile of frontendFiles) {
25320
- const relFile = relative9(projectRoot, absFile);
27539
+ const relFile = relative10(projectRoot, absFile);
25321
27540
  let source;
25322
27541
  try {
25323
- source = readFileSync27(absFile, "utf-8");
27542
+ source = readFileSync30(absFile, "utf-8");
25324
27543
  } catch {
25325
27544
  continue;
25326
27545
  }
@@ -25348,13 +27567,13 @@ function walkFrontendFiles(dir) {
25348
27567
  const files = [];
25349
27568
  const exclude = ["node_modules", ".next", "dist", ".git", "__pycache__", ".venv", "venv"];
25350
27569
  try {
25351
- const entries = readdirSync18(dir, { withFileTypes: true });
27570
+ const entries = readdirSync21(dir, { withFileTypes: true });
25352
27571
  for (const entry of entries) {
25353
27572
  if (entry.isDirectory()) {
25354
27573
  if (exclude.includes(entry.name)) continue;
25355
- files.push(...walkFrontendFiles(join16(dir, entry.name)));
27574
+ files.push(...walkFrontendFiles(join19(dir, entry.name)));
25356
27575
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
25357
- files.push(join16(dir, entry.name));
27576
+ files.push(join19(dir, entry.name));
25358
27577
  }
25359
27578
  }
25360
27579
  } catch {
@@ -25725,8 +27944,8 @@ var init_memory_tools = __esm({
25725
27944
  });
25726
27945
 
25727
27946
  // src/docs-tools.ts
25728
- import { readFileSync as readFileSync28, existsSync as existsSync28 } from "fs";
25729
- import { resolve as resolve25, basename as basename5 } from "path";
27947
+ import { readFileSync as readFileSync31, existsSync as existsSync29 } from "fs";
27948
+ import { resolve as resolve25, basename as basename6 } from "path";
25730
27949
  function p3(baseName) {
25731
27950
  return `${getConfig().toolPrefix}_${baseName}`;
25732
27951
  }
@@ -25781,10 +28000,10 @@ function handleDocsToolCall(name2, args3) {
25781
28000
  }
25782
28001
  function loadDocsMap() {
25783
28002
  const mapPath = getResolvedPaths().docsMapPath;
25784
- if (!existsSync28(mapPath)) {
28003
+ if (!existsSync29(mapPath)) {
25785
28004
  throw new Error(`docs-map.json not found at ${mapPath}`);
25786
28005
  }
25787
- return JSON.parse(readFileSync28(mapPath, "utf-8"));
28006
+ return JSON.parse(readFileSync31(mapPath, "utf-8"));
25788
28007
  }
25789
28008
  function matchesPattern(filePath, pattern) {
25790
28009
  const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
@@ -25793,7 +28012,7 @@ function matchesPattern(filePath, pattern) {
25793
28012
  function findAffectedMappings(docsMap, changedFiles) {
25794
28013
  const affected = /* @__PURE__ */ new Map();
25795
28014
  for (const file of changedFiles) {
25796
- const fileName = basename5(file);
28015
+ const fileName = basename6(file);
25797
28016
  for (const mapping of docsMap.mappings) {
25798
28017
  let matched = false;
25799
28018
  for (const routePattern of mapping.appRoutes) {
@@ -25855,12 +28074,12 @@ function extractFrontmatter(content) {
25855
28074
  function extractProcedureNames(routerPath) {
25856
28075
  const root = getProjectRoot();
25857
28076
  const absPath = ensureWithinRoot(resolve25(getResolvedPaths().srcDir, "..", routerPath), root);
25858
- if (!existsSync28(absPath)) {
25859
- const altPath = ensureWithinRoot(resolve25(getResolvedPaths().srcDir, "../server/api/routers", basename5(routerPath)), root);
25860
- if (!existsSync28(altPath)) return [];
25861
- return extractProcedureNamesFromContent(readFileSync28(altPath, "utf-8"));
28077
+ if (!existsSync29(absPath)) {
28078
+ const altPath = ensureWithinRoot(resolve25(getResolvedPaths().srcDir, "../server/api/routers", basename6(routerPath)), root);
28079
+ if (!existsSync29(altPath)) return [];
28080
+ return extractProcedureNamesFromContent(readFileSync31(altPath, "utf-8"));
25862
28081
  }
25863
- return extractProcedureNamesFromContent(readFileSync28(absPath, "utf-8"));
28082
+ return extractProcedureNamesFromContent(readFileSync31(absPath, "utf-8"));
25864
28083
  }
25865
28084
  function extractProcedureNamesFromContent(content) {
25866
28085
  const procRegex = /\.(?:query|mutation)\s*\(/g;
@@ -25901,7 +28120,7 @@ function handleDocsAudit(args3) {
25901
28120
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
25902
28121
  if (!mapping) continue;
25903
28122
  const helpPagePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
25904
- if (!existsSync28(helpPagePath)) {
28123
+ if (!existsSync29(helpPagePath)) {
25905
28124
  results.push({
25906
28125
  helpPage: mapping.helpPage,
25907
28126
  mappingId,
@@ -25913,12 +28132,12 @@ function handleDocsAudit(args3) {
25913
28132
  });
25914
28133
  continue;
25915
28134
  }
25916
- const content = readFileSync28(helpPagePath, "utf-8");
28135
+ const content = readFileSync31(helpPagePath, "utf-8");
25917
28136
  const sections = extractSections(content);
25918
28137
  const frontmatter = extractFrontmatter(content);
25919
28138
  const staleReasons = [];
25920
28139
  for (const file of triggeringFiles) {
25921
- const fileName = basename5(file);
28140
+ const fileName = basename6(file);
25922
28141
  if (mapping.routers.includes(fileName)) {
25923
28142
  const procedures = extractProcedureNames(file);
25924
28143
  const undocumented = procedures.filter((p19) => !contentMentions(content, p19));
@@ -25949,13 +28168,13 @@ function handleDocsAudit(args3) {
25949
28168
  reason: staleReasons.length > 0 ? staleReasons.join("; ") : "Content appears current",
25950
28169
  sections,
25951
28170
  changedFiles: triggeringFiles,
25952
- suggestedAction: status === "STALE" ? `Review and update ${mapping.helpPage} to reflect changes in: ${triggeringFiles.map((f2) => basename5(f2)).join(", ")}` : "No action needed"
28171
+ suggestedAction: status === "STALE" ? `Review and update ${mapping.helpPage} to reflect changes in: ${triggeringFiles.map((f2) => basename6(f2)).join(", ")}` : "No action needed"
25953
28172
  });
25954
28173
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
25955
28174
  if (parentId === mappingId) {
25956
28175
  const guidePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
25957
- if (existsSync28(guidePath)) {
25958
- const guideContent = readFileSync28(guidePath, "utf-8");
28176
+ if (existsSync29(guidePath)) {
28177
+ const guideContent = readFileSync31(guidePath, "utf-8");
25959
28178
  const guideFrontmatter = extractFrontmatter(guideContent);
25960
28179
  if (!guideFrontmatter?.lastVerified || status === "STALE") {
25961
28180
  results.push({
@@ -25989,13 +28208,13 @@ function handleDocsCoverage(args3) {
25989
28208
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
25990
28209
  for (const mapping of mappings) {
25991
28210
  const helpPagePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
25992
- const exists = existsSync28(helpPagePath);
28211
+ const exists = existsSync29(helpPagePath);
25993
28212
  let hasContent = false;
25994
28213
  let lineCount = 0;
25995
28214
  let lastVerified = null;
25996
28215
  let status = null;
25997
28216
  if (exists) {
25998
- const content = readFileSync28(helpPagePath, "utf-8");
28217
+ const content = readFileSync31(helpPagePath, "utf-8");
25999
28218
  lineCount = content.split("\n").length;
26000
28219
  hasContent = lineCount > 10;
26001
28220
  const frontmatter = extractFrontmatter(content);
@@ -26336,7 +28555,7 @@ var init_observability_tools = __esm({
26336
28555
  });
26337
28556
 
26338
28557
  // src/sentinel-db.ts
26339
- import { existsSync as existsSync29 } from "fs";
28558
+ import { existsSync as existsSync30 } from "fs";
26340
28559
  import { resolve as resolve26 } from "path";
26341
28560
  function parsePortalScope(raw) {
26342
28561
  if (!raw) return [];
@@ -26574,22 +28793,22 @@ function validateFeatures(db, domainFilter) {
26574
28793
  const missingPages = [];
26575
28794
  for (const comp of components) {
26576
28795
  const absPath = resolve26(PROJECT_ROOT, comp.component_file);
26577
- if (!existsSync29(absPath)) {
28796
+ if (!existsSync30(absPath)) {
26578
28797
  missingComponents.push(comp.component_file);
26579
28798
  }
26580
28799
  }
26581
28800
  for (const proc of procedures) {
26582
28801
  const routerPath = resolve26(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
26583
- if (!existsSync29(routerPath)) {
28802
+ if (!existsSync30(routerPath)) {
26584
28803
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
26585
28804
  }
26586
28805
  }
26587
28806
  for (const page of pages) {
26588
28807
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
26589
28808
  const absPath = resolve26(PROJECT_ROOT, routeToPath);
26590
- if (page.page_route.startsWith("/") && !existsSync29(absPath)) {
28809
+ if (page.page_route.startsWith("/") && !existsSync30(absPath)) {
26591
28810
  const altPath = resolve26(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
26592
- if (!existsSync29(altPath)) {
28811
+ if (!existsSync30(altPath)) {
26593
28812
  missingPages.push(page.page_route);
26594
28813
  }
26595
28814
  }
@@ -27113,8 +29332,8 @@ var init_sentinel_tools = __esm({
27113
29332
  });
27114
29333
 
27115
29334
  // src/sentinel-scanner.ts
27116
- import { readFileSync as readFileSync29, existsSync as existsSync30, readdirSync as readdirSync19, statSync as statSync11 } from "fs";
27117
- import { resolve as resolve27, join as join17, basename as basename6, dirname as dirname13, relative as relative10 } from "path";
29335
+ import { readFileSync as readFileSync32, existsSync as existsSync31, readdirSync as readdirSync22, statSync as statSync13 } from "fs";
29336
+ import { resolve as resolve27, join as join20, basename as basename7, dirname as dirname14, relative as relative11 } from "path";
27118
29337
  function inferDomain(filePath) {
27119
29338
  const domains = getConfig().domains;
27120
29339
  const path = filePath.toLowerCase();
@@ -27244,9 +29463,9 @@ function scanComponentExports(dataDb2) {
27244
29463
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
27245
29464
  const componentDirs = [];
27246
29465
  const basePath = resolve27(projectRoot, componentsBase);
27247
- if (existsSync30(basePath)) {
29466
+ if (existsSync31(basePath)) {
27248
29467
  try {
27249
- const entries = readdirSync19(basePath, { withFileTypes: true });
29468
+ const entries = readdirSync22(basePath, { withFileTypes: true });
27250
29469
  for (const entry of entries) {
27251
29470
  if (entry.isDirectory()) {
27252
29471
  componentDirs.push(componentsBase + "/" + entry.name);
@@ -27257,11 +29476,11 @@ function scanComponentExports(dataDb2) {
27257
29476
  }
27258
29477
  for (const dir of componentDirs) {
27259
29478
  const absDir = resolve27(projectRoot, dir);
27260
- if (!existsSync30(absDir)) continue;
27261
- const files = walkDir(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
29479
+ if (!existsSync31(absDir)) continue;
29480
+ const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
27262
29481
  for (const file of files) {
27263
- const relPath = relative10(projectRoot, file);
27264
- const source = readFileSync29(file, "utf-8");
29482
+ const relPath = relative11(projectRoot, file);
29483
+ const source = readFileSync32(file, "utf-8");
27265
29484
  const annotations = parseFeatureAnnotations(source);
27266
29485
  if (annotations.length > 0) {
27267
29486
  for (const ann of annotations) {
@@ -27287,7 +29506,7 @@ function scanComponentExports(dataDb2) {
27287
29506
  if (hasHandlers && exportMatch) {
27288
29507
  const componentName = exportMatch[1];
27289
29508
  const domain = inferDomain(relPath);
27290
- const subdomain = basename6(dirname13(relPath));
29509
+ const subdomain = basename7(dirname14(relPath));
27291
29510
  const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
27292
29511
  if (!annotations.some((a2) => a2.featureKey === featureKey)) {
27293
29512
  features.push({
@@ -27307,16 +29526,16 @@ function scanComponentExports(dataDb2) {
27307
29526
  }
27308
29527
  return features;
27309
29528
  }
27310
- function walkDir(dir) {
29529
+ function walkDir2(dir) {
27311
29530
  const results = [];
27312
29531
  try {
27313
- const entries = readdirSync19(dir);
29532
+ const entries = readdirSync22(dir);
27314
29533
  for (const entry of entries) {
27315
- const fullPath = join17(dir, entry);
29534
+ const fullPath = join20(dir, entry);
27316
29535
  try {
27317
- const stat = statSync11(fullPath);
29536
+ const stat = statSync13(fullPath);
27318
29537
  if (stat.isDirectory()) {
27319
- results.push(...walkDir(fullPath));
29538
+ results.push(...walkDir2(fullPath));
27320
29539
  } else {
27321
29540
  results.push(fullPath);
27322
29541
  }
@@ -28300,7 +30519,7 @@ var init_audit_trail = __esm({
28300
30519
  });
28301
30520
 
28302
30521
  // src/validation-engine.ts
28303
- import { existsSync as existsSync31, readFileSync as readFileSync30 } from "fs";
30522
+ import { existsSync as existsSync32, readFileSync as readFileSync33 } from "fs";
28304
30523
  function p10(baseName) {
28305
30524
  return `${getConfig().toolPrefix}_${baseName}`;
28306
30525
  }
@@ -28331,7 +30550,7 @@ function validateFile(filePath, projectRoot) {
28331
30550
  });
28332
30551
  return checks;
28333
30552
  }
28334
- if (!existsSync31(absPath)) {
30553
+ if (!existsSync32(absPath)) {
28335
30554
  checks.push({
28336
30555
  name: "file_exists",
28337
30556
  severity: "error",
@@ -28340,7 +30559,7 @@ function validateFile(filePath, projectRoot) {
28340
30559
  });
28341
30560
  return checks;
28342
30561
  }
28343
- const source = readFileSync30(absPath, "utf-8");
30562
+ const source = readFileSync33(absPath, "utf-8");
28344
30563
  const lines = source.split("\n");
28345
30564
  if (activeChecks.rule_compliance !== false) {
28346
30565
  for (const ruleSet of config.rules) {
@@ -28785,7 +31004,7 @@ var init_adr_generator = __esm({
28785
31004
  });
28786
31005
 
28787
31006
  // src/security-scorer.ts
28788
- import { existsSync as existsSync32, readFileSync as readFileSync31 } from "fs";
31007
+ import { existsSync as existsSync33, readFileSync as readFileSync34 } from "fs";
28789
31008
  function p12(baseName) {
28790
31009
  return `${getConfig().toolPrefix}_${baseName}`;
28791
31010
  }
@@ -28809,12 +31028,12 @@ function scoreFileSecurity(filePath, projectRoot) {
28809
31028
  }]
28810
31029
  };
28811
31030
  }
28812
- if (!existsSync32(absPath)) {
31031
+ if (!existsSync33(absPath)) {
28813
31032
  return { riskScore: 0, findings: [] };
28814
31033
  }
28815
31034
  let source;
28816
31035
  try {
28817
- source = readFileSync31(absPath, "utf-8");
31036
+ source = readFileSync34(absPath, "utf-8");
28818
31037
  } catch {
28819
31038
  return { riskScore: 0, findings: [] };
28820
31039
  }
@@ -29103,7 +31322,7 @@ var init_security_scorer = __esm({
29103
31322
  });
29104
31323
 
29105
31324
  // src/dependency-scorer.ts
29106
- import { existsSync as existsSync33, readFileSync as readFileSync32 } from "fs";
31325
+ import { existsSync as existsSync34, readFileSync as readFileSync35 } from "fs";
29107
31326
  import { resolve as resolve28 } from "path";
29108
31327
  function p13(baseName) {
29109
31328
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -29137,9 +31356,9 @@ function calculateDepRisk(factors) {
29137
31356
  }
29138
31357
  function getInstalledPackages(projectRoot) {
29139
31358
  const pkgPath = resolve28(projectRoot, "package.json");
29140
- if (!existsSync33(pkgPath)) return /* @__PURE__ */ new Map();
31359
+ if (!existsSync34(pkgPath)) return /* @__PURE__ */ new Map();
29141
31360
  try {
29142
- const pkg = JSON.parse(readFileSync32(pkgPath, "utf-8"));
31361
+ const pkg = JSON.parse(readFileSync35(pkgPath, "utf-8"));
29143
31362
  const packages = /* @__PURE__ */ new Map();
29144
31363
  for (const [name2, version] of Object.entries(pkg.dependencies ?? {})) {
29145
31364
  packages.set(name2, version);
@@ -29741,9 +31960,9 @@ var init_regression_detector = __esm({
29741
31960
  });
29742
31961
 
29743
31962
  // src/knowledge-indexer.ts
29744
- import { createHash as createHash7 } from "crypto";
29745
- import { readFileSync as readFileSync33, readdirSync as readdirSync20, statSync as statSync12, existsSync as existsSync34 } from "fs";
29746
- import { resolve as resolve29, relative as relative11, basename as basename7, extname } from "path";
31963
+ import { createHash as createHash8 } from "crypto";
31964
+ import { readFileSync as readFileSync36, readdirSync as readdirSync23, statSync as statSync14, existsSync as existsSync35 } from "fs";
31965
+ import { resolve as resolve29, relative as relative12, basename as basename8, extname as extname2 } from "path";
29747
31966
  function getKnowledgePaths() {
29748
31967
  const resolved = getResolvedPaths();
29749
31968
  const config = getConfig();
@@ -29769,7 +31988,7 @@ function discoverMarkdownFiles(baseDir) {
29769
31988
  const files = [];
29770
31989
  function walk(dir) {
29771
31990
  try {
29772
- const entries = readdirSync20(dir, { withFileTypes: true });
31991
+ const entries = readdirSync23(dir, { withFileTypes: true });
29773
31992
  for (const entry of entries) {
29774
31993
  const fullPath = resolve29(dir, entry.name);
29775
31994
  if (entry.isDirectory()) {
@@ -29777,7 +31996,7 @@ function discoverMarkdownFiles(baseDir) {
29777
31996
  if (entry.name === "archive" && dir.includes("status")) continue;
29778
31997
  if (entry.name === "node_modules") continue;
29779
31998
  walk(fullPath);
29780
- } else if (entry.isFile() && extname(entry.name) === ".md") {
31999
+ } else if (entry.isFile() && extname2(entry.name) === ".md") {
29781
32000
  files.push(fullPath);
29782
32001
  }
29783
32002
  }
@@ -29791,7 +32010,7 @@ function categorizeFile(filePath) {
29791
32010
  const paths = getKnowledgePaths();
29792
32011
  if (filePath.startsWith(paths.plansDir)) return "plan";
29793
32012
  if (filePath.startsWith(paths.docsDir)) {
29794
- const relFromDocs = relative11(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
32013
+ const relFromDocs = relative12(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
29795
32014
  if (relFromDocs.startsWith("plans/")) return "plan";
29796
32015
  if (relFromDocs.includes("architecture")) return "architecture";
29797
32016
  if (relFromDocs.includes("security")) return "security";
@@ -29807,7 +32026,7 @@ function categorizeFile(filePath) {
29807
32026
  }
29808
32027
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
29809
32028
  if (filePath.includes(`${claudeDirName}/projects/`) && filePath.includes("/memory/")) return "memory";
29810
- const rel = relative11(paths.claudeDir, filePath).replace(/\\/g, "/");
32029
+ const rel = relative12(paths.claudeDir, filePath).replace(/\\/g, "/");
29811
32030
  const firstDir = rel.split("/")[0];
29812
32031
  const knownCategories = getConfig().conventions?.knowledgeCategories ?? [
29813
32032
  "patterns",
@@ -29829,7 +32048,7 @@ function categorizeFile(filePath) {
29829
32048
  return "root";
29830
32049
  }
29831
32050
  function hashContent2(content) {
29832
- return createHash7("sha256").update(content).digest("hex");
32051
+ return createHash8("sha256").update(content).digest("hex");
29833
32052
  }
29834
32053
  function parseCRTable(content) {
29835
32054
  const rules = [];
@@ -29948,7 +32167,7 @@ function parseCorrections(content) {
29948
32167
  function extractTitle(content, filePath) {
29949
32168
  const h1Match = content.match(/^#\s+(.+)/m);
29950
32169
  if (h1Match) return h1Match[1].trim();
29951
- return basename7(filePath, ".md");
32170
+ return basename8(filePath, ".md");
29952
32171
  }
29953
32172
  function extractDescription2(content) {
29954
32173
  const fmMatch = content.match(/^---\s*\n[\s\S]*?description:\s*"?([^"\n]+)"?\s*\n[\s\S]*?---/);
@@ -29977,7 +32196,7 @@ function buildCrossReferences(db) {
29977
32196
  }
29978
32197
  }
29979
32198
  if (rule.reference_path) {
29980
- const patternName = basename7(rule.reference_path, ".md");
32199
+ const patternName = basename8(rule.reference_path, ".md");
29981
32200
  insertEdge.run("cr", rule.rule_id, "pattern", patternName, "references");
29982
32201
  edgeCount++;
29983
32202
  }
@@ -30049,11 +32268,11 @@ function indexAllKnowledge(db) {
30049
32268
  files.push(...memFiles);
30050
32269
  } catch {
30051
32270
  }
30052
- if (existsSync34(paths.plansDir)) {
32271
+ if (existsSync35(paths.plansDir)) {
30053
32272
  const planFiles = discoverMarkdownFiles(paths.plansDir);
30054
32273
  files.push(...planFiles);
30055
32274
  }
30056
- if (existsSync34(paths.docsDir)) {
32275
+ if (existsSync35(paths.docsDir)) {
30057
32276
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
30058
32277
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
30059
32278
  files.push(...docsFiles);
@@ -30096,10 +32315,10 @@ function indexAllKnowledge(db) {
30096
32315
  } catch {
30097
32316
  }
30098
32317
  for (const filePath of files) {
30099
- if (!existsSync34(filePath)) continue;
30100
- const content = readFileSync33(filePath, "utf-8");
32318
+ if (!existsSync35(filePath)) continue;
32319
+ const content = readFileSync36(filePath, "utf-8");
30101
32320
  const hash = hashContent2(content);
30102
- const relPath = filePath.startsWith(paths.claudeDir) ? relative11(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative11(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative11(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative11(paths.memoryDir, filePath)}` : basename7(filePath);
32321
+ const relPath = filePath.startsWith(paths.claudeDir) ? relative12(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative12(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative12(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative12(paths.memoryDir, filePath)}` : basename8(filePath);
30103
32322
  const category = categorizeFile(filePath);
30104
32323
  const title = extractTitle(content, filePath);
30105
32324
  const description = extractDescription2(content);
@@ -30113,10 +32332,10 @@ function indexAllKnowledge(db) {
30113
32332
  stats.chunksCreated++;
30114
32333
  }
30115
32334
  }
30116
- const fileName = basename7(filePath);
32335
+ const fileName = basename8(filePath);
30117
32336
  const fileNameLower = fileName.toLowerCase();
30118
32337
  const relPathLower = relPath.toLowerCase();
30119
- const claudeMdName = basename7(getResolvedPaths().claudeMdPath).toLowerCase();
32338
+ const claudeMdName = basename8(getResolvedPaths().claudeMdPath).toLowerCase();
30120
32339
  if (fileNameLower === claudeMdName || relPathLower.includes(claudeMdName)) {
30121
32340
  const crRules = parseCRTable(content);
30122
32341
  for (const rule of crRules) {
@@ -30150,7 +32369,7 @@ function indexAllKnowledge(db) {
30150
32369
  }
30151
32370
  }
30152
32371
  if (category === "commands" && fileName !== "_shared-preamble.md") {
30153
- const cmdName = basename7(filePath, ".md");
32372
+ const cmdName = basename8(filePath, ".md");
30154
32373
  insertChunk.run(docId, "command", cmdName, content.substring(0, 1e3), 1, null, JSON.stringify({ command_name: cmdName }));
30155
32374
  stats.chunksCreated++;
30156
32375
  }
@@ -30217,17 +32436,17 @@ function isKnowledgeStale(db) {
30217
32436
  files.push(...discoverMarkdownFiles(paths.memoryDir));
30218
32437
  } catch {
30219
32438
  }
30220
- if (existsSync34(paths.plansDir)) {
32439
+ if (existsSync35(paths.plansDir)) {
30221
32440
  files.push(...discoverMarkdownFiles(paths.plansDir));
30222
32441
  }
30223
- if (existsSync34(paths.docsDir)) {
32442
+ if (existsSync35(paths.docsDir)) {
30224
32443
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
30225
32444
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
30226
32445
  files.push(...docsFiles);
30227
32446
  }
30228
32447
  for (const filePath of files) {
30229
32448
  try {
30230
- const stat = statSync12(filePath);
32449
+ const stat = statSync14(filePath);
30231
32450
  if (stat.mtimeMs > lastIndexTime) return true;
30232
32451
  } catch {
30233
32452
  continue;
@@ -30249,8 +32468,8 @@ var init_knowledge_indexer = __esm({
30249
32468
  });
30250
32469
 
30251
32470
  // src/knowledge-tools.ts
30252
- import { readFileSync as readFileSync34, writeFileSync as writeFileSync4, appendFileSync as appendFileSync2, readdirSync as readdirSync21 } from "fs";
30253
- import { resolve as resolve30, basename as basename8 } from "path";
32471
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync24 } from "fs";
32472
+ import { resolve as resolve30, basename as basename9 } from "path";
30254
32473
  function p16(baseName) {
30255
32474
  return `${getConfig().toolPrefix}_${baseName}`;
30256
32475
  }
@@ -31000,14 +33219,14 @@ ${crRule ? `- **CR**: ${crRule}
31000
33219
  `;
31001
33220
  let existing = "";
31002
33221
  try {
31003
- existing = readFileSync34(correctionsPath, "utf-8");
33222
+ existing = readFileSync37(correctionsPath, "utf-8");
31004
33223
  } catch {
31005
33224
  }
31006
33225
  const archiveIdx = existing.indexOf("## Archived");
31007
33226
  if (archiveIdx > 0) {
31008
33227
  const before = existing.slice(0, archiveIdx);
31009
33228
  const after = existing.slice(archiveIdx);
31010
- writeFileSync4(correctionsPath, before + entry + after);
33229
+ writeFileSync5(correctionsPath, before + entry + after);
31011
33230
  } else {
31012
33231
  appendFileSync2(correctionsPath, entry);
31013
33232
  }
@@ -31049,7 +33268,7 @@ function handlePlan(db, args3) {
31049
33268
  AND kc.content LIKE ?
31050
33269
  ORDER BY kd.file_path DESC
31051
33270
  LIMIT 20
31052
- `).all(`%${basename8(file)}%`);
33271
+ `).all(`%${basename9(file)}%`);
31053
33272
  lines.push(`## Plans referencing \`${file}\` (${results.length} found)`);
31054
33273
  lines.push("");
31055
33274
  for (const r2 of results) {
@@ -31182,7 +33401,7 @@ function handleGaps(db, args3) {
31182
33401
  } else if (checkType === "routers") {
31183
33402
  try {
31184
33403
  const routersDir = getResolvedPaths().routersDir;
31185
- const routerFiles = readdirSync21(routersDir).filter((f2) => f2.endsWith(".ts") && !f2.startsWith("_"));
33404
+ const routerFiles = readdirSync24(routersDir).filter((f2) => f2.endsWith(".ts") && !f2.startsWith("_"));
31186
33405
  lines.push(`| Router | Knowledge Hits | Status |`);
31187
33406
  lines.push(`|--------|----------------|--------|`);
31188
33407
  for (const file of routerFiles) {
@@ -31338,13 +33557,13 @@ var init_knowledge_tools = __esm({
31338
33557
 
31339
33558
  // src/knowledge-db.ts
31340
33559
  import Database3 from "better-sqlite3";
31341
- import { dirname as dirname14 } from "path";
31342
- import { existsSync as existsSync36, mkdirSync as mkdirSync9 } from "fs";
33560
+ import { dirname as dirname15 } from "path";
33561
+ import { existsSync as existsSync37, mkdirSync as mkdirSync10 } from "fs";
31343
33562
  function getKnowledgeDb() {
31344
33563
  const dbPath = getResolvedPaths().knowledgeDbPath;
31345
- const dir = dirname14(dbPath);
31346
- if (!existsSync36(dir)) {
31347
- mkdirSync9(dir, { recursive: true });
33564
+ const dir = dirname15(dbPath);
33565
+ if (!existsSync37(dir)) {
33566
+ mkdirSync10(dir, { recursive: true });
31348
33567
  }
31349
33568
  const db = new Database3(dbPath);
31350
33569
  db.pragma("journal_mode = WAL");
@@ -32077,8 +34296,8 @@ var init_python_tools = __esm({
32077
34296
  });
32078
34297
 
32079
34298
  // src/tools.ts
32080
- import { readFileSync as readFileSync35, existsSync as existsSync37 } from "fs";
32081
- import { resolve as resolve31, basename as basename9 } from "path";
34299
+ import { readFileSync as readFileSync38, existsSync as existsSync38 } from "fs";
34300
+ import { resolve as resolve31, basename as basename10 } from "path";
32082
34301
  function prefix2() {
32083
34302
  return getConfig().toolPrefix;
32084
34303
  }
@@ -32513,8 +34732,8 @@ function handleContext(file, dataDb2, codegraphDb2) {
32513
34732
  const resolvedPaths = getResolvedPaths();
32514
34733
  const root = getProjectRoot();
32515
34734
  const absFilePath = ensureWithinRoot(resolve31(resolvedPaths.srcDir, "..", file), root);
32516
- if (existsSync37(absFilePath)) {
32517
- const fileContent = readFileSync35(absFilePath, "utf-8").slice(0, 3e3);
34735
+ if (existsSync38(absFilePath)) {
34736
+ const fileContent = readFileSync38(absFilePath, "utf-8").slice(0, 3e3);
32518
34737
  const keywords = [];
32519
34738
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
32520
34739
  if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
@@ -32608,7 +34827,7 @@ function handleContext(file, dataDb2, codegraphDb2) {
32608
34827
  WHERE o.files_involved LIKE ?
32609
34828
  ORDER BY o.importance DESC, o.created_at_epoch DESC
32610
34829
  LIMIT 5
32611
- `).all(`%${basename9(file)}%`);
34830
+ `).all(`%${basename10(file)}%`);
32612
34831
  if (fileObservations.length > 0) {
32613
34832
  lines.push("## Past Observations (This File)");
32614
34833
  for (const obs of fileObservations) {
@@ -32624,7 +34843,7 @@ function handleContext(file, dataDb2, codegraphDb2) {
32624
34843
  WHERE type = 'failed_attempt' AND files_involved LIKE ?
32625
34844
  ORDER BY recurrence_count DESC
32626
34845
  LIMIT 3
32627
- `).all(`%${basename9(file)}%`);
34846
+ `).all(`%${basename10(file)}%`);
32628
34847
  if (failures.length > 0) {
32629
34848
  lines.push("## Failed Attempts (DO NOT RETRY)");
32630
34849
  for (const f2 of failures) {
@@ -32939,10 +35158,10 @@ function handleSchema(args3) {
32939
35158
  lines.push("");
32940
35159
  const projectRoot = getProjectRoot();
32941
35160
  const absPath = ensureWithinRoot(resolve31(projectRoot, file), projectRoot);
32942
- if (!existsSync37(absPath)) {
35161
+ if (!existsSync38(absPath)) {
32943
35162
  return text17(`File not found: ${file}`);
32944
35163
  }
32945
- const source = readFileSync35(absPath, "utf-8");
35164
+ const source = readFileSync38(absPath, "utf-8");
32946
35165
  const config = getConfig();
32947
35166
  const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
32948
35167
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
@@ -33020,8 +35239,8 @@ var init_tools = __esm({
33020
35239
 
33021
35240
  // src/server.ts
33022
35241
  var server_exports = {};
33023
- import { readFileSync as readFileSync36 } from "fs";
33024
- import { resolve as resolve32, dirname as dirname15 } from "path";
35242
+ import { readFileSync as readFileSync39 } from "fs";
35243
+ import { resolve as resolve32, dirname as dirname16 } from "path";
33025
35244
  import { fileURLToPath as fileURLToPath4 } from "url";
33026
35245
  function getDb() {
33027
35246
  if (!codegraphDb) codegraphDb = getCodeGraphDb();
@@ -33113,10 +35332,10 @@ var init_server = __esm({
33113
35332
  init_tools();
33114
35333
  init_memory_db();
33115
35334
  init_license();
33116
- __dirname4 = dirname15(fileURLToPath4(import.meta.url));
35335
+ __dirname4 = dirname16(fileURLToPath4(import.meta.url));
33117
35336
  PKG_VERSION = (() => {
33118
35337
  try {
33119
- const pkg = JSON.parse(readFileSync36(resolve32(__dirname4, "..", "package.json"), "utf-8"));
35338
+ const pkg = JSON.parse(readFileSync39(resolve32(__dirname4, "..", "package.json"), "utf-8"));
33120
35339
  return pkg.version ?? "0.0.0";
33121
35340
  } catch {
33122
35341
  return "0.0.0";
@@ -33419,7 +35638,7 @@ var config_upgrade_exports = {};
33419
35638
  __export(config_upgrade_exports, {
33420
35639
  runConfigUpgrade: () => runConfigUpgrade
33421
35640
  });
33422
- import { existsSync as existsSync38, readFileSync as readFileSync37, writeFileSync as writeFileSync5, copyFileSync, unlinkSync } from "fs";
35641
+ import { existsSync as existsSync39, readFileSync as readFileSync40, writeFileSync as writeFileSync6, copyFileSync, unlinkSync as unlinkSync2 } from "fs";
33423
35642
  import { resolve as resolve33 } from "path";
33424
35643
  import { parse as parseYaml6 } from "yaml";
33425
35644
  async function runConfigUpgrade(opts = {}) {
@@ -33431,14 +35650,14 @@ async function runConfigUpgrade(opts = {}) {
33431
35650
  const err2 = opts.silent ? () => {
33432
35651
  } : (s) => process.stderr.write(s);
33433
35652
  if (opts.rollback) {
33434
- if (!existsSync38(bakPath)) {
35653
+ if (!existsSync39(bakPath)) {
33435
35654
  const message = `No backup found at ${bakPath}`;
33436
35655
  err2(message + "\n");
33437
35656
  return { exitCode: 1, action: "none", message };
33438
35657
  }
33439
35658
  try {
33440
35659
  copyFileSync(bakPath, configPath);
33441
- unlinkSync(bakPath);
35660
+ unlinkSync2(bakPath);
33442
35661
  log("Config restored from backup.\n");
33443
35662
  return { exitCode: 0, action: "rolled-back" };
33444
35663
  } catch (e2) {
@@ -33447,14 +35666,14 @@ async function runConfigUpgrade(opts = {}) {
33447
35666
  return { exitCode: 2, action: "none", message };
33448
35667
  }
33449
35668
  }
33450
- if (!existsSync38(configPath)) {
35669
+ if (!existsSync39(configPath)) {
33451
35670
  const message = "massu.config.yaml not found. Run: npx massu init";
33452
35671
  err2(message + "\n");
33453
35672
  return { exitCode: 1, action: "none", message };
33454
35673
  }
33455
35674
  let existing;
33456
35675
  try {
33457
- const content = readFileSync37(configPath, "utf-8");
35676
+ const content = readFileSync40(configPath, "utf-8");
33458
35677
  const parsed = parseYaml6(content);
33459
35678
  if (!parsed || typeof parsed !== "object") {
33460
35679
  throw new Error("config is not a YAML object");
@@ -33477,8 +35696,8 @@ async function runConfigUpgrade(opts = {}) {
33477
35696
  fingerprint: computeFingerprint(detection)
33478
35697
  };
33479
35698
  try {
33480
- const original = readFileSync37(configPath, "utf-8");
33481
- writeFileSync5(bakPath, original, "utf-8");
35699
+ const original = readFileSync40(configPath, "utf-8");
35700
+ writeFileSync6(bakPath, original, "utf-8");
33482
35701
  } catch (e2) {
33483
35702
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
33484
35703
  err2(message + "\n");
@@ -33511,7 +35730,7 @@ var config_check_drift_exports = {};
33511
35730
  __export(config_check_drift_exports, {
33512
35731
  runConfigCheckDrift: () => runConfigCheckDrift
33513
35732
  });
33514
- import { existsSync as existsSync39, readFileSync as readFileSync38 } from "fs";
35733
+ import { existsSync as existsSync40, readFileSync as readFileSync41 } from "fs";
33515
35734
  import { resolve as resolve34 } from "path";
33516
35735
  import { parse as parseYaml7 } from "yaml";
33517
35736
  function renderChanges(changes) {
@@ -33525,7 +35744,7 @@ async function runConfigCheckDrift(opts = {}) {
33525
35744
  } : (s) => process.stdout.write(s);
33526
35745
  const err2 = opts.silent ? () => {
33527
35746
  } : (s) => process.stderr.write(s);
33528
- if (!existsSync39(configPath)) {
35747
+ if (!existsSync40(configPath)) {
33529
35748
  const message = "massu.config.yaml not found. Run: npx massu init";
33530
35749
  err2(message + "\n");
33531
35750
  return {
@@ -33539,7 +35758,7 @@ async function runConfigCheckDrift(opts = {}) {
33539
35758
  }
33540
35759
  let config;
33541
35760
  try {
33542
- const content = readFileSync38(configPath, "utf-8");
35761
+ const content = readFileSync41(configPath, "utf-8");
33543
35762
  const parsed = parseYaml7(content);
33544
35763
  if (!parsed || typeof parsed !== "object") {
33545
35764
  throw new Error("config is not a YAML object");
@@ -33606,11 +35825,11 @@ var init_config_check_drift = __esm({
33606
35825
  });
33607
35826
 
33608
35827
  // src/cli.ts
33609
- import { readFileSync as readFileSync39 } from "fs";
33610
- import { resolve as resolve35, dirname as dirname16 } from "path";
35828
+ import { readFileSync as readFileSync42 } from "fs";
35829
+ import { resolve as resolve35, dirname as dirname17 } from "path";
33611
35830
  import { fileURLToPath as fileURLToPath5 } from "url";
33612
35831
  var __filename4 = fileURLToPath5(import.meta.url);
33613
- var __dirname5 = dirname16(__filename4);
35832
+ var __dirname5 = dirname17(__filename4);
33614
35833
  var args2 = process.argv.slice(2);
33615
35834
  var subcommand = args2[0];
33616
35835
  async function main() {
@@ -33792,7 +36011,7 @@ Examples:
33792
36011
  }
33793
36012
  function printVersion() {
33794
36013
  try {
33795
- const pkg = JSON.parse(readFileSync39(resolve35(__dirname5, "../package.json"), "utf-8"));
36014
+ const pkg = JSON.parse(readFileSync42(resolve35(__dirname5, "../package.json"), "utf-8"));
33796
36015
  console.log(`massu v${pkg.version}`);
33797
36016
  } catch {
33798
36017
  console.log("massu v0.1.0");