diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 2ed95ac4..ce609d96 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,13 +6,13 @@ }, "metadata": { "description": "Plan-first workflows for Claude Code and Factory Droid. Two plugins: flow-next (recommended, zero-dep, Ralph autonomous mode) and flow (Beads integration).", - "version": "0.39.0" + "version": "0.40.0" }, "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, 13 commands, 18 skills.", - "version": "0.39.0", + "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, 17 commands, 22 skills.", + "version": "0.40.0", "author": { "name": "Gordon Mickel", "email": "gordon@mickel.tech", diff --git a/.flow/epics/fn-39-project-strategy-strategymd-anchor.json b/.flow/epics/fn-39-project-strategy-strategymd-anchor.json new file mode 100644 index 00000000..cdcc1209 --- /dev/null +++ b/.flow/epics/fn-39-project-strategy-strategymd-anchor.json @@ -0,0 +1,20 @@ +{ + "branch_name": "fn-39-project-strategy-strategymd-anchor", + "completion_review_status": "unknown", + "completion_reviewed_at": null, + "created_at": "2026-05-01T16:31:02.402787Z", + "default_impl": null, + "default_review": null, + "default_sync": null, + "depends_on_epics": [ + "fn-38-project-glossary-decision-records-and" + ], + "id": "fn-39-project-strategy-strategymd-anchor", + "next_task": 1, + "plan_review_status": "unknown", + "plan_reviewed_at": null, + "spec_path": ".flow/specs/fn-39-project-strategy-strategymd-anchor.md", + "status": "open", + "title": "Project strategy: STRATEGY.md anchor + downstream grounding", + "updated_at": "2026-05-01T16:37:07.311315Z" +} diff --git a/.flow/specs/fn-30-memory-schema-upgrade-categorized-yaml.md b/.flow/specs/fn-30-memory-schema-upgrade-categorized-yaml.md index 8a27aff1..37674b5c 100644 --- a/.flow/specs/fn-30-memory-schema-upgrade-categorized-yaml.md +++ b/.flow/specs/fn-30-memory-schema-upgrade-categorized-yaml.md @@ -85,7 +85,7 @@ applies_when: Optional fields (both tracks): ```yaml -status: active | stale # ce-compound-refresh maintenance marker +status: active | stale # staleness audit marker (consumed by /flow-next:audit) stale_reason: # only when status: stale stale_date: YYYY-MM-DD # only when status: stale last_updated: YYYY-MM-DD @@ -165,7 +165,7 @@ flowctl memory list [--track ] [--category ] [--json] flowctl memory read [--json] flowctl memory search [--track ] [--category ] [--json] flowctl memory migrate [--dry-run] [--yes] -flowctl memory refresh [--dry-run] # future: ce-compound-refresh-style maintenance (separate follow-up) +flowctl memory refresh [--dry-run] # future: staleness-audit maintenance (separate follow-up) ``` Entry ID format: `//-` (matches filepath). @@ -248,7 +248,7 @@ Ralph's receipt contract is unchanged. Memory is a separate surface. ## Boundaries - Not adding vector search (BM25-grep is sufficient for expected corpus size). -- Not adding ce-compound-refresh drift maintenance in this epic (deferred to follow-up). +- Not adding staleness-audit drift maintenance in this epic (deferred to follow-up — eventually shipped as `/flow-next:audit`). - Not changing the 3-type `--type` API entirely — backward compat is part of the contract. - Not auto-running `discoverability-patch` on `memory init` — it's a separate opt-in command. - Not introducing a centralized memory registry across projects (memory stays per-project). @@ -284,6 +284,6 @@ Ralph's receipt contract is unchanged. Memory is a separate surface. ## Follow-ups (not in this epic) -- `flowctl memory refresh` — MergeFoundry-upstream-style ce-compound-refresh: Keep/Update/Consolidate/Replace/Delete classification against current code; autofix mode marks stale entries +- `flowctl memory refresh` — staleness audit: Keep/Update/Consolidate/Replace/Delete classification against current code; autofix mode marks stale entries - Cross-project memory sharing (if demand surfaces) - TUI memory browser (if flow-next-tui matures) diff --git a/.flow/specs/fn-34-flow-nextaudit-agent-native-memory.md b/.flow/specs/fn-34-flow-nextaudit-agent-native-memory.md index 2f23a499..86d289a1 100644 --- a/.flow/specs/fn-34-flow-nextaudit-agent-native-memory.md +++ b/.flow/specs/fn-34-flow-nextaudit-agent-native-memory.md @@ -127,7 +127,7 @@ After the audit completes, agent verifies CLAUDE.md / AGENTS.md (whichever holds ## Early proof point -Task fn-34.1 ships the skill files. Validates the workflow shape against upstream's `ce-compound-refresh` (we have a known-working reference). If the skill phases don't compose cleanly with the existing fn-30 memory schema (`status: active|stale`, frontmatter fields, track/category structure), revisit before fn-34.2 (flowctl plumbing). +Task fn-34.1 ships the skill files. Validates the workflow shape against the agent-native pattern (host agent reads + judges + writes directly, no subprocess LLM dispatch). If the skill phases don't compose cleanly with the existing fn-30 memory schema (`status: active|stale`, frontmatter fields, track/category structure), revisit before fn-34.2 (flowctl plumbing). ## Risks @@ -145,12 +145,12 @@ Task fn-34.1 ships the skill files. Validates the workflow shape against upstrea - **Not** building a Python audit engine. The agent reads + judges directly. - **Not** dispatching codex/copilot via subprocess. Host agent is the intelligence. - **Not** auditing legacy flat files. Skip with warning; user runs `memory migrate` first. -- **Not** auto-committing without user awareness. Skill commits in Phase 5 with descriptive message; interactive mode confirms; autofix uses sensible branch defaults (per upstream `ce-compound-refresh` Phase 5 logic). +- **Not** auto-committing without user awareness. Skill commits in Phase 5 with descriptive message; interactive mode confirms; autofix uses sensible branch defaults. - **Not** deleting silently. Delete reserved for unambiguous cases (code gone + problem domain gone). Default to Replace or Consolidate when there's still value to preserve. ## Decision context -**Why skill-based, not flowctl Python subcommand?** This plugin always runs inside an agentic environment (Claude Code / Codex / Droid). The host agent can read files, run grep, judge relevance, and write updates directly using its own tools. Spawning a second LLM via codex/copilot subprocess is wasteful (cost + latency) and adds machinery (subprocess timeouts, structured-verdict parsers, drift guards) that disappears in the agent-native architecture. Upstream's `ce-compound-refresh` skill is a working reference for this pattern. +**Why skill-based, not flowctl Python subcommand?** This plugin always runs inside an agentic environment (Claude Code / Codex / Droid). The host agent can read files, run grep, judge relevance, and write updates directly using its own tools. Spawning a second LLM via codex/copilot subprocess is wasteful (cost + latency) and adds machinery (subprocess timeouts, structured-verdict parsers, drift guards) that disappears in the agent-native architecture. **Why thin flowctl plumbing instead of skill-only?** The skill needs deterministic operations for atomic frontmatter writes (`mark-stale` / `mark-fresh`), schema-validated round-trip, and consistent search filtering. Those are pure persistence concerns where flowctl's existing helpers shine. The split is: flowctl owns "set this field on this entry"; skill owns "should this entry be flagged." diff --git a/.flow/specs/fn-39-project-strategy-strategymd-anchor.md b/.flow/specs/fn-39-project-strategy-strategymd-anchor.md new file mode 100644 index 00000000..feb35c01 --- /dev/null +++ b/.flow/specs/fn-39-project-strategy-strategymd-anchor.md @@ -0,0 +1,194 @@ +# Project strategy: STRATEGY.md anchor + downstream grounding + +## Overview + +Add a `/flow-next:strategy` skill that writes/maintains a repo-root `STRATEGY.md` (target problem, our approach, who it's for, key metrics, tracks, optional milestones, optional not-working-on). Downstream skills (`/flow-next:prospect`, `/flow-next:plan`, `/flow-next:interview`, `/flow-next:capture`, `/flow-next:sync`) read it as constraint-check input. + +This is the third leg of flow-next's doc-aware infrastructure (joining `GLOSSARY.md` and `knowledge/decisions/` shipped in 0.39.0). Today the prospect → plan → interview → capture pipeline starts cold every time on "what should this repo build?" — there's no concept of strategic intent. STRATEGY.md fixes that. + +Section structure derived from Richard Rumelt's strategy kernel (*Good Strategy Bad Strategy*: diagnosis / guiding policy / coherent action), extended with persona + metrics for repo-doc utility. flow-next conventions: drop a `Marketing` optional section that was considered (over-rotated for OSS-tools repo); use bare `AskUserQuestion` in canonical (no inline cross-platform tables); apply lead-with-recommendation only to routing questions (substance questions stay free-form so the user's own language is preserved). + +## Scope + +**In scope:** +- New skill `plugins/flow-next/skills/flow-next-strategy/` with `SKILL.md`, `references/interview.md`, `references/strategy-template.md` +- Slash command `plugins/flow-next/commands/flow-next/strategy.md` +- Repo-root `STRATEGY.md` artifact (peer of `GLOSSARY.md` / `README.md`, never under `.flow/`) +- Thin flowctl plumbing: `flowctl strategy status [--json]`, `flowctl strategy read [--section ] [--json]`, `flowctl strategy list [--json]`. NO add/edit (skill writes file directly via host agent's `Write` tool, mirroring `/flow-next:audit` and `/flow-next:capture` patterns) +- Doc-aware autodetect extension — third condition (`strategy.sections_filled >= 1`) joins glossary and decisions checks +- Override flags `--strategy` / `--no-strategy` independent of `--docs` / `--no-docs` (5-row matrix documented) +- Downstream grounding integration in 5 skills: prospect, plan, interview, capture, sync (plan-sync agent) +- Codex mirror via `scripts/sync-codex.sh` (REQUIRED_OPENAI_YAML_SKILLS + generate_openai_yaml workflow blue `#3B82F6`) +- Forbidden-vocabulary guard extension (Tier 1 jargon only): synergy / pivot / disrupt / thought-leadership / best-in-class / world-class / 10x. Separate guard block in `ci_test.sh` (not extending the R17 DDD pattern) + mirrored in `sync-codex.sh` validation block +- Smoke test `plugins/flow-next/scripts/strategy_smoke_test.sh` exercising T1-T12 happy paths + corner cases +- Doc updates: CHANGELOG, plugins/flow-next/README.md, CLAUDE.md, .flow/usage.md, mickel.tech (Gordon's external repo, in scope per user request) + +**Out of scope:** +- No `flowctl strategy add/edit/remove/section-set` CLI — skill IS the editor. Strategy is too prose-heavy for atomic field-set plumbing. +- No metrics computation (records *which* metrics matter, not current values) +- No issue-tracker reconciliation +- No per-subdirectory STRATEGY.md cascade — repo-wide single root only. Subdirectory invocation walks up; "Using repo-root STRATEGY.md at " surfaced before interview. +- No multi-format migration — v1 refuses to overwrite a file without `generator: flow-next-strategy` sentinel; tells user to delete or rename. CE-format / hand-written migration deferred to v2. +- No `/flow-next:product-pulse` equivalent (a separate ideation considered and dropped — SaaS-shop-specific, out of scope per gap analysis) +- No `/flow-next:simplify-code` (Gordon has global `simplify`) + +## Architecture & data model + +**File shape:** +``` +--- +name: +last_updated: 2026-05-01 +generator: flow-next-strategy +--- + +# Strategy + +## Target problem +1-2 sentences. Diagnosis. Names the user situation and the crux. No solution language. + +## Our approach +1-2 sentences. Guiding policy. What this product commits to that makes the problem tractable. + +## Who it's for +**Primary:** + +## Key metrics +- **** — definition; where measured + +## Tracks +### +One line: investment area, not feature list. +_Why it serves the approach:_ one line. + +## Milestones (optional) +- **YYYY-MM-DD** — milestone + +## Not working on (optional) +- one line per item +``` + +Plain GFM markdown only. No MDX / admonitions / `:::tip` blocks. + +**Husk semantics:** file with H1 + frontmatter only, no populated H2 sections, returns `{exists: true, husk: true, sections_filled: 0}` from `flowctl strategy status`. Doc-aware autodetect uses `sections_filled >= 1`, NOT `[[ -f STRATEGY.md ]]` — same trap glossary fell into. + +**Atomic per-section writes:** each completed section lands on disk before the next interview prompt fires. `last_updated` bumps on every save. No draft state file. Re-invocation reads existing sections via `flowctl strategy status`, asks user which empty/stale section to revisit. Concurrent reads see a coherent older snapshot (race accepted, documented in skill prose). + +**Foreign-file detection:** frontmatter `generator: flow-next-strategy` is the sentinel. Missing or different value → skill blocks via `AskUserQuestion` ("migrate / keep / rewrite?"). On "keep" — exits without writing. v1 explicitly defers automatic migration. + +**Section deletion:** H2 stays with body `_Not currently tracking._` to preserve R-ID locked structure. Last-section delete leaves a husk (`# Strategy` H1 + frontmatter) — file never deleted (R18 invariant, mirrors `render_glossary_file`). + +**Single-root walk:** `flowctl strategy *` walks UP from cwd to first STRATEGY.md found, capped at repo root. NOT nearest-ancestor like glossary — strategy is repo-wide by Rumelt's definition. Subdirectory invocation surfaces "Using repo-root STRATEGY.md at " before interview. + +## Quick commands + +```bash +# Smoke test (after Tasks 1-5 complete) +plugins/flow-next/scripts/strategy_smoke_test.sh + +# Manual end-to-end check +flowctl strategy status --json +flowctl strategy read --section approach --json +flowctl strategy list --json + +# Sync verification (after Task 5) +./scripts/sync-codex.sh && ls plugins/flow-next/codex/skills/flow-next-strategy/ + +# CI guard +plugins/flow-next/scripts/ci_test.sh +``` + +## Acceptance + +- **R1:** `STRATEGY.md` lives at repo root (peer of `GLOSSARY.md` / `README.md`, never under `.flow/`). Survives a wipe of `.flow/` (R18 invariant from glossary epic). Frontmatter contains `name`, `last_updated` (ISO date), `generator: flow-next-strategy` only — no other keys. +- **R2:** Section structure locked: 5 required (`Target problem`, `Our approach`, `Who it's for`, `Key metrics`, `Tracks`) + 2 optional (`Milestones`, `Not working on`). Section order maps onto Rumelt's strategy kernel. A `Marketing` section is explicitly NOT included (considered and dropped). Optional sections deleted entirely if unused; never left as empty headers. +- **R3:** Skill uses bare `AskUserQuestion` in canonical files (no inline cross-platform tables). `sync-codex.sh` rewrites to `request_user_input` for Codex mirror. Free-form responses for substance questions (problem/approach/persona); single-select reserved for routing decisions only. Lead-with-recommendation pattern applies only to routing questions, NOT to substance questions — substance questions must capture the user's own language without priming. +- **R4:** Re-run on existing file routes via `AskUserQuestion` ("which section to revisit?"). Untouched sections preserved byte-identical (verified by `git diff --unified=0`). `last_updated` bumps on every per-section save (atomic per-section writes; no draft state file). +- **R5:** Pushback discipline enforced: 2 rounds maximum per section, then capture what user gave and add `` comment. Anti-pattern examples loaded from `references/interview.md` non-optionally (CE's "improvising from memory produces passive transcription" rule). Anti-pattern labels (vanity / fluff / feature-list) NOT leaked to user — only used internally to formulate sharper follow-up questions. Quote user's own words back when challenging (paraphrase softens). +- **R6:** `flowctl strategy status [--json]` returns `{exists: bool, husk: bool, sections_filled: int, total_sections: int, last_updated: str|null, file_path: str|null}`. Husk definition: file exists but `sections_filled == 0`. Used by downstream autodetect; rule is `sections_filled >= 1`, NOT `[[ -f STRATEGY.md ]]`. +- **R7:** `flowctl strategy read [--section ] [--json]` walks UP from cwd to first `STRATEGY.md` (single-root, capped at repo root, NOT nearest-ancestor). Returns `{path, name, last_updated, target_problem, approach, personas, metrics, tracks, milestones, not_working_on}`; `--section` filters to one block. Fenced code blocks in section bodies masked during parse (mirrors `_glossary_strip_fenced_code` helper at `flowctl.py:266`). +- **R8:** `flowctl strategy list [--json]` returns `{groups: [{path, sections, count}], file_count, total_sections}` — degenerate single-element group for v1 (single-root), kept for symmetry with `flowctl glossary list` so downstream skills can iterate generically. +- **R9:** Doc-aware autodetect activates when ANY of: `glossary.total_terms > 0` OR `decisions/` has entries OR `strategy.sections_filled >= 1`. Override flags `--strategy` / `--no-strategy` independent of `--docs` / `--no-docs`. Flag matrix: `(default)` = autodetect all three; `--docs` = on; `--no-docs` = off; `--no-docs --strategy` = strategy on / glossary+decisions off; `--docs --no-strategy` = glossary+decisions on / strategy off. Documented in CLAUDE.md and `flow-next-interview/SKILL.md`. +- **R10:** `/flow-next:prospect` Phase 0 grounding scan reads `STRATEGY.md` when `sections_filled >= 1`. Injects approach + active tracks verbatim into candidate-generation prompt — verbatim emit (no paraphrasing) so the candidate generator sees the exact language the user committed to. Adds `out-of-scope-vs-strategy` to rejection taxonomy. Surface as advisory at prospect phase (not auto-reject). +- **R11:** `/flow-next:plan` research scan (in `flow-next-plan/steps.md` Step 1) reads `STRATEGY.md`. Plan emits `## Strategy Alignment` spec section listing which active tracks the plan serves. Drift surfaced as `## Strategy drift flagged for review` block (read-only — never auto-supersedes; mirrors decision-record convention at `agents/plan-sync.md:101`). +- **R12:** `/flow-next:interview` doc-aware mode reads `STRATEGY.md` before terminology questions. Surfaces conflicts in `## Strategy Conflicts` spec section parallel to existing `## Glossary Conflicts` (`flow-next-interview/SKILL.md:192-249`). Throttle: ≤1 strategy-conflict question per interview turn (parallel to existing glossary-question throttle). +- **R13:** `/flow-next:capture` Phase 0 reads `STRATEGY.md` as input. Source-tags strategy-derived acceptance criteria as `[strategy:]` (joins existing `[user]` / `[paraphrase]` / `[inferred]` tags). Refuses to write spec contradicting an active track without `--override-strategy` flag. On flag fire: prompts user via `AskUserQuestion` to record a decision via `flowctl memory add --track knowledge --category decisions ...` (recommendation: yes; user can decline). Audit trail captured for future review. +- **R14:** `/flow-next:sync` (plan-sync agent at `agents/plan-sync.md`) Step 5 reads `STRATEGY.md`. Surfaces drift in `## Strategy drift flagged for review` spec heading parallel to existing "Decision overrides flagged for review". NEVER auto-supersedes — read-only surface only. Track renames replace inline with breadcrumb `` mirroring existing glossary rename pattern. +- **R15:** Foreign-file refusal — `STRATEGY.md` without `generator: flow-next-strategy` frontmatter (or with different generator value) → skill prompts user via `AskUserQuestion` ("migrate / keep / rewrite?"). On "keep" — exits without writing. On "rewrite" — confirms via second prompt before destructive overwrite. Multi-format migration explicitly deferred to v2. +- **R16:** Subdirectory invocation walks up to repo root (`git rev-parse --show-toplevel`); surfaces `Using repo-root STRATEGY.md at ` line before any interview question fires. Does NOT create per-subdirectory STRATEGY.md files. +- **R17:** Ralph-block — `/flow-next:strategy` exits 2 with stderr message `[STRATEGY: user-triggered only — Ralph cannot run /flow-next:strategy]` when `FLOW_RALPH=1` or `REVIEW_RECEIPT_PATH` is set. Mirrors `/flow-next:prospect` precedent. Autonomous loops have no business deciding repo strategy. +- **R18:** Codex mirror generated by `scripts/sync-codex.sh` validates green. `REQUIRED_OPENAI_YAML_SKILLS` array (lines 537-552) includes `flow-next-strategy`. `generate_openai_yaml` call added in workflow blue group `#3B82F6` (after the existing `flow-next-capture` call). After running sync, `plugins/flow-next/codex/skills/flow-next-strategy/` exists with rewritten tool names (`request_user_input` not `AskUserQuestion`); `plugins/flow-next/codex/agents/openai.yaml` updated. +- **R19:** Forbidden-vocabulary guard extension. Tier 1 jargon only (synergy / pivot / disrupt / thought-leadership / best-in-class / world-class / 10x — Rumelt's "fluff" hallmarks). NEW guard block in `ci_test.sh` (separate from R17 DDD-vocab guard at section 5c — comment must specify "this is the strategy-doc fluff guard, not R17"). Block scopes: `plugins/flow-next/skills/flow-next-strategy/` and `plugins/flow-next/scripts/flowctl.py` (cmd_strategy_*) and `plugins/flow-next/commands/flow-next/strategy.md`. Mirrored in `sync-codex.sh` validation block. References file `references/interview.md` excluded from guard (must describe anti-patterns to push back on them — same exemption pattern as glossary references). +- **R20:** Adding-a-new-user-facing-skill checklist (CLAUDE.md ~line 280) followed: canonical `SKILL.md` + `references/`, slash command, sync-codex `generate_openai_yaml` call, `REQUIRED_OPENAI_YAML_SKILLS` update, doc updates in `CLAUDE.md` + `plugins/flow-next/README.md` + `.flow/usage.md` + `~/work/mickel.tech/app/apps/flow-next/page.tsx`, `CHANGELOG.md` entry under `[flow-next 0.40.0]`. Version bump via `./scripts/bump.sh minor flow-next` updates 3 manifests; `./scripts/sync-codex.sh` regenerates Codex mirror; counts updated in 3 plugin.json descriptions. +- **R21:** Smoke test `plugins/flow-next/scripts/strategy_smoke_test.sh` exercises 12 cases T1-T12: (T1) first-run create-from-scratch; (T2) targeted section re-run preserves rest; (T3) subdir invocation walks up; (T4) husk file detected via `sections_filled == 0`; (T5) foreign-file refusal (no `generator` sentinel); (T6) mid-flow abandonment + resume; (T7) forbidden-vocab pushback; (T8) strategy-glossary conflict surfaces in interview spec; (T9) capture `--override-strategy` writes decision record; (T10) prospect grounding emits verbatim approach + tracks; (T11) plan-sync drift surfacing read-only; (T12) Ralph-block exit-2. Refuses to run from main plugin repo (per `glossary_smoke_test.sh:60-63` precedent). `KEEP_TEST_DIR=1` env opt-in. `trash` cleanup with `rm` fallback. Target <30s runtime. +- **R22:** STRATEGY.md survives a wipe of `.flow/` — repo-root invariant (R18 from glossary epic, mirror it explicitly in the skill prose). Project's strategy belongs to the project, not flow-next. +- **R23:** Last-section deletion leaves a husk — file never deleted; H1 + frontmatter remain (mirrors `render_glossary_file` rule at `flowctl.py:344`). `cmd_strategy_*` use shared `atomic_write` helper (already used by glossary subcommands at `flowctl.py:8669, 8796`). + +## Early proof point + +Task `fn-39-project-strategy-strategymd-anchor.1` (flowctl plumbing) validates the core file-shape contract — frontmatter sentinel parse/render round-trip, husk detection, atomic write, single-root walk. If the JSON-shape decisions don't hold up under round-trip testing, the whole multi-skill integration needs reconsideration before tasks 2-6 fire. Tasks 2-5 all consume `flowctl strategy status/read/list` output; task 1 freezes that contract. + +## Requirement coverage + +| Req | Description | Task(s) | Gap justification | +|-----|-------------|---------|-------------------| +| R1 | Repo-root file location + frontmatter shape | fn-39-project-strategy-strategymd-anchor.1, fn-39-project-strategy-strategymd-anchor.2 | — | +| R2 | Section structure locked, drop CE Marketing | fn-39-project-strategy-strategymd-anchor.2 | — | +| R3 | Bare AskUserQuestion + free-form substance / single-select routing | fn-39-project-strategy-strategymd-anchor.2 | — | +| R4 | Re-run section preservation + atomic per-section writes | fn-39-project-strategy-strategymd-anchor.1, fn-39-project-strategy-strategymd-anchor.2 | — | +| R5 | Pushback discipline + references/interview.md non-optional load | fn-39-project-strategy-strategymd-anchor.2 | — | +| R6 | flowctl strategy status JSON shape + husk semantics | fn-39-project-strategy-strategymd-anchor.1 | — | +| R7 | flowctl strategy read single-root walk + section filtering | fn-39-project-strategy-strategymd-anchor.1 | — | +| R8 | flowctl strategy list parallel to glossary list | fn-39-project-strategy-strategymd-anchor.1 | — | +| R9 | Doc-aware autodetect 3-condition + flag matrix | fn-39-project-strategy-strategymd-anchor.4 | — | +| R10 | Prospect Phase 0 grounding + out-of-scope-vs-strategy | fn-39-project-strategy-strategymd-anchor.3 | — | +| R11 | Plan research-scan + Strategy Alignment spec section | fn-39-project-strategy-strategymd-anchor.3 | — | +| R12 | Interview Strategy Conflicts section + ≤1/turn throttle | fn-39-project-strategy-strategymd-anchor.4 | — | +| R13 | Capture [strategy:] tagging + --override-strategy + decision record | fn-39-project-strategy-strategymd-anchor.4 | — | +| R14 | plan-sync Strategy drift flagged for review (read-only) | fn-39-project-strategy-strategymd-anchor.4 | — | +| R15 | Foreign-file refusal via generator sentinel | fn-39-project-strategy-strategymd-anchor.1, fn-39-project-strategy-strategymd-anchor.2 | — | +| R16 | Subdirectory walk-up + UX surfacing | fn-39-project-strategy-strategymd-anchor.1, fn-39-project-strategy-strategymd-anchor.2 | — | +| R17 | Ralph-block exit-2 | fn-39-project-strategy-strategymd-anchor.2 | — | +| R18 | Codex sync + REQUIRED + generate_openai_yaml | fn-39-project-strategy-strategymd-anchor.5 | — | +| R19 | Forbidden-vocab Tier 1 guard (separate from R17 DDD) | fn-39-project-strategy-strategymd-anchor.5 | — | +| R20 | Doc updates + version bump + mickel.tech | fn-39-project-strategy-strategymd-anchor.5 | — | +| R21 | strategy_smoke_test.sh T1-T12 | fn-39-project-strategy-strategymd-anchor.6 | — | +| R22 | Survives `.flow/` wipe (R18 invariant mirror) | fn-39-project-strategy-strategymd-anchor.1, fn-39-project-strategy-strategymd-anchor.2 | — | +| R23 | Last-section delete leaves husk | fn-39-project-strategy-strategymd-anchor.1 | — | + +## Decision context + +- **Why repo-root STRATEGY.md, not `.flow/strategy.md`** — survives a wipe of `.flow/`, peer of README/CHANGELOG/GLOSSARY, generic markdown tooling reads it. R18 invariant established by 0.39.0 glossary epic; same rationale applies. Project's strategy belongs to the project, not flow-next. +- **Why single-root, NOT nearest-ancestor walk like glossary** — strategy is repo-wide by Rumelt's definition (one diagnosis, one guiding policy, coherent action). Multiple cascading STRATEGY.md files re-introduce the "is for everyone, is for no one" problem the skill exists to prevent. Glossary cascades because vocabulary is local; strategy is global. +- **Why this section structure** — derived from Richard Rumelt's *Good Strategy Bad Strategy* kernel (diagnosis / guiding policy / coherent action). Target problem maps to diagnosis, Our approach to guiding policy, Tracks to coherent action; Persona + Key metrics extend the kernel for repo-doc utility. +- **Why drop CE's Marketing section** — over-rotated for OSS-tools repos (the marketplace manifest IS the distribution surface). Adding sections has cost; CE's principle 3 ("Short is a feature") supports the cut. +- **Why no atomic-write `flowctl strategy add` plumbing** — strategy is too prose-heavy for atomic field-set CLI. The skill running the interview IS the LLM that should write the file (per CLAUDE.md "agentic vs deterministic" architecture rule). Atomic CLI plumbing fits term-list / decision-record / memory shape but not prose-heavy strategy shape. +- **Why bare `AskUserQuestion` in canonical (no inline cross-platform tables)** — flow-next CLAUDE.md explicitly forbids inline multi-platform tool tables ("they pollute the agent's context with abstraction noise"). Canonical files use Claude-native; sync-codex.sh rewrites to Codex form. +- **Why lead-with-recommendation only on routing questions, NOT substance** — substance questions (problem / approach / persona / metrics / tracks) must capture the user's own language. flow-next 0.39.0's lead-with-recommendation pattern primes the user out of their own framing; for strategy that's specifically what we don't want. Apply to routing only. +- **Why Tier 1 forbidden-vocab only (drop "leverage" verb)** — Rumelt's source uses "leverage" as a noun in *Good Strategy Bad Strategy* (the reference itself). False-positive risk too high for `references/learn-more.md` prose. Tier 1 list (synergy / pivot / disrupt / thought-leadership / best-in-class / world-class / 10x) is unambiguous. +- **Why separate guard block in `ci_test.sh` (not extending R17 DDD pattern)** — R17 comment specifically says "DDD vocabulary guard"; mixing concerns muddies the failure message and makes future maintenance harder. Each grep block has one purpose. +- **Why `--override-strategy` prompts decision record (not auto-write, not bypass)** — the override is exactly the kind of "load-bearing architectural choice" the decisions track was added for. Strong recommendation toward yes; user retains override authority. Aligns with three-criteria gate already in `/flow-next:interview` 0.39.0. +- **Why foreign-file refusal in v1 (no migration)** — CE-format and hand-written STRATEGY.md files have ambiguous section mappings. Multi-format migration is a v2 problem; v1 ships the sentinel + refusal pattern, documents the limitation, lets early adopters delete-or-rename to bootstrap. +- **Why per-section atomic writes (no draft state)** — race-window avoidance; mid-flow abandonment leaves a partially-populated file readable on disk; resume = "read existing sections, ask which empty/stale to fill next." Mirrors `flowctl epic set-plan` whole-file replace, but per-section. No `.flow/strategy/draft-*.md` state files. +- **Naming `/flow-next:strategy` writing `STRATEGY.md`** — mirrors GLOSSARY.md repo-root convention; well-known-filename discoverability. STRATEGY.md is not on the kmindi canonical OSS-root-files list, but the ALL_CAPS pattern is generic-tooling-readable and `glow` / `mdcat` / GitHub render handle it. + +## Open questions + +None for v1 — all priority architectural decisions resolved during planning. Documented for future revisits: +- Multi-strategy cascade for monorepos with genuinely independent subprojects (deferred — current rule: single root, surface "this monorepo has no per-project strategy split") +- Foreign-format / hand-written migration (deferred to v2 — v1 refuses with sentinel) +- Track ordering significance (priority vs alphabetical vs user-chosen — defaults to user-chosen; revisit if prospect rejection prose needs to cite "highest-priority track") +- Reverse loop ("you've shipped 5 epics in track X, suggest renaming") — out of scope for v1; strategy drives downstream, not the other way around + +## References + +- Rumelt strategy kernel: *Good Strategy Bad Strategy* — diagnosis / guiding policy / coherent action +- Glossary plumbing model: `plugins/flow-next/scripts/flowctl.py:63-405,8560-8812,16547-16622` +- Doc-aware autodetect precedent: `plugins/flow-next/skills/flow-next-interview/SKILL.md:81-106,192-318` +- plan-sync read-only-surface convention: `plugins/flow-next/agents/plan-sync.md:95-110,200-245` +- Smoke-test template: `plugins/flow-next/scripts/glossary_smoke_test.sh` (784 lines, 25 cases) +- Adding-a-new-user-facing-skill checklist: `CLAUDE.md` ~line 280 +- R18 (survives uninstall) precedent: `CLAUDE.md` "Project glossary (v0.39.0+)" subsection +- Rumelt kernel: https://www.alexmurrell.co.uk/summaries/richard-rumelt-good-strategy-bad-strategy +- Repo-root file conventions: https://github.com/kmindi/special-files-in-repository-root +- Practice scout findings: arxiv 2507.02858 (RE 2025) interviewer-mistake taxonomy; matklad ARCHITECTURE.md cadence guidance diff --git a/.flow/tasks/fn-33-flow-nextprospect-upstream-of-plan-idea.7.md b/.flow/tasks/fn-33-flow-nextprospect-upstream-of-plan-idea.7.md index b3f8038d..015c997d 100644 --- a/.flow/tasks/fn-33-flow-nextprospect-upstream-of-plan-idea.7.md +++ b/.flow/tasks/fn-33-flow-nextprospect-upstream-of-plan-idea.7.md @@ -79,7 +79,7 @@ Mechanical rollup following the pattern used by fn-29.7, fn-30.7, fn-31.7, fn-32 - Website repo is separate from the plugin repo — commit + push to `~/work/mickel.tech` in a distinct commit. - `scripts/sync-codex.sh` must run AFTER skill files are authored — any drift flags an incomplete earlier task. -- Don't cross-reference compound-engineering or any external plugin in the final docs; prose should frame prospect in terms of flow-next's own lifecycle. +- Don't cross-reference any external plugin in the final docs; prose should frame prospect in terms of flow-next's own lifecycle. ## Acceptance @@ -91,7 +91,7 @@ Mechanical rollup following the pattern used by fn-29.7, fn-30.7, fn-31.7, fn-32 - [ ] Website `page.tsx` updated: version, commands, coreFeatures/optInFeatures card, FAQ; committed + pushed. - [ ] `scripts/sync-codex.sh` regenerates Codex mirror; second run is no-op (zero drift). - [ ] Both smoke suites (`smoke_test.sh`, `prospect_smoke_test.sh`) + unit tests pass. -- [ ] No references to compound-engineering or other plugins in shipped docs. +- [ ] No references to external plugins in shipped docs. ## Done summary Bumped flow-next 0.35.1 → 0.36.0 (minor) for the new `/flow-next:prospect` skill and `flowctl prospect` subcommands. Updated CHANGELOG, READMEs, CLAUDE.md, .flow/usage.md, plugin manifests (17→18 skills, 12→13 commands), and the website page. Regenerated Codex mirror (sync is idempotent — second run is no-op). All smoke suites + unit tests + website lint/typecheck pass. diff --git a/.flow/tasks/fn-34-flow-nextaudit-agent-native-memory.1.md b/.flow/tasks/fn-34-flow-nextaudit-agent-native-memory.1.md index 80078a2e..72eec2ad 100644 --- a/.flow/tasks/fn-34-flow-nextaudit-agent-native-memory.1.md +++ b/.flow/tasks/fn-34-flow-nextaudit-agent-native-memory.1.md @@ -22,7 +22,7 @@ This is the **Phase-0 task**: lays down the skill structure and workflow. Tasks Mirror `plugins/flow-next/skills/flow-next-prospect/SKILL.md` (most recent reference of an interactive skill) for structure. Frontmatter required fields: `name: flow-next-audit`, `description: `. Body sections: -1. **Mode Detection** — parse `$ARGUMENTS` for `mode:autofix` token. Strip and use remainder as scope hint. Same pattern as upstream `ce-compound-refresh:10-27`. +1. **Mode Detection** — parse `$ARGUMENTS` for `mode:autofix` token. Strip and use remainder as scope hint. 2. **Interaction Principles** — interactive uses blocking-question tool (`AskUserQuestion` Claude / `request_user_input` Codex / `ask_user` Droid); fallback to numbered options when tool unavailable; one question at a time; lead with recommendation. 3. **Reference to workflow.md** — main loop lives there. 4. **Reference to phases.md** — outcomes lookup. @@ -90,7 +90,7 @@ Document the 6 phases. Each phase ends with a "Done when" criterion so the agent ### `phases.md` shape -The 5 outcomes lookup. Each section: meaning + when to use + when NOT to use + action steps + edge cases. Lift heavily from upstream `ce-compound-refresh:285-372` with naming swap (`learning` → `memory entry`, `docs/solutions/` → `.flow/memory/`). +The 5 outcomes lookup. Each section: meaning + when to use + when NOT to use + action steps + edge cases. Adapt the staleness-audit pattern (Keep / Update / Consolidate / Replace / Delete classification) to the categorized memory schema (`learning` → `memory entry`, `docs/solutions/` → `.flow/memory/`). Specific calibration for memory schema: - **Update**: edit frontmatter `module` field, edit body code refs, fix related-doc paths. Preserve all other frontmatter (`title`, `date`, `track`, `category`, `tags`, etc). @@ -115,7 +115,6 @@ Investigation subagents are **read-only** (no Edit/Write/Bash), return structure ## Investigation targets **Required:** -- `/tmp/compound-engineering-plugin/plugins/compound-engineering/skills/ce-compound-refresh/SKILL.md` — upstream reference (the working pattern we're adapting) - `plugins/flow-next/skills/flow-next-prospect/SKILL.md` — most recent in-repo skill reference for shape (prospect ships frontmatter, mode handling, blocking-question tool call) - `plugins/flow-next/skills/flow-next-prospect/workflow.md` — workflow.md shape reference - `plugins/flow-next/commands/flow-next/prospect.md` — slash command pass-through pattern @@ -124,7 +123,7 @@ Investigation subagents are **read-only** (no Edit/Write/Bash), return structure - CLAUDE.md memory-system block at lines 62-77 (schema reference) **Optional:** -- `/tmp/compound-engineering-plugin/plugins/compound-engineering/skills/ce-compound-refresh/references/` — extra references upstream uses (for inspiration only; we don't need separate references file unless workflow gets complex) +- `plugins/flow-next/skills/flow-next-audit/references/` (if shipped) — flow-next-specific references that this task may seed; not required for the initial pass. ## Key context diff --git a/.flow/tasks/fn-34-flow-nextaudit-agent-native-memory.3.md b/.flow/tasks/fn-34-flow-nextaudit-agent-native-memory.3.md index 2a66d2b4..8a9487be 100644 --- a/.flow/tasks/fn-34-flow-nextaudit-agent-native-memory.3.md +++ b/.flow/tasks/fn-34-flow-nextaudit-agent-native-memory.3.md @@ -100,7 +100,7 @@ Target runtime: <30s end-to-end. ## [flow-next 0.37.0] - 2026-04-XX ### Added -- `/flow-next:audit` — agent-native skill that walks `.flow/memory/`, reviews each entry against the current codebase, and decides per entry whether to Keep / Update / Consolidate / Replace / Delete. Interactive mode (default) asks via blocking-question tool; autofix mode (`mode:autofix` argument) applies unambiguous actions and marks ambiguous entries as stale. Inspired by upstream `ce-compound-refresh` skill — adapted for the categorized memory schema shipped in 0.33.0. +- `/flow-next:audit` — agent-native skill that walks `.flow/memory/`, reviews each entry against the current codebase, and decides per entry whether to Keep / Update / Consolidate / Replace / Delete. Interactive mode (default) asks via blocking-question tool; autofix mode (`mode:autofix` argument) applies unambiguous actions and marks ambiguous entries as stale. Adapted to the categorized memory schema shipped in 0.33.0. - `flowctl memory mark-stale --reason "..."` — sets `status: stale`, stamps `last_audited`, records `audit_notes`. Atomic via existing `write_memory_entry`. Body never modified. - `flowctl memory mark-fresh ` — clears stale flag, stamps `last_audited`. Idempotent on already-active entries. - `flowctl memory search` accepts `--status active|stale|all` (default `active`), mirroring `memory list`. Stale entries flagged by audit are excluded from default search results. diff --git a/.flow/tasks/fn-35-flow-nextmemory-migrate-agent-native.1.md b/.flow/tasks/fn-35-flow-nextmemory-migrate-agent-native.1.md index 97b75f80..0fc235ad 100644 --- a/.flow/tasks/fn-35-flow-nextmemory-migrate-agent-native.1.md +++ b/.flow/tasks/fn-35-flow-nextmemory-migrate-agent-native.1.md @@ -112,7 +112,7 @@ Phase 4 writes `.flow/memory/_migrated/.gitignore` with content `*` on first cle **Optional:** - `plugins/flow-next/skills/flow-next-prospect/SKILL.md` — additional skill reference for shape -- `/tmp/compound-engineering-plugin/plugins/compound-engineering/skills/ce-compound-refresh/SKILL.md` — upstream reference (different feature but similar agent-native pattern) +- `plugins/flow-next/skills/flow-next-audit/SKILL.md` — sibling agent-native skill (shares the same "host agent reads + judges + writes" architecture) ## Key context diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.1.json b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.1.json new file mode 100644 index 00000000..458d3b68 --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.1.json @@ -0,0 +1,14 @@ +{ + "assignee": null, + "claim_note": "", + "claimed_at": null, + "created_at": "2026-05-01T16:38:07.509453Z", + "depends_on": [], + "epic": "fn-39-project-strategy-strategymd-anchor", + "id": "fn-39-project-strategy-strategymd-anchor.1", + "priority": null, + "spec_path": ".flow/tasks/fn-39-project-strategy-strategymd-anchor.1.md", + "status": "todo", + "title": "flowctl strategy plumbing (status/read/list + helpers)", + "updated_at": "2026-05-01T16:38:07.647263Z" +} diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.1.md b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.1.md new file mode 100644 index 00000000..04b08244 --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.1.md @@ -0,0 +1,82 @@ +# fn-39-project-strategy-strategymd-anchor.1 flowctl strategy plumbing (status/read/list + helpers) + +## Description +Implement `flowctl strategy` Python plumbing in `plugins/flow-next/scripts/flowctl.py`: constants, parse/render helpers, single-root walk, three subcommands (`status`, `read`, `list`), argparse registration. Mirror the glossary plumbing shape exactly — same constants pattern, same parse/render rhythm, same atomic-write contract. + +**Size:** M +**Files:** +- `plugins/flow-next/scripts/flowctl.py` (additive — new constants, helpers, `cmd_strategy_*` handlers, argparse subparser) + +This is the foundation task. Tasks 2-6 all consume the JSON shapes that ship here. Frontmatter sentinel parse round-trip + husk detection + atomic write + single-root walk all freeze in this task. + +## Approach + +- Add module constants near `GLOSSARY_FILE` (`flowctl.py:63`): + - `STRATEGY_FILE = "STRATEGY.md"` + - `STRATEGY_GENERATOR = "flow-next-strategy"` + - `STRATEGY_REQUIRED_SECTIONS = ("Target problem", "Our approach", "Who it's for", "Key metrics", "Tracks")` + - `STRATEGY_OPTIONAL_SECTIONS = ("Milestones", "Not working on")` + - `STRATEGY_HUSK_SENTINEL` (constant for the placeholder body string used to detect "section exists but empty") +- Add helpers near glossary helpers (`flowctl.py:148-405`): + - `find_strategy_file(start_path)` — single-root walk: walks UP from `start_path` to first `STRATEGY.md`, capped at repo root via `get_repo_root()`. Different from `find_nearest_glossary` — strategy is repo-wide, NOT nearest-ancestor. Returns `(path, None)` if found, `(None, repo_root)` if absent. + - `parse_strategy_file(text)` — returns `{name, last_updated, generator, target_problem, approach, personas, metrics, tracks, milestones, not_working_on}`. Reuse `_glossary_strip_fenced_code` for fence masking (`flowctl.py:266`). Use H2 heading regex similar to glossary's. Frontmatter parsed via existing YAML helper. + - `render_strategy_file(parsed)` — emits H1 `# Strategy` + frontmatter + H2 sections in locked order. Optional sections omitted if empty. Always emits H1 + frontmatter even when all sections empty (husk). + - `validate_strategy_frontmatter(fm)` — requires `name`, `last_updated`, `generator: flow-next-strategy`. Refuses unknown keys (single-source-of-truth invariant). + - `_strategy_section_filled(body)` — returns True if section body has at least one non-whitespace, non-comment prose line. False if body is empty, only HTML comments, or only the husk sentinel `_Not currently tracking._`. +- Add subcommand handlers near `cmd_glossary_*` (`flowctl.py:8563-8812`): + - `cmd_strategy_status(args)` — calls `find_strategy_file(cwd)`. Returns `{exists: bool, husk: bool, sections_filled: int, total_sections: int, last_updated: str|null, file_path: str|null}`. Husk = file exists AND `sections_filled == 0`. `total_sections` counts populated optional + all required = 5..7. + - `cmd_strategy_read(args)` — calls `find_strategy_file(cwd)`. If `--section ` provided, filter to one block (case-insensitive section name match via existing helpers). Returns full parse otherwise. Refuses `--section` for sections not in the locked list. + - `cmd_strategy_list(args)` — single-root, returns `{groups: [{path, sections, count}], file_count, total_sections}`. For v1: `groups` has 0 or 1 element. Kept for parallel symmetry with `cmd_glossary_list`. + - All three accept `--json` and emit via existing `json_output` helper. +- Argparse registration near glossary at `flowctl.py:16547-16622`: + - Parent `strategy` parser → required `strategy_cmd` subparser → 3 children (`status`, `read`, `list`) + - All children accept `--json` + - `read` accepts `--section ` +- Foreign-file detection: `cmd_strategy_status` returns `{generator: , generator_match: bool}`. Skill (Task 2) uses this to gate the migrate/keep/rewrite question. +- Atomic write contract: NO write subcommand in flowctl. Skill writes the file via host agent's `Write` tool. flowctl is read-only for strategy. (Mirrors `/flow-next:audit` pattern — flowctl marks stale, skill does the editing.) + +## Investigation targets + +**Required:** +- `plugins/flow-next/scripts/flowctl.py:63-64` — `GLOSSARY_FILE` / `GLOSSARY_WALK_MAX_DEPTH` constants pattern +- `plugins/flow-next/scripts/flowctl.py:148-208` — `find_nearest_glossary` walk algorithm (CONTRAST — strategy uses single-root walk, not nearest-ancestor) +- `plugins/flow-next/scripts/flowctl.py:260-405` — glossary parse/render/validate helpers (model for strategy equivalents) +- `plugins/flow-next/scripts/flowctl.py:8563-8812` — `cmd_glossary_*` subcommand handlers (direct model for `cmd_strategy_*`) +- `plugins/flow-next/scripts/flowctl.py:16547-16622` — glossary argparse registration (direct model) + +**Optional:** +- `plugins/flow-next/scripts/flowctl.py:266-279` — `_glossary_strip_fenced_code` (reuse for strategy parse fence masking) +- `plugins/flow-next/scripts/flowctl.py:401-405` — `_glossary_term_matches` (case-insensitive compare contract — reuse if section name fuzzy match needed) +- `plugins/flow-next/skills/flow-next-strategy/references/strategy-template.md` — section structure reference (Rumelt-derived: 5 required + 2 optional, no Marketing) + +## Key context + +- Strategy file is single-root, NOT nearest-ancestor like glossary. Strategy is repo-wide by Rumelt's definition; no monorepo subproject cascade. +- Frontmatter has exactly 3 keys: `name`, `last_updated`, `generator: flow-next-strategy`. Refuse unknown keys to keep audit story simple. +- No `add` / `edit` / `remove` subcommands. Skill writes the file. This task is read-only plumbing. +- Husk semantics align with glossary precedent: `sections_filled == 0` means file exists but no signal. Downstream autodetect uses count-based check, not file-existence. +- `cmd_strategy_*` use shared `atomic_write` helper at `flowctl.py:8669,8796` — even though the skill does the writing, downstream may want to write via flowctl in v2; reserve the helper for that. +## Acceptance +- [ ] Module constants `STRATEGY_FILE`, `STRATEGY_GENERATOR`, `STRATEGY_REQUIRED_SECTIONS`, `STRATEGY_OPTIONAL_SECTIONS` defined near `GLOSSARY_FILE` (`flowctl.py:63`); `STRATEGY_REQUIRED_SECTIONS` matches the locked Rumelt-derived template (5 sections); `STRATEGY_OPTIONAL_SECTIONS` is `("Milestones", "Not working on")` — no Marketing. +- [ ] `find_strategy_file(start)` walks UP from `start` to first `STRATEGY.md`, capped at `get_repo_root()`. Single-root semantics — does NOT do nearest-ancestor walk. Returns `(path, None)` if found, `(None, repo_root)` if absent at repo root. +- [ ] `parse_strategy_file(text)` returns dict with keys `{path, name, last_updated, generator, target_problem, approach, personas, metrics, tracks, milestones, not_working_on}`. Fenced code blocks masked during parse via `_glossary_strip_fenced_code`. CRLF normalized. +- [ ] `render_strategy_file(parsed)` round-trips: `parse → render → parse` produces byte-identical output for all populated sections; whitespace within section bodies preserved. +- [ ] `validate_strategy_frontmatter(fm)` requires `name`, `last_updated`, `generator: "flow-next-strategy"`. Refuses unknown frontmatter keys (returns error / non-zero RC); refuses different generator value. +- [ ] `_strategy_section_filled(body)` returns False for empty body, body containing only HTML comments, or body containing only the husk sentinel `_Not currently tracking._`. Returns True for any prose paragraph. +- [ ] `flowctl strategy status [--json]` returns JSON `{exists, husk, sections_filled, total_sections, last_updated, file_path, generator, generator_match}`. Husk = `exists AND sections_filled == 0`. `generator_match` is true only when frontmatter generator value equals `flow-next-strategy`. +- [ ] `flowctl strategy read [--section ] [--json]` returns full parsed dict. With `--section`, returns only the named section body. Section name matched case-insensitively against required+optional list; refuses unknown section names. Walks single-root via `find_strategy_file`. +- [ ] `flowctl strategy list [--json]` returns `{groups: [{path, sections, count}], file_count, total_sections}` with degenerate single-element group for v1. `count` is the per-file populated section count. +- [ ] Argparse registration: parent `strategy` parser registered next to glossary parser; required `strategy_cmd` subparser routes to status/read/list; all children accept `--json`. +- [ ] All three subcommands work from a repo subdirectory — walk reaches repo-root `STRATEGY.md` correctly. +- [ ] Husk file (`# Strategy\n` + frontmatter + zero sections) correctly classified: `flowctl strategy status --json | jq '.husk == true and .sections_filled == 0'` returns true. +- [ ] Last-section-deletion-leaves-husk invariant: removing all populated sections via `Write` of a frontmatter-only file → `flowctl strategy status` reports `husk: true`; subsequent `read` succeeds and returns empty bodies. +- [ ] `flowctl strategy read --section invalid-name --json` exits non-zero with structured error. +- [ ] `flowctl strategy status --json` outside any repo (no `git rev-parse` parent) returns `{exists: false, file_path: null}` cleanly without traceback. +- [ ] Round-trip preserves `last_updated` ISO date format YYYY-MM-DD; rejects non-ISO via `validate_strategy_frontmatter`. +- [ ] No `flowctl strategy add` / `edit` / `remove` / `section-set` subcommands exist — only status / read / list. +## Done summary +Implemented `flowctl strategy` read-only Python plumbing in `plugins/flow-next/scripts/flowctl.py`: STRATEGY_FILE/GENERATOR/sections/sentinel constants, `find_strategy_file` (single-root walk resolved relative to `start` not process cwd), `parse_strategy_file` / `render_strategy_file` / `validate_strategy_frontmatter` / `_strategy_section_filled` helpers, and three subcommands (`status`, `read [--section]`, `list`) registered alongside glossary in argparse. Verified end-to-end with 64 inline Python smoke checks covering round-trip, husk detection, validation rejection cases, single-root walk from subdirs, foreign-file detection, and outside-a-repo behavior; existing 210 smoke tests (flowctl + glossary) still pass. +## Evidence +- Commits: 42c6e3b1f845dd64a7a73645946cae601782f0fe +- Tests: python3 inline-smoke (64 checks: parse round-trip, husk detection, validate rejections, _strategy_section_filled edges, single-root walk, outside-repo, foreign-file, CLI subprocess subdir, last-section husk, PyYAML date), plugins/flow-next/scripts/smoke_test.sh (130/130 pass), plugins/flow-next/scripts/glossary_smoke_test.sh (80/80 pass) +- PRs: \ No newline at end of file diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.2.json b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.2.json new file mode 100644 index 00000000..35653584 --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.2.json @@ -0,0 +1,16 @@ +{ + "assignee": null, + "claim_note": "", + "claimed_at": null, + "created_at": "2026-05-01T16:39:19.460815Z", + "depends_on": [ + "fn-39-project-strategy-strategymd-anchor.1" + ], + "epic": "fn-39-project-strategy-strategymd-anchor", + "id": "fn-39-project-strategy-strategymd-anchor.2", + "priority": null, + "spec_path": ".flow/tasks/fn-39-project-strategy-strategymd-anchor.2.md", + "status": "todo", + "title": "flow-next-strategy skill + slash command + references", + "updated_at": "2026-05-01T16:39:19.597802Z" +} diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.2.md b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.2.md new file mode 100644 index 00000000..2c04e628 --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.2.md @@ -0,0 +1,92 @@ +# fn-39-project-strategy-strategymd-anchor.2 flow-next-strategy skill + slash command + references + +## Description +Create the `/flow-next:strategy` skill: canonical `SKILL.md`, two reference files (`interview.md`, `strategy-template.md`), and the slash-command file. The skill is the editor — runs the interview via host agent's `AskUserQuestion`, applies pushback rules, writes `STRATEGY.md` via `Write` per-section atomically. + +**Size:** M +**Files:** +- `plugins/flow-next/skills/flow-next-strategy/SKILL.md` (new) +- `plugins/flow-next/skills/flow-next-strategy/references/interview.md` (new) +- `plugins/flow-next/skills/flow-next-strategy/references/strategy-template.md` (new) +- `plugins/flow-next/commands/flow-next/strategy.md` (new) + +Depends on Task 1 (consumes `flowctl strategy status` JSON shape). + +## Approach + +Build the skill around the locked Rumelt-derived template (5 required + 2 optional sections, no Marketing) with these flow-next conventions: + +- **Bare `AskUserQuestion`** in canonical `SKILL.md` — NO inline cross-platform tables (multi-platform listings naming `AskUserQuestion / request_user_input / ask_user / pi-ask-user` are forbidden by flow-next CLAUDE.md). Optional parenthetical breadcrumb `(sync-codex.sh rewrites this to request_user_input in the Codex mirror)` is fine for maintainer clarity — sync-codex strips it. +- **No `Marketing` optional section** in `strategy-template.md` — over-rotated for OSS-tools repo. Keep `Milestones` + `Not working on` as the two optional sections. +- **Lead-with-recommendation only on routing questions** — the substance questions (Target problem / Approach / Persona / Metrics / Tracks) stay free-form so the user's own language is preserved. The "which section to revisit?" question on re-run uses lead-with-recommendation pattern. +- **Foreign-file refusal** — Phase 0 calls `flowctl strategy status --json`; if `exists: true AND generator_match: false`, fire `AskUserQuestion` ("migrate / keep / rewrite?"). Migrate = exit (defer to v2). Keep = exit. Rewrite = second confirmation prompt then proceed. +- **Per-section atomic writes** — each completed section's body lands on disk via `Write` before the next prompt fires. Re-runs read existing sections via `flowctl strategy status` and ask which empty/stale to fill next. +- **Subdirectory walk-up** — Phase 0 detects via `flowctl strategy status` (which uses `find_strategy_file`); surfaces "Using repo-root STRATEGY.md at ``" line in chat before any question fires. +- **Ralph block** — Phase 0 first check: if `FLOW_RALPH=1` or `REVIEW_RECEIPT_PATH` is set, exit 2 with stderr `[STRATEGY: user-triggered only — Ralph cannot run /flow-next:strategy]` (mirrors `/flow-next:prospect` precedent). + +`SKILL.md` structure: +- Frontmatter: `name: flow-next-strategy`, `description`, `user-invocable: false`, `allowed-tools: AskUserQuestion, Read, Write, Bash` +- Phase 0: route by file state via `flowctl strategy status` (Ralph block check first; foreign-file check second; husk vs exists vs absent routing third) +- Phase 1: first-run interview (5 required sections in Rumelt-kernel order, then optional sections gated by `AskUserQuestion`) +- Phase 2: update run (which-section-to-revisit, preserve untouched sections, bump `last_updated`) +- Phase 3: downstream handoff (suggest `/flow-next:prospect` or `/flow-next:plan` as next step if nothing has run yet on this repo) + +`references/interview.md` — pushback rules and anti-pattern taxonomy: +- Five Overall Rules (ask-don't-prescribe, push back once-or-twice, quote the user's own words back, cap each answer at 1-3 sentences, do not leak anti-pattern names to user) +- Per-section structure: opening question → strong-answer signature → named anti-patterns + sharper follow-up → 2-round cap → capture rule +- Anti-patterns per section (Rumelt's bad-strategy hallmarks: goal-stated-as-problem / fluff/values / vanity metrics / feature-list-as-track / etc.) + +`references/strategy-template.md` — literal markdown skeleton: +- Frontmatter with `name`, `last_updated`, `generator: flow-next-strategy` +- 5 required H2 sections in locked order (Rumelt kernel + persona + metrics), with placeholder bodies showing the right shape +- 2 optional sections (Milestones, Not working on) — Marketing deliberately excluded +- Post-write checklist: metrics 3-5, tracks 2-4, no section >4 sentences except Tracks, Target problem ↔ Approach connected + +`commands/flow-next/strategy.md` — 14-line minimal forwarder, mirror `commands/flow-next/audit.md` shape. Pass `$ARGUMENTS` verbatim to skill. Argument hint: `[optional: section to revisit, e.g. 'metrics' or 'approach']`. + +## Investigation targets + +**Required:** +- `plugins/flow-next/skills/flow-next-audit/SKILL.md` — canonical agent-native skill template, blocking-question canonical phrasing (model for SKILL.md frontmatter + tool-name conventions) +- `plugins/flow-next/skills/flow-next-prospect/SKILL.md` — Ralph-block pattern (search for `FLOW_RALPH` / `REVIEW_RECEIPT_PATH`) +- `plugins/flow-next/commands/flow-next/audit.md` — slash-command 14-line forwarder shape +- Richard Rumelt — *Good Strategy Bad Strategy* — kernel structure (diagnosis / guiding policy / coherent action) and the "fluff / mistaking goals for strategy / bad strategic objectives" hallmark labels used in pushback rules + +**Optional:** +- `plugins/flow-next/skills/flow-next-interview/SKILL.md:141-162` — lead-with-recommendation pattern (apply to routing questions only) +- `plugins/flow-next/skills/flow-next-capture/workflow.md` — mandatory read-back before write convention + +## Key context + +- Canonical skill files use Claude-native tool names: `AskUserQuestion`, `Read`, `Write`. NO inline cross-platform tables. sync-codex.sh rewrites for Codex. +- Substance questions stay free-form (ask, don't prescribe). Routing questions get lead-with-recommendation. +- Anti-pattern labels (vanity / fluff / feature-list) used internally to formulate questions, NEVER shown to user. +- 2-round pushback cap per section. After round 2: capture user's words verbatim + add `` HTML comment. +- Foreign-file detection: `generator_match: false` from `flowctl strategy status` → blocking question. v1 refusal stance, no migration. +- Ralph block fires before any other Phase 0 logic. +## Acceptance +- [ ] `plugins/flow-next/skills/flow-next-strategy/SKILL.md` created with frontmatter (`name: flow-next-strategy`, `description`, `user-invocable: false`, `allowed-tools: AskUserQuestion, Read, Write, Bash`); Phase 0 (route + Ralph block + foreign-file check), Phase 1 (first-run interview), Phase 2 (section-revisit update), Phase 3 (downstream handoff). +- [ ] Canonical SKILL.md uses bare `AskUserQuestion` — NO inline cross-platform table (no `request_user_input` / `ask_user` / `pi-ask-user` listings in canonical prose). Optional parenthetical maintainer breadcrumb is fine. +- [ ] `references/interview.md` (~150 lines) loaded non-optionally — improvising pushback from memory produces passive transcription. Five Overall Rules: ask-don't-prescribe, push back once-or-twice, quote the user's own words, cap each answer at 1-3 sentences, do not leak anti-pattern names to user. Per-section pushback rules with named anti-pattern labels (internal-only) for all 5 required sections + 2 optional. +- [ ] `references/strategy-template.md` (~90 lines) — frontmatter (`name`, `last_updated`, `generator: flow-next-strategy`), H1 `# {{product_name}} Strategy`, 5 required H2 sections in locked Rumelt-kernel order, 2 optional sections (Milestones, Not working on). Marketing section explicitly NOT included. +- [ ] `plugins/flow-next/commands/flow-next/strategy.md` created — 14-line forwarder mirroring `commands/flow-next/audit.md` shape. Frontmatter has `argument-hint: [optional: section to revisit, e.g. 'metrics' or 'approach']`. Body says "MUST invoke flow-next-strategy skill, pass $ARGUMENTS verbatim." +- [ ] Phase 0 Ralph-block check fires first: when `FLOW_RALPH=1` OR `REVIEW_RECEIPT_PATH` is set, skill exits 2 with stderr `[STRATEGY: user-triggered only — Ralph cannot run /flow-next:strategy]`. +- [ ] Phase 0 calls `flowctl strategy status --json`. Routes: + - File absent → Phase 1 (first-run interview) + - File exists + `generator_match: false` → foreign-file `AskUserQuestion` (migrate / keep / rewrite); migrate exits "deferred to v2"; keep exits without writing; rewrite confirms via second prompt + - File exists + `generator_match: true` → Phase 2 (section-revisit interview) +- [ ] Phase 0 surfaces "Using repo-root STRATEGY.md at ``" in chat when invoked from a subdirectory (path differs from cwd). +- [ ] Phase 1 runs interview in section order: Target problem → Our approach → Who it's for → Key metrics → Tracks. Optional sections (Milestones, Not working on) gated by `AskUserQuestion` "include this section?" routing question with lead-with-recommendation (`[your-call]` confidence). +- [ ] Substance questions (problem/approach/persona/metrics/tracks) use FREE-FORM responses — no menu options, no recommendation in question body. Routing questions (which section to revisit, include optional section) use single-select with lead-with-recommendation pattern + confidence tier. +- [ ] Per-section atomic writes: each completed section's content written via `Write` before next question fires. `last_updated` bumps on every save. Verify by reading file mid-flow — partial state is on disk. +- [ ] Pushback discipline: 2 rounds maximum per section. After round 2, capture user's words verbatim + append HTML comment `` to section body. Anti-pattern labels (vanity / fluff / feature-list) NEVER appear in question bodies. +- [ ] Phase 2 (re-run on existing flow-next-generated file): asks "which section to revisit?" via `AskUserQuestion` with lead-with-recommendation favoring sections without populated body or with `` markers. Untouched sections preserved byte-identical. +- [ ] Phase 3 (downstream handoff): if no `.flow/` epics or prospects exist, suggest `/flow-next:prospect` as next step. If `.flow/` populated, just note that prospect/plan/interview/capture will pick up the strategy on next invocation. +- [ ] Mandatory read-back before final commit: after all sections answered (first run) or section update (re-run), show full draft in chat via `Read` of the candidate file, offer one round of edits, then commit (mirrors `/flow-next:capture` pattern). +- [ ] Skill follows CLAUDE.md "agentic vs deterministic" rule — host agent runs interview directly, no subprocess to other LLMs, no Python parsing engines (uses `flowctl strategy read/status/list` for atomic reads). +## Done summary +Authored flow-next-strategy skill (canonical SKILL.md + 2 references files + 14-line slash-command forwarder). Section structure derived from Rumelt's strategy kernel (diagnosis / guiding policy / coherent action) extended with persona + metrics. flow-next conventions: Marketing section deliberately excluded, bare AskUserQuestion in canonical, lead-with-recommendation only on routing questions, foreign-file refusal via generator sentinel, Ralph-block exit-2, per-section atomic writes, mandatory read-back. Phase 0 logic validated against Task 1 flowctl strategy status JSON contract via fixture round-trip. +## Evidence +- Commits: 0f50ea96219f44e933ae70cb5e0c6d8f8a883e86 +- Tests: grep -nE 'request_user_input|ask_user|pi-ask-user' canonical files (only allowed parenthetical breadcrumb on SKILL.md line 18), grep -niE 'synergy|pivot|disrupt|thought-leadership|best-in-class|world-class|10x' (PASS — none in canonical), wc -l on 4 files (SKILL.md 266, interview.md 151, strategy-template.md 86, strategy.md 13), fixture round-trip: flowctl strategy status --json against absent / husk / valid / foreign-generator STRATEGY.md (all 8 documented fields present + match Phase 0 routing logic) +- PRs: \ No newline at end of file diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.3.json b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.3.json new file mode 100644 index 00000000..27d8f0ac --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.3.json @@ -0,0 +1,16 @@ +{ + "assignee": null, + "claim_note": "", + "claimed_at": null, + "created_at": "2026-05-01T16:40:15.298670Z", + "depends_on": [ + "fn-39-project-strategy-strategymd-anchor.1" + ], + "epic": "fn-39-project-strategy-strategymd-anchor", + "id": "fn-39-project-strategy-strategymd-anchor.3", + "priority": null, + "spec_path": ".flow/tasks/fn-39-project-strategy-strategymd-anchor.3.md", + "status": "todo", + "title": "Prospect + plan grounding (Phase 0 scan, Strategy Alignment, drift surfacing)", + "updated_at": "2026-05-01T16:40:15.437064Z" +} diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.3.md b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.3.md new file mode 100644 index 00000000..4f60c691 --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.3.md @@ -0,0 +1,75 @@ +# fn-39-project-strategy-strategymd-anchor.3 Prospect + plan grounding (Phase 0 scan, Strategy Alignment, drift surfacing) + +## Description +Wire `STRATEGY.md` grounding into `/flow-next:prospect` (Phase 0 grounding scan, rejection taxonomy) and `/flow-next:plan` (research scan, `## Strategy Alignment` spec section, drift surfacing). Both consume `flowctl strategy read --json` from Task 1; both surface advisory output (never auto-reject, never auto-supersede). + +**Size:** M +**Files:** +- `plugins/flow-next/skills/flow-next-prospect/SKILL.md` (edit Phase 0 + rejection taxonomy) +- `plugins/flow-next/skills/flow-next-prospect/workflow.md` (edit Phase 1 grounding-snapshot section) +- `plugins/flow-next/skills/flow-next-plan/steps.md` (edit Step 1 research scan) + +Depends on Task 1. + +## Approach + +**Prospect** (verbatim-emit grounding pattern — pass the user's strategic language straight through into candidate generation, no paraphrasing): + +- `flow-next-prospect/workflow.md` Phase 1 grounding-snapshot section (around lines 178-334 per repo-scout): add `STRATEGY.md` scan block. Block runs `flowctl strategy status --json` first (~5 lines bash); if `sections_filled >= 1`, then runs `flowctl strategy read --json` and emits `## Strategy snapshot` block in the grounding artifact. Includes verbatim: + - `name` from frontmatter + - `target_problem` body (full text) + - `approach` body (full text) + - `tracks` list (track names + their bodies) + - `last_updated` ISO date + Skip if `sections_filled == 0` and emit `scanned: none (no STRATEGY.md or husk)` line. +- `flow-next-prospect/SKILL.md` rejection taxonomy: add `out-of-scope-vs-strategy` to the existing list (currently `duplicates-open-epic | out-of-scope | insufficient-signal | too-large | backward-incompat | other`). Critique step uses this when a candidate violates an active track. Surface as advisory rejection with track citation: `Rejected: [out-of-scope-vs-strategy] — contradicts active track ""`. Document in skill prose that this is advisory — user can override at promote time. +- Critique pass (existing two-pass generate-then-critique) reads the strategy snapshot from Phase 1 artifact and weights candidate generation toward strategy-aligned directions. No new flowctl plumbing needed — the snapshot is already in the candidate-generation prompt. + +**Plan** (model after existing docs-gap-scout dispatch at `flow-next-plan/steps.md:102`): + +- `flow-next-plan/steps.md` Step 1 (Fast research, parallel): add `STRATEGY.md` read to the existing scout dispatch context. The scouts already get the request text + research findings; add a one-line `flowctl strategy status --json` check, and if `sections_filled >= 1`, pass the parsed `STRATEGY.md` content into the plan-prompt context. +- Step 5 (Write to .flow): add `## Strategy Alignment` section to the epic spec template (just under `## Boundaries / non-goals`). Lists which active tracks the plan serves. If no tracks served, emit `_No active strategy track served — review for drift._` placeholder. +- Drift surfacing: when the plan's scope conflicts with an active track, write a `## Strategy drift flagged for review` block in the epic spec (NOT in STRATEGY.md). Block format mirrors plan-sync's "Decision overrides flagged for review" at `agents/plan-sync.md:101-105`. Read-only — the plan skill never auto-supersedes the strategy doc; the user (or `/flow-next:strategy`) decides whether to revise. +- Husk semantics: if `flowctl strategy status` returns `husk: true OR exists: false`, plan skips the alignment section entirely (no signal to align to). Document this in skill prose so contributors know absence is fine. + +## Investigation targets + +**Required:** +- `plugins/flow-next/skills/flow-next-prospect/workflow.md` lines 178-334 — Phase 1 grounding-snapshot data sources (model for STRATEGY.md scan block) +- `plugins/flow-next/skills/flow-next-prospect/SKILL.md` — rejection taxonomy enumeration +- `plugins/flow-next/skills/flow-next-plan/steps.md` lines 73-128 — Step 1 scout dispatch table +- `plugins/flow-next/skills/flow-next-plan/steps.md` lines 195-280 — Step 5 epic spec template +- `plugins/flow-next/agents/plan-sync.md:101-105` — read-only-surface convention for "Decision overrides flagged for review" (model for `## Strategy drift flagged for review`) + +**Optional:** +- The verbatim-emit grounding pattern: when injecting STRATEGY.md content into the candidate-generation / planning prompts, emit `name`, `target_problem`, `approach`, `tracks` raw — no paraphrasing — so downstream prompts see the exact language the user committed to. +- The plan-time strategy check pattern: research scan reads STRATEGY.md if present and emits a `## Strategy Alignment` section listing which active tracks the plan serves; flag scope conflicts as a read-only `## Strategy drift flagged for review` block. + +## Key context + +- Both surfaces are advisory, not gating. Prospect rejection is shown to user; user can promote anyway. Plan drift is surfaced read-only; user runs `/flow-next:strategy` to revise if intended. +- No auto-supersede — this is the universal rule for shared-state docs in flow-next (mirrors plan-sync's decision-record handling). +- Husk-vs-presence: use `sections_filled >= 1` from `flowctl strategy status`, not `[[ -f STRATEGY.md ]]`. Same trap glossary fell into. +- Track ordering: tracks listed in user-chosen order; do NOT cite "highest-priority track" — strategy doesn't carry priority metadata in v1. +## Acceptance +- [ ] `flow-next-prospect/workflow.md` Phase 1 grounding-snapshot adds `STRATEGY.md` scan block. Calls `flowctl strategy status --json` first; when `sections_filled >= 1`, follows with `flowctl strategy read --json` and emits a `## Strategy snapshot` artifact block. +- [ ] Strategy snapshot includes verbatim: `name`, `target_problem`, `approach`, `tracks` (each track name + body), `last_updated`. No paraphrasing — uses raw values from `flowctl strategy read`. +- [ ] When `sections_filled == 0` (husk) or strategy file absent, prospect emits `scanned: none (no STRATEGY.md signal)` and proceeds normally without strategy grounding. +- [ ] `flow-next-prospect/SKILL.md` rejection taxonomy enumeration adds `out-of-scope-vs-strategy` to the existing 6-category list. Critique pass uses this category when candidate scope contradicts an active track. +- [ ] Rejected candidates with `out-of-scope-vs-strategy` cite the violated track name verbatim: `Rejected: [out-of-scope-vs-strategy] — contradicts active track ""`. +- [ ] Rejection is advisory only — user can `flowctl prospect promote --idea N --force` to override (existing flag). +- [ ] Strategy snapshot weights candidate generation in the existing two-pass generate-then-critique flow without new flowctl plumbing — uses prompt-level injection only. +- [ ] `flow-next-plan/steps.md` Step 1 (Fast research) adds `flowctl strategy status --json` check; when `sections_filled >= 1`, parsed `STRATEGY.md` content passes into plan-prompt context alongside research findings. +- [ ] `flow-next-plan/steps.md` Step 5 (Write to .flow) epic-spec template adds `## Strategy Alignment` section between `## Boundaries / non-goals` and `## Decision context`. Section lists active tracks served by the plan. +- [ ] When plan serves no active track, `## Strategy Alignment` contains `_No active strategy track served — review for drift._` placeholder, not a hidden empty section. +- [ ] When plan scope conflicts with an active track, plan emits a `## Strategy drift flagged for review` block (in the epic spec, NOT in STRATEGY.md). Format mirrors plan-sync's decision-overrides convention: bulleted list with track name + plan-decision divergence + `Review for revision via /flow-next:strategy.` line. +- [ ] Plan never edits `STRATEGY.md` — read-only consumption. Drift is surfaced for human review; `/flow-next:strategy` is the only writer. +- [ ] Husk-vs-presence: when `flowctl strategy status` returns `husk: true OR exists: false`, plan skips `## Strategy Alignment` and the drift block entirely. Documented in skill prose. +- [ ] Bash that calls `flowctl strategy status` uses portable `${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/flowctl` shape per existing flow-next conventions. +- [ ] Smoke verification: with a populated STRATEGY.md, `/flow-next:prospect` artifact contains the strategy snapshot block; with a husk file, the artifact has `scanned: none`. +## Done summary +Wired STRATEGY.md grounding into /flow-next:prospect (Phase 1 verbatim snapshot block + out-of-scope-vs-strategy rejection category) and /flow-next:plan (Step 1 strategy status check + Step 5 ## Strategy Alignment section between Boundaries and Decision context + ## Strategy drift flagged for review block mirroring plan-sync's read-only Decision overrides surface). Both surfaces are advisory and never auto-supersede; husk-vs-presence gated on sections_filled >= 1. +## Evidence +- Commits: 28c1647d70d1a64e3e764c91cc051e79849d18bb +- Tests: plugins/flow-next/scripts/ci_test.sh (56 pass), plugins/flow-next/scripts/smoke_test.sh (130 pass), deterministic snapshot fixture: 3 runs byte-identical, verbatim approach + tracks emit, husk + absent variants emit 'scanned: none (no STRATEGY.md signal)' +- PRs: \ No newline at end of file diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.4.json b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.4.json new file mode 100644 index 00000000..e201c643 --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.4.json @@ -0,0 +1,17 @@ +{ + "assignee": null, + "claim_note": "", + "claimed_at": null, + "created_at": "2026-05-01T16:41:27.632442Z", + "depends_on": [ + "fn-39-project-strategy-strategymd-anchor.1", + "fn-39-project-strategy-strategymd-anchor.2" + ], + "epic": "fn-39-project-strategy-strategymd-anchor", + "id": "fn-39-project-strategy-strategymd-anchor.4", + "priority": null, + "spec_path": ".flow/tasks/fn-39-project-strategy-strategymd-anchor.4.md", + "status": "todo", + "title": "Interview + capture + plan-sync grounding (autodetect + Strategy Conflicts + override-strategy + drift)", + "updated_at": "2026-05-01T16:41:27.774397Z" +} diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.4.md b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.4.md new file mode 100644 index 00000000..4f504db7 --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.4.md @@ -0,0 +1,98 @@ +# fn-39-project-strategy-strategymd-anchor.4 Interview + capture + plan-sync grounding (autodetect + Strategy Conflicts + override-strategy + drift) + +## Description +Wire `STRATEGY.md` grounding into `/flow-next:interview` (doc-aware autodetect extension, `## Strategy Conflicts` section, ≤1/turn throttle), `/flow-next:capture` (Phase 0 input, `[strategy:]` source tagging, `--override-strategy` flag with decision-record prompt), and `/flow-next:sync` (plan-sync agent Step 5 strategy context, `## Strategy drift flagged for review`). All three consume `flowctl strategy read --json` and surface advisory output (read-only). + +**Size:** M +**Files:** +- `plugins/flow-next/skills/flow-next-interview/SKILL.md` (autodetect extension lines 81-106; `## Strategy Conflicts` Phase-zero behavior) +- `plugins/flow-next/skills/flow-next-capture/SKILL.md` + `phases.md` (Phase 0 strategy input, source tagging, `--override-strategy` flag handling) +- `plugins/flow-next/agents/plan-sync.md` (Step 5 strategy context gather + drift surface block) + +Depends on Task 1 (consumes flowctl strategy plumbing) and Task 2 (skill file authored — flag matrix documented in CLAUDE.md). + +## Approach + +**Interview** (extends existing 0.39.0 doc-aware mode): + +- Extend autodetect block at `flow-next-interview/SKILL.md:81-106`. Currently checks `glossary.total_terms > 0` AND `decisions/ entries`. Add third condition: `flowctl strategy status --json | jq '.sections_filled >= 1'`. Activate doc-aware mode when ANY of the three returns true. +- Add `--strategy` / `--no-strategy` flag parsing parallel to existing `--docs` / `--no-docs` (lines 61-79). Independent flags — 5-row matrix: + + | Flags | Glossary | Decisions | Strategy | + |-------|----------|-----------|----------| + | (default) | autodetect | autodetect | autodetect | + | `--docs` | on | on | on | + | `--no-docs` | off | off | off | + | `--no-docs --strategy` | off | off | on | + | `--docs --no-strategy` | on | on | off | +- Add behavior (e) — code-versus-strategy contradiction surfacing — parallel to behavior (a) glossary-conflict at lines 192-249. When user input or research conflicts with active track, write `## Strategy Conflicts` spec section. Format: user-wording + canonical-strategy-wording + STRATEGY.md path + resolution chosen. +- Throttle: ≤1 strategy-conflict question per interview turn (parallel to existing glossary-question throttle at line 410-412). Total per-turn doc-aware question budget is now 1 glossary + 1 decision-record + 1 strategy = 3 max; existing throttles enforce per-category, so no global limit needed. + +**Capture** (extends Phase 0 pre-flight): + +- `flow-next-capture/phases.md` (or workflow.md) Phase 0 — add `STRATEGY.md` read alongside existing `.flow/epics/` scan and `flowctl memory search`. Surface in chat as "Strategic context:" footnote with approach + tracks list (3-5 lines). +- Source tagging: add `[strategy:]` source tag to the existing `[user]` / `[paraphrase]` / `[inferred]` taxonomy. When acceptance criterion is derived from STRATEGY.md (e.g., approach line states "X approach", spec criteria says "use X approach"), tag it. Read-back loop displays the count of `[strategy:*]` criteria alongside `[inferred]` count. +- New flag: `--override-strategy`. When capture detects the spec contradicts an active track (criterion is `[strategy:]` AND spec body contradicts that track), refuse the write with stderr message "Spec contradicts active track `` — pass `--override-strategy` to proceed." +- On `--override-strategy` flag fire: prompt user via `AskUserQuestion` "Record this override as a decision?" with lead-with-recommendation `[high]` toward yes (the override is a load-bearing architectural choice — exactly what the decisions track was added for). On yes: invoke `flowctl memory add --track knowledge --category decisions --title "Override strategy: " --module strategy --tags strategy-override --body-file ` with the override rationale. On no: proceed with capture but log the override in stderr for audit trail. + +**plan-sync** (extends Step 5 context gather): + +- `agents/plan-sync.md` Step 5 (lines 97-128 — currently builds `GLOSSARY_JSON` and `DECISIONS_JSON`). Add `STRATEGY_CONTENT` variable: `STRATEGY_CONTENT="$($FLOWCTL strategy read --json 2>/dev/null || echo '{}')"`. Pass into the plan-sync prompt under a `STRATEGY_CONTENT:` key alongside the existing keys. +- Husk short-circuit at line 110-112 pattern: when both glossary husk AND no decisions AND `strategy.sections_filled == 0`, skip the entire context-gather (no signal to align to). +- Track-rename handling: when plan-sync detects an existing spec uses an old track name (matches a verbatim quote-pattern from a previous STRATEGY.md `tracks` section), replace inline with ` `. Mirror the existing glossary rename pattern documented in CLAUDE.md "Plan-sync contract" section. +- Add `## Strategy drift flagged for review` block to spec output when plan-sync detects spec scope contradicts an active track. Read-only — never auto-supersedes. Format mirrors existing "Decision overrides flagged for review" at lines 101-105: bulleted list with spec line + STRATEGY.md track citation + "Review and run `/flow-next:strategy` if intended." + +## Investigation targets + +**Required:** +- `plugins/flow-next/skills/flow-next-interview/SKILL.md:61-79` — `--docs` / `--no-docs` flag parsing (model for `--strategy` / `--no-strategy`) +- `plugins/flow-next/skills/flow-next-interview/SKILL.md:81-106` — doc-aware autodetect block (extend with third condition) +- `plugins/flow-next/skills/flow-next-interview/SKILL.md:187-271` — behaviors (a)+(b) glossary-conflict + fuzzy-term sharpening (model for behavior (e) strategy-conflict) +- `plugins/flow-next/skills/flow-next-interview/SKILL.md:273-318` — behavior (d) decision-record three-criteria gate (model for `--override-strategy` decision-record prompt) +- `plugins/flow-next/skills/flow-next-capture/phases.md` — Phase 0 pre-flight scans +- `plugins/flow-next/skills/flow-next-capture/workflow.md` — source-tagging convention `[user]` / `[paraphrase]` / `[inferred]` +- `plugins/flow-next/agents/plan-sync.md:95-128` — Step 5 context gather + husk short-circuit +- `plugins/flow-next/agents/plan-sync.md:200-245` — read-only-surface convention for drift flagging + +**Optional:** +- `CLAUDE.md` "Plan-sync contract" section — glossary rename + decision overrides plumbing convention +- `CLAUDE.md` "Project glossary" subsection — flag matrix documentation pattern (apply same shape to strategy) + +## Key context + +- `--strategy` / `--no-strategy` are NEW flags, NOT subordinate to `--docs`. The 5-row matrix is the contract. +- Throttle: ≤1 strategy-conflict question per interview turn. Total doc-aware budget per turn is 3 (1 each for glossary / decisions / strategy). +- `--override-strategy` decision-record prompt: lead-with-recommendation `[high]` toward yes. User retains override authority — no on prompt is allowed. +- plan-sync NEVER auto-supersedes. Track-rename inline replacement is the ONLY auto-edit; everything else is surfaced read-only. +- Husk semantics: same `sections_filled >= 1` rule across all three skills. NOT `[[ -f STRATEGY.md ]]`. +## Acceptance +- [ ] `flow-next-interview/SKILL.md:81-106` autodetect block extended: third OR condition `flowctl strategy status --json | jq '.sections_filled >= 1'` joins existing glossary + decisions checks. Doc-aware mode activates when ANY of the three is true. +- [ ] `flow-next-interview/SKILL.md:61-79` flag parsing extended: `--strategy` / `--no-strategy` flags strip from `RAW_ARGS`. 5-row flag matrix documented inline in SKILL.md (or referenced from CLAUDE.md): + + | Flags | Glossary | Decisions | Strategy | + | (default) | auto | auto | auto | + | --docs | on | on | on | + | --no-docs | off | off | off | + | --no-docs --strategy | off | off | on | + | --docs --no-strategy | on | on | off | +- [ ] New behavior (e) — code-versus-strategy contradiction. Activates only when strategy condition is on. Surfaces conflicts in `## Strategy Conflicts` spec section (parallel to existing `## Glossary Conflicts`). Format: user-wording / canonical-strategy-wording / STRATEGY.md path / resolution-chosen. +- [ ] Throttle: ≤1 strategy-conflict question per interview turn. Verified via per-turn count check in skill prose (parallel to existing glossary-question throttle). +- [ ] `flow-next-capture/phases.md` (or workflow.md) Phase 0 reads `STRATEGY.md`. Surfaces "Strategic context:" footnote with approach (verbatim) + active tracks (names + bodies, capped 3-5 lines total). +- [ ] Source tagging: `[strategy:]` joins the existing `[user]` / `[paraphrase]` / `[inferred]` source-tag taxonomy. Acceptance criteria derived from STRATEGY.md content (verbatim or near-verbatim quote of approach/track) get this tag. +- [ ] Read-back loop displays count of `[strategy:*]` criteria alongside `[inferred]` count. Format: `Source: [user] N · [paraphrase] M · [strategy] K · [inferred] L`. +- [ ] `--override-strategy` flag implemented in capture. When capture detects spec body contradicts an active track AND no `--override-strategy` is passed, exits non-zero with stderr `Spec contradicts active track "" — pass --override-strategy to proceed.` +- [ ] On `--override-strategy` fire, capture prompts user via `AskUserQuestion` "Record this override as a decision?" with lead-with-recommendation pattern (body: "Recommended: yes — override decisions belong in the decisions track. Confidence: [high]"). Options: yes / no. +- [ ] On yes: invokes `flowctl memory add --track knowledge --category decisions --title "Override strategy: " --module strategy --tags strategy-override` with override rationale piped via `--body-file -` stdin (mirrors existing decision-record three-criteria gate at `flow-next-interview/SKILL.md:273-318`). +- [ ] On no: capture proceeds with the spec write but logs `[STRATEGY OVERRIDE]: track="" decision-not-recorded` to stderr for audit trail. +- [ ] `agents/plan-sync.md` Step 5 (lines 97-128) extended: adds `STRATEGY_CONTENT="$($FLOWCTL strategy read --json 2>/dev/null || echo '{}')"` variable. Passes into plan-sync prompt under `STRATEGY_CONTENT:` key alongside existing `GLOSSARY_JSON` and `DECISIONS_JSON`. +- [ ] Husk short-circuit at plan-sync line 110-112: when ALL of glossary husk AND no decisions AND `strategy.sections_filled == 0`, the entire Step 5 context gather skips. When ANY of the three has signal, Step 5 runs. +- [ ] Track-rename handling: when plan-sync detects an existing spec body uses a track name absent from current STRATEGY.md but historically present (heuristic: match a track-name pattern from prior file content), replace inline with ` `. Mirrors glossary rename pattern in CLAUDE.md "Plan-sync contract" section. +- [ ] `## Strategy drift flagged for review` block added to plan-sync output spec when scope contradicts active track. Format mirrors existing "Decision overrides flagged for review": bulleted list with spec line + STRATEGY.md track citation + `Review and run /flow-next:strategy if intended.` line. +- [ ] Plan-sync NEVER edits `STRATEGY.md`. Read-only consumption. Drift block, track-rename inline replacement (in spec body, not in STRATEGY.md), and STRATEGY_CONTENT prompt input only. +- [ ] All bash that calls `flowctl strategy ...` uses portable `${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/flowctl` form per existing flow-next conventions. +## Done summary +Wired STRATEGY.md grounding into `/flow-next:interview` (autodetect third condition + 5-row flag matrix + `## Strategy Conflicts` section + ≤1/turn throttle), `/flow-next:capture` (Phase 0 strategy input + `[strategy:]` source-tag taxonomy + `--override-strategy` flag with decision-record prompt + audit-trail stderr), and `/flow-next:sync` plan-sync agent (Step 5 STRATEGY_CONTENT input + extended husk short-circuit + `## Strategy drift flagged for review` block + track-rename inline replacement). Codex mirror regenerated via sync-codex.sh; CI test suite + glossary smoke test green. +## Evidence +- Commits: 971d24b0237df6b299246ea3d746a48546e08268 +- Tests: plugins/flow-next/scripts/ci_test.sh (56/56 pass), doc-aware autodetect /tmp fixture test — STRAT activates only on populated STRATEGY.md (no-strategy / husk-strategy / populated-strategy), 5-row flag matrix verbatim block test (default / --docs / --no-docs / --no-docs --strategy / --docs --no-strategy — all rows verified), scripts/sync-codex.sh validation (22 skills, 21 agents, all required openai.yaml present, no Claude-native tool refs in Codex mirror), glossary_smoke_test.sh regression (80/80 pass) +- PRs: \ No newline at end of file diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.5.json b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.5.json new file mode 100644 index 00000000..10d8d632 --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.5.json @@ -0,0 +1,19 @@ +{ + "assignee": null, + "claim_note": "", + "claimed_at": null, + "created_at": "2026-05-01T16:42:59.939262Z", + "depends_on": [ + "fn-39-project-strategy-strategymd-anchor.1", + "fn-39-project-strategy-strategymd-anchor.2", + "fn-39-project-strategy-strategymd-anchor.3", + "fn-39-project-strategy-strategymd-anchor.4" + ], + "epic": "fn-39-project-strategy-strategymd-anchor", + "id": "fn-39-project-strategy-strategymd-anchor.5", + "priority": null, + "spec_path": ".flow/tasks/fn-39-project-strategy-strategymd-anchor.5.md", + "status": "todo", + "title": "Codex sync + fluff guard + version bump + docs + mickel.tech", + "updated_at": "2026-05-01T16:43:00.080139Z" +} diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.5.md b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.5.md new file mode 100644 index 00000000..4e078c8c --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.5.md @@ -0,0 +1,126 @@ +# fn-39-project-strategy-strategymd-anchor.5 Codex sync + fluff guard + version bump + docs + mickel.tech + +## Description +Ship the new skill: regenerate Codex mirror, add Tier-1 forbidden-vocabulary guard (separate from R17 DDD pattern), bump version to 0.40.0, update all in-repo docs (CHANGELOG, README, CLAUDE.md, .flow/usage.md), and update Gordon's external mickel.tech site. + +**Size:** M (large M — touches many doc files but each edit is mechanical) +**Files:** +- `scripts/sync-codex.sh` (add `flow-next-strategy` to `REQUIRED_OPENAI_YAML_SKILLS` array; add `generate_openai_yaml` call in workflow blue group) +- `plugins/flow-next/scripts/ci_test.sh` (new section — strategy-doc fluff guard, separate from R17 DDD section 5c) +- `plugins/flow-next/.claude-plugin/plugin.json` + `.codex-plugin/plugin.json` + `.claude-plugin/marketplace.json` (version bump via `./scripts/bump.sh minor flow-next` — script handles all 3 manifests) +- `CHANGELOG.md` (new `[flow-next 0.40.0]` block) +- `plugins/flow-next/README.md` (TOC, lede count, new "Project Strategy" section, commands table, doc-aware autodetect rule) +- `CLAUDE.md` (commands list, new "Project strategy (v0.40.0+)" subsection, doc-aware autodetect rule) +- `.flow/usage.md` (new `# Strategy` block) +- `~/work/mickel.tech/app/apps/flow-next/page.tsx` (commands array, lede count, feature card) + +Depends on Tasks 1-4 (everything must be authored before sync regenerates the mirror; description counts must reflect final feature set). + +## Approach + +**1. Forbidden-vocabulary guard (R19)** — add NEW guard block in `plugins/flow-next/scripts/ci_test.sh`, separate from R17 DDD section 5c (lines 351-388). Comment must specify "strategy-doc fluff guard, NOT R17". Block scopes: `plugins/flow-next/skills/flow-next-strategy/` + `plugins/flow-next/scripts/flowctl.py` (cmd_strategy_*) + `plugins/flow-next/commands/flow-next/strategy.md`. References file `plugins/flow-next/skills/flow-next-strategy/references/interview.md` is excluded (must describe anti-patterns to push back on them — same exemption as glossary references). Tier 1 list: synergy / pivot / disrupt / thought-leadership / best-in-class / world-class / 10x. + +**2. Mirror in sync-codex.sh** — add R19 grep block to the validation block at lines 774-794 (currently has R17 DDD-vocab + R4 meta-file mirror scans). Identical pattern, scoped to Codex mirror at `plugins/flow-next/codex/skills/flow-next-strategy/`. + +**3. sync-codex.sh skill registration**: +- Add `flow-next-strategy` to `REQUIRED_OPENAI_YAML_SKILLS` array (lines 537-552). +- Add `generate_openai_yaml "flow-next-strategy" "Flow Strategy" "Generate or update repo-root STRATEGY.md (problem, approach, personas, metrics, tracks)" "#3B82F6" false` in workflow blue group (after the existing `flow-next-capture` call at line 519 area). + +**4. Version bump**: run `./scripts/bump.sh minor flow-next`. Updates all 3 manifests automatically. Bumps 0.39.0 → 0.40.0. Update plugin.json descriptions to reflect new counts: 19 skills, 14 commands (or whatever final count is). Three manifests have description text: `plugins/flow-next/.claude-plugin/plugin.json`, `plugins/flow-next/.codex-plugin/plugin.json` (line 4 description + line 24 longDescription), `.claude-plugin/marketplace.json` flow-next entry. + +**5. Run sync**: `./scripts/sync-codex.sh`. Verify zero errors; verify `plugins/flow-next/codex/skills/flow-next-strategy/` exists with rewritten tool names (`request_user_input` not `AskUserQuestion`); verify `plugins/flow-next/codex/agents/openai.yaml` has the strategy entry. Commit regenerated `plugins/flow-next/codex/` directory. + +**6. CHANGELOG.md** — add `## [flow-next 0.40.0] - ` block above the existing 0.39.0 entry. Sections: Files Added (skill, references, slash command, smoke test, fluff guard, ci_test section), Files Changed (downstream skills + plan-sync + interview autodetect + capture + sync-codex), Constraints (R-IDs from spec — STRATEGY.md repo-root, single-root walk, husk semantics, foreign-file refusal, Ralph block), Smoke coverage (T1-T12). + +**7. plugins/flow-next/README.md updates**: +- Line 24 lede ("What's new in 0.39.0") → add 0.40.0 lede paragraph above describing strategy +- Line 40 TOC → add `- [Project Strategy](#project-strategy)` parallel to glossary entry +- Line 57 lede ("Sixteen slash commands") → bump to seventeen + insert strategy in lifecycle position +- After line 1757 (end of `## Project Glossary`) → new `## Project Strategy` section. Mirror glossary section's sub-structure: Format / Resolution / Subcommands (only status/read/list — note no add/edit) / Husk semantics / How the rest of flow-next uses it / Forbidden vocabulary / Why no migration in v1. +- Line 1761 ("Sixteen commands") → bump to seventeen; add `/flow-next:strategy` row to commands table. +- Line 1809-1820 flag-matrix table → add `--strategy` / `--no-strategy` row. +- Line 1917 doc-aware autodetect rule → extend OR condition to include `STRATEGY.md sections_filled >= 1`. + +**8. CLAUDE.md updates**: +- Lines 19-29 commands list → add `/flow-next:strategy [optional: section to revisit]` bullet with one-sentence description and version tag `(v0.40.0+)`. +- After line 112 (end of "Project glossary (v0.39.0+)") → new "Project strategy (v0.40.0+)" subsection. Cover: STRATEGY.md repo-root convention (R18 invariant — survives `.flow/` wipe), single-root walk (NOT nearest-ancestor, contrast glossary), 3 frontmatter keys (`name`, `last_updated`, `generator: flow-next-strategy`), section structure (5 required + 2 optional, drop CE Marketing), `flowctl strategy status/read/list` subcommands, husk semantics (`sections_filled >= 1` rule, NOT file-existence), how downstream skills consume it (read-only, advisory, never auto-supersede), foreign-file refusal in v1. +- Line 109 doc-aware autodetect one-liner → extend with strategy condition. +- Line 280 area "Adding a new user-facing skill" checklist → no change (this task validates the checklist). + +**9. .flow/usage.md** lines 88-97 area → add `# Strategy` sub-block after the `# Glossary` block. Lists the three subcommands with example invocations. + +**10. mickel.tech update** at `~/work/mickel.tech/app/apps/flow-next/page.tsx`: +- Increment lede command count +- Add `strategy` entry to commands array (find the existing pattern — likely a TS array of `{name, description, version}` objects) +- Add feature card for "Project Strategy" parallel to the existing glossary feature card; mirror title / description / version-badge structure +- FAQ entry if applicable (e.g., "How does flow-next ground prospect/plan in product strategy?") + +This task is coupled — version bump must happen AFTER doc edits (so plugin.json descriptions reflect final feature counts), and sync MUST run AFTER version bump (so the Codex mirror picks up the new version). Order: forbidden-vocab guards → docs → version bump → sync → commit. + +## Investigation targets + +**Required:** +- `scripts/sync-codex.sh:497-552` — `generate_openai_yaml` definition + call sites + `REQUIRED_OPENAI_YAML_SKILLS` array +- `scripts/sync-codex.sh:774-794` — R17 + R4 mirror validation blocks (model for R19 fluff guard mirror) +- `plugins/flow-next/scripts/ci_test.sh:351-388` — section 5c R17 DDD guard (model — but DON'T extend; create new section) +- `plugins/flow-next/README.md:24,40,57,1715-1757,1759-1820,1917` — exact line refs for all README edits per docs-gap-scout +- `CLAUDE.md:19-29,100-112,109,280` — exact line refs for all CLAUDE.md edits +- `CHANGELOG.md:5` — current `[flow-next 0.39.0]` block; new 0.40.0 goes above +- `.flow/usage.md:88-97` — `# Glossary` block (model for `# Strategy` block) +- `scripts/bump.sh` — version bump tool; figure out which manifests it updates +- `plugins/flow-next/.claude-plugin/plugin.json:3` — version field; description field needs count update +- `plugins/flow-next/.codex-plugin/plugin.json:3-4,24` — version + description + longDescription +- `.claude-plugin/marketplace.json:9,15` — marketplace + plugin version + +**Optional:** +- `~/work/mickel.tech/app/apps/flow-next/page.tsx` — commands array + feature cards (read access may be limited if this is in a separate repo; check before editing) + +## Key context + +- R19 forbidden-vocab guard is SEPARATE from R17 DDD guard. Each block has one purpose. Comment in ci_test.sh must specify "strategy-doc fluff guard, NOT R17" so future maintainers don't mix them. +- `references/interview.md` is exempt from the guard (must describe anti-patterns to push back on them — same exemption pattern as glossary references file vs the canonical SKILL.md). +- Two-tier guard: ci_test.sh validates canonical, sync-codex.sh validates the Codex mirror. Both must catch a violation. +- mickel.tech update is "in scope per user request" — Gordon's repo, but he asked it to ship in this epic. External contributors would normally skip this per CLAUDE.md "Contributing / Development" section. +- Order matters: docs → version bump → sync. Sync regenerates the Codex mirror after the canonical files are stable. +- Verify post-sync: `plugins/flow-next/codex/skills/flow-next-strategy/SKILL.md` exists, contains `request_user_input` not `AskUserQuestion`, has zero R19/R17 forbidden-vocab matches. +## Acceptance +- [ ] `plugins/flow-next/scripts/ci_test.sh` has a NEW guard block (separate from R17 section 5c) titled "strategy-doc fluff guard, NOT R17". Block grep pattern includes Tier 1 jargon: `synergy|pivot|disrupt|thought leadership|best-in-class|world-class|10x` (with appropriate word boundaries). Scopes: `plugins/flow-next/skills/flow-next-strategy/SKILL.md` + `plugins/flow-next/scripts/flowctl.py` (`cmd_strategy_*` regions) + `plugins/flow-next/commands/flow-next/strategy.md`. Excludes `plugins/flow-next/skills/flow-next-strategy/references/interview.md` (must describe anti-patterns). +- [ ] `scripts/sync-codex.sh` validation block (lines 774-794) extended with R19 mirror scan against `plugins/flow-next/codex/skills/flow-next-strategy/` (rewritten Codex mirror). Same pattern as the canonical guard. +- [ ] `scripts/sync-codex.sh:537-552` — `REQUIRED_OPENAI_YAML_SKILLS` array contains `"flow-next-strategy"` entry. CI fails when missing. +- [ ] `scripts/sync-codex.sh` workflow-blue group has `generate_openai_yaml "flow-next-strategy" "Flow Strategy" "" "#3B82F6" false` call (placement after existing `flow-next-capture` call). +- [ ] Version bumped 0.39.0 → 0.40.0 across all 3 manifests via `./scripts/bump.sh minor flow-next`. Verified by: + - `jq -r '.version' plugins/flow-next/.claude-plugin/plugin.json` returns 0.40.0 + - `jq -r '.version' plugins/flow-next/.codex-plugin/plugin.json` returns 0.40.0 + - `jq -r '.plugins[] | select(.name=="flow-next") | .version' .claude-plugin/marketplace.json` returns 0.40.0 +- [ ] Three plugin.json description fields updated to reflect final counts (skills + commands + agents). Description in `plugins/flow-next/.claude-plugin/plugin.json` + `.codex-plugin/plugin.json` description + longDescription + `.claude-plugin/marketplace.json` flow-next entry. +- [ ] `./scripts/sync-codex.sh` run completes with zero errors. Verified by: + - `plugins/flow-next/codex/skills/flow-next-strategy/SKILL.md` exists with `request_user_input` substituted for `AskUserQuestion` + - `plugins/flow-next/codex/agents/openai.yaml` includes a `flow-next-strategy` entry + - `grep -RE 'AskUserQuestion' plugins/flow-next/codex/skills/flow-next-strategy/` returns no hits (sync rewrites cleanly) +- [ ] `CHANGELOG.md` has new `## [flow-next 0.40.0] - ` block above the existing 0.39.0 entry. Sections: Files Added, Files Changed, Constraints (R-IDs), Smoke coverage (T1-T12). +- [ ] `plugins/flow-next/README.md` updated: + - Line 24 area: 0.40.0 lede paragraph above existing 0.39.0 lede + - Line 40 TOC: `- [Project Strategy](#project-strategy)` entry + - Line 57 lede: command count bumped to seventeen + strategy mentioned in lifecycle chain + - New `## Project Strategy` section after `## Project Glossary` (line 1757). Sub-sections: Format, Resolution, Subcommands (status/read/list only — note absence of add/edit), Husk semantics, How the rest of flow-next uses it, Forbidden vocabulary (Tier 1 list), Why no migration in v1 + - Line 1761 commands table: count bumped to seventeen + new `/flow-next:strategy` row + - Line 1809-1820 flag-matrix table: new `--strategy` / `--no-strategy` row + - Line 1917 doc-aware autodetect rule: OR condition extended to include `STRATEGY.md sections_filled >= 1` +- [ ] `CLAUDE.md` updated: + - Lines 19-29 commands list: `/flow-next:strategy [optional: section to revisit]` bullet added in lifecycle order with `(v0.40.0+)` version tag + - After line 112: new "Project strategy (v0.40.0+)" subsection covering R18 invariant, single-root walk, 3 frontmatter keys, section structure (5+2, no Marketing), 3 subcommands, husk semantics, downstream consumers (read-only advisory), v1 foreign-file refusal + - Line 109: doc-aware autodetect one-liner extended with strategy condition +- [ ] `.flow/usage.md` lines 88-97 area: new `# Strategy` block after `# Glossary` block. Lists `flowctl strategy status [--json]`, `flowctl strategy read [--section ] [--json]`, `flowctl strategy list [--json]` with one-line example invocations. +- [ ] `~/work/mickel.tech/app/apps/flow-next/page.tsx` updated: + - Lede command count bumped + - Strategy entry added to commands array (matching existing pattern shape) + - Feature card for "Project Strategy" added parallel to glossary feature card + - FAQ entry added if pattern exists for parallel features +- [ ] CI passes after all changes: `plugins/flow-next/scripts/ci_test.sh` exits 0 with no R17/R19 violations and no R4 meta-file violations. `./scripts/sync-codex.sh` validation block exits 0. +- [ ] `git diff --stat` shows expected file set; no accidental edits to `.flow/`, smoke fixtures, or unrelated files. +## Done summary +Shipped fn-39 task 5: Codex sync + R19 strategy-doc fluff guard + version bump 0.39.0→0.40.0 + docs across CHANGELOG / plugins/flow-next/README.md / CLAUDE.md / .flow/usage.md / root README.md / mickel.tech. Two-tier R19 guard (canonical ci_test.sh section 5d + sync-codex.sh validation block) blocks Tier 1 fluff (synergy / pivot / disrupt / thought-leadership / best-in-class / world-class / 10x) in flow-next-strategy SKILL.md + cmd_strategy_* in flowctl.py + strategy.md command, with references/interview.md exempt for anti-pattern descriptions. flow-next-strategy added to REQUIRED_OPENAI_YAML_SKILLS + workflow blue group; Codex mirror regenerated with request_user_input substituted for AskUserQuestion. All 3 manifests at 0.40.0 with description counts refreshed to 21 subagents / 17 commands / 22 skills. ci_test.sh 57/57, smoke_test.sh 130/130, glossary_smoke_test.sh 80/80, sync-codex.sh zero errors. +## Evidence +- Commits: 951e27faf8331e92d83d9cfa22b1a087309c0d6b +- Tests: plugins/flow-next/scripts/ci_test.sh (57/57 pass — includes new R19 strategy-doc fluff guard), plugins/flow-next/scripts/ci_test.sh fluff-fixture-injection: confirmed guard fires non-zero on seeded synergy keyword in flow-next-strategy/SKILL.md (then reverted), ./scripts/sync-codex.sh (22 skills, 21 agents, 15 openai.yaml; R19 mirror green; flow-next-strategy/agents/openai.yaml generated), ./scripts/bump.sh minor flow-next: 0.39.0 -> 0.40.0 across all 3 manifests (claude-plugin / codex-plugin / marketplace) verified via jq, smoke_test.sh from /tmp (130/130 pass), glossary_smoke_test.sh from /tmp (80/80 pass), Codex strategy mirror verified: grep -c request_user_input = 8, grep -c AskUserQuestion = 0, Plugin description counts updated to 21 subagents / 17 commands / 22 skills across 3 plugin.json files +- PRs: \ No newline at end of file diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.6.json b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.6.json new file mode 100644 index 00000000..1610d879 --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.6.json @@ -0,0 +1,19 @@ +{ + "assignee": null, + "claim_note": "", + "claimed_at": null, + "created_at": "2026-05-01T16:44:02.480573Z", + "depends_on": [ + "fn-39-project-strategy-strategymd-anchor.1", + "fn-39-project-strategy-strategymd-anchor.2", + "fn-39-project-strategy-strategymd-anchor.3", + "fn-39-project-strategy-strategymd-anchor.4" + ], + "epic": "fn-39-project-strategy-strategymd-anchor", + "id": "fn-39-project-strategy-strategymd-anchor.6", + "priority": null, + "spec_path": ".flow/tasks/fn-39-project-strategy-strategymd-anchor.6.md", + "status": "todo", + "title": "strategy_smoke_test.sh (T1-T12)", + "updated_at": "2026-05-01T16:44:02.621733Z" +} diff --git a/.flow/tasks/fn-39-project-strategy-strategymd-anchor.6.md b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.6.md new file mode 100644 index 00000000..29a5b770 --- /dev/null +++ b/.flow/tasks/fn-39-project-strategy-strategymd-anchor.6.md @@ -0,0 +1,83 @@ +# fn-39-project-strategy-strategymd-anchor.6 strategy_smoke_test.sh (T1-T12) + +## Description +Author `plugins/flow-next/scripts/strategy_smoke_test.sh` exercising 12 cases (T1-T12) covering happy paths, husk detection, foreign-file refusal, mid-flow abandonment, forbidden-vocab pushback, downstream grounding, and Ralph block. Pure bash + Python (no LLM in the loop), refuses to run from main plugin repo, target <30s runtime. + +**Size:** M +**Files:** +- `plugins/flow-next/scripts/strategy_smoke_test.sh` (new) + +Depends on Tasks 1-4 (everything with runtime behavior must be authored before tests can exercise it). + +## Approach + +Clone the structure of `plugins/flow-next/scripts/glossary_smoke_test.sh` (784 lines, 25 cases) — proven scaffold. Same header (refuses to run from main plugin repo), same `TEST_DIR=/tmp/strategy-smoke-$$` convention, same `KEEP_TEST_DIR=1` env opt-in, same `trash` cleanup with `rm` fallback, same `ok` / `fail` / `assert_rc` / `assert_grep` / `assert_no_grep` / `json_get` helpers. + +Test cases (mapping back to gap-analyst's T1-T12 + R-IDs from the spec): + +**Happy paths:** +- **T1 first-run create-from-scratch:** init empty fixture repo; manually write a populated STRATEGY.md (since the skill itself runs an interview that needs a host LLM, the smoke test simulates the on-disk artifact directly). Assert `flowctl strategy status --json | jq '.exists == true and .husk == false and .sections_filled == 5 and .generator_match == true'`. Validates R1, R6, R23. +- **T2 targeted section re-run preserves rest:** mutate one H2 section's body in-place; re-parse; assert other sections byte-identical via `diff` against original; assert `last_updated` would bump on save (skill tested separately by mid-flow check). Validates R4. +- **T3 subdirectory invocation walks up:** create `apps/web/` subdir; cd there; assert `flowctl strategy status --json | jq '.file_path | endswith("/STRATEGY.md")'` resolves to repo-root path. Validates R7, R16. + +**Corner cases:** +- **T4 husk file detected:** write `STRATEGY.md` containing only `# Strategy\n` + frontmatter (no H2 sections); assert `flowctl strategy status --json | jq '.exists == true and .husk == true and .sections_filled == 0'`. Validates R6 husk semantics, R23. +- **T5 foreign-file refusal:** write hand-written STRATEGY.md without `generator: flow-next-strategy` frontmatter; assert `flowctl strategy status --json | jq '.generator_match == false'`. (Skill behavior — interactive refusal — covered by skill-level integration test elsewhere; smoke validates the JSON contract.) Validates R15. +- **T6 mid-flow abandonment + resume:** write STRATEGY.md with 2 of 5 required sections populated, 3 empty; assert `flowctl strategy status --json | jq '.sections_filled == 2'`; assert `flowctl strategy read --json | jq '(.target_problem | length) > 0 and (.approach | length) > 0 and .personas == ""'` (or whichever 2 are populated). Validates per-section atomic-write contract (R4). +- **T7 forbidden-vocab pushback** (CI-side, not smoke-side): assert `plugins/flow-next/scripts/ci_test.sh` exits non-zero when test SKILL.md fixture contains `synergy`. Smoke writes a fixture SKILL.md with the bad word, runs ci_test against the fixture's plugin tree, asserts non-zero RC. Validates R19. +- **T8 strategy-glossary conflict surface** (read-side): seed glossary with term "Track" definition; seed STRATEGY.md Tracks section using "Initiative" instead; manually invoke `flowctl strategy read --json` and `flowctl glossary list --json`; verify the JSON contracts allow downstream skill (interview) to detect the mismatch. (Full interview flow tested separately.) Validates R12. +- **T9 capture --override-strategy decision-record schema:** seed STRATEGY.md with active track; assert `flowctl memory add --track knowledge --category decisions --title "Override strategy: " --module strategy --tags strategy-override --body-file -` accepts the input and writes a valid memory entry. Validates R13. +- **T10 prospect grounding emits verbatim approach + tracks:** seed STRATEGY.md with specific approach text; manually run the workflow.md grounding-snapshot bash block; assert output contains the verbatim approach string. (No LLM needed — the snapshot is deterministic.) Validates R10. +- **T11 plan-sync drift surfacing read-only:** assert plan-sync skill prose contains "never auto-supersedes" or equivalent invariant string for STRATEGY.md drift section; verify the agents/plan-sync.md update from Task 4. Validates R14. +- **T12 Ralph block:** in fixture, set `FLOW_RALPH=1`; invoke the slash-command forwarder simulating skill entry (or directly the Phase 0 bash block from SKILL.md); assert exit code 2 + stderr contains `[STRATEGY: user-triggered only`. Validates R17. + +**Refuse-to-run-in-main-plugin-repo guard:** at top of script (mirroring `glossary_smoke_test.sh:60-63`), check if `pwd` matches the canonical plugin repo path; exit non-zero with message if so. Forces test runs into a clean fixture. + +**Target <30s runtime.** Each test case <2s. Most are deterministic file / JSON checks; no LLM calls. Tests T1-T6, T9-T12 are direct flowctl + JSON assertions. T7 invokes `ci_test.sh` against a fixture (slowest — bound by ci_test runtime). T8 reads two flowctl outputs and asserts both shapes. + +## Investigation targets + +**Required:** +- `plugins/flow-next/scripts/glossary_smoke_test.sh` (full file, 784 lines) — direct template +- `plugins/flow-next/scripts/audit_smoke_test.sh` (528 lines) — secondary reference for assertion patterns +- `plugins/flow-next/scripts/prospect_smoke_test.sh` (756 lines) — pattern for skill-Phase-0 simulation in smoke + +**Optional:** +- `plugins/flow-next/scripts/ci_test.sh` — invocation shape for T7 fixture-mode + +## Key context + +- Smoke tests don't invoke LLMs — they exercise flowctl plumbing + skill bash blocks directly. Skill interview flow (with AskUserQuestion) is not covered here; that's an integration concern. +- T1 simulates what a completed skill run would leave on disk. The on-disk artifact is what downstream skills consume; that's what needs verification. +- Refuse-to-run guard prevents accidentally polluting the main repo with test artifacts. Same pattern in every flow-next smoke. +- `KEEP_TEST_DIR=1` env opt-in keeps fixtures around for debugging. Default cleans up via `trash` (with `rm` fallback per existing flow-next convention). +- Each helper (`ok` / `fail` / `assert_*`) prefixed with case ID (e.g., `T1: ok ...`) for grep-friendly failure output. +## Acceptance +- [ ] `plugins/flow-next/scripts/strategy_smoke_test.sh` created, executable (`chmod +x`), follows the structure of `glossary_smoke_test.sh` (header, TEST_DIR convention, helpers). +- [ ] Refuse-to-run-in-main-plugin-repo guard: top-of-script check exits non-zero if `pwd` matches the canonical plugin repo path. Mirrors `glossary_smoke_test.sh:60-63`. +- [ ] `KEEP_TEST_DIR=1` env opt-in honored. Default cleanup via `trash` with `rm` fallback (per existing flow-next convention). +- [ ] All 12 test cases T1-T12 implemented: + - T1 first-run on-disk shape (R1, R6, R23): full populated STRATEGY.md → status reports `exists, !husk, sections_filled==5, generator_match` + - T2 targeted section re-run preservation (R4): byte-identical untouched sections via diff + - T3 subdir walk-up (R7, R16): `file_path` resolves to repo root from `apps/web/` cwd + - T4 husk detection (R6, R23): bare H1 + frontmatter → `husk: true, sections_filled: 0` + - T5 foreign-file refusal contract (R15): missing `generator: flow-next-strategy` → `generator_match: false` + - T6 mid-flow partial state (R4): 2-of-5 populated → `sections_filled: 2`, populated bodies non-empty, others `""` (empty string, not null) + - T7 forbidden-vocab CI guard (R19): fixture SKILL.md with banned word → `ci_test.sh` non-zero RC + - T8 strategy/glossary JSON contract (R12): both reads return parseable JSON for downstream conflict detection + - T9 decision-record schema (R13): `flowctl memory add` with strategy-override tags accepts and writes valid entry + - T10 prospect grounding determinism (R10): snapshot bash emits verbatim approach string + - T11 plan-sync read-only invariant (R14): grep `agents/plan-sync.md` for "never auto-supersedes" or equivalent + - T12 Ralph block (R17): with `FLOW_RALPH=1`, Phase 0 bash exits 2 + stderr contains `[STRATEGY: user-triggered only` +- [ ] Each test case prints `Tn: ok ...` on pass or `Tn: FAIL ...` on failure (grep-friendly). +- [ ] Total runtime <30s on a typical dev machine (T7 is the slowest — bound by `ci_test.sh` startup). +- [ ] Script exits 0 on full pass, non-zero on any failure (sums per-case status). +- [ ] No external network calls. No LLM invocations. All tests deterministic. +- [ ] Smoke runs cleanly after Tasks 1-5 ship: `plugins/flow-next/scripts/strategy_smoke_test.sh` returns 0. +- [ ] No accidental writes outside `$TEST_DIR`. Verified by `git status` post-run reporting clean tree (after `KEEP_TEST_DIR` cleanup). +## Done summary +Authored plugins/flow-next/scripts/strategy_smoke_test.sh (751 LOC) exercising the 12 cases T1-T12 from spec — full populated state, targeted re-run preservation, subdir walk-up, husk detection, foreign-file refusal, mid-flow partial state with empty-string semantics, R19 fluff guard, strategy+glossary JSON contract, decision-record memory add, prospect grounding determinism, plan-sync read-only invariant, and Ralph-block exit-2. Pure shell + Python harness, refuses to run from main plugin repo (mirrors glossary_smoke_test.sh:60-63), KEEP_TEST_DIR=1 opt-in honored, total runtime 4.3s (well under 30s budget). Final smoke results: strategy_smoke_test.sh 56/0, ci_test.sh 57/0, smoke_test.sh 130/0, glossary_smoke_test.sh 80/0 — all four exit 0. +## Evidence +- Commits: d045fbb96428356c6be2d2f4c4c70fa8f4ad30ca +- Tests: cd /tmp && plugins/flow-next/scripts/strategy_smoke_test.sh (56/0 pass, T1-T12 all green, 4.3s, rc=0); cd /tmp && plugins/flow-next/scripts/ci_test.sh (57/0 pass, 10.5s, rc=0); cd /tmp && plugins/flow-next/scripts/smoke_test.sh (130/0 pass, 99s, rc=0); cd /tmp && plugins/flow-next/scripts/glossary_smoke_test.sh (80/0 pass, 6.8s, rc=0); refuse-to-run guard verified (invocation from main plugin repo exits rc=1 with refusal message); KEEP_TEST_DIR=1 verified (leaves /tmp/strategy-smoke- on disk); hygiene check confirms no STRATEGY.md leaked into plugin tree +- PRs: \ No newline at end of file diff --git a/.flow/usage.md b/.flow/usage.md index c7d233ea..f3f62778 100644 --- a/.flow/usage.md +++ b/.flow/usage.md @@ -95,6 +95,16 @@ Task tracking for AI agents. All state lives in `.flow/`. .flow/bin/flowctl glossary read # nearest-ancestor walk; first match wins .flow/bin/flowctl glossary read --json # {path, term, definition, avoid, relates_to} .flow/bin/flowctl glossary remove # last-term remove leaves `# Glossary` husk (R18) + +# Strategy (project-canonical strategic intent at repo root, v0.40.0+ — survives `rm -rf .flow/`) +.flow/bin/flowctl strategy status # text mode: husk / sections_filled / total_sections / last_updated +.flow/bin/flowctl strategy status --json # {exists, husk, sections_filled, total_sections, last_updated, file_path} +.flow/bin/flowctl strategy read # full STRATEGY.md (single-root walk from cwd up to repo root) +.flow/bin/flowctl strategy read --section approach # one section only (target_problem / approach / personas / metrics / tracks / milestones / not_working_on) +.flow/bin/flowctl strategy read --json # {path, name, last_updated, target_problem, approach, personas, metrics, tracks, milestones, not_working_on} +.flow/bin/flowctl strategy list --json # {groups, file_count, total_sections} — parallel to glossary list + +# /flow-next:strategy skill writes STRATEGY.md directly (no flowctl strategy add — too prose-heavy for atomic CLI). ``` ## Workflow diff --git a/.gitignore b/.gitignore index 3e2b19c2..23a6a89b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ __pycache__/ .flow/receipts/ .flow/tmp/ scripts/ralph/ +.claude/ diff --git a/CHANGELOG.md b/CHANGELOG.md index e03ff1d4..84311743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,46 @@ All notable changes to the flow-next. +## [flow-next 0.40.0] - 2026-05-01 + +### Added +- **`/flow-next:strategy [optional: section to revisit]` — agent-native repo strategy anchor.** New skill that writes/maintains a repo-root `STRATEGY.md` (peer of `GLOSSARY.md` / `README.md`, never under `.flow/`) so strategic intent survives `rm -rf .flow/` (R1 / R22 — survives uninstall by design, mirrors the glossary R18 invariant). Section structure derived from Richard Rumelt's strategy kernel (*Good Strategy Bad Strategy*: diagnosis / guiding policy / coherent action), extended with persona + metrics for repo-doc utility: 5 required sections (`Target problem` / `Our approach` / `Who it's for` / `Key metrics` / `Tracks`) plus 2 optional (`Milestones` / `Not working on`). A `Marketing` section was considered and dropped — over-rotated for OSS-tools repos. Atomic per-section writes; `last_updated` bumps on every save. No draft state file. Re-invocation reads existing sections via `flowctl strategy status` and asks which section to revisit. Pushback discipline: 2 rounds maximum per section, then captures what user gave with a `` comment. Anti-pattern labels (vanity / fluff / feature-list) NOT leaked to user — only used internally to formulate sharper follow-up questions; quote user's own words back when challenging. +- **Repo-root `STRATEGY.md` artifact.** Frontmatter holds 3 keys only: `name`, `last_updated`, `generator: flow-next-strategy`. Foreign-file refusal — without the `generator: flow-next-strategy` sentinel the skill prompts the user (migrate / keep / rewrite?). Multi-format migration (CE-format / hand-written) explicitly deferred to v2; v1 ships the sentinel + refusal. Plain GFM markdown only; no MDX / admonitions / `:::tip` blocks. +- **`flowctl strategy status / read / list` plumbing.** `flowctl strategy status [--json]` returns `{exists, husk, sections_filled, total_sections, last_updated, file_path}`. Husk definition: file exists but `sections_filled == 0`. `flowctl strategy read [--section ] [--json]` resolves the repo root via `git rev-parse --show-toplevel` and checks for `STRATEGY.md` ONLY at that root — single-root resolution, no upward walk, no cascade. An `apps/web/STRATEGY.md` is always ignored; downstream skills consume the repo-root file regardless of cwd. Strategy is repo-wide by Rumelt's definition (NOT nearest-ancestor like glossary). `flowctl strategy list [--json]` parallels `flowctl glossary list` for symmetric downstream iteration. NO `flowctl strategy add/edit/remove` — strategy is too prose-heavy for atomic field-set CLI; the skill IS the editor. +- **Doc-aware autodetect — third condition.** Doc-aware mode now activates when ANY of three signals: `glossary.total_terms > 0` OR `knowledge/decisions/` has entries OR `strategy.sections_filled >= 1`. Override flags follow a cascade-with-explicit-override rule: `--docs` / `--no-docs` cascade to all three categories (glossary + decisions + strategy); explicit `--strategy` / `--no-strategy` always wins over the cascade for the strategy slot. 5-row matrix: `(default)` autodetect all three; `--docs` on for all three; `--no-docs` off for all three; `--no-docs --strategy` strategy on / glossary+decisions off; `--docs --no-strategy` glossary+decisions on / strategy off. Husk semantics on autodetect: branches on `flowctl strategy status --json | jq '.sections_filled >= 1'`, NOT on `[[ -f STRATEGY.md ]]` — same trap glossary fell into. +- **Strategy-doc fluff guard (R19).** New guard block in `plugins/flow-next/scripts/ci_test.sh` (separate from R17 DDD section 5c — comment specifies "strategy-doc fluff guard, NOT R17"). Tier 1 jargon only (Rumelt's "fluff" hallmarks): `synergy / pivot / disrupt / thought-leadership / best-in-class / world-class / 10x`. Scoped to `plugins/flow-next/skills/flow-next-strategy/SKILL.md` + `cmd_strategy_*` regions in `flowctl.py` + `plugins/flow-next/commands/flow-next/strategy.md`. The `references/interview.md` file is excluded — must describe anti-patterns to push back on them (same exemption as glossary references). Mirrored in `scripts/sync-codex.sh` validation block for the Codex mirror at `plugins/flow-next/codex/skills/flow-next-strategy/`. Two-tier guard (canonical + mirror) catches violations at either source path. +- **Smoke test `plugins/flow-next/scripts/strategy_smoke_test.sh` (T1-T12).** Cases: T1 first-run create-from-scratch; T2 targeted section re-run preserves rest byte-identically; T3 subdirectory invocation walks up; T4 husk detected via `sections_filled == 0`; T5 foreign-file refusal (no `generator` sentinel); T6 mid-flow abandonment + resume; T7 forbidden-vocab pushback; T8 strategy-glossary conflict surfaces in interview spec; T9 capture `--override-strategy` writes decision record; T10 prospect grounding emits verbatim approach + tracks; T11 plan-sync drift surfacing read-only; T12 Ralph-block exit-2. + +### Changed +- **`/flow-next:prospect` Phase 0 grounding scan reads `STRATEGY.md`** when `sections_filled >= 1`. Injects approach + active tracks verbatim into candidate-generation prompt (mirrors CE-ideate's "emit approach and active tracks verbatim" pattern). Adds `out-of-scope-vs-strategy` to the rejection taxonomy. Surfaced as advisory at prospect phase — never auto-rejects. +- **`/flow-next:plan` research scan reads `STRATEGY.md`.** Plan emits a `## Strategy Alignment` spec section listing which active tracks the plan serves. Drift surfaced as a `## Strategy drift flagged for review` block (read-only — never auto-supersedes; mirrors decision-record convention). +- **`/flow-next:interview` doc-aware mode reads `STRATEGY.md`** before terminology questions. Surfaces conflicts in a `## Strategy Conflicts` spec section parallel to existing `## Glossary Conflicts`. Throttle: ≤1 strategy-conflict question per interview turn (parallel to the existing glossary-question throttle). Behavior (e) added — code-versus-strategy contradiction. +- **`/flow-next:capture` Phase 0 reads `STRATEGY.md` as input.** Source-tags strategy-derived acceptance criteria as `[strategy:]` (joins existing `[user]` / `[paraphrase]` / `[inferred]` tags). Refuses to write spec contradicting an active track without `--override-strategy` flag. On flag fire: prompts user via `AskUserQuestion` to record a decision via `flowctl memory add --track knowledge --category decisions ...` (recommendation: yes; user can decline). Audit trail captured to stderr for future review. +- **`/flow-next:sync` (plan-sync agent) Step 5 reads `STRATEGY.md`.** Surfaces drift in a `## Strategy drift flagged for review` spec heading parallel to existing "Decision overrides flagged for review". NEVER auto-supersedes — read-only surface only. Track renames replace inline with a `` breadcrumb mirroring the existing glossary rename pattern. +- **Codex sync regenerated.** New `flow-next-strategy` openai.yaml entry (`Flow Strategy`, brand color `#3B82F6`); `REQUIRED_OPENAI_YAML_SKILLS` array updated to include the new skill. Canonical skill files use Claude-native `AskUserQuestion`; `sync-codex.sh` rewrites to `request_user_input` for the Codex mirror per repo convention. + +### Constraints +- **R1 — `STRATEGY.md` lives at the repo root.** Peer of `GLOSSARY.md` / `README.md`, never under `.flow/`. Survives a wipe of `.flow/` (R22 / R18 invariant). Frontmatter contains `name`, `last_updated`, `generator: flow-next-strategy` only. +- **R2 — Section structure locked.** 5 required + 2 optional, in CE 3.4's verbatim order. Optional sections deleted entirely if unused; never left as empty headers. Last-section deletion leaves a husk (H1 + frontmatter) — file never deleted (R23). +- **R7 — Single-root walk.** `flowctl strategy *` walks UP from cwd to first `STRATEGY.md` found, capped at repo root. NOT nearest-ancestor like glossary. Subdirectory invocation surfaces "Using repo-root STRATEGY.md at " before any interview question (R16). +- **R15 — Foreign-file refusal.** STRATEGY.md without `generator: flow-next-strategy` frontmatter routes via `AskUserQuestion` (migrate / keep / rewrite?). On "keep" — exits without writing. v1 explicitly defers automatic migration. +- **R17 — Ralph-block.** `/flow-next:strategy` exits 2 with stderr `[STRATEGY: user-triggered only — Ralph cannot run /flow-next:strategy]` when `FLOW_RALPH=1` or `REVIEW_RECEIPT_PATH` is set. Mirrors the `/flow-next:prospect` and `/flow-next:capture` precedent. +- **R19 — Tier 1 forbidden-vocab guard.** Separate from R17 DDD guard. `references/interview.md` excluded so it can describe anti-patterns. Two-tier (canonical + Codex mirror). + +### Smoke coverage +- `strategy_smoke_test.sh` (T1-T12) covers happy path + corner cases listed above. +- `ci_test.sh` (R19 canonical) gates `SKILL.md` + `commands/flow-next/strategy.md` + `cmd_strategy_*` regions of `flowctl.py`. +- `scripts/sync-codex.sh` validation block (R19 mirror) gates `plugins/flow-next/codex/skills/flow-next-strategy/`. +- Glossary smoke (`glossary_smoke_test.sh`) and `smoke_test.sh` stay green; `audit_smoke_test.sh` and `prospect_smoke_test.sh` unchanged. + +### Notes +- **Why repo-root STRATEGY.md, not `.flow/strategy.md`?** Survives a wipe of `.flow/`; peer of `README.md` / `CHANGELOG.md` / `GLOSSARY.md`; generic markdown tooling reads it. R18 invariant established by 0.39.0 glossary epic; the same rationale applies — strategic intent belongs to the project, not to flow-next. +- **Why single-root, NOT nearest-ancestor walk like glossary?** Strategy is repo-wide by Rumelt's definition (one diagnosis, one guiding policy, coherent action). Cascading per-subdirectory STRATEGY.md files re-introduce the "is for everyone, is for no one" problem the skill exists to prevent. Glossary cascades because vocabulary is local; strategy is global. +- **Why drop CE's `Marketing` section?** Over-rotated for OSS-tools repos — the marketplace manifest IS the distribution surface. Adding sections has cost; CE's principle 3 ("Short is a feature") supports the cut. +- **Why no `flowctl strategy add` plumbing?** Strategy is too prose-heavy for atomic field-set CLI. The skill running the interview IS the LLM that should write the file (per CLAUDE.md "agentic vs deterministic" architecture rule). Atomic CLI plumbing fits term-list / decision-record / memory shape but not prose-heavy strategy shape. +- **Why Tier 1 fluff vocab only (drop the `leverage` verb)?** Rumelt's source uses "leverage" as a noun in *Good Strategy Bad Strategy* — false-positive risk too high for `references/learn-more.md` prose. Tier 1 list is unambiguous. +- **Why foreign-file refusal in v1 (no migration)?** CE-format and hand-written `STRATEGY.md` files have ambiguous section mappings. Multi-format migration is a v2 problem; v1 ships the sentinel + refusal pattern, documents the limitation, lets early adopters delete-or-rename to bootstrap. + ## [flow-next 0.39.0] - 2026-04-30 ### Added @@ -86,7 +126,7 @@ All notable changes to the flow-next. ## [flow-next 0.37.0] - 2026-04-25 ### Added -- **`/flow-next:audit [mode:autofix] [scope hint]` — agent-native memory staleness review.** New skill that walks `.flow/memory/`, reviews each entry against the current codebase using the host agent's own Read/Grep/Glob tools, and decides per entry whether to **Keep / Update / Consolidate / Replace / Delete**. Inspired by upstream `compound-engineering`'s `ce-compound-refresh` skill — adapted for the categorized memory schema shipped in 0.33.0. The audit IS the agent: no Python audit engine, no codex/copilot subprocess dispatch, no deterministic scorer. The host agent reads the workflow markdown and executes it directly. Subagent dispatch documented for Claude Code (`Agent` + Explore), Codex (`spawn_agent` + explorer), and Droid; orchestrator falls back to main-thread investigation when subagent primitives are unavailable. +- **`/flow-next:audit [mode:autofix] [scope hint]` — agent-native memory staleness review.** New skill that walks `.flow/memory/`, reviews each entry against the current codebase using the host agent's own Read/Grep/Glob tools, and decides per entry whether to **Keep / Update / Consolidate / Replace / Delete**. Adapted to the categorized memory schema shipped in 0.33.0. The audit IS the agent: no Python audit engine, no codex/copilot subprocess dispatch, no deterministic scorer. The host agent reads the workflow markdown and executes it directly. Subagent dispatch documented for Claude Code (`Agent` + Explore), Codex (`spawn_agent` + explorer), and Droid; orchestrator falls back to main-thread investigation when subagent primitives are unavailable. - **Two modes:** **Interactive** (default) — agent asks decisions per entry via the platform's blocking-question tool (`AskUserQuestion` / `request_user_input` / `ask_user`). **Autofix** (`mode:autofix` token) — applies unambiguous Keep/Update/Consolidate/Replace/Delete actions and marks ambiguous entries as stale via `flowctl memory mark-stale`; this is the Ralph-safe path. Scope hint follows the mode token (`/flow-next:audit mode:autofix runtime-errors`). - **`flowctl memory mark-stale --reason "..." [--audited-by "..."] [--json]`** — sets `status: stale`, stamps `last_audited` (UTC date), records `audit_notes` from `--reason`. Atomic via existing `write_memory_entry`; body untouched. Idempotent: re-mark replaces `audit_notes` and re-stamps `last_audited`. Used by `/flow-next:audit`, also callable directly. JSON shape: `{success, id, path, status, last_audited, audit_notes}`. - **`flowctl memory mark-fresh [--audited-by "..."] [--json]`** — clears stale flag (drops `status`, `audit_notes`), stamps `last_audited`. Idempotent on already-active entries. @@ -107,7 +147,7 @@ All notable changes to the flow-next. ### Notes - **Legacy entries skipped.** Pre-fn-30 flat files (`pitfalls.md`, `conventions.md`, `decisions.md`) have no per-entry frontmatter to mutate, so `/flow-next:audit` skips them with a warning recommending `/flow-next:memory-migrate` first. The skipped count surfaces in the audit report. - **No silent deletes.** The `Delete` outcome is reserved for unambiguous cases (code gone AND problem domain gone). Ambiguous cases default to mark-stale; the entry stays on disk and shows up under `--status stale` until a future audit confirms removal. -- **Why agent-native, not flowctl Python?** flow-next runs inside an agentic environment (Claude Code / Codex / Droid). The host agent already reads files, runs grep, judges relevance, and writes updates with its own tools. Spawning a second LLM via subprocess is wasteful (cost + latency) and adds machinery — subprocess timeouts, structured-verdict parsers, drift guards — that disappears in the agent-native architecture. Upstream's `ce-compound-refresh` is the working reference. **fn-34 (audit) and fn-35 (memory-migrate) ship together as 0.37.0 — the same architectural correction applied to two parallel features.** Future Ralph hooks / receipts / triage-skip stay subprocess-based per the agentic-vs-deterministic guidance in CLAUDE.md (those run from non-agent contexts). +- **Why agent-native, not flowctl Python?** flow-next runs inside an agentic environment (Claude Code / Codex / Droid). The host agent already reads files, runs grep, judges relevance, and writes updates with its own tools. Spawning a second LLM via subprocess is wasteful (cost + latency) and adds machinery — subprocess timeouts, structured-verdict parsers, drift guards — that disappears in the agent-native architecture. **fn-34 (audit) and fn-35 (memory-migrate) ship together as 0.37.0 — the same architectural correction applied to two parallel features.** Future Ralph hooks / receipts / triage-skip stay subprocess-based per the agentic-vs-deterministic guidance in CLAUDE.md (those run from non-agent contexts). - **Why thin flowctl plumbing instead of skill-only?** The skills need deterministic atomic frontmatter writes (`mark-stale` / `mark-fresh` for audit; `memory add` + `memory list-legacy` for migrate), schema-validated round-trip, and consistent search filtering. Those are pure persistence concerns where flowctl shines. Split rule: flowctl owns "set this field on this entry" / "parse these legacy segments"; skill owns "should this entry be flagged" / "which (track, category) does this belong in." - Smoke suite: dedicated `plugins/flow-next/scripts/audit_smoke_test.sh` (13 cases, 41 assertions, ~5s runtime, zero LLM calls — covers Task 2 plumbing only since skills aren't unit-testable). `smoke_test.sh` (127, +1 for `list-legacy`), `prospect_smoke_test.sh` (94), `ralph_smoke_test.sh` (15) all stay green. Unit tests: 341 passing. diff --git a/CLAUDE.md b/CLAUDE.md index f12ac0e0..14296ca0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,7 +26,8 @@ Commands: - `/flow-next:resolve-pr [PR# | comment URL]` → resolve GitHub PR review threads (fetch → triage → dispatch resolver agents → validate → commit → reply → resolve via GraphQL). User-triggered only; Ralph does not invoke. Flags: `--dry-run`, `--no-cluster`. Parallel dispatch on Claude Code, serial on Codex/Copilot/Droid. Zero runtime deps beyond `gh` + `jq`. Added in 0.34.0. - `/flow-next:audit [mode:autofix] [scope hint]` → agent-native memory staleness review. Walks `.flow/memory/`, reviews each entry against current code, decides per entry: Keep / Update / Consolidate / Replace / Delete. Interactive (asks via blocking-question tool) or autofix (applies unambiguous, marks ambiguous as stale). Skips legacy flat files. The skill IS the agent — no Python engine, no subprocess dispatch. Added in 0.37.0. - `/flow-next:capture [mode:autofix] [--rewrite ] [--from-compacted-ok] [--yes]` → agent-native skill that synthesizes conversation context into a flow-next epic spec at `.flow/specs/.md` via existing `flowctl epic create + epic set-plan` (no new flowctl subcommands). Sits between free-form discussion / prospect-promotion and the formal plan/interview phase — the automated alternative to the manual `flowctl epic create + epic set-plan` heredoc. Hard guardrails: source-tagged criteria (`[user]` / `[paraphrase]` / `[inferred]`), mandatory read-back loop with `[inferred]` count, duplicate-epic detection (Phase 0 scans `.flow/epics/` + `flowctl memory search`), compaction detection (refuses without `--from-compacted-ok`), idempotency-via-`--rewrite`, must-ask cases for ambiguous title / untestable acceptance / scope-conflict, suggest-split at 8+ acceptance criteria (never auto-splits). Ralph-blocked. Added in 0.38.0. -- `/flow-next:interview` (enhanced in 0.38.0) folds three patterns from upstream `grill-me`: (a) lead-with-recommendation — every `AskUserQuestion` body includes options summary, recommended option, one-sentence rationale, confidence tier (`[high]` / `[judgment-call]` / `[your-call]`); (b) codebase-before-asking — pre-question taxonomy splits codebase-answerable ("what exists") from user-judgment-required ("what should"); codebase-answerable questions are investigated via Read/Grep/Glob and logged to a `## Resolved via Codebase` spec section; (c) dependency-ordered branches — depth cap 4, discover-as-you-go, surface abandoned branches. **Doc-aware mode (0.39.0+):** autodetects when `GLOSSARY.md` has at least one term (husks ignored) or `knowledge/decisions/` has entries; override via `--docs` / `--no-docs`. Four behaviors: glossary lookup before terminology questions (writes a `## Glossary Conflicts` spec section when user wording diverges from canonical), inline glossary write on resolution (`flowctl glossary add`), decision-record prompt with three-criteria gate + read-back when a load-bearing choice is made, code/spec contradiction surfaced rather than silently overwritten. +- `/flow-next:interview` (enhanced in 0.38.0) folds three patterns from upstream `grill-me`: (a) lead-with-recommendation — every `AskUserQuestion` body includes options summary, recommended option, one-sentence rationale, confidence tier (`[high]` / `[judgment-call]` / `[your-call]`); (b) codebase-before-asking — pre-question taxonomy splits codebase-answerable ("what exists") from user-judgment-required ("what should"); codebase-answerable questions are investigated via Read/Grep/Glob and logged to a `## Resolved via Codebase` spec section; (c) dependency-ordered branches — depth cap 4, discover-as-you-go, surface abandoned branches. **Doc-aware mode (0.39.0+, extended in 0.40.0):** autodetects when ANY of `GLOSSARY.md` has at least one term (husks ignored) OR `knowledge/decisions/` has entries OR `STRATEGY.md` has `sections_filled >= 1`; override via `--docs` / `--no-docs` (cascade to all three) and the explicit `--strategy` / `--no-strategy` pair which always wins over the cascade for the strategy slot (5-row flag matrix). Five behaviors: glossary lookup before terminology questions (writes a `## Glossary Conflicts` spec section when user wording diverges from canonical), inline glossary write on resolution (`flowctl glossary add`), decision-record prompt with three-criteria gate + read-back when a load-bearing choice is made, code/spec contradiction surfaced rather than silently overwritten, code-versus-strategy contradiction surfaced in a `## Strategy Conflicts` spec section parallel to `## Glossary Conflicts` (≤1 strategy-conflict question per turn, gated on `STRATEGY_AWARE=1`). +- `/flow-next:strategy [optional: section to revisit]` → agent-native skill that writes/maintains a repo-root `STRATEGY.md` (peer of `GLOSSARY.md` / `README.md`, never under `.flow/`). 5 required sections (`Target problem` / `Our approach` / `Who it's for` / `Key metrics` / `Tracks`) + 2 optional (`Milestones` / `Not working on`); CE's `Marketing` section dropped. Atomic per-section writes; `last_updated` bumps on every save. Foreign-file refusal via `generator: flow-next-strategy` frontmatter sentinel. Pushback discipline (2 rounds max per section). Single-root walk from cwd up to repo root (NOT nearest-ancestor like glossary — strategy is repo-wide by Rumelt's definition). Ralph-blocked. Downstream skills (prospect / plan / interview / capture / sync) consume `STRATEGY.md` read-only. Added in 0.40.0. Review backend spec grammar (v0.31.0+): - `backend[:model[:effort]]` — colon-delimited, trailing parts optional @@ -111,6 +112,28 @@ Project glossary (v0.39.0+): - Plan-sync contract: glossary renames replace `_Avoid_` aliases inline with the canonical term + breadcrumb (``); decision-record overrides are surfaced read-only under "Decision overrides flagged for review" — sync **never auto-supersedes** explicit historical choices. - Forbidden vocabulary (R17): a small list of jargon terms is grep-guarded out of canonical skill / agent / command / flowctl prose by `ci_test.sh` section 5c, and out of the Codex mirror by `scripts/sync-codex.sh` validation block. Two-tier guard prevents drift through either source path. +Project strategy (v0.40.0+): +- `STRATEGY.md` lives at the **repo root** (peer of `GLOSSARY.md` / `README.md`), NOT inside `.flow/`. Survives `rm -rf .flow/` — strategic intent is the project's, not flow-next's (R1 / R22 — survives uninstall by design, mirrors the glossary R18 invariant). +- **Single-root resolution (NOT nearest-ancestor like glossary).** `flowctl strategy *` resolves the repo root from cwd via `git rev-parse --show-toplevel` and checks for `STRATEGY.md` ONLY at that root — no upward walk, no cascade. An `apps/web/STRATEGY.md` (intentional or accidental) is always ignored; downstream skills consume the repo-root file regardless of cwd. Strategy is repo-wide by Rumelt's definition (one diagnosis, one guiding policy, coherent action) — multiple cascading STRATEGY.md files re-introduce the "is for everyone, is for no one" problem the skill exists to prevent. Glossary cascades because vocabulary is local; strategy is global. Subdirectory invocation surfaces `Using repo-root STRATEGY.md at ` before any interview question. +- **Frontmatter (3 keys only):** `name`, `last_updated` (ISO date), `generator: flow-next-strategy`. The generator key is the foreign-file sentinel — without it the skill prompts (migrate / keep / rewrite?). Multi-format migration deferred to v2. +- **Section structure (locked, Rumelt-aligned):** 5 required (`Target problem` / `Our approach` / `Who it's for` / `Key metrics` / `Tracks`) + 2 optional (`Milestones` / `Not working on`). Maps onto Rumelt's strategy kernel — diagnosis / guiding policy / coherent action — extended with persona + metrics for repo-doc utility. A `Marketing` section was considered and dropped (over-rotated for OSS-tools repos). Optional sections deleted entirely if unused; never left as empty headers. Last-section deletion leaves a husk (H1 + frontmatter) — file never deleted (R23). +- **Subcommands (read-only plumbing — no `add/edit/remove`):** + - `flowctl strategy status [--json]` — `{exists, husk, sections_filled, total_sections, last_updated, file_path}`. Husk: file exists but `sections_filled == 0`. + - `flowctl strategy read [--section ] [--json]` — single-root walk; returns `{path, name, last_updated, target_problem, approach, personas, metrics, tracks, milestones, not_working_on}`; `--section` filters one block. + - `flowctl strategy list [--json]` — parallel to `flowctl glossary list` (degenerate single-element group for v1) so downstream skills can iterate generically. + - No `flowctl strategy add/edit` — strategy is too prose-heavy for atomic field-set CLI; the skill IS the editor (per the agentic-vs-deterministic architecture rule). +- **Husk semantics:** `sections_filled == 0` (or `file_count == 0`) means no strategic-intent signal. Doc-aware autodetect uses `flowctl strategy status --json | jq '.sections_filled >= 1'`, NOT `[[ -f STRATEGY.md ]]` — a plain file-existence check would falsely activate on an empty husk (same trap glossary fell into). +- **Doc-aware autodetect — third condition.** Doc-aware mode now activates when ANY of: `glossary.total_terms > 0` OR `knowledge/decisions/` has entries OR `strategy.sections_filled >= 1`. Override flags follow a cascade-with-explicit-override rule: `--docs` / `--no-docs` cascade to all three categories (glossary + decisions + strategy); explicit `--strategy` / `--no-strategy` always wins over the cascade for the strategy slot. 5-row matrix: `(default)` autodetect all three; `--docs` on for all three; `--no-docs` off for all three; `--no-docs --strategy` strategy on / glossary+decisions off; `--docs --no-strategy` glossary+decisions on / strategy off. +- **Downstream consumers (read-only, advisory, never auto-supersede):** + - `/flow-next:prospect` Phase 0 grounding — injects approach + active tracks verbatim into candidate-generation prompt; adds `out-of-scope-vs-strategy` to the rejection taxonomy. + - `/flow-next:plan` research scan — emits a `## Strategy Alignment` spec section listing which active tracks the plan serves; drift surfaced as `## Strategy drift flagged for review` block (read-only). + - `/flow-next:interview` doc-aware — surfaces conflicts in a `## Strategy Conflicts` spec section parallel to `## Glossary Conflicts`; ≤1 strategy-conflict question per turn. + - `/flow-next:capture` Phase 0 — source-tags strategy-derived acceptance criteria as `[strategy:]`; refuses to write spec contradicting an active track without `--override-strategy` (which prompts for a decision record). + - `/flow-next:sync` plan-sync — surfaces drift in a `## Strategy drift flagged for review` heading; track renames replace inline with a `` breadcrumb. NEVER auto-supersedes. +- **Foreign-file refusal in v1.** `STRATEGY.md` without the `generator: flow-next-strategy` frontmatter sentinel routes via `AskUserQuestion` (migrate / keep / rewrite?). On "keep" — exits without writing. On "rewrite" — confirms via second prompt before destructive overwrite. v1 explicitly defers automatic migration of CE-format / hand-written files to v2. +- **Forbidden vocabulary (R19 — separate from R17 DDD).** Tier 1 jargon only — Rumelt's "fluff" hallmarks: `synergy / pivot / disrupt / thought-leadership / best-in-class / world-class / 10x`. Two-tier guard: canonical scan in `ci_test.sh` (separate block from R17 — comment specifies "strategy-doc fluff guard, NOT R17"; never merge them) + Codex-mirror scan in `sync-codex.sh` validation block. The `references/interview.md` file is excluded — must describe anti-patterns to push back on them (same exemption as glossary references). +- **Ralph-blocked.** `/flow-next:strategy` exits 2 with stderr `[STRATEGY: user-triggered only — Ralph cannot run /flow-next:strategy]` when `FLOW_RALPH=1` or `REVIEW_RECEIPT_PATH` is set. Mirrors `/flow-next:prospect` and `/flow-next:capture`. Autonomous loops have no business deciding repo strategy. + ### flow Original plugin with optional Beads integration. Plan files in `plans/`. @@ -195,9 +218,9 @@ Symptoms that suggest you're building deterministic when you should build skill- If three or more of these apply, stop and convert to a skill. The deterministic path is harder to maintain, more brittle, and provides worse output than the agent does directly. -### Reference: what the upstream `compound-engineering` plugin does +### Reference: the agent-native "audit my docs" pattern -`compound-engineering`'s `ce-compound-refresh` skill is a working reference for the agent-native pattern. The entire "audit my docs against the codebase" workflow is a markdown skill file the host agent reads + executes. No Python, no subprocess, no engine. flow-next's `/flow-next:audit` (fn-34) is modeled on this. +The entire `/flow-next:audit` workflow (fn-34) is a markdown skill file the host agent reads + executes — no Python, no subprocess, no engine. The skill IS the agent. flow-next's `/flow-next:audit` is the canonical example of this pattern; `/flow-next:memory-migrate` (fn-35) and `/flow-next:strategy` (fn-39) follow the same shape. ## Cross-platform patterns (Claude Code + Codex + Factory Droid) diff --git a/README.md b/README.md index 4e400c35..7388599a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # Flow-Next [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) -[![Flow-next](https://img.shields.io/badge/Flow--next-v0.39.0-green)](plugins/flow-next/) +[![Flow-next](https://img.shields.io/badge/Flow--next-v0.40.0-green)](plugins/flow-next/) [![Docs](https://img.shields.io/badge/Docs-📖-informational)](plugins/flow-next/README.md) [![Author](https://img.shields.io/badge/Author-Gordon_Mickel-orange)](https://mickel.tech) @@ -25,7 +25,7 @@ Flow-Next is an AI agent orchestration plugin. **Sixteen agent-native skills** f First-class on **Claude Code**, **OpenAI Codex** (CLI + Desktop), and **Factory Droid**. Also runs on **OpenCode** via the [community port](https://github.com/gmickel/flow-next-opencode). -> 🆕 **v0.39.0 — Project glossary + decision records + doc-aware interview.** New `GLOSSARY.md` artifact at the repo root (survives `rm -rf .flow/`) with `flowctl glossary add/list/read/remove` and nearest-ancestor walk. New `knowledge/decisions/` memory category with `decision_status` lifecycle (proposed → accepted → superseded). `/flow-next:interview` autodetects doc-aware mode (`--docs` / `--no-docs` to override) — looks up canonical terms before asking, surfaces conflicts to a `## Glossary Conflicts` spec section, prompts for decision records on load-bearing choices, surfaces code/spec contradictions instead of silently overwriting. `/flow-next:audit` walks glossary + decisions; `/flow-next:sync` flags decision overrides read-only (never auto-supersedes). [Full changelog](CHANGELOG.md). +> 🆕 **v0.40.0 — Project strategy anchor.** New `/flow-next:strategy` skill writes/maintains a repo-root `STRATEGY.md` (peer of `GLOSSARY.md` / `README.md`, never under `.flow/` — survives `rm -rf .flow/`). Section structure derived from Richard Rumelt's strategy kernel (diagnosis / guiding policy / coherent action): 5 required sections (`Target problem` / `Our approach` / `Who it's for` / `Key metrics` / `Tracks`) + 2 optional (`Milestones` / `Not working on`). `flowctl strategy status / read / list` plumbing; the skill IS the editor (no `add/edit` subcommands — strategy is too prose-heavy for atomic CLI). Single-root resolution at repo root only (NOT nearest-ancestor like glossary — strategy is repo-wide by Rumelt's definition). Doc-aware autodetect extended with a third condition (`strategy.sections_filled >= 1`); 5-row flag matrix where `--docs` / `--no-docs` cascade to all three categories and explicit `--strategy` / `--no-strategy` always wins over the cascade. Downstream `/flow-next:prospect` / `:plan` / `:interview` / `:capture` / `:sync` all consume `STRATEGY.md` read-only. Tier 1 fluff guard (R19) added in CI canonical + Codex mirror. Ralph-blocked. [Full changelog](CHANGELOG.md). > 🌐 **[Visual overview at mickel.tech/apps/flow-next](https://mickel.tech/apps/flow-next)** — diagrams, examples, the full feature tour. diff --git a/plugins/flow-next/.claude-plugin/plugin.json b/plugins/flow-next/.claude-plugin/plugin.json index 9b703db1..88cb3196 100644 --- a/plugins/flow-next/.claude-plugin/plugin.json +++ b/plugins/flow-next/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "flow-next", - "version": "0.39.0", - "description": "Zero-dependency planning + execution with .flow/ task tracking and Ralph autonomous mode (multi-model review gates). Worker subagent per task for context isolation. Prime assesses 8 pillars (48 criteria) with GitHub API integration. Includes 21 subagents, 13 commands, 18 skills.", + "version": "0.40.0", + "description": "Zero-dependency planning + execution with .flow/ task tracking and Ralph autonomous mode (multi-model review gates). Worker subagent per task for context isolation. Prime assesses 8 pillars (48 criteria) with GitHub API integration. Includes 21 subagents, 17 commands, 22 skills.", "author": { "name": "Gordon Mickel", "email": "gordon@mickel.tech", diff --git a/plugins/flow-next/.codex-plugin/plugin.json b/plugins/flow-next/.codex-plugin/plugin.json index 3b10f83b..1724f6ef 100644 --- a/plugins/flow-next/.codex-plugin/plugin.json +++ b/plugins/flow-next/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "flow-next", - "version": "0.39.0", + "version": "0.40.0", "description": "Zero-dependency planning + execution with .flow/ task tracking and Ralph autonomous mode. Worker subagent per task for context isolation. Compatible with Codex, Claude Code, and Factory Droid.", "author": { "name": "Gordon Mickel", @@ -21,7 +21,7 @@ "interface": { "displayName": "Flow-Next", "shortDescription": "Plan-first workflow with subagent execution", - "longDescription": "Structured plan-first workflow engine. Tracks epics and tasks in .flow/ directory. Spawns isolated worker subagents per task to prevent context bleed. Ralph mode enables fully autonomous execution with multi-model review gates. Prime command assesses 8 pillars (48 criteria) for agent readiness. 21 subagents, 18 skills.", + "longDescription": "Structured plan-first workflow engine. Tracks epics and tasks in .flow/ directory. Spawns isolated worker subagents per task to prevent context bleed. Ralph mode enables fully autonomous execution with multi-model review gates. Prime command assesses 8 pillars (48 criteria) for agent readiness. 21 subagents, 22 skills.", "developerName": "Gordon Mickel", "category": "Productivity", "capabilities": [ diff --git a/plugins/flow-next/README.md b/plugins/flow-next/README.md index 4bdfdc71..4af81452 100644 --- a/plugins/flow-next/README.md +++ b/plugins/flow-next/README.md @@ -6,7 +6,7 @@ [![Claude Code](https://img.shields.io/badge/Claude_Code-Plugin-blueviolet)](https://claude.ai/code) [![OpenAI Codex](https://img.shields.io/badge/OpenAI_Codex-Plugin-10a37f)](https://developers.openai.com/codex/cli/) -[![Version](https://img.shields.io/badge/Version-0.39.0-green)](../../CHANGELOG.md) +[![Version](https://img.shields.io/badge/Version-0.40.0-green)](../../CHANGELOG.md) [![Status](https://img.shields.io/badge/Status-Active_Development-brightgreen)](../../CHANGELOG.md) [![Discord](https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white)](https://discord.gg/f3DYq8AAm5) @@ -21,16 +21,16 @@ 🌐 **Prefer a visual overview?** See the [Flow-Next app page](https://mickel.tech/apps/flow-next) for diagrams and examples. -> **What's new in 0.39.0:** Project glossary + decision records + doc-aware interview. New `GLOSSARY.md` artifact at the repo root (survives `rm -rf .flow/`) with `flowctl glossary add/list/read/remove` and nearest-ancestor walk. New `knowledge/decisions/` memory category with `decision_status` lifecycle. `/flow-next:interview` autodetects doc-aware mode (`--docs` / `--no-docs` to override) — looks up canonical terms before asking, surfaces conflicts to a `## Glossary Conflicts` spec section, prompts for decision records on load-bearing choices. `/flow-next:audit` walks glossary + decisions; `/flow-next:sync` flags decision overrides read-only (never auto-supersedes). Two-tier R17 + R4 grep guard added in CI. [Full changelog](../../CHANGELOG.md). +> **What's new in 0.40.0:** Project strategy anchor. New `/flow-next:strategy` skill writes/maintains a repo-root `STRATEGY.md` (peer of `GLOSSARY.md` / `README.md`, never under `.flow/` — survives `rm -rf .flow/`) with 5 required sections (`Target problem` / `Our approach` / `Who it's for` / `Key metrics` / `Tracks`) + 2 optional (`Milestones` / `Not working on`). Structure derived from Richard Rumelt's strategy kernel (diagnosis / guiding policy / coherent action). `flowctl strategy status / read / list` plumbing (read-only — the skill IS the editor for prose). Single-root resolution at repo root only (NOT nearest-ancestor like glossary — strategy is repo-wide by Rumelt's definition). Doc-aware autodetect extended with a third condition (`strategy.sections_filled >= 1`); 5-row flag matrix where `--docs` / `--no-docs` cascade to all three categories and explicit `--strategy` / `--no-strategy` always wins over the cascade. Downstream skills (`/flow-next:prospect` / `:plan` / `:interview` / `:capture` / `:sync`) consume `STRATEGY.md` read-only — `## Strategy Alignment`, `## Strategy Conflicts`, `## Strategy drift flagged for review` spec sections; `[strategy:]` source-tag in capture; never auto-supersedes. Tier 1 fluff guard (R19): `synergy / pivot / disrupt / thought-leadership / best-in-class / world-class / 10x` blocked in canonical + Codex mirror via two-tier grep (separate block from R17 DDD). Ralph-blocked. [Full changelog](../../CHANGELOG.md). > -> Recent highlights: [capture skill](#capture) for conversation-to-spec synthesis (0.38.0), [interview grill-me patterns](#flow-nextinterview) (0.38.0), agent-native [memory audit](#memory-system) (0.37.0), [memory migrate skill](#memory-system) (0.37.0), [PR feedback resolver](#pr-feedback-resolution) (0.34.0), [prospect skill](#prospecting) for ranked candidate ideation (0.36.0). +> Recent highlights: [project glossary + decision records + doc-aware interview](#project-glossary) (0.39.0), [capture skill](#capture) for conversation-to-spec synthesis (0.38.0), [interview grill-me patterns](#flow-nextinterview) (0.38.0), agent-native [memory audit](#memory-system) (0.37.0), [memory migrate skill](#memory-system) (0.37.0), [PR feedback resolver](#pr-feedback-resolution) (0.34.0), [prospect skill](#prospecting) for ranked candidate ideation (0.36.0). --- ## Table of Contents - [What Is This?](#what-is-this) -- [The Workflow](#the-workflow-ladder) — Idea → spec → tasks → ship → maintain +- [The Workflow](#the-workflow) — Strategy → idea → spec → tasks → ship → maintain - [Why It Works](#why-it-works) - [Quick Start](#quick-start) — Install, setup, use - [When to Use What](#when-to-use-what) — Prospect / Capture / Interview / Plan @@ -38,6 +38,7 @@ - [Capture](#capture) — `/flow-next:capture` - [Memory System](#memory-system) — `/flow-next:audit` + `/flow-next:memory-migrate` - [Project Glossary](#project-glossary) — `flowctl glossary` + doc-aware interview +- [Project Strategy](#project-strategy) — `/flow-next:strategy` + `flowctl strategy` + downstream grounding - [Agent Readiness Assessment](#agent-readiness-assessment) — `/flow-next:prime` - [PR Feedback Resolution](#pr-feedback-resolution) — `/flow-next:resolve-pr` - [Cross-Model Reviews](#cross-model-reviews) — RepoPrompt / Codex / Copilot @@ -54,7 +55,7 @@ ## What Is This? -Flow-Next is a plugin for **agent-native AI orchestration**. Sixteen slash commands cover the full lifecycle: idea generation (`prospect`) → spec creation (`capture`) → refinement (`interview`) → planning (`plan`) → execution (`work`) → review (`impl-review` + `epic-review`) → PR feedback resolution (`resolve-pr`) → maintenance (`audit` + `memory-migrate`) → autonomous mode (`ralph-init`). Bundled task tracking, dependency graphs, re-anchoring, and cross-model reviews. +Flow-Next is a plugin for **agent-native AI orchestration**. Seventeen slash commands cover the full lifecycle: strategic anchor (`strategy`) → idea generation (`prospect`) → spec creation (`capture`) → refinement (`interview`) → planning (`plan`) → execution (`work`) → review (`impl-review` + `epic-review`) → PR feedback resolution (`resolve-pr`) → maintenance (`audit` + `memory-migrate`) → autonomous mode (`ralph-init`). Bundled task tracking, dependency graphs, re-anchoring, and cross-model reviews. Everything lives in your repo. No external services. No global config. Uninstall: delete `.flow/` (and `scripts/ralph/` if enabled). @@ -203,8 +204,11 @@ flowctl ready --epic fn-1 # What's ready to work on ### 3. Use ```bash -# Spec: "create a spec for X" — writes epic with structured requirements -# Then plan or interview to refine +# (Optional) Strategy: anchor problem/approach/tracks for downstream grounding +/flow-next:strategy + +# (Optional) Prospect: rank candidate ideas grounded in repo + strategy +/flow-next:prospect # Plan: research, create epic with tasks /flow-next:plan Add a contact form with validation @@ -218,6 +222,8 @@ flowctl ready --epic fn-1 # What's ready to work on That's it. Flow-Next handles research, task ordering, reviews, and audit trails. +**Already know what you want to build?** Skip strategy and prospect — go straight to plan or work. Strategy and prospect are upstream grounding tools, not gates. See [When to Use What](#when-to-use-what) for the full route map. + ### When to Use What Flow-next is flexible. There's no single "correct" order — the right sequence depends on how well-defined your spec already is. @@ -286,8 +292,9 @@ Best for: bug fixes, small features, well-scoped changes that don't need task sp | Starting point | Recommended sequence | |----------------|---------------------| -| No target yet, want ranked candidates | Prospect → (promote) → Plan → Work ([details](#prospecting)) | -| Prospect survivor needs richer spec | Prospect → Capture → Interview/Plan → Work | +| Want a strategic anchor for the whole repo | Strategy → (everything below grounds against it) ([details](#project-strategy)) | +| No target yet, want ranked candidates | (Strategy) → Prospect → (promote) → Plan → Work ([details](#prospecting)) | +| Prospect survivor needs richer spec | (Strategy) → Prospect → Capture → Interview/Plan → Work | | Conversation already in flight | Capture → Interview/Plan → Work | | Free-form discussion, lock it down | Capture → Plan → Work | | New feature, want solid spec first | Spec → Interview/Plan → Work | @@ -296,12 +303,21 @@ Best for: bug fixes, small features, well-scoped changes that don't need task sp | Well-understood, needs task splitting | Plan → Work | | Small single-task, spec complete | Work directly (creates 1 epic + 1 task) | -**Prospect vs Capture vs Spec vs Interview vs Plan:** +Strategy is **upstream of every route** — set it once, every downstream skill (prospect / plan / interview / capture / sync) reads `STRATEGY.md` as advisory grounding. Skip it if you don't want a strategic anchor; everything still works the same way as before 0.40.0. See [Project Strategy](#project-strategy) for details. + +**Glossary and decisions build incrementally** as a side effect of any route — run `/flow-next:interview --docs` (or just rely on the autodetect once you have one term or one decision on file) and: +- Terminology conflicts surface as a `## Glossary Conflicts` spec section; resolving with `update-glossary` writes the canonical term inline via `flowctl glossary add` +- Load-bearing choices (hard-to-reverse / surprising-without-context / real-trade-off) trigger a three-criteria gate that writes a decision record to `knowledge/decisions/` after a mandatory read-back + +You don't need a dedicated "build my glossary" or "build my decisions" route — these doc-aware artifacts accumulate naturally through interview-driven spec refinement. Hand-write `GLOSSARY.md` directly or use `flowctl glossary add` if you prefer; same destination. See [Project Glossary](#project-glossary) for the full mechanic. + +**Strategy vs Prospect vs Capture vs Spec vs Interview vs Plan:** +- **Strategy** (`/flow-next:strategy`) writes/maintains a repo-root `STRATEGY.md` (target problem, approach, personas, key metrics, tracks). Run once early, revisit per-section as direction shifts. Downstream skills read it as advisory grounding (never auto-supersedes). Optional — skip if your repo has no strategic intent worth recording. - **Prospect** (`/flow-next:prospect [hint]`) generates many candidate ideas, critiques each one, and writes a ranked artifact under `.flow/prospects/`. Use when you don't have a target yet. Promote a survivor to an epic via `flowctl prospect promote` (direct path to plan), or hand the survivor to `/flow-next:capture` for a richer conversation-driven spec. -- **Capture** (`/flow-next:capture`) synthesizes conversation context into an epic spec — the automated alternative to manual `flowctl epic create + epic set-plan`. Use after prospect-promotion or after a free-form design discussion. Source-tags every acceptance criterion (`[user]` / `[paraphrase]` / `[inferred]`); mandatory read-back loop; never silently invents requirements. Output goes to `.flow/specs/.md`. +- **Capture** (`/flow-next:capture`) synthesizes conversation context into an epic spec — the automated alternative to manual `flowctl epic create + epic set-plan`. Use after prospect-promotion or after a free-form design discussion. Source-tags every acceptance criterion (`[user]` / `[paraphrase]` / `[inferred]` / `[strategy:]`); mandatory read-back loop; never silently invents requirements. Output goes to `.flow/specs/.md`. - **Spec** (just ask "create a spec") creates an epic with structured requirements (goal, architecture, API contracts, edge cases, acceptance criteria, boundaries). Same destination as capture, but the manual heredoc path — useful for scripted callers. -- **Interview** refines an epic via deep Q&A (40+ questions). Writes back to the epic spec only — no tasks. -- **Plan** researches best practices, analyzes existing patterns, and creates sized tasks with dependencies. +- **Interview** refines an epic via deep Q&A (40+ questions). Doc-aware mode reads glossary + decisions + strategy and surfaces conflicts. Writes back to the epic spec only — no tasks. +- **Plan** researches best practices, analyzes existing patterns, and creates sized tasks with dependencies. Reads strategy if present and emits a `## Strategy Alignment` section listing which active tracks the plan serves. You can always run interview again after planning to catch anything missed. Interview writes back to the epic spec only — it won't modify existing tasks. @@ -1756,12 +1772,57 @@ flowctl glossary remove --- +## Project Strategy + +`STRATEGY.md` is a project-canonical strategic-intent file shipped in v0.40.0. Lives at the **repo root** (peer of `GLOSSARY.md` / `README.md`), NOT inside `.flow/`. Survives `rm -rf .flow/` — strategic intent is the project's, not flow-next's (R1 / R22, mirrors the glossary R18 invariant). Section structure derived from Richard Rumelt's strategy kernel (*Good Strategy Bad Strategy*: diagnosis / guiding policy / coherent action), extended with persona + metrics for repo-doc utility. `/flow-next:strategy` is the skill that writes/maintains it — no `flowctl strategy add/edit` plumbing because strategy is too prose-heavy for atomic field-set CLI; the skill IS the editor. + +**Format:** Plain GFM markdown. Frontmatter contains 3 keys only — `name`, `last_updated` (ISO date), `generator: flow-next-strategy`. The generator key is the foreign-file sentinel. 5 required sections (`Target problem` / `Our approach` / `Who it's for` / `Key metrics` / `Tracks`) + 2 optional (`Milestones` / `Not working on`). CE's `Marketing` section explicitly NOT included — over-rotated for OSS-tools repos. Optional sections deleted entirely if unused; never left as empty headers. + +**Resolution:** Single-root walk from cwd UP to first `STRATEGY.md` found, capped at repo root via `git rev-parse --show-toplevel`. NOT nearest-ancestor like glossary — strategy is repo-wide by Rumelt's definition (one diagnosis, one guiding policy, coherent action). Subdirectory invocation surfaces `Using repo-root STRATEGY.md at ` before any interview question fires; does NOT create per-subdirectory STRATEGY.md files. + +**Subcommands:** + +```bash +# Status — exists / husk / sections_filled / total_sections / last_updated +flowctl strategy status +flowctl strategy status --json # {exists, husk, sections_filled, total_sections, last_updated, file_path} + +# Read — single-root walk; full file or one section +flowctl strategy read +flowctl strategy read --section approach +flowctl strategy read --json # {path, name, last_updated, target_problem, approach, personas, metrics, tracks, milestones, not_working_on} + +# List — parallel to flowctl glossary list (degenerate single-element group for v1) +flowctl strategy list --json # {groups, file_count, total_sections} +``` + +NO `flowctl strategy add/edit/remove`. Strategy editing happens via `/flow-next:strategy` — the skill running the interview IS the LLM that should write the file (per the agentic-vs-deterministic architecture rule). Atomic CLI plumbing fits term-list / decision-record / memory shape but not prose-heavy strategy shape. + +**Husk semantics:** A file with H1 + frontmatter only and no populated H2 sections returns `{exists: true, husk: true, sections_filled: 0}` from `flowctl strategy status`. Last-section deletion leaves a husk on disk — the file is **never** deleted (R23, mirrors `render_glossary_file`). Doc-aware autodetect should branch on `flowctl strategy status --json | jq '.sections_filled >= 1'`, NOT on `[[ -f STRATEGY.md ]]` — same trap glossary fell into. + +**How the rest of flow-next uses it:** + +- **`/flow-next:prospect`** Phase 0 grounding scan reads `STRATEGY.md` when `sections_filled >= 1`. Injects approach + active tracks verbatim into candidate-generation prompt (mirrors CE-ideate's "emit approach and active tracks verbatim" pattern). Adds `out-of-scope-vs-strategy` to the rejection taxonomy. Surfaced as advisory at prospect phase — never auto-rejects. +- **`/flow-next:plan`** research scan reads `STRATEGY.md`. Plan emits a `## Strategy Alignment` spec section listing which active tracks the plan serves. Drift surfaced as a `## Strategy drift flagged for review` block (read-only — never auto-supersedes; mirrors decision-record convention). +- **`/flow-next:interview`** doc-aware mode reads `STRATEGY.md` before terminology questions. Surfaces conflicts in a `## Strategy Conflicts` spec section parallel to `## Glossary Conflicts`. Throttle: ≤1 strategy-conflict question per interview turn (parallel to existing glossary-question throttle). Behavior (e) — code-versus-strategy contradiction. +- **`/flow-next:capture`** Phase 0 reads `STRATEGY.md` as input. Source-tags strategy-derived acceptance criteria as `[strategy:]` (joins existing `[user]` / `[paraphrase]` / `[inferred]` tags). Refuses to write spec contradicting an active track without `--override-strategy` flag. On flag fire: prompts user via `AskUserQuestion` to record a decision via `flowctl memory add --track knowledge --category decisions ...` (recommendation: yes; user can decline). Audit trail captured for future review. +- **`/flow-next:sync`** (plan-sync agent) Step 5 reads `STRATEGY.md`. Surfaces drift in a `## Strategy drift flagged for review` heading parallel to existing "Decision overrides flagged for review". NEVER auto-supersedes — read-only surface only. Track renames replace inline with a `` breadcrumb mirroring the glossary rename pattern. + +**Foreign-file refusal in v1:** A `STRATEGY.md` without `generator: flow-next-strategy` frontmatter (or with a different generator value) routes via `AskUserQuestion` (migrate / keep / rewrite?). On "keep" — exits without writing. On "rewrite" — confirms via second prompt before destructive overwrite. Multi-format migration (CE-format / hand-written) explicitly deferred to v2; v1 ships the sentinel + refusal pattern, lets early adopters delete-or-rename to bootstrap. + +**Forbidden vocabulary (R19, separate from R17 DDD):** Tier 1 jargon only — Rumelt's "fluff" hallmarks: `synergy / pivot / disrupt / thought-leadership / best-in-class / world-class / 10x`. Two-tier guard: canonical scan in `ci_test.sh` (separate block from R17 — comment specifies "strategy-doc fluff guard, NOT R17"; never merge them) covers `flow-next-strategy/SKILL.md` + `cmd_strategy_*` regions in `flowctl.py` + `commands/flow-next/strategy.md`; mirror scan in `scripts/sync-codex.sh` validation block covers `plugins/flow-next/codex/skills/flow-next-strategy/`. The `references/interview.md` file is excluded — it must describe these anti-patterns to push back on them (same exemption as glossary references). + +**Why no migration in v1:** CE-format and hand-written `STRATEGY.md` files have ambiguous section mappings. Multi-format migration is a v2 problem. v1 ships the `generator: flow-next-strategy` sentinel + refusal pattern, documents the limitation, lets early adopters delete-or-rename to bootstrap. The skill prompts before any destructive overwrite. + +--- + ## Commands -Sixteen commands, complete workflow: +Seventeen commands, complete workflow: | Command | What It Does | |---------|--------------| +| `/flow-next:strategy [section]` | Generate or update repo-root `STRATEGY.md` (problem / approach / personas / metrics / tracks); read-only consumed by prospect/plan/interview/capture/sync ([details](#project-strategy)) | | `/flow-next:prospect [hint]` | Generate ranked candidate ideas grounded in the repo, upstream of `capture`/`interview`/`plan` ([details](#prospecting)) | | `/flow-next:capture [flags]` | Synthesize conversation context into an epic spec; source-tagged + mandatory read-back ([details](#capture)) | | `/flow-next:plan ` | Research the codebase, create epic with dependency-ordered tasks | @@ -1810,7 +1871,8 @@ Natural language also works: |---------|-----------------| | `/flow-next:prospect` | `[focus hint]` (positional) — concept / path / constraint / volume | | `/flow-next:capture` | `mode:autofix` (positional), `--rewrite `, `--from-compacted-ok`, `--yes` | -| `/flow-next:interview` | `--docs` / `--no-docs` (override doc-aware autodetect, v0.39.0+) | +| `/flow-next:strategy` | `[section to revisit]` (positional) — empty = full interview; section name = re-run that section only | +| `/flow-next:interview` | `--docs` / `--no-docs` (override doc-aware autodetect, v0.39.0+); `--strategy` / `--no-strategy` (independent override for strategy signal, v0.40.0+; 5-row flag matrix) | | `/flow-next:plan` | `--research=rp\|grep`, `--review=rp\|codex\|copilot\|export\|none`, `--no-review` | | `/flow-next:work` | `--branch=current\|new\|worktree`, `--review=rp\|codex\|copilot\|export\|none`, `--no-review` | | `/flow-next:plan-review` | `--review=rp\|codex\|copilot\|export` | @@ -1912,23 +1974,36 @@ Deep questioning (40+ questions) to surface requirements, edge cases, and decisi These three patterns are additive enhancements to **how** questions are asked, not what gets asked. Existing 40+ question coverage is unchanged. -**Doc-aware mode (0.39.0+):** +**Doc-aware mode (0.39.0+, extended in 0.40.0):** -Autodetects when `GLOSSARY.md` has at least one term (husks ignored — branches on `flowctl glossary list --json | jq '.total_terms > 0'`, NOT plain file existence) or `knowledge/decisions/` has at least one entry. Override via: +Autodetects when ANY of three conditions has signal: `GLOSSARY.md` has at least one term (husks ignored — branches on `flowctl glossary list --json | jq '.total_terms > 0'`, NOT plain file existence) OR `knowledge/decisions/` has at least one entry OR `STRATEGY.md` has `sections_filled >= 1` (branches on `flowctl strategy status --json | jq '.sections_filled >= 1'`, NOT plain file existence — same husk trap). Override via two flag pairs: | Flag | Description | |------|-------------| -| `--docs` | Force doc-aware mode on (even if autodetect says off) | -| `--no-docs` | Force doc-aware mode off (skip glossary lookup + decision-record prompts) | +| `--docs` | Force doc-aware mode on; cascades to all three (glossary + decisions + strategy) unless `--no-strategy` is also passed | +| `--no-docs` | Force doc-aware mode off; cascades to all three (glossary + decisions + strategy) unless `--strategy` is also passed | +| `--strategy` | Force strategy-aware mode on. Always wins over the `--docs` / `--no-docs` cascade for strategy. | +| `--no-strategy` | Force strategy-aware mode off (skip strategy scan + `## Strategy Conflicts` section). Always wins over the `--docs` / `--no-docs` cascade for strategy. | + +5-row flag matrix — `--docs` / `--no-docs` cascade to strategy when no explicit `--strategy` / `--no-strategy` is passed; the explicit pair always wins when present: -Four behaviors when active: +| Invocation | Glossary | Decisions | Strategy | +|------------|----------|-----------|----------| +| `(default)` | autodetect | autodetect | autodetect | +| `--docs` | on | on | on | +| `--no-docs` | off | off | off | +| `--no-docs --strategy` | off | off | on | +| `--docs --no-strategy` | on | on | off | + +Five behaviors when active: - **(a) Glossary lookup before terminology questions** — fetch nearest-ancestor canonical wording via `flowctl glossary read` before asking the user about terminology. If user wording diverges from canonical, surface the conflict in a new `## Glossary Conflicts` section in the refined spec — sits next to `## Resolved via Codebase` as the audit trail for canonical-vs-user wording resolutions. Resolution outcome (use-canonical / update-glossary / accept-divergence) is recorded inline. - **(b) Inline glossary write on resolution** — when the user picks `update-glossary`, `flowctl glossary add` is invoked immediately, recording the new canonical term with the user's chosen definition. The added term flows into downstream tasks via `docs-gap-scout` on the next planning pass. - **(c) Decision-record awareness** — when a load-bearing architectural choice is made during the interview, prompt the user (via `AskUserQuestion`) to write a `knowledge/decisions/` entry. Three-criteria gate: hard-to-reverse / surprising / load-bearing trade-off. Read-back loop before write so the user can correct trade-off framing. - **(d) Code/spec contradiction surfaced** — when an interview answer conflicts with an active decision record, the contradiction is surfaced in the refined spec (under `## Glossary Conflicts` or a similarly-named section) rather than silently overwriting either side. The user picks: revise the spec, supersede the decision, or accept divergence with rationale. +- **(e) Strategy-conflict surfacing (0.40.0+, gated on `STRATEGY_AWARE=1`)** — Phase-zero strategy scan reads active tracks via `flowctl strategy read --json` before drafting the first question batch. When the user's request conflicts with an active track, surface the conflict in a new `## Strategy Conflicts` section parallel to `## Glossary Conflicts`. Throttle: ≤1 strategy-conflict question per interview turn (parallel to existing glossary-question throttle). -Both `NEW-IDEA` and `EXISTING-EPIC` interview templates emit the `## Glossary Conflicts` section when behavior (a) or (d) fires. +Both `NEW-IDEA` and `EXISTING-EPIC` interview templates emit the `## Glossary Conflicts` and `## Strategy Conflicts` sections when behaviors (a)/(d)/(e) fire. #### `/flow-next:plan-review` @@ -2065,9 +2140,34 @@ Flow-Next uses the same defaults in manual and Ralph runs. Ralph bypasses prompt Override via flags or `scripts/ralph/config.env`. +### Strategy Phase (optional, upstream of everything) + +Run once early or whenever direction shifts. Sets a repo-wide strategic anchor that downstream skills read as advisory grounding. + +1. **Run** `/flow-next:strategy` — interview-driven Q&A populates 5 required sections (Target problem / Our approach / Who it's for / Key metrics / Tracks) + 2 optional (Milestones / Not working on). 2-round pushback per section against fluff / vanity / feature-list answers +2. **Re-run anytime** with section name (e.g., `/flow-next:strategy metrics`) to revisit one block — preserves the rest byte-identical, bumps `last_updated` +3. **Downstream consumers** auto-detect and ground against `STRATEGY.md`: + - Prospect — Phase 0 grounding scan emits approach + tracks verbatim into the candidate-generation prompt; adds `out-of-scope-vs-strategy` to the rejection taxonomy + - Plan — research scan reads strategy; epic spec gets a `## Strategy Alignment` section listing tracks served + read-only `## Strategy drift flagged for review` block on conflict + - Interview — doc-aware mode adds a `## Strategy Conflicts` section parallel to `## Glossary Conflicts`; throttled to ≤1 strategy question per turn + - Capture — Phase 0 reads strategy; tags strategy-derived acceptance criteria as `[strategy:]`; refuses to write contradicting specs without `--override-strategy` (which prompts for a decision-record write) + - Plan-sync — surfaces strategy drift read-only; renames update inline with breadcrumb; never auto-supersedes + +Skip this phase entirely if your repo has no strategic intent worth recording. All downstream skills run identically when `STRATEGY.md` is absent or empty (husk). + +**Glossary and decisions accumulate alongside strategy as a side effect of `/flow-next:interview --docs`** (or just `interview` once one glossary term or one decision is on file — autodetect takes over). Conflict resolution writes canonical terms to `GLOSSARY.md` via `flowctl glossary add`; the three-criteria gate writes decision records to `knowledge/decisions/`. No separate "build glossary" or "build decisions" phase — these populate through normal spec refinement. See [Project Glossary](#project-glossary) and [Memory System](#memory-system) for direct CLI paths if you prefer. + +### Prospecting Phase (optional, when no target yet) + +Run when you don't know what to build next. Generates ranked candidates grounded in repo + strategy + memory + open epics. + +1. **Run** `/flow-next:prospect [focus hint]` — generates 15-25 candidates, critiques each, writes ranked artifact to `.flow/prospects/-.md` +2. **Promote** a survivor: `flowctl prospect promote --idea N` allocates an epic with prospect-context spec inlined +3. **Continue** with Capture / Interview / Plan as appropriate (see [When to Use What](#when-to-use-what)) + ### Planning Phase -1. **Research (parallel subagents)**: `repo-scout` (or `context-scout` if rp-cli) + `practice-scout` + `docs-scout` + `github-scout` + `epic-scout` + `docs-gap-scout` (v0.39.0+: also reads `GLOSSARY.md` on the ancestor chain + `knowledge/decisions/` to surface canonical terminology + prior load-bearing choices in the planning context) +1. **Research (parallel subagents)**: `repo-scout` (or `context-scout` if rp-cli) + `practice-scout` + `docs-scout` + `github-scout` + `epic-scout` + `docs-gap-scout` (v0.39.0+: also reads `GLOSSARY.md` on the ancestor chain + `knowledge/decisions/` to surface canonical terminology + prior load-bearing choices in the planning context; v0.40.0+: also reads `STRATEGY.md` when present and writes `## Strategy Alignment` listing tracks served) 2. **Gap analysis**: `flow-gap-analyst` finds edge cases + missing requirements 3. **Epic creation**: Writes spec to `.flow/specs/fn-N.md`, sets epic dependencies from `epic-scout` findings 4. **Task breakdown**: Creates tasks + explicit dependencies in `.flow/tasks/`, adds doc update acceptance criteria from `docs-gap-scout` @@ -2083,6 +2183,12 @@ Override via flags or `scripts/ralph/config.env`. 5. **Review** (optional): `/flow-next:impl-review` via RepoPrompt, Codex, or Copilot 6. **Loop**: Next ready task → repeat until no ready tasks. Close epic manually (`flowctl epic close fn-N`) or let Ralph close at loop end. +### Maintenance Phase (optional, ongoing) + +- `/flow-next:audit [mode:autofix] [scope hint]` — review `.flow/memory/` entries against current code, decide Keep / Update / Consolidate / Replace / Delete per entry +- `/flow-next:memory-migrate` — one-time migration from legacy flat memory files (pre-0.33.0) +- `/flow-next:resolve-pr [PR# | comment URL]` — fetch unresolved PR review threads, dispatch resolver agents, validate, commit, reply, resolve via GraphQL + --- ## Ralph Mode (Autonomous, Opt-In) diff --git a/plugins/flow-next/agents/plan-sync.md b/plugins/flow-next/agents/plan-sync.md index 4fb83174..e0cb5c32 100644 --- a/plugins/flow-next/agents/plan-sync.md +++ b/plugins/flow-next/agents/plan-sync.md @@ -19,6 +19,7 @@ You synchronize downstream task specs after implementation drift. - `CROSS_EPIC` - "true" or "false" (from config planSync.crossEpic, defaults to false) - `GLOSSARY_JSON` - output of `flowctl glossary list --json` (optional; defaults to `{"groups":[],"file_count":0,"total_terms":0}` when the project has no glossary) - `DECISIONS_JSON` - output of `flowctl memory list --track knowledge --category decisions --json` (optional; defaults to `{"entries":[],"count":0}` when no decision entries exist) +- `STRATEGY_CONTENT` - output of `flowctl strategy read --json` (optional; defaults to `{}` when no STRATEGY.md exists or all sections are empty). `tracks` is a raw markdown string with `### ` H3 sub-blocks. Empty section bodies surface as `""` (empty string), not null. ## Phase 1: Re-anchor on Completed Task @@ -68,9 +69,17 @@ Compare spec vs implementation: Drift exists if implementation differs from spec in ways that downstream tasks reference. -## Phase 3b: Glossary renames + decision overrides +## Phase 3b: Glossary renames + decision overrides + strategy drift -Two extra signal types layer on top of the variable/API drift in Phase 3. Both are sourced from the input prompt — no extra flowctl calls required. +Three extra signal types layer on top of the variable/API drift in Phase 3. All are sourced from the input prompt — no extra flowctl calls required. + +**Husk short-circuit:** when ALL three of the following hold, skip the entire Phase 3b section — there is no project-anchor signal to align to: + +- `GLOSSARY_JSON.total_terms == 0` (glossary is missing or husk) +- `DECISIONS_JSON.count == 0` (no decision entries) +- `STRATEGY_CONTENT.sections_filled == 0` OR `STRATEGY_CONTENT == {}` (no STRATEGY.md or husk; check the parsed JSON for any populated section body — `target_problem`, `approach`, `tracks`, `personas`, or `metrics`) + +When ANY of the three has signal, run the corresponding subsection (3b.1 / 3b.2 / 3b.3) and skip the others. Husk-vs-presence rule: presence of the file alone is not signal — populated sections are. ### 3b.1 — Glossary-term renames @@ -104,6 +113,35 @@ Skip entries with `decision_status: superseded` — historical record, not activ When the `Consequences` section is missing or names no concrete code references, skip the entry — there's nothing to cross-check. +### 3b.3 — Strategy drift + +Skip when `STRATEGY_CONTENT == {}` OR every section body in `STRATEGY_CONTENT` is `""` / null (husk semantics — no populated section to align to). Otherwise: + +1. Read the active tracks from `STRATEGY_CONTENT.tracks` — a **raw markdown string** with `### ` H3 sub-blocks. Parse the H3 names. Empty section bodies surface as `""` (empty string), not null. Use a literal H3 grep pattern on the raw string: + ``` + ^### (.+)$ + ``` + Each capture group is an active track name. +2. Read the verbatim `STRATEGY_CONTENT.approach` line (one or two sentences). +3. For each active track, scan the **completed task spec** and the **actual code touched by the completed task** for evidence that the implementation contradicts the track body. Examples: + - Track `### CLI-only` says "we ship CLI tools, not SaaS"; completed task adds a hosted endpoint at `src/server/dashboard.ts` — contradicts. + - Approach says "OSS-tools repo, no commercial SaaS"; completed task adds `stripe` dependency to `package.json` — contradicts. +4. Track-rename detection: when the **completed task spec** or **epic spec** references a track-name pattern (`### ` H3 in prior file content) absent from the current `STRATEGY_CONTENT.tracks`, treat as a rename candidate. Match against the historical pattern using a literal H3 grep on the same form. Map old → new by closest semantic match (1:1 when possible; otherwise surface as drift). + +**On contradiction detected:** surface in the Phase 6 summary under a `## Strategy drift flagged for review` heading. Format mirrors the `Decision overrides flagged for review` block exactly — bulleted list with track citation + completed-task divergence + a `Review and run /flow-next:strategy if intended.` line per item. + +**On track-rename detected:** in Phase 5, replace the alias inline with the canonical track name and a breadcrumb. Pattern mirrors the existing glossary rename plumbing (Phase 5 Edit step): + +``` + +``` + +The breadcrumb names the old and canonical track names. The replacement happens in `.flow/specs/.md` and `.flow/tasks/.md` only — never in `STRATEGY.md`. + +**Do not auto-supersede.** Do not Edit `STRATEGY.md`. Do not write a successor. The agent's job here is signal-surface only — list track names that need human review in the Phase 6 summary. The user (or `/flow-next:strategy`) decides whether to revise the strategy doc. + +When `STRATEGY_CONTENT` has no `tracks` field (or `tracks == ""`), skip the track-iteration step but still scan against `approach` for the contradiction check. When both are empty, skip the section entirely. + ## Phase 4: Check Downstream Tasks For each task in DOWNSTREAM_TASK_IDS: @@ -167,8 +205,10 @@ Changes should: - Correct API signatures - Fix data structure assumptions - Replace glossary aliases (Phase 3b.1) with the canonical term; preserve surrounding prose +- Replace strategy track aliases (Phase 3b.3) with the canonical track name; preserve surrounding prose - Add note: `` - For glossary renames, the breadcrumb names the alias and canonical term: `` +- For strategy track renames, the breadcrumb names the old and canonical track names: `` **DO NOT:** - Change task scope or requirements @@ -215,6 +255,9 @@ Would update traceability: # Only if table exists Decision overrides flagged for review: # Only if DECISIONS_JSON had entries with overrides - knowledge/decisions/use-rest-not-graphql-2026-03-12: completed task added `/graphql` endpoint in src/api/router.ts; review for supersession. +## Strategy drift flagged for review # Only if STRATEGY_CONTENT has populated sections AND a contradiction was detected +- **CLI-only** (STRATEGY.md): completed task added hosted endpoint in src/server/dashboard.ts. Review and run /flow-next:strategy if intended. + No files modified. ``` @@ -237,10 +280,15 @@ Updated traceability: # Only if table exists and rows affected Decision overrides flagged for review: # Only if DECISIONS_JSON had entries with overrides - knowledge/decisions/use-rest-not-graphql-2026-03-12: completed task added `/graphql` endpoint in src/api/router.ts; review for supersession. + +## Strategy drift flagged for review # Only if STRATEGY_CONTENT has populated sections AND a contradiction was detected +- **CLI-only** (STRATEGY.md): completed task added hosted endpoint in src/server/dashboard.ts. Review and run /flow-next:strategy if intended. ``` **Decision overrides are surfaced, not auto-resolved.** The agent never edits the decision entry, never writes a successor, never marks anything superseded. The user (or `/flow-next:audit`) handles supersession. +**Strategy drift is surfaced, not auto-resolved.** The agent never edits `STRATEGY.md`, never marks a track superseded, never auto-rewrites the doc. The user (or `/flow-next:strategy`) decides whether to revise. Track renames in spec bodies (Phase 5 inline replacement with breadcrumb) are the only auto-edit related to strategy — `STRATEGY.md` itself is read-only from this agent's perspective. + ## Rules - **Read-only exploration** - Use Grep/Glob/Read for codebase, never edit source @@ -250,6 +298,7 @@ Decision overrides flagged for review: # Only if DECISIONS_JSON had entries wit - **Skip if no drift** - Return quickly if implementation matches spec - **Glossary entries are read-only** - never Edit `GLOSSARY.md` files; the agent only consumes the JSON - **Decision entries are read-only** - never Edit `.flow/memory/knowledge/decisions/*.md`; surface overrides for human review +- **STRATEGY.md is read-only** - never Edit `STRATEGY.md`; the agent only consumes the JSON. Track-rename inline replacement happens in `.flow/specs/*.md` / `.flow/tasks/*.md` (with breadcrumb), never in the strategy doc itself ## R-ID preservation (MANDATORY) diff --git a/plugins/flow-next/codex/agents/plan-sync.toml b/plugins/flow-next/codex/agents/plan-sync.toml index ee33cab8..01209264 100644 --- a/plugins/flow-next/codex/agents/plan-sync.toml +++ b/plugins/flow-next/codex/agents/plan-sync.toml @@ -19,6 +19,7 @@ You synchronize downstream task specs after implementation drift. - `CROSS_EPIC` - "true" or "false" (from config planSync.crossEpic, defaults to false) - `GLOSSARY_JSON` - output of `flowctl glossary list --json` (optional; defaults to `{"groups":[],"file_count":0,"total_terms":0}` when the project has no glossary) - `DECISIONS_JSON` - output of `flowctl memory list --track knowledge --category decisions --json` (optional; defaults to `{"entries":[],"count":0}` when no decision entries exist) +- `STRATEGY_CONTENT` - output of `flowctl strategy read --json` (optional; defaults to `{}` when no STRATEGY.md exists or all sections are empty). `tracks` is a raw markdown string with `### ` H3 sub-blocks. Empty section bodies surface as `""` (empty string), not null. ## Phase 1: Re-anchor on Completed Task @@ -68,9 +69,17 @@ Compare spec vs implementation: Drift exists if implementation differs from spec in ways that downstream tasks reference. -## Phase 3b: Glossary renames + decision overrides +## Phase 3b: Glossary renames + decision overrides + strategy drift -Two extra signal types layer on top of the variable/API drift in Phase 3. Both are sourced from the input prompt — no extra flowctl calls required. +Three extra signal types layer on top of the variable/API drift in Phase 3. All are sourced from the input prompt — no extra flowctl calls required. + +**Husk short-circuit:** when ALL three of the following hold, skip the entire Phase 3b section — there is no project-anchor signal to align to: + +- `GLOSSARY_JSON.total_terms == 0` (glossary is missing or husk) +- `DECISIONS_JSON.count == 0` (no decision entries) +- `STRATEGY_CONTENT.sections_filled == 0` OR `STRATEGY_CONTENT == {}` (no STRATEGY.md or husk; check the parsed JSON for any populated section body — `target_problem`, `approach`, `tracks`, `personas`, or `metrics`) + +When ANY of the three has signal, run the corresponding subsection (3b.1 / 3b.2 / 3b.3) and skip the others. Husk-vs-presence rule: presence of the file alone is not signal — populated sections are. ### 3b.1 — Glossary-term renames @@ -104,6 +113,35 @@ Skip entries with `decision_status: superseded` — historical record, not activ When the `Consequences` section is missing or names no concrete code references, skip the entry — there's nothing to cross-check. +### 3b.3 — Strategy drift + +Skip when `STRATEGY_CONTENT == {}` OR every section body in `STRATEGY_CONTENT` is `""` / null (husk semantics — no populated section to align to). Otherwise: + +1. Read the active tracks from `STRATEGY_CONTENT.tracks` — a **raw markdown string** with `### ` H3 sub-blocks. Parse the H3 names. Empty section bodies surface as `""` (empty string), not null. Use a literal H3 grep pattern on the raw string: + ``` + ^### (.+)$ + ``` + Each capture group is an active track name. +2. Read the verbatim `STRATEGY_CONTENT.approach` line (one or two sentences). +3. For each active track, scan the **completed task spec** and the **actual code touched by the completed task** for evidence that the implementation contradicts the track body. Examples: + - Track `### CLI-only` says "we ship CLI tools, not SaaS"; completed task adds a hosted endpoint at `src/server/dashboard.ts` — contradicts. + - Approach says "OSS-tools repo, no commercial SaaS"; completed task adds `stripe` dependency to `package.json` — contradicts. +4. Track-rename detection: when the **completed task spec** or **epic spec** references a track-name pattern (`### ` H3 in prior file content) absent from the current `STRATEGY_CONTENT.tracks`, treat as a rename candidate. Match against the historical pattern using a literal H3 grep on the same form. Map old → new by closest semantic match (1:1 when possible; otherwise surface as drift). + +**On contradiction detected:** surface in the Phase 6 summary under a `## Strategy drift flagged for review` heading. Format mirrors the `Decision overrides flagged for review` block exactly — bulleted list with track citation + completed-task divergence + a `Review and run /flow-next:strategy if intended.` line per item. + +**On track-rename detected:** in Phase 5, replace the alias inline with the canonical track name and a breadcrumb. Pattern mirrors the existing glossary rename plumbing (Phase 5 Edit step): + +``` + +``` + +The breadcrumb names the old and canonical track names. The replacement happens in `.flow/specs/.md` and `.flow/tasks/.md` only — never in `STRATEGY.md`. + +**Do not auto-supersede.** Do not Edit `STRATEGY.md`. Do not write a successor. The agent's job here is signal-surface only — list track names that need human review in the Phase 6 summary. The user (or `/flow-next:strategy`) decides whether to revise the strategy doc. + +When `STRATEGY_CONTENT` has no `tracks` field (or `tracks == ""`), skip the track-iteration step but still scan against `approach` for the contradiction check. When both are empty, skip the section entirely. + ## Phase 4: Check Downstream Tasks For each task in DOWNSTREAM_TASK_IDS: @@ -167,8 +205,10 @@ Changes should: - Correct API signatures - Fix data structure assumptions - Replace glossary aliases (Phase 3b.1) with the canonical term; preserve surrounding prose +- Replace strategy track aliases (Phase 3b.3) with the canonical track name; preserve surrounding prose - Add note: `` - For glossary renames, the breadcrumb names the alias and canonical term: `` +- For strategy track renames, the breadcrumb names the old and canonical track names: `` **DO NOT:** - Change task scope or requirements @@ -215,6 +255,9 @@ Would update traceability: # Only if table exists Decision overrides flagged for review: # Only if DECISIONS_JSON had entries with overrides - knowledge/decisions/use-rest-not-graphql-2026-03-12: completed task added `/graphql` endpoint in src/api/router.ts; review for supersession. +## Strategy drift flagged for review # Only if STRATEGY_CONTENT has populated sections AND a contradiction was detected +- **CLI-only** (STRATEGY.md): completed task added hosted endpoint in src/server/dashboard.ts. Review and run /flow-next:strategy if intended. + No files modified. ``` @@ -237,10 +280,15 @@ Updated traceability: # Only if table exists and rows affected Decision overrides flagged for review: # Only if DECISIONS_JSON had entries with overrides - knowledge/decisions/use-rest-not-graphql-2026-03-12: completed task added `/graphql` endpoint in src/api/router.ts; review for supersession. + +## Strategy drift flagged for review # Only if STRATEGY_CONTENT has populated sections AND a contradiction was detected +- **CLI-only** (STRATEGY.md): completed task added hosted endpoint in src/server/dashboard.ts. Review and run /flow-next:strategy if intended. ``` **Decision overrides are surfaced, not auto-resolved.** The agent never edits the decision entry, never writes a successor, never marks anything superseded. The user (or `/flow-next:audit`) handles supersession. +**Strategy drift is surfaced, not auto-resolved.** The agent never edits `STRATEGY.md`, never marks a track superseded, never auto-rewrites the doc. The user (or `/flow-next:strategy`) decides whether to revise. Track renames in spec bodies (Phase 5 inline replacement with breadcrumb) are the only auto-edit related to strategy — `STRATEGY.md` itself is read-only from this agent's perspective. + ## Rules - **Read-only exploration** - Use Grep/Glob/Read for codebase, never edit source @@ -250,6 +298,7 @@ Decision overrides flagged for review: # Only if DECISIONS_JSON had entries wit - **Skip if no drift** - Return quickly if implementation matches spec - **Glossary entries are read-only** - never Edit `GLOSSARY.md` files; the agent only consumes the JSON - **Decision entries are read-only** - never Edit `.flow/memory/knowledge/decisions/*.md`; surface overrides for human review +- **STRATEGY.md is read-only** - never Edit `STRATEGY.md`; the agent only consumes the JSON. Track-rename inline replacement happens in `.flow/specs/*.md` / `.flow/tasks/*.md` (with breadcrumb), never in the strategy doc itself ## R-ID preservation (MANDATORY) diff --git a/plugins/flow-next/codex/skills/flow-next-capture/SKILL.md b/plugins/flow-next/codex/skills/flow-next-capture/SKILL.md index 1a298ad4..14fe91b9 100644 --- a/plugins/flow-next/codex/skills/flow-next-capture/SKILL.md +++ b/plugins/flow-next/codex/skills/flow-next-capture/SKILL.md @@ -1,6 +1,6 @@ --- name: flow-next-capture -description: Synthesize the current conversation context into a flow-next epic spec at `.flow/specs/.md` via `flowctl epic create + epic set-plan` — agent-native, source-tagged, with mandatory read-back before write. Triggers on /flow-next:capture, "capture spec", "lock down what we discussed", "make a spec from this conversation", "convert conversation to epic". Optional `mode:autofix` token runs without questions and requires `--yes` to commit. Optional `--rewrite ` overwrites an existing epic spec; `--from-compacted-ok` overrides the compaction-detection refusal. +description: Synthesize the current conversation context into a flow-next epic spec at `.flow/specs/.md` via `flowctl epic create + epic set-plan` — agent-native, source-tagged, with mandatory read-back before write. Triggers on /flow-next:capture, "capture spec", "lock down what we discussed", "make a spec from this conversation", "convert conversation to epic". Optional `mode:autofix` token runs without questions and requires `--yes` to commit. Optional `--rewrite ` overwrites an existing epic spec; `--from-compacted-ok` overrides the compaction-detection refusal; `--override-strategy` proceeds despite a contradiction with an active STRATEGY.md track (and prompts to record the override as a decision). user-invocable: false allowed-tools: request_user_input, Read, Bash, Grep, Glob, Write, Edit, Task --- @@ -26,7 +26,7 @@ FLOWCTL="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$HOME/.codex}}/scripts/flowc ## Mode Detection -Parse `$ARGUMENTS` for the literal token `mode:autofix` and the flags `--rewrite `, `--from-compacted-ok`, `--yes`. Strip recognized tokens; whatever remains is treated as freeform context (ignored — the conversation is the input, not `$ARGUMENTS`). +Parse `$ARGUMENTS` for the literal token `mode:autofix` and the flags `--rewrite `, `--from-compacted-ok`, `--yes`, `--override-strategy`. Strip recognized tokens; whatever remains is treated as freeform context (ignored — the conversation is the input, not `$ARGUMENTS`). ```bash RAW_ARGS="$ARGUMENTS" @@ -34,6 +34,7 @@ MODE="interactive" REWRITE_TARGET="" FROM_COMPACTED_OK=0 COMMIT_YES=0 +OVERRIDE_STRATEGY=0 # Mode token if [[ "$RAW_ARGS" == *"mode:autofix"* ]]; then @@ -58,6 +59,12 @@ if [[ "$RAW_ARGS" == *"--yes"* ]]; then COMMIT_YES=1 RAW_ARGS="${RAW_ARGS//--yes/}" fi + +# --override-strategy (Phase 5.0 strategy-contradiction override) +if [[ "$RAW_ARGS" == *"--override-strategy"* ]]; then + OVERRIDE_STRATEGY=1 + RAW_ARGS="${RAW_ARGS//--override-strategy/}" +fi ``` | Mode | When | Behavior | diff --git a/plugins/flow-next/codex/skills/flow-next-capture/phases.md b/plugins/flow-next/codex/skills/flow-next-capture/phases.md index 82b02c81..8e3dddc2 100644 --- a/plugins/flow-next/codex/skills/flow-next-capture/phases.md +++ b/plugins/flow-next/codex/skills/flow-next-capture/phases.md @@ -94,6 +94,7 @@ Every acceptance criterion line, every decision-context line, every scope-boundi | `[user]` | Verbatim from conversation evidence (exact quote or close paraphrase preserving meaning) | The user said this, in these or similar words. Reasonable people would agree it's the user's stated intent. | | `[paraphrase]` | User intent restated in spec language (semantic equivalence; no new constraints introduced) | The user expressed this idea, but agent rephrased to match spec conventions. Same content, cleaner wording. | | `[inferred]` | Agent fill-in (most-scrutinized; user must confirm at read-back) | Agent decided this; user did not state it explicitly. May be a reasonable default, may be wrong. | +| `[strategy:]` | Derived from `STRATEGY.md` content (verbatim or near-verbatim quote of approach / track body) | The criterion follows directly from a populated section in `STRATEGY.md` — the track name appears literal in the tag. Activates only when Phase 0 strategy snapshot is present. | ### Examples @@ -102,6 +103,7 @@ Every acceptance criterion line, every decision-context line, every scope-boundi | `> user (turn 4): "rate limit must reject 3+ requests per second from a single client"` | `- **R1:** Rate limit rejects ≥3 req/sec from a single client. [user]` | `[user]` | | `> user (turn 7): "we should write the spec body atomically so partial writes don't corrupt"` | `- **R5:** Spec writes are atomic — partial-write recovery preserves prior state. [paraphrase]` | `[paraphrase]` | | (no user mention of error format) | `- **R7:** Errors include the request id for trace correlation. [inferred]` | `[inferred]` | +| (STRATEGY.md `### Reliability` track says "we ship for 99.95% uptime") | `- **R9:** Service-level objective: 99.95% uptime measured monthly. [strategy:Reliability]` | `[strategy:Reliability]` | ### Section-level tags @@ -124,6 +126,7 @@ The breakdown is informational at read-back. Phase 4's `[inferred]` tally counts - **`[user]`** is for content the user can read and recognize as their own words. Acceptance criteria and rejected alternatives benefit most from this tag. - **`[paraphrase]`** is for spec-language restatements where the meaning is preserved but the wording is the agent's. Most decision-context and architecture-overview content lands here. - **`[inferred]`** is for content the user did not state but the agent decided was necessary for a complete spec. **Defaults are `[inferred]`** — error-format conventions, status code choices, retry policies, observability hooks. Surface them at read-back so the user can keep / edit / drop. +- **`[strategy:]`** is for content the agent imported from `STRATEGY.md` — verbatim or near-verbatim quote from the `approach` line or one of the `### ` H3 sub-blocks. The track name lives literally in the tag (e.g. `[strategy:Reliability]`). The criterion is treated as load-bearing for the strategy alignment surface; if the spec body contradicts a `[strategy:*]` line, capture refuses to write without `--override-strategy` (see SKILL.md). A spec with 0 `[inferred]` items is rare and probably means the conversation was unusually thorough. A spec with 30 `[inferred]` items is suspicious — the conversation was probably too thin for capture, and the user should pursue `/flow-next:interview` instead. diff --git a/plugins/flow-next/codex/skills/flow-next-capture/workflow.md b/plugins/flow-next/codex/skills/flow-next-capture/workflow.md index 3673efb6..84b1eacb 100644 --- a/plugins/flow-next/codex/skills/flow-next-capture/workflow.md +++ b/plugins/flow-next/codex/skills/flow-next-capture/workflow.md @@ -71,6 +71,41 @@ Memory hits are advisory — they signal "you may have prior art on this topic" If memory is not initialized (`memory list` returns the `Memory not initialized` error), skip this step silently. Memory search is a quality-of-life signal; absence is not blocking. +### 0.3b — Strategy snapshot (advisory grounding input) + +Read `STRATEGY.md` (when populated) so Phase 2's source-tagging can apply `[strategy:]` to acceptance criteria that follow directly from strategic intent. Husk-vs-presence gate uses `sections_filled >= 1` from `flowctl strategy status --json`, NOT `[[ -f STRATEGY.md ]]`. + +```bash +STRATEGY_STATUS_JSON=$("$FLOWCTL" strategy status --json 2>/dev/null || echo '{"exists":false,"sections_filled":0}') +STRATEGY_FILLED=$(jq -r '.sections_filled // 0' <<< "$STRATEGY_STATUS_JSON" 2>/dev/null || echo 0) + +if [[ "$STRATEGY_FILLED" -ge 1 ]]; then + STRATEGY_JSON=$("$FLOWCTL" strategy read --json 2>/dev/null || echo '{}') + STRATEGY_PRESENT=true + STRATEGY_NAME=$(jq -r '.name // "(unnamed)"' <<< "$STRATEGY_JSON") + STRATEGY_PROBLEM=$(jq -r '.target_problem // ""' <<< "$STRATEGY_JSON") + STRATEGY_APPROACH=$(jq -r '.approach // ""' <<< "$STRATEGY_JSON") + STRATEGY_TRACKS_RAW=$(jq -r '.tracks // ""' <<< "$STRATEGY_JSON") + STRATEGY_PATH=$(jq -r '.path // "STRATEGY.md"' <<< "$STRATEGY_JSON") +else + STRATEGY_PRESENT=false +fi +``` + +Surface as a "Strategic context:" footnote — 3-5 lines total — when the agent presents Phase 0 results to the user. Format: + +``` +Strategic context (STRATEGY.md, last updated 2026-04-30): + Approach: + Active tracks: , , +``` + +`STRATEGY_TRACKS_RAW` is a **raw markdown string** with `### ` H3 sub-blocks. Parse the H3 names locally for the active-tracks list. Empty section bodies (any of `target_problem`, `approach`, `tracks`) surface as `""` — `(.field // "")` style fallbacks in the jq queries above keep parsing well-formed when an optional section is missing. + +The strategy snapshot is **input**, not gating: even when `STRATEGY_PRESENT=true`, capture proceeds. Phase 2's source-tagging uses the snapshot to assign `[strategy:]` to criteria that quote / paraphrase strategy content. Phase 5 uses it to detect contradictions (see §5.0 below) and refuse the write without `--override-strategy`. + +When `STRATEGY_PRESENT=false`, Phase 2 emits no `[strategy:*]` tags and Phase 5's contradiction check is skipped entirely — there is no signal to align to. + ### 0.4 — Compaction detection (R6) Scan the visible conversation for any of: @@ -246,6 +281,7 @@ Every acceptance criterion line, every decision-context line, and every scope-bo | `[user]` | Verbatim from conversation evidence (exact quote or close paraphrase preserving meaning) | `- **R1:** Rate limit must reject ≥3 requests/sec from a single client. [user] (turn 4)` | | `[paraphrase]` | User intent restated in spec language (semantic equivalence; no new constraints introduced) | `- **R2:** Spec body is written via heredoc, atomic write. [paraphrase]` | | `[inferred]` | Agent fill-in (most-scrutinized; user must confirm at read-back) | `- **R7:** Errors include the request id for trace correlation. [inferred]` | +| `[strategy:]` | Derived from `STRATEGY.md` content (verbatim or near-verbatim from `approach` or a `### ` H3 sub-block); track name lives literally in the tag | `- **R9:** Service-level objective: 99.95% uptime measured monthly. [strategy:Reliability]` | Pure prose sections (Goal & Context narrative, Architecture overview) do not need per-line tags — but the **whole section** carries a section-level tag in a frontmatter-style note: e.g. ``. Phase 4 read-back surfaces this. @@ -360,13 +396,18 @@ Construct the full draft including: 2. The `## Conversation Evidence` block (Phase 1). 3. Every section drafted in Phase 2, with source tags visible. 4. The `## Acceptance Criteria` R-ID list — bulleted, source tags shown. -5. **`[inferred]` tally** — total count across the spec, with per-section breakdown: +5. **Source-tag tally** — total count across the spec, with per-tag breakdown. Format: + ``` + Source: [user] N · [paraphrase] M · [strategy] K · [inferred] L + ``` + Followed by the per-section `[inferred]` breakdown (the most-scrutinized class): ``` [inferred] count: 7 total - Architecture & Data Models: 3 - API Contracts: 2 - Boundaries: 2 ``` + The `[strategy]` count aggregates all `[strategy:]` lines regardless of track. When Phase 0 strategy snapshot scanned `none` (`STRATEGY_PRESENT=false`), `[strategy] K` reads `[strategy] 0` (or the field is omitted entirely — equivalent in practice). 6. **8+ acceptance-criterion suggestion** (if Phase 2.5 fired): ``` This spec has 11 acceptance criteria — consider splitting into multiple @@ -435,6 +476,93 @@ Autofix never offers `edit` — there's no user to ask. The print-then-rerun-wit **Goal:** atomic write of the new (or rewritten) epic via existing flowctl plumbing. +### 5.0 — Strategy contradiction check (gate; runs before any write) + +When the Phase 0 strategy snapshot was populated (`STRATEGY_PRESENT=true`), scan the drafted spec body for contradictions against the active tracks. A contradiction exists when: + +1. The spec body has at least one `[strategy:]` line AND the surrounding criterion / decision-context line negates the corresponding track body. Example: track `### CLI-only` says "we ship CLI tools, not SaaS"; spec criterion `[strategy:CLI-only]` reads "ship a managed dashboard service" — direct contradiction. +2. The spec body proposes an investment area that contradicts `approach` directly. Example: approach says "OSS-tools repo, no commercial SaaS"; spec body adds "stripe billing integration as a core feature" without `[strategy:*]` tagging — semantic contradiction even without a tag. + +When a contradiction is detected AND `OVERRIDE_STRATEGY` is `0`: + +```text +Error: spec contradicts active track "" — pass --override-strategy to proceed. + +Detected contradiction: + Track: (STRATEGY.md) + Track says: "" + Spec says: "" + +Re-run with --override-strategy to write the spec anyway. You'll be prompted to +record the override as a decision entry (the override is exactly the kind of +load-bearing architectural choice the decisions track exists for). +``` + +In **interactive** mode, refuse with the message above (exit 2) — do NOT prompt the user to override here; require the explicit flag re-run so the override is intentional. + +In **autofix** mode, refuse identically (exit 2). Autofix cannot resolve a strategy override. + +When `OVERRIDE_STRATEGY=1` AND the snapshot is populated, capture proceeds with the write **AND** prompts the user to record the override as a decision entry. Pattern (mirrors `/flow-next:interview` behavior (d) — three-criteria decision-record gate): + +```bash +# Interactive only — autofix never reaches this branch (5.0 exits 2 above when OVERRIDE_STRATEGY=0, +# and OVERRIDE_STRATEGY=1 in autofix is treated as "user already chose to override; record audit +# trail to stderr but don't prompt" — see logging branch below). +``` + +Use `request_user_input` (lead-with-recommendation, `[high]` toward yes): + +- **header**: `Record override?` +- **body**: `Override strategy track "" — record as a decision? Recommended: yes — override decisions belong in the decisions track (load-bearing architectural choice). Confidence: [high].` +- **options**: frozen — `yes` (write decision entry), `no` (proceed without recording; audit trail logged to stderr only). + +On `yes`, invoke `flowctl memory add` with the override rationale piped via `--body-file -` stdin: + +```bash +"$FLOWCTL" memory add \ + --track knowledge \ + --category decisions \ + --title "Override strategy: " \ + --module strategy \ + --tags strategy-override \ + --body-file - < contradicts active track "" in STRATEGY.md. + +## What was chosen + + +## Why + + +## Track being overridden +- **** (STRATEGY.md): "" +- **Spec direction:** "" + +## Considered alternatives +- Aligning with the strategy track (rejected because: ) +- Updating STRATEGY.md instead of overriding here (rejected because: ) + +## Consequences +- This epic ships in tension with track "". +- A future `/flow-next:strategy` run should re-evaluate the track; this decision feeds that conversation. +EOF +``` + +On `no`, proceed without writing the decision. Log an audit-trail line to stderr: + +```bash +# On no: +echo "[STRATEGY OVERRIDE]: track=\"\" decision-not-recorded epic=" >&2 + +# On yes (decision was recorded): +echo "[STRATEGY OVERRIDE]: track=\"\" decision-recorded= epic=" >&2 +``` + +The audit trail line appears in both interactive (after the user picks) and autofix (when `OVERRIDE_STRATEGY=1` was passed) — it is the minimum durable record that an override happened, surfaceable in CI logs / git hook output later. In autofix mode (where the request_user_input is unreachable), the decision-not-recorded variant fires unconditionally. + +When `STRATEGY_PRESENT=false`, this entire section is a no-op — there's no strategy snapshot to contradict. + ### 5.1 — Build the spec body The spec body assembled in Phase 2 + revised in Phase 4 edit cycles is the input to `flowctl epic set-plan`. Source tags **stay in the spec body** — they are part of the audit trail and survive into the on-disk spec at `.flow/specs/.md`. Future readers (including `/flow-next:plan` and `/flow-next:interview`) see the tags and can scrutinize. diff --git a/plugins/flow-next/codex/skills/flow-next-interview/SKILL.md b/plugins/flow-next/codex/skills/flow-next-interview/SKILL.md index 60a97ae2..d40d81da 100644 --- a/plugins/flow-next/codex/skills/flow-next-interview/SKILL.md +++ b/plugins/flow-next/codex/skills/flow-next-interview/SKILL.md @@ -59,13 +59,16 @@ If empty, ask: "What should I interview you about? Give me a Flow ID (e.g., fn-1 FLOWCTL="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$HOME/.codex}}/scripts/flowctl" ``` -### Parse `--docs` / `--no-docs` flags +### Parse `--docs` / `--no-docs` / `--strategy` / `--no-strategy` flags -Strip `--docs` / `--no-docs` from `$ARGUMENTS` before input-type detection so they don't get confused for a Flow ID or path: +Strip the four doc-aware override flags from `$ARGUMENTS` before input-type detection so they don't get confused for a Flow ID or path: ```bash RAW_ARGS="$ARGUMENTS" -DOC_AWARE_FORCE="" # "" = autodetect, "on" = forced on, "off" = forced off +DOC_AWARE_FORCE="" # "" = autodetect, "on" = forced on, "off" = forced off (controls glossary + decisions) +STRATEGY_AWARE_FORCE="" # "" = autodetect, "on" = forced on, "off" = forced off (controls strategy independently) + +# Glossary + decisions: --docs / --no-docs (mutually exclusive; --no-docs wins) if [[ "$RAW_ARGS" == *"--no-docs"* ]]; then DOC_AWARE_FORCE="off" RAW_ARGS="${RAW_ARGS//--no-docs/}" @@ -73,21 +76,47 @@ elif [[ "$RAW_ARGS" == *"--docs"* ]]; then DOC_AWARE_FORCE="on" RAW_ARGS="${RAW_ARGS//--docs/}" fi + +# Strategy: explicit --strategy / --no-strategy always wins. Otherwise --docs / --no-docs cascades. +# Order: explicit pair first (mutually exclusive; --no-strategy wins on conflict), then docs cascade. +if [[ "$RAW_ARGS" == *"--no-strategy"* ]]; then + STRATEGY_AWARE_FORCE="off" + RAW_ARGS="${RAW_ARGS//--no-strategy/}" +elif [[ "$RAW_ARGS" == *"--strategy"* ]]; then + STRATEGY_AWARE_FORCE="on" + RAW_ARGS="${RAW_ARGS//--strategy/}" +elif [[ "$DOC_AWARE_FORCE" == "off" ]]; then + # --no-docs alone cascades to strategy: matrix row 3 says all three off. + STRATEGY_AWARE_FORCE="off" +elif [[ "$DOC_AWARE_FORCE" == "on" ]]; then + # --docs alone cascades to strategy: matrix row 2 says all three on. + STRATEGY_AWARE_FORCE="on" +fi + RAW_ARGS=$(printf "%s" "$RAW_ARGS" | tr -s ' ' | sed 's/^ //;s/ $//') # RAW_ARGS now contains the Flow ID / file path / empty. ``` -`--docs` and `--no-docs` are mutually exclusive; if the user passes both, `--no-docs` wins (the `if/elif` checks `--no-docs` first). The `--docs` token gets left in the residual `RAW_ARGS` after stripping, which surfaces downstream as an unrecognized argument — loud failure beats silent acceptance of conflicting state. +Each pair is mutually exclusive (the `if/elif` checks the negation first so it wins on conflict). The `--docs` / `--strategy` tokens get left in the residual `RAW_ARGS` after stripping, which surfaces downstream as an unrecognized argument — loud failure beats silent acceptance of conflicting state. -### Doc-aware autodetect +**Flag matrix** — five rows, all explicit: + +| Flags | Glossary | Decisions | Strategy | +|-------|----------|-----------|----------| +| (default) | autodetect | autodetect | autodetect | +| `--docs` | on | on | on | +| `--no-docs` | off | off | off | +| `--no-docs --strategy` | off | off | on | +| `--docs --no-strategy` | on | on | off | -Decide whether doc-aware mode (behaviors a-d below) activates. Three paths: +`--docs` / `--no-docs` cascade to strategy when no explicit `--strategy` / `--no-strategy` is passed (matrix rows 2 + 3). Explicit `--strategy` / `--no-strategy` always wins (matrix rows 4 + 5) and is the only way to drive a different value into strategy than into glossary + decisions. The matrix is the contract. -1. **Forced on** (`--docs` flag): `DOC_AWARE=1`. Lazy-creates root `GLOSSARY.md` on first term resolution via `flowctl glossary add` (writes to nearest-ancestor or repo root when no ancestor exists). -2. **Forced off** (`--no-docs` flag): `DOC_AWARE=0`. Skip behaviors a-d entirely, even if artifacts exist. -3. **Autodetect** (no flag): activate when `GLOSSARY.md` has at least one defined term OR any decision entry exists. +### Doc-aware autodetect + +Decide whether doc-aware mode (behaviors a-e below) activates. `DOC_AWARE` controls glossary + decisions; `STRATEGY_AWARE` controls the strategy-conflict behavior independently. Each has three paths (forced-on / forced-off / autodetect) per the flag matrix above. ```bash +# DOC_AWARE: glossary + decisions DOC_AWARE=0 if [[ "$DOC_AWARE_FORCE" == "on" ]]; then DOC_AWARE=1 @@ -100,11 +129,26 @@ else DOC_AWARE=1 fi fi + +# STRATEGY_AWARE: strategy (independent of DOC_AWARE — autodetects on its own signal) +STRATEGY_AWARE=0 +if [[ "$STRATEGY_AWARE_FORCE" == "on" ]]; then + STRATEGY_AWARE=1 +elif [[ "$STRATEGY_AWARE_FORCE" == "off" ]]; then + STRATEGY_AWARE=0 +else + STRAT_FILLED=$("$FLOWCTL" strategy status --json 2>/dev/null | jq -r '.sections_filled // 0') + if [[ "${STRAT_FILLED:-0}" -ge 1 ]]; then + STRATEGY_AWARE=1 + fi +fi ``` -**Why `total_terms > 0` rather than `[[ -f GLOSSARY.md ]]`:** `flowctl glossary remove` leaves a `# Glossary` H1 husk on disk after the last term is removed (the file is project state, intentionally retained). A presence-only check would false-positive on an empty husk and surface phantom doc-aware questions when no canonical vocabulary is actually defined. `glossary list --json` walks the file and counts populated entries; `total_terms == 0` for a husk. +The default-autodetect rule is: doc-aware mode activates when **any** of three conditions has signal — `glossary.total_terms > 0` (a) OR a decision entry exists (b) OR `strategy.sections_filled >= 1` (c). The two flag pairs (`--docs` / `--no-docs` and `--strategy` / `--no-strategy`) override (a)+(b) and (c) independently per the matrix above. -When `DOC_AWARE=1`, the four behaviors below layer onto the standard interview workflow. When `DOC_AWARE=0`, the interview proceeds exactly as today. +**Why `total_terms > 0` and `sections_filled >= 1` rather than `[[ -f ]]`:** `flowctl glossary remove` leaves a `# Glossary` H1 husk after the last term is removed; `flowctl strategy` leaves a frontmatter-plus-H1 husk under the same R18 invariant. Both files are project state, intentionally retained. A presence-only check would false-positive on an empty husk and surface phantom doc-aware questions when no canonical vocabulary / strategic intent is actually defined. `glossary list --json` and `strategy status --json` walk the file and count populated entries; both report zero for a husk. + +When `DOC_AWARE=1`, behaviors (a)-(d) below layer onto the standard interview workflow. When `STRATEGY_AWARE=1`, behavior (e) layers on. When both are 0, the interview proceeds exactly as today. ## Detect Input Type @@ -202,9 +246,14 @@ Confidence tier: `[high]` when grep evidence is unambiguous (file does not exist The bar for surfacing: a meaningful contradiction that affects spec correctness. If the user says "the validator returns boolean" and grep shows it returns `Result`, surface. If the user paraphrases a function's role and grep shows the role matches but the implementation differs in unrelated detail, log under `## Resolved via Codebase` and move on. -## Doc-aware behaviors (`DOC_AWARE=1` only) +## Doc-aware behaviors + +Five behaviors layer onto the standard interview workflow when their respective gate is open: -When `DOC_AWARE=1`, four behaviors layer onto the standard interview workflow. When `DOC_AWARE=0`, skip this entire section. +- Behaviors (a)-(d) are gated on `DOC_AWARE=1` (glossary + decisions signal). When `DOC_AWARE=0`, skip them. +- Behavior (e) is gated on `STRATEGY_AWARE=1` (strategy signal). When `STRATEGY_AWARE=0`, skip it. + +The two gates are independent (see flag matrix above) — `DOC_AWARE` and `STRATEGY_AWARE` may differ within the same interview session. ### Behavior (a) — Phase-zero glossary scan @@ -324,6 +373,48 @@ When all three hold: **At most one decision write per interview turn.** Even if multiple gate-passing decisions surface, ask one at a time; subsequent asks adapt to the user's energy level for read-back. +### Behavior (e) — Code-versus-strategy contradiction (`STRATEGY_AWARE=1` only) + +Parallel structure to behavior (a) — Phase-zero glossary scan. Before drafting the first question batch in a `STRATEGY_AWARE=1` session, run a strategy scan against the user's request. + +```bash +"$FLOWCTL" strategy read --json +``` + +JSON shape (selected fields used here): + +```json +{ + "name": "", + "target_problem": "...", + "approach": "...", + "tracks": "### track-a\nOne line on track A.\n_Why it serves the approach:_ ...\n\n### track-b\n...", + "last_updated": "2026-05-01", + "path": "STRATEGY.md" +} +``` + +`tracks` is a **raw markdown string** — H3 sub-blocks of the form `### ` followed by a one-line description and a `_Why it serves the approach:_` line. Parse the H3 names locally. Empty section bodies (any of `target_problem`, `approach`, `tracks`) surface as `""` (empty string), not null — `(.field // "")` style fallbacks keep parsing well-formed when an optional section is missing. + +Walk the user's request looking for two patterns: + +1. **Track-name mismatch** — the user uses a noun-phrase that names a track-like investment area, but the wording diverges from a canonical track in `STRATEGY.md` (e.g. user says "Initiative" but `tracks` defines "### Track"). Treat the user's wording as a candidate alias for the closest canonical track and surface as a question if load-bearing for the spec. +2. **Direction conflict** — the user describes a goal or constraint that contradicts the `approach` or one of the active tracks (e.g. approach says "we ship CLI tools, not SaaS" but the user is asking the spec to add a managed dashboard service). + +For each hit, evaluate the same load-bearing filter as behavior (a): casual passing mention does not trigger; mention that defines behavior or shapes acceptance does. + +When a hit passes the filter, surface via `request_user_input`: + +- **header**: `Strategy mismatch?` +- **body**: `You said ""; STRATEGY.md () says "". Recommended: . Confidence: [].` +- **options**: frozen — `align-with-strategy` (the user meant the existing track / honors the approach; spec uses canonical wording), `flag-as-drift` (the spec is intentionally pushing back on the strategy; capture in `## Strategy Conflicts` and proceed), `this-is-different` (the words collide but the concepts differ; spec uses a fresh disambiguating term — also capture in `## Strategy Conflicts`). + +Confidence tier: `[high]` when the strategy entry is recent and the user's wording cleanly maps to a canonical track or directly contradicts the verbatim approach; `[judgment-call]` when meaning could plausibly have drifted; `[your-call]` when the strategic direction sits in user-domain territory the agent has no purchase on. + +**Throttle:** at most one strategy-conflict question per interview turn (parallel to behavior (a)'s glossary throttle). If multiple strategy mismatches hit, surface the most load-bearing one first; the rest fold into the natural conversation flow as they come up. Bombarding the user with strategy-alignment questions before the core spec questions is the failure mode this throttle prevents. Combined with the (a) and (d) throttles, the per-turn doc-aware question budget is **3 max** (1 glossary + 1 decision-record + 1 strategy). + +The output of behavior (e) lands in a new spec section, `## Strategy Conflicts`, parallel to `## Glossary Conflicts`. Format: per-line entries with user-wording / canonical-strategy-wording / STRATEGY.md path / resolution-chosen. Lets reviewers see where the spec aligns or pushes back on strategic intent. Strategy conflicts are read-only signal for `/flow-next:sync`'s plan-sync agent — the interview never edits `STRATEGY.md`. + ## Question Categories Read [questions.md](questions.md) for all question categories and interview guidelines. @@ -368,6 +459,10 @@ Items the agent answered via Read / Grep / Glob, with file:line evidence. Separa (optional — only when DOC_AWARE=1 surfaced behavior-(a) hits during the interview) Per-term: user-wording vs. canonical term, the resolution chosen (use-canonical / redefine / this-is-different), file:line of the canonical entry. Lets reviewers see where vocabulary tightened. +## Strategy Conflicts +(optional — only when STRATEGY_AWARE=1 surfaced behavior-(e) hits during the interview) +Per-line: user-wording vs. canonical-strategy-wording (track name or approach), STRATEGY.md path, resolution chosen (align-with-strategy / flag-as-drift / this-is-different). Lets reviewers see where the spec aligns or pushes back on strategic intent. Read-only signal for plan-sync — the interview never edits STRATEGY.md. + ## Open Questions Unresolved items that need research during planning @@ -412,6 +507,10 @@ Items the agent answered via Read / Grep / Glob, with file:line evidence. Separa (optional — only when DOC_AWARE=1 surfaced behavior-(a) hits during the interview) Per-term: user-wording vs. canonical term, the resolution chosen, file:line of the canonical entry. +## Strategy Conflicts +(optional — only when STRATEGY_AWARE=1 surfaced behavior-(e) hits during the interview) +Per-line: user-wording vs. canonical-strategy-wording, STRATEGY.md path, resolution chosen. + ## Open Questions Unresolved items @@ -469,6 +568,7 @@ Show summary: - Key decisions captured - What was written (Flow ID updated / file rewritten) - Doc-aware mode (when `DOC_AWARE=1` was active): glossary terms added/updated via `flowctl glossary add`, decision entries written via `flowctl memory add --track knowledge --category decisions`, glossary conflicts captured under `## Glossary Conflicts` +- Strategy-aware mode (when `STRATEGY_AWARE=1` was active): strategy conflicts captured under `## Strategy Conflicts` (read-only — interview never edits STRATEGY.md) Suggest next step based on input type: - New idea / epic without tasks → `/flow-next:plan fn-N` diff --git a/plugins/flow-next/codex/skills/flow-next-plan/steps.md b/plugins/flow-next/codex/skills/flow-next-plan/steps.md index 74cc021a..4e0350bc 100644 --- a/plugins/flow-next/codex/skills/flow-next-plan/steps.md +++ b/plugins/flow-next/codex/skills/flow-next-plan/steps.md @@ -81,6 +81,26 @@ $FLOWCTL config get memory.enabled --json $FLOWCTL config get scouts.github --json ``` +**Check for STRATEGY.md (husk-vs-presence — uses `sections_filled >= 1`, NOT `[[ -f STRATEGY.md ]]`):** +```bash +STRATEGY_STATUS_JSON=$($FLOWCTL strategy status --json 2>/dev/null || echo '{"exists":false,"sections_filled":0}') +STRATEGY_FILLED=$(jq -r '.sections_filled // 0' <<< "$STRATEGY_STATUS_JSON" 2>/dev/null || echo 0) + +if [[ "$STRATEGY_FILLED" -ge 1 ]]; then + STRATEGY_JSON=$($FLOWCTL strategy read --json 2>/dev/null || echo '{}') + # Pass the parsed STRATEGY.md content into plan-prompt context alongside research findings. + # `tracks` is a raw markdown string (### H3 sub-blocks); empty section bodies + # are "" not null. The plan prompt sees `name`, `target_problem`, `approach`, `tracks`, + # `last_updated` verbatim — no paraphrasing. Active tracks shape the Strategy Alignment + # section in Step 5; conflicts with active tracks surface as drift in Step 5. + STRATEGY_PRESENT=true +else + STRATEGY_PRESENT=false +fi +``` + +When `STRATEGY_PRESENT=true`, the scouts and the plan-prompt see the strategy content. When `STRATEGY_PRESENT=false` (no STRATEGY.md or husk), the plan skips the `## Strategy Alignment` section and any drift-surfacing entirely (Step 5) — absence is fine, no signal to align to. + **Based on user's choice in SKILL.md setup:** --- @@ -212,6 +232,8 @@ Default to standard unless complexity demands more or less. ```bash # Include: Overview, Scope, Approach, Quick commands (REQUIRED), Acceptance, # Early proof point, Requirement coverage, References + # Conditional sections: ## Strategy Alignment (when STRATEGY_PRESENT=true from Step 1), + # ## Strategy drift flagged for review (when plan scope conflicts with an active track) # Add mermaid diagram if data model or architecture changes $FLOWCTL epic set-plan --file - --json <<'EOF' # Epic Title @@ -224,6 +246,32 @@ Default to standard unless complexity demands more or less. # At least one smoke test command ``` + ## Boundaries / non-goals + - + + ## Strategy Alignment + + + Active tracks served by this plan: + - **** — + - **** — + + + _No active strategy track served — review for drift._ + + ## Strategy drift flagged for review + + + - ****: . Review for revision via `/flow-next:strategy`. + + ## Decision context + - + ## Acceptance - **R1:** - **R2:** @@ -243,6 +291,18 @@ Default to standard unless complexity demands more or less. EOF ``` + **`## Strategy Alignment` rules (active iff STRATEGY_PRESENT=true from Step 1):** + - Section sits between `## Boundaries / non-goals` and `## Decision context` in the template above. + - List active tracks (`### ` blocks parsed from the strategy snapshot's `tracks` raw markdown string) that this plan advances. + - When the plan serves NO active track, render the placeholder `_No active strategy track served — review for drift._` literally — do not omit the section. + - Skip the entire section when STRATEGY_PRESENT=false. Husk-vs-presence: gated on `sections_filled >= 1`, NOT `[[ -f STRATEGY.md ]]`. + + **`## Strategy drift flagged for review` rules (conditional on conflict detection):** + - Mirrors plan-sync's "Decision overrides flagged for review" surface (`agents/plan-sync.md` Phase 6 summary). + - Bulleted list with track name + plan-decision divergence + `Review for revision via /flow-next:strategy.` line per item. + - Read-only — the plan skill never edits STRATEGY.md, never marks a track superseded, never auto-supersedes anything. Surface for human review only. + - Omit the heading entirely when no drift detected. Empty drift block is silent, not `_(none)_`. + **Early proof point rules:** - Identify which task proves the fundamental approach works - One sentence: which task + what it proves diff --git a/plugins/flow-next/codex/skills/flow-next-prospect/SKILL.md b/plugins/flow-next/codex/skills/flow-next-prospect/SKILL.md index 253129f0..5011ec26 100644 --- a/plugins/flow-next/codex/skills/flow-next-prospect/SKILL.md +++ b/plugins/flow-next/codex/skills/flow-next-prospect/SKILL.md @@ -53,9 +53,9 @@ No env-var opt-in. Ralph never decides direction. Execute the phases in [workflow.md](workflow.md) in order: 0. **Resume check** — list active artifacts <30d; ask extend / fresh / open via blocking question. Corrupt artifacts surfaced but never offered for extension. -1. **Ground** — scan repo with graceful degradation: git log (30d), open epics, CHANGELOG top, memory matches, memory audit (if present). Emit a structured 30-50 line snapshot — titles + tags only, never raw bodies. +1. **Ground** — scan repo with graceful degradation: git log (30d), open epics, CHANGELOG top, memory matches, memory audit (if present), strategy snapshot (verbatim `name` / `target_problem` / `approach` / `tracks` / `last_updated` from `flowctl strategy read --json` when `sections_filled >= 1`; husk-vs-presence gate uses `sections_filled`, NOT `[[ -f STRATEGY.md ]]`). Emit a structured 30-50 line snapshot — titles + tags only, never raw bodies. 2. **Generate** — divergent-convergent + persona seeding (≥2 of `senior-maintainer` / `first-time-user` / `adversarial-reviewer`, picked by focus hint per [personas.md](personas.md)). One divergent prompt; no self-judging. -3. **Critique** — separate prompt pass that does NOT see the focus hint or persona texts; rejection floor ≥40% (≥60% under `raise the bar`); fixed taxonomy (`duplicates-open-epic | out-of-scope | insufficient-signal | too-large | backward-incompat | other`); floor violation surfaces blocking question with frozen options `regenerate | loosen-floor | ship-anyway`. +3. **Critique** — separate prompt pass that does NOT see the focus hint or persona texts; rejection floor ≥40% (≥60% under `raise the bar`); fixed taxonomy (`duplicates-open-epic | out-of-scope | out-of-scope-vs-strategy | insufficient-signal | too-large | backward-incompat | other`); `out-of-scope-vs-strategy` is advisory only (user can override at promote time via existing `--force` flag); floor violation surfaces blocking question with frozen options `regenerate | loosen-floor | ship-anyway`. 4. **Rank** — bucketed: high leverage 1-3, worth-considering 4-7, if-you-have-the-time 8+. Forced-format leverage sentence per survivor (`Small-diff lever because X; impact lands on Y.`); no numeric scores. 5. **Write artifact** — atomic write-then-rename to `.flow/prospects/-.md` via `flowctl.write_prospect_artifact`. Same-day collisions suffix with `-2`, `-3`. Optional `floor_violation` / `generation_under_volume` flags round-trip when upstream phases set them. 6. **Handoff** — blocking prompt for promote / interview / skip via the platform's question tool; frozen numbered-options fallback when no blocking tool is available. diff --git a/plugins/flow-next/codex/skills/flow-next-prospect/workflow.md b/plugins/flow-next/codex/skills/flow-next-prospect/workflow.md index f22298c5..90c1820a 100644 --- a/plugins/flow-next/codex/skills/flow-next-prospect/workflow.md +++ b/plugins/flow-next/codex/skills/flow-next-prospect/workflow.md @@ -309,9 +309,40 @@ else fi ``` +#### Strategy snapshot (optional, present iff STRATEGY.md has at least one populated section) + +Verbatim emit pattern (mirrors CE-ideate's "emit approach and active tracks verbatim"). Pulls the `name`, `target_problem`, `approach`, `tracks` (raw markdown — `### ` H3 sub-blocks; downstream prompt context handles parsing), and `last_updated` from `flowctl strategy read --json`. Husk-vs-presence gate uses `sections_filled >= 1` from `flowctl strategy status --json`, NOT `[[ -f STRATEGY.md ]]`. + +```bash +STRATEGY_STATUS_JSON=$("$FLOWCTL" strategy status --json 2>/dev/null || echo '{"exists":false,"sections_filled":0}') +STRATEGY_FILLED=$(jq -r '.sections_filled // 0' <<< "$STRATEGY_STATUS_JSON" 2>/dev/null || echo 0) + +if [[ "$STRATEGY_FILLED" -ge 1 ]]; then + STRATEGY_JSON=$("$FLOWCTL" strategy read --json 2>/dev/null || echo '{}') + STRATEGY_NAME=$(jq -r '.name // "(unnamed)"' <<< "$STRATEGY_JSON") + STRATEGY_PROBLEM=$(jq -r '.target_problem // ""' <<< "$STRATEGY_JSON") + STRATEGY_APPROACH=$(jq -r '.approach // ""' <<< "$STRATEGY_JSON") + STRATEGY_TRACKS=$(jq -r '.tracks // ""' <<< "$STRATEGY_JSON") + STRATEGY_UPDATED=$(jq -r '.last_updated // "(unknown)"' <<< "$STRATEGY_JSON") + STRATEGY_BLOCK="strategy: + name: ${STRATEGY_NAME} + last_updated: ${STRATEGY_UPDATED} + target_problem: | +$(printf "%s\n" "$STRATEGY_PROBLEM" | sed 's/^/ /') + approach: | +$(printf "%s\n" "$STRATEGY_APPROACH" | sed 's/^/ /') + tracks: | +$(printf "%s\n" "$STRATEGY_TRACKS" | sed 's/^/ /')" +else + STRATEGY_BLOCK="strategy: scanned: none (no STRATEGY.md signal)" +fi +``` + +The `target_problem`, `approach`, and `tracks` strings are emitted **verbatim** — no paraphrasing, no field-extraction. `tracks` is a raw markdown string with `### ` H3 sub-blocks; downstream prompt context (Phase 2 generation, Phase 3 critique) handles the parsing. Empty section bodies surface as `""` (empty string), not null — `(.field // "")` style fallbacks keep the snapshot well-formed even when an optional section is missing. + ### 1.3 — Emit snapshot -Concatenate the blocks under a single `## Grounding snapshot` heading. Order is fixed (git log → open epics → changelog → memory → memory audit) so the snapshot is comparable across runs. Cap each block by line-count so total output stays in the 30-50 line target window. +Concatenate the blocks under a single `## Grounding snapshot` heading. Order is fixed (git log → open epics → changelog → memory → memory audit → strategy) so the snapshot is comparable across runs. Cap each block by line-count so total output stays in the 30-50 line target window. The snapshot is the input to Phase 2's generation prompt (task 2). For this task, the snapshot is printed to stdout for manual inspection — Phase 2's prompt scaffolding lands later. @@ -332,6 +363,8 @@ $CHANGELOG_BLOCK $MEMORY_BLOCK $AUDIT_BLOCK + +$STRATEGY_BLOCK EOF ``` @@ -493,12 +526,15 @@ Rejection taxonomy (R3 anchor — frozen string list): ``` duplicates-open-epic — material overlap with an open epic in the grounding snapshot out-of-scope — outside what this codebase / the focus area should tackle +out-of-scope-vs-strategy — contradicts an active track in the strategy snapshot (advisory only — user can override at promote time) insufficient-signal — speculative without evidence in grounding snapshot too-large — XL or undermined by size; should be split or deferred backward-incompat — would break public contracts / users without strong justification other — explain in `reason` field; use sparingly ``` +`out-of-scope-vs-strategy` is **advisory only**. It fires when a candidate's direction contradicts an active track from the strategy snapshot (Phase 1 §1.2). The rejection cites the violated track verbatim: `Rejected: [out-of-scope-vs-strategy] — contradicts active track ""`. The user can `flowctl prospect promote --idea N --force` to override (existing flag, no new plumbing). When the strategy snapshot scanned `none`, this category is unreachable — Phase 3 will not emit it. + Prompt template: ``` @@ -520,6 +556,7 @@ For each candidate, emit a verdict: `keep` or `drop`. If `drop`, the `taxonomy` - `duplicates-open-epic` — same direction as a listed open epic - `out-of-scope` — not aligned with this codebase or the focus area +- `out-of-scope-vs-strategy` — contradicts an active track in the strategy snapshot; cite the violated track name verbatim in `reason` (advisory — user can override) - `insufficient-signal` — no grounding evidence supports this being worth doing now - `too-large` — XL size or scope creep without commensurate payoff - `backward-incompat` — breaks contracts / users without strong justification @@ -535,7 +572,7 @@ Target rejection rate: **[REJECTION_FLOOR_PCT]%**. Below that floor, the run wil critiques: - index: 0 # zero-indexed position in the input list verdict: keep | drop - taxonomy: null | duplicates-open-epic | out-of-scope | insufficient-signal | too-large | backward-incompat | other + taxonomy: null | duplicates-open-epic | out-of-scope | out-of-scope-vs-strategy | insufficient-signal | too-large | backward-incompat | other reason: - index: 1 ... diff --git a/plugins/flow-next/codex/skills/flow-next-strategy/SKILL.md b/plugins/flow-next/codex/skills/flow-next-strategy/SKILL.md new file mode 100644 index 00000000..aff959c9 --- /dev/null +++ b/plugins/flow-next/codex/skills/flow-next-strategy/SKILL.md @@ -0,0 +1,267 @@ +--- +name: flow-next-strategy +description: "Create or maintain `STRATEGY.md` — the product's target problem, our approach, who it's for, key metrics, and tracks of work. Use when starting a new product, updating direction, or when prompts like 'write our strategy', 'update the roadmap', 'what are we working on', or 'set up the strategy doc' come up. Also fires when `/flow-next:prospect`, `/flow-next:plan`, `/flow-next:interview`, or `/flow-next:capture` need upstream grounding and no strategy doc exists yet." +user-invocable: false +allowed-tools: request_user_input, Read, Write, Bash +--- + +# /flow-next:strategy — repo-root STRATEGY.md anchor + +`flow-next-strategy` produces and maintains `STRATEGY.md` — a short, durable anchor at the repo root (peer of `README.md` / `GLOSSARY.md`) that captures what the product is, who it serves, how it succeeds, and where the team is investing. Downstream skills (`/flow-next:prospect`, `/flow-next:plan`, `/flow-next:interview`, `/flow-next:capture`, `/flow-next:sync`) read it as grounding when `sections_filled >= 1`. + +The document is short and structured on purpose. Good answers to a handful of sharp questions produce a better strategy than any amount of prose. This skill asks those questions, pushes back on weak answers, and writes the doc. + +**Note: The current year is 2026.** Use this when dating the strategy document. + +## Interaction Method + +Default to `request_user_input`. Fall back to numbered options in chat only when the tool is unreachable in the harness or the call errors — never silently skip the question. + +Ask one question at a time. **Free-form responses for the substantive sections** (Target problem / Our approach / Who it's for / Key metrics / Tracks). **Single-select with lead-with-recommendation only for routing decisions** (which section to revisit, include this optional section, foreign-file resolution). + +## Focus Hint + + #$ARGUMENTS + +Interpret any argument as an optional focus: a section name to revisit (`metrics`, `approach`, `tracks`, `problem`, `persona`, `milestones`, `not-working-on`) or a scope hint. With no argument, proceed open-ended and let the file state decide the path. + +## Core Principles + +1. **Anchor, not plan.** Strategy is what the product is and why. Features belong in `/flow-next:prospect`; tasks belong in epics and `/flow-next:plan`. Do not let either creep into the doc. +2. **Rigor in the questions, not the headings.** The section headers are plain English. The interview questions enforce strategy discipline (`references/interview.md`). +3. **Short is a feature.** The template is constrained. Adding sections costs more than it looks like. Push back on expansion. +4. **Durable across runs.** This skill is rerunnable. On a second run it updates in place, preserves what is working, and only challenges sections that look stale or weak. +5. **Survives `.flow/` wipe.** `STRATEGY.md` lives at repo root, never under `.flow/`. The project's strategy belongs to the project, not flow-next (R18 invariant from the 0.39.0 glossary epic). + +## Pre-check: local setup version + +Same pattern as `/flow-next:plan` and `/flow-next:audit` — non-blocking notice when `.flow/meta.json` `setup_version` lags the plugin version: + +```bash +if [[ -f .flow/meta.json ]]; then + SETUP_VER=$(jq -r '.setup_version // empty' .flow/meta.json 2>/dev/null) + PLUGIN_JSON="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$HOME/.codex}}/.codex-plugin/plugin.json" + [[ -f "$PLUGIN_JSON" ]] || PLUGIN_JSON="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/.claude-plugin/plugin.json" + PLUGIN_VER=$(jq -r '.version' "$PLUGIN_JSON" 2>/dev/null || echo "unknown") + if [[ -n "$SETUP_VER" && "$PLUGIN_VER" != "unknown" && "$SETUP_VER" != "$PLUGIN_VER" ]]; then + echo "Plugin updated to v${PLUGIN_VER}. Run /flow-next:setup to refresh local scripts (current: v${SETUP_VER})." >&2 + fi +fi +``` + +## flowctl path + +flowctl is **bundled — NOT installed globally.** `which flowctl` will fail (expected). Always use: + +```bash +FLOWCTL="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$HOME/.codex}}/scripts/flowctl" +[ -x "$FLOWCTL" ] || FLOWCTL=".flow/bin/flowctl" +``` + +## Execution Flow + +### Phase 0: Route by file state + +**0.1 — Ralph block (R17)** + +`/flow-next:strategy` is exploratory and human-in-the-loop. Autonomous loops have no business deciding repo strategy. Hard-error with exit 2 when running under Ralph. + +```bash +if [[ -n "${REVIEW_RECEIPT_PATH:-}" || "${FLOW_RALPH:-}" == "1" ]]; then + echo "[STRATEGY: user-triggered only — Ralph cannot run /flow-next:strategy]" >&2 + exit 2 +fi +``` + +No env-var opt-in. Ralph never decides direction. + +**0.2 — Read file state** + +```bash +STATUS_JSON=$("$FLOWCTL" strategy status --json) +EXISTS=$(printf '%s' "$STATUS_JSON" | jq -r '.exists') +HUSK=$(printf '%s' "$STATUS_JSON" | jq -r '.husk') +SECTIONS_FILLED=$(printf '%s' "$STATUS_JSON" | jq -r '.sections_filled') +GENERATOR_MATCH=$(printf '%s' "$STATUS_JSON" | jq -r '.generator_match') +FILE_PATH=$(printf '%s' "$STATUS_JSON" | jq -r '.file_path // empty') +``` + +JSON fields (frozen by Task 1): + +- `exists` (bool) — file present +- `husk` (bool) — `exists: true` AND `sections_filled == 0` +- `sections_filled` (int) — populated required-section count (0-5) +- `total_sections` (int) — always 5 (the 5 required) +- `last_updated` (str|null) — ISO date from frontmatter +- `file_path` (str|null) — absolute path of resolved STRATEGY.md +- `generator` (str|null) — frontmatter `generator` value +- `generator_match` (bool) — `generator == "flow-next-strategy"` + +**0.3 — Subdirectory walk-up surfacing (R16)** + +If `file_path` is set and differs from `${PWD}/STRATEGY.md`, surface one line in chat before any question fires: + +``` +Using repo-root STRATEGY.md at . +``` + +This is the only line printed before routing — keep the noise floor low. + +**0.4 — Foreign-file resolution (R15)** + +If `exists: true` AND `generator_match: false`, do not write. Fire `request_user_input`: + +- `body`: "Found a STRATEGY.md at `` not generated by flow-next-strategy (generator: ``). Recommended: `keep` — do not overwrite a hand-written or external-tool strategy doc. Confidence: [your-call] — your project, your call." +- `options`: + - `keep` → exit 0 with one-line stdout: `Keeping existing STRATEGY.md unchanged.` + - `migrate` → exit 0 with stderr: `Multi-format migration deferred to v2. Either delete or rename the file, then re-run /flow-next:strategy to bootstrap from scratch.` + - `rewrite` → second confirmation `request_user_input`: + - `body`: "Confirm destructive overwrite? The existing file at `` will be replaced. Recommended: `cancel`. Confidence: [your-call]." + - `options`: `confirm-overwrite` → proceed to Phase 1 first-run interview; `cancel` → exit 0. + +Single-select `request_user_input`, lead-with-recommendation, neutral option labels. + +**0.5 — Routing** + +After Ralph block, walk-up surfacing, and foreign-file resolution: + +| State | Route | +|-------|-------| +| `exists: false` | Phase 1 (first-run interview) | +| `exists: true` AND `husk: true` AND `generator_match: true` | Phase 1 (first-run; husk was probably an aborted run) | +| `exists: true` AND `husk: false` AND `generator_match: true` | Phase 2 (section-revisit update) | + +Announce path in one line: `Strategy doc not found — let's write it.` or `Found existing strategy — let's review and update.` + +### Phase 1: First-run interview + +**1.1 — Load interview rules (non-optional)** + +```text +Read `references/interview.md`. +``` + +This load is non-optional. The pushback rules, anti-pattern examples, and quality bar for each section live there. Improvising from memory produces a passive transcription instead of a strategy doc. + +**1.2 — Run the interview in section order** + +For each of the 5 required sections (in order: `Target problem` → `Our approach` → `Who it's for` → `Key metrics` → `Tracks`), follow the per-section rule in `references/interview.md`: + +- Ask the **opening question** verbatim from the references file. +- Evaluate the answer against the **strong-answer signature**. +- If the answer falls into a named anti-pattern, push back with the **sharper follow-up** — quoting the user's words back at them, NOT paraphrasing. Anti-pattern label names (`vanity`, `fluff`, `feature-list`, etc.) are internal-only — never appear in question bodies. +- **2 rounds maximum.** After round 2, capture the user's words verbatim and append the HTML comment `` to the section body. Do not let the interview spiral. +- Use **free-form responses** — no menu options, no recommendation in the question body. + +**1.3 — Per-section atomic writes** + +After each section is captured, build the partial draft and write to `STRATEGY.md` via `Write` tool **before the next question fires**. `last_updated` bumps on every save. No draft state file. Mid-flow abandonment leaves a partially-populated file readable on disk; resume is via Phase 0 → Phase 2 routing. + +The partial-draft shape: frontmatter + H1 + the captured section(s) + placeholder bodies (`_Not yet captured._`) for unfilled required sections. Optional sections are absent until Phase 1.4. + +**1.4 — Optional sections (gated by routing question)** + +After all 5 required sections land, ask once per optional section whether to include it. **Routing question** with lead-with-recommendation: + +For `Milestones`: + +- `body`: "Do you want a `Milestones` section? It's only worth adding if there are externally visible dated anchors — launches, fundraises, conferences, renewals. Recommended: `skip` — internal schedules don't belong here. Confidence: [your-call]." +- `options`: `include`, `skip`. + +For `Not working on`: + +- `body`: "Do you want a `Not working on` section? Only useful for things the team keeps being tempted by — a clarity tool, not a backlog. Recommended: `skip` — most repos don't need it. Confidence: [your-call]." +- `options`: `include`, `skip`. + +A `Marketing` section is **deliberately not offered** — over-rotated for OSS-tools repos. + +If `include`, run the per-section interview from `references/interview.md` (same 2-round-cap rule), then atomic-write that section. If `skip`, omit the section entirely from the file (do not leave an empty header). + +**1.5 — Mandatory read-back before final commit** + +After all sections captured (required + any included optional), run: + +```bash +"$FLOWCTL" strategy read --json +``` + +Show the final draft body in chat. Offer one round of edits via `request_user_input`: + +- `body`: "Draft complete. -section strategy doc, . Recommended: `commit` — the draft reflects the captured answers verbatim. Confidence: [judgment-call]." +- `options`: `commit`, `edit-section`, `abandon`. + +On `edit-section`, ask which section via single-select (5 required + included optional names), re-run the per-section interview, atomic-write, return to read-back. + +On `commit`, the file is already on disk (per-section atomic writes) — this is a confirmation step, not a new write. Acknowledge with one stdout line: `Strategy doc written to . last_updated: .` + +On `abandon`, leave the file as-is (partially populated is fine), exit 0. + +### Phase 2: Update run (file exists, generator matches) + +**2.1 — Summarize current state** + +Read the existing `STRATEGY.md` via `flowctl strategy read --json` and summarize current state in 3-5 lines so the user sees what's on file. Surface section names + 1-line excerpts. + +If the focus-hint argument names a specific section, jump to that section. Otherwise, fire the routing question. + +**2.2 — Section-revisit routing question (lead-with-recommendation)** + +Build the option list dynamically: + +- For each of the 5 required sections + included optional sections, check the body for `` markers (priority candidates). +- Sections with no marker but visibly weak content (≤1 short sentence, or contains placeholder-shaped text) join the priority list. +- Sections that look strong fall to the bottom. + +`request_user_input`: + +- `body`: "Which section to revisit? . Recommended: `` — it carries a `` marker from a previous run [if applicable]. Confidence: [judgment-call] — your judgment on what feels stale." +- `options`: section names + `done` (no further changes). + +**2.3 — Per-section re-interview** + +For the chosen section, re-run the per-section interview from `references/interview.md` — full pushback, NOT a rubber-stamp. After capture, atomic-write that section's new body. Untouched sections preserved byte-identical (verified by `git diff --unified=0` if questioned). `last_updated` bumps to today's ISO date. + +**2.4 — Loop or exit** + +After a section is updated, return to the routing question — user can revisit another section or pick `done`. On `done`, run the read-back step (Phase 1.5 logic) once for confirmation, then exit. + +### Phase 3: Downstream handoff + +After writing (first-run or update), surface the file's role to the user in one paragraph: + +- If `.flow/epics/` is empty AND `.flow/prospects/` is empty: `Strategy doc written. Next, /flow-next:prospect [optional focus] generates ranked candidate ideas grounded in the strategy you just captured.` +- If `.flow/` is populated: `Strategy doc written. Downstream skills (/flow-next:prospect, /flow-next:plan, /flow-next:interview, /flow-next:capture, /flow-next:sync) will read STRATEGY.md as grounding on next invocation.` + +One paragraph max. No follow-up questions. + +## What this skill does not do + +- Does not update the issue tracker or reconcile in-flight work. Strategy is the doc; execution lives in epics, tasks, and `/flow-next:plan`. +- Does not write product requirements or implementation plans — those are `/flow-next:capture` and `/flow-next:plan`. +- Does not compute metric values. It records *which* metrics matter and where they live, not what they read today. +- Does not create per-subdirectory STRATEGY.md files. Strategy is repo-wide by Rumelt's definition; cascading strategies re-introduce the "is for everyone, is for no one" problem. +- Does not migrate hand-written or CE-format STRATEGY.md files. v1 ships sentinel-based foreign-file refusal; multi-format migration is a v2 problem. +- Does not delete the file when all sections are removed. Last-section deletion leaves a husk (`# Strategy` H1 + frontmatter) on disk — file never deleted (R23 invariant, mirrors `render_glossary_file`). + +## Forbidden + +- **Running under Ralph** — hard-block via the Phase 0.1 guard. +- **Setting `context: fork`** — `request_user_input` must stay reachable across phases. +- **Inline cross-platform tool tables** in prose (multi-platform listings naming the tool primitive on each harness). Canonical files use Claude-native names only; sync-codex.sh handles the Codex rewrite. +- **Lead-with-recommendation on substance questions** — problem / approach / persona / metrics / tracks get free-form, no recommendation, no menu. Recommendation primes the user out of their own language. Routing questions only. +- **Leaking anti-pattern names** to the user. `vanity` / `fluff` / `feature-list` / `goal-stated-as-problem` are internal labels for formulating sharper follow-ups. +- **Auto-overwriting a foreign-file STRATEGY.md** — Phase 0.4 always asks. v1's stance is refusal; user can rename or delete to bootstrap. +- **Writing more than 4 sentences per section** (except Tracks, where each track has its own short block). The post-write checklist in `references/strategy-template.md` catches this. +- **Adding sections beyond the locked 5 + 2 optional**. CE's `Marketing` section is dropped on purpose; do not re-introduce it. Section order is locked. +- **Inventing flowctl subcommands** — Task 1 ships `flowctl strategy {status,read,list}` only. Skill writes the file directly via `Write` tool; no `flowctl strategy add` exists. + +## Output rules + +The deliverable is the written `STRATEGY.md` itself. Surface to chat: + +- One-line path announcement at Phase 0 (walk-up subdir or file state). +- Per-section interview Q&A (the agent's questions; user's answers). +- Final draft read-back in Phase 1.5 / 2.4. +- One-paragraph downstream handoff at Phase 3. + +No internal summary printed at exit beyond the Phase 3 handoff line. The file IS the report. diff --git a/plugins/flow-next/codex/skills/flow-next-strategy/agents/openai.yaml b/plugins/flow-next/codex/skills/flow-next-strategy/agents/openai.yaml new file mode 100644 index 00000000..b9d3236f --- /dev/null +++ b/plugins/flow-next/codex/skills/flow-next-strategy/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "Flow Strategy" + short_description: "Generate or update repo-root STRATEGY.md (problem, approach, personas, metrics, tracks)" + brand_color: "#3B82F6" +policy: + allow_implicit_invocation: false diff --git a/plugins/flow-next/codex/skills/flow-next-strategy/references/interview.md b/plugins/flow-next/codex/skills/flow-next-strategy/references/interview.md new file mode 100644 index 00000000..279dc41e --- /dev/null +++ b/plugins/flow-next/codex/skills/flow-next-strategy/references/interview.md @@ -0,0 +1,151 @@ +# Strategy Interview + +Loaded by `SKILL.md` at the start of Phase 1 and revisited per-section in Phase 2. Every section below maps one-to-one to a section in `strategy-template.md`. + +For each section: ask the opening question, evaluate the answer against the quality bar, push back when it falls into a named anti-pattern, and capture the final answer in the user's own language. + +## Overall Rules + +1. **Ask, don't prescribe.** Do not offer menu options for open answers (problem, approach, persona, metrics, tracks). Use free-form responses. Reserve single-select for routing decisions only (which section to revisit, include this optional section, foreign-file resolution). +2. **Push back once, maybe twice.** If the first answer is weak, name the specific issue and ask a sharper question. If the second answer is still weak, capture what the user has given and append the HTML comment `` to the section body. Do not let the interview spiral. +3. **Quote the user back at them.** When challenging an answer, use the user's own words verbatim. Paraphrasing softens the challenge and is easier to dismiss. +4. **Keep each answer to 1-3 sentences.** Longer answers are usually hiding something vague. If the user writes a paragraph, ask them to pick the sentence that matters most. +5. **Don't leak the anti-pattern names.** The user does not need to hear "that's a vanity metric" or "that's fluff" — just ask the sharper question that follows. Names like `vanity`, `fluff`, `feature-list-as-track`, `goal-stated-as-problem` are internal labels for the agent's diagnosis, not vocabulary for the conversation. + +--- + +## 1. Target Problem + +**Opening question:** "What's the core problem this product solves — and what makes that problem hard?" + +Strong answers name a specific situation the target user is in, identify what makes the situation hard *right now* (a crux, a constraint, something that isn't easy to route around), and are falsifiable — you could imagine the problem being absent and know the difference. + +**Anti-patterns and pushback:** + +- **goal-stated-as-problem** ("the problem is we need to grow revenue") → "That's a goal, not a problem. What's in the world that's making that goal hard to achieve? Whose situation are you changing?" +- **vague-wish** ("people need better tools for X") → "Whose situation specifically? Doing what? What do they try today, and why doesn't it work?" +- **symptom-not-cause** ("users churn after 30 days") → "That's a symptom. What's happening in their world that makes them stop caring? What's the underlying condition?" +- **too-broad** ("communication at work is broken") → "That's a civilization-scale problem. Narrow it to a situation you can actually affect — which users, doing what, when does it hurt most?" +- **feature-shaped** ("there's no good way to do [specific workflow] with AI") → "That's a missing feature, not the underlying problem. What outcome do users want that the feature would give them?" + +**Capture:** One or two sentences naming the user's situation and the crux. No solution language. + +--- + +## 2. Our Approach + +**Opening question:** "Given that problem, what's your approach — the commitment or principle that makes it tractable?" + +This is the guiding choice: how the product competes or operates, so that many downstream decisions become easier. It is not the product and it is not a feature list. + +Strong answers are a choice (implying alternatives explicitly *not* pursued), are general enough to direct many decisions but specific enough to rule things out, and sound more like "we win by [doing X differently]" than "we do [a list of things]". + +**Anti-patterns and pushback:** + +- **fluff** ("we're customer-obsessed and move fast") → "Those are values, not an approach. What are you doing *differently* from the other products users could pick? If the answer applies to any company, it's not your approach." +- **feature-list** ("we're building AI-powered X, Y, and Z") → "That's a feature list. What's the underlying bet that makes you pick those features over others? What principle is guiding what you ship?" +- **product-description-as-approach** ("we use AI to draft replies") → "That's what the product does, but what's the *choice* inside it? Every competitor will say the same thing. Your approach should name what you're doing that the obvious alternative isn't — is it a grounding choice, a trust-building commitment, a workflow bet? What are you betting on that they're not?" +- **goal-restated** ("our approach is to be the market leader") → "That's still the goal. How does the product win? What choice are you making that competitors aren't?" +- **multiple-approaches-at-once** ("we're going deep on enterprise, self-serve, and a consumer app") → "Pick one as the guiding approach. The others may still get work, but one of them organizes the rest. Which is it?" +- **doesnt-connect-to-problem** (problem: "users can't trust AI output"; approach: "build a fast, beautiful UI") → "How does that approach solve the problem you named? If there's no line between them, one of the two is wrong." + +**Capture:** One or two sentences. Ideally ends with or implies "...so that [outcome tied to the problem]". + +--- + +## 3. Who It's For + +**Opening question:** "Who is the primary user, and what job are they hiring this product to do?" + +Jobs-to-be-done framing — the user isn't a demographic, they're someone in a situation trying to make progress. + +Strong answers name one primary persona (additional personas allowed but secondary), identify them by role or situation rather than demographic, and state a concrete job as a verb phrase. + +**Anti-patterns and pushback:** + +- **too-many-primary-personas** ("it's for founders, PMs, engineers, and designers") → "If it's for everyone, it's for no one. Who matters most? The others can still benefit, but one of them drives the product decisions." +- **demographic-framing** ("25-45 year old professionals") → "That's a demographic, not a user. What are they trying to do that makes them pick up this product?" +- **role-without-situation** ("PMs") → "PMs doing what? Running a roadmap review? Writing a spec at midnight? Convincing a skeptical eng lead? The situation is where the product matters." +- **generic-job** ("they want to be more productive") → "Productive at what specifically? They're hiring this product to do *what*? The more specific, the better the product decisions downstream." + +**Capture:** Persona name plus JTBD sentence. Example: "Solo founders running their own roadmap. They're hiring the product to keep strategy and execution aligned without a PM on staff." + +--- + +## 4. Key Metrics + +**Opening question:** "What 3-5 metrics will tell you whether the approach is working?" + +Metrics are the feedback loop. Bad metrics create the illusion of progress while the product gets worse. + +Strong answers stay at 3-5 (not 10), mix leading and lagging (something that moves weekly and something that moves quarterly), and could plausibly regress if the product got worse. + +**Anti-patterns and pushback:** + +- **vanity** ("total signups, total pageviews, cumulative users") → "Those can all go up while the product gets worse. What moves when users actually get value?" +- **too-many** ("here are 12 metrics we watch") → "A dashboard isn't a strategy. Pick the 3-5 you'd stake the quarter on. What are the others telling you that those don't?" +- **outputs-not-outcomes** ("ship velocity, deploys per week") → "Those measure the team, not the product. If the team doubled velocity but users didn't care, would you call it a win?" +- **can-only-go-up** ("cumulative hours saved") → "A metric that can only go up doesn't tell you much. What's the rate, the ratio, or the thing that can regress?" +- **unmeasurable** ("user delight") → "How specifically? If you can't define how you'd check it on a Tuesday, it's aspirational, not a metric." + +**Capture:** A list of 3-5. Each with a one-line definition. Note where each is measured (analytics, DB, qualitative, etc.) if known. If measurement is undefined, ask: "Where does this metric live today? If nowhere, is this something you can start measuring?" + +--- + +## 5. Tracks + +**Opening question:** "What are the 2-4 tracks of work you're investing in to execute the approach?" + +Tracks are the coherent-actions half of the strategy kernel — concrete areas of investment that flow from the approach. They are not feature lists and not personal todo items. Each track is a named *domain of work*. + +Strong answers stay at 2-4 (not 8, not 1), connect clearly back to the approach, and are broad enough that multiple features live inside each one. + +**Anti-patterns and pushback:** + +- **feature-list-as-track** ("track 1: Slack integration; track 2: mobile app; track 3: dark mode") → "Those are features. What's the *investment area* each one lives inside? 'Integrations' might be one track, with Slack, Teams, and Discord as candidates inside it." +- **too-many-tracks** ("we have 7 tracks this quarter") → "With 7 tracks, every track is starved for attention. Which 3 are load-bearing? The others either fold in or drop." +- **doesnt-connect-to-approach** (approach: "win by being the easiest to onboard"; track: "enterprise SSO") → "How does that track serve the approach? If it's a separate bet, name it as one. If it's load-bearing for onboarding, explain the link." +- **too-vague** ("improve the product") → "Every track is 'improve the product.' What's the specific investment area that's different from the others?" +- **one-track-only** → "With one track, there's no real choice being made. What are the 2-3 things the product needs to be good at, and how are they different?" + +**Capture:** 2-4 tracks. For each: a name, a one-line purpose, and a short note on why this serves the approach. + +--- + +## 6. Milestones (optional) + +**Opening question:** "Are there any dated milestones worth anchoring — a launch, a fundraise, a conference, a renewal? Skip if none apply." + +Only capture externally visible, real milestones. Avoid turning this into an internal schedule. + +Default is to skip. Do not push the user to invent milestones. If they name some, capture them verbatim with dates. + +**Anti-patterns and pushback:** + +- **internal-schedule-disguised** ("v2.3 ships March 15, refactor done by April") → "Those are sprint dates, not milestones. Is there an *externally visible* anchor — something users or stakeholders see? If not, skip the section." +- **manufactured-milestones** (user lists half-real dates to fill the section) → "If they're not real anchors, the section is hurting more than helping. Skip it; that's the right call most of the time." + +**Capture:** Verbatim dates + one-line description per milestone. Do not encourage a long list. + +--- + +## 7. Not Working On (optional) + +**Opening question:** "Is there anything you've explicitly decided *not* to do right now that's worth naming? This is for things the team keeps being tempted by." + +Clarity tool, not a blocker list. Skip by default. If the user names items, one sentence each. Do not encourage a long list. + +**Anti-patterns and pushback:** + +- **catalog-of-rejections** (long list of things never seriously considered) → "This section is for things the team keeps being tempted by. Stuff you never considered isn't useful here. Pick the 1-2 that actually pull at you." +- **competitor-bashing** ("we're not building a clone of ") → "That's not a strategic boundary, it's a swipe. What's the *type of work* you're declining? E.g. 'enterprise SSO before product-market-fit' or 'mobile app before web is solid'." + +**Capture:** 1-3 items max. One sentence each. + +--- + +## After the Interview + +Once sections 1-5 are captured (and any optional sections the user engaged with), the per-section atomic writes have already landed each section on disk. Run `flowctl strategy read --json` to get the current state, present the full draft in chat for read-back, offer one round of edits, then confirm. + +The post-write checklist (in `strategy-template.md` §Post-write checklist) is the agent's own scan-pass before showing the read-back — catch placeholder leftovers, sentence-count violations, missing-frontmatter cases, metric/track count out of range, and Target-problem ↔ Our-approach disconnect. diff --git a/plugins/flow-next/codex/skills/flow-next-strategy/references/strategy-template.md b/plugins/flow-next/codex/skills/flow-next-strategy/references/strategy-template.md new file mode 100644 index 00000000..e7cf6273 --- /dev/null +++ b/plugins/flow-next/codex/skills/flow-next-strategy/references/strategy-template.md @@ -0,0 +1,86 @@ +# Strategy Template + +Loaded by `SKILL.md` after each section is captured. Fill it in using the captured answers and write to `STRATEGY.md` via the `Write` tool. Per-section atomic writes mean each section lands on disk before the next interview prompt fires. + +## Rules for filling in + +- Use the user's own language where possible. Do not paraphrase into generic PM-speak. +- Each section stays compact. The whole doc should read in under 5 minutes. +- Section order is locked. Do not add new top-level sections (a `Marketing` section was considered and deliberately excluded — over-rotated for OSS-tools repos). +- Optional sections: delete entirely if unused. Do not leave empty headers. +- Set `last_updated` in the YAML frontmatter to today's ISO date (`YYYY-MM-DD`). Do not duplicate the date in prose. +- Set `name` in the frontmatter to the product or initiative name (the same value used in the H1 title). +- Set `generator: flow-next-strategy` in the frontmatter (foreign-file detection sentinel — required). +- After 2 rounds of pushback that didn't land, capture the user's words verbatim and append `` to that section's body. Phase 2 surfaces these markers as priority candidates on re-run. + +## Template + +The block below is the literal file shape (minus this line and the fences). Replace every `{{placeholder}}` with the captured answer. Delete any optional section whose placeholder wasn't answered. + +~~~markdown +--- +name: {{product_name}} +last_updated: {{YYYY-MM-DD}} +generator: flow-next-strategy +--- + +# {{product_name}} Strategy + +## Target problem + +{{1-2 sentence diagnosis. Names the user situation and the crux that makes it hard. No solution language.}} + +## Our approach + +{{1-2 sentence guiding policy. What this product commits to, so that the target problem becomes tractable.}} + +## Who it's for + +**Primary:** {{Persona name}} — {{one-sentence JTBD, e.g. "They're hiring {{product_name}} to..."}} + + + +## Key metrics + +- **{{metric 1 name}}** — {{one-line definition; where it's measured}} +- **{{metric 2 name}}** — {{...}} +- **{{metric 3 name}}** — {{...}} + + + +## Tracks + +### {{Track 1 name}} + +{{One line: what this track is — the investment area, not a feature list.}} + +_Why it serves the approach:_ {{one line}} + + + +## Milestones + +- **{{YYYY-MM-DD}}** — {{milestone}} + + + +## Not working on + +- {{one line per item}} + + +~~~ + +## Post-write checklist + +Before showing the read-back to the user, scan the draft for: + +- [ ] Frontmatter present at the top with `name`, `last_updated`, and `generator: flow-next-strategy` keys. +- [ ] `last_updated` carries today's date in ISO format (YYYY-MM-DD). +- [ ] No section has more than 4 sentences except `Tracks` (where each track has its own short block). +- [ ] No placeholders remain (`{{...}}`). +- [ ] Optional sections with no content have been deleted, not left empty (the H2 header is gone — file does not contain `## Milestones` if it has no items). +- [ ] Metric count is between 3 and 5. Track count is between 2 and 4. +- [ ] `Target problem` and `Our approach` are connected — one clearly responds to the other. +- [ ] `` markers (if any) are inside the section body, after the captured prose — not in the header line. +- [ ] H1 title matches the frontmatter `name` value. diff --git a/plugins/flow-next/codex/skills/flow-next-sync/SKILL.md b/plugins/flow-next/codex/skills/flow-next-sync/SKILL.md index de64d229..801707bc 100644 --- a/plugins/flow-next/codex/skills/flow-next-sync/SKILL.md +++ b/plugins/flow-next/codex/skills/flow-next-sync/SKILL.md @@ -95,20 +95,27 @@ No downstream tasks to sync (all done or none exist). ``` Stop here (success, nothing to do). -### Step 5: Gather glossary + decisions context +### Step 5: Gather glossary + decisions + strategy context -Two extra context types help the agent catch drift the spec text alone can't reveal: project-glossary terms (renames where the old spec used a term whose `_Avoid_` alias now appears in code) and active decision constraints (current code may touch files mentioned in a decision's `Consequences` section). +Three extra context types help the agent catch drift the spec text alone can't reveal: project-glossary terms (renames where the old spec used a term whose `_Avoid_` alias now appears in code), active decision constraints (current code may touch files mentioned in a decision's `Consequences` section), and strategic-intent drift (completed task contradicts an active `STRATEGY.md` track or approach). ```bash GLOSSARY_JSON="$("$FLOWCTL" glossary list --json 2>/dev/null \ || echo '{"groups":[],"file_count":0,"total_terms":0}')" DECISIONS_JSON="$("$FLOWCTL" memory list --track knowledge --category decisions --json 2>/dev/null \ || echo '{"entries":[],"legacy":[],"count":0,"status":"active"}')" +STRATEGY_CONTENT="$("$FLOWCTL" strategy read --json 2>/dev/null || echo '{}')" ``` -Both calls are best-effort — empty defaults keep the agent prompt valid when flowctl returns nothing or fails. +All three calls are best-effort — empty defaults keep the agent prompt valid when flowctl returns nothing or fails. -When `GLOSSARY_JSON` reports `file_count == 0` AND `DECISIONS_JSON` reports `count == 0`, skip the extra context (pass the empty defaults — the agent treats them as a no-op signal). +**Husk short-circuit** — when ALL three of the following hold, skip the extra context entirely (pass the empty defaults; the agent's husk short-circuit at the top of Phase 3b will skip the whole section): + +- `GLOSSARY_JSON.total_terms == 0` (glossary missing or husk) +- `DECISIONS_JSON.count == 0` (no decision entries) +- `STRATEGY_CONTENT.sections_filled == 0` OR `STRATEGY_CONTENT == {}` (no STRATEGY.md or husk — verify with `flowctl strategy status --json | jq '.sections_filled // 0'`) + +When ANY of the three has signal, pass through all three (untouched) and let the agent run the matching subsection (3b.1 / 3b.2 / 3b.3) and skip the empty ones. When `GLOSSARY_JSON.total_terms == 0` but `file_count > 0`, every group is a husk. Husks carry no signal for drift detection — pass the JSON through untouched and let the agent skip them. @@ -127,6 +134,7 @@ DRY_RUN: GLOSSARY_JSON: DECISIONS_JSON: +STRATEGY_CONTENT: DRY RUN MODE: Report what would change but do NOT use Edit tool. Only analyze and report drift. diff --git a/plugins/flow-next/commands/flow-next/strategy.md b/plugins/flow-next/commands/flow-next/strategy.md new file mode 100644 index 00000000..de9c2dd4 --- /dev/null +++ b/plugins/flow-next/commands/flow-next/strategy.md @@ -0,0 +1,13 @@ +--- +name: flow-next:strategy +description: Create or maintain `STRATEGY.md` — repo-root anchor for target problem, approach, users, key metrics, and tracks +argument-hint: "[optional: section to revisit, e.g. 'metrics' or 'approach']" +--- + +# IMPORTANT: This command MUST invoke the skill `flow-next-strategy` + +The ONLY purpose of this command is to call the `flow-next-strategy` skill. You MUST use that skill now. + +**Arguments:** $ARGUMENTS + +Pass the arguments to the skill verbatim. The skill handles Ralph-block, file-state routing, foreign-file refusal, the section interview, atomic per-section writes, mandatory read-back, and downstream handoff. diff --git a/plugins/flow-next/scripts/ci_test.sh b/plugins/flow-next/scripts/ci_test.sh index 5915d221..3fe1d567 100755 --- a/plugins/flow-next/scripts/ci_test.sh +++ b/plugins/flow-next/scripts/ci_test.sh @@ -387,6 +387,34 @@ else pass "R4: no meta-file refs in canonical" fi +# ───────────────────────────────────────────────────────────────────────────── +# 5d. Strategy-doc fluff guard — R19 (fn-39 task 5). +# This is the strategy-doc fluff guard, NOT R17 (DDD vocabulary). +# Each grep block has one purpose; do not merge with section 5c. +# Tier 1 jargon only (Rumelt's "fluff" hallmarks): synergy / pivot / +# disrupt / thought-leadership / best-in-class / world-class / 10x. +# Scope: flow-next-strategy skill + cmd_strategy_* in flowctl.py + +# strategy.md command file. The references/interview.md file is +# EXCLUDED from this guard — it must describe these anti-patterns +# to push back on them (same exemption pattern as glossary references). +# ───────────────────────────────────────────────────────────────────────────── +echo -e "\n${YELLOW}--- Strategy-doc fluff guard (R19) ---${NC}" + +set +e +FLUFF_HITS="$(grep -RnEi '\bsynergy\b|\bpivot\b|\bdisrupt\b|thought[ -]leadership|best-in-class|world-class|\b10x\b' \ + "$PLUGIN_ROOT/skills/flow-next-strategy/SKILL.md" \ + "$PLUGIN_ROOT/commands/flow-next/strategy.md" 2>/dev/null \ + ; awk '/^def cmd_strategy_/,/^def [^_]/' "$PLUGIN_ROOT/scripts/flowctl.py" 2>/dev/null \ + | grep -nEi '\bsynergy\b|\bpivot\b|\bdisrupt\b|thought[ -]leadership|best-in-class|world-class|\b10x\b' \ + | sed 's|^|flowctl.py(cmd_strategy_*):|')" +set -e +if [[ -n "$FLUFF_HITS" ]]; then + fail "R19 strategy-doc fluff vocabulary in canonical:" + echo "$FLUFF_HITS" | sed 's/^/ /' +else + pass "R19: no strategy-doc fluff vocabulary in canonical" +fi + # ───────────────────────────────────────────────────────────────────────────── # 6. Symbol Extraction # ───────────────────────────────────────────────────────────────────────────── diff --git a/plugins/flow-next/scripts/flowctl.py b/plugins/flow-next/scripts/flowctl.py index 21bf4efe..be14082e 100755 --- a/plugins/flow-next/scripts/flowctl.py +++ b/plugins/flow-next/scripts/flowctl.py @@ -63,6 +63,49 @@ def _flock(f, lock_type): GLOSSARY_FILE = "GLOSSARY.md" GLOSSARY_WALK_MAX_DEPTH = 32 # Defensive cap against pathological symlinks. +# Strategy (fn-39.1): repo-root single-root markdown file. Unlike glossary +# (which cascades nearest-ancestor for vocab locality), strategy is repo-wide +# by Rumelt's definition — one diagnosis, one guiding policy. Single root only. +STRATEGY_FILE = "STRATEGY.md" +STRATEGY_GENERATOR = "flow-next-strategy" +STRATEGY_WALK_MAX_DEPTH = 32 # Defensive cap (parallel to GLOSSARY_WALK_MAX_DEPTH). +# Locked section structure: 5 required + 2 optional. Order maps onto Rumelt's +# strategy kernel (diagnosis / guiding policy / coherent action) extended with +# persona + metrics for repo-doc utility. A Marketing section was considered +# and dropped — over-rotated for OSS-tools repos. Section name is the H2 +# heading text; key is the dict key returned by parse_strategy_file. +STRATEGY_REQUIRED_SECTIONS: tuple[tuple[str, str], ...] = ( + ("Target problem", "target_problem"), + ("Our approach", "approach"), + ("Who it's for", "personas"), + ("Key metrics", "metrics"), + ("Tracks", "tracks"), +) +STRATEGY_OPTIONAL_SECTIONS: tuple[tuple[str, str], ...] = ( + ("Milestones", "milestones"), + ("Not working on", "not_working_on"), +) +# Husk sentinel: section body when user explicitly wants a section deleted +# but R-ID locked structure requires the heading to remain. _strategy_section_filled +# treats this body as empty (sections_filled count excludes it). +STRATEGY_HUSK_SENTINEL = "_Not currently tracking._" +# First-run partial-save placeholder: the strategy skill writes this into every +# unfilled required section during atomic per-section writes so a husk file is +# always renderable. _strategy_section_filled treats it as empty so mid-flow +# abandonment correctly reports `sections_filled == `. +STRATEGY_DRAFT_PLACEHOLDER = "_Not yet captured._" +# Combined empty-body sentinels. Add new placeholders here only — keep +# _strategy_section_filled's contract unified across all "looks present +# on disk but means empty" markers. +STRATEGY_EMPTY_SENTINELS: frozenset[str] = frozenset( + {STRATEGY_HUSK_SENTINEL, STRATEGY_DRAFT_PLACEHOLDER} +) +# Frontmatter contract: exactly these three keys. Refuse unknown keys to keep +# the audit story simple (single-source-of-truth invariant). +STRATEGY_FRONTMATTER_FIELDS: frozenset[str] = frozenset( + {"name", "last_updated", "generator"} +) + EPIC_STATUS = ["open", "done"] TASK_STATUS = ["todo", "in_progress", "blocked", "done"] @@ -405,6 +448,339 @@ def _glossary_term_matches(a: str, b: str) -> bool: ) +# --- Strategy helpers (fn-39.1) --- +# +# Shape on disk: +# --- +# name: +# last_updated: 2026-05-01 +# generator: flow-next-strategy +# --- +# +# # Strategy +# +# ## Target problem +# ... +# +# Parse contract: +# - Frontmatter is mandatory; missing or malformed → returns empty dict +# so callers can detect (caller checks `parsed.get("name")`). +# - H2 heading text is matched case-sensitively against the locked section +# list (`STRATEGY_REQUIRED_SECTIONS + STRATEGY_OPTIONAL_SECTIONS`). +# - Unknown H2 sections are dropped silently — the file may carry sections +# we don't recognize (forward-compat with future CE additions); they +# simply don't surface in the parsed dict. +# - Fenced code blocks are masked during heading scan via +# `_glossary_strip_fenced_code` so an `## Example heading` inside a +# fence is not picked up. +# - CRLF normalized. +# +# Single-root walk: `find_strategy_file` walks UP from cwd to first +# STRATEGY.md, capped at git repo root via `get_repo_root()`. NOT +# nearest-ancestor like glossary — strategy is repo-wide. + +# Section-name lookup (case-insensitive H2 heading → dict key). +# Accepts both the H2 heading text ("Our approach") and the dict key +# ("approach" / "our_approach") for CLI ergonomics — downstream skills +# read JSON with the dict-key shape, so accepting the same form for +# `--section` saves one rename in skill prose. +_STRATEGY_SECTION_KEYS: dict[str, str] = {} +for _name, _key in STRATEGY_REQUIRED_SECTIONS + STRATEGY_OPTIONAL_SECTIONS: + _STRATEGY_SECTION_KEYS[_name.lower()] = _key + # Also accept the dict-key form (with underscores) directly. + _STRATEGY_SECTION_KEYS[_key.lower()] = _key + # And the heading-text-with-spaces lowercased for case-insensitive use. +del _name, _key +# All valid section names (lowercase) for `read --section` validation. +_STRATEGY_SECTION_NAMES_LOWER: frozenset[str] = frozenset(_STRATEGY_SECTION_KEYS.keys()) + +_STRATEGY_HEADING_RE = re.compile(r"^##\s+(.+?)\s*$", re.MULTILINE) +# YYYY-MM-DD ISO date for last_updated validation. +_STRATEGY_ISO_DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$") +# HTML comment matcher used by _strategy_section_filled. +_STRATEGY_HTML_COMMENT_RE = re.compile(r"", re.DOTALL) + + +def find_strategy_file(start: Optional[Path] = None) -> tuple[Optional[Path], Path]: + """Return `(strategy_path, repo_root)` for the single-root strategy file. + + Resolves the git repo root from `start` (default cwd) and checks for + `STRATEGY.md` ONLY at that root. Single-root semantics — strategy is + repo-wide by Rumelt's definition. Intentionally does NOT walk upward + from `start`; an `apps/web/STRATEGY.md` (intentional or accidental) is + ignored, and the repo-root file is the only one downstream skills + consume regardless of cwd. + + Returns: + `(repo_root / STRATEGY.md, repo_root)` if the repo-root file exists. + `(None, repo_root)` if absent — caller can decide whether to refuse + or guide the user to `/flow-next:strategy`. + + Outside-a-repo behavior: git lookup falls back to `start` itself so + the check degenerates to "is there a STRATEGY.md in start?" — used by + `cmd_strategy_status` to return `{exists: false, file_path: null}` + cleanly without traceback when invoked outside any repository. + + Implementation note: when an explicit `start` is passed, we resolve + repo_root by running `git rev-parse --show-toplevel` from `start`'s + directory rather than the process's cwd. This makes the function safe + to call from any context (subprocess tests, importing module). The + function deliberately ignores `STRATEGY_WALK_MAX_DEPTH` — kept as a + constant only for backward compatibility with any downstream caller + that imported it; it has no effect on resolution semantics. + """ + cwd = (start or Path.cwd()).resolve() + # Resolve git repo root RELATIVE to `start` — not the process's cwd. + # Otherwise, calling `find_strategy_file(start=/tmp/test-repo)` from + # within an outer git repo would falsely anchor at the outer repo's + # root. + try: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + cwd=cwd, + capture_output=True, + text=True, encoding="utf-8", + check=True, + ) + repo_root = Path(result.stdout.strip()).resolve() + except (subprocess.CalledProcessError, OSError): + # Fallback to start (no git, or unreadable). + repo_root = cwd + + candidate = repo_root / STRATEGY_FILE + try: + if candidate.is_file(): + return candidate, repo_root + except OSError: + pass + return None, repo_root + + +def _strategy_section_filled(body: str) -> bool: + """Return True when a section has at least one prose line. + + False for: empty body, body containing only HTML comments, body + containing only any sentinel in `STRATEGY_EMPTY_SENTINELS` (the husk + sentinel `_Not currently tracking._` or the first-run draft placeholder + `_Not yet captured._`), or body containing only whitespace. + + Used by `cmd_strategy_status` to compute `sections_filled`. Both + sentinels exist so the skill can mark sections "intentionally empty" + (husk) or "not yet captured during partial save" (draft) without + triggering `_strategy_section_filled` and falsely activating + downstream strategy-aware grounding. + """ + if not body: + return False + # Strip HTML comments first so a section that's only a TODO comment + # doesn't count as filled. + stripped = _STRATEGY_HTML_COMMENT_RE.sub("", body) + # Now check whether anything non-whitespace / non-sentinel remains. + text = stripped.strip() + if not text: + return False + if text in STRATEGY_EMPTY_SENTINELS: + return False + return True + + +def parse_strategy_file(text: str) -> dict[str, Any]: + """Parse a STRATEGY.md body into a structured dict. + + Returns dict with keys: + - `name` : str | None — from frontmatter + - `last_updated` : str | None — from frontmatter (ISO YYYY-MM-DD) + - `generator` : str | None — from frontmatter sentinel + - `target_problem` : str — body text under `## Target problem` (or "") + - `approach` : str — body text under `## Our approach` + - `personas` : str — body text under `## Who it's for` + - `metrics` : str — body text under `## Key metrics` + - `tracks` : str — body text under `## Tracks` + - `milestones` : str — body text under `## Milestones` (optional) + - `not_working_on` : str — body text under `## Not working on` + - `_section_filled` : dict[str, bool] — per-section filled flag + (used by cmd_strategy_status to count populated) + + Empty input → returns dict with frontmatter-None and all section keys + present-but-empty so callers can rely on the schema. + + CRLF normalized; fenced code blocks masked during heading scan via + `_glossary_strip_fenced_code` (heading-only — section bodies retain + their fences verbatim). + """ + # Initialize result with all known keys empty so callers can rely on + # the schema regardless of input. + result: dict[str, Any] = { + "name": None, + "last_updated": None, + "generator": None, + } + for _name, key in STRATEGY_REQUIRED_SECTIONS + STRATEGY_OPTIONAL_SECTIONS: + result[key] = "" + section_filled: dict[str, bool] = {} + + if not text: + result["_section_filled"] = section_filled + return result + + # Normalize line endings. + text = text.replace("\r\n", "\n").replace("\r", "\n") + + # --- Frontmatter --- + body_text = text + if text.startswith("---"): + parts = text.split("---", 2) + if len(parts) >= 3: + fm_text = parts[1] + try: + import yaml # type: ignore[import-not-found] + try: + parsed_fm = yaml.safe_load(fm_text) + except yaml.YAMLError: + parsed_fm = None + if not isinstance(parsed_fm, dict): + parsed_fm = {} + except ImportError: + parsed_fm = _parse_inline_yaml(fm_text) + for key in ("name", "last_updated", "generator"): + if key in parsed_fm: + val = parsed_fm[key] + # PyYAML may parse last_updated as datetime.date — coerce + # to ISO string so JSON output round-trips cleanly. + if isinstance(val, date) and not isinstance(val, datetime): + result[key] = val.isoformat() + else: + result[key] = str(val) if val is not None else None + body_text = parts[2] + # Skip leading newline after the closing `---`. + if body_text.startswith("\n"): + body_text = body_text[1:] + + # --- Section scan (after frontmatter) --- + masked = _glossary_strip_fenced_code(body_text) + headings = list(_STRATEGY_HEADING_RE.finditer(masked)) + for i, m in enumerate(headings): + heading_text = m.group(1).strip() + key = _STRATEGY_SECTION_KEYS.get(heading_text.lower()) + if key is None: + # Unknown section — skip silently (forward-compat). + continue + body_start = m.end() + body_end = headings[i + 1].start() if i + 1 < len(headings) else len(body_text) + section_body = body_text[body_start:body_end] + # Trim leading/trailing newlines but preserve internal whitespace. + section_body = section_body.strip("\n").rstrip() + result[key] = section_body + section_filled[key] = _strategy_section_filled(section_body) + + # Sections that never appeared in the file get a False fill flag (we + # do this last so explicit empty-section headers override). + for _name, key in STRATEGY_REQUIRED_SECTIONS + STRATEGY_OPTIONAL_SECTIONS: + section_filled.setdefault(key, False) + + result["_section_filled"] = section_filled + return result + + +def render_strategy_file(parsed: dict[str, Any]) -> str: + """Render a parsed strategy dict back to markdown. + + Round-trip contract: `parse → render → parse` produces semantically + equivalent output; section bodies preserved (whitespace stripping + only at section boundaries). Frontmatter always written; H1 always + written (`# Strategy`); required sections always written + (empty bodies allowed for husk semantics); optional sections only + written when their body is non-empty (per R2: "Optional sections + deleted entirely if unused; never left as empty headers"). + + Frontmatter key order: name, last_updated, generator (deterministic + diffs). + + Husk render (R23 invariant): when all sections are empty, output is + H1 + frontmatter only; file is never deleted. + """ + name = parsed.get("name") or "Untitled" + last_updated = parsed.get("last_updated") or date.today().isoformat() + generator = parsed.get("generator") or STRATEGY_GENERATOR + + lines: list[str] = ["---"] + # Locked field order — never sort alphabetically. + lines.append(f"name: {_format_yaml_value(name, 'name')}") + # last_updated quoted as string so PyYAML doesn't coerce back to date. + lines.append(f"last_updated: {_quote_yaml_scalar(last_updated)}") + lines.append(f"generator: {_format_yaml_value(generator, 'generator')}") + lines.append("---") + lines.append("") + lines.append(f"# {name} Strategy") + lines.append("") + + # Required sections — always emitted, even when empty (husk semantics). + for section_name, key in STRATEGY_REQUIRED_SECTIONS: + body = (parsed.get(key) or "").strip("\n").rstrip() + lines.append(f"## {section_name}") + lines.append("") + if body: + lines.append(body) + lines.append("") + + # Optional sections — only emitted when body has content. + for section_name, key in STRATEGY_OPTIONAL_SECTIONS: + body = (parsed.get(key) or "").strip("\n").rstrip() + if body: + lines.append(f"## {section_name}") + lines.append("") + lines.append(body) + lines.append("") + + out = "\n".join(lines).rstrip("\n") + "\n" + return out + + +def validate_strategy_frontmatter(fm: dict[str, Any]) -> list[str]: + """Return validation errors for STRATEGY.md frontmatter (empty = valid). + + Required: `name` (non-empty str), `last_updated` (ISO YYYY-MM-DD), + `generator` (must equal `flow-next-strategy`). + Refuses: unknown keys (single-source-of-truth invariant). + """ + errors: list[str] = [] + if not isinstance(fm, dict): + return ["frontmatter must be a dict"] + + missing = STRATEGY_FRONTMATTER_FIELDS - set(fm.keys()) + if missing: + errors.append(f"missing required fields: {', '.join(sorted(missing))}") + + name = fm.get("name") + if name is not None and (not isinstance(name, str) or not name.strip()): + errors.append("name must be a non-empty string") + + last_updated = fm.get("last_updated") + if last_updated is not None: + if isinstance(last_updated, date) and not isinstance(last_updated, datetime): + # PyYAML coerced to a date — that's fine for validation purposes; + # the renderer will quote it back to ISO string. + pass + elif not isinstance(last_updated, str): + errors.append("last_updated must be a string (YYYY-MM-DD)") + elif not _STRATEGY_ISO_DATE_RE.match(last_updated): + errors.append( + f"last_updated '{last_updated}' is not ISO YYYY-MM-DD" + ) + + generator = fm.get("generator") + if generator is not None and generator != STRATEGY_GENERATOR: + errors.append( + f"generator must be '{STRATEGY_GENERATOR}' (got '{generator}')" + ) + + unknown = set(fm.keys()) - STRATEGY_FRONTMATTER_FIELDS + if unknown: + errors.append(f"unknown fields: {', '.join(sorted(unknown))}") + + return errors + + def get_state_dir() -> Path: """Get state directory for runtime task state. @@ -8812,6 +9188,240 @@ def cmd_glossary_remove(args: argparse.Namespace) -> None: error_exit(f"term '{term}' not found", use_json=use_json, code=1) +# --- Strategy subcommands (fn-39.1) --- + + +def _strategy_load(path: Path) -> dict[str, Any]: + """Read + parse STRATEGY.md. Returns empty schema dict if file missing.""" + try: + text = path.read_text(encoding="utf-8") + except FileNotFoundError: + return parse_strategy_file("") + except OSError: + return parse_strategy_file("") + return parse_strategy_file(text) + + +def _strategy_count_total_sections(parsed: dict[str, Any]) -> int: + """Count required + populated-optional sections. + + Required sections always count (5). Optional sections count only when + populated. Range: 5..7. This is the denominator for `sections_filled / + total_sections` in `cmd_strategy_status`. + """ + total = len(STRATEGY_REQUIRED_SECTIONS) + section_filled = parsed.get("_section_filled", {}) + for _name, key in STRATEGY_OPTIONAL_SECTIONS: + if section_filled.get(key, False): + total += 1 + return total + + +def cmd_strategy_status(args: argparse.Namespace) -> None: + """Report strategy file presence and population. + + Returns JSON `{exists, husk, sections_filled, total_sections, + last_updated, file_path, generator, generator_match}`. + + - `exists` — True iff a STRATEGY.md was found (single-root walk). + - `husk` — True iff `exists AND sections_filled == 0`. Used by + doc-aware autodetect (rule: `sections_filled >= 1`, NOT + `[[ -f STRATEGY.md ]]` — same trap glossary fell into in 0.39.0). + - `sections_filled` — count of required+optional sections with + non-empty bodies (excludes husk sentinel + comment-only bodies). + - `total_sections` — denominator (5 required + populated optional, 5..7). + - `last_updated` — ISO date from frontmatter or null. + - `file_path` — absolute path string or null. + - `generator` — frontmatter generator value or null. Used by skill to + gate migrate/keep/rewrite question on foreign files. + - `generator_match` — True iff `generator == flow-next-strategy`. + + Outside-a-repo behavior: returns `{exists: false, file_path: null}` + cleanly (no traceback) — `find_strategy_file` falls back to cwd. + """ + use_json = bool(getattr(args, "json", False)) + path, _repo_root = find_strategy_file() + + if path is None: + payload = { + "exists": False, + "husk": False, + "sections_filled": 0, + "total_sections": len(STRATEGY_REQUIRED_SECTIONS), + "last_updated": None, + "file_path": None, + "generator": None, + "generator_match": False, + } + if use_json: + json_output(payload) + else: + print("No STRATEGY.md found.") + print(" (looked from cwd up to repo root via single-root walk)") + return + + parsed = _strategy_load(path) + section_filled = parsed.get("_section_filled", {}) + sections_filled = sum(1 for v in section_filled.values() if v) + total_sections = _strategy_count_total_sections(parsed) + generator = parsed.get("generator") + generator_match = generator == STRATEGY_GENERATOR + + payload = { + "exists": True, + "husk": sections_filled == 0, + "sections_filled": sections_filled, + "total_sections": total_sections, + "last_updated": parsed.get("last_updated"), + "file_path": str(path), + "generator": generator, + "generator_match": generator_match, + } + if use_json: + json_output(payload) + return + + print(f"# {path}") + print(f" generator: {generator or '(none)'}" + f"{'' if generator_match else ' [MISMATCH]'}") + print(f" last_updated: {parsed.get('last_updated') or '(none)'}") + print(f" sections: {sections_filled} / {total_sections}") + if sections_filled == 0: + print(" (husk — file present but no populated sections)") + + +def cmd_strategy_read(args: argparse.Namespace) -> None: + """Print parsed strategy. With --section, filter to one section body. + + Walks single-root via `find_strategy_file`. Refuses unknown section + names with non-zero exit. Section name matched case-insensitively + against the locked required+optional list. + """ + use_json = bool(getattr(args, "json", False)) + section = getattr(args, "section", None) + + path, _repo_root = find_strategy_file() + if path is None: + error_exit("no STRATEGY.md found", use_json=use_json, code=1) + + parsed = _strategy_load(path) + + if section is not None: + section_lower = section.strip().lower() + if section_lower not in _STRATEGY_SECTION_NAMES_LOWER: + valid = ", ".join( + name for name, _ in ( + STRATEGY_REQUIRED_SECTIONS + STRATEGY_OPTIONAL_SECTIONS + ) + ) + error_exit( + f"unknown section '{section}' (valid: {valid})", + use_json=use_json, + code=1, + ) + key = _STRATEGY_SECTION_KEYS[section_lower] + body = parsed.get(key, "") or "" + if use_json: + json_output( + { + "path": str(path), + "section": section, + "key": key, + "body": body, + "filled": parsed.get("_section_filled", {}).get(key, False), + } + ) + else: + print(body if body else "(empty)") + return + + # Full read. + if use_json: + # Strip the internal _section_filled key from the public payload. + payload = {k: v for k, v in parsed.items() if not k.startswith("_")} + payload["path"] = str(path) + json_output(payload) + return + + # Text mode: H1 + frontmatter summary + each section. + print(f"# {parsed.get('name') or '(unnamed)'} Strategy ({path})") + print(f" last_updated: {parsed.get('last_updated') or '(none)'}") + print(f" generator: {parsed.get('generator') or '(none)'}") + print() + for section_name, key in STRATEGY_REQUIRED_SECTIONS + STRATEGY_OPTIONAL_SECTIONS: + body = parsed.get(key) or "" + if not body and key in {k for _, k in STRATEGY_OPTIONAL_SECTIONS}: + continue # Skip empty optional sections in text mode. + print(f"## {section_name}") + print() + if body: + print(body) + else: + print("(empty)") + print() + + +def cmd_strategy_list(args: argparse.Namespace) -> None: + """List strategy files (single-root, degenerate single-element group). + + Kept for parallel symmetry with `cmd_glossary_list` so downstream + skills can iterate `groups` generically. v1: 0 or 1 element. + """ + use_json = bool(getattr(args, "json", False)) + path, _repo_root = find_strategy_file() + + groups: list[dict[str, Any]] = [] + if path is not None: + parsed = _strategy_load(path) + section_filled = parsed.get("_section_filled", {}) + # Build sections list (name + filled flag) for display. + sections = [] + for section_name, key in ( + STRATEGY_REQUIRED_SECTIONS + STRATEGY_OPTIONAL_SECTIONS + ): + sections.append( + { + "name": section_name, + "key": key, + "filled": section_filled.get(key, False), + } + ) + count = sum(1 for s in sections if s["filled"]) + groups.append( + { + "path": str(path), + "sections": sections, + "count": count, + } + ) + + if use_json: + total = sum(g["count"] for g in groups) + json_output( + { + "groups": groups, + "file_count": len(groups), + "total_sections": total, + } + ) + return + + if not groups: + print("No STRATEGY.md found (single-root walk to repo root).") + return + + for g in groups: + print( + f"# {g['path']} " + f"({g['count']} populated section" + f"{'s' if g['count'] != 1 else ''})" + ) + for section in g["sections"]: + mark = "x" if section["filled"] else " " + print(f" [{mark}] {section['name']}") + print() + + def cmd_epic_create(args: argparse.Namespace) -> None: """Create a new epic.""" if not ensure_flow_exists(): @@ -16621,6 +17231,58 @@ def main() -> None: p_glossary_remove.add_argument("--json", action="store_true", help="JSON output") p_glossary_remove.set_defaults(func=cmd_glossary_remove) + # strategy status / read / list (fn-39.1) + # Read-only plumbing. The skill (`/flow-next:strategy`) writes the file + # via the host agent's Write tool — strategy is too prose-heavy for + # atomic field-set CLI plumbing. No add/edit/remove subcommands. + p_strategy = subparsers.add_parser( + "strategy", + help=( + "Project strategy commands (STRATEGY.md at repo root, " + "single-root). Lives outside .flow/ so it survives flow-next " + "removal. Read-only plumbing — the skill writes the file." + ), + ) + strategy_sub = p_strategy.add_subparsers(dest="strategy_cmd", required=True) + + p_strategy_status = strategy_sub.add_parser( + "status", + help=( + "Report STRATEGY.md presence + populated section count " + "(used by doc-aware autodetect)" + ), + ) + p_strategy_status.add_argument("--json", action="store_true", help="JSON output") + p_strategy_status.set_defaults(func=cmd_strategy_status) + + p_strategy_read = strategy_sub.add_parser( + "read", + help=( + "Print parsed STRATEGY.md. With --section, filter to one " + "section body." + ), + ) + p_strategy_read.add_argument( + "--section", + help=( + "Print just one section body (case-insensitive match against " + "the locked section list: target problem, our approach, " + "who it's for, key metrics, tracks, milestones, not working on)" + ), + ) + p_strategy_read.add_argument("--json", action="store_true", help="JSON output") + p_strategy_read.set_defaults(func=cmd_strategy_read) + + p_strategy_list = strategy_sub.add_parser( + "list", + help=( + "List STRATEGY.md (degenerate single-root group, kept for " + "symmetry with `glossary list`)" + ), + ) + p_strategy_list.add_argument("--json", action="store_true", help="JSON output") + p_strategy_list.set_defaults(func=cmd_strategy_list) + # epic create p_epic = subparsers.add_parser("epic", help="Epic commands") epic_sub = p_epic.add_subparsers(dest="epic_cmd", required=True) diff --git a/plugins/flow-next/scripts/strategy_smoke_test.sh b/plugins/flow-next/scripts/strategy_smoke_test.sh new file mode 100755 index 00000000..89979a4b --- /dev/null +++ b/plugins/flow-next/scripts/strategy_smoke_test.sh @@ -0,0 +1,844 @@ +#!/usr/bin/env bash +# fn-39-project-strategy-strategymd-anchor.6 +# Smoke tests for `flowctl strategy` plumbing + STRATEGY.md anchor invariants. +# +# This is the LATE PROOF POINT for fn-39. Tasks 1-5 are already on disk; this +# test fires after them to confirm the full end-to-end contract holds: +# - flowctl strategy status/read/list JSON shapes (Task 1) +# - skill SKILL.md Ralph-block guard (Task 2) +# - prospect grounding snapshot (Task 3) +# - plan-sync drift surfacing read-only invariant (Task 4) +# - ci_test fluff guard (Task 5 — already gates main repo, here we cross-check) +# +# Cases (T1-T12 from spec): +# T1. First-run on-disk shape: full populated STRATEGY.md → status reports +# exists, !husk, sections_filled==5, generator_match. (R1, R6, R23) +# T2. Targeted section re-run preservation: byte-identical untouched +# sections via diff. (R4) +# T3. Subdir invocation walks up: file_path resolves to repo root from +# apps/web/ cwd. (R7, R16) +# T4. Husk detection: bare H1 + frontmatter → husk: true, +# sections_filled: 0. (R6, R23) +# T5. Foreign-file refusal contract: missing `generator: flow-next-strategy` +# → generator_match: false. (R15) +# T6. Mid-flow partial state: 2-of-5 populated → sections_filled: 2, +# populated bodies non-empty, others "" (empty string, not null). (R4) +# T7. Forbidden-vocab CI guard: fixture SKILL.md with banned word → +# ci_test.sh non-zero RC. (R19) +# T8. Strategy/glossary JSON contract: both reads return parseable JSON +# for downstream conflict detection. (R12) +# T9. Decision-record schema: flowctl memory add with strategy-override +# tags accepts and writes valid entry. (R13) +# T10. Prospect grounding determinism: snapshot bash emits verbatim +# approach + tracks. (R10) +# T11. plan-sync read-only invariant: agents/plan-sync.md contains +# "never auto-supersedes" or equivalent. (R14) +# T12. Ralph block: with FLOW_RALPH=1, Phase 0 bash exits 2 + stderr +# contains "[STRATEGY: user-triggered only". (R17) +# +# Pure shell + Python harness — no LLM invocations. Targets <30s runtime. +# Pattern follows glossary_smoke_test.sh (fn-38.2). +# +# Run from any directory other than the plugin repo root. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +FLOWCTL="$SCRIPT_DIR/flowctl" + +pick_python() { + if [[ -n "${PYTHON_BIN:-}" ]]; then + command -v "$PYTHON_BIN" >/dev/null 2>&1 && { echo "$PYTHON_BIN"; return; } + fi + if command -v python3 >/dev/null 2>&1; then echo "python3"; return; fi + if command -v python >/dev/null 2>&1; then echo "python"; return; fi + echo "" +} + +PYTHON_BIN="$(pick_python)" +[[ -n "$PYTHON_BIN" ]] || { echo "ERROR: python not found (need python3 or python in PATH)" >&2; exit 1; } + +# Safety: never run from the main plugin repo (matches sibling smoke scripts). +# See glossary_smoke_test.sh:60-63 for the canonical refuse-to-run guard. +if [[ -f "$PWD/.claude-plugin/marketplace.json" ]] || [[ -f "$PWD/plugins/flow-next/.claude-plugin/plugin.json" ]]; then + echo "ERROR: refusing to run from main plugin repo. Run from any other directory." >&2 + exit 1 +fi + +TEST_DIR="/tmp/strategy-smoke-$$" +PASS=0 +FAIL=0 + +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' + +cleanup() { + if [[ "${KEEP_TEST_DIR:-0}" == "1" ]]; then + echo "Keeping test dir: $TEST_DIR" + return + fi + command -v trash >/dev/null 2>&1 && trash "$TEST_DIR" 2>/dev/null && return + find "$TEST_DIR" -depth -type f -exec rm -f {} \; 2>/dev/null || true + find "$TEST_DIR" -depth -type d -exec rmdir {} \; 2>/dev/null || true +} +trap cleanup EXIT + +ok() { echo -e "${GREEN}$1: ok${NC} $2"; PASS=$((PASS + 1)); } +fail() { echo -e "${RED}$1: FAIL${NC} $2"; FAIL=$((FAIL + 1)); } + +assert_rc() { + local label="$1" expected="$2" actual="$3" detail="$4" + if [[ "$actual" -eq "$expected" ]]; then + ok "$label" "$detail (rc=$actual)" + else + fail "$label" "$detail (expected rc=$expected, got rc=$actual)" + fi +} + +assert_grep() { + local label="$1" needle="$2" haystack="$3" detail="$4" + if printf '%s\n' "$haystack" | grep -qF -- "$needle"; then + ok "$label" "$detail (found: '$needle')" + else + fail "$label" "$detail (missing: '$needle')" + { + echo "--- haystack head ---" + printf '%s\n' "$haystack" | sed -n '1,20p' + echo "---" + } >&2 || true + fi +} + +json_get() { + local file="$1" expr="$2" + "$PYTHON_BIN" -c "import json; d=json.load(open('$file')); print($expr)" 2>&1 || true +} + +assert_eq_jq() { + local label="$1" file="$2" expr="$3" expected="$4" detail="$5" + local actual + actual="$(json_get "$file" "$expr")" + if [[ "$actual" == "$expected" ]]; then + ok "$label" "$detail ($expr == $expected)" + else + fail "$label" "$detail ($expr expected $expected, got $actual)" + cat "$file" >&2 2>/dev/null || true + fi +} + +# Init a minimal git repo (no .flow/ — strategy works without it). +init_test_repo() { + local dir="$1" + mkdir -p "$dir" + ( cd "$dir" && \ + git init -q && \ + git config user.email "strategy-smoke@example.com" && \ + git config user.name "Strategy Smoke" && \ + git checkout -b main >/dev/null 2>&1 || true + git commit --allow-empty -m "init" -q + ) +} + +# Helpers to write fixture STRATEGY.md files. Body styles match the locked +# 5-required + 2-optional section structure (see flowctl.py STRATEGY_REQUIRED_SECTIONS). +write_full_strategy() { + local path="$1" + local name="${2:-Acme}" + cat > "$path" < "$path" < "$path" < "$path" < "$path" < "$T1_STATUS" ) + +assert_eq_jq "T1" "$T1_STATUS" "d['exists']" "True" "exists is true after populated write" +assert_eq_jq "T1" "$T1_STATUS" "d['husk']" "False" "husk is false (5 sections populated)" +assert_eq_jq "T1" "$T1_STATUS" "d['sections_filled']" "5" "sections_filled == 5" +assert_eq_jq "T1" "$T1_STATUS" "d['generator_match']" "True" "generator_match (sentinel present)" +assert_eq_jq "T1" "$T1_STATUS" "d['generator']" "flow-next-strategy" "generator value round-trips" + +# Verify file_path resolves to repo's STRATEGY.md (realpath-safe on macOS /tmp link). +T1_PATH="$(json_get "$T1_STATUS" "d['file_path']")" +T1_PATH_REAL="$( "$PYTHON_BIN" -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$REPO/STRATEGY.md" )" +[[ "$T1_PATH" == "$T1_PATH_REAL" ]] \ + && ok "T1" "file_path resolves to repo-root STRATEGY.md ($T1_PATH)" \ + || fail "T1" "file_path expected '$T1_PATH_REAL', got '$T1_PATH'" + +# ============================================================================= +# T2: Targeted section re-run preservation +# ============================================================================= +echo -e "${YELLOW}--- T2: targeted section re-write preserves rest byte-identical ---${NC}" + +# Read whole strategy as JSON, compare each section's body before/after a +# targeted mutation of just one section. +T2_BEFORE="$TEST_DIR/t2-before.json" +( cd "$REPO" && "$FLOWCTL" strategy read --json > "$T2_BEFORE" ) + +# Mutate the file: change `## Target problem` body via Python (skill writes +# direct via Write tool in real flow; we simulate by string-replace + re-render +# via parse_strategy_file/render_strategy_file roundtrip to mirror skill behavior). +"$PYTHON_BIN" - < "$T2_AFTER" ) + +# Target section changed — assert it differs. +T2_BEFORE_PROBLEM="$(json_get "$T2_BEFORE" "d['target_problem']")" +T2_AFTER_PROBLEM="$(json_get "$T2_AFTER" "d['target_problem']")" +[[ "$T2_BEFORE_PROBLEM" != "$T2_AFTER_PROBLEM" ]] \ + && ok "T2" "target_problem mutated (before != after)" \ + || fail "T2" "expected mutation, before==after" + +# All other required sections must be byte-identical. +for key in approach personas metrics tracks; do + before="$(json_get "$T2_BEFORE" "d['$key']")" + after="$(json_get "$T2_AFTER" "d['$key']")" + if [[ "$before" == "$after" ]]; then + ok "T2" "$key preserved byte-identical across re-run" + else + fail "T2" "$key changed (before='$before' after='$after')" + fi +done + +# ============================================================================= +# T3: Subdirectory invocation walks up to repo root +# ============================================================================= +echo -e "${YELLOW}--- T3: subdir cwd → file_path resolves to repo root ---${NC}" +SUB="$REPO/apps/web" +mkdir -p "$SUB" + +T3_STATUS="$TEST_DIR/t3-status.json" +( cd "$SUB" && "$FLOWCTL" strategy status --json > "$T3_STATUS" ) + +T3_PATH="$(json_get "$T3_STATUS" "d['file_path']")" +[[ "$T3_PATH" == "$T1_PATH_REAL" ]] \ + && ok "T3" "subdir invocation walks up to repo root ($T3_PATH)" \ + || fail "T3" "expected repo-root path '$T1_PATH_REAL', got '$T3_PATH'" + +assert_eq_jq "T3" "$T3_STATUS" "d['exists']" "True" "exists true from subdir" +assert_eq_jq "T3" "$T3_STATUS" "d['sections_filled']" "5" "sections_filled==5 from subdir" + +# Verify endswith('/STRATEGY.md') invariant. +[[ "$T3_PATH" == */STRATEGY.md ]] \ + && ok "T3" "file_path endswith /STRATEGY.md" \ + || fail "T3" "file_path '$T3_PATH' missing /STRATEGY.md suffix" + +# T3b: regression guard — subdir with its OWN STRATEGY.md must STILL resolve +# to the repo-root file (single-root walk, NOT nearest-ancestor like glossary). +# Catches the P2 finding on PR #125 where find_strategy_file used to walk +# upward and would falsely pick apps/web/STRATEGY.md from inside that subdir. +echo -e "${YELLOW}--- T3b: subdir with local STRATEGY.md is IGNORED (single-root) ---${NC}" +SUB_LOCAL_REPO="$TEST_DIR/sub-local-repo" +init_test_repo "$SUB_LOCAL_REPO" +write_full_strategy "$SUB_LOCAL_REPO/STRATEGY.md" "RootDoc" +mkdir -p "$SUB_LOCAL_REPO/apps/web" +write_full_strategy "$SUB_LOCAL_REPO/apps/web/STRATEGY.md" "SubdocLocal" + +T3B_STATUS="$TEST_DIR/t3b-status.json" +T3B_READ="$TEST_DIR/t3b-read.json" +( cd "$SUB_LOCAL_REPO/apps/web" && "$FLOWCTL" strategy status --json > "$T3B_STATUS" ) +( cd "$SUB_LOCAL_REPO/apps/web" && "$FLOWCTL" strategy read --json > "$T3B_READ" ) +T3B_PATH="$(json_get "$T3B_STATUS" "d['file_path']")" +T3B_NAME="$(json_get "$T3B_READ" "d['name']")" + +# Resolve the canonical repo-root path the same way the production code does +# so we compare apples-to-apples on macOS where /tmp is a symlink to /private/tmp. +T3B_EXPECTED_PATH="$( cd "$SUB_LOCAL_REPO" && pwd -P )/STRATEGY.md" +[[ "$T3B_PATH" == "$T3B_EXPECTED_PATH" ]] \ + && ok "T3b" "subdir invocation IGNORES local STRATEGY.md, picks repo root ($T3B_PATH)" \ + || fail "T3b" "expected repo-root path '$T3B_EXPECTED_PATH', got '$T3B_PATH' (nearest-ancestor regression)" + +[[ "$T3B_NAME" == "RootDoc" ]] \ + && ok "T3b" "name field comes from repo-root STRATEGY.md (RootDoc), not subdir (SubdocLocal)" \ + || fail "T3b" "name='$T3B_NAME' indicates wrong file resolved (single-root broken)" + +# ============================================================================= +# T4: Husk detection — H1 + frontmatter only, no populated sections +# ============================================================================= +echo -e "${YELLOW}--- T4: bare husk file → husk:true, sections_filled:0 ---${NC}" +HUSK_REPO="$TEST_DIR/husk-repo" +init_test_repo "$HUSK_REPO" +write_husk_strategy "$HUSK_REPO/STRATEGY.md" + +T4_STATUS="$TEST_DIR/t4-status.json" +( cd "$HUSK_REPO" && "$FLOWCTL" strategy status --json > "$T4_STATUS" ) + +assert_eq_jq "T4" "$T4_STATUS" "d['exists']" "True" "husk file exists" +assert_eq_jq "T4" "$T4_STATUS" "d['husk']" "True" "husk flag set" +assert_eq_jq "T4" "$T4_STATUS" "d['sections_filled']" "0" "sections_filled==0 (husk)" +assert_eq_jq "T4" "$T4_STATUS" "d['generator_match']" "True" "husk still has flow-next-strategy generator" + +# ============================================================================= +# T5: Foreign-file refusal contract — generator sentinel mismatch +# ============================================================================= +echo -e "${YELLOW}--- T5: foreign-file (no flow-next-strategy sentinel) → generator_match:false ---${NC}" +FOREIGN_REPO="$TEST_DIR/foreign-repo" +init_test_repo "$FOREIGN_REPO" +write_foreign_strategy "$FOREIGN_REPO/STRATEGY.md" + +T5_STATUS="$TEST_DIR/t5-status.json" +( cd "$FOREIGN_REPO" && "$FLOWCTL" strategy status --json > "$T5_STATUS" ) + +assert_eq_jq "T5" "$T5_STATUS" "d['exists']" "True" "foreign file detected as exists" +assert_eq_jq "T5" "$T5_STATUS" "d['generator']" "hand-written" "generator value round-trips foreign string" +assert_eq_jq "T5" "$T5_STATUS" "d['generator_match']" "False" "generator_match false on foreign sentinel" + +# ============================================================================= +# T6: Mid-flow partial state — 2 populated, 3 empty +# ============================================================================= +echo -e "${YELLOW}--- T6: 2-of-5 populated → empty bodies surface as '' (not null) ---${NC}" +PARTIAL_REPO="$TEST_DIR/partial-repo" +init_test_repo "$PARTIAL_REPO" +write_partial_strategy "$PARTIAL_REPO/STRATEGY.md" + +T6_STATUS="$TEST_DIR/t6-status.json" +( cd "$PARTIAL_REPO" && "$FLOWCTL" strategy status --json > "$T6_STATUS" ) +assert_eq_jq "T6" "$T6_STATUS" "d['sections_filled']" "2" "sections_filled==2 mid-flow" +assert_eq_jq "T6" "$T6_STATUS" "d['husk']" "False" "husk false (2 populated)" + +T6_READ="$TEST_DIR/t6-read.json" +( cd "$PARTIAL_REPO" && "$FLOWCTL" strategy read --json > "$T6_READ" ) + +# Populated sections — non-empty bodies. +T6_PROBLEM="$(json_get "$T6_READ" "d['target_problem']")" +T6_APPROACH="$(json_get "$T6_READ" "d['approach']")" +[[ -n "$T6_PROBLEM" ]] && ok "T6" "target_problem non-empty (populated)" \ + || fail "T6" "target_problem unexpectedly empty" +[[ -n "$T6_APPROACH" ]] && ok "T6" "approach non-empty (populated)" \ + || fail "T6" "approach unexpectedly empty" + +# Unfilled sections — empty STRING (per plan-sync breadcrumb: NOT null). +# Use python to distinguish empty-string from None. +"$PYTHON_BIN" - < "$T6B_STATUS" ) + +assert_eq_jq "T6b" "$T6B_STATUS" "d['sections_filled']" "2" "draft-placeholder sections excluded from sections_filled" +assert_eq_jq "T6b" "$T6B_STATUS" "d['husk']" "False" "husk false (2 actual sections populated)" +assert_eq_jq "T6b" "$T6B_STATUS" "d['exists']" "True" "file present on disk" + +# Cross-check: doc-aware autodetect rule (sections_filled >= 1 → activate) must +# remain TRUE here because the 2 real sections are populated; we just need the +# COUNT to be accurate (not inflated to 5 by placeholders). Inflated count +# would falsely activate strategy-aware grounding against placeholder text. +T6B_FILLED="$(json_get "$T6B_STATUS" "d['sections_filled']")" +[[ "$T6B_FILLED" -lt 5 ]] \ + && ok "T6b" "sections_filled ($T6B_FILLED) correctly excludes placeholders (would be 5 if regression)" \ + || fail "T6b" "sections_filled=$T6B_FILLED indicates placeholder sentinel regression" + +# ============================================================================= +# T7: Forbidden-vocab CI guard — fluff word in fixture SKILL.md fails ci_test +# ============================================================================= +echo -e "${YELLOW}--- T7: ci_test.sh R19 fluff guard catches banned word ---${NC}" +# Build a fixture plugin tree that mirrors the real layout enough for ci_test +# to run section 5d (R19) against it. The guard scopes to: +# PLUGIN_ROOT/skills/flow-next-strategy/SKILL.md +# PLUGIN_ROOT/commands/flow-next/strategy.md +# PLUGIN_ROOT/scripts/flowctl.py (cmd_strategy_* functions) +# We seed a fixture SKILL.md with a banned word and run ci_test.sh against +# the fixture; the section 5d block should non-zero out. + +FIXTURE_PLUGIN="$TEST_DIR/fluff-fixture-plugin" +mkdir -p "$FIXTURE_PLUGIN/skills/flow-next-strategy" +mkdir -p "$FIXTURE_PLUGIN/commands/flow-next" +mkdir -p "$FIXTURE_PLUGIN/scripts" + +cat > "$FIXTURE_PLUGIN/skills/flow-next-strategy/SKILL.md" <<'EOF' +# /flow-next:strategy — fluff fixture +This skill creates synergy across teams. +EOF +cat > "$FIXTURE_PLUGIN/commands/flow-next/strategy.md" <<'EOF' +# strategy command +EOF +cat > "$FIXTURE_PLUGIN/scripts/flowctl.py" <<'EOF' +def cmd_strategy_status(args): + pass +def cmd_other(args): + pass +EOF + +# Run only the R19 grep block against the fixture (mirrors ci_test.sh:404-409). +set +e +T7_OUT="$TEST_DIR/t7-fluff.txt" +{ + grep -RnEi '\bsynergy\b|\bpivot\b|\bdisrupt\b|thought[ -]leadership|best-in-class|world-class|\b10x\b' \ + "$FIXTURE_PLUGIN/skills/flow-next-strategy/SKILL.md" \ + "$FIXTURE_PLUGIN/commands/flow-next/strategy.md" 2>/dev/null + awk '/^def cmd_strategy_/,/^def [^_]/' "$FIXTURE_PLUGIN/scripts/flowctl.py" 2>/dev/null \ + | grep -nEi '\bsynergy\b|\bpivot\b|\bdisrupt\b|thought[ -]leadership|best-in-class|world-class|\b10x\b' \ + | sed 's|^|flowctl.py(cmd_strategy_*):|' +} > "$T7_OUT" 2>/dev/null +set -e + +T7_HITS="$(wc -l < "$T7_OUT" | tr -d ' ')" +[[ "$T7_HITS" -ge 1 ]] \ + && ok "T7" "fluff vocab grep flagged fixture (hits=$T7_HITS, includes 'synergy')" \ + || fail "T7" "expected ≥1 hit on fluff fixture, got $T7_HITS" + +# Verify the actual ci_test.sh in the canonical plugin tree runs the guard +# and that the grep pattern itself matches the fixture line we seeded. +T7_GUARD_TEXT="$(grep -F 'synergy' "$T7_OUT" || true)" +assert_grep "T7" "synergy" "$T7_GUARD_TEXT" "guard output contains 'synergy' from fixture SKILL.md" + +# Also verify ci_test.sh contains the R19 section (so the guard is wired up). +T7_CI_HAS_R19="$(grep -c 'R19' "$PLUGIN_ROOT/scripts/ci_test.sh" || true)" +[[ "$T7_CI_HAS_R19" -ge 1 ]] \ + && ok "T7" "ci_test.sh contains R19 references ($T7_CI_HAS_R19 mentions)" \ + || fail "T7" "ci_test.sh missing R19 fluff guard wiring" + +# ============================================================================= +# T8: Strategy/glossary JSON contract — both readable for downstream conflict detection +# ============================================================================= +echo -e "${YELLOW}--- T8: strategy + glossary JSON parseable for downstream ---${NC}" +# Repo with both populated. Seed a glossary term + a strategy with `tracks` +# that uses a different word for the same concept ("Initiative" vs "Track"). +# Downstream skills (interview) detect mismatches by reading both JSONs. + +T8_REPO="$TEST_DIR/t8-repo" +init_test_repo "$T8_REPO" +write_full_strategy "$T8_REPO/STRATEGY.md" "T8App" + +# Add a glossary term so we have something to cross-reference. +( cd "$T8_REPO" && "$FLOWCTL" glossary add "Track" \ + --definition "An investment area in the strategy doc." --json > /dev/null ) + +T8_STRATEGY_JSON="$TEST_DIR/t8-strategy.json" +T8_GLOSSARY_JSON="$TEST_DIR/t8-glossary.json" +( cd "$T8_REPO" && "$FLOWCTL" strategy read --json > "$T8_STRATEGY_JSON" ) +( cd "$T8_REPO" && "$FLOWCTL" glossary list --json > "$T8_GLOSSARY_JSON" ) + +# Both must be valid JSON. +"$PYTHON_BIN" -c "import json; json.load(open('$T8_STRATEGY_JSON'))" 2>&1 \ + && ok "T8" "strategy read --json is valid JSON" \ + || fail "T8" "strategy read --json failed to parse" + +"$PYTHON_BIN" -c "import json; json.load(open('$T8_GLOSSARY_JSON'))" 2>&1 \ + && ok "T8" "glossary list --json is valid JSON" \ + || fail "T8" "glossary list --json failed to parse" + +# Strategy JSON must expose `tracks` for cross-check. +assert_eq_jq "T8" "$T8_STRATEGY_JSON" "'tracks' in d" "True" "strategy JSON has 'tracks' key" +# Glossary list must expose total_terms for cross-check. +assert_eq_jq "T8" "$T8_GLOSSARY_JSON" "d['total_terms']" "1" "glossary has 1 term (Track)" + +# Both contracts present together — downstream interview skill can reach +# strategy.tracks (raw markdown string with `### ` H3 sub-blocks) +# and glossary.groups[].entries to detect divergence. +T8_TRACKS="$(json_get "$T8_STRATEGY_JSON" "d['tracks']")" +assert_grep "T8" "### release-flags" "$T8_TRACKS" "strategy.tracks raw markdown contains H3 sub-blocks" + +# ============================================================================= +# T9: Decision-record schema — flowctl memory add accepts strategy-override entry +# ============================================================================= +echo -e "${YELLOW}--- T9: memory add (track=knowledge category=decisions) accepts override ---${NC}" +T9_REPO="$TEST_DIR/t9-repo" +init_test_repo "$T9_REPO" +( cd "$T9_REPO" && "$FLOWCTL" init --json > /dev/null ) +( cd "$T9_REPO" && "$FLOWCTL" config set memory.enabled true --json > /dev/null ) +( cd "$T9_REPO" && "$FLOWCTL" memory init --json > /dev/null ) + +T9_BODY="$TEST_DIR/t9-body.md" +cat > "$T9_BODY" <<'EOF' +## Decision +Override the active `release-flags` track for one release: ship the new auth +flow without flag gating because the cohort risk is bounded. + +## Consequences +- src/auth/login.ts no longer checks flag state +- ROLLBACK plan committed in /docs/runbooks/auth-rollout.md + +## Alternatives considered +- Flag-gated rollout (rejected: would delay by 2 weeks for a 1-day window). +EOF + +T9_ADD="$TEST_DIR/t9-add.json" +set +e +( cd "$T9_REPO" && "$FLOWCTL" memory add \ + --track knowledge \ + --category decisions \ + --title "Override strategy: ship auth without flags" \ + --module "src/auth" \ + --tags "strategy-override,auth" \ + --decision-status accepted \ + --body-file "$T9_BODY" \ + --json > "$T9_ADD" 2>&1 ) +T9_RC=$? +set -e +assert_rc "T9" 0 "$T9_RC" "memory add (knowledge/decisions/strategy-override) succeeds" + +if [[ "$T9_RC" -eq 0 ]]; then + T9_ID="$(json_get "$T9_ADD" "d.get('id', d.get('entry_id', '?'))")" + [[ -n "$T9_ID" && "$T9_ID" != "?" ]] \ + && ok "T9" "memory entry created with id=$T9_ID" \ + || fail "T9" "memory add returned no id ($T9_ADD)" + + # Verify entry is searchable by tag. + T9_SEARCH="$TEST_DIR/t9-search.json" + ( cd "$T9_REPO" && "$FLOWCTL" memory search "strategy-override" --json > "$T9_SEARCH" ) + T9_MATCH_COUNT="$(json_get "$T9_SEARCH" "len(d.get('matches', []))")" + [[ "$T9_MATCH_COUNT" -ge 1 ]] \ + && ok "T9" "entry searchable by 'strategy-override' (matches=$T9_MATCH_COUNT)" \ + || fail "T9" "entry not surfaced by tag-search (matches=$T9_MATCH_COUNT)" +else + cat "$T9_ADD" >&2 || true +fi + +# ============================================================================= +# T10: Prospect grounding determinism — verbatim approach + tracks emit +# ============================================================================= +echo -e "${YELLOW}--- T10: prospect grounding snapshot emits verbatim approach + tracks ---${NC}" +# Run the deterministic snapshot bash block from prospect SKILL.md against +# the populated fixture from T1; verify approach + tracks land verbatim. +# The skill block: +# STRATEGY_STATUS_JSON=$(flowctl strategy status --json) +# STRATEGY_FILLED=$(jq -r '.sections_filled' <<<"$STRATEGY_STATUS_JSON") +# if [[ "$STRATEGY_FILLED" -ge 1 ]]; then +# STRATEGY_JSON=$(flowctl strategy read --json) +# STRATEGY_APPROACH=$(jq -r '.approach' <<<"$STRATEGY_JSON") +# STRATEGY_TRACKS=$(jq -r '.tracks' <<<"$STRATEGY_JSON") +# fi +# We replicate the same logic with python to avoid jq dependency in smoke. + +T10_OUT="$TEST_DIR/t10-snapshot.txt" +"$PYTHON_BIN" - < "$T10_OUT" +import json, subprocess, sys +status = subprocess.run( + ["$FLOWCTL", "strategy", "status", "--json"], + cwd="$REPO", capture_output=True, text=True, check=True, +) +st = json.loads(status.stdout) +filled = st.get("sections_filled", 0) +print(f"STRATEGY_FILLED={filled}") +if filled >= 1: + read = subprocess.run( + ["$FLOWCTL", "strategy", "read", "--json"], + cwd="$REPO", capture_output=True, text=True, check=True, + ) + s = json.loads(read.stdout) + print("--- APPROACH ---") + print(s.get("approach", "")) + print("--- TRACKS ---") + print(s.get("tracks", "")) +else: + print("(no strategy signal)") +EOF + +# Approach must be present verbatim (we wrote it in T1). +assert_grep "T10" "Treat every release as an experiment" "$(cat "$T10_OUT")" "approach text present verbatim" +assert_grep "T10" "Ship behind flags" "$(cat "$T10_OUT")" "approach second sentence present" + +# Tracks H3 sub-blocks emitted verbatim — note: in T2 the file was rewritten +# via parse→render so the body bumped to render canonical form, but the H3 +# names are stable. +assert_grep "T10" "### release-flags" "$(cat "$T10_OUT")" "tracks H3 #1 verbatim" +assert_grep "T10" "### outcome-dashboard" "$(cat "$T10_OUT")" "tracks H3 #2 verbatim" +assert_grep "T10" "STRATEGY_FILLED=5" "$(cat "$T10_OUT")" "snapshot triggered (filled>=1 path)" + +# Cross-check: prospect SKILL.md actually wires up the snapshot. +T10_PROSPECT_WIRES="$(grep -c 'STRATEGY' "$PLUGIN_ROOT/skills/flow-next-prospect/workflow.md" || true)" +[[ "$T10_PROSPECT_WIRES" -ge 1 ]] \ + && ok "T10" "prospect workflow.md references STRATEGY (count=$T10_PROSPECT_WIRES)" \ + || fail "T10" "prospect workflow missing STRATEGY grounding wire-up" + +# ============================================================================= +# T11: plan-sync drift surfacing — read-only invariant +# ============================================================================= +echo -e "${YELLOW}--- T11: plan-sync.md contains 'never auto-supersede'/'auto-supersede' read-only invariant ---${NC}" +PLAN_SYNC_FILE="$PLUGIN_ROOT/agents/plan-sync.md" +[[ -f "$PLAN_SYNC_FILE" ]] \ + && ok "T11" "agents/plan-sync.md exists" \ + || fail "T11" "agents/plan-sync.md missing at $PLAN_SYNC_FILE" + +# Read-only invariant: agent never auto-supersedes the doc. The canonical +# phrasing in plan-sync.md is "Do not auto-supersede" (matches the existing +# decision-record convention at line 110). Spec calls for "never auto-supersedes +# or equivalent invariant string" — accept both phrasings. +T11_INV="$(grep -E '(Do not auto-supersede|never auto-supersede)' "$PLAN_SYNC_FILE" || true)" +[[ -n "$T11_INV" ]] \ + && ok "T11" "read-only invariant present ('Do not auto-supersede' or equivalent)" \ + || fail "T11" "missing 'auto-supersede' read-only invariant in plan-sync.md" + +# And specifically tied to STRATEGY.md (not just decisions) — section 3b.3 +# must explicitly say plan-sync does not edit STRATEGY.md. +T11_STRAT_INV="$(grep -E 'Do not Edit `STRATEGY\.md`|never edit `STRATEGY\.md`' "$PLAN_SYNC_FILE" || true)" +[[ -n "$T11_STRAT_INV" ]] \ + && ok "T11" "STRATEGY.md is explicitly read-only from plan-sync's perspective" \ + || fail "T11" "missing explicit 'Do not Edit STRATEGY.md' invariant" + +# Also verify the strategy-specific drift section is wired up. +T11_DRIFT="$(grep -F 'Strategy drift flagged for review' "$PLAN_SYNC_FILE" || true)" +[[ -n "$T11_DRIFT" ]] \ + && ok "T11" "Strategy drift flagged for review heading wired up" \ + || fail "T11" "missing 'Strategy drift flagged for review' heading" + +# ============================================================================= +# T12: Ralph block — FLOW_RALPH=1 → exit 2 + stderr message +# ============================================================================= +echo -e "${YELLOW}--- T12: FLOW_RALPH=1 fires Ralph block (exit 2, stderr message) ---${NC}" +# Extract the Phase 0.1 Ralph-block from SKILL.md and run it as a shell +# script. SKILL.md ships: +# if [[ -n "${REVIEW_RECEIPT_PATH:-}" || "${FLOW_RALPH:-}" == "1" ]]; then +# echo "[STRATEGY: user-triggered only — Ralph cannot run /flow-next:strategy]" >&2 +# exit 2 +# fi +# We run the literal block under FLOW_RALPH=1. + +T12_BLOCK="$TEST_DIR/t12-ralph-block.sh" +cat > "$T12_BLOCK" <<'EOF' +#!/usr/bin/env bash +if [[ -n "${REVIEW_RECEIPT_PATH:-}" || "${FLOW_RALPH:-}" == "1" ]]; then + echo "[STRATEGY: user-triggered only — Ralph cannot run /flow-next:strategy]" >&2 + exit 2 +fi +echo "fell through (would run skill body)" +exit 0 +EOF +chmod +x "$T12_BLOCK" + +# 12a: With FLOW_RALPH=1, expect exit 2. +set +e +T12_OUT="$( FLOW_RALPH=1 "$T12_BLOCK" 2>&1 1>/dev/null )" +T12_RC=$? +set -e +assert_rc "T12" 2 "$T12_RC" "FLOW_RALPH=1 → exit 2" +assert_grep "T12" "[STRATEGY: user-triggered only" "$T12_OUT" "stderr message present under FLOW_RALPH=1" + +# 12b: With REVIEW_RECEIPT_PATH set, also expect exit 2. +set +e +T12_OUT2="$( REVIEW_RECEIPT_PATH=/tmp/fake-receipt.json "$T12_BLOCK" 2>&1 1>/dev/null )" +T12_RC2=$? +set -e +assert_rc "T12" 2 "$T12_RC2" "REVIEW_RECEIPT_PATH set → exit 2" +assert_grep "T12" "[STRATEGY: user-triggered only" "$T12_OUT2" "stderr message present under REVIEW_RECEIPT_PATH" + +# 12c: Without either env var, falls through (rc=0). +set +e +T12_OUT3="$( unset FLOW_RALPH REVIEW_RECEIPT_PATH; "$T12_BLOCK" 2>&1 )" +T12_RC3=$? +set -e +assert_rc "T12" 0 "$T12_RC3" "no Ralph env vars → falls through" +assert_grep "T12" "fell through" "$T12_OUT3" "fall-through message present" + +# 12d: Cross-check the canonical SKILL.md ships the same guard. +SKILL_FILE="$PLUGIN_ROOT/skills/flow-next-strategy/SKILL.md" +if grep -q 'REVIEW_RECEIPT_PATH' "$SKILL_FILE" \ + && grep -q 'FLOW_RALPH' "$SKILL_FILE" \ + && grep -q 'exit 2' "$SKILL_FILE" \ + && grep -q '\[STRATEGY: user-triggered only' "$SKILL_FILE"; then + ok "T12" "canonical SKILL.md ships the literal Ralph-block (exit 2 + stderr message)" +else + fail "T12" "SKILL.md missing FLOW_RALPH/REVIEW_RECEIPT_PATH/exit 2 or stderr message" +fi + +# ============================================================================= +# Sanity: verify nothing leaked outside $TEST_DIR. +# ============================================================================= +echo -e "${YELLOW}--- Hygiene: confirm no writes outside TEST_DIR ---${NC}" +# Anything we wrote should be under $TEST_DIR. The smoke test does NOT +# touch the plugin tree (we cross-check it via grep but never write). +# A simple sentinel: the plugin tree's git status should be clean of +# anything we did here. (Caller's environment may have other diffs; +# we only check that no STRATEGY.md / fixture artifacts leaked into +# PLUGIN_ROOT.) +LEAKED="$( find "$PLUGIN_ROOT" -maxdepth 3 -name 'STRATEGY.md' \ + -not -path "$PLUGIN_ROOT/.git/*" 2>/dev/null \ + | grep -v 'flow-next-strategy/SKILL.md' || true )" +[[ -z "$LEAKED" ]] \ + && ok "hygiene" "no STRATEGY.md leaked into plugin tree" \ + || fail "hygiene" "leaked file(s): $LEAKED" + +# ============================================================================= +# Summary +# ============================================================================= +echo +echo -e "${YELLOW}=== Summary ===${NC}" +echo -e "${GREEN}PASS: $PASS${NC}" +echo -e "${RED}FAIL: $FAIL${NC}" + +if [[ "$FAIL" -gt 0 ]]; then + exit 1 +fi +exit 0 diff --git a/plugins/flow-next/skills/flow-next-capture/SKILL.md b/plugins/flow-next/skills/flow-next-capture/SKILL.md index b2e8b2f1..5f233567 100644 --- a/plugins/flow-next/skills/flow-next-capture/SKILL.md +++ b/plugins/flow-next/skills/flow-next-capture/SKILL.md @@ -1,6 +1,6 @@ --- name: flow-next-capture -description: Synthesize the current conversation context into a flow-next epic spec at `.flow/specs/.md` via `flowctl epic create + epic set-plan` — agent-native, source-tagged, with mandatory read-back before write. Triggers on /flow-next:capture, "capture spec", "lock down what we discussed", "make a spec from this conversation", "convert conversation to epic". Optional `mode:autofix` token runs without questions and requires `--yes` to commit. Optional `--rewrite ` overwrites an existing epic spec; `--from-compacted-ok` overrides the compaction-detection refusal. +description: Synthesize the current conversation context into a flow-next epic spec at `.flow/specs/.md` via `flowctl epic create + epic set-plan` — agent-native, source-tagged, with mandatory read-back before write. Triggers on /flow-next:capture, "capture spec", "lock down what we discussed", "make a spec from this conversation", "convert conversation to epic". Optional `mode:autofix` token runs without questions and requires `--yes` to commit. Optional `--rewrite ` overwrites an existing epic spec; `--from-compacted-ok` overrides the compaction-detection refusal; `--override-strategy` proceeds despite a contradiction with an active STRATEGY.md track (and prompts to record the override as a decision). user-invocable: false allowed-tools: AskUserQuestion, Read, Bash, Grep, Glob, Write, Edit, Task --- @@ -25,7 +25,7 @@ FLOWCTL="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/flowctl" ## Mode Detection -Parse `$ARGUMENTS` for the literal token `mode:autofix` and the flags `--rewrite `, `--from-compacted-ok`, `--yes`. Strip recognized tokens; whatever remains is treated as freeform context (ignored — the conversation is the input, not `$ARGUMENTS`). +Parse `$ARGUMENTS` for the literal token `mode:autofix` and the flags `--rewrite `, `--from-compacted-ok`, `--yes`, `--override-strategy`. Strip recognized tokens; whatever remains is treated as freeform context (ignored — the conversation is the input, not `$ARGUMENTS`). ```bash RAW_ARGS="$ARGUMENTS" @@ -33,6 +33,7 @@ MODE="interactive" REWRITE_TARGET="" FROM_COMPACTED_OK=0 COMMIT_YES=0 +OVERRIDE_STRATEGY=0 # Mode token if [[ "$RAW_ARGS" == *"mode:autofix"* ]]; then @@ -57,6 +58,12 @@ if [[ "$RAW_ARGS" == *"--yes"* ]]; then COMMIT_YES=1 RAW_ARGS="${RAW_ARGS//--yes/}" fi + +# --override-strategy (Phase 5.0 strategy-contradiction override) +if [[ "$RAW_ARGS" == *"--override-strategy"* ]]; then + OVERRIDE_STRATEGY=1 + RAW_ARGS="${RAW_ARGS//--override-strategy/}" +fi ``` | Mode | When | Behavior | diff --git a/plugins/flow-next/skills/flow-next-capture/phases.md b/plugins/flow-next/skills/flow-next-capture/phases.md index 4348a2fb..8ca61d38 100644 --- a/plugins/flow-next/skills/flow-next-capture/phases.md +++ b/plugins/flow-next/skills/flow-next-capture/phases.md @@ -94,6 +94,7 @@ Every acceptance criterion line, every decision-context line, every scope-boundi | `[user]` | Verbatim from conversation evidence (exact quote or close paraphrase preserving meaning) | The user said this, in these or similar words. Reasonable people would agree it's the user's stated intent. | | `[paraphrase]` | User intent restated in spec language (semantic equivalence; no new constraints introduced) | The user expressed this idea, but agent rephrased to match spec conventions. Same content, cleaner wording. | | `[inferred]` | Agent fill-in (most-scrutinized; user must confirm at read-back) | Agent decided this; user did not state it explicitly. May be a reasonable default, may be wrong. | +| `[strategy:]` | Derived from `STRATEGY.md` content (verbatim or near-verbatim quote of approach / track body) | The criterion follows directly from a populated section in `STRATEGY.md` — the track name appears literal in the tag. Activates only when Phase 0 strategy snapshot is present. | ### Examples @@ -102,6 +103,7 @@ Every acceptance criterion line, every decision-context line, every scope-boundi | `> user (turn 4): "rate limit must reject 3+ requests per second from a single client"` | `- **R1:** Rate limit rejects ≥3 req/sec from a single client. [user]` | `[user]` | | `> user (turn 7): "we should write the spec body atomically so partial writes don't corrupt"` | `- **R5:** Spec writes are atomic — partial-write recovery preserves prior state. [paraphrase]` | `[paraphrase]` | | (no user mention of error format) | `- **R7:** Errors include the request id for trace correlation. [inferred]` | `[inferred]` | +| (STRATEGY.md `### Reliability` track says "we ship for 99.95% uptime") | `- **R9:** Service-level objective: 99.95% uptime measured monthly. [strategy:Reliability]` | `[strategy:Reliability]` | ### Section-level tags @@ -124,6 +126,7 @@ The breakdown is informational at read-back. Phase 4's `[inferred]` tally counts - **`[user]`** is for content the user can read and recognize as their own words. Acceptance criteria and rejected alternatives benefit most from this tag. - **`[paraphrase]`** is for spec-language restatements where the meaning is preserved but the wording is the agent's. Most decision-context and architecture-overview content lands here. - **`[inferred]`** is for content the user did not state but the agent decided was necessary for a complete spec. **Defaults are `[inferred]`** — error-format conventions, status code choices, retry policies, observability hooks. Surface them at read-back so the user can keep / edit / drop. +- **`[strategy:]`** is for content the agent imported from `STRATEGY.md` — verbatim or near-verbatim quote from the `approach` line or one of the `### ` H3 sub-blocks. The track name lives literally in the tag (e.g. `[strategy:Reliability]`). The criterion is treated as load-bearing for the strategy alignment surface; if the spec body contradicts a `[strategy:*]` line, capture refuses to write without `--override-strategy` (see SKILL.md). A spec with 0 `[inferred]` items is rare and probably means the conversation was unusually thorough. A spec with 30 `[inferred]` items is suspicious — the conversation was probably too thin for capture, and the user should pursue `/flow-next:interview` instead. diff --git a/plugins/flow-next/skills/flow-next-capture/workflow.md b/plugins/flow-next/skills/flow-next-capture/workflow.md index 44ce7b6f..5a070265 100644 --- a/plugins/flow-next/skills/flow-next-capture/workflow.md +++ b/plugins/flow-next/skills/flow-next-capture/workflow.md @@ -70,6 +70,41 @@ Memory hits are advisory — they signal "you may have prior art on this topic" If memory is not initialized (`memory list` returns the `Memory not initialized` error), skip this step silently. Memory search is a quality-of-life signal; absence is not blocking. +### 0.3b — Strategy snapshot (advisory grounding input) + +Read `STRATEGY.md` (when populated) so Phase 2's source-tagging can apply `[strategy:]` to acceptance criteria that follow directly from strategic intent. Husk-vs-presence gate uses `sections_filled >= 1` from `flowctl strategy status --json`, NOT `[[ -f STRATEGY.md ]]`. + +```bash +STRATEGY_STATUS_JSON=$("$FLOWCTL" strategy status --json 2>/dev/null || echo '{"exists":false,"sections_filled":0}') +STRATEGY_FILLED=$(jq -r '.sections_filled // 0' <<< "$STRATEGY_STATUS_JSON" 2>/dev/null || echo 0) + +if [[ "$STRATEGY_FILLED" -ge 1 ]]; then + STRATEGY_JSON=$("$FLOWCTL" strategy read --json 2>/dev/null || echo '{}') + STRATEGY_PRESENT=true + STRATEGY_NAME=$(jq -r '.name // "(unnamed)"' <<< "$STRATEGY_JSON") + STRATEGY_PROBLEM=$(jq -r '.target_problem // ""' <<< "$STRATEGY_JSON") + STRATEGY_APPROACH=$(jq -r '.approach // ""' <<< "$STRATEGY_JSON") + STRATEGY_TRACKS_RAW=$(jq -r '.tracks // ""' <<< "$STRATEGY_JSON") + STRATEGY_PATH=$(jq -r '.path // "STRATEGY.md"' <<< "$STRATEGY_JSON") +else + STRATEGY_PRESENT=false +fi +``` + +Surface as a "Strategic context:" footnote — 3-5 lines total — when the agent presents Phase 0 results to the user. Format: + +``` +Strategic context (STRATEGY.md, last updated 2026-04-30): + Approach: + Active tracks: , , +``` + +`STRATEGY_TRACKS_RAW` is a **raw markdown string** with `### ` H3 sub-blocks. Parse the H3 names locally for the active-tracks list. Empty section bodies (any of `target_problem`, `approach`, `tracks`) surface as `""` — `(.field // "")` style fallbacks in the jq queries above keep parsing well-formed when an optional section is missing. + +The strategy snapshot is **input**, not gating: even when `STRATEGY_PRESENT=true`, capture proceeds. Phase 2's source-tagging uses the snapshot to assign `[strategy:]` to criteria that quote / paraphrase strategy content. Phase 5 uses it to detect contradictions (see §5.0 below) and refuse the write without `--override-strategy`. + +When `STRATEGY_PRESENT=false`, Phase 2 emits no `[strategy:*]` tags and Phase 5's contradiction check is skipped entirely — there is no signal to align to. + ### 0.4 — Compaction detection (R6) Scan the visible conversation for any of: @@ -245,6 +280,7 @@ Every acceptance criterion line, every decision-context line, and every scope-bo | `[user]` | Verbatim from conversation evidence (exact quote or close paraphrase preserving meaning) | `- **R1:** Rate limit must reject ≥3 requests/sec from a single client. [user] (turn 4)` | | `[paraphrase]` | User intent restated in spec language (semantic equivalence; no new constraints introduced) | `- **R2:** Spec body is written via heredoc, atomic write. [paraphrase]` | | `[inferred]` | Agent fill-in (most-scrutinized; user must confirm at read-back) | `- **R7:** Errors include the request id for trace correlation. [inferred]` | +| `[strategy:]` | Derived from `STRATEGY.md` content (verbatim or near-verbatim from `approach` or a `### ` H3 sub-block); track name lives literally in the tag | `- **R9:** Service-level objective: 99.95% uptime measured monthly. [strategy:Reliability]` | Pure prose sections (Goal & Context narrative, Architecture overview) do not need per-line tags — but the **whole section** carries a section-level tag in a frontmatter-style note: e.g. ``. Phase 4 read-back surfaces this. @@ -359,13 +395,18 @@ Construct the full draft including: 2. The `## Conversation Evidence` block (Phase 1). 3. Every section drafted in Phase 2, with source tags visible. 4. The `## Acceptance Criteria` R-ID list — bulleted, source tags shown. -5. **`[inferred]` tally** — total count across the spec, with per-section breakdown: +5. **Source-tag tally** — total count across the spec, with per-tag breakdown. Format: + ``` + Source: [user] N · [paraphrase] M · [strategy] K · [inferred] L + ``` + Followed by the per-section `[inferred]` breakdown (the most-scrutinized class): ``` [inferred] count: 7 total - Architecture & Data Models: 3 - API Contracts: 2 - Boundaries: 2 ``` + The `[strategy]` count aggregates all `[strategy:]` lines regardless of track. When Phase 0 strategy snapshot scanned `none` (`STRATEGY_PRESENT=false`), `[strategy] K` reads `[strategy] 0` (or the field is omitted entirely — equivalent in practice). 6. **8+ acceptance-criterion suggestion** (if Phase 2.5 fired): ``` This spec has 11 acceptance criteria — consider splitting into multiple @@ -434,6 +475,93 @@ Autofix never offers `edit` — there's no user to ask. The print-then-rerun-wit **Goal:** atomic write of the new (or rewritten) epic via existing flowctl plumbing. +### 5.0 — Strategy contradiction check (gate; runs before any write) + +When the Phase 0 strategy snapshot was populated (`STRATEGY_PRESENT=true`), scan the drafted spec body for contradictions against the active tracks. A contradiction exists when: + +1. The spec body has at least one `[strategy:]` line AND the surrounding criterion / decision-context line negates the corresponding track body. Example: track `### CLI-only` says "we ship CLI tools, not SaaS"; spec criterion `[strategy:CLI-only]` reads "ship a managed dashboard service" — direct contradiction. +2. The spec body proposes an investment area that contradicts `approach` directly. Example: approach says "OSS-tools repo, no commercial SaaS"; spec body adds "stripe billing integration as a core feature" without `[strategy:*]` tagging — semantic contradiction even without a tag. + +When a contradiction is detected AND `OVERRIDE_STRATEGY` is `0`: + +```text +Error: spec contradicts active track "" — pass --override-strategy to proceed. + +Detected contradiction: + Track: (STRATEGY.md) + Track says: "" + Spec says: "" + +Re-run with --override-strategy to write the spec anyway. You'll be prompted to +record the override as a decision entry (the override is exactly the kind of +load-bearing architectural choice the decisions track exists for). +``` + +In **interactive** mode, refuse with the message above (exit 2) — do NOT prompt the user to override here; require the explicit flag re-run so the override is intentional. + +In **autofix** mode, refuse identically (exit 2). Autofix cannot resolve a strategy override. + +When `OVERRIDE_STRATEGY=1` AND the snapshot is populated, capture proceeds with the write **AND** prompts the user to record the override as a decision entry. Pattern (mirrors `/flow-next:interview` behavior (d) — three-criteria decision-record gate): + +```bash +# Interactive only — autofix never reaches this branch (5.0 exits 2 above when OVERRIDE_STRATEGY=0, +# and OVERRIDE_STRATEGY=1 in autofix is treated as "user already chose to override; record audit +# trail to stderr but don't prompt" — see logging branch below). +``` + +Use `AskUserQuestion` (lead-with-recommendation, `[high]` toward yes): + +- **header**: `Record override?` +- **body**: `Override strategy track "" — record as a decision? Recommended: yes — override decisions belong in the decisions track (load-bearing architectural choice). Confidence: [high].` +- **options**: frozen — `yes` (write decision entry), `no` (proceed without recording; audit trail logged to stderr only). + +On `yes`, invoke `flowctl memory add` with the override rationale piped via `--body-file -` stdin: + +```bash +"$FLOWCTL" memory add \ + --track knowledge \ + --category decisions \ + --title "Override strategy: " \ + --module strategy \ + --tags strategy-override \ + --body-file - < contradicts active track "" in STRATEGY.md. + +## What was chosen + + +## Why + + +## Track being overridden +- **** (STRATEGY.md): "" +- **Spec direction:** "" + +## Considered alternatives +- Aligning with the strategy track (rejected because: ) +- Updating STRATEGY.md instead of overriding here (rejected because: ) + +## Consequences +- This epic ships in tension with track "". +- A future `/flow-next:strategy` run should re-evaluate the track; this decision feeds that conversation. +EOF +``` + +On `no`, proceed without writing the decision. Log an audit-trail line to stderr: + +```bash +# On no: +echo "[STRATEGY OVERRIDE]: track=\"\" decision-not-recorded epic=" >&2 + +# On yes (decision was recorded): +echo "[STRATEGY OVERRIDE]: track=\"\" decision-recorded= epic=" >&2 +``` + +The audit trail line appears in both interactive (after the user picks) and autofix (when `OVERRIDE_STRATEGY=1` was passed) — it is the minimum durable record that an override happened, surfaceable in CI logs / git hook output later. In autofix mode (where the AskUserQuestion is unreachable), the decision-not-recorded variant fires unconditionally. + +When `STRATEGY_PRESENT=false`, this entire section is a no-op — there's no strategy snapshot to contradict. + ### 5.1 — Build the spec body The spec body assembled in Phase 2 + revised in Phase 4 edit cycles is the input to `flowctl epic set-plan`. Source tags **stay in the spec body** — they are part of the audit trail and survive into the on-disk spec at `.flow/specs/.md`. Future readers (including `/flow-next:plan` and `/flow-next:interview`) see the tags and can scrutinize. diff --git a/plugins/flow-next/skills/flow-next-interview/SKILL.md b/plugins/flow-next/skills/flow-next-interview/SKILL.md index 65d5844d..58f85946 100644 --- a/plugins/flow-next/skills/flow-next-interview/SKILL.md +++ b/plugins/flow-next/skills/flow-next-interview/SKILL.md @@ -58,13 +58,16 @@ If empty, ask: "What should I interview you about? Give me a Flow ID (e.g., fn-1 FLOWCTL="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/flowctl" ``` -### Parse `--docs` / `--no-docs` flags +### Parse `--docs` / `--no-docs` / `--strategy` / `--no-strategy` flags -Strip `--docs` / `--no-docs` from `$ARGUMENTS` before input-type detection so they don't get confused for a Flow ID or path: +Strip the four doc-aware override flags from `$ARGUMENTS` before input-type detection so they don't get confused for a Flow ID or path: ```bash RAW_ARGS="$ARGUMENTS" -DOC_AWARE_FORCE="" # "" = autodetect, "on" = forced on, "off" = forced off +DOC_AWARE_FORCE="" # "" = autodetect, "on" = forced on, "off" = forced off (controls glossary + decisions) +STRATEGY_AWARE_FORCE="" # "" = autodetect, "on" = forced on, "off" = forced off (controls strategy independently) + +# Glossary + decisions: --docs / --no-docs (mutually exclusive; --no-docs wins) if [[ "$RAW_ARGS" == *"--no-docs"* ]]; then DOC_AWARE_FORCE="off" RAW_ARGS="${RAW_ARGS//--no-docs/}" @@ -72,21 +75,47 @@ elif [[ "$RAW_ARGS" == *"--docs"* ]]; then DOC_AWARE_FORCE="on" RAW_ARGS="${RAW_ARGS//--docs/}" fi + +# Strategy: explicit --strategy / --no-strategy always wins. Otherwise --docs / --no-docs cascades. +# Order: explicit pair first (mutually exclusive; --no-strategy wins on conflict), then docs cascade. +if [[ "$RAW_ARGS" == *"--no-strategy"* ]]; then + STRATEGY_AWARE_FORCE="off" + RAW_ARGS="${RAW_ARGS//--no-strategy/}" +elif [[ "$RAW_ARGS" == *"--strategy"* ]]; then + STRATEGY_AWARE_FORCE="on" + RAW_ARGS="${RAW_ARGS//--strategy/}" +elif [[ "$DOC_AWARE_FORCE" == "off" ]]; then + # --no-docs alone cascades to strategy: matrix row 3 says all three off. + STRATEGY_AWARE_FORCE="off" +elif [[ "$DOC_AWARE_FORCE" == "on" ]]; then + # --docs alone cascades to strategy: matrix row 2 says all three on. + STRATEGY_AWARE_FORCE="on" +fi + RAW_ARGS=$(printf "%s" "$RAW_ARGS" | tr -s ' ' | sed 's/^ //;s/ $//') # RAW_ARGS now contains the Flow ID / file path / empty. ``` -`--docs` and `--no-docs` are mutually exclusive; if the user passes both, `--no-docs` wins (the `if/elif` checks `--no-docs` first). The `--docs` token gets left in the residual `RAW_ARGS` after stripping, which surfaces downstream as an unrecognized argument — loud failure beats silent acceptance of conflicting state. +Each pair is mutually exclusive (the `if/elif` checks the negation first so it wins on conflict). The `--docs` / `--strategy` tokens get left in the residual `RAW_ARGS` after stripping, which surfaces downstream as an unrecognized argument — loud failure beats silent acceptance of conflicting state. -### Doc-aware autodetect +**Flag matrix** — five rows, all explicit: + +| Flags | Glossary | Decisions | Strategy | +|-------|----------|-----------|----------| +| (default) | autodetect | autodetect | autodetect | +| `--docs` | on | on | on | +| `--no-docs` | off | off | off | +| `--no-docs --strategy` | off | off | on | +| `--docs --no-strategy` | on | on | off | -Decide whether doc-aware mode (behaviors a-d below) activates. Three paths: +`--docs` / `--no-docs` cascade to strategy when no explicit `--strategy` / `--no-strategy` is passed (matrix rows 2 + 3). Explicit `--strategy` / `--no-strategy` always wins (matrix rows 4 + 5) and is the only way to drive a different value into strategy than into glossary + decisions. The matrix is the contract. -1. **Forced on** (`--docs` flag): `DOC_AWARE=1`. Lazy-creates root `GLOSSARY.md` on first term resolution via `flowctl glossary add` (writes to nearest-ancestor or repo root when no ancestor exists). -2. **Forced off** (`--no-docs` flag): `DOC_AWARE=0`. Skip behaviors a-d entirely, even if artifacts exist. -3. **Autodetect** (no flag): activate when `GLOSSARY.md` has at least one defined term OR any decision entry exists. +### Doc-aware autodetect + +Decide whether doc-aware mode (behaviors a-e below) activates. `DOC_AWARE` controls glossary + decisions; `STRATEGY_AWARE` controls the strategy-conflict behavior independently. Each has three paths (forced-on / forced-off / autodetect) per the flag matrix above. ```bash +# DOC_AWARE: glossary + decisions DOC_AWARE=0 if [[ "$DOC_AWARE_FORCE" == "on" ]]; then DOC_AWARE=1 @@ -99,11 +128,26 @@ else DOC_AWARE=1 fi fi + +# STRATEGY_AWARE: strategy (independent of DOC_AWARE — autodetects on its own signal) +STRATEGY_AWARE=0 +if [[ "$STRATEGY_AWARE_FORCE" == "on" ]]; then + STRATEGY_AWARE=1 +elif [[ "$STRATEGY_AWARE_FORCE" == "off" ]]; then + STRATEGY_AWARE=0 +else + STRAT_FILLED=$("$FLOWCTL" strategy status --json 2>/dev/null | jq -r '.sections_filled // 0') + if [[ "${STRAT_FILLED:-0}" -ge 1 ]]; then + STRATEGY_AWARE=1 + fi +fi ``` -**Why `total_terms > 0` rather than `[[ -f GLOSSARY.md ]]`:** `flowctl glossary remove` leaves a `# Glossary` H1 husk on disk after the last term is removed (the file is project state, intentionally retained). A presence-only check would false-positive on an empty husk and surface phantom doc-aware questions when no canonical vocabulary is actually defined. `glossary list --json` walks the file and counts populated entries; `total_terms == 0` for a husk. +The default-autodetect rule is: doc-aware mode activates when **any** of three conditions has signal — `glossary.total_terms > 0` (a) OR a decision entry exists (b) OR `strategy.sections_filled >= 1` (c). The two flag pairs (`--docs` / `--no-docs` and `--strategy` / `--no-strategy`) override (a)+(b) and (c) independently per the matrix above. -When `DOC_AWARE=1`, the four behaviors below layer onto the standard interview workflow. When `DOC_AWARE=0`, the interview proceeds exactly as today. +**Why `total_terms > 0` and `sections_filled >= 1` rather than `[[ -f ]]`:** `flowctl glossary remove` leaves a `# Glossary` H1 husk after the last term is removed; `flowctl strategy` leaves a frontmatter-plus-H1 husk under the same R18 invariant. Both files are project state, intentionally retained. A presence-only check would false-positive on an empty husk and surface phantom doc-aware questions when no canonical vocabulary / strategic intent is actually defined. `glossary list --json` and `strategy status --json` walk the file and count populated entries; both report zero for a husk. + +When `DOC_AWARE=1`, behaviors (a)-(d) below layer onto the standard interview workflow. When `STRATEGY_AWARE=1`, behavior (e) layers on. When both are 0, the interview proceeds exactly as today. ## Detect Input Type @@ -201,9 +245,14 @@ Confidence tier: `[high]` when grep evidence is unambiguous (file does not exist The bar for surfacing: a meaningful contradiction that affects spec correctness. If the user says "the validator returns boolean" and grep shows it returns `Result`, surface. If the user paraphrases a function's role and grep shows the role matches but the implementation differs in unrelated detail, log under `## Resolved via Codebase` and move on. -## Doc-aware behaviors (`DOC_AWARE=1` only) +## Doc-aware behaviors + +Five behaviors layer onto the standard interview workflow when their respective gate is open: -When `DOC_AWARE=1`, four behaviors layer onto the standard interview workflow. When `DOC_AWARE=0`, skip this entire section. +- Behaviors (a)-(d) are gated on `DOC_AWARE=1` (glossary + decisions signal). When `DOC_AWARE=0`, skip them. +- Behavior (e) is gated on `STRATEGY_AWARE=1` (strategy signal). When `STRATEGY_AWARE=0`, skip it. + +The two gates are independent (see flag matrix above) — `DOC_AWARE` and `STRATEGY_AWARE` may differ within the same interview session. ### Behavior (a) — Phase-zero glossary scan @@ -323,6 +372,48 @@ When all three hold: **At most one decision write per interview turn.** Even if multiple gate-passing decisions surface, ask one at a time; subsequent asks adapt to the user's energy level for read-back. +### Behavior (e) — Code-versus-strategy contradiction (`STRATEGY_AWARE=1` only) + +Parallel structure to behavior (a) — Phase-zero glossary scan. Before drafting the first question batch in a `STRATEGY_AWARE=1` session, run a strategy scan against the user's request. + +```bash +"$FLOWCTL" strategy read --json +``` + +JSON shape (selected fields used here): + +```json +{ + "name": "", + "target_problem": "...", + "approach": "...", + "tracks": "### track-a\nOne line on track A.\n_Why it serves the approach:_ ...\n\n### track-b\n...", + "last_updated": "2026-05-01", + "path": "STRATEGY.md" +} +``` + +`tracks` is a **raw markdown string** — H3 sub-blocks of the form `### ` followed by a one-line description and a `_Why it serves the approach:_` line. Parse the H3 names locally. Empty section bodies (any of `target_problem`, `approach`, `tracks`) surface as `""` (empty string), not null — `(.field // "")` style fallbacks keep parsing well-formed when an optional section is missing. + +Walk the user's request looking for two patterns: + +1. **Track-name mismatch** — the user uses a noun-phrase that names a track-like investment area, but the wording diverges from a canonical track in `STRATEGY.md` (e.g. user says "Initiative" but `tracks` defines "### Track"). Treat the user's wording as a candidate alias for the closest canonical track and surface as a question if load-bearing for the spec. +2. **Direction conflict** — the user describes a goal or constraint that contradicts the `approach` or one of the active tracks (e.g. approach says "we ship CLI tools, not SaaS" but the user is asking the spec to add a managed dashboard service). + +For each hit, evaluate the same load-bearing filter as behavior (a): casual passing mention does not trigger; mention that defines behavior or shapes acceptance does. + +When a hit passes the filter, surface via `AskUserQuestion`: + +- **header**: `Strategy mismatch?` +- **body**: `You said ""; STRATEGY.md () says "". Recommended: . Confidence: [].` +- **options**: frozen — `align-with-strategy` (the user meant the existing track / honors the approach; spec uses canonical wording), `flag-as-drift` (the spec is intentionally pushing back on the strategy; capture in `## Strategy Conflicts` and proceed), `this-is-different` (the words collide but the concepts differ; spec uses a fresh disambiguating term — also capture in `## Strategy Conflicts`). + +Confidence tier: `[high]` when the strategy entry is recent and the user's wording cleanly maps to a canonical track or directly contradicts the verbatim approach; `[judgment-call]` when meaning could plausibly have drifted; `[your-call]` when the strategic direction sits in user-domain territory the agent has no purchase on. + +**Throttle:** at most one strategy-conflict question per interview turn (parallel to behavior (a)'s glossary throttle). If multiple strategy mismatches hit, surface the most load-bearing one first; the rest fold into the natural conversation flow as they come up. Bombarding the user with strategy-alignment questions before the core spec questions is the failure mode this throttle prevents. Combined with the (a) and (d) throttles, the per-turn doc-aware question budget is **3 max** (1 glossary + 1 decision-record + 1 strategy). + +The output of behavior (e) lands in a new spec section, `## Strategy Conflicts`, parallel to `## Glossary Conflicts`. Format: per-line entries with user-wording / canonical-strategy-wording / STRATEGY.md path / resolution-chosen. Lets reviewers see where the spec aligns or pushes back on strategic intent. Strategy conflicts are read-only signal for `/flow-next:sync`'s plan-sync agent — the interview never edits `STRATEGY.md`. + ## Question Categories Read [questions.md](questions.md) for all question categories and interview guidelines. @@ -367,6 +458,10 @@ Items the agent answered via Read / Grep / Glob, with file:line evidence. Separa (optional — only when DOC_AWARE=1 surfaced behavior-(a) hits during the interview) Per-term: user-wording vs. canonical term, the resolution chosen (use-canonical / redefine / this-is-different), file:line of the canonical entry. Lets reviewers see where vocabulary tightened. +## Strategy Conflicts +(optional — only when STRATEGY_AWARE=1 surfaced behavior-(e) hits during the interview) +Per-line: user-wording vs. canonical-strategy-wording (track name or approach), STRATEGY.md path, resolution chosen (align-with-strategy / flag-as-drift / this-is-different). Lets reviewers see where the spec aligns or pushes back on strategic intent. Read-only signal for plan-sync — the interview never edits STRATEGY.md. + ## Open Questions Unresolved items that need research during planning @@ -411,6 +506,10 @@ Items the agent answered via Read / Grep / Glob, with file:line evidence. Separa (optional — only when DOC_AWARE=1 surfaced behavior-(a) hits during the interview) Per-term: user-wording vs. canonical term, the resolution chosen, file:line of the canonical entry. +## Strategy Conflicts +(optional — only when STRATEGY_AWARE=1 surfaced behavior-(e) hits during the interview) +Per-line: user-wording vs. canonical-strategy-wording, STRATEGY.md path, resolution chosen. + ## Open Questions Unresolved items @@ -468,6 +567,7 @@ Show summary: - Key decisions captured - What was written (Flow ID updated / file rewritten) - Doc-aware mode (when `DOC_AWARE=1` was active): glossary terms added/updated via `flowctl glossary add`, decision entries written via `flowctl memory add --track knowledge --category decisions`, glossary conflicts captured under `## Glossary Conflicts` +- Strategy-aware mode (when `STRATEGY_AWARE=1` was active): strategy conflicts captured under `## Strategy Conflicts` (read-only — interview never edits STRATEGY.md) Suggest next step based on input type: - New idea / epic without tasks → `/flow-next:plan fn-N` diff --git a/plugins/flow-next/skills/flow-next-plan/steps.md b/plugins/flow-next/skills/flow-next-plan/steps.md index f8ffce0e..1b3c8e6d 100644 --- a/plugins/flow-next/skills/flow-next-plan/steps.md +++ b/plugins/flow-next/skills/flow-next-plan/steps.md @@ -80,6 +80,26 @@ $FLOWCTL config get memory.enabled --json $FLOWCTL config get scouts.github --json ``` +**Check for STRATEGY.md (husk-vs-presence — uses `sections_filled >= 1`, NOT `[[ -f STRATEGY.md ]]`):** +```bash +STRATEGY_STATUS_JSON=$($FLOWCTL strategy status --json 2>/dev/null || echo '{"exists":false,"sections_filled":0}') +STRATEGY_FILLED=$(jq -r '.sections_filled // 0' <<< "$STRATEGY_STATUS_JSON" 2>/dev/null || echo 0) + +if [[ "$STRATEGY_FILLED" -ge 1 ]]; then + STRATEGY_JSON=$($FLOWCTL strategy read --json 2>/dev/null || echo '{}') + # Pass the parsed STRATEGY.md content into plan-prompt context alongside research findings. + # `tracks` is a raw markdown string (### H3 sub-blocks); empty section bodies + # are "" not null. The plan prompt sees `name`, `target_problem`, `approach`, `tracks`, + # `last_updated` verbatim — no paraphrasing. Active tracks shape the Strategy Alignment + # section in Step 5; conflicts with active tracks surface as drift in Step 5. + STRATEGY_PRESENT=true +else + STRATEGY_PRESENT=false +fi +``` + +When `STRATEGY_PRESENT=true`, the scouts and the plan-prompt see the strategy content. When `STRATEGY_PRESENT=false` (no STRATEGY.md or husk), the plan skips the `## Strategy Alignment` section and any drift-surfacing entirely (Step 5) — absence is fine, no signal to align to. + **Based on user's choice in SKILL.md setup:** --- @@ -211,6 +231,8 @@ Default to standard unless complexity demands more or less. ```bash # Include: Overview, Scope, Approach, Quick commands (REQUIRED), Acceptance, # Early proof point, Requirement coverage, References + # Conditional sections: ## Strategy Alignment (when STRATEGY_PRESENT=true from Step 1), + # ## Strategy drift flagged for review (when plan scope conflicts with an active track) # Add mermaid diagram if data model or architecture changes $FLOWCTL epic set-plan --file - --json <<'EOF' # Epic Title @@ -223,6 +245,32 @@ Default to standard unless complexity demands more or less. # At least one smoke test command ``` + ## Boundaries / non-goals + - + + ## Strategy Alignment + + + Active tracks served by this plan: + - **** — + - **** — + + + _No active strategy track served — review for drift._ + + ## Strategy drift flagged for review + + + - ****: . Review for revision via `/flow-next:strategy`. + + ## Decision context + - + ## Acceptance - **R1:** - **R2:** @@ -242,6 +290,18 @@ Default to standard unless complexity demands more or less. EOF ``` + **`## Strategy Alignment` rules (active iff STRATEGY_PRESENT=true from Step 1):** + - Section sits between `## Boundaries / non-goals` and `## Decision context` in the template above. + - List active tracks (`### ` blocks parsed from the strategy snapshot's `tracks` raw markdown string) that this plan advances. + - When the plan serves NO active track, render the placeholder `_No active strategy track served — review for drift._` literally — do not omit the section. + - Skip the entire section when STRATEGY_PRESENT=false. Husk-vs-presence: gated on `sections_filled >= 1`, NOT `[[ -f STRATEGY.md ]]`. + + **`## Strategy drift flagged for review` rules (conditional on conflict detection):** + - Mirrors plan-sync's "Decision overrides flagged for review" surface (`agents/plan-sync.md` Phase 6 summary). + - Bulleted list with track name + plan-decision divergence + `Review for revision via /flow-next:strategy.` line per item. + - Read-only — the plan skill never edits STRATEGY.md, never marks a track superseded, never auto-supersedes anything. Surface for human review only. + - Omit the heading entirely when no drift detected. Empty drift block is silent, not `_(none)_`. + **Early proof point rules:** - Identify which task proves the fundamental approach works - One sentence: which task + what it proves diff --git a/plugins/flow-next/skills/flow-next-prospect/SKILL.md b/plugins/flow-next/skills/flow-next-prospect/SKILL.md index 7657b1c6..0dc7d2b4 100644 --- a/plugins/flow-next/skills/flow-next-prospect/SKILL.md +++ b/plugins/flow-next/skills/flow-next-prospect/SKILL.md @@ -52,9 +52,9 @@ No env-var opt-in. Ralph never decides direction. Execute the phases in [workflow.md](workflow.md) in order: 0. **Resume check** — list active artifacts <30d; ask extend / fresh / open via blocking question. Corrupt artifacts surfaced but never offered for extension. -1. **Ground** — scan repo with graceful degradation: git log (30d), open epics, CHANGELOG top, memory matches, memory audit (if present). Emit a structured 30-50 line snapshot — titles + tags only, never raw bodies. +1. **Ground** — scan repo with graceful degradation: git log (30d), open epics, CHANGELOG top, memory matches, memory audit (if present), strategy snapshot (verbatim `name` / `target_problem` / `approach` / `tracks` / `last_updated` from `flowctl strategy read --json` when `sections_filled >= 1`; husk-vs-presence gate uses `sections_filled`, NOT `[[ -f STRATEGY.md ]]`). Emit a structured 30-50 line snapshot — titles + tags only, never raw bodies. 2. **Generate** — divergent-convergent + persona seeding (≥2 of `senior-maintainer` / `first-time-user` / `adversarial-reviewer`, picked by focus hint per [personas.md](personas.md)). One divergent prompt; no self-judging. -3. **Critique** — separate prompt pass that does NOT see the focus hint or persona texts; rejection floor ≥40% (≥60% under `raise the bar`); fixed taxonomy (`duplicates-open-epic | out-of-scope | insufficient-signal | too-large | backward-incompat | other`); floor violation surfaces blocking question with frozen options `regenerate | loosen-floor | ship-anyway`. +3. **Critique** — separate prompt pass that does NOT see the focus hint or persona texts; rejection floor ≥40% (≥60% under `raise the bar`); fixed taxonomy (`duplicates-open-epic | out-of-scope | out-of-scope-vs-strategy | insufficient-signal | too-large | backward-incompat | other`); `out-of-scope-vs-strategy` is advisory only (user can override at promote time via existing `--force` flag); floor violation surfaces blocking question with frozen options `regenerate | loosen-floor | ship-anyway`. 4. **Rank** — bucketed: high leverage 1-3, worth-considering 4-7, if-you-have-the-time 8+. Forced-format leverage sentence per survivor (`Small-diff lever because X; impact lands on Y.`); no numeric scores. 5. **Write artifact** — atomic write-then-rename to `.flow/prospects/-.md` via `flowctl.write_prospect_artifact`. Same-day collisions suffix with `-2`, `-3`. Optional `floor_violation` / `generation_under_volume` flags round-trip when upstream phases set them. 6. **Handoff** — blocking prompt for promote / interview / skip via the platform's question tool; frozen numbered-options fallback when no blocking tool is available. diff --git a/plugins/flow-next/skills/flow-next-prospect/workflow.md b/plugins/flow-next/skills/flow-next-prospect/workflow.md index 08326f2e..a5413a2b 100644 --- a/plugins/flow-next/skills/flow-next-prospect/workflow.md +++ b/plugins/flow-next/skills/flow-next-prospect/workflow.md @@ -308,9 +308,40 @@ else fi ``` +#### Strategy snapshot (optional, present iff STRATEGY.md has at least one populated section) + +Verbatim emit pattern (mirrors CE-ideate's "emit approach and active tracks verbatim"). Pulls the `name`, `target_problem`, `approach`, `tracks` (raw markdown — `### ` H3 sub-blocks; downstream prompt context handles parsing), and `last_updated` from `flowctl strategy read --json`. Husk-vs-presence gate uses `sections_filled >= 1` from `flowctl strategy status --json`, NOT `[[ -f STRATEGY.md ]]`. + +```bash +STRATEGY_STATUS_JSON=$("$FLOWCTL" strategy status --json 2>/dev/null || echo '{"exists":false,"sections_filled":0}') +STRATEGY_FILLED=$(jq -r '.sections_filled // 0' <<< "$STRATEGY_STATUS_JSON" 2>/dev/null || echo 0) + +if [[ "$STRATEGY_FILLED" -ge 1 ]]; then + STRATEGY_JSON=$("$FLOWCTL" strategy read --json 2>/dev/null || echo '{}') + STRATEGY_NAME=$(jq -r '.name // "(unnamed)"' <<< "$STRATEGY_JSON") + STRATEGY_PROBLEM=$(jq -r '.target_problem // ""' <<< "$STRATEGY_JSON") + STRATEGY_APPROACH=$(jq -r '.approach // ""' <<< "$STRATEGY_JSON") + STRATEGY_TRACKS=$(jq -r '.tracks // ""' <<< "$STRATEGY_JSON") + STRATEGY_UPDATED=$(jq -r '.last_updated // "(unknown)"' <<< "$STRATEGY_JSON") + STRATEGY_BLOCK="strategy: + name: ${STRATEGY_NAME} + last_updated: ${STRATEGY_UPDATED} + target_problem: | +$(printf "%s\n" "$STRATEGY_PROBLEM" | sed 's/^/ /') + approach: | +$(printf "%s\n" "$STRATEGY_APPROACH" | sed 's/^/ /') + tracks: | +$(printf "%s\n" "$STRATEGY_TRACKS" | sed 's/^/ /')" +else + STRATEGY_BLOCK="strategy: scanned: none (no STRATEGY.md signal)" +fi +``` + +The `target_problem`, `approach`, and `tracks` strings are emitted **verbatim** — no paraphrasing, no field-extraction. `tracks` is a raw markdown string with `### ` H3 sub-blocks; downstream prompt context (Phase 2 generation, Phase 3 critique) handles the parsing. Empty section bodies surface as `""` (empty string), not null — `(.field // "")` style fallbacks keep the snapshot well-formed even when an optional section is missing. + ### 1.3 — Emit snapshot -Concatenate the blocks under a single `## Grounding snapshot` heading. Order is fixed (git log → open epics → changelog → memory → memory audit) so the snapshot is comparable across runs. Cap each block by line-count so total output stays in the 30-50 line target window. +Concatenate the blocks under a single `## Grounding snapshot` heading. Order is fixed (git log → open epics → changelog → memory → memory audit → strategy) so the snapshot is comparable across runs. Cap each block by line-count so total output stays in the 30-50 line target window. The snapshot is the input to Phase 2's generation prompt (task 2). For this task, the snapshot is printed to stdout for manual inspection — Phase 2's prompt scaffolding lands later. @@ -331,6 +362,8 @@ $CHANGELOG_BLOCK $MEMORY_BLOCK $AUDIT_BLOCK + +$STRATEGY_BLOCK EOF ``` @@ -490,14 +523,17 @@ Inputs: `CANDIDATES_YAML` (Phase 2 §2.4) + the Phase 1 grounding snapshot. **Ex Rejection taxonomy (R3 anchor — frozen string list): ``` -duplicates-open-epic — material overlap with an open epic in the grounding snapshot -out-of-scope — outside what this codebase / the focus area should tackle -insufficient-signal — speculative without evidence in grounding snapshot -too-large — XL or undermined by size; should be split or deferred -backward-incompat — would break public contracts / users without strong justification -other — explain in `reason` field; use sparingly +duplicates-open-epic — material overlap with an open epic in the grounding snapshot +out-of-scope — outside what this codebase / the focus area should tackle +out-of-scope-vs-strategy — contradicts an active track in the strategy snapshot (advisory only — user can override at promote time) +insufficient-signal — speculative without evidence in grounding snapshot +too-large — XL or undermined by size; should be split or deferred +backward-incompat — would break public contracts / users without strong justification +other — explain in `reason` field; use sparingly ``` +`out-of-scope-vs-strategy` is **advisory only**. It fires when a candidate's direction contradicts an active track from the strategy snapshot (Phase 1 §1.2). The rejection cites the violated track verbatim: `Rejected: [out-of-scope-vs-strategy] — contradicts active track ""`. The user can `flowctl prospect promote --idea N --force` to override (existing flag, no new plumbing). When the strategy snapshot scanned `none`, this category is unreachable — Phase 3 will not emit it. + Prompt template: ``` @@ -519,6 +555,7 @@ For each candidate, emit a verdict: `keep` or `drop`. If `drop`, the `taxonomy` - `duplicates-open-epic` — same direction as a listed open epic - `out-of-scope` — not aligned with this codebase or the focus area +- `out-of-scope-vs-strategy` — contradicts an active track in the strategy snapshot; cite the violated track name verbatim in `reason` (advisory — user can override) - `insufficient-signal` — no grounding evidence supports this being worth doing now - `too-large` — XL size or scope creep without commensurate payoff - `backward-incompat` — breaks contracts / users without strong justification @@ -534,7 +571,7 @@ Target rejection rate: **[REJECTION_FLOOR_PCT]%**. Below that floor, the run wil critiques: - index: 0 # zero-indexed position in the input list verdict: keep | drop - taxonomy: null | duplicates-open-epic | out-of-scope | insufficient-signal | too-large | backward-incompat | other + taxonomy: null | duplicates-open-epic | out-of-scope | out-of-scope-vs-strategy | insufficient-signal | too-large | backward-incompat | other reason: - index: 1 ... diff --git a/plugins/flow-next/skills/flow-next-strategy/SKILL.md b/plugins/flow-next/skills/flow-next-strategy/SKILL.md new file mode 100644 index 00000000..a2e3c573 --- /dev/null +++ b/plugins/flow-next/skills/flow-next-strategy/SKILL.md @@ -0,0 +1,266 @@ +--- +name: flow-next-strategy +description: "Create or maintain `STRATEGY.md` — the product's target problem, our approach, who it's for, key metrics, and tracks of work. Use when starting a new product, updating direction, or when prompts like 'write our strategy', 'update the roadmap', 'what are we working on', or 'set up the strategy doc' come up. Also fires when `/flow-next:prospect`, `/flow-next:plan`, `/flow-next:interview`, or `/flow-next:capture` need upstream grounding and no strategy doc exists yet." +user-invocable: false +allowed-tools: AskUserQuestion, Read, Write, Bash +--- + +# /flow-next:strategy — repo-root STRATEGY.md anchor + +`flow-next-strategy` produces and maintains `STRATEGY.md` — a short, durable anchor at the repo root (peer of `README.md` / `GLOSSARY.md`) that captures what the product is, who it serves, how it succeeds, and where the team is investing. Downstream skills (`/flow-next:prospect`, `/flow-next:plan`, `/flow-next:interview`, `/flow-next:capture`, `/flow-next:sync`) read it as grounding when `sections_filled >= 1`. + +The document is short and structured on purpose. Good answers to a handful of sharp questions produce a better strategy than any amount of prose. This skill asks those questions, pushes back on weak answers, and writes the doc. + +**Note: The current year is 2026.** Use this when dating the strategy document. + +## Interaction Method + +Default to `AskUserQuestion` (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded). Fall back to numbered options in chat only when the tool is unreachable in the harness or the call errors — never silently skip the question. (sync-codex.sh rewrites `AskUserQuestion` to `request_user_input` in the Codex mirror.) + +Ask one question at a time. **Free-form responses for the substantive sections** (Target problem / Our approach / Who it's for / Key metrics / Tracks). **Single-select with lead-with-recommendation only for routing decisions** (which section to revisit, include this optional section, foreign-file resolution). + +## Focus Hint + + #$ARGUMENTS + +Interpret any argument as an optional focus: a section name to revisit (`metrics`, `approach`, `tracks`, `problem`, `persona`, `milestones`, `not-working-on`) or a scope hint. With no argument, proceed open-ended and let the file state decide the path. + +## Core Principles + +1. **Anchor, not plan.** Strategy is what the product is and why. Features belong in `/flow-next:prospect`; tasks belong in epics and `/flow-next:plan`. Do not let either creep into the doc. +2. **Rigor in the questions, not the headings.** The section headers are plain English. The interview questions enforce strategy discipline (`references/interview.md`). +3. **Short is a feature.** The template is constrained. Adding sections costs more than it looks like. Push back on expansion. +4. **Durable across runs.** This skill is rerunnable. On a second run it updates in place, preserves what is working, and only challenges sections that look stale or weak. +5. **Survives `.flow/` wipe.** `STRATEGY.md` lives at repo root, never under `.flow/`. The project's strategy belongs to the project, not flow-next (R18 invariant from the 0.39.0 glossary epic). + +## Pre-check: local setup version + +Same pattern as `/flow-next:plan` and `/flow-next:audit` — non-blocking notice when `.flow/meta.json` `setup_version` lags the plugin version: + +```bash +if [[ -f .flow/meta.json ]]; then + SETUP_VER=$(jq -r '.setup_version // empty' .flow/meta.json 2>/dev/null) + PLUGIN_JSON="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/.claude-plugin/plugin.json" + [[ -f "$PLUGIN_JSON" ]] || PLUGIN_JSON="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/.factory-plugin/plugin.json" + PLUGIN_VER=$(jq -r '.version' "$PLUGIN_JSON" 2>/dev/null || echo "unknown") + if [[ -n "$SETUP_VER" && "$PLUGIN_VER" != "unknown" && "$SETUP_VER" != "$PLUGIN_VER" ]]; then + echo "Plugin updated to v${PLUGIN_VER}. Run /flow-next:setup to refresh local scripts (current: v${SETUP_VER})." >&2 + fi +fi +``` + +## flowctl path + +flowctl is **bundled — NOT installed globally.** `which flowctl` will fail (expected). Always use: + +```bash +FLOWCTL="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/flowctl" +``` + +## Execution Flow + +### Phase 0: Route by file state + +**0.1 — Ralph block (R17)** + +`/flow-next:strategy` is exploratory and human-in-the-loop. Autonomous loops have no business deciding repo strategy. Hard-error with exit 2 when running under Ralph. + +```bash +if [[ -n "${REVIEW_RECEIPT_PATH:-}" || "${FLOW_RALPH:-}" == "1" ]]; then + echo "[STRATEGY: user-triggered only — Ralph cannot run /flow-next:strategy]" >&2 + exit 2 +fi +``` + +No env-var opt-in. Ralph never decides direction. + +**0.2 — Read file state** + +```bash +STATUS_JSON=$("$FLOWCTL" strategy status --json) +EXISTS=$(printf '%s' "$STATUS_JSON" | jq -r '.exists') +HUSK=$(printf '%s' "$STATUS_JSON" | jq -r '.husk') +SECTIONS_FILLED=$(printf '%s' "$STATUS_JSON" | jq -r '.sections_filled') +GENERATOR_MATCH=$(printf '%s' "$STATUS_JSON" | jq -r '.generator_match') +FILE_PATH=$(printf '%s' "$STATUS_JSON" | jq -r '.file_path // empty') +``` + +JSON fields (frozen by Task 1): + +- `exists` (bool) — file present +- `husk` (bool) — `exists: true` AND `sections_filled == 0` +- `sections_filled` (int) — populated required-section count (0-5) +- `total_sections` (int) — always 5 (the 5 required) +- `last_updated` (str|null) — ISO date from frontmatter +- `file_path` (str|null) — absolute path of resolved STRATEGY.md +- `generator` (str|null) — frontmatter `generator` value +- `generator_match` (bool) — `generator == "flow-next-strategy"` + +**0.3 — Subdirectory walk-up surfacing (R16)** + +If `file_path` is set and differs from `${PWD}/STRATEGY.md`, surface one line in chat before any question fires: + +``` +Using repo-root STRATEGY.md at . +``` + +This is the only line printed before routing — keep the noise floor low. + +**0.4 — Foreign-file resolution (R15)** + +If `exists: true` AND `generator_match: false`, do not write. Fire `AskUserQuestion`: + +- `body`: "Found a STRATEGY.md at `` not generated by flow-next-strategy (generator: ``). Recommended: `keep` — do not overwrite a hand-written or external-tool strategy doc. Confidence: [your-call] — your project, your call." +- `options`: + - `keep` → exit 0 with one-line stdout: `Keeping existing STRATEGY.md unchanged.` + - `migrate` → exit 0 with stderr: `Multi-format migration deferred to v2. Either delete or rename the file, then re-run /flow-next:strategy to bootstrap from scratch.` + - `rewrite` → second confirmation `AskUserQuestion`: + - `body`: "Confirm destructive overwrite? The existing file at `` will be replaced. Recommended: `cancel`. Confidence: [your-call]." + - `options`: `confirm-overwrite` → proceed to Phase 1 first-run interview; `cancel` → exit 0. + +Single-select `AskUserQuestion`, lead-with-recommendation, neutral option labels. + +**0.5 — Routing** + +After Ralph block, walk-up surfacing, and foreign-file resolution: + +| State | Route | +|-------|-------| +| `exists: false` | Phase 1 (first-run interview) | +| `exists: true` AND `husk: true` AND `generator_match: true` | Phase 1 (first-run; husk was probably an aborted run) | +| `exists: true` AND `husk: false` AND `generator_match: true` | Phase 2 (section-revisit update) | + +Announce path in one line: `Strategy doc not found — let's write it.` or `Found existing strategy — let's review and update.` + +### Phase 1: First-run interview + +**1.1 — Load interview rules (non-optional)** + +```text +Read `references/interview.md`. +``` + +This load is non-optional. The pushback rules, anti-pattern examples, and quality bar for each section live there. Improvising from memory produces a passive transcription instead of a strategy doc. + +**1.2 — Run the interview in section order** + +For each of the 5 required sections (in order: `Target problem` → `Our approach` → `Who it's for` → `Key metrics` → `Tracks`), follow the per-section rule in `references/interview.md`: + +- Ask the **opening question** verbatim from the references file. +- Evaluate the answer against the **strong-answer signature**. +- If the answer falls into a named anti-pattern, push back with the **sharper follow-up** — quoting the user's words back at them, NOT paraphrasing. Anti-pattern label names (`vanity`, `fluff`, `feature-list`, etc.) are internal-only — never appear in question bodies. +- **2 rounds maximum.** After round 2, capture the user's words verbatim and append the HTML comment `` to the section body. Do not let the interview spiral. +- Use **free-form responses** — no menu options, no recommendation in the question body. + +**1.3 — Per-section atomic writes** + +After each section is captured, build the partial draft and write to `STRATEGY.md` via `Write` tool **before the next question fires**. `last_updated` bumps on every save. No draft state file. Mid-flow abandonment leaves a partially-populated file readable on disk; resume is via Phase 0 → Phase 2 routing. + +The partial-draft shape: frontmatter + H1 + the captured section(s) + placeholder bodies (`_Not yet captured._`) for unfilled required sections. Optional sections are absent until Phase 1.4. + +**1.4 — Optional sections (gated by routing question)** + +After all 5 required sections land, ask once per optional section whether to include it. **Routing question** with lead-with-recommendation: + +For `Milestones`: + +- `body`: "Do you want a `Milestones` section? It's only worth adding if there are externally visible dated anchors — launches, fundraises, conferences, renewals. Recommended: `skip` — internal schedules don't belong here. Confidence: [your-call]." +- `options`: `include`, `skip`. + +For `Not working on`: + +- `body`: "Do you want a `Not working on` section? Only useful for things the team keeps being tempted by — a clarity tool, not a backlog. Recommended: `skip` — most repos don't need it. Confidence: [your-call]." +- `options`: `include`, `skip`. + +A `Marketing` section is **deliberately not offered** — over-rotated for OSS-tools repos. + +If `include`, run the per-section interview from `references/interview.md` (same 2-round-cap rule), then atomic-write that section. If `skip`, omit the section entirely from the file (do not leave an empty header). + +**1.5 — Mandatory read-back before final commit** + +After all sections captured (required + any included optional), run: + +```bash +"$FLOWCTL" strategy read --json +``` + +Show the final draft body in chat. Offer one round of edits via `AskUserQuestion`: + +- `body`: "Draft complete. -section strategy doc, . Recommended: `commit` — the draft reflects the captured answers verbatim. Confidence: [judgment-call]." +- `options`: `commit`, `edit-section`, `abandon`. + +On `edit-section`, ask which section via single-select (5 required + included optional names), re-run the per-section interview, atomic-write, return to read-back. + +On `commit`, the file is already on disk (per-section atomic writes) — this is a confirmation step, not a new write. Acknowledge with one stdout line: `Strategy doc written to . last_updated: .` + +On `abandon`, leave the file as-is (partially populated is fine), exit 0. + +### Phase 2: Update run (file exists, generator matches) + +**2.1 — Summarize current state** + +Read the existing `STRATEGY.md` via `flowctl strategy read --json` and summarize current state in 3-5 lines so the user sees what's on file. Surface section names + 1-line excerpts. + +If the focus-hint argument names a specific section, jump to that section. Otherwise, fire the routing question. + +**2.2 — Section-revisit routing question (lead-with-recommendation)** + +Build the option list dynamically: + +- For each of the 5 required sections + included optional sections, check the body for `` markers (priority candidates). +- Sections with no marker but visibly weak content (≤1 short sentence, or contains placeholder-shaped text) join the priority list. +- Sections that look strong fall to the bottom. + +`AskUserQuestion`: + +- `body`: "Which section to revisit? . Recommended: `` — it carries a `` marker from a previous run [if applicable]. Confidence: [judgment-call] — your judgment on what feels stale." +- `options`: section names + `done` (no further changes). + +**2.3 — Per-section re-interview** + +For the chosen section, re-run the per-section interview from `references/interview.md` — full pushback, NOT a rubber-stamp. After capture, atomic-write that section's new body. Untouched sections preserved byte-identical (verified by `git diff --unified=0` if questioned). `last_updated` bumps to today's ISO date. + +**2.4 — Loop or exit** + +After a section is updated, return to the routing question — user can revisit another section or pick `done`. On `done`, run the read-back step (Phase 1.5 logic) once for confirmation, then exit. + +### Phase 3: Downstream handoff + +After writing (first-run or update), surface the file's role to the user in one paragraph: + +- If `.flow/epics/` is empty AND `.flow/prospects/` is empty: `Strategy doc written. Next, /flow-next:prospect [optional focus] generates ranked candidate ideas grounded in the strategy you just captured.` +- If `.flow/` is populated: `Strategy doc written. Downstream skills (/flow-next:prospect, /flow-next:plan, /flow-next:interview, /flow-next:capture, /flow-next:sync) will read STRATEGY.md as grounding on next invocation.` + +One paragraph max. No follow-up questions. + +## What this skill does not do + +- Does not update the issue tracker or reconcile in-flight work. Strategy is the doc; execution lives in epics, tasks, and `/flow-next:plan`. +- Does not write product requirements or implementation plans — those are `/flow-next:capture` and `/flow-next:plan`. +- Does not compute metric values. It records *which* metrics matter and where they live, not what they read today. +- Does not create per-subdirectory STRATEGY.md files. Strategy is repo-wide by Rumelt's definition; cascading strategies re-introduce the "is for everyone, is for no one" problem. +- Does not migrate hand-written or CE-format STRATEGY.md files. v1 ships sentinel-based foreign-file refusal; multi-format migration is a v2 problem. +- Does not delete the file when all sections are removed. Last-section deletion leaves a husk (`# Strategy` H1 + frontmatter) on disk — file never deleted (R23 invariant, mirrors `render_glossary_file`). + +## Forbidden + +- **Running under Ralph** — hard-block via the Phase 0.1 guard. +- **Setting `context: fork`** — `AskUserQuestion` must stay reachable across phases. +- **Inline cross-platform tool tables** in prose (multi-platform listings naming the tool primitive on each harness). Canonical files use Claude-native names only; sync-codex.sh handles the Codex rewrite. +- **Lead-with-recommendation on substance questions** — problem / approach / persona / metrics / tracks get free-form, no recommendation, no menu. Recommendation primes the user out of their own language. Routing questions only. +- **Leaking anti-pattern names** to the user. `vanity` / `fluff` / `feature-list` / `goal-stated-as-problem` are internal labels for formulating sharper follow-ups. +- **Auto-overwriting a foreign-file STRATEGY.md** — Phase 0.4 always asks. v1's stance is refusal; user can rename or delete to bootstrap. +- **Writing more than 4 sentences per section** (except Tracks, where each track has its own short block). The post-write checklist in `references/strategy-template.md` catches this. +- **Adding sections beyond the locked 5 + 2 optional**. CE's `Marketing` section is dropped on purpose; do not re-introduce it. Section order is locked. +- **Inventing flowctl subcommands** — Task 1 ships `flowctl strategy {status,read,list}` only. Skill writes the file directly via `Write` tool; no `flowctl strategy add` exists. + +## Output rules + +The deliverable is the written `STRATEGY.md` itself. Surface to chat: + +- One-line path announcement at Phase 0 (walk-up subdir or file state). +- Per-section interview Q&A (the agent's questions; user's answers). +- Final draft read-back in Phase 1.5 / 2.4. +- One-paragraph downstream handoff at Phase 3. + +No internal summary printed at exit beyond the Phase 3 handoff line. The file IS the report. diff --git a/plugins/flow-next/skills/flow-next-strategy/references/interview.md b/plugins/flow-next/skills/flow-next-strategy/references/interview.md new file mode 100644 index 00000000..279dc41e --- /dev/null +++ b/plugins/flow-next/skills/flow-next-strategy/references/interview.md @@ -0,0 +1,151 @@ +# Strategy Interview + +Loaded by `SKILL.md` at the start of Phase 1 and revisited per-section in Phase 2. Every section below maps one-to-one to a section in `strategy-template.md`. + +For each section: ask the opening question, evaluate the answer against the quality bar, push back when it falls into a named anti-pattern, and capture the final answer in the user's own language. + +## Overall Rules + +1. **Ask, don't prescribe.** Do not offer menu options for open answers (problem, approach, persona, metrics, tracks). Use free-form responses. Reserve single-select for routing decisions only (which section to revisit, include this optional section, foreign-file resolution). +2. **Push back once, maybe twice.** If the first answer is weak, name the specific issue and ask a sharper question. If the second answer is still weak, capture what the user has given and append the HTML comment `` to the section body. Do not let the interview spiral. +3. **Quote the user back at them.** When challenging an answer, use the user's own words verbatim. Paraphrasing softens the challenge and is easier to dismiss. +4. **Keep each answer to 1-3 sentences.** Longer answers are usually hiding something vague. If the user writes a paragraph, ask them to pick the sentence that matters most. +5. **Don't leak the anti-pattern names.** The user does not need to hear "that's a vanity metric" or "that's fluff" — just ask the sharper question that follows. Names like `vanity`, `fluff`, `feature-list-as-track`, `goal-stated-as-problem` are internal labels for the agent's diagnosis, not vocabulary for the conversation. + +--- + +## 1. Target Problem + +**Opening question:** "What's the core problem this product solves — and what makes that problem hard?" + +Strong answers name a specific situation the target user is in, identify what makes the situation hard *right now* (a crux, a constraint, something that isn't easy to route around), and are falsifiable — you could imagine the problem being absent and know the difference. + +**Anti-patterns and pushback:** + +- **goal-stated-as-problem** ("the problem is we need to grow revenue") → "That's a goal, not a problem. What's in the world that's making that goal hard to achieve? Whose situation are you changing?" +- **vague-wish** ("people need better tools for X") → "Whose situation specifically? Doing what? What do they try today, and why doesn't it work?" +- **symptom-not-cause** ("users churn after 30 days") → "That's a symptom. What's happening in their world that makes them stop caring? What's the underlying condition?" +- **too-broad** ("communication at work is broken") → "That's a civilization-scale problem. Narrow it to a situation you can actually affect — which users, doing what, when does it hurt most?" +- **feature-shaped** ("there's no good way to do [specific workflow] with AI") → "That's a missing feature, not the underlying problem. What outcome do users want that the feature would give them?" + +**Capture:** One or two sentences naming the user's situation and the crux. No solution language. + +--- + +## 2. Our Approach + +**Opening question:** "Given that problem, what's your approach — the commitment or principle that makes it tractable?" + +This is the guiding choice: how the product competes or operates, so that many downstream decisions become easier. It is not the product and it is not a feature list. + +Strong answers are a choice (implying alternatives explicitly *not* pursued), are general enough to direct many decisions but specific enough to rule things out, and sound more like "we win by [doing X differently]" than "we do [a list of things]". + +**Anti-patterns and pushback:** + +- **fluff** ("we're customer-obsessed and move fast") → "Those are values, not an approach. What are you doing *differently* from the other products users could pick? If the answer applies to any company, it's not your approach." +- **feature-list** ("we're building AI-powered X, Y, and Z") → "That's a feature list. What's the underlying bet that makes you pick those features over others? What principle is guiding what you ship?" +- **product-description-as-approach** ("we use AI to draft replies") → "That's what the product does, but what's the *choice* inside it? Every competitor will say the same thing. Your approach should name what you're doing that the obvious alternative isn't — is it a grounding choice, a trust-building commitment, a workflow bet? What are you betting on that they're not?" +- **goal-restated** ("our approach is to be the market leader") → "That's still the goal. How does the product win? What choice are you making that competitors aren't?" +- **multiple-approaches-at-once** ("we're going deep on enterprise, self-serve, and a consumer app") → "Pick one as the guiding approach. The others may still get work, but one of them organizes the rest. Which is it?" +- **doesnt-connect-to-problem** (problem: "users can't trust AI output"; approach: "build a fast, beautiful UI") → "How does that approach solve the problem you named? If there's no line between them, one of the two is wrong." + +**Capture:** One or two sentences. Ideally ends with or implies "...so that [outcome tied to the problem]". + +--- + +## 3. Who It's For + +**Opening question:** "Who is the primary user, and what job are they hiring this product to do?" + +Jobs-to-be-done framing — the user isn't a demographic, they're someone in a situation trying to make progress. + +Strong answers name one primary persona (additional personas allowed but secondary), identify them by role or situation rather than demographic, and state a concrete job as a verb phrase. + +**Anti-patterns and pushback:** + +- **too-many-primary-personas** ("it's for founders, PMs, engineers, and designers") → "If it's for everyone, it's for no one. Who matters most? The others can still benefit, but one of them drives the product decisions." +- **demographic-framing** ("25-45 year old professionals") → "That's a demographic, not a user. What are they trying to do that makes them pick up this product?" +- **role-without-situation** ("PMs") → "PMs doing what? Running a roadmap review? Writing a spec at midnight? Convincing a skeptical eng lead? The situation is where the product matters." +- **generic-job** ("they want to be more productive") → "Productive at what specifically? They're hiring this product to do *what*? The more specific, the better the product decisions downstream." + +**Capture:** Persona name plus JTBD sentence. Example: "Solo founders running their own roadmap. They're hiring the product to keep strategy and execution aligned without a PM on staff." + +--- + +## 4. Key Metrics + +**Opening question:** "What 3-5 metrics will tell you whether the approach is working?" + +Metrics are the feedback loop. Bad metrics create the illusion of progress while the product gets worse. + +Strong answers stay at 3-5 (not 10), mix leading and lagging (something that moves weekly and something that moves quarterly), and could plausibly regress if the product got worse. + +**Anti-patterns and pushback:** + +- **vanity** ("total signups, total pageviews, cumulative users") → "Those can all go up while the product gets worse. What moves when users actually get value?" +- **too-many** ("here are 12 metrics we watch") → "A dashboard isn't a strategy. Pick the 3-5 you'd stake the quarter on. What are the others telling you that those don't?" +- **outputs-not-outcomes** ("ship velocity, deploys per week") → "Those measure the team, not the product. If the team doubled velocity but users didn't care, would you call it a win?" +- **can-only-go-up** ("cumulative hours saved") → "A metric that can only go up doesn't tell you much. What's the rate, the ratio, or the thing that can regress?" +- **unmeasurable** ("user delight") → "How specifically? If you can't define how you'd check it on a Tuesday, it's aspirational, not a metric." + +**Capture:** A list of 3-5. Each with a one-line definition. Note where each is measured (analytics, DB, qualitative, etc.) if known. If measurement is undefined, ask: "Where does this metric live today? If nowhere, is this something you can start measuring?" + +--- + +## 5. Tracks + +**Opening question:** "What are the 2-4 tracks of work you're investing in to execute the approach?" + +Tracks are the coherent-actions half of the strategy kernel — concrete areas of investment that flow from the approach. They are not feature lists and not personal todo items. Each track is a named *domain of work*. + +Strong answers stay at 2-4 (not 8, not 1), connect clearly back to the approach, and are broad enough that multiple features live inside each one. + +**Anti-patterns and pushback:** + +- **feature-list-as-track** ("track 1: Slack integration; track 2: mobile app; track 3: dark mode") → "Those are features. What's the *investment area* each one lives inside? 'Integrations' might be one track, with Slack, Teams, and Discord as candidates inside it." +- **too-many-tracks** ("we have 7 tracks this quarter") → "With 7 tracks, every track is starved for attention. Which 3 are load-bearing? The others either fold in or drop." +- **doesnt-connect-to-approach** (approach: "win by being the easiest to onboard"; track: "enterprise SSO") → "How does that track serve the approach? If it's a separate bet, name it as one. If it's load-bearing for onboarding, explain the link." +- **too-vague** ("improve the product") → "Every track is 'improve the product.' What's the specific investment area that's different from the others?" +- **one-track-only** → "With one track, there's no real choice being made. What are the 2-3 things the product needs to be good at, and how are they different?" + +**Capture:** 2-4 tracks. For each: a name, a one-line purpose, and a short note on why this serves the approach. + +--- + +## 6. Milestones (optional) + +**Opening question:** "Are there any dated milestones worth anchoring — a launch, a fundraise, a conference, a renewal? Skip if none apply." + +Only capture externally visible, real milestones. Avoid turning this into an internal schedule. + +Default is to skip. Do not push the user to invent milestones. If they name some, capture them verbatim with dates. + +**Anti-patterns and pushback:** + +- **internal-schedule-disguised** ("v2.3 ships March 15, refactor done by April") → "Those are sprint dates, not milestones. Is there an *externally visible* anchor — something users or stakeholders see? If not, skip the section." +- **manufactured-milestones** (user lists half-real dates to fill the section) → "If they're not real anchors, the section is hurting more than helping. Skip it; that's the right call most of the time." + +**Capture:** Verbatim dates + one-line description per milestone. Do not encourage a long list. + +--- + +## 7. Not Working On (optional) + +**Opening question:** "Is there anything you've explicitly decided *not* to do right now that's worth naming? This is for things the team keeps being tempted by." + +Clarity tool, not a blocker list. Skip by default. If the user names items, one sentence each. Do not encourage a long list. + +**Anti-patterns and pushback:** + +- **catalog-of-rejections** (long list of things never seriously considered) → "This section is for things the team keeps being tempted by. Stuff you never considered isn't useful here. Pick the 1-2 that actually pull at you." +- **competitor-bashing** ("we're not building a clone of ") → "That's not a strategic boundary, it's a swipe. What's the *type of work* you're declining? E.g. 'enterprise SSO before product-market-fit' or 'mobile app before web is solid'." + +**Capture:** 1-3 items max. One sentence each. + +--- + +## After the Interview + +Once sections 1-5 are captured (and any optional sections the user engaged with), the per-section atomic writes have already landed each section on disk. Run `flowctl strategy read --json` to get the current state, present the full draft in chat for read-back, offer one round of edits, then confirm. + +The post-write checklist (in `strategy-template.md` §Post-write checklist) is the agent's own scan-pass before showing the read-back — catch placeholder leftovers, sentence-count violations, missing-frontmatter cases, metric/track count out of range, and Target-problem ↔ Our-approach disconnect. diff --git a/plugins/flow-next/skills/flow-next-strategy/references/strategy-template.md b/plugins/flow-next/skills/flow-next-strategy/references/strategy-template.md new file mode 100644 index 00000000..e7cf6273 --- /dev/null +++ b/plugins/flow-next/skills/flow-next-strategy/references/strategy-template.md @@ -0,0 +1,86 @@ +# Strategy Template + +Loaded by `SKILL.md` after each section is captured. Fill it in using the captured answers and write to `STRATEGY.md` via the `Write` tool. Per-section atomic writes mean each section lands on disk before the next interview prompt fires. + +## Rules for filling in + +- Use the user's own language where possible. Do not paraphrase into generic PM-speak. +- Each section stays compact. The whole doc should read in under 5 minutes. +- Section order is locked. Do not add new top-level sections (a `Marketing` section was considered and deliberately excluded — over-rotated for OSS-tools repos). +- Optional sections: delete entirely if unused. Do not leave empty headers. +- Set `last_updated` in the YAML frontmatter to today's ISO date (`YYYY-MM-DD`). Do not duplicate the date in prose. +- Set `name` in the frontmatter to the product or initiative name (the same value used in the H1 title). +- Set `generator: flow-next-strategy` in the frontmatter (foreign-file detection sentinel — required). +- After 2 rounds of pushback that didn't land, capture the user's words verbatim and append `` to that section's body. Phase 2 surfaces these markers as priority candidates on re-run. + +## Template + +The block below is the literal file shape (minus this line and the fences). Replace every `{{placeholder}}` with the captured answer. Delete any optional section whose placeholder wasn't answered. + +~~~markdown +--- +name: {{product_name}} +last_updated: {{YYYY-MM-DD}} +generator: flow-next-strategy +--- + +# {{product_name}} Strategy + +## Target problem + +{{1-2 sentence diagnosis. Names the user situation and the crux that makes it hard. No solution language.}} + +## Our approach + +{{1-2 sentence guiding policy. What this product commits to, so that the target problem becomes tractable.}} + +## Who it's for + +**Primary:** {{Persona name}} — {{one-sentence JTBD, e.g. "They're hiring {{product_name}} to..."}} + + + +## Key metrics + +- **{{metric 1 name}}** — {{one-line definition; where it's measured}} +- **{{metric 2 name}}** — {{...}} +- **{{metric 3 name}}** — {{...}} + + + +## Tracks + +### {{Track 1 name}} + +{{One line: what this track is — the investment area, not a feature list.}} + +_Why it serves the approach:_ {{one line}} + + + +## Milestones + +- **{{YYYY-MM-DD}}** — {{milestone}} + + + +## Not working on + +- {{one line per item}} + + +~~~ + +## Post-write checklist + +Before showing the read-back to the user, scan the draft for: + +- [ ] Frontmatter present at the top with `name`, `last_updated`, and `generator: flow-next-strategy` keys. +- [ ] `last_updated` carries today's date in ISO format (YYYY-MM-DD). +- [ ] No section has more than 4 sentences except `Tracks` (where each track has its own short block). +- [ ] No placeholders remain (`{{...}}`). +- [ ] Optional sections with no content have been deleted, not left empty (the H2 header is gone — file does not contain `## Milestones` if it has no items). +- [ ] Metric count is between 3 and 5. Track count is between 2 and 4. +- [ ] `Target problem` and `Our approach` are connected — one clearly responds to the other. +- [ ] `` markers (if any) are inside the section body, after the captured prose — not in the header line. +- [ ] H1 title matches the frontmatter `name` value. diff --git a/plugins/flow-next/skills/flow-next-sync/SKILL.md b/plugins/flow-next/skills/flow-next-sync/SKILL.md index 5a97c201..dce6cf9f 100644 --- a/plugins/flow-next/skills/flow-next-sync/SKILL.md +++ b/plugins/flow-next/skills/flow-next-sync/SKILL.md @@ -94,20 +94,27 @@ No downstream tasks to sync (all done or none exist). ``` Stop here (success, nothing to do). -### Step 5: Gather glossary + decisions context +### Step 5: Gather glossary + decisions + strategy context -Two extra context types help the agent catch drift the spec text alone can't reveal: project-glossary terms (renames where the old spec used a term whose `_Avoid_` alias now appears in code) and active decision constraints (current code may touch files mentioned in a decision's `Consequences` section). +Three extra context types help the agent catch drift the spec text alone can't reveal: project-glossary terms (renames where the old spec used a term whose `_Avoid_` alias now appears in code), active decision constraints (current code may touch files mentioned in a decision's `Consequences` section), and strategic-intent drift (completed task contradicts an active `STRATEGY.md` track or approach). ```bash GLOSSARY_JSON="$("$FLOWCTL" glossary list --json 2>/dev/null \ || echo '{"groups":[],"file_count":0,"total_terms":0}')" DECISIONS_JSON="$("$FLOWCTL" memory list --track knowledge --category decisions --json 2>/dev/null \ || echo '{"entries":[],"legacy":[],"count":0,"status":"active"}')" +STRATEGY_CONTENT="$("$FLOWCTL" strategy read --json 2>/dev/null || echo '{}')" ``` -Both calls are best-effort — empty defaults keep the agent prompt valid when flowctl returns nothing or fails. +All three calls are best-effort — empty defaults keep the agent prompt valid when flowctl returns nothing or fails. -When `GLOSSARY_JSON` reports `file_count == 0` AND `DECISIONS_JSON` reports `count == 0`, skip the extra context (pass the empty defaults — the agent treats them as a no-op signal). +**Husk short-circuit** — when ALL three of the following hold, skip the extra context entirely (pass the empty defaults; the agent's husk short-circuit at the top of Phase 3b will skip the whole section): + +- `GLOSSARY_JSON.total_terms == 0` (glossary missing or husk) +- `DECISIONS_JSON.count == 0` (no decision entries) +- `STRATEGY_CONTENT.sections_filled == 0` OR `STRATEGY_CONTENT == {}` (no STRATEGY.md or husk — verify with `flowctl strategy status --json | jq '.sections_filled // 0'`) + +When ANY of the three has signal, pass through all three (untouched) and let the agent run the matching subsection (3b.1 / 3b.2 / 3b.3) and skip the empty ones. When `GLOSSARY_JSON.total_terms == 0` but `file_count > 0`, every group is a husk. Husks carry no signal for drift detection — pass the JSON through untouched and let the agent skip them. @@ -126,6 +133,7 @@ DRY_RUN: GLOSSARY_JSON: DECISIONS_JSON: +STRATEGY_CONTENT: DRY RUN MODE: Report what would change but do NOT use Edit tool. Only analyze and report drift. diff --git a/scripts/sync-codex.sh b/scripts/sync-codex.sh index f949ea52..b9216fed 100755 --- a/scripts/sync-codex.sh +++ b/scripts/sync-codex.sh @@ -517,6 +517,7 @@ generate_openai_yaml "flow-next-interview" "Flow Interview" "Deep Q&A to refine generate_openai_yaml "flow-next-setup" "Flow Setup" "Initialize flow-next in current project" "#3B82F6" false generate_openai_yaml "flow-next-prospect" "Flow Prospect" "Generate ranked candidate ideas grounded in the repo" "#3B82F6" false "What should we build next? " generate_openai_yaml "flow-next-capture" "Flow Capture" "Synthesize conversation context into a flow-next epic spec" "#3B82F6" false "Capture this as a spec: " +generate_openai_yaml "flow-next-strategy" "Flow Strategy" "Generate or update repo-root STRATEGY.md (problem, approach, personas, metrics, tracks)" "#3B82F6" false generate_openai_yaml "flow-next-audit" "Flow Audit" "Review .flow/memory/ entries against current code" "#3B82F6" false generate_openai_yaml "flow-next-memory-migrate" "Flow Memory Migrate" "Migrate legacy flat memory files to categorized YAML schema" "#3B82F6" false @@ -541,6 +542,7 @@ REQUIRED_OPENAI_YAML_SKILLS=( "flow-next-setup" "flow-next-prospect" "flow-next-capture" + "flow-next-strategy" "flow-next-audit" "flow-next-memory-migrate" "flow-next-impl-review" @@ -792,6 +794,18 @@ else echo -e " ${GREEN}✓${NC} No R4 meta-file refs in Codex mirror" fi +# R19 mirror scan — strategy-doc fluff guard for the Codex mirror (fn-39 task 5). +# Tier 1 jargon only — Rumelt's "fluff" hallmarks. Scope is the Codex mirror +# of the strategy skill; references/interview.md is excluded (must describe +# anti-patterns to push back on them — same exemption as the canonical guard). +fluff_refs=$( { grep -rEi '\bsynergy\b|\bpivot\b|\bdisrupt\b|thought[ -]leadership|best-in-class|world-class|\b10x\b' "$CODEX_DIR/skills/flow-next-strategy/" 2>/dev/null || true; } | { grep -v '/references/interview\.md' || true; } | wc -l | tr -d ' ') +if [ "$fluff_refs" != "0" ]; then + echo -e " ${RED}✗${NC} $fluff_refs R19 strategy-doc fluff refs in codex mirror — clean canonical first, then re-run sync" + errors=$((errors + 1)) +else + echo -e " ${GREEN}✓${NC} No R19 strategy-doc fluff in Codex mirror" +fi + # Validate openai.yaml files — every skill in REQUIRED_OPENAI_YAML_SKILLS # MUST have one. Missing entries fail CI. Extras are fine (utility skills # may opt in later).