taskops-cli 0.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 (198) hide show
  1. taskops_cli-0.2.0/LICENSE +21 -0
  2. taskops_cli-0.2.0/PKG-INFO +291 -0
  3. taskops_cli-0.2.0/README.md +265 -0
  4. taskops_cli-0.2.0/pyproject.toml +129 -0
  5. taskops_cli-0.2.0/setup.cfg +4 -0
  6. taskops_cli-0.2.0/src/taskops/__init__.py +39 -0
  7. taskops_cli-0.2.0/src/taskops/_clock.py +32 -0
  8. taskops_cli-0.2.0/src/taskops/_errors.py +160 -0
  9. taskops_cli-0.2.0/src/taskops/_ids.py +63 -0
  10. taskops_cli-0.2.0/src/taskops/_types.py +106 -0
  11. taskops_cli-0.2.0/src/taskops/_version.py +7 -0
  12. taskops_cli-0.2.0/src/taskops/assets/GUIDE.md +220 -0
  13. taskops_cli-0.2.0/src/taskops/contracts/__init__.py +96 -0
  14. taskops_cli-0.2.0/src/taskops/contracts/_fields.py +113 -0
  15. taskops_cli-0.2.0/src/taskops/contracts/actor.py +30 -0
  16. taskops_cli-0.2.0/src/taskops/contracts/board.py +142 -0
  17. taskops_cli-0.2.0/src/taskops/contracts/commit.py +36 -0
  18. taskops_cli-0.2.0/src/taskops/contracts/day.py +150 -0
  19. taskops_cli-0.2.0/src/taskops/contracts/dep.py +23 -0
  20. taskops_cli-0.2.0/src/taskops/contracts/event.py +58 -0
  21. taskops_cli-0.2.0/src/taskops/contracts/gitstate.py +45 -0
  22. taskops_cli-0.2.0/src/taskops/contracts/index.py +37 -0
  23. taskops_cli-0.2.0/src/taskops/contracts/lease.py +43 -0
  24. taskops_cli-0.2.0/src/taskops/contracts/log.py +59 -0
  25. taskops_cli-0.2.0/src/taskops/contracts/remote.py +48 -0
  26. taskops_cli-0.2.0/src/taskops/contracts/results.py +84 -0
  27. taskops_cli-0.2.0/src/taskops/contracts/task.py +94 -0
  28. taskops_cli-0.2.0/src/taskops/contracts/tools.py +110 -0
  29. taskops_cli-0.2.0/src/taskops/contracts/wire.py +60 -0
  30. taskops_cli-0.2.0/src/taskops/engine/__init__.py +34 -0
  31. taskops_cli-0.2.0/src/taskops/engine/_blocks.py +48 -0
  32. taskops_cli-0.2.0/src/taskops/engine/_briefs.py +92 -0
  33. taskops_cli-0.2.0/src/taskops/engine/_chunks.py +66 -0
  34. taskops_cli-0.2.0/src/taskops/engine/_closed.py +65 -0
  35. taskops_cli-0.2.0/src/taskops/engine/_entries.py +126 -0
  36. taskops_cli-0.2.0/src/taskops/engine/_events.py +55 -0
  37. taskops_cli-0.2.0/src/taskops/engine/_opened.py +51 -0
  38. taskops_cli-0.2.0/src/taskops/engine/_process.py +80 -0
  39. taskops_cli-0.2.0/src/taskops/engine/_prompts.py +98 -0
  40. taskops_cli-0.2.0/src/taskops/engine/_stream.py +129 -0
  41. taskops_cli-0.2.0/src/taskops/engine/activity.py +94 -0
  42. taskops_cli-0.2.0/src/taskops/engine/bus.py +42 -0
  43. taskops_cli-0.2.0/src/taskops/engine/commitline.py +110 -0
  44. taskops_cli-0.2.0/src/taskops/engine/day.py +142 -0
  45. taskops_cli-0.2.0/src/taskops/engine/diffstat.py +63 -0
  46. taskops_cli-0.2.0/src/taskops/engine/gitio.py +108 -0
  47. taskops_cli-0.2.0/src/taskops/engine/gitstate.py +96 -0
  48. taskops_cli-0.2.0/src/taskops/engine/history.py +76 -0
  49. taskops_cli-0.2.0/src/taskops/engine/identity.py +84 -0
  50. taskops_cli-0.2.0/src/taskops/engine/log.py +68 -0
  51. taskops_cli-0.2.0/src/taskops/engine/machine.py +160 -0
  52. taskops_cli-0.2.0/src/taskops/engine/narrate.py +91 -0
  53. taskops_cli-0.2.0/src/taskops/engine/project.py +57 -0
  54. taskops_cli-0.2.0/src/taskops/engine/replay.py +142 -0
  55. taskops_cli-0.2.0/src/taskops/engine/reports.py +67 -0
  56. taskops_cli-0.2.0/src/taskops/engine/scheduler.py +135 -0
  57. taskops_cli-0.2.0/src/taskops/engine/transcript.py +127 -0
  58. taskops_cli-0.2.0/src/taskops/engine/wire.py +92 -0
  59. taskops_cli-0.2.0/src/taskops/engine/worker.py +115 -0
  60. taskops_cli-0.2.0/src/taskops/py.typed +0 -0
  61. taskops_cli-0.2.0/src/taskops/render/__init__.py +40 -0
  62. taskops_cli-0.2.0/src/taskops/render/_closed_days.py +64 -0
  63. taskops_cli-0.2.0/src/taskops/render/_dossier.py +68 -0
  64. taskops_cli-0.2.0/src/taskops/render/_opened.py +64 -0
  65. taskops_cli-0.2.0/src/taskops/render/_sections.py +51 -0
  66. taskops_cli-0.2.0/src/taskops/render/_tasklist.py +72 -0
  67. taskops_cli-0.2.0/src/taskops/render/_text.py +74 -0
  68. taskops_cli-0.2.0/src/taskops/render/_verbatim.py +65 -0
  69. taskops_cli-0.2.0/src/taskops/render/ansi.py +92 -0
  70. taskops_cli-0.2.0/src/taskops/render/board.py +55 -0
  71. taskops_cli-0.2.0/src/taskops/render/day.py +102 -0
  72. taskops_cli-0.2.0/src/taskops/render/dispatch.py +104 -0
  73. taskops_cli-0.2.0/src/taskops/render/inbox.py +27 -0
  74. taskops_cli-0.2.0/src/taskops/render/log.py +47 -0
  75. taskops_cli-0.2.0/src/taskops/render/recover.py +64 -0
  76. taskops_cli-0.2.0/src/taskops/render/report.py +51 -0
  77. taskops_cli-0.2.0/src/taskops/render/reports.py +57 -0
  78. taskops_cli-0.2.0/src/taskops/render/results.py +96 -0
  79. taskops_cli-0.2.0/src/taskops/render/session.py +75 -0
  80. taskops_cli-0.2.0/src/taskops/render/task.py +88 -0
  81. taskops_cli-0.2.0/src/taskops/render/tasklist.py +65 -0
  82. taskops_cli-0.2.0/src/taskops/storage/__init__.py +36 -0
  83. taskops_cli-0.2.0/src/taskops/storage/_ddl.py +78 -0
  84. taskops_cli-0.2.0/src/taskops/storage/_delivered.py +51 -0
  85. taskops_cli-0.2.0/src/taskops/storage/_deps.py +69 -0
  86. taskops_cli-0.2.0/src/taskops/storage/_events.py +127 -0
  87. taskops_cli-0.2.0/src/taskops/storage/_leases.py +108 -0
  88. taskops_cli-0.2.0/src/taskops/storage/_rows.py +90 -0
  89. taskops_cli-0.2.0/src/taskops/storage/_tasks.py +124 -0
  90. taskops_cli-0.2.0/src/taskops/storage/locate.py +76 -0
  91. taskops_cli-0.2.0/src/taskops/storage/schema.py +66 -0
  92. taskops_cli-0.2.0/src/taskops/storage/store.py +120 -0
  93. taskops_cli-0.2.0/src/taskops/storage/sync.py +139 -0
  94. taskops_cli-0.2.0/src/taskops/transports/__init__.py +6 -0
  95. taskops_cli-0.2.0/src/taskops/transports/cli/__init__.py +0 -0
  96. taskops_cli-0.2.0/src/taskops/transports/cli/commands/__init__.py +3 -0
  97. taskops_cli-0.2.0/src/taskops/transports/cli/commands/_digest.py +64 -0
  98. taskops_cli-0.2.0/src/taskops/transports/cli/commands/_serve_init.py +65 -0
  99. taskops_cli-0.2.0/src/taskops/transports/cli/commands/_shared.py +50 -0
  100. taskops_cli-0.2.0/src/taskops/transports/cli/commands/_tasks_args.py +112 -0
  101. taskops_cli-0.2.0/src/taskops/transports/cli/commands/_window.py +45 -0
  102. taskops_cli-0.2.0/src/taskops/transports/cli/commands/ask.py +27 -0
  103. taskops_cli-0.2.0/src/taskops/transports/cli/commands/dispatch.py +43 -0
  104. taskops_cli-0.2.0/src/taskops/transports/cli/commands/init.py +48 -0
  105. taskops_cli-0.2.0/src/taskops/transports/cli/commands/log.py +22 -0
  106. taskops_cli-0.2.0/src/taskops/transports/cli/commands/plan.py +43 -0
  107. taskops_cli-0.2.0/src/taskops/transports/cli/commands/pushpull.py +53 -0
  108. taskops_cli-0.2.0/src/taskops/transports/cli/commands/recover.py +30 -0
  109. taskops_cli-0.2.0/src/taskops/transports/cli/commands/remote.py +56 -0
  110. taskops_cli-0.2.0/src/taskops/transports/cli/commands/report.py +82 -0
  111. taskops_cli-0.2.0/src/taskops/transports/cli/commands/run_.py +60 -0
  112. taskops_cli-0.2.0/src/taskops/transports/cli/commands/serve.py +74 -0
  113. taskops_cli-0.2.0/src/taskops/transports/cli/commands/sync.py +22 -0
  114. taskops_cli-0.2.0/src/taskops/transports/cli/commands/tasks.py +79 -0
  115. taskops_cli-0.2.0/src/taskops/transports/cli/commands/ui.py +72 -0
  116. taskops_cli-0.2.0/src/taskops/transports/cli/commands/update.py +25 -0
  117. taskops_cli-0.2.0/src/taskops/transports/cli/main.py +85 -0
  118. taskops_cli-0.2.0/src/taskops/transports/hooks/__init__.py +15 -0
  119. taskops_cli-0.2.0/src/taskops/transports/hooks/__main__.py +63 -0
  120. taskops_cli-0.2.0/src/taskops/transports/hooks/_args.py +28 -0
  121. taskops_cli-0.2.0/src/taskops/transports/hooks/claude.py +78 -0
  122. taskops_cli-0.2.0/src/taskops/transports/hooks/commit.py +61 -0
  123. taskops_cli-0.2.0/src/taskops/transports/hooks/events.py +107 -0
  124. taskops_cli-0.2.0/src/taskops/transports/hooks/record.py +55 -0
  125. taskops_cli-0.2.0/src/taskops/transports/http/__init__.py +21 -0
  126. taskops_cli-0.2.0/src/taskops/transports/http/_handler.py +132 -0
  127. taskops_cli-0.2.0/src/taskops/transports/http/_wire.py +79 -0
  128. taskops_cli-0.2.0/src/taskops/transports/http/_wsframes.py +116 -0
  129. taskops_cli-0.2.0/src/taskops/transports/http/agentapi.py +69 -0
  130. taskops_cli-0.2.0/src/taskops/transports/http/api.py +122 -0
  131. taskops_cli-0.2.0/src/taskops/transports/http/exchange.py +80 -0
  132. taskops_cli-0.2.0/src/taskops/transports/http/live.py +160 -0
  133. taskops_cli-0.2.0/src/taskops/transports/http/policy.py +110 -0
  134. taskops_cli-0.2.0/src/taskops/transports/http/projects.py +116 -0
  135. taskops_cli-0.2.0/src/taskops/transports/http/reports.py +75 -0
  136. taskops_cli-0.2.0/src/taskops/transports/http/router.py +90 -0
  137. taskops_cli-0.2.0/src/taskops/transports/http/server.py +50 -0
  138. taskops_cli-0.2.0/src/taskops/transports/http/static.py +86 -0
  139. taskops_cli-0.2.0/src/taskops/transports/http/ui/app.js +59 -0
  140. taskops_cli-0.2.0/src/taskops/transports/http/ui/index.html +23 -0
  141. taskops_cli-0.2.0/src/taskops/transports/http/ui/style.css +1 -0
  142. taskops_cli-0.2.0/src/taskops/transports/http/websocket.py +60 -0
  143. taskops_cli-0.2.0/src/taskops/transports/mcp/__init__.py +19 -0
  144. taskops_cli-0.2.0/src/taskops/transports/mcp/__main__.py +7 -0
  145. taskops_cli-0.2.0/src/taskops/transports/mcp/_descriptions.py +102 -0
  146. taskops_cli-0.2.0/src/taskops/transports/mcp/_reads.py +82 -0
  147. taskops_cli-0.2.0/src/taskops/transports/mcp/_writes.py +72 -0
  148. taskops_cli-0.2.0/src/taskops/transports/mcp/answers.py +44 -0
  149. taskops_cli-0.2.0/src/taskops/transports/mcp/arguments.py +104 -0
  150. taskops_cli-0.2.0/src/taskops/transports/mcp/dispatch.py +47 -0
  151. taskops_cli-0.2.0/src/taskops/transports/mcp/protocol.py +83 -0
  152. taskops_cli-0.2.0/src/taskops/transports/mcp/schema.py +50 -0
  153. taskops_cli-0.2.0/src/taskops/transports/mcp/server.py +49 -0
  154. taskops_cli-0.2.0/src/taskops/transports/mcp/tools.py +66 -0
  155. taskops_cli-0.2.0/src/taskops/usecases/__init__.py +56 -0
  156. taskops_cli-0.2.0/src/taskops/usecases/_entry.py +84 -0
  157. taskops_cli-0.2.0/src/taskops/usecases/_facts.py +49 -0
  158. taskops_cli-0.2.0/src/taskops/usecases/_freeing.py +93 -0
  159. taskops_cli-0.2.0/src/taskops/usecases/_gitignore.py +94 -0
  160. taskops_cli-0.2.0/src/taskops/usecases/_mirroring.py +57 -0
  161. taskops_cli-0.2.0/src/taskops/usecases/_narrating.py +77 -0
  162. taskops_cli-0.2.0/src/taskops/usecases/_project.py +67 -0
  163. taskops_cli-0.2.0/src/taskops/usecases/_range.py +102 -0
  164. taskops_cli-0.2.0/src/taskops/usecases/_reasons.py +54 -0
  165. taskops_cli-0.2.0/src/taskops/usecases/_remotefile.py +62 -0
  166. taskops_cli-0.2.0/src/taskops/usecases/_reportsync.py +108 -0
  167. taskops_cli-0.2.0/src/taskops/usecases/_routing.py +91 -0
  168. taskops_cli-0.2.0/src/taskops/usecases/_wireclient.py +141 -0
  169. taskops_cli-0.2.0/src/taskops/usecases/ask.py +41 -0
  170. taskops_cli-0.2.0/src/taskops/usecases/claim.py +104 -0
  171. taskops_cli-0.2.0/src/taskops/usecases/dispatch.py +160 -0
  172. taskops_cli-0.2.0/src/taskops/usecases/dossier.py +155 -0
  173. taskops_cli-0.2.0/src/taskops/usecases/edit.py +71 -0
  174. taskops_cli-0.2.0/src/taskops/usecases/exchange.py +86 -0
  175. taskops_cli-0.2.0/src/taskops/usecases/feed.py +138 -0
  176. taskops_cli-0.2.0/src/taskops/usecases/guard.py +122 -0
  177. taskops_cli-0.2.0/src/taskops/usecases/hooks.py +127 -0
  178. taskops_cli-0.2.0/src/taskops/usecases/index.py +62 -0
  179. taskops_cli-0.2.0/src/taskops/usecases/ingest.py +60 -0
  180. taskops_cli-0.2.0/src/taskops/usecases/log.py +134 -0
  181. taskops_cli-0.2.0/src/taskops/usecases/narration.py +91 -0
  182. taskops_cli-0.2.0/src/taskops/usecases/plan.py +128 -0
  183. taskops_cli-0.2.0/src/taskops/usecases/pushpull.py +119 -0
  184. taskops_cli-0.2.0/src/taskops/usecases/recover.py +84 -0
  185. taskops_cli-0.2.0/src/taskops/usecases/remote.py +78 -0
  186. taskops_cli-0.2.0/src/taskops/usecases/report.py +87 -0
  187. taskops_cli-0.2.0/src/taskops/usecases/reportfile.py +106 -0
  188. taskops_cli-0.2.0/src/taskops/usecases/session.py +134 -0
  189. taskops_cli-0.2.0/src/taskops/usecases/setup.py +92 -0
  190. taskops_cli-0.2.0/src/taskops/usecases/sync.py +78 -0
  191. taskops_cli-0.2.0/src/taskops/usecases/update.py +123 -0
  192. taskops_cli-0.2.0/src/taskops/usecases/view.py +112 -0
  193. taskops_cli-0.2.0/src/taskops_cli.egg-info/PKG-INFO +291 -0
  194. taskops_cli-0.2.0/src/taskops_cli.egg-info/SOURCES.txt +196 -0
  195. taskops_cli-0.2.0/src/taskops_cli.egg-info/dependency_links.txt +1 -0
  196. taskops_cli-0.2.0/src/taskops_cli.egg-info/entry_points.txt +2 -0
  197. taskops_cli-0.2.0/src/taskops_cli.egg-info/requires.txt +3 -0
  198. taskops_cli-0.2.0/src/taskops_cli.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bernardo Castro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,291 @@
1
+ Metadata-Version: 2.4
2
+ Name: taskops-cli
3
+ Version: 0.2.0
4
+ Summary: The coordination substrate for Claude Code agents: persistent tasks with a dependency DAG, atomic claims, git-bound commits, and a live board.
5
+ Author-email: Bernardo Castro <me@bernardocastro.dev>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/bernatch22/taskops
8
+ Project-URL: Repository, https://github.com/bernatch22/taskops
9
+ Project-URL: Changelog, https://github.com/bernatch22/taskops/blob/master/CHANGELOG.md
10
+ Keywords: claude-code,mcp,agents,task-management,orchestration,multi-agent,kanban,developer-tools
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Typing :: Typed
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: live
24
+ Requires-Dist: websockets>=12; extra == "live"
25
+ Dynamic: license-file
26
+
27
+ # taskops
28
+
29
+ **The shared task board for Claude Code agents.** Persistent tasks with a dependency graph, atomic claims that survive a crashed agent, every commit bound to the work that motivated it, daily reports written by AI from a log that cannot lie — and a team mode where agents on different machines can never grab the same card.
30
+
31
+ Zero runtime dependencies. One SQLite file per repository, one committed event log. A server is optional.
32
+
33
+ ```
34
+ claim ──▶ work ──▶ git commit ──▶ done
35
+ │ │ │
36
+ a lease, the spec, the Task: trailer whatever was waiting
37
+ and a warning if is injected on you becomes ready
38
+ another agent is for you for everyone
39
+ in your files
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Why
45
+
46
+ Coding agents are good at working and terrible at remembering. A session plans five tasks, finishes two, and dies — and with it dies the plan, the reasoning, and the claim on the work. Run several agents at once and it gets worse: two of them edit the same file, both mark things "done" that nobody can verify, and tomorrow no one can say what actually happened.
47
+
48
+ taskops is the durable half. Tasks live **in the repository**, not in anybody's session. Claims are **leases** that expire when a process dies. Commits are **bound to tasks by enforcement**, not convention. And because the source of truth is an append-only event log with content-hashed ids, the whole board travels through `git push` — or through a shared server when you want claims to be atomic across machines.
49
+
50
+ Two rules are enforced rather than suggested:
51
+
52
+ - **A commit belongs to a claimed task.** A hook denies an agent's commit when it holds no claim, and rewrites its own `git commit -m …` to carry a `Task: tk-4f2a9c` trailer. The agent never writes the trailer and never sees an error about it.
53
+ - **`done` requires evidence.** Closing a task with no commit bound to it is refused — otherwise "done" means only that an agent said so, which is exactly what reading a board instead of the diff is meant to avoid. Research and decisions close with `no_code` plus a written justification, which is recorded.
54
+
55
+ ## Install
56
+
57
+ ```sh
58
+ pip install taskops # or: uv tool install taskops
59
+
60
+ cd your-repo
61
+ taskops init # creates .taskops/, installs the git hooks
62
+ claude mcp add taskops -- python3 -m taskops.transports.mcp
63
+ ```
64
+
65
+ For the Claude Code hooks and the `/taskops:*` skills, install the plugin from `plugin/`. `taskops init` is safe to re-run — re-running is how you repair a fresh clone, since `.git/hooks` is never tracked.
66
+
67
+ ## Quickstart
68
+
69
+ Ask Claude, in plain language:
70
+
71
+ > Read the auth module and plan the work to add refresh tokens. Use taskops.
72
+
73
+ It calls `taskops_plan` once with the whole tree — tasks, specs, dependencies — and the board exists. Then:
74
+
75
+ > Claim the next task and start.
76
+
77
+ It calls `taskops_next`, receives the spec, the exact branch to create, and a warning if another agent is editing the same files. It works, commits (the trailer is added for it), and closes with `taskops_update`. Whatever was blocked on that task becomes ready — for every agent, automatically.
78
+
79
+ You watch it happen:
80
+
81
+ ```sh
82
+ taskops ui # the live board → http://127.0.0.1:2140
83
+ ```
84
+
85
+ ---
86
+
87
+ ## The CLI — for people
88
+
89
+ Eleven commands, all of them yours. Agents never use the CLI (they have MCP), and the hook wiring is a separate module nobody types.
90
+
91
+ | Command | What it does |
92
+ |---|---|
93
+ | `taskops init` | Create `.taskops/`, install the git hooks, write the agent guide. |
94
+ | `taskops ui` | The live web interface: board, activity timeline, reports. |
95
+ | `taskops tasks` | List, read, create, edit and close tasks. |
96
+ | `taskops report` | Board, standup, or a written dossier of a day, a range, or everything. |
97
+ | `taskops run` | Run cards with headless Claude workers *(experimental, billed)*. |
98
+ | `taskops recover` | Release cards held by workers that went silent. |
99
+ | `taskops sync` | Reconcile with the committed event log (the git path). |
100
+ | `taskops serve` | Host many projects' boards on one port, one token each. |
101
+ | `taskops remote` | Point this project at a server. |
102
+ | `taskops push` / `pull` | Exchange events and reports with the server. |
103
+
104
+ ### Working with tasks
105
+
106
+ ```sh
107
+ taskops tasks # open tasks; a finished project lists its closed ones
108
+ taskops tasks show tk-4f2a9c # one card in full: spec, thread, commits, dependencies
109
+ taskops tasks add "Fix the timeout" --spec "DONE = the retry test passes" --files api/client.py
110
+ taskops tasks edit tk-4f2a9c --priority 0 # title, spec and priority stay correctable
111
+ taskops tasks done tk-4f2a9c -m "landed; expiry is a column, not a job"
112
+ taskops tasks release tk-4f2a9c -m "out of depth — the parser needs someone who knows the grammar"
113
+ taskops tasks search "refresh"
114
+ ```
115
+
116
+ `tasks done` goes through the same guard an agent faces: no commit bound, no close.
117
+
118
+ ### Reports
119
+
120
+ Every report is a **projection of the event log** — generated, so it cannot be out of date and cannot flatter anyone.
121
+
122
+ ```sh
123
+ taskops report # the board
124
+ taskops report standup --since 24h # what changed, per actor, and what needs a human
125
+ taskops report day # one calendar day in full: what closed, with every
126
+ # commit's diff size, what opened, the whole conversation
127
+ taskops report range --last 7d # a week, grouped by day
128
+ taskops report all # the entire project, from the first event
129
+ ```
130
+
131
+ Add `--digest` and Claude reads the dossier and writes the narration — what was asked versus what was delivered, card by card, decisions, surprises, and what is still owed. It streams into your terminal as it is written, uses your existing Claude Code login (never an API key), and lands in `.taskops/reports/<label>.md`, committed like source. The facts are written before the model is called, so a failed narration never costs the record.
132
+
133
+ ```sh
134
+ taskops report day --digest # yesterday, explained
135
+ taskops report all --digest # the whole project, as a document you read instead of the git log
136
+ ```
137
+
138
+ ### Running a fleet
139
+
140
+ ```sh
141
+ taskops run --dry-run # which cards would get a worker — free
142
+ taskops run tk-a99e3f tk-4c84b3 # one headless Claude per card, each in its own git worktree
143
+ taskops recover # a killed worker's card returns to the queue
144
+ ```
145
+
146
+ Each worker gets its own **worktree** on its own branch — a lease coordinates who owns a *task*, not whose bytes are on disk, and the worktree is what keeps parallel agents from overwriting each other. Workers never inherit your `ANTHROPIC_API_KEY`: they use the subscription you are already logged into, and `--use-api-key` is an explicit opt-in.
147
+
148
+ The cheaper default is `dispatch` through MCP: the orchestrating session spawns its **own sub-agents**, one per prepared brief, on the subscription that is already paid for.
149
+
150
+ ---
151
+
152
+ ## The MCP tools — for agents
153
+
154
+ Seven tools. The `inputSchema` of each is generated from its typed contract, so a parameter cannot exist on the wire without existing in the dispatch. Deliberately short: every tool costs every connected agent context.
155
+
156
+ | Tool | What it does |
157
+ |---|---|
158
+ | `taskops_next` | Claim work. Returns the spec, the branch to create, the agent's inbox, and a collision warning naming anyone else in your files. Says *why* when there is nothing. |
159
+ | `taskops_update` | Progress, a comment, a close, a handoff — and `mentions`, which is how agents message each other. |
160
+ | `taskops_ask` | One task in full, or free-text search across titles, specs and comments. |
161
+ | `taskops_plan` | A whole decomposition in one call: tasks, tree, dependencies. |
162
+ | `taskops_dispatch` | Prepare worker briefs — assign cards, create worktrees. The caller spawns its own sub-agents. |
163
+ | `taskops_recover` | Hand back the cards of workers that died. |
164
+ | `taskops_report` | `board`, `standup`, `day`, `range` — generated, never stale. |
165
+
166
+ ### What a session actually does
167
+
168
+ Plan once, with dependencies referencing earlier entries by index:
169
+
170
+ ```
171
+ taskops_plan tasks=[
172
+ {"title": "Add the refresh token table",
173
+ "spec": "Migration plus model. DONE = a token round-trips with an expiry. Do not touch sessions.",
174
+ "files": ["db/schema.sql"]},
175
+ {"title": "Issue a refresh token on login",
176
+ "spec": "Return one alongside the access token. DONE = the login test asserts both.",
177
+ "files": ["auth/login.py"],
178
+ "after": [0]}
179
+ ]
180
+ ```
181
+
182
+ Claim — and note what rides along:
183
+
184
+ ```
185
+ taskops_next
186
+ → # tk-4f2a9c — Add the refresh token table
187
+ Claimed. Create the branch and work there:
188
+ git switch -c tk/tk-4f2a9c/add-the-refresh-token-table
189
+
190
+ ### 📬 1 message(s) for you
191
+ **agent:ana/api-1** on tk-8b31d0 (3m ago): careful with token.py
192
+
193
+ ### ⚠ Also touching these files
194
+ - tk-8b31d0 (in_progress, agent:ana/api-1) — Issue tokens
195
+ _Message them before editing shared files, not after the merge._
196
+ ```
197
+
198
+ Talk to another developer's agent — there is no sixth chat tool on purpose. A message about a task lives in that task's thread, where it is still findable in three weeks:
199
+
200
+ ```
201
+ taskops_update task=tk-8b31d0
202
+ comment="I'm rewriting the token model in models/token.py — hold off until I land it."
203
+ mentions="agent:ana/api-1"
204
+ ```
205
+
206
+ Delivery is honest about what Claude Code allows: a session cannot be pushed to mid-turn, so the inbox arrives through hooks on the recipient's **very next tool call** — seconds, for a working agent. Delivery is tracked per `(actor, event)`, never by a timestamp cursor, so a message that arrives out of order is never silently skipped.
207
+
208
+ Discover a prerequisite mid-task, and record it in the graph rather than in prose:
209
+
210
+ ```
211
+ taskops_update task=tk-4f2a9c blocked_on=tk-9d21aa
212
+ comment="the schema migration has to land first"
213
+ ```
214
+
215
+ A dependency that lives only in a comment is one the scheduler will walk somebody else straight into.
216
+
217
+ ### How agents avoid each other
218
+
219
+ - **A claim is a lease.** Every taskops call renews it; if the process dies, the lease lapses and the task returns to the queue. The TTL bounds a *crash*, never a slow task.
220
+ - **A race is an INSERT.** Two agents claiming one task are two inserts on one primary key; SQLite decides. Verified with fifty real threads: exactly one winner.
221
+ - **Files repel.** A task whose declared `files` overlap what a live agent is editing sorts behind everything else, regardless of priority — the cheapest place to prevent a merge conflict is before either side starts.
222
+ - **Assignment hides.** A card assigned to a worker is invisible to every other agent — not sorted last, *gone* — which is what makes "this one is yours" mean something.
223
+
224
+ ---
225
+
226
+ ## Working as a team
227
+
228
+ ### Through git — no server at all
229
+
230
+ `.taskops/events.jsonl` is committed. Append-only, content-hashed ids: appending to different ends of a file is the one edit git merges without help, and importing the same event twice is a no-op. Two developers' boards converge through ordinary `git pull`; the post-merge hook reconciles automatically. Events are facts about the past — the union of two logs *is* the correct log, so there is nothing to conflict.
231
+
232
+ ### Through a server — when claims must be atomic across machines
233
+
234
+ ```sh
235
+ # somewhere reachable
236
+ taskops serve init myproject --root ~/taskops-server # prints the project token once
237
+ taskops serve --root ~/taskops-server --host 0.0.0.0
238
+
239
+ # each developer
240
+ taskops remote add https://boards.example.com/myproject --token <token>
241
+ taskops push # send your events up, take theirs down
242
+ taskops pull
243
+ ```
244
+
245
+ One port, many projects, one token each. No token, no access — not even reads. With a remote configured, agents' claims and closes execute **in the server's database**: two agents on two continents asking for the same card are two inserts on one primary key again, and exactly one wins. There is no window.
246
+
247
+ Reports sync under a rule that refuses to destroy work: the narration is the one part a machine cannot regenerate, so a conflicting report is a `409` naming both versions — never a silent overwrite. A hand-written narration can never be clobbered by a generated one.
248
+
249
+ The token is the trust boundary. It is minted by the server, stored at `0600`, covered by the ignore rules so it cannot reach git, and never printed twice.
250
+
251
+ ---
252
+
253
+ ## The web interface
254
+
255
+ `taskops ui` serves a live board over the same contracts everything else uses. No polling from the browser — a WebSocket (with SSE fallback) pushes every change, and the green dot ticks on each event.
256
+
257
+ - **Board** — kanban that moves by itself. Click a card for the spec, the thread, the dependency graph, the commits with their files, and a reply box that reaches agents' inboxes.
258
+ - **Activity** — the event log as a history: a filterable timeline, and a roll-up per actor ranked by tasks touched rather than noise made.
259
+ - **Reports** — the daily dossiers, rendered. Generate a narration from the browser and **watch it being written**, streamed over the same socket.
260
+
261
+ The UI ships inside the wheel as a committed bundle — `pip install taskops` serves the board with no Node toolchain anywhere.
262
+
263
+ ## Architecture, briefly
264
+
265
+ ```
266
+ contracts/ every payload that crosses a boundary, as TypedDicts. Zero logic.
267
+ storage/ the ONLY package that writes SQL. One SQLite file per repo, WAL.
268
+ engine/ the decisions: state machine, scheduler, projections, git, buses.
269
+ render/ contract → text. Pure functions, no I/O.
270
+ usecases/ one module per verb. Sync, all the way down.
271
+ transports/ cli · mcp · http · hooks — thin, and forbidden to reach deeper.
272
+ ```
273
+
274
+ The rules that matter are executable — an architecture test suite fails the build when one breaks:
275
+
276
+ - **The event log is the truth; SQLite is a cache.** Delete the database and rebuild it from the log. Every view — board, standup, report — is a projection, so a new view is a rendering decision, never a migration.
277
+ - **One transport per audience.** The CLI is the developer's, MCP is the agent's, `taskops.transports.hooks` is what git and Claude Code invoke. None of them contains logic; all of them call the same use cases, which is why three surfaces cannot disagree about what `done` requires.
278
+ - **The state machine has one home.** A transition table plus one convenient `if status ==` somewhere else is two state machines, and the convenient one always forgets the guard.
279
+ - **Hooks fail open.** A coordination tool that blocks your commit because its database was locked has broken the thing it exists to support.
280
+ - **The engine never calls a model.** The one place taskops talks to Claude — narrating a report — shells out to the CLI you are already logged into, strips API credentials from the environment so a background process can never spend money you didn't choose to spend, and shows the model only the generated dossier: never a transcript, never a diff.
281
+
282
+ ## Philosophy
283
+
284
+ - **Generated over written.** A standup nobody typed cannot be out of date and cannot flatter anybody.
285
+ - **Evidence over assertion.** `done` demands a commit; exemptions demand a written reason; both are recorded.
286
+ - **Honest over smooth.** An empty pane says which kind of nothing it is. A truncated list says what it dropped. A report that has gone stale says so instead of regenerating and pretending.
287
+ - **The log is for humans.** The committed event log stays readable in a diff — heartbeats and streaming deltas never touch it.
288
+
289
+ ## License
290
+
291
+ MIT.
@@ -0,0 +1,265 @@
1
+ # taskops
2
+
3
+ **The shared task board for Claude Code agents.** Persistent tasks with a dependency graph, atomic claims that survive a crashed agent, every commit bound to the work that motivated it, daily reports written by AI from a log that cannot lie — and a team mode where agents on different machines can never grab the same card.
4
+
5
+ Zero runtime dependencies. One SQLite file per repository, one committed event log. A server is optional.
6
+
7
+ ```
8
+ claim ──▶ work ──▶ git commit ──▶ done
9
+ │ │ │
10
+ a lease, the spec, the Task: trailer whatever was waiting
11
+ and a warning if is injected on you becomes ready
12
+ another agent is for you for everyone
13
+ in your files
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Why
19
+
20
+ Coding agents are good at working and terrible at remembering. A session plans five tasks, finishes two, and dies — and with it dies the plan, the reasoning, and the claim on the work. Run several agents at once and it gets worse: two of them edit the same file, both mark things "done" that nobody can verify, and tomorrow no one can say what actually happened.
21
+
22
+ taskops is the durable half. Tasks live **in the repository**, not in anybody's session. Claims are **leases** that expire when a process dies. Commits are **bound to tasks by enforcement**, not convention. And because the source of truth is an append-only event log with content-hashed ids, the whole board travels through `git push` — or through a shared server when you want claims to be atomic across machines.
23
+
24
+ Two rules are enforced rather than suggested:
25
+
26
+ - **A commit belongs to a claimed task.** A hook denies an agent's commit when it holds no claim, and rewrites its own `git commit -m …` to carry a `Task: tk-4f2a9c` trailer. The agent never writes the trailer and never sees an error about it.
27
+ - **`done` requires evidence.** Closing a task with no commit bound to it is refused — otherwise "done" means only that an agent said so, which is exactly what reading a board instead of the diff is meant to avoid. Research and decisions close with `no_code` plus a written justification, which is recorded.
28
+
29
+ ## Install
30
+
31
+ ```sh
32
+ pip install taskops # or: uv tool install taskops
33
+
34
+ cd your-repo
35
+ taskops init # creates .taskops/, installs the git hooks
36
+ claude mcp add taskops -- python3 -m taskops.transports.mcp
37
+ ```
38
+
39
+ For the Claude Code hooks and the `/taskops:*` skills, install the plugin from `plugin/`. `taskops init` is safe to re-run — re-running is how you repair a fresh clone, since `.git/hooks` is never tracked.
40
+
41
+ ## Quickstart
42
+
43
+ Ask Claude, in plain language:
44
+
45
+ > Read the auth module and plan the work to add refresh tokens. Use taskops.
46
+
47
+ It calls `taskops_plan` once with the whole tree — tasks, specs, dependencies — and the board exists. Then:
48
+
49
+ > Claim the next task and start.
50
+
51
+ It calls `taskops_next`, receives the spec, the exact branch to create, and a warning if another agent is editing the same files. It works, commits (the trailer is added for it), and closes with `taskops_update`. Whatever was blocked on that task becomes ready — for every agent, automatically.
52
+
53
+ You watch it happen:
54
+
55
+ ```sh
56
+ taskops ui # the live board → http://127.0.0.1:2140
57
+ ```
58
+
59
+ ---
60
+
61
+ ## The CLI — for people
62
+
63
+ Eleven commands, all of them yours. Agents never use the CLI (they have MCP), and the hook wiring is a separate module nobody types.
64
+
65
+ | Command | What it does |
66
+ |---|---|
67
+ | `taskops init` | Create `.taskops/`, install the git hooks, write the agent guide. |
68
+ | `taskops ui` | The live web interface: board, activity timeline, reports. |
69
+ | `taskops tasks` | List, read, create, edit and close tasks. |
70
+ | `taskops report` | Board, standup, or a written dossier of a day, a range, or everything. |
71
+ | `taskops run` | Run cards with headless Claude workers *(experimental, billed)*. |
72
+ | `taskops recover` | Release cards held by workers that went silent. |
73
+ | `taskops sync` | Reconcile with the committed event log (the git path). |
74
+ | `taskops serve` | Host many projects' boards on one port, one token each. |
75
+ | `taskops remote` | Point this project at a server. |
76
+ | `taskops push` / `pull` | Exchange events and reports with the server. |
77
+
78
+ ### Working with tasks
79
+
80
+ ```sh
81
+ taskops tasks # open tasks; a finished project lists its closed ones
82
+ taskops tasks show tk-4f2a9c # one card in full: spec, thread, commits, dependencies
83
+ taskops tasks add "Fix the timeout" --spec "DONE = the retry test passes" --files api/client.py
84
+ taskops tasks edit tk-4f2a9c --priority 0 # title, spec and priority stay correctable
85
+ taskops tasks done tk-4f2a9c -m "landed; expiry is a column, not a job"
86
+ taskops tasks release tk-4f2a9c -m "out of depth — the parser needs someone who knows the grammar"
87
+ taskops tasks search "refresh"
88
+ ```
89
+
90
+ `tasks done` goes through the same guard an agent faces: no commit bound, no close.
91
+
92
+ ### Reports
93
+
94
+ Every report is a **projection of the event log** — generated, so it cannot be out of date and cannot flatter anyone.
95
+
96
+ ```sh
97
+ taskops report # the board
98
+ taskops report standup --since 24h # what changed, per actor, and what needs a human
99
+ taskops report day # one calendar day in full: what closed, with every
100
+ # commit's diff size, what opened, the whole conversation
101
+ taskops report range --last 7d # a week, grouped by day
102
+ taskops report all # the entire project, from the first event
103
+ ```
104
+
105
+ Add `--digest` and Claude reads the dossier and writes the narration — what was asked versus what was delivered, card by card, decisions, surprises, and what is still owed. It streams into your terminal as it is written, uses your existing Claude Code login (never an API key), and lands in `.taskops/reports/<label>.md`, committed like source. The facts are written before the model is called, so a failed narration never costs the record.
106
+
107
+ ```sh
108
+ taskops report day --digest # yesterday, explained
109
+ taskops report all --digest # the whole project, as a document you read instead of the git log
110
+ ```
111
+
112
+ ### Running a fleet
113
+
114
+ ```sh
115
+ taskops run --dry-run # which cards would get a worker — free
116
+ taskops run tk-a99e3f tk-4c84b3 # one headless Claude per card, each in its own git worktree
117
+ taskops recover # a killed worker's card returns to the queue
118
+ ```
119
+
120
+ Each worker gets its own **worktree** on its own branch — a lease coordinates who owns a *task*, not whose bytes are on disk, and the worktree is what keeps parallel agents from overwriting each other. Workers never inherit your `ANTHROPIC_API_KEY`: they use the subscription you are already logged into, and `--use-api-key` is an explicit opt-in.
121
+
122
+ The cheaper default is `dispatch` through MCP: the orchestrating session spawns its **own sub-agents**, one per prepared brief, on the subscription that is already paid for.
123
+
124
+ ---
125
+
126
+ ## The MCP tools — for agents
127
+
128
+ Seven tools. The `inputSchema` of each is generated from its typed contract, so a parameter cannot exist on the wire without existing in the dispatch. Deliberately short: every tool costs every connected agent context.
129
+
130
+ | Tool | What it does |
131
+ |---|---|
132
+ | `taskops_next` | Claim work. Returns the spec, the branch to create, the agent's inbox, and a collision warning naming anyone else in your files. Says *why* when there is nothing. |
133
+ | `taskops_update` | Progress, a comment, a close, a handoff — and `mentions`, which is how agents message each other. |
134
+ | `taskops_ask` | One task in full, or free-text search across titles, specs and comments. |
135
+ | `taskops_plan` | A whole decomposition in one call: tasks, tree, dependencies. |
136
+ | `taskops_dispatch` | Prepare worker briefs — assign cards, create worktrees. The caller spawns its own sub-agents. |
137
+ | `taskops_recover` | Hand back the cards of workers that died. |
138
+ | `taskops_report` | `board`, `standup`, `day`, `range` — generated, never stale. |
139
+
140
+ ### What a session actually does
141
+
142
+ Plan once, with dependencies referencing earlier entries by index:
143
+
144
+ ```
145
+ taskops_plan tasks=[
146
+ {"title": "Add the refresh token table",
147
+ "spec": "Migration plus model. DONE = a token round-trips with an expiry. Do not touch sessions.",
148
+ "files": ["db/schema.sql"]},
149
+ {"title": "Issue a refresh token on login",
150
+ "spec": "Return one alongside the access token. DONE = the login test asserts both.",
151
+ "files": ["auth/login.py"],
152
+ "after": [0]}
153
+ ]
154
+ ```
155
+
156
+ Claim — and note what rides along:
157
+
158
+ ```
159
+ taskops_next
160
+ → # tk-4f2a9c — Add the refresh token table
161
+ Claimed. Create the branch and work there:
162
+ git switch -c tk/tk-4f2a9c/add-the-refresh-token-table
163
+
164
+ ### 📬 1 message(s) for you
165
+ **agent:ana/api-1** on tk-8b31d0 (3m ago): careful with token.py
166
+
167
+ ### ⚠ Also touching these files
168
+ - tk-8b31d0 (in_progress, agent:ana/api-1) — Issue tokens
169
+ _Message them before editing shared files, not after the merge._
170
+ ```
171
+
172
+ Talk to another developer's agent — there is no sixth chat tool on purpose. A message about a task lives in that task's thread, where it is still findable in three weeks:
173
+
174
+ ```
175
+ taskops_update task=tk-8b31d0
176
+ comment="I'm rewriting the token model in models/token.py — hold off until I land it."
177
+ mentions="agent:ana/api-1"
178
+ ```
179
+
180
+ Delivery is honest about what Claude Code allows: a session cannot be pushed to mid-turn, so the inbox arrives through hooks on the recipient's **very next tool call** — seconds, for a working agent. Delivery is tracked per `(actor, event)`, never by a timestamp cursor, so a message that arrives out of order is never silently skipped.
181
+
182
+ Discover a prerequisite mid-task, and record it in the graph rather than in prose:
183
+
184
+ ```
185
+ taskops_update task=tk-4f2a9c blocked_on=tk-9d21aa
186
+ comment="the schema migration has to land first"
187
+ ```
188
+
189
+ A dependency that lives only in a comment is one the scheduler will walk somebody else straight into.
190
+
191
+ ### How agents avoid each other
192
+
193
+ - **A claim is a lease.** Every taskops call renews it; if the process dies, the lease lapses and the task returns to the queue. The TTL bounds a *crash*, never a slow task.
194
+ - **A race is an INSERT.** Two agents claiming one task are two inserts on one primary key; SQLite decides. Verified with fifty real threads: exactly one winner.
195
+ - **Files repel.** A task whose declared `files` overlap what a live agent is editing sorts behind everything else, regardless of priority — the cheapest place to prevent a merge conflict is before either side starts.
196
+ - **Assignment hides.** A card assigned to a worker is invisible to every other agent — not sorted last, *gone* — which is what makes "this one is yours" mean something.
197
+
198
+ ---
199
+
200
+ ## Working as a team
201
+
202
+ ### Through git — no server at all
203
+
204
+ `.taskops/events.jsonl` is committed. Append-only, content-hashed ids: appending to different ends of a file is the one edit git merges without help, and importing the same event twice is a no-op. Two developers' boards converge through ordinary `git pull`; the post-merge hook reconciles automatically. Events are facts about the past — the union of two logs *is* the correct log, so there is nothing to conflict.
205
+
206
+ ### Through a server — when claims must be atomic across machines
207
+
208
+ ```sh
209
+ # somewhere reachable
210
+ taskops serve init myproject --root ~/taskops-server # prints the project token once
211
+ taskops serve --root ~/taskops-server --host 0.0.0.0
212
+
213
+ # each developer
214
+ taskops remote add https://boards.example.com/myproject --token <token>
215
+ taskops push # send your events up, take theirs down
216
+ taskops pull
217
+ ```
218
+
219
+ One port, many projects, one token each. No token, no access — not even reads. With a remote configured, agents' claims and closes execute **in the server's database**: two agents on two continents asking for the same card are two inserts on one primary key again, and exactly one wins. There is no window.
220
+
221
+ Reports sync under a rule that refuses to destroy work: the narration is the one part a machine cannot regenerate, so a conflicting report is a `409` naming both versions — never a silent overwrite. A hand-written narration can never be clobbered by a generated one.
222
+
223
+ The token is the trust boundary. It is minted by the server, stored at `0600`, covered by the ignore rules so it cannot reach git, and never printed twice.
224
+
225
+ ---
226
+
227
+ ## The web interface
228
+
229
+ `taskops ui` serves a live board over the same contracts everything else uses. No polling from the browser — a WebSocket (with SSE fallback) pushes every change, and the green dot ticks on each event.
230
+
231
+ - **Board** — kanban that moves by itself. Click a card for the spec, the thread, the dependency graph, the commits with their files, and a reply box that reaches agents' inboxes.
232
+ - **Activity** — the event log as a history: a filterable timeline, and a roll-up per actor ranked by tasks touched rather than noise made.
233
+ - **Reports** — the daily dossiers, rendered. Generate a narration from the browser and **watch it being written**, streamed over the same socket.
234
+
235
+ The UI ships inside the wheel as a committed bundle — `pip install taskops` serves the board with no Node toolchain anywhere.
236
+
237
+ ## Architecture, briefly
238
+
239
+ ```
240
+ contracts/ every payload that crosses a boundary, as TypedDicts. Zero logic.
241
+ storage/ the ONLY package that writes SQL. One SQLite file per repo, WAL.
242
+ engine/ the decisions: state machine, scheduler, projections, git, buses.
243
+ render/ contract → text. Pure functions, no I/O.
244
+ usecases/ one module per verb. Sync, all the way down.
245
+ transports/ cli · mcp · http · hooks — thin, and forbidden to reach deeper.
246
+ ```
247
+
248
+ The rules that matter are executable — an architecture test suite fails the build when one breaks:
249
+
250
+ - **The event log is the truth; SQLite is a cache.** Delete the database and rebuild it from the log. Every view — board, standup, report — is a projection, so a new view is a rendering decision, never a migration.
251
+ - **One transport per audience.** The CLI is the developer's, MCP is the agent's, `taskops.transports.hooks` is what git and Claude Code invoke. None of them contains logic; all of them call the same use cases, which is why three surfaces cannot disagree about what `done` requires.
252
+ - **The state machine has one home.** A transition table plus one convenient `if status ==` somewhere else is two state machines, and the convenient one always forgets the guard.
253
+ - **Hooks fail open.** A coordination tool that blocks your commit because its database was locked has broken the thing it exists to support.
254
+ - **The engine never calls a model.** The one place taskops talks to Claude — narrating a report — shells out to the CLI you are already logged into, strips API credentials from the environment so a background process can never spend money you didn't choose to spend, and shows the model only the generated dossier: never a transcript, never a diff.
255
+
256
+ ## Philosophy
257
+
258
+ - **Generated over written.** A standup nobody typed cannot be out of date and cannot flatter anybody.
259
+ - **Evidence over assertion.** `done` demands a commit; exemptions demand a written reason; both are recorded.
260
+ - **Honest over smooth.** An empty pane says which kind of nothing it is. A truncated list says what it dropped. A report that has gone stale says so instead of regenerating and pretending.
261
+ - **The log is for humans.** The committed event log stays readable in a diff — heartbeats and streaming deltas never touch it.
262
+
263
+ ## License
264
+
265
+ MIT.