Skip to content

Commit 6bae6f5

Browse files
authored
Merge pull request #12 from nullhack/release/v0.5.0-fine-sift
release: v0.5.0 Fine Sift
2 parents 23a53e3 + 3c905e3 commit 6bae6f5

27 files changed

Lines changed: 1140 additions & 219 deletions

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,10 @@ cython_debug/
175175
.opencode/
176176
.templates/
177177
AGENTS.md
178+
179+
# smith managed
180+
.flowr/
181+
.opencode/
182+
.templates/
183+
AGENTS.md
184+
# end smith managed

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,37 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [v0.5.0+20260505] - Fine Sift - 2026-05-05
6+
7+
### Added
8+
9+
- **Subflow exit resolution**: when a subflow exits, the exit name is resolved through the parent flow's transition map to determine the actual target state; previously the exit name was used directly as a state ID, causing sessions to land on invalid states
10+
- **Subflow chaining**: after exiting a subflow, if the resolved target state has a `flow:` field, the stack is pushed again to enter the next subflow atomically (e.g., discovery-flow → exit → architecture-flow)
11+
- **`session init` auto-enters subflow**: when the first state has a `flow:` field, `session init` pushes the stack and enters the subflow's initial state automatically
12+
- **`next` shows all transitions**: the `next` command now displays ALL transitions including blocked/guarded ones, with trigger→target mapping and status markers (`[blocked]` + condition hints)
13+
- **`next` JSON output uses `transitions` array**: replaces the old `next: [strings]` with `transitions: [{trigger, target, status, conditions}]` — a breaking change (pre-release)
14+
- **`states --session`**: lists states in the current (sub)flow resolved from the session
15+
- **`validate --session`**: validates the current (sub)flow resolved from the session
16+
- **`check --session <trigger>`**: correctly shows transition conditions for the given trigger (previously the argument was silently captured by argparse as `flow_file`)
17+
- **Session-aware dispatch refactor**: extracted `_dispatch_session_command()` to reduce cyclomatic complexity; unified session-aware routing for all commands
18+
- **Post-mortem**: `PM_20260505_subflow-mechanism-non-functional` documenting the two critical bugs and testing gap
19+
20+
### Changed
21+
22+
- **`flowr/domain/loader.py`**: `resolve_subflows()` now tries the `flow` field path as-is first, then appends `.yaml` if not found — making the `.yaml` extension optional in flow definitions
23+
- **`flowr/__main__.py`**: new helper functions `_find_flow_file()`, `_enter_subflow()`, `_resolve_subflow_exit()`, `_build_transition_list()`, `_format_transitions_text()`; `_apply_session_transition()` gains `flows_dir` parameter for parent flow resolution; `_cmd_next` and `_cmd_next_session` use rich transition output; argparse for `validate` and `states` now accept optional `flow_file` and `--session`
24+
- **`flowr/cli/session_cmd.py`**: `cmd_session_init` auto-enters initial subflow when first state has `flow:` field
25+
- **JSON is now default CLI output**: `--json` flag replaced with `--text` — JSON is the default for all commands; use `--text` for human-readable output
26+
- **ADR_20260426_subflow_resolution** amended: `.yaml` extension now optional with fallback-append
27+
28+
### Fixed
29+
30+
- **Subflow path resolution**: `resolve_subflows()` failed for flow references without `.yaml` extension (e.g., `flow: discovery-flow`) — now tries as-is first, then appends `.yaml`
31+
- **Subflow exit state resolution**: `pop_stack(target)` used exit name directly as state ID, producing invalid states — now resolves through parent transition map
32+
- **`check --session <target>` argparse capture**: target argument was silently consumed as `flow_file` positional — now correctly routed as target trigger name
33+
- **Test fixture gap**: added tests for flow references without `.yaml` extension and subflow exit resolution/chaining
34+
- **Stack frame state bug**: `_enter_subflow()` recorded the pre-transition state instead of the subflow wrapper state in the stack frame, causing exit resolution to look up the wrong parent `next` map — now records the target (subflow wrapper) state
35+
536
## [v0.4.0+20260502] - Refined Semolina - 2026-05-02
637

738
### Added

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ flowr defines a YAML format for non-deterministic state machines with per-state
4343
```
4444
flowr validate deploy.yaml → valid: True
4545
flowr states deploy.yaml → prepare, execute, review
46-
flowr next deploy.yaml review → approve (guarded), reject
46+
flowr next deploy.yaml review → approve → deployed [blocked] need: score=>=80
47+
→ reject → failed
4748
flowr transition deploy.yaml review approve --evidence score=85
48-
→ from: review, to: deployed
49+
→ from: review, to: deployed
4950
flowr session init deploy-flow → session created at state: prepare
5051
flowr --session transition approve → from: prepare, to: review
5152
flowr mermaid deploy.yaml → stateDiagram-v2 ...
@@ -55,7 +56,7 @@ flowr mermaid deploy.yaml → stateDiagram-v2 ...
5556

5657
**Query.** States, transitions, conditions, attributes — ask any question the flow can answer.
5758

58-
**Sessions.** Init, show, set-state, transition, list. Subflow push/pop for nested workflows. One `--session` flag turns any command session-aware.
59+
**Sessions.** Init, show, set-state, transition, list. Subflow push/pop for nested workflows. Auto-enters initial subflow on `session init`. One `--session` flag turns any command session-aware (including `validate` and `states`).
5960

6061
**Config.** `flowr config` shows where every value comes from — default, pyproject.toml, or CLI override.
6162

@@ -111,8 +112,8 @@ review
111112

112113
$ flowr next deploy.yaml review
113114
state: review
114-
next: approve (guarded)
115-
next: reject
115+
approve → deployed [blocked] need: score=>=80
116+
reject → failed
116117

117118
$ flowr transition deploy.yaml review approve --evidence score=85
118119
from: review
@@ -152,15 +153,15 @@ default_session = default (default)
152153
| `flowr validate <flow>` | Validate a flow definition |
153154
| `flowr states <flow>` | List all state ids |
154155
| `flowr check <flow> <state> [<target>]` | Show state details or transition conditions |
155-
| `flowr next <flow> <state> [--evidence K=V]` | Show valid next transitions |
156+
| `flowr next <flow> <state> [--evidence K=V]` | Show all transitions with trigger→target and condition status |
156157
| `flowr transition <flow> <state> <trigger> [--evidence K=V]` | Compute next state |
157158
| `flowr mermaid <flow>` | Export as Mermaid state diagram |
158-
| `flowr session init <flow> [--name NAME]` | Create a new session at the flow's initial state |
159+
| `flowr session init <flow> [--name NAME]` | Create a new session (auto-enters initial subflow) |
159160
| `flowr session show [--name NAME] [--format FORMAT]` | Display current session state |
160161
| `flowr session set-state <state> [--name NAME]` | Update the session's current state |
161162
| `flowr session list [--format FORMAT]` | List all sessions |
162163
| `flowr config [--json]` | Show resolved configuration with sources |
163-
| `flowr --session <command>` | Run a command using session state |
164+
| `flowr --session <command>` | Run a command using session state (works with validate, states, check, next, transition) |
164165

165166
`<flow>` accepts a file path or a short flow name (resolved from `.flowr/flows/`). Use `--flows-dir` to override the configured flows directory. All commands accept `--json` for machine-readable output. Evidence: `--evidence key=value` (repeatable) or `--evidence-json '{"key": "value"}'`.
166167

docs/adr/ADR_20260426_subflow_resolution.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@ When a state references a subflow via its `flow` field, the CLI needs to locate
1717

1818
## Decision
1919

20-
Subflow lookup resolves the `flow` field as a relative path from the root flow's directory, including file extension. Subflow entry is displayed as `<flow-name>/<first-state-id>`.
20+
Subflow lookup resolves the `flow` field as a relative path from the root flow's directory. The resolver tries the path as-is first; if the file does not exist, it appends `.yaml` and tries again. This makes the `.yaml` extension optional — flow authors can write `flow: discovery-flow` or `flow: discovery-flow.yaml`. Subflow entry is displayed as `<flow-name>/<first-state-id>`.
21+
22+
**Amendment (2026-05-05):** The original decision required the `.yaml` extension explicitly. This was changed because real production flows omit the extension (e.g., `flow: discovery-flow`), and requiring it added friction. The fallback-append approach keeps explicit extensions working while also supporting bare names.
2123

2224
## Reason
2325

24-
Convention-over-configuration with path flexibility; matches project layout; supports subdirectories; no extra CLI flags needed.
26+
Convention-over-configuration with path flexibility; matches project layout; supports subdirectories; no extra CLI flags needed. The extension-optional fallback reduces ceremony for the common case (same directory, `.yaml` files) while preserving support for explicit paths and subdirectories.
2527

2628
## Alternatives Considered
2729

2830
- **Explicit --flows-dir flag**: over-engineered for v1; adds cognitive load
2931
- **Walk parent directories**: fragile; could find wrong files
30-
- **Auto-append .yaml extension**: adds magic; explicit extension is simpler
32+
- **Require .yaml extension always**: original decision; rejected after production flows showed authors prefer bare names
3133
- **Subflow output as just first state**: loses context about which subflow you're entering
3234
- **Subflow output as just flow name**: loses context about entry point
3335

@@ -36,11 +38,19 @@ Convention-over-configuration with path flexibility; matches project layout; sup
3638
- (+) Zero configuration; works with existing flow file layout
3739
- (+) Supports subdirectories and relative paths
3840
- (+) Clear, unambiguous output showing subflow name + entry state
39-
- (-) Flow field must include `.yaml` extension — existing reference YAML files need updating
41+
- (+) `.yaml` extension is optional — bare flow names work (e.g., `flow: discovery-flow`)
4042
- (-) Flow field has dual meaning (lookup path vs canonical flow name from loaded YAML)
4143

4244
## Risk Assessment
4345

4446
| Risk | Probability | Impact | Mitigation | Accepted? |
4547
|------|------------|--------|------------|-----------|
46-
| Flow field has dual meaning (lookup path vs canonical flow name) | Low | Medium | Ambiguity is mitigated by convention; loader extracts canonical name from loaded YAML | Yes |
48+
| Flow field has dual meaning (lookup path vs canonical flow name) | Low | Medium | Ambiguity is mitigated by convention; loader extracts canonical name from loaded YAML | Yes |
49+
| Bare name matches wrong file in subdirectory | Low | Low | Resolution is relative to root flow's directory; convention keeps flows flat | Yes |
50+
51+
## Changes
52+
53+
| Date | Change | Reason |
54+
|------|--------|--------|
55+
| 2026-04-26 | Initial decision: require `.yaml` extension | Original ADR |
56+
| 2026-05-05 | Made `.yaml` extension optional with fallback | Production flows use bare names; requiring extension was friction (PM_20260505_subflow-mechanism-non-functional) |
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
Feature: Subflow Transition Overhaul
2+
3+
The subflow mechanism (push/pop stack for nested flows) is completely
4+
non-functional for real flows due to two critical bugs in path resolution
5+
and exit handling. Additionally, the `next` command hides critical
6+
navigation information — trigger names, guarded transitions, and evidence
7+
requirements — making autonomous agent navigation impossible.
8+
9+
This feature fixes the subflow mechanism, enhances `next` to show the
10+
complete navigation picture, and improves session command robustness.
11+
12+
Depends on: session-management (core), session-management-extended.
13+
14+
Status: ELICITING
15+
16+
Rules (Business):
17+
- Flow references without `.yaml` extension are resolved by appending `.yaml` if the bare path doesn't exist
18+
- Subflow exit resolves through the parent flow's transition map, not by using the exit name directly
19+
- Subflow chaining: after exiting one subflow, if the resolved target enters another subflow, the stack is pushed again
20+
- `session init` auto-enters the initial subflow if the first state has a `flow:` field
21+
- `next` always shows ALL transitions including blocked/guarded ones, with status markers
22+
- `next` output shows trigger→target mapping with inline condition requirements
23+
- `next` JSON output uses `transitions` array of objects with `trigger`, `target`, `status`, `conditions`
24+
- `check --session <target>` correctly shows transition conditions
25+
- `states --session` and `validate --session` resolve the current flow from session
26+
27+
Constraints:
28+
- Non-session commands remain backward compatible
29+
- `next` JSON output is a breaking change: `next` array of strings replaced with `transitions` array of objects
30+
- Domain layer (Session, SessionStackFrame) changes are minimal — fixes are in CLI/transition logic
31+
32+
## Questions
33+
34+
| ID | Question | Status | Answer / Assumption |
35+
|----|----------|--------|---------------------|
36+
| Q1 | Should `next` show all transitions or require `--all` flag? | Resolved | Always show allagents need the full picture |
37+
| Q2 | Should `next` JSON preserve backward compatibility? | Resolved | Noclean break, replace `next` array with `transitions` objects |
38+
| Q3 | Should `set-state` support crossing flow boundaries? | Open | Phase 3may add `--flow` flag for recovery scenarios |
39+
40+
## Changes
41+
42+
| Session | Q-IDs | Change |
43+
|---------|-------|--------|
44+
| 2026-05-05 S1 | Q1-Q3 | Created: subflow resolution, exit handling, next overhaul, session robustness |
45+
46+
Rule: Subflow Path Resolution
47+
As a flowr user
48+
I want flow references without file extensions to resolve correctly
49+
So that I can use clean flow names in YAML definitions
50+
51+
@id:sf-001
52+
Example: Flow reference without .yaml extension resolves correctly
53+
Given a flow YAML with `flow: discovery-flow` referencing a file `discovery-flow.yaml`
54+
When the CLI resolves subflows
55+
Then the subflow is loaded successfully
56+
57+
@id:sf-002
58+
Example: Flow reference with .yaml extension still works
59+
Given a flow YAML with `flow: child.yaml` referencing a file `child.yaml`
60+
When the CLI resolves subflows
61+
Then the subflow is loaded successfully
62+
63+
Rule: Subflow Exit Resolution
64+
As a flowr user
65+
I want subflow exits to correctly resolve through the parent flow's transitions
66+
So that my session lands on the correct next state after completing a subflow
67+
68+
@id:sf-003
69+
Example: Subflow exit resolves parent transition target
70+
Given a session inside discovery-flow with stack frame pointing to main-flow/discovery
71+
And discovery-flow exits with `complete`
72+
And main-flow/discovery maps `complete → architecture`
73+
When the user transitions with the exit trigger
74+
Then the session pops the stack and lands at main-flow/architecture
75+
76+
@id:sf-004
77+
Example: Subflow chaining enters next subflow after exit
78+
Given a session exiting discovery-flow back to main-flow
79+
And the resolved target `architecture` has `flow: architecture-flow`
80+
When the subflow exit resolves to architecture
81+
Then the session pushes a new stack frame and enters architecture-flow
82+
83+
@id:sf-005
84+
Example: Subflow exit with invalid parent state produces error
85+
Given a session inside a subflow with a corrupted stack frame
86+
When the subflow exit cannot find the parent state
87+
Then the CLI prints a clear error indicating the parent state is invalid
88+
89+
Rule: Session Init Enters Subflow
90+
As a flowr user
91+
I want session init to automatically enter the initial subflow
92+
So that I can start working immediately without an extra transition step
93+
94+
@id:sf-006
95+
Example: Session init auto-enters subflow when initial state has flow field
96+
Given a flow whose initial state has `flow: discovery-flow`
97+
When the user runs flowr session init main-flow
98+
Then the session is created inside discovery-flow with a stack frame pointing to main-flow
99+
100+
@id:sf-007
101+
Example: Session init without subflow works as before
102+
Given a flow whose initial state has no `flow:` field
103+
When the user runs flowr session init simple-flow
104+
Then the session is created at the initial state with no stack
105+
106+
Rule: Next Shows Full Transition Map
107+
As a flowr user
108+
I want to see all transitions with trigger names, targets, and condition requirements
109+
So that I can navigate without reading raw YAML files
110+
111+
@id:sf-008
112+
Example: Next shows trigger→target mapping for all transitions
113+
Given a session at a state with transitions `needs_full_discovery → event-storming`, `needs_scope_only → scope-boundary`
114+
When the user runs flowr next --session
115+
Then the output shows each trigger name alongside its target state
116+
117+
@id:sf-009
118+
Example: Next shows blocked guarded transitions with condition hints
119+
Given a session at a state with a guarded transition requiring `committed_to_main_locally=verified`
120+
When the user runs flowr next --session without evidence
121+
Then the blocked transition appears with a marker showing the required evidence
122+
123+
@id:sf-010
124+
Example: Next shows passing guarded transitions
125+
Given a session at a state with a guarded transition requiring `committed_to_main_locally=verified`
126+
When the user runs flowr next --session --evidence committed_to_main_locally=verified
127+
Then the transition appears as passing/open
128+
129+
@id:sf-011
130+
Example: Next JSON output uses transitions array with full details
131+
Given a session at a state with transitions
132+
When the user runs flowr next --session --json
133+
Then the output contains a `transitions` array where each entry has `trigger`, `target`, `status`, and `conditions`
134+
135+
Rule: Check Session Shows Conditions
136+
As a flowr user
137+
I want to check transition conditions from session mode
138+
So that I know exactly what evidence to provide
139+
140+
@id:sf-012
141+
Example: Check session with target shows transition conditions
142+
Given a session at a state with a guarded transition
143+
When the user runs flowr check --session <trigger-name>
144+
Then the output shows the conditions required for that transition
145+
146+
Rule: Session-Aware States and Validate
147+
As a flowr user
148+
I want to list states and validate the current flow from session
149+
So that I can inspect my current (sub)flow context without specifying the flow name
150+
151+
@id:sf-013
152+
Example: States command with --session lists current flow's states
153+
Given a session inside architecture-flow
154+
When the user runs flowr states --session
155+
Then the output lists all states in architecture-flow
156+
157+
@id:sf-014
158+
Example: Validate command with --session validates current flow
159+
Given a session inside architecture-flow
160+
When the user runs flowr validate --session
161+
Then the output validates architecture-flow

0 commit comments

Comments
 (0)