State files live in .flow-states/ at the project root, named after the branch:
.flow-states/app-payment-webhooks.json
.flow-states/app-payment-webhooks.log
.flow-states/app-payment-webhooks-phases.json
.flow-states/app-payment-webhooks-ci-passed
.flow-states/user-profile-redesign.json
.flow-states/user-profile-redesign.log
.flow-states/user-profile-redesign-phases.json
.flow-states/user-profile-redesign-ci-passed
Each feature has up to four files: the state file (.json), the log file (.log), a frozen copy of flow-phases.json (-phases.json), and a CI sentinel (-ci-passed). The CI sentinel caches the last passing bin/flow ci snapshot so subsequent runs skip automatically when nothing changed (use --force to bypass). Multiple features can run simultaneously with no conflicts. The directory is added to .git/info/exclude by /flow-start (per-repo, not committed). Created by /flow-start, deleted by /flow-complete.
State files are local to each machine. In a multi-engineer team, each engineer’s .flow-states/ directory only contains their own features. GitHub (issues, PRs, labels) is the shared coordination layer visible to all engineers. The “Flow In-Progress” label on issues is the mechanism for cross-engineer WIP detection — see /flow-issues.
The frozen phases file is a snapshot of flow-phases.json taken at start time. Scripts use it instead of the live plugin source so that phase config changes during FLOW development don’t break in-progress features.
{
"schema_version": 1,
"branch": "app-payment-webhooks",
"repo": "org/repo",
"pr_number": 42,
"pr_url": "https://github.com/org/repo/pull/42",
"started_at": "2026-02-20T10:00:00-08:00",
"current_phase": "flow-plan",
"framework": "rails",
"prompt": "fix #83 and #89 — close issues at complete time",
"files": {
"plan": null,
"dag": null,
"log": ".flow-states/app-payment-webhooks.log",
"state": ".flow-states/app-payment-webhooks.json"
},
"plan_file": null,
"session_id": null,
"transcript_path": null,
"skills": {
"flow-start": {"continue": "manual"},
"flow-plan": {"continue": "auto", "dag": "auto"},
"flow-code": {"commit": "manual", "continue": "manual"},
"flow-code-review": {"commit": "auto", "continue": "auto"},
"flow-learn": {"commit": "auto", "continue": "auto"},
"flow-abort": "auto",
"flow-complete": "auto"
},
"phases": {
"flow-start": {
"name": "Start",
"status": "complete",
"started_at": "2026-02-20T10:00:00-08:00",
"completed_at": "2026-02-20T10:05:00-08:00",
"session_started_at": null,
"cumulative_seconds": 300,
"visit_count": 1
},
"flow-plan": {
"name": "Plan",
"status": "in_progress",
"started_at": "2026-02-20T10:05:00-08:00",
"completed_at": null,
"session_started_at": "2026-02-20T10:30:00-08:00",
"cumulative_seconds": 1800,
"visit_count": 2
},
"flow-code": {
"name": "Code",
"status": "pending",
"started_at": null,
"completed_at": null,
"session_started_at": null,
"cumulative_seconds": 0,
"visit_count": 0
}
},
"phase_transitions": [],
"issues_filed": [],
"slack_thread_ts": null,
"slack_notifications": []
}
| Field | Type | Description |
|---|---|---|
schema_version |
integer | Schema version marker — currently 1 |
branch |
string | Git branch name — slug format. Canonical identity field. Feature name and worktree path are derived from this at read time |
repo |
string / null | GitHub repo in owner/repo format, cached during /flow-start. Used by issue.py to avoid repeated git remote calls. Null if detection fails |
pr_number |
integer / null | GitHub PR number. Null during early Start (before PR creation) when created by init-state — backfilled by start-setup after PR creation |
pr_url |
string / null | Full GitHub PR URL. Null during early Start — backfilled by start-setup after PR creation |
started_at |
ISO 8601 | When the feature was started (Phase 1 entry) |
current_phase |
string | The currently active phase key (e.g. "flow-code") |
framework |
string | "rails", "python", "ios", "go", or "rust" — set during /flow-prime, copied to state by /flow-start |
files |
object | Structured artifact file paths — see Files Object |
plan_file |
string / null | Legacy: absolute path to the plan file. Superseded by files.plan — kept for backward compatibility |
session_id |
string / null | Claude Code session UUID — set by Stop hook from hook stdin |
transcript_path |
string / null | Absolute path to session transcript .jsonl — set by Stop hook from hook stdin |
skills |
object / absent | Per-skill autonomy settings copied from .flow.json by /flow-start — see Skills Object |
start_step |
integer | Current Start phase step (0-11). Set by init-state --start-step at creation, then updated by start-step subcommand at each step boundary. Used by the TUI to show “step 3 of 11” in the Start phase annotation. Absent when Start is not in progress |
start_steps_total |
integer | Total number of Start phase steps (hardcoded 11). Set by init-state --start-steps-total at creation. Used by the TUI for “step N of M” display |
plan_step |
integer | Current Plan phase step (0-4). Set via set-timestamp at each step boundary. Used by the TUI to show “step 2 of 4” in the Plan phase annotation. Absent when Plan is not in progress |
plan_steps_total |
integer | Total number of Plan phase steps (hardcoded 4). Set via set-timestamp after phase entry. Used by the TUI for “step N of M” display |
code_review_step |
integer | Last completed Code Review step (0-6). Set to 0 on phase entry, incremented after each step. Used by the TUI to show “simplifying - step 1 of 6” and for resume after context compaction |
code_tasks_total |
integer / absent | Total number of implementation tasks from the plan. Set by Phase 2 (Plan) Step 4 via set-timestamp. Used by the TUI to show “task 3 of 8” in the Code phase annotation. Absent in state files created before v0.40 |
code_task_name |
string / absent | Short description of the current Code task from the plan. Set by Phase 3 (Code) via set-timestamp before each task starts. Used by the TUI to show “Update tests - task 2 of 3” in the Code phase annotation. Absent when Code phase is not in progress or in state files created before the feature was added |
learn_step |
integer | Last completed Learn step (0-6). Set via set-timestamp at each step boundary. TUI displays learn_step + 1 as the current step. Used for resume and TUI annotation “gathering sources - step 1 of 7” |
learn_steps_total |
integer | Total number of Learn phase steps (hardcoded 7). Set via set-timestamp after phase entry. Used by the TUI for “step N of M” display |
complete_step |
integer | Current Complete phase step (1-12). Set via set-timestamp at each step start. Used by the TUI to show “merging PR - step 8 of 12” and as resume point for CI gate loops |
complete_steps_total |
integer | Total number of Complete phase steps (hardcoded 12). Set via set-timestamp after phase entry. Used by the TUI for “step N of M” display |
_continue_pending |
string | Child skill or action currently executing. Phase skills set this before invoking a child skill so the Stop hook (stop-continue.py) blocks the turn from ending and forces continuation. Values are either a child skill name (e.g. decompose) or the action commit (used by flow-code, flow-code-review, flow-learn, and flow-complete when invoking /flow:flow-commit). Cleared by the Stop hook after forcing continuation. Empty string or absent means no continuation pending. |
_continue_context |
string | Specific next-step instructions for the model after a child skill returns. Written by phase skills before _continue_pending, read and cleared by the Stop hook. Included in the block reason so the model knows what to do after the turn boundary. Empty string or absent means use the generic fallback message. |
_blocked |
ISO 8601 / null | Timestamp when the flow was blocked on AskUserQuestion. Set by PreToolUse hook (validate-ask-user.py) when allowing a prompt through. Cleared by PostToolUse hook (clear-blocked.py) after user responds and by Stop hook (stop-continue.py) as a safety net for crashed sessions. Transient. |
_last_failure |
object / null | API error context from the last StopFailure event. Contains type (string — error category, e.g. rate_limit, auth_failure, network_timeout), message (string — error details), and timestamp (ISO 8601 — when the failure occurred). Written by StopFailure hook (stop-failure.py), consumed and cleared by SessionStart hook. Transient. |
_auto_continue |
string | Command to invoke next (e.g. /flow:flow-plan). Set by phase_complete() when skills.<phase>.continue is "auto". Cleared by phase_enter() when the next phase starts. A PreToolUse hook on AskUserQuestion automatically answers prompts via updatedInput while this flag is set. |
prompt |
string | The full text passed to /flow-start — used by Plan as feature description and by Complete to extract #N issue references for auto-closing |
notes |
array | Corrections captured via /flow-note — see Notes Array |
phase_transitions |
array | Phase entry log recording every phase_enter() call with from/to/timestamp and optional reason — see Phase Transitions Array |
issues_filed |
array | GitHub issues filed during the feature — see Issues Filed Array |
compact_summary |
string / null | Conversation summary from last compaction. Written by PostCompact hook, consumed and cleared by SessionStart hook. Transient. |
compact_cwd |
string / null | CWD at last compaction time. Written by PostCompact hook, consumed and cleared by SessionStart hook. Transient. |
compact_count |
integer | Total number of context compactions during this feature. Incremented by PostCompact hook. Permanent. |
slack_thread_ts |
string / null | Slack message timestamp of the initial thread message. Set by Start phase after first notify-slack call. Used by subsequent phases as thread_ts to reply in the same thread. Null or absent if Slack is not configured. |
slack_notifications |
array | Slack notifications sent during the feature — see Slack Notifications Array |
Each phase entry has identical fields regardless of status.
| Field | Type | Description |
|---|---|---|
name |
string | Human-readable phase name |
status |
string | pending, in_progress, or complete |
started_at |
ISO 8601 / null | First time this phase was entered — never overwritten |
completed_at |
ISO 8601 / null | Most recent time this phase was exited — updated on every completion |
session_started_at |
ISO 8601 / null | Timestamp when current session entered this phase — reset to now() on resume, cleared to null on clean exit |
cumulative_seconds |
integer | Total seconds spent in this phase across all visits — additive |
visit_count |
integer | Number of times this phase has been entered |
started_at is set on first entry and never changed againcompleted_at is set on every exit — reflects the most recent completionsession_started_at is set on entry and cleared to null on exitsession_started_at is not null, elapsed time is accumulated into cumulative_seconds and session_started_at is reset to now() to continue timingcumulative_seconds increments by (exit_time - session_started_at) on each clean exitCopied from .flow.json into the state file by /flow-start. Phase skills read autonomy config from the state file rather than .flow.json, because .flow.json lives at the project root and is not accessible from worktrees.
Present only when .flow.json contains a skills key (i.e., after running /flow-prime with Customize or a preset). Phase skills that don’t find a skills key in the state file fall back to built-in defaults.
"skills": {
"flow-start": {"continue": "manual"},
"flow-code": {"commit": "manual", "continue": "manual"},
"flow-code-review": {"commit": "auto", "continue": "auto"},
"flow-learn": {"commit": "auto", "continue": "auto"},
"flow-abort": "auto",
"flow-complete": "auto"
}
Structured artifact file paths using relative paths (relative to project root)
for portability. Created by /flow-start with plan and dag set to null.
Updated by /flow-plan via set-timestamp --set files.plan=<path> and
set-timestamp --set files.dag=<path>.
"files": {
"plan": ".flow-states/app-payment-webhooks-plan.md",
"dag": ".flow-states/app-payment-webhooks-dag.md",
"log": ".flow-states/app-payment-webhooks.log",
"state": ".flow-states/app-payment-webhooks.json"
}
| Field | Type | Description |
|---|---|---|
plan |
string / null | Relative path to the implementation plan file — set by Phase 2 |
dag |
string / null | Relative path to the DAG analysis file — set by Phase 2 |
log |
string | Relative path to the session log file — set at creation |
state |
string | Relative path to this state file — set at creation |
Populated throughout the session by /flow-note. Survives compaction
and session restarts. Read by Learn as a primary source.
"notes": [
{
"phase": "flow-code",
"phase_name": "Code",
"timestamp": "2026-02-20T14:23:00-08:00",
"type": "correction",
"note": "Never assume branch-behind is unlikely — multiple active sessions means branches regularly fall behind main"
}
]
Populated by phase_enter() on every phase entry. Records the journey
through phases, enabling the Learn phase to identify rework patterns.
"phase_transitions": [
{"from": "flow-start", "to": "flow-plan", "timestamp": "2026-02-20T10:05:00-08:00"},
{"from": "flow-plan", "to": "flow-code", "timestamp": "2026-02-20T10:30:00-08:00"},
{"from": "flow-code", "to": "flow-code-review", "timestamp": "2026-02-20T14:00:00-08:00"},
{"from": "flow-code-review", "to": "flow-code", "timestamp": "2026-02-20T14:30:00-08:00", "reason": "test failures"}
]
| Field | Type | Description |
|---|---|---|
from |
string / null | Phase key before transition. Null on first entry |
to |
string | Phase key being entered |
timestamp |
ISO 8601 | When the transition occurred |
reason |
string / absent | Optional reason for backward transitions |
Populated by bin/flow add-issue whenever a skill files a GitHub issue
via bin/flow issue. Surfaced in the Complete phase PR body and Done banner.
"issues_filed": [
{
"label": "Rule",
"title": "Add rule: never use git checkout for file ops",
"url": "https://github.com/org/repo/issues/42",
"phase": "flow-learn",
"phase_name": "Learn",
"timestamp": "2026-03-12T10:00:00-07:00"
}
]
| Field | Type | Description |
|---|---|---|
label |
string | Issue category: Rule, Flow, Flaky Test, Tech Debt, or Documentation Drift |
title |
string | Issue title as filed on GitHub |
url |
string | Full GitHub issue URL |
phase |
string | Phase key where the issue was filed (e.g. "flow-learn") |
phase_name |
string | Human-readable phase name |
timestamp |
ISO 8601 | When the issue was filed |
Populated by bin/flow add-notification when a phase skill sends a Slack message via bin/flow notify-slack. Only present when Slack is configured via /flow:flow-prime. Surfaced in the Complete phase PR body.
"slack_notifications": [
{
"phase": "flow-start",
"phase_name": "Start",
"ts": "1234567890.123456",
"thread_ts": "1234567890.123456",
"message_preview": "Feature started",
"timestamp": "2026-03-20T10:00:00-07:00"
}
]
| Field | Type | Description |
|---|---|---|
phase |
string | Phase key that sent the notification (e.g. "flow-start") |
phase_name |
string | Human-readable phase name |
ts |
string | Slack message timestamp (unique ID for the posted message) |
thread_ts |
string | Slack thread timestamp (matches slack_thread_ts for thread replies) |
message_preview |
string | First 100 characters of the message text |
timestamp |
ISO 8601 | When the notification was sent |
The plan lives at .flow-states/<branch>-plan.md alongside other feature artifacts. The state file stores the relative path in files.plan. The plan file includes:
Valid phase transitions are defined in flow-phases.json at the plugin root. Forward progression is always valid. Backward transitions are limited per phase.
Valid transitions are defined in flow-phases.json: Code can return to Plan; Code Review can return to Code or Plan.