@duytransipher/gitnexus 1.4.6-sipher.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +261 -0
  3. package/dist/cli/ai-context.d.ts +23 -0
  4. package/dist/cli/ai-context.js +265 -0
  5. package/dist/cli/analyze.d.ts +12 -0
  6. package/dist/cli/analyze.js +345 -0
  7. package/dist/cli/augment.d.ts +13 -0
  8. package/dist/cli/augment.js +33 -0
  9. package/dist/cli/clean.d.ts +10 -0
  10. package/dist/cli/clean.js +60 -0
  11. package/dist/cli/eval-server.d.ts +37 -0
  12. package/dist/cli/eval-server.js +389 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +137 -0
  15. package/dist/cli/lazy-action.d.ts +6 -0
  16. package/dist/cli/lazy-action.js +18 -0
  17. package/dist/cli/list.d.ts +6 -0
  18. package/dist/cli/list.js +30 -0
  19. package/dist/cli/mcp.d.ts +8 -0
  20. package/dist/cli/mcp.js +36 -0
  21. package/dist/cli/serve.d.ts +4 -0
  22. package/dist/cli/serve.js +6 -0
  23. package/dist/cli/setup.d.ts +8 -0
  24. package/dist/cli/setup.js +367 -0
  25. package/dist/cli/sipher-patched.d.ts +2 -0
  26. package/dist/cli/sipher-patched.js +77 -0
  27. package/dist/cli/skill-gen.d.ts +26 -0
  28. package/dist/cli/skill-gen.js +549 -0
  29. package/dist/cli/status.d.ts +6 -0
  30. package/dist/cli/status.js +36 -0
  31. package/dist/cli/tool.d.ts +60 -0
  32. package/dist/cli/tool.js +180 -0
  33. package/dist/cli/wiki.d.ts +15 -0
  34. package/dist/cli/wiki.js +365 -0
  35. package/dist/config/ignore-service.d.ts +26 -0
  36. package/dist/config/ignore-service.js +284 -0
  37. package/dist/config/supported-languages.d.ts +15 -0
  38. package/dist/config/supported-languages.js +16 -0
  39. package/dist/core/augmentation/engine.d.ts +26 -0
  40. package/dist/core/augmentation/engine.js +240 -0
  41. package/dist/core/embeddings/embedder.d.ts +60 -0
  42. package/dist/core/embeddings/embedder.js +251 -0
  43. package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
  44. package/dist/core/embeddings/embedding-pipeline.js +356 -0
  45. package/dist/core/embeddings/index.d.ts +9 -0
  46. package/dist/core/embeddings/index.js +9 -0
  47. package/dist/core/embeddings/text-generator.d.ts +24 -0
  48. package/dist/core/embeddings/text-generator.js +182 -0
  49. package/dist/core/embeddings/types.d.ts +87 -0
  50. package/dist/core/embeddings/types.js +32 -0
  51. package/dist/core/graph/graph.d.ts +2 -0
  52. package/dist/core/graph/graph.js +66 -0
  53. package/dist/core/graph/types.d.ts +66 -0
  54. package/dist/core/graph/types.js +1 -0
  55. package/dist/core/ingestion/ast-cache.d.ts +11 -0
  56. package/dist/core/ingestion/ast-cache.js +35 -0
  57. package/dist/core/ingestion/call-processor.d.ts +23 -0
  58. package/dist/core/ingestion/call-processor.js +793 -0
  59. package/dist/core/ingestion/call-routing.d.ts +68 -0
  60. package/dist/core/ingestion/call-routing.js +129 -0
  61. package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
  62. package/dist/core/ingestion/cluster-enricher.js +170 -0
  63. package/dist/core/ingestion/community-processor.d.ts +39 -0
  64. package/dist/core/ingestion/community-processor.js +312 -0
  65. package/dist/core/ingestion/constants.d.ts +16 -0
  66. package/dist/core/ingestion/constants.js +16 -0
  67. package/dist/core/ingestion/entry-point-scoring.d.ts +40 -0
  68. package/dist/core/ingestion/entry-point-scoring.js +353 -0
  69. package/dist/core/ingestion/export-detection.d.ts +18 -0
  70. package/dist/core/ingestion/export-detection.js +231 -0
  71. package/dist/core/ingestion/filesystem-walker.d.ts +28 -0
  72. package/dist/core/ingestion/filesystem-walker.js +81 -0
  73. package/dist/core/ingestion/framework-detection.d.ts +54 -0
  74. package/dist/core/ingestion/framework-detection.js +411 -0
  75. package/dist/core/ingestion/heritage-processor.d.ts +28 -0
  76. package/dist/core/ingestion/heritage-processor.js +251 -0
  77. package/dist/core/ingestion/import-processor.d.ts +34 -0
  78. package/dist/core/ingestion/import-processor.js +398 -0
  79. package/dist/core/ingestion/language-config.d.ts +46 -0
  80. package/dist/core/ingestion/language-config.js +167 -0
  81. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  82. package/dist/core/ingestion/mro-processor.js +369 -0
  83. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  84. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  85. package/dist/core/ingestion/parsing-processor.d.ts +19 -0
  86. package/dist/core/ingestion/parsing-processor.js +315 -0
  87. package/dist/core/ingestion/pipeline.d.ts +6 -0
  88. package/dist/core/ingestion/pipeline.js +401 -0
  89. package/dist/core/ingestion/process-processor.d.ts +51 -0
  90. package/dist/core/ingestion/process-processor.js +315 -0
  91. package/dist/core/ingestion/resolution-context.d.ts +53 -0
  92. package/dist/core/ingestion/resolution-context.js +132 -0
  93. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  94. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  95. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  96. package/dist/core/ingestion/resolvers/go.js +42 -0
  97. package/dist/core/ingestion/resolvers/index.d.ts +18 -0
  98. package/dist/core/ingestion/resolvers/index.js +13 -0
  99. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  100. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  101. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  102. package/dist/core/ingestion/resolvers/php.js +35 -0
  103. package/dist/core/ingestion/resolvers/python.d.ts +19 -0
  104. package/dist/core/ingestion/resolvers/python.js +52 -0
  105. package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
  106. package/dist/core/ingestion/resolvers/ruby.js +15 -0
  107. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  108. package/dist/core/ingestion/resolvers/rust.js +73 -0
  109. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  110. package/dist/core/ingestion/resolvers/standard.js +123 -0
  111. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  112. package/dist/core/ingestion/resolvers/utils.js +122 -0
  113. package/dist/core/ingestion/structure-processor.d.ts +2 -0
  114. package/dist/core/ingestion/structure-processor.js +36 -0
  115. package/dist/core/ingestion/symbol-table.d.ts +63 -0
  116. package/dist/core/ingestion/symbol-table.js +85 -0
  117. package/dist/core/ingestion/tree-sitter-queries.d.ts +15 -0
  118. package/dist/core/ingestion/tree-sitter-queries.js +888 -0
  119. package/dist/core/ingestion/type-env.d.ts +49 -0
  120. package/dist/core/ingestion/type-env.js +613 -0
  121. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  122. package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
  123. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  124. package/dist/core/ingestion/type-extractors/csharp.js +383 -0
  125. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  126. package/dist/core/ingestion/type-extractors/go.js +467 -0
  127. package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
  128. package/dist/core/ingestion/type-extractors/index.js +31 -0
  129. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  130. package/dist/core/ingestion/type-extractors/jvm.js +681 -0
  131. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  132. package/dist/core/ingestion/type-extractors/php.js +549 -0
  133. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  134. package/dist/core/ingestion/type-extractors/python.js +455 -0
  135. package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
  136. package/dist/core/ingestion/type-extractors/ruby.js +389 -0
  137. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  138. package/dist/core/ingestion/type-extractors/rust.js +456 -0
  139. package/dist/core/ingestion/type-extractors/shared.d.ts +145 -0
  140. package/dist/core/ingestion/type-extractors/shared.js +810 -0
  141. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  142. package/dist/core/ingestion/type-extractors/swift.js +137 -0
  143. package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
  144. package/dist/core/ingestion/type-extractors/types.js +1 -0
  145. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  146. package/dist/core/ingestion/type-extractors/typescript.js +494 -0
  147. package/dist/core/ingestion/utils.d.ts +138 -0
  148. package/dist/core/ingestion/utils.js +1290 -0
  149. package/dist/core/ingestion/workers/parse-worker.d.ts +122 -0
  150. package/dist/core/ingestion/workers/parse-worker.js +1126 -0
  151. package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
  152. package/dist/core/ingestion/workers/worker-pool.js +128 -0
  153. package/dist/core/lbug/csv-generator.d.ts +33 -0
  154. package/dist/core/lbug/csv-generator.js +366 -0
  155. package/dist/core/lbug/lbug-adapter.d.ts +103 -0
  156. package/dist/core/lbug/lbug-adapter.js +769 -0
  157. package/dist/core/lbug/schema.d.ts +53 -0
  158. package/dist/core/lbug/schema.js +430 -0
  159. package/dist/core/search/bm25-index.d.ts +23 -0
  160. package/dist/core/search/bm25-index.js +96 -0
  161. package/dist/core/search/hybrid-search.d.ts +49 -0
  162. package/dist/core/search/hybrid-search.js +118 -0
  163. package/dist/core/tree-sitter/parser-loader.d.ts +5 -0
  164. package/dist/core/tree-sitter/parser-loader.js +63 -0
  165. package/dist/core/wiki/generator.d.ts +120 -0
  166. package/dist/core/wiki/generator.js +939 -0
  167. package/dist/core/wiki/graph-queries.d.ts +80 -0
  168. package/dist/core/wiki/graph-queries.js +238 -0
  169. package/dist/core/wiki/html-viewer.d.ts +10 -0
  170. package/dist/core/wiki/html-viewer.js +297 -0
  171. package/dist/core/wiki/llm-client.d.ts +43 -0
  172. package/dist/core/wiki/llm-client.js +186 -0
  173. package/dist/core/wiki/prompts.d.ts +53 -0
  174. package/dist/core/wiki/prompts.js +174 -0
  175. package/dist/lib/utils.d.ts +1 -0
  176. package/dist/lib/utils.js +3 -0
  177. package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
  178. package/dist/mcp/compatible-stdio-transport.js +200 -0
  179. package/dist/mcp/core/embedder.d.ts +27 -0
  180. package/dist/mcp/core/embedder.js +108 -0
  181. package/dist/mcp/core/lbug-adapter.d.ts +57 -0
  182. package/dist/mcp/core/lbug-adapter.js +455 -0
  183. package/dist/mcp/local/local-backend.d.ts +181 -0
  184. package/dist/mcp/local/local-backend.js +1722 -0
  185. package/dist/mcp/resources.d.ts +31 -0
  186. package/dist/mcp/resources.js +411 -0
  187. package/dist/mcp/server.d.ts +23 -0
  188. package/dist/mcp/server.js +296 -0
  189. package/dist/mcp/staleness.d.ts +15 -0
  190. package/dist/mcp/staleness.js +29 -0
  191. package/dist/mcp/tools.d.ts +24 -0
  192. package/dist/mcp/tools.js +292 -0
  193. package/dist/server/api.d.ts +10 -0
  194. package/dist/server/api.js +344 -0
  195. package/dist/server/mcp-http.d.ts +13 -0
  196. package/dist/server/mcp-http.js +100 -0
  197. package/dist/storage/git.d.ts +6 -0
  198. package/dist/storage/git.js +35 -0
  199. package/dist/storage/repo-manager.d.ts +138 -0
  200. package/dist/storage/repo-manager.js +299 -0
  201. package/dist/types/pipeline.d.ts +32 -0
  202. package/dist/types/pipeline.js +18 -0
  203. package/dist/unreal/bridge.d.ts +4 -0
  204. package/dist/unreal/bridge.js +113 -0
  205. package/dist/unreal/config.d.ts +6 -0
  206. package/dist/unreal/config.js +55 -0
  207. package/dist/unreal/types.d.ts +105 -0
  208. package/dist/unreal/types.js +1 -0
  209. package/hooks/claude/gitnexus-hook.cjs +238 -0
  210. package/hooks/claude/pre-tool-use.sh +79 -0
  211. package/hooks/claude/session-start.sh +42 -0
  212. package/package.json +100 -0
  213. package/scripts/ensure-cli-executable.cjs +21 -0
  214. package/scripts/patch-tree-sitter-swift.cjs +74 -0
  215. package/scripts/setup-unreal-gitnexus.ps1 +191 -0
  216. package/skills/gitnexus-cli.md +82 -0
  217. package/skills/gitnexus-debugging.md +89 -0
  218. package/skills/gitnexus-exploring.md +78 -0
  219. package/skills/gitnexus-guide.md +64 -0
  220. package/skills/gitnexus-impact-analysis.md +97 -0
  221. package/skills/gitnexus-pr-review.md +163 -0
  222. package/skills/gitnexus-refactoring.md +121 -0
  223. package/vendor/leiden/index.cjs +355 -0
  224. package/vendor/leiden/utils.cjs +392 -0
@@ -0,0 +1,401 @@
1
+ import { createKnowledgeGraph } from '../graph/graph.js';
2
+ import { processStructure } from './structure-processor.js';
3
+ import { processParsing } from './parsing-processor.js';
4
+ import { processImports, processImportsFromExtracted, buildImportResolutionContext } from './import-processor.js';
5
+ import { processCalls, processCallsFromExtracted, processAssignmentsFromExtracted, processRoutesFromExtracted } from './call-processor.js';
6
+ import { processHeritage, processHeritageFromExtracted } from './heritage-processor.js';
7
+ import { computeMRO } from './mro-processor.js';
8
+ import { processCommunities } from './community-processor.js';
9
+ import { processProcesses } from './process-processor.js';
10
+ import { createResolutionContext } from './resolution-context.js';
11
+ import { createASTCache } from './ast-cache.js';
12
+ import { walkRepositoryPaths, readFileContents } from './filesystem-walker.js';
13
+ import { getLanguageFromFilename } from './utils.js';
14
+ import { isLanguageAvailable } from '../tree-sitter/parser-loader.js';
15
+ import { createWorkerPool } from './workers/worker-pool.js';
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { fileURLToPath, pathToFileURL } from 'node:url';
19
+ const isDev = process.env.NODE_ENV === 'development';
20
+ /** Max bytes of source content to load per parse chunk. Each chunk's source +
21
+ * parsed ASTs + extracted records + worker serialization overhead all live in
22
+ * memory simultaneously, so this must be conservative. 20MB source ≈ 200-400MB
23
+ * peak working memory per chunk after parse expansion. */
24
+ const CHUNK_BYTE_BUDGET = 20 * 1024 * 1024; // 20MB
25
+ /** Max AST trees to keep in LRU cache */
26
+ const AST_CACHE_CAP = 50;
27
+ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
28
+ const graph = createKnowledgeGraph();
29
+ const ctx = createResolutionContext();
30
+ const symbolTable = ctx.symbols;
31
+ let astCache = createASTCache(AST_CACHE_CAP);
32
+ const cleanup = () => {
33
+ astCache.clear();
34
+ ctx.clear();
35
+ };
36
+ try {
37
+ // ── Phase 1: Scan paths only (no content read) ─────────────────────
38
+ onProgress({
39
+ phase: 'extracting',
40
+ percent: 0,
41
+ message: 'Scanning repository...',
42
+ });
43
+ const scannedFiles = await walkRepositoryPaths(repoPath, (current, total, filePath) => {
44
+ const scanProgress = Math.round((current / total) * 15);
45
+ onProgress({
46
+ phase: 'extracting',
47
+ percent: scanProgress,
48
+ message: 'Scanning repository...',
49
+ detail: filePath,
50
+ stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
51
+ });
52
+ });
53
+ const totalFiles = scannedFiles.length;
54
+ onProgress({
55
+ phase: 'extracting',
56
+ percent: 15,
57
+ message: 'Repository scanned successfully',
58
+ stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
59
+ });
60
+ // ── Phase 2: Structure (paths only — no content needed) ────────────
61
+ onProgress({
62
+ phase: 'structure',
63
+ percent: 15,
64
+ message: 'Analyzing project structure...',
65
+ stats: { filesProcessed: 0, totalFiles, nodesCreated: graph.nodeCount },
66
+ });
67
+ const allPaths = scannedFiles.map(f => f.path);
68
+ processStructure(graph, allPaths);
69
+ onProgress({
70
+ phase: 'structure',
71
+ percent: 20,
72
+ message: 'Project structure analyzed',
73
+ stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
74
+ });
75
+ // ── Phase 3+4: Chunked read + parse ────────────────────────────────
76
+ // Group parseable files into byte-budget chunks so only ~20MB of source
77
+ // is in memory at a time. Each chunk is: read → parse → extract → free.
78
+ const parseableScanned = scannedFiles.filter(f => {
79
+ const lang = getLanguageFromFilename(f.path);
80
+ return lang && isLanguageAvailable(lang);
81
+ });
82
+ // Warn about files skipped due to unavailable parsers
83
+ const skippedByLang = new Map();
84
+ for (const f of scannedFiles) {
85
+ const lang = getLanguageFromFilename(f.path);
86
+ if (lang && !isLanguageAvailable(lang)) {
87
+ skippedByLang.set(lang, (skippedByLang.get(lang) || 0) + 1);
88
+ }
89
+ }
90
+ for (const [lang, count] of skippedByLang) {
91
+ console.warn(`Skipping ${count} ${lang} file(s) — ${lang} parser not available (native binding may not have built). Try: npm rebuild tree-sitter-${lang}`);
92
+ }
93
+ const totalParseable = parseableScanned.length;
94
+ if (totalParseable === 0) {
95
+ onProgress({
96
+ phase: 'parsing',
97
+ percent: 82,
98
+ message: 'No parseable files found — skipping parsing phase',
99
+ stats: { filesProcessed: 0, totalFiles: 0, nodesCreated: graph.nodeCount },
100
+ });
101
+ }
102
+ // Build byte-budget chunks
103
+ const chunks = [];
104
+ let currentChunk = [];
105
+ let currentBytes = 0;
106
+ for (const file of parseableScanned) {
107
+ if (currentChunk.length > 0 && currentBytes + file.size > CHUNK_BYTE_BUDGET) {
108
+ chunks.push(currentChunk);
109
+ currentChunk = [];
110
+ currentBytes = 0;
111
+ }
112
+ currentChunk.push(file.path);
113
+ currentBytes += file.size;
114
+ }
115
+ if (currentChunk.length > 0)
116
+ chunks.push(currentChunk);
117
+ const numChunks = chunks.length;
118
+ if (isDev) {
119
+ const totalMB = parseableScanned.reduce((s, f) => s + f.size, 0) / (1024 * 1024);
120
+ console.log(`📂 Scan: ${totalFiles} paths, ${totalParseable} parseable (${totalMB.toFixed(0)}MB), ${numChunks} chunks @ ${CHUNK_BYTE_BUDGET / (1024 * 1024)}MB budget`);
121
+ }
122
+ onProgress({
123
+ phase: 'parsing',
124
+ percent: 20,
125
+ message: `Parsing ${totalParseable} files in ${numChunks} chunk${numChunks !== 1 ? 's' : ''}...`,
126
+ stats: { filesProcessed: 0, totalFiles: totalParseable, nodesCreated: graph.nodeCount },
127
+ });
128
+ // Don't spawn workers for tiny repos — overhead exceeds benefit
129
+ const MIN_FILES_FOR_WORKERS = 15;
130
+ const MIN_BYTES_FOR_WORKERS = 512 * 1024;
131
+ const totalBytes = parseableScanned.reduce((s, f) => s + f.size, 0);
132
+ // Create worker pool once, reuse across chunks
133
+ let workerPool;
134
+ if (totalParseable >= MIN_FILES_FOR_WORKERS || totalBytes >= MIN_BYTES_FOR_WORKERS) {
135
+ try {
136
+ let workerUrl = new URL('./workers/parse-worker.js', import.meta.url);
137
+ // When running under vitest, import.meta.url points to src/ where no .js exists.
138
+ // Fall back to the compiled dist/ worker so the pool can spawn real worker threads.
139
+ const thisDir = fileURLToPath(new URL('.', import.meta.url));
140
+ if (!fs.existsSync(fileURLToPath(workerUrl))) {
141
+ const distWorker = path.resolve(thisDir, '..', '..', '..', 'dist', 'core', 'ingestion', 'workers', 'parse-worker.js');
142
+ if (fs.existsSync(distWorker)) {
143
+ workerUrl = pathToFileURL(distWorker);
144
+ }
145
+ }
146
+ workerPool = createWorkerPool(workerUrl);
147
+ }
148
+ catch (err) {
149
+ if (isDev)
150
+ console.warn('Worker pool creation failed, using sequential fallback:', err.message);
151
+ }
152
+ }
153
+ let filesParsedSoFar = 0;
154
+ // AST cache sized for one chunk (sequential fallback uses it for import/call/heritage)
155
+ const maxChunkFiles = chunks.reduce((max, c) => Math.max(max, c.length), 0);
156
+ astCache = createASTCache(maxChunkFiles);
157
+ // Build import resolution context once — suffix index, file lists, resolve cache.
158
+ // Reused across all chunks to avoid rebuilding O(files × path_depth) structures.
159
+ const importCtx = buildImportResolutionContext(allPaths);
160
+ const allPathObjects = allPaths.map(p => ({ path: p }));
161
+ // Single-pass: parse + resolve imports/calls/heritage per chunk.
162
+ // Calls/heritage use the symbol table built so far (symbols from earlier chunks
163
+ // are already registered). This trades ~5% cross-chunk resolution accuracy for
164
+ // 200-400MB less memory — critical for Linux-kernel-scale repos.
165
+ const sequentialChunkPaths = [];
166
+ try {
167
+ for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
168
+ const chunkPaths = chunks[chunkIdx];
169
+ // Read content for this chunk only
170
+ const chunkContents = await readFileContents(repoPath, chunkPaths);
171
+ const chunkFiles = chunkPaths
172
+ .filter(p => chunkContents.has(p))
173
+ .map(p => ({ path: p, content: chunkContents.get(p) }));
174
+ // Parse this chunk (workers or sequential fallback)
175
+ const chunkWorkerData = await processParsing(graph, chunkFiles, symbolTable, astCache, (current, _total, filePath) => {
176
+ const globalCurrent = filesParsedSoFar + current;
177
+ const parsingProgress = 20 + ((globalCurrent / totalParseable) * 62);
178
+ onProgress({
179
+ phase: 'parsing',
180
+ percent: Math.round(parsingProgress),
181
+ message: `Parsing chunk ${chunkIdx + 1}/${numChunks}...`,
182
+ detail: filePath,
183
+ stats: { filesProcessed: globalCurrent, totalFiles: totalParseable, nodesCreated: graph.nodeCount },
184
+ });
185
+ }, workerPool);
186
+ const chunkBasePercent = 20 + ((filesParsedSoFar / totalParseable) * 62);
187
+ if (chunkWorkerData) {
188
+ // Imports
189
+ await processImportsFromExtracted(graph, allPathObjects, chunkWorkerData.imports, ctx, (current, total) => {
190
+ onProgress({
191
+ phase: 'parsing',
192
+ percent: Math.round(chunkBasePercent),
193
+ message: `Resolving imports (chunk ${chunkIdx + 1}/${numChunks})...`,
194
+ detail: `${current}/${total} files`,
195
+ stats: { filesProcessed: filesParsedSoFar, totalFiles: totalParseable, nodesCreated: graph.nodeCount },
196
+ });
197
+ }, repoPath, importCtx);
198
+ // Calls + Heritage + Routes — resolve in parallel (no shared mutable state between them)
199
+ // This is safe because each writes disjoint relationship types into idempotent id-keyed Maps,
200
+ // and the single-threaded event loop prevents races between synchronous addRelationship calls.
201
+ await Promise.all([
202
+ processCallsFromExtracted(graph, chunkWorkerData.calls, ctx, (current, total) => {
203
+ onProgress({
204
+ phase: 'parsing',
205
+ percent: Math.round(chunkBasePercent),
206
+ message: `Resolving calls (chunk ${chunkIdx + 1}/${numChunks})...`,
207
+ detail: `${current}/${total} files`,
208
+ stats: { filesProcessed: filesParsedSoFar, totalFiles: totalParseable, nodesCreated: graph.nodeCount },
209
+ });
210
+ }, chunkWorkerData.constructorBindings),
211
+ processHeritageFromExtracted(graph, chunkWorkerData.heritage, ctx, (current, total) => {
212
+ onProgress({
213
+ phase: 'parsing',
214
+ percent: Math.round(chunkBasePercent),
215
+ message: `Resolving heritage (chunk ${chunkIdx + 1}/${numChunks})...`,
216
+ detail: `${current}/${total} records`,
217
+ stats: { filesProcessed: filesParsedSoFar, totalFiles: totalParseable, nodesCreated: graph.nodeCount },
218
+ });
219
+ }),
220
+ processRoutesFromExtracted(graph, chunkWorkerData.routes ?? [], ctx, (current, total) => {
221
+ onProgress({
222
+ phase: 'parsing',
223
+ percent: Math.round(chunkBasePercent),
224
+ message: `Resolving routes (chunk ${chunkIdx + 1}/${numChunks})...`,
225
+ detail: `${current}/${total} routes`,
226
+ stats: { filesProcessed: filesParsedSoFar, totalFiles: totalParseable, nodesCreated: graph.nodeCount },
227
+ });
228
+ }),
229
+ ]);
230
+ // Process field write assignments (synchronous, runs after calls resolve)
231
+ if (chunkWorkerData.assignments?.length) {
232
+ processAssignmentsFromExtracted(graph, chunkWorkerData.assignments, ctx, chunkWorkerData.constructorBindings);
233
+ }
234
+ }
235
+ else {
236
+ await processImports(graph, chunkFiles, astCache, ctx, undefined, repoPath, allPaths);
237
+ sequentialChunkPaths.push(chunkPaths);
238
+ }
239
+ filesParsedSoFar += chunkFiles.length;
240
+ // Clear AST cache between chunks to free memory
241
+ astCache.clear();
242
+ // chunkContents + chunkFiles + chunkWorkerData go out of scope → GC reclaims
243
+ }
244
+ }
245
+ finally {
246
+ await workerPool?.terminate();
247
+ }
248
+ // Sequential fallback chunks: re-read source for call/heritage resolution
249
+ for (const chunkPaths of sequentialChunkPaths) {
250
+ const chunkContents = await readFileContents(repoPath, chunkPaths);
251
+ const chunkFiles = chunkPaths
252
+ .filter(p => chunkContents.has(p))
253
+ .map(p => ({ path: p, content: chunkContents.get(p) }));
254
+ astCache = createASTCache(chunkFiles.length);
255
+ const rubyHeritage = await processCalls(graph, chunkFiles, astCache, ctx);
256
+ await processHeritage(graph, chunkFiles, astCache, ctx);
257
+ if (rubyHeritage.length > 0) {
258
+ await processHeritageFromExtracted(graph, rubyHeritage, ctx);
259
+ }
260
+ astCache.clear();
261
+ }
262
+ // Log resolution cache stats
263
+ if (isDev) {
264
+ const rcStats = ctx.getStats();
265
+ const total = rcStats.cacheHits + rcStats.cacheMisses;
266
+ const hitRate = total > 0 ? ((rcStats.cacheHits / total) * 100).toFixed(1) : '0';
267
+ console.log(`🔍 Resolution cache: ${rcStats.cacheHits} hits, ${rcStats.cacheMisses} misses (${hitRate}% hit rate)`);
268
+ }
269
+ // Free import resolution context — suffix index + resolve cache no longer needed
270
+ // (allPathObjects and importCtx hold ~94MB+ for large repos)
271
+ allPathObjects.length = 0;
272
+ importCtx.resolveCache.clear();
273
+ importCtx.suffixIndex = null;
274
+ importCtx.normalizedFileList = null;
275
+ let communityResult;
276
+ let processResult;
277
+ if (!options?.skipGraphPhases) {
278
+ // ── Phase 4.5: Method Resolution Order ──────────────────────────────
279
+ onProgress({
280
+ phase: 'parsing',
281
+ percent: 81,
282
+ message: 'Computing method resolution order...',
283
+ stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
284
+ });
285
+ const mroResult = computeMRO(graph);
286
+ if (isDev && mroResult.entries.length > 0) {
287
+ console.log(`🔀 MRO: ${mroResult.entries.length} classes analyzed, ${mroResult.ambiguityCount} ambiguities found, ${mroResult.overrideEdges} OVERRIDES edges`);
288
+ }
289
+ // ── Phase 5: Communities ───────────────────────────────────────────
290
+ onProgress({
291
+ phase: 'communities',
292
+ percent: 82,
293
+ message: 'Detecting code communities...',
294
+ stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
295
+ });
296
+ communityResult = await processCommunities(graph, (message, progress) => {
297
+ const communityProgress = 82 + (progress * 0.10);
298
+ onProgress({
299
+ phase: 'communities',
300
+ percent: Math.round(communityProgress),
301
+ message,
302
+ stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
303
+ });
304
+ });
305
+ if (isDev) {
306
+ console.log(`🏘️ Community detection: ${communityResult.stats.totalCommunities} communities found (modularity: ${communityResult.stats.modularity.toFixed(3)})`);
307
+ }
308
+ communityResult.communities.forEach(comm => {
309
+ graph.addNode({
310
+ id: comm.id,
311
+ label: 'Community',
312
+ properties: {
313
+ name: comm.label,
314
+ filePath: '',
315
+ heuristicLabel: comm.heuristicLabel,
316
+ cohesion: comm.cohesion,
317
+ symbolCount: comm.symbolCount,
318
+ }
319
+ });
320
+ });
321
+ communityResult.memberships.forEach(membership => {
322
+ graph.addRelationship({
323
+ id: `${membership.nodeId}_member_of_${membership.communityId}`,
324
+ type: 'MEMBER_OF',
325
+ sourceId: membership.nodeId,
326
+ targetId: membership.communityId,
327
+ confidence: 1.0,
328
+ reason: 'leiden-algorithm',
329
+ });
330
+ });
331
+ // ── Phase 6: Processes ─────────────────────────────────────────────
332
+ onProgress({
333
+ phase: 'processes',
334
+ percent: 94,
335
+ message: 'Detecting execution flows...',
336
+ stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
337
+ });
338
+ let symbolCount = 0;
339
+ graph.forEachNode(n => { if (n.label !== 'File')
340
+ symbolCount++; });
341
+ const dynamicMaxProcesses = Math.max(20, Math.min(300, Math.round(symbolCount / 10)));
342
+ processResult = await processProcesses(graph, communityResult.memberships, (message, progress) => {
343
+ const processProgress = 94 + (progress * 0.05);
344
+ onProgress({
345
+ phase: 'processes',
346
+ percent: Math.round(processProgress),
347
+ message,
348
+ stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
349
+ });
350
+ }, { maxProcesses: dynamicMaxProcesses, minSteps: 3 });
351
+ if (isDev) {
352
+ console.log(`🔄 Process detection: ${processResult.stats.totalProcesses} processes found (${processResult.stats.crossCommunityCount} cross-community)`);
353
+ }
354
+ processResult.processes.forEach(proc => {
355
+ graph.addNode({
356
+ id: proc.id,
357
+ label: 'Process',
358
+ properties: {
359
+ name: proc.label,
360
+ filePath: '',
361
+ heuristicLabel: proc.heuristicLabel,
362
+ processType: proc.processType,
363
+ stepCount: proc.stepCount,
364
+ communities: proc.communities,
365
+ entryPointId: proc.entryPointId,
366
+ terminalId: proc.terminalId,
367
+ }
368
+ });
369
+ });
370
+ processResult.steps.forEach(step => {
371
+ graph.addRelationship({
372
+ id: `${step.nodeId}_step_${step.step}_${step.processId}`,
373
+ type: 'STEP_IN_PROCESS',
374
+ sourceId: step.nodeId,
375
+ targetId: step.processId,
376
+ confidence: 1.0,
377
+ reason: 'trace-detection',
378
+ step: step.step,
379
+ });
380
+ });
381
+ }
382
+ onProgress({
383
+ phase: 'complete',
384
+ percent: 100,
385
+ message: communityResult && processResult
386
+ ? `Graph complete! ${communityResult.stats.totalCommunities} communities, ${processResult.stats.totalProcesses} processes detected.`
387
+ : 'Graph complete! (graph phases skipped)',
388
+ stats: {
389
+ filesProcessed: totalFiles,
390
+ totalFiles,
391
+ nodesCreated: graph.nodeCount
392
+ },
393
+ });
394
+ astCache.clear();
395
+ return { graph, repoPath, totalFileCount: totalFiles, communityResult, processResult };
396
+ }
397
+ catch (error) {
398
+ cleanup();
399
+ throw error;
400
+ }
401
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Process Detection Processor
3
+ *
4
+ * Detects execution flows (Processes) in the code graph by:
5
+ * 1. Finding entry points (functions with no internal callers)
6
+ * 2. Tracing forward via CALLS edges (BFS)
7
+ * 3. Grouping and deduplicating similar paths
8
+ * 4. Labeling with heuristic names
9
+ *
10
+ * Processes help agents understand how features work through the codebase.
11
+ */
12
+ import { KnowledgeGraph } from '../graph/types.js';
13
+ import { CommunityMembership } from './community-processor.js';
14
+ export interface ProcessDetectionConfig {
15
+ maxTraceDepth: number;
16
+ maxBranching: number;
17
+ maxProcesses: number;
18
+ minSteps: number;
19
+ }
20
+ export interface ProcessNode {
21
+ id: string;
22
+ label: string;
23
+ heuristicLabel: string;
24
+ processType: 'intra_community' | 'cross_community';
25
+ stepCount: number;
26
+ communities: string[];
27
+ entryPointId: string;
28
+ terminalId: string;
29
+ trace: string[];
30
+ }
31
+ export interface ProcessStep {
32
+ nodeId: string;
33
+ processId: string;
34
+ step: number;
35
+ }
36
+ export interface ProcessDetectionResult {
37
+ processes: ProcessNode[];
38
+ steps: ProcessStep[];
39
+ stats: {
40
+ totalProcesses: number;
41
+ crossCommunityCount: number;
42
+ avgStepCount: number;
43
+ entryPointsFound: number;
44
+ };
45
+ }
46
+ /**
47
+ * Detect processes (execution flows) in the knowledge graph
48
+ *
49
+ * This runs AFTER community detection, using CALLS edges to trace flows.
50
+ */
51
+ export declare const processProcesses: (knowledgeGraph: KnowledgeGraph, memberships: CommunityMembership[], onProgress?: (message: string, progress: number) => void, config?: Partial<ProcessDetectionConfig>) => Promise<ProcessDetectionResult>;