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.
Files changed (35) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/build-meta.json +164 -115
  3. package/dist/index.mjs +986 -555
  4. package/dist/index.mjs.map +4 -4
  5. package/docs/REVIEW-FINDINGS-2026-07-CORE.md +139 -0
  6. package/docs/REVIEW-FINDINGS-2026-07.md +125 -0
  7. package/docs/bugs/bug-quota-display-truncation.md +223 -0
  8. package/docs/stories/README.md +3 -1
  9. package/docs/stories/US-DEPS-major-upgrade.md +62 -0
  10. package/package.json +4 -4
  11. package/src/agents/agent-config.ts +2 -0
  12. package/src/agents/discover-agents.ts +4 -0
  13. package/src/config/config.ts +4 -0
  14. package/src/extension/crew-vibes/config.ts +1 -1
  15. package/src/extension/crew-vibes/figures.ts +1 -1
  16. package/src/extension/crew-vibes/footer.ts +292 -0
  17. package/src/extension/crew-vibes/index.ts +74 -70
  18. package/src/extension/crew-vibes/provider-usage.ts +119 -53
  19. package/src/extension/team-tool.ts +11 -0
  20. package/src/prompt/prompt-runtime.ts +65 -0
  21. package/src/runtime/child-pi.ts +58 -43
  22. package/src/runtime/cross-extension-rpc.ts +1 -1
  23. package/src/runtime/pi-args.ts +2 -0
  24. package/src/runtime/post-exit-stdio-guard.ts +22 -4
  25. package/src/runtime/stale-reconciler.ts +9 -4
  26. package/src/runtime/task-runner.ts +1 -0
  27. package/src/runtime/team-runner.ts +11 -16
  28. package/src/state/atomic-write.ts +46 -19
  29. package/src/state/event-log.ts +77 -24
  30. package/src/state/mailbox.ts +6 -3
  31. package/src/ui/pi-ui-compat.ts +11 -0
  32. package/src/utils/conflict-detect.ts +9 -3
  33. package/src/utils/paths.ts +7 -1
  34. package/src/worktree/cleanup.ts +19 -0
  35. 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()`)
@@ -13,7 +13,9 @@ Story-sized work packets for pi-crew development.
13
13
 
14
14
  ## Active Stories
15
15
 
16
- No active stories. Pick from backlog or create new ones via feature intake.
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.27",
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.49",
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.4.15",
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.22.3",
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
@@ -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: 300000,
81
+ providerRefreshMs: 120000,
82
82
  },
83
83
  };
84
84