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