oh-my-opencode 4.3.1 → 4.5.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/.agents/command/get-unpublished-changes.md +148 -0
- package/.agents/command/omomomo.md +37 -0
- package/.agents/command/publish.md +376 -0
- package/.agents/command/remove-deadcode.md +221 -0
- package/.agents/command/security-research.md +16 -0
- package/.agents/skills/get-unpublished-changes/SKILL.md +24 -0
- package/.agents/skills/github-triage/SKILL.md +587 -0
- package/.agents/skills/github-triage/scripts/gh_fetch.py +398 -0
- package/.agents/skills/hyperplan/SKILL.md +450 -0
- package/.agents/skills/omomomo/SKILL.md +36 -0
- package/.agents/skills/pre-publish-review/SKILL.md +407 -0
- package/.agents/skills/publish/SKILL.md +428 -0
- package/.agents/skills/remove-deadcode/SKILL.md +216 -0
- package/.agents/skills/security-research/SKILL.md +204 -0
- package/.agents/skills/work-with-pr/SKILL.md +360 -0
- package/.agents/skills/work-with-pr-workspace/evals/evals.json +76 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/benchmark.json +138 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/benchmark.md +42 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/eval_metadata.json +57 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/grading.json +15 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/code-changes.md +454 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/execution-plan.md +136 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/pr-description.md +47 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/verification-strategy.md +163 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/grading.json +15 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/code-changes.md +615 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/execution-plan.md +99 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/pr-description.md +50 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/verification-strategy.md +111 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/eval_metadata.json +37 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/grading.json +11 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/code-changes.md +205 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/execution-plan.md +78 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/pr-description.md +42 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/verification-strategy.md +87 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/grading.json +11 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/code-changes.md +334 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/execution-plan.md +86 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/pr-description.md +23 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/verification-strategy.md +119 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/eval_metadata.json +32 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/code-changes.md +221 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/execution-plan.md +104 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/pr-description.md +41 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/verification-strategy.md +84 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/code-changes.md +342 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/execution-plan.md +131 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/pr-description.md +39 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/verification-strategy.md +128 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/eval_metadata.json +32 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/code-changes.md +143 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/execution-plan.md +82 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/pr-description.md +51 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/verification-strategy.md +69 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/code-changes.md +252 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/execution-plan.md +83 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/pr-description.md +33 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/verification-strategy.md +101 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/eval_metadata.json +32 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/code-changes.md +387 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/execution-plan.md +112 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/pr-description.md +51 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/verification-strategy.md +75 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/grading.json +10 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/code-changes.md +529 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/execution-plan.md +127 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/pr-description.md +42 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/verification-strategy.md +120 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/timing.json +1 -0
- package/.agents/skills/work-with-pr-workspace/iteration-1/review.html +1326 -0
- package/.opencode/command/get-unpublished-changes.md +148 -0
- package/.opencode/command/omomomo.md +37 -0
- package/.opencode/command/publish.md +376 -0
- package/.opencode/command/remove-deadcode.md +221 -0
- package/.opencode/command/security-research.md +16 -0
- package/.opencode/skills/github-triage/SKILL.md +587 -0
- package/.opencode/skills/github-triage/scripts/gh_fetch.py +398 -0
- package/.opencode/skills/hyperplan/SKILL.md +450 -0
- package/.opencode/skills/pre-publish-review/SKILL.md +407 -0
- package/.opencode/skills/work-with-pr/SKILL.md +360 -0
- package/.opencode/skills/work-with-pr-workspace/evals/evals.json +76 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/benchmark.json +138 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/benchmark.md +42 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/eval_metadata.json +57 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/grading.json +15 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/code-changes.md +454 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/execution-plan.md +136 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/pr-description.md +47 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/verification-strategy.md +163 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/grading.json +15 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/code-changes.md +615 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/execution-plan.md +99 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/pr-description.md +50 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/verification-strategy.md +111 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/eval_metadata.json +37 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/grading.json +11 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/code-changes.md +205 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/execution-plan.md +78 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/pr-description.md +42 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/verification-strategy.md +87 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/grading.json +11 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/code-changes.md +334 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/execution-plan.md +86 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/pr-description.md +23 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/verification-strategy.md +119 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/eval_metadata.json +32 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/code-changes.md +221 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/execution-plan.md +104 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/pr-description.md +41 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/verification-strategy.md +84 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/code-changes.md +342 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/execution-plan.md +131 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/pr-description.md +39 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/verification-strategy.md +128 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/eval_metadata.json +32 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/code-changes.md +143 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/execution-plan.md +82 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/pr-description.md +51 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/verification-strategy.md +69 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/code-changes.md +252 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/execution-plan.md +83 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/pr-description.md +33 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/verification-strategy.md +101 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/eval_metadata.json +32 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/code-changes.md +387 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/execution-plan.md +112 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/pr-description.md +51 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/verification-strategy.md +75 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/grading.json +10 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/code-changes.md +529 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/execution-plan.md +127 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/pr-description.md +42 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/verification-strategy.md +120 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/timing.json +1 -0
- package/.opencode/skills/work-with-pr-workspace/iteration-1/review.html +1326 -0
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.ru.md +1 -1
- package/README.zh-cn.md +1 -1
- package/dist/agents/atlas/agent.d.ts +6 -6
- package/dist/agents/prometheus/gemini.d.ts +0 -11
- package/dist/agents/prometheus/gpt.d.ts +0 -10
- package/dist/agents/prometheus/system-prompt.d.ts +2 -20
- package/dist/agents/types.d.ts +1 -16
- package/dist/cli/index.js +60 -20
- package/dist/config/schema/agent-names.d.ts +3 -3
- package/dist/config/schema/agent-overrides.d.ts +208 -208
- package/dist/config/schema/categories.d.ts +28 -28
- package/dist/config/schema/fallback-models.d.ts +20 -20
- package/dist/config/schema/oh-my-opencode-config.d.ts +208 -208
- package/dist/features/background-agent/parent-wake-dedupe.d.ts +19 -0
- package/dist/features/background-agent/parent-wake-notifier.d.ts +8 -19
- package/dist/help/schema/acp.d.ts +95 -0
- package/dist/help/schema/doctor.d.ts +147 -0
- package/dist/help/schema/sandbox.d.ts +74 -0
- package/dist/help/schema/status.d.ts +139 -0
- package/dist/hooks/keyword-detector/analyze/default.d.ts +1 -1
- package/dist/hooks/keyword-detector/hyperplan/default.d.ts +1 -1
- package/dist/hooks/keyword-detector/search/default.d.ts +1 -1
- package/dist/hooks/keyword-detector/team/default.d.ts +2 -7
- package/dist/hooks/keyword-detector/ultrawork/default.d.ts +1 -9
- package/dist/hooks/keyword-detector/ultrawork/gemini.d.ts +1 -16
- package/dist/hooks/keyword-detector/ultrawork/gpt.d.ts +1 -10
- package/dist/hooks/keyword-detector/ultrawork/planner.d.ts +1 -5
- package/dist/hooks/ralph-loop/no-progress-turn-detector.d.ts +7 -0
- package/dist/hooks/ralph-loop/pending-verification-handler.d.ts +1 -0
- package/dist/hooks/ralph-loop/types.d.ts +1 -0
- package/dist/hooks/runtime-fallback/error-classifier.d.ts +1 -0
- package/dist/hooks/tool-pair-validator/hook.d.ts +6 -1
- package/dist/index.js +51976 -50299
- package/dist/plugin-handlers/provider-config-handler.d.ts +1 -0
- package/dist/shared/migration/model-versions.d.ts +6 -0
- package/dist/shared/prompt-async-gate/pending-tool-turn.d.ts +1 -0
- package/dist/shared/prompt-async-gate/types.d.ts +4 -3
- package/package.json +19 -13
- package/dist/agents/atlas/default-prompt-sections.d.ts +0 -6
- package/dist/agents/atlas/default.d.ts +0 -2
- package/dist/agents/atlas/gemini-prompt-sections.d.ts +0 -6
- package/dist/agents/atlas/gemini.d.ts +0 -2
- package/dist/agents/atlas/gpt-prompt-sections.d.ts +0 -6
- package/dist/agents/atlas/gpt.d.ts +0 -2
- package/dist/agents/atlas/kimi-prompt-sections.d.ts +0 -6
- package/dist/agents/atlas/kimi.d.ts +0 -2
- package/dist/agents/atlas/opus-4-7-prompt-sections.d.ts +0 -6
- package/dist/agents/atlas/opus-4-7.d.ts +0 -2
- package/dist/agents/atlas/shared-prompt.d.ts +0 -9
- package/dist/agents/prometheus/behavioral-summary.d.ts +0 -6
- package/dist/agents/prometheus/high-accuracy-mode.d.ts +0 -6
- package/dist/agents/prometheus/identity-constraints.d.ts +0 -7
- package/dist/agents/prometheus/interview-mode.d.ts +0 -7
- package/dist/agents/prometheus/plan-generation.d.ts +0 -7
- package/dist/agents/prometheus/plan-template.d.ts +0 -7
- package/dist/agents/prometheus/spec-driven-mode.d.ts +0 -7
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# Code Changes: Fix Atlas Hook Crash on Missing worktree_path
|
|
2
|
+
|
|
3
|
+
## Change 1: Harden `readBoulderState()` validation
|
|
4
|
+
|
|
5
|
+
**File:** `src/features/boulder-state/storage.ts`
|
|
6
|
+
|
|
7
|
+
### Before (lines 16-36):
|
|
8
|
+
```typescript
|
|
9
|
+
export function readBoulderState(directory: string): BoulderState | null {
|
|
10
|
+
const filePath = getBoulderFilePath(directory)
|
|
11
|
+
|
|
12
|
+
if (!existsSync(filePath)) {
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const content = readFileSync(filePath, "utf-8")
|
|
18
|
+
const parsed = JSON.parse(content)
|
|
19
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
if (!Array.isArray(parsed.session_ids)) {
|
|
23
|
+
parsed.session_ids = []
|
|
24
|
+
}
|
|
25
|
+
return parsed as BoulderState
|
|
26
|
+
} catch {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### After:
|
|
33
|
+
```typescript
|
|
34
|
+
export function readBoulderState(directory: string): BoulderState | null {
|
|
35
|
+
const filePath = getBoulderFilePath(directory)
|
|
36
|
+
|
|
37
|
+
if (!existsSync(filePath)) {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const content = readFileSync(filePath, "utf-8")
|
|
43
|
+
const parsed = JSON.parse(content)
|
|
44
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
if (typeof parsed.active_plan !== "string" || typeof parsed.plan_name !== "string") {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
if (!Array.isArray(parsed.session_ids)) {
|
|
51
|
+
parsed.session_ids = []
|
|
52
|
+
}
|
|
53
|
+
if (parsed.worktree_path !== undefined && typeof parsed.worktree_path !== "string") {
|
|
54
|
+
delete parsed.worktree_path
|
|
55
|
+
}
|
|
56
|
+
return parsed as BoulderState
|
|
57
|
+
} catch {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Rationale:** Validates that required fields (`active_plan`, `plan_name`) are strings. Strips `worktree_path` if it's present but not a string (e.g., `null`, number). This prevents downstream crashes from `existsSync(undefined)` and ensures type safety at the boundary.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Change 2: Add try/catch in setTimeout retry callback
|
|
68
|
+
|
|
69
|
+
**File:** `src/hooks/atlas/idle-event.ts`
|
|
70
|
+
|
|
71
|
+
### Before (lines 62-88):
|
|
72
|
+
```typescript
|
|
73
|
+
sessionState.pendingRetryTimer = setTimeout(async () => {
|
|
74
|
+
sessionState.pendingRetryTimer = undefined
|
|
75
|
+
|
|
76
|
+
if (sessionState.promptFailureCount >= 2) return
|
|
77
|
+
if (sessionState.waitingForFinalWaveApproval) return
|
|
78
|
+
|
|
79
|
+
const currentBoulder = readBoulderState(ctx.directory)
|
|
80
|
+
if (!currentBoulder) return
|
|
81
|
+
if (!currentBoulder.session_ids?.includes(sessionID)) return
|
|
82
|
+
|
|
83
|
+
const currentProgress = getPlanProgress(currentBoulder.active_plan)
|
|
84
|
+
if (currentProgress.isComplete) return
|
|
85
|
+
if (options?.isContinuationStopped?.(sessionID)) return
|
|
86
|
+
if (options?.shouldSkipContinuation?.(sessionID)) return
|
|
87
|
+
if (hasRunningBackgroundTasks(sessionID, options)) return
|
|
88
|
+
|
|
89
|
+
await injectContinuation({
|
|
90
|
+
ctx,
|
|
91
|
+
sessionID,
|
|
92
|
+
sessionState,
|
|
93
|
+
options,
|
|
94
|
+
planName: currentBoulder.plan_name,
|
|
95
|
+
progress: currentProgress,
|
|
96
|
+
agent: currentBoulder.agent,
|
|
97
|
+
worktreePath: currentBoulder.worktree_path,
|
|
98
|
+
})
|
|
99
|
+
}, RETRY_DELAY_MS)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### After:
|
|
103
|
+
```typescript
|
|
104
|
+
sessionState.pendingRetryTimer = setTimeout(async () => {
|
|
105
|
+
sessionState.pendingRetryTimer = undefined
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
if (sessionState.promptFailureCount >= 2) return
|
|
109
|
+
if (sessionState.waitingForFinalWaveApproval) return
|
|
110
|
+
|
|
111
|
+
const currentBoulder = readBoulderState(ctx.directory)
|
|
112
|
+
if (!currentBoulder) return
|
|
113
|
+
if (!currentBoulder.session_ids?.includes(sessionID)) return
|
|
114
|
+
|
|
115
|
+
const currentProgress = getPlanProgress(currentBoulder.active_plan)
|
|
116
|
+
if (currentProgress.isComplete) return
|
|
117
|
+
if (options?.isContinuationStopped?.(sessionID)) return
|
|
118
|
+
if (options?.shouldSkipContinuation?.(sessionID)) return
|
|
119
|
+
if (hasRunningBackgroundTasks(sessionID, options)) return
|
|
120
|
+
|
|
121
|
+
await injectContinuation({
|
|
122
|
+
ctx,
|
|
123
|
+
sessionID,
|
|
124
|
+
sessionState,
|
|
125
|
+
options,
|
|
126
|
+
planName: currentBoulder.plan_name,
|
|
127
|
+
progress: currentProgress,
|
|
128
|
+
agent: currentBoulder.agent,
|
|
129
|
+
worktreePath: currentBoulder.worktree_path,
|
|
130
|
+
})
|
|
131
|
+
} catch (error) {
|
|
132
|
+
log(`[${HOOK_NAME}] Retry continuation failed`, { sessionID, error: String(error) })
|
|
133
|
+
}
|
|
134
|
+
}, RETRY_DELAY_MS)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Rationale:** The async callback in setTimeout creates a floating promise. Without try/catch, any error becomes an unhandled rejection that can crash the process. This is the critical safety net even after the `readBoulderState` fix.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Change 3: Defensive guard in `getPlanProgress`
|
|
142
|
+
|
|
143
|
+
**File:** `src/features/boulder-state/storage.ts`
|
|
144
|
+
|
|
145
|
+
### Before (lines 115-118):
|
|
146
|
+
```typescript
|
|
147
|
+
export function getPlanProgress(planPath: string): PlanProgress {
|
|
148
|
+
if (!existsSync(planPath)) {
|
|
149
|
+
return { total: 0, completed: 0, isComplete: true }
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### After:
|
|
154
|
+
```typescript
|
|
155
|
+
export function getPlanProgress(planPath: string): PlanProgress {
|
|
156
|
+
if (typeof planPath !== "string" || !existsSync(planPath)) {
|
|
157
|
+
return { total: 0, completed: 0, isComplete: true }
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Rationale:** Defense-in-depth. Even though `readBoulderState` now validates `active_plan`, the `getPlanProgress` function is a public API that could be called from other paths with invalid input. A `typeof` check before `existsSync` prevents the TypeError from `existsSync(undefined)`.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Change 4: New tests
|
|
166
|
+
|
|
167
|
+
### File: `src/features/boulder-state/storage.test.ts` (additions)
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
test("should return null when active_plan is missing", () => {
|
|
171
|
+
// given - boulder.json without active_plan
|
|
172
|
+
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
|
|
173
|
+
writeFileSync(boulderFile, JSON.stringify({
|
|
174
|
+
started_at: "2026-01-01T00:00:00Z",
|
|
175
|
+
session_ids: ["ses-1"],
|
|
176
|
+
plan_name: "plan",
|
|
177
|
+
}))
|
|
178
|
+
|
|
179
|
+
// when
|
|
180
|
+
const result = readBoulderState(TEST_DIR)
|
|
181
|
+
|
|
182
|
+
// then
|
|
183
|
+
expect(result).toBeNull()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test("should return null when plan_name is missing", () => {
|
|
187
|
+
// given - boulder.json without plan_name
|
|
188
|
+
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
|
|
189
|
+
writeFileSync(boulderFile, JSON.stringify({
|
|
190
|
+
active_plan: "/path/to/plan.md",
|
|
191
|
+
started_at: "2026-01-01T00:00:00Z",
|
|
192
|
+
session_ids: ["ses-1"],
|
|
193
|
+
}))
|
|
194
|
+
|
|
195
|
+
// when
|
|
196
|
+
const result = readBoulderState(TEST_DIR)
|
|
197
|
+
|
|
198
|
+
// then
|
|
199
|
+
expect(result).toBeNull()
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
test("should strip non-string worktree_path from boulder state", () => {
|
|
203
|
+
// given - boulder.json with worktree_path set to null
|
|
204
|
+
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
|
|
205
|
+
writeFileSync(boulderFile, JSON.stringify({
|
|
206
|
+
active_plan: "/path/to/plan.md",
|
|
207
|
+
started_at: "2026-01-01T00:00:00Z",
|
|
208
|
+
session_ids: ["ses-1"],
|
|
209
|
+
plan_name: "plan",
|
|
210
|
+
worktree_path: null,
|
|
211
|
+
}))
|
|
212
|
+
|
|
213
|
+
// when
|
|
214
|
+
const result = readBoulderState(TEST_DIR)
|
|
215
|
+
|
|
216
|
+
// then
|
|
217
|
+
expect(result).not.toBeNull()
|
|
218
|
+
expect(result!.worktree_path).toBeUndefined()
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test("should preserve valid worktree_path string", () => {
|
|
222
|
+
// given - boulder.json with valid worktree_path
|
|
223
|
+
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
|
|
224
|
+
writeFileSync(boulderFile, JSON.stringify({
|
|
225
|
+
active_plan: "/path/to/plan.md",
|
|
226
|
+
started_at: "2026-01-01T00:00:00Z",
|
|
227
|
+
session_ids: ["ses-1"],
|
|
228
|
+
plan_name: "plan",
|
|
229
|
+
worktree_path: "/valid/worktree/path",
|
|
230
|
+
}))
|
|
231
|
+
|
|
232
|
+
// when
|
|
233
|
+
const result = readBoulderState(TEST_DIR)
|
|
234
|
+
|
|
235
|
+
// then
|
|
236
|
+
expect(result).not.toBeNull()
|
|
237
|
+
expect(result!.worktree_path).toBe("/valid/worktree/path")
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### File: `src/features/boulder-state/storage.test.ts` (getPlanProgress additions)
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
test("should handle undefined planPath without crashing", () => {
|
|
245
|
+
// given - undefined as planPath (from malformed boulder state)
|
|
246
|
+
|
|
247
|
+
// when
|
|
248
|
+
const progress = getPlanProgress(undefined as unknown as string)
|
|
249
|
+
|
|
250
|
+
// then
|
|
251
|
+
expect(progress.total).toBe(0)
|
|
252
|
+
expect(progress.isComplete).toBe(true)
|
|
253
|
+
})
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### File: `src/hooks/atlas/index.test.ts` (additions to session.idle section)
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
test("should handle boulder state without worktree_path gracefully", async () => {
|
|
260
|
+
// given - boulder state with incomplete plan, no worktree_path
|
|
261
|
+
const planPath = join(TEST_DIR, "test-plan.md")
|
|
262
|
+
writeFileSync(planPath, "# Plan\n- [ ] Task 1\n- [x] Task 2")
|
|
263
|
+
|
|
264
|
+
const state: BoulderState = {
|
|
265
|
+
active_plan: planPath,
|
|
266
|
+
started_at: "2026-01-02T10:00:00Z",
|
|
267
|
+
session_ids: [MAIN_SESSION_ID],
|
|
268
|
+
plan_name: "test-plan",
|
|
269
|
+
// worktree_path intentionally omitted
|
|
270
|
+
}
|
|
271
|
+
writeBoulderState(TEST_DIR, state)
|
|
272
|
+
|
|
273
|
+
const mockInput = createMockPluginInput()
|
|
274
|
+
const hook = createAtlasHook(mockInput)
|
|
275
|
+
|
|
276
|
+
// when
|
|
277
|
+
await hook.handler({
|
|
278
|
+
event: {
|
|
279
|
+
type: "session.idle",
|
|
280
|
+
properties: { sessionID: MAIN_SESSION_ID },
|
|
281
|
+
},
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
// then - should call prompt without crashing, continuation should not contain worktree context
|
|
285
|
+
expect(mockInput._promptMock).toHaveBeenCalled()
|
|
286
|
+
const callArgs = mockInput._promptMock.mock.calls[0][0]
|
|
287
|
+
expect(callArgs.body.parts[0].text).toContain("incomplete tasks")
|
|
288
|
+
expect(callArgs.body.parts[0].text).not.toContain("[Worktree:")
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test("should include worktree context when worktree_path is present in boulder state", async () => {
|
|
292
|
+
// given - boulder state with worktree_path
|
|
293
|
+
const planPath = join(TEST_DIR, "test-plan.md")
|
|
294
|
+
writeFileSync(planPath, "# Plan\n- [ ] Task 1")
|
|
295
|
+
|
|
296
|
+
const state: BoulderState = {
|
|
297
|
+
active_plan: planPath,
|
|
298
|
+
started_at: "2026-01-02T10:00:00Z",
|
|
299
|
+
session_ids: [MAIN_SESSION_ID],
|
|
300
|
+
plan_name: "test-plan",
|
|
301
|
+
worktree_path: "/some/worktree/path",
|
|
302
|
+
}
|
|
303
|
+
writeBoulderState(TEST_DIR, state)
|
|
304
|
+
|
|
305
|
+
const mockInput = createMockPluginInput()
|
|
306
|
+
const hook = createAtlasHook(mockInput)
|
|
307
|
+
|
|
308
|
+
// when
|
|
309
|
+
await hook.handler({
|
|
310
|
+
event: {
|
|
311
|
+
type: "session.idle",
|
|
312
|
+
properties: { sessionID: MAIN_SESSION_ID },
|
|
313
|
+
},
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
// then - should include worktree context in continuation prompt
|
|
317
|
+
expect(mockInput._promptMock).toHaveBeenCalled()
|
|
318
|
+
const callArgs = mockInput._promptMock.mock.calls[0][0]
|
|
319
|
+
expect(callArgs.body.parts[0].text).toContain("[Worktree: /some/worktree/path]")
|
|
320
|
+
})
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Summary of Changes
|
|
326
|
+
|
|
327
|
+
| File | Change | Lines Modified |
|
|
328
|
+
|------|--------|---------------|
|
|
329
|
+
| `src/features/boulder-state/storage.ts` | Validate required fields + sanitize worktree_path + guard getPlanProgress | ~8 lines added |
|
|
330
|
+
| `src/hooks/atlas/idle-event.ts` | try/catch around setTimeout async callback | ~4 lines added |
|
|
331
|
+
| `src/features/boulder-state/storage.test.ts` | 5 new tests for validation | ~60 lines added |
|
|
332
|
+
| `src/hooks/atlas/index.test.ts` | 2 new tests for worktree_path handling | ~50 lines added |
|
|
333
|
+
|
|
334
|
+
Total: ~4 production lines changed, ~8 defensive lines added, ~110 test lines added.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Execution Plan: Fix Atlas Hook Crash on Missing worktree_path
|
|
2
|
+
|
|
3
|
+
## Bug Analysis
|
|
4
|
+
|
|
5
|
+
### Root Cause
|
|
6
|
+
|
|
7
|
+
`readBoulderState()` in `src/features/boulder-state/storage.ts` performs minimal validation when parsing `boulder.json`:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
const parsed = JSON.parse(content)
|
|
11
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null
|
|
12
|
+
if (!Array.isArray(parsed.session_ids)) parsed.session_ids = []
|
|
13
|
+
return parsed as BoulderState // <-- unsafe cast, no field validation
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
It validates `session_ids` but NOT `active_plan`, `plan_name`, or `worktree_path`. This means a malformed `boulder.json` (e.g., `{}` or missing key fields) passes through and downstream code crashes.
|
|
17
|
+
|
|
18
|
+
### Crash Path
|
|
19
|
+
|
|
20
|
+
1. `boulder.json` is written without required fields (manual edit, corruption, partial write)
|
|
21
|
+
2. `readBoulderState()` returns it as `BoulderState` with `active_plan: undefined`
|
|
22
|
+
3. Multiple call sites pass `boulderState.active_plan` to `getPlanProgress(planPath: string)`:
|
|
23
|
+
- `src/hooks/atlas/idle-event.ts:72` (inside `setTimeout` callback - unhandled rejection!)
|
|
24
|
+
- `src/hooks/atlas/resolve-active-boulder-session.ts:21`
|
|
25
|
+
- `src/hooks/atlas/tool-execute-after.ts:74`
|
|
26
|
+
4. `getPlanProgress()` calls `existsSync(undefined)` which throws: `TypeError: The "path" argument must be of type string`
|
|
27
|
+
|
|
28
|
+
### worktree_path-Specific Issues
|
|
29
|
+
|
|
30
|
+
When `worktree_path` field is missing from `boulder.json`:
|
|
31
|
+
- The `idle-event.ts` `scheduleRetry` setTimeout callback (lines 62-88) has NO try/catch. An unhandled promise rejection from the async callback crashes the process.
|
|
32
|
+
- `readBoulderState()` returns `worktree_path: undefined` which itself is handled in `boulder-continuation-injector.ts` (line 42 uses truthiness check), but the surrounding code in the setTimeout lacks error protection.
|
|
33
|
+
|
|
34
|
+
### Secondary Issue: Unhandled Promise in setTimeout
|
|
35
|
+
|
|
36
|
+
In `idle-event.ts` lines 62-88:
|
|
37
|
+
```typescript
|
|
38
|
+
sessionState.pendingRetryTimer = setTimeout(async () => {
|
|
39
|
+
// ... no try/catch wrapper
|
|
40
|
+
const currentBoulder = readBoulderState(ctx.directory)
|
|
41
|
+
const currentProgress = getPlanProgress(currentBoulder.active_plan) // CRASH if active_plan undefined
|
|
42
|
+
// ...
|
|
43
|
+
}, RETRY_DELAY_MS)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The async callback creates a floating promise. Any thrown error becomes an unhandled rejection.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Step-by-Step Plan
|
|
51
|
+
|
|
52
|
+
### Step 1: Harden `readBoulderState()` validation
|
|
53
|
+
**File:** `src/features/boulder-state/storage.ts`
|
|
54
|
+
|
|
55
|
+
- After the `session_ids` fix, add validation for `active_plan` and `plan_name` (required fields)
|
|
56
|
+
- Validate `worktree_path` is either `undefined` or a string (not `null`, not a number)
|
|
57
|
+
- Return `null` for boulder states with missing required fields
|
|
58
|
+
|
|
59
|
+
### Step 2: Add try/catch in setTimeout callback
|
|
60
|
+
**File:** `src/hooks/atlas/idle-event.ts`
|
|
61
|
+
|
|
62
|
+
- Wrap the `setTimeout` async callback body in try/catch
|
|
63
|
+
- Log errors with the atlas hook logger
|
|
64
|
+
|
|
65
|
+
### Step 3: Add defensive guard in `getPlanProgress`
|
|
66
|
+
**File:** `src/features/boulder-state/storage.ts`
|
|
67
|
+
|
|
68
|
+
- Add early return for non-string `planPath` argument
|
|
69
|
+
|
|
70
|
+
### Step 4: Add tests
|
|
71
|
+
**Files:**
|
|
72
|
+
- `src/features/boulder-state/storage.test.ts` - test missing/malformed fields
|
|
73
|
+
- `src/hooks/atlas/index.test.ts` - test atlas hook with boulder missing worktree_path
|
|
74
|
+
|
|
75
|
+
### Step 5: Run CI checks
|
|
76
|
+
```bash
|
|
77
|
+
bun run typecheck
|
|
78
|
+
bun test src/features/boulder-state/storage.test.ts
|
|
79
|
+
bun test src/hooks/atlas/index.test.ts
|
|
80
|
+
bun test # full suite
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Step 6: Create PR
|
|
84
|
+
- Branch: `fix/atlas-hook-missing-worktree-path`
|
|
85
|
+
- Target: `dev`
|
|
86
|
+
- Run CI and verify passes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
- Fix crash in atlas hook when `boulder.json` is missing `worktree_path` (or other required fields) by hardening `readBoulderState()` validation
|
|
4
|
+
- Wrap the unprotected `setTimeout` retry callback in `idle-event.ts` with try/catch to prevent unhandled promise rejections
|
|
5
|
+
- Add defensive type guard in `getPlanProgress()` to prevent `existsSync(undefined)` TypeError
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
When `boulder.json` is malformed or manually edited to omit fields, `readBoulderState()` returns an object cast as `BoulderState` without validating required fields. Downstream callers like `getPlanProgress(boulderState.active_plan)` then pass `undefined` to `existsSync()`, which throws a TypeError. This crash is especially dangerous in the `setTimeout` retry callback in `idle-event.ts`, where the error becomes an unhandled promise rejection.
|
|
10
|
+
|
|
11
|
+
## Changes
|
|
12
|
+
|
|
13
|
+
### `src/features/boulder-state/storage.ts`
|
|
14
|
+
- `readBoulderState()`: Validate `active_plan` and `plan_name` are strings (return `null` if not)
|
|
15
|
+
- `readBoulderState()`: Strip `worktree_path` if present but not a string type
|
|
16
|
+
- `getPlanProgress()`: Add `typeof planPath !== "string"` guard before `existsSync`
|
|
17
|
+
|
|
18
|
+
### `src/hooks/atlas/idle-event.ts`
|
|
19
|
+
- Wrap `scheduleRetry` setTimeout async callback body in try/catch
|
|
20
|
+
|
|
21
|
+
### Tests
|
|
22
|
+
- `src/features/boulder-state/storage.test.ts`: 5 new tests for missing/malformed fields
|
|
23
|
+
- `src/hooks/atlas/index.test.ts`: 2 new tests for worktree_path presence/absence in continuation prompt
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Verification Strategy
|
|
2
|
+
|
|
3
|
+
## 1. Unit Tests (Direct Verification)
|
|
4
|
+
|
|
5
|
+
### boulder-state storage tests
|
|
6
|
+
```bash
|
|
7
|
+
bun test src/features/boulder-state/storage.test.ts
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Verify:
|
|
11
|
+
- `readBoulderState()` returns `null` when `active_plan` missing
|
|
12
|
+
- `readBoulderState()` returns `null` when `plan_name` missing
|
|
13
|
+
- `readBoulderState()` strips non-string `worktree_path` (e.g., `null`)
|
|
14
|
+
- `readBoulderState()` preserves valid string `worktree_path`
|
|
15
|
+
- `getPlanProgress(undefined)` returns safe default without crashing
|
|
16
|
+
- Existing tests still pass (session_ids defaults, empty object, etc.)
|
|
17
|
+
|
|
18
|
+
### atlas hook tests
|
|
19
|
+
```bash
|
|
20
|
+
bun test src/hooks/atlas/index.test.ts
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Verify:
|
|
24
|
+
- session.idle handler works with boulder state missing `worktree_path` (no crash, prompt injected)
|
|
25
|
+
- session.idle handler includes `[Worktree: ...]` context when `worktree_path` IS present
|
|
26
|
+
- All 30+ existing tests still pass
|
|
27
|
+
|
|
28
|
+
### atlas idle-event lineage tests
|
|
29
|
+
```bash
|
|
30
|
+
bun test src/hooks/atlas/idle-event-lineage.test.ts
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Verify existing lineage tests unaffected.
|
|
34
|
+
|
|
35
|
+
### start-work hook tests
|
|
36
|
+
```bash
|
|
37
|
+
bun test src/hooks/start-work/index.test.ts
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Verify worktree-related start-work tests still pass (these create boulder states with/without `worktree_path`).
|
|
41
|
+
|
|
42
|
+
## 2. Type Safety
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bun run typecheck
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Verify zero new TypeScript errors. The changes are purely additive runtime guards that align with existing types (`worktree_path?: string`).
|
|
49
|
+
|
|
50
|
+
## 3. LSP Diagnostics on Changed Files
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
lsp_diagnostics on:
|
|
54
|
+
- src/features/boulder-state/storage.ts
|
|
55
|
+
- src/hooks/atlas/idle-event.ts
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Verify zero errors/warnings.
|
|
59
|
+
|
|
60
|
+
## 4. Full Test Suite
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bun test
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Verify no regressions across the entire codebase.
|
|
67
|
+
|
|
68
|
+
## 5. Build
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
bun run build
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Verify build succeeds.
|
|
75
|
+
|
|
76
|
+
## 6. Manual Smoke Test (Reproduction)
|
|
77
|
+
|
|
78
|
+
To manually verify the fix:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Create a malformed boulder.json (missing worktree_path)
|
|
82
|
+
mkdir -p .sisyphus
|
|
83
|
+
echo '{"active_plan": ".sisyphus/plans/test.md", "plan_name": "test", "session_ids": ["ses-1"]}' > .sisyphus/boulder.json
|
|
84
|
+
|
|
85
|
+
# Create a plan file
|
|
86
|
+
mkdir -p .sisyphus/plans
|
|
87
|
+
echo '# Plan\n- [ ] Task 1' > .sisyphus/plans/test.md
|
|
88
|
+
|
|
89
|
+
# Start opencode - atlas hook should NOT crash when session.idle fires
|
|
90
|
+
# Verify /tmp/oh-my-opencode.log shows normal continuation behavior
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Also test the extreme case:
|
|
94
|
+
```bash
|
|
95
|
+
# boulder.json with no required fields
|
|
96
|
+
echo '{}' > .sisyphus/boulder.json
|
|
97
|
+
|
|
98
|
+
# After fix: readBoulderState returns null, atlas hook gracefully skips
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 7. CI Pipeline
|
|
102
|
+
|
|
103
|
+
After pushing the branch, verify:
|
|
104
|
+
- `ci.yml` workflow passes: tests (split: mock-heavy isolated + batch), typecheck, build
|
|
105
|
+
- No new lint warnings
|
|
106
|
+
|
|
107
|
+
## 8. Edge Cases Covered
|
|
108
|
+
|
|
109
|
+
| Scenario | Expected Behavior |
|
|
110
|
+
|----------|-------------------|
|
|
111
|
+
| `boulder.json` = `{}` | `readBoulderState` returns `null` |
|
|
112
|
+
| `boulder.json` missing `active_plan` | `readBoulderState` returns `null` |
|
|
113
|
+
| `boulder.json` missing `plan_name` | `readBoulderState` returns `null` |
|
|
114
|
+
| `boulder.json` has `worktree_path: null` | Field stripped, returned as `undefined` |
|
|
115
|
+
| `boulder.json` has `worktree_path: 42` | Field stripped, returned as `undefined` |
|
|
116
|
+
| `boulder.json` has no `worktree_path` | Works normally, no crash |
|
|
117
|
+
| `boulder.json` has valid `worktree_path` | Preserved, included in continuation prompt |
|
|
118
|
+
| setTimeout retry with corrupted boulder.json | Error caught and logged, no process crash |
|
|
119
|
+
| `getPlanProgress(undefined)` | Returns `{ total: 0, completed: 0, isComplete: true }` |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"total_tokens": null, "duration_ms": 325000, "total_duration_seconds": 325}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"eval_id": 3,
|
|
3
|
+
"eval_name": "refactor-split-constants",
|
|
4
|
+
"prompt": "Refactor src/tools/delegate-task/constants.ts to split DEFAULT_CATEGORIES and CATEGORY_MODEL_REQUIREMENTS into separate files. Keep backward compatibility with the barrel export. Make a PR.",
|
|
5
|
+
"assertions": [
|
|
6
|
+
{
|
|
7
|
+
"id": "worktree-isolation",
|
|
8
|
+
"text": "Plan uses git worktree in a sibling directory",
|
|
9
|
+
"type": "manual"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"id": "multiple-atomic-commits",
|
|
13
|
+
"text": "Uses 2+ commits for the multi-file refactor",
|
|
14
|
+
"type": "manual"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "barrel-export",
|
|
18
|
+
"text": "Maintains backward compatibility via barrel re-export in constants.ts or index.ts",
|
|
19
|
+
"type": "manual"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "three-gates",
|
|
23
|
+
"text": "Verification loop includes all 3 gates",
|
|
24
|
+
"type": "manual"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "real-constants-file",
|
|
28
|
+
"text": "References actual src/tools/delegate-task/constants.ts file and its exports",
|
|
29
|
+
"type": "manual"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"run_id": "eval-3-with_skill",
|
|
3
|
+
"expectations": [
|
|
4
|
+
{"text": "Plan uses git worktree in a sibling directory", "passed": true, "evidence": "../omo-wt/refactor-delegate-task-constants"},
|
|
5
|
+
{"text": "Uses 2+ commits for the multi-file refactor", "passed": true, "evidence": "Commit 1: category defaults+appends, Commit 2: plan agent prompt+names"},
|
|
6
|
+
{"text": "Maintains backward compatibility via barrel re-export", "passed": true, "evidence": "constants.ts converted to re-export from 4 new files, full import map verified"},
|
|
7
|
+
{"text": "Verification loop includes all 3 gates", "passed": true, "evidence": "Gate A (CI), Gate B (review-work), Gate C (Cubic)"},
|
|
8
|
+
{"text": "References actual src/tools/delegate-task/constants.ts", "passed": true, "evidence": "654 lines analyzed, 4 responsibilities identified, full external+internal import map"}
|
|
9
|
+
]
|
|
10
|
+
}
|