@ctxo/cli 0.0.1 → 0.7.0-alpha.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alper Hankendi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,303 @@
1
+ <div align="center">
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@ctxo/cli.svg)](https://www.npmjs.com/package/@ctxo/cli)
4
+ [![CI](https://github.com/alperhankendi/Ctxo/actions/workflows/ci.yml/badge.svg)](https://github.com/alperhankendi/Ctxo/actions/workflows/ci.yml)
5
+ [![Release](https://github.com/alperhankendi/Ctxo/actions/workflows/release.yml/badge.svg)](https://github.com/alperhankendi/Ctxo/actions/workflows/release.yml)
6
+
7
+ <picture>
8
+ <source media="(prefers-color-scheme: dark)" srcset="docs/img/hero-svg.svg">
9
+ <source media="(prefers-color-scheme: light)" srcset="docs/img/hero-svg.svg">
10
+ <img alt="Ctxo — Code intelligence for AI agents" src="docs/img/hero-svg.svg" width="100%">
11
+ </picture>
12
+
13
+ </div>
14
+
15
+ ***
16
+
17
+ ### The Problem
18
+
19
+ AI coding assistants like Copilot, Claude Code, and Cursor rely on generic tools `grep`, `find`, file reads to understand your codebase. On brownfield projects with thousands of files, this brute-force exploration creates a chain of problems:
20
+
21
+ * **Context window saturation** The agent fills its window reading files one by one, leaving little room for actual reasoning
22
+ * **Partial context hallucination** It sees a function but misses its dependencies, leading to wrong assumptions and broken suggestions
23
+ * **Lost-in-the-middle** Critical information buried deep in a long context gets ignored by the model
24
+ * **Context poisoning** Irrelevant code pulled in during exploration biases the model's output
25
+ * **Iteration overhead** Understanding one symbol takes 10-20 tool calls, each adding more noise to the context
26
+ * **Stale reasoning** After too many iterations, the agent contradicts its own earlier assumptions
27
+
28
+ The result: more tokens burned, slower responses, higher cost, and lower quality output.
29
+
30
+ ### The Solution
31
+
32
+ Ctxo is an **MCP server** that **enhances** your existing AI tools with dependency-aware, history-enriched code intelligence. Instead of hundreds of `grep` and `read_file` calls, your agent gets the full picture symbol graphs, blast radius, git intent, and risk scores in a **single MCP call**.
33
+
34
+ * **Clean context** Only relevant symbols and their transitive dependencies, nothing more
35
+ * **Fewer iterations** One call replaces an entire exploration cycle
36
+ * **Higher quality** The agent reasons over structured, complete context instead of fragmented file reads
37
+
38
+ > A senior developer takes \~10 minutes to gather context across files. Ctxo delivers that same context in **under 500ms**.
39
+
40
+ ## Quick Start
41
+
42
+ Install the CLI, then run the setup wizard:
43
+
44
+ ```Shell
45
+ npm i -g @ctxo/cli
46
+ ctxo init
47
+ ```
48
+
49
+ That's it. The interactive wizard detects your AI tools, registers the ctxo MCP server in the correct config file (`.mcp.json`, `.vscode/mcp.json`, etc.), generates usage rules so your assistant knows when to call each tool, and prompts to install language plugins it detects in your project (pass `--no-install` to skip).
50
+
51
+ ### Language Plugins
52
+
53
+ As of v0.7, Ctxo discovers language support from npm packages listed in your project's `package.json`. Three official plugins ship today:
54
+
55
+ ```Shell
56
+ npm i -D @ctxo/lang-typescript @ctxo/lang-go @ctxo/lang-csharp
57
+ ```
58
+
59
+ Or install one at a time with the CLI shortcut: `ctxo install typescript` (supports `--yes`, `--global`, `--dry-run`, `--pm`, `--version`). Any package named `@ctxo/lang-*` or `ctxo-lang-*` in your dependencies is auto-loaded.
60
+
61
+ <details>
62
+ <summary>Manual MCP config (if not using <code>ctxo init</code>)</summary>
63
+
64
+ **Claude Code / Cursor / Windsurf / Augment / Antigravity** — `.mcp.json`:
65
+
66
+ ```JSON
67
+ { "mcpServers": { "ctxo": { "command": "npx", "args": ["-y", "@ctxo/cli"] } } }
68
+ ```
69
+
70
+ **VS Code (Copilot)** — `.vscode/mcp.json`:
71
+
72
+ ```JSON
73
+ { "servers": { "ctxo": { "type": "stdio", "command": "npx", "args": ["-y", "@ctxo/cli"] } } }
74
+ ```
75
+
76
+ **Amazon Q** — `.amazonq/mcp.json`:
77
+
78
+ ```JSON
79
+ { "mcpServers": { "ctxo": { "command": "npx", "args": ["-y", "@ctxo/cli"] } } }
80
+ ```
81
+
82
+ **Zed** — `settings.json`:
83
+
84
+ ```JSON
85
+ { "context_servers": { "ctxo": { "command": { "path": "npx", "args": ["-y", "@ctxo/cli"] } } } }
86
+ ```
87
+
88
+ </details>
89
+
90
+ ## 14 Tools
91
+
92
+ | Tool | What it does |
93
+ | --------------------------- | ---------------------------------------------------------------------------------------- |
94
+ | `get_logic_slice` | Symbol + transitive dependencies (L1-L4 progressive detail) |
95
+ | `get_blast_radius` | What breaks if this changes (3-tier: confirmed/likely/potential) |
96
+ | `get_architectural_overlay` | Project layer map (Domain/Infrastructure/Adapter) |
97
+ | `get_why_context` | Git commit intent + anti-pattern warnings (reverts, rollbacks) |
98
+ | `get_change_intelligence` | Complexity x churn composite score |
99
+ | `find_dead_code` | Unreachable symbols, unused exports, scaffolding markers |
100
+ | `get_context_for_task` | Task-optimized context (fix/extend/refactor/understand) |
101
+ | `get_ranked_context` | Two-phase BM25 search (camelCase-aware, fuzzy correction) + PageRank within token budget |
102
+ | `search_symbols` | Symbol name/regex search across index (`mode: 'fts'` for BM25) |
103
+ | `get_changed_symbols` | Symbols in recently changed files (git diff) |
104
+ | `find_importers` | Reverse dependency lookup ("who uses this?") |
105
+ | `get_class_hierarchy` | Class inheritance tree (ancestors + descendants) |
106
+ | `get_symbol_importance` | PageRank centrality ranking |
107
+ | `get_pr_impact` | Full PR risk assessment in a single call |
108
+
109
+ ## Tool Selection Guide
110
+
111
+ ```
112
+ Reviewing a PR? → get_pr_impact
113
+ About to modify code? → get_blast_radius → get_why_context
114
+ Understanding a symbol? → get_context_for_task(taskType: "understand")
115
+ Fixing a bug? → get_context_for_task(taskType: "fix")
116
+ Refactoring? → get_context_for_task(taskType: "refactor")
117
+ Don't know the name? → search_symbols or get_ranked_context
118
+ Finding unused code? → find_dead_code
119
+ Safe to delete? → find_importers
120
+ Onboarding? → get_architectural_overlay → get_symbol_importance
121
+ ```
122
+
123
+ ## CLI Commands
124
+
125
+ ```Shell
126
+ # Setup
127
+ ctxo init # Interactive setup (detects languages, prompts to install plugins, writes AI tool rules, git hooks)
128
+ ctxo init --tools claude-code,cursor -y # Non-interactive setup
129
+ ctxo init --no-install # Skip plugin install prompt
130
+ ctxo init --rules # Regenerate AI tool rules only
131
+ ctxo init --dry-run # Preview what would be created
132
+
133
+ # Plugins (v0.7+)
134
+ ctxo install # Install plugins for all detected languages
135
+ ctxo install typescript go csharp # Install specific plugins
136
+ ctxo install typescript --yes --pm pnpm # CI-friendly, pick package manager
137
+ ctxo install typescript --global --version 0.7.0 # Pin version, install globally
138
+
139
+ # Indexing
140
+ ctxo index # Build full codebase index
141
+ ctxo index --install-missing # Auto-install any missing plugins first
142
+ ctxo index --check # CI gate: fail if index stale
143
+ ctxo index --skip-history # Fast re-index without git history
144
+ ctxo watch # File watcher for incremental re-index
145
+ ctxo sync # Rebuild SQLite cache from committed JSON
146
+
147
+ # Diagnostics
148
+ ctxo status # Show index manifest
149
+ ctxo doctor # Health check all subsystems (--json, --quiet)
150
+ ctxo doctor --fix # Dependency-ordered auto-remediation (--dry-run, --yes)
151
+ ctxo stats # Show usage statistics (--json, --days N, --clear)
152
+ ctxo --version # Print CLI version (--verbose, --json)
153
+ ctxo version # Verbose version subcommand (CLI + plugins + runtime)
154
+ ```
155
+
156
+ **Example output:**
157
+
158
+ ```
159
+ ctxo stats
160
+
161
+ Usage Summary (all time)
162
+ ────────────────────────────────────────
163
+ Total tool calls: 30
164
+ Total tokens served: 26.0K
165
+
166
+ Top Tools
167
+ ────────────────────────────────────────
168
+ get_logic_slice 14 calls avg 352 tokens
169
+ get_blast_radius 3 calls avg 1,279 tokens
170
+ get_ranked_context 3 calls avg 898 tokens
171
+ find_importers 2 calls avg 1,342 tokens
172
+ get_context_for_task 2 calls avg 740 tokens
173
+
174
+ Top Queried Symbols
175
+ ────────────────────────────────────────
176
+ SymbolNode 15 queries
177
+ LogicSliceQuery 4 queries
178
+ SqliteStorageAdapter 2 queries
179
+
180
+ Detail Level Distribution
181
+ ────────────────────────────────────────
182
+ L1: ███░░░░░░░ 25%
183
+ L2: ███░░░░░░░ 25%
184
+ L3: ███░░░░░░░ 25%
185
+ L4: ███░░░░░░░ 25%
186
+ ```
187
+
188
+ ## CI Integration
189
+
190
+ Keep the committed `.ctxo/index/` in sync with the source tree by failing PRs when the index is stale. Drop-in examples:
191
+
192
+ - GitHub Actions: [examples/github-actions/ctxo-check.yml](examples/github-actions/ctxo-check.yml)
193
+ - GitLab CI: [examples/gitlab-ci/.gitlab-ci.yml](examples/gitlab-ci/.gitlab-ci.yml)
194
+
195
+ Both run `ctxo index --check` on every PR/MR. If the index is stale, the job fails and the author runs `ctxo index` locally, then commits the refreshed index. Combined with the `post-commit` hook from `ctxo init`, this keeps the index current without any webhook infrastructure.
196
+
197
+ ## Features
198
+
199
+ **Response Envelope** All responses include `_meta` with item counts, truncation info, and drill-in hints. Large results auto-truncated at 8KB (configurable via `CTXO_RESPONSE_LIMIT`).
200
+
201
+ **Intent Filtering** 4 tools accept `intent` parameter for keyword-based result filtering. `get_blast_radius(symbolId, intent: "test")` returns only test-related impacts.
202
+
203
+ **Tool Annotations** All tools declare `readOnlyHint: true`, `idempotentHint: true`, `openWorldHint: false` for safe auto-approval in agent frameworks.
204
+
205
+ **Privacy Masking** AWS keys, GCP service accounts, Azure connection strings, JWTs, private IPs, env secrets automatically redacted. Extensible via `.ctxo/masking.json`.
206
+
207
+ **Interactive Graph** `ctxo visualize` generates a self-contained HTML with vis.js dependency graph. ForceAtlas2 layout, PageRank-based sizing, layer coloring, blast radius on click, git history per symbol, dead code and anti-pattern filters, dark/light theme.
208
+
209
+ **Debug Mode** `DEBUG=ctxo:*` for all debug output, or `DEBUG=ctxo:git,ctxo:storage` for specific namespaces.
210
+
211
+ **Per-tool savings vs manual approach:**
212
+
213
+ ```
214
+ Manual Tokens Ctxo Tokens Savings
215
+ get_logic_slice ████████ 1,950 █ 150 92%
216
+ get_blast_radius ███ 800 ██ 600 25%
217
+ get_overlay ████████████ 25K██ 500 98%
218
+ get_why_context █ 200 █ 200 0%
219
+ get_change_intelligence ████████ 2,100 ▏ 50 98%
220
+ find_dead_code █████████ 5,000 ████ 2,000 60%
221
+ ────────────────────────────────────────────────────────────
222
+ TOTAL 35,050 tokens 3,500 tokens 90%
223
+ 329+ calls 6 calls 98%
224
+ ```
225
+
226
+ ## Agentic AI Usage
227
+
228
+ **Claude Agent SDK:**
229
+
230
+ ```TypeScript
231
+ import { query } from "@anthropic-ai/claude-agent-sdk";
232
+
233
+ for await (const message of query({
234
+ prompt: "Analyze the blast radius of AuthService",
235
+ options: {
236
+ mcpServers: { ctxo: { command: "npx", args: ["-y", "@ctxo/cli"] } },
237
+ allowedTools: ["mcp__ctxo__*"]
238
+ }
239
+ })) { /* ... */ }
240
+ ```
241
+
242
+ **OpenAI Agents SDK:**
243
+
244
+ ```Python
245
+ from agents import Agent, Runner
246
+ from agents.mcp import MCPServerStdio
247
+
248
+ async with MCPServerStdio(params={"command": "npx", "args": ["@ctxo/cli"]}) as ctxo:
249
+ agent = Agent(name="Reviewer", mcp_servers=[ctxo])
250
+ result = await Runner.run(agent, "Review the PR impact")
251
+ ```
252
+
253
+ See [Agentic AI Integration Guide](docs/agentic-ai-integration.md) for LangChain, raw MCP client, and CI/CD examples.
254
+
255
+ ## Multi-Language Support
256
+
257
+ Language support ships as standalone plugins. Install only what your repo needs.
258
+
259
+ | Language | Plugin Package | Parser | Tier | Features |
260
+ | --------------------- | --------------------- | ----------- | ------ | ---------------------------------------------------------------- |
261
+ | TypeScript/JavaScript | `@ctxo/lang-typescript` | ts-morph | Full | Type-aware resolution, cross-file imports, `this.method()` calls |
262
+ | Go | `@ctxo/lang-go` | tree-sitter | Syntax | Structs, interfaces, functions, methods, import edges |
263
+ | C# | `@ctxo/lang-csharp` | tree-sitter | Syntax | Classes, interfaces, methods, enums, namespace qualification |
264
+
265
+ Third-party plugins are supported via the `@ctxo/plugin-api` protocol (v1). Any package named `@ctxo/lang-*` or `ctxo-lang-*` declared in your project dependencies is auto-discovered.
266
+
267
+ ## Index Visualizer
268
+
269
+ Ctxo ships with an interactive visualizer that renders your codebase index as a dependency graph. Explore symbols, edges, layers, and PageRank scores visually deployed automatically to GitHub Pages on every push.
270
+
271
+ ![1.00](docs/img/ui.png)
272
+
273
+ [Open Live Visualizer](https://alperhankendi.github.io/Ctxo/ctxo-visualizer.html)
274
+
275
+ ## How It Works
276
+
277
+ <img src="docs/img/mcp-server.png" alt="Ctxo MCP Server Architecture" width="400">
278
+
279
+ Ctxo builds a **committed JSON index** (`.ctxo/index/`) that captures symbols, dependency edges, git history, and co-change data. The MCP server reads this index to answer queries no runtime parsing, no external services.
280
+
281
+ The codebase is a pnpm monorepo with 5 packages: `@ctxo/cli` (this package), `@ctxo/plugin-api` (plugin protocol v1), and three official language plugins (`@ctxo/lang-typescript`, `@ctxo/lang-go`, `@ctxo/lang-csharp`).
282
+
283
+ ```
284
+ .ctxo/
285
+ index/ ← committed (per-file JSON, reviewable in PRs)
286
+ .cache/ ← gitignored (local SQLite, auto-rebuilt)
287
+ config.yaml ← committed (team settings)
288
+ masking.json ← committed (custom masking patterns)
289
+ ```
290
+
291
+ ## Links
292
+
293
+ * [npm](https://www.npmjs.com/package/@ctxo/cli)
294
+ * [Changelog](CHANGELOG.md)
295
+ * [LLM Reference](llms-full.txt)
296
+ * [MCP Validation Runbook](docs/runbook/mcp-validation/mcp-validation.md)
297
+ * [CLI Validation Runbook](docs/runbook/cli-validation/cli-validation.md)
298
+ * [Architecture](docs/artifacts/architecture.md)
299
+ * [PRD](docs/artifacts/prd.md)
300
+
301
+ ## License
302
+
303
+ MIT
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ export {
10
+ __require
11
+ };
12
+ //# sourceMappingURL=chunk-7OCVIDC7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/adapters/storage/json-index-reader.ts
4
+ import { readFileSync, readdirSync, existsSync, realpathSync } from "fs";
5
+ import { join, relative } from "path";
6
+
7
+ // src/core/types.ts
8
+ import { z } from "zod";
9
+ var SYMBOL_KINDS = [
10
+ "function",
11
+ "class",
12
+ "interface",
13
+ "method",
14
+ "variable",
15
+ "type"
16
+ ];
17
+ var SymbolKindSchema = z.enum(SYMBOL_KINDS);
18
+ var EDGE_KINDS = [
19
+ "imports",
20
+ "calls",
21
+ "extends",
22
+ "implements",
23
+ "uses"
24
+ ];
25
+ var EdgeKindSchema = z.enum(EDGE_KINDS);
26
+ var DetailLevelSchema = z.union([
27
+ z.literal(1),
28
+ z.literal(2),
29
+ z.literal(3),
30
+ z.literal(4)
31
+ ]);
32
+ var SymbolIdSchema = z.string().min(1).refine(
33
+ (value) => {
34
+ const parts = value.split("::");
35
+ if (parts.length !== 3) return false;
36
+ const [file, name, kind] = parts;
37
+ if (!file || !name || !kind) return false;
38
+ return SymbolKindSchema.safeParse(kind).success;
39
+ },
40
+ { message: 'Symbol ID must match format "<file>::<name>::<kind>"' }
41
+ );
42
+ var SymbolNodeSchema = z.object({
43
+ symbolId: SymbolIdSchema,
44
+ name: z.string().min(1),
45
+ kind: SymbolKindSchema,
46
+ startLine: z.number().int().nonnegative(),
47
+ endLine: z.number().int().nonnegative(),
48
+ startOffset: z.number().int().nonnegative().optional(),
49
+ endOffset: z.number().int().nonnegative().optional()
50
+ }).refine((node) => node.endLine >= node.startLine, {
51
+ message: "endLine must be >= startLine"
52
+ });
53
+ var GraphEdgeSchema = z.object({
54
+ from: SymbolIdSchema,
55
+ to: SymbolIdSchema,
56
+ kind: EdgeKindSchema,
57
+ typeOnly: z.boolean().optional()
58
+ });
59
+ var CommitIntentSchema = z.object({
60
+ hash: z.string().min(1),
61
+ message: z.string(),
62
+ date: z.string().min(1),
63
+ kind: z.literal("commit")
64
+ });
65
+ var AntiPatternSchema = z.object({
66
+ hash: z.string().min(1),
67
+ message: z.string(),
68
+ date: z.string().min(1)
69
+ });
70
+ var ComplexityMetricsSchema = z.object({
71
+ symbolId: z.string().min(1),
72
+ cyclomatic: z.number().nonnegative()
73
+ });
74
+ var FileIndexSchema = z.object({
75
+ file: z.string().min(1),
76
+ lastModified: z.number().nonnegative(),
77
+ contentHash: z.string().optional(),
78
+ symbols: z.array(SymbolNodeSchema),
79
+ edges: z.array(GraphEdgeSchema),
80
+ complexity: z.array(ComplexityMetricsSchema).optional(),
81
+ intent: z.array(CommitIntentSchema),
82
+ antiPatterns: z.array(AntiPatternSchema)
83
+ });
84
+
85
+ // src/adapters/storage/json-index-reader.ts
86
+ var JsonIndexReader = class {
87
+ indexDir;
88
+ constructor(ctxoRoot) {
89
+ this.indexDir = join(ctxoRoot, "index");
90
+ }
91
+ readAll() {
92
+ if (!existsSync(this.indexDir)) {
93
+ return [];
94
+ }
95
+ const jsonFiles = this.collectJsonFiles(this.indexDir);
96
+ const results = [];
97
+ for (const filePath of jsonFiles) {
98
+ const parsed = this.readSingle(filePath);
99
+ if (parsed) {
100
+ results.push(parsed);
101
+ }
102
+ }
103
+ return results;
104
+ }
105
+ readSingle(absolutePath) {
106
+ try {
107
+ const raw = readFileSync(absolutePath, "utf-8");
108
+ const data = JSON.parse(raw);
109
+ const result = FileIndexSchema.safeParse(data);
110
+ if (!result.success) {
111
+ const rel = relative(this.indexDir, absolutePath);
112
+ console.error(
113
+ `[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`
114
+ );
115
+ return void 0;
116
+ }
117
+ return result.data;
118
+ } catch (err) {
119
+ const rel = relative(this.indexDir, absolutePath);
120
+ console.error(
121
+ `[ctxo:json-reader] Failed to read ${rel}: ${err.message}`
122
+ );
123
+ return void 0;
124
+ }
125
+ }
126
+ collectJsonFiles(dir, visited = /* @__PURE__ */ new Set()) {
127
+ const realDir = realpathSync(dir);
128
+ if (visited.has(realDir)) return [];
129
+ visited.add(realDir);
130
+ const files = [];
131
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
132
+ if (entry.isSymbolicLink()) continue;
133
+ const fullPath = join(dir, entry.name);
134
+ if (entry.isDirectory()) {
135
+ files.push(...this.collectJsonFiles(fullPath, visited));
136
+ } else if (entry.name.endsWith(".json") && entry.name !== "co-changes.json") {
137
+ files.push(fullPath);
138
+ }
139
+ }
140
+ return files;
141
+ }
142
+ };
143
+
144
+ export {
145
+ DetailLevelSchema,
146
+ JsonIndexReader
147
+ };
148
+ //# sourceMappingURL=chunk-I4TUA7CD.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 { FileIndexSchema, type FileIndex } from '../../core/types.js';\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 const rel = relative(this.indexDir, absolutePath);\n console.error(\n `[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`,\n );\n return undefined;\n }\n\n return result.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 files.push(...this.collectJsonFiles(fullPath, visited));\n } else if (entry.name.endsWith('.json') && entry.name !== 'co-changes.json') {\n files.push(fullPath);\n }\n }\n\n return files;\n }\n}\n","import { z } from 'zod';\r\n\r\n// ── Symbol Kinds ────────────────────────────────────────────────\r\n\r\nexport const SYMBOL_KINDS = [\r\n 'function',\r\n 'class',\r\n 'interface',\r\n 'method',\r\n 'variable',\r\n 'type',\r\n] as const;\r\n\r\nexport const SymbolKindSchema = z.enum(SYMBOL_KINDS);\r\nexport type SymbolKind = z.infer<typeof SymbolKindSchema>;\r\n\r\n// ── Edge Kinds ──────────────────────────────────────────────────\r\n\r\nexport const EDGE_KINDS = [\r\n 'imports',\r\n 'calls',\r\n 'extends',\r\n 'implements',\r\n 'uses',\r\n] as const;\r\n\r\nexport const EdgeKindSchema = z.enum(EDGE_KINDS);\r\nexport type EdgeKind = z.infer<typeof EdgeKindSchema>;\r\n\r\n// ── Detail Levels ───────────────────────────────────────────────\r\n\r\nexport const DETAIL_LEVELS = [1, 2, 3, 4] as const;\r\n\r\nexport const DetailLevelSchema = z.union([\r\n z.literal(1),\r\n z.literal(2),\r\n z.literal(3),\r\n z.literal(4),\r\n]);\r\nexport type DetailLevel = z.infer<typeof DetailLevelSchema>;\r\n\r\n// ── Symbol ID ───────────────────────────────────────────────────\r\n\r\n/**\r\n * Format: \"<relativeFile>::<name>::<kind>\"\r\n * Example: \"src/foo.ts::myFn::function\"\r\n */\r\nexport const SymbolIdSchema = z\r\n .string()\r\n .min(1)\r\n .refine(\r\n (value) => {\r\n const parts = value.split('::');\r\n if (parts.length !== 3) return false;\r\n const [file, name, kind] = parts;\r\n if (!file || !name || !kind) return false;\r\n return SymbolKindSchema.safeParse(kind).success;\r\n },\r\n { message: 'Symbol ID must match format \"<file>::<name>::<kind>\"' },\r\n );\r\nexport type SymbolId = z.infer<typeof SymbolIdSchema>;\r\n\r\n// ── Symbol Node ─────────────────────────────────────────────────\r\n\r\nexport const SymbolNodeSchema = z\r\n .object({\r\n symbolId: SymbolIdSchema,\r\n name: z.string().min(1),\r\n kind: SymbolKindSchema,\r\n startLine: z.number().int().nonnegative(),\r\n endLine: z.number().int().nonnegative(),\r\n startOffset: z.number().int().nonnegative().optional(),\r\n endOffset: z.number().int().nonnegative().optional(),\r\n })\r\n .refine((node) => node.endLine >= node.startLine, {\r\n message: 'endLine must be >= startLine',\r\n });\r\nexport type SymbolNode = z.infer<typeof SymbolNodeSchema>;\r\n\r\n// ── Graph Edge ──────────────────────────────────────────────────\r\n\r\nexport const GraphEdgeSchema = z.object({\r\n from: SymbolIdSchema,\r\n to: SymbolIdSchema,\r\n kind: EdgeKindSchema,\r\n typeOnly: z.boolean().optional(),\r\n});\r\nexport type GraphEdge = z.infer<typeof GraphEdgeSchema>;\r\n\r\n// ── Commit Intent ───────────────────────────────────────────────\r\n\r\nexport const CommitIntentSchema = z.object({\r\n hash: z.string().min(1),\r\n message: z.string(),\r\n date: z.string().min(1),\r\n kind: z.literal('commit'),\r\n});\r\nexport type CommitIntent = z.infer<typeof CommitIntentSchema>;\r\n\r\n// ── Anti-Pattern ────────────────────────────────────────────────\r\n\r\nexport const AntiPatternSchema = z.object({\r\n hash: z.string().min(1),\r\n message: z.string(),\r\n date: z.string().min(1),\r\n});\r\nexport type AntiPattern = z.infer<typeof AntiPatternSchema>;\r\n\r\n// ── File Index ──────────────────────────────────────────────────\r\n\r\nexport const ComplexityMetricsSchema = z.object({\r\n symbolId: z.string().min(1),\r\n cyclomatic: z.number().nonnegative(),\r\n});\r\n\r\nexport const FileIndexSchema = z.object({\r\n file: z.string().min(1),\r\n lastModified: z.number().nonnegative(),\r\n contentHash: z.string().optional(),\r\n symbols: z.array(SymbolNodeSchema),\r\n edges: z.array(GraphEdgeSchema),\r\n complexity: z.array(ComplexityMetricsSchema).optional(),\r\n intent: z.array(CommitIntentSchema),\r\n antiPatterns: z.array(AntiPatternSchema),\r\n});\r\nexport type FileIndex = z.infer<typeof FileIndexSchema>;\r\n\r\n// ── Logic-Slice Result ──────────────────────────────────────────\r\n\r\nexport interface LogicSliceResult {\r\n readonly root: SymbolNode;\r\n readonly dependencies: readonly SymbolNode[];\r\n readonly edges: readonly GraphEdge[];\r\n}\r\n\r\n// ── Formatted Slice ─────────────────────────────────────────────\r\n\r\nexport interface TruncationInfo {\r\n readonly truncated: true;\r\n readonly reason: 'token_budget_exceeded';\r\n}\r\n\r\nexport interface FormattedSlice {\r\n readonly root: SymbolNode;\r\n readonly dependencies: readonly SymbolNode[];\r\n readonly edges: readonly GraphEdge[];\r\n readonly level: DetailLevel;\r\n readonly levelDescription?: string;\r\n readonly truncation?: TruncationInfo;\r\n}\r\n\r\n// ── Complexity & Churn ──────────────────────────────────────────\r\n\r\nexport interface ComplexityMetrics {\r\n readonly symbolId: string;\r\n readonly cyclomatic: number;\r\n}\r\n\r\nexport interface ChurnData {\r\n readonly filePath: string;\r\n readonly commitCount: number;\r\n}\r\n\r\nexport const SCORE_BANDS = ['low', 'medium', 'high'] as const;\r\nexport type ScoreBand = (typeof SCORE_BANDS)[number];\r\n\r\nexport interface ChangeIntelligenceScore {\r\n readonly symbolId: string;\r\n readonly complexity: number;\r\n readonly churn: number;\r\n readonly composite: number;\r\n readonly band: ScoreBand;\r\n}\r\n\r\n// ── Why-Context Result ──────────────────────────────────────────\r\n\r\nexport interface WhyContextResult {\r\n readonly commitHistory: readonly CommitIntent[];\r\n readonly antiPatternWarnings: readonly AntiPattern[];\r\n readonly changeIntelligence?: ChangeIntelligenceScore;\r\n}\r\n\r\n// ── Co-Change Analysis ─────────────────────────────────────────\r\n\r\nexport interface CoChangeEntry {\r\n readonly file1: string;\r\n readonly file2: string;\r\n readonly sharedCommits: number;\r\n readonly frequency: number;\r\n}\r\n\r\nexport interface CoChangeMatrix {\r\n readonly version: 1;\r\n readonly timestamp: number;\r\n readonly entries: readonly CoChangeEntry[];\r\n}\r\n\r\n// ── Commit Record (from git adapter) ────────────────────────────\r\n\r\nexport interface CommitRecord {\r\n readonly hash: string;\r\n readonly message: string;\r\n readonly date: string;\r\n readonly author: string;\r\n}\r\n\r\n// ── Blame Line (from git adapter) ───────────────────────────────\r\n\r\nexport interface BlameLine {\r\n readonly hash: string;\r\n readonly lineNumber: number;\r\n readonly author: string;\r\n readonly date: string;\r\n}\r\n\r\n// ── Search Types ───────────────────────────────────────────────\r\n\r\nexport interface SearchResult {\r\n symbolId: string;\r\n name: string;\r\n kind: string;\r\n filePath: string;\r\n /** BM25 relevance score (higher = more relevant) */\r\n relevanceScore: number;\r\n /** PageRank importance score (0-1) */\r\n importanceScore: number;\r\n /** Final combined score after all boosts */\r\n combinedScore: number;\r\n}\r\n\r\nexport interface SearchMetrics {\r\n porterHits: number;\r\n trigramHits: number;\r\n phase2Activated: boolean;\r\n fuzzyApplied: boolean;\r\n latencyMs: number;\r\n}\r\n\r\nexport interface FuzzyCorrection {\r\n originalQuery: string;\r\n correctedQuery: string;\r\n corrections: Array<{\r\n original: string;\r\n corrected: string;\r\n distance: number;\r\n }>;\r\n}\r\n\r\nexport interface SearchResponse {\r\n query: string;\r\n results: SearchResult[];\r\n metrics: SearchMetrics;\r\n fuzzyCorrection?: FuzzyCorrection;\r\n}\r\n"],"mappings":"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","names":[]}