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
package/dist/cli/ai-context.js
CHANGED
|
@@ -24,84 +24,111 @@ const GITNEXUS_END_MARKER = '<!-- gitnexus:end -->';
|
|
|
24
24
|
* - Exact tool commands with parameters — vague directives get ignored
|
|
25
25
|
* - Self-review checklist — forces model to verify its own work
|
|
26
26
|
*/
|
|
27
|
-
function generateGitNexusContent(projectName, stats) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
##
|
|
75
|
-
|
|
76
|
-
|
|
|
77
|
-
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
|
88
|
-
|
|
89
|
-
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
|
|
104
|
-
|
|
27
|
+
function generateGitNexusContent(projectName, stats, generatedSkills) {
|
|
28
|
+
const generatedRows = (generatedSkills && generatedSkills.length > 0)
|
|
29
|
+
? generatedSkills.map(s => `| Work in the ${s.label} area (${s.symbolCount} symbols) | \`.claude/skills/generated/${s.name}/SKILL.md\` |`).join('\n')
|
|
30
|
+
: '';
|
|
31
|
+
const skillsTable = `| Task | Read this skill file |
|
|
32
|
+
|------|---------------------|
|
|
33
|
+
| Understand architecture / "How does X work?" | \`.claude/skills/gitnexus/gitnexus-exploring/SKILL.md\` |
|
|
34
|
+
| Blast radius / "What breaks if I change X?" | \`.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md\` |
|
|
35
|
+
| Trace bugs / "Why is X failing?" | \`.claude/skills/gitnexus/gitnexus-debugging/SKILL.md\` |
|
|
36
|
+
| Rename / extract / split / refactor | \`.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md\` |
|
|
37
|
+
| Tools, resources, schema reference | \`.claude/skills/gitnexus/gitnexus-guide/SKILL.md\` |
|
|
38
|
+
| Index, status, clean, wiki CLI commands | \`.claude/skills/gitnexus/gitnexus-cli/SKILL.md\` |${generatedRows ? '\n' + generatedRows : ''}`;
|
|
39
|
+
return `${GITNEXUS_START_MARKER}
|
|
40
|
+
# GitNexus — Code Intelligence
|
|
41
|
+
|
|
42
|
+
This project is indexed by GitNexus as **${projectName}** (${stats.nodes || 0} symbols, ${stats.edges || 0} relationships, ${stats.processes || 0} execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
|
43
|
+
|
|
44
|
+
> If any GitNexus tool warns the index is stale, run \`npx gitnexus analyze\` in terminal first.
|
|
45
|
+
|
|
46
|
+
## Always Do
|
|
47
|
+
|
|
48
|
+
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run \`gitnexus_impact({target: "symbolName", direction: "upstream"})\` and report the blast radius (direct callers, affected processes, risk level) to the user.
|
|
49
|
+
- **MUST run \`gitnexus_detect_changes()\` before committing** to verify your changes only affect expected symbols and execution flows.
|
|
50
|
+
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
|
|
51
|
+
- When exploring unfamiliar code, use \`gitnexus_query({query: "concept"})\` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
|
|
52
|
+
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use \`gitnexus_context({name: "symbolName"})\`.
|
|
53
|
+
|
|
54
|
+
## When Debugging
|
|
55
|
+
|
|
56
|
+
1. \`gitnexus_query({query: "<error or symptom>"})\` — find execution flows related to the issue
|
|
57
|
+
2. \`gitnexus_context({name: "<suspect function>"})\` — see all callers, callees, and process participation
|
|
58
|
+
3. \`READ gitnexus://repo/${projectName}/process/{processName}\` — trace the full execution flow step by step
|
|
59
|
+
4. For regressions: \`gitnexus_detect_changes({scope: "compare", base_ref: "main"})\` — see what your branch changed
|
|
60
|
+
|
|
61
|
+
## When Refactoring
|
|
62
|
+
|
|
63
|
+
- **Renaming**: MUST use \`gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})\` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with \`dry_run: false\`.
|
|
64
|
+
- **Extracting/Splitting**: MUST run \`gitnexus_context({name: "target"})\` to see all incoming/outgoing refs, then \`gitnexus_impact({target: "target", direction: "upstream"})\` to find all external callers before moving code.
|
|
65
|
+
- After any refactor: run \`gitnexus_detect_changes({scope: "all"})\` to verify only expected files changed.
|
|
66
|
+
|
|
67
|
+
## Never Do
|
|
68
|
+
|
|
69
|
+
- NEVER edit a function, class, or method without first running \`gitnexus_impact\` on it.
|
|
70
|
+
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
|
|
71
|
+
- NEVER rename symbols with find-and-replace — use \`gitnexus_rename\` which understands the call graph.
|
|
72
|
+
- NEVER commit changes without running \`gitnexus_detect_changes()\` to check affected scope.
|
|
73
|
+
|
|
74
|
+
## Tools Quick Reference
|
|
75
|
+
|
|
76
|
+
| Tool | When to use | Command |
|
|
77
|
+
|------|-------------|---------|
|
|
78
|
+
| \`query\` | Find code by concept | \`gitnexus_query({query: "auth validation"})\` |
|
|
79
|
+
| \`context\` | 360-degree view of one symbol | \`gitnexus_context({name: "validateUser"})\` |
|
|
80
|
+
| \`impact\` | Blast radius before editing | \`gitnexus_impact({target: "X", direction: "upstream"})\` |
|
|
81
|
+
| \`detect_changes\` | Pre-commit scope check | \`gitnexus_detect_changes({scope: "staged"})\` |
|
|
82
|
+
| \`rename\` | Safe multi-file rename | \`gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})\` |
|
|
83
|
+
| \`cypher\` | Custom graph queries | \`gitnexus_cypher({query: "MATCH ..."})\` |
|
|
84
|
+
|
|
85
|
+
## Impact Risk Levels
|
|
86
|
+
|
|
87
|
+
| Depth | Meaning | Action |
|
|
88
|
+
|-------|---------|--------|
|
|
89
|
+
| d=1 | WILL BREAK — direct callers/importers | MUST update these |
|
|
90
|
+
| d=2 | LIKELY AFFECTED — indirect deps | Should test |
|
|
91
|
+
| d=3 | MAY NEED TESTING — transitive | Test if critical path |
|
|
92
|
+
|
|
93
|
+
## Resources
|
|
94
|
+
|
|
95
|
+
| Resource | Use for |
|
|
96
|
+
|----------|---------|
|
|
97
|
+
| \`gitnexus://repo/${projectName}/context\` | Codebase overview, check index freshness |
|
|
98
|
+
| \`gitnexus://repo/${projectName}/clusters\` | All functional areas |
|
|
99
|
+
| \`gitnexus://repo/${projectName}/processes\` | All execution flows |
|
|
100
|
+
| \`gitnexus://repo/${projectName}/process/{name}\` | Step-by-step execution trace |
|
|
101
|
+
|
|
102
|
+
## Self-Check Before Finishing
|
|
103
|
+
|
|
104
|
+
Before completing any code modification task, verify:
|
|
105
|
+
1. \`gitnexus_impact\` was run for all modified symbols
|
|
106
|
+
2. No HIGH/CRITICAL risk warnings were ignored
|
|
107
|
+
3. \`gitnexus_detect_changes()\` confirms changes match expected scope
|
|
108
|
+
4. All d=1 (WILL BREAK) dependents were updated
|
|
109
|
+
|
|
110
|
+
## Keeping the Index Fresh
|
|
111
|
+
|
|
112
|
+
After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:
|
|
113
|
+
|
|
114
|
+
\`\`\`bash
|
|
115
|
+
npx gitnexus analyze
|
|
116
|
+
\`\`\`
|
|
117
|
+
|
|
118
|
+
If the index previously included embeddings, preserve them by adding \`--embeddings\`:
|
|
119
|
+
|
|
120
|
+
\`\`\`bash
|
|
121
|
+
npx gitnexus analyze --embeddings
|
|
122
|
+
\`\`\`
|
|
123
|
+
|
|
124
|
+
To check whether embeddings exist, inspect \`.gitnexus/meta.json\` — the \`stats.embeddings\` field shows the count (0 means no embeddings). **Running analyze without \`--embeddings\` will delete any previously generated embeddings.**
|
|
125
|
+
|
|
126
|
+
> Claude Code users: A PostToolUse hook handles this automatically after \`git commit\` and \`git merge\`.
|
|
127
|
+
|
|
128
|
+
## CLI
|
|
129
|
+
|
|
130
|
+
${skillsTable}
|
|
131
|
+
|
|
105
132
|
${GITNEXUS_END_MARKER}`;
|
|
106
133
|
}
|
|
107
134
|
/**
|
|
@@ -193,16 +220,16 @@ async function installSkills(repoPath) {
|
|
|
193
220
|
}
|
|
194
221
|
catch {
|
|
195
222
|
// Fallback: generate minimal skill content
|
|
196
|
-
skillContent = `---
|
|
197
|
-
name: ${skill.name}
|
|
198
|
-
description: ${skill.description}
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
# ${skill.name.charAt(0).toUpperCase() + skill.name.slice(1)}
|
|
202
|
-
|
|
203
|
-
${skill.description}
|
|
204
|
-
|
|
205
|
-
Use GitNexus tools to accomplish this task.
|
|
223
|
+
skillContent = `---
|
|
224
|
+
name: ${skill.name}
|
|
225
|
+
description: ${skill.description}
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
# ${skill.name.charAt(0).toUpperCase() + skill.name.slice(1)}
|
|
229
|
+
|
|
230
|
+
${skill.description}
|
|
231
|
+
|
|
232
|
+
Use GitNexus tools to accomplish this task.
|
|
206
233
|
`;
|
|
207
234
|
}
|
|
208
235
|
await fs.writeFile(skillPath, skillContent, 'utf-8');
|
|
@@ -218,8 +245,8 @@ Use GitNexus tools to accomplish this task.
|
|
|
218
245
|
/**
|
|
219
246
|
* Generate AI context files after indexing
|
|
220
247
|
*/
|
|
221
|
-
export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats) {
|
|
222
|
-
const content = generateGitNexusContent(projectName, stats);
|
|
248
|
+
export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats, generatedSkills) {
|
|
249
|
+
const content = generateGitNexusContent(projectName, stats, generatedSkills);
|
|
223
250
|
const createdFiles = [];
|
|
224
251
|
// Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Cline, etc.)
|
|
225
252
|
const agentsPath = path.join(repoPath, 'AGENTS.md');
|
package/dist/cli/analyze.d.ts
CHANGED
package/dist/cli/analyze.js
CHANGED
|
@@ -8,14 +8,15 @@ import { execFileSync } from 'child_process';
|
|
|
8
8
|
import v8 from 'v8';
|
|
9
9
|
import cliProgress from 'cli-progress';
|
|
10
10
|
import { runPipelineFromRepo } from '../core/ingestion/pipeline.js';
|
|
11
|
-
import {
|
|
11
|
+
import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, createFTSIndex, loadCachedEmbeddings } from '../core/lbug/lbug-adapter.js';
|
|
12
12
|
// Embedding imports are lazy (dynamic import) so onnxruntime-node is never
|
|
13
13
|
// loaded when embeddings are not requested. This avoids crashes on Node
|
|
14
14
|
// versions whose ABI is not yet supported by the native binary (#89).
|
|
15
15
|
// disposeEmbedder intentionally not called — ONNX Runtime segfaults on cleanup (see #38)
|
|
16
|
-
import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath } from '../storage/repo-manager.js';
|
|
16
|
+
import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath, cleanupOldKuzuFiles } from '../storage/repo-manager.js';
|
|
17
17
|
import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
|
|
18
18
|
import { generateAIContextFiles } from './ai-context.js';
|
|
19
|
+
import { generateSkillFiles } from './skill-gen.js';
|
|
19
20
|
import fs from 'fs/promises';
|
|
20
21
|
const HEAP_MB = 8192;
|
|
21
22
|
const HEAP_FLAG = `--max-old-space-size=${HEAP_MB}`;
|
|
@@ -50,7 +51,7 @@ const PHASE_LABELS = {
|
|
|
50
51
|
communities: 'Detecting communities',
|
|
51
52
|
processes: 'Detecting processes',
|
|
52
53
|
complete: 'Pipeline complete',
|
|
53
|
-
|
|
54
|
+
lbug: 'Loading into LadybugDB',
|
|
54
55
|
fts: 'Creating search indexes',
|
|
55
56
|
embeddings: 'Generating embeddings',
|
|
56
57
|
done: 'Done',
|
|
@@ -58,6 +59,9 @@ const PHASE_LABELS = {
|
|
|
58
59
|
export const analyzeCommand = async (inputPath, options) => {
|
|
59
60
|
if (ensureHeap())
|
|
60
61
|
return;
|
|
62
|
+
if (options?.verbose) {
|
|
63
|
+
process.env.GITNEXUS_VERBOSE = '1';
|
|
64
|
+
}
|
|
61
65
|
console.log('\n GitNexus Analyzer\n');
|
|
62
66
|
let repoPath;
|
|
63
67
|
if (inputPath) {
|
|
@@ -77,13 +81,22 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
77
81
|
process.exitCode = 1;
|
|
78
82
|
return;
|
|
79
83
|
}
|
|
80
|
-
const { storagePath,
|
|
84
|
+
const { storagePath, lbugPath } = getStoragePaths(repoPath);
|
|
85
|
+
// Clean up stale KuzuDB files from before the LadybugDB migration.
|
|
86
|
+
// If kuzu existed but lbug doesn't, we're doing a migration re-index — say so.
|
|
87
|
+
const kuzuResult = await cleanupOldKuzuFiles(storagePath);
|
|
88
|
+
if (kuzuResult.found && kuzuResult.needsReindex) {
|
|
89
|
+
console.log(' Migrating from KuzuDB to LadybugDB — rebuilding index...\n');
|
|
90
|
+
}
|
|
81
91
|
const currentCommit = getCurrentCommit(repoPath);
|
|
82
92
|
const existingMeta = await loadMeta(storagePath);
|
|
83
|
-
if (existingMeta && !options?.force && existingMeta.lastCommit === currentCommit) {
|
|
93
|
+
if (existingMeta && !options?.force && !options?.skills && existingMeta.lastCommit === currentCommit) {
|
|
84
94
|
console.log(' Already up to date\n');
|
|
85
95
|
return;
|
|
86
96
|
}
|
|
97
|
+
if (process.env.GITNEXUS_NO_GITIGNORE) {
|
|
98
|
+
console.log(' GITNEXUS_NO_GITIGNORE is set — skipping .gitignore (still reading .gitnexusignore)\n');
|
|
99
|
+
}
|
|
87
100
|
// Single progress bar for entire pipeline
|
|
88
101
|
const bar = new cliProgress.SingleBar({
|
|
89
102
|
format: ' {bar} {percentage}% | {phase}',
|
|
@@ -104,7 +117,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
104
117
|
aborted = true;
|
|
105
118
|
bar.stop();
|
|
106
119
|
console.log('\n Interrupted — cleaning up...');
|
|
107
|
-
|
|
120
|
+
closeLbug().catch(() => { }).finally(() => process.exit(130));
|
|
108
121
|
};
|
|
109
122
|
process.on('SIGINT', sigintHandler);
|
|
110
123
|
// Route all console output through bar.log() so the bar doesn't stamp itself
|
|
@@ -150,15 +163,15 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
150
163
|
if (options?.embeddings && existingMeta && !options?.force) {
|
|
151
164
|
try {
|
|
152
165
|
updateBar(0, 'Caching embeddings...');
|
|
153
|
-
await
|
|
166
|
+
await initLbug(lbugPath);
|
|
154
167
|
const cached = await loadCachedEmbeddings();
|
|
155
168
|
cachedEmbeddingNodeIds = cached.embeddingNodeIds;
|
|
156
169
|
cachedEmbeddings = cached.embeddings;
|
|
157
|
-
await
|
|
170
|
+
await closeLbug();
|
|
158
171
|
}
|
|
159
172
|
catch {
|
|
160
173
|
try {
|
|
161
|
-
await
|
|
174
|
+
await closeLbug();
|
|
162
175
|
}
|
|
163
176
|
catch { }
|
|
164
177
|
}
|
|
@@ -169,26 +182,26 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
169
182
|
const scaled = Math.round(progress.percent * 0.6);
|
|
170
183
|
updateBar(scaled, phaseLabel);
|
|
171
184
|
});
|
|
172
|
-
// ── Phase 2:
|
|
173
|
-
updateBar(60, 'Loading into
|
|
174
|
-
await
|
|
175
|
-
const
|
|
176
|
-
for (const f of
|
|
185
|
+
// ── Phase 2: LadybugDB (60–85%) ──────────────────────────────────────
|
|
186
|
+
updateBar(60, 'Loading into LadybugDB...');
|
|
187
|
+
await closeLbug();
|
|
188
|
+
const lbugFiles = [lbugPath, `${lbugPath}.wal`, `${lbugPath}.lock`];
|
|
189
|
+
for (const f of lbugFiles) {
|
|
177
190
|
try {
|
|
178
191
|
await fs.rm(f, { recursive: true, force: true });
|
|
179
192
|
}
|
|
180
193
|
catch { }
|
|
181
194
|
}
|
|
182
|
-
const
|
|
183
|
-
await
|
|
184
|
-
let
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
const progress = Math.min(84, 60 + Math.round((
|
|
195
|
+
const t0Lbug = Date.now();
|
|
196
|
+
await initLbug(lbugPath);
|
|
197
|
+
let lbugMsgCount = 0;
|
|
198
|
+
const lbugResult = await loadGraphToLbug(pipelineResult.graph, pipelineResult.repoPath, storagePath, (msg) => {
|
|
199
|
+
lbugMsgCount++;
|
|
200
|
+
const progress = Math.min(84, 60 + Math.round((lbugMsgCount / (lbugMsgCount + 10)) * 24));
|
|
188
201
|
updateBar(progress, msg);
|
|
189
202
|
});
|
|
190
|
-
const
|
|
191
|
-
const
|
|
203
|
+
const lbugTime = ((Date.now() - t0Lbug) / 1000).toFixed(1);
|
|
204
|
+
const lbugWarnings = lbugResult.warnings;
|
|
192
205
|
// ── Phase 3: FTS (85–90%) ─────────────────────────────────────────
|
|
193
206
|
updateBar(85, 'Creating search indexes...');
|
|
194
207
|
const t0Fts = Date.now();
|
|
@@ -217,7 +230,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
217
230
|
}
|
|
218
231
|
}
|
|
219
232
|
// ── Phase 4: Embeddings (90–98%) ──────────────────────────────────
|
|
220
|
-
const stats = await
|
|
233
|
+
const stats = await getLbugStats();
|
|
221
234
|
let embeddingTime = '0.0';
|
|
222
235
|
let embeddingSkipped = true;
|
|
223
236
|
let embeddingSkipReason = 'off (use --embeddings to enable)';
|
|
@@ -242,6 +255,13 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
242
255
|
}
|
|
243
256
|
// ── Phase 5: Finalize (98–100%) ───────────────────────────────────
|
|
244
257
|
updateBar(98, 'Saving metadata...');
|
|
258
|
+
// Count embeddings in the index (cached + newly generated)
|
|
259
|
+
let embeddingCount = 0;
|
|
260
|
+
try {
|
|
261
|
+
const embResult = await executeQuery(`MATCH (e:CodeEmbedding) RETURN count(e) AS cnt`);
|
|
262
|
+
embeddingCount = embResult?.[0]?.cnt ?? 0;
|
|
263
|
+
}
|
|
264
|
+
catch { /* table may not exist if embeddings never ran */ }
|
|
245
265
|
const meta = {
|
|
246
266
|
repoPath,
|
|
247
267
|
lastCommit: currentCommit,
|
|
@@ -252,6 +272,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
252
272
|
edges: stats.edges,
|
|
253
273
|
communities: pipelineResult.communityResult?.stats.totalCommunities,
|
|
254
274
|
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
275
|
+
embeddings: embeddingCount,
|
|
255
276
|
},
|
|
256
277
|
};
|
|
257
278
|
await saveMeta(storagePath, meta);
|
|
@@ -267,6 +288,12 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
267
288
|
}
|
|
268
289
|
aggregatedClusterCount = Array.from(groups.values()).filter(count => count >= 5).length;
|
|
269
290
|
}
|
|
291
|
+
let generatedSkills = [];
|
|
292
|
+
if (options?.skills && pipelineResult.communityResult) {
|
|
293
|
+
updateBar(99, 'Generating skill files...');
|
|
294
|
+
const skillResult = await generateSkillFiles(repoPath, projectName, pipelineResult);
|
|
295
|
+
generatedSkills = skillResult.skills;
|
|
296
|
+
}
|
|
270
297
|
const aiContext = await generateAIContextFiles(repoPath, storagePath, projectName, {
|
|
271
298
|
files: pipelineResult.totalFileCount,
|
|
272
299
|
nodes: stats.nodes,
|
|
@@ -274,8 +301,8 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
274
301
|
communities: pipelineResult.communityResult?.stats.totalCommunities,
|
|
275
302
|
clusters: aggregatedClusterCount,
|
|
276
303
|
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
277
|
-
});
|
|
278
|
-
await
|
|
304
|
+
}, generatedSkills);
|
|
305
|
+
await closeLbug();
|
|
279
306
|
// Note: we intentionally do NOT call disposeEmbedder() here.
|
|
280
307
|
// ONNX Runtime's native cleanup segfaults on macOS and some Linux configs.
|
|
281
308
|
// Since the process exits immediately after, Node.js reclaims everything.
|
|
@@ -291,18 +318,18 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
291
318
|
const embeddingsCached = cachedEmbeddings.length > 0;
|
|
292
319
|
console.log(`\n Repository indexed successfully (${totalTime}s)${embeddingsCached ? ` [${cachedEmbeddings.length} embeddings cached]` : ''}\n`);
|
|
293
320
|
console.log(` ${stats.nodes.toLocaleString()} nodes | ${stats.edges.toLocaleString()} edges | ${pipelineResult.communityResult?.stats.totalCommunities || 0} clusters | ${pipelineResult.processResult?.stats.totalProcesses || 0} flows`);
|
|
294
|
-
console.log(`
|
|
321
|
+
console.log(` LadybugDB ${lbugTime}s | FTS ${ftsTime}s | Embeddings ${embeddingSkipped ? embeddingSkipReason : embeddingTime + 's'}`);
|
|
295
322
|
console.log(` ${repoPath}`);
|
|
296
323
|
if (aiContext.files.length > 0) {
|
|
297
324
|
console.log(` Context: ${aiContext.files.join(', ')}`);
|
|
298
325
|
}
|
|
299
326
|
// Show a quiet summary if some edge types needed fallback insertion
|
|
300
|
-
if (
|
|
301
|
-
const totalFallback =
|
|
327
|
+
if (lbugWarnings.length > 0) {
|
|
328
|
+
const totalFallback = lbugWarnings.reduce((sum, w) => {
|
|
302
329
|
const m = w.match(/\((\d+) edges\)/);
|
|
303
330
|
return sum + (m ? parseInt(m[1]) : 0);
|
|
304
331
|
}, 0);
|
|
305
|
-
console.log(` Note: ${totalFallback} edges across ${
|
|
332
|
+
console.log(` Note: ${totalFallback} edges across ${lbugWarnings.length} types inserted via fallback (schema will be updated in next release)`);
|
|
306
333
|
}
|
|
307
334
|
try {
|
|
308
335
|
await fs.access(getGlobalRegistryPath());
|
|
@@ -311,7 +338,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
311
338
|
console.log('\n Tip: Run `gitnexus setup` to configure MCP for your editor.');
|
|
312
339
|
}
|
|
313
340
|
console.log('');
|
|
314
|
-
//
|
|
341
|
+
// LadybugDB's native module holds open handles that prevent Node from exiting.
|
|
315
342
|
// ONNX Runtime also registers native atexit hooks that segfault on some
|
|
316
343
|
// platforms (#38, #40). Force-exit to ensure clean termination.
|
|
317
344
|
process.exit(0);
|
package/dist/cli/augment.js
CHANGED
|
@@ -19,7 +19,7 @@ export async function augmentCommand(pattern) {
|
|
|
19
19
|
const result = await augment(pattern, process.cwd());
|
|
20
20
|
if (result) {
|
|
21
21
|
// IMPORTANT: Write to stderr, NOT stdout.
|
|
22
|
-
//
|
|
22
|
+
// LadybugDB's native module captures stdout fd at OS level during init,
|
|
23
23
|
// which makes stdout permanently broken in subprocess contexts.
|
|
24
24
|
// stderr is never captured, so it works reliably everywhere.
|
|
25
25
|
// The hook reads from the subprocess's stderr.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Eval Server — Lightweight HTTP server for SWE-bench evaluation
|
|
3
3
|
*
|
|
4
|
-
* Keeps
|
|
4
|
+
* Keeps LadybugDB warm in memory so tool calls from the agent are near-instant.
|
|
5
5
|
* Designed to run inside Docker containers during SWE-bench evaluation.
|
|
6
6
|
*
|
|
7
7
|
* KEY DESIGN: Returns LLM-friendly text, not raw JSON.
|
package/dist/cli/eval-server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Eval Server — Lightweight HTTP server for SWE-bench evaluation
|
|
3
3
|
*
|
|
4
|
-
* Keeps
|
|
4
|
+
* Keeps LadybugDB warm in memory so tool calls from the agent are near-instant.
|
|
5
5
|
* Designed to run inside Docker containers during SWE-bench evaluation.
|
|
6
6
|
*
|
|
7
7
|
* KEY DESIGN: Returns LLM-friendly text, not raw JSON.
|
package/dist/cli/index.js
CHANGED
|
@@ -2,18 +2,8 @@
|
|
|
2
2
|
// Heap re-spawn removed — only analyze.ts needs the 8GB heap (via its own ensureHeap()).
|
|
3
3
|
// Removing it from here improves MCP server startup time significantly.
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
-
import { analyzeCommand } from './analyze.js';
|
|
6
|
-
import { serveCommand } from './serve.js';
|
|
7
|
-
import { listCommand } from './list.js';
|
|
8
|
-
import { statusCommand } from './status.js';
|
|
9
|
-
import { mcpCommand } from './mcp.js';
|
|
10
|
-
import { cleanCommand } from './clean.js';
|
|
11
|
-
import { setupCommand } from './setup.js';
|
|
12
|
-
import { augmentCommand } from './augment.js';
|
|
13
|
-
import { wikiCommand } from './wiki.js';
|
|
14
|
-
import { queryCommand, contextCommand, impactCommand, cypherCommand } from './tool.js';
|
|
15
|
-
import { evalServerCommand } from './eval-server.js';
|
|
16
5
|
import { createRequire } from 'node:module';
|
|
6
|
+
import { createLazyAction } from './lazy-action.js';
|
|
17
7
|
const _require = createRequire(import.meta.url);
|
|
18
8
|
const pkg = _require('../../package.json');
|
|
19
9
|
const program = new Command();
|
|
@@ -24,37 +14,40 @@ program
|
|
|
24
14
|
program
|
|
25
15
|
.command('setup')
|
|
26
16
|
.description('One-time setup: configure MCP for Cursor, Claude Code, OpenCode')
|
|
27
|
-
.action(setupCommand);
|
|
17
|
+
.action(createLazyAction(() => import('./setup.js'), 'setupCommand'));
|
|
28
18
|
program
|
|
29
19
|
.command('analyze [path]')
|
|
30
20
|
.description('Index a repository (full analysis)')
|
|
31
21
|
.option('-f, --force', 'Force full re-index even if up to date')
|
|
32
22
|
.option('--embeddings', 'Enable embedding generation for semantic search (off by default)')
|
|
33
|
-
.
|
|
23
|
+
.option('--skills', 'Generate repo-specific skill files from detected communities')
|
|
24
|
+
.option('-v, --verbose', 'Enable verbose ingestion warnings (default: false)')
|
|
25
|
+
.addHelpText('after', '\nEnvironment variables:\n GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)')
|
|
26
|
+
.action(createLazyAction(() => import('./analyze.js'), 'analyzeCommand'));
|
|
34
27
|
program
|
|
35
28
|
.command('serve')
|
|
36
29
|
.description('Start local HTTP server for web UI connection')
|
|
37
30
|
.option('-p, --port <port>', 'Port number', '4747')
|
|
38
31
|
.option('--host <host>', 'Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)')
|
|
39
|
-
.action(serveCommand);
|
|
32
|
+
.action(createLazyAction(() => import('./serve.js'), 'serveCommand'));
|
|
40
33
|
program
|
|
41
34
|
.command('mcp')
|
|
42
35
|
.description('Start MCP server (stdio) — serves all indexed repos')
|
|
43
|
-
.action(mcpCommand);
|
|
36
|
+
.action(createLazyAction(() => import('./mcp.js'), 'mcpCommand'));
|
|
44
37
|
program
|
|
45
38
|
.command('list')
|
|
46
39
|
.description('List all indexed repositories')
|
|
47
|
-
.action(listCommand);
|
|
40
|
+
.action(createLazyAction(() => import('./list.js'), 'listCommand'));
|
|
48
41
|
program
|
|
49
42
|
.command('status')
|
|
50
43
|
.description('Show index status for current repo')
|
|
51
|
-
.action(statusCommand);
|
|
44
|
+
.action(createLazyAction(() => import('./status.js'), 'statusCommand'));
|
|
52
45
|
program
|
|
53
46
|
.command('clean')
|
|
54
47
|
.description('Delete GitNexus index for current repo')
|
|
55
48
|
.option('-f, --force', 'Skip confirmation prompt')
|
|
56
49
|
.option('--all', 'Clean all indexed repos')
|
|
57
|
-
.action(cleanCommand);
|
|
50
|
+
.action(createLazyAction(() => import('./clean.js'), 'cleanCommand'));
|
|
58
51
|
program
|
|
59
52
|
.command('wiki [path]')
|
|
60
53
|
.description('Generate repository wiki from knowledge graph')
|
|
@@ -64,11 +57,11 @@ program
|
|
|
64
57
|
.option('--api-key <key>', 'LLM API key (saved to ~/.gitnexus/config.json)')
|
|
65
58
|
.option('--concurrency <n>', 'Parallel LLM calls (default: 3)', '3')
|
|
66
59
|
.option('--gist', 'Publish wiki as a public GitHub Gist after generation')
|
|
67
|
-
.action(wikiCommand);
|
|
60
|
+
.action(createLazyAction(() => import('./wiki.js'), 'wikiCommand'));
|
|
68
61
|
program
|
|
69
62
|
.command('augment <pattern>')
|
|
70
63
|
.description('Augment a search pattern with knowledge graph context (used by hooks)')
|
|
71
|
-
.action(augmentCommand);
|
|
64
|
+
.action(createLazyAction(() => import('./augment.js'), 'augmentCommand'));
|
|
72
65
|
// ─── Direct Tool Commands (no MCP overhead) ────────────────────────
|
|
73
66
|
// These invoke LocalBackend directly for use in eval, scripts, and CI.
|
|
74
67
|
program
|
|
@@ -79,7 +72,7 @@ program
|
|
|
79
72
|
.option('-g, --goal <text>', 'What you want to find')
|
|
80
73
|
.option('-l, --limit <n>', 'Max processes to return (default: 5)')
|
|
81
74
|
.option('--content', 'Include full symbol source code')
|
|
82
|
-
.action(queryCommand);
|
|
75
|
+
.action(createLazyAction(() => import('./tool.js'), 'queryCommand'));
|
|
83
76
|
program
|
|
84
77
|
.command('context [name]')
|
|
85
78
|
.description('360-degree view of a code symbol: callers, callees, processes')
|
|
@@ -87,7 +80,7 @@ program
|
|
|
87
80
|
.option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
|
|
88
81
|
.option('-f, --file <path>', 'File path to disambiguate common names')
|
|
89
82
|
.option('--content', 'Include full symbol source code')
|
|
90
|
-
.action(contextCommand);
|
|
83
|
+
.action(createLazyAction(() => import('./tool.js'), 'contextCommand'));
|
|
91
84
|
program
|
|
92
85
|
.command('impact <target>')
|
|
93
86
|
.description('Blast radius analysis: what breaks if you change a symbol')
|
|
@@ -95,17 +88,17 @@ program
|
|
|
95
88
|
.option('-r, --repo <name>', 'Target repository')
|
|
96
89
|
.option('--depth <n>', 'Max relationship depth (default: 3)')
|
|
97
90
|
.option('--include-tests', 'Include test files in results')
|
|
98
|
-
.action(impactCommand);
|
|
91
|
+
.action(createLazyAction(() => import('./tool.js'), 'impactCommand'));
|
|
99
92
|
program
|
|
100
93
|
.command('cypher <query>')
|
|
101
94
|
.description('Execute raw Cypher query against the knowledge graph')
|
|
102
95
|
.option('-r, --repo <name>', 'Target repository')
|
|
103
|
-
.action(cypherCommand);
|
|
96
|
+
.action(createLazyAction(() => import('./tool.js'), 'cypherCommand'));
|
|
104
97
|
// ─── Eval Server (persistent daemon for SWE-bench) ─────────────────
|
|
105
98
|
program
|
|
106
99
|
.command('eval-server')
|
|
107
100
|
.description('Start lightweight HTTP server for fast tool calls during evaluation')
|
|
108
101
|
.option('-p, --port <port>', 'Port number', '4848')
|
|
109
102
|
.option('--idle-timeout <seconds>', 'Auto-shutdown after N seconds idle (0 = disabled)', '0')
|
|
110
|
-
.action(evalServerCommand);
|
|
103
|
+
.action(createLazyAction(() => import('./eval-server.js'), 'evalServerCommand'));
|
|
111
104
|
program.parse(process.argv);
|
|
@@ -0,0 +1,6 @@
|
|
|
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>;
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
}
|