Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3cfb574
chore(flow): fix fn-66 task deps (T2←T1, T4←T1/T2/T3)
gmickel Jun 18, 2026
0480e82
feat(tracker-sync): gate terminal Done/verified on merge evidence; ad…
gmickel Jun 18, 2026
6948fe8
fix(tracker-sync): wire in-review into reconcile loop; model ambiguou…
gmickel Jun 18, 2026
5a7502d
chore(flow): fn-66.1 done-record + review memory
gmickel Jun 18, 2026
0ac5dae
feat(tracker-sync): re-scope touchpoints — completionReview never-ter…
gmickel Jun 18, 2026
d78f505
fix(tracker-sync): address impl-review — land self-check branches op,…
gmickel Jun 18, 2026
fe62787
fix(tracker-sync): address impl-review round 2 — steps.md duplicate c…
gmickel Jun 18, 2026
5d022d4
fix(tracker-sync): flowctl.py activation header — note fn-66 uncondit…
gmickel Jun 18, 2026
014e593
chore(flow): fn-66.2 done-record + review memory
gmickel Jun 18, 2026
17e58dc
feat(pilot): all-done hardening — open-PR defers to land, never silen…
gmickel Jun 18, 2026
6f69001
fix(pilot): register DEFERRED_TO_LAND in ralph.md /goal driver gramma…
gmickel Jun 18, 2026
9aae140
chore(flow): fn-66.3 done-record
gmickel Jun 18, 2026
8decd4a
docs(tracker-sync): fn-66.4 — decision-record note + CHANGELOG + vers…
gmickel Jun 18, 2026
ff6dd08
chore(flow): fn-66.4 done-record
gmickel Jun 18, 2026
e52409f
fix(land): tracker Done gate must re-probe MERGED after merge, not re…
gmickel Jun 18, 2026
d178612
chore(flow): fn-66 completion-review SHIP (rp)
gmickel Jun 18, 2026
d6d9d59
fix(tracker-sync): flowToNormalized must evaluate prEvidence FIRST — …
gmickel Jun 18, 2026
3e92c39
fix(make-pr): In Review touchpoint must reconcile (body-preserving), …
gmickel Jun 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"plugins": [
{
"name": "flow-next",
"version": "2.1.1",
"version": "2.1.2",
"source": {
"source": "local",
"path": "./plugins/flow-next"
Expand Down
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
},
"metadata": {
"description": "Plan-first workflows for Claude Code and Factory Droid. Ships flow-next: zero-dep, spec-driven, Ralph autonomous mode.",
"version": "2.1.1"
"version": "2.1.2"
},
"plugins": [
{
"name": "flow-next",
"description": "Zero-dependency planning + execution with .flow/ task tracking and Ralph autonomous mode (multi-model review gates). Worker subagent per task for context isolation. Includes 21 subagents, 24 commands, 28 skills.",
"version": "2.1.1",
"version": "2.1.2",
"author": {
"name": "Gordon Mickel",
"email": "gordon@mickel.tech",
Expand Down
21 changes: 16 additions & 5 deletions .flow/bin/flowctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,8 +1021,11 @@ def save_task_definition(task_id: str, definition: dict) -> None:
# because `load_flow_config()` always merges this default block in, an absent /
# null / unrelated write must NOT activate the bridge. The bridge is active iff
# raw `tracker.enabled == true` OR raw `tracker.type ∈ {linear, github}` (see
# `tracker_sync_active`). All `perEvent` leaves default `off`, so even a stray
# `enabled=true` does nothing until a specific event is opted in.
# `tracker_sync_active`). All `perEvent` leaves default `off`, so a stray
# `enabled=true` opts in no PER-EVENT lifecycle sync until a specific event is
# enabled — EXCEPT the two fn-66 unconditional bridge-active paths: make-pr's
# PR↔issue link + In Review push, and `land.merged`'s Done-on-merge (both ride the
# bridge-active predicate alone, not a perEvent leaf — see SKILL.md / steps.md).
TRACKER_TYPES = {"linear", "github"}
TRACKER_PER_EVENT_LEAVES = {"off", "pull", "push", "reconcile", "comment"}
TRACKER_TIEBREAKS = {"flow-wins", "tracker-wins", "always-ask"}
Expand Down Expand Up @@ -1068,9 +1071,17 @@ def get_default_tracker_config() -> dict:
# /flow-next:qa skill treats any non-`off` value as `comment`.
# Default `off` keeps every existing repo silent until opted in.
"qa": "off",
# fn-60 (R13) — opt-in post-merge touchpoint for /flow-next:land
# (flip linked issue terminal + release/verdict comment). Nested
# like work.*; default off keeps non-land repos at zero overhead.
# fn-60 (R13) / fn-66 (R10) — post-merge touchpoint for /flow-next:land.
# fn-66 makes land.merged the SOLE `Done` driver and ACTIVE-BY-DEFAULT
# whenever the bridge is active (like make-pr's unconditional PR-link
# path) — the merge→Done projection rides the bridge-active predicate
# alone, NOT this leaf. A real merge is the only event that legitimately
# projects terminal Done (gated on the GitHub MERGED probe), so leaving
# it opt-in would strand boards at In Review post-merge. The schema
# default stays `off` (a bare enabled=true activates no lifecycle sync
# — fn-52.1 invariant); when the bridge IS active the land skill fires
# this touchpoint regardless of the leaf, which then only tunes the
# optional verdict comment, never the (MERGED-gated) status write.
"land": {"merged": "off"},
},
"perTracker": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: "Policy-claim inversion: sweep ALL surfaces (both ceremony copies, docs, CLI head"
date: "2026-06-18"
track: bug
category: build-errors
module: plugins/flow-next/skills/flow-next-tracker-sync/steps.md
tags: [fn-66, tracker-sync, ceremony-duplicate, dispatch-grammar, docs-parity, steps.md, SKILL.md]
problem_type: build-error
symptoms: 3 NEEDS_WORK rounds — each surfaced the next un-updated duplicate of an inverted policy claim (completionReview/land.merged Done ownership)
root_cause: "discovery ceremony + 'owns Done' prose duplicated across SKILL.md, steps.md, 3 docs pages, github.md, and flowctl.py header; first pass touched only the primary site"
resolution_type: fix
related_to: [bug/build-errors/id-grammar-widening-must-cover-the-full-2026-06-03, bug/build-errors/status-policy-map-needs-a-matching-2026-06-18]
---

## Problem
Re-scoping the tracker-sync lifecycle touchpoints (fn-66.2) took three NEEDS_WORK rounds because a policy claim ("completionReview flips Done/verified", "the one exception is make-pr") was duplicated across MORE surfaces than the first edit pass touched. The discovery ceremony exists in TWO places (`flow-next-tracker-sync/SKILL.md:87-94` AND `steps.md:49-60`); the stale "owns Done" / "one exception" prose also lived in `docs/teams.md`, `docs/flowctl.md`, `references/github.md`, and the `flowctl.py` activation header. Each review round surfaced the next un-updated copy.

## What Didn't Work
Editing the "primary" site (SKILL.md ceremony + the three touchpoint files) and assuming the policy was now consistent. The reviewer kept (correctly) citing a stale duplicate — most notably `steps.md`'s second copy of the ceremony, which I'd missed entirely.

## Solution
- Grep the ENTIRE skill + docs tree for the policy claim's keywords BEFORE the first review, not after: `grep -rn "flip Done\|owns the Done\|one exception\|completionReview reconcile\|enabled=true does nothing"` across `skills/`, `docs/`, and `scripts/flowctl.py`.
- The discovery ceremony is mirrored in `SKILL.md` AND `steps.md` — any ceremony-seed or activation-exception change must touch BOTH.
- Lifecycle dispatch grammar must stay canonical `operation: <verb> <id>, event: <key>` — descriptors like "(In Review)", "+ link $PR_URL", "verdict + R-ID coverage" belong in surrounding prose, NEVER inside the operation token (memory mirror-regen-exposes-latent-canonical). Both the touchpoint dispatch AND its retro-fire copy carry the grammar.

## Prevention
- When inverting a policy claim, treat it as a repo-wide string sweep: enumerate every surface (canonical skills, both ceremony copies, all docs pages, the CLI header comment) before sending to review.
- A dispatch-grammar change needs a grep for the op token across the touchpoint file's main path AND its end-of-run retro-fire path.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: Status-policy map needs a matching reconcile-loop branch per rung (map ≠ write)
date: "2026-06-18"
track: bug
category: build-errors
module: plugins/flow-next/skills/flow-next-tracker-sync/references/status-sync.md
tags: [fn-66, tracker-sync, status, reconcile, who-wins, in-review, merge-evidence, rp-review]
problem_type: build-error
symptoms: "new normalized rung mapped in the table but unreachable — reconcile loop had no branch, setStatus never fired"
root_cause: edited the flow→normalized table + fixtures without adding the matching reconcile-loop branch; fixture asserted a setStatus the loop couldn't produce
resolution_type: fix
related_to: [bug/build-errors/id-grammar-widening-must-cover-the-full-2026-06-03, bug/build-errors/skill-prose-must-match-real-flowctl-2026-06-10]
---

## Problem
A status-policy reference table can map a flow state to a normalized rung (e.g. open PR → `in-review`) while the *reconcile loop* that actually writes status has NO branch for that rung — so the projected status silently falls through to the conflictTiebreak default and never drives a `setStatus`. fn-66.1 added `flowToNormalized(spec, prEvidence)` mapping open-PR → `in-review`, but the `reconcileStatus` if/elif ladder had no `flowNorm == in-review` branch. Fixture S-H asserted `setStatus(in-review)`, contradicting the loop. RP impl-review caught it (NEEDS_WORK).

## What Didn't Work
Editing only the flow→normalized lookup table and the fixtures. A who-wins/reconcile policy has TWO layers — the mapping (what a state *means*) and the loop (what to *write*). Changing the map without adding the matching loop branch leaves the new rung unreachable.

## Solution
For every new normalized rung the map can emit, add a matching explicit branch in the reconcile loop, ordered correctly against the existing deadlock-first / terminal-wins rules. Also: a "preserve existing state" nuance that lives only in a fixture must be promoted to a real loop branch (S-G: prEvidence=none projects in-review but must NOT force a downgrade — needs its own guarded branch checked BEFORE the generic in-review push). And model ALL probe outcomes as explicit enum values (added `ambiguous` + `probe-error` alongside merged/open/closed-unmerged/none) so unknown-branch / gh-failure routes to non-terminal+NEEDS_HUMAN rather than being mistaken for `none`/`merged`. status-sync.md reconcile loop + flow→normalized table + fixtures S-G/S-J.

## Prevention
When editing a status/who-wins policy that has a mapping table AND a reconcile loop: every rung the table can emit needs a corresponding loop branch that decides whether/what to write, ordered against the collision-first rules. A fixture asserting a `setStatus(X)` is the oracle — grep the loop for a branch that produces X before shipping. Model probe/evidence inputs as an exhaustive enum (including the failure/ambiguous cases), never just the happy buckets.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Build the tracker sync bridge as PROJECTION / visibility, not coordination. The
- Body, status, and comments all sync two-way. Body reconciliation is agentic (host-agent semantic 3-way merge against a `lastSyncedAt` merge-base snapshot, translating between flow's structured spec and the tracker's free-form issue); only genuine contradictions surface for a human, and in Ralph mode they queue while confident merges proceed.
- Linear-first ordering accepts the headless/Codex/cron MCP-availability caveat as a documented constraint (GitHub-via-gh is the headless-robust path, shipped second).
- A future /flow-next:strategy run should fold this into an explicit track; this decision feeds that conversation.
- **fn-66 (FLOW-15) refines the status projection, not the projection-vs-coordination stance.** Two of the two-way status states are now evidence-gated: `In Review` is **actively projected** at make-pr (an open PR is the In Review lifecycle rung, projected unconditionally whenever the bridge is active), and terminal `Done` is **gated on a GitHub-confirmed `MERGED` probe** for the spec branch — local Flow completion (`done` + completion-review `ship`) is necessary, never sufficient, and `land.merged` is the sole Done driver (active-by-default when the bridge is active). This keeps the tracker an honest mirror of reality ("In Review" / "shipped") without making it a control plane: flow still drives state, the merge probe is read-only evidence, and a human's manual board edit still wins per the who-wins tiebreak. Projection-not-coordination is unchanged; the projection is just more lifecycle-accurate.

## Future extension (NOT in fn-52): board-triggered per-spec executor
A Symphony-shaped trigger layer can sit on top of the fn-52 sync bridge — but it is a SEPARATE strategy decision (it makes the tracker a trigger, revisiting projection-not-coordination).
Expand Down
Loading
Loading