flow-next-prospect · diff
git:20260721.b4cb47b to git:20260724.e764b0b
0 added, 55 removed. Audit A to A.
---
name: flow-next-prospect
description: Generate ranked candidate ideas grounded in the repo, upstream of /flow-next:plan. Triggers on /flow-next:prospect with an optional focus hint (concept, path, constraint, or volume).
user-invocable: false
allowed-tools: AskUserQuestion, Read, Bash, Grep, Glob, Write, Edit, Task
---
# Prospect — upstream-of-plan idea generation
**Read [workflow.md](workflow.md) for full phase-by-phase execution.**
Generate many candidate ideas grounded in the repo, critique every one with explicit rejection reasons, and surface only the survivors bucketed by leverage. Output is a ranked artifact under `.flow/prospects/<slug>-<date>.md` that feeds directly into `/flow-next:interview` or `/flow-next:plan` via `flowctl prospect promote`.
**Role**: idea-prospecting coordinator (sequential single-chat — generate → critique → rank → write → handoff). Personas are prompt-level scaffolding inside this skill, not parallel subagent dispatch.
## Preamble
**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Define once; subsequent blocks (here and in `workflow.md`) use `$FLOWCTL`:
```bash
FLOWCTL="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/flowctl"
[ -x "$FLOWCTL" ] || FLOWCTL=".flow/bin/flowctl"
```
**Inline skill (no `context: fork`)** — keeps `AskUserQuestion` available throughout. Subagents can't call blocking question tools (Claude Code issues #12890, #34592), and Phases 0 + 6 both require user choice. (sync-codex.sh rewrites this to a plain-text numbered prompt in the Codex mirror.)
## Input
Arguments: `$ARGUMENTS`
Format: `[focus hint]` — freeform single string. Optional. May be:
- **Concept** — `DX improvements`, `review-skill polish`, `test-suite health`
- **Path** — `plugins/flow-next/skills/` (ideate inside a subtree)
- **Constraint** — `quick wins under 200 LOC`, `minor-bump only`, `no new deps`
- **Volume hint** — `top 3` (exactly 3 survivors), `50 ideas` (generate ≥50), `raise the bar` (60-70% rejection target)
If empty, the skill picks its own coverage targets (15-25 candidates → 5-8 survivors).
## Ralph-block (R8)
`/flow-next:prospect` is exploratory and human-in-the-loop. Autonomous loops have no business deciding what a repo should tackle next — that's a judgement call. Hard-error with exit 2 when running under Ralph.
```bash
if [[ -n "${REVIEW_RECEIPT_PATH:-}" || "${FLOW_RALPH:-}" == "1" ]]; then
echo "Error: /flow-next:prospect requires a user at the terminal; not compatible with Ralph mode (REVIEW_RECEIPT_PATH or FLOW_RALPH detected)." >&2
exit 2
fi
```
No env-var opt-in. Ralph never decides direction.
## Workflow
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 specs, 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 | 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/<slug>-<date>.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.
Phases 0-6 are implemented. Promote command + list/read/archive land in tasks 4-5.
- ## Pre-check: Local setup version
-
- Compare `.flow/meta.json` `setup_version` to the plugin version; on mismatch, escalate once per plugin version. Fail-open throughout: a missing `jq`, `.flow/meta.json`, or plugin manifest silently continues.
-
- ```bash
- SETUP_MODE=$(jq -r '.setup_mode // empty' .flow/meta.json 2>/dev/null)
- 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"
- PLUGIN_VER=$(jq -r '.version' "$PLUGIN_JSON" 2>/dev/null || echo "unknown")
- VERSION_ACK=$(jq -r '.version_ack // empty' .flow/meta.json 2>/dev/null)
- if [[ "$SETUP_MODE" == "plugin" ]]; then
- # fn-121 plugin mode: no local copies exist to go stale - the version compare is
- # moot. Check only the CLAUDE.md snippet contract (sentinel vs the plugin's
- # expected v1; keep the literal in sync with SNIPPET_SCHEMA_VERSION in flowctl.py).
- SNIP_ACK=$(jq -r '.snippet_ack // empty' .flow/meta.json 2>/dev/null)
- SNIP_VER=$(grep -m1 -o 'flow-next:snippet:v[0-9]*' CLAUDE.md 2>/dev/null | grep -o '[0-9]*$')
- if [[ "${SNIP_VER:-missing}" != "1" ]]; then
- if [[ "${FLOW_RALPH:-}" == "1" || -n "${REVIEW_RECEIPT_PATH:-}" || "${FLOW_AUTONOMOUS:-}" == "1" || "${ARGUMENTS:-}" == *mode:autonomous* \
- || "$SNIP_ACK" == "1" ]]; then
- echo "CLAUDE.md flow-next snippet contract v${SNIP_VER:-missing} != plugin v1. Refresh via /flow-next:setup or the interactive ask." >&2
- else
- echo "FLOW_SNIPPET_ASK ${SNIP_VER:-missing} 1"
- fi
- fi
- elif [[ -n "$SETUP_VER" && "$PLUGIN_VER" != "unknown" && "$SETUP_VER" != "$PLUGIN_VER" ]]; then
- if [[ "${FLOW_RALPH:-}" == "1" || -n "${REVIEW_RECEIPT_PATH:-}" \
- || "${FLOW_AUTONOMOUS:-}" == "1" || "${ARGUMENTS:-}" == *mode:autonomous* \
- || "$VERSION_ACK" == "$PLUGIN_VER" ]]; then
- echo "Local setup v${SETUP_VER} differs from plugin v${PLUGIN_VER}. Run /flow-next:setup to refresh local scripts." >&2
- else
- echo "FLOW_SETUP_ASK ${SETUP_VER} ${PLUGIN_VER}"
- fi
- fi
- ```
-
- If the block printed a `FLOW_SNIPPET_ASK` line (plugin mode only; suppressed to the stderr note under the autonomy markers above), before proceeding ask the user with AskUserQuestion (the CLAUDE.md flow-next snippet block is on an older contract than this plugin version; refresh the marker block?), offering exactly the options **Refresh now**, **Remind me next version**, **Skip this run**, then continue the skill whichever is chosen:
- - **Refresh now**: run `"${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/flowctl" setup-block apply --file CLAUDE.md --template "${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/skills/flow-next-setup/templates/claude-md-snippet-plugin.md" --json`; if it returns `action: ask`, re-run as `setup-block resolve` with the same `--file`/`--template` plus `--choice overwrite --json` - this question WAS the consent. Marker-bounded: content outside the block is never touched.
- - **Remind me next version**: record the acknowledgement so this contract version is not re-asked (fail-open: on any error, continue anyway):
- ```bash
- rm -f .flow/meta.json.tmp && jq '.snippet_ack = "1"' .flow/meta.json > .flow/meta.json.tmp && mv .flow/meta.json.tmp .flow/meta.json
- ```
- - **Skip this run**: continue without writing anything; the next invocation asks again.
-
- If the block printed a `FLOW_SETUP_ASK` line, before proceeding ask the user with AskUserQuestion (local setup differs from the plugin; refresh now?), offering exactly the options **Refresh now**, **Remind me next version**, **Skip this run**, then continue the skill whichever is chosen:
- - **Refresh now**: pause and have the user run `/flow-next:setup` in this session (do not run setup yourself), then continue once it finishes.
- - **Remind me next version**: record the acknowledgement so this version is not re-asked (only a later plugin version re-arms it), then continue. Run this self-contained write (fail-open: on any error, continue anyway):
- ```bash
- PJ="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/.claude-plugin/plugin.json"
- PV=$(jq -r '.version' "$PJ" 2>/dev/null)
- [[ -n "$PV" && "$PV" != "null" ]] && rm -f .flow/meta.json.tmp && jq --arg v "$PV" '.version_ack = $v' .flow/meta.json > .flow/meta.json.tmp && mv .flow/meta.json.tmp .flow/meta.json
- ```
- - **Skip this run**: continue without writing anything; the next invocation asks again.
-
- Any other output (the one-line differs notice, or nothing) is non-blocking: continue.
-
## Forbidden
- Running under Ralph — hard-block via the guard above.
- Setting `context: fork` — blocking question tools must stay reachable.
- Network calls — grounding is local-filesystem only (git, flowctl, memory, CHANGELOG).
- Writing to `.flow/specs/` directly — only `flowctl prospect promote` may do that.
- Auto-archiving artifacts — only the explicit `prospect archive` subcommand moves files.
- Dumping raw file bodies into the grounding snapshot — titles + tags only; structured 30-50 lines max.