nixcfg/config/opencode/commands/workflow.md
Harald Hoyer e2e35acdae refactor(opencode): assume opencode runs in the worktree, drop bare-repo plumbing
The workflow previously created a worktree itself (Phase 3) and worked
around opencode's lack of per-subagent CWD by capturing absolute paths
and threading them through every dispatch (the "Subagent Dispatch
Convention"). That ceremony exists only because the orchestrator's CWD
differed from where subagents were rooted.

Now the workflow assumes the user has already created the worktree and
launched opencode inside it. Subagents inherit that as their project
root, so all the absolute-path plumbing goes away. Phase 3 is removed,
phases renumber to 1-9, and the Subagent Dispatch Convention section
is dropped.

Phase 1 is a sanity check (non-bare worktree, TODO.md present, HEAD
not detached, current branch != base branch) that resolves the base
branch from an optional second argument or by trying main then master.
@pm now uses live filesystem mode against ./TODO.md throughout (the
git-ref read mode stays available for ad-hoc use). Phase 8's diff
uses git diff "$BASE_BRANCH"...HEAD without git -C wrapping.
2026-05-06 17:31:56 +02:00

242 lines
11 KiB
Markdown

---
description: "Multi-agent workflow for the current worktree: plan, test, implement, commit"
agent: build
---
You are executing the multi-agent workflow inside the worktree this opencode session was started from. Run all phases without waiting for user input. The user has walked away.
**Prerequisites (the user handles before launching opencode):**
- A git worktree is checked out for the issue's feature branch
- `opencode` was launched from the root of that worktree
- `TODO.md` is committed to the repo and present at `./TODO.md`
**Task reference:** $ARGUMENTS
If `$ARGUMENTS` is empty, stop immediately: "Usage: `/workflow <ISSUE-ID> [base-branch]` (e.g. `/workflow ABC-1`). The ID must exist in `./TODO.md`. Base branch defaults to `main` (then `master`)."
Parse `$ARGUMENTS`: the first whitespace-separated token is the issue ID, an optional second token overrides the base branch.
---
## Phase 1: Sanity Check
1. Verify CWD is a non-bare git worktree: `git rev-parse --is-bare-repository 2>/dev/null` must output `false`. If not, stop: "Workflow must be run from a non-bare worktree (the directory opencode was launched in)."
2. Verify `./TODO.md` exists. If not, stop: "TODO.md not found in the current worktree. Commit a TODO.md to the repo first."
3. Verify HEAD is not detached: `git symbolic-ref --short HEAD` must succeed. If it fails, stop: "Cannot run on a detached HEAD. Check out a feature branch first."
4. Capture the current branch: `BRANCH_NAME="$(git symbolic-ref --short HEAD)"`.
5. Resolve the base branch (`BASE_BRANCH`):
- If `$ARGUMENTS` provided a second token, use it.
- Else if `git rev-parse --verify --quiet main` succeeds, use `main`.
- Else if `git rev-parse --verify --quiet master` succeeds, use `master`.
- Else stop: "Could not determine base branch (no `main` or `master`). Pass it as the second argument: `/workflow <ISSUE-ID> <base-branch>`."
6. Verify the current branch is not the base branch: if `BRANCH_NAME == BASE_BRANCH`, stop: "Cannot run workflow on the base branch (`$BASE_BRANCH`). Switch to a feature branch first."
---
## Phase 2: Issue Context
Dispatch `@pm` to read `./TODO.md` (live filesystem mode) and fetch the issue matching the parsed ID:
- Issue title, description, acceptance criteria
- Labels and priority
- Existing status
If the issue does not exist or `@pm` fails, stop with error.
If the issue's status is `Backlog` or `Todo`, ask `@pm` to set it to `In Progress` (this edit will be staged in Phase 9 alongside other TODO.md updates).
---
## Phase 3: Plan
Analyze the codebase. Create a detailed implementation plan addressing the issue's requirements and acceptance criteria.
The plan should include:
- Problem summary (from issue context)
- Proposed approach with rationale
- Files to modify (with brief description of changes)
- New files to create
- Risks and open questions
- **Test Design (conditional — include for non-trivial tasks):**
- Key behaviors to verify (what tests should assert)
- Edge cases and error conditions worth testing
- What explicitly should NOT be tested (prevents bloat)
- Testability concerns (heavy external deps, GPU-only paths, etc.)
**Include Test Design for:** Public API changes, bug fixes with behavioral impact, new features with business logic, multi-module changes.
**Skip Test Design for:** Config-only changes, decorator swaps, import reorganization, documentation.
When skipped, `@test` derives test cases directly from acceptance criteria.
---
## Phase 4: Review Plan
Dispatch `@check` and `@simplify` in parallel to review the plan.
Reviewers should evaluate testability:
- `@check`: Is the design testable? Are the right behaviors identified? (Review Framework §8)
- `@simplify`: Is the test scope appropriate? Over-testing proposed?
**Merge rules:**
- `@check` safety/correctness findings are hard constraints
- If `@simplify` recommends removing something `@check` flags as needed, `@check` wins
- Note conflicts explicitly
**Review loop (max 3 cycles):**
1. Send plan to both reviewers
2. Merge findings
3. If verdict is ACCEPTABLE from both (or JUSTIFIED COMPLEXITY from `@simplify`): proceed to Phase 5
4. If BLOCK or NEEDS WORK: revise the plan addressing findings, then re-review
5. **Convergence detection:** if reviewers return the same findings as the previous cycle, stop the loop early
6. If still unresolved after 3 cycles: note unresolved blockers and proceed anyway (they will be documented in the workflow summary and commit message)
---
## Phase 5: Split into Tasks
Break the approved plan into discrete tasks for `@make`. Each task needs:
| Required | Description |
|----------|-------------|
| **Task** | Clear description of what to implement |
| **Acceptance Criteria** | Specific, testable criteria (checkbox format) |
| **Code Context** | Actual code snippets from the codebase, not just file paths |
| **Files to Modify** | Explicit list, mark new files with "(create)" |
| **Test File** | Path for test file (colocated pattern), e.g., `<module>/tests/test_<feature>.py (create)` |
Include **Integration Contracts** when a task adds/changes function signatures, APIs, config keys, or has dependencies on other tasks.
Include **Test Design** from Phase 3 when available, attached to the relevant task(s).
**Task size:** ~10-30 minutes each, single coherent change, clear boundaries.
---
## Phase 6: Write Tests
For each task from Phase 5, dispatch `@test` with:
- The task spec (acceptance criteria, code context, files to modify)
- The Test Design section from the plan (if provided)
- The test file path to create (following colocated pattern)
`@test` writes failing tests and verifies RED with structured failure codes.
**Post-step file gate (MANDATORY):**
Before dispatching `@test`, snapshot the current changed files:
```bash
git diff --name-only > /tmp/pre_test_baseline.txt
```
After `@test` completes, validate only NEW changes:
```bash
git diff --name-only | comm -23 - /tmp/pre_test_baseline.txt > /tmp/test_new_files.txt
```
All new files must match the project's test patterns:
- Python: `**/test_*.py`, `**/*_test.py`, `**/conftest.py` (new only), `**/test_data/**`, `**/test_fixtures/**`
- Rust: `tests/**/*.rs`, `**/tests/**/*.rs`, `**/test_data/**`, `**/test_fixtures/**`
If any non-matching file appears: discard `@test` output, report violation.
**Decision table — handling `@test` results:**
| Condition | Action |
|-----------|--------|
| `TESTS_READY` + `escalate_to_check: false` | Proceed to Phase 7 |
| `TESTS_READY` + `escalate_to_check: true` | Route tests to `@check` for light review. `@check` diagnoses, caller routes fixes to `@test`. Then proceed. |
| `NOT_TESTABLE` | Route to `@check` for sign-off on justification. If approved, task goes to `@make` without tests. |
| `BLOCKED` | Investigate. May need to revise task spec or plan. |
| Test passes immediately | Investigate — behavior may already exist. Task spec may be wrong. |
**Parallelism:** Independent tasks can have tests written in parallel.
**Constraint:** `@test` must not modify existing conftest.py files (prevents collision during parallel execution).
---
## Phase 7: Implement
Execute each task by dispatching `@make` with:
- The task spec (from Phase 5)
- Relevant code context (actual snippets)
- **Pre-written failing tests and handoff from `@test` (if TESTS_READY)**
`@make` runs in TDD mode when tests are provided:
1. Entry validation: run tests, verify RED, check failure codes match handoff
2. Implement minimal code to make tests pass (GREEN)
3. Regression check on broader area
4. Refactor while keeping green
5. Report RED→GREEN evidence
**Escalation:** If `@make` flags test quality concerns during entry validation:
1. `@make` reports the issue to caller
2. Caller routes to `@check` for diagnosis
3. `@check` reports findings
4. Caller routes to `@test` for fixes
5. Fixed tests return to `@make`
For NOT_TESTABLE tasks, `@make` runs in standard mode.
After all tasks complete, verify overall integration:
- Run the project's test suite if available
- Run linting/type checking if configured
- Fix any integration issues between tasks
---
## Phase 8: Final Review
Dispatch `@check` and `@simplify` in parallel to review the full implementation (all changes across all files).
Provide reviewers with:
- The original plan
- The full diff (`git diff "$BASE_BRANCH"...HEAD`)
- Any decisions or deviations from the plan
**Review loop (max 3 cycles):**
1. Send implementation to both reviewers
2. Merge findings (same precedence rules as Phase 4)
3. If ACCEPTABLE: proceed to Phase 9
4. If issues found: fix them directly (no need to re-dispatch `@make` for small fixes), then re-review
5. **Convergence detection:** same findings twice = stop loop early
6. If unresolved after 3 cycles: document blockers, proceed to commit anyway
---
## Phase 9: Commit and Wrap Up
The workflow is forge-agnostic. It commits locally and stops. **Do not push, and do not open a pull/merge request** — the user chooses their forge and review workflow manually.
### Commit Code Changes
- Stage code changes only. **Do not stage `TODO.md`** (committed separately below) and **do not stage `.opencode/workflow-summary.md`** (intentionally never committed — see Local Summary).
- Write a conventional commit message summarizing the implementation. Reference the TODO.md issue ID in the body (e.g. `Refs: ABC-1`).
- If changes are large/varied, use multiple atomic commits (one per logical unit)
### TODO Update
- Dispatch `@pm` against `./TODO.md` (live filesystem mode). Ask it to:
- Set **Branch** to `$BRANCH_NAME`
- Set **Status** to `In Review`
- Add a comment with the branch name, latest commit SHA, and a one-line summary
- If acceptance-criteria checkboxes were addressed by the implementation, ask `@pm` to check them off
- Commit the TODO.md change as a separate atomic commit: `chore(todo): update <issue-id> status and progress`
### Local Summary
- Write `.opencode/workflow-summary.md` in the worktree with:
- Run timestamp
- Issue reference and title
- Branch name and final commit SHA(s)
- Summary of implementation
- TDD evidence (RED→GREEN per task, NOT_TESTABLE justifications)
- Review outcomes (plan review + final review verdicts)
- Unresolved items (if any)
- Files changed
- **Do not commit this file.** It is a per-run, per-branch artifact; committing it would create merge conflicts whenever multiple workflow branches are merged. Leave it untracked. Recommend the user add `.opencode/` to `.gitignore` if not already.
---
## Failure Handling
At any phase, if an unrecoverable error occurs:
1. Write `.opencode/workflow-summary.md` with what was completed and what failed. Do **not** stage or commit this file.
2. If any code was written, commit it with message `wip: incomplete workflow run for <issue-id>`. Stage code only — exclude `.opencode/workflow-summary.md`.
3. Leave the branch and worktree intact for the user to inspect — do not push, do not delete.
4. Dispatch `@pm` against `./TODO.md` to add a comment on the issue summarising what failed.
5. Stop execution.
**Never hang on interactive prompts.** If any command appears to require input, treat it as a failure and follow the above procedure.