ctxo-mcp 0.2.0 → 0.3.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 +188 -0
- package/dist/chunk-JIDIH7DS.js +28 -0
- package/dist/{chunk-54ETLIQX.js → chunk-N6GPODUY.js} +8 -4
- package/dist/chunk-N6GPODUY.js.map +1 -0
- package/dist/{chunk-P7JUSY3I.js → chunk-XSHNN6PU.js} +167 -16
- package/dist/chunk-XSHNN6PU.js.map +1 -0
- package/dist/{cli-router-PIWHLS5F.js → cli-router-NRUGPICL.js} +808 -69
- package/dist/cli-router-NRUGPICL.js.map +1 -0
- package/dist/index.js +1643 -66
- package/dist/index.js.map +1 -1
- package/dist/json-index-reader-FCKSKA6R.js +9 -0
- package/dist/json-index-reader-FCKSKA6R.js.map +1 -0
- package/dist/{staleness-detector-5AN223FM.js → staleness-detector-VSDPTPX7.js} +2 -1
- package/dist/{staleness-detector-5AN223FM.js.map → staleness-detector-VSDPTPX7.js.map} +1 -1
- package/llms-full.txt +260 -0
- package/llms.txt +53 -0
- package/package.json +7 -2
- package/dist/chunk-54ETLIQX.js.map +0 -1
- package/dist/chunk-P7JUSY3I.js.map +0 -1
- package/dist/cli-router-PIWHLS5F.js.map +0 -1
- package/dist/json-index-reader-PNLPAS42.js +0 -8
- /package/dist/{json-index-reader-PNLPAS42.js.map → chunk-JIDIH7DS.js.map} +0 -0
package/README.md
CHANGED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Ctxo
|
|
2
|
+
|
|
3
|
+
**Code intelligence for AI agents — one call instead of hundreds.**
|
|
4
|
+
|
|
5
|
+
AI coding assistants waste context window reading files one by one, still missing dependencies. Ctxo gives them the full picture in a single MCP call: symbol graphs, blast radius, git intent, and risk scores.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Context per query set (full codebase investigation):
|
|
9
|
+
|
|
10
|
+
Manual ████████████████████████████████████████ 140,000 tokens
|
|
11
|
+
Ctxo █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2,900 tokens → 48x less
|
|
12
|
+
|
|
13
|
+
Tool calls per query set:
|
|
14
|
+
|
|
15
|
+
Manual ████████████████████████████████████████ 409+ calls
|
|
16
|
+
Ctxo █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 8 calls → 51x fewer
|
|
17
|
+
|
|
18
|
+
Context after 10 investigation rounds:
|
|
19
|
+
|
|
20
|
+
Manual ██████████████████████████████████████████ 140% — OOM at round 7
|
|
21
|
+
Ctxo █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2.9% — 97% free for coding
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```Shell
|
|
27
|
+
# 1. Index your codebase
|
|
28
|
+
npx ctxo-mcp index
|
|
29
|
+
|
|
30
|
+
# 2. Add to your IDE's MCP config
|
|
31
|
+
# (see IDE Setup below)
|
|
32
|
+
|
|
33
|
+
# 3. Start using — your AI assistant now has 14 code intelligence tools
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## IDE Setup
|
|
37
|
+
|
|
38
|
+
**Claude Code / Cursor / Windsurf / Cline** — `.mcp.json`:
|
|
39
|
+
|
|
40
|
+
```JSON
|
|
41
|
+
{ "mcpServers": { "ctxo": { "command": "npx", "args": ["-y", "ctxo-mcp"] } } }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**VS Code (Copilot)** — `.vscode/mcp.json`:
|
|
45
|
+
|
|
46
|
+
```JSON
|
|
47
|
+
{ "servers": { "ctxo": { "type": "stdio", "command": "npx", "args": ["-y", "ctxo-mcp"] } } }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Zed** — `settings.json`:
|
|
51
|
+
|
|
52
|
+
```JSON
|
|
53
|
+
{ "context_servers": { "ctxo": { "command": { "path": "npx", "args": ["-y", "ctxo-mcp"] } } } }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 14 Tools
|
|
57
|
+
|
|
58
|
+
| Tool | What it does |
|
|
59
|
+
| --------------------------- | ---------------------------------------------------------------- |
|
|
60
|
+
| `get_logic_slice` | Symbol + transitive dependencies (L1-L4 progressive detail) |
|
|
61
|
+
| `get_blast_radius` | What breaks if this changes (3-tier: confirmed/likely/potential) |
|
|
62
|
+
| `get_architectural_overlay` | Project layer map (Domain/Infrastructure/Adapter) |
|
|
63
|
+
| `get_why_context` | Git commit intent + anti-pattern warnings (reverts, rollbacks) |
|
|
64
|
+
| `get_change_intelligence` | Complexity x churn composite score |
|
|
65
|
+
| `find_dead_code` | Unreachable symbols, unused exports, scaffolding markers |
|
|
66
|
+
| `get_context_for_task` | Task-optimized context (fix/extend/refactor/understand) |
|
|
67
|
+
| `get_ranked_context` | BM25 + PageRank search within token budget |
|
|
68
|
+
| `search_symbols` | Symbol name/regex search across index |
|
|
69
|
+
| `get_changed_symbols` | Symbols in recently changed files (git diff) |
|
|
70
|
+
| `find_importers` | Reverse dependency lookup ("who uses this?") |
|
|
71
|
+
| `get_class_hierarchy` | Class inheritance tree (ancestors + descendants) |
|
|
72
|
+
| `get_symbol_importance` | PageRank centrality ranking |
|
|
73
|
+
| `get_pr_impact` | Full PR risk assessment in a single call |
|
|
74
|
+
|
|
75
|
+
## Tool Selection Guide
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Reviewing a PR? → get_pr_impact
|
|
79
|
+
About to modify code? → get_blast_radius → get_why_context
|
|
80
|
+
Understanding a symbol? → get_context_for_task(taskType: "understand")
|
|
81
|
+
Fixing a bug? → get_context_for_task(taskType: "fix")
|
|
82
|
+
Refactoring? → get_context_for_task(taskType: "refactor")
|
|
83
|
+
Don't know the name? → search_symbols or get_ranked_context
|
|
84
|
+
Finding unused code? → find_dead_code
|
|
85
|
+
Safe to delete? → find_importers
|
|
86
|
+
Onboarding? → get_architectural_overlay → get_symbol_importance
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## CLI Commands
|
|
90
|
+
|
|
91
|
+
```Shell
|
|
92
|
+
npx ctxo-mcp index # Build full codebase index
|
|
93
|
+
npx ctxo-mcp index --check # CI gate: fail if index stale
|
|
94
|
+
npx ctxo-mcp index --skip-history # Fast re-index without git history
|
|
95
|
+
npx ctxo-mcp watch # File watcher for incremental re-index
|
|
96
|
+
npx ctxo-mcp init # Install git hooks (post-commit, post-merge)
|
|
97
|
+
npx ctxo-mcp status # Show index manifest
|
|
98
|
+
npx ctxo-mcp sync # Rebuild SQLite cache from committed JSON
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Features
|
|
102
|
+
|
|
103
|
+
**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`).
|
|
104
|
+
|
|
105
|
+
**Intent Filtering** — 4 tools accept `intent` parameter for keyword-based result filtering. `get_blast_radius(symbolId, intent: "test")` returns only test-related impacts.
|
|
106
|
+
|
|
107
|
+
**Tool Annotations** — All tools declare `readOnlyHint: true`, `idempotentHint: true`, `openWorldHint: false` for safe auto-approval in agent frameworks.
|
|
108
|
+
|
|
109
|
+
**Privacy Masking** — AWS keys, GCP service accounts, Azure connection strings, JWTs, private IPs, env secrets automatically redacted. Extensible via `.ctxo/masking.json`.
|
|
110
|
+
|
|
111
|
+
**Debug Mode** — `DEBUG=ctxo:*` for all debug output, or `DEBUG=ctxo:git,ctxo:storage` for specific namespaces.
|
|
112
|
+
|
|
113
|
+
**Per-tool savings vs manual approach:**
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
Manual Tokens Ctxo Tokens Savings
|
|
117
|
+
get_logic_slice ████████ 1,950 █ 150 92%
|
|
118
|
+
get_blast_radius ███ 800 ██ 600 25%
|
|
119
|
+
get_overlay ████████████ 25K██ 500 98%
|
|
120
|
+
get_why_context █ 200 █ 200 0%
|
|
121
|
+
get_change_intelligence ████████ 2,100 ▏ 50 98%
|
|
122
|
+
find_dead_code █████████ 5,000 ████ 2,000 60%
|
|
123
|
+
────────────────────────────────────────────────────────────
|
|
124
|
+
TOTAL 35,050 tokens 3,500 tokens 90%
|
|
125
|
+
329+ calls 6 calls 98%
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Agentic AI Usage
|
|
129
|
+
|
|
130
|
+
**Claude Agent SDK:**
|
|
131
|
+
|
|
132
|
+
```TypeScript
|
|
133
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
134
|
+
|
|
135
|
+
for await (const message of query({
|
|
136
|
+
prompt: "Analyze the blast radius of AuthService",
|
|
137
|
+
options: {
|
|
138
|
+
mcpServers: { ctxo: { command: "npx", args: ["-y", "ctxo-mcp"] } },
|
|
139
|
+
allowedTools: ["mcp__ctxo__*"]
|
|
140
|
+
}
|
|
141
|
+
})) { /* ... */ }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**OpenAI Agents SDK:**
|
|
145
|
+
|
|
146
|
+
```Python
|
|
147
|
+
from agents import Agent, Runner
|
|
148
|
+
from agents.mcp import MCPServerStdio
|
|
149
|
+
|
|
150
|
+
async with MCPServerStdio(params={"command": "npx", "args": ["ctxo-mcp"]}) as ctxo:
|
|
151
|
+
agent = Agent(name="Reviewer", mcp_servers=[ctxo])
|
|
152
|
+
result = await Runner.run(agent, "Review the PR impact")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
See [Agentic AI Integration Guide](docs/agentic-ai-integration.md) for LangChain, raw MCP client, and CI/CD examples.
|
|
156
|
+
|
|
157
|
+
## Multi-Language Support
|
|
158
|
+
|
|
159
|
+
| Language | Parser | Tier | Features |
|
|
160
|
+
| --------------------- | ----------- | ------ | ---------------------------------------------------------------- |
|
|
161
|
+
| TypeScript/JavaScript | ts-morph | Full | Type-aware resolution, cross-file imports, `this.method()` calls |
|
|
162
|
+
| Go | tree-sitter | Syntax | Structs, interfaces, functions, methods, import edges |
|
|
163
|
+
| C# | tree-sitter | Syntax | Classes, interfaces, methods, enums, namespace qualification |
|
|
164
|
+
|
|
165
|
+
## How It Works
|
|
166
|
+
|
|
167
|
+
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.
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
.ctxo/
|
|
171
|
+
index/ ← committed (per-file JSON, reviewable in PRs)
|
|
172
|
+
.cache/ ← gitignored (local SQLite, auto-rebuilt)
|
|
173
|
+
config.yaml ← committed (team settings)
|
|
174
|
+
masking.json ← committed (custom masking patterns)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Links
|
|
178
|
+
|
|
179
|
+
* [npm](https://www.npmjs.com/package/ctxo-mcp)
|
|
180
|
+
* [Changelog](CHANGELOG.md)
|
|
181
|
+
* [LLM Reference](llms-full.txt)
|
|
182
|
+
* [Validation Runbook](docs/runbook/mcp-validation/mcp-validation.md)
|
|
183
|
+
* [Architecture](docs/artifacts/architecture.md)
|
|
184
|
+
* [PRD](docs/artifacts/prd.md)
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __esm = (fn, res) => function __init() {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
};
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
__esm,
|
|
25
|
+
__export,
|
|
26
|
+
__toCommonJS
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=chunk-JIDIH7DS.js.map
|
|
@@ -44,14 +44,17 @@ var SymbolNodeSchema = z.object({
|
|
|
44
44
|
name: z.string().min(1),
|
|
45
45
|
kind: SymbolKindSchema,
|
|
46
46
|
startLine: z.number().int().nonnegative(),
|
|
47
|
-
endLine: z.number().int().nonnegative()
|
|
47
|
+
endLine: z.number().int().nonnegative(),
|
|
48
|
+
startOffset: z.number().int().nonnegative().optional(),
|
|
49
|
+
endOffset: z.number().int().nonnegative().optional()
|
|
48
50
|
}).refine((node) => node.endLine >= node.startLine, {
|
|
49
51
|
message: "endLine must be >= startLine"
|
|
50
52
|
});
|
|
51
53
|
var GraphEdgeSchema = z.object({
|
|
52
54
|
from: SymbolIdSchema,
|
|
53
55
|
to: SymbolIdSchema,
|
|
54
|
-
kind: EdgeKindSchema
|
|
56
|
+
kind: EdgeKindSchema,
|
|
57
|
+
typeOnly: z.boolean().optional()
|
|
55
58
|
});
|
|
56
59
|
var CommitIntentSchema = z.object({
|
|
57
60
|
hash: z.string().min(1),
|
|
@@ -130,7 +133,7 @@ var JsonIndexReader = class {
|
|
|
130
133
|
const fullPath = join(dir, entry.name);
|
|
131
134
|
if (entry.isDirectory()) {
|
|
132
135
|
files.push(...this.collectJsonFiles(fullPath, visited));
|
|
133
|
-
} else if (entry.name.endsWith(".json")) {
|
|
136
|
+
} else if (entry.name.endsWith(".json") && entry.name !== "co-changes.json") {
|
|
134
137
|
files.push(fullPath);
|
|
135
138
|
}
|
|
136
139
|
}
|
|
@@ -139,7 +142,8 @@ var JsonIndexReader = class {
|
|
|
139
142
|
};
|
|
140
143
|
|
|
141
144
|
export {
|
|
145
|
+
SYMBOL_KINDS,
|
|
142
146
|
DetailLevelSchema,
|
|
143
147
|
JsonIndexReader
|
|
144
148
|
};
|
|
145
|
-
//# sourceMappingURL=chunk-
|
|
149
|
+
//# sourceMappingURL=chunk-N6GPODUY.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';\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// ── 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,\n to: SymbolIdSchema,\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"],"mappings":"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","names":[]}
|
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
JsonIndexReader
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-N6GPODUY.js";
|
|
5
|
+
|
|
6
|
+
// src/core/logger.ts
|
|
7
|
+
function isDebugEnabled(namespace) {
|
|
8
|
+
const debugEnv = process.env["DEBUG"] ?? "";
|
|
9
|
+
if (!debugEnv) return false;
|
|
10
|
+
const patterns = debugEnv.split(",").map((p) => p.trim());
|
|
11
|
+
return patterns.some((p) => {
|
|
12
|
+
if (p === "*" || p === "ctxo:*") return true;
|
|
13
|
+
if (p === namespace) return true;
|
|
14
|
+
if (p.endsWith(":*") && namespace.startsWith(p.slice(0, -1))) return true;
|
|
15
|
+
return false;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function createLogger(namespace) {
|
|
19
|
+
const prefix = `[${namespace}]`;
|
|
20
|
+
return {
|
|
21
|
+
debug(message, ...args) {
|
|
22
|
+
if (isDebugEnabled(namespace)) {
|
|
23
|
+
console.error(`${prefix} DEBUG ${message}`, ...args);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
info(message, ...args) {
|
|
27
|
+
console.error(`${prefix} ${message}`, ...args);
|
|
28
|
+
},
|
|
29
|
+
warn(message, ...args) {
|
|
30
|
+
console.error(`${prefix} WARN ${message}`, ...args);
|
|
31
|
+
},
|
|
32
|
+
error(message, ...args) {
|
|
33
|
+
console.error(`${prefix} ERROR ${message}`, ...args);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
5
37
|
|
|
6
38
|
// src/adapters/storage/sqlite-storage-adapter.ts
|
|
7
39
|
import initSqlJs from "sql.js";
|
|
8
40
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
9
41
|
import { join, dirname } from "path";
|
|
42
|
+
var log = createLogger("ctxo:storage");
|
|
10
43
|
var SqliteStorageAdapter = class {
|
|
11
44
|
db;
|
|
12
45
|
dbPath;
|
|
@@ -23,16 +56,15 @@ var SqliteStorageAdapter = class {
|
|
|
23
56
|
this.db = new SQL.Database(buffer);
|
|
24
57
|
this.verifyIntegrity();
|
|
25
58
|
} catch {
|
|
26
|
-
|
|
59
|
+
log.warn("Corrupt DB detected, rebuilding from JSON index");
|
|
27
60
|
this.db = new SQL.Database();
|
|
28
61
|
this.createTables();
|
|
29
|
-
this.rebuildFromJson();
|
|
30
62
|
}
|
|
31
63
|
} else {
|
|
32
64
|
this.db = new SQL.Database();
|
|
33
65
|
this.createTables();
|
|
34
|
-
this.rebuildFromJson();
|
|
35
66
|
}
|
|
67
|
+
this.rebuildFromJson();
|
|
36
68
|
}
|
|
37
69
|
async initEmpty() {
|
|
38
70
|
const SQL = await initSqlJs();
|
|
@@ -107,7 +139,7 @@ var SqliteStorageAdapter = class {
|
|
|
107
139
|
);
|
|
108
140
|
for (const sym of fileIndex.symbols) {
|
|
109
141
|
db.run(
|
|
110
|
-
"INSERT INTO symbols (symbol_id, name, kind, file_path, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?)",
|
|
142
|
+
"INSERT OR REPLACE INTO symbols (symbol_id, name, kind, file_path, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?)",
|
|
111
143
|
[sym.symbolId, sym.name, sym.kind, fileIndex.file, sym.startLine, sym.endLine]
|
|
112
144
|
);
|
|
113
145
|
}
|
|
@@ -238,7 +270,7 @@ var SqliteStorageAdapter = class {
|
|
|
238
270
|
);
|
|
239
271
|
for (const sym of fileIndex.symbols) {
|
|
240
272
|
db.run(
|
|
241
|
-
"INSERT INTO symbols (symbol_id, name, kind, file_path, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?)",
|
|
273
|
+
"INSERT OR REPLACE INTO symbols (symbol_id, name, kind, file_path, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?)",
|
|
242
274
|
[sym.symbolId, sym.name, sym.kind, fileIndex.file, sym.startLine, sym.endLine]
|
|
243
275
|
);
|
|
244
276
|
}
|
|
@@ -274,7 +306,7 @@ var SqliteStorageAdapter = class {
|
|
|
274
306
|
try {
|
|
275
307
|
this.persist();
|
|
276
308
|
} catch (err) {
|
|
277
|
-
|
|
309
|
+
log.error(`Failed to persist DB: ${err.message}`);
|
|
278
310
|
}
|
|
279
311
|
}
|
|
280
312
|
deleteFileData(db, filePath) {
|
|
@@ -322,10 +354,12 @@ var SqliteStorageAdapter = class {
|
|
|
322
354
|
|
|
323
355
|
// src/adapters/git/simple-git-adapter.ts
|
|
324
356
|
import { simpleGit } from "simple-git";
|
|
357
|
+
var log2 = createLogger("ctxo:git");
|
|
325
358
|
var SimpleGitAdapter = class {
|
|
326
359
|
git;
|
|
327
360
|
constructor(projectRoot) {
|
|
328
361
|
this.git = simpleGit(projectRoot);
|
|
362
|
+
log2.debug("Initialized with root: %s", projectRoot);
|
|
329
363
|
}
|
|
330
364
|
async isAvailable() {
|
|
331
365
|
try {
|
|
@@ -335,29 +369,72 @@ var SimpleGitAdapter = class {
|
|
|
335
369
|
return false;
|
|
336
370
|
}
|
|
337
371
|
}
|
|
338
|
-
async getCommitHistory(filePath) {
|
|
372
|
+
async getCommitHistory(filePath, maxCount) {
|
|
339
373
|
try {
|
|
340
|
-
const
|
|
341
|
-
|
|
374
|
+
const log3 = await this.git.log({
|
|
375
|
+
file: filePath,
|
|
376
|
+
"--follow": null,
|
|
377
|
+
...maxCount ? { maxCount } : {}
|
|
378
|
+
});
|
|
379
|
+
return log3.all.map((entry) => ({
|
|
342
380
|
hash: entry.hash,
|
|
343
381
|
message: entry.message,
|
|
344
382
|
date: entry.date,
|
|
345
383
|
author: entry.author_name
|
|
346
384
|
}));
|
|
347
385
|
} catch (err) {
|
|
348
|
-
|
|
386
|
+
log2.error(` Failed to get history for ${filePath}: ${err.message}`);
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
async getBatchHistory(maxCount = 20) {
|
|
391
|
+
try {
|
|
392
|
+
const log3 = await this.git.log({ maxCount: maxCount * 50, "--name-only": null });
|
|
393
|
+
const result = /* @__PURE__ */ new Map();
|
|
394
|
+
for (const entry of log3.all) {
|
|
395
|
+
const record = {
|
|
396
|
+
hash: entry.hash,
|
|
397
|
+
message: entry.message,
|
|
398
|
+
date: entry.date,
|
|
399
|
+
author: entry.author_name
|
|
400
|
+
};
|
|
401
|
+
const diff = entry.diff;
|
|
402
|
+
const files = diff?.files?.map((f) => f.file.replace(/\\/g, "/")) ?? [];
|
|
403
|
+
for (const file of files) {
|
|
404
|
+
let list = result.get(file);
|
|
405
|
+
if (!list) {
|
|
406
|
+
list = [];
|
|
407
|
+
result.set(file, list);
|
|
408
|
+
}
|
|
409
|
+
if (list.length < maxCount) {
|
|
410
|
+
list.push(record);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return result;
|
|
415
|
+
} catch (err) {
|
|
416
|
+
log2.error(` Batch history failed: ${err.message}`);
|
|
417
|
+
return /* @__PURE__ */ new Map();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async getChangedFiles(since) {
|
|
421
|
+
try {
|
|
422
|
+
const diff = await this.git.diffSummary([since]);
|
|
423
|
+
return diff.files.map((f) => f.file.replace(/\\/g, "/"));
|
|
424
|
+
} catch (err) {
|
|
425
|
+
log2.error(` Failed to get changed files since ${since}: ${err.message}`);
|
|
349
426
|
return [];
|
|
350
427
|
}
|
|
351
428
|
}
|
|
352
429
|
async getFileChurn(filePath) {
|
|
353
430
|
try {
|
|
354
|
-
const
|
|
431
|
+
const log3 = await this.git.log({ file: filePath, "--follow": null });
|
|
355
432
|
return {
|
|
356
433
|
filePath,
|
|
357
|
-
commitCount:
|
|
434
|
+
commitCount: log3.total
|
|
358
435
|
};
|
|
359
436
|
} catch (err) {
|
|
360
|
-
|
|
437
|
+
log2.error(` Failed to get churn for ${filePath}: ${err.message}`);
|
|
361
438
|
return { filePath, commitCount: 0 };
|
|
362
439
|
}
|
|
363
440
|
}
|
|
@@ -402,9 +479,83 @@ var RevertDetector = class {
|
|
|
402
479
|
}
|
|
403
480
|
};
|
|
404
481
|
|
|
482
|
+
// src/core/co-change/co-change-analyzer.ts
|
|
483
|
+
var MIN_SHARED_COMMITS = 2;
|
|
484
|
+
var MIN_FREQUENCY = 0.1;
|
|
485
|
+
function aggregateCoChanges(indices) {
|
|
486
|
+
const commitToFiles = /* @__PURE__ */ new Map();
|
|
487
|
+
const fileCommitCounts = /* @__PURE__ */ new Map();
|
|
488
|
+
for (const idx of indices) {
|
|
489
|
+
const file = idx.file;
|
|
490
|
+
let count = 0;
|
|
491
|
+
for (const intent of idx.intent) {
|
|
492
|
+
count++;
|
|
493
|
+
let files = commitToFiles.get(intent.hash);
|
|
494
|
+
if (!files) {
|
|
495
|
+
files = /* @__PURE__ */ new Set();
|
|
496
|
+
commitToFiles.set(intent.hash, files);
|
|
497
|
+
}
|
|
498
|
+
files.add(file);
|
|
499
|
+
}
|
|
500
|
+
if (count > 0) {
|
|
501
|
+
fileCommitCounts.set(file, count);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const pairCounts = /* @__PURE__ */ new Map();
|
|
505
|
+
for (const files of commitToFiles.values()) {
|
|
506
|
+
if (files.size < 2) continue;
|
|
507
|
+
const sorted = [...files].sort();
|
|
508
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
509
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
510
|
+
const key = `${sorted[i]}|${sorted[j]}`;
|
|
511
|
+
pairCounts.set(key, (pairCounts.get(key) ?? 0) + 1);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const entries = [];
|
|
516
|
+
for (const [key, sharedCommits] of pairCounts) {
|
|
517
|
+
if (sharedCommits < MIN_SHARED_COMMITS) continue;
|
|
518
|
+
const [file1, file2] = key.split("|");
|
|
519
|
+
const count1 = fileCommitCounts.get(file1) ?? 0;
|
|
520
|
+
const count2 = fileCommitCounts.get(file2) ?? 0;
|
|
521
|
+
const minCount = Math.min(count1, count2);
|
|
522
|
+
if (minCount === 0) continue;
|
|
523
|
+
const frequency = Math.round(sharedCommits / minCount * 1e3) / 1e3;
|
|
524
|
+
if (frequency < MIN_FREQUENCY) continue;
|
|
525
|
+
entries.push({ file1, file2, sharedCommits, frequency });
|
|
526
|
+
}
|
|
527
|
+
entries.sort((a, b) => b.frequency - a.frequency || b.sharedCommits - a.sharedCommits);
|
|
528
|
+
return {
|
|
529
|
+
version: 1,
|
|
530
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
531
|
+
entries
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function loadCoChangeMap(matrix) {
|
|
535
|
+
const map = /* @__PURE__ */ new Map();
|
|
536
|
+
for (const entry of matrix.entries) {
|
|
537
|
+
let list1 = map.get(entry.file1);
|
|
538
|
+
if (!list1) {
|
|
539
|
+
list1 = [];
|
|
540
|
+
map.set(entry.file1, list1);
|
|
541
|
+
}
|
|
542
|
+
list1.push(entry);
|
|
543
|
+
let list2 = map.get(entry.file2);
|
|
544
|
+
if (!list2) {
|
|
545
|
+
list2 = [];
|
|
546
|
+
map.set(entry.file2, list2);
|
|
547
|
+
}
|
|
548
|
+
list2.push(entry);
|
|
549
|
+
}
|
|
550
|
+
return map;
|
|
551
|
+
}
|
|
552
|
+
|
|
405
553
|
export {
|
|
554
|
+
createLogger,
|
|
406
555
|
SqliteStorageAdapter,
|
|
407
556
|
RevertDetector,
|
|
408
|
-
SimpleGitAdapter
|
|
557
|
+
SimpleGitAdapter,
|
|
558
|
+
aggregateCoChanges,
|
|
559
|
+
loadCoChangeMap
|
|
409
560
|
};
|
|
410
|
-
//# sourceMappingURL=chunk-
|
|
561
|
+
//# sourceMappingURL=chunk-XSHNN6PU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/logger.ts","../src/adapters/storage/sqlite-storage-adapter.ts","../src/adapters/git/simple-git-adapter.ts","../src/core/why-context/revert-detector.ts","../src/core/co-change/co-change-analyzer.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","import initSqlJs, { type Database } from 'sql.js';\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport type { FileIndex, GraphEdge, SymbolNode } from '../../core/types.js';\nimport type { IStoragePort } from '../../ports/i-storage-port.js';\nimport { JsonIndexReader } from './json-index-reader.js';\nimport { createLogger } from '../../core/logger.js';\n\nconst log = createLogger('ctxo:storage');\n\nexport class SqliteStorageAdapter implements IStoragePort {\n private db: Database | undefined;\n private readonly dbPath: string;\n private readonly ctxoRoot: string;\n\n constructor(ctxoRoot: string) {\n this.ctxoRoot = ctxoRoot;\n this.dbPath = join(ctxoRoot, '.cache', 'symbols.db');\n }\n\n async init(): Promise<void> {\n const SQL = await initSqlJs();\n\n if (existsSync(this.dbPath)) {\n try {\n const buffer = readFileSync(this.dbPath);\n this.db = new SQL.Database(buffer);\n this.verifyIntegrity();\n } catch {\n log.warn('Corrupt DB detected, rebuilding from JSON index');\n this.db = new SQL.Database();\n this.createTables();\n }\n } else {\n this.db = new SQL.Database();\n this.createTables();\n }\n\n // Always rebuild from JSON — it is the source of truth\n this.rebuildFromJson();\n }\n\n async initEmpty(): Promise<void> {\n const SQL = await initSqlJs();\n this.db = new SQL.Database();\n this.createTables();\n }\n\n private database(): Database {\n if (!this.db) {\n throw new Error('SqliteStorageAdapter not initialized. Call init() first.');\n }\n return this.db;\n }\n\n private verifyIntegrity(): void {\n const db = this.database();\n const result = db.exec('PRAGMA integrity_check');\n const firstRow = result[0]?.values[0];\n if (!firstRow || firstRow[0] !== 'ok') {\n throw new Error('SQLite integrity check failed');\n }\n\n const tables = db.exec(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('symbols', 'edges', 'files')\",\n );\n if (!tables[0] || tables[0].values.length < 3) {\n throw new Error('Missing required tables');\n }\n }\n\n private createTables(): void {\n const db = this.database();\n db.run(`\n CREATE TABLE IF NOT EXISTS files (\n file_path TEXT PRIMARY KEY,\n last_modified INTEGER NOT NULL\n )\n `);\n db.run(`\n CREATE TABLE IF NOT EXISTS symbols (\n symbol_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n kind TEXT NOT NULL,\n file_path TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL\n )\n `);\n db.run(`\n CREATE TABLE IF NOT EXISTS edges (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n from_symbol TEXT NOT NULL,\n to_symbol TEXT NOT NULL,\n kind TEXT NOT NULL\n )\n `);\n db.run('CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path)');\n db.run('CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(from_symbol)');\n db.run('CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(to_symbol)');\n }\n\n private rebuildFromJson(): void {\n const reader = new JsonIndexReader(this.ctxoRoot);\n const indices = reader.readAll();\n\n if (indices.length > 0) {\n this.bulkWrite(indices);\n }\n }\n\n writeSymbolFile(fileIndex: FileIndex): void {\n const db = this.database();\n\n db.run('BEGIN TRANSACTION');\n try {\n this.deleteFileData(db, fileIndex.file);\n\n db.run(\n 'INSERT INTO files (file_path, last_modified) VALUES (?, ?)',\n [fileIndex.file, fileIndex.lastModified],\n );\n\n for (const sym of fileIndex.symbols) {\n db.run(\n 'INSERT OR REPLACE INTO symbols (symbol_id, name, kind, file_path, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?)',\n [sym.symbolId, sym.name, sym.kind, fileIndex.file, sym.startLine, sym.endLine],\n );\n }\n\n for (const edge of fileIndex.edges) {\n db.run(\n 'INSERT INTO edges (from_symbol, to_symbol, kind) VALUES (?, ?, ?)',\n [edge.from, edge.to, edge.kind],\n );\n }\n\n db.run('COMMIT');\n this.persistIfNeeded();\n } catch (err) {\n db.run('ROLLBACK');\n throw err;\n }\n }\n\n /**\n * Read symbol file from SQLite cache.\n * NOTE: SQLite only caches symbols + edges (graph topology).\n * intent, antiPatterns, and complexity live in committed JSON index only.\n * Use JsonIndexReader for full FileIndex data.\n */\n readSymbolFile(relativePath: string): FileIndex | undefined {\n const db = this.database();\n\n const fileResult = db.exec(\n 'SELECT file_path, last_modified FROM files WHERE file_path = ?',\n [relativePath],\n );\n if (!fileResult[0] || fileResult[0].values.length === 0) {\n return undefined;\n }\n\n const [filePath, lastModified] = fileResult[0].values[0] as [string, number];\n\n const symbols = this.getSymbolsForFile(db, filePath);\n const edges = this.getEdgesForFile(db, filePath);\n\n return {\n file: filePath,\n lastModified,\n symbols,\n edges,\n // Not stored in SQLite — use JsonIndexReader for these fields\n intent: [],\n antiPatterns: [],\n };\n }\n\n listIndexedFiles(): string[] {\n const db = this.database();\n const result = db.exec('SELECT file_path FROM files ORDER BY file_path');\n if (!result[0]) return [];\n return result[0].values.map((row) => row[0] as string);\n }\n\n deleteSymbolFile(relativePath: string): void {\n const db = this.database();\n this.deleteFileData(db, relativePath);\n }\n\n getSymbolById(symbolId: string): SymbolNode | undefined {\n const db = this.database();\n const result = db.exec(\n 'SELECT symbol_id, name, kind, start_line, end_line FROM symbols WHERE symbol_id = ?',\n [symbolId],\n );\n if (!result[0] || result[0].values.length === 0) {\n return undefined;\n }\n\n const [sid, name, kind, startLine, endLine] = result[0].values[0] as [string, string, string, number, number];\n return { symbolId: sid, name, kind: kind as SymbolNode['kind'], startLine, endLine };\n }\n\n getEdgesFrom(symbolId: string): GraphEdge[] {\n const db = this.database();\n const result = db.exec(\n 'SELECT from_symbol, to_symbol, kind FROM edges WHERE from_symbol = ?',\n [symbolId],\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n from: row[0] as string,\n to: row[1] as string,\n kind: row[2] as GraphEdge['kind'],\n }));\n }\n\n getEdgesTo(symbolId: string): GraphEdge[] {\n const db = this.database();\n const result = db.exec(\n 'SELECT from_symbol, to_symbol, kind FROM edges WHERE to_symbol = ?',\n [symbolId],\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n from: row[0] as string,\n to: row[1] as string,\n kind: row[2] as GraphEdge['kind'],\n }));\n }\n\n getAllSymbols(): SymbolNode[] {\n const db = this.database();\n const result = db.exec(\n 'SELECT symbol_id, name, kind, start_line, end_line FROM symbols',\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n symbolId: row[0] as string,\n name: row[1] as string,\n kind: row[2] as SymbolNode['kind'],\n startLine: row[3] as number,\n endLine: row[4] as number,\n }));\n }\n\n getAllEdges(): GraphEdge[] {\n const db = this.database();\n const result = db.exec(\n 'SELECT from_symbol, to_symbol, kind FROM edges',\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n from: row[0] as string,\n to: row[1] as string,\n kind: row[2] as GraphEdge['kind'],\n }));\n }\n\n bulkWrite(indices: FileIndex[]): void {\n const db = this.database();\n\n db.run('BEGIN TRANSACTION');\n try {\n for (const fileIndex of indices) {\n this.deleteFileData(db, fileIndex.file);\n\n db.run(\n 'INSERT INTO files (file_path, last_modified) VALUES (?, ?)',\n [fileIndex.file, fileIndex.lastModified],\n );\n\n for (const sym of fileIndex.symbols) {\n db.run(\n 'INSERT OR REPLACE INTO symbols (symbol_id, name, kind, file_path, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?)',\n [sym.symbolId, sym.name, sym.kind, fileIndex.file, sym.startLine, sym.endLine],\n );\n }\n\n for (const edge of fileIndex.edges) {\n db.run(\n 'INSERT INTO edges (from_symbol, to_symbol, kind) VALUES (?, ?, ?)',\n [edge.from, edge.to, edge.kind],\n );\n }\n }\n\n db.run('COMMIT');\n this.persistIfNeeded();\n } catch (err) {\n db.run('ROLLBACK');\n throw err;\n }\n }\n\n persist(): void {\n const db = this.database();\n const data = db.export();\n const buffer = Buffer.from(data);\n mkdirSync(dirname(this.dbPath), { recursive: true });\n writeFileSync(this.dbPath, buffer);\n }\n\n close(): void {\n if (this.db) {\n this.persist();\n this.db.close();\n this.db = undefined;\n }\n }\n\n private persistIfNeeded(): void {\n try {\n this.persist();\n } catch (err) {\n log.error(`Failed to persist DB: ${(err as Error).message}`);\n }\n }\n\n private deleteFileData(db: Database, filePath: string): void {\n // Delete edges originating from this file's symbols\n const symResult = db.exec(\n 'SELECT symbol_id FROM symbols WHERE file_path = ?',\n [filePath],\n );\n if (symResult[0]) {\n for (const row of symResult[0].values) {\n db.run('DELETE FROM edges WHERE from_symbol = ?', [row[0]]);\n }\n }\n db.run('DELETE FROM symbols WHERE file_path = ?', [filePath]);\n db.run('DELETE FROM files WHERE file_path = ?', [filePath]);\n }\n\n private getSymbolsForFile(db: Database, filePath: string): SymbolNode[] {\n const result = db.exec(\n 'SELECT symbol_id, name, kind, start_line, end_line FROM symbols WHERE file_path = ? ORDER BY start_line',\n [filePath],\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n symbolId: row[0] as string,\n name: row[1] as string,\n kind: row[2] as SymbolNode['kind'],\n startLine: row[3] as number,\n endLine: row[4] as number,\n }));\n }\n\n private getEdgesForFile(db: Database, filePath: string): GraphEdge[] {\n const result = db.exec(\n `SELECT e.from_symbol, e.to_symbol, e.kind FROM edges e\n INNER JOIN symbols s ON e.from_symbol = s.symbol_id\n WHERE s.file_path = ?`,\n [filePath],\n );\n if (!result[0]) return [];\n return result[0].values.map((row) => ({\n from: row[0] as string,\n to: row[1] as string,\n kind: row[2] as GraphEdge['kind'],\n }));\n }\n}\n","import { simpleGit, type SimpleGit } from 'simple-git';\nimport type { IGitPort } from '../../ports/i-git-port.js';\nimport type { CommitRecord, ChurnData } from '../../core/types.js';\nimport { createLogger } from '../../core/logger.js';\n\nconst log = createLogger('ctxo:git');\n\nexport class SimpleGitAdapter implements IGitPort {\n private readonly git: SimpleGit;\n\n constructor(projectRoot: string) {\n this.git = simpleGit(projectRoot);\n log.debug('Initialized with root: %s', projectRoot);\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n await this.git.version();\n return true;\n } catch {\n return false;\n }\n }\n\n async getCommitHistory(filePath: string, maxCount?: number): Promise<CommitRecord[]> {\n try {\n const log = await this.git.log({\n file: filePath,\n '--follow': null,\n ...(maxCount ? { maxCount } : {}),\n });\n\n return log.all.map((entry) => ({\n hash: entry.hash,\n message: entry.message,\n date: entry.date,\n author: entry.author_name,\n }));\n } catch (err) {\n log.error(` Failed to get history for ${filePath}: ${(err as Error).message}`);\n return [];\n }\n }\n\n async getBatchHistory(maxCount = 20): Promise<Map<string, CommitRecord[]>> {\n try {\n const log = await this.git.log({ maxCount: maxCount * 50, '--name-only': null });\n const result = new Map<string, CommitRecord[]>();\n\n for (const entry of log.all) {\n const record: CommitRecord = {\n hash: entry.hash,\n message: entry.message,\n date: entry.date,\n author: entry.author_name,\n };\n\n const diff = entry.diff;\n const files: string[] = diff?.files?.map((f: { file: string }) => f.file.replace(/\\\\/g, '/')) ?? [];\n\n for (const file of files) {\n let list = result.get(file);\n if (!list) { list = []; result.set(file, list); }\n if (list.length < maxCount) {\n list.push(record);\n }\n }\n }\n\n return result;\n } catch (err) {\n log.error(` Batch history failed: ${(err as Error).message}`);\n return new Map();\n }\n }\n\n async getChangedFiles(since: string): Promise<string[]> {\n try {\n const diff = await this.git.diffSummary([since]);\n return diff.files.map((f) => f.file.replace(/\\\\/g, '/'));\n } catch (err) {\n log.error(` Failed to get changed files since ${since}: ${(err as Error).message}`);\n return [];\n }\n }\n\n async getFileChurn(filePath: string): Promise<ChurnData> {\n try {\n const log = await this.git.log({ file: filePath, '--follow': null });\n\n return {\n filePath,\n commitCount: log.total,\n };\n } catch (err) {\n log.error(` Failed to get churn for ${filePath}: ${(err as Error).message}`);\n return { filePath, commitCount: 0 };\n }\n }\n}\n","import type { CommitRecord, AntiPattern } from '../types.js';\n\n// Explicit revert patterns\nconst REVERT_QUOTED_PATTERN = /^Revert \"(.+)\"$/;\nconst REVERT_PREFIX_PATTERN = /^revert:\\s*(.+)$/i;\n\n// Undo/rollback patterns\nconst UNDO_PATTERN = /^undo[:\\s]/i;\nconst ROLLBACK_PATTERN = /^rollback[:\\s]/i;\n\n// Indirect revert indicators (keywords in commit message body)\nconst INDIRECT_KEYWORDS = [\n /\\brevert(?:s|ed|ing)?\\b/i,\n /\\broll(?:s|ed|ing)?\\s*back\\b/i,\n /\\bundo(?:es|ne|ing)?\\b/i,\n /\\bbacked?\\s*out\\b/i,\n /\\bremov(?:e|es|ed|ing)\\s+(?:broken|buggy|faulty)\\b/i,\n];\n\nexport class RevertDetector {\n detect(commits: readonly CommitRecord[]): AntiPattern[] {\n const antiPatterns: AntiPattern[] = [];\n\n for (const commit of commits) {\n if (!commit.message) continue;\n\n if (this.isRevert(commit.message)) {\n antiPatterns.push({\n hash: commit.hash,\n message: commit.message,\n date: commit.date,\n });\n }\n }\n\n return antiPatterns;\n }\n\n private isRevert(message: string): boolean {\n // Explicit patterns (high confidence)\n if (REVERT_QUOTED_PATTERN.test(message)) return true;\n if (REVERT_PREFIX_PATTERN.test(message)) return true;\n if (UNDO_PATTERN.test(message)) return true;\n if (ROLLBACK_PATTERN.test(message)) return true;\n\n // Indirect indicators (keyword search in full message)\n for (const pattern of INDIRECT_KEYWORDS) {\n if (pattern.test(message)) return true;\n }\n\n return false;\n }\n}\n","import type { CoChangeEntry, CoChangeMatrix, FileIndex } from '../types.js';\n\nconst MIN_SHARED_COMMITS = 2;\nconst MIN_FREQUENCY = 0.1;\n\nexport function aggregateCoChanges(indices: readonly FileIndex[]): CoChangeMatrix {\n // Step 1: Build commit → files map\n const commitToFiles = new Map<string, Set<string>>();\n const fileCommitCounts = new Map<string, number>();\n\n for (const idx of indices) {\n const file = idx.file;\n let count = 0;\n for (const intent of idx.intent) {\n count++;\n let files = commitToFiles.get(intent.hash);\n if (!files) {\n files = new Set();\n commitToFiles.set(intent.hash, files);\n }\n files.add(file);\n }\n if (count > 0) {\n fileCommitCounts.set(file, count);\n }\n }\n\n // Step 2: Count shared commits per file pair\n const pairCounts = new Map<string, number>();\n for (const files of commitToFiles.values()) {\n if (files.size < 2) continue;\n const sorted = [...files].sort();\n for (let i = 0; i < sorted.length; i++) {\n for (let j = i + 1; j < sorted.length; j++) {\n const key = `${sorted[i]}|${sorted[j]}`;\n pairCounts.set(key, (pairCounts.get(key) ?? 0) + 1);\n }\n }\n }\n\n // Step 3: Calculate frequency and filter\n const entries: CoChangeEntry[] = [];\n for (const [key, sharedCommits] of pairCounts) {\n if (sharedCommits < MIN_SHARED_COMMITS) continue;\n\n const [file1, file2] = key.split('|') as [string, string];\n const count1 = fileCommitCounts.get(file1) ?? 0;\n const count2 = fileCommitCounts.get(file2) ?? 0;\n const minCount = Math.min(count1, count2);\n if (minCount === 0) continue;\n\n const frequency = Math.round((sharedCommits / minCount) * 1000) / 1000;\n if (frequency < MIN_FREQUENCY) continue;\n\n entries.push({ file1, file2, sharedCommits, frequency });\n }\n\n entries.sort((a, b) => b.frequency - a.frequency || b.sharedCommits - a.sharedCommits);\n\n return {\n version: 1,\n timestamp: Math.floor(Date.now() / 1000),\n entries,\n };\n}\n\nexport function loadCoChangeMap(matrix: CoChangeMatrix): Map<string, CoChangeEntry[]> {\n const map = new Map<string, CoChangeEntry[]>();\n for (const entry of matrix.entries) {\n let list1 = map.get(entry.file1);\n if (!list1) { list1 = []; map.set(entry.file1, list1); }\n list1.push(entry);\n\n let list2 = map.get(entry.file2);\n if (!list2) { list2 = []; map.set(entry.file2, list2); }\n list2.push(entry);\n }\n return map;\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;;;ACrDA,OAAO,eAAkC;AACzC,SAAS,cAAc,eAAe,YAAY,iBAAiB;AACnE,SAAS,MAAM,eAAe;AAM9B,IAAM,MAAM,aAAa,cAAc;AAEhC,IAAM,uBAAN,MAAmD;AAAA,EAChD;AAAA,EACS;AAAA,EACA;AAAA,EAEjB,YAAY,UAAkB;AAC5B,SAAK,WAAW;AAChB,SAAK,SAAS,KAAK,UAAU,UAAU,YAAY;AAAA,EACrD;AAAA,EAEA,MAAM,OAAsB;AAC1B,UAAM,MAAM,MAAM,UAAU;AAE5B,QAAI,WAAW,KAAK,MAAM,GAAG;AAC3B,UAAI;AACF,cAAM,SAAS,aAAa,KAAK,MAAM;AACvC,aAAK,KAAK,IAAI,IAAI,SAAS,MAAM;AACjC,aAAK,gBAAgB;AAAA,MACvB,QAAQ;AACN,YAAI,KAAK,iDAAiD;AAC1D,aAAK,KAAK,IAAI,IAAI,SAAS;AAC3B,aAAK,aAAa;AAAA,MACpB;AAAA,IACF,OAAO;AACL,WAAK,KAAK,IAAI,IAAI,SAAS;AAC3B,WAAK,aAAa;AAAA,IACpB;AAGA,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,MAAM,YAA2B;AAC/B,UAAM,MAAM,MAAM,UAAU;AAC5B,SAAK,KAAK,IAAI,IAAI,SAAS;AAC3B,SAAK,aAAa;AAAA,EACpB;AAAA,EAEQ,WAAqB;AAC3B,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,IAAI,MAAM,0DAA0D;AAAA,IAC5E;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,kBAAwB;AAC9B,UAAM,KAAK,KAAK,SAAS;AACzB,UAAM,SAAS,GAAG,KAAK,wBAAwB;AAC/C,UAAM,WAAW,OAAO,CAAC,GAAG,OAAO,CAAC;AACpC,QAAI,CAAC,YAAY,SAAS,CAAC,MAAM,MAAM;AACrC,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AAEA,UAAM,SAAS,GAAG;AAAA,MAChB;AAAA,IACF;AACA,QAAI,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,EAAE,OAAO,SAAS,GAAG;AAC7C,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AAAA,EACF;AAAA,EAEQ,eAAqB;AAC3B,UAAM,KAAK,KAAK,SAAS;AACzB,OAAG,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,KAKN;AACD,OAAG,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KASN;AACD,OAAG,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAON;AACD,OAAG,IAAI,mEAAmE;AAC1E,OAAG,IAAI,iEAAiE;AACxE,OAAG,IAAI,6DAA6D;AAAA,EACtE;AAAA,EAEQ,kBAAwB;AAC9B,UAAM,SAAS,IAAI,gBAAgB,KAAK,QAAQ;AAChD,UAAM,UAAU,OAAO,QAAQ;AAE/B,QAAI,QAAQ,SAAS,GAAG;AACtB,WAAK,UAAU,OAAO;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,gBAAgB,WAA4B;AAC1C,UAAM,KAAK,KAAK,SAAS;AAEzB,OAAG,IAAI,mBAAmB;AAC1B,QAAI;AACF,WAAK,eAAe,IAAI,UAAU,IAAI;AAEtC,SAAG;AAAA,QACD;AAAA,QACA,CAAC,UAAU,MAAM,UAAU,YAAY;AAAA,MACzC;AAEA,iBAAW,OAAO,UAAU,SAAS;AACnC,WAAG;AAAA,UACD;AAAA,UACA,CAAC,IAAI,UAAU,IAAI,MAAM,IAAI,MAAM,UAAU,MAAM,IAAI,WAAW,IAAI,OAAO;AAAA,QAC/E;AAAA,MACF;AAEA,iBAAW,QAAQ,UAAU,OAAO;AAClC,WAAG;AAAA,UACD;AAAA,UACA,CAAC,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI;AAAA,QAChC;AAAA,MACF;AAEA,SAAG,IAAI,QAAQ;AACf,WAAK,gBAAgB;AAAA,IACvB,SAAS,KAAK;AACZ,SAAG,IAAI,UAAU;AACjB,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,cAA6C;AAC1D,UAAM,KAAK,KAAK,SAAS;AAEzB,UAAM,aAAa,GAAG;AAAA,MACpB;AAAA,MACA,CAAC,YAAY;AAAA,IACf;AACA,QAAI,CAAC,WAAW,CAAC,KAAK,WAAW,CAAC,EAAE,OAAO,WAAW,GAAG;AACvD,aAAO;AAAA,IACT;AAEA,UAAM,CAAC,UAAU,YAAY,IAAI,WAAW,CAAC,EAAE,OAAO,CAAC;AAEvD,UAAM,UAAU,KAAK,kBAAkB,IAAI,QAAQ;AACnD,UAAM,QAAQ,KAAK,gBAAgB,IAAI,QAAQ;AAE/C,WAAO;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA,QAAQ,CAAC;AAAA,MACT,cAAc,CAAC;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,mBAA6B;AAC3B,UAAM,KAAK,KAAK,SAAS;AACzB,UAAM,SAAS,GAAG,KAAK,gDAAgD;AACvE,QAAI,CAAC,OAAO,CAAC,EAAG,QAAO,CAAC;AACxB,WAAO,OAAO,CAAC,EAAE,OAAO,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAW;AAAA,EACvD;AAAA,EAEA,iBAAiB,cAA4B;AAC3C,UAAM,KAAK,KAAK,SAAS;AACzB,SAAK,eAAe,IAAI,YAAY;AAAA,EACtC;AAAA,EAEA,cAAc,UAA0C;AACtD,UAAM,KAAK,KAAK,SAAS;AACzB,UAAM,SAAS,GAAG;AAAA,MAChB;AAAA,MACA,CAAC,QAAQ;AAAA,IACX;AACA,QAAI,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,EAAE,OAAO,WAAW,GAAG;AAC/C,aAAO;AAAA,IACT;AAEA,UAAM,CAAC,KAAK,MAAM,MAAM,WAAW,OAAO,IAAI,OAAO,CAAC,EAAE,OAAO,CAAC;AAChE,WAAO,EAAE,UAAU,KAAK,MAAM,MAAkC,WAAW,QAAQ;AAAA,EACrF;AAAA,EAEA,aAAa,UAA+B;AAC1C,UAAM,KAAK,KAAK,SAAS;AACzB,UAAM,SAAS,GAAG;AAAA,MAChB;AAAA,MACA,CAAC,QAAQ;AAAA,IACX;AACA,QAAI,CAAC,OAAO,CAAC,EAAG,QAAO,CAAC;AACxB,WAAO,OAAO,CAAC,EAAE,OAAO,IAAI,CAAC,SAAS;AAAA,MACpC,MAAM,IAAI,CAAC;AAAA,MACX,IAAI,IAAI,CAAC;AAAA,MACT,MAAM,IAAI,CAAC;AAAA,IACb,EAAE;AAAA,EACJ;AAAA,EAEA,WAAW,UAA+B;AACxC,UAAM,KAAK,KAAK,SAAS;AACzB,UAAM,SAAS,GAAG;AAAA,MAChB;AAAA,MACA,CAAC,QAAQ;AAAA,IACX;AACA,QAAI,CAAC,OAAO,CAAC,EAAG,QAAO,CAAC;AACxB,WAAO,OAAO,CAAC,EAAE,OAAO,IAAI,CAAC,SAAS;AAAA,MACpC,MAAM,IAAI,CAAC;AAAA,MACX,IAAI,IAAI,CAAC;AAAA,MACT,MAAM,IAAI,CAAC;AAAA,IACb,EAAE;AAAA,EACJ;AAAA,EAEA,gBAA8B;AAC5B,UAAM,KAAK,KAAK,SAAS;AACzB,UAAM,SAAS,GAAG;AAAA,MAChB;AAAA,IACF;AACA,QAAI,CAAC,OAAO,CAAC,EAAG,QAAO,CAAC;AACxB,WAAO,OAAO,CAAC,EAAE,OAAO,IAAI,CAAC,SAAS;AAAA,MACpC,UAAU,IAAI,CAAC;AAAA,MACf,MAAM,IAAI,CAAC;AAAA,MACX,MAAM,IAAI,CAAC;AAAA,MACX,WAAW,IAAI,CAAC;AAAA,MAChB,SAAS,IAAI,CAAC;AAAA,IAChB,EAAE;AAAA,EACJ;AAAA,EAEA,cAA2B;AACzB,UAAM,KAAK,KAAK,SAAS;AACzB,UAAM,SAAS,GAAG;AAAA,MAChB;AAAA,IACF;AACA,QAAI,CAAC,OAAO,CAAC,EAAG,QAAO,CAAC;AACxB,WAAO,OAAO,CAAC,EAAE,OAAO,IAAI,CAAC,SAAS;AAAA,MACpC,MAAM,IAAI,CAAC;AAAA,MACX,IAAI,IAAI,CAAC;AAAA,MACT,MAAM,IAAI,CAAC;AAAA,IACb,EAAE;AAAA,EACJ;AAAA,EAEA,UAAU,SAA4B;AACpC,UAAM,KAAK,KAAK,SAAS;AAEzB,OAAG,IAAI,mBAAmB;AAC1B,QAAI;AACF,iBAAW,aAAa,SAAS;AAC/B,aAAK,eAAe,IAAI,UAAU,IAAI;AAEtC,WAAG;AAAA,UACD;AAAA,UACA,CAAC,UAAU,MAAM,UAAU,YAAY;AAAA,QACzC;AAEA,mBAAW,OAAO,UAAU,SAAS;AACnC,aAAG;AAAA,YACD;AAAA,YACA,CAAC,IAAI,UAAU,IAAI,MAAM,IAAI,MAAM,UAAU,MAAM,IAAI,WAAW,IAAI,OAAO;AAAA,UAC/E;AAAA,QACF;AAEA,mBAAW,QAAQ,UAAU,OAAO;AAClC,aAAG;AAAA,YACD;AAAA,YACA,CAAC,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI;AAAA,UAChC;AAAA,QACF;AAAA,MACF;AAEA,SAAG,IAAI,QAAQ;AACf,WAAK,gBAAgB;AAAA,IACvB,SAAS,KAAK;AACZ,SAAG,IAAI,UAAU;AACjB,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,UAAM,KAAK,KAAK,SAAS;AACzB,UAAM,OAAO,GAAG,OAAO;AACvB,UAAM,SAAS,OAAO,KAAK,IAAI;AAC/B,cAAU,QAAQ,KAAK,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,kBAAc,KAAK,QAAQ,MAAM;AAAA,EACnC;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,IAAI;AACX,WAAK,QAAQ;AACb,WAAK,GAAG,MAAM;AACd,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAAA,EAEQ,kBAAwB;AAC9B,QAAI;AACF,WAAK,QAAQ;AAAA,IACf,SAAS,KAAK;AACZ,UAAI,MAAM,yBAA0B,IAAc,OAAO,EAAE;AAAA,IAC7D;AAAA,EACF;AAAA,EAEQ,eAAe,IAAc,UAAwB;AAE3D,UAAM,YAAY,GAAG;AAAA,MACnB;AAAA,MACA,CAAC,QAAQ;AAAA,IACX;AACA,QAAI,UAAU,CAAC,GAAG;AAChB,iBAAW,OAAO,UAAU,CAAC,EAAE,QAAQ;AACrC,WAAG,IAAI,2CAA2C,CAAC,IAAI,CAAC,CAAC,CAAC;AAAA,MAC5D;AAAA,IACF;AACA,OAAG,IAAI,2CAA2C,CAAC,QAAQ,CAAC;AAC5D,OAAG,IAAI,yCAAyC,CAAC,QAAQ,CAAC;AAAA,EAC5D;AAAA,EAEQ,kBAAkB,IAAc,UAAgC;AACtE,UAAM,SAAS,GAAG;AAAA,MAChB;AAAA,MACA,CAAC,QAAQ;AAAA,IACX;AACA,QAAI,CAAC,OAAO,CAAC,EAAG,QAAO,CAAC;AACxB,WAAO,OAAO,CAAC,EAAE,OAAO,IAAI,CAAC,SAAS;AAAA,MACpC,UAAU,IAAI,CAAC;AAAA,MACf,MAAM,IAAI,CAAC;AAAA,MACX,MAAM,IAAI,CAAC;AAAA,MACX,WAAW,IAAI,CAAC;AAAA,MAChB,SAAS,IAAI,CAAC;AAAA,IAChB,EAAE;AAAA,EACJ;AAAA,EAEQ,gBAAgB,IAAc,UAA+B;AACnE,UAAM,SAAS,GAAG;AAAA,MAChB;AAAA;AAAA;AAAA,MAGA,CAAC,QAAQ;AAAA,IACX;AACA,QAAI,CAAC,OAAO,CAAC,EAAG,QAAO,CAAC;AACxB,WAAO,OAAO,CAAC,EAAE,OAAO,IAAI,CAAC,SAAS;AAAA,MACpC,MAAM,IAAI,CAAC;AAAA,MACX,IAAI,IAAI,CAAC;AAAA,MACT,MAAM,IAAI,CAAC;AAAA,IACb,EAAE;AAAA,EACJ;AACF;;;AC5WA,SAAS,iBAAiC;AAK1C,IAAMA,OAAM,aAAa,UAAU;AAE5B,IAAM,mBAAN,MAA2C;AAAA,EAC/B;AAAA,EAEjB,YAAY,aAAqB;AAC/B,SAAK,MAAM,UAAU,WAAW;AAChC,IAAAA,KAAI,MAAM,6BAA6B,WAAW;AAAA,EACpD;AAAA,EAEA,MAAM,cAAgC;AACpC,QAAI;AACF,YAAM,KAAK,IAAI,QAAQ;AACvB,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,iBAAiB,UAAkB,UAA4C;AACnF,QAAI;AACF,YAAMA,OAAM,MAAM,KAAK,IAAI,IAAI;AAAA,QAC7B,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,MACjC,CAAC;AAED,aAAOA,KAAI,IAAI,IAAI,CAAC,WAAW;AAAA,QAC7B,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,MAAM,MAAM;AAAA,QACZ,QAAQ,MAAM;AAAA,MAChB,EAAE;AAAA,IACJ,SAAS,KAAK;AACZ,MAAAA,KAAI,MAAM,8BAA8B,QAAQ,KAAM,IAAc,OAAO,EAAE;AAC7E,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,gBAAgB,WAAW,IAA0C;AACzE,QAAI;AACF,YAAMA,OAAM,MAAM,KAAK,IAAI,IAAI,EAAE,UAAU,WAAW,IAAI,eAAe,KAAK,CAAC;AAC/E,YAAM,SAAS,oBAAI,IAA4B;AAE/C,iBAAW,SAASA,KAAI,KAAK;AAC3B,cAAM,SAAuB;AAAA,UAC3B,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM;AAAA,UACf,MAAM,MAAM;AAAA,UACZ,QAAQ,MAAM;AAAA,QAChB;AAEA,cAAM,OAAO,MAAM;AACnB,cAAM,QAAkB,MAAM,OAAO,IAAI,CAAC,MAAwB,EAAE,KAAK,QAAQ,OAAO,GAAG,CAAC,KAAK,CAAC;AAElG,mBAAW,QAAQ,OAAO;AACxB,cAAI,OAAO,OAAO,IAAI,IAAI;AAC1B,cAAI,CAAC,MAAM;AAAE,mBAAO,CAAC;AAAG,mBAAO,IAAI,MAAM,IAAI;AAAA,UAAG;AAChD,cAAI,KAAK,SAAS,UAAU;AAC1B,iBAAK,KAAK,MAAM;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,MAAAA,KAAI,MAAM,0BAA2B,IAAc,OAAO,EAAE;AAC5D,aAAO,oBAAI,IAAI;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,MAAM,gBAAgB,OAAkC;AACtD,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,IAAI,YAAY,CAAC,KAAK,CAAC;AAC/C,aAAO,KAAK,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK,QAAQ,OAAO,GAAG,CAAC;AAAA,IACzD,SAAS,KAAK;AACZ,MAAAA,KAAI,MAAM,sCAAsC,KAAK,KAAM,IAAc,OAAO,EAAE;AAClF,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,UAAsC;AACvD,QAAI;AACF,YAAMA,OAAM,MAAM,KAAK,IAAI,IAAI,EAAE,MAAM,UAAU,YAAY,KAAK,CAAC;AAEnE,aAAO;AAAA,QACL;AAAA,QACA,aAAaA,KAAI;AAAA,MACnB;AAAA,IACF,SAAS,KAAK;AACZ,MAAAA,KAAI,MAAM,4BAA4B,QAAQ,KAAM,IAAc,OAAO,EAAE;AAC3E,aAAO,EAAE,UAAU,aAAa,EAAE;AAAA,IACpC;AAAA,EACF;AACF;;;AChGA,IAAM,wBAAwB;AAC9B,IAAM,wBAAwB;AAG9B,IAAM,eAAe;AACrB,IAAM,mBAAmB;AAGzB,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,iBAAN,MAAqB;AAAA,EAC1B,OAAO,SAAiD;AACtD,UAAM,eAA8B,CAAC;AAErC,eAAW,UAAU,SAAS;AAC5B,UAAI,CAAC,OAAO,QAAS;AAErB,UAAI,KAAK,SAAS,OAAO,OAAO,GAAG;AACjC,qBAAa,KAAK;AAAA,UAChB,MAAM,OAAO;AAAA,UACb,SAAS,OAAO;AAAA,UAChB,MAAM,OAAO;AAAA,QACf,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,SAAS,SAA0B;AAEzC,QAAI,sBAAsB,KAAK,OAAO,EAAG,QAAO;AAChD,QAAI,sBAAsB,KAAK,OAAO,EAAG,QAAO;AAChD,QAAI,aAAa,KAAK,OAAO,EAAG,QAAO;AACvC,QAAI,iBAAiB,KAAK,OAAO,EAAG,QAAO;AAG3C,eAAW,WAAW,mBAAmB;AACvC,UAAI,QAAQ,KAAK,OAAO,EAAG,QAAO;AAAA,IACpC;AAEA,WAAO;AAAA,EACT;AACF;;;AClDA,IAAM,qBAAqB;AAC3B,IAAM,gBAAgB;AAEf,SAAS,mBAAmB,SAA+C;AAEhF,QAAM,gBAAgB,oBAAI,IAAyB;AACnD,QAAM,mBAAmB,oBAAI,IAAoB;AAEjD,aAAW,OAAO,SAAS;AACzB,UAAM,OAAO,IAAI;AACjB,QAAI,QAAQ;AACZ,eAAW,UAAU,IAAI,QAAQ;AAC/B;AACA,UAAI,QAAQ,cAAc,IAAI,OAAO,IAAI;AACzC,UAAI,CAAC,OAAO;AACV,gBAAQ,oBAAI,IAAI;AAChB,sBAAc,IAAI,OAAO,MAAM,KAAK;AAAA,MACtC;AACA,YAAM,IAAI,IAAI;AAAA,IAChB;AACA,QAAI,QAAQ,GAAG;AACb,uBAAiB,IAAI,MAAM,KAAK;AAAA,IAClC;AAAA,EACF;AAGA,QAAM,aAAa,oBAAI,IAAoB;AAC3C,aAAW,SAAS,cAAc,OAAO,GAAG;AAC1C,QAAI,MAAM,OAAO,EAAG;AACpB,UAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK;AAC/B,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,eAAS,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AAC1C,cAAM,MAAM,GAAG,OAAO,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC;AACrC,mBAAW,IAAI,MAAM,WAAW,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAGA,QAAM,UAA2B,CAAC;AAClC,aAAW,CAAC,KAAK,aAAa,KAAK,YAAY;AAC7C,QAAI,gBAAgB,mBAAoB;AAExC,UAAM,CAAC,OAAO,KAAK,IAAI,IAAI,MAAM,GAAG;AACpC,UAAM,SAAS,iBAAiB,IAAI,KAAK,KAAK;AAC9C,UAAM,SAAS,iBAAiB,IAAI,KAAK,KAAK;AAC9C,UAAM,WAAW,KAAK,IAAI,QAAQ,MAAM;AACxC,QAAI,aAAa,EAAG;AAEpB,UAAM,YAAY,KAAK,MAAO,gBAAgB,WAAY,GAAI,IAAI;AAClE,QAAI,YAAY,cAAe;AAE/B,YAAQ,KAAK,EAAE,OAAO,OAAO,eAAe,UAAU,CAAC;AAAA,EACzD;AAEA,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa;AAErF,SAAO;AAAA,IACL,SAAS;AAAA,IACT,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACvC;AAAA,EACF;AACF;AAEO,SAAS,gBAAgB,QAAsD;AACpF,QAAM,MAAM,oBAAI,IAA6B;AAC7C,aAAW,SAAS,OAAO,SAAS;AAClC,QAAI,QAAQ,IAAI,IAAI,MAAM,KAAK;AAC/B,QAAI,CAAC,OAAO;AAAE,cAAQ,CAAC;AAAG,UAAI,IAAI,MAAM,OAAO,KAAK;AAAA,IAAG;AACvD,UAAM,KAAK,KAAK;AAEhB,QAAI,QAAQ,IAAI,IAAI,MAAM,KAAK;AAC/B,QAAI,CAAC,OAAO;AAAE,cAAQ,CAAC;AAAG,UAAI,IAAI,MAAM,OAAO,KAAK;AAAA,IAAG;AACvD,UAAM,KAAK,KAAK;AAAA,EAClB;AACA,SAAO;AACT;","names":["log"]}
|