@zhushanwen/pi-subagent-workflow 8.6.0 → 8.7.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 (347) hide show
  1. package/package.json +9 -13
  2. package/relay/relay.mjs +1 -1
  3. package/src/host/__tests__/pi-host.test.ts +214 -0
  4. package/src/host/pi-host.ts +109 -0
  5. package/src/index.ts +89 -32
  6. package/src/injectors/__tests__/engine-awareness.test.ts +292 -0
  7. package/src/injectors/__tests__/engine-section-stability.test.ts +341 -0
  8. package/src/injectors/__tests__/subagent-list-injector.test.ts +5 -5
  9. package/src/injectors/__tests__/workflow-list-injector.test.ts +5 -5
  10. package/src/injectors/engine-awareness.ts +123 -0
  11. package/src/injectors/model-list-injector.ts +1 -1
  12. package/src/injectors/subagent-list-injector.ts +8 -8
  13. package/src/injectors/workflow-list-injector.ts +8 -8
  14. package/src/interface/__tests__/detectors.test.ts +3 -3
  15. package/src/interface/__tests__/subagent-tool-path-guard.test.ts +2 -2
  16. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +2 -2
  17. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +1 -1
  18. package/src/interface/bg-notify-render.ts +4 -4
  19. package/src/interface/commands.ts +3 -3
  20. package/src/interface/format.ts +2 -2
  21. package/src/interface/helpers.ts +1 -1
  22. package/src/interface/list-component.ts +4 -4
  23. package/src/interface/list-shared.ts +2 -2
  24. package/src/interface/list-view.ts +2 -2
  25. package/src/interface/subagent-actions.ts +263 -11
  26. package/src/interface/subagent-tool-schema.ts +20 -13
  27. package/src/interface/subagent-tool.ts +11 -6
  28. package/src/interface/subagents.ts +3 -3
  29. package/src/interface/tool-render.ts +34 -2
  30. package/src/interface/tool-workflow-script.ts +4 -4
  31. package/src/interface/tool-workflow.ts +6 -6
  32. package/src/interface/views/WorkflowsView.ts +5 -5
  33. package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
  34. package/src/interface/views/__tests__/detail-content-session-file.test.ts +2 -2
  35. package/src/interface/views/detail-content.ts +4 -4
  36. package/src/interface/views/format.ts +4 -4
  37. package/src/{orchestration/jsonl-run-store.ts → jsonl-run-store.ts} +11 -11
  38. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +0 -53
  39. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +0 -1
  40. package/src/execution/__tests__/agent-registry.test.ts +0 -363
  41. package/src/execution/__tests__/agent-result-mapper.test.ts +0 -150
  42. package/src/execution/__tests__/alive-store.test.ts +0 -147
  43. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  44. package/src/execution/__tests__/before-agent-start-injection.test.ts +0 -132
  45. package/src/execution/__tests__/bg-notify-render.test.ts +0 -329
  46. package/src/execution/__tests__/channel-registry-handshake.test.ts +0 -243
  47. package/src/execution/__tests__/chat-engine-routing.test.ts +0 -601
  48. package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +0 -365
  49. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -190
  50. package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +0 -215
  51. package/src/execution/__tests__/concurrency-pool.test.ts +0 -250
  52. package/src/execution/__tests__/config.test.ts +0 -110
  53. package/src/execution/__tests__/conversation-wiring.test.ts +0 -198
  54. package/src/execution/__tests__/crash-recovery.test.ts +0 -320
  55. package/src/execution/__tests__/delivery-methods.test.ts +0 -422
  56. package/src/execution/__tests__/dialog-queue.test.ts +0 -299
  57. package/src/execution/__tests__/epipe-fallback.test.ts +0 -241
  58. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -243
  59. package/src/execution/__tests__/execute-nesting.test.ts +0 -323
  60. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  61. package/src/execution/__tests__/execution-record.test.ts +0 -1394
  62. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +0 -171
  63. package/src/execution/__tests__/finalize-record.test.ts +0 -368
  64. package/src/execution/__tests__/finalized-marker.test.ts +0 -82
  65. package/src/execution/__tests__/format-schema-instruction.test.ts +0 -169
  66. package/src/execution/__tests__/format.test.ts +0 -458
  67. package/src/execution/__tests__/gc-timer.test.ts +0 -184
  68. package/src/execution/__tests__/get-record-for-action-restart.test.ts +0 -254
  69. package/src/execution/__tests__/gui-mode-dispatch.test.ts +0 -59
  70. package/src/execution/__tests__/helpers/mock-extension-api.ts +0 -30
  71. package/src/execution/__tests__/helpers/spawn-mock.ts +0 -242
  72. package/src/execution/__tests__/host-mode.test.ts +0 -87
  73. package/src/execution/__tests__/index-session-start-identity.test.ts +0 -371
  74. package/src/execution/__tests__/index-session-start.test.ts +0 -672
  75. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  76. package/src/execution/__tests__/lifecycle-manager.test.ts +0 -383
  77. package/src/execution/__tests__/lifecycle-predicates.test.ts +0 -116
  78. package/src/execution/__tests__/list-component.test.ts +0 -401
  79. package/src/execution/__tests__/list-fields.test.ts +0 -140
  80. package/src/execution/__tests__/manifest-parentid.test.ts +0 -117
  81. package/src/execution/__tests__/model-resolver.test.ts +0 -465
  82. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +0 -287
  83. package/src/execution/__tests__/nested-visibility.test.ts +0 -325
  84. package/src/execution/__tests__/notifier-flush.test.ts +0 -612
  85. package/src/execution/__tests__/notifier-golden-snapshot.test.ts +0 -261
  86. package/src/execution/__tests__/notify-ledger.test.ts +0 -826
  87. package/src/execution/__tests__/one-shot-upgrade.test.ts +0 -205
  88. package/src/execution/__tests__/output-collector.test.ts +0 -358
  89. package/src/execution/__tests__/parent-child-matrix.test.ts +0 -336
  90. package/src/execution/__tests__/path-encoding.test.ts +0 -104
  91. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  92. package/src/execution/__tests__/record-store.test.ts +0 -1057
  93. package/src/execution/__tests__/records-cwd-isolation.test.ts +0 -91
  94. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +0 -339
  95. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -273
  96. package/src/execution/__tests__/relay-agent.test.ts +0 -448
  97. package/src/execution/__tests__/relay-env.test.ts +0 -42
  98. package/src/execution/__tests__/resource-policy.test.ts +0 -109
  99. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  100. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -267
  101. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -253
  102. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -687
  103. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  104. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  105. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  106. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -199
  107. package/src/execution/__tests__/sdk-contract.test.ts +0 -273
  108. package/src/execution/__tests__/session-context-resolver.test.ts +0 -167
  109. package/src/execution/__tests__/session-file-gc.test.ts +0 -293
  110. package/src/execution/__tests__/session-pending.test.ts +0 -197
  111. package/src/execution/__tests__/session-reconstructor.test.ts +0 -379
  112. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -178
  113. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -350
  114. package/src/execution/__tests__/session-start-reaper.test.ts +0 -233
  115. package/src/execution/__tests__/spawn-args.test.ts +0 -445
  116. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -189
  117. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  118. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -207
  119. package/src/execution/__tests__/spawned-children.test.ts +0 -92
  120. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  121. package/src/execution/__tests__/startup-config-declaration.test.ts +0 -35
  122. package/src/execution/__tests__/status-refactor.test.ts +0 -345
  123. package/src/execution/__tests__/stdin-writer.test.ts +0 -463
  124. package/src/execution/__tests__/stream-sink-retirement.test.ts +0 -261
  125. package/src/execution/__tests__/subagent-service-abort.test.ts +0 -60
  126. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -629
  127. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +0 -180
  128. package/src/execution/__tests__/subagent-service.test.ts +0 -788
  129. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -310
  130. package/src/execution/__tests__/subprocess-agent-runner.test.ts +0 -599
  131. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  132. package/src/execution/__tests__/timeout-integration.test.ts +0 -615
  133. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  134. package/src/execution/__tests__/tool-action.test.ts +0 -476
  135. package/src/execution/__tests__/truncline-snapshot.test.ts +0 -81
  136. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  137. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  138. package/src/execution/__tests__/ui-channels.test.ts +0 -187
  139. package/src/execution/__tests__/ui-interaction-model.test.ts +0 -67
  140. package/src/execution/__tests__/ui-request-handler-factory.test.ts +0 -370
  141. package/src/execution/__tests__/ui-request-handler.test.ts +0 -204
  142. package/src/execution/__tests__/ui-request-observability.test.ts +0 -114
  143. package/src/execution/__tests__/ui-request-queue.test.ts +0 -133
  144. package/src/execution/__tests__/worktree-manager.test.ts +0 -633
  145. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -229
  146. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +0 -181
  147. package/src/execution/__tests__/worktree-registry.test.ts +0 -199
  148. package/src/execution/agent-registry.ts +0 -199
  149. package/src/execution/agent-result-mapper.ts +0 -90
  150. package/src/execution/alive-store.ts +0 -92
  151. package/src/execution/argv-mirror.ts +0 -113
  152. package/src/execution/best-effort.ts +0 -38
  153. package/src/execution/channel-registry-access.ts +0 -144
  154. package/src/execution/concurrency-pool.ts +0 -116
  155. package/src/execution/config.ts +0 -96
  156. package/src/execution/dialog-queue.ts +0 -330
  157. package/src/execution/engine/__tests__/common/data-dir.test.ts +0 -53
  158. package/src/execution/engine/__tests__/common/errors.test.ts +0 -132
  159. package/src/execution/engine/__tests__/common/event-journal.test.ts +0 -177
  160. package/src/execution/engine/__tests__/common/kill-chain.test.ts +0 -192
  161. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +0 -81
  162. package/src/execution/engine/__tests__/common/persona-router.test.ts +0 -123
  163. package/src/execution/engine/__tests__/common/pool-manager.test.ts +0 -154
  164. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  165. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  166. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +0 -141
  167. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +0 -109
  168. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +0 -101
  169. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +0 -77
  170. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +0 -104
  171. package/src/execution/engine/__tests__/conformance/contract.relay.test.ts +0 -342
  172. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +0 -201
  173. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +0 -76
  174. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +0 -79
  175. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -87
  176. package/src/execution/engine/__tests__/engines-declaration.test.ts +0 -36
  177. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -85
  178. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  179. package/src/execution/engine/__tests__/registry.test.ts +0 -120
  180. package/src/execution/engine/__tests__/routing.test.ts +0 -231
  181. package/src/execution/engine/common/data-dir.ts +0 -62
  182. package/src/execution/engine/common/errors.ts +0 -183
  183. package/src/execution/engine/common/event-journal.ts +0 -254
  184. package/src/execution/engine/common/journal-replay.ts +0 -62
  185. package/src/execution/engine/common/kill-chain.ts +0 -221
  186. package/src/execution/engine/common/nesting-guard.ts +0 -50
  187. package/src/execution/engine/common/persona-router.ts +0 -108
  188. package/src/execution/engine/common/pool-manager.ts +0 -226
  189. package/src/execution/engine/common/schema-emulation.ts +0 -189
  190. package/src/execution/engine/common/session-view-projection.ts +0 -51
  191. package/src/execution/engine/engine-discovery.ts +0 -65
  192. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -469
  193. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  194. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  195. package/src/execution/engine/engines/pi/pi-engine.ts +0 -415
  196. package/src/execution/engine/engines/pi/reader.ts +0 -48
  197. package/src/execution/engine/engines/pi/registration.ts +0 -35
  198. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  199. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  200. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  201. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -246
  202. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -228
  203. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  204. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -64
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -127
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  207. package/src/execution/engine/engines/zcode/constants.ts +0 -43
  208. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -39
  209. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  210. package/src/execution/engine/engines/zcode/parser.ts +0 -436
  211. package/src/execution/engine/engines/zcode/preparer.ts +0 -363
  212. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  213. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  214. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -658
  215. package/src/execution/engine/host-task-spec.ts +0 -47
  216. package/src/execution/engine/model-prompt.ts +0 -59
  217. package/src/execution/engine/paths.ts +0 -42
  218. package/src/execution/engine/port.ts +0 -153
  219. package/src/execution/engine/registry.ts +0 -123
  220. package/src/execution/engine/routing.ts +0 -218
  221. package/src/execution/engine/types.ts +0 -309
  222. package/src/execution/execute-options-mapper.ts +0 -112
  223. package/src/execution/execution-record.ts +0 -965
  224. package/src/execution/finalize-record.ts +0 -251
  225. package/src/execution/finalized-marker.ts +0 -51
  226. package/src/execution/get-state-handshake.ts +0 -104
  227. package/src/execution/host-mode.ts +0 -56
  228. package/src/execution/idle-gc.ts +0 -47
  229. package/src/execution/lifecycle-manager.ts +0 -513
  230. package/src/execution/lifecycle-predicates.ts +0 -65
  231. package/src/execution/manifest-store.ts +0 -256
  232. package/src/execution/model-config-service.ts +0 -219
  233. package/src/execution/model-resolver.ts +0 -248
  234. package/src/execution/notifier.ts +0 -330
  235. package/src/execution/notify-ledger.ts +0 -580
  236. package/src/execution/output-collector.ts +0 -228
  237. package/src/execution/path-encoding.ts +0 -60
  238. package/src/execution/pi-invocation.ts +0 -120
  239. package/src/execution/record-entry.ts +0 -132
  240. package/src/execution/record-store.ts +0 -1234
  241. package/src/execution/relay-env.ts +0 -37
  242. package/src/execution/session-context-resolver.ts +0 -64
  243. package/src/execution/session-file-gc.ts +0 -120
  244. package/src/execution/session-pending.ts +0 -164
  245. package/src/execution/session-reconstructor.ts +0 -678
  246. package/src/execution/session-runner.ts +0 -1781
  247. package/src/execution/sessions-index.ts +0 -304
  248. package/src/execution/spawn-event-adapter.ts +0 -363
  249. package/src/execution/stdin-writer.ts +0 -198
  250. package/src/execution/stream-sink.ts +0 -126
  251. package/src/execution/subagent-service.ts +0 -2141
  252. package/src/execution/subprocess-agent-runner.ts +0 -317
  253. package/src/execution/temp-prompt.ts +0 -62
  254. package/src/execution/tombstone-store.ts +0 -72
  255. package/src/execution/turn-limiter.ts +0 -102
  256. package/src/execution/types.ts +0 -973
  257. package/src/execution/ui-channels.ts +0 -216
  258. package/src/execution/ui-interaction-model.ts +0 -48
  259. package/src/execution/ui-request-handler-factory.ts +0 -212
  260. package/src/execution/ui-request-observability.ts +0 -81
  261. package/src/execution/ui-request-queue.ts +0 -184
  262. package/src/execution/worktree-manager.ts +0 -688
  263. package/src/execution/worktree-registry.ts +0 -222
  264. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +0 -349
  265. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -202
  266. package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -152
  267. package/src/orchestration/__tests__/args-validator.test.ts +0 -143
  268. package/src/orchestration/__tests__/config-loader.test.ts +0 -503
  269. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +0 -809
  270. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +0 -405
  271. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +0 -56
  272. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +0 -166
  273. package/src/orchestration/__tests__/execute-agent-call.test.ts +0 -451
  274. package/src/orchestration/__tests__/jsonl-run-store-corrupt-entry.test.ts +0 -150
  275. package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +0 -171
  276. package/src/orchestration/__tests__/jsonl-run-store-retention.test.ts +0 -202
  277. package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +0 -1030
  278. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -600
  279. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +0 -96
  280. package/src/orchestration/__tests__/lifecycle.test.ts +0 -659
  281. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +0 -95
  282. package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +0 -2025
  283. package/src/orchestration/__tests__/script-lint.test.ts +0 -831
  284. package/src/orchestration/__tests__/skill-discovery.test.ts +0 -201
  285. package/src/orchestration/__tests__/test-mocks.ts +0 -197
  286. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +0 -368
  287. package/src/orchestration/__tests__/worker-host.test.ts +0 -120
  288. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +0 -164
  289. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +0 -563
  290. package/src/orchestration/__tests__/worker-script-builder.test.ts +0 -309
  291. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +0 -129
  292. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -317
  293. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +0 -110
  294. package/src/orchestration/__tests__/workflows-e2e.test.ts +0 -537
  295. package/src/orchestration/agent-opts-resolver.ts +0 -174
  296. package/src/orchestration/args-validator.ts +0 -127
  297. package/src/orchestration/config-loader.ts +0 -315
  298. package/src/orchestration/error-recovery.ts +0 -1018
  299. package/src/orchestration/execute-agent-call.ts +0 -256
  300. package/src/orchestration/launcher.ts +0 -455
  301. package/src/orchestration/lifecycle.ts +0 -399
  302. package/src/orchestration/models/__tests__/budget.test.ts +0 -307
  303. package/src/orchestration/models/__tests__/trace.test.ts +0 -408
  304. package/src/orchestration/models/agent-call.ts +0 -83
  305. package/src/orchestration/models/budget.ts +0 -118
  306. package/src/orchestration/models/ports.ts +0 -164
  307. package/src/orchestration/models/run-runtime.ts +0 -104
  308. package/src/orchestration/models/run-spec.ts +0 -83
  309. package/src/orchestration/models/run-state.ts +0 -44
  310. package/src/orchestration/models/trace.ts +0 -183
  311. package/src/orchestration/models/types.ts +0 -301
  312. package/src/orchestration/models/workflow-run.ts +0 -254
  313. package/src/orchestration/models/workflow-script-registry.ts +0 -35
  314. package/src/orchestration/models/workflow-script.ts +0 -117
  315. package/src/orchestration/script-lint.ts +0 -766
  316. package/src/orchestration/skill-discovery.ts +0 -115
  317. package/src/orchestration/worker-handle.ts +0 -115
  318. package/src/orchestration/worker-host.ts +0 -98
  319. package/src/orchestration/worker-script-builder.ts +0 -421
  320. package/src/orchestration/workflow-files.ts +0 -85
  321. package/src/orchestration/workflow-script-registry-impl.ts +0 -133
  322. package/src/shared/__tests__/agent-ref.test.ts +0 -34
  323. package/src/shared/__tests__/meta-parser.test.ts +0 -304
  324. package/src/shared/__tests__/model-ref.test.ts +0 -306
  325. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -280
  326. package/src/shared/__tests__/resource-discovery.test.ts +0 -736
  327. package/src/shared/__tests__/resource-meta.test.ts +0 -51
  328. package/src/shared/__tests__/schema-jsonify.test.ts +0 -81
  329. package/src/shared/__tests__/timer-delay.test.ts +0 -61
  330. package/src/shared/agent-event.ts +0 -13
  331. package/src/shared/agent-ref.ts +0 -57
  332. package/src/shared/meta-parser.ts +0 -261
  333. package/src/shared/model-ref.ts +0 -286
  334. package/src/shared/resource-discovery.ts +0 -811
  335. package/src/shared/resource-meta.ts +0 -65
  336. package/src/shared/schema-env.ts +0 -44
  337. package/src/shared/schema-jsonify.ts +0 -58
  338. package/src/shared/timer-delay.ts +0 -54
  339. package/src/shared/xml-injection.ts +0 -35
  340. package/workflows/README.md +0 -81
  341. package/workflows/_shared/agent-refs.cjs +0 -40
  342. package/workflows/chain.js +0 -131
  343. package/workflows/map-reduce.js +0 -174
  344. package/workflows/parallel.js +0 -148
  345. package/workflows/review-fix-loop-utils.cjs +0 -1309
  346. package/workflows/review-fix-loop.js +0 -1404
  347. 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
- });