gitnexus 1.6.3-rc.8 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -5
- package/dist/_shared/graph/types.d.ts +16 -0
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +20 -2
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +11 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/def-index.js +2 -2
- package/dist/_shared/scope-resolution/def-index.js.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
- package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
- package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/parsed-file.js +54 -0
- package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
- package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
- package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/position-index.js +2 -2
- package/dist/_shared/scope-resolution/position-index.js.map +1 -1
- package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
- package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/reference-site.js +24 -0
- package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts +27 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js +30 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts +69 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.js +44 -0
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts +56 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.js +150 -0
- package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts +26 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js +31 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +81 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js +332 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts +33 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js +56 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts +36 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js +32 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts +43 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js +60 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js.map +1 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js +3 -2
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
- package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +11 -0
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.js +35 -4
- package/dist/cli/analyze.d.ts +27 -0
- package/dist/cli/analyze.js +31 -1
- package/dist/cli/clean.js +19 -1
- package/dist/cli/group.js +73 -0
- package/dist/cli/index-repo.js +8 -1
- package/dist/cli/index.js +26 -1
- package/dist/cli/list.js +11 -1
- package/dist/cli/remove.d.ts +30 -0
- package/dist/cli/remove.js +99 -0
- package/dist/cli/setup.js +185 -57
- package/dist/cli/tool.d.ts +5 -0
- package/dist/cli/tool.js +42 -0
- package/dist/config/ignore-service.d.ts +9 -0
- package/dist/config/ignore-service.js +80 -13
- package/dist/core/embedding-mode.d.ts +30 -0
- package/dist/core/embedding-mode.js +30 -0
- package/dist/core/embeddings/ast-utils.js +22 -22
- package/dist/core/embeddings/chunker.js +30 -25
- package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
- package/dist/core/embeddings/embedding-pipeline.js +15 -6
- package/dist/core/embeddings/text-generator.d.ts +1 -1
- package/dist/core/embeddings/text-generator.js +33 -24
- package/dist/core/embeddings/types.d.ts +43 -1
- package/dist/core/embeddings/types.js +101 -29
- package/dist/core/git-staleness.d.ts +18 -0
- package/dist/core/git-staleness.js +108 -0
- package/dist/core/graph/graph.js +115 -20
- package/dist/core/graph/types.d.ts +12 -1
- package/dist/core/group/config-parser.d.ts +4 -0
- package/dist/core/group/config-parser.js +18 -1
- package/dist/core/group/cross-impact.d.ts +41 -0
- package/dist/core/group/cross-impact.js +441 -0
- package/dist/core/group/extractors/http-patterns/php.js +126 -18
- package/dist/core/group/group-path-utils.d.ts +17 -0
- package/dist/core/group/group-path-utils.js +40 -0
- package/dist/core/group/resolve-at-member.d.ts +10 -0
- package/dist/core/group/resolve-at-member.js +31 -0
- package/dist/core/group/service.d.ts +9 -0
- package/dist/core/group/service.js +259 -25
- package/dist/core/group/types.d.ts +30 -0
- package/dist/core/ingestion/ast-cache.d.ts +16 -1
- package/dist/core/ingestion/ast-cache.js +14 -2
- package/dist/core/ingestion/call-processor.js +9 -0
- package/dist/core/ingestion/emit-references.d.ts +88 -0
- package/dist/core/ingestion/emit-references.js +229 -0
- package/dist/core/ingestion/filesystem-walker.js +6 -4
- package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
- package/dist/core/ingestion/finalize-orchestrator.js +139 -0
- package/dist/core/ingestion/framework-detection.js +6 -2
- package/dist/core/ingestion/import-processor.js +4 -0
- package/dist/core/ingestion/import-resolvers/python.js +9 -6
- package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
- package/dist/core/ingestion/import-target-adapter.js +95 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -33
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
- package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
- package/dist/core/ingestion/languages/csharp/arity.js +37 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
- package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/captures.js +249 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
- package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
- package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
- package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
- package/dist/core/ingestion/languages/csharp/index.js +82 -0
- package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
- package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
- package/dist/core/ingestion/languages/csharp/query.js +515 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
- package/dist/core/ingestion/languages/csharp.js +14 -0
- package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
- package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
- package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
- package/dist/core/ingestion/languages/python/arity.js +38 -0
- package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
- package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
- package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/python/captures.js +106 -0
- package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
- package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
- package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
- package/dist/core/ingestion/languages/python/import-target.js +99 -0
- package/dist/core/ingestion/languages/python/index.d.ts +80 -0
- package/dist/core/ingestion/languages/python/index.js +80 -0
- package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/python/interpret.js +191 -0
- package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
- package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
- package/dist/core/ingestion/languages/python/query.d.ts +9 -0
- package/dist/core/ingestion/languages/python/query.js +267 -0
- package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
- package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
- package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
- package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
- package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
- package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
- package/dist/core/ingestion/languages/python.js +14 -0
- package/dist/core/ingestion/model/method-registry.d.ts +9 -0
- package/dist/core/ingestion/model/method-registry.js +4 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
- package/dist/core/ingestion/model/semantic-model.js +55 -0
- package/dist/core/ingestion/mro-processor.js +38 -22
- package/dist/core/ingestion/parsing-processor.d.ts +18 -1
- package/dist/core/ingestion/parsing-processor.js +45 -11
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
- package/dist/core/ingestion/pipeline.js +2 -1
- package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
- package/dist/core/ingestion/registry-primary-flag.js +111 -0
- package/dist/core/ingestion/resolve-references.d.ts +63 -0
- package/dist/core/ingestion/resolve-references.js +175 -0
- package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
- package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
- package/dist/core/ingestion/scope-extractor.d.ts +86 -0
- package/dist/core/ingestion/scope-extractor.js +758 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
- package/dist/core/ingestion/shadow-harness.d.ts +113 -0
- package/dist/core/ingestion/shadow-harness.js +148 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
- package/dist/core/ingestion/utils/ast-helpers.js +70 -0
- package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
- package/dist/core/ingestion/utils/max-file-size.js +52 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
- package/dist/core/ingestion/workers/parse-worker.js +57 -21
- package/dist/core/lbug/lbug-adapter.d.ts +22 -2
- package/dist/core/lbug/lbug-adapter.js +58 -14
- package/dist/core/lbug/pool-adapter.d.ts +17 -0
- package/dist/core/lbug/pool-adapter.js +24 -14
- package/dist/core/run-analyze.d.ts +32 -0
- package/dist/core/run-analyze.js +74 -19
- package/dist/core/search/bm25-index.d.ts +18 -0
- package/dist/core/search/bm25-index.js +125 -12
- package/dist/core/tree-sitter/parser-loader.js +6 -1
- package/dist/mcp/local/local-backend.d.ts +67 -3
- package/dist/mcp/local/local-backend.js +296 -34
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +100 -17
- package/dist/mcp/tools.d.ts +4 -1
- package/dist/mcp/tools.js +75 -54
- package/dist/server/api.js +6 -2
- package/dist/storage/git.d.ts +49 -0
- package/dist/storage/git.js +111 -0
- package/dist/storage/repo-manager.d.ts +246 -1
- package/dist/storage/repo-manager.js +391 -9
- package/package.json +7 -6
- package/scripts/bench-scope-resolution.ts +134 -0
- package/scripts/ci-list-migrated-languages.ts +24 -0
- package/skills/gitnexus-cli.md +1 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map MCP/CLI `@groupName` or `@groupName/memberPath` to a concrete member path in group.yaml.
|
|
3
|
+
*/
|
|
4
|
+
import { loadGroupConfig } from './config-parser.js';
|
|
5
|
+
import { getDefaultGitnexusDir, getGroupDir } from './storage.js';
|
|
6
|
+
export async function resolveAtGroupMemberRepoPath(groupName, explicitMemberPath) {
|
|
7
|
+
const trimmed = groupName.trim();
|
|
8
|
+
if (!trimmed)
|
|
9
|
+
return { ok: false, error: 'Group name is empty.' };
|
|
10
|
+
try {
|
|
11
|
+
const groupDir = getGroupDir(getDefaultGitnexusDir(), trimmed);
|
|
12
|
+
const config = await loadGroupConfig(groupDir);
|
|
13
|
+
const keys = Object.keys(config.repos).sort((a, b) => a.localeCompare(b));
|
|
14
|
+
if (keys.length === 0) {
|
|
15
|
+
return { ok: false, error: `Group "${trimmed}" has no repos in group.yaml.` };
|
|
16
|
+
}
|
|
17
|
+
if (explicitMemberPath !== undefined && explicitMemberPath !== '') {
|
|
18
|
+
if (!(explicitMemberPath in config.repos)) {
|
|
19
|
+
return {
|
|
20
|
+
ok: false,
|
|
21
|
+
error: `Unknown member path "${explicitMemberPath}" in group "${trimmed}". Known paths: ${keys.join(', ')}`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return { ok: true, repoPath: explicitMemberPath };
|
|
25
|
+
}
|
|
26
|
+
return { ok: true, repoPath: keys[0] };
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Group orchestration shared by MCP (LocalBackend) and CLI.
|
|
3
3
|
* DB access is injected via GroupToolPort so this module stays free of LocalBackend private API.
|
|
4
4
|
*/
|
|
5
|
+
import type { GroupContextResult } from './types.js';
|
|
5
6
|
export interface GroupRepoHandle {
|
|
6
7
|
id: string;
|
|
7
8
|
name: string;
|
|
@@ -34,6 +35,12 @@ export interface GroupToolPort {
|
|
|
34
35
|
minConfidence: number;
|
|
35
36
|
includeTests: boolean;
|
|
36
37
|
}): Promise<unknown | null>;
|
|
38
|
+
context(repo: GroupRepoHandle, params: {
|
|
39
|
+
name?: string;
|
|
40
|
+
uid?: string;
|
|
41
|
+
file_path?: string;
|
|
42
|
+
include_content?: boolean;
|
|
43
|
+
}): Promise<unknown>;
|
|
37
44
|
}
|
|
38
45
|
export declare class GroupService {
|
|
39
46
|
private readonly port;
|
|
@@ -41,6 +48,8 @@ export declare class GroupService {
|
|
|
41
48
|
groupList(params: Record<string, unknown>): Promise<unknown>;
|
|
42
49
|
groupSync(params: Record<string, unknown>): Promise<unknown>;
|
|
43
50
|
groupContracts(params: Record<string, unknown>): Promise<unknown>;
|
|
51
|
+
groupImpact(params: Record<string, unknown>): Promise<unknown>;
|
|
52
|
+
groupContext(params: Record<string, unknown>): Promise<GroupContextResult>;
|
|
44
53
|
groupQuery(params: Record<string, unknown>): Promise<unknown>;
|
|
45
54
|
groupStatus(params: Record<string, unknown>): Promise<unknown>;
|
|
46
55
|
}
|
|
@@ -2,15 +2,125 @@
|
|
|
2
2
|
* Group orchestration shared by MCP (LocalBackend) and CLI.
|
|
3
3
|
* DB access is injected via GroupToolPort so this module stays free of LocalBackend private API.
|
|
4
4
|
*/
|
|
5
|
+
import fsp from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
5
7
|
import { checkStaleness } from '../git-staleness.js';
|
|
6
|
-
import { loadGroupConfig } from './config-parser.js';
|
|
8
|
+
import { GroupNotFoundError, loadGroupConfig } from './config-parser.js';
|
|
9
|
+
import { fileMatchesServicePrefix, normalizeServicePrefix, repoInSubgroup, } from './group-path-utils.js';
|
|
7
10
|
import { getDefaultGitnexusDir, getGroupDir, listGroups, readContractRegistry } from './storage.js';
|
|
8
11
|
import { syncGroup } from './sync.js';
|
|
9
|
-
function
|
|
10
|
-
if (!
|
|
11
|
-
return
|
|
12
|
-
const
|
|
13
|
-
return
|
|
12
|
+
function isStoredContract(raw) {
|
|
13
|
+
if (!raw || typeof raw !== 'object')
|
|
14
|
+
return false;
|
|
15
|
+
const o = raw;
|
|
16
|
+
return (typeof o.contractId === 'string' &&
|
|
17
|
+
typeof o.type === 'string' &&
|
|
18
|
+
typeof o.repo === 'string' &&
|
|
19
|
+
typeof o.role === 'string' &&
|
|
20
|
+
(o.role === 'provider' || o.role === 'consumer') &&
|
|
21
|
+
typeof o.symbolUid === 'string' &&
|
|
22
|
+
typeof o.symbolName === 'string' &&
|
|
23
|
+
typeof o.confidence === 'number' &&
|
|
24
|
+
o.meta !== undefined &&
|
|
25
|
+
typeof o.meta === 'object' &&
|
|
26
|
+
o.meta !== null &&
|
|
27
|
+
o.symbolRef !== undefined &&
|
|
28
|
+
typeof o.symbolRef === 'object' &&
|
|
29
|
+
o.symbolRef !== null &&
|
|
30
|
+
typeof o.symbolRef.filePath === 'string' &&
|
|
31
|
+
typeof o.symbolRef.name === 'string');
|
|
32
|
+
}
|
|
33
|
+
function filterQueryByServicePrefix(queryResult, servicePrefix) {
|
|
34
|
+
const symbols = (queryResult.process_symbols || []).filter((s) => fileMatchesServicePrefix(typeof s.filePath === 'string' ? s.filePath : undefined, servicePrefix));
|
|
35
|
+
const allowed = new Set(symbols.map((s) => String(s.process_id ?? '')).filter(Boolean));
|
|
36
|
+
const processes = (queryResult.processes || []).filter((p) => allowed.has(String(p.id)));
|
|
37
|
+
return { processes, process_symbols: symbols };
|
|
38
|
+
}
|
|
39
|
+
function isCrossLink(raw) {
|
|
40
|
+
if (!raw || typeof raw !== 'object')
|
|
41
|
+
return false;
|
|
42
|
+
const o = raw;
|
|
43
|
+
const from = o.from;
|
|
44
|
+
const to = o.to;
|
|
45
|
+
if (!from || !to)
|
|
46
|
+
return false;
|
|
47
|
+
if (typeof from.repo !== 'string' || typeof to.repo !== 'string')
|
|
48
|
+
return false;
|
|
49
|
+
return typeof o.contractId === 'string' && typeof o.type === 'string';
|
|
50
|
+
}
|
|
51
|
+
async function loadContractRegistryResilient(groupDir) {
|
|
52
|
+
const filePath = path.join(groupDir, 'contracts.json');
|
|
53
|
+
let raw;
|
|
54
|
+
try {
|
|
55
|
+
raw = await fsp.readFile(filePath, 'utf-8');
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
if (e.code === 'ENOENT') {
|
|
59
|
+
return { ok: false, error: `No contracts.json for this group. Run group_sync first.` };
|
|
60
|
+
}
|
|
61
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
62
|
+
}
|
|
63
|
+
let root;
|
|
64
|
+
try {
|
|
65
|
+
root = JSON.parse(raw);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return { ok: false, error: 'contracts.json is not valid JSON' };
|
|
69
|
+
}
|
|
70
|
+
if (!root || typeof root !== 'object' || Array.isArray(root)) {
|
|
71
|
+
return { ok: false, error: 'contracts.json has an invalid root object' };
|
|
72
|
+
}
|
|
73
|
+
const base = root;
|
|
74
|
+
const contractsRaw = base.contracts;
|
|
75
|
+
const crossRaw = base.crossLinks;
|
|
76
|
+
let skippedCorrupt = 0;
|
|
77
|
+
const contracts = [];
|
|
78
|
+
if (Array.isArray(contractsRaw)) {
|
|
79
|
+
for (const row of contractsRaw) {
|
|
80
|
+
try {
|
|
81
|
+
if (isStoredContract(row)) {
|
|
82
|
+
contracts.push(row);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
skippedCorrupt++;
|
|
86
|
+
console.warn('[group] skipping corrupt contract row in contracts.json');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
skippedCorrupt++;
|
|
91
|
+
console.warn('[group] skipping corrupt contract row in contracts.json');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const crossLinks = [];
|
|
96
|
+
if (Array.isArray(crossRaw)) {
|
|
97
|
+
for (const row of crossRaw) {
|
|
98
|
+
try {
|
|
99
|
+
if (isCrossLink(row)) {
|
|
100
|
+
crossLinks.push(row);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
skippedCorrupt++;
|
|
104
|
+
console.warn('[group] skipping corrupt crossLinks row in contracts.json');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
skippedCorrupt++;
|
|
109
|
+
console.warn('[group] skipping corrupt crossLinks row in contracts.json');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const registry = {
|
|
114
|
+
version: typeof base.version === 'number' ? base.version : 0,
|
|
115
|
+
generatedAt: typeof base.generatedAt === 'string' ? base.generatedAt : '',
|
|
116
|
+
repoSnapshots: base.repoSnapshots && typeof base.repoSnapshots === 'object' && base.repoSnapshots !== null
|
|
117
|
+
? base.repoSnapshots
|
|
118
|
+
: {},
|
|
119
|
+
missingRepos: Array.isArray(base.missingRepos) ? base.missingRepos : [],
|
|
120
|
+
contracts,
|
|
121
|
+
crossLinks,
|
|
122
|
+
};
|
|
123
|
+
return { ok: true, registry, skippedCorrupt };
|
|
14
124
|
}
|
|
15
125
|
export class GroupService {
|
|
16
126
|
port;
|
|
@@ -24,7 +134,15 @@ export class GroupService {
|
|
|
24
134
|
return { groups };
|
|
25
135
|
}
|
|
26
136
|
const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
|
|
27
|
-
|
|
137
|
+
let config;
|
|
138
|
+
try {
|
|
139
|
+
config = await loadGroupConfig(groupDir);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
if (err instanceof GroupNotFoundError)
|
|
143
|
+
return { error: `Group "${name}" not found. Run group_list to see configured groups.` };
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
28
146
|
return {
|
|
29
147
|
name: config.name,
|
|
30
148
|
description: config.description,
|
|
@@ -37,7 +155,15 @@ export class GroupService {
|
|
|
37
155
|
if (!name)
|
|
38
156
|
return { error: 'name is required' };
|
|
39
157
|
const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
|
|
40
|
-
|
|
158
|
+
let config;
|
|
159
|
+
try {
|
|
160
|
+
config = await loadGroupConfig(groupDir);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
if (err instanceof GroupNotFoundError)
|
|
164
|
+
return { error: `Group "${name}" not found. Run group_list to see configured groups.` };
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
41
167
|
const result = await syncGroup(config, {
|
|
42
168
|
groupDir,
|
|
43
169
|
exactOnly: Boolean(params.exactOnly),
|
|
@@ -57,10 +183,14 @@ export class GroupService {
|
|
|
57
183
|
if (!name)
|
|
58
184
|
return { error: 'name is required' };
|
|
59
185
|
const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
62
|
-
|
|
186
|
+
const loaded = await loadContractRegistryResilient(groupDir);
|
|
187
|
+
if (loaded.ok === false) {
|
|
188
|
+
if (loaded.error.includes('No contracts.json')) {
|
|
189
|
+
return { error: `No contracts.json for group "${name}". Run group_sync first.` };
|
|
190
|
+
}
|
|
191
|
+
return { error: loaded.error };
|
|
63
192
|
}
|
|
193
|
+
const { registry, skippedCorrupt } = loaded;
|
|
64
194
|
let contracts = registry.contracts;
|
|
65
195
|
if (params.type)
|
|
66
196
|
contracts = contracts.filter((c) => c.type === params.type);
|
|
@@ -73,21 +203,117 @@ export class GroupService {
|
|
|
73
203
|
]));
|
|
74
204
|
contracts = contracts.filter((c) => !matchedIds.has(`${c.repo}::${c.contractId}`));
|
|
75
205
|
}
|
|
76
|
-
|
|
206
|
+
const out = { contracts, crossLinks: registry.crossLinks };
|
|
207
|
+
if (skippedCorrupt > 0)
|
|
208
|
+
out.skippedCorrupt = skippedCorrupt;
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
async groupImpact(params) {
|
|
212
|
+
const { runGroupImpact } = await import('./cross-impact.js');
|
|
213
|
+
return runGroupImpact({ port: this.port, gitnexusDir: getDefaultGitnexusDir() }, params);
|
|
214
|
+
}
|
|
215
|
+
async groupContext(params) {
|
|
216
|
+
const name = String(params.name ?? '').trim();
|
|
217
|
+
const target = typeof params.target === 'string' ? params.target.trim() : '';
|
|
218
|
+
const uid = typeof params.uid === 'string' ? params.uid.trim() : undefined;
|
|
219
|
+
const file_path = typeof params.file_path === 'string' ? params.file_path : undefined;
|
|
220
|
+
const include_content = Boolean(params.include_content);
|
|
221
|
+
if (params.service !== undefined &&
|
|
222
|
+
params.service !== null &&
|
|
223
|
+
String(params.service).trim() === '') {
|
|
224
|
+
return { group: name || '', error: 'service must not be an empty string', results: [] };
|
|
225
|
+
}
|
|
226
|
+
const servicePrefix = normalizeServicePrefix(params.service);
|
|
227
|
+
const subgroup = typeof params.subgroup === 'string' ? params.subgroup : undefined;
|
|
228
|
+
const subgroupExact = params.subgroupExact === true;
|
|
229
|
+
if (!name) {
|
|
230
|
+
return { group: '', error: 'name is required', results: [] };
|
|
231
|
+
}
|
|
232
|
+
if (!uid && !target) {
|
|
233
|
+
return { group: name, error: 'target or uid is required', results: [] };
|
|
234
|
+
}
|
|
235
|
+
const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
|
|
236
|
+
let config;
|
|
237
|
+
try {
|
|
238
|
+
config = await loadGroupConfig(groupDir);
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
if (e instanceof GroupNotFoundError)
|
|
242
|
+
return {
|
|
243
|
+
group: name,
|
|
244
|
+
target: target || uid,
|
|
245
|
+
service: servicePrefix,
|
|
246
|
+
error: `Group "${name}" not found. Run group_list to see configured groups.`,
|
|
247
|
+
results: [],
|
|
248
|
+
};
|
|
249
|
+
return {
|
|
250
|
+
group: name,
|
|
251
|
+
target: target || uid,
|
|
252
|
+
service: servicePrefix,
|
|
253
|
+
error: e instanceof Error ? e.message : String(e),
|
|
254
|
+
results: [],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const memberEntries = Object.entries(config.repos).filter(([repoPath]) => repoInSubgroup(repoPath, subgroup, subgroupExact));
|
|
258
|
+
const results = await Promise.all(memberEntries.map(async ([repoPath, registryName]) => {
|
|
259
|
+
try {
|
|
260
|
+
const repoObj = await this.port.resolveRepo(registryName);
|
|
261
|
+
const payload = await this.port.context(repoObj, {
|
|
262
|
+
name: target || undefined,
|
|
263
|
+
uid,
|
|
264
|
+
file_path,
|
|
265
|
+
include_content,
|
|
266
|
+
});
|
|
267
|
+
if (servicePrefix) {
|
|
268
|
+
const st = payload?.status;
|
|
269
|
+
const sym = payload?.symbol;
|
|
270
|
+
if (st === 'found' && !fileMatchesServicePrefix(sym?.filePath, servicePrefix)) {
|
|
271
|
+
return { repoPath, registryName, payload: {} };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return { repoPath, registryName, payload };
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
return {
|
|
278
|
+
repoPath,
|
|
279
|
+
registryName,
|
|
280
|
+
payload: { error: e instanceof Error ? e.message : String(e) },
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}));
|
|
284
|
+
return {
|
|
285
|
+
group: name,
|
|
286
|
+
target: target || uid,
|
|
287
|
+
service: servicePrefix,
|
|
288
|
+
results,
|
|
289
|
+
};
|
|
77
290
|
}
|
|
78
291
|
async groupQuery(params) {
|
|
79
292
|
const name = String(params.name ?? '').trim();
|
|
80
293
|
const queryText = String(params.query ?? '').trim();
|
|
81
294
|
if (!name || !queryText)
|
|
82
295
|
return { error: 'name and query are required' };
|
|
296
|
+
if (params.service !== undefined &&
|
|
297
|
+
params.service !== null &&
|
|
298
|
+
String(params.service).trim() === '') {
|
|
299
|
+
return { error: 'service must not be an empty string' };
|
|
300
|
+
}
|
|
301
|
+
const servicePrefix = normalizeServicePrefix(params.service);
|
|
83
302
|
const limit = typeof params.limit === 'number' && params.limit > 0 ? params.limit : 5;
|
|
84
303
|
const subgroup = typeof params.subgroup === 'string' ? params.subgroup : undefined;
|
|
304
|
+
const subgroupExact = params.subgroupExact === true;
|
|
85
305
|
const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
306
|
+
let config;
|
|
307
|
+
try {
|
|
308
|
+
config = await loadGroupConfig(groupDir);
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
if (err instanceof GroupNotFoundError)
|
|
312
|
+
return { error: `Group "${name}" not found. Run group_list to see configured groups.` };
|
|
313
|
+
throw err;
|
|
314
|
+
}
|
|
315
|
+
const memberEntries = Object.entries(config.repos).filter(([repoPath]) => repoInSubgroup(repoPath, subgroup, subgroupExact));
|
|
316
|
+
const perRepo = await Promise.all(memberEntries.map(async ([repoPath, registryName]) => {
|
|
91
317
|
try {
|
|
92
318
|
const repoObj = await this.port.resolveRepo(registryName);
|
|
93
319
|
const queryResult = (await this.port.query(repoObj, {
|
|
@@ -96,18 +322,20 @@ export class GroupService {
|
|
|
96
322
|
max_symbols: 10,
|
|
97
323
|
include_content: false,
|
|
98
324
|
}));
|
|
99
|
-
const processes =
|
|
325
|
+
const processes = servicePrefix
|
|
326
|
+
? filterQueryByServicePrefix(queryResult, servicePrefix).processes
|
|
327
|
+
: queryResult.processes || [];
|
|
100
328
|
const scored = processes.map((p, idx) => ({
|
|
101
329
|
...p,
|
|
102
330
|
_rrf_score: 1 / (idx + 1 + 60),
|
|
103
331
|
_repo: repoPath,
|
|
104
332
|
}));
|
|
105
|
-
|
|
333
|
+
return { repo: repoPath, score: 0, processes: scored };
|
|
106
334
|
}
|
|
107
335
|
catch {
|
|
108
|
-
|
|
336
|
+
return { repo: repoPath, score: 0, processes: [] };
|
|
109
337
|
}
|
|
110
|
-
}
|
|
338
|
+
}));
|
|
111
339
|
const allProcesses = perRepo.flatMap((r) => r.processes);
|
|
112
340
|
allProcesses.sort((a, b) => b._rrf_score - a._rrf_score);
|
|
113
341
|
const topN = allProcesses.slice(0, limit);
|
|
@@ -123,15 +351,21 @@ export class GroupService {
|
|
|
123
351
|
if (!name)
|
|
124
352
|
return { error: 'name is required' };
|
|
125
353
|
const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
|
|
126
|
-
|
|
354
|
+
let config;
|
|
355
|
+
try {
|
|
356
|
+
config = await loadGroupConfig(groupDir);
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
if (err instanceof GroupNotFoundError)
|
|
360
|
+
return { error: `Group "${name}" not found. Run group_list to see configured groups.` };
|
|
361
|
+
throw err;
|
|
362
|
+
}
|
|
127
363
|
const registry = await readContractRegistry(groupDir);
|
|
128
364
|
const repoStatuses = {};
|
|
129
|
-
const fsp = await import('node:fs/promises');
|
|
130
|
-
const pathMod = await import('node:path');
|
|
131
365
|
for (const [repoPath, registryName] of Object.entries(config.repos)) {
|
|
132
366
|
try {
|
|
133
367
|
const repoObj = await this.port.resolveRepo(registryName);
|
|
134
|
-
const metaPath =
|
|
368
|
+
const metaPath = path.join(repoObj.storagePath, 'meta.json');
|
|
135
369
|
const metaRaw = await fsp.readFile(metaPath, 'utf-8').catch(() => '{}');
|
|
136
370
|
const meta = JSON.parse(metaRaw);
|
|
137
371
|
const staleness = meta.lastCommit
|
|
@@ -83,6 +83,8 @@ export interface RepoHandle {
|
|
|
83
83
|
repoPath: string;
|
|
84
84
|
storagePath: string;
|
|
85
85
|
}
|
|
86
|
+
/** Why local impact or fan-out stopped early (e.g. wall-clock budget exhausted). */
|
|
87
|
+
export type GroupImpactTruncationReason = 'timeout' | 'partial';
|
|
86
88
|
export interface GroupImpactResult {
|
|
87
89
|
local: unknown;
|
|
88
90
|
group: string;
|
|
@@ -97,6 +99,34 @@ export interface GroupImpactResult {
|
|
|
97
99
|
cross_repo_hits: number;
|
|
98
100
|
};
|
|
99
101
|
risk: string;
|
|
102
|
+
/**
|
|
103
|
+
* Milliseconds budget applied to the **Phase 1 local impact** leg (`safeLocalImpact`).
|
|
104
|
+
* If the walk hits this wall first, expect `truncationReason: 'timeout'` and a partial `local` payload.
|
|
105
|
+
*/
|
|
106
|
+
timeoutMs?: number;
|
|
107
|
+
/** Present when local impact or fan-out stopped early (timeout, graph cap, etc.). */
|
|
108
|
+
truncationReason?: GroupImpactTruncationReason;
|
|
109
|
+
/**
|
|
110
|
+
* Human-readable note when `crossDepth` was clamped (e.g. multi-hop not implemented yet).
|
|
111
|
+
*/
|
|
112
|
+
crossDepthWarning?: string;
|
|
113
|
+
}
|
|
114
|
+
/** One repo’s `context` tool payload in a group-scoped context run. */
|
|
115
|
+
export interface GroupContextRepoEntry {
|
|
116
|
+
repoPath: string;
|
|
117
|
+
registryName: string;
|
|
118
|
+
payload: unknown;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Aggregated group `context`: explicit per-repo rows (no merged symbol payloads).
|
|
122
|
+
* Use top-level `error` only for unrecoverable failures, not for “no matches” or service scope misses.
|
|
123
|
+
*/
|
|
124
|
+
export interface GroupContextResult {
|
|
125
|
+
group: string;
|
|
126
|
+
target?: string;
|
|
127
|
+
service?: string;
|
|
128
|
+
error?: string;
|
|
129
|
+
results: GroupContextRepoEntry[];
|
|
100
130
|
}
|
|
101
131
|
export interface CrossRepoImpact {
|
|
102
132
|
repo: string;
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import Parser from 'tree-sitter';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural shape consumers need when reading Trees back
|
|
4
|
+
* through a phase-dependency boundary. Declared here so phases that
|
|
5
|
+
* receive ASTCache via `getPhaseOutput<...>` don't hand-roll their
|
|
6
|
+
* own inline structural types that silently drift when ASTCache's
|
|
7
|
+
* contract changes.
|
|
8
|
+
*
|
|
9
|
+
* Typed as `unknown` at the Tree boundary because consumers on the
|
|
10
|
+
* other side of the phase-output map don't share tree-sitter's type
|
|
11
|
+
* graph (e.g. COBOL's standalone processor).
|
|
12
|
+
*/
|
|
13
|
+
export interface ASTCacheReader {
|
|
14
|
+
get(filePath: string): unknown;
|
|
15
|
+
clear(): void;
|
|
16
|
+
}
|
|
17
|
+
export interface ASTCache extends ASTCacheReader {
|
|
3
18
|
get: (filePath: string) => Parser.Tree | undefined;
|
|
4
19
|
set: (filePath: string, tree: Parser.Tree) => void;
|
|
5
20
|
clear: () => void;
|
|
@@ -7,8 +7,20 @@ export const createASTCache = (maxSize = 50) => {
|
|
|
7
7
|
max: effectiveMax,
|
|
8
8
|
dispose: (tree) => {
|
|
9
9
|
try {
|
|
10
|
-
// NOTE: web-tree-sitter has tree.delete(); native tree-sitter
|
|
11
|
-
//
|
|
10
|
+
// NOTE: web-tree-sitter has tree.delete(); native tree-sitter
|
|
11
|
+
// trees are GC-managed and .delete is absent (no-op here).
|
|
12
|
+
//
|
|
13
|
+
// Single-owner invariant (load-bearing under WASM): a given
|
|
14
|
+
// Parser.Tree reference must live in AT MOST ONE ASTCache
|
|
15
|
+
// that disposes. The parse-phase chunk-local cache clears
|
|
16
|
+
// between chunks; the cross-phase `scopeTreeCache` (also an
|
|
17
|
+
// ASTCache today) holds the same Tree by reference. Under
|
|
18
|
+
// native tree-sitter this is benign (dispose is a no-op).
|
|
19
|
+
// If/when GitNexus adopts web-tree-sitter for sequential
|
|
20
|
+
// parsing, the cross-phase cache must either (a) skip
|
|
21
|
+
// writing Trees that are already owned by a disposing cache,
|
|
22
|
+
// or (b) use tree.copy() per entry. Failing to pick one
|
|
23
|
+
// will hand freed memory to scope-resolution.
|
|
12
24
|
tree.delete?.();
|
|
13
25
|
}
|
|
14
26
|
catch (e) {
|
|
@@ -26,6 +26,7 @@ import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/pa
|
|
|
26
26
|
import { getProvider } from './languages/index.js';
|
|
27
27
|
import { generateId } from '../../lib/utils.js';
|
|
28
28
|
import { getLanguageFromFilename, SupportedLanguages } from '../../_shared/index.js';
|
|
29
|
+
import { isRegistryPrimary } from './registry-primary-flag.js';
|
|
29
30
|
import { isVerboseIngestionEnabled } from './utils/verbose.js';
|
|
30
31
|
import { yieldToEventLoop } from './utils/event-loop.js';
|
|
31
32
|
import { FUNCTION_NODE_TYPES, findEnclosingClassId, findEnclosingClassInfo, genericFuncName, inferFunctionLabel, } from './utils/ast-helpers.js';
|
|
@@ -594,6 +595,9 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
|
|
|
594
595
|
const language = getLanguageFromFilename(file.path);
|
|
595
596
|
if (!language)
|
|
596
597
|
continue;
|
|
598
|
+
// Registry-primary gate: scope-based phase owns CALLS for this lang.
|
|
599
|
+
if (isRegistryPrimary(language))
|
|
600
|
+
continue;
|
|
597
601
|
if (!isLanguageAvailable(language)) {
|
|
598
602
|
if (skippedByLang) {
|
|
599
603
|
skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
|
|
@@ -2162,6 +2166,11 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
|
|
|
2162
2166
|
onProgress?.(filesProcessed, totalFiles);
|
|
2163
2167
|
await yieldToEventLoop();
|
|
2164
2168
|
}
|
|
2169
|
+
// Registry-primary gate: skip Python (etc.) entirely when the
|
|
2170
|
+
// scope-based phase owns CALLS for this language.
|
|
2171
|
+
const fileLanguage = getLanguageFromFilename(filePath);
|
|
2172
|
+
if (fileLanguage && isRegistryPrimary(fileLanguage))
|
|
2173
|
+
continue;
|
|
2165
2174
|
ctx.enableCache(filePath);
|
|
2166
2175
|
const widenCache = new Map();
|
|
2167
2176
|
const receiverMap = fileReceiverTypes.get(filePath);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5 of the RFC #909 ingestion lifecycle: drain `ReferenceIndex`
|
|
3
|
+
* into the knowledge graph as labeled edges with `confidence` and
|
|
4
|
+
* `evidence` properties (Ring 2 PKG #925).
|
|
5
|
+
*
|
|
6
|
+
* The resolution phase (future PR) writes `Reference` records into
|
|
7
|
+
* `model.scopes.referenceSites`-derived `ReferenceIndex`; this module
|
|
8
|
+
* materializes those records as `GraphRelationship`s via
|
|
9
|
+
* `graph.addRelationship`. Every emitted edge carries:
|
|
10
|
+
*
|
|
11
|
+
* - `type`: one of `'CALLS' | 'ACCESSES' | 'INHERITS' | 'USES'`
|
|
12
|
+
* (mapped from `Reference.kind` — `'read'` and `'write'` both route
|
|
13
|
+
* to `ACCESSES`; `'type-reference'` and `'import-use'` route to
|
|
14
|
+
* `USES`; `'call'` stays `CALLS`; `'inherits'` stays `INHERITS`).
|
|
15
|
+
* - `confidence`: the pre-computed confidence from the Reference record.
|
|
16
|
+
* - `reason`: human-readable summary (`"scope-resolution: call | confidence 0.75"`).
|
|
17
|
+
* - `evidence`: the full `ResolutionEvidence[]` trace — additive graph
|
|
18
|
+
* property (see `GraphRelationship.evidence` in gitnexus-shared),
|
|
19
|
+
* so queries that don't know about it are unaffected.
|
|
20
|
+
* - `step`: carries the reference's access-kind discriminant when
|
|
21
|
+
* available (`1` for read, `2` for write) so `ACCESSES` edges retain
|
|
22
|
+
* the read/write distinction without forcing a new edge type.
|
|
23
|
+
*
|
|
24
|
+
* ## Optional scope-tree flush
|
|
25
|
+
*
|
|
26
|
+
* When `INGESTION_EMIT_SCOPES=1` is set, this module also emits:
|
|
27
|
+
*
|
|
28
|
+
* - `Scope` nodes for every `Scope` in the tree
|
|
29
|
+
* - `CONTAINS` edges from parent scope to child scope
|
|
30
|
+
* - `DEFINES` edges from scope to its `ownedDefs` members
|
|
31
|
+
* - `IMPORTS` edges from scope to `targetModuleScope` of each finalized
|
|
32
|
+
* `ImportEdge` that carries one
|
|
33
|
+
*
|
|
34
|
+
* Off by default — existing queries that don't know about `Scope` nodes
|
|
35
|
+
* continue to work, and the storage cost is opt-in.
|
|
36
|
+
*
|
|
37
|
+
* ## Source-of-truth: the caller def for a reference
|
|
38
|
+
*
|
|
39
|
+
* A `Reference` says "some code inside `fromScope` references `toDef`".
|
|
40
|
+
* The graph wants `(callerNodeId, calleeNodeId)`. We resolve the caller
|
|
41
|
+
* by walking up the scope tree from `fromScope` until we find a scope
|
|
42
|
+
* whose `ownedDefs` contains a Function-like def. If no such ancestor
|
|
43
|
+
* exists, the edge is attributed to the first def owned by the innermost
|
|
44
|
+
* ancestor scope, and if THAT produces nothing either the edge is
|
|
45
|
+
* skipped (with a count returned in `EmitStats.skippedNoCaller`).
|
|
46
|
+
*/
|
|
47
|
+
import type { ReferenceIndex } from '../../_shared/index.js';
|
|
48
|
+
import type { KnowledgeGraph } from '../graph/types.js';
|
|
49
|
+
import type { ScopeResolutionIndexes } from './model/scope-resolution-indexes.js';
|
|
50
|
+
export interface EmitStats {
|
|
51
|
+
readonly edgesEmitted: number;
|
|
52
|
+
/** References dropped because no caller def could be resolved. */
|
|
53
|
+
readonly skippedNoCaller: number;
|
|
54
|
+
/** References dropped because `toDef` was not found in the DefIndex. */
|
|
55
|
+
readonly skippedMissingTarget: number;
|
|
56
|
+
/** Scope nodes emitted — `0` unless `INGESTION_EMIT_SCOPES=1`. */
|
|
57
|
+
readonly scopeNodesEmitted: number;
|
|
58
|
+
/** Scope-tree structural edges emitted — `0` unless `INGESTION_EMIT_SCOPES=1`. */
|
|
59
|
+
readonly scopeEdgesEmitted: number;
|
|
60
|
+
}
|
|
61
|
+
export interface EmitReferencesInput {
|
|
62
|
+
readonly graph: KnowledgeGraph;
|
|
63
|
+
readonly scopes: ScopeResolutionIndexes;
|
|
64
|
+
readonly referenceIndex: ReferenceIndex;
|
|
65
|
+
/** Human-consumable label for the `reason` prefix. Defaults to `'scope-resolution'`. */
|
|
66
|
+
readonly sourceLabel?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Drain `referenceIndex.bySourceScope` into graph edges.
|
|
70
|
+
*
|
|
71
|
+
* The scope-tree flush is controlled separately by
|
|
72
|
+
* `INGESTION_EMIT_SCOPES` — callers can run `emitReferencesToGraph`
|
|
73
|
+
* without scope-node emission or layer the two calls as needed.
|
|
74
|
+
*/
|
|
75
|
+
export declare function emitReferencesToGraph(input: EmitReferencesInput): EmitStats;
|
|
76
|
+
/**
|
|
77
|
+
* Emit `Scope` nodes + `CONTAINS`/`DEFINES`/`IMPORTS` edges representing
|
|
78
|
+
* the lexical scope tree itself. Skipped unless `INGESTION_EMIT_SCOPES=1`
|
|
79
|
+
* at the public entry point; exported here for tests that want to
|
|
80
|
+
* exercise the path directly.
|
|
81
|
+
*/
|
|
82
|
+
export declare function emitScopeGraph(input: {
|
|
83
|
+
readonly graph: KnowledgeGraph;
|
|
84
|
+
readonly scopes: ScopeResolutionIndexes;
|
|
85
|
+
}): {
|
|
86
|
+
readonly scopeNodesEmitted: number;
|
|
87
|
+
readonly scopeEdgesEmitted: number;
|
|
88
|
+
};
|