-
Notifications
You must be signed in to change notification settings - Fork 0
Phases
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
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), incrementgoal_wavesby 1 -
Caps: 3
goal_waves/ 20goal_ticketsmaximum 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
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:
-
Claim the ticket: Move from
## TODOto## DOING, change checkbox to[/], setowner:,claim_time:per RFC 1.4 - KNOWLEDGE/ first: Already know this architecture? Skip re-reading
- Read ticket's files + ONE similar neighbor: Understand patterns, not just the isolated change point
- Note: naming conventions, error style, imports, test utilities, build commands
- Find the repo's architecture: Never invent a parallel one. If the repo uses MVC, don't add Flux
-
Durable findings -> KNOWLEDGE/: If this insight will matter again, write it to
KNOWLEDGE/ADR-xxx.mdorKNOWLEDGE/decisions.md - 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
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.tomlbefore importing - Wrong file -> scout told you which file to edit. Don't guess.
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 ->
## BLOCKEDwith concrete facts + dead ends listed. NOT "it doesn't work" — list what you tried, what happened, what you ruled out -
tools/validate.pyruns 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
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.
File: phases/ship.md
Input: Reviewed, approved implementation
Output: Committed, pushed, tagged
Ship the completed work. This is the release gate.
Exact steps:
-
VERSIONbump (semver patch/minor/major per impact) -
CHANGELOG.mdupdate with release notes - Stage all changed files
-
git commitwith conventional commit message:vX.Y.Z: short description - bullet points of what changed git tag vX.Y.Z-
README.mdbadge version update 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.pybefore commit -> pre-commit hook blocks, wastes time
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_actionto 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"
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):
- Bugfix — things that are broken. Always ticket + SCOUT, never inline fix
- Complementary feature — Bold implies Italic. Open implies Save
- Workflow step — Save implies Save_As. Login implies Logout
- UX consistency — if 3 buttons use icon+label and the 4th uses only icon, fix it
- 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.
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
## TODOtickets: superseded by later tickets, or underlying issue already resolved by unrelated work - Re-check
## BLOCKEDtickets: blocker resolved elsewhere? Move back to## TODO. Still stuck but abandoned? Prune it. Not resolvable but not abandonable? Surface asWAIT:with the concrete question -
Structural repair: deduplicate tickets found under two headings, merge duplicate
## DONEblocks, 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.mdvia 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
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)
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.
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 translaterun re-scans drift since last run - Version badge drift is machine-detectable via
tools/validate.py -
githooks/pre-commitcatches badge drift before commit
File: phases/blocked.md
Trigger: Unblockable condition detected
Handles session-level blocks that prevent any work from continuing.
Behaviors:
- Set
STATE.blockerto the blocking condition (factual, not vague) - Set
STATE.next_actiontoWAIT: <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)
- GOOD:
- 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)
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_homeis 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.
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.pyfor structural integrity - If linked worktree: check
--git-common-dirfor the real.saipen/location - Report any blockers before starting work
Pass: PREPARE -> (actual phase from STATE.md)
Fail: PREPARE -> BLOCKED or RECOVERY
SAIPEN v7.158.0 — One command. Zero dependencies. Zero amnesia. — MIT