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
@@ -3,8 +3,8 @@ import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { logInternalError } from "../utils/internal-error.ts";
6
- import { isWorkerAtomicWriterEnabled, atomicWriteFileViaWorker } from "./worker-atomic-writer.ts";
7
6
  import { sleepSync } from "../utils/sleep.ts";
7
+ import { atomicWriteFileViaWorker, isWorkerAtomicWriterEnabled } from "./worker-atomic-writer.ts";
8
8
 
9
9
  function hashContent(content: string): string {
10
10
  return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
@@ -68,7 +68,9 @@ export function isSymlinkSafePath(filePath: string): boolean {
68
68
  // Found existing ancestor — join with remaining non-existent parts
69
69
  realBase = path.join(resolved, path.relative(walk, baseDir));
70
70
  break;
71
- } catch { /* not found yet, keep walking */ }
71
+ } catch {
72
+ /* not found yet, keep walking */
73
+ }
72
74
  }
73
75
  const realDirNorm = realDir.endsWith(path.sep) ? realDir : realDir + path.sep;
74
76
  const realBaseNorm = realBase.endsWith(path.sep) ? realBase : realBase + path.sep;
@@ -84,7 +86,11 @@ export function isSymlinkSafePath(filePath: string): boolean {
84
86
  if (typeof process.getuid === "function" && realStat.uid !== process.getuid()) {
85
87
  const systemTmp = (typeof os.tmpdir === "function" ? os.tmpdir() : "/tmp").toLowerCase();
86
88
  let realSystemTmp = systemTmp;
87
- try { realSystemTmp = fs.realpathSync(systemTmp).toLowerCase(); } catch { /* ok */ }
89
+ try {
90
+ realSystemTmp = fs.realpathSync(systemTmp).toLowerCase();
91
+ } catch {
92
+ /* ok */
93
+ }
88
94
  const realDirLower = realDir.toLowerCase();
89
95
  const dirNorm = realDirLower.endsWith(path.sep) ? realDirLower : realDirLower + path.sep;
90
96
  const tmpNorm = realSystemTmp.endsWith(path.sep) ? realSystemTmp : realSystemTmp + path.sep;
@@ -95,11 +101,14 @@ export function isSymlinkSafePath(filePath: string): boolean {
95
101
  // Also accept canonical system temp paths:
96
102
  // - /tmp → /private/tmp (macOS)
97
103
  // - /var/folders → /private/var/folders (macOS)
98
- const isSystemTmp = process.platform === "darwin" && (
99
- realDirLower === "/tmp" || realDirLower === "/private/tmp" ||
100
- realDirLower === "/var/folders" || realDirLower === "/private/var/folders" ||
101
- realDirLower.startsWith("/var/folders/") || realDirLower.startsWith("/private/var/folders/")
102
- );
104
+ const isSystemTmp =
105
+ process.platform === "darwin" &&
106
+ (realDirLower === "/tmp" ||
107
+ realDirLower === "/private/tmp" ||
108
+ realDirLower === "/var/folders" ||
109
+ realDirLower === "/private/var/folders" ||
110
+ realDirLower.startsWith("/var/folders/") ||
111
+ realDirLower.startsWith("/private/var/folders/"));
103
112
  if (!isInsideTmp && !isAncestorOfTmp && !isSystemTmp) {
104
113
  return false;
105
114
  }
@@ -124,7 +133,6 @@ export function isSymlinkSafePath(filePath: string): boolean {
124
133
  currentPath = dir;
125
134
  }
126
135
 
127
-
128
136
  // Issue 1 fix: verify baseDir itself is not a symlink.
129
137
  // The while loop above walks ancestors but stops when currentPath reaches
130
138
  // root (currentPath === path.dirname(currentPath)). If baseDir is a symlink
@@ -152,18 +160,20 @@ export function isSymlinkSafePath(filePath: string): boolean {
152
160
  }
153
161
  }
154
162
 
155
-
156
-
157
163
  function sleep(ms: number): Promise<void> {
158
164
  return new Promise((resolve) => setTimeout(resolve, ms));
159
165
  }
160
166
 
161
167
  function isRetryableRenameError(error: unknown): boolean {
162
- return Boolean(error && typeof error === "object" && "code" in error && RETRYABLE_RENAME_CODES.has(String((error as NodeJS.ErrnoException).code)));
168
+ return Boolean(
169
+ error && typeof error === "object" && "code" in error && RETRYABLE_RENAME_CODES.has(String((error as NodeJS.ErrnoException).code)),
170
+ );
163
171
  }
164
172
 
165
173
  function isRetryableLinkError(error: unknown): boolean {
166
- return Boolean(error && typeof error === "object" && "code" in error && RETRYABLE_LINK_CODES.has(String((error as NodeJS.ErrnoException).code)));
174
+ return Boolean(
175
+ error && typeof error === "object" && "code" in error && RETRYABLE_LINK_CODES.has(String((error as NodeJS.ErrnoException).code)),
176
+ );
167
177
  }
168
178
 
169
179
  /**
@@ -181,7 +191,11 @@ function renameWithLinkSync(tempPath: string, filePath: string, retries = 8): vo
181
191
  // path aliases are mixed (e.g. RUNNER~1 vs runneradmin). Use
182
192
  // renameSync which handles this correctly via MoveFileEx.
183
193
  if (process.platform === "win32") {
184
- try { fs.unlinkSync(filePath); } catch { /* destination may not exist */ }
194
+ try {
195
+ fs.unlinkSync(filePath);
196
+ } catch {
197
+ /* destination may not exist */
198
+ }
185
199
  try {
186
200
  fs.renameSync(tempPath, filePath);
187
201
  return;
@@ -191,7 +205,11 @@ function renameWithLinkSync(tempPath: string, filePath: string, retries = 8): vo
191
205
  }
192
206
  } else {
193
207
  // First try to unlink any existing file at destination (hard links don't follow symlinks)
194
- try { fs.unlinkSync(filePath); } catch { /* destination may not exist */ }
208
+ try {
209
+ fs.unlinkSync(filePath);
210
+ } catch {
211
+ /* destination may not exist */
212
+ }
195
213
  // Create hard link - does NOT follow symlinks at filePath
196
214
  fs.linkSync(tempPath, filePath);
197
215
  // Successfully linked - now unlink the temp file
@@ -214,7 +232,11 @@ async function renameWithLinkAsync(tempPath: string, filePath: string, retries =
214
232
  for (let attempt = 0; attempt <= retries; attempt++) {
215
233
  try {
216
234
  if (process.platform === "win32") {
217
- try { await fs.promises.unlink(filePath); } catch { /* destination may not exist */ }
235
+ try {
236
+ await fs.promises.unlink(filePath);
237
+ } catch {
238
+ /* destination may not exist */
239
+ }
218
240
  try {
219
241
  await fs.promises.rename(tempPath, filePath);
220
242
  return;
@@ -223,7 +245,11 @@ async function renameWithLinkAsync(tempPath: string, filePath: string, retries =
223
245
  if (!isRetryableLinkError(renameError) || attempt === retries) break;
224
246
  }
225
247
  } else {
226
- try { await fs.promises.unlink(filePath); } catch { /* destination may not exist */ }
248
+ try {
249
+ await fs.promises.unlink(filePath);
250
+ } catch {
251
+ /* destination may not exist */
252
+ }
227
253
  await fs.promises.link(tempPath, filePath);
228
254
  await fs.promises.unlink(tempPath);
229
255
  return;
@@ -239,7 +265,12 @@ async function renameWithLinkAsync(tempPath: string, filePath: string, retries =
239
265
  throw lastError;
240
266
  }
241
267
 
242
- export function renameWithRetry(tempPath: string, filePath: string, retries = 8, rename: (oldPath: string, newPath: string) => void = fs.renameSync): void {
268
+ export function renameWithRetry(
269
+ tempPath: string,
270
+ filePath: string,
271
+ retries = 8,
272
+ rename: (oldPath: string, newPath: string) => void = fs.renameSync,
273
+ ): void {
243
274
  let lastError: unknown;
244
275
  for (let attempt = 0; attempt <= retries; attempt++) {
245
276
  try {
@@ -262,7 +293,12 @@ export function renameWithRetry(tempPath: string, filePath: string, retries = 8,
262
293
  /** Test alias for renameWithRetry. */
263
294
  export const __test__renameWithRetry = renameWithRetry;
264
295
 
265
- export async function renameWithRetryAsync(tempPath: string, filePath: string, retries = 8, rename: (oldPath: string, newPath: string) => Promise<void> = (source, destination) => fs.promises.rename(source, destination)): Promise<void> {
296
+ export async function renameWithRetryAsync(
297
+ tempPath: string,
298
+ filePath: string,
299
+ retries = 8,
300
+ rename: (oldPath: string, newPath: string) => Promise<void> = (source, destination) => fs.promises.rename(source, destination),
301
+ ): Promise<void> {
266
302
  let lastError: unknown;
267
303
  for (let attempt = 0; attempt <= retries; attempt++) {
268
304
  try {
@@ -303,7 +339,11 @@ export function atomicWriteFile(filePath: string, content: string, expectedHash?
303
339
  const r = fs.realpathSync.native(p);
304
340
  return r.startsWith("\\\\?\\") ? r.slice(4) : r;
305
341
  } catch {
306
- try { return fs.realpathSync(p); } catch { return p; }
342
+ try {
343
+ return fs.realpathSync(p);
344
+ } catch {
345
+ return p;
346
+ }
307
347
  }
308
348
  };
309
349
  const dirPath = path.dirname(filePath);
@@ -312,7 +352,7 @@ export function atomicWriteFile(filePath: string, content: string, expectedHash?
312
352
  } catch (error) {
313
353
  if (process.platform === "win32" && (error as NodeJS.ErrnoException).code === "EPERM") {
314
354
  // mkdir hit a short/long-name alias wall — retry with the canonical
315
- // form. The write itself still targets the original filePath below.
355
+ // form. The write itself still targets the original filePath below.
316
356
  const realDir = canonicalize(dirPath);
317
357
  if (realDir !== dirPath) fs.mkdirSync(realDir, { recursive: true });
318
358
  } else {
@@ -332,6 +372,16 @@ export function atomicWriteFile(filePath: string, content: string, expectedHash?
332
372
  throw new Error(`Refusing to write: opened path is not a regular file: ${tempPath}`);
333
373
  }
334
374
  fs.writeSync(fd, content, undefined, "utf-8");
375
+ // FIX (2026-07-01 mailbox-replay CI flake investigation): fsync the data
376
+ // before close. Without this, writeSync puts content in page cache but
377
+ // the data may not be flushed to disk before the subsequent rename.
378
+ // On shared CI runners (Ubuntu, high I/O pressure), a subsequent
379
+ // readFileSync could see the OLD content if the page cache evicted
380
+ // between write and read. fsyncSync forces the data to disk so the
381
+ // rename + post-rename read always sees the new content. Cost: ~1ms
382
+ // per write (acceptable for the small delivery.json / manifest.json
383
+ // files this function writes; large state-store uses the async path).
384
+ fs.fsyncSync(fd);
335
385
  fs.closeSync(fd);
336
386
  try {
337
387
  // Issue 1 fix: re-check symlink safety immediately before rename.
@@ -346,6 +396,23 @@ export function atomicWriteFile(filePath: string, content: string, expectedHash?
346
396
  }
347
397
  // Issue 1 fix: use link+unlink instead of rename to avoid following symlinks
348
398
  renameWithLinkSync(tempPath, filePath);
399
+ // FIX (2026-07-01 mailbox-replay CI flake): fsync the parent directory
400
+ // after rename so the directory entry update is durable. On most Linux
401
+ // filesystems, rename is journaled but the journal flush isn't always
402
+ // synchronous; without this, a subsequent open() after rename can
403
+ // race and see ENOENT or stale content on high-IO CI runners.
404
+ // We skip on Windows where directory fsync semantics differ (and
405
+ // where the Windows-specific rename path in renameWithLinkSync already
406
+ // uses MoveFileEx which is fully durable).
407
+ if (process.platform !== "win32") {
408
+ try {
409
+ const dirFd = fs.openSync(path.dirname(filePath), "r");
410
+ fs.fsyncSync(dirFd);
411
+ fs.closeSync(dirFd);
412
+ } catch {
413
+ /* best-effort — not all filesystems support directory fsync */
414
+ }
415
+ }
349
416
  } catch (renameError) {
350
417
  // Issue 4 fix: use finally block to guarantee temp file cleanup.
351
418
  // Between the initial isSymlinkSafePath check and rename attempt,
@@ -374,12 +441,15 @@ export function atomicWriteFile(filePath: string, content: string, expectedHash?
374
441
  // Issue 4 fix: always clean up temp file, regardless of success or error path.
375
442
  // This ensures no orphaned temp files remain when errors occur at any point.
376
443
  if (fd !== undefined) {
377
- try { fs.rmSync(tempPath, { force: true }); } catch { /* best-effort */ }
444
+ try {
445
+ fs.rmSync(tempPath, { force: true });
446
+ } catch {
447
+ /* best-effort */
448
+ }
378
449
  }
379
450
  }
380
451
  }
381
452
 
382
-
383
453
  export async function atomicWriteFileAsync(filePath: string, content: string): Promise<void> {
384
454
  // Phase 1.5 (RFC 15): when the worker-thread atomic writer is enabled
385
455
  // (PI_CREW_WORKER_ATOMIC_WRITER=1), dispatch to a dedicated worker thread
@@ -412,7 +482,11 @@ export async function atomicWriteFileAsync(filePath: string, content: string): P
412
482
  // The post-rename lstat check only runs on rename failure, so we must
413
483
  // check BEFORE the rename to catch this TOCTOU race.
414
484
  if (!isSymlinkSafePath(filePath)) {
415
- try { await fs.promises.rm(tempPath, { force: true }); } catch { /* best-effort */ }
485
+ try {
486
+ await fs.promises.rm(tempPath, { force: true });
487
+ } catch {
488
+ /* best-effort */
489
+ }
416
490
  throw new Error(`Refusing to rename: target became a symlink or inside untrusted directory: ${filePath}`);
417
491
  }
418
492
  // Issue 1 fix: use link+unlink instead of rename to avoid following symlinks
@@ -445,7 +519,6 @@ export async function atomicWriteFileAsync(filePath: string, content: string): P
445
519
  }
446
520
  }
447
521
 
448
-
449
522
  export function atomicWriteJson<T>(filePath: string, value: T): void {
450
523
  atomicWriteFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
451
524
  }
@@ -500,7 +573,13 @@ export function atomicWriteJsonCoalesced<T>(filePath: string, value: T, coalesce
500
573
  timer.unref();
501
574
  // Issue 2 fix: increment generation for each new entry
502
575
  const generation = ++writeGeneration;
503
- pendingAtomicWrites.set(filePath, { content, timer, coalesceMs, retryCount: 0, generation });
576
+ pendingAtomicWrites.set(filePath, {
577
+ content,
578
+ timer,
579
+ coalesceMs,
580
+ retryCount: 0,
581
+ generation,
582
+ });
504
583
  }
505
584
 
506
585
  function flushOnePendingAtomicWrite(filePath: string): void {
@@ -536,7 +615,7 @@ function flushOnePendingAtomicWrite(filePath: string): void {
536
615
  throw error;
537
616
  }
538
617
  // Exponential backoff: base delay * 2^(retryCount-1), capped at 30 seconds
539
- const backoffMs = Math.min(30000, current.coalesceMs * Math.pow(2, current.retryCount - 1));
618
+ const backoffMs = Math.min(30000, current.coalesceMs * 2 ** (current.retryCount - 1));
540
619
  const timer = setTimeout(() => flushOnePendingAtomicWrite(filePath), backoffMs);
541
620
  timer.unref();
542
621
  current.timer = timer;
@@ -1,10 +1,10 @@
1
+ import { createHash, randomUUID } from "node:crypto";
1
2
  import * as fs from "node:fs";
2
3
  import * as path from "node:path";
3
- import { createHash, randomUUID } from "node:crypto";
4
4
  import { resolveRealContainedPath } from "../utils/safe-paths.ts";
5
+ import { sleepSync } from "../utils/sleep.ts";
5
6
  import { atomicWriteFile, renameWithRetry } from "./atomic-write.ts";
6
7
  import { withFileLockSync } from "./locks.ts";
7
- import { sleepSync } from "../utils/sleep.ts";
8
8
 
9
9
  const SHA256_HEX = /^[a-f0-9]{64}$/i;
10
10
 
@@ -32,11 +32,19 @@ function atomicWriteBuffer(filePath: string, content: Buffer): void {
32
32
  fs.writeSync(fd, content, 0, content.length);
33
33
  renameWithRetry(tempPath, filePath);
34
34
  } catch (error) {
35
- try { fs.rmSync(tempPath, { force: true }); } catch { /* best-effort */ }
35
+ try {
36
+ fs.rmSync(tempPath, { force: true });
37
+ } catch {
38
+ /* best-effort */
39
+ }
36
40
  throw error;
37
41
  } finally {
38
42
  // Always close fd; closeSync is safe to call even if fd was already closed
39
- try { fs.closeSync(fd); } catch { /* best-effort */ }
43
+ try {
44
+ fs.closeSync(fd);
45
+ } catch {
46
+ /* best-effort */
47
+ }
40
48
  }
41
49
  }
42
50
 
@@ -75,7 +83,9 @@ export interface BlobWriteResult {
75
83
  }
76
84
 
77
85
  function sha256Of(content: string | Buffer): string {
78
- return createHash("sha256").update(typeof content === "string" ? content : content).digest("hex");
86
+ return createHash("sha256")
87
+ .update(typeof content === "string" ? content : content)
88
+ .digest("hex");
79
89
  }
80
90
 
81
91
  /**
@@ -85,16 +95,19 @@ function sha256Of(content: string | Buffer): string {
85
95
  * partial writes on crash. The deduplication check is now advisory (the atomic
86
96
  * write handles concurrent writes correctly via O_EXCL temp file pattern).
87
97
  */
88
- export function writeBlob(artifactsRoot: string, input: {
89
- content: string | Buffer;
90
- runId: string;
91
- taskId?: string;
92
- mime?: string;
93
- producer: string;
94
- originalPath: string;
95
- redacted?: boolean;
96
- retention?: BlobMetadata["retention"];
97
- }): BlobWriteResult {
98
+ export function writeBlob(
99
+ artifactsRoot: string,
100
+ input: {
101
+ content: string | Buffer;
102
+ runId: string;
103
+ taskId?: string;
104
+ mime?: string;
105
+ producer: string;
106
+ originalPath: string;
107
+ redacted?: boolean;
108
+ retention?: BlobMetadata["retention"];
109
+ },
110
+ ): BlobWriteResult {
98
111
  const content = input.content;
99
112
  const hash = sha256Of(content);
100
113
  const algorithm = SHA256_PREFIX;
@@ -165,11 +178,15 @@ export function writeBlob(artifactsRoot: string, input: {
165
178
  // taskId, the conflict check passes and both writes succeed — the
166
179
  // final taskId reflects whichever write completed last.
167
180
  // Callers should not rely on taskId for correctness-critical decisions.
168
- if (existingMeta.mime !== metadata.mime ||
181
+ if (
182
+ existingMeta.mime !== metadata.mime ||
169
183
  existingMeta.retention !== metadata.retention ||
170
184
  existingMeta.producer !== metadata.producer ||
171
- existingMeta.originalPath !== metadata.originalPath) {
172
- throw new Error(`Concurrent metadata write conflict for blob ${hash}: different metadata values detected. Existing: ${JSON.stringify(existingMeta)}, New: ${JSON.stringify(metadata)}`);
185
+ existingMeta.originalPath !== metadata.originalPath
186
+ ) {
187
+ throw new Error(
188
+ `Concurrent metadata write conflict for blob ${hash}: different metadata values detected. Existing: ${JSON.stringify(existingMeta)}, New: ${JSON.stringify(metadata)}`,
189
+ );
173
190
  }
174
191
  } catch (err) {
175
192
  if (err instanceof Error && err.message.includes("ENOENT")) {
@@ -203,7 +220,11 @@ export function writeBlob(artifactsRoot: string, input: {
203
220
  // file from a failed content write) — which is the only unambiguously-safe
204
221
  // case to clean up here.
205
222
  if (!blobContentWritten) {
206
- try { fs.rmSync(blobPath, { force: true }); } catch { /* best-effort */ }
223
+ try {
224
+ fs.rmSync(blobPath, { force: true });
225
+ } catch {
226
+ /* best-effort */
227
+ }
207
228
  }
208
229
  throw error;
209
230
  }
@@ -211,7 +232,13 @@ export function writeBlob(artifactsRoot: string, input: {
211
232
  // Issue 2 fix: resolve paths before writes and return cached values
212
233
  const resolvedBlobPath = resolveRealContainedPath(artifactsRoot, blobPath);
213
234
  const resolvedMetadataPath = resolveRealContainedPath(artifactsRoot, metadataPath);
214
- return { hash, algorithm, blobPath: resolvedBlobPath, metadataPath: resolvedMetadataPath, sizeBytes: metadata.sizeBytes };
235
+ return {
236
+ hash,
237
+ algorithm,
238
+ blobPath: resolvedBlobPath,
239
+ metadataPath: resolvedMetadataPath,
240
+ sizeBytes: metadata.sizeBytes,
241
+ };
215
242
  }
216
243
 
217
244
  /**
@@ -1,11 +1,26 @@
1
1
  export const TEAM_RUN_STATUSES = ["queued", "planning", "running", "blocked", "completed", "failed", "cancelled"] as const;
2
- export type TeamRunStatus = typeof TEAM_RUN_STATUSES[number];
2
+ export type TeamRunStatus = (typeof TEAM_RUN_STATUSES)[number];
3
3
 
4
- export const TEAM_TASK_STATUSES = ["queued", "running", "waiting", "completed", "failed", "cancelled", "skipped", "needs_attention"] as const;
5
- export type TeamTaskStatus = typeof TEAM_TASK_STATUSES[number];
4
+ export const TEAM_TASK_STATUSES = [
5
+ "queued",
6
+ "running",
7
+ "waiting",
8
+ "completed",
9
+ "failed",
10
+ "cancelled",
11
+ "skipped",
12
+ "needs_attention",
13
+ ] as const;
14
+ export type TeamTaskStatus = (typeof TEAM_TASK_STATUSES)[number];
6
15
 
7
16
  export const TEAM_TERMINAL_RUN_STATUSES: ReadonlySet<TeamRunStatus> = new Set(["blocked", "completed", "failed", "cancelled"]);
8
- export const TEAM_TERMINAL_TASK_STATUSES: ReadonlySet<TeamTaskStatus> = new Set(["completed", "failed", "cancelled", "skipped", "needs_attention"]);
17
+ export const TEAM_TERMINAL_TASK_STATUSES: ReadonlySet<TeamTaskStatus> = new Set([
18
+ "completed",
19
+ "failed",
20
+ "cancelled",
21
+ "skipped",
22
+ "needs_attention",
23
+ ]);
9
24
 
10
25
  export const TEAM_RUN_STATUS_TRANSITIONS: Readonly<Record<TeamRunStatus, readonly TeamRunStatus[]>> = {
11
26
  queued: ["planning", "running", "cancelled", "failed"],
@@ -93,7 +108,7 @@ const TEAM_EVENT_TYPES = [
93
108
  "dwf.failed",
94
109
  "dwf.log",
95
110
  ] as const;
96
- export type TeamEventType = typeof TEAM_EVENT_TYPES[number];
111
+ export type TeamEventType = (typeof TEAM_EVENT_TYPES)[number];
97
112
 
98
113
  export const TEAM_WAKEABLE_EVENT_TYPES: ReadonlySet<TeamEventType> = new Set([
99
114
  "run.blocked",
@@ -89,10 +89,7 @@ function parseRoot(start: string): string {
89
89
  const rest = start.slice(2);
90
90
  const firstSep = Math.max(rest.indexOf("\\"), rest.indexOf("/"));
91
91
  if (firstSep === -1) return start;
92
- const secondSep = Math.max(
93
- rest.indexOf("\\", firstSep + 1),
94
- rest.indexOf("/", firstSep + 1),
95
- );
92
+ const secondSep = Math.max(rest.indexOf("\\", firstSep + 1), rest.indexOf("/", firstSep + 1));
96
93
  if (secondSep === -1) return start;
97
94
  // secondSep is an index into `rest`; add 2 to map back to `start`.
98
95
  return start.slice(0, 2 + secondSep);
@@ -136,17 +133,11 @@ function safeJoin(...parts: string[]): string {
136
133
  }
137
134
  // Strip the leading separator(s) from the first part before joining, so
138
135
  // the collapse regex doesn't re-collapse them.
139
- const firstPartStripped =
140
- sep === "\\"
141
- ? firstPart.replace(/^\\{1,2}/, "")
142
- : firstPart.replace(/^\/+/, "");
136
+ const firstPartStripped = sep === "\\" ? firstPart.replace(/^\\{1,2}/, "") : firstPart.replace(/^\/+/, "");
143
137
  const rest = filtered.slice(1);
144
138
  const joined = [firstPartStripped, ...rest].filter(Boolean).join(sep);
145
139
  // Collapse internal runs of the separator.
146
- const collapsed = joined.replace(
147
- new RegExp(`${sep === "\\" ? "\\\\" : "/"}{2,}`, "g"),
148
- sep,
149
- );
140
+ const collapsed = joined.replace(new RegExp(`${sep === "\\" ? "\\\\" : "/"}{2,}`, "g"), sep);
150
141
  return leading + collapsed;
151
142
  }
152
143
 
@@ -171,17 +162,9 @@ function safeResolve(p: string, pathDep?: typeof path): string {
171
162
  return p;
172
163
  }
173
164
 
174
- function findProjectRoot(
175
- start: string,
176
- pathDep?: typeof path,
177
- ): string | undefined {
165
+ function findProjectRoot(start: string, pathDep?: typeof path): string | undefined {
178
166
  const dirMarkers = [".git", ".hg", ".svn"];
179
- const fileMarkers = [
180
- "package.json",
181
- "pyproject.toml",
182
- "Cargo.toml",
183
- "go.mod",
184
- ];
167
+ const fileMarkers = ["package.json", "pyproject.toml", "Cargo.toml", "go.mod"];
185
168
  // Use `parseRoot` (inlined above) to avoid `path.parse` for the critical
186
169
  // termination root — fixes the jiti namespace race in issue #28.
187
170
  const root = parseRoot(start);
@@ -30,24 +30,15 @@ export interface RolloutEntry {
30
30
  * Uses projectCrewRoot() to honour the `.pi/teams/` fallback for `.pi`-based
31
31
  * projects (see issue #29).
32
32
  */
33
- function getLedgerPath(
34
- runId: string,
35
- stateRoot?: string,
36
- cwd?: string,
37
- ): string {
38
- const base =
39
- stateRoot ??
40
- join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
33
+ function getLedgerPath(runId: string, stateRoot?: string, cwd?: string): string {
34
+ const base = stateRoot ?? join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
41
35
  return `${base}/decision-ledger.jsonl`;
42
36
  }
43
37
 
44
38
  /**
45
39
  * Compute coherence marks based on existing ledger entries.
46
40
  */
47
- function computeCoherence(
48
- entry: RolloutEntry,
49
- ledger: RolloutEntry[],
50
- ): CoherenceMark {
41
+ function computeCoherence(entry: RolloutEntry, ledger: RolloutEntry[]): CoherenceMark {
51
42
  if (ledger.length === 0) {
52
43
  return {
53
44
  matchesPrior: false,
@@ -59,22 +50,15 @@ function computeCoherence(
59
50
 
60
51
  const previousEntry = ledger[ledger.length - 1];
61
52
  const matchesPrior: boolean =
62
- entry.decisionMark === previousEntry.decisionMark ||
63
- Boolean(
64
- entry.priorWinner &&
65
- entry.topCandidates.includes(entry.priorWinner),
66
- );
53
+ entry.decisionMark === previousEntry.decisionMark || Boolean(entry.priorWinner && entry.topCandidates.includes(entry.priorWinner));
67
54
 
68
55
  // Check last 10 entries for recursive pattern
69
56
  const recentEntries = ledger.slice(-10);
70
57
  const recentDecisions = recentEntries.map((e) => e.decisionMark);
71
58
  const currentDecision = entry.decisionMark;
72
59
 
73
- const recursiveMatches = recentDecisions.filter(
74
- (d) => d === currentDecision,
75
- ).length;
76
- const matchesRecursive =
77
- recursiveMatches >= Math.ceil(recentDecisions.length / 2); // At least half match
60
+ const recursiveMatches = recentDecisions.filter((d) => d === currentDecision).length;
61
+ const matchesRecursive = recursiveMatches >= Math.ceil(recentDecisions.length / 2); // At least half match
78
62
 
79
63
  const promotionAllowed = matchesPrior || matchesRecursive;
80
64
 
@@ -146,10 +130,7 @@ export function appendEntry(runId: string, entry: RolloutEntry): RolloutEntry {
146
130
  // Append to JSONL file using atomic write to prevent corruption
147
131
  // Use the already-loaded ledger content (no double-read)
148
132
  const line = JSON.stringify(entryWithCoherence) + "\n";
149
- const existingContent =
150
- ledger.length > 0
151
- ? ledger.map((e) => JSON.stringify(e)).join("\n") + "\n"
152
- : "";
133
+ const existingContent = ledger.length > 0 ? ledger.map((e) => JSON.stringify(e)).join("\n") + "\n" : "";
153
134
  atomicWriteFile(ledgerPath, existingContent + line);
154
135
  });
155
136
 
@@ -201,15 +182,7 @@ export function summarizeLedger(runId: string): string {
201
182
  return "# Decision Ledger Summary\n\n*No entries recorded yet.*";
202
183
  }
203
184
 
204
- const lines: string[] = [
205
- "# Decision Ledger Summary",
206
- "",
207
- `Run ID: ${runId}`,
208
- `Total Entries: ${ledger.length}`,
209
- "",
210
- "## Entries",
211
- "",
212
- ];
185
+ const lines: string[] = ["# Decision Ledger Summary", "", `Run ID: ${runId}`, `Total Entries: ${ledger.length}`, "", "## Entries", ""];
213
186
 
214
187
  for (let i = 0; i < ledger.length; i++) {
215
188
  const entry = ledger[i];
@@ -224,20 +197,12 @@ export function summarizeLedger(runId: string): string {
224
197
  lines.push(`- **Prior Winner**: ${entry.priorWinner}`);
225
198
  }
226
199
 
227
- lines.push(
228
- `- **Top Candidates**: ${entry.topCandidates.join(", ") || "(none)"}`,
229
- );
200
+ lines.push(`- **Top Candidates**: ${entry.topCandidates.join(", ") || "(none)"}`);
230
201
  lines.push("");
231
202
  lines.push("#### Coherence");
232
- lines.push(
233
- `- **Matches Prior**: ${entry.coherenceMark.matchesPrior ? "✓" : "✗"}`,
234
- );
235
- lines.push(
236
- `- **Matches Recursive**: ${entry.coherenceMark.matchesRecursive ? "✓" : "✗"}`,
237
- );
238
- lines.push(
239
- `- **Promotion Allowed**: ${entry.coherenceMark.promotionAllowed ? "✓" : "✗"}`,
240
- );
203
+ lines.push(`- **Matches Prior**: ${entry.coherenceMark.matchesPrior ? "✓" : "✗"}`);
204
+ lines.push(`- **Matches Recursive**: ${entry.coherenceMark.matchesRecursive ? "✓" : "✗"}`);
205
+ lines.push(`- **Promotion Allowed**: ${entry.coherenceMark.promotionAllowed ? "✓" : "✗"}`);
241
206
  lines.push(`- **Reason**: ${entry.coherenceMark.reason}`);
242
207
  lines.push("");
243
208
  }
@@ -259,12 +224,8 @@ export function summarizeLedger(runId: string): string {
259
224
  lines.push(`| Decay | ${decayCount} |`);
260
225
  lines.push("");
261
226
 
262
- const promotedCount = ledger.filter(
263
- (e) => e.coherenceMark.promotionAllowed,
264
- ).length;
265
- lines.push(
266
- `**Promotion Rate**: ${promotedCount}/${ledger.length} (${((promotedCount / ledger.length) * 100).toFixed(1)}%)`,
267
- );
227
+ const promotedCount = ledger.filter((e) => e.coherenceMark.promotionAllowed).length;
228
+ lines.push(`**Promotion Rate**: ${promotedCount}/${ledger.length} (${((promotedCount / ledger.length) * 100).toFixed(1)}%)`);
268
229
 
269
230
  return lines.join("\n");
270
231
  }
@@ -282,10 +243,7 @@ export function summarizeLedger(runId: string): string {
282
243
  * FIX: Wrap read+write in file lock to prevent concurrent promotion/decay
283
244
  * calls from losing entries.
284
245
  */
285
- export function promoteCandidate(
286
- runId: string,
287
- candidate: string,
288
- ): RolloutEntry {
246
+ export function promoteCandidate(runId: string, candidate: string): RolloutEntry {
289
247
  assertSafePathId("runId", runId);
290
248
  assertSafePathId("candidate", candidate);
291
249
 
@@ -314,10 +272,7 @@ export function promoteCandidate(
314
272
  };
315
273
 
316
274
  // Compute coherence (empty ledger = no matches)
317
- const coherenceMark = computeCoherence(
318
- entryWithoutCoherence as RolloutEntry,
319
- ledger,
320
- );
275
+ const coherenceMark = computeCoherence(entryWithoutCoherence as RolloutEntry, ledger);
321
276
 
322
277
  // Manual promotion always allows further promotion
323
278
  coherenceMark.promotionAllowed = true;
@@ -330,10 +285,7 @@ export function promoteCandidate(
330
285
  ledger.push(entry);
331
286
 
332
287
  // Rewrite entire ledger atomically to preserve all entries
333
- atomicWriteFile(
334
- ledgerPath,
335
- ledger.map((e) => JSON.stringify(e)).join("\n") + "\n",
336
- );
288
+ atomicWriteFile(ledgerPath, ledger.map((e) => JSON.stringify(e)).join("\n") + "\n");
337
289
  });
338
290
 
339
291
  return entry!;
@@ -373,10 +325,7 @@ export function decayCandidate(runId: string, candidate: string): RolloutEntry {
373
325
  };
374
326
 
375
327
  // Compute coherence (empty ledger = no matches)
376
- const coherenceMark = computeCoherence(
377
- entryWithoutCoherence as RolloutEntry,
378
- ledger,
379
- );
328
+ const coherenceMark = computeCoherence(entryWithoutCoherence as RolloutEntry, ledger);
380
329
 
381
330
  // Manual decay never allows promotion
382
331
  coherenceMark.promotionAllowed = false;
@@ -389,10 +338,7 @@ export function decayCandidate(runId: string, candidate: string): RolloutEntry {
389
338
  ledger.push(entry);
390
339
 
391
340
  // Rewrite entire ledger to preserve all entries
392
- atomicWriteFile(
393
- ledgerPath,
394
- ledger.map((e) => JSON.stringify(e)).join("\n") + "\n",
395
- );
341
+ atomicWriteFile(ledgerPath, ledger.map((e) => JSON.stringify(e)).join("\n") + "\n");
396
342
  });
397
343
 
398
344
  return entry!;