@laurentenhoor/devclaw 0.1.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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +406 -0
  3. package/dist/index.d.ts +88 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +107 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/audit.d.ts +2 -0
  8. package/dist/lib/audit.d.ts.map +1 -0
  9. package/dist/lib/audit.js +42 -0
  10. package/dist/lib/audit.js.map +1 -0
  11. package/dist/lib/binding-manager.d.ts +35 -0
  12. package/dist/lib/binding-manager.d.ts.map +1 -0
  13. package/dist/lib/binding-manager.js +88 -0
  14. package/dist/lib/binding-manager.js.map +1 -0
  15. package/dist/lib/cli.d.ts +12 -0
  16. package/dist/lib/cli.d.ts.map +1 -0
  17. package/dist/lib/cli.js +69 -0
  18. package/dist/lib/cli.js.map +1 -0
  19. package/dist/lib/dispatch.d.ts +58 -0
  20. package/dist/lib/dispatch.d.ts.map +1 -0
  21. package/dist/lib/dispatch.js +163 -0
  22. package/dist/lib/dispatch.js.map +1 -0
  23. package/dist/lib/model-selector.d.ts +21 -0
  24. package/dist/lib/model-selector.d.ts.map +1 -0
  25. package/dist/lib/model-selector.js +74 -0
  26. package/dist/lib/model-selector.js.map +1 -0
  27. package/dist/lib/notify.d.ts +54 -0
  28. package/dist/lib/notify.d.ts.map +1 -0
  29. package/dist/lib/notify.js +143 -0
  30. package/dist/lib/notify.js.map +1 -0
  31. package/dist/lib/onboarding.d.ts +5 -0
  32. package/dist/lib/onboarding.d.ts.map +1 -0
  33. package/dist/lib/onboarding.js +124 -0
  34. package/dist/lib/onboarding.js.map +1 -0
  35. package/dist/lib/projects.d.ts +64 -0
  36. package/dist/lib/projects.d.ts.map +1 -0
  37. package/dist/lib/projects.js +127 -0
  38. package/dist/lib/projects.js.map +1 -0
  39. package/dist/lib/providers/github.d.ts +23 -0
  40. package/dist/lib/providers/github.d.ts.map +1 -0
  41. package/dist/lib/providers/github.js +130 -0
  42. package/dist/lib/providers/github.js.map +1 -0
  43. package/dist/lib/providers/gitlab.d.ts +23 -0
  44. package/dist/lib/providers/gitlab.d.ts.map +1 -0
  45. package/dist/lib/providers/gitlab.js +133 -0
  46. package/dist/lib/providers/gitlab.js.map +1 -0
  47. package/dist/lib/providers/index.d.ts +12 -0
  48. package/dist/lib/providers/index.d.ts.map +1 -0
  49. package/dist/lib/providers/index.js +25 -0
  50. package/dist/lib/providers/index.js.map +1 -0
  51. package/dist/lib/providers/provider.d.ts +35 -0
  52. package/dist/lib/providers/provider.d.ts.map +1 -0
  53. package/dist/lib/providers/provider.js +13 -0
  54. package/dist/lib/providers/provider.js.map +1 -0
  55. package/dist/lib/services/health.d.ts +38 -0
  56. package/dist/lib/services/health.d.ts.map +1 -0
  57. package/dist/lib/services/health.js +100 -0
  58. package/dist/lib/services/health.js.map +1 -0
  59. package/dist/lib/services/heartbeat.d.ts +38 -0
  60. package/dist/lib/services/heartbeat.d.ts.map +1 -0
  61. package/dist/lib/services/heartbeat.js +199 -0
  62. package/dist/lib/services/heartbeat.js.map +1 -0
  63. package/dist/lib/services/pipeline.d.ts +36 -0
  64. package/dist/lib/services/pipeline.d.ts.map +1 -0
  65. package/dist/lib/services/pipeline.js +90 -0
  66. package/dist/lib/services/pipeline.js.map +1 -0
  67. package/dist/lib/services/queue.d.ts +14 -0
  68. package/dist/lib/services/queue.d.ts.map +1 -0
  69. package/dist/lib/services/queue.js +31 -0
  70. package/dist/lib/services/queue.js.map +1 -0
  71. package/dist/lib/services/tick.d.ts +62 -0
  72. package/dist/lib/services/tick.d.ts.map +1 -0
  73. package/dist/lib/services/tick.js +160 -0
  74. package/dist/lib/services/tick.js.map +1 -0
  75. package/dist/lib/setup/agent.d.ts +14 -0
  76. package/dist/lib/setup/agent.d.ts.map +1 -0
  77. package/dist/lib/setup/agent.js +72 -0
  78. package/dist/lib/setup/agent.js.map +1 -0
  79. package/dist/lib/setup/config.d.ts +22 -0
  80. package/dist/lib/setup/config.d.ts.map +1 -0
  81. package/dist/lib/setup/config.js +67 -0
  82. package/dist/lib/setup/config.js.map +1 -0
  83. package/dist/lib/setup/index.d.ts +53 -0
  84. package/dist/lib/setup/index.d.ts.map +1 -0
  85. package/dist/lib/setup/index.js +68 -0
  86. package/dist/lib/setup/index.js.map +1 -0
  87. package/dist/lib/setup/workspace.d.ts +6 -0
  88. package/dist/lib/setup/workspace.d.ts.map +1 -0
  89. package/dist/lib/setup/workspace.js +69 -0
  90. package/dist/lib/setup/workspace.js.map +1 -0
  91. package/dist/lib/templates.d.ts +9 -0
  92. package/dist/lib/templates.d.ts.map +1 -0
  93. package/dist/lib/templates.js +163 -0
  94. package/dist/lib/templates.js.map +1 -0
  95. package/dist/lib/tiers.d.ts +55 -0
  96. package/dist/lib/tiers.d.ts.map +1 -0
  97. package/dist/lib/tiers.js +74 -0
  98. package/dist/lib/tiers.js.map +1 -0
  99. package/dist/lib/tool-helpers.d.ts +44 -0
  100. package/dist/lib/tool-helpers.d.ts.map +1 -0
  101. package/dist/lib/tool-helpers.js +65 -0
  102. package/dist/lib/tool-helpers.js.map +1 -0
  103. package/dist/lib/tools/health.d.ts +28 -0
  104. package/dist/lib/tools/health.d.ts.map +1 -0
  105. package/dist/lib/tools/health.js +61 -0
  106. package/dist/lib/tools/health.js.map +1 -0
  107. package/dist/lib/tools/onboard.d.ts +24 -0
  108. package/dist/lib/tools/onboard.d.ts.map +1 -0
  109. package/dist/lib/tools/onboard.js +27 -0
  110. package/dist/lib/tools/onboard.js.map +1 -0
  111. package/dist/lib/tools/project-register.d.ts +51 -0
  112. package/dist/lib/tools/project-register.d.ts.map +1 -0
  113. package/dist/lib/tools/project-register.js +172 -0
  114. package/dist/lib/tools/project-register.js.map +1 -0
  115. package/dist/lib/tools/queue-status.test.d.ts +2 -0
  116. package/dist/lib/tools/queue-status.test.d.ts.map +1 -0
  117. package/dist/lib/tools/queue-status.test.js +48 -0
  118. package/dist/lib/tools/queue-status.test.js.map +1 -0
  119. package/dist/lib/tools/setup.d.ts +76 -0
  120. package/dist/lib/tools/setup.d.ts.map +1 -0
  121. package/dist/lib/tools/setup.js +102 -0
  122. package/dist/lib/tools/setup.js.map +1 -0
  123. package/dist/lib/tools/status.d.ts +24 -0
  124. package/dist/lib/tools/status.d.ts.map +1 -0
  125. package/dist/lib/tools/status.js +53 -0
  126. package/dist/lib/tools/status.js.map +1 -0
  127. package/dist/lib/tools/task-comment.d.ts +40 -0
  128. package/dist/lib/tools/task-comment.d.ts.map +1 -0
  129. package/dist/lib/tools/task-comment.js +84 -0
  130. package/dist/lib/tools/task-comment.js.map +1 -0
  131. package/dist/lib/tools/task-create.d.ts +54 -0
  132. package/dist/lib/tools/task-create.d.ts.map +1 -0
  133. package/dist/lib/tools/task-create.js +77 -0
  134. package/dist/lib/tools/task-create.js.map +1 -0
  135. package/dist/lib/tools/task-update.d.ts +40 -0
  136. package/dist/lib/tools/task-update.d.ts.map +1 -0
  137. package/dist/lib/tools/task-update.js +79 -0
  138. package/dist/lib/tools/task-update.js.map +1 -0
  139. package/dist/lib/tools/task-update.test.d.ts +7 -0
  140. package/dist/lib/tools/task-update.test.d.ts.map +1 -0
  141. package/dist/lib/tools/task-update.test.js +55 -0
  142. package/dist/lib/tools/task-update.test.js.map +1 -0
  143. package/dist/lib/tools/work-finish.d.ts +43 -0
  144. package/dist/lib/tools/work-finish.d.ts.map +1 -0
  145. package/dist/lib/tools/work-finish.js +77 -0
  146. package/dist/lib/tools/work-finish.js.map +1 -0
  147. package/dist/lib/tools/work-start.d.ts +39 -0
  148. package/dist/lib/tools/work-start.d.ts.map +1 -0
  149. package/dist/lib/tools/work-start.js +129 -0
  150. package/dist/lib/tools/work-start.js.map +1 -0
  151. package/dist/lib/types.d.ts +17 -0
  152. package/dist/lib/types.d.ts.map +1 -0
  153. package/dist/lib/types.js +8 -0
  154. package/dist/lib/types.js.map +1 -0
  155. package/docs/ARCHITECTURE.md +662 -0
  156. package/docs/CONFIGURATION.md +336 -0
  157. package/docs/MANAGEMENT.md +120 -0
  158. package/docs/ONBOARDING.md +251 -0
  159. package/docs/QA_WORKFLOW.md +120 -0
  160. package/docs/ROADMAP.md +96 -0
  161. package/docs/TESTING.md +339 -0
  162. package/docs/TOOLS.md +361 -0
  163. package/package.json +55 -0
@@ -0,0 +1,662 @@
1
+ # DevClaw — Architecture & Component Interaction
2
+
3
+ ## How it works
4
+
5
+ One OpenClaw agent process serves multiple group chats — each group gives it a different project context. The orchestrator role, the workers, the task queue, and all state are fully isolated per group.
6
+
7
+ ```mermaid
8
+ graph TB
9
+ subgraph "Group Chat A"
10
+ direction TB
11
+ A_O["Orchestrator"]
12
+ A_GL[GitHub/GitLab Issues]
13
+ A_DEV["DEV (worker session)"]
14
+ A_QA["QA (worker session)"]
15
+ A_O -->|work_start| A_GL
16
+ A_O -->|dispatches| A_DEV
17
+ A_O -->|dispatches| A_QA
18
+ end
19
+
20
+ subgraph "Group Chat B"
21
+ direction TB
22
+ B_O["Orchestrator"]
23
+ B_GL[GitHub/GitLab Issues]
24
+ B_DEV["DEV (worker session)"]
25
+ B_QA["QA (worker session)"]
26
+ B_O -->|work_start| B_GL
27
+ B_O -->|dispatches| B_DEV
28
+ B_O -->|dispatches| B_QA
29
+ end
30
+
31
+ AGENT["Single OpenClaw Agent"]
32
+ AGENT --- A_O
33
+ AGENT --- B_O
34
+ ```
35
+
36
+ Worker sessions are expensive to start — each new spawn reads the full codebase (~50K tokens). DevClaw maintains **separate sessions per level per role** ([session-per-level design](#session-per-level-design)). When a medior dev finishes task A and picks up task B on the same project, the accumulated context carries over — no re-reading the repo. The plugin handles all session dispatch internally via OpenClaw CLI; the orchestrator agent never calls `sessions_spawn` or `sessions_send`.
37
+
38
+ ```mermaid
39
+ sequenceDiagram
40
+ participant O as Orchestrator
41
+ participant DC as DevClaw Plugin
42
+ participant IT as Issue Tracker
43
+ participant S as Worker Session
44
+
45
+ O->>DC: work_start({ issueId: 42, role: "dev" })
46
+ DC->>IT: Fetch issue, verify label
47
+ DC->>DC: Assign level (junior/medior/senior)
48
+ DC->>DC: Check existing session for assigned level
49
+ DC->>IT: Transition label (To Do → Doing)
50
+ DC->>S: Dispatch task via CLI (create or reuse session)
51
+ DC->>DC: Update projects.json, write audit log
52
+ DC-->>O: { success: true, announcement: "..." }
53
+ ```
54
+
55
+ ## Agents vs Sessions
56
+
57
+ Understanding the OpenClaw model is key to understanding how DevClaw works:
58
+
59
+ - **Agent** — A configured entity in `openclaw.json`. Has a workspace, model, identity files (SOUL.md, IDENTITY.md), and tool permissions. Persists across restarts.
60
+ - **Session** — A runtime conversation instance. Each session has its own context window and conversation history, stored as a `.jsonl` transcript file.
61
+ - **Sub-agent session** — A session created under the orchestrator agent for a specific worker role. NOT a separate agent — it's a child session running under the same agent, with its own isolated context. Format: `agent:<parent>:subagent:<project>-<role>-<level>`.
62
+
63
+ ### Session-per-level design
64
+
65
+ Each project maintains **separate sessions per developer level per role**. A project's DEV might have a junior session, a medior session, and a senior session — each accumulating its own codebase context over time.
66
+
67
+ ```
68
+ Orchestrator Agent (configured in openclaw.json)
69
+ └─ Main session (long-lived, handles all projects)
70
+
71
+ ├─ Project A
72
+ │ ├─ DEV sessions: { junior: <key>, medior: <key>, senior: null }
73
+ │ └─ QA sessions: { reviewer: <key>, tester: null }
74
+
75
+ └─ Project B
76
+ ├─ DEV sessions: { junior: null, medior: <key>, senior: null }
77
+ └─ QA sessions: { reviewer: <key>, tester: null }
78
+ ```
79
+
80
+ Why per-level instead of switching models on one session:
81
+ - **No model switching overhead** — each session always uses the same model
82
+ - **Accumulated context** — a junior session that's done 20 typo fixes knows the project well; a medior session that's done 5 features knows it differently
83
+ - **No cross-model confusion** — conversation history stays with the model that generated it
84
+ - **Deterministic reuse** — level selection directly maps to a session key, no patching needed
85
+
86
+ ### Plugin-controlled session lifecycle
87
+
88
+ DevClaw controls the **full** session lifecycle end-to-end. The orchestrator agent never calls `sessions_spawn` or `sessions_send` — the plugin handles session creation and task dispatch internally using the OpenClaw CLI:
89
+
90
+ ```
91
+ Plugin dispatch (inside work_start):
92
+ 1. Assign level, look up session, decide spawn vs send
93
+ 2. New session: openclaw gateway call sessions.patch → create entry + set model
94
+ openclaw gateway call agent → dispatch task
95
+ 3. Existing: openclaw gateway call agent → dispatch task to existing session
96
+ 4. Return result to orchestrator (announcement text, no session instructions)
97
+ ```
98
+
99
+ The agent's only job after `work_start` returns is to post the announcement to Telegram. Everything else — level assignment, session creation, task dispatch, state update, audit logging — is deterministic plugin code.
100
+
101
+ **Why this matters:** Previously the plugin returned instructions like `{ sessionAction: "spawn", model: "sonnet" }` and the agent had to correctly call `sessions_spawn` with the right params. This was the fragile handoff point where agents would forget `cleanup: "keep"`, use wrong models, or corrupt session state. Moving dispatch into the plugin eliminates that entire class of errors.
102
+
103
+ **Session persistence:** Sessions created via `sessions.patch` persist indefinitely (no auto-cleanup). The plugin manages lifecycle explicitly through the `health` tool.
104
+
105
+ **What we trade off vs. registered sub-agents:**
106
+
107
+ | Feature | Sub-agent system | Plugin-controlled | DevClaw equivalent |
108
+ |---|---|---|---|
109
+ | Auto-reporting | Sub-agent reports to parent | No | Heartbeat polls for completion |
110
+ | Concurrency control | `maxConcurrent` | No | `work_start` checks `active` flag |
111
+ | Lifecycle tracking | Parent-child registry | No | `projects.json` tracks all sessions |
112
+ | Timeout detection | `runTimeoutSeconds` | No | `health` flags stale >2h |
113
+ | Cleanup | Auto-archive | No | `health` manual cleanup |
114
+
115
+ DevClaw provides equivalent guardrails for everything except auto-reporting, which the heartbeat handles.
116
+
117
+ ## System overview
118
+
119
+ ```mermaid
120
+ graph TB
121
+ subgraph "Telegram"
122
+ H[Human]
123
+ TG[Group Chat]
124
+ end
125
+
126
+ subgraph "OpenClaw Runtime"
127
+ MS[Main Session<br/>orchestrator agent]
128
+ GW[Gateway RPC<br/>sessions.patch / sessions.list]
129
+ CLI[openclaw gateway call agent]
130
+ DEV_J[DEV session<br/>junior]
131
+ DEV_M[DEV session<br/>medior]
132
+ DEV_S[DEV session<br/>senior]
133
+ QA_R[QA session<br/>reviewer]
134
+ end
135
+
136
+ subgraph "DevClaw Plugin"
137
+ WS[work_start]
138
+ WF[work_finish]
139
+ TCR[task_create]
140
+ ST[status]
141
+ SH[health]
142
+ PR[project_register]
143
+ DS[setup]
144
+ TIER[Level Resolver]
145
+ PJ[projects.json]
146
+ AL[audit.log]
147
+ end
148
+
149
+ subgraph "External"
150
+ GL[Issue Tracker]
151
+ REPO[Git Repository]
152
+ end
153
+
154
+ H -->|messages| TG
155
+ TG -->|delivers| MS
156
+ MS -->|announces| TG
157
+
158
+ MS -->|calls| WS
159
+ MS -->|calls| WF
160
+ MS -->|calls| TCR
161
+ MS -->|calls| ST
162
+ MS -->|calls| SH
163
+ MS -->|calls| PR
164
+ MS -->|calls| DS
165
+
166
+ WS -->|resolves level| TIER
167
+ WS -->|transitions labels| GL
168
+ WS -->|reads/writes| PJ
169
+ WS -->|appends| AL
170
+ WS -->|creates session| GW
171
+ WS -->|dispatches task| CLI
172
+
173
+ WF -->|transitions labels| GL
174
+ WF -->|closes/reopens| GL
175
+ WF -->|reads/writes| PJ
176
+ WF -->|git pull| REPO
177
+ WF -->|tick dispatch| CLI
178
+ WF -->|appends| AL
179
+
180
+ TCR -->|creates issue| GL
181
+ TCR -->|appends| AL
182
+
183
+ ST -->|lists issues by label| GL
184
+ ST -->|reads| PJ
185
+ ST -->|appends| AL
186
+
187
+ SH -->|reads/writes| PJ
188
+ SH -->|checks sessions| GW
189
+ SH -->|reverts labels| GL
190
+ SH -->|appends| AL
191
+
192
+ PR -->|creates labels| GL
193
+ PR -->|writes entry| PJ
194
+ PR -->|appends| AL
195
+
196
+ CLI -->|sends task| DEV_J
197
+ CLI -->|sends task| DEV_M
198
+ CLI -->|sends task| DEV_S
199
+ CLI -->|sends task| QA_R
200
+
201
+ DEV_J -->|writes code, creates MRs| REPO
202
+ DEV_M -->|writes code, creates MRs| REPO
203
+ DEV_S -->|writes code, creates MRs| REPO
204
+ QA_R -->|reviews code, tests| REPO
205
+ ```
206
+
207
+ ## End-to-end flow: human to sub-agent
208
+
209
+ This diagram shows the complete path from a human message in Telegram through to a sub-agent session working on code:
210
+
211
+ ```mermaid
212
+ sequenceDiagram
213
+ participant H as Human (Telegram)
214
+ participant TG as Telegram Channel
215
+ participant MS as Main Session<br/>(orchestrator)
216
+ participant DC as DevClaw Plugin
217
+ participant GW as Gateway RPC
218
+ participant CLI as openclaw gateway call agent
219
+ participant DEV as DEV Session<br/>(medior)
220
+ participant GL as Issue Tracker
221
+
222
+ Note over H,GL: Issue exists in queue (To Do)
223
+
224
+ H->>TG: "check status" (or heartbeat triggers)
225
+ TG->>MS: delivers message
226
+ MS->>DC: status()
227
+ DC->>GL: list issues by label "To Do"
228
+ DC-->>MS: { toDo: [#42], dev: idle }
229
+
230
+ Note over MS: Decides to pick up #42 for DEV as medior
231
+
232
+ MS->>DC: work_start({ issueId: 42, role: "dev", level: "medior", ... })
233
+ DC->>DC: resolve level "medior" → model ID
234
+ DC->>DC: lookup dev.sessions.medior → null (first time)
235
+ DC->>GL: transition label "To Do" → "Doing"
236
+ DC->>GW: sessions.patch({ key: new-session-key, model: "anthropic/claude-sonnet-4-5" })
237
+ DC->>CLI: openclaw gateway call agent --params { sessionKey, message }
238
+ CLI->>DEV: creates session, delivers task
239
+ DC->>DC: store session key in projects.json + append audit.log
240
+ DC-->>MS: { success: true, announcement: "🔧 Spawning DEV (medior) for #42" }
241
+
242
+ MS->>TG: "🔧 Spawning DEV (medior) for #42: Add login page"
243
+ TG->>H: sees announcement
244
+
245
+ Note over DEV: Works autonomously — reads code, writes code, creates MR
246
+ Note over DEV: Calls work_finish when done
247
+
248
+ DEV->>DC: work_finish({ role: "dev", result: "done", ... })
249
+ DC->>GL: transition label "Doing" → "To Test"
250
+ DC->>DC: deactivate worker (sessions preserved)
251
+ DC-->>DEV: { announcement: "✅ DEV DONE #42" }
252
+
253
+ MS->>TG: "✅ DEV DONE #42 — moved to QA queue"
254
+ TG->>H: sees announcement
255
+ ```
256
+
257
+ On the **next DEV task** for this project that also assigns medior:
258
+
259
+ ```mermaid
260
+ sequenceDiagram
261
+ participant MS as Main Session
262
+ participant DC as DevClaw Plugin
263
+ participant CLI as openclaw gateway call agent
264
+ participant DEV as DEV Session<br/>(medior, existing)
265
+
266
+ MS->>DC: work_start({ issueId: 57, role: "dev", level: "medior", ... })
267
+ DC->>DC: resolve level "medior" → model ID
268
+ DC->>DC: lookup dev.sessions.medior → existing key!
269
+ Note over DC: No sessions.patch needed — session already exists
270
+ DC->>CLI: openclaw gateway call agent --params { sessionKey, message }
271
+ CLI->>DEV: delivers task to existing session (has full codebase context)
272
+ DC-->>MS: { success: true, announcement: "⚡ Sending DEV (medior) for #57" }
273
+ ```
274
+
275
+ Session reuse saves ~50K tokens per task by not re-reading the codebase.
276
+
277
+ ## Complete ticket lifecycle
278
+
279
+ This traces a single issue from creation to completion, showing every component interaction, data write, and message.
280
+
281
+ ### Phase 1: Issue created
282
+
283
+ Issues are created by the orchestrator agent or by sub-agent sessions via `task_create` or directly via `gh`/`glab`. The orchestrator can create issues based on user requests in Telegram, backlog planning, or QA feedback. Sub-agents can also create issues when they discover bugs during development.
284
+
285
+ ```
286
+ Orchestrator Agent → Issue Tracker: creates issue #42 with label "Planning"
287
+ ```
288
+
289
+ **State:** Issue tracker has issue #42 labeled "Planning". Nothing in DevClaw yet.
290
+
291
+ ### Phase 2: Heartbeat detects work
292
+
293
+ ```
294
+ Heartbeat triggers → Orchestrator calls status()
295
+ ```
296
+
297
+ ```mermaid
298
+ sequenceDiagram
299
+ participant A as Orchestrator
300
+ participant QS as status
301
+ participant GL as Issue Tracker
302
+ participant PJ as projects.json
303
+ participant AL as audit.log
304
+
305
+ A->>QS: status({ projectGroupId: "-123" })
306
+ QS->>PJ: readProjects()
307
+ PJ-->>QS: { dev: idle, qa: idle }
308
+ QS->>GL: list issues by label "To Do"
309
+ GL-->>QS: [{ id: 42, title: "Add login page" }]
310
+ QS->>GL: list issues by label "To Test"
311
+ GL-->>QS: []
312
+ QS->>GL: list issues by label "To Improve"
313
+ GL-->>QS: []
314
+ QS->>AL: append { event: "status", ... }
315
+ QS-->>A: { dev: idle, queue: { toDo: [#42] } }
316
+ ```
317
+
318
+ **Orchestrator decides:** DEV is idle, issue #42 is in To Do → pick it up. Evaluates complexity → assigns medior level.
319
+
320
+ ### Phase 3: DEV pickup
321
+
322
+ The plugin handles everything end-to-end — level resolution, session lookup, label transition, state update, **and** task dispatch to the worker session. The agent's only job after is to post the announcement.
323
+
324
+ ```mermaid
325
+ sequenceDiagram
326
+ participant A as Orchestrator
327
+ participant WS as work_start
328
+ participant GL as Issue Tracker
329
+ participant TIER as Level Resolver
330
+ participant GW as Gateway RPC
331
+ participant CLI as openclaw gateway call agent
332
+ participant PJ as projects.json
333
+ participant AL as audit.log
334
+
335
+ A->>WS: work_start({ issueId: 42, role: "dev", projectGroupId: "-123", level: "medior" })
336
+ WS->>PJ: readProjects()
337
+ WS->>GL: getIssue(42)
338
+ GL-->>WS: { title: "Add login page", labels: ["To Do"] }
339
+ WS->>WS: Verify label is "To Do"
340
+ WS->>TIER: resolve "medior" → "anthropic/claude-sonnet-4-5"
341
+ WS->>PJ: lookup dev.sessions.medior
342
+ WS->>GL: transitionLabel(42, "To Do", "Doing")
343
+ alt New session
344
+ WS->>GW: sessions.patch({ key: new-key, model: "anthropic/claude-sonnet-4-5" })
345
+ end
346
+ WS->>CLI: openclaw gateway call agent --params { sessionKey, message }
347
+ WS->>PJ: activateWorker + store session key
348
+ WS->>AL: append work_start + model_selection
349
+ WS-->>A: { success: true, announcement: "🔧 ..." }
350
+ ```
351
+
352
+ **Writes:**
353
+ - `Issue Tracker`: label "To Do" → "Doing"
354
+ - `projects.json`: dev.active=true, dev.issueId="42", dev.level="medior", dev.sessions.medior=key
355
+ - `audit.log`: 2 entries (work_start, model_selection)
356
+ - `Session`: task message delivered to worker session via CLI
357
+
358
+ ### Phase 4: DEV works
359
+
360
+ ```
361
+ DEV sub-agent session → reads codebase, writes code, creates MR
362
+ DEV sub-agent session → calls work_finish({ role: "dev", result: "done", ... })
363
+ ```
364
+
365
+ This happens inside the OpenClaw session. The worker calls `work_finish` directly for atomic state updates. If the worker discovers unrelated bugs, it calls `task_create` to file them.
366
+
367
+ ### Phase 5: DEV complete (worker self-reports)
368
+
369
+ ```mermaid
370
+ sequenceDiagram
371
+ participant DEV as DEV Session
372
+ participant WF as work_finish
373
+ participant GL as Issue Tracker
374
+ participant PJ as projects.json
375
+ participant AL as audit.log
376
+ participant REPO as Git Repo
377
+ participant QA as QA Session
378
+
379
+ DEV->>WF: work_finish({ role: "dev", result: "done", projectGroupId: "-123", summary: "Login page with OAuth" })
380
+ WF->>PJ: readProjects()
381
+ PJ-->>WF: { dev: { active: true, issueId: "42" } }
382
+ WF->>REPO: git pull
383
+ WF->>PJ: deactivateWorker(-123, dev)
384
+ Note over PJ: active→false, issueId→null<br/>sessions map PRESERVED
385
+ WF->>GL: transitionLabel "Doing" → "To Test"
386
+ WF->>AL: append { event: "work_finish", role: "dev", result: "done" }
387
+
388
+ WF->>WF: tick queue (fill free slots)
389
+ Note over WF: Scheduler sees "To Test" issue, QA slot free → dispatches QA
390
+ WF-->>DEV: { announcement: "✅ DEV DONE #42", tickPickups: [...] }
391
+ ```
392
+
393
+ **Writes:**
394
+ - `Git repo`: pulled latest (has DEV's merged code)
395
+ - `projects.json`: dev.active=false, dev.issueId=null (sessions map preserved for reuse)
396
+ - `Issue Tracker`: label "Doing" → "To Test"
397
+ - `audit.log`: 1 entry (work_finish) + tick entries if workers dispatched
398
+
399
+ ### Phase 6: QA pickup
400
+
401
+ Same as Phase 3, but with `role: "qa"`. Label transitions "To Test" → "Testing". Uses the reviewer level.
402
+
403
+ ### Phase 7: QA result (4 possible outcomes)
404
+
405
+ #### 7a. QA Pass
406
+
407
+ ```mermaid
408
+ sequenceDiagram
409
+ participant QA as QA Session
410
+ participant WF as work_finish
411
+ participant GL as Issue Tracker
412
+ participant PJ as projects.json
413
+ participant AL as audit.log
414
+
415
+ QA->>WF: work_finish({ role: "qa", result: "pass", projectGroupId: "-123" })
416
+ WF->>PJ: deactivateWorker(-123, qa)
417
+ WF->>GL: transitionLabel(42, "Testing", "Done")
418
+ WF->>GL: closeIssue(42)
419
+ WF->>AL: append { event: "work_finish", role: "qa", result: "pass" }
420
+ WF-->>QA: { announcement: "🎉 QA PASS #42. Issue closed." }
421
+ ```
422
+
423
+ **Ticket complete.** Issue closed, label "Done".
424
+
425
+ #### 7b. QA Fail
426
+
427
+ ```mermaid
428
+ sequenceDiagram
429
+ participant QA as QA Session
430
+ participant WF as work_finish
431
+ participant GL as Issue Tracker
432
+ participant PJ as projects.json
433
+ participant AL as audit.log
434
+
435
+ QA->>WF: work_finish({ role: "qa", result: "fail", projectGroupId: "-123", summary: "OAuth redirect broken" })
436
+ WF->>PJ: deactivateWorker(-123, qa)
437
+ WF->>GL: transitionLabel(42, "Testing", "To Improve")
438
+ WF->>GL: reopenIssue(42)
439
+ WF->>AL: append { event: "work_finish", role: "qa", result: "fail" }
440
+ WF-->>QA: { announcement: "❌ QA FAIL #42 — OAuth redirect broken. Sent back to DEV." }
441
+ ```
442
+
443
+ **Cycle restarts:** Issue goes to "To Improve". Next heartbeat, DEV picks it up again (Phase 3, but from "To Improve" instead of "To Do").
444
+
445
+ #### 7c. QA Refine
446
+
447
+ ```
448
+ Label: "Testing" → "Refining"
449
+ ```
450
+
451
+ Issue needs human decision. Pipeline pauses until human moves it to "To Do" or closes it.
452
+
453
+ #### 7d. Blocked (DEV or QA)
454
+
455
+ ```
456
+ DEV Blocked: "Doing" → "To Do"
457
+ QA Blocked: "Testing" → "To Test"
458
+ ```
459
+
460
+ Worker cannot complete (missing info, environment errors, etc.). Issue returns to queue for retry. The task is available for the next heartbeat pickup.
461
+
462
+ ### Completion enforcement
463
+
464
+ Three layers guarantee that `work_finish` always runs:
465
+
466
+ 1. **Completion contract** — Every task message sent to a worker session includes a mandatory `## MANDATORY: Task Completion` section listing available results and requiring `work_finish` even on failure. Workers are instructed to use `"blocked"` if stuck.
467
+
468
+ 2. **Blocked result** — Both DEV and QA can use `"blocked"` to gracefully return a task to queue without losing work. DEV blocked: `Doing → To Do`. QA blocked: `Testing → To Test`. This gives workers an escape hatch instead of silently dying.
469
+
470
+ 3. **Stale worker watchdog** — The heartbeat's health check detects workers active for >2 hours. With `fix=true`, it deactivates the worker and reverts the label back to queue. This catches sessions that crashed, ran out of context, or otherwise failed without calling `work_finish`. The `health` tool provides the same check for manual invocation.
471
+
472
+ ### Phase 8: Heartbeat (continuous)
473
+
474
+ The heartbeat runs periodically (via background service or manual `work_heartbeat` trigger). It combines health check + queue scan:
475
+
476
+ ```mermaid
477
+ sequenceDiagram
478
+ participant HB as Heartbeat Service
479
+ participant SH as health check
480
+ participant TK as projectTick
481
+ participant WS as work_start (dispatch)
482
+ Note over HB: Tick triggered (every 60s)
483
+
484
+ HB->>SH: checkWorkerHealth per project per role
485
+ Note over SH: Checks for zombies, stale workers
486
+ SH-->>HB: { fixes applied }
487
+
488
+ HB->>TK: projectTick per project
489
+ Note over TK: Scans queue: To Improve > To Test > To Do
490
+ TK->>WS: dispatchTask (fill free slots)
491
+ WS-->>TK: { dispatched }
492
+ TK-->>HB: { pickups, skipped }
493
+ ```
494
+
495
+ ## Data flow map
496
+
497
+ Every piece of data and where it lives:
498
+
499
+ ```
500
+ ┌─────────────────────────────────────────────────────────────────┐
501
+ │ Issue Tracker (source of truth for tasks) │
502
+ │ │
503
+ │ Issue #42: "Add login page" │
504
+ │ Labels: [Planning | To Do | Doing | To Test | Testing | ...] │
505
+ │ State: open / closed │
506
+ │ MRs/PRs: linked merge/pull requests │
507
+ │ Created by: orchestrator (task_create), workers, or humans │
508
+ └─────────────────────────────────────────────────────────────────┘
509
+ ↕ gh/glab CLI (read/write, auto-detected)
510
+ ┌─────────────────────────────────────────────────────────────────┐
511
+ │ DevClaw Plugin (orchestration logic) │
512
+ │ │
513
+ │ setup → agent creation + workspace + model config │
514
+ │ work_start → level + label + dispatch + role instr (e2e) │
515
+ │ work_finish → label + state + git pull + tick queue │
516
+ │ task_create → create issue in tracker │
517
+ │ task_update → manual label state change │
518
+ │ task_comment → add comment to issue │
519
+ │ status → read labels + read state │
520
+ │ health → check sessions + fix zombies │
521
+ │ project_register → labels + prompts + state init (one-time) │
522
+ └─────────────────────────────────────────────────────────────────┘
523
+ ↕ atomic file I/O ↕ OpenClaw CLI (plugin shells out)
524
+ ┌────────────────────────────────┐ ┌──────────────────────────────┐
525
+ │ projects/projects.json │ │ OpenClaw Gateway + CLI │
526
+ │ │ │ (called by plugin, not agent)│
527
+ │ Per project: │ │ │
528
+ │ dev: │ │ openclaw gateway call │
529
+ │ active, issueId, level │ │ sessions.patch → create │
530
+ │ sessions: │ │ sessions.list → health │
531
+ │ junior: <key> │ │ sessions.delete → cleanup │
532
+ │ medior: <key> │ │ │
533
+ │ senior: <key> │ │ openclaw gateway call agent │
534
+ │ qa: │ │ --params { sessionKey, │
535
+ │ active, issueId, level │ │ message, agentId } │
536
+ │ sessions: │ │ → dispatches to session │
537
+ │ reviewer: <key> │ │ │
538
+ │ tester: <key> │ │ │
539
+ └────────────────────────────────┘ └──────────────────────────────┘
540
+ ↕ append-only
541
+ ┌─────────────────────────────────────────────────────────────────┐
542
+ │ log/audit.log (observability) │
543
+ │ │
544
+ │ NDJSON, one line per event: │
545
+ │ work_start, work_finish, model_selection, │
546
+ │ status, health, task_create, task_update, │
547
+ │ task_comment, project_register, setup, heartbeat_tick │
548
+ │ │
549
+ │ Query: cat audit.log | jq 'select(.event=="work_start")' │
550
+ └─────────────────────────────────────────────────────────────────┘
551
+
552
+ ┌─────────────────────────────────────────────────────────────────┐
553
+ │ Telegram / WhatsApp (user-facing messages) │
554
+ │ │
555
+ │ Per group chat: │
556
+ │ "🔧 Spawning DEV (medior) for #42: Add login page" │
557
+ │ "⚡ Sending DEV (medior) for #57: Fix validation" │
558
+ │ "✅ DEV DONE #42 — Login page with OAuth." │
559
+ │ "🎉 QA PASS #42. Issue closed." │
560
+ │ "❌ QA FAIL #42 — OAuth redirect broken." │
561
+ │ "🚫 DEV BLOCKED #42 — Missing dependencies." │
562
+ │ "🚫 QA BLOCKED #42 — Env not available." │
563
+ └─────────────────────────────────────────────────────────────────┘
564
+
565
+ ┌─────────────────────────────────────────────────────────────────┐
566
+ │ Git Repository (codebase) │
567
+ │ │
568
+ │ DEV sub-agent sessions: read code, write code, create MRs │
569
+ │ QA sub-agent sessions: read code, run tests, review MRs │
570
+ │ work_finish (DEV done): git pull to sync latest │
571
+ └─────────────────────────────────────────────────────────────────┘
572
+ ```
573
+
574
+ ## Scope boundaries
575
+
576
+ What DevClaw controls vs. what it delegates:
577
+
578
+ ```mermaid
579
+ graph LR
580
+ subgraph "DevClaw controls (deterministic)"
581
+ L[Label transitions]
582
+ S[Worker state]
583
+ PR[Project registration]
584
+ SETUP[Agent + workspace setup]
585
+ SD[Session dispatch<br/>create + send via CLI]
586
+ AC[Scheduling<br/>tick queue after work_finish]
587
+ RI[Role instructions<br/>loaded per project]
588
+ A[Audit logging]
589
+ Z[Zombie cleanup]
590
+ end
591
+
592
+ subgraph "Orchestrator handles"
593
+ MSG[Telegram announcements]
594
+ HB[Heartbeat scheduling]
595
+ DEC[Task prioritization]
596
+ M[Developer assignment<br/>junior/medior/senior]
597
+ end
598
+
599
+ subgraph "Sub-agent sessions handle"
600
+ CR[Code writing]
601
+ MR[MR creation/review]
602
+ WF_W[Task completion<br/>via work_finish]
603
+ BUG[Bug filing<br/>via task_create]
604
+ end
605
+
606
+ subgraph "External"
607
+ DEPLOY[Deployment]
608
+ HR[Human decisions]
609
+ end
610
+ ```
611
+
612
+ ## IssueProvider abstraction
613
+
614
+ All issue tracker operations go through the `IssueProvider` interface, defined in `lib/providers/provider.ts`. This abstraction allows DevClaw to support multiple issue trackers without changing tool logic.
615
+
616
+ **Interface methods:**
617
+ - `ensureLabel` / `ensureAllStateLabels` — idempotent label creation
618
+ - `createIssue` — create issue with label and assignees
619
+ - `listIssuesByLabel` / `getIssue` — issue queries
620
+ - `transitionLabel` — atomic label state transition (unlabel + label)
621
+ - `closeIssue` / `reopenIssue` — issue lifecycle
622
+ - `hasStateLabel` / `getCurrentStateLabel` — label inspection
623
+ - `hasMergedMR` / `getMergedMRUrl` — MR/PR verification
624
+ - `addComment` — add comment to issue
625
+ - `healthCheck` — verify provider connectivity
626
+
627
+ **Current providers:**
628
+ - **GitHub** (`lib/providers/github.ts`) — wraps `gh` CLI
629
+ - **GitLab** (`lib/providers/gitlab.ts`) — wraps `glab` CLI
630
+
631
+ **Planned providers:**
632
+ - **Jira** — via REST API
633
+
634
+ Provider selection is handled by `createProvider()` in `lib/providers/index.ts`. Auto-detects GitHub vs GitLab from the git remote URL.
635
+
636
+ ## Error recovery
637
+
638
+ | Failure | Detection | Recovery |
639
+ |---|---|---|
640
+ | Session dies mid-task | `health` checks via `sessions.list` Gateway RPC | `fix=true`: reverts label, clears active state. Next heartbeat picks up task again (creates fresh session for that level). |
641
+ | gh/glab command fails | Plugin tool throws error, returns to agent | Agent retries or reports to Telegram group |
642
+ | `openclaw gateway call agent` fails | Plugin catches error during dispatch | Plugin rolls back: reverts label, clears active state. Returns error. No orphaned state. |
643
+ | `sessions.patch` fails | Plugin catches error during session creation | Plugin rolls back label transition. Returns error. |
644
+ | projects.json corrupted | Tool can't parse JSON | Manual fix needed. Atomic writes (temp+rename) prevent partial writes. |
645
+ | Label out of sync | `work_start` verifies label before transitioning | Throws error if label doesn't match expected state. |
646
+ | Worker already active | `work_start` checks `active` flag | Throws error: "DEV already active on project". Must complete current task first. |
647
+ | Stale worker (>2h) | `health` and heartbeat health check | `fix=true`: deactivates worker, reverts label to queue. Task available for next pickup. |
648
+ | Worker stuck/blocked | Worker calls `work_finish` with `"blocked"` | Deactivates worker, reverts label to queue. Issue available for retry. |
649
+ | `project_register` fails | Plugin catches error during label creation or state write | Clean error returned. Labels are idempotent, projects.json not written until all labels succeed. |
650
+
651
+ ## File locations
652
+
653
+ | File | Location | Purpose |
654
+ |---|---|---|
655
+ | Plugin source | `~/.openclaw/extensions/devclaw/` | Plugin code |
656
+ | Plugin manifest | `~/.openclaw/extensions/devclaw/openclaw.plugin.json` | Plugin registration |
657
+ | Agent config | `~/.openclaw/openclaw.json` | Agent definition + tool permissions + model config |
658
+ | Worker state | `~/.openclaw/workspace-<agent>/projects/projects.json` | Per-project DEV/QA state |
659
+ | Role instructions | `~/.openclaw/workspace-<agent>/projects/roles/<project>/` | Per-project `dev.md` and `qa.md` |
660
+ | Audit log | `~/.openclaw/workspace-<agent>/log/audit.log` | NDJSON event log |
661
+ | Session transcripts | `~/.openclaw/agents/<agent>/sessions/<uuid>.jsonl` | Conversation history per session |
662
+ | Git repos | `~/git/<project>/` | Project source code |