@zhushanwen/subagent-core 0.2.0 → 0.5.1

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/README.md +45 -2
  2. package/agents/analyst.md +60 -0
  3. package/agents/coder.md +69 -0
  4. package/agents/debugger.md +66 -0
  5. package/agents/doc-reviewer.md +49 -0
  6. package/agents/explorer.md +63 -0
  7. package/agents/general-purpose.md +32 -0
  8. package/agents/orchestrator.md +61 -0
  9. package/agents/planner.md +53 -0
  10. package/agents/researcher.md +67 -0
  11. package/agents/reviewer.md +73 -0
  12. package/dist/chunk-43ONBFZX.js +240 -0
  13. package/dist/chunk-APZY4IME.js +27 -0
  14. package/dist/chunk-V3VHZ2VX.js +59 -0
  15. package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
  16. package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
  17. package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
  18. package/dist/execution/engine/engines/zcode/constants.js +39 -7
  19. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  20. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  21. package/dist/execution/engine/engines/zcode/reader.js +4 -234
  22. package/dist/execution/engine/paths.js +7 -19
  23. package/dist/index.cjs +18234 -1277
  24. package/dist/index.d.cts +5646 -673
  25. package/dist/index.d.ts +5646 -673
  26. package/dist/index.js +17828 -1277
  27. package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
  28. package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
  29. package/package.json +7 -4
  30. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  31. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  32. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  33. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  34. package/src/__tests__/manifest-store.test.ts +10 -9
  35. package/src/__tests__/record-store-cache.test.ts +0 -2
  36. package/src/__tests__/record-store-index.test.ts +1 -2
  37. package/src/__tests__/review-fix-loop-script.test.ts +93 -3
  38. package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
  39. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  40. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  41. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  42. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  43. package/src/__tests__/session-runner.test.ts +1 -1
  44. package/src/__tests__/smoke.test.ts +9 -2
  45. package/src/core/__tests__/host-services.test.ts +2 -2
  46. package/src/core/__tests__/logger.test.ts +1 -1
  47. package/src/core/error-message.ts +9 -0
  48. package/src/core/host-services.ts +21 -5
  49. package/src/core/notify-ports.ts +18 -5
  50. package/src/execution/__tests__/agent-profile.test.ts +232 -0
  51. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  52. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  53. package/src/execution/__tests__/agents-assembly.test.ts +219 -0
  54. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  55. package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
  56. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
  57. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  58. package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
  59. package/src/execution/__tests__/delivery-methods.test.ts +77 -62
  60. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  61. package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
  62. package/src/execution/__tests__/dialog-queue.test.ts +199 -1
  63. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  64. package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
  65. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  66. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  67. package/src/execution/__tests__/execution-record.test.ts +217 -60
  68. package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
  69. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
  70. package/src/execution/__tests__/finalize-record.test.ts +278 -16
  71. package/src/execution/__tests__/gc-timer.test.ts +57 -2
  72. package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
  73. package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
  74. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  75. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  76. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  77. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  78. package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
  79. package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
  80. package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
  81. package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
  82. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  83. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  84. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
  85. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
  86. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  87. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  88. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  89. package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
  90. package/src/execution/__tests__/output-collector.test.ts +117 -6
  91. package/src/execution/__tests__/pi-invocation.test.ts +1 -1
  92. package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
  93. package/src/execution/__tests__/record-store.test.ts +89 -5
  94. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  95. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  96. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  97. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  98. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  99. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
  100. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
  101. package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
  102. package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
  103. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  104. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  105. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
  106. package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
  107. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  108. package/src/execution/__tests__/session-file-gc.test.ts +35 -0
  109. package/src/execution/__tests__/session-pending.test.ts +471 -0
  110. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  111. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
  112. package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
  113. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  114. package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
  115. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
  116. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  117. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  118. package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
  119. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  120. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  121. package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
  122. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  123. package/src/execution/__tests__/spawned-children.test.ts +155 -11
  124. package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
  125. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  126. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  127. package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
  128. package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
  129. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
  130. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
  131. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
  132. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
  133. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  134. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  135. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  136. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  137. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  138. package/src/execution/__tests__/timeout-integration.test.ts +13 -12
  139. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  140. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  141. package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
  142. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  143. package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
  144. package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
  145. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  146. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  147. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
  148. package/src/execution/agent-registry.ts +298 -30
  149. package/src/execution/agent-result-mapper.ts +3 -0
  150. package/src/execution/agents-assembly.ts +88 -0
  151. package/src/execution/cold-resurrect.ts +199 -0
  152. package/src/execution/concurrency-pool.ts +71 -9
  153. package/src/execution/dialog-queue.ts +101 -3
  154. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  155. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  156. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  157. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  158. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  159. package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
  160. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  161. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  162. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
  163. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
  168. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  169. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  170. package/src/execution/engine/__tests__/registry.test.ts +94 -3
  171. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  172. package/src/execution/engine/common/capability-gate.ts +92 -0
  173. package/src/execution/engine/common/journal-wiring.ts +97 -0
  174. package/src/execution/engine/common/kill-chain.ts +42 -10
  175. package/src/execution/engine/common/nesting-guard.ts +66 -0
  176. package/src/execution/engine/common/persona-router.ts +18 -8
  177. package/src/execution/engine/common/pool-manager.ts +332 -78
  178. package/src/execution/engine/common/session-view-service.ts +506 -0
  179. package/src/execution/engine/common/session-view-types.ts +125 -0
  180. package/src/execution/engine/engine-discovery.ts +12 -16
  181. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  182. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  183. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  184. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
  185. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  186. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  187. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  188. package/src/execution/engine/engines/pi/reader.ts +11 -0
  189. package/src/execution/engine/engines/pi/registration.ts +4 -2
  190. package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
  191. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  192. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  193. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  194. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  195. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
  196. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
  197. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  198. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
  199. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  200. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  202. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  203. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  209. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  210. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
  211. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  212. package/src/execution/engine/engines/zcode/connection.ts +598 -0
  213. package/src/execution/engine/engines/zcode/constants.ts +147 -14
  214. package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
  215. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  216. package/src/execution/engine/engines/zcode/preparer.ts +22 -150
  217. package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
  218. package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
  219. package/src/execution/engine/host-task-spec.ts +32 -36
  220. package/src/execution/engine/model-validation.ts +177 -0
  221. package/src/execution/engine/port.ts +65 -7
  222. package/src/execution/engine/registry.ts +53 -0
  223. package/src/execution/engine/routing.ts +75 -1
  224. package/src/execution/engine/types.ts +30 -103
  225. package/src/execution/execution-record.ts +222 -144
  226. package/src/execution/finalize-record.ts +181 -85
  227. package/src/execution/idle-gc.ts +28 -0
  228. package/src/execution/lifecycle-manager.ts +27 -3
  229. package/src/execution/lifecycle-predicates.ts +1 -1
  230. package/src/execution/manifest-store.ts +53 -62
  231. package/src/execution/notifier.ts +10 -10
  232. package/src/execution/notify-host.ts +212 -0
  233. package/src/execution/notify-ledger.ts +117 -16
  234. package/src/execution/record-entry.ts +8 -2
  235. package/src/execution/record-store.ts +148 -88
  236. package/src/execution/round-settlement.ts +93 -0
  237. package/src/execution/session-file-gc.ts +96 -62
  238. package/src/execution/session-pending.ts +213 -62
  239. package/src/execution/session-reconstructor.ts +256 -143
  240. package/src/execution/sessions-index.ts +127 -102
  241. package/src/execution/settled-watchdog.ts +311 -0
  242. package/src/execution/subagent-actions-core.ts +686 -0
  243. package/src/execution/subagent-service.ts +1106 -907
  244. package/src/execution/subprocess-agent-runner.ts +144 -97
  245. package/src/execution/types.ts +9 -0
  246. package/src/execution/ui-request-handler-factory.ts +126 -75
  247. package/src/execution/ui-request-queue.ts +50 -19
  248. package/src/execution/worktree-git-ops.ts +397 -0
  249. package/src/execution/worktree-manager.ts +72 -10
  250. package/src/execution/worktree-registry.ts +6 -12
  251. package/src/index.ts +542 -9
  252. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
  253. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
  254. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  255. package/src/orchestration/__tests__/args-meta.test.ts +358 -0
  256. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  257. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
  258. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
  259. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  260. package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
  261. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
  262. package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
  263. package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
  264. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
  265. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
  266. package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
  267. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  268. package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
  269. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  270. package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
  271. package/src/orchestration/__tests__/script-generate.test.ts +314 -0
  272. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  273. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  274. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  275. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  276. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
  277. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  278. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  279. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  280. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
  281. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
  282. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  283. package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
  284. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  285. package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
  286. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
  287. package/src/orchestration/agent-opts-resolver.ts +8 -8
  288. package/src/orchestration/args-meta.ts +198 -0
  289. package/src/orchestration/args-validator.ts +53 -31
  290. package/src/orchestration/execute-agent-call.ts +25 -79
  291. package/src/orchestration/file-run-store.ts +327 -0
  292. package/src/orchestration/launcher.ts +160 -104
  293. package/src/orchestration/lifecycle.ts +371 -97
  294. package/src/orchestration/models/agent-call.ts +7 -7
  295. package/src/orchestration/models/budget.ts +5 -5
  296. package/src/orchestration/models/ports.ts +4 -4
  297. package/src/orchestration/models/run-runtime.ts +15 -15
  298. package/src/orchestration/models/trace.ts +9 -9
  299. package/src/orchestration/models/types.ts +111 -22
  300. package/src/orchestration/models/workflow-run.ts +28 -28
  301. package/src/orchestration/models/workflow-script.ts +4 -4
  302. package/src/orchestration/run-snapshot.ts +266 -0
  303. package/src/orchestration/script-generate.ts +154 -0
  304. package/src/orchestration/script-lint.ts +123 -90
  305. package/src/orchestration/worker-handle.ts +10 -10
  306. package/src/orchestration/worker-host.ts +10 -10
  307. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
  308. package/src/orchestration/worker-script-builder.ts +401 -346
  309. package/src/orchestration/workflow-files.ts +37 -11
  310. package/src/orchestration/workflow-run-summary.ts +69 -0
  311. package/src/orchestration/workflow-script-registry-impl.ts +31 -9
  312. package/src/shared/__tests__/agent-ref.test.ts +209 -2
  313. package/src/shared/__tests__/atomic-write.test.ts +267 -0
  314. package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
  315. package/src/shared/__tests__/injection-render.test.ts +518 -0
  316. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  317. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
  318. package/src/shared/__tests__/resource-discovery.test.ts +261 -217
  319. package/src/shared/agent-ref.ts +143 -2
  320. package/src/shared/atomic-write.ts +323 -0
  321. package/src/shared/bounded-serialize.ts +154 -0
  322. package/src/shared/injection-render.ts +279 -0
  323. package/src/shared/meta-parser.ts +182 -68
  324. package/src/shared/model-ref.ts +15 -2
  325. package/src/shared/resource-discovery.ts +97 -204
  326. package/src/shared/resource-meta.ts +14 -0
  327. package/src/shared/schema-jsonify.ts +1 -1
  328. package/src/shared/xml-injection.ts +9 -9
  329. package/workflows/README.md +9 -9
  330. package/workflows/chain.js +4 -2
  331. package/workflows/map-reduce.js +5 -3
  332. package/workflows/parallel.js +5 -3
  333. package/workflows/review-fix-loop-utils.cjs +165 -92
  334. package/workflows/review-fix-loop-utils.d.cts +287 -0
  335. package/workflows/review-fix-loop.js +18 -7
  336. package/workflows/scatter-gather.js +4 -2
  337. package/dist/chunk-3VOERJPJ.js +0 -22
  338. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  339. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  340. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  341. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  342. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  343. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  344. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  345. package/src/execution/execute-options-mapper.ts +0 -115
  346. package/src/execution/session-runner.ts +0 -1789
  347. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -0,0 +1,571 @@
1
+ // src/execution/__tests__/worktree-git-ops.test.ts
2
+ //
3
+ // worktree-git-ops 测试(sink 设计 U5 / ⛔3):真实 git 临时仓集成(不 mock
4
+ // execFile/fs,先例 worktree-reconcile.integration.test.ts)。
5
+ //
6
+ // 覆盖:
7
+ // - ⛔3 降级路径①锚点缺失/损坏(三形态:文件缺失 / 内容空白 / 内容不被 git 认)
8
+ // → 断言 warn 发出 + 裸 diff + patchIncomplete:true
9
+ // - ⛔3 降级路径②add 失败(真实 index.lock 冲突构造)→ 同上断言
10
+ // - 新文件 + 已提交改动场景:完整机制 patch 含两者(`diff --cached <base>`)
11
+ // - 大 diff maxBuffer:缺省 1MB 超限形态如实登记(GitRunError 上抛 + ⛔3① git
12
+ // 层降级 warn)/ 32MB 放宽后 gitRun 与 collectWorktreePatch 完整产出
13
+ // - cleanupWorktree 三步容错(remove 失败不阻断 / onRemoved 抛错不 reject)
14
+ // - listWorktreePorcelain 原始输出保真(与 execFileSync 原始输出逐字节全等)
15
+ // - 保真读 / GitRunError / SafeId / dirty 谓词
16
+ //
17
+ // warn 断言经 configureCore 注入 log spy(facade 每次调用动态解析,配置即生效)。
18
+
19
+ import { execFileSync } from "node:child_process";
20
+ import * as fs from "node:fs";
21
+ import * as os from "node:os";
22
+ import * as path from "node:path";
23
+
24
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
25
+
26
+ import { configureCore, resetCoreForTests } from "../../core/host-services.ts";
27
+ import { DirtyWorktreeError } from "../types.ts";
28
+ import {
29
+ assertSafeId,
30
+ cleanupWorktree,
31
+ collectWorktreePatch,
32
+ GitRunError,
33
+ gitRun,
34
+ isSafeId,
35
+ isTreeDirty,
36
+ listWorktreePorcelain,
37
+ } from "../worktree-git-ops.ts";
38
+
39
+ /** git 辅助:repo/worktree 内执行(原始 stdout,不 trim——保真对照用)。 */
40
+ function git(dir: string, ...args: string[]): string {
41
+ return execFileSync("git", ["-C", dir, ...args], { encoding: "utf-8" });
42
+ }
43
+
44
+ // [HISTORICAL] 真实 git 子进程集成用例显式超时(对齐 worktree-reconcile.integration
45
+ // 满并行防超出口径)。
46
+ describe("worktree-git-ops", { timeout: 30_000 }, () => {
47
+ let outerDir: string;
48
+ let repo: string;
49
+ let worktree: string;
50
+ let branch: string;
51
+ let baseCommit: string;
52
+ let logCalls: Array<{ level: string; component: string; message: string; data?: unknown }>;
53
+
54
+ /** warn 级日志调用(⛔3 断言用)。 */
55
+ function warnCalls(): Array<{ message: string; data?: unknown }> {
56
+ return logCalls.filter((c) => c.level === "warn");
57
+ }
58
+
59
+ beforeEach(() => {
60
+ outerDir = fs.mkdtempSync(path.join(os.tmpdir(), "wt-gitops-"));
61
+ repo = path.join(outerDir, "repo");
62
+ fs.mkdirSync(repo, { recursive: true });
63
+ git(repo, "init", "-q");
64
+ git(repo, "config", "user.email", "test@test.local");
65
+ git(repo, "config", "user.name", "test");
66
+ git(repo, "config", "commit.gpgsign", "false");
67
+ fs.writeFileSync(path.join(repo, "base.txt"), "base\n", "utf-8");
68
+ git(repo, "add", "-A");
69
+ git(repo, "commit", "-q", "-m", "base");
70
+ baseCommit = git(repo, "rev-parse", "HEAD").trim();
71
+
72
+ branch = "pi-sub-t1";
73
+ worktree = path.join(outerDir, "wt");
74
+ git(repo, "worktree", "add", "-q", "-b", branch, worktree, baseCommit);
75
+
76
+ logCalls = [];
77
+ configureCore({
78
+ dataRoot: () => outerDir,
79
+ log: (level, component, message, data) => {
80
+ logCalls.push({ level, component, message, data });
81
+ },
82
+ });
83
+ });
84
+
85
+ afterEach(() => {
86
+ resetCoreForTests();
87
+ fs.rmSync(outerDir, { recursive: true, force: true });
88
+ });
89
+
90
+ /**
91
+ * 标准三件套场景(⛔3 验收②的原材料):
92
+ * ① 已提交改动(worktree HEAD 前进一个 commit,新增 committed.txt)
93
+ * ② 未提交改动(base.txt 追加一行)
94
+ * ③ 新文件(new.txt,未跟踪)
95
+ */
96
+ function stageCommittedAndUncommittedAndNewFile(): void {
97
+ fs.writeFileSync(path.join(worktree, "committed.txt"), "committed-change\n", "utf-8");
98
+ git(worktree, "add", "-A");
99
+ git(worktree, "commit", "-q", "-m", "wip");
100
+ fs.appendFileSync(path.join(worktree, "base.txt"), "uncommitted\n", "utf-8");
101
+ fs.writeFileSync(path.join(worktree, "new.txt"), "brand-new\n", "utf-8");
102
+ }
103
+
104
+ function patchFile(): string {
105
+ return path.join(outerDir, "out.patch");
106
+ }
107
+
108
+ function commitAnchor(): { kind: "commit"; baseCommit: string } {
109
+ return { kind: "commit", baseCommit };
110
+ }
111
+
112
+ function writeAnchorFile(content: string): string {
113
+ const p = path.join(outerDir, "baseline.anchor");
114
+ fs.writeFileSync(p, content, "utf-8");
115
+ return p;
116
+ }
117
+
118
+ // ============================================================
119
+ // collectWorktreePatch:完整机制(anchor 双形态)
120
+ // ============================================================
121
+
122
+ describe("collectWorktreePatch 完整机制", () => {
123
+ it("commit 锚点:新文件 + 已提交改动场景 patch 含两者(⛔3 验收②,真实 git)", async () => {
124
+ stageCommittedAndUncommittedAndNewFile();
125
+
126
+ const result = await collectWorktreePatch({
127
+ worktreePath: worktree,
128
+ patchFile: patchFile(),
129
+ anchor: commitAnchor(),
130
+ });
131
+
132
+ expect(result.written).toBe(true);
133
+ expect(result.patchIncomplete).toBeUndefined();
134
+ const patch = fs.readFileSync(result.patchFile, "utf-8");
135
+ // 已提交改动(committed.txt 在 worktree HEAD 前进的 commit 里引入)
136
+ expect(patch).toContain("diff --git a/committed.txt b/committed.txt");
137
+ // 新文件(add -A 暂存后 --cached diff 捕获)
138
+ expect(patch).toContain("diff --git a/new.txt b/new.txt");
139
+ // 未提交改动也在(同一基线机制覆盖全量)
140
+ expect(patch).toContain("diff --git a/base.txt b/base.txt");
141
+ // 无降级留痕时 warn 零发出
142
+ expect(warnCalls()).toHaveLength(0);
143
+ });
144
+
145
+ it("anchor-file 锚点:宿主持久锚点文件(sidecar 语义)与 commit 锚点等价", async () => {
146
+ stageCommittedAndUncommittedAndNewFile();
147
+ const anchorFile = writeAnchorFile(`${baseCommit}\n`);
148
+
149
+ const result = await collectWorktreePatch({
150
+ worktreePath: worktree,
151
+ patchFile: patchFile(),
152
+ anchor: { kind: "anchor-file", path: anchorFile },
153
+ });
154
+
155
+ expect(result.written).toBe(true);
156
+ expect(result.patchIncomplete).toBeUndefined();
157
+ const patch = fs.readFileSync(result.patchFile, "utf-8");
158
+ expect(patch).toContain("diff --git a/committed.txt b/committed.txt");
159
+ expect(patch).toContain("diff --git a/new.txt b/new.txt");
160
+ });
161
+
162
+ it("保真读:patch 文件保留 git 原始尾换行(trim 会产出 corrupt patch)", async () => {
163
+ fs.appendFileSync(path.join(worktree, "base.txt"), "uncommitted\n", "utf-8");
164
+
165
+ const result = await collectWorktreePatch({
166
+ worktreePath: worktree,
167
+ patchFile: patchFile(),
168
+ anchor: commitAnchor(),
169
+ });
170
+
171
+ const patch = fs.readFileSync(result.patchFile, "utf-8");
172
+ expect(result.written).toBe(true);
173
+ expect(patch.endsWith("\n")).toBe(true);
174
+ expect(patch).toContain("@@ -1");
175
+ });
176
+
177
+ it("空 diff:written=false 且不写文件(避免悬空路径)", async () => {
178
+ const out = patchFile();
179
+
180
+ const result = await collectWorktreePatch({
181
+ worktreePath: worktree,
182
+ patchFile: out,
183
+ anchor: commitAnchor(),
184
+ });
185
+
186
+ expect(result.written).toBe(false);
187
+ expect(result.patchIncomplete).toBeUndefined();
188
+ expect(fs.existsSync(out)).toBe(false);
189
+ expect(warnCalls()).toHaveLength(0);
190
+ });
191
+ });
192
+
193
+ // ============================================================
194
+ // 大 diff maxBuffer:Node execFile 缺省 1MB 超限 / 宿主 32MB 放宽
195
+ // ============================================================
196
+
197
+ describe("大 diff maxBuffer(缺省 1MB 超限 / 宿主 32MB)", () => {
198
+ /** ~1.5MB 多行文本(30000 行 × ~51B),贴近批量重构的大 diff 真实场景。 */
199
+ const BIG_CONTENT = Array.from(
200
+ { length: 30_000 },
201
+ (_, i) => `line-${i} ${"x".repeat(40)}`,
202
+ ).join("\n");
203
+
204
+ /** 在 worktree 给已跟踪 base.txt 追加 ~1.5MB(30000 行 × ~51B)。
205
+ * 用已跟踪文件修改而非 untracked 新文件:纯 `git diff`(工作区 vs index)
206
+ * 不含 untracked,直接 diff 用例才有大输出(贴近批量重构改既有文件场景)。 */
207
+ function stageBigDiff(): void {
208
+ fs.appendFileSync(path.join(worktree, "base.txt"), `\n${BIG_CONTENT}\n`, "utf-8");
209
+ }
210
+
211
+ it("gitRun 不传 maxBuffer:stdout 超 1MB → GitRunError(Node execFile 缺省行为不变)", async () => {
212
+ stageBigDiff();
213
+
214
+ await expect(gitRun(["diff"], { cwd: worktree })).rejects.toThrow(GitRunError);
215
+ });
216
+
217
+ it("gitRun 传 maxBuffer 32MB:超 1MB stdout 完整返回", async () => {
218
+ stageBigDiff();
219
+
220
+ const out = await gitRun(["diff"], { cwd: worktree, maxBuffer: 32 * 1024 * 1024 });
221
+
222
+ expect(out.length).toBeGreaterThan(1_500_000);
223
+ expect(out).toContain("diff --git a/base.txt b/base.txt");
224
+ });
225
+
226
+ it(
227
+ "collectWorktreePatch 缺省:大 diff → GitRunError 原样上抛" +
228
+ "(如实登记:diff --cached 超限先被 ⛔3① git 层按「锚点被拒」降级 warn," +
229
+ "降级裸 diff HEAD 再超限、无 catch 上抛)",
230
+ async () => {
231
+ stageBigDiff();
232
+
233
+ const err = await collectWorktreePatch({
234
+ worktreePath: worktree,
235
+ patchFile: patchFile(),
236
+ anchor: commitAnchor(),
237
+ }).catch((e: unknown) => e);
238
+
239
+ // 终态:降级裸 diff HEAD 同样超限(该路径无 catch),GitRunError 原样上抛,
240
+ // patch 文件不产出
241
+ expect(err).toBeInstanceOf(GitRunError);
242
+ expect((err as GitRunError).message).toContain("git diff failed");
243
+ expect(fs.existsSync(patchFile())).toBe(false);
244
+ // 中间留痕:diff --cached 超限 ≠ 锚点损坏,但 ⛔3① git 层按既有 catch
245
+ // 语义降级(最小修复不改降级判定),warn 如实发出
246
+ const warns = warnCalls();
247
+ expect(warns).toHaveLength(1);
248
+ expect(warns[0].message).toContain("rejected by git");
249
+ },
250
+ );
251
+
252
+ it("collectWorktreePatch 传 maxBuffer 32MB:大 diff 完整成 patch(无降级留痕)", async () => {
253
+ stageBigDiff();
254
+
255
+ const result = await collectWorktreePatch({
256
+ worktreePath: worktree,
257
+ patchFile: patchFile(),
258
+ anchor: commitAnchor(),
259
+ maxBuffer: 32 * 1024 * 1024,
260
+ });
261
+
262
+ expect(result.written).toBe(true);
263
+ expect(result.patchIncomplete).toBeUndefined();
264
+ const patch = fs.readFileSync(result.patchFile, "utf-8");
265
+ expect(patch).toContain("diff --git a/base.txt b/base.txt");
266
+ // patch 完整:全文行进 diff(内容 + diff 前缀 > 1.5MB,远超 1MB 缺省)
267
+ expect(patch.length).toBeGreaterThan(1_500_000);
268
+ expect(patch.endsWith("\n")).toBe(true);
269
+ expect(warnCalls()).toHaveLength(0);
270
+ });
271
+ });
272
+
273
+ // ============================================================
274
+ // collectWorktreePatch:⛔3 降级路径①锚点缺失/损坏
275
+ // ============================================================
276
+
277
+ describe("⛔3① 锚点缺失/损坏 → warn + 裸 diff + patchIncomplete:true", () => {
278
+ it("锚点文件缺失:warn 发出 + 裸 diff + patchIncomplete:true + 丢已提交改动", async () => {
279
+ stageCommittedAndUncommittedAndNewFile();
280
+ const missing = path.join(outerDir, "never-written.anchor");
281
+
282
+ const result = await collectWorktreePatch({
283
+ worktreePath: worktree,
284
+ patchFile: patchFile(),
285
+ anchor: { kind: "anchor-file", path: missing },
286
+ });
287
+
288
+ // 显著 warn(不做纯静默)
289
+ const warns = warnCalls();
290
+ expect(warns).toHaveLength(1);
291
+ expect(warns[0].message).toContain("anchor file missing or unreadable");
292
+ expect(warns[0].data).toMatchObject({ anchorFile: missing });
293
+ // patchIncomplete 留痕
294
+ expect(result.written).toBe(true);
295
+ expect(result.patchIncomplete).toBe(true);
296
+ // 裸 diff:仅未提交改动 + 已暂存新文件(add -A 成功),丢已提交改动
297
+ const patch = fs.readFileSync(result.patchFile, "utf-8");
298
+ expect(patch).toContain("diff --git a/base.txt b/base.txt");
299
+ expect(patch).toContain("diff --git a/new.txt b/new.txt");
300
+ expect(patch).not.toContain("committed.txt");
301
+ });
302
+
303
+ it("锚点文件内容空白(损坏形态一):warn 发出 + 裸 diff + patchIncomplete:true", async () => {
304
+ stageCommittedAndUncommittedAndNewFile();
305
+ const anchorFile = writeAnchorFile(" \n\t\n");
306
+
307
+ const result = await collectWorktreePatch({
308
+ worktreePath: worktree,
309
+ patchFile: patchFile(),
310
+ anchor: { kind: "anchor-file", path: anchorFile },
311
+ });
312
+
313
+ const warns = warnCalls();
314
+ expect(warns).toHaveLength(1);
315
+ expect(warns[0].message).toContain("empty or blank");
316
+ expect(result.patchIncomplete).toBe(true);
317
+ const patch = fs.readFileSync(result.patchFile, "utf-8");
318
+ expect(patch).toContain("diff --git a/base.txt b/base.txt");
319
+ expect(patch).not.toContain("committed.txt");
320
+ });
321
+
322
+ it("锚点内容不被 git 认(损坏形态二):warn 发出 + 裸 diff + patchIncomplete:true", async () => {
323
+ stageCommittedAndUncommittedAndNewFile();
324
+ const anchorFile = writeAnchorFile("deadbeef-not-a-real-commit\n");
325
+
326
+ const result = await collectWorktreePatch({
327
+ worktreePath: worktree,
328
+ patchFile: patchFile(),
329
+ anchor: { kind: "anchor-file", path: anchorFile },
330
+ });
331
+
332
+ const warns = warnCalls();
333
+ expect(warns).toHaveLength(1);
334
+ expect(warns[0].message).toContain("rejected by git");
335
+ expect(result.written).toBe(true);
336
+ expect(result.patchIncomplete).toBe(true);
337
+ const patch = fs.readFileSync(result.patchFile, "utf-8");
338
+ expect(patch).toContain("diff --git a/base.txt b/base.txt");
339
+ expect(patch).not.toContain("committed.txt");
340
+ });
341
+
342
+ it("commit 锚点为空串:warn 发出 + 裸 diff + patchIncomplete:true", async () => {
343
+ stageCommittedAndUncommittedAndNewFile();
344
+
345
+ const result = await collectWorktreePatch({
346
+ worktreePath: worktree,
347
+ patchFile: patchFile(),
348
+ anchor: { kind: "commit", baseCommit: " " },
349
+ });
350
+
351
+ const warns = warnCalls();
352
+ expect(warns).toHaveLength(1);
353
+ expect(warns[0].message).toContain("anchor commit is empty");
354
+ expect(result.patchIncomplete).toBe(true);
355
+ expect(fs.readFileSync(result.patchFile, "utf-8")).toContain(
356
+ "diff --git a/base.txt b/base.txt",
357
+ );
358
+ });
359
+
360
+ it("锚点缺失且树干净:空 diff 不写文件,patchIncomplete 留痕不因 written=false 丢失", async () => {
361
+ const result = await collectWorktreePatch({
362
+ worktreePath: worktree,
363
+ patchFile: patchFile(),
364
+ anchor: { kind: "anchor-file", path: path.join(outerDir, "absent.anchor") },
365
+ });
366
+
367
+ expect(result.written).toBe(false);
368
+ expect(result.patchIncomplete).toBe(true);
369
+ expect(fs.existsSync(result.patchFile)).toBe(false);
370
+ expect(warnCalls()).toHaveLength(1);
371
+ });
372
+ });
373
+
374
+ // ============================================================
375
+ // collectWorktreePatch:⛔3 降级路径②add 失败
376
+ // ============================================================
377
+
378
+ describe("⛔3② add 失败 → warn + 裸 diff + patchIncomplete:true(真实 index.lock 冲突)", () => {
379
+ /** 在 worktree 的 git 元数据目录放 index.lock(真实 git add 必然失败)。 */
380
+ function blockIndex(): void {
381
+ const gitDir = git(worktree, "rev-parse", "--git-dir").trim();
382
+ fs.writeFileSync(path.join(gitDir, "index.lock"), "", "utf-8");
383
+ }
384
+
385
+ it("add 失败:warn 发出 + 裸 diff + patchIncomplete:true + 新文件不进 patch", async () => {
386
+ stageCommittedAndUncommittedAndNewFile();
387
+ blockIndex();
388
+
389
+ const result = await collectWorktreePatch({
390
+ worktreePath: worktree,
391
+ patchFile: patchFile(),
392
+ anchor: commitAnchor(),
393
+ });
394
+
395
+ // 非致命:不 reject,warn 显著(不做 fail-fast 也不静默)
396
+ const warns = warnCalls();
397
+ expect(warns).toHaveLength(1);
398
+ expect(warns[0].message).toContain("git add -A failed");
399
+ expect(warns[0].data).toMatchObject({ worktreePath: worktree });
400
+ expect(result.written).toBe(true);
401
+ expect(result.patchIncomplete).toBe(true);
402
+ // 裸 diff HEAD:已跟踪未提交改动在;已提交改动与 untracked 新文件丢
403
+ //(D5 裁决:降级形态较 zsw `diff <base>` 多丢已提交改动,留痕可判断)
404
+ const patch = fs.readFileSync(result.patchFile, "utf-8");
405
+ expect(patch).toContain("diff --git a/base.txt b/base.txt");
406
+ expect(patch).not.toContain("committed.txt");
407
+ expect(patch).not.toContain("new.txt");
408
+ });
409
+
410
+ it("add 失败且树干净:written=false,patchIncomplete 留痕仍在", async () => {
411
+ blockIndex();
412
+
413
+ const result = await collectWorktreePatch({
414
+ worktreePath: worktree,
415
+ patchFile: patchFile(),
416
+ anchor: commitAnchor(),
417
+ });
418
+
419
+ expect(result.written).toBe(false);
420
+ expect(result.patchIncomplete).toBe(true);
421
+ expect(warnCalls()).toHaveLength(1);
422
+ });
423
+ });
424
+
425
+ // ============================================================
426
+ // cleanupWorktree:三步容错
427
+ // ============================================================
428
+
429
+ describe("cleanupWorktree 三步容错", () => {
430
+ it("正常路径:remove + branch -D + onRemoved 全部执行,资源真实消失", async () => {
431
+ const onRemoved = vi.fn();
432
+
433
+ await cleanupWorktree({ repo, worktreePath: worktree, branch, onRemoved });
434
+
435
+ expect(onRemoved).toHaveBeenCalledTimes(1);
436
+ expect(git(repo, "branch", "--list", branch).trim()).toBe("");
437
+ expect(fs.existsSync(worktree)).toBe(false);
438
+ expect(warnCalls()).toHaveLength(0);
439
+ });
440
+
441
+ it("remove 失败(locked worktree)不阻断:onRemoved 仍被调,不 reject", async () => {
442
+ const onRemoved = vi.fn();
443
+ // 确定性 remove 失败构造:locked working tree 连 --force 也拒绝移除
444
+ git(repo, "worktree", "lock", worktree);
445
+
446
+ await cleanupWorktree({ repo, worktreePath: worktree, branch, onRemoved });
447
+
448
+ expect(onRemoved).toHaveBeenCalledTimes(1);
449
+ // branch -D 因 worktree 元数据未清(used by worktree)也失败被容错吞掉——
450
+ // 分支留给宿主 reaper/对账兜底收敛,本函数只保证不阻断、不丢步。
451
+ expect(git(repo, "branch", "--list", branch).trim()).not.toBe("");
452
+ });
453
+
454
+ it("onRemoved 抛错不 reject:前两步已执行,失败仅 warn", async () => {
455
+ const onRemoved = vi.fn(() => {
456
+ throw new Error("registry remove failed");
457
+ });
458
+
459
+ await expect(
460
+ cleanupWorktree({ repo, worktreePath: worktree, branch, onRemoved }),
461
+ ).resolves.toBeUndefined();
462
+
463
+ expect(onRemoved).toHaveBeenCalledTimes(1);
464
+ expect(git(repo, "branch", "--list", branch).trim()).toBe("");
465
+ const warns = warnCalls();
466
+ expect(warns).toHaveLength(1);
467
+ expect(warns[0].message).toContain("onRemoved host hook failed");
468
+ });
469
+
470
+ it("onRemoved 缺席(可选钩子)不抛", async () => {
471
+ await expect(
472
+ cleanupWorktree({ repo, worktreePath: worktree, branch }),
473
+ ).resolves.toBeUndefined();
474
+ expect(git(repo, "branch", "--list", branch).trim()).toBe("");
475
+ });
476
+ });
477
+
478
+ // ============================================================
479
+ // listWorktreePorcelain:原始输出保真
480
+ // ============================================================
481
+
482
+ describe("listWorktreePorcelain 原始输出保真", () => {
483
+ it("与 git 原始 stdout 逐字节全等(不 trim / 不加工)", async () => {
484
+ const result = await listWorktreePorcelain({ repo });
485
+ const raw = execFileSync("git", ["-C", repo, "worktree", "list", "--porcelain"], {
486
+ encoding: "utf-8",
487
+ });
488
+
489
+ expect(result).toBe(raw);
490
+ expect(result.endsWith("\n")).toBe(true);
491
+ });
492
+
493
+ it("内容含 worktree checkout 与 branch 行(git 原始行文即 realpath 形态)", async () => {
494
+ const result = await listWorktreePorcelain({ repo });
495
+
496
+ // git 输出的是 realpath 形态(macOS /var→/private/var)——这正是宿主
497
+ // realpath 对账依赖原始输出、禁止加工的原因(设计 D5)。
498
+ expect(result).toContain(`worktree ${fs.realpathSync(repo)}\n`);
499
+ expect(result).toContain(`worktree ${fs.realpathSync(worktree)}\n`);
500
+ expect(result).toContain(`branch refs/heads/${branch}\n`);
501
+ });
502
+
503
+ it("非 git 目录 → GitRunError", async () => {
504
+ const notARepo = path.join(outerDir, "not-a-repo");
505
+ fs.mkdirSync(notARepo, { recursive: true });
506
+
507
+ await expect(listWorktreePorcelain({ repo: notARepo })).rejects.toBeInstanceOf(GitRunError);
508
+ });
509
+ });
510
+
511
+ // ============================================================
512
+ // gitRun / GitRunError / SafeId / dirty 谓词
513
+ // ============================================================
514
+
515
+ describe("gitRun / GitRunError", () => {
516
+ it("失败命令 reject GitRunError(name/exitCode/stderr 诊断属性)", async () => {
517
+ const err = await gitRun(["rev-parse", "definitely-not-a-ref"], { cwd: repo }).catch(
518
+ (e: unknown) => e,
519
+ );
520
+
521
+ expect(err).toBeInstanceOf(GitRunError);
522
+ const gitErr = err as GitRunError;
523
+ expect(gitErr.name).toBe("GitRunError");
524
+ expect(gitErr.message).toContain("git rev-parse failed");
525
+ expect(typeof gitErr.exitCode).toBe("number");
526
+ expect(gitErr.exitCode).not.toBe(0);
527
+ expect(typeof gitErr.stderr).toBe("string");
528
+ });
529
+
530
+ it("成功命令 stdout 保真返回(不 trim,尾换行保留)", async () => {
531
+ const out = await gitRun(["rev-parse", "HEAD"], { cwd: repo });
532
+
533
+ expect(out.trim()).toBe(baseCommit);
534
+ expect(out.endsWith("\n")).toBe(true);
535
+ });
536
+ });
537
+
538
+ describe("SafeId", () => {
539
+ it("isSafeId:白名单 ^[\\w-]+$ 放行 / 路径注入拒绝", () => {
540
+ expect(isSafeId("bg-42_abc")).toBe(true);
541
+ expect(isSafeId("a".repeat(64))).toBe(true);
542
+ expect(isSafeId("../evil")).toBe(false);
543
+ expect(isSafeId("a b")).toBe(false);
544
+ expect(isSafeId("")).toBe(false);
545
+ expect(isSafeId("a/b")).toBe(false);
546
+ });
547
+
548
+ it("assertSafeId:不合法抛 DirtyWorktreeError(消息含白名单说明)", () => {
549
+ expect(() => assertSafeId("../evil")).toThrow(DirtyWorktreeError);
550
+ expect(() => assertSafeId("../evil")).toThrow("must match ^[\\w-]+$");
551
+ expect(() => assertSafeId("../evil", "sessionId")).toThrow(/sessionId/);
552
+ expect(() => assertSafeId("ok-id_1")).not.toThrow();
553
+ });
554
+ });
555
+
556
+ describe("isTreeDirty(dirty 谓词)", () => {
557
+ it("空输出 / 纯空白 → false;porcelain 条目 → true", () => {
558
+ expect(isTreeDirty("")).toBe(false);
559
+ expect(isTreeDirty("\n")).toBe(false);
560
+ expect(isTreeDirty(" \n")).toBe(false);
561
+ expect(isTreeDirty(" M base.txt\n")).toBe(true);
562
+ expect(isTreeDirty("?? new.txt\n")).toBe(true);
563
+ });
564
+
565
+ it("与真实 git status --porcelain 联动:干净树 false,改动后 true", async () => {
566
+ expect(isTreeDirty(git(worktree, "status", "--porcelain"))).toBe(false);
567
+ fs.appendFileSync(path.join(worktree, "base.txt"), "x\n", "utf-8");
568
+ expect(isTreeDirty(git(worktree, "status", "--porcelain"))).toBe(true);
569
+ });
570
+ });
571
+ });
@@ -80,7 +80,16 @@ import { isProcessAlive } from "../alive-store.ts";
80
80
  import { encodeCwd } from "../path-encoding.ts";
81
81
  import { WorktreeManager } from "../worktree-manager.ts";
82
82
 
83
- const mockExecFile = vi.mocked(execFile);
83
+ // 被测链路(gitRunAsync)调用四参形态(file, args, options, callback);vi.mocked
84
+ // 直接包 execFile 会推导到无 options 重载,显式绑定四参签名
85
+ const mockExecFile = vi.mocked(
86
+ execFile as unknown as (
87
+ file: string,
88
+ args: readonly string[],
89
+ options: unknown,
90
+ callback: (err: Error | null, stdout: string, stderr: string) => void,
91
+ ) => void,
92
+ );
84
93
  const mockExistsSync = vi.mocked(fs.existsSync);
85
94
  const mockIsProcessAlive = vi.mocked(isProcessAlive);
86
95
 
@@ -95,7 +104,7 @@ type ExecFileResult = {
95
104
  };
96
105
  function setupExecFile(impl?: (args: readonly string[]) => ExecFileResult): void {
97
106
  mockExecFile.mockImplementation(
98
- (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout?: string, stderr?: string) => void) => {
107
+ (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
99
108
  const r = impl ? impl(args) : { stdout: "" };
100
109
  if (r.err) cb(r.err, r.stdout ?? "", r.stderr ?? "");
101
110
  else cb(null, r.stdout ?? "", r.stderr ?? "");
@@ -178,7 +187,7 @@ describe("WorktreeManager", () => {
178
187
  await mgr.create(MAIN_CWD, RECORD_ID);
179
188
 
180
189
  expect(mockAdd).toHaveBeenCalledTimes(1);
181
- const entry = mockAdd.mock.calls[0][0] as { repo: string; branch: string; pid: number };
190
+ const entry = mockAdd.mock.calls[0]![0] as { repo: string; branch: string; pid: number };
182
191
  expect(entry.repo).toBe(MAIN_CWD);
183
192
  expect(entry.branch).toBe(`pi-sub-${RECORD_ID}`);
184
193
  expect(entry.pid).toBe(0);
@@ -266,7 +275,7 @@ describe("WorktreeManager", () => {
266
275
  let maxWriteActive = 0;
267
276
  let addCount = 0;
268
277
  mockExecFile.mockImplementation(
269
- (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout?: string, stderr?: string) => void) => {
278
+ (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
270
279
  const isAdd = args[0] === "worktree" && args[1] === "add";
271
280
  if (isAdd) {
272
281
  // 只对写命令(worktree add)计并发——读命令(status/rev-parse)无锁可并行是设计预期
@@ -442,7 +451,7 @@ describe("WorktreeManager", () => {
442
451
  let maxActive = 0;
443
452
  // 异步完成 cb:若队列失效,后继命令的 impl 入口会先于前驱 cb 到达 → active=2
444
453
  mockExecFile.mockImplementation(
445
- (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout?: string, stderr?: string) => void) => {
454
+ (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
446
455
  active++;
447
456
  maxActive = Math.max(maxActive, active);
448
457
  setTimeout(() => {
@@ -52,7 +52,7 @@ vi.mock("./pi-invocation.ts", () => ({
52
52
 
53
53
  import { WorktreeManager } from "../worktree-manager.ts";
54
54
  import { WorktreeRegistry, SPAWN_GRACE_MS } from "../worktree-registry.ts";
55
- import { runSpawn } from "../session-runner.ts";
55
+ import { runSpawn } from "../engine/engines/pi/session-runner.ts";
56
56
  import type { WorktreeHandle } from "../types.ts";
57
57
  import { makeCtx, makeOpts, makeRecord } from "./helpers/spawn-mock.ts";
58
58
 
@@ -118,7 +118,7 @@ async function cleanup(): Promise<void> {
118
118
  handle = undefined;
119
119
  }
120
120
  try {
121
- fs.rmSync(tmpRoot, { recursive: true, force: true });
121
+ fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
122
122
  } catch {
123
123
  // best-effort
124
124
  }
@@ -213,7 +213,8 @@ describe("worktree pid 注册链路(真实 spawn 集成)", { timeout: 30_000
213
213
  makeOpts({ worktree: handle }),
214
214
  ctx,
215
215
  );
216
- expect(result.status).not.toBe("error"); // 进程正常退出(exit 0),非 spawn 失败
216
+ // 历史「status !== error」弱断言:AgentResult status 字段,经视图访问保持断言语义不变
217
+ expect((result as { status?: string }).status).not.toBe("error"); // 进程正常退出(exit 0),非 spawn 失败
217
218
 
218
219
  // 3. pid 已补全(短命进程退出后 pid 仍有效,registerPid 同步执行不受退出影响)
219
220
  const entry = readEntry(handle.branch);