Skip to content
VAC34 edited this page Jul 27, 2026 · 13 revisions

Phases Reference

SAIPEN runs a structured phase workflow. Each phase has a dedicated doc in saipen/phases/. Every phase transition writes a LOG event, updates BOARD (if a ticket changed status), and rewrites STATE.md with the new phase and next_action.

Core pipeline: PLAN -> SCOUT -> BUILD <-> VERIFY -> REVIEW -> SHIP -> DONE

Auxiliary phases: ADD, CLEAN, HUNT, MARKHUNT, TRANSLATE, BLOCKED, INIT, PREPARE


PLAN

File: phases/plan.md
Input: User intent (bare prompt or specific request)
Output: BOARD.md populated with structured tickets

Amplify user intent into actionable tickets. Agent evaluates codebase, KNOWLEDGE/, and git log to produce a structured ticket board.

Ticket shape (RFC 1.2):

- [ ] T-42 [P2] add export button | needs: T-41 | owner: claude | verify: click export -> CSV downloads
- [ ] T-43 [P1] fix crash on empty state | verify: python -m pytest tests/test_export.py

Every ticket MUST be independently verifiable. The verify: field captures how to check it's done.

Behaviors:

  • Bare saipen plan (no prompt) generates autonomous proposal from codebase analysis
  • Ticket shape: one goal, independently verifiable, needs: for deps
  • Board order = execution order. If >10 tickets, use waves: detail only current wave
  • Size gate: <=2 files + obvious change -> skip PLAN, LOG the decision, go to BUILD
  • Goal mode (RFC 2.4): goal_mode: true -> proceed to SCOUT directly (do not wait), increment goal_waves by 1
  • Caps: 3 goal_waves / 20 goal_tickets maximum per sprint. Hit either cap -> STOP, checkpoint, report, wait for user

State transition:

STATE.phase: PLAN -> SCOUT (goal_mode) or DONE (proposal mode)
STATE.next_action: "SCOUT T-1" or "Wait for user to select a ticket"

Common pitfalls:

  • Proposing tickets that aren't independently verifiable -> each ticket needs | verify:
  • Forgetting the size gate -> if work fits in <=2 files, skip PLAN phase entirely
  • Not checking goal caps -> at 3 waves or 20 tickets, STOP

SCOUT

File: phases/scout.md
Input: A ticket in ## TODO
Output: OWNED ticket in ## DOING, KNOWLEDGE/ populated, BUILD plan clear

Investigate a ticket before building. Mandatory before BUILD — you cannot build what you haven't scouted.

Behaviors:

  1. Claim the ticket: Move from ## TODO to ## DOING, change checkbox to [/], set owner:, claim_time: per RFC 1.4
  2. KNOWLEDGE/ first: Already know this architecture? Skip re-reading
  3. Read ticket's files + ONE similar neighbor: Understand patterns, not just the isolated change point
  4. Note: naming conventions, error style, imports, test utilities, build commands
  5. Find the repo's architecture: Never invent a parallel one. If the repo uses MVC, don't add Flux
  6. Durable findings -> KNOWLEDGE/: If this insight will matter again, write it to KNOWLEDGE/ADR-xxx.md or KNOWLEDGE/decisions.md
  7. Grep before read: Don't read entire files. Search for what you need.

State transition:

STATE.phase: SCOUT -> BUILD
STATE.next_action: "BUILD T-42: implement export button"

Common pitfalls:

  • Building without scouting -> violates protocol, leads to wrong architecture
  • Reading too much -> grep first, read only what's needed
  • Not claiming the ticket -> another agent might claim it simultaneously
  • Forgetting to set claim_time: -> staleness detection won't work
  • Inventing parallel architecture -> the repo has ONE architecture, find it

BUILD

File: phases/build.md
Input: A scouted, claimed ticket
Output: Working implementation

Implement a scouted ticket following existing conventions exactly.

Behaviors:

  • Implement exactly what the ticket specifies — no scope creep
  • Follow existing code conventions: naming, error handling, imports, test style
  • Use existing libraries and utilities — NEVER assume a library is available without checking package.json, requirements.txt, etc.
  • Don't add comments unless the code genuinely needs explanation
  • Minimal, working implementation: solve the ticket, nothing more

Iteration: BUILD and VERIFY form a loop. If VERIFY fails, return to BUILD.

State transition:

STATE.phase: BUILD -> VERIFY
STATE.next_action: "VERIFY T-42: run pytest, check export CSV downloads"

Common pitfalls:

  • Scope creep -> the ticket says "add export button", not "redesign the entire export system"
  • Adding comments -> code should be self-documenting. Comments lie; code doesn't
  • Library assumption -> always check package.json/requirements.txt/Cargo.toml before importing
  • Wrong file -> scout told you which file to edit. Don't guess.

VERIFY

File: phases/verify.md
Input: Built implementation
Output: Verified (pass or documented fail)

Verify a built ticket passes its acceptance criteria. BUILD-VERIFY loop until passing.

Behaviors:

  • Check the ticket's verify: criterion FIRST — that's the acceptance bar
  • Run relevant tests: pytest tests/, npm test, cargo test
  • Verify against the actual requirement, not against "it runs without crashing"
  • Safety net: if VERIFY reveals a design flaw -> RETURN to SCOUT or PLAN (LOG the reason)
  • Debug cap: multiple failed attempts -> ## BLOCKED with concrete facts + dead ends listed. NOT "it doesn't work" — list what you tried, what happened, what you ruled out
  • tools/validate.py runs structural checks. If that fails, fix the structural issue before shipping

After VERIFY:

STATE.phase: VERIFY -> REVIEW (if pass) or VERIFY -> BUILD (if fail)
STATE.next_action: "REVIEW T-42" or "BUILD T-42: fix edge case on null input"

Common pitfalls:

  • Verifying against "it runs" instead of the ticket's acceptance criteria
  • Not running the actual test suite -> a passing manual test doesn't mean tests exist
  • Infinite BUILD-VERIFY loop -> use the debug cap: after N failures, BLOCKED with facts
  • Forgetting validate.py -> a structural issue will block SHIP

REVIEW

File: phases/review.md
Input: Verified implementation
Output: Reviewed (approved or returned)

Review the implementation before shipping.

Behaviors:

  • Diff review: check for scope creep, dead code, incomplete error handling, debug logging left in
  • Backwards compatibility: existing behavior must not break. If the API changed, was it intentional?
  • Tests exist and pass: not just "the code works" — the TESTS work
  • Documentation updated: if behavior changed, docs changed too
  • One-pass review: if too many issues, return to SCOUT or BUILD (don't cherry-pick)

After REVIEW:

STATE.phase: REVIEW -> SHIP (if approved) or REVIEW -> BUILD/SCOUT (if issues)
STATE.next_action: "SHIP T-42" or "BUILD T-42: fix review comments"

Common pitfalls:

  • Approving scope creep -> the ticket said X but the build changed Y too. Reject it.
  • Missing dead code -> leftover print statements, commented-out blocks, unused imports
  • Skipping docs update -> a feature that isn't documented doesn't exist for users
  • Multi-pass review -> one pass. If too many issues, return to BUILD for a clean re-do.

SHIP

File: phases/ship.md
Input: Reviewed, approved implementation
Output: Committed, pushed, tagged

Ship the completed work. This is the release gate.

Exact steps:

  1. VERSION bump (semver patch/minor/major per impact)
  2. CHANGELOG.md update with release notes
  3. Stage all changed files
  4. git commit with conventional commit message:
    vX.Y.Z: short description
    
    - bullet points of what changed
    
  5. git tag vX.Y.Z
  6. README.md badge version update
  7. git push && git push --tags

After SHIP:

STATE.phase: SHIP -> DONE
STATE.next_action: "Wait for user command"

Common pitfalls:

  • Forgetting to tag -> releases are not findable
  • Forgetting the changelog -> users don't know what changed
  • Pushing without pulling first -> merge conflicts
  • Not running validate.py before commit -> pre-commit hook blocks, wastes time

DONE

File: phases/done.md
Input: Shipped ticket
Output: Clean checkpoint, ready for next command

Final completion state. Everything is committed, pushed, tagged, and validated.

Behaviors:

  • Verify nothing was left dangling: uncommitted work, unpushed branches, unstaged changes
  • Set next_action to a neutral "Wait for user command" — never "what should I do?"
  • Complete checkpoint: LOG event -> update BOARD (move to DONE) -> update STATE
  • If goal_mode: true, check if another TODO ticket exists. If yes -> claim and SCOUT

After DONE:

STATE.phase: DONE -> (waiting) or DONE -> SCOUT (goal_mode with more tickets)
STATE.next_action: "Wait for user command" or "SCOUT T-43"

ADD (Evolutionary Completer)

File: phases/add.md
Trigger: saipen add or detected gap during HUNT

Systematically expand capabilities. SAIPEN is evolutionary, not creative — it completes software, never reinvents it.

Priority ladder (evaluated in order):

  1. Bugfix — things that are broken. Always ticket + SCOUT, never inline fix
  2. Complementary feature — Bold implies Italic. Open implies Save
  3. Workflow step — Save implies Save_As. Login implies Logout
  4. UX consistency — if 3 buttons use icon+label and the 4th uses only icon, fix it
  5. Platform convention — Ctrl+S saves on every platform. If your app doesn't, add it

Implementation paths (RFC pseudocode):

FOR priority IN [bugfix, complementary, workflow_step, ux, platform]:
  IF exists(priority):
    IF priority == bugfix:
      TICKET(priority); RETURN SCOUT
    IF minimal_delta AND existing_design_language:
      TICKET(priority); CLAIM(ticket); RETURN BUILD
    ELSE:
      TICKET(priority); RETURN PLAN or SCOUT
RETURN DONE  // product is mature, stop

Industrial Completion Rule (RFC 2.3): When user requests one step of a well-known workflow, evaluate what else is needed for a minimal coherent set. "Apply" implies "Cancel" and "Save" — but NOT "Cloud Sync".

Complete before extending: Finish the requested workflow before proposing different ones. "Login" implies "Logout" — not OAuth or SSO.

Goal mode: HUNT->ADD cycle increments goal_waves by 1. If product is mature, set goal_mode: false, write final report, STATE -> DONE.


CLEAN

File: phases/clean.md
Trigger: saipen clean

Deep repository scrub. Executed in strict order. Each step depends on the previous.

Safety floor: CLEAN MUST NOT delete user data without explicit confirmation. "Obviously safe" means scaffolding, cache, build artifacts — never something the user might have meant to keep.

Step 1: Board Scrub

  • Remove [x] DONE tickets older than current active work. Every ticket's real events are preserved in LOG.md's append-only graph — nothing is lost, just decluttered
  • Prune stale ## TODO tickets: superseded by later tickets, or underlying issue already resolved by unrelated work
  • Re-check ## BLOCKED tickets: blocker resolved elsewhere? Move back to ## TODO. Still stuck but abandoned? Prune it. Not resolvable but not abandonable? Surface as WAIT: with the concrete question
  • Structural repair: deduplicate tickets found under two headings, merge duplicate ## DONE blocks, fix malformed lines

Step 2: Orphan Hunt

  • Find and delete unconnected files (orphaned assets, unused scripts)
  • Ambiguous items MUST be ticketed for human review, never silently deleted

Step 3: Link & Path Audit

  • Fix broken internal paths or dead links in markdown
  • Fix incorrect imports or code references

Step 4: Trash Removal

  • Delete temp files, caches, scaffold leftovers (__pycache__, .tmp, .bak)
  • Clear empty directories
  • Delete stale kitchen/ files where owner ticket is DONE and content is superseded
  • Seal LOG.md if past ~300 lines / ~64 KB: move to .saipen/logs/LOG-NNN.md via crash-safe temp+rename, start fresh active LOG.md

Step 5: Freshness Check

  • Ensure repo paths and dependencies are current
  • Confirm project structure matches expectations

HUNT

File: phases/hunt.md
Trigger: saipen hunt or autonomous schedule

Autonomous bug/code-quality sweep. Runs without disrupting main workflow.

Hash-match optimization: If LOG.md tail contains hunt -> clean @<CURRENT_HASH>, SKIP entirely — no re-scan for unchanged code. This makes repeated hunts nearly free.

6 categories scanned:

# Category What to check Tools
1 Failing tests Run test suite, report regressions pytest, npm test, cargo test
2 Commits unverified Cross-reference LOG events vs git log entries git log --oneline
3 Stale TODOs `rg "TODO FIXME
4 Silent failures except: pass, ignored return values, missing IO error paths code review
5 Symmetry gaps Feature with no counterpart (import/export, save/load, on/off) code review
6 Dead code Orphan files, unused functions, unreachable branches rg for function refs

Outcome:

  • Up to 5 "obvious junk" files per sweep auto-deleted (no ticket needed)
  • Real findings -> ticketed with severity
  • LOG: hunt -> clean @ABc1234 (hash-match optimization for next time)

MARKHUNT

File: phases/markhunt.md
Trigger: Every 6 releases or significant architectural drift

Full manual-hunt process with triage. Deeper than HUNT — covers spec, architecture, and business concerns.

5 vectors:

Vector What it catches Example finding
Logical Spec contradictions, edge cases, missing failure modes "Delete button has no confirmation dialog"
Manual Concurrency bugs, error handling gaps, silent data loss "Race condition on concurrent save"
Structural Dependency graph issues, state machine gaps, resource leaks "No circuit breaker on API call"
Human Setup friction, poor error messages, footguns "Error says 'something went wrong' with no details"
Business License violations, stale deps, security practices "Dependency 3 major versions behind, has CVE"

Outcome: Each finding ticketed with P0-P3 severity. P0/P1 fixed same session. P2/P3 triaged to backlog. LOG tracks each finding.


TRANSLATE

File: phases/translate.md
Trigger: saipen translate

Deep, isolated translation preparation. Runs in a quarantined environment — never touches main project files.

Isolation rules:

  • Work happens exclusively inside .saipen/saitranslate/kitchen/
  • If running as a parallel dedicated agent: own STATE.md at .saipen/saitranslate/STATE.md, never writes main .saipen/STATE.md
  • MUST NOT modify main project files during translation
  • Completion LOG goes to main LOG.md: translate -> done @SHORT-HASH

Translation surface:

  • Docs: README.md, SECURITY.md, CONTRIBUTING.md, SPEC.md — top-level user-facing docs. All 4 are always translated
  • UI strings: ONLY if the software has real in-app UI strings (grep for existing i18n files BEFORE inventing them). SAIPEN itself has no UI strings — don't fabricate them
  • Hand-maintained siblings: If a locale already has a hand-maintained README_XX.md, never overwrite it. Note it as covered and skip

Core vs SubSaipen split (v7.73.0 rule):

Handled by Languages
Core agent English, Russian, Estonian, Дед (angry-grandpa voice)
SubSaipen instances 29 languages: JA, UK, DE, FR, ES, IT, PT, NL, PL, SV, DA, FI, NO, ZH, KO, TH, VI, AR, HE, TR, HI, ID, EL, CS, RO, HU, BG, SK, HR

Core MUST NOT grind through 29 languages "while it's here" — tickets them for subSaipen instances and moves on. Core MAY still verify any language (UTF-8, structure, spot-checks) and MUST repair corruption it finds.

Maintenance:

  • Every saipen translate run re-scans drift since last run
  • Version badge drift is machine-detectable via tools/validate.py
  • githooks/pre-commit catches badge drift before commit

BLOCKED

File: phases/blocked.md
Trigger: Unblockable condition detected

Handles session-level blocks that prevent any work from continuing.

Behaviors:

  • Set STATE.blocker to the blocking condition (factual, not vague)
  • Set STATE.next_action to WAIT: <the exact question or decision needed>
    • GOOD: WAIT: T-42 depends on PR #17 being merged — is it ready?
    • BAD: WAIT: what should I do? (not a concrete decision)
  • Re-check periodically: is block still active? Yes -> stay blocked. No -> resume
  • If possible, switch to independent work: kitchen tasks, doc cleanup, README fixes

BLOCKED vs HALTED:

  • BLOCKED: external blocker (waiting on human, service down, dependency not merged)
  • HALTED: internal blocker within the task (design ambiguity, too many unknowns -> RETURN to PLAN)

INIT

File: phases/init.md
Trigger: saipen set

Initialize a new SAIPEN project.

Behaviors:

  • Verify current directory is a git repo (or offer to git init)
  • Create .saipen/ directory with:
    • STATE.md — default frontmatter, phase: DONE
    • BOARD.md — empty sections (DOING/TODO/DONE/BLOCKED)
    • LOG.md — empty event graph
    • kitchen/ — scratch directory
    • recovery/ — for crash snapshots
  • Set initial phase to DONE (ready for first PLAN)
  • Register project in SAIPEN home if saipen_home is configured
  • If .saipen/ already exists: report and exit (this is a continuation, not a re-init)

After INIT: STATE -> DONE. Ready for first saipen plan.


PREPARE

File: phases/prepare.md
Trigger: Before any phase transition

Pre-flight checks. Ensures the project is in a valid state before work begins.

Behaviors:

  • Verify .saipen/ exists and is readable
  • Check STATE.md frontmatter is valid YAML with all required fields
  • Verify BOARD.md has all 4 required sections
  • Check git status: any conflicts? Dirty state? Unpushed commits?
  • Run tools/validate.py for structural integrity
  • If linked worktree: check --git-common-dir for the real .saipen/ location
  • Report any blockers before starting work

Pass: PREPARE -> (actual phase from STATE.md)
Fail: PREPARE -> BLOCKED or RECOVERY

Clone this wiki locally