@eeymoo/context-todos 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +261 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +88 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/db.d.ts +29 -0
- package/dist/src/mcp/db.d.ts.map +1 -0
- package/dist/src/mcp/db.js +111 -0
- package/dist/src/mcp/db.js.map +1 -0
- package/dist/src/mcp/gitignore.d.ts +6 -0
- package/dist/src/mcp/gitignore.d.ts.map +1 -0
- package/dist/src/mcp/gitignore.js +32 -0
- package/dist/src/mcp/gitignore.js.map +1 -0
- package/dist/src/mcp/index.d.ts +7 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +87 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/scanner.d.ts +7 -0
- package/dist/src/mcp/scanner.d.ts.map +1 -0
- package/dist/src/mcp/scanner.js +45 -0
- package/dist/src/mcp/scanner.js.map +1 -0
- package/dist/src/mcp/tools/get-todo-stats.d.ts +3 -0
- package/dist/src/mcp/tools/get-todo-stats.d.ts.map +1 -0
- package/dist/src/mcp/tools/get-todo-stats.js +37 -0
- package/dist/src/mcp/tools/get-todo-stats.js.map +1 -0
- package/dist/src/mcp/tools/list-extensions.d.ts +3 -0
- package/dist/src/mcp/tools/list-extensions.d.ts.map +1 -0
- package/dist/src/mcp/tools/list-extensions.js +28 -0
- package/dist/src/mcp/tools/list-extensions.js.map +1 -0
- package/dist/src/mcp/tools/list-todos.d.ts +3 -0
- package/dist/src/mcp/tools/list-todos.d.ts.map +1 -0
- package/dist/src/mcp/tools/list-todos.js +60 -0
- package/dist/src/mcp/tools/list-todos.js.map +1 -0
- package/dist/src/mcp/tools/scan-directory.d.ts +3 -0
- package/dist/src/mcp/tools/scan-directory.d.ts.map +1 -0
- package/dist/src/mcp/tools/scan-directory.js +78 -0
- package/dist/src/mcp/tools/scan-directory.js.map +1 -0
- package/dist/src/mcp/tools/scan-file.d.ts +3 -0
- package/dist/src/mcp/tools/scan-file.d.ts.map +1 -0
- package/dist/src/mcp/tools/scan-file.js +63 -0
- package/dist/src/mcp/tools/scan-file.js.map +1 -0
- package/dist/src/mcp/tools/watch.d.ts +4 -0
- package/dist/src/mcp/tools/watch.d.ts.map +1 -0
- package/dist/src/mcp/tools/watch.js +110 -0
- package/dist/src/mcp/tools/watch.js.map +1 -0
- package/dist/src/mcp/types.d.ts +35 -0
- package/dist/src/mcp/types.d.ts.map +1 -0
- package/dist/src/mcp/types.js +23 -0
- package/dist/src/mcp/types.js.map +1 -0
- package/dist/src/mcp/watcher.d.ts +7 -0
- package/dist/src/mcp/watcher.d.ts.map +1 -0
- package/dist/src/mcp/watcher.js +74 -0
- package/dist/src/mcp/watcher.js.map +1 -0
- package/dist/tests/db.test.d.ts +2 -0
- package/dist/tests/db.test.d.ts.map +1 -0
- package/dist/tests/db.test.js +339 -0
- package/dist/tests/db.test.js.map +1 -0
- package/dist/tests/mcp/index.test.d.ts +2 -0
- package/dist/tests/mcp/index.test.d.ts.map +1 -0
- package/dist/tests/mcp/index.test.js +276 -0
- package/dist/tests/mcp/index.test.js.map +1 -0
- package/dist/tests/scanner.test.d.ts +2 -0
- package/dist/tests/scanner.test.d.ts.map +1 -0
- package/dist/tests/scanner.test.js +101 -0
- package/dist/tests/scanner.test.js.map +1 -0
- package/dist/tests/tools/get-todo-stats.test.d.ts +2 -0
- package/dist/tests/tools/get-todo-stats.test.d.ts.map +1 -0
- package/dist/tests/tools/get-todo-stats.test.js +256 -0
- package/dist/tests/tools/get-todo-stats.test.js.map +1 -0
- package/dist/tests/tools/list-extensions.test.d.ts +2 -0
- package/dist/tests/tools/list-extensions.test.d.ts.map +1 -0
- package/dist/tests/tools/list-extensions.test.js +123 -0
- package/dist/tests/tools/list-extensions.test.js.map +1 -0
- package/dist/tests/tools/list-todos.test.d.ts +2 -0
- package/dist/tests/tools/list-todos.test.d.ts.map +1 -0
- package/dist/tests/tools/list-todos.test.js +279 -0
- package/dist/tests/tools/list-todos.test.js.map +1 -0
- package/dist/tests/tools/scan-directory.test.d.ts +2 -0
- package/dist/tests/tools/scan-directory.test.d.ts.map +1 -0
- package/dist/tests/tools/scan-directory.test.js +194 -0
- package/dist/tests/tools/scan-directory.test.js.map +1 -0
- package/dist/tests/tools/scan-file.test.d.ts +2 -0
- package/dist/tests/tools/scan-file.test.d.ts.map +1 -0
- package/dist/tests/tools/scan-file.test.js +176 -0
- package/dist/tests/tools/scan-file.test.js.map +1 -0
- package/dist/tests/tools/watch.test.d.ts +2 -0
- package/dist/tests/tools/watch.test.d.ts.map +1 -0
- package/dist/tests/tools/watch.test.js +286 -0
- package/dist/tests/tools/watch.test.js.map +1 -0
- package/dist/tests/watcher.test.d.ts +2 -0
- package/dist/tests/watcher.test.d.ts.map +1 -0
- package/dist/tests/watcher.test.js +372 -0
- package/dist/tests/watcher.test.js.map +1 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +15 -0
- package/dist/vitest.config.js.map +1 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Context-Todos
|
|
2
|
+
|
|
3
|
+
[简体中文](./README_ZH.md) | English
|
|
4
|
+
|
|
5
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that scans and manages TODO/FIXME/HACK/XXX comments in your codebase.
|
|
6
|
+
|
|
7
|
+
## What It Does
|
|
8
|
+
|
|
9
|
+
Context-Todos helps you track and manage code annotations across your entire project. It scans source files for common comment tags and provides tools to query, watch, and analyze them through the MCP protocol.
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- **Scan Files**: Extract TODO comments from a single file
|
|
14
|
+
- **Scan Directories**: Recursively scan all supported files in a directory
|
|
15
|
+
- **List Extensions**: View all supported file extensions
|
|
16
|
+
- **Watch Mode**: Real-time monitoring of file changes (max/labs mode)
|
|
17
|
+
- **TODO Database**: Persistent storage of all TODO items (max/labs mode)
|
|
18
|
+
- **Statistics**: Get TODO statistics by tag and file (labs mode)
|
|
19
|
+
|
|
20
|
+
### Supported TODO Tags
|
|
21
|
+
|
|
22
|
+
| Tag | Usage |
|
|
23
|
+
|-----|-------|
|
|
24
|
+
| `TODO` | General tasks to be done |
|
|
25
|
+
| `FIXME` | Code that needs to be fixed |
|
|
26
|
+
| `HACK` | Temporary workarounds |
|
|
27
|
+
| `XXX` | Warning or problematic code |
|
|
28
|
+
|
|
29
|
+
### Supported File Extensions
|
|
30
|
+
|
|
31
|
+
| Category | Extensions |
|
|
32
|
+
|----------|------------|
|
|
33
|
+
| JavaScript/TypeScript | `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs` |
|
|
34
|
+
| Python | `.py` |
|
|
35
|
+
| Ruby | `.rb` |
|
|
36
|
+
| Java | `.java` |
|
|
37
|
+
| Go | `.go` |
|
|
38
|
+
| Rust | `.rs` |
|
|
39
|
+
| C/C++ | `.c`, `.cpp`, `.h`, `.hpp` |
|
|
40
|
+
| C# | `.cs` |
|
|
41
|
+
| PHP | `.php` |
|
|
42
|
+
| Swift | `.swift` |
|
|
43
|
+
| Kotlin/Scala | `.kt`, `.scala` |
|
|
44
|
+
| Shell | `.sh`, `.bash` |
|
|
45
|
+
| Frontend | `.css`, `.scss`, `.less`, `.html`, `.vue`, `.svelte` |
|
|
46
|
+
| Config | `.yaml`, `.yml`, `.toml`, `.ini`, `.cfg` |
|
|
47
|
+
| Database | `.sql`, `.lua` |
|
|
48
|
+
| Other | `.r`, `.m`, `.mm`, `.pl`, `.pm`, `.ex`, `.exs`, `.erl`, `.hs`, `.elm`, `.clj`, `.cljs`, `.tf`, `.hcl`, `.dockerfile` |
|
|
49
|
+
|
|
50
|
+
## How to Use
|
|
51
|
+
|
|
52
|
+
### Quick Start (npx)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Run directly with npx (Stable Standard)
|
|
56
|
+
npx @eeymoo/context-todos mcp
|
|
57
|
+
|
|
58
|
+
# Stable Max (with watching)
|
|
59
|
+
npx @eeymoo/context-todos mcp --max
|
|
60
|
+
|
|
61
|
+
# Labs Standard
|
|
62
|
+
npx @eeymoo/context-todos mcp --labs
|
|
63
|
+
|
|
64
|
+
# Labs Max (all features including experimental)
|
|
65
|
+
npx @eeymoo/context-todos mcp --labs --max
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Installation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Install globally
|
|
72
|
+
npm install -g @eeymoo/context-todos
|
|
73
|
+
|
|
74
|
+
# Or with pnpm
|
|
75
|
+
pnpm add -g @eeymoo/context-todos
|
|
76
|
+
|
|
77
|
+
# Then run
|
|
78
|
+
context-todos mcp
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Configure MCP Client
|
|
82
|
+
|
|
83
|
+
Add to your MCP client configuration (e.g., Claude Desktop, Cursor, etc.):
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"context-todos": {
|
|
89
|
+
"command": "npx",
|
|
90
|
+
"args": ["-y", "@eeymoo/context-todos", "mcp"]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or with max/labs mode:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"mcpServers": {
|
|
101
|
+
"context-todos": {
|
|
102
|
+
"command": "npx",
|
|
103
|
+
"args": ["-y", "@eeymoo/context-todos", "mcp", "--max"]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Development
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Clone the repository
|
|
113
|
+
git clone https://github.com/eeymoo/context-todos.git
|
|
114
|
+
cd context-todos
|
|
115
|
+
|
|
116
|
+
# Install dependencies
|
|
117
|
+
pnpm install
|
|
118
|
+
|
|
119
|
+
# Build
|
|
120
|
+
pnpm build
|
|
121
|
+
|
|
122
|
+
# Run in development
|
|
123
|
+
pnpm dev
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Server Modes
|
|
127
|
+
|
|
128
|
+
The product has two categories of features:
|
|
129
|
+
|
|
130
|
+
- **Stable**: Production-ready features with `Standard` and `Max` variants
|
|
131
|
+
- **Labs**: Experimental features, also with `Standard` and `Max` variants
|
|
132
|
+
|
|
133
|
+
| Category | Mode | CLI | Description | Tools |
|
|
134
|
+
|----------|------|-----|-------------|-------|
|
|
135
|
+
| Stable | Standard | (default) | Basic scanning tools | `scan-file`, `scan-directory`, `list-supported-extensions` |
|
|
136
|
+
| Stable | Max | `--max` | Standard + file watching & database | All standard + `watch`, `unwatch`, `list-todos` |
|
|
137
|
+
| Labs | Standard | `--labs` | Experimental standard mode | Same as Stable Standard |
|
|
138
|
+
| Labs | Max | `--labs --max` | All features including experimental | All max + `get-todo-stats` |
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Stable Standard (default)
|
|
142
|
+
npx @eeymoo/context-todos mcp
|
|
143
|
+
|
|
144
|
+
# Stable Max (with watching)
|
|
145
|
+
npx @eeymoo/context-todos mcp --max
|
|
146
|
+
|
|
147
|
+
# Labs Standard
|
|
148
|
+
npx @eeymoo/context-todos mcp --labs
|
|
149
|
+
|
|
150
|
+
# Labs Max (all features)
|
|
151
|
+
npx @eeymoo/context-todos mcp --labs --max
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### MCP Tools
|
|
155
|
+
|
|
156
|
+
#### `scan-file`
|
|
157
|
+
|
|
158
|
+
Scan a single file for TODO comments.
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"name": "scan-file",
|
|
163
|
+
"arguments": {
|
|
164
|
+
"path": "/path/to/file.ts"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### `scan-directory`
|
|
170
|
+
|
|
171
|
+
Recursively scan a directory for TODO comments.
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"name": "scan-directory",
|
|
176
|
+
"arguments": {
|
|
177
|
+
"path": "/path/to/project",
|
|
178
|
+
"extensions": [".ts", ".js"]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### `list-supported-extensions`
|
|
184
|
+
|
|
185
|
+
List all supported file extensions.
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"name": "list-supported-extensions",
|
|
190
|
+
"arguments": {}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### `watch` (max / labs --max)
|
|
195
|
+
|
|
196
|
+
Start watching a directory for file changes.
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"name": "watch",
|
|
201
|
+
"arguments": {
|
|
202
|
+
"path": "/path/to/project",
|
|
203
|
+
"extensions": [".ts", ".js"]
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### `unwatch` (max / labs --max)
|
|
209
|
+
|
|
210
|
+
Stop watching.
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"name": "unwatch",
|
|
215
|
+
"arguments": {}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### `list-todos` (max / labs --max)
|
|
220
|
+
|
|
221
|
+
List all tracked TODOs from the database.
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"name": "list-todos",
|
|
226
|
+
"arguments": {
|
|
227
|
+
"tag": "TODO",
|
|
228
|
+
"file": "src/index.ts"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### `get-todo-stats` (labs --max only)
|
|
234
|
+
|
|
235
|
+
Get TODO statistics grouped by tag and file.
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"name": "get-todo-stats",
|
|
240
|
+
"arguments": {}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Scripts
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
pnpm dev # Run in development mode
|
|
248
|
+
pnpm build # Build for production
|
|
249
|
+
pnpm test # Run tests
|
|
250
|
+
pnpm test:watch # Run tests with watch mode
|
|
251
|
+
pnpm inspector # Run with MCP inspector (for debugging)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Requirements
|
|
255
|
+
|
|
256
|
+
- Node.js >= 18.0.0
|
|
257
|
+
- pnpm (recommended)
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
ISC
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
5
|
+
import { createServer } from './mcp/index.js';
|
|
6
|
+
import { createServer as createNodeHttpServer } from 'node:http';
|
|
7
|
+
import { consola } from 'consola';
|
|
8
|
+
const program = new Command();
|
|
9
|
+
const logger = consola;
|
|
10
|
+
program
|
|
11
|
+
.name('context-todos')
|
|
12
|
+
.description('AI context-aware TODO tracker')
|
|
13
|
+
.version('1.0.0');
|
|
14
|
+
program
|
|
15
|
+
.command('mcp')
|
|
16
|
+
.description('Start MCP server')
|
|
17
|
+
.option('-p, --port <number>', 'Port to run the MCP server on (enables SSE mode)')
|
|
18
|
+
.option('-w, --watch <path>', 'Path to watch for changes', '.')
|
|
19
|
+
.option('--stdio', 'Force stdio mode instead of SSE')
|
|
20
|
+
.option('--max', 'Enable Max mode (watcher + database persistence)')
|
|
21
|
+
.option('--labs', 'Enable Labs mode (experimental features)')
|
|
22
|
+
.option('--use-gitignore', 'Use .gitignore to filter files', false)
|
|
23
|
+
.option('--gitignore-path <path>', 'Custom path to gitignore file (default: .gitignore)')
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
let mode;
|
|
26
|
+
if (options.labs && options.max) {
|
|
27
|
+
mode = 'labs-max';
|
|
28
|
+
}
|
|
29
|
+
else if (options.labs) {
|
|
30
|
+
mode = 'labs-standard';
|
|
31
|
+
}
|
|
32
|
+
else if (options.max) {
|
|
33
|
+
mode = 'max';
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
mode = 'standard';
|
|
37
|
+
}
|
|
38
|
+
const useSSE = options.port !== undefined && !options.stdio;
|
|
39
|
+
const serverOptions = {
|
|
40
|
+
mode,
|
|
41
|
+
watchPath: options.watch,
|
|
42
|
+
useGitignore: options.useGitignore,
|
|
43
|
+
...(options.gitignorePath && { gitignorePath: options.gitignorePath }),
|
|
44
|
+
};
|
|
45
|
+
const { server, totalTodos } = await createServer(serverOptions);
|
|
46
|
+
if (mode !== 'standard' && mode !== 'labs-standard') {
|
|
47
|
+
const modeMsg = mode === 'labs-max'
|
|
48
|
+
? 'Starting in labs-max mode (all features + experimental)'
|
|
49
|
+
: 'Starting in max mode (watcher + database)';
|
|
50
|
+
logger.info(modeMsg);
|
|
51
|
+
logger.info(`Initial scan complete: ${totalTodos} TODO(s) persisted`);
|
|
52
|
+
}
|
|
53
|
+
if (useSSE) {
|
|
54
|
+
const port = Number(options.port);
|
|
55
|
+
let sseTransport;
|
|
56
|
+
const httpServer = createNodeHttpServer(async (req, res) => {
|
|
57
|
+
if (req.method === 'GET' && req.url === '/sse') {
|
|
58
|
+
sseTransport = new SSEServerTransport('/messages', res);
|
|
59
|
+
await server.connect(sseTransport);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (req.method === 'POST' && req.url === '/messages') {
|
|
63
|
+
if (!sseTransport) {
|
|
64
|
+
res.writeHead(400);
|
|
65
|
+
res.end('No SSE connection established');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const body = [];
|
|
69
|
+
req.on('data', (chunk) => body.push(chunk));
|
|
70
|
+
req.on('end', async () => {
|
|
71
|
+
await sseTransport.handlePostMessage(req, res, Buffer.concat(body).toString());
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
res.writeHead(404);
|
|
76
|
+
res.end('Not found');
|
|
77
|
+
});
|
|
78
|
+
httpServer.listen(port, () => {
|
|
79
|
+
logger.success(`SSE server running on http://localhost:${port}/sse`);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const transport = new StdioServerTransport();
|
|
84
|
+
await server.connect(transport);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
program.parse();
|
|
88
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Client } from '@libsql/client';
|
|
2
|
+
import type { TodoItem } from './types.js';
|
|
3
|
+
export declare function initDb(basePath?: string): Promise<Client>;
|
|
4
|
+
export declare function getDb(): Client;
|
|
5
|
+
export declare function syncFileTodos(file: string, todos: TodoItem[]): Promise<void>;
|
|
6
|
+
export declare function removeFileTodos(file: string): Promise<void>;
|
|
7
|
+
export interface TodoQuery {
|
|
8
|
+
tag?: string | undefined;
|
|
9
|
+
file?: string | undefined;
|
|
10
|
+
limit?: number | undefined;
|
|
11
|
+
offset?: number | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare function queryTodos(query?: TodoQuery): Promise<{
|
|
14
|
+
todos: TodoItem[];
|
|
15
|
+
total: number;
|
|
16
|
+
}>;
|
|
17
|
+
export interface TodoStats {
|
|
18
|
+
total: number;
|
|
19
|
+
byTag: {
|
|
20
|
+
tag: string;
|
|
21
|
+
count: number;
|
|
22
|
+
}[];
|
|
23
|
+
byFile: {
|
|
24
|
+
file: string;
|
|
25
|
+
count: number;
|
|
26
|
+
}[];
|
|
27
|
+
}
|
|
28
|
+
export declare function getTodoStats(): Promise<TodoStats>;
|
|
29
|
+
//# sourceMappingURL=db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../../src/mcp/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM3C,wBAAsB,MAAM,CAAC,QAAQ,GAAE,MAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBpE;AAED,wBAAgB,KAAK,IAAI,MAAM,CAK9B;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BlF;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGjE;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,wBAAsB,UAAU,CAAC,KAAK,GAAE,SAAc,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAwCrG;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACxC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC,CAqBvD"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createClient } from '@libsql/client';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
const DB_FILE = '.context-todos.db';
|
|
4
|
+
let client = null;
|
|
5
|
+
export async function initDb(basePath = '.') {
|
|
6
|
+
const dbPath = resolve(basePath, DB_FILE);
|
|
7
|
+
client = createClient({ url: `file:${dbPath}` });
|
|
8
|
+
await client.batch([
|
|
9
|
+
`CREATE TABLE IF NOT EXISTS todos (
|
|
10
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
11
|
+
file TEXT NOT NULL,
|
|
12
|
+
tag TEXT NOT NULL,
|
|
13
|
+
line INTEGER NOT NULL,
|
|
14
|
+
ref TEXT NOT NULL DEFAULT '',
|
|
15
|
+
text TEXT NOT NULL,
|
|
16
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch('now', 'subsec') * 1000),
|
|
17
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch('now', 'subsec') * 1000),
|
|
18
|
+
UNIQUE(file, line, tag)
|
|
19
|
+
)`,
|
|
20
|
+
`CREATE INDEX IF NOT EXISTS idx_todos_file ON todos(file)`,
|
|
21
|
+
`CREATE INDEX IF NOT EXISTS idx_todos_tag ON todos(tag)`,
|
|
22
|
+
`CREATE INDEX IF NOT EXISTS idx_todos_updated ON todos(updated_at DESC)`,
|
|
23
|
+
], 'write');
|
|
24
|
+
return client;
|
|
25
|
+
}
|
|
26
|
+
export function getDb() {
|
|
27
|
+
if (!client) {
|
|
28
|
+
throw new Error('Database not initialized. Call initDb() first.');
|
|
29
|
+
}
|
|
30
|
+
return client;
|
|
31
|
+
}
|
|
32
|
+
export async function syncFileTodos(file, todos) {
|
|
33
|
+
const db = getDb();
|
|
34
|
+
if (todos.length === 0) {
|
|
35
|
+
await db.execute({ sql: 'DELETE FROM todos WHERE file = ?', args: [file] });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const tx = await db.transaction('write');
|
|
39
|
+
try {
|
|
40
|
+
await tx.execute({ sql: 'DELETE FROM todos WHERE file = ?', args: [file] });
|
|
41
|
+
for (const todo of todos) {
|
|
42
|
+
await tx.execute({
|
|
43
|
+
sql: `INSERT INTO todos (file, tag, line, ref, text, updated_at)
|
|
44
|
+
VALUES (?, ?, ?, ?, ?, unixepoch('now', 'subsec') * 1000)`,
|
|
45
|
+
args: [todo.file, todo.tag, todo.line, todo.ref || '', todo.text],
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
await tx.commit();
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
await tx.rollback();
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
tx.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function removeFileTodos(file) {
|
|
59
|
+
const db = getDb();
|
|
60
|
+
await db.execute({ sql: 'DELETE FROM todos WHERE file = ?', args: [file] });
|
|
61
|
+
}
|
|
62
|
+
export async function queryTodos(query = {}) {
|
|
63
|
+
const db = getDb();
|
|
64
|
+
const conditions = [];
|
|
65
|
+
const args = [];
|
|
66
|
+
if (query.tag) {
|
|
67
|
+
conditions.push('tag = ?');
|
|
68
|
+
args.push(query.tag);
|
|
69
|
+
}
|
|
70
|
+
if (query.file) {
|
|
71
|
+
conditions.push('file LIKE ?');
|
|
72
|
+
args.push(`%${query.file}%`);
|
|
73
|
+
}
|
|
74
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
75
|
+
const countResult = await db.execute({
|
|
76
|
+
sql: `SELECT COUNT(*) as total FROM todos ${where}`,
|
|
77
|
+
args,
|
|
78
|
+
});
|
|
79
|
+
const total = Number(countResult.rows[0]?.total ?? 0);
|
|
80
|
+
const limit = query.limit ?? 100;
|
|
81
|
+
const offset = query.offset ?? 0;
|
|
82
|
+
const result = await db.execute({
|
|
83
|
+
sql: `SELECT file, tag, line, ref, text FROM todos ${where} ORDER BY updated_at DESC LIMIT ? OFFSET ?`,
|
|
84
|
+
args: [...args, limit, offset],
|
|
85
|
+
});
|
|
86
|
+
const todos = result.rows.map((row) => ({
|
|
87
|
+
file: String(row.file),
|
|
88
|
+
tag: String(row.tag),
|
|
89
|
+
line: Number(row.line),
|
|
90
|
+
ref: String(row.ref),
|
|
91
|
+
text: String(row.text),
|
|
92
|
+
}));
|
|
93
|
+
return { todos, total };
|
|
94
|
+
}
|
|
95
|
+
export async function getTodoStats() {
|
|
96
|
+
const db = getDb();
|
|
97
|
+
const results = await db.batch([
|
|
98
|
+
'SELECT COUNT(*) as total FROM todos',
|
|
99
|
+
'SELECT tag, COUNT(*) as count FROM todos GROUP BY tag ORDER BY count DESC',
|
|
100
|
+
'SELECT file, COUNT(*) as count FROM todos GROUP BY file ORDER BY count DESC LIMIT 20',
|
|
101
|
+
], 'read');
|
|
102
|
+
const totalResult = results[0];
|
|
103
|
+
const tagResult = results[1];
|
|
104
|
+
const fileResult = results[2];
|
|
105
|
+
return {
|
|
106
|
+
total: Number(totalResult?.rows[0]?.total ?? 0),
|
|
107
|
+
byTag: (tagResult?.rows ?? []).map((r) => ({ tag: String(r.tag), count: Number(r.count) })),
|
|
108
|
+
byFile: (fileResult?.rows ?? []).map((r) => ({ file: String(r.file), count: Number(r.count) })),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/mcp/db.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../../src/mcp/gitignore.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACtC;AAOD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,aAAa,GAAE,MAAqB,GACnC,eAAe,CA0BjB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import ignore from 'ignore';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve, relative } from 'node:path';
|
|
4
|
+
const noopFilter = {
|
|
5
|
+
ignores: () => false,
|
|
6
|
+
ignoresDir: () => false,
|
|
7
|
+
};
|
|
8
|
+
export function createGitignoreFilter(rootDir, gitignorePath = '.gitignore') {
|
|
9
|
+
const absoluteGitignorePath = resolve(rootDir, gitignorePath);
|
|
10
|
+
if (!existsSync(absoluteGitignorePath)) {
|
|
11
|
+
return noopFilter;
|
|
12
|
+
}
|
|
13
|
+
const gitignoreContent = readFileSync(absoluteGitignorePath, 'utf8');
|
|
14
|
+
const ig = ignore().add(gitignoreContent);
|
|
15
|
+
return {
|
|
16
|
+
ignores: (pathname) => {
|
|
17
|
+
const relativePath = relative(rootDir, pathname);
|
|
18
|
+
if (!relativePath || relativePath.startsWith('..')) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return ig.ignores(relativePath);
|
|
22
|
+
},
|
|
23
|
+
ignoresDir: (dirPath) => {
|
|
24
|
+
const relativePath = relative(rootDir, dirPath);
|
|
25
|
+
if (!relativePath || relativePath.startsWith('..')) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return ig.ignores(relativePath) || ig.ignores(relativePath + '/');
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=gitignore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../../src/mcp/gitignore.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAO9C,MAAM,UAAU,GAAoB;IAClC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK;IACpB,UAAU,EAAE,GAAG,EAAE,CAAC,KAAK;CACxB,CAAC;AAEF,MAAM,UAAU,qBAAqB,CACnC,OAAe,EACf,gBAAwB,YAAY;IAEpC,MAAM,qBAAqB,GAAG,OAAO,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAE9D,IAAI,CAAC,UAAU,CAAC,qBAAqB,CAAC,EAAE,CAAC;QACvC,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,gBAAgB,GAAG,YAAY,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;IACrE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAE1C,OAAO;QACL,OAAO,EAAE,CAAC,QAAgB,EAAE,EAAE;YAC5B,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YACjD,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnD,OAAO,KAAK,CAAC;YACf,CAAC;YACD,OAAO,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAClC,CAAC;QACD,UAAU,EAAE,CAAC,OAAe,EAAE,EAAE;YAC9B,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAChD,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnD,OAAO,KAAK,CAAC;YACf,CAAC;YACD,OAAO,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC;QACpE,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { ServerOptions } from './types.js';
|
|
3
|
+
export declare function createServer(options?: ServerOptions): Promise<{
|
|
4
|
+
server: McpServer;
|
|
5
|
+
totalTodos: number;
|
|
6
|
+
}>;
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAWpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,wBAAsB,YAAY,CAAC,OAAO,GAAE,aAAoC;;;GAmF/E"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { resolve, relative } from 'node:path';
|
|
3
|
+
import { watch } from 'chokidar';
|
|
4
|
+
import { registerScanFile } from './tools/scan-file.js';
|
|
5
|
+
import { registerScanDirectory } from './tools/scan-directory.js';
|
|
6
|
+
import { registerListExtensions } from './tools/list-extensions.js';
|
|
7
|
+
import { registerWatchTools } from './tools/watch.js';
|
|
8
|
+
import { registerListTodos } from './tools/list-todos.js';
|
|
9
|
+
import { registerGetTodoStats } from './tools/get-todo-stats.js';
|
|
10
|
+
import { initDb, syncFileTodos, removeFileTodos } from './db.js';
|
|
11
|
+
import { collectFiles, scanFile } from './scanner.js';
|
|
12
|
+
import { modeConfigs } from './types.js';
|
|
13
|
+
import { createGitignoreFilter } from './gitignore.js';
|
|
14
|
+
export async function createServer(options = { mode: 'standard' }) {
|
|
15
|
+
const { mode } = options;
|
|
16
|
+
const config = modeConfigs[mode];
|
|
17
|
+
const watchPath = resolve(options.watchPath ?? '.');
|
|
18
|
+
const gitignoreFilter = options.useGitignore
|
|
19
|
+
? createGitignoreFilter(watchPath, options.gitignorePath)
|
|
20
|
+
: { ignores: () => false, ignoresDir: () => false };
|
|
21
|
+
const server = new McpServer({ name: 'context-todos', version: '1.0.0' }, { capabilities: { logging: {} } });
|
|
22
|
+
registerScanFile(server);
|
|
23
|
+
registerScanDirectory(server);
|
|
24
|
+
registerListExtensions(server);
|
|
25
|
+
if (config.enableWatcher) {
|
|
26
|
+
registerWatchTools(server, gitignoreFilter);
|
|
27
|
+
}
|
|
28
|
+
if (config.enableDatabase) {
|
|
29
|
+
registerListTodos(server);
|
|
30
|
+
await initDb(watchPath);
|
|
31
|
+
const files = collectFiles(watchPath, undefined, gitignoreFilter);
|
|
32
|
+
let totalTodos = 0;
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
const todos = await scanFile(file);
|
|
35
|
+
if (todos.length > 0) {
|
|
36
|
+
const relFile = relative(watchPath, file);
|
|
37
|
+
const relTodos = todos.map((t) => ({ ...t, file: relFile }));
|
|
38
|
+
await syncFileTodos(relFile, relTodos);
|
|
39
|
+
totalTodos += todos.length;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const dbWatcher = watch(watchPath, {
|
|
43
|
+
ignored: (fp, stats) => {
|
|
44
|
+
const relPath = relative(watchPath, fp);
|
|
45
|
+
if (gitignoreFilter.ignores(relPath))
|
|
46
|
+
return true;
|
|
47
|
+
if (stats?.isFile() && options.extensions) {
|
|
48
|
+
const ext = '.' + fp.split('.').pop();
|
|
49
|
+
return !options.extensions.includes(ext);
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
},
|
|
53
|
+
persistent: true,
|
|
54
|
+
ignoreInitial: true,
|
|
55
|
+
});
|
|
56
|
+
dbWatcher
|
|
57
|
+
.on('add', (p) => {
|
|
58
|
+
void (async () => {
|
|
59
|
+
const relPath = relative(watchPath, p);
|
|
60
|
+
try {
|
|
61
|
+
const todos = await scanFile(p);
|
|
62
|
+
await syncFileTodos(relPath, todos.map((t) => ({ ...t, file: relPath })));
|
|
63
|
+
}
|
|
64
|
+
catch { /* file may be temporarily unreadable */ }
|
|
65
|
+
})();
|
|
66
|
+
})
|
|
67
|
+
.on('change', (p) => {
|
|
68
|
+
void (async () => {
|
|
69
|
+
const relPath = relative(watchPath, p);
|
|
70
|
+
try {
|
|
71
|
+
const todos = await scanFile(p);
|
|
72
|
+
await syncFileTodos(relPath, todos.map((t) => ({ ...t, file: relPath })));
|
|
73
|
+
}
|
|
74
|
+
catch { /* file may be temporarily unreadable */ }
|
|
75
|
+
})();
|
|
76
|
+
})
|
|
77
|
+
.on('unlink', (p) => {
|
|
78
|
+
void removeFileTodos(relative(watchPath, p));
|
|
79
|
+
});
|
|
80
|
+
if (config.enableGetTodoStats) {
|
|
81
|
+
registerGetTodoStats(server);
|
|
82
|
+
}
|
|
83
|
+
return { server, totalTodos };
|
|
84
|
+
}
|
|
85
|
+
return { server, totalTodos: 0 };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=index.js.map
|