pi-crew 0.9.53 → 0.9.55

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 (493) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/package.json +6 -3
  3. package/scripts/clean-strip-types.mjs +60 -0
  4. package/src/adapters/claude-adapter.ts +23 -0
  5. package/src/adapters/codex-adapter.ts +21 -0
  6. package/src/adapters/cursor-adapter.ts +17 -0
  7. package/src/adapters/export-util.ts +143 -0
  8. package/src/adapters/index.ts +15 -0
  9. package/src/adapters/registry.ts +18 -0
  10. package/src/adapters/types.ts +23 -0
  11. package/src/agents/agent-config.ts +194 -0
  12. package/src/agents/agent-search.ts +98 -0
  13. package/src/agents/agent-serializer.ts +38 -0
  14. package/src/agents/discover-agents.ts +623 -0
  15. package/src/benchmark/benchmark-runner.ts +313 -0
  16. package/src/benchmark/feedback-loop.ts +73 -0
  17. package/src/config/config.ts +1270 -0
  18. package/src/config/defaults.ts +193 -0
  19. package/src/config/drift-detector.ts +290 -0
  20. package/src/config/markers.ts +330 -0
  21. package/src/config/resilient-parser.ts +117 -0
  22. package/src/config/role-tools.ts +118 -0
  23. package/src/config/suggestions.ts +75 -0
  24. package/src/config/types.ts +280 -0
  25. package/src/errors.ts +194 -0
  26. package/src/extension/action-suggestions.ts +117 -0
  27. package/src/extension/async-notifier.ts +179 -0
  28. package/src/extension/autonomous-policy.ts +209 -0
  29. package/src/extension/command-completions.ts +120 -0
  30. package/src/extension/context-status-injection.ts +185 -0
  31. package/src/extension/crew-autocomplete.ts +131 -0
  32. package/src/extension/crew-cleanup.ts +168 -0
  33. package/src/extension/crew-input-router.ts +93 -0
  34. package/src/extension/crew-shortcuts.ts +87 -0
  35. package/src/extension/crew-vibes/cat-frames.ts +18 -0
  36. package/src/extension/crew-vibes/config.ts +195 -0
  37. package/src/extension/crew-vibes/figures.ts +101 -0
  38. package/src/extension/crew-vibes/font-detect.ts +71 -0
  39. package/src/extension/crew-vibes/footer.ts +292 -0
  40. package/src/extension/crew-vibes/index.ts +432 -0
  41. package/src/extension/crew-vibes/provider-usage.ts +396 -0
  42. package/src/extension/crew-vibes/render.ts +206 -0
  43. package/src/extension/crew-vibes/speed.ts +286 -0
  44. package/src/extension/cross-extension-rpc.ts +350 -0
  45. package/src/extension/help.ts +62 -0
  46. package/src/extension/import-index.ts +74 -0
  47. package/src/extension/knowledge-injection.ts +402 -0
  48. package/src/extension/management.ts +571 -0
  49. package/src/extension/message-renderers.ts +108 -0
  50. package/src/extension/notification-router.ts +152 -0
  51. package/src/extension/notification-sink.ts +54 -0
  52. package/src/extension/pi-api.ts +56 -0
  53. package/src/extension/plan-orchestrate.ts +302 -0
  54. package/src/extension/project-init.ts +170 -0
  55. package/src/extension/register.ts +113 -0
  56. package/src/extension/registration/artifact-cleanup.ts +20 -0
  57. package/src/extension/registration/command-registration.ts +58 -0
  58. package/src/extension/registration/command-utils.ts +58 -0
  59. package/src/extension/registration/commands.ts +1228 -0
  60. package/src/extension/registration/compaction-guard.ts +333 -0
  61. package/src/extension/registration/context-builder.ts +135 -0
  62. package/src/extension/registration/crash-recovery-cache.ts +54 -0
  63. package/src/extension/registration/foreground-run-controller.ts +238 -0
  64. package/src/extension/registration/hook-registration.ts +119 -0
  65. package/src/extension/registration/lazy-configurers.ts +124 -0
  66. package/src/extension/registration/lifecycle-handlers.ts +923 -0
  67. package/src/extension/registration/lifecycle.ts +259 -0
  68. package/src/extension/registration/observability.ts +322 -0
  69. package/src/extension/registration/registration-types.ts +158 -0
  70. package/src/extension/registration/runtime-cleanup.ts +230 -0
  71. package/src/extension/registration/subagent-helpers.ts +125 -0
  72. package/src/extension/registration/subagent-manager-setup.ts +330 -0
  73. package/src/extension/registration/subagent-tools.ts +456 -0
  74. package/src/extension/registration/team-tool.ts +225 -0
  75. package/src/extension/registration/tool-registration.ts +50 -0
  76. package/src/extension/registration/ui.ts +172 -0
  77. package/src/extension/registration/viewers.ts +121 -0
  78. package/src/extension/registration/wire-cross-extension.ts +37 -0
  79. package/src/extension/result-watcher.ts +139 -0
  80. package/src/extension/rpc-hmac.ts +256 -0
  81. package/src/extension/run-bundle-schema.ts +104 -0
  82. package/src/extension/run-export.ts +97 -0
  83. package/src/extension/run-import.ts +168 -0
  84. package/src/extension/run-index.ts +131 -0
  85. package/src/extension/run-maintenance.ts +190 -0
  86. package/src/extension/session-summary.ts +18 -0
  87. package/src/extension/team-manager-command.ts +153 -0
  88. package/src/extension/team-onboard.ts +180 -0
  89. package/src/extension/team-recommendation.ts +288 -0
  90. package/src/extension/team-tool/anchor.ts +172 -0
  91. package/src/extension/team-tool/api.ts +1244 -0
  92. package/src/extension/team-tool/auto-summarize.ts +142 -0
  93. package/src/extension/team-tool/cache-control.ts +19 -0
  94. package/src/extension/team-tool/cancel.ts +337 -0
  95. package/src/extension/team-tool/chain-dispatch.ts +95 -0
  96. package/src/extension/team-tool/chain-executor.ts +379 -0
  97. package/src/extension/team-tool/config-patch.ts +50 -0
  98. package/src/extension/team-tool/context.ts +149 -0
  99. package/src/extension/team-tool/destructive-gate.ts +52 -0
  100. package/src/extension/team-tool/dispatch/automate.ts +80 -0
  101. package/src/extension/team-tool/dispatch/control.ts +42 -0
  102. package/src/extension/team-tool/dispatch/index.ts +95 -0
  103. package/src/extension/team-tool/dispatch/manage.ts +161 -0
  104. package/src/extension/team-tool/dispatch/run.ts +53 -0
  105. package/src/extension/team-tool/dispatch/status.ts +187 -0
  106. package/src/extension/team-tool/doctor.ts +431 -0
  107. package/src/extension/team-tool/explain.ts +282 -0
  108. package/src/extension/team-tool/failure-patterns.ts +118 -0
  109. package/src/extension/team-tool/goal-wrap.ts +330 -0
  110. package/src/extension/team-tool/goal.ts +556 -0
  111. package/src/extension/team-tool/handle-schedule.ts +332 -0
  112. package/src/extension/team-tool/handle-settings.ts +520 -0
  113. package/src/extension/team-tool/health-monitor.ts +481 -0
  114. package/src/extension/team-tool/inspect.ts +88 -0
  115. package/src/extension/team-tool/intent-policy.ts +45 -0
  116. package/src/extension/team-tool/lifecycle-actions.ts +615 -0
  117. package/src/extension/team-tool/orchestrate.ts +98 -0
  118. package/src/extension/team-tool/parallel-dispatch.ts +183 -0
  119. package/src/extension/team-tool/plan.ts +50 -0
  120. package/src/extension/team-tool/respond.ts +150 -0
  121. package/src/extension/team-tool/run-deadline.ts +66 -0
  122. package/src/extension/team-tool/run-not-found.ts +49 -0
  123. package/src/extension/team-tool/run.ts +1051 -0
  124. package/src/extension/team-tool/status.ts +260 -0
  125. package/src/extension/team-tool/workflow-manage.ts +261 -0
  126. package/src/extension/team-tool-types.ts +26 -0
  127. package/src/extension/team-tool.ts +750 -0
  128. package/src/extension/tool-result.ts +16 -0
  129. package/src/extension/validate-resources.ts +108 -0
  130. package/src/hooks/registry.ts +200 -0
  131. package/src/hooks/types.ts +69 -0
  132. package/src/i18n.ts +212 -0
  133. package/src/observability/correlation.ts +50 -0
  134. package/src/observability/event-bus.ts +86 -0
  135. package/src/observability/event-to-metric.ts +170 -0
  136. package/src/observability/exporters/adapter.ts +27 -0
  137. package/src/observability/exporters/otlp-exporter.ts +356 -0
  138. package/src/observability/exporters/prometheus-exporter.ts +54 -0
  139. package/src/observability/metric-registry.ts +100 -0
  140. package/src/observability/metric-retention.ts +64 -0
  141. package/src/observability/metric-sink.ts +99 -0
  142. package/src/observability/metrics-primitives.ts +219 -0
  143. package/src/plugins/plugin-define.ts +6 -0
  144. package/src/plugins/plugin-registry.ts +32 -0
  145. package/src/plugins/plugins/index.ts +3 -0
  146. package/src/plugins/plugins/nextjs.ts +19 -0
  147. package/src/plugins/plugins/vite.ts +10 -0
  148. package/src/plugins/plugins/vitest.ts +9 -0
  149. package/src/prompt/prompt-runtime.ts +380 -0
  150. package/src/runtime/adaptive-plan.ts +563 -0
  151. package/src/runtime/agent-control.ts +215 -0
  152. package/src/runtime/agent-memory.ts +81 -0
  153. package/src/runtime/agent-observability.ts +122 -0
  154. package/src/runtime/anchor-manager.ts +475 -0
  155. package/src/runtime/async-marker.ts +32 -0
  156. package/src/runtime/async-runner.ts +381 -0
  157. package/src/runtime/attention-events.ts +28 -0
  158. package/src/runtime/auto-summarize.ts +344 -0
  159. package/src/runtime/background-runner.ts +840 -0
  160. package/src/runtime/batch-barrier.ts +147 -0
  161. package/src/runtime/broker-issuer.ts +39 -0
  162. package/src/runtime/cancellation-token.ts +99 -0
  163. package/src/runtime/cancellation.ts +99 -0
  164. package/src/runtime/capability-inventory.ts +137 -0
  165. package/src/runtime/chain-parser.ts +237 -0
  166. package/src/runtime/chain-runner.ts +606 -0
  167. package/src/runtime/checkpoint.ts +291 -0
  168. package/src/runtime/child-pi-constants.ts +42 -0
  169. package/src/runtime/child-pi-kill.ts +180 -0
  170. package/src/runtime/child-pi-pool.ts +68 -0
  171. package/src/runtime/child-pi-spawn.ts +287 -0
  172. package/src/runtime/child-pi-steering.ts +128 -0
  173. package/src/runtime/child-pi-streams.ts +296 -0
  174. package/src/runtime/child-pi-transcript.ts +169 -0
  175. package/src/runtime/child-pi.ts +1105 -0
  176. package/src/runtime/coalesce-tasks.ts +268 -0
  177. package/src/runtime/code-summary.ts +292 -0
  178. package/src/runtime/command-trace.ts +105 -0
  179. package/src/runtime/compact-pipeline.ts +56 -0
  180. package/src/runtime/compact-stages/ansi-strip-stage.ts +25 -0
  181. package/src/runtime/compact-stages/blank-collapse-stage.ts +31 -0
  182. package/src/runtime/compact-stages/deduplicate-stage.ts +34 -0
  183. package/src/runtime/compact-stages/head-snap-stage.ts +57 -0
  184. package/src/runtime/compact-stages/index.ts +23 -0
  185. package/src/runtime/compact-stages/tail-capture-stage.ts +77 -0
  186. package/src/runtime/compact-stages/truncation-stage.ts +74 -0
  187. package/src/runtime/compaction-summary.ts +278 -0
  188. package/src/runtime/completion-guard.ts +207 -0
  189. package/src/runtime/concurrency.ts +58 -0
  190. package/src/runtime/crash-classification.ts +229 -0
  191. package/src/runtime/crash-recovery.ts +594 -0
  192. package/src/runtime/crew-agent-records.ts +595 -0
  193. package/src/runtime/crew-agent-runtime.ts +62 -0
  194. package/src/runtime/crew-broker-child.ts +88 -0
  195. package/src/runtime/crew-broker-client.ts +673 -0
  196. package/src/runtime/crew-broker-tokens.ts +159 -0
  197. package/src/runtime/crew-broker.ts +1304 -0
  198. package/src/runtime/crew-hooks.ts +227 -0
  199. package/src/runtime/cross-extension-rpc.ts +143 -0
  200. package/src/runtime/custom-tools/irc-tool.ts +282 -0
  201. package/src/runtime/custom-tools/submit-result-tool.ts +102 -0
  202. package/src/runtime/deadletter.ts +47 -0
  203. package/src/runtime/delivery-coordinator.ts +211 -0
  204. package/src/runtime/delta-conflict.ts +348 -0
  205. package/src/runtime/deterministic-ast.ts +161 -0
  206. package/src/runtime/diagnostic-export.ts +171 -0
  207. package/src/runtime/direct-run.ts +45 -0
  208. package/src/runtime/dwf-state-store.ts +102 -0
  209. package/src/runtime/dynamic-workflow-context.ts +1013 -0
  210. package/src/runtime/dynamic-workflow-runner.ts +325 -0
  211. package/src/runtime/effectiveness.ts +90 -0
  212. package/src/runtime/errors/crew-errors.ts +162 -0
  213. package/src/runtime/event-stream-bridge.ts +98 -0
  214. package/src/runtime/foreground-control.ts +193 -0
  215. package/src/runtime/foreground-watchdog.ts +132 -0
  216. package/src/runtime/global-worker-cap.ts +96 -0
  217. package/src/runtime/goal-achievement.ts +148 -0
  218. package/src/runtime/goal-evaluator.ts +365 -0
  219. package/src/runtime/goal-loop-runner.ts +825 -0
  220. package/src/runtime/goal-state-store.ts +219 -0
  221. package/src/runtime/green-contract.ts +55 -0
  222. package/src/runtime/group-join.ts +267 -0
  223. package/src/runtime/handoff-manager.ts +598 -0
  224. package/src/runtime/heartbeat-gradient.ts +36 -0
  225. package/src/runtime/heartbeat-watcher.ts +239 -0
  226. package/src/runtime/hidden-handoff.ts +407 -0
  227. package/src/runtime/important-line-classifier.ts +140 -0
  228. package/src/runtime/intercom-bridge.ts +187 -0
  229. package/src/runtime/iteration-hooks.ts +305 -0
  230. package/src/runtime/live-agent-control.ts +136 -0
  231. package/src/runtime/live-agent-manager.ts +704 -0
  232. package/src/runtime/live-control-realtime.ts +56 -0
  233. package/src/runtime/live-extension-bridge.ts +148 -0
  234. package/src/runtime/live-irc.ts +97 -0
  235. package/src/runtime/live-session-health.ts +108 -0
  236. package/src/runtime/live-session-runtime.ts +1146 -0
  237. package/src/runtime/loop-gates.ts +128 -0
  238. package/src/runtime/manifest-cache.ts +375 -0
  239. package/src/runtime/mcp-proxy.ts +105 -0
  240. package/src/runtime/metric-parser.ts +36 -0
  241. package/src/runtime/model-fallback.ts +409 -0
  242. package/src/runtime/model-resolver.ts +126 -0
  243. package/src/runtime/model-scope.ts +158 -0
  244. package/src/runtime/orphan-worker-registry.ts +465 -0
  245. package/src/runtime/output-validator.ts +202 -0
  246. package/src/runtime/overflow-recovery.ts +206 -0
  247. package/src/runtime/parallel-research.ts +68 -0
  248. package/src/runtime/parallel-utils.ts +160 -0
  249. package/src/runtime/parent-guard.ts +134 -0
  250. package/src/runtime/path-overlap.ts +150 -0
  251. package/src/runtime/peer-dep.ts +292 -0
  252. package/src/runtime/per-write-validator.ts +181 -0
  253. package/src/runtime/phase-progress.ts +217 -0
  254. package/src/runtime/phase-tracker.ts +385 -0
  255. package/src/runtime/pi-args.ts +655 -0
  256. package/src/runtime/pi-json-output.ts +170 -0
  257. package/src/runtime/pi-spawn.ts +273 -0
  258. package/src/runtime/pipeline-runner.ts +523 -0
  259. package/src/runtime/plan-templates.ts +201 -0
  260. package/src/runtime/policy-engine.ts +115 -0
  261. package/src/runtime/post-checks.ts +142 -0
  262. package/src/runtime/post-exit-stdio-guard.ts +109 -0
  263. package/src/runtime/process-lifecycle.ts +491 -0
  264. package/src/runtime/process-status.ts +154 -0
  265. package/src/runtime/progress-event-coalescer.ts +44 -0
  266. package/src/runtime/progress-tracker.ts +124 -0
  267. package/src/runtime/prose-compressor.ts +162 -0
  268. package/src/runtime/recovery-recipes.ts +203 -0
  269. package/src/runtime/replace.ts +570 -0
  270. package/src/runtime/resilient-edit.ts +153 -0
  271. package/src/runtime/result-extractor.ts +217 -0
  272. package/src/runtime/retry-executor.ts +109 -0
  273. package/src/runtime/retry-runner.ts +336 -0
  274. package/src/runtime/role-permission.ts +59 -0
  275. package/src/runtime/run-coalesced-task-group.ts +308 -0
  276. package/src/runtime/run-drift.ts +219 -0
  277. package/src/runtime/run-tracker.ts +116 -0
  278. package/src/runtime/run-worker.ts +78 -0
  279. package/src/runtime/runtime-policy.ts +29 -0
  280. package/src/runtime/runtime-resolver.ts +172 -0
  281. package/src/runtime/runtime-warmup.ts +160 -0
  282. package/src/runtime/scheduler.ts +353 -0
  283. package/src/runtime/semaphore.ts +141 -0
  284. package/src/runtime/sensitive-paths.ts +93 -0
  285. package/src/runtime/session-resources.ts +25 -0
  286. package/src/runtime/session-snapshot.ts +59 -0
  287. package/src/runtime/session-usage.ts +79 -0
  288. package/src/runtime/settings-store.ts +155 -0
  289. package/src/runtime/sidechain-output.ts +35 -0
  290. package/src/runtime/single-agent-compose.ts +84 -0
  291. package/src/runtime/skill-effectiveness.ts +524 -0
  292. package/src/runtime/skill-instructions.ts +394 -0
  293. package/src/runtime/stale-reconciler.ts +680 -0
  294. package/src/runtime/stream-preview.ts +184 -0
  295. package/src/runtime/streaming-output.ts +50 -0
  296. package/src/runtime/subagent-manager.ts +561 -0
  297. package/src/runtime/subprocess-tool-registry.ts +70 -0
  298. package/src/runtime/supervisor-contact.ts +64 -0
  299. package/src/runtime/task-display.ts +52 -0
  300. package/src/runtime/task-graph-scheduler.ts +227 -0
  301. package/src/runtime/task-graph.ts +290 -0
  302. package/src/runtime/task-health.ts +83 -0
  303. package/src/runtime/task-id.ts +161 -0
  304. package/src/runtime/task-output-context.ts +602 -0
  305. package/src/runtime/task-packet.ts +268 -0
  306. package/src/runtime/task-quality.ts +199 -0
  307. package/src/runtime/task-runner/capabilities.ts +78 -0
  308. package/src/runtime/task-runner/child-executor.ts +790 -0
  309. package/src/runtime/task-runner/context-retrieval.ts +163 -0
  310. package/src/runtime/task-runner/live-executor.ts +227 -0
  311. package/src/runtime/task-runner/output-splitter.ts +152 -0
  312. package/src/runtime/task-runner/post-execution.ts +480 -0
  313. package/src/runtime/task-runner/pre-execution.ts +365 -0
  314. package/src/runtime/task-runner/progress.ts +157 -0
  315. package/src/runtime/task-runner/prompt-builder.ts +278 -0
  316. package/src/runtime/task-runner/prompt-pipeline.ts +88 -0
  317. package/src/runtime/task-runner/result-utils.ts +16 -0
  318. package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
  319. package/src/runtime/task-runner/run-projection.ts +125 -0
  320. package/src/runtime/task-runner/scaffold-executor.ts +25 -0
  321. package/src/runtime/task-runner/state-helpers.ts +176 -0
  322. package/src/runtime/task-runner/tail-read.ts +34 -0
  323. package/src/runtime/task-runner.ts +216 -0
  324. package/src/runtime/team-runner-artifacts.ts +13 -0
  325. package/src/runtime/team-runner.ts +2396 -0
  326. package/src/runtime/tool-output-pruner.ts +333 -0
  327. package/src/runtime/tool-progress.ts +278 -0
  328. package/src/runtime/usage-tracker.ts +73 -0
  329. package/src/runtime/verification-gates.ts +579 -0
  330. package/src/runtime/verification-integrity.ts +108 -0
  331. package/src/runtime/verification-worktree.ts +186 -0
  332. package/src/runtime/worker-heartbeat.ts +25 -0
  333. package/src/runtime/worker-startup.ts +83 -0
  334. package/src/runtime/workflow-state.ts +195 -0
  335. package/src/runtime/workspace-lock.ts +443 -0
  336. package/src/runtime/workspace-tree.ts +312 -0
  337. package/src/runtime/yield-handler.ts +224 -0
  338. package/src/runtime/zombie-scanner.ts +298 -0
  339. package/src/schema/config-schema.ts +327 -0
  340. package/src/schema/team-tool-schema.ts +492 -0
  341. package/src/schema/validation-types.ts +159 -0
  342. package/src/skills/discover-skills.ts +203 -0
  343. package/src/skills/skill-templates.ts +456 -0
  344. package/src/skills/validate.ts +285 -0
  345. package/src/state/active-run-registry.ts +439 -0
  346. package/src/state/artifact-store.ts +176 -0
  347. package/src/state/atomic-write.ts +970 -0
  348. package/src/state/blob-store.ts +308 -0
  349. package/src/state/contracts.ts +160 -0
  350. package/src/state/crew-init.ts +269 -0
  351. package/src/state/decision-ledger.ts +372 -0
  352. package/src/state/event-log-rotation.ts +399 -0
  353. package/src/state/event-log.ts +1465 -0
  354. package/src/state/event-reconstructor.ts +229 -0
  355. package/src/state/gitignore-manager.ts +46 -0
  356. package/src/state/health-store.ts +79 -0
  357. package/src/state/hook-instinct-bridge.ts +94 -0
  358. package/src/state/hook-integrations.ts +51 -0
  359. package/src/state/instinct-store.ts +275 -0
  360. package/src/state/jsonl-writer.ts +107 -0
  361. package/src/state/locks.ts +562 -0
  362. package/src/state/mailbox.ts +916 -0
  363. package/src/state/observation-store.ts +176 -0
  364. package/src/state/run-cache.ts +193 -0
  365. package/src/state/run-graph.ts +183 -0
  366. package/src/state/run-metrics.ts +165 -0
  367. package/src/state/schedule.ts +166 -0
  368. package/src/state/session-state-map.ts +51 -0
  369. package/src/state/state-store.ts +973 -0
  370. package/src/state/task-claims.ts +61 -0
  371. package/src/state/tiered-eval.ts +480 -0
  372. package/src/state/types-eval.ts +58 -0
  373. package/src/state/types.ts +447 -0
  374. package/src/state/usage.ts +138 -0
  375. package/src/state/worker-atomic-writer.ts +198 -0
  376. package/src/subagents/async-entry.ts +1 -0
  377. package/src/subagents/index.ts +3 -0
  378. package/src/subagents/live/control.ts +1 -0
  379. package/src/subagents/live/manager.ts +1 -0
  380. package/src/subagents/live/realtime.ts +1 -0
  381. package/src/subagents/live/session-runtime.ts +1 -0
  382. package/src/subagents/manager.ts +1 -0
  383. package/src/subagents/spawn.ts +1 -0
  384. package/src/teams/discover-teams.ts +187 -0
  385. package/src/teams/team-config.ts +27 -0
  386. package/src/teams/team-serializer.ts +38 -0
  387. package/src/tools/safe-bash-extension.ts +54 -0
  388. package/src/tools/safe-bash.ts +505 -0
  389. package/src/types/diff.d.ts +18 -0
  390. package/src/types/new-api-types.ts +35 -0
  391. package/src/ui/agent-management-overlay.ts +160 -0
  392. package/src/ui/card-colors.ts +139 -0
  393. package/src/ui/crew-footer.ts +102 -0
  394. package/src/ui/crew-select-list.ts +112 -0
  395. package/src/ui/dashboard-panes/agents-pane.ts +164 -0
  396. package/src/ui/dashboard-panes/cancellation-pane.ts +66 -0
  397. package/src/ui/dashboard-panes/capability-pane.ts +77 -0
  398. package/src/ui/dashboard-panes/health-pane.ts +31 -0
  399. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -0
  400. package/src/ui/dashboard-panes/metrics-pane.ts +37 -0
  401. package/src/ui/dashboard-panes/progress-pane.ts +38 -0
  402. package/src/ui/dashboard-panes/transcript-pane.ts +10 -0
  403. package/src/ui/deploy-bundled-themes.ts +71 -0
  404. package/src/ui/dwf-phase-display.ts +151 -0
  405. package/src/ui/dynamic-border.ts +35 -0
  406. package/src/ui/format-helpers.ts +31 -0
  407. package/src/ui/heartbeat-aggregator.ts +74 -0
  408. package/src/ui/key-utils.ts +42 -0
  409. package/src/ui/keybinding-map.ts +248 -0
  410. package/src/ui/layout-primitives.ts +106 -0
  411. package/src/ui/live-conversation-overlay.ts +183 -0
  412. package/src/ui/live-duration.ts +55 -0
  413. package/src/ui/live-run-sidebar.ts +293 -0
  414. package/src/ui/loaders.ts +6 -0
  415. package/src/ui/mascot.ts +449 -0
  416. package/src/ui/overlays/agent-picker-overlay.ts +66 -0
  417. package/src/ui/overlays/confirm-overlay.ts +60 -0
  418. package/src/ui/overlays/help-overlay.ts +177 -0
  419. package/src/ui/overlays/mailbox-compose-overlay.ts +181 -0
  420. package/src/ui/overlays/mailbox-compose-preview.ts +78 -0
  421. package/src/ui/overlays/mailbox-detail-overlay.ts +155 -0
  422. package/src/ui/pi-ui-compat.ts +68 -0
  423. package/src/ui/powerbar-publisher.ts +459 -0
  424. package/src/ui/render-coalescer.ts +101 -0
  425. package/src/ui/render-diff.ts +160 -0
  426. package/src/ui/render-scheduler.ts +278 -0
  427. package/src/ui/run-action-dispatcher.ts +182 -0
  428. package/src/ui/run-dashboard.ts +910 -0
  429. package/src/ui/run-event-bus.ts +381 -0
  430. package/src/ui/run-snapshot-cache.ts +1057 -0
  431. package/src/ui/settings-overlay.ts +1067 -0
  432. package/src/ui/shared-overlay-scheduler.ts +96 -0
  433. package/src/ui/snapshot-types.ts +90 -0
  434. package/src/ui/spinner.ts +17 -0
  435. package/src/ui/status-colors.ts +143 -0
  436. package/src/ui/syntax-highlight.ts +136 -0
  437. package/src/ui/terminal-status.ts +269 -0
  438. package/src/ui/theme-adapter.ts +271 -0
  439. package/src/ui/theme-discovery.ts +199 -0
  440. package/src/ui/tool-progress-formatter.ts +93 -0
  441. package/src/ui/tool-renderers/brief-mode.ts +225 -0
  442. package/src/ui/tool-renderers/index.ts +726 -0
  443. package/src/ui/transcript-cache.ts +128 -0
  444. package/src/ui/transcript-entries.ts +256 -0
  445. package/src/ui/transcript-viewer.ts +424 -0
  446. package/src/ui/widget/index.ts +418 -0
  447. package/src/ui/widget/widget-formatters.ts +182 -0
  448. package/src/ui/widget/widget-model.ts +110 -0
  449. package/src/ui/widget/widget-renderer.ts +182 -0
  450. package/src/ui/widget/widget-types.ts +36 -0
  451. package/src/utils/bm25-search.ts +235 -0
  452. package/src/utils/completion-dedupe.ts +63 -0
  453. package/src/utils/conflict-detect.ts +668 -0
  454. package/src/utils/env-allowlist.ts +30 -0
  455. package/src/utils/env-filter.ts +155 -0
  456. package/src/utils/file-coalescer.ts +98 -0
  457. package/src/utils/fingerprint.ts +180 -0
  458. package/src/utils/frontmatter.ts +70 -0
  459. package/src/utils/fs-watch.ts +37 -0
  460. package/src/utils/gh-protocol.ts +556 -0
  461. package/src/utils/git.ts +260 -0
  462. package/src/utils/guards.ts +107 -0
  463. package/src/utils/ids.ts +24 -0
  464. package/src/utils/incremental-reader.ts +220 -0
  465. package/src/utils/internal-error.ts +10 -0
  466. package/src/utils/names.ts +36 -0
  467. package/src/utils/ndjson.ts +115 -0
  468. package/src/utils/paths.ts +227 -0
  469. package/src/utils/project-detector.ts +160 -0
  470. package/src/utils/redaction.ts +370 -0
  471. package/src/utils/resolve-shell.ts +36 -0
  472. package/src/utils/run-watcher-registry.ts +152 -0
  473. package/src/utils/safe-paths.ts +393 -0
  474. package/src/utils/scan-cache.ts +144 -0
  475. package/src/utils/session-utils.ts +110 -0
  476. package/src/utils/sleep.ts +54 -0
  477. package/src/utils/socket-path.ts +164 -0
  478. package/src/utils/sse-parser.ts +131 -0
  479. package/src/utils/task-name-generator.ts +337 -0
  480. package/src/utils/timings.ts +33 -0
  481. package/src/utils/token-counter.ts +200 -0
  482. package/src/utils/visual.ts +207 -0
  483. package/src/workflows/cost-estimator.ts +34 -0
  484. package/src/workflows/discover-workflows.ts +308 -0
  485. package/src/workflows/intermediate-store.ts +166 -0
  486. package/src/workflows/preflight-validator.ts +168 -0
  487. package/src/workflows/topology-analyzer.ts +203 -0
  488. package/src/workflows/validate-workflow.ts +50 -0
  489. package/src/workflows/workflow-config.ts +75 -0
  490. package/src/workflows/workflow-serializer.ts +32 -0
  491. package/src/worktree/branch-freshness.ts +112 -0
  492. package/src/worktree/cleanup.ts +341 -0
  493. package/src/worktree/worktree-manager.ts +1204 -0
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Auto-initialize .crew directory structure and .gitignore entries.
3
+ * Called on first team run in a workspace to ensure all required
4
+ * directories and files exist.
5
+ *
6
+ * IMPORTANT: This module is dynamically `import()`'d from concurrent child
7
+ * Pi subprocesses (3+ parallel subagents). Under load, the `path` namespace
8
+ * binding can intermittently arrive as `undefined` in jiti's ESM/CJS interop
9
+ * layer. We therefore use the inline helpers `parseRoot`, `safeJoin`,
10
+ * `safeDirname`, and `safeResolve` so that critical path operations do not
11
+ * depend on the `path` namespace binding.
12
+ *
13
+ * The `node:path` import is retained as a *fallback* (only used when the
14
+ * binding is healthy). Don't add new dependencies on other pi-crew modules.
15
+ *
16
+ * See: https://github.com/baphuongna/pi-crew/issues/28
17
+ */
18
+ import * as fs from "node:fs";
19
+ import * as path from "node:path";
20
+ import { atomicWriteFile } from "./atomic-write.ts";
21
+ import { updateGitignore } from "./gitignore-manager.ts";
22
+
23
+ // Re-export updateGitignore for backwards compatibility with tests.
24
+ export { updateGitignore };
25
+
26
+ /**
27
+ * README content for the .crew directory.
28
+ *
29
+ * Defined as a function (not a `const`) to avoid the Temporal Dead Zone race
30
+ * documented in issue #28 + RFC 17. When this module is loaded via
31
+ * `jiti.import()` (pi's extension loader) wrapped in an async function, a
32
+ * `const` initializer can be hit in TDZ by functions hoisted above it (the
33
+ * same pattern that bit `crewInitPromise` in team-tool/run.ts — see commit
34
+ * fixing it). A function declaration is hoisted with its body available
35
+ * immediately, so callers always get the fully-built string.
36
+ */
37
+ function buildCrewReadme(): string {
38
+ return `# .crew — pi-crew Runtime Directory
39
+
40
+ This directory contains pi-crew runtime state and artifacts.
41
+
42
+ ## What's Here
43
+
44
+ | Directory | Purpose | Commit? |
45
+ |-----------|---------|---------|
46
+ | \`state/runs/\` | Run manifests, tasks, events | No |
47
+ | \`state/subagents/\` | Subagent state | No |
48
+ | \`artifacts/\` | Run outputs (test files, docs, etc.) | Optional |
49
+ | \`cache/\` | Cached run results (fingerprint-based) | No |
50
+ | \`graphs/\` | Archived run graphs | Optional |
51
+ | \`audit/\` | Security event logs | No |
52
+
53
+ ## Cleanup
54
+
55
+ To prune old runs:
56
+ \`\`\`bash
57
+ team action='prune' keep=5
58
+ \`\`\`
59
+
60
+ To clear cache:
61
+ \`\`\`bash
62
+ team action='cache' action='clear'
63
+ \`\`\`
64
+ `;
65
+ }
66
+ /**
67
+ * Find the project root by walking up from start directory.
68
+ * Inline implementation to avoid module dependency on paths.ts.
69
+ * Matches the logic in src/utils/paths.ts:computeRepoRoot().
70
+ */
71
+ /**
72
+ * Detect filesystem root for `start` without relying on `path.parse()`.
73
+ *
74
+ * **Why this exists**: This module is dynamically `import()`'d from concurrent
75
+ * child Pi subprocesses (3+ parallel subagents). Under load, the `path` namespace
76
+ * binding can intermittently arrive as `undefined` in jiti's ESM/CJS interop layer,
77
+ * crashing `findProjectRoot` with `TypeError: Cannot read properties of undefined
78
+ * (reading 'parse')` — see https://github.com/baphuongna/pi-crew/issues/28.
79
+ *
80
+ * Inlining `parse` for the termination root eliminates the dependency on the
81
+ * `path` binding for that critical call path.
82
+ */
83
+ function parseRoot(start: string): string {
84
+ if (!start) return "/";
85
+ if (start[0] === "/") return "/";
86
+ // Windows: "C:\\" or "C:/" -> "C:\\"
87
+ if (/^[A-Za-z]:[\\/]/.test(start)) return start.slice(0, 3);
88
+ // UNC: "\\\\server\\share" — find the second path separator.
89
+ if (start.startsWith("\\\\") || start.startsWith("//")) {
90
+ const rest = start.slice(2);
91
+ const firstSep = Math.max(rest.indexOf("\\"), rest.indexOf("/"));
92
+ if (firstSep === -1) return start;
93
+ const secondSep = Math.max(rest.indexOf("\\", firstSep + 1), rest.indexOf("/", firstSep + 1));
94
+ if (secondSep === -1) return start;
95
+ // secondSep is an index into `rest`; add 2 to map back to `start`.
96
+ return start.slice(0, 2 + secondSep);
97
+ }
98
+ // Relative path — no fixed root, use start itself as terminator.
99
+ return start;
100
+ }
101
+
102
+ /**
103
+ * Defensive wrappers around `path` for use in dynamic-import contexts.
104
+ *
105
+ * **Why these exist**: This module is dynamically `import()`'d from concurrent
106
+ * child Pi subprocesses (3+ parallel subagents). Under load, the `path` namespace
107
+ * binding can intermittently arrive as `undefined` in jiti's ESM/CJS interop layer,
108
+ * crashing `findProjectRoot` with `TypeError: Cannot read properties of undefined
109
+ * (reading 'parse')` — see https://github.com/baphuongna/pi-crew/issues/28.
110
+ *
111
+ * Each helper checks that the corresponding `path` function exists before
112
+ * calling it, falling back to an inline implementation. This keeps the file
113
+ * self-contained even if the namespace binding is missing.
114
+ */
115
+ function safeJoin(...parts: string[]): string {
116
+ // Cross-platform join — picks the separator based on the parts.
117
+ // Don't delegate to `path.join` because POSIX/Windows disagree on which
118
+ // separator is the path separator, and the dynamic-import context (issue
119
+ // #28) may have a partially-initialized `path` namespace.
120
+ const filtered = parts.filter(Boolean);
121
+ if (filtered.length === 0) return "";
122
+ const sep = filtered.some((p) => p.includes("\\")) ? "\\" : "/";
123
+ // Detect if the first part begins with a leading separator (or UNC "\\\\")
124
+ // so we can preserve it. F-8: collapses runs of the separator everywhere
125
+ // (including the body), but re-prepends the leading separator that the
126
+ // collapse regex would otherwise eat.
127
+ const firstPart = filtered[0];
128
+ let leading = "";
129
+ if (sep === "\\") {
130
+ if (firstPart.startsWith("\\\\")) leading = "\\\\";
131
+ else if (firstPart.startsWith("\\")) leading = "\\";
132
+ } else if (firstPart.startsWith("/")) {
133
+ leading = "/";
134
+ }
135
+ // Strip the leading separator(s) from the first part before joining, so
136
+ // the collapse regex doesn't re-collapse them.
137
+ const firstPartStripped = sep === "\\" ? firstPart.replace(/^\\{1,2}/, "") : firstPart.replace(/^\/+/, "");
138
+ const rest = filtered.slice(1);
139
+ const joined = [firstPartStripped, ...rest].filter(Boolean).join(sep);
140
+ // Collapse internal runs of the separator.
141
+ const collapsed = joined.replace(new RegExp(`${sep === "\\" ? "\\\\" : "/"}{2,}`, "g"), sep);
142
+ return leading + collapsed;
143
+ }
144
+
145
+ function safeDirname(p: string): string {
146
+ // Cross-platform dirname — handles BOTH `/` and `\` separators.
147
+ // Note: we don't delegate to `path.dirname` here because on POSIX it treats
148
+ // backslashes as part of a filename, and on Windows it treats forward
149
+ // slashes the same way. The dynamic-import context (issue #28) may also
150
+ // have a partially-initialized `path` namespace. Using a unified inline
151
+ // implementation ensures consistent behavior across all platforms.
152
+ const idx = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
153
+ if (idx === -1) return p; // No separator at all
154
+ if (idx === 0) return p[0] === "/" || p[0] === "\\" ? p[0] : p; // Root: "/" or "\"
155
+ // Preserve drive letter roots like "C:\"
156
+ if (idx === 2 && /[A-Za-z]:/.test(p.slice(0, 2))) return p.slice(0, 3);
157
+ return p.slice(0, idx);
158
+ }
159
+
160
+ function safeResolve(p: string, pathDep?: typeof path): string {
161
+ const dep = pathDep ?? path;
162
+ if (dep && typeof dep.resolve === "function") return dep.resolve(p);
163
+ return p;
164
+ }
165
+
166
+ function findProjectRoot(start: string, pathDep?: typeof path): string | undefined {
167
+ const dirMarkers = [".git", ".hg", ".svn"];
168
+ const fileMarkers = ["package.json", "pyproject.toml", "Cargo.toml", "go.mod"];
169
+ // Use `parseRoot` (inlined above) to avoid `path.parse` for the critical
170
+ // termination root — fixes the jiti namespace race in issue #28.
171
+ const root = parseRoot(start);
172
+ let current = safeResolve(start, pathDep);
173
+ // Walk up to find project root
174
+ while (current !== root) {
175
+ for (const marker of dirMarkers) {
176
+ if (fs.existsSync(safeJoin(current, marker))) return current;
177
+ }
178
+ for (const marker of fileMarkers) {
179
+ if (fs.existsSync(safeJoin(current, marker))) return current;
180
+ }
181
+ const parent = safeDirname(current);
182
+ if (parent === current) break;
183
+ current = parent;
184
+ }
185
+ // Check root as fallback
186
+ if (dirMarkers.some((m) => fs.existsSync(safeJoin(root, m)))) return root;
187
+ return undefined;
188
+ }
189
+
190
+ /**
191
+ * Compute the crew root directory for a given working directory.
192
+ * Matches src/utils/paths.ts:projectCrewRoot() logic.
193
+ */
194
+ function computeCrewRoot(cwd: string): string {
195
+ const repoRoot = findProjectRoot(cwd) ?? cwd;
196
+ const crewDir = safeJoin(repoRoot, ".crew");
197
+ // Keep existing .crew/ stable even when .pi/ exists for project config.
198
+ if (fs.existsSync(crewDir)) return crewDir;
199
+ // Legacy reuse: if .pi/ already exists, namespace under .pi/teams/
200
+ const piDir = safeJoin(repoRoot, ".pi");
201
+ return fs.existsSync(piDir) ? safeJoin(piDir, "teams") : crewDir;
202
+ }
203
+
204
+ /**
205
+ * Ensure the .crew directory structure exists with all required subdirectories,
206
+ * placeholder files, README, and .gitignore entries.
207
+ *
208
+ * This function is self-contained with NO dependencies on other pi-crew modules.
209
+ * It uses inline implementations of findProjectRoot and computeCrewRoot to avoid
210
+ * module binding issues in child-process contexts.
211
+ */
212
+ export async function ensureCrewDirectory(cwd: string): Promise<void> {
213
+ const crewRoot = computeCrewRoot(cwd);
214
+
215
+ // 1. Create directory structure
216
+ const dirs = [
217
+ crewRoot,
218
+ safeJoin(crewRoot, "state", "runs"),
219
+ safeJoin(crewRoot, "state", "subagents"),
220
+ safeJoin(crewRoot, "artifacts"),
221
+ safeJoin(crewRoot, "cache"),
222
+ safeJoin(crewRoot, "graphs"),
223
+ safeJoin(crewRoot, "audit"),
224
+ ];
225
+
226
+ for (const dir of dirs) {
227
+ // Use mkdirSync directly with recursive:true to avoid TOCTOU race.
228
+ // This is atomic and doesn't require existsSync check.
229
+ fs.mkdirSync(dir, { recursive: true });
230
+ }
231
+
232
+ // 2. Create .gitkeep placeholders in directories that should be tracked
233
+ const placeholders = [
234
+ safeJoin(crewRoot, "artifacts", ".gitkeep"),
235
+ safeJoin(crewRoot, "cache", ".gitkeep"),
236
+ safeJoin(crewRoot, "graphs", ".gitkeep"),
237
+ safeJoin(crewRoot, "audit", ".gitkeep"),
238
+ ];
239
+
240
+ for (const placeholder of placeholders) {
241
+ if (!fs.existsSync(placeholder)) {
242
+ atomicWriteFile(placeholder, "");
243
+ }
244
+ }
245
+
246
+ // 3. Write README.md (always overwrite to keep it current)
247
+ atomicWriteFile(safeJoin(crewRoot, "README.md"), buildCrewReadme());
248
+
249
+ // 4. Update .gitignore at project root
250
+ const repoRoot = findProjectRoot(cwd);
251
+ if (repoRoot) {
252
+ const gitignorePath = safeJoin(repoRoot, ".gitignore");
253
+ // LAZY: dodge the jiti ESM/CJS interop TDZ race on the static `import { updateGitignore }` above (issue #28, RFC 17). At this point the module body has fully evaluated, so the dynamic import resolves to a live binding.
254
+ const { updateGitignore: updateGitignoreFn } = await import("./gitignore-manager.ts");
255
+ await updateGitignoreFn(gitignorePath);
256
+ }
257
+ }
258
+
259
+ // Exported only for regression tests of issue #28.
260
+ // NOT part of the public API — the `__test__` prefix follows the project
261
+ // convention used in atomic-write.ts, state-store.ts, team-runner.ts, etc.
262
+ // See F-4 in the post-fix review for the convention rationale.
263
+ export const __test__internals = {
264
+ parseRoot,
265
+ safeJoin,
266
+ safeDirname,
267
+ safeResolve,
268
+ findProjectRoot,
269
+ };
@@ -0,0 +1,372 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { projectCrewRoot } from "../utils/paths.ts";
4
+ import { assertSafePathId } from "../utils/safe-paths.ts";
5
+ import { withFileLockSync } from "./locks.ts";
6
+
7
+ export interface CoherenceMark {
8
+ matchesPrior: boolean;
9
+ matchesRecursive: boolean;
10
+ promotionAllowed: boolean;
11
+ reason: string;
12
+ }
13
+
14
+ export interface RolloutEntry {
15
+ rolloutId: string;
16
+ timestamp: string;
17
+ priorWinner?: string;
18
+ searchSpace: string;
19
+ trialCount: number;
20
+ topCandidates: string[];
21
+ decisionMark: "accept" | "watch" | "reject" | "decay";
22
+ coherenceMark: CoherenceMark;
23
+ }
24
+
25
+ /**
26
+ * Get the ledger file path for a given run ID.
27
+ * SECURITY: Accept stateRoot param to use it for path computation
28
+ * instead of hardcoded path, ensuring stateRoot containment.
29
+ * Uses projectCrewRoot() to honour the `.pi/teams/` fallback for `.pi`-based
30
+ * projects (see issue #29).
31
+ */
32
+ function getLedgerPath(runId: string, stateRoot?: string, cwd?: string): string {
33
+ const base = stateRoot ?? join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
34
+ return `${base}/decision-ledger.jsonl`;
35
+ }
36
+
37
+ /**
38
+ * Compute coherence marks based on existing ledger entries.
39
+ */
40
+ function computeCoherence(entry: RolloutEntry, ledger: RolloutEntry[]): CoherenceMark {
41
+ if (ledger.length === 0) {
42
+ return {
43
+ matchesPrior: false,
44
+ matchesRecursive: false,
45
+ promotionAllowed: true,
46
+ reason: "No prior entries - first rollout, promotion allowed",
47
+ };
48
+ }
49
+
50
+ const previousEntry = ledger[ledger.length - 1];
51
+ const matchesPrior: boolean =
52
+ entry.decisionMark === previousEntry.decisionMark || Boolean(entry.priorWinner && entry.topCandidates.includes(entry.priorWinner));
53
+
54
+ // Check last 10 entries for recursive pattern
55
+ const recentEntries = ledger.slice(-10);
56
+ const recentDecisions = recentEntries.map((e) => e.decisionMark);
57
+ const currentDecision = entry.decisionMark;
58
+
59
+ const recursiveMatches = recentDecisions.filter((d) => d === currentDecision).length;
60
+ const matchesRecursive = recursiveMatches >= Math.ceil(recentDecisions.length / 2); // At least half match
61
+
62
+ const promotionAllowed = matchesPrior || matchesRecursive;
63
+
64
+ let reason: string;
65
+ if (matchesPrior && matchesRecursive) {
66
+ reason = `Matches prior winner and recursive pattern (${recursiveMatches}/${recentDecisions.length} recent decisions)`;
67
+ } else if (matchesPrior) {
68
+ reason = `Matches prior winner decision`;
69
+ } else if (matchesRecursive) {
70
+ reason = `Matches recursive pattern (${recursiveMatches}/3 recent decisions)`;
71
+ } else {
72
+ reason = `No match with prior or recursive pattern - requires human review`;
73
+ }
74
+
75
+ return {
76
+ matchesPrior,
77
+ matchesRecursive,
78
+ promotionAllowed,
79
+ reason,
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Initialize a new decision ledger for a run.
85
+ * Creates the directory and ledger file if they don't exist.
86
+ */
87
+ export function initLedger(runId: string): void {
88
+ assertSafePathId("runId", runId);
89
+ const ledgerPath = getLedgerPath(runId);
90
+ const dir = dirname(ledgerPath);
91
+
92
+ if (!existsSync(dir)) {
93
+ mkdirSync(dir, { recursive: true });
94
+ }
95
+
96
+ // Create empty file if it doesn't exist
97
+ if (!existsSync(ledgerPath)) {
98
+ writeFileSync(ledgerPath, "", "utf-8");
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Append a new entry to the decision ledger.
104
+ * Automatically computes and adds coherence marks.
105
+ * FIX: Uses atomic write to prevent partial writes on crash.
106
+ * FIX: Uses withFileLockSync to prevent concurrent appendEntry calls from
107
+ * losing entries (classic read-check-write race).
108
+ */
109
+ export function appendEntry(runId: string, entry: RolloutEntry): RolloutEntry {
110
+ assertSafePathId("runId", runId);
111
+ // Ensure directory exists
112
+ const ledgerPath = getLedgerPath(runId);
113
+ const dir = dirname(ledgerPath);
114
+ if (!existsSync(dir)) {
115
+ mkdirSync(dir, { recursive: true });
116
+ }
117
+
118
+ // FIX: Wrap read+write in file lock to prevent concurrent writers from
119
+ // reading stale state and overwriting each other's entries.
120
+ // P-02: Previously read the ENTIRE ledger (O(n)) and rewrote the ENTIRE
121
+ // file (atomicWriteFile) on every append → O(n²). Now reads only the tail
122
+ // (last 10 entries, which is all coherence needs) and appends a single
123
+ // line via appendFileSync (one write syscall → no partial-line risk).
124
+ let entryWithCoherence: RolloutEntry;
125
+ withFileLockSync(ledgerPath, () => {
126
+ // P-02: read tail only (last 10) for coherence computation.
127
+ // computeCoherence only uses ledger.slice(-10), so the full read
128
+ // was wasted work.
129
+ const tail = readTailEntries(runId, 10);
130
+
131
+ // Compute coherence
132
+ const coherenceMark = computeCoherence(entry, tail);
133
+ entryWithCoherence = { ...entry, coherenceMark };
134
+
135
+ // P-02: append single JSONL line instead of rewriting the entire
136
+ // file. appendFileSync issues one write syscall for small buffers,
137
+ // so a crash mid-write cannot corrupt earlier entries (at worst the
138
+ // last line is truncated — getLedger filters empty lines).
139
+ const line = JSON.stringify(entryWithCoherence) + "\n";
140
+ appendFileSync(ledgerPath, line, "utf-8");
141
+ });
142
+
143
+ return entryWithCoherence!;
144
+ }
145
+
146
+ /**
147
+ * Read all entries from the decision ledger.
148
+ */
149
+ export function getLedger(runId: string): RolloutEntry[] {
150
+ assertSafePathId("runId", runId);
151
+ const ledgerPath = getLedgerPath(runId);
152
+
153
+ if (!existsSync(ledgerPath)) {
154
+ return [];
155
+ }
156
+
157
+ const content = readFileSync(ledgerPath, "utf-8");
158
+ if (!content.trim()) {
159
+ return [];
160
+ }
161
+
162
+ return content
163
+ .split("\n")
164
+ .filter((line) => line.trim())
165
+ .map((line) => JSON.parse(line) as RolloutEntry);
166
+ }
167
+
168
+ /**
169
+ * Read only the last `n` entries from the decision ledger.
170
+ *
171
+ * P-02: coherence computation only inspects `ledger.slice(-10)`, so reading
172
+ * the entire file on every append is O(n) per write → O(n²) overall. This
173
+ * helper reads just the tail, making append O(1) amortised. The file is
174
+ * bounded by run lifetime (no unbounded growth across runs).
175
+ */
176
+ function readTailEntries(runId: string, n: number = 10): RolloutEntry[] {
177
+ assertSafePathId("runId", runId);
178
+ const ledgerPath = getLedgerPath(runId);
179
+
180
+ if (!existsSync(ledgerPath)) {
181
+ return [];
182
+ }
183
+
184
+ const content = readFileSync(ledgerPath, "utf-8");
185
+ if (!content.trim()) {
186
+ return [];
187
+ }
188
+
189
+ const lines = content.split("\n").filter((line) => line.trim());
190
+ return lines.slice(-n).map((line) => JSON.parse(line) as RolloutEntry);
191
+ }
192
+
193
+ /**
194
+ * Get the most recent entry from the decision ledger.
195
+ */
196
+ export function getLatestDecision(runId: string): RolloutEntry | null {
197
+ assertSafePathId("runId", runId);
198
+ const tail = readTailEntries(runId, 1);
199
+ if (tail.length === 0) {
200
+ return null;
201
+ }
202
+ return tail[tail.length - 1];
203
+ }
204
+
205
+ /**
206
+ * Generate a human-readable markdown summary of the ledger.
207
+ */
208
+ export function summarizeLedger(runId: string): string {
209
+ assertSafePathId("runId", runId);
210
+ const ledger = getLedger(runId);
211
+
212
+ if (ledger.length === 0) {
213
+ return "# Decision Ledger Summary\n\n*No entries recorded yet.*";
214
+ }
215
+
216
+ const lines: string[] = ["# Decision Ledger Summary", "", `Run ID: ${runId}`, `Total Entries: ${ledger.length}`, "", "## Entries", ""];
217
+
218
+ for (let i = 0; i < ledger.length; i++) {
219
+ const entry = ledger[i];
220
+ lines.push(`### ${i + 1}. ${entry.rolloutId}`);
221
+ lines.push("");
222
+ lines.push(`- **Timestamp**: ${entry.timestamp}`);
223
+ lines.push(`- **Search Space**: ${entry.searchSpace}`);
224
+ lines.push(`- **Trial Count**: ${entry.trialCount}`);
225
+ lines.push(`- **Decision**: ${entry.decisionMark}`);
226
+
227
+ if (entry.priorWinner) {
228
+ lines.push(`- **Prior Winner**: ${entry.priorWinner}`);
229
+ }
230
+
231
+ lines.push(`- **Top Candidates**: ${entry.topCandidates.join(", ") || "(none)"}`);
232
+ lines.push("");
233
+ lines.push("#### Coherence");
234
+ lines.push(`- **Matches Prior**: ${entry.coherenceMark.matchesPrior ? "✓" : "✗"}`);
235
+ lines.push(`- **Matches Recursive**: ${entry.coherenceMark.matchesRecursive ? "✓" : "✗"}`);
236
+ lines.push(`- **Promotion Allowed**: ${entry.coherenceMark.promotionAllowed ? "✓" : "✗"}`);
237
+ lines.push(`- **Reason**: ${entry.coherenceMark.reason}`);
238
+ lines.push("");
239
+ }
240
+
241
+ // Summary statistics
242
+ const decisions = ledger.map((e) => e.decisionMark);
243
+ const acceptCount = decisions.filter((d) => d === "accept").length;
244
+ const watchCount = decisions.filter((d) => d === "watch").length;
245
+ const rejectCount = decisions.filter((d) => d === "reject").length;
246
+ const decayCount = decisions.filter((d) => d === "decay").length;
247
+
248
+ lines.push("## Summary");
249
+ lines.push("");
250
+ lines.push(`| Decision | Count |`);
251
+ lines.push(`|----------|-------|`);
252
+ lines.push(`| Accept | ${acceptCount} |`);
253
+ lines.push(`| Watch | ${watchCount} |`);
254
+ lines.push(`| Reject | ${rejectCount} |`);
255
+ lines.push(`| Decay | ${decayCount} |`);
256
+ lines.push("");
257
+
258
+ const promotedCount = ledger.filter((e) => e.coherenceMark.promotionAllowed).length;
259
+ lines.push(`**Promotion Rate**: ${promotedCount}/${ledger.length} (${((promotedCount / ledger.length) * 100).toFixed(1)}%)`);
260
+
261
+ return lines.join("\n");
262
+ }
263
+
264
+ /**
265
+ * Override the coherence mark of the last entry in the ledger.
266
+ * FIX: This preserves all previous entries while updating just the last one.
267
+ * Previously this would truncate the entire ledger!
268
+ */
269
+ // NOTE: overrideLastEntry was dead code (never called). Removed in post-v0.6.2 review.
270
+ // If needed in the future, re-implement with assertSafePathId guard.
271
+
272
+ /**
273
+ * Promote a candidate by marking it as accepted with proper coherence.
274
+ * FIX: Wrap read+write in file lock to prevent concurrent promotion/decay
275
+ * calls from losing entries.
276
+ */
277
+ export function promoteCandidate(runId: string, candidate: string): RolloutEntry {
278
+ assertSafePathId("runId", runId);
279
+ assertSafePathId("candidate", candidate);
280
+
281
+ const ledgerPath = getLedgerPath(runId);
282
+ const dir = dirname(ledgerPath);
283
+ if (!existsSync(dir)) {
284
+ mkdirSync(dir, { recursive: true });
285
+ }
286
+
287
+ let entry: RolloutEntry;
288
+ withFileLockSync(ledgerPath, () => {
289
+ // P-02: read tail only (last 10) for coherence + latest decision.
290
+ // Avoids O(n) full read on every promote/decay.
291
+ const tail = readTailEntries(runId, 10);
292
+ const latestDecision = tail.length > 0 ? tail[tail.length - 1] : null;
293
+
294
+ // Create entry without coherence first
295
+ const entryWithoutCoherence = {
296
+ rolloutId: `promote-${Date.now()}`,
297
+ timestamp: new Date().toISOString(),
298
+ priorWinner: latestDecision?.topCandidates[0],
299
+ searchSpace: latestDecision?.searchSpace || "unknown",
300
+ trialCount: (latestDecision?.trialCount || 0) + 1,
301
+ topCandidates: [candidate],
302
+ decisionMark: "accept" as const,
303
+ };
304
+
305
+ // Compute coherence (empty ledger = no matches)
306
+ const coherenceMark = computeCoherence(entryWithoutCoherence as RolloutEntry, tail);
307
+
308
+ // Manual promotion always allows further promotion
309
+ coherenceMark.promotionAllowed = true;
310
+ coherenceMark.reason = "Manual promotion - promotion allowed";
311
+
312
+ // Create full entry with coherence
313
+ entry = { ...entryWithoutCoherence, coherenceMark };
314
+
315
+ // P-02: append single JSONL line instead of rewriting entire file.
316
+ const line = JSON.stringify(entry) + "\n";
317
+ appendFileSync(ledgerPath, line, "utf-8");
318
+ });
319
+
320
+ return entry!;
321
+ }
322
+
323
+ /**
324
+ * Decay a candidate by marking it as accepted with proper coherence.
325
+ * FIX: Wrap read+write in file lock to prevent concurrent promotion/decay
326
+ * calls from losing entries.
327
+ */
328
+ export function decayCandidate(runId: string, candidate: string): RolloutEntry {
329
+ assertSafePathId("runId", runId);
330
+ assertSafePathId("candidate", candidate);
331
+
332
+ const ledgerPath = getLedgerPath(runId);
333
+ const dir = dirname(ledgerPath);
334
+ if (!existsSync(dir)) {
335
+ mkdirSync(dir, { recursive: true });
336
+ }
337
+
338
+ let entry: RolloutEntry;
339
+ withFileLockSync(ledgerPath, () => {
340
+ // P-02: read tail only (last 10) for coherence + latest decision.
341
+ // Avoids O(n) full read on every promote/decay.
342
+ const tail = readTailEntries(runId, 10);
343
+ const latestDecision = tail.length > 0 ? tail[tail.length - 1] : null;
344
+
345
+ // Create entry without coherence first
346
+ const entryWithoutCoherence = {
347
+ rolloutId: `decay-${Date.now()}`,
348
+ timestamp: new Date().toISOString(),
349
+ priorWinner: latestDecision?.topCandidates[0],
350
+ searchSpace: latestDecision?.searchSpace || "unknown",
351
+ trialCount: (latestDecision?.trialCount || 0) + 1,
352
+ topCandidates: [candidate],
353
+ decisionMark: "decay" as const,
354
+ };
355
+
356
+ // Compute coherence (empty ledger = no matches)
357
+ const coherenceMark = computeCoherence(entryWithoutCoherence as RolloutEntry, tail);
358
+
359
+ // Manual decay never allows promotion
360
+ coherenceMark.promotionAllowed = false;
361
+ coherenceMark.reason = "Manual decay - promotion not allowed";
362
+
363
+ // Create full entry with coherence
364
+ entry = { ...entryWithoutCoherence, coherenceMark };
365
+
366
+ // P-02: append single JSONL line instead of rewriting entire file.
367
+ const line = JSON.stringify(entry) + "\n";
368
+ appendFileSync(ledgerPath, line, "utf-8");
369
+ });
370
+
371
+ return entry!;
372
+ }