@vohongtho.infotech/code-intel 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/main.js +3401 -237
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +386 -31
- package/dist/index.js +3940 -17
- package/dist/index.js.map +1 -1
- package/package.json +15 -12
- package/dist/call-graph/call-builder.d.ts +0 -6
- package/dist/call-graph/call-builder.d.ts.map +0 -1
- package/dist/call-graph/call-builder.js +0 -69
- package/dist/call-graph/call-builder.js.map +0 -1
- package/dist/call-graph/call-classifier.d.ts +0 -12
- package/dist/call-graph/call-classifier.d.ts.map +0 -1
- package/dist/call-graph/call-classifier.js +0 -11
- package/dist/call-graph/call-classifier.js.map +0 -1
- package/dist/call-graph/index.d.ts +0 -4
- package/dist/call-graph/index.d.ts.map +0 -1
- package/dist/call-graph/index.js +0 -3
- package/dist/call-graph/index.js.map +0 -1
- package/dist/cli/context-writer.d.ts +0 -18
- package/dist/cli/context-writer.d.ts.map +0 -1
- package/dist/cli/context-writer.js +0 -104
- package/dist/cli/context-writer.js.map +0 -1
- package/dist/cli/main.d.ts +0 -3
- package/dist/cli/main.d.ts.map +0 -1
- package/dist/cli/skill-writer.d.ts +0 -17
- package/dist/cli/skill-writer.d.ts.map +0 -1
- package/dist/cli/skill-writer.js +0 -249
- package/dist/cli/skill-writer.js.map +0 -1
- package/dist/clustering/community-detector.d.ts +0 -9
- package/dist/clustering/community-detector.d.ts.map +0 -1
- package/dist/clustering/community-detector.js +0 -67
- package/dist/clustering/community-detector.js.map +0 -1
- package/dist/clustering/index.d.ts +0 -3
- package/dist/clustering/index.d.ts.map +0 -1
- package/dist/clustering/index.js +0 -2
- package/dist/clustering/index.js.map +0 -1
- package/dist/flow-detection/entry-point-finder.d.ts +0 -14
- package/dist/flow-detection/entry-point-finder.d.ts.map +0 -1
- package/dist/flow-detection/entry-point-finder.js +0 -86
- package/dist/flow-detection/entry-point-finder.js.map +0 -1
- package/dist/flow-detection/index.d.ts +0 -3
- package/dist/flow-detection/index.d.ts.map +0 -1
- package/dist/flow-detection/index.js +0 -2
- package/dist/flow-detection/index.js.map +0 -1
- package/dist/graph/id-generator.d.ts +0 -4
- package/dist/graph/id-generator.d.ts.map +0 -1
- package/dist/graph/id-generator.js +0 -7
- package/dist/graph/id-generator.js.map +0 -1
- package/dist/graph/index.d.ts +0 -4
- package/dist/graph/index.d.ts.map +0 -1
- package/dist/graph/index.js +0 -3
- package/dist/graph/index.js.map +0 -1
- package/dist/graph/knowledge-graph.d.ts +0 -21
- package/dist/graph/knowledge-graph.d.ts.map +0 -1
- package/dist/graph/knowledge-graph.js +0 -126
- package/dist/graph/knowledge-graph.js.map +0 -1
- package/dist/http/app.d.ts +0 -5
- package/dist/http/app.d.ts.map +0 -1
- package/dist/http/app.js +0 -361
- package/dist/http/app.js.map +0 -1
- package/dist/http/index.d.ts +0 -2
- package/dist/http/index.d.ts.map +0 -1
- package/dist/http/index.js +0 -2
- package/dist/http/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/inheritance/heritage-builder.d.ts +0 -9
- package/dist/inheritance/heritage-builder.d.ts.map +0 -1
- package/dist/inheritance/heritage-builder.js +0 -40
- package/dist/inheritance/heritage-builder.js.map +0 -1
- package/dist/inheritance/index.d.ts +0 -6
- package/dist/inheritance/index.d.ts.map +0 -1
- package/dist/inheritance/index.js +0 -4
- package/dist/inheritance/index.js.map +0 -1
- package/dist/inheritance/mro-walker.d.ts +0 -3
- package/dist/inheritance/mro-walker.d.ts.map +0 -1
- package/dist/inheritance/mro-walker.js +0 -80
- package/dist/inheritance/mro-walker.js.map +0 -1
- package/dist/inheritance/override-detector.d.ts +0 -4
- package/dist/inheritance/override-detector.d.ts.map +0 -1
- package/dist/inheritance/override-detector.js +0 -39
- package/dist/inheritance/override-detector.js.map +0 -1
- package/dist/languages/index.d.ts +0 -3
- package/dist/languages/index.d.ts.map +0 -1
- package/dist/languages/index.js +0 -2
- package/dist/languages/index.js.map +0 -1
- package/dist/languages/modules/c.d.ts +0 -3
- package/dist/languages/modules/c.d.ts.map +0 -1
- package/dist/languages/modules/c.js +0 -23
- package/dist/languages/modules/c.js.map +0 -1
- package/dist/languages/modules/cpp.d.ts +0 -3
- package/dist/languages/modules/cpp.d.ts.map +0 -1
- package/dist/languages/modules/cpp.js +0 -23
- package/dist/languages/modules/cpp.js.map +0 -1
- package/dist/languages/modules/csharp.d.ts +0 -3
- package/dist/languages/modules/csharp.d.ts.map +0 -1
- package/dist/languages/modules/csharp.js +0 -21
- package/dist/languages/modules/csharp.js.map +0 -1
- package/dist/languages/modules/dart.d.ts +0 -3
- package/dist/languages/modules/dart.d.ts.map +0 -1
- package/dist/languages/modules/dart.js +0 -30
- package/dist/languages/modules/dart.js.map +0 -1
- package/dist/languages/modules/go.d.ts +0 -3
- package/dist/languages/modules/go.d.ts.map +0 -1
- package/dist/languages/modules/go.js +0 -25
- package/dist/languages/modules/go.js.map +0 -1
- package/dist/languages/modules/java.d.ts +0 -3
- package/dist/languages/modules/java.d.ts.map +0 -1
- package/dist/languages/modules/java.js +0 -23
- package/dist/languages/modules/java.js.map +0 -1
- package/dist/languages/modules/kotlin.d.ts +0 -3
- package/dist/languages/modules/kotlin.d.ts.map +0 -1
- package/dist/languages/modules/kotlin.js +0 -22
- package/dist/languages/modules/kotlin.js.map +0 -1
- package/dist/languages/modules/php.d.ts +0 -3
- package/dist/languages/modules/php.d.ts.map +0 -1
- package/dist/languages/modules/php.js +0 -21
- package/dist/languages/modules/php.js.map +0 -1
- package/dist/languages/modules/python.d.ts +0 -3
- package/dist/languages/modules/python.d.ts.map +0 -1
- package/dist/languages/modules/python.js +0 -36
- package/dist/languages/modules/python.js.map +0 -1
- package/dist/languages/modules/ruby.d.ts +0 -3
- package/dist/languages/modules/ruby.d.ts.map +0 -1
- package/dist/languages/modules/ruby.js +0 -20
- package/dist/languages/modules/ruby.js.map +0 -1
- package/dist/languages/modules/rust.d.ts +0 -3
- package/dist/languages/modules/rust.d.ts.map +0 -1
- package/dist/languages/modules/rust.js +0 -23
- package/dist/languages/modules/rust.js.map +0 -1
- package/dist/languages/modules/swift.d.ts +0 -3
- package/dist/languages/modules/swift.d.ts.map +0 -1
- package/dist/languages/modules/swift.js +0 -21
- package/dist/languages/modules/swift.js.map +0 -1
- package/dist/languages/modules/typescript.d.ts +0 -4
- package/dist/languages/modules/typescript.d.ts.map +0 -1
- package/dist/languages/modules/typescript.js +0 -47
- package/dist/languages/modules/typescript.js.map +0 -1
- package/dist/languages/registry.d.ts +0 -5
- package/dist/languages/registry.d.ts.map +0 -1
- package/dist/languages/registry.js +0 -37
- package/dist/languages/registry.js.map +0 -1
- package/dist/languages/types.d.ts +0 -18
- package/dist/languages/types.d.ts.map +0 -1
- package/dist/languages/types.js +0 -2
- package/dist/languages/types.js.map +0 -1
- package/dist/mcp-server/index.d.ts +0 -2
- package/dist/mcp-server/index.d.ts.map +0 -1
- package/dist/mcp-server/index.js +0 -2
- package/dist/mcp-server/index.js.map +0 -1
- package/dist/mcp-server/server.d.ts +0 -5
- package/dist/mcp-server/server.d.ts.map +0 -1
- package/dist/mcp-server/server.js +0 -224
- package/dist/mcp-server/server.js.map +0 -1
- package/dist/multi-repo/cross-repo-search.d.ts +0 -3
- package/dist/multi-repo/cross-repo-search.d.ts.map +0 -1
- package/dist/multi-repo/cross-repo-search.js +0 -5
- package/dist/multi-repo/cross-repo-search.js.map +0 -1
- package/dist/multi-repo/group-config.d.ts +0 -10
- package/dist/multi-repo/group-config.d.ts.map +0 -1
- package/dist/multi-repo/group-config.js +0 -54
- package/dist/multi-repo/group-config.js.map +0 -1
- package/dist/multi-repo/group-manager.d.ts +0 -6
- package/dist/multi-repo/group-manager.d.ts.map +0 -1
- package/dist/multi-repo/group-manager.js +0 -42
- package/dist/multi-repo/group-manager.js.map +0 -1
- package/dist/multi-repo/index.d.ts +0 -5
- package/dist/multi-repo/index.d.ts.map +0 -1
- package/dist/multi-repo/index.js +0 -4
- package/dist/multi-repo/index.js.map +0 -1
- package/dist/parsing/ast-cache.d.ts +0 -13
- package/dist/parsing/ast-cache.d.ts.map +0 -1
- package/dist/parsing/ast-cache.js +0 -45
- package/dist/parsing/ast-cache.js.map +0 -1
- package/dist/parsing/index.d.ts +0 -5
- package/dist/parsing/index.d.ts.map +0 -1
- package/dist/parsing/index.js +0 -4
- package/dist/parsing/index.js.map +0 -1
- package/dist/parsing/parser-manager.d.ts +0 -7
- package/dist/parsing/parser-manager.d.ts.map +0 -1
- package/dist/parsing/parser-manager.js +0 -48
- package/dist/parsing/parser-manager.js.map +0 -1
- package/dist/parsing/queries/c.d.ts +0 -2
- package/dist/parsing/queries/c.d.ts.map +0 -1
- package/dist/parsing/queries/c.js +0 -32
- package/dist/parsing/queries/c.js.map +0 -1
- package/dist/parsing/queries/cpp.d.ts +0 -2
- package/dist/parsing/queries/cpp.d.ts.map +0 -1
- package/dist/parsing/queries/cpp.js +0 -45
- package/dist/parsing/queries/cpp.js.map +0 -1
- package/dist/parsing/queries/csharp.d.ts +0 -2
- package/dist/parsing/queries/csharp.d.ts.map +0 -1
- package/dist/parsing/queries/csharp.js +0 -55
- package/dist/parsing/queries/csharp.js.map +0 -1
- package/dist/parsing/queries/go.d.ts +0 -2
- package/dist/parsing/queries/go.d.ts.map +0 -1
- package/dist/parsing/queries/go.js +0 -46
- package/dist/parsing/queries/go.js.map +0 -1
- package/dist/parsing/queries/index.d.ts +0 -13
- package/dist/parsing/queries/index.d.ts.map +0 -1
- package/dist/parsing/queries/index.js +0 -13
- package/dist/parsing/queries/index.js.map +0 -1
- package/dist/parsing/queries/java.d.ts +0 -2
- package/dist/parsing/queries/java.d.ts.map +0 -1
- package/dist/parsing/queries/java.js +0 -46
- package/dist/parsing/queries/java.js.map +0 -1
- package/dist/parsing/queries/kotlin.d.ts +0 -2
- package/dist/parsing/queries/kotlin.d.ts.map +0 -1
- package/dist/parsing/queries/kotlin.js +0 -41
- package/dist/parsing/queries/kotlin.js.map +0 -1
- package/dist/parsing/queries/php.d.ts +0 -2
- package/dist/parsing/queries/php.d.ts.map +0 -1
- package/dist/parsing/queries/php.js +0 -60
- package/dist/parsing/queries/php.js.map +0 -1
- package/dist/parsing/queries/python.d.ts +0 -2
- package/dist/parsing/queries/python.d.ts.map +0 -1
- package/dist/parsing/queries/python.js +0 -47
- package/dist/parsing/queries/python.js.map +0 -1
- package/dist/parsing/queries/ruby.d.ts +0 -2
- package/dist/parsing/queries/ruby.d.ts.map +0 -1
- package/dist/parsing/queries/ruby.js +0 -36
- package/dist/parsing/queries/ruby.js.map +0 -1
- package/dist/parsing/queries/rust.d.ts +0 -2
- package/dist/parsing/queries/rust.d.ts.map +0 -1
- package/dist/parsing/queries/rust.js +0 -47
- package/dist/parsing/queries/rust.js.map +0 -1
- package/dist/parsing/queries/swift.d.ts +0 -2
- package/dist/parsing/queries/swift.d.ts.map +0 -1
- package/dist/parsing/queries/swift.js +0 -39
- package/dist/parsing/queries/swift.js.map +0 -1
- package/dist/parsing/queries/typescript.d.ts +0 -2
- package/dist/parsing/queries/typescript.d.ts.map +0 -1
- package/dist/parsing/queries/typescript.js +0 -84
- package/dist/parsing/queries/typescript.js.map +0 -1
- package/dist/parsing/query-runner.d.ts +0 -8
- package/dist/parsing/query-runner.d.ts.map +0 -1
- package/dist/parsing/query-runner.js +0 -16
- package/dist/parsing/query-runner.js.map +0 -1
- package/dist/pipeline/dag-validator.d.ts +0 -8
- package/dist/pipeline/dag-validator.d.ts.map +0 -1
- package/dist/pipeline/dag-validator.js +0 -88
- package/dist/pipeline/dag-validator.js.map +0 -1
- package/dist/pipeline/index.d.ts +0 -6
- package/dist/pipeline/index.d.ts.map +0 -1
- package/dist/pipeline/index.js +0 -4
- package/dist/pipeline/index.js.map +0 -1
- package/dist/pipeline/orchestrator.d.ts +0 -8
- package/dist/pipeline/orchestrator.d.ts.map +0 -1
- package/dist/pipeline/orchestrator.js +0 -47
- package/dist/pipeline/orchestrator.js.map +0 -1
- package/dist/pipeline/phases/cluster-phase.d.ts +0 -3
- package/dist/pipeline/phases/cluster-phase.d.ts.map +0 -1
- package/dist/pipeline/phases/cluster-phase.js +0 -52
- package/dist/pipeline/phases/cluster-phase.js.map +0 -1
- package/dist/pipeline/phases/flow-phase.d.ts +0 -3
- package/dist/pipeline/phases/flow-phase.d.ts.map +0 -1
- package/dist/pipeline/phases/flow-phase.js +0 -92
- package/dist/pipeline/phases/flow-phase.js.map +0 -1
- package/dist/pipeline/phases/index.d.ts +0 -6
- package/dist/pipeline/phases/index.d.ts.map +0 -1
- package/dist/pipeline/phases/index.js +0 -6
- package/dist/pipeline/phases/index.js.map +0 -1
- package/dist/pipeline/phases/parse-phase.d.ts +0 -3
- package/dist/pipeline/phases/parse-phase.d.ts.map +0 -1
- package/dist/pipeline/phases/parse-phase.js +0 -288
- package/dist/pipeline/phases/parse-phase.js.map +0 -1
- package/dist/pipeline/phases/resolve-phase.d.ts +0 -3
- package/dist/pipeline/phases/resolve-phase.d.ts.map +0 -1
- package/dist/pipeline/phases/resolve-phase.js +0 -388
- package/dist/pipeline/phases/resolve-phase.js.map +0 -1
- package/dist/pipeline/phases/scan-phase.d.ts +0 -4
- package/dist/pipeline/phases/scan-phase.d.ts.map +0 -1
- package/dist/pipeline/phases/scan-phase.js +0 -119
- package/dist/pipeline/phases/scan-phase.js.map +0 -1
- package/dist/pipeline/types.d.ts +0 -19
- package/dist/pipeline/types.d.ts.map +0 -1
- package/dist/pipeline/types.js +0 -2
- package/dist/pipeline/types.js.map +0 -1
- package/dist/resolver/binding-tracker.d.ts +0 -15
- package/dist/resolver/binding-tracker.d.ts.map +0 -1
- package/dist/resolver/binding-tracker.js +0 -22
- package/dist/resolver/binding-tracker.js.map +0 -1
- package/dist/resolver/import-resolver.d.ts +0 -16
- package/dist/resolver/import-resolver.d.ts.map +0 -1
- package/dist/resolver/import-resolver.js +0 -43
- package/dist/resolver/import-resolver.js.map +0 -1
- package/dist/resolver/index.d.ts +0 -5
- package/dist/resolver/index.d.ts.map +0 -1
- package/dist/resolver/index.js +0 -3
- package/dist/resolver/index.js.map +0 -1
- package/dist/resolver/strategies/namespace-alias.d.ts +0 -3
- package/dist/resolver/strategies/namespace-alias.d.ts.map +0 -1
- package/dist/resolver/strategies/namespace-alias.js +0 -15
- package/dist/resolver/strategies/namespace-alias.js.map +0 -1
- package/dist/resolver/strategies/package-lookup.d.ts +0 -3
- package/dist/resolver/strategies/package-lookup.d.ts.map +0 -1
- package/dist/resolver/strategies/package-lookup.js +0 -10
- package/dist/resolver/strategies/package-lookup.js.map +0 -1
- package/dist/resolver/strategies/relative-path.d.ts +0 -3
- package/dist/resolver/strategies/relative-path.d.ts.map +0 -1
- package/dist/resolver/strategies/relative-path.js +0 -12
- package/dist/resolver/strategies/relative-path.js.map +0 -1
- package/dist/resolver/strategies/types.d.ts +0 -11
- package/dist/resolver/strategies/types.d.ts.map +0 -1
- package/dist/resolver/strategies/types.js +0 -2
- package/dist/resolver/strategies/types.js.map +0 -1
- package/dist/resolver/strategies/wildcard-expand.d.ts +0 -3
- package/dist/resolver/strategies/wildcard-expand.d.ts.map +0 -1
- package/dist/resolver/strategies/wildcard-expand.js +0 -8
- package/dist/resolver/strategies/wildcard-expand.js.map +0 -1
- package/dist/scope-analysis/index.d.ts +0 -3
- package/dist/scope-analysis/index.d.ts.map +0 -1
- package/dist/scope-analysis/index.js +0 -2
- package/dist/scope-analysis/index.js.map +0 -1
- package/dist/scope-analysis/scope-builder.d.ts +0 -16
- package/dist/scope-analysis/scope-builder.d.ts.map +0 -1
- package/dist/scope-analysis/scope-builder.js +0 -19
- package/dist/scope-analysis/scope-builder.js.map +0 -1
- package/dist/search/embedder.d.ts +0 -14
- package/dist/search/embedder.d.ts.map +0 -1
- package/dist/search/embedder.js +0 -45
- package/dist/search/embedder.js.map +0 -1
- package/dist/search/index.d.ts +0 -7
- package/dist/search/index.d.ts.map +0 -1
- package/dist/search/index.js +0 -4
- package/dist/search/index.js.map +0 -1
- package/dist/search/text-search.d.ts +0 -12
- package/dist/search/text-search.d.ts.map +0 -1
- package/dist/search/text-search.js +0 -72
- package/dist/search/text-search.js.map +0 -1
- package/dist/search/vector-index.d.ts +0 -18
- package/dist/search/vector-index.d.ts.map +0 -1
- package/dist/search/vector-index.js +0 -70
- package/dist/search/vector-index.js.map +0 -1
- package/dist/shared/detection.d.ts +0 -4
- package/dist/shared/detection.d.ts.map +0 -1
- package/dist/shared/detection.js +0 -38
- package/dist/shared/detection.js.map +0 -1
- package/dist/shared/graph-types.d.ts +0 -22
- package/dist/shared/graph-types.d.ts.map +0 -1
- package/dist/shared/graph-types.js +0 -2
- package/dist/shared/graph-types.js.map +0 -1
- package/dist/shared/index.d.ts +0 -5
- package/dist/shared/index.d.ts.map +0 -1
- package/dist/shared/index.js +0 -3
- package/dist/shared/index.js.map +0 -1
- package/dist/shared/languages.d.ts +0 -17
- package/dist/shared/languages.d.ts.map +0 -1
- package/dist/shared/languages.js +0 -18
- package/dist/shared/languages.js.map +0 -1
- package/dist/shared/pipeline-types.d.ts +0 -21
- package/dist/shared/pipeline-types.d.ts.map +0 -1
- package/dist/shared/pipeline-types.js +0 -2
- package/dist/shared/pipeline-types.js.map +0 -1
- package/dist/storage/csv-writer.d.ts +0 -9
- package/dist/storage/csv-writer.d.ts.map +0 -1
- package/dist/storage/csv-writer.js +0 -77
- package/dist/storage/csv-writer.js.map +0 -1
- package/dist/storage/db-manager.d.ts +0 -12
- package/dist/storage/db-manager.d.ts.map +0 -1
- package/dist/storage/db-manager.js +0 -50
- package/dist/storage/db-manager.js.map +0 -1
- package/dist/storage/graph-loader.d.ts +0 -7
- package/dist/storage/graph-loader.d.ts.map +0 -1
- package/dist/storage/graph-loader.js +0 -71
- package/dist/storage/graph-loader.js.map +0 -1
- package/dist/storage/index.d.ts +0 -10
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js +0 -7
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/metadata.d.ts +0 -15
- package/dist/storage/metadata.d.ts.map +0 -1
- package/dist/storage/metadata.js +0 -23
- package/dist/storage/metadata.js.map +0 -1
- package/dist/storage/repo-registry.d.ts +0 -15
- package/dist/storage/repo-registry.d.ts.map +0 -1
- package/dist/storage/repo-registry.js +0 -34
- package/dist/storage/repo-registry.js.map +0 -1
- package/dist/storage/schema.d.ts +0 -6
- package/dist/storage/schema.d.ts.map +0 -1
- package/dist/storage/schema.js +0 -54
- package/dist/storage/schema.js.map +0 -1
package/dist/cli/main.js
CHANGED
|
@@ -1,282 +1,3446 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { Database, Connection } from '@ladybugdb/core';
|
|
3
|
+
import path, { dirname, join } from 'path';
|
|
4
|
+
import fs8, { readFileSync } from 'fs';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
2
7
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
8
|
+
import express from 'express';
|
|
9
|
+
import cors from 'cors';
|
|
10
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
|
|
14
|
+
var __defProp = Object.defineProperty;
|
|
15
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
16
|
+
var __esm = (fn, res) => function __init() {
|
|
17
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
22
|
+
};
|
|
23
|
+
var DbManager;
|
|
24
|
+
var init_db_manager = __esm({
|
|
25
|
+
"src/storage/db-manager.ts"() {
|
|
26
|
+
DbManager = class {
|
|
27
|
+
db = null;
|
|
28
|
+
conn = null;
|
|
29
|
+
dbPath;
|
|
30
|
+
constructor(dbPath) {
|
|
31
|
+
this.dbPath = dbPath;
|
|
32
|
+
}
|
|
33
|
+
async init() {
|
|
34
|
+
fs8.mkdirSync(path.dirname(this.dbPath), { recursive: true });
|
|
35
|
+
this.db = new Database(this.dbPath);
|
|
36
|
+
await this.db.init();
|
|
37
|
+
this.conn = new Connection(this.db);
|
|
38
|
+
await this.conn.init();
|
|
39
|
+
}
|
|
40
|
+
async query(cypher) {
|
|
41
|
+
if (!this.conn) throw new Error("Database not initialized");
|
|
42
|
+
const result = await this.conn.query(cypher);
|
|
43
|
+
const qr = Array.isArray(result) ? result[0] : result;
|
|
44
|
+
const rows = await qr.getAll();
|
|
45
|
+
qr.close();
|
|
46
|
+
return rows;
|
|
47
|
+
}
|
|
48
|
+
async execute(cypher) {
|
|
49
|
+
if (!this.conn) throw new Error("Database not initialized");
|
|
50
|
+
const result = await this.conn.query(cypher);
|
|
51
|
+
const qr = Array.isArray(result) ? result[0] : result;
|
|
52
|
+
qr.close();
|
|
53
|
+
}
|
|
54
|
+
close() {
|
|
55
|
+
try {
|
|
56
|
+
this.conn?.close();
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
this.db?.close();
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
this.conn = null;
|
|
64
|
+
this.db = null;
|
|
65
|
+
}
|
|
66
|
+
get isOpen() {
|
|
67
|
+
return this.conn !== null;
|
|
68
|
+
}
|
|
31
69
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// src/storage/schema.ts
|
|
74
|
+
function getCreateNodeTableDDL(tableName) {
|
|
75
|
+
return `CREATE NODE TABLE IF NOT EXISTS ${tableName} (
|
|
76
|
+
id STRING,
|
|
77
|
+
name STRING,
|
|
78
|
+
file_path STRING,
|
|
79
|
+
start_line INT64,
|
|
80
|
+
end_line INT64,
|
|
81
|
+
exported BOOLEAN,
|
|
82
|
+
content STRING,
|
|
83
|
+
metadata STRING,
|
|
84
|
+
PRIMARY KEY (id)
|
|
85
|
+
)`;
|
|
86
|
+
}
|
|
87
|
+
function getCreateEdgeTableDDL() {
|
|
88
|
+
const ddls = [];
|
|
89
|
+
const uniqueTables = ALL_NODE_TABLES;
|
|
90
|
+
const fromToPairs = [];
|
|
91
|
+
for (const from of uniqueTables) {
|
|
92
|
+
for (const to of uniqueTables) {
|
|
93
|
+
fromToPairs.push(`FROM ${from} TO ${to}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
ddls.push(`CREATE REL TABLE IF NOT EXISTS code_edges (
|
|
97
|
+
${fromToPairs.join(",\n ")},
|
|
98
|
+
kind STRING,
|
|
99
|
+
weight DOUBLE,
|
|
100
|
+
label STRING
|
|
101
|
+
)`);
|
|
102
|
+
return ddls;
|
|
103
|
+
}
|
|
104
|
+
var NODE_TABLE_MAP, ALL_NODE_TABLES;
|
|
105
|
+
var init_schema = __esm({
|
|
106
|
+
"src/storage/schema.ts"() {
|
|
107
|
+
NODE_TABLE_MAP = {
|
|
108
|
+
file: "file_nodes",
|
|
109
|
+
directory: "dir_nodes",
|
|
110
|
+
function: "func_nodes",
|
|
111
|
+
class: "class_nodes",
|
|
112
|
+
interface: "iface_nodes",
|
|
113
|
+
method: "method_nodes",
|
|
114
|
+
constructor: "ctor_nodes",
|
|
115
|
+
variable: "var_nodes",
|
|
116
|
+
property: "prop_nodes",
|
|
117
|
+
struct: "struct_nodes",
|
|
118
|
+
enum: "enum_nodes",
|
|
119
|
+
trait: "trait_nodes",
|
|
120
|
+
namespace: "ns_nodes",
|
|
121
|
+
module: "mod_nodes",
|
|
122
|
+
type_alias: "type_nodes",
|
|
123
|
+
constant: "const_nodes",
|
|
124
|
+
route: "route_nodes",
|
|
125
|
+
cluster: "cluster_nodes",
|
|
126
|
+
flow: "flow_nodes"
|
|
127
|
+
};
|
|
128
|
+
ALL_NODE_TABLES = [...new Set(Object.values(NODE_TABLE_MAP))];
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
function writeNodeCSVs(graph, outputDir) {
|
|
132
|
+
fs8.mkdirSync(outputDir, { recursive: true });
|
|
133
|
+
const tableFiles = /* @__PURE__ */ new Map();
|
|
134
|
+
const tableFilePaths = /* @__PURE__ */ new Map();
|
|
135
|
+
const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
|
|
136
|
+
for (const node of graph.allNodes()) {
|
|
137
|
+
const table = NODE_TABLE_MAP[node.kind];
|
|
138
|
+
if (!tableFiles.has(table)) {
|
|
139
|
+
const filePath = path.join(outputDir, `${table}.csv`);
|
|
140
|
+
const stream2 = fs8.createWriteStream(filePath);
|
|
141
|
+
stream2.write(header);
|
|
142
|
+
tableFiles.set(table, stream2);
|
|
143
|
+
tableFilePaths.set(table, filePath);
|
|
144
|
+
}
|
|
145
|
+
const stream = tableFiles.get(table);
|
|
146
|
+
stream.write(csvRow([
|
|
147
|
+
node.id,
|
|
148
|
+
node.name,
|
|
149
|
+
node.filePath,
|
|
150
|
+
String(node.startLine ?? ""),
|
|
151
|
+
String(node.endLine ?? ""),
|
|
152
|
+
String(node.exported ?? false),
|
|
153
|
+
(node.content ?? "").slice(0, 1e3),
|
|
154
|
+
node.metadata ? JSON.stringify(node.metadata) : ""
|
|
155
|
+
]) + "\n");
|
|
156
|
+
}
|
|
157
|
+
for (const stream of tableFiles.values()) {
|
|
158
|
+
stream.end();
|
|
159
|
+
}
|
|
160
|
+
return tableFilePaths;
|
|
161
|
+
}
|
|
162
|
+
function writeEdgeCSV(graph, outputDir) {
|
|
163
|
+
fs8.mkdirSync(outputDir, { recursive: true });
|
|
164
|
+
const groups = /* @__PURE__ */ new Map();
|
|
165
|
+
const header = "from_id,to_id,kind,weight,label\n";
|
|
166
|
+
for (const edge of graph.allEdges()) {
|
|
167
|
+
const sourceNode = graph.getNode(edge.source);
|
|
168
|
+
const targetNode = graph.getNode(edge.target);
|
|
169
|
+
if (!sourceNode || !targetNode) continue;
|
|
170
|
+
const fromTable = NODE_TABLE_MAP[sourceNode.kind];
|
|
171
|
+
const toTable = NODE_TABLE_MAP[targetNode.kind];
|
|
172
|
+
const key = `${fromTable}->${toTable}`;
|
|
173
|
+
if (!groups.has(key)) {
|
|
174
|
+
const filePath = path.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
|
|
175
|
+
const stream = fs8.createWriteStream(filePath);
|
|
176
|
+
stream.write(header);
|
|
177
|
+
groups.set(key, { stream, filePath, from: fromTable, to: toTable });
|
|
178
|
+
}
|
|
179
|
+
const group = groups.get(key);
|
|
180
|
+
group.stream.write(csvRow([
|
|
181
|
+
edge.source,
|
|
182
|
+
edge.target,
|
|
183
|
+
edge.kind,
|
|
184
|
+
String(edge.weight ?? 1),
|
|
185
|
+
edge.label ?? ""
|
|
186
|
+
]) + "\n");
|
|
187
|
+
}
|
|
188
|
+
const result = [];
|
|
189
|
+
for (const group of groups.values()) {
|
|
190
|
+
group.stream.end();
|
|
191
|
+
result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
function csvRow(fields) {
|
|
196
|
+
return fields.map((f) => {
|
|
197
|
+
if (f.includes(",") || f.includes('"') || f.includes("\n")) {
|
|
198
|
+
return '"' + f.replace(/"/g, '""') + '"';
|
|
199
|
+
}
|
|
200
|
+
return f;
|
|
201
|
+
}).join(",");
|
|
202
|
+
}
|
|
203
|
+
var init_csv_writer = __esm({
|
|
204
|
+
"src/storage/csv-writer.ts"() {
|
|
205
|
+
init_schema();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// src/storage/graph-loader.ts
|
|
210
|
+
async function loadGraphToDB(graph, dbManager) {
|
|
211
|
+
for (const table of ALL_NODE_TABLES) {
|
|
212
|
+
await dbManager.execute(getCreateNodeTableDDL(table));
|
|
213
|
+
}
|
|
214
|
+
const edgeDDLs = getCreateEdgeTableDDL();
|
|
215
|
+
for (const ddl of edgeDDLs) {
|
|
56
216
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
217
|
+
await dbManager.execute(ddl);
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
let nodeCount = 0;
|
|
222
|
+
for (const node of graph.allNodes()) {
|
|
223
|
+
const table = NODE_TABLE_MAP[node.kind];
|
|
224
|
+
const props = buildNodeProps(node);
|
|
225
|
+
try {
|
|
226
|
+
await dbManager.execute(`CREATE (:${table} ${props})`);
|
|
227
|
+
nodeCount++;
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
let edgeCount = 0;
|
|
232
|
+
for (const edge of graph.allEdges()) {
|
|
233
|
+
const sourceNode = graph.getNode(edge.source);
|
|
234
|
+
const targetNode = graph.getNode(edge.target);
|
|
235
|
+
if (!sourceNode || !targetNode) continue;
|
|
236
|
+
const fromTable = NODE_TABLE_MAP[sourceNode.kind];
|
|
237
|
+
const toTable = NODE_TABLE_MAP[targetNode.kind];
|
|
238
|
+
try {
|
|
239
|
+
await dbManager.execute(
|
|
240
|
+
`MATCH (a:${fromTable} {id: '${escCypher(edge.source)}'}), (b:${toTable} {id: '${escCypher(edge.target)}'}) CREATE (a)-[:code_edges {kind: '${edge.kind}', weight: ${edge.weight ?? 1}, label: '${escCypher(edge.label ?? "")}'}]->(b)`
|
|
241
|
+
);
|
|
242
|
+
edgeCount++;
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return { nodeCount, edgeCount };
|
|
247
|
+
}
|
|
248
|
+
function buildNodeProps(node) {
|
|
249
|
+
const parts = [
|
|
250
|
+
`id: '${escCypher(node.id)}'`,
|
|
251
|
+
`name: '${escCypher(node.name)}'`,
|
|
252
|
+
`file_path: '${escCypher(node.filePath)}'`
|
|
253
|
+
];
|
|
254
|
+
if (node.startLine !== void 0) parts.push(`start_line: ${node.startLine}`);
|
|
255
|
+
if (node.endLine !== void 0) parts.push(`end_line: ${node.endLine}`);
|
|
256
|
+
if (node.exported !== void 0) parts.push(`exported: ${node.exported}`);
|
|
257
|
+
if (node.content) parts.push(`content: '${escCypher(node.content.slice(0, 500))}'`);
|
|
258
|
+
if (node.metadata) parts.push(`metadata: '${escCypher(JSON.stringify(node.metadata))}'`);
|
|
259
|
+
return `{${parts.join(", ")}}`;
|
|
260
|
+
}
|
|
261
|
+
function escCypher(s) {
|
|
262
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
|
|
263
|
+
}
|
|
264
|
+
var init_graph_loader = __esm({
|
|
265
|
+
"src/storage/graph-loader.ts"() {
|
|
266
|
+
init_schema();
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
function loadRegistry() {
|
|
270
|
+
try {
|
|
271
|
+
const data = fs8.readFileSync(REPOS_FILE, "utf-8");
|
|
272
|
+
return JSON.parse(data);
|
|
273
|
+
} catch {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function saveRegistry(entries) {
|
|
278
|
+
fs8.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
279
|
+
fs8.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
|
|
280
|
+
}
|
|
281
|
+
function upsertRepo(entry) {
|
|
282
|
+
const entries = loadRegistry();
|
|
283
|
+
const idx = entries.findIndex((e) => e.path === entry.path);
|
|
284
|
+
if (idx >= 0) {
|
|
285
|
+
entries[idx] = entry;
|
|
286
|
+
} else {
|
|
287
|
+
entries.push(entry);
|
|
288
|
+
}
|
|
289
|
+
saveRegistry(entries);
|
|
290
|
+
}
|
|
291
|
+
function removeRepo(repoPath) {
|
|
292
|
+
const entries = loadRegistry().filter((e) => e.path !== repoPath);
|
|
293
|
+
saveRegistry(entries);
|
|
294
|
+
}
|
|
295
|
+
var GLOBAL_DIR, REPOS_FILE;
|
|
296
|
+
var init_repo_registry = __esm({
|
|
297
|
+
"src/storage/repo-registry.ts"() {
|
|
298
|
+
GLOBAL_DIR = path.join(os.homedir(), ".code-intel");
|
|
299
|
+
REPOS_FILE = path.join(GLOBAL_DIR, "repos.json");
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
function saveMetadata(repoDir, metadata) {
|
|
303
|
+
const metaDir = path.join(repoDir, ".code-intel");
|
|
304
|
+
fs8.mkdirSync(metaDir, { recursive: true });
|
|
305
|
+
fs8.writeFileSync(path.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
|
|
306
|
+
}
|
|
307
|
+
function loadMetadata(repoDir) {
|
|
308
|
+
try {
|
|
309
|
+
const data = fs8.readFileSync(path.join(repoDir, ".code-intel", "meta.json"), "utf-8");
|
|
310
|
+
return JSON.parse(data);
|
|
311
|
+
} catch {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function getDbPath(repoDir) {
|
|
316
|
+
return path.join(repoDir, ".code-intel", "graph.db");
|
|
317
|
+
}
|
|
318
|
+
function getVectorDbPath(repoDir) {
|
|
319
|
+
return path.join(repoDir, ".code-intel", "vector.db");
|
|
320
|
+
}
|
|
321
|
+
var init_metadata = __esm({
|
|
322
|
+
"src/storage/metadata.ts"() {
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// src/storage/index.ts
|
|
327
|
+
var storage_exports = {};
|
|
328
|
+
__export(storage_exports, {
|
|
329
|
+
ALL_NODE_TABLES: () => ALL_NODE_TABLES,
|
|
330
|
+
DbManager: () => DbManager,
|
|
331
|
+
NODE_TABLE_MAP: () => NODE_TABLE_MAP,
|
|
332
|
+
getCreateEdgeTableDDL: () => getCreateEdgeTableDDL,
|
|
333
|
+
getCreateNodeTableDDL: () => getCreateNodeTableDDL,
|
|
334
|
+
getDbPath: () => getDbPath,
|
|
335
|
+
getVectorDbPath: () => getVectorDbPath,
|
|
336
|
+
loadGraphToDB: () => loadGraphToDB,
|
|
337
|
+
loadMetadata: () => loadMetadata,
|
|
338
|
+
loadRegistry: () => loadRegistry,
|
|
339
|
+
removeRepo: () => removeRepo,
|
|
340
|
+
saveMetadata: () => saveMetadata,
|
|
341
|
+
saveRegistry: () => saveRegistry,
|
|
342
|
+
upsertRepo: () => upsertRepo,
|
|
343
|
+
writeEdgeCSV: () => writeEdgeCSV,
|
|
344
|
+
writeNodeCSVs: () => writeNodeCSVs
|
|
345
|
+
});
|
|
346
|
+
var init_storage = __esm({
|
|
347
|
+
"src/storage/index.ts"() {
|
|
348
|
+
init_db_manager();
|
|
349
|
+
init_schema();
|
|
350
|
+
init_csv_writer();
|
|
351
|
+
init_graph_loader();
|
|
352
|
+
init_repo_registry();
|
|
353
|
+
init_metadata();
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// src/search/vector-index.ts
|
|
358
|
+
var vector_index_exports = {};
|
|
359
|
+
__export(vector_index_exports, {
|
|
360
|
+
VectorIndex: () => VectorIndex
|
|
361
|
+
});
|
|
362
|
+
function esc(s) {
|
|
363
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
|
|
364
|
+
}
|
|
365
|
+
var EMBED_TABLE, EMBED_DIM, INDEX_NAME, VectorIndex;
|
|
366
|
+
var init_vector_index = __esm({
|
|
367
|
+
"src/search/vector-index.ts"() {
|
|
368
|
+
EMBED_TABLE = "embed_nodes";
|
|
369
|
+
EMBED_DIM = 384;
|
|
370
|
+
INDEX_NAME = "embed_vec_idx";
|
|
371
|
+
VectorIndex = class {
|
|
372
|
+
db;
|
|
373
|
+
constructor(db) {
|
|
374
|
+
this.db = db;
|
|
375
|
+
}
|
|
376
|
+
async init() {
|
|
377
|
+
await this.db.execute("INSTALL VECTOR");
|
|
378
|
+
await this.db.execute("LOAD EXTENSION VECTOR");
|
|
379
|
+
await this.db.execute(`
|
|
380
|
+
CREATE NODE TABLE IF NOT EXISTS ${EMBED_TABLE} (
|
|
381
|
+
id STRING,
|
|
382
|
+
name STRING,
|
|
383
|
+
kind STRING,
|
|
384
|
+
file_path STRING,
|
|
385
|
+
text STRING,
|
|
386
|
+
embedding FLOAT[${EMBED_DIM}],
|
|
387
|
+
PRIMARY KEY (id)
|
|
388
|
+
)
|
|
389
|
+
`);
|
|
390
|
+
}
|
|
391
|
+
async buildIndex(nodes) {
|
|
392
|
+
await this.db.execute(`MATCH (n:${EMBED_TABLE}) DETACH DELETE n`).catch(() => {
|
|
393
|
+
});
|
|
394
|
+
for (const node of nodes) {
|
|
395
|
+
const vecLiteral = `[${node.embedding.join(",")}]`;
|
|
396
|
+
await this.db.execute(
|
|
397
|
+
`CREATE (:${EMBED_TABLE} {
|
|
398
|
+
id: '${esc(node.id)}',
|
|
399
|
+
name: '${esc(node.name)}',
|
|
400
|
+
kind: '${esc(node.kind)}',
|
|
401
|
+
file_path: '${esc(node.filePath)}',
|
|
402
|
+
text: '${esc(node.text)}',
|
|
403
|
+
embedding: ${vecLiteral}
|
|
404
|
+
})`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
await this.db.execute(`CALL DROP_VECTOR_INDEX('${EMBED_TABLE}', '${INDEX_NAME}')`).catch(() => {
|
|
408
|
+
});
|
|
409
|
+
await this.db.execute(`CALL CREATE_VECTOR_INDEX('${EMBED_TABLE}', '${INDEX_NAME}', 'embedding')`);
|
|
410
|
+
}
|
|
411
|
+
async search(queryEmbedding, topK = 10) {
|
|
412
|
+
const vecLiteral = `[${queryEmbedding.join(",")}]`;
|
|
413
|
+
const rows = await this.db.query(
|
|
414
|
+
`CALL QUERY_VECTOR_INDEX('${EMBED_TABLE}', '${INDEX_NAME}', ${vecLiteral}, ${topK}) RETURN node.id, node.name, node.kind, node.file_path, distance`
|
|
415
|
+
);
|
|
416
|
+
return rows.map((r) => ({
|
|
417
|
+
nodeId: String(r["node.id"]),
|
|
418
|
+
name: String(r["node.name"]),
|
|
419
|
+
kind: String(r["node.kind"]),
|
|
420
|
+
filePath: String(r["node.file_path"]),
|
|
421
|
+
score: 1 - Number(r["distance"])
|
|
422
|
+
// cosine distance → similarity
|
|
423
|
+
}));
|
|
424
|
+
}
|
|
425
|
+
async isBuilt() {
|
|
426
|
+
try {
|
|
427
|
+
const rows = await this.db.query(`MATCH (n:${EMBED_TABLE}) RETURN count(n) AS cnt`);
|
|
428
|
+
return Number(rows[0]?.cnt ?? 0) > 0;
|
|
429
|
+
} catch {
|
|
430
|
+
return false;
|
|
64
431
|
}
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// src/multi-repo/group-registry.ts
|
|
438
|
+
var group_registry_exports = {};
|
|
439
|
+
__export(group_registry_exports, {
|
|
440
|
+
addMember: () => addMember,
|
|
441
|
+
deleteGroup: () => deleteGroup,
|
|
442
|
+
groupExists: () => groupExists,
|
|
443
|
+
listGroups: () => listGroups,
|
|
444
|
+
loadGroup: () => loadGroup,
|
|
445
|
+
loadSyncResult: () => loadSyncResult,
|
|
446
|
+
removeMember: () => removeMember,
|
|
447
|
+
saveGroup: () => saveGroup,
|
|
448
|
+
saveSyncResult: () => saveSyncResult
|
|
449
|
+
});
|
|
450
|
+
function groupFile(name) {
|
|
451
|
+
return path.join(GROUPS_DIR, `${name}.json`);
|
|
452
|
+
}
|
|
453
|
+
function loadGroup(name) {
|
|
454
|
+
try {
|
|
455
|
+
return JSON.parse(fs8.readFileSync(groupFile(name), "utf-8"));
|
|
456
|
+
} catch {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function saveGroup(group) {
|
|
461
|
+
fs8.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
462
|
+
fs8.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
|
|
463
|
+
}
|
|
464
|
+
function listGroups() {
|
|
465
|
+
const groups = [];
|
|
466
|
+
try {
|
|
467
|
+
for (const file of fs8.readdirSync(GROUPS_DIR)) {
|
|
468
|
+
if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
|
|
469
|
+
try {
|
|
470
|
+
const g = JSON.parse(
|
|
471
|
+
fs8.readFileSync(path.join(GROUPS_DIR, file), "utf-8")
|
|
472
|
+
);
|
|
473
|
+
groups.push(g);
|
|
474
|
+
} catch {
|
|
475
|
+
}
|
|
65
476
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
return groups;
|
|
480
|
+
}
|
|
481
|
+
function deleteGroup(name) {
|
|
482
|
+
try {
|
|
483
|
+
fs8.unlinkSync(groupFile(name));
|
|
484
|
+
} catch {
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
fs8.unlinkSync(path.join(GROUPS_DIR, `${name}.sync.json`));
|
|
488
|
+
} catch {
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function groupExists(name) {
|
|
492
|
+
return fs8.existsSync(groupFile(name));
|
|
493
|
+
}
|
|
494
|
+
function addMember(groupName, member) {
|
|
495
|
+
const group = loadGroup(groupName);
|
|
496
|
+
if (!group) throw new Error(`Group "${groupName}" not found.`);
|
|
497
|
+
const idx = group.members.findIndex((m) => m.groupPath === member.groupPath);
|
|
498
|
+
if (idx >= 0) {
|
|
499
|
+
group.members[idx] = member;
|
|
500
|
+
} else {
|
|
501
|
+
group.members.push(member);
|
|
502
|
+
}
|
|
503
|
+
saveGroup(group);
|
|
504
|
+
return group;
|
|
505
|
+
}
|
|
506
|
+
function removeMember(groupName, groupPath) {
|
|
507
|
+
const group = loadGroup(groupName);
|
|
508
|
+
if (!group) throw new Error(`Group "${groupName}" not found.`);
|
|
509
|
+
const before = group.members.length;
|
|
510
|
+
group.members = group.members.filter((m) => m.groupPath !== groupPath);
|
|
511
|
+
if (group.members.length === before) {
|
|
512
|
+
throw new Error(`No member at path "${groupPath}" in group "${groupName}".`);
|
|
513
|
+
}
|
|
514
|
+
saveGroup(group);
|
|
515
|
+
return group;
|
|
516
|
+
}
|
|
517
|
+
function saveSyncResult(result) {
|
|
518
|
+
fs8.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
519
|
+
fs8.writeFileSync(
|
|
520
|
+
path.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
521
|
+
JSON.stringify(result, null, 2) + "\n"
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
function loadSyncResult(groupName) {
|
|
525
|
+
try {
|
|
526
|
+
return JSON.parse(
|
|
527
|
+
fs8.readFileSync(path.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
|
|
528
|
+
);
|
|
529
|
+
} catch {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
var GROUPS_DIR;
|
|
534
|
+
var init_group_registry = __esm({
|
|
535
|
+
"src/multi-repo/group-registry.ts"() {
|
|
536
|
+
GROUPS_DIR = path.join(os.homedir(), ".code-intel", "groups");
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// src/search/embedder.ts
|
|
541
|
+
var embedder_exports = {};
|
|
542
|
+
__export(embedder_exports, {
|
|
543
|
+
embedNodes: () => embedNodes
|
|
544
|
+
});
|
|
545
|
+
async function getEmbedder() {
|
|
546
|
+
if (!pipelineInstance) {
|
|
547
|
+
const { pipeline } = await import('@huggingface/transformers');
|
|
548
|
+
pipelineInstance = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
|
|
549
|
+
}
|
|
550
|
+
return pipelineInstance;
|
|
551
|
+
}
|
|
552
|
+
async function embedNodes(graph, opts = {}) {
|
|
553
|
+
const { batchSize = 32, onProgress } = opts;
|
|
554
|
+
const candidates = [];
|
|
555
|
+
for (const node of graph.allNodes()) {
|
|
556
|
+
if (["cluster", "directory", "flow"].includes(node.kind)) continue;
|
|
557
|
+
const text = buildText(node);
|
|
558
|
+
candidates.push({ id: node.id, name: node.name, kind: node.kind, filePath: node.filePath, text });
|
|
559
|
+
}
|
|
560
|
+
const embedder = await getEmbedder();
|
|
561
|
+
const results = [];
|
|
562
|
+
for (let i = 0; i < candidates.length; i += batchSize) {
|
|
563
|
+
const batch = candidates.slice(i, i + batchSize);
|
|
564
|
+
const texts = batch.map((c) => c.text);
|
|
565
|
+
for (let j = 0; j < texts.length; j++) {
|
|
566
|
+
const out = await embedder(texts[j], { pooling: "mean", normalize: true });
|
|
567
|
+
results.push({ ...batch[j], embedding: Array.from(out.data) });
|
|
568
|
+
}
|
|
569
|
+
onProgress?.(Math.min(i + batchSize, candidates.length), candidates.length);
|
|
570
|
+
}
|
|
571
|
+
return results;
|
|
572
|
+
}
|
|
573
|
+
function buildText(node) {
|
|
574
|
+
const parts = [`${node.kind} ${node.name}`];
|
|
575
|
+
const sig = node.metadata?.signature;
|
|
576
|
+
if (sig) parts.push(sig);
|
|
577
|
+
if (node.content) parts.push(node.content.slice(0, 256));
|
|
578
|
+
parts.push(node.filePath);
|
|
579
|
+
return parts.join(" ").slice(0, 512);
|
|
580
|
+
}
|
|
581
|
+
var pipelineInstance;
|
|
582
|
+
var init_embedder = __esm({
|
|
583
|
+
"src/search/embedder.ts"() {
|
|
584
|
+
pipelineInstance = null;
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// src/graph/knowledge-graph.ts
|
|
589
|
+
function createKnowledgeGraph() {
|
|
590
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
591
|
+
const edges = /* @__PURE__ */ new Map();
|
|
592
|
+
const edgesByKind = /* @__PURE__ */ new Map();
|
|
593
|
+
const edgesFromNode = /* @__PURE__ */ new Map();
|
|
594
|
+
const edgesToNode = /* @__PURE__ */ new Map();
|
|
595
|
+
function indexEdge(edge) {
|
|
596
|
+
let kindSet = edgesByKind.get(edge.kind);
|
|
597
|
+
if (!kindSet) {
|
|
598
|
+
kindSet = /* @__PURE__ */ new Set();
|
|
599
|
+
edgesByKind.set(edge.kind, kindSet);
|
|
600
|
+
}
|
|
601
|
+
kindSet.add(edge.id);
|
|
602
|
+
let fromSet = edgesFromNode.get(edge.source);
|
|
603
|
+
if (!fromSet) {
|
|
604
|
+
fromSet = /* @__PURE__ */ new Set();
|
|
605
|
+
edgesFromNode.set(edge.source, fromSet);
|
|
606
|
+
}
|
|
607
|
+
fromSet.add(edge.id);
|
|
608
|
+
let toSet = edgesToNode.get(edge.target);
|
|
609
|
+
if (!toSet) {
|
|
610
|
+
toSet = /* @__PURE__ */ new Set();
|
|
611
|
+
edgesToNode.set(edge.target, toSet);
|
|
612
|
+
}
|
|
613
|
+
toSet.add(edge.id);
|
|
614
|
+
}
|
|
615
|
+
function unindexEdge(edge) {
|
|
616
|
+
edgesByKind.get(edge.kind)?.delete(edge.id);
|
|
617
|
+
edgesFromNode.get(edge.source)?.delete(edge.id);
|
|
618
|
+
edgesToNode.get(edge.target)?.delete(edge.id);
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
addNode(node) {
|
|
622
|
+
nodes.set(node.id, node);
|
|
623
|
+
},
|
|
624
|
+
addEdge(edge) {
|
|
625
|
+
edges.set(edge.id, edge);
|
|
626
|
+
indexEdge(edge);
|
|
627
|
+
},
|
|
628
|
+
getNode(id) {
|
|
629
|
+
return nodes.get(id);
|
|
630
|
+
},
|
|
631
|
+
getEdge(id) {
|
|
632
|
+
return edges.get(id);
|
|
633
|
+
},
|
|
634
|
+
*findEdgesByKind(kind) {
|
|
635
|
+
const ids = edgesByKind.get(kind);
|
|
636
|
+
if (!ids) return;
|
|
637
|
+
for (const id of ids) {
|
|
638
|
+
const edge = edges.get(id);
|
|
639
|
+
if (edge) yield edge;
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
*findEdgesFrom(sourceId) {
|
|
643
|
+
const ids = edgesFromNode.get(sourceId);
|
|
644
|
+
if (!ids) return;
|
|
645
|
+
for (const id of ids) {
|
|
646
|
+
const edge = edges.get(id);
|
|
647
|
+
if (edge) yield edge;
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
*findEdgesTo(targetId) {
|
|
651
|
+
const ids = edgesToNode.get(targetId);
|
|
652
|
+
if (!ids) return;
|
|
653
|
+
for (const id of ids) {
|
|
654
|
+
const edge = edges.get(id);
|
|
655
|
+
if (edge) yield edge;
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
removeNodeCascade(id) {
|
|
659
|
+
const fromEdges = edgesFromNode.get(id);
|
|
660
|
+
if (fromEdges) {
|
|
661
|
+
for (const edgeId of [...fromEdges]) {
|
|
662
|
+
const edge = edges.get(edgeId);
|
|
663
|
+
if (edge) {
|
|
664
|
+
unindexEdge(edge);
|
|
665
|
+
edges.delete(edgeId);
|
|
666
|
+
}
|
|
69
667
|
}
|
|
668
|
+
}
|
|
669
|
+
const toEdges = edgesToNode.get(id);
|
|
670
|
+
if (toEdges) {
|
|
671
|
+
for (const edgeId of [...toEdges]) {
|
|
672
|
+
const edge = edges.get(edgeId);
|
|
673
|
+
if (edge) {
|
|
674
|
+
unindexEdge(edge);
|
|
675
|
+
edges.delete(edgeId);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
edgesFromNode.delete(id);
|
|
680
|
+
edgesToNode.delete(id);
|
|
681
|
+
nodes.delete(id);
|
|
682
|
+
},
|
|
683
|
+
removeEdge(id) {
|
|
684
|
+
const edge = edges.get(id);
|
|
685
|
+
if (edge) {
|
|
686
|
+
unindexEdge(edge);
|
|
687
|
+
edges.delete(id);
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
*allNodes() {
|
|
691
|
+
yield* nodes.values();
|
|
692
|
+
},
|
|
693
|
+
*allEdges() {
|
|
694
|
+
yield* edges.values();
|
|
695
|
+
},
|
|
696
|
+
get size() {
|
|
697
|
+
return { nodes: nodes.size, edges: edges.size };
|
|
698
|
+
},
|
|
699
|
+
clear() {
|
|
700
|
+
nodes.clear();
|
|
701
|
+
edges.clear();
|
|
702
|
+
edgesByKind.clear();
|
|
703
|
+
edgesFromNode.clear();
|
|
704
|
+
edgesToNode.clear();
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/pipeline/dag-validator.ts
|
|
710
|
+
function validateDAG(phases) {
|
|
711
|
+
const errors = [];
|
|
712
|
+
const names = /* @__PURE__ */ new Set();
|
|
713
|
+
for (const phase of phases) {
|
|
714
|
+
if (names.has(phase.name)) {
|
|
715
|
+
errors.push({ type: "duplicate", message: `Duplicate phase name: ${phase.name}` });
|
|
716
|
+
}
|
|
717
|
+
names.add(phase.name);
|
|
718
|
+
}
|
|
719
|
+
for (const phase of phases) {
|
|
720
|
+
for (const dep of phase.dependencies) {
|
|
721
|
+
if (!names.has(dep)) {
|
|
722
|
+
errors.push({
|
|
723
|
+
type: "missing-dep",
|
|
724
|
+
message: `Phase "${phase.name}" depends on missing phase "${dep}"`
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
730
|
+
const visited = /* @__PURE__ */ new Set();
|
|
731
|
+
const phaseMap = new Map(phases.map((p) => [p.name, p]));
|
|
732
|
+
function dfs(name, path15) {
|
|
733
|
+
if (visiting.has(name)) {
|
|
734
|
+
const cycleStart = path15.indexOf(name);
|
|
735
|
+
const cycle = path15.slice(cycleStart).concat(name);
|
|
736
|
+
errors.push({ type: "cycle", message: `Cycle detected: ${cycle.join(" \u2192 ")}` });
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
if (visited.has(name)) return false;
|
|
740
|
+
visiting.add(name);
|
|
741
|
+
path15.push(name);
|
|
742
|
+
const phase = phaseMap.get(name);
|
|
743
|
+
if (phase) {
|
|
744
|
+
for (const dep of phase.dependencies) {
|
|
745
|
+
if (dfs(dep, path15)) return true;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
visiting.delete(name);
|
|
749
|
+
visited.add(name);
|
|
750
|
+
path15.pop();
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
for (const phase of phases) {
|
|
754
|
+
if (!visited.has(phase.name)) {
|
|
755
|
+
dfs(phase.name, []);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return errors;
|
|
759
|
+
}
|
|
760
|
+
function topologicalSort(phases) {
|
|
761
|
+
const phaseMap = new Map(phases.map((p) => [p.name, p]));
|
|
762
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
763
|
+
const adjList = /* @__PURE__ */ new Map();
|
|
764
|
+
for (const phase of phases) {
|
|
765
|
+
inDegree.set(phase.name, 0);
|
|
766
|
+
adjList.set(phase.name, []);
|
|
767
|
+
}
|
|
768
|
+
for (const phase of phases) {
|
|
769
|
+
for (const dep of phase.dependencies) {
|
|
770
|
+
adjList.get(dep)?.push(phase.name);
|
|
771
|
+
inDegree.set(phase.name, (inDegree.get(phase.name) ?? 0) + 1);
|
|
70
772
|
}
|
|
71
|
-
|
|
72
|
-
|
|
773
|
+
}
|
|
774
|
+
const queue = [];
|
|
775
|
+
for (const [name, degree] of inDegree) {
|
|
776
|
+
if (degree === 0) queue.push(name);
|
|
777
|
+
}
|
|
778
|
+
const sorted = [];
|
|
779
|
+
while (queue.length > 0) {
|
|
780
|
+
const current = queue.shift();
|
|
781
|
+
sorted.push(phaseMap.get(current));
|
|
782
|
+
for (const neighbor of adjList.get(current) ?? []) {
|
|
783
|
+
const newDegree = (inDegree.get(neighbor) ?? 1) - 1;
|
|
784
|
+
inDegree.set(neighbor, newDegree);
|
|
785
|
+
if (newDegree === 0) queue.push(neighbor);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return sorted;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/pipeline/orchestrator.ts
|
|
792
|
+
async function runPipeline(phases, context) {
|
|
793
|
+
const errors = validateDAG(phases);
|
|
794
|
+
if (errors.length > 0) {
|
|
795
|
+
throw new Error(`Pipeline validation failed:
|
|
796
|
+
${errors.map((e) => e.message).join("\n")}`);
|
|
797
|
+
}
|
|
798
|
+
const sorted = topologicalSort(phases);
|
|
799
|
+
const results = /* @__PURE__ */ new Map();
|
|
800
|
+
const startTime = Date.now();
|
|
801
|
+
let success = true;
|
|
802
|
+
for (const phase of sorted) {
|
|
803
|
+
context.onProgress?.(phase.name, "running");
|
|
804
|
+
const phaseStart = Date.now();
|
|
73
805
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
806
|
+
const depResults = /* @__PURE__ */ new Map();
|
|
807
|
+
for (const dep of phase.dependencies) {
|
|
808
|
+
const depResult = results.get(dep);
|
|
809
|
+
if (depResult) depResults.set(dep, depResult);
|
|
810
|
+
}
|
|
811
|
+
const result = await phase.execute(context, depResults);
|
|
812
|
+
results.set(phase.name, result);
|
|
813
|
+
context.onProgress?.(phase.name, result.status);
|
|
814
|
+
if (result.status === "failed") {
|
|
815
|
+
success = false;
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
} catch (err) {
|
|
819
|
+
const result = {
|
|
820
|
+
status: "failed",
|
|
821
|
+
duration: Date.now() - phaseStart,
|
|
822
|
+
message: err instanceof Error ? err.message : String(err)
|
|
823
|
+
};
|
|
824
|
+
results.set(phase.name, result);
|
|
825
|
+
context.onProgress?.(phase.name, "failed");
|
|
826
|
+
success = false;
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
success,
|
|
832
|
+
results,
|
|
833
|
+
totalDuration: Date.now() - startTime
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// src/shared/detection.ts
|
|
838
|
+
var EXTENSION_MAP = {
|
|
839
|
+
".ts": "typescript" /* TypeScript */,
|
|
840
|
+
".tsx": "typescript" /* TypeScript */,
|
|
841
|
+
".mts": "typescript" /* TypeScript */,
|
|
842
|
+
".cts": "typescript" /* TypeScript */,
|
|
843
|
+
".js": "javascript" /* JavaScript */,
|
|
844
|
+
".jsx": "javascript" /* JavaScript */,
|
|
845
|
+
".mjs": "javascript" /* JavaScript */,
|
|
846
|
+
".cjs": "javascript" /* JavaScript */,
|
|
847
|
+
".py": "python" /* Python */,
|
|
848
|
+
".pyi": "python" /* Python */,
|
|
849
|
+
".java": "java" /* Java */,
|
|
850
|
+
".go": "go" /* Go */,
|
|
851
|
+
".c": "c" /* C */,
|
|
852
|
+
".h": "c" /* C */,
|
|
853
|
+
".cpp": "cpp" /* Cpp */,
|
|
854
|
+
".cxx": "cpp" /* Cpp */,
|
|
855
|
+
".cc": "cpp" /* Cpp */,
|
|
856
|
+
".hpp": "cpp" /* Cpp */,
|
|
857
|
+
".hxx": "cpp" /* Cpp */,
|
|
858
|
+
".cs": "csharp" /* CSharp */,
|
|
859
|
+
".rs": "rust" /* Rust */,
|
|
860
|
+
".php": "php" /* PHP */,
|
|
861
|
+
".kt": "kotlin" /* Kotlin */,
|
|
862
|
+
".kts": "kotlin" /* Kotlin */,
|
|
863
|
+
".rb": "ruby" /* Ruby */,
|
|
864
|
+
".swift": "swift" /* Swift */,
|
|
865
|
+
".dart": "dart" /* Dart */
|
|
866
|
+
};
|
|
867
|
+
function detectLanguage(filePath) {
|
|
868
|
+
const ext = filePath.slice(filePath.lastIndexOf("."));
|
|
869
|
+
return EXTENSION_MAP[ext] ?? null;
|
|
870
|
+
}
|
|
871
|
+
function getSupportedExtensions() {
|
|
872
|
+
return Object.keys(EXTENSION_MAP);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// src/graph/id-generator.ts
|
|
876
|
+
function generateNodeId(kind, filePath, qualifiedName) {
|
|
877
|
+
return `${kind}:${filePath}:${qualifiedName}`;
|
|
878
|
+
}
|
|
879
|
+
function generateEdgeId(source, target, kind) {
|
|
880
|
+
return `${kind}:${source}->${target}`;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/pipeline/phases/scan-phase.ts
|
|
884
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
885
|
+
"node_modules",
|
|
886
|
+
".git",
|
|
887
|
+
".svn",
|
|
888
|
+
".hg",
|
|
889
|
+
"dist",
|
|
890
|
+
"dist-tests",
|
|
891
|
+
"build",
|
|
892
|
+
"out",
|
|
893
|
+
"__pycache__",
|
|
894
|
+
".tox",
|
|
895
|
+
".pytest_cache",
|
|
896
|
+
".mypy_cache",
|
|
897
|
+
"vendor",
|
|
898
|
+
"target",
|
|
899
|
+
".code-intel",
|
|
900
|
+
"coverage",
|
|
901
|
+
".next",
|
|
902
|
+
".turbo",
|
|
903
|
+
".cache",
|
|
904
|
+
"tmp",
|
|
905
|
+
"temp",
|
|
906
|
+
".parcel-cache"
|
|
907
|
+
]);
|
|
908
|
+
function loadIgnorePatterns(workspaceRoot) {
|
|
909
|
+
try {
|
|
910
|
+
const raw = fs8.readFileSync(path.join(workspaceRoot, ".codeintelignore"), "utf-8");
|
|
911
|
+
const extras = /* @__PURE__ */ new Set();
|
|
912
|
+
for (const line of raw.split("\n")) {
|
|
913
|
+
const trimmed = line.trim();
|
|
914
|
+
if (trimmed && !trimmed.startsWith("#")) extras.add(trimmed);
|
|
915
|
+
}
|
|
916
|
+
return extras;
|
|
917
|
+
} catch {
|
|
918
|
+
return /* @__PURE__ */ new Set();
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
var scanPhase = {
|
|
922
|
+
name: "scan",
|
|
923
|
+
dependencies: [],
|
|
924
|
+
async execute(context) {
|
|
925
|
+
const start = Date.now();
|
|
926
|
+
const extensions = new Set(getSupportedExtensions());
|
|
927
|
+
const filePaths = [];
|
|
928
|
+
const extraIgnore = loadIgnorePatterns(context.workspaceRoot);
|
|
929
|
+
function walk(dir) {
|
|
930
|
+
let entries;
|
|
931
|
+
try {
|
|
932
|
+
entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
933
|
+
} catch {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
for (const entry of entries) {
|
|
937
|
+
if (entry.name.startsWith(".") && entry.isDirectory()) continue;
|
|
938
|
+
if (IGNORED_DIRS.has(entry.name) && entry.isDirectory()) continue;
|
|
939
|
+
if (extraIgnore.has(entry.name) && entry.isDirectory()) continue;
|
|
940
|
+
const fullPath = path.join(dir, entry.name);
|
|
941
|
+
if (entry.isDirectory()) {
|
|
942
|
+
walk(fullPath);
|
|
943
|
+
} else if (entry.isFile()) {
|
|
944
|
+
const ext = path.extname(entry.name);
|
|
945
|
+
const fullName = entry.name;
|
|
946
|
+
if (fullName.endsWith(".d.ts") || fullName.endsWith(".js.map") || fullName.endsWith(".d.ts.map")) continue;
|
|
947
|
+
if (extensions.has(ext)) {
|
|
948
|
+
filePaths.push(fullPath);
|
|
949
|
+
}
|
|
78
950
|
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
walk(context.workspaceRoot);
|
|
954
|
+
context.filePaths.push(...filePaths);
|
|
955
|
+
return {
|
|
956
|
+
status: "completed",
|
|
957
|
+
duration: Date.now() - start,
|
|
958
|
+
message: `Found ${filePaths.length} source files`
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
var structurePhase = {
|
|
963
|
+
name: "structure",
|
|
964
|
+
dependencies: ["scan"],
|
|
965
|
+
async execute(context) {
|
|
966
|
+
const start = Date.now();
|
|
967
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
968
|
+
for (const filePath of context.filePaths) {
|
|
969
|
+
const relativePath = path.relative(context.workspaceRoot, filePath);
|
|
970
|
+
const lang = detectLanguage(filePath);
|
|
971
|
+
context.graph.addNode({
|
|
972
|
+
id: generateNodeId("file", relativePath, relativePath),
|
|
973
|
+
kind: "file",
|
|
974
|
+
name: path.basename(filePath),
|
|
975
|
+
filePath: relativePath,
|
|
976
|
+
metadata: lang ? { language: lang } : void 0
|
|
977
|
+
});
|
|
978
|
+
let dir = path.dirname(relativePath);
|
|
979
|
+
while (dir && dir !== "." && dir !== "") {
|
|
980
|
+
if (dirs.has(dir)) break;
|
|
981
|
+
dirs.add(dir);
|
|
982
|
+
dir = path.dirname(dir);
|
|
983
|
+
}
|
|
79
984
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
985
|
+
for (const dir of dirs) {
|
|
986
|
+
context.graph.addNode({
|
|
987
|
+
id: generateNodeId("directory", dir, dir),
|
|
988
|
+
kind: "directory",
|
|
989
|
+
name: path.basename(dir),
|
|
990
|
+
filePath: dir
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
return {
|
|
994
|
+
status: "completed",
|
|
995
|
+
duration: Date.now() - start,
|
|
996
|
+
message: `Created ${context.filePaths.length} file nodes, ${dirs.size} directory nodes`
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
var parsePhase = {
|
|
1001
|
+
name: "parse",
|
|
1002
|
+
dependencies: ["structure"],
|
|
1003
|
+
async execute(context) {
|
|
1004
|
+
const start = Date.now();
|
|
1005
|
+
let symbolCount = 0;
|
|
1006
|
+
for (const filePath of context.filePaths) {
|
|
1007
|
+
const lang = detectLanguage(filePath);
|
|
1008
|
+
if (!lang) {
|
|
1009
|
+
if (context.verbose) {
|
|
1010
|
+
const relativePath2 = path.relative(context.workspaceRoot, filePath);
|
|
1011
|
+
console.log(` [parse] skipped (no parser): ${relativePath2}`);
|
|
83
1012
|
}
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
const relativePath = path.relative(context.workspaceRoot, filePath);
|
|
1016
|
+
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
1017
|
+
let source;
|
|
1018
|
+
try {
|
|
1019
|
+
source = fs8.readFileSync(filePath, "utf-8");
|
|
1020
|
+
} catch {
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
const fileNode = context.graph.getNode(fileNodeId);
|
|
1024
|
+
if (fileNode) {
|
|
1025
|
+
fileNode.content = source.slice(0, 2e3);
|
|
1026
|
+
}
|
|
1027
|
+
const nodes = [];
|
|
1028
|
+
const edges = [];
|
|
1029
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1030
|
+
const lines = source.split("\n");
|
|
1031
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1032
|
+
const line = lines[i];
|
|
1033
|
+
const trimmed = line.trim();
|
|
1034
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*") || trimmed.startsWith("/*")) continue;
|
|
1035
|
+
const extracted = extractSymbol(trimmed, lang);
|
|
1036
|
+
if (!extracted) continue;
|
|
1037
|
+
if (seen.has(extracted.name + ":" + extracted.kind)) continue;
|
|
1038
|
+
seen.add(extracted.name + ":" + extracted.kind);
|
|
1039
|
+
const nodeId = generateNodeId(extracted.kind, relativePath, extracted.name);
|
|
1040
|
+
nodes.push({
|
|
1041
|
+
id: nodeId,
|
|
1042
|
+
kind: extracted.kind,
|
|
1043
|
+
name: extracted.name,
|
|
1044
|
+
filePath: relativePath,
|
|
1045
|
+
startLine: i + 1,
|
|
1046
|
+
exported: extracted.exported,
|
|
1047
|
+
content: extractBlock(lines, i, 20)
|
|
1048
|
+
});
|
|
1049
|
+
edges.push({
|
|
1050
|
+
id: generateEdgeId(fileNodeId, nodeId, "contains"),
|
|
1051
|
+
source: fileNodeId,
|
|
1052
|
+
target: nodeId,
|
|
1053
|
+
kind: "contains",
|
|
1054
|
+
weight: 1
|
|
1055
|
+
});
|
|
1056
|
+
if (extracted.ownerName) {
|
|
1057
|
+
const ownerId = generateNodeId("class", relativePath, extracted.ownerName);
|
|
1058
|
+
if (context.graph.getNode(ownerId) || nodes.some((n) => n.id === ownerId)) {
|
|
1059
|
+
edges.push({
|
|
1060
|
+
id: generateEdgeId(ownerId, nodeId, "has_member"),
|
|
1061
|
+
source: ownerId,
|
|
1062
|
+
target: nodeId,
|
|
1063
|
+
kind: "has_member",
|
|
1064
|
+
weight: 1
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
symbolCount++;
|
|
1069
|
+
}
|
|
1070
|
+
for (const n of nodes) context.graph.addNode(n);
|
|
1071
|
+
for (const e of edges) context.graph.addEdge(e);
|
|
84
1072
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
1073
|
+
return {
|
|
1074
|
+
status: "completed",
|
|
1075
|
+
duration: Date.now() - start,
|
|
1076
|
+
message: `Extracted ${symbolCount} symbols`
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
function extractSymbol(line, lang, _lineNum, _filePath) {
|
|
1081
|
+
if (lang === "typescript" /* TypeScript */ || lang === "javascript" /* JavaScript */) {
|
|
1082
|
+
const func = line.match(/^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(\w+)/);
|
|
1083
|
+
if (func) return { kind: "function", name: func[1], exported: line.includes("export") };
|
|
1084
|
+
const arrowFunc = line.match(/^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(/);
|
|
1085
|
+
if (arrowFunc) return { kind: "function", name: arrowFunc[1], exported: line.includes("export") };
|
|
1086
|
+
const arrowFunc2 = line.match(/^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_]\w*)\s*=>/);
|
|
1087
|
+
if (arrowFunc2) return { kind: "function", name: arrowFunc2[1], exported: line.includes("export") };
|
|
1088
|
+
const cls = line.match(/^(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+(\w+)/);
|
|
1089
|
+
if (cls) return { kind: "class", name: cls[1], exported: line.includes("export") };
|
|
1090
|
+
const iface = line.match(/^(?:export\s+)?interface\s+(\w+)/);
|
|
1091
|
+
if (iface) return { kind: "interface", name: iface[1], exported: line.includes("export") };
|
|
1092
|
+
const enumM = line.match(/^(?:export\s+)?enum\s+(\w+)/);
|
|
1093
|
+
if (enumM) return { kind: "enum", name: enumM[1], exported: line.includes("export") };
|
|
1094
|
+
const typeAlias = line.match(/^(?:export\s+)?type\s+(\w+)\s*[=<]/);
|
|
1095
|
+
if (typeAlias) return { kind: "type_alias", name: typeAlias[1], exported: line.includes("export") };
|
|
1096
|
+
const constVar = line.match(/^(?:export\s+)?const\s+(\w+)\s*(?::\s*\w[^=]*)?\s*=/);
|
|
1097
|
+
if (constVar && /^[A-Z_]+$/.test(constVar[1])) {
|
|
1098
|
+
return { kind: "constant", name: constVar[1], exported: line.includes("export") };
|
|
1099
|
+
}
|
|
1100
|
+
const method = line.match(/^(?:(?:public|private|protected|static|async|readonly)\s+)*(\w+)\s*\(/);
|
|
1101
|
+
if (method && !["if", "for", "while", "switch", "catch", "return", "constructor"].includes(method[1])) {
|
|
1102
|
+
if (method[1] === "constructor") {
|
|
1103
|
+
return { kind: "constructor", name: "constructor", exported: false };
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
if (lang === "python" /* Python */) {
|
|
1108
|
+
const func = line.match(/^(?:async\s+)?def\s+(\w+)/);
|
|
1109
|
+
if (func) return { kind: func[1].startsWith("__") ? "method" : "function", name: func[1], exported: !func[1].startsWith("_") };
|
|
1110
|
+
const cls = line.match(/^class\s+(\w+)/);
|
|
1111
|
+
if (cls) return { kind: "class", name: cls[1], exported: !cls[1].startsWith("_") };
|
|
1112
|
+
}
|
|
1113
|
+
if (lang === "java" /* Java */) {
|
|
1114
|
+
const cls = line.match(/(?:public|private|protected)?\s*(?:static\s+)?(?:abstract\s+)?(?:final\s+)?class\s+(\w+)/);
|
|
1115
|
+
if (cls) return { kind: "class", name: cls[1], exported: line.includes("public") };
|
|
1116
|
+
const iface = line.match(/(?:public\s+)?interface\s+(\w+)/);
|
|
1117
|
+
if (iface) return { kind: "interface", name: iface[1], exported: line.includes("public") };
|
|
1118
|
+
const enumM = line.match(/(?:public\s+)?enum\s+(\w+)/);
|
|
1119
|
+
if (enumM) return { kind: "enum", name: enumM[1], exported: line.includes("public") };
|
|
1120
|
+
const method = line.match(/(?:public|private|protected)\s+(?:static\s+)?(?:[\w<>\[\]]+)\s+(\w+)\s*\(/);
|
|
1121
|
+
if (method) return { kind: "method", name: method[1], exported: line.includes("public") };
|
|
1122
|
+
}
|
|
1123
|
+
if (lang === "go" /* Go */) {
|
|
1124
|
+
const func = line.match(/^func\s+(\w+)\s*\(/);
|
|
1125
|
+
if (func) return { kind: "function", name: func[1], exported: func[1][0] === func[1][0].toUpperCase() };
|
|
1126
|
+
const method = line.match(/^func\s+\([^)]+\)\s+(\w+)\s*\(/);
|
|
1127
|
+
if (method) return { kind: "method", name: method[1], exported: method[1][0] === method[1][0].toUpperCase() };
|
|
1128
|
+
const structM = line.match(/^type\s+(\w+)\s+struct\b/);
|
|
1129
|
+
if (structM) return { kind: "struct", name: structM[1], exported: structM[1][0] === structM[1][0].toUpperCase() };
|
|
1130
|
+
const ifaceM = line.match(/^type\s+(\w+)\s+interface\b/);
|
|
1131
|
+
if (ifaceM) return { kind: "interface", name: ifaceM[1], exported: ifaceM[1][0] === ifaceM[1][0].toUpperCase() };
|
|
1132
|
+
}
|
|
1133
|
+
if (lang === "rust" /* Rust */) {
|
|
1134
|
+
const func = line.match(/^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/);
|
|
1135
|
+
if (func) return { kind: "function", name: func[1], exported: line.startsWith("pub") };
|
|
1136
|
+
const structM = line.match(/^(?:pub\s+)?struct\s+(\w+)/);
|
|
1137
|
+
if (structM) return { kind: "struct", name: structM[1], exported: line.startsWith("pub") };
|
|
1138
|
+
const enumM = line.match(/^(?:pub\s+)?enum\s+(\w+)/);
|
|
1139
|
+
if (enumM) return { kind: "enum", name: enumM[1], exported: line.startsWith("pub") };
|
|
1140
|
+
const traitM = line.match(/^(?:pub\s+)?trait\s+(\w+)/);
|
|
1141
|
+
if (traitM) return { kind: "trait", name: traitM[1], exported: line.startsWith("pub") };
|
|
1142
|
+
const implM = line.match(/^impl(?:<[^>]*>)?\s+(\w+)/);
|
|
1143
|
+
if (implM) return { kind: "class", name: implM[1], exported: false };
|
|
1144
|
+
}
|
|
1145
|
+
if (lang === "c" /* C */ || lang === "cpp" /* Cpp */) {
|
|
1146
|
+
const cls = line.match(/^(?:class|struct)\s+(\w+)/);
|
|
1147
|
+
if (cls) return { kind: lang === "cpp" /* Cpp */ ? "class" : "struct", name: cls[1], exported: true };
|
|
1148
|
+
const nsM = line.match(/^namespace\s+(\w+)/);
|
|
1149
|
+
if (nsM) return { kind: "namespace", name: nsM[1], exported: true };
|
|
1150
|
+
const func = line.match(/^(?:[\w:*&<>\[\]]+\s+)+(\w+)\s*\([^;]*$/);
|
|
1151
|
+
if (func && !["if", "for", "while", "switch", "return"].includes(func[1])) {
|
|
1152
|
+
return { kind: "function", name: func[1], exported: true };
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
if (lang === "csharp" /* CSharp */) {
|
|
1156
|
+
const cls = line.match(/(?:public|internal|private)?\s*(?:static\s+)?(?:abstract\s+)?(?:partial\s+)?class\s+(\w+)/);
|
|
1157
|
+
if (cls) return { kind: "class", name: cls[1], exported: line.includes("public") };
|
|
1158
|
+
const iface = line.match(/(?:public\s+)?interface\s+(\w+)/);
|
|
1159
|
+
if (iface) return { kind: "interface", name: iface[1], exported: line.includes("public") };
|
|
1160
|
+
const structM = line.match(/(?:public\s+)?struct\s+(\w+)/);
|
|
1161
|
+
if (structM) return { kind: "struct", name: structM[1], exported: line.includes("public") };
|
|
1162
|
+
const method = line.match(/(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:[\w<>\[\]?]+)\s+(\w+)\s*\(/);
|
|
1163
|
+
if (method) return { kind: "method", name: method[1], exported: line.includes("public") };
|
|
1164
|
+
const nsM = line.match(/namespace\s+([\w.]+)/);
|
|
1165
|
+
if (nsM) return { kind: "namespace", name: nsM[1], exported: true };
|
|
1166
|
+
}
|
|
1167
|
+
if (lang === "php" /* PHP */) {
|
|
1168
|
+
const cls = line.match(/(?:abstract\s+)?class\s+(\w+)/);
|
|
1169
|
+
if (cls) return { kind: "class", name: cls[1], exported: true };
|
|
1170
|
+
const func = line.match(/(?:public|private|protected|static\s+)*function\s+(\w+)/);
|
|
1171
|
+
if (func) return { kind: "function", name: func[1], exported: line.includes("public") || !line.includes("private") };
|
|
1172
|
+
const iface = line.match(/interface\s+(\w+)/);
|
|
1173
|
+
if (iface) return { kind: "interface", name: iface[1], exported: true };
|
|
1174
|
+
const traitM = line.match(/trait\s+(\w+)/);
|
|
1175
|
+
if (traitM) return { kind: "trait", name: traitM[1], exported: true };
|
|
1176
|
+
}
|
|
1177
|
+
if (lang === "kotlin" /* Kotlin */) {
|
|
1178
|
+
const cls = line.match(/(?:data\s+|sealed\s+|abstract\s+|open\s+)?class\s+(\w+)/);
|
|
1179
|
+
if (cls) return { kind: "class", name: cls[1], exported: !line.includes("private") };
|
|
1180
|
+
const iface = line.match(/interface\s+(\w+)/);
|
|
1181
|
+
if (iface) return { kind: "interface", name: iface[1], exported: !line.includes("private") };
|
|
1182
|
+
const func = line.match(/(?:suspend\s+)?fun\s+(\w+)/);
|
|
1183
|
+
if (func) return { kind: "function", name: func[1], exported: !line.includes("private") };
|
|
1184
|
+
const obj = line.match(/object\s+(\w+)/);
|
|
1185
|
+
if (obj) return { kind: "class", name: obj[1], exported: !line.includes("private") };
|
|
1186
|
+
}
|
|
1187
|
+
if (lang === "ruby" /* Ruby */) {
|
|
1188
|
+
const cls = line.match(/^class\s+(\w+)/);
|
|
1189
|
+
if (cls) return { kind: "class", name: cls[1], exported: true };
|
|
1190
|
+
const modM = line.match(/^module\s+(\w+)/);
|
|
1191
|
+
if (modM) return { kind: "module", name: modM[1], exported: true };
|
|
1192
|
+
const method = line.match(/^(?:def\s+(?:self\.)?(\w+))/);
|
|
1193
|
+
if (method) return { kind: "method", name: method[1], exported: true };
|
|
1194
|
+
}
|
|
1195
|
+
if (lang === "swift" /* Swift */) {
|
|
1196
|
+
const cls = line.match(/(?:public\s+|open\s+)?(?:final\s+)?class\s+(\w+)/);
|
|
1197
|
+
if (cls) return { kind: "class", name: cls[1], exported: !line.includes("private") };
|
|
1198
|
+
const structM = line.match(/(?:public\s+)?struct\s+(\w+)/);
|
|
1199
|
+
if (structM) return { kind: "struct", name: structM[1], exported: !line.includes("private") };
|
|
1200
|
+
const proto = line.match(/(?:public\s+)?protocol\s+(\w+)/);
|
|
1201
|
+
if (proto) return { kind: "interface", name: proto[1], exported: !line.includes("private") };
|
|
1202
|
+
const enumM = line.match(/(?:public\s+)?enum\s+(\w+)/);
|
|
1203
|
+
if (enumM) return { kind: "enum", name: enumM[1], exported: !line.includes("private") };
|
|
1204
|
+
const func = line.match(/(?:public\s+|private\s+|internal\s+)?(?:static\s+)?func\s+(\w+)/);
|
|
1205
|
+
if (func) return { kind: "function", name: func[1], exported: !line.includes("private") };
|
|
1206
|
+
}
|
|
1207
|
+
if (lang === "dart" /* Dart */) {
|
|
1208
|
+
const cls = line.match(/(?:abstract\s+)?class\s+(\w+)/);
|
|
1209
|
+
if (cls) return { kind: "class", name: cls[1], exported: !cls[1].startsWith("_") };
|
|
1210
|
+
const func = line.match(/^(?:\w+\s+)?(\w+)\s*\(/);
|
|
1211
|
+
if (func && !["if", "for", "while", "switch", "catch", "return"].includes(func[1])) {
|
|
1212
|
+
return { kind: "function", name: func[1], exported: !func[1].startsWith("_") };
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
function extractBlock(lines, startIdx, maxLines) {
|
|
1218
|
+
const end = Math.min(startIdx + maxLines, lines.length);
|
|
1219
|
+
return lines.slice(startIdx, end).join("\n");
|
|
1220
|
+
}
|
|
1221
|
+
var resolvePhase = {
|
|
1222
|
+
name: "resolve",
|
|
1223
|
+
dependencies: ["parse"],
|
|
1224
|
+
async execute(context) {
|
|
1225
|
+
const start = Date.now();
|
|
1226
|
+
const { graph, workspaceRoot, filePaths } = context;
|
|
1227
|
+
let importEdges = 0;
|
|
1228
|
+
let callEdges = 0;
|
|
1229
|
+
let heritageEdges = 0;
|
|
1230
|
+
const fileIndex = /* @__PURE__ */ new Map();
|
|
1231
|
+
for (const fp of filePaths) {
|
|
1232
|
+
const rel = path.relative(workspaceRoot, fp);
|
|
1233
|
+
fileIndex.set(rel, fp);
|
|
1234
|
+
const noExt = rel.replace(/\.\w+$/, "");
|
|
1235
|
+
if (!fileIndex.has(noExt)) fileIndex.set(noExt, fp);
|
|
1236
|
+
const base = path.basename(rel, path.extname(rel));
|
|
1237
|
+
if (!fileIndex.has(base)) fileIndex.set(base, fp);
|
|
1238
|
+
}
|
|
1239
|
+
const symbolIndex = /* @__PURE__ */ new Map();
|
|
1240
|
+
const fileSymbolIndex = /* @__PURE__ */ new Map();
|
|
1241
|
+
for (const node of graph.allNodes()) {
|
|
1242
|
+
if (["function", "class", "interface", "method", "enum", "type_alias", "variable", "constant", "struct", "trait"].includes(node.kind)) {
|
|
1243
|
+
symbolIndex.set(node.name, node.id);
|
|
1244
|
+
let fileMap = fileSymbolIndex.get(node.filePath);
|
|
1245
|
+
if (!fileMap) {
|
|
1246
|
+
fileMap = /* @__PURE__ */ new Map();
|
|
1247
|
+
fileSymbolIndex.set(node.filePath, fileMap);
|
|
95
1248
|
}
|
|
1249
|
+
fileMap.set(node.name, node.id);
|
|
1250
|
+
}
|
|
96
1251
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
1252
|
+
for (const filePath of filePaths) {
|
|
1253
|
+
const lang = detectLanguage(filePath);
|
|
1254
|
+
if (!lang) continue;
|
|
1255
|
+
const relativePath = path.relative(workspaceRoot, filePath);
|
|
1256
|
+
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
1257
|
+
let source;
|
|
1258
|
+
try {
|
|
1259
|
+
source = fs8.readFileSync(filePath, "utf-8");
|
|
1260
|
+
} catch {
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
const lines = source.split("\n");
|
|
1264
|
+
const imports = extractImports(lines, lang === "python");
|
|
1265
|
+
const calls = extractCalls(lines);
|
|
1266
|
+
const heritages = extractHeritage(lines);
|
|
1267
|
+
for (const imp of imports) {
|
|
1268
|
+
const cleaned = imp.rawPath.replace(/['"]/g, "");
|
|
1269
|
+
let resolvedRelPath = null;
|
|
1270
|
+
if (cleaned.startsWith(".")) {
|
|
1271
|
+
const cleanedNoJs = cleaned.replace(/\.(js|jsx)$/, "");
|
|
1272
|
+
const fromDir = path.dirname(relativePath);
|
|
1273
|
+
for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", "/index.ts", "/index.js"]) {
|
|
1274
|
+
const candidate = path.join(fromDir, cleanedNoJs + ext);
|
|
1275
|
+
const normalized = path.normalize(candidate);
|
|
1276
|
+
if (fileIndex.has(normalized)) {
|
|
1277
|
+
const absPath = fileIndex.get(normalized);
|
|
1278
|
+
resolvedRelPath = path.relative(workspaceRoot, absPath);
|
|
1279
|
+
break;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
} else {
|
|
1283
|
+
for (const ext of ["", ".ts", ".js", ".py", ".java", ".go"]) {
|
|
1284
|
+
if (fileIndex.has(cleaned + ext)) {
|
|
1285
|
+
resolvedRelPath = cleaned + ext;
|
|
1286
|
+
break;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
const asPath = cleaned.replace(/\./g, "/");
|
|
1290
|
+
for (const ext of ["", ".ts", ".js", ".py", ".java", ".go", "/index.ts", "/__init__.py"]) {
|
|
1291
|
+
if (fileIndex.has(asPath + ext)) {
|
|
1292
|
+
resolvedRelPath = asPath + ext;
|
|
1293
|
+
break;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
100
1296
|
}
|
|
1297
|
+
if (resolvedRelPath) {
|
|
1298
|
+
const targetFileId = generateNodeId("file", resolvedRelPath, resolvedRelPath);
|
|
1299
|
+
if (graph.getNode(targetFileId)) {
|
|
1300
|
+
const edgeId = generateEdgeId(fileNodeId, targetFileId, "imports");
|
|
1301
|
+
if (!graph.getEdge(edgeId)) {
|
|
1302
|
+
graph.addEdge({
|
|
1303
|
+
id: edgeId,
|
|
1304
|
+
source: fileNodeId,
|
|
1305
|
+
target: targetFileId,
|
|
1306
|
+
kind: "imports",
|
|
1307
|
+
weight: 0.95,
|
|
1308
|
+
label: cleaned
|
|
1309
|
+
});
|
|
1310
|
+
importEdges++;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
const localSymbols = fileSymbolIndex.get(relativePath);
|
|
1316
|
+
for (const call of calls) {
|
|
1317
|
+
let targetId = localSymbols?.get(call.name);
|
|
1318
|
+
let confidence = 0.95;
|
|
1319
|
+
if (!targetId) {
|
|
1320
|
+
targetId = symbolIndex.get(call.name);
|
|
1321
|
+
confidence = 0.5;
|
|
1322
|
+
}
|
|
1323
|
+
if (targetId) {
|
|
1324
|
+
const callerNodeId = findEnclosingFunction(graph, relativePath, call.line);
|
|
1325
|
+
const sourceId = callerNodeId ?? fileNodeId;
|
|
1326
|
+
if (sourceId !== targetId) {
|
|
1327
|
+
const edgeId = generateEdgeId(sourceId, targetId, "calls");
|
|
1328
|
+
if (!graph.getEdge(edgeId)) {
|
|
1329
|
+
graph.addEdge({
|
|
1330
|
+
id: edgeId,
|
|
1331
|
+
source: sourceId,
|
|
1332
|
+
target: targetId,
|
|
1333
|
+
kind: "calls",
|
|
1334
|
+
weight: confidence,
|
|
1335
|
+
label: call.name
|
|
1336
|
+
});
|
|
1337
|
+
callEdges++;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
for (const h of heritages) {
|
|
1343
|
+
const classNodeId = localSymbols?.get(h.className) ?? symbolIndex.get(h.className);
|
|
1344
|
+
if (!classNodeId) continue;
|
|
1345
|
+
for (const ext of h.extendsNames) {
|
|
1346
|
+
const targetId = symbolIndex.get(ext);
|
|
1347
|
+
if (targetId) {
|
|
1348
|
+
const edgeId = generateEdgeId(classNodeId, targetId, "extends");
|
|
1349
|
+
if (!graph.getEdge(edgeId)) {
|
|
1350
|
+
graph.addEdge({
|
|
1351
|
+
id: edgeId,
|
|
1352
|
+
source: classNodeId,
|
|
1353
|
+
target: targetId,
|
|
1354
|
+
kind: "extends",
|
|
1355
|
+
weight: 1,
|
|
1356
|
+
label: `extends ${ext}`
|
|
1357
|
+
});
|
|
1358
|
+
heritageEdges++;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
for (const impl of h.implementsNames) {
|
|
1363
|
+
const targetId = symbolIndex.get(impl);
|
|
1364
|
+
if (targetId) {
|
|
1365
|
+
const edgeId = generateEdgeId(classNodeId, targetId, "implements");
|
|
1366
|
+
if (!graph.getEdge(edgeId)) {
|
|
1367
|
+
graph.addEdge({
|
|
1368
|
+
id: edgeId,
|
|
1369
|
+
source: classNodeId,
|
|
1370
|
+
target: targetId,
|
|
1371
|
+
kind: "implements",
|
|
1372
|
+
weight: 1,
|
|
1373
|
+
label: `implements ${impl}`
|
|
1374
|
+
});
|
|
1375
|
+
heritageEdges++;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
101
1380
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.action(async (targetPath) => {
|
|
132
|
-
const { graph, repoName } = await analyzeWorkspace(targetPath, { silent: true });
|
|
133
|
-
await startMcpStdio(graph, repoName);
|
|
134
|
-
});
|
|
135
|
-
program
|
|
136
|
-
.command('search')
|
|
137
|
-
.description('Search the knowledge graph')
|
|
138
|
-
.argument('<query>', 'Search query')
|
|
139
|
-
.option('-l, --limit <limit>', 'Max results', '20')
|
|
140
|
-
.option('-p, --path <path>', 'Path to analyze', '.')
|
|
141
|
-
.action(async (query, options) => {
|
|
142
|
-
const { graph } = await analyzeWorkspace(options.path, { silent: true });
|
|
143
|
-
const results = textSearch(graph, query, parseInt(options.limit, 10));
|
|
144
|
-
if (results.length === 0) {
|
|
145
|
-
console.log('No results found.');
|
|
146
|
-
return;
|
|
1381
|
+
return {
|
|
1382
|
+
status: "completed",
|
|
1383
|
+
duration: Date.now() - start,
|
|
1384
|
+
message: `Resolved ${importEdges} imports, ${callEdges} calls, ${heritageEdges} heritage edges. Graph: ${graph.size.nodes} nodes, ${graph.size.edges} edges`
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
function extractImports(lines, isPython) {
|
|
1389
|
+
const imports = [];
|
|
1390
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1391
|
+
const line = lines[i].trim();
|
|
1392
|
+
const tsImport = line.match(/import\s+.*?from\s+['"]([^'"]+)['"]/);
|
|
1393
|
+
if (tsImport) {
|
|
1394
|
+
const names = [];
|
|
1395
|
+
const namedMatch = line.match(/\{([^}]+)\}/);
|
|
1396
|
+
if (namedMatch) {
|
|
1397
|
+
names.push(...namedMatch[1].split(",").map((n) => n.trim().split(/\s+as\s+/).pop().trim()).filter(Boolean));
|
|
1398
|
+
}
|
|
1399
|
+
const defaultMatch = line.match(/import\s+(\w+)/);
|
|
1400
|
+
if (defaultMatch && defaultMatch[1] !== "type") {
|
|
1401
|
+
names.push(defaultMatch[1]);
|
|
1402
|
+
}
|
|
1403
|
+
imports.push({
|
|
1404
|
+
rawPath: tsImport[1],
|
|
1405
|
+
localNames: names,
|
|
1406
|
+
isDefault: !namedMatch,
|
|
1407
|
+
line: i + 1
|
|
1408
|
+
});
|
|
1409
|
+
continue;
|
|
147
1410
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
1411
|
+
if (isPython) {
|
|
1412
|
+
const fromImport = line.match(/from\s+([\w.]+)\s+import\s+(.+)/);
|
|
1413
|
+
if (fromImport) {
|
|
1414
|
+
const names = fromImport[2].split(",").map((n) => n.trim().split(/\s+as\s+/).pop().trim()).filter(Boolean);
|
|
1415
|
+
imports.push({ rawPath: fromImport[1], localNames: names, isDefault: false, line: i + 1 });
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
const directImport = line.match(/^import\s+([\w.]+)(?:\s+as\s+(\w+))?/);
|
|
1419
|
+
if (directImport) {
|
|
1420
|
+
imports.push({
|
|
1421
|
+
rawPath: directImport[1],
|
|
1422
|
+
localNames: [directImport[2] ?? directImport[1].split(".").pop()],
|
|
1423
|
+
isDefault: false,
|
|
1424
|
+
line: i + 1
|
|
1425
|
+
});
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
151
1428
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
1429
|
+
const javaImport = line.match(/^import\s+(?:static\s+)?([\w.]+)/);
|
|
1430
|
+
if (javaImport && !line.includes("from")) {
|
|
1431
|
+
const parts = javaImport[1].split(".");
|
|
1432
|
+
imports.push({
|
|
1433
|
+
rawPath: javaImport[1],
|
|
1434
|
+
localNames: [parts[parts.length - 1]],
|
|
1435
|
+
isDefault: false,
|
|
1436
|
+
line: i + 1
|
|
1437
|
+
});
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
const goImport = line.match(/^\s*"([^"]+)"/);
|
|
1441
|
+
if (goImport && (i > 0 && lines[i - 1]?.includes("import") || line.match(/^import\s+"/))) {
|
|
1442
|
+
const parts = goImport[1].split("/");
|
|
1443
|
+
imports.push({
|
|
1444
|
+
rawPath: goImport[1],
|
|
1445
|
+
localNames: [parts[parts.length - 1]],
|
|
1446
|
+
isDefault: false,
|
|
1447
|
+
line: i + 1
|
|
1448
|
+
});
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
const includeMatch = line.match(/#include\s+[<"]([^>"]+)[>"]/);
|
|
1452
|
+
if (includeMatch) {
|
|
1453
|
+
imports.push({
|
|
1454
|
+
rawPath: includeMatch[1],
|
|
1455
|
+
localNames: [],
|
|
1456
|
+
isDefault: false,
|
|
1457
|
+
line: i + 1
|
|
1458
|
+
});
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
const rustUse = line.match(/^use\s+([\w:]+)/);
|
|
1462
|
+
if (rustUse) {
|
|
1463
|
+
const parts = rustUse[1].split("::");
|
|
1464
|
+
imports.push({
|
|
1465
|
+
rawPath: rustUse[1],
|
|
1466
|
+
localNames: [parts[parts.length - 1]],
|
|
1467
|
+
isDefault: false,
|
|
1468
|
+
line: i + 1
|
|
1469
|
+
});
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
const usingMatch = line.match(/^using\s+([\w.]+)/);
|
|
1473
|
+
if (usingMatch) {
|
|
1474
|
+
const parts = usingMatch[1].split(".");
|
|
1475
|
+
imports.push({
|
|
1476
|
+
rawPath: usingMatch[1],
|
|
1477
|
+
localNames: [parts[parts.length - 1]],
|
|
1478
|
+
isDefault: false,
|
|
1479
|
+
line: i + 1
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
const requireMatch = line.match(/require\s+['"]([^'"]+)['"]/);
|
|
1483
|
+
if (requireMatch) {
|
|
1484
|
+
imports.push({
|
|
1485
|
+
rawPath: requireMatch[1],
|
|
1486
|
+
localNames: [],
|
|
1487
|
+
isDefault: false,
|
|
1488
|
+
line: i + 1
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
return imports;
|
|
1493
|
+
}
|
|
1494
|
+
function extractCalls(lines) {
|
|
1495
|
+
const calls = [];
|
|
1496
|
+
const callRegex = /(?:new\s+)?(\w+)\s*\(/g;
|
|
1497
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1498
|
+
const line = lines[i];
|
|
1499
|
+
if (/^\s*(export\s+)?(async\s+)?function\s/.test(line)) continue;
|
|
1500
|
+
if (/^\s*(export\s+)?(abstract\s+)?class\s/.test(line)) continue;
|
|
1501
|
+
if (/^\s*(export\s+)?interface\s/.test(line)) continue;
|
|
1502
|
+
if (/^\s*(export\s+)?enum\s/.test(line)) continue;
|
|
1503
|
+
if (/^\s*(export\s+)?type\s+\w+\s*=/.test(line)) continue;
|
|
1504
|
+
if (/^\s*import\s/.test(line)) continue;
|
|
1505
|
+
if (/^\s*\/\//.test(line)) continue;
|
|
1506
|
+
let match;
|
|
1507
|
+
callRegex.lastIndex = 0;
|
|
1508
|
+
while ((match = callRegex.exec(line)) !== null) {
|
|
1509
|
+
const name = match[1];
|
|
1510
|
+
if (["if", "for", "while", "switch", "catch", "return", "throw", "typeof", "instanceof", "delete", "void", "new", "import", "export", "from", "const", "let", "var", "function", "class", "interface", "type", "enum", "extends", "implements"].includes(name)) continue;
|
|
1511
|
+
const isNew = line.substring(Math.max(0, match.index - 4), match.index).includes("new");
|
|
1512
|
+
calls.push({ name, isNew, line: i + 1 });
|
|
1513
|
+
}
|
|
1514
|
+
const memberCallRegex = /(\w+)\.(\w+)\s*\(/g;
|
|
1515
|
+
memberCallRegex.lastIndex = 0;
|
|
1516
|
+
while ((match = memberCallRegex.exec(line)) !== null) {
|
|
1517
|
+
calls.push({
|
|
1518
|
+
name: match[2],
|
|
1519
|
+
receiverText: match[1],
|
|
1520
|
+
isNew: false,
|
|
1521
|
+
line: i + 1
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
return calls;
|
|
1526
|
+
}
|
|
1527
|
+
function extractHeritage(lines) {
|
|
1528
|
+
const heritages = [];
|
|
1529
|
+
for (const line of lines) {
|
|
1530
|
+
const classMatch = line.match(/class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s]+))?/);
|
|
1531
|
+
if (classMatch) {
|
|
1532
|
+
const extendsNames = classMatch[2] ? [classMatch[2]] : [];
|
|
1533
|
+
const implementsNames = classMatch[3] ? classMatch[3].split(",").map((n) => n.trim()).filter(Boolean) : [];
|
|
1534
|
+
heritages.push({ className: classMatch[1], extendsNames, implementsNames });
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
const pyClassMatch = line.match(/class\s+(\w+)\(([^)]+)\)/);
|
|
1538
|
+
if (pyClassMatch) {
|
|
1539
|
+
const bases = pyClassMatch[2].split(",").map((n) => n.trim()).filter(Boolean);
|
|
1540
|
+
heritages.push({ className: pyClassMatch[1], extendsNames: bases, implementsNames: [] });
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
return heritages;
|
|
1544
|
+
}
|
|
1545
|
+
function findEnclosingFunction(graph, filePath, line) {
|
|
1546
|
+
let best = null;
|
|
1547
|
+
for (const node of graph.allNodes()) {
|
|
1548
|
+
if (node.filePath !== filePath) continue;
|
|
1549
|
+
if (!["function", "method"].includes(node.kind)) continue;
|
|
1550
|
+
if (!node.startLine) continue;
|
|
1551
|
+
if (node.startLine <= line) {
|
|
1552
|
+
if (!best || node.startLine > best.startLine) {
|
|
1553
|
+
best = { id: node.id, startLine: node.startLine };
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
return best?.id ?? null;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// src/pipeline/phases/cluster-phase.ts
|
|
1561
|
+
var clusterPhase = {
|
|
1562
|
+
name: "cluster",
|
|
1563
|
+
dependencies: ["resolve"],
|
|
1564
|
+
async execute(context) {
|
|
1565
|
+
const start = Date.now();
|
|
1566
|
+
const { graph } = context;
|
|
1567
|
+
const relevantKinds = /* @__PURE__ */ new Set(["function", "class", "method", "interface", "struct", "trait", "enum"]);
|
|
1568
|
+
const nodesByDir = /* @__PURE__ */ new Map();
|
|
161
1569
|
for (const node of graph.allNodes()) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
1570
|
+
if (!relevantKinds.has(node.kind)) continue;
|
|
1571
|
+
const dir = node.filePath.split("/").slice(0, -1).join("/") || ".";
|
|
1572
|
+
let group = nodesByDir.get(dir);
|
|
1573
|
+
if (!group) {
|
|
1574
|
+
group = [];
|
|
1575
|
+
nodesByDir.set(dir, group);
|
|
1576
|
+
}
|
|
1577
|
+
group.push({ id: node.id, name: node.name });
|
|
1578
|
+
}
|
|
1579
|
+
let clusterCount = 0;
|
|
1580
|
+
for (const [dir, members] of nodesByDir) {
|
|
1581
|
+
if (members.length < 2) continue;
|
|
1582
|
+
const clusterId = generateNodeId("cluster", dir, `cluster-${clusterCount}`);
|
|
1583
|
+
const label = dir.split("/").filter(Boolean).pop() ?? `cluster-${clusterCount}`;
|
|
1584
|
+
graph.addNode({
|
|
1585
|
+
id: clusterId,
|
|
1586
|
+
kind: "cluster",
|
|
1587
|
+
name: label,
|
|
1588
|
+
filePath: dir,
|
|
1589
|
+
metadata: { memberCount: members.length }
|
|
1590
|
+
});
|
|
1591
|
+
for (const member of members) {
|
|
1592
|
+
graph.addEdge({
|
|
1593
|
+
id: generateEdgeId(member.id, clusterId, "belongs_to"),
|
|
1594
|
+
source: member.id,
|
|
1595
|
+
target: clusterId,
|
|
1596
|
+
kind: "belongs_to",
|
|
1597
|
+
weight: 1
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
clusterCount++;
|
|
1601
|
+
}
|
|
1602
|
+
return {
|
|
1603
|
+
status: "completed",
|
|
1604
|
+
duration: Date.now() - start,
|
|
1605
|
+
message: `Created ${clusterCount} clusters`
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
};
|
|
1609
|
+
|
|
1610
|
+
// src/pipeline/phases/flow-phase.ts
|
|
1611
|
+
var flowPhase = {
|
|
1612
|
+
name: "flow",
|
|
1613
|
+
dependencies: ["resolve"],
|
|
1614
|
+
async execute(context) {
|
|
1615
|
+
const start = Date.now();
|
|
1616
|
+
const { graph } = context;
|
|
1617
|
+
const calledNodes = /* @__PURE__ */ new Set();
|
|
1618
|
+
for (const edge of graph.findEdgesByKind("calls")) {
|
|
1619
|
+
calledNodes.add(edge.target);
|
|
1620
|
+
}
|
|
1621
|
+
const entryPoints = [];
|
|
1622
|
+
for (const node of graph.allNodes()) {
|
|
1623
|
+
if (!["function", "method"].includes(node.kind)) continue;
|
|
1624
|
+
let score = 0;
|
|
1625
|
+
const hasCallers = calledNodes.has(node.id);
|
|
1626
|
+
const outCalls = [...graph.findEdgesFrom(node.id)].filter((e) => e.kind === "calls");
|
|
1627
|
+
if (!hasCallers && outCalls.length > 0) score += 10;
|
|
1628
|
+
if (node.exported) score += 5;
|
|
1629
|
+
if (/^(main|handle|init|start|run|execute|process|serve|listen|bootstrap)/.test(node.name)) score += 3;
|
|
1630
|
+
if (node.filePath.includes("test") || node.filePath.includes("spec") || node.filePath.includes("__test")) score -= 20;
|
|
1631
|
+
if (node.filePath.includes("route") || node.filePath.includes("controller") || node.filePath.includes("handler")) score += 8;
|
|
1632
|
+
if (score >= 5) {
|
|
1633
|
+
entryPoints.push({ id: node.id, name: node.name, score, filePath: node.filePath });
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
entryPoints.sort((a, b) => b.score - a.score);
|
|
1637
|
+
const maxFlows = 75;
|
|
1638
|
+
const maxDepth = 10;
|
|
1639
|
+
const maxBranching = 4;
|
|
1640
|
+
let flowCount = 0;
|
|
1641
|
+
for (const ep of entryPoints.slice(0, 20)) {
|
|
1642
|
+
if (flowCount >= maxFlows) break;
|
|
1643
|
+
const queue = [{ nodeId: ep.id, path: [ep.id] }];
|
|
1644
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1645
|
+
while (queue.length > 0 && flowCount < maxFlows) {
|
|
1646
|
+
const { nodeId, path: path15 } = queue.shift();
|
|
1647
|
+
if (path15.length > maxDepth) continue;
|
|
1648
|
+
const callEdges = [...graph.findEdgesFrom(nodeId)].filter((e) => e.kind === "calls").slice(0, maxBranching);
|
|
1649
|
+
if (callEdges.length === 0 && path15.length >= 3) {
|
|
1650
|
+
const flowId = generateNodeId("flow", ep.filePath, `flow-${flowCount}`);
|
|
1651
|
+
graph.addNode({
|
|
1652
|
+
id: flowId,
|
|
1653
|
+
kind: "flow",
|
|
1654
|
+
name: `${ep.name} flow ${flowCount}`,
|
|
1655
|
+
filePath: ep.filePath,
|
|
1656
|
+
metadata: { steps: path15, entryPoint: ep.name }
|
|
1657
|
+
});
|
|
1658
|
+
for (let i = 0; i < path15.length; i++) {
|
|
1659
|
+
graph.addEdge({
|
|
1660
|
+
id: generateEdgeId(path15[i], flowId, `step_of_${i}`),
|
|
1661
|
+
source: path15[i],
|
|
1662
|
+
target: flowId,
|
|
1663
|
+
kind: "step_of",
|
|
1664
|
+
weight: 1,
|
|
1665
|
+
label: `step ${i + 1}`
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
flowCount++;
|
|
1669
|
+
continue;
|
|
1670
|
+
}
|
|
1671
|
+
for (const edge of callEdges) {
|
|
1672
|
+
if (visited.has(edge.target)) continue;
|
|
1673
|
+
visited.add(edge.target);
|
|
1674
|
+
queue.push({ nodeId: edge.target, path: [...path15, edge.target] });
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
return {
|
|
1679
|
+
status: "completed",
|
|
1680
|
+
duration: Date.now() - start,
|
|
1681
|
+
message: `Found ${entryPoints.length} entry points, traced ${flowCount} flows`
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
};
|
|
1685
|
+
|
|
1686
|
+
// src/search/text-search.ts
|
|
1687
|
+
function textSearch(graph, query, limit = 20) {
|
|
1688
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1689
|
+
const results = [];
|
|
1690
|
+
const isTestPath = (fp) => fp.includes("test") || fp.includes("spec") || fp.includes("__test");
|
|
1691
|
+
const isDistPath = (fp) => fp.includes("/dist") || fp.includes("\\dist") || fp.includes(".d.ts");
|
|
1692
|
+
for (const node of graph.allNodes()) {
|
|
1693
|
+
if (["directory", "cluster", "flow"].includes(node.kind)) continue;
|
|
1694
|
+
let score = 0;
|
|
1695
|
+
const nameLC = node.name.toLowerCase();
|
|
1696
|
+
const pathLC = node.filePath.toLowerCase();
|
|
1697
|
+
for (const term of terms) {
|
|
1698
|
+
if (nameLC === term) score += 10;
|
|
1699
|
+
else if (nameLC.startsWith(term)) score += 7;
|
|
1700
|
+
else if (nameLC.includes(term)) score += 5;
|
|
1701
|
+
if (pathLC.includes(term)) score += 2;
|
|
1702
|
+
if (node.content?.toLowerCase().includes(term)) score += 3;
|
|
1703
|
+
}
|
|
1704
|
+
if (score > 0) {
|
|
1705
|
+
if (isDistPath(node.filePath)) score -= 8;
|
|
1706
|
+
if (isTestPath(node.filePath)) score -= 4;
|
|
1707
|
+
if (["function", "class", "interface", "method"].includes(node.kind)) score += 1;
|
|
1708
|
+
}
|
|
1709
|
+
if (score > 0) {
|
|
1710
|
+
results.push({
|
|
1711
|
+
nodeId: node.id,
|
|
1712
|
+
name: node.name,
|
|
1713
|
+
kind: node.kind,
|
|
1714
|
+
filePath: node.filePath,
|
|
1715
|
+
score,
|
|
1716
|
+
snippet: node.content?.slice(0, 200)
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
results.sort((a, b) => b.score - a.score);
|
|
1721
|
+
return results.slice(0, limit);
|
|
1722
|
+
}
|
|
1723
|
+
function reciprocalRankFusion(...rankings) {
|
|
1724
|
+
const K = 60;
|
|
1725
|
+
const scoreMap = /* @__PURE__ */ new Map();
|
|
1726
|
+
for (const ranking of rankings) {
|
|
1727
|
+
for (let rank = 0; rank < ranking.length; rank++) {
|
|
1728
|
+
const result = ranking[rank];
|
|
1729
|
+
const existing = scoreMap.get(result.nodeId);
|
|
1730
|
+
const rrfContribution = 1 / (K + rank + 1);
|
|
1731
|
+
if (existing) {
|
|
1732
|
+
existing.rrfScore += rrfContribution;
|
|
1733
|
+
} else {
|
|
1734
|
+
scoreMap.set(result.nodeId, {
|
|
1735
|
+
result,
|
|
1736
|
+
rrfScore: rrfContribution
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
return [...scoreMap.values()].sort((a, b) => b.rrfScore - a.rrfScore).map((entry) => ({ ...entry.result, score: entry.rrfScore }));
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// src/http/app.ts
|
|
1745
|
+
init_storage();
|
|
1746
|
+
init_vector_index();
|
|
1747
|
+
init_group_registry();
|
|
1748
|
+
|
|
1749
|
+
// src/multi-repo/group-sync.ts
|
|
1750
|
+
init_repo_registry();
|
|
1751
|
+
init_db_manager();
|
|
1752
|
+
|
|
1753
|
+
// src/multi-repo/graph-from-db.ts
|
|
1754
|
+
init_schema();
|
|
1755
|
+
var TABLE_TO_KIND = Object.fromEntries(
|
|
1756
|
+
Object.entries(NODE_TABLE_MAP).map(([kind, table]) => [table, kind])
|
|
1757
|
+
);
|
|
1758
|
+
function parseRow(row, kind) {
|
|
1759
|
+
return {
|
|
1760
|
+
id: String(row["id"] ?? ""),
|
|
1761
|
+
kind,
|
|
1762
|
+
name: String(row["name"] ?? ""),
|
|
1763
|
+
filePath: String(row["file_path"] ?? ""),
|
|
1764
|
+
startLine: row["start_line"] != null ? Number(row["start_line"]) : void 0,
|
|
1765
|
+
endLine: row["end_line"] != null ? Number(row["end_line"]) : void 0,
|
|
1766
|
+
exported: row["exported"] != null ? Boolean(row["exported"]) : void 0,
|
|
1767
|
+
content: row["content"] ? String(row["content"]) : void 0,
|
|
1768
|
+
metadata: row["metadata"] ? (() => {
|
|
1769
|
+
try {
|
|
1770
|
+
return JSON.parse(String(row["metadata"]));
|
|
1771
|
+
} catch {
|
|
1772
|
+
return void 0;
|
|
1773
|
+
}
|
|
1774
|
+
})() : void 0
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
async function loadGraphFromDB(graph, db) {
|
|
1778
|
+
for (const table of ALL_NODE_TABLES) {
|
|
1779
|
+
const kind = TABLE_TO_KIND[table];
|
|
1780
|
+
if (!kind) continue;
|
|
1781
|
+
let rows = [];
|
|
1782
|
+
try {
|
|
1783
|
+
rows = await db.query(`MATCH (n:${table}) RETURN n.id, n.name, n.file_path, n.start_line, n.end_line, n.exported, n.content, n.metadata`);
|
|
1784
|
+
} catch {
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
for (const row of rows) {
|
|
1788
|
+
const node = parseRow({
|
|
1789
|
+
id: row["n.id"],
|
|
1790
|
+
name: row["n.name"],
|
|
1791
|
+
file_path: row["n.file_path"],
|
|
1792
|
+
start_line: row["n.start_line"],
|
|
1793
|
+
end_line: row["n.end_line"],
|
|
1794
|
+
exported: row["n.exported"],
|
|
1795
|
+
content: row["n.content"],
|
|
1796
|
+
metadata: row["n.metadata"]
|
|
1797
|
+
}, kind);
|
|
1798
|
+
if (node.id && node.name) graph.addNode(node);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
try {
|
|
1802
|
+
const edgeRows = await db.query(
|
|
1803
|
+
`MATCH (a)-[e:code_edges]->(b) RETURN a.id, b.id, e.kind, e.weight, e.label`
|
|
1804
|
+
);
|
|
1805
|
+
for (const row of edgeRows) {
|
|
1806
|
+
const sourceId = String(row["a.id"] ?? "");
|
|
1807
|
+
const targetId = String(row["b.id"] ?? "");
|
|
1808
|
+
const kind = String(row["e.kind"] ?? "");
|
|
1809
|
+
if (!sourceId || !targetId || !kind) continue;
|
|
1810
|
+
const edge = {
|
|
1811
|
+
id: `${sourceId}::${kind}::${targetId}`,
|
|
1812
|
+
source: sourceId,
|
|
1813
|
+
target: targetId,
|
|
1814
|
+
kind,
|
|
1815
|
+
weight: row["e.weight"] != null ? Number(row["e.weight"]) : void 0,
|
|
1816
|
+
label: row["e.label"] ? String(row["e.label"]) : void 0
|
|
1817
|
+
};
|
|
1818
|
+
graph.addEdge(edge);
|
|
1819
|
+
}
|
|
1820
|
+
} catch {
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// src/multi-repo/group-sync.ts
|
|
1825
|
+
function extractContracts(graph, repoName, repoPath) {
|
|
1826
|
+
const contracts = [];
|
|
1827
|
+
for (const node of graph.allNodes()) {
|
|
1828
|
+
if (node.exported === true && ["function", "class", "interface", "method", "type_alias", "constant", "enum", "struct", "trait"].includes(node.kind)) {
|
|
1829
|
+
contracts.push({
|
|
1830
|
+
repoName,
|
|
1831
|
+
repoPath,
|
|
1832
|
+
kind: "export",
|
|
1833
|
+
name: node.name,
|
|
1834
|
+
nodeId: node.id,
|
|
1835
|
+
nodeKind: node.kind,
|
|
1836
|
+
filePath: node.filePath,
|
|
1837
|
+
signature: node.content?.split("\n")[0]?.trim()
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
if (node.kind === "route") {
|
|
1841
|
+
contracts.push({
|
|
1842
|
+
repoName,
|
|
1843
|
+
repoPath,
|
|
1844
|
+
kind: "route",
|
|
1845
|
+
name: node.name,
|
|
1846
|
+
nodeId: node.id,
|
|
1847
|
+
nodeKind: node.kind,
|
|
1848
|
+
filePath: node.filePath,
|
|
1849
|
+
signature: node.content?.split("\n")[0]?.trim()
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
if (["interface", "type_alias"].includes(node.kind)) {
|
|
1853
|
+
const nameLower = node.name.toLowerCase();
|
|
1854
|
+
if (nameLower.includes("event") || nameLower.includes("message")) {
|
|
1855
|
+
contracts.push({
|
|
1856
|
+
repoName,
|
|
1857
|
+
repoPath,
|
|
1858
|
+
kind: "event",
|
|
1859
|
+
name: node.name,
|
|
1860
|
+
nodeId: node.id,
|
|
1861
|
+
nodeKind: node.kind,
|
|
1862
|
+
filePath: node.filePath
|
|
1863
|
+
});
|
|
1864
|
+
} else if (nameLower.includes("schema") || nameLower.includes("dto") || nameLower.includes("request") || nameLower.includes("response")) {
|
|
1865
|
+
contracts.push({
|
|
1866
|
+
repoName,
|
|
1867
|
+
repoPath,
|
|
1868
|
+
kind: "schema",
|
|
1869
|
+
name: node.name,
|
|
1870
|
+
nodeId: node.id,
|
|
1871
|
+
nodeKind: node.kind,
|
|
1872
|
+
filePath: node.filePath
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
return contracts;
|
|
1878
|
+
}
|
|
1879
|
+
function matchContracts(allContracts) {
|
|
1880
|
+
const links = [];
|
|
1881
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
1882
|
+
for (const c of allContracts) {
|
|
1883
|
+
const arr = byRepo.get(c.repoName) ?? [];
|
|
1884
|
+
arr.push(c);
|
|
1885
|
+
byRepo.set(c.repoName, arr);
|
|
1886
|
+
}
|
|
1887
|
+
const repoNames = [...byRepo.keys()];
|
|
1888
|
+
for (let i = 0; i < repoNames.length; i++) {
|
|
1889
|
+
for (let j = 0; j < repoNames.length; j++) {
|
|
1890
|
+
if (i === j) continue;
|
|
1891
|
+
const providerContracts = byRepo.get(repoNames[i]);
|
|
1892
|
+
const consumerContracts = byRepo.get(repoNames[j]);
|
|
1893
|
+
const consumerByName = /* @__PURE__ */ new Map();
|
|
1894
|
+
for (const c of consumerContracts) consumerByName.set(c.name, c);
|
|
1895
|
+
for (const provider of providerContracts) {
|
|
1896
|
+
const consumer = consumerByName.get(provider.name);
|
|
1897
|
+
if (consumer) {
|
|
1898
|
+
const sameKind = provider.kind === consumer.kind;
|
|
1899
|
+
links.push({
|
|
1900
|
+
providerRepo: provider.repoName,
|
|
1901
|
+
providerContract: provider.name,
|
|
1902
|
+
consumerRepo: consumer.repoName,
|
|
1903
|
+
consumerContract: consumer.name,
|
|
1904
|
+
matchKind: provider.kind === "route" ? "route-match" : "name-match",
|
|
1905
|
+
confidence: sameKind ? 0.9 : 0.6
|
|
1906
|
+
});
|
|
1907
|
+
} else {
|
|
1908
|
+
const providerLC = provider.name.toLowerCase();
|
|
1909
|
+
for (const c of consumerContracts) {
|
|
1910
|
+
if (c.name.toLowerCase().includes(providerLC) || providerLC.includes(c.name.toLowerCase())) {
|
|
1911
|
+
if (c.name.length >= 4 && provider.name.length >= 4) {
|
|
1912
|
+
links.push({
|
|
1913
|
+
providerRepo: provider.repoName,
|
|
1914
|
+
providerContract: provider.name,
|
|
1915
|
+
consumerRepo: c.repoName,
|
|
1916
|
+
consumerContract: c.name,
|
|
1917
|
+
matchKind: "name-match",
|
|
1918
|
+
confidence: 0.4
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
177
1921
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1928
|
+
for (const link of links) {
|
|
1929
|
+
const key = `${link.providerRepo}:${link.providerContract}:${link.consumerRepo}:${link.consumerContract}`;
|
|
1930
|
+
const existing = seen.get(key);
|
|
1931
|
+
if (!existing || link.confidence > existing.confidence) {
|
|
1932
|
+
seen.set(key, link);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
return [...seen.values()].sort((a, b) => b.confidence - a.confidence);
|
|
1936
|
+
}
|
|
1937
|
+
async function syncGroup(group) {
|
|
1938
|
+
const registry = loadRegistry();
|
|
1939
|
+
const allContracts = [];
|
|
1940
|
+
for (const member of group.members) {
|
|
1941
|
+
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
1942
|
+
if (!regEntry) {
|
|
1943
|
+
console.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
const dbPath = path.join(regEntry.path, ".code-intel", "graph.db");
|
|
1947
|
+
if (!fs8.existsSync(dbPath)) {
|
|
1948
|
+
console.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
const graph = createKnowledgeGraph();
|
|
1952
|
+
const db = new DbManager(dbPath);
|
|
1953
|
+
try {
|
|
1954
|
+
await db.init();
|
|
1955
|
+
await loadGraphFromDB(graph, db);
|
|
1956
|
+
db.close();
|
|
1957
|
+
} catch (err) {
|
|
1958
|
+
db.close();
|
|
1959
|
+
console.warn(` \u26A0 Could not load graph for "${member.registryName}": ${err instanceof Error ? err.message : err}`);
|
|
1960
|
+
continue;
|
|
1961
|
+
}
|
|
1962
|
+
const contracts = extractContracts(graph, member.registryName, regEntry.path);
|
|
1963
|
+
console.log(` \u2713 ${member.registryName} (${member.groupPath}): ${contracts.length} contracts`);
|
|
1964
|
+
allContracts.push(...contracts);
|
|
1965
|
+
}
|
|
1966
|
+
const links = matchContracts(allContracts);
|
|
1967
|
+
return {
|
|
1968
|
+
groupName: group.name,
|
|
1969
|
+
syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1970
|
+
memberCount: group.members.length,
|
|
1971
|
+
contracts: allContracts,
|
|
1972
|
+
links
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
init_repo_registry();
|
|
1976
|
+
init_db_manager();
|
|
1977
|
+
async function queryGroup(group, query, limit = 20) {
|
|
1978
|
+
const registry = loadRegistry();
|
|
1979
|
+
const perRepo = [];
|
|
1980
|
+
const allRankings = [];
|
|
1981
|
+
for (const member of group.members) {
|
|
1982
|
+
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
1983
|
+
if (!regEntry) continue;
|
|
1984
|
+
const dbPath = path.join(regEntry.path, ".code-intel", "graph.db");
|
|
1985
|
+
if (!fs8.existsSync(dbPath)) continue;
|
|
1986
|
+
const graph = createKnowledgeGraph();
|
|
1987
|
+
const db = new DbManager(dbPath);
|
|
1988
|
+
try {
|
|
1989
|
+
await db.init();
|
|
1990
|
+
await loadGraphFromDB(graph, db);
|
|
1991
|
+
db.close();
|
|
1992
|
+
} catch {
|
|
1993
|
+
db.close();
|
|
1994
|
+
continue;
|
|
1995
|
+
}
|
|
1996
|
+
const results = textSearch(graph, query, limit);
|
|
1997
|
+
const taggedResults = results.map((r) => ({
|
|
1998
|
+
...r,
|
|
1999
|
+
snippet: `[${member.registryName}] ${r.snippet ?? ""}`.trim()
|
|
2000
|
+
}));
|
|
2001
|
+
perRepo.push({
|
|
2002
|
+
repoName: member.registryName,
|
|
2003
|
+
repoPath: regEntry.path,
|
|
2004
|
+
groupPath: member.groupPath,
|
|
2005
|
+
results: taggedResults
|
|
2006
|
+
});
|
|
2007
|
+
allRankings.push(taggedResults);
|
|
2008
|
+
}
|
|
2009
|
+
const merged = reciprocalRankFusion(...allRankings).slice(0, limit);
|
|
2010
|
+
return { perRepo, merged };
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// src/http/app.ts
|
|
2014
|
+
init_repo_registry();
|
|
2015
|
+
var __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
|
|
2016
|
+
var WEB_DIST = path.resolve(__dirname$1, "..", "..", "..", "web", "dist");
|
|
2017
|
+
function createApp(graph, repoName, workspaceRoot) {
|
|
2018
|
+
const app = express();
|
|
2019
|
+
app.use(cors({ origin: true }));
|
|
2020
|
+
app.use(express.json({ limit: "10mb" }));
|
|
2021
|
+
let vectorIndex = null;
|
|
2022
|
+
let vectorIndexBuilding = false;
|
|
2023
|
+
let vectorIndexReady = false;
|
|
2024
|
+
async function ensureVectorIndex() {
|
|
2025
|
+
if (vectorIndexReady && vectorIndex) return vectorIndex;
|
|
2026
|
+
if (!workspaceRoot || vectorIndexBuilding) return null;
|
|
2027
|
+
vectorIndexBuilding = true;
|
|
2028
|
+
try {
|
|
2029
|
+
const { embedNodes: embedNodes2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
|
|
2030
|
+
const dbPath = getVectorDbPath(workspaceRoot);
|
|
2031
|
+
const db = new DbManager(dbPath);
|
|
2032
|
+
await db.init();
|
|
2033
|
+
const idx = new VectorIndex(db);
|
|
2034
|
+
await idx.init();
|
|
2035
|
+
const alreadyBuilt = await idx.isBuilt();
|
|
2036
|
+
if (!alreadyBuilt) {
|
|
2037
|
+
console.log(" [vector] Building embeddings\u2026");
|
|
2038
|
+
const nodes = await embedNodes2(graph, {
|
|
2039
|
+
onProgress: (done, total) => {
|
|
2040
|
+
if (done % 50 === 0 || done === total) process.stdout.write(`\r [vector] ${done}/${total}`);
|
|
2041
|
+
}
|
|
2042
|
+
});
|
|
2043
|
+
console.log("");
|
|
2044
|
+
await idx.buildIndex(nodes);
|
|
2045
|
+
console.log(` [vector] Index built: ${nodes.length} embeddings`);
|
|
2046
|
+
} else {
|
|
2047
|
+
console.log(" [vector] Index already exists, skipping rebuild.");
|
|
2048
|
+
}
|
|
2049
|
+
vectorIndex = idx;
|
|
2050
|
+
vectorIndexReady = true;
|
|
2051
|
+
return idx;
|
|
2052
|
+
} catch (err) {
|
|
2053
|
+
console.warn(" [vector] Index build failed:", err instanceof Error ? err.message : err);
|
|
2054
|
+
return null;
|
|
2055
|
+
} finally {
|
|
2056
|
+
vectorIndexBuilding = false;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
if (workspaceRoot) {
|
|
2060
|
+
setImmediate(() => ensureVectorIndex().catch(() => {
|
|
2061
|
+
}));
|
|
2062
|
+
}
|
|
2063
|
+
app.get("/api/health", (_req, res) => {
|
|
2064
|
+
res.json({ status: "ok", nodes: graph.size.nodes, edges: graph.size.edges });
|
|
2065
|
+
});
|
|
2066
|
+
app.get("/api/repos", (_req, res) => {
|
|
2067
|
+
res.json([{ name: repoName, nodes: graph.size.nodes, edges: graph.size.edges }]);
|
|
2068
|
+
});
|
|
2069
|
+
app.get("/api/graph/:repo", (_req, res) => {
|
|
2070
|
+
const nodes = [...graph.allNodes()];
|
|
2071
|
+
const edges = [...graph.allEdges()];
|
|
2072
|
+
res.json({ nodes, edges });
|
|
2073
|
+
});
|
|
2074
|
+
app.post("/api/search", (req, res) => {
|
|
2075
|
+
const { query, limit } = req.body;
|
|
2076
|
+
const results = textSearch(graph, query, limit ?? 20);
|
|
2077
|
+
res.json({ results });
|
|
2078
|
+
});
|
|
2079
|
+
app.post("/api/vector-search", async (req, res) => {
|
|
2080
|
+
const { query, limit = 10 } = req.body;
|
|
2081
|
+
if (!query) {
|
|
2082
|
+
res.status(400).json({ error: "Missing query" });
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
const idx = await ensureVectorIndex();
|
|
2086
|
+
if (!idx) {
|
|
2087
|
+
const results = textSearch(graph, query, limit);
|
|
2088
|
+
res.json({ results, source: "text-fallback", vectorReady: false });
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
try {
|
|
2092
|
+
const { pipeline } = await import('@huggingface/transformers');
|
|
2093
|
+
const embedder = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
|
|
2094
|
+
const out = await embedder(query, { pooling: "mean", normalize: true });
|
|
2095
|
+
const queryEmbedding = Array.from(out.data);
|
|
2096
|
+
const hits = await idx.search(queryEmbedding, limit);
|
|
2097
|
+
const results = hits.map((h) => ({
|
|
2098
|
+
nodeId: h.nodeId,
|
|
2099
|
+
name: h.name,
|
|
2100
|
+
kind: h.kind,
|
|
2101
|
+
filePath: h.filePath,
|
|
2102
|
+
score: h.score
|
|
2103
|
+
}));
|
|
2104
|
+
res.json({ results, source: "vector", vectorReady: true });
|
|
2105
|
+
} catch (err) {
|
|
2106
|
+
const results = textSearch(graph, query, limit);
|
|
2107
|
+
res.json({ results, source: "text-fallback", vectorReady: false, error: err instanceof Error ? err.message : String(err) });
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
app.get("/api/vector-status", (_req, res) => {
|
|
2111
|
+
res.json({ ready: vectorIndexReady, building: vectorIndexBuilding });
|
|
2112
|
+
});
|
|
2113
|
+
app.post("/api/files/read", (req, res) => {
|
|
2114
|
+
const { file_path } = req.body;
|
|
2115
|
+
try {
|
|
2116
|
+
const content = fs8.readFileSync(file_path, "utf-8");
|
|
2117
|
+
res.json({ content });
|
|
2118
|
+
} catch {
|
|
2119
|
+
res.status(404).json({ error: "File not found" });
|
|
2120
|
+
}
|
|
2121
|
+
});
|
|
2122
|
+
app.post("/api/grep", (req, res) => {
|
|
2123
|
+
const { pattern, file_paths } = req.body;
|
|
2124
|
+
const results = [];
|
|
2125
|
+
try {
|
|
2126
|
+
const regex = new RegExp(pattern, "gi");
|
|
2127
|
+
const paths = file_paths ?? [];
|
|
2128
|
+
if (paths.length === 0) {
|
|
2129
|
+
for (const node of graph.allNodes()) {
|
|
2130
|
+
if (node.kind === "file" && node.content) {
|
|
2131
|
+
const lines = node.content.split("\n");
|
|
2132
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2133
|
+
if (regex.test(lines[i])) {
|
|
2134
|
+
results.push({ file: node.filePath, line: i + 1, text: lines[i].trim() });
|
|
2135
|
+
}
|
|
2136
|
+
regex.lastIndex = 0;
|
|
184
2137
|
}
|
|
185
|
-
|
|
2138
|
+
}
|
|
186
2139
|
}
|
|
2140
|
+
}
|
|
2141
|
+
res.json({ results: results.slice(0, 100) });
|
|
2142
|
+
} catch {
|
|
2143
|
+
res.status(400).json({ error: "Invalid regex pattern" });
|
|
187
2144
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
2145
|
+
});
|
|
2146
|
+
app.post("/api/cypher", async (req, res) => {
|
|
2147
|
+
const { query: q } = req.body;
|
|
2148
|
+
if (!q) {
|
|
2149
|
+
res.status(400).json({ error: "Missing query" });
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
if (workspaceRoot) {
|
|
2153
|
+
try {
|
|
2154
|
+
const dbPath = getDbPath(workspaceRoot);
|
|
2155
|
+
const dbm = new DbManager(dbPath);
|
|
2156
|
+
await dbm.init();
|
|
2157
|
+
const rows = await dbm.query(q);
|
|
2158
|
+
dbm.close();
|
|
2159
|
+
res.json({ results: rows });
|
|
2160
|
+
return;
|
|
2161
|
+
} catch (err) {
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
try {
|
|
2165
|
+
const nameMatch = q?.match(/name\s*=\s*['"]([^'"]+)['"]/i);
|
|
2166
|
+
if (nameMatch) {
|
|
2167
|
+
const name = nameMatch[1];
|
|
2168
|
+
const results = [];
|
|
2169
|
+
for (const node of graph.allNodes()) {
|
|
2170
|
+
if (node.name === name) {
|
|
2171
|
+
const incoming = [...graph.findEdgesTo(node.id)];
|
|
2172
|
+
const outgoing = [...graph.findEdgesFrom(node.id)];
|
|
2173
|
+
results.push({ node, incoming: incoming.length, outgoing: outgoing.length });
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
res.json({ results });
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
const kindMatch = q?.match(/:\s*(\w+)/);
|
|
2180
|
+
if (kindMatch) {
|
|
2181
|
+
const kind = kindMatch[1];
|
|
2182
|
+
const results = [];
|
|
2183
|
+
for (const node of graph.allNodes()) {
|
|
2184
|
+
if (node.kind === kind) results.push(node);
|
|
2185
|
+
if (results.length >= 50) break;
|
|
2186
|
+
}
|
|
2187
|
+
res.json({ results });
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
res.json({ results: [], message: "Query not recognized." });
|
|
2191
|
+
} catch {
|
|
2192
|
+
res.status(400).json({ error: "Invalid query" });
|
|
2193
|
+
}
|
|
2194
|
+
});
|
|
2195
|
+
app.get("/api/nodes/:id", (req, res) => {
|
|
2196
|
+
const nodeId = decodeURIComponent(req.params.id);
|
|
2197
|
+
const node = graph.getNode(nodeId);
|
|
2198
|
+
if (!node) {
|
|
2199
|
+
res.status(404).json({ error: "Node not found" });
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
const incoming = [...graph.findEdgesTo(nodeId)];
|
|
2203
|
+
const outgoing = [...graph.findEdgesFrom(nodeId)];
|
|
2204
|
+
res.json({
|
|
2205
|
+
node,
|
|
2206
|
+
callers: incoming.filter((e) => e.kind === "calls").map((e) => ({
|
|
2207
|
+
id: e.source,
|
|
2208
|
+
name: graph.getNode(e.source)?.name,
|
|
2209
|
+
weight: e.weight
|
|
2210
|
+
})),
|
|
2211
|
+
callees: outgoing.filter((e) => e.kind === "calls").map((e) => ({
|
|
2212
|
+
id: e.target,
|
|
2213
|
+
name: graph.getNode(e.target)?.name,
|
|
2214
|
+
weight: e.weight
|
|
2215
|
+
})),
|
|
2216
|
+
imports: outgoing.filter((e) => e.kind === "imports").map((e) => ({
|
|
2217
|
+
id: e.target,
|
|
2218
|
+
name: graph.getNode(e.target)?.name
|
|
2219
|
+
})),
|
|
2220
|
+
importedBy: incoming.filter((e) => e.kind === "imports").map((e) => ({
|
|
2221
|
+
id: e.source,
|
|
2222
|
+
name: graph.getNode(e.source)?.name
|
|
2223
|
+
})),
|
|
2224
|
+
extends: outgoing.filter((e) => e.kind === "extends").map((e) => ({
|
|
2225
|
+
id: e.target,
|
|
2226
|
+
name: graph.getNode(e.target)?.name
|
|
2227
|
+
})),
|
|
2228
|
+
implementsEdges: outgoing.filter((e) => e.kind === "implements").map((e) => ({
|
|
2229
|
+
id: e.target,
|
|
2230
|
+
name: graph.getNode(e.target)?.name
|
|
2231
|
+
})),
|
|
2232
|
+
members: outgoing.filter((e) => e.kind === "has_member").map((e) => ({
|
|
2233
|
+
id: e.target,
|
|
2234
|
+
name: graph.getNode(e.target)?.name,
|
|
2235
|
+
kind: graph.getNode(e.target)?.kind
|
|
2236
|
+
})),
|
|
2237
|
+
cluster: incoming.filter((e) => e.kind === "belongs_to").map((e) => graph.getNode(e.target)?.name)[0]
|
|
2238
|
+
});
|
|
2239
|
+
});
|
|
2240
|
+
app.post("/api/blast-radius", (req, res) => {
|
|
2241
|
+
const { target, direction = "both", max_hops = 5 } = req.body;
|
|
200
2242
|
let targetNode = null;
|
|
201
2243
|
for (const node of graph.allNodes()) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
2244
|
+
if (node.name === target || node.id === target) {
|
|
2245
|
+
targetNode = node;
|
|
2246
|
+
break;
|
|
2247
|
+
}
|
|
206
2248
|
}
|
|
207
2249
|
if (!targetNode) {
|
|
208
|
-
|
|
209
|
-
|
|
2250
|
+
res.status(404).json({ error: `Symbol "${target}" not found` });
|
|
2251
|
+
return;
|
|
210
2252
|
}
|
|
211
|
-
const affected = new
|
|
2253
|
+
const affected = /* @__PURE__ */ new Map();
|
|
212
2254
|
const queue = [{ id: targetNode.id, depth: 0 }];
|
|
213
|
-
const visited = new Set();
|
|
2255
|
+
const visited = /* @__PURE__ */ new Set();
|
|
214
2256
|
while (queue.length > 0) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
2257
|
+
const { id, depth } = queue.shift();
|
|
2258
|
+
if (visited.has(id) || depth > max_hops) continue;
|
|
2259
|
+
visited.add(id);
|
|
2260
|
+
const node = graph.getNode(id);
|
|
2261
|
+
if (node) {
|
|
2262
|
+
affected.set(id, { name: node.name, kind: node.kind, depth });
|
|
2263
|
+
}
|
|
2264
|
+
if (direction === "callers" || direction === "both") {
|
|
220
2265
|
for (const edge of graph.findEdgesTo(id)) {
|
|
221
|
-
|
|
222
|
-
|
|
2266
|
+
if (edge.kind === "calls" || edge.kind === "imports") {
|
|
2267
|
+
queue.push({ id: edge.source, depth: depth + 1 });
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
if (direction === "callees" || direction === "both") {
|
|
2272
|
+
for (const edge of graph.findEdgesFrom(id)) {
|
|
2273
|
+
if (edge.kind === "calls" || edge.kind === "imports") {
|
|
2274
|
+
queue.push({ id: edge.target, depth: depth + 1 });
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
res.json({
|
|
2280
|
+
target: targetNode.name,
|
|
2281
|
+
affectedCount: [...affected.values()].filter((a) => a.depth > 0).length,
|
|
2282
|
+
affected: [...affected.entries()].map(([id, info]) => ({ id, ...info })).filter((a) => a.depth > 0)
|
|
2283
|
+
});
|
|
2284
|
+
});
|
|
2285
|
+
app.get("/api/flows", (_req, res) => {
|
|
2286
|
+
const flows = [];
|
|
2287
|
+
for (const node of graph.allNodes()) {
|
|
2288
|
+
if (node.kind === "flow") {
|
|
2289
|
+
flows.push({
|
|
2290
|
+
id: node.id,
|
|
2291
|
+
name: node.name,
|
|
2292
|
+
steps: node.metadata?.steps
|
|
2293
|
+
});
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
res.json({ flows });
|
|
2297
|
+
});
|
|
2298
|
+
app.get("/api/clusters", (_req, res) => {
|
|
2299
|
+
const clusters = [];
|
|
2300
|
+
for (const node of graph.allNodes()) {
|
|
2301
|
+
if (node.kind === "cluster") {
|
|
2302
|
+
clusters.push({
|
|
2303
|
+
id: node.id,
|
|
2304
|
+
name: node.name,
|
|
2305
|
+
memberCount: node.metadata?.memberCount ?? 0
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
res.json({ clusters });
|
|
2310
|
+
});
|
|
2311
|
+
app.get("/api/groups", (_req, res) => {
|
|
2312
|
+
const groups = listGroups();
|
|
2313
|
+
res.json(groups.map((g) => ({
|
|
2314
|
+
name: g.name,
|
|
2315
|
+
memberCount: g.members.length,
|
|
2316
|
+
lastSync: g.lastSync ?? null,
|
|
2317
|
+
createdAt: g.createdAt
|
|
2318
|
+
})));
|
|
2319
|
+
});
|
|
2320
|
+
app.get("/api/groups/:name", (req, res) => {
|
|
2321
|
+
const group = loadGroup(req.params.name);
|
|
2322
|
+
if (!group) {
|
|
2323
|
+
res.status(404).json({ error: "Group not found" });
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
res.json(group);
|
|
2327
|
+
});
|
|
2328
|
+
app.get("/api/groups/:name/contracts", (req, res) => {
|
|
2329
|
+
const result = loadSyncResult(req.params.name);
|
|
2330
|
+
if (!result) {
|
|
2331
|
+
res.status(404).json({ error: "No sync result. Run sync first." });
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
res.json(result);
|
|
2335
|
+
});
|
|
2336
|
+
app.post("/api/groups/:name/sync", async (req, res) => {
|
|
2337
|
+
const group = loadGroup(req.params.name);
|
|
2338
|
+
if (!group) {
|
|
2339
|
+
res.status(404).json({ error: "Group not found" });
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
try {
|
|
2343
|
+
const result = await syncGroup(group);
|
|
2344
|
+
saveSyncResult(result);
|
|
2345
|
+
group.lastSync = result.syncedAt;
|
|
2346
|
+
const { saveGroup: saveGroup2 } = await Promise.resolve().then(() => (init_group_registry(), group_registry_exports));
|
|
2347
|
+
saveGroup2(group);
|
|
2348
|
+
res.json(result);
|
|
2349
|
+
} catch (err) {
|
|
2350
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
2351
|
+
}
|
|
2352
|
+
});
|
|
2353
|
+
app.post("/api/groups/:name/search", async (req, res) => {
|
|
2354
|
+
const group = loadGroup(req.params.name);
|
|
2355
|
+
if (!group) {
|
|
2356
|
+
res.status(404).json({ error: "Group not found" });
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
const { q, limit = 20 } = req.body;
|
|
2360
|
+
if (!q) {
|
|
2361
|
+
res.status(400).json({ error: "Missing query q" });
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
try {
|
|
2365
|
+
const { perRepo, merged } = await queryGroup(group, q, limit);
|
|
2366
|
+
res.json({ perRepo, merged });
|
|
2367
|
+
} catch (err) {
|
|
2368
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
2369
|
+
}
|
|
2370
|
+
});
|
|
2371
|
+
app.get("/api/groups/:name/graph", async (req, res) => {
|
|
2372
|
+
const group = loadGroup(req.params.name);
|
|
2373
|
+
if (!group) {
|
|
2374
|
+
res.status(404).json({ error: "Group not found" });
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
const registry = loadRegistry();
|
|
2378
|
+
const mergedGraph = createKnowledgeGraph();
|
|
2379
|
+
for (const member of group.members) {
|
|
2380
|
+
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
2381
|
+
if (!regEntry) continue;
|
|
2382
|
+
const dbPath = path.join(regEntry.path, ".code-intel", "graph.db");
|
|
2383
|
+
if (!fs8.existsSync(dbPath)) continue;
|
|
2384
|
+
const db = new DbManager(dbPath);
|
|
2385
|
+
try {
|
|
2386
|
+
await db.init();
|
|
2387
|
+
await loadGraphFromDB(mergedGraph, db);
|
|
2388
|
+
db.close();
|
|
2389
|
+
} catch {
|
|
2390
|
+
db.close();
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
res.json({ nodes: [...mergedGraph.allNodes()], edges: [...mergedGraph.allEdges()] });
|
|
2394
|
+
});
|
|
2395
|
+
if (fs8.existsSync(WEB_DIST)) {
|
|
2396
|
+
app.use(express.static(WEB_DIST));
|
|
2397
|
+
app.get("/{*path}", (_req, res) => {
|
|
2398
|
+
res.sendFile(path.join(WEB_DIST, "index.html"));
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2401
|
+
return app;
|
|
2402
|
+
}
|
|
2403
|
+
function startHttpServer(graph, repoName, port = 4747, workspaceRoot) {
|
|
2404
|
+
const app = createApp(graph, repoName, workspaceRoot);
|
|
2405
|
+
app.listen(port, () => {
|
|
2406
|
+
console.log(`Code Intelligence server running at http://localhost:${port}`);
|
|
2407
|
+
console.log(` Graph: ${graph.size.nodes} nodes, ${graph.size.edges} edges`);
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
function createMcpServer(graph, repoName) {
|
|
2411
|
+
const server = new Server(
|
|
2412
|
+
{ name: "code-intel", version: "0.1.0" },
|
|
2413
|
+
{ capabilities: { tools: {}, resources: {} } }
|
|
2414
|
+
);
|
|
2415
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
2416
|
+
tools: [
|
|
2417
|
+
{
|
|
2418
|
+
name: "repos",
|
|
2419
|
+
description: "List indexed repositories",
|
|
2420
|
+
inputSchema: { type: "object", properties: {} }
|
|
2421
|
+
},
|
|
2422
|
+
{
|
|
2423
|
+
name: "search",
|
|
2424
|
+
description: "Hybrid search across the codebase knowledge graph",
|
|
2425
|
+
inputSchema: {
|
|
2426
|
+
type: "object",
|
|
2427
|
+
properties: {
|
|
2428
|
+
query: { type: "string", description: "Search query" },
|
|
2429
|
+
limit: { type: "number", description: "Max results (default 20)" }
|
|
2430
|
+
},
|
|
2431
|
+
required: ["query"]
|
|
2432
|
+
}
|
|
2433
|
+
},
|
|
2434
|
+
{
|
|
2435
|
+
name: "inspect",
|
|
2436
|
+
description: "360\xB0 view of a symbol: definition, callers, callees, heritage, references",
|
|
2437
|
+
inputSchema: {
|
|
2438
|
+
type: "object",
|
|
2439
|
+
properties: {
|
|
2440
|
+
symbol_name: { type: "string", description: "Symbol name to inspect" }
|
|
2441
|
+
},
|
|
2442
|
+
required: ["symbol_name"]
|
|
2443
|
+
}
|
|
2444
|
+
},
|
|
2445
|
+
{
|
|
2446
|
+
name: "blast_radius",
|
|
2447
|
+
description: "Impact analysis: what depends on / is affected by this symbol",
|
|
2448
|
+
inputSchema: {
|
|
2449
|
+
type: "object",
|
|
2450
|
+
properties: {
|
|
2451
|
+
target: { type: "string", description: "Target symbol name" },
|
|
2452
|
+
direction: { type: "string", enum: ["callers", "callees", "both"], description: "Direction to trace" },
|
|
2453
|
+
max_hops: { type: "number", description: "Max hops (default 5)" }
|
|
2454
|
+
},
|
|
2455
|
+
required: ["target"]
|
|
2456
|
+
}
|
|
2457
|
+
},
|
|
2458
|
+
{
|
|
2459
|
+
name: "routes",
|
|
2460
|
+
description: "List route handler mappings in the codebase",
|
|
2461
|
+
inputSchema: { type: "object", properties: {} }
|
|
2462
|
+
},
|
|
2463
|
+
{
|
|
2464
|
+
name: "raw_query",
|
|
2465
|
+
description: "Execute a graph query (simplified Cypher-like)",
|
|
2466
|
+
inputSchema: {
|
|
2467
|
+
type: "object",
|
|
2468
|
+
properties: {
|
|
2469
|
+
cypher: { type: "string", description: "Query string (name='X' or :kind patterns)" }
|
|
2470
|
+
},
|
|
2471
|
+
required: ["cypher"]
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
]
|
|
2475
|
+
}));
|
|
2476
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2477
|
+
const { name, arguments: args } = request.params;
|
|
2478
|
+
const a = args ?? {};
|
|
2479
|
+
switch (name) {
|
|
2480
|
+
case "repos": {
|
|
2481
|
+
return { content: [{ type: "text", text: JSON.stringify([{ name: repoName, nodes: graph.size.nodes, edges: graph.size.edges }], null, 2) }] };
|
|
2482
|
+
}
|
|
2483
|
+
case "search": {
|
|
2484
|
+
const query = a.query;
|
|
2485
|
+
const limit = a.limit ?? 20;
|
|
2486
|
+
const results = textSearch(graph, query, limit);
|
|
2487
|
+
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
2488
|
+
}
|
|
2489
|
+
case "inspect": {
|
|
2490
|
+
const symbolName = a.symbol_name;
|
|
2491
|
+
const node = findNodeByName(graph, symbolName);
|
|
2492
|
+
if (!node) return { content: [{ type: "text", text: `Symbol "${symbolName}" not found` }] };
|
|
2493
|
+
const incoming = [...graph.findEdgesTo(node.id)];
|
|
2494
|
+
const outgoing = [...graph.findEdgesFrom(node.id)];
|
|
2495
|
+
return {
|
|
2496
|
+
content: [{
|
|
2497
|
+
type: "text",
|
|
2498
|
+
text: JSON.stringify({
|
|
2499
|
+
node: { id: node.id, kind: node.kind, name: node.name, filePath: node.filePath, startLine: node.startLine, endLine: node.endLine, exported: node.exported },
|
|
2500
|
+
callers: incoming.filter((e) => e.kind === "calls").map((e) => ({ id: e.source, name: graph.getNode(e.source)?.name })),
|
|
2501
|
+
callees: outgoing.filter((e) => e.kind === "calls").map((e) => ({ id: e.target, name: graph.getNode(e.target)?.name })),
|
|
2502
|
+
extends: outgoing.filter((e) => e.kind === "extends").map((e) => graph.getNode(e.target)?.name),
|
|
2503
|
+
implements: outgoing.filter((e) => e.kind === "implements").map((e) => graph.getNode(e.target)?.name),
|
|
2504
|
+
members: outgoing.filter((e) => e.kind === "has_member").map((e) => ({ name: graph.getNode(e.target)?.name, kind: graph.getNode(e.target)?.kind })),
|
|
2505
|
+
cluster: incoming.filter((e) => e.kind === "belongs_to").map((e) => graph.getNode(e.target)?.name)[0],
|
|
2506
|
+
content: node.content?.slice(0, 500)
|
|
2507
|
+
}, null, 2)
|
|
2508
|
+
}]
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
case "blast_radius": {
|
|
2512
|
+
const target = a.target;
|
|
2513
|
+
const direction = a.direction ?? "both";
|
|
2514
|
+
const maxHops = a.max_hops ?? 5;
|
|
2515
|
+
const node = findNodeByName(graph, target);
|
|
2516
|
+
if (!node) return { content: [{ type: "text", text: `Symbol "${target}" not found` }] };
|
|
2517
|
+
const affected = /* @__PURE__ */ new Set();
|
|
2518
|
+
const queue = [{ id: node.id, depth: 0 }];
|
|
2519
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2520
|
+
while (queue.length > 0) {
|
|
2521
|
+
const { id, depth } = queue.shift();
|
|
2522
|
+
if (visited.has(id) || depth > maxHops) continue;
|
|
2523
|
+
visited.add(id);
|
|
2524
|
+
affected.add(id);
|
|
2525
|
+
if (direction === "callers" || direction === "both") {
|
|
2526
|
+
for (const edge of graph.findEdgesTo(id)) {
|
|
2527
|
+
if (edge.kind === "calls" || edge.kind === "imports") queue.push({ id: edge.source, depth: depth + 1 });
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
if (direction === "callees" || direction === "both") {
|
|
2531
|
+
for (const edge of graph.findEdgesFrom(id)) {
|
|
2532
|
+
if (edge.kind === "calls" || edge.kind === "imports") queue.push({ id: edge.target, depth: depth + 1 });
|
|
223
2533
|
}
|
|
2534
|
+
}
|
|
224
2535
|
}
|
|
2536
|
+
const affectedDetails = [...affected].map((id) => {
|
|
2537
|
+
const n = graph.getNode(id);
|
|
2538
|
+
return n ? { id, name: n.name, kind: n.kind, filePath: n.filePath } : { id };
|
|
2539
|
+
});
|
|
2540
|
+
return { content: [{ type: "text", text: JSON.stringify({ target: node.name, affectedCount: affected.size, affected: affectedDetails }, null, 2) }] };
|
|
2541
|
+
}
|
|
2542
|
+
case "routes": {
|
|
2543
|
+
const routes = [];
|
|
2544
|
+
for (const node of graph.allNodes()) {
|
|
2545
|
+
if (node.kind === "route" || node.kind === "function" && /route|handler|controller/i.test(node.filePath)) {
|
|
2546
|
+
routes.push({ name: node.name, filePath: node.filePath });
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
return { content: [{ type: "text", text: JSON.stringify(routes, null, 2) }] };
|
|
2550
|
+
}
|
|
2551
|
+
case "raw_query": {
|
|
2552
|
+
const q = a.cypher;
|
|
2553
|
+
const nameMatch = q?.match(/name\s*=\s*['"]([^'"]+)['"]/i);
|
|
2554
|
+
if (nameMatch) {
|
|
2555
|
+
const results = [];
|
|
2556
|
+
for (const node of graph.allNodes()) {
|
|
2557
|
+
if (node.name === nameMatch[1]) results.push(node);
|
|
2558
|
+
}
|
|
2559
|
+
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
2560
|
+
}
|
|
2561
|
+
const kindMatch = q?.match(/:\s*(\w+)/);
|
|
2562
|
+
if (kindMatch) {
|
|
2563
|
+
const results = [];
|
|
2564
|
+
for (const node of graph.allNodes()) {
|
|
2565
|
+
if (node.kind === kindMatch[1]) results.push(node);
|
|
2566
|
+
if (results.length >= 50) break;
|
|
2567
|
+
}
|
|
2568
|
+
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
2569
|
+
}
|
|
2570
|
+
return { content: [{ type: "text", text: "Query not recognized" }] };
|
|
2571
|
+
}
|
|
2572
|
+
default:
|
|
2573
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
2577
|
+
resources: [
|
|
2578
|
+
{ uri: `codeintel://repo/${repoName}/overview`, name: `${repoName} Overview`, mimeType: "application/json" },
|
|
2579
|
+
{ uri: `codeintel://repo/${repoName}/clusters`, name: `${repoName} Clusters`, mimeType: "application/json" },
|
|
2580
|
+
{ uri: `codeintel://repo/${repoName}/flows`, name: `${repoName} Flows`, mimeType: "application/json" }
|
|
2581
|
+
]
|
|
2582
|
+
}));
|
|
2583
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
2584
|
+
const { uri } = request.params;
|
|
2585
|
+
if (uri.endsWith("/overview")) {
|
|
2586
|
+
const kindCounts = {};
|
|
2587
|
+
for (const node of graph.allNodes()) {
|
|
2588
|
+
kindCounts[node.kind] = (kindCounts[node.kind] ?? 0) + 1;
|
|
2589
|
+
}
|
|
2590
|
+
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ repo: repoName, stats: graph.size, nodeCounts: kindCounts }) }] };
|
|
2591
|
+
}
|
|
2592
|
+
if (uri.endsWith("/clusters")) {
|
|
2593
|
+
const clusters = [];
|
|
2594
|
+
for (const node of graph.allNodes()) {
|
|
2595
|
+
if (node.kind === "cluster") clusters.push({ id: node.id, name: node.name, memberCount: node.metadata?.memberCount });
|
|
2596
|
+
}
|
|
2597
|
+
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(clusters) }] };
|
|
2598
|
+
}
|
|
2599
|
+
if (uri.endsWith("/flows")) {
|
|
2600
|
+
const flows = [];
|
|
2601
|
+
for (const node of graph.allNodes()) {
|
|
2602
|
+
if (node.kind === "flow") flows.push({ id: node.id, name: node.name, steps: node.metadata?.steps, entryPoint: node.metadata?.entryPoint });
|
|
2603
|
+
}
|
|
2604
|
+
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(flows) }] };
|
|
2605
|
+
}
|
|
2606
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
2607
|
+
});
|
|
2608
|
+
return server;
|
|
2609
|
+
}
|
|
2610
|
+
async function startMcpStdio(graph, repoName) {
|
|
2611
|
+
const server = createMcpServer(graph, repoName);
|
|
2612
|
+
const transport = new StdioServerTransport();
|
|
2613
|
+
await server.connect(transport);
|
|
2614
|
+
}
|
|
2615
|
+
function findNodeByName(graph, name) {
|
|
2616
|
+
for (const node of graph.allNodes()) {
|
|
2617
|
+
if (node.name === name) return node;
|
|
2618
|
+
}
|
|
2619
|
+
return void 0;
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
// src/cli/main.ts
|
|
2623
|
+
init_metadata();
|
|
2624
|
+
async function writeSkillFiles(graph, workspaceRoot, projectName) {
|
|
2625
|
+
const outputDir = path.join(workspaceRoot, ".claude", "skills", "code-intel");
|
|
2626
|
+
const areas = buildAreaMap(graph, workspaceRoot);
|
|
2627
|
+
if (areas.length === 0) return { skills: [], outputDir };
|
|
2628
|
+
fs8.rmSync(outputDir, { recursive: true, force: true });
|
|
2629
|
+
fs8.mkdirSync(outputDir, { recursive: true });
|
|
2630
|
+
const skills = [];
|
|
2631
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
2632
|
+
for (const area of areas) {
|
|
2633
|
+
const kebab = uniqueKebab(area.label, usedNames);
|
|
2634
|
+
usedNames.add(kebab);
|
|
2635
|
+
const content = renderSkill(area, projectName, kebab);
|
|
2636
|
+
const dir = path.join(outputDir, kebab);
|
|
2637
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
2638
|
+
fs8.writeFileSync(path.join(dir, "SKILL.md"), content, "utf-8");
|
|
2639
|
+
skills.push({ name: kebab, label: area.label, symbolCount: area.nodes.length, fileCount: area.files.size });
|
|
2640
|
+
}
|
|
2641
|
+
return { skills, outputDir };
|
|
2642
|
+
}
|
|
2643
|
+
function buildAreaMap(graph, workspaceRoot) {
|
|
2644
|
+
const clusterLabel = /* @__PURE__ */ new Map();
|
|
2645
|
+
const clusterMembers = /* @__PURE__ */ new Map();
|
|
2646
|
+
for (const node of graph.allNodes()) {
|
|
2647
|
+
if (node.kind === "cluster") {
|
|
2648
|
+
clusterLabel.set(node.id, node.name);
|
|
2649
|
+
clusterMembers.set(node.id, []);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
for (const edge of graph.findEdgesByKind("belongs_to")) {
|
|
2653
|
+
const bucket = clusterMembers.get(edge.target);
|
|
2654
|
+
const node = graph.getNode(edge.source);
|
|
2655
|
+
if (bucket && node) bucket.push(node);
|
|
2656
|
+
}
|
|
2657
|
+
const inDeg = /* @__PURE__ */ new Map();
|
|
2658
|
+
const outDeg = /* @__PURE__ */ new Map();
|
|
2659
|
+
const calledIds = /* @__PURE__ */ new Set();
|
|
2660
|
+
for (const edge of graph.findEdgesByKind("calls")) {
|
|
2661
|
+
calledIds.add(edge.target);
|
|
2662
|
+
inDeg.set(edge.target, (inDeg.get(edge.target) ?? 0) + 1);
|
|
2663
|
+
outDeg.set(edge.source, (outDeg.get(edge.source) ?? 0) + 1);
|
|
2664
|
+
}
|
|
2665
|
+
const areas = [];
|
|
2666
|
+
for (const [clusterId, members] of clusterMembers) {
|
|
2667
|
+
if (members.length < 3) continue;
|
|
2668
|
+
const label = clusterLabel.get(clusterId) ?? "unknown";
|
|
2669
|
+
const dir = members[0]?.filePath.split("/").slice(0, -1).join("/") ?? "";
|
|
2670
|
+
const files = /* @__PURE__ */ new Map();
|
|
2671
|
+
for (const n of members) {
|
|
2672
|
+
const rel = relPath(n.filePath, workspaceRoot);
|
|
2673
|
+
let list = files.get(rel);
|
|
2674
|
+
if (!list) {
|
|
2675
|
+
list = [];
|
|
2676
|
+
files.set(rel, list);
|
|
2677
|
+
}
|
|
2678
|
+
list.push(n);
|
|
2679
|
+
}
|
|
2680
|
+
const entryPoints = members.filter((n) => n.exported && !calledIds.has(n.id) && ["function", "method", "class"].includes(n.kind)).slice(0, 6);
|
|
2681
|
+
const memberIds = new Set(members.map((n) => n.id));
|
|
2682
|
+
const hotNodes = members.map((n) => ({ node: n, inDeg: inDeg.get(n.id) ?? 0, outDeg: outDeg.get(n.id) ?? 0 })).sort((a, b) => b.inDeg + b.outDeg - (a.inDeg + a.outDeg)).slice(0, 12);
|
|
2683
|
+
let callEdgesInArea = 0;
|
|
2684
|
+
for (const edge of graph.findEdgesByKind("calls")) {
|
|
2685
|
+
if (memberIds.has(edge.source) && memberIds.has(edge.target)) callEdgesInArea++;
|
|
2686
|
+
}
|
|
2687
|
+
const flowIds = [];
|
|
2688
|
+
for (const node of graph.allNodes()) {
|
|
2689
|
+
if (node.kind !== "flow") continue;
|
|
2690
|
+
const steps = node.metadata?.steps;
|
|
2691
|
+
if (steps?.some((s) => memberIds.has(s))) flowIds.push(node.id);
|
|
2692
|
+
if (flowIds.length >= 8) break;
|
|
2693
|
+
}
|
|
2694
|
+
areas.push({ label, dir, nodes: members, files, entryPoints, hotNodes, callEdgesInArea, flowIds });
|
|
2695
|
+
}
|
|
2696
|
+
areas.sort((a, b) => b.nodes.length - a.nodes.length);
|
|
2697
|
+
return areas.slice(0, 20);
|
|
2698
|
+
}
|
|
2699
|
+
function renderSkill(area, projectName, kebabName) {
|
|
2700
|
+
const density = area.nodes.length > 0 ? Math.round(area.callEdgesInArea / area.nodes.length * 10) / 10 : 0;
|
|
2701
|
+
const topEntryNames = area.entryPoints.slice(0, 3).map((n) => n.name);
|
|
2702
|
+
const topHotNames = area.hotNodes.slice(0, 3).map((h) => h.node.name);
|
|
2703
|
+
const triggerNames = topEntryNames.length > 0 ? topEntryNames : topHotNames;
|
|
2704
|
+
const triggerStr = triggerNames.map((n) => `\`${n}\``).join(", ");
|
|
2705
|
+
const dirs = [...area.files.keys()].map((f) => f.split("/").slice(0, -1).join("/") || ".");
|
|
2706
|
+
const dirCounts = /* @__PURE__ */ new Map();
|
|
2707
|
+
for (const d of dirs) dirCounts.set(d, (dirCounts.get(d) ?? 0) + 1);
|
|
2708
|
+
const dominantDir = [...dirCounts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? area.dir;
|
|
2709
|
+
const lines = [];
|
|
2710
|
+
const description = [
|
|
2711
|
+
`Covers the **${area.label}** subsystem of ${projectName}.`,
|
|
2712
|
+
`${area.nodes.length} symbols across ${area.files.size} files.`,
|
|
2713
|
+
triggerStr ? `Key symbols: ${triggerStr}.` : "",
|
|
2714
|
+
`Internal call density: ${density} calls/symbol.`,
|
|
2715
|
+
area.flowIds.length > 0 ? `Participates in ${area.flowIds.length} execution flow(s).` : ""
|
|
2716
|
+
].filter(Boolean).join(" ");
|
|
2717
|
+
lines.push("---");
|
|
2718
|
+
lines.push(`name: ${kebabName}`);
|
|
2719
|
+
lines.push(`description: "${description.replace(/"/g, "'")}"`);
|
|
2720
|
+
lines.push("---", "");
|
|
2721
|
+
lines.push(`# ${area.label}`);
|
|
2722
|
+
lines.push("");
|
|
2723
|
+
lines.push(`> **${area.nodes.length} symbols** | **${area.files.size} files** | path: \`${dominantDir}/\` | call density: ${density}/sym`);
|
|
2724
|
+
lines.push("");
|
|
2725
|
+
lines.push("## When to Use");
|
|
2726
|
+
lines.push("");
|
|
2727
|
+
lines.push("Load this skill when:");
|
|
2728
|
+
lines.push(`- The task involves code in \`${dominantDir}/\``);
|
|
2729
|
+
if (triggerStr) lines.push(`- The user mentions ${triggerStr} or asks how they work`);
|
|
2730
|
+
lines.push(`- Adding, modifying, or debugging ${area.label.toLowerCase()}-related functionality`);
|
|
2731
|
+
lines.push(`- Tracing call chains that pass through the ${area.label} layer`);
|
|
2732
|
+
lines.push("");
|
|
2733
|
+
lines.push("## Key Files");
|
|
2734
|
+
lines.push("");
|
|
2735
|
+
lines.push("| File | Symbols | Notes |");
|
|
2736
|
+
lines.push("|------|---------|-------|");
|
|
2737
|
+
const sortedFiles = [...area.files.entries()].sort((a, b) => b[1].length - a[1].length);
|
|
2738
|
+
for (const [file, nodes] of sortedFiles.slice(0, 10)) {
|
|
2739
|
+
const exported = nodes.filter((n) => n.exported);
|
|
2740
|
+
const names = nodes.slice(0, 4).map((n) => `\`${n.name}\``).join(", ");
|
|
2741
|
+
const extra = nodes.length > 4 ? ` +(${nodes.length - 4})` : "";
|
|
2742
|
+
const note = exported.length > 0 ? `${exported.length} exported` : "internal";
|
|
2743
|
+
lines.push(`| \`${file}\` | ${names}${extra} | ${note} |`);
|
|
2744
|
+
}
|
|
2745
|
+
lines.push("");
|
|
2746
|
+
if (area.entryPoints.length > 0) {
|
|
2747
|
+
lines.push("## Entry Points");
|
|
2748
|
+
lines.push("");
|
|
2749
|
+
lines.push("Start exploration here \u2014 exported symbols with no external callers:");
|
|
2750
|
+
lines.push("");
|
|
2751
|
+
for (const ep of area.entryPoints) {
|
|
2752
|
+
const loc = ep.startLine ? `:${ep.startLine}` : "";
|
|
2753
|
+
lines.push(`- **\`${ep.name}\`** \`(${ep.kind})\` \u2192 \`${ep.filePath}${loc}\``);
|
|
2754
|
+
}
|
|
2755
|
+
lines.push("");
|
|
2756
|
+
}
|
|
2757
|
+
lines.push("## Hot Symbols");
|
|
2758
|
+
lines.push("");
|
|
2759
|
+
lines.push("Sorted by call graph degree (changing these has the highest blast radius):");
|
|
2760
|
+
lines.push("");
|
|
2761
|
+
lines.push("| Symbol | Kind | In \u2190 | \u2192 Out | File |");
|
|
2762
|
+
lines.push("|--------|------|-----:|------:|------|");
|
|
2763
|
+
for (const { node: n, inDeg: i, outDeg: o } of area.hotNodes) {
|
|
2764
|
+
lines.push(`| \`${n.name}\` | ${n.kind} | ${i} | ${o} | \`${relFile(n.filePath)}\` |`);
|
|
2765
|
+
}
|
|
2766
|
+
lines.push("");
|
|
2767
|
+
if (area.flowIds.length > 0) {
|
|
2768
|
+
lines.push("## Execution Flows");
|
|
2769
|
+
lines.push("");
|
|
2770
|
+
lines.push(`**${area.flowIds.length}** execution path(s) pass through this area.`);
|
|
2771
|
+
lines.push("Run `code-intel inspect <symbol>` on a hot symbol to trace the full call chain.");
|
|
2772
|
+
lines.push("");
|
|
2773
|
+
}
|
|
2774
|
+
lines.push("## Impact Guidance");
|
|
2775
|
+
lines.push("");
|
|
2776
|
+
lines.push("Before modifying any symbol in this area:");
|
|
2777
|
+
lines.push(`1. **High-degree symbols** (In \u2190 \u2265 3) \u2014 check all callers before changing signatures`);
|
|
2778
|
+
lines.push(`2. **Entry points** \u2014 changes propagate to external consumers`);
|
|
2779
|
+
lines.push(`3. Run \`code-intel impact <symbol>\` to get full blast radius`);
|
|
2780
|
+
lines.push("");
|
|
2781
|
+
const firstHot = area.hotNodes[0]?.node.name ?? area.nodes[0]?.name ?? area.label;
|
|
2782
|
+
const firstEntry = area.entryPoints[0]?.name ?? firstHot;
|
|
2783
|
+
lines.push("## Quick Commands");
|
|
2784
|
+
lines.push("");
|
|
2785
|
+
lines.push("```bash");
|
|
2786
|
+
lines.push(`# Inspect most-connected symbol`);
|
|
2787
|
+
lines.push(`code-intel inspect ${firstHot}`);
|
|
2788
|
+
lines.push(`# Blast radius for entry point`);
|
|
2789
|
+
lines.push(`code-intel impact ${firstEntry}`);
|
|
2790
|
+
lines.push(`# Search this area`);
|
|
2791
|
+
lines.push(`code-intel search "${area.label.toLowerCase()}"`);
|
|
2792
|
+
lines.push("```");
|
|
2793
|
+
lines.push("");
|
|
2794
|
+
return lines.join("\n");
|
|
2795
|
+
}
|
|
2796
|
+
function uniqueKebab(label, used) {
|
|
2797
|
+
let base = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50) || "skill";
|
|
2798
|
+
let candidate = base;
|
|
2799
|
+
let n = 2;
|
|
2800
|
+
while (used.has(candidate)) {
|
|
2801
|
+
candidate = `${base}-${n++}`;
|
|
2802
|
+
}
|
|
2803
|
+
return candidate;
|
|
2804
|
+
}
|
|
2805
|
+
function relPath(filePath, workspaceRoot) {
|
|
2806
|
+
const norm = filePath.replace(/\\/g, "/");
|
|
2807
|
+
const root = workspaceRoot.replace(/\\/g, "/").replace(/\/?$/, "/");
|
|
2808
|
+
return norm.startsWith(root) ? norm.slice(root.length) : norm.replace(/^\//, "");
|
|
2809
|
+
}
|
|
2810
|
+
function relFile(filePath) {
|
|
2811
|
+
const parts = filePath.replace(/\\/g, "/").split("/");
|
|
2812
|
+
return parts.slice(-2).join("/");
|
|
2813
|
+
}
|
|
2814
|
+
var BLOCK_START = "<!-- code-intel:start -->";
|
|
2815
|
+
var BLOCK_END = "<!-- code-intel:end -->";
|
|
2816
|
+
function writeContextFiles(workspaceRoot, projectName, stats, skills) {
|
|
2817
|
+
const block = buildBlock(projectName, stats, skills);
|
|
2818
|
+
upsertFile(path.join(workspaceRoot, "AGENTS.md"), block);
|
|
2819
|
+
upsertFile(path.join(workspaceRoot, "CLAUDE.md"), block);
|
|
2820
|
+
}
|
|
2821
|
+
function buildBlock(projectName, stats, skills) {
|
|
2822
|
+
const skillRows = skills.map((s) => `| Work in \`${s.label}\` (${s.symbolCount} symbols) | \`.claude/skills/code-intel/${s.name}/SKILL.md\` |`).join("\n");
|
|
2823
|
+
const skillTable = `| Task | Skill file |
|
|
2824
|
+
|------|------------|
|
|
2825
|
+
| Understand architecture / "How does X work?" | Load \`code-intel-exploring\` skill |
|
|
2826
|
+
| Blast radius / "What breaks if I change X?" | Load \`code-intel-impact\` skill |
|
|
2827
|
+
| Debugging / "Why is X failing?" | Load \`code-intel-debugging\` skill |
|
|
2828
|
+
${skillRows ? skillRows + "\n" : ""}`;
|
|
2829
|
+
return `${BLOCK_START}
|
|
2830
|
+
# Code Intelligence \u2014 ${projectName}
|
|
2831
|
+
|
|
2832
|
+
Indexed: **${stats.nodes.toLocaleString()} nodes** | **${stats.edges.toLocaleString()} edges** | **${stats.files} files** | analyzed in ${(stats.duration / 1e3).toFixed(1)}s
|
|
2833
|
+
|
|
2834
|
+
> If the index is stale, re-run: \`code-intel analyze\`
|
|
2835
|
+
|
|
2836
|
+
## Always Do
|
|
2837
|
+
|
|
2838
|
+
- **Before editing any symbol**, run \`code-intel impact <symbol>\` and review blast radius.
|
|
2839
|
+
- **Before committing**, verify scope with \`code-intel inspect <symbol>\`.
|
|
2840
|
+
- Use \`code-intel search "<concept>"\` to find related symbols instead of grepping.
|
|
2841
|
+
- Warn the user if impact shows \u2265 5 direct callers (HIGH risk).
|
|
2842
|
+
|
|
2843
|
+
## Never Do
|
|
2844
|
+
|
|
2845
|
+
- NEVER rename symbols with find-and-replace \u2014 use \`code-intel inspect\` to find all usages first.
|
|
2846
|
+
- NEVER ignore impact warnings \u2014 always report blast radius to the user.
|
|
2847
|
+
|
|
2848
|
+
## CLI Quick Reference
|
|
2849
|
+
|
|
2850
|
+
\`\`\`bash
|
|
2851
|
+
code-intel analyze [path] # Build / refresh the knowledge graph
|
|
2852
|
+
code-intel serve [path] # Start HTTP API + Web UI on :4747
|
|
2853
|
+
code-intel search <query> # Text search across all symbols
|
|
2854
|
+
code-intel inspect <symbol> # Callers, callees, imports, cluster
|
|
2855
|
+
code-intel impact <symbol> # Blast radius (who breaks if this changes)
|
|
2856
|
+
code-intel status [path] # Index freshness and stats
|
|
2857
|
+
code-intel clean [path] # Remove index data
|
|
2858
|
+
\`\`\`
|
|
2859
|
+
|
|
2860
|
+
## Skills
|
|
2861
|
+
|
|
2862
|
+
${skillTable}
|
|
2863
|
+
${BLOCK_END}`;
|
|
2864
|
+
}
|
|
2865
|
+
function upsertFile(filePath, block) {
|
|
2866
|
+
if (!fs8.existsSync(filePath)) {
|
|
2867
|
+
fs8.writeFileSync(filePath, block + "\n", "utf-8");
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
const existing = fs8.readFileSync(filePath, "utf-8");
|
|
2871
|
+
const startIdx = findLineMarker(existing, BLOCK_START);
|
|
2872
|
+
const endIdx = findLineMarker(existing, BLOCK_END, startIdx === -1 ? 0 : startIdx);
|
|
2873
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
2874
|
+
const before = existing.slice(0, startIdx);
|
|
2875
|
+
const after = existing.slice(endIdx + BLOCK_END.length);
|
|
2876
|
+
fs8.writeFileSync(filePath, (before + block + after).trimEnd() + "\n", "utf-8");
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
fs8.writeFileSync(filePath, existing.trimEnd() + "\n\n" + block + "\n", "utf-8");
|
|
2880
|
+
}
|
|
2881
|
+
function findLineMarker(content, marker, startFrom = 0) {
|
|
2882
|
+
let idx = content.indexOf(marker, startFrom);
|
|
2883
|
+
while (idx !== -1) {
|
|
2884
|
+
const atStart = idx === 0 || content[idx - 1] === "\n";
|
|
2885
|
+
const end = idx + marker.length;
|
|
2886
|
+
const atEnd = end === content.length || content[end] === "\n" || content[end] === "\r";
|
|
2887
|
+
if (atStart && atEnd) return idx;
|
|
2888
|
+
idx = content.indexOf(marker, idx + 1);
|
|
2889
|
+
}
|
|
2890
|
+
return -1;
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
// src/cli/main.ts
|
|
2894
|
+
init_repo_registry();
|
|
2895
|
+
init_storage();
|
|
2896
|
+
init_group_registry();
|
|
2897
|
+
var __filename$1 = fileURLToPath(import.meta.url);
|
|
2898
|
+
var __dirname2 = dirname(__filename$1);
|
|
2899
|
+
var _pkg = JSON.parse(readFileSync(join(__dirname2, "../../package.json"), "utf-8"));
|
|
2900
|
+
var program = new Command();
|
|
2901
|
+
program.name("code-intel").description("Code Intelligence Platform \u2014 Static Analysis + Knowledge Graph").version(_pkg.version);
|
|
2902
|
+
async function analyzeWorkspace(targetPath, options) {
|
|
2903
|
+
const workspaceRoot = path.resolve(targetPath);
|
|
2904
|
+
if (!options?.silent) console.log(`Analyzing: ${workspaceRoot}`);
|
|
2905
|
+
if (!options?.skipGit) {
|
|
2906
|
+
const gitDir = path.join(workspaceRoot, ".git");
|
|
2907
|
+
if (!fs8.existsSync(gitDir)) {
|
|
2908
|
+
console.warn(` Warning: ${workspaceRoot} is not a Git repository. Use --skip-git to suppress this warning.`);
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
const graph = createKnowledgeGraph();
|
|
2912
|
+
const context = {
|
|
2913
|
+
workspaceRoot,
|
|
2914
|
+
graph,
|
|
2915
|
+
filePaths: [],
|
|
2916
|
+
verbose: options?.verbose,
|
|
2917
|
+
onProgress: options?.silent ? void 0 : (phase, msg) => console.log(` [${phase}] ${msg}`)
|
|
2918
|
+
};
|
|
2919
|
+
const phases = [scanPhase, structurePhase, parsePhase, resolvePhase, clusterPhase, flowPhase];
|
|
2920
|
+
const result = await runPipeline(phases, context);
|
|
2921
|
+
const repoName = path.basename(workspaceRoot);
|
|
2922
|
+
saveMetadata(workspaceRoot, {
|
|
2923
|
+
indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2924
|
+
stats: {
|
|
2925
|
+
nodes: graph.size.nodes,
|
|
2926
|
+
edges: graph.size.edges,
|
|
2927
|
+
files: context.filePaths.length,
|
|
2928
|
+
duration: result.totalDuration
|
|
2929
|
+
}
|
|
2930
|
+
});
|
|
2931
|
+
upsertRepo({
|
|
2932
|
+
name: repoName,
|
|
2933
|
+
path: workspaceRoot,
|
|
2934
|
+
indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2935
|
+
stats: {
|
|
2936
|
+
nodes: graph.size.nodes,
|
|
2937
|
+
edges: graph.size.edges,
|
|
2938
|
+
files: context.filePaths.length
|
|
2939
|
+
}
|
|
2940
|
+
});
|
|
2941
|
+
try {
|
|
2942
|
+
const dbPath = getDbPath(workspaceRoot);
|
|
2943
|
+
const db = new DbManager(dbPath);
|
|
2944
|
+
await db.init();
|
|
2945
|
+
const { nodeCount, edgeCount } = await loadGraphToDB(graph, db);
|
|
2946
|
+
db.close();
|
|
2947
|
+
if (!options?.silent) {
|
|
2948
|
+
console.log(` DB: ${nodeCount} nodes, ${edgeCount} edges persisted`);
|
|
2949
|
+
}
|
|
2950
|
+
} catch (err) {
|
|
2951
|
+
if (!options?.silent) {
|
|
2952
|
+
console.warn(` DB persist warning: ${err instanceof Error ? err.message : err}`);
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
const doEmbeddings = options?.embeddings && !options?.skipEmbeddings;
|
|
2956
|
+
if (doEmbeddings) {
|
|
2957
|
+
if (!options?.silent) console.log(" Embeddings: building vector index\u2026");
|
|
2958
|
+
try {
|
|
2959
|
+
const { embedNodes: embedNodes2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
|
|
2960
|
+
const { getVectorDbPath: getVectorDbPath2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
|
|
2961
|
+
const { VectorIndex: VectorIndex2 } = await Promise.resolve().then(() => (init_vector_index(), vector_index_exports));
|
|
2962
|
+
const vdbPath = getVectorDbPath2(workspaceRoot);
|
|
2963
|
+
const vdb = new DbManager(vdbPath);
|
|
2964
|
+
await vdb.init();
|
|
2965
|
+
const idx = new VectorIndex2(vdb);
|
|
2966
|
+
await idx.init();
|
|
2967
|
+
const nodes = await embedNodes2(graph, {
|
|
2968
|
+
onProgress: (done, total) => {
|
|
2969
|
+
if (!options?.silent) process.stdout.write(`\r [vector] ${done}/${total}`);
|
|
2970
|
+
}
|
|
2971
|
+
});
|
|
2972
|
+
if (!options?.silent) console.log("");
|
|
2973
|
+
await idx.buildIndex(nodes);
|
|
2974
|
+
if (!options?.silent) console.log(` Embeddings: ${nodes.length} vectors built`);
|
|
2975
|
+
vdb.close();
|
|
2976
|
+
} catch (err) {
|
|
2977
|
+
if (!options?.silent) {
|
|
2978
|
+
console.warn(` Embeddings warning: ${err instanceof Error ? err.message : err}`);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
} else if (!options?.skipEmbeddings && !options?.silent) {
|
|
2982
|
+
console.log(" Embeddings: skipped (use --embeddings to enable)");
|
|
2983
|
+
}
|
|
2984
|
+
const doSkills = options?.skills !== false;
|
|
2985
|
+
let skillSummaries = [];
|
|
2986
|
+
if (doSkills) {
|
|
2987
|
+
try {
|
|
2988
|
+
const { skills } = await writeSkillFiles(graph, workspaceRoot, repoName);
|
|
2989
|
+
skillSummaries = skills;
|
|
2990
|
+
if (!options?.silent && skills.length > 0) {
|
|
2991
|
+
console.log(` Skills: ${skills.length} generated \u2192 .claude/skills/code-intel/`);
|
|
2992
|
+
}
|
|
2993
|
+
} catch (err) {
|
|
2994
|
+
if (!options?.silent) {
|
|
2995
|
+
console.warn(` Skills warning: ${err instanceof Error ? err.message : err}`);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
if (!options?.skipAgentsMd) {
|
|
3000
|
+
try {
|
|
3001
|
+
writeContextFiles(workspaceRoot, repoName, {
|
|
3002
|
+
nodes: graph.size.nodes,
|
|
3003
|
+
edges: graph.size.edges,
|
|
3004
|
+
files: context.filePaths.length,
|
|
3005
|
+
duration: result.totalDuration
|
|
3006
|
+
}, skillSummaries);
|
|
3007
|
+
if (!options?.silent) {
|
|
3008
|
+
console.log(` Context: AGENTS.md + CLAUDE.md updated`);
|
|
3009
|
+
}
|
|
3010
|
+
} catch (err) {
|
|
3011
|
+
if (!options?.silent) {
|
|
3012
|
+
console.warn(` Context warning: ${err instanceof Error ? err.message : err}`);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
if (!options?.silent) {
|
|
3017
|
+
console.log(`
|
|
3018
|
+
Done in ${result.totalDuration}ms`);
|
|
3019
|
+
console.log(` Nodes: ${graph.size.nodes}`);
|
|
3020
|
+
console.log(` Edges: ${graph.size.edges}`);
|
|
3021
|
+
console.log(` Files: ${context.filePaths.length}`);
|
|
3022
|
+
console.log(` Success: ${result.success}`);
|
|
3023
|
+
}
|
|
3024
|
+
return { graph, result, repoName, workspaceRoot };
|
|
3025
|
+
}
|
|
3026
|
+
program.command("setup").description("Configure MCP server for your editors (one-time setup)").action(() => {
|
|
3027
|
+
const configDir = process.env.HOME ? `${process.env.HOME}/.config/claude` : null;
|
|
3028
|
+
console.log("\n\u{1F4E1} Code Intelligence MCP Setup\n");
|
|
3029
|
+
console.log("Add the following to your editor MCP configuration:\n");
|
|
3030
|
+
const mcpConfig = {
|
|
3031
|
+
mcpServers: {
|
|
3032
|
+
"code-intel": {
|
|
3033
|
+
command: "npx",
|
|
3034
|
+
args: ["@vohongtho.infotech/code-intel", "mcp", "."]
|
|
3035
|
+
}
|
|
225
3036
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
3037
|
+
};
|
|
3038
|
+
console.log("For Claude Desktop / Claude Code (~/.config/claude/claude_desktop_config.json):");
|
|
3039
|
+
console.log(JSON.stringify(mcpConfig, null, 2));
|
|
3040
|
+
if (configDir) {
|
|
3041
|
+
const configFile = `${configDir}/claude_desktop_config.json`;
|
|
3042
|
+
try {
|
|
3043
|
+
let existing = {};
|
|
3044
|
+
if (fs8.existsSync(configFile)) {
|
|
3045
|
+
existing = JSON.parse(fs8.readFileSync(configFile, "utf-8"));
|
|
3046
|
+
}
|
|
3047
|
+
const merged = {
|
|
3048
|
+
...existing,
|
|
3049
|
+
mcpServers: {
|
|
3050
|
+
...existing.mcpServers ?? {},
|
|
3051
|
+
...mcpConfig.mcpServers
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
fs8.mkdirSync(configDir, { recursive: true });
|
|
3055
|
+
fs8.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
3056
|
+
console.log(`
|
|
3057
|
+
\u2705 Written to ${configFile}`);
|
|
3058
|
+
} catch (err) {
|
|
3059
|
+
console.warn(`
|
|
3060
|
+
\u26A0 Could not auto-write config: ${err instanceof Error ? err.message : err}`);
|
|
3061
|
+
console.log("Please add the config above manually.");
|
|
231
3062
|
}
|
|
3063
|
+
}
|
|
3064
|
+
console.log("\nFor VS Code (settings.json or .vscode/mcp.json), use the same mcpServers block.");
|
|
3065
|
+
console.log("\nThen run `code-intel analyze` in your project to index it.\n");
|
|
3066
|
+
});
|
|
3067
|
+
program.command("analyze").description("Index a repository (or update stale index)").argument("[path]", "Path to analyze", ".").option("--force", "Force full re-index even if already indexed").option("--skills", "Generate repo-specific skill files from detected communities").option("--skip-embeddings", "Skip embedding generation (faster)").option("--skip-agents-md", "Preserve custom AGENTS.md/CLAUDE.md code-intel section edits").option("--skip-git", "Index folders that are not Git repositories").option("--embeddings", "Enable embedding generation (slower, better search)").option("--verbose", "Log skipped files when parsers are unavailable").addHelpText("after", `
|
|
3068
|
+
Examples:
|
|
3069
|
+
code-intel analyze Index current directory
|
|
3070
|
+
code-intel analyze ./my-project Index a specific path
|
|
3071
|
+
code-intel analyze --force Force full re-index
|
|
3072
|
+
code-intel analyze --skills Also generate .claude/skills/ files
|
|
3073
|
+
code-intel analyze --skip-embeddings Skip vector embeddings (faster)
|
|
3074
|
+
code-intel analyze --skip-agents-md Preserve custom AGENTS.md edits
|
|
3075
|
+
code-intel analyze --skip-git Allow non-Git folders
|
|
3076
|
+
code-intel analyze --embeddings Enable vector embeddings
|
|
3077
|
+
code-intel analyze --verbose Show skipped files`).action(async (targetPath, opts) => {
|
|
3078
|
+
await analyzeWorkspace(targetPath, {
|
|
3079
|
+
force: opts.force,
|
|
3080
|
+
skills: opts.skills,
|
|
3081
|
+
skipEmbeddings: opts.skipEmbeddings,
|
|
3082
|
+
skipAgentsMd: opts.skipAgentsMd,
|
|
3083
|
+
skipGit: opts.skipGit,
|
|
3084
|
+
embeddings: opts.embeddings,
|
|
3085
|
+
verbose: opts.verbose
|
|
3086
|
+
});
|
|
3087
|
+
});
|
|
3088
|
+
program.command("mcp").description("Start MCP server (stdio) \u2014 serves all indexed repos").argument("[path]", "Path to analyze", ".").action(async (targetPath) => {
|
|
3089
|
+
const { graph, repoName } = await analyzeWorkspace(targetPath, { silent: true });
|
|
3090
|
+
await startMcpStdio(graph, repoName);
|
|
3091
|
+
});
|
|
3092
|
+
program.command("serve").description("Start local HTTP server + web UI (http://localhost:4747)").argument("[path]", "Path to analyze", ".").option("-p, --port <port>", "Port number", "4747").action(async (targetPath, options) => {
|
|
3093
|
+
const { graph, repoName, workspaceRoot } = await analyzeWorkspace(targetPath);
|
|
3094
|
+
startHttpServer(graph, repoName, parseInt(options.port, 10), workspaceRoot);
|
|
3095
|
+
});
|
|
3096
|
+
program.command("list").description("List all indexed repositories").action(() => {
|
|
3097
|
+
const repos = loadRegistry();
|
|
3098
|
+
if (repos.length === 0) {
|
|
3099
|
+
console.log("No indexed repositories. Run `code-intel analyze <path>` first.");
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
3102
|
+
console.log(`
|
|
3103
|
+
Indexed repositories (${repos.length}):
|
|
3104
|
+
`);
|
|
3105
|
+
for (const r of repos) {
|
|
3106
|
+
console.log(` ${r.name.padEnd(25)} ${r.stats.nodes} nodes, ${r.stats.edges} edges, ${r.stats.files} files`);
|
|
3107
|
+
console.log(` Path: ${r.path}`);
|
|
3108
|
+
console.log(` Indexed: ${r.indexedAt}`);
|
|
3109
|
+
}
|
|
3110
|
+
});
|
|
3111
|
+
program.command("status").description("Show index status for current repo").argument("[path]", "Path to check", ".").action((targetPath) => {
|
|
3112
|
+
const workspaceRoot = path.resolve(targetPath);
|
|
3113
|
+
const meta = loadMetadata(workspaceRoot);
|
|
3114
|
+
if (!meta) {
|
|
3115
|
+
console.log("Not indexed. Run `code-intel analyze` first.");
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
console.log(`
|
|
3119
|
+
Index status for ${workspaceRoot}:`);
|
|
3120
|
+
console.log(` Indexed at: ${meta.indexedAt}`);
|
|
3121
|
+
console.log(` Nodes: ${meta.stats.nodes}`);
|
|
3122
|
+
console.log(` Edges: ${meta.stats.edges}`);
|
|
3123
|
+
console.log(` Files: ${meta.stats.files}`);
|
|
3124
|
+
console.log(` Duration: ${meta.stats.duration}ms`);
|
|
232
3125
|
});
|
|
233
|
-
program
|
|
234
|
-
|
|
235
|
-
.
|
|
236
|
-
|
|
3126
|
+
program.command("clean").description("Delete index for current repo (or all repos with --all --force)").argument("[path]", "Path to clean", ".").option("--all", "Clean all indexed repositories").option("--force", "Required with --all to confirm destructive operation").action((targetPath, opts) => {
|
|
3127
|
+
if (opts.all) {
|
|
3128
|
+
if (!opts.force) {
|
|
3129
|
+
console.error("Error: --all requires --force to confirm. Run: code-intel clean --all --force");
|
|
3130
|
+
process.exit(1);
|
|
3131
|
+
}
|
|
237
3132
|
const repos = loadRegistry();
|
|
238
3133
|
if (repos.length === 0) {
|
|
239
|
-
|
|
240
|
-
|
|
3134
|
+
console.log("No indexed repositories to clean.");
|
|
3135
|
+
return;
|
|
241
3136
|
}
|
|
242
|
-
console.log(`\nIndexed repositories (${repos.length}):\n`);
|
|
243
3137
|
for (const r of repos) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
3138
|
+
const codeIntelDir2 = path.join(r.path, ".code-intel");
|
|
3139
|
+
if (fs8.existsSync(codeIntelDir2)) {
|
|
3140
|
+
fs8.rmSync(codeIntelDir2, { recursive: true, force: true });
|
|
3141
|
+
console.log(` Removed ${codeIntelDir2}`);
|
|
3142
|
+
}
|
|
3143
|
+
removeRepo(r.path);
|
|
3144
|
+
}
|
|
3145
|
+
console.log(`
|
|
3146
|
+
Cleaned ${repos.length} repositor${repos.length === 1 ? "y" : "ies"}.`);
|
|
3147
|
+
return;
|
|
3148
|
+
}
|
|
3149
|
+
const workspaceRoot = path.resolve(targetPath);
|
|
3150
|
+
const codeIntelDir = path.join(workspaceRoot, ".code-intel");
|
|
3151
|
+
if (fs8.existsSync(codeIntelDir)) {
|
|
3152
|
+
fs8.rmSync(codeIntelDir, { recursive: true, force: true });
|
|
3153
|
+
console.log(`Removed ${codeIntelDir}`);
|
|
3154
|
+
}
|
|
3155
|
+
removeRepo(workspaceRoot);
|
|
3156
|
+
console.log("Index cleaned.");
|
|
3157
|
+
});
|
|
3158
|
+
program.command("search").description("Search the knowledge graph").argument("<query>", "Search query").option("-l, --limit <limit>", "Max results", "20").option("-p, --path <path>", "Path to analyze", ".").action(async (query, options) => {
|
|
3159
|
+
const { graph } = await analyzeWorkspace(options.path, { silent: true });
|
|
3160
|
+
const results = textSearch(graph, query, parseInt(options.limit, 10));
|
|
3161
|
+
if (results.length === 0) {
|
|
3162
|
+
console.log("No results found.");
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
console.log(`Found ${results.length} results for "${query}":
|
|
3166
|
+
`);
|
|
3167
|
+
for (const r of results) {
|
|
3168
|
+
console.log(` ${r.kind.padEnd(12)} ${r.name.padEnd(30)} ${r.filePath}`);
|
|
3169
|
+
}
|
|
3170
|
+
});
|
|
3171
|
+
program.command("inspect").description("Inspect a symbol: callers, callees, location").argument("<symbol>", "Symbol name").option("-p, --path <path>", "Path to analyze", ".").action(async (symbol, options) => {
|
|
3172
|
+
const { graph } = await analyzeWorkspace(options.path, { silent: true });
|
|
3173
|
+
let found = false;
|
|
3174
|
+
for (const node of graph.allNodes()) {
|
|
3175
|
+
if (node.name === symbol) {
|
|
3176
|
+
found = true;
|
|
3177
|
+
console.log(`
|
|
3178
|
+
${node.kind}: ${node.name}`);
|
|
3179
|
+
console.log(` File: ${node.filePath}:${node.startLine ?? "?"}`);
|
|
3180
|
+
console.log(` Exported: ${node.exported ?? "unknown"}`);
|
|
3181
|
+
const incoming = [...graph.findEdgesTo(node.id)];
|
|
3182
|
+
const outgoing = [...graph.findEdgesFrom(node.id)];
|
|
3183
|
+
const callers = incoming.filter((e) => e.kind === "calls");
|
|
3184
|
+
const callees = outgoing.filter((e) => e.kind === "calls");
|
|
3185
|
+
if (callers.length > 0) {
|
|
3186
|
+
console.log(` Callers (${callers.length}):`);
|
|
3187
|
+
for (const c of callers.slice(0, 10)) {
|
|
3188
|
+
const n = graph.getNode(c.source);
|
|
3189
|
+
console.log(` \u2190 ${n?.name ?? c.source} (${n?.filePath})`);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
if (callees.length > 0) {
|
|
3193
|
+
console.log(` Callees (${callees.length}):`);
|
|
3194
|
+
for (const c of callees.slice(0, 10)) {
|
|
3195
|
+
const n = graph.getNode(c.target);
|
|
3196
|
+
console.log(` \u2192 ${n?.name ?? c.target} (${n?.filePath})`);
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
break;
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
if (!found) console.log(`Symbol "${symbol}" not found.`);
|
|
3203
|
+
});
|
|
3204
|
+
program.command("impact").description("Show blast radius for a symbol").argument("<symbol>", "Symbol name").option("-p, --path <path>", "Path to analyze", ".").option("-d, --depth <depth>", "Max hops", "5").action(async (symbol, options) => {
|
|
3205
|
+
const { graph } = await analyzeWorkspace(options.path, { silent: true });
|
|
3206
|
+
const maxHops = parseInt(options.depth, 10);
|
|
3207
|
+
let targetNode = null;
|
|
3208
|
+
for (const node of graph.allNodes()) {
|
|
3209
|
+
if (node.name === symbol) {
|
|
3210
|
+
targetNode = node;
|
|
3211
|
+
break;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
if (!targetNode) {
|
|
3215
|
+
console.log(`Symbol "${symbol}" not found.`);
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
const affected = /* @__PURE__ */ new Set();
|
|
3219
|
+
const queue = [{ id: targetNode.id, depth: 0 }];
|
|
3220
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3221
|
+
while (queue.length > 0) {
|
|
3222
|
+
const { id, depth } = queue.shift();
|
|
3223
|
+
if (visited.has(id) || depth > maxHops) continue;
|
|
3224
|
+
visited.add(id);
|
|
3225
|
+
affected.add(id);
|
|
3226
|
+
for (const edge of graph.findEdgesTo(id)) {
|
|
3227
|
+
if (edge.kind === "calls" || edge.kind === "imports") {
|
|
3228
|
+
queue.push({ id: edge.source, depth: depth + 1 });
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
console.log(`
|
|
3233
|
+
Blast radius for "${symbol}": ${affected.size} affected symbols
|
|
3234
|
+
`);
|
|
3235
|
+
for (const id of affected) {
|
|
3236
|
+
const n = graph.getNode(id);
|
|
3237
|
+
if (n) console.log(` ${n.kind.padEnd(12)} ${n.name.padEnd(30)} ${n.filePath}`);
|
|
3238
|
+
}
|
|
3239
|
+
});
|
|
3240
|
+
var groupCmd = program.command("group").description("Manage repository groups (multi-repo / monorepo service tracking)");
|
|
3241
|
+
groupCmd.command("create <name>").description("Create a repository group").action((name) => {
|
|
3242
|
+
if (groupExists(name)) {
|
|
3243
|
+
console.error(`Error: Group "${name}" already exists.`);
|
|
3244
|
+
process.exit(1);
|
|
3245
|
+
}
|
|
3246
|
+
saveGroup({ name, createdAt: (/* @__PURE__ */ new Date()).toISOString(), members: [] });
|
|
3247
|
+
console.log(`\u2705 Group "${name}" created.`);
|
|
3248
|
+
});
|
|
3249
|
+
groupCmd.command("add <group> <groupPath> <registryName>").description("Add a repo to a group. <groupPath> is a hierarchy path (e.g. hr/hiring/backend); <registryName> is from `code-intel list`").action((group, groupPath, registryName) => {
|
|
3250
|
+
const registry = loadRegistry();
|
|
3251
|
+
const regEntry = registry.find((r) => r.name === registryName);
|
|
3252
|
+
if (!regEntry) {
|
|
3253
|
+
console.error(`Error: Registry entry "${registryName}" not found. Run \`code-intel list\` to see available repos.`);
|
|
3254
|
+
process.exit(1);
|
|
3255
|
+
}
|
|
3256
|
+
if (!groupExists(group)) {
|
|
3257
|
+
console.error(`Error: Group "${group}" does not exist. Create it first with \`code-intel group create ${group}\`.`);
|
|
3258
|
+
process.exit(1);
|
|
3259
|
+
}
|
|
3260
|
+
addMember(group, { groupPath, registryName });
|
|
3261
|
+
console.log(`\u2705 Added "${registryName}" to group "${group}" at path "${groupPath}".`);
|
|
3262
|
+
});
|
|
3263
|
+
groupCmd.command("remove <group> <groupPath>").description("Remove a repo from a group by its hierarchy path").action((group, groupPath) => {
|
|
3264
|
+
if (!groupExists(group)) {
|
|
3265
|
+
console.error(`Error: Group "${group}" does not exist.`);
|
|
3266
|
+
process.exit(1);
|
|
3267
|
+
}
|
|
3268
|
+
try {
|
|
3269
|
+
removeMember(group, groupPath);
|
|
3270
|
+
console.log(`\u2705 Removed member at path "${groupPath}" from group "${group}".`);
|
|
3271
|
+
} catch (err) {
|
|
3272
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
3273
|
+
process.exit(1);
|
|
3274
|
+
}
|
|
3275
|
+
});
|
|
3276
|
+
groupCmd.command("list [name]").description("List all groups, or show one group's config").action((name) => {
|
|
3277
|
+
if (name) {
|
|
3278
|
+
const group = loadGroup(name);
|
|
3279
|
+
if (!group) {
|
|
3280
|
+
console.error(`Error: Group "${name}" not found.`);
|
|
3281
|
+
process.exit(1);
|
|
3282
|
+
}
|
|
3283
|
+
console.log(`
|
|
3284
|
+
Group: ${group.name}`);
|
|
3285
|
+
console.log(`Created: ${group.createdAt}`);
|
|
3286
|
+
if (group.lastSync) console.log(`Last sync: ${group.lastSync}`);
|
|
3287
|
+
console.log(`
|
|
3288
|
+
Members (${group.members.length}):`);
|
|
3289
|
+
if (group.members.length === 0) {
|
|
3290
|
+
console.log(" (none \u2014 use `code-intel group add` to add repos)");
|
|
3291
|
+
} else {
|
|
3292
|
+
for (const m of group.members) {
|
|
3293
|
+
console.log(` ${m.groupPath.padEnd(35)} \u2192 ${m.registryName}`);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
} else {
|
|
3297
|
+
const groups = listGroups();
|
|
3298
|
+
if (groups.length === 0) {
|
|
3299
|
+
console.log("No groups found. Create one with `code-intel group create <name>`.");
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
console.log(`
|
|
3303
|
+
Repository groups (${groups.length}):
|
|
3304
|
+
`);
|
|
3305
|
+
for (const g of groups) {
|
|
3306
|
+
const sync = g.lastSync ? `synced ${g.lastSync}` : "not synced";
|
|
3307
|
+
console.log(` ${g.name.padEnd(25)} ${g.members.length} member(s) [${sync}]`);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
});
|
|
3311
|
+
groupCmd.command("sync <name>").description("Extract contracts and match across repos/services in a group").action(async (name) => {
|
|
3312
|
+
const group = loadGroup(name);
|
|
3313
|
+
if (!group) {
|
|
3314
|
+
console.error(`Error: Group "${name}" not found.`);
|
|
3315
|
+
process.exit(1);
|
|
3316
|
+
}
|
|
3317
|
+
if (group.members.length === 0) {
|
|
3318
|
+
console.error(`Error: Group "${name}" has no members. Add repos with \`code-intel group add\`.`);
|
|
3319
|
+
process.exit(1);
|
|
3320
|
+
}
|
|
3321
|
+
console.log(`
|
|
3322
|
+
\u{1F504} Syncing group "${name}" (${group.members.length} member(s))\u2026
|
|
3323
|
+
`);
|
|
3324
|
+
const result = await syncGroup(group);
|
|
3325
|
+
saveSyncResult(result);
|
|
3326
|
+
group.lastSync = result.syncedAt;
|
|
3327
|
+
saveGroup(group);
|
|
3328
|
+
console.log(`
|
|
3329
|
+
\u2705 Sync complete:`);
|
|
3330
|
+
console.log(` Repos synced: ${result.memberCount}`);
|
|
3331
|
+
console.log(` Contracts: ${result.contracts.length}`);
|
|
3332
|
+
console.log(` Cross-links: ${result.links.length}`);
|
|
3333
|
+
if (result.links.length > 0) {
|
|
3334
|
+
console.log(`
|
|
3335
|
+
Top cross-repo links:`);
|
|
3336
|
+
for (const link of result.links.slice(0, 10)) {
|
|
3337
|
+
const conf = (link.confidence * 100).toFixed(0).padStart(3);
|
|
3338
|
+
console.log(` ${conf}% ${link.providerRepo} \u2237 ${link.providerContract.padEnd(30)} \u2194 ${link.consumerRepo} \u2237 ${link.consumerContract}`);
|
|
3339
|
+
}
|
|
3340
|
+
if (result.links.length > 10) {
|
|
3341
|
+
console.log(` \u2026 and ${result.links.length - 10} more. Run \`code-intel group contracts ${name}\` for full details.`);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
});
|
|
3345
|
+
groupCmd.command("contracts <name>").description("Inspect extracted contracts and cross-links from the last sync").option("--kind <kind>", "Filter by contract kind: export | route | schema | event").option("--repo <repo>", "Filter by registry name").option("--min-confidence <pct>", "Minimum link confidence 0-100 (default: 0)", "0").action((name, opts) => {
|
|
3346
|
+
const result = loadSyncResult(name);
|
|
3347
|
+
if (!result) {
|
|
3348
|
+
console.error(`No sync data for group "${name}". Run \`code-intel group sync ${name}\` first.`);
|
|
3349
|
+
process.exit(1);
|
|
3350
|
+
}
|
|
3351
|
+
const minConf = parseInt(opts.minConfidence, 10) / 100;
|
|
3352
|
+
let contracts = result.contracts;
|
|
3353
|
+
if (opts.kind) contracts = contracts.filter((c) => c.kind === opts.kind);
|
|
3354
|
+
if (opts.repo) contracts = contracts.filter((c) => c.repoName === opts.repo);
|
|
3355
|
+
let links = result.links.filter((l) => l.confidence >= minConf);
|
|
3356
|
+
if (opts.repo) links = links.filter((l) => l.providerRepo === opts.repo || l.consumerRepo === opts.repo);
|
|
3357
|
+
console.log(`
|
|
3358
|
+
\u{1F4E6} Group "${name}" \u2014 synced ${result.syncedAt}
|
|
3359
|
+
`);
|
|
3360
|
+
console.log(`Contracts (${contracts.length}):`);
|
|
3361
|
+
for (const c of contracts) {
|
|
3362
|
+
const sig = c.signature ? ` ${c.signature.slice(0, 60)}` : "";
|
|
3363
|
+
console.log(` [${c.kind.padEnd(6)}] ${c.repoName.padEnd(20)} ${c.name.padEnd(35)}${sig}`);
|
|
3364
|
+
}
|
|
3365
|
+
console.log(`
|
|
3366
|
+
Cross-repo links (${links.length}):`);
|
|
3367
|
+
if (links.length === 0) {
|
|
3368
|
+
console.log(" (none)");
|
|
3369
|
+
} else {
|
|
3370
|
+
for (const link of links) {
|
|
3371
|
+
const conf = (link.confidence * 100).toFixed(0).padStart(3);
|
|
3372
|
+
console.log(` ${conf}% [${link.matchKind}] ${link.providerRepo} \u2237 ${link.providerContract.padEnd(30)} \u2194 ${link.consumerRepo} \u2237 ${link.consumerContract}`);
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
});
|
|
3376
|
+
groupCmd.command("query <name> <q>").description("Search execution flows across all repos in a group").option("-l, --limit <limit>", "Max results per repo", "10").action(async (name, q, opts) => {
|
|
3377
|
+
const group = loadGroup(name);
|
|
3378
|
+
if (!group) {
|
|
3379
|
+
console.error(`Error: Group "${name}" not found.`);
|
|
3380
|
+
process.exit(1);
|
|
3381
|
+
}
|
|
3382
|
+
console.log(`
|
|
3383
|
+
\u{1F50D} Querying group "${name}" for: "${q}"
|
|
3384
|
+
`);
|
|
3385
|
+
const limit = parseInt(opts.limit, 10);
|
|
3386
|
+
const { perRepo, merged } = await queryGroup(group, q, limit);
|
|
3387
|
+
if (merged.length === 0) {
|
|
3388
|
+
console.log("No results found across any repo in this group.");
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
console.log(`Merged results (${merged.length} total, ranked by Reciprocal Rank Fusion):
|
|
3392
|
+
`);
|
|
3393
|
+
for (const r of merged) {
|
|
3394
|
+
console.log(` ${r.kind.padEnd(12)} ${r.name.padEnd(30)} ${r.filePath}`);
|
|
3395
|
+
if (r.snippet) console.log(` ${r.snippet.slice(0, 100)}`);
|
|
3396
|
+
}
|
|
3397
|
+
console.log(`
|
|
3398
|
+
Per-repo breakdown:`);
|
|
3399
|
+
for (const rr of perRepo) {
|
|
3400
|
+
console.log(` ${rr.repoName} (${rr.groupPath}): ${rr.results.length} result(s)`);
|
|
3401
|
+
}
|
|
3402
|
+
});
|
|
3403
|
+
groupCmd.command("status <name>").description("Check staleness of repos in a group").action((name) => {
|
|
3404
|
+
const group = loadGroup(name);
|
|
3405
|
+
if (!group) {
|
|
3406
|
+
console.error(`Error: Group "${name}" not found.`);
|
|
3407
|
+
process.exit(1);
|
|
3408
|
+
}
|
|
3409
|
+
const registry = loadRegistry();
|
|
3410
|
+
const now = Date.now();
|
|
3411
|
+
console.log(`
|
|
3412
|
+
\u{1F4CA} Group "${name}" status
|
|
3413
|
+
`);
|
|
3414
|
+
if (group.lastSync) {
|
|
3415
|
+
const age = Math.round((now - new Date(group.lastSync).getTime()) / 6e4);
|
|
3416
|
+
console.log(`Last sync: ${group.lastSync} (${age} min ago)`);
|
|
3417
|
+
} else {
|
|
3418
|
+
console.log("Last sync: never (run `code-intel group sync " + name + "`)");
|
|
3419
|
+
}
|
|
3420
|
+
console.log(`
|
|
3421
|
+
Members (${group.members.length}):
|
|
3422
|
+
`);
|
|
3423
|
+
for (const m of group.members) {
|
|
3424
|
+
const regEntry = registry.find((r) => r.name === m.registryName);
|
|
3425
|
+
if (!regEntry) {
|
|
3426
|
+
console.log(` \u2717 ${m.groupPath.padEnd(35)} [${m.registryName}] \u2014 NOT IN REGISTRY`);
|
|
3427
|
+
continue;
|
|
3428
|
+
}
|
|
3429
|
+
const metaPath = path.join(regEntry.path, ".code-intel", "meta.json");
|
|
3430
|
+
let indexedAt = regEntry.indexedAt;
|
|
3431
|
+
try {
|
|
3432
|
+
const meta = JSON.parse(fs8.readFileSync(metaPath, "utf-8"));
|
|
3433
|
+
indexedAt = meta.indexedAt;
|
|
3434
|
+
const ageMin = Math.round((now - new Date(indexedAt).getTime()) / 6e4);
|
|
3435
|
+
const stale = ageMin > 1440 ? " \u26A0 STALE (>24h)" : "";
|
|
3436
|
+
console.log(` \u2713 ${m.groupPath.padEnd(35)} [${m.registryName}] indexed ${indexedAt} (${ageMin} min ago)${stale}`);
|
|
3437
|
+
console.log(` ${regEntry.path}`);
|
|
3438
|
+
console.log(` ${meta.stats.nodes} nodes, ${meta.stats.edges} edges, ${meta.stats.files} files`);
|
|
3439
|
+
} catch {
|
|
3440
|
+
console.log(` \u2717 ${m.groupPath.padEnd(35)} [${m.registryName}] \u2014 NOT INDEXED (run: code-intel analyze ${regEntry.path})`);
|
|
259
3441
|
}
|
|
260
|
-
|
|
261
|
-
console.log(` Indexed at: ${meta.indexedAt}`);
|
|
262
|
-
console.log(` Nodes: ${meta.stats.nodes}`);
|
|
263
|
-
console.log(` Edges: ${meta.stats.edges}`);
|
|
264
|
-
console.log(` Files: ${meta.stats.files}`);
|
|
265
|
-
console.log(` Duration: ${meta.stats.duration}ms`);
|
|
266
|
-
});
|
|
267
|
-
program
|
|
268
|
-
.command('clean')
|
|
269
|
-
.description('Remove index data')
|
|
270
|
-
.argument('[path]', 'Path to clean', '.')
|
|
271
|
-
.action((targetPath) => {
|
|
272
|
-
const workspaceRoot = path.resolve(targetPath);
|
|
273
|
-
const codeIntelDir = path.join(workspaceRoot, '.code-intel');
|
|
274
|
-
if (fs.existsSync(codeIntelDir)) {
|
|
275
|
-
fs.rmSync(codeIntelDir, { recursive: true, force: true });
|
|
276
|
-
console.log(`Removed ${codeIntelDir}`);
|
|
277
|
-
}
|
|
278
|
-
removeRepo(workspaceRoot);
|
|
279
|
-
console.log('Index cleaned.');
|
|
3442
|
+
}
|
|
280
3443
|
});
|
|
281
3444
|
program.parse();
|
|
3445
|
+
//# sourceMappingURL=main.js.map
|
|
282
3446
|
//# sourceMappingURL=main.js.map
|