gitnexus 1.4.1 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -194
- package/dist/cli/ai-context.d.ts +2 -1
- package/dist/cli/ai-context.js +117 -90
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +57 -30
- package/dist/cli/augment.js +1 -1
- package/dist/cli/eval-server.d.ts +1 -1
- package/dist/cli/eval-server.js +1 -1
- package/dist/cli/index.js +18 -25
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/mcp.js +1 -1
- package/dist/cli/setup.js +42 -32
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.js +13 -4
- package/dist/cli/tool.d.ts +1 -1
- package/dist/cli/tool.js +2 -2
- package/dist/cli/wiki.js +2 -2
- package/dist/config/ignore-service.d.ts +25 -0
- package/dist/config/ignore-service.js +76 -0
- package/dist/config/supported-languages.d.ts +1 -0
- package/dist/config/supported-languages.js +1 -1
- package/dist/core/augmentation/engine.js +99 -72
- package/dist/core/embeddings/embedder.d.ts +1 -1
- package/dist/core/embeddings/embedder.js +1 -1
- package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
- package/dist/core/embeddings/embedding-pipeline.js +74 -47
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/types.d.ts +5 -2
- package/dist/core/ingestion/ast-cache.js +3 -2
- package/dist/core/ingestion/call-processor.d.ts +6 -7
- package/dist/core/ingestion/call-processor.js +560 -282
- package/dist/core/ingestion/call-routing.d.ts +53 -0
- package/dist/core/ingestion/call-routing.js +108 -0
- package/dist/core/ingestion/cluster-enricher.js +16 -16
- package/dist/core/ingestion/constants.d.ts +16 -0
- package/dist/core/ingestion/constants.js +16 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
- package/dist/core/ingestion/entry-point-scoring.js +94 -24
- package/dist/core/ingestion/export-detection.d.ts +18 -0
- package/dist/core/ingestion/export-detection.js +231 -0
- package/dist/core/ingestion/filesystem-walker.js +4 -3
- package/dist/core/ingestion/framework-detection.d.ts +5 -1
- package/dist/core/ingestion/framework-detection.js +48 -8
- package/dist/core/ingestion/heritage-processor.d.ts +13 -5
- package/dist/core/ingestion/heritage-processor.js +109 -55
- package/dist/core/ingestion/import-processor.d.ts +16 -20
- package/dist/core/ingestion/import-processor.js +202 -696
- package/dist/core/ingestion/language-config.d.ts +46 -0
- package/dist/core/ingestion/language-config.js +167 -0
- package/dist/core/ingestion/mro-processor.d.ts +45 -0
- package/dist/core/ingestion/mro-processor.js +369 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
- package/dist/core/ingestion/named-binding-extraction.js +363 -0
- package/dist/core/ingestion/parsing-processor.d.ts +3 -11
- package/dist/core/ingestion/parsing-processor.js +82 -181
- package/dist/core/ingestion/pipeline.d.ts +5 -1
- package/dist/core/ingestion/pipeline.js +192 -116
- package/dist/core/ingestion/process-processor.js +2 -1
- package/dist/core/ingestion/resolution-context.d.ts +53 -0
- package/dist/core/ingestion/resolution-context.js +132 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
- package/dist/core/ingestion/resolvers/csharp.js +109 -0
- package/dist/core/ingestion/resolvers/go.d.ts +19 -0
- package/dist/core/ingestion/resolvers/go.js +42 -0
- package/dist/core/ingestion/resolvers/index.d.ts +18 -0
- package/dist/core/ingestion/resolvers/index.js +13 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
- package/dist/core/ingestion/resolvers/jvm.js +87 -0
- package/dist/core/ingestion/resolvers/php.d.ts +15 -0
- package/dist/core/ingestion/resolvers/php.js +35 -0
- package/dist/core/ingestion/resolvers/python.d.ts +19 -0
- package/dist/core/ingestion/resolvers/python.js +52 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
- package/dist/core/ingestion/resolvers/ruby.js +15 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
- package/dist/core/ingestion/resolvers/rust.js +73 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
- package/dist/core/ingestion/resolvers/standard.js +123 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
- package/dist/core/ingestion/resolvers/utils.js +122 -0
- package/dist/core/ingestion/symbol-table.d.ts +15 -1
- package/dist/core/ingestion/symbol-table.js +20 -12
- package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -11
- package/dist/core/ingestion/tree-sitter-queries.js +642 -485
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +559 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/csharp.js +369 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/go.js +436 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
- package/dist/core/ingestion/type-extractors/index.js +31 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
- package/dist/core/ingestion/type-extractors/jvm.js +654 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/php.js +411 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/python.js +392 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/ruby.js +389 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/rust.js +436 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +132 -0
- package/dist/core/ingestion/type-extractors/shared.js +571 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/swift.js +137 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +95 -0
- package/dist/core/ingestion/type-extractors/types.js +1 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/typescript.js +480 -0
- package/dist/core/ingestion/utils.d.ts +98 -0
- package/dist/core/ingestion/utils.js +1064 -9
- package/dist/core/ingestion/workers/parse-worker.d.ts +38 -4
- package/dist/core/ingestion/workers/parse-worker.js +248 -359
- package/dist/core/ingestion/workers/worker-pool.js +8 -0
- package/dist/core/{kuzu → lbug}/csv-generator.d.ts +1 -1
- package/dist/core/{kuzu → lbug}/csv-generator.js +20 -4
- package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +19 -19
- package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +82 -82
- package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
- package/dist/core/{kuzu → lbug}/schema.js +304 -289
- package/dist/core/search/bm25-index.d.ts +4 -4
- package/dist/core/search/bm25-index.js +17 -16
- package/dist/core/search/hybrid-search.d.ts +2 -2
- package/dist/core/search/hybrid-search.js +9 -9
- package/dist/core/tree-sitter/parser-loader.js +9 -2
- package/dist/core/wiki/generator.d.ts +4 -52
- package/dist/core/wiki/generator.js +53 -552
- package/dist/core/wiki/graph-queries.d.ts +4 -46
- package/dist/core/wiki/graph-queries.js +103 -282
- package/dist/core/wiki/html-viewer.js +192 -192
- package/dist/core/wiki/llm-client.js +11 -73
- package/dist/core/wiki/prompts.d.ts +8 -52
- package/dist/core/wiki/prompts.js +86 -200
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +7 -9
- package/dist/mcp/core/{kuzu-adapter.js → lbug-adapter.js} +77 -79
- package/dist/mcp/local/local-backend.d.ts +6 -6
- package/dist/mcp/local/local-backend.js +153 -146
- package/dist/mcp/resources.js +42 -42
- package/dist/mcp/server.js +18 -19
- package/dist/mcp/tools.js +103 -104
- package/dist/server/api.js +12 -12
- package/dist/server/mcp-http.d.ts +1 -1
- package/dist/server/mcp-http.js +1 -1
- package/dist/storage/repo-manager.d.ts +20 -2
- package/dist/storage/repo-manager.js +55 -1
- package/dist/types/pipeline.d.ts +1 -1
- package/hooks/claude/gitnexus-hook.cjs +238 -155
- package/hooks/claude/pre-tool-use.sh +79 -79
- package/hooks/claude/session-start.sh +42 -42
- package/package.json +98 -96
- package/scripts/patch-tree-sitter-swift.cjs +74 -74
- package/skills/gitnexus-cli.md +82 -82
- package/skills/gitnexus-debugging.md +89 -89
- package/skills/gitnexus-exploring.md +78 -78
- package/skills/gitnexus-guide.md +64 -64
- package/skills/gitnexus-impact-analysis.md +97 -97
- package/skills/gitnexus-pr-review.md +163 -163
- package/skills/gitnexus-refactoring.md +121 -121
- package/vendor/leiden/index.cjs +355 -355
- package/vendor/leiden/utils.cjs +392 -392
- package/dist/core/wiki/diagrams.d.ts +0 -27
- package/dist/core/wiki/diagrams.js +0 -163
|
@@ -0,0 +1,549 @@
|
|
|
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
|
+
};
|
package/dist/cli/status.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Shows the indexing status of the current repository.
|
|
5
5
|
*/
|
|
6
|
-
import { findRepo } from '../storage/repo-manager.js';
|
|
7
|
-
import { getCurrentCommit, isGitRepo } from '../storage/git.js';
|
|
6
|
+
import { findRepo, getStoragePaths, hasKuzuIndex } from '../storage/repo-manager.js';
|
|
7
|
+
import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
|
|
8
8
|
export const statusCommand = async () => {
|
|
9
9
|
const cwd = process.cwd();
|
|
10
10
|
if (!isGitRepo(cwd)) {
|
|
@@ -13,8 +13,17 @@ export const statusCommand = async () => {
|
|
|
13
13
|
}
|
|
14
14
|
const repo = await findRepo(cwd);
|
|
15
15
|
if (!repo) {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// Check if there's a stale KuzuDB index that needs migration
|
|
17
|
+
const repoRoot = getGitRoot(cwd) ?? cwd;
|
|
18
|
+
const { storagePath } = getStoragePaths(repoRoot);
|
|
19
|
+
if (await hasKuzuIndex(storagePath)) {
|
|
20
|
+
console.log('Repository has a stale KuzuDB index from a previous version.');
|
|
21
|
+
console.log('Run: gitnexus analyze (rebuilds the index with LadybugDB)');
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.log('Repository not indexed.');
|
|
25
|
+
console.log('Run: gitnexus analyze');
|
|
26
|
+
}
|
|
18
27
|
return;
|
|
19
28
|
}
|
|
20
29
|
const currentCommit = getCurrentCommit(repo.repoPath);
|
package/dist/cli/tool.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* gitnexus impact --target "AuthService" --direction upstream
|
|
11
11
|
* gitnexus cypher "MATCH (n:Function) RETURN n.name LIMIT 10"
|
|
12
12
|
*
|
|
13
|
-
* Note: Output goes to stderr because
|
|
13
|
+
* Note: Output goes to stderr because LadybugDB's native module captures stdout
|
|
14
14
|
* at the OS level during init. This is consistent with augment.ts.
|
|
15
15
|
*/
|
|
16
16
|
export declare function queryCommand(queryText: string, options?: {
|
package/dist/cli/tool.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* gitnexus impact --target "AuthService" --direction upstream
|
|
11
11
|
* gitnexus cypher "MATCH (n:Function) RETURN n.name LIMIT 10"
|
|
12
12
|
*
|
|
13
|
-
* Note: Output goes to stderr because
|
|
13
|
+
* Note: Output goes to stderr because LadybugDB's native module captures stdout
|
|
14
14
|
* at the OS level during init. This is consistent with augment.ts.
|
|
15
15
|
*/
|
|
16
16
|
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
@@ -28,7 +28,7 @@ async function getBackend() {
|
|
|
28
28
|
}
|
|
29
29
|
function output(data) {
|
|
30
30
|
const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
31
|
-
// stderr because
|
|
31
|
+
// stderr because LadybugDB captures stdout at OS level
|
|
32
32
|
process.stderr.write(text + '\n');
|
|
33
33
|
}
|
|
34
34
|
export async function queryCommand(queryText, options) {
|
package/dist/cli/wiki.js
CHANGED
|
@@ -86,7 +86,7 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
// ── Check for existing index ────────────────────────────────────────
|
|
89
|
-
const { storagePath,
|
|
89
|
+
const { storagePath, lbugPath } = getStoragePaths(repoPath);
|
|
90
90
|
const meta = await loadMeta(storagePath);
|
|
91
91
|
if (!meta) {
|
|
92
92
|
console.log(' Error: No GitNexus index found.');
|
|
@@ -217,7 +217,7 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
217
217
|
baseUrl: options?.baseUrl,
|
|
218
218
|
concurrency: options?.concurrency ? parseInt(options.concurrency, 10) : undefined,
|
|
219
219
|
};
|
|
220
|
-
const generator = new WikiGenerator(repoPath, storagePath,
|
|
220
|
+
const generator = new WikiGenerator(repoPath, storagePath, lbugPath, llmConfig, wikiOptions, (phase, percent, detail) => {
|
|
221
221
|
const label = detail || phase;
|
|
222
222
|
if (label !== lastPhase) {
|
|
223
223
|
lastPhase = label;
|
|
@@ -1 +1,26 @@
|
|
|
1
|
+
import { type Ignore } from 'ignore';
|
|
2
|
+
import type { Path } from 'path-scurry';
|
|
1
3
|
export declare const shouldIgnorePath: (filePath: string) => boolean;
|
|
4
|
+
/** Check if a directory name is in the hardcoded ignore list */
|
|
5
|
+
export declare const isHardcodedIgnoredDirectory: (name: string) => boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Load .gitignore and .gitnexusignore rules from the repo root.
|
|
8
|
+
* Returns an `ignore` instance with all patterns, or null if no files found.
|
|
9
|
+
*/
|
|
10
|
+
export interface IgnoreOptions {
|
|
11
|
+
/** Skip .gitignore parsing, only read .gitnexusignore. Defaults to GITNEXUS_NO_GITIGNORE env var. */
|
|
12
|
+
noGitignore?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare const loadIgnoreRules: (repoPath: string, options?: IgnoreOptions) => Promise<Ignore | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Create a glob-compatible ignore filter combining:
|
|
17
|
+
* - .gitignore / .gitnexusignore patterns (via `ignore` package)
|
|
18
|
+
* - Hardcoded DEFAULT_IGNORE_LIST, IGNORED_EXTENSIONS, IGNORED_FILES
|
|
19
|
+
*
|
|
20
|
+
* Returns an IgnoreLike object for glob's `ignore` option,
|
|
21
|
+
* enabling directory-level pruning during traversal.
|
|
22
|
+
*/
|
|
23
|
+
export declare const createIgnoreFilter: (repoPath: string, options?: IgnoreOptions) => Promise<{
|
|
24
|
+
ignored(p: Path): boolean;
|
|
25
|
+
childrenIgnored(p: Path): boolean;
|
|
26
|
+
}>;
|