pi-crew 0.8.14 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +271 -0
- package/README.md +112 -2
- package/docs/FEATURE_INTAKE.md +1 -1
- package/docs/HARNESS.md +20 -19
- package/docs/PROJECT_REVIEW.md +132 -133
- package/docs/PROJECT_REVIEW_FIXES.md +130 -131
- package/docs/actions-reference.md +127 -121
- package/docs/architecture.md +1 -1
- package/docs/code-review-2026-05-11.md +134 -134
- package/docs/commands-reference.md +108 -106
- package/docs/comparison-pi-subagents-vs-pi-crew.md +105 -105
- package/docs/deep-review-report.md +1 -1
- package/docs/dynamic-workflows.md +90 -0
- package/docs/fixes/BATCH_A_H1_H2.md +17 -17
- package/docs/fixes/bug-007-async-notifier-stale-ctx.md +23 -23
- package/docs/followup-plan-2026-05-12.md +135 -135
- package/docs/followup-review-2026-05-12.md +86 -86
- package/docs/followup-review-round3-2026-05-12.md +123 -123
- package/docs/goals.md +59 -0
- package/docs/implementation-plan-top3.md +4 -4
- package/docs/issue-29-analysis.md +2 -2
- package/docs/oh-my-pi-research.md +154 -154
- package/docs/optimization-plan.md +2 -0
- package/docs/perf/baseline-2026-05.md +9 -9
- package/docs/perf/final-report-2026-05.md +2 -2
- package/docs/perf/sprint-1-report.md +2 -2
- package/docs/perf/sprint-2-report.md +1 -1
- package/docs/perf/upgrade-plan-2026-05.md +72 -72
- package/docs/pi-crew-bugs.md +230 -230
- package/docs/pi-crew-investigation-report.md +102 -102
- package/docs/pi-crew-test-round5.md +4 -4
- package/docs/runtime-analysis-child-vs-live.md +57 -57
- package/docs/runtime-migration-in-process-analysis.md +97 -97
- package/package.json +2 -4
- package/skills/orchestration/SKILL.md +11 -11
- package/src/agents/agent-config.ts +4 -0
- package/src/config/config.ts +39 -0
- package/src/config/types.ts +11 -0
- package/src/extension/action-suggestions.ts +2 -1
- package/src/extension/async-notifier.ts +10 -0
- package/src/extension/help.ts +14 -0
- package/src/extension/registration/commands.ts +27 -0
- package/src/extension/team-tool/destructive-gate.ts +1 -1
- package/src/extension/team-tool/goal-wrap.ts +288 -0
- package/src/extension/team-tool/goal.ts +405 -0
- package/src/extension/team-tool/run.ts +103 -4
- package/src/extension/team-tool/workflow-manage.ts +194 -0
- package/src/extension/team-tool.ts +20 -0
- package/src/hooks/types.ts +3 -1
- package/src/runtime/async-runner.ts +24 -2
- package/src/runtime/background-runner.ts +68 -19
- package/src/runtime/child-pi.ts +6 -1
- package/src/runtime/completion-guard.ts +1 -1
- package/src/runtime/dynamic-workflow-context.ts +450 -0
- package/src/runtime/dynamic-workflow-runner.ts +180 -0
- package/src/runtime/global-worker-cap.ts +96 -0
- package/src/runtime/goal-evaluator.ts +294 -0
- package/src/runtime/goal-loop-runner.ts +612 -0
- package/src/runtime/goal-state-store.ts +209 -0
- package/src/runtime/pi-args.ts +10 -2
- package/src/runtime/result-extractor.ts +32 -0
- package/src/runtime/team-runner.ts +11 -1
- package/src/runtime/verification-gates.ts +85 -5
- package/src/runtime/verification-integrity.ts +110 -0
- package/src/runtime/verification-worktree.ts +136 -0
- package/src/runtime/workspace-lock.ts +448 -0
- package/src/schema/config-schema.ts +26 -0
- package/src/schema/team-tool-schema.ts +39 -4
- package/src/state/atomic-write.ts +9 -0
- package/src/state/contracts.ts +14 -0
- package/src/state/crew-init.ts +18 -5
- package/src/state/event-log.ts +7 -1
- package/src/state/state-store.ts +2 -0
- package/src/state/types.ts +82 -0
- package/src/state/worker-atomic-writer.ts +176 -0
- package/src/utils/redaction.ts +104 -24
- package/src/workflows/discover-workflows.ts +25 -1
- package/src/workflows/workflow-config.ts +13 -0
- package/teams/parallel-research.team.md +1 -1
- package/workflows/examples/hello.dwf.ts +24 -0
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
# Bug #7: Async notifier "stale ctx detected; stopping notifier" —
|
|
1
|
+
# Bug #7: Async notifier "stale ctx detected; stopping notifier" — does not restart
|
|
2
2
|
|
|
3
3
|
| Field | Value |
|
|
4
4
|
|---|---|
|
|
5
5
|
| **Severity** | 🔴 HIGH |
|
|
6
6
|
| **Status** | Root cause confirmed, fix pending |
|
|
7
|
-
| **Affected** |
|
|
8
|
-
| **Symptom** |
|
|
7
|
+
| **Affected** | All pi-crew users after a Pi session restarts/compacts |
|
|
8
|
+
| **Symptom** | After restart/compact, the notifier stops completely — run-completion notifications are no longer received |
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Description
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
After Pi restarts (or /clear, /compact, session switch), the following error appears:
|
|
13
13
|
```
|
|
14
14
|
[pi-crew] async notifier stale ctx detected; stopping notifier.
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
After that, pi-crew **no longer delivers notifications** for run completions. Background runs finish, but the user is never notified.
|
|
18
18
|
|
|
19
19
|
## Root cause
|
|
20
20
|
|
|
21
|
-
###
|
|
21
|
+
### Normal flow (expected):
|
|
22
22
|
|
|
23
23
|
```
|
|
24
24
|
Pi session_start event
|
|
@@ -26,28 +26,28 @@ Pi session_start event
|
|
|
26
26
|
→ currentCtx = newCtx
|
|
27
27
|
→ cleanupRuntime() (stop old notifier)
|
|
28
28
|
→ startAsyncRunNotifier(newCtx, ...)
|
|
29
|
-
→ New notifier
|
|
29
|
+
→ New notifier runs with newCtx ✅
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
###
|
|
32
|
+
### Buggy flow:
|
|
33
33
|
|
|
34
34
|
```
|
|
35
35
|
1. Old notifier interval tick fires
|
|
36
|
-
2. isCurrent(generation) check → true (generation
|
|
36
|
+
2. isCurrent(generation) check → true (generation hasn't incremented yet)
|
|
37
37
|
3. ctx.ui.notify() called
|
|
38
|
-
4. Pi
|
|
38
|
+
4. Pi has already invalidated the old ctx → throws Error("This extension ctx is stale...")
|
|
39
39
|
5. Catch block: message.includes("stale") → true
|
|
40
40
|
6. stopAsyncRunNotifier(state) → clearInterval(interval)
|
|
41
41
|
7. console.error("stale ctx detected; stopping notifier.")
|
|
42
42
|
8. ❌ Notifier stopped permanently
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
The problem: the **session_start handler** hasn't had a chance to run yet to start the new notifier.
|
|
46
46
|
|
|
47
|
-
###
|
|
47
|
+
### Why hasn't session_start run yet?
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
-
|
|
49
|
+
When Pi invalidates ctx, the old notifier interval **is still running** (clearInterval hasn't been called yet). The interval tick occurs **before** `cleanupRuntime()` or the `session_start` handler runs. This is a **race condition** between:
|
|
50
|
+
- The old notifier's setInterval tick
|
|
51
51
|
- Pi's session shutdown/start event sequence
|
|
52
52
|
|
|
53
53
|
### Code location
|
|
@@ -67,21 +67,21 @@ Khi Pi invalidate ctx, old notifier interval **vẫn đang chạy** (clearInterv
|
|
|
67
67
|
### Why it matters
|
|
68
68
|
|
|
69
69
|
- User restarts Pi → notifier dies → background runs complete silently
|
|
70
|
-
-
|
|
71
|
-
-
|
|
70
|
+
- The user has to manually check `team status` to learn that runs finished
|
|
71
|
+
- Severe UX impact: pi-crew goes "silent" after a restart
|
|
72
72
|
|
|
73
73
|
## Fix
|
|
74
74
|
|
|
75
|
-
### Option A:
|
|
75
|
+
### Option A: Silently swallow stale errors (recommended)
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
Instead of stopping the notifier, just **skip this notification** and let session_start restart the notifier with the new ctx:
|
|
78
78
|
|
|
79
79
|
```typescript
|
|
80
80
|
} catch (error) {
|
|
81
81
|
const message = error instanceof Error ? error.message : String(error);
|
|
82
82
|
if (message.includes("stale") || message.includes("session replacement") || message.includes("old ctx")) {
|
|
83
83
|
// Don't stop — session_start will create a new notifier with the new ctx.
|
|
84
|
-
// This old notifier's isCurrent guard will return false on next tick,
|
|
84
|
+
// This old notifier's isCurrent guard will return false on the next tick,
|
|
85
85
|
// making it effectively dormant until cleaned up.
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
@@ -89,11 +89,11 @@ Thay vì stop notifier, chỉ **skip notification** này và chờ session_start
|
|
|
89
89
|
}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
Rationale: `isCurrent` guard
|
|
92
|
+
Rationale: the `isCurrent` guard will return false once `sessionGeneration++` runs → the old notifier interval keeps running but does nothing (silent). The new notifier from `session_start` will work normally.
|
|
93
93
|
|
|
94
|
-
### Option B: Add explicit restart mechanism
|
|
94
|
+
### Option B: Add an explicit restart mechanism
|
|
95
95
|
|
|
96
|
-
Add `restartAsyncRunNotifier()` function
|
|
96
|
+
Add a `restartAsyncRunNotifier()` function and call it from the `session_start` handler. But Option A is simpler and sufficient.
|
|
97
97
|
|
|
98
98
|
## Key files
|
|
99
99
|
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# Follow-up Plan — pi-crew (2026-05-12)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Author: Droid (Factory) | Related: `docs/code-review-2026-05-11.md`, commits `2aebf33`, `7c5b3c2`.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
1. **
|
|
7
|
-
2. **
|
|
5
|
+
This document consolidates the outstanding items AFTER fixing BUG-001..BUG-007 + NIT-001..NIT-004, comprising:
|
|
6
|
+
1. **Minor concerns** arising from the fixes just applied.
|
|
7
|
+
2. **Newly discovered gaps** found when re-reviewing the entire `pi-crew/` codebase.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Every item includes an effort estimate, priority level, related file/code section, and a concrete fix proposal.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Part A — Adjusting minor concerns from the fixes just applied
|
|
14
14
|
|
|
15
|
-
### A1 — `branchExists`
|
|
15
|
+
### A1 — `branchExists` only checks local ref → misses remote-tracking branches
|
|
16
16
|
|
|
17
|
-
**Severity:** Low | **Effort:** ~20
|
|
17
|
+
**Severity:** Low | **Effort:** ~20 minutes | **File:** `src/worktree/worktree-manager.ts:100-107`
|
|
18
18
|
|
|
19
|
-
**
|
|
19
|
+
**Current state:**
|
|
20
20
|
```ts
|
|
21
21
|
function branchExists(repoRoot: string, branch: string): boolean {
|
|
22
22
|
try {
|
|
@@ -26,11 +26,11 @@ function branchExists(repoRoot: string, branch: string): boolean {
|
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
**
|
|
30
|
-
-
|
|
31
|
-
-
|
|
29
|
+
**Problem:**
|
|
30
|
+
- Only checks `refs/heads/<branch>`. If the repo has a remote-tracking `refs/remotes/origin/<branch>` (pushed from another machine) but no local one yet, the function returns `false` → enters the `worktree add -b <branch> HEAD` branch → git may fail with "a branch named ... already exists" (because git creates a local branch from the remote) or create a local branch divergent from the remote.
|
|
31
|
+
- Rare in single-machine workflows but easily hit with CI/runner shared repos.
|
|
32
32
|
|
|
33
|
-
**
|
|
33
|
+
**Proposed fix:**
|
|
34
34
|
```ts
|
|
35
35
|
function branchExists(repoRoot: string, branch: string): { local: boolean; remoteOnly: boolean } {
|
|
36
36
|
let local = false;
|
|
@@ -56,32 +56,32 @@ if (exists.local) {
|
|
|
56
56
|
}
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
**
|
|
60
|
-
1. Mock `git for-each-ref`
|
|
61
|
-
2. Branch
|
|
59
|
+
**Tests to add:** `test/unit/worktree-manager.test.ts`:
|
|
60
|
+
1. Mock `git for-each-ref` returning a remote-tracking ref → assert no throw.
|
|
61
|
+
2. Branch exists in both local and remote → prefer local (reuse).
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
-
### A2 — `resolveJitiRegisterPath` fallback
|
|
65
|
+
### A2 — `resolveJitiRegisterPath` fallback now requires an `exists()` check
|
|
66
66
|
|
|
67
|
-
**Severity:** Low | **Effort:** ~10
|
|
67
|
+
**Severity:** Low | **Effort:** ~10 minutes | **File:** `src/runtime/async-runner.ts:33-39`
|
|
68
68
|
|
|
69
|
-
**
|
|
69
|
+
**Current state:**
|
|
70
70
|
```ts
|
|
71
71
|
try {
|
|
72
72
|
const fromRequire = jitiRegisterPathFromPackageJson(requireFromHere.resolve("jiti/package.json"));
|
|
73
|
-
if (exists(fromRequire)) return fromRequire; // ←
|
|
73
|
+
if (exists(fromRequire)) return fromRequire; // ← added exists() check
|
|
74
74
|
} catch { /* Fall through. */ }
|
|
75
75
|
return undefined;
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
**
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
78
|
+
**Problem:**
|
|
79
|
+
- Old behavior: `require.resolve()` succeeds → push into candidates → return path (assumes `lib/jiti-register.mjs` always exists next to `package.json`).
|
|
80
|
+
- New behavior: adds an `exists()` check → if the test mock for `exists` only accepts one different path, the fallback always fails. The behavior is fine for production (file always exists), but reduces robustness with exotic jiti packaging (e.g. `lib` renamed in a distro build).
|
|
81
|
+
- Not really a bug, just a defensive change.
|
|
82
82
|
|
|
83
|
-
**
|
|
84
|
-
|
|
83
|
+
**Proposed fix:**
|
|
84
|
+
Keep the `exists()` check (safer), but add another fallback with `path.join(dirname, "register.mjs")` for different jiti versions, and log a diagnostic event when both miss:
|
|
85
85
|
|
|
86
86
|
```ts
|
|
87
87
|
try {
|
|
@@ -96,41 +96,41 @@ try {
|
|
|
96
96
|
return undefined;
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
**
|
|
100
|
-
- Case `lib/jiti-register.mjs` missing
|
|
99
|
+
**Tests to add:** `test/unit/async-runner.test.ts`:
|
|
100
|
+
- Case where `lib/jiti-register.mjs` is missing but `register.mjs` exists → resolver returns it.
|
|
101
101
|
|
|
102
102
|
---
|
|
103
103
|
|
|
104
|
-
### A3 — Test alias `__test__renameWithRetry*`
|
|
104
|
+
### A3 — Test alias `__test__renameWithRetry*` became a `const`, may disable monkey-patching
|
|
105
105
|
|
|
106
|
-
**Severity:** Info | **Effort:** ~5
|
|
106
|
+
**Severity:** Info | **Effort:** ~5 minutes | **File:** `src/state/atomic-write.ts:73, 91`
|
|
107
107
|
|
|
108
|
-
**
|
|
108
|
+
**Current state:**
|
|
109
109
|
```ts
|
|
110
110
|
export const __test__renameWithRetry = renameWithRetry;
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
**
|
|
114
|
-
-
|
|
115
|
-
-
|
|
113
|
+
**Problem:**
|
|
114
|
+
- If old tests do `import * as mod from "../atomic-write.ts"; mod.__test__renameWithRetry = mockFn;` → the assignment fails (read-only) or has no effect on `atomicWriteFile` (which calls the local `renameWithRetry`).
|
|
115
|
+
- Currently no test in the repo monkey-patches this field (`Grep` confirmed), so it's not a bug.
|
|
116
116
|
|
|
117
|
-
**
|
|
118
|
-
-
|
|
119
|
-
-
|
|
117
|
+
**Proposed fix:**
|
|
118
|
+
- Keep the alias as-is (zero practical impact).
|
|
119
|
+
- Or remove both aliases to reduce the API surface: only export `renameWithRetry`, `renameWithRetryAsync`. Update tests if any use the alias.
|
|
120
120
|
|
|
121
|
-
**Action:**
|
|
121
|
+
**Action:** only do this when cleaning up the API surface; not urgent.
|
|
122
122
|
|
|
123
123
|
---
|
|
124
124
|
|
|
125
|
-
##
|
|
125
|
+
## Part B — Newly discovered gaps from re-review
|
|
126
126
|
|
|
127
|
-
### B1 — `bash` hardcoded → broken
|
|
127
|
+
### B1 — `bash` hardcoded → broken on Windows (root cause of 8 test failures)
|
|
128
128
|
|
|
129
|
-
**Severity:** Medium | **Effort:** ~45
|
|
129
|
+
**Severity:** Medium | **Effort:** ~45 minutes | **Files:**
|
|
130
130
|
- `src/runtime/post-checks.ts:82`
|
|
131
131
|
- `src/runtime/iteration-hooks.ts:137`
|
|
132
132
|
|
|
133
|
-
**
|
|
133
|
+
**Current state:**
|
|
134
134
|
```ts
|
|
135
135
|
// post-checks.ts:82
|
|
136
136
|
const output = execFileSync("bash", [scriptPath], { ... });
|
|
@@ -139,13 +139,13 @@ const output = execFileSync("bash", [scriptPath], { ... });
|
|
|
139
139
|
const child = spawn("bash", [hookScriptPath], { ... });
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
**
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
142
|
+
**Problem:**
|
|
143
|
+
- On Windows, `bash` is usually not on the PATH (unless Git Bash/WSL is installed). All 8 currently failing tests are due to this.
|
|
144
|
+
- Hot code path (post-task check, iteration hook) → Windows users cannot use this feature.
|
|
145
|
+
- The comment in the file says "Spawns `bash <script>`" → docs need updating too.
|
|
146
146
|
|
|
147
|
-
**
|
|
148
|
-
1.
|
|
147
|
+
**Proposed fix:**
|
|
148
|
+
1. Resolve bash intelligently:
|
|
149
149
|
```ts
|
|
150
150
|
function resolveBashCmd(): string {
|
|
151
151
|
if (process.platform !== "win32") return "bash";
|
|
@@ -160,26 +160,26 @@ function resolveBashCmd(): string {
|
|
|
160
160
|
return "bash"; // last resort — let spawn fail with clearer error
|
|
161
161
|
}
|
|
162
162
|
```
|
|
163
|
-
2.
|
|
164
|
-
3.
|
|
165
|
-
4.
|
|
163
|
+
2. Replace both `"bash"` occurrences with `resolveBashCmd()`.
|
|
164
|
+
3. On Windows, if the script is `.ps1` → use `powershell -File`; if `.cmd/.bat` → spawn directly.
|
|
165
|
+
4. Or simpler: skip the test if `!isScriptRunnable("bash")`.
|
|
166
166
|
|
|
167
|
-
**
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
- Mock test
|
|
167
|
+
**Tests to add:**
|
|
168
|
+
- On Linux CI: current tests pass.
|
|
169
|
+
- On Windows: skip if bash is unavailable, or create a `.ps1` variant.
|
|
170
|
+
- Mock test for `resolveBashCmd()` with a platform stub.
|
|
171
171
|
|
|
172
172
|
---
|
|
173
173
|
|
|
174
|
-
### B2 —
|
|
174
|
+
### B2 — Missing direct tests for `worktree-manager.ts` (covers BUG-005, BUG-006)
|
|
175
175
|
|
|
176
|
-
**Severity:** Medium | **Effort:** ~1h | **File:**
|
|
176
|
+
**Severity:** Medium | **Effort:** ~1h | **File:** missing `test/unit/worktree-manager.test.ts`
|
|
177
177
|
|
|
178
|
-
**
|
|
179
|
-
- BUG-005 fix (`branchExists` + `pruneStaleWorktrees`)
|
|
180
|
-
-
|
|
178
|
+
**Problem:**
|
|
179
|
+
- The BUG-005 fix (`branchExists` + `pruneStaleWorktrees`) and BUG-006 fix (`isDirectory()` check) have **no direct tests**.
|
|
180
|
+
- The code review flagged this, but the fix commit only added tests for the schema.
|
|
181
181
|
|
|
182
|
-
**
|
|
182
|
+
**Proposed fix:** create `test/unit/worktree-manager.test.ts` with (using `tmpdir` + real git):
|
|
183
183
|
|
|
184
184
|
```ts
|
|
185
185
|
import { test } from "node:test";
|
|
@@ -214,15 +214,15 @@ test("linkNodeModulesIfPresent rejects when node_modules is a file", () => {
|
|
|
214
214
|
|
|
215
215
|
---
|
|
216
216
|
|
|
217
|
-
### B3 — `artifact-store.ts`
|
|
217
|
+
### B3 — `artifact-store.ts` missing tests for hash/size integrity (covers BUG-002)
|
|
218
218
|
|
|
219
|
-
**Severity:** Medium | **Effort:** ~20
|
|
219
|
+
**Severity:** Medium | **Effort:** ~20 minutes | **File:** missing `test/unit/artifact-store.test.ts`
|
|
220
220
|
|
|
221
|
-
**
|
|
222
|
-
- BUG-002 fix
|
|
223
|
-
-
|
|
221
|
+
**Problem:**
|
|
222
|
+
- The BUG-002 fix changed the hash from `options.content` to `redactSecretString(options.content)` ensuring `contentHash` matches the bytes on disk.
|
|
223
|
+
- There is no test verifying this. There's `api-artifact-security.test.ts` but it doesn't assert the hash.
|
|
224
224
|
|
|
225
|
-
**
|
|
225
|
+
**Proposed fix:** create `test/unit/artifact-store.test.ts`:
|
|
226
226
|
```ts
|
|
227
227
|
import { test } from "node:test";
|
|
228
228
|
import assert from "node:assert/strict";
|
|
@@ -256,14 +256,14 @@ test("writeArtifact: rejects path traversal", () => {
|
|
|
256
256
|
|
|
257
257
|
---
|
|
258
258
|
|
|
259
|
-
### B4 —
|
|
259
|
+
### B4 — Missing sync vs async parity test for lock retry (covers BUG-004)
|
|
260
260
|
|
|
261
|
-
**Severity:** Low | **Effort:** ~30
|
|
261
|
+
**Severity:** Low | **Effort:** ~30 minutes | **File:** extend `test/unit/locks-race.test.ts`
|
|
262
262
|
|
|
263
|
-
**
|
|
264
|
-
-
|
|
263
|
+
**Problem:**
|
|
264
|
+
- The BUG-004 fix synchronized sync ↔ async behavior (both retry up to the deadline). `locks-race.test.ts` only tests superficially; there's no case for `rmSync` race + asserting the lock is acquired after retry for both sync and async.
|
|
265
265
|
|
|
266
|
-
**
|
|
266
|
+
**Proposed fix:** add 2 tests:
|
|
267
267
|
```ts
|
|
268
268
|
test("withRunLockSync retries when rmSync fails once on stale lock", async () => {
|
|
269
269
|
// Create stale lock, monkey-patch fs.rmSync first call → throw EBUSY
|
|
@@ -277,11 +277,11 @@ test("withRunLock (async) and withRunLockSync exhibit identical stale-lock recov
|
|
|
277
277
|
|
|
278
278
|
---
|
|
279
279
|
|
|
280
|
-
### B5 — `runSetupHook`
|
|
280
|
+
### B5 — `runSetupHook` doesn't filter dangerous env vars when spawning the hook
|
|
281
281
|
|
|
282
|
-
**Severity:** Low (defense-in-depth) | **Effort:** ~15
|
|
282
|
+
**Severity:** Low (defense-in-depth) | **Effort:** ~15 minutes | **File:** `src/worktree/worktree-manager.ts:67-88`
|
|
283
283
|
|
|
284
|
-
**
|
|
284
|
+
**Current state:**
|
|
285
285
|
```ts
|
|
286
286
|
const result = spawnSync(nodeHook ? process.execPath : hookPath, ..., {
|
|
287
287
|
cwd: worktreePath,
|
|
@@ -289,17 +289,17 @@ const result = spawnSync(nodeHook ? process.execPath : hookPath, ..., {
|
|
|
289
289
|
input: JSON.stringify({...}),
|
|
290
290
|
timeout: cfg.setupHookTimeoutMs ?? 30_000,
|
|
291
291
|
shell: false,
|
|
292
|
-
// ←
|
|
292
|
+
// ← Does NOT pass env → spawn uses process.env (full inherit)
|
|
293
293
|
});
|
|
294
294
|
```
|
|
295
295
|
|
|
296
|
-
**
|
|
297
|
-
-
|
|
296
|
+
**Problem:**
|
|
297
|
+
- The hook runs with the full `process.env` → may leak `API_KEY`, `*_TOKEN`, `OPENAI_KEY`, etc. In contrast, `post-checks.ts` and `iteration-hooks.ts` already restrict env.
|
|
298
298
|
- AGENTS.md security baseline: "env-secret filtering before spawn".
|
|
299
299
|
|
|
300
|
-
**
|
|
300
|
+
**Proposed fix:**
|
|
301
301
|
```ts
|
|
302
|
-
import { sanitizeEnvSecrets } from "../utils/redaction.ts"; //
|
|
302
|
+
import { sanitizeEnvSecrets } from "../utils/redaction.ts"; // or equivalent
|
|
303
303
|
|
|
304
304
|
const result = spawnSync(..., {
|
|
305
305
|
cwd: worktreePath,
|
|
@@ -311,15 +311,15 @@ const result = spawnSync(..., {
|
|
|
311
311
|
});
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
-
(
|
|
314
|
+
(Leverage the existing helper in `child-pi.ts` — refactor into a shared util.)
|
|
315
315
|
|
|
316
316
|
---
|
|
317
317
|
|
|
318
|
-
### B6 — `prepareTaskWorkspace`
|
|
318
|
+
### B6 — `prepareTaskWorkspace` has no assertion about branch sanity after `branchExists`
|
|
319
319
|
|
|
320
|
-
**Severity:** Info | **Effort:** ~10
|
|
320
|
+
**Severity:** Info | **Effort:** ~10 minutes | **File:** `src/worktree/worktree-manager.ts:127-133`
|
|
321
321
|
|
|
322
|
-
**
|
|
322
|
+
**Current state:**
|
|
323
323
|
```ts
|
|
324
324
|
pruneStaleWorktrees(repoRoot);
|
|
325
325
|
if (branchExists(repoRoot, branch)) {
|
|
@@ -327,10 +327,10 @@ if (branchExists(repoRoot, branch)) {
|
|
|
327
327
|
}
|
|
328
328
|
```
|
|
329
329
|
|
|
330
|
-
**
|
|
331
|
-
-
|
|
330
|
+
**Problem:**
|
|
331
|
+
- If the branch is currently **checked out** in another worktree (not yet pruned), `git worktree add <path> <branch>` will fail. We should catch this and emit a clearer hint to the user.
|
|
332
332
|
|
|
333
|
-
**
|
|
333
|
+
**Proposed fix:** wrap with try/catch, parse the error message, throw an error with an actionable message:
|
|
334
334
|
```ts
|
|
335
335
|
try {
|
|
336
336
|
git(repoRoot, ["worktree", "add", worktreePath, branch]);
|
|
@@ -345,11 +345,11 @@ try {
|
|
|
345
345
|
|
|
346
346
|
---
|
|
347
347
|
|
|
348
|
-
### B7 — `team-tool.ts`
|
|
348
|
+
### B7 — `team-tool.ts` has 1 lazy-import `handleRun` not following the `// LAZY: <reason>` style
|
|
349
349
|
|
|
350
|
-
**Severity:** Info | **Effort:** ~2
|
|
350
|
+
**Severity:** Info | **Effort:** ~2 minutes | **File:** `src/extension/team-tool.ts:56-62`
|
|
351
351
|
|
|
352
|
-
**
|
|
352
|
+
**Current state:**
|
|
353
353
|
```ts
|
|
354
354
|
// Lazy-loaded: run.ts pulls in spawnBackgroundTeamRun, resolveCrewRuntime, etc.
|
|
355
355
|
// Static import fails silently in some jiti contexts (child-process), leaving handleRun undefined.
|
|
@@ -357,13 +357,13 @@ import type { handleRun as HandleRunFn } from "./team-tool/run.ts";
|
|
|
357
357
|
let _cachedHandleRun: typeof HandleRunFn | undefined;
|
|
358
358
|
async function handleRun(...args: Parameters<typeof HandleRunFn>): Promise<...> {
|
|
359
359
|
if (!_cachedHandleRun) {
|
|
360
|
-
const mod = await import("./team-tool/run.ts"); // ←
|
|
360
|
+
const mod = await import("./team-tool/run.ts"); // ← missing // LAZY: marker
|
|
361
361
|
...
|
|
362
362
|
}
|
|
363
363
|
}
|
|
364
364
|
```
|
|
365
365
|
|
|
366
|
-
**
|
|
366
|
+
**Proposed fix:** add the marker to align with the other 11 already-marked sites:
|
|
367
367
|
```ts
|
|
368
368
|
async function handleRun(...) {
|
|
369
369
|
if (!_cachedHandleRun) {
|
|
@@ -376,42 +376,42 @@ async function handleRun(...) {
|
|
|
376
376
|
|
|
377
377
|
---
|
|
378
378
|
|
|
379
|
-
### B8 — `redaction-transcript-roundtrip.test.ts`
|
|
379
|
+
### B8 — `redaction-transcript-roundtrip.test.ts` doesn't exist yet (NIT-004)
|
|
380
380
|
|
|
381
|
-
**Severity:** Low | **Effort:** ~30
|
|
381
|
+
**Severity:** Low | **Effort:** ~30 minutes | **File:** missing new test
|
|
382
382
|
|
|
383
|
-
**
|
|
384
|
-
- Code review NIT-004
|
|
383
|
+
**Problem:**
|
|
384
|
+
- Code review NIT-004 suggested a test verifying that the transcript on disk and the artifact result both contain no raw secrets. This test doesn't exist yet.
|
|
385
385
|
|
|
386
|
-
**
|
|
387
|
-
1.
|
|
388
|
-
2. Call `appendTranscript` (
|
|
389
|
-
3. Read file → assert
|
|
390
|
-
4. Call recovery path → assert artifact result
|
|
386
|
+
**Proposed fix:** create `test/unit/redaction-transcript-roundtrip.test.ts`:
|
|
387
|
+
1. Create a fake transcript JSONL with a line containing `OPENAI_API_KEY=sk-abc...`.
|
|
388
|
+
2. Call `appendTranscript` (via the `child-pi` helper export).
|
|
389
|
+
3. Read the file → assert no raw secret.
|
|
390
|
+
4. Call the recovery path → assert the artifact result is also redacted.
|
|
391
391
|
|
|
392
392
|
---
|
|
393
393
|
|
|
394
|
-
### B9 — CI grep-check
|
|
394
|
+
### B9 — CI grep-check to block `await import(...)` without a marker
|
|
395
395
|
|
|
396
|
-
**Severity:** Low | **Effort:** ~15
|
|
396
|
+
**Severity:** Low | **Effort:** ~15 minutes | **File:** add script + GitHub Actions
|
|
397
397
|
|
|
398
|
-
**
|
|
399
|
-
- Code review BUG-003 Option A
|
|
398
|
+
**Problem:**
|
|
399
|
+
- Code review BUG-003 Option A proposed "add a grep-check in CI to block unmarked dynamic imports". Not done yet.
|
|
400
400
|
|
|
401
|
-
**
|
|
401
|
+
**Proposed fix:** create `scripts/check-lazy-imports.mjs`:
|
|
402
402
|
```js
|
|
403
403
|
import { execSync } from "node:child_process";
|
|
404
404
|
const out = execSync(
|
|
405
405
|
`git grep -nE 'await import\\(' -- 'src/**/*.ts' | grep -v '// LAZY:'`,
|
|
406
406
|
{ encoding: "utf-8" }
|
|
407
407
|
).split("\n").filter((l) => l && !l.includes("// LAZY:"));
|
|
408
|
-
//
|
|
408
|
+
// Or check that the preceding line contains `// LAZY:`
|
|
409
409
|
if (out.length > 0) {
|
|
410
410
|
console.error("Dynamic imports without `// LAZY:` marker:\n" + out.join("\n"));
|
|
411
411
|
process.exit(1);
|
|
412
412
|
}
|
|
413
413
|
```
|
|
414
|
-
|
|
414
|
+
Add to `package.json`:
|
|
415
415
|
```json
|
|
416
416
|
"scripts": {
|
|
417
417
|
"check:lazy-imports": "node scripts/check-lazy-imports.mjs",
|
|
@@ -421,43 +421,43 @@ Thêm vào `package.json`:
|
|
|
421
421
|
|
|
422
422
|
---
|
|
423
423
|
|
|
424
|
-
##
|
|
424
|
+
## Implementation priority
|
|
425
425
|
|
|
426
|
-
| # | Item | Severity | Effort |
|
|
426
|
+
| # | Item | Severity | Effort | Recommendation |
|
|
427
427
|
|---|---|---|---|---|
|
|
428
|
-
| 1 | B1 (bash portability) | Medium | 45
|
|
429
|
-
| 2 | B2 (worktree test) | Medium | 1h |
|
|
430
|
-
| 3 | B3 (artifact-store test) | Medium | 20
|
|
431
|
-
| 4 | A1 (branchExists remote-tracking) | Low | 20
|
|
432
|
-
| 5 | B5 (setup-hook env filter) | Low | 15
|
|
433
|
-
| 6 | B6 (worktree checked-out hint) | Info | 10
|
|
434
|
-
| 7 | B7 (LAZY marker
|
|
435
|
-
| 8 | B4 (lock parity test) | Low | 30
|
|
436
|
-
| 9 | B8 (redaction roundtrip test) | Low | 30
|
|
437
|
-
| 10 | B9 (CI grep-check) | Low | 15
|
|
438
|
-
| 11 | A2 (async-runner fallback robust) | Low | 10
|
|
439
|
-
| 12 | A3 (test alias cleanup) | Info | 5
|
|
440
|
-
|
|
441
|
-
**
|
|
442
|
-
**
|
|
428
|
+
| 1 | B1 (bash portability) | Medium | 45 minutes | Current sprint — fix 8 failing tests on Windows |
|
|
429
|
+
| 2 | B2 (worktree test) | Medium | 1h | Current sprint — regression guard for BUG-005/006 |
|
|
430
|
+
| 3 | B3 (artifact-store test) | Medium | 20 minutes | Current sprint — verify BUG-002 fix |
|
|
431
|
+
| 4 | A1 (branchExists remote-tracking) | Low | 20 minutes | Next sprint |
|
|
432
|
+
| 5 | B5 (setup-hook env filter) | Low | 15 minutes | Next sprint — defense-in-depth |
|
|
433
|
+
| 6 | B6 (worktree checked-out hint) | Info | 10 minutes | Next sprint — UX |
|
|
434
|
+
| 7 | B7 (LAZY marker consistency) | Info | 2 minutes | Anytime |
|
|
435
|
+
| 8 | B4 (lock parity test) | Low | 30 minutes | Next sprint |
|
|
436
|
+
| 9 | B8 (redaction roundtrip test) | Low | 30 minutes | Next sprint |
|
|
437
|
+
| 10 | B9 (CI grep-check) | Low | 15 minutes | Next sprint |
|
|
438
|
+
| 11 | A2 (async-runner fallback robust) | Low | 10 minutes | Not urgent |
|
|
439
|
+
| 12 | A3 (test alias cleanup) | Info | 5 minutes | Optional |
|
|
440
|
+
|
|
441
|
+
**Total effort priority 1 (medium):** ~2h 5 minutes.
|
|
442
|
+
**Total effort priority 2 (low):** ~1h 50 minutes.
|
|
443
443
|
|
|
444
444
|
---
|
|
445
445
|
|
|
446
|
-
##
|
|
446
|
+
## Proposed commit batches
|
|
447
447
|
|
|
448
448
|
- **Batch 1 (must-fix):** B1 + B2 + B3 → 1 PR "test+portability hardening" (~2h).
|
|
449
449
|
- **Batch 2 (nice-to-have):** A1 + B5 + B6 + B7 + B9 → 1 PR "worktree + lazy-import polish" (~1h).
|
|
450
450
|
- **Batch 3 (test debt):** B4 + B8 → 1 PR "additional regression tests" (~1h).
|
|
451
|
-
- **Defer:** A2, A3
|
|
451
|
+
- **Defer:** A2, A3 until there's a user-visible issue.
|
|
452
452
|
|
|
453
453
|
---
|
|
454
454
|
|
|
455
|
-
##
|
|
455
|
+
## Positive notes after round 2 review
|
|
456
456
|
|
|
457
|
-
-
|
|
458
|
-
-
|
|
459
|
-
- Lock retry logic
|
|
460
|
-
- Worktree handle resume
|
|
461
|
-
- Artifact `contentHash`
|
|
462
|
-
-
|
|
463
|
-
- `node_modules/jiti` resolution robust
|
|
457
|
+
- All 7 BUGs + 4 NITs from the prior code review have been fixed with clear commits, with tests for the schema.
|
|
458
|
+
- The `// LAZY:` comment has been consistently added at 11/12 dynamic import sites.
|
|
459
|
+
- Lock retry logic is now unified sync ↔ async (both have deadline + retry).
|
|
460
|
+
- Worktree handle crash resume works correctly (prune + branchExists fallback).
|
|
461
|
+
- Artifact `contentHash` is now verifiable via `sha256(fs.readFileSync(desc.path))`.
|
|
462
|
+
- No more `any` types in `src/` (grep confirmed).
|
|
463
|
+
- `node_modules/jiti` resolution is robust across any monorepo layout.
|