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,46 +1,46 @@
1
- # Review các fix đã áp dụng
1
+ # Review of the fixes applied
2
2
 
3
- > Ngày: 2026-05-18
4
- > Phiên bản: `pi-crew@0.2.20`
5
- > Base: PROJECT_REVIEW.md (cùng thư mục) — báo cáo ban đầu.
6
- > Working tree: 33 file thay đổi (`git diff --stat`), bao gồm cài `@biomejs/biome`, thêm `biome.json`, sửa source + test.
3
+ > Date: 2026-05-18
4
+ > Version: `pi-crew@0.2.20`
5
+ > Base: PROJECT_REVIEW.md (same directory) — the original report.
6
+ > Working tree: 33 files changed (`git diff --stat`), including installing `@biomejs/biome`, adding `biome.json`, fixing source + tests.
7
7
 
8
8
  ## TL;DR
9
9
 
10
- Đã fix đúng hướng **toàn bộ test vẫn pass** (1596/1598, 0 fail). Tuy nhiên **3 lỗi correctness mới do fix tạo ra** **2 quy ước cần dọn**:
10
+ The fixes go in the right direction and **all tests still pass** (1596/1598, 0 fail). However, there are **3 new correctness bugs introduced by the fixes** and **2 conventions to clean up**:
11
11
 
12
- | ID | File | Mức | Tình trạng |
12
+ | ID | File | Severity | Status |
13
13
  |---|---|---|---|
14
- | **NEW-1** | `src/state/event-log-rotation.ts` (rotateEventLog) | HIGH | `require()` trong ESM → throw silently |
15
- | **NEW-2** | `src/runtime/task-runner.ts` (M1 transcript per attempt) | HIGH | logic sai, vẫn dùng chung 1 file |
16
- | **NEW-3** | `src/runtime/task-runner.ts` (M2 transcript cap) | MED | đọc tail không cắt theo dòng → JSONL corrupt; ghi artifact với relativePath |
17
- | LINT-1 | `src/runtime/task-runner.ts:350` | LOW | `yieldResult` unused (yield logic bị remove?) |
18
- | LINT-2 | `src/runtime/team-runner.ts:270` | LOW | `runPromise` unused (đăng Promise rồi bỏ tham chiếu) |
14
+ | **NEW-1** | `src/state/event-log-rotation.ts` (rotateEventLog) | HIGH | `require()` in ESM → throws silently |
15
+ | **NEW-2** | `src/runtime/task-runner.ts` (M1 transcript per attempt) | HIGH | logic is wrong, still uses a single shared file |
16
+ | **NEW-3** | `src/runtime/task-runner.ts` (M2 transcript cap) | MED | tail read does not cut on line boundaries corrupt JSONL; writes artifact with the old relativePath |
17
+ | LINT-1 | `src/runtime/task-runner.ts:350` | LOW | `yieldResult` unused (yield logic removed?) |
18
+ | LINT-2 | `src/runtime/team-runner.ts:270` | LOW | `runPromise` unused (registers a Promise then drops the reference) |
19
19
 
20
- Status từng issue gốc:
20
+ Status of each original issue:
21
21
 
22
- | Issue | Status | Ghi chú |
22
+ | Issue | Status | Notes |
23
23
  |---|---|---|
24
- | **H1** event-log overflow | OK | đúng pattern: ưu tiên terminal events, compact + rotate trước khi append |
25
- | **H2** mailbox lock | OK | dùng `withEventLogLockSync` |
26
- | **H3** atomic-write fallback symlink | OK | re-check `lstatSync.isSymbolicLink()` trước fallback |
27
- | **H4** rename `__test__mergeTaskUpdates` | OK | đã đổi tên + giữ alias deprecated |
28
- | **M1** transcript per attempt | **BROKEN (NEW-2)** | logic không đúng |
29
- | **M2** transcript cap | **PARTIAL (NEW-3)** | cap nhưng cắt sai chỗ |
30
- | **M3** cleanup race-safe stat | OK | dùng `withFileTypes` + try/catch |
31
- | **M4** runSetupHook full-JSON | OK | thử full trimmed trước, fallback last-line |
32
- | **M5** symlink fail logging | OK | log do, hint Windows non-admin |
33
- | **M6** final-drain telemetry | OK | log internal error khi override exit |
34
- | **L1** ESLint/Biome | OK | đã add `@biomejs/biome` + `biome.json` |
35
- | **L12** rename references | OK | đã mở rộng cho workflow step.role + test fixtures |
24
+ | **H1** event-log overflow | OK | correct pattern: prioritize terminal events, compact + rotate before append |
25
+ | **H2** mailbox lock | OK | uses `withEventLogLockSync` |
26
+ | **H3** atomic-write fallback symlink | OK | re-checks `lstatSync.isSymbolicLink()` before fallback |
27
+ | **H4** rename `__test__mergeTaskUpdates` | OK | renamed + kept deprecated alias |
28
+ | **M1** transcript per attempt | **BROKEN (NEW-2)** | logic is incorrect |
29
+ | **M2** transcript cap | **PARTIAL (NEW-3)** | has a cap but cuts at the wrong place |
30
+ | **M3** cleanup race-safe stat | OK | uses `withFileTypes` + try/catch |
31
+ | **M4** runSetupHook full-JSON | OK | tries full trimmed first, falls back to last-line |
32
+ | **M5** symlink fail logging | OK | logs the reason, hints at Windows non-admin |
33
+ | **M6** final-drain telemetry | OK | logs internal error when overriding exit |
34
+ | **L1** ESLint/Biome | OK | added `@biomejs/biome` + `biome.json` |
35
+ | **L12** rename references | OK | expanded for workflow step.role + test fixtures |
36
36
 
37
37
  ---
38
38
 
39
- ## 1. Bugs mới do fix tạo ra (NEW-*)
39
+ ## 1. New bugs introduced by the fixes (NEW-*)
40
40
 
41
- ### NEW-1 (HIGH) — `rotateEventLog` dùng `require()` trong ESM
41
+ ### NEW-1 (HIGH) — `rotateEventLog` uses `require()` in an ESM module
42
42
 
43
- **File**: `src/state/event-log-rotation.ts` (dòng 124–129)
43
+ **File**: `src/state/event-log-rotation.ts` (lines 124–129)
44
44
 
45
45
  ```ts
46
46
  } catch (error) {
@@ -55,12 +55,12 @@ Status từng issue gốc:
55
55
  }
56
56
  ```
57
57
 
58
- **Vấn đề**:
59
- 1. Project khai báo `"type": "module"` (ESM). Trong ESM scope, **`require` không tồn tại** → throw `ReferenceError: require is not defined`.
60
- 2. Path `"./internal-error.ts"` sai — file thực tế `../utils/internal-error.ts`.
61
- 3. Outer try-catch swallow lỗikhi `rename` fail, hàm sẽ trả `false` nhưng **không log nào được ghi**. H1 fix dựa vào rotateEventLog để giảm size; nếu rotate fail im lặng, ta quay lại scenario silent-drop.
58
+ **Problem**:
59
+ 1. The project declares `"type": "module"` (ESM). In an ESM scope, **`require` does not exist** → throws `ReferenceError: require is not defined`.
60
+ 2. The path `"./internal-error.ts"` is wrong the file is actually at `../utils/internal-error.ts`.
61
+ 3. The outer try-catch swallows the error when `rename` fails, the function returns `false` but **no log is written**. The H1 fix relies on rotateEventLog to reduce size; if rotate fails silently, we are back to the silent-drop scenario.
62
62
 
63
- **Fix đúng**: import top-of-file giống `compactEventLog` đã làm:
63
+ **Correct fix**: import at the top of the file like `compactEventLog` already does:
64
64
  ```ts
65
65
  import { logInternalError } from "../utils/internal-error.ts";
66
66
  // ...
@@ -69,28 +69,28 @@ import { logInternalError } from "../utils/internal-error.ts";
69
69
  return false;
70
70
  }
71
71
  ```
72
- Không circular dependency `internal-error.ts` không import từ `state/`.
72
+ There is no circular dependency because `internal-error.ts` does not import from `state/`.
73
73
 
74
74
  ---
75
75
 
76
- ### NEW-2 (HIGH) — Transcript-per-attempt không hoạt động
76
+ ### NEW-2 (HIGH) — Transcript-per-attempt does not work
77
77
 
78
- **File**: `src/runtime/task-runner.ts` (dòng 155–158)
78
+ **File**: `src/runtime/task-runner.ts` (lines 155–158)
79
79
 
80
80
  ```ts
81
81
  modelAttempts = [];
82
82
  // M1 fix: transcript path per attempt to avoid mixing across fallback attempts.
83
- const attempt = modelAttempts.length; // 0-based index ← luôn = 0
83
+ const attempt = modelAttempts.length; // 0-based index ← always 0
84
84
  transcriptPath = `${manifest.artifactsRoot}/transcripts/${task.id}.attempt-${attempt}.jsonl`;
85
85
  ```
86
86
 
87
- **Vấn đề**:
88
- - `modelAttempts = []` vừa khởi tạo rỗng → `modelAttempts.length` **luôn 0**.
89
- - `transcriptPath` được set **ngoài** vòng `for (let i = 0; i < attemptModels.length; i++)`.
90
- - Cả N lần attempt đều ghi vào `transcripts/${task.id}.attempt-0.jsonl` → vẫn mixing y nguyên như trước.
91
- - Hơn nữa: `parsePiJsonOutput(fs.readFileSync(transcriptPath))` đọc accumulated content → final text/usage vẫn lẫn nhiều attempt.
87
+ **Problem**:
88
+ - `modelAttempts = []` was just initialized empty → `modelAttempts.length` **is always 0**.
89
+ - `transcriptPath` is set **outside** the `for (let i = 0; i < attemptModels.length; i++)` loop.
90
+ - All N attempts write to `transcripts/${task.id}.attempt-0.jsonl` → still mixing exactly like before.
91
+ - Furthermore: `parsePiJsonOutput(fs.readFileSync(transcriptPath))` reads accumulated content → final text/usage is still mixed across attempts.
92
92
 
93
- **Fix đúng**: dùng biến loop `i`, set transcriptPath bên trong vòng for:
93
+ **Correct fix**: use the loop variable `i`, set transcriptPath inside the for loop:
94
94
  ```ts
95
95
  for (let i = 0; i < attemptModels.length; i++) {
96
96
  transcriptPath = `${manifest.artifactsRoot}/transcripts/${task.id}.attempt-${i}.jsonl`;
@@ -100,9 +100,9 @@ for (let i = 0; i < attemptModels.length; i++) {
100
100
 
101
101
  ---
102
102
 
103
- ### NEW-3 (MED) — Transcript cap đọc tail không tôn trọng line boundary
103
+ ### NEW-3 (MED) — Transcript cap tail read does not respect line boundaries
104
104
 
105
- **File**: `src/runtime/task-runner.ts` (dòng 294–315)
105
+ **File**: `src/runtime/task-runner.ts` (lines 294–315)
106
106
 
107
107
  ```ts
108
108
  const MAX_TRANSCRIPT_ARTIFACT_BYTES = 5 * 1024 * 1024;
@@ -121,35 +121,35 @@ if (fs.existsSync(transcriptPath)) {
121
121
  }
122
122
  transcriptArtifact = writeArtifact(manifest.artifactsRoot, {
123
123
  kind: "log",
124
- relativePath: `transcripts/${task.id}.jsonl`, // ← tên artifact khác source!
124
+ relativePath: `transcripts/${task.id}.jsonl`, // ← artifact name differs from source!
125
125
  content: transcriptContent,
126
126
  producer: task.id,
127
127
  });
128
128
  }
129
129
  ```
130
130
 
131
- **Vấn đề**:
132
- 1. **JSONL corruption**: tail-read cắt offset byte cố định, không cắt theo `\n` → dòng đầu của transcript artifact rất khả năng **partial JSON line** không parse được. Bất kỳ tool nào replay transcript sẽ skip dòng đầu (mất event quan trọng).
133
- - Fix: sau khi đọc, tìm newline đầu tiên, drop bytes trước nó. Hoặc prepend header marker `[truncated head]`.
134
- 2. **`relativePath` không match source file**: nếu NEW-2 fix đúng (`attempt-i.jsonl`), thì artifact đáng lẽ phải tham chiếu tên đó. Hiện tại artifact luôn ghi `transcripts/${task.id}.jsonl` → mất thông tin attempt.
135
- 3. **UTF-8 boundary**: `buf.slice(0, bytesRead).toString('utf-8')` thể cắt giữa 1 tự multi-byte → tự đầu thành `\uFFFD`. Nhỏ nhưng đáng nhắc.
136
- 4. **Cap chỉ 5MB** cho artifact, nhưng source `transcriptPath` không bị capvẫn thể grow rất lớn (M2 chỉ giải quyết artifact memory, chưa giải quyết disk).
131
+ **Problem**:
132
+ 1. **JSONL corruption**: the tail read cuts at a fixed byte offset, not on a `\n` → the first line of the transcript artifact is very likely a **partial JSON line** that cannot be parsed. Any tool replaying the transcript will skip the first line (losing an important event).
133
+ - Fix: after reading, find the first newline and drop the bytes before it. Or prepend a header marker `[truncated head]`.
134
+ 2. **`relativePath` does not match the source file**: if NEW-2 is fixed correctly (`attempt-i.jsonl`), then the artifact should reference that name. Currently the artifact always writes `transcripts/${task.id}.jsonl` → loses the attempt information.
135
+ 3. **UTF-8 boundary**: `buf.slice(0, bytesRead).toString('utf-8')` can cut in the middle of a multi-byte character the first character becomes `\uFFFD`. Minor but worth noting.
136
+ 4. **Cap is only 5MB** for the artifact, but the source `transcriptPath` is not cappedcan still grow very large (M2 only solves the artifact memory, not the disk).
137
137
 
138
138
  ---
139
139
 
140
- ## 2. Lint cảnh báo còn lại
140
+ ## 2. Remaining lint warnings
141
141
 
142
- Cài `@biomejs/biome` (L1 OK). Khi chạy `npx biome lint` trên các file đã sửa, còn 2 warning:
142
+ Installed `@biomejs/biome` (L1 OK). When running `npx biome lint` on the changed files, 2 warnings remain:
143
143
 
144
144
  ### LINT-1 — `task-runner.ts:350` `yieldResult` unused
145
145
 
146
146
  ```ts
147
147
  let yieldResult: YieldResult | undefined;
148
- // ... gán yieldResult = extractYieldResult(yieldEvent);
149
- // nhưng không đọc lại
148
+ // ... assigns yieldResult = extractYieldResult(yieldEvent);
149
+ // but never read again
150
150
  ```
151
151
 
152
- `yieldResult` được gán nhưng không được sử dụng ở đâu phía dưới. Logic yield đang bị "treo". Hoặc remove biến, hoặc dùng để override task.result/finalText. Cần xác nhận với owner.
152
+ `yieldResult` is assigned but never used anywhere below. The yield logic is "dangling". Either remove the variable, or use it to override task.result/finalText. Needs confirmation from the owner.
153
153
 
154
154
  ### LINT-2 — `team-runner.ts:270` `runPromise` unused
155
155
 
@@ -157,34 +157,34 @@ let yieldResult: YieldResult | undefined;
157
157
  const runPromise = registerRunPromise(manifest.runId);
158
158
  ```
159
159
 
160
- `registerRunPromise` side-effect (đăng vào tracker), nhưng tên biến không cần thiết. thể đổi thành `void registerRunPromise(manifest.runId);` để biome bỏ qua, hoặc đổi tên `_runPromise`.
160
+ `registerRunPromise` has a side effect (registers into the tracker), but the variable name is unnecessary. You could change it to `void registerRunPromise(manifest.runId);` so biome ignores it, or rename to `_runPromise`.
161
161
 
162
- > Không nên gắn `lint:check` vào CI cho đến khi 2 cảnh báo này được fix, nếu không sẽ noise trên mỗi PR.
162
+ > Do not wire `lint:check` into CI until these 2 warnings are fixed, otherwise it will add noise to every PR.
163
163
 
164
164
  ---
165
165
 
166
- ## 3. Issues GỐC đã fix tốt (chi tiết)
166
+ ## 3. Original issues fixed well (details)
167
167
 
168
168
  ### H1 — Event-log overflow (PASS)
169
169
 
170
- `appendEventInsideLock` đã được sửa hợp lý:
171
- - Terminal event luôn được append bất kể size.
172
- - Non-terminal event gặp overflow → `compactEventLog` ngay, nếu vẫn quá thì `rotateEventLog`.
173
- - `skippedDueToSize` flag chỉ đặt khi cả compact + rotate đều không giảm được size (rất hiếm).
170
+ `appendEventInsideLock` was fixed correctly:
171
+ - Terminal events are always appended regardless of size.
172
+ - Non-terminal events that hit the overflow → `compactEventLog` immediately, and if still too large → `rotateEventLog`.
173
+ - The `skippedDueToSize` flag is only set when both compact + rotate fail to reduce size (very rare).
174
174
 
175
- **Lưu ý nhỏ**:
176
- - `appendCounter++` vẫn chạy kể cả khi `skippedDueToSize === true`. Không phải lỗi nhưng làm `% 100` rotation kích hoạt sớm hơn 1 chu kỳ không ảnh hưởng correctness.
177
- - Seq number vẫn được consume khi skipped → khi consumer thấy "gap" seq họ thể lo lắng. thể đặt `metadata.appended: false` (đã ) để consumer skip an toàn. OK.
178
- - Phụ thuộc `rotateEventLog` (NEW-1 broken). Khi NEW-1 fail, fallback path `appendFileSync` vẫn append vào file > 50MB → file ngày càng to.
175
+ **Minor notes**:
176
+ - `appendCounter++` still runs even when `skippedDueToSize === true`. Not a bug but makes the `% 100` rotation trigger one cycle earlydoes not affect correctness.
177
+ - The seq number is still consumed when skipped → when a consumer sees a "gap" in seq they may worry. You can set `metadata.appended: false` (already present) so consumers skip safely. OK.
178
+ - Depends on `rotateEventLog` (NEW-1 broken). When NEW-1 fails, the fallback path is `appendFileSync` which still appends to a file > 50MB → the file keeps growing.
179
179
 
180
180
  ### H2 — Mailbox lock (PASS)
181
181
 
182
- Bọc `appendFileSync` trong `withEventLogLockSync`. Hợp lý.
182
+ Wraps `appendFileSync` in `withEventLogLockSync`. Reasonable.
183
183
 
184
- **Lưu ý**:
185
- - Lock theo `eventsPath` thực ra theo `mailboxFile(...)`, tức `inbox.jsonl` `outbox.jsonl` lock độc lập. OK cross-process.
186
- - `withEventLogLockSync` không export trước đó, đã được đổi thành `export function` — chấp nhận được nhưng tên hơi misleading khi dùng cho mailbox. Cân nhắc tách thành `withJsonlAppendLock` chung.
187
- - Lock chỉ bảo vệ append. Các path khác như `updateMailboxMessageReply` (đã dùng `atomicWriteFile` rewrite) hoặc `validateMailbox` không bị ảnh hưởng.
184
+ **Notes**:
185
+ - The lock by `eventsPath` is actually by `mailboxFile(...)`, i.e., `inbox.jsonl` and `outbox.jsonl` have independent locks. OK for cross-process.
186
+ - `withEventLogLockSync` was not exported before; it was changed to `export function` — acceptable but the name is slightly misleading when used for the mailbox. Consider extracting a generic `withJsonlAppendLock`.
187
+ - The lock only protects append. Other paths like `updateMailboxMessageReply` (which already uses `atomicWriteFile` rewrite) or `validateMailbox` are not affected.
188
188
 
189
189
  ### H3 — Atomic-write fallback symlink TOCTOU (PASS)
190
190
 
@@ -200,7 +200,7 @@ try {
200
200
  }
201
201
  ```
202
202
 
203
- OK. Lưu ý: outer catch swallow **mọi** lỗi từ `lstatSync`, không chỉ ENOENT. Nếu `lstatSync` fail EACCES (permission denied), fallback sẽ proceed mặc thể không an toàn. thể narrow xuống `(err as NodeJS.ErrnoException).code === "ENOENT"`.
203
+ OK. Note: the outer catch swallows **all** errors from `lstatSync`, not just ENOENT. If `lstatSync` fails with EACCES (permission denied), the fallback proceeds even though it may not be safe. Could narrow to `(err as NodeJS.ErrnoException).code === "ENOENT"`.
204
204
 
205
205
  ### H4 — Rename `__test__mergeTaskUpdates` (PASS)
206
206
 
@@ -210,29 +210,29 @@ export function mergeTaskUpdatesPreservingTerminal(...) { ... }
210
210
  export const __test__mergeTaskUpdates = mergeTaskUpdatesPreservingTerminal;
211
211
  ```
212
212
 
213
- Đẹp. Backward compat tốt. Caller bên trong `executeTeamRunCore` cũng cần updatekiểm tra nhanh:
213
+ Nice. Good backward compatibility. The caller inside `executeTeamRunCore` also needs updatingquick check:
214
214
 
215
215
  ```
216
216
  > rg "__test__mergeTaskUpdates" -n src
217
217
  src/runtime/team-runner.ts:117:export const __test__mergeTaskUpdates = mergeTaskUpdatesPreservingTerminal;
218
- src/runtime/team-runner.ts:545: tasks = __test__mergeTaskUpdates(tasks, results); ← vẫn dùng alias
218
+ src/runtime/team-runner.ts:545: tasks = __test__mergeTaskUpdates(tasks, results); ← still uses the alias
219
219
  ```
220
220
 
221
- Production code vẫn gọi alias `__test__mergeTaskUpdates`. Đề nghị: đổi caller sang `mergeTaskUpdatesPreservingTerminal` để chỉ test file dùng alias.
221
+ Production code still calls the alias `__test__mergeTaskUpdates`. Suggestion: change the caller to `mergeTaskUpdatesPreservingTerminal` so only the test file uses the alias.
222
222
 
223
223
  ### M3 — Cleanup race-safe stat (PASS)
224
224
 
225
- Dùng `withFileTypes`, bọc `statSync` trong try/catch. OK.
225
+ Uses `withFileTypes`, wraps `statSync` in try/catch. OK.
226
226
 
227
227
  ### M4 — runSetupHook multi-line JSON (PASS)
228
228
 
229
- Thử `JSON.parse(trimmed)` trước, rồi fallback last-line. OK.
229
+ Tries `JSON.parse(trimmed)` first, then falls back to the last line. OK.
230
230
 
231
- **Lưu ý nhỏ**: hai try/catch lồng nhau bên trong outer try → outer catch (parse error logging) gần như không bao giờ trigger vì inner catch đã swallow. thể clean up. Không ảnh hưởng correctness.
231
+ **Minor note**: two nested try/catch inside the outer try → the outer catch (parse error logging) almost never triggers because the inner catch already swallows. Could be cleaned up. Does not affect correctness.
232
232
 
233
233
  ### M5 — symlink fail logging (PASS)
234
234
 
235
- Log do + hint Windows non-admin. Lưu ý indentation hơi lệch (5 tab thay 1) — biome auto-format sẽ sửa.
235
+ Logs the reason + a Windows non-admin hint. Note the indentation is slightly off (5 tabs instead of 1) — biome auto-format will fix it.
236
236
 
237
237
  ### M6 — final-drain telemetry (PASS)
238
238
 
@@ -242,22 +242,22 @@ if (forcedFinalDrain && !timeoutError && exitCode !== 0) {
242
242
  }
243
243
  ```
244
244
 
245
- OK. Đang dùng `logInternalError` (không phải metric counter). Trong tương lai nên emit metric `crew.child.final_drain_force_zero_total` qua MetricRegistry để dashboard đếm — `logInternalError` chỉ backup observability.
245
+ OK. Uses `logInternalError` (not a metric counter). In the future, emit a metric `crew.child.final_drain_force_zero_total` via MetricRegistry so the dashboard can count it — `logInternalError` is only a backup observability.
246
246
 
247
- **Lưu ý**: indentation block lệch (5 tabs cho if-block trong block 4-tab parent). Biome sẽ flag.
247
+ **Note**: the indentation block is off (5 tabs for the if-block inside a 4-tab parent block). Biome will flag it.
248
248
 
249
249
  ### L1 — Biome added (PASS)
250
250
 
251
- `@biomejs/biome ^2.4.15` + `biome.json` config tốt:
252
- - `recommended: true`, indent tab × 4, double quote, semicolons always.
253
- - Tắt một số rule không phù hợp (`noNonNullAssertion`, `noUselessSwitchCase`, …).
254
- - `useIgnoreFile: true` đọc `.gitignore`.
251
+ `@biomejs/biome ^2.4.15` + good `biome.json` config:
252
+ - `recommended: true`, indent tab × 4, double quotes, semicolons always.
253
+ - Disables some unsuitable rules (`noNonNullAssertion`, `noUselessSwitchCase`, …).
254
+ - `useIgnoreFile: true` reads `.gitignore`.
255
255
 
256
- **Chưa có**:
257
- - `npm run lint` script trong `package.json`.
258
- - CI chưa chạy biome trong `npm run ci`.
256
+ **Still missing**:
257
+ - `npm run lint` script in `package.json`.
258
+ - CI does not run biome in `npm run ci`.
259
259
 
260
- Đề nghị thêm:
260
+ Suggested additions:
261
261
  ```json
262
262
  "scripts": {
263
263
  "lint": "biome lint .",
@@ -266,35 +266,35 @@ OK. Đang dùng `logInternalError` (không phải metric counter). Trong tương
266
266
  }
267
267
  ```
268
268
 
269
- ### L12 — Rename references (PASS, rủi ro)
269
+ ### L12 — Rename references (PASS, with risk)
270
270
 
271
- `updateReferencesForRename` đã mở rộng:
272
- 1. Workflow step.role → rename theo agent rename. **Cảnh báo logic**: `step.role` thực ra tên role trong team, không phải tên agent. Hai khái niệm khác nhau: agent `coder` thể được dùng cho role `developer`. Update step.role khi đổi agent name **sai semantic**, thể phá vỡ workflow hợp lệ.
273
- - Đề nghị: chỉ rename `team.roles[*].agent` (đã làm sẵn trong loop trước), không động vào `step.role`.
274
- 2. Update test fixtures qua regex.
271
+ `updateReferencesForRename` was expanded:
272
+ 1. Workflow step.role → renamed along with the agent rename. **Logic warning**: `step.role` is actually the role name within the team, not the agent name. These are two different concepts: agent `coder` can be used for role `developer`. Updating step.role when renaming an agent is **semantically wrong** and can break a valid workflow.
273
+ - Suggestion: only rename `team.roles[*].agent` (already done in the earlier loop), do not touch `step.role`.
274
+ 2. Update test fixtures via regex.
275
275
  ```ts
276
276
  const agentPattern = new RegExp('(["\'\\`]agent[="\':\\s]*)' + escapeRegex(oldName) + '(["\'\\`]|\\s)', 'g');
277
277
  ```
278
- - Regex này phức tạp + template-literal mess, rất dễ false positive/negative. dụ:
279
- - Sẽ match `"agent": "coder"` (OK)
280
- - Sẽ KHÔNG match `agent: coder` (không quote oldName)
281
- - Sẽ false-match nếu một biến tên `agent_other = "coder"`
282
- - `escapeRegex` regex: `/[.*+?^${}()|[\\]\\]/g` — đúng (đã verify character class).
283
- - **Đề nghị**: test fixture rewrite không nên dùng regex; nếu cần thì parse YAML/markdown frontmatter / TS AST.
284
- 3. `walkTsFiles` đệ quy tất cả `.ts`/`.md` trong test dir. OK nhưng I/O nặng cho rename op.
278
+ - This regex is complex + has a template-literal mess, very prone to false positives/negatives. For example:
279
+ - It will match `"agent": "coder"` (OK)
280
+ - It will NOT match `agent: coder` (oldName without quotes)
281
+ - It will false-match if there is a variable named `agent_other = "coder"`
282
+ - The `escapeRegex` regex: `/[.*+?^${}()|[\\]\\]/g` — correct (verified the character class).
283
+ - **Suggestion**: test fixture rewrites should not use regex; if needed, parse YAML/markdown frontmatter / TS AST.
284
+ 3. `walkTsFiles` recursively processes all `.ts`/`.md` in the test dir. OK but I/O-heavy for a rename op.
285
285
 
286
286
  ---
287
287
 
288
- ## 4. Side fixes phụ (không trong scope ban đầu)
288
+ ## 4. Incidental side fixes (not in the original scope)
289
289
 
290
- Một số file thay đổi không thuộc 4 batch trên vẻ tổng dọn dẹp:
290
+ Some changed files do not belong to the 4 batches aboveappear to be general cleanup:
291
291
 
292
- - `src/extension/team-tool.ts` — đổi `import { … }` thành `import type { … }` cho 2 chỗ lazy-load. Hợp (tránh runtime import side-effect).
293
- - `src/extension/team-tool.ts` — `let nextTasks` → `const nextTasks`. Đúng (không reassign).
294
- - `src/runtime/team-runner.ts` — `let workflow` → `const workflow`. Đúng.
295
- - `src/runtime/code-summary.ts`, `manifest-cache.ts`, `prose-compressor.ts`, `result-extractor.ts`, `retry-executor.ts`, `skill-instructions.ts`, `observability/event-to-metric.ts`, `utils/gh-protocol.ts`, `utils/names.ts`, `utils/sse-parser.ts`, `config/markers.ts`, `config/resilient-parser.ts`, `adapters/export-util.ts`, `worktree/cleanup.ts` (M3 + others) — hầu hết là biome auto-fix (formatting / unused imports). Diff stat nhỏ (~1-2 dòng/file).
292
+ - `src/extension/team-tool.ts` — changed `import { … }` to `import type { … }` for 2 lazy-load spots. Reasonable (avoids runtime import side effects).
293
+ - `src/extension/team-tool.ts` — `let nextTasks` → `const nextTasks`. Correct (not reassigned).
294
+ - `src/runtime/team-runner.ts` — `let workflow` → `const workflow`. Correct.
295
+ - `src/runtime/code-summary.ts`, `manifest-cache.ts`, `prose-compressor.ts`, `result-extractor.ts`, `retry-executor.ts`, `skill-instructions.ts`, `observability/event-to-metric.ts`, `utils/gh-protocol.ts`, `utils/names.ts`, `utils/sse-parser.ts`, `config/markers.ts`, `config/resilient-parser.ts`, `adapters/export-util.ts`, `worktree/cleanup.ts` (M3 + others) — mostly biome auto-fixes (formatting / unused imports). Diff stat is small (~1-2 lines/file).
296
296
 
297
- Cần xác minh không phải biome đã làm hỏng logic (đặc biệt remove `noUnusedImports` rule đã off nhưng các thay đổi `1 deletion` `result-extractor.ts`, `skill-instructions.ts`, `sse-parser.ts` rất khả nghi).
297
+ Need to verify biome did not break logic (especially since the `noUnusedImports` rule was turned off but the `1 deletion` changes in `result-extractor.ts`, `skill-instructions.ts`, `sse-parser.ts` are suspicious).
298
298
 
299
299
  ```bash
300
300
  git diff src/runtime/result-extractor.ts src/runtime/skill-instructions.ts src/utils/sse-parser.ts
@@ -310,34 +310,33 @@ npm run test:unit → 1596 pass / 2 skip / 0 fail / 87s
310
310
  npx biome lint <changed files> → 2 warnings (LINT-1, LINT-2)
311
311
  ```
312
312
 
313
- Tests vẫn pass vì:
314
- - NEW-1 không trigger trong unit tests (rotateEventLog chỉ chạy khi file > 50MB).
315
- - NEW-2 không test cụ thể cho transcript-per-attempt collision.
316
- - NEW-3 không test cho transcript cap > 5MB.
313
+ Tests still pass because:
314
+ - NEW-1 does not trigger in unit tests (rotateEventLog only runs when the file > 50MB).
315
+ - NEW-2 has no specific test for transcript-per-attempt collision.
316
+ - NEW-3 has no test for a transcript cap > 5MB.
317
317
 
318
318
  ---
319
319
 
320
- ## 6. Khuyến nghị hành động (ưu tiên)
320
+ ## 6. Recommended actions (by priority)
321
321
 
322
- 1. **Fix NEW-1 ngay**: chuyển `require` → top-level `import { logInternalError } from "../utils/internal-error.ts"`. (1 phút)
323
- 2. **Fix NEW-2**: di chuyển dòng `transcriptPath = ...attempt-${i}...` vào trong vòng `for`. (2 phút)
324
- 3. **Fix NEW-3**: cắt tail theo `\n` boundary; cập nhật `relativePath` artifact match với source filename; prepend marker `[truncated]\n` để consumer biết.
325
- 4. **Thêm unit tests** cho:
322
+ 1. **Fix NEW-1 now**: change `require` → top-level `import { logInternalError } from "../utils/internal-error.ts"`. (1 minute)
323
+ 2. **Fix NEW-2**: move the `transcriptPath = ...attempt-${i}...` line inside the `for` loop. (2 minutes)
324
+ 3. **Fix NEW-3**: cut the tail on the `\n` boundary; update the artifact `relativePath` to match the source filename; prepend a `[truncated]\n` marker so consumers know.
325
+ 4. **Add unit tests** for:
326
326
  - `rotateEventLog` (rename + create empty)
327
- - `appendEvent` với file > 50MB → terminal event vẫn được persist
328
- - `appendMailboxMessage` concurrent (spawn 2 worker, kiểm tra không interleave)
329
- - Transcript per-attempt (mock 2 attempts, verify 2 file riêng biệt)
327
+ - `appendEvent` with a file > 50MB → terminal event still persisted
328
+ - `appendMailboxMessage` concurrent (spawn 2 workers, check no interleaving)
329
+ - Transcript per-attempt (mock 2 attempts, verify 2 separate files)
330
330
  - Atomic-write fallback symlink TOCTOU (mock rename fail + symlink swap)
331
- 5. **Dọn LINT-1, LINT-2** trước khi gắn biome vào CI.
332
- 6. **Đề nghị thêm `lint` script** vào `package.json` + chạy biome trong `ci`.
333
- 7. **Review lại L12**: bỏ logic update `step.role` (sai semantic) hoặc gate qua `--unsafe-rename` flag.
334
- 8. **Re-verify side fixes biome auto-fix** `result-extractor.ts`, `skill-instructions.ts`, `sse-parser.ts` (3 file `-1 deletion` khả nghi).
331
+ 5. **Clean up LINT-1, LINT-2** before wiring biome into CI.
332
+ 6. **Suggested: add a `lint` script** to `package.json` + run biome in `ci`.
333
+ 7. **Re-review L12**: remove the `step.role` update logic (semantically wrong) or gate it behind an `--unsafe-rename` flag.
334
+ 8. **Re-verify biome auto-fix side fixes** in `result-extractor.ts`, `skill-instructions.ts`, `sse-parser.ts` (3 files with suspicious `-1 deletion`).
335
335
 
336
336
  ---
337
337
 
338
- ## 7. Kết luận
338
+ ## 7. Conclusion
339
339
 
340
- Hướng đi đúng, đa số issue ban đầu đã được giải quyết. Tuy nhiên 3 fix bị **bug logic** (NEW-1, NEW-2, NEW-3) khiến chính tính năng "anti-overflow" "per-attempt transcript" không hoạt động như mong đợi. tests không cover các đường code này, regression đi qua được suite hiện tại.
341
-
342
- Sau khi fix 3 bugs trên + bổ sung test, ta sẽ có một codebase chắc chắn hơn đáng kể so với baseline review.
340
+ The direction is correct, most of the original issues have been resolved. However, 3 fixes have **logic bugs** (NEW-1, NEW-2, NEW-3) that mean the "anti-overflow" and "per-attempt transcript" features do not work as intended. Because the old tests do not cover these code paths, the regressions pass the current suite.
343
341
 
342
+ After fixing the 3 bugs above + adding tests, we will have a significantly more robust codebase compared to the baseline review.