@zhushanwen/pi-subagent-workflow 8.6.0 → 8.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/README.md +5 -11
  2. package/package.json +13 -20
  3. package/relay/relay.mjs +1 -1
  4. package/skills/workflow-script-format/SKILL.md +32 -13
  5. package/src/host/__tests__/pi-host.test.ts +235 -0
  6. package/src/host/pi-host.ts +163 -0
  7. package/src/index.ts +141 -138
  8. package/src/injectors/__tests__/engine-awareness.test.ts +292 -0
  9. package/src/injectors/__tests__/engine-section-stability.test.ts +343 -0
  10. package/src/injectors/__tests__/model-list-injector.test.ts +18 -21
  11. package/src/injectors/__tests__/subagent-list-injector.test.ts +59 -19
  12. package/src/injectors/__tests__/workflow-list-injector.test.ts +31 -17
  13. package/src/injectors/engine-awareness.ts +119 -0
  14. package/src/injectors/model-list-injector.ts +15 -58
  15. package/src/injectors/subagent-list-injector.ts +56 -114
  16. package/src/injectors/workflow-list-injector.ts +37 -70
  17. package/src/interface/__tests__/detectors.test.ts +48 -37
  18. package/src/interface/__tests__/subagent-tool-path-guard.test.ts +2 -2
  19. package/src/interface/__tests__/subagent-tool-prompt.test.ts +6 -3
  20. package/src/interface/__tests__/tool-workflow-run-builtin-name.test.ts +216 -0
  21. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +17 -6
  22. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +38 -2
  23. package/src/interface/bg-notify-render.ts +7 -17
  24. package/src/interface/command-actions.ts +5 -15
  25. package/src/interface/commands.ts +4 -4
  26. package/src/interface/format.ts +17 -6
  27. package/src/interface/gui-mappers.ts +18 -22
  28. package/src/interface/helpers.ts +9 -130
  29. package/src/interface/list-component.ts +5 -5
  30. package/src/interface/list-shared.ts +3 -3
  31. package/src/interface/list-view.ts +3 -3
  32. package/src/interface/subagent-actions.ts +66 -439
  33. package/src/interface/subagent-tool-schema.ts +21 -17
  34. package/src/interface/subagent-tool.ts +12 -7
  35. package/src/interface/subagents.ts +4 -4
  36. package/src/interface/tool-render.ts +37 -15
  37. package/src/interface/tool-workflow-script.ts +34 -76
  38. package/src/interface/tool-workflow.ts +57 -94
  39. package/src/interface/views/WorkflowsView.ts +8 -16
  40. package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
  41. package/src/interface/views/__tests__/detail-content-session-file.test.ts +2 -2
  42. package/src/interface/views/detail-content.ts +4 -4
  43. package/src/interface/views/format.ts +23 -62
  44. package/src/{orchestration/jsonl-run-store.ts → jsonl-run-store.ts} +137 -225
  45. package/agents/analyst.md +0 -61
  46. package/agents/coder.md +0 -70
  47. package/agents/debugger.md +0 -67
  48. package/agents/doc-reviewer.md +0 -50
  49. package/agents/explorer.md +0 -64
  50. package/agents/general-purpose.md +0 -32
  51. package/agents/orchestrator.md +0 -63
  52. package/agents/planner.md +0 -54
  53. package/agents/researcher.md +0 -65
  54. package/agents/reviewer.md +0 -74
  55. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +0 -53
  56. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +0 -1
  57. package/src/execution/__tests__/agent-registry.test.ts +0 -363
  58. package/src/execution/__tests__/agent-result-mapper.test.ts +0 -150
  59. package/src/execution/__tests__/alive-store.test.ts +0 -147
  60. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  61. package/src/execution/__tests__/before-agent-start-injection.test.ts +0 -132
  62. package/src/execution/__tests__/bg-notify-render.test.ts +0 -329
  63. package/src/execution/__tests__/channel-registry-handshake.test.ts +0 -243
  64. package/src/execution/__tests__/chat-engine-routing.test.ts +0 -601
  65. package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +0 -365
  66. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -190
  67. package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +0 -215
  68. package/src/execution/__tests__/concurrency-pool.test.ts +0 -250
  69. package/src/execution/__tests__/config.test.ts +0 -110
  70. package/src/execution/__tests__/conversation-wiring.test.ts +0 -198
  71. package/src/execution/__tests__/crash-recovery.test.ts +0 -320
  72. package/src/execution/__tests__/delivery-methods.test.ts +0 -422
  73. package/src/execution/__tests__/dialog-queue.test.ts +0 -299
  74. package/src/execution/__tests__/epipe-fallback.test.ts +0 -241
  75. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -243
  76. package/src/execution/__tests__/execute-nesting.test.ts +0 -323
  77. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  78. package/src/execution/__tests__/execution-record.test.ts +0 -1394
  79. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +0 -171
  80. package/src/execution/__tests__/finalize-record.test.ts +0 -368
  81. package/src/execution/__tests__/finalized-marker.test.ts +0 -82
  82. package/src/execution/__tests__/format-schema-instruction.test.ts +0 -169
  83. package/src/execution/__tests__/format.test.ts +0 -458
  84. package/src/execution/__tests__/gc-timer.test.ts +0 -184
  85. package/src/execution/__tests__/get-record-for-action-restart.test.ts +0 -254
  86. package/src/execution/__tests__/gui-mode-dispatch.test.ts +0 -59
  87. package/src/execution/__tests__/helpers/mock-extension-api.ts +0 -30
  88. package/src/execution/__tests__/helpers/spawn-mock.ts +0 -242
  89. package/src/execution/__tests__/host-mode.test.ts +0 -87
  90. package/src/execution/__tests__/index-session-start-identity.test.ts +0 -371
  91. package/src/execution/__tests__/index-session-start.test.ts +0 -672
  92. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  93. package/src/execution/__tests__/lifecycle-manager.test.ts +0 -383
  94. package/src/execution/__tests__/lifecycle-predicates.test.ts +0 -116
  95. package/src/execution/__tests__/list-component.test.ts +0 -401
  96. package/src/execution/__tests__/list-fields.test.ts +0 -140
  97. package/src/execution/__tests__/manifest-parentid.test.ts +0 -117
  98. package/src/execution/__tests__/model-resolver.test.ts +0 -465
  99. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +0 -287
  100. package/src/execution/__tests__/nested-visibility.test.ts +0 -325
  101. package/src/execution/__tests__/notifier-flush.test.ts +0 -612
  102. package/src/execution/__tests__/notifier-golden-snapshot.test.ts +0 -261
  103. package/src/execution/__tests__/notify-ledger.test.ts +0 -826
  104. package/src/execution/__tests__/one-shot-upgrade.test.ts +0 -205
  105. package/src/execution/__tests__/output-collector.test.ts +0 -358
  106. package/src/execution/__tests__/parent-child-matrix.test.ts +0 -336
  107. package/src/execution/__tests__/path-encoding.test.ts +0 -104
  108. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  109. package/src/execution/__tests__/record-store.test.ts +0 -1057
  110. package/src/execution/__tests__/records-cwd-isolation.test.ts +0 -91
  111. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +0 -339
  112. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -273
  113. package/src/execution/__tests__/relay-agent.test.ts +0 -448
  114. package/src/execution/__tests__/relay-env.test.ts +0 -42
  115. package/src/execution/__tests__/resource-policy.test.ts +0 -109
  116. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  117. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -267
  118. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -253
  119. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -687
  120. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  121. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  122. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  123. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -199
  124. package/src/execution/__tests__/sdk-contract.test.ts +0 -273
  125. package/src/execution/__tests__/session-context-resolver.test.ts +0 -167
  126. package/src/execution/__tests__/session-file-gc.test.ts +0 -293
  127. package/src/execution/__tests__/session-pending.test.ts +0 -197
  128. package/src/execution/__tests__/session-reconstructor.test.ts +0 -379
  129. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -178
  130. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -350
  131. package/src/execution/__tests__/session-start-reaper.test.ts +0 -233
  132. package/src/execution/__tests__/spawn-args.test.ts +0 -445
  133. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -189
  134. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  135. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -207
  136. package/src/execution/__tests__/spawned-children.test.ts +0 -92
  137. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  138. package/src/execution/__tests__/startup-config-declaration.test.ts +0 -35
  139. package/src/execution/__tests__/status-refactor.test.ts +0 -345
  140. package/src/execution/__tests__/stdin-writer.test.ts +0 -463
  141. package/src/execution/__tests__/stream-sink-retirement.test.ts +0 -261
  142. package/src/execution/__tests__/subagent-service-abort.test.ts +0 -60
  143. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -629
  144. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +0 -180
  145. package/src/execution/__tests__/subagent-service.test.ts +0 -788
  146. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -310
  147. package/src/execution/__tests__/subprocess-agent-runner.test.ts +0 -599
  148. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  149. package/src/execution/__tests__/timeout-integration.test.ts +0 -615
  150. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  151. package/src/execution/__tests__/tool-action.test.ts +0 -476
  152. package/src/execution/__tests__/truncline-snapshot.test.ts +0 -81
  153. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  154. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  155. package/src/execution/__tests__/ui-channels.test.ts +0 -187
  156. package/src/execution/__tests__/ui-interaction-model.test.ts +0 -67
  157. package/src/execution/__tests__/ui-request-handler-factory.test.ts +0 -370
  158. package/src/execution/__tests__/ui-request-handler.test.ts +0 -204
  159. package/src/execution/__tests__/ui-request-observability.test.ts +0 -114
  160. package/src/execution/__tests__/ui-request-queue.test.ts +0 -133
  161. package/src/execution/__tests__/worktree-manager.test.ts +0 -633
  162. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -229
  163. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +0 -181
  164. package/src/execution/__tests__/worktree-registry.test.ts +0 -199
  165. package/src/execution/agent-registry.ts +0 -199
  166. package/src/execution/agent-result-mapper.ts +0 -90
  167. package/src/execution/alive-store.ts +0 -92
  168. package/src/execution/argv-mirror.ts +0 -113
  169. package/src/execution/best-effort.ts +0 -38
  170. package/src/execution/channel-registry-access.ts +0 -144
  171. package/src/execution/concurrency-pool.ts +0 -116
  172. package/src/execution/config.ts +0 -96
  173. package/src/execution/dialog-queue.ts +0 -330
  174. package/src/execution/engine/__tests__/common/data-dir.test.ts +0 -53
  175. package/src/execution/engine/__tests__/common/errors.test.ts +0 -132
  176. package/src/execution/engine/__tests__/common/event-journal.test.ts +0 -177
  177. package/src/execution/engine/__tests__/common/kill-chain.test.ts +0 -192
  178. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +0 -81
  179. package/src/execution/engine/__tests__/common/persona-router.test.ts +0 -123
  180. package/src/execution/engine/__tests__/common/pool-manager.test.ts +0 -154
  181. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  182. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  183. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +0 -141
  184. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +0 -109
  185. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +0 -101
  186. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +0 -77
  187. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +0 -104
  188. package/src/execution/engine/__tests__/conformance/contract.relay.test.ts +0 -342
  189. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +0 -201
  190. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +0 -76
  191. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +0 -79
  192. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -87
  193. package/src/execution/engine/__tests__/engines-declaration.test.ts +0 -36
  194. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -85
  195. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  196. package/src/execution/engine/__tests__/registry.test.ts +0 -120
  197. package/src/execution/engine/__tests__/routing.test.ts +0 -231
  198. package/src/execution/engine/common/data-dir.ts +0 -62
  199. package/src/execution/engine/common/errors.ts +0 -183
  200. package/src/execution/engine/common/event-journal.ts +0 -254
  201. package/src/execution/engine/common/journal-replay.ts +0 -62
  202. package/src/execution/engine/common/kill-chain.ts +0 -221
  203. package/src/execution/engine/common/nesting-guard.ts +0 -50
  204. package/src/execution/engine/common/persona-router.ts +0 -108
  205. package/src/execution/engine/common/pool-manager.ts +0 -226
  206. package/src/execution/engine/common/schema-emulation.ts +0 -189
  207. package/src/execution/engine/common/session-view-projection.ts +0 -51
  208. package/src/execution/engine/engine-discovery.ts +0 -65
  209. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -469
  210. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  211. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  212. package/src/execution/engine/engines/pi/pi-engine.ts +0 -415
  213. package/src/execution/engine/engines/pi/reader.ts +0 -48
  214. package/src/execution/engine/engines/pi/registration.ts +0 -35
  215. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  216. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  217. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  218. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -246
  219. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -228
  220. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  221. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -64
  222. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -127
  223. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  224. package/src/execution/engine/engines/zcode/constants.ts +0 -43
  225. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -39
  226. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  227. package/src/execution/engine/engines/zcode/parser.ts +0 -436
  228. package/src/execution/engine/engines/zcode/preparer.ts +0 -363
  229. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  230. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  231. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -658
  232. package/src/execution/engine/host-task-spec.ts +0 -47
  233. package/src/execution/engine/model-prompt.ts +0 -59
  234. package/src/execution/engine/paths.ts +0 -42
  235. package/src/execution/engine/port.ts +0 -153
  236. package/src/execution/engine/registry.ts +0 -123
  237. package/src/execution/engine/routing.ts +0 -218
  238. package/src/execution/engine/types.ts +0 -309
  239. package/src/execution/execute-options-mapper.ts +0 -112
  240. package/src/execution/execution-record.ts +0 -965
  241. package/src/execution/finalize-record.ts +0 -251
  242. package/src/execution/finalized-marker.ts +0 -51
  243. package/src/execution/get-state-handshake.ts +0 -104
  244. package/src/execution/host-mode.ts +0 -56
  245. package/src/execution/idle-gc.ts +0 -47
  246. package/src/execution/lifecycle-manager.ts +0 -513
  247. package/src/execution/lifecycle-predicates.ts +0 -65
  248. package/src/execution/manifest-store.ts +0 -256
  249. package/src/execution/model-config-service.ts +0 -219
  250. package/src/execution/model-resolver.ts +0 -248
  251. package/src/execution/notifier.ts +0 -330
  252. package/src/execution/notify-ledger.ts +0 -580
  253. package/src/execution/output-collector.ts +0 -228
  254. package/src/execution/path-encoding.ts +0 -60
  255. package/src/execution/pi-invocation.ts +0 -120
  256. package/src/execution/record-entry.ts +0 -132
  257. package/src/execution/record-store.ts +0 -1234
  258. package/src/execution/relay-env.ts +0 -37
  259. package/src/execution/session-context-resolver.ts +0 -64
  260. package/src/execution/session-file-gc.ts +0 -120
  261. package/src/execution/session-pending.ts +0 -164
  262. package/src/execution/session-reconstructor.ts +0 -678
  263. package/src/execution/session-runner.ts +0 -1781
  264. package/src/execution/sessions-index.ts +0 -304
  265. package/src/execution/spawn-event-adapter.ts +0 -363
  266. package/src/execution/stdin-writer.ts +0 -198
  267. package/src/execution/stream-sink.ts +0 -126
  268. package/src/execution/subagent-service.ts +0 -2141
  269. package/src/execution/subprocess-agent-runner.ts +0 -317
  270. package/src/execution/temp-prompt.ts +0 -62
  271. package/src/execution/tombstone-store.ts +0 -72
  272. package/src/execution/turn-limiter.ts +0 -102
  273. package/src/execution/types.ts +0 -973
  274. package/src/execution/ui-channels.ts +0 -216
  275. package/src/execution/ui-interaction-model.ts +0 -48
  276. package/src/execution/ui-request-handler-factory.ts +0 -212
  277. package/src/execution/ui-request-observability.ts +0 -81
  278. package/src/execution/ui-request-queue.ts +0 -184
  279. package/src/execution/worktree-manager.ts +0 -688
  280. package/src/execution/worktree-registry.ts +0 -222
  281. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +0 -349
  282. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -202
  283. package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -152
  284. package/src/orchestration/__tests__/args-validator.test.ts +0 -143
  285. package/src/orchestration/__tests__/config-loader.test.ts +0 -503
  286. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +0 -809
  287. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +0 -405
  288. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +0 -56
  289. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +0 -166
  290. package/src/orchestration/__tests__/execute-agent-call.test.ts +0 -451
  291. package/src/orchestration/__tests__/jsonl-run-store-corrupt-entry.test.ts +0 -150
  292. package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +0 -171
  293. package/src/orchestration/__tests__/jsonl-run-store-retention.test.ts +0 -202
  294. package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +0 -1030
  295. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -600
  296. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +0 -96
  297. package/src/orchestration/__tests__/lifecycle.test.ts +0 -659
  298. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +0 -95
  299. package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +0 -2025
  300. package/src/orchestration/__tests__/script-lint.test.ts +0 -831
  301. package/src/orchestration/__tests__/skill-discovery.test.ts +0 -201
  302. package/src/orchestration/__tests__/test-mocks.ts +0 -197
  303. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +0 -368
  304. package/src/orchestration/__tests__/worker-host.test.ts +0 -120
  305. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +0 -164
  306. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +0 -563
  307. package/src/orchestration/__tests__/worker-script-builder.test.ts +0 -309
  308. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +0 -129
  309. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -317
  310. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +0 -110
  311. package/src/orchestration/__tests__/workflows-e2e.test.ts +0 -537
  312. package/src/orchestration/agent-opts-resolver.ts +0 -174
  313. package/src/orchestration/args-validator.ts +0 -127
  314. package/src/orchestration/config-loader.ts +0 -315
  315. package/src/orchestration/error-recovery.ts +0 -1018
  316. package/src/orchestration/execute-agent-call.ts +0 -256
  317. package/src/orchestration/launcher.ts +0 -455
  318. package/src/orchestration/lifecycle.ts +0 -399
  319. package/src/orchestration/models/__tests__/budget.test.ts +0 -307
  320. package/src/orchestration/models/__tests__/trace.test.ts +0 -408
  321. package/src/orchestration/models/agent-call.ts +0 -83
  322. package/src/orchestration/models/budget.ts +0 -118
  323. package/src/orchestration/models/ports.ts +0 -164
  324. package/src/orchestration/models/run-runtime.ts +0 -104
  325. package/src/orchestration/models/run-spec.ts +0 -83
  326. package/src/orchestration/models/run-state.ts +0 -44
  327. package/src/orchestration/models/trace.ts +0 -183
  328. package/src/orchestration/models/types.ts +0 -301
  329. package/src/orchestration/models/workflow-run.ts +0 -254
  330. package/src/orchestration/models/workflow-script-registry.ts +0 -35
  331. package/src/orchestration/models/workflow-script.ts +0 -117
  332. package/src/orchestration/script-lint.ts +0 -766
  333. package/src/orchestration/skill-discovery.ts +0 -115
  334. package/src/orchestration/worker-handle.ts +0 -115
  335. package/src/orchestration/worker-host.ts +0 -98
  336. package/src/orchestration/worker-script-builder.ts +0 -421
  337. package/src/orchestration/workflow-files.ts +0 -85
  338. package/src/orchestration/workflow-script-registry-impl.ts +0 -133
  339. package/src/shared/__tests__/agent-ref.test.ts +0 -34
  340. package/src/shared/__tests__/meta-parser.test.ts +0 -304
  341. package/src/shared/__tests__/model-ref.test.ts +0 -306
  342. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -280
  343. package/src/shared/__tests__/resource-discovery.test.ts +0 -736
  344. package/src/shared/__tests__/resource-meta.test.ts +0 -51
  345. package/src/shared/__tests__/schema-jsonify.test.ts +0 -81
  346. package/src/shared/__tests__/timer-delay.test.ts +0 -61
  347. package/src/shared/agent-event.ts +0 -13
  348. package/src/shared/agent-ref.ts +0 -57
  349. package/src/shared/meta-parser.ts +0 -261
  350. package/src/shared/model-ref.ts +0 -286
  351. package/src/shared/resource-discovery.ts +0 -811
  352. package/src/shared/resource-meta.ts +0 -65
  353. package/src/shared/schema-env.ts +0 -44
  354. package/src/shared/schema-jsonify.ts +0 -58
  355. package/src/shared/timer-delay.ts +0 -54
  356. package/src/shared/xml-injection.ts +0 -35
  357. package/workflows/README.md +0 -81
  358. package/workflows/_shared/agent-refs.cjs +0 -40
  359. package/workflows/chain.js +0 -131
  360. package/workflows/map-reduce.js +0 -174
  361. package/workflows/parallel.js +0 -148
  362. package/workflows/review-fix-loop-utils.cjs +0 -1309
  363. package/workflows/review-fix-loop.js +0 -1404
  364. package/workflows/scatter-gather.js +0 -178
@@ -1,633 +0,0 @@
1
- // src/__tests__/worktree-manager.test.ts
2
- //
3
- // WorktreeManager 单元测试。
4
- // mock execFile(git 命令)+ WorktreeRegistry(注册表)+ alive-store(进程探活)。
5
- //
6
- // [全局注册表重构] scan 不再读 sidecar / 不再依赖 cwd 是否 git repo。
7
- // 判据从「终态 marker 状态机」改为「pid 死活」。
8
- // 测试重点覆盖原方案缺失的崩溃残留场景(P0)。
9
-
10
- import { beforeEach, describe, expect, it, vi } from "vitest";
11
-
12
- import { DirtyWorktreeError } from "../types.ts";
13
-
14
- // ── mock modules ──
15
-
16
- vi.mock("node:child_process", () => ({
17
- execFile: vi.fn(),
18
- }));
19
-
20
- vi.mock("node:fs", () => ({
21
- default: {
22
- existsSync: vi.fn(),
23
- rmSync: vi.fn(),
24
- symlinkSync: vi.fn(),
25
- writeFileSync: vi.fn(),
26
- },
27
- existsSync: vi.fn(),
28
- rmSync: vi.fn(),
29
- symlinkSync: vi.fn(),
30
- writeFileSync: vi.fn(),
31
- }));
32
-
33
- vi.mock("../alive-store.ts", () => ({
34
- isProcessAlive: vi.fn(),
35
- }));
36
-
37
- // WorktreeRegistry mock:内存数组模拟,add/updatePid/remove/load 全部可追踪
38
- const { mockLoad, mockAdd, mockUpdatePid, mockRemove, registryEntries } = vi.hoisted(() => {
39
- type Entry = { repo: string; branch: string; checkout: string; pid: number; createdAt: number; sessionFile?: string };
40
- const entries: Entry[] = [];
41
- return {
42
- registryEntries: entries,
43
- mockLoad: vi.fn((): Entry[] => entries.slice()),
44
- mockAdd: vi.fn((e: Entry): void => {
45
- const idx = entries.findIndex((x) => x.branch === e.branch);
46
- if (idx >= 0) entries[idx] = e;
47
- else entries.push(e);
48
- }),
49
- mockUpdatePid: vi.fn((branch: string, pid: number, sessionFile?: string): void => {
50
- const e = entries.find((x) => x.branch === branch);
51
- if (e) {
52
- e.pid = pid;
53
- if (sessionFile !== undefined) e.sessionFile = sessionFile;
54
- }
55
- }),
56
- mockRemove: vi.fn((branch: string): void => {
57
- const idx = entries.findIndex((x) => x.branch === branch);
58
- if (idx >= 0) entries.splice(idx, 1);
59
- }),
60
- };
61
- });
62
-
63
- vi.mock("../worktree-registry.ts", () => ({
64
- WorktreeRegistry: class {
65
- add = mockAdd;
66
- updatePid = mockUpdatePid;
67
- remove = mockRemove;
68
- load = mockLoad;
69
- },
70
- SPAWN_GRACE_MS: 60_000,
71
- }));
72
-
73
-
74
- import { execFile } from "node:child_process";
75
- import * as fs from "node:fs";
76
- import * as os from "node:os";
77
- import * as path from "node:path";
78
-
79
- import { isProcessAlive } from "../alive-store.ts";
80
- import { encodeCwd } from "../path-encoding.ts";
81
- import { WorktreeManager } from "../worktree-manager.ts";
82
-
83
- const mockExecFile = vi.mocked(execFile);
84
- const mockExistsSync = vi.mocked(fs.existsSync);
85
- const mockIsProcessAlive = vi.mocked(isProcessAlive);
86
-
87
- /**
88
- * gitRunAsync(execFile callback 风格)mock 装配。
89
- * impl 返回 {err?, stdout?, stderr?};err 模拟失败路径(exitCode=killed/signal 挂在 err 上)。
90
- */
91
- type ExecFileResult = {
92
- err?: (Error & { code?: unknown; killed?: boolean; signal?: string }) | null;
93
- stdout?: string;
94
- stderr?: string;
95
- };
96
- function setupExecFile(impl?: (args: readonly string[]) => ExecFileResult): void {
97
- mockExecFile.mockImplementation(
98
- (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout?: string, stderr?: string) => void) => {
99
- const r = impl ? impl(args) : { stdout: "" };
100
- if (r.err) cb(r.err, r.stdout ?? "", r.stderr ?? "");
101
- else cb(null, r.stdout ?? "", r.stderr ?? "");
102
- },
103
- );
104
- }
105
-
106
- const MAIN_CWD = "/home/user/project";
107
- const AGENT_DIR = "/home/user/.pi/agent";
108
- const RECORD_ID = "bg-42-abc";
109
- const BASE_COMMIT = "abc123def456";
110
-
111
- /** create 路径期望(tmpdir/pi-subagents/<enc(mainCwd)> 下) */
112
- function expectedCreatePath(recordId: string): string {
113
- return path.join(os.tmpdir(), "pi-subagents", encodeCwd(MAIN_CWD), `pi-sub-${recordId}`);
114
- }
115
-
116
- /** 构造完整 handle(含 mainCwd,供 cleanup/collectPatch 测试用) */
117
- function makeHandle(checkoutPath: string = expectedCreatePath(RECORD_ID)) {
118
- return Object.freeze({
119
- path: checkoutPath,
120
- branch: `pi-sub-${RECORD_ID}`,
121
- baseCommit: BASE_COMMIT,
122
- mainCwd: MAIN_CWD,
123
- });
124
- }
125
-
126
- function setupCleanTree(): void {
127
- setupExecFile((_args: readonly string[]) => {
128
- // rev-parse 真实输出形态:hash + \n(gitRunAsync 不 trim,消费点 trim 后才得干净 hash)
129
- if (_args?.[0] === "rev-parse" && _args?.[1] === "HEAD") return { stdout: `${BASE_COMMIT}\n` };
130
- return { stdout: "" };
131
- });
132
- // worktreePath 不存在(无需前置清理);node_modules 存在
133
- mockExistsSync.mockImplementation((p: unknown) => {
134
- const s = String(p);
135
- if (s.includes("pi-sub-")) return false; // checkout 目录不存在
136
- if (s.includes("node_modules")) return true;
137
- return false;
138
- });
139
- }
140
-
141
- /** 向注册表注入一条活条目(模拟 create 后的状态)。 */
142
- function injectEntry(overrides: Partial<{ branch: string; pid: number; checkout: string; repo: string; createdAt: number; sessionFile: string }> = {}): void {
143
- registryEntries.push({
144
- repo: overrides.repo ?? MAIN_CWD,
145
- branch: overrides.branch ?? "pi-sub-orphan1",
146
- checkout: overrides.checkout ?? path.join(os.tmpdir(), "pi-sub-orphan1"),
147
- pid: overrides.pid ?? 0,
148
- createdAt: overrides.createdAt ?? Date.now(),
149
- sessionFile: overrides.sessionFile,
150
- });
151
- }
152
-
153
- describe("WorktreeManager", () => {
154
- let mgr: WorktreeManager;
155
-
156
- beforeEach(() => {
157
- vi.clearAllMocks();
158
- registryEntries.length = 0;
159
- mgr = new WorktreeManager(AGENT_DIR);
160
- });
161
-
162
- describe("create", () => {
163
- it("正常流程返回冻结 handle(path 在 tmpdir,含 mainCwd)", async () => {
164
- setupCleanTree();
165
-
166
- const handle = await mgr.create(MAIN_CWD, RECORD_ID);
167
-
168
- expect(handle.path).toBe(expectedCreatePath(RECORD_ID));
169
- expect(handle.branch).toBe(`pi-sub-${RECORD_ID}`);
170
- expect(handle.baseCommit).toBe(BASE_COMMIT);
171
- expect(handle.mainCwd).toBe(MAIN_CWD);
172
- expect(Object.isFrozen(handle)).toBe(true);
173
- });
174
-
175
- it("成功后写入注册表(pid=0 占位)", async () => {
176
- setupCleanTree();
177
-
178
- await mgr.create(MAIN_CWD, RECORD_ID);
179
-
180
- expect(mockAdd).toHaveBeenCalledTimes(1);
181
- const entry = mockAdd.mock.calls[0][0] as { repo: string; branch: string; pid: number };
182
- expect(entry.repo).toBe(MAIN_CWD);
183
- expect(entry.branch).toBe(`pi-sub-${RECORD_ID}`);
184
- expect(entry.pid).toBe(0);
185
- });
186
-
187
- it("脏树抛 DirtyWorktreeError 且不写注册表(status 与 rev-parse 并行,均发起)", async () => {
188
- setupExecFile((args: readonly string[]) => {
189
- if (args[0] === "status") return { stdout: "M src/index.ts\n" };
190
- return { stdout: "" };
191
- });
192
-
193
- // async create:同步 throw 变 rejected promise
194
- await expect(mgr.create(MAIN_CWD, RECORD_ID)).rejects.toThrow(DirtyWorktreeError);
195
- expect(mockAdd).not.toHaveBeenCalled();
196
- // allSettled 并行:两条读命令都发起(rev-parse 结果在脏树时被丢弃)
197
- const cmds = mockExecFile.mock.calls.map((c) => `${c[1]?.[0]} ${c[1]?.[1] ?? ""}`);
198
- expect(cmds).toContain("status --porcelain");
199
- expect(cmds).toContain("rev-parse HEAD");
200
- });
201
-
202
- it("recordId 含特殊字符抛 DirtyWorktreeError", async () => {
203
- await expect(mgr.create(MAIN_CWD, "../evil-path")).rejects.toThrow(DirtyWorktreeError);
204
- await expect(mgr.create(MAIN_CWD, "hello world")).rejects.toThrow(DirtyWorktreeError);
205
- await expect(mgr.create(MAIN_CWD, "")).rejects.toThrow(DirtyWorktreeError);
206
- await expect(mgr.create(MAIN_CWD, "a;b")).rejects.toThrow(DirtyWorktreeError);
207
- });
208
-
209
- it("recordId 单字符 / 连续短横线合法", async () => {
210
- setupCleanTree();
211
- expect((await mgr.create(MAIN_CWD, "a")).branch).toBe("pi-sub-a");
212
- expect((await mgr.create(MAIN_CWD, "--test--")).branch).toBe("pi-sub---test--");
213
- });
214
-
215
- it("残留 checkout 目录存在时前置清理(fs.rmSync)", async () => {
216
- setupCleanTree();
217
- // worktreePath 已存在 → 触发前置 rmSync
218
- mockExistsSync.mockImplementation((p: unknown) => {
219
- const s = String(p);
220
- if (s.includes("pi-sub-bg-42-abc")) return true; // checkout 残留
221
- if (s.includes("node_modules")) return true;
222
- return false;
223
- });
224
-
225
- await mgr.create(MAIN_CWD, RECORD_ID);
226
-
227
- expect(fs.rmSync).toHaveBeenCalledWith(
228
- expectedCreatePath(RECORD_ID),
229
- { recursive: true, force: true },
230
- );
231
- });
232
-
233
- it("symlink 失败时回滚 worktree + 分支 + 注册表(回滚经 gitRunAsync)", async () => {
234
- setupCleanTree();
235
- // symlink 抛错触发 MF#3 回滚
236
- vi.mocked(fs.symlinkSync).mockImplementation(() => {
237
- throw new Error("symlink permission denied");
238
- });
239
-
240
- await expect(mgr.create(MAIN_CWD, RECORD_ID)).rejects.toThrow("symlink permission denied");
241
-
242
- // 回滚:worktree remove + branch delete(异步路径)+ registry remove
243
- expect(mockExecFile).toHaveBeenCalledWith(
244
- "git",
245
- expect.arrayContaining(["worktree", "remove", "--force"]),
246
- expect.anything(),
247
- expect.anything(),
248
- );
249
- expect(mockExecFile).toHaveBeenCalledWith(
250
- "git",
251
- expect.arrayContaining(["branch", "-D"]),
252
- expect.anything(),
253
- expect.anything(),
254
- );
255
- expect(mockRemove).toHaveBeenCalledWith(`pi-sub-${RECORD_ID}`);
256
- });
257
- it("per-repo mutex:同 repo 并发 create 的 worktree add 串行(maxActive=1)", async () => {
258
- // clearAllMocks 不清 mockImplementation——重置前例注入的 symlinkSync throw,本用例走完整 create
259
- vi.mocked(fs.symlinkSync).mockImplementation(() => {});
260
- mockExistsSync.mockImplementation((p: unknown) => {
261
- const s = String(p);
262
- if (s.includes("node_modules")) return true;
263
- return false;
264
- });
265
- let writeActive = 0;
266
- let maxWriteActive = 0;
267
- let addCount = 0;
268
- mockExecFile.mockImplementation(
269
- (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout?: string, stderr?: string) => void) => {
270
- const isAdd = args[0] === "worktree" && args[1] === "add";
271
- if (isAdd) {
272
- // 只对写命令(worktree add)计并发——读命令(status/rev-parse)无锁可并行是设计预期
273
- writeActive++;
274
- maxWriteActive = Math.max(maxWriteActive, writeActive);
275
- addCount++;
276
- }
277
- setTimeout(() => {
278
- if (isAdd) writeActive--;
279
- // rev-parse 真实输出带尾换行;baseCommit 由消费点 trim
280
- cb(null, args[0] === "rev-parse" ? `${BASE_COMMIT}\n` : "", "");
281
- }, 5);
282
- },
283
- );
284
-
285
- const handles = await Promise.all([
286
- mgr.create(MAIN_CWD, "con-a"),
287
- mgr.create(MAIN_CWD, "con-b"),
288
- ]);
289
-
290
- // 两个 create 全部成功、branch 不同
291
- expect(handles[0].branch).toBe("pi-sub-con-a");
292
- expect(handles[1].branch).toBe("pi-sub-con-b");
293
- // 同 repo 的 2 个 worktree add 全部执行且互斥(per-repo mutex)
294
- expect(addCount).toBe(2);
295
- expect(maxWriteActive).toBe(1);
296
- });
297
- });
298
-
299
- describe("registerPid", () => {
300
- it("委托 registry.updatePid", () => {
301
- mgr.registerPid("pi-sub-bg-1", 12345);
302
- expect(mockUpdatePid).toHaveBeenCalledWith("pi-sub-bg-1", 12345, undefined);
303
- });
304
- });
305
-
306
- describe("cleanup", () => {
307
- it("worktree remove + branch -D + 注册表移除(cwd 用 handle.mainCwd)", async () => {
308
- setupExecFile();
309
- const handle = makeHandle();
310
-
311
- await mgr.cleanup(handle);
312
-
313
- expect(mockExecFile).toHaveBeenCalledWith(
314
- "git",
315
- ["worktree", "remove", "--force", handle.path],
316
- expect.objectContaining({ cwd: MAIN_CWD }),
317
- expect.anything(),
318
- );
319
- expect(mockExecFile).toHaveBeenCalledWith(
320
- "git",
321
- ["branch", "-D", handle.branch],
322
- expect.objectContaining({ cwd: MAIN_CWD }),
323
- expect.anything(),
324
- );
325
- expect(mockRemove).toHaveBeenCalledWith(handle.branch);
326
- });
327
-
328
- it("worktree remove 失败时 branch -D + 注册表移除仍执行(best-effort 分离)", async () => {
329
- // 第一条 git(worktree remove)失败,第二条(branch -D)应仍执行
330
- setupExecFile((args) => {
331
- if (args[1] === "remove") {
332
- return { err: Object.assign(new Error("Command failed: git worktree remove"), { code: 128 }), stderr: "fatal: worktree locked" };
333
- }
334
- return { stdout: "" };
335
- });
336
- const handle = makeHandle();
337
-
338
- // 不应抛错
339
- await expect(mgr.cleanup(handle)).resolves.toBeUndefined();
340
-
341
- // branch -D 仍被调用
342
- expect(mockExecFile).toHaveBeenCalledWith(
343
- "git",
344
- ["branch", "-D", handle.branch],
345
- expect.anything(),
346
- expect.anything(),
347
- );
348
- // 注册表仍被移除
349
- expect(mockRemove).toHaveBeenCalledWith(handle.branch);
350
- });
351
- });
352
-
353
- describe("collectPatch", () => {
354
- it("有改动返回 patch 文件", async () => {
355
- setupExecFile((args) => (args[0] === "diff" ? { stdout: "diff --git a/src\n+// new" } : { stdout: "" }));
356
-
357
- const handle = makeHandle();
358
- const patchFile = path.join(os.tmpdir(), `outside-${RECORD_ID}.patch`);
359
-
360
- const result = await mgr.collectPatch(handle, patchFile);
361
-
362
- expect(result.failed).toBe(false);
363
- expect(result.written).toBe(true);
364
- expect(mockExecFile).toHaveBeenCalledWith("git", ["add", "-A"], expect.anything(), expect.anything());
365
- expect(mockExecFile).toHaveBeenCalledWith(
366
- "git",
367
- ["diff", "--cached", BASE_COMMIT],
368
- expect.anything(),
369
- expect.anything(),
370
- );
371
- });
372
-
373
- it("无改动返回 failed=false, written=false(不写文件)", async () => {
374
- setupExecFile();
375
- const result = await mgr.collectPatch(makeHandle(), "/tmp/x.patch");
376
- expect(result.failed).toBe(false);
377
- expect(result.written).toBe(false);
378
- });
379
-
380
- it("patch 内容 = git diff stdout 原文(含尾部换行不被裁剪),git apply --check 兼容", async () => {
381
- // 真实 git diff 输出以 \n 结尾;旧实现 gitRunAsync resolve(stdout.trim())
382
- // 裁掉尾换行 → 落盘 patch 在干净副本上 `git apply --check` 报
383
- // "corrupt patch at line N"(2026-08-16 门 4 实测,exit 128)
384
- const rawDiff =
385
- "diff --git a/data.txt b/data.txt\n" +
386
- "index c690e0d..f858a88 100644\n" +
387
- "--- a/data.txt\n" +
388
- "+++ b/data.txt\n" +
389
- "@@ -1 +1,2 @@\n" +
390
- " base content\n" +
391
- "+appended\n";
392
- setupExecFile((args) => (args[0] === "diff" ? { stdout: rawDiff } : { stdout: "" }));
393
-
394
- await mgr.collectPatch(makeHandle(), "/tmp/x.patch");
395
-
396
- // 写盘字节与 diff stdout 逐字节一致(保真 = 不裁剪两端空白)
397
- const written = vi.mocked(fs.writeFileSync).mock.calls[0][1] as string;
398
- expect(written).toBe(rawDiff);
399
- expect(written.endsWith("\n")).toBe(true);
400
- });
401
- });
402
-
403
- // ============================================================
404
- // gitRunAsync 包装与 per-repo mutex(Phase 1 异步化新增)
405
- // ============================================================
406
- describe("gitRunAsync(经 collectPatch/cleanup 公共面触达)", () => {
407
- it("非零退出:message 同构 + exitCode/stderr 属性(P-errshape:stderr 在 callback 第三参)", async () => {
408
- setupExecFile((args) => {
409
- if (args[0] === "diff") {
410
- return {
411
- err: Object.assign(new Error("Command failed: git diff --cached abc123def456\nfatal: bad object"), { code: 128 }),
412
- stderr: "fatal: bad object",
413
- };
414
- }
415
- return { stdout: "" };
416
- });
417
-
418
- // diff 无 try/catch,直接向上抛(与旧同步 gitRun 行为一致)
419
- const err = await mgr.collectPatch(makeHandle(), "/tmp/x.patch").catch((e: unknown) => e);
420
- expect(err).toBeInstanceOf(Error);
421
- expect((err as Error).message).toContain("git diff failed: Command failed: git diff --cached");
422
- expect((err as { exitCode?: number }).exitCode).toBe(128);
423
- expect((err as { stderr?: string }).stderr).toBe("fatal: bad object");
424
- });
425
-
426
- it("超时路径:killed+SIGTERM 标记 timedOut,不挂误导性 exitCode", async () => {
427
- setupExecFile((args) => {
428
- if (args[0] === "diff") {
429
- return { err: Object.assign(new Error("Command failed: git diff --cached"), { killed: true, signal: "SIGTERM", code: null }) };
430
- }
431
- return { stdout: "" };
432
- });
433
-
434
- const err = await mgr.collectPatch(makeHandle(), "/tmp/x.patch").catch((e: unknown) => e);
435
- expect((err as Error).message).toContain("git diff failed:");
436
- expect((err as { timedOut?: boolean }).timedOut).toBe(true);
437
- expect((err as { exitCode?: number }).exitCode).toBeUndefined();
438
- });
439
-
440
- it("per-repo mutex:同 repo 写类命令串行(最大并发 1),全部完成无饥饿", async () => {
441
- let active = 0;
442
- let maxActive = 0;
443
- // 异步完成 cb:若队列失效,后继命令的 impl 入口会先于前驱 cb 到达 → active=2
444
- mockExecFile.mockImplementation(
445
- (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout?: string, stderr?: string) => void) => {
446
- active++;
447
- maxActive = Math.max(maxActive, active);
448
- setTimeout(() => {
449
- active--;
450
- cb(null, "", "");
451
- }, 5);
452
- void args;
453
- },
454
- );
455
-
456
- const distinct = [1, 2, 3].map((i) =>
457
- Object.freeze({ ...makeHandle(path.join(os.tmpdir(), `pi-sub-mut-${i}`)), branch: `pi-sub-mut-${i}` }),
458
- );
459
- await Promise.all(distinct.map((h) => mgr.cleanup(h)));
460
-
461
- // 同 repo 6 条写命令(3×(remove+branch -D))全部执行(无饥饿)
462
- const writeCalls = mockExecFile.mock.calls.filter(
463
- (c) => (c[1]?.[0] === "worktree" && c[1]?.[1] === "remove") || (c[1]?.[0] === "branch" && c[1]?.[1] === "-D"),
464
- );
465
- expect(writeCalls).toHaveLength(6);
466
- // 队列生效:任何时刻至多 1 条写命令在途
467
- expect(maxActive).toBe(1);
468
- });
469
-
470
- it("mutex 前驱失败不传染后继:第一个 remove 失败,后续同 repo 写命令正常执行", async () => {
471
- let removeCount = 0;
472
- setupExecFile((args) => {
473
- if (args[0] === "worktree" && args[1] === "remove") {
474
- removeCount++;
475
- if (removeCount === 1) {
476
- return { err: Object.assign(new Error("Command failed: git worktree remove"), { code: 128 }) };
477
- }
478
- }
479
- return { stdout: "" };
480
- });
481
-
482
- const h1 = Object.freeze({ ...makeHandle(), branch: "pi-sub-fail-1" });
483
- const h2 = Object.freeze({ ...makeHandle(), branch: "pi-sub-fail-2" });
484
- await Promise.all([mgr.cleanup(h1), mgr.cleanup(h2)]);
485
-
486
- // h2 的 remove 正常执行(第 2 次),h1/h2 的 branch -D 各自执行
487
- expect(removeCount).toBe(2);
488
- const branchCalls = mockExecFile.mock.calls.filter((c) => c[1]?.[0] === "branch" && c[1]?.[1] === "-D");
489
- expect(branchCalls).toHaveLength(2);
490
- // 两个注册表条目都移除(各自 cleanup 尾部执行)
491
- expect(mockRemove).toHaveBeenCalledWith("pi-sub-fail-1");
492
- expect(mockRemove).toHaveBeenCalledWith("pi-sub-fail-2");
493
- });
494
- });
495
-
496
- // ============================================================
497
- // scan:全局注册表 + pid 死活判据(核心重构)
498
- // ============================================================
499
- describe("scan", () => {
500
- it("pid 已死的条目被清理(正常退出未 cleanup / 崩溃残留)", async () => {
501
- setupExecFile();
502
- injectEntry({ branch: "pi-sub-dead", pid: 11111 });
503
- mockIsProcessAlive.mockReturnValue(false); // pid 死
504
-
505
- await mgr.scan();
506
-
507
- expect(mockExecFile).toHaveBeenCalledWith(
508
- "git",
509
- ["worktree", "remove", "--force", expect.any(String)],
510
- expect.objectContaining({ cwd: MAIN_CWD }),
511
- expect.anything(),
512
- );
513
- expect(mockExecFile).toHaveBeenCalledWith(
514
- "git",
515
- ["branch", "-D", "pi-sub-dead"],
516
- expect.objectContaining({ cwd: MAIN_CWD }),
517
- expect.anything(),
518
- );
519
- expect(mockRemove).toHaveBeenCalledWith("pi-sub-dead");
520
- });
521
-
522
- it("pid 活的条目不删(绝不删活进程)", async () => {
523
- // 物理面也 mock 存在(branch --list 返回该分支 + checkout 目录在)——
524
- // D5b 对账的两个方向(清孤儿 / 清幻影条目)都必须不动活条目。
525
- setupExecFile((args: readonly string[]) => {
526
- if (args[0] === "branch" && args[1] === "--list") return { stdout: "pi-sub-alive\n" };
527
- return { stdout: "" };
528
- });
529
- const checkout = path.join(os.tmpdir(), "pi-sub-orphan1");
530
- injectEntry({ branch: "pi-sub-alive", pid: 22222, checkout });
531
- mockIsProcessAlive.mockReturnValue(true); // pid 活
532
- mockExistsSync.mockImplementation((p: unknown) => String(p) === checkout);
533
-
534
- await mgr.scan();
535
-
536
- const removeCalls = mockExecFile.mock.calls.filter(
537
- (c) => c[1]?.[0] === "worktree" && c[1]?.[1] === "remove",
538
- );
539
- expect(removeCalls).toHaveLength(0);
540
- expect(mockRemove).not.toHaveBeenCalled();
541
- });
542
-
543
- it("崩溃残留:无终态 + pid 死 → 清理(原 P0 缺陷场景)", async () => {
544
- // 这是原 reaper 永远泄漏的场景:进程崩溃无人写终态 marker
545
- setupExecFile();
546
- injectEntry({ branch: "pi-sub-crash", pid: 33333 });
547
- mockIsProcessAlive.mockReturnValue(false); // 崩溃后进程已死
548
-
549
- await mgr.scan();
550
-
551
- expect(mockExecFile).toHaveBeenCalledWith(
552
- "git",
553
- ["branch", "-D", "pi-sub-crash"],
554
- expect.anything(),
555
- expect.anything(),
556
- );
557
- expect(mockRemove).toHaveBeenCalledWith("pi-sub-crash");
558
- });
559
-
560
- it("pid=0 + 未超 SPAWN_GRACE → 跳过(可能正在 spawn)", async () => {
561
- setupExecFile();
562
- injectEntry({ branch: "pi-sub-spawning", pid: 0, createdAt: Date.now() });
563
-
564
- await mgr.scan();
565
-
566
- const removeCalls = mockExecFile.mock.calls.filter(
567
- (c) => c[1]?.[0] === "worktree" && c[1]?.[1] === "remove",
568
- );
569
- expect(removeCalls).toHaveLength(0);
570
- });
571
-
572
- it("pid=0 + 超 SPAWN_GRACE → 清理(create 后崩溃)", async () => {
573
- setupExecFile();
574
- const sixtyOneMinAgo = Date.now() - 61 * 60 * 1000;
575
- injectEntry({ branch: "pi-sub-spawn-crash", pid: 0, createdAt: sixtyOneMinAgo });
576
-
577
- await mgr.scan();
578
-
579
- expect(mockExecFile).toHaveBeenCalledWith(
580
- "git",
581
- ["branch", "-D", "pi-sub-spawn-crash"],
582
- expect.anything(),
583
- expect.anything(),
584
- );
585
- expect(mockRemove).toHaveBeenCalledWith("pi-sub-spawn-crash");
586
- });
587
-
588
- it("跨 repo 清理:条目 repo 字段作为 git -C 目标", async () => {
589
- setupExecFile();
590
- const OTHER_REPO = "/home/user/other-repo";
591
- injectEntry({ branch: "pi-sub-cross", pid: 44444, repo: OTHER_REPO });
592
- mockIsProcessAlive.mockReturnValue(false);
593
-
594
- await mgr.scan();
595
-
596
- // git 命令的 cwd 应为 OTHER_REPO,非 MAIN_CWD
597
- expect(mockExecFile).toHaveBeenCalledWith(
598
- "git",
599
- expect.arrayContaining(["branch", "-D", "pi-sub-cross"]),
600
- expect.objectContaining({ cwd: OTHER_REPO }),
601
- expect.anything(),
602
- );
603
- });
604
-
605
- it("空注册表时无操作", async () => {
606
- setupExecFile();
607
- // registryEntries 已在 beforeEach 清空
608
- await mgr.scan();
609
- expect(mockExecFile).not.toHaveBeenCalled();
610
- expect(mockRemove).not.toHaveBeenCalled();
611
- });
612
-
613
- it("worktree remove 失败时 branch -D + 注册表移除仍执行(best-effort)", async () => {
614
- setupExecFile((args) => {
615
- if (args[0] === "worktree") {
616
- return { err: Object.assign(new Error("Command failed: git worktree remove"), { code: 128 }) };
617
- }
618
- return { stdout: "" };
619
- });
620
- injectEntry({ branch: "pi-sub-stubborn", pid: 55555 });
621
- mockIsProcessAlive.mockReturnValue(false);
622
-
623
- await mgr.scan();
624
-
625
- // branch -D 仍被调用(两次 git 调用各自独立)
626
- const branchDeleteCalls = mockExecFile.mock.calls.filter(
627
- (c) => c[1]?.[0] === "branch" && c[1]?.[1] === "-D",
628
- );
629
- expect(branchDeleteCalls).toHaveLength(1);
630
- expect(mockRemove).toHaveBeenCalledWith("pi-sub-stubborn");
631
- });
632
- });
633
- });