pi-crew 0.2.3 → 0.2.5

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 (348) hide show
  1. package/AGENTS.md +57 -32
  2. package/CHANGELOG.md +466 -448
  3. package/LICENSE +21 -21
  4. package/NOTICE.md +16 -16
  5. package/README.md +323 -323
  6. package/docs/FEATURE_INTAKE.md +126 -0
  7. package/docs/HARNESS.md +86 -0
  8. package/docs/HARNESS_BACKLOG.md +41 -0
  9. package/docs/TEST_MATRIX.md +49 -0
  10. package/docs/actions-reference.md +595 -595
  11. package/docs/architecture.md +180 -180
  12. package/docs/code-review-2026-05-11.md +592 -592
  13. package/docs/commands-reference.md +347 -347
  14. package/docs/comparison-pi-subagents-vs-pi-crew.md +303 -0
  15. package/docs/decisions/0001-durable-state.md +41 -0
  16. package/docs/decisions/0002-child-process-for-async.md +42 -0
  17. package/docs/decisions/0003-depth-guard.md +36 -0
  18. package/docs/decisions/0004-execfile-over-exec.md +34 -0
  19. package/docs/decisions/0005-no-parameter-properties.md +49 -0
  20. package/docs/decisions/0006-publish-bundled-esm.md +63 -0
  21. package/docs/decisions/0007-active-run-binary-index.md +54 -0
  22. package/docs/decisions/0008-child-pi-warm-pool.md +61 -0
  23. package/docs/decisions/README.md +23 -0
  24. package/docs/followup-review-round4-2026-05-13.md +107 -0
  25. package/docs/implementation-plan-top3.md +333 -0
  26. package/docs/live-mailbox-runtime.md +36 -36
  27. package/docs/next-upgrade-roadmap.md +808 -808
  28. package/docs/oh-my-pi-research.md +509 -0
  29. package/docs/perf/baseline-2026-05.md +113 -0
  30. package/docs/perf/final-report-2026-05.md +206 -0
  31. package/docs/perf/sprint-1-report.md +71 -0
  32. package/docs/perf/sprint-2-report.md +81 -0
  33. package/docs/perf/sprint-2.5-report.md +53 -0
  34. package/docs/perf/sprint-3-report.md +36 -0
  35. package/docs/perf/sprint-4-report.md +47 -0
  36. package/docs/perf/sprint-5-report.md +51 -0
  37. package/docs/perf/sprint-6-report.md +94 -0
  38. package/docs/perf/sprint-7-report.md +74 -0
  39. package/docs/perf/upgrade-plan-2026-05.md +147 -0
  40. package/docs/pi-subagents3-deep-analysis.md +508 -0
  41. package/docs/product/README.md +31 -0
  42. package/docs/product/platform.md +27 -0
  43. package/docs/product/runtime-safety.md +37 -0
  44. package/docs/product/team-run.md +39 -0
  45. package/docs/product/team-tool.md +37 -0
  46. package/docs/publishing.md +65 -65
  47. package/docs/resource-formats.md +134 -134
  48. package/docs/runtime-analysis-child-vs-live.md +171 -0
  49. package/docs/runtime-flow.md +148 -148
  50. package/docs/runtime-migration-in-process-analysis.md +250 -0
  51. package/docs/stories/README.md +30 -0
  52. package/docs/stories/backlog.md +36 -0
  53. package/docs/templates/decision.md +27 -0
  54. package/docs/templates/story.md +44 -0
  55. package/docs/templates/validation-report.md +32 -0
  56. package/docs/usage.md +238 -238
  57. package/index.ts +7 -6
  58. package/install.mjs +65 -65
  59. package/package.json +107 -100
  60. package/schema.json +222 -222
  61. package/skills/child-pi-spawning/SKILL.md +213 -0
  62. package/skills/context-artifact-hygiene/SKILL.md +32 -0
  63. package/skills/event-log-tracing/SKILL.md +299 -0
  64. package/skills/git-master/SKILL.md +225 -24
  65. package/skills/live-agent-lifecycle/SKILL.md +192 -0
  66. package/skills/mailbox-interactive/SKILL.md +300 -19
  67. package/skills/model-routing-context/SKILL.md +94 -0
  68. package/skills/multi-perspective-review/SKILL.md +88 -0
  69. package/skills/read-only-explorer/SKILL.md +250 -26
  70. package/skills/safe-bash/SKILL.md +307 -21
  71. package/skills/verification-before-done/SKILL.md +11 -2
  72. package/skills/widget-rendering/SKILL.md +258 -0
  73. package/skills/workspace-isolation/SKILL.md +202 -0
  74. package/skills/worktree-isolation/SKILL.md +202 -18
  75. package/src/adapters/claude-adapter.ts +25 -25
  76. package/src/adapters/codex-adapter.ts +21 -21
  77. package/src/adapters/cursor-adapter.ts +17 -17
  78. package/src/adapters/export-util.ts +137 -137
  79. package/src/adapters/index.ts +15 -15
  80. package/src/adapters/registry.ts +18 -18
  81. package/src/adapters/types.ts +23 -23
  82. package/src/agents/agent-config.ts +38 -38
  83. package/src/agents/agent-serializer.ts +38 -38
  84. package/src/agents/discover-agents.ts +121 -118
  85. package/src/config/config.ts +740 -858
  86. package/src/config/defaults.ts +96 -96
  87. package/src/config/drift-detector.ts +211 -211
  88. package/src/config/markers.ts +327 -327
  89. package/src/config/resilient-parser.ts +109 -108
  90. package/src/config/suggestions.ts +74 -74
  91. package/src/config/types.ts +199 -0
  92. package/src/extension/async-notifier.ts +123 -89
  93. package/src/extension/autonomous-policy.ts +169 -169
  94. package/src/extension/cross-extension-rpc.ts +104 -104
  95. package/src/extension/help.ts +47 -47
  96. package/src/extension/import-index.ts +69 -69
  97. package/src/extension/management.ts +395 -382
  98. package/src/extension/notification-router.ts +116 -116
  99. package/src/extension/notification-sink.ts +51 -51
  100. package/src/extension/project-init.ts +168 -168
  101. package/src/extension/register.ts +859 -668
  102. package/src/extension/registration/artifact-cleanup.ts +15 -15
  103. package/src/extension/registration/command-utils.ts +54 -54
  104. package/src/extension/registration/commands.ts +559 -452
  105. package/src/extension/registration/compaction-guard.ts +125 -125
  106. package/src/extension/registration/subagent-helpers.ts +102 -102
  107. package/src/extension/registration/subagent-tools.ts +220 -159
  108. package/src/extension/registration/team-tool.ts +159 -99
  109. package/src/extension/registration/viewers.ts +29 -0
  110. package/src/extension/result-watcher.ts +128 -128
  111. package/src/extension/run-bundle-schema.ts +89 -89
  112. package/src/extension/run-export.ts +73 -73
  113. package/src/extension/run-import.ts +84 -84
  114. package/src/extension/run-index.ts +94 -94
  115. package/src/extension/run-maintenance.ts +142 -142
  116. package/src/extension/session-summary.ts +8 -8
  117. package/src/extension/team-manager-command.ts +96 -96
  118. package/src/extension/team-recommendation.ts +188 -188
  119. package/src/extension/team-tool/api.ts +5 -2
  120. package/src/extension/team-tool/cancel.ts +224 -209
  121. package/src/extension/team-tool/config-patch.ts +36 -36
  122. package/src/extension/team-tool/context.ts +60 -60
  123. package/src/extension/team-tool/doctor.ts +242 -242
  124. package/src/extension/team-tool/handle-settings.ts +421 -195
  125. package/src/extension/team-tool/inspect.ts +41 -41
  126. package/src/extension/team-tool/lifecycle-actions.ts +139 -139
  127. package/src/extension/team-tool/parallel-dispatch.ts +156 -156
  128. package/src/extension/team-tool/plan.ts +19 -19
  129. package/src/extension/team-tool/respond.ts +112 -111
  130. package/src/extension/team-tool/run.ts +246 -229
  131. package/src/extension/team-tool/status.ts +110 -110
  132. package/src/extension/team-tool-types.ts +13 -13
  133. package/src/extension/team-tool.ts +344 -344
  134. package/src/extension/tool-result.ts +16 -16
  135. package/src/extension/validate-resources.ts +77 -77
  136. package/src/hooks/registry.ts +61 -61
  137. package/src/hooks/types.ts +40 -40
  138. package/src/i18n.ts +184 -184
  139. package/src/observability/correlation.ts +35 -35
  140. package/src/observability/event-to-metric.ts +68 -68
  141. package/src/observability/exporters/adapter.ts +30 -30
  142. package/src/observability/exporters/otlp-exporter.ts +106 -92
  143. package/src/observability/exporters/prometheus-exporter.ts +54 -54
  144. package/src/observability/metric-registry.ts +87 -87
  145. package/src/observability/metric-retention.ts +54 -54
  146. package/src/observability/metric-sink.ts +81 -56
  147. package/src/observability/metrics-primitives.ts +167 -167
  148. package/src/prompt/prompt-runtime.ts +72 -72
  149. package/src/runtime/adaptive-plan.ts +338 -0
  150. package/src/runtime/agent-control.ts +169 -169
  151. package/src/runtime/agent-memory.ts +72 -72
  152. package/src/runtime/agent-observability.ts +114 -114
  153. package/src/runtime/async-marker.ts +26 -26
  154. package/src/runtime/async-runner.ts +153 -153
  155. package/src/runtime/attention-events.ts +28 -28
  156. package/src/runtime/auto-resume.ts +100 -100
  157. package/src/runtime/background-runner.ts +122 -89
  158. package/src/runtime/cancellation.ts +61 -61
  159. package/src/runtime/capability-inventory.ts +116 -116
  160. package/src/runtime/child-pi-pool.ts +68 -0
  161. package/src/runtime/child-pi.ts +541 -461
  162. package/src/runtime/code-summary.ts +247 -247
  163. package/src/runtime/compaction-summary.ts +271 -271
  164. package/src/runtime/concurrency.ts +58 -58
  165. package/src/runtime/crash-recovery.ts +317 -301
  166. package/src/runtime/crew-agent-records.ts +379 -281
  167. package/src/runtime/crew-agent-runtime.ts +60 -60
  168. package/src/runtime/cross-extension-rpc.ts +72 -0
  169. package/src/runtime/custom-tools/irc-tool.ts +201 -201
  170. package/src/runtime/custom-tools/submit-result-tool.ts +90 -90
  171. package/src/runtime/deadletter.ts +47 -47
  172. package/src/runtime/delivery-coordinator.ts +176 -176
  173. package/src/runtime/delta-conflict.ts +360 -360
  174. package/src/runtime/diagnostic-export.ts +102 -102
  175. package/src/runtime/direct-run.ts +35 -35
  176. package/src/runtime/effectiveness.ts +82 -81
  177. package/src/runtime/errors/crew-errors.ts +166 -0
  178. package/src/runtime/event-stream-bridge.ts +92 -92
  179. package/src/runtime/foreground-control.ts +82 -82
  180. package/src/runtime/green-contract.ts +46 -46
  181. package/src/runtime/group-join.ts +234 -106
  182. package/src/runtime/heartbeat-watcher.ts +145 -124
  183. package/src/runtime/iteration-hooks.ts +267 -267
  184. package/src/runtime/live-agent-control.ts +88 -88
  185. package/src/runtime/live-agent-manager.ts +377 -179
  186. package/src/runtime/live-control-realtime.ts +36 -36
  187. package/src/runtime/live-session-runtime.ts +676 -600
  188. package/src/runtime/loop-gates.ts +129 -129
  189. package/src/runtime/manifest-cache.ts +263 -263
  190. package/src/runtime/mcp-proxy.ts +113 -113
  191. package/src/runtime/metric-parser.ts +40 -40
  192. package/src/runtime/model-fallback.ts +282 -274
  193. package/src/runtime/model-resolver.ts +118 -0
  194. package/src/runtime/output-validator.ts +187 -187
  195. package/src/runtime/overflow-recovery.ts +175 -175
  196. package/src/runtime/parallel-research.ts +44 -44
  197. package/src/runtime/parallel-utils.ts +156 -156
  198. package/src/runtime/parent-guard.ts +80 -80
  199. package/src/runtime/phase-progress.ts +217 -217
  200. package/src/runtime/pi-args.ts +165 -165
  201. package/src/runtime/pi-json-output.ts +111 -111
  202. package/src/runtime/pi-spawn.ts +167 -167
  203. package/src/runtime/policy-engine.ts +79 -79
  204. package/src/runtime/post-checks.ts +125 -125
  205. package/src/runtime/post-exit-stdio-guard.ts +86 -86
  206. package/src/runtime/process-status.ts +97 -73
  207. package/src/runtime/progress-event-coalescer.ts +43 -43
  208. package/src/runtime/recovery-recipes.ts +74 -74
  209. package/src/runtime/retry-executor.ts +81 -81
  210. package/src/runtime/role-permission.ts +39 -39
  211. package/src/runtime/run-tracker.ts +99 -0
  212. package/src/runtime/runtime-policy.ts +21 -0
  213. package/src/runtime/runtime-resolver.ts +94 -91
  214. package/src/runtime/scheduler.ts +294 -0
  215. package/src/runtime/semaphore.ts +131 -131
  216. package/src/runtime/sensitive-paths.ts +92 -92
  217. package/src/runtime/session-usage.ts +79 -79
  218. package/src/runtime/settings-store.ts +103 -0
  219. package/src/runtime/sidechain-output.ts +29 -29
  220. package/src/runtime/skill-instructions.ts +222 -222
  221. package/src/runtime/stale-reconciler.ts +198 -189
  222. package/src/runtime/streaming-output.ts +47 -0
  223. package/src/runtime/subagent-manager.ts +404 -400
  224. package/src/runtime/subprocess-tool-registry.ts +67 -67
  225. package/src/runtime/task-display.ts +38 -38
  226. package/src/runtime/task-graph-scheduler.ts +122 -122
  227. package/src/runtime/task-graph.ts +207 -207
  228. package/src/runtime/task-output-context.ts +177 -177
  229. package/src/runtime/task-packet.ts +93 -93
  230. package/src/runtime/task-quality.ts +207 -207
  231. package/src/runtime/task-runner/capabilities.ts +78 -78
  232. package/src/runtime/task-runner/live-executor.ts +131 -113
  233. package/src/runtime/task-runner/progress.ts +119 -119
  234. package/src/runtime/task-runner/prompt-builder.ts +139 -139
  235. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  236. package/src/runtime/task-runner/result-utils.ts +14 -14
  237. package/src/runtime/task-runner/run-projection.ts +103 -103
  238. package/src/runtime/task-runner/state-helpers.ts +22 -22
  239. package/src/runtime/task-runner.ts +469 -459
  240. package/src/runtime/team-runner.ts +693 -945
  241. package/src/runtime/usage-tracker.ts +71 -0
  242. package/src/runtime/worker-heartbeat.ts +21 -21
  243. package/src/runtime/worker-startup.ts +57 -57
  244. package/src/runtime/workflow-state.ts +187 -187
  245. package/src/runtime/yield-handler.ts +190 -190
  246. package/src/schema/config-schema.ts +172 -168
  247. package/src/schema/team-tool-schema.ts +126 -126
  248. package/src/schema/validation-types.ts +151 -148
  249. package/src/skills/discover-skills.ts +67 -67
  250. package/src/skills/skill-templates.ts +374 -374
  251. package/src/state/active-run-registry.ts +227 -191
  252. package/src/state/artifact-store.ts +130 -129
  253. package/src/state/atomic-write.ts +262 -195
  254. package/src/state/blob-store.ts +116 -116
  255. package/src/state/contracts.ts +111 -111
  256. package/src/state/event-log-rotation.ts +161 -158
  257. package/src/state/event-log.ts +383 -303
  258. package/src/state/event-reconstructor.ts +217 -217
  259. package/src/state/jsonl-writer.ts +82 -82
  260. package/src/state/locks.ts +146 -146
  261. package/src/state/mailbox.ts +446 -405
  262. package/src/state/state-store.ts +364 -351
  263. package/src/state/task-claims.ts +44 -44
  264. package/src/state/types.ts +285 -285
  265. package/src/state/usage.ts +29 -29
  266. package/src/subagents/async-entry.ts +1 -1
  267. package/src/subagents/index.ts +3 -3
  268. package/src/subagents/live/control.ts +1 -1
  269. package/src/subagents/live/manager.ts +1 -1
  270. package/src/subagents/live/realtime.ts +1 -1
  271. package/src/subagents/live/session-runtime.ts +1 -1
  272. package/src/subagents/manager.ts +1 -1
  273. package/src/subagents/spawn.ts +1 -1
  274. package/src/teams/discover-teams.ts +116 -116
  275. package/src/teams/team-config.ts +27 -27
  276. package/src/teams/team-serializer.ts +38 -38
  277. package/src/types/diff.d.ts +18 -18
  278. package/src/ui/agent-management-overlay.ts +144 -144
  279. package/src/ui/crew-widget.ts +487 -370
  280. package/src/ui/dashboard-panes/agents-pane.ts +109 -28
  281. package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
  282. package/src/ui/dashboard-panes/capability-pane.ts +59 -59
  283. package/src/ui/dashboard-panes/health-pane.ts +30 -30
  284. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
  285. package/src/ui/dashboard-panes/progress-pane.ts +30 -30
  286. package/src/ui/dashboard-panes/transcript-pane.ts +10 -10
  287. package/src/ui/heartbeat-aggregator.ts +63 -63
  288. package/src/ui/keybinding-map.ts +97 -94
  289. package/src/ui/live-conversation-overlay.ts +152 -0
  290. package/src/ui/live-run-sidebar.ts +180 -180
  291. package/src/ui/mascot.ts +442 -442
  292. package/src/ui/overlays/agent-picker-overlay.ts +57 -57
  293. package/src/ui/overlays/confirm-overlay.ts +58 -58
  294. package/src/ui/overlays/mailbox-compose-overlay.ts +144 -144
  295. package/src/ui/overlays/mailbox-compose-preview.ts +63 -63
  296. package/src/ui/overlays/mailbox-detail-overlay.ts +122 -122
  297. package/src/ui/pi-ui-compat.ts +57 -57
  298. package/src/ui/powerbar-publisher.ts +221 -197
  299. package/src/ui/render-scheduler.ts +216 -143
  300. package/src/ui/run-action-dispatcher.ts +118 -118
  301. package/src/ui/run-dashboard.ts +526 -464
  302. package/src/ui/run-event-bus.ts +208 -208
  303. package/src/ui/run-snapshot-cache.ts +826 -777
  304. package/src/ui/settings-overlay.ts +721 -0
  305. package/src/ui/snapshot-types.ts +86 -70
  306. package/src/ui/theme-adapter.ts +190 -190
  307. package/src/ui/tool-progress-formatter.ts +89 -0
  308. package/src/ui/transcript-cache.ts +94 -94
  309. package/src/ui/transcript-viewer.ts +335 -335
  310. package/src/utils/conflict-detect.ts +662 -0
  311. package/src/utils/file-coalescer.ts +86 -86
  312. package/src/utils/frontmatter.ts +68 -68
  313. package/src/utils/fs-watch.ts +88 -31
  314. package/src/utils/gh-protocol.ts +479 -0
  315. package/src/utils/ids.ts +17 -17
  316. package/src/utils/incremental-reader.ts +104 -104
  317. package/src/utils/internal-error.ts +6 -6
  318. package/src/utils/names.ts +27 -27
  319. package/src/utils/paths.ts +102 -63
  320. package/src/utils/redaction.ts +44 -44
  321. package/src/utils/safe-paths.ts +47 -47
  322. package/src/utils/scan-cache.ts +136 -136
  323. package/src/utils/sse-parser.ts +134 -134
  324. package/src/utils/task-name-generator.ts +337 -337
  325. package/src/utils/timings.ts +33 -33
  326. package/src/utils/visual.ts +243 -198
  327. package/src/workflows/discover-workflows.ts +139 -139
  328. package/src/workflows/validate-workflow.ts +40 -40
  329. package/src/workflows/workflow-config.ts +26 -26
  330. package/src/workflows/workflow-serializer.ts +32 -32
  331. package/src/worktree/branch-freshness.ts +45 -45
  332. package/src/worktree/cleanup.ts +75 -75
  333. package/src/worktree/worktree-manager.ts +188 -188
  334. package/teams/default.team.md +12 -12
  335. package/teams/fast-fix.team.md +11 -11
  336. package/teams/implementation.team.md +18 -18
  337. package/teams/parallel-research.team.md +14 -14
  338. package/teams/research.team.md +11 -11
  339. package/teams/review.team.md +12 -12
  340. package/tsconfig.json +19 -19
  341. package/workflows/default.workflow.md +30 -30
  342. package/workflows/fast-fix.workflow.md +23 -23
  343. package/workflows/implementation.workflow.md +43 -43
  344. package/workflows/parallel-research.workflow.md +46 -46
  345. package/workflows/research.workflow.md +22 -22
  346. package/workflows/review.workflow.md +30 -30
  347. package/skills/task-packet/SKILL.md +0 -28
  348. package/skills/verify-evidence/SKILL.md +0 -27
@@ -0,0 +1,258 @@
1
+ ---
2
+ name: widget-rendering
3
+ description: Pi TUI crew widget data sources, display priority, and rendering performance. Use when debugging empty agents, ghost runs, or widget timing issues.
4
+ ---
5
+
6
+ # widget-rendering
7
+
8
+ The crew widget (`src/ui/crew-widget.ts`) displays active runs and their agents in the Pi TUI. It must render synchronously at TTY refresh rate without blocking. Understanding the data sources and timing rules is essential for debugging display issues.
9
+
10
+ ## Three Data Sources
11
+
12
+ The widget has three sources, used in priority order:
13
+
14
+ ### 1. `liveAgents` Map (real-time, highest priority)
15
+
16
+ In-memory map from `live-agent-manager.ts`. Provides:
17
+ - Real-time tool names: `activeTools` Map (toolName → description)
18
+ - Turn count, response text, compaction count
19
+ - Session stats: context %, token usage
20
+ - Status from the handle
21
+
22
+ **When used:** Agents with `liveHandle && liveHandle.status === "running"` get the live activity description (tool labels, response text, turn counter).
23
+
24
+ **When NOT used:** After `evictStaleLiveAgentHandles()` removes a handle, widget falls back to agent records on disk.
25
+
26
+ ### 2. Snapshot cache (500ms TTL)
27
+
28
+ `RunSnapshotCache` from `run-snapshot-cache.ts` caches parsed manifests and agents for 500ms. Reduces disk reads during rapid refresh.
29
+
30
+ **When used:** As the fallback when no live handle exists. Prevents excessive disk reads on every render tick.
31
+
32
+ **Invalidation:** Cache is invalidated when:
33
+ - `invalidate()` is called on a specific run
34
+ - An empty result is returned (forces refresh on next tick)
35
+ - TTL expires (500ms)
36
+
37
+ ### 3. `agents.json` on disk (durables, lowest priority)
38
+
39
+ `readCrewAgents(run)` reads `artifactsRoot/agents.json`. Provides:
40
+ - Final agent status (completed/failed/cancelled)
41
+ - Tool count, token usage from final record
42
+ - Error messages
43
+ - Timestamps (startedAt, completedAt)
44
+
45
+ **When used:** For completed agents, or when snapshot cache misses.
46
+
47
+ ---
48
+
49
+ ## Display Priority
50
+
51
+ ```
52
+ for each active run:
53
+ for each agent in run:
54
+ if liveAgents has this agent (by agentId or taskId):
55
+ → use live activity description (tool labels, response text)
56
+ → use live status (running/queued/waiting)
57
+ → use live session stats (context %, turns, tokens)
58
+ else if snapshot cache has fresh data:
59
+ → use cached agent status
60
+ → use cached tool count, tokens, progress
61
+ else:
62
+ → read agents.json from disk
63
+ → use disk agent status
64
+
65
+ if status is completed/failed/cancelled:
66
+ → apply linger rules (finishedAgents: 1min, errors: 2min)
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Active Runs Filtering
72
+
73
+ `activeWidgetRuns()` determines which runs to show. Key filter: `isDisplayActiveRun(manifest, tasks)` from `process-status.ts`.
74
+
75
+ **Rule: `hasStaleAsyncProcess()`**
76
+
77
+ A run with an async PID is considered stale (hidden) if:
78
+ 1. PID is recorded but process is dead, AND
79
+ 2. The run is more than 30 minutes old (`STALE_ACTIVE_RUN_MS = 30 * 60 * 1000`)
80
+
81
+ **Rule: `isDisplayActiveRun()`**
82
+
83
+ ```typescript
84
+ export function isDisplayActiveRun(manifest: TeamRunManifest, tasks: TeamTaskState[]): boolean {
85
+ if (manifest.status === "running" || manifest.status === "waiting") {
86
+ if (manifest.async?.pid) {
87
+ if (hasStaleAsyncProcess(manifest.async.pid, manifest.updatedAt)) return false;
88
+ }
89
+ const hasActiveTask = tasks.some((t) => t.status === "running" || t.status === "queued" || t.status === "waiting");
90
+ if (!hasActiveTask) return false;
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+ ```
96
+
97
+ This filters out ghost runs (PID dead, manifest still "running") that are more than 30 minutes old.
98
+
99
+ ---
100
+
101
+ ## Stale Handle Eviction
102
+
103
+ **On every widget refresh**, `evictStaleLiveAgentHandles()` is called at the start of `activeWidgetRuns()`:
104
+
105
+ ```typescript
106
+ export function activeWidgetRuns(...): WidgetRun[] {
107
+ evictStaleLiveAgentHandles(); // prevent memory leaks
108
+ const runs = preloadedManifests ?? ...;
109
+ // ...
110
+ }
111
+ ```
112
+
113
+ **Eviction rule:** Remove handles where:
114
+ - Status is terminal (not running/queued/waiting), AND
115
+ - `updatedAt` is more than 10 minutes ago
116
+
117
+ ```typescript
118
+ const STALE_HANDLE_MS = 10 * 60 * 1000;
119
+ if (handle.status !== "running" && handle.status !== "queued" && handle.status !== "waiting") {
120
+ const age = now - new Date(handle.updatedAt).getTime();
121
+ if (age > STALE_HANDLE_MS) {
122
+ liveAgents.delete(agentId);
123
+ safeDisposeLiveSession(handle);
124
+ }
125
+ }
126
+ ```
127
+
128
+ **Why called on every refresh:** Ensures the in-memory Map stays bounded even during long Pi sessions. Completed agents linger for 10 minutes (for visibility), then get evicted.
129
+
130
+ ---
131
+
132
+ ## Frame Timing Rules
133
+
134
+ ### `renderTick()` must be non-blocking
135
+
136
+ Every render cycle (`renderTick` / `requestAnimationFrame`) must complete in <16ms to maintain 60fps. The widget must not:
137
+ - Call `fs.readFileSync` on hot paths
138
+ - Call `loadConfig()` during render
139
+ - Scan directories (`readdirSync`)
140
+ - Make network calls
141
+
142
+ **Solution:** Preload everything async before the first render.
143
+
144
+ ### Widget refresh intervals
145
+
146
+ | Scenario | Interval |
147
+ |---|---|
148
+ | Live agents running | 160ms (`LIVE_REFRESH_MS`) |
149
+ | No live agents, recent activity | 2s |
150
+ | Idle | 10s |
151
+
152
+ ### TTL interactions
153
+
154
+ - Snapshot cache TTL = 500ms
155
+ - Preload interval must be < TTL to avoid render-time gaps
156
+ - If preload interval ≥ TTL, the cache always has fresh data for render
157
+
158
+ ---
159
+
160
+ ## Agent Activity Description
161
+
162
+ `agentActivity(agent, liveHandle?)` generates the activity string shown in the widget:
163
+
164
+ ```typescript
165
+ function agentActivity(agent: CrewAgentRecord, liveHandle?: LiveAgentHandle): string {
166
+ if (liveHandle && liveHandle.status === "running") {
167
+ const live = describeLiveActivity(liveHandle);
168
+ // Prefer richer agent.progress data if live is just the fallback
169
+ if (live === "thinking…" && agent.progress?.currentTool)
170
+ return `${TOOL_LABELS[agent.progress.currentTool] ?? agent.progress.currentTool}…`;
171
+ return live;
172
+ }
173
+ // Fallback chain from agent records
174
+ if (agent.progress?.currentTool) return `${TOOL_LABELS[agent.progress.currentTool]}…`;
175
+ if (recent output) return lastOutput line;
176
+ if (activityState === "needs_attention") return "needs attention";
177
+ if (status === "queued") return "queued";
178
+ if (status === "running") {
179
+ if (age < 5s && no tool) return "spawning…";
180
+ return "thinking…";
181
+ }
182
+ if (status === "failed") return agent.error ?? "failed";
183
+ return "done";
184
+ }
185
+ ```
186
+
187
+ **Tool name extraction:** `TOOL_LABELS` maps tool names to readable labels:
188
+ ```typescript
189
+ const TOOL_LABELS = {
190
+ read: "reading",
191
+ bash: "running command",
192
+ edit: "editing",
193
+ write: "writing",
194
+ grep: "searching",
195
+ find: "finding files",
196
+ ls: "listing",
197
+ };
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Ghost Run Display Bug Patterns
203
+
204
+ ### Bug: Agent shows "running" in widget but process is dead
205
+
206
+ **Root cause:** `agents.json` still has status "running" while the actual PID is dead.
207
+
208
+ **Fix path:** `reconcileAllStaleRuns` now calls `upsertCrewAgent` when repairing tasks, syncing agent status files. Also `purgeStaleActiveRunIndex` and `cancelOrphanedRuns` now sync agent records.
209
+
210
+ ### Bug: Widget shows empty agent (no name, no status)
211
+
212
+ **Root cause:** `agentActivity` fallback chain returned `"done"` with no name. The agent description construction had fallbacks to empty strings.
213
+
214
+ **Fix applied:** `agentActivity` now uses `handle.agent ?? handle.role ?? agent.agent ?? agent.role ?? "Agent"` as the description base. Plus `(running)` suffix uses `handle.status` not `agent.status`.
215
+
216
+ ### Bug: Ghost runs appear after Pi restart
217
+
218
+ **Root cause:** `active-run-index.json` is missing on restart, so `purgeStaleActiveRunIndex()` doesn't know about orphaned runs. `reconcileAllStaleRuns` (disk scan) was never called.
219
+
220
+ **Fix applied:** `reconcileAllStaleRuns` is now called at session start in `register.ts`.
221
+
222
+ ### Bug: "worker blinks" — agent appears and disappears in 1 frame
223
+
224
+ **Root cause:** Worker spawns and crashes in <1 frame. Structured logs (`worker.spawned` + rapid `worker.exit`) expose the crash cause.
225
+
226
+ **Fix:** Event log tracing skill documents this pattern.
227
+
228
+ ---
229
+
230
+ ## Anti-patterns
231
+
232
+ - **Blocking render with fs calls**: Every `readFileSync`, `readdirSync`, `fs.statSync` in the render path causes frame drops. Preload everything async.
233
+ - **Stale cache in hot path**: If snapshot cache TTL is too long, widget shows outdated state. Keep TTL at 500ms or less.
234
+ - **No invalidation on empty**: When `readCrewAgents` returns `[]` (no agents yet), the cache must be invalidated on next tick to prevent showing empty for too long.
235
+ - **Expired handles accumulating**: Without `evictStaleLiveAgentHandles`, the Map grows indefinitely. Call it on every refresh.
236
+ - **Widget showing stale health warnings**: Completed/cancelled/failed runs should not show health warnings. Filter by status.
237
+
238
+ ---
239
+
240
+ ## Source patterns
241
+
242
+ - `src/ui/crew-widget.ts` — render, refresh, activeWidgetRuns, evictStaleLiveAgentHandles, agentActivity, describeLiveActivity
243
+ - `src/ui/run-snapshot-cache.ts` — SnapshotCache, get, refreshIfStale, TTL=500ms
244
+ - `src/runtime/crew-agent-records.ts` — readCrewAgents, agents.json
245
+ - `src/runtime/process-status.ts` — hasStaleAsyncProcess, isDisplayActiveRun
246
+ - `src/runtime/background-runner.ts` — active run filtering with async PID check
247
+ - `src/runtime/active-run-registry.ts` — purgeStaleActiveRunIndex
248
+
249
+ ---
250
+
251
+ ## Verification
252
+
253
+ ```bash
254
+ cd pi-crew
255
+ npx tsc --noEmit
256
+ node --experimental-strip-types --test test/unit/crew-widget.test.ts test/unit/run-snapshot-cache.test.ts test/unit/live-agent-manager.test.ts
257
+ npm test
258
+ ```
@@ -0,0 +1,202 @@
1
+ ---
2
+ name: workspace-isolation
3
+ description: Workspace isolation boundaries in pi-crew. Use when ensuring agents from workspace A cannot access workspace B, or when implementing worktree-based parallel execution.
4
+ ---
5
+
6
+ # workspace-isolation
7
+
8
+ pi-crew enforces workspace isolation so that agents, runs, and live sessions from one project folder cannot be accessed from another. The workspace boundary is `manifest.cwd` — the directory where a run was initiated.
9
+
10
+ ## Workspace Boundary Definition
11
+
12
+ **`manifest.cwd`** is the canonical workspace root. Every run record carries the directory where it was created.
13
+
14
+ **Why it matters:** Pi can have multiple workspace folders open simultaneously. Without isolation, an agent from workspace A could be steered/controlled from workspace B.
15
+
16
+ **Rules:**
17
+ - Every run's `manifest.cwd` is set at creation time
18
+ - Every live agent handle carries `workspaceId = manifest.cwd`
19
+ - Widget queries filter by `manifest.cwd`
20
+ - API operations reject cross-workspace access
21
+
22
+ ## Live Agent Workspace Check
23
+
24
+ `LiveAgentHandle.workspaceId` field (added to prevent cross-workspace access):
25
+
26
+ ```typescript
27
+ interface LiveAgentHandle {
28
+ // ... other fields
29
+ /** Workspace where this agent was spawned — used for session-scoped visibility. */
30
+ workspaceId: string;
31
+ }
32
+ ```
33
+
34
+ **Enforcement in `api.ts`** (team-tool operations):
35
+
36
+ ```typescript
37
+ // list-active-live-agents: filter by workspace
38
+ listActiveLiveAgentsByWorkspace(manifest.cwd);
39
+
40
+ // steer-agent, follow-up-agent, stop-agent, resume-agent:
41
+ const live = getLiveAgent(agentId);
42
+ if (live && live.workspaceId !== manifest.cwd)
43
+ return result(`Live agent '${agentId}' does not belong to workspace ${manifest.cwd}.`, { status: "error" }, true);
44
+ ```
45
+
46
+ **Enforcement in `live-agent-manager.ts`**:
47
+
48
+ ```typescript
49
+ // listLiveAgentsByWorkspace(workspaceId): filter by workspaceId
50
+ export function listLiveAgentsByWorkspace(workspaceId: string): LiveAgentHandle[] {
51
+ return listLiveAgents().filter((a) => a.workspaceId === workspaceId);
52
+ }
53
+ ```
54
+
55
+ ## Team Workspace Modes
56
+
57
+ ### `single` (default)
58
+
59
+ - All agents run in the project root (`manifest.cwd`)
60
+ - No worktree creation
61
+ - Simpler, but all workers share the same git state
62
+
63
+ ### `worktree` (parallel isolation)
64
+
65
+ - Each task (or phase) gets its own git worktree
66
+ - Worktree path: `<repo-root>/.worktrees/<runId>/<taskId>/`
67
+ - Branch name: `crew/<runId>-<taskId>` (sanitized)
68
+ - Allows parallel code-changing tasks without git conflicts
69
+
70
+ **Entry point in `team-runner.ts`:**
71
+ ```typescript
72
+ const worktree = workspaceMode === "worktree" && task.worktree !== undefined
73
+ ? { path: task.worktree.path, branch: task.worktree.branch, reused: task.worktree.reused }
74
+ : undefined;
75
+ ```
76
+
77
+ **Worktree lifecycle:**
78
+
79
+ 1. **Creation** (`prepareTaskWorkspace` in `worktree-manager.ts`):
80
+ - Check leader repo is clean (`assertCleanLeader`)
81
+ - `git worktree add <path> <branch>`
82
+ - Link `node_modules` if present
83
+ - Mark reused if already exists
84
+
85
+ 2. **Naming convention**:
86
+ ```
87
+ Branch: crew/<sanitized-runId>-<sanitized-taskId>
88
+ Path: .worktrees/<runId>/<taskId>/
89
+ ```
90
+
91
+ 3. **Cleanup** (on task/run completion):
92
+ - Check dirty state
93
+ - `git worktree remove <path> --force` (only if force=true)
94
+ - Preserve dirty worktrees unless explicitly forced
95
+
96
+ **Safety rules:**
97
+ - Leader repo must be clean before creating worktrees
98
+ - One owner per file/symbol/migration path
99
+ - Branch names derived deterministically from run/task IDs (no user-controlled path fragments)
100
+
101
+ ## Cross-Workspace Prevention
102
+
103
+ **In api.ts (`handleTeamToolCall`):**
104
+
105
+ ```typescript
106
+ if (operation === "list-live-agents") {
107
+ return result(JSON.stringify(
108
+ listActiveLiveAgentsByWorkspace(loaded.manifest.cwd), // ← filtered by workspace
109
+ null, 2
110
+ ), { action: "api", status: "ok", runId: loaded.manifest.runId });
111
+ }
112
+ ```
113
+
114
+ **In cancel.ts:**
115
+ - Verifies run ownership before allowing cancel
116
+ - Cross-session cancel rejected unless force=true
117
+
118
+ **In respond.ts:**
119
+ - Verifies task ownership before responding
120
+ - Cross-session respond rejected unless force=true
121
+
122
+ **In crash-recovery.ts:**
123
+ - `purgeStaleActiveRunIndex` only affects runs in the current workspace (cwd)
124
+ - `reconcileAllStaleRuns` only scans the current workspace's `.crew/state/runs/`
125
+
126
+ ## Live Session Workspace
127
+
128
+ `LiveSessionConfig` carries workspaceId:
129
+
130
+ ```typescript
131
+ interface LiveSessionConfig {
132
+ // ... other fields
133
+ /** Workspace directory — used for path containment and isolation. */
134
+ workspaceId: string;
135
+ }
136
+ ```
137
+
138
+ **Propagation chain:**
139
+ ```
140
+ team-tool.ts (handleTeamToolCall)
141
+ → TeamContext { workspaceId: cwd }
142
+ → LiveSessionConfig { workspaceId }
143
+ → registerLiveAgent({ workspaceId })
144
+ → LiveAgentHandle { workspaceId }
145
+ ```
146
+
147
+ ## Configuration
148
+
149
+ **defaults.ts isolation settings:**
150
+
151
+ ```typescript
152
+ const DEFAULT_PATHS = {
153
+ crewRoot: ".crew", // under project root
154
+ stateRoot: ".crew/state", // under project root
155
+ };
156
+ ```
157
+
158
+ All paths are resolved relative to `manifest.cwd`, ensuring state stays under the project root.
159
+
160
+ ## Anti-patterns
161
+
162
+ - **Passing raw cwd without validation**: Always use `resolveContainedPath` to ensure paths stay under workspace root.
163
+ - **Cross-workspace respond/cancel**: Even with force=true, foreign session operations should be rejected. Check `ownerSessionId`.
164
+ - **Symlink traversal**: Use `resolveRealContainedPath` to resolve symlinks and detect escape attempts.
165
+ - **Worktree name collision**: Use deterministic names from run/task IDs. Never accept user-controlled branch names.
166
+ - **Dirty worktree removal**: Never force-remove worktrees with uncommitted changes unless explicitly confirmed.
167
+
168
+ ---
169
+
170
+ ## Source patterns
171
+
172
+ - `src/extension/team-tool/api.ts` — workspaceId filter in list-live-agents, steer-agent, follow-up-agent, stop-agent, resume-agent
173
+ - `src/runtime/live-agent-manager.ts` — workspaceId in LiveAgentHandle, listLiveAgentsByWorkspace, listActiveLiveAgentsByWorkspace
174
+ - `src/runtime/live-session-runtime.ts` — LiveSessionConfig, workspaceId in session creation
175
+ - `src/runtime/team-runner.ts` — workspaceId passed through executeTeamRun
176
+ - `src/state/state-store.ts` — initRunManifest with cwd, manifest.cwd
177
+ - `src/worktree/worktree-manager.ts` — prepareTaskWorkspace, assertCleanLeader, linkNodeModulesIfPresent
178
+ - `src/config/defaults.ts` — DEFAULT_PATHS (state under project root)
179
+
180
+ ---
181
+
182
+ ## Verification
183
+
184
+ ```bash
185
+ cd pi-crew
186
+ # Verify workspace filter in list-live-agents
187
+ node --experimental-strip-types -e "
188
+ import { listLiveAgentsByWorkspace, listActiveLiveAgentsByWorkspace } from './src/runtime/live-agent-manager.ts';
189
+ console.log('By workspace:', listLiveAgentsByWorkspace(process.cwd()).length);
190
+ console.log('Active by workspace:', listActiveLiveAgentsByWorkspace(process.cwd()).length);
191
+ "
192
+
193
+ # Verify worktree creation
194
+ node --experimental-strip-types -e "
195
+ import { prepareTaskWorkspace } from './src/worktree/worktree-manager.ts';
196
+ // Requires a clean git repo and workspaceMode='worktree'
197
+ "
198
+
199
+ npx tsc --noEmit
200
+ node --experimental-strip-types --test test/unit/worktree-manager.test.ts test/unit/isolation-policy.test.ts
201
+ npm test
202
+ ```
@@ -5,35 +5,219 @@ description: Conflict-safe git worktree workflow. Use when running parallel impl
5
5
 
6
6
  # worktree-isolation
7
7
 
8
- Use this skill for worktree-based execution or cleanup.
8
+ Use this skill for worktree-based execution or cleanup. Git worktrees create isolated working directories that allow parallel code-changing tasks without git conflicts.
9
9
 
10
- ## Source patterns distilled
10
+ ## How Worktrees Work
11
11
 
12
- - pi-subagents worktree runner and cleanup patterns
13
- - pi-crew worktrees: `src/worktree/worktree-manager.ts`, `src/worktree/cleanup.ts`, `src/worktree/branch-freshness.ts`
14
- - Team runner workspace mode: `src/runtime/team-runner.ts`, workflow/team resource fields
12
+ A git worktree is a separate working directory linked to the same repository. It has its own:
13
+ - Working directory (different path)
14
+ - HEAD (can be on a different branch)
15
+ - Staged/unstaged changes
15
16
 
16
- ## Rules
17
+ But it shares:
18
+ - Object database (`.git/objects`)
19
+ - Refs (branches, tags)
17
20
 
18
- - Use worktree mode for parallel or risky code-changing tasks when the repository is clean enough and merge ownership is clear.
19
- - Assign one owner per file/symbol/migration path to avoid conflict-heavy merges.
20
- - Name branches/worktrees deterministically from run/task IDs; avoid user-controlled path fragments without sanitization.
21
- - Before cleanup, check dirty state. Preserve dirty worktrees unless `force` is explicitly set.
22
- - Record worktree paths and branch metadata in artifacts/events so the operator can inspect or recover.
23
- - Do not run destructive git operations without explicit confirmation and evidence of target path containment.
21
+ This means creating a worktree is cheap (no clone needed) and fast.
22
+
23
+ ## When to Use Worktrees
24
+
25
+ **Use worktree mode when:**
26
+ - Running parallel implementation workers that modify the same repo
27
+ - Isolating risky changes that might need to be discarded
28
+ - Running multiple agents on the same codebase simultaneously
29
+ - Running a long task that would block other work
30
+
31
+ **Don't use worktree mode when:**
32
+ - The task is read-only (use scaffold mode instead)
33
+ - Only one agent needs to work at a time
34
+ - The repository has uncommitted changes (must be clean)
35
+
36
+ ## Worktree Lifecycle
37
+
38
+ ### 1. Creation
39
+
40
+ **Prerequisites:**
41
+ - Leader repository must be clean (`git status` empty)
42
+ - Sufficient disk space for worktree directory
43
+
44
+ **Creation flow:**
45
+ ```
46
+ team-runner.ts (workspaceMode: "worktree")
47
+ → prepareTaskWorkspace(manifest, task)
48
+ → assertCleanLeader(repoRoot)
49
+ → git worktree add <path> <branch>
50
+ → linkNodeModulesIfPresent(repoRoot, worktreePath)
51
+ → return { cwd: worktreePath, worktreePath, branch }
52
+ ```
53
+
54
+ **Naming convention:**
55
+ - Branch: `crew/<sanitized-runId>-<sanitized-taskId>`
56
+ - Path: `.worktrees/<runId>/<taskId>/`
57
+ - Deterministic from run/task IDs — no user-controlled fragments
58
+
59
+ **Example:**
60
+ ```
61
+ Run: team_20260514092752_218fe358085d7115
62
+ Task: 01_explore
63
+
64
+ Branch: crew/team-20260514092752-218fe358085d7115/01-explore
65
+ Path: .worktrees/team-20260514092752-218fe358085d7115/01-explore/
66
+ ```
67
+
68
+ ### 2. Reuse
69
+
70
+ If a worktree with the same branch already exists, it is reused instead of recreated:
71
+
72
+ ```typescript
73
+ // Check if worktree already exists
74
+ const existing = git(cwd, ["worktree", "list", "--porcelain"]);
75
+ if (existing.includes(branch)) {
76
+ return { reused: true, worktreePath: parsePath(existing) };
77
+ }
78
+ ```
79
+
80
+ Reuse is safe when the worktree's base branch hasn't diverged (checked via `branch-freshness.ts`).
81
+
82
+ ### 3. Work in worktree
83
+
84
+ Each task works in its own worktree directory:
85
+ - `cwd` = worktree path
86
+ - `git status` shows only that task's changes
87
+ - Changes are isolated from other worktrees and the leader
88
+
89
+ ### 4. Cleanup
90
+
91
+ **On task completion:**
92
+ 1. Check dirty state (uncommitted changes)
93
+ 2. If dirty and not forced → preserve (report to operator)
94
+ 3. If clean → `git worktree remove <path>`
95
+
96
+ **On force cleanup:**
97
+ - `git worktree remove <path> --force`
98
+ - Removes even if there are changes (but logs a warning)
99
+
100
+ **Safety rules:**
101
+ - Never force-remove dirty worktrees by default
102
+ - Always check `git status` before cleanup
103
+ - Report worktree paths in events/artifacts for recovery
104
+
105
+ ## Stale Worktree Detection
106
+
107
+ Worktrees can become stale when:
108
+ - The base branch has moved
109
+ - The run was abandoned mid-task
110
+ - Node modules are out of date
111
+
112
+ **Detection approach:**
113
+ ```typescript
114
+ // Check if base branch has diverged
115
+ function isStaleWorktree(worktreePath: string, baseBranch: string): boolean {
116
+ const current = git(worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]);
117
+ const ahead = git(worktreePath, ["rev-list", "--count", `${baseBranch}..HEAD`]);
118
+ const behind = git(worktreePath, ["rev-list", "--count", `HEAD..${baseBranch}`]);
119
+ return Number(ahead) > 10 || Number(behind) > 0;
120
+ }
121
+ ```
122
+
123
+ **Cleanup stale worktrees:**
124
+ ```bash
125
+ # List all worktrees
126
+ git worktree list
127
+
128
+ # Remove stale worktree
129
+ git worktree remove .worktrees/stale-task --force
130
+ ```
131
+
132
+ ## Merge Conflict Strategy
133
+
134
+ When worktrees complete and changes need to be merged back:
135
+
136
+ 1. **One owner per file/symbol**: Assign each file to exactly one worktree. No two worktrees modify the same file.
137
+ 2. **Merge order**: If multiple worktrees produce changes, merge in reverse creation order.
138
+ 3. **Conflict detection**: `git status --porcelain` shows conflicts.
139
+ 4. **Conflict resolution**: Resolve in the leader branch, then continue.
140
+
141
+ **If conflicts occur:**
142
+ ```bash
143
+ git merge --no-commit <branch>
144
+ # Resolve conflicts manually
145
+ git add <resolved-files>
146
+ git commit -m "Merge branch and resolve conflicts"
147
+ ```
148
+
149
+ ## Branch Freshness Check
150
+
151
+ Before reusing a worktree, verify the base branch hasn't diverged:
152
+
153
+ ```typescript
154
+ function checkBranchFreshness(worktreePath: string, baseBranch: string): {
155
+ fresh: boolean;
156
+ ahead: number;
157
+ behind: number;
158
+ } {
159
+ const status = git(worktreePath, ["status", "--porcelain"]);
160
+ if (status.trim()) return { fresh: false, ahead: 0, behind: 0 }; // dirty
161
+
162
+ const current = git(worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]).trim();
163
+ if (current !== baseBranch) return { fresh: false, ahead: 0, behind: 0 }; // different branch
164
+
165
+ // Check divergence
166
+ const ahead = Number(git(worktreePath, ["rev-list", "--count", `${baseBranch}..HEAD`]));
167
+ const behind = Number(git(worktreePath, ["rev-list", "--count", `HEAD..${baseBranch}`]));
168
+ return { fresh: ahead === 0 && behind === 0, ahead, behind };
169
+ }
170
+ ```
171
+
172
+ ## Crash Recovery
173
+
174
+ If a task crashes mid-worktree:
175
+ 1. Find orphaned worktrees: `git worktree list`
176
+ 2. Check for abandoned runs in `.crew/state/runs/`
177
+ 3. If run is failed/cancelled and worktree is dirty → report to operator
178
+ 4. If run is completed → safe to clean up
24
179
 
25
180
  ## Anti-patterns
26
181
 
27
- - Parallel editing the same file in multiple worktrees without a merge plan.
28
- - Force-removing dirty worktrees by default.
29
- - Reusing stale worktrees after the base branch has moved without freshness checks.
30
- - Storing worktrees outside the intended contained workspace root.
182
+ - **Parallel editing same file**: Assign one owner per file. Use the task ID in branch names to track ownership.
183
+ - **Force-removing dirty worktrees**: Always report dirty state to operator before cleanup.
184
+ - **Reusing stale worktrees**: Check `branch-freshness.ts` before reuse. If base branch moved, recreate instead.
185
+ - **Storing worktrees outside workspace root**: All worktrees must be under `<repo-root>/.worktrees/`. Never store outside.
186
+ - **Worktree name collision**: Use deterministic naming from run/task IDs, not user input.
187
+
188
+ ---
189
+
190
+ ## Source patterns
191
+
192
+ - `src/worktree/worktree-manager.ts` — prepareTaskWorkspace, assertCleanLeader, linkNodeModulesIfPresent, sanitizeBranchPart
193
+ - `src/worktree/cleanup.ts` — worktree cleanup logic, dirty state detection
194
+ - `src/worktree/branch-freshness.ts` — branch divergence detection
195
+ - `src/runtime/team-runner.ts` — workspaceMode handling, worktree passed to task
196
+ - `src/runtime/task-runner.ts` — worktreePath in task context
197
+
198
+ ---
31
199
 
32
200
  ## Verification
33
201
 
34
202
  ```bash
35
203
  cd pi-crew
204
+
205
+ # List all worktrees
206
+ git worktree list
207
+
208
+ # Check leader repo is clean
209
+ git status --short
210
+
211
+ # Verify worktree creation
212
+ node --experimental-strip-types -e "
213
+ import { prepareTaskWorkspace } from './src/worktree/worktree-manager.ts';
214
+ // Requires clean repo and workspaceMode='worktree'
215
+ "
216
+
217
+ # TypeScript
36
218
  npx tsc --noEmit
37
- node --experimental-strip-types --test test/integration/worktree-mode.test.ts test/unit/run-index.test.ts
219
+
220
+ # Tests
221
+ node --experimental-strip-types --test test/unit/worktree-manager.test.ts test/integration/worktree-mode.test.ts
38
222
  npm test
39
- ```
223
+ ```