pi-crew 0.9.16 → 0.9.18

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 (435) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +19 -0
  3. package/dist/build-meta.json +21784 -0
  4. package/dist/index.mjs +83169 -0
  5. package/dist/index.mjs.map +7 -0
  6. package/docs/A +358 -0
  7. package/docs/M-A +357 -0
  8. package/docs/REVIEW-FINDINGS-2026-06 +357 -0
  9. package/docs/Y +357 -0
  10. package/docs/a +357 -0
  11. package/docs/aA +358 -0
  12. package/docs/archive/README.md +91 -0
  13. package/docs/migration/atomic-write-v2-migration.md +297 -0
  14. package/docs/patterns/command-agent-skill.md +1 -1
  15. package/docs/perf/performance-review-2026-07.md +287 -0
  16. package/docs/skills/REFERENCE.md +0 -17
  17. package/docs//303/242mmaAAA/303/242 +357 -0
  18. package/index.bundle.ts +25 -0
  19. package/index.ts +95 -4
  20. package/package.json +15 -4
  21. package/skills/iterative-audit/SKILL.md +0 -1
  22. package/skills/widget-rendering/SKILL.md +17 -0
  23. package/src/adapters/claude-adapter.ts +1 -3
  24. package/src/adapters/export-util.ts +16 -16
  25. package/src/adapters/index.ts +5 -5
  26. package/src/agents/agent-config.ts +20 -14
  27. package/src/agents/agent-serializer.ts +1 -1
  28. package/src/agents/discover-agents.ts +83 -39
  29. package/src/benchmark/benchmark-runner.ts +51 -51
  30. package/src/benchmark/feedback-loop.ts +1 -1
  31. package/src/config/config.ts +303 -591
  32. package/src/config/defaults.ts +28 -2
  33. package/src/config/drift-detector.ts +11 -14
  34. package/src/config/markers.ts +201 -203
  35. package/src/config/resilient-parser.ts +14 -6
  36. package/src/config/role-tools.ts +3 -3
  37. package/src/config/suggestions.ts +5 -12
  38. package/src/config/types.ts +9 -25
  39. package/src/errors.ts +151 -157
  40. package/src/extension/action-suggestions.ts +54 -9
  41. package/src/extension/async-notifier.ts +43 -13
  42. package/src/extension/autonomous-policy.ts +77 -37
  43. package/src/extension/command-completions.ts +9 -8
  44. package/src/extension/context-status-injection.ts +14 -12
  45. package/src/extension/crew-autocomplete.ts +8 -16
  46. package/src/extension/crew-cleanup.ts +2 -1
  47. package/src/extension/crew-shortcuts.ts +9 -6
  48. package/src/extension/cross-extension-rpc.ts +136 -42
  49. package/src/extension/help.ts +1 -1
  50. package/src/extension/import-index.ts +10 -5
  51. package/src/extension/knowledge-injection.ts +88 -15
  52. package/src/extension/management.ts +89 -349
  53. package/src/extension/message-renderers.ts +3 -4
  54. package/src/extension/notification-router.ts +22 -5
  55. package/src/extension/notification-sink.ts +6 -3
  56. package/src/extension/pi-api.ts +17 -8
  57. package/src/extension/plan-orchestrate.ts +8 -27
  58. package/src/extension/project-init.ts +21 -6
  59. package/src/extension/register.ts +329 -975
  60. package/src/extension/registration/artifact-cleanup.ts +9 -4
  61. package/src/extension/registration/command-utils.ts +5 -1
  62. package/src/extension/registration/commands.ts +806 -314
  63. package/src/extension/registration/compaction-guard.ts +15 -15
  64. package/src/extension/registration/lifecycle.ts +259 -0
  65. package/src/extension/registration/observability.ts +293 -0
  66. package/src/extension/registration/subagent-helpers.ts +29 -7
  67. package/src/extension/registration/subagent-tools.ts +253 -44
  68. package/src/extension/registration/team-tool.ts +19 -84
  69. package/src/extension/registration/ui.ts +168 -0
  70. package/src/extension/registration/viewers.ts +64 -24
  71. package/src/extension/result-watcher.ts +18 -7
  72. package/src/extension/run-bundle-schema.ts +21 -6
  73. package/src/extension/run-export.ts +15 -6
  74. package/src/extension/run-import.ts +64 -29
  75. package/src/extension/run-index.ts +28 -9
  76. package/src/extension/run-maintenance.ts +32 -12
  77. package/src/extension/session-summary.ts +1 -1
  78. package/src/extension/team-manager-command.ts +64 -7
  79. package/src/extension/team-onboard.ts +149 -150
  80. package/src/extension/team-recommendation.ts +121 -21
  81. package/src/extension/team-tool/anchor.ts +31 -64
  82. package/src/extension/team-tool/api.ts +929 -141
  83. package/src/extension/team-tool/auto-summarize.ts +14 -26
  84. package/src/extension/team-tool/cache-control.ts +1 -5
  85. package/src/extension/team-tool/cancel.ts +138 -38
  86. package/src/extension/team-tool/chain-dispatch.ts +5 -13
  87. package/src/extension/team-tool/chain-executor.ts +29 -51
  88. package/src/extension/team-tool/config-patch.ts +1 -1
  89. package/src/extension/team-tool/context.ts +40 -20
  90. package/src/extension/team-tool/destructive-gate.ts +10 -5
  91. package/src/extension/team-tool/doctor.ts +170 -51
  92. package/src/extension/team-tool/explain.ts +228 -214
  93. package/src/extension/team-tool/failure-patterns.ts +3 -9
  94. package/src/extension/team-tool/goal-wrap.ts +71 -29
  95. package/src/extension/team-tool/goal.ts +185 -35
  96. package/src/extension/team-tool/handle-schedule.ts +34 -15
  97. package/src/extension/team-tool/handle-settings.ts +94 -56
  98. package/src/extension/team-tool/health-monitor.ts +27 -108
  99. package/src/extension/team-tool/inspect.ts +42 -9
  100. package/src/extension/team-tool/intent-policy.ts +4 -1
  101. package/src/extension/team-tool/lifecycle-actions.ts +244 -48
  102. package/src/extension/team-tool/orchestrate.ts +7 -18
  103. package/src/extension/team-tool/parallel-dispatch.ts +51 -29
  104. package/src/extension/team-tool/plan.ts +29 -5
  105. package/src/extension/team-tool/respond.ts +49 -16
  106. package/src/extension/team-tool/run-not-found.ts +3 -8
  107. package/src/extension/team-tool/run.ts +80 -317
  108. package/src/extension/team-tool/status.ts +135 -43
  109. package/src/extension/team-tool/workflow-manage.ts +92 -25
  110. package/src/extension/team-tool-types.ts +8 -1
  111. package/src/extension/team-tool.ts +142 -551
  112. package/src/extension/validate-resources.ts +39 -8
  113. package/src/hooks/registry.ts +66 -17
  114. package/src/hooks/types.ts +1 -1
  115. package/src/i18n.ts +25 -11
  116. package/src/observability/correlation.ts +17 -3
  117. package/src/observability/event-bus.ts +51 -56
  118. package/src/observability/event-to-metric.ts +117 -15
  119. package/src/observability/exporters/otlp-exporter.ts +56 -17
  120. package/src/observability/exporters/prometheus-exporter.ts +1 -1
  121. package/src/observability/metric-registry.ts +16 -4
  122. package/src/observability/metric-retention.ts +8 -2
  123. package/src/observability/metric-sink.ts +11 -3
  124. package/src/observability/metrics-primitives.ts +42 -9
  125. package/src/plugins/plugin-define.ts +2 -2
  126. package/src/plugins/plugin-registry.ts +25 -25
  127. package/src/plugins/plugins/index.ts +1 -1
  128. package/src/plugins/plugins/nextjs.ts +16 -16
  129. package/src/plugins/plugins/vite.ts +7 -11
  130. package/src/plugins/plugins/vitest.ts +6 -11
  131. package/src/runtime/adaptive-plan.ts +225 -40
  132. package/src/runtime/agent-control.ts +58 -15
  133. package/src/runtime/agent-memory.ts +15 -9
  134. package/src/runtime/agent-observability.ts +10 -2
  135. package/src/runtime/anchor-manager.ts +27 -25
  136. package/src/runtime/async-marker.ts +7 -1
  137. package/src/runtime/async-runner.ts +39 -24
  138. package/src/runtime/auto-summarize.ts +7 -13
  139. package/src/runtime/background-runner.ts +189 -251
  140. package/src/runtime/batch-barrier.ts +18 -16
  141. package/src/runtime/cancellation-token.ts +16 -6
  142. package/src/runtime/cancellation.ts +46 -8
  143. package/src/runtime/capability-inventory.ts +6 -4
  144. package/src/runtime/chain-parser.ts +44 -11
  145. package/src/runtime/chain-runner.ts +63 -69
  146. package/src/runtime/checkpoint.ts +12 -56
  147. package/src/runtime/child-pi.ts +344 -79
  148. package/src/runtime/coalesce-tasks.ts +268 -0
  149. package/src/runtime/code-summary.ts +71 -26
  150. package/src/runtime/compact-stages/index.ts +15 -5
  151. package/src/runtime/compact-stages/tail-capture-stage.ts +7 -2
  152. package/src/runtime/compact-stages/truncation-stage.ts +4 -1
  153. package/src/runtime/compaction-summary.ts +17 -10
  154. package/src/runtime/completion-guard.ts +33 -16
  155. package/src/runtime/crash-classification.ts +28 -7
  156. package/src/runtime/crash-recovery.ts +177 -37
  157. package/src/runtime/crew-agent-records.ts +145 -47
  158. package/src/runtime/crew-hooks.ts +9 -24
  159. package/src/runtime/cross-extension-rpc.ts +25 -23
  160. package/src/runtime/custom-tools/irc-tool.ts +43 -15
  161. package/src/runtime/custom-tools/submit-result-tool.ts +16 -5
  162. package/src/runtime/delivery-coordinator.ts +34 -7
  163. package/src/runtime/delta-conflict.ts +9 -21
  164. package/src/runtime/deterministic-ast.ts +1 -1
  165. package/src/runtime/diagnostic-export.ts +53 -20
  166. package/src/runtime/direct-run.ts +12 -2
  167. package/src/runtime/dwf-state-store.ts +1 -1
  168. package/src/runtime/dynamic-workflow-context.ts +187 -59
  169. package/src/runtime/dynamic-workflow-runner.ts +42 -20
  170. package/src/runtime/effectiveness.ts +16 -8
  171. package/src/runtime/errors/crew-errors.ts +7 -11
  172. package/src/runtime/event-stream-bridge.ts +9 -3
  173. package/src/runtime/foreground-control.ts +54 -14
  174. package/src/runtime/foreground-watchdog.ts +9 -6
  175. package/src/runtime/goal-achievement.ts +27 -10
  176. package/src/runtime/goal-evaluator.ts +54 -19
  177. package/src/runtime/goal-loop-runner.ts +280 -99
  178. package/src/runtime/goal-state-store.ts +27 -17
  179. package/src/runtime/green-contract.ts +11 -2
  180. package/src/runtime/group-join.ts +64 -31
  181. package/src/runtime/handoff-manager.ts +37 -42
  182. package/src/runtime/heartbeat-gradient.ts +10 -2
  183. package/src/runtime/heartbeat-watcher.ts +32 -6
  184. package/src/runtime/hidden-handoff.ts +13 -31
  185. package/src/runtime/important-line-classifier.ts +12 -2
  186. package/src/runtime/iteration-hooks.ts +25 -15
  187. package/src/runtime/live-agent-control.ts +55 -7
  188. package/src/runtime/live-agent-manager.ts +130 -27
  189. package/src/runtime/live-control-realtime.ts +23 -3
  190. package/src/runtime/live-irc.ts +4 -1
  191. package/src/runtime/live-session-health.ts +12 -4
  192. package/src/runtime/live-session-runtime.ts +379 -117
  193. package/src/runtime/manifest-cache.ts +28 -12
  194. package/src/runtime/mcp-proxy.ts +10 -18
  195. package/src/runtime/metric-parser.ts +1 -5
  196. package/src/runtime/model-fallback.ts +55 -18
  197. package/src/runtime/model-resolver.ts +2 -6
  198. package/src/runtime/model-scope.ts +19 -3
  199. package/src/runtime/notebook-helpers.ts +60 -62
  200. package/src/runtime/orphan-worker-registry.ts +37 -26
  201. package/src/runtime/output-validator.ts +18 -3
  202. package/src/runtime/overflow-recovery.ts +5 -4
  203. package/src/runtime/parallel-research.ts +29 -5
  204. package/src/runtime/parallel-utils.ts +9 -11
  205. package/src/runtime/path-overlap.ts +150 -0
  206. package/src/runtime/peer-dep.ts +12 -17
  207. package/src/runtime/per-write-validator.ts +1 -3
  208. package/src/runtime/phase-tracker.ts +342 -330
  209. package/src/runtime/pi-args.ts +26 -8
  210. package/src/runtime/pi-json-output.ts +1 -1
  211. package/src/runtime/pi-spawn.ts +30 -19
  212. package/src/runtime/pipeline-runner.ts +56 -55
  213. package/src/runtime/plan-templates.ts +5 -6
  214. package/src/runtime/policy-engine.ts +43 -7
  215. package/src/runtime/post-checks.ts +12 -4
  216. package/src/runtime/post-exit-stdio-guard.ts +2 -2
  217. package/src/runtime/process-lifecycle.ts +20 -10
  218. package/src/runtime/process-status.ts +16 -5
  219. package/src/runtime/progress-event-coalescer.ts +2 -1
  220. package/src/runtime/progress-tracker.ts +103 -103
  221. package/src/runtime/prose-compressor.ts +9 -11
  222. package/src/runtime/recovery-recipes.ts +118 -24
  223. package/src/runtime/replace.ts +25 -10
  224. package/src/runtime/resilient-edit.ts +10 -25
  225. package/src/runtime/result-extractor.ts +2 -6
  226. package/src/runtime/retry-executor.ts +20 -4
  227. package/src/runtime/retry-runner.ts +27 -51
  228. package/src/runtime/role-permission.ts +6 -1
  229. package/src/runtime/run-coalesced-task-group.ts +256 -0
  230. package/src/runtime/run-drift.ts +14 -15
  231. package/src/runtime/run-tracker.ts +6 -28
  232. package/src/runtime/runtime-policy.ts +1 -6
  233. package/src/runtime/runtime-resolver.ts +80 -15
  234. package/src/runtime/scheduler.ts +47 -18
  235. package/src/runtime/semaphore.ts +5 -7
  236. package/src/runtime/sensitive-paths.ts +2 -1
  237. package/src/runtime/session-usage.ts +1 -1
  238. package/src/runtime/settings-store.ts +16 -12
  239. package/src/runtime/sidechain-output.ts +6 -2
  240. package/src/runtime/single-agent-compose.ts +1 -4
  241. package/src/runtime/skill-effectiveness.ts +29 -97
  242. package/src/runtime/skill-instructions.ts +40 -83
  243. package/src/runtime/stale-reconciler.ts +73 -197
  244. package/src/runtime/stream-preview.ts +1 -1
  245. package/src/runtime/streaming-output.ts +1 -1
  246. package/src/runtime/subagent-manager.ts +44 -169
  247. package/src/runtime/subprocess-tool-registry.ts +4 -1
  248. package/src/runtime/supervisor-contact.ts +10 -5
  249. package/src/runtime/task-display.ts +19 -5
  250. package/src/runtime/task-graph-scheduler.ts +95 -21
  251. package/src/runtime/task-graph.ts +5 -11
  252. package/src/runtime/task-health.ts +54 -47
  253. package/src/runtime/task-id.ts +12 -19
  254. package/src/runtime/task-output-context.ts +265 -81
  255. package/src/runtime/task-packet.ts +12 -20
  256. package/src/runtime/task-quality.ts +6 -14
  257. package/src/runtime/task-runner/context-retrieval.ts +4 -13
  258. package/src/runtime/task-runner/live-executor.ts +114 -36
  259. package/src/runtime/task-runner/output-splitter.ts +152 -0
  260. package/src/runtime/task-runner/progress.ts +50 -12
  261. package/src/runtime/task-runner/prompt-builder.ts +31 -7
  262. package/src/runtime/task-runner/prompt-pipeline.ts +31 -7
  263. package/src/runtime/task-runner/result-utils.ts +3 -1
  264. package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
  265. package/src/runtime/task-runner/run-projection.ts +27 -8
  266. package/src/runtime/task-runner/state-helpers.ts +50 -14
  267. package/src/runtime/task-runner/tail-read.ts +2 -6
  268. package/src/runtime/task-runner.ts +180 -326
  269. package/src/runtime/team-runner-artifacts.ts +13 -0
  270. package/src/runtime/team-runner.ts +456 -974
  271. package/src/runtime/tool-output-pruner.ts +6 -9
  272. package/src/runtime/tool-progress.ts +11 -21
  273. package/src/runtime/verification-gates.ts +33 -24
  274. package/src/runtime/verification-integrity.ts +2 -7
  275. package/src/runtime/verification-worktree.ts +62 -12
  276. package/src/runtime/worker-heartbeat.ts +5 -1
  277. package/src/runtime/worker-startup.ts +29 -5
  278. package/src/runtime/workflow-state.ts +17 -9
  279. package/src/runtime/workspace-lock.ts +30 -35
  280. package/src/runtime/workspace-tree.ts +20 -32
  281. package/src/runtime/yield-handler.ts +43 -9
  282. package/src/runtime/zombie-scanner.ts +5 -4
  283. package/src/schema/config-schema.ts +266 -172
  284. package/src/schema/team-tool-schema.ts +29 -74
  285. package/src/schema/validation-types.ts +25 -17
  286. package/src/skills/discover-skills.ts +35 -10
  287. package/src/skills/skill-templates.ts +109 -27
  288. package/src/skills/validate.ts +26 -8
  289. package/src/state/active-run-registry.ts +87 -28
  290. package/src/state/artifact-store.ts +9 -5
  291. package/src/state/atomic-write-v2.ts +85 -63
  292. package/src/state/atomic-write.ts +106 -27
  293. package/src/state/blob-store.ts +47 -20
  294. package/src/state/contracts.ts +20 -5
  295. package/src/state/crew-init.ts +5 -22
  296. package/src/state/decision-ledger.ts +19 -73
  297. package/src/state/event-log-rotation.ts +41 -15
  298. package/src/state/event-log.ts +168 -53
  299. package/src/state/event-reconstructor.ts +11 -1
  300. package/src/state/gitignore-manager.ts +2 -8
  301. package/src/state/health-store.ts +57 -57
  302. package/src/state/hook-instinct-bridge.ts +1 -1
  303. package/src/state/hook-integrations.ts +1 -1
  304. package/src/state/instinct-store.ts +17 -5
  305. package/src/state/jsonl-writer.ts +1 -1
  306. package/src/state/locks.ts +23 -9
  307. package/src/state/mailbox.ts +151 -28
  308. package/src/state/observation-store.ts +20 -14
  309. package/src/state/run-cache.ts +142 -135
  310. package/src/state/run-graph.ts +6 -15
  311. package/src/state/run-metrics.ts +5 -18
  312. package/src/state/schedule.ts +15 -9
  313. package/src/state/state-store.ts +137 -47
  314. package/src/state/task-claims.ts +12 -2
  315. package/src/state/tiered-eval.ts +52 -43
  316. package/src/state/types-eval.ts +2 -2
  317. package/src/state/types.ts +24 -20
  318. package/src/state/usage.ts +20 -4
  319. package/src/state/worker-atomic-writer.ts +6 -3
  320. package/src/subagents/index.ts +2 -2
  321. package/src/teams/discover-teams.ts +21 -6
  322. package/src/tools/safe-bash-extension.ts +5 -6
  323. package/src/tools/safe-bash.ts +10 -11
  324. package/src/types/new-api-types.ts +6 -10
  325. package/src/ui/agent-management-overlay.ts +52 -40
  326. package/src/ui/card-colors.ts +13 -4
  327. package/src/ui/crew-footer.ts +8 -7
  328. package/src/ui/crew-select-list.ts +1 -1
  329. package/src/ui/dashboard-panes/agents-pane.ts +41 -25
  330. package/src/ui/dashboard-panes/cancellation-pane.ts +1 -1
  331. package/src/ui/dashboard-panes/capability-pane.ts +32 -15
  332. package/src/ui/dashboard-panes/health-pane.ts +2 -1
  333. package/src/ui/dashboard-panes/metrics-pane.ts +4 -1
  334. package/src/ui/dashboard-panes/progress-pane.ts +12 -9
  335. package/src/ui/heartbeat-aggregator.ts +15 -4
  336. package/src/ui/keybinding-map.ts +40 -7
  337. package/src/ui/live-conversation-overlay.ts +36 -20
  338. package/src/ui/live-duration.ts +1 -4
  339. package/src/ui/live-run-sidebar.ts +88 -25
  340. package/src/ui/loaders.ts +2 -8
  341. package/src/ui/mascot.ts +20 -36
  342. package/src/ui/overlays/agent-picker-overlay.ts +11 -3
  343. package/src/ui/overlays/confirm-overlay.ts +4 -2
  344. package/src/ui/overlays/help-overlay.ts +25 -14
  345. package/src/ui/overlays/mailbox-compose-overlay.ts +48 -11
  346. package/src/ui/overlays/mailbox-compose-preview.ts +20 -5
  347. package/src/ui/overlays/mailbox-detail-overlay.ts +27 -6
  348. package/src/ui/pi-ui-compat.ts +7 -7
  349. package/src/ui/powerbar-publisher.ts +120 -42
  350. package/src/ui/render-diff.ts +10 -3
  351. package/src/ui/render-scheduler.ts +12 -4
  352. package/src/ui/run-action-dispatcher.ts +81 -18
  353. package/src/ui/run-dashboard.ts +172 -76
  354. package/src/ui/run-event-bus.ts +59 -37
  355. package/src/ui/run-snapshot-cache.ts +276 -86
  356. package/src/ui/settings-overlay.ts +361 -74
  357. package/src/ui/status-colors.ts +12 -1
  358. package/src/ui/syntax-highlight.ts +1 -1
  359. package/src/ui/terminal-status.ts +1 -3
  360. package/src/ui/theme-adapter.ts +16 -14
  361. package/src/ui/theme-discovery.ts +13 -2
  362. package/src/ui/tool-progress-formatter.ts +7 -7
  363. package/src/ui/tool-render.ts +128 -56
  364. package/src/ui/tool-renderers/brief-mode.ts +45 -27
  365. package/src/ui/tool-renderers/index.ts +109 -43
  366. package/src/ui/transcript-cache.ts +32 -6
  367. package/src/ui/transcript-entries.ts +25 -23
  368. package/src/ui/transcript-viewer.ts +78 -29
  369. package/src/ui/widget/index.ts +99 -40
  370. package/src/ui/widget/widget-formatters.ts +13 -6
  371. package/src/ui/widget/widget-model.ts +19 -8
  372. package/src/ui/widget/widget-renderer.ts +23 -13
  373. package/src/ui/widget/widget-types.ts +1 -1
  374. package/src/utils/bm25-search.ts +199 -199
  375. package/src/utils/conflict-detect.ts +22 -21
  376. package/src/utils/env-filter.ts +14 -7
  377. package/src/utils/file-coalescer.ts +5 -1
  378. package/src/utils/fingerprint.ts +3 -6
  379. package/src/utils/frontmatter.ts +3 -1
  380. package/src/utils/fs-watch.ts +2 -6
  381. package/src/utils/gh-protocol.ts +119 -44
  382. package/src/utils/git.ts +13 -15
  383. package/src/utils/guards.ts +2 -5
  384. package/src/utils/ids.ts +9 -2
  385. package/src/utils/incremental-reader.ts +14 -3
  386. package/src/utils/internal-error.ts +2 -1
  387. package/src/utils/names.ts +12 -3
  388. package/src/utils/paths.ts +49 -5
  389. package/src/utils/project-detector.ts +2 -2
  390. package/src/utils/redaction.ts +29 -19
  391. package/src/utils/resolve-shell.ts +9 -7
  392. package/src/utils/run-watcher-registry.ts +19 -31
  393. package/src/utils/safe-paths.ts +46 -29
  394. package/src/utils/scan-cache.ts +9 -2
  395. package/src/utils/session-utils.ts +2 -4
  396. package/src/utils/sleep.ts +12 -5
  397. package/src/utils/sse-parser.ts +5 -17
  398. package/src/utils/visual.ts +52 -32
  399. package/src/workflows/discover-workflows.ts +41 -109
  400. package/src/workflows/intermediate-store.ts +5 -21
  401. package/src/workflows/preflight-validator.ts +6 -33
  402. package/src/workflows/topology-analyzer.ts +5 -21
  403. package/src/workflows/workflow-config.ts +7 -6
  404. package/src/worktree/branch-freshness.ts +66 -8
  405. package/src/worktree/cleanup.ts +130 -20
  406. package/src/worktree/worktree-manager.ts +212 -69
  407. package/skills/artifact-analysis-loop/SKILL.md +0 -303
  408. package/skills/detection-pipeline-design/SKILL.md +0 -286
  409. package/skills/hunting-investigation-loop/SKILL.md +0 -402
  410. package/skills/incident-playbook-construction/SKILL.md +0 -384
  411. package/skills/security-review/SKILL.md +0 -561
  412. package/skills/threat-hypothesis-framework/SKILL.md +0 -176
  413. package/skills/ui-render-performance/SKILL.md +0 -58
  414. /package/docs/{followup-review-round3-2026-05-12.md → archive/followup-review-round3-2026-05-12.md} +0 -0
  415. /package/docs/{followup-review-round4-2026-05-13.md → archive/followup-review-round4-2026-05-13.md} +0 -0
  416. /package/docs/{pi-crew-bugs.md → archive/pi-crew-bugs.md} +0 -0
  417. /package/docs/{pi-crew-test-final.md → archive/pi-crew-test-final.md} +0 -0
  418. /package/docs/{pi-crew-test-results.md → archive/pi-crew-test-results.md} +0 -0
  419. /package/docs/{pi-crew-test-round2.md → archive/pi-crew-test-round2.md} +0 -0
  420. /package/docs/{pi-crew-test-round4.md → archive/pi-crew-test-round4.md} +0 -0
  421. /package/docs/{pi-crew-test-round5.md → archive/pi-crew-test-round5.md} +0 -0
  422. /package/docs/{pi-crew-test-round6.md → archive/pi-crew-test-round6.md} +0 -0
  423. /package/docs/{pi-crew-v0.5.10-audit-fix-plan.md → archive/pi-crew-v0.5.10-audit-fix-plan.md} +0 -0
  424. /package/docs/{pi-crew-v0.5.11-audit-fix-plan.md → archive/pi-crew-v0.5.11-audit-fix-plan.md} +0 -0
  425. /package/docs/{pi-crew-v0.5.12-audit-fix-plan.md → archive/pi-crew-v0.5.12-audit-fix-plan.md} +0 -0
  426. /package/docs/{pi-crew-v0.5.13-audit-fix-plan.md → archive/pi-crew-v0.5.13-audit-fix-plan.md} +0 -0
  427. /package/docs/{pi-crew-v0.5.14-audit-fix-plan.md → archive/pi-crew-v0.5.14-audit-fix-plan.md} +0 -0
  428. /package/docs/{pi-crew-v0.5.16-audit-fix-plan.md → archive/pi-crew-v0.5.16-audit-fix-plan.md} +0 -0
  429. /package/docs/{pi-crew-v0.5.17-audit-fix-plan.md → archive/pi-crew-v0.5.17-audit-fix-plan.md} +0 -0
  430. /package/docs/{pi-crew-v0.5.5-audit-fix-plan.md → archive/pi-crew-v0.5.5-audit-fix-plan.md} +0 -0
  431. /package/docs/{pi-crew-v0.5.9-audit-fix-plan.md → archive/pi-crew-v0.5.9-audit-fix-plan.md} +0 -0
  432. /package/docs/{pi-mono-opportunities.md → archive/pi-mono-opportunities.md} +0 -0
  433. /package/docs/{pi-mono-review.md → archive/pi-mono-review.md} +0 -0
  434. /package/docs/{pi-subagent4-comparison.md → archive/pi-subagent4-comparison.md} +0 -0
  435. /package/docs/{pi-subagents3-deep-analysis.md → archive/pi-subagents3-deep-analysis.md} +0 -0
@@ -0,0 +1,310 @@
1
+ /**
2
+ * M3: Iterative file-retrieval orchestrator.
3
+ *
4
+ * Pattern: workers progressively discover relevant context files
5
+ * (e.g. "which source file handles X?") using ripgrep-driven keyword
6
+ * search + the existing context-retrieval.ts scoring/convergence
7
+ * helpers. Max 3 cycles, fall back to in-memory heuristic when
8
+ * ripgrep is not available (e.g. minimal Windows CI runners).
9
+ *
10
+ * Signal flow:
11
+ * renderTaskPrompt (in prompt-builder.ts)
12
+ * → runRetrievalCycle(task, goal, cwd)
13
+ * → cycle 1: rg --files, then score each file
14
+ * → cycle 2: refine query, rg --json for keyword filter
15
+ * → cycle 3: same; if !shouldContinue, stop early
16
+ * → returns top-N files (5..10)
17
+ * renderTaskPrompt injects "Suggested files to read (top-N by
18
+ * retrieval score):" section before final prompt assembly.
19
+ *
20
+ * Production code path — not @experimental. Fallback is mandatory so
21
+ * the prompt is never blocked by a missing ripgrep binary.
22
+ */
23
+ import { spawn } from "node:child_process";
24
+ import * as fs from "node:fs";
25
+ import * as path from "node:path";
26
+ import type { RelevanceEvaluation } from "./context-retrieval.ts";
27
+ import { hasConverged, scoreRelevance, shouldContinue } from "./context-retrieval.ts";
28
+
29
+ /** Max retrieval cycles per prompt render. Matches context-retrieval.MAX_CYCLES. */
30
+ export const MAX_CYCLES = 3;
31
+
32
+ /** Hard cap on suggested files injected into the worker prompt. */
33
+ export const MAX_SUGGESTED_FILES = 10;
34
+
35
+ /** Minimum files suggested when retrieval returns anything. */
36
+ export const MIN_SUGGESTED_FILES = 5;
37
+
38
+ /** Stopwords dropped during keyword tokenization (lowercase comparison). */
39
+ const STOPWORDS: ReadonlySet<string> = new Set(["the", "a", "an", "and", "or", "to", "of", "in", "for", "on", "is", "are", "be", "with"]);
40
+
41
+ /** File extensions considered relevant for retrieval. */
42
+ const RELEVANT_EXTS: ReadonlySet<string> = new Set([
43
+ ".ts",
44
+ ".tsx",
45
+ ".js",
46
+ ".jsx",
47
+ ".mjs",
48
+ ".cjs",
49
+ ".md",
50
+ ".markdown",
51
+ ".json",
52
+ ".yaml",
53
+ ".yml",
54
+ ]);
55
+
56
+ /** Result of a single runRetrievalCycle call. */
57
+ export interface RetrievalResult {
58
+ files: Array<{ path: string; score: number; reason: string }>;
59
+ cycles: number;
60
+ converged: boolean;
61
+ usedFallback: boolean;
62
+ }
63
+
64
+ interface RipgrepAvailable {
65
+ available: boolean;
66
+ version?: string;
67
+ }
68
+
69
+ let cachedRgCheck: RipgrepAvailable | undefined;
70
+
71
+ /**
72
+ * Detect ripgrep availability once per process. Uses `rg --version` and
73
+ * catches ENOENT or non-zero exit. Cached so the cost (one spawn) is
74
+ * paid only on the first retrieval cycle.
75
+ */
76
+ export async function detectRipgrep(): Promise<RipgrepAvailable> {
77
+ if (cachedRgCheck !== undefined) return cachedRgCheck;
78
+ return await new Promise<RipgrepAvailable>((resolve) => {
79
+ let settled = false;
80
+ try {
81
+ const child = spawn("rg", ["--version"], { stdio: ["ignore", "pipe", "pipe"] });
82
+ let stdout = "";
83
+ child.stdout?.on("data", (chunk) => {
84
+ stdout += chunk.toString("utf-8");
85
+ });
86
+ child.on("error", () => {
87
+ if (settled) return;
88
+ settled = true;
89
+ cachedRgCheck = { available: false };
90
+ resolve(cachedRgCheck);
91
+ });
92
+ child.on("close", (code) => {
93
+ if (settled) return;
94
+ settled = true;
95
+ if (code === 0) {
96
+ cachedRgCheck = { available: true, version: stdout.split("\n")[0] ?? undefined };
97
+ } else {
98
+ cachedRgCheck = { available: false };
99
+ }
100
+ resolve(cachedRgCheck);
101
+ });
102
+ } catch {
103
+ if (settled) return;
104
+ settled = true;
105
+ cachedRgCheck = { available: false };
106
+ resolve(cachedRgCheck);
107
+ }
108
+ });
109
+ }
110
+
111
+ /** @internal Test-only: reset the ripgrep detection cache. */
112
+ export function __test_resetRipgrepCache(): void {
113
+ cachedRgCheck = undefined;
114
+ }
115
+
116
+ /**
117
+ * Tokenize a task + goal string into lowercase keywords, dropping
118
+ * stopwords. Single-letter tokens and pure-punctuation tokens are
119
+ * dropped. Output is deduped, original-order preserved.
120
+ */
121
+ export function tokenizeQuery(task: string, goal: string): string[] {
122
+ const combined = `${task}\n${goal}`.toLowerCase();
123
+ const tokens = combined
124
+ .split(/[^a-z0-9_-]+/)
125
+ .map((t) => t.trim())
126
+ .filter((t) => t.length >= 2 && !STOPWORDS.has(t));
127
+ return [...new Set(tokens)];
128
+ }
129
+
130
+ /** Reason template for a discovery hit. Exported for tests. */
131
+ export function reasonFor(file: string, keywords: string[]): string {
132
+ const lower = file.toLowerCase();
133
+ const hits = keywords.filter((k) => lower.includes(k));
134
+ if (hits.length === 0) return `matched by relevance score (no direct keyword hit in path)`;
135
+ return `keyword match: ${hits.join(", ")}`;
136
+ }
137
+
138
+ /**
139
+ * Run ripgrep with the given args, returning stdout as a string.
140
+ * Throws on ENOENT / non-zero exit. Caller handles fallback.
141
+ */
142
+ function runRipgrep(args: string[], cwd: string): Promise<string> {
143
+ return new Promise<string>((resolve, reject) => {
144
+ let settled = false;
145
+ let stdout = "";
146
+ let stderr = "";
147
+ try {
148
+ const child = spawn("rg", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
149
+ child.stdout?.on("data", (chunk) => {
150
+ stdout += chunk.toString("utf-8");
151
+ });
152
+ child.stderr?.on("data", (chunk) => {
153
+ stderr += chunk.toString("utf-8");
154
+ });
155
+ child.on("error", (err) => {
156
+ if (settled) return;
157
+ settled = true;
158
+ reject(err);
159
+ });
160
+ child.on("close", (code) => {
161
+ if (settled) return;
162
+ settled = true;
163
+ // rg exit code 1 = "no matches" (NOT an error). Any other
164
+ // non-zero exit IS an error.
165
+ if (code === 0 || code === 1) {
166
+ resolve(stdout);
167
+ } else {
168
+ reject(new Error(`rg exited ${code}: ${stderr.slice(0, 200)}`));
169
+ }
170
+ });
171
+ } catch (e) {
172
+ if (settled) return;
173
+ settled = true;
174
+ reject(e);
175
+ }
176
+ });
177
+ }
178
+
179
+ /**
180
+ * In-memory fallback: walk cwd with readdir({recursive:true}), filter
181
+ * to relevant extensions, score by filename keyword match. Used when
182
+ * ripgrep is not installed. Mirrors the rg --files path closely so
183
+ * downstream scoring behaves the same.
184
+ */
185
+ async function walkFilesFallback(cwd: string, keywords: string[]): Promise<Array<{ path: string; score: number; reason: string }>> {
186
+ const out: Array<{ path: string; score: number; reason: string }> = [];
187
+ const lowerCwd = cwd.toLowerCase();
188
+ async function walk(dir: string): Promise<void> {
189
+ let entries: fs.Dirent[];
190
+ try {
191
+ entries = await fs.promises.readdir(dir, { withFileTypes: true });
192
+ } catch {
193
+ return;
194
+ }
195
+ for (const entry of entries) {
196
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
197
+ const full = path.join(dir, entry.name);
198
+ if (entry.isDirectory()) {
199
+ await walk(full);
200
+ continue;
201
+ }
202
+ if (!entry.isFile()) continue;
203
+ const ext = path.extname(entry.name).toLowerCase();
204
+ if (!RELEVANT_EXTS.has(ext)) continue;
205
+ // scoreRelevance expects file content, but for the fallback we
206
+ // only have the filename. Use the filename as a proxy (the
207
+ // existing scoreRelevance handles short content gracefully —
208
+ // path match contributes 0.3, content match 0.05 × log2(N)).
209
+ const content = "";
210
+ const score = scoreRelevance(full, content, keywords);
211
+ if (score > 0) {
212
+ out.push({ path: path.relative(lowerCwd, full), score, reason: reasonFor(full, keywords) });
213
+ }
214
+ }
215
+ }
216
+ await walk(cwd);
217
+ return out;
218
+ }
219
+
220
+ /**
221
+ * Iterate up to MAX_CYCLES times. Each cycle: discover files, score
222
+ * them, check convergence. Stop early on convergence or when
223
+ * shouldContinue returns false.
224
+ */
225
+ export async function runRetrievalCycle(task: string, goal: string, cwd: string): Promise<RetrievalResult> {
226
+ const keywords = tokenizeQuery(task, goal);
227
+ if (keywords.length === 0) {
228
+ return { files: [], cycles: 0, converged: true, usedFallback: false };
229
+ }
230
+ const rg = await detectRipgrep();
231
+ const useRg = rg.available;
232
+ let usedFallback = !useRg;
233
+ const evaluations: RelevanceEvaluation[] = [];
234
+ let cycle = 0;
235
+ let converged = false;
236
+ for (; cycle < MAX_CYCLES; cycle++) {
237
+ let discovered: string[] = [];
238
+ try {
239
+ if (useRg) {
240
+ // Cycle 0: enumerate all relevant files via `rg --files`.
241
+ // Later cycles: filter by keywords via `rg --files | rg pattern`.
242
+ // We use the simpler `rg --files` + filter strategy because
243
+ // `rg --json` parsing adds complexity for marginal gain.
244
+ // `rg --files` respects .gitignore by default; we add an
245
+ // explicit -g '!node_modules' and -g '!.git' to be safe on
246
+ // repos that don't ignore them.
247
+ const stdout = await runRipgrep(["--files", "-g", "!node_modules", "-g", "!.git", cwd], cwd);
248
+ discovered = stdout
249
+ .split("\n")
250
+ .map((p) => p.trim())
251
+ .filter((p) => p && RELEVANT_EXTS.has(path.extname(p).toLowerCase()))
252
+ .map((p) => path.relative(cwd, p));
253
+ } else {
254
+ discovered = (await walkFilesFallback(cwd, keywords)).map((f) => f.path);
255
+ }
256
+ } catch {
257
+ // rg errored mid-run — switch to fallback for this cycle.
258
+ usedFallback = true;
259
+ discovered = (await walkFilesFallback(cwd, keywords)).map((f) => f.path);
260
+ }
261
+ // Score each discovered file. Path-only scoring (no file read) so
262
+ // we don't slow down prompt building for hundreds of files.
263
+ const seenInThisCycle = new Set<string>();
264
+ for (const relPath of discovered) {
265
+ if (seenInThisCycle.has(relPath)) continue;
266
+ seenInThisCycle.add(relPath);
267
+ const absPath = path.isAbsolute(relPath) ? relPath : path.join(cwd, relPath);
268
+ const score = scoreRelevance(absPath, "", keywords);
269
+ if (score > 0) {
270
+ evaluations.push({
271
+ path: absPath,
272
+ relevance: score,
273
+ reason: reasonFor(absPath, keywords),
274
+ missingContext: [],
275
+ });
276
+ }
277
+ }
278
+ converged = hasConverged(evaluations);
279
+ if (converged) break;
280
+ if (!shouldContinue(evaluations, cycle)) break;
281
+ }
282
+ // Sort by score desc, take top N (5..10).
283
+ evaluations.sort((a, b) => b.relevance - a.relevance);
284
+ const cap = Math.min(MAX_SUGGESTED_FILES, Math.max(MIN_SUGGESTED_FILES, evaluations.length));
285
+ const top = evaluations.slice(0, cap).map((e) => ({
286
+ path: path.isAbsolute(e.path) ? path.relative(cwd, e.path) : e.path,
287
+ score: e.relevance,
288
+ reason: e.reason,
289
+ }));
290
+ return { files: top, cycles: cycle, converged, usedFallback };
291
+ }
292
+
293
+ /**
294
+ * Render the "Suggested files to read" section for injection into the
295
+ * worker prompt. Returns an empty string when retrieval returned no
296
+ * files. Format is a markdown bullet list (one line per file) prefixed
297
+ * with a heading so the worker can `grep` for it.
298
+ */
299
+ export function renderSuggestedFilesSection(result: RetrievalResult): string {
300
+ if (result.files.length === 0) return "";
301
+ const lines: string[] = [
302
+ `# Suggested files to read (top-${result.files.length} by retrieval score)`,
303
+ `Retrieval ran for ${result.cycles} cycle(s)${result.usedFallback ? " (in-memory fallback, rg unavailable)" : ""}${result.converged ? " and converged" : ""}.`,
304
+ "",
305
+ ];
306
+ for (const file of result.files) {
307
+ lines.push(`- ${file.path} — ${file.reason} (score ${file.score.toFixed(2)})`);
308
+ }
309
+ return lines.join("\n");
310
+ }
@@ -1,6 +1,5 @@
1
- import type { TeamRunManifest, TeamTaskState } from "../../state/types.ts";
2
1
  import type { MailboxMessage } from "../../state/mailbox.ts";
3
- import type { ArtifactDescriptor } from "../../state/types.ts";
2
+ import type { ArtifactDescriptor, TeamRunManifest, TeamTaskState } from "../../state/types.ts";
4
3
 
5
4
  export interface RunProjectionSource {
6
5
  kind: "events" | "mailbox" | "artifacts" | "ui_metadata" | "runtime_metadata";
@@ -44,7 +43,11 @@ export function transformRunContextBeforeWorkerStart(input: {
44
43
  const completedTasks = input.tasks.filter((t) => t.status === "completed" || t.status === "failed");
45
44
  if (completedTasks.length > 0) {
46
45
  const tasks = completedTasks.slice(0, maxEvents);
47
- sources.push({ kind: "events", bounded: true, reference: `tasks:${tasks.length}/${completedTasks.length}` });
46
+ sources.push({
47
+ kind: "events",
48
+ bounded: true,
49
+ reference: `tasks:${tasks.length}/${completedTasks.length}`,
50
+ });
48
51
  lines.push(`Previous tasks (${tasks.length}/${completedTasks.length}):`);
49
52
  for (const task of tasks) {
50
53
  lines.push(`- ${task.id}: ${task.status}${task.error ? ` (${task.error})` : ""}`);
@@ -54,7 +57,11 @@ export function transformRunContextBeforeWorkerStart(input: {
54
57
  // Project pending mailbox that is relevant to this worker
55
58
  if (input.pendingMailbox.length > 0) {
56
59
  const messages = input.pendingMailbox.slice(0, maxMailbox);
57
- sources.push({ kind: "mailbox", bounded: true, reference: `mailbox:${messages.length}/${input.pendingMailbox.length}` });
60
+ sources.push({
61
+ kind: "mailbox",
62
+ bounded: true,
63
+ reference: `mailbox:${messages.length}/${input.pendingMailbox.length}`,
64
+ });
58
65
  lines.push(`Pending messages (${messages.length}/${input.pendingMailbox.length}):`);
59
66
  for (const msg of messages) {
60
67
  lines.push(`- ${msg.kind ?? "message"}: ${msg.body.slice(0, 100)}`);
@@ -64,7 +71,11 @@ export function transformRunContextBeforeWorkerStart(input: {
64
71
  // Project artifact references (not content)
65
72
  if (input.artifacts.length > 0) {
66
73
  const artifacts = input.artifacts.slice(0, maxArtifacts);
67
- sources.push({ kind: "artifacts", bounded: true, reference: `artifacts:${artifacts.length}/${input.artifacts.length}` });
74
+ sources.push({
75
+ kind: "artifacts",
76
+ bounded: true,
77
+ reference: `artifacts:${artifacts.length}/${input.artifacts.length}`,
78
+ });
68
79
  lines.push(`Available artifacts (${artifacts.length}/${input.artifacts.length}):`);
69
80
  for (const art of artifacts) {
70
81
  lines.push(`- ${art.kind} (${art.producer})`);
@@ -72,8 +83,16 @@ export function transformRunContextBeforeWorkerStart(input: {
72
83
  }
73
84
 
74
85
  // Metadata markers — not injected as prompt instructions
75
- sources.push({ kind: "ui_metadata", bounded: false, reference: "excluded_from_prompt" });
76
- sources.push({ kind: "runtime_metadata", bounded: false, reference: "excluded_from_prompt" });
86
+ sources.push({
87
+ kind: "ui_metadata",
88
+ bounded: false,
89
+ reference: "excluded_from_prompt",
90
+ });
91
+ sources.push({
92
+ kind: "runtime_metadata",
93
+ bounded: false,
94
+ reference: "excluded_from_prompt",
95
+ });
77
96
 
78
97
  return {
79
98
  sources,
@@ -101,4 +120,4 @@ export function convertRunHistoryToWorkerPrompt(input: {
101
120
  "",
102
121
  `Projection sources: ${projection.sources.map((s) => s.kind).join(", ")}`,
103
122
  ].join("\n");
104
- }
123
+ }
@@ -1,12 +1,13 @@
1
1
  import * as fs from "node:fs";
2
+ import { flushPendingAtomicWrites } from "../../state/atomic-write.ts";
3
+ import { withRunLockSync } from "../../state/locks.ts";
4
+ import { loadRunManifestById, saveRunTasksCoalesced } from "../../state/state-store.ts";
2
5
  import type { TaskCheckpointState, TeamRunManifest, TeamTaskState } from "../../state/types.ts";
3
- import { loadRunManifestById, saveRunTasks } from "../../state/state-store.ts";
4
- import { recordFromTask, upsertCrewAgent } from "../crew-agent-records.ts";
5
6
  import { logInternalError } from "../../utils/internal-error.ts";
6
- import { withRunLockSync } from "../../state/locks.ts";
7
+ import { recordFromTask, upsertCrewAgent } from "../crew-agent-records.ts";
7
8
 
8
9
  export function updateTask(tasks: TeamTaskState[], updated: TeamTaskState): TeamTaskState[] {
9
- return tasks.map((task) => task.id === updated.id ? updated : task);
10
+ return tasks.map((task) => (task.id === updated.id ? updated : task));
10
11
  }
11
12
 
12
13
  /**
@@ -25,7 +26,12 @@ export function updateTask(tasks: TeamTaskState[], updated: TeamTaskState): Team
25
26
  *
26
27
  * @param checkpointPhase - Optional checkpoint phase to include in the task state alongside the update.
27
28
  */
28
- export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks: TeamTaskState[], updated: TeamTaskState, checkpointPhase?: TaskCheckpointState["phase"]): TeamTaskState[] {
29
+ export function persistSingleTaskUpdate(
30
+ manifest: TeamRunManifest,
31
+ fallbackTasks: TeamTaskState[],
32
+ updated: TeamTaskState,
33
+ checkpointPhase?: TaskCheckpointState["phase"],
34
+ ): TeamTaskState[] {
29
35
  let baseMtime = 0;
30
36
  try {
31
37
  baseMtime = fs.statSync(manifest.tasksPath).mtimeMs;
@@ -38,12 +44,27 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
38
44
 
39
45
  // Build the task with optional checkpoint phase
40
46
  const taskWithCheckpoint = checkpointPhase
41
- ? { ...updated, checkpoint: { phase: checkpointPhase, updatedAt: new Date().toISOString() } }
47
+ ? {
48
+ ...updated,
49
+ checkpoint: {
50
+ phase: checkpointPhase,
51
+ updatedAt: new Date().toISOString(),
52
+ },
53
+ }
42
54
  : updated;
43
55
 
44
56
  try {
45
57
  return withRunLockSync(manifest, () => {
46
- retryLoop: for (let attempt = 0; attempt < 100; attempt++) {
58
+ for (let attempt = 0; attempt < 100; attempt++) {
59
+ // F4: persistSingleTaskUpdate now uses saveRunTasksCoalesced below
60
+ // (50ms debounce window). Read-modify-write loops are unsafe under
61
+ // coalescing — a parallel writer's buffered write is invisible to
62
+ // loadRunManifestById until it actually lands. Force any pending
63
+ // coalesced writes to flush first so this read sees the latest
64
+ // durable state. Without this guard, a parallel writer could
65
+ // overwrite our buffered write between our load and our (async)
66
+ // fsync, silently losing the intermediate update.
67
+ flushPendingAtomicWrites();
47
68
  const latest = loadRunManifestById(manifest.cwd, manifest.runId)?.tasks ?? fallbackTasks;
48
69
  merged = updateTask(latest, taskWithCheckpoint);
49
70
 
@@ -58,7 +79,7 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
58
79
  if (currentMtime !== baseMtime) {
59
80
  // Another writer committed — their update is in latest, re-merge on top
60
81
  baseMtime = currentMtime;
61
- continue retryLoop;
82
+ continue;
62
83
  }
63
84
 
64
85
  // No concurrent writer — check that our merged result is based on the
@@ -72,7 +93,7 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
72
93
  }
73
94
  if (recheckMtime !== baseMtime) {
74
95
  baseMtime = recheckMtime;
75
- continue retryLoop;
96
+ continue;
76
97
  }
77
98
 
78
99
  // Final pre-write mtime check to catch any concurrent writer that completed
@@ -86,10 +107,10 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
86
107
  if (preWriteMtime !== baseMtime) {
87
108
  // Another writer committed — retry
88
109
  baseMtime = preWriteMtime;
89
- continue retryLoop;
110
+ continue;
90
111
  }
91
112
 
92
- break retryLoop;
113
+ break;
93
114
  }
94
115
 
95
116
  if (merged === undefined) {
@@ -98,7 +119,12 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
98
119
  }
99
120
 
100
121
  try {
101
- saveRunTasks(manifest, merged);
122
+ // F4: coalesced write inside the withRunLockSync critical section.
123
+ // The mtime CAS retry loop above still guards against concurrent
124
+ // non-coalesced writers; the flushPendingAtomicWrites() guard at
125
+ // the top of the retry loop ensures reads see any other coalesced
126
+ // writer's flushed-before-this-call state.
127
+ saveRunTasksCoalesced(manifest, merged);
102
128
  } catch (err) {
103
129
  logInternalError("persistSingleTaskUpdate", err);
104
130
  throw err;
@@ -113,8 +139,18 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
113
139
  }
114
140
  }
115
141
 
116
- export function checkpointTask(manifest: TeamRunManifest, tasks: TeamTaskState[], task: TeamTaskState, phase: TaskCheckpointState["phase"], childPid?: number): { task: TeamTaskState; tasks: TeamTaskState[] } {
117
- const checkpoint: TaskCheckpointState = { phase, updatedAt: new Date().toISOString(), ...(childPid ? { childPid } : task.checkpoint?.childPid ? { childPid: task.checkpoint.childPid } : {}) };
142
+ export function checkpointTask(
143
+ manifest: TeamRunManifest,
144
+ tasks: TeamTaskState[],
145
+ task: TeamTaskState,
146
+ phase: TaskCheckpointState["phase"],
147
+ childPid?: number,
148
+ ): { task: TeamTaskState; tasks: TeamTaskState[] } {
149
+ const checkpoint: TaskCheckpointState = {
150
+ phase,
151
+ updatedAt: new Date().toISOString(),
152
+ ...(childPid ? { childPid } : task.checkpoint?.childPid ? { childPid: task.checkpoint.childPid } : {}),
153
+ };
118
154
  const nextTask = { ...task, checkpoint };
119
155
  const nextTasks = persistSingleTaskUpdate(manifest, updateTask(tasks, nextTask), nextTask);
120
156
  try {
@@ -5,11 +5,7 @@ import * as fs from "node:fs";
5
5
  * If the file exceeds maxBytes, reads only the last maxBytes and snaps
6
6
  * to the nearest newline boundary to avoid partial JSONL lines.
7
7
  */
8
- export function tailReadWithLineSnap(
9
- filePath: string,
10
- maxBytes: number,
11
- fallbackContent: string,
12
- ): string {
8
+ export function tailReadWithLineSnap(filePath: string, maxBytes: number, fallbackContent: string): string {
13
9
  if (!fs.existsSync(filePath)) return fallbackContent;
14
10
  const stat = fs.statSync(filePath);
15
11
  if (stat.size <= maxBytes) return fs.readFileSync(filePath, "utf-8");
@@ -23,4 +19,4 @@ export function tailReadWithLineSnap(
23
19
  } finally {
24
20
  fs.closeSync(fd);
25
21
  }
26
- }
22
+ }