Imported from garygentry/feature-forge (
adapters/pi/skills/forge-2-tech/SKILL.md). Install upstream withnpx skills add garygentry/feature-forge --skill forge-2-tech. Copyright stays with the author.
forge-2-tech — Technical Specification Driver
Create a thorough technical specification by interviewing the user about technology decisions, grounded in PRD requirements.
Prerequisites
Read and follow references/shared-conventions.md for feature name validation, configuration reading, and force mode handling before proceeding.
Step 1: Validate Prerequisites
Resolve the feature directory first via the Feature Directory Resolution block in references/shared-conventions.md, setting {resolvedFeatureDir}.
Prerequisite check: Read {resolvedFeatureDir}/.pipeline-state.json. If not in force mode and forge-1-prd is not complete, STOP and tell the user: "The PRD for '{feature}' isn't complete yet. Run /skill:forge-1-prd {feature} first."
Carried-over note check. If that state's top-level notes is a non-empty string, surface it verbatim before proceeding and treat it as input to this stage — it was persisted for exactly this cross-session handoff (often at the previous stage's exit). It never overrides the PRD or config; raise any conflict instead of silently following either side.
After the prerequisite check, invoke the Stage-Entry Guard block in references/shared-conventions.md with {stage} = forge-2-tech — it detects an interrupted or already-complete tech-spec, runs the resume/restart or new-version gate, and stamps status: "in-progress" + startedAt + currentStage before the research and interview.
Read {resolvedFeatureDir}/PRD.md into context. This is your foundation — every technology decision must trace back to a PRD requirement.
After reading the PRD, invoke the Epic Context Injection block in references/shared-conventions.md. It self-gates on the resolved feature's epic back-pointer: for a standalone feature it is a no-op; for an epic member it loads EPIC.md, this feature's charter, and the completed direct dependencies' specs into context before the research and interview.
Step 2: Examine Existing Context
Before interviewing, you need to understand the existing codebase. This involves reading many files across the project, which consumes context.
Recommended: Delegate to forge-researcher Subagent
Spawn the forge-researcher subagent via the host's subagent mechanism to scan the codebase. Pass a prompt like: "Research the codebase for planning the {feature} feature. Focus on integration points, established patterns, and relevant packages."
If this feature belongs to an epic, also add to the dispatch prompt: "If this feature belongs to an epic, also account for these epic contracts: {paste this feature's consumes and the exposes of its direct deps}, and the completed dependency tech-specs at {paths}. Do not re-research transitive deps." This threads epic context into the researcher without changing the agent's behavior.
The researcher runs in its own context window, reads the project structure, and returns a concise integration report. This keeps your main conversation context clean for the interactive interview.
Single vs. parallel research. For a small or well-understood codebase, one
researcher is the right default. For a large codebase or uncertain scope (many
packages, several integration surfaces, monorepo), dispatch multiple forge-researcher
subagents in parallel — a single message with multiple subagent calls (the
superpowers:dispatching-parallel-agents pattern), each scoped to a disjoint focus
so they don't re-read the same ground:
- one on project structure & conventions (layout, build, naming, error/test patterns),
- one per major integration area / subsystem the feature touches (its exports, types, public API),
- optionally one on existing feature specs & in-progress conflicts.
Each returns its own report; you merge them into a single integration picture for the
interview. This cuts latency and deepens coverage versus one researcher sweeping
everything serially. No agent change is needed — forge-researcher already returns a
self-contained report; just give each instance a narrower focus.
If the forge-researcher subagent is not available, perform the research inline (steps below).
Manual Research (fallback)
- Read the PRD thoroughly: Understand all requirements and constraints
- Check for project-level stack decisions: Look for a project stack-decisions file, first existing path wins:
.feature-forge/stack-decisions.md(preferred), then.agents/references/stack-decisions.md, then.claude/references/stack-decisions.md(legacy alias). If present, read it — these are established technology choices that should be respected unless there's a strong reason to deviate. - Read the plugin's default stack reference: Read
references/stack-discovery-checklist.mdfor general stack context (only if no project-level override exists) - Examine the existing codebase: Look at
package.jsonfiles, existing packages, directory structure, and established patterns. Understand what conventions are already in place. - Review other features' tech specs: Check
{specsDir}/*/tech-spec.mdand{specsDir}/*/*/tech-spec.md(depth-2, to find nested epic members) for consistency in approach and to identify shared infrastructure. Apply the feature-shaped-dir bound: only treat a directory as a feature if it directly contains a.pipeline-state.json(filter matches whose parent directory holds one, or enumerate members via the helper). A flat-only tree has no depth-2 feature dirs, so this gains no new matches there (REQ-COMPAT-01). - Identify integration points: For each existing package that this feature touches, read its exports, types, and public API. Document these as constraints.
Stack Detection and Persistence
After researching the codebase, identify the primary stack (language, build tool, package manager, framework). Read references/stack-resolution.md for the full resolution protocol.
- Check if
forge.config.jsonalready has astackfield — if so, use it - Otherwise, detect from project files and use
AskUserQuestionto confirm: "I detected this as a {stack} project. Correct?" - Update
forge.config.jsonwithstack,typeCheckCommand, andtestCommand. If the feature has a runtime entrypoint (HTTP server, CLI, worker, or a library with a bootstrap contract), also offer to setsmokeCommand— an end-to-end command that boots the wired app and drives one happy-path request (exit 0 = pass). It is distinct fromtestCommand(unit tests, which may self-bootstrap) and powers impl-verify's runnability check; leave itnullfor a pure library with no runnable surface. - Verify that a matching stack profile exists at
references/stacks/{stack}.md. If it does, load it for stack-specific guidance during this and all subsequent stages. If no profile exists, inform the user: "No dedicated profile for {stack}. Using generic fallback — spec conventions, verification checks, and examples will be language-neutral. Consider creating a project-level override at.feature-forge/stack-decisions.md." Then loadreferences/stacks/_generic.md.
Step 3: Conduct the Interview
Interview the user about technology decisions. Unlike the PRD interview, here you SHOULD discuss specific technologies, libraries, patterns, and architecture.
Interview Approach
Turn structure: Output your research findings, analysis, or technical proposals as regular text. Then use AskUserQuestion for the actual questions. NEVER put questions in your text output — they MUST go through AskUserQuestion. At rung 2/3, follow the Interaction Capability Ladder.
Pacing: Present 1-2 decision areas per AskUserQuestion call and STOP to wait for the user's response before continuing. After receiving answers, probe deeper on anything incomplete before moving to the next topic. Signal progress in your text before the next question batch. Do NOT dump all decision areas in a single message — the interview is a conversation, not a document.
First message pattern: Output the research summary as text, then use AskUserQuestion to confirm the stack and ask about the first decision area (typically package/module structure). Wait for the user to respond before proceeding to subsequent areas.
Question strategies (use these as content for AskUserQuestion, not as inline prose). Follow the Decision Support protocol in references/shared-conventions.md — this interview is the richest decision surface in the pipeline, so don't just list options; lead with a recommended approach, put the trade-off in each option's description, and give a one-line rationale:
- For each PRD requirement, propose a technical approach with its trade-off and your recommendation, then ask for confirmation or alternatives — don't present competing approaches flatly. You've just researched the codebase; spend that research here.
- Recommend approaches consistent with the established stack, and say why the convention favors it (evidence-backed mode). Where the choice is genuine taste (e.g. folder layout, naming), give a default but flag it as preference.
- Challenge over-engineering: does the feature need this, or is a simpler approach sufficient? Frame the simpler option's trade-off (less flexibility now vs. less to maintain).
- Ask about every integration point and how the feature interacts with existing modules.
- For competing module structures or code-shape choices, use the
AskUserQuestionpreviewfield to show the candidates side-by-side.
Parking lot: If the user raises a concern that belongs to a different pipeline stage (e.g., backlog granularity, documentation format), acknowledge it and persist it immediately, at the moment it is raised — never deferred to stage closure — by running state-note (fenced immediately below) with a concise one-line statement of the concern, then say "Good point — I've noted that for the [specs/backlog/docs stage]. Let's continue with the tech spec." Add --epic "{epic}" to that call when this feature is an epic member — required, per the Pipeline State Protocol in references/shared-conventions.md; omitting it for a member is an error and must never fall back to a same-named flat feature. state-note overwrites the single top-level notes string rather than appending, so when a note already exists, read the current notes value out of the feature's .pipeline-state.json first and pass one combined concise string in a single --note — never edit or round-trip the JSON by hand. This interview-time call is separate from the optional completion note in Step 6 and must not be folded into it. If the call exits 2, surface the Error: line verbatim with the named feature (and epic) and the recovery instruction, and stop claiming the concern was recorded — it was not. The full recipe is single-sourced as Immediate Downstream Note (Parking Lot) in references/shared-conventions.md.
R="$(bash -c 'for d in "${FEATURE_FORGE_ROOT:-}" "$HOME"/.claude/skills/feature-forge "$HOME"/.claude/plugins/cache/*/feature-forge/* "$HOME"/.claude/plugins/*/feature-forge "$HOME"/.agents/skills/feature-forge ./.agents/skills/feature-forge; do [ -x "$d/scripts/forge-root.sh" ] && exec "$d/scripts/forge-root.sh"; done')"
[ -n "$R" ] || { echo "feature-forge: cannot locate plugin root" >&2; exit 1; }
python3 "$R/scripts/forge-session.py" state-note \
--feature "{feature}" --note "<concise downstream concern>" \
--specs-dir "{specsDir}"
Epic-level concern (backflow): The parking lot above is for concerns about a later stage of THIS feature. If instead the design reveals the epic decomposition itself is wrong — a sibling feature must be added, a frozen boundary between features must move, a feature must split, or a dependency edge is wrong — that is an epic-level concern and does not go in notes. It only applies when this feature is an epic member (its .pipeline-state.json has an epic back-pointer); for a standalone feature there is no epic to reconcile. To record one, run state-ecr (fenced below) with --kind (add-feature|redep|move-boundary|split), --target, --rationale, --raised-by forge-2-tech and --blocks-current — it appends the entry to the member state's epicChangeRequests[] array, filling in raisedAt (ISO-8601 UTC) and status: "open" for you. Set blocksCurrent: true when the change alters a contract (exposes/consumes) or dependency edge this feature relies on for its next stage — proceeding to specs would build on a soon-to-change decomposition (this is the point of no cheap return, so bias toward true); false for a peer/downstream change this feature does not consume. When a contract/dep edge is touched and the classification is ambiguous, confirm blocksCurrent with a single AskUserQuestion, defaulting to true. Do not edit epic-manifest.json here — recording is not applying; only /skill:forge-0-epic edit mode mutates the epic. Then acknowledge without blocking and continue the tech spec.
R="$(bash -c 'for d in "${FEATURE_FORGE_ROOT:-}" "$HOME"/.claude/skills/feature-forge "$HOME"/.claude/plugins/cache/*/feature-forge/* "$HOME"/.claude/plugins/*/feature-forge "$HOME"/.agents/skills/feature-forge ./.agents/skills/feature-forge; do [ -x "$d/scripts/forge-root.sh" ] && exec "$d/scripts/forge-root.sh"; done')"
[ -n "$R" ] || { echo "feature-forge: cannot locate plugin root" >&2; exit 1; }
python3 "$R/scripts/forge-session.py" state-ecr \
--feature "{feature}" --epic "{epic}" --kind "<kind>" --target "<target>" \
--rationale "<why>" --raised-by forge-2-tech --blocks-current "<true|false>" \
--specs-dir "{specsDir}"
Key Decision Areas to Cover
Work through these areas across multiple turns, grouping related areas (1-2 per message):
- Package/module structure: Where does this live in the project? What are its exports? (For non-monorepo projects, this becomes module organization — where the code lives, how it's organized, and what its exports are.)
- Data model: What are the key entities, their schemas, and storage approach?
- API design: What endpoints or interfaces does this expose?
- Dependencies: What external and internal packages are needed?
- Patterns: Which established patterns from the codebase apply here?
- Error handling: How are errors surfaced, propagated, and recovered from?
- Testing strategy: Unit, integration, e2e — what approach for this feature?
- Configuration: What's configurable? How is it configured?
- Migration/deployment: Any special rollout considerations?
Requirement Traceability
Every technical decision MUST reference the PRD requirement(s) it addresses. Use the format:
### JWT-based Session Tokens (REQ-AUTH-01, REQ-SEC-02)
Sessions will use signed JWT tokens with...
If you find yourself writing a technical section that doesn't trace to any PRD requirement, STOP and ask: "I'm about to specify X, but I can't find a PRD requirement for it. Should we add one, or is this unnecessary?"
Step 4: Integration Analysis (Required)
Before finalizing the tech spec, this section is MANDATORY:
- List every existing package this feature depends on
- List every existing package that will need to import from this feature
- For each integration point, document:
- Which types or contracts are shared
- How data flows between packages
- Any patterns established by existing code that must be followed
- The EXACT function signatures and import paths verified from source code. If you cannot locate an expected export, note explicitly: "WARNING: Could not locate X export in {module} — verify this exists before implementing."
- Check for potential conflicts with in-progress features (other spec directories)
Step 5: Write the Tech Spec
Write {resolvedFeatureDir}/tech-spec.md with this structure:
# {Feature Name} — Technical Specification
## 1. Overview
Brief technical summary and key architectural decisions.
## 2. Module Structure
Project location, directory layout, public API surface.
## 3. Technical Decisions
### 3.1 {Decision Area} (REQ-XXX-NN)
Decision, rationale, alternatives considered.
## 4. Data Model
Schemas, types, storage approach.
## 5. API Design
Endpoints, interfaces, contracts.
## 6. Integration Points
How this feature connects to existing packages.
## 7. Error Handling
Error types, propagation, recovery.
## 8. Testing Approach
Strategy, tooling, coverage targets.
## 9. Dependencies
External packages, internal packages, version constraints.
## 10. Open Technical Questions
Unresolved technical decisions.
Step 6: Review with User
This is a blocking review — per the Stage Review Gate in references/shared-conventions.md, do not proceed until the user confirms.
Present the complete tech spec. Ask:
- "Does this capture all the technical decisions correctly?"
- "Any patterns from the existing codebase I missed?"
- "Are the integration points complete?"
Use AskUserQuestion to collect this feedback.
Step 7: Update Pipeline State and Commit
Before writing state or running the stage exit, invoke the Stage-Completion Re-check block in references/shared-conventions.md with {stage} = forge-2-tech — a resumed mid-stage continuation must not overwrite a committed tech-spec.md or re-fire a finished exit.
Pipeline state is written by the state-* verbs — see the Pipeline State Protocol in references/shared-conventions.md.
- Record completion by running
state-complete(below) with--version, one--artifactper file this stage produced, and--based-on forge-1-prd=<current forge-1-prd version>. It setsstatus: "complete",completedAt, the version andbasedOnVersions, and applies the downstream staleness cascade deterministically, so no downstream status is set by hand. - Offer a note — don't force one. As a statement (not a blocking question), let the user know they can jot anything worth preserving across sessions and you'll store it in the
notesfield. If they volunteer something, store it viastate-note— it overwrites the singlenotesstring (latest note wins), so fold any still-relevant existing note into the one combined string; otherwise proceed. The next stage's Step 1 reads and surfaces this note. - If
gitCommitAfterStageis true, follow the Git Commit Protocol inreferences/shared-conventions.md: stage files, attempt commit with message"{commitPrefix}({feature}): complete tech-spec v{n}"(markingstages.forge-2-tech.statuscompletewithcommitHash: nullin that commit), then record the artifact-commit hash via the protocol's two-commit follow-up (never--amend) only on success. If commit fails, leave status asin-progress. - Close with the Stage Exit Protocol (single-sourced in
references/stage-exit-protocol.md; do not improvise a "Next steps" list):
The state-complete call for item 1 — and the state-note call only when the user volunteered a note in item 2 — with the portable plugin-root prelude. Add --epic "{epic}" to each call when this feature is an epic member — required, per the Pipeline State Protocol in references/shared-conventions.md:
R="$(bash -c 'for d in "${FEATURE_FORGE_ROOT:-}" "$HOME"/.claude/skills/feature-forge "$HOME"/.claude/plugins/cache/*/feature-forge/* "$HOME"/.claude/plugins/*/feature-forge "$HOME"/.agents/skills/feature-forge ./.agents/skills/feature-forge; do [ -x "$d/scripts/forge-root.sh" ] && exec "$d/scripts/forge-root.sh"; done')"
[ -n "$R" ] || { echo "feature-forge: cannot locate plugin root" >&2; exit 1; }
python3 "$R/scripts/forge-session.py" state-complete \
--feature "{feature}" --stage forge-2-tech --version {n} \
--based-on "forge-1-prd=<n>" --artifact tech-spec.md --specs-dir "{specsDir}"
# ONLY run the next call if the user volunteered a note in item 2 — otherwise stop here.
python3 "$R/scripts/forge-session.py" state-note \
--feature "{feature}" --note "<what the user volunteered>" --specs-dir "{specsDir}"
Determine --verify-capability before running the exit (full rule: references/stage-exit-protocol.md; summary: Verify Capability in references/shared-conventions.md). Pass interactive only when a question mechanism equivalent to AskUserQuestion is available and a clean-room forge-verifier may be dispatched; otherwise pass manual. Dispatch capability means permitted dispatch, not a listed tool — the test is "may I dispatch forge-verifier right now", not "is a dispatch tool in my tool surface". A session that bars unsolicited dispatch while offering a question mechanism is therefore interactive, not manual: the gate's affirmative choice is the user request that authorizes the dispatch. Such a bar is never grounds to skip verification, and never grounds to fence the production successor while verification is unresolved — on the runInStageVerify: true path the emitted verifyGate stays none, so reuse the Standard Verify Gate block for consent with choice 2 omitted, leaving exactly two choices: Verify now (recommended) and Skip for now. The clean-room forge-verifier is dispatched on the affirmative choice, never merely printed for the user to run later; Skip for now is persisted as an explicit skipped before any advancing block. Add --epic "{epic}" to the call below when this feature is an epic member.
Close this stage with the Scripted Stage Exit (contract: references/stage-exit-protocol.md; do not improvise a "Next steps" list). Run:
R="$(bash -c 'for d in "${FEATURE_FORGE_ROOT:-}" "$HOME"/.claude/skills/feature-forge "$HOME"/.claude/plugins/cache/*/feature-forge/* "$HOME"/.claude/plugins/*/feature-forge "$HOME"/.agents/skills/feature-forge ./.agents/skills/feature-forge; do [ -x "$d/scripts/forge-root.sh" ] && exec "$d/scripts/forge-root.sh"; done')"
[ -n "$R" ] || { echo "feature-forge: cannot locate plugin root" >&2; exit 1; }
python3 "$R/scripts/forge-session.py" stage-exit --feature "{feature}" --stage forge-2-tech --specs-dir "{specsDir}" --host pi --verify-capability "{verify-capability}"
Obey the DIRECTIVES it prints, in the consumption order this protocol fixes: surface invalidAutoVerifyKeys and every warnings entry first; runInStageVerify: true → run the in-stage clean-room verify chain now (honoring autoFixEligible, and asking through the Standard Verify Gate first when you may not dispatch unsolicited); verifyGate: "standard" → present the Standard Verify Gate; verifyGate: "manual-print" → print the verifyCommand for the user and do not dispatch inline. Then, and only when terminalOwnedBy is "self", print the NEXT-STEPS block verbatim as your absolute last output — nothing after its sentinel line. A terminalOwnedBy: "outer" payload carries nextSteps: null: return your structured result to the caller and print no terminal block at all.
Gotchas
- Don't duplicate the PRD. The tech spec answers HOW, not WHAT. If you find yourself restating requirements, reference them by ID instead.
- When the user's stack decisions differ from what you'd recommend, document their choice AND note your concern as an "Alternatives Considered" item — don't silently override their preference.
- Integration points are the #1 source of implementation surprises. Spend extra time here. Read the actual code of packages this feature touches, don't just guess at their APIs.
- If the existing codebase has inconsistent patterns (it happens), call it out and ask which pattern should be followed for this feature.
Host execution notes (Pi)
This Pi bundle preserves Claude's AskUserQuestion references because it ships a Pi compatibility extension registering an AskUserQuestion tool. On Pi:
- User input: use
AskUserQuestionfor genuine user decisions. It supports multiple questions, option descriptions, recommended ordering, multi-select, previews, and free-form Other/custom answers. - Non-interactive (
-p/--mode json):AskUserQuestionis stripped from the tool list, so its absence alone cannot tell you the rung — read that from the Interaction Capability Ladder'sinteraction-moderecord. A call attempted anyway fails withError: UI not available (running in non-interactive mode)— never read that as a decline. Take the Interaction Capability Ladder's declared conservative default, state it in your output, and useno-default: abort — <question> requires a human answerfor an interview question with no sane default (references/shared-conventions.md). - Skill dispatch: Pi uses
/skill:<name>commands. If you cannot invoke a skill directly, print the exact/skill:<name> ...command for the user to run. - Subagents: this bundle declares its custom agents (
forge-researcher,forge-spec-writer,forge-verifier) as package agents. If asubagenttool is registered, dispatch one with{ agent: "forge-verifier", task: "..." }, or fan several out concurrently with{ tasks: [{ agent: "forge-spec-writer", task: "..." }, ...] }. If nosubagenttool is available, run that step inline yourself. - Background / monitoring (forge-5-loop): Pi has no built-in background bash, persistent monitor, or push-notification, so do not run the loop runner in the foreground and do not try to arm one. This bundle registers a forge-loop-supervisor extension that IS the "background-execution mechanism" and "monitoring mechanism" Steps 3b–3f refer to. Concretely:
- Launch (Step 3b): call
forge_loop_launchwith the backlog dir (andreview/agent/iterationsas resolved from config). It starts the loop detached — it runs in rauf's server and outlives this session — and returns immediately; you do not build or redirect a command yourself. - Supervise (Steps 3d–3f): the extension then watches the runner's
events.ndjsonfor you. It reports each completed item as one quiet line and wakes this session automatically on needs-human, blocked, stuck, review-failed, error, and completion — so do not arm a monitor, set a continuous tail, send a notification, poll, or foreground-sleep, and do not treat any manual stop as the terminal signal. When completion wakes you, go straight to Step 4 and read the authoritative counts with the status/list command. Useforge_loop_statusto check progress on demand. - Stop / session end:
forge_loop_stopdeliberately stops the runner; use it only when the user wants the loop to actually stop. Ending the Pi session does not stop the loop (it is detached), and the next session reattaches automatically without re-reporting what you already saw.
- Launch (Step 3b): call