saga-mcp 1.6.0 → 1.7.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.
Files changed (43) hide show
  1. package/README.md +197 -7
  2. package/dist/db.js +20 -0
  3. package/dist/db.js.map +1 -1
  4. package/dist/helpers/project-scope.d.ts +31 -0
  5. package/dist/helpers/project-scope.js +113 -0
  6. package/dist/helpers/project-scope.js.map +1 -0
  7. package/dist/helpers/slim.d.ts +17 -0
  8. package/dist/helpers/slim.js +45 -0
  9. package/dist/helpers/slim.js.map +1 -0
  10. package/dist/index.js +37 -3
  11. package/dist/index.js.map +1 -1
  12. package/dist/schema.d.ts +1 -1
  13. package/dist/schema.js +4 -0
  14. package/dist/schema.js.map +1 -1
  15. package/dist/tools/activity.js +13 -1
  16. package/dist/tools/activity.js.map +1 -1
  17. package/dist/tools/comments.js +78 -4
  18. package/dist/tools/comments.js.map +1 -1
  19. package/dist/tools/dashboard.js +29 -9
  20. package/dist/tools/dashboard.js.map +1 -1
  21. package/dist/tools/epics.js +7 -6
  22. package/dist/tools/epics.js.map +1 -1
  23. package/dist/tools/export-import.js +6 -1
  24. package/dist/tools/export-import.js.map +1 -1
  25. package/dist/tools/notes.js +8 -0
  26. package/dist/tools/notes.js.map +1 -1
  27. package/dist/tools/projects.js +2 -1
  28. package/dist/tools/projects.js.map +1 -1
  29. package/dist/tools/search.js +38 -13
  30. package/dist/tools/search.js.map +1 -1
  31. package/dist/tools/tasks.js +15 -4
  32. package/dist/tools/tasks.js.map +1 -1
  33. package/dist/web/index.d.ts +2 -0
  34. package/dist/web/index.js +342 -0
  35. package/dist/web/index.js.map +1 -0
  36. package/dist/web/queries.d.ts +32 -0
  37. package/dist/web/queries.js +186 -0
  38. package/dist/web/queries.js.map +1 -0
  39. package/dist/web/ui.d.ts +10 -0
  40. package/dist/web/ui.js +1193 -0
  41. package/dist/web/ui.js.map +1 -0
  42. package/manifest.json +21 -4
  43. package/package.json +15 -4
package/README.md CHANGED
@@ -1,31 +1,87 @@
1
1
  # saga-mcp
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/saga-mcp)](https://www.npmjs.com/package/saga-mcp)
4
+ [![npm downloads](https://img.shields.io/npm/dm/saga-mcp)](https://www.npmjs.com/package/saga-mcp)
3
5
  [![IdeaCred](https://ideacred.com/api/badge/spranab/saga-mcp)](https://ideacred.com/profile/spranab)
4
6
 
5
- A Jira-like project tracker MCP server for AI agents. SQLite-backed, per-project scoped, with full hierarchy and activity logging — so LLMs never lose track.
7
+ Your coding agent loses the plan between sessions. You come back tomorrow and
8
+ it has no idea which of the five things you agreed on are done, which one is
9
+ blocked on which, or why you rejected the second approach — because the plan
10
+ lived in the context window, or in a `TODO.md` nobody updates.
6
11
 
7
- **No more scattered markdown files.** saga-mcp gives your AI assistant a structured database to track projects, epics, tasks, subtasks, notes, and decisions across sessions.
12
+ saga-mcp gives the agent a real tracker instead: a SQLite file in your project
13
+ holding projects, epics, tasks, subtasks, dependencies, comments, notes and
14
+ decisions, exposed as 33 MCP tools. The agent writes to it as it works and
15
+ reads the dashboard when it comes back. No accounts, no external service, no
16
+ network calls — the database is a file you own.
17
+
18
+ ## Install (60 seconds)
19
+
20
+ Claude Code — add to your project's `.mcp.json`:
21
+
22
+ ```json
23
+ {
24
+ "mcpServers": {
25
+ "saga": {
26
+ "command": "npx",
27
+ "args": ["-y", "saga-mcp"],
28
+ "env": { "DB_PATH": "/absolute/path/to/your/project/.tracker.db" }
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ Restart the client. `DB_PATH` is the only setting; the file and schema are
35
+ created on first use.
36
+
37
+ ## What it looks like
38
+
39
+ **You:** "Set up tracking for the e-commerce API and plan out auth."
40
+
41
+ ```
42
+ tracker_init({ project_name: "E-Commerce API" })
43
+ epic_create({ project_id: 1, name: "Authentication", priority: "high" })
44
+ task_create({ epic_id: 1, title: "Design auth schema", priority: "critical" })
45
+ task_create({ epic_id: 1, title: "Implement JWT auth", depends_on: [1] })
46
+ task_create({ epic_id: 1, title: "Add OAuth2 Google login", depends_on: [2] })
47
+ ```
48
+
49
+ Tasks 2 and 3 come back **blocked** — their dependencies aren't done. Finish
50
+ task 1 and task 2 unblocks itself.
51
+
52
+ **Next session, you:** "Where were we?"
53
+
54
+ ```
55
+ tracker_dashboard({})
56
+ → "E-Commerce API: 5 tasks across 2 epics. 40% complete.
57
+ Active: Authentication (2/3 done). Next up: Product Catalog (2 tasks).
58
+ 1 blocked task(s)."
59
+ ```
60
+
61
+ Plus the structured data behind it: stats, epics, blocked and overdue tasks,
62
+ recent activity, notes.
8
63
 
9
64
  ## Features
10
65
 
11
66
  - **Full hierarchy**: Projects > Epics > Tasks > Subtasks
12
67
  - **Task dependencies**: Express sequencing with auto-block/unblock when deps are met
13
- - **Comments**: Threaded discussions on tasks — leave breadcrumbs across sessions
68
+ - **Comments**: Threaded discussions on tasks — leave breadcrumbs across sessions, with reversible soft-delete
69
+ - **Web UI**: `saga-web` serves a local dashboard for browsing *and* editing the same database
14
70
  - **Templates**: Reusable task sets with `{variable}` substitution
15
71
  - **Dashboard**: One tool call gives full overview with natural language summary
16
72
  - **SQLite**: Self-contained `.tracker.db` file per project — zero setup, no external database
17
73
  - **Activity log**: Every mutation is automatically tracked with old/new values
18
74
  - **Notes system**: Decisions, context, meeting notes, blockers — all searchable
19
75
  - **Batch operations**: Create multiple subtasks or update multiple tasks in one call
20
- - **31 focused tools**: With MCP safety annotations on every tool
76
+ - **33 focused tools**: With MCP safety annotations on every tool
21
77
  - **Import/export**: Full project backup and migration as JSON (with dependencies and comments)
22
78
  - **Source references**: Link tasks to specific code locations
23
79
  - **Auto time tracking**: Hours computed automatically from activity log
24
80
  - **Cross-platform**: Works on macOS, Windows, and Linux
25
81
 
26
- ## Quick Start
82
+ ## Other clients
27
83
 
28
- ### With Claude Code
84
+ ### Claude Code
29
85
 
30
86
  Add to your project's `.mcp.json`:
31
87
 
@@ -75,9 +131,29 @@ saga-mcp requires a single environment variable:
75
131
  | Variable | Required | Description |
76
132
  |----------|----------|-------------|
77
133
  | `DB_PATH` | Yes | Absolute path to the `.tracker.db` SQLite file. The file and schema are auto-created on first use. |
134
+ | `SAGA_PROJECT` | No | Scope every tool to one project, by id or name. Set this per repo when several repos share one database. Unset, tools read across the whole file. |
135
+ | `SAGA_TOOLS` | No | `full` (default) lists all 33 tools. `core` lists only the 12 an ordinary tracking session needs, cutting ~3,300 tokens of context per session. Tools left off the list still work if called by name. |
78
136
 
79
137
  No API keys, no accounts, no external services. Everything is stored locally in the SQLite file you specify.
80
138
 
139
+ ### Token cost
140
+
141
+ The tool list is context every session pays before any work happens, and list responses are
142
+ context it pays again on every call. Both are kept deliberately small:
143
+
144
+ - Responses are compact JSON — no pretty-print indentation, which measured 20-27% of every response
145
+ - `task_list` rows omit nulls and `metadata`, and truncate descriptions to 120 characters
146
+ (call `task_get` for a task's full text) — 19-39% smaller depending on how long your descriptions run
147
+ - `activity_log` omits null columns and the row id (no tool takes one) — about 27% smaller
148
+ - `tracker_search` returns previews rather than whole records — about 47% smaller; follow up with
149
+ `task_get` or `note_list` for the full text
150
+ - `SAGA_TOOLS=core` drops the listed tool surface from ~6,000 to ~2,700 tokens
151
+
152
+ `note_list` deliberately keeps full note content — it is the retrieval tool, not a preview.
153
+
154
+ Set `SAGA_TOOLS=core` when an agent only tracks work; leave it unset when you want templates,
155
+ import/export, session diffs and the rest discoverable.
156
+
81
157
  ## Tools
82
158
 
83
159
  ### Getting Started
@@ -126,7 +202,9 @@ No API keys, no accounts, no external services. Everything is stored locally in
126
202
  | Tool | Description | Annotations |
127
203
  |------|-------------|-------------|
128
204
  | `comment_add` | Add a comment to a task (threaded discussion) | `readOnly: false` |
129
- | `comment_list` | List all comments on a task | `readOnly: true` |
205
+ | `comment_list` | List comments on a task (removed ones hidden unless `include_deleted`) | `readOnly: true` |
206
+ | `comment_delete` | Remove a comment — soft delete, row kept for audit | `readOnly: false`, `idempotent: true` |
207
+ | `comment_restore` | Restore a removed comment | `readOnly: false`, `idempotent: true` |
130
208
 
131
209
  ### Templates
132
210
 
@@ -225,6 +303,99 @@ task_update({ id: 5, status: "done" })
225
303
 
226
304
  Comments persist across sessions — next time an agent calls `task_get(5)`, it sees the full discussion thread.
227
305
 
306
+ If a comment turns out to be wrong, retract it without losing the trail:
307
+
308
+ ```
309
+ comment_delete({ id: 12, reason: "Root cause was wrong — it was a proxy timeout", deleted_by: "pranab" })
310
+ ```
311
+
312
+ The row stays in the database and in the activity log. `comment_list` and `task_get` skip it,
313
+ `comment_list({ task_id: 5, include_deleted: true })` shows it with its reason, and
314
+ `comment_restore({ id: 12 })` brings it back. Nothing an agent removes is unrecoverable.
315
+
316
+ ## One database, many projects
317
+
318
+ saga-mcp works either way: a `.tracker.db` per repo (portable, keeps unrelated work apart),
319
+ or one shared database that every repo points at.
320
+
321
+ The shared setup needs one extra thing. `projects` is the top-level table, so a shared file holds
322
+ several projects — but `task_list`, `note_list`, `activity_log` and `tracker_search` read across
323
+ the whole file unless told otherwise. An agent in repo B would see repo A's tasks. Set
324
+ `SAGA_PROJECT` per repo and each agent sees only its own:
325
+
326
+ ```json
327
+ {
328
+ "mcpServers": {
329
+ "saga": {
330
+ "command": "npx",
331
+ "args": ["-y", "saga-mcp"],
332
+ "env": {
333
+ "DB_PATH": "/Users/you/saga/central.tracker.db",
334
+ "SAGA_PROJECT": "Payments platform"
335
+ }
336
+ }
337
+ }
338
+ }
339
+ ```
340
+
341
+ `SAGA_PROJECT` takes a project id or a project name (case-insensitive), and fails on startup with
342
+ the list of real projects if it matches neither. Every scoped tool also accepts an explicit
343
+ `project_id` argument, which wins over the environment variable.
344
+
345
+ | Setup | What to set | Result |
346
+ |-------|-------------|--------|
347
+ | One database per repo | `DB_PATH` | Nothing to scope — one project per file |
348
+ | Shared database, per-repo agents | `DB_PATH` + `SAGA_PROJECT` | Each agent sees only its project |
349
+ | Shared database, one agent over everything | `DB_PATH` | Tools read across all projects |
350
+
351
+ With neither `SAGA_PROJECT` nor a `project_id`, `tracker_dashboard` falls back to the first project
352
+ in the file and says so — the response carries `other_projects` and the summary explains that the
353
+ project was a guess, rather than silently reporting on the wrong repo.
354
+
355
+ The web UI is unaffected either way: its project switcher lists every project in the database, and
356
+ each tab is scoped to the selected one.
357
+
358
+ ## Web UI
359
+
360
+ Everything above is agent-facing. `saga-web` puts the same database in a browser — for the times
361
+ when reviewing a spec an agent just wrote, or fixing one field by hand, is faster than another prompt.
362
+
363
+ ```bash
364
+ npx -p saga-mcp saga-web ./.tracker.db --open
365
+ ```
366
+
367
+ Or against a database you already point your MCP server at:
368
+
369
+ ```bash
370
+ saga-web --db ~/saga/central.tracker.db --port 8080
371
+ ```
372
+
373
+ | Option | Default | Description |
374
+ |--------|---------|-------------|
375
+ | `--db <path>` | `$DB_PATH` | Database to open. A positional path works too. |
376
+ | `--port <n>` | first free from `4319` | Omit it and saga-web takes the first free port, so one instance per project just works. `--port N` binds exactly N and fails if taken; `--port 0` lets the OS choose. Also `SAGA_WEB_PORT`. |
377
+ | `--host <addr>` | `127.0.0.1` | Bind address. Local-only by default. |
378
+ | `--read-only` | off | Serve the UI with every editing control removed. |
379
+ | `--open` | off | Open the UI in your default browser. |
380
+
381
+ What you get:
382
+
383
+ - **Overview** — stats, per-epic progress, blocked and overdue tasks
384
+ - **Board** — kanban across the five task statuses; drag a card to change its status
385
+ - **Epics** — the full Epic → Task → Subtask tree, which is the fastest way to review a spec an agent just wrote
386
+ - **Notes** and **Activity** — decisions and the complete change history
387
+ - **Task drawer** — edit any field, tick subtasks, comment, remove or restore a comment
388
+ - **Project switcher** — every project in the database, so one central `.tracker.db` covers all your repos; every tab, including Activity, is scoped to the selected project
389
+
390
+ Writes from the UI call the *same handlers* the MCP tools do, so edits you make by hand are
391
+ validated identically and land in the same activity log as the agent's — an agent calling
392
+ `tracker_dashboard` after you fix something sees the fix and how it happened.
393
+
394
+ A few deliberate limits: it binds to `127.0.0.1` unless you ask otherwise, it has no
395
+ authentication (don't put it on a shared network), and it will not create a database — point it
396
+ at one your MCP server already uses. Separate `.tracker.db` files are not yet aggregated into
397
+ one view; a single database with multiple projects is.
398
+
228
399
  ## How It Works
229
400
 
230
401
  saga-mcp stores everything in a single SQLite file (`.tracker.db`) per project. The database is auto-created on first use with all tables and indexes — no migration step needed.
@@ -300,6 +471,9 @@ cd saga-mcp
300
471
  npm install
301
472
  npm run build
302
473
  DB_PATH=./test.db npm start
474
+
475
+ # the web UI against the same database
476
+ node dist/web/index.js ./test.db --open
303
477
  ```
304
478
 
305
479
  ## Support
@@ -307,6 +481,22 @@ DB_PATH=./test.db npm start
307
481
  - **Issues**: https://github.com/spranab/saga-mcp/issues
308
482
  - **Repository**: https://github.com/spranab/saga-mcp
309
483
 
484
+ ## Related projects
485
+
486
+ Part of a set of agent infrastructure built by one person, meant to be used
487
+ together:
488
+
489
+ - [yantrikdb-mcp](https://github.com/yantrikos/yantrikdb-mcp) — persistent
490
+ cognitive memory for the same agent: what it learned, not what it planned.
491
+ - [brainstorm-mcp](https://github.com/spranab/brainstorm-mcp) — multi-model
492
+ debate before you commit a plan to the tracker.
493
+ - [swarmcode](https://github.com/spranab/swarmcode) — real-time channel
494
+ between Claude Code instances on different machines.
495
+ - [truenas-mcp](https://github.com/spranab/truenas-mcp) — 278 TrueNAS SCALE
496
+ actions behind one hierarchical tool.
497
+ - [mcpier](https://github.com/spranab/mcpier) — self-hosted MCP control plane
498
+ that keeps API keys off your clients.
499
+
310
500
  ## License
311
501
 
312
502
  MIT
package/dist/db.js CHANGED
@@ -27,6 +27,26 @@ export function getDb() {
27
27
  db.exec('CREATE INDEX IF NOT EXISTS idx_epics_branch ON epics(branch)');
28
28
  }
29
29
  catch { /* index already exists */ }
30
+ try {
31
+ db.exec('ALTER TABLE comments ADD COLUMN is_deleted INTEGER NOT NULL DEFAULT 0');
32
+ }
33
+ catch { /* column already exists */ }
34
+ try {
35
+ db.exec('ALTER TABLE comments ADD COLUMN deleted_at TEXT');
36
+ }
37
+ catch { /* column already exists */ }
38
+ try {
39
+ db.exec('ALTER TABLE comments ADD COLUMN deleted_by TEXT');
40
+ }
41
+ catch { /* column already exists */ }
42
+ try {
43
+ db.exec('ALTER TABLE comments ADD COLUMN delete_reason TEXT');
44
+ }
45
+ catch { /* column already exists */ }
46
+ try {
47
+ db.exec('CREATE INDEX IF NOT EXISTS idx_comments_task_live ON comments(task_id, is_deleted)');
48
+ }
49
+ catch { /* index already exists */ }
30
50
  return db;
31
51
  }
32
52
  export function closeDb() {
package/dist/db.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,IAAI,EAAE,GAA6B,IAAI,CAAC;AAExC,MAAM,UAAU,KAAK;IACnB,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAElB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;IACnC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,mIAAmI,CACpI,CAAC;IACJ,CAAC;IAED,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE1B,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC/B,EAAE,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IACjC,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;IAElC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAEpB,oCAAoC;IACpC,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IACtG,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IAClG,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,8DAA8D,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,0BAA0B,CAAC,CAAC;IAErH,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,IAAI,EAAE,EAAE,CAAC;QACP,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,EAAE,GAAG,IAAI,CAAC;IACZ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,IAAI,EAAE,GAA6B,IAAI,CAAC;AAExC,MAAM,UAAU,KAAK;IACnB,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAElB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;IACnC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,mIAAmI,CACpI,CAAC;IACJ,CAAC;IAED,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE1B,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC/B,EAAE,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IACjC,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;IAElC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAEpB,oCAAoC;IACpC,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IACtG,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IAClG,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,8DAA8D,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,0BAA0B,CAAC,CAAC;IACrH,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,uEAAuE,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IAC/H,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IACzG,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IACzG,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IAC5G,IAAI,CAAC;QAAC,EAAE,CAAC,IAAI,CAAC,oFAAoF,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,0BAA0B,CAAC,CAAC;IAE3I,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,IAAI,EAAE,EAAE,CAAC;QACP,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,EAAE,GAAG,IAAI,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type Database from 'better-sqlite3';
2
+ /** The project a call should be scoped to, or undefined for the whole database. */
3
+ export declare function resolveProjectId(db: Database.Database, args: Record<string, unknown>): number | undefined;
4
+ export declare function projectCount(db: Database.Database): number;
5
+ /** Shared JSON-schema property, so every scoped tool describes it identically. */
6
+ export declare const PROJECT_ID_SCHEMA: {
7
+ type: "integer";
8
+ description: string;
9
+ };
10
+ /**
11
+ * `WHERE` fragment selecting tasks in a project, given an alias for the epics
12
+ * table already joined by the caller.
13
+ */
14
+ export declare function taskScopeClause(epicAlias?: string): string;
15
+ /** Notes attached to a project, or to any epic/task inside it. Unattached notes count as global. */
16
+ export declare function noteScopeClause(alias?: string): {
17
+ sql: string;
18
+ paramCount: number;
19
+ };
20
+ /**
21
+ * Activity rows name an entity by type and id, so scoping means walking each
22
+ * type back up to its project.
23
+ */
24
+ export declare function activityScopeClause(alias?: string): {
25
+ sql: string;
26
+ paramCount: number;
27
+ };
28
+ /** Repeat a project id n times, for the multi-placeholder clauses above. */
29
+ export declare function repeatId(projectId: number, n: number): number[];
30
+ /** Only for tests — SAGA_PROJECT is read once and cached. */
31
+ export declare function resetProjectScopeCache(): void;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * One database can hold many projects — the "global" / centralized setup, where
3
+ * every repo points its MCP server at the same .tracker.db.
4
+ *
5
+ * Without scoping, an agent working in repo B sees repo A's tasks: task_list,
6
+ * note_list, activity_log and tracker_search all read across the whole file.
7
+ * These helpers give every such tool the same scoping rule:
8
+ *
9
+ * explicit project_id argument > SAGA_PROJECT env var > unscoped
10
+ *
11
+ * Set SAGA_PROJECT (an id, or a project name) in each repo's MCP config and a
12
+ * shared database behaves exactly like a per-project one.
13
+ */
14
+ let cachedEnvProject;
15
+ /** Resolve SAGA_PROJECT, which may be a numeric id or a project name. */
16
+ function envProjectId(db) {
17
+ const raw = process.env.SAGA_PROJECT?.trim();
18
+ if (!raw)
19
+ return undefined;
20
+ if (cachedEnvProject && cachedEnvProject.raw === raw)
21
+ return cachedEnvProject.id;
22
+ let row;
23
+ if (/^\d+$/.test(raw)) {
24
+ row = db.prepare('SELECT id FROM projects WHERE id = ?').get(Number(raw));
25
+ }
26
+ else {
27
+ row = db.prepare('SELECT id FROM projects WHERE name = ? COLLATE NOCASE').get(raw);
28
+ }
29
+ if (!row) {
30
+ const known = db.prepare('SELECT id, name FROM projects ORDER BY id').all()
31
+ .map((p) => `${p.id}=${p.name}`)
32
+ .join(', ');
33
+ throw new Error(`SAGA_PROJECT is set to '${raw}', which is not a project in this database. Known projects: ${known || '(none)'}`);
34
+ }
35
+ cachedEnvProject = { raw, id: row.id };
36
+ return row.id;
37
+ }
38
+ /** The project a call should be scoped to, or undefined for the whole database. */
39
+ export function resolveProjectId(db, args) {
40
+ const explicit = args.project_id;
41
+ if (typeof explicit === 'number')
42
+ return explicit;
43
+ if (typeof explicit === 'string' && /^\d+$/.test(explicit))
44
+ return Number(explicit);
45
+ return envProjectId(db);
46
+ }
47
+ export function projectCount(db) {
48
+ return db.prepare('SELECT COUNT(*) as n FROM projects').get().n;
49
+ }
50
+ /** Shared JSON-schema property, so every scoped tool describes it identically. */
51
+ export const PROJECT_ID_SCHEMA = {
52
+ type: 'integer',
53
+ description: 'Scope to one project. Needed when a single database holds several projects; defaults to the SAGA_PROJECT env var if set, otherwise the whole database.',
54
+ };
55
+ /**
56
+ * `WHERE` fragment selecting tasks in a project, given an alias for the epics
57
+ * table already joined by the caller.
58
+ */
59
+ export function taskScopeClause(epicAlias = 'e') {
60
+ return `${epicAlias}.project_id = ?`;
61
+ }
62
+ /** Notes attached to a project, or to any epic/task inside it. Unattached notes count as global. */
63
+ export function noteScopeClause(alias = 'notes') {
64
+ return {
65
+ sql: `(
66
+ (${alias}.related_entity_type = 'project' AND ${alias}.related_entity_id = ?)
67
+ OR (${alias}.related_entity_type = 'epic' AND ${alias}.related_entity_id IN
68
+ (SELECT id FROM epics WHERE project_id = ?))
69
+ OR (${alias}.related_entity_type = 'task' AND ${alias}.related_entity_id IN
70
+ (SELECT t.id FROM tasks t JOIN epics e ON e.id = t.epic_id WHERE e.project_id = ?))
71
+ OR ${alias}.related_entity_type IS NULL
72
+ )`,
73
+ paramCount: 3,
74
+ };
75
+ }
76
+ /**
77
+ * Activity rows name an entity by type and id, so scoping means walking each
78
+ * type back up to its project.
79
+ */
80
+ export function activityScopeClause(alias = 'activity_log') {
81
+ return {
82
+ sql: `(
83
+ (${alias}.entity_type = 'project' AND ${alias}.entity_id = ?)
84
+ OR (${alias}.entity_type = 'epic' AND ${alias}.entity_id IN
85
+ (SELECT id FROM epics WHERE project_id = ?))
86
+ OR (${alias}.entity_type = 'task' AND ${alias}.entity_id IN
87
+ (SELECT t.id FROM tasks t JOIN epics e ON e.id = t.epic_id WHERE e.project_id = ?))
88
+ OR (${alias}.entity_type = 'subtask' AND ${alias}.entity_id IN
89
+ (SELECT s.id FROM subtasks s JOIN tasks t ON t.id = s.task_id
90
+ JOIN epics e ON e.id = t.epic_id WHERE e.project_id = ?))
91
+ OR (${alias}.entity_type = 'comment' AND ${alias}.entity_id IN
92
+ (SELECT c.id FROM comments c JOIN tasks t ON t.id = c.task_id
93
+ JOIN epics e ON e.id = t.epic_id WHERE e.project_id = ?))
94
+ OR (${alias}.entity_type = 'note' AND ${alias}.entity_id IN
95
+ (SELECT n.id FROM notes n WHERE
96
+ (n.related_entity_type = 'project' AND n.related_entity_id = ?)
97
+ OR (n.related_entity_type = 'epic' AND n.related_entity_id IN
98
+ (SELECT id FROM epics WHERE project_id = ?))
99
+ OR (n.related_entity_type = 'task' AND n.related_entity_id IN
100
+ (SELECT t.id FROM tasks t JOIN epics e ON e.id = t.epic_id WHERE e.project_id = ?))))
101
+ )`,
102
+ paramCount: 8,
103
+ };
104
+ }
105
+ /** Repeat a project id n times, for the multi-placeholder clauses above. */
106
+ export function repeatId(projectId, n) {
107
+ return Array.from({ length: n }, () => projectId);
108
+ }
109
+ /** Only for tests — SAGA_PROJECT is read once and cached. */
110
+ export function resetProjectScopeCache() {
111
+ cachedEnvProject = undefined;
112
+ }
113
+ //# sourceMappingURL=project-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-scope.js","sourceRoot":"","sources":["../../src/helpers/project-scope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * List responses are the tokens an agent pays over and over. Detail responses
3
+ * (task_get) stay complete; list rows drop what a scan does not need.
4
+ *
5
+ * Measured on a 40-task project: task_list went from ~5,300 to ~3,600 tokens.
6
+ */
7
+ /** Longest description kept in a list row. Longer text is cut with an ellipsis. */
8
+ export declare const LIST_DESCRIPTION_CHARS = 120;
9
+ export declare function truncate(text: string, max?: number): string;
10
+ /**
11
+ * Strip a list row down: no nulls, no metadata blob, no empty tags array, and
12
+ * long text fields truncated. Everything removed here is still available from
13
+ * the matching *_get tool.
14
+ */
15
+ export declare function slimListRow(row: Record<string, unknown>, truncateFields?: string[], drop?: string[]): Record<string, unknown>;
16
+ /** Apply slimListRow across a result set. */
17
+ export declare function slimList(rows: Array<Record<string, unknown>>, truncateFields?: string[], drop?: string[]): Array<Record<string, unknown>>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * List responses are the tokens an agent pays over and over. Detail responses
3
+ * (task_get) stay complete; list rows drop what a scan does not need.
4
+ *
5
+ * Measured on a 40-task project: task_list went from ~5,300 to ~3,600 tokens.
6
+ */
7
+ /** Longest description kept in a list row. Longer text is cut with an ellipsis. */
8
+ export const LIST_DESCRIPTION_CHARS = 120;
9
+ export function truncate(text, max = LIST_DESCRIPTION_CHARS) {
10
+ if (text.length <= max)
11
+ return text;
12
+ // Prefer a word boundary so the cut reads as prose rather than mid-token.
13
+ const cut = text.slice(0, max);
14
+ const lastSpace = cut.lastIndexOf(' ');
15
+ return (lastSpace > max * 0.6 ? cut.slice(0, lastSpace) : cut).trimEnd() + '…';
16
+ }
17
+ /** Fields never worth their bytes in a list response. */
18
+ const ALWAYS_DROP = new Set(['metadata']);
19
+ /**
20
+ * Strip a list row down: no nulls, no metadata blob, no empty tags array, and
21
+ * long text fields truncated. Everything removed here is still available from
22
+ * the matching *_get tool.
23
+ */
24
+ export function slimListRow(row, truncateFields = ['description'], drop = []) {
25
+ const out = {};
26
+ for (const [key, value] of Object.entries(row)) {
27
+ if (value === null || value === undefined)
28
+ continue;
29
+ if (ALWAYS_DROP.has(key) || drop.includes(key))
30
+ continue;
31
+ if (key === 'tags' && (value === '[]' || value === ''))
32
+ continue;
33
+ if (truncateFields.includes(key) && typeof value === 'string') {
34
+ out[key] = truncate(value);
35
+ continue;
36
+ }
37
+ out[key] = value;
38
+ }
39
+ return out;
40
+ }
41
+ /** Apply slimListRow across a result set. */
42
+ export function slimList(rows, truncateFields, drop) {
43
+ return rows.map((row) => slimListRow(row, truncateFields, drop));
44
+ }
45
+ //# sourceMappingURL=slim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slim.js","sourceRoot":"","sources":["../../src/helpers/slim.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,mFAAmF;AACnF,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,GAAG,GAAG,sBAAsB;IACjE,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC;IACpC,0EAA0E;IAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/B,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACvC,OAAO,CAAC,SAAS,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC;AACjF,CAAC;AAED,yDAAyD;AACzD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;AAE1C;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,GAA4B,EAC5B,iBAA2B,CAAC,aAAa,CAAC,EAC1C,OAAiB,EAAE;IAEnB,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QACpD,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,SAAS;QACzD,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YAAE,SAAS;QACjE,IAAI,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9D,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC3B,SAAS;QACX,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,QAAQ,CACtB,IAAoC,EACpC,cAAyB,EACzB,IAAe;IAEf,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC,CAAC;AACnE,CAAC"}
package/dist/index.js CHANGED
@@ -40,9 +40,39 @@ const ALL_HANDLERS = {
40
40
  ...activityHandlers,
41
41
  ...exportImportHandlers,
42
42
  };
43
+ /**
44
+ * The full tool list costs ~6,000 tokens of context in every session before any
45
+ * work happens. SAGA_TOOLS=core exposes the subset that covers ordinary tracking
46
+ * (~2,300 tokens); handlers stay registered either way, so a client that already
47
+ * knows a tool name can still call it. Unset (or "full") keeps every tool listed.
48
+ */
49
+ const CORE_TOOLS = new Set([
50
+ 'tracker_init',
51
+ 'tracker_dashboard',
52
+ 'project_list',
53
+ 'epic_create',
54
+ 'epic_list',
55
+ 'task_create',
56
+ 'task_list',
57
+ 'task_get',
58
+ 'task_update',
59
+ 'subtask_create',
60
+ 'note_save',
61
+ 'comment_add',
62
+ ]);
63
+ function listedTools() {
64
+ const mode = (process.env.SAGA_TOOLS ?? 'full').trim().toLowerCase();
65
+ if (mode === 'full' || mode === '')
66
+ return ALL_TOOLS;
67
+ if (mode !== 'core') {
68
+ console.error(`Unknown SAGA_TOOLS value '${mode}' — expected 'core' or 'full'. Listing all tools.`);
69
+ return ALL_TOOLS;
70
+ }
71
+ return ALL_TOOLS.filter((t) => CORE_TOOLS.has(t.name));
72
+ }
43
73
  const server = new Server({ name: 'tracker', version: '1.0.0' }, { capabilities: { tools: {} } });
44
74
  server.setRequestHandler(ListToolsRequestSchema, async () => {
45
- return { tools: ALL_TOOLS };
75
+ return { tools: listedTools() };
46
76
  });
47
77
  function friendlyError(msg) {
48
78
  if (msg.includes('UNIQUE constraint failed')) {
@@ -70,7 +100,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
70
100
  }
71
101
  const result = handler(args ?? {});
72
102
  return {
73
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
103
+ // Compact, not pretty-printed: indentation costs ~22% of every response
104
+ // in tokens and buys the model nothing.
105
+ content: [{ type: 'text', text: JSON.stringify(result) }],
74
106
  };
75
107
  }
76
108
  catch (error) {
@@ -90,7 +122,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
90
122
  async function main() {
91
123
  const transport = new StdioServerTransport();
92
124
  await server.connect(transport);
93
- console.error('Tracker MCP Server running on stdio');
125
+ const listed = listedTools().length;
126
+ console.error(`Tracker MCP Server running on stdio (${listed} of ${ALL_TOOLS.length} tools listed` +
127
+ `${listed < ALL_TOOLS.length ? ' — SAGA_TOOLS=core' : ''})`);
94
128
  }
95
129
  process.on('SIGINT', () => {
96
130
  closeDb();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/schema.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SCHEMA_SQL = "\n-- Core hierarchy: projects > epics > tasks > subtasks\n\nCREATE TABLE IF NOT EXISTS projects (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'active'\n CHECK (status IN ('active', 'on_hold', 'completed', 'archived')),\n tags TEXT NOT NULL DEFAULT '[]',\n metadata TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS epics (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'planned'\n CHECK (status IN ('planned', 'in_progress', 'completed', 'cancelled')),\n priority TEXT NOT NULL DEFAULT 'medium'\n CHECK (priority IN ('low', 'medium', 'high', 'critical')),\n sort_order INTEGER NOT NULL DEFAULT 0,\n branch TEXT,\n tags TEXT NOT NULL DEFAULT '[]',\n metadata TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS tasks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n epic_id INTEGER NOT NULL REFERENCES epics(id) ON DELETE CASCADE,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'todo'\n CHECK (status IN ('todo', 'in_progress', 'review', 'done', 'blocked')),\n priority TEXT NOT NULL DEFAULT 'medium'\n CHECK (priority IN ('low', 'medium', 'high', 'critical')),\n sort_order INTEGER NOT NULL DEFAULT 0,\n assigned_to TEXT,\n estimated_hours REAL,\n actual_hours REAL,\n due_date TEXT,\n source_ref TEXT,\n tags TEXT NOT NULL DEFAULT '[]',\n metadata TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS subtasks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n title TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'todo'\n CHECK (status IN ('todo', 'in_progress', 'done')),\n sort_order INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Task dependencies (junction table)\n\nCREATE TABLE IF NOT EXISTS task_dependencies (\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n depends_on_task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (task_id, depends_on_task_id)\n);\n\n-- Comments (threaded discussions on tasks)\n\nCREATE TABLE IF NOT EXISTS comments (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n author TEXT,\n content TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Task templates\n\nCREATE TABLE IF NOT EXISTS templates (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n template_data TEXT NOT NULL DEFAULT '[]',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Unified notes (replaces summaries + status_updates + context)\n\nCREATE TABLE IF NOT EXISTS notes (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n title TEXT NOT NULL,\n content TEXT NOT NULL,\n note_type TEXT NOT NULL DEFAULT 'general'\n CHECK (note_type IN (\n 'general', 'decision', 'context', 'meeting',\n 'technical', 'blocker', 'progress', 'release'\n )),\n related_entity_type TEXT CHECK (related_entity_type IN ('project', 'epic', 'task') OR related_entity_type IS NULL),\n related_entity_id INTEGER,\n tags TEXT NOT NULL DEFAULT '[]',\n metadata TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Automatic activity log\n\nCREATE TABLE IF NOT EXISTS activity_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n entity_type TEXT NOT NULL,\n entity_id INTEGER NOT NULL,\n action TEXT NOT NULL,\n field_name TEXT,\n old_value TEXT,\n new_value TEXT,\n summary TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Indexes\n\nCREATE INDEX IF NOT EXISTS idx_epics_project_id ON epics(project_id);\nCREATE INDEX IF NOT EXISTS idx_tasks_epic_id ON tasks(epic_id);\nCREATE INDEX IF NOT EXISTS idx_subtasks_task_id ON subtasks(task_id);\n\nCREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);\nCREATE INDEX IF NOT EXISTS idx_epics_status ON epics(status);\nCREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);\nCREATE INDEX IF NOT EXISTS idx_subtasks_status ON subtasks(status);\n\nCREATE INDEX IF NOT EXISTS idx_epics_priority ON epics(priority);\nCREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);\n\nCREATE INDEX IF NOT EXISTS idx_epics_sort ON epics(project_id, sort_order);\nCREATE INDEX IF NOT EXISTS idx_tasks_sort ON tasks(epic_id, sort_order);\nCREATE INDEX IF NOT EXISTS idx_subtasks_sort ON subtasks(task_id, sort_order);\n\nCREATE INDEX IF NOT EXISTS idx_notes_type ON notes(note_type);\nCREATE INDEX IF NOT EXISTS idx_notes_entity ON notes(related_entity_type, related_entity_id);\nCREATE INDEX IF NOT EXISTS idx_notes_created ON notes(created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_activity_entity ON activity_log(entity_type, entity_id);\nCREATE INDEX IF NOT EXISTS idx_activity_created ON activity_log(created_at DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_action ON activity_log(action);\n\nCREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to);\nCREATE INDEX IF NOT EXISTS idx_tasks_due ON tasks(due_date);\n\nCREATE INDEX IF NOT EXISTS idx_task_deps_depends ON task_dependencies(depends_on_task_id);\nCREATE INDEX IF NOT EXISTS idx_comments_task ON comments(task_id);\n";
1
+ export declare const SCHEMA_SQL = "\n-- Core hierarchy: projects > epics > tasks > subtasks\n\nCREATE TABLE IF NOT EXISTS projects (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'active'\n CHECK (status IN ('active', 'on_hold', 'completed', 'archived')),\n tags TEXT NOT NULL DEFAULT '[]',\n metadata TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS epics (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'planned'\n CHECK (status IN ('planned', 'in_progress', 'completed', 'cancelled')),\n priority TEXT NOT NULL DEFAULT 'medium'\n CHECK (priority IN ('low', 'medium', 'high', 'critical')),\n sort_order INTEGER NOT NULL DEFAULT 0,\n branch TEXT,\n tags TEXT NOT NULL DEFAULT '[]',\n metadata TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS tasks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n epic_id INTEGER NOT NULL REFERENCES epics(id) ON DELETE CASCADE,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'todo'\n CHECK (status IN ('todo', 'in_progress', 'review', 'done', 'blocked')),\n priority TEXT NOT NULL DEFAULT 'medium'\n CHECK (priority IN ('low', 'medium', 'high', 'critical')),\n sort_order INTEGER NOT NULL DEFAULT 0,\n assigned_to TEXT,\n estimated_hours REAL,\n actual_hours REAL,\n due_date TEXT,\n source_ref TEXT,\n tags TEXT NOT NULL DEFAULT '[]',\n metadata TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS subtasks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n title TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'todo'\n CHECK (status IN ('todo', 'in_progress', 'done')),\n sort_order INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Task dependencies (junction table)\n\nCREATE TABLE IF NOT EXISTS task_dependencies (\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n depends_on_task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (task_id, depends_on_task_id)\n);\n\n-- Comments (threaded discussions on tasks)\n\nCREATE TABLE IF NOT EXISTS comments (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n author TEXT,\n content TEXT NOT NULL,\n is_deleted INTEGER NOT NULL DEFAULT 0,\n deleted_at TEXT,\n deleted_by TEXT,\n delete_reason TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Task templates\n\nCREATE TABLE IF NOT EXISTS templates (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n template_data TEXT NOT NULL DEFAULT '[]',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Unified notes (replaces summaries + status_updates + context)\n\nCREATE TABLE IF NOT EXISTS notes (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n title TEXT NOT NULL,\n content TEXT NOT NULL,\n note_type TEXT NOT NULL DEFAULT 'general'\n CHECK (note_type IN (\n 'general', 'decision', 'context', 'meeting',\n 'technical', 'blocker', 'progress', 'release'\n )),\n related_entity_type TEXT CHECK (related_entity_type IN ('project', 'epic', 'task') OR related_entity_type IS NULL),\n related_entity_id INTEGER,\n tags TEXT NOT NULL DEFAULT '[]',\n metadata TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Automatic activity log\n\nCREATE TABLE IF NOT EXISTS activity_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n entity_type TEXT NOT NULL,\n entity_id INTEGER NOT NULL,\n action TEXT NOT NULL,\n field_name TEXT,\n old_value TEXT,\n new_value TEXT,\n summary TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Indexes\n\nCREATE INDEX IF NOT EXISTS idx_epics_project_id ON epics(project_id);\nCREATE INDEX IF NOT EXISTS idx_tasks_epic_id ON tasks(epic_id);\nCREATE INDEX IF NOT EXISTS idx_subtasks_task_id ON subtasks(task_id);\n\nCREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);\nCREATE INDEX IF NOT EXISTS idx_epics_status ON epics(status);\nCREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);\nCREATE INDEX IF NOT EXISTS idx_subtasks_status ON subtasks(status);\n\nCREATE INDEX IF NOT EXISTS idx_epics_priority ON epics(priority);\nCREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);\n\nCREATE INDEX IF NOT EXISTS idx_epics_sort ON epics(project_id, sort_order);\nCREATE INDEX IF NOT EXISTS idx_tasks_sort ON tasks(epic_id, sort_order);\nCREATE INDEX IF NOT EXISTS idx_subtasks_sort ON subtasks(task_id, sort_order);\n\nCREATE INDEX IF NOT EXISTS idx_notes_type ON notes(note_type);\nCREATE INDEX IF NOT EXISTS idx_notes_entity ON notes(related_entity_type, related_entity_id);\nCREATE INDEX IF NOT EXISTS idx_notes_created ON notes(created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_activity_entity ON activity_log(entity_type, entity_id);\nCREATE INDEX IF NOT EXISTS idx_activity_created ON activity_log(created_at DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_action ON activity_log(action);\n\nCREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to);\nCREATE INDEX IF NOT EXISTS idx_tasks_due ON tasks(due_date);\n\nCREATE INDEX IF NOT EXISTS idx_task_deps_depends ON task_dependencies(depends_on_task_id);\nCREATE INDEX IF NOT EXISTS idx_comments_task ON comments(task_id);\n";