pi-crew 0.2.2 → 0.2.4

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 (354) hide show
  1. package/AGENTS.md +57 -32
  2. package/CHANGELOG.md +466 -413
  3. package/LICENSE +21 -21
  4. package/NOTICE.md +16 -16
  5. package/README.md +323 -323
  6. package/docs/FEATURE_INTAKE.md +126 -0
  7. package/docs/HARNESS.md +86 -0
  8. package/docs/HARNESS_BACKLOG.md +41 -0
  9. package/docs/TEST_MATRIX.md +49 -0
  10. package/docs/actions-reference.md +595 -595
  11. package/docs/architecture.md +180 -180
  12. package/docs/code-review-2026-05-11.md +592 -0
  13. package/docs/commands-reference.md +347 -347
  14. package/docs/comparison-pi-subagents-vs-pi-crew.md +303 -0
  15. package/docs/decisions/0001-durable-state.md +41 -0
  16. package/docs/decisions/0002-child-process-for-async.md +42 -0
  17. package/docs/decisions/0003-depth-guard.md +36 -0
  18. package/docs/decisions/0004-execfile-over-exec.md +34 -0
  19. package/docs/decisions/0005-no-parameter-properties.md +49 -0
  20. package/docs/decisions/0006-publish-bundled-esm.md +63 -0
  21. package/docs/decisions/0007-active-run-binary-index.md +54 -0
  22. package/docs/decisions/0008-child-pi-warm-pool.md +61 -0
  23. package/docs/decisions/README.md +23 -0
  24. package/docs/followup-plan-2026-05-12.md +463 -0
  25. package/docs/followup-review-2026-05-12.md +297 -0
  26. package/docs/followup-review-round3-2026-05-12.md +342 -0
  27. package/docs/followup-review-round4-2026-05-13.md +107 -0
  28. package/docs/implementation-plan-top3.md +333 -0
  29. package/docs/live-mailbox-runtime.md +36 -36
  30. package/docs/next-upgrade-roadmap.md +808 -808
  31. package/docs/oh-my-pi-research.md +509 -0
  32. package/docs/perf/baseline-2026-05.md +113 -0
  33. package/docs/perf/final-report-2026-05.md +206 -0
  34. package/docs/perf/sprint-1-report.md +71 -0
  35. package/docs/perf/sprint-2-report.md +81 -0
  36. package/docs/perf/sprint-2.5-report.md +53 -0
  37. package/docs/perf/sprint-3-report.md +36 -0
  38. package/docs/perf/sprint-4-report.md +47 -0
  39. package/docs/perf/sprint-5-report.md +51 -0
  40. package/docs/perf/sprint-6-report.md +94 -0
  41. package/docs/perf/sprint-7-report.md +74 -0
  42. package/docs/perf/upgrade-plan-2026-05.md +147 -0
  43. package/docs/pi-subagents3-deep-analysis.md +508 -0
  44. package/docs/product/README.md +31 -0
  45. package/docs/product/platform.md +27 -0
  46. package/docs/product/runtime-safety.md +37 -0
  47. package/docs/product/team-run.md +39 -0
  48. package/docs/product/team-tool.md +37 -0
  49. package/docs/publishing.md +65 -65
  50. package/docs/resource-formats.md +134 -134
  51. package/docs/runtime-analysis-child-vs-live.md +171 -0
  52. package/docs/runtime-flow.md +148 -148
  53. package/docs/runtime-migration-in-process-analysis.md +250 -0
  54. package/docs/stories/README.md +30 -0
  55. package/docs/stories/backlog.md +36 -0
  56. package/docs/templates/decision.md +27 -0
  57. package/docs/templates/story.md +44 -0
  58. package/docs/templates/validation-report.md +32 -0
  59. package/docs/usage.md +238 -238
  60. package/index.ts +7 -6
  61. package/install.mjs +65 -65
  62. package/package.json +107 -99
  63. package/schema.json +222 -222
  64. package/skills/child-pi-spawning/SKILL.md +213 -0
  65. package/skills/context-artifact-hygiene/SKILL.md +32 -0
  66. package/skills/event-log-tracing/SKILL.md +299 -0
  67. package/skills/git-master/SKILL.md +225 -24
  68. package/skills/live-agent-lifecycle/SKILL.md +192 -0
  69. package/skills/mailbox-interactive/SKILL.md +300 -19
  70. package/skills/model-routing-context/SKILL.md +94 -0
  71. package/skills/multi-perspective-review/SKILL.md +88 -0
  72. package/skills/read-only-explorer/SKILL.md +250 -26
  73. package/skills/safe-bash/SKILL.md +307 -21
  74. package/skills/verification-before-done/SKILL.md +11 -2
  75. package/skills/widget-rendering/SKILL.md +258 -0
  76. package/skills/workspace-isolation/SKILL.md +202 -0
  77. package/skills/worktree-isolation/SKILL.md +202 -18
  78. package/src/adapters/claude-adapter.ts +25 -25
  79. package/src/adapters/codex-adapter.ts +21 -21
  80. package/src/adapters/cursor-adapter.ts +17 -17
  81. package/src/adapters/export-util.ts +137 -137
  82. package/src/adapters/index.ts +15 -15
  83. package/src/adapters/registry.ts +18 -18
  84. package/src/adapters/types.ts +23 -23
  85. package/src/agents/agent-config.ts +38 -38
  86. package/src/agents/agent-serializer.ts +38 -38
  87. package/src/agents/discover-agents.ts +121 -118
  88. package/src/config/config.ts +740 -858
  89. package/src/config/defaults.ts +96 -96
  90. package/src/config/drift-detector.ts +211 -211
  91. package/src/config/markers.ts +327 -327
  92. package/src/config/resilient-parser.ts +109 -108
  93. package/src/config/suggestions.ts +74 -74
  94. package/src/config/types.ts +199 -0
  95. package/src/extension/async-notifier.ts +123 -89
  96. package/src/extension/autonomous-policy.ts +169 -169
  97. package/src/extension/cross-extension-rpc.ts +104 -103
  98. package/src/extension/help.ts +47 -47
  99. package/src/extension/import-index.ts +69 -69
  100. package/src/extension/management.ts +395 -382
  101. package/src/extension/notification-router.ts +116 -116
  102. package/src/extension/notification-sink.ts +51 -51
  103. package/src/extension/project-init.ts +168 -168
  104. package/src/extension/register.ts +859 -668
  105. package/src/extension/registration/artifact-cleanup.ts +15 -15
  106. package/src/extension/registration/command-utils.ts +54 -54
  107. package/src/extension/registration/commands.ts +559 -452
  108. package/src/extension/registration/compaction-guard.ts +125 -125
  109. package/src/extension/registration/subagent-helpers.ts +102 -102
  110. package/src/extension/registration/subagent-tools.ts +220 -158
  111. package/src/extension/registration/team-tool.ts +159 -98
  112. package/src/extension/registration/viewers.ts +29 -0
  113. package/src/extension/result-watcher.ts +128 -128
  114. package/src/extension/run-bundle-schema.ts +89 -89
  115. package/src/extension/run-export.ts +73 -73
  116. package/src/extension/run-import.ts +84 -84
  117. package/src/extension/run-index.ts +94 -94
  118. package/src/extension/run-maintenance.ts +142 -142
  119. package/src/extension/session-summary.ts +8 -8
  120. package/src/extension/team-manager-command.ts +96 -95
  121. package/src/extension/team-recommendation.ts +188 -188
  122. package/src/extension/team-tool/api.ts +5 -2
  123. package/src/extension/team-tool/cancel.ts +224 -209
  124. package/src/extension/team-tool/config-patch.ts +36 -36
  125. package/src/extension/team-tool/context.ts +60 -60
  126. package/src/extension/team-tool/doctor.ts +242 -242
  127. package/src/extension/team-tool/handle-settings.ts +421 -195
  128. package/src/extension/team-tool/inspect.ts +41 -41
  129. package/src/extension/team-tool/lifecycle-actions.ts +139 -139
  130. package/src/extension/team-tool/parallel-dispatch.ts +156 -156
  131. package/src/extension/team-tool/plan.ts +19 -19
  132. package/src/extension/team-tool/respond.ts +112 -111
  133. package/src/extension/team-tool/run.ts +246 -228
  134. package/src/extension/team-tool/status.ts +110 -110
  135. package/src/extension/team-tool-types.ts +13 -13
  136. package/src/extension/team-tool.ts +16 -4
  137. package/src/extension/tool-result.ts +16 -16
  138. package/src/extension/validate-resources.ts +77 -77
  139. package/src/hooks/registry.ts +61 -61
  140. package/src/hooks/types.ts +40 -40
  141. package/src/i18n.ts +184 -184
  142. package/src/observability/correlation.ts +35 -35
  143. package/src/observability/event-to-metric.ts +68 -68
  144. package/src/observability/exporters/adapter.ts +30 -30
  145. package/src/observability/exporters/otlp-exporter.ts +106 -92
  146. package/src/observability/exporters/prometheus-exporter.ts +54 -54
  147. package/src/observability/metric-registry.ts +87 -87
  148. package/src/observability/metric-retention.ts +54 -54
  149. package/src/observability/metric-sink.ts +81 -56
  150. package/src/observability/metrics-primitives.ts +167 -167
  151. package/src/prompt/prompt-runtime.ts +72 -72
  152. package/src/runtime/adaptive-plan.ts +338 -0
  153. package/src/runtime/agent-control.ts +169 -169
  154. package/src/runtime/agent-memory.ts +72 -72
  155. package/src/runtime/agent-observability.ts +114 -114
  156. package/src/runtime/async-marker.ts +26 -26
  157. package/src/runtime/async-runner.ts +153 -79
  158. package/src/runtime/attention-events.ts +28 -28
  159. package/src/runtime/auto-resume.ts +100 -100
  160. package/src/runtime/background-runner.ts +122 -88
  161. package/src/runtime/cancellation.ts +61 -61
  162. package/src/runtime/capability-inventory.ts +116 -116
  163. package/src/runtime/child-pi-pool.ts +68 -0
  164. package/src/runtime/child-pi.ts +541 -463
  165. package/src/runtime/code-summary.ts +247 -247
  166. package/src/runtime/compaction-summary.ts +271 -271
  167. package/src/runtime/concurrency.ts +58 -58
  168. package/src/runtime/crash-recovery.ts +317 -301
  169. package/src/runtime/crew-agent-records.ts +379 -281
  170. package/src/runtime/crew-agent-runtime.ts +60 -60
  171. package/src/runtime/cross-extension-rpc.ts +72 -0
  172. package/src/runtime/custom-tools/irc-tool.ts +201 -201
  173. package/src/runtime/custom-tools/submit-result-tool.ts +90 -90
  174. package/src/runtime/deadletter.ts +47 -47
  175. package/src/runtime/delivery-coordinator.ts +176 -176
  176. package/src/runtime/delta-conflict.ts +360 -360
  177. package/src/runtime/diagnostic-export.ts +102 -102
  178. package/src/runtime/direct-run.ts +35 -35
  179. package/src/runtime/effectiveness.ts +82 -81
  180. package/src/runtime/errors/crew-errors.ts +166 -0
  181. package/src/runtime/event-stream-bridge.ts +92 -92
  182. package/src/runtime/foreground-control.ts +82 -82
  183. package/src/runtime/green-contract.ts +46 -46
  184. package/src/runtime/group-join.ts +234 -106
  185. package/src/runtime/heartbeat-watcher.ts +145 -124
  186. package/src/runtime/iteration-hooks.ts +267 -264
  187. package/src/runtime/live-agent-control.ts +88 -88
  188. package/src/runtime/live-agent-manager.ts +377 -179
  189. package/src/runtime/live-control-realtime.ts +36 -36
  190. package/src/runtime/live-session-runtime.ts +676 -599
  191. package/src/runtime/loop-gates.ts +129 -129
  192. package/src/runtime/manifest-cache.ts +263 -263
  193. package/src/runtime/mcp-proxy.ts +113 -113
  194. package/src/runtime/metric-parser.ts +40 -40
  195. package/src/runtime/model-fallback.ts +282 -274
  196. package/src/runtime/model-resolver.ts +118 -0
  197. package/src/runtime/output-validator.ts +187 -187
  198. package/src/runtime/overflow-recovery.ts +175 -175
  199. package/src/runtime/parallel-research.ts +44 -44
  200. package/src/runtime/parallel-utils.ts +156 -156
  201. package/src/runtime/parent-guard.ts +80 -80
  202. package/src/runtime/phase-progress.ts +217 -217
  203. package/src/runtime/pi-args.ts +165 -165
  204. package/src/runtime/pi-json-output.ts +111 -111
  205. package/src/runtime/pi-spawn.ts +167 -167
  206. package/src/runtime/policy-engine.ts +79 -79
  207. package/src/runtime/post-checks.ts +125 -122
  208. package/src/runtime/post-exit-stdio-guard.ts +86 -86
  209. package/src/runtime/process-status.ts +97 -73
  210. package/src/runtime/progress-event-coalescer.ts +43 -43
  211. package/src/runtime/recovery-recipes.ts +74 -74
  212. package/src/runtime/retry-executor.ts +81 -81
  213. package/src/runtime/role-permission.ts +39 -39
  214. package/src/runtime/run-tracker.ts +99 -0
  215. package/src/runtime/runtime-policy.ts +21 -0
  216. package/src/runtime/runtime-resolver.ts +94 -90
  217. package/src/runtime/scheduler.ts +294 -0
  218. package/src/runtime/semaphore.ts +131 -131
  219. package/src/runtime/sensitive-paths.ts +92 -92
  220. package/src/runtime/session-usage.ts +79 -79
  221. package/src/runtime/settings-store.ts +103 -0
  222. package/src/runtime/sidechain-output.ts +29 -29
  223. package/src/runtime/skill-instructions.ts +222 -222
  224. package/src/runtime/stale-reconciler.ts +198 -189
  225. package/src/runtime/streaming-output.ts +47 -0
  226. package/src/runtime/subagent-manager.ts +404 -395
  227. package/src/runtime/subprocess-tool-registry.ts +67 -67
  228. package/src/runtime/task-display.ts +38 -38
  229. package/src/runtime/task-graph-scheduler.ts +122 -122
  230. package/src/runtime/task-graph.ts +207 -207
  231. package/src/runtime/task-output-context.ts +177 -177
  232. package/src/runtime/task-packet.ts +93 -93
  233. package/src/runtime/task-quality.ts +207 -207
  234. package/src/runtime/task-runner/capabilities.ts +78 -78
  235. package/src/runtime/task-runner/live-executor.ts +131 -113
  236. package/src/runtime/task-runner/progress.ts +119 -119
  237. package/src/runtime/task-runner/prompt-builder.ts +139 -139
  238. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  239. package/src/runtime/task-runner/result-utils.ts +14 -14
  240. package/src/runtime/task-runner/run-projection.ts +103 -103
  241. package/src/runtime/task-runner/state-helpers.ts +22 -22
  242. package/src/runtime/task-runner.ts +469 -458
  243. package/src/runtime/team-runner.ts +693 -945
  244. package/src/runtime/usage-tracker.ts +71 -0
  245. package/src/runtime/worker-heartbeat.ts +21 -21
  246. package/src/runtime/worker-startup.ts +57 -57
  247. package/src/runtime/workflow-state.ts +187 -187
  248. package/src/runtime/yield-handler.ts +190 -189
  249. package/src/schema/config-schema.ts +172 -168
  250. package/src/schema/team-tool-schema.ts +126 -125
  251. package/src/schema/validation-types.ts +151 -148
  252. package/src/skills/discover-skills.ts +67 -67
  253. package/src/skills/skill-templates.ts +374 -374
  254. package/src/state/active-run-registry.ts +227 -191
  255. package/src/state/artifact-store.ts +130 -129
  256. package/src/state/atomic-write.ts +262 -178
  257. package/src/state/blob-store.ts +116 -116
  258. package/src/state/contracts.ts +111 -111
  259. package/src/state/event-log-rotation.ts +161 -158
  260. package/src/state/event-log.ts +383 -240
  261. package/src/state/event-reconstructor.ts +217 -217
  262. package/src/state/jsonl-writer.ts +82 -82
  263. package/src/state/locks.ts +146 -148
  264. package/src/state/mailbox.ts +446 -405
  265. package/src/state/state-store.ts +364 -351
  266. package/src/state/task-claims.ts +44 -44
  267. package/src/state/types.ts +285 -285
  268. package/src/state/usage.ts +29 -29
  269. package/src/subagents/async-entry.ts +1 -1
  270. package/src/subagents/index.ts +3 -3
  271. package/src/subagents/live/control.ts +1 -1
  272. package/src/subagents/live/manager.ts +1 -1
  273. package/src/subagents/live/realtime.ts +1 -1
  274. package/src/subagents/live/session-runtime.ts +1 -1
  275. package/src/subagents/manager.ts +1 -1
  276. package/src/subagents/spawn.ts +1 -1
  277. package/src/teams/discover-teams.ts +116 -116
  278. package/src/teams/team-config.ts +27 -27
  279. package/src/teams/team-serializer.ts +38 -38
  280. package/src/types/diff.d.ts +18 -18
  281. package/src/ui/agent-management-overlay.ts +144 -144
  282. package/src/ui/crew-widget.ts +487 -370
  283. package/src/ui/dashboard-panes/agents-pane.ts +109 -28
  284. package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
  285. package/src/ui/dashboard-panes/capability-pane.ts +59 -59
  286. package/src/ui/dashboard-panes/health-pane.ts +30 -30
  287. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
  288. package/src/ui/dashboard-panes/progress-pane.ts +30 -30
  289. package/src/ui/dashboard-panes/transcript-pane.ts +10 -10
  290. package/src/ui/heartbeat-aggregator.ts +63 -63
  291. package/src/ui/keybinding-map.ts +97 -94
  292. package/src/ui/live-conversation-overlay.ts +152 -0
  293. package/src/ui/live-run-sidebar.ts +180 -180
  294. package/src/ui/mascot.ts +442 -442
  295. package/src/ui/overlays/agent-picker-overlay.ts +57 -57
  296. package/src/ui/overlays/confirm-overlay.ts +58 -58
  297. package/src/ui/overlays/mailbox-compose-overlay.ts +144 -144
  298. package/src/ui/overlays/mailbox-compose-preview.ts +63 -63
  299. package/src/ui/overlays/mailbox-detail-overlay.ts +122 -122
  300. package/src/ui/pi-ui-compat.ts +57 -57
  301. package/src/ui/powerbar-publisher.ts +221 -197
  302. package/src/ui/render-scheduler.ts +216 -143
  303. package/src/ui/run-action-dispatcher.ts +118 -117
  304. package/src/ui/run-dashboard.ts +526 -464
  305. package/src/ui/run-event-bus.ts +208 -208
  306. package/src/ui/run-snapshot-cache.ts +826 -777
  307. package/src/ui/settings-overlay.ts +721 -0
  308. package/src/ui/snapshot-types.ts +86 -70
  309. package/src/ui/theme-adapter.ts +190 -190
  310. package/src/ui/tool-progress-formatter.ts +89 -0
  311. package/src/ui/transcript-cache.ts +94 -94
  312. package/src/ui/transcript-viewer.ts +335 -335
  313. package/src/utils/conflict-detect.ts +662 -0
  314. package/src/utils/env-filter.ts +30 -0
  315. package/src/utils/file-coalescer.ts +86 -86
  316. package/src/utils/frontmatter.ts +68 -68
  317. package/src/utils/fs-watch.ts +88 -31
  318. package/src/utils/gh-protocol.ts +479 -0
  319. package/src/utils/ids.ts +17 -17
  320. package/src/utils/incremental-reader.ts +104 -104
  321. package/src/utils/internal-error.ts +6 -6
  322. package/src/utils/names.ts +27 -27
  323. package/src/utils/paths.ts +102 -63
  324. package/src/utils/redaction.ts +44 -44
  325. package/src/utils/resolve-shell.ts +34 -0
  326. package/src/utils/safe-paths.ts +47 -47
  327. package/src/utils/scan-cache.ts +136 -136
  328. package/src/utils/sleep.ts +2 -1
  329. package/src/utils/sse-parser.ts +134 -134
  330. package/src/utils/task-name-generator.ts +337 -337
  331. package/src/utils/timings.ts +33 -33
  332. package/src/utils/visual.ts +243 -198
  333. package/src/workflows/discover-workflows.ts +139 -139
  334. package/src/workflows/validate-workflow.ts +40 -40
  335. package/src/workflows/workflow-config.ts +26 -26
  336. package/src/workflows/workflow-serializer.ts +32 -32
  337. package/src/worktree/branch-freshness.ts +45 -45
  338. package/src/worktree/cleanup.ts +75 -72
  339. package/src/worktree/worktree-manager.ts +188 -146
  340. package/teams/default.team.md +12 -12
  341. package/teams/fast-fix.team.md +11 -11
  342. package/teams/implementation.team.md +18 -18
  343. package/teams/parallel-research.team.md +14 -14
  344. package/teams/research.team.md +11 -11
  345. package/teams/review.team.md +12 -12
  346. package/tsconfig.json +19 -19
  347. package/workflows/default.workflow.md +30 -30
  348. package/workflows/fast-fix.workflow.md +23 -23
  349. package/workflows/implementation.workflow.md +43 -43
  350. package/workflows/parallel-research.workflow.md +46 -46
  351. package/workflows/research.workflow.md +22 -22
  352. package/workflows/review.workflow.md +30 -30
  353. package/skills/task-packet/SKILL.md +0 -28
  354. package/skills/verify-evidence/SKILL.md +0 -27
@@ -0,0 +1,61 @@
1
+ # 0008 Child-pi Warm Pool
2
+
3
+ Date: 2026-05-14
4
+
5
+ ## Status
6
+
7
+ Proposed — not yet implemented.
8
+
9
+ ## Context
10
+
11
+ Each task in a parallel team run spawns a fresh `pi` child process.
12
+ Cold-start of each child takes 2–5 s on Windows (most of that is Node
13
+ startup + module load). For a phase with 4 parallel tasks the spawn
14
+ cost serialises into the wall-clock floor.
15
+
16
+ ## Decision (proposed)
17
+
18
+ Add an opt-in warm pool managed by `child-pi.ts`:
19
+
20
+ - Config: `runtime.warmPool.enabled: false` (default), `runtime.warmPool.size: 2`.
21
+ - Pool process is spawned with a `PI_CREW_POOL_HEALTH=1` ping handshake
22
+ on startup; it parks in a `wait-for-prompt` state.
23
+ - On task dispatch, the parent writes the prompt + skill paths over
24
+ stdin and reads stdout normally.
25
+ - Each pool process is single-use: after one task completes, the
26
+ process exits and the pool refills in the background.
27
+ - A health check on reuse rejects any process that has unread stderr,
28
+ is past `maxIdleMs`, or has an open file handle outside its
29
+ scratch dir.
30
+
31
+ ## Alternatives considered
32
+
33
+ 1. Long-lived shared pool with state — unsafe (one task can pollute
34
+ the next); rejected.
35
+ 2. Pre-warm only when team-runner predicts a parallel batch — saves
36
+ pool size but adds prediction logic; defer to later optimisation.
37
+ 3. Status quo — retain the 2–5 s per task floor.
38
+
39
+ ## Consequences
40
+
41
+ Positive:
42
+ - Wall-clock floor of parallel batches drops by 2–4 s per pool slot.
43
+ - Behaviour identical from the worker's perspective (single prompt
44
+ per process).
45
+
46
+ Tradeoffs / risks:
47
+ - Pool processes are zombies until used. CPU cost is negligible but
48
+ RAM cost is ~30–60 MB per slot.
49
+ - Crash-recovery semantics need an extra branch: `child-stdout-final`
50
+ marker valid only after prompt was actually sent.
51
+ - Disabled by default for one release; enable globally once 50
52
+ consecutive runs pass on the dogfood machine.
53
+
54
+ ## Next steps
55
+
56
+ 1. Implement pool inside `src/runtime/child-pi.ts`; expose
57
+ `getPooledChild()` / `releasePooledChild()`.
58
+ 2. Add health-check protocol over stdin/stdout JSON.
59
+ 3. Stress test: 50 consecutive runs on a single pool, measure leak.
60
+ 4. Default off; flip default after one stable release window.
61
+ 5. Land ADR as "Accepted" after default-on rollout.
@@ -0,0 +1,23 @@
1
+ # Decisions
2
+
3
+ Decision records explain why important product, architecture, or harness choices were made.
4
+
5
+ Add a decision when:
6
+ - Runtime mode changes (child-process vs live-session)
7
+ - State format changes
8
+ - New dependency introduced
9
+ - API contract changes
10
+ - Security hardening applied
11
+ - Validation requirements added/removed/changed
12
+
13
+ Current decisions derived from 9 review rounds and 13 bug fixes.
14
+
15
+ ## Index
16
+
17
+ | ID | Title | Status |
18
+ |----|-------|--------|
19
+ | 0001 | Durable state as source of truth | Accepted |
20
+ | 0002 | Child-process for async runners | Accepted |
21
+ | 0003 | Depth guard for nested live-session | Accepted |
22
+ | 0004 | execFileSync over execSync | Accepted |
23
+ | 0005 | No TypeScript parameter properties | Accepted |
@@ -0,0 +1,463 @@
1
+ # Follow-up Plan — pi-crew (2026-05-12)
2
+
3
+ Tác giả: Droid (Factory) | Liên quan: `docs/code-review-2026-05-11.md`, commits `2aebf33`, `7c5b3c2`.
4
+
5
+ Tài liệu này tổng hợp các điểm tồn đọng SAU khi đã fix BUG-001..BUG-007 + NIT-001..NIT-004, gồm:
6
+ 1. **Quan ngại nhỏ** phát sinh từ chính các fix vừa apply.
7
+ 2. **Gaps mới phát hiện** khi review lại toàn bộ `pi-crew/` lần nữa.
8
+
9
+ Tất cả mục đều có ước lượng effort, mức ưu tiên, file/đoạn code liên quan, và đề xuất fix cụ thể.
10
+
11
+ ---
12
+
13
+ ## Phần A — Điều chỉnh các "quan ngại nhỏ" của fix vừa apply
14
+
15
+ ### A1 — `branchExists` chỉ check local ref → bỏ sót remote-tracking branch
16
+
17
+ **Severity:** Low | **Effort:** ~20 phút | **File:** `src/worktree/worktree-manager.ts:100-107`
18
+
19
+ **Hiện trạng:**
20
+ ```ts
21
+ function branchExists(repoRoot: string, branch: string): boolean {
22
+ try {
23
+ git(repoRoot, ["rev-parse", "--verify", `refs/heads/${branch}`]);
24
+ return true;
25
+ } catch { return false; }
26
+ }
27
+ ```
28
+
29
+ **Vấn đề:**
30
+ - Chỉ check `refs/heads/<branch>`. Nếu repo có remote-tracking `refs/remotes/origin/<branch>` (push từ máy khác) mà chưa có local, hàm trả `false` → đi vào nhánh `worktree add -b <branch> HEAD` → git có thể fail với "a branch named ... already exists" (vì git tạo local branch từ remote) hoặc tạo local branch divergent với remote.
31
+ - Hiếm trong workflow đơn-máy nhưng dễ gặp với CI/runner shared repo.
32
+
33
+ **Fix đề xuất:**
34
+ ```ts
35
+ function branchExists(repoRoot: string, branch: string): { local: boolean; remoteOnly: boolean } {
36
+ let local = false;
37
+ try { git(repoRoot, ["rev-parse", "--verify", `refs/heads/${branch}`]); local = true; } catch {}
38
+ if (local) return { local: true, remoteOnly: false };
39
+ // Check remote-tracking
40
+ try {
41
+ const out = execFileSync("git", ["for-each-ref", "--format=%(refname)", `refs/remotes/*/${branch}`],
42
+ { cwd: repoRoot, encoding: "utf-8" }).trim();
43
+ return { local: false, remoteOnly: out.length > 0 };
44
+ } catch { return { local: false, remoteOnly: false }; }
45
+ }
46
+
47
+ // In prepareTaskWorkspace:
48
+ const exists = branchExists(repoRoot, branch);
49
+ if (exists.local) {
50
+ git(repoRoot, ["worktree", "add", worktreePath, branch]);
51
+ } else if (exists.remoteOnly) {
52
+ // Create local from HEAD instead of remote (avoid divergent track)
53
+ git(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
54
+ } else {
55
+ git(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
56
+ }
57
+ ```
58
+
59
+ **Test cần thêm:** `test/unit/worktree-manager.test.ts`:
60
+ 1. Mock `git for-each-ref` trả về remote-tracking → assert không throw.
61
+ 2. Branch tồn tại cả local lẫn remote → ưu tiên local (reuse).
62
+
63
+ ---
64
+
65
+ ### A2 — `resolveJitiRegisterPath` fallback giờ bắt buộc `exists()` check
66
+
67
+ **Severity:** Low | **Effort:** ~10 phút | **File:** `src/runtime/async-runner.ts:33-39`
68
+
69
+ **Hiện trạng:**
70
+ ```ts
71
+ try {
72
+ const fromRequire = jitiRegisterPathFromPackageJson(requireFromHere.resolve("jiti/package.json"));
73
+ if (exists(fromRequire)) return fromRequire; // ← thêm exists() check
74
+ } catch { /* Fall through. */ }
75
+ return undefined;
76
+ ```
77
+
78
+ **Vấn đề:**
79
+ - Behaviour cũ: `require.resolve()` thành công → push vào candidates → return path (giả định `lib/jiti-register.mjs` luôn tồn tại bên cạnh `package.json`).
80
+ - Behaviour mới: thêm `exists()` check → nếu test mock `exists` chỉ accept đúng 1 path khác, fallback luôn fail. Behaviour ổn cho production (file luôn tồn tại), nhưng giảm robustness với jiti packaging exotic (vd. `lib` được rename trong distro).
81
+ - Không thực sự là bug, chỉ là defensive change.
82
+
83
+ **Fix đề xuất:**
84
+ Giữ nguyên `exists()` check (an toàn hơn), nhưng thêm 1 fallback nữa với `path.join(dirname, "register.mjs")` cho các phiên bản jiti khác nhau, và log diagnostic event khi cả 2 đều miss:
85
+
86
+ ```ts
87
+ try {
88
+ const pkgPath = requireFromHere.resolve("jiti/package.json");
89
+ const candidates = [
90
+ jitiRegisterPathFromPackageJson(pkgPath), // lib/jiti-register.mjs
91
+ path.join(path.dirname(pkgPath), "register.mjs"), // register.mjs (older jiti)
92
+ path.join(path.dirname(pkgPath), "dist", "register.mjs"), // dist/register.mjs (some pkg layouts)
93
+ ];
94
+ for (const c of candidates) if (exists(c)) return c;
95
+ } catch { /* Fall through. */ }
96
+ return undefined;
97
+ ```
98
+
99
+ **Test cần thêm:** `test/unit/async-runner.test.ts`:
100
+ - Case `lib/jiti-register.mjs` missing nhưng `register.mjs` tồn tại → resolver return.
101
+
102
+ ---
103
+
104
+ ### A3 — Test alias `__test__renameWithRetry*` trở thành `const`, có thể vô hiệu monkey-patch
105
+
106
+ **Severity:** Info | **Effort:** ~5 phút | **File:** `src/state/atomic-write.ts:73, 91`
107
+
108
+ **Hiện trạng:**
109
+ ```ts
110
+ export const __test__renameWithRetry = renameWithRetry;
111
+ ```
112
+
113
+ **Vấn đề:**
114
+ - Nếu test cũ làm `import * as mod from "../atomic-write.ts"; mod.__test__renameWithRetry = mockFn;` → assignment fail (read-only) hoặc không có effect trên `atomicWriteFile` (nó gọi `renameWithRetry` local).
115
+ - Hiện tại không có test nào trong repo monkey-patch field này (`Grep` confirm), nên không phải bug.
116
+
117
+ **Fix đề xuất:**
118
+ - Giữ nguyên alias (zero impact thực tế).
119
+ - Hoặc xoá hẳn 2 alias để giảm surface: chỉ export `renameWithRetry`, `renameWithRetryAsync`. Update test nếu có chỗ dùng alias.
120
+
121
+ **Action:** chỉ làm khi cần dọn dẹp API surface, không khẩn cấp.
122
+
123
+ ---
124
+
125
+ ## Phần B — Gaps mới phát hiện khi review lại
126
+
127
+ ### B1 — `bash` hardcoded → broken trên Windows (root cause của 8 test fail)
128
+
129
+ **Severity:** Medium | **Effort:** ~45 phút | **Files:**
130
+ - `src/runtime/post-checks.ts:82`
131
+ - `src/runtime/iteration-hooks.ts:137`
132
+
133
+ **Hiện trạng:**
134
+ ```ts
135
+ // post-checks.ts:82
136
+ const output = execFileSync("bash", [scriptPath], { ... });
137
+
138
+ // iteration-hooks.ts:137
139
+ const child = spawn("bash", [hookScriptPath], { ... });
140
+ ```
141
+
142
+ **Vấn đề:**
143
+ - Trên Windows, `bash` thường không có trên PATH (trừ khi cài Git Bash/WSL). 8 tests fail hiện tại đều do điều này.
144
+ - Code path nóng (post-task check, iteration hook) → user Windows không dùng được tính năng này.
145
+ - Comment trong file đã ghi "Spawns `bash <script>`" → docs cũng cần update.
146
+
147
+ **Fix đề xuất:**
148
+ 1. Tìm bash thông minh:
149
+ ```ts
150
+ function resolveBashCmd(): string {
151
+ if (process.platform !== "win32") return "bash";
152
+ // Try Git Bash locations
153
+ for (const cand of [
154
+ process.env.SHELL,
155
+ "C:\\Program Files\\Git\\bin\\bash.exe",
156
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
157
+ ]) {
158
+ if (cand && fs.existsSync(cand)) return cand;
159
+ }
160
+ return "bash"; // last resort — let spawn fail with clearer error
161
+ }
162
+ ```
163
+ 2. Thay 2 chỗ `"bash"` bằng `resolveBashCmd()`.
164
+ 3. Trên Windows, nếu script là `.ps1` → dùng `powershell -File`; nếu `.cmd/.bat` → spawn trực tiếp.
165
+ 4. Hoặc đơn giản hơn: skip test nếu `!isScriptRunnable("bash")`.
166
+
167
+ **Test cần thêm:**
168
+ - Trên CI Linux: tests hiện tại pass.
169
+ - Trên Windows: skip nếu không có bash, hoặc tạo `.ps1` variant.
170
+ - Mock test cho `resolveBashCmd()` với platform stub.
171
+
172
+ ---
173
+
174
+ ### B2 — Thiếu test trực tiếp cho `worktree-manager.ts` (covers BUG-005, BUG-006)
175
+
176
+ **Severity:** Medium | **Effort:** ~1h | **File:** thiếu `test/unit/worktree-manager.test.ts`
177
+
178
+ **Vấn đề:**
179
+ - BUG-005 fix (`branchExists` + `pruneStaleWorktrees`) và BUG-006 fix (`isDirectory()` check) **không có test trực tiếp**.
180
+ - Code review đã flag điều này nhưng commit fix chỉ thêm test cho schema.
181
+
182
+ **Fix đề xuất:** tạo `test/unit/worktree-manager.test.ts` với (sử dụng `tmpdir` + real git):
183
+
184
+ ```ts
185
+ import { test } from "node:test";
186
+ import assert from "node:assert/strict";
187
+ import * as fs from "node:fs";
188
+ import * as os from "node:os";
189
+ import * as path from "node:path";
190
+ import { execFileSync } from "node:child_process";
191
+ import { prepareTaskWorkspace } from "../../src/worktree/worktree-manager.ts";
192
+ import type { TeamRunManifest, TeamTaskState } from "../../src/state/types.ts";
193
+
194
+ function initGitRepo(dir: string) {
195
+ execFileSync("git", ["init", "-q", "--initial-branch=main"], { cwd: dir });
196
+ execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", "commit", "--allow-empty", "-m", "init"], { cwd: dir });
197
+ }
198
+
199
+ test("prepareTaskWorkspace recovers when branch exists but worktree dir is gone", () => {
200
+ const repo = fs.mkdtempSync(path.join(os.tmpdir(), "pi-crew-wt-"));
201
+ initGitRepo(repo);
202
+ // Pre-create the branch (simulating leftover from crashed run)
203
+ execFileSync("git", ["branch", "pi-crew/run1/task1"], { cwd: repo });
204
+ const manifest = { /* ... minimal manifest ... */ } as TeamRunManifest;
205
+ const task: TeamTaskState = { id: "task1", /* ... */ } as TeamTaskState;
206
+ const result = prepareTaskWorkspace(manifest, task);
207
+ assert.ok(result.worktreePath);
208
+ });
209
+
210
+ test("linkNodeModulesIfPresent rejects when node_modules is a file", () => {
211
+ // ... create file at node_modules location → assert no symlink created
212
+ });
213
+ ```
214
+
215
+ ---
216
+
217
+ ### B3 — `artifact-store.ts` thiếu test cho hash/size integrity (covers BUG-002)
218
+
219
+ **Severity:** Medium | **Effort:** ~20 phút | **File:** thiếu `test/unit/artifact-store.test.ts`
220
+
221
+ **Vấn đề:**
222
+ - BUG-002 fix đổi hash từ `options.content` sang `redactSecretString(options.content)` đảm bảo `contentHash` khớp bytes trên đĩa.
223
+ - Không có test verify điều này. Có `api-artifact-security.test.ts` nhưng không assert hash.
224
+
225
+ **Fix đề xuất:** tạo `test/unit/artifact-store.test.ts`:
226
+ ```ts
227
+ import { test } from "node:test";
228
+ import assert from "node:assert/strict";
229
+ import * as fs from "node:fs";
230
+ import * as path from "node:path";
231
+ import * as os from "node:os";
232
+ import { createHash } from "node:crypto";
233
+ import { writeArtifact } from "../../src/state/artifact-store.ts";
234
+
235
+ test("writeArtifact: contentHash matches sha256 of bytes on disk", () => {
236
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-crew-art-"));
237
+ const desc = writeArtifact(root, {
238
+ kind: "log", relativePath: "x.log",
239
+ content: "api_key=AKIA0123456789ABCDEF\nplain text",
240
+ producer: "test",
241
+ });
242
+ const onDisk = fs.readFileSync(desc.path);
243
+ const expected = createHash("sha256").update(onDisk).digest("hex");
244
+ assert.strictEqual(desc.contentHash, expected);
245
+ assert.strictEqual(desc.sizeBytes, onDisk.length);
246
+ assert.ok(!onDisk.toString("utf-8").includes("AKIA0123456789ABCDEF"));
247
+ });
248
+
249
+ test("writeArtifact: rejects path traversal", () => {
250
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-crew-art-"));
251
+ assert.throws(() => writeArtifact(root, {
252
+ kind: "log", relativePath: "../escape.log", content: "x", producer: "t",
253
+ }), /Invalid artifact path/);
254
+ });
255
+ ```
256
+
257
+ ---
258
+
259
+ ### B4 — Thiếu test parity sync vs async cho lock retry (covers BUG-004)
260
+
261
+ **Severity:** Low | **Effort:** ~30 phút | **File:** mở rộng `test/unit/locks-race.test.ts`
262
+
263
+ **Vấn đề:**
264
+ - Fix BUG-004 đồng bộ hoá behaviour sync ↔ async (cả 2 cùng retry tới deadline). `locks-race.test.ts` chỉ test sơ bộ; không có case `rmSync` race + assert lock được acquire sau retry trên cả sync và async.
265
+
266
+ **Fix đề xuất:** thêm 2 test:
267
+ ```ts
268
+ test("withRunLockSync retries when rmSync fails once on stale lock", async () => {
269
+ // Create stale lock, monkey-patch fs.rmSync first call → throw EBUSY
270
+ // Assert lock is acquired on retry
271
+ });
272
+
273
+ test("withRunLock (async) and withRunLockSync exhibit identical stale-lock recovery", async () => {
274
+ // Same scenario for both → both succeed
275
+ });
276
+ ```
277
+
278
+ ---
279
+
280
+ ### B5 — `runSetupHook` không filter dangerous env vars khi spawn hook
281
+
282
+ **Severity:** Low (defense-in-depth) | **Effort:** ~15 phút | **File:** `src/worktree/worktree-manager.ts:67-88`
283
+
284
+ **Hiện trạng:**
285
+ ```ts
286
+ const result = spawnSync(nodeHook ? process.execPath : hookPath, ..., {
287
+ cwd: worktreePath,
288
+ encoding: "utf-8",
289
+ input: JSON.stringify({...}),
290
+ timeout: cfg.setupHookTimeoutMs ?? 30_000,
291
+ shell: false,
292
+ // ← KHÔNG truyền env → spawn dùng process.env (full inherit)
293
+ });
294
+ ```
295
+
296
+ **Vấn đề:**
297
+ - Hook chạy với `process.env` đầy đủ → có thể leak `API_KEY`, `*_TOKEN`, `OPENAI_KEY`, etc. Đối lập với `post-checks.ts` và `iteration-hooks.ts` (đã restrict env).
298
+ - AGENTS.md security baseline: "env-secret filtering before spawn".
299
+
300
+ **Fix đề xuất:**
301
+ ```ts
302
+ import { sanitizeEnvSecrets } from "../utils/redaction.ts"; // hoặc tương đương
303
+
304
+ const result = spawnSync(..., {
305
+ cwd: worktreePath,
306
+ encoding: "utf-8",
307
+ input: JSON.stringify({...}),
308
+ timeout: cfg.setupHookTimeoutMs ?? 30_000,
309
+ shell: false,
310
+ env: sanitizeEnvSecrets(process.env, { allowList: ["PATH", "HOME", "USERPROFILE", "TEMP", "TMP", "LANG", "PI_*"] }),
311
+ });
312
+ ```
313
+
314
+ (Tận dụng helper đã có trong `child-pi.ts` — refactor thành util chung.)
315
+
316
+ ---
317
+
318
+ ### B6 — `prepareTaskWorkspace` không có assertion về branch sanity sau `branchExists`
319
+
320
+ **Severity:** Info | **Effort:** ~10 phút | **File:** `src/worktree/worktree-manager.ts:127-133`
321
+
322
+ **Hiện trạng:**
323
+ ```ts
324
+ pruneStaleWorktrees(repoRoot);
325
+ if (branchExists(repoRoot, branch)) {
326
+ git(repoRoot, ["worktree", "add", worktreePath, branch]);
327
+ }
328
+ ```
329
+
330
+ **Vấn đề:**
331
+ - Nếu branch hiện đang **checked out** ở một worktree khác (chưa bị prune), `git worktree add <path> <branch>` sẽ fail. Cần catch và emit hint rõ hơn cho user.
332
+
333
+ **Fix đề xuất:** wrap với try/catch, parse error message, throw error có actionable message:
334
+ ```ts
335
+ try {
336
+ git(repoRoot, ["worktree", "add", worktreePath, branch]);
337
+ } catch (error) {
338
+ const msg = error instanceof Error ? error.message : String(error);
339
+ if (/already checked out at/.test(msg)) {
340
+ throw new Error(`Branch '${branch}' is checked out at another worktree. Run \`team cleanup runId=${manifest.runId} force=true\` or manually remove the conflicting worktree.`);
341
+ }
342
+ throw error;
343
+ }
344
+ ```
345
+
346
+ ---
347
+
348
+ ### B7 — `team-tool.ts` có 1 lazy-import `handleRun` không theo style `// LAZY: <reason>`
349
+
350
+ **Severity:** Info | **Effort:** ~2 phút | **File:** `src/extension/team-tool.ts:56-62`
351
+
352
+ **Hiện trạng:**
353
+ ```ts
354
+ // Lazy-loaded: run.ts pulls in spawnBackgroundTeamRun, resolveCrewRuntime, etc.
355
+ // Static import fails silently in some jiti contexts (child-process), leaving handleRun undefined.
356
+ import type { handleRun as HandleRunFn } from "./team-tool/run.ts";
357
+ let _cachedHandleRun: typeof HandleRunFn | undefined;
358
+ async function handleRun(...args: Parameters<typeof HandleRunFn>): Promise<...> {
359
+ if (!_cachedHandleRun) {
360
+ const mod = await import("./team-tool/run.ts"); // ← thiếu // LAZY: marker
361
+ ...
362
+ }
363
+ }
364
+ ```
365
+
366
+ **Fix đề xuất:** thêm marker để đồng nhất với 11 chỗ còn lại đã được đánh dấu:
367
+ ```ts
368
+ async function handleRun(...) {
369
+ if (!_cachedHandleRun) {
370
+ // LAZY: run.ts pulls in spawnBackgroundTeamRun + resolveCrewRuntime; also avoids jiti import race.
371
+ const mod = await import("./team-tool/run.ts");
372
+ ...
373
+ }
374
+ }
375
+ ```
376
+
377
+ ---
378
+
379
+ ### B8 — `redaction-transcript-roundtrip.test.ts` chưa tồn tại (NIT-004)
380
+
381
+ **Severity:** Low | **Effort:** ~30 phút | **File:** thiếu test mới
382
+
383
+ **Vấn đề:**
384
+ - Code review NIT-004 đề xuất test verify rằng transcript trên đĩa và artifact result đều không chứa secret raw. Hiện chưa có test này.
385
+
386
+ **Fix đề xuất:** tạo `test/unit/redaction-transcript-roundtrip.test.ts`:
387
+ 1. Tạo fake transcript JSONL với line chứa `OPENAI_API_KEY=sk-abc...`.
388
+ 2. Call `appendTranscript` (qua `child-pi` helper export).
389
+ 3. Read file → assert không có secret raw.
390
+ 4. Call recovery path → assert artifact result cũng đã redact.
391
+
392
+ ---
393
+
394
+ ### B9 — CI grep-check chặn `await import(...)` không marker
395
+
396
+ **Severity:** Low | **Effort:** ~15 phút | **File:** thêm script + GitHub Actions
397
+
398
+ **Vấn đề:**
399
+ - Code review BUG-003 Option A đề xuất "thêm grep-check trong CI để chặn dynamic import không marker". Chưa làm.
400
+
401
+ **Fix đề xuất:** tạo `scripts/check-lazy-imports.mjs`:
402
+ ```js
403
+ import { execSync } from "node:child_process";
404
+ const out = execSync(
405
+ `git grep -nE 'await import\\(' -- 'src/**/*.ts' | grep -v '// LAZY:'`,
406
+ { encoding: "utf-8" }
407
+ ).split("\n").filter((l) => l && !l.includes("// LAZY:"));
408
+ // Hoặc kiểm tra dòng ngay trước có chứa `// LAZY:`
409
+ if (out.length > 0) {
410
+ console.error("Dynamic imports without `// LAZY:` marker:\n" + out.join("\n"));
411
+ process.exit(1);
412
+ }
413
+ ```
414
+ Thêm vào `package.json`:
415
+ ```json
416
+ "scripts": {
417
+ "check:lazy-imports": "node scripts/check-lazy-imports.mjs",
418
+ "ci": "npm run typecheck && npm run check:lazy-imports && npm test && npm pack --dry-run"
419
+ }
420
+ ```
421
+
422
+ ---
423
+
424
+ ## Ưu tiên thực hiện
425
+
426
+ | # | Item | Severity | Effort | Khuyến nghị |
427
+ |---|---|---|---|---|
428
+ | 1 | B1 (bash portability) | Medium | 45 phút | Sprint hiện tại — fix 8 test fail trên Windows |
429
+ | 2 | B2 (worktree test) | Medium | 1h | Sprint hiện tại — regression guard cho BUG-005/006 |
430
+ | 3 | B3 (artifact-store test) | Medium | 20 phút | Sprint hiện tại — verify BUG-002 fix |
431
+ | 4 | A1 (branchExists remote-tracking) | Low | 20 phút | Sprint kế tiếp |
432
+ | 5 | B5 (setup-hook env filter) | Low | 15 phút | Sprint kế tiếp — defense-in-depth |
433
+ | 6 | B6 (worktree checked-out hint) | Info | 10 phút | Sprint kế tiếp — UX |
434
+ | 7 | B7 (LAZY marker đồng nhất) | Info | 2 phút | Lúc nào cũng được |
435
+ | 8 | B4 (lock parity test) | Low | 30 phút | Sprint kế tiếp |
436
+ | 9 | B8 (redaction roundtrip test) | Low | 30 phút | Sprint kế tiếp |
437
+ | 10 | B9 (CI grep-check) | Low | 15 phút | Sprint kế tiếp |
438
+ | 11 | A2 (async-runner fallback robust) | Low | 10 phút | Không cấp bách |
439
+ | 12 | A3 (test alias cleanup) | Info | 5 phút | Tuỳ ý |
440
+
441
+ **Tổng effort ưu tiên 1 (medium):** ~2h 5 phút.
442
+ **Tổng effort ưu tiên 2 (low):** ~1h 50 phút.
443
+
444
+ ---
445
+
446
+ ## Đề xuất commit batches
447
+
448
+ - **Batch 1 (must-fix):** B1 + B2 + B3 → 1 PR "test+portability hardening" (~2h).
449
+ - **Batch 2 (nice-to-have):** A1 + B5 + B6 + B7 + B9 → 1 PR "worktree + lazy-import polish" (~1h).
450
+ - **Batch 3 (test debt):** B4 + B8 → 1 PR "additional regression tests" (~1h).
451
+ - **Defer:** A2, A3 cho đến khi có user-visible issue.
452
+
453
+ ---
454
+
455
+ ## Điểm tích cực sau review lần 2
456
+
457
+ - Tất cả 7 BUG + 4 NIT từ code review trước đã được fix với commit rõ ràng, có test cho schema.
458
+ - Comment `// LAZY:` đã được thêm consistent ở 11/12 site dynamic import.
459
+ - Lock retry logic giờ thống nhất sync ↔ async (cả 2 đều có deadline + retry).
460
+ - Worktree handle resume crash đúng cách (prune + branchExists fallback).
461
+ - Artifact `contentHash` giờ verifiable bằng `sha256(fs.readFileSync(desc.path))`.
462
+ - Không còn `any` type trong `src/` (grep confirm).
463
+ - `node_modules/jiti` resolution robust với mọi monorepo layout.