| name | flow-next-interview |
| description | Interview user in-depth about a spec, task, or spec file to extract complete implementation details. Use when user wants to flesh out a spec, refine requirements, or clarify a feature before building. Triggers on /flow-next:interview with Flow IDs (fn-1-add-oauth, fn-1-add-oauth.2, or legacy fn-1, fn-1.2, fn-1-xxx, fn-1-xxx.2) or file paths. |
| user-invocable | false |
Flow interview
Conduct an extremely thorough interview about a task/spec and write refined details back.
IMPORTANT: This plugin uses .flow/ for ALL task tracking. Do NOT use markdown TODOs, plan files, TodoWrite, or other tracking methods. All task state must be read and written via flowctl.
Preamble
CRITICAL: flowctl is BUNDLED — NOT installed globally. which flowctl will fail (expected). Define once; subsequent blocks use $FLOWCTL:
FLOWCTL="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/flowctl"
[ -x "$FLOWCTL" ] || FLOWCTL=".flow/bin/flowctl"
Pre-check: Local setup version
If .flow/meta.json exists and has setup_version, compare to plugin version:
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")
if [[ -n "$SETUP_VER" && "$PLUGIN_VER" != "unknown" ]]; then
[[ "$SETUP_VER" = "$PLUGIN_VER" ]] || echo "Plugin updated to v${PLUGIN_VER}. Run /flow-next:setup to refresh local scripts (current: v${SETUP_VER})."
fi
Continue regardless (non-blocking).
Role: technical interviewer, spec refiner
Goal: extract complete implementation details through deep questioning (40+ questions typical)
Input
Full request: $ARGUMENTS
Accepts:
- Flow spec ID
fn-N-slug (e.g., fn-1-add-oauth) or legacy fn-N/fn-N-xxx: Fetch with flowctl show, write back with flowctl spec set-plan
- Flow task ID
fn-N-slug.M (e.g., fn-1-add-oauth.2) or legacy fn-N.M/fn-N-xxx.M: Fetch with flowctl show, write back with flowctl task set-description/set-acceptance
- File path (e.g.,
docs/spec.md): Read file, interview, rewrite file
- Empty: Prompt for target
Examples:
/flow-next:interview fn-1-add-oauth
/flow-next:interview fn-1-add-oauth.3
/flow-next:interview fn-1 (legacy formats fn-1, fn-1-xxx still supported)
/flow-next:interview docs/oauth-spec.md
If empty, ask: "What should I interview you about? Give me a Flow ID (e.g., fn-1-add-oauth) or file path (e.g., docs/spec.md)"
Setup
Parse --scope=business|technical|both (fn-44.1 plumbing)
Token-safe parsing for --scope / --biz / --tech lives in flowctl scope resolve — never re-implement inline. The subcommand strips scope tokens, preserves every other token in order (Flow IDs, paths, --docs, --strategy, ...), and emits the resolved scope. Default scope when no scope flag is passed: technical (1.0.2 backward-compat).
RESOLVED_JSON=$("$FLOWCTL" scope resolve --json --raw "$ARGUMENTS")
SCOPE=$(printf '%s' "$RESOLVED_JSON" | jq -r '.scope')
ARGUMENTS=$(printf '%s' "$RESOLVED_JSON" | jq -r '.remaining_args | join(" ")')
The section-write policy for the resolved scope is computed by flowctl scope write-policy, called BEFORE any markdown edit. It returns which sections the pass MAY write and which it MUST preserve byte-for-byte (per the fn-44 spec Edge Cases merge contract):
WRITE_POLICY=$(echo "$CURRENT_SECTIONS" | "$FLOWCTL" scope write-policy "$SCOPE" --current-sections-json -)
The question-bank path for the resolved scope is resolved by flowctl scope bank, called when loading the question taxonomy:
BANK_PATH=$("$FLOWCTL" scope bank "$SCOPE")
The full pass-aware behavior (loading the resolved bank, per-section writes that honor the policy, technical-pass-reads-business-sections-first) lives in the "Scope-aware pass behavior" section below. The skill MUST call these subcommands rather than re-implementing parse/policy logic inline.
Parse --docs / --no-docs / --strategy / --no-strategy flags
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:
RAW_ARGS="$ARGUMENTS"
DOC_AWARE_FORCE=""
STRATEGY_AWARE_FORCE=""
if [[ "$RAW_ARGS" == *"--no-docs"* ]]; then
DOC_AWARE_FORCE="off"
RAW_ARGS="${RAW_ARGS//--no-docs/}"
elif [[ "$RAW_ARGS" == *"--docs"* ]]; then
DOC_AWARE_FORCE="on"
RAW_ARGS="${RAW_ARGS//--docs/}"
fi
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
STRATEGY_AWARE_FORCE="off"
elif [[ "$DOC_AWARE_FORCE" == "on" ]]; then
STRATEGY_AWARE_FORCE="on"
fi
RAW_ARGS=$(printf "%s" "$RAW_ARGS" | tr -s ' ' | sed 's/^ //;s/ $//')
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.
Flag matrix — doc-aware flags (rows describe glossary / decisions / strategy gates):
| 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 |
--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.
Scope x doc/strategy — the --scope axis is orthogonal to the doc-aware matrix above. Each row of this table is a valid combination:
| Scope | Doc-aware default | Pass behavior |
|---|
--scope=technical (default, also --tech) | autodetect cascade above runs | tech-owned sections (Architecture / API Contracts / Edge Cases / verifiable AC); preserves biz sections byte-for-byte; reads biz sections when populated, silent when absent |
--scope=business (also --biz) | autodetect cascade still runs; doc-awareness does NOT auto-activate from biz pass alone (R26 adds project-docs investigation independently) | biz-owned sections (Goal & Context / Boundaries / outcome AC / ### Motivation); preserves tech sections byte-for-byte; writes placeholder *Pending technical-scope interview pass.* ONLY under EMPTY tech sections |
--scope=both | autodetect cascade runs | runs biz pass first, then tech pass; same merge contract applies in each phase |
R26 project-docs investigation is gated on SCOPE=business (and the biz-pass phase of both) — runs BEFORE drafting the first biz question, regardless of doc-aware autodetect state.
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.
DOC_AWARE=0
if [[ "$DOC_AWARE_FORCE" == "on" ]]; then
DOC_AWARE=1
elif [[ "$DOC_AWARE_FORCE" == "off" ]]; then
DOC_AWARE=0
else
TERMS=$("$FLOWCTL" glossary list --json 2>/dev/null | jq -r '.total_terms // 0')
DECS=$("$FLOWCTL" memory list --track knowledge --category decisions --json 2>/dev/null | jq -r '.entries | length // 0')
if [[ "${TERMS:-0}" -gt 0 || "${DECS:-0}" -gt 0 ]]; then
DOC_AWARE=1
fi
fi
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
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.
Why total_terms > 0 and sections_filled >= 1 rather than [[ -f <file> ]]: 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
Handle-recognition rule (R16): do NOT gate on a hard "must start with fn-" check. Before treating a single-token arg as a file path or freeform, route it through $FLOWCTL show <arg> --json — flowctl's widened resolver (fn-52.10) maps a tracker key (wor-17 / wor-17.M) to its linked spec/task, so a resolvable handle is the existing spec/task, never a new idea. Patterns 1-2 below are the common case; pattern 3 generalizes them to any resolvable handle.
- Flow spec ID pattern: matches
fn-\d+(-[a-z0-9-]+)? (e.g., fn-1-add-oauth, fn-12, fn-2-fix-login-bug)
- Fetch:
$FLOWCTL show <id> --json