@massu/core 1.5.3 → 1.5.4

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,10 +15186,137 @@ 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
+ };
14614
15315
  }
14615
15316
  });
14616
15317
 
14617
15318
  // src/detect/adapters/python-django.ts
15319
+ var DECORATOR_QUERY, CLASS_BASE_QUERY, URLPATTERNS_QUERY, DJANGO_MIXIN_NAMES, DJANGO_DECORATOR_PATTERNS, pythonDjangoAdapter;
14618
15320
  var init_python_django = __esm({
14619
15321
  "src/detect/adapters/python-django.ts"() {
14620
15322
  "use strict";
@@ -14622,10 +15324,151 @@ var init_python_django = __esm({
14622
15324
  init_query_helpers();
14623
15325
  init_tree_sitter_loader();
14624
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
+ };
14625
15467
  }
14626
15468
  });
14627
15469
 
14628
15470
  // src/detect/adapters/nextjs-trpc.ts
15471
+ var ROUTER_BUILDER_QUERY, PROCEDURE_QUERY, KNOWN_BUILDERS, nextjsTrpcAdapter;
14629
15472
  var init_nextjs_trpc = __esm({
14630
15473
  "src/detect/adapters/nextjs-trpc.ts"() {
14631
15474
  "use strict";
@@ -14633,10 +15476,123 @@ var init_nextjs_trpc = __esm({
14633
15476
  init_query_helpers();
14634
15477
  init_tree_sitter_loader();
14635
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
+ };
14636
15591
  }
14637
15592
  });
14638
15593
 
14639
15594
  // src/detect/adapters/swift-swiftui.ts
15595
+ var API_CLASS_QUERY, POLICY_QUERY, NAV_QUERY, POLICY_NAMES, swiftSwiftUiAdapter;
14640
15596
  var init_swift_swiftui = __esm({
14641
15597
  "src/detect/adapters/swift-swiftui.ts"() {
14642
15598
  "use strict";
@@ -14644,10 +15600,289 @@ var init_swift_swiftui = __esm({
14644
15600
  init_query_helpers();
14645
15601
  init_tree_sitter_loader();
14646
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/file-sampler.ts
15721
+ var file_sampler_exports = {};
15722
+ __export(file_sampler_exports, {
15723
+ SAMPLE_EXTENSIONS: () => SAMPLE_EXTENSIONS,
15724
+ SAMPLE_TEST_FILE_PATTERNS: () => SAMPLE_TEST_FILE_PATTERNS,
15725
+ sampleFilesForAdapter: () => sampleFilesForAdapter
15726
+ });
15727
+ import { readdirSync as readdirSync9, readFileSync as readFileSync9, lstatSync as lstatSync4 } from "node:fs";
15728
+ import { join as join9, extname } from "node:path";
15729
+ function sampleFilesForAdapter(adapter, projectRoot, detection, options = {}) {
15730
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
15731
+ const maxFiles = options.maxFilesPerAdapter ?? DEFAULT_MAX_FILES;
15732
+ const out2 = [];
15733
+ const seen = /* @__PURE__ */ new Set();
15734
+ for (const lang of adapter.languages) {
15735
+ if (out2.length >= maxFiles) break;
15736
+ const exts = SAMPLE_EXTENSIONS[lang];
15737
+ if (!exts || exts.length === 0) continue;
15738
+ const testPatterns = SAMPLE_TEST_FILE_PATTERNS[lang] ?? [];
15739
+ const langKey = lang;
15740
+ const langDetection = detection.sourceDirs[langKey];
15741
+ const candidateDirs = [];
15742
+ if (langDetection?.source_dirs && langDetection.source_dirs.length > 0) {
15743
+ candidateDirs.push(...langDetection.source_dirs.map((d2) => join9(projectRoot, d2)));
15744
+ } else {
15745
+ candidateDirs.push(projectRoot);
15746
+ }
15747
+ for (const dir of candidateDirs) {
15748
+ if (out2.length >= maxFiles) break;
15749
+ walkDir(dir, exts, testPatterns, lang, maxDepth, 0, out2, seen, maxFiles);
15750
+ }
15751
+ }
15752
+ return out2;
15753
+ }
15754
+ function walkDir(dir, exts, testPatterns, lang, maxDepth, curDepth, out2, seen, maxFiles) {
15755
+ if (curDepth > maxDepth) return;
15756
+ if (out2.length >= maxFiles) return;
15757
+ let entries;
15758
+ try {
15759
+ entries = readdirSync9(dir);
15760
+ } catch {
15761
+ return;
15762
+ }
15763
+ for (const entry of entries) {
15764
+ if (out2.length >= maxFiles) return;
15765
+ if (entry.startsWith(".")) continue;
15766
+ if (IGNORED_DIRS3.has(entry)) continue;
15767
+ const fullPath = join9(dir, entry);
15768
+ let st;
15769
+ try {
15770
+ st = lstatSync4(fullPath);
15771
+ } catch {
15772
+ continue;
15773
+ }
15774
+ if (st.isSymbolicLink()) continue;
15775
+ if (st.isDirectory()) {
15776
+ walkDir(fullPath, exts, testPatterns, lang, maxDepth, curDepth + 1, out2, seen, maxFiles);
15777
+ continue;
15778
+ }
15779
+ if (!st.isFile()) continue;
15780
+ if (st.size > MAX_AST_FILE_BYTES) continue;
15781
+ const ext = extname(entry).slice(1);
15782
+ if (!exts.includes(ext)) continue;
15783
+ if (testPatterns.some((p19) => p19.test(fullPath))) continue;
15784
+ if (seen.has(fullPath)) continue;
15785
+ seen.add(fullPath);
15786
+ let content;
15787
+ try {
15788
+ content = readFileSync9(fullPath, "utf-8");
15789
+ } catch {
15790
+ continue;
15791
+ }
15792
+ out2.push({
15793
+ path: fullPath,
15794
+ content,
15795
+ language: lang,
15796
+ size: st.size
15797
+ });
15798
+ }
15799
+ }
15800
+ var SAMPLE_EXTENSIONS, SAMPLE_TEST_FILE_PATTERNS, IGNORED_DIRS3, DEFAULT_MAX_DEPTH, DEFAULT_MAX_FILES;
15801
+ var init_file_sampler = __esm({
15802
+ "src/detect/adapters/file-sampler.ts"() {
15803
+ "use strict";
15804
+ init_parse_guard();
15805
+ SAMPLE_EXTENSIONS = {
15806
+ python: ["py"],
15807
+ typescript: ["ts", "tsx"],
15808
+ javascript: ["js", "jsx", "mjs", "cjs"],
15809
+ swift: ["swift"],
15810
+ rust: ["rs"],
15811
+ go: ["go"],
15812
+ ruby: ["rb"],
15813
+ php: ["php"],
15814
+ java: ["java", "kt"],
15815
+ kotlin: ["kt", "kts"],
15816
+ elixir: ["ex", "exs"],
15817
+ erlang: ["erl", "hrl"],
15818
+ csharp: ["cs"],
15819
+ cpp: ["cpp", "cc", "cxx", "h", "hpp"],
15820
+ haskell: ["hs", "lhs"],
15821
+ ocaml: ["ml", "mli"]
15822
+ };
15823
+ SAMPLE_TEST_FILE_PATTERNS = {
15824
+ python: [/_test\.py$/, /test_[^/]*\.py$/, /\/tests?\//],
15825
+ typescript: [/\.test\.tsx?$/, /\.spec\.tsx?$/, /\/__tests__\//],
15826
+ javascript: [/\.test\.[mc]?jsx?$/, /\.spec\.[mc]?jsx?$/, /\/__tests__\//],
15827
+ swift: [/Tests\//],
15828
+ rust: [/tests\/.*\.rs$/],
15829
+ go: [/_test\.go$/],
15830
+ ruby: [/_spec\.rb$/, /_test\.rb$/, /\/spec\//],
15831
+ php: [/Test\.php$/, /\/tests?\//i],
15832
+ java: [/Test[^/]*\.(java|kt)$/, /[^/]*Test\.(java|kt)$/, /\/test\//],
15833
+ kotlin: [/Test[^/]*\.kt$/, /[^/]*Test\.kt$/],
15834
+ elixir: [/_test\.exs$/, /\/test\//],
15835
+ erlang: [/_SUITE\.erl$/],
15836
+ csharp: [/Tests?\.cs$/, /\.Tests?\//],
15837
+ cpp: [/_test\.(cpp|cc)$/i, /\/tests?\//i],
15838
+ haskell: [/Spec\.hs$/, /\/test\//],
15839
+ ocaml: [/_test\.ml$/, /\/test\//]
15840
+ };
15841
+ IGNORED_DIRS3 = /* @__PURE__ */ new Set([
15842
+ "node_modules",
15843
+ ".venv",
15844
+ "venv",
15845
+ "__pycache__",
15846
+ "dist",
15847
+ "build",
15848
+ ".build",
15849
+ "target",
15850
+ ".next",
15851
+ ".nuxt",
15852
+ "coverage",
15853
+ ".git",
15854
+ ".massu",
15855
+ ".turbo",
15856
+ ".cache",
15857
+ ".pytest_cache",
15858
+ ".mypy_cache",
15859
+ "DerivedData",
15860
+ "Pods",
15861
+ "_build",
15862
+ "deps",
15863
+ "priv",
15864
+ "cover",
15865
+ ".elixir_ls",
15866
+ // Elixir/Phoenix
15867
+ "bin",
15868
+ "obj",
15869
+ ".vs",
15870
+ "packages",
15871
+ "publish",
15872
+ "TestResults"
15873
+ // .NET
15874
+ ]);
15875
+ DEFAULT_MAX_DEPTH = 3;
15876
+ DEFAULT_MAX_FILES = 50;
14647
15877
  }
14648
15878
  });
14649
15879
 
14650
15880
  // src/detect/codebase-introspector.ts
15881
+ var codebase_introspector_exports = {};
15882
+ __export(codebase_introspector_exports, {
15883
+ introspect: () => introspect,
15884
+ introspectAsync: () => introspectAsync
15885
+ });
14651
15886
  function introspect(detection, projectRoot) {
14652
15887
  const out2 = {};
14653
15888
  const languages = Array.from(
@@ -14667,6 +15902,35 @@ function introspect(detection, projectRoot) {
14667
15902
  }
14668
15903
  return out2;
14669
15904
  }
15905
+ async function introspectAsync(detection, projectRoot) {
15906
+ const out2 = introspect(detection, projectRoot);
15907
+ const signals = buildDetectionSignals(projectRoot);
15908
+ const { sampleFilesForAdapter: sampleFilesForAdapter2 } = await Promise.resolve().then(() => (init_file_sampler(), file_sampler_exports));
15909
+ let merged;
15910
+ try {
15911
+ merged = await runAdapters(FIRST_PARTY_ADAPTERS, projectRoot, signals, {
15912
+ sampleFiles: async (adapter, root) => {
15913
+ return sampleFilesForAdapter2(adapter, root, detection);
15914
+ }
15915
+ });
15916
+ } catch {
15917
+ return out2;
15918
+ }
15919
+ for (const [adapterId, resolved] of Object.entries(merged.byAdapter)) {
15920
+ if (resolved.confidence === "none") continue;
15921
+ out2[adapterId] = serializeAdapterBlock(resolved);
15922
+ }
15923
+ return out2;
15924
+ }
15925
+ function serializeAdapterBlock(r2) {
15926
+ const block = { ...r2.conventions };
15927
+ if (Object.keys(r2._provenance).length > 0) {
15928
+ block._provenance = r2._provenance;
15929
+ }
15930
+ block._confidence = r2.confidence;
15931
+ return block;
15932
+ }
15933
+ var FIRST_PARTY_ADAPTERS;
14670
15934
  var init_codebase_introspector = __esm({
14671
15935
  "src/detect/codebase-introspector.ts"() {
14672
15936
  "use strict";
@@ -14676,6 +15940,12 @@ var init_codebase_introspector = __esm({
14676
15940
  init_python_django();
14677
15941
  init_nextjs_trpc();
14678
15942
  init_swift_swiftui();
15943
+ FIRST_PARTY_ADAPTERS = [
15944
+ pythonFastApiAdapter,
15945
+ pythonDjangoAdapter,
15946
+ nextjsTrpcAdapter,
15947
+ swiftSwiftUiAdapter
15948
+ ];
14679
15949
  }
14680
15950
  });
14681
15951
 
@@ -14741,7 +16011,7 @@ var init_detect = __esm({
14741
16011
  });
14742
16012
 
14743
16013
  // src/detect/drift.ts
14744
- import { createHash as createHash2 } from "crypto";
16014
+ import { createHash as createHash3 } from "crypto";
14745
16015
  function summarizeDetection(det) {
14746
16016
  const languages = Array.from(new Set(det.manifests.map((m3) => m3.language))).sort();
14747
16017
  const frameworks = {};
@@ -14772,7 +16042,7 @@ function summarizeDetection(det) {
14772
16042
  function computeFingerprint(det) {
14773
16043
  const data = summarizeDetection(det);
14774
16044
  const stable = JSON.stringify(data, Object.keys(data).sort());
14775
- return createHash2("sha256").update(stable).digest("hex");
16045
+ return createHash3("sha256").update(stable).digest("hex");
14776
16046
  }
14777
16047
  function stringOf(v3) {
14778
16048
  if (typeof v3 === "string") return v3;
@@ -15931,10 +17201,10 @@ __export(init_exports, {
15931
17201
  validateWrittenConfig: () => validateWrittenConfig,
15932
17202
  writeConfigAtomic: () => writeConfigAtomic
15933
17203
  });
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";
17204
+ 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";
17205
+ import { resolve as resolve6, basename as basename4, dirname as dirname5 } from "path";
15936
17206
  import { fileURLToPath as fileURLToPath2 } from "url";
15937
- import { homedir as homedir2 } from "os";
17207
+ import { homedir as homedir3 } from "os";
15938
17208
  import { stringify as yamlStringify, parse as yamlParse } from "yaml";
15939
17209
  function detectFramework(projectRoot) {
15940
17210
  const result = {
@@ -15944,9 +17214,9 @@ function detectFramework(projectRoot) {
15944
17214
  ui: "none"
15945
17215
  };
15946
17216
  const pkgPath = resolve6(projectRoot, "package.json");
15947
- if (!existsSync9(pkgPath)) return result;
17217
+ if (!existsSync10(pkgPath)) return result;
15948
17218
  try {
15949
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
17219
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
15950
17220
  const allDeps = {
15951
17221
  ...pkg.dependencies,
15952
17222
  ...pkg.devDependencies
@@ -15980,7 +17250,7 @@ function detectPython(projectRoot) {
15980
17250
  alembicDir: null
15981
17251
  };
15982
17252
  const markers = ["pyproject.toml", "setup.py", "requirements.txt", "Pipfile"];
15983
- const hasMarker = markers.some((m3) => existsSync9(resolve6(projectRoot, m3)));
17253
+ const hasMarker = markers.some((m3) => existsSync10(resolve6(projectRoot, m3)));
15984
17254
  if (!hasMarker) return result;
15985
17255
  result.detected = true;
15986
17256
  const depFiles = [
@@ -15991,34 +17261,34 @@ function detectPython(projectRoot) {
15991
17261
  ];
15992
17262
  for (const { file } of depFiles) {
15993
17263
  const filePath = resolve6(projectRoot, file);
15994
- if (existsSync9(filePath)) {
17264
+ if (existsSync10(filePath)) {
15995
17265
  try {
15996
- const content = readFileSync7(filePath, "utf-8").toLowerCase();
17266
+ const content = readFileSync10(filePath, "utf-8").toLowerCase();
15997
17267
  if (content.includes("fastapi")) result.hasFastapi = true;
15998
17268
  if (content.includes("sqlalchemy")) result.hasSqlalchemy = true;
15999
17269
  } catch {
16000
17270
  }
16001
17271
  }
16002
17272
  }
16003
- if (existsSync9(resolve6(projectRoot, "alembic.ini"))) {
17273
+ if (existsSync10(resolve6(projectRoot, "alembic.ini"))) {
16004
17274
  result.hasAlembic = true;
16005
- if (existsSync9(resolve6(projectRoot, "alembic"))) {
17275
+ if (existsSync10(resolve6(projectRoot, "alembic"))) {
16006
17276
  result.alembicDir = "alembic";
16007
17277
  }
16008
- } else if (existsSync9(resolve6(projectRoot, "alembic"))) {
17278
+ } else if (existsSync10(resolve6(projectRoot, "alembic"))) {
16009
17279
  result.hasAlembic = true;
16010
17280
  result.alembicDir = "alembic";
16011
17281
  }
16012
17282
  const candidateRoots = ["app", "src", "backend", "api"];
16013
17283
  for (const candidate of candidateRoots) {
16014
17284
  const candidatePath = resolve6(projectRoot, candidate);
16015
- if (existsSync9(candidatePath) && existsSync9(resolve6(candidatePath, "__init__.py"))) {
17285
+ if (existsSync10(candidatePath) && existsSync10(resolve6(candidatePath, "__init__.py"))) {
16016
17286
  result.root = candidate;
16017
17287
  break;
16018
17288
  }
16019
- if (existsSync9(candidatePath)) {
17289
+ if (existsSync10(candidatePath)) {
16020
17290
  try {
16021
- const files = readdirSync7(candidatePath);
17291
+ const files = readdirSync10(candidatePath);
16022
17292
  if (files.some((f2) => f2.endsWith(".py"))) {
16023
17293
  result.root = candidate;
16024
17294
  break;
@@ -16037,10 +17307,10 @@ function generateConfig(projectRoot, framework) {
16037
17307
  "[@massu/core] generateConfig() is deprecated since 1.2.1 \u2014 use buildConfigFromDetection instead. It cannot produce valid configs for monorepos."
16038
17308
  );
16039
17309
  const configPath = resolve6(projectRoot, "massu.config.yaml");
16040
- if (existsSync9(configPath)) {
17310
+ if (existsSync10(configPath)) {
16041
17311
  return false;
16042
17312
  }
16043
- const projectName = basename3(projectRoot);
17313
+ const projectName = basename4(projectRoot);
16044
17314
  const config = {
16045
17315
  project: {
16046
17316
  name: projectName,
@@ -16083,7 +17353,7 @@ function generateConfig(projectRoot, framework) {
16083
17353
  # Documentation: https://massu.ai/docs/getting-started/configuration
16084
17354
 
16085
17355
  ${yamlStringify(config)}`;
16086
- writeFileSync(configPath, yamlContent, "utf-8");
17356
+ writeFileSync2(configPath, yamlContent, "utf-8");
16087
17357
  return true;
16088
17358
  }
16089
17359
  function monorepoCommonRoot(packages) {
@@ -16105,7 +17375,7 @@ function buildConfigFromDetection(opts) {
16105
17375
  if (!detection) {
16106
17376
  throw new Error("buildConfigFromDetection requires a detection result");
16107
17377
  }
16108
- const projectName = opts.projectName ?? basename3(projectRoot);
17378
+ const projectName = opts.projectName ?? basename4(projectRoot);
16109
17379
  const languages = Array.from(
16110
17380
  new Set(detection.manifests.map((m3) => m3.language))
16111
17381
  );
@@ -16226,7 +17496,7 @@ function buildConfigFromDetection(opts) {
16226
17496
  };
16227
17497
  if (pyFw?.framework) pythonBlock.framework = pyFw.framework;
16228
17498
  if (pyFw?.orm) pythonBlock.orm = pyFw.orm;
16229
- if (existsSync9(resolve6(projectRoot, "alembic.ini")) || existsSync9(resolve6(projectRoot, "alembic"))) {
17499
+ if (existsSync10(resolve6(projectRoot, "alembic.ini")) || existsSync10(resolve6(projectRoot, "alembic"))) {
16230
17500
  pythonBlock.alembic_dir = "alembic";
16231
17501
  }
16232
17502
  config.python = pythonBlock;
@@ -16251,10 +17521,10 @@ function applyVariantTemplate(config, templatesDir) {
16251
17521
  }
16252
17522
  if (templateId === null) return config;
16253
17523
  const templatePath = resolve6(templatesDir, templateId, "massu.config.yaml");
16254
- if (!existsSync9(templatePath)) return config;
17524
+ if (!existsSync10(templatePath)) return config;
16255
17525
  let template;
16256
17526
  try {
16257
- template = yamlParse(readFileSync7(templatePath, "utf-8"));
17527
+ template = yamlParse(readFileSync10(templatePath, "utf-8"));
16258
17528
  } catch {
16259
17529
  return config;
16260
17530
  }
@@ -16313,15 +17583,15 @@ ${yamlStringify(config)}`;
16313
17583
  function writeConfigAtomic(configPath, content) {
16314
17584
  const tmpPath = `${configPath}.tmp`;
16315
17585
  let existingMode;
16316
- if (existsSync9(configPath)) {
17586
+ if (existsSync10(configPath)) {
16317
17587
  try {
16318
- existingMode = statSync5(configPath).mode;
17588
+ existingMode = statSync7(configPath).mode;
16319
17589
  } catch {
16320
17590
  existingMode = void 0;
16321
17591
  }
16322
17592
  }
16323
17593
  try {
16324
- mkdirSync3(dirname4(configPath), { recursive: true });
17594
+ mkdirSync4(dirname5(configPath), { recursive: true });
16325
17595
  const fd = openSync2(tmpPath, "w", 420);
16326
17596
  try {
16327
17597
  const buf = Buffer.from(content, "utf-8");
@@ -16334,16 +17604,16 @@ function writeConfigAtomic(configPath, content) {
16334
17604
  if (parsed === null || typeof parsed !== "object") {
16335
17605
  throw new Error("Generated config is not a valid YAML object");
16336
17606
  }
16337
- renameSync2(tmpPath, configPath);
17607
+ renameSync3(tmpPath, configPath);
16338
17608
  if (existingMode !== void 0) {
16339
17609
  try {
16340
- chmodSync(configPath, existingMode);
17610
+ chmodSync2(configPath, existingMode);
16341
17611
  } catch {
16342
17612
  }
16343
17613
  }
16344
17614
  return { validated: true };
16345
17615
  } catch (err2) {
16346
- if (existsSync9(tmpPath)) {
17616
+ if (existsSync10(tmpPath)) {
16347
17617
  try {
16348
17618
  rmSync2(tmpPath, { force: true });
16349
17619
  } catch {
@@ -16354,8 +17624,8 @@ function writeConfigAtomic(configPath, content) {
16354
17624
  }
16355
17625
  function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16356
17626
  try {
16357
- if (!existsSync9(configPath)) return "Config file does not exist after write";
16358
- const content = readFileSync7(configPath, "utf-8");
17627
+ if (!existsSync10(configPath)) return "Config file does not exist after write";
17628
+ const content = readFileSync10(configPath, "utf-8");
16359
17629
  const parsed = yamlParse(content);
16360
17630
  if (parsed === null || typeof parsed !== "object") {
16361
17631
  return "Config is not a valid YAML object";
@@ -16376,7 +17646,7 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16376
17646
  const src = cfg.paths.source;
16377
17647
  if (src && src !== ".") {
16378
17648
  const srcAbs = resolve6(projectRoot, src);
16379
- if (!existsSync9(srcAbs)) {
17649
+ if (!existsSync10(srcAbs)) {
16380
17650
  return `paths.source '${src}' does not exist on disk`;
16381
17651
  }
16382
17652
  }
@@ -16387,7 +17657,7 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16387
17657
  for (const d2 of rawDirs) {
16388
17658
  if (typeof d2 !== "string" || d2 === ".") continue;
16389
17659
  const abs = resolve6(projectRoot, d2);
16390
- if (!existsSync9(abs)) {
17660
+ if (!existsSync10(abs)) {
16391
17661
  return `framework.languages.${lang}.source_dirs '${d2}' does not exist on disk`;
16392
17662
  }
16393
17663
  }
@@ -16396,7 +17666,7 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16396
17666
  if (Array.isArray(mRoots)) {
16397
17667
  for (const r2 of mRoots) {
16398
17668
  if (typeof r2 !== "string" || r2 === ".") continue;
16399
- if (!existsSync9(resolve6(projectRoot, r2))) {
17669
+ if (!existsSync10(resolve6(projectRoot, r2))) {
16400
17670
  return `paths.monorepo_roots '${r2}' does not exist on disk`;
16401
17671
  }
16402
17672
  }
@@ -16442,7 +17712,7 @@ function resolveTemplatesDir() {
16442
17712
  resolve6(__dirname2, "../../../templates")
16443
17713
  ];
16444
17714
  for (const c2 of candidates) {
16445
- if (existsSync9(c2)) return c2;
17715
+ if (existsSync10(c2)) return c2;
16446
17716
  }
16447
17717
  return null;
16448
17718
  }
@@ -16452,13 +17722,13 @@ function copyTemplateConfig(templateName, targetPath, projectName) {
16452
17722
  return { success: false, error: `Templates directory not found (looked in node_modules and dist/src)` };
16453
17723
  }
16454
17724
  const srcPath = resolve6(templatesDir, templateName, "massu.config.yaml");
16455
- if (!existsSync9(srcPath)) {
17725
+ if (!existsSync10(srcPath)) {
16456
17726
  return { success: false, error: `Template '${templateName}' not found at ${srcPath}` };
16457
17727
  }
16458
17728
  try {
16459
- let content = readFileSync7(srcPath, "utf-8");
17729
+ let content = readFileSync10(srcPath, "utf-8");
16460
17730
  content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
16461
- writeFileSync(targetPath, content, "utf-8");
17731
+ writeFileSync2(targetPath, content, "utf-8");
16462
17732
  return { success: true };
16463
17733
  } catch (err2) {
16464
17734
  return { success: false, error: err2 instanceof Error ? err2.message : String(err2) };
@@ -16467,9 +17737,9 @@ function copyTemplateConfig(templateName, targetPath, projectName) {
16467
17737
  function registerMcpServer(projectRoot) {
16468
17738
  const mcpPath = resolve6(projectRoot, ".mcp.json");
16469
17739
  let existing = {};
16470
- if (existsSync9(mcpPath)) {
17740
+ if (existsSync10(mcpPath)) {
16471
17741
  try {
16472
- existing = JSON.parse(readFileSync7(mcpPath, "utf-8"));
17742
+ existing = JSON.parse(readFileSync10(mcpPath, "utf-8"));
16473
17743
  } catch {
16474
17744
  existing = {};
16475
17745
  }
@@ -16484,17 +17754,17 @@ function registerMcpServer(projectRoot) {
16484
17754
  args: ["-y", "@massu/core"]
16485
17755
  };
16486
17756
  existing.mcpServers = servers;
16487
- writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
17757
+ writeFileSync2(mcpPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
16488
17758
  return true;
16489
17759
  }
16490
17760
  function resolveHooksDir() {
16491
17761
  const cwd = process.cwd();
16492
17762
  const nodeModulesPath = resolve6(cwd, "node_modules/@massu/core/dist/hooks");
16493
- if (existsSync9(nodeModulesPath)) {
17763
+ if (existsSync10(nodeModulesPath)) {
16494
17764
  return "node_modules/@massu/core/dist/hooks";
16495
17765
  }
16496
17766
  const localPath = resolve6(__dirname2, "../dist/hooks");
16497
- if (existsSync9(localPath)) {
17767
+ if (existsSync10(localPath)) {
16498
17768
  return localPath;
16499
17769
  }
16500
17770
  return "node_modules/@massu/core/dist/hooks";
@@ -16588,13 +17858,13 @@ function installHooks(projectRoot) {
16588
17858
  }
16589
17859
  const claudeDir = resolve6(projectRoot, claudeDirName);
16590
17860
  const settingsPath = resolve6(claudeDir, "settings.local.json");
16591
- if (!existsSync9(claudeDir)) {
16592
- mkdirSync3(claudeDir, { recursive: true });
17861
+ if (!existsSync10(claudeDir)) {
17862
+ mkdirSync4(claudeDir, { recursive: true });
16593
17863
  }
16594
17864
  let settings = {};
16595
- if (existsSync9(settingsPath)) {
17865
+ if (existsSync10(settingsPath)) {
16596
17866
  try {
16597
- settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
17867
+ settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
16598
17868
  } catch {
16599
17869
  settings = {};
16600
17870
  }
@@ -16608,21 +17878,21 @@ function installHooks(projectRoot) {
16608
17878
  }
16609
17879
  }
16610
17880
  settings.hooks = hooksConfig;
16611
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
17881
+ writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
16612
17882
  return { installed: true, count: hookCount };
16613
17883
  }
16614
17884
  function initMemoryDir(projectRoot) {
16615
17885
  const encodedRoot = "-" + projectRoot.replace(/\//g, "-");
16616
- const memoryDir = resolve6(homedir2(), `.claude/projects/${encodedRoot}/memory`);
17886
+ const memoryDir = resolve6(homedir3(), `.claude/projects/${encodedRoot}/memory`);
16617
17887
  let created = false;
16618
- if (!existsSync9(memoryDir)) {
16619
- mkdirSync3(memoryDir, { recursive: true });
17888
+ if (!existsSync10(memoryDir)) {
17889
+ mkdirSync4(memoryDir, { recursive: true });
16620
17890
  created = true;
16621
17891
  }
16622
17892
  const memoryMdPath = resolve6(memoryDir, "MEMORY.md");
16623
17893
  let memoryMdCreated = false;
16624
- if (!existsSync9(memoryMdPath)) {
16625
- const projectName = basename3(projectRoot);
17894
+ if (!existsSync10(memoryMdPath)) {
17895
+ const projectName = basename4(projectRoot);
16626
17896
  const memoryContent = `# ${projectName} - Massu Memory
16627
17897
 
16628
17898
  ## Key Learnings
@@ -16637,7 +17907,7 @@ function initMemoryDir(projectRoot) {
16637
17907
  ## File Index
16638
17908
  <!-- Significant files and directories -->
16639
17909
  `;
16640
- writeFileSync(memoryMdPath, memoryContent, "utf-8");
17910
+ writeFileSync2(memoryMdPath, memoryContent, "utf-8");
16641
17911
  memoryMdCreated = true;
16642
17912
  }
16643
17913
  return { created, memoryMdCreated };
@@ -16649,6 +17919,7 @@ function parseInitArgs(argv) {
16649
17919
  if (a2 === "--ci") opts.ci = true;
16650
17920
  else if (a2 === "--force") opts.force = true;
16651
17921
  else if (a2 === "--skip-commands") opts.skipCommands = true;
17922
+ else if (a2 === "--no-introspect") opts.skipIntrospect = true;
16652
17923
  else if (a2 === "--help" || a2 === "-h") opts.help = true;
16653
17924
  else if (a2 === "--template") {
16654
17925
  const next = argv[i2 + 1];
@@ -16727,7 +17998,7 @@ async function runInit(argv, overrides) {
16727
17998
  log("========================");
16728
17999
  log("");
16729
18000
  const configPath = resolve6(projectRoot, "massu.config.yaml");
16730
- if (existsSync9(configPath)) {
18001
+ if (existsSync10(configPath)) {
16731
18002
  if (opts.ci && !opts.force) {
16732
18003
  errLog(`error: massu.config.yaml already exists at ${configPath}`);
16733
18004
  errLog(" rerun with --force to overwrite, or remove the file first");
@@ -16746,7 +18017,7 @@ async function runInit(argv, overrides) {
16746
18017
  errLog(`error: unknown template '${opts.template}'. Available: ${TEMPLATE_NAMES.join(", ")}`);
16747
18018
  throw new Error(`Unknown template: ${opts.template}`);
16748
18019
  }
16749
- const projectName = basename3(projectRoot);
18020
+ const projectName = basename4(projectRoot);
16750
18021
  const res = copyTemplateConfig(opts.template, configPath, projectName);
16751
18022
  if (!res.success) {
16752
18023
  errLog(`error: template copy failed: ${res.error}`);
@@ -16805,7 +18076,25 @@ async function runInit(argv, overrides) {
16805
18076
  }
16806
18077
  }
16807
18078
  const baseConfig = buildConfigFromDetection({ projectRoot, detection });
16808
- const config = applyVariantTemplate(baseConfig, resolveTemplatesDir());
18079
+ const withVariant = applyVariantTemplate(baseConfig, resolveTemplatesDir());
18080
+ let config = withVariant;
18081
+ if (!opts.skipIntrospect) {
18082
+ try {
18083
+ const { introspectAsync: introspectAsync2 } = await Promise.resolve().then(() => (init_codebase_introspector(), codebase_introspector_exports));
18084
+ const introspected = await introspectAsync2(detection, projectRoot);
18085
+ const detectedBlocks = {};
18086
+ for (const [key, block] of Object.entries(introspected)) {
18087
+ if (block && typeof block === "object" && "_confidence" in block) {
18088
+ detectedBlocks[key] = block;
18089
+ }
18090
+ }
18091
+ if (Object.keys(detectedBlocks).length > 0) {
18092
+ config = { ...config, detected: detectedBlocks };
18093
+ }
18094
+ } catch (err2) {
18095
+ errLog(`warning: AST adapter introspection failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
18096
+ }
18097
+ }
16809
18098
  const content = renderConfigYaml(config);
16810
18099
  const writeRes = writeConfigAtomic(configPath, content);
16811
18100
  if (!writeRes.validated) {
@@ -16849,7 +18138,7 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
16849
18138
  const stackResolved = !emptyStack && commandStats && (commandStats.installed > 0 || commandStats.updated > 0 || commandStats.kept > 0);
16850
18139
  if (!stackResolved) {
16851
18140
  const placeholderPath = resolve6(cmdResult.claudeDir, "commands", "_massu-needs-stack.md");
16852
- if (!existsSync9(placeholderPath)) {
18141
+ if (!existsSync10(placeholderPath)) {
16853
18142
  const placeholderBody = [
16854
18143
  "# Massu \u2014 stack not yet detected",
16855
18144
  "",
@@ -16870,8 +18159,8 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
16870
18159
  "\u2014 Massu"
16871
18160
  ].join("\n");
16872
18161
  try {
16873
- mkdirSync3(resolve6(cmdResult.claudeDir, "commands"), { recursive: true });
16874
- writeFileSync(placeholderPath, placeholderBody, "utf-8");
18162
+ mkdirSync4(resolve6(cmdResult.claudeDir, "commands"), { recursive: true });
18163
+ writeFileSync2(placeholderPath, placeholderBody, "utf-8");
16875
18164
  log(" Wrote _massu-needs-stack.md placeholder (no stack detected yet)");
16876
18165
  } catch {
16877
18166
  }
@@ -16890,8 +18179,8 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
16890
18179
  (async () => {
16891
18180
  try {
16892
18181
  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") : [];
18182
+ const memoryDir = resolve6(homedir3(), ".claude", "projects", encodedRoot, "memory");
18183
+ const memFiles = existsSync10(memoryDir) ? readdirSync10(memoryDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md") : [];
16895
18184
  if (memFiles.length > 0) {
16896
18185
  const { getMemoryDb: getMemoryDb2 } = await Promise.resolve().then(() => (init_memory_db(), memory_db_exports));
16897
18186
  const db = getMemoryDb2();
@@ -16951,7 +18240,7 @@ var init_init = __esm({
16951
18240
  init_detect();
16952
18241
  init_drift();
16953
18242
  __filename2 = fileURLToPath2(import.meta.url);
16954
- __dirname2 = dirname4(__filename2);
18243
+ __dirname2 = dirname5(__filename2);
16955
18244
  FRAMEWORK_TO_TEMPLATE_ID = {
16956
18245
  rails: "rails",
16957
18246
  phoenix: "phoenix",
@@ -16973,7 +18262,7 @@ var init_init = __esm({
16973
18262
  });
16974
18263
 
16975
18264
  // src/license.ts
16976
- import { createHash as createHash3 } from "crypto";
18265
+ import { createHash as createHash4 } from "crypto";
16977
18266
  function tierLevel(tier) {
16978
18267
  return TIER_LEVELS[tier] ?? 0;
16979
18268
  }
@@ -16998,7 +18287,7 @@ function annotateToolDefinitions(defs) {
16998
18287
  });
16999
18288
  }
17000
18289
  async function validateLicense(apiKey) {
17001
- const keyHash = createHash3("sha256").update(apiKey).digest("hex");
18290
+ const keyHash = createHash4("sha256").update(apiKey).digest("hex");
17002
18291
  const memDb = getMemoryDb();
17003
18292
  try {
17004
18293
  const cached = memDb.prepare(
@@ -17059,7 +18348,7 @@ async function validateLicense(apiKey) {
17059
18348
  }
17060
18349
  }
17061
18350
  function updateLicenseCache(apiKey, tier, validUntil, features = []) {
17062
- const keyHash = createHash3("sha256").update(apiKey).digest("hex");
18351
+ const keyHash = createHash4("sha256").update(apiKey).digest("hex");
17063
18352
  const memDb = getMemoryDb();
17064
18353
  try {
17065
18354
  memDb.prepare(`
@@ -17267,17 +18556,17 @@ __export(doctor_exports, {
17267
18556
  runDoctor: () => runDoctor,
17268
18557
  runValidateConfig: () => runValidateConfig
17269
18558
  });
17270
- import { existsSync as existsSync10, readFileSync as readFileSync8, readdirSync as readdirSync8 } from "fs";
17271
- import { resolve as resolve7, dirname as dirname5 } from "path";
18559
+ import { existsSync as existsSync11, readFileSync as readFileSync11, readdirSync as readdirSync11 } from "fs";
18560
+ import { resolve as resolve7, dirname as dirname6 } from "path";
17272
18561
  import { fileURLToPath as fileURLToPath3 } from "url";
17273
18562
  import { parse as parseYaml4 } from "yaml";
17274
18563
  function checkConfig(projectRoot) {
17275
18564
  const configPath = resolve7(projectRoot, "massu.config.yaml");
17276
- if (!existsSync10(configPath)) {
18565
+ if (!existsSync11(configPath)) {
17277
18566
  return { name: "Configuration", status: "fail", detail: "massu.config.yaml not found. Run: npx massu init" };
17278
18567
  }
17279
18568
  try {
17280
- const content = readFileSync8(configPath, "utf-8");
18569
+ const content = readFileSync11(configPath, "utf-8");
17281
18570
  const parsed = parseYaml4(content);
17282
18571
  if (!parsed || typeof parsed !== "object") {
17283
18572
  return { name: "Configuration", status: "fail", detail: "massu.config.yaml is empty or invalid YAML" };
@@ -17289,11 +18578,11 @@ function checkConfig(projectRoot) {
17289
18578
  }
17290
18579
  function checkMcpServer(projectRoot) {
17291
18580
  const mcpPath = getResolvedPaths().mcpJsonPath;
17292
- if (!existsSync10(mcpPath)) {
18581
+ if (!existsSync11(mcpPath)) {
17293
18582
  return { name: "MCP Server", status: "fail", detail: ".mcp.json not found. Run: npx massu init" };
17294
18583
  }
17295
18584
  try {
17296
- const content = JSON.parse(readFileSync8(mcpPath, "utf-8"));
18585
+ const content = JSON.parse(readFileSync11(mcpPath, "utf-8"));
17297
18586
  const servers = content.mcpServers ?? {};
17298
18587
  if (!servers.massu) {
17299
18588
  return { name: "MCP Server", status: "fail", detail: "massu not registered in .mcp.json. Run: npx massu init" };
@@ -17305,11 +18594,11 @@ function checkMcpServer(projectRoot) {
17305
18594
  }
17306
18595
  function checkHooksConfig(projectRoot) {
17307
18596
  const settingsPath = getResolvedPaths().settingsLocalPath;
17308
- if (!existsSync10(settingsPath)) {
18597
+ if (!existsSync11(settingsPath)) {
17309
18598
  return { name: "Hooks Config", status: "fail", detail: ".claude/settings.local.json not found. Run: npx massu init" };
17310
18599
  }
17311
18600
  try {
17312
- const content = JSON.parse(readFileSync8(settingsPath, "utf-8"));
18601
+ const content = JSON.parse(readFileSync11(settingsPath, "utf-8"));
17313
18602
  if (!content.hooks) {
17314
18603
  return { name: "Hooks Config", status: "fail", detail: "No hooks configured. Run: npx massu install-hooks" };
17315
18604
  }
@@ -17335,9 +18624,9 @@ function checkHooksConfig(projectRoot) {
17335
18624
  function checkHookFiles(projectRoot) {
17336
18625
  const nodeModulesHooksDir = resolve7(projectRoot, "node_modules/@massu/core/dist/hooks");
17337
18626
  let hooksDir = nodeModulesHooksDir;
17338
- if (!existsSync10(nodeModulesHooksDir)) {
18627
+ if (!existsSync11(nodeModulesHooksDir)) {
17339
18628
  const devHooksDir = resolve7(__dirname3, "../../dist/hooks");
17340
- if (existsSync10(devHooksDir)) {
18629
+ if (existsSync11(devHooksDir)) {
17341
18630
  hooksDir = devHooksDir;
17342
18631
  } else {
17343
18632
  return { name: "Hook Files", status: "fail", detail: "Compiled hooks not found. Run: npm install @massu/core" };
@@ -17345,7 +18634,7 @@ function checkHookFiles(projectRoot) {
17345
18634
  }
17346
18635
  const missing = [];
17347
18636
  for (const hookFile of EXPECTED_HOOKS) {
17348
- if (!existsSync10(resolve7(hooksDir, hookFile))) {
18637
+ if (!existsSync11(resolve7(hooksDir, hookFile))) {
17349
18638
  missing.push(hookFile);
17350
18639
  }
17351
18640
  }
@@ -17372,7 +18661,7 @@ function checkNodeVersion() {
17372
18661
  }
17373
18662
  async function checkGitRepo(projectRoot) {
17374
18663
  const gitDir = resolve7(projectRoot, ".git");
17375
- if (!existsSync10(gitDir)) {
18664
+ if (!existsSync11(gitDir)) {
17376
18665
  return { name: "Git Repository", status: "warn", detail: "Not a git repository (optional but recommended)" };
17377
18666
  }
17378
18667
  try {
@@ -17390,7 +18679,7 @@ async function checkGitRepo(projectRoot) {
17390
18679
  }
17391
18680
  function checkKnowledgeDb(projectRoot) {
17392
18681
  const knowledgeDbPath = getResolvedPaths().memoryDbPath;
17393
- if (!existsSync10(knowledgeDbPath)) {
18682
+ if (!existsSync11(knowledgeDbPath)) {
17394
18683
  return {
17395
18684
  name: "Knowledge DB",
17396
18685
  status: "warn",
@@ -17401,7 +18690,7 @@ function checkKnowledgeDb(projectRoot) {
17401
18690
  }
17402
18691
  function checkMemoryDir(_projectRoot2) {
17403
18692
  const memoryDir = getResolvedPaths().memoryDir;
17404
- if (!existsSync10(memoryDir)) {
18693
+ if (!existsSync11(memoryDir)) {
17405
18694
  return {
17406
18695
  name: "Memory Directory",
17407
18696
  status: "warn",
@@ -17412,7 +18701,7 @@ function checkMemoryDir(_projectRoot2) {
17412
18701
  }
17413
18702
  function checkShellHooksWired(_projectRoot2) {
17414
18703
  const settingsPath = getResolvedPaths().settingsLocalPath;
17415
- if (!existsSync10(settingsPath)) {
18704
+ if (!existsSync11(settingsPath)) {
17416
18705
  return {
17417
18706
  name: "Shell Hooks",
17418
18707
  status: "fail",
@@ -17420,7 +18709,7 @@ function checkShellHooksWired(_projectRoot2) {
17420
18709
  };
17421
18710
  }
17422
18711
  try {
17423
- const content = JSON.parse(readFileSync8(settingsPath, "utf-8"));
18712
+ const content = JSON.parse(readFileSync11(settingsPath, "utf-8"));
17424
18713
  const hooks = content.hooks ?? {};
17425
18714
  const hasSessionStart = Array.isArray(hooks.SessionStart) && hooks.SessionStart.length > 0;
17426
18715
  const hasPreToolUse = Array.isArray(hooks.PreToolUse) && hooks.PreToolUse.length > 0;
@@ -17472,7 +18761,7 @@ function checkPythonHealth(projectRoot) {
17472
18761
  const config = getConfig();
17473
18762
  if (!config.python?.root) return null;
17474
18763
  const pythonRoot = resolve7(projectRoot, config.python.root);
17475
- if (!existsSync10(pythonRoot)) {
18764
+ if (!existsSync11(pythonRoot)) {
17476
18765
  return {
17477
18766
  name: "Python",
17478
18767
  status: "fail",
@@ -17486,15 +18775,15 @@ function checkPythonHealth(projectRoot) {
17486
18775
  function scanDir(dir, depth) {
17487
18776
  if (depth > 5) return;
17488
18777
  try {
17489
- const entries = readdirSync8(dir, { withFileTypes: true });
18778
+ const entries = readdirSync11(dir, { withFileTypes: true });
17490
18779
  for (const entry of entries) {
17491
18780
  if (entry.isDirectory()) {
17492
18781
  const excludeDirs = config.python?.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
17493
18782
  if (!excludeDirs.includes(entry.name)) {
17494
18783
  const subdir = resolve7(dir, entry.name);
17495
- if (depth <= 2 && !existsSync10(resolve7(subdir, "__init__.py"))) {
18784
+ if (depth <= 2 && !existsSync11(resolve7(subdir, "__init__.py"))) {
17496
18785
  try {
17497
- const subEntries = readdirSync8(subdir);
18786
+ const subEntries = readdirSync11(subdir);
17498
18787
  if (subEntries.some((f2) => f2.endsWith(".py") && f2 !== "__init__.py")) {
17499
18788
  initPyMissing.push(entry.name);
17500
18789
  }
@@ -17588,14 +18877,14 @@ async function runDoctor() {
17588
18877
  async function runValidateConfig() {
17589
18878
  const projectRoot = process.cwd();
17590
18879
  const configPath = resolve7(projectRoot, "massu.config.yaml");
17591
- if (!existsSync10(configPath)) {
18880
+ if (!existsSync11(configPath)) {
17592
18881
  console.error("Error: massu.config.yaml not found in current directory");
17593
18882
  console.error("Run: npx massu init");
17594
18883
  process.exit(1);
17595
18884
  return;
17596
18885
  }
17597
18886
  try {
17598
- const content = readFileSync8(configPath, "utf-8");
18887
+ const content = readFileSync11(configPath, "utf-8");
17599
18888
  const parsed = parseYaml4(content);
17600
18889
  if (!parsed || typeof parsed !== "object") {
17601
18890
  console.error("Error: massu.config.yaml is empty or not a valid YAML object");
@@ -17634,7 +18923,7 @@ var init_doctor = __esm({
17634
18923
  init_config();
17635
18924
  init_license();
17636
18925
  __filename3 = fileURLToPath3(import.meta.url);
17637
- __dirname3 = dirname5(__filename3);
18926
+ __dirname3 = dirname6(__filename3);
17638
18927
  EXPECTED_HOOKS = [
17639
18928
  "session-start.js",
17640
18929
  "session-end.js",
@@ -17680,7 +18969,7 @@ var show_template_exports = {};
17680
18969
  __export(show_template_exports, {
17681
18970
  runShowTemplate: () => runShowTemplate
17682
18971
  });
17683
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
18972
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
17684
18973
  import { resolve as resolve8 } from "path";
17685
18974
  function normalizeBaseName(input) {
17686
18975
  return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
@@ -17710,13 +18999,13 @@ async function runShowTemplate(args3) {
17710
18999
  }
17711
19000
  const suffix = choice.kind === "hit" ? choice.suffix : "";
17712
19001
  const file = suffix === "" ? resolve8(sourceDir, `${baseName}.md`) : resolve8(sourceDir, `${baseName}${suffix}.md`);
17713
- if (!existsSync11(file)) {
19002
+ if (!existsSync12(file)) {
17714
19003
  process.stderr.write(`massu: resolved template "${file}" no longer exists
17715
19004
  `);
17716
19005
  process.exit(1);
17717
19006
  return;
17718
19007
  }
17719
- process.stdout.write(readFileSync9(file, "utf-8"));
19008
+ process.stdout.write(readFileSync12(file, "utf-8"));
17720
19009
  }
17721
19010
  var init_show_template = __esm({
17722
19011
  "src/commands/show-template.ts"() {
@@ -17769,12 +19058,12 @@ var init_passthrough = __esm({
17769
19058
  });
17770
19059
 
17771
19060
  // 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";
19061
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync13, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
19062
+ import { dirname as dirname7 } from "path";
17774
19063
  import * as lockfile from "proper-lockfile";
17775
19064
  function readLockHolderPid(lockPath) {
17776
19065
  try {
17777
- const raw = readFileSync10(`${lockPath}.pid`, "utf-8").trim();
19066
+ const raw = readFileSync13(`${lockPath}.pid`, "utf-8").trim();
17778
19067
  const pid = Number.parseInt(raw, 10);
17779
19068
  if (!Number.isFinite(pid) || pid <= 0) return null;
17780
19069
  return pid;
@@ -17793,7 +19082,7 @@ function busyWaitSync(ms) {
17793
19082
  Atomics.wait(view, 0, 0, ms);
17794
19083
  }
17795
19084
  function withFileLockSync(lockPath, fn, opts = {}) {
17796
- mkdirSync4(dirname6(lockPath), { recursive: true });
19085
+ mkdirSync5(dirname7(lockPath), { recursive: true });
17797
19086
  const staleMs = opts.staleMs ?? 3e4;
17798
19087
  const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
17799
19088
  const pollIntervalMs = opts.pollIntervalMs ?? 100;
@@ -17810,7 +19099,7 @@ function withFileLockSync(lockPath, fn, opts = {}) {
17810
19099
  realpath: false
17811
19100
  });
17812
19101
  try {
17813
- writeFileSync2(`${lockPath}.pid`, String(process.pid), "utf-8");
19102
+ writeFileSync3(`${lockPath}.pid`, String(process.pid), "utf-8");
17814
19103
  } catch {
17815
19104
  }
17816
19105
  break;
@@ -17899,7 +19188,7 @@ __export(config_refresh_exports, {
17899
19188
  mergeRefresh: () => mergeRefresh,
17900
19189
  runConfigRefresh: () => runConfigRefresh
17901
19190
  });
17902
- import { existsSync as existsSync12, readFileSync as readFileSync11, rmSync as rmSync4 } from "fs";
19191
+ import { existsSync as existsSync13, readFileSync as readFileSync14, rmSync as rmSync4 } from "fs";
17903
19192
  import { resolve as resolve10 } from "path";
17904
19193
  import { parse as parseYaml5 } from "yaml";
17905
19194
  function flatten(obj, prefix3 = "") {
@@ -18031,14 +19320,14 @@ async function runConfigRefresh(opts = {}) {
18031
19320
  const configPath = resolve10(cwd, "massu.config.yaml");
18032
19321
  const log = opts.silent ? () => {
18033
19322
  } : (s) => process.stdout.write(s);
18034
- if (!existsSync12(configPath)) {
19323
+ if (!existsSync13(configPath)) {
18035
19324
  const message = "massu.config.yaml not found. Run: npx massu init";
18036
19325
  if (!opts.silent) process.stderr.write(message + "\n");
18037
19326
  return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
18038
19327
  }
18039
19328
  let existing;
18040
19329
  try {
18041
- const content = readFileSync11(configPath, "utf-8");
19330
+ const content = readFileSync14(configPath, "utf-8");
18042
19331
  const parsed = parseYaml5(content);
18043
19332
  if (!parsed || typeof parsed !== "object") {
18044
19333
  throw new Error("config is not a YAML object");
@@ -18112,7 +19401,7 @@ async function runConfigRefresh(opts = {}) {
18112
19401
  const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
18113
19402
  if (stackResolved) {
18114
19403
  const placeholderPath = resolve10(installResult.claudeDir, "commands", "_massu-needs-stack.md");
18115
- if (existsSync12(placeholderPath)) {
19404
+ if (existsSync13(placeholderPath)) {
18116
19405
  try {
18117
19406
  rmSync4(placeholderPath, { force: true });
18118
19407
  log("Removed _massu-needs-stack.md (stack now declared).\n");
@@ -18221,14 +19510,14 @@ var init_gitToplevel = __esm({
18221
19510
  });
18222
19511
 
18223
19512
  // src/watch/lockfile-detector.ts
18224
- import { existsSync as existsSync13, statSync as statSync6 } from "fs";
19513
+ import { existsSync as existsSync14, statSync as statSync8 } from "fs";
18225
19514
  import { resolve as resolve11 } from "path";
18226
19515
  function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
18227
19516
  for (const lf of KNOWN_LOCKFILES) {
18228
19517
  const p19 = resolve11(projectRoot, lf);
18229
- if (!existsSync13(p19)) continue;
19518
+ if (!existsSync14(p19)) continue;
18230
19519
  try {
18231
- const stat = statSync6(p19);
19520
+ const stat = statSync8(p19);
18232
19521
  const delta = now - stat.mtimeMs;
18233
19522
  if (delta >= 0 && delta < windowMs) return true;
18234
19523
  } catch {
@@ -18239,7 +19528,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
18239
19528
  function gitMidOperation(projectRoot) {
18240
19529
  const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
18241
19530
  for (const s of sentinels) {
18242
- if (existsSync13(resolve11(projectRoot, ".git", s))) return true;
19531
+ if (existsSync14(resolve11(projectRoot, ".git", s))) return true;
18243
19532
  }
18244
19533
  return false;
18245
19534
  }
@@ -18436,8 +19725,8 @@ var init_paths = __esm({
18436
19725
  });
18437
19726
 
18438
19727
  // 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";
19728
+ 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";
19729
+ import { dirname as dirname8, resolve as resolve12 } from "path";
18441
19730
  function watchStatePath(projectRoot) {
18442
19731
  return resolve12(projectRoot, ".massu", "watch-state.json");
18443
19732
  }
@@ -18446,8 +19735,8 @@ function backupStatePath(projectRoot) {
18446
19735
  }
18447
19736
  function readState(projectRoot) {
18448
19737
  const path = watchStatePath(projectRoot);
18449
- if (!existsSync14(path)) return { ...DEFAULT_STATE };
18450
- const content = readFileSync12(path, "utf-8");
19738
+ if (!existsSync15(path)) return { ...DEFAULT_STATE };
19739
+ const content = readFileSync15(path, "utf-8");
18451
19740
  let raw;
18452
19741
  try {
18453
19742
  raw = JSON.parse(content);
@@ -18489,14 +19778,14 @@ function readState(projectRoot) {
18489
19778
  }
18490
19779
  function archiveCorrupt(projectRoot, content) {
18491
19780
  const bak = backupStatePath(projectRoot);
18492
- mkdirSync5(dirname7(bak), { recursive: true });
18493
- writeFileSync3(bak, content, "utf-8");
19781
+ mkdirSync6(dirname8(bak), { recursive: true });
19782
+ writeFileSync4(bak, content, "utf-8");
18494
19783
  }
18495
19784
  function writeStateAtomic(projectRoot, state) {
18496
19785
  const path = watchStatePath(projectRoot);
18497
19786
  writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
18498
19787
  const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
18499
- mkdirSync5(dirname7(path), { recursive: true });
19788
+ mkdirSync6(dirname8(path), { recursive: true });
18500
19789
  let renamed = false;
18501
19790
  try {
18502
19791
  const fd = openSync3(tmp, "w");
@@ -18507,10 +19796,10 @@ function writeStateAtomic(projectRoot, state) {
18507
19796
  } finally {
18508
19797
  closeSync3(fd);
18509
19798
  }
18510
- renameSync3(tmp, path);
19799
+ renameSync4(tmp, path);
18511
19800
  renamed = true;
18512
19801
  } finally {
18513
- if (!renamed && existsSync14(tmp)) {
19802
+ if (!renamed && existsSync15(tmp)) {
18514
19803
  try {
18515
19804
  rmSync5(tmp, { force: true });
18516
19805
  } catch {
@@ -18790,8 +20079,8 @@ __export(watch_exports, {
18790
20079
  runWatch: () => runWatch
18791
20080
  });
18792
20081
  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";
20082
+ import { basename as basename5, dirname as dirname9, resolve as resolve13 } from "path";
20083
+ import { appendFileSync, existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync16 } from "fs";
18795
20084
  function parseFlags(args3) {
18796
20085
  const out2 = {
18797
20086
  foreground: false,
@@ -18817,7 +20106,7 @@ function parseFlags(args3) {
18817
20106
  function findClaudeBg() {
18818
20107
  const home = process.env.HOME ?? "";
18819
20108
  const fixed = home ? resolve13(home, ".claude", "bin", "claude-bg") : null;
18820
- if (fixed && existsSync15(fixed)) return fixed;
20109
+ if (fixed && existsSync16(fixed)) return fixed;
18821
20110
  const which = spawnSync3("which", ["claude-bg"], { encoding: "utf-8" });
18822
20111
  if (which.status === 0 && which.stdout) {
18823
20112
  const p19 = which.stdout.trim();
@@ -18826,7 +20115,7 @@ function findClaudeBg() {
18826
20115
  return null;
18827
20116
  }
18828
20117
  function watchName(root) {
18829
- return `massu-watch-${basename4(root)}`;
20118
+ return `massu-watch-${basename5(root)}`;
18830
20119
  }
18831
20120
  function printHelp(out2) {
18832
20121
  out2(`
@@ -18878,7 +20167,7 @@ async function runWatch(args3) {
18878
20167
  }
18879
20168
  function runStatus(root) {
18880
20169
  const path = watchStatePath(root);
18881
- if (!existsSync15(path)) {
20170
+ if (!existsSync16(path)) {
18882
20171
  process.stdout.write("massu watch: not running (no state file)\n");
18883
20172
  return { exitCode: 0 };
18884
20173
  }
@@ -19036,18 +20325,18 @@ function refreshLogPath(projectRoot) {
19036
20325
  function appendRefreshLog(projectRoot, event) {
19037
20326
  const path = refreshLogPath(projectRoot);
19038
20327
  try {
19039
- mkdirSync6(dirname8(path), { recursive: true });
20328
+ mkdirSync7(dirname9(path), { recursive: true });
19040
20329
  appendFileSync(path, JSON.stringify(event) + "\n", "utf-8");
19041
20330
  } catch {
19042
20331
  }
19043
20332
  }
19044
20333
  function readRefreshLog(projectRoot, limit = 10, opts = {}) {
19045
20334
  const path = refreshLogPath(projectRoot);
19046
- if (!existsSync15(path)) return [];
20335
+ if (!existsSync16(path)) return [];
19047
20336
  const warn = opts.warn ?? ((s) => {
19048
20337
  process.stderr.write(s);
19049
20338
  });
19050
- const lines = readFileSync13(path, "utf-8").split("\n").filter(Boolean);
20339
+ const lines = readFileSync16(path, "utf-8").split("\n").filter(Boolean);
19051
20340
  const tail = lines.slice(-limit);
19052
20341
  const out2 = [];
19053
20342
  let corrupt = 0;
@@ -19118,28 +20407,28 @@ var init_refresh_log = __esm({
19118
20407
 
19119
20408
  // src/security/atomic-write.ts
19120
20409
  import {
19121
- chmodSync as chmodSync2,
20410
+ chmodSync as chmodSync3,
19122
20411
  closeSync as closeSync4,
19123
- existsSync as existsSync16,
20412
+ existsSync as existsSync17,
19124
20413
  fsyncSync as fsyncSync4,
19125
- mkdirSync as mkdirSync7,
20414
+ mkdirSync as mkdirSync8,
19126
20415
  openSync as openSync4,
19127
- renameSync as renameSync4,
20416
+ renameSync as renameSync5,
19128
20417
  rmSync as rmSync6,
19129
- statSync as statSync7,
20418
+ statSync as statSync9,
19130
20419
  writeSync as writeSync4
19131
20420
  } from "node:fs";
19132
- import { dirname as dirname9 } from "node:path";
20421
+ import { dirname as dirname10 } from "node:path";
19133
20422
  function atomicWrite(path, content, opts = {}) {
19134
20423
  const tmpPath = `${path}.tmp`;
19135
- const parentDir = dirname9(path);
20424
+ const parentDir = dirname10(path);
19136
20425
  try {
19137
- if (!existsSync16(parentDir)) {
20426
+ if (!existsSync17(parentDir)) {
19138
20427
  const mkdirOpts = { recursive: true };
19139
20428
  if (opts.ensureParentDirMode !== void 0) {
19140
20429
  mkdirOpts.mode = opts.ensureParentDirMode;
19141
20430
  }
19142
- mkdirSync7(parentDir, mkdirOpts);
20431
+ mkdirSync8(parentDir, mkdirOpts);
19143
20432
  }
19144
20433
  const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
19145
20434
  const openMode = opts.mode ?? 420;
@@ -19151,12 +20440,12 @@ function atomicWrite(path, content, opts = {}) {
19151
20440
  closeSync4(fd);
19152
20441
  }
19153
20442
  if (opts.mode !== void 0) {
19154
- chmodSync2(tmpPath, opts.mode);
20443
+ chmodSync3(tmpPath, opts.mode);
19155
20444
  }
19156
- renameSync4(tmpPath, path);
20445
+ renameSync5(tmpPath, path);
19157
20446
  return { written: true };
19158
20447
  } catch (err2) {
19159
- if (existsSync16(tmpPath)) {
20448
+ if (existsSync17(tmpPath)) {
19160
20449
  try {
19161
20450
  rmSync6(tmpPath, { force: true });
19162
20451
  } catch {
@@ -21460,9 +22749,9 @@ var init_registry_pubkey_generated = __esm({
21460
22749
  });
21461
22750
 
21462
22751
  // src/security/adapter-verifier.ts
21463
- import { createHash as createHash4 } from "node:crypto";
22752
+ import { createHash as createHash5 } from "node:crypto";
21464
22753
  function sha256Hex(bytes) {
21465
- return createHash4("sha256").update(bytes).digest("hex");
22754
+ return createHash5("sha256").update(bytes).digest("hex");
21466
22755
  }
21467
22756
  function reviver(key, value) {
21468
22757
  if (key === "__proto__" || key === "constructor" || key === "prototype") {
@@ -21724,24 +23013,24 @@ var init_fetcher = __esm({
21724
23013
  });
21725
23014
 
21726
23015
  // 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";
23016
+ import { existsSync as existsSync18, readFileSync as readFileSync17, statSync as statSync10 } from "node:fs";
23017
+ import { homedir as homedir4 } from "node:os";
21729
23018
  import { resolve as resolve14 } from "node:path";
21730
23019
  import { z as z4 } from "zod";
21731
23020
  function defaultCachePaths() {
21732
- const dir = resolve14(homedir3(), ".massu");
23021
+ const dir = resolve14(homedir4(), ".massu");
21733
23022
  return {
21734
23023
  cachePath: resolve14(dir, "adapter-manifest.json"),
21735
23024
  lockPath: resolve14(dir, ".adapter-manifest.lock")
21736
23025
  };
21737
23026
  }
21738
23027
  function loadCachedManifest(paths = defaultCachePaths()) {
21739
- if (!existsSync17(paths.cachePath)) {
23028
+ if (!existsSync18(paths.cachePath)) {
21740
23029
  return { kind: "absent" };
21741
23030
  }
21742
23031
  let raw;
21743
23032
  try {
21744
- const content = readFileSync14(paths.cachePath, "utf-8");
23033
+ const content = readFileSync17(paths.cachePath, "utf-8");
21745
23034
  raw = JSON.parse(content);
21746
23035
  } catch (err2) {
21747
23036
  return {
@@ -21930,10 +23219,10 @@ var init_adapter_origin = __esm({
21930
23219
  });
21931
23220
 
21932
23221
  // 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";
23222
+ import { existsSync as existsSync19, readFileSync as readFileSync18, lstatSync as lstatSync5 } from "node:fs";
23223
+ import { homedir as homedir5 } from "node:os";
21935
23224
  import { resolve as resolve15, isAbsolute } from "node:path";
21936
- import { createHash as createHash5 } from "node:crypto";
23225
+ import { createHash as createHash6 } from "node:crypto";
21937
23226
  import { z as z5 } from "zod";
21938
23227
  function computeLocalFingerprint(localPaths, projectRoot) {
21939
23228
  const tuples = [];
@@ -21941,13 +23230,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
21941
23230
  const abs = isAbsolute(p19) ? p19 : resolve15(projectRoot, p19);
21942
23231
  let contentTag;
21943
23232
  try {
21944
- const lst = lstatSync3(abs);
23233
+ const lst = lstatSync5(abs);
21945
23234
  if (lst.isSymbolicLink()) {
21946
23235
  contentTag = "<symlink>";
21947
23236
  } else if (!lst.isFile()) {
21948
23237
  contentTag = "<not-a-file>";
21949
23238
  } else {
21950
- contentTag = createHash5("sha256").update(readFileSync15(abs)).digest("hex");
23239
+ contentTag = createHash6("sha256").update(readFileSync18(abs)).digest("hex");
21951
23240
  }
21952
23241
  } catch {
21953
23242
  contentTag = "<missing>";
@@ -21956,13 +23245,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
21956
23245
  }
21957
23246
  tuples.sort((a2, b2) => a2.path < b2.path ? -1 : a2.path > b2.path ? 1 : 0);
21958
23247
  const canonical = JSON.stringify(tuples);
21959
- return createHash5("sha256").update(canonical).digest("hex");
23248
+ return createHash6("sha256").update(canonical).digest("hex");
21960
23249
  }
21961
23250
  function readFingerprintSentinel(path = FINGERPRINT_PATH) {
21962
- if (!existsSync18(path)) return null;
23251
+ if (!existsSync19(path)) return null;
21963
23252
  let raw;
21964
23253
  try {
21965
- raw = JSON.parse(readFileSync15(path, "utf-8"));
23254
+ raw = JSON.parse(readFileSync18(path, "utf-8"));
21966
23255
  } catch {
21967
23256
  return null;
21968
23257
  }
@@ -22010,7 +23299,7 @@ var init_local_fingerprint = __esm({
22010
23299
  "use strict";
22011
23300
  init_atomic_write();
22012
23301
  init_manifest_schema();
22013
- FINGERPRINT_PATH = resolve15(homedir4(), ".massu", "adapters-local-fingerprint.json");
23302
+ FINGERPRINT_PATH = resolve15(homedir5(), ".massu", "adapters-local-fingerprint.json");
22014
23303
  FingerprintSentinelSchema = z5.object({
22015
23304
  fingerprint: z5.string().regex(/^[0-9a-f]{64}$/),
22016
23305
  source: z5.enum(["cli", "cli-resync"]),
@@ -22026,15 +23315,15 @@ var init_local_fingerprint = __esm({
22026
23315
  });
22027
23316
 
22028
23317
  // 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";
23318
+ import { readFileSync as readFileSync19, readdirSync as readdirSync12, lstatSync as lstatSync6, existsSync as existsSync20 } from "node:fs";
23319
+ import { join as join10, relative as relative5, sep } from "node:path";
23320
+ import { homedir as homedir6 } from "node:os";
22032
23321
  import { resolve as resolve16 } from "node:path";
22033
- import { createHash as createHash6 } from "node:crypto";
23322
+ import { createHash as createHash7 } from "node:crypto";
22034
23323
  import { z as z6 } from "zod";
22035
23324
  function containsHiddenDirs(packageDir) {
22036
23325
  for (const hidden of EXCLUDED_DIR_NAMES) {
22037
- if (existsSync19(`${packageDir}/${hidden}`)) {
23326
+ if (existsSync20(`${packageDir}/${hidden}`)) {
22038
23327
  return hidden;
22039
23328
  }
22040
23329
  }
@@ -22046,15 +23335,15 @@ function sha256OfDir(dir, opts = {}) {
22046
23335
  function walk(currentDir) {
22047
23336
  let entries;
22048
23337
  try {
22049
- entries = readdirSync9(currentDir);
23338
+ entries = readdirSync12(currentDir);
22050
23339
  } catch {
22051
23340
  return;
22052
23341
  }
22053
23342
  for (const entry of entries.sort()) {
22054
- const absPath = join7(currentDir, entry);
23343
+ const absPath = join10(currentDir, entry);
22055
23344
  let lst;
22056
23345
  try {
22057
- lst = lstatSync4(absPath);
23346
+ lst = lstatSync6(absPath);
22058
23347
  } catch {
22059
23348
  continue;
22060
23349
  }
@@ -22072,15 +23361,15 @@ function sha256OfDir(dir, opts = {}) {
22072
23361
  `sha256OfDir: file ${absPath} exceeds maxFileBytes (${lst.size} > ${maxFileBytes}); adapter packages should not ship files this large.`
22073
23362
  );
22074
23363
  }
22075
- const rel = relative4(dir, absPath).split(sep).join("/");
23364
+ const rel = relative5(dir, absPath).split(sep).join("/");
22076
23365
  files.push({ relativePath: rel, absPath });
22077
23366
  }
22078
23367
  }
22079
23368
  walk(dir);
22080
23369
  files.sort((a2, b2) => a2.relativePath < b2.relativePath ? -1 : a2.relativePath > b2.relativePath ? 1 : 0);
22081
- const top = createHash6("sha256");
23370
+ const top = createHash7("sha256");
22082
23371
  for (const f2 of files) {
22083
- const fileHash = createHash6("sha256").update(readFileSync16(f2.absPath)).digest("hex");
23372
+ const fileHash = createHash7("sha256").update(readFileSync19(f2.absPath)).digest("hex");
22084
23373
  top.update(f2.relativePath, "utf-8");
22085
23374
  top.update("\0", "utf-8");
22086
23375
  top.update(fileHash, "utf-8");
@@ -22089,10 +23378,10 @@ function sha256OfDir(dir, opts = {}) {
22089
23378
  return top.digest("hex");
22090
23379
  }
22091
23380
  function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
22092
- if (!existsSync19(path)) return {};
23381
+ if (!existsSync20(path)) return {};
22093
23382
  let raw;
22094
23383
  try {
22095
- raw = JSON.parse(readFileSync16(path, "utf-8"));
23384
+ raw = JSON.parse(readFileSync19(path, "utf-8"));
22096
23385
  } catch {
22097
23386
  return {};
22098
23387
  }
@@ -22156,7 +23445,7 @@ var init_install_tracking = __esm({
22156
23445
  "src/security/install-tracking.ts"() {
22157
23446
  "use strict";
22158
23447
  init_atomic_write();
22159
- INSTALLED_MANIFEST_PATH = resolve16(homedir5(), ".massu", "adapter-manifest-installed.json");
23448
+ INSTALLED_MANIFEST_PATH = resolve16(homedir6(), ".massu", "adapter-manifest-installed.json");
22160
23449
  DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
22161
23450
  EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
22162
23451
  InstallEntrySchema = z6.object({
@@ -22179,18 +23468,18 @@ var init_install_tracking = __esm({
22179
23468
  });
22180
23469
 
22181
23470
  // src/detect/adapters/discover.ts
22182
- import { existsSync as existsSync20, readdirSync as readdirSync10, readFileSync as readFileSync17, lstatSync as lstatSync5 } from "node:fs";
23471
+ import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync20, lstatSync as lstatSync7 } from "node:fs";
22183
23472
  import { resolve as resolve17, isAbsolute as isAbsolute2 } from "node:path";
22184
23473
  import { z as z7 } from "zod";
22185
23474
  function walkNodeModules(projectRoot, warnings) {
22186
23475
  const nodeModulesDir = resolve17(projectRoot, "node_modules");
22187
- if (!existsSync20(nodeModulesDir)) {
23476
+ if (!existsSync21(nodeModulesDir)) {
22188
23477
  return [];
22189
23478
  }
22190
23479
  const candidates = [];
22191
23480
  let topLevelEntries;
22192
23481
  try {
22193
- topLevelEntries = readdirSync10(nodeModulesDir);
23482
+ topLevelEntries = readdirSync13(nodeModulesDir);
22194
23483
  } catch (err2) {
22195
23484
  warnings.push(`failed to read node_modules: ${err2 instanceof Error ? err2.message : String(err2)}`);
22196
23485
  return [];
@@ -22200,7 +23489,7 @@ function walkNodeModules(projectRoot, warnings) {
22200
23489
  const entryPath = resolve17(nodeModulesDir, entry);
22201
23490
  let entryStat;
22202
23491
  try {
22203
- entryStat = lstatSync5(entryPath);
23492
+ entryStat = lstatSync7(entryPath);
22204
23493
  } catch {
22205
23494
  continue;
22206
23495
  }
@@ -22214,7 +23503,7 @@ function walkNodeModules(projectRoot, warnings) {
22214
23503
  if (entry.startsWith("@")) {
22215
23504
  let scopedEntries;
22216
23505
  try {
22217
- scopedEntries = readdirSync10(entryPath);
23506
+ scopedEntries = readdirSync13(entryPath);
22218
23507
  } catch {
22219
23508
  continue;
22220
23509
  }
@@ -22222,7 +23511,7 @@ function walkNodeModules(projectRoot, warnings) {
22222
23511
  const subPath = resolve17(entryPath, sub);
22223
23512
  let subStat;
22224
23513
  try {
22225
- subStat = lstatSync5(subPath);
23514
+ subStat = lstatSync7(subPath);
22226
23515
  } catch {
22227
23516
  continue;
22228
23517
  }
@@ -22239,10 +23528,10 @@ function walkNodeModules(projectRoot, warnings) {
22239
23528
  }
22240
23529
  function tryReadAdapterPackage(packageDir, warnings) {
22241
23530
  const pkgJsonPath = resolve17(packageDir, "package.json");
22242
- if (!existsSync20(pkgJsonPath)) return null;
23531
+ if (!existsSync21(pkgJsonPath)) return null;
22243
23532
  let raw;
22244
23533
  try {
22245
- raw = JSON.parse(readFileSync17(pkgJsonPath, "utf-8"));
23534
+ raw = JSON.parse(readFileSync20(pkgJsonPath, "utf-8"));
22246
23535
  } catch (err2) {
22247
23536
  warnings.push(
22248
23537
  `skipping ${packageDir}: package.json parse failed (${err2 instanceof Error ? err2.message : String(err2)})`
@@ -22377,7 +23666,7 @@ function discoverAdapters(opts) {
22377
23666
  for (const localPath of opts.configLocalPaths) {
22378
23667
  if (seenIds.has(localPath)) continue;
22379
23668
  const absPath = isAbsolute2(localPath) ? localPath : resolve17(opts.projectRoot, localPath);
22380
- if (!existsSync20(absPath)) {
23669
+ if (!existsSync21(absPath)) {
22381
23670
  warnings.push(
22382
23671
  `local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
22383
23672
  );
@@ -22458,7 +23747,7 @@ __export(adapters_exports, {
22458
23747
  runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
22459
23748
  runAdaptersSearch: () => runAdaptersSearch
22460
23749
  });
22461
- import { existsSync as existsSync21, readFileSync as readFileSync18 } from "node:fs";
23750
+ import { existsSync as existsSync22, readFileSync as readFileSync21 } from "node:fs";
22462
23751
  import { resolve as resolve18 } from "node:path";
22463
23752
  import { parseDocument } from "yaml";
22464
23753
  async function handleAdaptersSubcommand(args3) {
@@ -22727,7 +24016,7 @@ function mutateLocalArray(mutator, command) {
22727
24016
  return { exitCode: 2 };
22728
24017
  }
22729
24018
  const yamlPath = resolve18(projectRoot, "massu.config.yaml");
22730
- if (!existsSync21(yamlPath)) {
24019
+ if (!existsSync22(yamlPath)) {
22731
24020
  process.stderr.write(
22732
24021
  `${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
22733
24022
  `
@@ -22736,7 +24025,7 @@ function mutateLocalArray(mutator, command) {
22736
24025
  }
22737
24026
  let yamlText;
22738
24027
  try {
22739
- yamlText = readFileSync18(yamlPath, "utf-8");
24028
+ yamlText = readFileSync21(yamlPath, "utf-8");
22740
24029
  } catch (err2) {
22741
24030
  process.stderr.write(`${command}: failed to read ${yamlPath}: ${err2 instanceof Error ? err2.message : String(err2)}
22742
24031
  `);
@@ -22827,7 +24116,7 @@ async function runAdaptersInstall(args3) {
22827
24116
  return { exitCode: 1 };
22828
24117
  }
22829
24118
  const packageDir = resolve18(projectRoot, "node_modules", ...packageName.split("/"));
22830
- if (!existsSync21(packageDir)) {
24119
+ if (!existsSync22(packageDir)) {
22831
24120
  process.stderr.write(
22832
24121
  `install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
22833
24122
  `
@@ -22835,14 +24124,14 @@ async function runAdaptersInstall(args3) {
22835
24124
  return { exitCode: 1 };
22836
24125
  }
22837
24126
  const pkgJsonPath = resolve18(packageDir, "package.json");
22838
- if (!existsSync21(pkgJsonPath)) {
24127
+ if (!existsSync22(pkgJsonPath)) {
22839
24128
  process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
22840
24129
  `);
22841
24130
  return { exitCode: 1 };
22842
24131
  }
22843
24132
  let pkgJson;
22844
24133
  try {
22845
- pkgJson = JSON.parse(readFileSync18(pkgJsonPath, "utf-8"));
24134
+ pkgJson = JSON.parse(readFileSync21(pkgJsonPath, "utf-8"));
22846
24135
  } catch (err2) {
22847
24136
  process.stderr.write(`install: ${packageName} has malformed package.json: ${err2 instanceof Error ? err2.message : String(err2)}
22848
24137
  `);
@@ -22962,7 +24251,7 @@ async function runAdaptersResign(_args) {
22962
24251
  continue;
22963
24252
  }
22964
24253
  const packageDir = resolve18(projectRoot, "node_modules", ...name2.split("/"));
22965
- if (!existsSync21(packageDir)) {
24254
+ if (!existsSync22(packageDir)) {
22966
24255
  removed++;
22967
24256
  warnings.push(`${name2}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
22968
24257
  removeInstalledManifestEntry(name2);
@@ -23049,11 +24338,11 @@ var init_adapters2 = __esm({
23049
24338
 
23050
24339
  // src/db.ts
23051
24340
  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";
24341
+ import { dirname as dirname11, join as join11 } from "path";
24342
+ import { existsSync as existsSync23, mkdirSync as mkdirSync9, readdirSync as readdirSync14, statSync as statSync11 } from "fs";
23054
24343
  function getCodeGraphDb() {
23055
24344
  const dbPath = getResolvedPaths().codegraphDbPath;
23056
- if (!existsSync22(dbPath)) {
24345
+ if (!existsSync23(dbPath)) {
23057
24346
  throw new Error(`CodeGraph database not found at ${dbPath}. Run 'npx @colbymchenry/codegraph sync' first.`);
23058
24347
  }
23059
24348
  const db = new Database2(dbPath, { readonly: true });
@@ -23062,9 +24351,9 @@ function getCodeGraphDb() {
23062
24351
  }
23063
24352
  function getDataDb() {
23064
24353
  const dbPath = getResolvedPaths().dataDbPath;
23065
- const dir = dirname10(dbPath);
23066
- if (!existsSync22(dir)) {
23067
- mkdirSync8(dir, { recursive: true });
24354
+ const dir = dirname11(dbPath);
24355
+ if (!existsSync23(dir)) {
24356
+ mkdirSync9(dir, { recursive: true });
23068
24357
  }
23069
24358
  const db = new Database2(dbPath);
23070
24359
  db.pragma("journal_mode = WAL");
@@ -23334,14 +24623,14 @@ function isPythonDataStale(dataDb2, pythonRoot) {
23334
24623
  const lastBuildTime = new Date(lastBuild.value).getTime();
23335
24624
  function checkDir(dir) {
23336
24625
  try {
23337
- const entries = readdirSync11(dir, { withFileTypes: true });
24626
+ const entries = readdirSync14(dir, { withFileTypes: true });
23338
24627
  for (const entry of entries) {
23339
- const fullPath = join8(dir, entry.name);
24628
+ const fullPath = join11(dir, entry.name);
23340
24629
  if (entry.isDirectory()) {
23341
24630
  if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
23342
24631
  if (checkDir(fullPath)) return true;
23343
24632
  } else if (entry.name.endsWith(".py")) {
23344
- if (statSync9(fullPath).mtimeMs > lastBuildTime) return true;
24633
+ if (statSync11(fullPath).mtimeMs > lastBuildTime) return true;
23345
24634
  }
23346
24635
  }
23347
24636
  } catch {
@@ -23437,8 +24726,8 @@ var init_rules = __esm({
23437
24726
  });
23438
24727
 
23439
24728
  // 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";
24729
+ import { readFileSync as readFileSync22, existsSync as existsSync24, statSync as statSync12 } from "fs";
24730
+ import { resolve as resolve20, dirname as dirname12, join as join12 } from "path";
23442
24731
  function parseImports(source) {
23443
24732
  const imports = [];
23444
24733
  const lines = source.split("\n");
@@ -23496,21 +24785,21 @@ function resolveImportPath(specifier, fromFile) {
23496
24785
  const paths = getResolvedPaths();
23497
24786
  basePath = resolve20(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
23498
24787
  } else {
23499
- basePath = resolve20(dirname11(fromFile), specifier);
24788
+ basePath = resolve20(dirname12(fromFile), specifier);
23500
24789
  }
23501
- if (existsSync23(basePath) && !isDirectory(basePath)) {
24790
+ if (existsSync24(basePath) && !isDirectory(basePath)) {
23502
24791
  return toRelative(basePath);
23503
24792
  }
23504
24793
  const resolvedPaths = getResolvedPaths();
23505
24794
  for (const ext of resolvedPaths.extensions) {
23506
24795
  const withExt = basePath + ext;
23507
- if (existsSync23(withExt)) {
24796
+ if (existsSync24(withExt)) {
23508
24797
  return toRelative(withExt);
23509
24798
  }
23510
24799
  }
23511
24800
  for (const indexFile of resolvedPaths.indexFiles) {
23512
- const indexPath = join9(basePath, indexFile);
23513
- if (existsSync23(indexPath)) {
24801
+ const indexPath = join12(basePath, indexFile);
24802
+ if (existsSync24(indexPath)) {
23514
24803
  return toRelative(indexPath);
23515
24804
  }
23516
24805
  }
@@ -23518,7 +24807,7 @@ function resolveImportPath(specifier, fromFile) {
23518
24807
  }
23519
24808
  function isDirectory(path) {
23520
24809
  try {
23521
- return statSync10(path).isDirectory();
24810
+ return statSync12(path).isDirectory();
23522
24811
  } catch {
23523
24812
  return false;
23524
24813
  }
@@ -23547,10 +24836,10 @@ function buildImportIndex(dataDb2, codegraphDb2) {
23547
24836
  let batch = [];
23548
24837
  for (const file of files) {
23549
24838
  const absPath = ensureWithinRoot(resolve20(projectRoot, file.path), projectRoot);
23550
- if (!existsSync23(absPath)) continue;
24839
+ if (!existsSync24(absPath)) continue;
23551
24840
  let source;
23552
24841
  try {
23553
- source = readFileSync19(absPath, "utf-8");
24842
+ source = readFileSync22(absPath, "utf-8");
23554
24843
  } catch {
23555
24844
  continue;
23556
24845
  }
@@ -23586,15 +24875,15 @@ var init_import_resolver = __esm({
23586
24875
  });
23587
24876
 
23588
24877
  // 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";
24878
+ import { readFileSync as readFileSync23, existsSync as existsSync25, readdirSync as readdirSync15 } from "fs";
24879
+ import { resolve as resolve21, join as join13 } from "path";
23591
24880
  function parseRootRouter() {
23592
24881
  const paths = getResolvedPaths();
23593
24882
  const rootPath = paths.rootRouterPath;
23594
- if (!existsSync24(rootPath)) {
24883
+ if (!existsSync25(rootPath)) {
23595
24884
  throw new Error(`Root router not found at ${rootPath}`);
23596
24885
  }
23597
- const source = readFileSync20(rootPath, "utf-8");
24886
+ const source = readFileSync23(rootPath, "utf-8");
23598
24887
  const mappings = [];
23599
24888
  const importMap = /* @__PURE__ */ new Map();
23600
24889
  const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
@@ -23606,12 +24895,12 @@ function parseRootRouter() {
23606
24895
  for (const ext of [".ts", ".tsx", ""]) {
23607
24896
  const candidate = fullPath + ext;
23608
24897
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
23609
- if (existsSync24(candidate)) {
24898
+ if (existsSync25(candidate)) {
23610
24899
  filePath = routersRelPath + "/" + filePath + ext;
23611
24900
  break;
23612
24901
  }
23613
- const indexCandidate = join10(fullPath, "index.ts");
23614
- if (existsSync24(indexCandidate)) {
24902
+ const indexCandidate = join13(fullPath, "index.ts");
24903
+ if (existsSync25(indexCandidate)) {
23615
24904
  filePath = routersRelPath + "/" + filePath + "/index.ts";
23616
24905
  break;
23617
24906
  }
@@ -23631,8 +24920,8 @@ function parseRootRouter() {
23631
24920
  }
23632
24921
  function extractProcedures(routerFilePath) {
23633
24922
  const absPath = resolve21(getProjectRoot(), routerFilePath);
23634
- if (!existsSync24(absPath)) return [];
23635
- const source = readFileSync20(absPath, "utf-8");
24923
+ if (!existsSync25(absPath)) return [];
24924
+ const source = readFileSync23(absPath, "utf-8");
23636
24925
  const procedures = [];
23637
24926
  const seen = /* @__PURE__ */ new Set();
23638
24927
  const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
@@ -23661,21 +24950,21 @@ function findUICallSites(routerKey, procedureName) {
23661
24950
  ];
23662
24951
  const searchPattern = `api.${routerKey}.${procedureName}`;
23663
24952
  for (const dir of searchDirs) {
23664
- if (!existsSync24(dir)) continue;
24953
+ if (!existsSync25(dir)) continue;
23665
24954
  searchDirectory(dir, searchPattern, callSites);
23666
24955
  }
23667
24956
  return callSites;
23668
24957
  }
23669
24958
  function searchDirectory(dir, pattern, results) {
23670
- const entries = readdirSync12(dir, { withFileTypes: true });
24959
+ const entries = readdirSync15(dir, { withFileTypes: true });
23671
24960
  for (const entry of entries) {
23672
- const fullPath = join10(dir, entry.name);
24961
+ const fullPath = join13(dir, entry.name);
23673
24962
  if (entry.isDirectory()) {
23674
24963
  if (entry.name === "node_modules" || entry.name === ".next") continue;
23675
24964
  searchDirectory(fullPath, pattern, results);
23676
24965
  } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
23677
24966
  try {
23678
- const source = readFileSync20(fullPath, "utf-8");
24967
+ const source = readFileSync23(fullPath, "utf-8");
23679
24968
  const lines = source.split("\n");
23680
24969
  for (let i2 = 0; i2 < lines.length; i2++) {
23681
24970
  if (lines[i2].includes(pattern)) {
@@ -23738,7 +25027,7 @@ var init_trpc_index = __esm({
23738
25027
  });
23739
25028
 
23740
25029
  // src/page-deps.ts
23741
- import { readFileSync as readFileSync21, existsSync as existsSync25 } from "fs";
25030
+ import { readFileSync as readFileSync24, existsSync as existsSync26 } from "fs";
23742
25031
  import { resolve as resolve22 } from "path";
23743
25032
  function deriveRoute(pageFile) {
23744
25033
  let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
@@ -23778,9 +25067,9 @@ function findRouterCalls(files) {
23778
25067
  const projectRoot = getProjectRoot();
23779
25068
  for (const file of files) {
23780
25069
  const absPath = ensureWithinRoot(resolve22(projectRoot, file), projectRoot);
23781
- if (!existsSync25(absPath)) continue;
25070
+ if (!existsSync26(absPath)) continue;
23782
25071
  try {
23783
- const source = readFileSync21(absPath, "utf-8");
25072
+ const source = readFileSync24(absPath, "utf-8");
23784
25073
  const apiCallRegex = /api\.(\w+)\.\w+/g;
23785
25074
  let match;
23786
25075
  while ((match = apiCallRegex.exec(source)) !== null) {
@@ -23799,9 +25088,9 @@ function findTablesFromRouters(routerNames, dataDb2) {
23799
25088
  ).all(routerName);
23800
25089
  for (const proc of procs) {
23801
25090
  const absPath = ensureWithinRoot(resolve22(getProjectRoot(), proc.router_file), getProjectRoot());
23802
- if (!existsSync25(absPath)) continue;
25091
+ if (!existsSync26(absPath)) continue;
23803
25092
  try {
23804
- const source = readFileSync21(absPath, "utf-8");
25093
+ const source = readFileSync24(absPath, "utf-8");
23805
25094
  const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
23806
25095
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
23807
25096
  const tableRegex = new RegExp(regexStr + "\\.", "g");
@@ -24070,14 +25359,14 @@ var init_domains = __esm({
24070
25359
  });
24071
25360
 
24072
25361
  // src/schema-mapper.ts
24073
- import { readFileSync as readFileSync22, existsSync as existsSync26, readdirSync as readdirSync13 } from "fs";
24074
- import { join as join11 } from "path";
25362
+ import { readFileSync as readFileSync25, existsSync as existsSync27, readdirSync as readdirSync16 } from "fs";
25363
+ import { join as join14 } from "path";
24075
25364
  function parsePrismaSchema() {
24076
25365
  const schemaPath = getResolvedPaths().prismaSchemaPath;
24077
- if (!existsSync26(schemaPath)) {
25366
+ if (!existsSync27(schemaPath)) {
24078
25367
  throw new Error(`Prisma schema not found at ${schemaPath}`);
24079
25368
  }
24080
- const source = readFileSync22(schemaPath, "utf-8");
25369
+ const source = readFileSync25(schemaPath, "utf-8");
24081
25370
  const models = [];
24082
25371
  const sourceLines = source.split("\n");
24083
25372
  let i2 = 0;
@@ -24135,14 +25424,14 @@ function toSnakeCase(str) {
24135
25424
  function findColumnUsageInRouters(tableName) {
24136
25425
  const usage = /* @__PURE__ */ new Map();
24137
25426
  const routersDir = getResolvedPaths().routersDir;
24138
- if (!existsSync26(routersDir)) return usage;
25427
+ if (!existsSync27(routersDir)) return usage;
24139
25428
  scanDirectory(routersDir, tableName, usage);
24140
25429
  return usage;
24141
25430
  }
24142
25431
  function scanDirectory(dir, tableName, usage) {
24143
- const entries = readdirSync13(dir, { withFileTypes: true });
25432
+ const entries = readdirSync16(dir, { withFileTypes: true });
24144
25433
  for (const entry of entries) {
24145
- const fullPath = join11(dir, entry.name);
25434
+ const fullPath = join14(dir, entry.name);
24146
25435
  if (entry.isDirectory()) {
24147
25436
  scanDirectory(fullPath, tableName, usage);
24148
25437
  } else if (entry.name.endsWith(".ts")) {
@@ -24152,7 +25441,7 @@ function scanDirectory(dir, tableName, usage) {
24152
25441
  }
24153
25442
  function scanFile(absPath, tableName, usage) {
24154
25443
  try {
24155
- const source = readFileSync22(absPath, "utf-8");
25444
+ const source = readFileSync25(absPath, "utf-8");
24156
25445
  if (!source.includes(tableName)) return;
24157
25446
  const relPath = absPath.slice(getProjectRoot().length + 1);
24158
25447
  const lines = source.split("\n");
@@ -24197,15 +25486,15 @@ function detectMismatches(models) {
24197
25486
  }
24198
25487
  function findFilesUsingColumn(dir, column, tableName) {
24199
25488
  const result = [];
24200
- if (!existsSync26(dir)) return result;
24201
- const entries = readdirSync13(dir, { withFileTypes: true });
25489
+ if (!existsSync27(dir)) return result;
25490
+ const entries = readdirSync16(dir, { withFileTypes: true });
24202
25491
  for (const entry of entries) {
24203
- const fullPath = join11(dir, entry.name);
25492
+ const fullPath = join14(dir, entry.name);
24204
25493
  if (entry.isDirectory()) {
24205
25494
  result.push(...findFilesUsingColumn(fullPath, column, tableName));
24206
25495
  } else if (entry.name.endsWith(".ts")) {
24207
25496
  try {
24208
- const source = readFileSync22(fullPath, "utf-8");
25497
+ const source = readFileSync25(fullPath, "utf-8");
24209
25498
  if (source.includes(tableName) && source.includes(column)) {
24210
25499
  result.push(fullPath.slice(getProjectRoot().length + 1));
24211
25500
  }
@@ -24350,48 +25639,48 @@ var init_import_parser = __esm({
24350
25639
  });
24351
25640
 
24352
25641
  // 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";
25642
+ import { readFileSync as readFileSync26, existsSync as existsSync28, readdirSync as readdirSync17 } from "fs";
25643
+ import { resolve as resolve24, join as join15, relative as relative6, dirname as dirname13 } from "path";
24355
25644
  function resolvePythonModulePath(module2, fromFile, pythonRoot, level) {
24356
25645
  const projectRoot = getProjectRoot();
24357
25646
  if (level > 0) {
24358
- let baseDir = dirname12(resolve24(projectRoot, fromFile));
25647
+ let baseDir = dirname13(resolve24(projectRoot, fromFile));
24359
25648
  for (let i2 = 1; i2 < level; i2++) {
24360
- baseDir = dirname12(baseDir);
25649
+ baseDir = dirname13(baseDir);
24361
25650
  }
24362
25651
  const modulePart = module2.replace(/^\.+/, "");
24363
25652
  if (modulePart) {
24364
25653
  const parts3 = modulePart.split(".");
24365
- return tryResolvePythonPath(join12(baseDir, ...parts3), projectRoot);
25654
+ return tryResolvePythonPath(join15(baseDir, ...parts3), projectRoot);
24366
25655
  }
24367
25656
  return tryResolvePythonPath(baseDir, projectRoot);
24368
25657
  }
24369
25658
  const parts2 = module2.split(".");
24370
- const candidate = join12(resolve24(projectRoot, pythonRoot), ...parts2);
25659
+ const candidate = join15(resolve24(projectRoot, pythonRoot), ...parts2);
24371
25660
  return tryResolvePythonPath(candidate, projectRoot);
24372
25661
  }
24373
25662
  function tryResolvePythonPath(basePath, projectRoot) {
24374
- if (existsSync27(basePath + ".py")) {
24375
- return relative5(projectRoot, basePath + ".py");
25663
+ if (existsSync28(basePath + ".py")) {
25664
+ return relative6(projectRoot, basePath + ".py");
24376
25665
  }
24377
- if (existsSync27(join12(basePath, "__init__.py"))) {
24378
- return relative5(projectRoot, join12(basePath, "__init__.py"));
25666
+ if (existsSync28(join15(basePath, "__init__.py"))) {
25667
+ return relative6(projectRoot, join15(basePath, "__init__.py"));
24379
25668
  }
24380
- if (basePath.endsWith(".py") && existsSync27(basePath)) {
24381
- return relative5(projectRoot, basePath);
25669
+ if (basePath.endsWith(".py") && existsSync28(basePath)) {
25670
+ return relative6(projectRoot, basePath);
24382
25671
  }
24383
25672
  return null;
24384
25673
  }
24385
25674
  function walkPythonFiles(dir, excludeDirs) {
24386
25675
  const files = [];
24387
25676
  try {
24388
- const entries = readdirSync14(dir, { withFileTypes: true });
25677
+ const entries = readdirSync17(dir, { withFileTypes: true });
24389
25678
  for (const entry of entries) {
24390
25679
  if (entry.isDirectory()) {
24391
25680
  if (excludeDirs.includes(entry.name)) continue;
24392
- files.push(...walkPythonFiles(join12(dir, entry.name), excludeDirs));
25681
+ files.push(...walkPythonFiles(join15(dir, entry.name), excludeDirs));
24393
25682
  } else if (entry.name.endsWith(".py")) {
24394
- files.push(join12(dir, entry.name));
25683
+ files.push(join15(dir, entry.name));
24395
25684
  }
24396
25685
  }
24397
25686
  } catch {
@@ -24414,10 +25703,10 @@ function buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__
24414
25703
  });
24415
25704
  const batch = [];
24416
25705
  for (const absFile of files) {
24417
- const relFile = relative5(projectRoot, absFile);
25706
+ const relFile = relative6(projectRoot, absFile);
24418
25707
  let source;
24419
25708
  try {
24420
- source = readFileSync23(absFile, "utf-8");
25709
+ source = readFileSync26(absFile, "utf-8");
24421
25710
  } catch {
24422
25711
  continue;
24423
25712
  }
@@ -24683,18 +25972,18 @@ var init_route_parser = __esm({
24683
25972
  });
24684
25973
 
24685
25974
  // 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";
25975
+ import { readFileSync as readFileSync27, readdirSync as readdirSync18 } from "fs";
25976
+ import { join as join16, relative as relative7 } from "path";
24688
25977
  function walkPyFiles(dir, excludeDirs) {
24689
25978
  const files = [];
24690
25979
  try {
24691
- const entries = readdirSync15(dir, { withFileTypes: true });
25980
+ const entries = readdirSync18(dir, { withFileTypes: true });
24692
25981
  for (const entry of entries) {
24693
25982
  if (entry.isDirectory()) {
24694
25983
  if (excludeDirs.includes(entry.name)) continue;
24695
- files.push(...walkPyFiles(join13(dir, entry.name), excludeDirs));
25984
+ files.push(...walkPyFiles(join16(dir, entry.name), excludeDirs));
24696
25985
  } else if (entry.name.endsWith(".py")) {
24697
- files.push(join13(dir, entry.name));
25986
+ files.push(join16(dir, entry.name));
24698
25987
  }
24699
25988
  }
24700
25989
  } catch {
@@ -24703,7 +25992,7 @@ function walkPyFiles(dir, excludeDirs) {
24703
25992
  }
24704
25993
  function buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
24705
25994
  const projectRoot = getProjectRoot();
24706
- const absRoot = join13(projectRoot, pythonRoot);
25995
+ const absRoot = join16(projectRoot, pythonRoot);
24707
25996
  dataDb2.exec("DELETE FROM massu_py_routes");
24708
25997
  dataDb2.exec("DELETE FROM massu_py_route_callers");
24709
25998
  const insertStmt = dataDb2.prepare(
@@ -24713,10 +26002,10 @@ function buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
24713
26002
  let count = 0;
24714
26003
  dataDb2.transaction(() => {
24715
26004
  for (const absFile of files) {
24716
- const relFile = relative6(projectRoot, absFile);
26005
+ const relFile = relative7(projectRoot, absFile);
24717
26006
  let source;
24718
26007
  try {
24719
- source = readFileSync24(absFile, "utf-8");
26008
+ source = readFileSync27(absFile, "utf-8");
24720
26009
  } catch {
24721
26010
  continue;
24722
26011
  }
@@ -24927,18 +26216,18 @@ var init_model_parser = __esm({
24927
26216
  });
24928
26217
 
24929
26218
  // 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";
26219
+ import { readFileSync as readFileSync28, readdirSync as readdirSync19 } from "fs";
26220
+ import { join as join17, relative as relative8 } from "path";
24932
26221
  function walkPyFiles2(dir, excludeDirs) {
24933
26222
  const files = [];
24934
26223
  try {
24935
- const entries = readdirSync16(dir, { withFileTypes: true });
26224
+ const entries = readdirSync19(dir, { withFileTypes: true });
24936
26225
  for (const entry of entries) {
24937
26226
  if (entry.isDirectory()) {
24938
26227
  if (excludeDirs.includes(entry.name)) continue;
24939
- files.push(...walkPyFiles2(join14(dir, entry.name), excludeDirs));
26228
+ files.push(...walkPyFiles2(join17(dir, entry.name), excludeDirs));
24940
26229
  } else if (entry.name.endsWith(".py")) {
24941
- files.push(join14(dir, entry.name));
26230
+ files.push(join17(dir, entry.name));
24942
26231
  }
24943
26232
  }
24944
26233
  } catch {
@@ -24947,7 +26236,7 @@ function walkPyFiles2(dir, excludeDirs) {
24947
26236
  }
24948
26237
  function buildPythonModelIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
24949
26238
  const projectRoot = getProjectRoot();
24950
- const absRoot = join14(projectRoot, pythonRoot);
26239
+ const absRoot = join17(projectRoot, pythonRoot);
24951
26240
  dataDb2.exec("DELETE FROM massu_py_models");
24952
26241
  dataDb2.exec("DELETE FROM massu_py_fk_edges");
24953
26242
  const insertModel = dataDb2.prepare(
@@ -24960,10 +26249,10 @@ function buildPythonModelIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
24960
26249
  let count = 0;
24961
26250
  dataDb2.transaction(() => {
24962
26251
  for (const absFile of files) {
24963
- const relFile = relative7(projectRoot, absFile);
26252
+ const relFile = relative8(projectRoot, absFile);
24964
26253
  let source;
24965
26254
  try {
24966
- source = readFileSync25(absFile, "utf-8");
26255
+ source = readFileSync28(absFile, "utf-8");
24967
26256
  } catch {
24968
26257
  continue;
24969
26258
  }
@@ -25223,19 +26512,19 @@ var init_migration_parser = __esm({
25223
26512
  });
25224
26513
 
25225
26514
  // 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";
26515
+ import { readFileSync as readFileSync29, readdirSync as readdirSync20 } from "fs";
26516
+ import { join as join18, relative as relative9 } from "path";
25228
26517
  function buildPythonMigrationIndex(dataDb2, alembicDir) {
25229
26518
  const projectRoot = getProjectRoot();
25230
- const absDir = join15(projectRoot, alembicDir);
26519
+ const absDir = join18(projectRoot, alembicDir);
25231
26520
  dataDb2.exec("DELETE FROM massu_py_migrations");
25232
- const versionsDir = join15(absDir, "versions");
26521
+ const versionsDir = join18(absDir, "versions");
25233
26522
  let files = [];
25234
26523
  try {
25235
- files = readdirSync17(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join15(versionsDir, f2));
26524
+ files = readdirSync20(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join18(versionsDir, f2));
25236
26525
  } catch {
25237
26526
  try {
25238
- files = readdirSync17(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join15(absDir, f2));
26527
+ files = readdirSync20(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join18(absDir, f2));
25239
26528
  } catch {
25240
26529
  }
25241
26530
  }
@@ -25249,7 +26538,7 @@ function buildPythonMigrationIndex(dataDb2, alembicDir) {
25249
26538
  for (const absFile of files) {
25250
26539
  let source;
25251
26540
  try {
25252
- source = readFileSync26(absFile, "utf-8");
26541
+ source = readFileSync29(absFile, "utf-8");
25253
26542
  } catch {
25254
26543
  continue;
25255
26544
  }
@@ -25260,7 +26549,7 @@ function buildPythonMigrationIndex(dataDb2, alembicDir) {
25260
26549
  rows.push({
25261
26550
  revision: parsed.revision,
25262
26551
  downRevision: parsed.downRevision,
25263
- file: relative8(projectRoot, absFile),
26552
+ file: relative9(projectRoot, absFile),
25264
26553
  description: parsed.description,
25265
26554
  operations: JSON.stringify(parsed.operations)
25266
26555
  });
@@ -25283,12 +26572,12 @@ var init_migration_indexer = __esm({
25283
26572
  });
25284
26573
 
25285
26574
  // 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";
26575
+ import { readFileSync as readFileSync30, readdirSync as readdirSync21 } from "fs";
26576
+ import { join as join19, relative as relative10 } from "path";
25288
26577
  function buildPythonCouplingIndex(dataDb2) {
25289
26578
  const projectRoot = getProjectRoot();
25290
26579
  const config = getConfig();
25291
- const srcDir = join16(projectRoot, config.paths.source);
26580
+ const srcDir = join19(projectRoot, config.paths.source);
25292
26581
  const routes = dataDb2.prepare("SELECT id, method, path FROM massu_py_routes").all();
25293
26582
  if (routes.length === 0) return 0;
25294
26583
  dataDb2.exec("DELETE FROM massu_py_route_callers");
@@ -25317,10 +26606,10 @@ function buildPythonCouplingIndex(dataDb2) {
25317
26606
  ];
25318
26607
  dataDb2.transaction(() => {
25319
26608
  for (const absFile of frontendFiles) {
25320
- const relFile = relative9(projectRoot, absFile);
26609
+ const relFile = relative10(projectRoot, absFile);
25321
26610
  let source;
25322
26611
  try {
25323
- source = readFileSync27(absFile, "utf-8");
26612
+ source = readFileSync30(absFile, "utf-8");
25324
26613
  } catch {
25325
26614
  continue;
25326
26615
  }
@@ -25348,13 +26637,13 @@ function walkFrontendFiles(dir) {
25348
26637
  const files = [];
25349
26638
  const exclude = ["node_modules", ".next", "dist", ".git", "__pycache__", ".venv", "venv"];
25350
26639
  try {
25351
- const entries = readdirSync18(dir, { withFileTypes: true });
26640
+ const entries = readdirSync21(dir, { withFileTypes: true });
25352
26641
  for (const entry of entries) {
25353
26642
  if (entry.isDirectory()) {
25354
26643
  if (exclude.includes(entry.name)) continue;
25355
- files.push(...walkFrontendFiles(join16(dir, entry.name)));
26644
+ files.push(...walkFrontendFiles(join19(dir, entry.name)));
25356
26645
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
25357
- files.push(join16(dir, entry.name));
26646
+ files.push(join19(dir, entry.name));
25358
26647
  }
25359
26648
  }
25360
26649
  } catch {
@@ -25725,8 +27014,8 @@ var init_memory_tools = __esm({
25725
27014
  });
25726
27015
 
25727
27016
  // src/docs-tools.ts
25728
- import { readFileSync as readFileSync28, existsSync as existsSync28 } from "fs";
25729
- import { resolve as resolve25, basename as basename5 } from "path";
27017
+ import { readFileSync as readFileSync31, existsSync as existsSync29 } from "fs";
27018
+ import { resolve as resolve25, basename as basename6 } from "path";
25730
27019
  function p3(baseName) {
25731
27020
  return `${getConfig().toolPrefix}_${baseName}`;
25732
27021
  }
@@ -25781,10 +27070,10 @@ function handleDocsToolCall(name2, args3) {
25781
27070
  }
25782
27071
  function loadDocsMap() {
25783
27072
  const mapPath = getResolvedPaths().docsMapPath;
25784
- if (!existsSync28(mapPath)) {
27073
+ if (!existsSync29(mapPath)) {
25785
27074
  throw new Error(`docs-map.json not found at ${mapPath}`);
25786
27075
  }
25787
- return JSON.parse(readFileSync28(mapPath, "utf-8"));
27076
+ return JSON.parse(readFileSync31(mapPath, "utf-8"));
25788
27077
  }
25789
27078
  function matchesPattern(filePath, pattern) {
25790
27079
  const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
@@ -25793,7 +27082,7 @@ function matchesPattern(filePath, pattern) {
25793
27082
  function findAffectedMappings(docsMap, changedFiles) {
25794
27083
  const affected = /* @__PURE__ */ new Map();
25795
27084
  for (const file of changedFiles) {
25796
- const fileName = basename5(file);
27085
+ const fileName = basename6(file);
25797
27086
  for (const mapping of docsMap.mappings) {
25798
27087
  let matched = false;
25799
27088
  for (const routePattern of mapping.appRoutes) {
@@ -25855,12 +27144,12 @@ function extractFrontmatter(content) {
25855
27144
  function extractProcedureNames(routerPath) {
25856
27145
  const root = getProjectRoot();
25857
27146
  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"));
27147
+ if (!existsSync29(absPath)) {
27148
+ const altPath = ensureWithinRoot(resolve25(getResolvedPaths().srcDir, "../server/api/routers", basename6(routerPath)), root);
27149
+ if (!existsSync29(altPath)) return [];
27150
+ return extractProcedureNamesFromContent(readFileSync31(altPath, "utf-8"));
25862
27151
  }
25863
- return extractProcedureNamesFromContent(readFileSync28(absPath, "utf-8"));
27152
+ return extractProcedureNamesFromContent(readFileSync31(absPath, "utf-8"));
25864
27153
  }
25865
27154
  function extractProcedureNamesFromContent(content) {
25866
27155
  const procRegex = /\.(?:query|mutation)\s*\(/g;
@@ -25901,7 +27190,7 @@ function handleDocsAudit(args3) {
25901
27190
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
25902
27191
  if (!mapping) continue;
25903
27192
  const helpPagePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
25904
- if (!existsSync28(helpPagePath)) {
27193
+ if (!existsSync29(helpPagePath)) {
25905
27194
  results.push({
25906
27195
  helpPage: mapping.helpPage,
25907
27196
  mappingId,
@@ -25913,12 +27202,12 @@ function handleDocsAudit(args3) {
25913
27202
  });
25914
27203
  continue;
25915
27204
  }
25916
- const content = readFileSync28(helpPagePath, "utf-8");
27205
+ const content = readFileSync31(helpPagePath, "utf-8");
25917
27206
  const sections = extractSections(content);
25918
27207
  const frontmatter = extractFrontmatter(content);
25919
27208
  const staleReasons = [];
25920
27209
  for (const file of triggeringFiles) {
25921
- const fileName = basename5(file);
27210
+ const fileName = basename6(file);
25922
27211
  if (mapping.routers.includes(fileName)) {
25923
27212
  const procedures = extractProcedureNames(file);
25924
27213
  const undocumented = procedures.filter((p19) => !contentMentions(content, p19));
@@ -25949,13 +27238,13 @@ function handleDocsAudit(args3) {
25949
27238
  reason: staleReasons.length > 0 ? staleReasons.join("; ") : "Content appears current",
25950
27239
  sections,
25951
27240
  changedFiles: triggeringFiles,
25952
- suggestedAction: status === "STALE" ? `Review and update ${mapping.helpPage} to reflect changes in: ${triggeringFiles.map((f2) => basename5(f2)).join(", ")}` : "No action needed"
27241
+ suggestedAction: status === "STALE" ? `Review and update ${mapping.helpPage} to reflect changes in: ${triggeringFiles.map((f2) => basename6(f2)).join(", ")}` : "No action needed"
25953
27242
  });
25954
27243
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
25955
27244
  if (parentId === mappingId) {
25956
27245
  const guidePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
25957
- if (existsSync28(guidePath)) {
25958
- const guideContent = readFileSync28(guidePath, "utf-8");
27246
+ if (existsSync29(guidePath)) {
27247
+ const guideContent = readFileSync31(guidePath, "utf-8");
25959
27248
  const guideFrontmatter = extractFrontmatter(guideContent);
25960
27249
  if (!guideFrontmatter?.lastVerified || status === "STALE") {
25961
27250
  results.push({
@@ -25989,13 +27278,13 @@ function handleDocsCoverage(args3) {
25989
27278
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
25990
27279
  for (const mapping of mappings) {
25991
27280
  const helpPagePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
25992
- const exists = existsSync28(helpPagePath);
27281
+ const exists = existsSync29(helpPagePath);
25993
27282
  let hasContent = false;
25994
27283
  let lineCount = 0;
25995
27284
  let lastVerified = null;
25996
27285
  let status = null;
25997
27286
  if (exists) {
25998
- const content = readFileSync28(helpPagePath, "utf-8");
27287
+ const content = readFileSync31(helpPagePath, "utf-8");
25999
27288
  lineCount = content.split("\n").length;
26000
27289
  hasContent = lineCount > 10;
26001
27290
  const frontmatter = extractFrontmatter(content);
@@ -26336,7 +27625,7 @@ var init_observability_tools = __esm({
26336
27625
  });
26337
27626
 
26338
27627
  // src/sentinel-db.ts
26339
- import { existsSync as existsSync29 } from "fs";
27628
+ import { existsSync as existsSync30 } from "fs";
26340
27629
  import { resolve as resolve26 } from "path";
26341
27630
  function parsePortalScope(raw) {
26342
27631
  if (!raw) return [];
@@ -26574,22 +27863,22 @@ function validateFeatures(db, domainFilter) {
26574
27863
  const missingPages = [];
26575
27864
  for (const comp of components) {
26576
27865
  const absPath = resolve26(PROJECT_ROOT, comp.component_file);
26577
- if (!existsSync29(absPath)) {
27866
+ if (!existsSync30(absPath)) {
26578
27867
  missingComponents.push(comp.component_file);
26579
27868
  }
26580
27869
  }
26581
27870
  for (const proc of procedures) {
26582
27871
  const routerPath = resolve26(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
26583
- if (!existsSync29(routerPath)) {
27872
+ if (!existsSync30(routerPath)) {
26584
27873
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
26585
27874
  }
26586
27875
  }
26587
27876
  for (const page of pages) {
26588
27877
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
26589
27878
  const absPath = resolve26(PROJECT_ROOT, routeToPath);
26590
- if (page.page_route.startsWith("/") && !existsSync29(absPath)) {
27879
+ if (page.page_route.startsWith("/") && !existsSync30(absPath)) {
26591
27880
  const altPath = resolve26(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
26592
- if (!existsSync29(altPath)) {
27881
+ if (!existsSync30(altPath)) {
26593
27882
  missingPages.push(page.page_route);
26594
27883
  }
26595
27884
  }
@@ -27113,8 +28402,8 @@ var init_sentinel_tools = __esm({
27113
28402
  });
27114
28403
 
27115
28404
  // 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";
28405
+ import { readFileSync as readFileSync32, existsSync as existsSync31, readdirSync as readdirSync22, statSync as statSync13 } from "fs";
28406
+ import { resolve as resolve27, join as join20, basename as basename7, dirname as dirname14, relative as relative11 } from "path";
27118
28407
  function inferDomain(filePath) {
27119
28408
  const domains = getConfig().domains;
27120
28409
  const path = filePath.toLowerCase();
@@ -27244,9 +28533,9 @@ function scanComponentExports(dataDb2) {
27244
28533
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
27245
28534
  const componentDirs = [];
27246
28535
  const basePath = resolve27(projectRoot, componentsBase);
27247
- if (existsSync30(basePath)) {
28536
+ if (existsSync31(basePath)) {
27248
28537
  try {
27249
- const entries = readdirSync19(basePath, { withFileTypes: true });
28538
+ const entries = readdirSync22(basePath, { withFileTypes: true });
27250
28539
  for (const entry of entries) {
27251
28540
  if (entry.isDirectory()) {
27252
28541
  componentDirs.push(componentsBase + "/" + entry.name);
@@ -27257,11 +28546,11 @@ function scanComponentExports(dataDb2) {
27257
28546
  }
27258
28547
  for (const dir of componentDirs) {
27259
28548
  const absDir = resolve27(projectRoot, dir);
27260
- if (!existsSync30(absDir)) continue;
27261
- const files = walkDir(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
28549
+ if (!existsSync31(absDir)) continue;
28550
+ const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
27262
28551
  for (const file of files) {
27263
- const relPath = relative10(projectRoot, file);
27264
- const source = readFileSync29(file, "utf-8");
28552
+ const relPath = relative11(projectRoot, file);
28553
+ const source = readFileSync32(file, "utf-8");
27265
28554
  const annotations = parseFeatureAnnotations(source);
27266
28555
  if (annotations.length > 0) {
27267
28556
  for (const ann of annotations) {
@@ -27287,7 +28576,7 @@ function scanComponentExports(dataDb2) {
27287
28576
  if (hasHandlers && exportMatch) {
27288
28577
  const componentName = exportMatch[1];
27289
28578
  const domain = inferDomain(relPath);
27290
- const subdomain = basename6(dirname13(relPath));
28579
+ const subdomain = basename7(dirname14(relPath));
27291
28580
  const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
27292
28581
  if (!annotations.some((a2) => a2.featureKey === featureKey)) {
27293
28582
  features.push({
@@ -27307,16 +28596,16 @@ function scanComponentExports(dataDb2) {
27307
28596
  }
27308
28597
  return features;
27309
28598
  }
27310
- function walkDir(dir) {
28599
+ function walkDir2(dir) {
27311
28600
  const results = [];
27312
28601
  try {
27313
- const entries = readdirSync19(dir);
28602
+ const entries = readdirSync22(dir);
27314
28603
  for (const entry of entries) {
27315
- const fullPath = join17(dir, entry);
28604
+ const fullPath = join20(dir, entry);
27316
28605
  try {
27317
- const stat = statSync11(fullPath);
28606
+ const stat = statSync13(fullPath);
27318
28607
  if (stat.isDirectory()) {
27319
- results.push(...walkDir(fullPath));
28608
+ results.push(...walkDir2(fullPath));
27320
28609
  } else {
27321
28610
  results.push(fullPath);
27322
28611
  }
@@ -28300,7 +29589,7 @@ var init_audit_trail = __esm({
28300
29589
  });
28301
29590
 
28302
29591
  // src/validation-engine.ts
28303
- import { existsSync as existsSync31, readFileSync as readFileSync30 } from "fs";
29592
+ import { existsSync as existsSync32, readFileSync as readFileSync33 } from "fs";
28304
29593
  function p10(baseName) {
28305
29594
  return `${getConfig().toolPrefix}_${baseName}`;
28306
29595
  }
@@ -28331,7 +29620,7 @@ function validateFile(filePath, projectRoot) {
28331
29620
  });
28332
29621
  return checks;
28333
29622
  }
28334
- if (!existsSync31(absPath)) {
29623
+ if (!existsSync32(absPath)) {
28335
29624
  checks.push({
28336
29625
  name: "file_exists",
28337
29626
  severity: "error",
@@ -28340,7 +29629,7 @@ function validateFile(filePath, projectRoot) {
28340
29629
  });
28341
29630
  return checks;
28342
29631
  }
28343
- const source = readFileSync30(absPath, "utf-8");
29632
+ const source = readFileSync33(absPath, "utf-8");
28344
29633
  const lines = source.split("\n");
28345
29634
  if (activeChecks.rule_compliance !== false) {
28346
29635
  for (const ruleSet of config.rules) {
@@ -28785,7 +30074,7 @@ var init_adr_generator = __esm({
28785
30074
  });
28786
30075
 
28787
30076
  // src/security-scorer.ts
28788
- import { existsSync as existsSync32, readFileSync as readFileSync31 } from "fs";
30077
+ import { existsSync as existsSync33, readFileSync as readFileSync34 } from "fs";
28789
30078
  function p12(baseName) {
28790
30079
  return `${getConfig().toolPrefix}_${baseName}`;
28791
30080
  }
@@ -28809,12 +30098,12 @@ function scoreFileSecurity(filePath, projectRoot) {
28809
30098
  }]
28810
30099
  };
28811
30100
  }
28812
- if (!existsSync32(absPath)) {
30101
+ if (!existsSync33(absPath)) {
28813
30102
  return { riskScore: 0, findings: [] };
28814
30103
  }
28815
30104
  let source;
28816
30105
  try {
28817
- source = readFileSync31(absPath, "utf-8");
30106
+ source = readFileSync34(absPath, "utf-8");
28818
30107
  } catch {
28819
30108
  return { riskScore: 0, findings: [] };
28820
30109
  }
@@ -29103,7 +30392,7 @@ var init_security_scorer = __esm({
29103
30392
  });
29104
30393
 
29105
30394
  // src/dependency-scorer.ts
29106
- import { existsSync as existsSync33, readFileSync as readFileSync32 } from "fs";
30395
+ import { existsSync as existsSync34, readFileSync as readFileSync35 } from "fs";
29107
30396
  import { resolve as resolve28 } from "path";
29108
30397
  function p13(baseName) {
29109
30398
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -29137,9 +30426,9 @@ function calculateDepRisk(factors) {
29137
30426
  }
29138
30427
  function getInstalledPackages(projectRoot) {
29139
30428
  const pkgPath = resolve28(projectRoot, "package.json");
29140
- if (!existsSync33(pkgPath)) return /* @__PURE__ */ new Map();
30429
+ if (!existsSync34(pkgPath)) return /* @__PURE__ */ new Map();
29141
30430
  try {
29142
- const pkg = JSON.parse(readFileSync32(pkgPath, "utf-8"));
30431
+ const pkg = JSON.parse(readFileSync35(pkgPath, "utf-8"));
29143
30432
  const packages = /* @__PURE__ */ new Map();
29144
30433
  for (const [name2, version] of Object.entries(pkg.dependencies ?? {})) {
29145
30434
  packages.set(name2, version);
@@ -29741,9 +31030,9 @@ var init_regression_detector = __esm({
29741
31030
  });
29742
31031
 
29743
31032
  // 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";
31033
+ import { createHash as createHash8 } from "crypto";
31034
+ import { readFileSync as readFileSync36, readdirSync as readdirSync23, statSync as statSync14, existsSync as existsSync35 } from "fs";
31035
+ import { resolve as resolve29, relative as relative12, basename as basename8, extname as extname2 } from "path";
29747
31036
  function getKnowledgePaths() {
29748
31037
  const resolved = getResolvedPaths();
29749
31038
  const config = getConfig();
@@ -29769,7 +31058,7 @@ function discoverMarkdownFiles(baseDir) {
29769
31058
  const files = [];
29770
31059
  function walk(dir) {
29771
31060
  try {
29772
- const entries = readdirSync20(dir, { withFileTypes: true });
31061
+ const entries = readdirSync23(dir, { withFileTypes: true });
29773
31062
  for (const entry of entries) {
29774
31063
  const fullPath = resolve29(dir, entry.name);
29775
31064
  if (entry.isDirectory()) {
@@ -29777,7 +31066,7 @@ function discoverMarkdownFiles(baseDir) {
29777
31066
  if (entry.name === "archive" && dir.includes("status")) continue;
29778
31067
  if (entry.name === "node_modules") continue;
29779
31068
  walk(fullPath);
29780
- } else if (entry.isFile() && extname(entry.name) === ".md") {
31069
+ } else if (entry.isFile() && extname2(entry.name) === ".md") {
29781
31070
  files.push(fullPath);
29782
31071
  }
29783
31072
  }
@@ -29791,7 +31080,7 @@ function categorizeFile(filePath) {
29791
31080
  const paths = getKnowledgePaths();
29792
31081
  if (filePath.startsWith(paths.plansDir)) return "plan";
29793
31082
  if (filePath.startsWith(paths.docsDir)) {
29794
- const relFromDocs = relative11(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
31083
+ const relFromDocs = relative12(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
29795
31084
  if (relFromDocs.startsWith("plans/")) return "plan";
29796
31085
  if (relFromDocs.includes("architecture")) return "architecture";
29797
31086
  if (relFromDocs.includes("security")) return "security";
@@ -29807,7 +31096,7 @@ function categorizeFile(filePath) {
29807
31096
  }
29808
31097
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
29809
31098
  if (filePath.includes(`${claudeDirName}/projects/`) && filePath.includes("/memory/")) return "memory";
29810
- const rel = relative11(paths.claudeDir, filePath).replace(/\\/g, "/");
31099
+ const rel = relative12(paths.claudeDir, filePath).replace(/\\/g, "/");
29811
31100
  const firstDir = rel.split("/")[0];
29812
31101
  const knownCategories = getConfig().conventions?.knowledgeCategories ?? [
29813
31102
  "patterns",
@@ -29829,7 +31118,7 @@ function categorizeFile(filePath) {
29829
31118
  return "root";
29830
31119
  }
29831
31120
  function hashContent2(content) {
29832
- return createHash7("sha256").update(content).digest("hex");
31121
+ return createHash8("sha256").update(content).digest("hex");
29833
31122
  }
29834
31123
  function parseCRTable(content) {
29835
31124
  const rules = [];
@@ -29948,7 +31237,7 @@ function parseCorrections(content) {
29948
31237
  function extractTitle(content, filePath) {
29949
31238
  const h1Match = content.match(/^#\s+(.+)/m);
29950
31239
  if (h1Match) return h1Match[1].trim();
29951
- return basename7(filePath, ".md");
31240
+ return basename8(filePath, ".md");
29952
31241
  }
29953
31242
  function extractDescription2(content) {
29954
31243
  const fmMatch = content.match(/^---\s*\n[\s\S]*?description:\s*"?([^"\n]+)"?\s*\n[\s\S]*?---/);
@@ -29977,7 +31266,7 @@ function buildCrossReferences(db) {
29977
31266
  }
29978
31267
  }
29979
31268
  if (rule.reference_path) {
29980
- const patternName = basename7(rule.reference_path, ".md");
31269
+ const patternName = basename8(rule.reference_path, ".md");
29981
31270
  insertEdge.run("cr", rule.rule_id, "pattern", patternName, "references");
29982
31271
  edgeCount++;
29983
31272
  }
@@ -30049,11 +31338,11 @@ function indexAllKnowledge(db) {
30049
31338
  files.push(...memFiles);
30050
31339
  } catch {
30051
31340
  }
30052
- if (existsSync34(paths.plansDir)) {
31341
+ if (existsSync35(paths.plansDir)) {
30053
31342
  const planFiles = discoverMarkdownFiles(paths.plansDir);
30054
31343
  files.push(...planFiles);
30055
31344
  }
30056
- if (existsSync34(paths.docsDir)) {
31345
+ if (existsSync35(paths.docsDir)) {
30057
31346
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
30058
31347
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
30059
31348
  files.push(...docsFiles);
@@ -30096,10 +31385,10 @@ function indexAllKnowledge(db) {
30096
31385
  } catch {
30097
31386
  }
30098
31387
  for (const filePath of files) {
30099
- if (!existsSync34(filePath)) continue;
30100
- const content = readFileSync33(filePath, "utf-8");
31388
+ if (!existsSync35(filePath)) continue;
31389
+ const content = readFileSync36(filePath, "utf-8");
30101
31390
  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);
31391
+ 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
31392
  const category = categorizeFile(filePath);
30104
31393
  const title = extractTitle(content, filePath);
30105
31394
  const description = extractDescription2(content);
@@ -30113,10 +31402,10 @@ function indexAllKnowledge(db) {
30113
31402
  stats.chunksCreated++;
30114
31403
  }
30115
31404
  }
30116
- const fileName = basename7(filePath);
31405
+ const fileName = basename8(filePath);
30117
31406
  const fileNameLower = fileName.toLowerCase();
30118
31407
  const relPathLower = relPath.toLowerCase();
30119
- const claudeMdName = basename7(getResolvedPaths().claudeMdPath).toLowerCase();
31408
+ const claudeMdName = basename8(getResolvedPaths().claudeMdPath).toLowerCase();
30120
31409
  if (fileNameLower === claudeMdName || relPathLower.includes(claudeMdName)) {
30121
31410
  const crRules = parseCRTable(content);
30122
31411
  for (const rule of crRules) {
@@ -30150,7 +31439,7 @@ function indexAllKnowledge(db) {
30150
31439
  }
30151
31440
  }
30152
31441
  if (category === "commands" && fileName !== "_shared-preamble.md") {
30153
- const cmdName = basename7(filePath, ".md");
31442
+ const cmdName = basename8(filePath, ".md");
30154
31443
  insertChunk.run(docId, "command", cmdName, content.substring(0, 1e3), 1, null, JSON.stringify({ command_name: cmdName }));
30155
31444
  stats.chunksCreated++;
30156
31445
  }
@@ -30217,17 +31506,17 @@ function isKnowledgeStale(db) {
30217
31506
  files.push(...discoverMarkdownFiles(paths.memoryDir));
30218
31507
  } catch {
30219
31508
  }
30220
- if (existsSync34(paths.plansDir)) {
31509
+ if (existsSync35(paths.plansDir)) {
30221
31510
  files.push(...discoverMarkdownFiles(paths.plansDir));
30222
31511
  }
30223
- if (existsSync34(paths.docsDir)) {
31512
+ if (existsSync35(paths.docsDir)) {
30224
31513
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
30225
31514
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
30226
31515
  files.push(...docsFiles);
30227
31516
  }
30228
31517
  for (const filePath of files) {
30229
31518
  try {
30230
- const stat = statSync12(filePath);
31519
+ const stat = statSync14(filePath);
30231
31520
  if (stat.mtimeMs > lastIndexTime) return true;
30232
31521
  } catch {
30233
31522
  continue;
@@ -30249,8 +31538,8 @@ var init_knowledge_indexer = __esm({
30249
31538
  });
30250
31539
 
30251
31540
  // 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";
31541
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync24 } from "fs";
31542
+ import { resolve as resolve30, basename as basename9 } from "path";
30254
31543
  function p16(baseName) {
30255
31544
  return `${getConfig().toolPrefix}_${baseName}`;
30256
31545
  }
@@ -31000,14 +32289,14 @@ ${crRule ? `- **CR**: ${crRule}
31000
32289
  `;
31001
32290
  let existing = "";
31002
32291
  try {
31003
- existing = readFileSync34(correctionsPath, "utf-8");
32292
+ existing = readFileSync37(correctionsPath, "utf-8");
31004
32293
  } catch {
31005
32294
  }
31006
32295
  const archiveIdx = existing.indexOf("## Archived");
31007
32296
  if (archiveIdx > 0) {
31008
32297
  const before = existing.slice(0, archiveIdx);
31009
32298
  const after = existing.slice(archiveIdx);
31010
- writeFileSync4(correctionsPath, before + entry + after);
32299
+ writeFileSync5(correctionsPath, before + entry + after);
31011
32300
  } else {
31012
32301
  appendFileSync2(correctionsPath, entry);
31013
32302
  }
@@ -31049,7 +32338,7 @@ function handlePlan(db, args3) {
31049
32338
  AND kc.content LIKE ?
31050
32339
  ORDER BY kd.file_path DESC
31051
32340
  LIMIT 20
31052
- `).all(`%${basename8(file)}%`);
32341
+ `).all(`%${basename9(file)}%`);
31053
32342
  lines.push(`## Plans referencing \`${file}\` (${results.length} found)`);
31054
32343
  lines.push("");
31055
32344
  for (const r2 of results) {
@@ -31182,7 +32471,7 @@ function handleGaps(db, args3) {
31182
32471
  } else if (checkType === "routers") {
31183
32472
  try {
31184
32473
  const routersDir = getResolvedPaths().routersDir;
31185
- const routerFiles = readdirSync21(routersDir).filter((f2) => f2.endsWith(".ts") && !f2.startsWith("_"));
32474
+ const routerFiles = readdirSync24(routersDir).filter((f2) => f2.endsWith(".ts") && !f2.startsWith("_"));
31186
32475
  lines.push(`| Router | Knowledge Hits | Status |`);
31187
32476
  lines.push(`|--------|----------------|--------|`);
31188
32477
  for (const file of routerFiles) {
@@ -31338,13 +32627,13 @@ var init_knowledge_tools = __esm({
31338
32627
 
31339
32628
  // src/knowledge-db.ts
31340
32629
  import Database3 from "better-sqlite3";
31341
- import { dirname as dirname14 } from "path";
31342
- import { existsSync as existsSync36, mkdirSync as mkdirSync9 } from "fs";
32630
+ import { dirname as dirname15 } from "path";
32631
+ import { existsSync as existsSync37, mkdirSync as mkdirSync10 } from "fs";
31343
32632
  function getKnowledgeDb() {
31344
32633
  const dbPath = getResolvedPaths().knowledgeDbPath;
31345
- const dir = dirname14(dbPath);
31346
- if (!existsSync36(dir)) {
31347
- mkdirSync9(dir, { recursive: true });
32634
+ const dir = dirname15(dbPath);
32635
+ if (!existsSync37(dir)) {
32636
+ mkdirSync10(dir, { recursive: true });
31348
32637
  }
31349
32638
  const db = new Database3(dbPath);
31350
32639
  db.pragma("journal_mode = WAL");
@@ -32077,8 +33366,8 @@ var init_python_tools = __esm({
32077
33366
  });
32078
33367
 
32079
33368
  // src/tools.ts
32080
- import { readFileSync as readFileSync35, existsSync as existsSync37 } from "fs";
32081
- import { resolve as resolve31, basename as basename9 } from "path";
33369
+ import { readFileSync as readFileSync38, existsSync as existsSync38 } from "fs";
33370
+ import { resolve as resolve31, basename as basename10 } from "path";
32082
33371
  function prefix2() {
32083
33372
  return getConfig().toolPrefix;
32084
33373
  }
@@ -32513,8 +33802,8 @@ function handleContext(file, dataDb2, codegraphDb2) {
32513
33802
  const resolvedPaths = getResolvedPaths();
32514
33803
  const root = getProjectRoot();
32515
33804
  const absFilePath = ensureWithinRoot(resolve31(resolvedPaths.srcDir, "..", file), root);
32516
- if (existsSync37(absFilePath)) {
32517
- const fileContent = readFileSync35(absFilePath, "utf-8").slice(0, 3e3);
33805
+ if (existsSync38(absFilePath)) {
33806
+ const fileContent = readFileSync38(absFilePath, "utf-8").slice(0, 3e3);
32518
33807
  const keywords = [];
32519
33808
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
32520
33809
  if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
@@ -32608,7 +33897,7 @@ function handleContext(file, dataDb2, codegraphDb2) {
32608
33897
  WHERE o.files_involved LIKE ?
32609
33898
  ORDER BY o.importance DESC, o.created_at_epoch DESC
32610
33899
  LIMIT 5
32611
- `).all(`%${basename9(file)}%`);
33900
+ `).all(`%${basename10(file)}%`);
32612
33901
  if (fileObservations.length > 0) {
32613
33902
  lines.push("## Past Observations (This File)");
32614
33903
  for (const obs of fileObservations) {
@@ -32624,7 +33913,7 @@ function handleContext(file, dataDb2, codegraphDb2) {
32624
33913
  WHERE type = 'failed_attempt' AND files_involved LIKE ?
32625
33914
  ORDER BY recurrence_count DESC
32626
33915
  LIMIT 3
32627
- `).all(`%${basename9(file)}%`);
33916
+ `).all(`%${basename10(file)}%`);
32628
33917
  if (failures.length > 0) {
32629
33918
  lines.push("## Failed Attempts (DO NOT RETRY)");
32630
33919
  for (const f2 of failures) {
@@ -32939,10 +34228,10 @@ function handleSchema(args3) {
32939
34228
  lines.push("");
32940
34229
  const projectRoot = getProjectRoot();
32941
34230
  const absPath = ensureWithinRoot(resolve31(projectRoot, file), projectRoot);
32942
- if (!existsSync37(absPath)) {
34231
+ if (!existsSync38(absPath)) {
32943
34232
  return text17(`File not found: ${file}`);
32944
34233
  }
32945
- const source = readFileSync35(absPath, "utf-8");
34234
+ const source = readFileSync38(absPath, "utf-8");
32946
34235
  const config = getConfig();
32947
34236
  const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
32948
34237
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
@@ -33020,8 +34309,8 @@ var init_tools = __esm({
33020
34309
 
33021
34310
  // src/server.ts
33022
34311
  var server_exports = {};
33023
- import { readFileSync as readFileSync36 } from "fs";
33024
- import { resolve as resolve32, dirname as dirname15 } from "path";
34312
+ import { readFileSync as readFileSync39 } from "fs";
34313
+ import { resolve as resolve32, dirname as dirname16 } from "path";
33025
34314
  import { fileURLToPath as fileURLToPath4 } from "url";
33026
34315
  function getDb() {
33027
34316
  if (!codegraphDb) codegraphDb = getCodeGraphDb();
@@ -33113,10 +34402,10 @@ var init_server = __esm({
33113
34402
  init_tools();
33114
34403
  init_memory_db();
33115
34404
  init_license();
33116
- __dirname4 = dirname15(fileURLToPath4(import.meta.url));
34405
+ __dirname4 = dirname16(fileURLToPath4(import.meta.url));
33117
34406
  PKG_VERSION = (() => {
33118
34407
  try {
33119
- const pkg = JSON.parse(readFileSync36(resolve32(__dirname4, "..", "package.json"), "utf-8"));
34408
+ const pkg = JSON.parse(readFileSync39(resolve32(__dirname4, "..", "package.json"), "utf-8"));
33120
34409
  return pkg.version ?? "0.0.0";
33121
34410
  } catch {
33122
34411
  return "0.0.0";
@@ -33419,7 +34708,7 @@ var config_upgrade_exports = {};
33419
34708
  __export(config_upgrade_exports, {
33420
34709
  runConfigUpgrade: () => runConfigUpgrade
33421
34710
  });
33422
- import { existsSync as existsSync38, readFileSync as readFileSync37, writeFileSync as writeFileSync5, copyFileSync, unlinkSync } from "fs";
34711
+ import { existsSync as existsSync39, readFileSync as readFileSync40, writeFileSync as writeFileSync6, copyFileSync, unlinkSync as unlinkSync2 } from "fs";
33423
34712
  import { resolve as resolve33 } from "path";
33424
34713
  import { parse as parseYaml6 } from "yaml";
33425
34714
  async function runConfigUpgrade(opts = {}) {
@@ -33431,14 +34720,14 @@ async function runConfigUpgrade(opts = {}) {
33431
34720
  const err2 = opts.silent ? () => {
33432
34721
  } : (s) => process.stderr.write(s);
33433
34722
  if (opts.rollback) {
33434
- if (!existsSync38(bakPath)) {
34723
+ if (!existsSync39(bakPath)) {
33435
34724
  const message = `No backup found at ${bakPath}`;
33436
34725
  err2(message + "\n");
33437
34726
  return { exitCode: 1, action: "none", message };
33438
34727
  }
33439
34728
  try {
33440
34729
  copyFileSync(bakPath, configPath);
33441
- unlinkSync(bakPath);
34730
+ unlinkSync2(bakPath);
33442
34731
  log("Config restored from backup.\n");
33443
34732
  return { exitCode: 0, action: "rolled-back" };
33444
34733
  } catch (e2) {
@@ -33447,14 +34736,14 @@ async function runConfigUpgrade(opts = {}) {
33447
34736
  return { exitCode: 2, action: "none", message };
33448
34737
  }
33449
34738
  }
33450
- if (!existsSync38(configPath)) {
34739
+ if (!existsSync39(configPath)) {
33451
34740
  const message = "massu.config.yaml not found. Run: npx massu init";
33452
34741
  err2(message + "\n");
33453
34742
  return { exitCode: 1, action: "none", message };
33454
34743
  }
33455
34744
  let existing;
33456
34745
  try {
33457
- const content = readFileSync37(configPath, "utf-8");
34746
+ const content = readFileSync40(configPath, "utf-8");
33458
34747
  const parsed = parseYaml6(content);
33459
34748
  if (!parsed || typeof parsed !== "object") {
33460
34749
  throw new Error("config is not a YAML object");
@@ -33477,8 +34766,8 @@ async function runConfigUpgrade(opts = {}) {
33477
34766
  fingerprint: computeFingerprint(detection)
33478
34767
  };
33479
34768
  try {
33480
- const original = readFileSync37(configPath, "utf-8");
33481
- writeFileSync5(bakPath, original, "utf-8");
34769
+ const original = readFileSync40(configPath, "utf-8");
34770
+ writeFileSync6(bakPath, original, "utf-8");
33482
34771
  } catch (e2) {
33483
34772
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
33484
34773
  err2(message + "\n");
@@ -33511,7 +34800,7 @@ var config_check_drift_exports = {};
33511
34800
  __export(config_check_drift_exports, {
33512
34801
  runConfigCheckDrift: () => runConfigCheckDrift
33513
34802
  });
33514
- import { existsSync as existsSync39, readFileSync as readFileSync38 } from "fs";
34803
+ import { existsSync as existsSync40, readFileSync as readFileSync41 } from "fs";
33515
34804
  import { resolve as resolve34 } from "path";
33516
34805
  import { parse as parseYaml7 } from "yaml";
33517
34806
  function renderChanges(changes) {
@@ -33525,7 +34814,7 @@ async function runConfigCheckDrift(opts = {}) {
33525
34814
  } : (s) => process.stdout.write(s);
33526
34815
  const err2 = opts.silent ? () => {
33527
34816
  } : (s) => process.stderr.write(s);
33528
- if (!existsSync39(configPath)) {
34817
+ if (!existsSync40(configPath)) {
33529
34818
  const message = "massu.config.yaml not found. Run: npx massu init";
33530
34819
  err2(message + "\n");
33531
34820
  return {
@@ -33539,7 +34828,7 @@ async function runConfigCheckDrift(opts = {}) {
33539
34828
  }
33540
34829
  let config;
33541
34830
  try {
33542
- const content = readFileSync38(configPath, "utf-8");
34831
+ const content = readFileSync41(configPath, "utf-8");
33543
34832
  const parsed = parseYaml7(content);
33544
34833
  if (!parsed || typeof parsed !== "object") {
33545
34834
  throw new Error("config is not a YAML object");
@@ -33606,11 +34895,11 @@ var init_config_check_drift = __esm({
33606
34895
  });
33607
34896
 
33608
34897
  // src/cli.ts
33609
- import { readFileSync as readFileSync39 } from "fs";
33610
- import { resolve as resolve35, dirname as dirname16 } from "path";
34898
+ import { readFileSync as readFileSync42 } from "fs";
34899
+ import { resolve as resolve35, dirname as dirname17 } from "path";
33611
34900
  import { fileURLToPath as fileURLToPath5 } from "url";
33612
34901
  var __filename4 = fileURLToPath5(import.meta.url);
33613
- var __dirname5 = dirname16(__filename4);
34902
+ var __dirname5 = dirname17(__filename4);
33614
34903
  var args2 = process.argv.slice(2);
33615
34904
  var subcommand = args2[0];
33616
34905
  async function main() {
@@ -33792,7 +35081,7 @@ Examples:
33792
35081
  }
33793
35082
  function printVersion() {
33794
35083
  try {
33795
- const pkg = JSON.parse(readFileSync39(resolve35(__dirname5, "../package.json"), "utf-8"));
35084
+ const pkg = JSON.parse(readFileSync42(resolve35(__dirname5, "../package.json"), "utf-8"));
33796
35085
  console.log(`massu v${pkg.version}`);
33797
35086
  } catch {
33798
35087
  console.log("massu v0.1.0");