@ctxo/cli 0.10.0 → 0.11.1
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 +18 -0
- package/dist/{atomic-write-AQNYJFAG.js → atomic-write-BAFH464Q.js} +3 -2
- package/dist/chunk-5DSKZJNN.js +38 -0
- package/dist/chunk-5DSKZJNN.js.map +1 -0
- package/dist/{chunk-2TVHKCCU.js → chunk-CDGQGCVN.js} +55 -5
- package/dist/chunk-CDGQGCVN.js.map +1 -0
- package/dist/{chunk-I6HLEK4G.js → chunk-HZ4UGA6J.js} +43 -9
- package/dist/chunk-HZ4UGA6J.js.map +1 -0
- package/dist/{chunk-6EKERTJ7.js → chunk-RFEXMBTG.js} +4 -36
- package/dist/chunk-RFEXMBTG.js.map +1 -0
- package/dist/{cli-router-45W4IRYE.js → cli-router-2JVPBW4S.js} +389 -215
- package/dist/cli-router-2JVPBW4S.js.map +1 -0
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/json-index-reader-UYS74USB.js +9 -0
- package/llms-full.txt +2 -1
- package/llms.txt +1 -0
- package/package.json +4 -4
- package/dist/chunk-2TVHKCCU.js.map +0 -1
- package/dist/chunk-6EKERTJ7.js.map +0 -1
- package/dist/chunk-I6HLEK4G.js.map +0 -1
- package/dist/cli-router-45W4IRYE.js.map +0 -1
- package/dist/json-index-reader-XOEE6VMD.js +0 -8
- /package/dist/{atomic-write-AQNYJFAG.js.map → atomic-write-BAFH464Q.js.map} +0 -0
- /package/dist/{json-index-reader-XOEE6VMD.js.map → json-index-reader-UYS74USB.js.map} +0 -0
package/README.md
CHANGED
|
@@ -97,6 +97,24 @@ gate:
|
|
|
97
97
|
| Cursor | no (no blocking pre-edit hook) | yes | yes |
|
|
98
98
|
| Other | no | where supported | yes |
|
|
99
99
|
|
|
100
|
+
## Supported Languages
|
|
101
|
+
|
|
102
|
+
| Language | Plugin | Tier | Notes |
|
|
103
|
+
|---|---|---|---|
|
|
104
|
+
| TypeScript / JavaScript | `@ctxo/lang-typescript` | full | ts-morph; type-aware cross-file resolution |
|
|
105
|
+
| Go | `@ctxo/lang-go` | syntax | tree-sitter; exported-symbol analysis |
|
|
106
|
+
| C# | `@ctxo/lang-csharp` | full | Roslyn; .NET SDK 8+ for full tier, tree-sitter fallback |
|
|
107
|
+
| Java | `@ctxo/lang-java` | syntax / full | tree-sitter syntax tier built-in; full tier via `@ctxo/lang-java-analyzer` companion (requires JRE 17+) |
|
|
108
|
+
|
|
109
|
+
Install via the CLI shortcut:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
ctxo install typescript go csharp # syntax or full tier auto-detected
|
|
113
|
+
ctxo install java # installs syntax tier; adds full-tier analyzer when JRE 17+ detected
|
|
114
|
+
ctxo install java --full-tier # force full-tier analyzer install
|
|
115
|
+
ctxo install java --syntax-only # skip analyzer, syntax tier only
|
|
116
|
+
```
|
|
117
|
+
|
|
100
118
|
## Links
|
|
101
119
|
|
|
102
120
|
* [Docs](https://alperhankendi.github.io/Ctxo/docs/) quick start, MCP tools, CLI reference, integrations
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
atomicWrite,
|
|
4
4
|
sweepStaleTmpFiles
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-RFEXMBTG.js";
|
|
6
|
+
import "./chunk-5DSKZJNN.js";
|
|
6
7
|
export {
|
|
7
8
|
atomicWrite,
|
|
8
9
|
sweepStaleTmpFiles
|
|
9
10
|
};
|
|
10
|
-
//# sourceMappingURL=atomic-write-
|
|
11
|
+
//# sourceMappingURL=atomic-write-BAFH464Q.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/logger.ts
|
|
4
|
+
function isDebugEnabled(namespace) {
|
|
5
|
+
const debugEnv = process.env["DEBUG"] ?? "";
|
|
6
|
+
if (!debugEnv) return false;
|
|
7
|
+
const patterns = debugEnv.split(",").map((p) => p.trim());
|
|
8
|
+
return patterns.some((p) => {
|
|
9
|
+
if (p === "*" || p === "ctxo:*") return true;
|
|
10
|
+
if (p === namespace) return true;
|
|
11
|
+
if (p.endsWith(":*") && namespace.startsWith(p.slice(0, -1))) return true;
|
|
12
|
+
return false;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function createLogger(namespace) {
|
|
16
|
+
const prefix = `[${namespace}]`;
|
|
17
|
+
return {
|
|
18
|
+
debug(message, ...args) {
|
|
19
|
+
if (isDebugEnabled(namespace)) {
|
|
20
|
+
console.error(`${prefix} DEBUG ${message}`, ...args);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
info(message, ...args) {
|
|
24
|
+
console.error(`${prefix} ${message}`, ...args);
|
|
25
|
+
},
|
|
26
|
+
warn(message, ...args) {
|
|
27
|
+
console.error(`${prefix} WARN ${message}`, ...args);
|
|
28
|
+
},
|
|
29
|
+
error(message, ...args) {
|
|
30
|
+
console.error(`${prefix} ERROR ${message}`, ...args);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
createLogger
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=chunk-5DSKZJNN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/logger.ts"],"sourcesContent":["/**\n * Structured logger for Ctxo — all output goes to stderr (stdout reserved for MCP JSON-RPC).\n *\n * Supports debug mode via DEBUG environment variable:\n * DEBUG=ctxo:* — all debug output\n * DEBUG=ctxo:git — only git adapter debug\n * DEBUG=ctxo:index — only indexing debug\n * DEBUG=ctxo:mcp — only MCP handler debug\n * DEBUG=ctxo:storage — only storage adapter debug\n * DEBUG=ctxo:masking — only masking pipeline debug\n *\n * Always outputs: info, warn, error (regardless of DEBUG).\n * Only outputs debug when matching namespace is enabled.\n */\n\nfunction isDebugEnabled(namespace: string): boolean {\n const debugEnv = process.env['DEBUG'] ?? '';\n if (!debugEnv) return false;\n const patterns = debugEnv.split(',').map(p => p.trim());\n return patterns.some(p => {\n if (p === '*' || p === 'ctxo:*') return true;\n if (p === namespace) return true;\n if (p.endsWith(':*') && namespace.startsWith(p.slice(0, -1))) return true;\n return false;\n });\n}\n\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\nexport function createLogger(namespace: string): Logger {\n const prefix = `[${namespace}]`;\n\n return {\n debug(message: string, ...args: unknown[]) {\n if (isDebugEnabled(namespace)) {\n console.error(`${prefix} DEBUG ${message}`, ...args);\n }\n },\n info(message: string, ...args: unknown[]) {\n console.error(`${prefix} ${message}`, ...args);\n },\n warn(message: string, ...args: unknown[]) {\n console.error(`${prefix} WARN ${message}`, ...args);\n },\n error(message: string, ...args: unknown[]) {\n console.error(`${prefix} ERROR ${message}`, ...args);\n },\n };\n}\n"],"mappings":";;;AAeA,SAAS,eAAe,WAA4B;AAClD,QAAM,WAAW,QAAQ,IAAI,OAAO,KAAK;AACzC,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,WAAW,SAAS,MAAM,GAAG,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC;AACtD,SAAO,SAAS,KAAK,OAAK;AACxB,QAAI,MAAM,OAAO,MAAM,SAAU,QAAO;AACxC,QAAI,MAAM,UAAW,QAAO;AAC5B,QAAI,EAAE,SAAS,IAAI,KAAK,UAAU,WAAW,EAAE,MAAM,GAAG,EAAE,CAAC,EAAG,QAAO;AACrE,WAAO;AAAA,EACT,CAAC;AACH;AASO,SAAS,aAAa,WAA2B;AACtD,QAAM,SAAS,IAAI,SAAS;AAE5B,SAAO;AAAA,IACL,MAAM,YAAoB,MAAiB;AACzC,UAAI,eAAe,SAAS,GAAG;AAC7B,gBAAQ,MAAM,GAAG,MAAM,UAAU,OAAO,IAAI,GAAG,IAAI;AAAA,MACrD;AAAA,IACF;AAAA,IACA,KAAK,YAAoB,MAAiB;AACxC,cAAQ,MAAM,GAAG,MAAM,IAAI,OAAO,IAAI,GAAG,IAAI;AAAA,IAC/C;AAAA,IACA,KAAK,YAAoB,MAAiB;AACxC,cAAQ,MAAM,GAAG,MAAM,SAAS,OAAO,IAAI,GAAG,IAAI;AAAA,IACpD;AAAA,IACA,MAAM,YAAoB,MAAiB;AACzC,cAAQ,MAAM,GAAG,MAAM,UAAU,OAAO,IAAI,GAAG,IAAI;AAAA,IACrD;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createLogger
|
|
4
|
+
} from "./chunk-5DSKZJNN.js";
|
|
2
5
|
|
|
3
6
|
// src/adapters/storage/json-index-reader.ts
|
|
4
7
|
import { readFileSync, readdirSync, existsSync, realpathSync } from "fs";
|
|
@@ -39,6 +42,16 @@ var SymbolIdSchema = z.string().min(1).refine(
|
|
|
39
42
|
},
|
|
40
43
|
{ message: 'Symbol ID must match format "<file>::<name>::<kind>"' }
|
|
41
44
|
);
|
|
45
|
+
var EdgeTargetSchema = z.string().refine(
|
|
46
|
+
(s) => {
|
|
47
|
+
const parts = s.split("::");
|
|
48
|
+
if (parts.length !== 3 && parts.length !== 2) return false;
|
|
49
|
+
if (parts.some((p) => p.length === 0)) return false;
|
|
50
|
+
const kind = parts[parts.length - 1];
|
|
51
|
+
return SYMBOL_KINDS.includes(kind);
|
|
52
|
+
},
|
|
53
|
+
{ message: "edge target must be <file>::<name>::<kind> or <name>::<kind>" }
|
|
54
|
+
);
|
|
42
55
|
var SymbolNodeSchema = z.object({
|
|
43
56
|
symbolId: SymbolIdSchema,
|
|
44
57
|
name: z.string().min(1),
|
|
@@ -52,7 +65,9 @@ var SymbolNodeSchema = z.object({
|
|
|
52
65
|
});
|
|
53
66
|
var GraphEdgeSchema = z.object({
|
|
54
67
|
from: SymbolIdSchema,
|
|
55
|
-
|
|
68
|
+
// strict 3-part: from is always a local, resolved symbol
|
|
69
|
+
to: EdgeTargetSchema,
|
|
70
|
+
// 3-part OR 2-part name-ref for cross-file/unresolved targets
|
|
56
71
|
kind: EdgeKindSchema,
|
|
57
72
|
typeOnly: z.boolean().optional()
|
|
58
73
|
});
|
|
@@ -83,6 +98,7 @@ var FileIndexSchema = z.object({
|
|
|
83
98
|
});
|
|
84
99
|
|
|
85
100
|
// src/adapters/storage/json-index-reader.ts
|
|
101
|
+
var log = createLogger("ctxo:json-reader");
|
|
86
102
|
var JsonIndexReader = class {
|
|
87
103
|
indexDir;
|
|
88
104
|
constructor(ctxoRoot) {
|
|
@@ -107,14 +123,48 @@ var JsonIndexReader = class {
|
|
|
107
123
|
const raw = readFileSync(absolutePath, "utf-8");
|
|
108
124
|
const data = JSON.parse(raw);
|
|
109
125
|
const result = FileIndexSchema.safeParse(data);
|
|
110
|
-
if (
|
|
111
|
-
|
|
126
|
+
if (result.success) {
|
|
127
|
+
return result.data;
|
|
128
|
+
}
|
|
129
|
+
const rel = relative(this.indexDir, absolutePath);
|
|
130
|
+
const raw_data = data;
|
|
131
|
+
if (typeof raw_data["file"] !== "string" || !raw_data["file"] || typeof raw_data["lastModified"] !== "number") {
|
|
132
|
+
console.error(
|
|
133
|
+
`[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`
|
|
134
|
+
);
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
137
|
+
const rawSymbols = Array.isArray(raw_data["symbols"]) ? raw_data["symbols"] : [];
|
|
138
|
+
const rawEdges = Array.isArray(raw_data["edges"]) ? raw_data["edges"] : [];
|
|
139
|
+
const rawIntent = Array.isArray(raw_data["intent"]) ? raw_data["intent"] : [];
|
|
140
|
+
const rawAntiPatterns = Array.isArray(raw_data["antiPatterns"]) ? raw_data["antiPatterns"] : [];
|
|
141
|
+
const validSymbols = rawSymbols.filter((s) => SymbolNodeSchema.safeParse(s).success);
|
|
142
|
+
const validEdges = rawEdges.filter((e) => GraphEdgeSchema.safeParse(e).success);
|
|
143
|
+
const validIntent = rawIntent.filter((i) => CommitIntentSchema.safeParse(i).success);
|
|
144
|
+
const validAntiPatterns = rawAntiPatterns.filter((a) => AntiPatternSchema.safeParse(a).success);
|
|
145
|
+
const droppedSymbols = rawSymbols.length - validSymbols.length;
|
|
146
|
+
const droppedEdges = rawEdges.length - validEdges.length;
|
|
147
|
+
const droppedIntent = rawIntent.length - validIntent.length;
|
|
148
|
+
const droppedAntiPatterns = rawAntiPatterns.length - validAntiPatterns.length;
|
|
149
|
+
if (droppedSymbols > 0 || droppedEdges > 0 || droppedIntent > 0 || droppedAntiPatterns > 0) {
|
|
150
|
+
log.warn(
|
|
151
|
+
`${rel}: dropped ${droppedSymbols} invalid symbol(s), ${droppedEdges} invalid edge(s), ${droppedIntent} invalid intent(s), ${droppedAntiPatterns} invalid antiPattern(s) \u2014 kept ${validSymbols.length} symbol(s), ${validEdges.length} edge(s), ${validIntent.length} intent(s), ${validAntiPatterns.length} antiPattern(s)`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const recovered = FileIndexSchema.safeParse({
|
|
155
|
+
...raw_data,
|
|
156
|
+
symbols: validSymbols,
|
|
157
|
+
edges: validEdges,
|
|
158
|
+
intent: validIntent,
|
|
159
|
+
antiPatterns: validAntiPatterns
|
|
160
|
+
});
|
|
161
|
+
if (!recovered.success) {
|
|
112
162
|
console.error(
|
|
113
163
|
`[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`
|
|
114
164
|
);
|
|
115
165
|
return void 0;
|
|
116
166
|
}
|
|
117
|
-
return
|
|
167
|
+
return recovered.data;
|
|
118
168
|
} catch (err) {
|
|
119
169
|
const rel = relative(this.indexDir, absolutePath);
|
|
120
170
|
console.error(
|
|
@@ -146,4 +196,4 @@ export {
|
|
|
146
196
|
DetailLevelSchema,
|
|
147
197
|
JsonIndexReader
|
|
148
198
|
};
|
|
149
|
-
//# sourceMappingURL=chunk-
|
|
199
|
+
//# sourceMappingURL=chunk-CDGQGCVN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/storage/json-index-reader.ts","../src/core/types.ts"],"sourcesContent":["import { readFileSync, readdirSync, existsSync, realpathSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport {\n FileIndexSchema,\n SymbolNodeSchema,\n GraphEdgeSchema,\n CommitIntentSchema,\n AntiPatternSchema,\n type FileIndex,\n} from '../../core/types.js';\nimport { createLogger } from '../../core/logger.js';\n\nconst log = createLogger('ctxo:json-reader');\n\nexport class JsonIndexReader {\n private readonly indexDir: string;\n\n constructor(ctxoRoot: string) {\n this.indexDir = join(ctxoRoot, 'index');\n }\n\n readAll(): FileIndex[] {\n if (!existsSync(this.indexDir)) {\n return [];\n }\n\n const jsonFiles = this.collectJsonFiles(this.indexDir);\n const results: FileIndex[] = [];\n\n for (const filePath of jsonFiles) {\n const parsed = this.readSingle(filePath);\n if (parsed) {\n results.push(parsed);\n }\n }\n\n return results;\n }\n\n readSingle(absolutePath: string): FileIndex | undefined {\n try {\n const raw = readFileSync(absolutePath, 'utf-8');\n const data: unknown = JSON.parse(raw);\n const result = FileIndexSchema.safeParse(data);\n\n if (result.success) {\n return result.data;\n }\n\n // Strict parse failed — attempt resilient recovery: keep valid items, drop invalid ones.\n const rel = relative(this.indexDir, absolutePath);\n const raw_data = data as Record<string, unknown>;\n\n // Must have at least a valid file name and lastModified to be recoverable.\n if (typeof raw_data['file'] !== 'string' || !raw_data['file'] ||\n typeof raw_data['lastModified'] !== 'number') {\n console.error(\n `[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`,\n );\n return undefined;\n }\n\n const rawSymbols = Array.isArray(raw_data['symbols']) ? raw_data['symbols'] : [];\n const rawEdges = Array.isArray(raw_data['edges']) ? raw_data['edges'] : [];\n const rawIntent = Array.isArray(raw_data['intent']) ? raw_data['intent'] : [];\n const rawAntiPatterns = Array.isArray(raw_data['antiPatterns']) ? raw_data['antiPatterns'] : [];\n\n const validSymbols = rawSymbols.filter((s) => SymbolNodeSchema.safeParse(s).success);\n const validEdges = rawEdges.filter((e) => GraphEdgeSchema.safeParse(e).success);\n const validIntent = rawIntent.filter((i) => CommitIntentSchema.safeParse(i).success);\n const validAntiPatterns = rawAntiPatterns.filter((a) => AntiPatternSchema.safeParse(a).success);\n\n const droppedSymbols = rawSymbols.length - validSymbols.length;\n const droppedEdges = rawEdges.length - validEdges.length;\n const droppedIntent = rawIntent.length - validIntent.length;\n const droppedAntiPatterns = rawAntiPatterns.length - validAntiPatterns.length;\n\n if (droppedSymbols > 0 || droppedEdges > 0 || droppedIntent > 0 || droppedAntiPatterns > 0) {\n log.warn(\n `${rel}: dropped ${droppedSymbols} invalid symbol(s), ${droppedEdges} invalid edge(s), ${droppedIntent} invalid intent(s), ${droppedAntiPatterns} invalid antiPattern(s) — kept ${validSymbols.length} symbol(s), ${validEdges.length} edge(s), ${validIntent.length} intent(s), ${validAntiPatterns.length} antiPattern(s)`,\n );\n }\n\n // Re-parse with the filtered arrays to get a fully-typed FileIndex.\n const recovered = FileIndexSchema.safeParse({\n ...raw_data,\n symbols: validSymbols,\n edges: validEdges,\n intent: validIntent,\n antiPatterns: validAntiPatterns,\n });\n\n if (!recovered.success) {\n console.error(\n `[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`,\n );\n return undefined;\n }\n\n return recovered.data;\n } catch (err) {\n const rel = relative(this.indexDir, absolutePath);\n console.error(\n `[ctxo:json-reader] Failed to read ${rel}: ${(err as Error).message}`,\n );\n return undefined;\n }\n }\n\n private collectJsonFiles(dir: string, visited: Set<string> = new Set()): string[] {\n const realDir = realpathSync(dir);\n if (visited.has(realDir)) return []; // Guard against symlink loops\n visited.add(realDir);\n\n const files: string[] = [];\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.isSymbolicLink()) continue; // Skip symlinks entirely\n\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n // Skip non-FileIndex directories under .ctxo/index/\n if (entry.name === 'communities.history') continue;\n files.push(...this.collectJsonFiles(fullPath, visited));\n } else if (\n entry.name.endsWith('.json') &&\n entry.name !== 'co-changes.json' &&\n entry.name !== 'communities.json' &&\n entry.name !== 'drift-events.json' &&\n entry.name !== 'boundary-violations.json'\n ) {\n files.push(fullPath);\n }\n }\n\n return files;\n }\n}\n","import { z } from 'zod';\n\n// ── Symbol Kinds ────────────────────────────────────────────────\n\nexport const SYMBOL_KINDS = [\n 'function',\n 'class',\n 'interface',\n 'method',\n 'variable',\n 'type',\n] as const;\n\nexport const SymbolKindSchema = z.enum(SYMBOL_KINDS);\nexport type SymbolKind = z.infer<typeof SymbolKindSchema>;\n\n// ── Edge Kinds ──────────────────────────────────────────────────\n\nexport const EDGE_KINDS = [\n 'imports',\n 'calls',\n 'extends',\n 'implements',\n 'uses',\n] as const;\n\nexport const EdgeKindSchema = z.enum(EDGE_KINDS);\nexport type EdgeKind = z.infer<typeof EdgeKindSchema>;\n\n// ── Detail Levels ───────────────────────────────────────────────\n\nexport const DETAIL_LEVELS = [1, 2, 3, 4] as const;\n\nexport const DetailLevelSchema = z.union([\n z.literal(1),\n z.literal(2),\n z.literal(3),\n z.literal(4),\n]);\nexport type DetailLevel = z.infer<typeof DetailLevelSchema>;\n\n// ── Symbol ID ───────────────────────────────────────────────────\n\n/**\n * Format: \"<relativeFile>::<name>::<kind>\"\n * Example: \"src/foo.ts::myFn::function\"\n */\nexport const SymbolIdSchema = z\n .string()\n .min(1)\n .refine(\n (value) => {\n const parts = value.split('::');\n if (parts.length !== 3) return false;\n const [file, name, kind] = parts;\n if (!file || !name || !kind) return false;\n return SymbolKindSchema.safeParse(kind).success;\n },\n { message: 'Symbol ID must match format \"<file>::<name>::<kind>\"' },\n );\nexport type SymbolId = z.infer<typeof SymbolIdSchema>;\n\n// ── Edge Target ─────────────────────────────────────────────────\n\n/**\n * An edge target: either a full 3-part symbol ID (\"<file>::<name>::<kind>\")\n * or a 2-part name-reference (\"<name>::<kind>\") for cross-file / unresolved\n * targets. The graph builder resolves name-references by name at query time.\n */\nexport const EdgeTargetSchema = z.string().refine(\n (s) => {\n const parts = s.split('::');\n if (parts.length !== 3 && parts.length !== 2) return false;\n if (parts.some((p) => p.length === 0)) return false;\n const kind = parts[parts.length - 1];\n return (SYMBOL_KINDS as readonly string[]).includes(kind as string);\n },\n { message: 'edge target must be <file>::<name>::<kind> or <name>::<kind>' },\n);\nexport type EdgeTarget = z.infer<typeof EdgeTargetSchema>;\n\n// ── Symbol Node ─────────────────────────────────────────────────\n\nexport const SymbolNodeSchema = z\n .object({\n symbolId: SymbolIdSchema,\n name: z.string().min(1),\n kind: SymbolKindSchema,\n startLine: z.number().int().nonnegative(),\n endLine: z.number().int().nonnegative(),\n startOffset: z.number().int().nonnegative().optional(),\n endOffset: z.number().int().nonnegative().optional(),\n })\n .refine((node) => node.endLine >= node.startLine, {\n message: 'endLine must be >= startLine',\n });\nexport type SymbolNode = z.infer<typeof SymbolNodeSchema>;\n\n// ── Graph Edge ──────────────────────────────────────────────────\n\nexport const GraphEdgeSchema = z.object({\n from: SymbolIdSchema, // strict 3-part: from is always a local, resolved symbol\n to: EdgeTargetSchema, // 3-part OR 2-part name-ref for cross-file/unresolved targets\n kind: EdgeKindSchema,\n typeOnly: z.boolean().optional(),\n});\nexport type GraphEdge = z.infer<typeof GraphEdgeSchema>;\n\n// ── Commit Intent ───────────────────────────────────────────────\n\nexport const CommitIntentSchema = z.object({\n hash: z.string().min(1),\n message: z.string(),\n date: z.string().min(1),\n kind: z.literal('commit'),\n});\nexport type CommitIntent = z.infer<typeof CommitIntentSchema>;\n\n// ── Anti-Pattern ────────────────────────────────────────────────\n\nexport const AntiPatternSchema = z.object({\n hash: z.string().min(1),\n message: z.string(),\n date: z.string().min(1),\n});\nexport type AntiPattern = z.infer<typeof AntiPatternSchema>;\n\n// ── File Index ──────────────────────────────────────────────────\n\nexport const ComplexityMetricsSchema = z.object({\n symbolId: z.string().min(1),\n cyclomatic: z.number().nonnegative(),\n});\n\nexport const FileIndexSchema = z.object({\n file: z.string().min(1),\n lastModified: z.number().nonnegative(),\n contentHash: z.string().optional(),\n symbols: z.array(SymbolNodeSchema),\n edges: z.array(GraphEdgeSchema),\n complexity: z.array(ComplexityMetricsSchema).optional(),\n intent: z.array(CommitIntentSchema),\n antiPatterns: z.array(AntiPatternSchema),\n});\nexport type FileIndex = z.infer<typeof FileIndexSchema>;\n\n// ── Logic-Slice Result ──────────────────────────────────────────\n\nexport interface LogicSliceResult {\n readonly root: SymbolNode;\n readonly dependencies: readonly SymbolNode[];\n readonly edges: readonly GraphEdge[];\n}\n\n// ── Formatted Slice ─────────────────────────────────────────────\n\nexport interface TruncationInfo {\n readonly truncated: true;\n readonly reason: 'token_budget_exceeded';\n}\n\nexport interface FormattedSlice {\n readonly root: SymbolNode;\n readonly dependencies: readonly SymbolNode[];\n readonly edges: readonly GraphEdge[];\n readonly level: DetailLevel;\n readonly levelDescription?: string;\n readonly truncation?: TruncationInfo;\n}\n\n// ── Complexity & Churn ──────────────────────────────────────────\n\nexport interface ComplexityMetrics {\n readonly symbolId: string;\n readonly cyclomatic: number;\n}\n\nexport interface ChurnData {\n readonly filePath: string;\n readonly commitCount: number;\n}\n\nexport const SCORE_BANDS = ['low', 'medium', 'high'] as const;\nexport type ScoreBand = (typeof SCORE_BANDS)[number];\n\nexport interface ChangeIntelligenceScore {\n readonly symbolId: string;\n readonly complexity: number;\n readonly churn: number;\n readonly composite: number;\n readonly band: ScoreBand;\n}\n\n// ── Why-Context Result ──────────────────────────────────────────\n\nexport interface WhyContextResult {\n readonly commitHistory: readonly CommitIntent[];\n readonly antiPatternWarnings: readonly AntiPattern[];\n readonly changeIntelligence?: ChangeIntelligenceScore;\n}\n\n// ── Co-Change Analysis ─────────────────────────────────────────\n\nexport interface CoChangeEntry {\n readonly file1: string;\n readonly file2: string;\n readonly sharedCommits: number;\n readonly frequency: number;\n}\n\nexport interface CoChangeMatrix {\n readonly version: 1;\n readonly timestamp: number;\n readonly entries: readonly CoChangeEntry[];\n}\n\n// ── Commit Record (from git adapter) ────────────────────────────\n\nexport interface CommitRecord {\n readonly hash: string;\n readonly message: string;\n readonly date: string;\n readonly author: string;\n}\n\n// ── Blame Line (from git adapter) ───────────────────────────────\n\nexport interface BlameLine {\n readonly hash: string;\n readonly lineNumber: number;\n readonly author: string;\n readonly date: string;\n}\n\n// ── Search Types ───────────────────────────────────────────────\n\nexport interface SearchResult {\n symbolId: string;\n name: string;\n kind: string;\n filePath: string;\n /** BM25 relevance score (higher = more relevant) */\n relevanceScore: number;\n /** PageRank importance score (0-1) */\n importanceScore: number;\n /** Final combined score after all boosts */\n combinedScore: number;\n}\n\nexport interface SearchMetrics {\n porterHits: number;\n trigramHits: number;\n phase2Activated: boolean;\n fuzzyApplied: boolean;\n latencyMs: number;\n}\n\nexport interface FuzzyCorrection {\n originalQuery: string;\n correctedQuery: string;\n corrections: Array<{\n original: string;\n corrected: string;\n distance: number;\n }>;\n}\n\nexport interface SearchResponse {\n query: string;\n results: SearchResult[];\n metrics: SearchMetrics;\n fuzzyCorrection?: FuzzyCorrection;\n}\n\n// ── Community Detection ────────────────────────────────────────\n\nexport interface CommunityEntry {\n readonly symbolId: string;\n readonly communityId: number;\n readonly communityLabel: string;\n}\n\nexport interface GodNode {\n readonly symbolId: string;\n readonly bridgedCommunities: readonly number[];\n readonly centralityScore: number;\n}\n\nexport const EDGE_QUALITY = ['full', 'syntax-only', 'mixed'] as const;\nexport type EdgeQuality = (typeof EDGE_QUALITY)[number];\n\nexport interface CommunitySnapshot {\n readonly version: 1;\n readonly computedAt: string;\n readonly commitSha: string;\n readonly modularity: number;\n readonly communities: readonly CommunityEntry[];\n readonly godNodes: readonly GodNode[];\n readonly edgeQuality: EdgeQuality;\n readonly crossClusterEdges: number;\n}\n\nexport interface DriftEvent {\n readonly symbolId: string;\n readonly movedFrom: { readonly id: number; readonly label: string };\n readonly movedTo: { readonly id: number; readonly label: string };\n readonly firstSeenInNewCluster: string;\n}\n\nexport interface BoundaryViolation {\n readonly from: {\n readonly symbolId: string;\n readonly communityId: number;\n readonly label: string;\n };\n readonly to: {\n readonly symbolId: string;\n readonly communityId: number;\n readonly label: string;\n };\n readonly edgeKind: EdgeKind;\n readonly historicalEdgesBetweenClusters: number;\n readonly severity: 'high' | 'medium';\n}\n\n// ── Persisted architectural artifacts ──────────────────────────\n// Written by `ctxo index` next to communities.json so the static\n// HTML visualizer can render Architecture views without re-running\n// detectors in the browser.\n\nexport type ConfidenceLevel = 'high' | 'medium' | 'low';\n\nexport interface DriftEventsArtifact {\n readonly version: 1;\n readonly computedAt: string;\n readonly commitSha: string;\n readonly confidence: ConfidenceLevel;\n readonly snapshotsAvailable: number;\n readonly hint?: string;\n readonly events: readonly DriftEvent[];\n}\n\nexport interface BoundaryViolationsArtifact {\n readonly version: 1;\n readonly computedAt: string;\n readonly commitSha: string;\n readonly confidence: ConfidenceLevel;\n readonly snapshotsAvailable: number;\n readonly hint?: string;\n readonly violations: readonly BoundaryViolation[];\n}\n"],"mappings":";;;;;;AAAA,SAAS,cAAc,aAAa,YAAY,oBAAoB;AACpE,SAAS,MAAM,gBAAgB;;;ACD/B,SAAS,SAAS;AAIX,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,mBAAmB,EAAE,KAAK,YAAY;AAK5C,IAAM,aAAa;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,iBAAiB,EAAE,KAAK,UAAU;AAOxC,IAAM,oBAAoB,EAAE,MAAM;AAAA,EACvC,EAAE,QAAQ,CAAC;AAAA,EACX,EAAE,QAAQ,CAAC;AAAA,EACX,EAAE,QAAQ,CAAC;AAAA,EACX,EAAE,QAAQ,CAAC;AACb,CAAC;AASM,IAAM,iBAAiB,EAC3B,OAAO,EACP,IAAI,CAAC,EACL;AAAA,EACC,CAAC,UAAU;AACT,UAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,CAAC,MAAM,MAAM,IAAI,IAAI;AAC3B,QAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAM,QAAO;AACpC,WAAO,iBAAiB,UAAU,IAAI,EAAE;AAAA,EAC1C;AAAA,EACA,EAAE,SAAS,uDAAuD;AACpE;AAUK,IAAM,mBAAmB,EAAE,OAAO,EAAE;AAAA,EACzC,CAAC,MAAM;AACL,UAAM,QAAQ,EAAE,MAAM,IAAI;AAC1B,QAAI,MAAM,WAAW,KAAK,MAAM,WAAW,EAAG,QAAO;AACrD,QAAI,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,EAAG,QAAO;AAC9C,UAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,WAAQ,aAAmC,SAAS,IAAc;AAAA,EACpE;AAAA,EACA,EAAE,SAAS,+DAA+D;AAC5E;AAKO,IAAM,mBAAmB,EAC7B,OAAO;AAAA,EACN,UAAU;AAAA,EACV,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,MAAM;AAAA,EACN,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACxC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACtC,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;AAAA,EACrD,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;AACrD,CAAC,EACA,OAAO,CAAC,SAAS,KAAK,WAAW,KAAK,WAAW;AAAA,EAChD,SAAS;AACX,CAAC;AAKI,IAAM,kBAAkB,EAAE,OAAO;AAAA,EACtC,MAAM;AAAA;AAAA,EACN,IAAI;AAAA;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,EAAE,QAAQ,EAAE,SAAS;AACjC,CAAC;AAKM,IAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,SAAS,EAAE,OAAO;AAAA,EAClB,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,MAAM,EAAE,QAAQ,QAAQ;AAC1B,CAAC;AAKM,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,SAAS,EAAE,OAAO;AAAA,EAClB,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AACxB,CAAC;AAKM,IAAM,0BAA0B,EAAE,OAAO;AAAA,EAC9C,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,YAAY,EAAE,OAAO,EAAE,YAAY;AACrC,CAAC;AAEM,IAAM,kBAAkB,EAAE,OAAO;AAAA,EACtC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,cAAc,EAAE,OAAO,EAAE,YAAY;AAAA,EACrC,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,SAAS,EAAE,MAAM,gBAAgB;AAAA,EACjC,OAAO,EAAE,MAAM,eAAe;AAAA,EAC9B,YAAY,EAAE,MAAM,uBAAuB,EAAE,SAAS;AAAA,EACtD,QAAQ,EAAE,MAAM,kBAAkB;AAAA,EAClC,cAAc,EAAE,MAAM,iBAAiB;AACzC,CAAC;;;ADnID,IAAM,MAAM,aAAa,kBAAkB;AAEpC,IAAM,kBAAN,MAAsB;AAAA,EACV;AAAA,EAEjB,YAAY,UAAkB;AAC5B,SAAK,WAAW,KAAK,UAAU,OAAO;AAAA,EACxC;AAAA,EAEA,UAAuB;AACrB,QAAI,CAAC,WAAW,KAAK,QAAQ,GAAG;AAC9B,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,YAAY,KAAK,iBAAiB,KAAK,QAAQ;AACrD,UAAM,UAAuB,CAAC;AAE9B,eAAW,YAAY,WAAW;AAChC,YAAM,SAAS,KAAK,WAAW,QAAQ;AACvC,UAAI,QAAQ;AACV,gBAAQ,KAAK,MAAM;AAAA,MACrB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,cAA6C;AACtD,QAAI;AACF,YAAM,MAAM,aAAa,cAAc,OAAO;AAC9C,YAAM,OAAgB,KAAK,MAAM,GAAG;AACpC,YAAM,SAAS,gBAAgB,UAAU,IAAI;AAE7C,UAAI,OAAO,SAAS;AAClB,eAAO,OAAO;AAAA,MAChB;AAGA,YAAM,MAAM,SAAS,KAAK,UAAU,YAAY;AAChD,YAAM,WAAW;AAGjB,UAAI,OAAO,SAAS,MAAM,MAAM,YAAY,CAAC,SAAS,MAAM,KACxD,OAAO,SAAS,cAAc,MAAM,UAAU;AAChD,gBAAQ;AAAA,UACN,wCAAwC,GAAG,KAAK,OAAO,MAAM,OAAO;AAAA,QACtE;AACA,eAAO;AAAA,MACT;AAEA,YAAM,aAAa,MAAM,QAAQ,SAAS,SAAS,CAAC,IAAI,SAAS,SAAS,IAAI,CAAC;AAC/E,YAAM,WAAW,MAAM,QAAQ,SAAS,OAAO,CAAC,IAAI,SAAS,OAAO,IAAI,CAAC;AACzE,YAAM,YAAY,MAAM,QAAQ,SAAS,QAAQ,CAAC,IAAI,SAAS,QAAQ,IAAI,CAAC;AAC5E,YAAM,kBAAkB,MAAM,QAAQ,SAAS,cAAc,CAAC,IAAI,SAAS,cAAc,IAAI,CAAC;AAE9F,YAAM,eAAe,WAAW,OAAO,CAAC,MAAM,iBAAiB,UAAU,CAAC,EAAE,OAAO;AACnF,YAAM,aAAa,SAAS,OAAO,CAAC,MAAM,gBAAgB,UAAU,CAAC,EAAE,OAAO;AAC9E,YAAM,cAAc,UAAU,OAAO,CAAC,MAAM,mBAAmB,UAAU,CAAC,EAAE,OAAO;AACnF,YAAM,oBAAoB,gBAAgB,OAAO,CAAC,MAAM,kBAAkB,UAAU,CAAC,EAAE,OAAO;AAE9F,YAAM,iBAAiB,WAAW,SAAS,aAAa;AACxD,YAAM,eAAe,SAAS,SAAS,WAAW;AAClD,YAAM,gBAAgB,UAAU,SAAS,YAAY;AACrD,YAAM,sBAAsB,gBAAgB,SAAS,kBAAkB;AAEvE,UAAI,iBAAiB,KAAK,eAAe,KAAK,gBAAgB,KAAK,sBAAsB,GAAG;AAC1F,YAAI;AAAA,UACF,GAAG,GAAG,aAAa,cAAc,uBAAuB,YAAY,qBAAqB,aAAa,uBAAuB,mBAAmB,uCAAkC,aAAa,MAAM,eAAe,WAAW,MAAM,aAAa,YAAY,MAAM,eAAe,kBAAkB,MAAM;AAAA,QAC7S;AAAA,MACF;AAGA,YAAM,YAAY,gBAAgB,UAAU;AAAA,QAC1C,GAAG;AAAA,QACH,SAAS;AAAA,QACT,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,cAAc;AAAA,MAChB,CAAC;AAED,UAAI,CAAC,UAAU,SAAS;AACtB,gBAAQ;AAAA,UACN,wCAAwC,GAAG,KAAK,OAAO,MAAM,OAAO;AAAA,QACtE;AACA,eAAO;AAAA,MACT;AAEA,aAAO,UAAU;AAAA,IACnB,SAAS,KAAK;AACZ,YAAM,MAAM,SAAS,KAAK,UAAU,YAAY;AAChD,cAAQ;AAAA,QACN,qCAAqC,GAAG,KAAM,IAAc,OAAO;AAAA,MACrE;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,iBAAiB,KAAa,UAAuB,oBAAI,IAAI,GAAa;AAChF,UAAM,UAAU,aAAa,GAAG;AAChC,QAAI,QAAQ,IAAI,OAAO,EAAG,QAAO,CAAC;AAClC,YAAQ,IAAI,OAAO;AAEnB,UAAM,QAAkB,CAAC;AAEzB,eAAW,SAAS,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,GAAG;AAC7D,UAAI,MAAM,eAAe,EAAG;AAE5B,YAAM,WAAW,KAAK,KAAK,MAAM,IAAI;AACrC,UAAI,MAAM,YAAY,GAAG;AAEvB,YAAI,MAAM,SAAS,sBAAuB;AAC1C,cAAM,KAAK,GAAG,KAAK,iBAAiB,UAAU,OAAO,CAAC;AAAA,MACxD,WACE,MAAM,KAAK,SAAS,OAAO,KAC3B,MAAM,SAAS,qBACf,MAAM,SAAS,sBACf,MAAM,SAAS,uBACf,MAAM,SAAS,4BACf;AACA,cAAM,KAAK,QAAQ;AAAA,MACrB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
atomicWrite
|
|
4
|
-
|
|
5
|
-
} from "./chunk-6EKERTJ7.js";
|
|
3
|
+
atomicWrite
|
|
4
|
+
} from "./chunk-RFEXMBTG.js";
|
|
6
5
|
import {
|
|
7
6
|
JsonIndexReader
|
|
8
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-CDGQGCVN.js";
|
|
8
|
+
import {
|
|
9
|
+
createLogger
|
|
10
|
+
} from "./chunk-5DSKZJNN.js";
|
|
9
11
|
|
|
10
12
|
// src/adapters/storage/sqlite-storage-adapter.ts
|
|
11
13
|
import initSqlJs from "sql.js";
|
|
@@ -888,15 +890,23 @@ function makeGlobMatcher(patterns) {
|
|
|
888
890
|
var SymbolGraph = class {
|
|
889
891
|
nodes = /* @__PURE__ */ new Map();
|
|
890
892
|
nodesByFileAndName = /* @__PURE__ */ new Map();
|
|
893
|
+
nodesByNameAndKind = /* @__PURE__ */ new Map();
|
|
891
894
|
forwardEdges = /* @__PURE__ */ new Map();
|
|
892
895
|
reverseEdges = /* @__PURE__ */ new Map();
|
|
893
896
|
edgeSet = /* @__PURE__ */ new Set();
|
|
894
897
|
addNode(node) {
|
|
895
898
|
this.nodes.set(node.symbolId, node);
|
|
896
|
-
const
|
|
899
|
+
const parts = node.symbolId.split("::");
|
|
900
|
+
const [p0, p1] = parts;
|
|
897
901
|
if (p0 !== void 0 && p1 !== void 0) {
|
|
898
902
|
this.nodesByFileAndName.set(`${p0}::${p1}`, node);
|
|
899
903
|
}
|
|
904
|
+
if (parts.length === 3) {
|
|
905
|
+
const nk = `${parts[1]}::${parts[2]}`;
|
|
906
|
+
const list = this.nodesByNameAndKind.get(nk);
|
|
907
|
+
if (list) list.push(node);
|
|
908
|
+
else this.nodesByNameAndKind.set(nk, [node]);
|
|
909
|
+
}
|
|
900
910
|
}
|
|
901
911
|
addEdge(edge) {
|
|
902
912
|
const resolvedFrom = this.resolveNodeId(edge.from);
|
|
@@ -917,7 +927,8 @@ var SymbolGraph = class {
|
|
|
917
927
|
}
|
|
918
928
|
resolveNodeId(id) {
|
|
919
929
|
if (this.nodes.has(id)) return id;
|
|
920
|
-
const
|
|
930
|
+
const segs = id.split("::");
|
|
931
|
+
const [p0, p1] = segs;
|
|
921
932
|
if (p0 !== void 0 && p1 !== void 0) {
|
|
922
933
|
const fuzzyKey = `${p0}::${p1}`;
|
|
923
934
|
const match = this.nodesByFileAndName.get(fuzzyKey);
|
|
@@ -929,18 +940,41 @@ var SymbolGraph = class {
|
|
|
929
940
|
if (altMatch) return altMatch.symbolId;
|
|
930
941
|
}
|
|
931
942
|
}
|
|
943
|
+
const nameKind = this.extractNameKind(segs);
|
|
944
|
+
if (nameKind) {
|
|
945
|
+
const candidates = this.nodesByNameAndKind.get(nameKind);
|
|
946
|
+
if (candidates && candidates.length === 1) return candidates[0].symbolId;
|
|
947
|
+
}
|
|
932
948
|
return id;
|
|
933
949
|
}
|
|
934
950
|
resolveNodeFuzzy(id) {
|
|
935
|
-
const
|
|
951
|
+
const segs = id.split("::");
|
|
952
|
+
const [p0, p1] = segs;
|
|
936
953
|
if (p0 !== void 0 && p1 !== void 0) {
|
|
937
954
|
const match = this.nodesByFileAndName.get(`${p0}::${p1}`);
|
|
938
955
|
if (match) return match;
|
|
939
956
|
const jsToTs = p0.replace(/\.js$/, ".ts");
|
|
940
957
|
if (jsToTs !== p0) {
|
|
941
|
-
|
|
958
|
+
const tsMatch = this.nodesByFileAndName.get(`${jsToTs}::${p1}`);
|
|
959
|
+
if (tsMatch) return tsMatch;
|
|
942
960
|
}
|
|
943
961
|
}
|
|
962
|
+
const nameKind = this.extractNameKind(segs);
|
|
963
|
+
if (nameKind) {
|
|
964
|
+
const candidates = this.nodesByNameAndKind.get(nameKind);
|
|
965
|
+
if (candidates && candidates.length === 1) return candidates[0];
|
|
966
|
+
}
|
|
967
|
+
return void 0;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Extracts a `name::kind` key from the segments of a reference ID, supporting:
|
|
971
|
+
* - 2-part: `Bar::class` → `Bar::class`
|
|
972
|
+
* - 3-part: `pkg.Bar::helper::method` → `helper::method`
|
|
973
|
+
* Returns undefined for 1-part IDs (no kind information available).
|
|
974
|
+
*/
|
|
975
|
+
extractNameKind(segs) {
|
|
976
|
+
if (segs.length === 3) return `${segs[1]}::${segs[2]}`;
|
|
977
|
+
if (segs.length === 2) return `${segs[0]}::${segs[1]}`;
|
|
944
978
|
return void 0;
|
|
945
979
|
}
|
|
946
980
|
getForwardEdges(symbolId) {
|
|
@@ -1762,4 +1796,4 @@ export {
|
|
|
1762
1796
|
watchSnapshotMinFileChanges,
|
|
1763
1797
|
makeGlobMatcher
|
|
1764
1798
|
};
|
|
1765
|
-
//# sourceMappingURL=chunk-
|
|
1799
|
+
//# sourceMappingURL=chunk-HZ4UGA6J.js.map
|