10 KiB
The Story-Driven Spec Workflow (SDSW)
Target Audience: Large Language Models (LLMs) acting as Senior Engineers. Goal: To maintain long-term project coherence, prevent context window exhaustion, and ensure high-quality, testable code generation in large software projects.
1. The Philosophy
We treat the codebase as the implementation of a "Living Specification." Instead of ephemeral chat prompts ("Fix this", "Add that"), we work through persistent artifacts.
- Stories define the Change.
- Specs define the Truth.
- Code defines the Reality.
The Golden Rule: You are not allowed to write code until the Spec reflects the new reality requested by the Story.
2. Directory Structure
When initializing a new project under this workflow, create the following structure immediately:
project_root/
.living_spec
|-- README.md # This document
├── stories/ # The "Inbox" of feature requests.
├── specs/ # The "Brain" of the project.
│ ├── README.md # Explains this workflow to future sessions.
│ ├── 00_CONTEXT.md # High-level goals, domain definition, and glossary.
│ ├── tech/ # Implementation details (Stack, Architecture, Constraints).
│ │ └── STACK.md # The "Constitution" (Languages, Libs, Patterns).
│ └── functional/ # Domain logic (Platform-agnostic behavior).
│ ├── 01_CORE.md
│ └── ...
└── src/ # The Code.
3. The Cycle (The "Loop")
When the user asks for a feature, follow this 4-step loop strictly:
Step 1: The Story (Ingest)
- User Input: "I want the robot to dance."
- Action: Create a file
stories/XX_robot_dance.md. - Content:
- User Story: "As a user, I want..."
- Acceptance Criteria: Bullet points of observable success.
- Out of scope: Things that are out of scope so that the LLM doesn't go crazy
- Git: Make a local feature branch for the story, named from the story (e.g.,
feature/story-33-camera-format-auto-selection). You must create and switch to the feature branch before making any edits.
Step 2: The Spec (Digest)
- Action: Update the files in
specs/. - Logic:
- Does
specs/functional/LOCOMOTION.mdexist? If no, create it. - Add the "Dance" state to the state machine definition in the spec.
- Check
specs/tech/STACK.md: Do we have an approved animation library? If no, propose adding one to the Stack or reject the feature.
- Does
- Output: Show the user the diff of the Spec. Wait for approval.
Step 3: The Implementation (Code)
- Action: Write the code to match the Spec (not just the Story).
- Constraint: adhere strictly to
specs/tech/STACK.md(e.g., if it says "Nounwrap()", you must not useunwrap()).
Step 4: Verification (Close)
- Action: Write a test case that maps directly to the Acceptance Criteria in the Story.
- Action: Run compilation and make sure it succeeds without errors. Consult
specs/tech/STACK.mdand run all required linters listed there (treat warnings as errors). Run tests and make sure they all pass before proceeding. Ask questions here if needed. - Action: Do not accept stories yourself. Ask the user if they accept the story. If they agree, move the story file to
stories/archive/. Tell the user they should commit (this gives them the chance to exclude files via .gitignore if necessary). - Action: When the user accepts:
- Move the story file to
stories/archive/(e.g.,mv stories/XX_story_name.md stories/archive/) - Commit both changes to the feature branch
- Perform the squash merge:
git merge --squash feature/story-name - Commit to master with a comprehensive commit message
- Delete the feature branch:
git branch -D feature/story-name
- Move the story file to
- Important: Do NOT mark acceptance criteria as complete before user acceptance. Only mark them complete when the user explicitly accepts the story.
CRITICAL - NO SUMMARY DOCUMENTS:
- NEVER create a separate summary document (e.g.,
STORY_XX_SUMMARY.md,IMPLEMENTATION_NOTES.md, etc.) - NEVER write terminal output to a markdown file for "documentation purposes"
- The
specs/folder IS the documentation. Keep it updated after each story. - If you find yourself typing
cat << 'EOF' > SUMMARY.mdor similar, STOP IMMEDIATELY. - The only files that should exist after story completion:
- Updated code in
src/ - Updated specs in
specs/ - Archived story in
stories/archive/
- Updated code in
3.5. Bug Workflow (Simplified Path)
Not everything needs to be a full story. Simple bugs can skip the story process:
When to Use Bug Workflow
- Defects in existing functionality (not new features)
- State inconsistencies or data corruption
- UI glitches that don't require spec changes
- Performance issues with known fixes
Bug Process
- Document Bug: Create
bugs/bug-N-short-description.mdwith:- Symptom: What the user observes
- Root Cause: Technical explanation (if known)
- Reproduction Steps: How to trigger the bug
- Proposed Fix: Brief technical approach
- Workaround: Temporary solution if available
- Fix Immediately: Make minimal code changes to fix the bug
- Archive: Move fixed bugs to
bugs/archive/when complete - No Spec Update Needed: Unless the bug reveals a spec deficiency
Bug vs Story
- Bug: Existing functionality is broken → Fix it
- Story: New functionality is needed → Spec it, then build it
- Spike: Uncertainty/feasibility discovery → Run spike workflow
3.6. Spike Workflow (Research Path)
Not everything needs a story or bug fix. Spikes are time-boxed investigations to reduce uncertainty.
When to Use a Spike
- Unclear root cause or feasibility
- Need to compare libraries/encoders/formats
- Need to validate performance constraints
Spike Process
- Document Spike: Create
spikes/spike-N-short-description.mdwith:- Question: What you need to answer
- Hypothesis: What you expect to be true
- Timebox: Strict limit for the research
- Investigation Plan: Steps/tools to use
- Findings: Evidence and observations
- Recommendation: Next step (Story, Bug, or No Action)
- Execute Research: Stay within the timebox. No production code changes.
- Escalate if Needed: If implementation is required, open a Story or Bug and follow that workflow.
- Archive: Move completed spikes to
spikes/archive/.
Spike Output
- Decision and evidence, not production code
- Specs updated only if the spike changes system truth
4. Context Reset Protocol
When the LLM context window fills up (or the chat gets slow/confused):
- Stop Coding.
- Instruction: Tell the user to open a new chat.
- Handoff: The only context the new LLM needs is in the
specs/folder.- Prompt for New Session: "I am working on Project X. Read
specs/00_CONTEXT.mdandspecs/tech/STACK.md. Then look atstories/to see what is pending."
- Prompt for New Session: "I am working on Project X. Read
5. Setup Instructions (For the LLM)
If a user hands you this document and says "Apply this process to my project":
- Analyze the Request: Ask for the high-level goal ("What are we building?") and the tech preferences ("Rust or Python?").
- Git Check: Check if the directory is a git repository (
git status). If not, rungit init. - Scaffold: Run commands to create the
specs/andstories/folders. - Draft Context: Write
specs/00_CONTEXT.mdbased on the user's answer. - Draft Stack: Write
specs/tech/STACK.mdbased on best practices for that language. - Wait: Ask the user for "Story #1".
6. Code Quality Tools
MANDATORY: Before completing Step 4 (Verification) of any story, you MUST run all applicable linters and fix ALL errors and warnings. Zero tolerance for warnings or errors.
TypeScript/JavaScript: Biome
- Tool: Biome - Fast formatter and linter
- Check Command:
npx @biomejs/biome check src/ - Fix Command:
npx @biomejs/biome check --write src/ - Unsafe Fixes:
npx @biomejs/biome check --write --unsafe src/ - Configuration:
biome.jsonin project root - When to Run:
- After every code change to TypeScript/React files
- Before committing any frontend changes
- During Step 4 (Verification) - must show 0 errors, 0 warnings
Biome Rules to Follow:
- No
anytypes (use proper TypeScript types orunknown) - No array index as
keyin React (use stable IDs) - No assignments in expressions (extract to separate statements)
- All buttons must have explicit
typeprop (button,submit, orreset) - Mouse events must be accompanied by keyboard events for accessibility
- Use template literals instead of string concatenation
- Import types with
import type { }syntax - Organize imports automatically
Rust: Clippy
- Tool: Clippy - Rust linter
- Check Command:
cargo clippy --all-targets --all-features - Fix Command:
cargo clippy --fix --allow-dirty --allow-staged - When to Run:
- After every code change to Rust files
- Before committing any backend changes
- During Step 4 (Verification) - must show 0 errors, 0 warnings
Clippy Rules to Follow:
- No unused variables (prefix with
_if intentionally unused) - No dead code (remove or mark with
#[allow(dead_code)]if used conditionally) - Use
?operator instead of explicit error handling where possible - Prefer
if letovermatchfor single-pattern matches - Use meaningful variable names
- Follow Rust idioms and best practices
Build Verification Checklist
Before asking for user acceptance in Step 4:
- Run
cargo clippy(Rust) - 0 errors, 0 warnings - Run
cargo check(Rust) - successful compilation - Run
cargo test(Rust) - all tests pass - Run
npx @biomejs/biome check src/(TypeScript) - 0 errors, 0 warnings - Run
npm run build(TypeScript) - successful build - Manually test the feature works as expected
- All acceptance criteria verified
Failure to meet these criteria means the story is NOT ready for acceptance.