pi-crew 0.2.20 → 0.2.21

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 (93) hide show
  1. package/CHANGELOG.md +23 -10
  2. package/README.md +4 -2
  3. package/docs/PROJECT_REVIEW.md +271 -0
  4. package/docs/PROJECT_REVIEW_FIXES.md +343 -0
  5. package/docs/PROJECT_REVIEW_ROUND4.md +156 -0
  6. package/docs/PROJECT_REVIEW_ROUND5.md +86 -0
  7. package/docs/fixes/BATCH_A_H1_H2.md +86 -0
  8. package/docs/fixes/bug-006-foreground-cancel-concurrent.md +78 -0
  9. package/docs/fixes/bug-007-async-notifier-stale-ctx.md +112 -0
  10. package/docs/fixes/bug-008-child-process-silent-timeout.md +100 -0
  11. package/docs/fixes/bug-009-executor-yield-limit-needs-attention.md +75 -0
  12. package/docs/fixes/bug-010-child-process-api-key-filtered.md +109 -0
  13. package/docs/fixes/bug-011-spawn-pi-enoent.md +92 -0
  14. package/docs/fixes/bug-012-essential-env-stripped.md +89 -0
  15. package/docs/fixes/bug-013-background-runner-death.md +84 -0
  16. package/docs/fixes/bug-014-infinite-retry-loop-needs-attention.md +82 -0
  17. package/docs/fixes/bug-015-background-runner-sigterm.md +65 -0
  18. package/docs/fixes/bug-017-background-runner-session-shutdown.md +66 -0
  19. package/docs/fixes/bug-017-background-runner-sigkill-double-fork.md +28 -0
  20. package/docs/fixes/bug-018-child-pi-worker-stdin-hang.md +61 -0
  21. package/docs/fixes/bug-019-phantom-runs-temp-workspace.md +52 -0
  22. package/docs/pi-crew-bugs.md +954 -0
  23. package/docs/pi-crew-investigation-report.md +411 -0
  24. package/docs/pi-crew-test-final.md +120 -0
  25. package/docs/pi-crew-test-results.md +260 -0
  26. package/docs/pi-crew-test-round2.md +136 -0
  27. package/docs/pi-crew-test-round4.md +100 -0
  28. package/docs/pi-crew-test-round5.md +70 -0
  29. package/docs/pi-crew-test-round6.md +110 -0
  30. package/docs/usage.md +14 -0
  31. package/package.json +4 -2
  32. package/src/adapters/export-util.ts +12 -6
  33. package/src/agents/agent-config.ts +2 -0
  34. package/src/config/defaults.ts +1 -1
  35. package/src/config/markers.ts +22 -17
  36. package/src/config/resilient-parser.ts +1 -1
  37. package/src/extension/async-notifier.ts +4 -2
  38. package/src/extension/management.ts +52 -0
  39. package/src/extension/register.ts +47 -10
  40. package/src/extension/run-index.ts +20 -2
  41. package/src/extension/run-maintenance.ts +2 -2
  42. package/src/extension/team-tool/parallel-dispatch.ts +1 -1
  43. package/src/extension/team-tool/run.ts +3 -6
  44. package/src/extension/team-tool.ts +67 -11
  45. package/src/observability/event-to-metric.ts +2 -1
  46. package/src/runtime/async-runner.ts +42 -34
  47. package/src/runtime/background-runner.ts +165 -7
  48. package/src/runtime/child-pi.ts +111 -18
  49. package/src/runtime/code-summary.ts +1 -1
  50. package/src/runtime/crash-recovery.ts +1 -1
  51. package/src/runtime/crew-agent-runtime.ts +2 -1
  52. package/src/runtime/heartbeat-watcher.ts +4 -0
  53. package/src/runtime/live-agent-manager.ts +1 -1
  54. package/src/runtime/live-session-runtime.ts +2 -1
  55. package/src/runtime/manifest-cache.ts +2 -2
  56. package/src/runtime/model-fallback.ts +2 -1
  57. package/src/runtime/phase-progress.ts +1 -1
  58. package/src/runtime/pi-args.ts +3 -1
  59. package/src/runtime/pi-spawn.ts +6 -0
  60. package/src/runtime/prose-compressor.ts +1 -1
  61. package/src/runtime/result-extractor.ts +0 -1
  62. package/src/runtime/retry-executor.ts +1 -1
  63. package/src/runtime/runtime-resolver.ts +1 -1
  64. package/src/runtime/skill-instructions.ts +0 -1
  65. package/src/runtime/stale-reconciler.ts +30 -3
  66. package/src/runtime/subagent-manager.ts +2 -0
  67. package/src/runtime/task-display.ts +1 -1
  68. package/src/runtime/task-graph-scheduler.ts +1 -1
  69. package/src/runtime/task-runner/tail-read.ts +26 -0
  70. package/src/runtime/task-runner.ts +1007 -383
  71. package/src/runtime/team-runner.ts +9 -5
  72. package/src/runtime/worker-startup.ts +3 -1
  73. package/src/schema/team-tool-schema.ts +2 -1
  74. package/src/state/active-run-registry.ts +8 -2
  75. package/src/state/atomic-write.ts +17 -0
  76. package/src/state/contracts.ts +5 -2
  77. package/src/state/event-log-rotation.ts +118 -31
  78. package/src/state/event-log.ts +33 -5
  79. package/src/state/event-reconstructor.ts +4 -2
  80. package/src/state/mailbox.ts +5 -1
  81. package/src/state/schedule.ts +146 -0
  82. package/src/state/types.ts +40 -0
  83. package/src/state/usage.ts +20 -0
  84. package/src/ui/crew-widget.ts +2 -2
  85. package/src/ui/run-event-bus.ts +1 -1
  86. package/src/ui/run-snapshot-cache.ts +2 -1
  87. package/src/ui/snapshot-types.ts +1 -0
  88. package/src/utils/gh-protocol.ts +2 -2
  89. package/src/utils/names.ts +1 -1
  90. package/src/utils/sse-parser.ts +0 -2
  91. package/src/worktree/branch-freshness.ts +1 -1
  92. package/src/worktree/cleanup.ts +54 -14
  93. package/src/worktree/worktree-manager.ts +19 -9
package/CHANGELOG.md CHANGED
@@ -1,22 +1,35 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [0.2.21] — 3 Bugs Fixed — Background Runner, Child-pi stdin, Phantom Runs (2026-05-22)
4
4
 
5
- ### Added
5
+ ## 0.2.20 — 14 Bugs Fixed — needs_attention, Heartbeat, OOM, API Keys (2026-05-20)
6
6
 
7
- - **Streaming progress for `team` and `Agent` foreground tool calls** — The Pi tool widget previously showed only the tool label "Agent" / "Team" while a foreground run was executing, then dumped the full result at completion. Both tools now wire the `onUpdate` callback and stream a compact 3-4 line progress block once per second (status, elapsed, active agent role, current tool, latest output snippet). Background subagents are unchanged. Files: `src/ui/tool-progress-formatter.ts` (new), `src/extension/registration/subagent-tools.ts`, `src/extension/registration/team-tool.ts`.
7
+ ### Features
8
8
 
9
- ### Fixed
9
+ - **needs_attention terminal task status** — Tasks that complete without calling `submit_result` now get `activityState: needs_attention` instead of `completed`. Workflow phases advance on either `completed` or `needs_attention`.
10
+ - **3-layer OOM protection for background runs** — `node --max-old-space-size=512` prevents Node OOM kills; heartbeat + `pid_dead` stale detection catches zombie workers; `SIGTERM`/`SIGINT`/`SIGUSR2` handlers log `async.failed` for diagnosis.
11
+ - **Essential env vars preserved for child processes** — `PATH`, `HOME`, `USER`, `LANG`, `LC_ALL` now passed to child Pi workers.
12
+ - **Model API key allow-list** — `MINIMAX_API_KEY` and other model keys are preserved in child process env.
13
+ - **Async notifier stale-ctx guard** — `isCurrent` flag prevents stale session notifications from corrupting active run state.
10
14
 
11
- - **Agent "stopped" + "No output." without explanation** — `SubagentManager.markStopped()` and the `start()` catch block previously set `status: "stopped"` but never wrote a reason into `record.error`, so the user only saw the generic "No output." fallback. Fix: `markStopped(record, reason?)` and `abortAll(reason?)` now persist the abort reason (e.g. "Session switching — foreground subagents cancelled."), and the `Agent` tool foreground result chain includes `record.error` before the "No output." fallback. Files: `src/runtime/subagent-manager.ts`, `src/extension/register.ts`, `src/extension/registration/subagent-tools.ts`.
12
- - **Foreground crew_agent runs now use startForegroundRun** — Previously the `crew_agent` / `Agent` tool's runner did not pass `startForegroundRun` to `handleTeamTool`, causing it to use the blocking `executeTeamRun` path instead of the non-blocking foreground run path. The blocking path was vulnerable to Pi tool signal aborts (user cancel, session switch), causing premature "stopped" results. The runner now receives `startForegroundRun` from the registration layer, so `handleRun` starts the team run as a non-blocking foreground run and returns immediately, while the SubagentManager polls `pollRunToTerminal` until completion. This mirrors the `team` tool's foreground path and avoids signal-abort race conditions. File: `src/extension/registration/subagent-tools.ts`, `src/extension/register.ts`.
13
- - **Pre-aborted signal detection** — Added a diagnostic check at the top of the `crew_agent` `execute` function: if the Pi tool signal is already aborted before the agent even starts, the tool returns an explicit error message instead of spawning a doomed subagent that would show "stopped + No output." File: `src/extension/registration/subagent-tools.ts`.
15
+ ### Bug Fixes
14
16
 
15
- ### Tests
17
+ - **Bug #1/2: 429 → stale heartbeat misclassification** — MiniMax `provider_error` with 429 status retried with fallback chain.
18
+ - **Bug #3: background.log silent on error** — Captures all stderr/exit output.
19
+ - **Bug #4: worker-startup.ts missing rate_limited** — `error.classification = "rate_limited"` added.
20
+ - **Bug #5: stale notifications after prune** — Heartbeat checked before declaring `pid_dead`.
21
+ - **Bug #6: concurrent tool calls cancel foreground runs** — Confirmed as design constraint.
22
+ - **Bug #7: async notifier stale-ctx dies** — `isCurrent` guard added.
23
+ - **Bug #8/10: MINIMAX_API_KEY filtered** — Added to env allow-list.
24
+ - **Bug #9: executor yield limit → needs_attention** — `noYield` path sets `activityState: needs_attention`.
25
+ - **Bug #11: background spawn ENOENT** — `resolveScriptPath` handles `node_modules` hoisting.
26
+ - **Bug #12: essential env stripped** — `PATH/HOME/USER/LANG/LC_ALL` preserved.
27
+ - **Bug #13: background runner dies at ~59s** — 3-layer OOM protection.
28
+ - **Bug #14: infinite retry loop** — `needs_attention` gets `queue: "done"` in task graph scheduler.
16
29
 
17
- - Added `test/unit/tool-progress-formatter.test.ts` (5 cases covering empty run, run-only header, active agent with tool, long-output trimming + usage tokens fallback, spawn error).
30
+ ### Tests
18
31
 
19
- ---
32
+ - Added `test/unit/needs-attention-status.test.ts` (9 cases for contracts, transitions, agent-control idle detection).
20
33
 
21
34
  ## 0.2.3 — Bug Fixes & Hardening (2026-05-12)
22
35
 
package/README.md CHANGED
@@ -9,7 +9,7 @@ npm: pi-crew
9
9
  repo: https://github.com/baphuongna/pi-crew
10
10
  ```
11
11
 
12
- **v0.2.0**: Security hardening + 76% faster startup. See [CHANGELOG.md](CHANGELOG.md).
12
+ **v0.2.21**: 3 bugs fixed background runner session_shutdown survival, child-pi stdin hang, phantom runs from temp workspaces. See [CHANGELOG.md](CHANGELOG.md) and [docs/pi-crew-bugs.md](docs/pi-crew-bugs.md).
13
13
 
14
14
  ---
15
15
 
@@ -17,6 +17,7 @@ repo: https://github.com/baphuongna/pi-crew
17
17
 
18
18
  - **One Pi tool** — `team` handles routing, planning, execution, review, and cleanup
19
19
  - **Autonomous delegation** — policy injection decides when/how to delegate based on task complexity
20
+ - **needs_attention status** — tasks that complete without calling `submit_result` get `needs_attention` (terminal) instead of `completed`; allows retry/re-run without blocking downstream phases
20
21
  - **Real child Pi workers** — each task spawns a separate Pi process by default; scaffold/dry-run opt-out
21
22
  - **Adaptive planning** — implementation workflow lets a planner agent decide subagent fanout
22
23
  - **Parallel execution** — tasks in the same phase run concurrently with configurable concurrency
@@ -104,6 +105,7 @@ When unsure which team/workflow fits:
104
105
  | `implementation` | Adaptive planner quyết fanout | Multi-file implementation |
105
106
  | `review` | explore → code-review → security-review → verify | Code review + security audit |
106
107
  | `research` | explore → analyze → write | Nghiên cứu và viết tài liệu |
108
+ | `parallel-research` | Parallel shards → synthesize → write | Multi-source research |
107
109
 
108
110
  ## Builtin Agents
109
111
 
@@ -172,7 +174,7 @@ Worktree mode is **opt-in** and requires a clean repo:
172
174
  | **Async** | `asyncByDefault`, `runtime.groupJoin` | `false`, `smart` |
173
175
  | **Autonomy** | `profile`: `manual` \| `suggested` \| `assisted` \| `aggressive` | `suggested` |
174
176
  | **UI** | `widgetPlacement`, `dashboardPlacement`, `showModel`, `showTokens` | compact widget |
175
- | **Reliability** | `autoRetry`, `autoRecover`, `deadletterThreshold` | all opt-in |
177
+ | **Reliability** | `autoRetry`, `autoRecover`, `deadletterThreshold`, `retryPolicy` | all opt-in |
176
178
  | **Observability** | `prometheus.enabled`, `otlp.enabled` | opt-in |
177
179
 
178
180
  > ⚠️ **Trust boundary**: project config cannot override sensitive execution controls (workers, runtime mode, autonomy, agent overrides). Set those in **user config** only.
@@ -0,0 +1,271 @@
1
+ # pi-crew — Project Review
2
+
3
+ > Ngày review: 2026-05-18
4
+ > Phiên bản: `pi-crew@0.2.19`
5
+ > Phạm vi: toàn bộ source (`index.ts`, `src/**`), config, test, tài liệu, scripts.
6
+ > Method: đọc trực tiếp source, đối chiếu `AGENTS.md`/`docs/architecture.md`, chạy `npm run typecheck` + `npm run test:unit`.
7
+
8
+ ## Tổng quan
9
+
10
+ `pi-crew` là một Pi extension điều phối multi-agent (teams + workflows + worktrees + async background runs), với **mô hình durable-first**: mọi run/task/event được persist xuống ổ đĩa (JSONL + JSON atomic write) để foreground, async background, dashboard và crash recovery đều đọc cùng một nguồn sự thật.
11
+
12
+ Codebase trưởng thành, có **TypeScript strict mode** (`noImplicitAny`, `strict: true`), bộ test rộng (1596 tests pass, 2 skipped, 0 fail), kiến trúc phân tầng rõ (extension / runtime / state / worktree / utils), và rất nhiều ghi chú phòng-thân ("3.1 backpressure", "2.10 cache", "P1 catch unhandled errors") cho thấy đã được iterate qua nhiều round review.
13
+
14
+ ### Kết quả health-check nhanh
15
+
16
+ | Hạng mục | Kết quả |
17
+ |---|---|
18
+ | `npm run typecheck` (`tsc --noEmit` + strip-types import) | PASS |
19
+ | `npm run test:unit` (1598 tests / 128 suites) | 1596 pass · 2 skip · 0 fail (~90s) |
20
+ | `npm pack --dry-run` (qua `npm run ci`) | Không kiểm tra trong session này |
21
+ | Linter (ESLint) | Không có script `lint`; dựa vào `tsc strict` |
22
+ | Số file `.ts` trong `src/` | ~190 modules |
23
+
24
+ ---
25
+
26
+ ## 1. Điểm mạnh đáng ghi nhận
27
+
28
+ 1. **Path-safety nhất quán**: `utils/safe-paths.ts` (`assertSafePathId`, `resolveContainedPath`, `resolveRealContainedPath`) được dùng đồng đều ở `state-store.ts`, `artifact-store.ts`, `mailbox.ts`. Có cả hai lớp: containment check theo string và real-path check (chống symlink escape sau khi mkdir).
29
+ 2. **Atomic write nhiều lớp phòng thân** (`state/atomic-write.ts`):
30
+ - `O_EXCL | O_CREAT | O_NOFOLLOW` khi mở temp file.
31
+ - `fstatSync` post-open để verify regular-file (chống TOCTOU trên Windows nơi `O_NOFOLLOW = 0`).
32
+ - Rename retry với exponential backoff + jitter (chống lockstep starvation).
33
+ - Coalesced variant `atomicWriteJsonCoalesced` cho high-frequency state writes; flush trên `exit`/`SIGTERM`/`SIGINT`.
34
+ 3. **Redaction (`utils/redaction.ts`)** xử lý nhiều pattern: PEM private key, Authorization headers, Bearer tokens, inline secret patterns, key-name match (`apiKey`, `password`, `secret`, ...). Áp dụng cả ở `appendEvent`, `appendMailboxMessage`, `writeArtifact`, `appendTranscript`.
35
+ 4. **Env sanitization (`utils/env-filter.ts`)**: secret-pattern deny-list mặc định, allow-list mode cho `worktree.setupHook` để chỉ pass `PATH`, `HOME`, `PI_*`.
36
+ 5. **Process kill tree** (`runtime/child-pi.ts`):
37
+ - Windows: `taskkill /T /F` + verify-after-2s + retry nếu PID còn sống.
38
+ - POSIX: `process.kill(-pid, "SIGTERM")` (process group) với fallback absolute pid; SIGKILL escalation sau `HARD_KILL_MS`; fast-cancel SIGKILL sau 200ms khi user cancel.
39
+ - Lifecycle events có structured shape `{ type, pid, exitCode?, error?, ts }`.
40
+ 6. **Backpressure**: pause child stdout khi vượt 256KB chưa drain.
41
+ 7. **Lazy imports được đánh dấu `// LAZY:`** với lý do cụ thể (giảm ~1.4s import cost ở registration), kèm script `check:lazy-imports` để bảo vệ.
42
+ 8. **Run / task contract guards**: `shouldMergeTaskUpdate` (không cho stale snapshot regress terminal state), monotonic finishedAt, `canTransitionRunStatus`, plan-approval-gating cho mutating tasks.
43
+ 9. **Crash & cancellation paths**: `executeTeamRun` catch-all đảm bảo manifest/tasks chuyển sang terminal khi unhandled error (tránh "running mãi mãi"); `background-runner` có `unhandledRejection` guard ghi `async.failed` trước exit; `parent-guard` để background runner tự chết khi parent chết.
44
+ 10. **Test coverage rất rộng** cho cả happy path và edge cases (yield, atomic-write retry, mergeTaskUpdates, mailbox validation, cancellation, model fallback...).
45
+ 11. **Config**:
46
+ - Schema validate qua TypeBox với fuzzy suggestions cho key sai chính tả.
47
+ - **Sanitize project-level config** (`sanitizeProjectConfig`): loại bỏ những key nhạy cảm (`executeWorkers`, `runtime.mode`, `worktree.setupHook`, `otlp.headers`, `agents.overrides`, …) ra khỏi project config, chỉ chấp nhận từ user config. Đây là phòng thân thiết yếu cho repo bị inject.
48
+
49
+ ---
50
+
51
+ ## 2. Bugs / Issues phát hiện
52
+
53
+ > Phân loại: **HIGH** (có thể gây mất dữ liệu / sai correctness), **MED** (correctness corner case / DX), **LOW** (cải thiện).
54
+
55
+ ### HIGH
56
+
57
+ **H1. `event-log.ts` — silent loss khi vượt `MAX_EVENTS_BYTES` (50MB)**
58
+ ```ts
59
+ // src/state/event-log.ts ~ appendEventInsideLock
60
+ if (fs.existsSync(eventsPath) && fs.statSync(eventsPath).size > MAX_EVENTS_BYTES) {
61
+ logInternalError(...);
62
+ return { ...fullEvent, metadata: { ...(fullEvent.metadata ?? {seq:0,...}), appended: false } };
63
+ }
64
+ ```
65
+ - Vấn đề: event bị bỏ ngay (kể cả terminal event như `run.failed`, `task.completed`) nhưng `appendCounter` cũng không tăng → `compactEventLog` (chỉ chạy mỗi 100 append) không được kích hoạt khi cần nhất. Hậu quả: một khi vượt ngưỡng, log bị "khoá" silently cho đến khi 100 append tiếp theo trigger rotation.
66
+ - Đề xuất: khi gặp ngưỡng, gọi `compactEventLog(eventsPath)` ngay hoặc rotate trước rồi append; đồng thời ưu tiên cho phép terminal event (TERMINAL_EVENT_TYPES) đi qua, vì những event đó là durable contract.
67
+
68
+ **H2. `mailbox.ts` — `appendMailboxMessage` không có lock cross-process**
69
+ ```ts
70
+ fs.appendFileSync(mailboxFile(manifest, complete.direction, complete.taskId), `${JSON.stringify(...)}\n`, "utf-8");
71
+ ```
72
+ - Vấn đề: `appendFileSync` không nguyên tử trên Windows giữa các process. Hai background runners + foreground steer cùng lúc có thể interleave JSON lines → `parseMailboxMessage` skip, message bị mất silently (lỗi report sau qua `validateMailbox`).
73
+ - Đề xuất: dùng pattern `withEventLogLockSync` (đã có sẵn) cho mailbox, hoặc dùng `atomicWriteFile` để rewrite (chậm hơn nhưng nguyên tử). Tối thiểu nên thêm `O_APPEND` nguyên tử trên POSIX (chỉ guarantee tới PIPE_BUF) và lock trên Windows.
74
+
75
+ **H3. `atomic-write.ts` — fallback `writeFileSync` không có symlink guard**
76
+ ```ts
77
+ try { renameWithRetry(tempPath, filePath); }
78
+ catch (renameError) {
79
+ try { fs.writeFileSync(filePath, content, "utf-8"); } // BYPASS symlink guard
80
+ catch { throw renameError; }
81
+ }
82
+ ```
83
+ - Vấn đề: nếu rename fail với EPERM trên Windows, fallback đi trực tiếp `writeFileSync(filePath)` — nếu `filePath` được tạo thành symlink giữa `isSymlinkSafePath` check (top of function) và fallback, write sẽ follow link. Time window nhỏ nhưng có thể bị adversary trên multi-user host.
84
+ - Đề xuất: trước fallback, re-check `fs.lstatSync(filePath).isSymbolicLink()`. Hoặc mở fd với `O_NOFOLLOW` và `O_TRUNC` rồi write.
85
+
86
+ **H4. `team-runner.ts` — Tên hàm `__test__mergeTaskUpdates` bị dùng trong production**
87
+ ```ts
88
+ // Re-export documented as test-only:
89
+ export function __test__mergeTaskUpdates(...) { ... }
90
+ // nhưng được gọi trong executeTeamRunCore:
91
+ tasks = __test__mergeTaskUpdates(tasks, results);
92
+ ```
93
+ - Vấn đề: convention `__test__` ngụ ý chỉ test mới import; thực ra đây là core merge logic của runner. Một dev khác có thể "dọn" helper này hoặc thay đổi behavior nghĩ rằng chỉ ảnh hưởng test → silent regression.
94
+ - Đề xuất: đổi tên `mergeTaskUpdatesPreservingTerminal()` (hoặc tương tự), giữ `__test__mergeTaskUpdates` làm alias export-only cho test, ghi comment.
95
+
96
+ ### MED
97
+
98
+ **M1. `task-runner.ts` — `transcriptPath` reused across model fallback attempts**
99
+ - Mỗi attempt append vào cùng file transcript. `parsePiJsonOutput(fs.readFileSync(transcriptPath, "utf-8"))` parse toàn bộ → final text/usage có thể mixed giữa attempts. `resultArtifact.content` lấy `parsedOutput?.finalText` có thể là final của attempt 1 (đã fail) nếu attempt 2 không có message_end hợp lệ.
100
+ - Đề xuất: hoặc dùng `transcripts/${task.id}.attempt-${i}.jsonl` per attempt, hoặc clear file đầu mỗi attempt nếu chính sách là "last attempt wins".
101
+
102
+ **M2. `task-runner.ts` — read toàn bộ transcript vào memory cho `transcriptArtifact`**
103
+ ```ts
104
+ content: fs.readFileSync(transcriptPath, "utf-8"),
105
+ ```
106
+ - Với task chạy lâu, transcript có thể vài chục MB. Cộng với việc compactChildPiEvent đã giảm size, nhưng vẫn unbounded. `MAX_CAPTURE_BYTES` chỉ áp dụng cho `stdout/stderr` in-memory, không cho transcript on-disk.
107
+ - Đề xuất: cap transcript file size (rotate khi vượt ngưỡng) hoặc artifact dùng reference (đường dẫn) thay vì copy nội dung.
108
+
109
+ **M3. `cleanup.ts` — `fs.statSync(worktreePath).isDirectory()` không guard race**
110
+ ```ts
111
+ for (const entry of fs.readdirSync(worktreeRoot)) {
112
+ const worktreePath = path.join(worktreeRoot, entry);
113
+ if (!fs.statSync(worktreePath).isDirectory()) continue;
114
+ ```
115
+ - Nếu entry bị xóa giữa `readdirSync` và `statSync`, throw uncaught.
116
+ - Đề xuất: bọc `try { fs.statSync... } catch { continue; }` hoặc dùng `fs.readdirSync(worktreeRoot, { withFileTypes: true })` rồi `entry.isDirectory()`.
117
+
118
+ **M4. `worktree-manager.ts` — `runSetupHook` parse JSON chỉ từ dòng cuối**
119
+ ```ts
120
+ const lastLine = lines[lines.length - 1] ?? trimmed;
121
+ const parsed = JSON.parse(lastLine);
122
+ ```
123
+ - Nếu hook xuất multi-line JSON (pretty-print) thì chỉ parse được dòng cuối → silently mất `syntheticPaths`. Đã có log warning, nhưng silent về phía caller.
124
+ - Đề xuất: thử parse `trimmed` trước, fallback last-line. Hoặc đặt protocol rõ ràng (one-line JSON, terminator marker).
125
+
126
+ **M5. `worktree-manager.ts` — `linkNodeModulesIfPresent` không cảnh báo khi `symlinkSync` fail**
127
+ ```ts
128
+ try { fs.symlinkSync(...); return true; } catch { return false; }
129
+ ```
130
+ - Trên Windows không có quyền tạo symlink (yêu cầu SeCreateSymbolicLinkPrivilege), fail im lặng, agent chạy mà thiếu `node_modules` — có thể fail moduleResolution nhưng caller không biết.
131
+ - Đề xuất: log lý do fail (đặc biệt cho Windows non-admin) qua `logInternalError`, hoặc trả về `{ linked, reason }`.
132
+
133
+ **M6. `child-pi.ts` — `forcedFinalDrain` ép `exitCode: 0`**
134
+ ```ts
135
+ const finalExitCode = forcedFinalDrain && !timeoutError ? 0 : exitCode;
136
+ ```
137
+ - Logic này (đã comment giải thích) chuyển một số exit ≠ 0 thành 0 sau khi child gửi final assistant event. Edge case: child crash trong cleanup sau final event → vẫn report success. Có thể che giấu memory leak hoặc crash trong child Pi.
138
+ - Đề xuất: thêm telemetry/metric đếm số lần `forcedFinalDrain → 0` để phát hiện regression. Hiện tại chỉ có lifecycle event "final_drain" nhưng không có metric đếm conversion.
139
+
140
+ **M7. `background-runner.ts` — `process.exit(130)` trong interrupt guard không await flush**
141
+ ```ts
142
+ if (last?.type === "interrupt" && last?.acknowledged !== true) {
143
+ appendEvent(...);
144
+ process.exit(130);
145
+ }
146
+ ```
147
+ - `process.exit` chạy `'exit'` handler nhưng không await async ops (e.g., `appendEventBuffered` Promise đang chờ). `flushEventLogBuffer` đăng ký trên `'exit'` là sync nên OK, nhưng `terminateLiveAgentsForRun` thì không. Có thể leak live agent.
148
+ - Đề xuất: `await terminateLiveAgentsForRun(...)` rồi mới exit, hoặc dùng `process.exitCode = 130` + return để cleanup chạy bình thường.
149
+
150
+ **M8. `state-store.ts` — manifest cache TTL invariant**
151
+ - Cache key là `stateRoot`, TTL 5 phút. Path validation phòng trường hợp manifest paths đổi. Nhưng nếu file mtime + size không đổi (extremely rare nhưng có thể với atomic-write coalesced khi same size & content), cache phục vụ stale content.
152
+ - Đề xuất: thêm `contentHash` (cheap để stat → fingerprint kiểu first 32 bytes) trong cache key, hoặc invalidate cache trong `atomicWriteJsonCoalesced` flush callback.
153
+
154
+ **M9. `event-log.ts` — `sequenceCache` không invalidated khi file truncate ngoài**
155
+ - Nếu external tool truncate `events.jsonl` (rotate manual), cached `seq` vẫn cao, làm `nextSequence` sinh seq sai (đã có fallback: `cached.size === stat.size`). OK với same-size race, nhưng nếu truncate xảy ra giữa `statSync` và `appendFileSync`, hai append sẽ có cùng seq.
156
+ - Đề xuất: persistSequence hiện đã dùng atomic write, có thể trust nó trong race. Test integration cho external truncate.
157
+
158
+ **M10. `runtime-resolver` / config — `executeWorkers=false` default fallback path**
159
+ - `handleResume` có logic phức tạp re-evaluate `runtime.mode` khi resume scaffold runs. Logic 3-cách (`resumeManifest.runtimeResolution?.safety === "explicit_dry_run"` + env var checks) dễ dẫn đến edge case nơi user expect actual workers nhưng resume vẫn scaffold. Khó test.
160
+ - Đề xuất: refactor thành state machine rõ ràng `resolveResumeRuntime({ original, override, env })` với unit test full truth table.
161
+
162
+ ### LOW
163
+
164
+ - **L1. `package.json` thiếu `lint` script**; `AGENTS.md` global có quy ước `eslint --max-warnings=0`. Hiện chỉ dựa vào `tsc strict`. Cân nhắc thêm ESLint hoặc Biome.
165
+ - **L2. Many `JSON.stringify(value, null, 2)` cho metadata artifact**. Pretty-printing 50+ artifact/task tốn I/O. Cân nhắc minified JSON cho metadata, pretty chỉ cho summary/progress mà user đọc.
166
+ - **L3. `task-runner.ts` tạo ~13 artifacts cho mỗi task** (prompt, result, inputs, coordination, skill, packet, verification, startup, permission, capability, prompt-pipeline, log, transcript, diff, diff-stat, output-validation). Mỗi cái là một `atomicWriteFile` syscall. Trong run lớn (50+ tasks), giảm xuống sub-artifacts hợp nhất sẽ giúp giảm I/O đáng kể.
167
+ - **L4. `registerYieldTool()` chạy ở module top-level** (`task-runner.ts` dòng 35). Side-effect khi import — nếu module bị import 2 lần (e.g., jiti vs strip-types), `subprocessToolRegistry` có thể duplicate. Kiểm tra `subprocess-tool-registry.ts` xem có idempotent không.
168
+ - **L5. `atomic-write.ts` `atomicWriteJsonCoalesced`** — API có caveat đáng kể (read-after-write trong buffer window đọc stale content). Risk surface lớn nếu future dev quên gọi `flushPendingAtomicWrites()`. Cân nhắc thêm read API riêng `readJsonFileWithCoalesceFlush()`.
169
+ - **L6. Cancellation paths không có metric đếm**. Đã có observability events nhưng không có gauge số task cancelled per run.
170
+ - **L7. `management.ts` `handleUpdate` rename+write** sequence không có rollback nếu writeFileSync fail sau rename (backup tồn tại, nhưng user phải manually restore). Có thể wrap trong try/catch + auto-restore from backup.
171
+ - **L8. `child-pi.ts` mock paths đọc env `PI_TEAMS_MOCK_CHILD_PI`** — nên có guard không cho prod accidentally bật (kiểm tra `process.env.NODE_ENV === "test"` hoặc test-flag rõ ràng).
172
+ - **L9. `worktree-manager.ts` `findGitRoot` throws** nếu cwd không phải git repo. `prepareTaskWorkspace` gọi nó trước khi check workspaceMode; thực ra check workspaceMode đầu hàm rồi, OK. Nhưng error message từ git ("not a git repository") sẽ propagate lên user — không user-friendly.
173
+ - **L10. Naming `crewRoot` vs `.crew/` vs `.pi/teams/`** đã có doc nhưng dễ confuse. `projectCrewRoot` có cả ba branch (existing `.crew` → `.crew`; existing `.pi` → `.pi/teams`; else → `.crew`). Test có cover nhưng dev mới khi xem code dễ nghĩ nhầm.
174
+ - **L11. Một số `let task: TeamTaskState = ...` rồi reassign nhiều lần trong `task-runner.ts`**. Hard to reason. Cân nhắc refactor thành reducer pattern.
175
+ - **L12. `update-references-for-rename` chỉ cập nhật team→agent và team.defaultWorkflow**, không cover workflow→step.role hay agent references trong test fixtures. Comment đã ghi nhận. Vẫn nên fix để rename an toàn.
176
+
177
+ ---
178
+
179
+ ## 3. Security review
180
+
181
+ | Mục | Trạng thái | Ghi chú |
182
+ |---|---|---|
183
+ | Path traversal | OK | `assertSafePathId`, `resolveContainedPath`, `resolveRealContainedPath` phủ khá đầy đủ. |
184
+ | Symlink escape | OK (corner case H3) | `O_NOFOLLOW`, `lstatSync`, post-open `fstatSync`. Có 1 fallback path bỏ check (H3). |
185
+ | Secret leak | OK | Redaction áp dụng đầu vào event log, transcript, mailbox, artifact. Env sanitization trước khi spawn child. |
186
+ | Code injection via setup hook | Mitigated | `runSetupHook` validate file tồn tại, dùng `shell: false`, allow-list env, timeout 30s. Nhưng vẫn execute user-provided code. Phải tin user. |
187
+ | Untrusted project config | OK | `sanitizeProjectConfig` strip key nhạy cảm trước khi merge. |
188
+ | Process tree leak (zombie child Pi) | OK | `terminateActiveChildPiProcesses` + `parent-guard` + Windows `taskkill /T /F`. |
189
+ | DoS qua concurrency | OK | Default hard-cap; `allowUnboundedConcurrency=true` cần explicit opt-in + emit event. |
190
+ | Event log injection | Mitigated | JSON.stringify mỗi line; readEvents skip parse error. Có rủi ro JSON-line corrupted vì `appendFileSync` race (H2 trong mailbox, nhưng event log có lock). |
191
+ | Dependency surface | Nhỏ | Chỉ runtime deps: typebox, cli-highlight, diff, jiti. |
192
+
193
+ Tóm lại: security posture **tốt**. Vấn đề lớn nhất là H2 (mailbox không lock) — có thể bị stale state nếu nhiều process race.
194
+
195
+ ---
196
+
197
+ ## 4. Performance review
198
+
199
+ - **Atomic write coalescer** (50ms window) đã giảm I/O cho high-frequency state writes.
200
+ - **Manifest cache** với mtime+size key tránh re-parse khi không đổi.
201
+ - **Lazy import boundaries** giảm import cost ~1.4s.
202
+ - **`projectRootCache` TTL 30s** giảm 14 `existsSync` × ancestor levels mỗi render tick.
203
+
204
+ Nóng còn tiềm năng tối ưu:
205
+ 1. Mỗi task hoàn thành sinh ~13 artifacts (L3). 50 tasks = 650 atomic writes cho metadata. Cân nhắc batch.
206
+ 2. `progress.md` và `summary.md` được write lại nhiều lần per batch (writeProgress trong loop). Coalesce ổn nhưng có thể dùng `atomicWriteJsonCoalesced`.
207
+ 3. `parsePiJsonOutput(fs.readFileSync(transcriptPath))` chạy mỗi attempt, parse full transcript. Stream parsing rẻ hơn cho transcript lớn.
208
+ 4. `aggregateUsage(tasks)` chạy O(n) trên tasks mỗi summary write.
209
+
210
+ ---
211
+
212
+ ## 5. DX / Maintainability
213
+
214
+ | Aspect | Note |
215
+ |---|---|
216
+ | TS strict | OK, `noImplicitAny` enforced. |
217
+ | Naming `__test__*` | Có lẫn lộn giữa pure test util và production helper (H4). |
218
+ | File size | `team-runner.ts` (694 dòng), `task-runner.ts` (440+ dòng), `register.ts` (1k+ dòng), `live-session-runtime.ts` (~750 dòng) đều > 500 dòng. AGENTS.md đã nhắc "prefer small modules". |
219
+ | Comment quality | Tốt — có "WHY" markers, version tags (`// 2.10`, `// H4`, `// 3.1`). |
220
+ | Test layout | `test/unit/*.test.ts` + `test/integration/*.test.ts`. Concurrency hợp lý. |
221
+ | Hard-coded magic numbers | Đã centralize vào `config/defaults.ts` cho phần lớn. |
222
+ | Error reporting | `logInternalError` consistent — best-effort, không throw. |
223
+ | Docs sync | `docs/architecture.md` khớp với code (trừ một số next-upgrade-roadmap chưa implement). |
224
+
225
+ ---
226
+
227
+ ## 6. Test-matrix gap (ứng viên thêm test)
228
+
229
+ - Cross-process race trên mailbox append (H2).
230
+ - Event log overflow recovery (H1) — đảm bảo terminal event vẫn được persist khi vượt 50MB.
231
+ - `forcedFinalDrain` không che giấu real child crash (M6).
232
+ - Resume with mixed `runtime.mode` overrides (M10).
233
+ - Atomic-write coalesced + read-after-write within window — đảm bảo doc behavior matches reality.
234
+ - `linkNodeModulesIfPresent` Windows non-admin fallback (M5).
235
+ - `runSetupHook` multi-line JSON output (M4).
236
+
237
+ ---
238
+
239
+ ## 7. Đề xuất ưu tiên (sorted)
240
+
241
+ 1. **Fix H1** (event-log overflow): rotate ngay khi vượt ngưỡng + ưu tiên terminal events.
242
+ 2. **Fix H2** (mailbox lock): áp dụng `withEventLogLockSync` pattern cho mailbox append.
243
+ 3. **Fix H3** (atomic-write symlink TOCTOU): re-check lstat trước fallback `writeFileSync`.
244
+ 4. **Fix H4** (rename `__test__mergeTaskUpdates` → `mergeTaskUpdates`, giữ alias).
245
+ 5. **M1/M2** transcript per-attempt + cap size.
246
+ 6. **M3** race-safe `statSync` trong cleanup.
247
+ 7. **M6** thêm metric `crew.child.final_drain_force_zero_total`.
248
+ 8. **L1** thêm ESLint hoặc Biome cho consistency (AGENTS.md global yêu cầu).
249
+ 9. **L3** batch artifact writes cho metadata.
250
+ 10. **L12** mở rộng `updateReferencesForRename` cho workflow→step + agent references.
251
+
252
+ ---
253
+
254
+ ## 8. Verification
255
+
256
+ ```
257
+ npx tsc --noEmit → PASS
258
+ node --experimental-strip-types -e "..." → PASS (strip-types import ok)
259
+ node --test test/unit/*.test.ts → 1596 pass / 2 skip / 0 fail / 90s
260
+ ```
261
+
262
+ Không có lint command trong project (chỉ `tsc strict`), không tìm thấy file `.eslintrc*`.
263
+
264
+ ---
265
+
266
+ ## 9. Kết luận
267
+
268
+ `pi-crew` là một codebase **trưởng thành, kỷ luật cao**, có nhiều lớp phòng thân chống TOCTOU, race, và crash mid-write. Test coverage rộng, architecture rõ ràng. Các vấn đề tìm thấy chủ yếu là edge-case correctness và hardening, không có lỗ hổng nghiêm trọng nào ở mức "broken core flow".
269
+
270
+ **Khuyến nghị**: ưu tiên fix H1–H4 và mở rộng test cho cross-process race (mailbox + event-log overflow). Tiếp theo là cân nhắc thêm linter, batch metadata artifact writes, và refactor một số orchestrator file lớn (`register.ts`, `team-runner.ts`, `live-session-runtime.ts`) thành sub-modules.
271
+