pi-crew 0.8.14 → 0.9.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 (86) hide show
  1. package/CHANGELOG.md +366 -0
  2. package/README.md +112 -2
  3. package/docs/FEATURE_INTAKE.md +1 -1
  4. package/docs/HARNESS.md +20 -19
  5. package/docs/PROJECT_REVIEW.md +132 -133
  6. package/docs/PROJECT_REVIEW_FIXES.md +130 -131
  7. package/docs/actions-reference.md +127 -121
  8. package/docs/architecture.md +1 -1
  9. package/docs/code-review-2026-05-11.md +134 -134
  10. package/docs/commands-reference.md +108 -106
  11. package/docs/comparison-pi-subagents-vs-pi-crew.md +105 -105
  12. package/docs/deep-review-report.md +1 -1
  13. package/docs/dynamic-workflows.md +90 -0
  14. package/docs/fixes/BATCH_A_H1_H2.md +17 -17
  15. package/docs/fixes/bug-007-async-notifier-stale-ctx.md +23 -23
  16. package/docs/followup-plan-2026-05-12.md +135 -135
  17. package/docs/followup-review-2026-05-12.md +86 -86
  18. package/docs/followup-review-round3-2026-05-12.md +123 -123
  19. package/docs/goals.md +59 -0
  20. package/docs/implementation-plan-top3.md +4 -4
  21. package/docs/issue-29-analysis.md +2 -2
  22. package/docs/oh-my-pi-research.md +154 -154
  23. package/docs/optimization-plan.md +2 -0
  24. package/docs/perf/baseline-2026-05.md +9 -9
  25. package/docs/perf/final-report-2026-05.md +2 -2
  26. package/docs/perf/sprint-1-report.md +2 -2
  27. package/docs/perf/sprint-2-report.md +1 -1
  28. package/docs/perf/upgrade-plan-2026-05.md +72 -72
  29. package/docs/pi-crew-bugs.md +230 -230
  30. package/docs/pi-crew-investigation-report.md +102 -102
  31. package/docs/pi-crew-test-round5.md +4 -4
  32. package/docs/runtime-analysis-child-vs-live.md +57 -57
  33. package/docs/runtime-migration-in-process-analysis.md +97 -97
  34. package/package.json +2 -4
  35. package/skills/orchestration/SKILL.md +11 -11
  36. package/src/agents/agent-config.ts +4 -0
  37. package/src/config/config.ts +39 -0
  38. package/src/config/types.ts +11 -0
  39. package/src/extension/action-suggestions.ts +2 -1
  40. package/src/extension/async-notifier.ts +10 -0
  41. package/src/extension/help.ts +14 -0
  42. package/src/extension/registration/commands.ts +27 -0
  43. package/src/extension/team-tool/destructive-gate.ts +1 -1
  44. package/src/extension/team-tool/goal-wrap.ts +288 -0
  45. package/src/extension/team-tool/goal.ts +405 -0
  46. package/src/extension/team-tool/run.ts +103 -4
  47. package/src/extension/team-tool/workflow-manage.ts +194 -0
  48. package/src/extension/team-tool.ts +20 -0
  49. package/src/hooks/types.ts +3 -1
  50. package/src/runtime/async-runner.ts +27 -2
  51. package/src/runtime/background-runner.ts +68 -19
  52. package/src/runtime/child-pi.ts +9 -1
  53. package/src/runtime/completion-guard.ts +1 -1
  54. package/src/runtime/dynamic-workflow-context.ts +450 -0
  55. package/src/runtime/dynamic-workflow-runner.ts +180 -0
  56. package/src/runtime/global-worker-cap.ts +96 -0
  57. package/src/runtime/goal-evaluator.ts +294 -0
  58. package/src/runtime/goal-loop-runner.ts +612 -0
  59. package/src/runtime/goal-state-store.ts +209 -0
  60. package/src/runtime/iteration-hooks.ts +2 -1
  61. package/src/runtime/pi-args.ts +10 -2
  62. package/src/runtime/post-checks.ts +2 -1
  63. package/src/runtime/result-extractor.ts +32 -0
  64. package/src/runtime/team-runner.ts +11 -1
  65. package/src/runtime/verification-gates.ts +88 -5
  66. package/src/runtime/verification-integrity.ts +110 -0
  67. package/src/runtime/verification-worktree.ts +136 -0
  68. package/src/runtime/workspace-lock.ts +448 -0
  69. package/src/schema/config-schema.ts +26 -0
  70. package/src/schema/team-tool-schema.ts +39 -4
  71. package/src/state/atomic-write.ts +9 -0
  72. package/src/state/contracts.ts +14 -0
  73. package/src/state/crew-init.ts +18 -5
  74. package/src/state/event-log.ts +7 -1
  75. package/src/state/state-store.ts +2 -0
  76. package/src/state/types.ts +82 -0
  77. package/src/state/worker-atomic-writer.ts +190 -0
  78. package/src/utils/env-allowlist.ts +30 -0
  79. package/src/utils/redaction.ts +104 -24
  80. package/src/utils/safe-paths.ts +55 -14
  81. package/src/workflows/discover-workflows.ts +25 -1
  82. package/src/workflows/workflow-config.ts +13 -0
  83. package/src/worktree/cleanup.ts +2 -1
  84. package/src/worktree/worktree-manager.ts +4 -3
  85. package/teams/parallel-research.team.md +1 -1
  86. package/workflows/examples/hello.dwf.ts +24 -0
@@ -1,24 +1,24 @@
1
- # Bug #7: Async notifier "stale ctx detected; stopping notifier" — không restart
1
+ # Bug #7: Async notifier "stale ctx detected; stopping notifier" — does not restart
2
2
 
3
3
  | Field | Value |
4
4
  |---|---|
5
5
  | **Severity** | 🔴 HIGH |
6
6
  | **Status** | Root cause confirmed, fix pending |
7
- | **Affected** | Tất cả pi-crew users sau khi Pi session restarts/compacts |
8
- | **Symptom** | Sau restart/compact, notifier dừng hoàn toàn không còn nhận notifications cho run completions |
7
+ | **Affected** | All pi-crew users after a Pi session restarts/compacts |
8
+ | **Symptom** | After restart/compact, the notifier stops completelyrun-completion notifications are no longer received |
9
9
 
10
- ## Mô tả
10
+ ## Description
11
11
 
12
- Sau khi Pi restart (hoặc /clear, /compact, session switch), xuất hiện error:
12
+ After Pi restarts (or /clear, /compact, session switch), the following error appears:
13
13
  ```
14
14
  [pi-crew] async notifier stale ctx detected; stopping notifier.
15
15
  ```
16
16
 
17
- Sau đó, pi-crew **không còn deliver notifications** cho run completions. Background runs hoàn thành nhưng user không được báo.
17
+ After that, pi-crew **no longer delivers notifications** for run completions. Background runs finish, but the user is never notified.
18
18
 
19
19
  ## Root cause
20
20
 
21
- ### Flow bình thường (mong đợi):
21
+ ### Normal flow (expected):
22
22
 
23
23
  ```
24
24
  Pi session_start event
@@ -26,28 +26,28 @@ Pi session_start event
26
26
  → currentCtx = newCtx
27
27
  → cleanupRuntime() (stop old notifier)
28
28
  → startAsyncRunNotifier(newCtx, ...)
29
- → New notifier hoạt động với newCtx ✅
29
+ → New notifier runs with newCtx ✅
30
30
  ```
31
31
 
32
- ### Flow bị lỗi:
32
+ ### Buggy flow:
33
33
 
34
34
  ```
35
35
  1. Old notifier interval tick fires
36
- 2. isCurrent(generation) check → true (generation chưa increment)
36
+ 2. isCurrent(generation) check → true (generation hasn't incremented yet)
37
37
  3. ctx.ui.notify() called
38
- 4. Pi đã invalidate old ctx → throw Error("This extension ctx is stale...")
38
+ 4. Pi has already invalidated the old ctx → throws Error("This extension ctx is stale...")
39
39
  5. Catch block: message.includes("stale") → true
40
40
  6. stopAsyncRunNotifier(state) → clearInterval(interval)
41
41
  7. console.error("stale ctx detected; stopping notifier.")
42
42
  8. ❌ Notifier stopped permanently
43
43
  ```
44
44
 
45
- Vấn đề: **session_start handler** CHƯA kịp chạy để start new notifier.
45
+ The problem: the **session_start handler** hasn't had a chance to run yet to start the new notifier.
46
46
 
47
- ### Tại sao session_start chưa chạy?
47
+ ### Why hasn't session_start run yet?
48
48
 
49
- Khi Pi invalidate ctx, old notifier interval **vẫn đang chạy** (clearInterval chưa được gọi). Interval tick xảy ra **trước khi** `cleanupRuntime()` hoặc `session_start` handler chạy. Đây **race condition** giữa:
50
- - Old notifier's setInterval tick
49
+ When Pi invalidates ctx, the old notifier interval **is still running** (clearInterval hasn't been called yet). The interval tick occurs **before** `cleanupRuntime()` or the `session_start` handler runs. This is a **race condition** between:
50
+ - The old notifier's setInterval tick
51
51
  - Pi's session shutdown/start event sequence
52
52
 
53
53
  ### Code location
@@ -67,21 +67,21 @@ Khi Pi invalidate ctx, old notifier interval **vẫn đang chạy** (clearInterv
67
67
  ### Why it matters
68
68
 
69
69
  - User restarts Pi → notifier dies → background runs complete silently
70
- - User phải manually check `team status` để biết runs hoàn thành
71
- - Ảnh hưởng UX nghiêm trọng: pi-crew "câm" sau restart
70
+ - The user has to manually check `team status` to learn that runs finished
71
+ - Severe UX impact: pi-crew goes "silent" after a restart
72
72
 
73
73
  ## Fix
74
74
 
75
- ### Option A: Silent swallow stale errors (recommended)
75
+ ### Option A: Silently swallow stale errors (recommended)
76
76
 
77
- Thay stop notifier, chỉ **skip notification** này chờ session_start restart notifier với new ctx:
77
+ Instead of stopping the notifier, just **skip this notification** and let session_start restart the notifier with the new ctx:
78
78
 
79
79
  ```typescript
80
80
  } catch (error) {
81
81
  const message = error instanceof Error ? error.message : String(error);
82
82
  if (message.includes("stale") || message.includes("session replacement") || message.includes("old ctx")) {
83
83
  // Don't stop — session_start will create a new notifier with the new ctx.
84
- // This old notifier's isCurrent guard will return false on next tick,
84
+ // This old notifier's isCurrent guard will return false on the next tick,
85
85
  // making it effectively dormant until cleaned up.
86
86
  return;
87
87
  }
@@ -89,11 +89,11 @@ Thay vì stop notifier, chỉ **skip notification** này và chờ session_start
89
89
  }
90
90
  ```
91
91
 
92
- Rationale: `isCurrent` guard sẽ return false sau khi `sessionGeneration++` → old notifier interval vẫn chạy nhưng không làm (silent). New notifier từ `session_start` sẽ hoạt động bình thường.
92
+ Rationale: the `isCurrent` guard will return false once `sessionGeneration++` runs the old notifier interval keeps running but does nothing (silent). The new notifier from `session_start` will work normally.
93
93
 
94
- ### Option B: Add explicit restart mechanism
94
+ ### Option B: Add an explicit restart mechanism
95
95
 
96
- Add `restartAsyncRunNotifier()` function gọi từ `session_start` handler. Nhưng Option A đơn giản hơn và đủ.
96
+ Add a `restartAsyncRunNotifier()` function and call it from the `session_start` handler. But Option A is simpler and sufficient.
97
97
 
98
98
  ## Key files
99
99
 
@@ -1,22 +1,22 @@
1
1
  # Follow-up Plan — pi-crew (2026-05-12)
2
2
 
3
- Tác giả: Droid (Factory) | Liên quan: `docs/code-review-2026-05-11.md`, commits `2aebf33`, `7c5b3c2`.
3
+ Author: Droid (Factory) | Related: `docs/code-review-2026-05-11.md`, commits `2aebf33`, `7c5b3c2`.
4
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.
5
+ This document consolidates the outstanding items AFTER fixing BUG-001..BUG-007 + NIT-001..NIT-004, comprising:
6
+ 1. **Minor concerns** arising from the fixes just applied.
7
+ 2. **Newly discovered gaps** found when re-reviewing the entire `pi-crew/` codebase.
8
8
 
9
- Tất cả mục đều ước lượng effort, mức ưu tiên, file/đoạn code liên quan, đề xuất fix cụ thể.
9
+ Every item includes an effort estimate, priority level, related file/code section, and a concrete fix proposal.
10
10
 
11
11
  ---
12
12
 
13
- ## Phần A — Điều chỉnh các "quan ngại nhỏ" của fix vừa apply
13
+ ## Part A — Adjusting minor concerns from the fixes just applied
14
14
 
15
- ### A1 — `branchExists` chỉ check local ref → bỏ sót remote-tracking branch
15
+ ### A1 — `branchExists` only checks local ref → misses remote-tracking branches
16
16
 
17
- **Severity:** Low | **Effort:** ~20 phút | **File:** `src/worktree/worktree-manager.ts:100-107`
17
+ **Severity:** Low | **Effort:** ~20 minutes | **File:** `src/worktree/worktree-manager.ts:100-107`
18
18
 
19
- **Hiện trạng:**
19
+ **Current state:**
20
20
  ```ts
21
21
  function branchExists(repoRoot: string, branch: string): boolean {
22
22
  try {
@@ -26,11 +26,11 @@ function branchExists(repoRoot: string, branch: string): boolean {
26
26
  }
27
27
  ```
28
28
 
29
- **Vấn đề:**
30
- - Chỉ check `refs/heads/<branch>`. Nếu repo remote-tracking `refs/remotes/origin/<branch>` (push từ máy khác) chưa local, hàm trả `false` → đi vào nhánh `worktree add -b <branch> HEAD` → git thể fail với "a branch named ... already exists" ( 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.
29
+ **Problem:**
30
+ - Only checks `refs/heads/<branch>`. If the repo has a remote-tracking `refs/remotes/origin/<branch>` (pushed from another machine) but no local one yet, the function returns `false` → enters the `worktree add -b <branch> HEAD` branch → git may fail with "a branch named ... already exists" (because git creates a local branch from the remote) or create a local branch divergent from the remote.
31
+ - Rare in single-machine workflows but easily hit with CI/runner shared repos.
32
32
 
33
- **Fix đề xuất:**
33
+ **Proposed fix:**
34
34
  ```ts
35
35
  function branchExists(repoRoot: string, branch: string): { local: boolean; remoteOnly: boolean } {
36
36
  let local = false;
@@ -56,32 +56,32 @@ if (exists.local) {
56
56
  }
57
57
  ```
58
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).
59
+ **Tests to add:** `test/unit/worktree-manager.test.ts`:
60
+ 1. Mock `git for-each-ref` returning a remote-tracking ref → assert no throw.
61
+ 2. Branch exists in both local and remote → prefer local (reuse).
62
62
 
63
63
  ---
64
64
 
65
- ### A2 — `resolveJitiRegisterPath` fallback giờ bắt buộc `exists()` check
65
+ ### A2 — `resolveJitiRegisterPath` fallback now requires an `exists()` check
66
66
 
67
- **Severity:** Low | **Effort:** ~10 phút | **File:** `src/runtime/async-runner.ts:33-39`
67
+ **Severity:** Low | **Effort:** ~10 minutes | **File:** `src/runtime/async-runner.ts:33-39`
68
68
 
69
- **Hiện trạng:**
69
+ **Current state:**
70
70
  ```ts
71
71
  try {
72
72
  const fromRequire = jitiRegisterPathFromPackageJson(requireFromHere.resolve("jiti/package.json"));
73
- if (exists(fromRequire)) return fromRequire; // ← thêm exists() check
73
+ if (exists(fromRequire)) return fromRequire; // ← added exists() check
74
74
  } catch { /* Fall through. */ }
75
75
  return undefined;
76
76
  ```
77
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ự bug, chỉ defensive change.
78
+ **Problem:**
79
+ - Old behavior: `require.resolve()` succeeds → push into candidates → return path (assumes `lib/jiti-register.mjs` always exists next to `package.json`).
80
+ - New behavior: adds an `exists()` check → if the test mock for `exists` only accepts one different path, the fallback always fails. The behavior is fine for production (file always exists), but reduces robustness with exotic jiti packaging (e.g. `lib` renamed in a distro build).
81
+ - Not really a bug, just a defensive change.
82
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, log diagnostic event khi cả 2 đều miss:
83
+ **Proposed fix:**
84
+ Keep the `exists()` check (safer), but add another fallback with `path.join(dirname, "register.mjs")` for different jiti versions, and log a diagnostic event when both miss:
85
85
 
86
86
  ```ts
87
87
  try {
@@ -96,41 +96,41 @@ try {
96
96
  return undefined;
97
97
  ```
98
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.
99
+ **Tests to add:** `test/unit/async-runner.test.ts`:
100
+ - Case where `lib/jiti-register.mjs` is missing but `register.mjs` exists → resolver returns it.
101
101
 
102
102
  ---
103
103
 
104
- ### A3 — Test alias `__test__renameWithRetry*` trở thành `const`, thể vô hiệu monkey-patch
104
+ ### A3 — Test alias `__test__renameWithRetry*` became a `const`, may disable monkey-patching
105
105
 
106
- **Severity:** Info | **Effort:** ~5 phút | **File:** `src/state/atomic-write.ts:73, 91`
106
+ **Severity:** Info | **Effort:** ~5 minutes | **File:** `src/state/atomic-write.ts:73, 91`
107
107
 
108
- **Hiện trạng:**
108
+ **Current state:**
109
109
  ```ts
110
110
  export const __test__renameWithRetry = renameWithRetry;
111
111
  ```
112
112
 
113
- **Vấn đề:**
114
- - Nếu test làm `import * as mod from "../atomic-write.ts"; mod.__test__renameWithRetry = mockFn;` → assignment fail (read-only) hoặc không effect trên `atomicWriteFile` ( 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.
113
+ **Problem:**
114
+ - If old tests do `import * as mod from "../atomic-write.ts"; mod.__test__renameWithRetry = mockFn;` → the assignment fails (read-only) or has no effect on `atomicWriteFile` (which calls the local `renameWithRetry`).
115
+ - Currently no test in the repo monkey-patches this field (`Grep` confirmed), so it's not a bug.
116
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 chỗ dùng alias.
117
+ **Proposed fix:**
118
+ - Keep the alias as-is (zero practical impact).
119
+ - Or remove both aliases to reduce the API surface: only export `renameWithRetry`, `renameWithRetryAsync`. Update tests if any use the alias.
120
120
 
121
- **Action:** chỉ làm khi cần dọn dẹp API surface, không khẩn cấp.
121
+ **Action:** only do this when cleaning up the API surface; not urgent.
122
122
 
123
123
  ---
124
124
 
125
- ## Phần B — Gaps mới phát hiện khi review lại
125
+ ## Part B — Newly discovered gaps from re-review
126
126
 
127
- ### B1 — `bash` hardcoded → broken trên Windows (root cause của 8 test fail)
127
+ ### B1 — `bash` hardcoded → broken on Windows (root cause of 8 test failures)
128
128
 
129
- **Severity:** Medium | **Effort:** ~45 phút | **Files:**
129
+ **Severity:** Medium | **Effort:** ~45 minutes | **Files:**
130
130
  - `src/runtime/post-checks.ts:82`
131
131
  - `src/runtime/iteration-hooks.ts:137`
132
132
 
133
- **Hiện trạng:**
133
+ **Current state:**
134
134
  ```ts
135
135
  // post-checks.ts:82
136
136
  const output = execFileSync("bash", [scriptPath], { ... });
@@ -139,13 +139,13 @@ const output = execFileSync("bash", [scriptPath], { ... });
139
139
  const child = spawn("bash", [hookScriptPath], { ... });
140
140
  ```
141
141
 
142
- **Vấn đề:**
143
- - Trên Windows, `bash` thường không 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.
142
+ **Problem:**
143
+ - On Windows, `bash` is usually not on the PATH (unless Git Bash/WSL is installed). All 8 currently failing tests are due to this.
144
+ - Hot code path (post-task check, iteration hook) → Windows users cannot use this feature.
145
+ - The comment in the file says "Spawns `bash <script>`" → docs need updating too.
146
146
 
147
- **Fix đề xuất:**
148
- 1. Tìm bash thông minh:
147
+ **Proposed fix:**
148
+ 1. Resolve bash intelligently:
149
149
  ```ts
150
150
  function resolveBashCmd(): string {
151
151
  if (process.platform !== "win32") return "bash";
@@ -160,26 +160,26 @@ function resolveBashCmd(): string {
160
160
  return "bash"; // last resort — let spawn fail with clearer error
161
161
  }
162
162
  ```
163
- 2. Thay 2 chỗ `"bash"` bằng `resolveBashCmd()`.
164
- 3. Trên Windows, nếu script `.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")`.
163
+ 2. Replace both `"bash"` occurrences with `resolveBashCmd()`.
164
+ 3. On Windows, if the script is `.ps1` → use `powershell -File`; if `.cmd/.bat` → spawn directly.
165
+ 4. Or simpler: skip the test if `!isScriptRunnable("bash")`.
166
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 bash, hoặc tạo `.ps1` variant.
170
- - Mock test cho `resolveBashCmd()` với platform stub.
167
+ **Tests to add:**
168
+ - On Linux CI: current tests pass.
169
+ - On Windows: skip if bash is unavailable, or create a `.ps1` variant.
170
+ - Mock test for `resolveBashCmd()` with a platform stub.
171
171
 
172
172
  ---
173
173
 
174
- ### B2 — Thiếu test trực tiếp cho `worktree-manager.ts` (covers BUG-005, BUG-006)
174
+ ### B2 — Missing direct tests for `worktree-manager.ts` (covers BUG-005, BUG-006)
175
175
 
176
- **Severity:** Medium | **Effort:** ~1h | **File:** thiếu `test/unit/worktree-manager.test.ts`
176
+ **Severity:** Medium | **Effort:** ~1h | **File:** missing `test/unit/worktree-manager.test.ts`
177
177
 
178
- **Vấn đề:**
179
- - BUG-005 fix (`branchExists` + `pruneStaleWorktrees`) BUG-006 fix (`isDirectory()` check) **không test trực tiếp**.
180
- - Code review đã flag điều này nhưng commit fix chỉ thêm test cho schema.
178
+ **Problem:**
179
+ - The BUG-005 fix (`branchExists` + `pruneStaleWorktrees`) and BUG-006 fix (`isDirectory()` check) have **no direct tests**.
180
+ - The code review flagged this, but the fix commit only added tests for the schema.
181
181
 
182
- **Fix đề xuất:** tạo `test/unit/worktree-manager.test.ts` với (sử dụng `tmpdir` + real git):
182
+ **Proposed fix:** create `test/unit/worktree-manager.test.ts` with (using `tmpdir` + real git):
183
183
 
184
184
  ```ts
185
185
  import { test } from "node:test";
@@ -214,15 +214,15 @@ test("linkNodeModulesIfPresent rejects when node_modules is a file", () => {
214
214
 
215
215
  ---
216
216
 
217
- ### B3 — `artifact-store.ts` thiếu test cho hash/size integrity (covers BUG-002)
217
+ ### B3 — `artifact-store.ts` missing tests for hash/size integrity (covers BUG-002)
218
218
 
219
- **Severity:** Medium | **Effort:** ~20 phút | **File:** thiếu `test/unit/artifact-store.test.ts`
219
+ **Severity:** Medium | **Effort:** ~20 minutes | **File:** missing `test/unit/artifact-store.test.ts`
220
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 test verify điều này. `api-artifact-security.test.ts` nhưng không assert hash.
221
+ **Problem:**
222
+ - The BUG-002 fix changed the hash from `options.content` to `redactSecretString(options.content)` ensuring `contentHash` matches the bytes on disk.
223
+ - There is no test verifying this. There's `api-artifact-security.test.ts` but it doesn't assert the hash.
224
224
 
225
- **Fix đề xuất:** tạo `test/unit/artifact-store.test.ts`:
225
+ **Proposed fix:** create `test/unit/artifact-store.test.ts`:
226
226
  ```ts
227
227
  import { test } from "node:test";
228
228
  import assert from "node:assert/strict";
@@ -256,14 +256,14 @@ test("writeArtifact: rejects path traversal", () => {
256
256
 
257
257
  ---
258
258
 
259
- ### B4 — Thiếu test parity sync vs async cho lock retry (covers BUG-004)
259
+ ### B4 — Missing sync vs async parity test for lock retry (covers BUG-004)
260
260
 
261
- **Severity:** Low | **Effort:** ~30 phút | **File:** mở rộng `test/unit/locks-race.test.ts`
261
+ **Severity:** Low | **Effort:** ~30 minutes | **File:** extend `test/unit/locks-race.test.ts`
262
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 bộ; không case `rmSync` race + assert lock được acquire sau retry trên cả sync async.
263
+ **Problem:**
264
+ - The BUG-004 fix synchronized sync ↔ async behavior (both retry up to the deadline). `locks-race.test.ts` only tests superficially; there's no case for `rmSync` race + asserting the lock is acquired after retry for both sync and async.
265
265
 
266
- **Fix đề xuất:** thêm 2 test:
266
+ **Proposed fix:** add 2 tests:
267
267
  ```ts
268
268
  test("withRunLockSync retries when rmSync fails once on stale lock", async () => {
269
269
  // Create stale lock, monkey-patch fs.rmSync first call → throw EBUSY
@@ -277,11 +277,11 @@ test("withRunLock (async) and withRunLockSync exhibit identical stale-lock recov
277
277
 
278
278
  ---
279
279
 
280
- ### B5 — `runSetupHook` không filter dangerous env vars khi spawn hook
280
+ ### B5 — `runSetupHook` doesn't filter dangerous env vars when spawning the hook
281
281
 
282
- **Severity:** Low (defense-in-depth) | **Effort:** ~15 phút | **File:** `src/worktree/worktree-manager.ts:67-88`
282
+ **Severity:** Low (defense-in-depth) | **Effort:** ~15 minutes | **File:** `src/worktree/worktree-manager.ts:67-88`
283
283
 
284
- **Hiện trạng:**
284
+ **Current state:**
285
285
  ```ts
286
286
  const result = spawnSync(nodeHook ? process.execPath : hookPath, ..., {
287
287
  cwd: worktreePath,
@@ -289,17 +289,17 @@ const result = spawnSync(nodeHook ? process.execPath : hookPath, ..., {
289
289
  input: JSON.stringify({...}),
290
290
  timeout: cfg.setupHookTimeoutMs ?? 30_000,
291
291
  shell: false,
292
- // ← KHÔNG truyền env → spawn dùng process.env (full inherit)
292
+ // ← Does NOT pass env → spawn uses process.env (full inherit)
293
293
  });
294
294
  ```
295
295
 
296
- **Vấn đề:**
297
- - Hook chạy với `process.env` đầy đủ thể leak `API_KEY`, `*_TOKEN`, `OPENAI_KEY`, etc. Đối lập với `post-checks.ts` `iteration-hooks.ts` (đã restrict env).
296
+ **Problem:**
297
+ - The hook runs with the full `process.env` → may leak `API_KEY`, `*_TOKEN`, `OPENAI_KEY`, etc. In contrast, `post-checks.ts` and `iteration-hooks.ts` already restrict env.
298
298
  - AGENTS.md security baseline: "env-secret filtering before spawn".
299
299
 
300
- **Fix đề xuất:**
300
+ **Proposed fix:**
301
301
  ```ts
302
- import { sanitizeEnvSecrets } from "../utils/redaction.ts"; // hoặc tương đương
302
+ import { sanitizeEnvSecrets } from "../utils/redaction.ts"; // or equivalent
303
303
 
304
304
  const result = spawnSync(..., {
305
305
  cwd: worktreePath,
@@ -311,15 +311,15 @@ const result = spawnSync(..., {
311
311
  });
312
312
  ```
313
313
 
314
- (Tận dụng helper đã có trong `child-pi.ts` — refactor thành util chung.)
314
+ (Leverage the existing helper in `child-pi.ts` — refactor into a shared util.)
315
315
 
316
316
  ---
317
317
 
318
- ### B6 — `prepareTaskWorkspace` không assertion về branch sanity sau `branchExists`
318
+ ### B6 — `prepareTaskWorkspace` has no assertion about branch sanity after `branchExists`
319
319
 
320
- **Severity:** Info | **Effort:** ~10 phút | **File:** `src/worktree/worktree-manager.ts:127-133`
320
+ **Severity:** Info | **Effort:** ~10 minutes | **File:** `src/worktree/worktree-manager.ts:127-133`
321
321
 
322
- **Hiện trạng:**
322
+ **Current state:**
323
323
  ```ts
324
324
  pruneStaleWorktrees(repoRoot);
325
325
  if (branchExists(repoRoot, branch)) {
@@ -327,10 +327,10 @@ if (branchExists(repoRoot, branch)) {
327
327
  }
328
328
  ```
329
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 emit hint hơn cho user.
330
+ **Problem:**
331
+ - If the branch is currently **checked out** in another worktree (not yet pruned), `git worktree add <path> <branch>` will fail. We should catch this and emit a clearer hint to the user.
332
332
 
333
- **Fix đề xuất:** wrap với try/catch, parse error message, throw error actionable message:
333
+ **Proposed fix:** wrap with try/catch, parse the error message, throw an error with an actionable message:
334
334
  ```ts
335
335
  try {
336
336
  git(repoRoot, ["worktree", "add", worktreePath, branch]);
@@ -345,11 +345,11 @@ try {
345
345
 
346
346
  ---
347
347
 
348
- ### B7 — `team-tool.ts` 1 lazy-import `handleRun` không theo style `// LAZY: <reason>`
348
+ ### B7 — `team-tool.ts` has 1 lazy-import `handleRun` not following the `// LAZY: <reason>` style
349
349
 
350
- **Severity:** Info | **Effort:** ~2 phút | **File:** `src/extension/team-tool.ts:56-62`
350
+ **Severity:** Info | **Effort:** ~2 minutes | **File:** `src/extension/team-tool.ts:56-62`
351
351
 
352
- **Hiện trạng:**
352
+ **Current state:**
353
353
  ```ts
354
354
  // Lazy-loaded: run.ts pulls in spawnBackgroundTeamRun, resolveCrewRuntime, etc.
355
355
  // Static import fails silently in some jiti contexts (child-process), leaving handleRun undefined.
@@ -357,13 +357,13 @@ import type { handleRun as HandleRunFn } from "./team-tool/run.ts";
357
357
  let _cachedHandleRun: typeof HandleRunFn | undefined;
358
358
  async function handleRun(...args: Parameters<typeof HandleRunFn>): Promise<...> {
359
359
  if (!_cachedHandleRun) {
360
- const mod = await import("./team-tool/run.ts"); // ← thiếu // LAZY: marker
360
+ const mod = await import("./team-tool/run.ts"); // ← missing // LAZY: marker
361
361
  ...
362
362
  }
363
363
  }
364
364
  ```
365
365
 
366
- **Fix đề xuất:** thêm marker để đồng nhất với 11 chỗ còn lại đã được đánh dấu:
366
+ **Proposed fix:** add the marker to align with the other 11 already-marked sites:
367
367
  ```ts
368
368
  async function handleRun(...) {
369
369
  if (!_cachedHandleRun) {
@@ -376,42 +376,42 @@ async function handleRun(...) {
376
376
 
377
377
  ---
378
378
 
379
- ### B8 — `redaction-transcript-roundtrip.test.ts` chưa tồn tại (NIT-004)
379
+ ### B8 — `redaction-transcript-roundtrip.test.ts` doesn't exist yet (NIT-004)
380
380
 
381
- **Severity:** Low | **Effort:** ~30 phút | **File:** thiếu test mới
381
+ **Severity:** Low | **Effort:** ~30 minutes | **File:** missing new test
382
382
 
383
- **Vấn đề:**
384
- - Code review NIT-004 đề xuất test verify rằng transcript trên đĩa artifact result đều không chứa secret raw. Hiện chưa test này.
383
+ **Problem:**
384
+ - Code review NIT-004 suggested a test verifying that the transcript on disk and the artifact result both contain no raw secrets. This test doesn't exist yet.
385
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 secret raw.
390
- 4. Call recovery path → assert artifact result cũng đã redact.
386
+ **Proposed fix:** create `test/unit/redaction-transcript-roundtrip.test.ts`:
387
+ 1. Create a fake transcript JSONL with a line containing `OPENAI_API_KEY=sk-abc...`.
388
+ 2. Call `appendTranscript` (via the `child-pi` helper export).
389
+ 3. Read the file → assert no raw secret.
390
+ 4. Call the recovery path → assert the artifact result is also redacted.
391
391
 
392
392
  ---
393
393
 
394
- ### B9 — CI grep-check chặn `await import(...)` không marker
394
+ ### B9 — CI grep-check to block `await import(...)` without a marker
395
395
 
396
- **Severity:** Low | **Effort:** ~15 phút | **File:** thêm script + GitHub Actions
396
+ **Severity:** Low | **Effort:** ~15 minutes | **File:** add script + GitHub Actions
397
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.
398
+ **Problem:**
399
+ - Code review BUG-003 Option A proposed "add a grep-check in CI to block unmarked dynamic imports". Not done yet.
400
400
 
401
- **Fix đề xuất:** tạo `scripts/check-lazy-imports.mjs`:
401
+ **Proposed fix:** create `scripts/check-lazy-imports.mjs`:
402
402
  ```js
403
403
  import { execSync } from "node:child_process";
404
404
  const out = execSync(
405
405
  `git grep -nE 'await import\\(' -- 'src/**/*.ts' | grep -v '// LAZY:'`,
406
406
  { encoding: "utf-8" }
407
407
  ).split("\n").filter((l) => l && !l.includes("// LAZY:"));
408
- // Hoặc kiểm tra dòng ngay trước chứa `// LAZY:`
408
+ // Or check that the preceding line contains `// LAZY:`
409
409
  if (out.length > 0) {
410
410
  console.error("Dynamic imports without `// LAZY:` marker:\n" + out.join("\n"));
411
411
  process.exit(1);
412
412
  }
413
413
  ```
414
- Thêm vào `package.json`:
414
+ Add to `package.json`:
415
415
  ```json
416
416
  "scripts": {
417
417
  "check:lazy-imports": "node scripts/check-lazy-imports.mjs",
@@ -421,43 +421,43 @@ Thêm vào `package.json`:
421
421
 
422
422
  ---
423
423
 
424
- ## Ưu tiên thực hiện
424
+ ## Implementation priority
425
425
 
426
- | # | Item | Severity | Effort | Khuyến nghị |
426
+ | # | Item | Severity | Effort | Recommendation |
427
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.
428
+ | 1 | B1 (bash portability) | Medium | 45 minutes | Current sprint — fix 8 failing tests on Windows |
429
+ | 2 | B2 (worktree test) | Medium | 1h | Current sprint — regression guard for BUG-005/006 |
430
+ | 3 | B3 (artifact-store test) | Medium | 20 minutes | Current sprint — verify BUG-002 fix |
431
+ | 4 | A1 (branchExists remote-tracking) | Low | 20 minutes | Next sprint |
432
+ | 5 | B5 (setup-hook env filter) | Low | 15 minutes | Next sprint — defense-in-depth |
433
+ | 6 | B6 (worktree checked-out hint) | Info | 10 minutes | Next sprint — UX |
434
+ | 7 | B7 (LAZY marker consistency) | Info | 2 minutes | Anytime |
435
+ | 8 | B4 (lock parity test) | Low | 30 minutes | Next sprint |
436
+ | 9 | B8 (redaction roundtrip test) | Low | 30 minutes | Next sprint |
437
+ | 10 | B9 (CI grep-check) | Low | 15 minutes | Next sprint |
438
+ | 11 | A2 (async-runner fallback robust) | Low | 10 minutes | Not urgent |
439
+ | 12 | A3 (test alias cleanup) | Info | 5 minutes | Optional |
440
+
441
+ **Total effort priority 1 (medium):** ~2h 5 minutes.
442
+ **Total effort priority 2 (low):** ~1h 50 minutes.
443
443
 
444
444
  ---
445
445
 
446
- ## Đề xuất commit batches
446
+ ## Proposed commit batches
447
447
 
448
448
  - **Batch 1 (must-fix):** B1 + B2 + B3 → 1 PR "test+portability hardening" (~2h).
449
449
  - **Batch 2 (nice-to-have):** A1 + B5 + B6 + B7 + B9 → 1 PR "worktree + lazy-import polish" (~1h).
450
450
  - **Batch 3 (test debt):** B4 + B8 → 1 PR "additional regression tests" (~1h).
451
- - **Defer:** A2, A3 cho đến khi user-visible issue.
451
+ - **Defer:** A2, A3 until there's a user-visible issue.
452
452
 
453
453
  ---
454
454
 
455
- ## Điểm tích cực sau review lần 2
455
+ ## Positive notes after round 2 review
456
456
 
457
- - Tất cả 7 BUG + 4 NIT từ code review trước đã được fix với commit ràng, 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.
457
+ - All 7 BUGs + 4 NITs from the prior code review have been fixed with clear commits, with tests for the schema.
458
+ - The `// LAZY:` comment has been consistently added at 11/12 dynamic import sites.
459
+ - Lock retry logic is now unified sync ↔ async (both have deadline + retry).
460
+ - Worktree handle crash resume works correctly (prune + branchExists fallback).
461
+ - Artifact `contentHash` is now verifiable via `sha256(fs.readFileSync(desc.path))`.
462
+ - No more `any` types in `src/` (grep confirmed).
463
+ - `node_modules/jiti` resolution is robust across any monorepo layout.