Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/8BitTacoSupreme/flowstate/llms.txt

Use this file to discover all available pages before exploring further.

Every pipeline run reads from and writes to a single flowstate.json file in the project root. The file is fully described by Pydantic models in flowstate/state.py — all reads go through FlowStateModel.model_validate(), so the schema is enforced on every load. State is written after context generation and after every tool step, giving crash-resilient progress tracking. State files written by v0.1.0 are automatically migrated on first read.

FlowStateModel

The top-level model that maps to the root of flowstate.json:
class FlowStateModel(BaseModel):
    version: str = "0.2.0"
    created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
    updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
    interview: InterviewAnswers = Field(default_factory=InterviewAnswers)
    preferences: ProjectPreferences = Field(default_factory=ProjectPreferences)
    tools: dict[str, ToolState] = Field(
        default_factory=lambda: {
            "research": ToolState(),
            "strategy": ToolState(),
            "gsd": ToolState(),
            "discipline": ToolState(),
        }
    )
    artifacts: dict[str, str] = Field(default_factory=dict)
    context_files: list[str] = Field(default_factory=list)
FieldTypeDescription
versionstrSchema version, currently "0.2.0"
created_atdatetimeUTC timestamp when the file was first created
updated_atdatetimeUTC timestamp updated by every save_state() call
interviewInterviewAnswersAll user answers from the intake interview
preferencesProjectPreferencesProject-level settings (model, budget, dry-run, etc.)
toolsdict[str, ToolState]Per-tool status keyed by "research", "strategy", "gsd", "discipline"
artifactsdict[str, str]Named artifact paths, e.g. {"research_report": "research/report.md"}
context_fileslist[str]Relative paths of files written by write_context_files()

ToolState

Tracks the lifecycle of a single tool execution:
class ToolState(BaseModel):
    status: ToolStatus = ToolStatus.READY
    started_at: datetime | None = None
    completed_at: datetime | None = None
    error: str | None = None
    artifacts: list[str] = Field(default_factory=list)
started_at is set when status transitions to RUNNING. completed_at is set on COMPLETED or BLOCKED. Both remain null in JSON until the tool runs.

ToolStatus Enum

class ToolStatus(StrEnum):
    READY     = "ready"
    RUNNING   = "running"
    COMPLETED = "completed"
    BLOCKED   = "blocked"
The lifecycle transitions enforced by update_tool():
READY → RUNNING → COMPLETED
                ↘ BLOCKED
BLOCKED means the tool’s execute_fn() returned success=False. The pipeline continues with remaining steps; BLOCKED tools are not retried automatically.

InterviewAnswers

Stores all answers captured by the intake interview:
class InterviewAnswers(BaseModel):
    research_focus: str = ""
    core_problem: str = ""
    ten_x_vision: str = ""
    milestones: list[str] = Field(default_factory=list)
    test_coverage: int = 80
    architecture_pattern: str = ""
FieldDefaultDescription
research_focus""Comma-separated topics for the research adapter
core_problem""The problem the project solves (used in strategy prompt)
ten_x_vision""The 10× improvement the project delivers
milestones[]Ordered list of milestone strings for roadmap generation
test_coverage80Target test coverage percentage
architecture_pattern""e.g. "event-driven", "CQRS", "monolith"

ProjectPreferences

Project-level settings that control bridge behaviour and pipeline mode:
class ProjectPreferences(BaseModel):
    project_name: str = ""
    dry_run: bool = False
    auto_branch_on_hardening: bool = True
    model: str = ""
    max_budget_usd: float | None = None
    effort: str = ""
FieldDefaultDescription
project_name""Used in PROJECT.md and CLAUDE.md headings
dry_runFalseIf True, all bridge calls are skipped and mock output is returned
auto_branch_on_hardeningTrueReserved for future branch automation on hardening phases
model""Overrides the default Claude model for all bridge calls
max_budget_usdNoneOptional spend cap passed to BridgeConfig
effort""Optional effort level passed to BridgeConfig

State Functions

load_state(root) → FlowStateModel

Reads and validates flowstate.json from the project root. If the file does not exist, returns a fresh default FlowStateModel. Runs _migrate_state() before validation to handle v0.1.0 files:
def load_state(root: Path | None = None) -> FlowStateModel:
    p = state_path(root)
    if p.exists():
        raw = json_mod.loads(p.read_text())
        migrated = _migrate_state(raw)
        return FlowStateModel.model_validate(migrated)
    return FlowStateModel()

save_state(state, root) → Path

Sets updated_at to the current UTC time, serializes the model to JSON with 2-space indentation, and writes it to flowstate.json. Returns the path:
def save_state(state: FlowStateModel, root: Path | None = None) -> Path:
    p = state_path(root)
    state.updated_at = datetime.now(UTC)
    p.write_text(state.model_dump_json(indent=2) + "\n")
    return p

update_tool(state, tool, *, status, error, artifact)

Mutates a ToolState entry in-place. All keyword arguments are optional:
def update_tool(
    state: FlowStateModel,
    tool: str,
    *,
    status: ToolStatus | None = None,
    error: str | None = None,
    artifact: str | None = None,
) -> None:
    ts = state.tools[tool]
    if status is not None:
        ts.status = status
        if status == ToolStatus.RUNNING:
            ts.started_at = datetime.now(UTC)
        elif status in (ToolStatus.COMPLETED, ToolStatus.BLOCKED):
            ts.completed_at = datetime.now(UTC)
    if error is not None:
        ts.error = error
    if artifact is not None:
        ts.artifacts.append(artifact)

state_path(root) → Path

Returns the absolute path to flowstate.json for a given project root. Defaults to the current working directory:
def state_path(root: Path | None = None) -> Path:
    return (root or Path.cwd()) / "flowstate.json"

State Migration: v0.1.0 → v0.2.0

_migrate_state() is called by load_state() on every read. It checks the version field and renames old tool keys to their current names:
_OLD_TOOL_KEYS = {
    "autoresearch": "research",
    "gstack":       "strategy",
    "superpowers":  "discipline",
}
The migration also ensures context_files is present (it was not tracked in v0.1.0) and fills any missing tool keys with default ToolState values:
def _migrate_state(data: dict) -> dict:
    version = data.get("version", "0.1.0")
    if version >= "0.2.0":
        return data
    # rename old tool keys
    ...
    data["version"] = "0.2.0"
    if "context_files" not in data:
        data["context_files"] = []
    return data
Migration is non-destructive — the original file is overwritten only when save_state() is called after migration. Loading a v0.1.0 file and inspecting it without running the pipeline will not modify the file.

flowstate.json Schema Example

{
  "version": "0.2.0",
  "created_at": "2025-01-15T10:30:00Z",
  "updated_at": "2025-01-15T10:35:42Z",
  "interview": {
    "research_focus": "event sourcing, CQRS",
    "core_problem": "Teams lose context switching between planning tools",
    "ten_x_vision": "Single command bootstraps a fully-planned, research-backed project",
    "milestones": [
      "Core pipeline with dry-run mode",
      "Persistent memory with FTS5",
      "Native GSD integration"
    ],
    "test_coverage": 90,
    "architecture_pattern": "event-driven"
  },
  "preferences": {
    "project_name": "flowstate",
    "dry_run": false,
    "auto_branch_on_hardening": true,
    "model": "",
    "max_budget_usd": null,
    "effort": ""
  },
  "tools": {
    "research": {
      "status": "completed",
      "started_at": "2025-01-15T10:31:05Z",
      "completed_at": "2025-01-15T10:32:44Z",
      "error": null,
      "artifacts": ["/home/user/project/research/report.md"]
    },
    "strategy": {
      "status": "completed",
      "started_at": "2025-01-15T10:32:45Z",
      "completed_at": "2025-01-15T10:34:01Z",
      "error": null,
      "artifacts": ["/home/user/project/research/strategy.md"]
    },
    "gsd": {
      "status": "completed",
      "started_at": "2025-01-15T10:34:02Z",
      "completed_at": "2025-01-15T10:34:03Z",
      "error": null,
      "artifacts": ["/home/user/project/.planning/ROADMAP.md"]
    },
    "discipline": {
      "status": "completed",
      "started_at": "2025-01-15T10:34:03Z",
      "completed_at": "2025-01-15T10:34:03Z",
      "error": null,
      "artifacts": []
    }
  },
  "artifacts": {
    "research_report": "/home/user/project/research/report.md",
    "strategy_report": "/home/user/project/research/strategy.md",
    "roadmap": "/home/user/project/.planning/ROADMAP.md"
  },
  "context_files": [
    ".planning/PROJECT.md",
    ".planning/ROADMAP.md",
    ".planning/config.json",
    ".claude/CLAUDE.md",
    "research/brief.md"
  ]
}
flowstate.json is gitignored by default because it contains absolute paths to your local filesystem. Do not commit it to version control — re-run flowstate init --skip-interview in a new clone to regenerate it.

Build docs developers (and LLMs) love