@grafema/util 0.3.0-beta
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/LICENSE +190 -0
- package/dist/api/GraphAPI.d.ts +87 -0
- package/dist/api/GraphAPI.d.ts.map +1 -0
- package/dist/api/GraphAPI.js +212 -0
- package/dist/api/GraphAPI.js.map +1 -0
- package/dist/api/GuaranteeAPI.d.ts +147 -0
- package/dist/api/GuaranteeAPI.d.ts.map +1 -0
- package/dist/api/GuaranteeAPI.js +290 -0
- package/dist/api/GuaranteeAPI.js.map +1 -0
- package/dist/config/ConfigLoader.d.ts +214 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +441 -0
- package/dist/config/ConfigLoader.js.map +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +5 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/CoverageAnalyzer.d.ts +65 -0
- package/dist/core/CoverageAnalyzer.d.ts.map +1 -0
- package/dist/core/CoverageAnalyzer.js +199 -0
- package/dist/core/CoverageAnalyzer.js.map +1 -0
- package/dist/core/FileExplainer.d.ts +101 -0
- package/dist/core/FileExplainer.d.ts.map +1 -0
- package/dist/core/FileExplainer.js +140 -0
- package/dist/core/FileExplainer.js.map +1 -0
- package/dist/core/FileOverview.d.ts +124 -0
- package/dist/core/FileOverview.d.ts.map +1 -0
- package/dist/core/FileOverview.js +279 -0
- package/dist/core/FileOverview.js.map +1 -0
- package/dist/core/GrafemaUri.d.ts +66 -0
- package/dist/core/GrafemaUri.d.ts.map +1 -0
- package/dist/core/GrafemaUri.js +191 -0
- package/dist/core/GrafemaUri.js.map +1 -0
- package/dist/core/GraphBackend.d.ts +158 -0
- package/dist/core/GraphBackend.d.ts.map +1 -0
- package/dist/core/GraphBackend.js +85 -0
- package/dist/core/GraphBackend.js.map +1 -0
- package/dist/core/GraphFreshnessChecker.d.ts +33 -0
- package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
- package/dist/core/GraphFreshnessChecker.js +104 -0
- package/dist/core/GraphFreshnessChecker.js.map +1 -0
- package/dist/core/GuaranteeManager.d.ts +254 -0
- package/dist/core/GuaranteeManager.d.ts.map +1 -0
- package/dist/core/GuaranteeManager.js +447 -0
- package/dist/core/GuaranteeManager.js.map +1 -0
- package/dist/core/HashUtils.d.ts +24 -0
- package/dist/core/HashUtils.d.ts.map +1 -0
- package/dist/core/HashUtils.js +46 -0
- package/dist/core/HashUtils.js.map +1 -0
- package/dist/core/IncrementalReanalyzer.d.ts +33 -0
- package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
- package/dist/core/IncrementalReanalyzer.js +67 -0
- package/dist/core/IncrementalReanalyzer.js.map +1 -0
- package/dist/core/ResourceRegistry.d.ts +17 -0
- package/dist/core/ResourceRegistry.d.ts.map +1 -0
- package/dist/core/ResourceRegistry.js +32 -0
- package/dist/core/ResourceRegistry.js.map +1 -0
- package/dist/core/SemanticId.d.ts +159 -0
- package/dist/core/SemanticId.d.ts.map +1 -0
- package/dist/core/SemanticId.js +291 -0
- package/dist/core/SemanticId.js.map +1 -0
- package/dist/core/VersionManager.d.ts +166 -0
- package/dist/core/VersionManager.d.ts.map +1 -0
- package/dist/core/VersionManager.js +239 -0
- package/dist/core/VersionManager.js.map +1 -0
- package/dist/core/brandNodeInternal.d.ts +14 -0
- package/dist/core/brandNodeInternal.d.ts.map +1 -0
- package/dist/core/brandNodeInternal.js +4 -0
- package/dist/core/brandNodeInternal.js.map +1 -0
- package/dist/core/nodes/GuaranteeNode.d.ts +76 -0
- package/dist/core/nodes/GuaranteeNode.d.ts.map +1 -0
- package/dist/core/nodes/GuaranteeNode.js +118 -0
- package/dist/core/nodes/GuaranteeNode.js.map +1 -0
- package/dist/core/nodes/IssueNode.d.ts +73 -0
- package/dist/core/nodes/IssueNode.d.ts.map +1 -0
- package/dist/core/nodes/IssueNode.js +130 -0
- package/dist/core/nodes/IssueNode.js.map +1 -0
- package/dist/core/nodes/NodeKind.d.ts +104 -0
- package/dist/core/nodes/NodeKind.d.ts.map +1 -0
- package/dist/core/nodes/NodeKind.js +166 -0
- package/dist/core/nodes/NodeKind.js.map +1 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts +103 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticCollector.js +133 -0
- package/dist/diagnostics/DiagnosticCollector.js.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts +122 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.js +300 -0
- package/dist/diagnostics/DiagnosticReporter.js.map +1 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticWriter.js +44 -0
- package/dist/diagnostics/DiagnosticWriter.js.map +1 -0
- package/dist/diagnostics/categories.d.ts +57 -0
- package/dist/diagnostics/categories.d.ts.map +1 -0
- package/dist/diagnostics/categories.js +71 -0
- package/dist/diagnostics/categories.js.map +1 -0
- package/dist/diagnostics/index.d.ts +17 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +15 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/errors/GrafemaError.d.ts +200 -0
- package/dist/errors/GrafemaError.d.ts.map +1 -0
- package/dist/errors/GrafemaError.js +209 -0
- package/dist/errors/GrafemaError.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions/index.d.ts +8 -0
- package/dist/instructions/index.d.ts.map +1 -0
- package/dist/instructions/index.js +20 -0
- package/dist/instructions/index.js.map +1 -0
- package/dist/instructions/onboarding.md +133 -0
- package/dist/knowledge/KnowledgeBase.d.ts +113 -0
- package/dist/knowledge/KnowledgeBase.d.ts.map +1 -0
- package/dist/knowledge/KnowledgeBase.js +420 -0
- package/dist/knowledge/KnowledgeBase.js.map +1 -0
- package/dist/knowledge/SemanticAddressResolver.d.ts +59 -0
- package/dist/knowledge/SemanticAddressResolver.d.ts.map +1 -0
- package/dist/knowledge/SemanticAddressResolver.js +160 -0
- package/dist/knowledge/SemanticAddressResolver.js.map +1 -0
- package/dist/knowledge/git-ingest.d.ts +58 -0
- package/dist/knowledge/git-ingest.d.ts.map +1 -0
- package/dist/knowledge/git-ingest.js +301 -0
- package/dist/knowledge/git-ingest.js.map +1 -0
- package/dist/knowledge/git-queries.d.ts +86 -0
- package/dist/knowledge/git-queries.d.ts.map +1 -0
- package/dist/knowledge/git-queries.js +177 -0
- package/dist/knowledge/git-queries.js.map +1 -0
- package/dist/knowledge/index.d.ts +14 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +10 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/knowledge/parser.d.ts +39 -0
- package/dist/knowledge/parser.d.ts.map +1 -0
- package/dist/knowledge/parser.js +292 -0
- package/dist/knowledge/parser.js.map +1 -0
- package/dist/knowledge/types.d.ts +133 -0
- package/dist/knowledge/types.d.ts.map +1 -0
- package/dist/knowledge/types.js +8 -0
- package/dist/knowledge/types.js.map +1 -0
- package/dist/logging/Logger.d.ts +98 -0
- package/dist/logging/Logger.d.ts.map +1 -0
- package/dist/logging/Logger.js +274 -0
- package/dist/logging/Logger.js.map +1 -0
- package/dist/notation/archetypes.d.ts +36 -0
- package/dist/notation/archetypes.d.ts.map +1 -0
- package/dist/notation/archetypes.js +173 -0
- package/dist/notation/archetypes.js.map +1 -0
- package/dist/notation/fold.d.ts +25 -0
- package/dist/notation/fold.d.ts.map +1 -0
- package/dist/notation/fold.js +598 -0
- package/dist/notation/fold.js.map +1 -0
- package/dist/notation/index.d.ts +18 -0
- package/dist/notation/index.d.ts.map +1 -0
- package/dist/notation/index.js +16 -0
- package/dist/notation/index.js.map +1 -0
- package/dist/notation/lodExtractor.d.ts +32 -0
- package/dist/notation/lodExtractor.d.ts.map +1 -0
- package/dist/notation/lodExtractor.js +149 -0
- package/dist/notation/lodExtractor.js.map +1 -0
- package/dist/notation/nameShortener.d.ts +22 -0
- package/dist/notation/nameShortener.d.ts.map +1 -0
- package/dist/notation/nameShortener.js +24 -0
- package/dist/notation/nameShortener.js.map +1 -0
- package/dist/notation/perspectives.d.ts +11 -0
- package/dist/notation/perspectives.d.ts.map +1 -0
- package/dist/notation/perspectives.js +16 -0
- package/dist/notation/perspectives.js.map +1 -0
- package/dist/notation/renderer.d.ts +31 -0
- package/dist/notation/renderer.d.ts.map +1 -0
- package/dist/notation/renderer.js +315 -0
- package/dist/notation/renderer.js.map +1 -0
- package/dist/notation/traceRenderer.d.ts +39 -0
- package/dist/notation/traceRenderer.d.ts.map +1 -0
- package/dist/notation/traceRenderer.js +358 -0
- package/dist/notation/traceRenderer.js.map +1 -0
- package/dist/notation/types.d.ts +66 -0
- package/dist/notation/types.d.ts.map +1 -0
- package/dist/notation/types.js +10 -0
- package/dist/notation/types.js.map +1 -0
- package/dist/queries/NodeContext.d.ts +81 -0
- package/dist/queries/NodeContext.d.ts.map +1 -0
- package/dist/queries/NodeContext.js +196 -0
- package/dist/queries/NodeContext.js.map +1 -0
- package/dist/queries/findCallsInFunction.d.ts +62 -0
- package/dist/queries/findCallsInFunction.d.ts.map +1 -0
- package/dist/queries/findCallsInFunction.js +169 -0
- package/dist/queries/findCallsInFunction.js.map +1 -0
- package/dist/queries/findContainingFunction.d.ts +57 -0
- package/dist/queries/findContainingFunction.d.ts.map +1 -0
- package/dist/queries/findContainingFunction.js +91 -0
- package/dist/queries/findContainingFunction.js.map +1 -0
- package/dist/queries/index.d.ts +18 -0
- package/dist/queries/index.d.ts.map +1 -0
- package/dist/queries/index.js +14 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/traceDataflow.d.ts +65 -0
- package/dist/queries/traceDataflow.d.ts.map +1 -0
- package/dist/queries/traceDataflow.js +754 -0
- package/dist/queries/traceDataflow.js.map +1 -0
- package/dist/queries/traceValues.d.ts +70 -0
- package/dist/queries/traceValues.d.ts.map +1 -0
- package/dist/queries/traceValues.js +373 -0
- package/dist/queries/traceValues.js.map +1 -0
- package/dist/queries/types.d.ts +166 -0
- package/dist/queries/types.d.ts.map +1 -0
- package/dist/queries/types.js +10 -0
- package/dist/queries/types.js.map +1 -0
- package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
- package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/GraphSchemaExtractor.js +125 -0
- package/dist/schema/GraphSchemaExtractor.js.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.js +113 -0
- package/dist/schema/InterfaceSchemaExtractor.js.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts +356 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -0
- package/dist/storage/backends/RFDBServerBackend.js +748 -0
- package/dist/storage/backends/RFDBServerBackend.js.map +1 -0
- package/dist/storage/backends/typeValidation.d.ts +47 -0
- package/dist/storage/backends/typeValidation.d.ts.map +1 -0
- package/dist/storage/backends/typeValidation.js +141 -0
- package/dist/storage/backends/typeValidation.js.map +1 -0
- package/dist/utils/findRfdbBinary.d.ts +67 -0
- package/dist/utils/findRfdbBinary.d.ts.map +1 -0
- package/dist/utils/findRfdbBinary.js +261 -0
- package/dist/utils/findRfdbBinary.js.map +1 -0
- package/dist/utils/lazyDownload.d.ts +43 -0
- package/dist/utils/lazyDownload.d.ts.map +1 -0
- package/dist/utils/lazyDownload.js +175 -0
- package/dist/utils/lazyDownload.js.map +1 -0
- package/dist/utils/moduleResolution.d.ts +134 -0
- package/dist/utils/moduleResolution.d.ts.map +1 -0
- package/dist/utils/moduleResolution.js +189 -0
- package/dist/utils/moduleResolution.js.map +1 -0
- package/dist/utils/resolveNodeFile.d.ts +13 -0
- package/dist/utils/resolveNodeFile.d.ts.map +1 -0
- package/dist/utils/resolveNodeFile.js +18 -0
- package/dist/utils/resolveNodeFile.js.map +1 -0
- package/dist/utils/startRfdbServer.d.ts +63 -0
- package/dist/utils/startRfdbServer.d.ts.map +1 -0
- package/dist/utils/startRfdbServer.js +142 -0
- package/dist/utils/startRfdbServer.js.map +1 -0
- package/dist/validation/PathValidator.d.ts +80 -0
- package/dist/validation/PathValidator.d.ts.map +1 -0
- package/dist/validation/PathValidator.js +252 -0
- package/dist/validation/PathValidator.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +26 -0
- package/dist/version.js.map +1 -0
- package/package.json +50 -0
- package/src/api/GraphAPI.ts +307 -0
- package/src/api/GuaranteeAPI.ts +402 -0
- package/src/config/ConfigLoader.ts +653 -0
- package/src/config/index.ts +13 -0
- package/src/core/CoverageAnalyzer.ts +243 -0
- package/src/core/FileExplainer.ts +179 -0
- package/src/core/FileOverview.ts +397 -0
- package/src/core/GrafemaUri.ts +216 -0
- package/src/core/GraphBackend.ts +266 -0
- package/src/core/GraphFreshnessChecker.ts +145 -0
- package/src/core/GuaranteeManager.ts +684 -0
- package/src/core/HashUtils.ts +48 -0
- package/src/core/IncrementalReanalyzer.ts +106 -0
- package/src/core/ResourceRegistry.ts +39 -0
- package/src/core/SemanticId.ts +423 -0
- package/src/core/VersionManager.ts +405 -0
- package/src/core/brandNodeInternal.ts +16 -0
- package/src/core/nodes/GuaranteeNode.ts +162 -0
- package/src/core/nodes/IssueNode.ts +177 -0
- package/src/core/nodes/NodeKind.ts +192 -0
- package/src/diagnostics/DiagnosticCollector.ts +170 -0
- package/src/diagnostics/DiagnosticReporter.ts +395 -0
- package/src/diagnostics/DiagnosticWriter.ts +50 -0
- package/src/diagnostics/categories.ts +104 -0
- package/src/diagnostics/index.ts +30 -0
- package/src/errors/GrafemaError.ts +297 -0
- package/src/index.ts +261 -0
- package/src/instructions/index.ts +21 -0
- package/src/instructions/onboarding.md +133 -0
- package/src/knowledge/KnowledgeBase.ts +486 -0
- package/src/knowledge/SemanticAddressResolver.ts +191 -0
- package/src/knowledge/git-ingest.ts +402 -0
- package/src/knowledge/git-queries.ts +269 -0
- package/src/knowledge/index.ts +29 -0
- package/src/knowledge/parser.ts +294 -0
- package/src/knowledge/types.ts +146 -0
- package/src/logging/Logger.ts +303 -0
- package/src/notation/archetypes.ts +189 -0
- package/src/notation/fold.ts +696 -0
- package/src/notation/index.ts +27 -0
- package/src/notation/lodExtractor.ts +177 -0
- package/src/notation/nameShortener.ts +24 -0
- package/src/notation/perspectives.ts +18 -0
- package/src/notation/renderer.ts +394 -0
- package/src/notation/traceRenderer.ts +458 -0
- package/src/notation/types.ts +79 -0
- package/src/queries/NodeContext.ts +280 -0
- package/src/queries/findCallsInFunction.ts +249 -0
- package/src/queries/findContainingFunction.ts +124 -0
- package/src/queries/index.ts +44 -0
- package/src/queries/traceDataflow.ts +838 -0
- package/src/queries/traceValues.ts +531 -0
- package/src/queries/types.ts +191 -0
- package/src/schema/GraphSchemaExtractor.ts +177 -0
- package/src/schema/InterfaceSchemaExtractor.ts +173 -0
- package/src/schema/index.ts +5 -0
- package/src/storage/backends/RFDBServerBackend.ts +895 -0
- package/src/storage/backends/typeValidation.ts +154 -0
- package/src/utils/findRfdbBinary.ts +288 -0
- package/src/utils/lazyDownload.ts +206 -0
- package/src/utils/moduleResolution.ts +271 -0
- package/src/utils/resolveNodeFile.ts +18 -0
- package/src/utils/startRfdbServer.ts +197 -0
- package/src/validation/PathValidator.ts +334 -0
- package/src/version.ts +28 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileOverview - Get a structured overview of all entities in a file.
|
|
3
|
+
*
|
|
4
|
+
* Purpose: Show what a file contains and how its parts relate to each other.
|
|
5
|
+
* Unlike FileExplainer (which lists ALL nodes flat), FileOverview shows only
|
|
6
|
+
* meaningful entities (functions, classes, variables) with their key relationships
|
|
7
|
+
* (calls, extends, assigned-from).
|
|
8
|
+
*
|
|
9
|
+
* Unlike the context command (which shows ONE node's full neighborhood),
|
|
10
|
+
* FileOverview shows ALL entities at file level with curated edges.
|
|
11
|
+
*
|
|
12
|
+
* Use this when:
|
|
13
|
+
* - AI agent needs to understand a file before diving deeper
|
|
14
|
+
* - User wants a table-of-contents of a file with relationships
|
|
15
|
+
* - Quick orientation before using get_context on specific nodes
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const overview = new FileOverview(backend);
|
|
20
|
+
* const result = await overview.getOverview('/abs/path/to/file.js');
|
|
21
|
+
* // result.functions[0].calls -> ["express", "Router"]
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @see REG-412
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { GraphBackend, BaseNodeRecord, NodeFilter } from '@grafema/types';
|
|
28
|
+
import type { CallInfo } from '../queries/types.js';
|
|
29
|
+
import { findCallsInFunction } from '../queries/findCallsInFunction.js';
|
|
30
|
+
|
|
31
|
+
// === Result Types ===
|
|
32
|
+
|
|
33
|
+
export interface ImportInfo {
|
|
34
|
+
id: string;
|
|
35
|
+
source: string;
|
|
36
|
+
specifiers: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ExportInfo {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
isDefault: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface FunctionOverview {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
line?: number;
|
|
49
|
+
async: boolean;
|
|
50
|
+
params?: string[];
|
|
51
|
+
calls: string[];
|
|
52
|
+
returnType?: string;
|
|
53
|
+
signature?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ClassOverview {
|
|
57
|
+
id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
line?: number;
|
|
60
|
+
extends?: string;
|
|
61
|
+
exported: boolean;
|
|
62
|
+
methods: FunctionOverview[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface VariableOverview {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
line?: number;
|
|
69
|
+
kind: string;
|
|
70
|
+
assignedFrom?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface FileOverviewResult {
|
|
74
|
+
file: string;
|
|
75
|
+
status: 'ANALYZED' | 'NOT_ANALYZED';
|
|
76
|
+
imports: ImportInfo[];
|
|
77
|
+
exports: ExportInfo[];
|
|
78
|
+
classes: ClassOverview[];
|
|
79
|
+
functions: FunctionOverview[];
|
|
80
|
+
variables: VariableOverview[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Node types we display in the file overview */
|
|
84
|
+
const OVERVIEW_NODE_TYPES = new Set([
|
|
85
|
+
'FUNCTION',
|
|
86
|
+
'CLASS',
|
|
87
|
+
'METHOD',
|
|
88
|
+
'VARIABLE',
|
|
89
|
+
'CONSTANT',
|
|
90
|
+
'IMPORT',
|
|
91
|
+
'EXPORT',
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
export class FileOverview {
|
|
95
|
+
constructor(private graph: GraphBackend) {}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get structured overview of a file's entities and relationships.
|
|
99
|
+
*
|
|
100
|
+
* @param filePath - Absolute file path (after realpath resolution)
|
|
101
|
+
* @param options - Optional: { includeEdges: boolean }
|
|
102
|
+
* @returns FileOverviewResult
|
|
103
|
+
*/
|
|
104
|
+
async getOverview(
|
|
105
|
+
filePath: string,
|
|
106
|
+
options: { includeEdges?: boolean } = {}
|
|
107
|
+
): Promise<FileOverviewResult> {
|
|
108
|
+
const { includeEdges = true } = options;
|
|
109
|
+
|
|
110
|
+
const moduleNode = await this.findModuleNode(filePath);
|
|
111
|
+
if (!moduleNode) {
|
|
112
|
+
return {
|
|
113
|
+
file: filePath,
|
|
114
|
+
status: 'NOT_ANALYZED',
|
|
115
|
+
imports: [],
|
|
116
|
+
exports: [],
|
|
117
|
+
classes: [],
|
|
118
|
+
functions: [],
|
|
119
|
+
variables: [],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const children = await this.getTopLevelEntities(moduleNode.id);
|
|
124
|
+
|
|
125
|
+
const imports: ImportInfo[] = [];
|
|
126
|
+
const exports: ExportInfo[] = [];
|
|
127
|
+
const classes: ClassOverview[] = [];
|
|
128
|
+
const functions: FunctionOverview[] = [];
|
|
129
|
+
const variables: VariableOverview[] = [];
|
|
130
|
+
|
|
131
|
+
for (const child of children) {
|
|
132
|
+
switch (child.type) {
|
|
133
|
+
case 'IMPORT':
|
|
134
|
+
imports.push(this.buildImportInfo(child));
|
|
135
|
+
break;
|
|
136
|
+
case 'EXPORT':
|
|
137
|
+
exports.push(this.buildExportInfo(child));
|
|
138
|
+
break;
|
|
139
|
+
case 'CLASS':
|
|
140
|
+
classes.push(await this.buildClassOverview(child, includeEdges));
|
|
141
|
+
break;
|
|
142
|
+
case 'FUNCTION':
|
|
143
|
+
case 'METHOD':
|
|
144
|
+
functions.push(await this.buildFunctionOverview(child, includeEdges));
|
|
145
|
+
break;
|
|
146
|
+
case 'VARIABLE':
|
|
147
|
+
case 'CONSTANT':
|
|
148
|
+
variables.push(await this.buildVariableOverview(child, includeEdges));
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const byLine = (a: { line?: number }, b: { line?: number }) =>
|
|
154
|
+
(a.line ?? 0) - (b.line ?? 0);
|
|
155
|
+
|
|
156
|
+
classes.sort(byLine);
|
|
157
|
+
functions.sort(byLine);
|
|
158
|
+
variables.sort(byLine);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
file: filePath,
|
|
162
|
+
status: 'ANALYZED',
|
|
163
|
+
imports,
|
|
164
|
+
exports,
|
|
165
|
+
classes,
|
|
166
|
+
functions,
|
|
167
|
+
variables,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Find the MODULE node for the given file path.
|
|
173
|
+
* Complexity: O(1) - server-side filtered query
|
|
174
|
+
*/
|
|
175
|
+
private async findModuleNode(
|
|
176
|
+
filePath: string
|
|
177
|
+
): Promise<BaseNodeRecord | null> {
|
|
178
|
+
const filter: NodeFilter = { file: filePath, type: 'MODULE' };
|
|
179
|
+
for await (const node of this.graph.queryNodes(filter)) {
|
|
180
|
+
if (node.file === filePath && node.type === 'MODULE') {
|
|
181
|
+
return node;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get direct children of MODULE node that are "interesting" types.
|
|
189
|
+
* Complexity: O(C) where C = total CONTAINS edges from MODULE
|
|
190
|
+
*/
|
|
191
|
+
private async getTopLevelEntities(
|
|
192
|
+
moduleId: string
|
|
193
|
+
): Promise<BaseNodeRecord[]> {
|
|
194
|
+
const containsEdges = await this.graph.getOutgoingEdges(
|
|
195
|
+
moduleId,
|
|
196
|
+
['CONTAINS']
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const entities: BaseNodeRecord[] = [];
|
|
200
|
+
for (const edge of containsEdges) {
|
|
201
|
+
const child = await this.graph.getNode(edge.dst);
|
|
202
|
+
if (child && OVERVIEW_NODE_TYPES.has(child.type)) {
|
|
203
|
+
entities.push(child);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return entities;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Build ImportInfo from an IMPORT node.
|
|
211
|
+
* Data read directly from node record fields.
|
|
212
|
+
* Complexity: O(1)
|
|
213
|
+
*/
|
|
214
|
+
private buildImportInfo(node: BaseNodeRecord): ImportInfo {
|
|
215
|
+
const source = (node.source as string) ?? (node.name || '');
|
|
216
|
+
const rawSpecifiers = node.specifiers;
|
|
217
|
+
let specifierNames: string[] = [];
|
|
218
|
+
|
|
219
|
+
if (Array.isArray(rawSpecifiers)) {
|
|
220
|
+
specifierNames = rawSpecifiers.map(
|
|
221
|
+
(s: { local?: string; imported?: string; type?: string }) =>
|
|
222
|
+
s.local || s.imported || 'unknown'
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
id: node.id,
|
|
228
|
+
source,
|
|
229
|
+
specifiers: specifierNames,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Build ExportInfo from an EXPORT node.
|
|
235
|
+
* Data read directly from node record fields.
|
|
236
|
+
* Complexity: O(1)
|
|
237
|
+
*/
|
|
238
|
+
private buildExportInfo(node: BaseNodeRecord): ExportInfo {
|
|
239
|
+
return {
|
|
240
|
+
id: node.id,
|
|
241
|
+
name: (node.exportedName as string) ?? node.name ?? '<anonymous>',
|
|
242
|
+
isDefault: (node.isDefault as boolean) ?? false,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Build FunctionOverview from a FUNCTION node.
|
|
248
|
+
* When includeEdges=true, resolves calls via findCallsInFunction.
|
|
249
|
+
* Complexity: Without edges O(1), with edges O(S + C)
|
|
250
|
+
*/
|
|
251
|
+
private async buildFunctionOverview(
|
|
252
|
+
node: BaseNodeRecord,
|
|
253
|
+
includeEdges: boolean
|
|
254
|
+
): Promise<FunctionOverview> {
|
|
255
|
+
const overview: FunctionOverview = {
|
|
256
|
+
id: node.id,
|
|
257
|
+
name: node.name ?? '<anonymous>',
|
|
258
|
+
line: node.line as number | undefined,
|
|
259
|
+
async: (node.async as boolean) ?? false,
|
|
260
|
+
params: node.params as string[] | undefined,
|
|
261
|
+
calls: [],
|
|
262
|
+
returnType: node.returnType as string | undefined,
|
|
263
|
+
signature: node.signature as string | undefined,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
if (includeEdges) {
|
|
267
|
+
const callInfos: CallInfo[] = await findCallsInFunction(
|
|
268
|
+
this.graph,
|
|
269
|
+
node.id,
|
|
270
|
+
{ transitive: false }
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const callNames = new Set<string>();
|
|
274
|
+
for (const call of callInfos) {
|
|
275
|
+
if (call.resolved && call.target) {
|
|
276
|
+
callNames.add(call.target.name);
|
|
277
|
+
} else {
|
|
278
|
+
callNames.add(call.name);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Fallback: when HAS_SCOPE edges are missing (orchestrator doesn't create
|
|
283
|
+
// FUNCTION → HAS_SCOPE), find CALL nodes by file + line range.
|
|
284
|
+
if (callNames.size === 0 && node.file && node.line != null) {
|
|
285
|
+
const endLine = (node.endLine as number | undefined) ?? (node.line as number) + 100000;
|
|
286
|
+
const nodeLine = node.line as number;
|
|
287
|
+
const filter: NodeFilter = { file: node.file, type: 'CALL' };
|
|
288
|
+
for await (const callNode of this.graph.queryNodes(filter)) {
|
|
289
|
+
const callLine = callNode.line as number | undefined;
|
|
290
|
+
if (callLine != null && callLine >= nodeLine && callLine <= endLine) {
|
|
291
|
+
const callsEdges = await this.graph.getOutgoingEdges(callNode.id, ['CALLS']);
|
|
292
|
+
if (callsEdges.length > 0) {
|
|
293
|
+
const target = await this.graph.getNode(callsEdges[0].dst);
|
|
294
|
+
if (target) {
|
|
295
|
+
callNames.add(target.name ?? callNode.name ?? '<unknown>');
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
callNames.add(callNode.name ?? '<unknown>');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
overview.calls = Array.from(callNames);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return overview;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Build ClassOverview from a CLASS node.
|
|
312
|
+
* Fetches EXTENDS edge and methods via CONTAINS.
|
|
313
|
+
* Complexity: Without edges O(M), with edges O(M * (S + C))
|
|
314
|
+
*/
|
|
315
|
+
private async buildClassOverview(
|
|
316
|
+
node: BaseNodeRecord,
|
|
317
|
+
includeEdges: boolean
|
|
318
|
+
): Promise<ClassOverview> {
|
|
319
|
+
const overview: ClassOverview = {
|
|
320
|
+
id: node.id,
|
|
321
|
+
name: node.name ?? '<anonymous>',
|
|
322
|
+
line: node.line as number | undefined,
|
|
323
|
+
exported: (node.exported as boolean) ?? false,
|
|
324
|
+
methods: [],
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Get superclass
|
|
328
|
+
if (includeEdges) {
|
|
329
|
+
const extendsEdges = await this.graph.getOutgoingEdges(
|
|
330
|
+
node.id,
|
|
331
|
+
['EXTENDS']
|
|
332
|
+
);
|
|
333
|
+
if (extendsEdges.length > 0) {
|
|
334
|
+
const superNode = await this.graph.getNode(extendsEdges[0].dst);
|
|
335
|
+
overview.extends = superNode?.name ?? (node.superClass as string);
|
|
336
|
+
} else if (node.superClass) {
|
|
337
|
+
overview.extends = node.superClass as string;
|
|
338
|
+
}
|
|
339
|
+
} else if (node.superClass) {
|
|
340
|
+
overview.extends = node.superClass as string;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Get methods via CONTAINS or HAS_METHOD edges
|
|
344
|
+
const containsEdges = await this.graph.getOutgoingEdges(
|
|
345
|
+
node.id,
|
|
346
|
+
['CONTAINS', 'HAS_METHOD']
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
for (const edge of containsEdges) {
|
|
350
|
+
const child = await this.graph.getNode(edge.dst);
|
|
351
|
+
if (!child) continue;
|
|
352
|
+
|
|
353
|
+
if (child.type === 'FUNCTION' || child.type === 'METHOD') {
|
|
354
|
+
const methodOverview = await this.buildFunctionOverview(
|
|
355
|
+
child,
|
|
356
|
+
includeEdges
|
|
357
|
+
);
|
|
358
|
+
overview.methods.push(methodOverview);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
overview.methods.sort((a, b) => (a.line ?? 0) - (b.line ?? 0));
|
|
363
|
+
|
|
364
|
+
return overview;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Build VariableOverview from a VARIABLE or CONSTANT node.
|
|
369
|
+
* Complexity: Without edges O(1), with edges O(1)
|
|
370
|
+
*/
|
|
371
|
+
private async buildVariableOverview(
|
|
372
|
+
node: BaseNodeRecord,
|
|
373
|
+
includeEdges: boolean
|
|
374
|
+
): Promise<VariableOverview> {
|
|
375
|
+
const overview: VariableOverview = {
|
|
376
|
+
id: node.id,
|
|
377
|
+
name: node.name ?? '<anonymous>',
|
|
378
|
+
line: node.line as number | undefined,
|
|
379
|
+
kind: (node.kind as string) ?? 'const',
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
if (includeEdges) {
|
|
383
|
+
const assignedEdges = await this.graph.getOutgoingEdges(
|
|
384
|
+
node.id,
|
|
385
|
+
['ASSIGNED_FROM']
|
|
386
|
+
);
|
|
387
|
+
if (assignedEdges.length > 0) {
|
|
388
|
+
const sourceNode = await this.graph.getNode(assignedEdges[0].dst);
|
|
389
|
+
if (sourceNode) {
|
|
390
|
+
overview.assignedFrom = sourceNode.name ?? sourceNode.type;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return overview;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GrafemaUri -- conversion between compact semantic IDs and grafema:// URIs.
|
|
3
|
+
*
|
|
4
|
+
* URI format: grafema://{authority}/{file}#{encoded_fragment}
|
|
5
|
+
* Virtual nodes: grafema://{authority}/_/{encoded_full_id}
|
|
6
|
+
* Module nodes: grafema://{authority}/{file}#MODULE
|
|
7
|
+
*
|
|
8
|
+
* Fragment encoding: only > [ ] # need percent-encoding.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const GRAFEMA_SCHEME = 'grafema://';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if a string is a grafema:// URI.
|
|
15
|
+
*/
|
|
16
|
+
export function isGrafemaUri(str: string): boolean {
|
|
17
|
+
return str.startsWith(GRAFEMA_SCHEME);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Encode a fragment string for grafema:// URIs.
|
|
22
|
+
* Only 4 chars need percent-encoding: > [ ] #
|
|
23
|
+
*/
|
|
24
|
+
export function encodeFragment(raw: string): string {
|
|
25
|
+
let out = '';
|
|
26
|
+
for (let i = 0; i < raw.length; i++) {
|
|
27
|
+
const ch = raw[i];
|
|
28
|
+
switch (ch) {
|
|
29
|
+
case '>': out += '%3E'; break;
|
|
30
|
+
case '[': out += '%5B'; break;
|
|
31
|
+
case ']': out += '%5D'; break;
|
|
32
|
+
case '#': out += '%23'; break;
|
|
33
|
+
default: out += ch;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Decode a percent-encoded fragment back to raw string.
|
|
41
|
+
*/
|
|
42
|
+
export function decodeFragment(encoded: string): string {
|
|
43
|
+
return encoded
|
|
44
|
+
.replaceAll('%3E', '>')
|
|
45
|
+
.replaceAll('%3e', '>')
|
|
46
|
+
.replaceAll('%5B', '[')
|
|
47
|
+
.replaceAll('%5b', '[')
|
|
48
|
+
.replaceAll('%5D', ']')
|
|
49
|
+
.replaceAll('%5d', ']')
|
|
50
|
+
.replaceAll('%23', '#');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Convert a compact semantic ID to a grafema:// URI.
|
|
55
|
+
*
|
|
56
|
+
* @param compactId - Compact format: "file->TYPE->name[in:p,h:x]#N", "MODULE#file", "EXTERNAL_MODULE->x"
|
|
57
|
+
* @param authority - URI authority: "github.com/owner/repo" or "localhost/project"
|
|
58
|
+
* @returns grafema:// URI string
|
|
59
|
+
*/
|
|
60
|
+
export function toGrafemaUri(compactId: string, authority: string): string {
|
|
61
|
+
// Case 1: MODULE#file
|
|
62
|
+
if (compactId.startsWith('MODULE#')) {
|
|
63
|
+
const file = compactId.slice(7); // len("MODULE#") = 7
|
|
64
|
+
return `${GRAFEMA_SCHEME}${authority}/${file}#MODULE`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Case 2 & 3: Check for file->REST pattern
|
|
68
|
+
const firstArrow = compactId.indexOf('->');
|
|
69
|
+
if (firstArrow !== -1) {
|
|
70
|
+
const beforeArrow = compactId.slice(0, firstArrow);
|
|
71
|
+
|
|
72
|
+
// Heuristic: file paths contain '/' or '.'
|
|
73
|
+
const isFilePath = beforeArrow.includes('/') || beforeArrow.includes('.');
|
|
74
|
+
|
|
75
|
+
if (isFilePath) {
|
|
76
|
+
// Standard node: file->REST
|
|
77
|
+
const file = beforeArrow;
|
|
78
|
+
const rest = compactId.slice(firstArrow + 2);
|
|
79
|
+
return `${GRAFEMA_SCHEME}${authority}/${file}#${encodeFragment(rest)}`;
|
|
80
|
+
} else {
|
|
81
|
+
// Virtual node: encode the whole ID
|
|
82
|
+
return `${GRAFEMA_SCHEME}${authority}/_/${encodeFragment(compactId)}`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// No arrow -- virtual node
|
|
87
|
+
return `${GRAFEMA_SCHEME}${authority}/_/${encodeFragment(compactId)}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parsed grafema:// URI components.
|
|
92
|
+
*/
|
|
93
|
+
export interface ParsedGrafemaUri {
|
|
94
|
+
/** URI authority, e.g. "github.com/owner/repo" */
|
|
95
|
+
authority: string;
|
|
96
|
+
/** File path within the project, or empty for virtual nodes */
|
|
97
|
+
filePath: string;
|
|
98
|
+
/** Decoded symbol part (the fragment, decoded) */
|
|
99
|
+
symbolPart: string;
|
|
100
|
+
/** Reconstructed compact semantic ID */
|
|
101
|
+
semanticId: string;
|
|
102
|
+
/** Whether this is a virtual node (uses _/ path) */
|
|
103
|
+
isVirtual: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Parse a grafema:// URI into components.
|
|
108
|
+
*
|
|
109
|
+
* @returns Parsed components or null if not a valid grafema:// URI
|
|
110
|
+
*/
|
|
111
|
+
export function parseGrafemaUri(uri: string): ParsedGrafemaUri | null {
|
|
112
|
+
if (!uri.startsWith(GRAFEMA_SCHEME)) return null;
|
|
113
|
+
|
|
114
|
+
const rest = uri.slice(GRAFEMA_SCHEME.length); // "authority/file#fragment"
|
|
115
|
+
|
|
116
|
+
// Find the authority: everything up to the first '/' after the host
|
|
117
|
+
// Authority format: "host/project" (e.g., "github.com/owner/repo" or "localhost/project")
|
|
118
|
+
// We need to find where authority ends and file path begins.
|
|
119
|
+
// Convention: authority is "host/path" where host has no '/', so we split on first '/' after host.
|
|
120
|
+
// Actually, authority can be multi-segment (github.com/owner/repo = 3 segments).
|
|
121
|
+
// Strategy: find '#' first (fragment delimiter), then split the path part.
|
|
122
|
+
|
|
123
|
+
const hashPos = rest.indexOf('#');
|
|
124
|
+
const slashUnderscoreSlash = rest.indexOf('/_/');
|
|
125
|
+
|
|
126
|
+
if (slashUnderscoreSlash !== -1 && (hashPos === -1 || slashUnderscoreSlash < hashPos)) {
|
|
127
|
+
// Virtual node: authority/_/encoded_id
|
|
128
|
+
const authority = rest.slice(0, slashUnderscoreSlash);
|
|
129
|
+
const encodedId = rest.slice(slashUnderscoreSlash + 3); // after "/_/"
|
|
130
|
+
const decodedId = decodeFragment(encodedId);
|
|
131
|
+
|
|
132
|
+
// Reconstruct compact ID -- it's just the decoded full ID
|
|
133
|
+
return {
|
|
134
|
+
authority,
|
|
135
|
+
filePath: '',
|
|
136
|
+
symbolPart: decodedId,
|
|
137
|
+
semanticId: decodedId,
|
|
138
|
+
isVirtual: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (hashPos === -1) return null; // No fragment = invalid
|
|
143
|
+
|
|
144
|
+
const pathPart = rest.slice(0, hashPos); // "authority/file/path"
|
|
145
|
+
const fragment = rest.slice(hashPos + 1); // "TYPE-%3Ename..."
|
|
146
|
+
const decodedFragment = decodeFragment(fragment);
|
|
147
|
+
|
|
148
|
+
// Split pathPart into authority and filePath.
|
|
149
|
+
// The authority is the first N segments that represent the host+project.
|
|
150
|
+
// Problem: we don't know where authority ends and file begins.
|
|
151
|
+
// Convention from the plan: authority = "host/project" (e.g., "localhost/grafema")
|
|
152
|
+
// or "github.com/owner/repo" (3 segments).
|
|
153
|
+
// The file path typically starts with src/, lib/, etc.
|
|
154
|
+
//
|
|
155
|
+
// Better approach: find the file path by working backward from the fragment.
|
|
156
|
+
// The fragment's decoded form tells us the type. The module_id for MODULE# format
|
|
157
|
+
// means filePath = pathPart minus authority.
|
|
158
|
+
//
|
|
159
|
+
// Simplest reliable approach: the authority is stored separately or we use a heuristic.
|
|
160
|
+
// For now: authority = first 2 segments for "host/project" format,
|
|
161
|
+
// first 3 segments for known hosts (github.com, gitlab.com, bitbucket.org).
|
|
162
|
+
|
|
163
|
+
const segments = pathPart.split('/');
|
|
164
|
+
let authoritySegments: number;
|
|
165
|
+
|
|
166
|
+
const host = segments[0];
|
|
167
|
+
if (host === 'github.com' || host === 'gitlab.com' || host === 'bitbucket.org') {
|
|
168
|
+
authoritySegments = 3; // host/owner/repo
|
|
169
|
+
} else {
|
|
170
|
+
authoritySegments = 2; // host/project (localhost/grafema)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (segments.length < authoritySegments) return null;
|
|
174
|
+
|
|
175
|
+
const authority = segments.slice(0, authoritySegments).join('/');
|
|
176
|
+
const filePath = segments.slice(authoritySegments).join('/');
|
|
177
|
+
|
|
178
|
+
// Reconstruct compact semantic ID
|
|
179
|
+
let semanticId: string;
|
|
180
|
+
if (decodedFragment === 'MODULE') {
|
|
181
|
+
semanticId = `MODULE#${filePath}`;
|
|
182
|
+
} else {
|
|
183
|
+
semanticId = `${filePath}->${decodedFragment}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
authority,
|
|
188
|
+
filePath,
|
|
189
|
+
symbolPart: decodedFragment,
|
|
190
|
+
semanticId,
|
|
191
|
+
isVirtual: false,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Convert a grafema:// URI to compact semantic ID format.
|
|
197
|
+
* Convenience wrapper around parseGrafemaUri.
|
|
198
|
+
*
|
|
199
|
+
* @returns Compact semantic ID or the input unchanged if not a grafema:// URI
|
|
200
|
+
*/
|
|
201
|
+
export function toCompactSemanticId(uri: string): string {
|
|
202
|
+
if (!isGrafemaUri(uri)) return uri;
|
|
203
|
+
const parsed = parseGrafemaUri(uri);
|
|
204
|
+
if (!parsed) return uri;
|
|
205
|
+
return parsed.semanticId;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Normalize a semantic ID input -- accepts either URI or compact format.
|
|
210
|
+
* If it's a URI, converts to compact. If already compact, returns as-is.
|
|
211
|
+
*
|
|
212
|
+
* Useful in MCP handlers that need to accept both formats.
|
|
213
|
+
*/
|
|
214
|
+
export function normalizeSemanticId(input: string): string {
|
|
215
|
+
return toCompactSemanticId(input);
|
|
216
|
+
}
|