pi-crew 0.6.0 → 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 (135) hide show
  1. package/CHANGELOG.md +225 -0
  2. package/README.md +70 -31
  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/agents/agent-config.ts +2 -1
  7. package/src/benchmark/feedback-loop.ts +4 -2
  8. package/src/config/config.ts +106 -15
  9. package/src/errors.ts +107 -0
  10. package/src/extension/async-notifier.ts +6 -2
  11. package/src/extension/crew-cleanup.ts +8 -5
  12. package/src/extension/cross-extension-rpc.ts +48 -0
  13. package/src/extension/management.ts +464 -109
  14. package/src/extension/register.ts +194 -34
  15. package/src/extension/registration/commands.ts +4 -3
  16. package/src/extension/registration/subagent-helpers.ts +2 -2
  17. package/src/extension/registration/subagent-tools.ts +3 -1
  18. package/src/extension/registration/team-tool.ts +3 -1
  19. package/src/extension/registration/viewers.ts +3 -2
  20. package/src/extension/run-export.ts +16 -1
  21. package/src/extension/run-import.ts +16 -0
  22. package/src/extension/team-tool/anchor.ts +5 -1
  23. package/src/extension/team-tool/api.ts +12 -7
  24. package/src/extension/team-tool/cancel.ts +3 -3
  25. package/src/extension/team-tool/config-patch.ts +15 -1
  26. package/src/extension/team-tool/explain.ts +1 -1
  27. package/src/extension/team-tool/handle-schedule.ts +40 -0
  28. package/src/extension/team-tool/inspect.ts +3 -3
  29. package/src/extension/team-tool/lifecycle-actions.ts +4 -4
  30. package/src/extension/team-tool/respond.ts +2 -2
  31. package/src/extension/team-tool/run.ts +60 -11
  32. package/src/extension/team-tool/status.ts +1 -1
  33. package/src/extension/team-tool.ts +175 -47
  34. package/src/hooks/registry.ts +84 -12
  35. package/src/hooks/types.ts +12 -3
  36. package/src/i18n.ts +15 -2
  37. package/src/observability/exporters/otlp-exporter.ts +73 -0
  38. package/src/plugins/plugin-define.ts +6 -0
  39. package/src/plugins/plugin-registry.ts +32 -0
  40. package/src/plugins/plugins/index.ts +3 -0
  41. package/src/plugins/plugins/nextjs.ts +19 -0
  42. package/src/plugins/plugins/vite.ts +14 -0
  43. package/src/plugins/plugins/vitest.ts +14 -0
  44. package/src/runtime/adaptive-plan.ts +24 -0
  45. package/src/runtime/agent-control.ts +6 -3
  46. package/src/runtime/async-runner.ts +86 -3
  47. package/src/runtime/background-runner.ts +529 -144
  48. package/src/runtime/chain-parser.ts +5 -1
  49. package/src/runtime/chain-runner.ts +67 -3
  50. package/src/runtime/checkpoint.ts +262 -180
  51. package/src/runtime/child-pi.ts +165 -38
  52. package/src/runtime/crash-recovery.ts +25 -14
  53. package/src/runtime/crew-agent-records.ts +6 -3
  54. package/src/runtime/cross-extension-rpc.ts +34 -8
  55. package/src/runtime/diagnostic-export.ts +4 -5
  56. package/src/runtime/dynamic-script-runner.ts +9 -6
  57. package/src/runtime/foreground-watchdog.ts +3 -3
  58. package/src/runtime/heartbeat-watcher.ts +19 -2
  59. package/src/runtime/intercom-bridge.ts +7 -0
  60. package/src/runtime/iteration-hooks.ts +1 -1
  61. package/src/runtime/live-agent-manager.ts +6 -3
  62. package/src/runtime/live-irc.ts +4 -2
  63. package/src/runtime/manifest-cache.ts +4 -0
  64. package/src/runtime/orphan-worker-registry.ts +444 -0
  65. package/src/runtime/parallel-utils.ts +2 -1
  66. package/src/runtime/parent-guard.ts +70 -16
  67. package/src/runtime/pi-args.ts +437 -14
  68. package/src/runtime/pi-spawn.ts +1 -0
  69. package/src/runtime/post-checks.ts +11 -4
  70. package/src/runtime/retry-runner.ts +9 -3
  71. package/src/runtime/{drift-detectors.ts → run-drift.ts} +1 -1
  72. package/src/runtime/run-tracker.ts +38 -10
  73. package/src/runtime/sandbox.ts +42 -21
  74. package/src/runtime/scheduler.ts +25 -2
  75. package/src/runtime/semaphore.ts +2 -1
  76. package/src/runtime/settings-store.ts +14 -2
  77. package/src/runtime/skill-effectiveness.ts +109 -64
  78. package/src/runtime/skill-instructions.ts +114 -33
  79. package/src/runtime/stale-reconciler.ts +310 -69
  80. package/src/runtime/subagent-manager.ts +265 -53
  81. package/src/runtime/subprocess-tool-registry.ts +2 -2
  82. package/src/runtime/task-health.ts +76 -0
  83. package/src/runtime/task-id.ts +20 -0
  84. package/src/runtime/task-packet.ts +13 -1
  85. package/src/runtime/task-runner/live-executor.ts +1 -1
  86. package/src/runtime/task-runner/progress.ts +1 -1
  87. package/src/runtime/task-runner/state-helpers.ts +110 -6
  88. package/src/runtime/task-runner.ts +98 -67
  89. package/src/runtime/team-runner.ts +186 -20
  90. package/src/runtime/usage-tracker.ts +4 -2
  91. package/src/runtime/verification-gates.ts +36 -9
  92. package/src/schema/team-tool-schema.ts +27 -9
  93. package/src/skills/discover-skills.ts +9 -3
  94. package/src/state/active-run-registry.ts +170 -38
  95. package/src/state/artifact-store.ts +25 -13
  96. package/src/state/atomic-write-v2.ts +86 -0
  97. package/src/state/atomic-write.ts +346 -55
  98. package/src/state/blob-store.ts +178 -10
  99. package/src/state/contracts.ts +2 -1
  100. package/src/state/crew-init.ts +161 -28
  101. package/src/state/decision-ledger.ts +172 -111
  102. package/src/state/event-log-rotation.ts +82 -52
  103. package/src/state/event-log.ts +270 -75
  104. package/src/state/health-store.ts +71 -0
  105. package/src/state/hook-instinct-bridge.ts +2 -1
  106. package/src/state/locks.ts +109 -20
  107. package/src/state/mailbox.ts +45 -7
  108. package/src/state/observation-store.ts +4 -1
  109. package/src/state/run-graph.ts +141 -130
  110. package/src/state/run-metrics.ts +24 -8
  111. package/src/state/state-store.ts +333 -44
  112. package/src/state/task-claims.ts +9 -2
  113. package/src/tools/safe-bash.ts +69 -20
  114. package/src/types/new-api-types.ts +10 -5
  115. package/src/ui/keybinding-map.ts +2 -1
  116. package/src/ui/live-run-sidebar.ts +1 -1
  117. package/src/ui/loaders.ts +4 -0
  118. package/src/ui/overlays/agent-picker-overlay.ts +1 -1
  119. package/src/ui/overlays/mailbox-detail-overlay.ts +1 -1
  120. package/src/ui/run-action-dispatcher.ts +4 -3
  121. package/src/ui/run-snapshot-cache.ts +1 -1
  122. package/src/ui/status-colors.ts +2 -1
  123. package/src/ui/syntax-highlight.ts +2 -1
  124. package/src/ui/tool-render.ts +13 -3
  125. package/src/utils/env-filter.ts +66 -0
  126. package/src/utils/file-coalescer.ts +9 -1
  127. package/src/utils/fs-watch.ts +4 -2
  128. package/src/utils/gh-protocol.ts +2 -1
  129. package/src/utils/paths.ts +80 -5
  130. package/src/utils/redaction.ts +6 -1
  131. package/src/utils/safe-paths.ts +287 -10
  132. package/src/worktree/cleanup.ts +117 -26
  133. package/src/worktree/worktree-manager.ts +128 -15
  134. package/test-bugs-all.mjs +10 -6
  135. package/test-integration-check.ts +4 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,230 @@
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
+
163
+ ## [0.6.1] — Post-v0.6.0 Security Hardening + Test Coverage (2026-06-04)
164
+
165
+ ### Highlights
166
+ - **42+ security issues fixed** — 7 CRITICAL, 10 HIGH, 11 MEDIUM, 14 post-restart review findings
167
+ - **~1,900 new tests** across 113+ test files — total suite now ~4,600 tests
168
+ - **38 dead exports cleaned** across 19 modules
169
+ - **12 `any` types replaced** with proper TypeScript types
170
+ - **Full battle-testing** — 2 Pi restart cycles, all team types, management operations verified
171
+
172
+ ### Security Fixes (CRITICAL)
173
+ - `async-runner.ts`: Environment variable leak in child process — sanitized with `sanitizeEnvSecrets()`
174
+ - `verification-gates.ts`: Shell injection via user-controlled strings — switched to `execFileSync`
175
+ - `sandbox.ts`: `String.fromCharCode` bypass — added `constructor` to `FORBIDDEN_PATTERNS`
176
+ - `locks.ts`: Timing-unsafe comparison on lock tokens — replaced with constant-time compare
177
+ - `event-log.ts`: Request IDs logged in plaintext — now hashed before logging
178
+ - `team-runner.ts`: Missing heartbeat for long-running tasks — added 30s heartbeat writer
179
+ - `worktree-manager.ts`: Environment secrets leaked to git subprocesses — `sanitizeEnvSecrets()`
180
+
181
+ ### Security Fixes (HIGH)
182
+ - `preStepScript` symlink traversal — `fs.realpathSync` before path containment check
183
+ - `childEnvAllowList` wildcard patterns (`LC_*`, `XDG_*`) could leak secrets
184
+ - Event log sync/async race condition — route sync `appendEvent` through async queue
185
+ - Subagent record validation — `sanitizePersistedRecord()` with allow-listed fields
186
+ - Verification gate redirect — allow single `>` for `2>&1`, block `>>` and `<[^&]`
187
+ - `allowPatterns` validation — reject patterns matching empty strings
188
+
189
+ ### Security Fixes (MEDIUM)
190
+ - `logInternalError` import paths normalized across all modules
191
+ - `Object.freeze()` narrowing fix — use `Readonly<{...}>` explicit types
192
+ - NTFS mtime granularity — write-first, `utimes`-after for cache invalidation
193
+ - Windows path separators — platform-agnostic assertions in tests
194
+ - `executeUnchecked` visibility — `__test_executeUnchecked` export pattern
195
+ - `seedPaths` containment — `normalizeSeedPaths()` validates paths stay within `repoRoot`
196
+
197
+ ### Code Quality
198
+ - 38 dead/unused exports removed across 19 source modules
199
+ - 12 `any` types replaced with proper interfaces
200
+ - `enforceLabelCap` MRU correctness — `delete`-then-`set` to maintain Map insertion order
201
+ - `readIfSmall` bounded reads — `Buffer.alloc` + `fs.readSync` instead of `readFileSync`
202
+
203
+ ### Test Coverage
204
+ - 113 new test files, ~1,900 new test cases
205
+ - Modules now covered: config, extension, workflow, subagent, observability, runtime, graph,
206
+ heartbeat, permissions, state, locks, event-log, safe-bash, sandbox, verification-gates,
207
+ async-runner, team-runner, background-runner, worktree, fingerprint, BM25 search, and more
208
+ - Windows CI verified: path separators, `npx.cmd` resolution, NTFS mtime all pass
209
+ - Test runner wrapper (`scripts/test-runner.mjs`) ensures non-zero exit on failures
210
+
211
+ ### Stats
212
+ - Test suite: ~4,600 pass, 0 fail
213
+ - TypeScript: 0 errors
214
+ - Lines added since v0.6.0: 22,520 (742 src + 21,777 test)
215
+ - Files changed: 204
216
+ - Security issues fixed: 42+
217
+ - Audit rounds: 42 (including post-v0.6.0 battle-testing)
218
+
219
+ ## [0.6.0] — Source Tour Patterns + 15 New Modules (2026-06-03)
220
+
221
+ ### Highlights
222
+ - **15 upstream patterns implemented** from 63-repository source tour
223
+ - **10 new source modules** (2,267 LOC): chain-parser, run-drift, intercom-bridge,
224
+ plan-templates, task-id, context-retrieval, intermediate-store, fingerprint,
225
+ memory-store, observation-store
226
+ - **37 skills reviewed** with origin fields, all passing validation
227
+
3
228
  ## [0.5.22] — Remaining Issues from Ultimate Sweep (2026-06-03)
4
229
 
5
230
  ### Highlights
package/README.md CHANGED
@@ -9,24 +9,28 @@ npm: pi-crew
9
9
  repo: https://github.com/baphuongna/pi-crew
10
10
  ```
11
11
 
12
- **v0.5.22**: See [CHANGELOG.md](CHANGELOG.md).
13
-
14
- ### Security highlights (v0.5.22)
15
-
16
- - **ReDoS-free secret redaction** — linear-time scanning in `redaction.ts`; no catastrophic backtracking
17
- - **v8.deserialize hardened** `BINARY_MAGIC` header guards on registry binaries prevent untrusted-file RCE
18
- - **Cache lock protection** — `withFileLockSync` and atomic writes across `run-cache.ts` and `state-store.ts`
19
- - **Shell injection prevented** `execFileSync` with array args everywhere (no shell-interpreted strings)
20
- - **Safe-bash line-continuation hardening** — `$\n(evil)` command substitution bypass blocked
21
- - **Sandbox prototype isolation** — `Object.freeze` scoped to VM context (not host process)
22
- - **Path traversal mitigated** — `resolveContainedPath`/`resolveRealContainedPath` across all file ops
23
- - **TOCTOU-free file ops** — atomic `mkdirSync` in `crew-init.ts`; `realpath`-based path validation
24
- - **Memory leaks capped** — Maps, Sets, arrays bounded with eviction across all modules
25
- - **Inline secret detection** — `token=`, `api_key=`, `password=` patterns redacted at event/mailbox boundaries
26
- - **CI exit code enforced** — `test-runner.mjs` wrapper ensures non-zero exit on failures
27
- - **38 audit rounds, 160+ issues fixed** — 3 CRITICAL + 6 HIGH + 3 MEDIUM security issues resolved
28
-
29
- 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.1 200 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
30
34
 
31
35
  ---
32
36
 
@@ -48,6 +52,9 @@ See [SECURITY-ISSUES.md](SECURITY-ISSUES.md) for the full list (SEC-001 – SEC-
48
52
  - **Adaptive plan fanout** — single `assess` step lets a planner pick the smallest effective crew
49
53
  - **Adaptive workflows** — `implementation`, `review`, `parallel-research`, `research` workflows ship in `workflows/`
50
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
51
58
 
52
59
  ---
53
60
 
@@ -118,13 +125,13 @@ When unsure which team/workflow fits:
118
125
 
119
126
  ## Builtin Teams
120
127
 
121
- | Team | Workflow | Mục đích |
128
+ | Team | Workflow | Purpose |
122
129
  |------|----------|----------|
123
- | `default` | explore → plan → execute → verify | Cân bằng, đa năng |
124
- | `fast-fix` | explore → execute → verify | Sửa bug nhỏ nhanh |
125
- | `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 |
126
133
  | `review` | explore → code-review → security-review → verify | Code review + security audit |
127
- | `research` | explore → analyze → write | Nghiên cứu viết tài liệu |
134
+ | `research` | explore → analyze → write | Research and documentation |
128
135
  | `parallel-research` | Parallel shards → synthesize → write | Multi-source research |
129
136
 
130
137
  ## Builtin Agents
@@ -178,7 +185,7 @@ Worktree mode creates an **isolated git worktree per task** — safe for paralle
178
185
  "action": "run",
179
186
  "team": "implementation",
180
187
  "goal": "Refactor auth",
181
- "worktree": { "enabled": true }
188
+ "workspaceMode": "worktree"
182
189
  }
183
190
  ```
184
191
 
@@ -187,10 +194,13 @@ Worktree mode creates an **isolated git worktree per task** — safe for paralle
187
194
  ```
188
195
 
189
196
  Requirements:
190
- - Git repository
191
- - 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`
192
200
  - Worktrees auto-cleanup on run completion/cancel
193
201
 
202
+ If preconditions are not met, a friendly error message is returned instead of crashing.
203
+
194
204
  ---
195
205
 
196
206
  ## Configuration
@@ -253,11 +263,18 @@ Requirements:
253
263
  { "action": "artifacts", "runId": "team_..." }
254
264
  { "action": "cancel", "runId": "team_..." }
255
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_..." }
256
270
 
257
271
  // Discovery
258
272
  { "action": "list" }
259
- { "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" }
260
276
  { "action": "recommend", "goal": "Refactor auth flow" }
277
+ { "action": "search", "goal": "heartbeat detection" }
261
278
 
262
279
  // Resource management
263
280
  { "action": "create", "resource": "agent", "config": { "name": "api-reviewer", ... } }
@@ -279,12 +296,34 @@ Requirements:
279
296
  { "action": "autonomy", "profile": "assisted" }
280
297
 
281
298
  // Advanced
282
- { "action": "api", "runId": "team_...", "operation": "read-manifest" }
299
+ { "action": "api", "runId": "team_...", "config": { "operation": "read-manifest" } }
283
300
  { "action": "plan", "team": "default", "goal": "..." }
301
+ { "action": "orchestrate", "planPath": "plan.md", "team": "implementation", "goal": "..." }
302
+ { "action": "parallel", "config": { "tasks": [{"goal": "...", "agent": "explorer"}] } }
284
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" }
285
324
  ```
286
325
 
287
- 📖 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)
288
327
 
289
328
  ---
290
329
 
@@ -379,13 +418,13 @@ Your system prompt here.
379
418
  ```bash
380
419
  cd pi-crew
381
420
  npm install # dependencies
382
- npm test # unit + integration tests
421
+ npm test # unit + integration tests (~4,800 tests)
383
422
  npm run typecheck # tsc --noEmit
384
423
  npm run ci # full CI-equivalent check
385
424
  npm pack --dry-run # package verification
386
425
  ```
387
426
 
388
- 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 ✅**
389
428
 
390
429
  ---
391
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.