29 KiB
Foreach Back-Edges Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make foreach bodies return through validated back-edges to their immediate owner, with unique static control regions and fresh persisted state for every dynamic foreach activation.
Architecture: Add one pure control-region analysis module and make both validation and context inventory consume its result. Keep dynamic execution separate: foreach activation metadata owns a barrier and item identities for one controller visit, while frame advancement recognizes an immediate-owner back-edge as item completion.
Tech Stack: Python 3.14, Pydantic workflow models, dataclass runtime state, pytest, pytest-asyncio, Ruff, basedpyright, markdownlint-cli2.
Spec:
docs/superpowers/specs/2026-09-04-foreach-back-edge-design.md
Global Constraints
- Preserve the flat serialized workflow graph; do not add nested body models.
- Keep
ENDas a workflow/subgraph terminal, never a foreach-item return. - Give every reachable node use exactly one static foreach-owner stack.
- Reject unreachable workflow nodes and structurally impossible item returns.
- Permit ordinary cycles when their nodes remain in one control region.
- Create a fresh persisted activation for every dynamic foreach-controller visit, including repeated visits in the same parent frame.
- Keep current
fail,skip,collect, reducer, interrupt, sync, and async behavior. - Keep nested context innermost-only; inherited structured foreach context is deferred.
- Do not add break/continue, fork/gather, retry execution, a step limit, a new Python DSL, or compatibility behavior for unobserved persisted data.
- Add docstrings or comments around owner-stack traversal, return-edge handling, activation lifecycle, and any fail-closed runtime checks.
- Do not modify or commit the user's dirty
docs/AGENTS.md.
Task 1: Build the Pure Control-Region Analyzer
Files:
- Create:
src/wf_core/analysis/control_regions.py - Modify:
src/wf_core/analysis/__init__.py - Test:
tests/core/test_foreach_control_regions.py
Interfaces:
-
Consumes:
Workflow,ForeachNode,EndNode,Edge, andEND. -
Produces:
type ForeachOwnerStack = tuple[str, ...] class ControlRegionIssueKind(StrEnum): UNREACHABLE_NODE = "unreachable_node" FOREACH_REGION_CONFLICT = "foreach_region_conflict" INVALID_FOREACH_RETURN = "invalid_foreach_return" INVALID_FOREACH_TERMINAL = "invalid_foreach_terminal" EMPTY_FOREACH_BODY = "empty_foreach_body" FOREACH_BODY_NO_RETURN = "foreach_body_no_return" @dataclass(frozen=True, slots=True) class ControlRegionIssue: kind: ControlRegionIssueKind path: str message: str @dataclass(frozen=True, slots=True) class ControlRegionAnalysis: owner_stack_by_node: dict[str, ForeachOwnerStack] issues: tuple[ControlRegionIssue, ...]Function:
analyze_control_regions(workflow: Workflow) -> ControlRegionAnalysis. -
owner_stack_by_nodecontains only nodes whose region is unambiguous. Later context analysis must not grant foreach fields to a conflicted node. -
Step 1: Write failing acceptance tests for legal regions
Add explicit tests named:
test_closed_root_cycle_has_one_empty_control_regiontest_foreach_cycle_with_possible_return_is_validtest_conditional_foreach_paths_can_both_returntest_nested_foreach_assigns_static_owner_stackstest_reentering_completed_foreach_keeps_one_static_region
The nested assertion must be exact:
assert analysis.owner_stack_by_node == { "f1": (), "f2": ("f1",), "work": ("f1", "f2"), "tail": ("f1",), "after": (), } assert analysis.issues == () -
Step 2: Run legal-region tests and confirm the missing module fails
Run:
uv run pytest -q tests/core/test_foreach_control_regions.pyExpected: collection fails because
wf_core.analysis.control_regionsdoes not exist. -
Step 3: Implement semantic traversal over node and owner stack
Use a bounded worklist of
(node_id, owner_stack)states. The special edge handling must follow this order:if source_is_foreach_loop: if edge.to == source.id: issue(EMPTY_FOREACH_BODY, edge_path) continue target_stack = (*owner_stack, source.id) else: target_stack = owner_stack if target_is_terminal and target_stack: issue(INVALID_FOREACH_TERMINAL, edge_path) elif edge.to == target_stack[-1]: record_item_return(edge, target_stack[-1]) connect_to_resumed_owner(edge.to, target_stack[:-1]) elif edge.to in target_stack: issue(INVALID_FOREACH_RETURN, edge_path) else: enqueue(edge.to, target_stack)An immediate return resumes the owner controller in the popped stack for structural analysis; it does not assign the owner node to its child's stack. Ignore unknown sources and targets here because ordinary edge validation already owns those diagnostics.
-
Step 4: Write failing tests for every invalid pressure case
Add one explicit test per topology:
test_external_entry_into_foreach_body_is_region_conflicttest_foreach_body_escape_is_region_conflicttest_skipping_inner_foreach_owner_is_invalid_returntest_entering_sibling_foreach_body_is_region_conflicttest_empty_foreach_body_is_rejectedtest_closed_foreach_body_cycle_has_no_returntest_foreach_body_cannot_target_end_tokentest_foreach_body_cannot_target_explicit_end_nodetest_every_unreachable_node_is_reported
Assert issue kind and location, for example:
assert (issue.kind, issue.path) == ( ControlRegionIssueKind.FOREACH_REGION_CONFLICT, "nodes[b]", ) -
Step 5: Implement conflicts, reachability, and returnability
Record the first stack for each node. If a second distinct stack reaches the same node, remove it from
owner_stack_by_nodeand emit one region conflict. After traversal, report every workflow node never reached fromstart.Build semantic state adjacency while traversing. For every reached state with a non-empty stack, require a graph path to a return edge for its current top owner. Returns from deeper nested foreach bodies may resume their controllers on the way. A closed root cycle remains valid because its stack is empty. Suppress cascading no-return diagnostics when a region conflict, invalid return, invalid terminal, or empty body already makes that state ambiguous.
-
Step 6: Run analyzer tests
Run:
uv run pytest -q tests/core/test_foreach_control_regions.py uv run ruff check src/wf_core/analysis/control_regions.py \ tests/core/test_foreach_control_regions.py uv run basedpyright --level error src/wf_core/analysis/control_regions.pyExpected: all commands pass.
-
Step 7: Commit the analyzer
git add src/wf_core/analysis/control_regions.py \ src/wf_core/analysis/__init__.py \ tests/core/test_foreach_control_regions.py git commit -m "feat: analyze foreach control regions"
Task 2: Make Context Inventory Consume Static Regions
Files:
- Modify:
src/wf_core/analysis/context_scopes.py - Modify:
tests/core/test_context_scopes.py
Interfaces:
-
Consumes:
analyze_control_regions(workflow)from Task 1. -
Produces: unchanged public functions
context_fields_by_node(workflow)andcontext_analysis_warnings(workflow). -
Keeps the existing runtime contract: the innermost foreach supplies
loop_item,loop_index, and its alias; completing it restores the outer context. It does not expose all enclosing aliases. -
Step 1: Rewrite context tests to canonical back-edges
Replace successful item routes such as:
{"from": "body", "outcome": "ok", "to": END}with:
{"from": "body", "outcome": "ok", "to": "each"}For the nested fixture use:
{"from": "inner_body", "outcome": "ok", "to": "inner"} {"from": "after_inner", "outcome": "ok", "to": "outer"} -
Step 2: Replace the mixed-reachability expectation
Delete the test that expects one node to receive conditional loop fields when reached both inside and outside a foreach. Add a test proving conflicted nodes receive no guaranteed foreach fields and the analysis warning includes the region conflict.
Keep the nested assertions:
assert "outer_item" not in inner assert inner["inner_item"].availability == "available" assert after_inner["outer_item"].availability == "available" assert "inner_item" not in after_inner -
Step 3: Run the context tests and confirm old traversal fails
Run:
uv run pytest -q tests/core/test_context_scopes.pyExpected: failures show that the single
FrameScopetraversal neither pops canonical return edges nor consumes region-conflict diagnostics. -
Step 4: Replace duplicate traversal with the analyzer result
Remove the local breadth-first scope traversal. For each unambiguous node, derive its active context from the final stack item:
stack = analysis.owner_stack_by_node.get(node_id) active_foreach_id = stack[-1] if stack else NonePass the controller's own static stack into item-schema resolution so an inner foreach may still declare
over="context.outer_item". Convert analyzer diagnostics to bounded context warnings. Do not reintroduce multiple scopes or conditional fields for a single node use. -
Step 5: Run context and authoring-contract tests
Run:
uv run pytest -q tests/core/test_context_scopes.py \ tests/wf_api/test_authoring_contracts.py uv run ruff check src/wf_core/analysis/context_scopes.py \ tests/core/test_context_scopes.py uv run basedpyright --level error src/wf_core/analysisExpected: all commands pass.
-
Step 6: Commit context integration
git add src/wf_core/analysis/context_scopes.py \ tests/core/test_context_scopes.py git commit -m "refactor: derive context from foreach control regions"
Task 3: Give Every Foreach Visit a Persisted Activation
Files:
- Modify:
src/wf_core/runtime/foreach_state.py - Modify:
src/wf_core/runtime/scheduler.py - Modify:
src/wf_core/runtime/lineage.py - Modify:
src/wf_core/runtime/ops/foreach.py - Modify:
src/wf_core/runtime/ops/nodes.py - Modify:
src/wf_core/runtime/step.py - Modify:
tests/core/test_foreach_barrier_state.py - Modify:
tests/core/test_scheduler.py - Modify:
tests/core/test_concurrent_foreach.py - Modify:
tests/core/test_concurrent_foreach_errors.py - Modify:
tests/core/test_concurrent_foreach_interrupts.py - Test:
tests/core/test_foreach_activations.py
Interfaces:
-
Produces:
@dataclass(slots=True) class ForeachActivationState: id: str foreach_node_id: str barrier: ForeachBarrierState @dataclass(frozen=True, slots=True) class ForeachItemOwner: parent_frame_id: str foreach_node_id: str activation_id: str item_index: intFunctions:
load_or_begin_foreach_activation(frame: ExecutionFrame, foreach_node_id: str, *, mode: Literal["serial", "concurrent"]) -> ForeachActivationStatesave_foreach_activation(frame: ExecutionFrame, activation: ForeachActivationState) -> Noneclose_foreach_activation(frame: ExecutionFrame, activation: ForeachActivationState) -> Noneitem_frame_owner(frame: ExecutionFrame) -> ForeachItemOwner | None
-
ForeachIterationMetadatagains requiredactivation_id: str. -
Callers compare owner fields by name; remove tuple slicing and positional unpacking.
-
Step 1: Write failing activation-lifecycle tests
Add tests proving:
first = load_or_begin_foreach_activation(frame, "each", mode="serial") save_foreach_activation(frame, first) restored = load_or_begin_foreach_activation(frame, "each", mode="serial") assert restored.id == first.id close_foreach_activation(frame, restored) second = load_or_begin_foreach_activation(frame, "each", mode="serial") assert second.id != first.id assert second.barrier.next_index == 0Also test malformed metadata, mode mismatch, closing a stale activation, and JSON round-trip through
ExecutionFrame.metadata. -
Step 2: Run activation tests and confirm failure
Run:
uv run pytest -q tests/core/test_foreach_activations.pyExpected: imports fail because activation lifecycle helpers do not exist.
-
Step 3: Implement the activation metadata seam
Hide the JSON dictionary shape inside
foreach_state.py. Persist, per parent frame and foreach node id, a monotonically increasing visit sequence plus at most one active activation. Derive opaque ids from parent frame id, foreach node id, and the persisted sequence; callers must never parse them.Closing removes the active barrier but preserves the next sequence. Do not retain a compatibility reader for the old barrier-only shape because the spec found no real persisted foreach data.
-
Step 4: Add activation identity to item metadata and helpers
Require this shape:
ForeachIterationMetadata( foreach_node_id=step.id, activation_id=activation.id, loop_index=loop_index, loop_item=item, loop_alias=step.as_, )Child frame and lineage ids must include
activation.id, so a later visit at item index zero cannot collide with the first visit. -
Step 5: Move runtime callers onto named owner and activation state
Update lineage reads, node-result buffering, async batching, failure collection, refill, and barrier commit to load the activation named by the child. Fail closed when a child result names a closed or different active activation.
-
Step 6: Update focused metadata tests
Replace hand-written item metadata in
test_foreach_barrier_state.pyandtest_scheduler.pywith required activation ids. Update hard-coded child frame and lineage ids in the concurrent, error, and interrupt suites to include the activation identity. Assertitem_frame_ownerreturnsForeachItemOwner, not a tuple. -
Step 7: Run runtime-state tests
Run:
uv run pytest -q tests/core/test_foreach_activations.py \ tests/core/test_foreach_barrier_state.py tests/core/test_scheduler.py \ tests/core/test_concurrent_foreach.py \ tests/core/test_concurrent_foreach_errors.py \ tests/core/test_concurrent_foreach_interrupts.py uv run ruff check src/wf_core/runtime tests/core/test_foreach_activations.py \ tests/core/test_foreach_barrier_state.py tests/core/test_scheduler.py uv run basedpyright --level error src/wf_core/runtimeExpected: all commands pass.
-
Step 8: Commit activation identity
git add src/wf_core/runtime/foreach_state.py \ src/wf_core/runtime/scheduler.py src/wf_core/runtime/lineage.py \ src/wf_core/runtime/ops/foreach.py src/wf_core/runtime/ops/nodes.py \ src/wf_core/runtime/step.py tests/core/test_foreach_activations.py \ tests/core/test_foreach_barrier_state.py tests/core/test_scheduler.py \ tests/core/test_concurrent_foreach.py \ tests/core/test_concurrent_foreach_errors.py \ tests/core/test_concurrent_foreach_interrupts.py git commit -m "feat: identify dynamic foreach activations"
Task 4: Execute Immediate-Owner Back-Edges
Files:
- Modify:
src/wf_core/runtime/ops/flow.py - Modify:
src/wf_core/runtime/foreach_state.py - Modify:
src/wf_core/runtime/ops/foreach.py - Test:
tests/core/test_foreach_back_edges.py - Modify:
tests/core/test_concurrent_foreach.py - Modify:
tests/core/test_concurrent_foreach_async.py - Modify:
tests/core/test_concurrent_foreach_errors.py - Modify:
tests/core/test_concurrent_foreach_interrupts.py - Modify:
examples/raw_concurrent_foreach.py - Modify:
examples/authoring_concurrent_foreach.py - Modify:
examples/demo_workflow.py - Modify:
tests/authoring/test_demo_workflow.py
Interfaces:
-
Consumes:
ForeachItemOwnerand activation lifecycle from Task 3. -
Produces:
advance_framerecognizes a target equal to the immediate owner's foreach node as item completion before generic node advancement. -
Produces an internal helper that derives ancestor foreach owners from frame ancestry for defensive non-local-return rejection.
-
Step 1: Write failing serial return tests
Add tests with canonical edges:
Edge.model_validate({"from": "each", "outcome": "loop", "to": "work"}) Edge.model_validate({"from": "work", "outcome": "ok", "to": "each"}) Edge.model_validate({"from": "each", "outcome": "done", "to": END})Prove two items execute, the child finishes at
each, the parent wakes, and the final workflow outcome remainsok. -
Step 2: Write failing cycle, nested, and re-entry runtime tests
Add explicit tests named:
test_foreach_body_cycle_can_repeat_then_returntest_conditional_body_can_return_on_either_outcometest_nested_foreach_returns_inner_then_outertest_reentering_foreach_uses_fresh_activation_and_item_framestest_subgraph_end_returns_to_subgraph_node_then_foreach_ownertest_nonlocal_runtime_return_fails_closed_when_validation_is_bypassed
The re-entry test must visit one foreach node twice in the root frame and assert two distinct activation ids and two distinct item-zero frame ids. The nested test must prove an inactive foreach is entered normally, while the direct defensive test constructs an invalid frame chain without running workflow preparation and asserts
WorkflowExecutionError. -
Step 3: Implement immediate-owner return in frame advancement
Before
ENDhandling or ordinary enqueue:owner = item_frame_owner(frame) if owner is not None and next_node_id == owner.foreach_node_id: source_node_id = frame.node_id frame.prior_outcome = outcome frame.activated_incoming_edge = source_node_id frame.node_id = owner.foreach_node_id frame.status = FrameStatus.COMPLETED frame.finished_at_node_id = owner.foreach_node_id wake_parent_for_child_progress(run, frame.id) run.sync_from_current_frame() returnAdd a comment explaining why the child does not execute the target node. If an item targets
END, or targets a foreach found below its immediate owner in the active ancestor chain, raiseWorkflowExecutionErrordefensively. -
Step 4: Close activations before controller completion edges
In both serial and concurrent completion paths, close the active activation before calling
advance_framefordoneorcompleted_with_errors. This makes a self-looping or later returning completion edge start a fresh visit. -
Step 5: Migrate executable foreach fixtures
Change item-success routes from
ENDto their owner in every file listed for this task. Keep controller completion routes toENDor their real outer continuation. For nested fixtures, return inner bodies to the inner foreach and outer-tail nodes to the outer foreach. -
Step 6: Prove concurrent, async, error, and interrupt behavior
Run:
uv run pytest -q tests/core/test_foreach_back_edges.py \ tests/core/test_concurrent_foreach.py \ tests/core/test_concurrent_foreach_async.py \ tests/core/test_concurrent_foreach_errors.py \ tests/core/test_concurrent_foreach_interrupts.py \ tests/core/test_raw_canonical_workflow_example.py \ tests/authoring/test_concurrent_foreach_examples.py \ tests/authoring/test_demo_workflow.pyAssert resumed interrupts retain the same activation id. Add a direct fail-closed test showing a completed activation cannot accept a result or wake-up from another activation.
-
Step 7: Run runtime static checks
Run:
uv run ruff check src/wf_core/runtime examples \ tests/core/test_foreach_back_edges.py \ tests/core/test_concurrent_foreach.py \ tests/core/test_concurrent_foreach_async.py \ tests/core/test_concurrent_foreach_errors.py \ tests/core/test_concurrent_foreach_interrupts.py uv run basedpyright --level error src/wf_core/runtimeExpected: all commands pass.
-
Step 8: Commit runtime back-edges
git add src/wf_core/runtime/ops/flow.py \ src/wf_core/runtime/foreach_state.py \ src/wf_core/runtime/ops/foreach.py \ examples/raw_concurrent_foreach.py \ examples/authoring_concurrent_foreach.py examples/demo_workflow.py \ tests/core/test_foreach_back_edges.py \ tests/core/test_concurrent_foreach.py \ tests/core/test_concurrent_foreach_async.py \ tests/core/test_concurrent_foreach_errors.py \ tests/core/test_concurrent_foreach_interrupts.py \ tests/authoring/test_demo_workflow.py git commit -m "feat: return foreach items through owner back-edges"
Task 5: Enforce Control Regions Through Public Validation
Files:
- Modify:
src/wf_core/validation/issues.py - Modify:
src/wf_core/validation/core.py - Modify:
tests/core/test_foreach_control_regions.py - Modify:
tests/core/test_foreach_policy.py - Modify:
tests/artifacts/test_draft_models.py - Modify:
tests/artifacts/test_draft_adapter.py
Interfaces:
-
Consumes:
ControlRegionAnalysisfrom Task 1. -
Produces public
ValidationIssueCodevalues matching everyControlRegionIssueKindvalue. -
Keeps
Workflow.validate_structure()andValidationReportsignatures unchanged. -
Step 1: Add failing public-validation assertions
For every pressure-case test, call both the pure analyzer and
workflow.validate_structure(). Invalid cases must assert the public code and offending path:matching = [ issue for issue in workflow.validate_structure().errors if issue.code == ValidationIssueCode.FOREACH_REGION_CONFLICT ] assert matching[0].path == "nodes[b]"Legal cases assert
report.ok. Add a test proving every unreachable node in one component receives its ownUNREACHABLE_NODEissue. -
Step 2: Run public-validation tests and confirm failure
Run:
uv run pytest -q tests/core/test_foreach_control_regions.pyExpected: analyzer tests pass, but public reports lack the new issue codes.
-
Step 3: Wire analysis into validation once
Add enum members with exactly the analyzer values. Call
analyze_control_regions(workflow)after ordinary node and edge validation, then translate each diagnostic:report.add( ValidationIssueCode(issue.kind.value), issue.path, issue.message, )Do not add a second graph traversal inside validation.
-
Step 4: Canonicalize policy and draft fixtures
Policy-only workflow helpers must include a distinct body node and route it back to the foreach owner. Draft fixtures with:
"routes": {"each_item": {"loop": "echo", "done": "__end__"}, "echo": {"ok": "__end__"}}become:
"routes": {"each_item": {"loop": "echo", "done": "__end__"}, "echo": {"ok": "each_item"}}Parse-only policy fixtures still use a distinct body; do not preserve an invalid
loop -> __end__shortcut just because the test does not execute it. -
Step 5: Run validation and draft suites
Run:
uv run pytest -q tests/core/test_foreach_control_regions.py \ tests/core/test_foreach_policy.py tests/artifacts/test_draft_models.py \ tests/artifacts/test_draft_adapter.py tests/wf_api/test_drafts_service.pyIf another fixture expected an unreachable node to be valid, either connect it when it represents intended execution or change that test to assert
UNREACHABLE_NODEwhen disconnection is the behavior under test. Do not add an allow-unreachable flag. -
Step 6: Run core validation static checks
Run:
uv run ruff check src/wf_core/validation \ tests/core/test_foreach_control_regions.py \ tests/core/test_foreach_policy.py tests/artifacts/test_draft_models.py \ tests/artifacts/test_draft_adapter.py uv run basedpyright --level error src/wf_core/validationExpected: all commands pass.
-
Step 7: Commit fail-closed validation
git add src/wf_core/validation tests/core/test_foreach_control_regions.py \ tests/core/test_foreach_policy.py tests/artifacts/test_draft_models.py \ tests/artifacts/test_draft_adapter.py git commit -m "feat: validate foreach control regions"
Task 6: Finish Migration, Documentation, and Full Verification
Files:
- Modify:
docs/wf_core_architecture.md - Modify:
docs/wf_authoring_control_flow.md - Modify:
docs/current_roadmap.md - Modify:
docs/superpowers/specs/2026-09-04-foreach-back-edge-design.md - Move after all checks pass:
docs/superpowers/plans/2026-09-04-foreach-back-edges.mdtodocs/historical/superpowers/plans/2026-09-04-foreach-back-edges.md
Interfaces:
-
Documents the implemented graph and runtime interface; adds no new runtime surface.
-
Preserves historical reports and recorded agent-challenge outputs verbatim.
-
Step 1: Search for stale canonical foreach returns
Run targeted searches:
rg -n 'child reaches `END`|body.*->.*END|record.*->.*END' \ docs skills examples tests src -g '*.md' -g '*.py' \ -g '!docs/historical/**' -g '!examples/agent_challenges/**' rg -n '"outcome": "loop".*"to": END|"loop": "__end__"' \ tests examples src -g '*.py'Classify each match: controller completion remains terminal; item-body completion changes to the owner. Do not rewrite unrelated ordinary terminal routes or immutable historical evidence.
-
Step 2: Update live architecture and authoring docs
Replace the old architecture statement that item children reach
ENDwith:An item child returns by targeting its immediate owning foreach. The child finishes at that owner location without executing the controller; the parent activation consumes the result and continues or completes its barrier.Add the canonical authoring example:
g.connect(each, "loop", record) g.connect(record, "ok", each) g.connect(each, "done", END)Document that region conflicts, unreachable nodes, body terminals, non-local returns, empty bodies, and bodies without possible returns fail validation.
-
Step 3: Mark the design implemented and roadmap item complete
Set the spec status to
Implemented on 2026-09-04. Move the roadmap bullet from active correction to recently completed runtime work. Keep fork/gather explicitly deferred. -
Step 4: Run the focused acceptance matrix
Run:
uv run pytest -q tests/core/test_foreach_control_regions.py \ tests/core/test_context_scopes.py tests/core/test_foreach_activations.py \ tests/core/test_foreach_back_edges.py \ tests/core/test_concurrent_foreach.py \ tests/core/test_concurrent_foreach_async.py \ tests/core/test_concurrent_foreach_errors.py \ tests/core/test_concurrent_foreach_interrupts.py \ tests/core/test_subgraph_step.py tests/authoring/test_demo_workflow.py \ tests/authoring/test_concurrent_foreach_examples.pyExpected: every current pressure-case row passes. The future-fork row remains documented and unimplemented because no fork node exists.
-
Step 5: Run repository verification
Run:
uv run pytest -q uv run ruff check uv run ruff format --check uv run basedpyright --level error pnpx markdownlint-cli2 \ 'docs/superpowers/specs/2026-09-04-foreach-back-edge-design.md' \ 'docs/wf_core_architecture.md' \ 'docs/wf_authoring_control_flow.md' git diff --checkExpected: all commands pass. If repository-wide Markdown files retain known unrelated lint debt, do not run an unsafe global auto-fix; report it and keep this slice's edited documents clean.
-
Step 6: Commit implementation documentation
git add docs/wf_core_architecture.md docs/wf_authoring_control_flow.md \ docs/current_roadmap.md \ docs/superpowers/specs/2026-09-04-foreach-back-edge-design.md git commit -m "docs: publish foreach back-edge semantics" -
Step 7: Archive the completed plan
After every prior task is complete and committed:
git mv docs/superpowers/plans/2026-09-04-foreach-back-edges.md \ docs/historical/superpowers/plans/2026-09-04-foreach-back-edges.md git add docs/historical/superpowers/plans/2026-09-04-foreach-back-edges.md git commit -m "docs: archive foreach back-edge plan"Search for and update any live links to the plan's historical path before committing. Leave the live implemented spec in
docs/superpowers/specs/.