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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-JIDIH7DS.js";
|
|
2
3
|
|
|
3
4
|
// src/core/staleness/staleness-detector.ts
|
|
4
5
|
import { statSync, existsSync } from "fs";
|
|
@@ -36,4 +37,4 @@ var StalenessDetector = class {
|
|
|
36
37
|
export {
|
|
37
38
|
StalenessDetector
|
|
38
39
|
};
|
|
39
|
-
//# sourceMappingURL=staleness-detector-
|
|
40
|
+
//# sourceMappingURL=staleness-detector-VSDPTPX7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/staleness/staleness-detector.ts"],"sourcesContent":["import { statSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface StalenessWarning {\n readonly staleFiles: string[];\n readonly message: string;\n}\n\nexport class StalenessDetector {\n private readonly projectRoot: string;\n private readonly indexDir: string;\n\n constructor(projectRoot: string, ctxoRoot: string) {\n this.projectRoot = projectRoot;\n this.indexDir = join(ctxoRoot, 'index');\n }\n\n check(indexedFiles: readonly string[]): StalenessWarning | undefined {\n if (!existsSync(this.indexDir)) return undefined;\n\n const staleFiles: string[] = [];\n\n for (const relativePath of indexedFiles) {\n const sourcePath = join(this.projectRoot, relativePath);\n const indexPath = join(this.indexDir, `${relativePath}.json`);\n\n if (!existsSync(sourcePath) || !existsSync(indexPath)) continue;\n\n try {\n const sourceMtime = Math.floor(statSync(sourcePath).mtimeMs / 1000);\n const indexMtime = Math.floor(statSync(indexPath).mtimeMs / 1000);\n\n if (sourceMtime > indexMtime) {\n staleFiles.push(relativePath);\n }\n } catch {\n // Skip files we can't stat\n }\n }\n\n if (staleFiles.length === 0) return undefined;\n\n return {\n staleFiles,\n message: `Index may be stale for ${staleFiles.length} file(s). Run \"ctxo index\" to refresh.`,\n };\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/core/staleness/staleness-detector.ts"],"sourcesContent":["import { statSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface StalenessWarning {\n readonly staleFiles: string[];\n readonly message: string;\n}\n\nexport class StalenessDetector {\n private readonly projectRoot: string;\n private readonly indexDir: string;\n\n constructor(projectRoot: string, ctxoRoot: string) {\n this.projectRoot = projectRoot;\n this.indexDir = join(ctxoRoot, 'index');\n }\n\n check(indexedFiles: readonly string[]): StalenessWarning | undefined {\n if (!existsSync(this.indexDir)) return undefined;\n\n const staleFiles: string[] = [];\n\n for (const relativePath of indexedFiles) {\n const sourcePath = join(this.projectRoot, relativePath);\n const indexPath = join(this.indexDir, `${relativePath}.json`);\n\n if (!existsSync(sourcePath) || !existsSync(indexPath)) continue;\n\n try {\n const sourceMtime = Math.floor(statSync(sourcePath).mtimeMs / 1000);\n const indexMtime = Math.floor(statSync(indexPath).mtimeMs / 1000);\n\n if (sourceMtime > indexMtime) {\n staleFiles.push(relativePath);\n }\n } catch {\n // Skip files we can't stat\n }\n }\n\n if (staleFiles.length === 0) return undefined;\n\n return {\n staleFiles,\n message: `Index may be stale for ${staleFiles.length} file(s). Run \"ctxo index\" to refresh.`,\n };\n }\n}\n"],"mappings":";;;;AAAA,SAAS,UAAU,kBAAkB;AACrC,SAAS,YAAY;AAOd,IAAM,oBAAN,MAAwB;AAAA,EACZ;AAAA,EACA;AAAA,EAEjB,YAAY,aAAqB,UAAkB;AACjD,SAAK,cAAc;AACnB,SAAK,WAAW,KAAK,UAAU,OAAO;AAAA,EACxC;AAAA,EAEA,MAAM,cAA+D;AACnE,QAAI,CAAC,WAAW,KAAK,QAAQ,EAAG,QAAO;AAEvC,UAAM,aAAuB,CAAC;AAE9B,eAAW,gBAAgB,cAAc;AACvC,YAAM,aAAa,KAAK,KAAK,aAAa,YAAY;AACtD,YAAM,YAAY,KAAK,KAAK,UAAU,GAAG,YAAY,OAAO;AAE5D,UAAI,CAAC,WAAW,UAAU,KAAK,CAAC,WAAW,SAAS,EAAG;AAEvD,UAAI;AACF,cAAM,cAAc,KAAK,MAAM,SAAS,UAAU,EAAE,UAAU,GAAI;AAClE,cAAM,aAAa,KAAK,MAAM,SAAS,SAAS,EAAE,UAAU,GAAI;AAEhE,YAAI,cAAc,YAAY;AAC5B,qBAAW,KAAK,YAAY;AAAA,QAC9B;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,QAAI,WAAW,WAAW,EAAG,QAAO;AAEpC,WAAO;AAAA,MACL;AAAA,MACA,SAAS,0BAA0B,WAAW,MAAM;AAAA,IACtD;AAAA,EACF;AACF;","names":[]}
|
package/llms-full.txt
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Ctxo MCP Server — Full Reference
|
|
2
|
+
|
|
3
|
+
> Dependency-aware, history-enriched code intelligence for AI coding assistants.
|
|
4
|
+
> npm: ctxo-mcp | GitHub: https://github.com/alperhankendi/Ctxo
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx ctxo-mcp index # Build codebase index (required once)
|
|
10
|
+
npx ctxo-mcp index --check # CI gate: fail if index stale
|
|
11
|
+
npx ctxo-mcp # Start MCP server (stdio transport)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
MCP client configuration:
|
|
15
|
+
```json
|
|
16
|
+
{ "mcpServers": { "ctxo": { "command": "npx", "args": ["-y", "ctxo-mcp"] } } }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Tool Reference (14 tools)
|
|
22
|
+
|
|
23
|
+
### get_logic_slice
|
|
24
|
+
Retrieve a symbol and all its transitive dependencies.
|
|
25
|
+
|
|
26
|
+
Parameters:
|
|
27
|
+
- symbolId (string, optional): Symbol ID (format: file::name::kind)
|
|
28
|
+
- symbolIds (string[], optional): Batch: array of symbol IDs
|
|
29
|
+
- level (number 1-4, default 3): L1=signature, L2=direct deps, L3=full closure, L4=with token budget
|
|
30
|
+
- intent (string, optional): Filter dependencies by keyword
|
|
31
|
+
|
|
32
|
+
When to use: Understanding what a symbol depends on (downstream view).
|
|
33
|
+
Instead of: get_blast_radius (upstream/impact), get_context_for_task (task-specific).
|
|
34
|
+
|
|
35
|
+
### get_blast_radius
|
|
36
|
+
Find all symbols that would break if a target changes.
|
|
37
|
+
|
|
38
|
+
Parameters:
|
|
39
|
+
- symbolId (string, required): Symbol ID
|
|
40
|
+
- confidence (enum: confirmed|likely|potential, optional): Filter by tier
|
|
41
|
+
- intent (string, optional): Filter by keyword
|
|
42
|
+
|
|
43
|
+
Returns: impactedSymbols array with symbolId, depth, riskScore, confidence, edgeKinds.
|
|
44
|
+
3-tier model: confirmed (calls/extends/implements), likely (uses/co-changed), potential (imports-only).
|
|
45
|
+
|
|
46
|
+
When to use: BEFORE modifying any function or class.
|
|
47
|
+
Instead of: get_logic_slice (forward deps), get_pr_impact (PR-level).
|
|
48
|
+
|
|
49
|
+
### get_architectural_overlay
|
|
50
|
+
Get project architectural layer map.
|
|
51
|
+
|
|
52
|
+
Parameters:
|
|
53
|
+
- layer (string, optional): Filter by layer name
|
|
54
|
+
|
|
55
|
+
Returns: Symbols grouped into Domain, Infrastructure, Adapter, Test, Composition, Configuration layers.
|
|
56
|
+
|
|
57
|
+
When to use: Onboarding to a new codebase, validating layer boundaries.
|
|
58
|
+
|
|
59
|
+
### get_why_context
|
|
60
|
+
Retrieve git commit history and anti-pattern warnings for a symbol.
|
|
61
|
+
|
|
62
|
+
Parameters:
|
|
63
|
+
- symbolId (string, required): Symbol ID
|
|
64
|
+
- maxCommits (number, optional): Limit to N most recent commits
|
|
65
|
+
|
|
66
|
+
Returns: commitHistory array + antiPatternWarnings (reverts, rollbacks, undo patterns).
|
|
67
|
+
|
|
68
|
+
When to use: Understanding WHY code was written this way, checking for problem history.
|
|
69
|
+
Pair with: get_change_intelligence (complexity/churn scores).
|
|
70
|
+
|
|
71
|
+
### get_change_intelligence
|
|
72
|
+
Retrieve complexity x churn composite score.
|
|
73
|
+
|
|
74
|
+
Parameters:
|
|
75
|
+
- symbolId (string, required): Symbol ID
|
|
76
|
+
|
|
77
|
+
Returns: complexity (0-1), churn (0-1), composite (0-1), band (low|medium|high|critical).
|
|
78
|
+
|
|
79
|
+
When to use: Identifying refactoring targets, assessing modification risk.
|
|
80
|
+
|
|
81
|
+
### find_dead_code
|
|
82
|
+
Find unreachable symbols and files.
|
|
83
|
+
|
|
84
|
+
Parameters:
|
|
85
|
+
- includeTests (boolean, default false): Include test files
|
|
86
|
+
- intent (string, optional): Filter results by keyword
|
|
87
|
+
|
|
88
|
+
Returns: deadSymbols (with confidence 1.0/0.9/0.7 and reason), deadFiles, unusedExports, scaffolding markers.
|
|
89
|
+
|
|
90
|
+
When to use: Cleanup, refactoring, confirming code is truly unused.
|
|
91
|
+
Instead of: find_importers (specific symbol reverse lookup).
|
|
92
|
+
|
|
93
|
+
### get_context_for_task
|
|
94
|
+
Get task-optimized context for a symbol.
|
|
95
|
+
|
|
96
|
+
Parameters:
|
|
97
|
+
- symbolId (string, required): Symbol ID
|
|
98
|
+
- taskType (enum: fix|extend|refactor|understand, required): Determines context mix
|
|
99
|
+
- tokenBudget (number, default 4000): Max tokens
|
|
100
|
+
|
|
101
|
+
Task types:
|
|
102
|
+
- fix: History + anti-patterns + direct deps (bug investigation)
|
|
103
|
+
- extend: Deps + blast radius + interfaces (adding features)
|
|
104
|
+
- refactor: Importers + complexity + churn (restructuring)
|
|
105
|
+
- understand: Full slice + architecture (learning)
|
|
106
|
+
|
|
107
|
+
When to use: BEST starting point when you know the symbol AND your intent.
|
|
108
|
+
|
|
109
|
+
### get_ranked_context
|
|
110
|
+
Search and rank symbols by relevance to a query.
|
|
111
|
+
|
|
112
|
+
Parameters:
|
|
113
|
+
- query (string, required): Natural language search query
|
|
114
|
+
- tokenBudget (number, default 4000): Max tokens for results
|
|
115
|
+
- strategy (enum: combined|dependency|importance, default combined): Ranking strategy
|
|
116
|
+
|
|
117
|
+
Returns: Symbols ranked by BM25 relevance + PageRank importance, packed within budget.
|
|
118
|
+
|
|
119
|
+
When to use: Have a question/topic but don't know which symbol to look at.
|
|
120
|
+
Instead of: search_symbols (exact name/regex search).
|
|
121
|
+
|
|
122
|
+
### search_symbols
|
|
123
|
+
Search symbols by name or regex pattern.
|
|
124
|
+
|
|
125
|
+
Parameters:
|
|
126
|
+
- pattern (string, required): Substring or regex pattern
|
|
127
|
+
- kind (enum: function|class|interface|method|variable|type, optional): Filter by kind
|
|
128
|
+
- filePattern (string, optional): Filter by file path substring
|
|
129
|
+
- limit (number 1-100, default 25): Max results
|
|
130
|
+
|
|
131
|
+
When to use: Know (part of) the symbol name, need its ID for other tools.
|
|
132
|
+
Instead of: get_ranked_context (semantic/relevance search).
|
|
133
|
+
|
|
134
|
+
### get_changed_symbols
|
|
135
|
+
Get symbols in recently changed files.
|
|
136
|
+
|
|
137
|
+
Parameters:
|
|
138
|
+
- since (string, default HEAD~1): Git ref to diff against
|
|
139
|
+
- maxFiles (number, default 50): Max files to process
|
|
140
|
+
|
|
141
|
+
When to use: See what was modified in recent commits.
|
|
142
|
+
Instead of: get_pr_impact (full PR risk assessment).
|
|
143
|
+
|
|
144
|
+
### find_importers
|
|
145
|
+
Find all symbols that depend on a given symbol (reverse dependency).
|
|
146
|
+
|
|
147
|
+
Parameters:
|
|
148
|
+
- symbolId (string, required): Symbol ID
|
|
149
|
+
- edgeKinds (string[], optional): Filter by imports|calls|extends|implements|uses
|
|
150
|
+
- transitive (boolean, default false): Follow transitive reverse edges
|
|
151
|
+
- maxDepth (number 1-10, default 5): Max BFS depth
|
|
152
|
+
- intent (string, optional): Filter by keyword
|
|
153
|
+
|
|
154
|
+
When to use: Check if safe to modify/delete, find all consumers.
|
|
155
|
+
Instead of: get_blast_radius (aggregated impact with risk scores).
|
|
156
|
+
|
|
157
|
+
### get_class_hierarchy
|
|
158
|
+
Get class inheritance hierarchy.
|
|
159
|
+
|
|
160
|
+
Parameters:
|
|
161
|
+
- symbolId (string, optional): Root symbol (omit for full project)
|
|
162
|
+
- direction (enum: ancestors|descendants|both, default both): Traversal direction
|
|
163
|
+
|
|
164
|
+
When to use: OOP code, understanding type relationships before modifying base class/interface.
|
|
165
|
+
|
|
166
|
+
### get_symbol_importance
|
|
167
|
+
Rank symbols by PageRank centrality.
|
|
168
|
+
|
|
169
|
+
Parameters:
|
|
170
|
+
- limit (number 1-200, default 25): Max results
|
|
171
|
+
- kind (enum: function|class|interface|method|variable|type, optional): Filter
|
|
172
|
+
- filePattern (string, optional): Filter by file path
|
|
173
|
+
- damping (number 0-1, default 0.85): PageRank damping factor
|
|
174
|
+
|
|
175
|
+
When to use: Identify most critical symbols, high-risk modification targets.
|
|
176
|
+
Instead of: find_dead_code (unused/unimportant code).
|
|
177
|
+
|
|
178
|
+
### get_pr_impact
|
|
179
|
+
Analyze PR impact in a single call.
|
|
180
|
+
|
|
181
|
+
Parameters:
|
|
182
|
+
- since (string, default HEAD~1): Git ref to diff against
|
|
183
|
+
- maxFiles (number, default 50): Max files
|
|
184
|
+
- confidence (enum: confirmed|likely|potential, optional): Filter tier
|
|
185
|
+
|
|
186
|
+
Returns: changedFiles, changedSymbols, totalImpact, riskLevel (low|medium|high), per-file blast radius, co-change data.
|
|
187
|
+
|
|
188
|
+
When to use: FIRST tool when reviewing a PR or evaluating recent commits.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Tool Selection Guide
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
Reviewing a PR or recent changes?
|
|
196
|
+
-> get_pr_impact (single call, full risk assessment)
|
|
197
|
+
|
|
198
|
+
About to modify a function or class?
|
|
199
|
+
-> get_blast_radius (what breaks if I change this?)
|
|
200
|
+
-> then get_why_context (any history of problems?)
|
|
201
|
+
|
|
202
|
+
Need to understand what a symbol does?
|
|
203
|
+
-> get_context_for_task(taskType: "understand")
|
|
204
|
+
-> or get_logic_slice (L2 for overview, L3 for full closure)
|
|
205
|
+
|
|
206
|
+
Fixing a bug?
|
|
207
|
+
-> get_context_for_task(taskType: "fix")
|
|
208
|
+
|
|
209
|
+
Adding a feature / extending code?
|
|
210
|
+
-> get_context_for_task(taskType: "extend")
|
|
211
|
+
|
|
212
|
+
Refactoring?
|
|
213
|
+
-> get_context_for_task(taskType: "refactor")
|
|
214
|
+
|
|
215
|
+
Don't know the symbol name?
|
|
216
|
+
-> search_symbols (by name/regex)
|
|
217
|
+
-> get_ranked_context (by natural language query)
|
|
218
|
+
|
|
219
|
+
Onboarding to a new codebase?
|
|
220
|
+
-> get_architectural_overlay (layer map)
|
|
221
|
+
-> get_symbol_importance (most critical symbols)
|
|
222
|
+
|
|
223
|
+
Cleaning up code?
|
|
224
|
+
-> find_dead_code (unused symbols)
|
|
225
|
+
-> get_change_intelligence (complexity hotspots)
|
|
226
|
+
|
|
227
|
+
Checking if safe to delete/rename?
|
|
228
|
+
-> find_importers (who depends on this?)
|
|
229
|
+
-> get_blast_radius (full impact)
|
|
230
|
+
|
|
231
|
+
Working with class hierarchies?
|
|
232
|
+
-> get_class_hierarchy (extends/implements tree)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Cross-Cutting Features
|
|
238
|
+
|
|
239
|
+
### Response Envelope (_meta)
|
|
240
|
+
All tool responses include:
|
|
241
|
+
```json
|
|
242
|
+
{ "_meta": { "totalItems": 50, "returnedItems": 25, "truncated": true, "totalBytes": 12400, "hint": "Use search_symbols to narrow results." } }
|
|
243
|
+
```
|
|
244
|
+
- Default truncation threshold: 8KB
|
|
245
|
+
- Configurable via CTXO_RESPONSE_LIMIT environment variable
|
|
246
|
+
|
|
247
|
+
### Intent Filtering
|
|
248
|
+
4 tools accept `intent` parameter: get_blast_radius, get_logic_slice, find_importers, find_dead_code.
|
|
249
|
+
- Keywords extracted from intent string, matched case-insensitively against symbolId, file, name, kind, edgeKind, reason
|
|
250
|
+
- Multiple keywords use OR logic
|
|
251
|
+
- No intent = full results (backward compatible)
|
|
252
|
+
|
|
253
|
+
### Symbol ID Format
|
|
254
|
+
All symbols use deterministic IDs: `"<relativePath>::<name>::<kind>"`
|
|
255
|
+
- Example: `"src/core/graph/symbol-graph.ts::SymbolGraph::class"`
|
|
256
|
+
- Valid kinds: function, class, interface, method, variable, type
|
|
257
|
+
- Valid edge kinds: imports, calls, extends, implements, uses
|
|
258
|
+
|
|
259
|
+
### Resources
|
|
260
|
+
- `ctxo://status` — Health check resource (confirms server is running)
|
package/llms.txt
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Ctxo MCP Server
|
|
2
|
+
|
|
3
|
+
> Dependency-aware, history-enriched code intelligence for AI coding assistants.
|
|
4
|
+
|
|
5
|
+
Ctxo is a Model Context Protocol (MCP) server that gives AI agents structured context about codebases: symbol graphs, blast radius, architectural overlays, change intelligence, dead code detection, and PR impact analysis.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npx ctxo-mcp index # Build codebase index
|
|
11
|
+
npx ctxo-mcp # Start MCP server (stdio)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 14 Tools
|
|
15
|
+
|
|
16
|
+
- get_logic_slice: Symbol + transitive dependencies (L1-L4 detail levels)
|
|
17
|
+
- get_blast_radius: What breaks if a symbol changes (3-tier: confirmed/likely/potential)
|
|
18
|
+
- get_architectural_overlay: Project layer map (Domain/Infrastructure/Adapter)
|
|
19
|
+
- get_why_context: Git commit intent + anti-pattern warnings (reverts, rollbacks)
|
|
20
|
+
- get_change_intelligence: Complexity x churn composite score
|
|
21
|
+
- find_dead_code: Unreachable symbols and files
|
|
22
|
+
- get_context_for_task: Task-optimized context (fix/extend/refactor/understand)
|
|
23
|
+
- get_ranked_context: BM25 + PageRank search within token budget
|
|
24
|
+
- search_symbols: Symbol name/regex search across index
|
|
25
|
+
- get_changed_symbols: Symbols in recently changed files (git diff)
|
|
26
|
+
- find_importers: Reverse dependency lookup ("who uses this?")
|
|
27
|
+
- get_class_hierarchy: Class inheritance tree (ancestors + descendants)
|
|
28
|
+
- get_symbol_importance: PageRank centrality ranking
|
|
29
|
+
- get_pr_impact: Full PR risk assessment in a single call
|
|
30
|
+
|
|
31
|
+
## Tool Selection Guide
|
|
32
|
+
|
|
33
|
+
- Reviewing a PR? -> get_pr_impact
|
|
34
|
+
- About to modify code? -> get_blast_radius
|
|
35
|
+
- Understanding a symbol? -> get_context_for_task(taskType: "understand")
|
|
36
|
+
- Fixing a bug? -> get_context_for_task(taskType: "fix")
|
|
37
|
+
- Refactoring? -> get_context_for_task(taskType: "refactor")
|
|
38
|
+
- Don't know the symbol name? -> search_symbols or get_ranked_context
|
|
39
|
+
- Finding unused code? -> find_dead_code
|
|
40
|
+
- Safe to delete? -> find_importers
|
|
41
|
+
|
|
42
|
+
## Key Concepts
|
|
43
|
+
|
|
44
|
+
- Symbol ID format: "file::name::kind" (e.g., "src/foo.ts::myFn::function")
|
|
45
|
+
- All responses include _meta: { totalItems, returnedItems, truncated, totalBytes }
|
|
46
|
+
- 4 tools support intent filtering: get_blast_radius, get_logic_slice, find_importers, find_dead_code
|
|
47
|
+
- Large responses auto-truncated at 8KB (configurable via CTXO_RESPONSE_LIMIT)
|
|
48
|
+
|
|
49
|
+
## Links
|
|
50
|
+
|
|
51
|
+
- npm: https://www.npmjs.com/package/ctxo-mcp
|
|
52
|
+
- GitHub: https://github.com/alperhankendi/Ctxo
|
|
53
|
+
- Full reference: llms-full.txt
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ctxo-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "MCP server delivering dependency-aware, history-enriched context for codebases",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist/",
|
|
14
|
-
"README.md"
|
|
14
|
+
"README.md",
|
|
15
|
+
"llms.txt",
|
|
16
|
+
"llms-full.txt"
|
|
15
17
|
],
|
|
16
18
|
"main": "dist/index.js",
|
|
17
19
|
"types": "dist/index.d.ts",
|
|
@@ -41,6 +43,9 @@
|
|
|
41
43
|
"chokidar": "^5.0.0",
|
|
42
44
|
"simple-git": "^3.27.0",
|
|
43
45
|
"sql.js": "^1.14.1",
|
|
46
|
+
"tree-sitter": "^0.21.1",
|
|
47
|
+
"tree-sitter-c-sharp": "^0.23.1",
|
|
48
|
+
"tree-sitter-go": "^0.23.4",
|
|
44
49
|
"ts-morph": "^27.0.2",
|
|
45
50
|
"zod": "^4.3.6"
|
|
46
51
|
},
|
|
@@ -1 +0,0 @@
|
|
|
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')) {\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 })\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});\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// ── 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 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/storage/sqlite-storage-adapter.ts","../src/adapters/git/simple-git-adapter.ts","../src/core/why-context/revert-detector.ts"],"sourcesContent":["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';\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 console.error('[ctxo:sqlite] Corrupt DB detected, rebuilding from JSON index');\n this.db = new SQL.Database();\n this.createTables();\n this.rebuildFromJson();\n }\n } else {\n this.db = new SQL.Database();\n this.createTables();\n this.rebuildFromJson();\n }\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 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 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 console.error(`[ctxo:sqlite] 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';\n\nexport class SimpleGitAdapter implements IGitPort {\n private readonly git: SimpleGit;\n\n constructor(projectRoot: string) {\n this.git = simpleGit(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): Promise<CommitRecord[]> {\n try {\n const log = await this.git.log({ file: filePath, '--follow': null });\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 console.error(`[ctxo:git] Failed to get history for ${filePath}: ${(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 console.error(`[ctxo:git] 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"],"mappings":";;;;;;AAAA,OAAO,eAAkC;AACzC,SAAS,cAAc,eAAe,YAAY,iBAAiB;AACnE,SAAS,MAAM,eAAe;AAKvB,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,gBAAQ,MAAM,+DAA+D;AAC7E,aAAK,KAAK,IAAI,IAAI,SAAS;AAC3B,aAAK,aAAa;AAClB,aAAK,gBAAgB;AAAA,MACvB;AAAA,IACF,OAAO;AACL,WAAK,KAAK,IAAI,IAAI,SAAS;AAC3B,WAAK,aAAa;AAClB,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;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,cAAQ,MAAM,uCAAwC,IAAc,OAAO,EAAE;AAAA,IAC/E;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;;;ACxWA,SAAS,iBAAiC;AAInC,IAAM,mBAAN,MAA2C;AAAA,EAC/B;AAAA,EAEjB,YAAY,aAAqB;AAC/B,SAAK,MAAM,UAAU,WAAW;AAAA,EAClC;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,UAA2C;AAChE,QAAI;AACF,YAAM,MAAM,MAAM,KAAK,IAAI,IAAI,EAAE,MAAM,UAAU,YAAY,KAAK,CAAC;AAEnE,aAAO,IAAI,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,cAAQ,MAAM,wCAAwC,QAAQ,KAAM,IAAc,OAAO,EAAE;AAC3F,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,UAAsC;AACvD,QAAI;AACF,YAAM,MAAM,MAAM,KAAK,IAAI,IAAI,EAAE,MAAM,UAAU,YAAY,KAAK,CAAC;AAEnE,aAAO;AAAA,QACL;AAAA,QACA,aAAa,IAAI;AAAA,MACnB;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,sCAAsC,QAAQ,KAAM,IAAc,OAAO,EAAE;AACzF,aAAO,EAAE,UAAU,aAAa,EAAE;AAAA,IACpC;AAAA,EACF;AACF;;;AC9CA,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;","names":[]}
|