pi-crew 0.6.1 → 0.6.3

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 (102) hide show
  1. package/CHANGELOG.md +160 -0
  2. package/README.md +70 -33
  3. package/docs/issue-29-analysis.md +189 -0
  4. package/docs/superpowers/plans/2026-06-09-fallow-patterns-adoption.md +1268 -0
  5. package/package.json +2 -2
  6. package/src/config/config.ts +106 -15
  7. package/src/errors.ts +107 -0
  8. package/src/extension/async-notifier.ts +6 -2
  9. package/src/extension/crew-cleanup.ts +8 -5
  10. package/src/extension/management.ts +464 -109
  11. package/src/extension/register.ts +194 -34
  12. package/src/extension/registration/commands.ts +2 -2
  13. package/src/extension/registration/subagent-helpers.ts +2 -2
  14. package/src/extension/registration/subagent-tools.ts +1 -1
  15. package/src/extension/registration/team-tool.ts +1 -1
  16. package/src/extension/registration/viewers.ts +2 -2
  17. package/src/extension/team-tool/api.ts +3 -3
  18. package/src/extension/team-tool/cancel.ts +3 -3
  19. package/src/extension/team-tool/explain.ts +1 -1
  20. package/src/extension/team-tool/handle-schedule.ts +40 -0
  21. package/src/extension/team-tool/inspect.ts +3 -3
  22. package/src/extension/team-tool/lifecycle-actions.ts +4 -4
  23. package/src/extension/team-tool/respond.ts +2 -2
  24. package/src/extension/team-tool/run.ts +60 -11
  25. package/src/extension/team-tool/status.ts +1 -1
  26. package/src/extension/team-tool.ts +173 -46
  27. package/src/hooks/registry.ts +77 -13
  28. package/src/hooks/types.ts +9 -0
  29. package/src/plugins/plugin-define.ts +6 -0
  30. package/src/plugins/plugin-registry.ts +32 -0
  31. package/src/plugins/plugins/index.ts +3 -0
  32. package/src/plugins/plugins/nextjs.ts +19 -0
  33. package/src/plugins/plugins/vite.ts +14 -0
  34. package/src/plugins/plugins/vitest.ts +14 -0
  35. package/src/runtime/async-runner.ts +35 -7
  36. package/src/runtime/background-runner.ts +529 -144
  37. package/src/runtime/chain-parser.ts +5 -1
  38. package/src/runtime/chain-runner.ts +9 -3
  39. package/src/runtime/checkpoint.ts +262 -180
  40. package/src/runtime/child-pi.ts +164 -37
  41. package/src/runtime/crash-recovery.ts +25 -14
  42. package/src/runtime/crew-agent-records.ts +2 -0
  43. package/src/runtime/diagnostic-export.ts +1 -1
  44. package/src/runtime/dynamic-script-runner.ts +10 -7
  45. package/src/runtime/foreground-watchdog.ts +1 -1
  46. package/src/runtime/heartbeat-watcher.ts +19 -2
  47. package/src/runtime/intercom-bridge.ts +7 -0
  48. package/src/runtime/iteration-hooks.ts +1 -1
  49. package/src/runtime/manifest-cache.ts +4 -0
  50. package/src/runtime/orphan-worker-registry.ts +444 -0
  51. package/src/runtime/parent-guard.ts +70 -16
  52. package/src/runtime/pi-args.ts +437 -14
  53. package/src/runtime/pi-spawn.ts +1 -0
  54. package/src/runtime/post-checks.ts +1 -1
  55. package/src/runtime/retry-runner.ts +9 -3
  56. package/src/runtime/run-tracker.ts +38 -10
  57. package/src/runtime/sandbox.ts +17 -2
  58. package/src/runtime/scheduler.ts +25 -2
  59. package/src/runtime/skill-effectiveness.ts +105 -62
  60. package/src/runtime/skill-instructions.ts +110 -32
  61. package/src/runtime/stale-reconciler.ts +310 -69
  62. package/src/runtime/subagent-manager.ts +251 -57
  63. package/src/runtime/task-health.ts +76 -0
  64. package/src/runtime/task-id.ts +20 -0
  65. package/src/runtime/task-runner/live-executor.ts +1 -1
  66. package/src/runtime/task-runner/progress.ts +1 -1
  67. package/src/runtime/task-runner/state-helpers.ts +110 -6
  68. package/src/runtime/task-runner.ts +92 -70
  69. package/src/runtime/team-runner.ts +186 -20
  70. package/src/schema/team-tool-schema.ts +27 -9
  71. package/src/skills/discover-skills.ts +9 -3
  72. package/src/state/active-run-registry.ts +170 -38
  73. package/src/state/artifact-store.ts +25 -13
  74. package/src/state/atomic-write-v2.ts +86 -0
  75. package/src/state/atomic-write.ts +346 -55
  76. package/src/state/blob-store.ts +178 -10
  77. package/src/state/crew-init.ts +161 -28
  78. package/src/state/decision-ledger.ts +172 -111
  79. package/src/state/event-log-rotation.ts +82 -52
  80. package/src/state/event-log.ts +254 -70
  81. package/src/state/health-store.ts +71 -0
  82. package/src/state/locks.ts +102 -20
  83. package/src/state/mailbox.ts +45 -7
  84. package/src/state/observation-store.ts +4 -1
  85. package/src/state/run-graph.ts +141 -130
  86. package/src/state/run-metrics.ts +24 -8
  87. package/src/state/state-store.ts +330 -43
  88. package/src/ui/live-run-sidebar.ts +1 -1
  89. package/src/ui/loaders.ts +4 -0
  90. package/src/ui/overlays/agent-picker-overlay.ts +1 -1
  91. package/src/ui/overlays/mailbox-detail-overlay.ts +1 -1
  92. package/src/ui/run-action-dispatcher.ts +2 -2
  93. package/src/ui/run-snapshot-cache.ts +1 -1
  94. package/src/utils/env-filter.ts +66 -0
  95. package/src/utils/file-coalescer.ts +9 -1
  96. package/src/utils/paths.ts +80 -5
  97. package/src/utils/redaction.ts +6 -1
  98. package/src/utils/safe-paths.ts +281 -10
  99. package/src/worktree/cleanup.ts +112 -24
  100. package/src/worktree/worktree-manager.ts +128 -15
  101. package/test-bugs-all.mjs +10 -6
  102. package/test-integration-check.ts +4 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,165 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.3] — Cross-Platform CI, 87 Test Fixes, Worktree Validation, Heartbeat & Crash Fixes (2026-06-12)
4
+
5
+ ### Highlights
6
+ - **Cross-platform CI green** — 0 failures across Ubuntu, macOS, and Windows (4,725 unit + 113 integration tests)
7
+ - **87 pre-existing test failures resolved** — 0 failures across 4,792 tests in 506 test files
8
+ - **Heartbeat false-positive dead detection fixed** — `message_start` added to progress flush events; PID liveness gate uses `task.checkpoint.childPid` fallback
9
+ - **ENOENT crash on prune/forget race fixed** — 4-layer defense in `persistSingleTaskUpdate`, `persistHeartbeat`, `saveRunTasks`, and `upsertCrewAgent`
10
+ - **Scheduled job lifecycle completed** — spawned runs tracked via `spawnedRunIds[]`, auto-cancelled on job removal, manifests stamped with `schedulerJobId` for traceability
11
+ - **Worktree precondition validation** — friendly error messages instead of crashes when cwd is not a git repo or repo has uncommitted changes
12
+ - **Cross-platform path handling** — `canonicalizePath` with `realpathSync.native` for Windows short-name/long-name aliasing; macOS `/var` → `/private/var` symlink resolution
13
+ - **Pipe buffer deadlock fix** — `spawnSync` with `stdio: 'pipe'` caused deadlock when OS pipe buffer (~64KB) filled at ~227 tests; switched to `stdio: 'inherit'`
14
+ - **Stale lock recovery** — removed `readLockToken` guard so stale locks without 'token' field are properly deleted
15
+ - **Full-feature smoke test** — 58 integration tests covering all pi-crew actions
16
+ - **Pre-push review**: 56 unpushed commits reviewed (116 files, +9,599/−980 lines), 1 release blocker found and fixed
17
+
18
+ ### Bug Fixes
19
+ - **`89ed975`** — Heartbeat watcher: added `message_start` to `shouldFlushProgressEvent()` so LLM stream start updates `lastActivityAt`. Previously, an 8m53s LLM response (365 file reads, no tool calls) triggered false `heartbeat_dead` at 300s threshold.
20
+ - **`9c1bf1f`** — Heartbeat watcher: PID liveness gate uses `task.heartbeat?.pid ?? task.checkpoint?.childPid` fallback. Review team discovered the gate was dead code because `createWorkerHeartbeat(taskId)` never receives PID.
21
+ - **`2bbbb99`** — ENOENT crash: `persistSingleTaskUpdate` recheck stat wrapped in try/catch; `persistHeartbeat` catches ENOENT; `saveRunTasks` guards with `statSync(stateRoot)`; `upsertCrewAgent` skips if stateRoot gone.
22
+ - **`08df7ce`** — Release blocker: `src/errors.ts` enum→const object, `src/state/health-store.ts` parameter property — both incompatible with Node 22 `--experimental-strip-types`.
23
+ - **`3e0b957`** — Sandbox constructor escape detection strengthened.
24
+ - **`dd279bc`** — EBADF (missing O_WRONLY flag), re-entrant sync locks, worktree list parsing, env-filter provider keys.
25
+ - **`38b8f5a`** — Create `transcripts/` directory before child-pi appends.
26
+ - **`d893434`** — Child-pi: remove API key allowlist; child Pi uses same config as parent.
27
+ - **`5cb9122`** — Cross-platform: `canonicalizePath` in `paths.ts` uses `realpathSync.native` for consistent long-name paths on Windows; all test temp dirs canonicalized through `.native`.
28
+ - **`3b46556`** — Cross-platform: `resolvedWorktreeRoot` uses `.native` for git worktree compatibility on Windows; worktree list comparison normalizes through `.native`.
29
+ - **`b2b7068`** — Worktree: precondition validation in `team-tool/run.ts` checks git repo existence and clean leader status before creating run manifest, returning friendly error messages instead of crashing.
30
+ - **`8090fe2`** — Worktree: respect `requireCleanWorktreeLeader` config setting in precondition check.
31
+ - **`2014739`** — Pipe buffer deadlock: `spawnSync` with `stdio: 'pipe'` caused deadlock when OS pipe buffer (~64KB) filled at ~227 tests; switched to `stdio: 'inherit'`.
32
+ - **`d6920bf`** — Stale lock recovery: removed `readLockToken` guard so stale locks without 'token' field are properly deleted.
33
+ - **`3897c1d`** — Mailbox: return paths as-is from `safeMailboxDir`, `safeMailboxFile`, `safeMailboxTasksRoot`, `taskMailboxDir` instead of re-resolving through `resolveRealContainedPath` which changed path forms on Windows.
34
+ - **`f867d4d`** — macOS: `realpathSync(os.tmpdir())` in 3 test files to handle `/var` → `/private/var` symlink.
35
+ - **`2fd0c1e`** — TypeScript: fix Property 'text' and Property 'taskId'→'id' errors for strict compiler checks.
36
+
37
+ ### Test Fixes (87 total)
38
+ - **`1ab7926`** — 33 failures: state-store mtime CAS, locks race, discovery, atomic-write, config-schema, blob-store, env-filter, sandbox, security-hardening, worktree
39
+ - **`bba0bed`** — 3 failures: blob dedup, auto-recovery cap, transcript append
40
+ - **`03dd9b3`** — 14 failures: team-runner, retry-runner, hooks, stale-reconciler, resume-checkpoint, dynamic-script-runner, adaptive-implementation
41
+ - **`a91c316`** — 5 failures: re-entrant sync locks (`withRunLockSync`), `registerWorker()` optional `registeredAt`, `phase8-smoke` PI_TEAMS_HOME isolation, `test-integration-check` PI_CREW_ALLOW_MOCK, `test-bugs-all.mjs` graceful skip
42
+ - **`952c14d`** — 58 full-feature smoke tests added
43
+
44
+ ### Features
45
+ - **`14269f0`** — Scheduler tracks spawned runs: `ScheduledJob.spawnedRunIds[]`, `CrewScheduler.recordSpawnedRun()`, `remove()` calls `runCancelFn` per spawned run, manifests stamped with `schedulerJobId`/`schedulerName`.
46
+ - **`e499570`** — Plugin registry system for framework context injection (Next.js, Vite, Vitest).
47
+ - **`84170c3`** — Team runner integrates plugin registry for framework-aware task context.
48
+ - **`ee466a8`** — Health score system with penalty-based scoring and time-series snapshots.
49
+ - **`daa53ab`** — Atomic write v2 with fsync + rename pattern for crash-safe state persistence.
50
+ - **`6c01f2c`** — CrewError taxonomy: E001–E006 structured error codes.
51
+ - **`2ce143f`** — State-store uses CrewError for structured errors.
52
+ - **`0cd4853`** — Stable task IDs via `stableIdFromContent` for cross-run consistency.
53
+ - **`ff3da92`** — Health snapshot saved on run completion.
54
+
55
+ ### Stats
56
+ - 137 commits since v0.6.1
57
+ - 200 files changed (+16,955 / −2,057 lines)
58
+ - 366 source files, ~70K lines TypeScript
59
+ - 506 test files, ~66K lines TypeScript
60
+ - 4,792 tests, 0 failures
61
+ - CI: Ubuntu ✅ macOS ✅ Windows ✅ (0 failures each)
62
+
63
+
64
+ ## [0.6.3] — Post-Release Hardening: Cleanup, Safe-Paths, State-Store Race (2026-06-08)
65
+
66
+ ### Highlights
67
+ - **State-store manifest/tasks mtime race fixed** (commits `04fe0be`, `f15ee98`) — `loadRunManifestById` no longer throws on benign mtime skew between `manifest.json` and `tasks.json`. A previous user review (run `team_20260608082852_*`) hit a 4812-second hang because of this throw; the fix prevents the same hang from recurring.
68
+ - **Orphan worker + temp dir cleanup hardened** (8 commits) — 4-layer defense (in-memory Set, per-session temp dir, user-root temp dir, legacy `/tmp` cleanup) with symlink guards, `O_NOFOLLOW` opens, and bounded batch sizes.
69
+ - **`PI_CREW_PARENT_PID` restored to child env allow-list** (commit `e1f7dfe`) — silent regression from a previous round fixed; parent-guard now works again for orphan-worker detection.
70
+ - **`safe-paths.resolveRealContainedPath` extended** (commits `ba0ce54`, `aa457a5`) — now supports creating new files (target does not have to exist) while keeping full symlink-ancestor protection.
71
+ - **`blob-store` metadata race fixed** (commit `5819b18`) — per-hash in-memory lock + atomic write of content-then-metadata prevents concurrent writers from corrupting metadata.
72
+ - **Behavior change: `parent-guard` no longer `.unref()`s its interval** — the guard timer now keeps the event loop alive by design so workers do not exit while their parent is still alive but the worker has no other pending work. See "Behavior Changes" below.
73
+
74
+ ### Security Fixes
75
+ - **`e1f7dfe`** — Restored `PI_CREW_PARENT_PID` in `child-pi.ts` env allow-list. Previous round (`dbf7a48`) replaced `PI_CREW_*`/`PI_TEAMS_*` wildcards with an explicit list but omitted `PI_CREW_PARENT_PID`, silently breaking the parent-guard mechanism.
76
+ - **`ba0ce54` / `aa457a5`** — `safe-paths.resolveRealContainedPath` now allows new-file creation while keeping symlink-ancestor protection. Documented the asymmetric ancestor policy in the function JSDoc.
77
+ - **`e1f7dfe`** (sibling) — `worktree/cleanup.ts` no longer uses `PI_*` / `PI_CREW_*` wildcards in `GIT_SAFE_ENV` (could match secret vars like `PI_PASSWORD`).
78
+ - **`2b8f27a` / `1bf67eb`** — Child env allow-list switched from dangerous wildcards (`*_API_KEY`, `*_TOKEN`, `*_SECRET`, `LC_*`, `XDG_*`, `NPM_*`) to an explicit list of 6 API keys + 12 essential env vars. Eliminates accidental secret leakage via matching name patterns.
79
+
80
+ ### Cleanup Hardening (8 commits)
81
+ - **`5edcb18`** — Track temp dirs globally via in-memory `Set<string>`, cap `reconcileOrphanedTempWorkspaces` scan size, fix cleanup stub.
82
+ - **`8ba270d`** — Move temp dirs from `/tmp` to `~/.pi/agent/pi-crew/tmp/` (uses `userPiRoot()` so `PI_TEAMS_HOME` / `PI_CODING_AGENT_DIR` are respected). Eliminates `/tmp` pollution and unifies state layout.
83
+ - **`ceb1cb1`** — Layer 4 periodic cleanup for orphan prompt/task temp dirs older than 24h.
84
+ - **`a76932d`** — Skip symlinks and in-use dirs during cleanup, plus a one-shot legacy `/tmp` sweep to clean up directories left behind by pre-`8ba270d` installations.
85
+ - **`c9eb430`** — Kill orphan background workers and trigger temp cleanup on `session_start`.
86
+ - **`a192509`** — 4 critical hardening fixes: never `rmSync` a symlink, double-check immediately before delete, skip dirs currently in use, and tear down the global tracker on process exit.
87
+ - **`992231d`** — 24 new unit tests in `test/unit/cleanup-orphan-temp.test.ts` covering each cleanup layer and failure mode.
88
+ - **`dbf7a48`** — UI: replace `console.log` cleanup messages with `notifyOperator` for proper user notification.
89
+
90
+ ### Bug Fixes
91
+ - **`5819b18`** — `blob-store.writeBlob` race condition: per-hash lock + atomic write of content-then-metadata (previously metadata first, leaving orphans on blob write failure).
92
+ - **`04fe0be`** — `state-store.loadRunManifestById` returned `undefined` (was: threw) on manifest/tasks mtime mismatch — the throw caused background runners to crash within 1s of startup.
93
+ - **`f15ee98`** — `state-store.loadRunManifestById` removed the false-positive mtime check entirely. The `saveManifestAndTasksAtomicSync` writer intentionally writes manifest before tasks, so a manifest with newer mtime than tasks is a NORMAL post-write state, not corruption.
94
+ - **`cd7ef89` / `a0c2ba3` / `098c8a9` / `b782424` / `e1ea7d4` / `de3f550` / `2b8f27a` / `1bf67eb`** — 8 deep-review auto-fix commits addressing 78+77+29+28+24+24+24+24 verified issues across the cleanup, state, runtime, and utils modules.
95
+ - **`e1f7dfe`** — `parent-guard.ts` unref'd timers were silently causing worker exit while parent was still alive; restoring the allow-list entry brought the guard back into effect.
96
+
97
+ ### Behavior Changes
98
+ - **`parent-guard.ts` no longer `.unref()`s the guard interval** (revert/restore series `0aed8b5` / `152ac80` / `ee0ddb4` / `81b9608`). The watchdog timer now keeps the event loop alive by design. If a worker has no other pending work (no I/O, no timers, no child processes), the guard interval is the only thing keeping the worker alive until either the parent dies or the worker is explicitly stopped. The previous revert-then-restore pattern ("to test if they cause pi hang") never conclusively identified a root cause; the current state was reached after manual testing. **Mitigation recommended**: add a max-worker-lifetime safety net in a future release.
99
+
100
+ ### New / Heavily Expanded Source Files
101
+ - **`src/runtime/orphan-worker-registry.ts`** (NEW, 307 lines) — PID+start-time+parent-PID verification before SIGKILL; file-locked registry at `<userPiRoot>/state/orphan-workers.json`; honest about residual userspace TOCTOU window between start-time re-check and actual `process.kill`.
102
+ - **`src/runtime/pi-args.ts`** (heavily expanded, 342 lines) — `createSafeTempDir` walks the full ancestor chain rejecting symlinks; `buildPiWorkerArgs` builds the child argv safely (no shell); `cleanupTempDir` / `cleanupAllTrackedTempDirs` / `cleanupOrphanTempDirs` / `cleanupLegacyOrphanTempDirs` provide 4 layers of defense with bounded work.
103
+
104
+ ### Test Coverage
105
+ - **`test/unit/orphan-worker-registry.test.ts`** (NEW, 279 lines) — `registerWorker` / `unregisterWorker` / `cleanupOrphanWorkers` with `__test_setRegistryPath` for isolation; covers invalid PIDs, dedup, parent-PID tolerance, current-session protection, dead-PID pruning.
106
+ - **`test/unit/cleanup-orphan-temp.test.ts`** (NEW, 242 lines) — covers `cleanupTempDir` / `cleanupAllTrackedTempDirs` / `cleanupOrphanTempDirs` / `cleanupLegacyOrphanTempDirs` with `utimesSync` to simulate aged dirs; tests symlink skip, in-use skip, and `/tmp` legacy cleanup.
107
+ - **`test/integration/cleanup-full-flow.test.ts`** (NEW, 241 lines) — end-to-end integration of all cleanup layers, simulating a crashed session.
108
+
109
+ ### Documentation
110
+ - **`src/runtime/parent-guard.ts`** — added a "Trust model" JSDoc section explaining why `PI_CREW_PARENT_PID` is safe to pass in env (PID is not a secret), what residual risks remain (child can spoof before guard starts), and why the guard is a self-termination signal, not a security boundary.
111
+ - **`src/utils/safe-paths.ts:resolveRealContainedPath`** — added a "Security model — asymmetric ancestor handling" JSDoc section explaining why `baseDir` ancestors must exist (cannot validate otherwise) while target ancestors may be non-existent (for new-file creation).
112
+
113
+ ### Stats
114
+ - 23 commits since v0.6.2: `0aed8b5`, `152ac80`, `ee0ddb4`, `81b9608`, `5edcb18`, `8ba270d`, `ceb1cb1`, `a76932d`, `c9eb430`, `a192509`, `992231d`, `dbf7a48`, `e1f7dfe`, `1bf67eb`, `2b8f27a`, `de3f550`, `e1ea7d4`, `5819b18`, `cd7ef89`, `b782424`, `a0c2ba3`, `098c8a9`, `ba0ce54`, `aa457a5`, `04fe0be`, `f15ee98`
115
+ - 79 files changed (+3567 / -712)
116
+ - 1 new state fix: manifest/tasks mtime false positive
117
+ - 1 new file: `orphan-worker-registry.ts` (307 lines)
118
+ - 3 new test files: 762 lines
119
+
120
+ ## [0.6.2] — Issue #28 + #29 Fixes + Post-Review Hardening (2026-06-05)
121
+
122
+ ### Highlights
123
+ - **Issue #28 fixed**: `crew-init.ts` jiti namespace race — inline `parseRoot`/`safeJoin`/`safeDirname`/`safeResolve` helpers; jiti upgraded 2.6.1 → 2.7.0
124
+ - **Issue #29 fixed**: 11 hardcoded `.crew/state/runs/...` sites now use `projectCrewRoot()` for `.pi/teams/` fallback
125
+ - **Subagent defense-in-depth**: `record.promise` rejection can no longer crash pi via `unhandledRejection`
126
+ - **2 new MEDIUM-severity path-traversal vulnerabilities fixed** in `decision-ledger.ts` (7 functions) and `run-graph.ts` (3 functions)
127
+ - **F-8 safeJoin edge case** fixed (trailing separator handling)
128
+ - **~100 new tests** across 7 files (5 e2e scripts + 18 regression tests for the new fixes)
129
+
130
+ ### Security Fixes (MEDIUM)
131
+ - `decision-ledger.ts`: `initLedger`, `appendEntry`, `getLedger`, `getLatestDecision`, `summarizeLedger`, `promoteCandidate`, `decayCandidate` now call `assertSafePathId("runId", runId)` to prevent path-traversal
132
+ - `run-graph.ts`: `saveRunGraph`, `loadRunGraph`, `listRunGraphs` use `projectCrewRoot()` and `assertSafePathId("runId", runId)` (same bug class as issue #29)
133
+
134
+ ### Bug Fixes
135
+ - **F-1** (post-issue-#29 review): removed duplicate "Defense in depth" comment in `subagent-manager.ts`
136
+ - **F-3** (post-issue-#29 review): added F-6 documentation in `test/unit/crew-init.test.ts` header
137
+ - **F-8** (post-issue-#29 review): `safeJoin("/", "foo")` no longer produces `"//foo"`; trailing separator in parts is stripped (UNC paths preserved)
138
+ - **`crew-init.ts safeJoin`**: collapse internal runs of separator while preserving leading UNC `\\\\` and POSIX `/`
139
+
140
+ ### Test Coverage
141
+ - 9 new regression tests in `test/unit/issue-29-pi-paths.test.ts` (resolver precedence, `waitForRun` error message in both layouts, checkpoint/skill-effectiveness round-trip in `.pi/`-only project, decision-ledger path, defense-in-depth via real child process)
142
+ - 13 new tests in `test/unit/crew-init.test.ts` (F-1 UNC preservation, F-2 jiti race via `path` Proxy, F-3 `safeResolve` graceful degradation, F-4 `__test__internals` export convention, F-5 stale docstring, F-6 async-runner rationale, F-8 trailing-separator)
143
+ - 4 new tests in `test/unit/crew-init.test.ts` for `safeJoin` (F-8 regression coverage)
144
+ - 7 new tests in `test/unit/decision-ledger.test.ts` (path-traversal rejection)
145
+ - 3 new tests in `test/unit/run-graph.test.ts` (path-traversal rejection)
146
+ - 14 new tests in `test/unit/skill-effectiveness.test.ts` (cwd parameter migration)
147
+ - 5 new E2E scripts in `scripts/test-issue-29-*.ts`:
148
+ - `test-issue-29-e2e.ts` — unit-level integration
149
+ - `test-issue-29-crash.ts` — focused crash reproduction
150
+ - `test-issue-29-team-tool.ts` — slow-path early-exit error message
151
+ - `test-issue-29-real-tasks.ts` — full `executeTeamRun` pipeline in `.pi/`-only project (25 assertions)
152
+ - **`test-issue-29-real-runtime.ts`** — spawns REAL detached `background-runner.ts` process (most realistic)
153
+
154
+ ### Test Quality Improvements
155
+ - **F-4** (post-issue-#29 review): `crew-init.ts findProjectRoot` now accepts optional `path` dep; test passes the `stubPath` Proxy directly to source code (not a copy)
156
+ - **F-5** (post-issue-#29 review): defense-in-depth crash test now spawns a real child process so the host's `unhandledRejection` detector can actually fire (was previously a comment-only test)
157
+
158
+ ### Stats
159
+ - 7 source files changed (3 fixes + 1 new path-resolver usage)
160
+ - 7 test files changed (4 unit + 5 e2e scripts)
161
+ - 9 commits since v0.6.1: `e95e055`, `cd8c3b8`, `a80fe6c`, `362789c`, `0c78307`, `083afaf`, `d33b86c`, `03c0a20`, `1bedd24`, `0ce3d5a`, `b17fb6b`, `f8731e6`, `105d31d`, `00c66a5`, `2d49910`, `6cbbafa`
162
+
3
163
  ## [0.6.1] — Post-v0.6.0 Security Hardening + Test Coverage (2026-06-04)
4
164
 
5
165
  ### Highlights
package/README.md CHANGED
@@ -9,26 +9,28 @@ npm: pi-crew
9
9
  repo: https://github.com/baphuongna/pi-crew
10
10
  ```
11
11
 
12
- **v0.6.1**: See [CHANGELOG.md](CHANGELOG.md).
13
-
14
- ### Security highlights (v0.6.1)
15
-
16
- - **42+ security issues fixed**7 CRITICAL, 10 HIGH, 11 MEDIUM, 14 post-restart findings
17
- - **Timing-safe token comparison** constant-time compare for lock tokens and request IDs
18
- - **Environment leak prevention** — `sanitizeEnvSecrets()` on all child process spawns
19
- - **Shell injection hardened** `execFileSync` with array args; blocked `String.fromCharCode` bypass
20
- - **ReDoS-free secret redaction** — linear-time scanning in `redaction.ts`
21
- - **Sandbox prototype isolation** — `Object.freeze` scoped to VM context; `constructor` pattern blocked
22
- - **Symlink traversal prevention** — `fs.realpathSync` before path containment checks
23
- - **Safe-bash line-continuation hardening** — `$\n(evil)` command substitution bypass blocked
24
- - **Path traversal mitigated** — `resolveContainedPath`/`resolveRealContainedPath` across all file ops
25
- - **Memory leaks capped** — Maps, Sets, arrays bounded with eviction across all modules
26
- - **Event log race conditions fixed** sync/async queue unification
27
- - **Subagent record sanitization** — allow-listed field persistence
28
- - **~1,900 new tests**, 113 test files total suite ~4,600 tests, 0 failures
29
- - **42+ audit rounds, 160+ issues fixed** across all severity levels
30
-
31
- See [SECURITY-ISSUES.md](SECURITY-ISSUES.md) for the full list (SEC-001 SEC-007 all marked fixed).
12
+ **v0.6.3**: See [CHANGELOG.md](CHANGELOG.md).
13
+
14
+ ### Highlights (v0.6.2 → v0.6.3)
15
+
16
+ - **137 commits** since v0.6.1200 files changed (+16,955 / −2,057 lines)
17
+ - **4,792 tests**, 506 test files **0 failures** across the entire suite
18
+ - **Cross-platform CI green** — 0 failures on Ubuntu, macOS, and Windows
19
+ - **366 source files**, ~70K lines of TypeScript
20
+ - **Worktree precondition validation** — friendly errors instead of crashes when cwd is not a git repo or repo is dirty
21
+ - **Cross-platform path handling** — `canonicalizePath` with `realpathSync.native` for Windows short-name/long-name aliasing; macOS symlink resolution
22
+ - **Scheduled job lifecycle** — spawned runs are tracked, cancelling a job kills its runs
23
+ - **Heartbeat false-positive fix** — PID liveness gate prevents dead detection during long LLM responses
24
+ - **ENOENT crash fix** — prune/forget race no longer crashes pi when persisting to deleted runs
25
+ - **Pipe buffer deadlock fix** — test runner no longer deadlocks when OS pipe buffer fills
26
+ - **Plugin registry** extensible framework context injection for Next.js, Vite, Vitest
27
+ - **Health score system** — penalty-based scoring with time-series snapshots
28
+ - **CrewError taxonomy** E001–E006 structured error codes replacing raw throws
29
+ - **Atomic write v2** — fsync + rename pattern for crash-safe state persistence
30
+ - **Pre-push review**: 56 unpushed commits reviewed, 1 release blocker found and fixed
31
+ - **Security**: sandbox constructor escape strengthened; env-filter provider key handling fixed
32
+ - **State-store race fix** — manifest/tasks mtime false positive eliminated
33
+ - **Orphan worker/temp cleanup** — 4-layer defense with session-scoped tracking
32
34
 
33
35
  ---
34
36
 
@@ -50,6 +52,9 @@ See [SECURITY-ISSUES.md](SECURITY-ISSUES.md) for the full list (SEC-001 – SEC-
50
52
  - **Adaptive plan fanout** — single `assess` step lets a planner pick the smallest effective crew
51
53
  - **Adaptive workflows** — `implementation`, `review`, `parallel-research`, `research` workflows ship in `workflows/`
52
54
  - **Hardened secrets** — linear-time detection covers PEM keys, Authorization headers, Bearer tokens, and `key=value` patterns
55
+ - **Scheduled runs** — `schedule`/`scheduled` actions with cron, interval, and one-shot support; spawned runs tracked and auto-cancelled on job removal
56
+ - **Plugin system** — framework-aware context injection (Next.js, Vite, Vitest) via plugin registry
57
+ - **Health scoring** — penalty-based run health with time-series snapshots
53
58
 
54
59
  ---
55
60
 
@@ -120,13 +125,13 @@ When unsure which team/workflow fits:
120
125
 
121
126
  ## Builtin Teams
122
127
 
123
- | Team | Workflow | Mục đích |
128
+ | Team | Workflow | Purpose |
124
129
  |------|----------|----------|
125
- | `default` | explore → plan → execute → verify | Cân bằng, đa năng |
126
- | `fast-fix` | explore → execute → verify | Sửa bug nhỏ nhanh |
127
- | `implementation` | Adaptive planner quyết fanout | Multi-file implementation |
130
+ | `default` | explore → plan → execute → verify | Balanced, general-purpose |
131
+ | `fast-fix` | explore → execute → verify | Quick bug fixes |
132
+ | `implementation` | Adaptive planner decides fanout | Multi-file implementation |
128
133
  | `review` | explore → code-review → security-review → verify | Code review + security audit |
129
- | `research` | explore → analyze → write | Nghiên cứu viết tài liệu |
134
+ | `research` | explore → analyze → write | Research and documentation |
130
135
  | `parallel-research` | Parallel shards → synthesize → write | Multi-source research |
131
136
 
132
137
  ## Builtin Agents
@@ -180,7 +185,7 @@ Worktree mode creates an **isolated git worktree per task** — safe for paralle
180
185
  "action": "run",
181
186
  "team": "implementation",
182
187
  "goal": "Refactor auth",
183
- "worktree": { "enabled": true }
188
+ "workspaceMode": "worktree"
184
189
  }
185
190
  ```
186
191
 
@@ -189,10 +194,13 @@ Worktree mode creates an **isolated git worktree per task** — safe for paralle
189
194
  ```
190
195
 
191
196
  Requirements:
192
- - Git repository
193
- - Clean working tree (no uncommitted changes in the main worktree)
197
+ - Git repository (cwd must be inside a git repo)
198
+ - Clean working tree (no uncommitted changes in the leader worktree)
199
+ - Can be disabled via config: `requireCleanWorktreeLeader: false`
194
200
  - Worktrees auto-cleanup on run completion/cancel
195
201
 
202
+ If preconditions are not met, a friendly error message is returned instead of crashing.
203
+
196
204
  ---
197
205
 
198
206
  ## Configuration
@@ -255,11 +263,18 @@ Requirements:
255
263
  { "action": "artifacts", "runId": "team_..." }
256
264
  { "action": "cancel", "runId": "team_..." }
257
265
  { "action": "resume", "runId": "team_..." }
266
+ { "action": "retry", "runId": "team_..." }
267
+ { "action": "steer", "runId": "team_...", "taskId": "01_explore", "message": "Focus on src/ only" }
268
+ { "action": "respond", "runId": "team_...", "message": "Answer" }
269
+ { "action": "wait", "runId": "team_..." }
258
270
 
259
271
  // Discovery
260
272
  { "action": "list" }
261
- { "action": "get", "resource": "team", "name": "default" }
273
+ { "action": "get", "resource": "team", "team": "default" }
274
+ { "action": "get", "resource": "agent", "agent": "explorer" }
275
+ { "action": "get", "resource": "workflow", "workflow": "review" }
262
276
  { "action": "recommend", "goal": "Refactor auth flow" }
277
+ { "action": "search", "goal": "heartbeat detection" }
263
278
 
264
279
  // Resource management
265
280
  { "action": "create", "resource": "agent", "config": { "name": "api-reviewer", ... } }
@@ -281,12 +296,34 @@ Requirements:
281
296
  { "action": "autonomy", "profile": "assisted" }
282
297
 
283
298
  // Advanced
284
- { "action": "api", "runId": "team_...", "operation": "read-manifest" }
299
+ { "action": "api", "runId": "team_...", "config": { "operation": "read-manifest" } }
285
300
  { "action": "plan", "team": "default", "goal": "..." }
301
+ { "action": "orchestrate", "planPath": "plan.md", "team": "implementation", "goal": "..." }
302
+ { "action": "parallel", "config": { "tasks": [{"goal": "...", "agent": "explorer"}] } }
286
303
  { "action": "worktrees", "runId": "team_..." }
304
+ { "action": "graph", "runId": "team_..." }
305
+ { "action": "explain", "runId": "team_..." }
306
+ { "action": "health" }
307
+ { "action": "doctor" }
308
+ { "action": "cache" }
309
+ { "action": "invalidate", "runId": "team_..." }
310
+
311
+ // Scheduled runs
312
+ { "action": "schedule", "team": "fast-fix", "goal": "Run tests", "cron": "0 9 * * MON" }
313
+ { "action": "schedule", "team": "default", "goal": "...", "interval": 3600000 }
314
+ { "action": "schedule", "team": "research", "goal": "...", "once": "+10m" }
315
+ { "action": "scheduled" }
316
+
317
+ // Diagnostics & settings
318
+ { "action": "config" }
319
+ { "action": "settings" }
320
+ { "action": "autonomy" }
321
+ { "action": "anchor" }
322
+ { "action": "onboard" }
323
+ { "action": "auto-summarize" }
287
324
  ```
288
325
 
289
- 📖 Full actions reference (28 actions): [docs/actions-reference.md](docs/actions-reference.md)
326
+ 📖 Full actions reference (40+ actions): [docs/actions-reference.md](docs/actions-reference.md)
290
327
 
291
328
  ---
292
329
 
@@ -381,13 +418,13 @@ Your system prompt here.
381
418
  ```bash
382
419
  cd pi-crew
383
420
  npm install # dependencies
384
- npm test # unit + integration tests
421
+ npm test # unit + integration tests (~4,800 tests)
385
422
  npm run typecheck # tsc --noEmit
386
423
  npm run ci # full CI-equivalent check
387
424
  npm pack --dry-run # package verification
388
425
  ```
389
426
 
390
- CI runs on: `ubuntu-latest` · `windows-latest` · `macos-latest`
427
+ Stats: **366 source files** (70K lines) · **506 test files** (66K lines) · **4,792 tests, 0 failures** · **CI: Ubuntu ✅ macOS ✅ Windows ✅**
391
428
 
392
429
  ---
393
430
 
@@ -0,0 +1,189 @@
1
+ # Issue #29 Analysis: Hardcoded `.crew/state/runs` Path Crashes pi
2
+
3
+ > **Status**: ANALYZED — no code changes applied (per user request).
4
+ > **Reporter**: sethmorton (external, opened 2026-06-05)
5
+ > **Affects**: pi-crew 0.6.0 / 0.6.1 (issue present in v0.6.1 too — not fixed in any post-v0.6.1 commit as of `e95e055`).
6
+ > **Severity**: **CRITICAL** — full-harness uncaughtException, can crash pi.
7
+
8
+ ---
9
+
10
+ ## 1. Reporter's Claims — Verification Matrix
11
+
12
+ | Claim | Verified? | Evidence |
13
+ |---|---|---|
14
+ | `waitForRun()` hardcodes `.crew/state/runs/<runId>` at `run-tracker.ts:82` | ✅ YES | `src/runtime/run-tracker.ts:82` — `const runDir = path.join(cwd, ".crew", "state", "runs", runId);` |
15
+ | `projectCrewRoot()` correctly resolves to `.pi/teams/` for `.pi/`-only projects | ✅ YES | `src/utils/paths.ts:88-97` |
16
+ | The throw escapes as an unhandled promise rejection | ✅ PARTIAL | Both direct call sites (`team-tool.ts:817`, `team-tool/run.ts:211,344`) wrap in `try/catch`. **However**, the issue is real when `waitForRun` is called indirectly through `subagent-manager.ts`. |
17
+ | `subagent-manager.ts:250` re-throws via `record.promise` IIFE | ⚠️ LINE-DRIFT | Issue says `~line 250` — in current code (commit `e95e055`), `record.promise` IIFE starts at line 258 and the `throw error;` is at line 281. The mechanism is correct; line numbers drifted from prior code. |
18
+
19
+ ## 2. Root Cause Analysis
20
+
21
+ ### Why it crashes (in `.pi/`-only projects)
22
+
23
+ `projectCrewRoot(cwd)` (in `src/utils/paths.ts:88-97`) returns:
24
+
25
+ ```
26
+ 1. .crew/ (if exists)
27
+ 2. .pi/teams/ (if .pi/ exists and .crew/ doesn't)
28
+ 3. .crew/ (default, would be created by ensureCrewDirectory)
29
+ ```
30
+
31
+ In a `.pi/`-based project **without** `.crew/`, run state lives at:
32
+ ```
33
+ .pi/teams/state/runs/<runId>/
34
+ ```
35
+
36
+ But `waitForRun()` (line 82) hardcodes:
37
+ ```
38
+ .crew/state/runs/<runId>/
39
+ ```
40
+
41
+ The early-exit check `fs.existsSync(runDir)` returns `false`, throwing:
42
+ ```
43
+ Error: Run <runId> not found. No run directory at <cwd>/.crew/state/runs/<runId>
44
+ ```
45
+
46
+ ### Why this becomes a `uncaughtException`
47
+
48
+ The throw in `waitForRun` propagates differently depending on caller:
49
+
50
+ | Caller | Wrapped in try/catch? | Result |
51
+ |---|---|---|
52
+ | `team-tool.ts:817` (wait action) | ✅ YES | Returns error to user — safe |
53
+ | `team-tool/run.ts:211` (async.run) | ✅ YES | Returns error to user — safe |
54
+ | `team-tool/run.ts:344` (foreground) | ✅ YES | Returns error to user — safe |
55
+ | **`subagent-manager.ts:270` (pollRunToTerminal)** | ⚠️ **NO** | `record.promise` rejects → line 281 `throw error` re-throws → if no caller awaits → **unhandled rejection** |
56
+
57
+ The `subagent-manager.ts` path is the actual crash site. Direct `waitForRun` calls in `team-tool.ts` are safe.
58
+
59
+ ### Why the run "exists" in `.pi/teams/...`
60
+
61
+ The background worker process (`background-runner.ts`) correctly **reads/writes** to the right path **once it uses `projectCrewRoot()`** — but it does NOT. The same hardcoding is present in `background-runner.ts:139, 172`. So the **background worker itself is writing to `.crew/state/runs/...` (wrong path)**, while the **manifest claims it lives at `.pi/teams/state/runs/...`** (correct path).
62
+
63
+ Wait — let me re-verify this. The manifest is created by which process?
64
+
65
+ Actually, the **run manifest is created at the orchestrator** (using `projectCrewRoot`), and the **background worker reads/writes log/exit-code files using the same wrong path**. The manifest's `stateRoot` field is correct (`.pi/teams/...`), but the background worker writes its log to `<cwd>/.crew/state/runs/.../background.log`, which is the wrong path.
66
+
67
+ Result: the manifest correctly points to `.pi/teams/...` but the background worker logs/exit-codes go to `.crew/.../background.log`. The manifest loader (`loadRunManifestById` → `scopeBaseRoot` → `projectCrewRoot`) reads from the correct path. So if the manifest is created and persists, polling SHOULD work. The early-exit `fs.existsSync` check fires only on the very first poll iteration before any manifest has been written.
68
+
69
+ ## 3. Affected Sites — Complete Inventory
70
+
71
+ Hardcoded `.crew/state/runs/...` paths that ignore `projectCrewRoot()`:
72
+
73
+ | File | Line | Code | Used for |
74
+ |---|---|---|---|
75
+ | `src/runtime/run-tracker.ts` | 82 | `path.join(cwd, ".crew", "state", "runs", runId)` | **CRASH SITE**: `waitForRun()` early-exit |
76
+ | `src/runtime/background-runner.ts` | 139 | `path.join(_cwd, ".crew/state/runs", _runId, "background.log")` | Background worker log redirect |
77
+ | `src/runtime/background-runner.ts` | 172 | `path.join(cwd, ".crew", "state", "runs", runId, "exit-code.txt")` | Exit code persistence |
78
+ | `src/runtime/skill-effectiveness.ts` | 115 | `join(process.cwd(), ".crew/state/runs/${runId}/skill-metrics.jsonl")` | Skill metrics storage |
79
+ | `src/runtime/skill-effectiveness.ts` | 125 | `join(process.cwd(), ".crew/state/runs/${runId}/skill-activations.jsonl")` | Skill activation storage |
80
+ | `src/runtime/checkpoint.ts` | 166 | `path.join(process.cwd(), ".crew/state/runs", runId)` | `saveCheckpoint` |
81
+ | `src/runtime/checkpoint.ts` | 177 | `path.join(process.cwd(), ".crew/state/runs", runId)` | `loadCheckpoint` |
82
+ | `src/runtime/checkpoint.ts` | 188 | `path.join(process.cwd(), ".crew/state/runs", runId)` | `clearCheckpoint` |
83
+ | `src/runtime/checkpoint.ts` | 199 | `path.join(process.cwd(), ".crew/state/runs", runId)` | `hasCheckpoint` |
84
+ | `src/runtime/checkpoint.ts` | 209 | `path.join(process.cwd(), ".crew/state/runs", runId)` | `listCheckpoints` |
85
+ | `src/state/decision-ledger.ts` | 29 | `stateRoot ?? \`.crew/state/runs/${runId}\`` | `getLedgerPath` fallback (always hit when called without `stateRoot`) |
86
+
87
+ **11 sites across 5 files** confirmed affected.
88
+
89
+ ### Already correct (for reference)
90
+
91
+ | File | Line | Why correct |
92
+ |---|---|---|
93
+ | `src/state/crew-init.ts` | 209 | `safeJoin(crewRoot, "state", "runs")` — uses `crewRoot` parameter |
94
+ | `src/extension/team-tool.ts` | 1210 | `path.join(projectCrewRoot(ctx.cwd), "state", "runs", params.runId)` ✓ |
95
+ | `src/extension/team-onboard.ts` | 28 | `path.join(crewRoot, "state", "runs")` — uses parameter |
96
+ | `src/runtime/stale-reconciler.ts` | 389 | Inside `tmpDir` workspace scan — these are pi-crew's own zombie workspaces, not user `.pi/` projects, so this is acceptable (but inconsistent) |
97
+
98
+ ## 4. Why the Bug Survives (it should have been caught)
99
+
100
+ - ✅ `loadRunManifestById` and `loadRunManifestByIdAsync` in `state-store.ts` correctly use `projectCrewRoot()` via `scopeBaseRoot` → `useProjectState`.
101
+ - ❌ `waitForRun()` and the 10 other sites above bypass the resolver and hardcode the path.
102
+ - The hardcoded path is **inconsistent** with the resolver — clearly a copy-paste/regression.
103
+ - The issue is **only triggered in `.pi/`-only projects** (no `.crew/`). Standard `.crew/` projects (the original pi-crew use case) never hit it.
104
+
105
+ ## 5. Recommended Fix (NOT APPLIED per user request)
106
+
107
+ ### Phase 1 — Use the resolver everywhere
108
+
109
+ Replace each hardcoded path with:
110
+
111
+ ```ts
112
+ import { projectCrewRoot } from "../utils/paths.ts";
113
+ // ...
114
+ const stateRoot = path.join(projectCrewRoot(cwd), "state", "runs", runId);
115
+ ```
116
+
117
+ For sites that take `process.cwd()`, switch to taking a `cwd` parameter (callers pass the real cwd). This is a small refactor for `skill-effectiveness.ts` and `checkpoint.ts` since their current API uses `process.cwd()`.
118
+
119
+ ### Phase 2 — Defense in depth (the reporter's second suggestion)
120
+
121
+ The reporter's point about `subagent-manager.ts` is also valid: **a single run failure should never crash the harness**. The current pattern at line 281 `throw error; // H4: Propagate rejection` deliberately rejects the promise, but if no one awaits it, it becomes unhandled.
122
+
123
+ Two options:
124
+
125
+ **A) Add a safety net in `subagent-manager.ts` (recommended)**:
126
+
127
+ ```ts
128
+ private start(record: SubagentRecord, ...) {
129
+ // ...
130
+ record.promise = (async () => { ... })();
131
+ // Defense in depth: a subagent failure should never crash pi.
132
+ // Attach a no-op catch so that even if no caller awaits, the rejection
133
+ // doesn't propagate to unhandledRejection → uncaughtException.
134
+ record.promise.catch((error) => {
135
+ logInternalError("subagent-manager.start.unhandled", error, `id=${record.id}`);
136
+ });
137
+ }
138
+ ```
139
+
140
+ This protects the harness against **any** future rejection path, not just the one fixed in Phase 1.
141
+
142
+ **B) Remove the `throw error;` at line 281** — the record is already marked `error` status, the caller can read it. This is a deeper change to the API contract.
143
+
144
+ Option (A) is the smaller, safer change.
145
+
146
+ ## 6. Test Coverage
147
+
148
+ Tests exist for `checkpoint` (`checkpoint.test.ts`, `checkpoint-cov.test.ts`) and `skill-effectiveness` (`skill-effectiveness.test.ts`) but they likely use a `.crew/`-based temp project, so they wouldn't catch this bug. A regression test should:
149
+
150
+ 1. Create a temp project with `.pi/` (no `.crew/`)
151
+ 2. Call each affected function (`waitForRun`, `saveCheckpoint`, `getSkillMetricsPath`, etc.)
152
+ 3. Assert the result path is `.pi/teams/...` (not `.crew/...`)
153
+
154
+ For the `subagent-manager.ts` defense-in-depth: spawn a subagent whose `runner` rejects, assert pi does not exit and the record is marked `error`.
155
+
156
+ ## 7. Impact Assessment
157
+
158
+ - **Blast radius**: All pi-crew users with `.pi/`-only projects.
159
+ - **Trigger frequency**: Low — requires the background worker to fail/die AND `waitForRun` to be called indirectly via `subagent-manager.ts`. The `team-tool` direct paths are safe.
160
+ - **Severity**: CRITICAL — full-harness crash with misleading "Run not found" error pointing at a path that, by design, should never exist.
161
+
162
+ ## 8. Recommended PR Description (draft)
163
+
164
+ > **Title**: fix(issue #29): use projectCrewRoot() for run-state paths in all 11 affected sites
165
+ >
166
+ > **Body**:
167
+ >
168
+ > The `.pi/`-based project layout (`.pi/teams/`) is not handled by the 11 hardcoded `.crew/state/runs/...` sites listed in the analysis. The most severe site is `waitForRun()` in `src/runtime/run-tracker.ts:82`, which throws on a non-existent path and (via `subagent-manager.ts:281`) can escape as an unhandled rejection that crashes pi.
169
+ >
170
+ > Fix:
171
+ > 1. Replace all 11 hardcoded paths with `path.join(projectCrewRoot(cwd), "state", "runs", runId)`.
172
+ > 2. For sites that use `process.cwd()` (`skill-effectiveness.ts`, `checkpoint.ts`), switch to taking a `cwd` parameter.
173
+ > 3. Add `record.promise.catch(...)` in `subagent-manager.ts:start()` as defense in depth — a subagent failure should never crash the harness.
174
+ > 4. Add regression tests using a `.pi/`-only temp project.
175
+ >
176
+ > Closes #29.
177
+
178
+ ---
179
+
180
+ ## Status
181
+
182
+ - ✅ Issue reproduced in code (all 11 sites verified)
183
+ - ✅ Root cause identified
184
+ - ✅ Fix plan drafted
185
+ - ❌ **Code changes NOT applied** (per user request: "chưa thực hiện sửa gì cả")
186
+ - ❌ Tests NOT added
187
+ - ❌ v0.6.2 NOT released
188
+
189
+ Sẵn sàng apply khi user yêu cầu.