dctracker 1.1.0__tar.gz → 1.2.0__tar.gz

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 (152) hide show
  1. {dctracker-1.1.0/dctracker.egg-info → dctracker-1.2.0}/PKG-INFO +26 -12
  2. {dctracker-1.1.0 → dctracker-1.2.0}/README.md +24 -11
  3. {dctracker-1.1.0 → dctracker-1.2.0}/dct/__init__.py +1 -1
  4. dctracker-1.2.0/dct/assets/__init__.py +19 -0
  5. {dctracker-1.1.0 → dctracker-1.2.0}/dct/cli.py +231 -35
  6. dctracker-1.2.0/dct/config.py +269 -0
  7. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/plans/ingest.py +12 -3
  8. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/projects.py +15 -1
  9. dctracker-1.2.0/dct/core/provenance.py +58 -0
  10. {dctracker-1.1.0 → dctracker-1.2.0}/dct/db.py +62 -0
  11. {dctracker-1.1.0 → dctracker-1.2.0}/dct/hooks/dct-task-mirror.sh +1 -1
  12. dctracker-1.2.0/dct/integrations/__init__.py +5 -0
  13. dctracker-1.1.0/dct/setup.py → dctracker-1.2.0/dct/integrations/claude.py +54 -19
  14. dctracker-1.2.0/dct/mcp/__init__.py +3 -0
  15. dctracker-1.2.0/dct/migrate.py +597 -0
  16. dctracker-1.2.0/dct/paths.py +103 -0
  17. dctracker-1.2.0/dct/server.py +10 -0
  18. dctracker-1.2.0/dct/services/__init__.py +5 -0
  19. dctracker-1.2.0/dct/setup.py +19 -0
  20. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/dct-init/SKILL.md +1 -1
  21. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/track-list/SKILL.md +1 -1
  22. {dctracker-1.1.0 → dctracker-1.2.0}/dct/update.py +12 -8
  23. dctracker-1.2.0/dct/workflows/__init__.py +3 -0
  24. {dctracker-1.1.0 → dctracker-1.2.0/dctracker.egg-info}/PKG-INFO +26 -12
  25. {dctracker-1.1.0 → dctracker-1.2.0}/dctracker.egg-info/SOURCES.txt +16 -1
  26. {dctracker-1.1.0 → dctracker-1.2.0}/dctracker.egg-info/requires.txt +3 -0
  27. {dctracker-1.1.0 → dctracker-1.2.0}/pyproject.toml +8 -2
  28. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_adversarial.py +1 -1
  29. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_cli_web.py +76 -2
  30. dctracker-1.2.0/tests/test_config.py +300 -0
  31. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_hook_bypass.py +1 -1
  32. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_hook_changelog_stop.py +1 -1
  33. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_hook_plan_autoscan.py +1 -1
  34. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_hook_task_mirror.py +7 -5
  35. dctracker-1.2.0/tests/test_migrate.py +679 -0
  36. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_packaging.py +1 -1
  37. dctracker-1.2.0/tests/test_paths.py +189 -0
  38. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_plan_classify.py +68 -6
  39. dctracker-1.2.0/tests/test_provenance.py +153 -0
  40. dctracker-1.2.0/tests/test_restructure.py +66 -0
  41. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_server_acks.py +1 -1
  42. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_server_alwaysload.py +1 -1
  43. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_server_handoff_view.py +1 -1
  44. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_setup.py +1 -1
  45. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_setup_wiring.py +1 -1
  46. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_update.py +48 -7
  47. dctracker-1.2.0/tests/test_windows_support.py +192 -0
  48. dctracker-1.1.0/dct/config.py +0 -170
  49. dctracker-1.1.0/tests/test_config.py +0 -55
  50. {dctracker-1.1.0 → dctracker-1.2.0}/LICENSE +0 -0
  51. {dctracker-1.1.0 → dctracker-1.2.0}/MANIFEST.in +0 -0
  52. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/__init__.py +0 -0
  53. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/analytics.py +0 -0
  54. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/changelog.py +0 -0
  55. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/checkpoints.py +0 -0
  56. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/decisions.py +0 -0
  57. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/engine.py +0 -0
  58. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/handoff.py +0 -0
  59. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/ids.py +0 -0
  60. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/items.py +0 -0
  61. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/plans/__init__.py +0 -0
  62. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/plans/classify.py +0 -0
  63. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/plans/crud.py +0 -0
  64. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/plans/parser.py +0 -0
  65. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/sprint.py +0 -0
  66. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/sprints.py +0 -0
  67. {dctracker-1.1.0 → dctracker-1.2.0}/dct/core/version.py +0 -0
  68. {dctracker-1.1.0 → dctracker-1.2.0}/dct/export.py +0 -0
  69. {dctracker-1.1.0 → dctracker-1.2.0}/dct/hooks/check-changelog-on-stop.sh +0 -0
  70. {dctracker-1.1.0 → dctracker-1.2.0}/dct/hooks/check-changelog.sh +0 -0
  71. {dctracker-1.1.0 → dctracker-1.2.0}/dct/hooks/dct-plan-autoscan.sh +0 -0
  72. {dctracker-1.1.0/dct → dctracker-1.2.0/dct/mcp}/server.py +0 -0
  73. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/clog/SKILL.md +0 -0
  74. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/commit/SKILL.md +0 -0
  75. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/dct-import/SKILL.md +0 -0
  76. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/handoff/SKILL.md +0 -0
  77. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/pickup/SKILL.md +0 -0
  78. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/plan-resolve-uncertain/SKILL.md +0 -0
  79. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/plan-status/SKILL.md +0 -0
  80. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/release/SKILL.md +0 -0
  81. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/track/SKILL.md +0 -0
  82. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/track-resolve/SKILL.md +0 -0
  83. {dctracker-1.1.0 → dctracker-1.2.0}/dct/skills/track-update/SKILL.md +0 -0
  84. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/__init__.py +0 -0
  85. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/app.py +0 -0
  86. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/blueprints/__init__.py +0 -0
  87. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/blueprints/analytics.py +0 -0
  88. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/blueprints/changelog.py +0 -0
  89. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/blueprints/decisions.py +0 -0
  90. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/blueprints/events.py +0 -0
  91. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/blueprints/handoffs.py +0 -0
  92. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/blueprints/home.py +0 -0
  93. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/blueprints/items.py +0 -0
  94. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/blueprints/plans.py +0 -0
  95. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/blueprints/projects.py +0 -0
  96. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/blueprints/sprints.py +0 -0
  97. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/changes.py +0 -0
  98. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/serializers.py +0 -0
  99. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/static/app.css +0 -0
  100. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/static/app.js +0 -0
  101. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/static/vendor/SOURCES.txt +0 -0
  102. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/static/vendor/htmx.min.js +0 -0
  103. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/static/vendor/uPlot.iife.min.js +0 -0
  104. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/static/vendor/uPlot.min.css +0 -0
  105. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/analytics.html +0 -0
  106. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/base.html +0 -0
  107. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/changelog/list.html +0 -0
  108. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/decisions/list.html +0 -0
  109. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/handoffs/list.html +0 -0
  110. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/home.html +0 -0
  111. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/item_detail.html +0 -0
  112. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/items_list.html +0 -0
  113. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/partials/_bars.html +0 -0
  114. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/partials/_checkpoint_steps.html +0 -0
  115. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/partials/_items_table.html +0 -0
  116. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/partials/_plan_section.html +0 -0
  117. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/partials/_project_card.html +0 -0
  118. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/plans/detail.html +0 -0
  119. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/plans/list.html +0 -0
  120. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/project_home.html +0 -0
  121. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/sprints/detail.html +0 -0
  122. {dctracker-1.1.0 → dctracker-1.2.0}/dct/web/templates/sprints/list.html +0 -0
  123. {dctracker-1.1.0 → dctracker-1.2.0}/dctracker.egg-info/dependency_links.txt +0 -0
  124. {dctracker-1.1.0 → dctracker-1.2.0}/dctracker.egg-info/entry_points.txt +0 -0
  125. {dctracker-1.1.0 → dctracker-1.2.0}/dctracker.egg-info/top_level.txt +0 -0
  126. {dctracker-1.1.0 → dctracker-1.2.0}/setup.cfg +0 -0
  127. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_analytics.py +0 -0
  128. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_changelog.py +0 -0
  129. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_changes.py +0 -0
  130. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_checkpoints.py +0 -0
  131. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_cli_prompts.py +0 -0
  132. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_cli_sprints.py +0 -0
  133. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_decisions_crud.py +0 -0
  134. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_engine.py +0 -0
  135. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_export.py +0 -0
  136. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_handoff.py +0 -0
  137. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_ids.py +0 -0
  138. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_ingest_dogfood.py +0 -0
  139. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_items.py +0 -0
  140. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_migration.py +0 -0
  141. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_plan_ingest.py +0 -0
  142. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_plan_parser.py +0 -0
  143. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_plans_crud.py +0 -0
  144. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_projects.py +0 -0
  145. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_sprint.py +0 -0
  146. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_sprints_crud.py +0 -0
  147. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_version.py +0 -0
  148. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_web_analytics.py +0 -0
  149. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_web_app.py +0 -0
  150. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_web_items.py +0 -0
  151. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_web_structural.py +0 -0
  152. {dctracker-1.1.0 → dctracker-1.2.0}/tests/test_web_views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dctracker
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Deterministic Context Tracker (dct) — cross-project tracking, changelog, and release tooling for Claude Code.
5
5
  Author-email: fotodeveloper <dev@fotodeveloper.com>
6
6
  License-Expression: MIT
@@ -21,6 +21,7 @@ Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  Requires-Dist: sqlalchemy>=2.0
23
23
  Requires-Dist: python-ulid>=2.0
24
+ Requires-Dist: platformdirs>=4.9.6; sys_platform == "win32"
24
25
  Provides-Extra: pg
25
26
  Requires-Dist: psycopg[binary]; extra == "pg"
26
27
  Provides-Extra: mcp
@@ -104,9 +105,9 @@ release day the changelog is already written:
104
105
 
105
106
  ```text
106
107
  > /release
107
- 15 unreleased entries → stamped as 1.1.0
108
+ 15 unreleased entries → stamped as 1.4.0
108
109
  changelog.md regenerated (Keep a Changelog format)
109
- release: v1.1.0 committed, project version bumped
110
+ release: v1.4.0 committed, project version bumped
110
111
  ```
111
112
 
112
113
  One command stamps the version, exports [Keep a Changelog](https://keepachangelog.com)
@@ -165,13 +166,13 @@ dct init
165
166
 
166
167
  The interactive wizard walks through the one-time global bootstrap:
167
168
 
168
- 1. **Database backend** — `sqlite` (default, zero setup at `~/.claude/dct.db`) or `postgresql` (you supply the URL). It test-connects before saving.
169
+ 1. **Database backend** — `sqlite` (default, zero setup at the platform data dir, e.g. `~/.local/share/dctools/dct.db`) or `postgresql` (you supply the URL). It test-connects before saving.
169
170
  2. **Register projects** — for each project it asks slug / path / name, then **auto-detects the version** from `VERSION`, `pyproject.toml`, `package.json`, `Cargo.toml`, or `src-tauri/tauri.conf.json`. If none matches, you get an interactive prompt (`0.0.1` / `0.1.0` / `1.0.0` shortcuts); in non-tty contexts it stores `current_version=NULL` and reminds you to set it later. Register more any time with `dct project add`.
170
171
  3. **MCP server** — registered at **user scope** in `~/.claude.json` (via `claude mcp add --scope user`, with a direct-edit fallback), so `/track`, `/clog`, `/release`, and the MCP tools are available in **every** Claude Code session, from any directory.
171
172
  4. **Skills + hooks** — symlinks the skills into `~/.claude/skills/` and the four hooks into `~/.claude/hooks/`. Both ship inside the package; with an editable/dev install the symlinks point at the repo, so a fix goes live without reinstall.
172
173
  5. **Hook wiring** — with your consent, merges the hooks into `~/.claude/settings.json`. The merge is idempotent, backs up the file before the first write, and never touches hooks you already have. Re-run any time with `dct hooks wire`.
173
174
 
174
- `dct init` writes `~/.claude/dct.toml`, `~/.claude.json` (MCP entry), the skill/hook symlinks, and (opt-in) the settings.json wiring — see [docs/architecture.md](docs/architecture.md) for the full config reference and scope model.
175
+ `dct init` writes the config to the platform config dir (`~/.config/dctools/dct.toml`; existing `~/.claude/dct.toml` installs keep working — `dct migrate-home` moves them), `~/.claude.json` (MCP entry), the skill/hook symlinks, and (opt-in) the settings.json wiring — see [docs/architecture.md](docs/architecture.md) for the full config reference and scope model.
175
176
 
176
177
  ### Your first session
177
178
 
@@ -202,7 +203,7 @@ happens in the conversation you're already having.
202
203
 
203
204
  ### Updating
204
205
 
205
- dct runs a passive, offline-safe update check — a cached PyPI probe (refreshed at most once a day, never blocking) surfaces a one-line "newer release available" nudge on the CLI and in the MCP server instructions. Opt out with `[update] check = false` in `~/.claude/dct.toml`. When a newer version exists:
206
+ dct runs a passive, offline-safe update check — a cached PyPI probe (refreshed at most once a day, never blocking) surfaces a one-line "newer release available" nudge on the CLI and in the MCP server instructions. Opt out with `[update] check = false` in the dct config file (`dct config path` shows where it lives). When a newer version exists:
206
207
 
207
208
  ```bash
208
209
  dct upgrade # detects install method (uv tool / pipx / pip), upgrades, re-links skills + hooks
@@ -249,6 +250,19 @@ dct checkpoint add 42 "Write parser"
249
250
  dct checkpoint list 42
250
251
  dct checkpoint done 3 # Mark CP #3 as done
251
252
  dct checkpoint reject 4 # Scope-change, keep in ledger
253
+
254
+ # Sprints (first-class, named units of work; /handoff manages the current-sprint set)
255
+ dct sprint list --status active
256
+ dct sprint create "Payments hardening" --code pay-1 --goal "Close the P1 backlog"
257
+ dct sprint show 3 # Details + linked items
258
+ dct sprint activate 3 # Shortcuts: activate / complete / defer
259
+
260
+ # Handoffs (amend an unconsumed handoff in place; creation is /handoff or MCP)
261
+ dct handoff update 12 --prompt "Wire the toggle into settings next"
262
+
263
+ # Config
264
+ dct config get integrations.claude.task_mirror # Read a dotted config key
265
+ dct config path # Show resolved config/data locations
252
266
  ```
253
267
 
254
268
  ## MCP Tools
@@ -288,7 +302,7 @@ dct web status # show pid + URL
288
302
  dct web stop # SIGTERM the daemon
289
303
  ```
290
304
 
291
- If the preferred port (default `8787`) is busy, the daemon auto-selects the next free port (disable with `--no-fallback`). Configure defaults under `[web]` in `~/.claude/dct.toml`.
305
+ If the preferred port (default `8787`) is busy, the daemon auto-selects the next free port (disable with `--no-fallback`). Configure defaults under `[web]` in the dct config file.
292
306
 
293
307
  ![dct web viewer — read-only dashboard over all registered projects](docs/assets/web-viewer.png)
294
308
 
@@ -333,26 +347,26 @@ Manual wiring reference (full JSON) and the five rules every dct hook follows: [
333
347
 
334
348
  ## Configuration
335
349
 
336
- dct reads `~/.claude/dct.toml`. The minimal config is just the database URL:
350
+ dct reads its config from the platform config dir — `~/.config/dctools/dct.toml` on Linux and macOS, `%LOCALAPPDATA%\dctools\dct.toml` on Windows — with a permanent fallback to a legacy `~/.claude/dct.toml` (run `dct migrate-home` to move an old install). The minimal config is just the database URL:
337
351
 
338
352
  ```toml
339
353
  [database]
340
- url = "sqlite:////Users/you/.claude/dct.db"
354
+ url = "sqlite:////home/you/.local/share/dctools/dct.db"
341
355
  # or: url = "postgresql+psycopg://localhost:5432/dct"
342
356
  ```
343
357
 
344
- The `DCT_DATABASE_URL` environment variable overrides `[database].url`. For the full reference (`[plans]`, `[mirror]`, `[web]` sections), the database schema, MCP scope precedence, and project auto-detection mechanics, see **[docs/architecture.md](docs/architecture.md)**.
358
+ The `DCT_DATABASE_URL` environment variable overrides `[database].url`. For the full reference (`[classification]`, `[integrations.claude]`, `[web]` sections), the database schema, MCP scope precedence, and project auto-detection mechanics, see **[docs/architecture.md](docs/architecture.md)**.
345
359
 
346
360
  ## Testing
347
361
 
348
362
  ```bash
349
- .venv/bin/pytest -q # All tests (670)
363
+ .venv/bin/pytest -q # All tests (938)
350
364
  .venv/bin/pytest tests/test_plan_parser.py -v # Parser only
351
365
  .venv/bin/pytest tests/test_plan_ingest.py -v # Ingest + sticky-resolved semantics
352
366
 
353
367
  # MCP server smoke (boot + tool list)
354
368
  uv run --no-project --with 'mcp[cli]' --with sqlalchemy --with 'psycopg[binary]' \
355
- python -c "import asyncio; from dct.server import mcp; \
369
+ python -c "import asyncio; from dct.mcp.server import mcp; \
356
370
  print(len(asyncio.run(mcp.list_tools())), 'tools')"
357
371
  ```
358
372
 
@@ -62,9 +62,9 @@ release day the changelog is already written:
62
62
 
63
63
  ```text
64
64
  > /release
65
- 15 unreleased entries → stamped as 1.1.0
65
+ 15 unreleased entries → stamped as 1.4.0
66
66
  changelog.md regenerated (Keep a Changelog format)
67
- release: v1.1.0 committed, project version bumped
67
+ release: v1.4.0 committed, project version bumped
68
68
  ```
69
69
 
70
70
  One command stamps the version, exports [Keep a Changelog](https://keepachangelog.com)
@@ -123,13 +123,13 @@ dct init
123
123
 
124
124
  The interactive wizard walks through the one-time global bootstrap:
125
125
 
126
- 1. **Database backend** — `sqlite` (default, zero setup at `~/.claude/dct.db`) or `postgresql` (you supply the URL). It test-connects before saving.
126
+ 1. **Database backend** — `sqlite` (default, zero setup at the platform data dir, e.g. `~/.local/share/dctools/dct.db`) or `postgresql` (you supply the URL). It test-connects before saving.
127
127
  2. **Register projects** — for each project it asks slug / path / name, then **auto-detects the version** from `VERSION`, `pyproject.toml`, `package.json`, `Cargo.toml`, or `src-tauri/tauri.conf.json`. If none matches, you get an interactive prompt (`0.0.1` / `0.1.0` / `1.0.0` shortcuts); in non-tty contexts it stores `current_version=NULL` and reminds you to set it later. Register more any time with `dct project add`.
128
128
  3. **MCP server** — registered at **user scope** in `~/.claude.json` (via `claude mcp add --scope user`, with a direct-edit fallback), so `/track`, `/clog`, `/release`, and the MCP tools are available in **every** Claude Code session, from any directory.
129
129
  4. **Skills + hooks** — symlinks the skills into `~/.claude/skills/` and the four hooks into `~/.claude/hooks/`. Both ship inside the package; with an editable/dev install the symlinks point at the repo, so a fix goes live without reinstall.
130
130
  5. **Hook wiring** — with your consent, merges the hooks into `~/.claude/settings.json`. The merge is idempotent, backs up the file before the first write, and never touches hooks you already have. Re-run any time with `dct hooks wire`.
131
131
 
132
- `dct init` writes `~/.claude/dct.toml`, `~/.claude.json` (MCP entry), the skill/hook symlinks, and (opt-in) the settings.json wiring — see [docs/architecture.md](docs/architecture.md) for the full config reference and scope model.
132
+ `dct init` writes the config to the platform config dir (`~/.config/dctools/dct.toml`; existing `~/.claude/dct.toml` installs keep working — `dct migrate-home` moves them), `~/.claude.json` (MCP entry), the skill/hook symlinks, and (opt-in) the settings.json wiring — see [docs/architecture.md](docs/architecture.md) for the full config reference and scope model.
133
133
 
134
134
  ### Your first session
135
135
 
@@ -160,7 +160,7 @@ happens in the conversation you're already having.
160
160
 
161
161
  ### Updating
162
162
 
163
- dct runs a passive, offline-safe update check — a cached PyPI probe (refreshed at most once a day, never blocking) surfaces a one-line "newer release available" nudge on the CLI and in the MCP server instructions. Opt out with `[update] check = false` in `~/.claude/dct.toml`. When a newer version exists:
163
+ dct runs a passive, offline-safe update check — a cached PyPI probe (refreshed at most once a day, never blocking) surfaces a one-line "newer release available" nudge on the CLI and in the MCP server instructions. Opt out with `[update] check = false` in the dct config file (`dct config path` shows where it lives). When a newer version exists:
164
164
 
165
165
  ```bash
166
166
  dct upgrade # detects install method (uv tool / pipx / pip), upgrades, re-links skills + hooks
@@ -207,6 +207,19 @@ dct checkpoint add 42 "Write parser"
207
207
  dct checkpoint list 42
208
208
  dct checkpoint done 3 # Mark CP #3 as done
209
209
  dct checkpoint reject 4 # Scope-change, keep in ledger
210
+
211
+ # Sprints (first-class, named units of work; /handoff manages the current-sprint set)
212
+ dct sprint list --status active
213
+ dct sprint create "Payments hardening" --code pay-1 --goal "Close the P1 backlog"
214
+ dct sprint show 3 # Details + linked items
215
+ dct sprint activate 3 # Shortcuts: activate / complete / defer
216
+
217
+ # Handoffs (amend an unconsumed handoff in place; creation is /handoff or MCP)
218
+ dct handoff update 12 --prompt "Wire the toggle into settings next"
219
+
220
+ # Config
221
+ dct config get integrations.claude.task_mirror # Read a dotted config key
222
+ dct config path # Show resolved config/data locations
210
223
  ```
211
224
 
212
225
  ## MCP Tools
@@ -246,7 +259,7 @@ dct web status # show pid + URL
246
259
  dct web stop # SIGTERM the daemon
247
260
  ```
248
261
 
249
- If the preferred port (default `8787`) is busy, the daemon auto-selects the next free port (disable with `--no-fallback`). Configure defaults under `[web]` in `~/.claude/dct.toml`.
262
+ If the preferred port (default `8787`) is busy, the daemon auto-selects the next free port (disable with `--no-fallback`). Configure defaults under `[web]` in the dct config file.
250
263
 
251
264
  ![dct web viewer — read-only dashboard over all registered projects](docs/assets/web-viewer.png)
252
265
 
@@ -291,26 +304,26 @@ Manual wiring reference (full JSON) and the five rules every dct hook follows: [
291
304
 
292
305
  ## Configuration
293
306
 
294
- dct reads `~/.claude/dct.toml`. The minimal config is just the database URL:
307
+ dct reads its config from the platform config dir — `~/.config/dctools/dct.toml` on Linux and macOS, `%LOCALAPPDATA%\dctools\dct.toml` on Windows — with a permanent fallback to a legacy `~/.claude/dct.toml` (run `dct migrate-home` to move an old install). The minimal config is just the database URL:
295
308
 
296
309
  ```toml
297
310
  [database]
298
- url = "sqlite:////Users/you/.claude/dct.db"
311
+ url = "sqlite:////home/you/.local/share/dctools/dct.db"
299
312
  # or: url = "postgresql+psycopg://localhost:5432/dct"
300
313
  ```
301
314
 
302
- The `DCT_DATABASE_URL` environment variable overrides `[database].url`. For the full reference (`[plans]`, `[mirror]`, `[web]` sections), the database schema, MCP scope precedence, and project auto-detection mechanics, see **[docs/architecture.md](docs/architecture.md)**.
315
+ The `DCT_DATABASE_URL` environment variable overrides `[database].url`. For the full reference (`[classification]`, `[integrations.claude]`, `[web]` sections), the database schema, MCP scope precedence, and project auto-detection mechanics, see **[docs/architecture.md](docs/architecture.md)**.
303
316
 
304
317
  ## Testing
305
318
 
306
319
  ```bash
307
- .venv/bin/pytest -q # All tests (670)
320
+ .venv/bin/pytest -q # All tests (938)
308
321
  .venv/bin/pytest tests/test_plan_parser.py -v # Parser only
309
322
  .venv/bin/pytest tests/test_plan_ingest.py -v # Ingest + sticky-resolved semantics
310
323
 
311
324
  # MCP server smoke (boot + tool list)
312
325
  uv run --no-project --with 'mcp[cli]' --with sqlalchemy --with 'psycopg[binary]' \
313
- python -c "import asyncio; from dct.server import mcp; \
326
+ python -c "import asyncio; from dct.mcp.server import mcp; \
314
327
  print(len(asyncio.run(mcp.list_tools())), 'tools')"
315
328
  ```
316
329
 
@@ -3,4 +3,4 @@ from importlib.metadata import version, PackageNotFoundError
3
3
  try:
4
4
  __version__ = version("dctracker")
5
5
  except PackageNotFoundError:
6
- __version__ = "1.1.0"
6
+ __version__ = "1.2.0"
@@ -0,0 +1,19 @@
1
+ """Packaged asset locations — the single indirection for skill/hook dirs.
2
+
3
+ The PHYSICAL directories stay at dct/skills and dct/hooks: the host's
4
+ ~/.claude symlinks point into them, so moving them is a breaking change
5
+ reserved for 2.0.0 (ADR #52 froze every public surface for 1.2.0). When
6
+ that window opens, this module is the one place that changes.
7
+ """
8
+
9
+ from pathlib import Path
10
+
11
+ _PKG_ROOT = Path(__file__).resolve().parent.parent
12
+
13
+
14
+ def skills_dir() -> Path:
15
+ return _PKG_ROOT / "skills"
16
+
17
+
18
+ def hooks_dir() -> Path:
19
+ return _PKG_ROOT / "hooks"