pi-crew 0.9.27 → 0.9.29
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.
- package/CHANGELOG.md +15 -0
- package/dist/build-meta.json +164 -115
- package/dist/index.mjs +986 -555
- package/dist/index.mjs.map +4 -4
- package/docs/REVIEW-FINDINGS-2026-07-CORE.md +139 -0
- package/docs/REVIEW-FINDINGS-2026-07.md +125 -0
- package/docs/bugs/bug-quota-display-truncation.md +223 -0
- package/docs/stories/README.md +3 -1
- package/docs/stories/US-DEPS-major-upgrade.md +62 -0
- package/package.json +4 -4
- package/src/agents/agent-config.ts +2 -0
- package/src/agents/discover-agents.ts +4 -0
- package/src/config/config.ts +4 -0
- package/src/extension/crew-vibes/config.ts +1 -1
- package/src/extension/crew-vibes/figures.ts +1 -1
- package/src/extension/crew-vibes/footer.ts +292 -0
- package/src/extension/crew-vibes/index.ts +74 -70
- package/src/extension/crew-vibes/provider-usage.ts +119 -53
- package/src/extension/team-tool.ts +11 -0
- package/src/prompt/prompt-runtime.ts +65 -0
- package/src/runtime/child-pi.ts +58 -43
- package/src/runtime/cross-extension-rpc.ts +1 -1
- package/src/runtime/pi-args.ts +2 -0
- package/src/runtime/post-exit-stdio-guard.ts +22 -4
- package/src/runtime/stale-reconciler.ts +9 -4
- package/src/runtime/task-runner.ts +1 -0
- package/src/runtime/team-runner.ts +11 -16
- package/src/state/atomic-write.ts +46 -19
- package/src/state/event-log.ts +77 -24
- package/src/state/mailbox.ts +6 -3
- package/src/ui/pi-ui-compat.ts +11 -0
- package/src/utils/conflict-detect.ts +9 -3
- package/src/utils/paths.ts +7 -1
- package/src/worktree/cleanup.ts +19 -0
- package/src/worktree/worktree-manager.ts +145 -34
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# pi-crew — Core Engine Review (2026-07)
|
|
2
|
+
|
|
3
|
+
Review **bộ core** (scheduler, vòng đời child process, state durability/concurrency, worktree isolation, conflict detection) — KHÔNG phải lớp ngoài (lint/deps/hygiene, xem `docs/REVIEW-FINDINGS-2026-07.md`).
|
|
4
|
+
|
|
5
|
+
- Phiên bản: **v0.9.28** · Node **v22.14.0** · Windows (win32 10.0.22631)
|
|
6
|
+
- Phạm vi đọc sâu (~9K dòng core): `runtime/team-runner.ts`, `runtime/task-runner.ts`, `runtime/child-pi.ts`, `runtime/live-session-runtime.ts`, `runtime/background-runner.ts`, `runtime/goal-loop-runner.ts`, `runtime/stale-reconciler.ts`, `state/state-store.ts`, `state/event-log.ts`, `state/atomic-write.ts`, `state/mailbox.ts`, `worktree/worktree-manager.ts`, `worktree/cleanup.ts`, `utils/conflict-detect.ts`, `config/config.ts`.
|
|
7
|
+
- Phương pháp: 4 luồng deep-read song song + **verify trực tiếp** từng finding trọng yếu bằng đọc lại source (trích dẫn file:line).
|
|
8
|
+
|
|
9
|
+
## Legend
|
|
10
|
+
|
|
11
|
+
| Ký hiệu | Nghĩa |
|
|
12
|
+
|---------|-------|
|
|
13
|
+
| ✅ Verified | Đã đọc trực tiếp source, xác nhận cơ chế + file:line |
|
|
14
|
+
| 🔬 Cần repro | Cơ chế hợp lý qua đọc code, **chưa kết luận** — cần dựng test repro để chốt |
|
|
15
|
+
| 🔴 HIGH · 🟡 MEDIUM · ⚪ LOW | Mức nghiêm trọng |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Nhóm A — Findings đã VERIFY trực tiếp trên source
|
|
20
|
+
|
|
21
|
+
### C1 🔴 ✅ Conflict không phát hiện được trên file CRLF (Windows)
|
|
22
|
+
|
|
23
|
+
- **File:** `src/utils/conflict-detect.ts:190, 101, 202`
|
|
24
|
+
- **Bằng chứng:** scanner `const lines = text.split("\n");` (190); so khớp chính xác `if (line === SEPARATOR)` với `SEPARATOR = "======="` (26/101); `matchMarker` yêu cầu `if (line.charCodeAt(prefix.length) !== 32 /* space */) return null;` (202). Trên file CRLF, git ghi `=======\r\n` → sau `split("\n")` thành `"=======\r"` ≠ `"======="`; closer `>>>>>>>\r` có `charCodeAt(7)===13` (CR) ≠ 32.
|
|
25
|
+
- **Bất đối xứng chứng minh:** hàm write-path (`expandContentTokens`) tại dòng 429 *có* strip CR: `const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;` — nhưng scanner (190) thì không.
|
|
26
|
+
- **Tác động:** conflict trong file CRLF không được phát hiện (false negative) → agent tưởng merge sạch và commit code còn `<<<<<<<`/`=======`/`>>>>>>>`. Project có target Windows (CI matrix + nhiều xử lý Windows) nên đây là lỗi thực tế.
|
|
27
|
+
- **Fix đề xuất:** strip trailing `\r` mỗi dòng trong scanner (mirror dòng 429), giữ CR khi re-splice để round-trip byte-accurate. Có thể mở rộng match ≥7 ký tự marker (`^<{7,}(\s|$)`) để chịu `merge.conflictMarkerSize > 7`.
|
|
28
|
+
- **Verify:** unit test feed buffer conflict với `\r\n` → assert 1 block đúng `startLine/separatorLine/endLine`; test LF cũ vẫn pass.
|
|
29
|
+
|
|
30
|
+
### C2 🔴 ✅ Run toàn task lỗi vẫn bị đánh dấu `completed`
|
|
31
|
+
|
|
32
|
+
- **File:** `src/runtime/stale-reconciler.ts:66-71`
|
|
33
|
+
- **Bằng chứng:** `checkResultFile` tính `allTerminal` gồm cả `failed`/`cancelled`; khi true: `manifest.status = "completed"; saveRunManifest(manifest);` — gán trực tiếp, bỏ qua `canTransitionRunStatus`.
|
|
34
|
+
- **Tác động:** run crash mà mọi task `failed`/`cancelled` vẫn được stamp `completed` → báo thành công sai, UI/logic downstream key theo `status==="completed"` hiểu nhầm.
|
|
35
|
+
- **Fix đề xuất:** suy ra status cuối từ outcome task: `completed` chỉ khi không có task `failed`; ngược lại `failed` (hoặc `cancelled` nếu toàn cancelled); đi qua `updateRunStatus`/`canTransitionRunStatus`.
|
|
36
|
+
- **Verify:** test `reconcile` với toàn task `failed` → manifest `failed`; mixed failed+completed → `failed`.
|
|
37
|
+
|
|
38
|
+
### C3 🔴 ✅ Crash-recovery ghi state KHÔNG atomic
|
|
39
|
+
|
|
40
|
+
- **File:** `src/runtime/stale-reconciler.ts:496, 504`
|
|
41
|
+
- **Bằng chứng:** nhánh quét orphan-temp dùng `fs.writeFileSync(tasksPath, JSON.stringify(result.repairedTasks, null, 2))` (496) và `fs.writeFileSync(manifestPath, JSON.stringify(updated, null, 2))` (504) — không temp+rename, không fsync, không lock. Phần còn lại của state layer dùng `atomicWriteJson`.
|
|
42
|
+
- **Tác động:** crash/đọc-đồng-thời giữa lúc ghi → `manifest.json`/`tasks.json` rách → `JSON.parse` fail → run vĩnh viễn không đọc được. Chính đường phục hồi lại corrupt state.
|
|
43
|
+
- **Fix đề xuất:** dùng `atomicWriteJson`/`saveRunManifest`/`saveRunTasks` và bọc read-modify-write trong `withRunLockSync`.
|
|
44
|
+
- **Verify:** test inject mid-write crash (mock `writeFileSync` throw sau N byte) → assert manifest vẫn parse được; assert dùng temp+rename (không để lại `.tmp`).
|
|
45
|
+
|
|
46
|
+
### C4 🔴 ✅ Mất dữ liệu khi reuse worktree "dirty"
|
|
47
|
+
|
|
48
|
+
- **File:** `src/worktree/worktree-manager.ts:626-637`
|
|
49
|
+
- **Bằng chứng:**
|
|
50
|
+
```ts
|
|
51
|
+
const dirtyStatus = git(worktreePath, ["status", "--porcelain"]);
|
|
52
|
+
if (dirtyStatus.trim()) {
|
|
53
|
+
logInternalError("worktree.reused.dirty", ...);
|
|
54
|
+
git(worktreePath, ["checkout", "--", "."]);
|
|
55
|
+
git(worktreePath, ["clean", "-fd"]); // xóa cả file chưa track
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
- **Tác động:** reuse worktree (vd resume sau crash) **hard-discard** thay đổi chưa commit + file untracked, chỉ `logInternalError`, **không có force gate**. Trái AGENTS.md "Worktree cleanup must preserve dirty worktrees unless `force`".
|
|
59
|
+
- **Fix đề xuất:** trước khi discard, commit-to-branch/stash (như `cleanup.ts`) hoặc yêu cầu `force`/opt-in; tối thiểu snapshot diff artifact.
|
|
60
|
+
- **Verify:** test reuse worktree có file untracked + tracked edit → assert thay đổi còn khôi phục được (branch/stash/artifact), không mất trắng.
|
|
61
|
+
|
|
62
|
+
### C5 🟡 ✅ Rò rỉ worktree + branch khi setup-hook/seed lỗi
|
|
63
|
+
|
|
64
|
+
- **File:** `src/worktree/worktree-manager.ts:650-682` (async: 790-821)
|
|
65
|
+
- **Bằng chứng:** `let worktreeCreated = false;` (650) được set `true` (662) nhưng **không bao giờ đọc**. `runSetupHook(...)` (682) + `overlaySeedPaths` chạy *sau* khi worktree/branch đã tạo; catch chỉ dọn khi `git worktree add` fail. `runSetupHook` throw khi hook lỗi; `normalizeSeedPaths` throw khi seed path traversal/absolute/symlink.
|
|
66
|
+
- **Tác động:** hook/seed lỗi để lại worktree dir + branch `pi-crew/<run>/<task>` mồ côi → lần sau vướng guard "already checked out", buộc `cleanup force=true`.
|
|
67
|
+
- **Fix đề xuất:** bọc `runSetupHook`/`linkNodeModules`/`overlaySeedPaths` trong try/catch, khi lỗi chạy `git worktree remove --force` + `git branch -D` (best-effort) trước khi rethrow, gate bằng `worktreeCreated`.
|
|
68
|
+
- **Verify:** test `setupHook` exit non-zero → `prepareTaskWorkspace` throw AND `git worktree list` không còn path, branch bị xóa.
|
|
69
|
+
|
|
70
|
+
### C6 🟡 ✅ `worktree.seedPaths` bị nuốt (tính năng chết)
|
|
71
|
+
|
|
72
|
+
- **File:** `src/config/config.ts:734-745`
|
|
73
|
+
- **Bằng chứng:** `parseWorktreeConfig` chỉ build `setupHook`, `setupHookTimeoutMs`, `linkNodeModules`. `seedPaths` không được parse dù có trong type (`config/types.ts` `seedPaths?: string[]`) và schema (`schema/config-schema.ts`). `worktree-manager` đọc `loadedConfig.config.worktree?.seedPaths ?? []` → luôn `[]`. `updateConfig` re-serialize `parseConfig(current)` → xóa cả `seedPaths` sửa tay.
|
|
74
|
+
- **Tác động:** seedPaths cấu hình toàn cục không bao giờ được overlay vào worktree; feature im lặng không hoạt động.
|
|
75
|
+
- **Fix đề xuất:** thêm `seedPaths: parseStringList(obj.seedPaths)` (validated array) vào `parseWorktreeConfig` + đưa vào điều kiện "some defined".
|
|
76
|
+
- **Verify:** unit `parseConfig({ worktree: { seedPaths: ["a","b"] } })` trả đúng; integration copy seed vào worktree.
|
|
77
|
+
|
|
78
|
+
### C7 🟡 ✅ `withEventLogLockAsync` phá mutual-exclusion
|
|
79
|
+
|
|
80
|
+
- **File:** `src/state/event-log.ts:383-394` (và cleanup `asyncQueues` ~591-609)
|
|
81
|
+
- **Bằng chứng:**
|
|
82
|
+
```ts
|
|
83
|
+
const prev = asyncLocks.get(queueKey) ?? Promise.resolve();
|
|
84
|
+
const next = prev.then(async () => { await fn(); });
|
|
85
|
+
asyncLocks.set(queueKey, next);
|
|
86
|
+
try { await next; } finally { asyncLocks.delete(queueKey); } // xóa vô điều kiện
|
|
87
|
+
```
|
|
88
|
+
- **Tác động:** khi ≥3 flush chồng lấn, `finally` của caller cũ `delete(queueKey)` xóa nhầm `next` mới (tail đang chờ); caller kế đọc `undefined` → chạy song song với batch trước → `appendEventBatchInsideLock` xen kẽ: trùng `seq`, rotation đua append → mất event. Reachable khi buffered throughput cao.
|
|
89
|
+
- **Fix đề xuất:** compare-and-delete: `if (asyncLocks.get(queueKey) === next) asyncLocks.delete(queueKey);` — áp dụng cả cleanup `asyncQueues`.
|
|
90
|
+
- **Verify:** test bắn 3+ flush chồng lấn với `fn` đo "in critical section" counter → assert ≤1 và `seq` unique/monotonic.
|
|
91
|
+
|
|
92
|
+
### C8 🟡 ✅ Steer "wrap up" qua stdin là dead code + log spam
|
|
93
|
+
|
|
94
|
+
- **File:** `src/runtime/child-pi.ts:388, 1139-1166`
|
|
95
|
+
- **Bằng chứng:** spawn `stdio: ["ignore", "pipe", "pipe"]` (388) → `child.stdin` là `null`; block `if (child.stdin?.writable) { ... child.stdin.write(steerPayload) }` (1139-1146) không bao giờ chạy; nhánh `else` `logInternalError("child-pi.steer-not-writable", ...)` (1165) log **mỗi lần** chạm soft-limit.
|
|
96
|
+
- **Tác động:** soft-limit steer không tới worker → worker luôn chạy tới `maxTurns+graceTurns` rồi hard-abort (lãng phí turn/token, mất cơ hội wrap-up); log nhiễu.
|
|
97
|
+
- **Fix đề xuất:** hoặc spawn `stdio:["pipe","pipe","pipe"]` + xử lý backpressure/EPIPE nếu muốn steer qua stdin; hoặc xóa block stdin, chỉ dựa file-based steering (`PI_CREW_STEERING_FILE`) + hard-abort và bỏ log `steer-not-writable`.
|
|
98
|
+
- **Verify:** test assert (a) steer được ghi khi soft-limit, hoặc (b) không có log `steer-not-writable`.
|
|
99
|
+
|
|
100
|
+
### C9 🟡 ✅ `cleanup` auto-commit dirty không cần `force`
|
|
101
|
+
|
|
102
|
+
- **File:** `src/worktree/cleanup.ts:130-207`
|
|
103
|
+
- **Bằng chứng:** nhánh `if (dirty)` luôn chạy `git add -A` → `git commit` → `git branch` → `["worktree","remove","--force",...]` bất kể `options.force`; chỉ nhánh non-dirty mới `if (options.force) args.push("--force")`.
|
|
104
|
+
- **Tác động:** `team cleanup runId=X` (không force) stage mọi file untracked (`-A`) và commit vào branch mới → rủi ro commit secrets/build artifacts; trái contract "preserve dirty unless force".
|
|
105
|
+
- **Fix đề xuất:** gate commit-and-remove-dirty sau `options.force` (hoặc option `commitDirty`); khi không force → giữ worktree + chỉ emit diff artifact.
|
|
106
|
+
- **Verify:** test `cleanupRunWorktrees(manifest, { force: false })` trên worktree dirty → assert được preserve (không commit/remove) và nằm trong `result.preserved`.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Nhóm B — Cần dựng test repro trước khi kết luận (🔬)
|
|
111
|
+
|
|
112
|
+
Cơ chế đã đọc thấy nhưng **chưa chốt**; mỗi item kèm kế hoạch repro.
|
|
113
|
+
|
|
114
|
+
| ID | Mức | File:line | Nghi vấn | Repro plan |
|
|
115
|
+
|----|-----|-----------|----------|------------|
|
|
116
|
+
| B1 | 🔴 | `runtime/team-runner.ts:773-786` | catch downgrade dựa `refreshTaskGraphQueues(input.tasks)`; nếu `input.tasks` là snapshot start (còn `queued`) → late-throw ghi đè task đã `completed` thành `failed` | Run 2 task: A completed+persist; ép `executeTeamRunCore` throw trước khi return; assert `tasks.json` giữ A `completed` |
|
|
117
|
+
| B2 | 🟡 | `runtime/task-runner.ts:667-691` | model-fallback re-resolve tính `alt` nhưng loop thoát → không bao giờ thử alt model | Chain 1 model trả 429 retryable + re-resolve ra candidate mới → assert `runChildPi` gọi lần 2 với alt |
|
|
118
|
+
| B3 | 🟡 | `runtime/team-runner.ts:1400-1408` | retry re-chạy `runTeamTask` lần `maxAttempts+1` khi task *throw* | Mock `runTeamTask` luôn throw → assert gọi đúng `maxAttempts` lần, task trả `failed` |
|
|
119
|
+
| B4 | 🟡 | `runtime/child-pi.ts:1461-1518` | post-exit stdio guard gắn trong handler `exit` → listener không chạy → guard no-op → risk treo | Fake ChildProcess với stdout/stderr không emit `end/close`; emit `exit`; assert `destroy` được gọi trong `hardMs` |
|
|
120
|
+
| B5 | 🟡 | `runtime/child-pi.ts:985` | `AbortSignal` đã aborted trước gọi vẫn spawn child | Gọi `runChildPi` với signal đã abort → assert không spawn, kết quả `cancelled` |
|
|
121
|
+
| B6 | 🟡 | `runtime/child-pi.ts:88-113` | `taskkill` (Windows) spawn không có listener `error` → uncaught crash parent | Windows: stub PATH để `taskkill` không resolve → assert log internal error, không crash |
|
|
122
|
+
| B7 | 🟡 | `state/event-log.ts` (sync vs async) | 2 đường append cùng `events.jsonl` dùng lock khác nhau, cross-process không lock → trùng `seq`/torn lines | 2 process hammer `appendEventAsync`/`appendEvent` payload >4KB → assert mọi line parse, `seq` unique |
|
|
123
|
+
| B8 | 🟡 | `state/mailbox.ts:475` | rotation chạy ngoài append lock → truncate mất message | Stress 2 loop `appendMailboxMessage` vượt ngưỡng rotate → assert đọc lại đúng số message |
|
|
124
|
+
| B9 | 🟡 | `state/atomic-write.ts:266-276` | Windows `unlink(dest)` trước `rename` mở cửa sổ ENOENT/mất manifest | Windows: reader lặp trong lúc overwrite → assert không thấy ENOENT; giả lập rename fail sau unlink → file cũ còn nguyên |
|
|
125
|
+
| B10 | 🟡 | `state/state-store.ts:565-599` | `updateRunStatus` check-then-write không lock → lost update | 2 `updateRunStatus` từ cùng base manifest → assert đúng 1 thành công, transition hợp lệ |
|
|
126
|
+
| B11 | 🟡 | `worktree/worktree-manager.ts:601-620` | `sanitizeBranchPart` va chạm (vd `foo.bar` vs `foo-bar`) → 2 task dùng chung worktree | 2 task ID khác nhau 1 ký tự bị strip → assert worktree/branch khác nhau hoặc lỗi rõ ràng |
|
|
127
|
+
| B12 | 🟡 | `worktree/worktree-manager.ts:410-430` | vài lệnh git (`for-each-ref`, `worktree prune`) không dùng `gitEnv()` sanitize → rò rỉ secrets | Assert child env của các call site không có `GIT_*`/secret vars |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Ưu tiên đề xuất
|
|
132
|
+
|
|
133
|
+
1. **P0 (correctness / mất dữ liệu):** C1, C4, C3, C2.
|
|
134
|
+
2. **P1 (đúng đắn / rò rỉ):** C7, C5, C6, C9, B1.
|
|
135
|
+
3. **P2:** C8, nhóm child-pi lifecycle (B4/B5/B6), mailbox/atomic-write Windows (B8/B9).
|
|
136
|
+
|
|
137
|
+
**Fix đầu tiên đã chọn: C1 (CRLF conflict false-negative).** Phác thảo: trong `scanFileForConflictsSync`/`scanConflictLines`, strip trailing `\r` mỗi dòng trước khi so khớp marker/separator (mirror logic dòng 429), và giữ `\r` khi ghi lại để round-trip byte-accurate; thêm unit test CRLF + giữ test LF cũ pass; chạy `npm run typecheck` + `test/unit/conflict-detect.test.ts` + `delta-conflict.test.ts`.
|
|
138
|
+
|
|
139
|
+
> Nhóm B: theo quyết định 2026-07, sẽ **dựng test repro** để xác nhận trước khi kết luận/sửa. Chưa treat như confirmed.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# pi-crew — Review & Upgrade Findings (2026-07)
|
|
2
|
+
|
|
3
|
+
Tổng hợp review toàn bộ project và các cơ hội nâng cấp/cải thiện, kèm **bằng chứng verification thực tế** (đã chạy lệnh, không suy đoán).
|
|
4
|
+
|
|
5
|
+
- Phiên bản đánh giá: **v0.9.28**
|
|
6
|
+
- Môi trường kiểm chứng: Node **v22.14.0**, npm **10.9.2**, Windows (win32 10.0.22631)
|
|
7
|
+
- Quy mô: **453** file nguồn (`src/**/*.ts`, ~**93.206** dòng), **652** file test
|
|
8
|
+
- Quy ước mức độ: 🔴 Cao · 🟡 Trung bình · ⚪ Thấp/Hygiene
|
|
9
|
+
- Quy ước effort: S (<0.5 ngày) · M (0.5–2 ngày) · L (>2 ngày)
|
|
10
|
+
|
|
11
|
+
> **Review bộ core** (scheduler, child-process lifecycle, state durability/concurrency, worktree, conflict detection): xem `docs/REVIEW-FINDINGS-2026-07-CORE.md` — 9 finding đã verify trực tiếp + 12 finding cần repro.
|
|
12
|
+
|
|
13
|
+
## Baseline health (đã verify)
|
|
14
|
+
|
|
15
|
+
| Kiểm tra | Lệnh | Kết quả | Bằng chứng |
|
|
16
|
+
|----------|------|---------|------------|
|
|
17
|
+
| Typecheck | `npm run typecheck` | ✅ PASS (16.7s) | `tsc --noEmit` OK + `strip-types import ok` |
|
|
18
|
+
| Unit test (mẫu) | `test/unit/{token-counter,conflict-detect,model-fallback}.test.ts` | ✅ 80/80 pass (0.67s) | node:test summary `# fail 0` |
|
|
19
|
+
| Unit suite (full) | `npm run test:unit` | ⚠️ chậm — vượt 420s cục bộ (timeout) | ~5800 test, budget CI 15 phút |
|
|
20
|
+
| Lint | `npm run lint` | ❌ FAIL — 6 lỗi (FIXABLE) | `assist/source/organizeImports` |
|
|
21
|
+
| Deps | `npm outdated` | ⚠️ 5 gói lỗi thời | xem F5/F6 |
|
|
22
|
+
|
|
23
|
+
## Bảng tổng hợp findings
|
|
24
|
+
|
|
25
|
+
| ID | Vấn đề | Mức | Effort | Khu vực |
|
|
26
|
+
|----|--------|-----|--------|---------|
|
|
27
|
+
| F1 | `npm run lint`/`npm run ci` fail — 6 lỗi import-sort | 🟡 | S | Chất lượng |
|
|
28
|
+
| F2 | `.gitignore` có dòng nối sai + trùng lặp | 🟡 | S | Hygiene/Git |
|
|
29
|
+
| F3 | CI không gate `lint`/`format`/`check:*` (khác local `ci`) | 🟡 | S | CI |
|
|
30
|
+
| F4 | Artifact bug: thư mục `undefined/.pi/teams/...` | 🟡 | M | Runtime/paths |
|
|
31
|
+
| F5 | Deps minor an toàn cần nâng (biome/tsx/esbuild) | ⚪ | S | Deps |
|
|
32
|
+
| F6 | Deps major cần plan (diff 5→9, TypeScript 7) | ⚪ | M-L | Deps |
|
|
33
|
+
| F7 | Unit suite chậm (>7 phút cục bộ) — DX | ⚪ | M | Test/DX |
|
|
34
|
+
| F8 | API `@deprecated` còn tồn tại — dọn ở bản major | ⚪ | S | Maintainability |
|
|
35
|
+
| F9 | Thư mục rác ở root (đã dọn 2026-07) | ⚪ | S | Hygiene |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🟡 Mức Trung bình
|
|
40
|
+
|
|
41
|
+
### F1 — `npm run lint` fail với 6 lỗi import-sort
|
|
42
|
+
|
|
43
|
+
**Bằng chứng:** `npm run lint` → exit 1, `Found 6 errors`, tất cả gắn nhãn `FIXABLE` (`assist/source/organizeImports`).
|
|
44
|
+
|
|
45
|
+
File liên quan:
|
|
46
|
+
- `src/extension/crew-vibes/index.ts`
|
|
47
|
+
- `src/prompt/prompt-runtime.ts`
|
|
48
|
+
- `src/runtime/cross-extension-rpc.ts`
|
|
49
|
+
- `test/unit/api-key-scoping.test.ts`
|
|
50
|
+
- `test/unit/rpc-hmac-auth.test.ts`
|
|
51
|
+
- `test/unit/token-counter.test.ts`
|
|
52
|
+
|
|
53
|
+
**Tác động:** `npm run ci` (pipeline "full CI" ghi trong CLAUDE.md) fail cục bộ ở bước `lint`. CI trên GitHub không bắt (xem F3) nên lỗi tồn tại âm thầm.
|
|
54
|
+
|
|
55
|
+
**Giải pháp:** `biome check --write` (auto-fix reorder import). **Verify:** `npm run lint` → 0 errors.
|
|
56
|
+
|
|
57
|
+
### F2 — `.gitignore` có dòng nối sai + trùng lặp
|
|
58
|
+
|
|
59
|
+
**Bằng chứng:** dòng `!.crew/graphs/.gitkeepfallow-audit-report/` — negation `!.crew/graphs/.gitkeep` bị nối nhầm với `fallow-audit-report/` (nghi do merge/paste). Hệ quả: cả 2 pattern đều không hoạt động đúng. Ngoài ra có trùng lặp: `dist/` (x2), `.crew/worktrees/` (x2), `/.crew/` vs `/.crew`.
|
|
60
|
+
|
|
61
|
+
**Giải pháp:** tách lại thành 2 dòng đúng (`!.crew/graphs/.gitkeep` và `fallow-audit-report/`) + gộp dòng trùng. **Verify:** `git check-ignore -v .crew/graphs/.gitkeep` và `git check-ignore -v fallow-audit-report/x`.
|
|
62
|
+
|
|
63
|
+
### F3 — CI không gate lint/format/check
|
|
64
|
+
|
|
65
|
+
**Bằng chứng:** `.github/workflows/ci.yml` job `test` chỉ chạy: `npm run typecheck`, `npm test`, `npm pack --dry-run`. KHÔNG có `npm run lint`, `format:check`, hay `check:*`. Local `npm run ci` thì có → drift (F1 minh chứng: lint đỏ nhưng CI vẫn xanh).
|
|
66
|
+
|
|
67
|
+
**Giải pháp:** thêm 1 job `quality` chạy `npm run lint && npm run format:check` (hoặc gọi `npm run ci` trực tiếp). **Verify:** push nhánh → xem GitHub Actions.
|
|
68
|
+
|
|
69
|
+
### F4 — Artifact bug: thư mục `undefined/.pi/teams/...`
|
|
70
|
+
|
|
71
|
+
**Bằng chứng:** tồn tại thư mục thật ở root: `undefined/.pi/teams/state/runs` (layout `.pi/teams` legacy). Một code path đã resolve base root thành chuỗi `"undefined"` (nhiều khả năng `path.join(String(rootUndefined), ".pi", "teams")` hoặc template literal `${cwd}` với `cwd` undefined). Là dữ liệu untracked (không thuộc git, không match `.gitignore`).
|
|
72
|
+
|
|
73
|
+
**Giải pháp:**
|
|
74
|
+
1. Dọn thư mục (đã thực hiện 2026-07, xem F9).
|
|
75
|
+
2. Root-cause + guard: chặn ghi state khi root resolve về undefined/empty (throw hoặc fallback rõ ràng) tại các điểm resolve crewRoot trong `src/utils/paths.ts` / `src/state/crew-init.ts`.
|
|
76
|
+
|
|
77
|
+
**Verify:** thêm unit test cho path-resolver khi input undefined → không tạo path `"undefined"`.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## ⚪ Mức Thấp / Hygiene / Deps
|
|
82
|
+
|
|
83
|
+
### F5 — Deps minor an toàn
|
|
84
|
+
|
|
85
|
+
**Bằng chứng (`npm outdated`):**
|
|
86
|
+
|
|
87
|
+
| Gói | Current | Latest | Loại |
|
|
88
|
+
|-----|---------|--------|------|
|
|
89
|
+
| `@biomejs/biome` | 2.4.15 | 2.5.3 | minor |
|
|
90
|
+
| `tsx` | 4.22.3 | 4.23.0 | minor |
|
|
91
|
+
| `esbuild` | 0.28.0 | 0.28.1 | patch |
|
|
92
|
+
|
|
93
|
+
Lưu ý: `@earendil-works/pi-*` current 0.77.0 > "latest" 0.74.2 → đang dùng kênh pre-release, **không** hạ.
|
|
94
|
+
|
|
95
|
+
**Giải pháp:** nâng 3 gói trên. **Verify:** `npm run typecheck && npm run lint && npm run test:unit (mẫu)`.
|
|
96
|
+
|
|
97
|
+
### F6 — Deps major (cần story riêng)
|
|
98
|
+
|
|
99
|
+
`diff` 5.2.2 → 9.0.0 (đổi ESM/API, ảnh hưởng `conflict-detect`), `typescript` 5.9.3 → 7.0.2 (native compiler, còn sớm cho production typecheck). → tách story: `docs/stories/US-DEPS-major-upgrade.md`.
|
|
100
|
+
|
|
101
|
+
### F7 — Unit suite chậm (DX)
|
|
102
|
+
|
|
103
|
+
**Bằng chứng:** `npm run test:unit` vượt 420s cục bộ. `scripts/test-runner.mjs` ghi chú ~5800 test, budget 15 phút trên Windows CI. Có sẵn `npm run test:changed` nhưng không phải mặc định vòng dev.
|
|
104
|
+
|
|
105
|
+
**Giải pháp (đề xuất):** sharding hoặc khuyến nghị `test:changed` cho vòng dev; giữ full suite cho CI.
|
|
106
|
+
|
|
107
|
+
### F8 — API `@deprecated` còn tồn tại
|
|
108
|
+
|
|
109
|
+
**Bằng chứng:** `src/state/event-log.ts` (sync `appendEvent`), `src/ui/tool-render.ts`, `src/runtime/stale-reconciler.ts` (legacy error builder), `src/runtime/team-runner.ts` (`mergeTaskUpdates` cũ), `src/extension/pi-api.ts` (drift detector removed). → gỡ ở bản major kế tiếp sau khi xác nhận không còn caller ngoài test-compat.
|
|
110
|
+
|
|
111
|
+
### F9 — Dọn thư mục rác ở root (ĐÃ THỰC HIỆN 2026-07)
|
|
112
|
+
|
|
113
|
+
Đã xóa (được người dùng ủy quyền):
|
|
114
|
+
- `undefined/` — artifact bug (F4)
|
|
115
|
+
- `tmp-glyph-previews/` — untracked (2 entries)
|
|
116
|
+
- `.test-artifacts-tmp/`, `.test-artifacts-tmp2/` — ignored, do test bỏ lại (3 entries mỗi cái)
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Ưu tiên đề xuất
|
|
121
|
+
|
|
122
|
+
1. **P0 (quick win):** F1 (fix lint) + F2 (fix .gitignore).
|
|
123
|
+
2. **P1:** F3 (CI gate lint) + F4 (root-cause bug undefined).
|
|
124
|
+
3. **P2:** F5 (minor deps) → F7 (test DX).
|
|
125
|
+
4. **Backlog:** F6 (major deps, story riêng) + F8 (dọn deprecated ở major).
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Bug: Provider quota display truncated to "W..." during sub-agent runs
|
|
2
|
+
|
|
3
|
+
**Status:** FIXED via custom footer (`ctx.ui.setFooter`). See "Resolution" below.
|
|
4
|
+
**Severity:** Cosmetic
|
|
5
|
+
**Affected:** `src/extension/crew-vibes/` (provider quota renderer)
|
|
6
|
+
**Reporter:** user (live observation, 2026-07-09)
|
|
7
|
+
|
|
8
|
+
> Correction: the earlier "UNFIXABLE IN EXTENSION LAYER / WONTFIX" verdict was
|
|
9
|
+
> wrong on two counts. (1) The `spreadLine` U+00A0 full-width padding (from
|
|
10
|
+
> commit `f0baee9`) was described as "reverted attempt 1" but was in fact still
|
|
11
|
+
> ACTIVE and was the direct cause — it padded our row to the full terminal width
|
|
12
|
+
> using `process.stdout.columns` (which differs from pi's real render width),
|
|
13
|
+
> guaranteeing overflow and right-truncation of the quota. (2) Option A (custom
|
|
14
|
+
> footer) was never actually attempted; it is viable and is what the fix uses.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Symptom
|
|
19
|
+
|
|
20
|
+
When pi-crew spawns sub-agents, the provider quota text in pi's footer
|
|
21
|
+
status bar is truncated. User reported seeing:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
⚙ lr • MiniMax-M3 75k o Orbit Minimax 5h ▬▬▬ 29% 1h6m W...
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The `Wk ▬▬▬▬▬▬▬▬ 19% 1d4h` segment (weekly quota bar + percent + reset) is
|
|
28
|
+
chopped to just `W` followed by the dim ellipsis `...`.
|
|
29
|
+
|
|
30
|
+
## Root cause
|
|
31
|
+
|
|
32
|
+
`@earendil-works/pi-coding-agent` (pi) joins ALL extension statuses into
|
|
33
|
+
ONE footer line via `ctx.ui.setStatus()`, sorted alphabetically by key,
|
|
34
|
+
then calls `truncateToWidth(joined, width, "...")` if joined width
|
|
35
|
+
exceeds terminal width.
|
|
36
|
+
|
|
37
|
+
Source: `dist/modes/interactive/components/footer.js` lines 213–222:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const sortedStatuses = Array.from(extensionStatuses.entries())
|
|
41
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
42
|
+
.map(([, text]) => sanitizeStatusText(text));
|
|
43
|
+
const statusLine = sortedStatuses.join(" ");
|
|
44
|
+
lines.push(truncateToWidth(statusLine, width, theme.fg("dim", "...")));
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Concretely, the joined line becomes:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
pi-crew + " " + pi-crew-bar + " " + pi-crew-quota
|
|
51
|
+
(widget) (capacity) (provider quota)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
When sub-agents spawn, `pi-crew` widget text (worker counts, queue,
|
|
55
|
+
model name — `statusSummary()` in `src/ui/widget/widget-model.ts`)
|
|
56
|
+
grows from ~10 chars to ~50-80 chars. The joined line exceeds terminal
|
|
57
|
+
width, and `truncateToWidth` chops the RIGHT side first — which is
|
|
58
|
+
where our provider quota lives. Result: weekly bar lost.
|
|
59
|
+
|
|
60
|
+
## Why naive fixes don't work
|
|
61
|
+
|
|
62
|
+
The extension knows only its OWN status text, not other extensions'
|
|
63
|
+
status texts. Several "obvious" fixes were attempted (all reverted):
|
|
64
|
+
|
|
65
|
+
### Attempt 1 — Right-align quota with U+00A0 padding
|
|
66
|
+
|
|
67
|
+
Prepend non-breaking spaces (U+00A0, which `sanitizeStatusText` does
|
|
68
|
+
not collapse) to push quota to the right edge of `process.stdout.columns`.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const cols = process.stdout.columns || 120;
|
|
72
|
+
const pad = Math.max(0, cols - quotaWidth - capWidth - 1);
|
|
73
|
+
const padded = capText + "\u00A0".repeat(pad) + quotaText;
|
|
74
|
+
setCapacityStatus(ctx, config, padded);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Why it fails:** pad accounts for our row's own capacity+quota width
|
|
78
|
+
but NOT for `pi-crew` widget text or `pi-sub-bar` text that prepends
|
|
79
|
+
ours. When those grow during sub-agent spawn, joined line exceeds
|
|
80
|
+
cols and pi's `truncateToWidth` chops quota anyway.
|
|
81
|
+
|
|
82
|
+
### Attempt 2 — Subtract capacity width dynamically
|
|
83
|
+
|
|
84
|
+
Cache `lastCapacityText` so we know our own row's capacity width when
|
|
85
|
+
padding quota:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
const capWidth = lastCapacityText ? visibleLen(lastCapacityText) : 0;
|
|
89
|
+
const pad = Math.max(0, cols - quotaWidth - capWidth - 1);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Why it fails:** still doesn't account for `pi-crew` widget width or
|
|
93
|
+
other extensions' widths. Same overflow problem.
|
|
94
|
+
|
|
95
|
+
### Attempt 3 — Reserve buffer for other extensions
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const otherReserve = Math.floor(cols * 0.5);
|
|
99
|
+
const pad = Math.max(0, cols - quotaWidth - capWidth - 1 - otherReserve);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Why it fails:** heuristic, not exact. 50% may be too much (quota
|
|
103
|
+
gets pushed left and looks unbalanced) or too little (still overflows).
|
|
104
|
+
Padding is not "dynamic" in the sense of reacting to actual other
|
|
105
|
+
status widths.
|
|
106
|
+
|
|
107
|
+
### Attempt 4 — Split into widget via setWidget("...", [cap, quota])
|
|
108
|
+
|
|
109
|
+
`setWidget()` with `placement: "belowEditor"` renders a Container with
|
|
110
|
+
one Text component per array entry → 2 separate lines below editor,
|
|
111
|
+
not subject to joined status truncation.
|
|
112
|
+
|
|
113
|
+
**Why it fails:** moves the display out of the footer status bar to
|
|
114
|
+
below the editor. User wanted it in the footer (status bar), not in
|
|
115
|
+
a new visual zone.
|
|
116
|
+
|
|
117
|
+
## Viable fixes (none attempted; documenting for future)
|
|
118
|
+
|
|
119
|
+
### A. Custom footer via `setFooter(factory)`
|
|
120
|
+
|
|
121
|
+
Pi exposes `ctx.ui.setFooter((tui, theme, footerData) => Component)` —
|
|
122
|
+
extensions can replace the entire footer. The factory receives
|
|
123
|
+
`footerData.getExtensionStatuses()` so we still see other extensions'
|
|
124
|
+
statuses.
|
|
125
|
+
|
|
126
|
+
Implementation outline:
|
|
127
|
+
1. Build a custom `FooterComponent` that renders cwd + stats + our
|
|
128
|
+
2 lines (capacity + quota right-aligned).
|
|
129
|
+
2. Call `ctx.ui.setFooter(factory)` on `session_start`.
|
|
130
|
+
3. On `session_shutdown`, call `ctx.ui.setFooter(undefined)` to restore
|
|
131
|
+
pi's built-in footer.
|
|
132
|
+
|
|
133
|
+
Cost: ~150 LoC + need to mirror pi's built-in footer logic (cwd,
|
|
134
|
+
branch, token stats, model, etc.) so we don't lose any info.
|
|
135
|
+
|
|
136
|
+
### B. Patch pi-coding-agent upstream
|
|
137
|
+
|
|
138
|
+
Request that pi support multi-line extension status — e.g., add a
|
|
139
|
+
`setStatus(key, lines: string[])` overload that puts each entry on its
|
|
140
|
+
own line, OR a `setStatusLine(position, key, text)` that allows
|
|
141
|
+
pinning a status to line 1 vs line 2.
|
|
142
|
+
|
|
143
|
+
Cost: requires upstream PR + waiting for new pi release.
|
|
144
|
+
|
|
145
|
+
### C. Shorten quota to always fit
|
|
146
|
+
|
|
147
|
+
Drop the weekly bar entirely (or shrink to 1-2 chars). Quota becomes
|
|
148
|
+
`Minimax 5h 29%` (~15 chars). Even if `pi-crew` widget text is 80 chars
|
|
149
|
+
and capacity is 22 chars, total = 80+1+22+1+15 = 119, fits in 120 cols
|
|
150
|
+
usually.
|
|
151
|
+
|
|
152
|
+
Cost: loses reset timers and weekly bar visualization. User originally
|
|
153
|
+
wanted the bars.
|
|
154
|
+
|
|
155
|
+
## Why the 5 prior attempts all failed (same root cause)
|
|
156
|
+
|
|
157
|
+
All five fought the truncation from INSIDE `setStatus`, where an extension
|
|
158
|
+
cannot see (a) the `pi-crew` widget status width that grows during sub-agent
|
|
159
|
+
runs, (b) other extensions' widths, (c) pi's real render width, and where pi
|
|
160
|
+
always right-truncates the joined line:
|
|
161
|
+
|
|
162
|
+
- `81859e6` — split into `pi-crew-quota` key. pi joins all keys onto one line;
|
|
163
|
+
quota still rightmost → still chopped. (Misdiagnosed as slot-overwrite.)
|
|
164
|
+
- `a74e453` — NBSP pad by `cols - quotaWidth - 1`. Ignores capacity + widget
|
|
165
|
+
widths → overflow → chop.
|
|
166
|
+
- `a9eb30e` — NBSP pad by `cols - quotaWidth - capWidth - 1`. Still ignores the
|
|
167
|
+
widget width and other extensions → overflow.
|
|
168
|
+
- `900737c` — `setWidget(belowEditor)` 2-line. Works (no truncation) but sits
|
|
169
|
+
below the editor, not in the footer. Rejected.
|
|
170
|
+
- `5e56b63` — one status line + NBSP pad (`process.stdout.columns`). Same flaw
|
|
171
|
+
as #2/#3, plus a width source that disagrees with pi's actual render width.
|
|
172
|
+
|
|
173
|
+
## Resolution (implemented)
|
|
174
|
+
|
|
175
|
+
Definitive fix = option A, **custom footer via `ctx.ui.setFooter(factory)`** —
|
|
176
|
+
the one approach none of the 5 attempts tried. It sidesteps every failure mode
|
|
177
|
+
above because the footer `Component`'s `render(width)` receives pi's REAL render
|
|
178
|
+
width and we own the line layout, so the meters get their own line(s) that the
|
|
179
|
+
join can never truncate.
|
|
180
|
+
|
|
181
|
+
- `src/extension/crew-vibes/footer.ts` — `CrewVibesFooter implements Component`.
|
|
182
|
+
Reproduces pi's built-in footer lines (pwd/branch/session; token stats + cost
|
|
183
|
+
+ context% + model + thinking) from `ctx.sessionManager`/`ctx.model`/
|
|
184
|
+
`ctx.getContextUsage()`/`footerData`, keeps other extensions' statuses on the
|
|
185
|
+
joined line (still truncated, like pi), and renders **capacity + quota on a
|
|
186
|
+
dedicated line** using the real width. When the terminal is too narrow, the
|
|
187
|
+
meters wrap to two lines (capacity above, quota right-aligned below).
|
|
188
|
+
- `src/extension/crew-vibes/index.ts` — installs the footer on `session_start`
|
|
189
|
+
(and re-install/remove in `applyConfig`), restores pi's built-in footer with
|
|
190
|
+
`setFooter(undefined)` on disable/`session_shutdown`, drops the `spreadLine`
|
|
191
|
+
padding, stores the raw provider-usage snapshot (rendered by the footer), and
|
|
192
|
+
tracks thinking level via the `thinking_level_select` event.
|
|
193
|
+
- `src/ui/pi-ui-compat.ts` — added a guarded `setFooter()` wrapper (no-op on
|
|
194
|
+
hosts that predate the API).
|
|
195
|
+
|
|
196
|
+
Accepted fidelity trade-offs (pi does not expose these to extensions):
|
|
197
|
+
- The `(auto)` compaction indicator is always shown (matches pi's default);
|
|
198
|
+
the toggle state is not observable.
|
|
199
|
+
- Thinking level comes from `thinking_level_select`; before the first switch it
|
|
200
|
+
shows the default ("off").
|
|
201
|
+
|
|
202
|
+
Proof: `test/unit/crew-vibes.test.ts` adds two tests — quota stays fully visible
|
|
203
|
+
while an overflowing status line is truncated, and the meters wrap to two lines
|
|
204
|
+
on a narrow terminal.
|
|
205
|
+
|
|
206
|
+
## How to revert fully
|
|
207
|
+
|
|
208
|
+
If you want to wipe all 5 reverted commits from history:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
git revert f74adae 7588b26 0f0905d a61d5de dced96a # the 5 reverts above
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Or just leave them — they're labeled "Revert ..." so it's clear what
|
|
215
|
+
they undo.
|
|
216
|
+
|
|
217
|
+
## References
|
|
218
|
+
|
|
219
|
+
- pi footer rendering: `/home/bom/.nvm/versions/node/v22.23.1/lib/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js:213-222`
|
|
220
|
+
- pi truncateToWidth: `node_modules/@earendil-works/pi-tui/dist/utils.js:821`
|
|
221
|
+
- pi-crew widget status source: `src/ui/widget/widget-model.ts:77-99` (`statusSummary()`)
|
|
222
|
+
- crew-vibes provider rendering: `src/extension/crew-vibes/render.ts:158-194` (`renderProviderUsage()`)
|
|
223
|
+
- crew-vibes quota publish: `src/extension/crew-vibes/index.ts` (`publishProviderQuota()`)
|
package/docs/stories/README.md
CHANGED
|
@@ -13,7 +13,9 @@ Story-sized work packets for pi-crew development.
|
|
|
13
13
|
|
|
14
14
|
## Active Stories
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
| ID | Title | Lane | Status |
|
|
17
|
+
|----|-------|------|--------|
|
|
18
|
+
| US-DEPS-major-upgrade | Nâng cấp deps major (diff 5→9, TypeScript 7) | normal | planned |
|
|
17
19
|
|
|
18
20
|
## Completed Stories (from review rounds)
|
|
19
21
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# US-DEPS-major-upgrade — Nâng cấp dependencies major
|
|
2
|
+
|
|
3
|
+
- **Lane:** normal (Dependency) — có thể tách thành 2 patch độc lập
|
|
4
|
+
- **Status:** planned
|
|
5
|
+
- **Nguồn:** `docs/REVIEW-FINDINGS-2026-07.md` (F6)
|
|
6
|
+
- **Ngày tạo:** 2026-07-09
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
`npm outdated` (Node v22.14.0, v0.9.28) cho thấy 2 gói lệch **major**:
|
|
11
|
+
|
|
12
|
+
| Gói | Current | Latest | Rủi ro |
|
|
13
|
+
|-----|---------|--------|--------|
|
|
14
|
+
| `diff` | 5.2.2 | 9.0.0 | API/ESM breaking |
|
|
15
|
+
| `typescript` | 5.9.3 | 7.0.2 | compiler mới (native) |
|
|
16
|
+
|
|
17
|
+
Cả hai KHÔNG gộp chung; mỗi cái là một patch riêng có validation độc lập.
|
|
18
|
+
|
|
19
|
+
## Phần A — `diff` 5 → 9
|
|
20
|
+
|
|
21
|
+
### Design
|
|
22
|
+
- `diff` v6+ chuyển sang ESM-first và tinh chỉnh chữ ký hàm (`structuredPatch`, `diffLines`, `applyPatch`...). Cần rà soát mọi import.
|
|
23
|
+
- Điểm chạm chính: `src/utils/conflict-detect.ts` (và bất kỳ nơi nào import `diff`).
|
|
24
|
+
|
|
25
|
+
### Exec plan
|
|
26
|
+
1. `git grep -n "from \"diff\"" src test` để liệt kê toàn bộ callsite.
|
|
27
|
+
2. Đọc CHANGELOG của `diff` v6/v7/v8/v9, lập bảng breaking → mapping.
|
|
28
|
+
3. `npm i diff@9`, sửa callsite theo API mới.
|
|
29
|
+
4. Chạy `npm run typecheck` + test liên quan (`conflict-detect.test.ts`, `delta-conflict.test.ts`).
|
|
30
|
+
|
|
31
|
+
### Validation
|
|
32
|
+
- `npm run typecheck` → PASS
|
|
33
|
+
- `node scripts/test-runner.mjs ... test/unit/conflict-detect.test.ts test/unit/delta-conflict.test.ts` → pass
|
|
34
|
+
- `npm run test:unit` full → không hồi quy
|
|
35
|
+
|
|
36
|
+
## Phần B — `typescript` 5.9 → 7.0
|
|
37
|
+
|
|
38
|
+
### Design
|
|
39
|
+
- TS 7.0 là compiler native (tsgo). Cần đánh giá:
|
|
40
|
+
- Tương thích flags trong `tsconfig.json` (`allowImportingTsExtensions`, `NodeNext`, `strict`).
|
|
41
|
+
- Tương thích với `tsx`/strip-types runtime path (index.ts import qua `--experimental-strip-types`).
|
|
42
|
+
- Khả năng phát sinh lỗi type mới do compiler chặt hơn.
|
|
43
|
+
- Rủi ro cao hơn A; cân nhắc chờ hệ sinh thái ổn định (còn sớm giữa 2026).
|
|
44
|
+
|
|
45
|
+
### Exec plan
|
|
46
|
+
1. Thử trên nhánh riêng: `npm i -D typescript@7`.
|
|
47
|
+
2. `npm run typecheck` — thu thập toàn bộ lỗi mới, phân loại.
|
|
48
|
+
3. Nếu lỗi ít/định vị được → sửa; nếu diện rộng → hoãn, ghi decision.
|
|
49
|
+
4. Xác nhận `tsx` + strip-types vẫn chạy (`node --experimental-strip-types -e "await import('./index.ts')"`).
|
|
50
|
+
|
|
51
|
+
### Validation
|
|
52
|
+
- `npm run typecheck` → PASS
|
|
53
|
+
- `npm run test:unit` → không hồi quy
|
|
54
|
+
- Cold-start bench (`npm run bench`) → không xấu đi
|
|
55
|
+
|
|
56
|
+
## Rollback
|
|
57
|
+
- Mỗi phần là 1 commit độc lập; revert commit tương ứng nếu hồi quy.
|
|
58
|
+
- Giữ `package-lock.json` cũ để pin lại nhanh.
|
|
59
|
+
|
|
60
|
+
## Ghi chú quyết định
|
|
61
|
+
- Ưu tiên Phần A trước (rủi ro thấp hơn, phạm vi hẹp).
|
|
62
|
+
- Phần B nên có `docs/decisions/` entry trước khi merge (thay đổi toolchain cốt lõi).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-crew",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.29",
|
|
4
4
|
"description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
|
|
5
5
|
"author": "baphuongna",
|
|
6
6
|
"license": "MIT",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"@earendil-works/pi-tui": "*"
|
|
104
104
|
},
|
|
105
105
|
"dependencies": {
|
|
106
|
-
"@sinclair/typebox": "^0.34.
|
|
106
|
+
"@sinclair/typebox": "^0.34.50",
|
|
107
107
|
"acorn": "^8.17.0",
|
|
108
108
|
"ajv": "^8.20.0",
|
|
109
109
|
"cli-highlight": "^2.1.11",
|
|
@@ -112,13 +112,13 @@
|
|
|
112
112
|
"yaml": "^2.9.0"
|
|
113
113
|
},
|
|
114
114
|
"devDependencies": {
|
|
115
|
-
"@biomejs/biome": "^2.
|
|
115
|
+
"@biomejs/biome": "^2.5.3",
|
|
116
116
|
"@earendil-works/pi-agent-core": "^0.77.0",
|
|
117
117
|
"@earendil-works/pi-ai": "^0.77.0",
|
|
118
118
|
"@earendil-works/pi-coding-agent": "^0.77.0",
|
|
119
119
|
"@earendil-works/pi-tui": "^0.77.0",
|
|
120
120
|
"esbuild": "^0.28.1",
|
|
121
|
-
"tsx": "^4.
|
|
121
|
+
"tsx": "^4.23.0",
|
|
122
122
|
"typescript": "^5.9.3"
|
|
123
123
|
},
|
|
124
124
|
"peerDependenciesMeta": {
|
|
@@ -83,6 +83,8 @@ export interface AgentConfig {
|
|
|
83
83
|
contextMode?: "fresh" | "fork";
|
|
84
84
|
/** Maximum turns for this agent. Overrides runtime config if set. */
|
|
85
85
|
maxTurns?: number;
|
|
86
|
+
/** Cap on output tokens per API call. Set via PI_CREW_MAX_OUTPUT_TOKENS env in child. */
|
|
87
|
+
maxTokens?: number;
|
|
86
88
|
/** Effort level for this agent. Controls how much work the agent puts in. */
|
|
87
89
|
effort?: "low" | "medium" | "high";
|
|
88
90
|
/** Tools to explicitly forbid for this agent. Takes precedence over allowedTools. */
|
|
@@ -431,6 +431,10 @@ function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig |
|
|
|
431
431
|
const n = Number.parseInt(frontmatter.maxTurns, 10);
|
|
432
432
|
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
433
433
|
})(),
|
|
434
|
+
maxTokens: (() => {
|
|
435
|
+
const n = Number.parseInt(frontmatter.maxTokens, 10);
|
|
436
|
+
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
437
|
+
})(),
|
|
434
438
|
effort:
|
|
435
439
|
frontmatter.effort === "low" || frontmatter.effort === "medium" || frontmatter.effort === "high"
|
|
436
440
|
? frontmatter.effort
|
package/src/config/config.ts
CHANGED
|
@@ -741,6 +741,10 @@ function parseWorktreeConfig(value: unknown): CrewWorktreeConfig | undefined {
|
|
|
741
741
|
setupHook: setupHook ? setupHook : undefined,
|
|
742
742
|
setupHookTimeoutMs: parsePositiveInteger(obj.setupHookTimeoutMs, 300_000),
|
|
743
743
|
linkNodeModules: parseWithSchema(Type.Boolean(), obj.linkNodeModules),
|
|
744
|
+
// C6: seedPaths was declared in the type + schema but never parsed here, so
|
|
745
|
+
// loadedConfig.config.worktree?.seedPaths was always undefined -> the global
|
|
746
|
+
// worktree seed overlay (worktree-manager.ts) silently never applied.
|
|
747
|
+
seedPaths: parseStringList(obj.seedPaths),
|
|
744
748
|
};
|
|
745
749
|
return Object.values(worktree).some((entry) => entry !== undefined) ? worktree : undefined;
|
|
746
750
|
}
|
|
@@ -78,7 +78,7 @@ export const DEFAULT_CONFIG: CrewVibesConfig = {
|
|
|
78
78
|
labels: ["Orbit", "Cruise", "Warp", "Black Hole", "Supernova", "Big Bang"],
|
|
79
79
|
icons: ["", "", "", "", "", ""],
|
|
80
80
|
providerUsage: true,
|
|
81
|
-
providerRefreshMs:
|
|
81
|
+
providerRefreshMs: 120000,
|
|
82
82
|
},
|
|
83
83
|
};
|
|
84
84
|
|