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,30 +1,30 @@
1
1
  # Code Review Findings — pi-crew (2026-05-11)
2
2
 
3
3
  Reviewer: Droid (Factory)
4
- Scope: toàn bộ `pi-crew/` (src + schema + worktree + state + extension), read-only.
5
- Phương pháp: đối chiếu code với `AGENTS.md` (project + workspace), kiểm tra security/concurrency/cleanup theo OWASP + best practices.
4
+ Scope: the entire `pi-crew/` directory (src + schema + worktree + state + extension), read-only.
5
+ Method: cross-referenced code against `AGENTS.md` (project + workspace), reviewed security/concurrency/cleanup per OWASP + best practices.
6
6
 
7
7
  ---
8
8
 
9
- ## Tóm tắt mức độ
9
+ ## Severity Summary
10
10
 
11
- | ID | Severity | Khu vực | Tiêu đề |
11
+ | ID | Severity | Area | Title |
12
12
  |---|---|---|---|
13
- | BUG-001 | **High** | Schema / Tool dispatch | `action: "retry"` bị schema từ chối nhưng handler |
14
- | BUG-002 | **High** | Artifact integrity | `contentHash` không khớp với bytes đã ghi xuống đĩa |
15
- | BUG-003 | Medium | AGENTS.md compliance | 12 vị trí `await import(...)` vi phạm rule "no dynamic inline imports" |
16
- | BUG-004 | Medium | Concurrency | `withRunLockSync` `withRunLock` xử stale-lock khác nhau |
17
- | BUG-005 | Medium | Worktree lifecycle | `git worktree add -b <branch>` fail khi branch đã tồn tại từ run |
18
- | BUG-006 | Low/Med | Worktree | `linkNodeModulesIfPresent` không kiểm tra source directory |
19
- | BUG-007 | Low | Worktree setup hook | Hook lỗi/non-JSON bị nuốt hoàn toàn, không log |
20
- | NIT-001 | Low | API hygiene | `__test__renameWithRetry` được gọi từ production path |
21
- | NIT-002 | Low | Code style | Empty-string argv flag trong `git worktree remove` |
22
- | NIT-003 | Low | Immutability | `executedConfig.runtime` bị mutate khi resume |
23
- | NIT-004 | Low | Redaction | Cần verify transcript trên đĩa luôn được redact |
13
+ | BUG-001 | **High** | Schema / Tool dispatch | `action: "retry"` rejected by schema but has a handler |
14
+ | BUG-002 | **High** | Artifact integrity | `contentHash` does not match the bytes written to disk |
15
+ | BUG-003 | Medium | AGENTS.md compliance | 12 `await import(...)` sites violate the "no dynamic inline imports" rule |
16
+ | BUG-004 | Medium | Concurrency | `withRunLockSync` and `withRunLock` handle stale locks differently |
17
+ | BUG-005 | Medium | Worktree lifecycle | `git worktree add -b <branch>` fails when the branch already exists from a previous run |
18
+ | BUG-006 | Low/Med | Worktree | `linkNodeModulesIfPresent` does not verify the source is a directory |
19
+ | BUG-007 | Low | Worktree setup hook | Errored / non-JSON hook output is swallowed entirely with no log |
20
+ | NIT-001 | Low | API hygiene | `__test__renameWithRetry` is called from a production code path |
21
+ | NIT-002 | Low | Code style | Empty-string argv flag in `git worktree remove` |
22
+ | NIT-003 | Low | Immutability | `executedConfig.runtime` is mutated on resume |
23
+ | NIT-004 | Low | Redaction | Need to verify the transcript on disk is always redacted |
24
24
 
25
25
  ---
26
26
 
27
- ## BUG-001 — `action: "retry"` bị schema từ chối nhưng handler
27
+ ## BUG-001 — `action: "retry"` rejected by schema but has a handler
28
28
 
29
29
  **Severity:** High
30
30
  **Files:**
@@ -33,9 +33,9 @@ Phương pháp: đối chiếu code với `AGENTS.md` (project + workspace), ki
33
33
  - `src/extension/team-tool.ts:264` (dispatch)
34
34
  - `src/extension/team-tool/cancel.ts` (`handleRetry`)
35
35
 
36
- ### Mô tả
36
+ ### Description
37
37
 
38
- TypeBox schema `TeamToolParams` định nghĩa `action` một `Type.Union` của các `Type.Literal`. Danh sách literal **không có** `"retry"`:
38
+ The TypeBox schema `TeamToolParams` defines `action` as a `Type.Union` of `Type.Literal` values. The literal list **does not include** `"retry"`:
39
39
 
40
40
  ```ts
41
41
  // src/schema/team-tool-schema.ts:18-49
@@ -47,43 +47,43 @@ action: Type.Optional(Type.Union([
47
47
  Type.Literal("list"),
48
48
  Type.Literal("get"),
49
49
  Type.Literal("cancel"),
50
- // ... KHÔNG Type.Literal("retry") ở đây
50
+ // ... there is NO Type.Literal("retry") here
51
51
  Type.Literal("resume"),
52
52
  Type.Literal("respond"),
53
53
  ...
54
54
  ])),
55
55
  ```
56
56
 
57
- Nhưng TypeScript interface lại **có** `"retry"`:
57
+ But the TypeScript interface **does include** `"retry"`:
58
58
 
59
59
  ```ts
60
60
  // src/schema/team-tool-schema.ts:95
61
61
  action?: "run" | "parallel" | "plan" | "status" | "list" | "get" | "cancel" | "retry" | "resume" | ...;
62
62
  ```
63
63
 
64
- `handleTeamTool` dispatch nó:
64
+ And `handleTeamTool` dispatches it:
65
65
 
66
66
  ```ts
67
67
  // src/extension/team-tool.ts:264
68
68
  case "retry": return handleRetry(params, ctx);
69
69
  ```
70
70
 
71
- ### Hậu quả
71
+ ### Consequences
72
72
 
73
- - Khi pi-coding-agent validate tool params bằng TypeBox schema (cách thông thường để gate input từ LLM), call `team {action: "retry"}` bị **reject ngay tại validation layer**, không bao giờ chạm tới `handleRetry`.
74
- - TS interface vs TypeBox schema lệch nhau, code path `handleRetry` **dead code** từ góc nhìn tool runtime.
73
+ - When pi-coding-agent validates tool params via the TypeBox schema (the usual way to gate input from the LLM), a call like `team {action: "retry"}` is **rejected at the validation layer** and never reaches `handleRetry`.
74
+ - The TS interface and TypeBox schema are out of sync; from the tool runtime's perspective, the `handleRetry` code path is **dead code**.
75
75
 
76
- ### Cách reproduce
76
+ ### How to reproduce
77
77
 
78
78
  ```bash
79
- # Từ pi REPL hoặc qua tool API:
79
+ # From the pi REPL or via the tool API:
80
80
  team(action="retry", runId="<id>")
81
81
  # → schema validation error "must be equal to one of the allowed values"
82
82
  ```
83
83
 
84
- ### Fix đề xuất
84
+ ### Suggested fix
85
85
 
86
- Thêm literal vào union đồng bộ test:
86
+ Add the literal to the union and sync the tests:
87
87
 
88
88
  ```ts
89
89
  // src/schema/team-tool-schema.ts
@@ -91,13 +91,13 @@ action: Type.Optional(Type.Union([
91
91
  Type.Literal("run"),
92
92
  ...
93
93
  Type.Literal("cancel"),
94
- Type.Literal("retry"), // ← thêm dòng này
94
+ Type.Literal("retry"), // ← add this line
95
95
  Type.Literal("resume"),
96
96
  ...
97
97
  ])),
98
98
  ```
99
99
 
100
- thêm test trong `test/unit/team-tool-schema.test.ts`:
100
+ And add a test in `test/unit/team-tool-schema.test.ts`:
101
101
 
102
102
  ```ts
103
103
  test("schema accepts action: retry", () => {
@@ -108,12 +108,12 @@ test("schema accepts action: retry", () => {
108
108
 
109
109
  ---
110
110
 
111
- ## BUG-002 — `writeArtifact` ghi nội dung đã redact nhưng hash bytes gốc
111
+ ## BUG-002 — `writeArtifact` writes redacted content but hashes the original bytes
112
112
 
113
113
  **Severity:** High
114
114
  **File:** `src/state/artifact-store.ts:106-129`
115
115
 
116
- ### Mô tả
116
+ ### Description
117
117
 
118
118
  ```ts
119
119
  // src/state/artifact-store.ts:117-121
@@ -126,43 +126,43 @@ return {
126
126
  kind: options.kind,
127
127
  path: filePath,
128
128
  ...
129
- sizeBytes: stats.size, // ← size của bytes đã redact
130
- contentHash, // ← hash của bytes gốc, chưa redact
129
+ sizeBytes: stats.size, // ← size of the redacted bytes
130
+ contentHash, // ← hash of the original, pre-redaction bytes
131
131
  ...
132
132
  };
133
133
  ```
134
134
 
135
- `contentHash` được compute trên `options.content` (chưa redact) trong khi file trên đĩa `redactSecretString(options.content)`. `sizeBytes` được lấy từ `fs.statSync(filePath)` → size của bytes đã redact.
135
+ `contentHash` is computed on `options.content` (pre-redaction) while the file on disk is `redactSecretString(options.content)`. `sizeBytes` is taken from `fs.statSync(filePath)` → it is the size of the redacted bytes.
136
136
 
137
- ### Hậu quả
137
+ ### Consequences
138
138
 
139
- - Bất kỳ consumer nào "verify integrity" bằng cách re-hash file path sẽ luôn nhận digest **khác** với `contentHash` mỗi khi nội dung gốc chứa secret pattern.
140
- - `sizeBytes` `contentHash` không nhất quán với nhau (size post-redaction, hash pre-redaction).
141
- - Comment "Compute hash on original content for integrity verification" nói **lý do** nhưng hợp đồng vẫn sai: integrity check đối chiếu hash với file trên đĩa, không phải với memory.
139
+ - Any consumer that "verifies integrity" by re-hashing the file path will always get a digest **different** from `contentHash` whenever the original content contains a secret pattern.
140
+ - `sizeBytes` and `contentHash` are inconsistent with each other (size is post-redaction, hash is pre-redaction).
141
+ - The comment "Compute hash on original content for integrity verification" states the **rationale**, but the contract is still wrong: an integrity check compares the hash against the file on disk, not against an in-memory value.
142
142
 
143
- ### Hai phương án sửa
143
+ ### Two ways to fix
144
144
 
145
- **Option A — Hash post-redaction (khuyến nghị):**
145
+ **Option A — Hash post-redaction (recommended):**
146
146
  ```ts
147
147
  const content = redactSecretString(options.content);
148
148
  atomicWriteFile(filePath, content);
149
149
  const contentHash = hashContent(content);
150
150
  const stats = fs.statSync(filePath);
151
151
  ```
152
- Đảm bảo `contentHash === sha256(fs.readFileSync(filePath))`. Mất khả năng "trace back to pre-redaction source" — nhưng đó behavior an toàn cho artifact-store.
152
+ Guarantees `contentHash === sha256(fs.readFileSync(filePath))`. You lose the ability to "trace back to the pre-redaction source" — but that is the safe behavior for the artifact store.
153
153
 
154
- **Option B — Lưu cả hai field nếu cần:**
154
+ **Option B — Store both fields if needed:**
155
155
  ```ts
156
156
  return {
157
157
  ...,
158
158
  contentHash, // pre-redaction (source-of-truth)
159
- storedContentHash: hashContent(content), // post-redaction (đúng với file)
159
+ storedContentHash: hashContent(content), // post-redaction (matches the file)
160
160
  sizeBytes: stats.size,
161
161
  };
162
162
  ```
163
- Sau đó update `ArtifactDescriptor` trong `src/state/types.ts:8-16` mọi consumer.
163
+ Then update `ArtifactDescriptor` in `src/state/types.ts:8-16` and every consumer.
164
164
 
165
- ### Cần thêm test
165
+ ### Test to add
166
166
 
167
167
  ```ts
168
168
  test("writeArtifact: contentHash matches bytes on disk", () => {
@@ -179,14 +179,14 @@ test("writeArtifact: contentHash matches bytes on disk", () => {
179
179
 
180
180
  ---
181
181
 
182
- ## BUG-003 — 12 vị trí `await import(...)` vi phạm rule "Avoid dynamic inline imports"
182
+ ## BUG-003 — 12 `await import(...)` sites violate the "Avoid dynamic inline imports" rule
183
183
 
184
- **Severity:** Medium (rule violation, không phải runtime bug)
185
- **Rule nguồn:** `pi-crew/AGENTS.md` — "Avoid dynamic inline imports."
184
+ **Severity:** Medium (rule violation, not a runtime bug)
185
+ **Source rule:** `pi-crew/AGENTS.md` — "Avoid dynamic inline imports."
186
186
 
187
- ### Danh sách vi phạm
187
+ ### List of violations
188
188
 
189
- | File | Line | Module được import lazy |
189
+ | File | Line | Module lazily imported |
190
190
  |---|---|---|
191
191
  | `src/extension/team-tool.ts` | 35 | `../runtime/team-runner.ts` |
192
192
  | `src/extension/team-tool/run.ts` | 18 | `../../runtime/team-runner.ts` |
@@ -201,35 +201,35 @@ test("writeArtifact: contentHash matches bytes on disk", () => {
201
201
  | `src/runtime/yield-handler.ts` | 9 | `ajv` |
202
202
  | `src/ui/run-action-dispatcher.ts` | 8 | `../extension/team-tool.ts` |
203
203
 
204
- ### Phân tích
204
+ ### Analysis
205
205
 
206
- Một số comment giải thích do (extension/team-tool.ts:33-34):
206
+ Some have a comment explaining the reason (extension/team-tool.ts:33-34):
207
207
  > Heavy runtime — lazy-loaded to avoid 1.4s import cost at extension registration. executeTeamRun is only called when a team run actually executes.
208
208
 
209
- Đây tối ưu hợp lệ. Nhưng AGENTS.md đang nói absolute "avoid", không exception. Hai cách giải quyết:
209
+ This is a legitimate optimization. But AGENTS.md states an absolute "avoid" with no exceptions. Two ways to resolve:
210
210
 
211
- **Option A — Update AGENTS.md để hợp pháp hoá lazy boundary:**
211
+ **Option A — Update AGENTS.md to legitimize the lazy boundary:**
212
212
  ```md
213
213
  - Avoid dynamic inline imports, EXCEPT at documented lazy-load boundaries
214
214
  to defer heavy runtime cost (mark with `// LAZY: <reason>`).
215
215
  ```
216
216
 
217
- **Option B — Refactor về top-level imports:**
218
- - Move heavy modules vào separate package hoặc dùng `import type` cho type-only, runtime import vào top.
219
- - thể vẫn giữ lazy cho `runtime-resolver.ts:40` (`@mariozechner/pi-coding-agent`) peer dependency optional.
217
+ **Option B — Refactor to top-level imports:**
218
+ - Move heavy modules into a separate package, or use `import type` for type-only and a top-level runtime import.
219
+ - You could keep the lazy import for `runtime-resolver.ts:40` (`@mariozechner/pi-coding-agent`) because it is an optional peer dependency.
220
220
 
221
221
  ### Recommendation
222
222
 
223
- Chọn **Option A**, thêm comment marker `// LAZY: <reason>` cho mỗi site thêm grep-check trong CI để chặn dynamic import không marker.
223
+ Choose **Option A**, add a `// LAZY: <reason>` marker comment to each site, and add a grep check in CI to block unmarked dynamic imports.
224
224
 
225
225
  ---
226
226
 
227
- ## BUG-004 — `withRunLockSync` `withRunLock` xử stale-lock khác nhau
227
+ ## BUG-004 — `withRunLockSync` and `withRunLock` handle stale locks differently
228
228
 
229
229
  **Severity:** Medium
230
230
  **File:** `src/state/locks.ts:50-91`
231
231
 
232
- ### Mô tả
232
+ ### Description
233
233
 
234
234
  **Sync path** (`acquireLockWithRetry` → `readLockState`):
235
235
  ```ts
@@ -238,9 +238,9 @@ function readLockState(filePath: string, staleMs: number): boolean {
238
238
  if (!isLockStale(filePath, staleMs)) return false;
239
239
  try {
240
240
  fs.rmSync(filePath, { force: true });
241
- return true; // ← chỉ true khi rmSync thành công
241
+ return true; // ← only true when rmSync succeeds
242
242
  } catch {
243
- return false; // ← throw sẽ xảy ra caller
243
+ return false; // ← a throw will happen at the caller
244
244
  }
245
245
  }
246
246
 
@@ -271,23 +271,23 @@ async function acquireLockWithRetryAsync(...) {
271
271
  if (Date.now() > deadline) {
272
272
  throw new Error(`Run '...' is locked by another operation.`);
273
273
  }
274
- readLockStateAsync(filePath, staleMs); // ← không check return
274
+ readLockStateAsync(filePath, staleMs); // ← return value not checked
275
275
  await sleep(delay);
276
276
  attempt++;
277
- // ← luôn loop lại
277
+ // ← always loops again
278
278
  }
279
279
  ```
280
280
 
281
- ### Hậu quả
281
+ ### Consequences
282
282
 
283
- - Sync version: nếu `rmSync` fail (file đang lock bởi process khác trên Windows), throw **ngay lập tức** lần đầu tiên thấy stale lock, không retry.
284
- - Async version: luôn retry tới `deadline`.
283
+ - Sync version: if `rmSync` fails (file is locked by another process on Windows), it throws **immediately** the first time it sees a stale lock, with no retry.
284
+ - Async version: always retries until the `deadline`.
285
285
 
286
- Inconsistent behavior → cùng một stale-lock + transient `rmSync` race thể fail trong sync code path nhưng pass trong async path.
286
+ Inconsistent behavior → the same stale-lock + transient `rmSync` race can fail in the sync code path but pass in the async path.
287
287
 
288
- ### Fix đề xuất
288
+ ### Suggested fix
289
289
 
290
- Đồng bộ behavior: sync version cũng nên retry tới deadline:
290
+ Align the behavior: the sync version should also retry until the deadline:
291
291
 
292
292
  ```ts
293
293
  function acquireLockWithRetry(filePath: string, staleMs: number): void {
@@ -303,7 +303,7 @@ function acquireLockWithRetry(filePath: string, staleMs: number): void {
303
303
  if (Date.now() > deadline) {
304
304
  throw new Error(`Run '${path.basename(filePath)}' is locked by another operation.`);
305
305
  }
306
- // Try to clear stale, but don't bail on rmSync error — let loop retry
306
+ // Try to clear the stale lock, but don't bail on an rmSync error — let the loop retry
307
307
  try {
308
308
  if (isLockStale(filePath, staleMs)) fs.rmSync(filePath, { force: true });
309
309
  } catch { /* race — let loop retry */ }
@@ -314,18 +314,18 @@ function acquireLockWithRetry(filePath: string, staleMs: number): void {
314
314
  }
315
315
  ```
316
316
 
317
- ### Test cần thêm
317
+ ### Test to add
318
318
 
319
- Mở rộng `test/unit/locks-race.test.ts` với case: stale lock + `rmSync` race (mock fs.rmSync để throw lần đầu, pass lần thứ hai) → assert lock được acquire sau retry.
319
+ Expand `test/unit/locks-race.test.ts` with a case: stale lock + `rmSync` race (mock `fs.rmSync` to throw the first time and pass the second) → assert the lock is acquired after a retry.
320
320
 
321
321
  ---
322
322
 
323
- ## BUG-005 — `git worktree add -b <branch>` fail khi branch đã tồn tại từ run
323
+ ## BUG-005 — `git worktree add -b <branch>` fails when the branch already exists from a previous run
324
324
 
325
325
  **Severity:** Medium
326
326
  **File:** `src/worktree/worktree-manager.ts:100-114`
327
327
 
328
- ### Mô tả
328
+ ### Description
329
329
 
330
330
  ```ts
331
331
  // worktree-manager.ts:100-114
@@ -336,16 +336,16 @@ if (fs.existsSync(worktreePath)) {
336
336
  git(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
337
337
  ```
338
338
 
339
- Điều kiện reuse chỉ check `worktreePath` directory. Nhưng branch `pi-crew/<runId>/<taskId>` thể tồn tại trong git worktree directory đã bị xoá thủ công (hoặc `cleanupRunWorktrees` xoá directory nhưng git worktree metadata còn).
339
+ The reuse condition only checks the `worktreePath` directory. But the branch `pi-crew/<runId>/<taskId>` can exist in git while the worktree directory was deleted manually (or `cleanupRunWorktrees` deleted the directory while the git worktree metadata remained).
340
340
 
341
- ### Hậu quả
341
+ ### Consequences
342
342
 
343
- - Sau crash hoặc cleanup không hoàn chỉnh, retry/resume run sẽ fail với git error: `fatal: a branch named 'pi-crew/.../...' already exists`.
344
- - User bị stuck, phải manual `git branch -D`.
343
+ - After a crash or an incomplete cleanup, a retry/resume run fails with a git error: `fatal: a branch named 'pi-crew/.../...' already exists`.
344
+ - The user gets stuck and must run `git branch -D` manually.
345
345
 
346
- ### Fix đề xuất
346
+ ### Suggested fix
347
347
 
348
- Thêm branch existence check trước `add`:
348
+ Add a branch-existence check before `add`:
349
349
 
350
350
  ```ts
351
351
  function branchExists(repoRoot: string, branch: string): boolean {
@@ -365,27 +365,27 @@ function pruneStaleWorktrees(repoRoot: string): void {
365
365
  // In prepareTaskWorkspace, before `git worktree add`:
366
366
  pruneStaleWorktrees(repoRoot);
367
367
  if (branchExists(repoRoot, branch)) {
368
- // Option 1: reuse from existing branch
368
+ // Option 1: reuse the existing branch
369
369
  git(repoRoot, ["worktree", "add", worktreePath, branch]);
370
370
  } else {
371
371
  git(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
372
372
  }
373
373
  ```
374
374
 
375
- ### Test cần thêm
375
+ ### Test to add
376
376
 
377
- `test/unit/worktree-manager.test.ts` (chưa tồn tại):
378
- 1. Create worktree, manual delete directory (`rm -rf` không qua git), branch still exists.
379
- 2. Call `prepareTaskWorkspace` again → expect success, not fatal.
377
+ `test/unit/worktree-manager.test.ts` (does not yet exist):
378
+ 1. Create a worktree, manually delete the directory (`rm -rf` outside of git), branch still exists.
379
+ 2. Call `prepareTaskWorkspace` again → expect success, not a fatal error.
380
380
 
381
381
  ---
382
382
 
383
- ## BUG-006 — `linkNodeModulesIfPresent` không kiểm tra source directory
383
+ ## BUG-006 — `linkNodeModulesIfPresent` does not verify the source is a directory
384
384
 
385
385
  **Severity:** Low/Medium
386
386
  **File:** `src/worktree/worktree-manager.ts:43-53`
387
387
 
388
- ### Mô tả
388
+ ### Description
389
389
 
390
390
  ```ts
391
391
  function linkNodeModulesIfPresent(repoRoot: string, worktreePath: string): boolean {
@@ -401,10 +401,10 @@ function linkNodeModulesIfPresent(repoRoot: string, worktreePath: string): boole
401
401
  }
402
402
  ```
403
403
 
404
- - Nếu `repoRoot/node_modules` **file** (hiếm nhưng thể xảy ra với corrupt setup), `existsSync` vẫn true, symlink được tạo với type `"dir"/"junction"` → behavior không xác định, đặc biệt junction trên Windows yêu cầu directory.
405
- - Nếu source **symlink to dir**, thể link chain → khó debug.
404
+ - If `repoRoot/node_modules` is a **file** (rare but possible with a corrupt setup), `existsSync` is still true, and the symlink is created with type `"dir"/"junction"` → undefined behavior, especially since a junction on Windows requires a directory.
405
+ - If the source is a **symlink to a directory**, a link chain can result hard to debug.
406
406
 
407
- ### Fix đề xuất
407
+ ### Suggested fix
408
408
 
409
409
  ```ts
410
410
  function linkNodeModulesIfPresent(repoRoot: string, worktreePath: string): boolean {
@@ -423,16 +423,16 @@ function linkNodeModulesIfPresent(repoRoot: string, worktreePath: string): boole
423
423
  }
424
424
  ```
425
425
 
426
- Dùng `statSync` (theo symlink) thay `existsSync` để cũng bắt case "source dangling symlink".
426
+ Use `statSync` (follows symlinks) instead of `existsSync` to also catch the "source is a dangling symlink" case.
427
427
 
428
428
  ---
429
429
 
430
- ## BUG-007 — Setup hook lỗi/non-JSON bị nuốt hoàn toàn, không log
430
+ ## BUG-007 — Errored / non-JSON setup hook output is swallowed entirely with no log
431
431
 
432
432
  **Severity:** Low
433
433
  **File:** `src/worktree/worktree-manager.ts:75-89`
434
434
 
435
- ### Mô tả
435
+ ### Description
436
436
 
437
437
  ```ts
438
438
  try {
@@ -447,9 +447,9 @@ try {
447
447
  }
448
448
  ```
449
449
 
450
- Hook trả về JSON parse error → return `[]` silently. User không biết hook đang chạy không đúng cho tới khi worktree thiếu paths.
450
+ The hook returns a JSON parse error → returns `[]` silently. The user has no idea the hook is misbehaving until the worktree is missing paths.
451
451
 
452
- ### Fix đề xuất
452
+ ### Suggested fix
453
453
 
454
454
  ```ts
455
455
  } catch (error) {
@@ -459,11 +459,11 @@ Hook trả về JSON parse error → return `[]` silently. User không biết ho
459
459
  }
460
460
  ```
461
461
 
462
- Hoặc nếu hook output không trống nhưng JSON parse fail → emit event vào event log của run.
462
+ Alternatively, if the hook output is non-empty but JSON parsing fails → emit an event into the run's event log.
463
463
 
464
464
  ---
465
465
 
466
- ## NIT-001 — `__test__renameWithRetry` được gọi từ production path
466
+ ## NIT-001 — `__test__renameWithRetry` called from a production code path
467
467
 
468
468
  **File:** `src/state/atomic-write.ts:55-67, 99`
469
469
 
@@ -479,11 +479,11 @@ export function atomicWriteFile(filePath: string, content: string): void {
479
479
  }
480
480
  ```
481
481
 
482
- Convention: tên `__test__` ngụ ý "chỉ dùng cho test, không stable". Production sử dụng smell. Đổi tên thành `renameWithRetry` (public utility) re-export bản test với alias.
482
+ Convention: the `__test__` name implies "test-only, not stable." Using it in production is a code smell. Rename it to `renameWithRetry` (a public utility) and re-export the test version under an alias.
483
483
 
484
484
  ---
485
485
 
486
- ## NIT-002 — Empty-string argv flag trong `git worktree remove`
486
+ ## NIT-002 — Empty-string argv flag in `git worktree remove`
487
487
 
488
488
  **File:** `src/worktree/cleanup.ts:64`
489
489
 
@@ -491,7 +491,7 @@ Convention: tên `__test__` ngụ ý "chỉ dùng cho test, không stable". Prod
491
491
  git(manifest.cwd, ["worktree", "remove", options.force ? "--force" : "", worktreePath].filter(Boolean));
492
492
  ```
493
493
 
494
- Pattern `cond ? "--force" : ""` rồi `.filter(Boolean)` hoạt động nhưng dễ gãy. Tốt hơn:
494
+ The `cond ? "--force" : ""` then `.filter(Boolean)` pattern works but is fragile. Better:
495
495
 
496
496
  ```ts
497
497
  const args = ["worktree", "remove"];
@@ -502,7 +502,7 @@ git(manifest.cwd, args);
502
502
 
503
503
  ---
504
504
 
505
- ## NIT-003 — `executedConfig.runtime` bị mutate khi resume
505
+ ## NIT-003 — `executedConfig.runtime` mutated on resume
506
506
 
507
507
  **File:** `src/extension/team-tool.ts:184-190`
508
508
 
@@ -514,7 +514,7 @@ if (!executedConfig.runtime?.mode && resumeManifest.runtimeResolution?.safety ==
514
514
  }
515
515
  ```
516
516
 
517
- Code thể đang assume `effectiveRunConfig` trả về object mới. Cần verify document immutability, hoặc thay bằng explicit clone:
517
+ The code may be assuming `effectiveRunConfig` returns a fresh object. Verify and document immutability, or replace with an explicit clone:
518
518
 
519
519
  ```ts
520
520
  const executedConfig: PiTeamsConfig = {
@@ -524,9 +524,9 @@ const executedConfig: PiTeamsConfig = {
524
524
 
525
525
  ---
526
526
 
527
- ## NIT-004 — Verify transcript trên đĩa luôn được redact
527
+ ## NIT-004 — Verify the transcript on disk is always redacted
528
528
 
529
- **File:** `src/runtime/child-pi.ts:148-152`, đối chiếu với `recoverCheckpointedTasks` (`src/extension/team-tool.ts:155-156`)
529
+ **File:** `src/runtime/child-pi.ts:148-152`, cross-referenced with `recoverCheckpointedTasks` (`src/extension/team-tool.ts:155-156`)
530
530
 
531
531
  ```ts
532
532
  // child-pi.ts:148-152
@@ -537,7 +537,7 @@ function appendTranscript(input: ChildPiRunInput, line: string): void {
537
537
  }
538
538
  ```
539
539
 
540
- Transcript được redact qua `redactJsonLine` — good. Nhưng trong recovery path:
540
+ The transcript is redacted via `redactJsonLine` — good. But in the recovery path:
541
541
 
542
542
  ```ts
543
543
  // team-tool.ts:155-156
@@ -549,44 +549,44 @@ const resultArtifact = writeArtifact(manifest.artifactsRoot, {
549
549
  });
550
550
  ```
551
551
 
552
- `writeArtifact` lại redact thêm lần nữa (đã verify BUG-002), double-redaction idempotent (`***` không match secret pattern). OK.
552
+ Because `writeArtifact` redacts again (verified in BUG-002), double-redaction is idempotent (`***` does not match the secret pattern). OK.
553
553
 
554
- **Action:** thêm test `test/unit/redaction-transcript-roundtrip.test.ts`:
555
- 1. Spawn mock child producing JSON line với secret.
556
- 2. Read transcript file → assert không secret raw.
557
- 3. Run `recoverCheckpointedTasks` → assert result artifact cũng không secret.
554
+ **Action:** add a test `test/unit/redaction-transcript-roundtrip.test.ts`:
555
+ 1. Spawn a mock child producing a JSON line with a secret.
556
+ 2. Read the transcript file → assert it contains no raw secret.
557
+ 3. Run `recoverCheckpointedTasks` → assert the result artifact also contains no secret.
558
558
 
559
559
  ---
560
560
 
561
- ## Gaps về test coverage
561
+ ## Test coverage gaps
562
562
 
563
- | Module | Trạng thái |
563
+ | Module | Status |
564
564
  |---|---|
565
- | `src/worktree/worktree-manager.ts` | Chỉ `branch-freshness.test.ts`. Thiếu test cho `prepareTaskWorkspace` (reuse path, branch mismatch, setupHook). |
566
- | `src/worktree/cleanup.ts` | `lifecycle-actions.test.ts` indirect. Thiếu test trực tiếp cho dirty-preserve + diff artifact. |
567
- | `src/state/locks.ts` (sync vs async parity) | `locks-race.test.ts` + `api-locks.test.ts` không assert sự khác biệt nêu BUG-004. |
568
- | `src/state/artifact-store.ts` | Cần test hash/size match (BUG-002). |
569
- | `src/schema/team-tool-schema.ts` | `team-tool-schema.test.ts` không case cho `retry` (BUG-001). |
565
+ | `src/worktree/worktree-manager.ts` | Only has `branch-freshness.test.ts`. Missing tests for `prepareTaskWorkspace` (reuse path, branch mismatch, setupHook). |
566
+ | `src/worktree/cleanup.ts` | Has `lifecycle-actions.test.ts` indirectly. Missing a direct test for dirty-preserve + diff artifact. |
567
+ | `src/state/locks.ts` (sync vs async parity) | `locks-race.test.ts` + `api-locks.test.ts` do not assert the difference described in BUG-004. |
568
+ | `src/state/artifact-store.ts` | Needs a hash/size match test (BUG-002). |
569
+ | `src/schema/team-tool-schema.ts` | `team-tool-schema.test.ts` has no case for `retry` (BUG-001). |
570
570
 
571
571
  ---
572
572
 
573
- ## Điểm tích cực
573
+ ## Positives
574
574
 
575
- - **Path-traversal guards** trong `resolveInside` (`artifact-store.ts:96-105`) combine cả relative-segment check, `path.relative` check `path.normalize + startsWith(base + sep)`.
576
- - **Atomic write** dùng `O_EXCL | O_NOFOLLOW`, post-open `fstatSync().isFile()` verification, Windows EPERM/EBUSY rename retry.
577
- - **Process management** trong `child-pi.ts` track PID trong `activeChildProcesses`, hỗ trợ `taskkill /T /F` (Win) + `process.kill(-pid, ...)` (POSIX), hard-kill fallback post-exit stdio guard.
578
- - **Env-secret filtering** trước khi spawn child Pi (`child-pi.ts:113`) dùng `SECRET_KEY_PATTERN` để loại token/api_key/password khỏi env.
579
- - **Default-safe execution**: `executeWorkers=false` / `PI_CREW_EXECUTE_WORKERS=0` / `PI_TEAMS_EXECUTE_WORKERS=0` block worker; `runtime.mode=scaffold` cho dry-run.
580
- - **Index.ts minimal**: đúng rule, chỉ 5 dòng.
581
- - **Lockstep destructive gates**: `delete` requires `confirm:true`, referenced resources block trừ khi `force:true` (verified `management.ts:344-353`).
575
+ - **Path-traversal guards** in `resolveInside` (`artifact-store.ts:96-105`) combine a relative-segment check, a `path.relative` check, and a `path.normalize + startsWith(base + sep)` check.
576
+ - **Atomic write** uses `O_EXCL | O_NOFOLLOW`, a post-open `fstatSync().isFile()` verification, and a Windows EPERM/EBUSY rename retry.
577
+ - **Process management** in `child-pi.ts` tracks the PID in `activeChildProcesses`, supports `taskkill /T /F` (Win) + `process.kill(-pid, ...)` (POSIX), has a hard-kill fallback, and a post-exit stdio guard.
578
+ - **Env-secret filtering** before spawning the child Pi (`child-pi.ts:113`) uses `SECRET_KEY_PATTERN` to strip token/api_key/password from the env.
579
+ - **Default-safe execution**: `executeWorkers=false` / `PI_CREW_EXECUTE_WORKERS=0` / `PI_TEAMS_EXECUTE_WORKERS=0` block workers; `runtime.mode=scaffold` for dry-runs.
580
+ - **Index.ts minimal**: follows the rule, only 5 lines.
581
+ - **Lockstep destructive gates**: `delete` requires `confirm:true`, referenced resources block unless `force:true` (verified in `management.ts:344-353`).
582
582
 
583
583
  ---
584
584
 
585
- ## Đề xuất ưu tiên fix
585
+ ## Suggested fix priority
586
586
 
587
- 1. **BUG-001** (5 phút): thêm 1 dòng `Type.Literal("retry")` + 1 test.
588
- 2. **BUG-002** (15 phút): chọn Option A, đổi thứ tự hash/write + thêm test integrity.
589
- 3. **BUG-004** (30 phút): đồng bộ sync/async lock retry behavior + test.
590
- 4. **BUG-005** (1 giờ): thêm branch existence check + worktree prune trước add, viết test.
591
- 5. **BUG-003** (1 giờ): update AGENTS.md với rule exception cho lazy boundaries, thêm marker comments.
592
- 6. Phần còn lại: batch trong release sau.
587
+ 1. **BUG-001** (5 minutes): add one line `Type.Literal("retry")` + 1 test.
588
+ 2. **BUG-002** (15 minutes): choose Option A, swap the hash/write order + add an integrity test.
589
+ 3. **BUG-004** (30 minutes): align the sync/async lock retry behavior + test.
590
+ 4. **BUG-005** (1 hour): add a branch-existence check + worktree prune before add, write tests.
591
+ 5. **BUG-003** (1 hour): update AGENTS.md with a rule exception for lazy boundaries, add marker comments.
592
+ 6. The rest: batch into a later release.