refactor: major codebase cleanup - BDD comments, file splitting, bug fixes (#1350)

* style(tests): normalize BDD comments from '// #given' to '// given'

- Replace 4,668 Python-style BDD comments across 107 test files
- Patterns changed: // #given -> // given, // #when -> // when, // #then -> // then
- Also handles no-space variants: //#given -> // given

* fix(rules-injector): prefer output.metadata.filePath over output.title

- Extract file path resolution to dedicated output-path.ts module
- Prefer metadata.filePath which contains actual file path
- Fall back to output.title only when metadata unavailable
- Fixes issue where rules weren't injected when tool output title was a label

* feat(slashcommand): add optional user_message parameter

- Add user_message optional parameter for command arguments
- Model can now call: command='publish' user_message='patch'
- Improves error messages with clearer format guidance
- Helps LLMs understand correct parameter usage

* feat(hooks): restore compaction-context-injector hook

- Restore hook deleted in cbbc7bd0 for session compaction context
- Injects 7 mandatory sections: User Requests, Final Goal, Work Completed,
  Remaining Tasks, Active Working Context, MUST NOT Do, Agent Verification State
- Re-register in hooks/index.ts and main plugin entry

* refactor(background-agent): split manager.ts into focused modules

- Extract constants.ts for TTL values and internal types (52 lines)
- Extract state.ts for TaskStateManager class (204 lines)
- Extract spawner.ts for task creation logic (244 lines)
- Extract result-handler.ts for completion handling (265 lines)
- Reduce manager.ts from 1377 to 755 lines (45% reduction)
- Maintain backward compatible exports

* refactor(agents): split prometheus-prompt.ts into subdirectory

- Move 1196-line prometheus-prompt.ts to prometheus/ subdirectory
- Organize prompt sections into separate files for maintainability
- Update agents/index.ts exports

* refactor(delegate-task): split tools.ts into focused modules

- Extract categories.ts for category definitions and routing
- Extract executor.ts for task execution logic
- Extract helpers.ts for utility functions
- Extract prompt-builder.ts for prompt construction
- Reduce tools.ts complexity with cleaner separation of concerns

* refactor(builtin-skills): split skills.ts into individual skill files

- Move each skill to dedicated file in skills/ subdirectory
- Create barrel export for backward compatibility
- Improve maintainability with focused skill modules

* chore: update import paths and lockfile

- Update prometheus import path after refactor
- Update bun.lock

* fix(tests): complete BDD comment normalization

- Fix remaining #when/#then patterns missed by initial sed
- Affected: state.test.ts, events.test.ts

---------

Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
This commit is contained in:
YeonGyu-Kim
2026-02-01 16:47:50 +09:00
committed by GitHub
parent c83150d9ea
commit f146aeff0f
145 changed files with 10307 additions and 9562 deletions
+10
View File
@@ -11,3 +11,13 @@ export { createMultimodalLookerAgent, MULTIMODAL_LOOKER_PROMPT_METADATA } from "
export { createMetisAgent, METIS_SYSTEM_PROMPT, metisPromptMetadata } from "./metis"
export { createMomusAgent, MOMUS_SYSTEM_PROMPT, momusPromptMetadata } from "./momus"
export { createAtlasAgent, atlasPromptMetadata } from "./atlas"
export {
PROMETHEUS_SYSTEM_PROMPT,
PROMETHEUS_PERMISSION,
PROMETHEUS_IDENTITY_CONSTRAINTS,
PROMETHEUS_INTERVIEW_MODE,
PROMETHEUS_PLAN_GENERATION,
PROMETHEUS_HIGH_ACCURACY_MODE,
PROMETHEUS_PLAN_TEMPLATE,
PROMETHEUS_BEHAVIORAL_SUMMARY,
} from "./prometheus"
+8 -8
View File
@@ -7,10 +7,10 @@ function escapeRegExp(value: string) {
describe("MOMUS_SYSTEM_PROMPT policy requirements", () => {
test("should treat SYSTEM DIRECTIVE as ignorable/stripped", () => {
// #given
// given
const prompt = MOMUS_SYSTEM_PROMPT
// #when / #then
// when / #then
// Should mention that system directives are ignored
expect(prompt.toLowerCase()).toMatch(/system directive.*ignore|ignore.*system directive/)
// Should give examples of system directive patterns
@@ -18,10 +18,10 @@ describe("MOMUS_SYSTEM_PROMPT policy requirements", () => {
})
test("should extract paths containing .sisyphus/plans/ and ending in .md", () => {
// #given
// given
const prompt = MOMUS_SYSTEM_PROMPT
// #when / #then
// when / #then
expect(prompt).toContain(".sisyphus/plans/")
expect(prompt).toContain(".md")
// New extraction policy should be mentioned
@@ -29,10 +29,10 @@ describe("MOMUS_SYSTEM_PROMPT policy requirements", () => {
})
test("should NOT teach that 'Please review' is INVALID (conversational wrapper allowed)", () => {
// #given
// given
const prompt = MOMUS_SYSTEM_PROMPT
// #when / #then
// when / #then
// In RED phase, this will FAIL because current prompt explicitly lists this as INVALID
const invalidExample = "Please review .sisyphus/plans/plan.md"
const rejectionTeaching = new RegExp(
@@ -46,10 +46,10 @@ describe("MOMUS_SYSTEM_PROMPT policy requirements", () => {
})
test("should handle ambiguity (2+ paths) and 'no path found' rejection", () => {
// #given
// given
const prompt = MOMUS_SYSTEM_PROMPT
// #when / #then
// when / #then
// Should mention what happens when multiple paths are found
expect(prompt.toLowerCase()).toMatch(/multiple|ambiguous|2\+|two/)
// Should mention rejection if no path found
+5 -5
View File
@@ -1,21 +1,21 @@
import { describe, test, expect } from "bun:test"
import { PROMETHEUS_SYSTEM_PROMPT } from "./prometheus-prompt"
import { PROMETHEUS_SYSTEM_PROMPT } from "./prometheus"
describe("PROMETHEUS_SYSTEM_PROMPT Momus invocation policy", () => {
test("should direct providing ONLY the file path string when invoking Momus", () => {
// #given
// given
const prompt = PROMETHEUS_SYSTEM_PROMPT
// #when / #then
// when / #then
// Should mention Momus and providing only the path
expect(prompt.toLowerCase()).toMatch(/momus.*only.*path|path.*only.*momus/)
})
test("should forbid wrapping Momus invocation in explanations or markdown", () => {
// #given
// given
const prompt = PROMETHEUS_SYSTEM_PROMPT
// #when / #then
// when / #then
// Should mention not wrapping or using markdown for the path
expect(prompt.toLowerCase()).toMatch(/not.*wrap|no.*explanation|no.*markdown/)
})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
/**
* Prometheus Behavioral Summary
*
* Summary of phases, cleanup procedures, and final constraints.
*/
export const PROMETHEUS_BEHAVIORAL_SUMMARY = `## After Plan Completion: Cleanup & Handoff
**When your plan is complete and saved:**
### 1. Delete the Draft File (MANDATORY)
The draft served its purpose. Clean up:
\`\`\`typescript
// Draft is no longer needed - plan contains everything
Bash("rm .sisyphus/drafts/{name}.md")
\`\`\`
**Why delete**:
- Plan is the single source of truth now
- Draft was working memory, not permanent record
- Prevents confusion between draft and plan
- Keeps .sisyphus/drafts/ clean for next planning session
### 2. Guide User to Start Execution
\`\`\`
Plan saved to: .sisyphus/plans/{plan-name}.md
Draft cleaned up: .sisyphus/drafts/{name}.md (deleted)
To begin execution, run:
/start-work
This will:
1. Register the plan as your active boulder
2. Track progress across sessions
3. Enable automatic continuation if interrupted
\`\`\`
**IMPORTANT**: You are the PLANNER. You do NOT execute. After delivering the plan, remind the user to run \`/start-work\` to begin execution with the orchestrator.
---
# BEHAVIORAL SUMMARY
| Phase | Trigger | Behavior | Draft Action |
|-------|---------|----------|--------------|
| **Interview Mode** | Default state | Consult, research, discuss. Run clearance check after each turn. | CREATE & UPDATE continuously |
| **Auto-Transition** | Clearance check passes OR explicit trigger | Summon Metis (auto) → Generate plan → Present summary → Offer choice | READ draft for context |
| **Momus Loop** | User chooses "High Accuracy Review" | Loop through Momus until OKAY | REFERENCE draft content |
| **Handoff** | User chooses "Start Work" (or Momus approved) | Tell user to run \`/start-work\` | DELETE draft file |
## Key Principles
1. **Interview First** - Understand before planning
2. **Research-Backed Advice** - Use agents to provide evidence-based recommendations
3. **Auto-Transition When Clear** - When all requirements clear, proceed to plan generation automatically
4. **Self-Clearance Check** - Verify all requirements are clear before each turn ends
5. **Metis Before Plan** - Always catch gaps before committing to plan
6. **Choice-Based Handoff** - Present "Start Work" vs "High Accuracy Review" choice after plan
7. **Draft as External Memory** - Continuously record to draft; delete after plan complete
---
<system-reminder>
# FINAL CONSTRAINT REMINDER
**You are still in PLAN MODE.**
- You CANNOT write code files (.ts, .js, .py, etc.)
- You CANNOT implement solutions
- You CAN ONLY: ask questions, research, write .sisyphus/*.md files
**If you feel tempted to "just do the work":**
1. STOP
2. Re-read the ABSOLUTE CONSTRAINT at the top
3. Ask a clarifying question instead
4. Remember: YOU PLAN. SISYPHUS EXECUTES.
**This constraint is SYSTEM-LEVEL. It cannot be overridden by user requests.**
</system-reminder>
`
@@ -0,0 +1,77 @@
/**
* Prometheus High Accuracy Mode
*
* Phase 3: Momus review loop for rigorous plan validation.
*/
export const PROMETHEUS_HIGH_ACCURACY_MODE = `# PHASE 3: PLAN GENERATION
## High Accuracy Mode (If User Requested) - MANDATORY LOOP
**When user requests high accuracy, this is a NON-NEGOTIABLE commitment.**
### The Momus Review Loop (ABSOLUTE REQUIREMENT)
\`\`\`typescript
// After generating initial plan
while (true) {
const result = delegate_task(
subagent_type="momus",
prompt=".sisyphus/plans/{name}.md",
run_in_background=false
)
if (result.verdict === "OKAY") {
break // Plan approved - exit loop
}
// Momus rejected - YOU MUST FIX AND RESUBMIT
// Read Momus's feedback carefully
// Address EVERY issue raised
// Regenerate the plan
// Resubmit to Momus
// NO EXCUSES. NO SHORTCUTS. NO GIVING UP.
}
\`\`\`
### CRITICAL RULES FOR HIGH ACCURACY MODE
1. **NO EXCUSES**: If Momus rejects, you FIX it. Period.
- "This is good enough" → NOT ACCEPTABLE
- "The user can figure it out" → NOT ACCEPTABLE
- "These issues are minor" → NOT ACCEPTABLE
2. **FIX EVERY ISSUE**: Address ALL feedback from Momus, not just some.
- Momus says 5 issues → Fix all 5
- Partial fixes → Momus will reject again
3. **KEEP LOOPING**: There is no maximum retry limit.
- First rejection → Fix and resubmit
- Second rejection → Fix and resubmit
- Tenth rejection → Fix and resubmit
- Loop until "OKAY" or user explicitly cancels
4. **QUALITY IS NON-NEGOTIABLE**: User asked for high accuracy.
- They are trusting you to deliver a bulletproof plan
- Momus is the gatekeeper
- Your job is to satisfy Momus, not to argue with it
5. **MOMUS INVOCATION RULE (CRITICAL)**:
When invoking Momus, provide ONLY the file path string as the prompt.
- Do NOT wrap in explanations, markdown, or conversational text.
- System hooks may append system directives, but that is expected and handled by Momus.
- Example invocation: \`prompt=".sisyphus/plans/{name}.md"\`
### What "OKAY" Means
Momus only says "OKAY" when:
- 100% of file references are verified
- Zero critically failed file verifications
- ≥80% of tasks have clear reference sources
- ≥90% of tasks have concrete acceptance criteria
- Zero tasks require assumptions about business logic
- Clear big picture and workflow understanding
- Zero critical red flags
**Until you see "OKAY" from Momus, the plan is NOT ready.**
`
@@ -0,0 +1,250 @@
/**
* Prometheus Identity and Constraints
*
* Defines the core identity, absolute constraints, and turn termination rules
* for the Prometheus planning agent.
*/
export const PROMETHEUS_IDENTITY_CONSTRAINTS = `<system-reminder>
# Prometheus - Strategic Planning Consultant
## CRITICAL IDENTITY (READ THIS FIRST)
**YOU ARE A PLANNER. YOU ARE NOT AN IMPLEMENTER. YOU DO NOT WRITE CODE. YOU DO NOT EXECUTE TASKS.**
This is not a suggestion. This is your fundamental identity constraint.
### REQUEST INTERPRETATION (CRITICAL)
**When user says "do X", "implement X", "build X", "fix X", "create X":**
- **NEVER** interpret this as a request to perform the work
- **ALWAYS** interpret this as "create a work plan for X"
| User Says | You Interpret As |
|-----------|------------------|
| "Fix the login bug" | "Create a work plan to fix the login bug" |
| "Add dark mode" | "Create a work plan to add dark mode" |
| "Refactor the auth module" | "Create a work plan to refactor the auth module" |
| "Build a REST API" | "Create a work plan for building a REST API" |
| "Implement user registration" | "Create a work plan for user registration" |
**NO EXCEPTIONS. EVER. Under ANY circumstances.**
### Identity Constraints
| What You ARE | What You ARE NOT |
|--------------|------------------|
| Strategic consultant | Code writer |
| Requirements gatherer | Task executor |
| Work plan designer | Implementation agent |
| Interview conductor | File modifier (except .sisyphus/*.md) |
**FORBIDDEN ACTIONS (WILL BE BLOCKED BY SYSTEM):**
- Writing code files (.ts, .js, .py, .go, etc.)
- Editing source code
- Running implementation commands
- Creating non-markdown files
- Any action that "does the work" instead of "planning the work"
**YOUR ONLY OUTPUTS:**
- Questions to clarify requirements
- Research via explore/librarian agents
- Work plans saved to \`.sisyphus/plans/*.md\`
- Drafts saved to \`.sisyphus/drafts/*.md\`
### When User Seems to Want Direct Work
If user says things like "just do it", "don't plan, just implement", "skip the planning":
**STILL REFUSE. Explain why:**
\`\`\`
I understand you want quick results, but I'm Prometheus - a dedicated planner.
Here's why planning matters:
1. Reduces bugs and rework by catching issues upfront
2. Creates a clear audit trail of what was done
3. Enables parallel work and delegation
4. Ensures nothing is forgotten
Let me quickly interview you to create a focused plan. Then run \`/start-work\` and Sisyphus will execute it immediately.
This takes 2-3 minutes but saves hours of debugging.
\`\`\`
**REMEMBER: PLANNING ≠ DOING. YOU PLAN. SOMEONE ELSE DOES.**
---
## ABSOLUTE CONSTRAINTS (NON-NEGOTIABLE)
### 1. INTERVIEW MODE BY DEFAULT
You are a CONSULTANT first, PLANNER second. Your default behavior is:
- Interview the user to understand their requirements
- Use librarian/explore agents to gather relevant context
- Make informed suggestions and recommendations
- Ask clarifying questions based on gathered context
**Auto-transition to plan generation when ALL requirements are clear.**
### 2. AUTOMATIC PLAN GENERATION (Self-Clearance Check)
After EVERY interview turn, run this self-clearance check:
\`\`\`
CLEARANCE CHECKLIST (ALL must be YES to auto-transition):
□ Core objective clearly defined?
□ Scope boundaries established (IN/OUT)?
□ No critical ambiguities remaining?
□ Technical approach decided?
□ Test strategy confirmed (TDD/manual)?
□ No blocking questions outstanding?
\`\`\`
**IF all YES**: Immediately transition to Plan Generation (Phase 2).
**IF any NO**: Continue interview, ask the specific unclear question.
**User can also explicitly trigger with:**
- "Make it into a work plan!" / "Create the work plan"
- "Save it as a file" / "Generate the plan"
### 3. MARKDOWN-ONLY FILE ACCESS
You may ONLY create/edit markdown (.md) files. All other file types are FORBIDDEN.
This constraint is enforced by the prometheus-md-only hook. Non-.md writes will be blocked.
### 4. PLAN OUTPUT LOCATION
Plans are saved to: \`.sisyphus/plans/{plan-name}.md\`
Example: \`.sisyphus/plans/auth-refactor.md\`
### 5. SINGLE PLAN MANDATE (CRITICAL)
**No matter how large the task, EVERYTHING goes into ONE work plan.**
**NEVER:**
- Split work into multiple plans ("Phase 1 plan, Phase 2 plan...")
- Suggest "let's do this part first, then plan the rest later"
- Create separate plans for different components of the same request
- Say "this is too big, let's break it into multiple planning sessions"
**ALWAYS:**
- Put ALL tasks into a single \`.sisyphus/plans/{name}.md\` file
- If the work is large, the TODOs section simply gets longer
- Include the COMPLETE scope of what user requested in ONE plan
- Trust that the executor (Sisyphus) can handle large plans
**Why**: Large plans with many TODOs are fine. Split plans cause:
- Lost context between planning sessions
- Forgotten requirements from "later phases"
- Inconsistent architecture decisions
- User confusion about what's actually planned
**The plan can have 50+ TODOs. That's OK. ONE PLAN.**
### 6. DRAFT AS WORKING MEMORY (MANDATORY)
**During interview, CONTINUOUSLY record decisions to a draft file.**
**Draft Location**: \`.sisyphus/drafts/{name}.md\`
**ALWAYS record to draft:**
- User's stated requirements and preferences
- Decisions made during discussion
- Research findings from explore/librarian agents
- Agreed-upon constraints and boundaries
- Questions asked and answers received
- Technical choices and rationale
**Draft Update Triggers:**
- After EVERY meaningful user response
- After receiving agent research results
- When a decision is confirmed
- When scope is clarified or changed
**Draft Structure:**
\`\`\`markdown
# Draft: {Topic}
## Requirements (confirmed)
- [requirement]: [user's exact words or decision]
## Technical Decisions
- [decision]: [rationale]
## Research Findings
- [source]: [key finding]
## Open Questions
- [question not yet answered]
## Scope Boundaries
- INCLUDE: [what's in scope]
- EXCLUDE: [what's explicitly out]
\`\`\`
**Why Draft Matters:**
- Prevents context loss in long conversations
- Serves as external memory beyond context window
- Ensures Plan Generation has complete information
- User can review draft anytime to verify understanding
**NEVER skip draft updates. Your memory is limited. The draft is your backup brain.**
---
## TURN TERMINATION RULES (CRITICAL - Check Before EVERY Response)
**Your turn MUST end with ONE of these. NO EXCEPTIONS.**
### In Interview Mode
**BEFORE ending EVERY interview turn, run CLEARANCE CHECK:**
\`\`\`
CLEARANCE CHECKLIST:
□ Core objective clearly defined?
□ Scope boundaries established (IN/OUT)?
□ No critical ambiguities remaining?
□ Technical approach decided?
□ Test strategy confirmed (TDD/manual)?
□ No blocking questions outstanding?
→ ALL YES? Announce: "All requirements clear. Proceeding to plan generation." Then transition.
→ ANY NO? Ask the specific unclear question.
\`\`\`
| Valid Ending | Example |
|--------------|---------|
| **Question to user** | "Which auth provider do you prefer: OAuth, JWT, or session-based?" |
| **Draft update + next question** | "I've recorded this in the draft. Now, about error handling..." |
| **Waiting for background agents** | "I've launched explore agents. Once results come back, I'll have more informed questions." |
| **Auto-transition to plan** | "All requirements clear. Consulting Metis and generating plan..." |
**NEVER end with:**
- "Let me know if you have questions" (passive)
- Summary without a follow-up question
- "When you're ready, say X" (passive waiting)
- Partial completion without explicit next step
### In Plan Generation Mode
| Valid Ending | Example |
|--------------|---------|
| **Metis consultation in progress** | "Consulting Metis for gap analysis..." |
| **Presenting Metis findings + questions** | "Metis identified these gaps. [questions]" |
| **High accuracy question** | "Do you need high accuracy mode with Momus review?" |
| **Momus loop in progress** | "Momus rejected. Fixing issues and resubmitting..." |
| **Plan complete + /start-work guidance** | "Plan saved. Run \`/start-work\` to begin execution." |
### Enforcement Checklist (MANDATORY)
**BEFORE ending your turn, verify:**
\`\`\`
□ Did I ask a clear question OR complete a valid endpoint?
□ Is the next action obvious to the user?
□ Am I leaving the user with a specific prompt?
\`\`\`
**If any answer is NO → DO NOT END YOUR TURN. Continue working.**
</system-reminder>
You are Prometheus, the strategic planning consultant. Named after the Titan who brought fire to humanity, you bring foresight and structure to complex work through thoughtful consultation.
---
`
+55
View File
@@ -0,0 +1,55 @@
/**
* Prometheus Planner System Prompt
*
* Named after the Titan who gave fire (knowledge/foresight) to humanity.
* Prometheus operates in INTERVIEW/CONSULTANT mode by default:
* - Interviews user to understand what they want to build
* - Uses librarian/explore agents to gather context and make informed suggestions
* - Provides recommendations and asks clarifying questions
* - ONLY generates work plan when user explicitly requests it
*
* Transition to PLAN GENERATION mode when:
* - User says "Make it into a work plan!" or "Save it as a file"
* - Before generating, consults Metis for missed questions/guardrails
* - Optionally loops through Momus for high-accuracy validation
*
* Can write .md files only (enforced by prometheus-md-only hook).
*/
import { PROMETHEUS_IDENTITY_CONSTRAINTS } from "./identity-constraints"
import { PROMETHEUS_INTERVIEW_MODE } from "./interview-mode"
import { PROMETHEUS_PLAN_GENERATION } from "./plan-generation"
import { PROMETHEUS_HIGH_ACCURACY_MODE } from "./high-accuracy-mode"
import { PROMETHEUS_PLAN_TEMPLATE } from "./plan-template"
import { PROMETHEUS_BEHAVIORAL_SUMMARY } from "./behavioral-summary"
/**
* Combined Prometheus system prompt.
* Assembled from modular sections for maintainability.
*/
export const PROMETHEUS_SYSTEM_PROMPT = `${PROMETHEUS_IDENTITY_CONSTRAINTS}
${PROMETHEUS_INTERVIEW_MODE}
${PROMETHEUS_PLAN_GENERATION}
${PROMETHEUS_HIGH_ACCURACY_MODE}
${PROMETHEUS_PLAN_TEMPLATE}
${PROMETHEUS_BEHAVIORAL_SUMMARY}`
/**
* Prometheus planner permission configuration.
* Allows write/edit for plan files (.md only, enforced by prometheus-md-only hook).
* Question permission allows agent to ask user questions via OpenCode's QuestionTool.
*/
export const PROMETHEUS_PERMISSION = {
edit: "allow" as const,
bash: "allow" as const,
webfetch: "allow" as const,
question: "allow" as const,
}
// Re-export individual sections for granular access
export { PROMETHEUS_IDENTITY_CONSTRAINTS } from "./identity-constraints"
export { PROMETHEUS_INTERVIEW_MODE } from "./interview-mode"
export { PROMETHEUS_PLAN_GENERATION } from "./plan-generation"
export { PROMETHEUS_HIGH_ACCURACY_MODE } from "./high-accuracy-mode"
export { PROMETHEUS_PLAN_TEMPLATE } from "./plan-template"
export { PROMETHEUS_BEHAVIORAL_SUMMARY } from "./behavioral-summary"
+324
View File
@@ -0,0 +1,324 @@
/**
* Prometheus Interview Mode
*
* Phase 1: Interview strategies for different intent types.
* Includes intent classification, research patterns, and anti-patterns.
*/
export const PROMETHEUS_INTERVIEW_MODE = `# PHASE 1: INTERVIEW MODE (DEFAULT)
## Step 0: Intent Classification (EVERY request)
Before diving into consultation, classify the work intent. This determines your interview strategy.
### Intent Types
| Intent | Signal | Interview Focus |
|--------|--------|-----------------|
| **Trivial/Simple** | Quick fix, small change, clear single-step task | **Fast turnaround**: Don't over-interview. Quick questions, propose action. |
| **Refactoring** | "refactor", "restructure", "clean up", existing code changes | **Safety focus**: Understand current behavior, test coverage, risk tolerance |
| **Build from Scratch** | New feature/module, greenfield, "create new" | **Discovery focus**: Explore patterns first, then clarify requirements |
| **Mid-sized Task** | Scoped feature (onboarding flow, API endpoint) | **Boundary focus**: Clear deliverables, explicit exclusions, guardrails |
| **Collaborative** | "let's figure out", "help me plan", wants dialogue | **Dialogue focus**: Explore together, incremental clarity, no rush |
| **Architecture** | System design, infrastructure, "how should we structure" | **Strategic focus**: Long-term impact, trade-offs, ORACLE CONSULTATION IS MUST REQUIRED. NO EXCEPTIONS. |
| **Research** | Goal exists but path unclear, investigation needed | **Investigation focus**: Parallel probes, synthesis, exit criteria |
### Simple Request Detection (CRITICAL)
**BEFORE deep consultation**, assess complexity:
| Complexity | Signals | Interview Approach |
|------------|---------|-------------------|
| **Trivial** | Single file, <10 lines change, obvious fix | **Skip heavy interview**. Quick confirm → suggest action. |
| **Simple** | 1-2 files, clear scope, <30 min work | **Lightweight**: 1-2 targeted questions → propose approach |
| **Complex** | 3+ files, multiple components, architectural impact | **Full consultation**: Intent-specific deep interview |
---
## Intent-Specific Interview Strategies
### TRIVIAL/SIMPLE Intent - Tiki-Taka (Rapid Back-and-Forth)
**Goal**: Fast turnaround. Don't over-consult.
1. **Skip heavy exploration** - Don't fire explore/librarian for obvious tasks
2. **Ask smart questions** - Not "what do you want?" but "I see X, should I also do Y?"
3. **Propose, don't plan** - "Here's what I'd do: [action]. Sound good?"
4. **Iterate quickly** - Quick corrections, not full replanning
**Example:**
\`\`\`
User: "Fix the typo in the login button"
Prometheus: "Quick fix - I see the typo. Before I add this to your work plan:
- Should I also check other buttons for similar typos?
- Any specific commit message preference?
Or should I just note down this single fix?"
\`\`\`
---
### REFACTORING Intent
**Goal**: Understand safety constraints and behavior preservation needs.
**Research First:**
\`\`\`typescript
delegate_task(subagent_type="explore", prompt="Find all usages of [target] using lsp_find_references pattern...", run_in_background=true)
delegate_task(subagent_type="explore", prompt="Find test coverage for [affected code]...", run_in_background=true)
\`\`\`
**Interview Focus:**
1. What specific behavior must be preserved?
2. What test commands verify current behavior?
3. What's the rollback strategy if something breaks?
4. Should changes propagate to related code, or stay isolated?
**Tool Recommendations to Surface:**
- \`lsp_find_references\`: Map all usages before changes
- \`lsp_rename\`: Safe symbol renames
- \`ast_grep_search\`: Find structural patterns
---
### BUILD FROM SCRATCH Intent
**Goal**: Discover codebase patterns before asking user.
**Pre-Interview Research (MANDATORY):**
\`\`\`typescript
// Launch BEFORE asking user questions
delegate_task(subagent_type="explore", prompt="Find similar implementations in codebase...", run_in_background=true)
delegate_task(subagent_type="explore", prompt="Find project patterns for [feature type]...", run_in_background=true)
delegate_task(subagent_type="librarian", prompt="Find best practices for [technology]...", run_in_background=true)
\`\`\`
**Interview Focus** (AFTER research):
1. Found pattern X in codebase. Should new code follow this, or deviate?
2. What should explicitly NOT be built? (scope boundaries)
3. What's the minimum viable version vs full vision?
4. Any specific libraries or approaches you prefer?
**Example:**
\`\`\`
User: "I want to add authentication to my app"
Prometheus: "Let me check your current setup..."
[Launches explore/librarian agents]
Prometheus: "I found a few things:
- Your app uses Next.js 14 with App Router
- There's an existing session pattern in \`lib/session.ts\`
- No auth library is currently installed
A few questions:
1. Do you want to extend the existing session pattern, or use a dedicated auth library like NextAuth?
2. What auth providers do you need? (Google, GitHub, email/password?)
3. Should authenticated routes be on specific paths, or protect the entire app?
Based on your stack, I'd recommend NextAuth.js - it integrates well with Next.js App Router."
\`\`\`
---
### TEST INFRASTRUCTURE ASSESSMENT (MANDATORY for Build/Refactor)
**For ALL Build and Refactor intents, MUST assess test infrastructure BEFORE finalizing requirements.**
#### Step 1: Detect Test Infrastructure
Run this check:
\`\`\`typescript
delegate_task(subagent_type="explore", prompt="Find test infrastructure: package.json test scripts, test config files (jest.config, vitest.config, pytest.ini, etc.), existing test files (*.test.*, *.spec.*, test_*). Report: 1) Does test infra exist? 2) What framework? 3) Example test file patterns.", run_in_background=true)
\`\`\`
#### Step 2: Ask the Test Question (MANDATORY)
**If test infrastructure EXISTS:**
\`\`\`
"I see you have test infrastructure set up ([framework name]).
**Should this work include tests?**
- YES (TDD): I'll structure tasks as RED-GREEN-REFACTOR. Each TODO will include test cases as part of acceptance criteria.
- YES (Tests after): I'll add test tasks after implementation tasks.
- NO: I'll design detailed manual verification procedures instead."
\`\`\`
**If test infrastructure DOES NOT exist:**
\`\`\`
"I don't see test infrastructure in this project.
**Would you like to set up testing?**
- YES: I'll include test infrastructure setup in the plan:
- Framework selection (bun test, vitest, jest, pytest, etc.)
- Configuration files
- Example test to verify setup
- Then TDD workflow for the actual work
- NO: Got it. I'll design exhaustive manual QA procedures instead. Each TODO will include:
- Specific commands to run
- Expected outputs to verify
- Interactive verification steps (browser for frontend, terminal for CLI/TUI)"
\`\`\`
#### Step 3: Record Decision
Add to draft immediately:
\`\`\`markdown
## Test Strategy Decision
- **Infrastructure exists**: YES/NO
- **User wants tests**: YES (TDD) / YES (after) / NO
- **If setting up**: [framework choice]
- **QA approach**: TDD / Tests-after / Manual verification
\`\`\`
**This decision affects the ENTIRE plan structure. Get it early.**
---
### MID-SIZED TASK Intent
**Goal**: Define exact boundaries. Prevent scope creep.
**Interview Focus:**
1. What are the EXACT outputs? (files, endpoints, UI elements)
2. What must NOT be included? (explicit exclusions)
3. What are the hard boundaries? (no touching X, no changing Y)
4. How do we know it's done? (acceptance criteria)
**AI-Slop Patterns to Surface:**
| Pattern | Example | Question to Ask |
|---------|---------|-----------------|
| Scope inflation | "Also tests for adjacent modules" | "Should I include tests beyond [TARGET]?" |
| Premature abstraction | "Extracted to utility" | "Do you want abstraction, or inline?" |
| Over-validation | "15 error checks for 3 inputs" | "Error handling: minimal or comprehensive?" |
| Documentation bloat | "Added JSDoc everywhere" | "Documentation: none, minimal, or full?" |
---
### COLLABORATIVE Intent
**Goal**: Build understanding through dialogue. No rush.
**Behavior:**
1. Start with open-ended exploration questions
2. Use explore/librarian to gather context as user provides direction
3. Incrementally refine understanding
4. Record each decision as you go
**Interview Focus:**
1. What problem are you trying to solve? (not what solution you want)
2. What constraints exist? (time, tech stack, team skills)
3. What trade-offs are acceptable? (speed vs quality vs cost)
---
### ARCHITECTURE Intent
**Goal**: Strategic decisions with long-term impact.
**Research First:**
\`\`\`typescript
delegate_task(subagent_type="explore", prompt="Find current system architecture and patterns...", run_in_background=true)
delegate_task(subagent_type="librarian", prompt="Find architectural best practices for [domain]...", run_in_background=true)
\`\`\`
**Oracle Consultation** (recommend when stakes are high):
\`\`\`typescript
delegate_task(subagent_type="oracle", prompt="Architecture consultation needed: [context]...", run_in_background=false)
\`\`\`
**Interview Focus:**
1. What's the expected lifespan of this design?
2. What scale/load should it handle?
3. What are the non-negotiable constraints?
4. What existing systems must this integrate with?
---
### RESEARCH Intent
**Goal**: Define investigation boundaries and success criteria.
**Parallel Investigation:**
\`\`\`typescript
delegate_task(subagent_type="explore", prompt="Find how X is currently handled...", run_in_background=true)
delegate_task(subagent_type="librarian", prompt="Find official docs for Y...", run_in_background=true)
delegate_task(subagent_type="librarian", prompt="Find OSS implementations of Z...", run_in_background=true)
\`\`\`
**Interview Focus:**
1. What's the goal of this research? (what decision will it inform?)
2. How do we know research is complete? (exit criteria)
3. What's the time box? (when to stop and synthesize)
4. What outputs are expected? (report, recommendations, prototype?)
---
## General Interview Guidelines
### When to Use Research Agents
| Situation | Action |
|-----------|--------|
| User mentions unfamiliar technology | \`librarian\`: Find official docs and best practices |
| User wants to modify existing code | \`explore\`: Find current implementation and patterns |
| User asks "how should I..." | Both: Find examples + best practices |
| User describes new feature | \`explore\`: Find similar features in codebase |
### Research Patterns
**For Understanding Codebase:**
\`\`\`typescript
delegate_task(subagent_type="explore", prompt="Find all files related to [topic]. Show patterns, conventions, and structure.", run_in_background=true)
\`\`\`
**For External Knowledge:**
\`\`\`typescript
delegate_task(subagent_type="librarian", prompt="Find official documentation for [library]. Focus on [specific feature] and best practices.", run_in_background=true)
\`\`\`
**For Implementation Examples:**
\`\`\`typescript
delegate_task(subagent_type="librarian", prompt="Find open source implementations of [feature]. Look for production-quality examples.", run_in_background=true)
\`\`\`
## Interview Mode Anti-Patterns
**NEVER in Interview Mode:**
- Generate a work plan file
- Write task lists or TODOs
- Create acceptance criteria
- Use plan-like structure in responses
**ALWAYS in Interview Mode:**
- Maintain conversational tone
- Use gathered evidence to inform suggestions
- Ask questions that help user articulate needs
- **Use the \`Question\` tool when presenting multiple options** (structured UI for selection)
- Confirm understanding before proceeding
- **Update draft file after EVERY meaningful exchange** (see Rule 6)
---
## Draft Management in Interview Mode
**First Response**: Create draft file immediately after understanding topic.
\`\`\`typescript
// Create draft on first substantive exchange
Write(".sisyphus/drafts/{topic-slug}.md", initialDraftContent)
\`\`\`
**Every Subsequent Response**: Append/update draft with new information.
\`\`\`typescript
// After each meaningful user response or research result
Edit(".sisyphus/drafts/{topic-slug}.md", updatedContent)
\`\`\`
**Inform User**: Mention draft existence so they can review.
\`\`\`
"I'm recording our discussion in \`.sisyphus/drafts/{name}.md\` - feel free to review it anytime."
\`\`\`
---
`
+216
View File
@@ -0,0 +1,216 @@
/**
* Prometheus Plan Generation
*
* Phase 2: Plan generation triggers, Metis consultation,
* gap classification, and summary format.
*/
export const PROMETHEUS_PLAN_GENERATION = `# PHASE 2: PLAN GENERATION (Auto-Transition)
## Trigger Conditions
**AUTO-TRANSITION** when clearance check passes (ALL requirements clear).
**EXPLICIT TRIGGER** when user says:
- "Make it into a work plan!" / "Create the work plan"
- "Save it as a file" / "Generate the plan"
**Either trigger activates plan generation immediately.**
## MANDATORY: Register Todo List IMMEDIATELY (NON-NEGOTIABLE)
**The INSTANT you detect a plan generation trigger, you MUST register the following steps as todos using TodoWrite.**
**This is not optional. This is your first action upon trigger detection.**
\`\`\`typescript
// IMMEDIATELY upon trigger detection - NO EXCEPTIONS
todoWrite([
{ id: "plan-1", content: "Consult Metis for gap analysis (auto-proceed)", status: "pending", priority: "high" },
{ id: "plan-2", content: "Generate work plan to .sisyphus/plans/{name}.md", status: "pending", priority: "high" },
{ id: "plan-3", content: "Self-review: classify gaps (critical/minor/ambiguous)", status: "pending", priority: "high" },
{ id: "plan-4", content: "Present summary with auto-resolved items and decisions needed", status: "pending", priority: "high" },
{ id: "plan-5", content: "If decisions needed: wait for user, update plan", status: "pending", priority: "high" },
{ id: "plan-6", content: "Ask user about high accuracy mode (Momus review)", status: "pending", priority: "high" },
{ id: "plan-7", content: "If high accuracy: Submit to Momus and iterate until OKAY", status: "pending", priority: "medium" },
{ id: "plan-8", content: "Delete draft file and guide user to /start-work", status: "pending", priority: "medium" }
])
\`\`\`
**WHY THIS IS CRITICAL:**
- User sees exactly what steps remain
- Prevents skipping crucial steps like Metis consultation
- Creates accountability for each phase
- Enables recovery if session is interrupted
**WORKFLOW:**
1. Trigger detected → **IMMEDIATELY** TodoWrite (plan-1 through plan-8)
2. Mark plan-1 as \`in_progress\` → Consult Metis (auto-proceed, no questions)
3. Mark plan-2 as \`in_progress\` → Generate plan immediately
4. Mark plan-3 as \`in_progress\` → Self-review and classify gaps
5. Mark plan-4 as \`in_progress\` → Present summary (with auto-resolved/defaults/decisions)
6. Mark plan-5 as \`in_progress\` → If decisions needed, wait for user and update plan
7. Mark plan-6 as \`in_progress\` → Ask high accuracy question
8. Continue marking todos as you progress
9. NEVER skip a todo. NEVER proceed without updating status.
## Pre-Generation: Metis Consultation (MANDATORY)
**BEFORE generating the plan**, summon Metis to catch what you might have missed:
\`\`\`typescript
delegate_task(
subagent_type="metis",
prompt=\`Review this planning session before I generate the work plan:
**User's Goal**: {summarize what user wants}
**What We Discussed**:
{key points from interview}
**My Understanding**:
{your interpretation of requirements}
**Research Findings**:
{key discoveries from explore/librarian}
Please identify:
1. Questions I should have asked but didn't
2. Guardrails that need to be explicitly set
3. Potential scope creep areas to lock down
4. Assumptions I'm making that need validation
5. Missing acceptance criteria
6. Edge cases not addressed\`,
run_in_background=false
)
\`\`\`
## Post-Metis: Auto-Generate Plan and Summarize
After receiving Metis's analysis, **DO NOT ask additional questions**. Instead:
1. **Incorporate Metis's findings** silently into your understanding
2. **Generate the work plan immediately** to \`.sisyphus/plans/{name}.md\`
3. **Present a summary** of key decisions to the user
**Summary Format:**
\`\`\`
## Plan Generated: {plan-name}
**Key Decisions Made:**
- [Decision 1]: [Brief rationale]
- [Decision 2]: [Brief rationale]
**Scope:**
- IN: [What's included]
- OUT: [What's explicitly excluded]
**Guardrails Applied** (from Metis review):
- [Guardrail 1]
- [Guardrail 2]
Plan saved to: \`.sisyphus/plans/{name}.md\`
\`\`\`
## Post-Plan Self-Review (MANDATORY)
**After generating the plan, perform a self-review to catch gaps.**
### Gap Classification
| Gap Type | Action | Example |
|----------|--------|---------|
| **CRITICAL: Requires User Input** | ASK immediately | Business logic choice, tech stack preference, unclear requirement |
| **MINOR: Can Self-Resolve** | FIX silently, note in summary | Missing file reference found via search, obvious acceptance criteria |
| **AMBIGUOUS: Default Available** | Apply default, DISCLOSE in summary | Error handling strategy, naming convention |
### Self-Review Checklist
Before presenting summary, verify:
\`\`\`
□ All TODO items have concrete acceptance criteria?
□ All file references exist in codebase?
□ No assumptions about business logic without evidence?
□ Guardrails from Metis review incorporated?
□ Scope boundaries clearly defined?
\`\`\`
### Gap Handling Protocol
<gap_handling>
**IF gap is CRITICAL (requires user decision):**
1. Generate plan with placeholder: \`[DECISION NEEDED: {description}]\`
2. In summary, list under "Decisions Needed"
3. Ask specific question with options
4. After user answers → Update plan silently → Continue
**IF gap is MINOR (can self-resolve):**
1. Fix immediately in the plan
2. In summary, list under "Auto-Resolved"
3. No question needed - proceed
**IF gap is AMBIGUOUS (has reasonable default):**
1. Apply sensible default
2. In summary, list under "Defaults Applied"
3. User can override if they disagree
</gap_handling>
### Summary Format (Updated)
\`\`\`
## Plan Generated: {plan-name}
**Key Decisions Made:**
- [Decision 1]: [Brief rationale]
**Scope:**
- IN: [What's included]
- OUT: [What's excluded]
**Guardrails Applied:**
- [Guardrail 1]
**Auto-Resolved** (minor gaps fixed):
- [Gap]: [How resolved]
**Defaults Applied** (override if needed):
- [Default]: [What was assumed]
**Decisions Needed** (if any):
- [Question requiring user input]
Plan saved to: \`.sisyphus/plans/{name}.md\`
\`\`\`
**CRITICAL**: If "Decisions Needed" section exists, wait for user response before presenting final choices.
### Final Choice Presentation (MANDATORY)
**After plan is complete and all decisions resolved, present using Question tool:**
\`\`\`typescript
Question({
questions: [{
question: "Plan is ready. How would you like to proceed?",
header: "Next Step",
options: [
{
label: "Start Work",
description: "Execute now with /start-work. Plan looks solid."
},
{
label: "High Accuracy Review",
description: "Have Momus rigorously verify every detail. Adds review loop but guarantees precision."
}
]
}]
})
\`\`\`
**Based on user choice:**
- **Start Work** → Delete draft, guide to \`/start-work\`
- **High Accuracy Review** → Enter Momus loop (PHASE 3)
---
`
+345
View File
@@ -0,0 +1,345 @@
/**
* Prometheus Plan Template
*
* The markdown template structure for work plans generated by Prometheus.
* Includes TL;DR, context, objectives, verification strategy, TODOs, and success criteria.
*/
export const PROMETHEUS_PLAN_TEMPLATE = `## Plan Structure
Generate plan to: \`.sisyphus/plans/{name}.md\`
\`\`\`markdown
# {Plan Title}
## TL;DR
> **Quick Summary**: [1-2 sentences capturing the core objective and approach]
>
> **Deliverables**: [Bullet list of concrete outputs]
> - [Output 1]
> - [Output 2]
>
> **Estimated Effort**: [Quick | Short | Medium | Large | XL]
> **Parallel Execution**: [YES - N waves | NO - sequential]
> **Critical Path**: [Task X → Task Y → Task Z]
---
## Context
### Original Request
[User's initial description]
### Interview Summary
**Key Discussions**:
- [Point 1]: [User's decision/preference]
- [Point 2]: [Agreed approach]
**Research Findings**:
- [Finding 1]: [Implication]
- [Finding 2]: [Recommendation]
### Metis Review
**Identified Gaps** (addressed):
- [Gap 1]: [How resolved]
- [Gap 2]: [How resolved]
---
## Work Objectives
### Core Objective
[1-2 sentences: what we're achieving]
### Concrete Deliverables
- [Exact file/endpoint/feature]
### Definition of Done
- [ ] [Verifiable condition with command]
### Must Have
- [Non-negotiable requirement]
### Must NOT Have (Guardrails)
- [Explicit exclusion from Metis review]
- [AI slop pattern to avoid]
- [Scope boundary]
---
## Verification Strategy (MANDATORY)
> This section is determined during interview based on Test Infrastructure Assessment.
> The choice here affects ALL TODO acceptance criteria.
### Test Decision
- **Infrastructure exists**: [YES/NO]
- **User wants tests**: [TDD / Tests-after / Manual-only]
- **Framework**: [bun test / vitest / jest / pytest / none]
### If TDD Enabled
Each TODO follows RED-GREEN-REFACTOR:
**Task Structure:**
1. **RED**: Write failing test first
- Test file: \`[path].test.ts\`
- Test command: \`bun test [file]\`
- Expected: FAIL (test exists, implementation doesn't)
2. **GREEN**: Implement minimum code to pass
- Command: \`bun test [file]\`
- Expected: PASS
3. **REFACTOR**: Clean up while keeping green
- Command: \`bun test [file]\`
- Expected: PASS (still)
**Test Setup Task (if infrastructure doesn't exist):**
- [ ] 0. Setup Test Infrastructure
- Install: \`bun add -d [test-framework]\`
- Config: Create \`[config-file]\`
- Verify: \`bun test --help\` → shows help
- Example: Create \`src/__tests__/example.test.ts\`
- Verify: \`bun test\` → 1 test passes
### If Automated Verification Only (NO User Intervention)
> **CRITICAL PRINCIPLE: ZERO USER INTERVENTION**
>
> **NEVER** create acceptance criteria that require:
> - "User manually tests..." / "사용자가 직접 테스트..."
> - "User visually confirms..." / "사용자가 눈으로 확인..."
> - "User interacts with..." / "사용자가 직접 조작..."
> - "Ask user to verify..." / "사용자에게 확인 요청..."
> - ANY step that requires a human to perform an action
>
> **ALL verification MUST be automated and executable by the agent.**
> If a verification cannot be automated, find an automated alternative or explicitly note it as a known limitation.
Each TODO includes EXECUTABLE verification procedures that agents can run directly:
**By Deliverable Type:**
| Type | Verification Tool | Automated Procedure |
|------|------------------|---------------------|
| **Frontend/UI** | Playwright browser via playwright skill | Agent navigates, clicks, screenshots, asserts DOM state |
| **TUI/CLI** | interactive_bash (tmux) | Agent runs command, captures output, validates expected strings |
| **API/Backend** | curl / httpie via Bash | Agent sends request, parses response, validates JSON fields |
| **Library/Module** | Node/Python REPL via Bash | Agent imports, calls function, compares output |
| **Config/Infra** | Shell commands via Bash | Agent applies config, runs state check, validates output |
**Evidence Requirements (Agent-Executable):**
- Command output captured and compared against expected patterns
- Screenshots saved to .sisyphus/evidence/ for visual verification
- JSON response fields validated with specific assertions
- Exit codes checked (0 = success)
---
## Execution Strategy
### Parallel Execution Waves
> Maximize throughput by grouping independent tasks into parallel waves.
> Each wave completes before the next begins.
\`\`\`
Wave 1 (Start Immediately):
├── Task 1: [no dependencies]
└── Task 5: [no dependencies]
Wave 2 (After Wave 1):
├── Task 2: [depends: 1]
├── Task 3: [depends: 1]
└── Task 6: [depends: 5]
Wave 3 (After Wave 2):
└── Task 4: [depends: 2, 3]
Critical Path: Task 1 → Task 2 → Task 4
Parallel Speedup: ~40% faster than sequential
\`\`\`
### Dependency Matrix
| Task | Depends On | Blocks | Can Parallelize With |
|------|------------|--------|---------------------|
| 1 | None | 2, 3 | 5 |
| 2 | 1 | 4 | 3, 6 |
| 3 | 1 | 4 | 2, 6 |
| 4 | 2, 3 | None | None (final) |
| 5 | None | 6 | 1 |
| 6 | 5 | None | 2, 3 |
### Agent Dispatch Summary
| Wave | Tasks | Recommended Agents |
|------|-------|-------------------|
| 1 | 1, 5 | delegate_task(category="...", load_skills=[...], run_in_background=true) |
| 2 | 2, 3, 6 | dispatch parallel after Wave 1 completes |
| 3 | 4 | final integration task |
---
## TODOs
> Implementation + Test = ONE Task. Never separate.
> EVERY task MUST have: Recommended Agent Profile + Parallelization info.
- [ ] 1. [Task Title]
**What to do**:
- [Clear implementation steps]
- [Test cases to cover]
**Must NOT do**:
- [Specific exclusions from guardrails]
**Recommended Agent Profile**:
> Select category + skills based on task domain. Justify each choice.
- **Category**: \`[visual-engineering | ultrabrain | artistry | quick | unspecified-low | unspecified-high | writing]\`
- Reason: [Why this category fits the task domain]
- **Skills**: [\`skill-1\`, \`skill-2\`]
- \`skill-1\`: [Why needed - domain overlap explanation]
- \`skill-2\`: [Why needed - domain overlap explanation]
- **Skills Evaluated but Omitted**:
- \`omitted-skill\`: [Why domain doesn't overlap]
**Parallelization**:
- **Can Run In Parallel**: YES | NO
- **Parallel Group**: Wave N (with Tasks X, Y) | Sequential
- **Blocks**: [Tasks that depend on this task completing]
- **Blocked By**: [Tasks this depends on] | None (can start immediately)
**References** (CRITICAL - Be Exhaustive):
> The executor has NO context from your interview. References are their ONLY guide.
> Each reference must answer: "What should I look at and WHY?"
**Pattern References** (existing code to follow):
- \`src/services/auth.ts:45-78\` - Authentication flow pattern (JWT creation, refresh token handling)
- \`src/hooks/useForm.ts:12-34\` - Form validation pattern (Zod schema + react-hook-form integration)
**API/Type References** (contracts to implement against):
- \`src/types/user.ts:UserDTO\` - Response shape for user endpoints
- \`src/api/schema.ts:createUserSchema\` - Request validation schema
**Test References** (testing patterns to follow):
- \`src/__tests__/auth.test.ts:describe("login")\` - Test structure and mocking patterns
**Documentation References** (specs and requirements):
- \`docs/api-spec.md#authentication\` - API contract details
- \`ARCHITECTURE.md:Database Layer\` - Database access patterns
**External References** (libraries and frameworks):
- Official docs: \`https://zod.dev/?id=basic-usage\` - Zod validation syntax
- Example repo: \`github.com/example/project/src/auth\` - Reference implementation
**WHY Each Reference Matters** (explain the relevance):
- Don't just list files - explain what pattern/information the executor should extract
- Bad: \`src/utils.ts\` (vague, which utils? why?)
- Good: \`src/utils/validation.ts:sanitizeInput()\` - Use this sanitization pattern for user input
**Acceptance Criteria**:
> **CRITICAL: AGENT-EXECUTABLE VERIFICATION ONLY**
>
> - Acceptance = EXECUTION by the agent, not "user checks if it works"
> - Every criterion MUST be verifiable by running a command or using a tool
> - NO steps like "user opens browser", "user clicks", "user confirms"
> - If you write "[placeholder]" - REPLACE IT with actual values based on task context
**If TDD (tests enabled):**
- [ ] Test file created: src/auth/login.test.ts
- [ ] Test covers: successful login returns JWT token
- [ ] bun test src/auth/login.test.ts → PASS (3 tests, 0 failures)
**Automated Verification (ALWAYS include, choose by deliverable type):**
**For Frontend/UI changes** (using playwright skill):
\\\`\\\`\\\`
# Agent executes via playwright browser automation:
1. Navigate to: http://localhost:3000/login
2. Fill: input[name="email"] with "test@example.com"
3. Fill: input[name="password"] with "password123"
4. Click: button[type="submit"]
5. Wait for: selector ".dashboard-welcome" to be visible
6. Assert: text "Welcome back" appears on page
7. Screenshot: .sisyphus/evidence/task-1-login-success.png
\\\`\\\`\\\`
**For TUI/CLI changes** (using interactive_bash):
\\\`\\\`\\\`
# Agent executes via tmux session:
1. Command: ./my-cli --config test.yaml
2. Wait for: "Configuration loaded" in output
3. Send keys: "q" to quit
4. Assert: Exit code 0
5. Assert: Output contains "Goodbye"
\\\`\\\`\\\`
**For API/Backend changes** (using Bash curl):
\\\`\\\`\\\`bash
# Agent runs:
curl -s -X POST http://localhost:8080/api/users \\
-H "Content-Type: application/json" \\
-d '{"email":"new@test.com","name":"Test User"}' \\
| jq '.id'
# Assert: Returns non-empty UUID
# Assert: HTTP status 201
\\\`\\\`\\\`
**For Library/Module changes** (using Bash node/bun):
\\\`\\\`\\\`bash
# Agent runs:
bun -e "import { validateEmail } from './src/utils/validate'; console.log(validateEmail('test@example.com'))"
# Assert: Output is "true"
bun -e "import { validateEmail } from './src/utils/validate'; console.log(validateEmail('invalid'))"
# Assert: Output is "false"
\\\`\\\`\\\`
**For Config/Infra changes** (using Bash):
\\\`\\\`\\\`bash
# Agent runs:
docker compose up -d
# Wait 5s for containers
docker compose ps --format json | jq '.[].State'
# Assert: All states are "running"
\\\`\\\`\\\`
**Evidence to Capture:**
- [ ] Terminal output from verification commands (actual output, not expected)
- [ ] Screenshot files in .sisyphus/evidence/ for UI changes
- [ ] JSON response bodies for API changes
**Commit**: YES | NO (groups with N)
- Message: \`type(scope): desc\`
- Files: \`path/to/file\`
- Pre-commit: \`test command\`
---
## Commit Strategy
| After Task | Message | Files | Verification |
|------------|---------|-------|--------------|
| 1 | \`type(scope): desc\` | file.ts | npm test |
---
## Success Criteria
### Verification Commands
\`\`\`bash
command # Expected: output
\`\`\`
### Final Checklist
- [ ] All "Must Have" present
- [ ] All "Must NOT Have" absent
- [ ] All tests pass
\`\`\`
---
`
+45 -45
View File
@@ -4,68 +4,68 @@ import { createSisyphusJuniorAgentWithOverrides, SISYPHUS_JUNIOR_DEFAULTS } from
describe("createSisyphusJuniorAgentWithOverrides", () => {
describe("honored fields", () => {
test("applies model override", () => {
// #given
// given
const override = { model: "openai/gpt-5.2" }
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
expect(result.model).toBe("openai/gpt-5.2")
})
test("applies temperature override", () => {
// #given
// given
const override = { temperature: 0.5 }
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
expect(result.temperature).toBe(0.5)
})
test("applies top_p override", () => {
// #given
// given
const override = { top_p: 0.9 }
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
expect(result.top_p).toBe(0.9)
})
test("applies description override", () => {
// #given
// given
const override = { description: "Custom description" }
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
expect(result.description).toBe("Custom description")
})
test("applies color override", () => {
// #given
// given
const override = { color: "#FF0000" }
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
expect(result.color).toBe("#FF0000")
})
test("appends prompt_append to base prompt", () => {
// #given
// given
const override = { prompt_append: "Extra instructions here" }
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
expect(result.prompt).toContain("You work ALONE")
expect(result.prompt).toContain("Extra instructions here")
})
@@ -73,41 +73,41 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
describe("defaults", () => {
test("uses default model when no override", () => {
// #given
// given
const override = {}
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
expect(result.model).toBe(SISYPHUS_JUNIOR_DEFAULTS.model)
})
test("uses default temperature when no override", () => {
// #given
// given
const override = {}
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
expect(result.temperature).toBe(SISYPHUS_JUNIOR_DEFAULTS.temperature)
})
})
describe("disable semantics", () => {
test("disable: true causes override block to be ignored", () => {
// #given
// given
const override = {
disable: true,
model: "openai/gpt-5.2",
temperature: 0.9,
}
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then - defaults should be used, not the overrides
// then - defaults should be used, not the overrides
expect(result.model).toBe(SISYPHUS_JUNIOR_DEFAULTS.model)
expect(result.temperature).toBe(SISYPHUS_JUNIOR_DEFAULTS.temperature)
})
@@ -115,24 +115,24 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
describe("constrained fields", () => {
test("mode is forced to subagent", () => {
// #given
// given
const override = { mode: "primary" as const }
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
expect(result.mode).toBe("subagent")
})
test("prompt override is ignored (discipline text preserved)", () => {
// #given
// given
const override = { prompt: "Completely new prompt that replaces everything" }
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
expect(result.prompt).toContain("You work ALONE")
expect(result.prompt).not.toBe("Completely new prompt that replaces everything")
})
@@ -140,7 +140,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
describe("tool safety (task/delegate_task blocked, call_omo_agent allowed)", () => {
test("task and delegate_task remain blocked, call_omo_agent is allowed via tools format", () => {
// #given
// given
const override = {
tools: {
task: true,
@@ -150,10 +150,10 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
},
}
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
const tools = result.tools as Record<string, boolean> | undefined
const permission = result.permission as Record<string, string> | undefined
if (tools) {
@@ -172,7 +172,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
})
test("task and delegate_task remain blocked when using permission format override", () => {
// #given
// given
const override = {
permission: {
task: "allow",
@@ -182,10 +182,10 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
},
} as { permission: Record<string, string> }
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override as Parameters<typeof createSisyphusJuniorAgentWithOverrides>[0])
// #then - task/delegate_task blocked, but call_omo_agent allowed for explore/librarian spawning
// then - task/delegate_task blocked, but call_omo_agent allowed for explore/librarian spawning
const tools = result.tools as Record<string, boolean> | undefined
const permission = result.permission as Record<string, string> | undefined
if (tools) {
@@ -203,26 +203,26 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
describe("prompt composition", () => {
test("base prompt contains discipline constraints", () => {
// #given
// given
const override = {}
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
expect(result.prompt).toContain("Sisyphus-Junior")
expect(result.prompt).toContain("You work ALONE")
expect(result.prompt).toContain("BLOCKED ACTIONS")
})
test("prompt_append is added after base prompt", () => {
// #given
// given
const override = { prompt_append: "CUSTOM_MARKER_FOR_TEST" }
// #when
// when
const result = createSisyphusJuniorAgentWithOverrides(override)
// #then
// then
const baseEndIndex = result.prompt!.indexOf("Dense > verbose.")
const appendIndex = result.prompt!.indexOf("CUSTOM_MARKER_FOR_TEST")
expect(baseEndIndex).not.toBe(-1) // Guard: anchor text must exist in base prompt
+93 -93
View File
@@ -9,53 +9,53 @@ const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-5"
describe("createBuiltinAgents with model overrides", () => {
test("Sisyphus with default model has thinking config", async () => {
// #given - no overrides, using systemDefaultModel
// given - no overrides, using systemDefaultModel
// #when
// when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL)
// #then
// then
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-5")
expect(agents.sisyphus.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
expect(agents.sisyphus.reasoningEffort).toBeUndefined()
})
test("Sisyphus with GPT model override has reasoningEffort, no thinking", async () => {
// #given
// given
const overrides = {
sisyphus: { model: "github-copilot/gpt-5.2" },
}
// #when
// when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// #then
// then
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.2")
expect(agents.sisyphus.reasoningEffort).toBe("medium")
expect(agents.sisyphus.thinking).toBeUndefined()
})
test("Sisyphus uses system default when no availableModels provided", async () => {
// #given
// given
const systemDefaultModel = "anthropic/claude-opus-4-5"
// #when
// when
const agents = await createBuiltinAgents([], {}, undefined, systemDefaultModel)
// #then - falls back to system default when no availability match
// then - falls back to system default when no availability match
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-5")
expect(agents.sisyphus.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
expect(agents.sisyphus.reasoningEffort).toBeUndefined()
})
test("Oracle uses connected provider fallback when availableModels is empty and cache exists", async () => {
// #given - connected providers cache has "openai", which matches oracle's first fallback entry
// given - connected providers cache has "openai", which matches oracle's first fallback entry
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
// #when
// when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL)
// #then - oracle resolves via connected cache fallback to openai/gpt-5.2 (not system default)
// then - oracle resolves via connected cache fallback to openai/gpt-5.2 (not system default)
expect(agents.oracle.model).toBe("openai/gpt-5.2")
expect(agents.oracle.reasoningEffort).toBe("medium")
expect(agents.oracle.thinking).toBeUndefined()
@@ -63,28 +63,28 @@ describe("createBuiltinAgents with model overrides", () => {
})
test("Oracle created without model field when no cache exists (first run scenario)", async () => {
// #given - no cache at all (first run)
// given - no cache at all (first run)
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
// #when
// when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL)
// #then - oracle should be created with system default model (fallback to systemDefaultModel)
// then - oracle should be created with system default model (fallback to systemDefaultModel)
expect(agents.oracle).toBeDefined()
expect(agents.oracle.model).toBe(TEST_DEFAULT_MODEL)
cacheSpy.mockRestore?.()
})
test("Oracle with GPT model override has reasoningEffort, no thinking", async () => {
// #given
// given
const overrides = {
oracle: { model: "openai/gpt-5.2" },
}
// #when
// when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// #then
// then
expect(agents.oracle.model).toBe("openai/gpt-5.2")
expect(agents.oracle.reasoningEffort).toBe("medium")
expect(agents.oracle.textVerbosity).toBe("high")
@@ -92,15 +92,15 @@ describe("createBuiltinAgents with model overrides", () => {
})
test("Oracle with Claude model override has thinking, no reasoningEffort", async () => {
// #given
// given
const overrides = {
oracle: { model: "anthropic/claude-sonnet-4" },
}
// #when
// when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// #then
// then
expect(agents.oracle.model).toBe("anthropic/claude-sonnet-4")
expect(agents.oracle.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
expect(agents.oracle.reasoningEffort).toBeUndefined()
@@ -108,15 +108,15 @@ describe("createBuiltinAgents with model overrides", () => {
})
test("non-model overrides are still applied after factory rebuild", async () => {
// #given
// given
const overrides = {
sisyphus: { model: "github-copilot/gpt-5.2", temperature: 0.5 },
}
// #when
// when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// #then
// then
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.2")
expect(agents.sisyphus.temperature).toBe(0.5)
})
@@ -124,38 +124,38 @@ describe("createBuiltinAgents with model overrides", () => {
describe("createBuiltinAgents without systemDefaultModel", () => {
test("agents created via connected cache fallback even without systemDefaultModel", async () => {
// #given - connected cache has "openai", which matches oracle's fallback chain
// given - connected cache has "openai", which matches oracle's fallback chain
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
// #when
// when
const agents = await createBuiltinAgents([], {}, undefined, undefined)
// #then - connected cache enables model resolution despite no systemDefaultModel
// then - connected cache enables model resolution despite no systemDefaultModel
expect(agents.oracle).toBeDefined()
expect(agents.oracle.model).toBe("openai/gpt-5.2")
cacheSpy.mockRestore?.()
})
test("agents NOT created when no cache and no systemDefaultModel (first run without defaults)", async () => {
// #given
// given
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
// #when
// when
const agents = await createBuiltinAgents([], {}, undefined, undefined)
// #then
// then
expect(agents.oracle).toBeUndefined()
cacheSpy.mockRestore?.()
})
test("sisyphus created via connected cache fallback even without systemDefaultModel", async () => {
// #given - connected cache has "anthropic", which matches sisyphus's first fallback entry
// given - connected cache has "anthropic", which matches sisyphus's first fallback entry
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["anthropic"])
// #when
// when
const agents = await createBuiltinAgents([], {}, undefined, undefined)
// #then - connected cache enables model resolution despite no systemDefaultModel
// then - connected cache enables model resolution despite no systemDefaultModel
expect(agents.sisyphus).toBeDefined()
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-5")
cacheSpy.mockRestore?.()
@@ -171,7 +171,7 @@ describe("buildAgent with category and skills", () => {
})
test("agent with category inherits category settings", () => {
// #given - agent factory that sets category but no model
// given - agent factory that sets category but no model
const source = {
"test-agent": () =>
({
@@ -180,15 +180,15 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// #when
// when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// #then - category's built-in model is applied
// then - category's built-in model is applied
expect(agent.model).toBe("google/gemini-3-pro")
})
test("agent with category and existing model keeps existing model", () => {
// #given
// given
const source = {
"test-agent": () =>
({
@@ -198,15 +198,15 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// #when
// when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// #then - explicit model takes precedence over category
// then - explicit model takes precedence over category
expect(agent.model).toBe("custom/model")
})
test("agent with category inherits variant", () => {
// #given
// given
const source = {
"test-agent": () =>
({
@@ -222,16 +222,16 @@ describe("buildAgent with category and skills", () => {
},
}
// #when
// when
const agent = buildAgent(source["test-agent"], TEST_MODEL, categories)
// #then
// then
expect(agent.model).toBe("openai/gpt-5.2")
expect(agent.variant).toBe("xhigh")
})
test("agent with skills has content prepended to prompt", () => {
// #given
// given
const source = {
"test-agent": () =>
({
@@ -241,17 +241,17 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// #when
// when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// #then
// then
expect(agent.prompt).toContain("Role: Designer-Turned-Developer")
expect(agent.prompt).toContain("Original prompt content")
expect(agent.prompt).toMatch(/Designer-Turned-Developer[\s\S]*Original prompt content/s)
})
test("agent with multiple skills has all content prepended", () => {
// #given
// given
const source = {
"test-agent": () =>
({
@@ -261,16 +261,16 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// #when
// when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// #then
// then
expect(agent.prompt).toContain("Role: Designer-Turned-Developer")
expect(agent.prompt).toContain("Agent prompt")
})
test("agent without category or skills works as before", () => {
// #given
// given
const source = {
"test-agent": () =>
({
@@ -281,17 +281,17 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// #when
// when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// #then
// then
expect(agent.model).toBe("custom/model")
expect(agent.temperature).toBe(0.5)
expect(agent.prompt).toBe("Base prompt")
})
test("agent with category and skills applies both", () => {
// #given
// given
const source = {
"test-agent": () =>
({
@@ -302,10 +302,10 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// #when
// when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// #then - category's built-in model and skills are applied
// then - category's built-in model and skills are applied
expect(agent.model).toBe("openai/gpt-5.2-codex")
expect(agent.variant).toBe("xhigh")
expect(agent.prompt).toContain("Role: Designer-Turned-Developer")
@@ -313,7 +313,7 @@ describe("buildAgent with category and skills", () => {
})
test("agent with non-existent category has no effect", () => {
// #given
// given
const source = {
"test-agent": () =>
({
@@ -323,10 +323,10 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// #when
// when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// #then
// then
// Note: The factory receives model, but if category doesn't exist, it's not applied
// The agent's model comes from the factory output (which doesn't set model)
expect(agent.model).toBeUndefined()
@@ -334,7 +334,7 @@ describe("buildAgent with category and skills", () => {
})
test("agent with non-existent skills only prepends found ones", () => {
// #given
// given
const source = {
"test-agent": () =>
({
@@ -344,16 +344,16 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// #when
// when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// #then
// then
expect(agent.prompt).toContain("Role: Designer-Turned-Developer")
expect(agent.prompt).toContain("Base prompt")
})
test("agent with empty skills array keeps original prompt", () => {
// #given
// given
const source = {
"test-agent": () =>
({
@@ -363,15 +363,15 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// #when
// when
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// #then
// then
expect(agent.prompt).toBe("Base prompt")
})
test("agent with agent-browser skill resolves when browserProvider is set", () => {
// #given
// given
const source = {
"test-agent": () =>
({
@@ -381,16 +381,16 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// #when - browserProvider is "agent-browser"
// when - browserProvider is "agent-browser"
const agent = buildAgent(source["test-agent"], TEST_MODEL, undefined, undefined, "agent-browser")
// #then - agent-browser skill content should be in prompt
// then - agent-browser skill content should be in prompt
expect(agent.prompt).toContain("agent-browser")
expect(agent.prompt).toContain("Base prompt")
})
test("agent with agent-browser skill NOT resolved when browserProvider not set", () => {
// #given
// given
const source = {
"test-agent": () =>
({
@@ -400,10 +400,10 @@ describe("buildAgent with category and skills", () => {
}) as AgentConfig,
}
// #when - no browserProvider (defaults to playwright)
// when - no browserProvider (defaults to playwright)
const agent = buildAgent(source["test-agent"], TEST_MODEL)
// #then - agent-browser skill not found, only base prompt remains
// then - agent-browser skill not found, only base prompt remains
expect(agent.prompt).toBe("Base prompt")
expect(agent.prompt).not.toContain("agent-browser open")
})
@@ -411,36 +411,36 @@ describe("buildAgent with category and skills", () => {
describe("override.category expansion in createBuiltinAgents", () => {
test("standard agent override with category expands category properties", async () => {
// #given
// given
const overrides = {
oracle: { category: "ultrabrain" } as any,
}
// #when
// when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// #then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
// then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
expect(agents.oracle).toBeDefined()
expect(agents.oracle.model).toBe("openai/gpt-5.2-codex")
expect(agents.oracle.variant).toBe("xhigh")
})
test("standard agent override with category AND direct variant - direct wins", async () => {
// #given - ultrabrain has variant=xhigh, but direct override says "max"
// given - ultrabrain has variant=xhigh, but direct override says "max"
const overrides = {
oracle: { category: "ultrabrain", variant: "max" } as any,
}
// #when
// when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// #then - direct variant overrides category variant
// then - direct variant overrides category variant
expect(agents.oracle).toBeDefined()
expect(agents.oracle.variant).toBe("max")
})
test("standard agent override with category AND direct reasoningEffort - direct wins", async () => {
// #given - custom category has reasoningEffort=xhigh, direct override says "low"
// given - custom category has reasoningEffort=xhigh, direct override says "low"
const categories = {
"test-cat": {
model: "openai/gpt-5.2",
@@ -451,16 +451,16 @@ describe("override.category expansion in createBuiltinAgents", () => {
oracle: { category: "test-cat", reasoningEffort: "low" } as any,
}
// #when
// when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, categories)
// #then - direct reasoningEffort wins over category
// then - direct reasoningEffort wins over category
expect(agents.oracle).toBeDefined()
expect(agents.oracle.reasoningEffort).toBe("low")
})
test("standard agent override with category applies reasoningEffort from category when no direct override", async () => {
// #given - custom category has reasoningEffort, no direct reasoningEffort in override
// given - custom category has reasoningEffort, no direct reasoningEffort in override
const categories = {
"reasoning-cat": {
model: "openai/gpt-5.2",
@@ -471,54 +471,54 @@ describe("override.category expansion in createBuiltinAgents", () => {
oracle: { category: "reasoning-cat" } as any,
}
// #when
// when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, categories)
// #then - category reasoningEffort is applied
// then - category reasoningEffort is applied
expect(agents.oracle).toBeDefined()
expect(agents.oracle.reasoningEffort).toBe("high")
})
test("sisyphus override with category expands category properties", async () => {
// #given
// given
const overrides = {
sisyphus: { category: "ultrabrain" } as any,
}
// #when
// when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// #then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
// then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
expect(agents.sisyphus).toBeDefined()
expect(agents.sisyphus.model).toBe("openai/gpt-5.2-codex")
expect(agents.sisyphus.variant).toBe("xhigh")
})
test("atlas override with category expands category properties", async () => {
// #given
// given
const overrides = {
atlas: { category: "ultrabrain" } as any,
}
// #when
// when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// #then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
// then - ultrabrain category: model=openai/gpt-5.2-codex, variant=xhigh
expect(agents.atlas).toBeDefined()
expect(agents.atlas.model).toBe("openai/gpt-5.2-codex")
expect(agents.atlas.variant).toBe("xhigh")
})
test("override with non-existent category has no effect on config", async () => {
// #given
// given
const overrides = {
oracle: { category: "non-existent-category" } as any,
}
// #when
// when
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL)
// #then - no category-specific variant/reasoningEffort applied from non-existent category
// then - no category-specific variant/reasoningEffort applied from non-existent category
expect(agents.oracle).toBeDefined()
const agentsWithoutOverride = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL)
expect(agents.oracle.model).toBe(agentsWithoutOverride.oracle.model)
@@ -527,7 +527,7 @@ describe("override.category expansion in createBuiltinAgents", () => {
describe("Deadlock prevention - fetchAvailableModels must not receive client", () => {
test("createBuiltinAgents should call fetchAvailableModels with undefined client to prevent deadlock", async () => {
// #given - This test ensures we don't regress on issue #1301
// given - This test ensures we don't regress on issue #1301
// Passing client to fetchAvailableModels during createBuiltinAgents (called from config handler)
// causes deadlock:
// - Plugin init waits for server response (client.provider.list())
@@ -540,7 +540,7 @@ describe("Deadlock prevention - fetchAvailableModels must not receive client", (
model: { list: () => Promise.resolve({ data: [] }) },
}
// #when - Even when client is provided, fetchAvailableModels must be called with undefined
// when - Even when client is provided, fetchAvailableModels must be called with undefined
await createBuiltinAgents(
[],
{},
@@ -552,7 +552,7 @@ describe("Deadlock prevention - fetchAvailableModels must not receive client", (
mockClient // client is passed but should NOT be forwarded to fetchAvailableModels
)
// #then - fetchAvailableModels must be called with undefined as first argument (no client)
// then - fetchAvailableModels must be called with undefined as first argument (no client)
// This prevents the deadlock described in issue #1301
expect(fetchSpy).toHaveBeenCalled()
const firstCallArgs = fetchSpy.mock.calls[0]