@team-agent/installer 0.2.11 → 0.3.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 (326) hide show
  1. package/Cargo.lock +744 -0
  2. package/Cargo.toml +34 -0
  3. package/crates/team-agent/Cargo.toml +33 -0
  4. package/crates/team-agent/src/cli/adapters.rs +1343 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +554 -0
  6. package/crates/team-agent/src/cli/emit.rs +1077 -0
  7. package/crates/team-agent/src/cli/helpers.rs +88 -0
  8. package/crates/team-agent/src/cli/leader.rs +216 -0
  9. package/crates/team-agent/src/cli/mod.rs +1141 -0
  10. package/crates/team-agent/src/cli/profile.rs +306 -0
  11. package/crates/team-agent/src/cli/send.rs +215 -0
  12. package/crates/team-agent/src/cli/status.rs +179 -0
  13. package/crates/team-agent/src/cli/status_port.rs +502 -0
  14. package/crates/team-agent/src/cli/tests/base.rs +616 -0
  15. package/crates/team-agent/src/cli/tests/compile.rs +96 -0
  16. package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
  21. package/crates/team-agent/src/cli/tests/mod.rs +97 -0
  22. package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
  23. package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
  25. package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
  26. package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
  27. package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
  28. package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
  29. package/crates/team-agent/src/cli/types.rs +605 -0
  30. package/crates/team-agent/src/compiler/tests.rs +701 -0
  31. package/crates/team-agent/src/compiler.rs +489 -0
  32. package/crates/team-agent/src/coordinator/backoff.rs +153 -0
  33. package/crates/team-agent/src/coordinator/health.rs +436 -0
  34. package/crates/team-agent/src/coordinator/mod.rs +80 -0
  35. package/crates/team-agent/src/coordinator/orphan.rs +179 -0
  36. package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
  37. package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
  38. package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
  39. package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
  40. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
  41. package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
  42. package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
  43. package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
  44. package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
  45. package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
  46. package/crates/team-agent/src/coordinator/tick.rs +2032 -0
  47. package/crates/team-agent/src/coordinator/types.rs +584 -0
  48. package/crates/team-agent/src/db/migration.rs +716 -0
  49. package/crates/team-agent/src/db/mod.rs +23 -0
  50. package/crates/team-agent/src/db/schema.rs +378 -0
  51. package/crates/team-agent/src/event_log.rs +375 -0
  52. package/crates/team-agent/src/fake_worker.rs +253 -0
  53. package/crates/team-agent/src/leader/helpers.rs +190 -0
  54. package/crates/team-agent/src/leader/inject.rs +33 -0
  55. package/crates/team-agent/src/leader/lease.rs +1063 -0
  56. package/crates/team-agent/src/leader/mod.rs +99 -0
  57. package/crates/team-agent/src/leader/owner_bind.rs +292 -0
  58. package/crates/team-agent/src/leader/rediscover/tests.rs +525 -0
  59. package/crates/team-agent/src/leader/rediscover.rs +1099 -0
  60. package/crates/team-agent/src/leader/start.rs +273 -0
  61. package/crates/team-agent/src/leader/takeover.rs +235 -0
  62. package/crates/team-agent/src/leader/tests/basics.rs +183 -0
  63. package/crates/team-agent/src/leader/tests/byte_findings.rs +234 -0
  64. package/crates/team-agent/src/leader/tests/identity.rs +206 -0
  65. package/crates/team-agent/src/leader/tests/idle.rs +271 -0
  66. package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
  67. package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -0
  68. package/crates/team-agent/src/leader/tests/mod.rs +125 -0
  69. package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
  70. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
  71. package/crates/team-agent/src/leader/types.rs +487 -0
  72. package/crates/team-agent/src/lib.rs +85 -0
  73. package/crates/team-agent/src/lifecycle/display.rs +228 -0
  74. package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
  75. package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
  76. package/crates/team-agent/src/lifecycle/launch.rs +1833 -0
  77. package/crates/team-agent/src/lifecycle/mod.rs +62 -0
  78. package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
  79. package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
  80. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
  81. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
  82. package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
  83. package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
  84. package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
  85. package/crates/team-agent/src/lifecycle/restart.rs +76 -0
  86. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
  87. package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
  88. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
  89. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +933 -0
  90. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
  91. package/crates/team-agent/src/lifecycle/tests.rs +27 -0
  92. package/crates/team-agent/src/lifecycle/types.rs +685 -0
  93. package/crates/team-agent/src/main.rs +41 -0
  94. package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
  95. package/crates/team-agent/src/mcp_server/mod.rs +183 -0
  96. package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
  97. package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
  98. package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
  99. package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
  100. package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
  101. package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
  102. package/crates/team-agent/src/mcp_server/tests/wire.rs +159 -0
  103. package/crates/team-agent/src/mcp_server/tests.rs +38 -0
  104. package/crates/team-agent/src/mcp_server/tools.rs +603 -0
  105. package/crates/team-agent/src/mcp_server/types.rs +421 -0
  106. package/crates/team-agent/src/mcp_server/wire.rs +388 -0
  107. package/crates/team-agent/src/message_store.rs +767 -0
  108. package/crates/team-agent/src/messaging/activity.rs +433 -0
  109. package/crates/team-agent/src/messaging/delivery.rs +542 -0
  110. package/crates/team-agent/src/messaging/helpers.rs +209 -0
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +340 -0
  112. package/crates/team-agent/src/messaging/mod.rs +147 -0
  113. package/crates/team-agent/src/messaging/peers.rs +32 -0
  114. package/crates/team-agent/src/messaging/results.rs +537 -0
  115. package/crates/team-agent/src/messaging/scheduler.rs +344 -0
  116. package/crates/team-agent/src/messaging/selftest.rs +100 -0
  117. package/crates/team-agent/src/messaging/send.rs +582 -0
  118. package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
  119. package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
  120. package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
  121. package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
  122. package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
  123. package/crates/team-agent/src/messaging/trust.rs +192 -0
  124. package/crates/team-agent/src/messaging/types.rs +355 -0
  125. package/crates/team-agent/src/messaging/watchers.rs +591 -0
  126. package/crates/team-agent/src/model/enums.rs +311 -0
  127. package/crates/team-agent/src/model/errors.rs +17 -0
  128. package/crates/team-agent/src/model/ids.rs +155 -0
  129. package/crates/team-agent/src/model/mod.rs +22 -0
  130. package/crates/team-agent/src/model/paths.rs +228 -0
  131. package/crates/team-agent/src/model/permissions.rs +567 -0
  132. package/crates/team-agent/src/model/routing.rs +340 -0
  133. package/crates/team-agent/src/model/spec.rs +680 -0
  134. package/crates/team-agent/src/model/task_graph.rs +380 -0
  135. package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
  136. package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
  137. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
  138. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
  139. package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
  140. package/crates/team-agent/src/model/yaml/tests.rs +288 -0
  141. package/crates/team-agent/src/model/yaml.rs +800 -0
  142. package/crates/team-agent/src/packaging/install.rs +305 -0
  143. package/crates/team-agent/src/packaging/migrate.rs +30 -0
  144. package/crates/team-agent/src/packaging/mod.rs +82 -0
  145. package/crates/team-agent/src/packaging/repair.rs +24 -0
  146. package/crates/team-agent/src/packaging/tests.rs +829 -0
  147. package/crates/team-agent/src/packaging/types.rs +369 -0
  148. package/crates/team-agent/src/provider/adapter.rs +801 -0
  149. package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
  150. package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
  151. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
  152. package/crates/team-agent/src/provider/classify.rs +456 -0
  153. package/crates/team-agent/src/provider/faults.rs +136 -0
  154. package/crates/team-agent/src/provider/helpers.rs +41 -0
  155. package/crates/team-agent/src/provider/mod.rs +53 -0
  156. package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
  157. package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
  158. package/crates/team-agent/src/provider/tests/classify.rs +240 -0
  159. package/crates/team-agent/src/provider/tests/faults.rs +120 -0
  160. package/crates/team-agent/src/provider/tests/idle.rs +208 -0
  161. package/crates/team-agent/src/provider/tests/wire.rs +213 -0
  162. package/crates/team-agent/src/provider/tests.rs +31 -0
  163. package/crates/team-agent/src/provider/types.rs +424 -0
  164. package/crates/team-agent/src/state/identity.rs +656 -0
  165. package/crates/team-agent/src/state/mod.rs +58 -0
  166. package/crates/team-agent/src/state/owner_gate.rs +423 -0
  167. package/crates/team-agent/src/state/persist.rs +712 -0
  168. package/crates/team-agent/src/state/projection.rs +657 -0
  169. package/crates/team-agent/src/state/selector.rs +105 -0
  170. package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
  171. package/crates/team-agent/src/tmux_backend/tests.rs +586 -0
  172. package/crates/team-agent/src/tmux_backend.rs +758 -0
  173. package/crates/team-agent/src/transport/test_support.rs +252 -0
  174. package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
  175. package/crates/team-agent/src/transport/tests/mod.rs +199 -0
  176. package/crates/team-agent/src/transport/tests/wire.rs +527 -0
  177. package/crates/team-agent/src/transport.rs +774 -0
  178. package/npm/install.mjs +90 -106
  179. package/package.json +15 -13
  180. package/crates/team-agent-core/Cargo.toml +0 -12
  181. package/crates/team-agent-core/src/lib.rs +0 -332
  182. package/crates/team-agent-core/src/main.rs +0 -152
  183. package/pyproject.toml +0 -18
  184. package/scripts/install.py +0 -88
  185. package/scripts/run_regression_tests.py +0 -83
  186. package/src/team_agent/__init__.py +0 -3
  187. package/src/team_agent/__main__.py +0 -5
  188. package/src/team_agent/_legacy_pane_discovery.py +0 -186
  189. package/src/team_agent/abnormal_track.py +0 -253
  190. package/src/team_agent/approvals/__init__.py +0 -65
  191. package/src/team_agent/approvals/constants.py +0 -6
  192. package/src/team_agent/approvals/parsing.py +0 -176
  193. package/src/team_agent/approvals/runtime_prompts.py +0 -171
  194. package/src/team_agent/approvals/status.py +0 -176
  195. package/src/team_agent/cli/__init__.py +0 -137
  196. package/src/team_agent/cli/commands.py +0 -481
  197. package/src/team_agent/cli/e2e.py +0 -202
  198. package/src/team_agent/cli/helpers.py +0 -226
  199. package/src/team_agent/cli/parser.py +0 -540
  200. package/src/team_agent/compiler.py +0 -334
  201. package/src/team_agent/coordinator/__init__.py +0 -53
  202. package/src/team_agent/coordinator/__main__.py +0 -119
  203. package/src/team_agent/coordinator/lifecycle.py +0 -411
  204. package/src/team_agent/coordinator/metadata.py +0 -61
  205. package/src/team_agent/coordinator/paths.py +0 -17
  206. package/src/team_agent/diagnose/__init__.py +0 -48
  207. package/src/team_agent/diagnose/checks.py +0 -101
  208. package/src/team_agent/diagnose/comms.py +0 -213
  209. package/src/team_agent/diagnose/health.py +0 -241
  210. package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
  211. package/src/team_agent/diagnose/preflight.py +0 -194
  212. package/src/team_agent/diagnose/quick_start.py +0 -324
  213. package/src/team_agent/display/__init__.py +0 -92
  214. package/src/team_agent/display/adaptive.py +0 -511
  215. package/src/team_agent/display/backend.py +0 -46
  216. package/src/team_agent/display/close.py +0 -154
  217. package/src/team_agent/display/ghostty.py +0 -77
  218. package/src/team_agent/display/rebuild.py +0 -102
  219. package/src/team_agent/display/tiling.py +0 -156
  220. package/src/team_agent/display/worker_window.py +0 -114
  221. package/src/team_agent/display/workspace.py +0 -382
  222. package/src/team_agent/errors.py +0 -10
  223. package/src/team_agent/events.py +0 -84
  224. package/src/team_agent/fake_worker.py +0 -80
  225. package/src/team_agent/idle_predicate.py +0 -218
  226. package/src/team_agent/idle_takeover.py +0 -59
  227. package/src/team_agent/idle_takeover_wiring.py +0 -114
  228. package/src/team_agent/launch/__init__.py +0 -41
  229. package/src/team_agent/launch/bootstrap.py +0 -85
  230. package/src/team_agent/launch/config.py +0 -106
  231. package/src/team_agent/launch/core.py +0 -301
  232. package/src/team_agent/launch/requirements.py +0 -57
  233. package/src/team_agent/leader/__init__.py +0 -926
  234. package/src/team_agent/leader_binding.py +0 -183
  235. package/src/team_agent/lifecycle/__init__.py +0 -5
  236. package/src/team_agent/lifecycle/agents.py +0 -278
  237. package/src/team_agent/lifecycle/operations.py +0 -411
  238. package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
  239. package/src/team_agent/lifecycle/start.py +0 -363
  240. package/src/team_agent/mcp_server/__init__.py +0 -42
  241. package/src/team_agent/mcp_server/__main__.py +0 -7
  242. package/src/team_agent/mcp_server/contracts.py +0 -148
  243. package/src/team_agent/mcp_server/normalize.py +0 -257
  244. package/src/team_agent/mcp_server/server.py +0 -150
  245. package/src/team_agent/mcp_server/tools.py +0 -352
  246. package/src/team_agent/message_store/__init__.py +0 -23
  247. package/src/team_agent/message_store/agent_health.py +0 -113
  248. package/src/team_agent/message_store/core.py +0 -497
  249. package/src/team_agent/message_store/leader_notification_log.py +0 -198
  250. package/src/team_agent/message_store/result_watchers.py +0 -251
  251. package/src/team_agent/message_store/schema.py +0 -308
  252. package/src/team_agent/message_store/schema_migration.py +0 -448
  253. package/src/team_agent/messaging/__init__.py +0 -1
  254. package/src/team_agent/messaging/activity_detector.py +0 -262
  255. package/src/team_agent/messaging/delivery.py +0 -504
  256. package/src/team_agent/messaging/deps.py +0 -247
  257. package/src/team_agent/messaging/idle_alerts.py +0 -423
  258. package/src/team_agent/messaging/internal_delivery.py +0 -46
  259. package/src/team_agent/messaging/leader.py +0 -497
  260. package/src/team_agent/messaging/leader_api_errors.py +0 -216
  261. package/src/team_agent/messaging/leader_panes.py +0 -673
  262. package/src/team_agent/messaging/owner_bypass.py +0 -29
  263. package/src/team_agent/messaging/result_delivery.py +0 -539
  264. package/src/team_agent/messaging/results.py +0 -447
  265. package/src/team_agent/messaging/scheduler.py +0 -450
  266. package/src/team_agent/messaging/send.py +0 -532
  267. package/src/team_agent/messaging/session_drift.py +0 -94
  268. package/src/team_agent/messaging/tmux_io.py +0 -506
  269. package/src/team_agent/messaging/tmux_prompt.py +0 -338
  270. package/src/team_agent/messaging/trust_auto_answer.py +0 -52
  271. package/src/team_agent/orchestrator/__init__.py +0 -376
  272. package/src/team_agent/orchestrator/plan.py +0 -122
  273. package/src/team_agent/orchestrator/state.py +0 -128
  274. package/src/team_agent/paths.py +0 -45
  275. package/src/team_agent/permissions.py +0 -123
  276. package/src/team_agent/profiles/__init__.py +0 -82
  277. package/src/team_agent/profiles/constants.py +0 -19
  278. package/src/team_agent/profiles/core.py +0 -407
  279. package/src/team_agent/profiles/helpers.py +0 -69
  280. package/src/team_agent/profiles/provider_env.py +0 -188
  281. package/src/team_agent/profiles/smoke.py +0 -201
  282. package/src/team_agent/provider_cli/__init__.py +0 -43
  283. package/src/team_agent/provider_cli/adapter.py +0 -172
  284. package/src/team_agent/provider_cli/base.py +0 -48
  285. package/src/team_agent/provider_cli/claude.py +0 -503
  286. package/src/team_agent/provider_cli/codex.py +0 -336
  287. package/src/team_agent/provider_cli/copilot.py +0 -8
  288. package/src/team_agent/provider_cli/fake.py +0 -39
  289. package/src/team_agent/provider_cli/gemini.py +0 -95
  290. package/src/team_agent/provider_cli/opencode.py +0 -8
  291. package/src/team_agent/provider_cli/prompt.py +0 -62
  292. package/src/team_agent/provider_cli/registry.py +0 -18
  293. package/src/team_agent/provider_cli/unsupported.py +0 -32
  294. package/src/team_agent/provider_state/README.md +0 -78
  295. package/src/team_agent/provider_state/__init__.py +0 -91
  296. package/src/team_agent/provider_state/claude.py +0 -86
  297. package/src/team_agent/provider_state/codex.py +0 -84
  298. package/src/team_agent/provider_state/common.py +0 -207
  299. package/src/team_agent/provider_state/registry.py +0 -118
  300. package/src/team_agent/providers.py +0 -163
  301. package/src/team_agent/quality_gates.py +0 -104
  302. package/src/team_agent/restart/__init__.py +0 -34
  303. package/src/team_agent/restart/orchestration.py +0 -554
  304. package/src/team_agent/restart/selection.py +0 -89
  305. package/src/team_agent/restart/snapshot.py +0 -70
  306. package/src/team_agent/routing.py +0 -84
  307. package/src/team_agent/runtime.py +0 -1243
  308. package/src/team_agent/rust_core.py +0 -327
  309. package/src/team_agent/sessions/__init__.py +0 -25
  310. package/src/team_agent/sessions/capture.py +0 -144
  311. package/src/team_agent/sessions/inventory.py +0 -44
  312. package/src/team_agent/sessions/resume.py +0 -135
  313. package/src/team_agent/simple_yaml.py +0 -236
  314. package/src/team_agent/spec.py +0 -370
  315. package/src/team_agent/state.py +0 -693
  316. package/src/team_agent/status/__init__.py +0 -63
  317. package/src/team_agent/status/approvals.py +0 -52
  318. package/src/team_agent/status/compact.py +0 -158
  319. package/src/team_agent/status/constants.py +0 -18
  320. package/src/team_agent/status/inbox.py +0 -58
  321. package/src/team_agent/status/peek.py +0 -117
  322. package/src/team_agent/status/queries.py +0 -199
  323. package/src/team_agent/task_graph.py +0 -80
  324. package/src/team_agent/terminal.py +0 -57
  325. package/src/team_agent/wake.py +0 -58
  326. package/src/team_agent/watch/__init__.py +0 -145
@@ -0,0 +1,380 @@
1
+ //! 移植 `team_agent/task_graph.py` 的三个纯函数:`find_dependency_cycle` /
2
+ //! `ready_tasks` / `update_task_status`(真相源 v0.2.11)。
3
+ //!
4
+ //! Python 在 `list[dict[str, Any]]` 上裸跑;此处把"一个 task"收成最小本地类型
5
+ //! [`TaskNode`](§3:id 用 [`TaskId`] newtype,status 用穷尽 [`TaskStatus`])。算法语义
6
+ //! **逐行对齐** Python:环检测的发现顺序(含 `stack.index` / `[node,node]` 两支)、ready
7
+ //! 判定的状态白名单 + deps 全 `done`、update 的状态校验与就地转移。
8
+ //!
9
+ //! §10:无 unwrap/expect/panic;未知状态 / 未知 id 返 [`ModelError`]。
10
+
11
+ use serde::{Deserialize, Serialize};
12
+
13
+ use crate::model::enums::TaskStatus;
14
+ use crate::model::errors::ModelError;
15
+ use crate::model::ids::TaskId;
16
+
17
+ /// 一个 task 节点 —— Python `dict` 里被 task_graph 触及的最小字段集。
18
+ ///
19
+ /// Python `status` 缺省视作 `"pending"`(`ready_tasks` 第 55 行 `get(...,"pending")`);
20
+ /// 此处 status 为必填穷尽枚举,缺省语义靠构造时落 [`TaskStatus::Pending`] 体现。
21
+ /// `last_result_summary` / `artifact_refs` 仅由 [`update_task_status`] 写入,故用 `Option`。
22
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23
+ pub struct TaskNode {
24
+ pub id: TaskId,
25
+ #[serde(default)]
26
+ pub deps: Vec<TaskId>,
27
+ pub status: TaskStatus,
28
+ #[serde(default, skip_serializing_if = "Option::is_none")]
29
+ pub last_result_summary: Option<String>,
30
+ /// `artifact_refs` 在 Python 是 `list[dict]` passthrough;此处保留为不透明 JSON 值。
31
+ #[serde(default, skip_serializing_if = "Option::is_none")]
32
+ pub artifact_refs: Option<Vec<serde_json::Value>>,
33
+ }
34
+
35
+ impl TaskNode {
36
+ /// 便捷构造:summary/artifacts 为空(仅由 [`update_task_status`] 写入)。
37
+ pub fn new(id: impl Into<TaskId>, deps: Vec<TaskId>, status: TaskStatus) -> Self {
38
+ Self {
39
+ id: id.into(),
40
+ deps,
41
+ status,
42
+ last_result_summary: None,
43
+ artifact_refs: None,
44
+ }
45
+ }
46
+ }
47
+
48
+ /// 找出依赖图中第一个被发现的环,返回其结点路径(含闭合的重复尾结点);无环返空 `Vec`。
49
+ ///
50
+ /// 逐行对齐 Python `find_dependency_cycle`(`task_graph.py:19-48`):
51
+ /// - graph 只含有 id 的 task(无 id 的被丢弃),按 task 列表顺序建立(= dict 插入序)。
52
+ /// - DFS 用 `visiting`/`visited`/`stack` 三态;命中正在访问的结点时,若它在 `stack` 则
53
+ /// 返回 `stack[idx..] + [node]`,否则退化返回 `[node, node]`(忠实保留此分支)。
54
+ /// - 外层按插入序遍历每个结点,返回首个命中的环。
55
+ pub fn find_dependency_cycle(tasks: &[TaskNode]) -> Vec<TaskId> {
56
+ // graph:保持插入序的 (id -> deps)。Python 用 dict comprehension,后写覆盖先写。
57
+ let mut order: Vec<&TaskId> = Vec::new();
58
+ let mut graph: std::collections::HashMap<&TaskId, &Vec<TaskId>> =
59
+ std::collections::HashMap::new();
60
+ for t in tasks {
61
+ if graph.insert(&t.id, &t.deps).is_none() {
62
+ order.push(&t.id);
63
+ }
64
+ }
65
+
66
+ let mut visiting: std::collections::HashSet<&TaskId> = std::collections::HashSet::new();
67
+ let mut visited: std::collections::HashSet<&TaskId> = std::collections::HashSet::new();
68
+ let mut stack: Vec<&TaskId> = Vec::new();
69
+
70
+ for &node in &order {
71
+ if let Some(cycle) = visit(node, &graph, &mut visiting, &mut visited, &mut stack) {
72
+ return cycle;
73
+ }
74
+ }
75
+ Vec::new()
76
+ }
77
+
78
+ /// `find_dependency_cycle` 内部 DFS(对齐 Python 闭包 `visit`,`task_graph.py:25-42`)。
79
+ /// 显式递归;命中环返回 `Some(path)`,否则 `None`。
80
+ fn visit<'a>(
81
+ node: &'a TaskId,
82
+ graph: &std::collections::HashMap<&'a TaskId, &'a Vec<TaskId>>,
83
+ visiting: &mut std::collections::HashSet<&'a TaskId>,
84
+ visited: &mut std::collections::HashSet<&'a TaskId>,
85
+ stack: &mut Vec<&'a TaskId>,
86
+ ) -> Option<Vec<TaskId>> {
87
+ if visited.contains(node) {
88
+ return None;
89
+ }
90
+ if visiting.contains(node) {
91
+ // `node in stack` → stack[index(node):] + [node];否则退化 [node, node]。
92
+ if let Some(idx) = stack.iter().position(|&n| n == node) {
93
+ let mut cycle: Vec<TaskId> = stack[idx..].iter().map(|&n| n.clone()).collect();
94
+ cycle.push(node.clone());
95
+ return Some(cycle);
96
+ }
97
+ return Some(vec![node.clone(), node.clone()]);
98
+ }
99
+ visiting.insert(node);
100
+ stack.push(node);
101
+ if let Some(deps) = graph.get(node) {
102
+ for dep in deps.iter() {
103
+ // Python:`if dep in graph` —— 只跟随图内结点,缺失依赖不入环。
104
+ if graph.contains_key(dep) {
105
+ if let Some(cycle) = visit(dep, graph, visiting, visited, stack) {
106
+ return Some(cycle);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ stack.pop();
112
+ visiting.remove(node);
113
+ visited.insert(node);
114
+ None
115
+ }
116
+
117
+ /// 返回所有"就绪"的 task(对齐 Python `ready_tasks`,`task_graph.py:51-60`):
118
+ /// status ∈ `{pending, ready, needs_retry}` **且** 所有依赖的 task 状态为 `done`。
119
+ ///
120
+ /// 依赖若不在 `tasks` 中(查无此 id),Python `by_id.get(dep,{}).get("status")` 为 `None`
121
+ /// → 非 `done` → 该 task 不就绪。此处用 `HashMap` 查表,缺失即 `None`,语义一致。
122
+ /// 保持输入顺序(Python 顺序遍历 `tasks`)。
123
+ pub fn ready_tasks(tasks: &[TaskNode]) -> Vec<&TaskNode> {
124
+ let by_id: std::collections::HashMap<&TaskId, &TaskNode> =
125
+ tasks.iter().map(|t| (&t.id, t)).collect();
126
+
127
+ let mut ready: Vec<&TaskNode> = Vec::new();
128
+ for task in tasks {
129
+ if !matches!(
130
+ task.status,
131
+ TaskStatus::Pending | TaskStatus::Ready | TaskStatus::NeedsRetry
132
+ ) {
133
+ continue;
134
+ }
135
+ let deps_done = task
136
+ .deps
137
+ .iter()
138
+ .all(|dep| by_id.get(dep).map(|d| d.status) == Some(TaskStatus::Done));
139
+ if deps_done {
140
+ ready.push(task);
141
+ }
142
+ }
143
+ ready
144
+ }
145
+
146
+ /// 就地更新某个 task 的状态(对齐 Python `update_task_status`,`task_graph.py:63-80`):
147
+ /// status 用穷尽 [`TaskStatus`] 已自带"合法状态集"约束(Python 的 `TASK_STATUSES` 校验
148
+ /// 由类型系统承担)。找到 id 匹配的 task 则写 status,并在 `summary`/`artifact_refs`
149
+ /// 为 `Some` 时一并写入(`None` 不触碰原字段,对齐 Python 的 `is not None` 守卫)。
150
+ ///
151
+ /// 找不到 id → `Err(ModelError::Runtime("Unknown task id: ..."))`(对齐 Python `KeyError`)。
152
+ pub fn update_task_status(
153
+ tasks: &mut [TaskNode],
154
+ task_id: &TaskId,
155
+ status: TaskStatus,
156
+ summary: Option<&str>,
157
+ artifact_refs: Option<Vec<serde_json::Value>>,
158
+ ) -> Result<(), ModelError> {
159
+ for task in tasks.iter_mut() {
160
+ if &task.id == task_id {
161
+ task.status = status;
162
+ if let Some(s) = summary {
163
+ task.last_result_summary = Some(s.to_string());
164
+ }
165
+ if let Some(refs) = artifact_refs {
166
+ task.artifact_refs = Some(refs);
167
+ }
168
+ return Ok(());
169
+ }
170
+ }
171
+ Err(ModelError::Runtime(format!("Unknown task id: {task_id}")))
172
+ }
173
+
174
+ #[cfg(test)]
175
+ mod tests {
176
+ #![allow(clippy::unwrap_used)]
177
+ use super::*;
178
+
179
+ fn t(id: &str, deps: &[&str], status: TaskStatus) -> TaskNode {
180
+ TaskNode::new(id, deps.iter().map(|d| TaskId::new(*d)).collect(), status)
181
+ }
182
+
183
+ fn cycle_ids(c: &[TaskId]) -> Vec<&str> {
184
+ c.iter().map(|i| i.as_str()).collect()
185
+ }
186
+ fn ready_ids<'a>(r: &[&'a TaskNode]) -> Vec<&'a str> {
187
+ r.iter().map(|n| n.id.as_str()).collect()
188
+ }
189
+
190
+ // ---- find_dependency_cycle:golden 来自 Python 真相源(§4.2 双跑) ----
191
+
192
+ #[test]
193
+ fn cycle_linear_chain_has_no_cycle() {
194
+ // Python C1 -> []
195
+ let tasks = [
196
+ t("a", &[], TaskStatus::Pending),
197
+ t("b", &["a"], TaskStatus::Pending),
198
+ t("c", &["b"], TaskStatus::Pending),
199
+ ];
200
+ assert!(find_dependency_cycle(&tasks).is_empty());
201
+ }
202
+
203
+ #[test]
204
+ fn cycle_two_node() {
205
+ // Python C2 -> ['a','b','a']
206
+ let tasks = [
207
+ t("a", &["b"], TaskStatus::Pending),
208
+ t("b", &["a"], TaskStatus::Pending),
209
+ ];
210
+ assert_eq!(cycle_ids(&find_dependency_cycle(&tasks)), vec!["a", "b", "a"]);
211
+ }
212
+
213
+ #[test]
214
+ fn cycle_three_node() {
215
+ // Python C3 -> ['a','b','c','a']
216
+ let tasks = [
217
+ t("a", &["b"], TaskStatus::Pending),
218
+ t("b", &["c"], TaskStatus::Pending),
219
+ t("c", &["a"], TaskStatus::Pending),
220
+ ];
221
+ assert_eq!(
222
+ cycle_ids(&find_dependency_cycle(&tasks)),
223
+ vec!["a", "b", "c", "a"]
224
+ );
225
+ }
226
+
227
+ #[test]
228
+ fn cycle_self_loop() {
229
+ // Python C4 -> ['a','a'](走 stack.index 分支,非 [node,node] 退化分支)
230
+ let tasks = [t("a", &["a"], TaskStatus::Pending)];
231
+ assert_eq!(cycle_ids(&find_dependency_cycle(&tasks)), vec!["a", "a"]);
232
+ }
233
+
234
+ #[test]
235
+ fn cycle_reached_from_acyclic_prefix() {
236
+ // Python C5:x->a->b->a -> ['a','b','a'](前缀 x 不在环里,被 stack.index 切掉)
237
+ let tasks = [
238
+ t("x", &["a"], TaskStatus::Pending),
239
+ t("a", &["b"], TaskStatus::Pending),
240
+ t("b", &["a"], TaskStatus::Pending),
241
+ ];
242
+ assert_eq!(cycle_ids(&find_dependency_cycle(&tasks)), vec!["a", "b", "a"]);
243
+ }
244
+
245
+ #[test]
246
+ fn cycle_missing_dep_is_ignored() {
247
+ // Python C6:a 依赖图外 'zzz' -> 不入环 -> []
248
+ let tasks = [
249
+ t("a", &["zzz"], TaskStatus::Pending),
250
+ t("b", &["a"], TaskStatus::Pending),
251
+ ];
252
+ assert!(find_dependency_cycle(&tasks).is_empty());
253
+ }
254
+
255
+ #[test]
256
+ fn cycle_empty_input() {
257
+ // Python C7 -> []
258
+ assert!(find_dependency_cycle(&[]).is_empty());
259
+ }
260
+
261
+ // ---- ready_tasks:golden 来自 Python 真相源 ----
262
+
263
+ #[test]
264
+ fn ready_pending_no_deps() {
265
+ // Python R1 -> ['a'](running 不在白名单)
266
+ let tasks = [
267
+ t("a", &[], TaskStatus::Pending),
268
+ t("b", &[], TaskStatus::Running),
269
+ ];
270
+ assert_eq!(ready_ids(&ready_tasks(&tasks)), vec!["a"]);
271
+ }
272
+
273
+ #[test]
274
+ fn ready_default_pending() {
275
+ // Python R2:无 status key 视作 pending -> ['a']。本类型用显式 Pending 表达缺省。
276
+ let tasks = [t("a", &[], TaskStatus::Pending)];
277
+ assert_eq!(ready_ids(&ready_tasks(&tasks)), vec!["a"]);
278
+ }
279
+
280
+ #[test]
281
+ fn ready_requires_deps_done() {
282
+ // Python R3:a done -> b 就绪;c 依赖未完成的 b -> 不就绪 -> ['b']
283
+ let tasks = [
284
+ t("a", &[], TaskStatus::Done),
285
+ t("b", &["a"], TaskStatus::Pending),
286
+ t("c", &["b"], TaskStatus::Pending),
287
+ ];
288
+ assert_eq!(ready_ids(&ready_tasks(&tasks)), vec!["b"]);
289
+ }
290
+
291
+ #[test]
292
+ fn ready_status_whitelist() {
293
+ // Python R4:needs_retry / ready 在白名单,blocked 不在 -> ['a','b']
294
+ let tasks = [
295
+ t("a", &[], TaskStatus::NeedsRetry),
296
+ t("b", &[], TaskStatus::Ready),
297
+ t("c", &[], TaskStatus::Blocked),
298
+ ];
299
+ assert_eq!(ready_ids(&ready_tasks(&tasks)), vec!["a", "b"]);
300
+ }
301
+
302
+ #[test]
303
+ fn ready_missing_dep_not_done() {
304
+ // Python R5:依赖查无此 id -> 视作非 done -> 不就绪 -> []
305
+ let tasks = [t("b", &["missing"], TaskStatus::Pending)];
306
+ assert!(ready_tasks(&tasks).is_empty());
307
+ }
308
+
309
+ #[test]
310
+ fn ready_terminal_statuses_excluded() {
311
+ // Python R6:done/failed/cancelled 均不在白名单 -> []
312
+ let tasks = [
313
+ t("a", &[], TaskStatus::Done),
314
+ t("b", &[], TaskStatus::Failed),
315
+ t("c", &[], TaskStatus::Cancelled),
316
+ ];
317
+ assert!(ready_tasks(&tasks).is_empty());
318
+ }
319
+
320
+ // ---- update_task_status:golden 来自 Python 真相源 ----
321
+
322
+ #[test]
323
+ fn update_sets_status_summary_and_artifacts() {
324
+ // Python U1:a -> done + summary "ok" + artifact_refs [{"path":"x"}]
325
+ let mut tasks = [
326
+ t("a", &[], TaskStatus::Pending),
327
+ t("b", &["a"], TaskStatus::Pending),
328
+ ];
329
+ let refs = vec![serde_json::json!({"path": "x"})];
330
+ update_task_status(
331
+ &mut tasks,
332
+ &TaskId::new("a"),
333
+ TaskStatus::Done,
334
+ Some("ok"),
335
+ Some(refs),
336
+ )
337
+ .unwrap();
338
+ assert_eq!(tasks[0].status, TaskStatus::Done);
339
+ assert_eq!(tasks[0].last_result_summary.as_deref(), Some("ok"));
340
+ assert_eq!(
341
+ tasks[0].artifact_refs,
342
+ Some(vec![serde_json::json!({"path": "x"})])
343
+ );
344
+ // b 不动
345
+ assert_eq!(tasks[1].status, TaskStatus::Pending);
346
+ }
347
+
348
+ #[test]
349
+ fn update_status_only_leaves_other_fields() {
350
+ // Python U2:仅改 status,既有 last_result_summary "old" 不被触碰
351
+ let mut node = t("a", &[], TaskStatus::Pending);
352
+ node.last_result_summary = Some("old".to_string());
353
+ let mut tasks = [node];
354
+ update_task_status(&mut tasks, &TaskId::new("a"), TaskStatus::Running, None, None).unwrap();
355
+ assert_eq!(tasks[0].status, TaskStatus::Running);
356
+ assert_eq!(tasks[0].last_result_summary.as_deref(), Some("old"));
357
+ }
358
+
359
+ #[test]
360
+ fn update_unknown_id_errors() {
361
+ // Python U4:未知 id -> KeyError "Unknown task id: zzz"。Rust -> Runtime。
362
+ let mut tasks = [t("a", &[], TaskStatus::Pending)];
363
+ let err =
364
+ update_task_status(&mut tasks, &TaskId::new("zzz"), TaskStatus::Done, None, None)
365
+ .unwrap_err();
366
+ assert_eq!(err, ModelError::Runtime("Unknown task id: zzz".to_string()));
367
+ }
368
+
369
+ #[test]
370
+ fn update_summary_none_but_artifacts_set() {
371
+ // Python U5:summary=None 只写 artifact_refs
372
+ let mut tasks = [t("a", &[], TaskStatus::Pending)];
373
+ let refs = vec![serde_json::json!({"r": 1})];
374
+ update_task_status(&mut tasks, &TaskId::new("a"), TaskStatus::Done, None, Some(refs))
375
+ .unwrap();
376
+ assert_eq!(tasks[0].status, TaskStatus::Done);
377
+ assert!(tasks[0].last_result_summary.is_none());
378
+ assert_eq!(tasks[0].artifact_refs, Some(vec![serde_json::json!({"r": 1})]));
379
+ }
380
+ }
@@ -0,0 +1,43 @@
1
+ version: 2
2
+ name: "edge-fixture"
3
+ empty_map: {}
4
+ empty_list: []
5
+ nullval: null
6
+ boolt: true
7
+ boolf: "no"
8
+ n1: 42
9
+ n2: 0
10
+ n3: 17
11
+ not_int: "3.14"
12
+ not_int2: "0xFF"
13
+ quoted_d: "he said \"hi\""
14
+ quoted_s: "plain"
15
+ unquoted_list: "[x, y, z]"
16
+ quoted_list: "['a', \"b\", 3, true, null]"
17
+ inline_comment: "value # not a comment"
18
+ nested:
19
+ deep:
20
+ deeper:
21
+ x: 1
22
+ y: "two"
23
+ list_of_maps:
24
+ - id: "a"
25
+ tags: "[p, q]"
26
+ meta:
27
+ kind: "leaf"
28
+ - id: "b"
29
+ nested_list:
30
+ - "one"
31
+ - "two"
32
+ scalars:
33
+ - 1
34
+ - "hello"
35
+ - false
36
+ block:
37
+ text: |
38
+ first line
39
+ second line
40
+
41
+ after blank
42
+ empty_after_key: null
43
+ trailing: "end"
@@ -0,0 +1,43 @@
1
+ version: 2
2
+ name: edge-fixture
3
+ empty_map: {}
4
+ empty_list: []
5
+ nullval: ~
6
+ boolt: True
7
+ boolf: no
8
+ n1: 042
9
+ n2: -0
10
+ n3: +17
11
+ not_int: 3.14
12
+ not_int2: 0xFF
13
+ quoted_d: "he said \"hi\""
14
+ quoted_s: 'plain'
15
+ unquoted_list: [x, y, z]
16
+ quoted_list: ['a', "b", 3, true, null]
17
+ inline_comment: value # not a comment
18
+ nested:
19
+ deep:
20
+ deeper:
21
+ x: 1
22
+ y: two
23
+ list_of_maps:
24
+ - id: a
25
+ tags: [p, q]
26
+ meta:
27
+ kind: leaf
28
+ - id: b
29
+ nested_list:
30
+ - one
31
+ - two
32
+ scalars:
33
+ - 1
34
+ - hello
35
+ - false
36
+ block:
37
+ text: |
38
+ first line
39
+ second line
40
+
41
+ after blank
42
+ empty_after_key:
43
+ trailing: end
@@ -0,0 +1,207 @@
1
+ version: 2
2
+ team:
3
+ name: "teamspec-full-example"
4
+ mode: "bad_mode"
5
+ objective: "Build, research, review, and document a code change with Codex CLI workers."
6
+ workspace: "."
7
+ leader:
8
+ id: "leader"
9
+ role: "leader"
10
+ provider: "badprov"
11
+ model: null
12
+ tools:
13
+ - "fs_read"
14
+ - "fs_list"
15
+ - "mcp_team"
16
+ - "provider_builtin"
17
+ context_policy:
18
+ keep_user_thread: true
19
+ receive_worker_outputs: "structured_only"
20
+ max_worker_result_tokens: 4000
21
+ agents:
22
+ - id: "codex_implementer"
23
+ role: "implementation_engineer"
24
+ provider: "codex"
25
+ model: null
26
+ working_directory: "."
27
+ system_prompt:
28
+ inline: |
29
+ You are the implementation worker. Make focused code changes, run relevant tests,
30
+ and report a result_envelope_v1 with changed files, tests, risks, artifacts, and next actions.
31
+ file: null
32
+ tools:
33
+ - "fs_read"
34
+ - "fs_write"
35
+ - "fs_list"
36
+ - "execute_bash"
37
+ - "git_diff"
38
+ - "mcp_team"
39
+ - "provider_builtin"
40
+ - "banana"
41
+ permission_mode: "restricted"
42
+ preferred_for:
43
+ - "implementation"
44
+ - "bug_fix"
45
+ - "test"
46
+ avoid_for:
47
+ - "final_risk_signoff"
48
+ output_contract:
49
+ format: "result_envelope_v1"
50
+ required_fields:
51
+ - "task_id"
52
+ - "status"
53
+ - "summary"
54
+ - "artifacts"
55
+ - id: "codex_researcher"
56
+ role: "researcher"
57
+ provider: "codex"
58
+ model: null
59
+ working_directory: "."
60
+ system_prompt:
61
+ inline: |
62
+ You are the research worker. Prefer read-only analysis and summarize findings
63
+ as result_envelope_v1. Do not edit files.
64
+ file: null
65
+ tools:
66
+ - "fs_read"
67
+ - "fs_list"
68
+ - "network"
69
+ - "mcp_team"
70
+ - "provider_builtin"
71
+ permission_mode: "restricted"
72
+ preferred_for:
73
+ - "research"
74
+ - "architecture"
75
+ - "docs"
76
+ avoid_for:
77
+ - "implementation"
78
+ output_contract:
79
+ format: "result_envelope_v1"
80
+ required_fields:
81
+ - "task_id"
82
+ - "status"
83
+ - "summary"
84
+ - "artifacts"
85
+ - id: "codex_reviewer"
86
+ role: "code_reviewer"
87
+ provider: "codex"
88
+ model: null
89
+ working_directory: "."
90
+ system_prompt:
91
+ inline: |
92
+ You are the reviewer. Find correctness, regression, security, and missing-test risks.
93
+ Stay read-only unless the leader explicitly changes your permissions.
94
+ file: null
95
+ tools:
96
+ - "fs_read"
97
+ - "fs_list"
98
+ - "git_diff"
99
+ - "mcp_team"
100
+ - "provider_builtin"
101
+ permission_mode: "restricted"
102
+ preferred_for:
103
+ - "review"
104
+ - "risk_check"
105
+ avoid_for:
106
+ - "implementation"
107
+ output_contract:
108
+ format: "result_envelope_v1"
109
+ required_fields:
110
+ - "task_id"
111
+ - "status"
112
+ - "summary"
113
+ - "artifacts"
114
+ routing:
115
+ default_assignee: "leader"
116
+ rules:
117
+ - id: "implementation-to-codex"
118
+ when: "task.type in [\"implementation\", \"bug_fix\", \"test\"]"
119
+ assign_to: "codex_implementer"
120
+ priority: 100
121
+ - id: "research-to-codex"
122
+ when: "task.type in [\"research\", \"architecture\", \"docs\"]"
123
+ assign_to: "codex_researcher"
124
+ priority: 90
125
+ - id: "review-to-codex"
126
+ when: "task.type in [\"review\", \"risk_check\"]"
127
+ assign_to: "codex_reviewer"
128
+ priority: 90
129
+ communication:
130
+ protocol: "mcp_inbox"
131
+ topology: "leader_centered"
132
+ worker_to_worker: false
133
+ ack_timeout_sec: 60
134
+ result_format: "result_envelope_v1"
135
+ message_store:
136
+ sqlite: ".team/runtime/team.db"
137
+ mirror_files: ".team/messages"
138
+ runtime:
139
+ backend: "tmux"
140
+ display_backend: "none"
141
+ session_name: "teamspec-full-example"
142
+ auto_launch: true
143
+ require_user_approval_before_launch: true
144
+ dangerous_auto_approve: false
145
+ max_active_agents: 3
146
+ startup_order:
147
+ - "codex_implementer"
148
+ - "codex_researcher"
149
+ - "codex_reviewer"
150
+ context:
151
+ state_file: "team_state.md"
152
+ artifact_dir: ".team/artifacts"
153
+ log_dir: ".team/logs"
154
+ summarization:
155
+ worker_full_logs: "retain_outside_leader_context"
156
+ state_update: "after_each_result"
157
+ tasks:
158
+ - id: "task_research"
159
+ title: "Read the task context and identify design risks."
160
+ type: "research"
161
+ assignee: null
162
+ deps: []
163
+ acceptance:
164
+ - "Result envelope includes summary and risks."
165
+ status: "pending"
166
+ requires_tools:
167
+ - "fs_read"
168
+ files:
169
+ - "**/*"
170
+ risk: "medium"
171
+ retry_limit: 1
172
+ human_confirmation: false
173
+ - id: "task_impl"
174
+ title: "Implement the requested code change and run tests."
175
+ type: "implementation"
176
+ assignee: null
177
+ deps:
178
+ - "task_research"
179
+ acceptance:
180
+ - "Changed files and tests are reported."
181
+ status: "pending"
182
+ requires_tools:
183
+ - "fs_write"
184
+ - "execute_bash"
185
+ files:
186
+ - "src/**"
187
+ - "tests/**"
188
+ risk: "medium"
189
+ retry_limit: 1
190
+ human_confirmation: false
191
+ - id: "task_review"
192
+ title: "Review implementation output and identify regressions."
193
+ type: "review"
194
+ assignee: null
195
+ deps:
196
+ - "task_impl"
197
+ acceptance:
198
+ - "Findings are structured with risk and artifacts."
199
+ status: "pending"
200
+ requires_tools:
201
+ - "fs_read"
202
+ - "git_diff"
203
+ files:
204
+ - "**/*"
205
+ risk: "medium"
206
+ retry_limit: 0
207
+ human_confirmation: false