gitnexus 1.4.0 → 1.4.1

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 (131) hide show
  1. package/README.md +194 -214
  2. package/dist/cli/ai-context.d.ts +1 -2
  3. package/dist/cli/ai-context.js +90 -117
  4. package/dist/cli/analyze.d.ts +0 -2
  5. package/dist/cli/analyze.js +2 -20
  6. package/dist/cli/index.js +25 -17
  7. package/dist/cli/setup.js +19 -17
  8. package/dist/core/augmentation/engine.js +20 -20
  9. package/dist/core/embeddings/embedding-pipeline.js +26 -26
  10. package/dist/core/graph/types.d.ts +2 -5
  11. package/dist/core/ingestion/ast-cache.js +2 -3
  12. package/dist/core/ingestion/call-processor.d.ts +5 -5
  13. package/dist/core/ingestion/call-processor.js +258 -173
  14. package/dist/core/ingestion/cluster-enricher.js +16 -16
  15. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -2
  16. package/dist/core/ingestion/entry-point-scoring.js +22 -81
  17. package/dist/core/ingestion/framework-detection.d.ts +1 -5
  18. package/dist/core/ingestion/framework-detection.js +8 -39
  19. package/dist/core/ingestion/heritage-processor.d.ts +4 -13
  20. package/dist/core/ingestion/heritage-processor.js +28 -92
  21. package/dist/core/ingestion/import-processor.d.ts +19 -17
  22. package/dist/core/ingestion/import-processor.js +695 -170
  23. package/dist/core/ingestion/parsing-processor.d.ts +10 -1
  24. package/dist/core/ingestion/parsing-processor.js +177 -41
  25. package/dist/core/ingestion/pipeline.js +26 -49
  26. package/dist/core/ingestion/process-processor.js +1 -2
  27. package/dist/core/ingestion/symbol-table.d.ts +1 -12
  28. package/dist/core/ingestion/symbol-table.js +12 -19
  29. package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
  30. package/dist/core/ingestion/tree-sitter-queries.js +485 -590
  31. package/dist/core/ingestion/utils.d.ts +0 -67
  32. package/dist/core/ingestion/utils.js +9 -692
  33. package/dist/core/ingestion/workers/parse-worker.d.ts +3 -20
  34. package/dist/core/ingestion/workers/parse-worker.js +345 -84
  35. package/dist/core/ingestion/workers/worker-pool.js +0 -8
  36. package/dist/core/kuzu/csv-generator.js +3 -19
  37. package/dist/core/kuzu/kuzu-adapter.js +19 -14
  38. package/dist/core/kuzu/schema.d.ts +3 -3
  39. package/dist/core/kuzu/schema.js +288 -303
  40. package/dist/core/search/bm25-index.js +6 -7
  41. package/dist/core/search/hybrid-search.js +3 -3
  42. package/dist/core/wiki/diagrams.d.ts +27 -0
  43. package/dist/core/wiki/diagrams.js +163 -0
  44. package/dist/core/wiki/generator.d.ts +50 -2
  45. package/dist/core/wiki/generator.js +548 -49
  46. package/dist/core/wiki/graph-queries.d.ts +42 -0
  47. package/dist/core/wiki/graph-queries.js +276 -97
  48. package/dist/core/wiki/html-viewer.js +192 -192
  49. package/dist/core/wiki/llm-client.js +73 -11
  50. package/dist/core/wiki/prompts.d.ts +52 -8
  51. package/dist/core/wiki/prompts.js +200 -86
  52. package/dist/mcp/core/kuzu-adapter.d.ts +3 -1
  53. package/dist/mcp/core/kuzu-adapter.js +44 -13
  54. package/dist/mcp/local/local-backend.js +128 -128
  55. package/dist/mcp/resources.js +42 -42
  56. package/dist/mcp/server.js +19 -18
  57. package/dist/mcp/tools.js +104 -103
  58. package/hooks/claude/gitnexus-hook.cjs +155 -238
  59. package/hooks/claude/pre-tool-use.sh +79 -79
  60. package/hooks/claude/session-start.sh +42 -42
  61. package/package.json +96 -96
  62. package/scripts/patch-tree-sitter-swift.cjs +74 -74
  63. package/skills/gitnexus-cli.md +82 -82
  64. package/skills/gitnexus-debugging.md +89 -89
  65. package/skills/gitnexus-exploring.md +78 -78
  66. package/skills/gitnexus-guide.md +64 -64
  67. package/skills/gitnexus-impact-analysis.md +97 -97
  68. package/skills/gitnexus-pr-review.md +163 -163
  69. package/skills/gitnexus-refactoring.md +121 -121
  70. package/vendor/leiden/index.cjs +355 -355
  71. package/vendor/leiden/utils.cjs +392 -392
  72. package/dist/cli/lazy-action.d.ts +0 -6
  73. package/dist/cli/lazy-action.js +0 -18
  74. package/dist/cli/skill-gen.d.ts +0 -26
  75. package/dist/cli/skill-gen.js +0 -549
  76. package/dist/core/ingestion/constants.d.ts +0 -16
  77. package/dist/core/ingestion/constants.js +0 -16
  78. package/dist/core/ingestion/export-detection.d.ts +0 -18
  79. package/dist/core/ingestion/export-detection.js +0 -230
  80. package/dist/core/ingestion/language-config.d.ts +0 -46
  81. package/dist/core/ingestion/language-config.js +0 -167
  82. package/dist/core/ingestion/mro-processor.d.ts +0 -45
  83. package/dist/core/ingestion/mro-processor.js +0 -369
  84. package/dist/core/ingestion/named-binding-extraction.d.ts +0 -61
  85. package/dist/core/ingestion/named-binding-extraction.js +0 -363
  86. package/dist/core/ingestion/resolvers/csharp.d.ts +0 -22
  87. package/dist/core/ingestion/resolvers/csharp.js +0 -109
  88. package/dist/core/ingestion/resolvers/go.d.ts +0 -19
  89. package/dist/core/ingestion/resolvers/go.js +0 -42
  90. package/dist/core/ingestion/resolvers/index.d.ts +0 -16
  91. package/dist/core/ingestion/resolvers/index.js +0 -11
  92. package/dist/core/ingestion/resolvers/jvm.d.ts +0 -23
  93. package/dist/core/ingestion/resolvers/jvm.js +0 -87
  94. package/dist/core/ingestion/resolvers/php.d.ts +0 -15
  95. package/dist/core/ingestion/resolvers/php.js +0 -35
  96. package/dist/core/ingestion/resolvers/rust.d.ts +0 -15
  97. package/dist/core/ingestion/resolvers/rust.js +0 -73
  98. package/dist/core/ingestion/resolvers/standard.d.ts +0 -28
  99. package/dist/core/ingestion/resolvers/standard.js +0 -145
  100. package/dist/core/ingestion/resolvers/utils.d.ts +0 -33
  101. package/dist/core/ingestion/resolvers/utils.js +0 -120
  102. package/dist/core/ingestion/symbol-resolver.d.ts +0 -32
  103. package/dist/core/ingestion/symbol-resolver.js +0 -83
  104. package/dist/core/ingestion/type-env.d.ts +0 -27
  105. package/dist/core/ingestion/type-env.js +0 -86
  106. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +0 -2
  107. package/dist/core/ingestion/type-extractors/c-cpp.js +0 -60
  108. package/dist/core/ingestion/type-extractors/csharp.d.ts +0 -2
  109. package/dist/core/ingestion/type-extractors/csharp.js +0 -89
  110. package/dist/core/ingestion/type-extractors/go.d.ts +0 -2
  111. package/dist/core/ingestion/type-extractors/go.js +0 -105
  112. package/dist/core/ingestion/type-extractors/index.d.ts +0 -21
  113. package/dist/core/ingestion/type-extractors/index.js +0 -29
  114. package/dist/core/ingestion/type-extractors/jvm.d.ts +0 -3
  115. package/dist/core/ingestion/type-extractors/jvm.js +0 -121
  116. package/dist/core/ingestion/type-extractors/php.d.ts +0 -2
  117. package/dist/core/ingestion/type-extractors/php.js +0 -31
  118. package/dist/core/ingestion/type-extractors/python.d.ts +0 -2
  119. package/dist/core/ingestion/type-extractors/python.js +0 -41
  120. package/dist/core/ingestion/type-extractors/rust.d.ts +0 -2
  121. package/dist/core/ingestion/type-extractors/rust.js +0 -39
  122. package/dist/core/ingestion/type-extractors/shared.d.ts +0 -17
  123. package/dist/core/ingestion/type-extractors/shared.js +0 -97
  124. package/dist/core/ingestion/type-extractors/swift.d.ts +0 -2
  125. package/dist/core/ingestion/type-extractors/swift.js +0 -43
  126. package/dist/core/ingestion/type-extractors/types.d.ts +0 -14
  127. package/dist/core/ingestion/type-extractors/types.js +0 -1
  128. package/dist/core/ingestion/type-extractors/typescript.d.ts +0 -2
  129. package/dist/core/ingestion/type-extractors/typescript.js +0 -46
  130. package/dist/mcp/compatible-stdio-transport.d.ts +0 -25
  131. package/dist/mcp/compatible-stdio-transport.js +0 -200
@@ -1,6 +0,0 @@
1
- /**
2
- * Creates a lazy-loaded CLI action that defers module import until invocation.
3
- * The generic constraints ensure the export name is a valid key of the module
4
- * at compile time — catching typos when used with concrete module imports.
5
- */
6
- export declare function createLazyAction<TModule extends Record<string, unknown>, TKey extends string & keyof TModule>(loader: () => Promise<TModule>, exportName: TKey): (...args: unknown[]) => Promise<void>;
@@ -1,18 +0,0 @@
1
- /**
2
- * Creates a lazy-loaded CLI action that defers module import until invocation.
3
- * The generic constraints ensure the export name is a valid key of the module
4
- * at compile time — catching typos when used with concrete module imports.
5
- */
6
- function isCallable(value) {
7
- return typeof value === 'function';
8
- }
9
- export function createLazyAction(loader, exportName) {
10
- return async (...args) => {
11
- const module = await loader();
12
- const action = module[exportName];
13
- if (!isCallable(action)) {
14
- throw new Error(`Lazy action export not found: ${exportName}`);
15
- }
16
- await action(...args);
17
- };
18
- }
@@ -1,26 +0,0 @@
1
- /**
2
- * Skill File Generator
3
- *
4
- * Generates repo-specific SKILL.md files from detected Leiden communities.
5
- * Each significant community becomes a skill that describes a functional area
6
- * of the codebase, including key files, entry points, execution flows, and
7
- * cross-community connections.
8
- */
9
- import { PipelineResult } from '../types/pipeline.js';
10
- export interface GeneratedSkillInfo {
11
- name: string;
12
- label: string;
13
- symbolCount: number;
14
- fileCount: number;
15
- }
16
- /**
17
- * @brief Generate repo-specific skill files from detected communities
18
- * @param {string} repoPath - Absolute path to the repository root
19
- * @param {string} projectName - Human-readable project name
20
- * @param {PipelineResult} pipelineResult - In-memory pipeline data with communities, processes, graph
21
- * @returns {Promise<{ skills: GeneratedSkillInfo[], outputPath: string }>} Generated skill metadata
22
- */
23
- export declare const generateSkillFiles: (repoPath: string, projectName: string, pipelineResult: PipelineResult) => Promise<{
24
- skills: GeneratedSkillInfo[];
25
- outputPath: string;
26
- }>;
@@ -1,549 +0,0 @@
1
- /**
2
- * Skill File Generator
3
- *
4
- * Generates repo-specific SKILL.md files from detected Leiden communities.
5
- * Each significant community becomes a skill that describes a functional area
6
- * of the codebase, including key files, entry points, execution flows, and
7
- * cross-community connections.
8
- */
9
- import fs from 'fs/promises';
10
- import path from 'path';
11
- // ============================================================================
12
- // MAIN EXPORT
13
- // ============================================================================
14
- /**
15
- * @brief Generate repo-specific skill files from detected communities
16
- * @param {string} repoPath - Absolute path to the repository root
17
- * @param {string} projectName - Human-readable project name
18
- * @param {PipelineResult} pipelineResult - In-memory pipeline data with communities, processes, graph
19
- * @returns {Promise<{ skills: GeneratedSkillInfo[], outputPath: string }>} Generated skill metadata
20
- */
21
- export const generateSkillFiles = async (repoPath, projectName, pipelineResult) => {
22
- const { communityResult, processResult, graph } = pipelineResult;
23
- const outputDir = path.join(repoPath, '.claude', 'skills', 'generated');
24
- if (!communityResult || !communityResult.memberships.length) {
25
- console.log('\n Skills: no communities detected, skipping skill generation');
26
- return { skills: [], outputPath: outputDir };
27
- }
28
- console.log('\n Generating repo-specific skills...');
29
- // Step 1: Build communities from memberships (not the filtered communities array).
30
- // The community processor skips singletons from its communities array but memberships
31
- // include ALL assignments. For repos with sparse CALLS edges, the communities array
32
- // can be empty while memberships still has useful groupings.
33
- const communities = communityResult.communities.length > 0
34
- ? communityResult.communities
35
- : buildCommunitiesFromMemberships(communityResult.memberships, graph, repoPath);
36
- const aggregated = aggregateCommunities(communities);
37
- // Step 2: Filter to significant communities
38
- // Keep communities with >= 3 symbols after aggregation.
39
- const significant = aggregated
40
- .filter(c => c.symbolCount >= 3)
41
- .sort((a, b) => b.symbolCount - a.symbolCount)
42
- .slice(0, 20);
43
- if (significant.length === 0) {
44
- console.log('\n Skills: no significant communities found (all below 3-symbol threshold)');
45
- return { skills: [], outputPath: outputDir };
46
- }
47
- // Step 3: Build lookup maps
48
- const membershipsByComm = buildMembershipMap(communityResult.memberships);
49
- const nodeIdToCommunityLabel = buildNodeCommunityLabelMap(communityResult.memberships, communities);
50
- // Step 4: Clear and recreate output directory
51
- try {
52
- await fs.rm(outputDir, { recursive: true, force: true });
53
- }
54
- catch { /* may not exist */ }
55
- await fs.mkdir(outputDir, { recursive: true });
56
- // Step 5: Generate skill files
57
- const skills = [];
58
- const usedNames = new Set();
59
- for (const community of significant) {
60
- // Gather member symbols
61
- const members = gatherMembers(community.rawIds, membershipsByComm, graph);
62
- if (members.length === 0)
63
- continue;
64
- // Gather file info
65
- const files = gatherFiles(members, repoPath);
66
- // Gather entry points
67
- const entryPoints = gatherEntryPoints(members);
68
- // Gather execution flows
69
- const flows = gatherFlows(community.rawIds, processResult?.processes || []);
70
- // Gather cross-community connections
71
- const connections = gatherCrossConnections(community.rawIds, community.label, membershipsByComm, nodeIdToCommunityLabel, graph);
72
- // Generate kebab name
73
- const kebabName = toKebabName(community.label, usedNames);
74
- usedNames.add(kebabName);
75
- // Generate SKILL.md content
76
- const content = renderSkillMarkdown(community, projectName, members, files, entryPoints, flows, connections, kebabName);
77
- // Write file
78
- const skillDir = path.join(outputDir, kebabName);
79
- await fs.mkdir(skillDir, { recursive: true });
80
- await fs.writeFile(path.join(skillDir, 'SKILL.md'), content, 'utf-8');
81
- const info = {
82
- name: kebabName,
83
- label: community.label,
84
- symbolCount: community.symbolCount,
85
- fileCount: files.length,
86
- };
87
- skills.push(info);
88
- console.log(` \u2713 ${community.label} (${community.symbolCount} symbols, ${files.length} files)`);
89
- }
90
- console.log(`\n ${skills.length} skills generated \u2192 .claude/skills/generated/`);
91
- return { skills, outputPath: outputDir };
92
- };
93
- // ============================================================================
94
- // FALLBACK COMMUNITY BUILDER
95
- // ============================================================================
96
- /**
97
- * @brief Build CommunityNode-like objects from raw memberships when the community
98
- * processor's communities array is empty (all singletons were filtered out)
99
- * @param {CommunityMembership[]} memberships - All node-to-community assignments
100
- * @param {KnowledgeGraph} graph - The knowledge graph for resolving node metadata
101
- * @param {string} repoPath - Repository root for path normalization
102
- * @returns {CommunityNode[]} Synthetic community nodes built from membership data
103
- */
104
- const buildCommunitiesFromMemberships = (memberships, graph, repoPath) => {
105
- // Group memberships by communityId
106
- const groups = new Map();
107
- for (const m of memberships) {
108
- const arr = groups.get(m.communityId);
109
- if (arr) {
110
- arr.push(m.nodeId);
111
- }
112
- else {
113
- groups.set(m.communityId, [m.nodeId]);
114
- }
115
- }
116
- const communities = [];
117
- for (const [commId, nodeIds] of groups) {
118
- // Derive a heuristic label from the most common parent directory
119
- const folderCounts = new Map();
120
- for (const nodeId of nodeIds) {
121
- const node = graph.getNode(nodeId);
122
- if (!node?.properties.filePath)
123
- continue;
124
- const normalized = node.properties.filePath.replace(/\\/g, '/');
125
- const parts = normalized.split('/').filter(Boolean);
126
- if (parts.length >= 2) {
127
- const folder = parts[parts.length - 2];
128
- if (!['src', 'lib', 'core', 'utils', 'common', 'shared', 'helpers'].includes(folder.toLowerCase())) {
129
- folderCounts.set(folder, (folderCounts.get(folder) || 0) + 1);
130
- }
131
- }
132
- }
133
- let bestFolder = '';
134
- let bestCount = 0;
135
- for (const [folder, count] of folderCounts) {
136
- if (count > bestCount) {
137
- bestCount = count;
138
- bestFolder = folder;
139
- }
140
- }
141
- const label = bestFolder
142
- ? bestFolder.charAt(0).toUpperCase() + bestFolder.slice(1)
143
- : `Cluster_${commId.replace('comm_', '')}`;
144
- // Compute cohesion as internal-edge ratio (matches backend calculateCohesion).
145
- // For each member node, count edges that stay inside the community vs total.
146
- const nodeSet = new Set(nodeIds);
147
- let internalEdges = 0;
148
- let totalEdges = 0;
149
- graph.forEachRelationship(rel => {
150
- if (nodeSet.has(rel.sourceId)) {
151
- totalEdges++;
152
- if (nodeSet.has(rel.targetId))
153
- internalEdges++;
154
- }
155
- });
156
- const cohesion = totalEdges > 0 ? Math.min(1.0, internalEdges / totalEdges) : 1.0;
157
- communities.push({
158
- id: commId,
159
- label,
160
- heuristicLabel: label,
161
- cohesion,
162
- symbolCount: nodeIds.length,
163
- });
164
- }
165
- return communities.sort((a, b) => b.symbolCount - a.symbolCount);
166
- };
167
- // ============================================================================
168
- // AGGREGATION
169
- // ============================================================================
170
- /**
171
- * @brief Aggregate raw Leiden communities by heuristicLabel
172
- * @param {CommunityNode[]} communities - Raw community nodes from Leiden detection
173
- * @returns {AggregatedCommunity[]} Aggregated communities grouped by label
174
- */
175
- const aggregateCommunities = (communities) => {
176
- const groups = new Map();
177
- for (const c of communities) {
178
- const label = c.heuristicLabel || c.label || 'Unknown';
179
- const symbols = c.symbolCount || 0;
180
- const cohesion = c.cohesion || 0;
181
- const existing = groups.get(label);
182
- if (!existing) {
183
- groups.set(label, {
184
- rawIds: [c.id],
185
- totalSymbols: symbols,
186
- weightedCohesion: cohesion * symbols,
187
- });
188
- }
189
- else {
190
- existing.rawIds.push(c.id);
191
- existing.totalSymbols += symbols;
192
- existing.weightedCohesion += cohesion * symbols;
193
- }
194
- }
195
- return Array.from(groups.entries()).map(([label, g]) => ({
196
- label,
197
- rawIds: g.rawIds,
198
- symbolCount: g.totalSymbols,
199
- cohesion: g.totalSymbols > 0 ? g.weightedCohesion / g.totalSymbols : 0,
200
- }));
201
- };
202
- // ============================================================================
203
- // LOOKUP MAP BUILDERS
204
- // ============================================================================
205
- /**
206
- * @brief Build a map from communityId to member nodeIds
207
- * @param {CommunityMembership[]} memberships - All membership records
208
- * @returns {Map<string, string[]>} Map of communityId -> nodeId[]
209
- */
210
- const buildMembershipMap = (memberships) => {
211
- const map = new Map();
212
- for (const m of memberships) {
213
- const arr = map.get(m.communityId);
214
- if (arr) {
215
- arr.push(m.nodeId);
216
- }
217
- else {
218
- map.set(m.communityId, [m.nodeId]);
219
- }
220
- }
221
- return map;
222
- };
223
- /**
224
- * @brief Build a map from nodeId to aggregated community label
225
- * @param {CommunityMembership[]} memberships - All membership records
226
- * @param {CommunityNode[]} communities - Community nodes with labels
227
- * @returns {Map<string, string>} Map of nodeId -> community label
228
- */
229
- const buildNodeCommunityLabelMap = (memberships, communities) => {
230
- const commIdToLabel = new Map();
231
- for (const c of communities) {
232
- commIdToLabel.set(c.id, c.heuristicLabel || c.label || 'Unknown');
233
- }
234
- const map = new Map();
235
- for (const m of memberships) {
236
- const label = commIdToLabel.get(m.communityId);
237
- if (label) {
238
- map.set(m.nodeId, label);
239
- }
240
- }
241
- return map;
242
- };
243
- // ============================================================================
244
- // DATA GATHERING
245
- // ============================================================================
246
- /**
247
- * @brief Gather member symbols for an aggregated community
248
- * @param {string[]} rawIds - Raw community IDs belonging to this aggregated community
249
- * @param {Map<string, string[]>} membershipsByComm - communityId -> nodeIds
250
- * @param {KnowledgeGraph} graph - The knowledge graph
251
- * @returns {MemberSymbol[]} Array of member symbol information
252
- */
253
- const gatherMembers = (rawIds, membershipsByComm, graph) => {
254
- const seen = new Set();
255
- const members = [];
256
- for (const commId of rawIds) {
257
- const nodeIds = membershipsByComm.get(commId) || [];
258
- for (const nodeId of nodeIds) {
259
- if (seen.has(nodeId))
260
- continue;
261
- seen.add(nodeId);
262
- const node = graph.getNode(nodeId);
263
- if (!node)
264
- continue;
265
- members.push({
266
- id: node.id,
267
- name: node.properties.name,
268
- label: node.label,
269
- filePath: node.properties.filePath || '',
270
- startLine: node.properties.startLine || 0,
271
- isExported: node.properties.isExported === true,
272
- });
273
- }
274
- }
275
- return members;
276
- };
277
- /**
278
- * @brief Gather deduplicated file info with per-file symbol names
279
- * @param {MemberSymbol[]} members - Member symbols
280
- * @param {string} repoPath - Repository root for relative path computation
281
- * @returns {FileInfo[]} Sorted by symbol count descending
282
- */
283
- const gatherFiles = (members, repoPath) => {
284
- const fileMap = new Map();
285
- for (const m of members) {
286
- if (!m.filePath)
287
- continue;
288
- const rel = toRelativePath(m.filePath, repoPath);
289
- const arr = fileMap.get(rel);
290
- if (arr) {
291
- arr.push(m.name);
292
- }
293
- else {
294
- fileMap.set(rel, [m.name]);
295
- }
296
- }
297
- return Array.from(fileMap.entries())
298
- .map(([relativePath, symbols]) => ({ relativePath, symbols }))
299
- .sort((a, b) => b.symbols.length - a.symbols.length);
300
- };
301
- /**
302
- * @brief Gather exported entry points prioritized by type
303
- * @param {MemberSymbol[]} members - Member symbols
304
- * @returns {MemberSymbol[]} Exported symbols sorted by type priority
305
- */
306
- const gatherEntryPoints = (members) => {
307
- const typePriority = {
308
- Function: 0,
309
- Class: 1,
310
- Method: 2,
311
- Interface: 3,
312
- };
313
- return members
314
- .filter(m => m.isExported)
315
- .sort((a, b) => {
316
- const pa = typePriority[a.label] ?? 99;
317
- const pb = typePriority[b.label] ?? 99;
318
- return pa - pb;
319
- });
320
- };
321
- /**
322
- * @brief Gather execution flows touching this community
323
- * @param {string[]} rawIds - Raw community IDs for this aggregated community
324
- * @param {ProcessNode[]} processes - All detected processes
325
- * @returns {ProcessNode[]} Processes whose communities intersect rawIds, sorted by stepCount
326
- */
327
- const gatherFlows = (rawIds, processes) => {
328
- const rawIdSet = new Set(rawIds);
329
- return processes
330
- .filter(proc => proc.communities.some(cid => rawIdSet.has(cid)))
331
- .sort((a, b) => b.stepCount - a.stepCount);
332
- };
333
- /**
334
- * @brief Gather cross-community call connections
335
- * @param {string[]} rawIds - Raw community IDs for this aggregated community
336
- * @param {string} ownLabel - This community's aggregated label
337
- * @param {Map<string, string[]>} membershipsByComm - communityId -> nodeIds
338
- * @param {Map<string, string>} nodeIdToCommunityLabel - nodeId -> community label
339
- * @param {KnowledgeGraph} graph - The knowledge graph
340
- * @returns {CrossConnection[]} Aggregated cross-community connections sorted by count
341
- */
342
- const gatherCrossConnections = (rawIds, ownLabel, membershipsByComm, nodeIdToCommunityLabel, graph) => {
343
- // Collect all node IDs in this aggregated community
344
- const ownNodeIds = new Set();
345
- for (const commId of rawIds) {
346
- const nodeIds = membershipsByComm.get(commId) || [];
347
- for (const nid of nodeIds) {
348
- ownNodeIds.add(nid);
349
- }
350
- }
351
- // Count outgoing CALLS to nodes in different communities
352
- const targetCounts = new Map();
353
- graph.forEachRelationship(rel => {
354
- if (rel.type !== 'CALLS')
355
- return;
356
- if (!ownNodeIds.has(rel.sourceId))
357
- return;
358
- if (ownNodeIds.has(rel.targetId))
359
- return; // same community
360
- const targetLabel = nodeIdToCommunityLabel.get(rel.targetId);
361
- if (!targetLabel || targetLabel === ownLabel)
362
- return;
363
- targetCounts.set(targetLabel, (targetCounts.get(targetLabel) || 0) + 1);
364
- });
365
- return Array.from(targetCounts.entries())
366
- .map(([targetLabel, count]) => ({ targetLabel, count }))
367
- .sort((a, b) => b.count - a.count);
368
- };
369
- // ============================================================================
370
- // MARKDOWN RENDERING
371
- // ============================================================================
372
- /**
373
- * @brief Render SKILL.md content for a single community
374
- * @param {AggregatedCommunity} community - The aggregated community data
375
- * @param {string} projectName - Project name for the description
376
- * @param {MemberSymbol[]} members - All member symbols
377
- * @param {FileInfo[]} files - File info with symbol names
378
- * @param {MemberSymbol[]} entryPoints - Exported entry point symbols
379
- * @param {ProcessNode[]} flows - Execution flows touching this community
380
- * @param {CrossConnection[]} connections - Cross-community connections
381
- * @param {string} kebabName - Kebab-case name for the skill
382
- * @returns {string} Full SKILL.md content
383
- */
384
- const renderSkillMarkdown = (community, projectName, members, files, entryPoints, flows, connections, kebabName) => {
385
- const cohesionPct = Math.round(community.cohesion * 100);
386
- // Dominant directory: most common top-level directory
387
- const dominantDir = getDominantDirectory(files);
388
- // Top symbol names for "When to Use"
389
- const topNames = entryPoints.slice(0, 3).map(e => e.name);
390
- if (topNames.length === 0) {
391
- // Fallback to any members
392
- topNames.push(...members.slice(0, 3).map(m => m.name));
393
- }
394
- const lines = [];
395
- // Frontmatter
396
- lines.push('---');
397
- lines.push(`name: ${kebabName}`);
398
- lines.push(`description: "Skill for the ${community.label} area of ${projectName}. ${community.symbolCount} symbols across ${files.length} files."`);
399
- lines.push('---');
400
- lines.push('');
401
- // Title
402
- lines.push(`# ${community.label}`);
403
- lines.push('');
404
- lines.push(`${community.symbolCount} symbols | ${files.length} files | Cohesion: ${cohesionPct}%`);
405
- lines.push('');
406
- // When to Use
407
- lines.push('## When to Use');
408
- lines.push('');
409
- if (dominantDir) {
410
- lines.push(`- Working with code in \`${dominantDir}/\``);
411
- }
412
- if (topNames.length > 0) {
413
- lines.push(`- Understanding how ${topNames.join(', ')} work`);
414
- }
415
- lines.push(`- Modifying ${community.label.toLowerCase()}-related functionality`);
416
- lines.push('');
417
- // Key Files (top 10)
418
- lines.push('## Key Files');
419
- lines.push('');
420
- lines.push('| File | Symbols |');
421
- lines.push('|------|---------|');
422
- for (const f of files.slice(0, 10)) {
423
- const symbolList = f.symbols.slice(0, 5).join(', ');
424
- const suffix = f.symbols.length > 5 ? ` (+${f.symbols.length - 5})` : '';
425
- lines.push(`| \`${f.relativePath}\` | ${symbolList}${suffix} |`);
426
- }
427
- lines.push('');
428
- // Entry Points (top 5)
429
- if (entryPoints.length > 0) {
430
- lines.push('## Entry Points');
431
- lines.push('');
432
- lines.push('Start here when exploring this area:');
433
- lines.push('');
434
- for (const ep of entryPoints.slice(0, 5)) {
435
- lines.push(`- **\`${ep.name}\`** (${ep.label}) \u2014 \`${ep.filePath}:${ep.startLine}\``);
436
- }
437
- lines.push('');
438
- }
439
- // Key Symbols (top 20, exported first, then by type)
440
- lines.push('## Key Symbols');
441
- lines.push('');
442
- lines.push('| Symbol | Type | File | Line |');
443
- lines.push('|--------|------|------|------|');
444
- const sortedMembers = [...members].sort((a, b) => {
445
- if (a.isExported !== b.isExported)
446
- return a.isExported ? -1 : 1;
447
- return a.label.localeCompare(b.label);
448
- });
449
- for (const m of sortedMembers.slice(0, 20)) {
450
- lines.push(`| \`${m.name}\` | ${m.label} | \`${m.filePath}\` | ${m.startLine} |`);
451
- }
452
- lines.push('');
453
- // Execution Flows
454
- if (flows.length > 0) {
455
- lines.push('## Execution Flows');
456
- lines.push('');
457
- lines.push('| Flow | Type | Steps |');
458
- lines.push('|------|------|-------|');
459
- for (const f of flows.slice(0, 10)) {
460
- lines.push(`| \`${f.heuristicLabel}\` | ${f.processType} | ${f.stepCount} |`);
461
- }
462
- lines.push('');
463
- }
464
- // Connected Areas
465
- if (connections.length > 0) {
466
- lines.push('## Connected Areas');
467
- lines.push('');
468
- lines.push('| Area | Connections |');
469
- lines.push('|------|-------------|');
470
- for (const c of connections.slice(0, 8)) {
471
- lines.push(`| ${c.targetLabel} | ${c.count} calls |`);
472
- }
473
- lines.push('');
474
- }
475
- // How to Explore
476
- const firstEntry = entryPoints.length > 0 ? entryPoints[0].name : (members.length > 0 ? members[0].name : community.label);
477
- lines.push('## How to Explore');
478
- lines.push('');
479
- lines.push(`1. \`gitnexus_context({name: "${firstEntry}"})\` \u2014 see callers and callees`);
480
- lines.push(`2. \`gitnexus_query({query: "${community.label.toLowerCase()}"})\` \u2014 find related execution flows`);
481
- lines.push('3. Read key files listed above for implementation details');
482
- lines.push('');
483
- return lines.join('\n');
484
- };
485
- // ============================================================================
486
- // UTILITY HELPERS
487
- // ============================================================================
488
- /**
489
- * @brief Convert a community label to a kebab-case directory name
490
- * @param {string} label - The community label
491
- * @param {Set<string>} usedNames - Already-used names for collision detection
492
- * @returns {string} Unique kebab-case name capped at 50 characters
493
- */
494
- const toKebabName = (label, usedNames) => {
495
- let name = label
496
- .toLowerCase()
497
- .replace(/[^a-z0-9]+/g, '-')
498
- .replace(/^-+|-+$/g, '')
499
- .slice(0, 50);
500
- if (!name)
501
- name = 'skill';
502
- let candidate = name;
503
- let counter = 2;
504
- while (usedNames.has(candidate)) {
505
- candidate = `${name}-${counter}`;
506
- counter++;
507
- }
508
- return candidate;
509
- };
510
- /**
511
- * @brief Convert an absolute or repo-relative file path to a clean relative path
512
- * @param {string} filePath - The file path from the graph node
513
- * @param {string} repoPath - Repository root path
514
- * @returns {string} Relative path using forward slashes
515
- */
516
- const toRelativePath = (filePath, repoPath) => {
517
- // Normalize to forward slashes for cross-platform consistency
518
- const normalizedFile = filePath.replace(/\\/g, '/');
519
- const normalizedRepo = repoPath.replace(/\\/g, '/');
520
- if (normalizedFile.startsWith(normalizedRepo)) {
521
- return normalizedFile.slice(normalizedRepo.length).replace(/^\//, '');
522
- }
523
- // Already relative or different root
524
- return normalizedFile.replace(/^\//, '');
525
- };
526
- /**
527
- * @brief Find the dominant (most common) top-level directory across files
528
- * @param {FileInfo[]} files - File info entries
529
- * @returns {string | null} Most common directory or null
530
- */
531
- const getDominantDirectory = (files) => {
532
- const dirCounts = new Map();
533
- for (const f of files) {
534
- const parts = f.relativePath.split('/');
535
- if (parts.length >= 2) {
536
- const dir = parts[0];
537
- dirCounts.set(dir, (dirCounts.get(dir) || 0) + f.symbols.length);
538
- }
539
- }
540
- let best = null;
541
- let bestCount = 0;
542
- for (const [dir, count] of dirCounts) {
543
- if (count > bestCount) {
544
- bestCount = count;
545
- best = dir;
546
- }
547
- }
548
- return best;
549
- };
@@ -1,16 +0,0 @@
1
- /**
2
- * Default minimum buffer size for tree-sitter parsing (512 KB).
3
- * tree-sitter requires bufferSize >= file size in bytes.
4
- */
5
- export declare const TREE_SITTER_BUFFER_SIZE: number;
6
- /**
7
- * Maximum buffer size cap (32 MB) to prevent OOM on huge files.
8
- * Also used as the file-size skip threshold — files larger than this are not parsed.
9
- */
10
- export declare const TREE_SITTER_MAX_BUFFER: number;
11
- /**
12
- * Compute adaptive buffer size for tree-sitter parsing.
13
- * Uses 2× file size, clamped between 512 KB and 32 MB.
14
- * Previous 256 KB fixed limit silently skipped files > ~200 KB (e.g., imgui.h at 411 KB).
15
- */
16
- export declare const getTreeSitterBufferSize: (contentLength: number) => number;
@@ -1,16 +0,0 @@
1
- /**
2
- * Default minimum buffer size for tree-sitter parsing (512 KB).
3
- * tree-sitter requires bufferSize >= file size in bytes.
4
- */
5
- export const TREE_SITTER_BUFFER_SIZE = 512 * 1024;
6
- /**
7
- * Maximum buffer size cap (32 MB) to prevent OOM on huge files.
8
- * Also used as the file-size skip threshold — files larger than this are not parsed.
9
- */
10
- export const TREE_SITTER_MAX_BUFFER = 32 * 1024 * 1024;
11
- /**
12
- * Compute adaptive buffer size for tree-sitter parsing.
13
- * Uses 2× file size, clamped between 512 KB and 32 MB.
14
- * Previous 256 KB fixed limit silently skipped files > ~200 KB (e.g., imgui.h at 411 KB).
15
- */
16
- export const getTreeSitterBufferSize = (contentLength) => Math.min(Math.max(contentLength * 2, TREE_SITTER_BUFFER_SIZE), TREE_SITTER_MAX_BUFFER);
@@ -1,18 +0,0 @@
1
- /**
2
- * Export Detection
3
- *
4
- * Determines whether a symbol (function, class, etc.) is exported/public
5
- * in its language. This is a pure function — safe for use in worker threads.
6
- *
7
- * Shared between parse-worker.ts (worker pool) and parsing-processor.ts (sequential fallback).
8
- */
9
- import { SyntaxNode } from './utils.js';
10
- import { SupportedLanguages } from '../../config/supported-languages.js';
11
- /**
12
- * Check if a tree-sitter node is exported/public in its language.
13
- * @param node - The tree-sitter AST node
14
- * @param name - The symbol name
15
- * @param language - The programming language
16
- * @returns true if the symbol is exported/public
17
- */
18
- export declare const isNodeExported: (node: SyntaxNode, name: string, language: SupportedLanguages) => boolean;